Compare commits

..

204 Commits

Author SHA1 Message Date
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
44 changed files with 3066 additions and 1051 deletions

View File

@@ -5,7 +5,7 @@ set -e
DIST_PREFIX="nexttrace"
DEBUG_MODE=${2}
TARGET_DIR="dist"
PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 linux/mips"
PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips 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')"
@@ -15,6 +15,7 @@ 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}
@@ -37,4 +38,24 @@ for pl in ${PLATFORMS}; do
-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

View File

@@ -14,7 +14,7 @@ jobs:
go-version: "1.18"
- name: Test
run: go test -v -coverprofile='coverage.out' -covermode=count ./...
run: sudo go test -v -coverprofile='coverage.out' -covermode=count ./...
Build:
needs: test
@@ -36,8 +36,16 @@ jobs:
files: |
dist/nexttrace_darwin_amd64
dist/nexttrace_darwin_arm64
dist/nexttrace_linux_386
dist/nexttrace_linux_amd64
dist/nexttrace_linux_arm64
dist/nexttrace_linux_armv7
dist/nexttrace_linux_mips
dist/nexttrace_windows_amd64.exe
dist/nexttrace_windows_arm64.exe
dist/nexttrace_openbsd_amd64
dist/nexttrace_openbsd_arm64
dist/nexttrace_freebsd_amd64
dist/nexttrace_freebsd_arm64
env:
GITHUB_TOKEN: ${{ secrets.GT_Token }}
GITHUB_TOKEN: ${{ secrets.GT_Token }}

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

@@ -0,0 +1,46 @@
name: Publish New Formula
# Controls when the action will run. Workflow runs when manually triggered using the UI
# or API.
on:
push:
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 }}."

1
CNAME Normal file
View File

@@ -0,0 +1 @@
trace.ac

344
README.md
View File

