Compare commits

...

550 Commits

Author SHA1 Message Date
tsosunchia
2c86f86204 fix bugs:ipv6下fasttrace异常退出的问题 2023-06-04 05:27:21 +08:00
tsosunchia
dcce91614f fix bugs:ft下maxhops、pktsize不正常显示、tracemap在配置环境变量时不能正常使用、trace时无法显示第一跳 2023-06-04 04:12:57 +08:00
tsosunchia
85df0121fd UPDATE README 2023-06-03 20:45:47 +08:00
tsosunchia
4a749285be 将DNS解析与WS握手同步进行 2023-06-03 20:19:01 +08:00
tsosunchia
a92cfc7783 删除无意义文件 2023-06-03 19:52:25 +08:00
tsosunchia
268971e85b Merge pull request #128 from fakeboboliu/fix1
improve: rootless trace on macOS
2023-06-03 17:26:52 +08:00
bobo liu
2bac716bd7 improve: rootless trace on macOS 2023-06-03 16:50:21 +08:00
tsosunchia
eb64f68663 修补timeout参数 2023-06-03 06:57:44 +08:00
tsosunchia
e9ca9cf388 fixbug:tcp ipv6 trace时遵从BeginHop 2023-06-03 05:47:38 +08:00
tsosunchia
364be22383 tcp,udp模式也适用ttl-time,send-time了 2023-06-03 05:25:14 +08:00
tsosunchia
2f6a2573ae fetchipdata加mutex,以后同时仅进行一次的查询 2023-06-03 04:28:10 +08:00
tsosunchia
a25157867c fixbug: fasttrace指定网卡异常退出 2023-06-03 03:33:36 +08:00
tsosunchia
7c37598804 将GeoIPInformatinDataCache推广到所有数据源 2023-06-02 18:13:27 +08:00
tsosunchia
8d30c59c17 fasttrace增加支持SrcDev,SrcAddr,BeginHop,MaxHops,RDns,AlwaysWaitRDNS,Lang,PktSize 2023-06-02 17:32:07 +08:00
tsosunchia
e8e5c1438e Merge remote-tracking branch 'refs/remotes/origin/main' 2023-06-02 14:20:48 +08:00
tsosunchia
cb0c988eda Update bug_report.md 2023-06-02 14:20:21 +08:00
tsosunchia
b1cabbc6d4 pow_test增加计时 2023-06-02 13:57:04 +08:00
tsosunchia
2493f471dc 对pow_token也进行了缓存 2023-06-02 04:37:40 +08:00
tsosunchia
8578109243 对rdns也进行了缓存 2023-06-02 02:12:35 +08:00
tsosunchia
089250fee1 提供不间断运行环境变量 2023-06-02 01:41:11 +08:00
tsosunchia
0e87592537 增加IP查询缓存机制 2023-06-02 01:04:32 +08:00
tsosunchia
14552cd853 推介OpenTrace 2023-06-01 18:43:43 +08:00
tsosunchia
44b70cf7e7 fix typo 2023-06-01 18:24:55 +08:00
tsosunchia
3f8b043821 format readme 2023-06-01 18:12:13 +08:00
tsosunchia
fa8eb050af 更新依赖 2023-06-01 17:29:35 +08:00
tsosunchia
50582f0fd0 重构UA存放位置 2023-06-01 16:18:28 +08:00
tsosunchia
64371fb41a GetTracemap也适用优选IP 2023-06-01 15:40:04 +08:00
tsosunchia
6476c3aff3 pow模块解耦合完成 2023-06-01 14:46:52 +08:00
tsosunchia
4d7831fd29 pow使用的ip也是优选IP 2023-06-01 14:08:32 +08:00
tsosunchia
d602941722 对pow模块解耦合 2023-06-01 12:30:36 +08:00
tsosunchia
2d548fcca0 增加一个设置POWtoken的环境变量 2023-06-01 00:46:45 +08:00
tsosunchia
8a79a5dd1c 调整POW超时时间为5s 2023-05-31 22:30:31 +08:00
tsosunchia
390efdd75d Merge remote-tracking branch 'origin/main' 2023-05-31 22:20:49 +08:00
tsosunchia
d0d1511c29 增加POW机制 2023-05-31 22:20:08 +08:00
Leo
7013fb6a41 doc: maintainer 2023-05-31 20:46:10 +08:00
tsosunchia
0513ab2a1b 更新README 2023-05-31 03:19:23 +08:00
tsosunchia
11489d7027 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	README.md
2023-05-31 03:12:11 +08:00
tsosunchia
97880049f7 更新README安装指引 2023-05-31 03:10:00 +08:00
tsosunchia
781c6ccaee Merge pull request #124 from chenrui333/docs/add-homebrew-installation-note
docs: update to use homebrew-core formula
2023-05-31 03:09:34 +08:00
Rui Chen
9e84a3da65 docs: update to use homebrew-core formula
Signed-off-by: Rui Chen <rui@chenrui.dev>
2023-05-30 12:47:50 -04:00
tsosunchia
4e5cd2d053 fix bug:重构版本信息的位置解决包引用环路问题 2023-05-30 14:55:47 +08:00
tsosunchia
288aee254f 变更:获取GEOIP信息适用于timeout参数 2023-05-30 14:38:42 +08:00
tsosunchia
2d235baa53 变更:获取GEOIP信息适用于timeout参数 2023-05-30 14:37:51 +08:00
sjlleo
d23f90dd19 Merge pull request #123 from sjlleo/newargs
增加timeout和psize参数
2023-05-30 12:21:38 +08:00
tsosunchia
fdc6145087 添加一个psize参数 2023-05-30 03:37:00 +08:00
tsosunchia
1d6ce5de88 添加一个timeout参数 2023-05-30 02:24:43 +08:00
tsosunchia
86b817291d Merge pull request #122 from sjlleo/resovle_v4_v6_only
增加指定解析IPv4/IPv6的功能
2023-05-29 21:58:28 +08:00
tsosunchia
b38fc18bb4 增加指定解析IPv4/IPv6的功能 2023-05-29 20:53:07 +08:00
tsosunchia
23d20de7c8 fix bug:fasttrace也进行nslookup 2023-05-29 19:51:16 +08:00
tsosunchia
bf4ec99323 fasttraceIP改为由dns获取 2023-05-29 18:34:15 +08:00
tsosunchia
4bfe85df1b 更改为仅在使用LEOMOEAPI时尝试获取tracemap 2023-05-28 16:26:22 +08:00
tsosunchia
e48b30c47a 增加json输出模式,修正使用ipinfo API查询的IP无ASN时的异常
PS: json输出模式主要目的是方便开发者调用NEXTTRACE
2023-05-28 13:58:27 +08:00
tsosunchia
f2fbce0358 增加不输出GEOIP信息的方法
PS: 为外部程序调用预留
2023-05-26 11:13:59 +08:00
tsosunchia
98bd95d046 update readme 2023-05-25 20:54:22 +08:00
tsosunchia
b622a40022 fix bug:raw和classic模式增加语言个性化输出
注意:classic模式将会被废弃,请不要在此模式基础上开发
2023-05-25 20:40:12 +08:00
tsosunchia
6bc74e14cb 增加关于IP信息纠错的联系方式 2023-05-25 18:24:05 +08:00
tsosunchia
8233c7c303 更新新模式说明 2023-05-24 22:50:11 +08:00
tsosunchia
3084effd78 修改输出提示,显示为当前真实的maxhops设置
之前无论maxhops设置为多少,都输出为 30 hops max
2023-05-24 16:34:30 +08:00
tsosunchia
ac1ee5e08c 增加tracemap的异常处理
防止与tracemap连接出现问题时报错
2023-05-24 16:08:59 +08:00
tsosunchia
723f5c4c5e raw模式使用新参数--raw
classic模式参数保持v1.1.3版本的设置
2023-05-23 22:22:14 +08:00
Leo
308ae73728 chore: add latency 2023-05-23 21:41:16 +08:00
Leo
b88d5292da chore: Classic Printer Change 2023-05-23 20:00:42 +08:00
tsosunchia
903b6f232d Update README_zh_CN.md 2023-05-23 02:39:59 +08:00
tsosunchia
5211daf814 Update README.md 2023-05-23 02:38:56 +08:00
tsosunchia
62f817af91 Merge pull request #111 from sjlleo/dependabot/go_modules/golang.org/x/net-0.10.0 2023-05-15 20:53:18 +08:00
dependabot[bot]
5907ebcf84 chore(deps): bump golang.org/x/net from 0.9.0 to 0.10.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/net/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-15 10:02:10 +00:00
tsosunchia
825eccf986 Merge pull request #109 from sjlleo/dependabot/go_modules/golang.org/x/sync-0.2.0
chore(deps): bump golang.org/x/sync from 0.1.0 to 0.2.0
2023-05-08 19:02:51 +08:00
dependabot[bot]
a33307d746 chore(deps): bump golang.org/x/sync from 0.1.0 to 0.2.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.1.0 to 0.2.0.
- [Commits](https://github.com/golang/sync/compare/v0.1.0...v0.2.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 10:04:38 +00:00
sjlleo
eee741d848 doc: add donate link 2023-05-04 21:08:21 +08:00
sjlleo
98344193e7 doc: add donate link 2023-05-04 21:06:12 +08:00
tsosunchia
7a74bb83c2 Update basic.go
更换北京9929IP
2023-05-02 01:21:47 +08:00
sjlleo
3e010bd9da update readme 2023-04-21 19:22:47 +08:00
tsosunchia
abc1ecd035 Merge pull request #94 from sjlleo/dependabot/go_modules/golang.org/x/net-0.9.0
chore(deps): bump golang.org/x/net from 0.8.0 to 0.9.0
2023-04-10 18:38:33 +08:00
dependabot[bot]
989dc29931 chore(deps): bump golang.org/x/net from 0.8.0 to 0.9.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 10:02:16 +00:00
tsosunchia
1f2b4cde1f Merge pull request #91 from sjlleo/dependabot/github_actions/actions/setup-go-4
chore(deps): bump actions/setup-go from 3 to 4
2023-03-20 18:15:33 +08:00
dependabot[bot]
939a4c7aab chore(deps): bump actions/setup-go from 3 to 4
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 10:08:31 +00:00
tsosunchia
4890615a2b 增加了关于IPInfo和IPInsight API TOKEN的环境变量 2023-03-14 00:20:42 +08:00
tsosunchia
9b8fd51ef2 Merge pull request #90 from sjlleo/dependabot/go_modules/github.com/fatih/color-1.15.0
chore(deps): bump github.com/fatih/color from 1.14.1 to 1.15.0
2023-03-13 20:46:22 +08:00
dependabot[bot]
73c9d91637 chore(deps): bump github.com/fatih/color from 1.14.1 to 1.15.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.14.1 to 1.15.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.14.1...v1.15.0)

---
updated-dependencies:
- dependency-name: github.com/fatih/color
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 12:40:57 +00:00
tsosunchia
06e85c11c6 修改GitHub Action 2023-03-13 20:36:55 +08:00
tsosunchia
d151416566 Merge pull request #86 from sjlleo/dependabot/go_modules/golang.org/x/net-0.8.0
chore(deps): bump golang.org/x/net from 0.7.0 to 0.8.0
2023-03-12 04:03:04 +08:00
tsosunchia
b9d9853458 Merge pull request #85 from sjlleo/dependabot/github_actions/actions/setup-go-3
chore(deps): bump actions/setup-go from 2 to 3
2023-03-12 04:00:26 +08:00
dependabot[bot]
3f177c87d2 chore(deps): bump golang.org/x/net from 0.7.0 to 0.8.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 04:52:00 +00:00
dependabot[bot]
d3a00335a1 chore(deps): bump actions/setup-go from 2 to 3
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 04:51:41 +00:00
tsosunchia
aa38ed374f Create .github/dependabot.yml 2023-03-06 12:51:25 +08:00
tsosunchia
08f87cc229 Update publishNewFormula.yml 2023-03-03 16:15:31 +08:00
tsosunchia
ead7ebbc25 Update fast_trace_test.go
减少TEST耗时
2023-03-03 13:42:32 +08:00
tsosunchia
7a048fa976 Update README.md 2023-03-03 13:32:55 +08:00
sjlleo
dbb844eb44 Merge pull request #84 from sjlleo/dev
多个API增加支持
2023-03-03 13:30:20 +08:00
tsosunchia
916ac3cb69 add CHUNZHEN api 2023-03-03 12:07:37 +08:00
tsosunchia
bdfba172d1 revert 2023-03-03 12:02:48 +08:00
tsosunchia
a95a741ce2 增加scanf时的默认值 2023-03-02 22:12:34 +08:00
tsosunchia
362317e95e format 2023-03-02 21:47:59 +08:00
tsosunchia
dc735b0f97 Update ipinfo.go 转换ISO3166 2023-03-02 21:21:43 +08:00
tsosunchia
fe0fce07ec Update README 2023-03-02 19:11:26 +08:00
tsosunchia
60a4454802 Update README 2023-03-02 18:56:25 +08:00
tsosunchia
525cfd8c8f merge 2023-03-02 18:47:25 +08:00
tsosunchia
c2eebca25c 修正IPInfo BUG 和增加IPInfoLocal 2023-03-02 18:38:42 +08:00
tsosunchia
110bdc27ff 修改一些第三方API信息
Update README_zh_CN.md
Update README.md
Update ipinsight.go
Update ipgeo.go
2023-03-02 12:05:32 +08:00
tsosunchia
510b0f6c32 可使用别名调用第三方API 2023-03-02 11:48:49 +08:00
tsosunchia
8a01bccbff Update ipinsight.go 2023-03-01 18:04:06 +08:00
tsosunchia
64224c905e Update ipinsight.go 2023-03-01 18:01:18 +08:00
tsosunchia
2403308c1b Update build.yml 2023-02-23 14:53:58 +08:00
sjlleo
560a1693af chore: add more detail 2023-02-20 17:11:21 +08:00
Leo
45e9fe604d chore: clean default args setting 2023-02-20 13:16:08 +08:00
sjlleo
88e69f7dc2 feat: enable pure Go and add DoT support 2023-02-20 03:49:10 +00:00
sjlleo
b1a66546a0 Update CNAME 2023-02-17 12:48:40 +08:00
sjlleo
5c46e4eddb chore: 补充配置文件默认存放位置 2023-02-16 22:39:43 +08:00
sjlleo
30d550753c chore: DN42 配置文件教程 2023-02-16 22:17:08 +08:00
Leo
50b3e442da dump golang.org/x/net -> 0.7.0 2023-02-16 21:18:11 +08:00
Leo
ba133e3c73 feat: add DN42 Mode 2023-02-16 21:16:47 +08:00
Leo
3a5184b06c dump to Go 1.20 2023-02-16 21:16:25 +08:00
Leo
e825b8d092 fix: IPv6 no message in fast_trace 2023-02-15 18:46:00 +08:00
Leo
f64934bfde Merge branch 'main' of https://github.com/sjlleo/nexttrace 2023-02-15 18:42:36 +08:00
Leo
9871449b30 chore: IPv6 TCP Tracert Support for Fast Trace 2023-02-15 18:41:59 +08:00
tsosunchia
31a9c356e1 修正readme 2023-02-15 18:41:49 +08:00
Leo
5c0c490ae8 feat: add TCP IPv6 Support 2023-02-15 18:16:09 +08:00
tsosunchia
22660143b0 更新截图 2023-02-13 23:52:56 +08:00
tsosunchia
f4b5de08ca 更换traceMap截图 2023-02-13 23:35:56 +08:00
sjlleo
b0e1329d66 simplify build.yml 2023-02-13 09:19:48 +08:00
sjlleo
805e827e45 try add: mips64 mips64le support 2023-02-13 09:00:13 +08:00
tsosunchia
ed2250f811 fix: ipv6地址分割问题 2023-02-11 01:53:30 +08:00
tsosunchia
fe18c80f83 支持直接传入未经修剪的URL 2023-02-11 01:03:02 +08:00
tsosunchia
d1c9fd6c3a 小修改 2023-02-10 00:53:53 +08:00
tsosunchia
c67e2d6384 更新MapTrace在readme的指引 2023-02-06 23:44:31 +08:00
tsosunchia
309ae9d74a fix:macOS下报错异常 2023-02-06 09:22:27 +08:00
sjlleo
cf65ff5e7e chore: translation 2023-02-01 22:12:54 +08:00
sjlleo
eae92ebfae chore: update English doc 2023-02-01 22:10:39 +08:00
Leo
afe18fc4c0 feat: add English support 2023-02-01 21:34:33 +08:00
Leo
cc1d6177ca 重构关于地理位置以及 PTR 记录获取的模块,大幅提升路由跟踪性能 2023-02-01 18:22:02 +08:00
tsosunchia
c8077919ce 优化rdns体验,默认开启traceMap 2023-02-01 15:56:12 +08:00
tsosunchia
cfc79489d4 fix: fmt error 2023-02-01 12:45:06 +08:00
Leo
f60e6fbc99 feat: LeoMoeAPI 优选 IP 2023-01-31 11:58:55 +08:00
Leo
de0f49d01b fix: homebrew git fail 2023-01-30 10:19:37 +08:00
Leo
7ae47fdb6f fix: Fast Trace Bug && Performance Improvement 2023-01-30 09:59:10 +08:00
YekongTAT
471412b740 Merge pull request #70 from haima3/main
fix: Fixed invalid test IP for Shanghai CT (163)
2023-01-29 22:57:34 +08:00
Haima
8aca06fe24 fix: Fixed invalid test IP for Shanghai CT (163)
Minor fix: Fixed invalid Shanghai CT (163) test IPv4 IP
2023-01-29 22:55:52 +08:00
Leo
003db157a9 try fix: #67 fatal error: concurrent map read and map write 2023-01-29 21:32:33 +08:00
Leo
6e88706d62 Merge branch 'main' of https://github.com/sjlleo/nexttrace 2023-01-25 18:05:17 +08:00
Leo
0940014b09 修改默认发包间隔为100 2023-01-25 18:04:48 +08:00
sjlleo
5a7d04ab1e chore: 添加 IP 骨干网准度说明 2023-01-25 14:18:16 +08:00
tsosunchia
c6b7db8d59 解决brew包总落后release一个版本的问题
以后publishnewformula集成到build中,
publishnewformula保留,但只能手动启用
2023-01-25 12:33:06 +08:00
Leo
95f8cef54c small improvement 2023-01-24 14:47:02 +08:00
Leo
99ad5649a0 fix: filter switch-case condition 2023-01-24 08:51:53 +08:00
sjlleo
4bc4f1c176 Merge pull request #64 from tsosunchia/main
improve: 在本地对部分IP做了过滤
2023-01-24 05:37:59 +08:00
tsosunchia
b663e69343 improve: 在本地对部分IP做了过滤
IANA Reserved Address Space
2023-01-24 01:37:29 +08:00
Leo
7619e74152 feat: 添加 report 模式 2023-01-23 21:58:22 +08:00
Leo
c682f90856 feat: 小彩蛋 2023-01-23 21:25:19 +08:00
Leo
68c84488ae fix a bug caused table crash when using IPv6 2023-01-23 20:55:31 +08:00
sjlleo
deaebfa383 Merge pull request #61 from tsosunchia/patch-2
减少actions报错
2023-01-22 10:27:13 +08:00
tsosunchia
b467128c24 减少actions报错
在代码没有变化时,git commit会报错:
位于分支 main
您的分支与上游分支 'origin/main' 一致。

