mirror of
https://github.com/nxtrace/NTrace-core.git
synced 2025-08-12 06:26:39 +00:00
Compare commits
342 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2084f2c316 | ||
|
|
c9ebf7465a | ||
|
|
f11f9c8234 | ||
|
|
36315c6d9d | ||
|
|
548839f564 | ||
|
|
28c2961490 | ||
|
|
e5fe66b9ab | ||
|
|
aca0ed10b8 | ||
|
|
a7fc2cd5d8 | ||
|
|
a26318ff70 | ||
|
|
251c8aa2e8 | ||
|
|
89f52ca766 | ||
|
|
dfabe225b9 | ||
|
|
97172466ed | ||
|
|
404f0a1c62 | ||
|
|
c59e843495 | ||
|
|
35bc15583e | ||
|
|
ee9111249c | ||
|
|
d7ab206e40 | ||
|
|
6d7eac1e16 | ||
|
|
b8772d4cca | ||
|
|
5c94a19944 | ||
|
|
b7d24d8779 | ||
|
|
f882b87a18 | ||
|
|
9412189a0c | ||
|
|
fbda4fb4ad | ||
|
|
eccc2d1a0b | ||
|
|
31f1947108 | ||
|
|
d864313898 | ||
|
|
cc86712c23 | ||
|
|
668c46cf5a | ||
|
|
8a428c4fb9 | ||
|
|
569cca02d9 | ||
|
|
90e304cf22 | ||
|
|
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 | ||
|
|
7712ebf953 | ||
|
|
14bbc62358 | ||
|
|
4323021f96 | ||
|
|
cbfb37f37b | ||
|
|
50d594e4df | ||
|
|
6b08727993 | ||
|
|
14730bb489 | ||
|
|
b688a1ae99 | ||
|
|
3548c47411 | ||
|
|
460e8ba78d | ||
|
|
abe4b685c4 | ||
|
|
9758a93382 | ||
|
|
6fffe31506 | ||
|
|
b7b966b93e | ||
|
|
433c8656a1 | ||
|
|
1542cb4b07 | ||
|
|
06ee8f7373 | ||
|
|
6792bafb02 | ||
|
|
70305caa1c | ||
|
|
671ad82780 | ||
|
|
e62575beba | ||
|
|
d92a1e10d3 | ||
|
|
971d68f93f | ||
|
|
f765dbafae | ||
|
|
ea7feab2f9 | ||
|
|
e941eaa167 | ||
|
|
46e32d697d | ||
|
|
97578e40f7 | ||
|
|
89ed828e6e | ||
|
|
afa61d6e8d | ||
|
|
a28ff7c034 | ||
|
|
97557159e7 | ||
|
|
c63fee7d06 | ||
|
|
9e1df30bbd | ||
|
|
8a6ebdfae1 | ||
|
|
be2e197b7c | ||
|
|
89504876bc | ||
|
|
f4f1cd7f33 | ||
|
|
82493dda43 | ||
|
|
42a141d360 | ||
|
|
2e681b48c5 | ||
|
|
13af96ae15 | ||
|
|
d65ac66cde | ||
|
|
24e1b1f497 | ||
|
|
0b9e30e21c | ||
|
|
ce736fd1c2 | ||
|
|
4b2a34aeaa | ||
|
|
b6f226c400 | ||
|
|
ebdc2157da | ||
|
|
53d16b74aa | ||
|
|
6898e5d727 | ||
|
|
a8874dd809 | ||
|
|
3d5734b7b3 | ||
|
|
584be93c8d | ||
|
|
262a97d7a5 | ||
|
|
8d82ad10bb |
@@ -2,10 +2,14 @@
|
||||
|
||||
set -e
|
||||
|
||||
DIST_PREFIX="BetterTrace"
|
||||
DIST_PREFIX="nexttrace"
|
||||
DEBUG_MODE=${2}
|
||||
TARGET_DIR="dist"
|
||||
PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 linux/arm64"
|
||||
PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips 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.
|
||||
81
.github/workflows/build.yml
vendored
81
.github/workflows/build.yml
vendored
@@ -1,16 +1,31 @@
|
||||
on:
|
||||
push: # 每次 push 的时候触发
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
name: Build Release
|
||||
name: Test & Build Release
|
||||
jobs:
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/') # 只有这次 Commit 是 创建 Tag 时,才进行后续发布操作
|
||||
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: sudo 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
|
||||
|
||||
@@ -19,9 +34,51 @@ jobs:
|
||||
with: # 将下述可执行文件 release 上去
|
||||
draft: false # Release草稿
|
||||
files: |
|
||||
dist/BetterTrace_darwin_amd64
|
||||
dist/BetterTrace_darwin_arm64
|
||||
dist/BetterTrace_linux_amd64
|
||||
dist/BetterTrace_linux_arm64
|
||||
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.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GT_Token }}
|
||||
publish-new-formula:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Runs a single command using the runners shell
|
||||
- name: config git
|
||||
run: |
|
||||
git config --global user.email "${{ secrets.git_mail }}"
|
||||
git config --global user.name "${{ secrets.git_name }}"
|
||||
- name: Clone repo
|
||||
run: |
|
||||
git clone https://github.com/xgadget-lab/homebrew-nexttrace.git
|
||||
- name: Exec scipt
|
||||
run: |
|
||||
cd homebrew-nexttrace
|
||||
bash genFormula.sh
|
||||
# - name: setup SSH keys and known_hosts
|
||||
# run: |
|
||||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
# ssh-agent -a $SSH_AUTH_SOCK > /dev/null
|
||||
# ssh-add - <<< "${{ secrets.ID_RSA }}"
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- name: Git Push
|
||||
run: |
|
||||
cd homebrew-nexttrace
|
||||
git commit -am 'Publish a new version with Formula'
|
||||
git remote set-url origin https://${{ secrets.gt_token }}@github.com/xgadget-lab/homebrew-nexttrace.git
|
||||
git push || 1
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
|
||||
45
.github/workflows/publishNewFormula.yml
vendored
Normal file
45
.github/workflows/publishNewFormula.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Publish New Formula
|
||||
|
||||
# Controls when the action will run. Workflow runs when manually triggered using the UI
|
||||
# or API.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "greet"
|
||||
publish-new-formula:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Runs a single command using the runners shell
|
||||
- name: config git
|
||||
run: |
|
||||
git config --global user.email "${{ secrets.git_mail }}"
|
||||
git config --global user.name "${{ secrets.git_name }}"
|
||||
- name: Clone repo
|
||||
run: |
|
||||
git clone https://github.com/xgadget-lab/homebrew-nexttrace.git
|
||||
- name: Exec scipt
|
||||
run: |
|
||||
cd homebrew-nexttrace
|
||||
bash genFormula.sh
|
||||
# - name: setup SSH keys and known_hosts
|
||||
# run: |
|
||||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
# ssh-agent -a $SSH_AUTH_SOCK > /dev/null
|
||||
# ssh-add - <<< "${{ secrets.ID_RSA }}"
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- name: Git Push
|
||||
run: |
|
||||
cd homebrew-nexttrace
|
||||
git commit -am 'Publish a new version with Formula'
|
||||
git remote set-url origin https://${{ secrets.gt_token }}@github.com/xgadget-lab/homebrew-nexttrace.git
|
||||
git push || 1
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
163
.gitignore
vendored
Normal file
163
.gitignore
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
### VisualStudioCode template
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Example user template template
|
||||
### Example user template
|
||||
|
||||
# IntelliJ project files
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
gen
|
||||
### Go template
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
### Windows template
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
237
README.md
237
README.md
@@ -1,2 +1,235 @@
|
||||
# traceroute
|
||||
可视化路由跟踪工具
|
||||
<div align="center">
|
||||
|
||||
<img src="asset/logo.png" height="200px" alt="NextTrace Logo"/>
|
||||
|
||||
</div>
|
||||
|
||||
## NextTrace Lite
|
||||
|
||||
Document Language: English | [简体中文](README_zh_CN.md)
|
||||
|
||||
An open source visual routing tool that pursues light weight, developed using Golang.
|
||||
|
||||
NextTrace has a total of 2 versions, the Lite version focusing on lightweight and the [Enhanced version](#nexttrace-enhanced) which is more enthusiast-oriented.
|
||||
|
||||
## How To Use
|
||||
|
||||
### Automated Installation
|
||||
|
||||
```bash
|
||||
# Linux one-click install script
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/xgadget-lab/nexttrace/main/nt_install.sh)
|
||||
|
||||
# macOS brew install command
|
||||
brew tap xgadget-lab/nexttrace && brew install nexttrace
|
||||
```
|
||||
|
||||
- `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 not available on `Windows` platform.
|
||||
|
||||
### Get Started
|
||||
|
||||
`NextTrace` uses the `ICMP` protocol to perform TraceRoute requests by default, which supports both `IPv4` and `IPv6`
|
||||
|
||||
```bash
|
||||
# IPv4 ICMP Trace
|
||||
nexttrace 1.0.0.1
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
`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 -f
|
||||
|
||||
# You can also use TCP SYN for testing
|
||||
nexttrace -f -T
|
||||
```
|
||||
|
||||
`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
|
||||
|
||||
# You can specify the port by yourself [here is 443], the default port is 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` also supports some advanced functions, such as ttl control, concurrent probe packet count control, mode switching, etc.
|
||||
|
||||
```bash
|
||||
# Send 2 probe packets per hop
|
||||
nexttrace -q 2 www.hkix.net
|
||||
|
||||
# No concurrent probe packets, only one probe packet is sent at a time
|
||||
nexttrace -r 1 www.hkix.net
|
||||
|
||||
# Start Trace with TTL of 5, end at TTL of 10
|
||||
nexttrace -b 5 -m 10 www.decix.net
|
||||
|
||||
# Turn off the IP reverse parsing function
|
||||
nexttrace -n 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 Aliyun「ALIDNS.COM『ALIDNS.COM』」
|
||||
nexttrace -report www.time.com.my
|
||||
```
|
||||
|
||||
`NextTrace` supports users to select their own IP API (currently supports: `LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`)
|
||||
|
||||
```bash
|
||||
# You can specify the IP database by yourself [IP.SB here], if not specified, LeoMoeAPI will be used
|
||||
nexttrace -d 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` supports mixed parameters
|
||||
|
||||
```bash
|
||||
Example:
|
||||
nexttrace -d IPInsight -m 20 -p 443 -q 5 -r 20 -rdns 1.1.1.1
|
||||
nexttrace -T -q 2 -r 1 -table -report 2001:4860:4860::8888
|
||||
```
|
||||
|
||||
### IP Database
|
||||
|
||||
The IP database is set to our own API service by default. If we encounter abuse, we may choose to close it.
|
||||
|
||||
We will also open-source the source code of the server in the near future, therefore you can also build your own API server according to the source code of the project by then.
|
||||
|
||||
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 Print Version
|
||||
-b int
|
||||
Set The Begin TTL (default 1)
|
||||
-d string
|
||||
Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight, IPAPI.com] (default "LeoMoeAPI")
|
||||
-f One-Key Fast Traceroute
|
||||
-m int
|
||||
Set the max number of hops (max TTL to be reached). (default 30)
|
||||
-n Disable IP Reverse DNS lookup
|
||||
-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)
|
||||
-report
|
||||
Route Path
|
||||
-table
|
||||
Output trace results as table
|
||||
```
|
||||
|
||||
## Project screenshot
|
||||
|
||||

|
||||
|
||||
## NextTrace Enhanced
|
||||
|
||||
`NextTrace Enhanced` is an enhanced version for enthusiasts, `Enhanced` provides trace route calls in the form of Web API and a simple Looking Glass webpage with built-in visualization.
|
||||
|
||||
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.
|
||||
|
||||
https://github.com/OwO-Network/nexttrace-enhanced
|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## Credits
|
||||
|
||||
[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 | Type | Data Source | Proportion |
|
||||
|:---------------------------:|:--------:|:--------------------:|:----------:|
|
||||
| China Telecom/Unicom/Mobile | Backbone | Internet Enthusiasts | 10% |
|
||||
| China Telecom/Unicom/Mobile | Local | Avon Technology | 90% |
|
||||
|
||||
#### WorldWide
|
||||
|
||||
##### Tier 1
|
||||
|
||||
| ISP | Type | Data Source | Proportion |
|
||||
|:-------:|:--------:|:---------------:|:----------:|
|
||||
| Tier 1 | Backbone | IPInfo | 2% |
|
||||
| Tier 1 | Backbone | Avon Technology | 3% |
|
||||
| Tier 1 | Backbone | IPInSight | 5% |
|
||||
| Tier 1 | Local | IPInSight | 90% |
|
||||
|
||||
##### General
|
||||
|
||||
| ISP | Type | Data Source | Proportion |
|
||||
|:------:|:--------:|:-----------:|:----------:|
|
||||
| General | Backbone | IPInSight | 5% |
|
||||
| General | Local | IPInSight | 95% |
|
||||
|
||||
### IPv6 Database
|
||||
|
||||
| ISP | Type | Data Source | Proportion |
|
||||
|:---:|:----:|:----------------:|:----------:|
|
||||
| All | All | IP2Location Lite | 100% |
|
||||
|
||||
This product includes IP2Location LITE data available from <a href="https://lite.ip2location.com">https://lite.ip2location.com</a>.
|
||||
|
||||
### Others
|
||||
|
||||
Although other third-party APIs are integrated in this project, please refer to the official website of the third-party APIs for specific TOS and AUP. If you encounter IP data errors, please contact them directly to correct them.
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#xgadget-lab/nexttrace&Date)
|
||||
|
||||
230
README_zh_CN.md
Normal file
230
README_zh_CN.md
Normal file
@@ -0,0 +1,230 @@
|
||||
<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`也同样支持一些进阶功能,如 TTL 控制、并发数控制、模式切换等
|
||||
|
||||
```bash
|
||||
# 每一跳发送2个探测包
|
||||
nexttrace -q 2 www.hkix.net
|
||||
|
||||
# 无并发,每次只发送一个探测包
|
||||
nexttrace -r 1 www.hkix.net
|
||||
|
||||
# 从TTL为5开始发送探测包,直到TTL为10结束
|
||||
nexttrace -b 5 -m 10 www.decix.net
|
||||
|
||||
# 关闭IP反向解析功能
|
||||
nexttrace -n 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 -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
|
||||
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 Print Version
|
||||
-b int
|
||||
Set The Begin TTL (default 1)
|
||||
-d string
|
||||
Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight, IPAPI.com] (default "LeoMoeAPI")
|
||||
-f One-Key Fast Traceroute
|
||||
-m int
|
||||
Set the max number of hops (max TTL to be reached). (default 30)
|
||||
-n Disable IP Reverse DNS lookup
|
||||
-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)
|
||||
-report
|
||||
Route Path
|
||||
-table
|
||||
Output trace results as table
|
||||
|
||||
```
|
||||
|
||||
## 项目截图
|
||||
|
||||