@@ -4,197 +4,309 @@
</div>
# NextTrace
## NextTrace
一款开源的可视化路由跟踪工具,使用 Golang 开发。
An open source visual routing tool that pursues light weight, developed using Golang.
## How To Use
### Automated Install
Document Language: English | [简体中文](README_zh_CN.md)
### Automated Installation
```bash
#下载nt_install.sh
curl -Ls https://github.com/xgadget-lab/nexttrace/raw/main/nt_install.sh -O
#执行nt_install.sh
sudo bash nt_install.sh
# Linux one-click install script
bash <(curl -Ls https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)
# GHPROXY 镜像(国内使用)
bash -c "$(curl -Ls https://ghproxy.com/https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)"
# macOS brew install command
brew tap xgadget-lab/nexttrace && brew install nexttrace
```
### Alternative methods
Windows users please go to [Release Page](https://github.com/sjlleo/nexttrace/releases/latest) directly and download exe file.
如果你的目的只是为了快速测试服务器的到中国内地的线路,没有手动使用 NextTrace 的需求,那么建议你直接使用本仓库的 `quicklytest.sh`
```bash
#下载quicklytest.sh
curl -Ls https://github.com/xgadget-lab/nexttrace/raw/main/quicklytest.sh -O
#执行quicklytest.sh
sudo bash quicklytest.sh
```
- `Release` provides compiled executables for many systems and architectures, if not, you can compile it yourself.
- Some of the necessary dependencies of this project are not fully implemented in `Golang` on `Windows`, so currently `NextTrace` is experimental on `Windows` platform.
### Get Started
`NextTrace`默认使用`icmp`协议发起`TraceRoute`请求,该协议同时支持`IPv4``IPv6`
`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
# 表格打印一次性输出全部跳数需等待20-40秒
nexttrace -table 1.0.0.1
# 实时打印 (-realtime与-table不能同时选择)
nexttrace -realtime 1.0.0.1
# Form printing (output all hops at one time, wait 20-40 seconds)
nexttrace --table 1.0.0.1
# IPv6 ICMP Trace
nexttrace 2606:4700:4700::1111
# Path Visualization With the -M parameter, a map URL is returned
nexttrace --map koreacentral.blob.core.windows.net
# MapTrace URL: https://api.leo.moe/tracemap/html/c14e439e-3250-5310-8965-42a1e3545266.html
```
`NextTrace`也可以使用`TCP``UDP`协议发起`Traceroute`请求,不过目前只支持`IPv4`
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 these protocols only supports `IPv4` now
```bash
# TCP SYN Trace
nexttrace -T www.bing.com
nexttrace --tcp www.bing.com
# 可以自行指定端口[此处为443]默认80端口
nexttrace -T -p 443 1.0.0.1
# You can specify the port by yourself [here is 443], the default port is 80
nexttrace --tcp --port 443 1.0.0.1
# UDP Trace
nexttrace -U 1.0.0.1
nexttrace --udp 1.0.0.1
nexttrace -U -p 53 1.0.0.1
nexttrace --udp --port 53 1.0.0.1
```
`NextTrace`也同样支持一些进阶功能,如 IP 反向解析、并发数控制、模式切换等
`NextTrace` also supports some advanced functions, such as ttl control, concurrent probe packet count control, mode switching, etc.
```bash
# 每一跳发送2个探测包
nexttrace -q 2 www.hkix.net
# Send 2 probe packets per hop
nexttrace --queries 2 www.hkix.net
# 无并发,每次只发送一个探测包
nexttrace -r 1 www.hkix.net
# No concurrent probe packets, only one probe packet is sent at a time
nexttrace --parallel-requests 1 www.hkix.net
# 打开IP反向解析功能在IPv6的骨干网定位辅助有较大帮助
nexttrace -rdns www.bbix.net
# Start Trace with TTL of 5, end at TTL of 10
nexttrace --first 5 --max-hops 10 www.decix.net
# 特色功能打印Route-Path图
# Route-Path图示例
# AS6453 塔塔通信「Singapore『Singapore』」
# Turn off the IP reverse parsing function
nexttrace --no-rdns www.bbix.net
# 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 阿里云「ALIDNS.COM『ALIDNS.COM』」
nexttrace -report www.time.com.my
# ╰AS37963 Aliyun「ALIDNS.COM『ALIDNS.COM』」
nexttrace --route-path www.time.com.my
```
`NextTrace`支持用户自主选择 IP 数据库(目前支持:`LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`
`NextTrace` supports users to select their own IP API (currently supports: `LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`)
```bash
# 可以自行指定IP数据库[此处为IP.SB]不指定则默认为LeoMoeAPI
nexttrace -d IP.SB
## 特别的:其中 ipinfo API 需要从ipinfo自行购买服务如有需要可以clone本项目添加其提供的token自行编译
## TOKEN填写路径ipgeo/tokens.go
## 另外由于IP.SB被滥用比较严重会经常出现无法查询的问题请知悉。
## IPAPI.com限制调用较为严格如有查询不到的情况请几分钟后再试。
# You can specify the IP database by yourself [IP.SB here], if not specified, LeoMoeAPI will be used
nexttrace --data-provider IP.SB
## Note that the ipinfo API needs users to purchase services from ipinfo. If necessary, you can clone this project, add the token provided by ipinfo and compile it yourself
## Fill the token to: ipgeo/tokens.go
## Please be aware: Due to the serious abuse of IP.SB, you will often be not able to query IP data from this source
## IPAPI.com has a stricter restiction on API calls, if you can't query IP data from this source, please try again in a few minutes.
```
`NextTrace`支持参数混合使用
`NextTrace` supports mixed parameters and shortened parameters
```bash
Example:
nexttrace -d IPInsight -m 20 -p 443 -q 5 -r 20 -rdns -table 1.1.1.1
nexttrace -T -q 2 -r 1 -rdns -realtime -report 2001:4860:4860::8888
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 数据库
### IP Database
目前使用的 IP 数据库默认为我们自己搭建的 API 服务,如果后期遇到滥用,我们可能会选择关闭。
#### We use [bgp.tools](https://bgp.tools) as a data provider for routing tables.
我们也会在后期开放服务端源代码,您也可以根据该项目的源码自行搭建属于您的 API 服务器。
NextTrace BackEnd is now open-source.
NextTrace 所有的的 IP 地理位置`API DEMO`可以参考[这里](https://github.com/xgadget-lab/nexttrace/blob/main/ipgeo/)
https://github.com/sjlleo/nexttrace-backend
### 全部用法详见 Usage 菜单
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 of nexttrace:
'nexttrace [options] <hostname>' or 'nexttrace <hostname> [option...]'
Options:
-T Use TCP SYN for tracerouting (default port is 80)
-U Use UDP Package for tracerouting (default port is 53 in UDP)
-V Check Version
-d string
Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight, IPAPI.com] (default "LeoMoeAPI")
-m int
Set the max number of hops (max TTL to be reached). (default 30)
-p int
Set SYN Traceroute Port (default 80)
-q int
Set the number of probes per each hop. (default 3)
-r int
Set ParallelRequests number. It should be 1 when there is a multi-routing. (default 18)
-rdns
Set whether rDNS will be display
-realtime
Output trace results in runtime
-table
Output trace results as table
-report
Route Path
Usage: nexttrace [-h|--help] [-T|--tcp] [-U|--udp] [-F|--fast-trace] [-p|--port
<integer>] [-q|--queries <integer>] [--parallel-requests
<integer>] [-m|--max-hops <integer>] [-d|--data-provider
(IP.SB|IPInfo|IPInsight|IPAPI.com)] [-n|--no-rdns]
[-r|--route-path] [-o|--output] [-t|--table] [-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>]
[IP Address or Domain name]
Arguments:
-h --help Print help information
-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
[LeoMoeAPI,IP.SB, IPInfo, IPInsight,
IPAPI.com]. Default: LeoMoeAPI
-n --no-rdns Do not resolve IP addresses to their
domain names
-r --route-path Print traceroute hop path by ASN and
location
-o --output Write trace result to file
(RealTimePrinter ONLY)
-t --table Output trace results as table
-c --classic Classic Output trace results like
BestTrace
-f --first Start from the first_ttl hop (instead from
1). Default: 1
-M --map Print Trace Map. This will return a Trace
Map URL
-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 the time interval for sending every
packet. Useful when some routers use
rate-limit for ICMP messages.. Default: 0
-i --ttl-time Set the time interval for sending packets
groups by TTL. Useful when some routers
use rate-limit for ICMP messages..
Default: 500
```
## 项目截图
## Project screenshot
![NextTrace Screenshot](asset/screenshot.png)
![image](https://user-images.githubusercontent.com/13616352/208289553-7f633f9c-7356-40d1-bbc4-cc2687419cca.png)
## FAQ 常见问题
![image](https://user-images.githubusercontent.com/13616352/208289568-2a135c2d-ae4a-4a3e-8a43-f5a9a87ade4a.png)
如果你在安装或者使用的时候遇到了问题,我们建议你不要把新建一个 `issue` 作为首选项
或许可以在这里找到答案 -> [前往 Github Wiki](https://github.com/xgadget-lab/nexttrace/wiki/FAQ---%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94)
## NextTrace Enhanced
如果在搜索引擎WiKi 处还是没有解决,可以试试在本项目讨论区交流 -> [前往 Github Discussions](https://github.com/xgadget-lab/nexttrace/discussions)
`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.
最终如果你确认遇到的是 BUG、上述方法都不能解决的话请按照提供好的模版在 Issues 区提出问题 -> [前往 Github Issues](https://github.com/xgadget-lab/nexttrace/issues)
The `Enhanced` version supports many functions that the `lite` version does not have, such as the ability to customize the timeout period, and the ability to specify TTL as the starting point for route tracking, etc. For ordinary users, the `lite` version is usually enough.
## Thanks
https://github.com/OwO-Network/nexttrace-enhanced
## 公告
我今天看到了一些非常难过的事情,一些用户在 BestTrace 和 WorstTrace 下面宣传 NextTrace 的完全可替代性。
这么做是不正确的NextTrace 从来都不是一个从零开始的软件NextTrace 之所以能够拥有某些功能特性,是因为吸取了 BestTrace 、WorstTrace 的一些想法。
我们希望您在使用的时候知晓这一点,**我们是站在巨人的肩膀上,而尊重其他软件作者,向他们或者是我们提交 Bug 或贡献代码,才是推动整个 traceroute 工具的软件多样化发展的最好方式**。
NextTrace 并不追求成为一个替代者,同类软件越多样化,才能满足更多人的需求,这才是我们希望看到的,而去诋毁其他软件,这违背了我们对于开发 NextTrace 的初衷。
我们希望看到这条公告的朋友应该主动删除自己过激的言论,如果您有任何问题或建议,请随时在我们的社区中发表。
## LeoMoeAPI Credit
NextTrace 重点在于研究 Go 语言 Traceroute 的实现,其 LeoMoeAPI 的地理位置信息并没有原始数据的支撑,故也不可能有商用版本。
LeoMoeAPI 存在部分社区贡献者校准的数据,也包含了部分其他第三方数据库的数据,这些数据的所有权归校准者、第三方数据库所有,**仅供路由跟踪地理位置的展示参考使用**,我们不对数据提供准度做任何保证,请尊重他们的成果,如用于其他用途后果自负,特此告知。
1. 对于辛勤提供马来西亚地区节点的 samleong123、全球节点的 TOHUNET Looking Glass 以及来自 Misaka 的 Ping.sx 表示感谢,目前 80% 以上的可靠校准数据出自这些节点的 ping / mtr 报告。
2. 同时感谢 isyekong 在基于 rDNS 校准上思路以及数据上做出的贡献LeoMoeAPI 正在加快对 rDNS 的解析功能研发,目前已经做到部分骨干网的地理位置自动化解析,但存在一定误判。
我们希望 NextTrace 在未来能成为对 One-Man ISP 友好的 Traceroute 工具,我们也在尽可能完善对这些 ASN 的微型骨干网的校准。
3. 在开发上,我要由衷感谢 missuo 以及 zhshch 在 Go 交叉编译、设计理念以及 TCP/UDP Traceroute 重构上的帮助、tsosunchia 在 TraceMap 上的倾力支持。
4. 我还要感谢 FFEE_CO、TheresaQWQ、stydxm 和其他朋友的帮助。LeoMoeAPI自首次发布以来得到了很多各方面的支持所以我想把他们都归功于此。
我们希望您能够在使用时尽可能多多反馈 IP 地理位置错误(详见 issue这样它就能够在第一时间得到校准他人也会因此而受益。
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 on 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.
## FAQ Frequently Asked Questions
If you encounter problems while installing or using it, we do not recommend you to choose creating an `issue` as a preference
Here is our recommended troubleshooting process:
1. Check if it is already in FAQ -> [Go to Github Wiki](https://github.com/xgadget-lab/nexttrace/wiki/FAQ---%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94)
2. Suspected bug or feature suggestion -> [Go to Github Issues](https://github.com/xgadget-lab/nexttrace/issues)
## 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 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)
[waiting4new](https://github.com/waiting4new)、[FFEE_CO](https://github.com/fkx4-p)、[nsnnns](https://github.com/tsosunchia)
[tsosunchia](https://github.com/tsosunchia)
## IP Database Copyright
[waiting4new](https://github.com/waiting4new)
### IPv4 Database
#### China MainLand
- 项目组自行维护 ~ 御三家骨干网数据 ~ 5%
- 埃文科技 Paid Database ~ 95%
**这里有朋友就要问了,为什么不全部使用埃文的付费库?**
埃文的库一直都不是最优选择IPIP.NET 才是,但是因为他们不对私,所以我们只能选择价格更便宜的埃文库。
埃文家的数据库,在骨干网这块,准度可以说是非常糟糕,作为一款可视化的路由跟踪工具,骨干网的数据库准度非常重要。
所以我们选择了尝试自行去校准一部分骨干网数据,但是由于我们缺乏检测节点以及志愿者,所以这项工作可能会进展的尤其缓慢。
#### WorldWide
- 埃文科技 Paid Database ~ 15%
- IpInfo Free ~ 15%
- IPInSight Free ~ 70%
### IPv6 Database
This product includes IP2Location LITE data available from <a href="https://lite.ip2location.com">https://lite.ip2location.com</a>.
[FFEE_CO](https://github.com/fkx4-p)
### Others
其他第三方 API 尽管集成在本项目内,但是具体的 TOS 以及 AUP,请详见第三方 API 官网。如遇到 IP 数据错误,也请直接联系他们纠错。
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.
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=xgadget-lab/nexttrace&type=Date)](https://star-history.com/#xgadget-lab/nexttrace&Date)

263
README_zh_CN.md Normal file
View File

@@ -0,0 +1,263 @@
<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/zu1k/nali/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>
## How To Use
### Automated Install
```bash
# Linux 一键安装脚本
bash <(curl -Ls https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)
# GHPROXY 镜像(国内使用)
bash <(curl -Ls https://ghproxy.com/https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)
# macOS brew 安装命令
brew tap xgadget-lab/nexttrace && brew install nexttrace
```
Windows 用户请直接前往 [Release](https://github.com/sjlleo/nexttrace/releases/latest) 下载编译后的二进制 exe 文件。
- `Release`里面为很多系统以及不同架构提供了编译好的二进制可执行文件,如果没有可以自行编译。
- 一些本项目的必要依赖在`Windows``Golang`底层实现不完全,所以目前`NextTrace``Windows`平台出于实验性支持阶段。
### Get Started
`NextTrace` 默认使用`ICMP`协议发起`TraceRoute`请求,该协议同时支持`IPv4``IPv6`
```bash
# IPv4 ICMP Trace
nexttrace 1.0.0.1
# 表格打印,使用 --table / -t 参数,将实时显示结果
nexttrace --table 1.0.0.1
# IPv6 ICMP Trace
nexttrace 2606:4700:4700::1111
# 路径可视化 使用 --map / -M 参数,将返回一个地图 URL
nexttrace --map 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 进行测试(不支持 IPv6
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`请求,不过目前只支持`IPv4`
```bash
# TCP SYN Trace
nexttrace --tcp www.bing.com
# 可以自行指定端口[此处为443]默认80端口
nexttrace --tcp --port 443 1.0.0.1
# 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
# 特色功能打印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`
```bash
# 可以自行指定IP数据库[此处为IP.SB]不指定则默认为LeoMoeAPI
nexttrace --data-provider IP.SB
## 特别的:其中 ipinfo API 需要从 ipinfo 自行购买服务,如有需要可以 clone 本项目添加其提供的 token 自行编译
## TOKEN填写路径ipgeo/tokens.go
## 另外由于IP.SB被滥用比较严重会经常出现无法查询的问题请知悉。
## IPAPI.com限制调用较为严格如有查询不到的情况请几分钟后再试。
```
`NextTrace`支持使用混合参数和简略参数
```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
```
### 全部用法详见 Usage 菜单
```shell
Usage: nexttrace [-h|--help] [-T|--tcp] [-U|--udp] [-F|--fast-trace] [-p|--port
<integer>] [-q|--queries <integer>] [--parallel-requests
<integer>] [-m|--max-hops <integer>] [-d|--data-provider
(IP.SB|IPInfo|IPInsight|IPAPI.com)] [-n|--no-rdns]
[-r|--route-path] [-o|--output] [-t|--table] [-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>]
[IP Address or Domain name]
Arguments:
-h --help Print help information
-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
[LeoMoeAPI,IP.SB, IPInfo, IPInsight,
IPAPI.com]. Default: LeoMoeAPI
-n --no-rdns Do not resolve IP addresses to their
domain names
-r --route-path Print traceroute hop path by ASN and
location
-o --output Write trace result to file
(RealTimePrinter ONLY)
-t --table Output trace results as table
-c --classic Classic Output trace results like
BestTrace
-f --first Start from the first_ttl hop (instead from
1). Default: 1
-M --map Print Trace Map. This will return a Trace
Map URL
-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 the time interval for sending every
packet. Useful when some routers use
rate-limit for ICMP messages.. Default: 0
-i --ttl-time Set the time interval for sending packets
groups by TTL. Useful when some routers
use rate-limit for ICMP messages..
Default: 500
```
## 项目截图
![image](https://user-images.githubusercontent.com/13616352/208289553-7f633f9c-7356-40d1-bbc4-cc2687419cca.png)
![image](https://user-images.githubusercontent.com/13616352/208289568-2a135c2d-ae4a-4a3e-8a43-f5a9a87ade4a.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 Enhanced
https://github.com/OwO-Network/nexttrace-enhanced
## Thanks
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 数据错误,也请直接联系他们纠错。

1
_config.yml Normal file
View File

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

BIN
asset/nexttrace021.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 907 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

242
cmd/cmd.go Normal file
View File

@@ -0,0 +1,242 @@
package cmd
import (
"encoding/json"
"fmt"
"log"
"net"
"os"
"os/signal"
"runtime"
"strings"
"time"
"github.com/akamensky/argparse"
"github.com/syndtr/gocapability/capability"
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
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{"IP.SB", "IPInfo", "IPInsight", "IPAPI.com"}, &argparse.Options{Default: "LeoMoeAPI",
Help: "Choose IP Geograph Data Provider [LeoMoeAPI,IP.SB, IPInfo, IPInsight, IPAPI.com]"})
noRdns := parser.Flag("n", "no-rdns", &argparse.Options{Help: " Do not resolve IP addresses to their domain names"})
routePath := parser.Flag("r", "route-path", &argparse.Options{Help: "Print traceroute hop path by ASN and location"})
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"})
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)"})
maptrace := parser.Flag("M", "map", &argparse.Options{Help: "Print Trace Map. This will return a Trace Map URL"})
ver := parser.Flag("v", "version", &argparse.Options{Help: "Print version info and exit"})
src_addr := parser.String("s", "source", &argparse.Options{Help: "Use source src_addr for outgoing packets"})
src_dev := 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]"})
packet_interval := parser.Int("z", "send-time", &argparse.Options{Default: 0, Help: "Set the time interval for sending every packet. Useful when some routers use rate-limit for ICMP messages."})
ttl_interval := parser.Int("i", "ttl-time", &argparse.Options{Default: 500, Help: "Set the time interval for sending packets groups by TTL. Useful when some routers use rate-limit for ICMP messages."})
str := parser.StringPositional(&argparse.Options{Help: "IP Address or domain name"})
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
}
printer.Version()
if *ver {
printer.CopyRight()
os.Exit(0)
}
domain := *str
if *port == 0 {
*port = 80
}
if *fast_trace {
fastTrace.FastTest(*tcp, *output)
if *output {
fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中")
}
os.Exit(0)
}
if domain == "" {
fmt.Print(parser.Usage(err))
return
}
capabilities_check()
// return
var ip net.IP
if runtime.GOOS == "windows" && (*tcp || *udp) {
fmt.Println("NextTrace 基于 Windows 的路由跟踪还在早期开发阶段目前还存在诸多问题TCP/UDP SYN 包请求可能不能正常运行")
}
if *tcp || *udp {
ip = util.DomainLookUp(domain, true)
} else {
ip = util.DomainLookUp(domain, false)
}
if *src_dev != "" {
dev, _ := net.InterfaceByName(*src_dev)
if addrs, err := dev.Addrs(); err == nil {
for _, addr := range addrs {
if (addr.(*net.IPNet).IP.To4() == nil) == (ip.To4() == nil) {
*src_addr = addr.(*net.IPNet).IP.String()
}
}
}
}
if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" {
w := wshandle.New()
w.Interrupt = make(chan os.Signal, 1)
signal.Notify(w.Interrupt, os.Interrupt)
defer func() {
w.Conn.Close()
}()
}
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
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{
SrcAddr: *src_addr,
BeginHop: *beginHop,
DestIP: ip,
DestPort: *port,
MaxHops: *maxHops,
PacketInterval: *packet_interval,
TTLInterval: *ttl_interval,
NumMeasurements: *numMeasurements,
ParallelRequests: *parallelRequests,
RDns: !*noRdns,
IPGeoSource: ipgeo.GetSource(*dataOrigin),
Timeout: 1 * time.Second,
}
if !*tablePrint {
if *classicPrint {
conf.RealtimePrinter = printer.ClassicPrinter
} else {
if *output {
conf.RealtimePrinter = tracelog.RealtimePrinter
} else if *router {
conf.RealtimePrinter = printer.RealtimePrinterWithRouter
fmt.Println("路由表数据源由 BGP.Tools 提供,在此特表感谢")
} else {
conf.RealtimePrinter = printer.RealtimePrinter
}
}
} else {
conf.AsyncPrinter = printer.TracerouteTablePrinter
}
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()
}
if *maptrace {
r, _ := json.Marshal(res)
tracemap.GetMapUrl(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 {
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 {
// 没权限啦
log.Println("您正在以普通用户权限运行 NextTrace但 NextTrace 未被赋予监听网络套接字的ICMP消息包、修改IP头信息TTL等路由跟踪所需的权限")
log.Println("请使用管理员用户执行 `sudo setcap cap_net_raw,cap_net_admin+eip ${your_nexttrace_path}/nexttrace` 命令,赋予相关权限后再运行~")
log.Fatalln("什么?为什么 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()
}

170
fast_trace/basic.go Normal file
View File

@@ -0,0 +1,170 @@
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
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"
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: "106.37.67.1",
IPv6: "240e:40:e002:1:a:3ee3:c00:0",
},
CU169: ISPCollection{
ISPName: CU169,
IP: "123.125.96.156",
IPv6: "2408:8000:1010:2::10",
},
CM: ISPCollection{
ISPName: CM,
IP: "211.136.25.153",
IPv6: "2409:8000:3800:8::3",
},
EDU: ISPCollection{
ISPName: EDU,
IP: "101.6.15.130",
IPv6: "2001:da8::666",
},
}
var Shanghai = BackBoneCollection{
Location: "上海",
CT163: ISPCollection{
ISPName: CT163,
IP: "101.226.28.198",
IPv6: "240e:18:2:153::89",
},
CTCN2: ISPCollection{
ISPName: CTCN2,
IP: "58.32.4.1",
},
CU169: ISPCollection{
ISPName: CU169,
IP: "139.226.206.150",
IPv6: "2408:8000:9000:0:4000::437",
},
CU9929: ISPCollection{
ISPName: CU9929,
IP: "210.13.86.1",
IPv6: "2408:8120:2::d6",
},
CM: ISPCollection{
ISPName: CM,
IP: "120.204.34.85",
IPv6: "2409:801e:f0:1::4e1",
},
EDU: ISPCollection{
ISPName: EDU,
IP: "202.120.58.155",
IPv6: "2001:da8:8000:1:202:120:2:100",
},
}
var Guangzhou = BackBoneCollection{
Location: "广州",
CT163: ISPCollection{
ISPName: CT163,
IP: "14.116.225.60",
IPv6: "240e:f9:8010::3:110:1",
},
CU169: ISPCollection{
ISPName: CU169,
IP: "157.18.0.22",
IPv6: "2408:8001:3161:4::1",
},
CM: ISPCollection{
ISPName: CM,
IP: "120.198.26.254",
IPv6: "2409:8055:3008:1116::150",
},
}
var Hangzhou = BackBoneCollection{
Location: "杭州",
CT163: ISPCollection{
ISPName: CT163,
IP: "61.164.23.196",
IPv6: "240e:f3:c000:201::10",
},
CU169: ISPCollection{
ISPName: CU169,
IP: "60.12.244.1",
IPv6: "",
},
CM: ISPCollection{
ISPName: CM,
IP: "112.17.224.98",
IPv6: "2409:8028:840:2::11",
},
// 浙江大学 教育网
EDU: ISPCollection{
ISPName: EDU,
IP: "210.32.2.1",
IPv6: "2001:da8:e000:1::1",
},
}
var Hefei = BackBoneCollection{
Location: "合肥",
// 中国科学技术大学 教育网
EDU: ISPCollection{
ISPName: EDU,
IP: "202.38.64.1",
IPv6: "2001:da8:d805:ffff:2::1",
},
// 中国科学技术大学 科技网
CST: ISPCollection{
ISPName: "中国科学技术大学 科技网 AS7497",
IP: "210.72.22.2",
},
}

View File

@@ -0,0 +1,134 @@
package fastTrace
import (
"fmt"
"log"
"net"
"os"
"os/signal"
"time"
"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/wshandle"
)
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 fp.Close()
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, 30 hops max, 32 byte packets\n", ispCollection.IPv6)
log.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ispCollection.IPv6)
ip := net.ParseIP(ispCollection.IPv6)
var conf = trace.Config{
BeginHop: 1,
DestIP: ip,
DestPort: 80,
MaxHops: 30,
NumMeasurements: 3,
ParallelRequests: 18,
RDns: true,
IPGeoSource: ipgeo.GetSource("LeoMoeAPI"),
Timeout: 1 * time.Second,
}
if f.TracerouteMethod == trace.ICMPTrace {
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) {
var c string
oe = outEnable
fmt.Println("您想测试哪些ISP的路由\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网")
fmt.Print("请选择选项:")
fmt.Scanln(&c)
ft := FastTracer{}
// 建立 WebSocket 连接
w := wshandle.New()
w.Interrupt = make(chan os.Signal, 1)
signal.Notify(w.Interrupt, os.Interrupt)
defer func() {
w.Conn.Close()
}()
ft.TracerouteMethod = trace.ICMPTrace
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()
}
}

153
fast_trace/fast_trace.go Normal file
View File

@@ -0,0 +1,153 @@
package fastTrace
import (
"fmt"
"log"
"net"
"os"
"os/signal"
"time"
"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/wshandle"
)
type FastTracer struct {
TracerouteMethod trace.Method
}
var oe bool = false
func (f *FastTracer) tracert(location string, ispCollection ISPCollection) {
fp, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
if err != nil {
return
}
defer fp.Close()
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, 30 hops max, 32 byte packets\n", ispCollection.IP)
log.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ispCollection.IP)
ip := net.ParseIP(ispCollection.IP)
var conf = trace.Config{
BeginHop: 1,
DestIP: ip,
DestPort: 80,
MaxHops: 30,
NumMeasurements: 3,
ParallelRequests: 18,
RDns: true,
IPGeoSource: ipgeo.GetSource("LeoMoeAPI"),
Timeout: 1 * time.Second,
}
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() {
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.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.Shanghai.Location, TestIPsCollection.Shanghai.CM)
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)
}
func FastTest(tm bool, outEnable bool) {
var c string
oe = outEnable
fmt.Println("请您选择要测试的 IP 类型\n1. IPv4\n2. IPv6")
fmt.Print("请选择选项:")
fmt.Scanln(&c)
if c == "2" {
FastTestv6(tm, outEnable)
return
}
fmt.Println("您想测试哪些ISP的路由\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网")
fmt.Print("请选择选项:")
fmt.Scanln(&c)
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()
case "2":
ft.testCT()
case "3":
ft.testCU()
case "4":
ft.testCM()
case "5":
ft.testEDU()
default:
ft.testAll()
}
}

View File

@@ -0,0 +1,25 @@
package fastTrace
import (
"os"
"os/signal"
"testing"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/wshandle"
)
// ICMP Use Too Many Time to Wait So we don't test it.
func TestTCPTrace(t *testing.T) {
ft := FastTracer{}
// 建立 WebSocket 连接
w := wshandle.New()
w.Interrupt = make(chan os.Signal, 1)
signal.Notify(w.Interrupt, os.Interrupt)
defer func() {
w.Conn.Close()
}()
ft.TracerouteMethod = trace.TCPTrace
ft.testCM()
ft.testEDU()
}

25
go.mod
View File

@@ -1,27 +1,30 @@
module github.com/xgadget-lab/nexttrace
go 1.18
go 1.19
require (
github.com/akamensky/argparse v1.4.0
github.com/google/gopacket v1.1.19
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
golang.org/x/net v0.5.0
golang.org/x/sync v0.1.0
)
require (
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.13.0
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rodaine/table v1.0.1
github.com/stretchr/testify v1.7.1
github.com/tidwall/gjson v1.14.1
github.com/gorilla/websocket v1.5.0
github.com/lionsoul2014/ip2region v2.11.0+incompatible
github.com/rodaine/table v1.1.0
github.com/stretchr/testify v1.7.1 // indirect
github.com/tidwall/gjson v1.14.4
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
github.com/tidwall/pretty v1.2.1 // indirect
golang.org/x/sys v0.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

42
go.sum
View File

@@ -1,53 +1,67 @@
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/lionsoul2014/ip2region v2.11.0+incompatible h1:+L3q50qdDztfPfwKFukSWoOFTpbPhiYRXMmr2cYPPdM=
github.com/lionsoul2014/ip2region v2.11.0+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
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.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/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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/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/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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.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/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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-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-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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=

77
ipgeo/ip2region.go Normal file
View File

@@ -0,0 +1,77 @@
package ipgeo
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"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) (*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
}

View File

@@ -38,6 +38,6 @@ func IPApiCom(ip string) (*IPGeoData, error) {
Country: res.Get("country").String(),
City: res.Get("city").String(),
Prov: res.Get("regionName").String(),
Isp: res.Get("isp").String(),
Owner: res.Get("isp").String(),
}, nil
}

View File

@@ -5,13 +5,24 @@ import (
)
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)
@@ -26,7 +37,11 @@ func GetSource(s string) Source {
return IPInSight
case "IPAPI.COM":
return IPApiCom
case "IPINFO":
return IPInfo
case "IP2REGION":
return IP2Region
default:
return nil
return LeoIP
}
}