无文件要提交,干净的工作区
2023-01-22 09:22:43 +08:00
sjlleo
de93a2b3cb Merge pull request #60 from tsosunchia/patch-1
add feature: Read ENV to replace default endpoint
2023-01-22 04:45:07 +08:00
tsosunchia
66de7df351 add feature: Read ENV to replace default endpoint
ENV:  "NEXTTRACE_HOSTPORT"
default endpoint:  "api.leo.moe:443"
2023-01-22 00:49:33 +08:00
sjlleo
9685b80820 chore: 更新一些已经与旧版本有所不同的部分 2023-01-20 20:14:06 +08:00
sjlleo
8a3a9d5e38 chore: 调整一下模板 2023-01-20 20:06:32 +08:00
sjlleo
c826dd50ec chore: 调整一下排版 2023-01-20 19:53:25 +08:00
sjlleo
4664bc727a Update README_zh_CN.md 2023-01-20 12:23:14 +08:00
sjlleo
b66b7edc46 chore: add report 2023-01-20 10:59:30 +08:00
sjlleo
457f25035c chore: 为什么说 BestTrace 是普通人更好的选择?您何时应该选择 NextTrace? 2023-01-19 20:34:32 +08:00
sjlleo
8220bd7920 Merge pull request #59 from tsosunchia/patch-1
Update README
2023-01-19 11:37:26 +08:00
tsosunchia
372ac9d383 Update README
Update README.md, README_zh_CN.md
2023-01-19 10:30:08 +08:00
sjlleo
916a5dc9d0 fix: 不能正常显示版本号 2023-01-18 19:24:15 +08:00
Leo
b9b18f5efa feat: 增加了对发包的时间管理控制 2023-01-18 19:19:19 +08:00
Leo
326034b41e fix: panic on IPv6 2023-01-18 13:44:47 +08:00
Leo
09a5b42443 fix: 在 Table 模式下,无法自动退出的问题 2023-01-18 13:26:48 +08:00
Leo
b8542489b6 fix: 修复使用 -f 导致打印卡住的问题 2023-01-18 13:19:27 +08:00
Leo
a73a306d0a chore: sync document 2023-01-17 20:30:49 +08:00
Leo
801f42801a fix: Fast Trace 无法使用 2023-01-17 20:10:35 +08:00
Leo
08c4a5ceae fix: Fast Trace 无法使用 2023-01-17 20:09:53 +08:00
Leo
ce1c773996 improve: rDNS 解析等待时间过长 2023-01-17 20:08:57 +08:00
Leo
cdec6cbd8d feat: refactor flag 2023-01-17 19:08:32 +08:00
sjlleo
aa891ebd7c Typo 2023-01-15 15:07:29 +08:00
sjlleo
5d132e73ab chore: add announcement 2023-01-15 14:13:09 +08:00
Leo
63ca4d0418 fix: 一些包合法性判断上的错误 2023-01-14 20:22:07 +08:00
Leo
a6da078eb0 feat: 重构整个 ICMP 包校验模块,增加了对 ICMP 异步发包的支持 2023-01-14 18:15:23 +08:00
Leo
7ee76591b4 fix: Error printing method called 2023-01-13 13:48:46 +08:00
Leo
2d95fed6b2 fix: Test #342 nil pointer 2023-01-13 13:41:20 +08:00
Leo
29b1d7b283 feat: 为 table 模式增加屏幕的实时刷新功能 2023-01-13 13:32:20 +08:00
sjlleo
1e654d1400 Merge pull request #53 from tsosunchia/patch-1
fix: 当有多个路由跟踪实例运行,对输出结果造成干扰的问题 (IPv6,ICMPv6)
2023-01-13 10:35:14 +08:00
tsosunchia
1746068302 fix: 当有多个路由跟踪实例运行,对输出结果造成干扰的问题 (IPv6,ICMPv6) 2023-01-12 23:21:41 +08:00
sjlleo
45c30ddb8d Merge branch 'main' of https://github.com/xgadget-lab/nexttrace 2023-01-10 06:26:44 -05:00
sjlleo
88fef52e71 feat: add IPv6 Fast Test 2023-01-10 06:24:14 -05:00
sjlleo
491f774336 Merge pull request #51 from isyekong/main
补充架构检测
2023-01-08 21:06:58 +08:00
YekongTAT
b435a36ee4 downPath 增加判断
部分奇怪的系统可能没有 `/usr/local` 目录,如果目录不存在就把 `nexttrace` 放到 `/usr/local`
2023-01-08 20:56:36 +08:00
YekongTAT
ba87933580 补充系统架构检测 2023-01-08 19:32:22 +08:00
sjlleo
6bf243794b chore: update readme about LeoMoeAPI 2023-01-04 12:30:35 +08:00
sjlleo
44d7d5a024 chore: add traceMap readme 2022-12-18 04:13:38 -05:00
sjlleo
5f096964dc Update README_zh_CN.md 2022-12-18 17:00:13 +08:00
sjlleo
69b893a587 chore: readme_EN 2022-12-18 16:58:33 +08:00
sjlleo
ab2462bead chore: readme 2022-12-18 03:43:59 -05:00
sjlleo
d9d60d09b2 Merge branch 'main' of https://github.com/xgadget-lab/nexttrace 2022-12-17 04:14:54 -05:00
sjlleo
91cd4fb8f4 chore: add traceMap (new) 2022-12-17 04:14:39 -05:00
sjlleo
c592c14f28 chore: readme_zh-CN 2022-12-14 13:11:30 +08:00
sjlleo
15829c7041 chore: screenshot 2022-12-12 15:29:59 +08:00
sjlleo
336151dc1b chore: 调整一下判断顺序 2022-12-11 22:11:04 -05:00
sjlleo
66ee62f22b chore: 在对于普通用户运行在检测到对应 Linux Capability 以后也给予放行,不再拦截 2022-12-11 22:07:51 -05:00
sjlleo
3afd28cb89 chore: screenshots update 2022-12-10 12:36:12 +08:00
sjlleo
690f546ff0 chore: add copyright 2022-12-08 09:49:40 +08:00
sjlleo
d561063a7c add: 对 Hop 的路由表展示 2022-11-18 04:41:47 -05:00
sjlleo
473ce3c5f2 add: windows support information 2022-10-19 03:14:34 -04:00
sjlleo
a1783e3563 fix: github action cannot find windows release file 2022-10-19 03:03:47 -04:00
sjlleo
7b9912f23f fix: 修正 log 输出功能在 Fast Trace 不可用的问题 2022-10-19 02:52:25 -04:00
sjlleo
fdeaf112f5 add: Windows 系统支持, log 日志输出 2022-10-19 02:46:44 -04:00
sjlleo
07a2aac7c7 fix: project shell 2022-10-04 15:58:52 +08:00
sjlleo
08f8daf9ce remove: donate info 2022-10-04 11:57:45 +08:00
sjlleo
7872e9ee0f remove: ipdata info 2022-10-04 11:55:23 +08:00
sjlleo
991f66cfe4 fix: realtime_printer 2022-09-20 05:12:20 -04:00
sjlleo
5b91fac860 fix: 使用第三方 API 不显示 ISP 或域名 2022-09-20 04:40:59 -04:00
sjlleo
62e877e248 add: 新增离线数据库支持 2022-09-20 04:34:03 -04:00
sjlleo
9e4b2ae577 fix: → →又手滑了... rDNS 在同一个 Hop 下完全相同的神奇 Bug 2022-09-20 03:47:43 -04:00
sjlleo
b02572d6ff update: English / Chinese Screenshot 2022-09-01 21:42:37 -04:00
sjlleo
e5742e1603 update: 软件截图更新至最新版 2022-09-01 21:40:13 -04:00
sjlleo
70a727bee6 update: 关于指定网卡进行路由跟踪的说明文档 2022-09-01 21:27:00 -04:00
sjlleo
1261e243f2 add: 指定网卡进行路由跟踪 2022-09-01 21:02:15 -04:00
sjlleo
49ce0cba8e add & fix: IPWhois 功能 / IPv6 显示对齐 2022-09-01 21:01:14 -04:00
sjlleo
9764533c8e update: 依赖更新 2022-09-01 21:00:30 -04:00
sjlleo
d752385c29 add: 添加爱发电创作者捐助平台 2022-08-19 17:19:44 +08:00
sjlleo
bf005fb37a update: screenshot 2022-08-17 10:54:40 +08:00
sjlleo
8dc5960d98 修改 brew 更新触发状态 2022-08-09 17:08:24 +08:00
sjlleo
ef104673b8 ignored: trace tests 2022-08-09 03:34:30 -04:00
sjlleo
46545bd8d9 add: 更现代化且简介的显示模式 2022-08-09 03:27:06 -04:00
sjlleo
a4124b50aa refactor: WebSocket 握手逻辑改进以及心跳包检测 2022-08-09 03:25:35 -04:00
sjlleo
b09d4bab74 update: 依赖包至最新版 2022-08-09 03:24:47 -04:00
sjlleo
09e493ebc3 Merge pull request #40 from tsosunchia/patch-1
Update .cross_compile.sh
2022-07-16 15:07:40 +08:00
tsosunchia
3d665ee03c Update .cross_compile.sh
Try to fix a bug where a platform uses musl as libc
2022-07-16 15:07:03 +08:00
sjlleo
493328c7be fix: 重复 commit 2022-07-02 02:51:35 +02:00
sjlleo
e2d778c34d API接口不稳定,不需要进行测试 2022-07-02 02:43:01 +02:00
sjlleo
bf54b61eb8 update: IPv6 默认使用 LeoMoeAPI 2022-07-02 02:35:16 +02:00
sjlleo
9cec64b207 Add: Some Tips 2022-06-24 13:51:41 +08:00
sjlleo
23006acd9f Add: 开源 LeoMoeAPI 后端 2022-06-21 09:53:29 +08:00
sjlleo
0210c94651 fix: ISP Domain display incorrectly 2022-06-20 22:51:15 +08:00
sjlleo
0ccdae851d remove: LeoMoeAPI Test 2022-06-20 22:29:12 +08:00
sjlleo
ddffdb389a fix: fast trace test 2022-06-20 22:25:58 +08:00
sjlleo
c533dd34ab fix: fast trace crash 2022-06-20 22:17:38 +08:00
sjlleo
e690ad85d9 add: websocket module 2022-06-20 22:13:06 +08:00
sjlleo
00e4f9391e refactor: using websocket 2022-06-20 22:12:55 +08:00
sjlleo
2084f2c316 update: now push status will be ignored. 2022-06-18 21:03:49 +08:00
sjlleo
c9ebf7465a fix: #36 crash when beginTTL is specific. 2022-06-18 20:57:52 +08:00
sjlleo
f11f9c8234 update: 删除一些无效内容 2022-06-16 08:46:02 +08:00
sjlleo
36315c6d9d update: 更正一些错误的描述 2022-06-15 21:27:43 +08:00
sjlleo
548839f564 add: JetBrain Support 2022-06-15 16:42:46 +08:00
sjlleo
28c2961490 update: improve fast test
增加了提示的颜色,排版略作改动
2022-06-15 14:29:03 +08:00
sjlleo
e5fe66b9ab add: Star History 2022-06-15 13:17:16 +08:00
sjlleo
aca0ed10b8 update: 更精确的说法 2022-06-14 16:27:05 +08:00
sjlleo
a7fc2cd5d8 typo 2022-06-14 14:33:12 +08:00
sjlleo
a26318ff70 update: 补充一些细节 2022-06-14 14:29:53 +08:00
sjlleo
251c8aa2e8 update: generate only with new tag 2022-06-12 21:34:30 +08:00
sjlleo
89f52ca766 update: Format layout 2022-06-12 21:31:47 +08:00
sjlleo
dfabe225b9 typo: dictionary name incorrect 2022-06-12 21:03:34 +08:00
sjlleo
97172466ed add: auto generate formula file with new tag 2022-06-12 21:02:05 +08:00
sjlleo
404f0a1c62 improve: 更详细的描述 2022-06-12 17:33:26 +08:00
sjlleo
c59e843495 fix: highlight func does not work 2022-06-12 14:04:32 +08:00
sjlleo
35bc15583e update: no longer test with icmp 2022-06-12 13:07:15 +08:00
sjlleo
ee9111249c try sudo
I am not sure this can fix.
2022-06-12 12:59:30 +08:00
sjlleo
d7ab206e40 add: fast trace test 2022-06-12 12:44:50 +08:00
sjlleo
6d7eac1e16 fix: crash when geo struct is not initialized. 2022-06-12 12:38:38 +08:00
sjlleo
b8772d4cca add: detect hop type & highlight 2022-06-12 12:18:43 +08:00
sjlleo
5c94a19944 improve: readme 2022-06-11 21:05:13 +08:00
sjlleo
b7d24d8779 fix: table cannot display 2022-06-11 16:02:34 +08:00
sjlleo
f882b87a18 English README 2022-06-11 15:56:03 +08:00
sjlleo
9412189a0c Chinese(Simplified) README 2022-06-11 15:55:41 +08:00
sjlleo
fbda4fb4ad fix: Image Deformation 2022-06-11 15:36:15 +08:00
sjlleo
eccc2d1a0b update: readme fix 2022-06-11 15:24:09 +08:00
sjlleo
31f1947108 Create CNAME 2022-06-11 14:12:33 +08:00
sjlleo
d864313898 为Page适配 2022-06-11 12:21:14 +08:00
sjlleo
cc86712c23 Set theme jekyll-theme-cayman 2022-06-11 12:19:44 +08:00
sjlleo
668c46cf5a typo fix 2022-06-11 10:27:08 +08:00
sjlleo
8a428c4fb9 update: How we calibrate BackBone IP Geographical Information 2022-06-11 10:17:07 +08:00
sjlleo
569cca02d9 add 注释 2022-06-10 22:45:13 +08:00
sjlleo
90e304cf22 add 注释 2022-06-10 22:43:21 +08:00
sjlleo
d6a154deb2 Prov -> Asnumber 2022-06-10 21:43:43 +08:00
sjlleo
e4cacc569e update: API错误信息提醒 2022-06-10 21:34:40 +08:00
sjlleo
4035dd7183 Merge branch 'main' of https://github.com/xgadget-lab/nexttrace 2022-06-10 21:21:28 +08:00
sjlleo
1789448d6c add: 新增起始TTL参数 2022-06-10 21:20:43 +08:00
sjlleo
9df6c2f23c update: 频率超限显示 2022-06-10 21:20:09 +08:00
sjlleo
a37f31922c add: nexttrace enhanced info 2022-06-10 17:03:41 +08:00
sjlleo
39917bb732 fix: IP 提供商显示为空 2022-06-08 20:23:10 +08:00
sjlleo
494f2ac819 remove: 过于具体的地区信息,此类信息往往精准度有待考察 2022-06-08 20:10:17 +08:00
sjlleo
0b09addd17 improve: 实时显示 2022-06-08 20:03:47 +08:00
sjlleo
99dffc959c fix: 意外的Test错误警告 2022-06-08 20:03:29 +08:00
sjlleo
c30bcfee11 fix & improve: 测试节点的 IP 错误 路由跟踪默认配置优化 2022-06-08 19:52:19 +08:00
sjlleo
1554565460 add: 支持 ipinfo 商业版,能够读取ASN信息 2022-06-08 19:51:19 +08:00
sjlleo
2a069d7afe remove: Lite版本不再使用配置文件 2022-06-08 19:50:51 +08:00
sjlleo
e6db19f5fd update: 改进TCP/UDP的实时模式 2022-06-08 19:49:42 +08:00
sjlleo
5603317fa3 add: 中南大学教育网测试节点 2022-06-06 21:09:58 +08:00
sjlleo
3e71926127 add: 杭州电信、联通、移动、浙江大学的教育网以及中国科学技术大学的科技网与教育网 2022-06-06 20:32:35 +08:00
sjlleo
e8f74c4ad3 update: 简化步骤,关闭 Discussion 板块 2022-06-06 15:48:50 +08:00
sjlleo
aad80205c3 add: 快速测试使用文档 2022-06-06 15:43:03 +08:00
sjlleo
50cc9858d4 No Longer Needed 2022-06-06 15:29:24 +08:00
sjlleo
27f49f9cd0 Merge pull request #34 from xgadget-lab/dev/fast-test
add: 新功能,快速路由测试
2022-06-06 15:27:40 +08:00
sjlleo
e4320da08d add: 新功能,快速路由测试 2022-06-06 15:27:09 +08:00
zhshch2002
7f16a27580 update: macOS brew 安装命令 2022-06-06 11:02:21 +08:00
zhshch2002
7db77024a3 revert: .macos_compil.sh 2022-06-06 10:59:28 +08:00
zhshch2002
c4ea506c35 update: .macos_compil.sh 2022-06-06 10:32:09 +08:00
zhshch2002
39e2471845 add: macOS homebrew 编译脚本 2022-06-06 10:18:11 +08:00
sjlleo
18a9eefec9 Revert "Revert "update: 脚本运行不需要保存在本地""
This reverts commit 3f0b14341a.
2022-06-05 15:51:38 +08:00
sjlleo
1b743d1f17 Revert "update: 移除 wget 依赖"
This reverts commit 8f77050fca.
2022-06-05 15:51:10 +08:00
sjlleo
3f0b14341a Revert "update: 脚本运行不需要保存在本地"
This reverts commit 2ffd7fdb58.
2022-06-05 15:50:45 +08:00
sjlleo
2ffd7fdb58 update: 脚本运行不需要保存在本地 2022-06-05 14:33:30 +08:00
sjlleo
8f77050fca update: 移除 wget 依赖 2022-06-05 14:29:41 +08:00
sjlleo
65652bd4e2 fix: 小问题 2022-06-05 14:22:24 +08:00
sjlleo
4176407a8a update: 精简安装脚本 2022-06-05 14:19:00 +08:00
sjlleo
6bc4abeaf8 update: TCP/UDP路由跟踪时,将不会再显示IPv6 IP 2022-06-04 21:42:44 +08:00
sjlleo
af0d886a02 update: yaml.v2 should direct. 2022-06-04 21:41:49 +08:00
sjlleo
09fdd2ac37 update: 现在偏好设置支持自定义数据源、自定义显示模式,以及对部分描述不当的参数说明进行了修正 2022-06-04 21:25:56 +08:00
sjlleo
2b9d8176d4 update: 现在默认无需配置即可使用,对于高阶用户,可以使用-c生成属于自己的偏好 2022-06-04 20:28:58 +08:00
tsosunchia
ed2f89310f Update README.md 2022-06-04 17:15:23 +08:00
tsosunchia
bd5a8902d4 更改部分提示信息 2022-06-04 17:02:26 +08:00
tsosunchia
e2a1bfe8cf 把部分选项隐藏放进了--expert选项 2022-06-04 16:59:07 +08:00
zhshch2002
356b782b3d update: screenshot 2022-06-04 11:04:42 +08:00
zhshch2002
40922ae13a update: screenshot 2022-06-04 10:45:33 +08:00
tsosunchia
8e90795a54 Update nt_install.sh 2022-06-03 19:48:47 +08:00
sjlleo
bbbb2377e1 fix: 当有多个路由跟踪实例,且跟踪通一个IP的时候的干扰问题 2022-06-03 19:28:53 +08:00
sjlleo
efdfd9d612 fix: 修复一个多线程路由跟踪错乱的问题 2022-06-03 16:26:01 +08:00
sjlleo
016f06bafd cleanup 2022-06-02 21:51:50 +08:00
sjlleo
1049986ebc cleanup 2022-06-02 21:48:19 +08:00
tsosunchia
80a75288d2 由于9.0.0.0/8 以及11.0.0.0/8大量用于其他IDC业务,因此清除特殊标记 2022-06-02 20:38:43 +08:00
tsosunchia
986b8ce300 Merge remote-tracking branch 'refs/remotes/origin/main'
fetch README
2022-05-31 21:00:07 +08:00
tsosunchia
ab774406ac 减少一处警告 2022-05-31 20:59:56 +08:00
sjlleo
9604b7befe update: announcement 2022-05-31 20:48:39 +08:00
tsosunchia
dc6537005a fix some bugs 2022-05-31 19:41:12 +08:00
tsosunchia
839d227770 fix some bugs 2022-05-31 19:15:39 +08:00
tsosunchia
84e989e71b fix some bugs 2022-05-31 19:15:29 +08:00
tsosunchia
3b74b302cc fix some bugs 2022-05-31 18:35:59 +08:00
tsosunchia
9b0e58359f 改善脚本在国内环境下的表现 2022-05-31 18:27:22 +08:00
tsosunchia
f0d7151144 Update README.md 2022-05-31 18:21:31 +08:00
tsosunchia
3c65c29eff 改善脚本在国内环境下的表现 2022-05-31 18:14:51 +08:00
sjlleo
3aa4696fa9 add: 新增配置文件模块 2022-05-30 21:15:22 +08:00
sjlleo
b4abaffc7c update: 让截图大小适中 2022-05-28 11:09:45 +08:00
sjlleo
c96fb4efa3 update: IP Database Copyright的IP占比信息用表格显示 2022-05-28 10:47:03 +08:00
sjlleo
876de6bde1 update: 完善描述 2022-05-28 10:26:31 +08:00
tsosunchia
7dbec0c7a1 update README.md 增加提示 2022-05-28 02:52:27 +08:00
tsosunchia
d690f680f5 增加一些平台支持 2022-05-28 02:29:18 +08:00
tsosunchia
e96686013d 增加一些平台支持 2022-05-28 02:07:45 +08:00
tsosunchia
6726b55fc9 Update .cross_compile.sh 2022-05-28 01:58:23 +08:00
tsosunchia
9f29c75491 增加一些平台支持 2022-05-28 01:51:30 +08:00
tsosunchia
2f5bf3f195 优化提示 2022-05-28 00:40:37 +08:00
tsosunchia
5981e82ee3 fix some bugs 2022-05-28 00:31:58 +08:00
tsosunchia
80f7857a65 Merge pull request #30 from tsosunchia/main
fix some bugs
2022-05-27 23:44:14 +08:00
tsosunchia
700d38de1c fix some bugs 2022-05-27 23:43:21 +08:00
tsosunchia
2dbf3f04a4 Merge pull request #29 from tsosunchia/main
fix some bugs
2022-05-27 23:10:46 +08:00
tsosunchia
45df06ea80 fix some bugs 2022-05-27 23:10:07 +08:00
tsosunchia
98953048ce fix some bugs 2022-05-27 22:54:07 +08:00
tsosunchia
eddd4226b0 Merge pull request #28 from tsosunchia/main
修补BUG
2022-05-27 22:45:55 +08:00
tsosunchia
5afd9eb09e Merge remote-tracking branch 'refs/remotes/origin/main' 2022-05-27 22:43:55 +08:00
tsosunchia
70006aaa13 优化逻辑结构 2022-05-27 22:43:28 +08:00
tsosunchia
497fb647c0 Merge pull request #27 from tsosunchia/main
优化逻辑结构
2022-05-27 22:31:44 +08:00
tsosunchia
be8552c3cd 优化逻辑结构 2022-05-27 22:30:54 +08:00
sjlleo
f320fd6202 update: 优化逻辑,废弃 realtime 参数 2022-05-27 21:27:26 +08:00
sjlleo
a42c5e3734 update: ttl 从 1 开始 2022-05-27 21:26:47 +08:00
sjlleo
e6480c84e0 Revert "update: 优化打印逻辑,废除 realtime 参数"
This reverts commit 126115c04e.
2022-05-27 21:14:16 +08:00
sjlleo
126115c04e update: 优化打印逻辑,废除 realtime 参数 2022-05-27 21:08:30 +08:00
tsosunchia
ea7fd2af0f Update quicklytest.sh 2022-05-27 19:55:54 +08:00
tsosunchia
3fc81f4e71 Merge pull request #26 from tsosunchia/main
修补之前再部分macOS设备出现的颜色显示问题:替换macOS中bash-3.1不支持的部分指令
2022-05-27 19:37:55 +08:00
tsosunchia
0c2b77bd81 Merge branch 'main' into main 2022-05-27 19:37:48 +08:00
tsosunchia
ac33c086c6 修补之前再部分macOS设备出现的颜色显示问题:替换macOS中bash-3.1不支持的部分指令 2022-05-27 19:34:35 +08:00
zhshch2002
fadfdc87d4 fix: nt_install.sh red color 2022-05-27 19:31:38 +08:00
tsosunchia
eb77a2b69b Merge pull request #25 from tsosunchia/main
替换macOS中bash-3.1不支持的部分指令
2022-05-27 19:14:44 +08:00
tsosunchia
02e6c6e1bf 替换macOS中bash-3.1不支持的部分指令 2022-05-27 19:13:05 +08:00
tsosunchia
838af3b7a1 Merge pull request #24 from tsosunchia/main
替换macOS下bash-3.1不兼容的部分指令 issues#21
2022-05-27 19:10:10 +08:00
tsosunchia
7cd16036a6 替换macOS中bash-3.1不支持的部分指令 2022-05-27 19:08:04 +08:00
tsosunchia
2ef4f61d7b Merge branch 'xgadget-lab:main' into main 2022-05-27 18:32:46 +08:00
tsosunchia
688622738f 整理排版 2022-05-27 18:32:23 +08:00
tsosunchia
83fe583f2a Merge pull request #23 from tsosunchia/main
fix some bugs
2022-05-27 18:30:49 +08:00
tsosunchia
4b32594c17 fix some bugs 2022-05-27 18:30:18 +08:00
tsosunchia
927b6d4035 Merge pull request #22 from tsosunchia/main
修正macOS下缺少md5sum导致的运行问题  issues#21
2022-05-27 18:16:59 +08:00
tsosunchia
aa651f30cc Merge branch 'xgadget-lab:main' into main 2022-05-27 18:15:38 +08:00
tsosunchia
ffec9a93cd update nt_install.sh 修正macOS下缺少md5sum导致的运行问题 issues#21 2022-05-27 18:15:10 +08:00
sjlleo
59a744b3b5 update: 修正文档里面有一个route-path错位的问题 2022-05-27 18:13:37 +08:00
sjlleo
9656dfe172 fix: 修复潜在数组越界的问题 2022-05-27 17:19:58 +08:00
sjlleo
84c48dae99 update: 完善一下 ipapicom 2022-05-27 17:18:31 +08:00
tsosunchia
4eaac372f6 Merge pull request #20 from tsosunchia/main
add ip-api.com API
2022-05-27 13:11:26 +08:00
tsosunchia
c92d8a5172 add ip-api.com API 2022-05-27 13:09:06 +08:00
tsosunchia
acab410d4c Update nt_install.sh 2022-05-27 11:14:31 +08:00
tsosunchia
858555fd86 Merge pull request #19 from tsosunchia/main
update nt_install.sh 修正在macOS下可能出现的问题
2022-05-27 11:12:14 +08:00
tsosunchia
31e419b199 update nt_install.sh 修正在macOS下可能出现的问题 2022-05-27 11:11:51 +08:00
zhshch2002
1dddd43e67 fix: 错误默认端口号造成无法TCP Trace 2022-05-27 10:38:17 +08:00
tsosunchia
4148d0d4b1 Merge pull request #18 from tsosunchia/main
修改部分提示
2022-05-27 10:37:03 +08:00
tsosunchia
4f7977da8f 修改部分提示 2022-05-27 10:36:34 +08:00
zhshch2002
cfc8034cb4 update: 允许参数后置 2022-05-27 10:26:32 +08:00
tsosunchia
dbc0f87847 Merge pull request #17 from tsosunchia/main
修改部分提示内容
2022-05-27 10:20:57 +08:00
tsosunchia
74a320898f Merge remote-tracking branch 'refs/remotes/origin/main' 2022-05-27 10:19:40 +08:00
tsosunchia
329b3fdd6b 修改部分提示内容 2022-05-27 10:19:25 +08:00
tsosunchia
3fb88f4cf4 Merge branch 'xgadget-lab:main' into main 2022-05-27 10:12:59 +08:00
tsosunchia
1de84cac71 修改部分提示内容 2022-05-27 10:12:14 +08:00
sjlleo
83d093f5aa fix: 修复一个 report 奔溃的问题 2022-05-27 10:08:28 +08:00
sjlleo
8b03ca7a38 update mod 2022-05-27 09:58:23 +08:00
tsosunchia
7a847bf0d5 Merge pull request #16 from tsosunchia/main
update README.md 增加说明
2022-05-27 09:43:28 +08:00
tsosunchia
11fe41611c update README.md 增加说明 2022-05-27 09:43:09 +08:00
tsosunchia
71b24fb7a0 Update README.md 2022-05-27 00:45:27 +08:00
tsosunchia
0b08e4b4a4 Merge pull request #15 from tsosunchia/main
update quicklytest.sh 增加部分模式下的Route-Path打印
2022-05-26 23:31:35 +08:00
tsosunchia
2608c05da1 Update quicklytest.sh 2022-05-26 23:24:03 +08:00
tsosunchia
314bdd0cce Update quicklytest.sh
启用-report
2022-05-26 23:07:14 +08:00
tsosunchia
ea958059c6 Merge branch 'main' into main 2022-05-26 23:02:49 +08:00
tsosunchia
b59c349264 Update README.md 2022-05-26 22:55:21 +08:00
tsosunchia
030a487526 Update README.md 2022-05-26 22:45:48 +08:00
tsosunchia
cac6d33fde Update quicklytest.sh 2022-05-26 22:38:14 +08:00
tsosunchia
1725a65827 Update nt_install.sh 2022-05-26 22:38:01 +08:00
tsosunchia
4d886066a3 Update quicklytest.sh 2022-05-26 22:35:25 +08:00
tsosunchia
156043730d Update nt_install.sh 2022-05-26 22:17:40 +08:00
tsosunchia
91ad3bc539 Update nt_install.sh 2022-05-26 22:03:01 +08:00
tsosunchia
351da5f5a3 Update nt_install.sh 2022-05-26 21:57:42 +08:00
tsosunchia
f8fc90d7a5 Update quicklytest.sh 2022-05-26 21:50:15 +08:00
tsosunchia
9c75635acc Update nt_install.sh 2022-05-26 21:31:14 +08:00
sjlleo
b20b27fd20 update: 现在TCP SYN模式下也将打印路由跟踪结果 2022-05-26 21:29:17 +08:00
tsosunchia
cfc1dfdfe5 Update nt_install.sh
增加检查脚本更新功能
2022-05-26 21:28:12 +08:00
tsosunchia
97c4387af4 Update quicklytest.sh 2022-05-26 21:25:17 +08:00
tsosunchia
37b5202126 Update README.md 2022-05-26 21:08:39 +08:00
tsosunchia
afb6a3e1df Update quicklytest.sh
增加自动检测更新功能
2022-05-26 21:02:19 +08:00
tsosunchia
c9a3916cd0 Update quicklytest.sh 2022-05-26 20:58:05 +08:00
tsosunchia
89d56c437e Update quicklytest.sh 2022-05-26 20:43:06 +08:00
tsosunchia
6299dcd9a3 Update quicklytest.sh 2022-05-26 20:40:06 +08:00
tsosunchia
82f28a13f3 Update quicklytest.sh 2022-05-26 20:39:26 +08:00
tsosunchia
af732bc212 Update quicklytest.sh
增加脚本提示
2022-05-26 19:55:58 +08:00
tsosunchia
8d5f58bf15 Update quicklytest.sh 2022-05-26 19:46:54 +08:00
tsosunchia
8bd5654474 Update quicklytest.sh
增加TCP,UDP模式选项
2022-05-26 19:41:57 +08:00
tsosunchia
4de61823ee Update nt_install.sh
整理排版
2022-05-26 17:36:44 +08:00
tsosunchia
39ec016d0d Update nt_install.sh
重构结构
2022-05-26 17:30:09 +08:00
tsosunchia
67999411af fix some bugs 2022-05-25 23:52:14 +08:00
tsosunchia
7cc6b71727 fix some bugs 2022-05-25 23:40:13 +08:00
tsosunchia
16ba835537 Merge pull request #14 from tsosunchia/main
update quicklytest.sh some bugs fix
2022-05-25 18:50:09 +08:00
tsosunchia
1b7c3b8d0d update quicklytest.sh some bugs fix 2022-05-25 18:49:43 +08:00
tsosunchia
bd47935a2d Merge pull request #13 from tsosunchia/main
增删一些测试节点
2022-05-25 18:35:05 +08:00
tsosunchia
1f16001e4f 增删一些测试节点 2022-05-25 18:34:29 +08:00
sjlleo
f56e6cdba3 update: 整理排版 2022-05-25 18:15:26 +08:00
tsosunchia
ecd3df8ee8 Update quicklytest.sh
替换部分已不可使用的测试节点
2022-05-25 18:11:19 +08:00
zhshch2002
1658da1653 update: README.md img tag alt 2022-05-25 18:05:04 +08:00
zhshch2002
5110c9b990 fix: README.md typo 2022-05-25 18:04:13 +08:00
zhshch2002
aa446574f1 update: README.md screenshot.png 2022-05-25 18:02:47 +08:00
tsosunchia
2016990629 Update README.md 2022-05-25 17:50:17 +08:00
tsosunchia
e639b7b12d Merge pull request #11 from tsosunchia/main
update README.md 增加对quicklytest.sh的说明
2022-05-25 17:46:23 +08:00
tsosunchia
3cfe6598dd update README.md 增加对quicklytest.sh的说明 2022-05-25 17:45:56 +08:00
tsosunchia
59193cae47 Merge pull request #10 from tsosunchia/main
Some bugs fix
2022-05-25 17:30:40 +08:00
tsosunchia
1def15e805 some bugs fix 2022-05-25 17:30:13 +08:00
tsosunchia
1950032371 Merge branch 'xgadget-lab:main' into main 2022-05-25 17:25:58 +08:00
sjlleo
f81a0b3da3 update: 当 ip.sb 触发 cloudflare 5s 墙的时候 自动退出 2022-05-25 17:25:26 +08:00
tsosunchia
9e3d4186a1 Merge pull request #1 from xgadget-lab/main
add quicklytest.sh
2022-05-25 17:24:50 +08:00
tsosunchia
4737669436 add quicklytest.sh 修改自https://github.com/KANIKIG/worst_testrace 一个快速的脚本测试你的服务器到中国的路由 2022-05-25 17:22:40 +08:00
sjlleo
dbecfd880d update: 风控IP会直接退出,避免没有数据 2022-05-25 17:19:54 +08:00
tsosunchia
a67a4bc559 Update nt_install.sh 2022-05-25 17:08:56 +08:00
tsosunchia
077d72d5cd Update nt_install.sh 2022-05-25 17:03:35 +08:00
zhshch2002
d45b5eb032 update: 减少GitHub Actions重复运行 2022-05-25 16:56:13 +08:00
zhshch2002
5fe1110ab3 update: 减少GitHub Actions重复运行 2022-05-25 16:52:26 +08:00
tsosunchia
812c953976 Delete bug_cn.md 2022-05-25 16:08:42 +08:00
tsosunchia
f89505ab87 Update issue templates 2022-05-25 16:08:00 +08:00
tsosunchia
640eb8c02d Create bug_cn.md 2022-05-25 15:57:28 +08:00
tsosunchia
fc3462ff9e Merge pull request #9 from tsosunchia/main
update README.md 更改安装方法说明
2022-05-25 15:33:57 +08:00
tsosunchia
071a6b124a update README.md 更改安装方法说明 2022-05-25 15:33:13 +08:00
tsosunchia
45a8cf21f6 Merge pull request #8 from tsosunchia/main
add 添加定时更新任务功能
2022-05-25 15:18:53 +08:00
tsosunchia
1315efa4d2 Merge branch 'xgadget-lab:main' into main 2022-05-25 15:16:22 +08:00
tsosunchia
c2bd51faab add 自动添加定时更新任务功能 2022-05-25 15:16:02 +08:00
sjlleo
f1ce2bbb77 update: 为什么我们要尝试自己维护骨干网的IP数据库 2022-05-25 14:43:02 +08:00
sjlleo
a18bf1889b add: faq 2022-05-25 14:16:38 +08:00
tsosunchia
12c93de8c5 Merge pull request #6 from tsosunchia/main
Implement update function
2022-05-25 14:04:09 +08:00
tsosunchia
8280b62881 Merge remote-tracking branch 'refs/remotes/origin/main' 2022-05-25 14:02:40 +08:00
tsosunchia
608847b1cb Implement update function 2022-05-25 14:01:21 +08:00
tsosunchia
b4838ba402 Merge pull request #5 from tsosunchia/main
添加macOS BREW包管理器
2022-05-25 13:38:06 +08:00
tsosunchia
fbd70a8eb1 add BREW PACKAGE MANAGER 2022-05-25 13:36:54 +08:00
tsosunchia
fd1632fccb Merge branch 'xgadget-lab:main' into main 2022-05-25 13:18:06 +08:00
zhshch2002
4a725d2c48 revert: 96bb323f "update: test.yml" 2022-05-25 13:15:06 +08:00
zhshch2002
96bb323f72 update: test.yml 2022-05-25 13:09:30 +08:00
zhshch2002
961c29e499 fix: build sh version var 2022-05-25 13:04:18 +08:00
zhshch2002
870d1f3b5a update: get version from git 2022-05-25 12:56:47 +08:00
sjlleo
ebd435db53 refactor: report 支持多线程和rDNS反查 2022-05-25 10:48:47 +08:00
sjlleo
53b2249ce5 fix: 尝试性修复部分 CentOS 6、Debian8 下 wget 安装失败 2022-05-25 08:13:57 +08:00
sjlleo
7215a1e2b7 update: 更新 NextTrace 最新的 CLI 参数 2022-05-24 21:33:18 +08:00
sjlleo
24b06d2fd7 fix: strange error 2022-05-24 21:10:28 +08:00
sjlleo
f1f95dff29 fix: route-path 显示不全的问题 2022-05-24 21:05:59 +08:00
zhshch2002
0d9b8c8861 fix: typo 2022-05-24 18:27:22 +08:00
zhshch2002
290524b502 add: Version print 2022-05-24 18:14:31 +08:00
sjlleo
905ef267f2 improve 2022-05-24 11:57:50 +08:00
sjlleo
9720c19153 add: route report 2022-05-24 11:57:10 +08:00
sjlleo
f2c494441b Add files via upload 2022-05-24 11:45:02 +08:00
sjlleo
44aba64505 Delete screenshot2.png 2022-05-24 11:44:30 +08:00
sjlleo
c0be6774c1 Delete screenshot3.png 2022-05-24 11:44:19 +08:00
zhshch2002
23693895e4 update: GitHub actions build.yml 2022-05-24 11:22:13 +08:00
zhshch2002
7edebf938b remove: test for TestIPSB api 2022-05-24 11:20:23 +08:00
zhshch2002
d4a176f864 add: test github actions 2022-05-24 11:17:43 +08:00
zhshch2002
69388c956e remove: listener_channe, signal, taskgroup 2022-05-24 11:17:43 +08:00
sjlleo
738ff949b1 Special Version for my friends - missuo 2022-05-24 10:59:38 +08:00
sjlleo
b59f0eb9da update: rdns 功能介绍 2022-05-24 09:21:30 +08:00
sjlleo
c7df49ca8e improve: #4 case insensitive 2022-05-24 09:05:56 +08:00
sjlleo
4d3a52bb28 Add: IP Database Copyright 2022-05-24 08:55:41 +08:00
sjlleo
fbf7335bc8 update: "./" is no longer need here 2022-05-24 05:37:19 +08:00
tsosunchia
24eedfa3fe Update nt_install.sh 2022-05-23 23:24:42 +08:00
sjlleo
d60c80e1ff Add files via upload 2022-05-23 21:28:10 +08:00
sjlleo
4f622b7afb Add files via upload 2022-05-23 21:27:50 +08:00
sjlleo
f51b07d388 Add files via upload 2022-05-23 21:17:43 +08:00
sjlleo
679dbbf01a Delete screenshot2.png 2022-05-23 21:17:18 +08:00
sjlleo
2a5e5572b4 update: fix command not found bug 2022-05-23 21:03:44 +08:00
sjlleo
974655a1b3 update: todo update version 2022-05-23 20:55:11 +08:00
sjlleo
eb9844f924 Add: screenshots 2022-05-23 20:41:48 +08:00
sjlleo
b8a51b3c44 add: screenshots 2022-05-23 20:40:37 +08:00
sjlleo
5d984fc3ac Merge pull request #3 from tsosunchia/main
对安装脚本一点小改善
2022-05-23 20:33:30 +08:00
tsosunchia
bcea9aa2cb 安装脚本增加checkVersion为之后增加自动更新功能做准备;优化了部分逻辑结构;在部分软件包管理器增加了安装依赖包之前更新软件源的步骤 2022-05-23 20:26:53 +08:00
sjlleo
ffc2c33e63 Merge pull request #2 from tsosunchia/main
修正安装脚本在macOS运行中出现的BUG
2022-05-23 19:55:39 +08:00
tsosunchia
919335133e 修正脚本在macOS运行中出现的BUG 2022-05-23 19:29:38 +08:00
sjlleo
4d0e3eb93d update: 修改用法 2022-05-23 18:10:40 +08:00
sjlleo
56d2f0554f update: sed is uncompatible in macOS, with 1:-1 instead (Thanks to nsnnns) 2022-05-23 18:07:48 +08:00
zhshch2002
e728b6b063 refactor: icmp 2022-05-23 17:35:40 +08:00
zhshch2002
8b2d2f3990 Merge branch 'main' of https://github.com/xgadget-lab/nexttrace 2022-05-23 17:01:15 +08:00
zhshch2002
4c51b2fbbe add: realtime and table output mode
Co-authored-by: sjlleo <sjlleo@users.noreply.github.com>
2022-05-23 17:00:24 +08:00
sjlleo
9a6586f27a update: import jq package 2022-05-23 16:01:16 +08:00
sjlleo
cc8e3e4838 update: 修复命令在直接使用sudo的情况下不可用 2022-05-23 10:32:26 +08:00
sjlleo
d69b7b9acb update: 新增国内镜像加速 2022-05-23 10:18:45 +08:00
sjlleo
483a90848d update: fix a bash problem 2022-05-23 10:05:48 +08:00
sjlleo
131a9e2e8a update: add some description 2022-05-23 09:47:06 +08:00
sjlleo
982e1064c2 update: sudo check first 2022-05-23 09:45:59 +08:00
sjlleo
5ff461af42 update: apt update first 2022-05-23 09:43:45 +08:00
sjlleo
8adc98a753 update: One-Key Install Script 2022-05-23 09:41:42 +08:00
sjlleo
937113ca33 add: 一键安装、升级脚本 2022-05-23 09:39:23 +08:00
zhshch2002
9f0c62506e update: README.md 快速安装脚本 2022-05-22 21:47:56 +08:00
sjlleo
cad5f944cb 完善 2022-05-22 21:23:39 +08:00
sjlleo
0f0fb91fb6 Add: Thank Member 2022-05-22 20:26:48 +08:00
sjlleo
e1c6f1ccf6 add: Thanks Member 2022-05-22 20:23:05 +08:00
sjlleo
5ac811bbae update: 完善ReadMe描述 2022-05-22 20:18:28 +08:00
zhshch2002
dbd8ae573c Merge branch 'main' of https://github.com/xgadget-lab/nexttrace 2022-05-22 20:03:43 +08:00
sjlleo
8db4c5e7b8 Add: logo.svg file 2022-05-22 20:02:54 +08:00
zhshch2002
7db2a717a4 Merge branch 'main' of https://github.com/xgadget-lab/nexttrace 2022-05-22 20:02:47 +08:00
zhshch2002
b0ba116c91 update: README.md 2022-05-22 20:01:48 +08:00
sjlleo
5f993961ed update: fix channel closed, printer imrove 2022-05-22 19:55:27 +08:00
sjlleo
ead46decf6 Add: IPv6 Traceroute 2022-05-22 15:38:05 +08:00
sjlleo
7712ebf953 Add mips 2022-05-19 14:58:09 +08:00
sjlleo
14bbc62358 Add linux mips release 2022-05-19 14:48:25 +08:00
sjlleo
4323021f96 Change Token 2022-05-19 14:31:26 +08:00
sjlleo
cbfb37f37b Update build.yml 2022-05-19 14:00:50 +08:00
sjlleo
50d594e4df Add: icmp trace 2022-05-19 13:35:50 +08:00
sjlleo
6b08727993 update: 暂不关闭hopCh管道,原因待查 2022-05-18 21:19:19 +08:00
sjlleo
14730bb489 Update: reporter 适配新的数据结构 2022-05-18 20:57:31 +08:00
sjlleo
b688a1ae99 Merge branch 'main' of https://github.com/xgadget-lab/nexttrace 2022-05-18 20:08:11 +08:00
sjlleo
3548c47411 refactor: tcp trace 2022-05-18 18:53:02 +08:00
sjlleo
460e8ba78d Add: add special thanks 2022-05-17 19:56:36 +08:00
zhshch2002
abe4b685c4 update: printer 2022-05-15 20:19:21 +08:00
zhshch2002
9758a93382 Merge branch 'dev/refactor-methods'
# Conflicts:
#	main.go
#	util/printer/printer.go
2022-05-15 20:16:26 +08:00
zhshch2002
6fffe31506 refactor: udp trace 2022-05-15 20:09:17 +08:00
sjlleo
b7b966b93e update: 更新使用指南 2022-05-15 14:19:30 +08:00
sjlleo
433c8656a1 update: 新增 route-path 模块 2022-05-15 11:12:13 +08:00
sjlleo
1542cb4b07 update: 骨干网判断优化 2022-05-14 16:55:50 +08:00
sjlleo
06ee8f7373 add: 新增路由报告模块接口 2022-05-14 16:55:28 +08:00
sjlleo
6792bafb02 update: 整理 2022-05-14 08:48:49 +08:00
sjlleo
70305caa1c update: 改Switch判断 (Git还我头像qwq) 2022-05-14 08:38:02 +08:00
sjlleo
671ad82780 update: 完善阿里云、腾讯云的内网识别范围 2022-05-14 08:28:30 +08:00
sjlleo
e62575beba update: 完善table显示,对阿里云、腾讯云内网的识别 2022-05-14 08:28:00 +08:00
sjlleo
d92a1e10d3 update: 完善参数提醒 2022-05-14 08:27:13 +08:00
sjlleo
971d68f93f update: 架构整理 2022-05-13 21:44:43 +08:00
sjlleo
f765dbafae update: go mod 2022-05-13 20:44:04 +08:00
zhshch2002
ea7feab2f9 update: build.yml trigger condition 2022-05-13 16:55:36 +08:00
zhshch2002
e941eaa167 update: build.yml Token 2022-05-13 16:43:25 +08:00
zhshch2002
46e32d697d fix: "lab 实验室"语意重复 2022-05-13 16:41:57 +08:00
sjlleo
97578e40f7 update: 修复一个作者名称的异常 2022-05-13 16:37:31 +08:00
95 changed files with 8125 additions and 1352 deletions

View File

@@ -5,12 +5,17 @@ set -e
DIST_PREFIX="nexttrace"
DEBUG_MODE=${2}
TARGET_DIR="dist"
PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 linux/arm64"
PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips linux/mips64 linux/mipsle linux/mips64le windows/amd64 windows/arm64 openbsd/amd64 openbsd/arm64 freebsd/amd64 freebsd/arm64"
BUILD_VERSION="$(git describe --tags --always)"
BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
COMMIT_SHA1="$(git rev-parse --short HEAD)"
rm -rf ${TARGET_DIR}
mkdir ${TARGET_DIR}
for pl in ${PLATFORMS}; do
export CGO_ENABLED=0
export GOOS=$(echo ${pl} | cut -d'/' -f1)
export GOARCH=$(echo ${pl} | cut -d'/' -f2)
export TARGET=${TARGET_DIR}/${DIST_PREFIX}_${GOOS}_${GOARCH}
@@ -21,16 +26,36 @@ for pl in ${PLATFORMS}; do
echo "build => ${TARGET}"
if [ "${DEBUG_MODE}" == "debug" ]; then
go build -trimpath -gcflags "all=-N -l" -o ${TARGET} \
-ldflags "-X 'main.version=${BUILD_VERSION}' \
-X 'main.buildDate=${BUILD_DATE}' \
-X 'main.commitID=${COMMIT_SHA1}'\
-ldflags "-X 'github.com/xgadget-lab/nexttrace/config.Version=${BUILD_VERSION}' \
-X 'github.com/xgadget-lab/nexttrace/config.BuildDate=${BUILD_DATE}' \
-X 'github.com/xgadget-lab/nexttrace/config.CommitID=${COMMIT_SHA1}'\
-w -s"
else
go build -trimpath -o ${TARGET} \
-ldflags "-X 'main.version=${BUILD_VERSION}' \
-X 'main.buildDate=${BUILD_DATE}' \
-X 'main.commitID=${COMMIT_SHA1}'\
-ldflags "-X 'github.com/xgadget-lab/nexttrace/config.Version=${BUILD_VERSION}' \
-X 'github.com/xgadget-lab/nexttrace/config.BuildDate=${BUILD_DATE}' \
-X 'github.com/xgadget-lab/nexttrace/config.CommitID=${COMMIT_SHA1}'\
-w -s"
fi
done
export CGO_ENABLED=0
export GOOS='linux'
export GOARCH='arm'
export GOARM='7'
export TARGET=${TARGET_DIR}/${DIST_PREFIX}_${GOOS}_${GOARCH}v7
echo "build => ${TARGET}"
if [ "${DEBUG_MODE}" == "debug" ]; then
go build -trimpath -gcflags "all=-N -l" -o ${TARGET} \
-ldflags "-X 'github.com/xgadget-lab/nexttrace/printer.version=${BUILD_VERSION}' \
-X 'github.com/xgadget-lab/nexttrace/printer.buildDate=${BUILD_DATE}' \
-X 'github.com/xgadget-lab/nexttrace/printer.commitID=${COMMIT_SHA1}'\
-w -s"
else
go build -trimpath -o ${TARGET} \
-ldflags "-X 'github.com/xgadget-lab/nexttrace/printer.version=${BUILD_VERSION}' \
-X 'github.com/xgadget-lab/nexttrace/printer.buildDate=${BUILD_DATE}' \
-X 'github.com/xgadget-lab/nexttrace/printer.commitID=${COMMIT_SHA1}'\
-w -s"
fi

