diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf6da9e..ffe00b0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,7 @@ on: push: pull_request: + workflow_dispatch: name: Test & Build Release jobs: @@ -11,24 +12,35 @@ jobs: - uses: actions/setup-go@v2 with: - go-version: "1.18" + go-version: "1.20" - name: Test run: sudo go test -v -coverprofile='coverage.out' -covermode=count ./... Build: - needs: test - if: startsWith(github.ref, 'refs/tags/v') + needs: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v2 with: - go-version: "1.18" - - - run: bash .cross_compile.sh + go-version: "1.20" + - name: compile + run: bash .cross_compile.sh + + - name: Upload files to Artifacts + uses: actions/upload-artifact@v3 + with: + path: | + dist/* + + Release: + needs: Build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: - name: Release uses: softprops/action-gh-release@v1 with: # 将下述可执行文件 release 上去 @@ -39,7 +51,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GT_Token }} publish-new-formula: - needs: build + needs: Release # The type of runner that the job will run on runs-on: ubuntu-latest diff --git a/README.md b/README.md index 1c03507..6b2f1c6 100644 --- a/README.md +++ b/README.md @@ -122,15 +122,25 @@ nexttrace --no-rdns www.bbix.net nexttrace --route-path www.time.com.my ``` -`NextTrace` supports users to select their own IP API (currently supports: `LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`) +`NextTrace` supports users to select their own IP API (currently supports: `LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`, `Ip2region`, `IPInfoLocal`) ```bash -# You can specify the IP database by yourself [IP.SB here], if not specified, LeoMoeAPI will be used -nexttrace --data-provider IP.SB -## Note that the ipinfo API needs users to purchase services from ipinfo. If necessary, you can clone this project, add the token provided by ipinfo and compile it yourself +# You can specify the IP database by yourself [IP-API.com here], if not specified, LeoMoeAPI will be used +nexttrace --data-provider ip-api.com +## Note There are frequency limits for free queries of the ipinfo and IPInsight APIs. You can purchase services from these providers to remove the limits +## If necessary, you can clone this project, add the token provided by ipinfo or IPInsight and compile it yourself +## Note For the offline database IPInfoLocal, please download it manually and rename it to ipinfoLocal.mmdb. (You can download it from here: https://ipinfo.io/signup?ref=free-database-downloads) +## For the offline database Ip2region, you can download it manually and rename it to ip2region.db, or let NextTrace download it automatically ## Fill the token to: ipgeo/tokens.go ## Please be aware: Due to the serious abuse of IP.SB, you will often be not able to query IP data from this source -## IPAPI.com has a stricter restiction on API calls, if you can't query IP data from this source, please try again in a few minutes. +## IP-API.com has a stricter restiction on API calls, if you can't query IP data from this source, please try again in a few minutes + +# The Pure-FTPd IP database defaults to using http://127.0.0.1:2060 as the query interface. To customize it, please use environment variables +export NEXTTRACE_CHUNZHENURL=http://127.0.0.1:2060 +## You can use https://github.com/freshcn/qqwry to build your own Pure-FTPd IP database service + +# You can also specify the default IP database by setting an environment variable +export NEXTTRACE_DATAPROVIDER=ipinfo ``` `NextTrace` supports mixed parameters and shortened parameters @@ -158,18 +168,18 @@ All NextTrace IP geolocation `API DEMO` can refer to [here](https://github.com/x ### For full usage list, please refer to the usage menu ```shell -usage: nexttrace [-h|--help] [-T|--tcp] [-U|--udp] [-F|--fast-trace] [-p|--port +Usage: nexttrace [-h|--help] [-T|--tcp] [-U|--udp] [-F|--fast-trace] [-p|--port ] [-q|--queries ] [--parallel-requests ] [-m|--max-hops ] [-d|--data-provider - (IP.SB|IPInfo|IPInsight|IPAPI.com)] [-n|--no-rdns] - [-a|--always-rdns] [-P|--route-path] [-r|--report] - [-o|--output] [-t|--table] [-c|--classic] [-f|--first - ] [-M|--map] [-v|--version] [-s|--source ""] - [-D|--dev ""] [-R|--route] [-z|--send-time ] - [-i|--ttl-time ] [-g|--language (en|cn)] [IP Address - or Domain] - - An open source visual route tracking CLI tool + (Ip2region|ip2region|IP.SB|ip.sb|IPInfo|ipinfo|IPInsight|ipinsight|IPAPI.com|ip-api.com|IPInfoLocal|ipinfolocal|chunzhen)] + [-n|--no-rdns] [-a|--always-rdns] [-P|--route-path] + [-r|--report] [--dn42] [-o|--output] [-t|--table] + [-c|--classic] [-f|--first ] [-M|--map] + [-v|--version] [-s|--source ""] [-D|--dev ""] + [-R|--route] [-z|--send-time ] [-i|--ttl-time + ] [_positionalArg_nexttrace_25 ""] + [--dot-server (dnssb|aliyun|dnspod|google|cloudflare)] + [-g|--language (en|cn)] Arguments: @@ -194,9 +204,9 @@ Arguments: 18 -m --max-hops Set the max number of hops (max TTL to be reached). Default: 30 - -d --data-provider Choose IP Geograph Data Provider - [LeoMoeAPI,IP.SB, IPInfo, IPInsight, - IPAPI.com]. Default: LeoMoeAPI + -d --data-provider Choose IP Geograph Data Provider [IP.SB, + IPInfo, IPInsight, IP-API.com, Ip2region, + IPInfoLocal, CHUNZHEN]. Default: LeoMoeAPI -n --no-rdns Do not resolve IP addresses to their domain names -a --always-rdns Always resolve IP addresses to their @@ -204,6 +214,7 @@ Arguments: -P --route-path Print traceroute hop path by ASN and location -r --report output using report mode + --dn42 DN42 Mode -o --output Write trace result to file (RealTimePrinter ONLY) -t --table Output trace results as table @@ -211,7 +222,7 @@ Arguments: BestTrace -f --first Start from the first_ttl hop (instead from 1). Default: 1 - -M --map Disable Print Trace Map Function + -M --map Disable Print Trace Map -v --version Print version info and exit -s --source Use source src_addr for outgoing packets -D --dev Use the following Network Devices as the @@ -224,6 +235,9 @@ Arguments: groups by TTL. Useful when some routers use rate-limit for ICMP messages. Default: 500 + --_positionalArg_nexttrace_25 IP Address or domain name + --dot-server Use DoT Server for DNS Parse [dnssb, + aliyun, dnspod, google, cloudflare] -g --language Choose the language for displaying [en, cn]. Default: cn ``` diff --git a/README_zh_CN.md b/README_zh_CN.md index b3ce6b5..7516602 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -141,27 +141,35 @@ nexttrace --no-rdns www.bbix.net nexttrace --route-path www.time.com.my ``` -`NextTrace`支持用户自主选择 IP 数据库(目前支持:`LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`) +`NextTrace`支持用户自主选择 IP 数据库(目前支持:`LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`, `Ip2region`, `IPInfoLocal`, `CHUNZHEN`) ```bash -# 可以自行指定IP数据库[此处为IP.SB],不指定则默认为LeoMoeAPI -nexttrace --data-provider IP.SB -## 特别的:其中 ipinfo API 需要从 ipinfo 自行购买服务,如有需要可以 clone 本项目添加其提供的 token 自行编译 +# 可以自行指定IP数据库[此处为IP-API.com],不指定则默认为LeoMoeAPI +nexttrace --data-provider ip-api.com +## 特别的: 其中 ipinfo 和 IPInsight API 对于免费版查询有频率限制,可从这些服务商自行购买服务以解除限制,如有需要可以 clone 本项目添加其提供的 token 自行编译 ## TOKEN填写路径:ipgeo/tokens.go - +## 特别的: 对于离线库 IPInfoLocal,请自行下载并命名为 ipinfoLocal.mmdb (可以从这里下载:https://ipinfo.io/signup?ref=free-database-downloads) +## 对于离线库 Ip2region 可NextTrace自动下载,也可自行下载并命名为 ip2region.db ## 另外:由于IP.SB被滥用比较严重,会经常出现无法查询的问题,请知悉。 -## IPAPI.com限制调用较为严格,如有查询不到的情况,请几分钟后再试。 +## IP-API.com限制调用较为严格,如有查询不到的情况,请几分钟后再试。 + +# 纯真IP数据库默认使用 http://127.0.0.1:2060 作为查询接口,如需自定义请使用环境变量 +export NEXTTRACE_CHUNZHENURL=http://127.0.0.1:2060 +## 可使用 https://github.com/freshcn/qqwry 自行搭建纯真IP数据库服务 + +# 也可以通过设置环境变量来指定默认IP数据库 +export NEXTTRACE_DATAPROVIDER=ipinfo ``` `NextTrace`支持使用混合参数和简略参数 ```bash Example: -nexttrace --data-provider IPAPI.com --max-hops 20 --tcp --port 443 --queries 5 --no-rdns 1.1.1.1 +nexttrace --data-provider ip-api.com --max-hops 20 --tcp --port 443 --queries 5 --no-rdns 1.1.1.1 nexttrace -tcp --queries 2 --parallel-requests 1 --table --route-path 2001:4860:4860::8888 Equivalent to: -nexttrace -d IPAPI.com -m 20 -T -p 443 -q 5 -n 1.1.1.1 +nexttrace -d ip-api.com -m 20 -T -p 443 -q 5 -n 1.1.1.1 nexttrace -T -q 2 --parallel-requests 1 -t -R 2001:4860:4860::8888 ``` @@ -171,12 +179,16 @@ nexttrace -T -q 2 --parallel-requests 1 -t -R 2001:4860:4860::8888 Usage: nexttrace [-h|--help] [-T|--tcp] [-U|--udp] [-F|--fast-trace] [-p|--port ] [-q|--queries ] [--parallel-requests ] [-m|--max-hops ] [-d|--data-provider - (IP.SB|IPInfo|IPInsight|IPAPI.com)] [-n|--no-rdns] - [-r|--route-path] [-o|--output] [-t|--table] [-c|--classic] - [-f|--first ] [-M|--map] [-v|--version] [-s|--source - ""] [-D|--dev ""] [-R|--route] [-z|--send-time - ] [-i|--ttl-time ] - [IP Address or Domain name] + (Ip2region|ip2region|IP.SB|ip.sb|IPInfo|ipinfo|IPInsight|ipinsight|IPAPI.com|ip-api.com|IPInfoLocal|ipinfolocal|chunzhen)] + [-n|--no-rdns] [-a|--always-rdns] [-P|--route-path] + [-r|--report] [--dn42] [-o|--output] [-t|--table] + [-c|--classic] [-f|--first ] [-M|--map] + [-v|--version] [-s|--source ""] [-D|--dev ""] + [-R|--route] [-z|--send-time ] [-i|--ttl-time + ] [_positionalArg_nexttrace_25 ""] + [--dot-server (dnssb|aliyun|dnspod|google|cloudflare)] + [-g|--language (en|cn)] + Arguments: -h --help Print help information @@ -200,13 +212,17 @@ Arguments: 18 -m --max-hops Set the max number of hops (max TTL to be reached). Default: 30 - -d --data-provider Choose IP Geograph Data Provider - [LeoMoeAPI,IP.SB, IPInfo, IPInsight, - IPAPI.com]. Default: LeoMoeAPI - -n --no-rdns Do not resolve IP addresses to their + -d --data-provider Choose IP Geograph Data Provider [IP.SB, + IPInfo, IPInsight, IP-API.com, Ip2region, + IPInfoLocal, CHUNZHEN]. Default: LeoMoeAPI + -n --no-rdns Do not resolve IP addresses to their domain names - -r --route-path Print traceroute hop path by ASN and + -a --always-rdns Always resolve IP addresses to their + domain names + -P --route-path Print traceroute hop path by ASN and location + -r --report output using report mode + --dn42 DN42 Mode -o --output Write trace result to file (RealTimePrinter ONLY) -t --table Output trace results as table @@ -214,7 +230,7 @@ Arguments: BestTrace -f --first Start from the first_ttl hop (instead from 1). Default: 1 - -M --map Disable Print Trace Map Function + -M --map Disable Print Trace Map -v --version Print version info and exit -s --source Use source src_addr for outgoing packets -D --dev Use the following Network Devices as the @@ -222,11 +238,14 @@ Arguments: -R --route Show Routing Table [Provided By BGP.Tools] -z --send-time Set the time interval for sending every packet. Useful when some routers use - rate-limit for ICMP messages.. Default: 0 + rate-limit for ICMP messages. Default: 100 -i --ttl-time Set the time interval for sending packets groups by TTL. Useful when some routers - use rate-limit for ICMP messages.. - Default: 500 + use rate-limit for ICMP messages. Default: + 500 + --_positionalArg_nexttrace_25 IP Address or domain name + --dot-server Use DoT Server for DNS Parse [dnssb, + aliyun, dnspod, google, cloudflare] -g --language Choose the language for displaying [en, cn]. Default: cn ``` diff --git a/cmd/cmd.go b/cmd/cmd.go index 02ef472..b922f0c 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -37,8 +37,8 @@ func Excute() { numMeasurements := parser.Int("q", "queries", &argparse.Options{Default: 3, Help: "Set the number of probes per each hop"}) parallelRequests := parser.Int("", "parallel-requests", &argparse.Options{Default: 18, Help: "Set ParallelRequests number. It should be 1 when there is a multi-routing"}) maxHops := parser.Int("m", "max-hops", &argparse.Options{Default: 30, Help: "Set the max number of hops (max TTL to be reached)"}) - dataOrigin := parser.Selector("d", "data-provider", []string{"IP.SB", "IPInfo", "IPInsight", "IPAPI.com"}, &argparse.Options{Default: "LeoMoeAPI", - Help: "Choose IP Geograph Data Provider [LeoMoeAPI,IP.SB, IPInfo, IPInsight, IPAPI.com]"}) + dataOrigin := parser.Selector("d", "data-provider", []string{"Ip2region", "ip2region", "IP.SB", "ip.sb", "IPInfo", "ipinfo", "IPInsight", "ipinsight", "IPAPI.com", "ip-api.com", "IPInfoLocal", "ipinfolocal", "chunzhen"}, &argparse.Options{Default: "LeoMoeAPI", + Help: "Choose IP Geograph Data Provider [IP.SB, IPInfo, IPInsight, IP-API.com, Ip2region, IPInfoLocal, CHUNZHEN]"}) noRdns := parser.Flag("n", "no-rdns", &argparse.Options{Help: "Do not resolve IP addresses to their domain names"}) alwaysRdns := parser.Flag("a", "always-rdns", &argparse.Options{Help: "Always resolve IP addresses to their domain names"}) routePath := parser.Flag("P", "route-path", &argparse.Options{Help: "Print traceroute hop path by ASN and location"}) @@ -141,12 +141,17 @@ func Excute() { } if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" { - w := wshandle.New() - w.Interrupt = make(chan os.Signal, 1) - signal.Notify(w.Interrupt, os.Interrupt) - defer func() { - w.Conn.Close() - }() + val, ok := os.LookupEnv("NEXTTRACE_DATAPROVIDER") + if ok { + *dataOrigin = val + } else { + w := wshandle.New() + w.Interrupt = make(chan os.Signal, 1) + signal.Notify(w.Interrupt, os.Interrupt) + defer func() { + w.Conn.Close() + }() + } } printer.PrintTraceRouteNav(ip, domain, *dataOrigin) diff --git a/config/viper.go b/config/viper.go index 8f31cce..3801e0b 100644 --- a/config/viper.go +++ b/config/viper.go @@ -27,8 +27,14 @@ func InitConfig() { err := viper.ReadInConfig() // Find and read the config file if err != nil { // Handle errors reading the config file fmt.Println("未能找到配置文件,我们将在您的运行目录为您创建 nt_config.yaml 默认配置") - viper.SafeWriteConfigAs("./nt_config.yaml") + err := viper.SafeWriteConfigAs("./nt_config.yaml") + if err != nil { + return + } } - viper.ReadInConfig() + err = viper.ReadInConfig() + if err != nil { + return + } } diff --git a/fast_trace/fast_trace ipv6.go b/fast_trace/fast_trace ipv6.go index 485de17..de85842 100644 --- a/fast_trace/fast_trace ipv6.go +++ b/fast_trace/fast_trace ipv6.go @@ -20,7 +20,12 @@ func (f *FastTracer) tracert_v6(location string, ispCollection ISPCollection) { if err != nil { return } - defer fp.Close() + defer func(fp *os.File) { + err := fp.Close() + if err != nil { + log.Fatal(err) + } + }(fp) log.SetOutput(fp) log.SetFlags(0) @@ -100,7 +105,10 @@ func FastTestv6(tm bool, outEnable bool) { fmt.Println("您想测试哪些ISP的路由?\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网") fmt.Print("请选择选项:") - fmt.Scanln(&c) + _, err := fmt.Scanln(&c) + if err != nil { + c = "1" + } ft := FastTracer{} diff --git a/fast_trace/fast_trace.go b/fast_trace/fast_trace.go index e7d71ec..a52907b 100644 --- a/fast_trace/fast_trace.go +++ b/fast_trace/fast_trace.go @@ -19,14 +19,19 @@ type FastTracer struct { TracerouteMethod trace.Method } -var oe bool = false +var oe = false func (f *FastTracer) tracert(location string, ispCollection ISPCollection) { fp, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) if err != nil { return } - defer fp.Close() + defer func(fp *os.File) { + err := fp.Close() + if err != nil { + log.Fatal(err) + } + }(fp) log.SetOutput(fp) log.SetFlags(0) @@ -114,7 +119,10 @@ func FastTest(tm bool, outEnable bool) { fmt.Println("Hi,欢迎使用 Fast Trace 功能,请注意 Fast Trace 功能只适合新手使用\n因为国内网络复杂,我们设置的测试目标有限,建议普通用户自测以获得更加精准的路由情况") fmt.Println("请您选择要测试的 IP 类型\n1. IPv4\n2. IPv6") fmt.Print("请选择选项:") - fmt.Scanln(&c) + _, err := fmt.Scanln(&c) + if err != nil { + c = "1" + } if c == "2" { FastTestv6(tm, outEnable) return @@ -122,7 +130,10 @@ func FastTest(tm bool, outEnable bool) { fmt.Println("您想测试哪些ISP的路由?\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网") fmt.Print("请选择选项:") - fmt.Scanln(&c) + _, err = fmt.Scanln(&c) + if err != nil { + c = "1" + } ft := FastTracer{} diff --git a/go.mod b/go.mod index b37be71..16c9ceb 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( github.com/akamensky/argparse v1.4.0 github.com/google/gopacket v1.1.19 + github.com/oschwald/maxminddb-golang v1.10.0 github.com/spf13/viper v1.15.0 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 golang.org/x/net v0.7.0 diff --git a/go.sum b/go.sum index 2d2cece..7024ea1 100644 --- a/go.sum +++ b/go.sum @@ -153,6 +153,8 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= +github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/ipgeo/chunzhen.go b/ipgeo/chunzhen.go new file mode 100644 index 0000000..8326892 --- /dev/null +++ b/ipgeo/chunzhen.go @@ -0,0 +1,91 @@ +package ipgeo + +import ( + "encoding/json" + "github.com/xgadget-lab/nexttrace/util" + "io" + "log" + "net/http" + "strings" + "time" +) + +func Chunzhen(ip string) (*IPGeoData, error) { + url := util.GetenvDefault("NEXTTRACE_CHUNZHENURL", "http://127.0.0.1:2060") + "?ip=" + ip + client := &http.Client{ + // 2 秒超时 + Timeout: 2 * time.Second, + } + req, _ := http.NewRequest("GET", url, nil) + content, err := client.Do(req) + if err != nil { + log.Println("纯真 请求超时(2s),请切换其他API使用") + return &IPGeoData{}, err + } + body, _ := io.ReadAll(content.Body) + + var data map[string]interface{} + err = json.Unmarshal(body, &data) + if err != nil { + return &IPGeoData{}, err + } + city := data[ip].(map[string]interface{})["area"].(string) + region := data[ip].(map[string]interface{})["country"].(string) + var asn string + if data[ip].(map[string]interface{})["asn"] != nil { + asn = data[ip].(map[string]interface{})["asn"].(string) + } + // 判断是否前两个字为香港或台湾 + var country string + provinces := []string{ + "北京", + "天津", + "河北", + "山西", + "内蒙古", + "辽宁", + "吉林", + "黑龙江", + "上海", + "江苏", + "浙江", + "安徽", + "福建", + "江西", + "山东", + "河南", + "湖北", + "湖南", + "广东", + "广西", + "海南", + "重庆", + "四川", + "贵州", + "云南", + "西藏", + "陕西", + "甘肃", + "青海", + "宁夏", + "新疆", + "台湾", + "香港", + "澳门", + } + for _, province := range provinces { + if strings.Contains(region, province) { + country = "中国" + city = region + city + break + } + } + if country == "" { + country = region + } + return &IPGeoData{ + Asnumber: asn, + Country: country, + City: city, + }, nil +} diff --git a/ipgeo/ipapicom.go b/ipgeo/ipapicom.go index 310d7a8..f2809a0 100644 --- a/ipgeo/ipapicom.go +++ b/ipgeo/ipapicom.go @@ -2,7 +2,7 @@ package ipgeo import ( "errors" - "io/ioutil" + "io" "log" "net/http" "regexp" @@ -24,7 +24,7 @@ func IPApiCom(ip string) (*IPGeoData, error) { log.Println("ip-api.com 请求超时(2s),请切换其他API使用") return nil, err } - body, _ := ioutil.ReadAll(content.Body) + body, _ := io.ReadAll(content.Body) res := gjson.ParseBytes(body) if res.Get("status").String() != "success" { diff --git a/ipgeo/ipfilter.go b/ipgeo/ipfilter.go index 6da1e3f..b7aa5b1 100644 --- a/ipgeo/ipfilter.go +++ b/ipgeo/ipfilter.go @@ -13,7 +13,7 @@ func cidrRangeContains(cidrRange string, checkIP string) bool { return ipNet.Contains(secondIP) } -// 被选到的返回 geodata, true 否则返回 nil, false +// Filter 被选到的返回 geodata, true 否则返回 nil, false func Filter(ip string) (*IPGeoData, bool) { //geodata := &IPGeoData{} asn := "" diff --git a/ipgeo/ipgeo.go b/ipgeo/ipgeo.go index 0b5e1dc..78264b6 100644 --- a/ipgeo/ipgeo.go +++ b/ipgeo/ipgeo.go @@ -39,10 +39,16 @@ func GetSource(s string) Source { return IPInSight case "IPAPI.COM": return IPApiCom + case "IP-API.COM": + return IPApiCom case "IPINFO": return IPInfo case "IP2REGION": return IP2Region + case "IPINFOLOCAL": + return IPInfoLocal + case "CHUNZHEN": + return Chunzhen default: return LeoIP } diff --git a/ipgeo/ipinfo.go b/ipgeo/ipinfo.go index 4433248..1b34294 100644 --- a/ipgeo/ipinfo.go +++ b/ipgeo/ipinfo.go @@ -1,8 +1,9 @@ package ipgeo import ( - "io/ioutil" + "io" "net/http" + "strings" "github.com/tidwall/gjson" ) @@ -13,7 +14,7 @@ func IPInfo(ip string) (*IPGeoData, error) { return nil, err } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } @@ -21,16 +22,276 @@ func IPInfo(ip string) (*IPGeoData, error) { res := gjson.ParseBytes(body) var country string - + country = res.Get("country").String() if res.Get("country").String() == "HK" || res.Get("country").String() == "TW" { country = "CN" } + // ISO-3166 转换 + var countryMap = map[string]string{ + "AF": "Afghanistan", + "AX": "Åland Islands", + "AL": "Albania", + "DZ": "Algeria", + "AS": "American Samoa", + "AD": "Andorra", + "AO": "Angola", + "AI": "Anguilla", + "AQ": "Antarctica", + "AG": "Antigua and Barbuda", + "AR": "Argentina", + "AM": "Armenia", + "AW": "Aruba", + "AU": "Australia", + "AT": "Austria", + "AZ": "Azerbaijan", + "BH": "Bahrain", + "BS": "Bahamas", + "BD": "Bangladesh", + "BB": "Barbados", + "BY": "Belarus", + "BE": "Belgium", + "BZ": "Belize", + "BJ": "Benin", + "BM": "Bermuda", + "BT": "Bhutan", + "BO": "Bolivia", + "BQ": "Bonaire", + "BA": "Bosnia and Herzegovina", + "BW": "Botswana", + "BV": "Bouvet Island", + "BR": "Brazil", + "IO": "British Indian Ocean Territory", + "BN": "Brunei Darussalam", + "BG": "Bulgaria", + "BF": "Burkina Faso", + "BI": "Burundi", + "KH": "Cambodia", + "CM": "Cameroon", + "CA": "Canada", + "CV": "Cape Verde", + "KY": "Cayman Islands", + "CF": "Central African Republic", + "TD": "Chad", + "CL": "Chile", + "CN": "China", + "CX": "Christmas Island", + "CC": "Cocos (Keeling) Islands", + "CO": "Colombia", + "KM": "Comoros", + "CG": "Congo", + "CD": "Congo", + "CK": "Cook Islands", + "CR": "Costa Rica", + "CI": "Côte d'Ivoire", + "HR": "Croatia", + "CU": "Cuba", + "CW": "Curaçao", + "CY": "Cyprus", + "CZ": "Czech Republic", + "DK": "Denmark", + "DJ": "Djibouti", + "DM": "Dominica", + "DO": "Dominican Republic", + "EC": "Ecuador", + "EG": "Egypt", + "SV": "El Salvador", + "GQ": "Equatorial Guinea", + "ER": "Eritrea", + "EE": "Estonia", + "ET": "Ethiopia", + "FK": "Falkland Islands (Malvinas)", + "FO": "Faroe Islands", + "FJ": "Fiji", + "FI": "Finland", + "FR": "France", + "GF": "French Guiana", + "PF": "French Polynesia", + "TF": "French Southern Territories", + "GA": "Gabon", + "GM": "Gambia", + "GE": "Georgia", + "DE": "Germany", + "GH": "Ghana", + "GI": "Gibraltar", + "GR": "Greece", + "GL": "Greenland", + "GD": "Grenada", + "GP": "Guadeloupe", + "GU": "Guam", + "GT": "Guatemala", + "GG": "Guernsey", + "GN": "Guinea", + "GW": "Guinea-Bissau", + "GY": "Guyana", + "HT": "Haiti", + "HM": "Heard Island and McDonald Islands", + "VA": "Holy See (Vatican City State)", + "HN": "Honduras", + "HK": "Hong Kong", + "HU": "Hungary", + "IS": "Iceland", + "IN": "India", + "ID": "Indonesia", + "IR": "Iran", + "IQ": "Iraq", + "IE": "Ireland", + "IM": "Isle of Man", + "IL": "Israel", + "IT": "Italy", + "JM": "Jamaica", + "JP": "Japan", + "JE": "Jersey", + "JO": "Jordan", + "KZ": "Kazakhstan", + "KE": "Kenya", + "KI": "Kiribati", + "KP": "Korea", + "KR": "Korea", + "KW": "Kuwait", + "KG": "Kyrgyzstan", + "LA": "Lao People's Democratic Republic", + "LV": "Latvia", + "LB": "Lebanon", + "LS": "Lesotho", + "LR": "Liberia", + "LY": "Libya", + "LI": "Liechtenstein", + "LT": "Lithuania", + "LU": "Luxembourg", + "MO": "Macao", + "MK": "Macedonia", + "MG": "Madagascar", + "MW": "Malawi", + "MY": "Malaysia", + "MV": "Maldives", + "ML": "Mali", + "MT": "Malta", + "MH": "Marshall Islands", + "MQ": "Martinique", + "MR": "Mauritania", + "MU": "Mauritius", + "YT": "Mayotte", + "MX": "Mexico", + "FM": "Micronesia", + "MD": "Moldova", + "MC": "Monaco", + "MN": "Mongolia", + "ME": "Montenegro", + "MS": "Montserrat", + "MA": "Morocco", + "MZ": "Mozambique", + "MM": "Myanmar", + "NA": "Namibia", + "NR": "Nauru", + "NP": "Nepal", + "NL": "Netherlands", + "NC": "New Caledonia", + "NZ": "New Zealand", + "NI": "Nicaragua", + "NE": "Niger", + "NG": "Nigeria", + "NU": "Niue", + "NF": "Norfolk Island", + "MP": "Northern Mariana Islands", + "NO": "Norway", + "OM": "Oman", + "PK": "Pakistan", + "PW": "Palau", + "PS": "Palestine", + "PA": "Panama", + "PG": "Papua New Guinea", + "PY": "Paraguay", + "PE": "Peru", + "PH": "Philippines", + "PN": "Pitcairn", + "PL": "Poland", + "PT": "Portugal", + "PR": "Puerto Rico", + "QA": "Qatar", + "RE": "Réunion", + "RO": "Romania", + "RU": "Russian Federation", + "RW": "Rwanda", + "BL": "Saint Barthélemy", + "SH": "Saint Helena", + "KN": "Saint Kitts and Nevis", + "LC": "Saint Lucia", + "MF": "Saint Martin (French part)", + "PM": "Saint Pierre and Miquelon", + "VC": "Saint Vincent and the Grenadines", + "WS": "Samoa", + "SM": "San Marino", + "ST": "Sao Tome and Principe", + "SA": "Saudi Arabia", + "SN": "Senegal", + "RS": "Serbia", + "SC": "Seychelles", + "SL": "Sierra Leone", + "SG": "Singapore", + "SX": "Sint Maarten (Dutch part)", + "SK": "Slovakia", + "SI": "Slovenia", + "SB": "Solomon Islands", + "SO": "Somalia", + "ZA": "South Africa", + "GS": "South Georgia and the South Sandwich Islands", + "SS": "South Sudan", + "ES": "Spain", + "LK": "Sri Lanka", + "SD": "Sudan", + "SR": "Suriname", + "SJ": "Svalbard and Jan Mayen", + "SZ": "Swaziland", + "SE": "Sweden", + "CH": "Switzerland", + "SY": "Syrian Arab Republic", + "TW": "Taiwan", + "TJ": "Tajikistan", + "TZ": "Tanzania", + "TH": "Thailand", + "TL": "Timor-Leste", + "TG": "Togo", + "TK": "Tokelau", + "TO": "Tonga", + "TT": "Trinidad and Tobago", + "TN": "Tunisia", + "TR": "Turkey", + "TM": "Turkmenistan", + "TC": "Turks and Caicos Islands", + "TV": "Tuvalu", + "UG": "Uganda", + "UA": "Ukraine", + "AE": "United Arab Emirates", + "GB": "United Kingdom", + "US": "United States of America", + "UM": "United States Minor Outlying Islands", + "UY": "Uruguay", + "UZ": "Uzbekistan", + "VU": "Vanuatu", + "VE": "Venezuela", + "VN": "Viet Nam", + "VG": "Virgin Islands", + "VI": "Virgin Islands", + "WF": "Wallis and Futuna", + "EH": "Western Sahara", + "YE": "Yemen", + "ZM": "Zambia", + "ZW": "Zimbabwe", + } + country = countryMap[country] + i := strings.Index(res.Get("org").String(), " ") + var owner string + if i == -1 { + owner = "" + } else { + owner = res.Get("org").String()[i:] + } return &IPGeoData{ - Asnumber: res.Get("asn").Get("asn").String(), + Asnumber: strings.Fields(strings.TrimPrefix(res.Get("org").String(), "AS"))[0], Country: country, City: res.Get("city").String(), Prov: res.Get("region").String(), - Owner: res.Get("asn").Get("domain").String(), + Owner: owner, }, nil } diff --git a/ipgeo/ipinfoLocal.go b/ipgeo/ipinfoLocal.go new file mode 100644 index 0000000..a3d75c0 --- /dev/null +++ b/ipgeo/ipinfoLocal.go @@ -0,0 +1,52 @@ +package ipgeo + +import ( + "errors" + "github.com/oschwald/maxminddb-golang" + "net" + "os" + "strings" +) + +const ( + ipinfoDataBasePath = "./ipinfoLocal.mmdb" +) + +func IPInfoLocal(ip string) (*IPGeoData, error) { + if _, err := os.Stat(ipinfoDataBasePath); os.IsNotExist(err) { + panic("Cannot find ipinfoLocal.mmdb") + } + region, err := maxminddb.Open(ipinfoDataBasePath) + if err != nil { + panic("Cannot find ipinfoLocal.mmdb") + } + defer func(region *maxminddb.Reader) { + err := region.Close() + if err != nil { + panic(err) + } + }(region) + var record interface{} + searchErr := region.Lookup(net.ParseIP(ip), &record) + if searchErr != nil { + return &IPGeoData{}, errors.New("no results") + } + recordMap := record.(map[string]interface{}) + country_name := recordMap["country_name"].(string) + prov := "" + if recordMap["country"].(string) == "HK" { + country_name = "China" + prov = "Hong Kong" + } + if recordMap["country"].(string) == "TW" { + country_name = "China" + prov = "Taiwan" + } + return &IPGeoData{ + Asnumber: strings.TrimPrefix(recordMap["asn"].(string), "AS"), + Country: country_name, + City: "", + Prov: prov, + Owner: recordMap["as_name"].(string), + }, nil +} diff --git a/ipgeo/ipinsight.go b/ipgeo/ipinsight.go index df0c233..ea62d2d 100644 --- a/ipgeo/ipinsight.go +++ b/ipgeo/ipinsight.go @@ -1,18 +1,18 @@ package ipgeo import ( - "io/ioutil" + "io" "net/http" "github.com/tidwall/gjson" ) func IPInSight(ip string) (*IPGeoData, error) { - resp, err := http.Get("https://ipinsight.io/query?ip=" + ip) + resp, err := http.Get("https://api.ipinsight.io/ip/" + ip + "?token=" + token.ipinsight) if err != nil { return nil, err } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } diff --git a/ipgeo/ipsb.go b/ipgeo/ipsb.go index cf79470..368f380 100644 --- a/ipgeo/ipsb.go +++ b/ipgeo/ipsb.go @@ -1,7 +1,7 @@ package ipgeo import ( - "io/ioutil" + "io" "log" "net/http" "os" @@ -24,7 +24,7 @@ func IPSB(ip string) (*IPGeoData, error) { log.Println("api.ip.sb 请求超时(2s),请切换其他API使用") return nil, err } - body, _ := ioutil.ReadAll(content.Body) + body, _ := io.ReadAll(content.Body) res := gjson.ParseBytes(body) if res.Get("country").String() == "" { diff --git a/ipgeo/leo.go b/ipgeo/leo.go index 9d5f309..efdcc3f 100644 --- a/ipgeo/leo.go +++ b/ipgeo/leo.go @@ -19,7 +19,7 @@ import ( * 运作模型可以理解为一个 Node 一直在等待数据,当获得一个新的任务后,转交给下一个协程,不再关注这个 Node 的下一步处理过程,并且回到空闲状态继续等待新的任务 ***/ -// IP 查询池 map - ip - ip channel +// IPPool IP 查询池 map - ip - ip channel type IPPool struct { pool map[string]chan IPGeoData poolMux sync.Mutex @@ -48,7 +48,7 @@ func receiveParse() { // json解析 -> data res := gjson.Parse(data) // 根据返回的IP信息,发送给对应等待回复的IP通道上 - var domain string = res.Get("domain").String() + var domain = res.Get("domain").String() if res.Get("domain").String() == "" { domain = res.Get("owner").String() diff --git a/reporter/reporter.go b/reporter/reporter.go index 26b5e6a..2ea348b 100644 --- a/reporter/reporter.go +++ b/reporter/reporter.go @@ -45,7 +45,7 @@ func experimentTag() { func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData, ttl uint16) { - var success bool = true + var success = true defer r.wg.Done() diff --git a/tracelog/log.go b/tracelog/log.go index 240dd24..2008be3 100644 --- a/tracelog/log.go +++ b/tracelog/log.go @@ -17,7 +17,12 @@ func RealtimePrinter(res *trace.Result, ttl int) { if err != nil { return } - defer f.Close() + defer func(f *os.File) { + err := f.Close() + if err != nil { + log.Fatal(err) + } + }(f) multiWriter := io.MultiWriter(os.Stdout, f) log.SetOutput(multiWriter) diff --git a/tracemap/tracemap.go b/tracemap/tracemap.go index e00a195..5546d69 100644 --- a/tracemap/tracemap.go +++ b/tracemap/tracemap.go @@ -11,7 +11,7 @@ import ( func GetMapUrl(r string) { url := "https://api.leo.moe/tracemap/api" - resp, _ := http.Post(url, "application/json", strings.NewReader(string(r))) + resp, _ := http.Post(url, "application/json", strings.NewReader(r)) body, _ := io.ReadAll(resp.Body) fmt.Fprintf(color.Output, "%s %s\n", color.New(color.FgWhite, color.Bold).Sprintf("%s", "MapTrace URL:"), diff --git a/util/util.go b/util/util.go index 1173859..40e50b9 100644 --- a/util/util.go +++ b/util/util.go @@ -10,7 +10,7 @@ import ( "github.com/fatih/color" ) -// get the local ip and port based on our destination ip +// LocalIPPort get the local ip and port based on our destination ip func LocalIPPort(dstip net.IP) (net.IP, int) { serverAddr, err := net.ResolveUDPAddr("udp", dstip.String()+":12345") if err != nil { @@ -95,7 +95,10 @@ func DomainLookUp(host string, ipv4Only bool, dotServer string) net.IP { } var index int fmt.Printf("Your Option: ") - fmt.Scanln(&index) + _, err := fmt.Scanln(&index) + if err != nil { + index = 0 + } if index >= len(ips) || index < 0 { fmt.Println("Your Option is invalid") os.Exit(3)