View File

@@ -1,52 +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) {
// 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 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 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)
}
// 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

@@ -8,7 +8,6 @@ import (
)
func IPInfo(ip string) (*IPGeoData, error) {
resp, err := http.Get("https://ipinfo.io/" + ip + "?token=" + token.ipinfo)
if err != nil {
return nil, err
@@ -21,9 +20,17 @@ func IPInfo(ip string) (*IPGeoData, error) {
res := gjson.ParseBytes(body)
var country string
if res.Get("country").String() == "HK" || res.Get("country").String() == "TW" {
country = "CN"
}
return &IPGeoData{
Country: res.Get("country").String(),
City: res.Get("city").String(),
Prov: res.Get("region").String(),
Asnumber: res.Get("asn").Get("asn").String(),
Country: country,
City: res.Get("city").String(),
Prov: res.Get("region").String(),
Owner: res.Get("asn").Get("domain").String(),
}, nil
}

View File

@@ -37,6 +37,6 @@ func IPSB(ip string) (*IPGeoData, error) {
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,108 @@
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 的下一步处理过程,并且回到空闲状态继续等待新的任务
***/
// 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 string = res.Get("domain").String()
if res.Get("domain").String() == "" {
domain = res.Get("owner").String()
}
m := make(map[string][]string)
json.Unmarshal([]byte(res.Get("router").String()), &m)
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) (*IPGeoData, error) {
// 初始化通道 - 向池子里添加IP的Channel返回IP数据是通过字典中对应键为IP的Channel来获取的
IPPools.poolMux.Lock()
defer IPPools.poolMux.Unlock()
// 如果之前已经被别的协程初始化过了就不用初始化了
if IPPools.pool[ip] == nil {
IPPools.pool[ip] = make(chan IPGeoData)
}
// 发送请求
sendIPRequest(ip)
// 同步开启监听
go receiveParse()
// 拥塞,等待数据返回
select {
case res := <-IPPools.pool[ip]:
return &res, nil
// 5秒后依旧没有接收到返回的IP数据不再等待超时异常处理
case <-time.After(5 * time.Second):
// default:
// 这里不可以返回一个 nil否则在访问对象内部的键值的时候会报空指针的 Fatal Error
return &IPGeoData{}, errors.New("TimeOut")
}
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
}

112
main.go
View File

@@ -1,117 +1,9 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"strings"
"time"
"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/util"
"github.com/xgadget-lab/nexttrace/cmd"
)
var fSet = flag.NewFlagSet("", flag.ExitOnError)
var tcpSYNFlag = fSet.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80)")
var udpPackageFlag = fSet.Bool("U", false, "Use UDP Package for tracerouting (default port is 53 in UDP)")
var port = flag.Int("p", 80, "Set SYN Traceroute Port")
var numMeasurements = fSet.Int("q", 3, "Set the number of probes per each hop.")
var parallelRequests = fSet.Int("r", 18, "Set ParallelRequests number. It should be 1 when there is a multi-routing.")
var maxHops = fSet.Int("m", 30, "Set the max number of hops (max TTL to be reached).")
var dataOrigin = fSet.String("d", "LeoMoeAPI", "Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight, IPAPI.com]")
var rdnsenable = fSet.Bool("rdns", false, "Set whether rDNS will be display")
var routePath = fSet.Bool("report", false, "Route Path")
var tablePrint = fSet.Bool("table", false, "Output trace results as table")
var ver = fSet.Bool("V", false, "Check Version")
func printArgHelp() {
fmt.Println("\nArgs Error\nUsage : 'nexttrace [option...] HOSTNAME' or 'nexttrace HOSTNAME [option...]'\nOPTIONS: [-VTU] [-d DATAORIGIN.STR ] [ -m TTL ] [ -p PORT ] [ -q PROBES.COUNT ] [ -r PARALLELREQUESTS.COUNT ] [-rdns] [ -realtime | -table ] -report")
fSet.PrintDefaults()
os.Exit(2)
}
func flagApply() string {
printer.Version()
target := ""
if len(os.Args) < 2 {
printArgHelp()
}
if !strings.HasPrefix(os.Args[1], "-") {
target = os.Args[1]
fSet.Parse(os.Args[2:])
} else {
fSet.Parse(os.Args[1:])
target = fSet.Arg(0)
}
if *ver {
os.Exit(0)
}
if target == "" {
printArgHelp()
}
return target
}
func main() {
domain := flagApply()
if os.Getuid() != 0 {
log.Fatalln("Traceroute requires root/sudo privileges.")
}
ip := util.DomainLookUp(domain)
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
var m trace.Method = ""
switch {
case *tcpSYNFlag:
m = trace.TCPTrace
case *udpPackageFlag:
m = trace.UDPTrace
default:
m = trace.ICMPTrace
}
if !*tcpSYNFlag && *port == 80 {
*port = 53
}
var conf = trace.Config{
DestIP: ip,
DestPort: *port,
MaxHops: *maxHops,
NumMeasurements: *numMeasurements,
ParallelRequests: *parallelRequests,
RDns: *rdnsenable,
IPGeoSource: ipgeo.GetSource(*dataOrigin),
Timeout: 2 * time.Second,
}
if m == trace.ICMPTrace && !*tablePrint {
conf.RealtimePrinter = printer.RealtimePrinter
}
res, err := trace.Traceroute(m, conf)
if err != nil {
log.Fatalln(err)
}
if !(*tcpSYNFlag || *udpPackageFlag) && *tablePrint {
printer.TracerouteTablePrinter(res)
} else if *tcpSYNFlag || *udpPackageFlag {
printer.TraceroutePrinter(res)
}
if *routePath {
r := reporter.New(res, ip.String())
r.Print()
}
cmd.Excute()
}