44
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,44 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
---
name: nexttrace 程序问题
about: "提交一个 nexttrace 的程序问题报告。"
copyright: [v2fly](https://github.com/v2fly)
---
<!--
除非特殊情况,请完整填写所有问题。不按模板发的 issue 将直接被关闭。
如果你遇到的问题不是 nexttrace 的 bug比如你不清楚如何配置请在 https://github.com/xgadget-lab/nexttrace/discussions 进行讨论。
-->
## 你正在使用哪个版本的 nexttrace
<!-- 比如linux_amd64 macOS_arm64 -->
## 你看到的异常现象是什么?
<!-- 请描述具体现象 -->
## 你期待看到的正常表现是怎样的?
## 请附上你的命令
<!-- 提交 issue 前,请隐去您的隐私信息 -->
## 请附上出错时软件输出的错误信息
## 是否查询过本仓库wiki有没有类似错误
<!-- wiki: https://github.com/sjlleo/nexttrace/wiki -->

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

15
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -1,27 +1,190 @@
name: Build & Release
on:
push: # 每次 push 的时候触发
name: Build Release
workflow_dispatch:
push:
branches:
- main
tags:
- "v*"
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/*.yml"
pull_request:
types: [opened, synchronize, reopened]
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/*.yml"
jobs:
release:
if: startsWith(github.ref, 'refs/tags/') # 只有这次 Commit 是 创建 Tag 时,才进行后续发布操作
build:
runs-on: ubuntu-latest
strategy:
matrix:
# Include amd64 on all platforms.
goos: [windows, freebsd, openbsd, linux, dragonfly, darwin]
goarch: [amd64, 386]
exclude:
# Exclude i386 on darwin and dragonfly.
- goarch: 386
goos: dragonfly
- goarch: 386
goos: darwin
include:
# BEIGIN MacOS ARM64
- goos: darwin
goarch: arm64
# END macOS ARM64
# BEGIN Linux ARM 5 6 7
- goos: linux
goarch: arm
goarm: 7
- goos: linux
goarch: arm
goarm: 6
- goos: linux
goarch: arm
goarm: 5
# END Linux ARM 5 6 7
# BEGIN Android ARM 8
- goos: android
goarch: arm64
# END Android ARM 8
# Windows ARM
- goos: windows
goarch: arm64
- goos: windows
goarch: arm
goarm: 7
# BEGIN Other architectures
# BEGIN riscv64 & ARM64
- goos: linux
goarch: arm64
- goos: linux
goarch: riscv64
# END riscv64 & ARM64
# BEGIN MIPS
- goos: linux
goarch: mips64
- goos: linux
goarch: mips64le
- goos: linux
goarch: mipsle
- goos: linux
goarch: mips
# END MIPS
# BEGIN PPC
- goos: linux
goarch: ppc64
- goos: linux
goarch: ppc64le
# END PPC
# BEGIN FreeBSD ARM
- goos: freebsd
goarch: arm64
- goos: freebsd
goarch: arm
goarm: 7
# END FreeBSD ARM
# BEGIN S390X
- goos: linux
goarch: s390x
# END S390X
# END Other architectures
# BEGIN OPENBSD ARM
- goos: openbsd
goarch: arm64
- goos: openbsd
goarch: arm
goarm: 7
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
GOARM: ${{ matrix.goarm }}
CGO_ENABLED: 0
steps:
- uses: actions/checkout@master # checkout 代码
- uses: actions/setup-go@v2 # 配置 Go 环境
- name: Checkout codebase
uses: actions/checkout@v3
- name: Show workflow information
run: |
if [ ! -z $GOARM ]; then
export GOARM=v$GOARM
fi
export _NAME="nexttrace_${GOOS}_${GOARCH}${GOARM}"
if [ "$GOOS" == "windows" ]; then
export _NAME="$_NAME.exe"
fi
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, RELEASE_NAME: $_NAME"
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
echo "BUILD_VERSION=$(git describe --tags --always)" >> $GITHUB_ENV
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
echo "COMMIT_SHA1=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.18" # 改成自己的版本
- run: bash .cross_compile.sh
go-version: '1.20'
- name: Get project dependencies
run: go mod download
- name: Build
run: |
go build -trimpath -o dist/${ASSET_NAME} \
-ldflags "-X 'github.com/xgadget-lab/nexttrace/config.Version=${BUILD_VERSION}' \
-X 'github.com/xgadget-lab/nexttrace/config.BuildDate=${BUILD_DATE}' \
-X 'github.com/xgadget-lab/nexttrace/config.CommitID=${COMMIT_SHA1}'\
-w -s"
- name: Upload files to Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ env.ASSET_NAME }}
path: |
dist/${{ env.ASSET_NAME }}
- name: Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v1
with: # 将下述可执行文件 release 上去
draft: false # Release草稿
files: |
dist/nexttrace_darwin_amd64
dist/nexttrace_darwin_arm64
dist/nexttrace_linux_amd64
dist/nexttrace_linux_arm64
dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GT_Token }}
publish-new-formula:
needs: build
# The type of runner that the job will run on
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Runs a single command using the runners shell
- name: config git
run: |
git config --global user.email "${{ secrets.git_mail }}"
git config --global user.name "${{ secrets.git_name }}"
- name: Clone repo
run: |
git clone https://github.com/xgadget-lab/homebrew-nexttrace.git
- name: Exec scipt
run: |
cd homebrew-nexttrace
bash genFormula.sh
# - name: setup SSH keys and known_hosts
# run: |
# mkdir -p ~/.ssh
# ssh-keyscan github.com >> ~/.ssh/known_hosts
# ssh-agent -a $SSH_AUTH_SOCK > /dev/null
# ssh-add - <<< "${{ secrets.ID_RSA }}"
# env:
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
- name: Git Push
run: |
cd homebrew-nexttrace
git commit -am 'Publish a new version with Formula' || true
git remote set-url origin https://${{ secrets.gt_token }}@github.com/xgadget-lab/homebrew-nexttrace.git
git push
# env:
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
- run: echo "🍏 This job's status is ${{ job.status }}."

45
.github/workflows/publishNewFormula.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Publish New Formula
# Controls when the action will run. Workflow runs when manually triggered using the UI
# or API.
on:
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "greet"
publish-new-formula:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Runs a single command using the runners shell
- name: config git
run: |
git config --global user.email "${{ secrets.git_mail }}"
git config --global user.name "${{ secrets.git_name }}"
- name: Clone repo
run: |
git clone https://github.com/xgadget-lab/homebrew-nexttrace.git
- name: Exec scipt
run: |
cd homebrew-nexttrace
bash genFormula.sh
# - name: setup SSH keys and known_hosts
# run: |
# mkdir -p ~/.ssh
# ssh-keyscan github.com >> ~/.ssh/known_hosts
# ssh-agent -a $SSH_AUTH_SOCK > /dev/null
# ssh-add - <<< "${{ secrets.ID_RSA }}"
# env:
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
- name: Git Push
run: |
cd homebrew-nexttrace
git commit -am 'Publish a new version with Formula' || true
git remote set-url origin https://${{ secrets.gt_token }}@github.com/xgadget-lab/homebrew-nexttrace.git
git push
# env:
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
- run: echo "🍏 This job's status is ${{ job.status }}."

43
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Test
on:
push:
branches:
- main
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/*.yml"
pull_request:
types: [opened, synchronize, reopened]
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/*.yml"
workflow_dispatch:
jobs:
test:
permissions:
contents: read
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'
check-latest: true
- name: Checkout codebase
uses: actions/checkout@v3
- name: Test with unix
if: ${{ matrix.os != 'windows-latest' }}
run: sudo go test -v -coverprofile='coverage.out' -covermode=count ./...
- name: Test with windows
if: ${{ matrix.os == 'windows-latest' }}
run: go test -v -coverprofile='coverage.out' -covermode=count ./...

2
.gitignore vendored
View File

@@ -161,3 +161,5 @@ Network Trash Folder
Temporary Items
.apdisk
# compile target directory
dist/

1
CNAME Normal file
View File

@@ -0,0 +1 @@
mtr.moe

406
README.md
View File

@@ -1,2 +1,404 @@
# traceroute
可视化路由跟踪工具
<div align="center">
<img src="asset/logo.png" height="200px" alt="NextTrace Logo"/>
</div>
## NextTrace
An open source visual routing tool that pursues light weight, developed using Golang.
NextTrace is part of the [OwO Network](https://github.com/OwO-Network) project. The project is a joint initiative of Leo and Vincent.
If you like this project, [Donate us](https://afdian.net/a/sjlleo/plan) to help us to provide long-lasting API cost expenses.
## How To Use
Document Language: English | [简体中文](README_zh_CN.md)
### Automated Install
* Linux
* One-click installation script
* Download from GitHub
```shell
bash -c "$(curl -Ls https://github.com/sjlleo/nexttrace/raw/main/nt_install.sh)"
```
* GHPROXY mirror (For use in Mainland China)
```shell
bash -c "$(curl -Ls https://ghproxy.com/https://github.com/sjlleo/nexttrace/raw/main/nt_install.sh)"
```
* Arch Linux AUR installation command
* Build from source
```shell
yay -S nexttrace
```
* Directly download bin package (only supports amd64)
```shell
yay -S nexttrace-bin
```
* The two types of AUR builds are maintained by huyz and ouuan, respectively
* Linuxbrew's installation command
Same as the macOS Homebrew's installation method (homebrew-core version only supports amd64)
* macOS
* macOS Homebrew's installation command
* Homebrew-core version
```shell
brew install nexttrace
```
* This repository's ACTIONS automatically built version (updates faster)
```shell
brew tap xgadget-lab/nexttrace && brew install nexttrace
```
* The homebrew-core build is maintained by chenrui333, please note that this version's updates may lag behind the repository Action automatically version
* Windows
* Windows Scoop installation command
* Scoop-extras version
```powershell
scoop bucket add extras && scoop install extras/nexttrace
```
* Scoop-extra is maintained by soenggam
Please note, the repositories for all of the above installation methods are maintained by open source enthusiasts. Availability and timely updates are not guaranteed. If you encounter problems, please contact the repository maintainer to solve them, or use the binary packages provided by the official build of this project.
### Manual Install
* Download the precompiled executable
For users not covered by the above methods, please go directly to [Release](https://github.com/sjlleo/nexttrace/releases/latest) to download the compiled binary executable.
* `Release` provides compiled binary executables for many systems and different architectures. If none are available, you can compile it yourself.
* Some essential dependencies of this project are not fully implemented on `Windows` by `Golang`, so currently, `NextTrace` is in an experimental support phase on the `Windows` platform.
* Install from source
After installing Go >= 1.20 yourself, you can use the following command to install
```shell
go install github.com/xgadget-lab/nexttrace@latest
```
After installation, the executable is in the `$GOPATH/bin` directory. If you have not set `GOPATH`, it is in the `$HOME/go/bin` directory.
### Get Started
`NextTrace` uses the `ICMP` protocol to perform TraceRoute requests by default, which supports both `IPv4` and `IPv6`
```bash
# IPv4 ICMP Trace
nexttrace 1.0.0.1
# URL
nexttrace http://example.com:8080/index.html?q=1
# Form printing
nexttrace --table 1.0.0.1
# An Output Easy to Parse
nexttrace --raw 1.0.0.1
nexttrace --json 1.0.0.1
# IPv4/IPv6 Resolve Only
nexttrace --ipv4 g.co
nexttrace --ipv6 g.co
# IPv6 ICMP Trace
nexttrace 2606:4700:4700::1111
# Disable Path Visualization With the -M parameter
nexttrace koreacentral.blob.core.windows.net
# MapTrace URL: https://api.leo.moe/tracemap/html/c14e439e-3250-5310-8965-42a1e3545266.html
```
PS: The routing visualization drawing module was written by [@tsosunchia](https://github.com/tsosunchia), and the specific code can be viewed at [tsosunchia/traceMap](https://github.com/tsosunchia/traceMap).
Note that in LeoMoeAPI 2.0, due to the addition of geographical location data, **we have deprecated the online query part of the OpenStreetMap API in the traceMap plugin and are using location information from our own database**.
The routing visualization function requires the geographical coordinates of each Hop, but third-party APIs generally do not provide this information, so this function is currently only supported when used with LeoMoeAPI.
`NextTrace` now supports quick testing, and friends who have a one-time backhaul routing test requirement can use it
```bash
# IPv4 ICMP Fast Test (Beijing + Shanghai + Guangzhou + Hangzhou) in China Telecom / Unicom / Mobile / Education Network
nexttrace --fast-trace
# You can also use TCP SYN for testing
nexttrace --fast-trace --tcp
```
`NextTrace` already supports route tracing for specified Network Devices
```bash
# Use eth0 network interface
nexttrace --dev eth0 2606:4700:4700::1111
# Use eth0 network interface's IP
# When using the network interface's IP for route tracing, note that the IP type to be traced should be the same as network interface's IP type (e.g. both IPv4)
nexttrace --source 204.98.134.56 9.9.9.9
```
`NextTrace` can also use `TCP` and `UDP` protocols to perform `Traceroute` requests, but `UDP` protocols only supports `IPv4` now
```bash
# TCP SYN Trace
nexttrace --tcp www.bing.com
# You can specify the port by yourself [here is 443], the default port is 80
nexttrace --tcp --port 443 2001:4860:4860::8888
# UDP Trace
nexttrace --udp 1.0.0.1
nexttrace --udp --port 53 1.0.0.1
```
`NextTrace` also supports some advanced functions, such as ttl control, concurrent probe packet count control, mode switching, etc.
```bash
# Send 2 probe packets per hop
nexttrace --queries 2 www.hkix.net
# No concurrent probe packets, only one probe packet is sent at a time
nexttrace --parallel-requests 1 www.hkix.net
# Start Trace with TTL of 5, end at TTL of 10
nexttrace --first 5 --max-hops 10 www.decix.net
# Turn off the IP reverse parsing function
nexttrace --no-rdns www.bbix.net
# Set the payload size to 1024 bytes
nexttrace --psize 1024 example.com
# Feature: print Route-Path diagram
# Route-Path diagram example:
# AS6453 Tata Communication「Singapore『Singapore』」
# ╭╯
# ╰AS9299 Philippine Long Distance Telephone Co.「Philippines『Metro Manila』」
# ╭╯
# ╰AS36776 Five9 Inc.「Philippines『Metro Manila』」
# ╭╯
# ╰AS37963 Aliyun「ALIDNS.COM『ALIDNS.COM』」
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`, `Ip2region`, `IPInfoLocal`, `CHUNZHEN`)
```bash
# 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
## 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
```bash
Example:
nexttrace --data-provider IPAPI.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 -T -q 2 --parallel-requests 1 -t -R 2001:4860:4860::8888
```
### IP Database
#### We use [bgp.tools](https://bgp.tools) as a data provider for routing tables.
NextTrace BackEnd is now open-source.
https://github.com/sjlleo/nexttrace-backend
NextTrace `LeoMoeAPI` now utilizes the Proof of Work (POW) mechanism to prevent abuse, where NextTrace introduces the powclient library as a client-side component. Both the POW CLIENT and SERVER are open source, and everyone is welcome to use them. (Please direct any POW module-related questions to the corresponding repositories)
- [GitHub - tsosunchia/powclient: Proof of Work CLIENT for NextTrace](https://github.com/tsosunchia/powclient)
- [GitHub - tsosunchia/powserver: Proof of Work SERVER for NextTrace](https://github.com/tsosunchia/powserver)
All NextTrace IP geolocation `API DEMO` can refer to [here](https://github.com/xgadget-lab/nexttrace/blob/main/ipgeo/)
### For full usage list, please refer to the usage menu
```shell
Usage: nexttrace [-h|--help] [-4|--ipv4] [-6|--ipv6] [-T|--tcp] [-U|--udp]
[-F|--fast-trace] [-p|--port <integer>] [-q|--queries
<integer>] [--parallel-requests <integer>] [-m|--max-hops
<integer>] [-d|--data-provider
(Ip2region|ip2region|IP.SB|ip.sb|IPInfo|ipinfo|IPInsight|ipinsight|IPAPI.com|ip-api.com|IPInfoLocal|ipinfolocal|chunzhen|LeoMoeAPI|leomoeapi|disable-geoip)]
[-n|--no-rdns] [-a|--always-rdns] [-P|--route-path]
[-r|--report] [--dn42] [-o|--output] [-t|--table] [--raw]
[-j|--json] [-c|--classic] [-f|--first <integer>] [-M|--map]
[-v|--version] [-s|--source "<value>"] [-D|--dev "<value>"]
[-R|--route] [-z|--send-time <integer>] [-i|--ttl-time
<integer>] [--timeout <integer>] [--psize <integer>]
[_positionalArg_nexttrace_31 "<value>"] [--dot-server
(dnssb|aliyun|dnspod|google|cloudflare)] [-g|--language
(en|cn)]
Arguments:
-h --help Print help information
-4 --ipv4 Use IPv4 only
-6 --ipv6 Use IPv6 only
-T --tcp Use TCP SYN for tracerouting (default port
is 80)
-U --udp Use UDP SYN for tracerouting (default port
is 53)
-F --fast-trace One-Key Fast Trace to China ISPs
-p --port Set the destination port to use. It is
either initial udp port value for
"default"method (incremented by each
probe, default is 33434), or initial seq
for "icmp" (incremented as well, default
from 1), or some constantdestination port
for other methods (with default of 80 for
"tcp", 53 for "udp", etc.)
-q --queries Set the number of probes per each hop.
Default: 3
--parallel-requests Set ParallelRequests number. It should be
1 when there is a multi-routing. Default:
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 [IP.SB,
IPInfo, IPInsight, IP-API.com, Ip2region,
IPInfoLocal, CHUNZHEN, disable-geoip].
Default: LeoMoeAPI
-n --no-rdns Do not resolve IP addresses to their
domain names
-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
--raw An Output Easy to Parse
-j --json Output trace results as JSON
-c --classic Classic Output trace results like
BestTrace
-f --first Start from the first_ttl hop (instead from
1). Default: 1
-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
source address in outgoing packets
-R --route Show Routing Table [Provided By BGP.Tools]
-z --send-time Set how many [milliseconds] between
sending each packet.. Useful when some
routers use rate-limit for ICMP messages.
Default: 100
-i --ttl-time Set how many [milliseconds] between
sending packets groups by TTL. Useful when
some routers use rate-limit for ICMP
messages. Default: 500
--timeout The number of [milliseconds] to keep probe
sockets open before giving up on the
connection.. Default: 1000
--psize Set the packet size (payload size).
Default: 52
--_positionalArg_nexttrace_31 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
```
## Project screenshot
![image](https://user-images.githubusercontent.com/13616352/216064486-5e0a4ad5-01d6-4b3c-85e9-2e6d2519dc5d.png)
![image](https://user-images.githubusercontent.com/59512455/218501311-1ceb9b79-79e6-4eb6-988a-9d38f626cdb8.png)
## NextTrace Enhanced
`NextTrace Enhanced` is an enhanced version for enthusiasts, `Enhanced` provides trace route calls in the form of Web API and a simple Looking Glass webpage with built-in visualization.
Please Notice that `NextTrace Enhanced` is currently not supported in English.
[https://github.com/OwO-Network/nexttrace-enhanced](https://github.com/OwO-Network/nexttrace-enhanced)
## OpenTrace
`OpenTrace` is the cross-platform `GUI` version of `NextTrace` developed by @Archeb, bringing a familiar but more powerful user experience.
This software is still in the early stages of development and may have many flaws and errors. We value your feedback.
[https://github.com/Archeb/opentrace](https://github.com/Archeb/opentrace)
## LeoMoeAPI Credit
NextTrace focuses on Golang Traceroute implementations, and its LeoMoeAPI geolocation information is not supported by raw data, so a commercial version is not possible.
The LeoMoeAPI data is subject to copyright restrictions from multiple data sources, and is only used for the purpose of displaying the geolocation of route tracing.
1. We would like to credit samleong123 for providing nodes in Malaysia, TOHUNET Looking Glass for global nodes, and Ping.sx from Misaka, where more than 80% of reliable calibration data comes from ping/mtr reports.
2. At the same time, we would like to credit isyekong for their contribution to rDNS-based calibration ideas and data. LeoMoeAPI is accelerating the development of rDNS resolution function, and has already achieved automated geolocation resolution for some backbone networks, but there are some misjudgments. We hope that NextTrace will become a One-Man ISP-friendly traceroute tool in the future, and we are working on improving the calibration of these ASN micro-backbones as much as possible.
3. In terms of development, I would like to credit missuo and zhshch for their help with Go cross-compilation, design concepts and TCP/UDP Traceroute refactoring, and tsosunchia for their support on TraceMap.
4. I would also like to credit FFEE_CO, TheresaQWQ, stydxm and others for their help. leoMoeAPI has received a lot of support since its first release, so I would like to credit them all!
We hope you can give us as much feedback as possible on IP geolocation errors (see issue) so that it can be calibrated in the first place and others can benefit from it.
## JetBrain Support
#### This Project uses [JetBrain Open-Source Project License](https://jb.gg/OpenSourceSupport). We Proudly Develop By Goland.
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/GoLand.png" title="" alt="GoLand logo" width="331">
## Credits
[BGP.TOOLS](https://bgp.tools) provided some data support for this project. And we would like to express our sincere gratitude.
[Vincent Young](https://github.com/missuo) (i@yyt.moe)
[Sam Sam](https://github.com/samleong123) (samsam123@samsam123.name.my)
[tsosunchia](https://github.com/tsosunchia)
[waiting4new](https://github.com/waiting4new)
[FFEE_CO](https://github.com/fkx4-p)
### Others
Although other third-party APIs are integrated in this project, please refer to the official website of the third-party APIs for specific TOS and AUP. If you encounter IP data errors, please contact them directly to correct them.
For feedback related to corrections about IP information, we currently have two channels available:
>- [IP 错误报告汇总帖](https://github.com/sjlleo/nexttrace/issues/41) in the GITHUB ISSUES section of this project (Recommended)
>- This project's dedicated correction email: `correction@moeqing.com` (Please note that this email is only for correcting IP-related information. For other feedback, please submit an ISSUE)
How to obtain the freshly baked binary executable of the latest commit?
> Please go to the most recent [Build & Release](https://github.com/sjlleo/nexttrace/actions/workflows/build.yml) workflow in GitHub Actions.
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=sjlleo/nexttrace&type=Date)](https://star-history.com/#sjlleo/nexttrace&Date)

449
README_zh_CN.md Normal file
View File

@@ -0,0 +1,449 @@
<div align="center">
<img src="asset/logo.png" height="200px" alt="NextTrace Logo"/>
</div>
<h1 align="center">
<br>NextTrace<br>
</h1>
<h4 align="center">一款追求轻量化的开源可视化路由跟踪工具。</h4>
<p align="center">
<a href="https://github.com/sjlleo/nexttrace/actions">
<img src="https://img.shields.io/github/actions/workflow/status/sjlleo/nexttrace/build.yml?branch=main&style=flat-square" alt="Github Actions">
</a>
<a href="https://goreportcard.com/report/github.com/sjlleo/nexttrace">
<img src="https://goreportcard.com/badge/github.com/sjlleo/nexttrace?style=flat-square">
</a>
<a href="https://github.com/sjlleo/nexttrace/releases">
<img src="https://img.shields.io/github/release/sjlleo/nexttrace/all.svg?style=flat-square">
</a>
</p>
如果您喜欢这个项目,可以通过[爱发电支持](https://afdian.net/a/sjlleo/plan)我们项目的持续发展,您的捐助将用于服务器和 API 开支,非常感谢!
## How To Use
### Before Using
使用 NextTrace 之前,我们建议您先阅读 [#IP 数据以及精准度说明](https://github.com/sjlleo/nexttrace/blob/main/README_zh_CN.md#ip-%E6%95%B0%E6%8D%AE%E4%BB%A5%E5%8F%8A%E7%B2%BE%E5%87%86%E5%BA%A6%E8%AF%B4%E6%98%8E),在了解您自己的对数据精准度需求以后再进行抉择。
### Automated Install
* Linux
* 一键安装脚本
* Github下载
```shell
bash -c "$(curl -Ls https://github.com/sjlleo/nexttrace/raw/main/nt_install.sh)"
```
* GHPROXY镜像中国大陆使用
```shell
bash -c "$(curl -Ls https://ghproxy.com/https://github.com/sjlleo/nexttrace/raw/main/nt_install.sh)"
```
* Arch Linux AUR 安装命令
* 由源码构建
```shell
yay -S nexttrace
```
* 直接下载bin包(仅支持amd64)
```shell
yay -S nexttrace-bin`
```
* AUR 的2种构建分别由 huyz 和 ouuan 维护
* Linuxbrew 安装命令
同macOS Homebrew安装方法(homebrew-core版仅支持amd64)
* macOS
* macOS Homebrew 安装命令
* homebrew-core版
```shell
brew install nexttrace
```
* 本仓库ACTIONS自动构建版(更新更快)
```shell
brew tap xgadget-lab/nexttrace && brew install nexttrace
```
* homebrew-core 构建由 chenrui333 维护请注意该版本更新可能会落后仓库Action自动构建版本
* Windows
* Windows Scoop 安装命令
* scoop-extras版
```powershell
scoop bucket add extras && scoop install extras/nexttrace
```
* scoop-extra 由 soenggam 维护
请注意,以上多种安装方式的仓库均由开源爱好者自行维护,不保证可用性和及时更新,如遇到问题请联系仓库维护者解决,或使用本项目官方编译提供的二进制包。
### Manual Install
* 下载预编译的可执行程序
对于以上方法没有涵盖的用户,请直接前往 [Release](https://github.com/sjlleo/nexttrace/releases/latest) 下载编译后的二进制可执行文件。
* `Release`里面为很多系统以及不同架构提供了编译好的二进制可执行文件,如果没有可以自行编译。
* 一些本项目的必要依赖在`Windows`上`Golang`底层实现不完全,所以目前`NextTrace`在`Windows`平台出于实验性支持阶段。
* 从源码安装
您可在自行安装Go >= 1.20后,使用以下命令安装
```shell
go install github.com/xgadget-lab/nexttrace@latest
```
安装后可执行文件在`$GOPATH/bin`目录下,如果您没有设置`GOPATH`,则在`$HOME/go/bin`目录下。
### Get Started
`NextTrace` 默认使用`ICMP`协议发起`TraceRoute`请求,该协议同时支持`IPv4`和`IPv6`
```bash
# IPv4 ICMP Trace
nexttrace 1.0.0.1
# URL
nexttrace http://example.com:8080/index.html?q=1
# 表格打印,使用 --table / -t 参数,将实时显示结果
nexttrace --table 1.0.0.1
# 一个方便供机器读取转化的模式
nexttrace --raw 1.0.0.1
nexttrace --json 1.0.0.1
# 只进行IPv4/IPv6解析
nexttrace --ipv4 g.co
nexttrace --ipv6 g.co
# IPv6 ICMP Trace
nexttrace 2606:4700:4700::1111
# 禁用路径可视化 使用 --map / -M 参数
nexttrace koreacentral.blob.core.windows.net
# MapTrace URL: https://api.leo.moe/tracemap/html/c14e439e-3250-5310-8965-42a1e3545266.html
```
PS: 路由可视化的绘制模块由 [@tsosunchia](https://github.com/tsosunchia) 同学编写,具体代码可在 [tsosunchia/traceMap](https://github.com/tsosunchia/traceMap) 查看
需要注意的是,在 LeoMoeAPI 2.0 中,由于新增了了地理位置数据,**我们已经弃用 traceMap 插件中 OpenStreetMap API 的在线查询的部分,并且使用自己数据库内的位置信息**。
路由可视化功能因为需要每个 Hop 的地理位置坐标,而第三方 API 通常不提供此类信息,所以此功能目前只支持搭配 LeoMoeAPI 使用。
`NextTrace` 现已经支持快速测试,有一次性测试回程路由需求的朋友可以使用
```bash
# 北上广(电信+联通+移动+教育网IPv4 / IPv6 ICMP 快速测试
nexttrace --fast-trace
# 也可以使用 TCP SYN 而非 ICMP 进行测试
nexttrace --fast-trace --tcp
```
`NextTrace` 已支持指定网卡进行路由跟踪
```bash
# 请注意 Lite 版本此参数不能和快速测试联用,如有需要请使用 enhanced 版本
# 使用 eth0 网卡
nexttrace --dev eth0 2606:4700:4700::1111
# 使用 eth0 网卡IP
# 网卡 IP 可以使用 ip a 或者 ifconfig 获取
# 使用网卡IP进行路由跟踪时需要注意跟踪的IP类型应该和网卡IP类型一致如都为 IPv4
nexttrace --source 204.98.134.56 9.9.9.9
```
`NextTrace` 也可以使用`TCP`和`UDP`协议发起`Traceroute`请求,不过目前`UDP`只支持`IPv4`
```bash
# TCP SYN Trace
nexttrace --tcp www.bing.com
# 可以自行指定端口[此处为443]默认80端口
nexttrace --tcp --port 443 2001:4860:4860::8888
# UDP Trace
nexttrace --udp 1.0.0.1
# 可以自行指定端口[此处为5353]默认53端口
nexttrace --udp --port 5353 1.0.0.1
```
`NextTrace`也同样支持一些进阶功能,如 TTL 控制、并发数控制、模式切换等
```bash
# 每一跳发送2个探测包
nexttrace --queries 2 www.hkix.net
# 无并发,每次只发送一个探测包
nexttrace --parallel-requests 1 www.hkix.net
# 从TTL为5开始发送探测包直到TTL为10结束
nexttrace --first 5 --max-hops 10 www.decix.net
# 关闭IP反向解析功能
nexttrace --no-rdns www.bbix.net
# 设置载荷大小为1024字节
nexttrace --psize 1024 example.com
# 特色功能打印Route-Path图
# Route-Path图示例
# AS6453 塔塔通信「Singapore『Singapore』」
# ╭╯
# ╰AS9299 Philippine Long Distance Telephone Co.「Philippines『Metro Manila』」
# ╭╯
# ╰AS36776 Five9 Inc.「Philippines『Metro Manila』」
# ╭╯
# ╰AS37963 阿里云「ALIDNS.COM『ALIDNS.COM』」
nexttrace --route-path www.time.com.my
```
`NextTrace`支持用户自主选择 IP 数据库(目前支持:`LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`, `Ip2region`, `IPInfoLocal`, `CHUNZHEN`)
```bash
# 可以自行指定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被滥用比较严重会经常出现无法查询的问题请知悉。
## 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 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 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
```
### 全部用法详见 Usage 菜单
```shell
Usage: nexttrace [-h|--help] [-4|--ipv4] [-6|--ipv6] [-T|--tcp] [-U|--udp]
[-F|--fast-trace] [-p|--port <integer>] [-q|--queries
<integer>] [--parallel-requests <integer>] [-m|--max-hops
<integer>] [-d|--data-provider
(Ip2region|ip2region|IP.SB|ip.sb|IPInfo|ipinfo|IPInsight|ipinsight|IPAPI.com|ip-api.com|IPInfoLocal|ipinfolocal|chunzhen|LeoMoeAPI|leomoeapi|disable-geoip)]
[-n|--no-rdns] [-a|--always-rdns] [-P|--route-path]
[-r|--report] [--dn42] [-o|--output] [-t|--table] [--raw]
[-j|--json] [-c|--classic] [-f|--first <integer>] [-M|--map]
[-v|--version] [-s|--source "<value>"] [-D|--dev "<value>"]
[-R|--route] [-z|--send-time <integer>] [-i|--ttl-time
<integer>] [--timeout <integer>] [--psize <integer>]
[_positionalArg_nexttrace_31 "<value>"] [--dot-server
(dnssb|aliyun|dnspod|google|cloudflare)] [-g|--language
(en|cn)]
Arguments:
-h --help Print help information
-4 --ipv4 Use IPv4 only
-6 --ipv6 Use IPv6 only
-T --tcp Use TCP SYN for tracerouting (default port
is 80)
-U --udp Use UDP SYN for tracerouting (default port
is 53)
-F --fast-trace One-Key Fast Trace to China ISPs
-p --port Set the destination port to use. It is
either initial udp port value for
"default"method (incremented by each
probe, default is 33434), or initial seq
for "icmp" (incremented as well, default
from 1), or some constantdestination port
for other methods (with default of 80 for
"tcp", 53 for "udp", etc.)
-q --queries Set the number of probes per each hop.
Default: 3
--parallel-requests Set ParallelRequests number. It should be
1 when there is a multi-routing. Default:
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 [IP.SB,
IPInfo, IPInsight, IP-API.com, Ip2region,
IPInfoLocal, CHUNZHEN, disable-geoip].
Default: LeoMoeAPI
-n --no-rdns Do not resolve IP addresses to their
domain names
-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
--raw An Output Easy to Parse
-j --json Output trace results as JSON
-c --classic Classic Output trace results like
BestTrace
-f --first Start from the first_ttl hop (instead from
1). Default: 1
-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
source address in outgoing packets
-R --route Show Routing Table [Provided By BGP.Tools]
-z --send-time Set how many [milliseconds] between
sending each packet.. Useful when some
routers use rate-limit for ICMP messages.
Default: 100
-i --ttl-time Set how many [milliseconds] between
sending packets groups by TTL. Useful when
some routers use rate-limit for ICMP
messages. Default: 500
--timeout The number of [milliseconds] to keep probe
sockets open before giving up on the
connection.. Default: 1000
--psize Set the packet size (payload size).
Default: 52
--_positionalArg_nexttrace_31 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
```
## 项目截图
![image](https://user-images.githubusercontent.com/59512455/218505939-287727ce-7207-43c4-8e31-fcda7df0b872.png)
![image](https://user-images.githubusercontent.com/59512455/218504874-06b9fa4b-48e0-420a-a195-08a1200d65a7.png)
## 第三方 IP 数据库 API 开发接口
NextTrace 所有的的 IP 地理位置 `API DEMO` 可以参考[这里](https://github.com/sjlleo/nexttrace/blob/main/ipgeo/)
你可以在这里添加你自己的 API 接口,为了 NextTrace 能够正确显示你接口中的内容,请参考 `leo.go` 中所需要的信息
✨NextTrace `LeoMoeAPI` 的后端 Demo
[GitHub - sjlleo/nexttrace-backend: NextTrace BackEnd](https://github.com/sjlleo/nexttrace-backend)
NextTrace `LeoMoeAPI`现已使用Proof of Work(POW)机制来防止滥用其中NextTrace作为客户端引入了powclient库POW CLIENT/SERVER均已开源欢迎大家使用。(POW模块相关问题请发到对应的仓库)
- [GitHub - tsosunchia/powclient: Proof of Work CLIENT for NextTrace](https://github.com/tsosunchia/powclient)
- [GitHub - tsosunchia/powserver: Proof of Work SERVER for NextTrace](https://github.com/tsosunchia/powserver)
```go
## NextTrace Enhanced
[https://github.com/OwO-Network/nexttrace-enhanced](https://github.com/OwO-Network/nexttrace-enhanced)
## OpenTrace
`OpenTrace`是 @Archeb 开发的`NextTrace`的跨平台`GUI`版本,带来您熟悉但更强大的用户体验。
该软件仍然处于早期开发阶段,可能存在许多缺陷和错误,需要您宝贵的使用反馈。
[https://github.com/Archeb/opentrace](https://github.com/Archeb/opentrace)
## Credits
[BGP.TOOLS](https://bgp.tools) 提供了本项目的一些数据支持,在此表示由衷地感谢。
[Vincent Young](https://github.com/missuo) (i@yyt.moe)
[Sam Sam](https://github.com/samleong123) (samsam123@samsam123.name.my)
[tsosunchia](https://github.com/tsosunchia)
[waiting4new](https://github.com/waiting4new)
[FFEE_CO](https://github.com/fkx4-p)
## Others
其他第三方 API 尽管集成在本项目内,但是具体的 TOS 以及 AUP请详见第三方 API 官网。如遇到 IP 数据错误,也请直接联系他们纠错。
如何获取最新commit的新鲜出炉的二进制可执行文件
>请前往GitHub Actions中最新一次 [Build & Release](https://github.com/sjlleo/nexttrace/actions/workflows/build.yml) workflow.
## IP 数据以及精准度说明
对于IP相关信息的纠错反馈我们目前开放了两个渠道
>- 本项目的GITHUB ISSUES区中的[IP 错误报告汇总帖](https://github.com/sjlleo/nexttrace/issues/41)
>- 本项目的纠错专用邮箱: `correction@moeqing.com` 请注意此邮箱仅供IP相关信息纠错专用其他反馈请发送ISSUE
NextTrace 有多个数据源可以选择,目前默认使用的 LeoMoeAPI 为我们项目维护的数据源。
该项目由 OwO Network 的 [Missuo](https://github.com/missuo) && [Leo](https://github.com/sjlleo) 发起,由 [Zhshch](https://github.com/zhshch2002/) 完成最早期架构的编写和指导,后由 Leo 完成了大部分开发工作,现主要交由 [tsosunchia](https://github.com/tsosunchia) 以及 MoeQing Network 完成后续的二开和维护工作。
LeoMoeAPI 是 [Leo](https://github.com/sjlleo) 的作品,归属于 Leo Network由 [Leo](https://github.com/sjlleo) 完成整套后端 API 编写,该接口未经允许不可用于任何第三方用途。
LeoMoeAPI 早期数据主要来自 IPInsight、IPInfo随着项目发展越来越多的志愿者参与进了这个项目。目前 LeoMoeAPI 有近一半的数据是社区提供的,而另外一半主要来自于包含 IPInfo、IPData、BigDataCloud、IPGeoLocation 在内的多个第三方数据。
LeoMoeAPI 的骨干网数据有近 70% 是社区自发反馈又或者是项目组成员校准的,这给本项目的路由跟踪基础功能带来了一定的保证,但是全球骨干网的体量庞大,我们并无能力如 IPIP 等商业公司拥有海量监测节点,这使得 LeoMoeAPI 的数据精准度无法和形如 BestTraceIPIP相提并论。
LeoMoeAPI 已经尽力校准了比较常见的骨干网路由,这部分在测试的时候经常会命中,但是如果遇到封闭型 ISP 的路由,大概率可以遇到错误,此类数据不仅是我们,哪怕 IPInsight、IPInfo 也无法正确定位,目前只有 IPIP 能够标记正确,如对此类数据的精确性有着非常高的要求,请务必使用 BestTrace 作为首选。
我们不保证我们的数据一定会及时更新,也不保证数据的精确性,我们希望您在发现数据错误的时候可以前往 issue 页面提交错误报告,谢谢。
当您使用 LeoMoeAPI 即视为您已经完全了解 NextTrace LeoMoeAPI 的数据精确性,并且同意如果您引用 LeoMoeAPI 其中的数据从而引发的一切问题,均由您自己承担。
## DN42 模式使用说明
使用这个模式需要您配置 2 个文件,分别是 geofeed.csv 以及 ptr.csv
当您初次运行 DN42 模式NT 会为您生成 nt_config.yaml 文件,您可以自定义 2 个文件的存放位置,默认应该存放在 NT 的运行目录下
### GeoFeed
对于 geofeed.csv 来说,格式如下:
```
IP_CDIR,LtdCode,ISO3166-2,CityName,ASN,IPWhois
```
比如,您可以这么写:
```
58.215.96.0/20,CN,CN-JS,Wuxi,23650,CHINANET-JS
```
如果您有一个大段作为骨干网使用,您也可以不写地理位置信息,如下:
```
202.97.0.0/16,,,4134,CHINANET-BACKBONE
```
### PTR
对于 ptr.csv 来说,格式如下:
```
IATA_CODE,LtdCode,RegionName,CityName
```
比如对于美国洛杉矶,您可以这么写
```
LAX,US,California,Los Anegles
```
需要注意的是NextTrace 支持自动匹配 CSV 中的城市名,如果您的 PTR 记录中有 `losangeles`,您可以只添加上面一条记录就可以正常识别并读取。

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

BIN
asset/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
asset/nexttrace021.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
asset/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
asset/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

369
cmd/cmd.go Normal file
View File

@@ -0,0 +1,369 @@
package cmd
import (
"encoding/json"
"fmt"
"log"
"net"
"os"
"os/signal"
"runtime"
"strings"
"time"
"github.com/akamensky/argparse"
"github.com/syndtr/gocapability/capability"
"github.com/xgadget-lab/nexttrace/config"
fastTrace "github.com/xgadget-lab/nexttrace/fast_trace"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/printer"
"github.com/xgadget-lab/nexttrace/reporter"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/tracelog"
"github.com/xgadget-lab/nexttrace/tracemap"
"github.com/xgadget-lab/nexttrace/util"
"github.com/xgadget-lab/nexttrace/wshandle"
)
func Excute() {
parser := argparse.NewParser("nexttrace", "An open source visual route tracking CLI tool")
// Create string flag
ipv4Only := parser.Flag("4", "ipv4", &argparse.Options{Help: "Use IPv4 only"})
ipv6Only := parser.Flag("6", "ipv6", &argparse.Options{Help: "Use IPv6 only"})
tcp := parser.Flag("T", "tcp", &argparse.Options{Help: "Use TCP SYN for tracerouting (default port is 80)"})
udp := parser.Flag("U", "udp", &argparse.Options{Help: "Use UDP SYN for tracerouting (default port is 53)"})
fast_trace := parser.Flag("F", "fast-trace", &argparse.Options{Help: "One-Key Fast Trace to China ISPs"})
port := parser.Int("p", "port", &argparse.Options{Help: "Set the destination port to use. It is either initial udp port value for \"default\"" +
"method (incremented by each probe, default is 33434), or initial seq for \"icmp\" (incremented as well, default from 1), or some constant" +
"destination port for other methods (with default of 80 for \"tcp\", 53 for \"udp\", etc.)"})
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{"Ip2region", "ip2region", "IP.SB", "ip.sb", "IPInfo", "ipinfo", "IPInsight", "ipinsight", "IPAPI.com", "ip-api.com", "IPInfoLocal", "ipinfolocal", "chunzhen", "LeoMoeAPI", "leomoeapi", "disable-geoip"}, &argparse.Options{Default: "LeoMoeAPI",
Help: "Choose IP Geograph Data Provider [IP.SB, IPInfo, IPInsight, IP-API.com, Ip2region, IPInfoLocal, CHUNZHEN, disable-geoip]"})
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"})
report := parser.Flag("r", "report", &argparse.Options{Help: "output using report mode"})
dn42 := parser.Flag("", "dn42", &argparse.Options{Help: "DN42 Mode"})
output := parser.Flag("o", "output", &argparse.Options{Help: "Write trace result to file (RealTimePrinter ONLY)"})
tablePrint := parser.Flag("t", "table", &argparse.Options{Help: "Output trace results as table"})
rawPrint := parser.Flag("", "raw", &argparse.Options{Help: "An Output Easy to Parse"})
jsonPrint := parser.Flag("j", "json", &argparse.Options{Help: "Output trace results as JSON"})
classicPrint := parser.Flag("c", "classic", &argparse.Options{Help: "Classic Output trace results like BestTrace"})
beginHop := parser.Int("f", "first", &argparse.Options{Default: 1, Help: "Start from the first_ttl hop (instead from 1)"})
disableMaptrace := parser.Flag("M", "map", &argparse.Options{Help: "Disable Print Trace Map"})
ver := parser.Flag("v", "version", &argparse.Options{Help: "Print version info and exit"})
srcAddr := parser.String("s", "source", &argparse.Options{Help: "Use source src_addr for outgoing packets"})
srcDev := parser.String("D", "dev", &argparse.Options{Help: "Use the following Network Devices as the source address in outgoing packets"})
router := parser.Flag("R", "route", &argparse.Options{Help: "Show Routing Table [Provided By BGP.Tools]"})
packetInterval := parser.Int("z", "send-time", &argparse.Options{Default: 100, Help: "Set how many [milliseconds] between sending each packet.. Useful when some routers use rate-limit for ICMP messages"})
ttlInterval := parser.Int("i", "ttl-time", &argparse.Options{Default: 500, Help: "Set how many [milliseconds] between sending packets groups by TTL. Useful when some routers use rate-limit for ICMP messages"})
timeout := parser.Int("", "timeout", &argparse.Options{Default: 1000, Help: "The number of [milliseconds] to keep probe sockets open before giving up on the connection."})
packetSize := parser.Int("", "psize", &argparse.Options{Default: 52, Help: "Set the packet size (payload size)"})
str := parser.StringPositional(&argparse.Options{Help: "IP Address or domain name"})
dot := parser.Selector("", "dot-server", []string{"dnssb", "aliyun", "dnspod", "google", "cloudflare"}, &argparse.Options{
Help: "Use DoT Server for DNS Parse [dnssb, aliyun, dnspod, google, cloudflare]"})
lang := parser.Selector("g", "language", []string{"en", "cn"}, &argparse.Options{Default: "cn",
Help: "Choose the language for displaying [en, cn]"})
err := parser.Parse(os.Args)
if err != nil {
// In case of error print error and print usage
// This can also be done by passing -h or --help flags
fmt.Print(parser.Usage(err))
return
}
if !*jsonPrint {
printer.Version()
}
if *ver {
printer.CopyRight()
os.Exit(0)
}
domain := *str
if *port == 0 {
*port = 80
}
if *fast_trace {
var paramsFastTrace = fastTrace.ParamsFastTrace{
SrcDev: *srcDev,
SrcAddr: *srcAddr,
BeginHop: *beginHop,
MaxHops: *maxHops,
RDns: !*noRdns,
AlwaysWaitRDNS: *alwaysRdns,
Lang: *lang,
PktSize: *packetSize,
Timeout: time.Duration(*timeout) * time.Millisecond,
}
fastTrace.FastTest(*tcp, *output, paramsFastTrace)
if *output {
fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中")
}
os.Exit(0)
}
// DOMAIN处理开始
if domain == "" {
fmt.Print(parser.Usage(err))
return
}
if strings.Contains(domain, "/") {
domain = strings.Split(domain, "/")[2]
}
if strings.Contains(domain, "]") {
domain = strings.Split(strings.Split(domain, "]")[0], "[")[1]
} else if strings.Contains(domain, ":") {
if strings.Count(domain, ":") == 1 {
domain = strings.Split(domain, ":")[0]
}
}
// DOMAIN处理结束
capabilities_check()
// return
var ip net.IP
if runtime.GOOS == "windows" && (*tcp || *udp) {
fmt.Println("NextTrace 基于 Windows 的路由跟踪还在早期开发阶段目前还存在诸多问题TCP/UDP SYN 包请求可能不能正常运行")
}
if *dn42 {
// 初始化配置
config.InitConfig()
*dataOrigin = "DN42"
*disableMaptrace = true
}
/**
* 此处若使用goroutine同时运行ws的建立与nslookup
* 会导致第一跳的IP信息无法获取原因不明。
*/
//var wg sync.WaitGroup
//wg.Add(2)
//
//go func() {
// defer wg.Done()
if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" {
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()
}()
}
}
//}()
//
//go func() {
// defer wg.Done()
if *udp {
if *ipv6Only {
fmt.Println("[Info] IPv6 UDP Traceroute is not supported right now.")
os.Exit(0)
}
ip = util.DomainLookUp(domain, "4", *dot, *jsonPrint)
} else {
if *ipv6Only {
ip = util.DomainLookUp(domain, "6", *dot, *jsonPrint)
} else if *ipv4Only {
ip = util.DomainLookUp(domain, "4", *dot, *jsonPrint)
} else {
ip = util.DomainLookUp(domain, "all", *dot, *jsonPrint)
}
}
//}()
//
//wg.Wait()
if *srcDev != "" {
dev, _ := net.InterfaceByName(*srcDev)
if addrs, err := dev.Addrs(); err == nil {
for _, addr := range addrs {
if (addr.(*net.IPNet).IP.To4() == nil) == (ip.To4() == nil) {
*srcAddr = addr.(*net.IPNet).IP.String()
}
}
}
}
if !*jsonPrint {
printer.PrintTraceRouteNav(ip, domain, *dataOrigin, *maxHops, *packetSize)
}
var m trace.Method = ""
switch {
case *tcp:
m = trace.TCPTrace
case *udp:
m = trace.UDPTrace
default:
m = trace.ICMPTrace
}
if !*tcp && *port == 80 {
*port = 53
}
var conf = trace.Config{
DN42: *dn42,
SrcAddr: *srcAddr,
BeginHop: *beginHop,
DestIP: ip,
DestPort: *port,
MaxHops: *maxHops,
PacketInterval: *packetInterval,
TTLInterval: *ttlInterval,
NumMeasurements: *numMeasurements,
ParallelRequests: *parallelRequests,
Lang: *lang,
RDns: !*noRdns,
AlwaysWaitRDNS: *alwaysRdns,
IPGeoSource: ipgeo.GetSource(*dataOrigin),
Timeout: time.Duration(*timeout) * time.Millisecond,
PktSize: *packetSize,
}
if !*tablePrint {
if *classicPrint {
conf.RealtimePrinter = printer.ClassicPrinter
} else if *rawPrint {
conf.RealtimePrinter = printer.EasyPrinter
} else {
if *output {
conf.RealtimePrinter = tracelog.RealtimePrinter
} else if *router {
conf.RealtimePrinter = printer.RealtimePrinterWithRouter
fmt.Println("路由表数据源由 BGP.Tools 提供,在此特表感谢")
} else {
conf.RealtimePrinter = printer.RealtimePrinter
}
}
} else {
if !*report {
conf.AsyncPrinter = printer.TracerouteTablePrinter
}
}
if *jsonPrint {
conf.RealtimePrinter = nil
conf.AsyncPrinter = nil
}
if util.Uninterrupted != "" && *rawPrint {
for {
_, err := trace.Traceroute(m, conf)
if err != nil {
fmt.Println(err)
}
}
}
res, err := trace.Traceroute(m, conf)
if err != nil {
log.Fatalln(err)
}
if *tablePrint {
printer.TracerouteTablePrinter(res)
}
if *routePath {
r := reporter.New(res, ip.String())
r.Print()
}
r, err := json.Marshal(res)
if err != nil {
fmt.Println(err)
return
}
if !*disableMaptrace && strings.ToUpper(*dataOrigin) == "LEOMOEAPI" {
url, err := tracemap.GetMapUrl(string(r))
if err != nil {
log.Fatalln(err)
}
res.TraceMapUrl = url
if !*jsonPrint {
tracemap.PrintMapUrl(url)
}
}
r, err = json.Marshal(res)
if err != nil {
fmt.Println(err)
return
}
if *jsonPrint {
fmt.Println(string(r))
}
}
func capabilities_check() {
// Windows 判断放在前面,防止遇到一些奇奇怪怪的问题
if runtime.GOOS == "windows" {
// Running on Windows, skip checking capabilities
return
}
uid := os.Getuid()
if uid == 0 {
// Running as root, skip checking capabilities
return
}
/***
* 检查当前进程是否有两个关键的权限
==== 看不到我 ====
* 没办法啦
* 自己之前承诺的坑补全篇
* 被迫填坑系列 qwq
==== 看不到我 ====
***/
// NewPid 已经被废弃了,这里改用 NewPid2 方法
caps, err := capability.NewPid2(0)
if err != nil {
// 判断是否为macOS
if runtime.GOOS == "darwin" {
// macOS下报错有问题
} else {
fmt.Println(err)
}
return
}
// load 获取全部的 caps 信息
err = caps.Load()
if err != nil {
fmt.Println(err)
return
}
// 判断一下权限有木有
if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) && caps.Get(capability.EFFECTIVE, capability.CAP_NET_ADMIN) {
// 有权限啦
return
} else {
// 没权限啦
fmt.Println("您正在以普通用户权限运行 NextTrace但 NextTrace 未被赋予监听网络套接字的ICMP消息包、修改IP头信息TTL等路由跟踪所需的权限")
fmt.Println("请使用管理员用户执行 `sudo setcap cap_net_raw,cap_net_admin+eip ${your_nexttrace_path}/nexttrace` 命令,赋予相关权限后再运行~")
fmt.Println("什么?为什么 ping 普通用户执行不要 root 权限?因为这些工具在管理员安装时就已经被赋予了一些必要的权限,具体请使用 `getcap /usr/bin/ping` 查看")
}
}

