diff --git a/README.md b/README.md index fc6ae41ff07..2a0473baa56 100644 --- a/README.md +++ b/README.md @@ -602,60 +602,186 @@ $ ./geoip convert -c config.json ### 查找 IP 或 CIDR 所在类别(`lookup`) +可能的返回结果: + +- 查询的字符串不是有效的 IP 或 CIDR,返回 `false` +- 查询的 IP 或 CIDR 不存在于任何一个类别中,返回 `false` +- 查询的 IP 或 CIDR 存在于某种格式文件的单个类别中: + - 若该格式文件只包含一个类别,返回 `true` + - 若该格式文件包含多个类别,返回匹配的类别名称 +- 查询的 IP 或 CIDR 存在于多个类别中,返回以英文逗号分隔的类别名称,如 `au,cloudflare` + ```bash -# lookup one IP from local file -$ ./geoip lookup -f text -u ./cn.txt -n cn 1.0.1.1 -cn +# ================= One-time Mode ================= # +# 从 text 格式的本地文件(只包含一个类别)中查找某个 IP 地址 +# lookup IP from local file (with only one list) in text format +$ ./geoip lookup -f text -u ./cn.txt 1.0.1.1 +true -# lookup one CIDR from local file -$ ./geoip lookup -f text -u ./cn.txt -n cn 1.0.1.1/24 -cn +# 从 text 格式的本地文件(只包含一个类别)中查找某个 IP 地址 +# lookup IP from local file (with only one list) in text format +$ ./geoip lookup -f text -u ./cn.txt 2.2.2.2 +false -# lookup IP or CIDR in REPL mode from local file -$ ./geoip lookup -f text -u ./cn.txt -n cn -Enter IP or CIDR (type `exit` to quit): ->> 1.0.1.1 -cn ->> 1.0.1.1/24 + +# 从 text 格式的本地文件(只包含一个类别)中查找某个 CIDR +# lookup CIDR from local file (with only one list) in text format +$ ./geoip lookup -f text -u ./cn.txt 1.0.1.1/24 +true + + +# 从 text 格式的本地文件(只包含一个类别)中查找某个 CIDR +# lookup CIDR from local file (with only one list) in text format +$ ./geoip lookup -f text -u ./cn.txt 1.0.1.1/23 +false + + +# 从 text 格式的远程 URL(只包含一个类别)中查找某个 IP 地址 +# lookup IP from remote URL (with only one list) in text format +$ ./geoip lookup -f text -u https://example.com/cn.txt 1.0.1.1 +true + + +# 从 v2rayGeoIPDat 格式的本地文件(只包含一个类别)中查找某个 IP 地址 +# lookup IP from local file (with only one list) in v2rayGeoIPDat format +$ ./geoip lookup -f v2rayGeoIPDat -u ./cn.dat 1.0.1.1 +true + + +# 从 v2rayGeoIPDat 格式的本地文件(包含多个类别)中查找某个 IP 地址 +# lookup IP from local file (with multiple lists) in v2rayGeoIPDat format +$ ./geoip lookup -f v2rayGeoIPDat -u ./geoip.dat 1.0.1.1 cn -# lookup IP or CIDR in REPL mode from remote file -$ ./geoip lookup -f text -u https://example.com/cn.txt -n cn -Enter IP or CIDR (type `exit` to quit): +# 从 v2rayGeoIPDat 格式的本地文件(包含多个类别)中查找某个 IP 地址 +# lookup IP from local file (with multiple lists) in v2rayGeoIPDat format +$ ./geoip lookup -f v2rayGeoIPDat -u ./geoip.dat 1.0.0.1 +au,cloudflare + + +# 从 v2rayGeoIPDat 格式的远程 URL(包含多个类别)中查找某个 CIDR +# lookup CIDR from remote URL (with multiple lists) in v2rayGeoIPDat format +$ ./geoip lookup -f v2rayGeoIPDat -u https://example.com/geoip.dat 1.0.0.1/24 +au,cloudflare + + + + +# ================= REPL Mode ================= # + +# 从 text 格式的本地文件(只包含一个类别)中查找某个 IP 地址或 CIDR +# lookup IP or CIDR from local file (with only one list) in text format +$ ./geoip lookup -f text -u ./cn.txt +Enter IP or CIDR (type "exit" to quit): >> 1.0.1.1 -cn +true + >> 1.0.1.1/24 -cn +true + +>> 1.0.1.1/23 +false + +>> 2.2.2.2 +false +>> 2.2.2.2/24 +false -# lookup IP or CIDR in REPL mode from local directory, got two lists joined with comma -$ ./geoip lookup -f text -d ./path/to/your/directory/ -Enter IP or CIDR (type `exit` to quit): +>> 300.300.300.300 +false + +>> 300.300.300.300/24 +false + +>> exit + + +# 从 text 格式的远程 URL(只包含一个类别)中查找某个 IP 地址或 CIDR +# lookup IP or CIDR from remote URL (with only one list) in text format +$ ./geoip lookup -f text -u https://example.com/cn.txt +Enter IP or CIDR (type "exit" to quit): >> 1.0.1.1 -cn,my-custom-list +true + >> 1.0.1.1/24 -cn,my-custom-list +true + +>> 1.0.1.1/23 +false + +>> 2.2.2.2 +false +>> 2.2.2.2/24 +false -# lookup IP or CIDR in REPL mode from specified lists in local directory -$ ./geoip lookup -f text -d ./path/to/your/directory/ -l cn,us,jp -Enter IP or CIDR (type `exit` to quit): +>> 300.300.300.300 +false + +>> 300.300.300.300/24 +false + +>> exit + + +# 从 v2rayGeoIPDat 格式的本地文件(只包含一个类别)中查找某个 IP 地址或 CIDR +# lookup IP or CIDR from local file (with only one list) in v2rayGeoIPDat format +$ ./geoip lookup -f v2rayGeoIPDat -u ./cn.dat +Enter IP or CIDR (type "exit" to quit): >> 1.0.1.1 -cn +true + >> 1.0.1.1/24 -cn +true +>> 1.0.1.1/23 +false -# lookup IP or CIDR in REPL mode with another format from specified lists in remote file -$ ./geoip lookup -f v2rayGeoIPDat -u https://example.com/geoip.dat -l cn,us,jp -Enter IP or CIDR (type `exit` to quit): +>> 2.2.2.2 +false + +>> 2.2.2.2/24 +false + +>> 300.300.300.300 +false + +>> 300.300.300.300/24 +false + +>> exit + + +# 从 v2rayGeoIPDat 格式的远程 URL(包含多个类别)中查找某个 IP 地址或 CIDR +# lookup IP or CIDR from remote URL (with multiple list) in v2rayGeoIPDat format +$ ./geoip lookup -f v2rayGeoIPDat -u https://example.com/geoip.dat +Enter IP or CIDR (type "exit" to quit): >> 1.0.1.1 cn + >> 1.0.1.1/24 cn + +>> 1.0.1.1/23 +false + +>> 1.0.0.1 +au,cloudflare + +>> 1.0.0.1/24 +au,cloudflare + +>> 300.300.300.300 +false + +>> 300.300.300.300/24 +false + +>> exit ``` ## 使用本项目的项目 diff --git a/lookup.go b/lookup.go index 079e1cc3bfe..090c2802af3 100644 --- a/lookup.go +++ b/lookup.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "log" + "net/netip" "os" "strings" @@ -12,34 +13,34 @@ import ( ) var supportedInputFormats = map[string]bool{ - strings.ToLower("maxmindMMDB"): true, - strings.ToLower("clashRuleSetClassical"): true, strings.ToLower("clashRuleSet"): true, + strings.ToLower("clashRuleSetClassical"): true, + strings.ToLower("maxmindMMDB"): true, + strings.ToLower("mihomoMRS"): true, + strings.ToLower("singboxSRS"): true, strings.ToLower("surgeRuleSet"): true, strings.ToLower("text"): true, - strings.ToLower("singboxSRS"): true, strings.ToLower("v2rayGeoIPDat"): true, } func init() { rootCmd.AddCommand(lookupCmd) - lookupCmd.Flags().StringP("format", "f", "", "The input format, available options: text, v2rayGeoIPDat, maxmindMMDB, singboxSRS, clashRuleSet, clashRuleSetClassical, surgeRuleSet") - lookupCmd.Flags().StringP("name", "n", "", "The name of the list, use with \"uri\" flag") - lookupCmd.Flags().StringP("uri", "u", "", "URI of the input file, support both local file path and remote HTTP(S) URL") - lookupCmd.Flags().StringP("dir", "d", "", "Path to the input directory. The filename without extension will be as the name of the list") + lookupCmd.Flags().StringP("format", "f", "", "(Required) The input format. Available formats: text, v2rayGeoIPDat, maxmindMMDB, mihomoMRS, singboxSRS, clashRuleSet, clashRuleSetClassical, surgeRuleSet") + lookupCmd.Flags().StringP("uri", "u", "", "URI of the input file, support both local file path and remote HTTP(S) URL. (Cannot be used with \"dir\" flag)") + lookupCmd.Flags().StringP("dir", "d", "", "Path to the input directory. The filename without extension will be as the name of the list. (Cannot be used with \"uri\" flag)") lookupCmd.Flags().StringSliceP("searchlist", "l", []string{}, "The lists to search from, separated by comma") lookupCmd.MarkFlagRequired("format") lookupCmd.MarkFlagsOneRequired("uri", "dir") - lookupCmd.MarkFlagsRequiredTogether("name", "uri") + lookupCmd.MarkFlagsMutuallyExclusive("uri", "dir") lookupCmd.MarkFlagDirname("dir") } var lookupCmd = &cobra.Command{ Use: "lookup", Aliases: []string{"find"}, - Short: "Lookup specified IP or CIDR in specified lists", + Short: "Lookup if specified IP or CIDR is in specified lists", Args: cobra.RangeArgs(0, 1), Run: func(cmd *cobra.Command, args []string) { // Validate format @@ -49,9 +50,8 @@ var lookupCmd = &cobra.Command{ log.Fatal("unsupported input format") } - // Get name - name, _ := cmd.Flags().GetString("name") - name = strings.ToLower(strings.TrimSpace(name)) + // Set name + name := "true" // Get uri uri, _ := cmd.Flags().GetString("uri") @@ -68,42 +68,38 @@ var lookupCmd = &cobra.Command{ switch len(args) > 0 { case true: // With search arg, run in once mode - search := strings.ToLower(args[0]) - config := generateConfigForLookup(format, name, uri, dir, search, searchListStr) - - instance, err := lib.NewInstance() - if err != nil { - log.Fatal(err) - } - if err := instance.InitFromBytes([]byte(config)); err != nil { - log.Fatal(err) + search := strings.ToLower(strings.TrimSpace(args[0])) + if !isValidIPOrCIDR(search) { + fmt.Println("false") + return } - if err := instance.Run(); err != nil { - log.Fatal(err) - } + execute(format, name, uri, dir, search, searchListStr) case false: // No search arg, run in REPL mode - fmt.Println("Enter IP or CIDR (type `exit` to quit):") + fmt.Println(`Enter IP or CIDR (type "exit" to quit):`) fmt.Print(">> ") scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { search := strings.ToLower(strings.TrimSpace(scanner.Text())) - if search == "exit" { + if search == "" { + fmt.Println() + fmt.Print(">> ") + continue + } + if search == "exit" || search == `"exit"` { break } - config := generateConfigForLookup(format, name, uri, dir, search, searchListStr) - instance, err := lib.NewInstance() - if err != nil { - log.Fatal(err) - } - if err := instance.InitFromBytes([]byte(config)); err != nil { - log.Fatal(err) - } - if err := instance.Run(); err != nil { - log.Fatal(err) + if !isValidIPOrCIDR(search) { + fmt.Println("false") + fmt.Println() + fmt.Print(">> ") + continue } + + execute(format, name, uri, dir, search, searchListStr) + fmt.Println() fmt.Print(">> ") } @@ -114,6 +110,40 @@ var lookupCmd = &cobra.Command{ }, } +// Check if the input is a valid IP or CIDR +func isValidIPOrCIDR(search string) bool { + if search == "" { + return false + } + + var err error + switch strings.Contains(search, "/") { + case true: // CIDR + _, err = netip.ParsePrefix(search) + case false: // IP + _, err = netip.ParseAddr(search) + } + + return err == nil +} + +func execute(format, name, uri, dir, search, searchListStr string) { + config := generateConfigForLookup(format, name, uri, dir, search, searchListStr) + + instance, err := lib.NewInstance() + if err != nil { + log.Fatal(err) + } + + if err := instance.InitFromBytes([]byte(config)); err != nil { + log.Fatal(err) + } + + if err := instance.Run(); err != nil { + log.Fatal(err) + } +} + func generateConfigForLookup(format, name, uri, dir, search, searchListStr string) string { return fmt.Sprintf(` { diff --git a/plugin/special/lookup.go b/plugin/special/lookup.go index 34ba0207dab..6b9e6af0b67 100644 --- a/plugin/special/lookup.go +++ b/plugin/special/lookup.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/netip" + "slices" "strings" "github.com/Loyalsoldier/geoip/lib" @@ -85,7 +86,10 @@ func (l *lookup) Output(container lib.Container) error { lists, found, _ := container.Lookup(l.Search, l.SearchList...) if found { + slices.Sort(lists) fmt.Println(strings.ToLower(strings.Join(lists, ","))) + } else { + fmt.Println("false") } return nil