View File

@@ -1,265 +1,134 @@
#!/bin/bash
auto=False
#是否忽略一切警告,按默认执行
if [[ $1 == "--auto" ]]; then
auto=True
echo "自动运行中"
fi
usrPath="/usr/local/bin"
function red() {
echo -e "\033[31m"${1}"\033[0m"
}
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 ]] && red "请使用sudo/root权限运行本脚本" && exit 1
[[ $EUID -ne 0 ]] && echo -e "${Error} 请使用sudo/root权限运行本脚本" && exit 1
}
checkSystemArch() {
arch=$(uname -m)
case $arch in
'x86_64')
archParam='amd64'
;;
'mips')
archParam='mips'
;;
'arm64' | 'aarch64')
archParam="arm64"
;;
*)
red "未知的系统架构,请联系开发者."
exit 1
;;
esac
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
darwin*)
osDistribution="darwin"
downPath="/var/tmp/nexttrace"
;;
linux*)
osDistribution="linux"
downPath="/var/tmp/nexttrace"
;;
linux*)
osDistribution="linux"
if [ ! -d "/usr/local" ];
then
downPath="/usr/bin/nexttrace"
else
downPath="/usr/local/bin/nexttrace"
fi
;;
*)
red "unknown: $OSTYPE"
exit 1
;;
echo "unknown: $OSTYPE"
exit 1
;;
esac
}
ask_if() {
local choice=""
red $1
read choice
[[ $choice == y ]] && return 0 || return 1
}
#检查脚本更新
check_script_update() {
if [[ ${osDistribution} == "darwin" ]]; then
[ "$(cat "${BASH_SOURCE[0]}" | md5)" == "$(curl -sL "https://github.com/xgadget-lab/nexttrace/raw/main/nt_install.sh" | md5)" ] && return 1 || return 0
else
[ "$(md5sum "${BASH_SOURCE[0]}" | awk '{print $1}')" == "$(md5sum <(curl -sL "https://github.com/xgadget-lab/nexttrace/raw/main/nt_install.sh") | awk '{print $1}')" ] && return 1 || return 0
fi
}
#更新脚本
update_script() {
if curl -sL -o "${BASH_SOURCE[0]}" "https://github.com/xgadget-lab/nexttrace/raw/main/nt_install.sh" || curl -sL -o "${BASH_SOURCE[0]}" "https://github.com/xgadget-lab/nexttrace/raw/main/nt_install.sh"; then
red "脚本更新完成,正在重启脚本..."
exec bash ${BASH_SOURCE[0]} --auto
else
red "更新脚本失败!"
exit 1
fi
}
ask_update_script() {
if check_script_update; then
red "脚本可升级"
[[ $auto == True ]] && update_script
ask_if "是否升级脚本?(n/y)[n]" && update_script
else
red "脚本已经是最新版本"
fi
}
getLocation() {
red "正在获取地理位置信息..."
countryCode=$(curl -s "http://ip-api.com/line/?fields=countryCode")
}
checkPackageManger() {
if [[ "$(which brew)" ]]; then #务必将brew置于第一位,macOS的apt是假的
# brew update
PACKAGE_MANAGEMENT_INSTALL='brew install'
PACKAGE_MANAGEMENT_REMOVE='brew uninstall'
elif [[ "$(which apt)" ]]; then
apt-get update
PACKAGE_MANAGEMENT_INSTALL='apt-get -y --no-install-recommends install'
PACKAGE_MANAGEMENT_REMOVE='apt-get purge'
elif [[ "$(which dnf)" ]]; then
dnf check-update
PACKAGE_MANAGEMENT_INSTALL='dnf -y install'
PACKAGE_MANAGEMENT_REMOVE='dnf remove'
elif [[ "$(which yum)" ]]; then
PACKAGE_MANAGEMENT_INSTALL='yum -y install'
PACKAGE_MANAGEMENT_REMOVE='yum remove'
elif [[ "$(which zypper)" ]]; then
zypper refresh
PACKAGE_MANAGEMENT_INSTALL='zypper install -y --no-recommends'
PACKAGE_MANAGEMENT_REMOVE='zypper remove'
elif [[ "$(which pacman)" ]]; then
PACKAGE_MANAGEMENT_INSTALL='pacman -Syu --noconfirm'
PACKAGE_MANAGEMENT_REMOVE='pacman -Rsn'
else
red "error: The script does not support the package manager in this operating system."
exit 1
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
}
install_software() {
package_name="$1"
which "$package_name" >/dev/null 2>&1 && return
[[ ${osDistribution} == "darwin" ]] && echo -e "由于macOS brew的权限限制请以非root权限执行下面一行提示的命令后再次运行本脚本(注意不要在该命令加sudo!):\nbrew update && ${PACKAGE_MANAGEMENT_INSTALL} $package_name " && exit 0
red "${package_name} 正在安装中...(此步骤时间可能较长,请耐心等待)"
if ${PACKAGE_MANAGEMENT_INSTALL} "$package_name"; then
red "info: $package_name is installed."
else
red "error: Installation of $package_name failed, please check your network."
exit 1
fi
}
checkVersion() {
nexttrace -h &>/dev/null
checkWgetPackage() {
wget -h &> /dev/null
if [ $? -ne 0 ]; then
return 0
installWgetPackage
fi
red "正在检查版本..."
version=$(curl -sL https://api.github.com/repos/xgadget-lab/nexttrace/releases/latest | jq -r '.tag_name')
if [[ $version == "" ]]; then
red "获取版本失败,请检查网络连接"
exit 1
fi
currentVersion=$(nexttrace -V | head -n 1 | awk '{print $2}') &>/dev/null
if [[ $currentVersion == $version ]]; then
red "当前版本已是最新版本"
exit 0
fi
red 当前最新release版本${version}
red 您当前的版本:${currentVersion}
if [[ $auto == True ]]; then
return 0
fi
read -r -p "是否更新软件? (n/y)[n]" input
case $input in
[yY][eE][sS] | [yY])
return 0
;;
[nN][oO] | [nN])
red "您选择了取消更新,脚本即将退出"
exit 1
;;
*)
return 0
;;
esac
}
downloadBinrayFile() {
red "正在获取最新版的 NextTrace 发行版文件信息..."
echo -e "${Info} 获取最新版的 NextTrace 发行版文件信息"
# 简单说明一下Github提供了一个API可以获取最新发行版本的二进制文件下载地址对应的是browser_download_url根据刚刚测得的osDistribution、archParam获取对应的下载地址
# red nexttrace_${osDistribution}_${archParam}
latestURL=$(curl -s https://api.github.com/repos/xgadget-lab/nexttrace/releases/latest | jq ".assets[] | select(.name == \"nexttrace_${osDistribution}_${archParam}\") | .browser_download_url")
latestURL=${latestURL:1:$((${#latestURL} - 1 - 1))}
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
if [[ $auto == True ]]; then
latestURL="https://ghproxy.com/"$latestURL
else
read -r -p "检测到国内网络环境,是否使用镜像下载以加速(n/y)[y]" input
case $input in
[yY][eE][sS] | [yY])
latestURL="https://ghproxy.com/"$latestURL
;;
[nN][oO] | [nN])
red "您选择了不使用镜像,下载可能会变得异常缓慢,或者失败"
;;
*)
latestURL="https://ghproxy.com/"$latestURL
;;
esac
fi
echo -e "${Info} 检测到国内环境,正在使用镜像下载"
latestURL="https://ghproxy.com/"$latestURL
fi
red "正在下载 NextTrace 二进制文件..."
wget -O ${downPath} ${latestURL} &>/dev/null
if [ $? -eq 0 ]; then
red "NextTrace 现在已经在您的系统中可用"
changeMode
mv ${downPath} ${usrPath}
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
red "NextTrace 下载失败,请检查您的网络是否正常"
exit 1
echo -e "${Error} NextTrace 下载失败,请检查您的网络是否正常"
exit 1
fi
}
changeMode() {
chmod +x ${downPath} &>/dev/null
[[ ${osDistribution} == "darwin" ]] && xattr -r -d com.apple.quarantine ${downPath}
chmod +x ${Temp_path} &> /dev/null
}
runBinrayFileHelp() {
if [ -e ${usrPath} ]; then
${usrPath}/nexttrace -h
if [ -e ${downPath} ]; then
${downPath} --version
echo -e "${Tips} 一切准备就绪!使用命令 nexttrace 1.1.1.1 开始您的第一次路由测试吧~ 更多进阶命令玩法可以用 nexttrace -h 查看哦\n 关于软件卸载因为nexttrace是绿色版单文件卸载只需输入命令 rm ${downPath} 即可"
fi
red "You may need to execute a command to remove dependent software: $PACKAGE_MANAGEMENT_REMOVE wget jq"
}
addCronTask() {
read -r -p "是否添加自动更新任务?(n/y)[n]" input
case $input in
[yY][eE][sS] | [yY])
if [[ ${osDistribution} == "darwin" ]]; then
crontab -l >crontab.bak 2>/dev/null
sed -i '' '/nt_install.sh/d' crontab.bak
elif [[ ${osDistribution} == "linux" ]]; then
crontab -l >crontab.bak 2>/dev/null
sed -i '/nt_install.sh/d' crontab.bak
else
red "暂不支持您的系统,无法自动添加crontab任务"
return 0
fi
echo "1 1 * * * $(dirname $(readlink -f "$0"))/nt_install.sh --auto >> /var/log/nt_install.log" >>crontab.bak
crontab crontab.bak
rm -f crontab.bak
;;
[nN][oO] | [nN])
red "您选择了不添加自动更新任务,您也可以通过命令 再次执行此脚本 手动更新"
;;
*)
red "您选择了不添加自动更新任务,您可以通过命令 再次执行此脚本 手动更新"
;;
esac
}
# Check Procedure
checkRootPermit
checkSystemDistribution
checkSystemArch
ask_update_script
checkPackageManger
install_software wget
install_software jq
checkVersion
checkWgetPackage
# Download Procedure
getLocation
@@ -267,4 +136,3 @@ downloadBinrayFile
# Run Procedure
runBinrayFileHelp
[[ $auto != True ]] && addCronTask