7
cmd/cmd_test.go Normal file
View File

@@ -0,0 +1,7 @@
package cmd
import "testing"
func TestCMD(t *testing.T) {
Excute()
}

5
config/basic.go Normal file
View File

@@ -0,0 +1,5 @@
package config
var Version = "v0.0.0.alpha"
var BuildDate = ""
var CommitID = ""

40
config/viper.go Normal file
View File

@@ -0,0 +1,40 @@
package config
import (
"fmt"
"github.com/spf13/viper"
)
func InitConfig() {
// 配置文件名, 不加扩展
viper.SetConfigName("nt_config") // name of config file (without extension)
// 设置文件的扩展名
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
// 查找配置文件所在路径
viper.AddConfigPath("/etc/bin/nexttrace/")
viper.AddConfigPath("/usr/local/bin/nexttrace/")
// 在当前路径进行查找
viper.AddConfigPath(".")
// viper.AddConfigPath("./config/")
// 配置默认值
viper.SetDefault("ptrPath", "./ptr.csv")
viper.SetDefault("geoFeedPath", "./geofeed.csv")
// 开始查找并读取配置文件
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
fmt.Println("未能找到配置文件,我们将在您的运行目录为您创建 nt_config.yaml 默认配置")
err := viper.SafeWriteConfigAs("./nt_config.yaml")
if err != nil {
return
}
}
err = viper.ReadInConfig()
if err != nil {
return
}
}

7
dn42/dn42.go Normal file
View File

@@ -0,0 +1,7 @@
package dn42
/***
[DN42 Package]
谨献给 DN42 所有的小伙伴们,祝你们终有一天能有自己的公网 ASN ~
By Leo
***/

101
dn42/geofeed.go Normal file
View File

@@ -0,0 +1,101 @@
package dn42
import (
"encoding/csv"
"net"
"os"
"sort"
"github.com/spf13/viper"
)
type GeoFeedRow struct {
IPNet *net.IPNet
CIDR string
LtdCode string
ISO3166 string
City string
ASN string
IPWhois string
}
func GetGeoFeed(ip string) (GeoFeedRow, bool) {
rows, err := ReadGeoFeed()
if err != nil {
// 处理错误
panic(err)
}
row, find := FindGeoFeedRow(ip, rows)
return row, find
}
func ReadGeoFeed() ([]GeoFeedRow, error) {
path := viper.Get("geoFeedPath").(string)
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
r := csv.NewReader(f)
rows, err := r.ReadAll()
if err != nil {
return nil, err
}
// 将 CSV 中的每一行转换为 GeoFeedRow 类型,并保存到 rowsSlice 中
var rowsSlice []GeoFeedRow
for _, row := range rows {
cidr := row[0] // 假设第一列是 CIDR 字段
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
// 如果解析 CIDR 失败,跳过这一行
continue
}
if len(row) == 4 {
rowsSlice = append(rowsSlice, GeoFeedRow{
IPNet: ipnet,
CIDR: cidr,
LtdCode: row[1],
ISO3166: row[2],
City: row[3],
})
} else {
rowsSlice = append(rowsSlice, GeoFeedRow{
IPNet: ipnet,
CIDR: cidr,
LtdCode: row[1],
ISO3166: row[2],
City: row[3],
ASN: row[4],
IPWhois: row[5],
})
}
}
// 根据 CIDR 范围从小到大排序,方便后面查找
sort.Slice(rowsSlice, func(i, j int) bool {
return rowsSlice[i].IPNet.Mask.String() > rowsSlice[j].IPNet.Mask.String()
})
return rowsSlice, nil
}
func FindGeoFeedRow(ipStr string, rows []GeoFeedRow) (GeoFeedRow, bool) {
ip := net.ParseIP(ipStr)
if ip == nil {
// 如果传入的 IP 无效,直接返回
return GeoFeedRow{}, false
}
// 遍历每个 CIDR 范围,找到第一个包含传入的 IP 的 CIDR
for _, row := range rows {
if row.IPNet.Contains(ip) {
return row, true
}
}
return GeoFeedRow{}, false
}

17
dn42/geofeed_test.go Normal file
View File

@@ -0,0 +1,17 @@
package dn42
// func TestGeoFeed(t *testing.T) {
// rows, err := ReadGeoFeed()
// if err != nil {
// // 处理错误
// }
// row, found := FindGeoFeedRow("2001:0418:1403:8080::6fff", rows)
// if found {
// // 处理符合条件的 row
// log.Println(row)
// } else {
// // 处理未找到的情况
// }
// }

83
dn42/ptr.go Normal file
View File

@@ -0,0 +1,83 @@
package dn42
import (
"encoding/csv"
"errors"
"fmt"
"os"
"regexp"
"strings"
"github.com/spf13/viper"
)
type PtrRow struct {
IATACode string
LtdCode string
Region string
City string
}
func matchesPattern(prefix string, s string) bool {
pattern := fmt.Sprintf(`^(.*[-.\d]|^)%s[-.\d].*$`, prefix)
r, err := regexp.Compile(pattern)
if err != nil {
fmt.Println("Invalid regular expression:", err)
return false
}
return r.MatchString(s)
}
func FindPtrRecord(ptr string) (PtrRow, error) {
path := viper.Get("ptrPath").(string)
f, err := os.Open(path)
if err != nil {
return PtrRow{}, err
}
defer f.Close()
r := csv.NewReader(f)
rows, err := r.ReadAll()
if err != nil {
return PtrRow{}, err
}
// 转小写
ptr = strings.ToLower(ptr)
// 先查城市名
for _, row := range rows {
city := row[3]
if city == "" {
continue
}
city = strings.ReplaceAll(city, " ", "")
city = strings.ToLower(city)
if matchesPattern(city, ptr) {
return PtrRow{
LtdCode: row[1],
Region: row[2],
City: row[3],
}, nil
}
}
// 查 IATA Code
for _, row := range rows {
iata := row[0]
if iata == "" {
continue
}
iata = strings.ToLower(iata)
if matchesPattern(iata, ptr) {
return PtrRow{
IATACode: iata,
LtdCode: row[1],
Region: row[2],
City: row[3],
}, nil
}
}
return PtrRow{}, errors.New("ptr not found")
}

50
dn42/ptr_test.go Normal file
View File

@@ -0,0 +1,50 @@
package dn42
import (
"testing"
)
func TestPTR(t *testing.T) {
// example_mis := []string{
// "sloutravel.com",
// "memeslou.org",
// "followsloucity.net",
// "slouslou.slou",
// "slouslou8.slou",
// }
// examples := []string{
// "1ge.slou.as1299.net",
// "1ge.slou2.as1299.net",
// "1ge-slou.as1299.net",
// "slou-1.as1299.net",
// "slou.as1299.com",
// "1ge-snge-6.as1299.net",
// "c-1.sin.sg.atlas.moeqing.com",
// "core.hkg1.hk.atlas.moeqing.com",
// "core.losangles.us.atlas.moeqing.com",
// }
// fmt.Println("容易误匹配的 PTR")
// for _, s := range example_mis {
// if r, err := FindPtrRecord("ptr.csv"); err == nil {
// fmt.Println(s, r)
// } else {
// fmt.Println(s, err)
// }
// }
// fmt.Println("\n应该正常匹配的 PTR")
// for _, s := range examples {
// if r, err := FindPtrRecord("ptr.csv"); err == nil {
// fmt.Println(s, r)
// } else {
// fmt.Println(s, err)
// }
// }
}

186
fast_trace/basic.go Normal file
View File

@@ -0,0 +1,186 @@
package fastTrace
type AllLocationCollection struct {
Beijing BackBoneCollection
Shanghai BackBoneCollection
Guangzhou BackBoneCollection
Hangzhou BackBoneCollection
Hefei BackBoneCollection
Changsha BackBoneCollection
}
type BackBoneCollection struct {
Location string
CT163 ISPCollection
CTCN2 ISPCollection
CU169 ISPCollection
CU9929 ISPCollection
CM ISPCollection
CMIN2 ISPCollection
EDU ISPCollection
CST ISPCollection
}
type ISPCollection struct {
ISPName string
IP string
IPv6 string
}
const (
CT163 string = "电信 163 AS4134"
CTCN2 string = "电信 CN2 AS4809"
CU169 string = "联通 169 AS4837"
CU9929 string = "联通 A网 AS9929"
CM string = "移动 骨干网 AS9808"
CMIN2 string = "移动 CMIN2 AS58807"
EDU string = "教育网 CERNET AS4538"
)
var TestIPsCollection = AllLocationCollection{
Beijing: Beijing,
Shanghai: Shanghai,
Guangzhou: Guangzhou,
Hangzhou: Hangzhou,
Hefei: Hefei,
}
var Beijing = BackBoneCollection{
Location: "北京",
CT163: ISPCollection{
ISPName: CT163,
IP: "ipv4.pek-4134.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.pek-4134.nexttrace-io-fasttrace-endpoint.win.",
},
CU169: ISPCollection{
ISPName: CU169,
IP: "ipv4.pek-4837.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.pek-4837.nexttrace-io-fasttrace-endpoint.win.",
},
CU9929: ISPCollection{
ISPName: CU9929,
IP: "ipv4.pek-9929.nexttrace-io-fasttrace-endpoint.win.",
},
CM: ISPCollection{
ISPName: CM,
IP: "ipv4.pek-9808.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.pek-9808.nexttrace-io-fasttrace-endpoint.win.",
},
CMIN2: ISPCollection{
ISPName: CMIN2,
IP: "ipv4.pek-58807.nexttrace-io-fasttrace-endpoint.win.",
},
EDU: ISPCollection{
ISPName: EDU,
IP: "ipv4.pek-4538.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.pek-4538.nexttrace-io-fasttrace-endpoint.win.",
},
}
var Shanghai = BackBoneCollection{
Location: "上海",
CT163: ISPCollection{
ISPName: CT163,
IP: "ipv4.sha-4134.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.sha-4134.nexttrace-io-fasttrace-endpoint.win.",
},
CTCN2: ISPCollection{
ISPName: CTCN2,
IP: "ipv4.sha-4809.nexttrace-io-fasttrace-endpoint.win.",
},
CU169: ISPCollection{
ISPName: CU169,
IP: "ipv4.sha-4837.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.sha-4837.nexttrace-io-fasttrace-endpoint.win.",
},
CU9929: ISPCollection{
ISPName: CU9929,
IP: "ipv4.sha-9929.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.sha-9929.nexttrace-io-fasttrace-endpoint.win.",
},
CM: ISPCollection{
ISPName: CM,
IP: "ipv4.sha-9808.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.sha-9808.nexttrace-io-fasttrace-endpoint.win.",
},
CMIN2: ISPCollection{
ISPName: CMIN2,
IP: "ipv4.sha-58807.nexttrace-io-fasttrace-endpoint.win.",
},
EDU: ISPCollection{
ISPName: EDU,
IP: "ipv4.sha-4538.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.sha-4538.nexttrace-io-fasttrace-endpoint.win.",
},
}
var Guangzhou = BackBoneCollection{
Location: "广州",
CT163: ISPCollection{
ISPName: CT163,
IP: "ipv4.can-4134.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.can-4134.nexttrace-io-fasttrace-endpoint.win.",
},
CU169: ISPCollection{
ISPName: CU169,
IP: "ipv4.can-4837.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.can-4837.nexttrace-io-fasttrace-endpoint.win.",
},
CM: ISPCollection{
ISPName: CM,
IP: "ipv4.can-9808.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.can-9808.nexttrace-io-fasttrace-endpoint.win.",
},
}
var Hangzhou = BackBoneCollection{
Location: "杭州",
CT163: ISPCollection{
ISPName: CT163,
IP: "ipv4.hgh-4134.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.hgh-4134.nexttrace-io-fasttrace-endpoint.win.",
},
CU169: ISPCollection{
ISPName: CU169,
IP: "ipv4.hgh-4837.nexttrace-io-fasttrace-endpoint.win.",
},
CM: ISPCollection{
ISPName: CM,
IP: "ipv4.hgh-9808.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.hgh-9808.nexttrace-io-fasttrace-endpoint.win.",
},
// 浙江大学 教育网
EDU: ISPCollection{
ISPName: EDU,
IP: "ipv4.hgh-4538.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.hgh-4538.nexttrace-io-fasttrace-endpoint.win.",
},
}
var Hefei = BackBoneCollection{
Location: "合肥",
// 中国科学技术大学 教育网
EDU: ISPCollection{
ISPName: EDU,
IP: "ipv4.hef-4538.nexttrace-io-fasttrace-endpoint.win.",
IPv6: "ipv6.hef-4538.nexttrace-io-fasttrace-endpoint.win.",
},
// 中国科学技术大学 科技网
CST: ISPCollection{
ISPName: "中国科学技术大学 科技网 AS7497",
IP: "ipv4.hef-7497.nexttrace-io-fasttrace-endpoint.win.",
},
}

View File

@@ -0,0 +1,150 @@
package fastTrace
import (
"fmt"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/printer"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/tracelog"
"github.com/xgadget-lab/nexttrace/util"
"github.com/xgadget-lab/nexttrace/wshandle"
"log"
"os"
"os/signal"
)
var pFastTracer ParamsFastTrace
func (f *FastTracer) tracert_v6(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 func(fp *os.File) {
err := fp.Close()
if err != nil {
log.Fatal(err)
}
}(fp)
log.SetOutput(fp)
log.SetFlags(0)
fmt.Printf("%s『%s %s 』%s\n", printer.YELLOW_PREFIX, location, ispCollection.ISPName, printer.RESET_PREFIX)
log.Printf("『%s %s 』\n", location, ispCollection.ISPName)
fmt.Printf("traceroute to %s, %d hops max, %d byte packets\n", ispCollection.IPv6, pFastTracer.MaxHops, pFastTracer.PktSize)
log.Printf("traceroute to %s, %d hops max, %d byte packets\n", ispCollection.IPv6, pFastTracer.MaxHops, pFastTracer.PktSize)
ip := util.DomainLookUp(ispCollection.IPv6, "6", "", true)
var conf = trace.Config{
BeginHop: pFastTracer.BeginHop,
DestIP: ip,
DestPort: 80,
MaxHops: pFastTracer.MaxHops,
NumMeasurements: 3,
ParallelRequests: 18,
RDns: pFastTracer.RDns,
AlwaysWaitRDNS: pFastTracer.AlwaysWaitRDNS,
PacketInterval: 100,
TTLInterval: 500,
IPGeoSource: ipgeo.GetSource("LeoMoeAPI"),
Timeout: pFastTracer.Timeout,
PktSize: pFastTracer.PktSize,
Lang: pFastTracer.Lang,
}
if oe {
conf.RealtimePrinter = tracelog.RealtimePrinter
} else {
conf.RealtimePrinter = printer.RealtimePrinter
}
_, err = trace.Traceroute(f.TracerouteMethod, conf)
if err != nil {
log.Fatal(err)
}
println()
}
func (f *FastTracer) testAll_v6() {
f.testCT_v6()
println()
f.testCU_v6()
println()
f.testCM_v6()
println()
f.testEDU_v6()
}
func (f *FastTracer) testCT_v6() {
f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CT163)
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CT163)
f.tracert_v6(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CT163)
f.tracert_v6(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CT163)
}
func (f *FastTracer) testCU_v6() {
f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU169)
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU169)
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU9929)
f.tracert_v6(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CU169)
}
func (f *FastTracer) testCM_v6() {
f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CM)
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CM)
f.tracert_v6(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CM)
f.tracert_v6(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CM)
}
func (f *FastTracer) testEDU_v6() {
f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.EDU)
f.tracert_v6(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.EDU)
}
func FastTestv6(tm bool, outEnable bool, paramsFastTrace ParamsFastTrace) {
var c string
oe = outEnable
pFastTracer = paramsFastTrace
fmt.Println("您想测试哪些ISP的路由\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网")
fmt.Print("请选择选项:")
_, err := fmt.Scanln(&c)
if err != nil {
c = "1"
}
ft := FastTracer{}
// 建立 WebSocket 连接
w := wshandle.New()
w.Interrupt = make(chan os.Signal, 1)
signal.Notify(w.Interrupt, os.Interrupt)
defer func() {
w.Conn.Close()
}()
if !tm {
ft.TracerouteMethod = trace.ICMPTrace
fmt.Println("您将默认使用ICMP协议进行路由跟踪如果您想使用TCP SYN进行路由跟踪可以加入 -T 参数")
} else {
ft.TracerouteMethod = trace.TCPTrace
}
switch c {
case "1":
ft.testAll_v6()
case "2":
ft.testCT_v6()
case "3":
ft.testCU_v6()
case "4":
ft.testCM_v6()
case "5":
ft.testEDU_v6()
default:
ft.testAll_v6()
}
}

199
fast_trace/fast_trace.go Normal file
View File

@@ -0,0 +1,199 @@
package fastTrace
import (
"fmt"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/printer"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/tracelog"
"github.com/xgadget-lab/nexttrace/util"
"github.com/xgadget-lab/nexttrace/wshandle"
"log"
"net"
"os"
"os/signal"
"time"
)
type FastTracer struct {
TracerouteMethod trace.Method
ParamsFastTrace ParamsFastTrace
}
type ParamsFastTrace struct {
SrcDev string
SrcAddr string
BeginHop int
MaxHops int
RDns bool
AlwaysWaitRDNS bool
Lang string
PktSize int
Timeout time.Duration
}
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 func(fp *os.File) {
err := fp.Close()
if err != nil {
log.Fatal(err)
}
}(fp)
log.SetOutput(fp)
log.SetFlags(0)
fmt.Printf("%s『%s %s 』%s\n", printer.YELLOW_PREFIX, location, ispCollection.ISPName, printer.RESET_PREFIX)
log.Printf("『%s %s 』\n", location, ispCollection.ISPName)
fmt.Printf("traceroute to %s, %d hops max, %d byte packets\n", ispCollection.IP, f.ParamsFastTrace.MaxHops, f.ParamsFastTrace.PktSize)
log.Printf("traceroute to %s, %d hops max, %d byte packets\n", ispCollection.IP, f.ParamsFastTrace.MaxHops, f.ParamsFastTrace.PktSize)
ip := util.DomainLookUp(ispCollection.IP, "4", "", true)
var conf = trace.Config{
BeginHop: f.ParamsFastTrace.BeginHop,
DestIP: ip,
DestPort: 80,
MaxHops: f.ParamsFastTrace.MaxHops,
NumMeasurements: 3,
ParallelRequests: 18,
RDns: f.ParamsFastTrace.RDns,
AlwaysWaitRDNS: f.ParamsFastTrace.AlwaysWaitRDNS,
PacketInterval: 100,
TTLInterval: 500,
IPGeoSource: ipgeo.GetSource("LeoMoeAPI"),
Timeout: f.ParamsFastTrace.Timeout,
SrcAddr: f.ParamsFastTrace.SrcAddr,
PktSize: f.ParamsFastTrace.PktSize,
Lang: f.ParamsFastTrace.Lang,
}
if oe {
conf.RealtimePrinter = tracelog.RealtimePrinter
} else {
conf.RealtimePrinter = printer.RealtimePrinter
}
_, err = trace.Traceroute(f.TracerouteMethod, conf)
if err != nil {
log.Fatal(err)
}
println()
}
func FastTest(tm bool, outEnable bool, paramsFastTrace ParamsFastTrace) {
var c string
pFastTrace := paramsFastTrace
oe = outEnable
fmt.Println("Hi欢迎使用 Fast Trace 功能,请注意 Fast Trace 功能只适合新手使用\n因为国内网络复杂我们设置的测试目标有限建议普通用户自测以获得更加精准的路由情况")
fmt.Println("请您选择要测试的 IP 类型\n1. IPv4\n2. IPv6")
fmt.Print("请选择选项:")
_, err := fmt.Scanln(&c)
if err != nil {
c = "1"
}
if c == "2" {
FastTestv6(tm, outEnable, paramsFastTrace)
return
}
if pFastTrace.SrcDev != "" {
dev, _ := net.InterfaceByName(pFastTrace.SrcDev)
if addrs, err := dev.Addrs(); err == nil {
for _, addr := range addrs {
if addr.(*net.IPNet).IP.To4() != nil {
pFastTrace.SrcAddr = addr.(*net.IPNet).IP.String()
}
}
}
}
fmt.Println("您想测试哪些ISP的路由\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网")
fmt.Print("请选择选项:")
_, err = fmt.Scanln(&c)
if err != nil {
c = "1"
}
ft := FastTracer{
ParamsFastTrace: pFastTrace,
}
// 建立 WebSocket 连接
w := wshandle.New()
w.Interrupt = make(chan os.Signal, 1)
signal.Notify(w.Interrupt, os.Interrupt)
defer func() {
w.Conn.Close()
}()
if !tm {
ft.TracerouteMethod = trace.ICMPTrace
fmt.Println("您将默认使用ICMP协议进行路由跟踪如果您想使用TCP SYN进行路由跟踪可以加入 -T 参数")
} else {
ft.TracerouteMethod = trace.TCPTrace
}
switch c {
case "1":
ft.testAll()
case "2":
ft.testCT()
case "3":
ft.testCU()
case "4":
ft.testCM()
case "5":
ft.testEDU()
default:
ft.testAll()
}
}
func (f *FastTracer) testAll() {
f.testCT()
println()
f.testCU()
println()
f.testCM()
println()
f.testEDU()
}
func (f *FastTracer) testCT() {
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CT163)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CT163)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CTCN2)
f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CT163)
f.tracert(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CT163)
}
func (f *FastTracer) testCU() {
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU169)
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU9929)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU169)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU9929)
f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CU169)
f.tracert(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CU169)
}
func (f *FastTracer) testCM() {
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CM)
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CMIN2)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CM)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CMIN2)
f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CM)
f.tracert(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CM)
}
func (f *FastTracer) testEDU() {
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.EDU)
f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.EDU)
// 科技网暂时算在EDU里面等拿到了足够多的数据再分离出去单独用于测试
f.tracert(TestIPsCollection.Hefei.Location, TestIPsCollection.Hefei.CST)
}

View File

@@ -0,0 +1,42 @@
package fastTrace
import (
"fmt"
"os"
"os/signal"
"testing"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/wshandle"
)
func TestTrace(t *testing.T) {
pFastTrace := ParamsFastTrace{
SrcDev: "",
SrcAddr: "",
BeginHop: 1,
MaxHops: 30,
RDns: false,
AlwaysWaitRDNS: false,
Lang: "",
PktSize: 52,
}
ft := FastTracer{ParamsFastTrace: pFastTrace}
// 建立 WebSocket 连接
w := wshandle.New()
w.Interrupt = make(chan os.Signal, 1)
signal.Notify(w.Interrupt, os.Interrupt)
defer func() {
w.Conn.Close()
}()
fmt.Println("TCP v4")
ft.TracerouteMethod = trace.TCPTrace
//ft.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
//fmt.Println("TCP v6")
//ft.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
//fmt.Println("ICMP v4")
//ft.TracerouteMethod = trace.ICMPTrace
//ft.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
//fmt.Println("ICMP v6")
//ft.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
}

3
geofeed.example.csv Normal file
View File

@@ -0,0 +1,3 @@
154.48.0.0/12,,,,174,COGENT-NET
200.15.12.0/22,BR,BR-SP,Sao Paulo,2914,NTT-BACKBONE
2001:0418:1403::/48,US,US-VA,Ashburn,2914,NTT-BACKBONE
1 154.48.0.0/12 174 COGENT-NET
2 200.15.12.0/22 BR BR-SP Sao Paulo 2914 NTT-BACKBONE
3 2001:0418:1403::/48 US US-VA Ashburn 2914 NTT-BACKBONE

52
go.mod
View File

@@ -1,26 +1,46 @@
module github.com/xgadget-lab/nexttrace
go 1.18
go 1.20
require (
github.com/akamensky/argparse v1.4.0
github.com/google/gopacket v1.1.19
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
github.com/oschwald/maxminddb-golang v1.10.0
github.com/spf13/viper v1.16.0
github.com/stretchr/testify v1.8.4
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
github.com/tsosunchia/powclient v0.1.1
golang.org/x/net v0.10.0
golang.org/x/sync v0.2.0
)
require (
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
)
require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/fatih/color v1.13.0
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rodaine/table v1.0.1 // indirect
github.com/stretchr/testify v1.7.1 // indirect
github.com/tidwall/gjson v1.14.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
require (
github.com/fatih/color v1.15.0
github.com/gorilla/websocket v1.5.0
github.com/lionsoul2014/ip2region v2.11.1+incompatible
github.com/rodaine/table v1.1.0
github.com/tidwall/gjson v1.14.4
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
golang.org/x/sys v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

518
go.sum
View File

@@ -1,50 +1,522 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lionsoul2014/ip2region v2.11.1+incompatible h1:FEqj5PvIP6YwnGt3RYndGQaiftEIJJGpw4BCB76nvfM=
github.com/lionsoul2014/ip2region v2.11.1+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
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.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tsosunchia/powclient v0.1.1 h1:Llv7vFNXTvpxrd2JjnAkpGz95gjVu6A2891RQCML3Rc=
github.com/tsosunchia/powclient v0.1.1/go.mod h1:Pm4MP3QqN74SfNskPpFIEyT+NQrcABGoXkkeRwjlMEE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

91
ipgeo/chunzhen.go Normal file
View File

@@ -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, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) {
url := util.GetenvDefault("NEXTTRACE_CHUNZHENURL", "http://127.0.0.1:2060") + "?ip=" + ip
client := &http.Client{
// 2 秒超时
Timeout: timeout,
}
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
}