|
||||
|
||||
## 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](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 | 类型 | 数据源 | 占比 |
|
||||
| :------------: | :----: | :-------: | :--: |
|
||||
| 电信/联通/移动 | 骨干网 | 网络爱好者 | 10% |
|
||||
| 电信/联通/移动 | 城域网 | 埃文科技 | 90% |
|
||||
|
||||
|
||||
- 参与骨干网维护的朋友都是网络爱好者群体,尽管我们多名志愿者通过自己的网络进行了大量的勘测,但是由于信息不足,依旧存在很多错误。
|
||||
- 对于更高精度的朋友,我们依旧强烈推荐IPIP.NET,他们开发的Besttrace是目前质量最好的路由可视化软件,我们多数爱好者能有今天这样的骨干网初步认知都是归功于他们,在此特表感谢。
|
||||
|
||||
#### WorldWide
|
||||
|
||||
##### Tier 01
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :-----: | :----: | :-------: | :--: |
|
||||
| Tier-01 | 骨干网 | IPInfo | 2% |
|
||||
| Tier-01 | 骨干网 | 埃文科技 | 3% |
|
||||
| Tier-01 | 骨干网 | IPInSight | 5% |
|
||||
| Tier-01 | 城域网 | IPInSight | 90% |
|
||||
|
||||
##### Other ISP
|
||||
|
||||
| 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 数据错误,也请直接联系他们纠错。
|
||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
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",
|
||||
},
|
||||
}
|
||||
123
fast_trace/fast_trace.go
Normal file
123
fast_trace/fast_trace.go
Normal file
@@ -0,0 +1,123 @@
|
||||
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 %s 』%s\n", printer.YELLOW_PREFIX, location, ispCollection.ISPName, printer.RESET_PREFIX)
|
||||
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)
|
||||
// 单次测试结束阻塞 3 秒,仅阻塞 TCP
|
||||
<-time.After(time.Second * 3)
|
||||
}
|
||||
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) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
15
fast_trace/fast_trace_test.go
Normal file
15
fast_trace/fast_trace_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package fastTrace
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
// ICMP Use Too Many Time to Wait So we don't test it.
|
||||
func TestTCPTrace(t *testing.T) {
|
||||
ft := FastTracer{}
|
||||
ft.TracerouteMethod = trace.TCPTrace
|
||||
ft.testCM()
|
||||
ft.testEDU()
|
||||
}
|
||||
24
go.mod
24
go.mod
@@ -1,10 +1,28 @@
|
||||
module traceroute
|
||||
module github.com/xgadget-lab/nexttrace
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/google/gopacket v1.1.19
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 // indirect
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // direct
|
||||
)
|
||||
|
||||
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/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
50
go.sum
50
go.sum
@@ -1,22 +1,56 @@
|
||||
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/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/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/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/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=
|
||||
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-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
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/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/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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.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
|
||||
}
|
||||
34
ipgeo/ipgeo.go
Normal file
34
ipgeo/ipgeo.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IPGeoData struct {
|
||||
Asnumber string
|
||||
Country string
|
||||
Prov string
|
||||
City string
|
||||
District string
|
||||
Owner string
|
||||
Isp string
|
||||
}
|
||||
|
||||
type Source = func(ip string) (*IPGeoData, error)
|
||||
|
||||
func GetSource(s string) Source {
|
||||
switch strings.ToUpper(s) {
|
||||
case "LEOMOEAPI":
|
||||
return LeoIP
|
||||
case "IP.SB":
|
||||
return IPSB
|
||||
case "IPINSIGHT":
|
||||
return IPInSight
|
||||
case "IPAPI.COM":
|
||||
return IPApiCom
|
||||
case "IPINFO":
|
||||
return IPInfo
|
||||
default:
|
||||
return LeoIP
|
||||
}
|
||||
}
|
||||
52
ipgeo/ipgeo_test.go
Normal file
52
ipgeo/ipgeo_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"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)
|
||||
}
|
||||
|
||||
func TestIPSB(t *testing.T) {
|
||||
// Not available
|
||||
//res, err := IPSB("1.1.1.1")
|
||||
//assert.Nil(t, err)
|
||||
//assert.NotNil(t, res)
|
||||
//assert.NotEmpty(t, res.Asnumber)
|
||||
//assert.NotEmpty(t, res.Isp)
|
||||
}
|
||||
|
||||
func TestIPInfo(t *testing.T) {
|
||||
res, err := IPInfo("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
// assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.City)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
}
|
||||
|
||||
func TestIPInSight(t *testing.T) {
|
||||
res, err := IPInSight("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
// 这个库有时候不提供城市信息,返回值为""
|
||||
//assert.NotEmpty(t, res.City)
|
||||
}
|
||||
|
||||
func TestIPApiCom(t *testing.T) {
|
||||
res, err := IPApiCom("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.City)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
}
|
||||
36
ipgeo/ipinfo.go
Normal file
36
ipgeo/ipinfo.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPInfo(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://ipinfo.io/" + ip + "?token=" + token.ipinfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
var country string
|
||||
|
||||
if res.Get("country").String() == "HK" || res.Get("country").String() == "TW" {
|
||||
country = "CN"
|
||||
}
|
||||
|
||||
return &IPGeoData{
|
||||
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
|
||||
}
|
||||
27
ipgeo/ipinsight.go
Normal file
27
ipgeo/ipinsight.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPInSight(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://ipinsight.io/query?ip=" + ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
return &IPGeoData{
|
||||
Country: res.Get("country_name").String(),
|
||||
City: res.Get("city_name").String(),
|
||||
Prov: res.Get("region_name").String(),
|
||||
}, nil
|
||||
}
|
||||
42
ipgeo/ipsb.go
Normal file
42
ipgeo/ipsb.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPSB(ip string) (*IPGeoData, error) {
|
||||
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
|
||||
}
|
||||
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(),
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("region").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
37
ipgeo/leo.go
Normal file
37
ipgeo/leo.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
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(),
|
||||
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
|
||||
}
|
||||
13
ipgeo/tokens.go
Normal file
13
ipgeo/tokens.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package ipgeo
|
||||
|
||||
type tokenData struct {
|
||||
ipinsight string
|
||||
ipinfo string
|
||||
ipleo string
|
||||
}
|
||||
|
||||
var token = tokenData{
|
||||
ipinsight: "",
|
||||
ipinfo: "",
|
||||
ipleo: "NextTraceDemo",
|
||||
}
|
||||
321
main.go
321
main.go
@@ -1,211 +1,138 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"traceroute/methods"
|
||||
"traceroute/methods/tcp"
|
||||
"traceroute/methods/udp"
|
||||
"os"
|
||||
"net"
|
||||
"time"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"strings"
|
||||
"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"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
)
|
||||
|
||||
type IPGeoData struct {
|
||||
Asnumber string `json:"asnumber"`
|
||||
Country string `json:"country"`
|
||||
Prov string `json:"prov"`
|
||||
City string `json:"city"`
|
||||
District string `json:"district"`
|
||||
Owner string `json:"owner"`
|
||||
Isp string `json:"isp"`
|
||||
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)
|
||||
}
|
||||
|
||||
var tcpSYNFlag = flag.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80 in TCP, 53 in UDP)")
|
||||
var port = flag.Int("p", 80, "Set SYN Traceroute Port")
|
||||
var numMeasurements = flag.Int("q", 3, "Set the number of probes per each hop.")
|
||||
var parallelRequests = flag.Int("r", 18, "Set ParallelRequests number. It should be 1 when there is a multi-routing.")
|
||||
var maxHops = flag.Int("m", 30, "Set the max number of hops (max TTL to be reached).")
|
||||
func flagApply() string {
|
||||
printer.Version()
|
||||
|
||||
target := ""
|
||||
if len(os.Args) < 2 {
|
||||
printArgHelp()
|
||||
}
|
||||
|
||||
// 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() {
|
||||
fmt.Println("BetterTrace v0.0.1 Alpha \nOwO Organiztion Leo (leo.moe) & Vincent (vincent.moe)")
|
||||
ip := domainLookUp(flagApply())
|
||||
fmt.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ip.String())
|
||||
|
||||
if (*tcpSYNFlag) {
|
||||
tcpTraceroute := tcp.New(ip, methods.TracerouteConfig{
|
||||
MaxHops: uint16(*maxHops),
|
||||
NumMeasurements: uint16(*numMeasurements),
|
||||
ParallelRequests: uint16(*parallelRequests),
|
||||
Port: *port,
|
||||
Timeout: time.Second / 2,
|
||||
})
|
||||
res, _ := tcpTraceroute.Start()
|
||||
domain := flagApply()
|
||||
|
||||
traceroutePrinter(ip, res)
|
||||
} else {
|
||||
if (*port == 80) {
|
||||
*port = 53
|
||||
}
|
||||
udpTraceroute := udp.New(ip, true, methods.TracerouteConfig{
|
||||
MaxHops: uint16(*maxHops),
|
||||
NumMeasurements: uint16(*numMeasurements),
|
||||
ParallelRequests: uint16(*parallelRequests),
|
||||
Port: *port,
|
||||
Timeout: 2 * time.Second,
|
||||
})
|
||||
res, _ := udpTraceroute.Start()
|
||||
if os.Getuid() != 0 {
|
||||
log.Fatalln("Traceroute requires root/sudo privileges.")
|
||||
}
|
||||
|
||||
traceroutePrinter(ip, res)
|
||||
}
|
||||
}
|
||||
|
||||
func traceroutePrinter(ip net.IP, res *map[uint16][]methods.TracerouteHop) {
|
||||
hopIndex := uint16(1)
|
||||
for ; hopIndex <= 29 ; {
|
||||
for k,v := range *res {
|
||||
if (k == hopIndex) {
|
||||
fmt.Print(k)
|
||||
for _,v2 := range v {
|
||||
ch := make(chan uint16)
|
||||
go hopPrinter(hopIndex, ip, v2, ch)
|
||||
hopIndex = <- ch
|
||||
}
|
||||
hopIndex = hopIndex + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func flagApply() string{
|
||||
flag.Parse()
|
||||
ipArg := flag.Args()
|
||||
if (flag.NArg() != 1) {
|
||||
fmt.Println("Args Error\nUsage : ./bettertrace [-T] [ -m <hops> ] [ -p <port> ] [ -q <probes> ] [ -r <parallelrequests> ] <hostname>")
|
||||
os.Exit(2)
|
||||
}
|
||||
return ipArg[0]
|
||||
}
|
||||
|
||||
func getIPGeo(ip string, c chan IPGeoData) {
|
||||
resp, err := http.Get("https://api.leo.moe/ip/?ip=" + ip)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
ipGeoData := IPGeoData{}
|
||||
err = json.Unmarshal(body,&ipGeoData)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
c <- ipGeoData
|
||||
}
|
||||
|
||||
func domainLookUp(host string) net.IP {
|
||||
ips, err := net.LookupIP(host)
|
||||
if (err != nil) {
|
||||
fmt.Println("Domain" + host + "Lookup Fail.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var ipSlice = []net.IP{}
|
||||
|
||||
for _, ip := range ips {
|
||||
ipSlice = append(ipSlice, ip)
|
||||
}
|
||||
if (len(ipSlice) == 1) {
|
||||
return ipSlice[0]
|
||||
} else {
|
||||
fmt.Println("Please Choose the IP You Want To TraceRoute")
|
||||
for i, ip := range ipSlice {
|
||||
fmt.Printf("%d. %s\n",i, ip)
|
||||
}
|
||||
var index int
|
||||
fmt.Printf("Your Option: ")
|
||||
fmt.Scanln(&index)
|
||||
if (index >= len(ipSlice) || index < 0) {
|
||||
fmt.Println("Your Option is invalid")
|
||||
os.Exit(3)
|
||||
}
|
||||
return ipSlice[index]
|
||||
}
|
||||
}
|
||||
|
||||
func hopPrinter(hopIndex uint16, ip net.IP, v2 methods.TracerouteHop, c chan uint16) {
|
||||
if (v2.Address == nil) {
|
||||
fmt.Println("\t*")
|
||||
} else {
|
||||
ip_str := fmt.Sprintf("%s", v2.Address)
|
||||
|
||||
ptr, err := net.LookupAddr(ip_str)
|
||||
|
||||
ch_b := make(chan IPGeoData)
|
||||
go getIPGeo(ip_str, ch_b)
|
||||
iPGeoData := <-ch_b
|
||||
|
||||
if (ip.String() == ip_str) {
|
||||
hopIndex = 30
|
||||
iPGeoData.Owner = iPGeoData.Isp
|
||||
}
|
||||
|
||||
if (strings.Index(ip_str, "9.31.") == 0 || strings.Index(ip_str, "11.72.") == 0) {
|
||||
fmt.Printf("\t%-15s %.2fms * 局域网, 腾讯云\n", v2.Address, v2.RTT.Seconds()*1000)
|
||||
c <- hopIndex
|
||||
return
|
||||
}
|
||||
|
||||
if (strings.Index(ip_str, "11.13.") == 0) {
|
||||
fmt.Printf("\t%-15s %.2fms * 局域网, 阿里云\n", v2.Address, v2.RTT.Seconds()*1000)
|
||||
c <- hopIndex
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (iPGeoData.Owner == "") {
|
||||
iPGeoData.Owner = iPGeoData.Isp
|
||||
}
|
||||
|
||||
if (iPGeoData.Asnumber == "") {
|
||||
iPGeoData.Asnumber = "*"
|
||||
} else {
|
||||
iPGeoData.Asnumber = "AS" + iPGeoData.Asnumber
|
||||
}
|
||||
|
||||
if (iPGeoData.District != "") {
|
||||
iPGeoData.City = iPGeoData.City + ", " + iPGeoData.District
|
||||
}
|
||||
|
||||
if (iPGeoData.Country == "") {
|
||||
fmt.Printf("\t%-15s %.2fms * 局域网\n", v2.Address, v2.RTT.Seconds()*1000)
|
||||
c <- hopIndex
|
||||
return
|
||||
}
|
||||
|
||||
if (iPGeoData.Prov == "" && iPGeoData.City == "") {
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("\t%-15s %.2fms %s %s, %s, %s 骨干网\n",v2.Address, v2.RTT.Seconds()*1000, iPGeoData.Asnumber, iPGeoData.Country, iPGeoData.Owner, iPGeoData.Owner)
|
||||
} else {
|
||||
fmt.Printf("\t%-15s (%s) %.2fms %s %s, %s, %s 骨干网\n",ptr[0], v2.Address, v2.RTT.Seconds()*1000, iPGeoData.Asnumber, iPGeoData.Country, iPGeoData.Owner, iPGeoData.Owner)
|
||||
}
|
||||
} else {
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("\t%-15s %.2fms %s %s, %s, %s, %s\n",v2.Address, v2.RTT.Seconds()*1000, iPGeoData.Asnumber, iPGeoData.Country, iPGeoData.Prov, iPGeoData.City, iPGeoData.Owner)
|
||||
} else {
|
||||
fmt.Printf("\t%-15s (%s) %.2fms %s %s, %s, %s, %s\n",ptr[0], v2.Address, v2.RTT.Seconds()*1000, iPGeoData.Asnumber, iPGeoData.Country, iPGeoData.Prov, iPGeoData.City, iPGeoData.Owner)
|
||||
}
|
||||
}
|
||||
}
|
||||
c <- hopIndex
|
||||
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 = ""
|
||||
|
||||
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{
|
||||
BeginHop: *beginHop,
|
||||
DestIP: ip,
|
||||
DestPort: *port,
|
||||
MaxHops: *maxHops,
|
||||
NumMeasurements: *numMeasurements,
|
||||
ParallelRequests: *parallelRequests,
|
||||
RDns: !*noRdns,
|
||||
IPGeoSource: ipgeo.GetSource(*dataOrigin),
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
if !*tablePrint {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package methods
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TracerouteHop type
|
||||
type TracerouteHop struct {
|
||||
Success bool
|
||||
Address net.Addr
|
||||
TTL uint16
|
||||
RTT *time.Duration
|
||||
}
|
||||
|
||||
type TracerouteConfig struct {
|
||||
MaxHops uint16
|
||||
NumMeasurements uint16
|
||||
ParallelRequests uint16
|
||||
|
||||
Port int
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func GetIPHeaderLength(data []byte) (int, error) {
|
||||
if len(data) < 1 {
|
||||
return 0, errors.New("received invalid IP header")
|
||||
}
|
||||
return int((data[0] & 0x0F) * 4), nil
|
||||
}
|
||||
|
||||
func GetICMPResponsePayload(data []byte) ([]byte, error) {
|
||||
length, err := GetIPHeaderLength(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) < length {
|
||||
return nil, errors.New("length of packet too short")
|
||||
}
|
||||
|
||||
return data[length:], nil
|
||||
}
|
||||
|
||||
func GetUDPSrcPort(data []byte) uint16 {
|
||||
srcPortBytes := data[:2]
|
||||
srcPort := binary.BigEndian.Uint16(srcPortBytes)
|
||||
return srcPort
|
||||
}
|
||||
|
||||
func GetTCPSeq(data []byte) uint32 {
|
||||
seqBytes := data[4:8]
|
||||
return binary.BigEndian.Uint32(seqBytes)
|
||||
}
|
||||
|
||||
func ReduceFinalResult(preliminary map[uint16][]TracerouteHop, maxHops uint16, destIP net.IP) map[uint16][]TracerouteHop {
|
||||
// reduce the results to remove all hops after the first encounter to final destination
|
||||
finalResults := map[uint16][]TracerouteHop{}
|
||||
for i := uint16(1); i < maxHops; i++ {
|
||||
foundFinal := false
|
||||
probes := preliminary[i]
|
||||
if probes == nil {
|
||||
break
|
||||
}
|
||||
finalResults[i] = []TracerouteHop{}
|
||||
for _, probe := range probes {
|
||||
if probe.Success && probe.Address.String() == destIP.String() {
|
||||
foundFinal = true
|
||||
}
|
||||
finalResults[i] = append(finalResults[i], probe)
|
||||
}
|
||||
if foundFinal {
|
||||
break
|
||||
}
|
||||
}
|
||||
return finalResults
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"traceroute/listener_channel"
|
||||
"traceroute/methods"
|
||||
"traceroute/parallel_limiter"
|
||||
"traceroute/signal"
|
||||
"traceroute/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type inflightData struct {
|
||||
start time.Time
|
||||
ttl uint16
|
||||
}
|
||||
|
||||
type results struct {
|
||||
inflightRequests sync.Map
|
||||
|
||||
results map[uint16][]methods.TracerouteHop
|
||||
resultsMu sync.Mutex
|
||||
err error
|
||||
|
||||
concurrentRequests *parallel_limiter.ParallelLimiter
|
||||
reachedFinalHop *signal.Signal
|
||||
}
|
||||
|
||||
type Traceroute struct {
|
||||
opConfig opConfig
|
||||
trcrtConfig methods.TracerouteConfig
|
||||
results results
|
||||
}
|
||||
|
||||
type opConfig struct {
|
||||
icmpConn net.PacketConn
|
||||
tcpConn net.PacketConn
|
||||
tcpMu sync.Mutex
|
||||
|
||||
destIP net.IP
|
||||
srcIP net.IP
|
||||
|
||||
wg *sync.WaitGroup
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func New(destIP net.IP, config methods.TracerouteConfig) *Traceroute {
|
||||
return &Traceroute{
|
||||
opConfig: opConfig{
|
||||
destIP: destIP,
|
||||
},
|
||||
trcrtConfig: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) Start() (*map[uint16][]methods.TracerouteHop, error) {
|
||||
tr.opConfig.ctx, tr.opConfig.cancel = context.WithCancel(context.Background())
|
||||
|
||||
tr.opConfig.srcIP, _ = util.LocalIPPort(tr.opConfig.destIP)
|
||||
|
||||
var err error
|
||||
tr.opConfig.tcpConn, err = net.ListenPacket("ip4:tcp", tr.opConfig.srcIP.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr.opConfig.icmpConn, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
tr.opConfig.wg = &wg
|
||||
|
||||
tr.results = results{
|
||||
inflightRequests: sync.Map{},
|
||||
concurrentRequests: parallel_limiter.New(int(tr.trcrtConfig.ParallelRequests)),
|
||||
reachedFinalHop: signal.New(),
|
||||
|
||||
results: map[uint16][]methods.TracerouteHop{},
|
||||
}
|
||||
|
||||
return tr.start()
|
||||
}
|
||||
|
||||
func (tr *Traceroute) timeoutLoop() {
|
||||
ticker := time.NewTicker(tr.trcrtConfig.Timeout / 4)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
tr.results.inflightRequests.Range(func(key, value interface{}) bool {
|
||||
request := value.(inflightData)
|
||||
expired := time.Since(request.start) > tr.trcrtConfig.Timeout
|
||||
if !expired {
|
||||
return true
|
||||
}
|
||||
tr.results.inflightRequests.Delete(key)
|
||||
tr.addToResult(request.ttl, methods.TracerouteHop{
|
||||
Success: false,
|
||||
TTL: request.ttl,
|
||||
})
|
||||
tr.results.concurrentRequests.Finished()
|
||||
tr.opConfig.wg.Done()
|
||||
return true
|
||||
})
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
ticker.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) addToResult(ttl uint16, hop methods.TracerouteHop) {
|
||||
tr.results.resultsMu.Lock()
|
||||
defer tr.results.resultsMu.Unlock()
|
||||
if tr.results.results[ttl] == nil {
|
||||
tr.results.results[ttl] = []methods.TracerouteHop{}
|
||||
}
|
||||
|
||||
tr.results.results[ttl] = append(tr.results.results[ttl], hop)
|
||||
}
|
||||
|
||||
func (tr *Traceroute) handleICMPMessage(msg listener_channel.ReceivedMessage, data []byte) {
|
||||
header, err := methods.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sequenceNumber := methods.GetTCPSeq(header)
|
||||
val, ok := tr.results.inflightRequests.LoadAndDelete(sequenceNumber)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
request := val.(inflightData)
|
||||
elapsed := time.Since(request.start)
|
||||
if msg.Peer.String() == tr.opConfig.destIP.String() {
|
||||
tr.results.reachedFinalHop.Signal()
|
||||
}
|
||||
tr.addToResult(request.ttl, methods.TracerouteHop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
TTL: request.ttl,
|
||||
RTT: &elapsed,
|
||||
})
|
||||
tr.results.concurrentRequests.Finished()
|
||||
tr.opConfig.wg.Done()
|
||||
}
|
||||
|
||||
func (tr *Traceroute) icmpListener() {
|
||||
lc := listener_channel.New(tr.opConfig.icmpConn)
|
||||
|
||||
defer lc.Stop()
|
||||
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
body := rm.Body.(*icmp.TimeExceeded).Data
|
||||
tr.handleICMPMessage(msg, body)
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
body := rm.Body.(*icmp.DstUnreach).Data
|
||||
tr.handleICMPMessage(msg, body)
|
||||
default:
|
||||
log.Println("received icmp message of unknown type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) tcpListener() {
|
||||
lc := listener_channel.New(tr.opConfig.tcpConn)
|
||||
|
||||
defer lc.Stop()
|
||||
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() != tr.opConfig.destIP.String() {
|
||||
continue
|
||||
}
|
||||
// Decode a packet
|
||||
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
|
||||
// Get the TCP layer from this packet
|
||||
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
|
||||
val, ok := tr.results.inflightRequests.LoadAndDelete(tcp.Ack - 1)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
request := val.(inflightData)
|
||||
tr.results.concurrentRequests.Finished()
|
||||
elapsed := time.Since(request.start)
|
||||
if msg.Peer.String() == tr.opConfig.destIP.String() {
|
||||
tr.results.reachedFinalHop.Signal()
|
||||
}
|
||||
tr.addToResult(request.ttl, methods.TracerouteHop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
TTL: request.ttl,
|
||||
RTT: &elapsed,
|
||||
})
|
||||
tr.opConfig.wg.Done()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) sendMessage(ttl uint16) {
|
||||
_, srcPort := util.LocalIPPort(tr.opConfig.destIP)
|
||||
ipHeader := &layers.IPv4{
|
||||
SrcIP: tr.opConfig.srcIP,
|
||||
DstIP: tr.opConfig.destIP,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
TTL: uint8(ttl),
|
||||
}
|
||||
|
||||
sequenceNumber := uint32(rand.Intn(math.MaxUint32))
|
||||
|
||||
tcpHeader := &layers.TCP{
|
||||
SrcPort: layers.TCPPort(srcPort),
|
||||
DstPort: layers.TCPPort(tr.trcrtConfig.Port),
|
||||
Seq: sequenceNumber,
|
||||
SYN: true,
|
||||
Window: 14600,
|
||||
}
|
||||
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
tr.opConfig.tcpMu.Lock()
|
||||
defer tr.opConfig.tcpMu.Unlock()
|
||||
err := ipv4.NewPacketConn(tr.opConfig.tcpConn).SetTTL(int(ttl))
|
||||
if err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := tr.opConfig.tcpConn.WriteTo(buf.Bytes(), &net.IPAddr{IP: tr.opConfig.destIP}); err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
tr.results.inflightRequests.Store(sequenceNumber, inflightData{start: start, ttl: ttl})
|
||||
}
|
||||
|
||||
func (tr *Traceroute) sendLoop() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
defer tr.opConfig.wg.Done()
|
||||
|
||||
for ttl := uint16(1); ttl <= tr.trcrtConfig.MaxHops; ttl++ {
|
||||
select {
|
||||
case <-tr.results.reachedFinalHop.Chan():
|
||||
return
|
||||
default:
|
||||
}
|
||||
for i := 0; i < int(tr.trcrtConfig.NumMeasurements); i++ {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case <-tr.results.concurrentRequests.Start():
|
||||
tr.opConfig.wg.Add(1)
|
||||
go tr.sendMessage(ttl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) start() (*map[uint16][]methods.TracerouteHop, error) {
|
||||
go tr.timeoutLoop()
|
||||
go tr.icmpListener()
|
||||
go tr.tcpListener()
|
||||
|
||||
tr.opConfig.wg.Add(1)
|
||||
go tr.sendLoop()
|
||||
|
||||
tr.opConfig.wg.Wait()
|
||||
tr.opConfig.cancel()
|
||||
|
||||
if tr.results.err != nil {
|
||||
return nil, tr.results.err
|
||||
}
|
||||
|
||||
result := methods.ReduceFinalResult(tr.results.results, tr.trcrtConfig.MaxHops, tr.opConfig.destIP)
|
||||
|
||||
return &result, tr.results.err
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
package udp
|
||||
|
||||
import (
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"traceroute/listener_channel"
|
||||
"traceroute/methods"
|
||||
"traceroute/methods/quic"
|
||||
"traceroute/parallel_limiter"
|
||||
"traceroute/signal"
|
||||
"traceroute/taskgroup"
|
||||
"traceroute/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
time "time"
|
||||
)
|
||||
|
||||
type inflightData struct {
|
||||
icmpMsg chan<- net.Addr
|
||||
}
|
||||
|
||||
type opConfig struct {
|
||||
quic bool
|
||||
destIP net.IP
|
||||
wg *taskgroup.TaskGroup
|
||||
|
||||
icmpConn net.PacketConn
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
type results struct {
|
||||
inflightRequests sync.Map
|
||||
|
||||
results map[uint16][]methods.TracerouteHop
|
||||
resultsMu sync.Mutex
|
||||
err error
|
||||
|
||||
concurrentRequests *parallel_limiter.ParallelLimiter
|
||||
reachedFinalHop *signal.Signal
|
||||
}
|
||||
|
||||
type Traceroute struct {
|
||||
trcrtConfig methods.TracerouteConfig
|
||||
opConfig opConfig
|
||||
results results
|
||||
}
|
||||
|
||||
func New(destIP net.IP, quic bool, config methods.TracerouteConfig) *Traceroute {
|
||||
return &Traceroute{
|
||||
opConfig: opConfig{
|
||||
quic: quic,
|
||||
destIP: destIP,
|
||||
},
|
||||
trcrtConfig: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) Start() (*map[uint16][]methods.TracerouteHop, error) {
|
||||
tr.opConfig.ctx, tr.opConfig.cancel = context.WithCancel(context.Background())
|
||||
|
||||
tr.results = results{
|
||||
inflightRequests: sync.Map{},
|
||||
concurrentRequests: parallel_limiter.New(int(tr.trcrtConfig.ParallelRequests)),
|
||||
results: map[uint16][]methods.TracerouteHop{},
|
||||
reachedFinalHop: signal.New(),
|
||||
}
|
||||
|
||||
var err error
|
||||
tr.opConfig.icmpConn, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tr.start()
|
||||
}
|
||||
|
||||
func (tr *Traceroute) addToResult(ttl uint16, hop methods.TracerouteHop) {
|
||||
tr.results.resultsMu.Lock()
|
||||
defer tr.results.resultsMu.Unlock()
|
||||
if tr.results.results[ttl] == nil {
|
||||
tr.results.results[ttl] = []methods.TracerouteHop{}
|
||||
}
|
||||
|
||||
tr.results.results[ttl] = append(tr.results.results[ttl], hop)
|
||||
}
|
||||
|
||||
func (tr *Traceroute) getUDPConn(try int) (net.IP, int, net.PacketConn) {
|
||||
srcIP, _ := util.LocalIPPort(tr.opConfig.destIP)
|
||||
|
||||
var ipString string
|
||||
|
||||
if srcIP == nil {
|
||||
ipString = ""
|
||||
} else {
|
||||
ipString = srcIP.String()
|
||||
}
|
||||
|
||||
udpConn, err := net.ListenPacket("udp", ipString+":0")
|
||||
if err != nil {
|
||||
if try > 3 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return tr.getUDPConn(try + 1)
|
||||
}
|
||||
|
||||
return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn
|
||||
}
|
||||
|
||||
func (tr *Traceroute) sendMessage(ttl uint16) {
|
||||
srcIP, srcPort, udpConn := tr.getUDPConn(0)
|
||||
|
||||
var payload []byte
|
||||
if tr.opConfig.quic {
|
||||
payload = quic.GenerateWithRandomIds()
|
||||
} else {
|
||||
ipHeader := &layers.IPv4{
|
||||
SrcIP: srcIP,
|
||||
DstIP: tr.opConfig.destIP,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
TTL: uint8(ttl),
|
||||
}
|
||||
|
||||
udpHeader := &layers.UDP{
|
||||
SrcPort: layers.UDPPort(srcPort),
|
||||
DstPort: layers.UDPPort(tr.trcrtConfig.Port),
|
||||
}
|
||||
_ = udpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, udpHeader, gopacket.Payload("HAJSFJHKAJSHFKJHAJKFHKASHKFHHKAFKHFAHSJK")); err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
payload = buf.Bytes()
|
||||
}
|
||||
|
||||
err := ipv4.NewPacketConn(udpConn).SetTTL(int(ttl))
|
||||
if err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
icmpMsg := make(chan net.Addr, 1)
|
||||
udpMsg := make(chan net.Addr, 1)
|
||||
|
||||
start := time.Now()
|
||||
if _, err := udpConn.WriteTo(payload, &net.UDPAddr{IP: tr.opConfig.destIP, Port: tr.trcrtConfig.Port}); err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
inflight := inflightData{
|
||||
icmpMsg: icmpMsg,
|
||||
}
|
||||
|
||||
tr.results.inflightRequests.Store(uint16(srcPort), inflight)
|
||||
|
||||
go func() {
|
||||
reply := make([]byte, 1500)
|
||||
_, peer, err := udpConn.ReadFrom(reply)
|
||||
if err != nil {
|
||||
// probably because we closed the connection
|
||||
return
|
||||
}
|
||||
udpMsg <- peer
|
||||
}()
|
||||
|
||||
select {
|
||||
case peer := <-icmpMsg:
|
||||
rtt := time.Since(start)
|
||||
if peer.(*net.IPAddr).IP.Equal(tr.opConfig.destIP) {
|
||||
tr.results.reachedFinalHop.Signal()
|
||||
}
|
||||
tr.addToResult(ttl, methods.TracerouteHop{
|
||||
Success: true,
|
||||
Address: peer,
|
||||
TTL: ttl,
|
||||
RTT: &rtt,
|
||||
})
|
||||
case peer := <-udpMsg:
|
||||
rtt := time.Since(start)
|
||||
ip := peer.(*net.UDPAddr).IP
|
||||
if ip.Equal(tr.opConfig.destIP) {
|
||||
tr.results.reachedFinalHop.Signal()
|
||||
}
|
||||
tr.addToResult(ttl, methods.TracerouteHop{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: ip},
|
||||
TTL: ttl,
|
||||
RTT: &rtt,
|
||||
})
|
||||
case <-time.After(tr.trcrtConfig.Timeout):
|
||||
tr.addToResult(ttl, methods.TracerouteHop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: ttl,
|
||||
RTT: nil,
|
||||
})
|
||||
}
|
||||
|
||||
tr.results.inflightRequests.Delete(uint16(srcPort))
|
||||
udpConn.Close()
|
||||
tr.results.concurrentRequests.Finished()
|
||||
tr.opConfig.wg.Done()
|
||||
}
|
||||
|
||||
func (tr *Traceroute) handleICMPMessage(msg listener_channel.ReceivedMessage, data []byte) {
|
||||
header, err := methods.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
srcPort := methods.GetUDPSrcPort(header)
|
||||
val, ok := tr.results.inflightRequests.LoadAndDelete(srcPort)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
request := val.(inflightData)
|
||||
request.icmpMsg <- msg.Peer
|
||||
}
|
||||
|
||||
func (tr *Traceroute) icmpListener() {
|
||||
lc := listener_channel.New(tr.opConfig.icmpConn)
|
||||
|
||||
defer lc.Stop()
|
||||
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
body := rm.Body.(*icmp.TimeExceeded).Data
|
||||
tr.handleICMPMessage(msg, body)
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
body := rm.Body.(*icmp.DstUnreach).Data
|
||||
tr.handleICMPMessage(msg, body)
|
||||
default:
|
||||
log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) sendLoop() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
for ttl := uint16(1); ttl <= tr.trcrtConfig.MaxHops; ttl++ {
|
||||
select {
|
||||
case <-tr.results.reachedFinalHop.Chan():
|
||||
return
|
||||
default:
|
||||
}
|
||||
for i := 0; i < int(tr.trcrtConfig.NumMeasurements); i++ {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case <-tr.results.concurrentRequests.Start():
|
||||
tr.opConfig.wg.Add()
|
||||
go tr.sendMessage(ttl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) start() (*map[uint16][]methods.TracerouteHop, error) {
|
||||
go tr.icmpListener()
|
||||
|
||||
wg := taskgroup.New()
|
||||
tr.opConfig.wg = wg
|
||||
|
||||
tr.sendLoop()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
tr.opConfig.cancel()
|
||||
tr.opConfig.icmpConn.Close()
|
||||
|
||||
if tr.results.err != nil {
|
||||
return nil, tr.results.err
|
||||
}
|
||||
|
||||
result := methods.ReduceFinalResult(tr.results.results, tr.trcrtConfig.MaxHops, tr.opConfig.destIP)
|
||||
|
||||
return &result, tr.results.err
|
||||
}
|
||||
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
|
||||
@@ -1,52 +0,0 @@
|
||||
package parallel_limiter
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ParallelLimiter struct {
|
||||
maxCount int
|
||||
|
||||
mu sync.Mutex
|
||||
currentRunning int
|
||||
|
||||
waiting []chan struct{}
|
||||
}
|
||||
|
||||
func New(count int) *ParallelLimiter {
|
||||
return &ParallelLimiter{
|
||||
maxCount: count,
|
||||
|
||||
currentRunning: 0,
|
||||
waiting: []chan struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParallelLimiter) Start() chan struct{} {
|
||||
p.mu.Lock()
|
||||
if p.currentRunning+1 > p.maxCount {
|
||||
waitChan := make(chan struct{})
|
||||
p.waiting = append(p.waiting, waitChan)
|
||||
p.mu.Unlock()
|
||||
return waitChan
|
||||
}
|
||||
p.currentRunning++
|
||||
p.mu.Unlock()
|
||||
instantResolveChan := make(chan struct{})
|
||||
go func() {
|
||||
instantResolveChan <- struct{}{}
|
||||
}()
|
||||
return instantResolveChan
|
||||
}
|
||||
|
||||
func (p *ParallelLimiter) Finished() {
|
||||
p.mu.Lock()
|
||||
if len(p.waiting) > 0 {
|
||||
first := p.waiting[0]
|
||||
p.waiting = p.waiting[1:]
|
||||
first <- struct{}{}
|
||||
p.currentRunning++
|
||||
}
|
||||
p.currentRunning--
|
||||
p.mu.Unlock()
|
||||
}
|
||||
25
printer/basic.go
Normal file
25
printer/basic.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
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) {
|
||||
fmt.Println("IP Geo Data Provider: " + dataOrigin)
|
||||
|
||||
if ip.String() == domain {
|
||||
fmt.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ip.String())
|
||||
} else {
|
||||
fmt.Printf("traceroute to %s (%s), 30 hops max, 32 byte packets\n", ip.String(), domain)
|
||||
}
|
||||
}
|
||||
109
printer/printer.go
Normal file
109
printer/printer.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
)
|
||||
|
||||
// var dataOrigin string
|
||||
|
||||
// func TraceroutePrinter(res *trace.Result) {
|
||||
// for i, hop := range res.Hops {
|
||||
// fmt.Print(i + 1)
|
||||
// for _, h := range hop {
|
||||
// HopPrinter(h)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
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 {
|
||||
txt := "\t"
|
||||
|
||||
if h.Hostname == "" {
|
||||
txt += fmt.Sprint(h.Address, " ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
|
||||
} else {
|
||||
txt += fmt.Sprint(h.Hostname, " (", h.Address, ") ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
|
||||
}
|
||||
|
||||
if h.Geo != nil {
|
||||
txt += " " + formatIpGeoData(h.Address.String(), h.Geo)
|
||||
}
|
||||
switch info {
|
||||
case IXP:
|
||||
fmt.Print(CYAN_PREFIX)
|
||||
case PoP:
|
||||
fmt.Print(CYAN_PREFIX)
|
||||
case Peer:
|
||||
fmt.Print(YELLOW_PREFIX)
|
||||
case Aboard:
|
||||
fmt.Print(GREEN_PREFIX)
|
||||
}
|
||||
|
||||
fmt.Println(txt)
|
||||
|
||||
if info != General {
|
||||
fmt.Print(RESET_PREFIX)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
|
||||
var res = make([]string, 0, 10)
|
||||
|
||||
if data.Asnumber == "" {
|
||||
res = append(res, "*")
|
||||
} else {
|
||||
res = append(res, "AS"+data.Asnumber)
|
||||
}
|
||||
|
||||
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
|
||||
// TODO: 移动IDC判断到Hop.fetchIPData函数,减少API调用
|
||||
if strings.HasPrefix(ip, "9.") {
|
||||
res = append(res, "LAN Address")
|
||||
} else if strings.HasPrefix(ip, "11.") {
|
||||
res = append(res, "LAN Address")
|
||||
} else if data.Country == "" {
|
||||
res = append(res, "LAN Address")
|
||||
} else {
|
||||
// 有些IP的归属信息为空,这个时候将ISP的信息填入
|
||||
if data.Owner == "" {
|
||||
data.Owner = data.Isp
|
||||
}
|
||||
if data.Prov == "" && data.City == "" {
|
||||
// anyCast或是骨干网数据不应该有国家信息
|
||||
data.Owner = data.Owner + ", " + data.Owner
|
||||
} else {
|
||||
// 非骨干网正常填入IP的国家信息数据
|
||||
res = append(res, data.Country)
|
||||
}
|
||||
|
||||
if data.Prov != "" {
|
||||
res = append(res, data.Prov)
|
||||
}
|
||||
if data.City != "" {
|
||||
res = append(res, data.City)
|
||||
}
|
||||
|
||||
if data.Owner != "" {
|
||||
res = append(res, data.Owner)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(res, ", ")
|
||||
}
|
||||
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)
|
||||
}
|
||||
104
printer/realtime_printer.go
Normal file
104
printer/realtime_printer.go
Normal 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 RealtimePrinter(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])
|
||||
}
|
||||
}
|
||||
127
printer/tableprinter.go
Normal file
127
printer/tableprinter.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/rodaine/table"
|
||||
)
|
||||
|
||||
type rowData struct {
|
||||
Hop string
|
||||
IP string
|
||||
Latency string
|
||||
Asnumber string
|
||||
Country string
|
||||
Prov string
|
||||
City string
|
||||
District string
|
||||
Owner string
|
||||
}
|
||||
|
||||
func TracerouteTablePrinter(res *trace.Result) {
|
||||
// 初始化表格
|
||||
tbl := New()
|
||||
for _, hop := range res.Hops {
|
||||
for k, h := range hop {
|
||||
data := tableDataGenerator(h)
|
||||
if k > 0 {
|
||||
data.Hop = ""
|
||||
}
|
||||
if data.Country == "" && data.Prov == "" && data.City == "" {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, "", data.Owner)
|
||||
} else {
|
||||
if data.City != "" {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country+", "+data.Prov+", "+data.City, data.Owner)
|
||||
} else {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country + ", " + data.Prov, data.Owner)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// 打印表格
|
||||
tbl.Print()
|
||||
}
|
||||
|
||||
func New() table.Table {
|
||||
// 初始化表格
|
||||
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
|
||||
columnFmt := color.New(color.FgYellow).SprintfFunc()
|
||||
|
||||
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Location", "Owner")
|
||||
tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
|
||||
return tbl
|
||||
}
|
||||
|
||||
func tableDataGenerator(h trace.Hop) *rowData {
|
||||
if h.Address == nil {
|
||||
return &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: "*",
|
||||
}
|
||||
} else {
|
||||
lantency := fmt.Sprintf("%.2fms", h.RTT.Seconds()*1000)
|
||||
IP := h.Address.String()
|
||||
|
||||
if strings.HasPrefix(IP, "9.") {
|
||||
return &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Country: "LAN Address",
|
||||
Prov: "LAN Address",
|
||||
Owner: "",
|
||||
}
|
||||
} else if strings.HasPrefix(IP, "11.") {
|
||||
return &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Country: "LAN Address",
|
||||
Prov: "LAN Address",
|
||||
Owner: "",
|
||||
}
|
||||
}
|
||||
|
||||
if h.Hostname != "" {
|
||||
IP = fmt.Sprint(h.Hostname, " (", IP, ") ")
|
||||
}
|
||||
|
||||
if h.Geo == nil {
|
||||
h.Geo = &ipgeo.IPGeoData{}
|
||||
}
|
||||
|
||||
r := &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Asnumber: h.Geo.Asnumber,
|
||||
Country: h.Geo.Country,
|
||||
Prov: h.Geo.Prov,
|
||||
City: h.Geo.City,
|
||||
District: h.Geo.District,
|
||||
Owner: h.Geo.Owner,
|
||||
}
|
||||
|
||||
if h.Geo == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
if h.Geo.Owner == "" {
|
||||
h.Geo.Owner = h.Geo.Isp
|
||||
}
|
||||
r.Asnumber = h.Geo.Asnumber
|
||||
r.Country = h.Geo.Country
|
||||
r.Prov = h.Geo.Prov
|
||||
r.City = h.Geo.City
|
||||
r.District = h.Geo.District
|
||||
r.Owner = h.Geo.Owner
|
||||
return r
|
||||
}
|
||||
}
|
||||
183
reporter/reporter.go
Normal file
183
reporter/reporter.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
type Reporter interface {
|
||||
Print()
|
||||
}
|
||||
|
||||
func New(rs *trace.Result, ip string) Reporter {
|
||||
experimentTag()
|
||||
r := reporter{
|
||||
routeResult: rs,
|
||||
targetIP: ip,
|
||||
}
|
||||
return &r
|
||||
}
|
||||
|
||||
type reporter struct {
|
||||
targetTTL uint16
|
||||
targetIP string
|
||||
routeReport map[uint16][]routeReportNode
|
||||
routeReportLock sync.Mutex
|
||||
routeResult *trace.Result
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
type routeReportNode struct {
|
||||
asn string
|
||||
isp string
|
||||
geo []string
|
||||
ix bool
|
||||
}
|
||||
|
||||
func experimentTag() {
|
||||
fmt.Println("Route-Path 功能实验室")
|
||||
}
|
||||
|
||||
func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData, ttl uint16) {
|
||||
|
||||
var success 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
|
||||
} else {
|
||||
rpn.ix = false
|
||||
}
|
||||
}
|
||||
// TODO: 这种写法不好,后面再重构一下
|
||||
// 判断反向解析的域名中又或者是IP地理位置数据库中,是否出现了 IX
|
||||
if strings.Contains(strings.ToLower(ipGeoData.Isp), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Isp), "ix") || strings.Contains(strings.ToLower(ipGeoData.Owner), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Owner), "ix") {
|
||||
rpn.ix = true
|
||||
}
|
||||
|
||||
// TODO: 正则判断POP并且提取带宽大小等信息
|
||||
|
||||
// CN2 需要特殊处理,因为他们很多没有ASN
|
||||
// 但是目前这种写法是不规范的,属于凭空标记4809的IP
|
||||
// TODO: 用更好的方式显示 CN2 骨干网的路由 Path
|
||||
if strings.HasPrefix(ip, "59.43") {
|
||||
rpn.asn = "4809"
|
||||
} else {
|
||||
rpn.asn = ipGeoData.Asnumber
|
||||
}
|
||||
|
||||
// 无论最后一跳是否为存在地理位置信息(AnyCast),都应该给予显示
|
||||
if (ipGeoData.Country == "" || ipGeoData.Country == "LAN Address" || ipGeoData.Country == "-") && ip != r.targetIP {
|
||||
success = false
|
||||
} else {
|
||||
if ipGeoData.City == "" {
|
||||
rpn.geo = []string{ipGeoData.Country, ipGeoData.Prov}
|
||||
} else {
|
||||
rpn.geo = []string{ipGeoData.Country, ipGeoData.City}
|
||||
}
|
||||
}
|
||||
if ipGeoData.Asnumber == "" {
|
||||
rpn.asn = "*"
|
||||
}
|
||||
|
||||
if ipGeoData.Isp == "" {
|
||||
rpn.isp = ipGeoData.Owner
|
||||
} else {
|
||||
rpn.isp = ipGeoData.Isp
|
||||
}
|
||||
|
||||
// 有效记录
|
||||
if success {
|
||||
// 锁住资源,防止同时写panic
|
||||
r.routeReportLock.Lock()
|
||||
// 添加到MAP中
|
||||
r.routeReport[ttl] = append(r.routeReport[ttl], rpn)
|
||||
// 写入完成,解锁释放资源给其他协程
|
||||
r.routeReportLock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reporter) InitialBaseData() Reporter {
|
||||
reportNodes := map[uint16][]routeReportNode{}
|
||||
|
||||
r.routeReport = reportNodes
|
||||
r.targetTTL = uint16(len(r.routeResult.Hops))
|
||||
|
||||
for i := uint16(0); i < r.targetTTL; i++ {
|
||||
traceHop := r.routeResult.Hops[i][0]
|
||||
if traceHop.Success {
|
||||
currentIP := traceHop.Address.String()
|
||||
r.wg.Add(1)
|
||||
go r.generateRouteReportNode(currentIP, *traceHop.Geo, i)
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有的子协程运行完毕
|
||||
r.wg.Wait()
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *reporter) Print() {
|
||||
var beforeActiveTTL uint16 = 0
|
||||
r.InitialBaseData()
|
||||
// 尝试首个有效 TTL
|
||||
for i := uint16(0); i < r.targetTTL; i++ {
|
||||
if len(r.routeReport[i]) != 0 {
|
||||
beforeActiveTTL = i
|
||||
// 找到以后便不再循环
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := beforeActiveTTL; i < r.targetTTL; i++ {
|
||||
// 计算该TTL内的数据长度,如果为0,则代表没有有效数据
|
||||
if len(r.routeReport[i]) == 0 {
|
||||
// 跳过改跃点的数据整理
|
||||
continue
|
||||
}
|
||||
nodeReport := r.routeReport[i][0]
|
||||
|
||||
if i == beforeActiveTTL {
|
||||
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||||
} else {
|
||||
nodeReportBefore := r.routeReport[beforeActiveTTL][0]
|
||||
// ASN 相同,同个 ISP 内部的数据传递
|
||||
if nodeReportBefore.asn == nodeReport.asn {
|
||||
// Same ASN but Coutry or City Changed
|
||||
if nodeReportBefore.geo[0] != nodeReport.geo[0] {
|
||||
fmt.Printf("』→ %s『%s", nodeReport.geo[0], nodeReport.geo[1])
|
||||
} else {
|
||||
if nodeReportBefore.geo[1] != nodeReport.geo[1] {
|
||||
fmt.Printf(" → %s", nodeReport.geo[1])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ASN 不同,跨 ISP 的数据传递,这里可能会出现 POP、IP Transit、Peer、Exchange
|
||||
fmt.Printf("』」")
|
||||
if int(i) != len(r.routeReport)+1 {
|
||||
// 部分 Shell 客户端可能无法很好的展示这个特殊字符
|
||||
// TODO: 寻找其他替代字符
|
||||
fmt.Printf("\n ╭╯\n ╰")
|
||||
}
|
||||
if nodeReport.ix {
|
||||
fmt.Printf("AS%s \033[42;37mIXP\033[0m %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||||
} else {
|
||||
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
// 标记为最新的一个有效跃点
|
||||
beforeActiveTTL = i
|
||||
}
|
||||
fmt.Println("』」")
|
||||
}
|
||||
115
reporter/reporter_test.go
Normal file
115
reporter/reporter_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
var testResult = &trace.Result{
|
||||
Hops: [][]trace.Hop{
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "4808",
|
||||
Country: "中国",
|
||||
Prov: "北京市",
|
||||
City: "北京市",
|
||||
District: "北京市",
|
||||
Owner: "",
|
||||
Isp: "中国联通",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("114.249.16.1")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "4808",
|
||||
Country: "中国",
|
||||
Prov: "北京市",
|
||||
City: "北京市",
|
||||
District: "北京市",
|
||||
Owner: "",
|
||||
Isp: "中国联通",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("219.158.5.150")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "4837",
|
||||
Country: "中国",
|
||||
Prov: "",
|
||||
City: "",
|
||||
District: "",
|
||||
Owner: "",
|
||||
Isp: "中国联通",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("62.115.125.160")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "1299",
|
||||
Country: "Sweden",
|
||||
Prov: "Stockholm County",
|
||||
City: "Stockholm",
|
||||
District: "",
|
||||
Owner: "",
|
||||
Isp: "Telia Company AB",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: net.ParseIP("213.226.68.73")},
|
||||
Hostname: "test",
|
||||
TTL: 0,
|
||||
RTT: 10 * time.Millisecond,
|
||||
Error: nil,
|
||||
Geo: &ipgeo.IPGeoData{
|
||||
Asnumber: "56630",
|
||||
Country: "Germany",
|
||||
Prov: "Hesse, Frankfurt",
|
||||
City: "",
|
||||
District: "",
|
||||
Owner: "",
|
||||
Isp: "Melbikomas UAB",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
r := New(testResult, "213.226.68.73")
|
||||
r.Print()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
189
trace/icmp_ipv4.go
Normal file
189
trace/icmp_ipv4.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type ICMPTracer struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
t.icmpListen, err = net.ListenPacket("ip4:1", "0.0.0.0")
|
||||
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 *ICMPTracer) listenICMP() {
|
||||
lc := NewPacketListener(t.icmpListen, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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.resCh <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) send(ttl int) error {
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
icmpHeader := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
},
|
||||
}
|
||||
|
||||
ipv4.NewPacketConn(t.icmpListen).SetTTL(ttl)
|
||||
|
||||
wb, err := icmpHeader.Marshal(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := t.icmpListen.WriteTo(wb, &net.IPAddr{IP: t.DestIP}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-t.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
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package listener_channel
|
||||
package trace
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
@@ -13,21 +13,19 @@ type ReceivedMessage struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
type ListenerChannel struct {
|
||||
type PacketListener struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
Conn net.PacketConn
|
||||
Messages chan ReceivedMessage
|
||||
}
|
||||
|
||||
func New(conn net.PacketConn) *ListenerChannel {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
func NewPacketListener(conn net.PacketConn, ctx context.Context) *PacketListener {
|
||||
results := make(chan ReceivedMessage, 50)
|
||||
|
||||
return &ListenerChannel{Conn: conn, ctx: ctx, cancel: cancel, Messages: results}
|
||||
return &PacketListener{Conn: conn, ctx: ctx, Messages: results}
|
||||
}
|
||||
|
||||
func (l *ListenerChannel) Start() {
|
||||
func (l *PacketListener) Start() {
|
||||
for {
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
@@ -55,7 +53,3 @@ func (l *ListenerChannel) Start() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ListenerChannel) Stop() {
|
||||
l.cancel()
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
287
trace/tcp_ipv4.go
Normal file
287
trace/tcp_ipv4.go
Normal file
@@ -0,0 +1,287 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type TCPTracer struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestLock sync.Mutex
|
||||
SrcIP net.IP
|
||||
icmp net.PacketConn
|
||||
tcp net.PacketConn
|
||||
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
|
||||
sem *semaphore.Weighted
|
||||
}
|
||||
|
||||
func (t *TCPTracer) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
|
||||
|
||||
var err error
|
||||
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")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
defer t.icmp.Close()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
go t.listenTCP()
|
||||
|
||||
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
|
||||
|
||||
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
|
||||
// 如果到达最终跳,则退出
|
||||
if t.final != -1 && ttl > t.final {
|
||||
break
|
||||
}
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (t *TCPTracer) listenICMP() {
|
||||
lc := NewPacketListener(t.icmp, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
dstip := net.IP(msg.Msg[24:28])
|
||||
if dstip.Equal(t.DestIP) {
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
|
||||
default:
|
||||
//log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @title listenTCP
|
||||
// @description 监听TCP的响应数据包
|
||||
func (t *TCPTracer) listenTCP() {
|
||||
lc := NewPacketListener(t.tcp, t.ctx)
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() != t.DestIP.String() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 解包
|
||||
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
|
||||
// 从包中获取TCP layer信息
|
||||
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
// 取得目标主机的Sequence Number
|
||||
|
||||
if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok {
|
||||
// 最后一跳
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
|
||||
header, err := util.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sequenceNumber := util.GetTCPSeq(header)
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[int(sequenceNumber)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *TCPTracer) send(ttl int) error {
|
||||
err := t.sem.Acquire(context.Background(), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.sem.Release(1)
|
||||
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
// 随机种子
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, srcPort := util.LocalIPPort(t.DestIP)
|
||||
ipHeader := &layers.IPv4{
|
||||
SrcIP: t.SrcIP,
|
||||
DstIP: t.DestIP,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
TTL: uint8(ttl),
|
||||
}
|
||||
// 使用Uint16兼容32位系统,防止在rand的时候因使用int32而溢出
|
||||
sequenceNumber := uint32(r.Intn(math.MaxUint16))
|
||||
tcpHeader := &layers.TCP{
|
||||
SrcPort: layers.TCPPort(srcPort),
|
||||
DstPort: layers.TCPPort(t.DestPort),
|
||||
Seq: sequenceNumber,
|
||||
SYN: true,
|
||||
Window: 14600,
|
||||
}
|
||||
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipv4.NewPacketConn(t.tcp).SetTTL(ttl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := t.tcp.WriteTo(buf.Bytes(), &net.IPAddr{IP: t.DestIP}); err != nil {
|
||||
return err
|
||||
}
|
||||
t.inflightRequestLock.Lock()
|
||||
hopCh := make(chan Hop)
|
||||
t.inflightRequest[int(sequenceNumber)] = hopCh
|
||||
t.inflightRequestLock.Unlock()
|
||||
/*
|
||||
// 这里属于 2个Sender,N个Reciever的情况,在哪里关闭Channel都容易导致Panic
|
||||
defer func() {
|
||||
t.inflightRequestLock.Lock()
|
||||
close(hopCh)
|
||||
delete(t.inflightRequest, srcPort)
|
||||
t.inflightRequestLock.Unlock()
|
||||
}()
|
||||
*/
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-hopCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
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
|
||||
}
|
||||
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, ", ")
|
||||
}
|
||||
127
trace/trace.go
Normal file
127
trace/trace.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidMethod = errors.New("invalid method")
|
||||
ErrTracerouteExecuted = errors.New("traceroute already executed")
|
||||
ErrHopLimitTimeout = errors.New("hop timeout")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BeginHop int
|
||||
MaxHops int
|
||||
NumMeasurements int
|
||||
ParallelRequests int
|
||||
Timeout time.Duration
|
||||
DestIP net.IP
|
||||
DestPort int
|
||||
Quic bool
|
||||
IPGeoSource ipgeo.Source
|
||||
RDns bool
|
||||
RealtimePrinter func(res *Result, ttl int)
|
||||
}
|
||||
|
||||
type Method string
|
||||
|
||||
const (
|
||||
ICMPTrace Method = "icmp"
|
||||
UDPTrace Method = "udp"
|
||||
TCPTrace Method = "tcp"
|
||||
)
|
||||
|
||||
type Tracer interface {
|
||||
Execute() (*Result, error)
|
||||
}
|
||||
|
||||
func Traceroute(method Method, config Config) (*Result, error) {
|
||||
var tracer Tracer
|
||||
|
||||
if config.MaxHops == 0 {
|
||||
config.MaxHops = 30
|
||||
}
|
||||
if config.NumMeasurements == 0 {
|
||||
config.NumMeasurements = 3
|
||||
}
|
||||
if config.ParallelRequests == 0 {
|
||||
config.ParallelRequests = config.NumMeasurements * 5
|
||||
}
|
||||
|
||||
switch method {
|
||||
case ICMPTrace:
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &ICMPTracer{Config: config}
|
||||
} else {
|
||||
tracer = &ICMPTracerv6{Config: config}
|
||||
}
|
||||
|
||||
case UDPTrace:
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &UDPTracer{Config: config}
|
||||
} else {
|
||||
return nil, errors.New("IPv6 UDP Traceroute is not supported")
|
||||
}
|
||||
case TCPTrace:
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &TCPTracer{Config: config}
|
||||
} else {
|
||||
// tracer = &TCPTracerv6{Config: config}
|
||||
return nil, errors.New("IPv6 TCP Traceroute is not supported")
|
||||
}
|
||||
default:
|
||||
return &Result{}, ErrInvalidMethod
|
||||
}
|
||||
return tracer.Execute()
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Hops [][]Hop
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (s *Result) add(hop Hop) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
k := hop.TTL - 1
|
||||
for len(s.Hops) < hop.TTL {
|
||||
s.Hops = append(s.Hops, make([]Hop, 0))
|
||||
}
|
||||
s.Hops[k] = append(s.Hops[k], hop)
|
||||
|
||||
}
|
||||
|
||||
func (s *Result) reduce(final int) {
|
||||
if final > 0 && final < len(s.Hops) {
|
||||
s.Hops = s.Hops[:final]
|
||||
}
|
||||
}
|
||||
|
||||
type Hop struct {
|
||||
Success bool
|
||||
Address net.Addr
|
||||
Hostname string
|
||||
TTL int
|
||||
RTT time.Duration
|
||||
Error error
|
||||
Geo *ipgeo.IPGeoData
|
||||
}
|
||||
|
||||
func (h *Hop) fetchIPData(c Config) (err error) {
|
||||
if c.RDns && h.Hostname == "" {
|
||||
ptr, err := net.LookupAddr(h.Address.String())
|
||||
if err == nil && len(ptr) > 0 {
|
||||
h.Hostname = ptr[0]
|
||||
}
|
||||
}
|
||||
if c.IPGeoSource != nil && h.Geo == nil {
|
||||
h.Geo, err = c.IPGeoSource(h.Address.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
270
trace/udp.go
Normal file
270
trace/udp.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type UDPTracer struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestLock sync.Mutex
|
||||
|
||||
icmp net.PacketConn
|
||||
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
|
||||
sem *semaphore.Weighted
|
||||
}
|
||||
|
||||
func (t *UDPTracer) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
var err error
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
defer t.icmp.Close()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
|
||||
for ttl := 1; ttl <= t.MaxHops; ttl++ {
|
||||
// 如果到达最终跳,则退出
|
||||
if t.final != -1 && ttl > t.final {
|
||||
break
|
||||
}
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (t *UDPTracer) listenICMP() {
|
||||
lc := NewPacketListener(t.icmp, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
|
||||
default:
|
||||
log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *UDPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
|
||||
header, err := util.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
srcPort := util.GetUDPSrcPort(header)
|
||||
//t.inflightRequestLock.Lock()
|
||||
//defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[int(srcPort)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *UDPTracer) getUDPConn(try int) (net.IP, int, net.PacketConn) {
|
||||
srcIP, _ := util.LocalIPPort(t.DestIP)
|
||||
|
||||
var ipString string
|
||||
if srcIP == nil {
|
||||
ipString = ""
|
||||
} else {
|
||||
ipString = srcIP.String()
|
||||
}
|
||||
|
||||
udpConn, err := net.ListenPacket("udp", ipString+":0")
|
||||
if err != nil {
|
||||
if try > 3 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return t.getUDPConn(try + 1)
|
||||
}
|
||||
return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn
|
||||
}
|
||||
|
||||
func (t *UDPTracer) send(ttl int) error {
|
||||
err := t.sem.Acquire(context.Background(), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.sem.Release(1)
|
||||
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
srcIP, srcPort, udpConn := t.getUDPConn(0)
|
||||
|
||||
var payload []byte
|
||||
if t.Quic {
|
||||
payload = GenerateQuicPayloadWithRandomIds()
|
||||
} else {
|
||||
ipHeader := &layers.IPv4{
|
||||
SrcIP: srcIP,
|
||||
DstIP: t.DestIP,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
TTL: uint8(ttl),
|
||||
}
|
||||
|
||||
udpHeader := &layers.UDP{
|
||||
SrcPort: layers.UDPPort(srcPort),
|
||||
DstPort: layers.UDPPort(t.DestPort),
|
||||
}
|
||||
_ = udpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, udpHeader, gopacket.Payload("HAJSFJHKAJSHFKJHAJKFHKASHKFHHKAFKHFAHSJK")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload = buf.Bytes()
|
||||
}
|
||||
|
||||
err = ipv4.NewPacketConn(udpConn).SetTTL(ttl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := udpConn.WriteTo(payload, &net.UDPAddr{IP: t.DestIP, Port: t.DestPort}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 在对inflightRequest进行写操作的时候应该加锁保护,以免多个goroutine协程试图同时写入造成panic
|
||||
t.inflightRequestLock.Lock()
|
||||
hopCh := make(chan Hop)
|
||||
t.inflightRequest[srcPort] = hopCh
|
||||
t.inflightRequestLock.Unlock()
|
||||
defer func() {
|
||||
t.inflightRequestLock.Lock()
|
||||
close(hopCh)
|
||||
delete(t.inflightRequest, srcPort)
|
||||
t.inflightRequestLock.Unlock()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
reply := make([]byte, 1500)
|
||||
_, peer, err := udpConn.ReadFrom(reply)
|
||||
if err != nil {
|
||||
// probably because we closed the connection
|
||||
return
|
||||
}
|
||||
hopCh <- Hop{
|
||||
Success: true,
|
||||
Address: peer,
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-hopCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.UDPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
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
|
||||
}
|
||||
37
util/trace.go
Normal file
37
util/trace.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func GetIPHeaderLength(data []byte) (int, error) {
|
||||
if len(data) < 1 {
|
||||
return 0, errors.New("received invalid IP header")
|
||||
}
|
||||
return int((data[0] & 0x0F) * 4), nil
|
||||
}
|
||||
|
||||
func GetICMPResponsePayload(data []byte) ([]byte, error) {
|
||||
length, err := GetIPHeaderLength(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) < length {
|
||||
return nil, errors.New("length of packet too short")
|
||||
}
|
||||
|
||||
return data[length:], nil
|
||||
}
|
||||
|
||||
func GetUDPSrcPort(data []byte) uint16 {
|
||||
srcPortBytes := data[:2]
|
||||
srcPort := binary.BigEndian.Uint16(srcPortBytes)
|
||||
return srcPort
|
||||
}
|
||||
|
||||
func GetTCPSeq(data []byte) uint32 {
|
||||
seqBytes := data[4:8]
|
||||
return binary.BigEndian.Uint32(seqBytes)
|
||||
}
|
||||
50
util/util.go
50
util/util.go
@@ -1,8 +1,10 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
// get the local ip and port based on our destination ip
|
||||
@@ -22,3 +24,51 @@ func LocalIPPort(dstip net.IP) (net.IP, int) {
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func DomainLookUp(host string, ipv4Only bool) net.IP {
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
fmt.Println("Domain " + host + " Lookup Fail.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var ipSlice = []net.IP{}
|
||||
var ipv6Flag = false
|
||||
|
||||
for _, ip := range ips {
|
||||
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 TCP/UDP Traceroute is not supported right now.")
|
||||
if len(ipSlice) == 0 {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ipSlice) == 1 {
|
||||
return ipSlice[0]
|
||||
} else {
|
||||
fmt.Println("Please Choose the IP You Want To TraceRoute")
|
||||
for i, ip := range ipSlice {
|
||||
fmt.Printf("%d. %s\n", i, ip)
|
||||
}
|
||||
var index int
|
||||
fmt.Printf("Your Option: ")
|
||||
fmt.Scanln(&index)
|
||||
if index >= len(ipSlice) || index < 0 {
|
||||
fmt.Println("Your Option is invalid")
|
||||
os.Exit(3)
|
||||
}
|
||||
return ipSlice[index]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user