View File

@@ -3,6 +3,8 @@ package printer
import (
"fmt"
"net"
"github.com/fatih/color"
)
var version = "v0.0.0.alpha"
@@ -10,8 +12,16 @@ var buildDate = ""
var commitID = ""
func Version() {
fmt.Println("NextTrace", version, buildDate, commitID)
fmt.Println("XGadget-lab Leo (leo.moe) & Vincent (vincent.moe) & zhshch (xzhsh.ch)")
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.Println("XGadget-lab Leo (leo.moe) & Tso (tsosunchia@gmail.com) & Vincent (vincent.moe) & zhshch (xzhsh.ch)")
}
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) {

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])
}
}

View File

@@ -9,18 +9,27 @@ import (
"github.com/xgadget-lab/nexttrace/ipgeo"
)
var dataOrigin string
// var dataOrigin string
func TraceroutePrinter(res *trace.Result) {
for i, hop := range res.Hops {
fmt.Print(i + 1)
for _, h := range hop {
HopPrinter(h)
}
}
}
// func TraceroutePrinter(res *trace.Result) {
// for i, hop := range res.Hops {
// fmt.Print(i + 1)
// for _, h := range hop {
// HopPrinter(h)
// }
// }
// }
func HopPrinter(h trace.Hop) {
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 {
@@ -35,8 +44,22 @@ func HopPrinter(h trace.Hop) {
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)
}
}
}
@@ -52,19 +75,16 @@ func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
// TODO: 移动IDC判断到Hop.fetchIPData函数减少API调用
if strings.HasPrefix(ip, "9.") {
res = append(res, "局域网", "腾讯云")
res = append(res, "LAN Address")
} else if strings.HasPrefix(ip, "11.") {
res = append(res, "局域网", "阿里云")
res = append(res, "LAN Address")
} else if data.Country == "" {
res = append(res, "局域网")
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

View File

@@ -1,104 +1,94 @@
package printer
import (
"errors"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/util"
"net"
"testing"
"time"
)
// func TestPrintTraceRouteNav(t *testing.T) {
// PrintTraceRouteNav(util.DomainLookUp("1.1.1.1", false), "1.1.1.1", "dataOrigin")
// }
func TestPrintTraceRouteNav(t *testing.T) {
PrintTraceRouteNav(util.DomainLookUp("1.1.1.1"), "1.1.1.1", "dataOrigin")
}
// var testGeo = &ipgeo.IPGeoData{
// Asnumber: "TestAsnumber",
// Country: "TestCountry",
// Prov: "TestProv",
// City: "TestCity",
// District: "TestDistrict",
// Owner: "TestOwner",
// Isp: "TestIsp",
// }
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,
// },
// },
// },
// }
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 TestTraceroutePrinter(t *testing.T) {
TraceroutePrinter(testResult)
}
// func TestTracerouteTablePrinter(t *testing.T) {
// TracerouteTablePrinter(testResult)
// }
func TestTracerouteTablePrinter(t *testing.T) {
TracerouteTablePrinter(testResult)
}
func TestRealtimePrinter(t *testing.T) {
RealtimePrinter(testResult, 0)
RealtimePrinter(testResult, 1)
RealtimePrinter(testResult, 2)
}
// func TestRealtimePrinter(t *testing.T) {
// RealtimePrinter(testResult, 0)
// // RealtimePrinter(testResult, 1)
// // RealtimePrinter(testResult, 2)
// }

View File

@@ -2,14 +2,153 @@ 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.Print(ttl)
for i := range res.Hops[ttl] {
HopPrinter(res.Hops[ttl][i])
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, 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 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 whoisFormat[0] == "[CNC-BACKBONE]":
fallthrough
case whoisFormat[0] == "[CUG-BACKBONE]":
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]))
}
}
if len(res.Hops[ttl][i].Geo.Country) <= 1 {
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]),
)
}
}
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),
)
}
}
}

