mirror of
https://github.com/nxtrace/NTrace-core.git
synced 2025-08-12 06:26:39 +00:00
Compare commits
252 Commits
v0.1.1-alp
...
v0.1.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6a154deb2 | ||
|
|
e4cacc569e | ||
|
|
4035dd7183 | ||
|
|
1789448d6c | ||
|
|
9df6c2f23c | ||
|
|
a37f31922c | ||
|
|
39917bb732 | ||
|
|
494f2ac819 | ||
|
|
0b09addd17 | ||
|
|
99dffc959c | ||
|
|
c30bcfee11 | ||
|
|
1554565460 | ||
|
|
2a069d7afe | ||
|
|
e6db19f5fd | ||
|
|
5603317fa3 | ||
|
|
3e71926127 | ||
|
|
e8f74c4ad3 | ||
|
|
aad80205c3 | ||
|
|
50cc9858d4 | ||
|
|
27f49f9cd0 | ||
|
|
e4320da08d | ||
|
|
7f16a27580 | ||
|
|
7db77024a3 | ||
|
|
c4ea506c35 | ||
|
|
39e2471845 | ||
|
|
18a9eefec9 | ||
|
|
1b743d1f17 | ||
|
|
3f0b14341a | ||
|
|
2ffd7fdb58 | ||
|
|
8f77050fca | ||
|
|
65652bd4e2 | ||
|
|
4176407a8a | ||
|
|
6bc4abeaf8 | ||
|
|
af0d886a02 | ||
|
|
09fdd2ac37 | ||
|
|
2b9d8176d4 | ||
|
|
ed2f89310f | ||
|
|
bd5a8902d4 | ||
|
|
e2a1bfe8cf | ||
|
|
356b782b3d | ||
|
|
40922ae13a | ||
|
|
8e90795a54 | ||
|
|
bbbb2377e1 | ||
|
|
efdfd9d612 | ||
|
|
016f06bafd | ||
|
|
1049986ebc | ||
|
|
80a75288d2 | ||
|
|
986b8ce300 | ||
|
|
ab774406ac | ||
|
|
9604b7befe | ||
|
|
dc6537005a | ||
|
|
839d227770 | ||
|
|
84e989e71b | ||
|
|
3b74b302cc | ||
|
|
9b0e58359f | ||
|
|
f0d7151144 | ||
|
|
3c65c29eff | ||
|
|
3aa4696fa9 | ||
|
|
b4abaffc7c | ||
|
|
c96fb4efa3 | ||
|
|
876de6bde1 | ||
|
|
7dbec0c7a1 | ||
|
|
d690f680f5 | ||
|
|
e96686013d | ||
|
|
6726b55fc9 | ||
|
|
9f29c75491 | ||
|
|
2f5bf3f195 | ||
|
|
5981e82ee3 | ||
|
|
80f7857a65 | ||
|
|
700d38de1c | ||
|
|
2dbf3f04a4 | ||
|
|
45df06ea80 | ||
|
|
98953048ce | ||
|
|
eddd4226b0 | ||
|
|
5afd9eb09e | ||
|
|
70006aaa13 | ||
|
|
497fb647c0 | ||
|
|
be8552c3cd | ||
|
|
f320fd6202 | ||
|
|
a42c5e3734 | ||
|
|
e6480c84e0 | ||
|
|
126115c04e | ||
|
|
ea7fd2af0f | ||
|
|
3fc81f4e71 | ||
|
|
0c2b77bd81 | ||
|
|
ac33c086c6 | ||
|
|
fadfdc87d4 | ||
|
|
eb77a2b69b | ||
|
|
02e6c6e1bf | ||
|
|
838af3b7a1 | ||
|
|
7cd16036a6 | ||
|
|
2ef4f61d7b | ||
|
|
688622738f | ||
|
|
83fe583f2a | ||
|
|
4b32594c17 | ||
|
|
927b6d4035 | ||
|
|
aa651f30cc | ||
|
|
ffec9a93cd | ||
|
|
59a744b3b5 | ||
|
|
9656dfe172 | ||
|
|
84c48dae99 | ||
|
|
4eaac372f6 | ||
|
|
c92d8a5172 | ||
|
|
acab410d4c | ||
|
|
858555fd86 | ||
|
|
31e419b199 | ||
|
|
1dddd43e67 | ||
|
|
4148d0d4b1 | ||
|
|
4f7977da8f | ||
|
|
cfc8034cb4 | ||
|
|
dbc0f87847 | ||
|
|
74a320898f | ||
|
|
329b3fdd6b | ||
|
|
3fb88f4cf4 | ||
|
|
1de84cac71 | ||
|
|
83d093f5aa | ||
|
|
8b03ca7a38 | ||
|
|
7a847bf0d5 | ||
|
|
11fe41611c | ||
|
|
71b24fb7a0 | ||
|
|
0b08e4b4a4 | ||
|
|
2608c05da1 | ||
|
|
314bdd0cce | ||
|
|
ea958059c6 | ||
|
|
b59c349264 | ||
|
|
030a487526 | ||
|
|
cac6d33fde | ||
|
|
1725a65827 | ||
|
|
4d886066a3 | ||
|
|
156043730d | ||
|
|
91ad3bc539 | ||
|
|
351da5f5a3 | ||
|
|
f8fc90d7a5 | ||
|
|
9c75635acc | ||
|
|
b20b27fd20 | ||
|
|
cfc1dfdfe5 | ||
|
|
97c4387af4 | ||
|
|
37b5202126 | ||
|
|
afb6a3e1df | ||
|
|
c9a3916cd0 | ||
|
|
89d56c437e | ||
|
|
6299dcd9a3 | ||
|
|
82f28a13f3 | ||
|
|
af732bc212 | ||
|
|
8d5f58bf15 | ||
|
|
8bd5654474 | ||
|
|
4de61823ee | ||
|
|
39ec016d0d | ||
|
|
67999411af | ||
|
|
7cc6b71727 | ||
|
|
16ba835537 | ||
|
|
1b7c3b8d0d | ||
|
|
bd47935a2d | ||
|
|
1f16001e4f | ||
|
|
f56e6cdba3 | ||
|
|
ecd3df8ee8 | ||
|
|
1658da1653 | ||
|
|
5110c9b990 | ||
|
|
aa446574f1 | ||
|
|
2016990629 | ||
|
|
e639b7b12d | ||
|
|
3cfe6598dd | ||
|
|
59193cae47 | ||
|
|
1def15e805 | ||
|
|
1950032371 | ||
|
|
f81a0b3da3 | ||
|
|
9e3d4186a1 | ||
|
|
4737669436 | ||
|
|
dbecfd880d | ||
|
|
a67a4bc559 | ||
|
|
077d72d5cd | ||
|
|
d45b5eb032 | ||
|
|
5fe1110ab3 | ||
|
|
812c953976 | ||
|
|
f89505ab87 | ||
|
|
640eb8c02d | ||
|
|
fc3462ff9e | ||
|
|
071a6b124a | ||
|
|
45a8cf21f6 | ||
|
|
1315efa4d2 | ||
|
|
c2bd51faab | ||
|
|
f1ce2bbb77 | ||
|
|
a18bf1889b | ||
|
|
12c93de8c5 | ||
|
|
8280b62881 | ||
|
|
608847b1cb | ||
|
|
b4838ba402 | ||
|
|
fbd70a8eb1 | ||
|
|
fd1632fccb | ||
|
|
4a725d2c48 | ||
|
|
96bb323f72 | ||
|
|
961c29e499 | ||
|
|
870d1f3b5a | ||
|
|
ebd435db53 | ||
|
|
53b2249ce5 | ||
|
|
7215a1e2b7 | ||
|
|
24b06d2fd7 | ||
|
|
f1f95dff29 | ||
|
|
0d9b8c8861 | ||
|
|
290524b502 | ||
|
|
905ef267f2 | ||
|
|
9720c19153 | ||
|
|
f2c494441b | ||
|
|
44aba64505 | ||
|
|
c0be6774c1 | ||
|
|
23693895e4 | ||
|
|
7edebf938b | ||
|
|
d4a176f864 | ||
|
|
69388c956e | ||
|
|
738ff949b1 | ||
|
|
b59f0eb9da | ||
|
|
c7df49ca8e | ||
|
|
4d3a52bb28 | ||
|
|
fbf7335bc8 | ||
|
|
24eedfa3fe | ||
|
|
d60c80e1ff | ||
|
|
4f622b7afb | ||
|
|
f51b07d388 | ||
|
|
679dbbf01a | ||
|
|
2a5e5572b4 | ||
|
|
974655a1b3 | ||
|
|
eb9844f924 | ||
|
|
b8a51b3c44 | ||
|
|
5d984fc3ac | ||
|
|
bcea9aa2cb | ||
|
|
ffc2c33e63 | ||
|
|
919335133e | ||
|
|
4d0e3eb93d | ||
|
|
56d2f0554f | ||
|
|
e728b6b063 | ||
|
|
8b2d2f3990 | ||
|
|
4c51b2fbbe | ||
|
|
9a6586f27a | ||
|
|
cc8e3e4838 | ||
|
|
d69b7b9acb | ||
|
|
483a90848d | ||
|
|
131a9e2e8a | ||
|
|
982e1064c2 | ||
|
|
5ff461af42 | ||
|
|
8adc98a753 | ||
|
|
937113ca33 | ||
|
|
9f0c62506e | ||
|
|
cad5f944cb | ||
|
|
0f0fb91fb6 | ||
|
|
e1c6f1ccf6 | ||
|
|
5ac811bbae | ||
|
|
dbd8ae573c | ||
|
|
8db4c5e7b8 | ||
|
|
7db2a717a4 | ||
|
|
b0ba116c91 | ||
|
|
5f993961ed | ||
|
|
ead46decf6 |
@@ -5,7 +5,11 @@ 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 openbsd/amd64 openbsd/arm64 freebsd/amd64 freebsd/arm64"
|
||||
|
||||
BUILD_VERSION="$(git describe --tags --always)"
|
||||
BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
COMMIT_SHA1="$(git rev-parse --short HEAD)"
|
||||
|
||||
rm -rf ${TARGET_DIR}
|
||||
mkdir ${TARGET_DIR}
|
||||
@@ -21,16 +25,36 @@ for pl in ${PLATFORMS}; do
|
||||
echo "build => ${TARGET}"
|
||||
if [ "${DEBUG_MODE}" == "debug" ]; then
|
||||
go build -trimpath -gcflags "all=-N -l" -o ${TARGET} \
|
||||
-ldflags "-X 'main.version=${BUILD_VERSION}' \
|
||||
-X 'main.buildDate=${BUILD_DATE}' \
|
||||
-X 'main.commitID=${COMMIT_SHA1}'\
|
||||
-ldflags "-X 'github.com/xgadget-lab/nexttrace/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 'main.version=${BUILD_VERSION}' \
|
||||
-X 'main.buildDate=${BUILD_DATE}' \
|
||||
-X 'main.commitID=${COMMIT_SHA1}'\
|
||||
-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
|
||||
done
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
name: nexttrace 程序问题
|
||||
about: "提交一个 nexttrace 的程序问题报告。"
|
||||
copyright: [v2fly](https://github.com/v2fly)
|
||||
---
|
||||
|
||||
<!--
|
||||
除非特殊情况,请完整填写所有问题。不按模板发的 issue 将直接被关闭。
|
||||
如果你遇到的问题不是 nexttrace 的 bug,比如你不清楚如何配置,请在 https://github.com/xgadget-lab/nexttrace/discussions 进行讨论。
|
||||
-->
|
||||
|
||||
## 你正在使用哪个版本的 nexttrace?
|
||||
|
||||
<!-- 比如linux_amd64 macOS_arm64 -->
|
||||
|
||||
|
||||
## 你看到的异常现象是什么?
|
||||
|
||||
<!-- 请描述具体现象 -->
|
||||
|
||||
|
||||
## 你期待看到的正常表现是怎样的?
|
||||
|
||||
|
||||
|
||||
## 请附上你的命令
|
||||
|
||||
<!-- 提交 issue 前,请隐去您的隐私信息 -->
|
||||
|
||||
|
||||
## 请附上出错时软件输出的错误信息
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
36
.github/workflows/build.yml
vendored
36
.github/workflows/build.yml
vendored
@@ -1,17 +1,31 @@
|
||||
on:
|
||||
push: # 每次带有 tag 的 push 候触发
|
||||
tags:
|
||||
- 'v*'
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
name: Build Release
|
||||
name: Test & Build Release
|
||||
jobs:
|
||||
release:
|
||||
Test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master # checkout 代码
|
||||
- uses: actions/setup-go@v2 # 配置 Go 环境
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: "1.18" # 改成自己的版本
|
||||
go-version: "1.18"
|
||||
|
||||
- name: Test
|
||||
run: go test -v -coverprofile='coverage.out' -covermode=count ./...
|
||||
|
||||
Build:
|
||||
needs: test
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: "1.18"
|
||||
|
||||
- run: bash .cross_compile.sh
|
||||
|
||||
@@ -22,8 +36,14 @@ 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_openbsd_amd64
|
||||
dist/nexttrace_openbsd_arm64
|
||||
dist/nexttrace_freebsd_amd64
|
||||
dist/nexttrace_freebsd_arm64
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GT_Token }}
|
||||
|
||||
209
README.md
209
README.md
@@ -1,18 +1,134 @@
|
||||
# NextTrace
|
||||
<div align="center">
|
||||
|
||||
可视化路由跟踪工具
|
||||
<img src="asset/logo.png" height="200px" alt="NextTrace Logo"/>
|
||||
|
||||
</div>
|
||||
|
||||
# NextTrace Lite
|
||||
|
||||
一款追求轻量的开源可视化路由跟踪工具,使用 Golang 开发。
|
||||
|
||||
NextTrace 一共有2个版本,专注于轻量的 Lite 版本以及更面向发烧友的 [Enhanced 版本](#nexttrace-enhanced)。
|
||||
|
||||
## How To Use
|
||||
|
||||
### Automated Install
|
||||
|
||||
```bash
|
||||
# Linux 一键安装脚本
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/xgadget-lab/nexttrace/main/nt_install.sh)
|
||||
|
||||
# GHPROXY 镜像(国内使用)
|
||||
bash <(curl -Ls https://ghproxy.com/https://raw.githubusercontent.com/xgadget-lab/nexttrace/main/nt_install.sh)
|
||||
|
||||
# macOS brew 安装命令
|
||||
brew tap xgadget-lab/nexttrace && brew install nexttrace
|
||||
```
|
||||
|
||||
- `Release`里面为很多系统以及不同架构提供了编译好的二进制可执行文件,如果没有可以自行编译。
|
||||
- 一些本项目的必要依赖在`Windows`上`Golang`底层实现不完全,所以目前`NextTrace`在`Windows`平台不可用。
|
||||
|
||||
### Get Started
|
||||
|
||||
`NextTrace` 默认使用`ICMP`协议发起`TraceRoute`请求,该协议同时支持`IPv4`和`IPv6`
|
||||
|
||||
```bash
|
||||
# IPv4 ICMP Trace
|
||||
nexttrace 1.0.0.1
|
||||
|
||||
# 表格打印(一次性输出全部跳数,需等待20-40秒)
|
||||
nexttrace -table 1.0.0.1
|
||||
|
||||
# IPv6 ICMP Trace
|
||||
nexttrace 2606:4700:4700::1111
|
||||
```
|
||||
|
||||
`NextTrace` 现已经支持快速测试,有一次性测试回程路由需求的朋友可以使用
|
||||
```bash
|
||||
# 北上广(电信+联通+移动+教育网)IPv4 ICMP 快速测试
|
||||
nexttrace -f
|
||||
|
||||
# 也可以使用 TCP SYN 而非 ICMP 进行测试
|
||||
nexttrace -f -T
|
||||
```
|
||||
|
||||
`NextTrace` 也可以使用`TCP`和`UDP`协议发起`Traceroute`请求,不过目前只支持`IPv4`
|
||||
|
||||
```bash
|
||||
# TCP SYN Trace
|
||||
nexttrace -T www.bing.com
|
||||
|
||||
# 可以自行指定端口[此处为443],默认80端口
|
||||
nexttrace -T -p 443 1.0.0.1
|
||||
|
||||
# UDP Trace
|
||||
nexttrace -U 1.0.0.1
|
||||
|
||||
nexttrace -U -p 53 1.0.0.1
|
||||
```
|
||||
|
||||
`NextTrace`也同样支持一些进阶功能,如 IP 反向解析、并发数控制、模式切换等
|
||||
|
||||
```bash
|
||||
# 每一跳发送2个探测包
|
||||
nexttrace -q 2 www.hkix.net
|
||||
|
||||
# 无并发,每次只发送一个探测包
|
||||
nexttrace -r 1 www.hkix.net
|
||||
|
||||
# 打开IP反向解析功能,在IPv6的骨干网定位辅助有较大帮助
|
||||
nexttrace -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 -report www.time.com.my
|
||||
```
|
||||
|
||||
`NextTrace`支持用户自主选择 IP 数据库(目前支持:`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限制调用较为严格,如有查询不到的情况,请几分钟后再试。
|
||||
```
|
||||
|
||||
`NextTrace`支持参数混合使用
|
||||
|
||||
```bash
|
||||
Example:
|
||||
nexttrace -d IPInsight -m 20 -p 443 -q 5 -r 20 -rdns 1.1.1.1
|
||||
nexttrace -T -q 2 -r 1 -rdns -table -report 2001:4860:4860::8888
|
||||
```
|
||||
|
||||
### IP 数据库
|
||||
|
||||
目前使用的 IP 数据库默认为我们自己搭建的 API 服务,如果后期遇到滥用,我们可能会选择关闭。
|
||||
|
||||
我们也会在后期开放服务端源代码,您也可以根据该项目的源码自行搭建属于您的 API 服务器。
|
||||
|
||||
NextTrace 所有的的 IP 地理位置`API DEMO`可以参考[这里](https://github.com/xgadget-lab/nexttrace/blob/main/ipgeo/)
|
||||
|
||||
### 全部用法详见 Usage 菜单
|
||||
|
||||
```shell
|
||||
NextTrace v0.1.0 Alpha
|
||||
xgadget-lab zhshch (xzhsh.ch) & leo (leo.moe)
|
||||
Usage of nexttrace:
|
||||
-T Use TCP SYN for tracerouting (default port is 80 in TCP, 53 in UDP)
|
||||
'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] (default "LeoMoeAPI")
|
||||
-displayMode string
|
||||
Choose The Display Mode [table, classic] (default "table")
|
||||
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
|
||||
@@ -23,11 +139,84 @@ Usage of nexttrace:
|
||||
Set ParallelRequests number. It should be 1 when there is a multi-routing. (default 18)
|
||||
-rdns
|
||||
Set whether rDNS will be display
|
||||
-table
|
||||
Output trace results as table
|
||||
-report
|
||||
Auto-Generate a Route-Path Report by Traceroute
|
||||
Route Path
|
||||
|
||||
```
|
||||
|
||||
## 项目截图
|
||||
|
||||
<div align="center">
|
||||
|
||||
<img src=asset/screenshot.png alt="NextTrace Screenshot" height="688" />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## NextTrace Enhanced
|
||||
|
||||
`NextTrace Enhanced` 是面向发烧友的增强版,`Enhanced`提供Web API形式的路由跟踪调用,以及一个简单的自带可视化的Looking Glass网页。
|
||||
|
||||
`Enhanced` 版本支持很多`lite`版本没有的功能,如能够自定义设置超时时间,也能指定TTL作为起点进行路由跟踪等,对于普通用户来说,通常`lite`版本已经足够完成大部分需要。
|
||||
|
||||
https://github.com/OwO-Network/nexttrace-enhanced
|
||||
|
||||
## FAQ 常见问题
|
||||
|
||||
如果你在安装或者使用的时候遇到了问题,我们建议你不要把新建一个 `issue` 作为首选项
|
||||
|
||||
以下是我们推荐的排错流程:
|
||||
|
||||
1. 查看是否为常见问题 -> [前往 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. 疑似 BUG、或者功能建议 -> [前往 Github Issues](https://github.com/xgadget-lab/nexttrace/issues)
|
||||
|
||||
## Thanks
|
||||
|
||||
Vincent Young (i@yyt.moe)
|
||||
[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)
|
||||
|
||||
## IP Database Copyright
|
||||
|
||||
### IPv4 Database
|
||||
|
||||
#### China
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :------------: | :----: | :-------: | :--: |
|
||||
| 电信/联通/移动 | 骨干网 | NextTrace | 10% |
|
||||
| 电信/联通/移动 | 城域网 | 埃文科技 | 90% |
|
||||
|
||||
#### WorldWide
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :-----: | :----: | :-------: | :--: |
|
||||
| Tier-01 | 骨干网 | IPInfo | 2% |
|
||||
| Tier-01 | 骨干网 | 埃文科技 | 3% |
|
||||
| Tier-01 | 骨干网 | IPInSight | 5% |
|
||||
| Tier-01 | 城域网 | IPInSight | 90% |
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :----: | :----: | :-------: | :--: |
|
||||
| Others | 骨干网 | IPInSight | 5% |
|
||||
| Others | 城域网 | IPInSight | 95% |
|
||||
|
||||
### IPv6 Database
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :-: | :--: | :--------------: | :--: |
|
||||
| All | 全部 | IP2Location Lite | 100% |
|
||||
|
||||
This product includes IP2Location LITE data available from <a href="https://lite.ip2location.com">https://lite.ip2location.com</a>.
|
||||
|
||||
### Others
|
||||
|
||||
其他第三方 API 尽管集成在本项目内,但是具体的 TOS 以及 AUP,请详见第三方 API 官网。如遇到 IP 数据错误,也请直接联系他们纠错。
|
||||
|
||||
BIN
asset/logo.png
Normal file
BIN
asset/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
asset/screenshot.png
Normal file
BIN
asset/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
asset/screenshot_2.png
Normal file
BIN
asset/screenshot_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
asset/screenshot_special.png
Normal file
BIN
asset/screenshot_special.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
152
fast_trace/basic.go
Normal file
152
fast_trace/basic.go
Normal file
@@ -0,0 +1,152 @@
|
||||
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
|
||||
}
|
||||
|
||||
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",
|
||||
},
|
||||
|
||||
CU169: ISPCollection{
|
||||
ISPName: CU169,
|
||||
IP: "123.125.96.156",
|
||||
},
|
||||
|
||||
CM: ISPCollection{
|
||||
ISPName: CM,
|
||||
IP: "211.136.25.153",
|
||||
},
|
||||
|
||||
EDU: ISPCollection{
|
||||
ISPName: EDU,
|
||||
IP: "101.6.15.130",
|
||||
},
|
||||
}
|
||||
|
||||
var Shanghai = BackBoneCollection{
|
||||
Location: "上海",
|
||||
CT163: ISPCollection{
|
||||
ISPName: CT163,
|
||||
IP: "101.226.28.198",
|
||||
},
|
||||
|
||||
CTCN2: ISPCollection{
|
||||
ISPName: CTCN2,
|
||||
IP: "58.32.4.1",
|
||||
},
|
||||
|
||||
CU169: ISPCollection{
|
||||
ISPName: CU169,
|
||||
IP: "139.226.206.150",
|
||||
},
|
||||
|
||||
CU9929: ISPCollection{
|
||||
ISPName: CU9929,
|
||||
IP: "210.13.86.1",
|
||||
},
|
||||
|
||||
CM: ISPCollection{
|
||||
ISPName: CM,
|
||||
IP: "120.204.34.85",
|
||||
},
|
||||
|
||||
EDU: ISPCollection{
|
||||
ISPName: EDU,
|
||||
IP: "202.120.58.155",
|
||||
},
|
||||
}
|
||||
|
||||
var Guangzhou = BackBoneCollection{
|
||||
Location: "广州",
|
||||
CT163: ISPCollection{
|
||||
ISPName: CT163,
|
||||
IP: "106.37.67.1",
|
||||
},
|
||||
|
||||
CU169: ISPCollection{
|
||||
ISPName: CU169,
|
||||
IP: "157.18.0.22",
|
||||
},
|
||||
|
||||
CM: ISPCollection{
|
||||
ISPName: CM,
|
||||
IP: "120.198.26.254",
|
||||
},
|
||||
}
|
||||
|
||||
var Hangzhou = BackBoneCollection{
|
||||
Location: "杭州",
|
||||
CT163: ISPCollection{
|
||||
ISPName: CT163,
|
||||
IP: "61.164.23.196",
|
||||
},
|
||||
CU169: ISPCollection{
|
||||
ISPName: CU169,
|
||||
IP: "60.12.244.1",
|
||||
},
|
||||
CM: ISPCollection{
|
||||
ISPName: CM,
|
||||
IP: "112.17.224.98",
|
||||
},
|
||||
// 浙江大学 教育网
|
||||
EDU: ISPCollection{
|
||||
ISPName: EDU,
|
||||
IP: "210.32.2.1",
|
||||
},
|
||||
}
|
||||
|
||||
var Hefei = BackBoneCollection{
|
||||
Location: "合肥",
|
||||
// 中国科学技术大学 教育网
|
||||
EDU: ISPCollection{
|
||||
ISPName: EDU,
|
||||
IP: "202.38.64.1",
|
||||
},
|
||||
// 中国科学技术大学 科技网
|
||||
CST: ISPCollection{
|
||||
ISPName: "中国科学技术大学 科技网 AS7497",
|
||||
IP: "210.72.22.2",
|
||||
},
|
||||
}
|
||||
118
fast_trace/fast_trace.go
Normal file
118
fast_trace/fast_trace.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package fastTrace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/printer"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
type FastTracer struct {
|
||||
TracerouteMethod trace.Method
|
||||
}
|
||||
|
||||
func (f *FastTracer) tracert(location string, ispCollection ISPCollection) {
|
||||
fmt.Printf("『%s %s 』\n", location, ispCollection.ISPName)
|
||||
fmt.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 f.TracerouteMethod == trace.ICMPTrace {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
|
||||
res, err := trace.Traceroute(f.TracerouteMethod, conf)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if f.TracerouteMethod == trace.TCPTrace {
|
||||
printer.TracerouteTablePrinter(res)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FastTracer) testAll() {
|
||||
f.testCT()
|
||||
f.testCU()
|
||||
f.testCM()
|
||||
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)
|
||||
f.tracert(TestIPsCollection.Hefei.Location, TestIPsCollection.Hefei.EDU)
|
||||
// 科技网暂时算在EDU里面,等拿到了足够多的数据再分离出去,单独用于测试
|
||||
f.tracert(TestIPsCollection.Hefei.Location, TestIPsCollection.Hefei.CST)
|
||||
}
|
||||
|
||||
func FastTest(tm bool) {
|
||||
var c string
|
||||
|
||||
fmt.Println("您想测试哪些ISP的路由?\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网")
|
||||
fmt.Print("请选择选项:")
|
||||
fmt.Scanln(&c)
|
||||
|
||||
ft := FastTracer{}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
4
go.mod
4
go.mod
@@ -5,13 +5,13 @@ go 1.18
|
||||
require (
|
||||
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
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/panjf2000/ants/v2 v2.5.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // direct
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
8
go.sum
8
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
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=
|
||||
@@ -12,8 +12,6 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
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/panjf2000/ants/v2 v2.5.0 h1:1rWGWSnxCsQBga+nQbA4/iY6VMeNoOIAM0ZWh9u3q2Q=
|
||||
github.com/panjf2000/ants/v2 v2.5.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE=
|
||||
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=
|
||||
@@ -51,6 +49,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
|
||||
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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
43
ipgeo/ipapicom.go
Normal file
43
ipgeo/ipapicom.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPApiCom(ip string) (*IPGeoData, error) {
|
||||
url := "http://ip-api.com/json/" + ip + "?fields=status,message,country,regionName,city,isp,as"
|
||||
client := &http.Client{
|
||||
// 2 秒超时
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0")
|
||||
content, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Println("ip-api.com 请求超时(2s),请切换其他API使用")
|
||||
return nil, err
|
||||
}
|
||||
body, _ := ioutil.ReadAll(content.Body)
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
if res.Get("status").String() != "success" {
|
||||
return &IPGeoData{}, errors.New("超过API阈值")
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("[0-9]+")
|
||||
|
||||
return &IPGeoData{
|
||||
Asnumber: re.FindString(res.Get("as").String()),
|
||||
Country: res.Get("country").String(),
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("regionName").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IPGeoData struct {
|
||||
Asnumber string
|
||||
Country string
|
||||
@@ -13,14 +17,18 @@ type IPGeoData struct {
|
||||
type Source = func(ip string) (*IPGeoData, error)
|
||||
|
||||
func GetSource(s string) Source {
|
||||
switch s {
|
||||
case "LeoMoeAPI":
|
||||
switch strings.ToUpper(s) {
|
||||
case "LEOMOEAPI":
|
||||
return LeoIP
|
||||
case "IP.SB":
|
||||
return IPSB
|
||||
case "IPInsight":
|
||||
case "IPINSIGHT":
|
||||
return IPInSight
|
||||
case "IPAPI.COM":
|
||||
return IPApiCom
|
||||
case "IPINFO":
|
||||
return IPInfo
|
||||
default:
|
||||
return nil
|
||||
return LeoIP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,19 @@ func TestLeoIP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPSB(t *testing.T) {
|
||||
res, err := IPSB("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Asnumber)
|
||||
assert.NotEmpty(t, res.Isp)
|
||||
// Not available
|
||||
//res, err := IPSB("1.1.1.1")
|
||||
//assert.Nil(t, err)
|
||||
//assert.NotNil(t, res)
|
||||
//assert.NotEmpty(t, res.Asnumber)
|
||||
//assert.NotEmpty(t, res.Isp)
|
||||
}
|
||||
|
||||
func TestIPInfo(t *testing.T) {
|
||||
res, err := IPInfo("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Country)
|
||||
// assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.City)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
}
|
||||
@@ -40,3 +41,12 @@ func TestIPInSight(t *testing.T) {
|
||||
// 这个库有时候不提供城市信息,返回值为""
|
||||
//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)
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
Isp: res.Get("asn").Get("domain").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -2,24 +2,36 @@ package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPSB(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://api.ip.sb/geoip/" + ip)
|
||||
url := "https://api.ip.sb/geoip/" + ip
|
||||
client := &http.Client{
|
||||
// 2 秒超时
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
// 设置 UA,ip.sb 默认禁止 go-client User-Agent 的 api 请求
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0")
|
||||
content, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Println("api.ip.sb 请求超时(2s),请切换其他API使用")
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, _ := ioutil.ReadAll(content.Body)
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
if res.Get("country").String() == "" {
|
||||
// 什么都拿不到,证明被Cloudflare风控了
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return &IPGeoData{
|
||||
Asnumber: res.Get("asn").String(),
|
||||
Country: res.Get("country").String(),
|
||||
|
||||
@@ -17,8 +17,14 @@ func LeoIP(ip string) (*IPGeoData, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
if res.Get("Message").String() != "" {
|
||||
return &IPGeoData{
|
||||
Asnumber: res.Get("Message").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &IPGeoData{
|
||||
Asnumber: res.Get("asnumber").String(),
|
||||
Country: res.Get("country").String(),
|
||||
|
||||
@@ -8,6 +8,6 @@ type tokenData struct {
|
||||
|
||||
var token = tokenData{
|
||||
ipinsight: "",
|
||||
ipinfo: "42764a944dabd0",
|
||||
ipinfo: "",
|
||||
ipleo: "NextTraceDemo",
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package listener_channel
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ReceivedMessage struct {
|
||||
N *int
|
||||
Peer net.Addr
|
||||
Msg []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
type ListenerChannel struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
Conn net.PacketConn
|
||||
Messages chan ReceivedMessage
|
||||
}
|
||||
|
||||
func New(conn net.PacketConn) *ListenerChannel {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
results := make(chan ReceivedMessage, 50)
|
||||
|
||||
return &ListenerChannel{Conn: conn, ctx: ctx, cancel: cancel, Messages: results}
|
||||
}
|
||||
|
||||
func (l *ListenerChannel) Start() {
|
||||
for {
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
reply := make([]byte, 1500)
|
||||
err := l.Conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
if err != nil {
|
||||
l.Messages <- ReceivedMessage{Err: err}
|
||||
continue
|
||||
}
|
||||
|
||||
n, peer, err := l.Conn.ReadFrom(reply)
|
||||
if err != nil {
|
||||
l.Messages <- ReceivedMessage{Err: err}
|
||||
continue
|
||||
}
|
||||
l.Messages <- ReceivedMessage{
|
||||
N: &n,
|
||||
Peer: peer,
|
||||
Err: nil,
|
||||
Msg: reply,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ListenerChannel) Stop() {
|
||||
l.cancel()
|
||||
}
|
||||
102
main.go
102
main.go
@@ -4,9 +4,12 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
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"
|
||||
@@ -14,34 +17,77 @@ import (
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
)
|
||||
|
||||
var tcpSYNFlag = flag.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80)")
|
||||
var udpPackageFlag = flag.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 = flag.Int("q", 3, "Set the number of probes per each hop.")
|
||||
var parallelRequests = flag.Int("r", 18, "Set ParallelRequests number. It should be 1 when there is a multi-routing.")
|
||||
var maxHops = flag.Int("m", 30, "Set the max number of hops (max TTL to be reached).")
|
||||
var dataOrigin = flag.String("d", "LeoMoeAPI", "Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight]")
|
||||
var displayMode = flag.String("displayMode", "table", "Choose The Display Mode [table, classic]")
|
||||
var rdnsenable = flag.Bool("rdns", false, "Set whether rDNS will be display")
|
||||
var routePath = flag.Bool("report", false, "Route Path")
|
||||
var fSet = flag.NewFlagSet("", flag.ExitOnError)
|
||||
var fastTest = fSet.Bool("f", false, "One-Key Fast Traceroute")
|
||||
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 = fSet.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 noRdns = fSet.Bool("n", false, "Disable IP Reverse DNS lookup")
|
||||
var routePath = fSet.Bool("report", false, "Route Path")
|
||||
var tablePrint = fSet.Bool("table", false, "Output trace results as table")
|
||||
var beginHop = fSet.Int("b", 1, "Set The Begin TTL")
|
||||
var ver = fSet.Bool("V", false, "Print 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] [ -table ] -report")
|
||||
fSet.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func flagApply() string {
|
||||
flag.Parse()
|
||||
ipArg := flag.Args()
|
||||
if flag.NArg() != 1 {
|
||||
fmt.Println("Args Error\nUsage : ./nexttrace [-T] [-rdns] [-displayMode <displayMode>] [-d <dataOrigin> ] [ -m <hops> ] [ -p <port> ] [ -q <probes> ] [ -r <parallelrequests> ] <hostname>")
|
||||
os.Exit(2)
|
||||
printer.Version()
|
||||
|
||||
target := ""
|
||||
if len(os.Args) < 2 {
|
||||
printArgHelp()
|
||||
}
|
||||
return ipArg[0]
|
||||
|
||||
// flag parse
|
||||
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)
|
||||
}
|
||||
|
||||
// Print Version
|
||||
if *ver {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// -f Fast Test
|
||||
if *fastTest {
|
||||
fastTrace.FastTest(*tcpSYNFlag)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if target == "" {
|
||||
printArgHelp()
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
domain := flagApply()
|
||||
|
||||
if os.Getuid() != 0 {
|
||||
log.Fatalln("Traceroute requires root/sudo privileges.")
|
||||
}
|
||||
|
||||
domain := flagApply()
|
||||
ip := util.DomainLookUp(domain)
|
||||
var ip net.IP
|
||||
|
||||
if *tcpSYNFlag || *udpPackageFlag {
|
||||
ip = util.DomainLookUp(domain, true)
|
||||
} else {
|
||||
ip = util.DomainLookUp(domain, false)
|
||||
}
|
||||
|
||||
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
|
||||
|
||||
var m trace.Method = ""
|
||||
@@ -60,16 +106,19 @@ func main() {
|
||||
}
|
||||
|
||||
var conf = trace.Config{
|
||||
BeginHop: *beginHop,
|
||||
DestIP: ip,
|
||||
DestPort: *port,
|
||||
MaxHops: *maxHops,
|
||||
NumMeasurements: *numMeasurements,
|
||||
ParallelRequests: *parallelRequests,
|
||||
RDns: *rdnsenable,
|
||||
RDns: !*noRdns,
|
||||
IPGeoSource: ipgeo.GetSource(*dataOrigin),
|
||||
Timeout: 2 * time.Second,
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
//Quic: false,
|
||||
if !*tablePrint {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
|
||||
res, err := trace.Traceroute(m, conf)
|
||||
@@ -78,15 +127,12 @@ func main() {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if *tablePrint {
|
||||
printer.TracerouteTablePrinter(res)
|
||||
}
|
||||
|
||||
if *routePath {
|
||||
r := reporter.New(res, ip.String())
|
||||
r.Print()
|
||||
return
|
||||
}
|
||||
|
||||
if *displayMode == "table" {
|
||||
printer.TracerouteTablePrinter(res)
|
||||
} else {
|
||||
printer.TraceroutePrinter(res)
|
||||
}
|
||||
}
|
||||
|
||||
127
nt_install.sh
Normal file
127
nt_install.sh
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
|
||||
Green_font="\033[32m"
|
||||
Yellow_font="\033[33m"
|
||||
Red_font="\033[31m"
|
||||
Font_suffix="\033[0m"
|
||||
Info="${Green_font}[Info]${Font_suffix}"
|
||||
Error="${Red_font}[Error]${Font_suffix}"
|
||||
Tips="${Green_font}[Tips]${Font_suffix}"
|
||||
Temp_path="/var/tmp/nexttrace"
|
||||
|
||||
checkRootPermit() {
|
||||
[[ $EUID -ne 0 ]] && echo -e "${Error} 请使用sudo/root权限运行本脚本" && exit 1
|
||||
}
|
||||
|
||||
checkSystemArch() {
|
||||
arch=$(uname -m)
|
||||
if [[ $arch == "x86_64" ]]; then
|
||||
archParam="amd64"
|
||||
fi
|
||||
|
||||
if [[ $arch == "aarch64" ]]; then
|
||||
archParam="arm64"
|
||||
fi
|
||||
}
|
||||
|
||||
checkSystemDistribution() {
|
||||
case "$OSTYPE" in
|
||||
linux*)
|
||||
osDistribution="linux"
|
||||
downPath="/usr/local/bin/nexttrace"
|
||||
;;
|
||||
*)
|
||||
echo "unknown: $OSTYPE"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
countryCode=$(curl -s "http://ip-api.com/line/?fields=countryCode")
|
||||
}
|
||||
|
||||
installWgetPackage() {
|
||||
echo -e "${Info} wget 正在安装中..."
|
||||
# try apt
|
||||
apt -h &> /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
# 先更新一下数据源,有些机器数据源比较老可能会404
|
||||
apt update -y &> /dev/null
|
||||
apt install wget -y &> /dev/null
|
||||
fi
|
||||
|
||||
# try yum
|
||||
yum -h &> /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
yum install wget -y &> /dev/null
|
||||
fi
|
||||
|
||||
# try dnf
|
||||
dnf -h &> /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
dnf install wget -y &> /dev/null
|
||||
fi
|
||||
|
||||
# try pacman
|
||||
pacman -h &> /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
pacman -Sy
|
||||
pacman -S wget
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
checkWgetPackage() {
|
||||
wget -h &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
installWgetPackage
|
||||
fi
|
||||
}
|
||||
|
||||
downloadBinrayFile() {
|
||||
echo -e "${Info} 获取最新版的 NextTrace 发行版文件信息"
|
||||
# 简单说明一下,Github提供了一个API,可以获取最新发行版本的二进制文件下载地址(对应的是browser_download_url),根据刚刚测得的osDistribution、archParam,获取对应的下载地址
|
||||
latestURL=$(curl -s https://api.github.com/repos/xgadget-lab/nexttrace/releases/latest | grep -i "browser_download_url.*${osDistribution}.*${archParam}" | awk -F '"' '{print $4}')
|
||||
|
||||
if [ "$countryCode" == "CN" ]; then
|
||||
echo -e "${Info} 检测到国内环境,正在使用镜像下载"
|
||||
latestURL="https://ghproxy.com/"$latestURL
|
||||
fi
|
||||
|
||||
echo -e "${Info} 正在下载 NextTrace 二进制文件..."
|
||||
wget -O ${Temp_path} ${latestURL} &> /dev/null
|
||||
if [ $? -eq 0 ];
|
||||
then
|
||||
changeMode
|
||||
mv ${Temp_path} ${downPath}
|
||||
echo -e "${Info} NextTrace 现在已经在您的系统中可用"
|
||||
else
|
||||
echo -e "${Error} NextTrace 下载失败,请检查您的网络是否正常"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
changeMode() {
|
||||
chmod +x ${Temp_path} &> /dev/null
|
||||
}
|
||||
|
||||
runBinrayFileHelp() {
|
||||
if [ -e ${downPath} ]; then
|
||||
${downPath} -V
|
||||
echo -e "${Tips} 一切准备就绪!使用命令 nexttrace 1.1.1.1 开始您的第一次路由测试吧~ 更多进阶命令玩法可以用 nexttrace -h 查看哦\n 关于软件卸载,因为nexttrace是绿色版单文件,卸载只需输入命令 rm /usr/local/bin/nexttrace 即可"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check Procedure
|
||||
checkRootPermit
|
||||
checkSystemDistribution
|
||||
checkSystemArch
|
||||
checkWgetPackage
|
||||
|
||||
# Download Procedure
|
||||
getLocation
|
||||
downloadBinrayFile
|
||||
|
||||
# Run Procedure
|
||||
runBinrayFileHelp
|
||||
@@ -5,8 +5,13 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func PrintCopyRight() {
|
||||
fmt.Println("NextTrace v0.1.0 Alpha \nxgadget-lab zhshch (xzhsh.ch) & leo (leo.moe)")
|
||||
var version = "v0.0.0.alpha"
|
||||
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)")
|
||||
}
|
||||
|
||||
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) {
|
||||
|
||||
@@ -2,9 +2,10 @@ package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
)
|
||||
|
||||
@@ -14,12 +15,12 @@ func TraceroutePrinter(res *trace.Result) {
|
||||
for i, hop := range res.Hops {
|
||||
fmt.Print(i + 1)
|
||||
for _, h := range hop {
|
||||
hopPrinter(h)
|
||||
HopPrinter(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hopPrinter(h trace.Hop) {
|
||||
func HopPrinter(h trace.Hop) {
|
||||
if h.Address == nil {
|
||||
fmt.Println("\t*")
|
||||
} else {
|
||||
@@ -51,19 +52,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
|
||||
|
||||
105
printer/printer_test.go
Normal file
105
printer/printer_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
)
|
||||
|
||||
func TestPrintTraceRouteNav(t *testing.T) {
|
||||
PrintTraceRouteNav(util.DomainLookUp("1.1.1.1", false), "1.1.1.1", "dataOrigin")
|
||||
}
|
||||
|
||||
var testGeo = &ipgeo.IPGeoData{
|
||||
Asnumber: "TestAsnumber",
|
||||
Country: "TestCountry",
|
||||
Prov: "TestProv",
|
||||
City: "TestCity",
|
||||
District: "TestDistrict",
|
||||
Owner: "TestOwner",
|
||||
Isp: "TestIsp",
|
||||
}
|
||||
|
||||
var testResult = &trace.Result{
|
||||
Hops: [][]trace.Hop{
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: testGeo,
|
||||
},
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: testGeo,
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
Hostname: "",
|
||||
TTL: 0,
|
||||
RTT: 0,
|
||||
Error: errors.New("test error"),
|
||||
Geo: nil,
|
||||
},
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 0,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{},
|
||||
},
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
Hostname: "",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: testGeo,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestTraceroutePrinter(t *testing.T) {
|
||||
TraceroutePrinter(testResult)
|
||||
}
|
||||
|
||||
func TestTracerouteTablePrinter(t *testing.T) {
|
||||
TracerouteTablePrinter(testResult)
|
||||
}
|
||||
|
||||
func TestRealtimePrinter(t *testing.T) {
|
||||
RealtimePrinter(testResult, 0)
|
||||
RealtimePrinter(testResult, 1)
|
||||
RealtimePrinter(testResult, 2)
|
||||
}
|
||||
15
printer/realtime_printer.go
Normal file
15
printer/realtime_printer.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
func RealtimePrinter(res *trace.Result, ttl int) {
|
||||
fmt.Print(ttl + 1)
|
||||
for i := range res.Hops[ttl] {
|
||||
HopPrinter(res.Hops[ttl][i])
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,9 +2,12 @@ package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/rodaine/table"
|
||||
)
|
||||
@@ -30,7 +33,16 @@ func TracerouteTablePrinter(res *trace.Result) {
|
||||
if k > 0 {
|
||||
data.Hop = ""
|
||||
}
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Prov, data.City, data.Owner)
|
||||
if data.Country == "" && data.Prov == "" && data.City == "" {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, "", data.Owner)
|
||||
} else {
|
||||
if data.City != "" {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country+", "+data.Prov+", "+data.City, data.Owner)
|
||||
} else {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country + ", " + data.Prov, data.Owner)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// 打印表格
|
||||
@@ -42,7 +54,7 @@ func New() table.Table {
|
||||
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
|
||||
columnFmt := color.New(color.FgYellow).SprintfFunc()
|
||||
|
||||
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Country", "Province", "City", "Owner")
|
||||
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Location", "Owner")
|
||||
tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
|
||||
return tbl
|
||||
}
|
||||
@@ -62,16 +74,18 @@ func tableDataGenerator(h trace.Hop) *rowData {
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Country: "局域网",
|
||||
Owner: "腾讯云",
|
||||
Country: "LAN Address",
|
||||
Prov: "LAN Address",
|
||||
Owner: "",
|
||||
}
|
||||
} else if strings.HasPrefix(IP, "11.") {
|
||||
return &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Country: "局域网",
|
||||
Owner: "阿里云",
|
||||
Country: "LAN Address",
|
||||
Prov: "LAN Address",
|
||||
Owner: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +93,10 @@ func tableDataGenerator(h trace.Hop) *rowData {
|
||||
IP = fmt.Sprint(h.Hostname, " (", IP, ") ")
|
||||
}
|
||||
|
||||
if h.Geo == nil {
|
||||
h.Geo = &ipgeo.IPGeoData{}
|
||||
}
|
||||
|
||||
r := &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: IP,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
@@ -24,9 +24,12 @@ func New(rs *trace.Result, ip string) Reporter {
|
||||
}
|
||||
|
||||
type reporter struct {
|
||||
targetIP string
|
||||
routeReport map[uint16][]routeReportNode
|
||||
routeResult *trace.Result
|
||||
targetTTL uint16
|
||||
targetIP string
|
||||
routeReport map[uint16][]routeReportNode
|
||||
routeReportLock sync.Mutex
|
||||
routeResult *trace.Result
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
type routeReportNode struct {
|
||||
@@ -40,9 +43,15 @@ func experimentTag() {
|
||||
fmt.Println("Route-Path 功能实验室")
|
||||
}
|
||||
|
||||
func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData) (routeReportNode, error) {
|
||||
func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData, ttl uint16) {
|
||||
|
||||
var success bool = true
|
||||
|
||||
defer r.wg.Done()
|
||||
|
||||
rpn := routeReportNode{}
|
||||
ptr, err := net.LookupAddr(ip)
|
||||
|
||||
if err == nil {
|
||||
if strings.Contains(strings.ToLower(ptr[0]), "ix") {
|
||||
rpn.ix = true
|
||||
@@ -50,59 +59,99 @@ func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData)
|
||||
rpn.ix = false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 这种写法不好,后面再重构一下
|
||||
// 判断反向解析的域名中又或者是IP地理位置数据库中,是否出现了 IX
|
||||
if strings.Contains(strings.ToLower(ipGeoData.Isp), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Isp), "ix") || strings.Contains(strings.ToLower(ipGeoData.Owner), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Owner), "ix") {
|
||||
rpn.ix = true
|
||||
}
|
||||
|
||||
// TODO: 正则判断POP并且提取带宽大小等信息
|
||||
|
||||
// CN2 需要特殊处理,因为他们很多没有ASN
|
||||
// 但是目前这种写法是不规范的,属于凭空标记4809的IP
|
||||
// TODO: 用更好的方式显示 CN2 骨干网的路由 Path
|
||||
if strings.HasPrefix(ip, "59.43") {
|
||||
rpn.asn = "4809"
|
||||
} else {
|
||||
rpn.asn = ipGeoData.Asnumber
|
||||
}
|
||||
|
||||
// 无论最后一跳是否为存在地理位置信息(AnyCast),都应该给予显示
|
||||
if ipGeoData.Country == "" || ipGeoData.City == "" && ip != r.targetIP {
|
||||
return rpn, errors.New("GeoData Search Failed")
|
||||
if (ipGeoData.Country == "" || ipGeoData.Country == "LAN Address" || ipGeoData.Country == "-") && ip != r.targetIP {
|
||||
success = false
|
||||
} else {
|
||||
if ipGeoData.City == "" {
|
||||
rpn.geo = []string{ipGeoData.Country, ipGeoData.Country}
|
||||
rpn.geo = []string{ipGeoData.Country, ipGeoData.Prov}
|
||||
} else {
|
||||
rpn.geo = []string{ipGeoData.Country, ipGeoData.City}
|
||||
}
|
||||
}
|
||||
if ipGeoData.Asnumber == "" {
|
||||
rpn.asn = "*"
|
||||
}
|
||||
|
||||
if ipGeoData.Isp == "" {
|
||||
rpn.isp = ipGeoData.Owner
|
||||
} else {
|
||||
rpn.isp = ipGeoData.Isp
|
||||
}
|
||||
return rpn, nil
|
||||
|
||||
// 有效记录
|
||||
if success {
|
||||
// 锁住资源,防止同时写panic
|
||||
r.routeReportLock.Lock()
|
||||
// 添加到MAP中
|
||||
r.routeReport[ttl] = append(r.routeReport[ttl], rpn)
|
||||
// 写入完成,解锁释放资源给其他协程
|
||||
r.routeReportLock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reporter) InitialBaseData() Reporter {
|
||||
var nodeIndex uint16 = 1
|
||||
reportNodes := map[uint16][]routeReportNode{}
|
||||
for i := uint16(0); int(i) < len(r.routeResult.Hops); i++ {
|
||||
|
||||
r.routeReport = reportNodes
|
||||
r.targetTTL = uint16(len(r.routeResult.Hops))
|
||||
|
||||
for i := uint16(0); i < r.targetTTL; i++ {
|
||||
traceHop := r.routeResult.Hops[i][0]
|
||||
if traceHop.Success {
|
||||
currentIP := traceHop.Address.String()
|
||||
rpn, err := r.generateRouteReportNode(currentIP, *traceHop.Geo)
|
||||
if err == nil {
|
||||
reportNodes[nodeIndex] = append(reportNodes[nodeIndex], rpn)
|
||||
nodeIndex += 1
|
||||
}
|
||||
r.wg.Add(1)
|
||||
go r.generateRouteReportNode(currentIP, *traceHop.Geo, i)
|
||||
}
|
||||
}
|
||||
r.routeReport = reportNodes
|
||||
|
||||
// 等待所有的子协程运行完毕
|
||||
r.wg.Wait()
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *reporter) Print() {
|
||||
var beforeActiveTTL uint16 = 0
|
||||
r.InitialBaseData()
|
||||
for i := uint16(1); int(i) < len(r.routeReport)+1; i++ {
|
||||
// 尝试首个有效 TTL
|
||||
for i := uint16(0); i < r.targetTTL; i++ {
|
||||
if len(r.routeReport[i]) != 0 {
|
||||
beforeActiveTTL = i
|
||||
// 找到以后便不再循环
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := beforeActiveTTL; i < r.targetTTL; i++ {
|
||||
// 计算该TTL内的数据长度,如果为0,则代表没有有效数据
|
||||
if len(r.routeReport[i]) == 0 {
|
||||
// 跳过改跃点的数据整理
|
||||
continue
|
||||
}
|
||||
nodeReport := r.routeReport[i][0]
|
||||
if i == 1 {
|
||||
|
||||
if i == beforeActiveTTL {
|
||||
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||||
} else {
|
||||
nodeReportBefore := r.routeReport[i-1][0]
|
||||
nodeReportBefore := r.routeReport[beforeActiveTTL][0]
|
||||
// ASN 相同,同个 ISP 内部的数据传递
|
||||
if nodeReportBefore.asn == nodeReport.asn {
|
||||
// Same ASN but Coutry or City Changed
|
||||
if nodeReportBefore.geo[0] != nodeReport.geo[0] {
|
||||
@@ -113,8 +162,11 @@ func (r *reporter) Print() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ASN 不同,跨 ISP 的数据传递,这里可能会出现 POP、IP Transit、Peer、Exchange
|
||||
fmt.Printf("』」")
|
||||
if int(i) != len(r.routeReport)+1 {
|
||||
// 部分 Shell 客户端可能无法很好的展示这个特殊字符
|
||||
// TODO: 寻找其他替代字符
|
||||
fmt.Printf("\n ╭╯\n ╰")
|
||||
}
|
||||
if nodeReport.ix {
|
||||
@@ -124,6 +176,8 @@ func (r *reporter) Print() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 标记为最新的一个有效跃点
|
||||
beforeActiveTTL = i
|
||||
}
|
||||
fmt.Println("』」")
|
||||
}
|
||||
|
||||
@@ -1,31 +1,115 @@
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
)
|
||||
|
||||
var testResult = &trace.Result{
|
||||
Hops: [][]trace.Hop{
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "4808",
|
||||
Country: "中国",
|
||||
Prov: "北京市",
|
||||
City: "北京市",
|
||||
District: "北京市",
|
||||
Owner: "",
|
||||
Isp: "中国联通",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("114.249.16.1")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "4808",
|
||||
Country: "中国",
|
||||
Prov: "北京市",
|
||||
City: "北京市",
|
||||
District: "北京市",
|
||||
Owner: "",
|
||||
Isp: "中国联通",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("219.158.5.150")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "4837",
|
||||
Country: "中国",
|
||||
Prov: "",
|
||||
City: "",
|
||||
District: "",
|
||||
Owner: "",
|
||||
Isp: "中国联通",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("62.115.125.160")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "1299",
|
||||
Country: "Sweden",
|
||||
Prov: "Stockholm County",
|
||||
City: "Stockholm",
|
||||
District: "",
|
||||
Owner: "",
|
||||
Isp: "Telia Company AB",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("213.226.68.73")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "56630",
|
||||
Country: "Germany",
|
||||
Prov: "Hesse, Frankfurt",
|
||||
City: "",
|
||||
District: "",
|
||||
Owner: "",
|
||||
Isp: "Melbikomas UAB",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
ip := util.DomainLookUp("213.226.68.73")
|
||||
var m trace.Method = "tcp"
|
||||
var conf = trace.Config{
|
||||
DestIP: ip,
|
||||
DestPort: 80,
|
||||
MaxHops: 30,
|
||||
NumMeasurements: 1,
|
||||
ParallelRequests: 1,
|
||||
RDns: true,
|
||||
IPGeoSource: ipgeo.GetSource("LeoMoeAPI"),
|
||||
Timeout: 2 * time.Second,
|
||||
|
||||
//Quic: false,
|
||||
}
|
||||
|
||||
res, _ := trace.Traceroute(m, conf)
|
||||
r := New(res, ip.String())
|
||||
r := New(testResult, "213.226.68.73")
|
||||
r.Print()
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package signal
|
||||
|
||||
type Signal struct {
|
||||
sigChan chan struct{}
|
||||
}
|
||||
|
||||
func New() *Signal {
|
||||
return &Signal{sigChan: make(chan struct{}, 1)}
|
||||
}
|
||||
|
||||
func (s *Signal) Signal() {
|
||||
if len(s.sigChan) == 0 {
|
||||
s.sigChan <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Signal) Chan() chan struct{} {
|
||||
return s.sigChan
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package taskgroup
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type TaskGroup struct {
|
||||
count int
|
||||
mu sync.Mutex
|
||||
done []chan struct{}
|
||||
}
|
||||
|
||||
func New() *TaskGroup {
|
||||
return &TaskGroup{
|
||||
count: 0,
|
||||
mu: sync.Mutex{},
|
||||
done: []chan struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TaskGroup) Add() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.count++
|
||||
}
|
||||
|
||||
func (t *TaskGroup) Done() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.count-1 == 0 {
|
||||
for _, doneChannel := range t.done {
|
||||
doneChannel <- struct{}{}
|
||||
}
|
||||
t.done = []chan struct{}{}
|
||||
}
|
||||
t.count--
|
||||
}
|
||||
|
||||
func (t *TaskGroup) Wait() {
|
||||
doneChannel := make(chan struct{})
|
||||
t.mu.Lock()
|
||||
t.done = append(t.done, doneChannel)
|
||||
t.mu.Unlock()
|
||||
<-doneChannel
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
@@ -10,27 +11,17 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type ICMPTracer struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestLock sync.Mutex
|
||||
icmpListen net.PacketConn
|
||||
workFork workFork
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
|
||||
sem *semaphore.Weighted
|
||||
}
|
||||
|
||||
type workFork struct {
|
||||
ttl int
|
||||
num int
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) Execute() (*Result, error) {
|
||||
@@ -49,21 +40,24 @@ func (t *ICMPTracer) Execute() (*Result, error) {
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.resCh = make(chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
|
||||
|
||||
for t.workFork.ttl = 1; t.workFork.ttl <= t.MaxHops; t.workFork.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(workFork{t.workFork.ttl, i})
|
||||
go t.send(ttl)
|
||||
}
|
||||
// 一组TTL全部退出(收到应答或者超时终止)以后,再进行下一个TTL的包发送
|
||||
t.wg.Wait()
|
||||
t.workFork.num = 0
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl-1)
|
||||
}
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
@@ -81,48 +75,47 @@ func (t *ICMPTracer) listenICMP() {
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
|
||||
if binary.BigEndian.Uint16(msg.Msg[32:34]) != uint16(os.Getpid()&0xffff) {
|
||||
// 如果类型为应答消息,且应答消息包的进程ID与主进程相同时不跳过
|
||||
if msg.Msg[0] != 0 || binary.BigEndian.Uint16(msg.Msg[4:6]) != uint16(os.Getpid()&0xffff) {
|
||||
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)
|
||||
|
||||
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)
|
||||
case ipv4.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
|
||||
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[t.workFork.num]
|
||||
t.workFork.num += 1
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- Hop{
|
||||
t.resCh <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) send(fork workFork) error {
|
||||
err := t.sem.Acquire(context.Background(), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.sem.Release(1)
|
||||
|
||||
func (t *ICMPTracer) send(ttl int) error {
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && fork.ttl > t.final {
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -134,7 +127,7 @@ func (t *ICMPTracer) send(fork workFork) error {
|
||||
},
|
||||
}
|
||||
|
||||
ipv4.NewPacketConn(t.icmpListen).SetTTL(fork.ttl)
|
||||
ipv4.NewPacketConn(t.icmpListen).SetTTL(ttl)
|
||||
|
||||
wb, err := icmpHeader.Marshal(nil)
|
||||
if err != nil {
|
||||
@@ -148,41 +141,30 @@ func (t *ICMPTracer) send(fork workFork) error {
|
||||
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
t.inflightRequestLock.Lock()
|
||||
hopCh := make(chan Hop)
|
||||
t.inflightRequest[fork.num] = hopCh
|
||||
t.inflightRequestLock.Unlock()
|
||||
|
||||
defer func() {
|
||||
t.inflightRequestLock.Lock()
|
||||
close(hopCh)
|
||||
delete(t.inflightRequest, fork.ttl)
|
||||
t.inflightRequestLock.Unlock()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-hopCh:
|
||||
case h := <-t.resCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && fork.ttl > t.final {
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || fork.ttl < t.final {
|
||||
t.final = fork.ttl
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || fork.ttl < t.final {
|
||||
t.final = fork.ttl
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = fork.ttl
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
h.fetchIPData(t.Config)
|
||||
@@ -190,14 +172,14 @@ func (t *ICMPTracer) send(fork workFork) error {
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && fork.ttl > t.final {
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: fork.ttl,
|
||||
TTL: ttl,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
179
trace/icmp_ipv6.go
Normal file
179
trace/icmp_ipv6.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
type ICMPTracerv6 struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
t.icmpListen, err = net.ListenPacket("ip6:58", "::")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
defer t.icmpListen.Close()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.resCh = make(chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) listenICMP() {
|
||||
lc := NewPacketListener(t.icmpListen, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
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) send(ttl int) error {
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
icmpHeader := icmp.Message{
|
||||
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
},
|
||||
}
|
||||
|
||||
p := ipv6.NewPacketConn(t.icmpListen)
|
||||
|
||||
icmpHeader.Body.(*icmp.Echo).Seq = ttl
|
||||
p.SetHopLimit(ttl)
|
||||
|
||||
wb, err := icmpHeader.Marshal(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := t.icmpListen.WriteTo(wb, &net.IPAddr{IP: t.DestIP}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-t.resCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
h.fetchIPData(t.Config)
|
||||
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: ttl,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/listener_channel"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
@@ -64,15 +63,27 @@ 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()
|
||||
// 如果是表格模式,则一次性并发请求
|
||||
if t.RealtimePrinter == nil {
|
||||
t.wg.Wait()
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
@@ -89,18 +100,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,10 +124,7 @@ func (t *TCPTracer) listenICMP() {
|
||||
// @title listenTCP
|
||||
// @description 监听TCP的响应数据包
|
||||
func (t *TCPTracer) listenTCP() {
|
||||
lc := listener_channel.New(t.tcp)
|
||||
|
||||
defer lc.Stop()
|
||||
|
||||
lc := NewPacketListener(t.tcp, t.ctx)
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
265
trace/tcp_ipv6.go
Normal file
265
trace/tcp_ipv6.go
Normal file
@@ -0,0 +1,265 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv6"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type TCPTracerv6 struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestLock sync.Mutex
|
||||
SrcIP net.IP
|
||||
icmp net.PacketConn
|
||||
tcp net.PacketConn
|
||||
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
|
||||
sem *semaphore.Weighted
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
|
||||
log.Println(util.LocalIPPort(t.DestIP))
|
||||
var err error
|
||||
t.tcp, err = net.ListenPacket("ip6:tcp", t.SrcIP.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.icmp, err = icmp.ListenPacket("ip6:53", "::")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
defer t.icmp.Close()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
go t.listenTCP()
|
||||
|
||||
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
|
||||
|
||||
for ttl := 1; ttl <= t.MaxHops; ttl++ {
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
t.wg.Wait()
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) listenICMP() {
|
||||
lc := NewPacketListener(t.icmp, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(53, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
log.Println(msg.Peer)
|
||||
switch rm.Type {
|
||||
case ipv6.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv6.ICMPTypeDestinationUnreachable:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
|
||||
default:
|
||||
//log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @title listenTCP
|
||||
// @description 监听TCP的响应数据包
|
||||
func (t *TCPTracerv6) listenTCP() {
|
||||
lc := NewPacketListener(t.tcp, t.ctx)
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() != t.DestIP.String() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 解包
|
||||
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
|
||||
// 从包中获取TCP layer信息
|
||||
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
// 取得目标主机的Sequence Number
|
||||
|
||||
if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok {
|
||||
// 最后一跳
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) handleICMPMessage(msg ReceivedMessage, data []byte) {
|
||||
header, err := util.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sequenceNumber := util.GetTCPSeq(header)
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[int(sequenceNumber)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) send(ttl int) error {
|
||||
err := t.sem.Acquire(context.Background(), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.sem.Release(1)
|
||||
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
// 随机种子
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, srcPort := util.LocalIPPort(t.DestIP)
|
||||
ipHeader := &layers.IPv6{
|
||||
SrcIP: t.SrcIP,
|
||||
DstIP: t.DestIP,
|
||||
NextHeader: layers.IPProtocolTCP,
|
||||
HopLimit: uint8(ttl),
|
||||
}
|
||||
// 使用Uint16兼容32位系统,防止在rand的时候因使用int32而溢出
|
||||
sequenceNumber := uint32(r.Intn(math.MaxUint16))
|
||||
tcpHeader := &layers.TCP{
|
||||
SrcPort: layers.TCPPort(srcPort),
|
||||
DstPort: layers.TCPPort(t.DestPort),
|
||||
Seq: sequenceNumber,
|
||||
SYN: true,
|
||||
Window: 14600,
|
||||
}
|
||||
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ipv6.NewPacketConn(t.tcp).SetHopLimit(ttl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := t.tcp.WriteTo(buf.Bytes(), &net.IPAddr{IP: t.DestIP}); err != nil {
|
||||
return err
|
||||
}
|
||||
t.inflightRequestLock.Lock()
|
||||
hopCh := make(chan Hop)
|
||||
t.inflightRequest[int(sequenceNumber)] = hopCh
|
||||
t.inflightRequestLock.Unlock()
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-hopCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
h.fetchIPData(t.Config)
|
||||
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: ttl,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
76
trace/temp_printer.go
Normal file
76
trace/temp_printer.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
)
|
||||
|
||||
func HopPrinter(h Hop) {
|
||||
if h.Address == nil {
|
||||
fmt.Println("\t*")
|
||||
} else {
|
||||
txt := "\t"
|
||||
|
||||
if h.Hostname == "" {
|
||||
txt += fmt.Sprint(h.Address, " ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
|
||||
} else {
|
||||
txt += fmt.Sprint(h.Hostname, " (", h.Address, ") ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
|
||||
}
|
||||
|
||||
if h.Geo != nil {
|
||||
txt += " " + formatIpGeoData(h.Address.String(), h.Geo)
|
||||
}
|
||||
|
||||
fmt.Println(txt)
|
||||
}
|
||||
}
|
||||
|
||||
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
|
||||
var res = make([]string, 0, 10)
|
||||
|
||||
if data.Asnumber == "" {
|
||||
res = append(res, "*")
|
||||
} else {
|
||||
res = append(res, "AS"+data.Asnumber)
|
||||
}
|
||||
|
||||
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
|
||||
// TODO: 移动IDC判断到Hop.fetchIPData函数,减少API调用
|
||||
if strings.HasPrefix(ip, "9.") {
|
||||
res = append(res, "LAN Address", "")
|
||||
} else if strings.HasPrefix(ip, "11.") {
|
||||
res = append(res, "LAN Address", "")
|
||||
} else if data.Country == "" {
|
||||
res = append(res, "LAN Address")
|
||||
} else {
|
||||
// 有些IP的归属信息为空,这个时候将ISP的信息填入
|
||||
if data.Owner == "" {
|
||||
data.Owner = data.Isp
|
||||
}
|
||||
if data.District != "" {
|
||||
data.City = data.City + ", " + data.District
|
||||
}
|
||||
if data.Prov == "" && data.City == "" {
|
||||
// anyCast或是骨干网数据不应该有国家信息
|
||||
data.Owner = data.Owner + ", " + data.Owner
|
||||
} else {
|
||||
// 非骨干网正常填入IP的国家信息数据
|
||||
res = append(res, data.Country)
|
||||
}
|
||||
|
||||
if data.Prov != "" {
|
||||
res = append(res, data.Prov)
|
||||
}
|
||||
if data.City != "" {
|
||||
res = append(res, data.City)
|
||||
}
|
||||
|
||||
if data.Owner != "" {
|
||||
res = append(res, data.Owner)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(res, ", ")
|
||||
}
|
||||
@@ -16,6 +16,7 @@ var (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BeginHop int
|
||||
MaxHops int
|
||||
NumMeasurements int
|
||||
ParallelRequests int
|
||||
@@ -25,6 +26,7 @@ type Config struct {
|
||||
Quic bool
|
||||
IPGeoSource ipgeo.Source
|
||||
RDns bool
|
||||
RealtimePrinter func(res *Result, ttl int)
|
||||
}
|
||||
|
||||
type Method string
|
||||
@@ -54,11 +56,25 @@ func Traceroute(method Method, config Config) (*Result, error) {
|
||||
|
||||
switch method {
|
||||
case ICMPTrace:
|
||||
tracer = &ICMPTracer{Config: config}
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &ICMPTracer{Config: config}
|
||||
} else {
|
||||
tracer = &ICMPTracerv6{Config: config}
|
||||
}
|
||||
|
||||
case UDPTrace:
|
||||
tracer = &UDPTracer{Config: config}
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &UDPTracer{Config: config}
|
||||
} else {
|
||||
return nil, errors.New("IPv6 UDP Traceroute is not supported")
|
||||
}
|
||||
case TCPTrace:
|
||||
tracer = &TCPTracer{Config: config}
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &TCPTracer{Config: config}
|
||||
} else {
|
||||
// tracer = &TCPTracerv6{Config: config}
|
||||
return nil, errors.New("IPv6 TCP Traceroute is not supported")
|
||||
}
|
||||
default:
|
||||
return &Result{}, ErrInvalidMethod
|
||||
}
|
||||
|
||||
34
trace/udp.go
34
trace/udp.go
@@ -1,6 +1,11 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
@@ -8,10 +13,6 @@ import (
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/sync/semaphore"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UDPTracer struct {
|
||||
@@ -52,13 +53,26 @@ 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)
|
||||
}
|
||||
// 如果是表格模式,则一次性并发请求
|
||||
if t.RealtimePrinter == nil {
|
||||
t.wg.Wait()
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
@@ -99,8 +113,8 @@ func (t *UDPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
|
||||
return
|
||||
}
|
||||
srcPort := util.GetUDPSrcPort(header)
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
//t.inflightRequestLock.Lock()
|
||||
//defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[int(srcPort)]
|
||||
if !ok {
|
||||
return
|
||||
@@ -128,7 +142,6 @@ func (t *UDPTracer) getUDPConn(try int) (net.IP, int, net.PacketConn) {
|
||||
}
|
||||
return t.getUDPConn(try + 1)
|
||||
}
|
||||
|
||||
return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn
|
||||
}
|
||||
|
||||
@@ -184,6 +197,7 @@ func (t *UDPTracer) send(ttl int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 在对inflightRequest进行写操作的时候应该加锁保护,以免多个goroutine协程试图同时写入造成panic
|
||||
t.inflightRequestLock.Lock()
|
||||
hopCh := make(chan Hop)
|
||||
t.inflightRequest[srcPort] = hopCh
|
||||
|
||||
16
util/util.go
16
util/util.go
@@ -25,7 +25,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,16 +36,20 @@ func DomainLookUp(host string) net.IP {
|
||||
var ipv6Flag = false
|
||||
|
||||
for _, ip := range ips {
|
||||
// 仅返回ipv4的ip
|
||||
if ip.To4() != nil {
|
||||
ipSlice = append(ipSlice, ip)
|
||||
if ipv4Only {
|
||||
// 仅返回ipv4的ip
|
||||
if ip.To4() != nil {
|
||||
ipSlice = append(ipSlice, ip)
|
||||
} else {
|
||||
ipv6Flag = true
|
||||
}
|
||||
} else {
|
||||
ipv6Flag = true
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user