mirror of
https://github.com/nxtrace/NTrace-core.git
synced 2025-08-12 06:26:39 +00:00
Compare commits
215 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b88d5292da | ||
|
|
903b6f232d | ||
|
|
5211daf814 | ||
|
|
62f817af91 | ||
|
|
5907ebcf84 | ||
|
|
825eccf986 | ||
|
|
a33307d746 | ||
|
|
eee741d848 | ||
|
|
98344193e7 | ||
|
|
7a74bb83c2 | ||
|
|
3e010bd9da | ||
|
|
abc1ecd035 | ||
|
|
989dc29931 | ||
|
|
1f2b4cde1f | ||
|
|
939a4c7aab | ||
|
|
4890615a2b | ||
|
|
9b8fd51ef2 | ||
|
|
73c9d91637 | ||
|
|
06e85c11c6 | ||
|
|
d151416566 | ||
|
|
b9d9853458 | ||
|
|
3f177c87d2 | ||
|
|
d3a00335a1 | ||
|
|
aa38ed374f | ||
|
|
08f87cc229 | ||
|
|
ead7ebbc25 | ||
|
|
7a048fa976 | ||
|
|
dbb844eb44 | ||
|
|
916ac3cb69 | ||
|
|
bdfba172d1 | ||
|
|
a95a741ce2 | ||
|
|
362317e95e | ||
|
|
dc735b0f97 | ||
|
|
fe0fce07ec | ||
|
|
60a4454802 | ||
|
|
525cfd8c8f | ||
|
|
c2eebca25c | ||
|
|
110bdc27ff | ||
|
|
510b0f6c32 | ||
|
|
8a01bccbff | ||
|
|
64224c905e | ||
|
|
2403308c1b | ||
|
|
560a1693af | ||
|
|
45e9fe604d | ||
|
|
88e69f7dc2 | ||
|
|
b1a66546a0 | ||
|
|
5c46e4eddb | ||
|
|
30d550753c | ||
|
|
50b3e442da | ||
|
|
ba133e3c73 | ||
|
|
3a5184b06c | ||
|
|
e825b8d092 | ||
|
|
f64934bfde | ||
|
|
9871449b30 | ||
|
|
31a9c356e1 | ||
|
|
5c0c490ae8 | ||
|
|
22660143b0 | ||
|
|
f4b5de08ca | ||
|
|
b0e1329d66 | ||
|
|
805e827e45 | ||
|
|
ed2250f811 | ||
|
|
fe18c80f83 | ||
|
|
d1c9fd6c3a | ||
|
|
c67e2d6384 | ||
|
|
309ae9d74a | ||
|
|
cf65ff5e7e | ||
|
|
eae92ebfae | ||
|
|
afe18fc4c0 | ||
|
|
cc1d6177ca | ||
|
|
c8077919ce | ||
|
|
cfc79489d4 | ||
|
|
f60e6fbc99 | ||
|
|
de0f49d01b | ||
|
|
7ae47fdb6f | ||
|
|
471412b740 | ||
|
|
8aca06fe24 | ||
|
|
003db157a9 | ||
|
|
6e88706d62 | ||
|
|
0940014b09 | ||
|
|
5a7d04ab1e | ||
|
|
c6b7db8d59 | ||
|
|
95f8cef54c | ||
|
|
99ad5649a0 | ||
|
|
4bc4f1c176 | ||
|
|
b663e69343 | ||
|
|
7619e74152 | ||
|
|
c682f90856 | ||
|
|
68c84488ae | ||
|
|
deaebfa383 | ||
|
|
b467128c24 | ||
|
|
de93a2b3cb | ||
|
|
66de7df351 | ||
|
|
9685b80820 | ||
|
|
8a3a9d5e38 | ||
|
|
c826dd50ec | ||
|
|
4664bc727a | ||
|
|
b66b7edc46 | ||
|
|
457f25035c | ||
|
|
8220bd7920 | ||
|
|
372ac9d383 | ||
|
|
916a5dc9d0 | ||
|
|
b9b18f5efa | ||
|
|
326034b41e | ||
|
|
09a5b42443 | ||
|
|
b8542489b6 | ||
|
|
a73a306d0a | ||
|
|
801f42801a | ||
|
|
08c4a5ceae | ||
|
|
ce1c773996 | ||
|
|
cdec6cbd8d | ||
|
|
aa891ebd7c | ||
|
|
5d132e73ab | ||
|
|
63ca4d0418 | ||
|
|
a6da078eb0 | ||
|
|
7ee76591b4 | ||
|
|
2d95fed6b2 | ||
|
|
29b1d7b283 | ||
|
|
1e654d1400 | ||
|
|
1746068302 | ||
|
|
45c30ddb8d | ||
|
|
88fef52e71 | ||
|
|
491f774336 | ||
|
|
b435a36ee4 | ||
|
|
ba87933580 | ||
|
|
6bf243794b | ||
|
|
44d7d5a024 | ||
|
|
5f096964dc | ||
|
|
69b893a587 | ||
|
|
ab2462bead | ||
|
|
d9d60d09b2 | ||
|
|
91cd4fb8f4 | ||
|
|
c592c14f28 | ||
|
|
15829c7041 | ||
|
|
336151dc1b | ||
|
|
66ee62f22b | ||
|
|
3afd28cb89 | ||
|
|
690f546ff0 | ||
|
|
d561063a7c | ||
|
|
473ce3c5f2 | ||
|
|
a1783e3563 | ||
|
|
7b9912f23f | ||
|
|
fdeaf112f5 | ||
|
|
07a2aac7c7 | ||
|
|
08f8daf9ce | ||
|
|
7872e9ee0f | ||
|
|
991f66cfe4 | ||
|
|
5b91fac860 | ||
|
|
62e877e248 | ||
|
|
9e4b2ae577 | ||
|
|
b02572d6ff | ||
|
|
e5742e1603 | ||
|
|
70a727bee6 | ||
|
|
1261e243f2 | ||
|
|
49ce0cba8e | ||
|
|
9764533c8e | ||
|
|
d752385c29 | ||
|
|
bf005fb37a | ||
|
|
8dc5960d98 | ||
|
|
ef104673b8 | ||
|
|
46545bd8d9 | ||
|
|
a4124b50aa | ||
|
|
b09d4bab74 | ||
|
|
09e493ebc3 | ||
|
|
3d665ee03c | ||
|
|
493328c7be | ||
|
|
e2d778c34d | ||
|
|
bf54b61eb8 | ||
|
|
9cec64b207 | ||
|
|
23006acd9f | ||
|
|
0210c94651 | ||
|
|
0ccdae851d | ||
|
|
ddffdb389a | ||
|
|
c533dd34ab | ||
|
|
e690ad85d9 | ||
|
|
00e4f9391e | ||
|
|
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 |
@@ -5,7 +5,7 @@ set -e
|
||||
DIST_PREFIX="nexttrace"
|
||||
DEBUG_MODE=${2}
|
||||
TARGET_DIR="dist"
|
||||
PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips openbsd/amd64 openbsd/arm64 freebsd/amd64 freebsd/arm64"
|
||||
PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips linux/mips64 linux/mipsle linux/mips64le windows/amd64 windows/arm64 openbsd/amd64 openbsd/arm64 freebsd/amd64 freebsd/arm64"
|
||||
|
||||
BUILD_VERSION="$(git describe --tags --always)"
|
||||
BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
@@ -15,6 +15,7 @@ rm -rf ${TARGET_DIR}
|
||||
mkdir ${TARGET_DIR}
|
||||
|
||||
for pl in ${PLATFORMS}; do
|
||||
export CGO_ENABLED=0
|
||||
export GOOS=$(echo ${pl} | cut -d'/' -f1)
|
||||
export GOARCH=$(echo ${pl} | cut -d'/' -f2)
|
||||
export TARGET=${TARGET_DIR}/${DIST_PREFIX}_${GOOS}_${GOARCH}
|
||||
@@ -37,7 +38,7 @@ for pl in ${PLATFORMS}; do
|
||||
-w -s"
|
||||
fi
|
||||
done
|
||||
|
||||
export CGO_ENABLED=0
|
||||
export GOOS='linux'
|
||||
export GOARCH='arm'
|
||||
export GOARM='7'
|
||||
|
||||
15
.github/dependabot.yml
vendored
Normal file
15
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
209
.github/workflows/build.yml
vendored
209
.github/workflows/build.yml
vendored
@@ -1,49 +1,190 @@
|
||||
name: Build & Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "v*"
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
pull_request:
|
||||
|
||||
name: Test & Build Release
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
jobs:
|
||||
Test:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# Include amd64 on all platforms.
|
||||
goos: [windows, freebsd, openbsd, linux, dragonfly, darwin]
|
||||
goarch: [amd64, 386]
|
||||
exclude:
|
||||
# Exclude i386 on darwin and dragonfly.
|
||||
- goarch: 386
|
||||
goos: dragonfly
|
||||
- goarch: 386
|
||||
goos: darwin
|
||||
include:
|
||||
# BEIGIN MacOS ARM64
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
# END MacOS ARM64
|
||||
# BEGIN Linux ARM 5 6 7
|
||||
- goos: linux
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
- goos: linux
|
||||
goarch: arm
|
||||
goarm: 6
|
||||
- goos: linux
|
||||
goarch: arm
|
||||
goarm: 5
|
||||
# END Linux ARM 5 6 7
|
||||
# BEGIN Android ARM 8
|
||||
- goos: android
|
||||
goarch: arm64
|
||||
# END Android ARM 8
|
||||
# Windows ARM
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
# BEGIN Other architectures
|
||||
# BEGIN riscv64 & ARM64
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
- goos: linux
|
||||
goarch: riscv64
|
||||
# END riscv64 & ARM64
|
||||
# BEGIN MIPS
|
||||
- goos: linux
|
||||
goarch: mips64
|
||||
- goos: linux
|
||||
goarch: mips64le
|
||||
- goos: linux
|
||||
goarch: mipsle
|
||||
- goos: linux
|
||||
goarch: mips
|
||||
# END MIPS
|
||||
# BEGIN PPC
|
||||
- goos: linux
|
||||
goarch: ppc64
|
||||
- goos: linux
|
||||
goarch: ppc64le
|
||||
# END PPC
|
||||
# BEGIN FreeBSD ARM
|
||||
- goos: freebsd
|
||||
goarch: arm64
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
# END FreeBSD ARM
|
||||
# BEGIN S390X
|
||||
- goos: linux
|
||||
goarch: s390x
|
||||
# END S390X
|
||||
# END Other architectures
|
||||
# BEGIN OPENBSD ARM
|
||||
- goos: openbsd
|
||||
goarch: arm64
|
||||
- goos: openbsd
|
||||
goarch: arm
|
||||
goarm: 7
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v3
|
||||
- name: Show workflow information
|
||||
run: |
|
||||
if [ ! -z $GOARM ]; then
|
||||
export GOARM=v$GOARM
|
||||
fi
|
||||
export _NAME="nexttrace_${GOOS}_${GOARCH}${GOARM}"
|
||||
if [ "$GOOS" == "windows" ]; then
|
||||
export _NAME="$_NAME.exe"
|
||||
fi
|
||||
echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, RELEASE_NAME: $_NAME"
|
||||
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
|
||||
echo "BUILD_VERSION=$(git describe --tags --always)" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
|
||||
echo "COMMIT_SHA1=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.18"
|
||||
|
||||
- name: Test
|
||||
run: go test -v -coverprofile='coverage.out' -covermode=count ./...
|
||||
|
||||
Build:
|
||||
needs: test
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
go-version: '1.20'
|
||||
- name: Get project dependencies
|
||||
run: go mod download
|
||||
- name: Build
|
||||
run: |
|
||||
go build -trimpath -o dist/${ASSET_NAME} \
|
||||
-ldflags "-X 'github.com/xgadget-lab/nexttrace/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"
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
go-version: "1.18"
|
||||
|
||||
- run: bash .cross_compile.sh
|
||||
|
||||
name: ${{ env.ASSET_NAME }}
|
||||
path: |
|
||||
dist/${{ env.ASSET_NAME }}
|
||||
- name: Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with: # 将下述可执行文件 release 上去
|
||||
draft: false # Release草稿
|
||||
files: |
|
||||
dist/nexttrace_darwin_amd64
|
||||
dist/nexttrace_darwin_arm64
|
||||
dist/nexttrace_linux_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
|
||||
dist/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GT_Token }}
|
||||
|
||||
publish-new-formula:
|
||||
needs: build
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Runs a single command using the runners shell
|
||||
- name: config git
|
||||
run: |
|
||||
git config --global user.email "${{ secrets.git_mail }}"
|
||||
git config --global user.name "${{ secrets.git_name }}"
|
||||
- name: Clone repo
|
||||
run: |
|
||||
git clone https://github.com/xgadget-lab/homebrew-nexttrace.git
|
||||
- name: Exec scipt
|
||||
run: |
|
||||
cd homebrew-nexttrace
|
||||
bash genFormula.sh
|
||||
# - name: setup SSH keys and known_hosts
|
||||
# run: |
|
||||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
# ssh-agent -a $SSH_AUTH_SOCK > /dev/null
|
||||
# ssh-add - <<< "${{ secrets.ID_RSA }}"
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- name: Git Push
|
||||
run: |
|
||||
cd homebrew-nexttrace
|
||||
git commit -am 'Publish a new version with Formula' || true
|
||||
git remote set-url origin https://${{ secrets.gt_token }}@github.com/xgadget-lab/homebrew-nexttrace.git
|
||||
git push
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
|
||||
45
.github/workflows/publishNewFormula.yml
vendored
Normal file
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' || true
|
||||
git remote set-url origin https://${{ secrets.gt_token }}@github.com/xgadget-lab/homebrew-nexttrace.git
|
||||
git push
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
43
.github/workflows/test.yml
vendored
Normal file
43
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "**/*.go"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- ".github/workflows/*.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
check-latest: true
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v3
|
||||
- name: Test with unix
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
run: sudo go test -v -coverprofile='coverage.out' -covermode=count ./...
|
||||
- name: Test with windows
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: go test -v -coverprofile='coverage.out' -covermode=count ./...
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -161,3 +161,5 @@ Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# compile target directory
|
||||
dist/
|
||||
|
||||
331
README.md
331
README.md
@@ -4,164 +4,286 @@
|
||||
|
||||
</div>
|
||||
|
||||
# NextTrace
|
||||
## NextTrace
|
||||
|
||||
一款开源的可视化路由跟踪工具,使用 Golang 开发。
|
||||
An open source visual routing tool that pursues light weight, developed using Golang.
|
||||
|
||||
NextTrace is part of the [OwO Network](https://github.com/OwO-Network) project. The project is a joint initiative of Leo and Vincent.
|
||||
|
||||
If you like this project, [Donate us](https://afdian.net/a/sjlleo/plan) to help us to provide long-lasting API cost expenses.
|
||||
|
||||
## How To Use
|
||||
|
||||
### Automated Install
|
||||
Document Language: English | [简体中文](README_zh_CN.md)
|
||||
|
||||
### Automated Installation
|
||||
|
||||
```bash
|
||||
# Linux 一键安装脚本
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/xgadget-lab/nexttrace/main/nt_install.sh)
|
||||
# Linux one-click install script
|
||||
bash -c "$(curl -Ls https://raw.githubusercontent.com/sjlleo/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 安装命令
|
||||
# macOS brew install command
|
||||
brew tap xgadget-lab/nexttrace && brew install nexttrace
|
||||
|
||||
# GHProxy Mirror (For China Mainland User)
|
||||
bash -c "$(curl -Ls https://ghproxy.com/https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)"
|
||||
```
|
||||
|
||||
- `Release`里面为很多系统以及不同架构提供了编译好的二进制可执行文件,如果没有可以自行编译。
|
||||
- 一些本项目的必要依赖在`Windows`上`Golang`底层实现不完全,所以目前`NextTrace`在`Windows`平台不可用。
|
||||
Windows users please go to [Release Page](https://github.com/sjlleo/nexttrace/releases/latest) directly and download exe file.
|
||||
|
||||
- `Release` provides compiled executables for many systems and architectures, if not, you can compile it yourself.
|
||||
- Some of the necessary dependencies of this project are not fully implemented in `Golang` on `Windows`, so currently `NextTrace` is experimental on `Windows` platform.
|
||||
|
||||
### Get Started
|
||||
|
||||
`NextTrace` 默认使用`ICMP`协议发起`TraceRoute`请求,该协议同时支持`IPv4`和`IPv6`
|
||||
`NextTrace` uses the `ICMP` protocol to perform TraceRoute requests by default, which supports both `IPv4` and `IPv6`
|
||||
|
||||
```bash
|
||||
# IPv4 ICMP Trace
|
||||
nexttrace 1.0.0.1
|
||||
# URL
|
||||
nexttrace http://example.com:8080/index.html?q=1
|
||||
|
||||
# 表格打印(一次性输出全部跳数,需等待20-40秒)
|
||||
nexttrace -table 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
|
||||
|
||||
# Disable Path Visualization With the -M parameter
|
||||
nexttrace koreacentral.blob.core.windows.net
|
||||
# MapTrace URL: https://api.leo.moe/tracemap/html/c14e439e-3250-5310-8965-42a1e3545266.html
|
||||
```
|
||||
|
||||
`NextTrace` 现已经支持快速测试,有一次性测试回程路由需求的朋友可以使用
|
||||
PS: The routing visualization drawing module was written by [@tsosunchia](https://github.com/tsosunchia), and the specific code can be viewed at [tsosunchia/traceMap](https://github.com/tsosunchia/traceMap).
|
||||
|
||||
Note that in LeoMoeAPI 2.0, due to the addition of geographical location data, **we have deprecated the online query part of the OpenStreetMap API in the traceMap plugin and are using location information from our own database**.
|
||||
|
||||
The routing visualization function requires the geographical coordinates of each Hop, but third-party APIs generally do not provide this information, so this function is currently only supported when used with LeoMoeAPI.
|
||||
|
||||
`NextTrace` now supports quick testing, and friends who have a one-time backhaul routing test requirement can use it
|
||||
|
||||
```bash
|
||||
# 北上广(电信+联通+移动+教育网)IPv4 ICMP 快速测试
|
||||
nexttrace -f
|
||||
# IPv4 ICMP Fast Test (Beijing + Shanghai + Guangzhou + Hangzhou) in China Telecom / Unicom / Mobile / Education Network
|
||||
nexttrace --fast-trace
|
||||
|
||||
# 也可以使用 TCP SYN 而非 ICMP 进行测试
|
||||
nexttrace -f -T
|
||||
# You can also use TCP SYN for testing
|
||||
nexttrace --fast-trace --tcp
|
||||
```
|
||||
|
||||
`NextTrace` 也可以使用`TCP`和`UDP`协议发起`Traceroute`请求,不过目前只支持`IPv4`
|
||||
`NextTrace` already supports route tracing for specified Network Devices
|
||||
|
||||
```bash
|
||||
# Use eth0 network interface
|
||||
nexttrace --dev eth0 2606:4700:4700::1111
|
||||
|
||||
# Use eth0 network interface's IP
|
||||
# When using the network interface's IP for route tracing, note that the IP type to be traced should be the same as network interface's IP type (e.g. both IPv4)
|
||||
nexttrace --source 204.98.134.56 9.9.9.9
|
||||
```
|
||||
|
||||
`NextTrace` can also use `TCP` and `UDP` protocols to perform `Traceroute` requests, but `UDP` protocols only supports `IPv4` now
|
||||
|
||||
```bash
|
||||
# TCP SYN Trace
|
||||
nexttrace -T www.bing.com
|
||||
nexttrace --tcp www.bing.com
|
||||
|
||||
# 可以自行指定端口[此处为443],默认80端口
|
||||
nexttrace -T -p 443 1.0.0.1
|
||||
# You can specify the port by yourself [here is 443], the default port is 80
|
||||
nexttrace --tcp --port 443 2001:4860:4860::8888
|
||||
|
||||
# UDP Trace
|
||||
nexttrace -U 1.0.0.1
|
||||
nexttrace --udp 1.0.0.1
|
||||
|
||||
nexttrace -U -p 53 1.0.0.1
|
||||
nexttrace --udp --port 53 1.0.0.1
|
||||
```
|
||||
|
||||
`NextTrace`也同样支持一些进阶功能,如 IP 反向解析、并发数控制、模式切换等
|
||||
`NextTrace` also supports some advanced functions, such as ttl control, concurrent probe packet count control, mode switching, etc.
|
||||
|
||||
```bash
|
||||
# 每一跳发送2个探测包
|
||||
nexttrace -q 2 www.hkix.net
|
||||
# Send 2 probe packets per hop
|
||||
nexttrace --queries 2 www.hkix.net
|
||||
|
||||
# 无并发,每次只发送一个探测包
|
||||
nexttrace -r 1 www.hkix.net
|
||||
# No concurrent probe packets, only one probe packet is sent at a time
|
||||
nexttrace --parallel-requests 1 www.hkix.net
|
||||
|
||||
# 打开IP反向解析功能,在IPv6的骨干网定位辅助有较大帮助
|
||||
nexttrace -rdns www.bbix.net
|
||||
# Start Trace with TTL of 5, end at TTL of 10
|
||||
nexttrace --first 5 --max-hops 10 www.decix.net
|
||||
|
||||
# 特色功能:打印Route-Path图
|
||||
# Route-Path图示例:
|
||||
# AS6453 塔塔通信「Singapore『Singapore』」
|
||||
# Turn off the IP reverse parsing function
|
||||
nexttrace --no-rdns www.bbix.net
|
||||
|
||||
# Feature: print Route-Path diagram
|
||||
# Route-Path diagram example:
|
||||
# AS6453 Tata Communication「Singapore『Singapore』」
|
||||
# ╭╯
|
||||
# ╰AS9299 Philippine Long Distance Telephone Co.「Philippines『Metro Manila』」
|
||||
# ╭╯
|
||||
# ╰AS36776 Five9 Inc.「Philippines『Metro Manila』」
|
||||
# ╭╯
|
||||
# ╰AS37963 阿里云「ALIDNS.COM『ALIDNS.COM』」
|
||||
nexttrace -report www.time.com.my
|
||||
# ╰AS37963 Aliyun「ALIDNS.COM『ALIDNS.COM』」
|
||||
nexttrace --route-path www.time.com.my
|
||||
```
|
||||
|
||||
`NextTrace`支持用户自主选择 IP 数据库(目前支持:`LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`)
|
||||
`NextTrace` supports users to select their own IP API (currently supports: `LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`, `Ip2region`, `IPInfoLocal`, `CHUNZHEN`)
|
||||
|
||||
```bash
|
||||
# 可以自行指定IP数据库[此处为IP.SB],不指定则默认为LeoMoeAPI
|
||||
nexttrace -d IP.SB
|
||||
## 特别的:其中 ipinfo API 需要从ipinfo自行购买服务,如有需要可以clone本项目添加其提供的token自行编译
|
||||
## TOKEN填写路径:ipgeo/tokens.go
|
||||
## 另外:由于IP.SB被滥用比较严重,会经常出现无法查询的问题,请知悉。
|
||||
## IPAPI.com限制调用较为严格,如有查询不到的情况,请几分钟后再试。
|
||||
# You can specify the IP database by yourself [IP-API.com here], if not specified, LeoMoeAPI will be used
|
||||
nexttrace --data-provider ip-api.com
|
||||
## Note There are frequency limits for free queries of the ipinfo and IPInsight APIs. You can purchase services from these providers to remove the limits
|
||||
## If necessary, you can clone this project, add the token provided by ipinfo or IPInsight and compile it yourself
|
||||
## Note For the offline database IPInfoLocal, please download it manually and rename it to ipinfoLocal.mmdb. (You can download it from here: https://ipinfo.io/signup?ref=free-database-downloads)
|
||||
## For the offline database Ip2region, you can download it manually and rename it to ip2region.db, or let NextTrace download it automatically
|
||||
## Fill the token to: ipgeo/tokens.go
|
||||
## Please be aware: Due to the serious abuse of IP.SB, you will often be not able to query IP data from this source
|
||||
## IP-API.com has a stricter restiction on API calls, if you can't query IP data from this source, please try again in a few minutes
|
||||
|
||||
# The Pure-FTPd IP database defaults to using http://127.0.0.1:2060 as the query interface. To customize it, please use environment variables
|
||||
export NEXTTRACE_CHUNZHENURL=http://127.0.0.1:2060
|
||||
## You can use https://github.com/freshcn/qqwry to build your own Pure-FTPd IP database service
|
||||
|
||||
# You can also specify the default IP database by setting an environment variable
|
||||
export NEXTTRACE_DATAPROVIDER=ipinfo
|
||||
```
|
||||
|
||||
`NextTrace`支持参数混合使用
|
||||
`NextTrace` supports mixed parameters and shortened 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 -rdns -table -report 2001:4860:4860::8888
|
||||
nexttrace --data-provider IPAPI.com --max-hops 20 --tcp --port 443 --queries 5 --no-rdns 1.1.1.1
|
||||
nexttrace -tcp --queries 2 --parallel-requests 1 --table --route-path 2001:4860:4860::8888
|
||||
|
||||
Equivalent to:
|
||||
nexttrace -d IPAPI.com -m 20 -T -p 443 -q 5 -n 1.1.1.1
|
||||
nexttrace -T -q 2 --parallel-requests 1 -t -R 2001:4860:4860::8888
|
||||
```
|
||||
|
||||
### IP 数据库
|
||||
### IP Database
|
||||
|
||||
目前使用的 IP 数据库默认为我们自己搭建的 API 服务,如果后期遇到滥用,我们可能会选择关闭。
|
||||
#### We use [bgp.tools](https://bgp.tools) as a data provider for routing tables.
|
||||
|
||||
我们也会在后期开放服务端源代码,您也可以根据该项目的源码自行搭建属于您的 API 服务器。
|
||||
NextTrace BackEnd is now open-source.
|
||||
|
||||
NextTrace 所有的的 IP 地理位置`API DEMO`可以参考[这里](https://github.com/xgadget-lab/nexttrace/blob/main/ipgeo/)
|
||||
https://github.com/sjlleo/nexttrace-backend
|
||||
|
||||
### 全部用法详见 Usage 菜单
|
||||
All NextTrace IP geolocation `API DEMO` can refer to [here](https://github.com/xgadget-lab/nexttrace/blob/main/ipgeo/)
|
||||
|
||||
### For full usage list, please refer to the usage menu
|
||||
|
||||
```shell
|
||||
Usage of nexttrace:
|
||||
'nexttrace [options] <hostname>' or 'nexttrace <hostname> [option...]'
|
||||
Options:
|
||||
-T Use TCP SYN for tracerouting (default port is 80)
|
||||
-U Use UDP Package for tracerouting (default port is 53 in UDP)
|
||||
-V Check Version
|
||||
-d string
|
||||
Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight, IPAPI.com] (default "LeoMoeAPI")
|
||||
-m int
|
||||
Set the max number of hops (max TTL to be reached). (default 30)
|
||||
-p int
|
||||
Set SYN Traceroute Port (default 80)
|
||||
-q int
|
||||
Set the number of probes per each hop. (default 3)
|
||||
-r int
|
||||
Set ParallelRequests number. It should be 1 when there is a multi-routing. (default 18)
|
||||
-rdns
|
||||
Set whether rDNS will be display
|
||||
-table
|
||||
Output trace results as table
|
||||
-report
|
||||
Route Path
|
||||
Usage: nexttrace [-h|--help] [-T|--tcp] [-U|--udp] [-F|--fast-trace] [-p|--port
|
||||
<integer>] [-q|--queries <integer>] [--parallel-requests
|
||||
<integer>] [-m|--max-hops <integer>] [-d|--data-provider
|
||||
(Ip2region|ip2region|IP.SB|ip.sb|IPInfo|ipinfo|IPInsight|ipinsight|IPAPI.com|ip-api.com|IPInfoLocal|ipinfolocal|chunzhen)]
|
||||
[-n|--no-rdns] [-a|--always-rdns] [-P|--route-path]
|
||||
[-r|--report] [--dn42] [-o|--output] [-t|--table]
|
||||
[-c|--classic] [-f|--first <integer>] [-M|--map]
|
||||
[-v|--version] [-s|--source "<value>"] [-D|--dev "<value>"]
|
||||
[-R|--route] [-z|--send-time <integer>] [-i|--ttl-time
|
||||
<integer>] [_positionalArg_nexttrace_25 "<value>"]
|
||||
[--dot-server (dnssb|aliyun|dnspod|google|cloudflare)]
|
||||
[-g|--language (en|cn)]
|
||||
|
||||
Arguments:
|
||||
|
||||
-h --help Print help information
|
||||
-T --tcp Use TCP SYN for tracerouting (default port
|
||||
is 80)
|
||||
-U --udp Use UDP SYN for tracerouting (default port
|
||||
is 53)
|
||||
-F --fast-trace One-Key Fast Trace to China ISPs
|
||||
-p --port Set the destination port to use. It is
|
||||
either initial udp port value for
|
||||
"default"method (incremented by each
|
||||
probe, default is 33434), or initial seq
|
||||
for "icmp" (incremented as well, default
|
||||
from 1), or some constantdestination port
|
||||
for other methods (with default of 80 for
|
||||
"tcp", 53 for "udp", etc.)
|
||||
-q --queries Set the number of probes per each hop.
|
||||
Default: 3
|
||||
--parallel-requests Set ParallelRequests number. It should be
|
||||
1 when there is a multi-routing. Default:
|
||||
18
|
||||
-m --max-hops Set the max number of hops (max TTL to be
|
||||
reached). Default: 30
|
||||
-d --data-provider Choose IP Geograph Data Provider [IP.SB,
|
||||
IPInfo, IPInsight, IP-API.com, Ip2region,
|
||||
IPInfoLocal, CHUNZHEN]. Default: LeoMoeAPI
|
||||
-n --no-rdns Do not resolve IP addresses to their
|
||||
domain names
|
||||
-a --always-rdns Always resolve IP addresses to their
|
||||
domain names
|
||||
-P --route-path Print traceroute hop path by ASN and
|
||||
location
|
||||
-r --report output using report mode
|
||||
--dn42 DN42 Mode
|
||||
-o --output Write trace result to file
|
||||
(RealTimePrinter ONLY)
|
||||
-t --table Output trace results as table
|
||||
-c --classic Classic Output trace results like
|
||||
BestTrace
|
||||
-f --first Start from the first_ttl hop (instead from
|
||||
1). Default: 1
|
||||
-M --map Disable Print Trace Map
|
||||
-v --version Print version info and exit
|
||||
-s --source Use source src_addr for outgoing packets
|
||||
-D --dev Use the following Network Devices as the
|
||||
source address in outgoing packets
|
||||
-R --route Show Routing Table [Provided By BGP.Tools]
|
||||
-z --send-time Set the time interval for sending every
|
||||
packet. Useful when some routers use
|
||||
rate-limit for ICMP messages. Default: 100
|
||||
-i --ttl-time Set the time interval for sending packets
|
||||
groups by TTL. Useful when some routers
|
||||
use rate-limit for ICMP messages. Default:
|
||||
500
|
||||
--_positionalArg_nexttrace_25 IP Address or domain name
|
||||
--dot-server Use DoT Server for DNS Parse [dnssb,
|
||||
aliyun, dnspod, google, cloudflare]
|
||||
-g --language Choose the language for displaying [en,
|
||||
cn]. Default: cn
|
||||
```
|
||||
|
||||
## 项目截图
|
||||
## Project screenshot
|
||||
|
||||
<div align="center">
|
||||

|
||||
|
||||
<img src=asset/screenshot.png alt="NextTrace Screenshot" height="688" />
|
||||

|
||||
|
||||
</div>
|
||||
## NextTrace Enhanced
|
||||
|
||||
## FAQ 常见问题
|
||||
`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.
|
||||
|
||||
如果你在安装或者使用的时候遇到了问题,我们建议你不要把新建一个 `issue` 作为首选项
|
||||
Please Notice that `NextTrace Enhanced` is currently not supported in English.
|
||||
|
||||
以下是我们推荐的排错流程:
|
||||
https://github.com/OwO-Network/nexttrace-enhanced
|
||||
|
||||
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)
|
||||
## LeoMoeAPI Credit
|
||||
|
||||
## Thanks
|
||||
NextTrace focuses on Golang Traceroute implementations, and its LeoMoeAPI geolocation information is not supported by raw data, so a commercial version is not possible.
|
||||
|
||||
The LeoMoeAPI data is subject to copyright restrictions from multiple data sources, and is only used for the purpose of displaying the geolocation of route tracing.
|
||||
|
||||
1. We would like to credit samleong123 for providing nodes in Malaysia, TOHUNET Looking Glass for global nodes, and Ping.sx from Misaka, where more than 80% of reliable calibration data comes from ping/mtr reports.
|
||||
|
||||
2. At the same time, we would like to credit isyekong for their contribution on rDNS-based calibration ideas and data. LeoMoeAPI is accelerating the development of rDNS resolution function, and has already achieved automated geolocation resolution for some backbone networks, but there are some misjudgments. We hope that NextTrace will become a One-Man ISP-friendly traceroute tool in the future, and we are working on improving the calibration of these ASN micro-backbones as much as possible.
|
||||
|
||||
3. In terms of development, I would like to credit missuo and zhshch for their help with Go cross-compilation, design concepts and TCP/UDP Traceroute refactoring, and tsosunchia for their support on TraceMap.
|
||||
|
||||
4. I would also like to credit FFEE_CO, TheresaQWQ, stydxm and others for their help. leoMoeAPI has received a lot of support since its first release, so I would like to credit them all!
|
||||
|
||||
We hope you can give us as much feedback as possible on IP geolocation errors (see issue) so that it can be calibrated in the first place and others can benefit from it.
|
||||
|
||||
|
||||
## JetBrain Support
|
||||
|
||||
#### This Project uses [JetBrain Open-Source Project License](https://jb.gg/OpenSourceSupport). We Proudly Develop By Goland.
|
||||
|
||||
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/GoLand.png" title="" alt="GoLand logo" width="331">
|
||||
|
||||
## Credits
|
||||
|
||||
BGP.TOOLS provided some data support for this project and we would like to express our sincere gratitude.
|
||||
|
||||
[Vincent Young](https://github.com/missuo) (i@yyt.moe)
|
||||
|
||||
@@ -171,41 +293,12 @@ Options:
|
||||
|
||||
[waiting4new](https://github.com/waiting4new)
|
||||
|
||||
[FFEE_CO](https://github.com/fkx4-p)
|
||||
|
||||
## IP Database Copyright
|
||||
|
||||
### IPv4 Database
|
||||
|
||||
#### China
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :------------: | :----: | :-------: | :--: |
|
||||
| 电信/联通/移动 | 骨干网 | NextTrace | 10% |
|
||||
| 电信/联通/移动 | 城域网 | 埃文科技 | 90% |
|
||||
|
||||
#### WorldWide
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :-----: | :----: | :-------: | :--: |
|
||||
| Tier-01 | 骨干网 | IPInfo | 2% |
|
||||
| Tier-01 | 骨干网 | 埃文科技 | 3% |
|
||||
| Tier-01 | 骨干网 | IPInSight | 5% |
|
||||
| Tier-01 | 城域网 | IPInSight | 90% |
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :----: | :----: | :-------: | :--: |
|
||||
| Others | 骨干网 | IPInSight | 5% |
|
||||
| Others | 城域网 | IPInSight | 95% |
|
||||
|
||||
### IPv6 Database
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :-: | :--: | :--------------: | :--: |
|
||||
| All | 全部 | IP2Location Lite | 100% |
|
||||
|
||||
This product includes IP2Location LITE data available from <a href="https://lite.ip2location.com">https://lite.ip2location.com</a>.
|
||||
[FFEE_CO](https://github.com/fkx4-p)
|
||||
|
||||
### Others
|
||||
|
||||
其他第三方 API 尽管集成在本项目内,但是具体的 TOS 以及 AUP,请详见第三方 API 官网。如遇到 IP 数据错误,也请直接联系他们纠错。
|
||||
Although other third-party APIs are integrated in this project, please refer to the official website of the third-party APIs for specific TOS and AUP. If you encounter IP data errors, please contact them directly to correct them.
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#sjlleo/nexttrace&Date)
|
||||
|
||||
348
README_zh_CN.md
Normal file
348
README_zh_CN.md
Normal file
@@ -0,0 +1,348 @@
|
||||
<div align="center">
|
||||
|
||||
<img src="asset/logo.png" height="200px" alt="NextTrace Logo"/>
|
||||
|
||||
</div>
|
||||
|
||||
<h1 align="center">
|
||||
<br>NextTrace<br>
|
||||
</h1>
|
||||
|
||||
|
||||
<h4 align="center">一款追求轻量化的开源可视化路由跟踪工具。</h4>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/sjlleo/nexttrace/actions">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/sjlleo/nexttrace/build.yml?branch=main&style=flat-square" alt="Github Actions">
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/github.com/sjlleo/nexttrace">
|
||||
<img src="https://goreportcard.com/badge/github.com/sjlleo/nexttrace?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/sjlleo/nexttrace/releases">
|
||||
<img src="https://img.shields.io/github/release/sjlleo/nexttrace/all.svg?style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
如果您喜欢这个项目,可以通过[爱发电支持](https://afdian.net/a/sjlleo/plan)我们项目的持续发展,您的捐助将用于服务器和 API 开支,非常感谢!
|
||||
|
||||
## How To Use
|
||||
|
||||
### Before Using
|
||||
|
||||
使用 NextTrace 之前,我们建议您先阅读 [#IP 数据以及精准度说明](https://github.com/sjlleo/nexttrace/blob/main/README_zh_CN.md#ip-%E6%95%B0%E6%8D%AE%E4%BB%A5%E5%8F%8A%E7%B2%BE%E5%87%86%E5%BA%A6%E8%AF%B4%E6%98%8E),在了解您自己的对数据精准度需求以后再进行抉择。
|
||||
|
||||
### Automated Install
|
||||
|
||||
```bash
|
||||
# Linux 一键安装脚本
|
||||
bash -c "$(curl -Ls https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)"
|
||||
|
||||
# macOS brew 安装命令
|
||||
brew tap xgadget-lab/nexttrace && brew install nexttrace
|
||||
|
||||
# GHPROXY 镜像(国内使用)
|
||||
bash -c "$(curl -Ls https://ghproxy.com/https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)"
|
||||
```
|
||||
|
||||
Windows 用户请直接前往 [Release](https://github.com/sjlleo/nexttrace/releases/latest) 下载编译后的二进制 exe 文件。
|
||||
|
||||
- `Release`里面为很多系统以及不同架构提供了编译好的二进制可执行文件,如果没有可以自行编译。
|
||||
- 一些本项目的必要依赖在`Windows`上`Golang`底层实现不完全,所以目前`NextTrace`在`Windows`平台出于实验性支持阶段。
|
||||
|
||||
### Get Started
|
||||
|
||||
`NextTrace` 默认使用`ICMP`协议发起`TraceRoute`请求,该协议同时支持`IPv4`和`IPv6`
|
||||
|
||||
```bash
|
||||
# IPv4 ICMP Trace
|
||||
nexttrace 1.0.0.1
|
||||
# URL
|
||||
nexttrace http://example.com:8080/index.html?q=1
|
||||
|
||||
# 表格打印,使用 --table / -t 参数,将实时显示结果
|
||||
nexttrace --table 1.0.0.1
|
||||
|
||||
# IPv6 ICMP Trace
|
||||
nexttrace 2606:4700:4700::1111
|
||||
|
||||
# 禁用路径可视化 使用 --map / -M 参数
|
||||
nexttrace koreacentral.blob.core.windows.net
|
||||
# MapTrace URL: https://api.leo.moe/tracemap/html/c14e439e-3250-5310-8965-42a1e3545266.html
|
||||
```
|
||||
|
||||
PS: 路由可视化的绘制模块由 [@tsosunchia](https://github.com/tsosunchia) 同学编写,具体代码可在 [tsosunchia/traceMap](https://github.com/tsosunchia/traceMap) 查看
|
||||
|
||||
需要注意的是,在 LeoMoeAPI 2.0 中,由于新增了了地理位置数据,**我们已经弃用 traceMap 插件中 OpenStreetMap API 的在线查询的部分,并且使用自己数据库内的位置信息**。
|
||||
|
||||
路由可视化功能因为需要每个 Hop 的地理位置坐标,而第三方 API 通常不提供此类信息,所以此功能目前只支持搭配 LeoMoeAPI 使用。
|
||||
|
||||
`NextTrace` 现已经支持快速测试,有一次性测试回程路由需求的朋友可以使用
|
||||
|
||||
```bash
|
||||
# 北上广(电信+联通+移动+教育网)IPv4 / IPv6 ICMP 快速测试
|
||||
nexttrace --fast-trace
|
||||
|
||||
# 也可以使用 TCP SYN 而非 ICMP 进行测试
|
||||
nexttrace --fast-trace --tcp
|
||||
```
|
||||
|
||||
`NextTrace` 已支持指定网卡进行路由跟踪
|
||||
|
||||
```bash
|
||||
# 请注意 Lite 版本此参数不能和快速测试联用,如有需要请使用 enhanced 版本
|
||||
# 使用 eth0 网卡
|
||||
nexttrace --dev eth0 2606:4700:4700::1111
|
||||
|
||||
# 使用 eth0 网卡IP
|
||||
# 网卡 IP 可以使用 ip a 或者 ifconfig 获取
|
||||
# 使用网卡IP进行路由跟踪时需要注意跟踪的IP类型应该和网卡IP类型一致(如都为 IPv4)
|
||||
nexttrace --source 204.98.134.56 9.9.9.9
|
||||
```
|
||||
|
||||
`NextTrace` 也可以使用`TCP`和`UDP`协议发起`Traceroute`请求,不过目前`UDP`只支持`IPv4`
|
||||
|
||||
```bash
|
||||
# TCP SYN Trace
|
||||
nexttrace --tcp www.bing.com
|
||||
|
||||
# 可以自行指定端口[此处为443],默认80端口
|
||||
nexttrace --tcp --port 443 2001:4860:4860::8888
|
||||
|
||||
# UDP Trace
|
||||
nexttrace --udp 1.0.0.1
|
||||
|
||||
# 可以自行指定端口[此处为5353],默认53端口
|
||||
nexttrace --udp --port 5353 1.0.0.1
|
||||
```
|
||||
|
||||
`NextTrace`也同样支持一些进阶功能,如 TTL 控制、并发数控制、模式切换等
|
||||
|
||||
```bash
|
||||
# 每一跳发送2个探测包
|
||||
nexttrace --queries 2 www.hkix.net
|
||||
|
||||
# 无并发,每次只发送一个探测包
|
||||
nexttrace --parallel-requests 1 www.hkix.net
|
||||
|
||||
# 从TTL为5开始发送探测包,直到TTL为10结束
|
||||
nexttrace --first 5 --max-hops 10 www.decix.net
|
||||
|
||||
# 关闭IP反向解析功能
|
||||
nexttrace --no-rdns www.bbix.net
|
||||
|
||||
# 特色功能:打印Route-Path图
|
||||
# Route-Path图示例:
|
||||
# AS6453 塔塔通信「Singapore『Singapore』」
|
||||
# ╭╯
|
||||
# ╰AS9299 Philippine Long Distance Telephone Co.「Philippines『Metro Manila』」
|
||||
# ╭╯
|
||||
# ╰AS36776 Five9 Inc.「Philippines『Metro Manila』」
|
||||
# ╭╯
|
||||
# ╰AS37963 阿里云「ALIDNS.COM『ALIDNS.COM』」
|
||||
nexttrace --route-path www.time.com.my
|
||||
```
|
||||
|
||||
`NextTrace`支持用户自主选择 IP 数据库(目前支持:`LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`, `Ip2region`, `IPInfoLocal`, `CHUNZHEN`)
|
||||
|
||||
```bash
|
||||
# 可以自行指定IP数据库[此处为IP-API.com],不指定则默认为LeoMoeAPI
|
||||
nexttrace --data-provider ip-api.com
|
||||
## 特别的: 其中 ipinfo 和 IPInsight API 对于免费版查询有频率限制,可从这些服务商自行购买服务以解除限制,如有需要可以 clone 本项目添加其提供的 token 自行编译
|
||||
## TOKEN填写路径:ipgeo/tokens.go
|
||||
## 特别的: 对于离线库 IPInfoLocal,请自行下载并命名为 ipinfoLocal.mmdb (可以从这里下载:https://ipinfo.io/signup?ref=free-database-downloads)
|
||||
## 对于离线库 Ip2region 可NextTrace自动下载,也可自行下载并命名为 ip2region.db
|
||||
## 另外:由于IP.SB被滥用比较严重,会经常出现无法查询的问题,请知悉。
|
||||
## IP-API.com限制调用较为严格,如有查询不到的情况,请几分钟后再试。
|
||||
|
||||
# 纯真IP数据库默认使用 http://127.0.0.1:2060 作为查询接口,如需自定义请使用环境变量
|
||||
export NEXTTRACE_CHUNZHENURL=http://127.0.0.1:2060
|
||||
## 可使用 https://github.com/freshcn/qqwry 自行搭建纯真IP数据库服务
|
||||
|
||||
# 也可以通过设置环境变量来指定默认IP数据库
|
||||
export NEXTTRACE_DATAPROVIDER=ipinfo
|
||||
```
|
||||
|
||||
`NextTrace`支持使用混合参数和简略参数
|
||||
|
||||
```bash
|
||||
Example:
|
||||
nexttrace --data-provider ip-api.com --max-hops 20 --tcp --port 443 --queries 5 --no-rdns 1.1.1.1
|
||||
nexttrace -tcp --queries 2 --parallel-requests 1 --table --route-path 2001:4860:4860::8888
|
||||
|
||||
Equivalent to:
|
||||
nexttrace -d ip-api.com -m 20 -T -p 443 -q 5 -n 1.1.1.1
|
||||
nexttrace -T -q 2 --parallel-requests 1 -t -R 2001:4860:4860::8888
|
||||
```
|
||||
|
||||
### 全部用法详见 Usage 菜单
|
||||
|
||||
```shell
|
||||
Usage: nexttrace [-h|--help] [-T|--tcp] [-U|--udp] [-F|--fast-trace] [-p|--port
|
||||
<integer>] [-q|--queries <integer>] [--parallel-requests
|
||||
<integer>] [-m|--max-hops <integer>] [-d|--data-provider
|
||||
(Ip2region|ip2region|IP.SB|ip.sb|IPInfo|ipinfo|IPInsight|ipinsight|IPAPI.com|ip-api.com|IPInfoLocal|ipinfolocal|chunzhen)]
|
||||
[-n|--no-rdns] [-a|--always-rdns] [-P|--route-path]
|
||||
[-r|--report] [--dn42] [-o|--output] [-t|--table]
|
||||
[-c|--classic] [-f|--first <integer>] [-M|--map]
|
||||
[-v|--version] [-s|--source "<value>"] [-D|--dev "<value>"]
|
||||
[-R|--route] [-z|--send-time <integer>] [-i|--ttl-time
|
||||
<integer>] [_positionalArg_nexttrace_25 "<value>"]
|
||||
[--dot-server (dnssb|aliyun|dnspod|google|cloudflare)]
|
||||
[-g|--language (en|cn)]
|
||||
|
||||
Arguments:
|
||||
|
||||
-h --help Print help information
|
||||
-T --tcp Use TCP SYN for tracerouting (default port
|
||||
is 80)
|
||||
-U --udp Use UDP SYN for tracerouting (default port
|
||||
is 53)
|
||||
-F --fast-trace One-Key Fast Trace to China ISPs
|
||||
-p --port Set the destination port to use. It is
|
||||
either initial udp port value for
|
||||
"default"method (incremented by each
|
||||
probe, default is 33434), or initial seq
|
||||
for "icmp" (incremented as well, default
|
||||
from 1), or some constantdestination port
|
||||
for other methods (with default of 80 for
|
||||
"tcp", 53 for "udp", etc.)
|
||||
-q --queries Set the number of probes per each hop.
|
||||
Default: 3
|
||||
--parallel-requests Set ParallelRequests number. It should be
|
||||
1 when there is a multi-routing. Default:
|
||||
18
|
||||
-m --max-hops Set the max number of hops (max TTL to be
|
||||
reached). Default: 30
|
||||
-d --data-provider Choose IP Geograph Data Provider [IP.SB,
|
||||
IPInfo, IPInsight, IP-API.com, Ip2region,
|
||||
IPInfoLocal, CHUNZHEN]. Default: LeoMoeAPI
|
||||
-n --no-rdns Do not resolve IP addresses to their
|
||||
domain names
|
||||
-a --always-rdns Always resolve IP addresses to their
|
||||
domain names
|
||||
-P --route-path Print traceroute hop path by ASN and
|
||||
location
|
||||
-r --report output using report mode
|
||||
--dn42 DN42 Mode
|
||||
-o --output Write trace result to file
|
||||
(RealTimePrinter ONLY)
|
||||
-t --table Output trace results as table
|
||||
-c --classic Classic Output trace results like
|
||||
BestTrace
|
||||
-f --first Start from the first_ttl hop (instead from
|
||||
1). Default: 1
|
||||
-M --map Disable Print Trace Map
|
||||
-v --version Print version info and exit
|
||||
-s --source Use source src_addr for outgoing packets
|
||||
-D --dev Use the following Network Devices as the
|
||||
source address in outgoing packets
|
||||
-R --route Show Routing Table [Provided By BGP.Tools]
|
||||
-z --send-time Set the time interval for sending every
|
||||
packet. Useful when some routers use
|
||||
rate-limit for ICMP messages. Default: 100
|
||||
-i --ttl-time Set the time interval for sending packets
|
||||
groups by TTL. Useful when some routers
|
||||
use rate-limit for ICMP messages. Default:
|
||||
500
|
||||
--_positionalArg_nexttrace_25 IP Address or domain name
|
||||
--dot-server Use DoT Server for DNS Parse [dnssb,
|
||||
aliyun, dnspod, google, cloudflare]
|
||||
-g --language Choose the language for displaying [en,
|
||||
cn]. Default: cn
|
||||
```
|
||||
|
||||
## 项目截图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 第三方 IP 数据库 API 开发接口
|
||||
|
||||
NextTrace 所有的的 IP 地理位置 `API DEMO` 可以参考[这里](https://github.com/sjlleo/nexttrace/blob/main/ipgeo/)
|
||||
|
||||
你可以在这里添加你自己的 API 接口,为了 NextTrace 能够正确显示你接口中的内容,请参考 `leo.go` 中所需要的信息
|
||||
|
||||
✨NextTrace `LeoMoeAPI` 的后端 Demo
|
||||
|
||||
[GitHub - sjlleo/nexttrace-backend: NextTrace BackEnd](https://github.com/sjlleo/nexttrace-backend)
|
||||
|
||||
## NextTrace Enhanced
|
||||
|
||||
https://github.com/OwO-Network/nexttrace-enhanced
|
||||
|
||||
## Credits
|
||||
|
||||
BGP.TOOLS 提供了本项目的一些数据支持,在此表示由衷地感谢。
|
||||
|
||||
[Vincent Young](https://github.com/missuo) (i@yyt.moe)
|
||||
|
||||
[Sam Sam](https://github.com/samleong123) (samsam123@samsam123.name.my)
|
||||
|
||||
[tsosunchia](https://github.com/tsosunchia)
|
||||
|
||||
[waiting4new](https://github.com/waiting4new)
|
||||
|
||||
[FFEE_CO](https://github.com/fkx4-p)
|
||||
|
||||
## Others
|
||||
|
||||
其他第三方 API 尽管集成在本项目内,但是具体的 TOS 以及 AUP,请详见第三方 API 官网。如遇到 IP 数据错误,也请直接联系他们纠错。
|
||||
|
||||
## IP 数据以及精准度说明
|
||||
|
||||
NextTrace 有多个数据源可以选择,目前默认使用的 LeoMoeAPI 为我们项目维护的数据源。
|
||||
|
||||
该项目由 OwO Network 的 [Missuo](https://github.com/missuo) && [Leo](https://github.com/sjlleo) 发起,由 [Zhshch](https://github.com/zhshch2002/) 完成最早期架构的编写和指导,后由 Leo 完成了大部分开发工作,现主要交由 [tsosunchia](https://github.com/tsosunchia) 以及 MoeQing Network 完成后续的二开和维护工作。
|
||||
|
||||
LeoMoeAPI 是 [Leo](https://github.com/sjlleo) 的作品,归属于 Leo Network,由 [Leo](https://github.com/sjlleo) 完成整套后端 API 编写,该接口未经允许不可用于任何第三方用途。
|
||||
|
||||
LeoMoeAPI 早期数据主要来自 IPInsight、IPInfo,随着项目发展,越来越多的志愿者参与进了这个项目。目前 LeoMoeAPI 有近一半的数据是社区提供的,而另外一半主要来自于包含 IPInfo、IPData、BigDataCloud、IPGeoLocation 在内的多个第三方数据。
|
||||
|
||||
LeoMoeAPI 的骨干网数据有近 70% 是社区自发反馈又或者是项目组成员校准的,这给本项目的路由跟踪基础功能带来了一定的保证,但是全球骨干网的体量庞大,我们并无能力如 IPIP 等商业公司拥有海量监测节点,这使得 LeoMoeAPI 的数据精准度无法和形如 BestTrace(IPIP)相提并论。
|
||||
|
||||
LeoMoeAPI 已经尽力校准了比较常见的骨干网路由,这部分在测试的时候经常会命中,但是如果遇到封闭型 ISP 的路由,大概率可以遇到错误,此类数据不仅是我们,哪怕 IPInsight、IPInfo 也无法正确定位,目前只有 IPIP 能够标记正确,如对此类数据的精确性有着非常高的要求,请务必使用 BestTrace 作为首选。
|
||||
|
||||
我们不保证我们的数据一定会及时更新,也不保证数据的精确性,我们希望您在发现数据错误的时候可以前往 issue 页面提交错误报告,谢谢。
|
||||
|
||||
当您使用 LeoMoeAPI 即视为您已经完全了解 NextTrace LeoMoeAPI 的数据精确性,并且同意如果您引用 LeoMoeAPI 其中的数据从而引发的一切问题,均由您自己承担。
|
||||
|
||||
## DN42 模式使用说明
|
||||
|
||||
使用这个模式需要您配置 2 个文件,分别是 geofeed.csv 以及 ptr.csv
|
||||
|
||||
当您初次运行 DN42 模式,NT 会为您生成 nt_config.yaml 文件,您可以自定义 2 个文件的存放位置,默认应该存放在 NT 的运行目录下
|
||||
|
||||
### GeoFeed
|
||||
|
||||
对于 geofeed.csv 来说,格式如下:
|
||||
```
|
||||
IP_CDIR,LtdCode,ISO3166-2,CityName,ASN,IPWhois
|
||||
```
|
||||
|
||||
比如,您可以这么写:
|
||||
|
||||
```
|
||||
58.215.96.0/20,CN,CN-JS,Wuxi,23650,CHINANET-JS
|
||||
```
|
||||
|
||||
如果您有一个大段作为骨干网使用,您也可以不写地理位置信息,如下:
|
||||
|
||||
```
|
||||
202.97.0.0/16,,,4134,CHINANET-BACKBONE
|
||||
```
|
||||
|
||||
### PTR
|
||||
|
||||
对于 ptr.csv 来说,格式如下:
|
||||
```
|
||||
IATA_CODE,LtdCode,RegionName,CityName
|
||||
```
|
||||
|
||||
比如对于美国洛杉矶,您可以这么写
|
||||
|
||||
```
|
||||
LAX,US,California,Los Anegles
|
||||
```
|
||||
|
||||
需要注意的是,NextTrace 支持自动匹配 CSV 中的城市名,如果您的 PTR 记录中有 `losangeles`,您可以只添加上面一条记录就可以正常识别并读取。
|
||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
||||
BIN
asset/nexttrace021.png
Normal file
BIN
asset/nexttrace021.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
284
cmd/cmd.go
Normal file
284
cmd/cmd.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/akamensky/argparse"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
"github.com/xgadget-lab/nexttrace/config"
|
||||
fastTrace "github.com/xgadget-lab/nexttrace/fast_trace"
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/printer"
|
||||
"github.com/xgadget-lab/nexttrace/reporter"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/tracelog"
|
||||
"github.com/xgadget-lab/nexttrace/tracemap"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
func Excute() {
|
||||
parser := argparse.NewParser("nexttrace", "An open source visual route tracking CLI tool")
|
||||
// Create string flag
|
||||
tcp := parser.Flag("T", "tcp", &argparse.Options{Help: "Use TCP SYN for tracerouting (default port is 80)"})
|
||||
udp := parser.Flag("U", "udp", &argparse.Options{Help: "Use UDP SYN for tracerouting (default port is 53)"})
|
||||
fast_trace := parser.Flag("F", "fast-trace", &argparse.Options{Help: "One-Key Fast Trace to China ISPs"})
|
||||
port := parser.Int("p", "port", &argparse.Options{Help: "Set the destination port to use. It is either initial udp port value for \"default\"" +
|
||||
"method (incremented by each probe, default is 33434), or initial seq for \"icmp\" (incremented as well, default from 1), or some constant" +
|
||||
"destination port for other methods (with default of 80 for \"tcp\", 53 for \"udp\", etc.)"})
|
||||
numMeasurements := parser.Int("q", "queries", &argparse.Options{Default: 3, Help: "Set the number of probes per each hop"})
|
||||
parallelRequests := parser.Int("", "parallel-requests", &argparse.Options{Default: 18, Help: "Set ParallelRequests number. It should be 1 when there is a multi-routing"})
|
||||
maxHops := parser.Int("m", "max-hops", &argparse.Options{Default: 30, Help: "Set the max number of hops (max TTL to be reached)"})
|
||||
dataOrigin := parser.Selector("d", "data-provider", []string{"Ip2region", "ip2region", "IP.SB", "ip.sb", "IPInfo", "ipinfo", "IPInsight", "ipinsight", "IPAPI.com", "ip-api.com", "IPInfoLocal", "ipinfolocal", "chunzhen"}, &argparse.Options{Default: "LeoMoeAPI",
|
||||
Help: "Choose IP Geograph Data Provider [IP.SB, IPInfo, IPInsight, IP-API.com, Ip2region, IPInfoLocal, CHUNZHEN]"})
|
||||
noRdns := parser.Flag("n", "no-rdns", &argparse.Options{Help: "Do not resolve IP addresses to their domain names"})
|
||||
alwaysRdns := parser.Flag("a", "always-rdns", &argparse.Options{Help: "Always resolve IP addresses to their domain names"})
|
||||
routePath := parser.Flag("P", "route-path", &argparse.Options{Help: "Print traceroute hop path by ASN and location"})
|
||||
report := parser.Flag("r", "report", &argparse.Options{Help: "output using report mode"})
|
||||
dn42 := parser.Flag("", "dn42", &argparse.Options{Help: "DN42 Mode"})
|
||||
output := parser.Flag("o", "output", &argparse.Options{Help: "Write trace result to file (RealTimePrinter ONLY)"})
|
||||
tablePrint := parser.Flag("t", "table", &argparse.Options{Help: "Output trace results as table"})
|
||||
classicPrint := parser.Flag("c", "classic", &argparse.Options{Help: "An Output Easy to Parse"})
|
||||
beginHop := parser.Int("f", "first", &argparse.Options{Default: 1, Help: "Start from the first_ttl hop (instead from 1)"})
|
||||
maptrace := parser.Flag("M", "map", &argparse.Options{Help: "Disable Print Trace Map"})
|
||||
ver := parser.Flag("v", "version", &argparse.Options{Help: "Print version info and exit"})
|
||||
src_addr := parser.String("s", "source", &argparse.Options{Help: "Use source src_addr for outgoing packets"})
|
||||
src_dev := parser.String("D", "dev", &argparse.Options{Help: "Use the following Network Devices as the source address in outgoing packets"})
|
||||
router := parser.Flag("R", "route", &argparse.Options{Help: "Show Routing Table [Provided By BGP.Tools]"})
|
||||
packet_interval := parser.Int("z", "send-time", &argparse.Options{Default: 100, Help: "Set the time interval for sending every packet. Useful when some routers use rate-limit for ICMP messages"})
|
||||
ttl_interval := parser.Int("i", "ttl-time", &argparse.Options{Default: 500, Help: "Set the time interval for sending packets groups by TTL. Useful when some routers use rate-limit for ICMP messages"})
|
||||
str := parser.StringPositional(&argparse.Options{Help: "IP Address or domain name"})
|
||||
dot := parser.Selector("", "dot-server", []string{"dnssb", "aliyun", "dnspod", "google", "cloudflare"}, &argparse.Options{
|
||||
Help: "Use DoT Server for DNS Parse [dnssb, aliyun, dnspod, google, cloudflare]"})
|
||||
lang := parser.Selector("g", "language", []string{"en", "cn"}, &argparse.Options{Default: "cn",
|
||||
Help: "Choose the language for displaying [en, cn]"})
|
||||
|
||||
err := parser.Parse(os.Args)
|
||||
if err != nil {
|
||||
// In case of error print error and print usage
|
||||
// This can also be done by passing -h or --help flags
|
||||
fmt.Print(parser.Usage(err))
|
||||
return
|
||||
}
|
||||
printer.Version()
|
||||
if *ver {
|
||||
printer.CopyRight()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
domain := *str
|
||||
|
||||
if *port == 0 {
|
||||
*port = 80
|
||||
}
|
||||
|
||||
if *fast_trace {
|
||||
fastTrace.FastTest(*tcp, *output)
|
||||
if *output {
|
||||
fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中")
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if domain == "" {
|
||||
fmt.Print(parser.Usage(err))
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(domain, "/") {
|
||||
domain = strings.Split(domain, "/")[2]
|
||||
}
|
||||
|
||||
if strings.Contains(domain, "]") {
|
||||
domain = strings.Split(strings.Split(domain, "]")[0], "[")[1]
|
||||
} else if strings.Contains(domain, ":") {
|
||||
if strings.Count(domain, ":") == 1 {
|
||||
domain = strings.Split(domain, ":")[0]
|
||||
}
|
||||
}
|
||||
|
||||
capabilities_check()
|
||||
// return
|
||||
|
||||
var ip net.IP
|
||||
|
||||
if runtime.GOOS == "windows" && (*tcp || *udp) {
|
||||
fmt.Println("NextTrace 基于 Windows 的路由跟踪还在早期开发阶段,目前还存在诸多问题,TCP/UDP SYN 包请求可能不能正常运行")
|
||||
}
|
||||
|
||||
if *udp {
|
||||
ip = util.DomainLookUp(domain, true, *dot)
|
||||
} else {
|
||||
ip = util.DomainLookUp(domain, false, *dot)
|
||||
}
|
||||
|
||||
if *src_dev != "" {
|
||||
dev, _ := net.InterfaceByName(*src_dev)
|
||||
|
||||
if addrs, err := dev.Addrs(); err == nil {
|
||||
for _, addr := range addrs {
|
||||
if (addr.(*net.IPNet).IP.To4() == nil) == (ip.To4() == nil) {
|
||||
*src_addr = addr.(*net.IPNet).IP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if *dn42 {
|
||||
// 初始化配置
|
||||
config.InitConfig()
|
||||
*dataOrigin = "DN42"
|
||||
*maptrace = true
|
||||
}
|
||||
|
||||
if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" {
|
||||
val, ok := os.LookupEnv("NEXTTRACE_DATAPROVIDER")
|
||||
if ok {
|
||||
*dataOrigin = val
|
||||
} else {
|
||||
w := wshandle.New()
|
||||
w.Interrupt = make(chan os.Signal, 1)
|
||||
signal.Notify(w.Interrupt, os.Interrupt)
|
||||
defer func() {
|
||||
w.Conn.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
|
||||
|
||||
var m trace.Method = ""
|
||||
|
||||
switch {
|
||||
case *tcp:
|
||||
m = trace.TCPTrace
|
||||
case *udp:
|
||||
m = trace.UDPTrace
|
||||
default:
|
||||
m = trace.ICMPTrace
|
||||
}
|
||||
|
||||
if !*tcp && *port == 80 {
|
||||
*port = 53
|
||||
}
|
||||
|
||||
var conf = trace.Config{
|
||||
DN42: *dn42,
|
||||
SrcAddr: *src_addr,
|
||||
BeginHop: *beginHop,
|
||||
DestIP: ip,
|
||||
DestPort: *port,
|
||||
MaxHops: *maxHops,
|
||||
PacketInterval: *packet_interval,
|
||||
TTLInterval: *ttl_interval,
|
||||
NumMeasurements: *numMeasurements,
|
||||
ParallelRequests: *parallelRequests,
|
||||
Lang: *lang,
|
||||
RDns: !*noRdns,
|
||||
AlwaysWaitRDNS: *alwaysRdns,
|
||||
IPGeoSource: ipgeo.GetSource(*dataOrigin),
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
if !*tablePrint {
|
||||
if *classicPrint {
|
||||
conf.RealtimePrinter = printer.EasyPrinter
|
||||
} else {
|
||||
if *output {
|
||||
conf.RealtimePrinter = tracelog.RealtimePrinter
|
||||
} else if *router {
|
||||
conf.RealtimePrinter = printer.RealtimePrinterWithRouter
|
||||
fmt.Println("路由表数据源由 BGP.Tools 提供,在此特表感谢")
|
||||
} else {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !*report {
|
||||
conf.AsyncPrinter = printer.TracerouteTablePrinter
|
||||
}
|
||||
}
|
||||
|
||||
res, err := trace.Traceroute(m, conf)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if *tablePrint {
|
||||
printer.TracerouteTablePrinter(res)
|
||||
}
|
||||
|
||||
if *routePath {
|
||||
r := reporter.New(res, ip.String())
|
||||
r.Print()
|
||||
}
|
||||
|
||||
if !*maptrace {
|
||||
r, _ := json.Marshal(res)
|
||||
tracemap.GetMapUrl(string(r))
|
||||
}
|
||||
}
|
||||
|
||||
func capabilities_check() {
|
||||
|
||||
// Windows 判断放在前面,防止遇到一些奇奇怪怪的问题
|
||||
if runtime.GOOS == "windows" {
|
||||
// Running on Windows, skip checking capabilities
|
||||
return
|
||||
}
|
||||
|
||||
uid := os.Getuid()
|
||||
if uid == 0 {
|
||||
// Running as root, skip checking capabilities
|
||||
return
|
||||
}
|
||||
|
||||
/***
|
||||
* 检查当前进程是否有两个关键的权限
|
||||
==== 看不到我 ====
|
||||
* 没办法啦
|
||||
* 自己之前承诺的坑补全篇
|
||||
* 被迫填坑系列 qwq
|
||||
==== 看不到我 ====
|
||||
***/
|
||||
|
||||
// NewPid 已经被废弃了,这里改用 NewPid2 方法
|
||||
caps, err := capability.NewPid2(0)
|
||||
if err != nil {
|
||||
// 判断是否为macOS
|
||||
if runtime.GOOS == "darwin" {
|
||||
// macOS下报错有问题
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// load 获取全部的 caps 信息
|
||||
err = caps.Load()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 判断一下权限有木有
|
||||
if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) && caps.Get(capability.EFFECTIVE, capability.CAP_NET_ADMIN) {
|
||||
// 有权限啦
|
||||
return
|
||||
} else {
|
||||
// 没权限啦
|
||||
fmt.Println("您正在以普通用户权限运行 NextTrace,但 NextTrace 未被赋予监听网络套接字的ICMP消息包、修改IP头信息(TTL)等路由跟踪所需的权限")
|
||||
fmt.Println("请使用管理员用户执行 `sudo setcap cap_net_raw,cap_net_admin+eip ${your_nexttrace_path}/nexttrace` 命令,赋予相关权限后再运行~")
|
||||
fmt.Println("什么?为什么 ping 普通用户执行不要 root 权限?因为这些工具在管理员安装时就已经被赋予了一些必要的权限,具体请使用 `getcap /usr/bin/ping` 查看")
|
||||
}
|
||||
}
|
||||
7
cmd/cmd_test.go
Normal file
7
cmd/cmd_test.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCMD(t *testing.T) {
|
||||
Excute()
|
||||
}
|
||||
40
config/viper.go
Normal file
40
config/viper.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func InitConfig() {
|
||||
|
||||
// 配置文件名, 不加扩展
|
||||
viper.SetConfigName("nt_config") // name of config file (without extension)
|
||||
// 设置文件的扩展名
|
||||
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
|
||||
// 查找配置文件所在路径
|
||||
viper.AddConfigPath("/etc/bin/nexttrace/")
|
||||
viper.AddConfigPath("/usr/local/bin/nexttrace/")
|
||||
// 在当前路径进行查找
|
||||
viper.AddConfigPath(".")
|
||||
// viper.AddConfigPath("./config/")
|
||||
|
||||
// 配置默认值
|
||||
viper.SetDefault("ptrPath", "./ptr.csv")
|
||||
viper.SetDefault("geoFeedPath", "./geofeed.csv")
|
||||
|
||||
// 开始查找并读取配置文件
|
||||
err := viper.ReadInConfig() // Find and read the config file
|
||||
if err != nil { // Handle errors reading the config file
|
||||
fmt.Println("未能找到配置文件,我们将在您的运行目录为您创建 nt_config.yaml 默认配置")
|
||||
err := viper.SafeWriteConfigAs("./nt_config.yaml")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = viper.ReadInConfig()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
7
dn42/dn42.go
Normal file
7
dn42/dn42.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package dn42
|
||||
|
||||
/***
|
||||
[DN42 Package]
|
||||
谨献给 DN42 所有的小伙伴们,祝你们终有一天能有自己的公网 ASN ~
|
||||
By Leo
|
||||
***/
|
||||
101
dn42/geofeed.go
Normal file
101
dn42/geofeed.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package dn42
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type GeoFeedRow struct {
|
||||
IPNet *net.IPNet
|
||||
CIDR string
|
||||
LtdCode string
|
||||
ISO3166 string
|
||||
City string
|
||||
ASN string
|
||||
IPWhois string
|
||||
}
|
||||
|
||||
func GetGeoFeed(ip string) (GeoFeedRow, bool) {
|
||||
rows, err := ReadGeoFeed()
|
||||
if err != nil {
|
||||
// 处理错误
|
||||
panic(err)
|
||||
}
|
||||
|
||||
row, find := FindGeoFeedRow(ip, rows)
|
||||
return row, find
|
||||
|
||||
}
|
||||
|
||||
func ReadGeoFeed() ([]GeoFeedRow, error) {
|
||||
path := viper.Get("geoFeedPath").(string)
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
r := csv.NewReader(f)
|
||||
rows, err := r.ReadAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将 CSV 中的每一行转换为 GeoFeedRow 类型,并保存到 rowsSlice 中
|
||||
var rowsSlice []GeoFeedRow
|
||||
for _, row := range rows {
|
||||
cidr := row[0] // 假设第一列是 CIDR 字段
|
||||
_, ipnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
// 如果解析 CIDR 失败,跳过这一行
|
||||
continue
|
||||
}
|
||||
if len(row) == 4 {
|
||||
rowsSlice = append(rowsSlice, GeoFeedRow{
|
||||
IPNet: ipnet,
|
||||
CIDR: cidr,
|
||||
LtdCode: row[1],
|
||||
ISO3166: row[2],
|
||||
City: row[3],
|
||||
})
|
||||
} else {
|
||||
rowsSlice = append(rowsSlice, GeoFeedRow{
|
||||
IPNet: ipnet,
|
||||
CIDR: cidr,
|
||||
LtdCode: row[1],
|
||||
ISO3166: row[2],
|
||||
City: row[3],
|
||||
ASN: row[4],
|
||||
IPWhois: row[5],
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
// 根据 CIDR 范围从小到大排序,方便后面查找
|
||||
sort.Slice(rowsSlice, func(i, j int) bool {
|
||||
return rowsSlice[i].IPNet.Mask.String() > rowsSlice[j].IPNet.Mask.String()
|
||||
})
|
||||
|
||||
return rowsSlice, nil
|
||||
}
|
||||
|
||||
func FindGeoFeedRow(ipStr string, rows []GeoFeedRow) (GeoFeedRow, bool) {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
// 如果传入的 IP 无效,直接返回
|
||||
return GeoFeedRow{}, false
|
||||
}
|
||||
|
||||
// 遍历每个 CIDR 范围,找到第一个包含传入的 IP 的 CIDR
|
||||
for _, row := range rows {
|
||||
if row.IPNet.Contains(ip) {
|
||||
return row, true
|
||||
}
|
||||
}
|
||||
|
||||
return GeoFeedRow{}, false
|
||||
}
|
||||
17
dn42/geofeed_test.go
Normal file
17
dn42/geofeed_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package dn42
|
||||
|
||||
// func TestGeoFeed(t *testing.T) {
|
||||
// rows, err := ReadGeoFeed()
|
||||
// if err != nil {
|
||||
// // 处理错误
|
||||
// }
|
||||
|
||||
// row, found := FindGeoFeedRow("2001:0418:1403:8080::6fff", rows)
|
||||
// if found {
|
||||
// // 处理符合条件的 row
|
||||
// log.Println(row)
|
||||
// } else {
|
||||
// // 处理未找到的情况
|
||||
// }
|
||||
|
||||
// }
|
||||
83
dn42/ptr.go
Normal file
83
dn42/ptr.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package dn42
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type PtrRow struct {
|
||||
IATACode string
|
||||
LtdCode string
|
||||
Region string
|
||||
City string
|
||||
}
|
||||
|
||||
func matchesPattern(prefix string, s string) bool {
|
||||
pattern := fmt.Sprintf(`^(.*[-.\d]|^)%s[-.\d].*$`, prefix)
|
||||
|
||||
r, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid regular expression:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return r.MatchString(s)
|
||||
}
|
||||
|
||||
func FindPtrRecord(ptr string) (PtrRow, error) {
|
||||
path := viper.Get("ptrPath").(string)
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return PtrRow{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
r := csv.NewReader(f)
|
||||
rows, err := r.ReadAll()
|
||||
if err != nil {
|
||||
return PtrRow{}, err
|
||||
}
|
||||
// 转小写
|
||||
ptr = strings.ToLower(ptr)
|
||||
// 先查城市名
|
||||
for _, row := range rows {
|
||||
city := row[3]
|
||||
if city == "" {
|
||||
continue
|
||||
}
|
||||
city = strings.ReplaceAll(city, " ", "")
|
||||
city = strings.ToLower(city)
|
||||
|
||||
if matchesPattern(city, ptr) {
|
||||
return PtrRow{
|
||||
LtdCode: row[1],
|
||||
Region: row[2],
|
||||
City: row[3],
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
// 查 IATA Code
|
||||
for _, row := range rows {
|
||||
iata := row[0]
|
||||
if iata == "" {
|
||||
continue
|
||||
}
|
||||
iata = strings.ToLower(iata)
|
||||
if matchesPattern(iata, ptr) {
|
||||
return PtrRow{
|
||||
IATACode: iata,
|
||||
LtdCode: row[1],
|
||||
Region: row[2],
|
||||
City: row[3],
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return PtrRow{}, errors.New("ptr not found")
|
||||
}
|
||||
50
dn42/ptr_test.go
Normal file
50
dn42/ptr_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package dn42
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPTR(t *testing.T) {
|
||||
|
||||
// example_mis := []string{
|
||||
// "sloutravel.com",
|
||||
// "memeslou.org",
|
||||
// "followsloucity.net",
|
||||
// "slouslou.slou",
|
||||
// "slouslou8.slou",
|
||||
// }
|
||||
|
||||
// examples := []string{
|
||||
|
||||
// "1ge.slou.as1299.net",
|
||||
// "1ge.slou2.as1299.net",
|
||||
// "1ge-slou.as1299.net",
|
||||
// "slou-1.as1299.net",
|
||||
// "slou.as1299.com",
|
||||
// "1ge-snge-6.as1299.net",
|
||||
// "c-1.sin.sg.atlas.moeqing.com",
|
||||
// "core.hkg1.hk.atlas.moeqing.com",
|
||||
// "core.losangles.us.atlas.moeqing.com",
|
||||
// }
|
||||
|
||||
// fmt.Println("容易误匹配的 PTR")
|
||||
|
||||
// for _, s := range example_mis {
|
||||
// if r, err := FindPtrRecord("ptr.csv"); err == nil {
|
||||
// fmt.Println(s, r)
|
||||
// } else {
|
||||
// fmt.Println(s, err)
|
||||
// }
|
||||
|
||||
// }
|
||||
// fmt.Println("\n应该正常匹配的 PTR")
|
||||
// for _, s := range examples {
|
||||
// if r, err := FindPtrRecord("ptr.csv"); err == nil {
|
||||
// fmt.Println(s, r)
|
||||
// } else {
|
||||
// fmt.Println(s, err)
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@ type BackBoneCollection struct {
|
||||
CU169 ISPCollection
|
||||
CU9929 ISPCollection
|
||||
CM ISPCollection
|
||||
CMIN2 ISPCollection
|
||||
EDU ISPCollection
|
||||
CST ISPCollection
|
||||
}
|
||||
@@ -23,6 +24,7 @@ type BackBoneCollection struct {
|
||||
type ISPCollection struct {
|
||||
ISPName string
|
||||
IP string
|
||||
IPv6 string
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -31,6 +33,7 @@ const (
|
||||
CU169 string = "联通 169 AS4837"
|
||||
CU9929 string = "联通 A网 AS9929"
|
||||
CM string = "移动 骨干网 AS9808"
|
||||
CMIN2 string = "移动 CMIN2 AS58807"
|
||||
EDU string = "教育网 CERNET AS4538"
|
||||
)
|
||||
|
||||
@@ -47,21 +50,35 @@ var Beijing = BackBoneCollection{
|
||||
CT163: ISPCollection{
|
||||
ISPName: CT163,
|
||||
IP: "106.37.67.1",
|
||||
IPv6: "240e:40:e002:1:a:3ee3:c00:0",
|
||||
},
|
||||
|
||||
CU169: ISPCollection{
|
||||
ISPName: CU169,
|
||||
IP: "123.125.96.156",
|
||||
IPv6: "2408:8000:1010:2::10",
|
||||
},
|
||||
|
||||
CU9929: ISPCollection{
|
||||
ISPName: CU9929,
|
||||
IP: "221.220.51.1",
|
||||
},
|
||||
|
||||
CM: ISPCollection{
|
||||
ISPName: CM,
|
||||
IP: "211.136.25.153",
|
||||
IPv6: "2409:8000:3800:8::3",
|
||||
},
|
||||
|
||||
CMIN2: ISPCollection{
|
||||
ISPName: CMIN2,
|
||||
IP: "223.70.155.55",
|
||||
},
|
||||
|
||||
EDU: ISPCollection{
|
||||
ISPName: EDU,
|
||||
IP: "101.6.15.130",
|
||||
IPv6: "2001:da8:215:4078:250:56ff:fe97:654d",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -69,7 +86,8 @@ var Shanghai = BackBoneCollection{
|
||||
Location: "上海",
|
||||
CT163: ISPCollection{
|
||||
ISPName: CT163,
|
||||
IP: "101.226.28.198",
|
||||
IP: "202.101.21.178",
|
||||
IPv6: "240e:18:2:153::89",
|
||||
},
|
||||
|
||||
CTCN2: ISPCollection{
|
||||
@@ -80,21 +98,30 @@ var Shanghai = BackBoneCollection{
|
||||
CU169: ISPCollection{
|
||||
ISPName: CU169,
|
||||
IP: "139.226.206.150",
|
||||
IPv6: "2408:8000:9000:0:4000::437",
|
||||
},
|
||||
|
||||
CU9929: ISPCollection{
|
||||
ISPName: CU9929,
|
||||
IP: "210.13.86.1",
|
||||
IPv6: "2408:8120:2::d6",
|
||||
},
|
||||
|
||||
CM: ISPCollection{
|
||||
ISPName: CM,
|
||||
IP: "120.204.34.85",
|
||||
IPv6: "2409:801e:f0:1::4e1",
|
||||
},
|
||||
|
||||
CMIN2: ISPCollection{
|
||||
ISPName: CMIN2,
|
||||
IP: "183.194.134.1",
|
||||
},
|
||||
|
||||
EDU: ISPCollection{
|
||||
ISPName: EDU,
|
||||
IP: "202.120.58.155",
|
||||
IPv6: "2001:da8:8000:1:202:120:2:100",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -102,17 +129,20 @@ var Guangzhou = BackBoneCollection{
|
||||
Location: "广州",
|
||||
CT163: ISPCollection{
|
||||
ISPName: CT163,
|
||||
IP: "106.37.67.1",
|
||||
IP: "14.116.225.60",
|
||||
IPv6: "240e:f9:8010::3:110:1",
|
||||
},
|
||||
|
||||
CU169: ISPCollection{
|
||||
ISPName: CU169,
|
||||
IP: "157.18.0.22",
|
||||
IPv6: "2408:8001:3161:4::1",
|
||||
},
|
||||
|
||||
CM: ISPCollection{
|
||||
ISPName: CM,
|
||||
IP: "120.198.26.254",
|
||||
IPv6: "2409:8055:3008:1116::150",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -121,19 +151,23 @@ var Hangzhou = BackBoneCollection{
|
||||
CT163: ISPCollection{
|
||||
ISPName: CT163,
|
||||
IP: "61.164.23.196",
|
||||
IPv6: "240e:f3:c000:201::10",
|
||||
},
|
||||
CU169: ISPCollection{
|
||||
ISPName: CU169,
|
||||
IP: "60.12.244.1",
|
||||
IPv6: "",
|
||||
},
|
||||
CM: ISPCollection{
|
||||
ISPName: CM,
|
||||
IP: "112.17.224.98",
|
||||
IPv6: "2409:8028:840:2::11",
|
||||
},
|
||||
// 浙江大学 教育网
|
||||
EDU: ISPCollection{
|
||||
ISPName: EDU,
|
||||
IP: "210.32.2.1",
|
||||
IPv6: "2001:da8:e000:1::1",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -143,6 +177,7 @@ var Hefei = BackBoneCollection{
|
||||
EDU: ISPCollection{
|
||||
ISPName: EDU,
|
||||
IP: "202.38.64.1",
|
||||
IPv6: "2001:da8:d805:ffff:2::1",
|
||||
},
|
||||
// 中国科学技术大学 科技网
|
||||
CST: ISPCollection{
|
||||
|
||||
144
fast_trace/fast_trace ipv6.go
Normal file
144
fast_trace/fast_trace ipv6.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package fastTrace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/printer"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/tracelog"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
func (f *FastTracer) tracert_v6(location string, ispCollection ISPCollection) {
|
||||
fp, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func(fp *os.File) {
|
||||
err := fp.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}(fp)
|
||||
|
||||
log.SetOutput(fp)
|
||||
log.SetFlags(0)
|
||||
fmt.Printf("%s『%s %s 』%s\n", printer.YELLOW_PREFIX, location, ispCollection.ISPName, printer.RESET_PREFIX)
|
||||
log.Printf("『%s %s 』\n", location, ispCollection.ISPName)
|
||||
fmt.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ispCollection.IPv6)
|
||||
log.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ispCollection.IPv6)
|
||||
ip := net.ParseIP(ispCollection.IPv6)
|
||||
var conf = trace.Config{
|
||||
BeginHop: 1,
|
||||
DestIP: ip,
|
||||
DestPort: 80,
|
||||
MaxHops: 30,
|
||||
NumMeasurements: 3,
|
||||
ParallelRequests: 18,
|
||||
RDns: true,
|
||||
IPGeoSource: ipgeo.GetSource("LeoMoeAPI"),
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
if oe {
|
||||
conf.RealtimePrinter = tracelog.RealtimePrinter
|
||||
} else {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
|
||||
_, err = trace.Traceroute(f.TracerouteMethod, conf)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
println()
|
||||
}
|
||||
|
||||
func (f *FastTracer) testAll_v6() {
|
||||
f.testCT_v6()
|
||||
println()
|
||||
f.testCU_v6()
|
||||
println()
|
||||
f.testCM_v6()
|
||||
println()
|
||||
f.testEDU_v6()
|
||||
}
|
||||
|
||||
func (f *FastTracer) testCT_v6() {
|
||||
f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CT163)
|
||||
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CT163)
|
||||
f.tracert_v6(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CT163)
|
||||
f.tracert_v6(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CT163)
|
||||
}
|
||||
|
||||
func (f *FastTracer) testCU_v6() {
|
||||
f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU169)
|
||||
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU169)
|
||||
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU9929)
|
||||
f.tracert_v6(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CU169)
|
||||
}
|
||||
|
||||
func (f *FastTracer) testCM_v6() {
|
||||
f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CM)
|
||||
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CM)
|
||||
f.tracert_v6(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CM)
|
||||
f.tracert_v6(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CM)
|
||||
}
|
||||
|
||||
func (f *FastTracer) testEDU_v6() {
|
||||
f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
|
||||
f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.EDU)
|
||||
f.tracert_v6(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.EDU)
|
||||
}
|
||||
|
||||
func FastTestv6(tm bool, outEnable bool) {
|
||||
var c string
|
||||
|
||||
oe = outEnable
|
||||
|
||||
fmt.Println("您想测试哪些ISP的路由?\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网")
|
||||
fmt.Print("请选择选项:")
|
||||
_, err := fmt.Scanln(&c)
|
||||
if err != nil {
|
||||
c = "1"
|
||||
}
|
||||
|
||||
ft := FastTracer{}
|
||||
|
||||
// 建立 WebSocket 连接
|
||||
w := wshandle.New()
|
||||
w.Interrupt = make(chan os.Signal, 1)
|
||||
signal.Notify(w.Interrupt, os.Interrupt)
|
||||
defer func() {
|
||||
w.Conn.Close()
|
||||
}()
|
||||
|
||||
if !tm {
|
||||
ft.TracerouteMethod = trace.ICMPTrace
|
||||
fmt.Println("您将默认使用ICMP协议进行路由跟踪,如果您想使用TCP SYN进行路由跟踪,可以加入 -T 参数")
|
||||
} else {
|
||||
ft.TracerouteMethod = trace.TCPTrace
|
||||
}
|
||||
|
||||
switch c {
|
||||
case "1":
|
||||
ft.testAll_v6()
|
||||
case "2":
|
||||
ft.testCT_v6()
|
||||
case "3":
|
||||
ft.testCU_v6()
|
||||
case "4":
|
||||
ft.testCM_v6()
|
||||
case "5":
|
||||
ft.testEDU_v6()
|
||||
default:
|
||||
ft.testAll_v6()
|
||||
}
|
||||
}
|
||||
@@ -4,51 +4,77 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/printer"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/tracelog"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
type FastTracer struct {
|
||||
TracerouteMethod trace.Method
|
||||
}
|
||||
|
||||
var oe = false
|
||||
|
||||
func (f *FastTracer) tracert(location string, ispCollection ISPCollection) {
|
||||
fmt.Printf("『%s %s 』\n", location, ispCollection.ISPName)
|
||||
fp, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func(fp *os.File) {
|
||||
err := fp.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}(fp)
|
||||
|
||||
log.SetOutput(fp)
|
||||
log.SetFlags(0)
|
||||
fmt.Printf("%s『%s %s 』%s\n", printer.YELLOW_PREFIX, location, ispCollection.ISPName, printer.RESET_PREFIX)
|
||||
log.Printf("『%s %s 』\n", location, ispCollection.ISPName)
|
||||
fmt.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ispCollection.IP)
|
||||
log.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ispCollection.IP)
|
||||
ip := net.ParseIP(ispCollection.IP)
|
||||
var conf = trace.Config{
|
||||
BeginHop: 1,
|
||||
DestIP: ip,
|
||||
DestPort: 80,
|
||||
MaxHops: 30,
|
||||
NumMeasurements: 3,
|
||||
ParallelRequests: 18,
|
||||
RDns: true,
|
||||
PacketInterval: 100,
|
||||
TTLInterval: 500,
|
||||
IPGeoSource: ipgeo.GetSource("LeoMoeAPI"),
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
if f.TracerouteMethod == trace.ICMPTrace {
|
||||
if oe {
|
||||
conf.RealtimePrinter = tracelog.RealtimePrinter
|
||||
} else {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
|
||||
res, err := trace.Traceroute(f.TracerouteMethod, conf)
|
||||
_, err = trace.Traceroute(f.TracerouteMethod, conf)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if f.TracerouteMethod == trace.TCPTrace {
|
||||
printer.TracerouteTablePrinter(res)
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
func (f *FastTracer) testAll() {
|
||||
f.testCT()
|
||||
println()
|
||||
f.testCU()
|
||||
println()
|
||||
f.testCM()
|
||||
println()
|
||||
f.testEDU()
|
||||
}
|
||||
|
||||
@@ -62,6 +88,7 @@ func (f *FastTracer) testCT() {
|
||||
|
||||
func (f *FastTracer) testCU() {
|
||||
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU169)
|
||||
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU9929)
|
||||
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU169)
|
||||
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU9929)
|
||||
f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CU169)
|
||||
@@ -70,7 +97,9 @@ func (f *FastTracer) testCU() {
|
||||
|
||||
func (f *FastTracer) testCM() {
|
||||
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CM)
|
||||
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CMIN2)
|
||||
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CM)
|
||||
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CMIN2)
|
||||
f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CM)
|
||||
f.tracert(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CM)
|
||||
}
|
||||
@@ -79,20 +108,43 @@ func (f *FastTracer) testEDU() {
|
||||
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
|
||||
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.EDU)
|
||||
f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.EDU)
|
||||
f.tracert(TestIPsCollection.Hefei.Location, TestIPsCollection.Hefei.EDU)
|
||||
// 科技网暂时算在EDU里面,等拿到了足够多的数据再分离出去,单独用于测试
|
||||
f.tracert(TestIPsCollection.Hefei.Location, TestIPsCollection.Hefei.CST)
|
||||
}
|
||||
|
||||
func FastTest(tm bool) {
|
||||
func FastTest(tm bool, outEnable bool) {
|
||||
var c string
|
||||
|
||||
oe = outEnable
|
||||
fmt.Println("Hi,欢迎使用 Fast Trace 功能,请注意 Fast Trace 功能只适合新手使用\n因为国内网络复杂,我们设置的测试目标有限,建议普通用户自测以获得更加精准的路由情况")
|
||||
fmt.Println("请您选择要测试的 IP 类型\n1. IPv4\n2. IPv6")
|
||||
fmt.Print("请选择选项:")
|
||||
_, err := fmt.Scanln(&c)
|
||||
if err != nil {
|
||||
c = "1"
|
||||
}
|
||||
if c == "2" {
|
||||
FastTestv6(tm, outEnable)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("您想测试哪些ISP的路由?\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网")
|
||||
fmt.Print("请选择选项:")
|
||||
fmt.Scanln(&c)
|
||||
_, err = fmt.Scanln(&c)
|
||||
if err != nil {
|
||||
c = "1"
|
||||
}
|
||||
|
||||
ft := FastTracer{}
|
||||
|
||||
// 建立 WebSocket 连接
|
||||
w := wshandle.New()
|
||||
w.Interrupt = make(chan os.Signal, 1)
|
||||
signal.Notify(w.Interrupt, os.Interrupt)
|
||||
defer func() {
|
||||
w.Conn.Close()
|
||||
}()
|
||||
|
||||
if !tm {
|
||||
ft.TracerouteMethod = trace.ICMPTrace
|
||||
fmt.Println("您将默认使用ICMP协议进行路由跟踪,如果您想使用TCP SYN进行路由跟踪,可以加入 -T 参数")
|
||||
|
||||
32
fast_trace/fast_trace_test.go
Normal file
32
fast_trace/fast_trace_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package fastTrace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"testing"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
func TestTrace(t *testing.T) {
|
||||
ft := FastTracer{}
|
||||
// 建立 WebSocket 连接
|
||||
w := wshandle.New()
|
||||
w.Interrupt = make(chan os.Signal, 1)
|
||||
signal.Notify(w.Interrupt, os.Interrupt)
|
||||
defer func() {
|
||||
w.Conn.Close()
|
||||
}()
|
||||
fmt.Println("TCP v4")
|
||||
ft.TracerouteMethod = trace.TCPTrace
|
||||
ft.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
|
||||
//fmt.Println("TCP v6")
|
||||
//ft.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
|
||||
//fmt.Println("ICMP v4")
|
||||
//ft.TracerouteMethod = trace.ICMPTrace
|
||||
//ft.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
|
||||
//fmt.Println("ICMP v6")
|
||||
//ft.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU)
|
||||
}
|
||||
3
geofeed.example.csv
Normal file
3
geofeed.example.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
154.48.0.0/12,,,,174,COGENT-NET
|
||||
200.15.12.0/22,BR,BR-SP,Sao Paulo,2914,NTT-BACKBONE
|
||||
2001:0418:1403::/48,US,US-VA,Ashburn,2914,NTT-BACKBONE
|
||||
|
44
go.mod
44
go.mod
@@ -1,28 +1,42 @@
|
||||
module github.com/xgadget-lab/nexttrace
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/akamensky/argparse v1.4.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||
github.com/oschwald/maxminddb-golang v1.10.0
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/sync v0.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // direct
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/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/fatih/color v1.15.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/lionsoul2014/ip2region v2.11.1+incompatible
|
||||
github.com/rodaine/table v1.1.0
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
510
go.sum
510
go.sum
@@ -1,56 +1,514 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
|
||||
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/lionsoul2014/ip2region v2.11.1+incompatible h1:FEqj5PvIP6YwnGt3RYndGQaiftEIJJGpw4BCB76nvfM=
|
||||
github.com/lionsoul2014/ip2region v2.11.1+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
||||
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
|
||||
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
||||
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
|
||||
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
91
ipgeo/chunzhen.go
Normal file
91
ipgeo/chunzhen.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Chunzhen(ip string) (*IPGeoData, error) {
|
||||
url := util.GetenvDefault("NEXTTRACE_CHUNZHENURL", "http://127.0.0.1:2060") + "?ip=" + ip
|
||||
client := &http.Client{
|
||||
// 2 秒超时
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
content, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Println("纯真 请求超时(2s),请切换其他API使用")
|
||||
return &IPGeoData{}, err
|
||||
}
|
||||
body, _ := io.ReadAll(content.Body)
|
||||
|
||||
var data map[string]interface{}
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return &IPGeoData{}, err
|
||||
}
|
||||
city := data[ip].(map[string]interface{})["area"].(string)
|
||||
region := data[ip].(map[string]interface{})["country"].(string)
|
||||
var asn string
|
||||
if data[ip].(map[string]interface{})["asn"] != nil {
|
||||
asn = data[ip].(map[string]interface{})["asn"].(string)
|
||||
}
|
||||
// 判断是否前两个字为香港或台湾
|
||||
var country string
|
||||
provinces := []string{
|
||||
"北京",
|
||||
"天津",
|
||||
"河北",
|
||||
"山西",
|
||||
"内蒙古",
|
||||
"辽宁",
|
||||
"吉林",
|
||||
"黑龙江",
|
||||
"上海",
|
||||
"江苏",
|
||||
"浙江",
|
||||
"安徽",
|
||||
"福建",
|
||||
"江西",
|
||||
"山东",
|
||||
"河南",
|
||||
"湖北",
|
||||
"湖南",
|
||||
"广东",
|
||||
"广西",
|
||||
"海南",
|
||||
"重庆",
|
||||
"四川",
|
||||
"贵州",
|
||||
"云南",
|
||||
"西藏",
|
||||
"陕西",
|
||||
"甘肃",
|
||||
"青海",
|
||||
"宁夏",
|
||||
"新疆",
|
||||
"台湾",
|
||||
"香港",
|
||||
"澳门",
|
||||
}
|
||||
for _, province := range provinces {
|
||||
if strings.Contains(region, province) {
|
||||
country = "中国"
|
||||
city = region + city
|
||||
break
|
||||
}
|
||||
}
|
||||
if country == "" {
|
||||
country = region
|
||||
}
|
||||
return &IPGeoData{
|
||||
Asnumber: asn,
|
||||
Country: country,
|
||||
City: city,
|
||||
}, nil
|
||||
}
|
||||
62
ipgeo/dn42.go
Normal file
62
ipgeo/dn42.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/dn42"
|
||||
)
|
||||
|
||||
func LtdCodeToCountryOrAreaName(Code string) string {
|
||||
countryName := []string{"United States", "Afghanistan", "Åland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", " Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada ", "Cape Verde", "Cayman Islands", "Central Africa", "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo (Brazzaville)", "DRC", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", " Denmark", "Djibouti", "Dominica", "Dominica", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)", "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", " French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Vatican", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "British Isles of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "North Korea", "South Korea", " Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "FYROM", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", " Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia (Federated States of)", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", "Rwanda", "St. Helena", "St. Kitts and Nevis", "St. Lucia", "St. Pierre and Miquelon", "St. Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen Islands", "Swaziland", "Sweden ", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", " United Kingdom", "U.S. Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "British Virgin Islands", "U.S. Virgin Islands", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"}
|
||||
countryCode := []string{"us", "af", "ax", "al", "dz", "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "au", "at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi", "kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cn", "cx", "cc", "co", "km", "cg", "cd", "ck", "cr", "ci", "hr", "cu", "cy", "cz", "dk", "dj", "dm", "do", "ec", "eg", "sv", "gq", "er", "ee", "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm", "ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg", "gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in", "id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo", "kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls", "lr", "ly", "li", "lt", "lu", "mo", "mk", "mg", "mw", "my", "mv", "ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc", "mn", "me", "ms", "ma", "mz", "mm", "na", "nr", "np", "nl", "an", "nc", "nz", "ni", "ne", "ng", "nu", "nf", "mp", "no", "om", "pk", "pw", "ps", "pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re", "ro", "ru", "rw", "sh", "kn", "lc", "pm", "vc", "ws", "sm", "st", "sa", "sn", "rs", "sc", "sl", "sg", "sk", "si", "sb", "so", "za", "gs", "es", "lk", "sd", "sr", "sj", "sz", "se", "ch", "sy", "tw", "tj", "tz", "th", "tl", "tg", "tk", "to", "tt", "tn", "tr", "tm", "tc", "tv", "ug", "ua", "ae", "gb", "um", "uy", "uz", "vu", "ve", "vn", "vg", "vi", "wf", "eh", "ye", "zm", "zw"}
|
||||
Code = strings.ToLower(Code)
|
||||
for i, v := range countryCode {
|
||||
if strings.Contains(Code, v) {
|
||||
return countryName[i]
|
||||
}
|
||||
}
|
||||
return Code
|
||||
}
|
||||
|
||||
func DN42(ip string) (*IPGeoData, error) {
|
||||
data := &IPGeoData{}
|
||||
// 先解析传入过来的数据
|
||||
ipTmp := strings.Split(ip, ",")
|
||||
if len(ipTmp) > 1 {
|
||||
ip = ipTmp[0]
|
||||
}
|
||||
// 先查找 GeoFeed
|
||||
if geo, find := dn42.GetGeoFeed(ip); find {
|
||||
data.Country = geo.LtdCode
|
||||
data.City = geo.City
|
||||
data.Asnumber = geo.ASN
|
||||
data.Whois = geo.IPWhois
|
||||
}
|
||||
// 如果没找到,查找 PTR
|
||||
if len(ipTmp) > 1 {
|
||||
// 存在 PTR 记录
|
||||
if res, err := dn42.FindPtrRecord(ipTmp[1]); err == nil && res.LtdCode != "" {
|
||||
data.Country = res.LtdCode
|
||||
data.Prov = res.Region
|
||||
data.City = res.City
|
||||
}
|
||||
}
|
||||
|
||||
data.Country = LtdCodeToCountryOrAreaName(data.Country)
|
||||
|
||||
switch data.Country {
|
||||
case "Hong Kong":
|
||||
data.Country = "China"
|
||||
data.Prov = "Hong Kong"
|
||||
case "Taiwan":
|
||||
data.Country = "China"
|
||||
data.Prov = "Taiwan"
|
||||
case "Macao":
|
||||
data.Country = "China"
|
||||
data.Prov = "Macao"
|
||||
case "":
|
||||
data.Country = "Unknown"
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
5
ipgeo/dn42_test.go
Normal file
5
ipgeo/dn42_test.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package ipgeo
|
||||
|
||||
// func TestDN42(t *testing.T) {
|
||||
// DN42("")
|
||||
// }
|
||||
77
ipgeo/ip2region.go
Normal file
77
ipgeo/ip2region.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/v1.0/binding/golang/ip2region"
|
||||
)
|
||||
|
||||
const (
|
||||
ipDataBasePath = "./ip2region.db"
|
||||
defaultDownURL = "1"
|
||||
originURL = "https://ghproxy.com/?q=https://github.com/bqf9979/ip2region/blob/master/data/ip2region.db?raw=true"
|
||||
)
|
||||
|
||||
func downloadDataBase() error {
|
||||
fmt.Println("Downloading DataBase...")
|
||||
resp, err := http.Get(originURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Create the file
|
||||
out, err := os.Create(ipDataBasePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
func IP2Region(ip string) (*IPGeoData, error) {
|
||||
if _, err := os.Stat(ipDataBasePath); os.IsNotExist(err) {
|
||||
if err = downloadDataBase(); err != nil {
|
||||
panic("Download Failed!")
|
||||
}
|
||||
}
|
||||
region, err := ip2region.New(ipDataBasePath)
|
||||
if err != nil {
|
||||
panic("Cannot find ip2region.db")
|
||||
}
|
||||
defer region.Close()
|
||||
info, searchErr := region.MemorySearch(ip)
|
||||
if searchErr != nil {
|
||||
return &IPGeoData{}, errors.New("no results")
|
||||
}
|
||||
|
||||
if info.Country == "0" {
|
||||
info.Country = ""
|
||||
}
|
||||
|
||||
if info.Province == "0" {
|
||||
info.Province = ""
|
||||
}
|
||||
|
||||
if info.City == "0" {
|
||||
info.City = ""
|
||||
}
|
||||
|
||||
if info.ISP == "0" {
|
||||
info.ISP = ""
|
||||
}
|
||||
|
||||
return &IPGeoData{
|
||||
Owner: info.ISP,
|
||||
Country: info.Country,
|
||||
Prov: info.Province,
|
||||
City: info.City,
|
||||
}, nil
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package ipgeo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -24,7 +24,7 @@ func IPApiCom(ip string) (*IPGeoData, error) {
|
||||
log.Println("ip-api.com 请求超时(2s),请切换其他API使用")
|
||||
return nil, err
|
||||
}
|
||||
body, _ := ioutil.ReadAll(content.Body)
|
||||
body, _ := io.ReadAll(content.Body)
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
if res.Get("status").String() != "success" {
|
||||
@@ -38,6 +38,6 @@ func IPApiCom(ip string) (*IPGeoData, error) {
|
||||
Country: res.Get("country").String(),
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("regionName").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
Owner: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
86
ipgeo/ipfilter.go
Normal file
86
ipgeo/ipfilter.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func cidrRangeContains(cidrRange string, checkIP string) bool {
|
||||
_, ipNet, err := net.ParseCIDR(cidrRange)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
secondIP := net.ParseIP(checkIP)
|
||||
return ipNet.Contains(secondIP)
|
||||
}
|
||||
|
||||
// Filter 被选到的返回 geodata, true 否则返回 nil, false
|
||||
func Filter(ip string) (*IPGeoData, bool) {
|
||||
//geodata := &IPGeoData{}
|
||||
asn := ""
|
||||
whois := ""
|
||||
isFiltered := false
|
||||
switch {
|
||||
//rfc1918
|
||||
case net.ParseIP(ip).IsPrivate():
|
||||
asn = ""
|
||||
whois = "RFC1918"
|
||||
isFiltered = true
|
||||
//IANA Reserved Address Space
|
||||
case cidrRangeContains("100.64.0.0/10", ip):
|
||||
asn = ""
|
||||
whois = "RFC6598"
|
||||
isFiltered = true
|
||||
case cidrRangeContains("198.18.0.0/15", ip):
|
||||
asn = ""
|
||||
whois = "RFC2544"
|
||||
isFiltered = true
|
||||
case cidrRangeContains("198.51.100.0/24", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("203.0.113.0/24", ip):
|
||||
asn = ""
|
||||
whois = "RFC5737"
|
||||
isFiltered = true
|
||||
case cidrRangeContains("240.0.0.0/4", ip):
|
||||
asn = ""
|
||||
whois = "RFC1112"
|
||||
isFiltered = true
|
||||
//Defense Information System Network
|
||||
case cidrRangeContains("6.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("7.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("11.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("21.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("22.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("26.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("28.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("29.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("30.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("33.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("55.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("214.0.0.0/8", ip):
|
||||
fallthrough
|
||||
case cidrRangeContains("215.0.0.0/8", ip):
|
||||
asn = ""
|
||||
whois = "DOD"
|
||||
isFiltered = true
|
||||
default:
|
||||
}
|
||||
if !isFiltered {
|
||||
return nil, false
|
||||
} else {
|
||||
return &IPGeoData{
|
||||
Asnumber: asn,
|
||||
Whois: whois,
|
||||
}, true
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,32 @@ import (
|
||||
)
|
||||
|
||||
type IPGeoData struct {
|
||||
Asnumber string
|
||||
Country string
|
||||
Prov string
|
||||
City string
|
||||
District string
|
||||
Owner string
|
||||
Isp string
|
||||
IP string `json:"ip"`
|
||||
Asnumber string `json:"asnumber"`
|
||||
Country string `json:"country"`
|
||||
CountryEn string `json:"country_en"`
|
||||
Prov string `json:"prov"`
|
||||
ProvEn string `json:"prov_en"`
|
||||
City string `json:"city"`
|
||||
CityEn string `json:"city_en"`
|
||||
District string `json:"district"`
|
||||
Owner string `json:"owner"`
|
||||
Isp string `json:"isp"`
|
||||
Domain string `json:"domain"`
|
||||
Whois string `json:"whois"`
|
||||
Lat float64 `json:"lat"`
|
||||
Lng float64 `json:"lng"`
|
||||
Prefix string `json:"prefix"`
|
||||
Router map[string][]string `json:"router"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
type Source = func(ip string) (*IPGeoData, error)
|
||||
|
||||
func GetSource(s string) Source {
|
||||
switch strings.ToUpper(s) {
|
||||
case "DN42":
|
||||
return DN42
|
||||
case "LEOMOEAPI":
|
||||
return LeoIP
|
||||
case "IP.SB":
|
||||
@@ -26,8 +39,16 @@ func GetSource(s string) Source {
|
||||
return IPInSight
|
||||
case "IPAPI.COM":
|
||||
return IPApiCom
|
||||
case "IP-API.COM":
|
||||
return IPApiCom
|
||||
case "IPINFO":
|
||||
return IPInfo
|
||||
case "IP2REGION":
|
||||
return IP2Region
|
||||
case "IPINFOLOCAL":
|
||||
return IPInfoLocal
|
||||
case "CHUNZHEN":
|
||||
return Chunzhen
|
||||
default:
|
||||
return LeoIP
|
||||
}
|
||||
|
||||
@@ -1,52 +1,113 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLeoIP(t *testing.T) {
|
||||
res, err := LeoIP("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Asnumber)
|
||||
assert.NotEmpty(t, res.Isp)
|
||||
// import (
|
||||
// "testing"
|
||||
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
|
||||
func TestXxx(t *testing.T) {
|
||||
const ID_FIXED_HEADER = "10"
|
||||
var processID = fmt.Sprintf("%07b", os.Getpid()&0x7f) //取进程ID的前7位
|
||||
var ttl = fmt.Sprintf("%06b", 95) //取TTL的后6位
|
||||
fmt.Println(os.Getpid()&0x7f, 95)
|
||||
|
||||
var parity int
|
||||
id := ID_FIXED_HEADER + processID + ttl
|
||||
for _, c := range id {
|
||||
if c == '1' {
|
||||
parity++
|
||||
}
|
||||
}
|
||||
if parity%2 == 0 {
|
||||
id += "1"
|
||||
} else {
|
||||
id += "0"
|
||||
}
|
||||
process_id, ttl_r, _ := reverseID(id)
|
||||
log.Println(process_id, ttl_r)
|
||||
}
|
||||
|
||||
func TestIPSB(t *testing.T) {
|
||||
// Not available
|
||||
//res, err := IPSB("1.1.1.1")
|
||||
//assert.Nil(t, err)
|
||||
//assert.NotNil(t, res)
|
||||
//assert.NotEmpty(t, res.Asnumber)
|
||||
//assert.NotEmpty(t, res.Isp)
|
||||
func reverseID(id string) (int64, int64, error) {
|
||||
ttl, _ := strconv.ParseInt(id[9:15], 2, 32)
|
||||
//process ID
|
||||
processID, _ := strconv.ParseInt(id[2:9], 2, 32)
|
||||
|
||||
parity := 0
|
||||
for i := 0; i < len(id)-1; i++ {
|
||||
if id[i] == '1' {
|
||||
parity++
|
||||
}
|
||||
}
|
||||
|
||||
if parity%2 == 1 {
|
||||
if id[len(id)-1] == '0' {
|
||||
fmt.Println("Parity check passed.")
|
||||
} else {
|
||||
fmt.Println("Parity check failed.")
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
} else {
|
||||
if id[len(id)-1] == '1' {
|
||||
fmt.Println("Parity check passed.")
|
||||
} else {
|
||||
fmt.Println("Parity check failed.")
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
}
|
||||
return processID, ttl, nil
|
||||
}
|
||||
|
||||
func TestIPInfo(t *testing.T) {
|
||||
res, err := IPInfo("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
// assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.City)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
}
|
||||
// func TestLeoIP(t *testing.T) {
|
||||
// // res, err := LeoIP("1.1.1.1")
|
||||
// // assert.Nil(t, err)
|
||||
// // assert.NotNil(t, res)
|
||||
// // assert.NotEmpty(t, res.Asnumber)
|
||||
// // assert.NotEmpty(t, res.Isp)
|
||||
// }
|
||||
|
||||
func TestIPInSight(t *testing.T) {
|
||||
res, err := IPInSight("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
// 这个库有时候不提供城市信息,返回值为""
|
||||
//assert.NotEmpty(t, res.City)
|
||||
}
|
||||
// func TestIPSB(t *testing.T) {
|
||||
// // Not available
|
||||
// //res, err := IPSB("1.1.1.1")
|
||||
// //assert.Nil(t, err)
|
||||
// //assert.NotNil(t, res)
|
||||
// //assert.NotEmpty(t, res.Asnumber)
|
||||
// //assert.NotEmpty(t, res.Isp)
|
||||
// }
|
||||
|
||||
func TestIPApiCom(t *testing.T) {
|
||||
res, err := IPApiCom("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.City)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
}
|
||||
// func TestIPInfo(t *testing.T) {
|
||||
// res, err := IPInfo("1.1.1.1")
|
||||
// assert.Nil(t, err)
|
||||
// assert.NotNil(t, res)
|
||||
// // assert.NotEmpty(t, res.Country)
|
||||
// assert.NotEmpty(t, res.City)
|
||||
// assert.NotEmpty(t, res.Prov)
|
||||
// }
|
||||
|
||||
// func TestIPInSight(t *testing.T) {
|
||||
// // res, err := IPInSight("1.1.1.1")
|
||||
// // assert.Nil(t, err)
|
||||
// // assert.NotNil(t, res)
|
||||
// // assert.NotEmpty(t, res.Country)
|
||||
// // assert.NotEmpty(t, res.Prov)
|
||||
// // 这个库有时候不提供城市信息,返回值为""
|
||||
// //assert.NotEmpty(t, res.City)
|
||||
// }
|
||||
|
||||
// func TestIPApiCom(t *testing.T) {
|
||||
// res, err := IPApiCom("1.1.1.1")
|
||||
// assert.Nil(t, err)
|
||||
// assert.NotNil(t, res)
|
||||
// assert.NotEmpty(t, res.Country)
|
||||
// assert.NotEmpty(t, res.City)
|
||||
// assert.NotEmpty(t, res.Prov)
|
||||
// }
|
||||
|
||||
271
ipgeo/ipinfo.go
271
ipgeo/ipinfo.go
@@ -1,8 +1,9 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
@@ -13,7 +14,7 @@ func IPInfo(ip string) (*IPGeoData, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -21,16 +22,276 @@ func IPInfo(ip string) (*IPGeoData, error) {
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
var country string
|
||||
|
||||
country = res.Get("country").String()
|
||||
if res.Get("country").String() == "HK" || res.Get("country").String() == "TW" {
|
||||
country = "CN"
|
||||
}
|
||||
// ISO-3166 转换
|
||||
var countryMap = map[string]string{
|
||||
"AF": "Afghanistan",
|
||||
"AX": "Åland Islands",
|
||||
"AL": "Albania",
|
||||
"DZ": "Algeria",
|
||||
"AS": "American Samoa",
|
||||
"AD": "Andorra",
|
||||
"AO": "Angola",
|
||||
"AI": "Anguilla",
|
||||
"AQ": "Antarctica",
|
||||
"AG": "Antigua and Barbuda",
|
||||
"AR": "Argentina",
|
||||
"AM": "Armenia",
|
||||
"AW": "Aruba",
|
||||
"AU": "Australia",
|
||||
"AT": "Austria",
|
||||
"AZ": "Azerbaijan",
|
||||
"BH": "Bahrain",
|
||||
"BS": "Bahamas",
|
||||
"BD": "Bangladesh",
|
||||
"BB": "Barbados",
|
||||
"BY": "Belarus",
|
||||
"BE": "Belgium",
|
||||
"BZ": "Belize",
|
||||
"BJ": "Benin",
|
||||
"BM": "Bermuda",
|
||||
"BT": "Bhutan",
|
||||
"BO": "Bolivia",
|
||||
"BQ": "Bonaire",
|
||||
"BA": "Bosnia and Herzegovina",
|
||||
"BW": "Botswana",
|
||||
"BV": "Bouvet Island",
|
||||
"BR": "Brazil",
|
||||
"IO": "British Indian Ocean Territory",
|
||||
"BN": "Brunei Darussalam",
|
||||
"BG": "Bulgaria",
|
||||
"BF": "Burkina Faso",
|
||||
"BI": "Burundi",
|
||||
"KH": "Cambodia",
|
||||
"CM": "Cameroon",
|
||||
"CA": "Canada",
|
||||
"CV": "Cape Verde",
|
||||
"KY": "Cayman Islands",
|
||||
"CF": "Central African Republic",
|
||||
"TD": "Chad",
|
||||
"CL": "Chile",
|
||||
"CN": "China",
|
||||
"CX": "Christmas Island",
|
||||
"CC": "Cocos (Keeling) Islands",
|
||||
"CO": "Colombia",
|
||||
"KM": "Comoros",
|
||||
"CG": "Congo",
|
||||
"CD": "Congo",
|
||||
"CK": "Cook Islands",
|
||||
"CR": "Costa Rica",
|
||||
"CI": "Côte d'Ivoire",
|
||||
"HR": "Croatia",
|
||||
"CU": "Cuba",
|
||||
"CW": "Curaçao",
|
||||
"CY": "Cyprus",
|
||||
"CZ": "Czech Republic",
|
||||
"DK": "Denmark",
|
||||
"DJ": "Djibouti",
|
||||
"DM": "Dominica",
|
||||
"DO": "Dominican Republic",
|
||||
"EC": "Ecuador",
|
||||
"EG": "Egypt",
|
||||
"SV": "El Salvador",
|
||||
"GQ": "Equatorial Guinea",
|
||||
"ER": "Eritrea",
|
||||
"EE": "Estonia",
|
||||
"ET": "Ethiopia",
|
||||
"FK": "Falkland Islands (Malvinas)",
|
||||
"FO": "Faroe Islands",
|
||||
"FJ": "Fiji",
|
||||
"FI": "Finland",
|
||||
"FR": "France",
|
||||
"GF": "French Guiana",
|
||||
"PF": "French Polynesia",
|
||||
"TF": "French Southern Territories",
|
||||
"GA": "Gabon",
|
||||
"GM": "Gambia",
|
||||
"GE": "Georgia",
|
||||
"DE": "Germany",
|
||||
"GH": "Ghana",
|
||||
"GI": "Gibraltar",
|
||||
"GR": "Greece",
|
||||
"GL": "Greenland",
|
||||
"GD": "Grenada",
|
||||
"GP": "Guadeloupe",
|
||||
"GU": "Guam",
|
||||
"GT": "Guatemala",
|
||||
"GG": "Guernsey",
|
||||
"GN": "Guinea",
|
||||
"GW": "Guinea-Bissau",
|
||||
"GY": "Guyana",
|
||||
"HT": "Haiti",
|
||||
"HM": "Heard Island and McDonald Islands",
|
||||
"VA": "Holy See (Vatican City State)",
|
||||
"HN": "Honduras",
|
||||
"HK": "Hong Kong",
|
||||
"HU": "Hungary",
|
||||
"IS": "Iceland",
|
||||
"IN": "India",
|
||||
"ID": "Indonesia",
|
||||
"IR": "Iran",
|
||||
"IQ": "Iraq",
|
||||
"IE": "Ireland",
|
||||
"IM": "Isle of Man",
|
||||
"IL": "Israel",
|
||||
"IT": "Italy",
|
||||
"JM": "Jamaica",
|
||||
"JP": "Japan",
|
||||
"JE": "Jersey",
|
||||
"JO": "Jordan",
|
||||
"KZ": "Kazakhstan",
|
||||
"KE": "Kenya",
|
||||
"KI": "Kiribati",
|
||||
"KP": "Korea",
|
||||
"KR": "Korea",
|
||||
"KW": "Kuwait",
|
||||
"KG": "Kyrgyzstan",
|
||||
"LA": "Lao People's Democratic Republic",
|
||||
"LV": "Latvia",
|
||||
"LB": "Lebanon",
|
||||
"LS": "Lesotho",
|
||||
"LR": "Liberia",
|
||||
"LY": "Libya",
|
||||
"LI": "Liechtenstein",
|
||||
"LT": "Lithuania",
|
||||
"LU": "Luxembourg",
|
||||
"MO": "Macao",
|
||||
"MK": "Macedonia",
|
||||
"MG": "Madagascar",
|
||||
"MW": "Malawi",
|
||||
"MY": "Malaysia",
|
||||
"MV": "Maldives",
|
||||
"ML": "Mali",
|
||||
"MT": "Malta",
|
||||
"MH": "Marshall Islands",
|
||||
"MQ": "Martinique",
|
||||
"MR": "Mauritania",
|
||||
"MU": "Mauritius",
|
||||
"YT": "Mayotte",
|
||||
"MX": "Mexico",
|
||||
"FM": "Micronesia",
|
||||
"MD": "Moldova",
|
||||
"MC": "Monaco",
|
||||
"MN": "Mongolia",
|
||||
"ME": "Montenegro",
|
||||
"MS": "Montserrat",
|
||||
"MA": "Morocco",
|
||||
"MZ": "Mozambique",
|
||||
"MM": "Myanmar",
|
||||
"NA": "Namibia",
|
||||
"NR": "Nauru",
|
||||
"NP": "Nepal",
|
||||
"NL": "Netherlands",
|
||||
"NC": "New Caledonia",
|
||||
"NZ": "New Zealand",
|
||||
"NI": "Nicaragua",
|
||||
"NE": "Niger",
|
||||
"NG": "Nigeria",
|
||||
"NU": "Niue",
|
||||
"NF": "Norfolk Island",
|
||||
"MP": "Northern Mariana Islands",
|
||||
"NO": "Norway",
|
||||
"OM": "Oman",
|
||||
"PK": "Pakistan",
|
||||
"PW": "Palau",
|
||||
"PS": "Palestine",
|
||||
"PA": "Panama",
|
||||
"PG": "Papua New Guinea",
|
||||
"PY": "Paraguay",
|
||||
"PE": "Peru",
|
||||
"PH": "Philippines",
|
||||
"PN": "Pitcairn",
|
||||
"PL": "Poland",
|
||||
"PT": "Portugal",
|
||||
"PR": "Puerto Rico",
|
||||
"QA": "Qatar",
|
||||
"RE": "Réunion",
|
||||
"RO": "Romania",
|
||||
"RU": "Russian Federation",
|
||||
"RW": "Rwanda",
|
||||
"BL": "Saint Barthélemy",
|
||||
"SH": "Saint Helena",
|
||||
"KN": "Saint Kitts and Nevis",
|
||||
"LC": "Saint Lucia",
|
||||
"MF": "Saint Martin (French part)",
|
||||
"PM": "Saint Pierre and Miquelon",
|
||||
"VC": "Saint Vincent and the Grenadines",
|
||||
"WS": "Samoa",
|
||||
"SM": "San Marino",
|
||||
"ST": "Sao Tome and Principe",
|
||||
"SA": "Saudi Arabia",
|
||||
"SN": "Senegal",
|
||||
"RS": "Serbia",
|
||||
"SC": "Seychelles",
|
||||
"SL": "Sierra Leone",
|
||||
"SG": "Singapore",
|
||||
"SX": "Sint Maarten (Dutch part)",
|
||||
"SK": "Slovakia",
|
||||
"SI": "Slovenia",
|
||||
"SB": "Solomon Islands",
|
||||
"SO": "Somalia",
|
||||
"ZA": "South Africa",
|
||||
"GS": "South Georgia and the South Sandwich Islands",
|
||||
"SS": "South Sudan",
|
||||
"ES": "Spain",
|
||||
"LK": "Sri Lanka",
|
||||
"SD": "Sudan",
|
||||
"SR": "Suriname",
|
||||
"SJ": "Svalbard and Jan Mayen",
|
||||
"SZ": "Swaziland",
|
||||
"SE": "Sweden",
|
||||
"CH": "Switzerland",
|
||||
"SY": "Syrian Arab Republic",
|
||||
"TW": "Taiwan",
|
||||
"TJ": "Tajikistan",
|
||||
"TZ": "Tanzania",
|
||||
"TH": "Thailand",
|
||||
"TL": "Timor-Leste",
|
||||
"TG": "Togo",
|
||||
"TK": "Tokelau",
|
||||
"TO": "Tonga",
|
||||
"TT": "Trinidad and Tobago",
|
||||
"TN": "Tunisia",
|
||||
"TR": "Turkey",
|
||||
"TM": "Turkmenistan",
|
||||
"TC": "Turks and Caicos Islands",
|
||||
"TV": "Tuvalu",
|
||||
"UG": "Uganda",
|
||||
"UA": "Ukraine",
|
||||
"AE": "United Arab Emirates",
|
||||
"GB": "United Kingdom",
|
||||
"US": "United States of America",
|
||||
"UM": "United States Minor Outlying Islands",
|
||||
"UY": "Uruguay",
|
||||
"UZ": "Uzbekistan",
|
||||
"VU": "Vanuatu",
|
||||
"VE": "Venezuela",
|
||||
"VN": "Viet Nam",
|
||||
"VG": "Virgin Islands",
|
||||
"VI": "Virgin Islands",
|
||||
"WF": "Wallis and Futuna",
|
||||
"EH": "Western Sahara",
|
||||
"YE": "Yemen",
|
||||
"ZM": "Zambia",
|
||||
"ZW": "Zimbabwe",
|
||||
}
|
||||
country = countryMap[country]
|
||||
i := strings.Index(res.Get("org").String(), " ")
|
||||
var owner string
|
||||
if i == -1 {
|
||||
owner = ""
|
||||
} else {
|
||||
owner = res.Get("org").String()[i:]
|
||||
}
|
||||
|
||||
return &IPGeoData{
|
||||
Asnumber: res.Get("asn").Get("asn").String(),
|
||||
Asnumber: strings.Fields(strings.TrimPrefix(res.Get("org").String(), "AS"))[0],
|
||||
Country: country,
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("region").String(),
|
||||
Isp: res.Get("asn").Get("domain").String(),
|
||||
Owner: owner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
52
ipgeo/ipinfoLocal.go
Normal file
52
ipgeo/ipinfoLocal.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ipinfoDataBasePath = "./ipinfoLocal.mmdb"
|
||||
)
|
||||
|
||||
func IPInfoLocal(ip string) (*IPGeoData, error) {
|
||||
if _, err := os.Stat(ipinfoDataBasePath); os.IsNotExist(err) {
|
||||
panic("Cannot find ipinfoLocal.mmdb")
|
||||
}
|
||||
region, err := maxminddb.Open(ipinfoDataBasePath)
|
||||
if err != nil {
|
||||
panic("Cannot find ipinfoLocal.mmdb")
|
||||
}
|
||||
defer func(region *maxminddb.Reader) {
|
||||
err := region.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}(region)
|
||||
var record interface{}
|
||||
searchErr := region.Lookup(net.ParseIP(ip), &record)
|
||||
if searchErr != nil {
|
||||
return &IPGeoData{}, errors.New("no results")
|
||||
}
|
||||
recordMap := record.(map[string]interface{})
|
||||
country_name := recordMap["country_name"].(string)
|
||||
prov := ""
|
||||
if recordMap["country"].(string) == "HK" {
|
||||
country_name = "China"
|
||||
prov = "Hong Kong"
|
||||
}
|
||||
if recordMap["country"].(string) == "TW" {
|
||||
country_name = "China"
|
||||
prov = "Taiwan"
|
||||
}
|
||||
return &IPGeoData{
|
||||
Asnumber: strings.TrimPrefix(recordMap["asn"].(string), "AS"),
|
||||
Country: country_name,
|
||||
City: "",
|
||||
Prov: prov,
|
||||
Owner: recordMap["as_name"].(string),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPInSight(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://ipinsight.io/query?ip=" + ip)
|
||||
resp, err := http.Get("https://api.ipinsight.io/ip/" + ip + "?token=" + token.ipinsight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -24,7 +24,7 @@ func IPSB(ip string) (*IPGeoData, error) {
|
||||
log.Println("api.ip.sb 请求超时(2s),请切换其他API使用")
|
||||
return nil, err
|
||||
}
|
||||
body, _ := ioutil.ReadAll(content.Body)
|
||||
body, _ := io.ReadAll(content.Body)
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
if res.Get("country").String() == "" {
|
||||
@@ -37,6 +37,6 @@ func IPSB(ip string) (*IPGeoData, error) {
|
||||
Country: res.Get("country").String(),
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("region").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
Owner: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
117
ipgeo/leo.go
117
ipgeo/leo.go
@@ -1,31 +1,108 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
func LeoIP(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://api.leo.moe/ip/?ip=" + ip + "&token=" + token.ipleo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
/***
|
||||
* 原理介绍 By Leo
|
||||
* WebSocket 一共开启了一个发送和一个接收协程,在 New 了一个连接的实例对象后,不给予关闭,持续化连接
|
||||
* 当有新的IP请求时,一直在等待IP数据的发送协程接收到从 leo.go 的 sendIPRequest 函数发来的IP数据,向服务端发送数据
|
||||
* 由于实际使用时有大量并发,但是 ws 在同一时刻每次有且只能处理一次发送一条数据,所以必须给 ws 连接上互斥锁,保证每次只有一个协程访问
|
||||
* 运作模型可以理解为一个 Node 一直在等待数据,当获得一个新的任务后,转交给下一个协程,不再关注这个 Node 的下一步处理过程,并且回到空闲状态继续等待新的任务
|
||||
***/
|
||||
|
||||
// IPPool IP 查询池 map - ip - ip channel
|
||||
type IPPool struct {
|
||||
pool map[string]chan IPGeoData
|
||||
poolMux sync.Mutex
|
||||
}
|
||||
|
||||
var IPPools = IPPool{
|
||||
pool: make(map[string]chan IPGeoData),
|
||||
}
|
||||
|
||||
func sendIPRequest(ip string) {
|
||||
wsConn := wshandle.GetWsConn()
|
||||
wsConn.MsgSendCh <- ip
|
||||
}
|
||||
|
||||
func receiveParse() {
|
||||
// 获得连接实例
|
||||
wsConn := wshandle.GetWsConn()
|
||||
// 防止多协程抢夺一个ws连接,导致死锁,当一个协程获得ws的控制权后上锁
|
||||
wsConn.ConnMux.Lock()
|
||||
// 函数退出时解锁,给其他协程使用
|
||||
defer wsConn.ConnMux.Unlock()
|
||||
for {
|
||||
// 接收到了一条IP信息
|
||||
data := <-wsConn.MsgReceiveCh
|
||||
|
||||
// json解析 -> data
|
||||
res := gjson.Parse(data)
|
||||
// 根据返回的IP信息,发送给对应等待回复的IP通道上
|
||||
var domain = res.Get("domain").String()
|
||||
|
||||
if res.Get("domain").String() == "" {
|
||||
domain = res.Get("owner").String()
|
||||
}
|
||||
|
||||
m := make(map[string][]string)
|
||||
json.Unmarshal([]byte(res.Get("router").String()), &m)
|
||||
|
||||
lat, _ := strconv.ParseFloat(res.Get("lat").String(), 32)
|
||||
lng, _ := strconv.ParseFloat(res.Get("lng").String(), 32)
|
||||
|
||||
IPPools.pool[gjson.Parse(data).Get("ip").String()] <- IPGeoData{
|
||||
Asnumber: res.Get("asnumber").String(),
|
||||
Country: res.Get("country").String(),
|
||||
CountryEn: res.Get("country_en").String(),
|
||||
Prov: res.Get("prov").String(),
|
||||
ProvEn: res.Get("prov_en").String(),
|
||||
City: res.Get("city").String(),
|
||||
CityEn: res.Get("city_en").String(),
|
||||
District: res.Get("district").String(),
|
||||
Owner: domain,
|
||||
Lat: lat,
|
||||
Lng: lng,
|
||||
Isp: res.Get("isp").String(),
|
||||
Whois: res.Get("whois").String(),
|
||||
Prefix: res.Get("prefix").String(),
|
||||
Router: m,
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func LeoIP(ip string) (*IPGeoData, error) {
|
||||
// 初始化通道 - 向池子里添加IP的Channel,返回IP数据是通过字典中对应键为IP的Channel来获取的
|
||||
IPPools.poolMux.Lock()
|
||||
defer IPPools.poolMux.Unlock()
|
||||
// 如果之前已经被别的协程初始化过了就不用初始化了
|
||||
if IPPools.pool[ip] == nil {
|
||||
IPPools.pool[ip] = make(chan IPGeoData)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
sendIPRequest(ip)
|
||||
// 同步开启监听
|
||||
go receiveParse()
|
||||
|
||||
// 拥塞,等待数据返回
|
||||
select {
|
||||
case res := <-IPPools.pool[ip]:
|
||||
return &res, nil
|
||||
// 5秒后依旧没有接收到返回的IP数据,不再等待,超时异常处理
|
||||
case <-time.After(5 * time.Second):
|
||||
// default:
|
||||
// 这里不可以返回一个 nil,否则在访问对象内部的键值的时候会报空指针的 Fatal Error
|
||||
return &IPGeoData{}, errors.New("TimeOut")
|
||||
}
|
||||
|
||||
res := gjson.ParseBytes(body)
|
||||
return &IPGeoData{
|
||||
Asnumber: res.Get("asnumber").String(),
|
||||
Country: res.Get("country").String(),
|
||||
Prov: res.Get("prov").String(),
|
||||
City: res.Get("city").String(),
|
||||
District: res.Get("district").String(),
|
||||
Owner: res.Get("owner").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package ipgeo
|
||||
|
||||
import "github.com/xgadget-lab/nexttrace/util"
|
||||
|
||||
type tokenData struct {
|
||||
ipinsight string
|
||||
ipinfo string
|
||||
@@ -7,7 +9,7 @@ type tokenData struct {
|
||||
}
|
||||
|
||||
var token = tokenData{
|
||||
ipinsight: "",
|
||||
ipinfo: "",
|
||||
ipinsight: util.GetenvDefault("NEXTTRACE_IPINSIGHT_TOKEN", ""),
|
||||
ipinfo: util.GetenvDefault("NEXTTRACE_IPINFO_TOKEN", ""),
|
||||
ipleo: "NextTraceDemo",
|
||||
}
|
||||
|
||||
131
main.go
131
main.go
@@ -1,136 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
fastTrace "github.com/xgadget-lab/nexttrace/fast_trace"
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/printer"
|
||||
"github.com/xgadget-lab/nexttrace/reporter"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"github.com/xgadget-lab/nexttrace/cmd"
|
||||
)
|
||||
|
||||
var fSet = flag.NewFlagSet("", flag.ExitOnError)
|
||||
var 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 ver = fSet.Bool("V", false, "Print Version")
|
||||
|
||||
func printArgHelp() {
|
||||
fmt.Println("\nArgs Error\nUsage : 'nexttrace [option...] HOSTNAME' or 'nexttrace HOSTNAME [option...]'\nOPTIONS: [-VTU] [-d DATAORIGIN.STR ] [ -m TTL ] [ -p PORT ] [ -q PROBES.COUNT ] [ -r PARALLELREQUESTS.COUNT ] [-rdns] [ -table ] -report")
|
||||
fSet.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func flagApply() string {
|
||||
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() {
|
||||
|
||||
domain := flagApply()
|
||||
|
||||
if os.Getuid() != 0 {
|
||||
log.Fatalln("Traceroute requires root/sudo privileges.")
|
||||
}
|
||||
|
||||
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{
|
||||
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()
|
||||
}
|
||||
cmd.Excute()
|
||||
}
|
||||
|
||||
2
nt_config.yaml
Normal file
2
nt_config.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
geofeedpath: ./geofeed.csv
|
||||
ptrpath: ./ptr.csv
|
||||
@@ -17,10 +17,14 @@ checkSystemArch() {
|
||||
arch=$(uname -m)
|
||||
if [[ $arch == "x86_64" ]]; then
|
||||
archParam="amd64"
|
||||
fi
|
||||
|
||||
if [[ $arch == "aarch64" ]]; then
|
||||
elif [[ $arch == "i386" ]]; then
|
||||
archParam="386"
|
||||
elif [[ $arch == "aarch64" ]]; then
|
||||
archParam="arm64"
|
||||
elif [[ $arch == "armv7l" ]] || [[ $arch == "armv7ml" ]]; then
|
||||
archParam="armv7"
|
||||
elif [[ $arch == "mips" ]]; then
|
||||
archParam="mips"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -28,7 +32,14 @@ checkSystemDistribution() {
|
||||
case "$OSTYPE" in
|
||||
linux*)
|
||||
osDistribution="linux"
|
||||
|
||||
if [ ! -d "/usr/local" ];
|
||||
then
|
||||
downPath="/usr/bin/nexttrace"
|
||||
else
|
||||
downPath="/usr/local/bin/nexttrace"
|
||||
fi
|
||||
|
||||
;;
|
||||
*)
|
||||
echo "unknown: $OSTYPE"
|
||||
@@ -82,7 +93,7 @@ checkWgetPackage() {
|
||||
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}')
|
||||
latestURL=$(curl -s https://api.github.com/repos/sjlleo/nexttrace/releases/latest | grep -i "browser_download_url.*${osDistribution}.*${archParam}" | awk -F '"' '{print $4}')
|
||||
|
||||
if [ "$countryCode" == "CN" ]; then
|
||||
echo -e "${Info} 检测到国内环境,正在使用镜像下载"
|
||||
@@ -108,8 +119,8 @@ changeMode() {
|
||||
|
||||
runBinrayFileHelp() {
|
||||
if [ -e ${downPath} ]; then
|
||||
${downPath} -V
|
||||
echo -e "${Tips} 一切准备就绪!使用命令 nexttrace 1.1.1.1 开始您的第一次路由测试吧~ 更多进阶命令玩法可以用 nexttrace -h 查看哦\n 关于软件卸载,因为nexttrace是绿色版单文件,卸载只需输入命令 rm /usr/local/bin/nexttrace 即可"
|
||||
${downPath} --version
|
||||
echo -e "${Tips} 一切准备就绪!使用命令 nexttrace 1.1.1.1 开始您的第一次路由测试吧~ 更多进阶命令玩法可以用 nexttrace -h 查看哦\n 关于软件卸载,因为nexttrace是绿色版单文件,卸载只需输入命令 rm ${downPath} 即可"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package printer
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var version = "v0.0.0.alpha"
|
||||
@@ -10,8 +12,53 @@ var buildDate = ""
|
||||
var commitID = ""
|
||||
|
||||
func Version() {
|
||||
fmt.Println("NextTrace", version, buildDate, commitID)
|
||||
fmt.Println("XGadget-lab Leo (leo.moe) & Vincent (vincent.moe) & zhshch (xzhsh.ch)")
|
||||
fmt.Fprintf(color.Output, "%s %s %s %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "NextTrace"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", version),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", buildDate),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", commitID),
|
||||
)
|
||||
}
|
||||
|
||||
func CopyRight() {
|
||||
fmt.Fprintf(color.Output, "\n%s\n%s\n%s %s\n\n%s\n%s %s\n%s %s\n%s %s\n\n%s\n%s\n%s %s\n\n",
|
||||
color.New(color.FgCyan, color.Bold).Sprintf("%s", "NextTrace CopyRight"),
|
||||
color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Project Creator"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Leo"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "i@leo.moe"),
|
||||
color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Project Maintainer"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Tso"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "tsosunchia@gmail.com"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Vincent"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "i@vincent.moe"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Leo"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "i@leo.moe"),
|
||||
color.New(color.FgCyan, color.Bold).Sprintf("%s", "Special Acknowledgement List"),
|
||||
color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Major Contributor"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "zhshch"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "zhshch@athorx.com"),
|
||||
)
|
||||
|
||||
MoeQingOrgCopyRight()
|
||||
PluginCopyRight()
|
||||
}
|
||||
|
||||
func MoeQingOrgCopyRight() {
|
||||
fmt.Fprintf(color.Output, "%s\n%s %s\n%s %s\n\n",
|
||||
color.New(color.FgHiYellow, color.Bold).Sprintf("%s", "MoeQing Network"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "YekongTAT"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "yekongtat@gmail.com"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Haima"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "haima@peers.cloud"),
|
||||
)
|
||||
}
|
||||
|
||||
func PluginCopyRight() {
|
||||
fmt.Fprintf(color.Output, "%s\n%s %s\n\n",
|
||||
color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Map Plugin Author"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "Tso"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "tsosunchia@gmail.com"),
|
||||
)
|
||||
}
|
||||
|
||||
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) {
|
||||
|
||||
104
printer/classic_printer.go
Normal file
104
printer/classic_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 ClassicPrinter(res *trace.Result, ttl int) {
|
||||
fmt.Print(ttl + 1)
|
||||
hopsTypeMap := makeHopsType(res, ttl)
|
||||
for i := range res.Hops[ttl] {
|
||||
HopPrinter(res.Hops[ttl][i], hopsTypeMap[i])
|
||||
}
|
||||
}
|
||||
17
printer/easy.go
Normal file
17
printer/easy.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
func EasyPrinter(res *trace.Result, ttl int) {
|
||||
for i := range res.Hops[ttl] {
|
||||
if res.Hops[ttl][i].Address == nil {
|
||||
fmt.Printf("%d|*||||||\n", ttl+1)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("%d|%s|%s|%s|%s|%s|%s|%s|%s|%.4f|%.4f\n", ttl+1, res.Hops[ttl][i].Address.String(), res.Hops[ttl][i].Hostname, res.Hops[ttl][i].Geo.Asnumber, res.Hops[ttl][i].Geo.Country, res.Hops[ttl][i].Geo.Prov, res.Hops[ttl][i].Geo.City, res.Hops[ttl][i].Geo.District, res.Hops[ttl][i].Geo.Owner, res.Hops[ttl][i].Geo.Lat, res.Hops[ttl][i].Geo.Lng)
|
||||
}
|
||||
}
|
||||
@@ -9,18 +9,27 @@ import (
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
)
|
||||
|
||||
var dataOrigin string
|
||||
// var dataOrigin string
|
||||
|
||||
func TraceroutePrinter(res *trace.Result) {
|
||||
for i, hop := range res.Hops {
|
||||
fmt.Print(i + 1)
|
||||
for _, h := range hop {
|
||||
HopPrinter(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
// func TraceroutePrinter(res *trace.Result) {
|
||||
// for i, hop := range res.Hops {
|
||||
// fmt.Print(i + 1)
|
||||
// for _, h := range hop {
|
||||
// HopPrinter(h)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func HopPrinter(h trace.Hop) {
|
||||
const (
|
||||
RED_PREFIX = "\033[1;31m"
|
||||
GREEN_PREFIX = "\033[1;32m"
|
||||
YELLOW_PREFIX = "\033[1;33m"
|
||||
BLUE_PREFIX = "\033[1;34m"
|
||||
CYAN_PREFIX = "\033[1;36m"
|
||||
RESET_PREFIX = "\033[0m"
|
||||
)
|
||||
|
||||
func HopPrinter(h trace.Hop, info HopInfo) {
|
||||
if h.Address == nil {
|
||||
fmt.Println("\t*")
|
||||
} else {
|
||||
@@ -35,8 +44,22 @@ func HopPrinter(h trace.Hop) {
|
||||
if h.Geo != nil {
|
||||
txt += " " + formatIpGeoData(h.Address.String(), h.Geo)
|
||||
}
|
||||
switch info {
|
||||
case IXP:
|
||||
fmt.Print(CYAN_PREFIX)
|
||||
case PoP:
|
||||
fmt.Print(CYAN_PREFIX)
|
||||
case Peer:
|
||||
fmt.Print(YELLOW_PREFIX)
|
||||
case Aboard:
|
||||
fmt.Print(GREEN_PREFIX)
|
||||
}
|
||||
|
||||
fmt.Println(txt)
|
||||
|
||||
if info != General {
|
||||
fmt.Print(RESET_PREFIX)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +74,13 @@ func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
|
||||
|
||||
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
|
||||
// TODO: 移动IDC判断到Hop.fetchIPData函数,减少API调用
|
||||
if strings.HasPrefix(ip, "9.") {
|
||||
res = append(res, "LAN Address")
|
||||
} else if strings.HasPrefix(ip, "11.") {
|
||||
res = append(res, "LAN Address")
|
||||
} else if data.Country == "" {
|
||||
res = append(res, "LAN Address")
|
||||
//if strings.HasPrefix(ip, "9.") {
|
||||
// res = append(res, "LAN Address")
|
||||
//} else if strings.HasPrefix(ip, "11.") {
|
||||
// res = append(res, "LAN Address")
|
||||
//} else if data.Country == "" {
|
||||
// res = append(res, "LAN Address")
|
||||
if false {
|
||||
} else {
|
||||
// 有些IP的归属信息为空,这个时候将ISP的信息填入
|
||||
if data.Owner == "" {
|
||||
|
||||
@@ -1,105 +1,94 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
// func TestPrintTraceRouteNav(t *testing.T) {
|
||||
// PrintTraceRouteNav(util.DomainLookUp("1.1.1.1", false), "1.1.1.1", "dataOrigin")
|
||||
// }
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
)
|
||||
// var testGeo = &ipgeo.IPGeoData{
|
||||
// Asnumber: "TestAsnumber",
|
||||
// Country: "TestCountry",
|
||||
// Prov: "TestProv",
|
||||
// City: "TestCity",
|
||||
// District: "TestDistrict",
|
||||
// Owner: "TestOwner",
|
||||
// Isp: "TestIsp",
|
||||
// }
|
||||
|
||||
func TestPrintTraceRouteNav(t *testing.T) {
|
||||
PrintTraceRouteNav(util.DomainLookUp("1.1.1.1", false), "1.1.1.1", "dataOrigin")
|
||||
}
|
||||
// var testResult = &trace.Result{
|
||||
// Hops: [][]trace.Hop{
|
||||
// {
|
||||
// {
|
||||
// Success: true,
|
||||
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
// Hostname: "test",
|
||||
// TTL: 0,
|
||||
// RTT: 10 * time.Millisecond,
|
||||
// Error: nil,
|
||||
// Geo: testGeo,
|
||||
// },
|
||||
// {
|
||||
// Success: true,
|
||||
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
// Hostname: "test",
|
||||
// TTL: 0,
|
||||
// RTT: 10 * time.Millisecond,
|
||||
// Error: nil,
|
||||
// Geo: testGeo,
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// {
|
||||
// Success: false,
|
||||
// Address: nil,
|
||||
// Hostname: "",
|
||||
// TTL: 0,
|
||||
// RTT: 0,
|
||||
// Error: errors.New("test error"),
|
||||
// Geo: nil,
|
||||
// },
|
||||
// {
|
||||
// Success: true,
|
||||
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
// Hostname: "test",
|
||||
// TTL: 0,
|
||||
// RTT: 10 * time.Millisecond,
|
||||
// Error: nil,
|
||||
// Geo: nil,
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// {
|
||||
// Success: true,
|
||||
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
// Hostname: "test",
|
||||
// TTL: 0,
|
||||
// RTT: 0,
|
||||
// Error: nil,
|
||||
// Geo: &ipgeo.IPGeoData{},
|
||||
// },
|
||||
// {
|
||||
// Success: true,
|
||||
// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")},
|
||||
// Hostname: "",
|
||||
// TTL: 0,
|
||||
// RTT: 10 * time.Millisecond,
|
||||
// Error: nil,
|
||||
// Geo: testGeo,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
var testGeo = &ipgeo.IPGeoData{
|
||||
Asnumber: "TestAsnumber",
|
||||
Country: "TestCountry",
|
||||
Prov: "TestProv",
|
||||
City: "TestCity",
|
||||
District: "TestDistrict",
|
||||
Owner: "TestOwner",
|
||||
Isp: "TestIsp",
|
||||
}
|
||||
// // func TestTraceroutePrinter(t *testing.T) {
|
||||
// // TraceroutePrinter(testResult)
|
||||
// // }
|
||||
|
||||
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 TestTracerouteTablePrinter(t *testing.T) {
|
||||
// TracerouteTablePrinter(testResult)
|
||||
// }
|
||||
|
||||
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)
|
||||
}
|
||||
// func TestRealtimePrinter(t *testing.T) {
|
||||
// RealtimePrinter(testResult, 0)
|
||||
// // RealtimePrinter(testResult, 1)
|
||||
// // RealtimePrinter(testResult, 2)
|
||||
// }
|
||||
|
||||
@@ -2,14 +2,183 @@ package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
func RealtimePrinter(res *trace.Result, ttl int) {
|
||||
fmt.Print(ttl + 1)
|
||||
for i := range res.Hops[ttl] {
|
||||
HopPrinter(res.Hops[ttl][i])
|
||||
fmt.Printf("%s ", color.New(color.FgHiYellow, color.Bold).Sprintf("%-2d", ttl+1))
|
||||
|
||||
// 去重
|
||||
var latestIP string
|
||||
tmpMap := make(map[string][]string)
|
||||
for i, v := range res.Hops[ttl] {
|
||||
if v.Address == nil && latestIP != "" {
|
||||
tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*"))
|
||||
continue
|
||||
} else if v.Address == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exist := tmpMap[v.Address.String()]; !exist {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i))
|
||||
// 首次进入
|
||||
if latestIP == "" {
|
||||
for j := 0; j < i; j++ {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*"))
|
||||
}
|
||||
}
|
||||
latestIP = v.Address.String()
|
||||
}
|
||||
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000))
|
||||
}
|
||||
|
||||
if latestIP == "" {
|
||||
fmt.Fprintf(color.Output, "%s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("*"),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var blockDisplay = false
|
||||
for ip, v := range tmpMap {
|
||||
if blockDisplay {
|
||||
fmt.Printf("%4s", "")
|
||||
}
|
||||
if net.ParseIP(ip).To4() == nil {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%-25s", ip),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip),
|
||||
)
|
||||
}
|
||||
|
||||
i, _ := strconv.Atoi(v[0])
|
||||
if res.Hops[ttl][i].Geo.Asnumber != "" {
|
||||
// CMIN2, CUII, CN2, CUG 改为壕金色高亮
|
||||
switch {
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "58807":
|
||||
fallthrough
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "10099":
|
||||
fallthrough
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "4809":
|
||||
fallthrough
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "9929":
|
||||
fallthrough
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "23764":
|
||||
fallthrough
|
||||
case res.Hops[ttl][i].Geo.Whois == "CMIN2-NET":
|
||||
fallthrough
|
||||
case strings.HasPrefix(res.Hops[ttl][i].Address.String(), "59.43."):
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiYellow, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
|
||||
default:
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
|
||||
}
|
||||
|
||||
} else {
|
||||
fmt.Printf(" %-8s", "*")
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-")
|
||||
if len(whoisFormat) > 1 {
|
||||
whoisFormat[0] = strings.Join(whoisFormat[:2], "-")
|
||||
}
|
||||
|
||||
if whoisFormat[0] != "" {
|
||||
whoisFormat[0] = "[" + whoisFormat[0] + "]"
|
||||
}
|
||||
|
||||
// CMIN2, CUII, CN2, CUG 改为壕金色高亮
|
||||
switch {
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "58807":
|
||||
fallthrough
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "10099":
|
||||
fallthrough
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "4809":
|
||||
fallthrough
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "9929":
|
||||
fallthrough
|
||||
case res.Hops[ttl][i].Geo.Asnumber == "23764":
|
||||
fallthrough
|
||||
case whoisFormat[0] == "[CNC-BACKBONE]":
|
||||
fallthrough
|
||||
case whoisFormat[0] == "[CUG-BACKBONE]":
|
||||
fallthrough
|
||||
case whoisFormat[0] == "[CMIN2-NET]":
|
||||
fallthrough
|
||||
case strings.HasPrefix(res.Hops[ttl][i].Address.String(), "59.43."):
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiYellow, color.Bold).Sprintf("%-16s", whoisFormat[0]))
|
||||
default:
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0]))
|
||||
}
|
||||
}
|
||||
|
||||
if len(res.Hops[ttl][i].Geo.Country) <= 1 {
|
||||
res.Hops[ttl][i].Geo.Country = "局域网"
|
||||
res.Hops[ttl][i].Geo.CountryEn = "LAN Address"
|
||||
}
|
||||
|
||||
if res.Hops[ttl][i].Lang == "en" {
|
||||
if res.Hops[ttl][i].Geo.Country == "Anycast" {
|
||||
|
||||
} else if res.Hops[ttl][i].Geo.Prov == "骨干网" {
|
||||
res.Hops[ttl][i].Geo.Prov = "BackBone"
|
||||
} else if res.Hops[ttl][i].Geo.ProvEn == "" {
|
||||
res.Hops[ttl][i].Geo.Country = res.Hops[ttl][i].Geo.CountryEn
|
||||
} else {
|
||||
if res.Hops[ttl][i].Geo.CityEn == "" {
|
||||
res.Hops[ttl][i].Geo.Country = res.Hops[ttl][i].Geo.ProvEn
|
||||
res.Hops[ttl][i].Geo.Prov = res.Hops[ttl][i].Geo.CountryEn
|
||||
res.Hops[ttl][i].Geo.City = ""
|
||||
} else {
|
||||
res.Hops[ttl][i].Geo.Country = res.Hops[ttl][i].Geo.CityEn
|
||||
res.Hops[ttl][i].Geo.Prov = res.Hops[ttl][i].Geo.ProvEn
|
||||
res.Hops[ttl][i].Geo.City = res.Hops[ttl][i].Geo.CountryEn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
|
||||
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
|
||||
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%-39s", res.Hops[ttl][i].Hostname),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
|
||||
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%-32s", res.Hops[ttl][i].Hostname),
|
||||
)
|
||||
}
|
||||
|
||||
for j := 1; j < len(v); j++ {
|
||||
if len(v) == 2 || j == 1 {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " / %s",
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
|
||||
)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
blockDisplay = true
|
||||
}
|
||||
}
|
||||
|
||||
152
printer/realtime_printer_router.go
Normal file
152
printer/realtime_printer_router.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
func RealtimePrinterWithRouter(res *trace.Result, ttl int) {
|
||||
fmt.Printf("%s ", color.New(color.FgHiYellow, color.Bold).Sprintf("%-2d", ttl+1))
|
||||
|
||||
// 去重
|
||||
var latestIP string
|
||||
tmpMap := make(map[string][]string)
|
||||
for i, v := range res.Hops[ttl] {
|
||||
if v.Address == nil && latestIP != "" {
|
||||
tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*"))
|
||||
continue
|
||||
} else if v.Address == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exist := tmpMap[v.Address.String()]; !exist {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i))
|
||||
// 首次进入
|
||||
if latestIP == "" {
|
||||
for j := 0; j < i; j++ {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*"))
|
||||
}
|
||||
}
|
||||
latestIP = v.Address.String()
|
||||
}
|
||||
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000))
|
||||
}
|
||||
|
||||
if latestIP == "" {
|
||||
fmt.Fprintf(color.Output, "%s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("*"),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var blockDisplay = false
|
||||
for ip, v := range tmpMap {
|
||||
if blockDisplay {
|
||||
fmt.Printf("%4s", "")
|
||||
}
|
||||
if net.ParseIP(ip).To4() == nil {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%-25s", ip),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip),
|
||||
)
|
||||
}
|
||||
|
||||
i, _ := strconv.Atoi(v[0])
|
||||
|
||||
if res.Hops[ttl][i].Geo.Asnumber != "" {
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
|
||||
} else {
|
||||
fmt.Printf(" %-8s", "*")
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-")
|
||||
if len(whoisFormat) > 1 {
|
||||
whoisFormat[0] = strings.Join(whoisFormat[:2], "-")
|
||||
}
|
||||
|
||||
if whoisFormat[0] != "" {
|
||||
whoisFormat[0] = "[" + whoisFormat[0] + "]"
|
||||
}
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0]))
|
||||
}
|
||||
|
||||
if res.Hops[ttl][i].Geo.Country == "" {
|
||||
res.Hops[ttl][i].Geo.Country = "LAN Address"
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
|
||||
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
|
||||
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%-39s", res.Hops[ttl][i].Hostname),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
|
||||
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%-32s", res.Hops[ttl][i].Hostname),
|
||||
)
|
||||
}
|
||||
|
||||
for j := 1; j < len(v); j++ {
|
||||
if len(v) == 2 || j == 1 {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " / %s",
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
|
||||
)
|
||||
}
|
||||
}
|
||||
i = 0
|
||||
fmt.Println()
|
||||
if res.Hops[ttl][i].Geo != nil && !blockDisplay {
|
||||
fmt.Fprintf(color.Output, "%s %s %s %s %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("-"),
|
||||
color.New(color.FgHiYellow, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prefix),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("路由表"),
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("Beta"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("-"),
|
||||
)
|
||||
GetRouter(&res.Hops[ttl][i].Geo.Router, "AS"+res.Hops[ttl][i].Geo.Asnumber)
|
||||
}
|
||||
blockDisplay = true
|
||||
}
|
||||
}
|
||||
|
||||
func GetRouter(r *map[string][]string, node string) {
|
||||
routeMap := *r
|
||||
for _, v := range routeMap[node] {
|
||||
if len(routeMap[v]) != 0 {
|
||||
fmt.Fprintf(color.Output, " %s %s %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", routeMap[v][0]),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", v),
|
||||
color.New(color.FgHiBlue, color.Bold).Sprintf("%s", node),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " %s %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", v),
|
||||
color.New(color.FgHiBlue, color.Bold).Sprintf("%s", node),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -37,14 +37,17 @@ func TracerouteTablePrinter(res *trace.Result) {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, "", data.Owner)
|
||||
} else {
|
||||
if data.City != "" {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country+", "+data.Prov+", "+data.City, data.Owner)
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.City+", "+data.Prov+", "+data.Country, data.Owner)
|
||||
} else if data.Prov != "" {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Prov+", "+data.Country, data.Owner)
|
||||
} else {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country + ", " + data.Prov, data.Owner)
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Owner)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Print("\033[H\033[2J")
|
||||
// 打印表格
|
||||
tbl.Print()
|
||||
}
|
||||
@@ -75,7 +78,7 @@ func tableDataGenerator(h trace.Hop) *rowData {
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Country: "LAN Address",
|
||||
Prov: "LAN Address",
|
||||
Prov: "",
|
||||
Owner: "",
|
||||
}
|
||||
} else if strings.HasPrefix(IP, "11.") {
|
||||
@@ -84,7 +87,7 @@ func tableDataGenerator(h trace.Hop) *rowData {
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Country: "LAN Address",
|
||||
Prov: "LAN Address",
|
||||
Prov: "",
|
||||
Owner: "",
|
||||
}
|
||||
}
|
||||
@@ -102,9 +105,9 @@ func tableDataGenerator(h trace.Hop) *rowData {
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Asnumber: h.Geo.Asnumber,
|
||||
Country: h.Geo.Country,
|
||||
Prov: h.Geo.Prov,
|
||||
City: h.Geo.City,
|
||||
Country: h.Geo.CountryEn,
|
||||
Prov: h.Geo.ProvEn,
|
||||
City: h.Geo.CityEn,
|
||||
District: h.Geo.District,
|
||||
Owner: h.Geo.Owner,
|
||||
}
|
||||
@@ -117,9 +120,9 @@ func tableDataGenerator(h trace.Hop) *rowData {
|
||||
h.Geo.Owner = h.Geo.Isp
|
||||
}
|
||||
r.Asnumber = h.Geo.Asnumber
|
||||
r.Country = h.Geo.Country
|
||||
r.Prov = h.Geo.Prov
|
||||
r.City = h.Geo.City
|
||||
r.Country = h.Geo.CountryEn
|
||||
r.Prov = h.Geo.ProvEn
|
||||
r.City = h.Geo.CityEn
|
||||
r.District = h.Geo.District
|
||||
r.Owner = h.Geo.Owner
|
||||
return r
|
||||
|
||||
4
ptr.example.csv
Normal file
4
ptr.example.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
snge,SG,,Singapore
|
||||
CXS,CN,Hunan,Changsha
|
||||
LAX,US,California,Los Angeles
|
||||
SJC,US,California,San Jose
|
||||
|
@@ -45,7 +45,7 @@ func experimentTag() {
|
||||
|
||||
func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData, ttl uint16) {
|
||||
|
||||
var success bool = true
|
||||
var success = true
|
||||
|
||||
defer r.wg.Done()
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@ package trace
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -15,23 +18,51 @@ import (
|
||||
|
||||
type ICMPTracer struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestRWLock sync.RWMutex
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) PrintFunc() {
|
||||
defer t.wg.Done()
|
||||
var ttl = t.Config.BeginHop - 1
|
||||
for {
|
||||
if t.AsyncPrinter != nil {
|
||||
t.AsyncPrinter(&t.res)
|
||||
}
|
||||
if len(t.res.Hops)-1 > ttl {
|
||||
if len(t.res.Hops[ttl]) == t.NumMeasurements {
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl)
|
||||
}
|
||||
ttl++
|
||||
|
||||
if ttl == t.final-1 || ttl >= t.MaxHops-1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
<-time.After(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) Execute() (*Result, error) {
|
||||
t.inflightRequestRWLock.Lock()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.inflightRequestRWLock.Unlock()
|
||||
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
t.icmpListen, err = net.ListenPacket("ip4:1", "0.0.0.0")
|
||||
t.icmpListen, err = net.ListenPacket("ip4:1", t.SrcAddr)
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
@@ -40,27 +71,46 @@ func (t *ICMPTracer) Execute() (*Result, error) {
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.resCh = make(chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
for ttl := 1; ttl <= t.MaxHops; ttl++ {
|
||||
t.wg.Add(1)
|
||||
go t.PrintFunc()
|
||||
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
|
||||
t.inflightRequestRWLock.Lock()
|
||||
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
|
||||
t.inflightRequestRWLock.Unlock()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
break
|
||||
}
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
|
||||
}
|
||||
// 一组TTL全部退出(收到应答或者超时终止)以后,再进行下一个TTL的包发送
|
||||
t.wg.Wait()
|
||||
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
|
||||
}
|
||||
|
||||
t.wg.Wait()
|
||||
t.res.reduce(t.final)
|
||||
if t.final != -1 {
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl-1)
|
||||
t.RealtimePrinter(&t.res, t.final-1)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: 30,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, t.MaxHops-1)
|
||||
}
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
|
||||
@@ -75,55 +125,136 @@ func (t *ICMPTracer) listenICMP() {
|
||||
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) {
|
||||
// 匹配再继续解析包,否则直接丢弃
|
||||
// log.Println(msg.Msg)
|
||||
if msg.Msg[0] == 0 {
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
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)
|
||||
echoReply := rm.Body.(*icmp.Echo)
|
||||
ttl := echoReply.Seq // This is the TTL value
|
||||
if ttl > 100 {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() == t.DestIP.String() {
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, ttl)
|
||||
}
|
||||
continue
|
||||
}
|
||||
packet_id := strconv.FormatInt(int64(binary.BigEndian.Uint16(msg.Msg[32:34])), 2)
|
||||
if process_id, ttl, err := reverseID(packet_id); err == nil {
|
||||
if process_id == int64(os.Getpid()&0x7f) {
|
||||
dstip := net.IP(msg.Msg[24:28])
|
||||
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv4zero) {
|
||||
// 匹配再继续解析包,否则直接丢弃
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data, int(ttl))
|
||||
case ipv4.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, int(ttl))
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
|
||||
t.resCh <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
|
||||
t.inflightRequestRWLock.RLock()
|
||||
defer t.inflightRequestRWLock.RUnlock()
|
||||
if _, ok := t.inflightRequest[ttl]; ok {
|
||||
t.inflightRequest[ttl] <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func gernerateID(ttl_int int) int {
|
||||
const ID_FIXED_HEADER = "10"
|
||||
var processID = fmt.Sprintf("%07b", os.Getpid()&0x7f) //取进程ID的前7位
|
||||
var ttl = fmt.Sprintf("%06b", ttl_int) //取TTL的后6位
|
||||
|
||||
var parity int
|
||||
id := ID_FIXED_HEADER + processID + ttl
|
||||
for _, c := range id {
|
||||
if c == '1' {
|
||||
parity++
|
||||
}
|
||||
}
|
||||
if parity%2 == 0 {
|
||||
id += "1"
|
||||
} else {
|
||||
id += "0"
|
||||
}
|
||||
|
||||
res, _ := strconv.ParseInt(id, 2, 64)
|
||||
return int(res)
|
||||
}
|
||||
|
||||
func reverseID(id string) (int64, int64, error) {
|
||||
if len(id) < 16 {
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
ttl, err := strconv.ParseInt(id[9:15], 2, 32)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
//process ID
|
||||
processID, _ := strconv.ParseInt(id[2:9], 2, 32)
|
||||
|
||||
parity := 0
|
||||
for i := 0; i < len(id)-1; i++ {
|
||||
if id[i] == '1' {
|
||||
parity++
|
||||
}
|
||||
}
|
||||
|
||||
if parity%2 == 1 {
|
||||
if id[len(id)-1] == '0' {
|
||||
// fmt.Println("Parity check passed.")
|
||||
} else {
|
||||
// fmt.Println("Parity check failed.")
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
} else {
|
||||
if id[len(id)-1] == '1' {
|
||||
// fmt.Println("Parity check passed.")
|
||||
} else {
|
||||
// fmt.Println("Parity check failed.")
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
}
|
||||
return processID, ttl, nil
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) send(ttl int) error {
|
||||
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
id := gernerateID(ttl)
|
||||
// log.Println("发送的", id)
|
||||
|
||||
icmpHeader := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
ID: id,
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
Seq: ttl,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -141,11 +272,10 @@ func (t *ICMPTracer) send(ttl int) error {
|
||||
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:
|
||||
case h := <-t.inflightRequest[ttl]:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
@@ -153,6 +283,7 @@ func (t *ICMPTracer) send(ttl int) error {
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
@@ -170,7 +301,6 @@ func (t *ICMPTracer) send(ttl int) error {
|
||||
h.fetchIPData(t.Config)
|
||||
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
@@ -183,6 +313,7 @@ func (t *ICMPTracer) send(ttl int) error {
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -14,23 +16,54 @@ import (
|
||||
|
||||
type ICMPTracerv6 struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestRWLock sync.RWMutex
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) PrintFunc() {
|
||||
// defer t.wg.Done()
|
||||
var ttl = t.Config.BeginHop - 1
|
||||
for {
|
||||
if t.AsyncPrinter != nil {
|
||||
t.AsyncPrinter(&t.res)
|
||||
}
|
||||
|
||||
// 接收的时候检查一下是不是 3 跳都齐了
|
||||
if len(t.res.Hops)-1 > ttl {
|
||||
if len(t.res.Hops[ttl]) == t.NumMeasurements {
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl)
|
||||
}
|
||||
ttl++
|
||||
if ttl == t.final {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<-time.After(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) Execute() (*Result, error) {
|
||||
t.inflightRequestRWLock.Lock()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.inflightRequestRWLock.Unlock()
|
||||
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
t.icmpListen, err = net.ListenPacket("ip6:58", "::")
|
||||
t.icmpListen, err = net.ListenPacket("ip6:58", t.SrcAddr)
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
@@ -43,22 +76,59 @@ func (t *ICMPTracerv6) Execute() (*Result, error) {
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
for ttl := 1; ttl <= t.MaxHops; ttl++ {
|
||||
go t.PrintFunc()
|
||||
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
|
||||
t.inflightRequestRWLock.Lock()
|
||||
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
|
||||
t.inflightRequestRWLock.Unlock()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
break
|
||||
}
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
|
||||
}
|
||||
// 一组TTL全部退出(收到应答或者超时终止)以后,再进行下一个TTL的包发送
|
||||
t.wg.Wait()
|
||||
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
|
||||
}
|
||||
// for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
|
||||
// if t.final != -1 && ttl > t.final {
|
||||
// break
|
||||
// }
|
||||
// for i := 0; i < t.NumMeasurements; i++ {
|
||||
// t.wg.Add(1)
|
||||
// go t.send(ttl)
|
||||
// }
|
||||
// // 一组TTL全部退出(收到应答或者超时终止)以后,再进行下一个TTL的包发送
|
||||
// t.wg.Wait()
|
||||
// if t.RealtimePrinter != nil {
|
||||
// t.RealtimePrinter(&t.res, ttl-1)
|
||||
// }
|
||||
|
||||
// if t.AsyncPrinter != nil {
|
||||
// t.AsyncPrinter(&t.res)
|
||||
// }
|
||||
// }
|
||||
t.wg.Wait()
|
||||
t.res.reduce(t.final)
|
||||
if t.final != -1 {
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl-1)
|
||||
t.RealtimePrinter(&t.res, t.final-1)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: 30,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, t.MaxHops-1)
|
||||
}
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
@@ -74,29 +144,98 @@ func (t *ICMPTracerv6) listenICMP() {
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
if msg.Msg[0] == 129 {
|
||||
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
echoReply, ok := rm.Body.(*icmp.Echo)
|
||||
|
||||
if ok {
|
||||
ttl := echoReply.Seq // This is the TTL value
|
||||
|
||||
if ttl > 100 {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() == t.DestIP.String() {
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// log.Println(msg.Peer)
|
||||
switch rm.Type {
|
||||
case ipv6.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv6.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
packet_id := strconv.FormatInt(int64(binary.BigEndian.Uint16(msg.Msg[52:54])), 2)
|
||||
if process_id, ttl, err := reverseID(packet_id); err == nil {
|
||||
if process_id == int64(os.Getpid()&0x7f) {
|
||||
dstip := net.IP(msg.Msg[32:48])
|
||||
// 无效包本地环回包
|
||||
if dstip.String() == "::" {
|
||||
continue
|
||||
}
|
||||
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
|
||||
// 匹配再继续解析包,否则直接丢弃
|
||||
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch rm.Type {
|
||||
case ipv6.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data, int(ttl))
|
||||
case ipv6.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, int(ttl))
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// dstip := net.IP(msg.Msg[32:48])
|
||||
// if binary.BigEndian.Uint16(msg.Msg[52:54]) != uint16(os.Getpid()&0xffff) {
|
||||
// // // 如果类型为应答消息,且应答消息包的进程ID与主进程相同时不跳过
|
||||
// if binary.BigEndian.Uint16(msg.Msg[52:54]) != 0 {
|
||||
// continue
|
||||
// } else {
|
||||
// if dstip.String() != "::" {
|
||||
// continue
|
||||
// }
|
||||
// if msg.Peer.String() != t.DestIP.String() {
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
|
||||
// rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// continue
|
||||
// }
|
||||
// // log.Println(msg.Peer)
|
||||
// switch rm.Type {
|
||||
// case ipv6.ICMPTypeTimeExceeded:
|
||||
// t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
// case ipv6.ICMPTypeEchoReply:
|
||||
// t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
|
||||
// default:
|
||||
// // log.Println("received icmp message of unknown type", rm.Type)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
|
||||
t.resCh <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
|
||||
t.inflightRequestRWLock.RLock()
|
||||
defer t.inflightRequestRWLock.RUnlock()
|
||||
if _, ok := t.inflightRequest[ttl]; ok {
|
||||
t.inflightRequest[ttl] <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,12 +244,14 @@ func (t *ICMPTracerv6) send(ttl int) error {
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
id := gernerateID(ttl)
|
||||
|
||||
icmpHeader := icmp.Message{
|
||||
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
ID: id,
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
Seq: ttl,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -135,7 +276,7 @@ func (t *ICMPTracerv6) send(ttl int) error {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-t.resCh:
|
||||
case h := <-t.inflightRequest[ttl]:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
|
||||
@@ -42,11 +42,16 @@ func (t *TCPTracer) Execute() (*Result, error) {
|
||||
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
|
||||
|
||||
var err error
|
||||
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcIP.String())
|
||||
if t.SrcAddr != "" {
|
||||
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcAddr)
|
||||
} else {
|
||||
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcIP.String())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", t.SrcAddr)
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
@@ -55,7 +60,10 @@ func (t *TCPTracer) Execute() (*Result, error) {
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.inflightRequestLock.Lock()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.inflightRequestLock.Unlock()
|
||||
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
@@ -63,7 +71,7 @@ func (t *TCPTracer) Execute() (*Result, error) {
|
||||
|
||||
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
|
||||
|
||||
for ttl := 1; ttl <= t.MaxHops; ttl++ {
|
||||
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
|
||||
// 如果到达最终跳,则退出
|
||||
if t.final != -1 && ttl > t.final {
|
||||
break
|
||||
@@ -78,8 +86,19 @@ func (t *TCPTracer) Execute() (*Result, error) {
|
||||
t.wg.Wait()
|
||||
t.RealtimePrinter(&t.res, ttl-1)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
go func() {
|
||||
if t.AsyncPrinter != nil {
|
||||
for {
|
||||
t.AsyncPrinter(&t.res)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// 如果是表格模式,则一次性并发请求
|
||||
if t.RealtimePrinter == nil {
|
||||
t.wg.Wait()
|
||||
@@ -145,7 +164,7 @@ func (t *TCPTracer) listenTCP() {
|
||||
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
// 取得目标主机的Sequence Number
|
||||
|
||||
t.inflightRequestLock.Lock()
|
||||
if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok {
|
||||
// 最后一跳
|
||||
ch <- Hop{
|
||||
@@ -153,6 +172,7 @@ func (t *TCPTracer) listenTCP() {
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
t.inflightRequestLock.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
@@ -39,14 +39,14 @@ func (t *TCPTracerv6) Execute() (*Result, error) {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
|
||||
log.Println(util.LocalIPPort(t.DestIP))
|
||||
t.SrcIP, _ = util.LocalIPPortv6(t.DestIP)
|
||||
// log.Println(util.LocalIPPortv6(t.DestIP))
|
||||
var err error
|
||||
t.tcp, err = net.ListenPacket("ip6:tcp", t.SrcIP.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.icmp, err = icmp.ListenPacket("ip6:53", "::")
|
||||
t.icmp, err = icmp.ListenPacket("ip6:58", "::")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
@@ -64,14 +64,36 @@ func (t *TCPTracerv6) Execute() (*Result, error) {
|
||||
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
|
||||
|
||||
for ttl := 1; ttl <= t.MaxHops; ttl++ {
|
||||
// 如果到达最终跳,则退出
|
||||
if t.final != -1 && ttl > t.final {
|
||||
break
|
||||
}
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
|
||||
}
|
||||
if t.RealtimePrinter != nil {
|
||||
// 对于实时模式,应该按照TTL进行并发请求
|
||||
t.wg.Wait()
|
||||
t.RealtimePrinter(&t.res, ttl-1)
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
|
||||
}
|
||||
|
||||
go func() {
|
||||
if t.AsyncPrinter != nil {
|
||||
for {
|
||||
t.AsyncPrinter(&t.res)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
if t.RealtimePrinter == nil {
|
||||
t.wg.Wait()
|
||||
}
|
||||
t.wg.Wait()
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
@@ -85,20 +107,21 @@ func (t *TCPTracerv6) listenICMP() {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
// log.Println(msg)
|
||||
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(53, msg.Msg[:*msg.N])
|
||||
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
// log.Println(err)
|
||||
continue
|
||||
}
|
||||
log.Println(msg.Peer)
|
||||
switch rm.Type {
|
||||
case ipv6.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
t.handleICMPMessage(msg)
|
||||
case ipv6.ICMPTypeDestinationUnreachable:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
|
||||
t.handleICMPMessage(msg)
|
||||
default:
|
||||
//log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
@@ -118,6 +141,8 @@ func (t *TCPTracerv6) listenTCP() {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
// log.Println(msg)
|
||||
// return
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
@@ -144,23 +169,21 @@ func (t *TCPTracerv6) listenTCP() {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) handleICMPMessage(msg ReceivedMessage, data []byte) {
|
||||
header, err := util.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sequenceNumber := util.GetTCPSeq(header)
|
||||
func (t *TCPTracerv6) handleICMPMessage(msg ReceivedMessage) {
|
||||
var sequenceNumber = binary.BigEndian.Uint32(msg.Msg[52:56])
|
||||
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[int(sequenceNumber)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// log.Println("发送数据", sequenceNumber)
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
|
||||
// log.Println("发送成功")
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) send(ttl int) error {
|
||||
@@ -176,7 +199,7 @@ func (t *TCPTracerv6) send(ttl int) error {
|
||||
}
|
||||
// 随机种子
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, srcPort := util.LocalIPPort(t.DestIP)
|
||||
_, srcPort := util.LocalIPPortv6(t.DestIP)
|
||||
ipHeader := &layers.IPv6{
|
||||
SrcIP: t.SrcIP,
|
||||
DstIP: t.DestIP,
|
||||
@@ -185,6 +208,7 @@ func (t *TCPTracerv6) send(ttl int) error {
|
||||
}
|
||||
// 使用Uint16兼容32位系统,防止在rand的时候因使用int32而溢出
|
||||
sequenceNumber := uint32(r.Intn(math.MaxUint16))
|
||||
|
||||
tcpHeader := &layers.TCP{
|
||||
SrcPort: layers.TCPPort(srcPort),
|
||||
DstPort: layers.TCPPort(t.DestPort),
|
||||
@@ -212,6 +236,7 @@ func (t *TCPTracerv6) send(ttl int) error {
|
||||
if _, err := t.tcp.WriteTo(buf.Bytes(), &net.IPAddr{IP: t.DestIP}); err != nil {
|
||||
return err
|
||||
}
|
||||
// log.Println(ttl, sequenceNumber)
|
||||
t.inflightRequestLock.Lock()
|
||||
hopCh := make(chan Hop)
|
||||
t.inflightRequest[int(sequenceNumber)] = hopCh
|
||||
|
||||
@@ -16,6 +16,8 @@ var (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SrcAddr string
|
||||
BeginHop int
|
||||
MaxHops int
|
||||
NumMeasurements int
|
||||
ParallelRequests int
|
||||
@@ -25,7 +27,13 @@ type Config struct {
|
||||
Quic bool
|
||||
IPGeoSource ipgeo.Source
|
||||
RDns bool
|
||||
AlwaysWaitRDNS bool
|
||||
PacketInterval int
|
||||
TTLInterval int
|
||||
Lang string
|
||||
DN42 bool
|
||||
RealtimePrinter func(res *Result, ttl int)
|
||||
AsyncPrinter func(res *Result)
|
||||
}
|
||||
|
||||
type Method string
|
||||
@@ -71,8 +79,8 @@ func Traceroute(method Method, config Config) (*Result, error) {
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &TCPTracer{Config: config}
|
||||
} else {
|
||||
// tracer = &TCPTracerv6{Config: config}
|
||||
return nil, errors.New("IPv6 TCP Traceroute is not supported")
|
||||
tracer = &TCPTracerv6{Config: config}
|
||||
// return nil, errors.New("IPv6 TCP Traceroute is not supported")
|
||||
}
|
||||
default:
|
||||
return &Result{}, ErrInvalidMethod
|
||||
@@ -110,17 +118,90 @@ type Hop struct {
|
||||
RTT time.Duration
|
||||
Error error
|
||||
Geo *ipgeo.IPGeoData
|
||||
Lang string
|
||||
}
|
||||
|
||||
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]
|
||||
// DN42
|
||||
if c.DN42 {
|
||||
var ip string
|
||||
// 首先查找 PTR 记录
|
||||
r, err := net.LookupAddr(h.Address.String())
|
||||
if err == nil && len(r) > 0 {
|
||||
h.Hostname = r[0][:len(r[0])-1]
|
||||
ip = h.Address.String() + "," + h.Hostname
|
||||
}
|
||||
h.Geo, err = c.IPGeoSource(ip)
|
||||
return nil
|
||||
}
|
||||
if c.IPGeoSource != nil && h.Geo == nil {
|
||||
h.Geo, err = c.IPGeoSource(h.Address.String())
|
||||
|
||||
// Debug Area
|
||||
// c.AlwaysWaitRDNS = true
|
||||
|
||||
// Initialize a rDNS Channel
|
||||
rDNSChan := make(chan []string)
|
||||
fetchDoneChan := make(chan bool)
|
||||
|
||||
if c.RDns && h.Hostname == "" {
|
||||
// Create a rDNS Query go-routine
|
||||
go func() {
|
||||
r, err := net.LookupAddr(h.Address.String())
|
||||
if err != nil {
|
||||
// No PTR Record
|
||||
rDNSChan <- nil
|
||||
} else {
|
||||
// One PTR Record is found
|
||||
rDNSChan <- r
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Create Data Fetcher go-routine
|
||||
go func() {
|
||||
// Start to fetch IP Geolocation data
|
||||
if c.IPGeoSource != nil && h.Geo == nil {
|
||||
res := false
|
||||
h.Lang = c.Lang
|
||||
h.Geo, res = ipgeo.Filter(h.Address.String())
|
||||
if !res {
|
||||
h.Geo, err = c.IPGeoSource(h.Address.String())
|
||||
}
|
||||
}
|
||||
// Fetch Done
|
||||
fetchDoneChan <- true
|
||||
}()
|
||||
|
||||
// Select Close Flag
|
||||
var selectClose bool
|
||||
if !c.AlwaysWaitRDNS {
|
||||
select {
|
||||
case <-fetchDoneChan:
|
||||
// When fetch done signal recieved, stop waiting PTR record
|
||||
case ptr := <-rDNSChan:
|
||||
// process result
|
||||
if err == nil && len(ptr) > 0 {
|
||||
h.Hostname = ptr[0][:len(ptr[0])-1]
|
||||
}
|
||||
selectClose = true
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case ptr := <-rDNSChan:
|
||||
// process result
|
||||
if err == nil && len(ptr) > 0 {
|
||||
h.Hostname = ptr[0]
|
||||
}
|
||||
// 1 second timeout
|
||||
case <-time.After(time.Second * 1):
|
||||
}
|
||||
selectClose = true
|
||||
}
|
||||
|
||||
// When Select Close, fetchDoneChan Reciever will also be closed
|
||||
if selectClose {
|
||||
// New a reciever to prevent channel congestion
|
||||
<-fetchDoneChan
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
18
trace/udp.go
18
trace/udp.go
@@ -37,7 +37,7 @@ func (t *UDPTracer) Execute() (*Result, error) {
|
||||
}
|
||||
|
||||
var err error
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", t.SrcAddr)
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
@@ -69,8 +69,16 @@ func (t *UDPTracer) Execute() (*Result, error) {
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
go func() {
|
||||
if t.AsyncPrinter != nil {
|
||||
for {
|
||||
t.AsyncPrinter(&t.res)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// 如果是表格模式,则一次性并发请求
|
||||
if t.RealtimePrinter == nil {
|
||||
if t.AsyncPrinter != nil {
|
||||
t.wg.Wait()
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
@@ -100,7 +108,7 @@ func (t *UDPTracer) listenICMP() {
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
|
||||
default:
|
||||
log.Println("received icmp message of unknown type", rm.Type)
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,8 +121,8 @@ func (t *UDPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
|
||||
return
|
||||
}
|
||||
srcPort := util.GetUDPSrcPort(header)
|
||||
//t.inflightRequestLock.Lock()
|
||||
//defer t.inflightRequestLock.Unlock()
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[int(srcPort)]
|
||||
if !ok {
|
||||
return
|
||||
|
||||
116
tracelog/log.go
Normal file
116
tracelog/log.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package tracelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
func RealtimePrinter(res *trace.Result, ttl int) {
|
||||
f, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func(f *os.File) {
|
||||
err := f.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}(f)
|
||||
|
||||
multiWriter := io.MultiWriter(os.Stdout, f)
|
||||
log.SetOutput(multiWriter)
|
||||
log.SetFlags(0)
|
||||
var res_str string
|
||||
res_str += fmt.Sprintf("%-2d ", ttl+1)
|
||||
|
||||
// 去重
|
||||
var latestIP string
|
||||
tmpMap := make(map[string][]string)
|
||||
for i, v := range res.Hops[ttl] {
|
||||
if v.Address == nil && latestIP != "" {
|
||||
tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*"))
|
||||
continue
|
||||
} else if v.Address == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exist := tmpMap[v.Address.String()]; !exist {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i))
|
||||
// 首次进入
|
||||
if latestIP == "" {
|
||||
for j := 0; j < i; j++ {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*"))
|
||||
}
|
||||
}
|
||||
latestIP = v.Address.String()
|
||||
}
|
||||
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000))
|
||||
}
|
||||
|
||||
if latestIP == "" {
|
||||
res_str += fmt.Sprintf("%s\n", "*")
|
||||
log.Print(res_str)
|
||||
return
|
||||
}
|
||||
|
||||
var blockDisplay = false
|
||||
for ip, v := range tmpMap {
|
||||
if blockDisplay {
|
||||
res_str += fmt.Sprintf("%4s", "")
|
||||
}
|
||||
if net.ParseIP(ip).To4() == nil {
|
||||
res_str += fmt.Sprintf("%-25s ", ip)
|
||||
} else {
|
||||
res_str += fmt.Sprintf("%-15s ", ip)
|
||||
}
|
||||
|
||||
i, _ := strconv.Atoi(v[0])
|
||||
|
||||
if res.Hops[ttl][i].Geo.Asnumber != "" {
|
||||
res_str += fmt.Sprintf("AS%-7s", res.Hops[ttl][i].Geo.Asnumber)
|
||||
} else {
|
||||
res_str += fmt.Sprintf(" %-8s", "*")
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-")
|
||||
if len(whoisFormat) > 1 {
|
||||
whoisFormat[0] = strings.Join(whoisFormat[:2], "-")
|
||||
}
|
||||
|
||||
if whoisFormat[0] != "" {
|
||||
whoisFormat[0] = "[" + whoisFormat[0] + "]"
|
||||
}
|
||||
res_str += fmt.Sprintf("%-16s", whoisFormat[0])
|
||||
}
|
||||
|
||||
if res.Hops[ttl][i].Geo.Country == "" {
|
||||
res.Hops[ttl][i].Geo.Country = "LAN Address"
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
|
||||
res_str += fmt.Sprintf(" %s %s %s %s %-6s\n %-39s ", res.Hops[ttl][i].Geo.Country, res.Hops[ttl][i].Geo.Prov, res.Hops[ttl][i].Geo.City, res.Hops[ttl][i].Geo.District, res.Hops[ttl][i].Geo.Owner, res.Hops[ttl][i].Hostname)
|
||||
} else {
|
||||
res_str += fmt.Sprintf(" %s %s %s %s %-6s\n %-35s ", res.Hops[ttl][i].Geo.Country, res.Hops[ttl][i].Geo.Prov, res.Hops[ttl][i].Geo.City, res.Hops[ttl][i].Geo.District, res.Hops[ttl][i].Geo.Owner, res.Hops[ttl][i].Hostname)
|
||||
}
|
||||
|
||||
for j := 1; j < len(v); j++ {
|
||||
if len(v) == 2 || j == 1 {
|
||||
res_str += v[j]
|
||||
} else {
|
||||
res_str += fmt.Sprintf("/ %s", v[j])
|
||||
}
|
||||
}
|
||||
log.Print(res_str)
|
||||
blockDisplay = true
|
||||
}
|
||||
}
|
||||
20
tracemap/tracemap.go
Normal file
20
tracemap/tracemap.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package tracemap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
func GetMapUrl(r string) {
|
||||
url := "https://api.leo.moe/tracemap/api"
|
||||
resp, _ := http.Post(url, "application/json", strings.NewReader(r))
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
fmt.Fprintf(color.Output, "%s %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "MapTrace URL:"),
|
||||
color.New(color.FgBlue, color.Bold).Sprintf("%s", string(body)),
|
||||
)
|
||||
}
|
||||
23
util/common.go
Normal file
23
util/common.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package util
|
||||
|
||||
import "net"
|
||||
|
||||
func Dnspod() *net.Resolver {
|
||||
return newDoTResolver("dot.pub", "dot.pub:853")
|
||||
}
|
||||
|
||||
func Aliyun() *net.Resolver {
|
||||
return newDoTResolver("dns.alidns.com", "dns.alidns.com:853")
|
||||
}
|
||||
|
||||
func DNSSB() *net.Resolver {
|
||||
return newDoTResolver("45.11.45.11", "dot.sb:853")
|
||||
}
|
||||
|
||||
func Cloudflare() *net.Resolver {
|
||||
return newDoTResolver("one.one.one.one", "one.one.one.one:853")
|
||||
}
|
||||
|
||||
func Google() *net.Resolver {
|
||||
return newDoTResolver("dns.google", "dns.google:853")
|
||||
}
|
||||
13
util/dns_test.go
Normal file
13
util/dns_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDNS(t *testing.T) {
|
||||
resolver := DNSSB()
|
||||
ips, _ := resolver.LookupHost(context.Background(), "www.google.com")
|
||||
fmt.Println(ips)
|
||||
}
|
||||
32
util/dot.go
Normal file
32
util/dot.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newDoTResolver(serverName string, addrs string) *net.Resolver {
|
||||
|
||||
d := &net.Dialer{
|
||||
// 设置超时时间
|
||||
Timeout: 1000 * time.Millisecond,
|
||||
}
|
||||
|
||||
tls_config := &tls.Config{
|
||||
// 设置 TLS Server Name 以确保证书能和域名对应
|
||||
ServerName: serverName,
|
||||
}
|
||||
return &net.Resolver{
|
||||
// 指定使用 Go Build-in 的 DNS Resolver 来解析
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
conn, err := d.DialContext(ctx, "tcp", addrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tls.Client(conn, tls_config), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
12
util/udp.go
Normal file
12
util/udp.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func newUDPResolver() *net.Resolver {
|
||||
return &net.Resolver{
|
||||
// 指定使用 Go Build-in 的 DNS Resolver 来解析
|
||||
PreferGo: true,
|
||||
}
|
||||
}
|
||||
94
util/util.go
94
util/util.go
@@ -1,13 +1,16 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
// get the local ip and port based on our destination ip
|
||||
// LocalIPPort get the local ip and port based on our destination ip
|
||||
func LocalIPPort(dstip net.IP) (net.IP, int) {
|
||||
serverAddr, err := net.ResolveUDPAddr("udp", dstip.String()+":12345")
|
||||
if err != nil {
|
||||
@@ -25,50 +28,89 @@ func LocalIPPort(dstip net.IP) (net.IP, int) {
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func DomainLookUp(host string, ipv4Only bool) net.IP {
|
||||
ips, err := net.LookupIP(host)
|
||||
func LocalIPPortv6(dstip net.IP) (net.IP, int) {
|
||||
serverAddr, err := net.ResolveUDPAddr("udp", "["+dstip.String()+"]:12345")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// We don't actually connect to anything, but we can determine
|
||||
// based on our destination ip what source ip we should use.
|
||||
if con, err := net.DialUDP("udp", nil, serverAddr); err == nil {
|
||||
defer con.Close()
|
||||
if udpaddr, ok := con.LocalAddr().(*net.UDPAddr); ok {
|
||||
return udpaddr.IP, udpaddr.Port
|
||||
}
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func DomainLookUp(host string, ipv4Only bool, dotServer string) net.IP {
|
||||
var (
|
||||
r *net.Resolver
|
||||
ips []net.IP
|
||||
)
|
||||
|
||||
switch dotServer {
|
||||
case "dnssb":
|
||||
r = DNSSB()
|
||||
case "aliyun":
|
||||
r = Aliyun()
|
||||
case "dnspod":
|
||||
r = Dnspod()
|
||||
case "google":
|
||||
r = Google()
|
||||
case "cloudflare":
|
||||
r = Cloudflare()
|
||||
default:
|
||||
r = newUDPResolver()
|
||||
}
|
||||
ips_str, err := r.LookupHost(context.Background(), host)
|
||||
for _, v := range ips_str {
|
||||
ips = append(ips, net.ParseIP(v))
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("Domain " + host + " Lookup Fail.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var ipSlice = []net.IP{}
|
||||
var ipv6Flag = false
|
||||
|
||||
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 {
|
||||
fmt.Println("[Info] IPv6 UDP Traceroute is not supported right now.")
|
||||
if len(ips) == 0 {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ipSlice) == 1 {
|
||||
return ipSlice[0]
|
||||
if len(ips) == 1 {
|
||||
return ips[0]
|
||||
} else {
|
||||
fmt.Println("Please Choose the IP You Want To TraceRoute")
|
||||
for i, ip := range ipSlice {
|
||||
fmt.Printf("%d. %s\n", i, ip)
|
||||
for i, ip := range ips {
|
||||
fmt.Fprintf(color.Output, "%s %s\n",
|
||||
color.New(color.FgHiYellow, color.Bold).Sprintf("%d.", i),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", ip),
|
||||
)
|
||||
}
|
||||
var index int
|
||||
fmt.Printf("Your Option: ")
|
||||
fmt.Scanln(&index)
|
||||
if index >= len(ipSlice) || index < 0 {
|
||||
_, err := fmt.Scanln(&index)
|
||||
if err != nil {
|
||||
index = 0
|
||||
}
|
||||
if index >= len(ips) || index < 0 {
|
||||
fmt.Println("Your Option is invalid")
|
||||
os.Exit(3)
|
||||
}
|
||||
return ipSlice[index]
|
||||
return ips[index]
|
||||
}
|
||||
}
|
||||
|
||||
func GetenvDefault(key, defVal string) string {
|
||||
val, ok := os.LookupEnv(key)
|
||||
if ok {
|
||||
return val
|
||||
}
|
||||
return defVal
|
||||
}
|
||||
|
||||
223
wshandle/client.go
Normal file
223
wshandle/client.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package wshandle
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
)
|
||||
|
||||
type WsConn struct {
|
||||
Connecting bool
|
||||
Connected bool // 连接状态
|
||||
MsgSendCh chan string // 消息发送通道
|
||||
MsgReceiveCh chan string // 消息接收通道
|
||||
Done chan struct{} // 发送结束通道
|
||||
Exit chan bool // 程序退出信号
|
||||
Interrupt chan os.Signal // 终端中止信号
|
||||
Conn *websocket.Conn // 主连接
|
||||
ConnMux sync.Mutex // 连接互斥锁
|
||||
}
|
||||
|
||||
var wsconn *WsConn
|
||||
var hostP = util.GetenvDefault("NEXTTRACE_HOSTPORT", "api.leo.moe")
|
||||
var host, port, fast_ip string
|
||||
|
||||
func (c *WsConn) keepAlive() {
|
||||
go func() {
|
||||
// 开启一个定时器
|
||||
for {
|
||||
<-time.After(time.Second * 54)
|
||||
if c.Connected {
|
||||
err := c.Conn.WriteMessage(websocket.TextMessage, []byte("ping"))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
c.Connected = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if !c.Connected && !c.Connecting {
|
||||
c.Connecting = true
|
||||
c.recreateWsConn()
|
||||
// log.Println("WebSocket 连接意外断开,正在尝试重连...")
|
||||
// return
|
||||
}
|
||||
// 降低检测频率,优化 CPU 占用情况
|
||||
<-time.After(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WsConn) messageReceiveHandler() {
|
||||
// defer close(c.Done)
|
||||
for {
|
||||
if c.Connected {
|
||||
_, msg, err := c.Conn.ReadMessage()
|
||||
if err != nil {
|
||||
// 读取信息出错,连接已经意外断开
|
||||
// log.Println(err)
|
||||
c.Connected = false
|
||||
return
|
||||
}
|
||||
if string(msg) != "pong" {
|
||||
c.MsgReceiveCh <- string(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WsConn) messageSendHandler() {
|
||||
for {
|
||||
// 循环监听发送
|
||||
select {
|
||||
case <-c.Done:
|
||||
log.Println("go-routine has been returned")
|
||||
return
|
||||
case t := <-c.MsgSendCh:
|
||||
// log.Println(t)
|
||||
if !c.Connected {
|
||||
c.MsgReceiveCh <- `{"ip":"` + t + `", "asnumber":"API Server Error"}`
|
||||
} else {
|
||||
err := c.Conn.WriteMessage(websocket.TextMessage, []byte(t))
|
||||
if err != nil {
|
||||
log.Println("write:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 来自终端的中断运行请求
|
||||
case <-c.Interrupt:
|
||||
// 向 websocket 发起关闭连接任务
|
||||
err := c.Conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
if err != nil {
|
||||
// log.Println("write close:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
select {
|
||||
// 等到了结果,直接退出
|
||||
case <-c.Done:
|
||||
// 如果等待 1s 还是拿不到结果,不再等待,超时退出
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
os.Exit(1)
|
||||
// return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WsConn) recreateWsConn() {
|
||||
u := url.URL{Scheme: "wss", Host: fast_ip + ":" + port, Path: "/v2/ipGeoWs"}
|
||||
// log.Printf("connecting to %s", u.String())
|
||||
requestHeader := http.Header{
|
||||
"Host": []string{host},
|
||||
}
|
||||
dialer := websocket.DefaultDialer
|
||||
dialer.TLSClientConfig = &tls.Config{
|
||||
ServerName: host,
|
||||
}
|
||||
ws, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader)
|
||||
c.Conn = ws
|
||||
if err != nil {
|
||||
log.Println("dial:", err)
|
||||
// <-time.After(time.Second * 1)
|
||||
c.Connected = false
|
||||
c.Connecting = false
|
||||
return
|
||||
} else {
|
||||
c.Connected = true
|
||||
}
|
||||
c.Connecting = false
|
||||
|
||||
c.Done = make(chan struct{})
|
||||
go c.messageReceiveHandler()
|
||||
}
|
||||
|
||||
func createWsConn() *WsConn {
|
||||
// 设置终端中断通道
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
// 解析域名
|
||||
hostArr := strings.Split(hostP, ":")
|
||||
// 判断是否有指定端口
|
||||
if len(hostArr) > 1 {
|
||||
// 判断是否为 IPv6
|
||||
if strings.HasPrefix(hostP, "[") {
|
||||
tmp := strings.Split(hostP, "]")
|
||||
host = tmp[0]
|
||||
host = host[1:]
|
||||
if port = tmp[1]; port != "" {
|
||||
port = port[1:]
|
||||
}
|
||||
} else {
|
||||
host, port = hostArr[0], hostArr[1]
|
||||
}
|
||||
} else {
|
||||
host = hostP
|
||||
}
|
||||
if port == "" {
|
||||
// 默认端口
|
||||
port = "443"
|
||||
}
|
||||
// 默认配置完成,开始寻找最优 IP
|
||||
fast_ip = GetFastIP(host, port)
|
||||
|
||||
// 如果 host 是一个 IP 使用默认域名
|
||||
if valid := net.ParseIP(host); valid != nil {
|
||||
host = "api.leo.moe"
|
||||
}
|
||||
// 判断是否是一个 IP
|
||||
requestHeader := http.Header{
|
||||
"Host": []string{host},
|
||||
}
|
||||
dialer := websocket.DefaultDialer
|
||||
dialer.TLSClientConfig = &tls.Config{
|
||||
ServerName: host,
|
||||
}
|
||||
u := url.URL{Scheme: "wss", Host: fast_ip + ":" + port, Path: "/v2/ipGeoWs"}
|
||||
// log.Printf("connecting to %s", u.String())
|
||||
|
||||
c, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader)
|
||||
|
||||
wsconn = &WsConn{
|
||||
Conn: c,
|
||||
Connected: true,
|
||||
Connecting: false,
|
||||
MsgSendCh: make(chan string, 10),
|
||||
MsgReceiveCh: make(chan string, 10),
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Println("dial:", err)
|
||||
// <-time.After(time.Second * 1)
|
||||
wsconn.Connected = false
|
||||
wsconn.Done = make(chan struct{})
|
||||
go wsconn.keepAlive()
|
||||
go wsconn.messageSendHandler()
|
||||
return wsconn
|
||||
}
|
||||
// defer c.Close()
|
||||
// 将连接写入WsConn,方便随时可取
|
||||
wsconn.Done = make(chan struct{})
|
||||
go wsconn.keepAlive()
|
||||
go wsconn.messageReceiveHandler()
|
||||
go wsconn.messageSendHandler()
|
||||
return wsconn
|
||||
}
|
||||
|
||||
func New() *WsConn {
|
||||
return createWsConn()
|
||||
}
|
||||
|
||||
func GetWsConn() *WsConn {
|
||||
return wsconn
|
||||
}
|
||||
71
wshandle/latency.go
Normal file
71
wshandle/latency.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package wshandle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var (
|
||||
result string
|
||||
results = make(chan string)
|
||||
)
|
||||
|
||||
func GetFastIP(domain string, port string) string {
|
||||
|
||||
ips, err := net.LookupIP(domain)
|
||||
if err != nil {
|
||||
log.Fatal("DNS resolution failed, please check your system DNS Settings")
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
go checkLatency(ip.String(), port)
|
||||
}
|
||||
|
||||
select {
|
||||
case result = <-results:
|
||||
case <-time.After(1 * time.Second):
|
||||
|
||||
}
|
||||
if result == "" {
|
||||
log.Fatal("IP connection has been timeout, please check your network")
|
||||
}
|
||||
res := strings.Split(result, "-")
|
||||
|
||||
if len(ips) > 1 {
|
||||
_, _ = fmt.Fprintf(color.Output, "%s prefered API IP - %s - %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("[NextTrace API]"),
|
||||
color.New(color.FgGreen, color.Bold).Sprintf("%s", res[0]),
|
||||
color.New(color.FgCyan, color.Bold).Sprintf("%sms", res[1]),
|
||||
)
|
||||
}
|
||||
|
||||
return res[0]
|
||||
}
|
||||
|
||||
func checkLatency(ip string, port string) {
|
||||
start := time.Now()
|
||||
if !strings.Contains(ip, ".") {
|
||||
ip = "[" + ip + "]"
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", ip+":"+port, time.Second*1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func(conn net.Conn) {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(conn)
|
||||
if result == "" {
|
||||
result = fmt.Sprintf("%s-%.2f", ip, float64(time.Since(start))/float64(time.Millisecond))
|
||||
results <- result
|
||||
return
|
||||
}
|
||||
}
|
||||
9
wshandle/latency_test.go
Normal file
9
wshandle/latency_test.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package wshandle
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetFastIP(t *testing.T) {
|
||||
GetFastIP("api.leo.moe", "443")
|
||||
}
|
||||
Reference in New Issue
Block a user