View File

@@ -2,9 +2,10 @@ package printer
import (
"fmt"
"github.com/xgadget-lab/nexttrace/ipgeo"
"strings"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/fatih/color"
@@ -36,7 +37,9 @@ func TracerouteTablePrinter(res *trace.Result) {
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.Country+", "+data.Prov+", "+data.City, data.Owner)
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)
}
@@ -44,6 +47,7 @@ func TracerouteTablePrinter(res *trace.Result) {
}
}
}
fmt.Print("\033[H\033[2J")
// 打印表格
tbl.Print()
}
@@ -73,16 +77,18 @@ func tableDataGenerator(h trace.Hop) *rowData {
Hop: fmt.Sprint(h.TTL),
IP: IP,
Latency: lantency,
Country: "局域网",
Owner: "腾讯云",
Country: "LAN Address",
Prov: "",
Owner: "",
}
} else if strings.HasPrefix(IP, "11.") {
return &rowData{
Hop: fmt.Sprint(h.TTL),
IP: IP,
Latency: lantency,
Country: "局域网",
Owner: "阿里云",
Country: "LAN Address",
Prov: "",
Owner: "",
}
}
@@ -99,9 +105,9 @@ func tableDataGenerator(h trace.Hop) *rowData {
IP: IP,
Latency: lantency,
Asnumber: h.Geo.Asnumber,
Country: h.Geo.Country,
Prov: h.Geo.Prov,
City: h.Geo.City,
Country: h.Geo.CountryEn,
Prov: h.Geo.ProvEn,
City: h.Geo.CityEn,
District: h.Geo.District,
Owner: h.Geo.Owner,
}
@@ -114,9 +120,9 @@ func tableDataGenerator(h trace.Hop) *rowData {
h.Geo.Owner = h.Geo.Isp
}
r.Asnumber = h.Geo.Asnumber
r.Country = h.Geo.Country
r.Prov = h.Geo.Prov
r.City = h.Geo.City
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

View File