63
ipgeo/dn42.go Normal file
View File

@@ -0,0 +1,63 @@
package ipgeo
import (
"strings"
"time"
"github.com/xgadget-lab/nexttrace/dn42"
)
func LtdCodeToCountryOrAreaName(Code string) string {
countryName := []string{"United States", "Afghanistan", "Åland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", " Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada ", "Cape Verde", "Cayman Islands", "Central Africa", "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo (Brazzaville)", "DRC", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", " Denmark", "Djibouti", "Dominica", "Dominica", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)", "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", " French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Vatican", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "British Isles of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "North Korea", "South Korea", " Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "FYROM", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", " Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia (Federated States of)", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", "Rwanda", "St. Helena", "St. Kitts and Nevis", "St. Lucia", "St. Pierre and Miquelon", "St. Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen Islands", "Swaziland", "Sweden ", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", " United Kingdom", "U.S. Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "British Virgin Islands", "U.S. Virgin Islands", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"}
countryCode := []string{"us", "af", "ax", "al", "dz", "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "au", "at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi", "kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cn", "cx", "cc", "co", "km", "cg", "cd", "ck", "cr", "ci", "hr", "cu", "cy", "cz", "dk", "dj", "dm", "do", "ec", "eg", "sv", "gq", "er", "ee", "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm", "ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg", "gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in", "id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo", "kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls", "lr", "ly", "li", "lt", "lu", "mo", "mk", "mg", "mw", "my", "mv", "ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc", "mn", "me", "ms", "ma", "mz", "mm", "na", "nr", "np", "nl", "an", "nc", "nz", "ni", "ne", "ng", "nu", "nf", "mp", "no", "om", "pk", "pw", "ps", "pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re", "ro", "ru", "rw", "sh", "kn", "lc", "pm", "vc", "ws", "sm", "st", "sa", "sn", "rs", "sc", "sl", "sg", "sk", "si", "sb", "so", "za", "gs", "es", "lk", "sd", "sr", "sj", "sz", "se", "ch", "sy", "tw", "tj", "tz", "th", "tl", "tg", "tk", "to", "tt", "tn", "tr", "tm", "tc", "tv", "ug", "ua", "ae", "gb", "um", "uy", "uz", "vu", "ve", "vn", "vg", "vi", "wf", "eh", "ye", "zm", "zw"}
Code = strings.ToLower(Code)
for i, v := range countryCode {
if strings.Contains(Code, v) {
return countryName[i]
}
}
return Code
}
func DN42(ip string, _ time.Duration, _ string, _ bool) (*IPGeoData, error) {
data := &IPGeoData{}
// 先解析传入过来的数据
ipTmp := strings.Split(ip, ",")
if len(ipTmp) > 1 {
ip = ipTmp[0]
}
// 先查找 GeoFeed
if geo, find := dn42.GetGeoFeed(ip); find {
data.Country = geo.LtdCode
data.City = geo.City
data.Asnumber = geo.ASN
data.Whois = geo.IPWhois
}
// 如果没找到,查找 PTR
if len(ipTmp) > 1 {
// 存在 PTR 记录
if res, err := dn42.FindPtrRecord(ipTmp[1]); err == nil && res.LtdCode != "" {
data.Country = res.LtdCode
data.Prov = res.Region
data.City = res.City
}
}
data.Country = LtdCodeToCountryOrAreaName(data.Country)
switch data.Country {
case "Hong Kong":
data.Country = "China"
data.Prov = "Hong Kong"
case "Taiwan":
data.Country = "China"
data.Prov = "Taiwan"
case "Macao":
data.Country = "China"
data.Prov = "Macao"
case "":
data.Country = "Unknown"
}
return data, nil
}

5
ipgeo/dn42_test.go Normal file
View File

@@ -0,0 +1,5 @@
package ipgeo
// func TestDN42(t *testing.T) {
// DN42("")
// }

78
ipgeo/ip2region.go Normal file
View File

@@ -0,0 +1,78 @@
package ipgeo
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/lionsoul2014/ip2region/v1.0/binding/golang/ip2region"
)
const (
ipDataBasePath = "./ip2region.db"
defaultDownURL = "1"
originURL = "https://ghproxy.com/?q=https://github.com/bqf9979/ip2region/blob/master/data/ip2region.db?raw=true"
)
func downloadDataBase() error {
fmt.Println("Downloading DataBase...")
resp, err := http.Get(originURL)
if err != nil {
return err
}
defer resp.Body.Close()
// Create the file
out, err := os.Create(ipDataBasePath)
if err != nil {
return err
}
defer out.Close()
// Write the body to file
_, err = io.Copy(out, resp.Body)
return err
}
func IP2Region(ip string, _ time.Duration, _ string, _ bool) (*IPGeoData, error) {
if _, err := os.Stat(ipDataBasePath); os.IsNotExist(err) {
if err = downloadDataBase(); err != nil {
panic("Download Failed!")
}
}
region, err := ip2region.New(ipDataBasePath)
if err != nil {
panic("Cannot find ip2region.db")
}
defer region.Close()
info, searchErr := region.MemorySearch(ip)
if searchErr != nil {
return &IPGeoData{}, errors.New("no results")
}
if info.Country == "0" {
info.Country = ""
}
if info.Province == "0" {
info.Province = ""
}
if info.City == "0" {
info.City = ""
}
if info.ISP == "0" {
info.ISP = ""
}
return &IPGeoData{
Owner: info.ISP,
Country: info.Country,
Prov: info.Province,
City: info.City,
}, nil
}

43
ipgeo/ipapicom.go Normal file
View File

@@ -0,0 +1,43 @@
package ipgeo
import (
"errors"
"io"
"log"
"net/http"
"regexp"
"time"
"github.com/tidwall/gjson"
)
func IPApiCom(ip string, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) {
url := "http://ip-api.com/json/" + ip + "?fields=status,message,country,regionName,city,isp,as"
client := &http.Client{
// 2 秒超时
Timeout: timeout,
}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0")
content, err := client.Do(req)
if err != nil {
log.Println("ip-api.com 请求超时(2s)请切换其他API使用")
return nil, err
}
body, _ := io.ReadAll(content.Body)
res := gjson.ParseBytes(body)
if res.Get("status").String() != "success" {
return &IPGeoData{}, errors.New("超过API阈值")
}
re := regexp.MustCompile("[0-9]+")
return &IPGeoData{
Asnumber: re.FindString(res.Get("as").String()),
Country: res.Get("country").String(),
City: res.Get("city").String(),
Prov: res.Get("regionName").String(),
Owner: res.Get("isp").String(),
}, nil
}

86
ipgeo/ipfilter.go Normal file
View File

@@ -0,0 +1,86 @@
package ipgeo
import (
"net"
)
func cidrRangeContains(cidrRange string, checkIP string) bool {
_, ipNet, err := net.ParseCIDR(cidrRange)
if err != nil {
return false
}
secondIP := net.ParseIP(checkIP)
return ipNet.Contains(secondIP)
}
// Filter 被选到的返回 geodata, true 否则返回 nil, false
func Filter(ip string) (*IPGeoData, bool) {
//geodata := &IPGeoData{}
asn := ""
whois := ""
isFiltered := false
switch {
//rfc1918
case net.ParseIP(ip).IsPrivate():
asn = ""
whois = "RFC1918"
isFiltered = true
//IANA Reserved Address Space
case cidrRangeContains("100.64.0.0/10", ip):
asn = ""
whois = "RFC6598"
isFiltered = true
case cidrRangeContains("198.18.0.0/15", ip):
asn = ""
whois = "RFC2544"
isFiltered = true
case cidrRangeContains("198.51.100.0/24", ip):
fallthrough
case cidrRangeContains("203.0.113.0/24", ip):
asn = ""
whois = "RFC5737"
isFiltered = true
case cidrRangeContains("240.0.0.0/4", ip):
asn = ""
whois = "RFC1112"
isFiltered = true
//Defense Information System Network
case cidrRangeContains("6.0.0.0/8", ip):
fallthrough
case cidrRangeContains("7.0.0.0/8", ip):
fallthrough
case cidrRangeContains("11.0.0.0/8", ip):
fallthrough
case cidrRangeContains("21.0.0.0/8", ip):
fallthrough
case cidrRangeContains("22.0.0.0/8", ip):
fallthrough
case cidrRangeContains("26.0.0.0/8", ip):
fallthrough
case cidrRangeContains("28.0.0.0/8", ip):
fallthrough
case cidrRangeContains("29.0.0.0/8", ip):
fallthrough
case cidrRangeContains("30.0.0.0/8", ip):
fallthrough
case cidrRangeContains("33.0.0.0/8", ip):
fallthrough
case cidrRangeContains("55.0.0.0/8", ip):
fallthrough
case cidrRangeContains("214.0.0.0/8", ip):
fallthrough
case cidrRangeContains("215.0.0.0/8", ip):
asn = ""
whois = "DOD"
isFiltered = true
default:
}
if !isFiltered {
return nil, false
} else {
return &IPGeoData{
Asnumber: asn,
Whois: whois,
}, true
}
}

View File

@@ -1,13 +1,62 @@
package ipgeo
import (
"strings"
"time"
)
type IPGeoData struct {
Asnumber string
Country string
Prov string
City string
District string
Owner string
Isp string
IP string `json:"ip"`
Asnumber string `json:"asnumber"`
Country string `json:"country"`
CountryEn string `json:"country_en"`
Prov string `json:"prov"`
ProvEn string `json:"prov_en"`
City string `json:"city"`
CityEn string `json:"city_en"`
District string `json:"district"`
Owner string `json:"owner"`
Isp string `json:"isp"`
Domain string `json:"domain"`
Whois string `json:"whois"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
Prefix string `json:"prefix"`
Router map[string][]string `json:"router"`
Source string `json:"source"`
}
type Source = func(ip string) (*IPGeoData, error)
type Source = func(ip string, timeout time.Duration, lang string, maptrace bool) (*IPGeoData, error)
func GetSource(s string) Source {
switch strings.ToUpper(s) {
case "DN42":
return DN42
case "LEOMOEAPI":
return LeoIP
case "IP.SB":
return IPSB
case "IPINSIGHT":
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
case "DISABLE-GEOIP":
return disableGeoIP
default:
return LeoIP
}
}
func disableGeoIP(string, time.Duration, string, bool) (*IPGeoData, error) {
return &IPGeoData{}, nil
}

View File

@@ -1,42 +1,113 @@
package ipgeo
import (
"errors"
"fmt"
"log"
"os"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLeoIP(t *testing.T) {
res, err := LeoIP("1.1.1.1")
assert.Nil(t, err)
assert.NotNil(t, res)
assert.NotEmpty(t, res.Asnumber)
assert.NotEmpty(t, res.Isp)
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
func TestXxx(t *testing.T) {
const ID_FIXED_HEADER = "10"
var processID = fmt.Sprintf("%07b", os.Getpid()&0x7f) //取进程ID的前7位
var ttl = fmt.Sprintf("%06b", 95) //取TTL的后6位
fmt.Println(os.Getpid()&0x7f, 95)
var parity int
id := ID_FIXED_HEADER + processID + ttl
for _, c := range id {
if c == '1' {
parity++
}
}
if parity%2 == 0 {
id += "1"
} else {
id += "0"
}
process_id, ttl_r, _ := reverseID(id)
log.Println(process_id, ttl_r)
}
func TestIPSB(t *testing.T) {
res, err := IPSB("1.1.1.1")
assert.Nil(t, err)
assert.NotNil(t, res)
assert.NotEmpty(t, res.Asnumber)
assert.NotEmpty(t, res.Isp)
func reverseID(id string) (int64, int64, error) {
ttl, _ := strconv.ParseInt(id[9:15], 2, 32)
//process ID
processID, _ := strconv.ParseInt(id[2:9], 2, 32)
parity := 0
for i := 0; i < len(id)-1; i++ {
if id[i] == '1' {
parity++
}
}
if parity%2 == 1 {
if id[len(id)-1] == '0' {
fmt.Println("Parity check passed.")
} else {
fmt.Println("Parity check failed.")
return 0, 0, errors.New("err")
}
} else {
if id[len(id)-1] == '1' {
fmt.Println("Parity check passed.")
} else {
fmt.Println("Parity check failed.")
return 0, 0, errors.New("err")
}
}
return processID, ttl, nil
}
func TestIPInfo(t *testing.T) {
res, err := IPInfo("1.1.1.1")
assert.Nil(t, err)
assert.NotNil(t, res)
assert.NotEmpty(t, res.Country)
assert.NotEmpty(t, res.City)
assert.NotEmpty(t, res.Prov)
}
// func TestLeoIP(t *testing.T) {
// // res, err := LeoIP("1.1.1.1")
// // assert.Nil(t, err)
// // assert.NotNil(t, res)
// // assert.NotEmpty(t, res.Asnumber)
// // assert.NotEmpty(t, res.Isp)
// }
func TestIPInSight(t *testing.T) {
res, err := IPInSight("1.1.1.1")
assert.Nil(t, err)
assert.NotNil(t, res)
assert.NotEmpty(t, res.Country)
assert.NotEmpty(t, res.Prov)
// 这个库有时候不提供城市信息,返回值为""
//assert.NotEmpty(t, res.City)
}
// func TestIPSB(t *testing.T) {
// // Not available
// //res, err := IPSB("1.1.1.1")
// //assert.Nil(t, err)
// //assert.NotNil(t, res)
// //assert.NotEmpty(t, res.Asnumber)
// //assert.NotEmpty(t, res.Isp)
// }
// func TestIPInfo(t *testing.T) {
// res, err := IPInfo("1.1.1.1")
// assert.Nil(t, err)
// assert.NotNil(t, res)
// // assert.NotEmpty(t, res.Country)
// assert.NotEmpty(t, res.City)
// assert.NotEmpty(t, res.Prov)
// }
// func TestIPInSight(t *testing.T) {
// // res, err := IPInSight("1.1.1.1")
// // assert.Nil(t, err)
// // assert.NotNil(t, res)
// // assert.NotEmpty(t, res.Country)
// // assert.NotEmpty(t, res.Prov)
// // 这个库有时候不提供城市信息,返回值为""
// //assert.NotEmpty(t, res.City)
// }
// func TestIPApiCom(t *testing.T) {
// res, err := IPApiCom("1.1.1.1")
// assert.Nil(t, err)
// assert.NotNil(t, res)
// assert.NotEmpty(t, res.Country)
// assert.NotEmpty(t, res.City)
// assert.NotEmpty(t, res.Prov)
// }

View File

@@ -1,29 +1,310 @@
package ipgeo
import (
"io/ioutil"
"io"
"net/http"
"strings"
"time"
"github.com/tidwall/gjson"
)
func IPInfo(ip string) (*IPGeoData, error) {
resp, err := http.Get("https://ipinfo.io/" + ip + "?token=" + token.ipinfo)
func IPInfo(ip string, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) {
url := "https://ipinfo.io/" + ip + "?token=" + token.ipinfo
client := &http.Client{
// 2 秒超时
Timeout: timeout,
}
resp, err := client.Get(url)
//resp, err := http.Get("https://ipinfo.io/" + ip + "?token=" + token.ipinfo)
if err != nil {
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
}
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:]
}
var asnumber = ""
// 有时候不返回asn或其本身没有asn
if strings.HasPrefix(res.Get("org").String(), "AS") {
asnumber = strings.Fields(strings.TrimPrefix(res.Get("org").String(), "AS"))[0]
}
return &IPGeoData{
Country: res.Get("country").String(),
City: res.Get("city").String(),
Prov: res.Get("region").String(),
Asnumber: asnumber,
Country: country,
City: res.Get("city").String(),
Prov: res.Get("region").String(),
Owner: owner,
}, nil
}

53
ipgeo/ipinfoLocal.go Normal file
View File

@@ -0,0 +1,53 @@
package ipgeo
import (
"errors"
"github.com/oschwald/maxminddb-golang"
"net"
"os"
"strings"
"time"
)
const (
ipinfoDataBasePath = "./ipinfoLocal.mmdb"
)
func IPInfoLocal(ip string, _ time.Duration, _ string, _ bool) (*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
}

View File

@@ -1,18 +1,23 @@
package ipgeo
import (
"io/ioutil"
"io"
"net/http"
"time"
"github.com/tidwall/gjson"
)
func IPInSight(ip string) (*IPGeoData, error) {
resp, err := http.Get("https://ipinsight.io/query?ip=" + ip)
func IPInSight(ip string, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) {
client := &http.Client{
// 2 秒超时
Timeout: timeout,
}
resp, err := client.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
}

View File

@@ -1,30 +1,42 @@
package ipgeo
import (
"io/ioutil"
"io"
"log"
"net/http"
"os"
"time"
"github.com/tidwall/gjson"
)
func IPSB(ip string) (*IPGeoData, error) {
resp, err := http.Get("https://api.ip.sb/geoip/" + ip)
func IPSB(ip string, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) {
url := "https://api.ip.sb/geoip/" + ip
client := &http.Client{
// 2 秒超时
Timeout: timeout,
}
req, _ := http.NewRequest("GET", url, nil)
// 设置 UAip.sb 默认禁止 go-client User-Agent 的 api 请求
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0")
content, err := client.Do(req)
if err != nil {
log.Println("api.ip.sb 请求超时(2s)请切换其他API使用")
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
body, _ := io.ReadAll(content.Body)
res := gjson.ParseBytes(body)
if res.Get("country").String() == "" {
// 什么都拿不到证明被Cloudflare风控了
os.Exit(1)
}
return &IPGeoData{
Asnumber: res.Get("asn").String(),
Country: res.Get("country").String(),
City: res.Get("city").String(),
Prov: res.Get("region").String(),
Isp: res.Get("isp").String(),
Owner: res.Get("isp").String(),
}, nil
}

View File

@@ -1,31 +1,114 @@
package ipgeo
import (
"io/ioutil"
"net/http"
"encoding/json"
"errors"
"strconv"
"sync"
"time"
"github.com/tidwall/gjson"
"github.com/xgadget-lab/nexttrace/wshandle"
)
func LeoIP(ip string) (*IPGeoData, error) {
resp, err := http.Get("https://api.leo.moe/ip/?ip=" + ip + "&token=" + token.ipleo)
if err != nil {
return nil, err
/***
* 原理介绍 By Leo
* WebSocket 一共开启了一个发送和一个接收协程,在 New 了一个连接的实例对象后,不给予关闭,持续化连接
* 当有新的IP请求时一直在等待IP数据的发送协程接收到从 leo.go 的 sendIPRequest 函数发来的IP数据向服务端发送数据
* 由于实际使用时有大量并发,但是 ws 在同一时刻每次有且只能处理一次发送一条数据,所以必须给 ws 连接上互斥锁,保证每次只有一个协程访问
* 运作模型可以理解为一个 Node 一直在等待数据,当获得一个新的任务后,转交给下一个协程,不再关注这个 Node 的下一步处理过程,并且回到空闲状态继续等待新的任务
***/
// IPPool IP 查询池 map - ip - ip channel
type IPPool struct {
pool map[string]chan IPGeoData
poolMux sync.Mutex
}
var IPPools = IPPool{
pool: make(map[string]chan IPGeoData),
}
func sendIPRequest(ip string) {
wsConn := wshandle.GetWsConn()
wsConn.MsgSendCh <- ip
}
func receiveParse() {
// 获得连接实例
wsConn := wshandle.GetWsConn()
// 防止多协程抢夺一个ws连接导致死锁当一个协程获得ws的控制权后上锁
wsConn.ConnMux.Lock()
// 函数退出时解锁,给其他协程使用
defer wsConn.ConnMux.Unlock()
for {
// 接收到了一条IP信息
data := <-wsConn.MsgReceiveCh
// json解析 -> data
res := gjson.Parse(data)
// 根据返回的IP信息发送给对应等待回复的IP通道上
var domain = res.Get("domain").String()
if res.Get("domain").String() == "" {
domain = res.Get("owner").String()
}
m := make(map[string][]string)
err := json.Unmarshal([]byte(res.Get("router").String()), &m)
if err != nil {
// 此处是正常的因为有些IP没有路由信息
}
lat, _ := strconv.ParseFloat(res.Get("lat").String(), 32)
lng, _ := strconv.ParseFloat(res.Get("lng").String(), 32)
IPPools.pool[gjson.Parse(data).Get("ip").String()] <- IPGeoData{
Asnumber: res.Get("asnumber").String(),
Country: res.Get("country").String(),
CountryEn: res.Get("country_en").String(),
Prov: res.Get("prov").String(),
ProvEn: res.Get("prov_en").String(),
City: res.Get("city").String(),
CityEn: res.Get("city_en").String(),
District: res.Get("district").String(),
Owner: domain,
Lat: lat,
Lng: lng,
Isp: res.Get("isp").String(),
Whois: res.Get("whois").String(),
Prefix: res.Get("prefix").String(),
Router: m,
}
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
func LeoIP(ip string, timeout time.Duration, lang string, maptrace bool) (*IPGeoData, error) {
// TODO: 根据lang的值请求中文/英文API
// TODO: 根据maptrace的值决定是否请求经纬度信息
if timeout < 5*time.Second {
timeout = 5 * time.Second
}
res := gjson.ParseBytes(body)
return &IPGeoData{
Asnumber: res.Get("asnumber").String(),
Country: res.Get("country").String(),
Prov: res.Get("prov").String(),
City: res.Get("city").String(),
District: res.Get("district").String(),
Owner: res.Get("owner").String(),
Isp: res.Get("isp").String(),
}, nil
// 缓存中没有找到IP信息需要请求API获取
IPPools.poolMux.Lock()
// 如果之前已经被别的协程初始化过了就不用初始化了
if IPPools.pool[ip] == nil {
IPPools.pool[ip] = make(chan IPGeoData)
}
IPPools.poolMux.Unlock()
// 发送请求
sendIPRequest(ip)
// 同步开启监听
go receiveParse()
// 拥塞,等待数据返回
select {
case res := <-IPPools.pool[ip]:
return &res, nil
// 5秒后依旧没有接收到返回的IP数据不再等待超时异常处理
case <-time.After(timeout):
// 这里不可以返回一个 nil否则在访问对象内部的键值的时候会报空指针的 Fatal Error
return &IPGeoData{}, errors.New("TimeOut")
}
}

View File

@@ -1,5 +1,7 @@
package ipgeo
import "github.com/xgadget-lab/nexttrace/util"
type tokenData struct {
ipinsight string
ipinfo string
@@ -7,7 +9,7 @@ type tokenData struct {
}
var token = tokenData{
ipinsight: "",
ipinfo: "42764a944dabd0",
ipinsight: util.GetenvDefault("NEXTTRACE_IPINSIGHT_TOKEN", ""),
ipinfo: util.GetenvDefault("NEXTTRACE_IPINFO_TOKEN", ""),
ipleo: "NextTraceDemo",
}

82
main.go
View File

@@ -1,87 +1,9 @@
package main
import (
"flag"
"fmt"
"os"
"time"
"github.com/xgadget-lab/nexttrace/methods"
"github.com/xgadget-lab/nexttrace/methods/tcp"
"github.com/xgadget-lab/nexttrace/methods/udp"
"github.com/xgadget-lab/nexttrace/util"
"github.com/xgadget-lab/nexttrace/util/printer"
"github.com/xgadget-lab/nexttrace/cmd"
)
var tcpSYNFlag = flag.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80 in TCP, 53 in UDP)")
var port = flag.Int("p", 80, "Set SYN Traceroute Port")
var numMeasurements = flag.Int("q", 3, "Set the number of probes per each hop.")
var parallelRequests = flag.Int("r", 18, "Set ParallelRequests number. It should be 1 when there is a multi-routing.")
var maxHops = flag.Int("m", 30, "Set the max number of hops (max TTL to be reached).")
var dataOrigin = flag.String("d", "LeoMoeAPI", "Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight]")
var displayMode = flag.String("displayMode", "table", "Choose The Display Mode [table, Besttrace]")
func main() {
printer.PrintCopyRight()
domain := flagApply()
ip := util.DomainLookUp(domain)
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
if *tcpSYNFlag {
tcpTraceroute := tcp.New(ip, methods.TracerouteConfig{
MaxHops: uint16(*maxHops),
NumMeasurements: uint16(*numMeasurements),
ParallelRequests: uint16(*parallelRequests),
Port: *port,
Timeout: time.Second / 2,
})
res, err := tcpTraceroute.Start()
if err != nil {
fmt.Println("请赋予 sudo (root) 权限运行本程序")
} else {
if *displayMode == "Besttrace" {
printer.TraceroutePrinter(ip, *res, *dataOrigin)
} else if *displayMode == "table" {
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
} else {
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
}
}
} else {
if *port == 80 {
*port = 53
}
udpTraceroute := udp.New(ip, true, methods.TracerouteConfig{
MaxHops: uint16(*maxHops),
NumMeasurements: uint16(*numMeasurements),
ParallelRequests: uint16(*parallelRequests),
Port: *port,
Timeout: 2 * time.Second,
})
res, err := udpTraceroute.Start()
if err != nil {
fmt.Println("请赋予 sudo (root) 权限运行本程序")
} else {
if *displayMode == "Besttrace" {
printer.TraceroutePrinter(ip, *res, *dataOrigin)
} else if *displayMode == "table" {
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
} else {
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
}
}
}
}
func flagApply() string {
flag.Parse()
ipArg := flag.Args()
if flag.NArg() != 1 {
fmt.Println("Args Error\nUsage : ./bettertrace [-T] [-d <dataOrigin> ] [ -m <hops> ] [ -p <port> ] [ -q <probes> ] [ -r <parallelrequests> ] <hostname>")
os.Exit(2)
}
return ipArg[0]
cmd.Excute()
}

View File

@@ -1,79 +0,0 @@
package methods
import (
"encoding/binary"
"errors"
"net"
"time"
)
// TracerouteHop type
type TracerouteHop struct {
Success bool
Address net.Addr
TTL uint16
RTT *time.Duration
}
type TracerouteConfig struct {
MaxHops uint16
NumMeasurements uint16
ParallelRequests uint16
Port int
Timeout time.Duration
}
func GetIPHeaderLength(data []byte) (int, error) {
if len(data) < 1 {
return 0, errors.New("received invalid IP header")
}
return int((data[0] & 0x0F) * 4), nil
}
func GetICMPResponsePayload(data []byte) ([]byte, error) {
length, err := GetIPHeaderLength(data)
if err != nil {
return nil, err
}
if len(data) < length {
return nil, errors.New("length of packet too short")
}
return data[length:], nil
}
func GetUDPSrcPort(data []byte) uint16 {
srcPortBytes := data[:2]
srcPort := binary.BigEndian.Uint16(srcPortBytes)
return srcPort
}
func GetTCPSeq(data []byte) uint32 {
seqBytes := data[4:8]
return binary.BigEndian.Uint32(seqBytes)
}
func ReduceFinalResult(preliminary map[uint16][]TracerouteHop, maxHops uint16, destIP net.IP) map[uint16][]TracerouteHop {
// reduce the results to remove all hops after the first encounter to final destination
finalResults := map[uint16][]TracerouteHop{}
for i := uint16(1); i < maxHops; i++ {
foundFinal := false
probes := preliminary[i]
if probes == nil {
break
}
finalResults[i] = []TracerouteHop{}
for _, probe := range probes {
if probe.Success && probe.Address.String() == destIP.String() {
foundFinal = true
}
finalResults[i] = append(finalResults[i], probe)
}
if foundFinal {
break
}
}
return finalResults
}

View File

@@ -1,329 +0,0 @@
package tcp
import (
"log"
"math"
"math/rand"
"net"
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/xgadget-lab/nexttrace/listener_channel"
"github.com/xgadget-lab/nexttrace/methods"
"github.com/xgadget-lab/nexttrace/parallel_limiter"
"github.com/xgadget-lab/nexttrace/signal"
"github.com/xgadget-lab/nexttrace/util"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
type inflightData struct {
start time.Time
ttl uint16
}
type results struct {
inflightRequests sync.Map
results map[uint16][]methods.TracerouteHop
resultsMu sync.Mutex
err error
concurrentRequests *parallel_limiter.ParallelLimiter
reachedFinalHop *signal.Signal
}
type Traceroute struct {
opConfig opConfig
trcrtConfig methods.TracerouteConfig
results results
}
type opConfig struct {
icmpConn net.PacketConn
tcpConn net.PacketConn
tcpMu sync.Mutex
destIP net.IP
srcIP net.IP
wg *sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
}
func New(destIP net.IP, config methods.TracerouteConfig) *Traceroute {
return &Traceroute{
opConfig: opConfig{
destIP: destIP,
},
trcrtConfig: config,
}
}
func (tr *Traceroute) Start() (*map[uint16][]methods.TracerouteHop, error) {
tr.opConfig.ctx, tr.opConfig.cancel = context.WithCancel(context.Background())
tr.opConfig.srcIP, _ = util.LocalIPPort(tr.opConfig.destIP)
var err error
tr.opConfig.tcpConn, err = net.ListenPacket("ip4:tcp", tr.opConfig.srcIP.String())
if err != nil {
return nil, err
}
tr.opConfig.icmpConn, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return nil, err
}
var wg sync.WaitGroup
tr.opConfig.wg = &wg
tr.results = results{
inflightRequests: sync.Map{},
concurrentRequests: parallel_limiter.New(int(tr.trcrtConfig.ParallelRequests)),
reachedFinalHop: signal.New(),
results: map[uint16][]methods.TracerouteHop{},
}
return tr.start()
}
func (tr *Traceroute) timeoutLoop() {
ticker := time.NewTicker(tr.trcrtConfig.Timeout / 4)
go func() {
for range ticker.C {
tr.results.inflightRequests.Range(func(key, value interface{}) bool {
request := value.(inflightData)
expired := time.Since(request.start) > tr.trcrtConfig.Timeout
if !expired {
return true
}
tr.results.inflightRequests.Delete(key)
tr.addToResult(request.ttl, methods.TracerouteHop{
Success: false,
TTL: request.ttl,
})
tr.results.concurrentRequests.Finished()
tr.opConfig.wg.Done()
return true
})
}
}()
select {
case <-tr.opConfig.ctx.Done():
ticker.Stop()
}
}
func (tr *Traceroute) addToResult(ttl uint16, hop methods.TracerouteHop) {
tr.results.resultsMu.Lock()
defer tr.results.resultsMu.Unlock()
if tr.results.results[ttl] == nil {
tr.results.results[ttl] = []methods.TracerouteHop{}
}
tr.results.results[ttl] = append(tr.results.results[ttl], hop)
}
func (tr *Traceroute) handleICMPMessage(msg listener_channel.ReceivedMessage, data []byte) {
header, err := methods.GetICMPResponsePayload(data)
if err != nil {
return
}
sequenceNumber := methods.GetTCPSeq(header)
val, ok := tr.results.inflightRequests.LoadAndDelete(sequenceNumber)
if !ok {
return
}
request := val.(inflightData)
elapsed := time.Since(request.start)
if msg.Peer.String() == tr.opConfig.destIP.String() {
tr.results.reachedFinalHop.Signal()
}
tr.addToResult(request.ttl, methods.TracerouteHop{
Success: true,
Address: msg.Peer,
TTL: request.ttl,
RTT: &elapsed,
})
tr.results.concurrentRequests.Finished()
tr.opConfig.wg.Done()
}
func (tr *Traceroute) icmpListener() {
lc := listener_channel.New(tr.opConfig.icmpConn)
defer lc.Stop()
go lc.Start()
for {
select {
case <-tr.opConfig.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv4.ICMPTypeTimeExceeded:
body := rm.Body.(*icmp.TimeExceeded).Data
tr.handleICMPMessage(msg, body)
case ipv4.ICMPTypeDestinationUnreachable:
body := rm.Body.(*icmp.DstUnreach).Data
tr.handleICMPMessage(msg, body)
default:
log.Println("received icmp message of unknown type")
}
}
}
}
func (tr *Traceroute) tcpListener() {
lc := listener_channel.New(tr.opConfig.tcpConn)
defer lc.Stop()
go lc.Start()
for {
select {
case <-tr.opConfig.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
if msg.Peer.String() != tr.opConfig.destIP.String() {
continue
}
// Decode a packet
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
// Get the TCP layer from this packet
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
val, ok := tr.results.inflightRequests.LoadAndDelete(tcp.Ack - 1)
if !ok {
continue
}
request := val.(inflightData)
tr.results.concurrentRequests.Finished()
elapsed := time.Since(request.start)
if msg.Peer.String() == tr.opConfig.destIP.String() {
tr.results.reachedFinalHop.Signal()
}
tr.addToResult(request.ttl, methods.TracerouteHop{
Success: true,
Address: msg.Peer,
TTL: request.ttl,
RTT: &elapsed,
})
tr.opConfig.wg.Done()
}
}
}
}
func (tr *Traceroute) sendMessage(ttl uint16) {
_, srcPort := util.LocalIPPort(tr.opConfig.destIP)
ipHeader := &layers.IPv4{
SrcIP: tr.opConfig.srcIP,
DstIP: tr.opConfig.destIP,
Protocol: layers.IPProtocolTCP,
TTL: uint8(ttl),
}
sequenceNumber := uint32(rand.Intn(math.MaxUint32))
tcpHeader := &layers.TCP{
SrcPort: layers.TCPPort(srcPort),
DstPort: layers.TCPPort(tr.trcrtConfig.Port),
Seq: sequenceNumber,
SYN: true,
Window: 14600,
}
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
tr.results.err = err
tr.opConfig.cancel()
return
}
tr.opConfig.tcpMu.Lock()
defer tr.opConfig.tcpMu.Unlock()
err := ipv4.NewPacketConn(tr.opConfig.tcpConn).SetTTL(int(ttl))
if err != nil {
tr.results.err = err
tr.opConfig.cancel()
return
}
start := time.Now()
if _, err := tr.opConfig.tcpConn.WriteTo(buf.Bytes(), &net.IPAddr{IP: tr.opConfig.destIP}); err != nil {
tr.results.err = err
tr.opConfig.cancel()
return
}
tr.results.inflightRequests.Store(sequenceNumber, inflightData{start: start, ttl: ttl})
}
func (tr *Traceroute) sendLoop() {
rand.Seed(time.Now().UTC().UnixNano())
defer tr.opConfig.wg.Done()
for ttl := uint16(1); ttl <= tr.trcrtConfig.MaxHops; ttl++ {
select {
case <-tr.results.reachedFinalHop.Chan():
return
default:
}
for i := 0; i < int(tr.trcrtConfig.NumMeasurements); i++ {
select {
case <-tr.opConfig.ctx.Done():
return
case <-tr.results.concurrentRequests.Start():
tr.opConfig.wg.Add(1)
go tr.sendMessage(ttl)
}
}
}
}
func (tr *Traceroute) start() (*map[uint16][]methods.TracerouteHop, error) {
go tr.timeoutLoop()
go tr.icmpListener()
go tr.tcpListener()
tr.opConfig.wg.Add(1)
go tr.sendLoop()
tr.opConfig.wg.Wait()
tr.opConfig.cancel()
if tr.results.err != nil {
return nil, tr.results.err
}
result := methods.ReduceFinalResult(tr.results.results, tr.trcrtConfig.MaxHops, tr.opConfig.destIP)
return &result, tr.results.err
}

View File

@@ -1,311 +0,0 @@
package udp
import (
"log"
"math/rand"
"net"
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/xgadget-lab/nexttrace/listener_channel"
"github.com/xgadget-lab/nexttrace/methods"
"github.com/xgadget-lab/nexttrace/methods/quic"
"github.com/xgadget-lab/nexttrace/parallel_limiter"
"github.com/xgadget-lab/nexttrace/signal"
"github.com/xgadget-lab/nexttrace/taskgroup"
"github.com/xgadget-lab/nexttrace/util"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
type inflightData struct {
icmpMsg chan<- net.Addr
}
type opConfig struct {
quic bool
destIP net.IP
wg *taskgroup.TaskGroup
icmpConn net.PacketConn
ctx context.Context
cancel context.CancelFunc
}
type results struct {
inflightRequests sync.Map
results map[uint16][]methods.TracerouteHop
resultsMu sync.Mutex
err error
concurrentRequests *parallel_limiter.ParallelLimiter
reachedFinalHop *signal.Signal
}
type Traceroute struct {
trcrtConfig methods.TracerouteConfig
opConfig opConfig
results results
}
func New(destIP net.IP, quic bool, config methods.TracerouteConfig) *Traceroute {
return &Traceroute{
opConfig: opConfig{
quic: quic,
destIP: destIP,
},
trcrtConfig: config,
}
}
func (tr *Traceroute) Start() (*map[uint16][]methods.TracerouteHop, error) {
tr.opConfig.ctx, tr.opConfig.cancel = context.WithCancel(context.Background())
tr.results = results{
inflightRequests: sync.Map{},
concurrentRequests: parallel_limiter.New(int(tr.trcrtConfig.ParallelRequests)),
results: map[uint16][]methods.TracerouteHop{},
reachedFinalHop: signal.New(),
}
var err error
tr.opConfig.icmpConn, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return nil, err
}
return tr.start()
}
func (tr *Traceroute) addToResult(ttl uint16, hop methods.TracerouteHop) {
tr.results.resultsMu.Lock()
defer tr.results.resultsMu.Unlock()
if tr.results.results[ttl] == nil {
tr.results.results[ttl] = []methods.TracerouteHop{}
}
tr.results.results[ttl] = append(tr.results.results[ttl], hop)
}
func (tr *Traceroute) getUDPConn(try int) (net.IP, int, net.PacketConn) {
srcIP, _ := util.LocalIPPort(tr.opConfig.destIP)
var ipString string
if srcIP == nil {
ipString = ""
} else {
ipString = srcIP.String()
}
udpConn, err := net.ListenPacket("udp", ipString+":0")
if err != nil {
if try > 3 {
log.Fatal(err)
}
return tr.getUDPConn(try + 1)
}
return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn
}
func (tr *Traceroute) sendMessage(ttl uint16) {
srcIP, srcPort, udpConn := tr.getUDPConn(0)
var payload []byte
if tr.opConfig.quic {
payload = quic.GenerateWithRandomIds()
} else {
ipHeader := &layers.IPv4{
SrcIP: srcIP,
DstIP: tr.opConfig.destIP,
Protocol: layers.IPProtocolTCP,
TTL: uint8(ttl),
}
udpHeader := &layers.UDP{
SrcPort: layers.UDPPort(srcPort),
DstPort: layers.UDPPort(tr.trcrtConfig.Port),
}
_ = udpHeader.SetNetworkLayerForChecksum(ipHeader)
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
if err := gopacket.SerializeLayers(buf, opts, udpHeader, gopacket.Payload("HAJSFJHKAJSHFKJHAJKFHKASHKFHHKAFKHFAHSJK")); err != nil {
tr.results.err = err
tr.opConfig.cancel()
return
}
payload = buf.Bytes()
}
err := ipv4.NewPacketConn(udpConn).SetTTL(int(ttl))
if err != nil {
tr.results.err = err
tr.opConfig.cancel()
return
}
icmpMsg := make(chan net.Addr, 1)
udpMsg := make(chan net.Addr, 1)
start := time.Now()
if _, err := udpConn.WriteTo(payload, &net.UDPAddr{IP: tr.opConfig.destIP, Port: tr.trcrtConfig.Port}); err != nil {
tr.results.err = err
tr.opConfig.cancel()
return
}
inflight := inflightData{
icmpMsg: icmpMsg,
}
tr.results.inflightRequests.Store(uint16(srcPort), inflight)
go func() {
reply := make([]byte, 1500)
_, peer, err := udpConn.ReadFrom(reply)
if err != nil {
// probably because we closed the connection
return
}
udpMsg <- peer
}()
select {
case peer := <-icmpMsg:
rtt := time.Since(start)
if peer.(*net.IPAddr).IP.Equal(tr.opConfig.destIP) {
tr.results.reachedFinalHop.Signal()
}
tr.addToResult(ttl, methods.TracerouteHop{
Success: true,
Address: peer,
TTL: ttl,
RTT: &rtt,
})
case peer := <-udpMsg:
rtt := time.Since(start)
ip := peer.(*net.UDPAddr).IP
if ip.Equal(tr.opConfig.destIP) {
tr.results.reachedFinalHop.Signal()
}
tr.addToResult(ttl, methods.TracerouteHop{
Success: true,
Address: &net.IPAddr{IP: ip},
TTL: ttl,
RTT: &rtt,
})
case <-time.After(tr.trcrtConfig.Timeout):
tr.addToResult(ttl, methods.TracerouteHop{
Success: false,
Address: nil,
TTL: ttl,
RTT: nil,
})
}
tr.results.inflightRequests.Delete(uint16(srcPort))
udpConn.Close()
tr.results.concurrentRequests.Finished()
tr.opConfig.wg.Done()
}
func (tr *Traceroute) handleICMPMessage(msg listener_channel.ReceivedMessage, data []byte) {
header, err := methods.GetICMPResponsePayload(data)
if err != nil {
return
}
srcPort := methods.GetUDPSrcPort(header)
val, ok := tr.results.inflightRequests.LoadAndDelete(srcPort)
if !ok {
return
}
request := val.(inflightData)
request.icmpMsg <- msg.Peer
}
func (tr *Traceroute) icmpListener() {
lc := listener_channel.New(tr.opConfig.icmpConn)
defer lc.Stop()
go lc.Start()
for {
select {
case <-tr.opConfig.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv4.ICMPTypeTimeExceeded:
body := rm.Body.(*icmp.TimeExceeded).Data
tr.handleICMPMessage(msg, body)
case ipv4.ICMPTypeDestinationUnreachable:
body := rm.Body.(*icmp.DstUnreach).Data
tr.handleICMPMessage(msg, body)
default:
log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
func (tr *Traceroute) sendLoop() {
rand.Seed(time.Now().UTC().UnixNano())
for ttl := uint16(1); ttl <= tr.trcrtConfig.MaxHops; ttl++ {
select {
case <-tr.results.reachedFinalHop.Chan():
return
default:
}
for i := 0; i < int(tr.trcrtConfig.NumMeasurements); i++ {
select {
case <-tr.opConfig.ctx.Done():
return
case <-tr.results.concurrentRequests.Start():
tr.opConfig.wg.Add()
go tr.sendMessage(ttl)
}
}
}
}
func (tr *Traceroute) start() (*map[uint16][]methods.TracerouteHop, error) {
go tr.icmpListener()
wg := taskgroup.New()
tr.opConfig.wg = wg
tr.sendLoop()
wg.Wait()
tr.opConfig.cancel()
tr.opConfig.icmpConn.Close()
if tr.results.err != nil {
return nil, tr.results.err
}
result := methods.ReduceFinalResult(tr.results.results, tr.trcrtConfig.MaxHops, tr.opConfig.destIP)
return &result, tr.results.err
}

2
nt_config.yaml Normal file
View File

@@ -0,0 +1,2 @@
geofeedpath: ./geofeed.csv
ptrpath: ./ptr.csv

138
nt_install.sh Normal file
View File

@@ -0,0 +1,138 @@
#!/bin/bash
Green_font="\033[32m"
Yellow_font="\033[33m"
Red_font="\033[31m"
Font_suffix="\033[0m"
Info="${Green_font}[Info]${Font_suffix}"
Error="${Red_font}[Error]${Font_suffix}"
Tips="${Green_font}[Tips]${Font_suffix}"
Temp_path="/var/tmp/nexttrace"
checkRootPermit() {
[[ $EUID -ne 0 ]] && echo -e "${Error} 请使用sudo/root权限运行本脚本" && exit 1
}
checkSystemArch() {
arch=$(uname -m)
if [[ $arch == "x86_64" ]]; then
archParam="amd64"
elif [[ $arch == "i386" ]]; then
archParam="386"
elif [[ $arch == "aarch64" ]]; then
archParam="arm64"
elif [[ $arch == "armv7l" ]] || [[ $arch == "armv7ml" ]]; then
archParam="armv7"
elif [[ $arch == "mips" ]]; then
archParam="mips"
fi
}
checkSystemDistribution() {
case "$OSTYPE" in
linux*)
osDistribution="linux"
if [ ! -d "/usr/local" ];
then
downPath="/usr/bin/nexttrace"
else
downPath="/usr/local/bin/nexttrace"
fi
;;
*)
echo "unknown: $OSTYPE"
exit 1
;;
esac
}
getLocation() {
countryCode=$(curl -s "http://ip-api.com/line/?fields=countryCode")
}
installWgetPackage() {
echo -e "${Info} wget 正在安装中..."
# try apt
apt -h &> /dev/null
if [ $? -eq 0 ]; then
# 先更新一下数据源有些机器数据源比较老可能会404
apt update -y &> /dev/null
apt install wget -y &> /dev/null
fi
# try yum
yum -h &> /dev/null
if [ $? -eq 0 ]; then
yum install wget -y &> /dev/null
fi
# try dnf
dnf -h &> /dev/null
if [ $? -eq 0 ]; then
dnf install wget -y &> /dev/null
fi
# try pacman
pacman -h &> /dev/null
if [ $? -eq 0 ]; then
pacman -Sy
pacman -S wget
fi
}
checkWgetPackage() {
wget -h &> /dev/null
if [ $? -ne 0 ]; then
installWgetPackage
fi
}
downloadBinrayFile() {
echo -e "${Info} 获取最新版的 NextTrace 发行版文件信息"
# 简单说明一下Github提供了一个API可以获取最新发行版本的二进制文件下载地址对应的是browser_download_url根据刚刚测得的osDistribution、archParam获取对应的下载地址
latestURL=$(curl -s https://api.github.com/repos/sjlleo/nexttrace/releases/latest | grep -i "browser_download_url.*${osDistribution}.*${archParam}" | awk -F '"' '{print $4}')
if [ "$countryCode" == "CN" ]; then
echo -e "${Info} 检测到国内环境,正在使用镜像下载"
latestURL="https://ghproxy.com/"$latestURL
fi
echo -e "${Info} 正在下载 NextTrace 二进制文件..."
wget -O ${Temp_path} ${latestURL} &> /dev/null
if [ $? -eq 0 ];
then
changeMode
mv ${Temp_path} ${downPath}
echo -e "${Info} NextTrace 现在已经在您的系统中可用"
else
echo -e "${Error} NextTrace 下载失败,请检查您的网络是否正常"
exit 1
fi
}
changeMode() {
chmod +x ${Temp_path} &> /dev/null
}
runBinrayFileHelp() {
if [ -e ${downPath} ]; then
${downPath} --version
echo -e "${Tips} 一切准备就绪!使用命令 nexttrace 1.1.1.1 开始您的第一次路由测试吧~ 更多进阶命令玩法可以用 nexttrace -h 查看哦\n 关于软件卸载因为nexttrace是绿色版单文件卸载只需输入命令 rm ${downPath} 即可"
fi
}
# Check Procedure
checkRootPermit
checkSystemDistribution
checkSystemArch
checkWgetPackage
# Download Procedure
getLocation
downloadBinrayFile
# Run Procedure
runBinrayFileHelp

View File

@@ -1,52 +0,0 @@
package parallel_limiter
import (
"sync"
)
type ParallelLimiter struct {
maxCount int
mu sync.Mutex
currentRunning int
waiting []chan struct{}
}
func New(count int) *ParallelLimiter {
return &ParallelLimiter{
maxCount: count,
currentRunning: 0,
waiting: []chan struct{}{},
}
}
func (p *ParallelLimiter) Start() chan struct{} {
p.mu.Lock()
if p.currentRunning+1 > p.maxCount {
waitChan := make(chan struct{})
p.waiting = append(p.waiting, waitChan)
p.mu.Unlock()
return waitChan
}
p.currentRunning++
p.mu.Unlock()
instantResolveChan := make(chan struct{})
go func() {
instantResolveChan <- struct{}{}
}()
return instantResolveChan
}
func (p *ParallelLimiter) Finished() {
p.mu.Lock()
if len(p.waiting) > 0 {
first := p.waiting[0]
p.waiting = p.waiting[1:]
first <- struct{}{}
p.currentRunning++
}
p.currentRunning--
p.mu.Unlock()
}

38
pow/pow.go Normal file
View File

@@ -0,0 +1,38 @@
package pow
import (
"fmt"
"github.com/tsosunchia/powclient"
"github.com/xgadget-lab/nexttrace/util"
"net/url"
"os"
)
const (
baseURL = "/v3/challenge"
)
func GetToken(fastIp string, host string, port string) (string, error) {
getTokenParams := powclient.NewGetTokenParams()
u := url.URL{Scheme: "https", Host: fastIp + ":" + port, Path: baseURL}
getTokenParams.BaseUrl = u.String()
getTokenParams.SNI = host
getTokenParams.Host = host
getTokenParams.UserAgent = util.UserAgent
var err error
// 尝试三次RetToken如果都失败了异常退出
for i := 0; i < 3; i++ {
token, err := powclient.RetToken(getTokenParams)
if err != nil {
continue
}
//fmt.Println("GetToken success", token, getTokenParams.UserAgent)
return token, nil
}
if err != nil {
fmt.Println(err)
}
fmt.Println("RetToken failed 3 times, exit")
os.Exit(1)
return "", nil
}

19
pow/pow_test.go Normal file
View File

@@ -0,0 +1,19 @@
package pow
import (
"fmt"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestGetToken(t *testing.T) {
// 计时开始
start := time.Now()
token, err := GetToken("103.120.18.35", "api.leo.moe", "443")
// 计时结束
end := time.Now()
fmt.Println("耗时:", end.Sub(start))
fmt.Println("token:", token)
assert.NoError(t, err, "GetToken() returned an error")
}

101
printer/basic.go Normal file
View File

@@ -0,0 +1,101 @@
package printer
import (
"fmt"
"github.com/xgadget-lab/nexttrace/config"
"github.com/xgadget-lab/nexttrace/trace"
"net"
"github.com/fatih/color"
)
var version = config.Version
var buildDate = config.BuildDate
var commitID = config.CommitID
func Version() {
fmt.Fprintf(color.Output, "%s %s %s %s\n",
color.New(color.FgWhite, color.Bold).Sprintf("%s", "NextTrace"),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", version),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", buildDate),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", commitID),
)
}
func CopyRight() {
fmt.Fprintf(color.Output, "\n%s\n%s\n%s %s\n\n%s\n%s %s\n%s %s\n%s %s\n\n%s\n%s\n%s %s\n\n",
color.New(color.FgCyan, color.Bold).Sprintf("%s", "NextTrace CopyRight"),
color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Project Creator"),
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Leo"),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "i@leo.moe"),
color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Project Maintainer"),
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Tso"),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "tsosunchia@gmail.com"),
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Vincent"),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "i@vincent.moe"),
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Leo"),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "i@leo.moe"),
color.New(color.FgCyan, color.Bold).Sprintf("%s", "Special Acknowledgement List"),
color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Major Contributor"),
color.New(color.FgWhite, color.Bold).Sprintf("%s", "zhshch"),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "zhshch@athorx.com"),
)
MoeQingOrgCopyRight()
PluginCopyRight()
}
func MoeQingOrgCopyRight() {
fmt.Fprintf(color.Output, "%s\n%s %s\n%s %s\n\n",
color.New(color.FgHiYellow, color.Bold).Sprintf("%s", "MoeQing Network"),
color.New(color.FgWhite, color.Bold).Sprintf("%s", "YekongTAT"),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "yekongtat@gmail.com"),
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Haima"),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "haima@peers.cloud"),
)
}
func PluginCopyRight() {
fmt.Fprintf(color.Output, "%s\n%s %s\n\n",
color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Map Plugin Author"),
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Tso"),
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "tsosunchia@gmail.com"),
)
}
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string, maxHops int, packetSize int) {
fmt.Println("IP Geo Data Provider: " + dataOrigin)
if ip.String() == domain {
fmt.Printf("traceroute to %s, %d hops max, %d bytes packets\n", ip.String(), maxHops, packetSize)
} else {
fmt.Printf("traceroute to %s (%s), %d hops max, %d bytes packets\n", ip.String(), domain, maxHops, packetSize)
}
}
func applyLangSetting(h *trace.Hop) {
if len(h.Geo.Country) <= 1 {
h.Geo.Country = "局域网"
h.Geo.CountryEn = "LAN Address"
}
if h.Lang == "en" {
if h.Geo.Country == "Anycast" {
} else if h.Geo.Prov == "骨干网" {
h.Geo.Prov = "BackBone"
} else if h.Geo.ProvEn == "" {
h.Geo.Country = h.Geo.CountryEn
} else {
if h.Geo.CityEn == "" {
h.Geo.Country = h.Geo.ProvEn
h.Geo.Prov = h.Geo.CountryEn
h.Geo.City = ""
} else {
h.Geo.Country = h.Geo.CityEn
h.Geo.Prov = h.Geo.ProvEn
h.Geo.City = h.Geo.CountryEn
}
}
}
}

104
printer/classic_printer.go Normal file
View File

@@ -0,0 +1,104 @@
package printer
import (
"fmt"
"strings"
"github.com/xgadget-lab/nexttrace/trace"
)
type HopInfo int
const (
General HopInfo = 0
IXP HopInfo = 1
Peer HopInfo = 2
PoP HopInfo = 3
Aboard HopInfo = 4
)
func findLatestAvailableHop(res *trace.Result, ttl int, probesIndex int) int {
for ttl > 0 {
// 查找上一个跃点是不是有效结果
ttl--
// 判断此TTL跃点是否有效并判断地理位置结构体是否已经初始化
if len(res.Hops[ttl]) != 0 && res.Hops[ttl][probesIndex].Success && res.Hops[ttl][probesIndex].Geo != nil {
// TTL虽有效但地理位置API没有能够正确返回数据依旧不能视为有效数据
if res.Hops[ttl][probesIndex].Geo.Country == "" {
// 跳过继续寻找上一个有效跃点
continue
}
return ttl
}
}
// 没找到
return -1
}
func unifyName(name string) string {
if name == "China" || name == "CN" {
return "中国"
} else if name == "Hong kong" || name == "香港" || name == "Central and Western" {
return "中国香港"
} else if name == "Taiwan" || name == "台湾" {
return "中国台湾"
} else {
return name
}
}
func chinaISPPeer(hostname string) bool {
var keyWords = []string{"china", "ct", "cu", "cm", "cnc", "4134", "4837", "4809", "9929"}
for _, k := range keyWords {
if strings.Contains(strings.ToLower(hostname), k) {
return true
}
}
return false
}
func chinaMainland(h trace.Hop) bool {
if unifyName(h.Geo.Country) == "中国" && unifyName(h.Geo.Prov) != "中国香港" && unifyName(h.Geo.Prov) != "中国台湾" {
return true
} else {
return false
}
}
func makeHopsType(res *trace.Result, ttl int) map[int]HopInfo {
// 创建一个字典存放所有当前TTL的跃点类型集合
hopProbesMap := make(map[int]HopInfo)
for i := range res.Hops[ttl] {
// 判断是否res.Hops[ttl][i]是一个有效的跃点并且地理位置信息已经初始化
if res.Hops[ttl][i].Success && res.Hops[ttl][i].Geo != nil {
if availableTTL := findLatestAvailableHop(res, ttl, i); availableTTL != -1 {
switch {
case strings.Contains(res.Hops[ttl][i].Geo.District, "IXP") || strings.Contains(strings.ToLower(res.Hops[ttl][i].Hostname), "ix"):
hopProbesMap[i] = IXP
case strings.Contains(res.Hops[ttl][i].Geo.District, "Peer") || chinaISPPeer(res.Hops[ttl][i].Hostname):
hopProbesMap[i] = Peer
case strings.Contains(res.Hops[ttl][i].Geo.District, "PoP"):
hopProbesMap[i] = PoP
// 2个有效跃点必须都为有效数据如果当前跳没有地理位置信息或者为局域网不能视为有效节点
case res.Hops[availableTTL][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "" &&
// 一个跃点在中国大陆,另外一个跃点在其他地区,则可以推断出数据包跨境
chinaMainland(res.Hops[availableTTL][i]) != chinaMainland(res.Hops[ttl][i]):
// TODO: 将先后2跳跃点信息汇报给API以完善相关数据
hopProbesMap[i] = Aboard
}
} else {
hopProbesMap[i] = General
}
}
}
return hopProbesMap
}
func ClassicPrinter(res *trace.Result, ttl int) {
fmt.Print(ttl + 1)
hopsTypeMap := makeHopsType(res, ttl)
for i := range res.Hops[ttl] {
HopPrinter(res.Hops[ttl][i], hopsTypeMap[i])
}
}

32
printer/easy.go Normal file
View File

@@ -0,0 +1,32 @@
package printer
import (
"fmt"
"github.com/xgadget-lab/nexttrace/trace"
)
func EasyPrinter(res *trace.Result, ttl int) {
for i := range res.Hops[ttl] {
if res.Hops[ttl][i].Address == nil {
fmt.Printf("%d|*||||||\n", ttl+1)
continue
}
applyLangSetting(&res.Hops[ttl][i]) // 应用语言设置
fmt.Printf(
"%d|%s|%s|%.2f|%s|%s|%s|%s|%s|%s|%.4f|%.4f\n",
ttl+1,
res.Hops[ttl][i].Address.String(),
res.Hops[ttl][i].Hostname,
float64(res.Hops[ttl][i].RTT.Microseconds())/1000,
res.Hops[ttl][i].Geo.Asnumber,
res.Hops[ttl][i].Geo.Country,
res.Hops[ttl][i].Geo.Prov,
res.Hops[ttl][i].Geo.City,
res.Hops[ttl][i].Geo.District,
res.Hops[ttl][i].Geo.Owner,
res.Hops[ttl][i].Geo.Lat,
res.Hops[ttl][i].Geo.Lng,
)
}
}

113
printer/printer.go Normal file
View File

@@ -0,0 +1,113 @@
package printer
import (
"fmt"
"strings"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/ipgeo"
)
// var dataOrigin string
// func TraceroutePrinter(res *trace.Result) {
// for i, hop := range res.Hops {
// fmt.Print(i + 1)
// for _, h := range hop {
// HopPrinter(h)
// }
// }
// }
//此文件目前仅供classic_printer使用
const (
RED_PREFIX = "\033[1;31m"
GREEN_PREFIX = "\033[1;32m"
YELLOW_PREFIX = "\033[1;33m"
BLUE_PREFIX = "\033[1;34m"
CYAN_PREFIX = "\033[1;36m"
RESET_PREFIX = "\033[0m"
)
func HopPrinter(h trace.Hop, info HopInfo) {
if h.Address == nil {
fmt.Println("\t*")
} else {
applyLangSetting(&h) // 应用语言设置
txt := "\t"
if h.Hostname == "" {
txt += fmt.Sprint(h.Address, " ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
} else {
txt += fmt.Sprint(h.Hostname, " (", h.Address, ") ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
}
if h.Geo != nil {
txt += " " + formatIpGeoData(h.Address.String(), h.Geo)
}
switch info {
case IXP:
fmt.Print(CYAN_PREFIX)
case PoP:
fmt.Print(CYAN_PREFIX)
case Peer:
fmt.Print(YELLOW_PREFIX)
case Aboard:
fmt.Print(GREEN_PREFIX)
}
fmt.Println(txt)
if info != General {
fmt.Print(RESET_PREFIX)
}
}
}
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
var res = make([]string, 0, 10)
if data.Asnumber == "" {
res = append(res, "*")
} else {
res = append(res, "AS"+data.Asnumber)
}
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
// TODO: 移动IDC判断到Hop.fetchIPData函数减少API调用
//if strings.HasPrefix(ip, "9.") {
// res = append(res, "LAN Address")
//} else if strings.HasPrefix(ip, "11.") {
// res = append(res, "LAN Address")
//} else if data.Country == "" {
// res = append(res, "LAN Address")
if false {
} else {
// 有些IP的归属信息为空这个时候将ISP的信息填入
if data.Owner == "" {
data.Owner = data.Isp
}
if data.Prov == "" && data.City == "" {
// anyCast或是骨干网数据不应该有国家信息
data.Owner = data.Owner + ", " + data.Owner
} else {
// 非骨干网正常填入IP的国家信息数据
res = append(res, data.Country)
}
if data.Prov != "" {
res = append(res, data.Prov)
}
if data.City != "" {
res = append(res, data.City)
}
if data.Owner != "" {
res = append(res, data.Owner)
}
}
return strings.Join(res, ", ")
}

94
printer/printer_test.go Normal file
View File

@@ -0,0 +1,94 @@
package printer
// func TestPrintTraceRouteNav(t *testing.T) {
// PrintTraceRouteNav(util.DomainLookUp("1.1.1.1", false), "1.1.1.1", "dataOrigin")
// }
// var testGeo = &ipgeo.IPGeoData{
// Asnumber: "TestAsnumber",
// Country: "TestCountry",
// Prov: "TestProv",
// City: "TestCity",
// District: "TestDistrict",
// Owner: "TestOwner",
// Isp: "TestIsp",
// }
// var testResult = &trace.Result{
// Hops: [][]trace.Hop{
// {
// {
// Success: true,
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
// Hostname: "test",
// TTL: 0,
// RTT: 10 * time.Millisecond,
// Error: nil,
// Geo: testGeo,
// },
// {
// Success: true,
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
// Hostname: "test",
// TTL: 0,
// RTT: 10 * time.Millisecond,
// Error: nil,
// Geo: testGeo,
// },
// },
// {
// {
// Success: false,
// Address: nil,
// Hostname: "",
// TTL: 0,
// RTT: 0,
// Error: errors.New("test error"),
// Geo: nil,
// },
// {
// Success: true,
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
// Hostname: "test",
// TTL: 0,
// RTT: 10 * time.Millisecond,
// Error: nil,
// Geo: nil,
// },
// },
// {
// {
// Success: true,
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
// Hostname: "test",
// TTL: 0,
// RTT: 0,
// Error: nil,
// Geo: &ipgeo.IPGeoData{},
// },
// {
// Success: true,
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
// Hostname: "",
// TTL: 0,
// RTT: 10 * time.Millisecond,
// Error: nil,
// Geo: testGeo,
// },
// },
// },
// }
// // func TestTraceroutePrinter(t *testing.T) {
// // TraceroutePrinter(testResult)
// // }
// func TestTracerouteTablePrinter(t *testing.T) {
// TracerouteTablePrinter(testResult)
// }
// func TestRealtimePrinter(t *testing.T) {
// RealtimePrinter(testResult, 0)
// // RealtimePrinter(testResult, 1)
// // RealtimePrinter(testResult, 2)
// }

166
printer/realtime_printer.go Normal file
View File

@@ -0,0 +1,166 @@
package printer
import (
"fmt"
"net"
"strconv"
"strings"
"github.com/fatih/color"
"github.com/xgadget-lab/nexttrace/trace"
)
func RealtimePrinter(res *trace.Result, ttl int) {
fmt.Printf("%s ", color.New(color.FgHiYellow, color.Bold).Sprintf("%-2d", ttl+1))
// 去重
var latestIP string
tmpMap := make(map[string][]string)
for i, v := range res.Hops[ttl] {
if v.Address == nil && latestIP != "" {
tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*"))
continue
} else if v.Address == nil {
continue
}
if _, exist := tmpMap[v.Address.String()]; !exist {
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i))
// 首次进入
if latestIP == "" {
for j := 0; j < i; j++ {
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*"))
}
}
latestIP = v.Address.String()
}
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000))
}
if latestIP == "" {
fmt.Fprintf(color.Output, "%s\n",
color.New(color.FgWhite, color.Bold).Sprintf("*"),
)
return
}
var blockDisplay = false
for ip, v := range tmpMap {
if blockDisplay {
fmt.Printf("%4s", "")
}
if net.ParseIP(ip).To4() == nil {
fmt.Fprintf(color.Output, "%s",
color.New(color.FgWhite, color.Bold).Sprintf("%-25s", ip),
)
} else {
fmt.Fprintf(color.Output, "%s",
color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip),
)
}
i, _ := strconv.Atoi(v[0])
if res.Hops[ttl][i].Geo.Asnumber != "" {
/*** CMIN2, CUG, CN2, CUII, CTG 改为壕金色高亮
/* 小孩子不懂事加着玩的
/* 此处的高亮不代表任何线路质量
/* 仅代表走了这部分的ASN
/* 如果使用这些ASN的IP同样会被高亮
***/
switch {
case res.Hops[ttl][i].Geo.Asnumber == "58807":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "10099":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "4809":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "9929":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "23764":
fallthrough
case res.Hops[ttl][i].Geo.Whois == "CMIN2-NET":
fallthrough
case strings.HasPrefix(res.Hops[ttl][i].Address.String(), "59.43."):
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiYellow, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
default:
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
}
} else {
fmt.Printf(" %-8s", "*")
}
if net.ParseIP(ip).To4() != nil {
whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-")
if len(whoisFormat) > 1 {
whoisFormat[0] = strings.Join(whoisFormat[:2], "-")
}
if whoisFormat[0] != "" {
whoisFormat[0] = "[" + whoisFormat[0] + "]"
}
// CMIN2, CUII, CN2, CUG 改为壕金色高亮
switch {
case res.Hops[ttl][i].Geo.Asnumber == "58807":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "10099":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "4809":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "9929":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "23764":
fallthrough
case whoisFormat[0] == "[CNC-BACKBONE]":
fallthrough
case whoisFormat[0] == "[CUG-BACKBONE]":
fallthrough
case whoisFormat[0] == "[CMIN2-NET]":
fallthrough
case strings.HasPrefix(res.Hops[ttl][i].Address.String(), "59.43."):
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiYellow, color.Bold).Sprintf("%-16s", whoisFormat[0]))
default:
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0]))
}
}
applyLangSetting(&res.Hops[ttl][i]) // 应用语言设置
if net.ParseIP(ip).To4() != nil {
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
color.New(color.FgHiBlack, color.Bold).Sprintf("%-39s", res.Hops[ttl][i].Hostname),
)
} else {
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
color.New(color.FgHiBlack, color.Bold).Sprintf("%-32s", res.Hops[ttl][i].Hostname),
)
}
for j := 1; j < len(v); j++ {
if len(v) == 2 || j == 1 {
fmt.Fprintf(color.Output, "%s",
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
)
} else {
fmt.Fprintf(color.Output, " / %s",
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
)
}
}
fmt.Println()
blockDisplay = true
}
}

View File

@@ -0,0 +1,152 @@
package printer
import (
"fmt"
"net"
"strconv"
"strings"
"github.com/fatih/color"
"github.com/xgadget-lab/nexttrace/trace"
)
func RealtimePrinterWithRouter(res *trace.Result, ttl int) {
fmt.Printf("%s ", color.New(color.FgHiYellow, color.Bold).Sprintf("%-2d", ttl+1))
// 去重
var latestIP string
tmpMap := make(map[string][]string)
for i, v := range res.Hops[ttl] {
if v.Address == nil && latestIP != "" {
tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*"))
continue
} else if v.Address == nil {
continue
}
if _, exist := tmpMap[v.Address.String()]; !exist {
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i))
// 首次进入
if latestIP == "" {
for j := 0; j < i; j++ {
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*"))
}
}
latestIP = v.Address.String()
}
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000))
}
if latestIP == "" {
fmt.Fprintf(color.Output, "%s\n",
color.New(color.FgWhite, color.Bold).Sprintf("*"),
)
return
}
var blockDisplay = false
for ip, v := range tmpMap {
if blockDisplay {
fmt.Printf("%4s", "")
}
if net.ParseIP(ip).To4() == nil {
fmt.Fprintf(color.Output, "%s",
color.New(color.FgWhite, color.Bold).Sprintf("%-25s", ip),
)
} else {
fmt.Fprintf(color.Output, "%s",
color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip),
)
}
i, _ := strconv.Atoi(v[0])
if res.Hops[ttl][i].Geo.Asnumber != "" {
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
} else {
fmt.Printf(" %-8s", "*")
}
if net.ParseIP(ip).To4() != nil {
whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-")
if len(whoisFormat) > 1 {
whoisFormat[0] = strings.Join(whoisFormat[:2], "-")
}
if whoisFormat[0] != "" {
whoisFormat[0] = "[" + whoisFormat[0] + "]"
}
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0]))
}
if res.Hops[ttl][i].Geo.Country == "" {
res.Hops[ttl][i].Geo.Country = "LAN Address"
}
if net.ParseIP(ip).To4() != nil {
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
color.New(color.FgHiBlack, color.Bold).Sprintf("%-39s", res.Hops[ttl][i].Hostname),
)
} else {
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
color.New(color.FgHiBlack, color.Bold).Sprintf("%-32s", res.Hops[ttl][i].Hostname),
)
}
for j := 1; j < len(v); j++ {
if len(v) == 2 || j == 1 {
fmt.Fprintf(color.Output, "%s",
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
)
} else {
fmt.Fprintf(color.Output, " / %s",
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
)
}
}
i = 0
fmt.Println()
if res.Hops[ttl][i].Geo != nil && !blockDisplay {
fmt.Fprintf(color.Output, "%s %s %s %s %s\n",
color.New(color.FgWhite, color.Bold).Sprintf("-"),
color.New(color.FgHiYellow, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prefix),
color.New(color.FgWhite, color.Bold).Sprintf("路由表"),
color.New(color.FgHiCyan, color.Bold).Sprintf("Beta"),
color.New(color.FgWhite, color.Bold).Sprintf("-"),
)
GetRouter(&res.Hops[ttl][i].Geo.Router, "AS"+res.Hops[ttl][i].Geo.Asnumber)
}
blockDisplay = true
}
}
func GetRouter(r *map[string][]string, node string) {
routeMap := *r
for _, v := range routeMap[node] {
if len(routeMap[v]) != 0 {
fmt.Fprintf(color.Output, " %s %s %s\n",
color.New(color.FgWhite, color.Bold).Sprintf("%s", routeMap[v][0]),
color.New(color.FgWhite, color.Bold).Sprintf("%s", v),
color.New(color.FgHiBlue, color.Bold).Sprintf("%s", node),
)
} else {
fmt.Fprintf(color.Output, " %s %s\n",
color.New(color.FgWhite, color.Bold).Sprintf("%s", v),
color.New(color.FgHiBlue, color.Bold).Sprintf("%s", node),
)
}
}
}

130
printer/tableprinter.go Normal file
View File

@@ -0,0 +1,130 @@
package printer
import (
"fmt"
"strings"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/fatih/color"
"github.com/rodaine/table"
)
type rowData struct {
Hop string
IP string
Latency string
Asnumber string
Country string
Prov string
City string
District string
Owner string
}
func TracerouteTablePrinter(res *trace.Result) {
// 初始化表格
tbl := New()
for _, hop := range res.Hops {
for k, h := range hop {
data := tableDataGenerator(h)
if k > 0 {
data.Hop = ""
}
if data.Country == "" && data.Prov == "" && data.City == "" {
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, "", data.Owner)
} else {
if data.City != "" {
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.City+", "+data.Prov+", "+data.Country, data.Owner)
} else if data.Prov != "" {
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Prov+", "+data.Country, data.Owner)
} else {
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Owner)
}
}
}
}
fmt.Print("\033[H\033[2J")
// 打印表格
tbl.Print()
}
func New() table.Table {
// 初始化表格
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
columnFmt := color.New(color.FgYellow).SprintfFunc()
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Location", "Owner")
tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
return tbl
}
func tableDataGenerator(h trace.Hop) *rowData {
if h.Address == nil {
return &rowData{
Hop: fmt.Sprint(h.TTL),
IP: "*",
}
} else {
lantency := fmt.Sprintf("%.2fms", h.RTT.Seconds()*1000)
IP := h.Address.String()
if strings.HasPrefix(IP, "9.") {
return &rowData{
Hop: fmt.Sprint(h.TTL),
IP: IP,
Latency: lantency,
Country: "LAN Address",
Prov: "",
Owner: "",
}
} else if strings.HasPrefix(IP, "11.") {
return &rowData{
Hop: fmt.Sprint(h.TTL),
IP: IP,
Latency: lantency,
Country: "LAN Address",
Prov: "",
Owner: "",
}
}
if h.Hostname != "" {
IP = fmt.Sprint(h.Hostname, " (", IP, ") ")
}
if h.Geo == nil {
h.Geo = &ipgeo.IPGeoData{}
}
r := &rowData{
Hop: fmt.Sprint(h.TTL),
IP: IP,
Latency: lantency,
Asnumber: h.Geo.Asnumber,
Country: h.Geo.CountryEn,
Prov: h.Geo.ProvEn,
City: h.Geo.CityEn,
District: h.Geo.District,
Owner: h.Geo.Owner,
}
if h.Geo == nil {
return r
}
if h.Geo.Owner == "" {
h.Geo.Owner = h.Geo.Isp
}
r.Asnumber = h.Geo.Asnumber
r.Country = h.Geo.CountryEn
r.Prov = h.Geo.ProvEn
r.City = h.Geo.CityEn
r.District = h.Geo.District
r.Owner = h.Geo.Owner
return r
}
}

4
ptr.example.csv Normal file
View File

@@ -0,0 +1,4 @@
snge,SG,,Singapore
CXS,CN,Hunan,Changsha
LAX,US,California,Los Angeles
SJC,US,California,San Jose
1 snge SG Singapore
2 CXS CN Hunan Changsha
3 LAX US California Los Angeles
4 SJC US California San Jose

183
reporter/reporter.go Normal file
View File

@@ -0,0 +1,183 @@
package reporter
import (
"fmt"
"net"
"strings"
"sync"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/trace"
)
type Reporter interface {
Print()
}
func New(rs *trace.Result, ip string) Reporter {
experimentTag()
r := reporter{
routeResult: rs,
targetIP: ip,
}
return &r
}
type reporter struct {
targetTTL uint16
targetIP string
routeReport map[uint16][]routeReportNode
routeReportLock sync.Mutex
routeResult *trace.Result
wg sync.WaitGroup
}
type routeReportNode struct {
asn string
isp string
geo []string
ix bool
}
func experimentTag() {
fmt.Println("Route-Path 功能实验室")
}
func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData, ttl uint16) {
var success = true
defer r.wg.Done()
rpn := routeReportNode{}
ptr, err := net.LookupAddr(ip)
if err == nil {
if strings.Contains(strings.ToLower(ptr[0]), "ix") {
rpn.ix = true
} else {
rpn.ix = false
}
}
// TODO: 这种写法不好,后面再重构一下
// 判断反向解析的域名中又或者是IP地理位置数据库中是否出现了 IX
if strings.Contains(strings.ToLower(ipGeoData.Isp), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Isp), "ix") || strings.Contains(strings.ToLower(ipGeoData.Owner), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Owner), "ix") {
rpn.ix = true
}
// TODO: 正则判断POP并且提取带宽大小等信息
// CN2 需要特殊处理因为他们很多没有ASN
// 但是目前这种写法是不规范的属于凭空标记4809的IP
// TODO: 用更好的方式显示 CN2 骨干网的路由 Path
if strings.HasPrefix(ip, "59.43") {
rpn.asn = "4809"
} else {
rpn.asn = ipGeoData.Asnumber
}
// 无论最后一跳是否为存在地理位置信息AnyCast都应该给予显示
if (ipGeoData.Country == "" || ipGeoData.Country == "LAN Address" || ipGeoData.Country == "-") && ip != r.targetIP {
success = false
} else {
if ipGeoData.City == "" {
rpn.geo = []string{ipGeoData.Country, ipGeoData.Prov}
} else {
rpn.geo = []string{ipGeoData.Country, ipGeoData.City}
}
}
if ipGeoData.Asnumber == "" {
rpn.asn = "*"
}
if ipGeoData.Isp == "" {
rpn.isp = ipGeoData.Owner
} else {
rpn.isp = ipGeoData.Isp
}
// 有效记录
if success {
// 锁住资源防止同时写panic
r.routeReportLock.Lock()
// 添加到MAP中
r.routeReport[ttl] = append(r.routeReport[ttl], rpn)
// 写入完成,解锁释放资源给其他协程
r.routeReportLock.Unlock()
}
}
func (r *reporter) InitialBaseData() Reporter {
reportNodes := map[uint16][]routeReportNode{}
r.routeReport = reportNodes
r.targetTTL = uint16(len(r.routeResult.Hops))
for i := uint16(0); i < r.targetTTL; i++ {
traceHop := r.routeResult.Hops[i][0]
if traceHop.Success {
currentIP := traceHop.Address.String()
r.wg.Add(1)
go r.generateRouteReportNode(currentIP, *traceHop.Geo, i)
}
}
// 等待所有的子协程运行完毕
r.wg.Wait()
return r
}
func (r *reporter) Print() {
var beforeActiveTTL uint16 = 0
r.InitialBaseData()
// 尝试首个有效 TTL
for i := uint16(0); i < r.targetTTL; i++ {
if len(r.routeReport[i]) != 0 {
beforeActiveTTL = i
// 找到以后便不再循环
break
}
}
for i := beforeActiveTTL; i < r.targetTTL; i++ {
// 计算该TTL内的数据长度如果为0则代表没有有效数据
if len(r.routeReport[i]) == 0 {
// 跳过改跃点的数据整理
continue
}
nodeReport := r.routeReport[i][0]
if i == beforeActiveTTL {
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
} else {
nodeReportBefore := r.routeReport[beforeActiveTTL][0]
// ASN 相同,同个 ISP 内部的数据传递
if nodeReportBefore.asn == nodeReport.asn {
// Same ASN but Coutry or City Changed
if nodeReportBefore.geo[0] != nodeReport.geo[0] {
fmt.Printf("』→ %s『%s", nodeReport.geo[0], nodeReport.geo[1])
} else {
if nodeReportBefore.geo[1] != nodeReport.geo[1] {
fmt.Printf(" → %s", nodeReport.geo[1])
}
}
} else {
// ASN 不同,跨 ISP 的数据传递,这里可能会出现 POP、IP Transit、Peer、Exchange
fmt.Printf("』」")
if int(i) != len(r.routeReport)+1 {
// 部分 Shell 客户端可能无法很好的展示这个特殊字符
// TODO: 寻找其他替代字符
fmt.Printf("\n ╭╯\n ╰")
}
if nodeReport.ix {
fmt.Printf("AS%s \033[42;37mIXP\033[0m %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
} else {
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
}
}
}
// 标记为最新的一个有效跃点
beforeActiveTTL = i
}
fmt.Println("』」")
}

115
reporter/reporter_test.go Normal file
View File

@@ -0,0 +1,115 @@
package reporter
import (
"net"
"testing"
"time"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/trace"
)
var testResult = &trace.Result{
Hops: [][]trace.Hop{
{
{
Success: true,
Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
Hostname: "test",
TTL: 0,
RTT: 10 * time.Millisecond,
Error: nil,
Geo: &ipgeo.IPGeoData{
Asnumber: "4808",
Country: "中国",
Prov: "北京市",
City: "北京市",
District: "北京市",
Owner: "",
Isp: "中国联通",
},
},
},
{
{
Success: true,
Address: &net.IPAddr{IP: net.ParseIP("114.249.16.1")},
Hostname: "test",
TTL: 0,
RTT: 10 * time.Millisecond,
Error: nil,
Geo: &ipgeo.IPGeoData{
Asnumber: "4808",
Country: "中国",
Prov: "北京市",
City: "北京市",
District: "北京市",
Owner: "",
Isp: "中国联通",
},
},
},
{
{
Success: true,
Address: &net.IPAddr{IP: net.ParseIP("219.158.5.150")},
Hostname: "test",
TTL: 0,
RTT: 10 * time.Millisecond,
Error: nil,
Geo: &ipgeo.IPGeoData{
Asnumber: "4837",
Country: "中国",
Prov: "",
City: "",
District: "",
Owner: "",
Isp: "中国联通",
},
},
},
{
{
Success: true,
Address: &net.IPAddr{IP: net.ParseIP("62.115.125.160")},
Hostname: "test",
TTL: 0,
RTT: 10 * time.Millisecond,
Error: nil,
Geo: &ipgeo.IPGeoData{
Asnumber: "1299",
Country: "Sweden",
Prov: "Stockholm County",
City: "Stockholm",
District: "",
Owner: "",
Isp: "Telia Company AB",
},
},
},
{
{
Success: true,
Address: &net.IPAddr{IP: net.ParseIP("213.226.68.73")},
Hostname: "test",
TTL: 0,
RTT: 10 * time.Millisecond,
Error: nil,
Geo: &ipgeo.IPGeoData{
Asnumber: "56630",
Country: "Germany",
Prov: "Hesse, Frankfurt",
City: "",
District: "",
Owner: "",
Isp: "Melbikomas UAB",
},
},
},
},
}
func TestPrint(t *testing.T) {
r := New(testResult, "213.226.68.73")
r.Print()
}

View File

@@ -1,19 +0,0 @@
package signal
type Signal struct {
sigChan chan struct{}
}
func New() *Signal {
return &Signal{sigChan: make(chan struct{}, 1)}
}
func (s *Signal) Signal() {
if len(s.sigChan) == 0 {
s.sigChan <- struct{}{}
}
}
func (s *Signal) Chan() chan struct{} {
return s.sigChan
}

View File

@@ -1,45 +0,0 @@
package taskgroup
import (
"sync"
)
type TaskGroup struct {
count int
mu sync.Mutex
done []chan struct{}
}
func New() *TaskGroup {
return &TaskGroup{
count: 0,
mu: sync.Mutex{},
done: []chan struct{}{},
}
}
func (t *TaskGroup) Add() {
t.mu.Lock()
defer t.mu.Unlock()
t.count++
}
func (t *TaskGroup) Done() {
t.mu.Lock()
defer t.mu.Unlock()
if t.count-1 == 0 {
for _, doneChannel := range t.done {
doneChannel <- struct{}{}
}
t.done = []chan struct{}{}
}
t.count--
}
func (t *TaskGroup) Wait() {
doneChannel := make(chan struct{})
t.mu.Lock()
t.done = append(t.done, doneChannel)
t.mu.Unlock()
<-doneChannel
}

326
trace/icmp_ipv4.go Normal file
View File

@@ -0,0 +1,326 @@
package trace
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"log"
"net"
"os"
"strconv"
"sync"
"time"
"github.com/xgadget-lab/nexttrace/trace/internal"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
type ICMPTracer struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
inflightRequest map[int]chan Hop
inflightRequestRWLock sync.RWMutex
icmpListen net.PacketConn
final int
finalLock sync.Mutex
fetchLock sync.Mutex
}
func (t *ICMPTracer) PrintFunc() {
defer t.wg.Done()
var ttl = t.Config.BeginHop - 1
for {
if t.AsyncPrinter != nil {
t.AsyncPrinter(&t.res)
}
if len(t.res.Hops)-1 > ttl {
if len(t.res.Hops[ttl]) == t.NumMeasurements {
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, ttl)
}
ttl++
if ttl == t.final-1 || ttl >= t.MaxHops-1 {
return
}
}
}
<-time.After(200 * time.Millisecond)
}
}
func (t *ICMPTracer) Execute() (*Result, error) {
t.inflightRequestRWLock.Lock()
t.inflightRequest = make(map[int]chan Hop)
t.inflightRequestRWLock.Unlock()
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
var err error
t.icmpListen, err = internal.ListenICMP("ip4:1", t.SrcAddr)
if err != nil {
return &t.res, err
}
defer t.icmpListen.Close()
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.final = -1
go t.listenICMP()
t.wg.Add(1)
go t.PrintFunc()
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
t.inflightRequestRWLock.Lock()
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
t.inflightRequestRWLock.Unlock()
if t.final != -1 && ttl > t.final {
break
}
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(ttl)
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
}
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
}
t.wg.Wait()
t.res.reduce(t.final)
if t.final != -1 {
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, t.final-1)
}
} else {
for i := 0; i < t.NumMeasurements; i++ {
t.res.add(Hop{
Success: false,
Address: nil,
TTL: 30,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, t.MaxHops-1)
}
}
return &t.res, nil
}
func (t *ICMPTracer) listenICMP() {
lc := NewPacketListener(t.icmpListen, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
// log.Println(msg.Msg)
if msg.Msg[0] == 0 {
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
echoReply := rm.Body.(*icmp.Echo)
ttl := echoReply.Seq // This is the TTL value
if ttl > 100 {
continue
}
if msg.Peer.String() == t.DestIP.String() {
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, ttl)
}
continue
}
packet_id := strconv.FormatInt(int64(binary.BigEndian.Uint16(msg.Msg[32:34])), 2)
if process_id, ttl, err := reverseID(packet_id); err == nil {
if process_id == int64(os.Getpid()&0x7f) {
dstip := net.IP(msg.Msg[24:28])
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv4zero) {
// 匹配再继续解析包,否则直接丢弃
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv4.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data, int(ttl))
case ipv4.ICMPTypeEchoReply:
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, int(ttl))
default:
// log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
}
}
}
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
t.inflightRequestRWLock.RLock()
defer t.inflightRequestRWLock.RUnlock()
if _, ok := t.inflightRequest[ttl]; ok {
t.inflightRequest[ttl] <- Hop{
Success: true,
Address: msg.Peer,
}
}
}
func gernerateID(ttl_int int) int {
const ID_FIXED_HEADER = "10"
var processID = fmt.Sprintf("%07b", os.Getpid()&0x7f) //取进程ID的前7位
var ttl = fmt.Sprintf("%06b", ttl_int) //取TTL的后6位
var parity int
id := ID_FIXED_HEADER + processID + ttl
for _, c := range id {
if c == '1' {
parity++
}
}
if parity%2 == 0 {
id += "1"
} else {
id += "0"
}
res, _ := strconv.ParseInt(id, 2, 64)
return int(res)
}
func reverseID(id string) (int64, int64, error) {
if len(id) < 16 {
return 0, 0, errors.New("err")
}
ttl, err := strconv.ParseInt(id[9:15], 2, 32)
if err != nil {
return 0, 0, err
}
//process ID
processID, _ := strconv.ParseInt(id[2:9], 2, 32)
parity := 0
for i := 0; i < len(id)-1; i++ {
if id[i] == '1' {
parity++
}
}
if parity%2 == 1 {
if id[len(id)-1] == '0' {
// fmt.Println("Parity check passed.")
} else {
// fmt.Println("Parity check failed.")
return 0, 0, errors.New("err")
}
} else {
if id[len(id)-1] == '1' {
// fmt.Println("Parity check passed.")
} else {
// fmt.Println("Parity check failed.")
return 0, 0, errors.New("err")
}
}
return processID, ttl, nil
}
func (t *ICMPTracer) send(ttl int) error {
defer t.wg.Done()
if t.final != -1 && ttl > t.final {
return nil
}
id := gernerateID(ttl)
// log.Println("发送的", id)
icmpHeader := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: id,
//Data: []byte("HELLO-R-U-THERE"),
Data: bytes.Repeat([]byte{1}, t.Config.PktSize),
Seq: ttl,
},
}
ipv4.NewPacketConn(t.icmpListen).SetTTL(ttl)
wb, err := icmpHeader.Marshal(nil)
if err != nil {
log.Fatal(err)
}
start := time.Now()
if _, err := t.icmpListen.WriteTo(wb, &net.IPAddr{IP: t.DestIP}); err != nil {
log.Fatal(err)
}
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
log.Fatal(err)
}
select {
case <-t.ctx.Done():
return nil
case h := <-t.inflightRequest[ttl]:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil
}
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
}
h.TTL = ttl
h.RTT = rtt
t.fetchLock.Lock()
defer t.fetchLock.Unlock()
h.fetchIPData(t.Config)
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && ttl > t.final {
return nil
}
t.res.add(Hop{
Success: false,
Address: nil,
TTL: ttl,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
return nil
}

326
trace/icmp_ipv6.go Normal file
View File

@@ -0,0 +1,326 @@
package trace
import (
"bytes"
"encoding/binary"
"log"
"net"
"os"
"strconv"
"sync"
"time"
"github.com/xgadget-lab/nexttrace/trace/internal"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
)
type ICMPTracerv6 struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
resCh chan Hop
inflightRequest map[int]chan Hop
inflightRequestRWLock sync.RWMutex
icmpListen net.PacketConn
final int
finalLock sync.Mutex
fetchLock sync.Mutex
}
func (t *ICMPTracerv6) PrintFunc() {
// defer t.wg.Done()
var ttl = t.Config.BeginHop - 1
for {
if t.AsyncPrinter != nil {
t.AsyncPrinter(&t.res)
}
// 接收的时候检查一下是不是 3 跳都齐了
if len(t.res.Hops)-1 > ttl {
if len(t.res.Hops[ttl]) == t.NumMeasurements {
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, ttl)
}
ttl++
if ttl == t.final {
return
}
}
}
<-time.After(200 * time.Millisecond)
}
}
func (t *ICMPTracerv6) Execute() (*Result, error) {
t.inflightRequestRWLock.Lock()
t.inflightRequest = make(map[int]chan Hop)
t.inflightRequestRWLock.Unlock()
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
var err error
t.icmpListen, err = internal.ListenICMP("ip6:58", t.SrcAddr)
if err != nil {
return &t.res, err
}
defer t.icmpListen.Close()
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.resCh = make(chan Hop)
t.final = -1
go t.listenICMP()
go t.PrintFunc()
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
t.inflightRequestRWLock.Lock()
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
t.inflightRequestRWLock.Unlock()
if t.final != -1 && ttl > t.final {
break
}
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(ttl)
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
}
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
}
// for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
// if t.final != -1 && ttl > t.final {
// break
// }
// for i := 0; i < t.NumMeasurements; i++ {
// t.wg.Add(1)
// go t.send(ttl)
// }
// // 一组TTL全部退出收到应答或者超时终止以后再进行下一个TTL的包发送
// t.wg.Wait()
// if t.RealtimePrinter != nil {
// t.RealtimePrinter(&t.res, ttl-1)
// }
// if t.AsyncPrinter != nil {
// t.AsyncPrinter(&t.res)
// }
// }
t.wg.Wait()
t.res.reduce(t.final)
if t.final != -1 {
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, t.final-1)
}
} else {
for i := 0; i < t.NumMeasurements; i++ {
t.res.add(Hop{
Success: false,
Address: nil,
TTL: 30,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, t.MaxHops-1)
}
}
return &t.res, nil
}
func (t *ICMPTracerv6) listenICMP() {
lc := NewPacketListener(t.icmpListen, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
if msg.Msg[0] == 129 {
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
echoReply, ok := rm.Body.(*icmp.Echo)
if ok {
ttl := echoReply.Seq // This is the TTL value
if ttl > 100 {
continue
}
if msg.Peer.String() == t.DestIP.String() {
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, ttl)
}
}
}
packet_id := strconv.FormatInt(int64(binary.BigEndian.Uint16(msg.Msg[52:54])), 2)
if process_id, ttl, err := reverseID(packet_id); err == nil {
if process_id == int64(os.Getpid()&0x7f) {
dstip := net.IP(msg.Msg[32:48])
// 无效包本地环回包
if dstip.String() == "::" {
continue
}
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
// 匹配再继续解析包,否则直接丢弃
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv6.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data, int(ttl))
case ipv6.ICMPTypeEchoReply:
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, int(ttl))
default:
// log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
// dstip := net.IP(msg.Msg[32:48])
// if binary.BigEndian.Uint16(msg.Msg[52:54]) != uint16(os.Getpid()&0xffff) {
// // // 如果类型为应答消息且应答消息包的进程ID与主进程相同时不跳过
// if binary.BigEndian.Uint16(msg.Msg[52:54]) != 0 {
// continue
// } else {
// if dstip.String() != "::" {
// continue
// }
// if msg.Peer.String() != t.DestIP.String() {
// continue
// }
// }
// }
// if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
// rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
// if err != nil {
// log.Println(err)
// continue
// }
// // log.Println(msg.Peer)
// switch rm.Type {
// case ipv6.ICMPTypeTimeExceeded:
// t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
// case ipv6.ICMPTypeEchoReply:
// t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
// default:
// // log.Println("received icmp message of unknown type", rm.Type)
// }
// }
}
}
}
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
t.inflightRequestRWLock.RLock()
defer t.inflightRequestRWLock.RUnlock()
if _, ok := t.inflightRequest[ttl]; ok {
t.inflightRequest[ttl] <- Hop{
Success: true,
Address: msg.Peer,
}
}
}
func (t *ICMPTracerv6) send(ttl int) error {
defer t.wg.Done()
if t.final != -1 && ttl > t.final {
return nil
}
id := gernerateID(ttl)
icmpHeader := icmp.Message{
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
Body: &icmp.Echo{
ID: id,
//Data: []byte("HELLO-R-U-THERE"),
Data: bytes.Repeat([]byte{1}, t.Config.PktSize),
Seq: ttl,
},
}
p := ipv6.NewPacketConn(t.icmpListen)
icmpHeader.Body.(*icmp.Echo).Seq = ttl
p.SetHopLimit(ttl)
wb, err := icmpHeader.Marshal(nil)
if err != nil {
log.Fatal(err)
}
start := time.Now()
if _, err := t.icmpListen.WriteTo(wb, &net.IPAddr{IP: t.DestIP}); err != nil {
log.Fatal(err)
}
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
log.Fatal(err)
}
select {
case <-t.ctx.Done():
return nil
case h := <-t.inflightRequest[ttl]:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil
}
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
}
h.TTL = ttl
h.RTT = rtt
t.fetchLock.Lock()
defer t.fetchLock.Unlock()
h.fetchIPData(t.Config)
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && ttl > t.final {
return nil
}
t.res.add(Hop{
Success: false,
Address: nil,
TTL: ttl,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
return nil
}

View File

@@ -0,0 +1,49 @@
//go:build darwin
package internal
import (
"context"
"errors"
"net"
"os"
"syscall"
"unsafe"
)
//go:linkname internetSocket net.internetSocket
func internetSocket(ctx context.Context, net string, laddr, raddr interface{}, sotype, proto int, mode string, ctrlCtxFn func(context.Context, string, string, syscall.RawConn) error) (fd unsafe.Pointer, err error)
//go:linkname newIPConn net.newIPConn
func newIPConn(fd unsafe.Pointer) *net.IPConn
var (
errUnknownNetwork = errors.New("unknown network type")
networkMap = map[string]string{
"ip4:icmp": "udp4",
"ip4:1": "udp4",
"ip6:icmp": "udp6",
"ip6:58": "udp6",
}
)
func ListenICMP(network string, laddr string) (net.PacketConn, error) {
if os.Getuid() == 0 { // root
return net.ListenPacket(network, laddr)
} else {
if nw, ok := networkMap[network]; ok {
proto := syscall.IPPROTO_ICMP
if nw == "udp6" {
proto = syscall.IPPROTO_ICMPV6
}
isock, err := internetSocket(context.Background(), nw, nil, nil, syscall.SOCK_DGRAM, proto, "listen", nil)
if err != nil {
panic(err)
}
return newIPConn(isock), nil
} else {
return nil, errUnknownNetwork
}
}
}

View File

@@ -0,0 +1,9 @@
//go:build !darwin
package internal
import "net"
func ListenICMP(network string, laddr string) (net.PacketConn, error) {
return net.ListenPacket(network, laddr)
}

View File

@@ -1,4 +1,4 @@
package listener_channel
package trace
import (
"golang.org/x/net/context"
@@ -13,21 +13,19 @@ type ReceivedMessage struct {
Err error
}
type ListenerChannel struct {
type PacketListener struct {
ctx context.Context
cancel context.CancelFunc
Conn net.PacketConn
Messages chan ReceivedMessage
}
func New(conn net.PacketConn) *ListenerChannel {
ctx, cancel := context.WithCancel(context.Background())
func NewPacketListener(conn net.PacketConn, ctx context.Context) *PacketListener {
results := make(chan ReceivedMessage, 50)
return &ListenerChannel{Conn: conn, ctx: ctx, cancel: cancel, Messages: results}
return &PacketListener{Conn: conn, ctx: ctx, Messages: results}
}
func (l *ListenerChannel) Start() {
func (l *PacketListener) Start() {
for {
select {
case <-l.ctx.Done():
@@ -55,7 +53,3 @@ func (l *ListenerChannel) Start() {
}
}
}
func (l *ListenerChannel) Stop() {
l.cancel()
}

File diff suppressed because one or more lines are too long

315
trace/tcp_ipv4.go Normal file
View File

@@ -0,0 +1,315 @@
package trace
import (
"log"
"math"
"math/rand"
"net"
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/xgadget-lab/nexttrace/util"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/sync/semaphore"
)
type TCPTracer struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
inflightRequest map[int]chan Hop
inflightRequestLock sync.Mutex
SrcIP net.IP
icmp net.PacketConn
tcp net.PacketConn
final int
finalLock sync.Mutex
sem *semaphore.Weighted
fetchLock sync.Mutex
}
func (t *TCPTracer) Execute() (*Result, error) {
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
var err error
if t.SrcAddr != "" {
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcAddr)
} else {
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcIP.String())
}
if err != nil {
return nil, err
}
t.icmp, err = icmp.ListenPacket("ip4:icmp", t.SrcAddr)
if err != nil {
return &t.res, err
}
defer t.icmp.Close()
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.inflightRequestLock.Lock()
t.inflightRequest = make(map[int]chan Hop)
t.inflightRequestLock.Unlock()
t.final = -1
go t.listenICMP()
go t.listenTCP()
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
// 如果到达最终跳,则退出
if t.final != -1 && ttl > t.final {
break
}
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(ttl)
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
}
if t.RealtimePrinter != nil {
// 对于实时模式应该按照TTL进行并发请求
t.wg.Wait()
t.RealtimePrinter(&t.res, ttl-1)
}
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
}
go func() {
if t.AsyncPrinter != nil {
for {
t.AsyncPrinter(&t.res)
time.Sleep(200 * time.Millisecond)
}
}
}()
// 如果是表格模式,则一次性并发请求
if t.RealtimePrinter == nil {
t.wg.Wait()
}
t.res.reduce(t.final)
return &t.res, nil
}
func (t *TCPTracer) listenICMP() {
lc := NewPacketListener(t.icmp, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
dstip := net.IP(msg.Msg[24:28])
if dstip.Equal(t.DestIP) {
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv4.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
case ipv4.ICMPTypeDestinationUnreachable:
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
default:
//log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
}
// @title listenTCP
// @description 监听TCP的响应数据包
func (t *TCPTracer) listenTCP() {
lc := NewPacketListener(t.tcp, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
if msg.Peer.String() != t.DestIP.String() {
continue
}
// 解包
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
// 从包中获取TCP layer信息
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
// 取得目标主机的Sequence Number
t.inflightRequestLock.Lock()
if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok {
// 最后一跳
ch <- Hop{
Success: true,
Address: msg.Peer,
}
}
t.inflightRequestLock.Unlock()
}
}
}
}
func (t *TCPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
header, err := util.GetICMPResponsePayload(data)
if err != nil {
return
}
sequenceNumber := util.GetTCPSeq(header)
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
ch, ok := t.inflightRequest[int(sequenceNumber)]
if !ok {
return
}
ch <- Hop{
Success: true,
Address: msg.Peer,
}
}
func (t *TCPTracer) send(ttl int) error {
err := t.sem.Acquire(context.Background(), 1)
if err != nil {
return err
}
defer t.sem.Release(1)
defer t.wg.Done()
if t.final != -1 && ttl > t.final {
return nil
}
// 随机种子
r := rand.New(rand.NewSource(time.Now().UnixNano()))
_, srcPort := util.LocalIPPort(t.DestIP)
ipHeader := &layers.IPv4{
SrcIP: t.SrcIP,
DstIP: t.DestIP,
Protocol: layers.IPProtocolTCP,
TTL: uint8(ttl),
}
// 使用Uint16兼容32位系统防止在rand的时候因使用int32而溢出
sequenceNumber := uint32(r.Intn(math.MaxUint16))
tcpHeader := &layers.TCP{
SrcPort: layers.TCPPort(srcPort),
DstPort: layers.TCPPort(t.DestPort),
Seq: sequenceNumber,
SYN: true,
Window: 14600,
}
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
desiredPayloadSize := t.Config.PktSize
payload := make([]byte, desiredPayloadSize)
copy(buf.Bytes(), payload)
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
return err
}
err = ipv4.NewPacketConn(t.tcp).SetTTL(ttl)
if err != nil {
return err
}
start := time.Now()
if _, err := t.tcp.WriteTo(buf.Bytes(), &net.IPAddr{IP: t.DestIP}); err != nil {
return err
}
t.inflightRequestLock.Lock()
hopCh := make(chan Hop)
t.inflightRequest[int(sequenceNumber)] = hopCh
t.inflightRequestLock.Unlock()
/*
// 这里属于 2个SenderN个Reciever的情况在哪里关闭Channel都容易导致Panic
defer func() {
t.inflightRequestLock.Lock()
close(hopCh)
delete(t.inflightRequest, srcPort)
t.inflightRequestLock.Unlock()
}()
*/
select {
case <-t.ctx.Done():
return nil
case h := <-hopCh:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil
}
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
}
h.TTL = ttl
h.RTT = rtt
t.fetchLock.Lock()
defer t.fetchLock.Unlock()
h.fetchIPData(t.Config)
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && ttl > t.final {
return nil
}
t.res.add(Hop{
Success: false,
Address: nil,
TTL: ttl,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
return nil
}

298
trace/tcp_ipv6.go Normal file
View File

@@ -0,0 +1,298 @@
package trace
import (
"encoding/binary"
"math"
"math/rand"
"net"
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/xgadget-lab/nexttrace/util"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
"golang.org/x/sync/semaphore"
)
type TCPTracerv6 struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
inflightRequest map[int]chan Hop
inflightRequestLock sync.Mutex
SrcIP net.IP
icmp net.PacketConn
tcp net.PacketConn
final int
finalLock sync.Mutex
sem *semaphore.Weighted
fetchLock sync.Mutex
}
func (t *TCPTracerv6) Execute() (*Result, error) {
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
t.SrcIP, _ = util.LocalIPPortv6(t.DestIP)
// log.Println(util.LocalIPPortv6(t.DestIP))
var err error
t.tcp, err = net.ListenPacket("ip6:tcp", t.SrcIP.String())
if err != nil {
return nil, err
}
t.icmp, err = icmp.ListenPacket("ip6:58", "::")
if err != nil {
return &t.res, err
}
defer t.icmp.Close()
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.inflightRequest = make(map[int]chan Hop)
t.final = -1
go t.listenICMP()
go t.listenTCP()
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
// 如果到达最终跳,则退出
if t.final != -1 && ttl > t.final {
break
}
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(ttl)
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
}
if t.RealtimePrinter != nil {
// 对于实时模式应该按照TTL进行并发请求
t.wg.Wait()
t.RealtimePrinter(&t.res, ttl-1)
}
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
}
go func() {
if t.AsyncPrinter != nil {
for {
t.AsyncPrinter(&t.res)
time.Sleep(200 * time.Millisecond)
}
}
}()
if t.RealtimePrinter == nil {
t.wg.Wait()
}
t.res.reduce(t.final)
return &t.res, nil
}
func (t *TCPTracerv6) listenICMP() {
lc := NewPacketListener(t.icmp, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
// log.Println(msg)
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
if err != nil {
// log.Println(err)
continue
}
switch rm.Type {
case ipv6.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg)
case ipv6.ICMPTypeDestinationUnreachable:
t.handleICMPMessage(msg)
default:
//log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
// @title listenTCP
// @description 监听TCP的响应数据包
func (t *TCPTracerv6) listenTCP() {
lc := NewPacketListener(t.tcp, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
// log.Println(msg)
// return
if msg.N == nil {
continue
}
if msg.Peer.String() != t.DestIP.String() {
continue
}
// 解包
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
// 从包中获取TCP layer信息
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
// 取得目标主机的Sequence Number
if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok {
// 最后一跳
ch <- Hop{
Success: true,
Address: msg.Peer,
}
}
}
}
}
}
func (t *TCPTracerv6) handleICMPMessage(msg ReceivedMessage) {
var sequenceNumber = binary.BigEndian.Uint32(msg.Msg[52:56])
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
ch, ok := t.inflightRequest[int(sequenceNumber)]
if !ok {
return
}
// log.Println("发送数据", sequenceNumber)
ch <- Hop{
Success: true,
Address: msg.Peer,
}
// log.Println("发送成功")
}
func (t *TCPTracerv6) send(ttl int) error {
err := t.sem.Acquire(context.Background(), 1)
if err != nil {
return err
}
defer t.sem.Release(1)
defer t.wg.Done()
if t.final != -1 && ttl > t.final {
return nil
}
// 随机种子
r := rand.New(rand.NewSource(time.Now().UnixNano()))
_, srcPort := util.LocalIPPortv6(t.DestIP)
ipHeader := &layers.IPv6{
SrcIP: t.SrcIP,
DstIP: t.DestIP,
NextHeader: layers.IPProtocolTCP,
HopLimit: uint8(ttl),
}
// 使用Uint16兼容32位系统防止在rand的时候因使用int32而溢出
sequenceNumber := uint32(r.Intn(math.MaxUint16))
tcpHeader := &layers.TCP{
SrcPort: layers.TCPPort(srcPort),
DstPort: layers.TCPPort(t.DestPort),
Seq: sequenceNumber,
SYN: true,
Window: 14600,
}
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
desiredPayloadSize := t.Config.PktSize
payload := make([]byte, desiredPayloadSize)
copy(buf.Bytes(), payload)
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
return err
}
ipv6.NewPacketConn(t.tcp).SetHopLimit(ttl)
if err != nil {
return err
}
start := time.Now()
if _, err := t.tcp.WriteTo(buf.Bytes(), &net.IPAddr{IP: t.DestIP}); err != nil {
return err
}
// log.Println(ttl, sequenceNumber)
t.inflightRequestLock.Lock()
hopCh := make(chan Hop)
t.inflightRequest[int(sequenceNumber)] = hopCh
t.inflightRequestLock.Unlock()
select {
case <-t.ctx.Done():
return nil
case h := <-hopCh:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil
}
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
}
h.TTL = ttl
h.RTT = rtt
t.fetchLock.Lock()
defer t.fetchLock.Unlock()
h.fetchIPData(t.Config)
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && ttl > t.final {
return nil
}
t.res.add(Hop{
Success: false,
Address: nil,
TTL: ttl,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
return nil
}

76
trace/temp_printer.go Normal file
View File

@@ -0,0 +1,76 @@
package trace
import (
"fmt"
"strings"
"github.com/xgadget-lab/nexttrace/ipgeo"
)
func HopPrinter(h Hop) {
if h.Address == nil {
fmt.Println("\t*")
} else {
txt := "\t"
if h.Hostname == "" {
txt += fmt.Sprint(h.Address, " ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
} else {
txt += fmt.Sprint(h.Hostname, " (", h.Address, ") ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
}
if h.Geo != nil {
txt += " " + formatIpGeoData(h.Address.String(), h.Geo)
}
fmt.Println(txt)
}
}
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
var res = make([]string, 0, 10)
if data.Asnumber == "" {
res = append(res, "*")
} else {
res = append(res, "AS"+data.Asnumber)
}
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
// TODO: 移动IDC判断到Hop.fetchIPData函数减少API调用
if strings.HasPrefix(ip, "9.") {
res = append(res, "LAN Address", "")
} else if strings.HasPrefix(ip, "11.") {
res = append(res, "LAN Address", "")
} else if data.Country == "" {
res = append(res, "LAN Address")
} else {
// 有些IP的归属信息为空这个时候将ISP的信息填入
if data.Owner == "" {
data.Owner = data.Isp
}
if data.District != "" {
data.City = data.City + ", " + data.District
}
if data.Prov == "" && data.City == "" {
// anyCast或是骨干网数据不应该有国家信息
data.Owner = data.Owner + ", " + data.Owner
} else {
// 非骨干网正常填入IP的国家信息数据
res = append(res, data.Country)
}
if data.Prov != "" {
res = append(res, data.Prov)
}
if data.City != "" {
res = append(res, data.City)
}
if data.Owner != "" {
res = append(res, data.Owner)
}
}
return strings.Join(res, ", ")
}

227
trace/trace.go Normal file
View File

@@ -0,0 +1,227 @@
package trace
import (
"errors"
"net"
"sync"
"time"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/util"
)
var (
ErrInvalidMethod = errors.New("invalid method")
ErrTracerouteExecuted = errors.New("traceroute already executed")
ErrHopLimitTimeout = errors.New("hop timeout")
geoCache = sync.Map{}
)
type Config struct {
SrcAddr string
BeginHop int
MaxHops int
NumMeasurements int
ParallelRequests int
Timeout time.Duration
DestIP net.IP
DestPort int
Quic bool
IPGeoSource ipgeo.Source
RDns bool
AlwaysWaitRDNS bool
PacketInterval int
TTLInterval int
Lang string
DN42 bool
RealtimePrinter func(res *Result, ttl int)
AsyncPrinter func(res *Result)
PktSize int
Maptrace bool
}
type Method string
const (
ICMPTrace Method = "icmp"
UDPTrace Method = "udp"
TCPTrace Method = "tcp"
)
type Tracer interface {
Execute() (*Result, error)
}
func Traceroute(method Method, config Config) (*Result, error) {
var tracer Tracer
if config.MaxHops == 0 {
config.MaxHops = 30
}
if config.NumMeasurements == 0 {
config.NumMeasurements = 3
}
if config.ParallelRequests == 0 {
config.ParallelRequests = config.NumMeasurements * 5
}
switch method {
case ICMPTrace:
if config.DestIP.To4() != nil {
tracer = &ICMPTracer{Config: config}
} else {
tracer = &ICMPTracerv6{Config: config}
}
case UDPTrace:
if config.DestIP.To4() != nil {
tracer = &UDPTracer{Config: config}
} else {
return nil, errors.New("IPv6 UDP Traceroute is not supported")
}
case TCPTrace:
if config.DestIP.To4() != nil {
tracer = &TCPTracer{Config: config}
} else {
tracer = &TCPTracerv6{Config: config}
// return nil, errors.New("IPv6 TCP Traceroute is not supported")
}
default:
return &Result{}, ErrInvalidMethod
}
return tracer.Execute()
}
type Result struct {
Hops [][]Hop
lock sync.Mutex
TraceMapUrl string
}
func (s *Result) add(hop Hop) {
s.lock.Lock()
defer s.lock.Unlock()
k := hop.TTL - 1
for len(s.Hops) < hop.TTL {
s.Hops = append(s.Hops, make([]Hop, 0))
}
s.Hops[k] = append(s.Hops[k], hop)
}
func (s *Result) reduce(final int) {
if final > 0 && final < len(s.Hops) {
s.Hops = s.Hops[:final]
}
}
type Hop struct {
Success bool
Address net.Addr
Hostname string
TTL int
RTT time.Duration
Error error
Geo *ipgeo.IPGeoData
Lang string
}
func (h *Hop) fetchIPData(c Config) (err error) {
// DN42
if c.DN42 {
var ip string
// 首先查找 PTR 记录
r, err := util.LookupAddr(h.Address.String())
if err == nil && len(r) > 0 {
h.Hostname = r[0][:len(r[0])-1]
ip = h.Address.String() + "," + h.Hostname
}
h.Geo, err = c.IPGeoSource(ip, c.Timeout, c.Lang, c.Maptrace)
return nil
}
// Debug Area
// c.AlwaysWaitRDNS = true
// Initialize a rDNS Channel
rDNSChan := make(chan []string)
fetchDoneChan := make(chan bool)
if c.RDns && h.Hostname == "" {
// Create a rDNS Query go-routine
go func() {
r, err := util.LookupAddr(h.Address.String())
if err != nil {
// No PTR Record
rDNSChan <- nil
} else {
// One PTR Record is found
rDNSChan <- r
}
}()
}
// Create Data Fetcher go-routine
go func() {
// Start to fetch IP Geolocation data
if c.IPGeoSource != nil && h.Geo == nil {
res := false
h.Lang = c.Lang
h.Geo, res = ipgeo.Filter(h.Address.String())
if !res {
timeout := c.Timeout
if c.Timeout < 2*time.Second {
timeout = 2 * time.Second
}
//h.Geo, err = c.IPGeoSource(h.Address.String(), timeout, c.Lang, c.Maptrace)
if cacheVal, ok := geoCache.Load(h.Address.String()); ok {
// 如果缓存中已有结果,直接使用
h.Geo = cacheVal.(*ipgeo.IPGeoData)
} else {
// 如果缓存中无结果,进行查询并将结果存入缓存
h.Geo, err = c.IPGeoSource(h.Address.String(), timeout, c.Lang, c.Maptrace)
if err == nil {
geoCache.Store(h.Address.String(), h.Geo)
}
}
}
}
// Fetch Done
fetchDoneChan <- true
}()
// Select Close Flag
var selectClose bool
if !c.AlwaysWaitRDNS {
select {
case <-fetchDoneChan:
// When fetch done signal recieved, stop waiting PTR record
case ptr := <-rDNSChan:
// process result
if err == nil && len(ptr) > 0 {
h.Hostname = ptr[0][:len(ptr[0])-1]
}
selectClose = true
}
} else {
select {
case ptr := <-rDNSChan:
// process result
if err == nil && len(ptr) > 0 {
h.Hostname = ptr[0]
}
// 1 second timeout
case <-time.After(time.Second * 1):
}
selectClose = true
}
// When Select Close, fetchDoneChan Reciever will also be closed
if selectClose {
// New a reciever to prevent channel congestion
<-fetchDoneChan
}
return
}

286
trace/udp.go Normal file
View File

@@ -0,0 +1,286 @@
package trace
import (
"log"
"net"
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/xgadget-lab/nexttrace/util"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/sync/semaphore"
)
type UDPTracer struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
inflightRequest map[int]chan Hop
inflightRequestLock sync.Mutex
icmp net.PacketConn
final int
finalLock sync.Mutex
sem *semaphore.Weighted
fetchLock sync.Mutex
}
func (t *UDPTracer) Execute() (*Result, error) {
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
var err error
t.icmp, err = icmp.ListenPacket("ip4:icmp", t.SrcAddr)
if err != nil {
return &t.res, err
}
defer t.icmp.Close()
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.inflightRequest = make(map[int]chan Hop)
t.final = -1
go t.listenICMP()
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
for ttl := 1; ttl <= t.MaxHops; ttl++ {
// 如果到达最终跳,则退出
if t.final != -1 && ttl > t.final {
break
}
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(ttl)
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
}
if t.RealtimePrinter != nil {
// 对于实时模式应该按照TTL进行并发请求
t.wg.Wait()
t.RealtimePrinter(&t.res, ttl-1)
}
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
}
go func() {
if t.AsyncPrinter != nil {
for {
t.AsyncPrinter(&t.res)
time.Sleep(200 * time.Millisecond)
}
}
}()
// 如果是表格模式,则一次性并发请求
if t.AsyncPrinter != nil {
t.wg.Wait()
}
t.res.reduce(t.final)
return &t.res, nil
}
func (t *UDPTracer) listenICMP() {
lc := NewPacketListener(t.icmp, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv4.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
case ipv4.ICMPTypeDestinationUnreachable:
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
default:
// log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
func (t *UDPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
header, err := util.GetICMPResponsePayload(data)
if err != nil {
return
}
srcPort := util.GetUDPSrcPort(header)
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
ch, ok := t.inflightRequest[int(srcPort)]
if !ok {
return
}
ch <- Hop{
Success: true,
Address: msg.Peer,
}
}
func (t *UDPTracer) getUDPConn(try int) (net.IP, int, net.PacketConn) {
srcIP, _ := util.LocalIPPort(t.DestIP)
var ipString string
if srcIP == nil {
ipString = ""
} else {
ipString = srcIP.String()
}
udpConn, err := net.ListenPacket("udp", ipString+":0")
if err != nil {
if try > 3 {
log.Fatal(err)
}
return t.getUDPConn(try + 1)
}
return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn
}
func (t *UDPTracer) send(ttl int) error {
err := t.sem.Acquire(context.Background(), 1)
if err != nil {
return err
}
defer t.sem.Release(1)
defer t.wg.Done()
if t.final != -1 && ttl > t.final {
return nil
}
srcIP, srcPort, udpConn := t.getUDPConn(0)
var payload []byte
if t.Quic {
payload = GenerateQuicPayloadWithRandomIds()
} else {
ipHeader := &layers.IPv4{
SrcIP: srcIP,
DstIP: t.DestIP,
Protocol: layers.IPProtocolTCP,
TTL: uint8(ttl),
}
udpHeader := &layers.UDP{
SrcPort: layers.UDPPort(srcPort),
DstPort: layers.UDPPort(t.DestPort),
}
_ = udpHeader.SetNetworkLayerForChecksum(ipHeader)
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
desiredPayloadSize := t.Config.PktSize
payload := make([]byte, desiredPayloadSize)
copy(buf.Bytes(), payload)
if err := gopacket.SerializeLayers(buf, opts, udpHeader); err != nil {
return err
}
payload = buf.Bytes()
}
err = ipv4.NewPacketConn(udpConn).SetTTL(ttl)
if err != nil {
return err
}
start := time.Now()
if _, err := udpConn.WriteTo(payload, &net.UDPAddr{IP: t.DestIP, Port: t.DestPort}); err != nil {
return err
}
// 在对inflightRequest进行写操作的时候应该加锁保护以免多个goroutine协程试图同时写入造成panic
t.inflightRequestLock.Lock()
hopCh := make(chan Hop)
t.inflightRequest[srcPort] = hopCh
t.inflightRequestLock.Unlock()
defer func() {
t.inflightRequestLock.Lock()
close(hopCh)
delete(t.inflightRequest, srcPort)
t.inflightRequestLock.Unlock()
}()
go func() {
reply := make([]byte, 1500)
_, peer, err := udpConn.ReadFrom(reply)
if err != nil {
// probably because we closed the connection
return
}
hopCh <- Hop{
Success: true,
Address: peer,
}
}()
select {
case <-t.ctx.Done():
return nil
case h := <-hopCh:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil
}
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
} else if addr, ok := h.Address.(*net.UDPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
}
h.TTL = ttl
h.RTT = rtt
t.fetchLock.Lock()
defer t.fetchLock.Unlock()
h.fetchIPData(t.Config)
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && ttl > t.final {
return nil
}
t.res.add(Hop{
Success: false,
Address: nil,
TTL: ttl,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
return nil
}

116
tracelog/log.go Normal file
View File

@@ -0,0 +1,116 @@
package tracelog
import (
"fmt"
"io"
"log"
"net"
"os"
"strconv"
"strings"
"github.com/xgadget-lab/nexttrace/trace"
)
func RealtimePrinter(res *trace.Result, ttl int) {
f, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
if err != nil {
return
}
defer func(f *os.File) {
err := f.Close()
if err != nil {
log.Fatal(err)
}
}(f)
multiWriter := io.MultiWriter(os.Stdout, f)
log.SetOutput(multiWriter)
log.SetFlags(0)
var res_str string
res_str += fmt.Sprintf("%-2d ", ttl+1)
// 去重
var latestIP string
tmpMap := make(map[string][]string)
for i, v := range res.Hops[ttl] {
if v.Address == nil && latestIP != "" {
tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*"))
continue
} else if v.Address == nil {
continue
}
if _, exist := tmpMap[v.Address.String()]; !exist {
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i))
// 首次进入
if latestIP == "" {
for j := 0; j < i; j++ {
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*"))
}
}
latestIP = v.Address.String()
}
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000))
}
if latestIP == "" {
res_str += fmt.Sprintf("%s\n", "*")
log.Print(res_str)
return
}
var blockDisplay = false
for ip, v := range tmpMap {
if blockDisplay {
res_str += fmt.Sprintf("%4s", "")
}
if net.ParseIP(ip).To4() == nil {
res_str += fmt.Sprintf("%-25s ", ip)
} else {
res_str += fmt.Sprintf("%-15s ", ip)
}
i, _ := strconv.Atoi(v[0])
if res.Hops[ttl][i].Geo.Asnumber != "" {
res_str += fmt.Sprintf("AS%-7s", res.Hops[ttl][i].Geo.Asnumber)
} else {
res_str += fmt.Sprintf(" %-8s", "*")
}
if net.ParseIP(ip).To4() != nil {
whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-")
if len(whoisFormat) > 1 {
whoisFormat[0] = strings.Join(whoisFormat[:2], "-")
}
if whoisFormat[0] != "" {
whoisFormat[0] = "[" + whoisFormat[0] + "]"
}
res_str += fmt.Sprintf("%-16s", whoisFormat[0])
}
if res.Hops[ttl][i].Geo.Country == "" {
res.Hops[ttl][i].Geo.Country = "LAN Address"
}
if net.ParseIP(ip).To4() != nil {
res_str += fmt.Sprintf(" %s %s %s %s %-6s\n %-39s ", res.Hops[ttl][i].Geo.Country, res.Hops[ttl][i].Geo.Prov, res.Hops[ttl][i].Geo.City, res.Hops[ttl][i].Geo.District, res.Hops[ttl][i].Geo.Owner, res.Hops[ttl][i].Hostname)
} else {
res_str += fmt.Sprintf(" %s %s %s %s %-6s\n %-35s ", res.Hops[ttl][i].Geo.Country, res.Hops[ttl][i].Geo.Prov, res.Hops[ttl][i].Geo.City, res.Hops[ttl][i].Geo.District, res.Hops[ttl][i].Geo.Owner, res.Hops[ttl][i].Hostname)
}
for j := 1; j < len(v); j++ {
if len(v) == 2 || j == 1 {
res_str += v[j]
} else {
res_str += fmt.Sprintf("/ %s", v[j])
}
}
log.Print(res_str)
blockDisplay = true
}
}

70
tracemap/tracemap.go Normal file
View File

@@ -0,0 +1,70 @@
package tracemap
import (
"crypto/tls"
"errors"
"fmt"
"github.com/fatih/color"
"github.com/xgadget-lab/nexttrace/util"
"io"
"net"
"net/http"
"net/url"
"strings"
"time"
)
func GetMapUrl(r string) (string, error) {
host, port := util.GetHostAndPort()
fastIp := "api.leo.moe"
// 如果 host 是一个 IP 使用默认域名
if valid := net.ParseIP(host); valid != nil {
host = "api.leo.moe"
} else {
// 默认配置完成,开始寻找最优 IP
fastIp = util.GetFastIP(host, port, false)
}
u := url.URL{Scheme: "https", Host: fastIp + ":" + port, Path: "/tracemap/api"}
tracemapUrl := u.String()
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
ServerName: host,
},
},
}
req, err := http.NewRequest("POST", tracemapUrl, strings.NewReader(r))
if err != nil {
return "", errors.New("an issue occurred while connecting to the tracemap API")
}
req.Header.Add("User-Agent", util.UserAgent)
req.Host = host
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return "", errors.New("an issue occurred while connecting to the tracemap API")
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", errors.New("an issue occurred while connecting to the tracemap API")
}
return string(body), nil
}
func PrintMapUrl(r string) {
_, err := fmt.Fprintf(color.Output, "%s %s\n",
color.New(color.FgWhite, color.Bold).Sprintf("%s", "MapTrace URL:"),
color.New(color.FgBlue, color.Bold).Sprintf("%s", string(r)),
)
if err != nil {
return
}
}

23
util/common.go Normal file
View File

@@ -0,0 +1,23 @@
package util
import "net"
func Dnspod() *net.Resolver {
return newDoTResolver("dot.pub", "dot.pub:853")
}
func Aliyun() *net.Resolver {
return newDoTResolver("dns.alidns.com", "dns.alidns.com:853")
}
func DNSSB() *net.Resolver {
return newDoTResolver("45.11.45.11", "dot.sb:853")
}
func Cloudflare() *net.Resolver {
return newDoTResolver("one.one.one.one", "one.one.one.one:853")
}
func Google() *net.Resolver {
return newDoTResolver("dns.google", "dns.google:853")
}

20
util/dns_test.go Normal file
View File

@@ -0,0 +1,20 @@
package util
import (
"context"
"fmt"
"testing"
)
func TestDNS(t *testing.T) {
resolver := DNSSB()
ips, _ := resolver.LookupHost(context.Background(), "www.bing.com")
fmt.Println(ips)
}
func TestDomainLookUp(t *testing.T) {
ips := DomainLookUp("pek-4134.nexttrace-io-fasttrace-endpoint.win.", "all", "", false)
fmt.Println(ips)
ips = DomainLookUp("pek-4134.nexttrace-io-fasttrace-endpoint.win.", "4", "", false)
fmt.Println(ips)
}

32
util/dot.go Normal file
View File

@@ -0,0 +1,32 @@
package util
import (
"context"
"crypto/tls"
"net"
"time"
)
func newDoTResolver(serverName string, addrs string) *net.Resolver {
d := &net.Dialer{
// 设置超时时间
Timeout: 1000 * time.Millisecond,
}
tls_config := &tls.Config{
// 设置 TLS Server Name 以确保证书能和域名对应
ServerName: serverName,
}
return &net.Resolver{
// 指定使用 Go Build-in 的 DNS Resolver 来解析
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
conn, err := d.DialContext(ctx, "tcp", addrs)
if err != nil {
return nil, err
}
return tls.Client(conn, tls_config), nil
},
}
}

75
util/latency.go Normal file
View File

@@ -0,0 +1,75 @@
package util
import (
"fmt"
"log"
"net"
"strings"
"time"
"github.com/fatih/color"
)
var (
result string
results = make(chan string)
)
var FastIpCache = ""
func GetFastIP(domain string, port string, enableOutput bool) string {
if FastIpCache != "" {
return FastIpCache
}
ips, err := net.LookupIP(domain)
if err != nil {
log.Fatal("DNS resolution failed, please check your system DNS Settings")
}
for _, ip := range ips {
go checkLatency(ip.String(), port)
}
select {
case result = <-results:
case <-time.After(1 * time.Second):
}
if result == "" {
log.Fatal("IP connection has been timeout, please check your network")
}
res := strings.Split(result, "-")
if len(ips) > 1 {
if enableOutput {
_, _ = fmt.Fprintf(color.Output, "%s prefered API IP - %s - %s\n",
color.New(color.FgWhite, color.Bold).Sprintf("[NextTrace API]"),
color.New(color.FgGreen, color.Bold).Sprintf("%s", res[0]),
color.New(color.FgCyan, color.Bold).Sprintf("%sms", res[1]),
)
}
}
FastIpCache = res[0]
return res[0]
}
func checkLatency(ip string, port string) {
start := time.Now()
if !strings.Contains(ip, ".") {
ip = "[" + ip + "]"
}
conn, err := net.DialTimeout("tcp", ip+":"+port, time.Second*1)
if err != nil {
return
}
defer func(conn net.Conn) {
err := conn.Close()
if err != nil {
return
}
}(conn)
if result == "" {
result = fmt.Sprintf("%s-%.2f", ip, float64(time.Since(start))/float64(time.Millisecond))
results <- result
return
}
}

9
util/latency_test.go Normal file
View File

@@ -0,0 +1,9 @@
package util
import (
"testing"
)
func TestGetFastIP(t *testing.T) {
GetFastIP("api.leo.moe", "443", true)
}

View File

@@ -1,11 +0,0 @@
package util
type IPGeoData struct {
Asnumber string `json:"asnumber"`
Country string `json:"country"`
Prov string `json:"prov"`
City string `json:"city"`
District string `json:"district"`
Owner string `json:"owner"`
Isp string `json:"isp"`
}

View File

@@ -1,20 +0,0 @@
package printer
import (
"fmt"
"net"
)
func PrintCopyRight() {
fmt.Println("NextTrace v0.1.0 Alpha \nxgadget-lab 实验室 zhsh (xzhsh.ch) & leo (leo.moe)")
}
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) {
fmt.Println("IP Geo Data Provider: " + dataOrigin)
if ip.String() == domain {
fmt.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ip.String())
} else {
fmt.Printf("traceroute to %s (%s), 30 hops max, 32 byte packets\n", ip.String(), domain)
}
}

View File

@@ -1,103 +0,0 @@
package printer
import (
"fmt"
"net"
"strings"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/methods"
)
var dataOrigin string
func TraceroutePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string) {
for hi := uint16(1); hi < 30; hi++ {
fmt.Print(hi)
for _, v := range res[hi] {
hopPrinter(v)
if v.Address != nil && ip.String() == v.Address.String() {
hi = 31
}
}
}
}
func hopPrinter(v2 methods.TracerouteHop) {
if v2.Address == nil {
fmt.Println("\t*")
} else {
var iPGeoData *ipgeo.IPGeoData
var err error
ipStr := v2.Address.String()
// TODO: 判断 err 返回并且在CLI终端提示错误
if dataOrigin == "LeoMoeAPI" {
iPGeoData, err = ipgeo.LeoIP(ipStr)
} else if dataOrigin == "IP.SB" {
iPGeoData, err = ipgeo.IPSB(ipStr)
} else if dataOrigin == "IPInfo" {
iPGeoData, err = ipgeo.IPInfo(ipStr)
} else if dataOrigin == "IPInsight" {
iPGeoData, err = ipgeo.IPInSight(ipStr)
} else {
iPGeoData, err = ipgeo.LeoIP(ipStr)
}
geo := ""
if err != nil {
geo = fmt.Sprint("Error: ", err)
} else {
geo = formatIpGeoData(ipStr, iPGeoData)
}
ptr, err := net.LookupAddr(ipStr)
txt := "\t"
if err != nil {
txt += fmt.Sprint(ipStr, " ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
} else {
txt += fmt.Sprint(ptr[0], " (", ipStr, ") ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
}
fmt.Println(txt)
}
}
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
var res = make([]string, 0, 10)
if data.Asnumber == "" {
res = append(res, "*")
} else {
res = append(res, "AS"+data.Asnumber)
}
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
if strings.HasPrefix(ip, "9.31.") || strings.HasPrefix(ip, "11.72.") {
res = append(res, "局域网", "腾讯云")
} else if strings.HasPrefix(ip, "11.13.") {
res = append(res, "局域网", "阿里云")
} else if data.Country == "" {
res = append(res, "局域网")
} else {
if data.Owner == "" {
data.Owner = data.Isp
}
if data.District != "" {
data.City = data.City + ", " + data.District
}
res = append(res, data.Country)
if data.Prov != "" {
res = append(res, data.Prov)
}
if data.City != "" {
res = append(res, data.City)
}
if data.Owner != "" {
res = append(res, data.Owner)
}
}
return strings.Join(res, ", ")
}

View File

@@ -1,114 +0,0 @@
package printer
import (
"fmt"
"net"
"github.com/fatih/color"
"github.com/rodaine/table"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/methods"
)
type rowData struct {
Hop int64
IP string
Latency string
Asnumber string
Country string
Prov string
City string
District string
Owner string
}
func TracerouteTablePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string) {
// 初始化表格
tbl := New()
for hi := uint16(1); hi < 30; hi++ {
for _, v := range res[hi] {
data := tableDataGenerator(v)
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Prov, data.City, data.Owner)
if v.Address != nil && ip.String() == v.Address.String() {
hi = 31
}
}
}
// 打印表格
tbl.Print()
}
func New() table.Table {
// 初始化表格
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
columnFmt := color.New(color.FgYellow).SprintfFunc()
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Country", "Province", "City", "Owner")
tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
return tbl
}
func tableDataGenerator(v2 methods.TracerouteHop) *rowData {
if v2.Address == nil {
return &rowData{
Hop: int64(v2.TTL),
IP: "*",
}
} else {
// 初始化变量
var iPGeoData *ipgeo.IPGeoData
var err error
var lantency, IP string
ipStr := v2.Address.String()
// TODO: 判断 err 返回并且在CLI终端提示错误
if dataOrigin == "LeoMoeAPI" {
iPGeoData, err = ipgeo.LeoIP(ipStr)
} else if dataOrigin == "IP.SB" {
iPGeoData, err = ipgeo.IPSB(ipStr)
} else if dataOrigin == "IPInfo" {
iPGeoData, err = ipgeo.IPInfo(ipStr)
} else if dataOrigin == "IPInsight" {
iPGeoData, err = ipgeo.IPInSight(ipStr)
} else {
iPGeoData, err = ipgeo.LeoIP(ipStr)
}
ptr, err_LookupAddr := net.LookupAddr(ipStr)
lantency = fmt.Sprintf("%.2fms", v2.RTT.Seconds()*1000)
if err_LookupAddr != nil {
IP = fmt.Sprint(ipStr)
} else {
IP = fmt.Sprint(ptr[0], " (", ipStr, ") ")
}
if iPGeoData.Owner == "" {
iPGeoData.Owner = iPGeoData.Isp
}
if err != nil {
fmt.Print("Error: ", err)
return &rowData{
Hop: int64(v2.TTL),
IP: IP,
Latency: lantency,
}
} else {
return &rowData{
Hop: int64(v2.TTL),
IP: IP,
Latency: lantency,
Asnumber: iPGeoData.Asnumber,
Country: iPGeoData.Country,
Prov: iPGeoData.Prov,
City: iPGeoData.City,
District: iPGeoData.District,
Owner: iPGeoData.Owner,
}
}
}
}

37
util/trace.go Normal file
View File

@@ -0,0 +1,37 @@
package util
import (
"encoding/binary"
"errors"
)
func GetIPHeaderLength(data []byte) (int, error) {
if len(data) < 1 {
return 0, errors.New("received invalid IP header")
}
return int((data[0] & 0x0F) * 4), nil
}
func GetICMPResponsePayload(data []byte) ([]byte, error) {
length, err := GetIPHeaderLength(data)
if err != nil {
return nil, err
}
if len(data) < length {
return nil, errors.New("length of packet too short")
}
return data[length:], nil
}
func GetUDPSrcPort(data []byte) uint16 {
srcPortBytes := data[:2]
srcPort := binary.BigEndian.Uint16(srcPortBytes)
return srcPort
}
func GetTCPSeq(data []byte) uint32 {
seqBytes := data[4:8]
return binary.BigEndian.Uint32(seqBytes)
}

12
util/udp.go Normal file
View File

@@ -0,0 +1,12 @@
package util
import (
"net"
)
func newUDPResolver() *net.Resolver {
return &net.Resolver{
// 指定使用 Go Build-in 的 DNS Resolver 来解析
PreferGo: true,
}
}

View File

@@ -1,13 +1,43 @@
package util
import (
"context"
"fmt"
"github.com/xgadget-lab/nexttrace/config"
"log"
"net"
"os"
"runtime"
"strings"
"sync"
"github.com/fatih/color"
)
// get the local ip and port based on our destination ip
var Uninterrupted = GetenvDefault("NEXTTRACE_UNINTERRUPTED", "")
var EnvToken = GetenvDefault("NEXTTRACE_TOKEN", "")
var UserAgent = fmt.Sprintf("NextTrace %s/%s/%s", config.Version, runtime.GOOS, runtime.GOARCH)
var RdnsCache sync.Map
func LookupAddr(addr string) ([]string, error) {
// 如果在缓存中找到,直接返回
if hostname, ok := RdnsCache.Load(addr); ok {
//fmt.Println("hit RdnsCache for", addr, hostname)
return []string{hostname.(string)}, nil
}
// 如果缓存中未找到,进行 DNS 查询
names, err := net.LookupAddr(addr)
if err != nil {
return nil, err
}
// 将查询结果存入缓存
if len(names) > 0 {
RdnsCache.Store(addr, names[0])
}
return names, nil
}
// 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 {
@@ -25,46 +55,130 @@ func LocalIPPort(dstip net.IP) (net.IP, int) {
return nil, -1
}
func DomainLookUp(host string) net.IP {
ips, err := net.LookupIP(host)
func LocalIPPortv6(dstip net.IP) (net.IP, int) {
serverAddr, err := net.ResolveUDPAddr("udp", "["+dstip.String()+"]:12345")
if err != nil {
log.Fatal(err)
}
// We don't actually connect to anything, but we can determine
// based on our destination ip what source ip we should use.
if con, err := net.DialUDP("udp", nil, serverAddr); err == nil {
defer con.Close()
if udpaddr, ok := con.LocalAddr().(*net.UDPAddr); ok {
return udpaddr.IP, udpaddr.Port
}
}
return nil, -1
}
func DomainLookUp(host string, ipVersion string, dotServer string, disableOutput bool) net.IP {
// ipVersion: 4, 6, all
var (
r *net.Resolver
ips []net.IP
)
switch dotServer {
case "dnssb":
r = DNSSB()
case "aliyun":
r = Aliyun()
case "dnspod":
r = Dnspod()
case "google":
r = Google()
case "cloudflare":
r = Cloudflare()
default:
r = newUDPResolver()
}
ipsStr, err := r.LookupHost(context.Background(), host)
for _, v := range ipsStr {
ips = append(ips, net.ParseIP(v))
}
if err != nil {
fmt.Println("Domain " + host + " Lookup Fail.")
os.Exit(1)
}
var ipSlice = []net.IP{}
var ipv6Flag = false
//var ipv6Flag = false
//TODO: 此处代码暂无意义
//if ipv6Flag {
// fmt.Println("[Info] IPv6 UDP Traceroute is not supported right now.")
// if len(ips) == 0 {
// os.Exit(0)
// }
//}
for _, ip := range ips {
// 仅返回ipv4的ip
if ip.To4() != nil {
ipSlice = append(ipSlice, ip)
} else {
ipv6Flag = true
// Filter by IPv4/IPv6
if ipVersion != "all" {
var filteredIPs []net.IP
for _, ip := range ips {
if ipVersion == "4" && ip.To4() != nil {
filteredIPs = append(filteredIPs, ip)
} else if ipVersion == "6" && strings.Contains(ip.String(), ":") {
filteredIPs = append(filteredIPs, ip)
}
}
ips = filteredIPs
}
if ipv6Flag {
fmt.Println("[Info] IPv6 Traceroute is not supported right now.")
if len(ipSlice) == 0 {
os.Exit(0)
}
}
if len(ipSlice) == 1 {
return ipSlice[0]
if (len(ips) == 1) || (disableOutput) {
return ips[0]
} else {
fmt.Println("Please Choose the IP You Want To TraceRoute")
for i, ip := range ipSlice {
fmt.Printf("%d. %s\n", i, ip)
for i, ip := range ips {
fmt.Fprintf(color.Output, "%s %s\n",
color.New(color.FgHiYellow, color.Bold).Sprintf("%d.", i),
color.New(color.FgWhite, color.Bold).Sprintf("%s", ip),
)
}
var index int
fmt.Printf("Your Option: ")
fmt.Scanln(&index)
if index >= len(ipSlice) || index < 0 {
_, err := fmt.Scanln(&index)
if err != nil {
index = 0
}
if index >= len(ips) || index < 0 {
fmt.Println("Your Option is invalid")
os.Exit(3)
}
return ipSlice[index]
return ips[index]
}
}
func GetenvDefault(key, defVal string) string {
val, ok := os.LookupEnv(key)
if ok {
return val
}
return defVal
}
func GetHostAndPort() (host string, port string) {
var hostP = GetenvDefault("NEXTTRACE_HOSTPORT", "api.leo.moe")
// 解析域名
hostArr := strings.Split(hostP, ":")
// 判断是否有指定端口
if len(hostArr) > 1 {
// 判断是否为 IPv6
if strings.HasPrefix(hostP, "[") {
tmp := strings.Split(hostP, "]")
host = tmp[0]
host = host[1:]
if port = tmp[1]; port != "" {
port = port[1:]
}
} else {
host, port = hostArr[0], hostArr[1]
}
} else {
host = hostP
}
if port == "" {
// 默认端口
port = "443"
}
return
}

244
wshandle/client.go Normal file
View File

@@ -0,0 +1,244 @@
package wshandle
import (
"crypto/tls"
"github.com/xgadget-lab/nexttrace/pow"
"github.com/xgadget-lab/nexttrace/util"
"log"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"sync"
"time"
"github.com/gorilla/websocket"
)
type WsConn struct {
Connecting bool
Connected bool // 连接状态
MsgSendCh chan string // 消息发送通道
MsgReceiveCh chan string // 消息接收通道
Done chan struct{} // 发送结束通道
Exit chan bool // 程序退出信号
Interrupt chan os.Signal // 终端中止信号
Conn *websocket.Conn // 主连接
ConnMux sync.Mutex // 连接互斥锁
}
var wsconn *WsConn
var host, port, fastIp string
var envToken = util.EnvToken
var cacheToken string
var cacheTokenFailedTimes int
func (c *WsConn) keepAlive() {
go func() {
// 开启一个定时器
for {
<-time.After(time.Second * 54)
if c.Connected {
err := c.Conn.WriteMessage(websocket.TextMessage, []byte("ping"))
if err != nil {
log.Println(err)
c.Connected = false
return
}
}
}
}()
for {
if !c.Connected && !c.Connecting {
c.Connecting = true
c.recreateWsConn()
// log.Println("WebSocket 连接意外断开,正在尝试重连...")
// return
}
// 降低检测频率,优化 CPU 占用情况
<-time.After(200 * time.Millisecond)
}
}
func (c *WsConn) messageReceiveHandler() {
// defer close(c.Done)
for {
if c.Connected {
_, msg, err := c.Conn.ReadMessage()
if err != nil {
// 读取信息出错,连接已经意外断开
// log.Println(err)
c.Connected = false
return
}
if string(msg) != "pong" {
c.MsgReceiveCh <- string(msg)
}
}
}
}
func (c *WsConn) messageSendHandler() {
for {
// 循环监听发送
select {
case <-c.Done:
log.Println("go-routine has been returned")
return
case t := <-c.MsgSendCh:
// log.Println(t)
if !c.Connected {
c.MsgReceiveCh <- `{"ip":"` + t + `", "asnumber":"API Server Error"}`
} else {
err := c.Conn.WriteMessage(websocket.TextMessage, []byte(t))
if err != nil {
log.Println("write:", err)
return
}
}
// 来自终端的中断运行请求
case <-c.Interrupt:
// 向 websocket 发起关闭连接任务
err := c.Conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
// log.Println("write close:", err)
os.Exit(1)
}
select {
// 等到了结果,直接退出
case <-c.Done:
// 如果等待 1s 还是拿不到结果,不再等待,超时退出
case <-time.After(time.Second):
}
os.Exit(1)
// return
}
}
}
func (c *WsConn) recreateWsConn() {
// 尝试重新连线
u := url.URL{Scheme: "wss", Host: fastIp + ":" + port, Path: "/v3/ipGeoWs"}
// log.Printf("connecting to %s", u.String())
jwtToken, ua := envToken, []string{"Privileged Client"}
err := error(nil)
if envToken == "" {
// 无环境变量 token
if cacheToken == "" {
// 无cacheToken, 重新获取 token
jwtToken, err = pow.GetToken(fastIp, host, port)
if err != nil {
log.Println(err)
os.Exit(1)
}
} else {
// 使用 cacheToken
jwtToken = cacheToken
}
ua = []string{util.UserAgent}
}
cacheToken = jwtToken
requestHeader := http.Header{
"Host": []string{host},
"User-Agent": ua,
"Authorization": []string{"Bearer " + jwtToken},
}
dialer := websocket.DefaultDialer
dialer.TLSClientConfig = &tls.Config{
ServerName: host,
}
ws, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader)
c.Conn = ws
if err != nil {
log.Println("dial:", err)
// <-time.After(time.Second * 1)
c.Connected = false
c.Connecting = false
if cacheTokenFailedTimes > 3 {
cacheToken = ""
}
cacheTokenFailedTimes += 1
//fmt.Println("重连失败", cacheTokenFailedTimes, "次")
return
} else {
c.Connected = true
}
c.Connecting = false
c.Done = make(chan struct{})
go c.messageReceiveHandler()
}
func createWsConn() *WsConn {
//fmt.Println("正在连接 WS")
// 设置终端中断通道
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
host, port = util.GetHostAndPort()
// 如果 host 是一个 IP 使用默认域名
if valid := net.ParseIP(host); valid != nil {
host = "api.leo.moe"
} else {
// 默认配置完成,开始寻找最优 IP
fastIp = util.GetFastIP(host, port, true)
}
jwtToken, ua := envToken, []string{"Privileged Client"}
err := error(nil)
if envToken == "" {
jwtToken, err = pow.GetToken(fastIp, host, port)
if err != nil {
log.Println(err)
os.Exit(1)
}
ua = []string{util.UserAgent}
}
cacheToken = jwtToken
cacheTokenFailedTimes = 0
requestHeader := http.Header{
"Host": []string{host},
"User-Agent": ua,
"Authorization": []string{"Bearer " + jwtToken},
}
dialer := websocket.DefaultDialer
dialer.TLSClientConfig = &tls.Config{
ServerName: host,
}
u := url.URL{Scheme: "wss", Host: fastIp + ":" + port, Path: "/v3/ipGeoWs"}
// log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader)
wsconn = &WsConn{
Conn: c,
Connected: true,
Connecting: false,
MsgSendCh: make(chan string, 10),
MsgReceiveCh: make(chan string, 10),
}
if err != nil {
log.Println("dial:", err)
// <-time.After(time.Second * 1)
wsconn.Connected = false
wsconn.Done = make(chan struct{})
go wsconn.keepAlive()
go wsconn.messageSendHandler()
return wsconn
}
// defer c.Close()
// 将连接写入WsConn方便随时可取
wsconn.Done = make(chan struct{})
go wsconn.keepAlive()
go wsconn.messageReceiveHandler()
go wsconn.messageSendHandler()
return wsconn
}
func New() *WsConn {
return createWsConn()
}
func GetWsConn() *WsConn {
return wsconn
}