@@ -1,274 +0,0 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
Green_font="\033[32m" && Red_font="\033[31m" && Font_suffix="\033[0m"
Info="${Green_font}[Info]${Font_suffix}"
Error="${Red_font}[Error]${Font_suffix}"
echo -e "${Green_font}
#======================================
# Project: NextTrace https://github.com/xgadget-lab/nexttrace
# Copyright Notice:
# This script is ported from @KANIKIG https://github.com/KANIKIG/
# The developer team made some modifications to adapt to NextTrace under the GPL-3.0 LICENSE
# NextTrace:
# XGadget-lab Leo (leo.moe) & Vincent (vincent.moe) & zhshch (xzhsh.ch)
# IP Geo Data Provider: LeoMoeAPI
#======================================
${Font_suffix}"
check_root() {
[[ "$(id -u)" != "0" ]] && echo -e "${Error} must be root user !" && exit 1
}
checkNexttrace() {
echo -e "${Info} 正在检查Nexttrace..."
if curl -sL -O "https://github.com/xgadget-lab/nexttrace/raw/main/nt_install.sh" || curl -sL -O "https://github.com/xgadget-lab/nexttrace/raw/main/nt_install.sh"; then
bash nt_install.sh --auto >/dev/null
fi
}
ask_if() {
local choice=""
echo -e "${Info} $1"
read choice
[[ $choice == y ]] && return 0
return 1
}
checkSystemDistribution() {
case "$OSTYPE" in
darwin*)
osDistribution="darwin"
downPath="/var/tmp/nexttrace"
;;
linux*)
osDistribution="linux"
downPath="/var/tmp/nexttrace"
;;
*)
red "unknown: $OSTYPE"
exit 1
;;
esac
}
#检查脚本更新
check_script_update() {
if [[ ${osDistribution} == "darwin" ]]; then
[ "$(cat "${BASH_SOURCE[0]}" | md5)" == "$(curl -sL "https://github.com/xgadget-lab/nexttrace/raw/main/nt_install.sh" | md5)" ] && return 1 || return 0
else
[ "$(md5sum "${BASH_SOURCE[0]}" | awk '{print $1}')" == "$(md5sum <(curl -sL "https://github.com/xgadget-lab/nexttrace/raw/main/nt_install.sh") | awk '{print $1}')" ] && return 1 || return 0
fi
}
#更新脚本
update_script() {
if curl -sL -o "${BASH_SOURCE[0]}" "https://github.com/xgadget-lab/nexttrace/raw/main/quicklytest.sh" || curl -sL -o "${BASH_SOURCE[0]}" "https://github.com/xgadget-lab/nexttrace/raw/main/quicklytest.sh"; then
echo -e "${Info} 脚本更新完成,正在重启脚本..."
exec bash ${BASH_SOURCE[0]}
else
echo -e "${Info} 更新脚本失败!"
exit 1
fi
}
ask_update_script() {
if check_script_update; then
echo -e "${Info} 脚本可升级"
ask_if "是否升级脚本?(n/y)[n]" && update_script
else
echo -e "${Info} 脚本已经是最新版本"
fi
}
check_mode() {
echo -e "${Info} Nexttrace目前支持以下三种协议发起Traceroute请求:\n1.ICMP\n2.TCP(速度最快,但部分节点不支持)\n3.UDP\n(IPv6暂只支持ICMP模式)" && read -p "输入数字以选择:" node
while [[ ! "${node}" =~ ^[1-3]$ ]]; do
echo -e "${Error} 无效输入"
echo -e "${Info} 请重新选择" && read -p "输入数字以选择:" node
done
[[ "${node}" == "1" ]] && TRACECMD="nexttrace"
[[ "${node}" == "2" ]] && TRACECMD="nexttrace -T"
[[ "${node}" == "3" ]] && TRACECMD="nexttrace -U"
echo -e "${Info} 结果是否制表?(制表模式为非实时显示)"
if ask_if "输入n/y以选择模式:[n]"; then
TRACECMD=${TRACECMD}" -rdns -table"
##Route-Path功能还未完善,临时替代:
[[ "${node}" == "2" ]] && TRACECMD=${TRACECMD}" -report"
##
else
TRACECMD=${TRACECMD}" -rdns -realtime"
##Route-Path功能还未完善,临时替代:
[[ "${node}" == "1" ]] && TRACECMD=${TRACECMD}" -report"
[[ "${node}" == "2" ]] && TRACECMD=${TRACECMD}" -report"
##
fi
#echo -e "${Info} 是否输出Route-Path?"
#ask_if "输入n/y以选择模式:[n]" && TRACECMD=${TRACECMD}" -report"
}
test_single() {
echo -e "${Info} 请输入你要测试的目标 ip :"
read -p "输入 ip 地址:" ip
while [[ -z "${ip}" ]]; do
echo -e "${Error} 无效输入"
echo -e "${Info} 请重新输入" && read -p "输入 ip 地址:" ip
done
${TRACECMD} ${ip} | grep -v -E 'NextTrace|XGadget-lab|Data\ Provider'
repeat_test_single
}
repeat_test_single() {
echo -e "${Info} 是否继续测试其他目标 ip ?"
if ask_if "输入n/y以选择:[n]"; then
test_single
else
echo -e "${Info} 退出脚本 ..." && exit 0
fi
}
test_alternative() {
select_alternative
set_alternative
result_alternative
}
select_alternative() {
echo -e "${Info} 选择需要测速的目标网络: \n1.中国电信\n2.中国联通\n3.中国移动\n4.教育网"
read -p "输入数字以选择:" ISP
while [[ ! "${ISP}" =~ ^[1-4]$ ]]; do
echo -e "${Error} 无效输入"
echo -e "${Info} 请重新选择" && read -p "输入数字以选择:" ISP
done
}
set_alternative() {
[[ "${ISP}" == "1" ]] && node_1
[[ "${ISP}" == "2" ]] && node_2
[[ "${ISP}" == "3" ]] && node_3
[[ "${ISP}" == "4" ]] && node_4
}
node_1() {
echo -e "1.上海电信(天翼云)\n2.厦门电信CN2\n3.北京电信\n4.江苏电信\n5.广东深圳电信\n6.广州电信(天翼云)\n7.浙江电信" && read -p "输入数字以选择:" node
while [[ ! "${node}" =~ ^[1-7]$ ]]; do
echo -e "${Error} 无效输入"
echo -e "${Info} 请重新选择" && read -p "输入数字以选择:" node
done
[[ "${node}" == "1" ]] && ISP_name="上海电信" && ip=101.89.132.9
[[ "${node}" == "2" ]] && ISP_name="厦门电信CN2" && ip=117.28.254.129
[[ "${node}" == "3" ]] && ISP_name="北京电信" && ip=120.92.180.135
[[ "${node}" == "4" ]] && ISP_name="江苏电信" && ip=221.229.173.233
[[ "${node}" == "5" ]] && ISP_name="广东深圳电信" && ip=116.6.211.41
[[ "${node}" == "6" ]] && ISP_name="广州电信(天翼云)" && ip=14.215.116.1
[[ "${node}" == "7" ]] && ISP_name="浙江电信" && ip=115.236.169.86
}
node_2() {
echo -e "1.上海联通\n2.重庆联通\n3.北京联通\n4.安徽合肥联通\n5.江苏南京联通\n6.浙江杭州联通\n7.广东联通" && read -p "输入数字以选择:" node
while [[ ! "${node}" =~ ^[1-7]$ ]]; do
echo -e "${Error} 无效输入"
echo -e "${Info} 请重新选择" && read -p "输入数字以选择:" node
done
[[ "${node}" == "1" ]] && ISP_name="上海联通" && ip=220.196.252.174
[[ "${node}" == "2" ]] && ISP_name="重庆联通" && ip=113.207.32.65
[[ "${node}" == "3" ]] && ISP_name="北京联通" && ip=202.106.54.150
[[ "${node}" == "4" ]] && ISP_name="安徽合肥联通" && ip=112.122.10.26
[[ "${node}" == "5" ]] && ISP_name="江苏联通" && ip=112.85.231.129
[[ "${node}" == "6" ]] && ISP_name="浙江联通" && ip=60.12.214.156
[[ "${node}" == "7" ]] && ISP_name="广东联通" && ip=58.252.2.194
}
node_3() {
echo -e "1.上海移动\n2.四川成都移动\n3.北京移动\n4.浙江杭州移动\n5.广东移动\n6.江苏移动\n7.浙江移动" && read -p "输入数字以选择:" node
while [[ ! "${node}" =~ ^[1-7]$ ]]; do
echo -e "${Error} 无效输入"
echo -e "${Info} 请重新选择" && read -p "输入数字以选择:" node
done
[[ "${node}" == "1" ]] && ISP_name="上海移动" && ip=117.184.42.114
[[ "${node}" == "2" ]] && ISP_name="四川成都移动" && ip=183.221.247.9
[[ "${node}" == "3" ]] && ISP_name="北京移动" && ip=111.13.217.125
[[ "${node}" == "4" ]] && ISP_name="浙江移动" && ip=183.246.69.139
[[ "${node}" == "5" ]] && ISP_name="广东移动" && ip=221.179.44.57
[[ "${node}" == "6" ]] && ISP_name="江苏移动" && ip=120.195.6.129
[[ "${node}" == "7" ]] && ISP_name="浙江移动" && ip=183.246.69.139
}
node_4() {
ISP_name="北京教育网" && ip=211.68.69.240
}
result_alternative() {
echo -e "${Info} 测试路由 到 ${ISP_name} 中 ..."
${TRACECMD} ${ip} | grep -v -E 'NextTrace|XGadget-lab|Data\ Provider'
echo -e "${Info} 测试路由 到 ${ISP_name} 完成 "
repeat_test_alternative
}
repeat_test_alternative() {
echo -e "${Info} 是否继续测试其他节点?"
if ask_if "输入n/y以选择:[n]"; then
test_alternative
else
echo -e "${Info} 退出脚本 ..." && exit 0
fi
}
test_all() {
result_all '116.6.211.41' '广东东莞CN2'
result_all '101.95.110.149' '上海电信'
result_all '112.85.231.129' '江苏徐州联通'
result_all '120.199.239.1' '浙江杭州移动'
result_all '211.68.69.240' '北京教育网'
echo -e "${Info} 四网路由快速测试 已完成 "
}
result_all() {
ISP_name=$2
echo -e "${Info} 测试路由 到 ${ISP_name} 中 ..."
${TRACECMD} $1 | grep -v -E 'NextTrace|XGadget-lab|Data\ Provider'
echo -e "${Info} 测试路由 到 ${ISP_name} 完成 "
}
check_root
checkSystemDistribution
ask_update_script
checkNexttrace
check_mode
echo -e "${Info} 选择你要使用的功能: "
echo -e "1.选择一个节点进行测试\n2.四网路由快速测试\n3.手动输入 ip 进行测试"
read -p "输入数字以选择:" function
while [[ ! "${function}" =~ ^[1-3]$ ]]; do
echo -e "${Error} 缺少或无效输入"
echo -e "${Info} 请重新选择" && read -p "输入数字以选择:" function
done
if [[ "${function}" == "1" ]]; then
test_alternative
elif [[ "${function}" == "2" ]]; then
test_all
else
test_single
fi

View File

@@ -1,9 +1,13 @@
package trace
import (
"encoding/binary"
"errors"
"fmt"
"log"
"net"
"os"
"strconv"
"sync"
"time"
@@ -14,23 +18,49 @@ import (
type ICMPTracer struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
resCh chan Hop
icmpListen net.PacketConn
final int
finalLock sync.Mutex
wg sync.WaitGroup
res Result
ctx context.Context
inflightRequest map[int]chan Hop
inflightRequestLock sync.Mutex
icmpListen net.PacketConn
final int
finalLock 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.inflightRequest = make(map[int]chan Hop)
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
var err error
t.icmpListen, err = net.ListenPacket("ip4:1", "0.0.0.0")
t.icmpListen, err = net.ListenPacket("ip4:1", t.SrcAddr)
if err != nil {
return &t.res, err
}
@@ -39,27 +69,46 @@ func (t *ICMPTracer) Execute() (*Result, error) {
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.resCh = make(chan Hop)
t.final = -1
go t.listenICMP()
for ttl := 1; ttl <= t.MaxHops; ttl++ {
t.wg.Add(1)
go t.PrintFunc()
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
t.inflightRequestLock.Lock()
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
t.inflightRequestLock.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))
}
// 一组TTL全部退出收到应答或者超时终止以后再进行下一个TTL的包发送
t.wg.Wait()
<-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, ttl-1)
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)
}
}
t.res.reduce(t.final)
return &t.res, nil
}
@@ -74,42 +123,136 @@ func (t *ICMPTracer) listenICMP() {
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
// 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
}
switch rm.Type {
case ipv4.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
case ipv4.ICMPTypeEchoReply:
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
default:
// log.Println("received icmp message of unknown type", rm.Type)
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) {
t.resCh <- Hop{
Success: true,
Address: msg.Peer,
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
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: os.Getpid() & 0xffff,
ID: id,
Data: []byte("HELLO-R-U-THERE"),
Seq: ttl,
},
}
@@ -131,7 +274,7 @@ func (t *ICMPTracer) send(ttl int) error {
select {
case <-t.ctx.Done():
return nil
case h := <-t.resCh:
case h := <-t.inflightRequest[ttl]:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil
@@ -139,6 +282,7 @@ func (t *ICMPTracer) send(ttl int) error {
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()
@@ -156,7 +300,6 @@ func (t *ICMPTracer) send(ttl int) error {
h.fetchIPData(t.Config)
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && ttl > t.final {
return nil
@@ -169,6 +312,7 @@ func (t *ICMPTracer) send(ttl int) error {
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
return nil

View File

@@ -1,9 +1,11 @@
package trace
import (
"encoding/binary"
"log"
"net"
"os"
"strconv"
"sync"
"time"
@@ -14,23 +16,52 @@ import (
type ICMPTracerv6 struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
resCh chan Hop
icmpListen net.PacketConn
final int
finalLock sync.Mutex
wg sync.WaitGroup
res Result
ctx context.Context
resCh chan Hop
inflightRequest map[int]chan Hop
inflightRequestLock sync.Mutex
icmpListen net.PacketConn
final int
finalLock 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.inflightRequest = make(map[int]chan Hop)
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
var err error
t.icmpListen, err = net.ListenPacket("ip6:58", "::")
t.icmpListen, err = net.ListenPacket("ip6:58", t.SrcAddr)
if err != nil {
return &t.res, err
}
@@ -43,22 +74,59 @@ func (t *ICMPTracerv6) Execute() (*Result, error) {
t.final = -1
go t.listenICMP()
for ttl := 1; ttl <= t.MaxHops; ttl++ {
go t.PrintFunc()
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
t.inflightRequestLock.Lock()
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
t.inflightRequestLock.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))
}
// 一组TTL全部退出收到应答或者超时终止以后再进行下一个TTL的包发送
t.wg.Wait()
<-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, ttl-1)
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)
}
}
t.res.reduce(t.final)
return &t.res, nil
}
@@ -74,29 +142,98 @@ func (t *ICMPTracerv6) listenICMP() {
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
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)
}
}
}
// 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)
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) {
t.resCh <- Hop{
Success: true,
Address: msg.Peer,
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
if _, ok := t.inflightRequest[ttl]; ok {
t.inflightRequest[ttl] <- Hop{
Success: true,
Address: msg.Peer,
}
}
}
@@ -105,12 +242,14 @@ func (t *ICMPTracerv6) send(ttl int) error {
if t.final != -1 && ttl > t.final {
return nil
}
id := gernerateID(ttl)
icmpHeader := icmp.Message{
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff,
ID: id,
Data: []byte("HELLO-R-U-THERE"),
Seq: ttl,
},
}
@@ -135,7 +274,7 @@ func (t *ICMPTracerv6) send(ttl int) error {
select {
case <-t.ctx.Done():
return nil
case h := <-t.resCh:
case h := <-t.inflightRequest[ttl]:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil

View File

@@ -42,11 +42,16 @@ func (t *TCPTracer) Execute() (*Result, error) {
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
var err error
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcIP.String())
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", "0.0.0.0")
t.icmp, err = icmp.ListenPacket("ip4:icmp", t.SrcAddr)
if err != nil {
return &t.res, err
}
@@ -63,15 +68,38 @@ func (t *TCPTracer) Execute() (*Result, error) {
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
for ttl := 1; ttl <= t.MaxHops; ttl++ {
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)
}
if t.RealtimePrinter != nil {
// 对于实时模式应该按照TTL进行并发请求
t.wg.Wait()
t.RealtimePrinter(&t.res, ttl-1)
}
time.Sleep(1 * time.Millisecond)
}
t.wg.Wait()
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
@@ -88,18 +116,21 @@ func (t *TCPTracer) listenICMP() {
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)
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)
}
}
}
}

View File

@@ -39,11 +39,11 @@ func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
// TODO: 移动IDC判断到Hop.fetchIPData函数减少API调用
if strings.HasPrefix(ip, "9.") {
res = append(res, "局域网", "腾讯云")
res = append(res, "LAN Address", "")
} else if strings.HasPrefix(ip, "11.") {
res = append(res, "局域网", "阿里云")
res = append(res, "LAN Address", "")
} else if data.Country == "" {
res = append(res, "局域网")
res = append(res, "LAN Address")
} else {
// 有些IP的归属信息为空这个时候将ISP的信息填入
if data.Owner == "" {

View File

@@ -16,6 +16,8 @@ var (
)
type Config struct {
SrcAddr string
BeginHop int
MaxHops int
NumMeasurements int
ParallelRequests int
@@ -25,7 +27,10 @@ type Config struct {
Quic bool
IPGeoSource ipgeo.Source
RDns bool
PacketInterval int
TTLInterval int
RealtimePrinter func(res *Result, ttl int)
AsyncPrinter func(res *Result)
}
type Method string
@@ -113,10 +118,25 @@ type Hop struct {
}
func (h *Hop) fetchIPData(c Config) (err error) {
timeout := time.Millisecond * 800
if c.RDns && h.Hostname == "" {
ptr, err := net.LookupAddr(h.Address.String())
if err == nil && len(ptr) > 0 {
h.Hostname = ptr[0]
result := make(chan []string)
go func() {
r, err := net.LookupAddr(h.Address.String())
if err != nil {
result <- nil
} else {
result <- r
}
}()
select {
case ptr := <-result:
// process result
if err == nil && len(ptr) > 0 {
h.Hostname = ptr[0]
}
case <-time.After(timeout):
// handle timeout
}
}
if c.IPGeoSource != nil && h.Geo == nil {

View File

@@ -37,7 +37,7 @@ func (t *UDPTracer) Execute() (*Result, error) {
}
var err error
t.icmp, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
t.icmp, err = icmp.ListenPacket("ip4:icmp", t.SrcAddr)
if err != nil {
return &t.res, err
}
@@ -53,13 +53,34 @@ func (t *UDPTracer) Execute() (*Result, error) {
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)
}
}
t.wg.Wait()
}
if t.RealtimePrinter != nil {
// 对于实时模式应该按照TTL进行并发请求
t.wg.Wait()
t.RealtimePrinter(&t.res, ttl-1)
}
time.Sleep(1 * time.Millisecond)
}
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
@@ -87,7 +108,7 @@ func (t *UDPTracer) listenICMP() {
case ipv4.ICMPTypeDestinationUnreachable:
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
default:
log.Println("received icmp message of unknown type", rm.Type)
// log.Println("received icmp message of unknown type", rm.Type)
}
}
}

111
tracelog/log.go Normal file
View File

@@ -0,0 +1,111 @@
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 f.Close()
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
}
}

20
tracemap/tracemap.go Normal file
View File

@@ -0,0 +1,20 @@
package tracemap
import (
"fmt"
"io"
"net/http"
"strings"
"github.com/fatih/color"
)
func GetMapUrl(r string) {
url := "https://api.leo.moe/tracemap/api"
resp, _ := http.Post(url, "application/json", strings.NewReader(string(r)))
body, _ := io.ReadAll(resp.Body)
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(body)),
)
}

View File

@@ -5,6 +5,8 @@ import (
"log"
"net"
"os"
"github.com/fatih/color"
)
// get the local ip and port based on our destination ip
@@ -25,7 +27,7 @@ func LocalIPPort(dstip net.IP) (net.IP, int) {
return nil, -1
}
func DomainLookUp(host string) net.IP {
func DomainLookUp(host string, ipv4Only bool) net.IP {
ips, err := net.LookupIP(host)
if err != nil {
fmt.Println("Domain " + host + " Lookup Fail.")
@@ -36,17 +38,20 @@ func DomainLookUp(host string) net.IP {
var ipv6Flag = false
for _, ip := range ips {
ipSlice = append(ipSlice, ip)
// 仅返回ipv4的ip
// if ip.To4() != nil {
// ipSlice = append(ipSlice, ip)
// } else {
// ipv6Flag = true
// }
if ipv4Only {
// 仅返回ipv4的ip
if ip.To4() != nil {
ipSlice = append(ipSlice, ip)
} else {
ipv6Flag = true
}
} else {
ipSlice = append(ipSlice, ip)
}
}
if ipv6Flag {
fmt.Println("[Info] IPv6 Traceroute is not supported right now.")
fmt.Println("[Info] IPv6 TCP/UDP Traceroute is not supported right now.")
if len(ipSlice) == 0 {
os.Exit(0)
}
@@ -57,7 +62,10 @@ func DomainLookUp(host string) net.IP {
} else {
fmt.Println("Please Choose the IP You Want To TraceRoute")
for i, ip := range ipSlice {
fmt.Printf("%d. %s\n", i, ip)
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: ")

177
wshandle/client.go Normal file
View File

@@ -0,0 +1,177 @@
package wshandle
import (
"log"
"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 hostP = GetenvDefault("NEXTTRACE_HOSTPORT", "api.leo.moe")
func (c *WsConn) keepAlive() {
go func() {
// 开启一个定时器
for {
<-time.After(time.Second * 54)
if c.Connected {
c.Conn.WriteMessage(websocket.TextMessage, []byte("ping"))
}
}
}()
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("发送协程已经退出")
return
case t := <-c.MsgSendCh:
// log.Println(t)
if !c.Connected {
c.MsgReceiveCh <- `{"ip":"` + t + `", "asnumber":"API服务端异常"}`
} 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: hostP, Path: "/v2/ipGeoWs"}
// log.Printf("connecting to %s", u.String())
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
c.Conn = ws
if err != nil {
log.Println("dial:", err)
// <-time.After(time.Second * 1)
c.Connected = false
c.Connecting = false
return
} else {
c.Connected = true
}
c.Connecting = false
c.Done = make(chan struct{})
go c.messageReceiveHandler()
}
func createWsConn() *WsConn {
// 设置终端中断通道
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u := url.URL{Scheme: "wss", Host: hostP, Path: "/v2/ipGeoWs"}
// log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
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
}
func GetenvDefault(key, defVal string) string {
val, ok := os.LookupEnv(key)
if ok {
return val
}
return defVal
}