From 1580c6111f6e7051dbe685b53967837847238b40 Mon Sep 17 00:00:00 2001 From: tsosunchia <59512455+tsosunchia@users.noreply.github.com> Date: Fri, 6 Oct 2023 20:59:40 +0800 Subject: [PATCH] add mpls func --- .cross_compile.sh | 24 +- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/dependabot.yml | 15 + .github/workflows/build.yml | 162 ++++++++-- .github/workflows/publishNewFormula.yml | 4 +- .github/workflows/test.yml | 43 +++ README.md | 7 +- README_zh_CN.md | 9 + cmd/cmd.go | 387 +++++++++++++++++++++++ cmd/cmd_test.go | 7 + cmd/root.go | 45 --- config/basic.go | 5 + config/viper.go | 40 +++ core/internal/icmp_darwin.s | 0 core/trace.go | 182 ----------- core/udp_ipv6.go | 264 ---------------- dn42/dn42.go | 7 + dn42/geofeed.go | 101 ++++++ dn42/geofeed_test.go | 17 + dn42/ptr.go | 83 +++++ dn42/ptr_test.go | 50 +++ example/traceroute_test.go | 39 --- fast_trace/basic.go | 186 +++++++++++ fast_trace/fast_trace ipv6.go | 159 ++++++++++ fast_trace/fast_trace.go | 208 ++++++++++++ fast_trace/fast_trace_test.go | 42 +++ go.mod | 33 +- go.sum | 67 ++-- ipgeo/chunzhen.go | 91 ++++++ ipgeo/dn42.go | 63 ++++ ipgeo/dn42_test.go | 5 + ipgeo/ip2region.go | 88 ++++++ ipgeo/ipapicom.go | 60 ++++ ipgeo/ipfilter.go | 174 ++++++++++ ipgeo/ipgeo.go | 62 ++++ ipgeo/ipgeo_test.go | 120 +++++++ ipgeo/ipinfo.go | 339 ++++++++++++++++++++ ipgeo/ipinfoLocal.go | 53 ++++ ipgeo/ipinsight.go | 32 ++ ipgeo/ipsb.go | 42 +++ ipgeo/leo.go | 114 +++++++ ipgeo/tokens.go | 15 + main.go | 6 +- nt_config.yaml | 2 + nt_install.sh | 2 +- plgn/debug.go | 42 --- plgn/default.go | 26 -- plgn/plugin.go | 46 --- pow/pow.go | 42 +++ pow/pow_test.go | 19 ++ printer/basic.go | 112 +++++++ printer/classic_printer.go | 104 ++++++ printer/easy.go | 32 ++ printer/printer.go | 116 +++++++ printer/printer_test.go | 94 ++++++ printer/realtime_printer.go | 177 +++++++++++ printer/realtime_printer_router.go | 152 +++++++++ printer/tableprinter.go | 130 ++++++++ reporter/reporter.go | 183 +++++++++++ reporter/reporter_test.go | 115 +++++++ {core => trace}/icmp_ipv4.go | 93 ++++-- {core => trace}/icmp_ipv6.go | 89 ++++-- {core => trace}/internal/icmp_darwin.go | 1 + {core => trace}/internal/icmp_general.go | 0 {core => trace}/packet_listener.go | 5 +- {core => trace}/quic.go | 4 +- {core => trace}/tcp_ipv4.go | 60 +++- {core => trace}/tcp_ipv6.go | 52 ++- trace/temp_printer.go | 76 +++++ trace/trace.go | 336 ++++++++++++++++++++ core/udp_ipv4.go => trace/udp.go | 46 ++- tracelog/log.go | 116 +++++++ tracemap/tracemap.go | 74 +++++ util/dns_test.go | 9 +- util/latency.go | 79 +++++ util/latency_test.go | 9 + util/util.go | 131 +++++++- wshandle/client.go | 260 +++++++++++++++ 78 files changed, 5459 insertions(+), 827 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/test.yml create mode 100644 cmd/cmd.go create mode 100644 cmd/cmd_test.go delete mode 100644 cmd/root.go create mode 100644 config/basic.go create mode 100644 config/viper.go delete mode 100644 core/internal/icmp_darwin.s delete mode 100644 core/trace.go delete mode 100644 core/udp_ipv6.go create mode 100644 dn42/dn42.go create mode 100644 dn42/geofeed.go create mode 100644 dn42/geofeed_test.go create mode 100644 dn42/ptr.go create mode 100644 dn42/ptr_test.go delete mode 100644 example/traceroute_test.go create mode 100644 fast_trace/basic.go create mode 100644 fast_trace/fast_trace ipv6.go create mode 100644 fast_trace/fast_trace.go create mode 100644 fast_trace/fast_trace_test.go create mode 100644 ipgeo/chunzhen.go create mode 100644 ipgeo/dn42.go create mode 100644 ipgeo/dn42_test.go create mode 100644 ipgeo/ip2region.go create mode 100644 ipgeo/ipapicom.go create mode 100644 ipgeo/ipfilter.go create mode 100644 ipgeo/ipgeo.go create mode 100644 ipgeo/ipgeo_test.go create mode 100644 ipgeo/ipinfo.go create mode 100644 ipgeo/ipinfoLocal.go create mode 100644 ipgeo/ipinsight.go create mode 100644 ipgeo/ipsb.go create mode 100644 ipgeo/leo.go create mode 100644 ipgeo/tokens.go create mode 100644 nt_config.yaml delete mode 100644 plgn/debug.go delete mode 100644 plgn/default.go delete mode 100644 plgn/plugin.go create mode 100644 pow/pow.go create mode 100644 pow/pow_test.go create mode 100644 printer/basic.go create mode 100644 printer/classic_printer.go create mode 100644 printer/easy.go create mode 100644 printer/printer.go create mode 100644 printer/printer_test.go create mode 100644 printer/realtime_printer.go create mode 100644 printer/realtime_printer_router.go create mode 100644 printer/tableprinter.go create mode 100644 reporter/reporter.go create mode 100644 reporter/reporter_test.go rename {core => trace}/icmp_ipv4.go (78%) rename {core => trace}/icmp_ipv6.go (76%) rename {core => trace}/internal/icmp_darwin.go (95%) rename {core => trace}/internal/icmp_general.go (100%) rename {core => trace}/packet_listener.go (98%) rename {core => trace}/quic.go (52%) rename {core => trace}/tcp_ipv4.go (83%) rename {core => trace}/tcp_ipv6.go (85%) create mode 100644 trace/temp_printer.go create mode 100644 trace/trace.go rename core/udp_ipv4.go => trace/udp.go (86%) create mode 100644 tracelog/log.go create mode 100644 tracemap/tracemap.go create mode 100644 util/latency.go create mode 100644 util/latency_test.go create mode 100644 wshandle/client.go diff --git a/.cross_compile.sh b/.cross_compile.sh index bb55a749..c16bb306 100644 --- a/.cross_compile.sh +++ b/.cross_compile.sh @@ -26,15 +26,15 @@ for pl in ${PLATFORMS}; do echo "build => ${TARGET}" if [ "${DEBUG_MODE}" == "debug" ]; then go build -trimpath -gcflags "all=-N -l" -o ${TARGET} \ - -ldflags "-X 'github.com/xgadget-lab/nexttrace/printer.version=${BUILD_VERSION}' \ - -X 'github.com/xgadget-lab/nexttrace/printer.buildDate=${BUILD_DATE}' \ - -X 'github.com/xgadget-lab/nexttrace/printer.commitID=${COMMIT_SHA1}'\ + -ldflags "-X 'github.com/nxtrace/NTrace-core/config.Version=${BUILD_VERSION}' \ + -X 'github.com/nxtrace/NTrace-core/config.BuildDate=${BUILD_DATE}' \ + -X 'github.com/nxtrace/NTrace-core/config.CommitID=${COMMIT_SHA1}'\ -w -s" else go build -trimpath -o ${TARGET} \ - -ldflags "-X 'github.com/xgadget-lab/nexttrace/printer.version=${BUILD_VERSION}' \ - -X 'github.com/xgadget-lab/nexttrace/printer.buildDate=${BUILD_DATE}' \ - -X 'github.com/xgadget-lab/nexttrace/printer.commitID=${COMMIT_SHA1}'\ + -ldflags "-X 'github.com/nxtrace/NTrace-core/config.Version=${BUILD_VERSION}' \ + -X 'github.com/nxtrace/NTrace-core/config.BuildDate=${BUILD_DATE}' \ + -X 'github.com/nxtrace/NTrace-core/config.CommitID=${COMMIT_SHA1}'\ -w -s" fi done @@ -46,15 +46,15 @@ done echo "build => ${TARGET}" if [ "${DEBUG_MODE}" == "debug" ]; then go build -trimpath -gcflags "all=-N -l" -o ${TARGET} \ - -ldflags "-X 'github.com/xgadget-lab/nexttrace/printer.version=${BUILD_VERSION}' \ - -X 'github.com/xgadget-lab/nexttrace/printer.buildDate=${BUILD_DATE}' \ - -X 'github.com/xgadget-lab/nexttrace/printer.commitID=${COMMIT_SHA1}'\ + -ldflags "-X 'github.com/nxtrace/NTrace-core/config.Version=${BUILD_VERSION}' \ + -X 'github.com/nxtrace/NTrace-core/config.BuildDate=${BUILD_DATE}' \ + -X 'github.com/nxtrace/NTrace-core/config.CommitID=${COMMIT_SHA1}'\ -w -s" else go build -trimpath -o ${TARGET} \ - -ldflags "-X 'github.com/xgadget-lab/nexttrace/printer.version=${BUILD_VERSION}' \ - -X 'github.com/xgadget-lab/nexttrace/printer.buildDate=${BUILD_DATE}' \ - -X 'github.com/xgadget-lab/nexttrace/printer.commitID=${COMMIT_SHA1}'\ + -ldflags "-X 'github.com/nxtrace/NTrace-core/config.Version=${BUILD_VERSION}' \ + -X 'github.com/nxtrace/NTrace-core/config.BuildDate=${BUILD_DATE}' \ + -X 'github.com/nxtrace/NTrace-core/config.CommitID=${COMMIT_SHA1}'\ -w -s" fi diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c282d881..7e76dadb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -15,7 +15,7 @@ copyright: [v2fly](https://github.com/v2fly) ## 你正在使用哪个版本的 nexttrace? diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..6cd4fd20 --- /dev/null +++ b/.github/dependabot.yml @@ -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" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf6da9e8..cd72dd41 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,38 +1,151 @@ +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@v4 + - 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: sudo go test -v -coverprofile='coverage.out' -covermode=count ./... - - Build: - needs: test - if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-go@v2 + go-version: '1.21' + - name: Get project dependencies + run: go mod download + - name: Build + run: | + go build -trimpath -o dist/${ASSET_NAME} \ + -ldflags "-X 'github.com/nxtrace/NTrace-core/config.Version=${BUILD_VERSION}' \ + -X 'github.com/nxtrace/NTrace-core/config.BuildDate=${BUILD_DATE}' \ + -X 'github.com/nxtrace/NTrace-core/config.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草稿 + draft: true # Release草稿 files: | dist/* env: @@ -42,6 +155,7 @@ jobs: 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: diff --git a/.github/workflows/publishNewFormula.yml b/.github/workflows/publishNewFormula.yml index 972457bd..8c64550b 100644 --- a/.github/workflows/publishNewFormula.yml +++ b/.github/workflows/publishNewFormula.yml @@ -21,7 +21,7 @@ jobs: git config --global user.name "${{ secrets.git_name }}" - name: Clone repo run: | - git clone https://github.com/sjlleo/homebrew-nexttrace.git + git clone https://github.com/xgadget-lab/homebrew-nexttrace.git - name: Exec scipt run: | cd homebrew-nexttrace @@ -38,7 +38,7 @@ jobs: 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/sjlleo/homebrew-nexttrace.git + 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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..601e9e67 --- /dev/null +++ b/.github/workflows/test.yml @@ -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.21' + check-latest: true + - name: Checkout codebase + uses: actions/checkout@v4 + - 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 ./... diff --git a/README.md b/README.md index dc0a45b7..7ac55ff3 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,10 @@ 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 + +# Disable MPLS display using the --disable-mpls / -e parameter or the NEXTTRACE_DISABLEMPLS environment variable +nexttrace --disable-mpls example.com +export NEXTTRACE_DISABLEMPLS=1 ``` 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). @@ -271,7 +275,7 @@ NextTrace `LeoMoeAPI` now utilizes the Proof of Work (POW) mechanism to prevent - [GitHub - tsosunchia/powclient: Proof of Work CLIENT for NextTrace](https://github.com/tsosunchia/powclient) - [GitHub - tsosunchia/powserver: Proof of Work SERVER for NextTrace](https://github.com/tsosunchia/powserver) -All NextTrace IP geolocation `API DEMO` can refer to [here](https://github.com/xgadget-lab/nexttrace/blob/main/ipgeo/) +All NextTrace IP geolocation `API DEMO` can refer to [here](https://github.com/nxtrace/NTrace-core/blob/main/ipgeo/) ### For full usage list, please refer to the usage menu @@ -341,6 +345,7 @@ Arguments: -f --first Start from the first_ttl hop (instead from 1). Default: 1 -M --map Disable Print Trace Map + -e --disable-mpls Disable MPLS -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 diff --git a/README_zh_CN.md b/README_zh_CN.md index 16088816..c4f9b849 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -60,6 +60,10 @@ Document Language: [English](README.md) | 简体中文 关于 **V1增强版本** 请转至 [nxtrace/Ntrace-V1](https://github.com/nxtrace/Ntrace-V1)。 +## How To Use + +Document Language: [English](README.md) | 简体中文 + ### 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),在了解您自己的对数据精准度需求以后再进行抉择。 @@ -162,6 +166,10 @@ 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 + +# 禁用MPLS显示 使用 --disable-mpls / -e 参数 或 NEXTTRACE_DISABLEMPLS 环境变量 +nexttrace --disable-mpls example.com +export NEXTTRACE_DISABLEMPLS=1 ``` PS: 路由可视化的绘制模块由 [@tsosunchia](https://github.com/tsosunchia) 同学编写,具体代码可在 [tsosunchia/traceMap](https://github.com/tsosunchia/traceMap) 查看 @@ -339,6 +347,7 @@ Arguments: -f --first Start from the first_ttl hop (instead from 1). Default: 1 -M --map Disable Print Trace Map + -e --disable-mpls Disable MPLS -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 diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 00000000..97f4a93f --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,387 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "log" + "net" + "os" + "os/signal" + "runtime" + "strings" + "time" + + "github.com/akamensky/argparse" + "github.com/nxtrace/NTrace-core/config" + fastTrace "github.com/nxtrace/NTrace-core/fast_trace" + "github.com/nxtrace/NTrace-core/ipgeo" + "github.com/nxtrace/NTrace-core/printer" + "github.com/nxtrace/NTrace-core/reporter" + "github.com/nxtrace/NTrace-core/trace" + "github.com/nxtrace/NTrace-core/tracelog" + "github.com/nxtrace/NTrace-core/tracemap" + "github.com/nxtrace/NTrace-core/util" + "github.com/nxtrace/NTrace-core/wshandle" + "github.com/syndtr/gocapability/capability" +) + +func Excute() { + parser := argparse.NewParser("nexttrace", "An open source visual route tracking CLI tool") + // Create string flag + ipv4Only := parser.Flag("4", "ipv4", &argparse.Options{Help: "Use IPv4 only"}) + ipv6Only := parser.Flag("6", "ipv6", &argparse.Options{Help: "Use IPv6 only"}) + tcp := parser.Flag("T", "tcp", &argparse.Options{Help: "Use TCP SYN for tracerouting (default port is 80)"}) + udp := parser.Flag("U", "udp", &argparse.Options{Help: "Use UDP SYN for tracerouting (default port is 53)"}) + fast_trace := parser.Flag("F", "fast-trace", &argparse.Options{Help: "One-Key Fast Trace to China ISPs"}) + port := parser.Int("p", "port", &argparse.Options{Help: "Set the destination port to use. It is either initial udp port value for \"default\"" + + "method (incremented by each probe, default is 33434), or initial seq for \"icmp\" (incremented as well, default from 1), or some constant" + + "destination port for other methods (with default of 80 for \"tcp\", 53 for \"udp\", etc.)"}) + numMeasurements := parser.Int("q", "queries", &argparse.Options{Default: 3, Help: "Set the number of probes per each hop"}) + parallelRequests := parser.Int("", "parallel-requests", &argparse.Options{Default: 18, Help: "Set ParallelRequests number. It should be 1 when there is a multi-routing"}) + maxHops := parser.Int("m", "max-hops", &argparse.Options{Default: 30, Help: "Set the max number of hops (max TTL to be reached)"}) + dataOrigin := parser.Selector("d", "data-provider", []string{"Ip2region", "ip2region", "IP.SB", "ip.sb", "IPInfo", "ipinfo", "IPInsight", "ipinsight", "IPAPI.com", "ip-api.com", "IPInfoLocal", "ipinfolocal", "chunzhen", "LeoMoeAPI", "leomoeapi", "disable-geoip"}, &argparse.Options{Default: "LeoMoeAPI", + Help: "Choose IP Geograph Data Provider [IP.SB, IPInfo, IPInsight, IP-API.com, Ip2region, IPInfoLocal, CHUNZHEN, disable-geoip]"}) + powProvider := parser.Selector("", "pow-provider", []string{"api.leo.moe", "sakura"}, &argparse.Options{Default: "api.leo.moe", + Help: "Choose PoW Provider [api.leo.moe, sakura] For China mainland users, please use sakura"}) + noRdns := parser.Flag("n", "no-rdns", &argparse.Options{Help: "Do not resolve IP addresses to their domain names"}) + alwaysRdns := parser.Flag("a", "always-rdns", &argparse.Options{Help: "Always resolve IP addresses to their domain names"}) + routePath := parser.Flag("P", "route-path", &argparse.Options{Help: "Print traceroute hop path by ASN and location"}) + report := parser.Flag("r", "report", &argparse.Options{Help: "output using report mode"}) + dn42 := parser.Flag("", "dn42", &argparse.Options{Help: "DN42 Mode"}) + output := parser.Flag("o", "output", &argparse.Options{Help: "Write trace result to file (RealTimePrinter ONLY)"}) + tablePrint := parser.Flag("t", "table", &argparse.Options{Help: "Output trace results as table"}) + rawPrint := parser.Flag("", "raw", &argparse.Options{Help: "An Output Easy to Parse"}) + jsonPrint := parser.Flag("j", "json", &argparse.Options{Help: "Output trace results as JSON"}) + classicPrint := parser.Flag("c", "classic", &argparse.Options{Help: "Classic Output trace results like BestTrace"}) + beginHop := parser.Int("f", "first", &argparse.Options{Default: 1, Help: "Start from the first_ttl hop (instead from 1)"}) + disableMaptrace := parser.Flag("M", "map", &argparse.Options{Help: "Disable Print Trace Map"}) + disableMPLS := parser.Flag("e", "disable-mpls", &argparse.Options{Help: "Disable MPLS"}) + ver := parser.Flag("v", "version", &argparse.Options{Help: "Print version info and exit"}) + srcAddr := parser.String("s", "source", &argparse.Options{Help: "Use source src_addr for outgoing packets"}) + srcDev := parser.String("D", "dev", &argparse.Options{Help: "Use the following Network Devices as the source address in outgoing packets"}) + router := parser.Flag("R", "route", &argparse.Options{Help: "Show Routing Table [Provided By BGP.Tools]"}) + packetInterval := parser.Int("z", "send-time", &argparse.Options{Default: 100, Help: "Set how many [milliseconds] between sending each packet.. Useful when some routers use rate-limit for ICMP messages"}) + ttlInterval := parser.Int("i", "ttl-time", &argparse.Options{Default: 500, Help: "Set how many [milliseconds] between sending packets groups by TTL. Useful when some routers use rate-limit for ICMP messages"}) + timeout := parser.Int("", "timeout", &argparse.Options{Default: 1000, Help: "The number of [milliseconds] to keep probe sockets open before giving up on the connection."}) + packetSize := parser.Int("", "psize", &argparse.Options{Default: 52, Help: "Set the packet size (payload size)"}) + str := parser.StringPositional(&argparse.Options{Help: "IP Address or domain name"}) + dot := parser.Selector("", "dot-server", []string{"dnssb", "aliyun", "dnspod", "google", "cloudflare"}, &argparse.Options{ + Help: "Use DoT Server for DNS Parse [dnssb, aliyun, dnspod, google, cloudflare]"}) + lang := parser.Selector("g", "language", []string{"en", "cn"}, &argparse.Options{Default: "cn", + Help: "Choose the language for displaying [en, cn]"}) + + err := parser.Parse(os.Args) + if err != nil { + // In case of error print error and print usage + // This can also be done by passing -h or --help flags + fmt.Print(parser.Usage(err)) + return + } + if !*jsonPrint { + printer.Version() + } + if *ver { + printer.CopyRight() + os.Exit(0) + } + + domain := *str + + if *port == 0 { + *port = 80 + } + + if *fast_trace { + var paramsFastTrace = fastTrace.ParamsFastTrace{ + SrcDev: *srcDev, + SrcAddr: *srcAddr, + BeginHop: *beginHop, + MaxHops: *maxHops, + RDns: !*noRdns, + AlwaysWaitRDNS: *alwaysRdns, + Lang: *lang, + PktSize: *packetSize, + Timeout: time.Duration(*timeout) * time.Millisecond, + } + + fastTrace.FastTest(*tcp, *output, paramsFastTrace) + if *output { + fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中") + } + + os.Exit(0) + } + + // DOMAIN处理开始 + if domain == "" { + fmt.Print(parser.Usage(err)) + return + } + + if strings.Contains(domain, "/") { + domain = strings.Split(domain, "/")[2] + } + + if strings.Contains(domain, "]") { + domain = strings.Split(strings.Split(domain, "]")[0], "[")[1] + } else if strings.Contains(domain, ":") { + if strings.Count(domain, ":") == 1 { + domain = strings.Split(domain, ":")[0] + } + } + // DOMAIN处理结束 + + capabilities_check() + // return + + var ip net.IP + + if runtime.GOOS == "windows" && (*tcp || *udp) { + fmt.Println("NextTrace 基于 Windows 的路由跟踪还在早期开发阶段,目前还存在诸多问题,TCP/UDP SYN 包请求可能不能正常运行") + } + + if *dn42 { + // 初始化配置 + config.InitConfig() + *dataOrigin = "DN42" + *disableMaptrace = true + } + + /** + * 此处若使用goroutine同时运行ws的建立与nslookup, + * 会导致第一跳的IP信息无法获取,原因不明。 + */ + //var wg sync.WaitGroup + //wg.Add(2) + // + //go func() { + // defer wg.Done() + if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" { + val, ok := os.LookupEnv("NEXTTRACE_DATAPROVIDER") + if strings.ToUpper(*powProvider) != "API.LEO.MOE" { + util.PowProviderParam = *powProvider + } + if ok { + *dataOrigin = val + } else { + w := wshandle.New() + w.Interrupt = make(chan os.Signal, 1) + signal.Notify(w.Interrupt, os.Interrupt) + defer func() { + w.Conn.Close() + }() + } + } + //}() + // + //go func() { + // defer wg.Done() + if *udp { + if *ipv6Only { + fmt.Println("[Info] IPv6 UDP Traceroute is not supported right now.") + os.Exit(0) + } + ip = util.DomainLookUp(domain, "4", *dot, *jsonPrint) + } else { + if *ipv6Only { + ip = util.DomainLookUp(domain, "6", *dot, *jsonPrint) + } else if *ipv4Only { + ip = util.DomainLookUp(domain, "4", *dot, *jsonPrint) + } else { + ip = util.DomainLookUp(domain, "all", *dot, *jsonPrint) + } + } + //}() + // + //wg.Wait() + + if *srcDev != "" { + dev, _ := net.InterfaceByName(*srcDev) + if addrs, err := dev.Addrs(); err == nil { + for _, addr := range addrs { + if (addr.(*net.IPNet).IP.To4() == nil) == (ip.To4() == nil) { + *srcAddr = addr.(*net.IPNet).IP.String() + // 检查是否是内网IP + if !(net.ParseIP(*srcAddr).IsPrivate() || + net.ParseIP(*srcAddr).IsLoopback() || + net.ParseIP(*srcAddr).IsLinkLocalUnicast() || + net.ParseIP(*srcAddr).IsLinkLocalMulticast()) { + // 若不是则跳出 + break + } + } + } + } + } + + if !*jsonPrint { + printer.PrintTraceRouteNav(ip, domain, *dataOrigin, *maxHops, *packetSize) + } + + var m trace.Method = "" + + switch { + case *tcp: + m = trace.TCPTrace + case *udp: + m = trace.UDPTrace + default: + m = trace.ICMPTrace + } + + if !*tcp && *port == 80 { + *port = 53 + } + + var conf = trace.Config{ + DN42: *dn42, + SrcAddr: *srcAddr, + BeginHop: *beginHop, + DestIP: ip, + DestPort: *port, + MaxHops: *maxHops, + PacketInterval: *packetInterval, + TTLInterval: *ttlInterval, + NumMeasurements: *numMeasurements, + ParallelRequests: *parallelRequests, + Lang: *lang, + RDns: !*noRdns, + AlwaysWaitRDNS: *alwaysRdns, + IPGeoSource: ipgeo.GetSource(*dataOrigin), + Timeout: time.Duration(*timeout) * time.Millisecond, + PktSize: *packetSize, + } + + if !*tablePrint { + if *classicPrint { + conf.RealtimePrinter = printer.ClassicPrinter + } else if *rawPrint { + conf.RealtimePrinter = printer.EasyPrinter + } else { + if *output { + conf.RealtimePrinter = tracelog.RealtimePrinter + } else if *router { + conf.RealtimePrinter = printer.RealtimePrinterWithRouter + fmt.Println("路由表数据源由 BGP.Tools 提供,在此特表感谢") + } else { + conf.RealtimePrinter = printer.RealtimePrinter + } + } + } else { + if !*report { + conf.AsyncPrinter = printer.TracerouteTablePrinter + } + } + + if *jsonPrint { + conf.RealtimePrinter = nil + conf.AsyncPrinter = nil + } + + if util.Uninterrupted != "" && *rawPrint { + for { + _, err := trace.Traceroute(m, conf) + if err != nil { + fmt.Println(err) + } + } + } + + if *disableMPLS { + util.DisableMPLS = "1" + } + + res, err := trace.Traceroute(m, conf) + + if err != nil { + log.Fatalln(err) + } + + if *tablePrint { + printer.TracerouteTablePrinter(res) + } + + if *routePath { + r := reporter.New(res, ip.String()) + r.Print() + } + + r, err := json.Marshal(res) + if err != nil { + fmt.Println(err) + return + } + if !*disableMaptrace && + (util.StringInSlice(strings.ToUpper(*dataOrigin), []string{"LEOMOEAPI", "IPINFO", "IPINFO", "IP-API.COM", "IPAPI.COM"})) { + url, err := tracemap.GetMapUrl(string(r)) + if err != nil { + log.Fatalln(err) + } + res.TraceMapUrl = url + if !*jsonPrint { + tracemap.PrintMapUrl(url) + } + } + r, err = json.Marshal(res) + if err != nil { + fmt.Println(err) + return + } + if *jsonPrint { + fmt.Println(string(r)) + } +} + +func capabilities_check() { + + // Windows 判断放在前面,防止遇到一些奇奇怪怪的问题 + if runtime.GOOS == "windows" { + // Running on Windows, skip checking capabilities + return + } + + uid := os.Getuid() + if uid == 0 { + // Running as root, skip checking capabilities + return + } + + /*** + * 检查当前进程是否有两个关键的权限 + ==== 看不到我 ==== + * 没办法啦 + * 自己之前承诺的坑补全篇 + * 被迫填坑系列 qwq + ==== 看不到我 ==== + ***/ + + // NewPid 已经被废弃了,这里改用 NewPid2 方法 + caps, err := capability.NewPid2(0) + if err != nil { + // 判断是否为macOS + if runtime.GOOS == "darwin" { + // macOS下报错有问题 + } else { + fmt.Println(err) + } + return + } + + // load 获取全部的 caps 信息 + err = caps.Load() + if err != nil { + fmt.Println(err) + return + } + + // 判断一下权限有木有 + if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) && caps.Get(capability.EFFECTIVE, capability.CAP_NET_ADMIN) { + // 有权限啦 + return + } else { + // 没权限啦 + fmt.Println("您正在以普通用户权限运行 NextTrace,但 NextTrace 未被赋予监听网络套接字的ICMP消息包、修改IP头信息(TTL)等路由跟踪所需的权限") + fmt.Println("请使用管理员用户执行 `sudo setcap cap_net_raw,cap_net_admin+eip ${your_nexttrace_path}/nexttrace` 命令,赋予相关权限后再运行~") + fmt.Println("什么?为什么 ping 普通用户执行不要 root 权限?因为这些工具在管理员安装时就已经被赋予了一些必要的权限,具体请使用 `getcap /usr/bin/ping` 查看") + } +} diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go new file mode 100644 index 00000000..93fd8d71 --- /dev/null +++ b/cmd/cmd_test.go @@ -0,0 +1,7 @@ +package cmd + +import "testing" + +func TestCMD(t *testing.T) { + Excute() +} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 5e0e1de6..00000000 --- a/cmd/root.go +++ /dev/null @@ -1,45 +0,0 @@ -package cmd - -import ( - "log" - - "github.com/sjlleo/nexttrace-core/core" - "github.com/sjlleo/nexttrace-core/plgn" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var rootCmd = &cobra.Command{Use: "NextTrace"} - -var cmdPrint = &cobra.Command{ - Use: "trace", - Short: "Traceroute", - Run: func(cmd *cobra.Command, args []string) { - debugLevel := viper.GetInt("debug-level") - enabledPlugins := viper.GetString("plugins") - - plgn.RegisterPlugin("debug", plgn.NewDebugPlugin) - plugins := plgn.CreatePlugins(enabledPlugins, debugLevel) - core.Traceroute(plugins) - }, -} - -// Execute parse subcommand and run -func Execute() { - if err := rootCmd.Execute(); err != nil { - log.Fatal(err.Error()) - } -} - -func init() { - rootCmd.AddCommand(cmdPrint) - rootCmd.PersistentFlags().Int("debug-level", 1, "Set debug level (1=info, 2=warn, 3=err)") - rootCmd.PersistentFlags().String("plugins", "default", "Comma-separated list of enabled plugins") - - viper.SetDefault("debug-level", 1) - viper.SetDefault("plugins", "default") - - viper.BindPFlag("debug-level", rootCmd.PersistentFlags().Lookup("debug-level")) - viper.BindPFlag("plugins", rootCmd.PersistentFlags().Lookup("plugins")) - -} diff --git a/config/basic.go b/config/basic.go new file mode 100644 index 00000000..94c5fd14 --- /dev/null +++ b/config/basic.go @@ -0,0 +1,5 @@ +package config + +var Version = "v0.0.0.alpha" +var BuildDate = "" +var CommitID = "" diff --git a/config/viper.go b/config/viper.go new file mode 100644 index 00000000..3801e0b1 --- /dev/null +++ b/config/viper.go @@ -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 + } +} diff --git a/core/internal/icmp_darwin.s b/core/internal/icmp_darwin.s deleted file mode 100644 index e69de29b..00000000 diff --git a/core/trace.go b/core/trace.go deleted file mode 100644 index 06d2809b..00000000 --- a/core/trace.go +++ /dev/null @@ -1,182 +0,0 @@ -package core - -import ( - "errors" - "fmt" - "log" - "net" - "sync" - "time" -) - -var ( - ErrInvalidMethod = errors.New("invalid method") - ErrTracerouteExecuted = errors.New("traceroute already executed") - ErrHopLimitTimeout = errors.New("hop timeout") -) - -type Method string - -type TraceInstance struct { - Tracer - ErrorStr string -} - -type Plugin interface { - OnDNSResolve(domain string) (net.IP, error) - OnNewIPFound(ip net.Addr) error - OnTTLChange(ttl int) error - OnTTLCompleted(ttl int, hop []Hop) error -} - -type Config struct { - TraceMethod Method - SrcAddr string - BeginHop int - MaxHops int - NumMeasurements int - ParallelRequests int - Timeout time.Duration - DestIP net.IP - DestPort int - Quic bool - PacketInterval time.Duration - TTLInterval time.Duration - Plugins []Plugin -} - -const ( - ICMPTrace Method = "icmp" - UDPTrace Method = "udp" - TCPTrace Method = "tcp" -) - -type Tracer interface { - Execute() (*Result, error) - GetConfig() *Config - SetConfig(Config) -} - -type Result struct { - Hops [][]Hop - lock sync.Mutex -} - -type Hop struct { - Address net.Addr - Hostname string - TTL int - RTT time.Duration - Error error -} - -func Traceroute(p []Plugin) { - var test_config = Config{ - DestIP: net.IPv4(1, 1, 1, 1), - DestPort: 443, - ParallelRequests: 30, - NumMeasurements: 3, - BeginHop: 1, - MaxHops: 30, - TTLInterval: 1 * time.Millisecond, - Timeout: 2 * time.Second, - TraceMethod: ICMPTrace, - Plugins: p, - } - traceInstance, err := NewTracer(test_config) - if err != nil { - log.Println(err) - return - } - - res, err := traceInstance.Traceroute() - if err != nil { - log.Println(err) - } - log.Println(res) -} - -func NewTracer(config Config) (*TraceInstance, error) { - t := TraceInstance{} - switch config.TraceMethod { - case ICMPTrace: - if config.DestIP.To4() != nil { - t.Tracer = &ICMPTracer{Config: config} - } else { - t.Tracer = &ICMPTracerv6{Config: config} - } - - case UDPTrace: - if config.DestIP.To4() != nil { - t.Tracer = &UDPTracer{Config: config} - } else { - t.Tracer = &UDPTracerv6{Config: config} - } - case TCPTrace: - if config.DestIP.To4() != nil { - t.Tracer = &TCPTracer{Config: config} - } else { - t.Tracer = &TCPTracerv6{Config: config} - } - default: - return &TraceInstance{}, ErrInvalidMethod - } - return &t, t.CheckConfig() -} - -func (t *TraceInstance) CheckConfig() (err error) { - c := t.GetConfig() - - configValidConditions := map[string]bool{ - "DestIP is null": c.DestIP == nil, - "BeginHop is empty": c.BeginHop == 0, - "MaxHops is empty": c.MaxHops == 0, - "NumMeasurements is empty": c.NumMeasurements == 0, - "ParallelRequests is empty": c.ParallelRequests == 0, - "Trace Timeout is empty": c.Timeout == 0, - "You must specific at least one of TTLInterval and PacketInterval": c.TTLInterval|c.PacketInterval == 0, - "You choose " + string(c.TraceMethod) + " trace. DestPort must be specified": (c.TraceMethod == TCPTrace || c.TraceMethod == UDPTrace) && c.DestPort == 0, - } - - var ( - inValidFlag bool - ) - - for condition, notValid := range configValidConditions { - if notValid { - inValidFlag = true - t.ErrorStr += fmt.Sprintf("Invalid config: %s\n", condition) - } - } - - if inValidFlag { - return fmt.Errorf(t.ErrorStr) - } - - return nil - -} - -func (t *TraceInstance) Traceroute() (*Result, error) { - if t.ErrorStr != "" { - log.Fatal(t.ErrorStr) - } - return t.Tracer.Execute() -} - -func (s *Result) add(hop Hop) { - s.lock.Lock() - defer s.lock.Unlock() - k := hop.TTL - 1 - for len(s.Hops) < hop.TTL { - s.Hops = append(s.Hops, make([]Hop, 0)) - } - s.Hops[k] = append(s.Hops[k], hop) - -} - -func (s *Result) reduce(final int) { - if final > 0 && final < len(s.Hops) { - s.Hops = s.Hops[:final] - } -} diff --git a/core/udp_ipv6.go b/core/udp_ipv6.go deleted file mode 100644 index ace1ab93..00000000 --- a/core/udp_ipv6.go +++ /dev/null @@ -1,264 +0,0 @@ -package core - -import ( - "log" - "net" - "sync" - "time" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - "github.com/sjlleo/nexttrace-core/util" - "golang.org/x/net/context" - "golang.org/x/net/icmp" - "golang.org/x/net/ipv4" - "golang.org/x/sync/semaphore" -) - -type UDPTracerv6 struct { - Config - wg sync.WaitGroup - res Result - ctx context.Context - inflightRequest map[int]chan Hop - inflightRequestLock sync.Mutex - - icmp net.PacketConn - - final int - finalLock sync.Mutex - - sem *semaphore.Weighted -} - -func (t *UDPTracerv6) GetConfig() *Config { - return &t.Config -} - -func (t *UDPTracerv6) SetConfig(c Config) { - t.Config = c -} - -func (t *UDPTracerv6) Execute() (*Result, error) { - if len(t.res.Hops) > 0 { - return &t.res, ErrTracerouteExecuted - } - - var err error - t.icmp, err = icmp.ListenPacket("ip6:58", t.SrcAddr) - if err != nil { - return &t.res, err - } - defer t.icmp.Close() - - var cancel context.CancelFunc - t.ctx, cancel = context.WithCancel(context.Background()) - defer cancel() - t.inflightRequest = make(map[int]chan Hop) - t.final = -1 - - go t.listenICMPv6() - - t.sem = semaphore.NewWeighted(int64(t.ParallelRequests)) - for ttl := 1; ttl <= t.MaxHops; ttl++ { - // 如果到达最终跳,则退出 - if t.final != -1 && ttl > t.final { - break - } - for i := 0; i < t.NumMeasurements; i++ { - t.wg.Add(1) - go t.send(ttl) - <-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval)) - } - <-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval)) - } - t.res.reduce(t.final) - - return &t.res, nil -} - -func (t *UDPTracerv6) listenICMPv6() { - lc := NewPacketListener(t.icmp, t.ctx) - go lc.Start() - for { - select { - case <-t.ctx.Done(): - return - case msg := <-lc.Messages: - if msg.N == nil { - continue - } - rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N]) - if err != nil { - log.Println(err) - continue - } - switch rm.Type { - case ipv4.ICMPTypeTimeExceeded: - t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data) - case ipv4.ICMPTypeDestinationUnreachable: - t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data) - default: - // log.Println("received icmp message of unknown type", rm.Type) - } - } - } - -} - -func (t *UDPTracerv6) handleICMPMessage(msg ReceivedMessage, data []byte) { - header, err := util.GetICMPResponsePayload(data) - if err != nil { - return - } - srcPort := util.GetUDPSrcPort(header) - t.inflightRequestLock.Lock() - defer t.inflightRequestLock.Unlock() - ch, ok := t.inflightRequest[int(srcPort)] - if !ok { - return - } - ch <- Hop{ - Address: msg.Peer, - } -} - -func (t *UDPTracerv6) getUDPConn(try int) (net.IP, int, net.PacketConn) { - srcIP, _ := util.LocalIPPort(t.DestIP) - - var ipString string - if srcIP == nil { - ipString = "" - } else { - ipString = srcIP.String() - } - - udpConn, err := net.ListenPacket("udp", "["+ipString+"]:0") - if err != nil { - if try > 3 { - log.Fatal(err) - } - return t.getUDPConn(try + 1) - } - return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn -} - -func (t *UDPTracerv6) send(ttl int) error { - err := t.sem.Acquire(context.Background(), 1) - if err != nil { - return err - } - defer t.sem.Release(1) - - defer t.wg.Done() - if t.final != -1 && ttl > t.final { - return nil - } - - srcIP, srcPort, udpConn := t.getUDPConn(0) - - var payload []byte - if t.Quic { - payload = GenerateQuicPayloadWithRandomIds() - } else { - ipHeader := &layers.IPv4{ - SrcIP: srcIP, - DstIP: t.DestIP, - Protocol: layers.IPProtocolTCP, - TTL: uint8(ttl), - } - - udpHeader := &layers.UDP{ - SrcPort: layers.UDPPort(srcPort), - DstPort: layers.UDPPort(t.DestPort), - } - _ = udpHeader.SetNetworkLayerForChecksum(ipHeader) - buf := gopacket.NewSerializeBuffer() - opts := gopacket.SerializeOptions{ - ComputeChecksums: true, - FixLengths: true, - } - if err := gopacket.SerializeLayers(buf, opts, udpHeader, gopacket.Payload("HAJSFJHKAJSHFKJHAJKFHKASHKFHHKAFKHFAHSJK")); err != nil { - return err - } - - payload = buf.Bytes() - } - - err = ipv4.NewPacketConn(udpConn).SetTTL(ttl) - if err != nil { - return err - } - - start := time.Now() - if _, err := udpConn.WriteTo(payload, &net.UDPAddr{IP: t.DestIP, Port: t.DestPort}); err != nil { - return err - } - - // 在对inflightRequest进行写操作的时候应该加锁保护,以免多个goroutine协程试图同时写入造成panic - t.inflightRequestLock.Lock() - hopCh := make(chan Hop) - t.inflightRequest[srcPort] = hopCh - t.inflightRequestLock.Unlock() - defer func() { - t.inflightRequestLock.Lock() - close(hopCh) - delete(t.inflightRequest, srcPort) - t.inflightRequestLock.Unlock() - }() - - go func() { - reply := make([]byte, 1500) - _, peer, err := udpConn.ReadFrom(reply) - if err != nil { - // probably because we closed the connection - return - } - hopCh <- Hop{ - Address: peer, - } - }() - - select { - case <-t.ctx.Done(): - return nil - case h := <-hopCh: - rtt := time.Since(start) - if t.final != -1 && ttl > t.final { - return nil - } - - if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) { - t.finalLock.Lock() - if t.final == -1 || ttl < t.final { - t.final = ttl - } - t.finalLock.Unlock() - } else if addr, ok := h.Address.(*net.UDPAddr); ok && addr.IP.Equal(t.DestIP) { - t.finalLock.Lock() - if t.final == -1 || ttl < t.final { - t.final = ttl - } - t.finalLock.Unlock() - } - - h.TTL = ttl - h.RTT = rtt - - t.res.add(h) - - case <-time.After(t.Timeout): - if t.final != -1 && ttl > t.final { - return nil - } - - t.res.add(Hop{ - Address: nil, - TTL: ttl, - RTT: 0, - Error: ErrHopLimitTimeout, - }) - } - - return nil -} diff --git a/dn42/dn42.go b/dn42/dn42.go new file mode 100644 index 00000000..7d17a4de --- /dev/null +++ b/dn42/dn42.go @@ -0,0 +1,7 @@ +package dn42 + +/*** + [DN42 Package] + 谨献给 DN42 所有的小伙伴们,祝你们终有一天能有自己的公网 ASN ~ + By Leo +***/ diff --git a/dn42/geofeed.go b/dn42/geofeed.go new file mode 100644 index 00000000..7b183f6d --- /dev/null +++ b/dn42/geofeed.go @@ -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 +} diff --git a/dn42/geofeed_test.go b/dn42/geofeed_test.go new file mode 100644 index 00000000..38daab4c --- /dev/null +++ b/dn42/geofeed_test.go @@ -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 { +// // 处理未找到的情况 +// } + +// } diff --git a/dn42/ptr.go b/dn42/ptr.go new file mode 100644 index 00000000..c470ff92 --- /dev/null +++ b/dn42/ptr.go @@ -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") +} diff --git a/dn42/ptr_test.go b/dn42/ptr_test.go new file mode 100644 index 00000000..e1a1faa9 --- /dev/null +++ b/dn42/ptr_test.go @@ -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) + // } + + // } + +} diff --git a/example/traceroute_test.go b/example/traceroute_test.go deleted file mode 100644 index deded5b1..00000000 --- a/example/traceroute_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package example - -import ( - "log" - "net" - "testing" - "time" - - "github.com/sjlleo/nexttrace-core/core" -) - -func traceroute() { - var test_config = core.Config{ - DestIP: net.IPv4(1, 1, 1, 1), - DestPort: 443, - ParallelRequests: 30, - NumMeasurements: 1, - BeginHop: 1, - MaxHops: 30, - TTLInterval: 1 * time.Millisecond, - Timeout: 2 * time.Second, - TraceMethod: core.ICMPTrace, - } - traceInstance, err := core.NewTracer(test_config) - if err != nil { - log.Println(err) - return - } - - res, err := traceInstance.Traceroute() - if err != nil { - log.Println(err) - } - log.Println(res) -} - -func TestTraceToCloudflareDNS(t *testing.T) { - traceroute() -} diff --git a/fast_trace/basic.go b/fast_trace/basic.go new file mode 100644 index 00000000..5b880810 --- /dev/null +++ b/fast_trace/basic.go @@ -0,0 +1,186 @@ +package fastTrace + +type AllLocationCollection struct { + Beijing BackBoneCollection + Shanghai BackBoneCollection + Guangzhou BackBoneCollection + Hangzhou BackBoneCollection + Hefei BackBoneCollection + Changsha BackBoneCollection +} + +type BackBoneCollection struct { + Location string + CT163 ISPCollection + CTCN2 ISPCollection + CU169 ISPCollection + CU9929 ISPCollection + CM ISPCollection + CMIN2 ISPCollection + EDU ISPCollection + CST ISPCollection +} + +type ISPCollection struct { + ISPName string + IP string + IPv6 string +} + +const ( + CT163 string = "电信 163 AS4134" + CTCN2 string = "电信 CN2 AS4809" + CU169 string = "联通 169 AS4837" + CU9929 string = "联通 A网 AS9929" + CM string = "移动 骨干网 AS9808" + CMIN2 string = "移动 CMIN2 AS58807" + EDU string = "教育网 CERNET AS4538" +) + +var TestIPsCollection = AllLocationCollection{ + Beijing: Beijing, + Shanghai: Shanghai, + Guangzhou: Guangzhou, + Hangzhou: Hangzhou, + Hefei: Hefei, +} + +var Beijing = BackBoneCollection{ + Location: "北京", + CT163: ISPCollection{ + ISPName: CT163, + IP: "ipv4.pek-4134.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.pek-4134.nexttrace-io-fasttrace-endpoint.win.", + }, + + CU169: ISPCollection{ + ISPName: CU169, + IP: "ipv4.pek-4837.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.pek-4837.nexttrace-io-fasttrace-endpoint.win.", + }, + + CU9929: ISPCollection{ + ISPName: CU9929, + IP: "ipv4.pek-9929.nexttrace-io-fasttrace-endpoint.win.", + }, + + CM: ISPCollection{ + ISPName: CM, + IP: "ipv4.pek-9808.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.pek-9808.nexttrace-io-fasttrace-endpoint.win.", + }, + + CMIN2: ISPCollection{ + ISPName: CMIN2, + IP: "ipv4.pek-58807.nexttrace-io-fasttrace-endpoint.win.", + }, + + EDU: ISPCollection{ + ISPName: EDU, + IP: "ipv4.pek-4538.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.pek-4538.nexttrace-io-fasttrace-endpoint.win.", + }, +} + +var Shanghai = BackBoneCollection{ + Location: "上海", + CT163: ISPCollection{ + ISPName: CT163, + IP: "ipv4.sha-4134.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.sha-4134.nexttrace-io-fasttrace-endpoint.win.", + }, + + CTCN2: ISPCollection{ + ISPName: CTCN2, + IP: "ipv4.sha-4809.nexttrace-io-fasttrace-endpoint.win.", + }, + + CU169: ISPCollection{ + ISPName: CU169, + IP: "ipv4.sha-4837.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.sha-4837.nexttrace-io-fasttrace-endpoint.win.", + }, + + CU9929: ISPCollection{ + ISPName: CU9929, + IP: "ipv4.sha-9929.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.sha-9929.nexttrace-io-fasttrace-endpoint.win.", + }, + + CM: ISPCollection{ + ISPName: CM, + IP: "ipv4.sha-9808.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.sha-9808.nexttrace-io-fasttrace-endpoint.win.", + }, + + CMIN2: ISPCollection{ + ISPName: CMIN2, + IP: "ipv4.sha-58807.nexttrace-io-fasttrace-endpoint.win.", + }, + + EDU: ISPCollection{ + ISPName: EDU, + IP: "ipv4.sha-4538.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.sha-4538.nexttrace-io-fasttrace-endpoint.win.", + }, +} + +var Guangzhou = BackBoneCollection{ + Location: "广州", + CT163: ISPCollection{ + ISPName: CT163, + IP: "ipv4.can-4134.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.can-4134.nexttrace-io-fasttrace-endpoint.win.", + }, + + CU169: ISPCollection{ + ISPName: CU169, + IP: "ipv4.can-4837.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.can-4837.nexttrace-io-fasttrace-endpoint.win.", + }, + + CM: ISPCollection{ + ISPName: CM, + IP: "ipv4.can-9808.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.can-9808.nexttrace-io-fasttrace-endpoint.win.", + }, +} + +var Hangzhou = BackBoneCollection{ + Location: "杭州", + CT163: ISPCollection{ + ISPName: CT163, + IP: "ipv4.hgh-4134.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.hgh-4134.nexttrace-io-fasttrace-endpoint.win.", + }, + CU169: ISPCollection{ + ISPName: CU169, + IP: "ipv4.hgh-4837.nexttrace-io-fasttrace-endpoint.win.", + }, + CM: ISPCollection{ + ISPName: CM, + IP: "ipv4.hgh-9808.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.hgh-9808.nexttrace-io-fasttrace-endpoint.win.", + }, + // 浙江大学 教育网 + EDU: ISPCollection{ + ISPName: EDU, + IP: "ipv4.hgh-4538.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.hgh-4538.nexttrace-io-fasttrace-endpoint.win.", + }, +} + +var Hefei = BackBoneCollection{ + Location: "合肥", + // 中国科学技术大学 教育网 + EDU: ISPCollection{ + ISPName: EDU, + IP: "ipv4.hfe-4538.nexttrace-io-fasttrace-endpoint.win.", + IPv6: "ipv6.hfe-4538.nexttrace-io-fasttrace-endpoint.win.", + }, + // 中国科学技术大学 科技网 + CST: ISPCollection{ + ISPName: "中国科学技术大学 科技网 AS7497", + IP: "ipv4.hfe-7497.nexttrace-io-fasttrace-endpoint.win.", + }, +} diff --git a/fast_trace/fast_trace ipv6.go b/fast_trace/fast_trace ipv6.go new file mode 100644 index 00000000..93bef127 --- /dev/null +++ b/fast_trace/fast_trace ipv6.go @@ -0,0 +1,159 @@ +package fastTrace + +import ( + "fmt" + "github.com/nxtrace/NTrace-core/ipgeo" + "github.com/nxtrace/NTrace-core/printer" + "github.com/nxtrace/NTrace-core/trace" + "github.com/nxtrace/NTrace-core/tracelog" + "github.com/nxtrace/NTrace-core/util" + "github.com/nxtrace/NTrace-core/wshandle" + "log" + "os" + "os/signal" +) + +var pFastTracer ParamsFastTrace + +func (f *FastTracer) tracert_v6(location string, ispCollection ISPCollection) { + fp, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) + if err != nil { + return + } + defer func(fp *os.File) { + err := fp.Close() + if err != nil { + log.Fatal(err) + } + }(fp) + + log.SetOutput(fp) + log.SetFlags(0) + fmt.Printf("%s『%s %s 』%s\n", printer.YELLOW_PREFIX, location, ispCollection.ISPName, printer.RESET_PREFIX) + log.Printf("『%s %s 』\n", location, ispCollection.ISPName) + fmt.Printf("traceroute to %s, %d hops max, %d byte packets\n", ispCollection.IPv6, pFastTracer.MaxHops, pFastTracer.PktSize) + log.Printf("traceroute to %s, %d hops max, %d byte packets\n", ispCollection.IPv6, pFastTracer.MaxHops, pFastTracer.PktSize) + ip := util.DomainLookUp(ispCollection.IPv6, "6", "", true) + var conf = trace.Config{ + BeginHop: pFastTracer.BeginHop, + DestIP: ip, + DestPort: 80, + MaxHops: pFastTracer.MaxHops, + NumMeasurements: 3, + ParallelRequests: 18, + RDns: pFastTracer.RDns, + AlwaysWaitRDNS: pFastTracer.AlwaysWaitRDNS, + PacketInterval: 100, + TTLInterval: 500, + IPGeoSource: ipgeo.GetSource("LeoMoeAPI"), + Timeout: pFastTracer.Timeout, + PktSize: pFastTracer.PktSize, + Lang: pFastTracer.Lang, + } + + if oe { + conf.RealtimePrinter = tracelog.RealtimePrinter + } else { + conf.RealtimePrinter = printer.RealtimePrinter + } + + _, err = trace.Traceroute(f.TracerouteMethod, conf) + + if err != nil { + log.Fatal(err) + } + + println() +} + +func (f *FastTracer) testAll_v6() { + f.testCT_v6() + println() + f.testCU_v6() + println() + f.testCM_v6() + println() + f.testEDU_v6() +} + +func (f *FastTracer) testCT_v6() { + f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CT163) + f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CT163) + f.tracert_v6(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CT163) + f.tracert_v6(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CT163) +} + +func (f *FastTracer) testCU_v6() { + f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU169) + f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU169) + f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU9929) + f.tracert_v6(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CU169) +} + +func (f *FastTracer) testCM_v6() { + f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CM) + f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CM) + f.tracert_v6(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CM) + f.tracert_v6(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CM) +} + +func (f *FastTracer) testEDU_v6() { + f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU) + f.tracert_v6(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.EDU) + f.tracert_v6(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.EDU) +} + +func (f *FastTracer) testFast_v6() { + f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CT163) + f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU169) + f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CM) + f.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU) +} + +func FastTestv6(tm bool, outEnable bool, paramsFastTrace ParamsFastTrace) { + var c string + + oe = outEnable + pFastTracer = paramsFastTrace + + fmt.Println("您想测试哪些ISP的路由?\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网\n6. 全部") + 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.testFast_v6() + case "2": + ft.testCT_v6() + case "3": + ft.testCU_v6() + case "4": + ft.testCM_v6() + case "5": + ft.testEDU_v6() + case "6": + ft.testAll_v6() + default: + ft.testFast_v6() + } +} diff --git a/fast_trace/fast_trace.go b/fast_trace/fast_trace.go new file mode 100644 index 00000000..bb449a91 --- /dev/null +++ b/fast_trace/fast_trace.go @@ -0,0 +1,208 @@ +package fastTrace + +import ( + "fmt" + "github.com/nxtrace/NTrace-core/ipgeo" + "github.com/nxtrace/NTrace-core/printer" + "github.com/nxtrace/NTrace-core/trace" + "github.com/nxtrace/NTrace-core/tracelog" + "github.com/nxtrace/NTrace-core/util" + "github.com/nxtrace/NTrace-core/wshandle" + "log" + "net" + "os" + "os/signal" + "time" +) + +type FastTracer struct { + TracerouteMethod trace.Method + ParamsFastTrace ParamsFastTrace +} + +type ParamsFastTrace struct { + SrcDev string + SrcAddr string + BeginHop int + MaxHops int + RDns bool + AlwaysWaitRDNS bool + Lang string + PktSize int + Timeout time.Duration +} + +var oe = false + +func (f *FastTracer) tracert(location string, ispCollection ISPCollection) { + fp, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm) + if err != nil { + return + } + defer func(fp *os.File) { + err := fp.Close() + if err != nil { + log.Fatal(err) + } + }(fp) + + log.SetOutput(fp) + log.SetFlags(0) + fmt.Printf("%s『%s %s 』%s\n", printer.YELLOW_PREFIX, location, ispCollection.ISPName, printer.RESET_PREFIX) + log.Printf("『%s %s 』\n", location, ispCollection.ISPName) + fmt.Printf("traceroute to %s, %d hops max, %d byte packets\n", ispCollection.IP, f.ParamsFastTrace.MaxHops, f.ParamsFastTrace.PktSize) + log.Printf("traceroute to %s, %d hops max, %d byte packets\n", ispCollection.IP, f.ParamsFastTrace.MaxHops, f.ParamsFastTrace.PktSize) + ip := util.DomainLookUp(ispCollection.IP, "4", "", true) + var conf = trace.Config{ + BeginHop: f.ParamsFastTrace.BeginHop, + DestIP: ip, + DestPort: 80, + MaxHops: f.ParamsFastTrace.MaxHops, + NumMeasurements: 3, + ParallelRequests: 18, + RDns: f.ParamsFastTrace.RDns, + AlwaysWaitRDNS: f.ParamsFastTrace.AlwaysWaitRDNS, + PacketInterval: 100, + TTLInterval: 500, + IPGeoSource: ipgeo.GetSource("LeoMoeAPI"), + Timeout: f.ParamsFastTrace.Timeout, + SrcAddr: f.ParamsFastTrace.SrcAddr, + PktSize: f.ParamsFastTrace.PktSize, + Lang: f.ParamsFastTrace.Lang, + } + + if oe { + conf.RealtimePrinter = tracelog.RealtimePrinter + } else { + conf.RealtimePrinter = printer.RealtimePrinter + } + + _, err = trace.Traceroute(f.TracerouteMethod, conf) + + if err != nil { + log.Fatal(err) + } + println() +} + +func FastTest(tm bool, outEnable bool, paramsFastTrace ParamsFastTrace) { + var c string + pFastTrace := paramsFastTrace + oe = outEnable + fmt.Println("Hi,欢迎使用 Fast Trace 功能,请注意 Fast Trace 功能只适合新手使用\n因为国内网络复杂,我们设置的测试目标有限,建议普通用户自测以获得更加精准的路由情况") + fmt.Println("请您选择要测试的 IP 类型\n1. IPv4\n2. IPv6") + fmt.Print("请选择选项:") + _, err := fmt.Scanln(&c) + if err != nil { + c = "1" + } + if c == "2" { + FastTestv6(tm, outEnable, paramsFastTrace) + return + } + + if pFastTrace.SrcDev != "" { + dev, _ := net.InterfaceByName(pFastTrace.SrcDev) + if addrs, err := dev.Addrs(); err == nil { + for _, addr := range addrs { + if addr.(*net.IPNet).IP.To4() != nil { + pFastTrace.SrcAddr = addr.(*net.IPNet).IP.String() + } + } + } + } + + fmt.Println("您想测试哪些ISP的路由?\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网\n6. 全部") + fmt.Print("请选择选项:") + _, err = fmt.Scanln(&c) + if err != nil { + c = "1" + } + + ft := FastTracer{ + ParamsFastTrace: pFastTrace, + } + + // 建立 WebSocket 连接 + w := wshandle.New() + w.Interrupt = make(chan os.Signal, 1) + signal.Notify(w.Interrupt, os.Interrupt) + defer func() { + w.Conn.Close() + }() + + if !tm { + ft.TracerouteMethod = trace.ICMPTrace + fmt.Println("您将默认使用ICMP协议进行路由跟踪,如果您想使用TCP SYN进行路由跟踪,可以加入 -T 参数") + } else { + ft.TracerouteMethod = trace.TCPTrace + } + + switch c { + case "1": + ft.testFast() + case "2": + ft.testCT() + case "3": + ft.testCU() + case "4": + ft.testCM() + case "5": + ft.testEDU() + case "6": + ft.testAll() + default: + ft.testFast() + } +} + +func (f *FastTracer) testAll() { + f.testCT() + println() + f.testCU() + println() + f.testCM() + println() + f.testEDU() +} + +func (f *FastTracer) testCT() { + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CT163) + f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CT163) + f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CTCN2) + f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CT163) + f.tracert(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CT163) +} + +func (f *FastTracer) testCU() { + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU169) + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU9929) + f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU169) + f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU9929) + f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CU169) + f.tracert(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CU169) +} + +func (f *FastTracer) testCM() { + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CM) + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CMIN2) + f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CM) + f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CMIN2) + f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CM) + f.tracert(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CM) +} + +func (f *FastTracer) testEDU() { + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU) + f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.EDU) + f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.EDU) + // 科技网暂时算在EDU里面,等拿到了足够多的数据再分离出去,单独用于测试 + f.tracert(TestIPsCollection.Hefei.Location, TestIPsCollection.Hefei.CST) +} + +func (f *FastTracer) testFast() { + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CT163) + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU169) + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CM) + f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU) +} diff --git a/fast_trace/fast_trace_test.go b/fast_trace/fast_trace_test.go new file mode 100644 index 00000000..e5fe70d9 --- /dev/null +++ b/fast_trace/fast_trace_test.go @@ -0,0 +1,42 @@ +package fastTrace + +import ( + "fmt" + "os" + "os/signal" + "testing" + + "github.com/nxtrace/NTrace-core/trace" + "github.com/nxtrace/NTrace-core/wshandle" +) + +func TestTrace(t *testing.T) { + pFastTrace := ParamsFastTrace{ + SrcDev: "", + SrcAddr: "", + BeginHop: 1, + MaxHops: 30, + RDns: false, + AlwaysWaitRDNS: false, + Lang: "", + PktSize: 52, + } + ft := FastTracer{ParamsFastTrace: pFastTrace} + // 建立 WebSocket 连接 + w := wshandle.New() + w.Interrupt = make(chan os.Signal, 1) + signal.Notify(w.Interrupt, os.Interrupt) + defer func() { + w.Conn.Close() + }() + fmt.Println("TCP v4") + ft.TracerouteMethod = trace.TCPTrace + //ft.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU) + //fmt.Println("TCP v6") + //ft.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU) + //fmt.Println("ICMP v4") + //ft.TracerouteMethod = trace.ICMPTrace + //ft.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU) + //fmt.Println("ICMP v6") + //ft.tracert_v6(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.EDU) +} diff --git a/go.mod b/go.mod index 6a50c5e9..abcb1117 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,46 @@ -module github.com/sjlleo/nexttrace-core +module github.com/nxtrace/NTrace-core -go 1.20 +go 1.21 require ( + github.com/akamensky/argparse v1.4.0 github.com/google/gopacket v1.1.19 - golang.org/x/net v0.10.0 - golang.org/x/sync v0.1.0 + github.com/oschwald/maxminddb-golang v1.12.0 + github.com/spf13/viper v1.16.0 + github.com/stretchr/testify v1.8.4 + github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 + github.com/tsosunchia/powclient v0.1.3 + golang.org/x/net v0.15.0 + golang.org/x/sync v0.3.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.1.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/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/spf13/afero v1.9.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.16.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect golang.org/x/text v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( - github.com/fatih/color v1.14.1 - github.com/spf13/cobra v1.7.0 + github.com/fatih/color v1.15.0 + github.com/gorilla/websocket v1.5.0 + github.com/lionsoul2014/ip2region v2.11.2+incompatible + github.com/rodaine/table v1.1.0 + github.com/tidwall/gjson v1.17.0 + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect golang.org/x/sys v0.12.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b17ed62b..81c0a375 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f 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= @@ -46,8 +48,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk 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/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/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= @@ -55,8 +57,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m 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.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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= @@ -98,6 +102,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -118,44 +124,58 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ 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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lionsoul2014/ip2region v2.11.2+incompatible h1:+VRsGcrHz8ewXI/2UzTptJlACsxD/p4xCxuql4u2nKU= +github.com/lionsoul2014/ip2region v2.11.2+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-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= +github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 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/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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 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= @@ -168,12 +188,25 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE 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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +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.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/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/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tsosunchia/powclient v0.1.3 h1:L29HQdyZZ0Vcrn0ZzNIyrTpLOnycm3oHF46sYJ+vkhQ= +github.com/tsosunchia/powclient v0.1.3/go.mod h1:Pm4MP3QqN74SfNskPpFIEyT+NQrcABGoXkkeRwjlMEE= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -256,10 +289,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 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= @@ -279,8 +310,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= @@ -318,8 +349,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w 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.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -476,6 +506,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD 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/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= diff --git a/ipgeo/chunzhen.go b/ipgeo/chunzhen.go new file mode 100644 index 00000000..c7e78131 --- /dev/null +++ b/ipgeo/chunzhen.go @@ -0,0 +1,91 @@ +package ipgeo + +import ( + "encoding/json" + "github.com/nxtrace/NTrace-core/util" + "io" + "log" + "net/http" + "strings" + "time" +) + +func Chunzhen(ip string, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) { + url := util.GetenvDefault("NEXTTRACE_CHUNZHENURL", "http://127.0.0.1:2060") + "?ip=" + ip + client := &http.Client{ + // 2 秒超时 + Timeout: timeout, + } + req, _ := http.NewRequest("GET", url, nil) + content, err := client.Do(req) + if err != nil { + log.Println("纯真 请求超时(2s),请切换其他API使用") + return &IPGeoData{}, err + } + body, _ := io.ReadAll(content.Body) + + var data map[string]interface{} + err = json.Unmarshal(body, &data) + if err != nil { + return &IPGeoData{}, err + } + city := data[ip].(map[string]interface{})["area"].(string) + region := data[ip].(map[string]interface{})["country"].(string) + var asn string + if data[ip].(map[string]interface{})["asn"] != nil { + asn = data[ip].(map[string]interface{})["asn"].(string) + } + // 判断是否前两个字为香港或台湾 + var country string + provinces := []string{ + "北京", + "天津", + "河北", + "山西", + "内蒙古", + "辽宁", + "吉林", + "黑龙江", + "上海", + "江苏", + "浙江", + "安徽", + "福建", + "江西", + "山东", + "河南", + "湖北", + "湖南", + "广东", + "广西", + "海南", + "重庆", + "四川", + "贵州", + "云南", + "西藏", + "陕西", + "甘肃", + "青海", + "宁夏", + "新疆", + "台湾", + "香港", + "澳门", + } + for _, province := range provinces { + if strings.Contains(region, province) { + country = "中国" + city = region + city + break + } + } + if country == "" { + country = region + } + return &IPGeoData{ + Asnumber: asn, + Country: country, + City: city, + }, nil +} diff --git a/ipgeo/dn42.go b/ipgeo/dn42.go new file mode 100644 index 00000000..f691313d --- /dev/null +++ b/ipgeo/dn42.go @@ -0,0 +1,63 @@ +package ipgeo + +import ( + "strings" + "time" + + "github.com/nxtrace/NTrace-core/dn42" +) + +func LtdCodeToCountryOrAreaName(Code string) string { + countryName := []string{"United States", "Afghanistan", "Åland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", " Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada ", "Cape Verde", "Cayman Islands", "Central Africa", "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo (Brazzaville)", "DRC", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", " Denmark", "Djibouti", "Dominica", "Dominica", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)", "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", " French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Vatican", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "British Isles of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "North Korea", "South Korea", " Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "FYROM", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", " Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia (Federated States of)", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", "Rwanda", "St. Helena", "St. Kitts and Nevis", "St. Lucia", "St. Pierre and Miquelon", "St. Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen Islands", "Swaziland", "Sweden ", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", " United Kingdom", "U.S. Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "British Virgin Islands", "U.S. Virgin Islands", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"} + countryCode := []string{"us", "af", "ax", "al", "dz", "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "au", "at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi", "kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cn", "cx", "cc", "co", "km", "cg", "cd", "ck", "cr", "ci", "hr", "cu", "cy", "cz", "dk", "dj", "dm", "do", "ec", "eg", "sv", "gq", "er", "ee", "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm", "ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg", "gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in", "id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo", "kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls", "lr", "ly", "li", "lt", "lu", "mo", "mk", "mg", "mw", "my", "mv", "ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc", "mn", "me", "ms", "ma", "mz", "mm", "na", "nr", "np", "nl", "an", "nc", "nz", "ni", "ne", "ng", "nu", "nf", "mp", "no", "om", "pk", "pw", "ps", "pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re", "ro", "ru", "rw", "sh", "kn", "lc", "pm", "vc", "ws", "sm", "st", "sa", "sn", "rs", "sc", "sl", "sg", "sk", "si", "sb", "so", "za", "gs", "es", "lk", "sd", "sr", "sj", "sz", "se", "ch", "sy", "tw", "tj", "tz", "th", "tl", "tg", "tk", "to", "tt", "tn", "tr", "tm", "tc", "tv", "ug", "ua", "ae", "gb", "um", "uy", "uz", "vu", "ve", "vn", "vg", "vi", "wf", "eh", "ye", "zm", "zw"} + Code = strings.ToLower(Code) + for i, v := range countryCode { + if strings.Contains(Code, v) { + return countryName[i] + } + } + return Code +} + +func DN42(ip string, _ time.Duration, _ string, _ bool) (*IPGeoData, error) { + data := &IPGeoData{} + // 先解析传入过来的数据 + ipTmp := strings.Split(ip, ",") + if len(ipTmp) > 1 { + ip = ipTmp[0] + } + // 先查找 GeoFeed + if geo, find := dn42.GetGeoFeed(ip); find { + data.Country = geo.LtdCode + data.City = geo.City + data.Asnumber = geo.ASN + data.Whois = geo.IPWhois + } + // 如果没找到,查找 PTR + if len(ipTmp) > 1 { + // 存在 PTR 记录 + if res, err := dn42.FindPtrRecord(ipTmp[1]); err == nil && res.LtdCode != "" { + data.Country = res.LtdCode + data.Prov = res.Region + data.City = res.City + } + } + + data.Country = LtdCodeToCountryOrAreaName(data.Country) + + switch data.Country { + case "Hong Kong": + data.Country = "China" + data.Prov = "Hong Kong" + case "Taiwan": + data.Country = "China" + data.Prov = "Taiwan" + case "Macao": + data.Country = "China" + data.Prov = "Macao" + case "": + data.Country = "Unknown" + } + + return data, nil +} diff --git a/ipgeo/dn42_test.go b/ipgeo/dn42_test.go new file mode 100644 index 00000000..0ebc61de --- /dev/null +++ b/ipgeo/dn42_test.go @@ -0,0 +1,5 @@ +package ipgeo + +// func TestDN42(t *testing.T) { +// DN42("") +// } diff --git a/ipgeo/ip2region.go b/ipgeo/ip2region.go new file mode 100644 index 00000000..a49c6dbd --- /dev/null +++ b/ipgeo/ip2region.go @@ -0,0 +1,88 @@ +package ipgeo + +import ( + "errors" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/lionsoul2014/ip2region/v1.0/binding/golang/ip2region" +) + +const ( + ipDataBasePath = "./ip2region.db" + defaultDownURL = "1" + originURL = "https://ghproxy.com/?q=https://github.com/bqf9979/ip2region/blob/master/data/ip2region.db?raw=true" +) + +func downloadDataBase() error { + fmt.Println("Downloading DataBase...") + resp, err := http.Get(originURL) + if err != nil { + return err + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + panic(err) + } + }(resp.Body) + + // Create the file + out, err := os.Create(ipDataBasePath) + if err != nil { + return err + } + defer func(out *os.File) { + err := out.Close() + if err != nil { + panic(err) + } + }(out) + + // Write the body to file + _, err = io.Copy(out, resp.Body) + return err +} + +func IP2Region(ip string, _ time.Duration, _ string, _ bool) (*IPGeoData, error) { + if _, err := os.Stat(ipDataBasePath); os.IsNotExist(err) { + if err = downloadDataBase(); err != nil { + panic("Download Failed!") + } + } + region, err := ip2region.New(ipDataBasePath) + if err != nil { + panic("Cannot find ip2region.db") + } + defer region.Close() + info, searchErr := region.MemorySearch(ip) + if searchErr != nil { + return &IPGeoData{}, errors.New("no results") + } + + if info.Country == "0" { + info.Country = "" + } + + if info.Province == "0" { + info.Province = "" + } + + if info.City == "0" { + info.City = "" + } + + if info.ISP == "0" { + info.ISP = "" + } + + return &IPGeoData{ + Owner: info.ISP, + Country: info.Country, + Prov: info.Province, + City: info.City, + }, nil +} diff --git a/ipgeo/ipapicom.go b/ipgeo/ipapicom.go new file mode 100644 index 00000000..270ca55e --- /dev/null +++ b/ipgeo/ipapicom.go @@ -0,0 +1,60 @@ +package ipgeo + +import ( + "errors" + "github.com/nxtrace/NTrace-core/util" + "io" + "log" + "net/http" + "regexp" + "strconv" + "time" + + "github.com/tidwall/gjson" +) + +func IPApiCom(ip string, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) { + url := "http://ip-api.com/json/" + ip + "?fields=status,message,country,regionName,city,isp,district,as,lat,lon" + client := &http.Client{ + // 2 秒超时 + Timeout: timeout, + } + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0") + content, err := client.Do(req) + if err != nil { + log.Println("ip-api.com 请求超时(2s),请切换其他API使用") + return nil, err + } + body, _ := io.ReadAll(content.Body) + res := gjson.ParseBytes(body) + + if res.Get("status").String() != "success" { + return &IPGeoData{}, errors.New("超过API阈值") + } + + re := regexp.MustCompile("[0-9]+") + var country = res.Get("country").String() + var prov = res.Get("region").String() + var city = res.Get("city").String() + var district = res.Get("district").String() + if util.StringInSlice(country, []string{"Hong Kong", "Taiwan", "Macao"}) { + district = prov + " " + city + " " + district + city = country + prov = "" + country = "China" + } + lat, _ := strconv.ParseFloat(res.Get("lat").String(), 32) + lng, _ := strconv.ParseFloat(res.Get("lon").String(), 32) + + return &IPGeoData{ + Asnumber: re.FindString(res.Get("as").String()), + Country: country, + City: city, + Prov: prov, + District: district, + Owner: res.Get("isp").String(), + Lat: lat, + Lng: lng, + }, nil +} diff --git a/ipgeo/ipfilter.go b/ipgeo/ipfilter.go new file mode 100644 index 00000000..a7b95db3 --- /dev/null +++ b/ipgeo/ipfilter.go @@ -0,0 +1,174 @@ +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 { + case cidrRangeContains("0.0.0.0/8", ip): + asn = "" + whois = "RFC1122" + isFiltered = true + //IANA Reserved Address Space + case cidrRangeContains("100.64.0.0/10", ip): + asn = "" + whois = "RFC6598" + isFiltered = true + //127.0.0.0/8 + case cidrRangeContains("127.0.0.0/8", ip): + asn = "" + whois = "RFC1122" + isFiltered = true + //169.254.0.0/16 + case cidrRangeContains("169.254.0.0/16", ip): + asn = "" + whois = "RFC3927" + isFiltered = true + //192.0.0.0/24 + case cidrRangeContains("192.0.0.0/24", ip): + asn = "" + whois = "RFC6890" + isFiltered = true + //192.0.2.0/24 + case cidrRangeContains("192.0.2.0/24", ip): + asn = "" + whois = "RFC5737" + isFiltered = true + //192.88.99.0/24 + case cidrRangeContains("192.88.99.0/24", ip): + asn = "" + whois = "RFC3068" + 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 + //224.0.0.0/4 + case cidrRangeContains("224.0.0.0/4", ip): + asn = "" + whois = "RFC5771" + isFiltered = true + //255.255.255.255/32 + case cidrRangeContains("255.255.255.255/32", ip): + asn = "" + whois = "RFC0919" + isFiltered = true + case cidrRangeContains("240.0.0.0/4", ip): + asn = "" + whois = "RFC1112" + isFiltered = true + case cidrRangeContains("fe80::/10", ip): + asn = "" + whois = "RFC4291" + isFiltered = true + case cidrRangeContains("ff00::/8", ip): + asn = "" + whois = "RFC4291" + isFiltered = true + case cidrRangeContains("fec0::/10", ip): + asn = "" + whois = "RFC3879" + isFiltered = true + case cidrRangeContains("fe00::/9", ip): + asn = "" + whois = "RFC4291" + isFiltered = true + case cidrRangeContains("64:ff9b::/96", ip): + asn = "" + whois = "RFC6052" + isFiltered = true + case cidrRangeContains("0::/96", ip): + asn = "" + whois = "RFC4291" + isFiltered = true + case cidrRangeContains("64:ff9b:1::/48", ip): + asn = "" + whois = "RFC6052" + isFiltered = true + case cidrRangeContains("2001:db8::/32", ip): + asn = "" + whois = "RFC3849" + isFiltered = true + case cidrRangeContains("2002::/16", ip): + asn = "" + whois = "RFC3056" + isFiltered = true + case net.ParseIP(ip).IsPrivate(): + //rfc4193 + if cidrRangeContains("fc00::/7", ip) { + asn = "" + whois = "RFC4193" + isFiltered = true + //rfc1918 + } else { + asn = "" + whois = "RFC1918" + 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: + } + // 判断是否为v6 且不在2000::/3 + if net.ParseIP(ip).To4() == nil && !cidrRangeContains("2000::/3", ip) { + asn = "" + whois = "INVALID" + isFiltered = true + } + if !isFiltered { + return nil, false + } else { + return &IPGeoData{ + Asnumber: asn, + Whois: whois, + }, true + } +} diff --git a/ipgeo/ipgeo.go b/ipgeo/ipgeo.go new file mode 100644 index 00000000..ec6923e1 --- /dev/null +++ b/ipgeo/ipgeo.go @@ -0,0 +1,62 @@ +package ipgeo + +import ( + "strings" + "time" +) + +type IPGeoData struct { + 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, timeout time.Duration, lang string, maptrace bool) (*IPGeoData, error) + +func GetSource(s string) Source { + switch strings.ToUpper(s) { + case "DN42": + return DN42 + case "LEOMOEAPI": + return LeoIP + case "IP.SB": + return IPSB + case "IPINSIGHT": + return IPInSight + case "IPAPI.COM": + return IPApiCom + case "IP-API.COM": + return IPApiCom + case "IPINFO": + return IPInfo + case "IP2REGION": + return IP2Region + case "IPINFOLOCAL": + return IPInfoLocal + case "CHUNZHEN": + return Chunzhen + case "DISABLE-GEOIP": + return disableGeoIP + default: + return LeoIP + } +} + +func disableGeoIP(string, time.Duration, string, bool) (*IPGeoData, error) { + return &IPGeoData{}, nil +} diff --git a/ipgeo/ipgeo_test.go b/ipgeo/ipgeo_test.go new file mode 100644 index 00000000..87999dee --- /dev/null +++ b/ipgeo/ipgeo_test.go @@ -0,0 +1,120 @@ +package ipgeo + +import ( + "errors" + "fmt" + "log" + "os" + "strconv" + "testing" +) + +// 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 TestFilter(t *testing.T) { + res, err := Filter("::1") + //打印whois信息 + fmt.Println(res.Whois) + print(err) +} + +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 TestLeoIP(t *testing.T) { +// // res, err := LeoIP("1.1.1.1") +// // assert.Nil(t, err) +// // assert.NotNil(t, res) +// // assert.NotEmpty(t, res.Asnumber) +// // assert.NotEmpty(t, res.Isp) +// } + +// func TestIPSB(t *testing.T) { +// // Not available +// //res, err := IPSB("1.1.1.1") +// //assert.Nil(t, err) +// //assert.NotNil(t, res) +// //assert.NotEmpty(t, res.Asnumber) +// //assert.NotEmpty(t, res.Isp) +// } + +// func TestIPInfo(t *testing.T) { +// res, err := IPInfo("1.1.1.1") +// assert.Nil(t, err) +// assert.NotNil(t, res) +// // assert.NotEmpty(t, res.Country) +// assert.NotEmpty(t, res.City) +// assert.NotEmpty(t, res.Prov) +// } + +// func TestIPInSight(t *testing.T) { +// // res, err := IPInSight("1.1.1.1") +// // assert.Nil(t, err) +// // assert.NotNil(t, res) +// // assert.NotEmpty(t, res.Country) +// // assert.NotEmpty(t, res.Prov) +// // 这个库有时候不提供城市信息,返回值为"" +// //assert.NotEmpty(t, res.City) +// } + +// func TestIPApiCom(t *testing.T) { +// res, err := IPApiCom("1.1.1.1") +// assert.Nil(t, err) +// assert.NotNil(t, res) +// assert.NotEmpty(t, res.Country) +// assert.NotEmpty(t, res.City) +// assert.NotEmpty(t, res.Prov) +// } diff --git a/ipgeo/ipinfo.go b/ipgeo/ipinfo.go new file mode 100644 index 00000000..30749c73 --- /dev/null +++ b/ipgeo/ipinfo.go @@ -0,0 +1,339 @@ +package ipgeo + +import ( + "github.com/nxtrace/NTrace-core/util" + "io" + "net/http" + "strconv" + "strings" + "time" + + "github.com/tidwall/gjson" +) + +func IPInfo(ip string, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) { + url := "http://ipinfo.io/" + ip + "?token=" + token.ipinfo + client := &http.Client{ + // 2 秒超时 + Timeout: timeout, + } + resp, err := client.Get(url) + //resp, err := http.Get("https://ipinfo.io/" + ip + "?token=" + token.ipinfo) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + res := gjson.ParseBytes(body) + + // 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", + } + var country = res.Get("country").String() + var prov = res.Get("region").String() + var city = res.Get("city").String() + var district = "" + if util.StringInSlice(country, []string{"TW", "MO", "HK"}) { + district = prov + " " + city + city = countryMap[country] + prov = "" + country = "CN" + } + country = countryMap[country] + + var anycast = false + if res.Get("anycast").String() == "true" { + country = "ANYCAST" + prov = "ANYCAST" + city = "" + anycast = true + } + + i := strings.Index(res.Get("org").String(), " ") + var owner string + if i == -1 { + owner = "" + } else { + owner = res.Get("org").String()[i:] + } + + var asnumber = "" + // 有时候不返回asn或其本身没有asn + if strings.HasPrefix(res.Get("org").String(), "AS") { + asnumber = strings.Fields(strings.TrimPrefix(res.Get("org").String(), "AS"))[0] + } + + //"loc": "34.0522,-118.2437", + var lat, lng float64 + if res.Get("loc").String() != "" { + lat, _ = strconv.ParseFloat(strings.Split(res.Get("loc").String(), ",")[0], 32) + lng, _ = strconv.ParseFloat(strings.Split(res.Get("loc").String(), ",")[1], 32) + } + if anycast { + lat, lng = 0, 0 + } + + return &IPGeoData{ + Asnumber: asnumber, + Country: country, + City: city, + Prov: prov, + District: district, + Owner: owner, + Lat: lat, + Lng: lng, + }, nil +} diff --git a/ipgeo/ipinfoLocal.go b/ipgeo/ipinfoLocal.go new file mode 100644 index 00000000..b897017f --- /dev/null +++ b/ipgeo/ipinfoLocal.go @@ -0,0 +1,53 @@ +package ipgeo + +import ( + "errors" + "github.com/oschwald/maxminddb-golang" + "net" + "os" + "strings" + "time" +) + +const ( + ipinfoDataBasePath = "./ipinfoLocal.mmdb" +) + +func IPInfoLocal(ip string, _ time.Duration, _ string, _ bool) (*IPGeoData, error) { + if _, err := os.Stat(ipinfoDataBasePath); os.IsNotExist(err) { + panic("Cannot find ipinfoLocal.mmdb") + } + region, err := maxminddb.Open(ipinfoDataBasePath) + if err != nil { + panic("Cannot find ipinfoLocal.mmdb") + } + defer func(region *maxminddb.Reader) { + err := region.Close() + if err != nil { + panic(err) + } + }(region) + var record interface{} + searchErr := region.Lookup(net.ParseIP(ip), &record) + if searchErr != nil { + return &IPGeoData{}, errors.New("no results") + } + recordMap := record.(map[string]interface{}) + country_name := recordMap["country_name"].(string) + prov := "" + if recordMap["country"].(string) == "HK" { + country_name = "China" + prov = "Hong Kong" + } + if recordMap["country"].(string) == "TW" { + country_name = "China" + prov = "Taiwan" + } + return &IPGeoData{ + Asnumber: strings.TrimPrefix(recordMap["asn"].(string), "AS"), + Country: country_name, + City: "", + Prov: prov, + Owner: recordMap["as_name"].(string), + }, nil +} diff --git a/ipgeo/ipinsight.go b/ipgeo/ipinsight.go new file mode 100644 index 00000000..0e7cfdac --- /dev/null +++ b/ipgeo/ipinsight.go @@ -0,0 +1,32 @@ +package ipgeo + +import ( + "io" + "net/http" + "time" + + "github.com/tidwall/gjson" +) + +func IPInSight(ip string, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) { + client := &http.Client{ + // 2 秒超时 + Timeout: timeout, + } + resp, err := client.Get("https://api.ipinsight.io/ip/" + ip + "?token=" + token.ipinsight) + if err != nil { + return nil, err + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + res := gjson.ParseBytes(body) + + return &IPGeoData{ + Country: res.Get("country_name").String(), + City: res.Get("city_name").String(), + Prov: res.Get("region_name").String(), + }, nil +} diff --git a/ipgeo/ipsb.go b/ipgeo/ipsb.go new file mode 100644 index 00000000..1d9fbad8 --- /dev/null +++ b/ipgeo/ipsb.go @@ -0,0 +1,42 @@ +package ipgeo + +import ( + "io" + "log" + "net/http" + "os" + "time" + + "github.com/tidwall/gjson" +) + +func IPSB(ip string, timeout time.Duration, _ string, _ bool) (*IPGeoData, error) { + url := "https://api.ip.sb/geoip/" + ip + client := &http.Client{ + // 2 秒超时 + Timeout: timeout, + } + req, _ := http.NewRequest("GET", url, nil) + // 设置 UA,ip.sb 默认禁止 go-client User-Agent 的 api 请求 + req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0") + content, err := client.Do(req) + if err != nil { + log.Println("api.ip.sb 请求超时(2s),请切换其他API使用") + return nil, err + } + body, _ := io.ReadAll(content.Body) + res := gjson.ParseBytes(body) + + if res.Get("country").String() == "" { + // 什么都拿不到,证明被Cloudflare风控了 + os.Exit(1) + } + + return &IPGeoData{ + Asnumber: res.Get("asn").String(), + Country: res.Get("country").String(), + City: res.Get("city").String(), + Prov: res.Get("region").String(), + Owner: res.Get("isp").String(), + }, nil +} diff --git a/ipgeo/leo.go b/ipgeo/leo.go new file mode 100644 index 00000000..ba09ca21 --- /dev/null +++ b/ipgeo/leo.go @@ -0,0 +1,114 @@ +package ipgeo + +import ( + "encoding/json" + "errors" + "strconv" + "sync" + "time" + + "github.com/nxtrace/NTrace-core/wshandle" + "github.com/tidwall/gjson" +) + +/*** + * 原理介绍 By Leo + * WebSocket 一共开启了一个发送和一个接收协程,在 New 了一个连接的实例对象后,不给予关闭,持续化连接 + * 当有新的IP请求时,一直在等待IP数据的发送协程接收到从 leo.go 的 sendIPRequest 函数发来的IP数据,向服务端发送数据 + * 由于实际使用时有大量并发,但是 ws 在同一时刻每次有且只能处理一次发送一条数据,所以必须给 ws 连接上互斥锁,保证每次只有一个协程访问 + * 运作模型可以理解为一个 Node 一直在等待数据,当获得一个新的任务后,转交给下一个协程,不再关注这个 Node 的下一步处理过程,并且回到空闲状态继续等待新的任务 +***/ + +// IPPool IP 查询池 map - ip - ip channel +type IPPool struct { + pool map[string]chan IPGeoData + poolMux sync.Mutex +} + +var IPPools = IPPool{ + pool: make(map[string]chan IPGeoData), +} + +func sendIPRequest(ip string) { + wsConn := wshandle.GetWsConn() + wsConn.MsgSendCh <- ip +} + +func receiveParse() { + // 获得连接实例 + wsConn := wshandle.GetWsConn() + // 防止多协程抢夺一个ws连接,导致死锁,当一个协程获得ws的控制权后上锁 + wsConn.ConnMux.Lock() + // 函数退出时解锁,给其他协程使用 + defer wsConn.ConnMux.Unlock() + for { + // 接收到了一条IP信息 + data := <-wsConn.MsgReceiveCh + + // json解析 -> data + res := gjson.Parse(data) + // 根据返回的IP信息,发送给对应等待回复的IP通道上 + var domain = res.Get("domain").String() + + if res.Get("domain").String() == "" { + domain = res.Get("owner").String() + } + + m := make(map[string][]string) + err := json.Unmarshal([]byte(res.Get("router").String()), &m) + if err != nil { + // 此处是正常的,因为有些IP没有路由信息 + } + + lat, _ := strconv.ParseFloat(res.Get("lat").String(), 32) + lng, _ := strconv.ParseFloat(res.Get("lng").String(), 32) + + IPPools.pool[gjson.Parse(data).Get("ip").String()] <- IPGeoData{ + Asnumber: res.Get("asnumber").String(), + Country: res.Get("country").String(), + CountryEn: res.Get("country_en").String(), + Prov: res.Get("prov").String(), + ProvEn: res.Get("prov_en").String(), + City: res.Get("city").String(), + CityEn: res.Get("city_en").String(), + District: res.Get("district").String(), + Owner: domain, + Lat: lat, + Lng: lng, + Isp: res.Get("isp").String(), + Whois: res.Get("whois").String(), + Prefix: res.Get("prefix").String(), + Router: m, + } + } +} + +func LeoIP(ip string, timeout time.Duration, lang string, maptrace bool) (*IPGeoData, error) { + // TODO: 根据lang的值请求中文/英文API + // TODO: 根据maptrace的值决定是否请求经纬度信息 + if timeout < 5*time.Second { + timeout = 5 * time.Second + } + + // 缓存中没有找到IP信息,需要请求API获取 + IPPools.poolMux.Lock() + // 如果之前已经被别的协程初始化过了就不用初始化了 + if IPPools.pool[ip] == nil { + IPPools.pool[ip] = make(chan IPGeoData) + } + IPPools.poolMux.Unlock() + // 发送请求 + sendIPRequest(ip) + // 同步开启监听 + go receiveParse() + + // 拥塞,等待数据返回 + select { + case res := <-IPPools.pool[ip]: + return &res, nil + // 5秒后依旧没有接收到返回的IP数据,不再等待,超时异常处理 + case <-time.After(timeout): + // 这里不可以返回一个 nil,否则在访问对象内部的键值的时候会报空指针的 Fatal Error + return &IPGeoData{}, errors.New("TimeOut") + } +} diff --git a/ipgeo/tokens.go b/ipgeo/tokens.go new file mode 100644 index 00000000..84d6a02c --- /dev/null +++ b/ipgeo/tokens.go @@ -0,0 +1,15 @@ +package ipgeo + +import "github.com/nxtrace/NTrace-core/util" + +type tokenData struct { + ipinsight string + ipinfo string + ipleo string +} + +var token = tokenData{ + ipinsight: util.GetenvDefault("NEXTTRACE_IPINSIGHT_TOKEN", ""), + ipinfo: util.GetenvDefault("NEXTTRACE_IPINFO_TOKEN", ""), + ipleo: "NextTraceDemo", +} diff --git a/main.go b/main.go index 6cc5ac8e..9f23ba90 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,9 @@ package main -import "github.com/sjlleo/nexttrace-core/cmd" +import ( + "github.com/nxtrace/NTrace-core/cmd" +) func main() { - cmd.Execute() + cmd.Excute() } diff --git a/nt_config.yaml b/nt_config.yaml new file mode 100644 index 00000000..e884f408 --- /dev/null +++ b/nt_config.yaml @@ -0,0 +1,2 @@ +geofeedpath: ./geofeed.csv +ptrpath: ./ptr.csv diff --git a/nt_install.sh b/nt_install.sh index d80fcb32..f24e1663 100644 --- a/nt_install.sh +++ b/nt_install.sh @@ -93,7 +93,7 @@ checkWgetPackage() { downloadBinrayFile() { echo -e "${Info} 获取最新版的 NextTrace 发行版文件信息" # 简单说明一下,Github提供了一个API,可以获取最新发行版本的二进制文件下载地址(对应的是browser_download_url),根据刚刚测得的osDistribution、archParam,获取对应的下载地址 - latestURL=$(curl -s http://nexttrace-io-leomoe-api-a0.shop/latest.json | grep -i "browser_download_url.*${osDistribution}.*${archParam}" | awk -F '"' '{print $4}') + latestURL=$(curl -sL http://nexttrace-io-leomoe-api-a0.shop/latest_v1.json | grep -i "browser_download_url.*${osDistribution}.*${archParam}" | awk -F '"' '{print $4}') echo -e "${Info} 正在下载 NextTrace 二进制文件..." wget -O ${Temp_path} ${latestURL} &> /dev/null diff --git a/plgn/debug.go b/plgn/debug.go deleted file mode 100644 index cc13d720..00000000 --- a/plgn/debug.go +++ /dev/null @@ -1,42 +0,0 @@ -package plgn - -import ( - "fmt" - "net" - - "github.com/sjlleo/nexttrace-core/core" -) - -type DebugPlugin struct { - DefaultPlugin - DebugLevel int -} - -func NewDebugPlugin(params interface{}) core.Plugin { - debugLevel, ok := params.(int) - if !ok { - return nil - } - return &DebugPlugin{DebugLevel: debugLevel} -} - -func (d *DebugPlugin) OnTTLChange(ttl int) error { - if d.DebugLevel <= 2 { - fmt.Println("Debug Level 2: TTL changed to", ttl) - } - return nil -} - -func (d *DebugPlugin) OnNewIPFound(ip net.Addr) error { - if d.DebugLevel <= 2 { - fmt.Println("Debug Level 2: New IP Found: ", ip) - } - return nil -} - -func (d *DebugPlugin) OnTTLCompleted(ttl int, hop []core.Hop) error { - if d.DebugLevel <= 2 { - fmt.Println("Debug Level 2: ttl=", ttl, "Hop:", hop) - } - return nil -} diff --git a/plgn/default.go b/plgn/default.go deleted file mode 100644 index f7b7b8f2..00000000 --- a/plgn/default.go +++ /dev/null @@ -1,26 +0,0 @@ -package plgn - -import ( - "net" - - "github.com/sjlleo/nexttrace-core/core" -) - -type DefaultPlugin struct { -} - -func (d *DefaultPlugin) OnDNSResolve(domain string) (net.IP, error) { - return nil, nil -} - -func (d *DefaultPlugin) OnNewIPFound(ip net.Addr) error { - return nil -} - -func (d *DefaultPlugin) OnTTLChange(ttl int) error { - return nil -} - -func (d *DefaultPlugin) OnTTLCompleted(ttl int, hop []core.Hop) error { - return nil -} diff --git a/plgn/plugin.go b/plgn/plugin.go deleted file mode 100644 index 68268858..00000000 --- a/plgn/plugin.go +++ /dev/null @@ -1,46 +0,0 @@ -package plgn - -import ( - "log" - "reflect" - "strings" - - "github.com/sjlleo/nexttrace-core/core" -) - -var pluginRegistry = make(map[string]func(interface{}) core.Plugin) - -func RegisterPlugin(name string, constructor func(interface{}) core.Plugin) { - pluginRegistry[name] = constructor -} - -func CreatePlugins(enabledPlugins string, params interface{}) []core.Plugin { - var plugins []core.Plugin - for _, name := range strings.Split(enabledPlugins, ",") { - if constructor, exists := pluginRegistry[name]; exists { - plugins = append(plugins, constructor(params)) - } - } - return plugins -} - -func ExecuteHook(plugin core.Plugin, hookName string, args ...interface{}) { - v := reflect.ValueOf(plugin) - method := v.MethodByName(hookName) - - if !method.IsValid() { - log.Printf("Method %s not found", hookName) - return - } - - in := make([]reflect.Value, len(args)) - for i, arg := range args { - in[i] = reflect.ValueOf(arg) - } - - ret := method.Call(in) - if len(ret) > 0 && !ret[0].IsNil() { - err := ret[0].Interface().(error) - log.Printf("Error in %s: %v", hookName, err) - } -} diff --git a/pow/pow.go b/pow/pow.go new file mode 100644 index 00000000..c539826f --- /dev/null +++ b/pow/pow.go @@ -0,0 +1,42 @@ +package pow + +import ( + "fmt" + "github.com/nxtrace/NTrace-core/util" + "github.com/tsosunchia/powclient" + "net/url" + "os" +) + +const ( + baseURL = "/v3/challenge" +) + +func GetToken(fastIp string, host string, port string) (string, error) { + getTokenParams := powclient.NewGetTokenParams() + u := url.URL{Scheme: "https", Host: fastIp + ":" + port, Path: baseURL} + getTokenParams.BaseUrl = u.String() + getTokenParams.SNI = host + getTokenParams.Host = host + getTokenParams.UserAgent = util.UserAgent + proxyUrl := util.GetProxy() + if proxyUrl != nil { + getTokenParams.Proxy = proxyUrl + } + var err error + // 尝试三次RetToken,如果都失败了,异常退出 + for i := 0; i < 3; i++ { + token, err := powclient.RetToken(getTokenParams) + if err != nil { + continue + } + //fmt.Println("GetToken success", token, getTokenParams.UserAgent) + return token, nil + } + if err != nil { + fmt.Println(err) + } + fmt.Println("RetToken failed 3 times, please try again after a while, exit") + os.Exit(1) + return "", nil +} diff --git a/pow/pow_test.go b/pow/pow_test.go new file mode 100644 index 00000000..26c53709 --- /dev/null +++ b/pow/pow_test.go @@ -0,0 +1,19 @@ +package pow + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestGetToken(t *testing.T) { + // 计时开始 + start := time.Now() + token, err := GetToken("api.leo.moe", "api.leo.moe", "443") + // 计时结束 + end := time.Now() + fmt.Println("耗时:", end.Sub(start)) + fmt.Println("token:", token) + assert.NoError(t, err, "GetToken() returned an error") +} diff --git a/printer/basic.go b/printer/basic.go new file mode 100644 index 00000000..686743a1 --- /dev/null +++ b/printer/basic.go @@ -0,0 +1,112 @@ +package printer + +import ( + "fmt" + "github.com/nxtrace/NTrace-core/config" + "github.com/nxtrace/NTrace-core/trace" + "net" + + "github.com/fatih/color" +) + +var version = config.Version +var buildDate = config.BuildDate +var commitID = config.CommitID + +func Version() { + fmt.Fprintf(color.Output, "%s %s %s %s\n", + color.New(color.FgWhite, color.Bold).Sprintf("%s", "NextTrace"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", version), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", buildDate), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", commitID), + ) +} + +func CopyRight() { + fmt.Fprintf(color.Output, "\n%s\n%s\n%s %s\n\n%s\n%s %s\n%s %s\n%s %s\n\n%s\n%s\n%s %s\n\n", + color.New(color.FgCyan, color.Bold).Sprintf("%s", "NextTrace CopyRight"), + color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Project Creator"), + color.New(color.FgWhite, color.Bold).Sprintf("%s", "Leo"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "i@leo.moe"), + color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Project Maintainer"), + color.New(color.FgWhite, color.Bold).Sprintf("%s", "Tso"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "tsosunchia@gmail.com"), + color.New(color.FgWhite, color.Bold).Sprintf("%s", "Vincent"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "i@vincent.moe"), + color.New(color.FgWhite, color.Bold).Sprintf("%s", "Leo"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "i@leo.moe"), + color.New(color.FgCyan, color.Bold).Sprintf("%s", "Special Acknowledgement List"), + color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Major Contributor"), + color.New(color.FgWhite, color.Bold).Sprintf("%s", "zhshch"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "zhshch@athorx.com"), + ) + + MoeQingOrgCopyRight() + PluginCopyRight() +} + +func MoeQingOrgCopyRight() { + fmt.Fprintf(color.Output, "%s\n%s %s\n%s %s\n\n", + color.New(color.FgHiYellow, color.Bold).Sprintf("%s", "MoeQing Network"), + color.New(color.FgWhite, color.Bold).Sprintf("%s", "YekongTAT"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "yekongtat@gmail.com"), + color.New(color.FgWhite, color.Bold).Sprintf("%s", "Haima"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "haima@peers.cloud"), + ) +} + +func PluginCopyRight() { + fmt.Fprintf(color.Output, "%s\n%s %s\n\n", + color.New(color.FgGreen, color.Bold).Sprintf("%s", "NextTrace Map Plugin Author"), + color.New(color.FgWhite, color.Bold).Sprintf("%s", "Tso"), + color.New(color.FgHiBlack, color.Bold).Sprintf("%s", "tsosunchia@gmail.com"), + ) +} + +func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string, maxHops int, packetSize int) { + fmt.Println("IP Geo Data Provider: " + dataOrigin) + + if ip.String() == domain { + fmt.Printf("traceroute to %s, %d hops max, %d bytes packets\n", ip.String(), maxHops, packetSize) + } else { + fmt.Printf("traceroute to %s (%s), %d hops max, %d bytes packets\n", ip.String(), domain, maxHops, packetSize) + } +} + +func applyLangSetting(h *trace.Hop) { + if len(h.Geo.Country) <= 1 { + //打印h.geo + if h.Geo.Whois != "" { + h.Geo.Country = h.Geo.Whois + } else { + if h.Geo.Source != "LeoMoeAPI" { + h.Geo.Country = "网络故障" + h.Geo.CountryEn = "Network Error" + } else { + h.Geo.Country = "未知" + h.Geo.CountryEn = "Unknown" + } + } + } + + if h.Lang == "en" { + if h.Geo.Country == "Anycast" { + + } else if h.Geo.Prov == "骨干网" { + h.Geo.Prov = "BackBone" + } else if h.Geo.ProvEn == "" { + h.Geo.Country = h.Geo.CountryEn + } else { + if h.Geo.CityEn == "" { + h.Geo.Country = h.Geo.ProvEn + h.Geo.Prov = h.Geo.CountryEn + h.Geo.City = "" + } else { + h.Geo.Country = h.Geo.CityEn + h.Geo.Prov = h.Geo.ProvEn + h.Geo.City = h.Geo.CountryEn + } + } + } + +} diff --git a/printer/classic_printer.go b/printer/classic_printer.go new file mode 100644 index 00000000..c5330abb --- /dev/null +++ b/printer/classic_printer.go @@ -0,0 +1,104 @@ +package printer + +import ( + "fmt" + "strings" + + "github.com/nxtrace/NTrace-core/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]) + } +} diff --git a/printer/easy.go b/printer/easy.go new file mode 100644 index 00000000..daf66486 --- /dev/null +++ b/printer/easy.go @@ -0,0 +1,32 @@ +package printer + +import ( + "fmt" + + "github.com/nxtrace/NTrace-core/trace" +) + +func EasyPrinter(res *trace.Result, ttl int) { + for i := range res.Hops[ttl] { + if res.Hops[ttl][i].Address == nil { + fmt.Printf("%d|*||||||\n", ttl+1) + continue + } + applyLangSetting(&res.Hops[ttl][i]) // 应用语言设置 + fmt.Printf( + "%d|%s|%s|%.2f|%s|%s|%s|%s|%s|%s|%.4f|%.4f\n", + ttl+1, + res.Hops[ttl][i].Address.String(), + res.Hops[ttl][i].Hostname, + float64(res.Hops[ttl][i].RTT.Microseconds())/1000, + res.Hops[ttl][i].Geo.Asnumber, + res.Hops[ttl][i].Geo.Country, + res.Hops[ttl][i].Geo.Prov, + res.Hops[ttl][i].Geo.City, + res.Hops[ttl][i].Geo.District, + res.Hops[ttl][i].Geo.Owner, + res.Hops[ttl][i].Geo.Lat, + res.Hops[ttl][i].Geo.Lng, + ) + } +} diff --git a/printer/printer.go b/printer/printer.go new file mode 100644 index 00000000..7ddfa89e --- /dev/null +++ b/printer/printer.go @@ -0,0 +1,116 @@ +package printer + +import ( + "fmt" + "strings" + + "github.com/nxtrace/NTrace-core/trace" + + "github.com/nxtrace/NTrace-core/ipgeo" +) + +// var dataOrigin string + +// func TraceroutePrinter(res *trace.Result) { +// for i, hop := range res.Hops { +// fmt.Print(i + 1) +// for _, h := range hop { +// HopPrinter(h) +// } +// } +// } + +//此文件目前仅供classic_printer使用 + +const ( + RED_PREFIX = "\033[1;31m" + GREEN_PREFIX = "\033[1;32m" + YELLOW_PREFIX = "\033[1;33m" + BLUE_PREFIX = "\033[1;34m" + CYAN_PREFIX = "\033[1;36m" + RESET_PREFIX = "\033[0m" +) + +func HopPrinter(h trace.Hop, info HopInfo) { + if h.Address == nil { + fmt.Println("\t*") + } else { + applyLangSetting(&h) // 应用语言设置 + txt := "\t" + + if h.Hostname == "" { + txt += fmt.Sprint(h.Address, " ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms") + } else { + txt += fmt.Sprint(h.Hostname, " (", h.Address, ") ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms") + } + + if h.Geo != nil { + txt += " " + formatIpGeoData(h.Address.String(), h.Geo) + } + for _, v := range h.MPLS { + txt += " " + v + } + switch info { + case IXP: + fmt.Print(CYAN_PREFIX) + case PoP: + fmt.Print(CYAN_PREFIX) + case Peer: + fmt.Print(YELLOW_PREFIX) + case Aboard: + fmt.Print(GREEN_PREFIX) + } + + fmt.Println(txt) + + if info != General { + fmt.Print(RESET_PREFIX) + } + } +} + +func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string { + var res = make([]string, 0, 10) + + if data.Asnumber == "" { + res = append(res, "*") + } else { + res = append(res, "AS"+data.Asnumber) + } + + // TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善 + // TODO: 移动IDC判断到Hop.fetchIPData函数,减少API调用 + //if strings.HasPrefix(ip, "9.") { + // res = append(res, "LAN Address") + //} else if strings.HasPrefix(ip, "11.") { + // res = append(res, "LAN Address") + //} else if data.Country == "" { + // res = append(res, "LAN Address") + if false { + } else { + // 有些IP的归属信息为空,这个时候将ISP的信息填入 + if data.Owner == "" { + data.Owner = data.Isp + } + if data.Prov == "" && data.City == "" { + // anyCast或是骨干网数据不应该有国家信息 + data.Owner = data.Owner + ", " + data.Owner + } else { + // 非骨干网正常填入IP的国家信息数据 + res = append(res, data.Country) + } + + if data.Prov != "" { + res = append(res, data.Prov) + } + if data.City != "" { + res = append(res, data.City) + } + + if data.Owner != "" { + res = append(res, data.Owner) + } + } + + return strings.Join(res, ", ") +} diff --git a/printer/printer_test.go b/printer/printer_test.go new file mode 100644 index 00000000..dfa7bc57 --- /dev/null +++ b/printer/printer_test.go @@ -0,0 +1,94 @@ +package printer + +// func TestPrintTraceRouteNav(t *testing.T) { +// PrintTraceRouteNav(util.DomainLookUp("1.1.1.1", false), "1.1.1.1", "dataOrigin") +// } + +// var testGeo = &ipgeo.IPGeoData{ +// Asnumber: "TestAsnumber", +// Country: "TestCountry", +// Prov: "TestProv", +// City: "TestCity", +// District: "TestDistrict", +// Owner: "TestOwner", +// Isp: "TestIsp", +// } + +// var testResult = &trace.Result{ +// Hops: [][]trace.Hop{ +// { +// { +// Success: true, +// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, +// Hostname: "test", +// TTL: 0, +// RTT: 10 * time.Millisecond, +// Error: nil, +// Geo: testGeo, +// }, +// { +// Success: true, +// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, +// Hostname: "test", +// TTL: 0, +// RTT: 10 * time.Millisecond, +// Error: nil, +// Geo: testGeo, +// }, +// }, +// { +// { +// Success: false, +// Address: nil, +// Hostname: "", +// TTL: 0, +// RTT: 0, +// Error: errors.New("test error"), +// Geo: nil, +// }, +// { +// Success: true, +// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, +// Hostname: "test", +// TTL: 0, +// RTT: 10 * time.Millisecond, +// Error: nil, +// Geo: nil, +// }, +// }, +// { +// { +// Success: true, +// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, +// Hostname: "test", +// TTL: 0, +// RTT: 0, +// Error: nil, +// Geo: &ipgeo.IPGeoData{}, +// }, +// { +// Success: true, +// Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, +// Hostname: "", +// TTL: 0, +// RTT: 10 * time.Millisecond, +// Error: nil, +// Geo: testGeo, +// }, +// }, +// }, +// } + +// // func TestTraceroutePrinter(t *testing.T) { +// // TraceroutePrinter(testResult) +// // } + +// func TestTracerouteTablePrinter(t *testing.T) { +// TracerouteTablePrinter(testResult) +// } + +// func TestRealtimePrinter(t *testing.T) { +// RealtimePrinter(testResult, 0) +// // RealtimePrinter(testResult, 1) +// // RealtimePrinter(testResult, 2) +// } diff --git a/printer/realtime_printer.go b/printer/realtime_printer.go new file mode 100644 index 00000000..f7bf1101 --- /dev/null +++ b/printer/realtime_printer.go @@ -0,0 +1,177 @@ +package printer + +import ( + "fmt" + "net" + "strconv" + "strings" + + "github.com/fatih/color" + "github.com/nxtrace/NTrace-core/trace" +) + +func RealtimePrinter(res *trace.Result, ttl int) { + fmt.Printf("%s ", color.New(color.FgHiYellow, color.Bold).Sprintf("%-2d", ttl+1)) + + // 去重 + var latestIP string + tmpMap := make(map[string][]string) + for i, v := range res.Hops[ttl] { + if v.Address == nil && latestIP != "" { + tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*")) + continue + } else if v.Address == nil { + continue + } + + if _, exist := tmpMap[v.Address.String()]; !exist { + tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i)) + // 首次进入 + if latestIP == "" { + for j := 0; j < i; j++ { + tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*")) + } + } + latestIP = v.Address.String() + } + + tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000)) + } + + if latestIP == "" { + fmt.Fprintf(color.Output, "%s\n", + color.New(color.FgWhite, color.Bold).Sprintf("*"), + ) + return + } + + var blockDisplay = false + for ip, v := range tmpMap { + if blockDisplay { + fmt.Printf("%4s", "") + } + if net.ParseIP(ip).To4() == nil { + fmt.Fprintf(color.Output, "%s", + color.New(color.FgWhite, color.Bold).Sprintf("%-25s", ip), + ) + } else { + fmt.Fprintf(color.Output, "%s", + color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip), + ) + } + + i, _ := strconv.Atoi(v[0]) + if res.Hops[ttl][i].Geo.Asnumber != "" { + /*** CMIN2, CUG, CN2, CUII, CTG 改为壕金色高亮 + /* 小孩子不懂事加着玩的 + /* 此处的高亮不代表任何线路质量 + /* 仅代表走了这部分的ASN + /* 如果使用这些ASN的IP同样会被高亮 + ***/ + switch { + case res.Hops[ttl][i].Geo.Asnumber == "58807": + fallthrough + case res.Hops[ttl][i].Geo.Asnumber == "10099": + fallthrough + case res.Hops[ttl][i].Geo.Asnumber == "4809": + fallthrough + case res.Hops[ttl][i].Geo.Asnumber == "9929": + fallthrough + case res.Hops[ttl][i].Geo.Asnumber == "23764": + fallthrough + case res.Hops[ttl][i].Geo.Whois == "CMIN2-NET": + fallthrough + case strings.HasPrefix(res.Hops[ttl][i].Address.String(), "59.43."): + fmt.Fprintf(color.Output, " %s", color.New(color.FgHiYellow, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber)) + default: + fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber)) + } + + } else { + fmt.Printf(" %-8s", "*") + } + + if net.ParseIP(ip).To4() != nil { + whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-") + if len(whoisFormat) > 1 { + whoisFormat[0] = strings.Join(whoisFormat[:2], "-") + } + + if whoisFormat[0] != "" { + //如果以RFC或DOD开头那么为空 + if !(strings.HasPrefix(whoisFormat[0], "RFC") || + strings.HasPrefix(whoisFormat[0], "DOD")) { + whoisFormat[0] = "[" + whoisFormat[0] + "]" + } else { + whoisFormat[0] = "" + } + } + + // CMIN2, CUII, CN2, CUG 改为壕金色高亮 + switch { + case res.Hops[ttl][i].Geo.Asnumber == "58807": + fallthrough + case res.Hops[ttl][i].Geo.Asnumber == "10099": + fallthrough + case res.Hops[ttl][i].Geo.Asnumber == "4809": + fallthrough + case res.Hops[ttl][i].Geo.Asnumber == "9929": + fallthrough + case res.Hops[ttl][i].Geo.Asnumber == "23764": + fallthrough + case whoisFormat[0] == "[CNC-BACKBONE]": + fallthrough + case whoisFormat[0] == "[CUG-BACKBONE]": + fallthrough + case whoisFormat[0] == "[CMIN2-NET]": + fallthrough + case strings.HasPrefix(res.Hops[ttl][i].Address.String(), "59.43."): + fmt.Fprintf(color.Output, " %s", color.New(color.FgHiYellow, color.Bold).Sprintf("%-16s", whoisFormat[0])) + default: + fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0])) + } + } + + applyLangSetting(&res.Hops[ttl][i]) // 应用语言设置 + + if net.ParseIP(ip).To4() != nil { + + fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ", + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country), + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov), + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City), + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District), + fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner), + color.New(color.FgHiBlack, color.Bold).Sprintf("%-39s", res.Hops[ttl][i].Hostname), + ) + } else { + fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ", + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country), + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov), + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City), + color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District), + fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner), + color.New(color.FgHiBlack, color.Bold).Sprintf("%-32s", res.Hops[ttl][i].Hostname), + ) + } + + for j := 1; j < len(v); j++ { + if len(v) == 2 || j == 1 { + fmt.Fprintf(color.Output, "%s", + color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]), + ) + } else { + fmt.Fprintf(color.Output, " / %s", + color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]), + ) + } + } + for _, v := range res.Hops[ttl][i].MPLS { + fmt.Fprintf(color.Output, "%s", + color.New(color.FgHiBlack, color.Bold).Sprintf("\n %s", v), + ) + } + fmt.Println() + blockDisplay = true + } +} diff --git a/printer/realtime_printer_router.go b/printer/realtime_printer_router.go new file mode 100644 index 00000000..6369cbdc --- /dev/null +++ b/printer/realtime_printer_router.go @@ -0,0 +1,152 @@ +package printer + +import ( + "fmt" + "net" + "strconv" + "strings" + + "github.com/fatih/color" + "github.com/nxtrace/NTrace-core/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), + ) + } + + } +} diff --git a/printer/tableprinter.go b/printer/tableprinter.go new file mode 100644 index 00000000..cd500574 --- /dev/null +++ b/printer/tableprinter.go @@ -0,0 +1,130 @@ +package printer + +import ( + "fmt" + "strings" + + "github.com/nxtrace/NTrace-core/ipgeo" + + "github.com/nxtrace/NTrace-core/trace" + + "github.com/fatih/color" + "github.com/rodaine/table" +) + +type rowData struct { + Hop string + IP string + Latency string + Asnumber string + Country string + Prov string + City string + District string + Owner string +} + +func TracerouteTablePrinter(res *trace.Result) { + // 初始化表格 + tbl := New() + for _, hop := range res.Hops { + for k, h := range hop { + data := tableDataGenerator(h) + if k > 0 { + data.Hop = "" + } + if data.Country == "" && data.Prov == "" && data.City == "" { + tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, "", data.Owner) + } else { + if data.City != "" { + tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.City+", "+data.Prov+", "+data.Country, data.Owner) + } else if data.Prov != "" { + tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Prov+", "+data.Country, data.Owner) + } else { + tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Owner) + } + + } + } + } + fmt.Print("\033[H\033[2J") + // 打印表格 + tbl.Print() +} + +func New() table.Table { + // 初始化表格 + headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() + columnFmt := color.New(color.FgYellow).SprintfFunc() + + tbl := table.New("Hop", "IP", "Lantency", "ASN", "Location", "Owner") + tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) + return tbl +} + +func tableDataGenerator(h trace.Hop) *rowData { + if h.Address == nil { + return &rowData{ + Hop: fmt.Sprint(h.TTL), + IP: "*", + } + } else { + lantency := fmt.Sprintf("%.2fms", h.RTT.Seconds()*1000) + IP := h.Address.String() + + if strings.HasPrefix(IP, "9.") { + return &rowData{ + Hop: fmt.Sprint(h.TTL), + IP: IP, + Latency: lantency, + Country: "LAN Address", + Prov: "", + Owner: "", + } + } else if strings.HasPrefix(IP, "11.") { + return &rowData{ + Hop: fmt.Sprint(h.TTL), + IP: IP, + Latency: lantency, + Country: "LAN Address", + Prov: "", + Owner: "", + } + } + + if h.Hostname != "" { + IP = fmt.Sprint(h.Hostname, " (", IP, ") ") + } + + if h.Geo == nil { + h.Geo = &ipgeo.IPGeoData{} + } + + r := &rowData{ + Hop: fmt.Sprint(h.TTL), + IP: IP, + Latency: lantency, + Asnumber: h.Geo.Asnumber, + Country: h.Geo.CountryEn, + Prov: h.Geo.ProvEn, + City: h.Geo.CityEn, + District: h.Geo.District, + Owner: h.Geo.Owner, + } + + if h.Geo == nil { + return r + } + + if h.Geo.Owner == "" { + h.Geo.Owner = h.Geo.Isp + } + r.Asnumber = h.Geo.Asnumber + r.Country = h.Geo.CountryEn + r.Prov = h.Geo.ProvEn + r.City = h.Geo.CityEn + r.District = h.Geo.District + r.Owner = h.Geo.Owner + return r + } +} diff --git a/reporter/reporter.go b/reporter/reporter.go new file mode 100644 index 00000000..1d084b66 --- /dev/null +++ b/reporter/reporter.go @@ -0,0 +1,183 @@ +package reporter + +import ( + "fmt" + "net" + "strings" + "sync" + + "github.com/nxtrace/NTrace-core/ipgeo" + "github.com/nxtrace/NTrace-core/trace" +) + +type Reporter interface { + Print() +} + +func New(rs *trace.Result, ip string) Reporter { + experimentTag() + r := reporter{ + routeResult: rs, + targetIP: ip, + } + return &r +} + +type reporter struct { + targetTTL uint16 + targetIP string + routeReport map[uint16][]routeReportNode + routeReportLock sync.Mutex + routeResult *trace.Result + wg sync.WaitGroup +} + +type routeReportNode struct { + asn string + isp string + geo []string + ix bool +} + +func experimentTag() { + fmt.Println("Route-Path 功能实验室") +} + +func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData, ttl uint16) { + + var success = true + + defer r.wg.Done() + + rpn := routeReportNode{} + ptr, err := net.LookupAddr(ip) + + if err == nil { + if strings.Contains(strings.ToLower(ptr[0]), "ix") { + rpn.ix = true + } else { + rpn.ix = false + } + } + // TODO: 这种写法不好,后面再重构一下 + // 判断反向解析的域名中又或者是IP地理位置数据库中,是否出现了 IX + if strings.Contains(strings.ToLower(ipGeoData.Isp), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Isp), "ix") || strings.Contains(strings.ToLower(ipGeoData.Owner), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Owner), "ix") { + rpn.ix = true + } + + // TODO: 正则判断POP并且提取带宽大小等信息 + + // CN2 需要特殊处理,因为他们很多没有ASN + // 但是目前这种写法是不规范的,属于凭空标记4809的IP + // TODO: 用更好的方式显示 CN2 骨干网的路由 Path + if strings.HasPrefix(ip, "59.43") { + rpn.asn = "4809" + } else { + rpn.asn = ipGeoData.Asnumber + } + + // 无论最后一跳是否为存在地理位置信息(AnyCast),都应该给予显示 + if (ipGeoData.Country == "" || ipGeoData.Country == "LAN Address" || ipGeoData.Country == "-") && ip != r.targetIP { + success = false + } else { + if ipGeoData.City == "" { + rpn.geo = []string{ipGeoData.Country, ipGeoData.Prov} + } else { + rpn.geo = []string{ipGeoData.Country, ipGeoData.City} + } + } + if ipGeoData.Asnumber == "" { + rpn.asn = "*" + } + + if ipGeoData.Isp == "" { + rpn.isp = ipGeoData.Owner + } else { + rpn.isp = ipGeoData.Isp + } + + // 有效记录 + if success { + // 锁住资源,防止同时写panic + r.routeReportLock.Lock() + // 添加到MAP中 + r.routeReport[ttl] = append(r.routeReport[ttl], rpn) + // 写入完成,解锁释放资源给其他协程 + r.routeReportLock.Unlock() + } +} + +func (r *reporter) InitialBaseData() Reporter { + reportNodes := map[uint16][]routeReportNode{} + + r.routeReport = reportNodes + r.targetTTL = uint16(len(r.routeResult.Hops)) + + for i := uint16(0); i < r.targetTTL; i++ { + traceHop := r.routeResult.Hops[i][0] + if traceHop.Success { + currentIP := traceHop.Address.String() + r.wg.Add(1) + go r.generateRouteReportNode(currentIP, *traceHop.Geo, i) + } + } + + // 等待所有的子协程运行完毕 + r.wg.Wait() + return r +} + +func (r *reporter) Print() { + var beforeActiveTTL uint16 = 0 + r.InitialBaseData() + // 尝试首个有效 TTL + for i := uint16(0); i < r.targetTTL; i++ { + if len(r.routeReport[i]) != 0 { + beforeActiveTTL = i + // 找到以后便不再循环 + break + } + } + + for i := beforeActiveTTL; i < r.targetTTL; i++ { + // 计算该TTL内的数据长度,如果为0,则代表没有有效数据 + if len(r.routeReport[i]) == 0 { + // 跳过改跃点的数据整理 + continue + } + nodeReport := r.routeReport[i][0] + + if i == beforeActiveTTL { + fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1]) + } else { + nodeReportBefore := r.routeReport[beforeActiveTTL][0] + // ASN 相同,同个 ISP 内部的数据传递 + if nodeReportBefore.asn == nodeReport.asn { + // Same ASN but Coutry or City Changed + if nodeReportBefore.geo[0] != nodeReport.geo[0] { + fmt.Printf("』→ %s『%s", nodeReport.geo[0], nodeReport.geo[1]) + } else { + if nodeReportBefore.geo[1] != nodeReport.geo[1] { + fmt.Printf(" → %s", nodeReport.geo[1]) + } + } + } else { + // ASN 不同,跨 ISP 的数据传递,这里可能会出现 POP、IP Transit、Peer、Exchange + fmt.Printf("』」") + if int(i) != len(r.routeReport)+1 { + // 部分 Shell 客户端可能无法很好的展示这个特殊字符 + // TODO: 寻找其他替代字符 + fmt.Printf("\n ╭╯\n ╰") + } + if nodeReport.ix { + fmt.Printf("AS%s \033[42;37mIXP\033[0m %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1]) + } else { + fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1]) + } + } + } + // 标记为最新的一个有效跃点 + beforeActiveTTL = i + } + fmt.Println("』」") +} diff --git a/reporter/reporter_test.go b/reporter/reporter_test.go new file mode 100644 index 00000000..28122972 --- /dev/null +++ b/reporter/reporter_test.go @@ -0,0 +1,115 @@ +package reporter + +import ( + "net" + "testing" + "time" + + "github.com/nxtrace/NTrace-core/ipgeo" + "github.com/nxtrace/NTrace-core/trace" +) + +var testResult = &trace.Result{ + Hops: [][]trace.Hop{ + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "4808", + Country: "中国", + Prov: "北京市", + City: "北京市", + District: "北京市", + Owner: "", + Isp: "中国联通", + }, + }, + }, + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("114.249.16.1")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "4808", + Country: "中国", + Prov: "北京市", + City: "北京市", + District: "北京市", + Owner: "", + Isp: "中国联通", + }, + }, + }, + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("219.158.5.150")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "4837", + Country: "中国", + Prov: "", + City: "", + District: "", + Owner: "", + Isp: "中国联通", + }, + }, + }, + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("62.115.125.160")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "1299", + Country: "Sweden", + Prov: "Stockholm County", + City: "Stockholm", + District: "", + Owner: "", + Isp: "Telia Company AB", + }, + }, + }, + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("213.226.68.73")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "56630", + Country: "Germany", + Prov: "Hesse, Frankfurt", + City: "", + District: "", + Owner: "", + Isp: "Melbikomas UAB", + }, + }, + }, + }, +} + +func TestPrint(t *testing.T) { + r := New(testResult, "213.226.68.73") + r.Print() +} diff --git a/core/icmp_ipv4.go b/trace/icmp_ipv4.go similarity index 78% rename from core/icmp_ipv4.go rename to trace/icmp_ipv4.go index f785be43..7e1b86e3 100644 --- a/core/icmp_ipv4.go +++ b/trace/icmp_ipv4.go @@ -1,6 +1,7 @@ -package core +package trace import ( + "bytes" "encoding/binary" "errors" "fmt" @@ -11,7 +12,6 @@ import ( "sync" "time" - "github.com/sjlleo/nexttrace-core/core/internal" "golang.org/x/net/context" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" @@ -27,21 +27,36 @@ type ICMPTracer struct { icmpListen net.PacketConn final int finalLock sync.Mutex - foundIPs map[string]bool + fetchLock sync.Mutex } -func (t *ICMPTracer) GetConfig() *Config { - return &t.Config -} +var psize = 52 + +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++ -func (t *ICMPTracer) SetConfig(c Config) { - t.Config = c + 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.foundIPs = make(map[string]bool) t.inflightRequest = make(map[int]chan Hop) t.inflightRequestRWLock.Unlock() @@ -51,7 +66,7 @@ func (t *ICMPTracer) Execute() (*Result, error) { var err error - t.icmpListen, err = internal.ListenICMP("ip4:1", t.SrcAddr) + t.icmpListen, err = net.ListenPacket("ip4:1", t.SrcAddr) if err != nil { return &t.res, err } @@ -63,10 +78,9 @@ func (t *ICMPTracer) Execute() (*Result, error) { t.final = -1 go t.listenICMP() + t.wg.Add(1) + go t.PrintFunc() for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ { - for _, plugin := range t.Plugins { - plugin.OnTTLChange(ttl) - } t.inflightRequestRWLock.Lock() t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements) t.inflightRequestRWLock.Unlock() @@ -76,29 +90,37 @@ func (t *ICMPTracer) Execute() (*Result, error) { for i := 0; i < t.NumMeasurements; i++ { t.wg.Add(1) go t.send(ttl) - <-time.After(t.Config.PacketInterval) + <-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval)) } - <-time.After(t.Config.TTLInterval) + <-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval)) } t.wg.Wait() t.res.reduce(t.final) if t.final != -1 { + if t.RealtimePrinter != nil { + t.RealtimePrinter(&t.res, t.final-1) + } } else { for i := 0; i < t.NumMeasurements; i++ { t.res.add(Hop{ + Success: false, Address: nil, TTL: 30, RTT: 0, Error: ErrHopLimitTimeout, }) } + if t.RealtimePrinter != nil { + t.RealtimePrinter(&t.res, t.MaxHops-1) + } } return &t.res, nil } func (t *ICMPTracer) listenICMP() { lc := NewPacketListener(t.icmpListen, t.ctx) + psize = t.Config.PktSize go lc.Start() for { select { @@ -156,9 +178,13 @@ func (t *ICMPTracer) listenICMP() { func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) { t.inflightRequestRWLock.RLock() defer t.inflightRequestRWLock.RUnlock() + + mpls := extractMPLS(msg, data) if _, ok := t.inflightRequest[ttl]; ok { t.inflightRequest[ttl] <- Hop{ + Success: true, Address: msg.Peer, + MPLS: mpls, } } } @@ -181,7 +207,7 @@ func gernerateID(ttl_int int) int { id += "0" } - res, _ := strconv.ParseInt(id, 2, 64) + res, _ := strconv.ParseInt(id, 2, 32) return int(res) } @@ -204,11 +230,17 @@ func reverseID(id string) (int64, int64, error) { } if parity%2 == 1 { - if id[len(id)-1] != '0' { + 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' { + if id[len(id)-1] == '1' { + // fmt.Println("Parity check passed.") + } else { + // fmt.Println("Parity check failed.") return 0, 0, errors.New("err") } } @@ -228,8 +260,9 @@ func (t *ICMPTracer) send(ttl int) error { icmpHeader := icmp.Message{ Type: ipv4.ICMPTypeEcho, Code: 0, Body: &icmp.Echo{ - ID: id, - Data: []byte("HELLO-R-U-THERE"), + ID: id, + //Data: []byte("HELLO-R-U-THERE"), + Data: append(bytes.Repeat([]byte{1}, t.Config.PktSize-4), 0x00, 0x00, 0x4f, 0xff), Seq: ttl, }, } @@ -253,16 +286,6 @@ func (t *ICMPTracer) send(ttl int) error { return nil case h := <-t.inflightRequest[ttl]: rtt := time.Since(start) - ipStr := h.Address.String() - if !t.foundIPs[ipStr] { - t.inflightRequestRWLock.Lock() - t.foundIPs[ipStr] = true - t.inflightRequestRWLock.Unlock() - // Trigger 所有插件的 OnIPFound 方法 - for _, plugin := range t.Plugins { - plugin.OnNewIPFound(h.Address) - } - } if t.final != -1 && ttl > t.final { return nil } @@ -284,6 +307,10 @@ func (t *ICMPTracer) send(ttl int) error { h.TTL = ttl h.RTT = rtt + t.fetchLock.Lock() + defer t.fetchLock.Unlock() + h.fetchIPData(t.Config) + t.res.add(h) case <-time.After(t.Timeout): if t.final != -1 && ttl > t.final { @@ -291,6 +318,7 @@ func (t *ICMPTracer) send(ttl int) error { } t.res.add(Hop{ + Success: false, Address: nil, TTL: ttl, RTT: 0, @@ -299,8 +327,5 @@ func (t *ICMPTracer) send(ttl int) error { } - for _, plugin := range t.Plugins { - plugin.OnTTLCompleted(len(t.res.Hops), t.res.Hops[len(t.res.Hops)-1]) - } return nil } diff --git a/core/icmp_ipv6.go b/trace/icmp_ipv6.go similarity index 76% rename from core/icmp_ipv6.go rename to trace/icmp_ipv6.go index dc8b09e0..82cac1e3 100644 --- a/core/icmp_ipv6.go +++ b/trace/icmp_ipv6.go @@ -1,6 +1,7 @@ -package core +package trace import ( + "bytes" "encoding/binary" "log" "net" @@ -9,7 +10,6 @@ import ( "sync" "time" - "github.com/sjlleo/nexttrace-core/core/internal" "golang.org/x/net/context" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" @@ -26,15 +26,32 @@ type ICMPTracerv6 struct { icmpListen net.PacketConn final int finalLock sync.Mutex - foundIPs map[string]bool + fetchLock sync.Mutex } -func (t *ICMPTracerv6) GetConfig() *Config { - return &t.Config -} +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 + } + } + } -func (t *ICMPTracerv6) SetConfig(c Config) { - t.Config = c + <-time.After(200 * time.Millisecond) + } } func (t *ICMPTracerv6) Execute() (*Result, error) { @@ -48,7 +65,7 @@ func (t *ICMPTracerv6) Execute() (*Result, error) { var err error - t.icmpListen, err = internal.ListenICMP("ip6:58", t.SrcAddr) + t.icmpListen, err = net.ListenPacket("ip6:58", t.SrcAddr) if err != nil { return &t.res, err } @@ -61,12 +78,9 @@ func (t *ICMPTracerv6) Execute() (*Result, error) { t.final = -1 go t.listenICMP() + go t.PrintFunc() for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ { - for _, plugin := range t.Plugins { - plugin.OnTTLChange(ttl) - } t.inflightRequestRWLock.Lock() - t.foundIPs = make(map[string]bool) t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements) t.inflightRequestRWLock.Unlock() if t.final != -1 && ttl > t.final { @@ -75,23 +89,47 @@ func (t *ICMPTracerv6) Execute() (*Result, error) { for i := 0; i < t.NumMeasurements; i++ { t.wg.Add(1) go t.send(ttl) - <-time.After(t.Config.PacketInterval) + <-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval)) } - <-time.After(t.Config.TTLInterval) + <-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.final != -1 { + if t.RealtimePrinter != nil { + t.RealtimePrinter(&t.res, t.final-1) + } + } else { for i := 0; i < t.NumMeasurements; i++ { t.res.add(Hop{ + Success: false, Address: nil, TTL: 30, RTT: 0, Error: ErrHopLimitTimeout, }) } + if t.RealtimePrinter != nil { + t.RealtimePrinter(&t.res, t.MaxHops-1) + } } return &t.res, nil @@ -99,6 +137,7 @@ func (t *ICMPTracerv6) Execute() (*Result, error) { func (t *ICMPTracerv6) listenICMP() { lc := NewPacketListener(t.icmpListen, t.ctx) + psize = t.Config.PktSize go lc.Start() for { select { @@ -195,9 +234,13 @@ func (t *ICMPTracerv6) listenICMP() { func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) { t.inflightRequestRWLock.RLock() defer t.inflightRequestRWLock.RUnlock() + + mpls := extractMPLS(msg, data) if _, ok := t.inflightRequest[ttl]; ok { t.inflightRequest[ttl] <- Hop{ + Success: true, Address: msg.Peer, + MPLS: mpls, } } } @@ -212,8 +255,9 @@ func (t *ICMPTracerv6) send(ttl int) error { icmpHeader := icmp.Message{ Type: ipv6.ICMPTypeEchoRequest, Code: 0, Body: &icmp.Echo{ - ID: id, - Data: []byte("HELLO-R-U-THERE"), + ID: id, + //Data: []byte("HELLO-R-U-THERE"), + Data: append(bytes.Repeat([]byte{1}, t.Config.PktSize-4), 0x00, 0x00, 0x4f, 0xff), Seq: ttl, }, } @@ -261,6 +305,10 @@ func (t *ICMPTracerv6) send(ttl int) error { h.TTL = ttl h.RTT = rtt + t.fetchLock.Lock() + defer t.fetchLock.Unlock() + h.fetchIPData(t.Config) + t.res.add(h) case <-time.After(t.Timeout): @@ -269,6 +317,7 @@ func (t *ICMPTracerv6) send(ttl int) error { } t.res.add(Hop{ + Success: false, Address: nil, TTL: ttl, RTT: 0, diff --git a/core/internal/icmp_darwin.go b/trace/internal/icmp_darwin.go similarity index 95% rename from core/internal/icmp_darwin.go rename to trace/internal/icmp_darwin.go index dd2fbe66..1ae6649e 100644 --- a/core/internal/icmp_darwin.go +++ b/trace/internal/icmp_darwin.go @@ -28,6 +28,7 @@ var ( } ) +// ListenICMP 会造成指定出口IP功能不可使用 func ListenICMP(network string, laddr string) (net.PacketConn, error) { if os.Getuid() == 0 { // root return net.ListenPacket(network, laddr) diff --git a/core/internal/icmp_general.go b/trace/internal/icmp_general.go similarity index 100% rename from core/internal/icmp_general.go rename to trace/internal/icmp_general.go diff --git a/core/packet_listener.go b/trace/packet_listener.go similarity index 98% rename from core/packet_listener.go rename to trace/packet_listener.go index 9fa49a7f..6bc9184e 100644 --- a/core/packet_listener.go +++ b/trace/packet_listener.go @@ -1,10 +1,9 @@ -package core +package trace import ( + "golang.org/x/net/context" "net" "time" - - "golang.org/x/net/context" ) type ReceivedMessage struct { diff --git a/core/quic.go b/trace/quic.go similarity index 52% rename from core/quic.go rename to trace/quic.go index 26c87fc6..3af8c50e 100644 --- a/core/quic.go +++ b/trace/quic.go @@ -1,11 +1,11 @@ -package core +package trace import ( "math/rand" "time" ) -// var Packet = []byte{202, 255, 255, 255, 255, 10, 253, 187, 139, 161, 69, 45, 65, 177, 68, 23, 4, 83, 226, 136, 32, 0, 68, 204, 168, 172, 81, 54, 106, 24, 100, 29, 160, 51, 70, 95, 107, 100, 4, 127, 168, 161, 43, 243, 194, 192, 4, 192, 99, 149, 103, 193, 233, 86, 254, 220, 167, 6, 45, 209, 193, 11, 77, 123, 131, 80, 17, 201, 248, 246, 246, 45, 153, 229, 169, 191, 76, 131, 162, 109, 188, 151, 22, 36, 1, 229, 201, 194, 26, 8, 63, 197, 207, 43, 190, 55, 224, 59, 247, 19, 142, 34, 186, 122, 108, 162, 110, 221, 15, 36, 224, 90, 73, 182, 177, 119, 26, 226, 16, 13, 18, 201, 96, 249, 192, 162, 13, 8, 132, 41, 63, 221, 43, 62, 143, 236, 219, 207, 10, 4, 115, 117, 214, 53, 64, 25, 180, 81, 144, 173, 155, 11, 32, 239, 253, 131, 115, 136, 227, 77, 132, 144, 48, 135, 137, 138, 214, 14, 67, 63, 187, 54, 197, 18, 191, 243, 128, 157, 74, 27, 225, 33, 12, 163, 0, 249, 126, 252, 242, 2, 33, 70, 255, 204, 200, 7, 4, 65, 154, 157, 83, 125, 197, 130, 53, 187, 254, 96, 54, 114, 93, 108, 198, 218, 198, 86, 33, 50, 74, 131, 154, 26, 206, 122, 148, 212, 177, 163, 99, 0, 138, 68, 235, 222, 55, 252, 232, 32, 103, 68, 119, 33, 188, 227, 71, 13, 123, 229, 99, 206, 245, 115, 21, 216, 31, 61, 30, 174, 97, 36, 96, 28, 66, 173, 89, 139, 37, 102, 81, 232, 158, 212, 8, 33, 123, 216, 191, 133, 200, 13, 197, 94, 223, 246, 29, 246, 19, 44, 135, 113, 59, 231, 36, 186, 53, 228, 33, 206, 85, 59, 105, 6, 225, 227, 111, 146, 211, 195, 73, 108, 200, 70, 73, 220, 163, 120, 47, 167, 139, 24, 149, 53, 11, 224, 22, 241, 52, 214, 38, 17, 137, 96, 134, 144, 16, 44, 116, 151, 15, 57, 87, 167, 137, 194, 63, 101, 195, 31, 21, 51, 7, 95, 101, 114, 195, 75, 211, 137, 149, 2, 80, 183, 82, 120, 185, 39, 192, 82, 102, 141, 153, 156, 118, 137, 99, 59, 4, 93, 74, 96, 164, 80, 142, 16, 197, 111, 178, 230, 250, 189, 26, 184, 19, 236, 249, 170, 68, 11, 41, 37, 243, 176, 208, 209, 208, 108, 74, 205, 55, 165, 30, 182, 170, 62, 184, 89, 109, 34, 135, 174, 237, 207, 52, 18, 32, 229, 131, 237, 204, 33, 77, 67, 242, 248, 72, 137, 32, 173, 226, 234, 146, 203, 35, 208, 156, 107, 190, 255, 29, 163, 167, 25, 187, 27, 26, 31, 227, 202, 135, 50, 248, 122, 68, 80, 218, 58, 142, 193, 71, 154, 27, 255, 109, 253, 216, 250, 219, 112, 89, 56, 23, 47, 98, 104, 163, 17, 152, 102, 16, 139, 157, 172, 39, 191, 129, 132, 29, 189, 68, 216, 138, 3, 163, 101, 165, 200, 61, 56, 197, 142, 226, 156, 172, 62, 251, 193, 100, 94, 96, 166, 96, 19, 116, 5, 237, 177, 39, 188, 32, 177, 9, 171, 16, 224, 209, 53, 217, 144, 245, 215, 217, 196, 55, 232, 16, 121, 217, 58, 44, 34, 213, 5, 184, 171, 81, 167, 10, 34, 5, 116, 239, 179, 206, 83, 181, 235, 35, 121, 196, 234, 191, 119, 189, 238, 103, 235, 147, 155, 47, 151, 29, 56, 35, 72, 251, 107, 121, 21, 188, 184, 183, 72, 94, 191, 230, 100, 214, 161, 143, 235, 10, 236, 25, 172, 110, 95, 99, 99, 55, 25, 217, 131, 113, 57, 124, 195, 165, 204, 242, 109, 156, 106, 194, 233, 42, 175, 241, 213, 164, 207, 131, 207, 74, 52, 207, 155, 210, 227, 136, 41, 251, 47, 81, 192, 96, 26, 133, 85, 24, 57, 75, 188, 214, 146, 17, 209, 229, 217, 151, 71, 166, 174, 232, 162, 69, 147, 116, 247, 172, 154, 110, 7, 170, 78, 185, 18, 43, 70, 23, 215, 105, 118, 125, 64, 222, 16, 149, 205, 159, 138, 194, 46, 99, 228, 115, 205, 30, 92, 223, 29, 72, 86, 57, 14, 209, 176, 244, 200, 26, 208, 198, 153, 82, 154, 233, 32, 55, 219, 161, 243, 150, 233, 70, 142, 96, 52, 254, 200, 133, 56, 246, 80, 153, 59, 74, 245, 93, 30, 241, 70, 192, 93, 32, 253, 254, 181, 9, 179, 152, 218, 173, 216, 175, 3, 69, 249, 82, 145, 41, 96, 162, 241, 83, 245, 162, 71, 128, 58, 116, 200, 236, 93, 207, 174, 173, 68, 184, 61, 172, 100, 39, 118, 250, 136, 66, 154, 165, 178, 153, 156, 116, 230, 10, 33, 237, 91, 132, 57, 11, 111, 141, 162, 242, 159, 124, 255, 142, 101, 205, 127, 214, 97, 17, 146, 130, 151, 37, 222, 140, 227, 152, 92, 217, 210, 64, 198, 97, 72, 11, 2, 182, 114, 101, 68, 246, 183, 55, 46, 227, 62, 163, 65, 123, 208, 223, 132, 66, 202, 82, 126, 31, 166, 92, 126, 105, 239, 255, 25, 34, 249, 205, 67, 217, 139, 129, 147, 178, 81, 251, 226, 194, 8, 69, 208, 67, 243, 210, 181, 121, 59, 22, 186, 120, 31, 27, 186, 54, 81, 145, 89, 53, 152, 72, 107, 125, 202, 124, 112, 134, 124, 226, 95, 3, 148, 225, 191, 107, 209, 139, 58, 200, 72, 213, 100, 164, 187, 164, 86, 61, 65, 45, 167, 253, 128, 253, 133, 52, 21, 31, 67, 178, 173, 68, 186, 82, 135, 50, 123, 122, 56, 178, 37, 233, 82, 83, 61, 229, 47, 195, 2, 169, 129, 154, 226, 71, 163, 211, 232, 156, 97, 67, 125, 104, 61, 0, 81, 253, 234, 225, 1, 164, 113, 33, 229, 24, 101, 238, 128, 227, 219, 40, 218, 221, 78, 213, 172, 12, 69, 179, 142, 97, 156, 138, 54, 50, 145, 14, 191, 120, 37, 128, 171, 75, 201, 79, 78, 144, 21, 163, 233, 54, 107, 234, 134, 11, 204, 233, 156, 77, 200, 9, 26, 32, 156, 132, 116, 81, 161, 218, 131, 110, 30, 175, 118, 48, 150, 146, 234, 195, 109, 228, 74, 43, 247, 114, 31, 139, 235, 214, 147, 206, 145, 170, 54, 83, 160, 48, 59, 65, 250, 192, 13, 87, 240, 234, 215, 209, 158, 10, 91, 208, 151, 177, 71, 96, 209, 184, 125, 82, 156, 34, 74, 15, 97, 73, 249, 185, 79, 34, 42, 157, 175, 52, 234, 131, 51, 144, 203, 6, 81, 245, 215, 107, 71, 68, 113, 82, 210, 116, 18, 88, 92, 141, 68, 227, 185, 55, 114, 54, 243, 152, 47, 117, 250, 87, 180, 30, 57, 187, 98, 90, 127, 243, 94, 122, 48, 23, 200, 40, 89, 89, 53, 83, 221, 56, 231, 117, 200, 201, 101, 159, 147, 25, 194, 236, 249, 5, 0, 70, 209, 122, 162, 103, 178, 217, 242, 36, 226, 16, 215, 144, 98, 198, 173, 134, 89, 115, 171, 81, 0, 112, 6, 152, 154, 119, 28, 36, 209, 174, 21, 93, 62, 33, 39, 57, 67, 71, 239, 11, 158, 53, 79, 103, 157, 100, 234, 28, 222, 212, 196, 216, 162, 49, 104, 49, 18, 175, 140, 255, 152, 175, 25, 233, 200, 60, 203, 225, 232, 121, 46, 220, 243, 13, 125, 255, 158, 2, 153, 203, 203, 223, 159, 65, 2, 219, 105, 235, 109, 126, 124, 53, 122} +//var Packet = []byte{202, 255, 255, 255, 255, 10, 253, 187, 139, 161, 69, 45, 65, 177, 68, 23, 4, 83, 226, 136, 32, 0, 68, 204, 168, 172, 81, 54, 106, 24, 100, 29, 160, 51, 70, 95, 107, 100, 4, 127, 168, 161, 43, 243, 194, 192, 4, 192, 99, 149, 103, 193, 233, 86, 254, 220, 167, 6, 45, 209, 193, 11, 77, 123, 131, 80, 17, 201, 248, 246, 246, 45, 153, 229, 169, 191, 76, 131, 162, 109, 188, 151, 22, 36, 1, 229, 201, 194, 26, 8, 63, 197, 207, 43, 190, 55, 224, 59, 247, 19, 142, 34, 186, 122, 108, 162, 110, 221, 15, 36, 224, 90, 73, 182, 177, 119, 26, 226, 16, 13, 18, 201, 96, 249, 192, 162, 13, 8, 132, 41, 63, 221, 43, 62, 143, 236, 219, 207, 10, 4, 115, 117, 214, 53, 64, 25, 180, 81, 144, 173, 155, 11, 32, 239, 253, 131, 115, 136, 227, 77, 132, 144, 48, 135, 137, 138, 214, 14, 67, 63, 187, 54, 197, 18, 191, 243, 128, 157, 74, 27, 225, 33, 12, 163, 0, 249, 126, 252, 242, 2, 33, 70, 255, 204, 200, 7, 4, 65, 154, 157, 83, 125, 197, 130, 53, 187, 254, 96, 54, 114, 93, 108, 198, 218, 198, 86, 33, 50, 74, 131, 154, 26, 206, 122, 148, 212, 177, 163, 99, 0, 138, 68, 235, 222, 55, 252, 232, 32, 103, 68, 119, 33, 188, 227, 71, 13, 123, 229, 99, 206, 245, 115, 21, 216, 31, 61, 30, 174, 97, 36, 96, 28, 66, 173, 89, 139, 37, 102, 81, 232, 158, 212, 8, 33, 123, 216, 191, 133, 200, 13, 197, 94, 223, 246, 29, 246, 19, 44, 135, 113, 59, 231, 36, 186, 53, 228, 33, 206, 85, 59, 105, 6, 225, 227, 111, 146, 211, 195, 73, 108, 200, 70, 73, 220, 163, 120, 47, 167, 139, 24, 149, 53, 11, 224, 22, 241, 52, 214, 38, 17, 137, 96, 134, 144, 16, 44, 116, 151, 15, 57, 87, 167, 137, 194, 63, 101, 195, 31, 21, 51, 7, 95, 101, 114, 195, 75, 211, 137, 149, 2, 80, 183, 82, 120, 185, 39, 192, 82, 102, 141, 153, 156, 118, 137, 99, 59, 4, 93, 74, 96, 164, 80, 142, 16, 197, 111, 178, 230, 250, 189, 26, 184, 19, 236, 249, 170, 68, 11, 41, 37, 243, 176, 208, 209, 208, 108, 74, 205, 55, 165, 30, 182, 170, 62, 184, 89, 109, 34, 135, 174, 237, 207, 52, 18, 32, 229, 131, 237, 204, 33, 77, 67, 242, 248, 72, 137, 32, 173, 226, 234, 146, 203, 35, 208, 156, 107, 190, 255, 29, 163, 167, 25, 187, 27, 26, 31, 227, 202, 135, 50, 248, 122, 68, 80, 218, 58, 142, 193, 71, 154, 27, 255, 109, 253, 216, 250, 219, 112, 89, 56, 23, 47, 98, 104, 163, 17, 152, 102, 16, 139, 157, 172, 39, 191, 129, 132, 29, 189, 68, 216, 138, 3, 163, 101, 165, 200, 61, 56, 197, 142, 226, 156, 172, 62, 251, 193, 100, 94, 96, 166, 96, 19, 116, 5, 237, 177, 39, 188, 32, 177, 9, 171, 16, 224, 209, 53, 217, 144, 245, 215, 217, 196, 55, 232, 16, 121, 217, 58, 44, 34, 213, 5, 184, 171, 81, 167, 10, 34, 5, 116, 239, 179, 206, 83, 181, 235, 35, 121, 196, 234, 191, 119, 189, 238, 103, 235, 147, 155, 47, 151, 29, 56, 35, 72, 251, 107, 121, 21, 188, 184, 183, 72, 94, 191, 230, 100, 214, 161, 143, 235, 10, 236, 25, 172, 110, 95, 99, 99, 55, 25, 217, 131, 113, 57, 124, 195, 165, 204, 242, 109, 156, 106, 194, 233, 42, 175, 241, 213, 164, 207, 131, 207, 74, 52, 207, 155, 210, 227, 136, 41, 251, 47, 81, 192, 96, 26, 133, 85, 24, 57, 75, 188, 214, 146, 17, 209, 229, 217, 151, 71, 166, 174, 232, 162, 69, 147, 116, 247, 172, 154, 110, 7, 170, 78, 185, 18, 43, 70, 23, 215, 105, 118, 125, 64, 222, 16, 149, 205, 159, 138, 194, 46, 99, 228, 115, 205, 30, 92, 223, 29, 72, 86, 57, 14, 209, 176, 244, 200, 26, 208, 198, 153, 82, 154, 233, 32, 55, 219, 161, 243, 150, 233, 70, 142, 96, 52, 254, 200, 133, 56, 246, 80, 153, 59, 74, 245, 93, 30, 241, 70, 192, 93, 32, 253, 254, 181, 9, 179, 152, 218, 173, 216, 175, 3, 69, 249, 82, 145, 41, 96, 162, 241, 83, 245, 162, 71, 128, 58, 116, 200, 236, 93, 207, 174, 173, 68, 184, 61, 172, 100, 39, 118, 250, 136, 66, 154, 165, 178, 153, 156, 116, 230, 10, 33, 237, 91, 132, 57, 11, 111, 141, 162, 242, 159, 124, 255, 142, 101, 205, 127, 214, 97, 17, 146, 130, 151, 37, 222, 140, 227, 152, 92, 217, 210, 64, 198, 97, 72, 11, 2, 182, 114, 101, 68, 246, 183, 55, 46, 227, 62, 163, 65, 123, 208, 223, 132, 66, 202, 82, 126, 31, 166, 92, 126, 105, 239, 255, 25, 34, 249, 205, 67, 217, 139, 129, 147, 178, 81, 251, 226, 194, 8, 69, 208, 67, 243, 210, 181, 121, 59, 22, 186, 120, 31, 27, 186, 54, 81, 145, 89, 53, 152, 72, 107, 125, 202, 124, 112, 134, 124, 226, 95, 3, 148, 225, 191, 107, 209, 139, 58, 200, 72, 213, 100, 164, 187, 164, 86, 61, 65, 45, 167, 253, 128, 253, 133, 52, 21, 31, 67, 178, 173, 68, 186, 82, 135, 50, 123, 122, 56, 178, 37, 233, 82, 83, 61, 229, 47, 195, 2, 169, 129, 154, 226, 71, 163, 211, 232, 156, 97, 67, 125, 104, 61, 0, 81, 253, 234, 225, 1, 164, 113, 33, 229, 24, 101, 238, 128, 227, 219, 40, 218, 221, 78, 213, 172, 12, 69, 179, 142, 97, 156, 138, 54, 50, 145, 14, 191, 120, 37, 128, 171, 75, 201, 79, 78, 144, 21, 163, 233, 54, 107, 234, 134, 11, 204, 233, 156, 77, 200, 9, 26, 32, 156, 132, 116, 81, 161, 218, 131, 110, 30, 175, 118, 48, 150, 146, 234, 195, 109, 228, 74, 43, 247, 114, 31, 139, 235, 214, 147, 206, 145, 170, 54, 83, 160, 48, 59, 65, 250, 192, 13, 87, 240, 234, 215, 209, 158, 10, 91, 208, 151, 177, 71, 96, 209, 184, 125, 82, 156, 34, 74, 15, 97, 73, 249, 185, 79, 34, 42, 157, 175, 52, 234, 131, 51, 144, 203, 6, 81, 245, 215, 107, 71, 68, 113, 82, 210, 116, 18, 88, 92, 141, 68, 227, 185, 55, 114, 54, 243, 152, 47, 117, 250, 87, 180, 30, 57, 187, 98, 90, 127, 243, 94, 122, 48, 23, 200, 40, 89, 89, 53, 83, 221, 56, 231, 117, 200, 201, 101, 159, 147, 25, 194, 236, 249, 5, 0, 70, 209, 122, 162, 103, 178, 217, 242, 36, 226, 16, 215, 144, 98, 198, 173, 134, 89, 115, 171, 81, 0, 112, 6, 152, 154, 119, 28, 36, 209, 174, 21, 93, 62, 33, 39, 57, 67, 71, 239, 11, 158, 53, 79, 103, 157, 100, 234, 28, 222, 212, 196, 216, 162, 49, 104, 49, 18, 175, 140, 255, 152, 175, 25, 233, 200, 60, 203, 225, 232, 121, 46, 220, 243, 13, 125, 255, 158, 2, 153, 203, 203, 223, 159, 65, 2, 219, 105, 235, 109, 126, 124, 53, 122} var beginHeader = []byte{202, 255, 255, 255, 255} //var endPacket = []byte{0, 68, 204, 168, 172, 81, 54, 106, 24, 100, 29, 160, 51, 70, 95, 107, 100, 4, 127, 168, 161, 43, 243, 194, 192, 4, 192, 99, 149, 103, 193, 233, 86, 254, 220, 167, 6, 45, 209, 193, 11, 77, 123, 131, 80, 17, 201, 248, 246, 246, 45, 153, 229, 169, 191, 76, 131, 162, 109, 188, 151, 22, 36, 1, 229, 201, 194, 26, 8, 63, 197, 207, 43, 190, 55, 224, 59, 247, 19, 142, 34, 186, 122, 108, 162, 110, 221, 15, 36, 224, 90, 73, 182, 177, 119, 26, 226, 16, 13, 18, 201, 96, 249, 192, 162, 13, 8, 132, 41, 63, 221, 43, 62, 143, 236, 219, 207, 10, 4, 115, 117, 214, 53, 64, 25, 180, 81, 144, 173, 155, 11, 32, 239, 253, 131, 115, 136, 227, 77, 132, 144, 48, 135, 137, 138, 214, 14, 67, 63, 187, 54, 197, 18, 191, 243, 128, 157, 74, 27, 225, 33, 12, 163, 0, 249, 126, 252, 242, 2, 33, 70, 255, 204, 200, 7, 4, 65, 154, 157, 83, 125, 197, 130, 53, 187, 254, 96, 54, 114, 93, 108, 198, 218, 198, 86, 33, 50, 74, 131, 154, 26, 206, 122, 148, 212, 177, 163, 99, 0, 138, 68, 235, 222, 55, 252, 232, 32, 103, 68, 119, 33, 188, 227, 71, 13, 123, 229, 99, 206, 245, 115, 21, 216, 31, 61, 30, 174, 97, 36, 96, 28, 66, 173, 89, 139, 37, 102, 81, 232, 158, 212, 8, 33, 123, 216, 191, 133, 200, 13, 197, 94, 223, 246, 29, 246, 19, 44, 135, 113, 59, 231, 36, 186, 53, 228, 33, 206, 85, 59, 105, 6, 225, 227, 111, 146, 211, 195, 73, 108, 200, 70, 73, 220, 163, 120, 47, 167, 139, 24, 149, 53, 11, 224, 22, 241, 52, 214, 38, 17, 137, 96, 134, 144, 16, 44, 116, 151, 15, 57, 87, 167, 137, 194, 63, 101, 195, 31, 21, 51, 7, 95, 101, 114, 195, 75, 211, 137, 149, 2, 80, 183, 82, 120, 185, 39, 192, 82, 102, 141, 153, 156, 118, 137, 99, 59, 4, 93, 74, 96, 164, 80, 142, 16, 197, 111, 178, 230, 250, 189, 26, 184, 19, 236, 249, 170, 68, 11, 41, 37, 243, 176, 208, 209, 208, 108, 74, 205, 55, 165, 30, 182, 170, 62, 184, 89, 109, 34, 135, 174, 237, 207, 52, 18, 32, 229, 131, 237, 204, 33, 77, 67, 242, 248, 72, 137, 32, 173, 226, 234, 146, 203, 35, 208, 156, 107, 190, 255, 29, 163, 167, 25, 187, 27, 26, 31, 227, 202, 135, 50, 248, 122, 68, 80, 218, 58, 142, 193, 71, 154, 27, 255, 109, 253, 216, 250, 219, 112, 89, 56, 23, 47, 98, 104, 163, 17, 152, 102, 16, 139, 157, 172, 39, 191, 129, 132, 29, 189, 68, 216, 138, 3, 163, 101, 165, 200, 61, 56, 197, 142, 226, 156, 172, 62, 251, 193, 100, 94, 96, 166, 96, 19, 116, 5, 237, 177, 39, 188, 32, 177, 9, 171, 16, 224, 209, 53, 217, 144, 245, 215, 217, 196, 55, 232, 16, 121, 217, 58, 44, 34, 213, 5, 184, 171, 81, 167, 10, 34, 5, 116, 239, 179, 206, 83, 181, 235, 35, 121, 196, 234, 191, 119, 189, 238, 103, 235, 147, 155, 47, 151, 29, 56, 35, 72, 251, 107, 121, 21, 188, 184, 183, 72, 94, 191, 230, 100, 214, 161, 143, 235, 10, 236, 25, 172, 110, 95, 99, 99, 55, 25, 217, 131, 113, 57, 124, 195, 165, 204, 242, 109, 156, 106, 194, 233, 42, 175, 241, 213, 164, 207, 131, 207, 74, 52, 207, 155, 210, 227, 136, 41, 251, 47, 81, 192, 96, 26, 133, 85, 24, 57, 75, 188, 214, 146, 17, 209, 229, 217, 151, 71, 166, 174, 232, 162, 69, 147, 116, 247, 172, 154, 110, 7, 170, 78, 185, 18, 43, 70, 23, 215, 105, 118, 125, 64, 222, 16, 149, 205, 159, 138, 194, 46, 99, 228, 115, 205, 30, 92, 223, 29, 72, 86, 57, 14, 209, 176, 244, 200, 26, 208, 198, 153, 82, 154, 233, 32, 55, 219, 161, 243, 150, 233, 70, 142, 96, 52, 254, 200, 133, 56, 246, 80, 153, 59, 74, 245, 93, 30, 241, 70, 192, 93, 32, 253, 254, 181, 9, 179, 152, 218, 173, 216, 175, 3, 69, 249, 82, 145, 41, 96, 162, 241, 83, 245, 162, 71, 128, 58, 116, 200, 236, 93, 207, 174, 173, 68, 184, 61, 172, 100, 39, 118, 250, 136, 66, 154, 165, 178, 153, 156, 116, 230, 10, 33, 237, 91, 132, 57, 11, 111, 141, 162, 242, 159, 124, 255, 142, 101, 205, 127, 214, 97, 17, 146, 130, 151, 37, 222, 140, 227, 152, 92, 217, 210, 64, 198, 97, 72, 11, 2, 182, 114, 101, 68, 246, 183, 55, 46, 227, 62, 163, 65, 123, 208, 223, 132, 66, 202, 82, 126, 31, 166, 92, 126, 105, 239, 255, 25, 34, 249, 205, 67, 217, 139, 129, 147, 178, 81, 251, 226, 194, 8, 69, 208, 67, 243, 210, 181, 121, 59, 22, 186, 120, 31, 27, 186, 54, 81, 145, 89, 53, 152, 72, 107, 125, 202, 124, 112, 134, 124, 226, 95, 3, 148, 225, 191, 107, 209, 139, 58, 200, 72, 213, 100, 164, 187, 164, 86, 61, 65, 45, 167, 253, 128, 253, 133, 52, 21, 31, 67, 178, 173, 68, 186, 82, 135, 50, 123, 122, 56, 178, 37, 233, 82, 83, 61, 229, 47, 195, 2, 169, 129, 154, 226, 71, 163, 211, 232, 156, 97, 67, 125, 104, 61, 0, 81, 253, 234, 225, 1, 164, 113, 33, 229, 24, 101, 238, 128, 227, 219, 40, 218, 221, 78, 213, 172, 12, 69, 179, 142, 97, 156, 138, 54, 50, 145, 14, 191, 120, 37, 128, 171, 75, 201, 79, 78, 144, 21, 163, 233, 54, 107, 234, 134, 11, 204, 233, 156, 77, 200, 9, 26, 32, 156, 132, 116, 81, 161, 218, 131, 110, 30, 175, 118, 48, 150, 146, 234, 195, 109, 228, 74, 43, 247, 114, 31, 139, 235, 214, 147, 206, 145, 170, 54, 83, 160, 48, 59, 65, 250, 192, 13, 87, 240, 234, 215, 209, 158, 10, 91, 208, 151, 177, 71, 96, 209, 184, 125, 82, 156, 34, 74, 15, 97, 73, 249, 185, 79, 34, 42, 157, 175, 52, 234, 131, 51, 144, 203, 6, 81, 245, 215, 107, 71, 68, 113, 82, 210, 116, 18, 88, 92, 141, 68, 227, 185, 55, 114, 54, 243, 152, 47, 117, 250, 87, 180, 30, 57, 187, 98, 90, 127, 243, 94, 122, 48, 23, 200, 40, 89, 89, 53, 83, 221, 56, 231, 117, 200, 201, 101, 159, 147, 25, 194, 236, 249, 5, 0, 70, 209, 122, 162, 103, 178, 217, 242, 36, 226, 16, 215, 144, 98, 198, 173, 134, 89, 115, 171, 81, 0, 112, 6, 152, 154, 119, 28, 36, 209, 174, 21, 93, 62, 33, 39, 57, 67, 71, 239, 11, 158, 53, 79, 103, 157, 100, 234, 28, 222, 212, 196, 216, 162, 49, 104, 49, 18, 175, 140, 255, 152, 175, 25, 233, 200, 60, 203, 225, 232, 121, 46, 220, 243, 13, 125, 255, 158, 2, 153, 203, 203, 223, 159, 65, 2, 219, 105, 235, 109, 126, 124, 53, 122} diff --git a/core/tcp_ipv4.go b/trace/tcp_ipv4.go similarity index 83% rename from core/tcp_ipv4.go rename to trace/tcp_ipv4.go index 5c098a49..1de6ebbd 100644 --- a/core/tcp_ipv4.go +++ b/trace/tcp_ipv4.go @@ -1,4 +1,4 @@ -package core +package trace import ( "log" @@ -10,7 +10,7 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" - "github.com/sjlleo/nexttrace-core/util" + "github.com/nxtrace/NTrace-core/util" "golang.org/x/net/context" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" @@ -31,15 +31,8 @@ type TCPTracer struct { final int finalLock sync.Mutex - sem *semaphore.Weighted -} - -func (t *TCPTracer) GetConfig() *Config { - return &t.Config -} - -func (t *TCPTracer) SetConfig(c Config) { - t.Config = c + sem *semaphore.Weighted + fetchLock sync.Mutex } func (t *TCPTracer) Execute() (*Result, error) { @@ -87,11 +80,30 @@ func (t *TCPTracer) Execute() (*Result, error) { for i := 0; i < t.NumMeasurements; i++ { t.wg.Add(1) go t.send(ttl) - <-time.After(t.Config.PacketInterval) + <-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval)) } - <-time.After(t.Config.TTLInterval) + if t.RealtimePrinter != nil { + // 对于实时模式,应该按照TTL进行并发请求 + t.wg.Wait() + t.RealtimePrinter(&t.res, ttl-1) + } + + <-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval)) } + go func() { + if t.AsyncPrinter != nil { + for { + t.AsyncPrinter(&t.res) + time.Sleep(200 * time.Millisecond) + } + } + + }() + // 如果是表格模式,则一次性并发请求 + if t.RealtimePrinter == nil { + t.wg.Wait() + } t.res.reduce(t.final) return &t.res, nil @@ -157,6 +169,7 @@ func (t *TCPTracer) listenTCP() { if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok { // 最后一跳 ch <- Hop{ + Success: true, Address: msg.Peer, } } @@ -179,6 +192,7 @@ func (t *TCPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) { return } ch <- Hop{ + Success: true, Address: msg.Peer, } @@ -220,6 +234,11 @@ func (t *TCPTracer) send(ttl int) error { ComputeChecksums: true, FixLengths: true, } + + desiredPayloadSize := t.Config.PktSize + payload := make([]byte, desiredPayloadSize) + copy(buf.Bytes(), payload) + if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil { return err } @@ -237,7 +256,15 @@ func (t *TCPTracer) send(ttl int) error { hopCh := make(chan Hop) t.inflightRequest[int(sequenceNumber)] = hopCh t.inflightRequestLock.Unlock() - + /* + // 这里属于 2个Sender,N个Reciever的情况,在哪里关闭Channel都容易导致Panic + defer func() { + t.inflightRequestLock.Lock() + close(hopCh) + delete(t.inflightRequest, srcPort) + t.inflightRequestLock.Unlock() + }() + */ select { case <-t.ctx.Done(): return nil @@ -264,6 +291,10 @@ func (t *TCPTracer) send(ttl int) error { h.TTL = ttl h.RTT = rtt + t.fetchLock.Lock() + defer t.fetchLock.Unlock() + h.fetchIPData(t.Config) + t.res.add(h) case <-time.After(t.Timeout): @@ -272,6 +303,7 @@ func (t *TCPTracer) send(ttl int) error { } t.res.add(Hop{ + Success: false, Address: nil, TTL: ttl, RTT: 0, diff --git a/core/tcp_ipv6.go b/trace/tcp_ipv6.go similarity index 85% rename from core/tcp_ipv6.go rename to trace/tcp_ipv6.go index d526ed40..75b6e0df 100644 --- a/core/tcp_ipv6.go +++ b/trace/tcp_ipv6.go @@ -1,4 +1,4 @@ -package core +package trace import ( "encoding/binary" @@ -10,7 +10,7 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" - "github.com/sjlleo/nexttrace-core/util" + "github.com/nxtrace/NTrace-core/util" "golang.org/x/net/context" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" @@ -31,15 +31,8 @@ type TCPTracerv6 struct { final int finalLock sync.Mutex - sem *semaphore.Weighted -} - -func (t *TCPTracerv6) GetConfig() *Config { - return &t.Config -} - -func (t *TCPTracerv6) SetConfig(c Config) { - t.Config = c + sem *semaphore.Weighted + fetchLock sync.Mutex } func (t *TCPTracerv6) Execute() (*Result, error) { @@ -48,12 +41,13 @@ func (t *TCPTracerv6) Execute() (*Result, error) { } t.SrcIP, _ = util.LocalIPPortv6(t.DestIP) + // log.Println(util.LocalIPPortv6(t.DestIP)) var err error t.tcp, err = net.ListenPacket("ip6:tcp", t.SrcIP.String()) if err != nil { return nil, err } - t.icmp, err = icmp.ListenPacket("ip6:58", t.SrcAddr) + t.icmp, err = icmp.ListenPacket("ip6:58", "::") if err != nil { return &t.res, err } @@ -78,11 +72,29 @@ func (t *TCPTracerv6) Execute() (*Result, error) { for i := 0; i < t.NumMeasurements; i++ { t.wg.Add(1) go t.send(ttl) - <-time.After(t.Config.PacketInterval) + <-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval)) } - <-time.After(t.Config.TTLInterval) + if t.RealtimePrinter != nil { + // 对于实时模式,应该按照TTL进行并发请求 + t.wg.Wait() + t.RealtimePrinter(&t.res, ttl-1) + } + <-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval)) } + go func() { + if t.AsyncPrinter != nil { + for { + t.AsyncPrinter(&t.res) + time.Sleep(200 * time.Millisecond) + } + } + + }() + + if t.RealtimePrinter == nil { + t.wg.Wait() + } t.res.reduce(t.final) return &t.res, nil @@ -149,6 +161,7 @@ func (t *TCPTracerv6) listenTCP() { if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok { // 最后一跳 ch <- Hop{ + Success: true, Address: msg.Peer, } } @@ -168,6 +181,7 @@ func (t *TCPTracerv6) handleICMPMessage(msg ReceivedMessage) { } // log.Println("发送数据", sequenceNumber) ch <- Hop{ + Success: true, Address: msg.Peer, } // log.Println("发送成功") @@ -210,6 +224,11 @@ func (t *TCPTracerv6) send(ttl int) error { ComputeChecksums: true, FixLengths: true, } + + desiredPayloadSize := t.Config.PktSize + payload := make([]byte, desiredPayloadSize) + copy(buf.Bytes(), payload) + if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil { return err } @@ -255,6 +274,10 @@ func (t *TCPTracerv6) send(ttl int) error { h.TTL = ttl h.RTT = rtt + t.fetchLock.Lock() + defer t.fetchLock.Unlock() + h.fetchIPData(t.Config) + t.res.add(h) case <-time.After(t.Timeout): @@ -263,6 +286,7 @@ func (t *TCPTracerv6) send(ttl int) error { } t.res.add(Hop{ + Success: false, Address: nil, TTL: ttl, RTT: 0, diff --git a/trace/temp_printer.go b/trace/temp_printer.go new file mode 100644 index 00000000..a88c5bea --- /dev/null +++ b/trace/temp_printer.go @@ -0,0 +1,76 @@ +package trace + +import ( + "fmt" + "strings" + + "github.com/nxtrace/NTrace-core/ipgeo" +) + +func HopPrinter(h Hop) { + if h.Address == nil { + fmt.Println("\t*") + } else { + txt := "\t" + + if h.Hostname == "" { + txt += fmt.Sprint(h.Address, " ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms") + } else { + txt += fmt.Sprint(h.Hostname, " (", h.Address, ") ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms") + } + + if h.Geo != nil { + txt += " " + formatIpGeoData(h.Address.String(), h.Geo) + } + + fmt.Println(txt) + } +} + +func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string { + var res = make([]string, 0, 10) + + if data.Asnumber == "" { + res = append(res, "*") + } else { + res = append(res, "AS"+data.Asnumber) + } + + // TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善 + // TODO: 移动IDC判断到Hop.fetchIPData函数,减少API调用 + if strings.HasPrefix(ip, "9.") { + res = append(res, "LAN Address", "") + } else if strings.HasPrefix(ip, "11.") { + res = append(res, "LAN Address", "") + } else if data.Country == "" { + res = append(res, "LAN Address") + } else { + // 有些IP的归属信息为空,这个时候将ISP的信息填入 + if data.Owner == "" { + data.Owner = data.Isp + } + if data.District != "" { + data.City = data.City + ", " + data.District + } + if data.Prov == "" && data.City == "" { + // anyCast或是骨干网数据不应该有国家信息 + data.Owner = data.Owner + ", " + data.Owner + } else { + // 非骨干网正常填入IP的国家信息数据 + res = append(res, data.Country) + } + + if data.Prov != "" { + res = append(res, data.Prov) + } + if data.City != "" { + res = append(res, data.City) + } + + if data.Owner != "" { + res = append(res, data.Owner) + } + } + + return strings.Join(res, ", ") +} diff --git a/trace/trace.go b/trace/trace.go new file mode 100644 index 00000000..82889871 --- /dev/null +++ b/trace/trace.go @@ -0,0 +1,336 @@ +package trace + +import ( + "errors" + "fmt" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/nxtrace/NTrace-core/ipgeo" + "github.com/nxtrace/NTrace-core/util" +) + +var ( + ErrInvalidMethod = errors.New("invalid method") + ErrTracerouteExecuted = errors.New("traceroute already executed") + ErrHopLimitTimeout = errors.New("hop timeout") + geoCache = sync.Map{} +) + +type Config struct { + SrcAddr string + BeginHop int + MaxHops int + NumMeasurements int + ParallelRequests int + Timeout time.Duration + DestIP net.IP + DestPort int + Quic bool + IPGeoSource ipgeo.Source + RDns bool + AlwaysWaitRDNS bool + PacketInterval int + TTLInterval int + Lang string + DN42 bool + RealtimePrinter func(res *Result, ttl int) + AsyncPrinter func(res *Result) + PktSize int + Maptrace bool +} + +type Method string + +const ( + ICMPTrace Method = "icmp" + UDPTrace Method = "udp" + TCPTrace Method = "tcp" +) + +type Tracer interface { + Execute() (*Result, error) +} + +func Traceroute(method Method, config Config) (*Result, error) { + var tracer Tracer + + if config.MaxHops == 0 { + config.MaxHops = 30 + } + if config.NumMeasurements == 0 { + config.NumMeasurements = 3 + } + if config.ParallelRequests == 0 { + config.ParallelRequests = config.NumMeasurements * 5 + } + + switch method { + case ICMPTrace: + if config.DestIP.To4() != nil { + tracer = &ICMPTracer{Config: config} + } else { + tracer = &ICMPTracerv6{Config: config} + } + + case UDPTrace: + if config.DestIP.To4() != nil { + tracer = &UDPTracer{Config: config} + } else { + return nil, errors.New("IPv6 UDP Traceroute is not supported") + } + case TCPTrace: + if config.DestIP.To4() != nil { + tracer = &TCPTracer{Config: config} + } else { + tracer = &TCPTracerv6{Config: config} + // return nil, errors.New("IPv6 TCP Traceroute is not supported") + } + default: + return &Result{}, ErrInvalidMethod + } + return tracer.Execute() +} + +type Result struct { + Hops [][]Hop + lock sync.Mutex + TraceMapUrl string +} + +func (s *Result) add(hop Hop) { + s.lock.Lock() + defer s.lock.Unlock() + k := hop.TTL - 1 + for len(s.Hops) < hop.TTL { + s.Hops = append(s.Hops, make([]Hop, 0)) + } + s.Hops[k] = append(s.Hops[k], hop) + +} + +func (s *Result) reduce(final int) { + if final > 0 && final < len(s.Hops) { + s.Hops = s.Hops[:final] + } +} + +type Hop struct { + Success bool + Address net.Addr + Hostname string + TTL int + RTT time.Duration + Error error + Geo *ipgeo.IPGeoData + Lang string + MPLS []string +} + +func (h *Hop) fetchIPData(c Config) (err error) { + + // DN42 + if c.DN42 { + var ip string + // 首先查找 PTR 记录 + r, err := util.LookupAddr(h.Address.String()) + if err == nil && len(r) > 0 { + h.Hostname = r[0][:len(r[0])-1] + ip = h.Address.String() + "," + h.Hostname + } + h.Geo, err = c.IPGeoSource(ip, c.Timeout, c.Lang, c.Maptrace) + return nil + } + + // Debug Area + // c.AlwaysWaitRDNS = true + + // Initialize a rDNS Channel + rDNSChan := make(chan []string) + fetchDoneChan := make(chan bool) + + if c.RDns && h.Hostname == "" { + // Create a rDNS Query go-routine + go func() { + r, err := util.LookupAddr(h.Address.String()) + if err != nil { + // No PTR Record + rDNSChan <- nil + } else { + // One PTR Record is found + rDNSChan <- r + } + }() + } + + // Create Data Fetcher go-routine + go func() { + // Start to fetch IP Geolocation data + if c.IPGeoSource != nil && h.Geo == nil { + res := false + h.Lang = c.Lang + h.Geo, res = ipgeo.Filter(h.Address.String()) + if !res { + timeout := c.Timeout + if c.Timeout < 2*time.Second { + timeout = 2 * time.Second + } + //h.Geo, err = c.IPGeoSource(h.Address.String(), timeout, c.Lang, c.Maptrace) + if cacheVal, ok := geoCache.Load(h.Address.String()); ok { + // 如果缓存中已有结果,直接使用 + h.Geo = cacheVal.(*ipgeo.IPGeoData) + } else { + // 如果缓存中无结果,进行查询并将结果存入缓存 + h.Geo, err = c.IPGeoSource(h.Address.String(), timeout, c.Lang, c.Maptrace) + if err == nil { + geoCache.Store(h.Address.String(), h.Geo) + } + } + } + } + // Fetch Done + fetchDoneChan <- true + }() + + // Select Close Flag + var selectClose bool + if !c.AlwaysWaitRDNS { + select { + case <-fetchDoneChan: + // When fetch done signal recieved, stop waiting PTR record + case ptr := <-rDNSChan: + // process result + if err == nil && len(ptr) > 0 { + h.Hostname = ptr[0][:len(ptr[0])-1] + } + selectClose = true + } + } else { + select { + case ptr := <-rDNSChan: + // process result + if err == nil && len(ptr) > 0 { + h.Hostname = ptr[0] + } + // 1 second timeout + case <-time.After(time.Second * 1): + } + selectClose = true + } + + // When Select Close, fetchDoneChan Reciever will also be closed + if selectClose { + // New a reciever to prevent channel congestion + <-fetchDoneChan + } + + return +} + +func extractMPLS(msg ReceivedMessage, data []byte) []string { + if util.DisableMPLS != "" { + return nil + } + + if psize != 52 { + return nil + } + + extensionOffset := 20 + 8 + psize + + if len(data) <= extensionOffset { + return nil + } + + extensionBody := data[extensionOffset:] + if len(extensionBody) < 8 || len(extensionBody)%8 != 0 { + return nil + } + + tmp := fmt.Sprintf("%x", msg.Msg[:*msg.N]) + + index := strings.Index(tmp, strings.Repeat("01", psize-4)+"00004fff") + if index == -1 { + return nil + } + tmp = tmp[index+psize*2:] + //由于限制长度了 + index1 := strings.Index(tmp, "00002000") + l := len(tmp[index1+4:])/8 - 2 + //fmt.Printf("l:%d\n", l) + + if l < 1 { + return nil + } + //去掉扩展头和MPLS头 + tmp = tmp[index1+4+8*2:] + //fmt.Print(tmp) + + var retStrList []string + for i := 0; i < l; i++ { + label, err := strconv.ParseInt(tmp[i*8+0:i*8+5], 16, 32) + if err != nil { + return nil + } + + strSlice := fmt.Sprintf("%s", []byte(tmp[i*8+5:i*8+6])) + //fmt.Printf("\nstrSlice: %s\n", strSlice) + + num, err := strconv.ParseUint(strSlice, 16, 64) + if err != nil { + return nil + } + binaryStr := fmt.Sprintf("%04s", strconv.FormatUint(num, 2)) + + //fmt.Printf("\nbinaryStr: %s\n", binaryStr) + tc, err := strconv.ParseInt(binaryStr[:3], 2, 32) + if err != nil { + return nil + } + s := binaryStr[3:] + + ttlMpls, err := strconv.ParseInt(tmp[i*8+6:i*8+8], 16, 32) + if err != nil { + return nil + } + + //if i > 0 { + // retStr += "\n " + //} + + retStrList = append(retStrList, fmt.Sprintf("[MPLS: Lbl %d, TC %d, S %s, TTL %d]", label, tc, s, ttlMpls)) + } + + //label, err := strconv.ParseInt(tmp[len(tmp)-8:len(tmp)-3], 16, 32) + //if err != nil { + // return "" + //} + // + //strSlice := fmt.Sprintf("%s", []byte(tmp[len(tmp)-3:len(tmp)-2])) + ////fmt.Printf("\nstrSlice: %s\n", strSlice) + // + //num, err := strconv.ParseUint(strSlice, 16, 64) + //if err != nil { + // return "" + //} + //binaryStr := fmt.Sprintf("%04s", strconv.FormatUint(num, 2)) + // + ////fmt.Printf("\nbinaryStr: %s\n", binaryStr) + //tc, err := strconv.ParseInt(binaryStr[:3], 2, 32) + //if err != nil { + // return "" + //} + //s := binaryStr[3:] + // + //ttlMpls, err := strconv.ParseInt(tmp[len(tmp)-2:], 16, 32) + //if err != nil { + // return "" + //} + // + //retStr := fmt.Sprintf("Lbl %d, TC %d, S %s, TTL %d", label, tc, s, ttlMpls) + + return retStrList +} diff --git a/core/udp_ipv4.go b/trace/udp.go similarity index 86% rename from core/udp_ipv4.go rename to trace/udp.go index 072a8036..ace186e7 100644 --- a/core/udp_ipv4.go +++ b/trace/udp.go @@ -1,4 +1,4 @@ -package core +package trace import ( "log" @@ -8,7 +8,7 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" - "github.com/sjlleo/nexttrace-core/util" + "github.com/nxtrace/NTrace-core/util" "golang.org/x/net/context" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" @@ -28,15 +28,8 @@ type UDPTracer struct { final int finalLock sync.Mutex - sem *semaphore.Weighted -} - -func (t *UDPTracer) GetConfig() *Config { - return &t.Config -} - -func (t *UDPTracer) SetConfig(c Config) { - t.Config = c + sem *semaphore.Weighted + fetchLock sync.Mutex } func (t *UDPTracer) Execute() (*Result, error) { @@ -70,8 +63,25 @@ func (t *UDPTracer) Execute() (*Result, error) { go t.send(ttl) <-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval)) } + if t.RealtimePrinter != nil { + // 对于实时模式,应该按照TTL进行并发请求 + t.wg.Wait() + t.RealtimePrinter(&t.res, ttl-1) + } <-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval)) } + go func() { + if t.AsyncPrinter != nil { + for { + t.AsyncPrinter(&t.res) + time.Sleep(200 * time.Millisecond) + } + } + }() + // 如果是表格模式,则一次性并发请求 + if t.AsyncPrinter != nil { + t.wg.Wait() + } t.res.reduce(t.final) return &t.res, nil @@ -119,6 +129,7 @@ func (t *UDPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) { return } ch <- Hop{ + Success: true, Address: msg.Peer, } } @@ -178,7 +189,12 @@ func (t *UDPTracer) send(ttl int) error { ComputeChecksums: true, FixLengths: true, } - if err := gopacket.SerializeLayers(buf, opts, udpHeader, gopacket.Payload("HAJSFJHKAJSHFKJHAJKFHKASHKFHHKAFKHFAHSJK")); err != nil { + + desiredPayloadSize := t.Config.PktSize + payload := make([]byte, desiredPayloadSize) + copy(buf.Bytes(), payload) + + if err := gopacket.SerializeLayers(buf, opts, udpHeader); err != nil { return err } @@ -215,6 +231,7 @@ func (t *UDPTracer) send(ttl int) error { return } hopCh <- Hop{ + Success: true, Address: peer, } }() @@ -245,6 +262,10 @@ func (t *UDPTracer) send(ttl int) error { h.TTL = ttl h.RTT = rtt + t.fetchLock.Lock() + defer t.fetchLock.Unlock() + h.fetchIPData(t.Config) + t.res.add(h) case <-time.After(t.Timeout): @@ -253,6 +274,7 @@ func (t *UDPTracer) send(ttl int) error { } t.res.add(Hop{ + Success: false, Address: nil, TTL: ttl, RTT: 0, diff --git a/tracelog/log.go b/tracelog/log.go new file mode 100644 index 00000000..e2f25691 --- /dev/null +++ b/tracelog/log.go @@ -0,0 +1,116 @@ +package tracelog + +import ( + "fmt" + "io" + "log" + "net" + "os" + "strconv" + "strings" + + "github.com/nxtrace/NTrace-core/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 + } +} diff --git a/tracemap/tracemap.go b/tracemap/tracemap.go new file mode 100644 index 00000000..94882933 --- /dev/null +++ b/tracemap/tracemap.go @@ -0,0 +1,74 @@ +package tracemap + +import ( + "crypto/tls" + "errors" + "fmt" + "github.com/fatih/color" + "github.com/nxtrace/NTrace-core/util" + "io" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +func GetMapUrl(r string) (string, error) { + host, port := util.GetHostAndPort() + fastIp := "api.leo.moe" + // 如果 host 是一个 IP 使用默认域名 + if valid := net.ParseIP(host); valid != nil { + host = "api.leo.moe" + } else { + // 默认配置完成,开始寻找最优 IP + fastIp = util.GetFastIP(host, port, false) + } + u := url.URL{Scheme: "https", Host: fastIp + ":" + port, Path: "/tracemap/api"} + tracemapUrl := u.String() + + client := &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + ServerName: host, + }, + }, + } + proxyUrl := util.GetProxy() + if proxyUrl != nil { + client.Transport.(*http.Transport).Proxy = http.ProxyURL(proxyUrl) + } + req, err := http.NewRequest("POST", tracemapUrl, strings.NewReader(r)) + if err != nil { + return "", errors.New("an issue occurred while connecting to the tracemap API") + } + req.Header.Add("User-Agent", util.UserAgent) + req.Host = host + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + return "", errors.New("an issue occurred while connecting to the tracemap API") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + return + } + }(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", errors.New("an issue occurred while connecting to the tracemap API") + } + return string(body), nil +} + +func PrintMapUrl(r string) { + _, err := fmt.Fprintf(color.Output, "%s %s\n", + color.New(color.FgWhite, color.Bold).Sprintf("%s", "MapTrace URL:"), + color.New(color.FgBlue, color.Bold).Sprintf("%s", r), + ) + if err != nil { + return + } +} diff --git a/util/dns_test.go b/util/dns_test.go index 9730b34a..568d599b 100644 --- a/util/dns_test.go +++ b/util/dns_test.go @@ -8,6 +8,13 @@ import ( func TestDNS(t *testing.T) { resolver := DNSSB() - ips, _ := resolver.LookupHost(context.Background(), "www.google.com") + ips, _ := resolver.LookupHost(context.Background(), "www.bing.com") + fmt.Println(ips) +} + +func TestDomainLookUp(t *testing.T) { + ips := DomainLookUp("pek-4134.nexttrace-io-fasttrace-endpoint.win.", "all", "", false) + fmt.Println(ips) + ips = DomainLookUp("pek-4134.nexttrace-io-fasttrace-endpoint.win.", "4", "", false) fmt.Println(ips) } diff --git a/util/latency.go b/util/latency.go new file mode 100644 index 00000000..4ac384dc --- /dev/null +++ b/util/latency.go @@ -0,0 +1,79 @@ +package util + +import ( + "fmt" + "log" + "net" + "strings" + "time" + + "github.com/fatih/color" +) + +var ( + result string + results = make(chan string) +) +var FastIpCache = "" + +func GetFastIP(domain string, port string, enableOutput bool) string { + proxyUrl := GetProxy() + if proxyUrl != nil { + return "api.leo.moe" + } + if FastIpCache != "" { + return FastIpCache + } + + ips, err := net.LookupIP(domain) + if err != nil { + log.Fatal("DNS resolution failed, please check your system DNS Settings") + } + + for _, ip := range ips { + go checkLatency(ip.String(), port) + } + + select { + case result = <-results: + case <-time.After(1 * time.Second): + } + if result == "" { + log.Fatal("IP connection has been timeout, please check your network") + } + res := strings.Split(result, "-") + + if len(ips) > 1 { + if enableOutput { + _, _ = fmt.Fprintf(color.Output, "%s prefered API IP - %s - %s\n", + color.New(color.FgWhite, color.Bold).Sprintf("[NextTrace API]"), + color.New(color.FgGreen, color.Bold).Sprintf("%s", res[0]), + color.New(color.FgCyan, color.Bold).Sprintf("%sms", res[1]), + ) + } + } + FastIpCache = res[0] + return res[0] +} + +func checkLatency(ip string, port string) { + start := time.Now() + if !strings.Contains(ip, ".") { + ip = "[" + ip + "]" + } + conn, err := net.DialTimeout("tcp", ip+":"+port, time.Second*1) + if err != nil { + return + } + defer func(conn net.Conn) { + err := conn.Close() + if err != nil { + return + } + }(conn) + if result == "" { + result = fmt.Sprintf("%s-%.2f", ip, float64(time.Since(start))/float64(time.Millisecond)) + results <- result + return + } +} diff --git a/util/latency_test.go b/util/latency_test.go new file mode 100644 index 00000000..4d5f85a6 --- /dev/null +++ b/util/latency_test.go @@ -0,0 +1,9 @@ +package util + +import ( + "testing" +) + +func TestGetFastIP(t *testing.T) { + GetFastIP("api.leo.moe", "443", true) +} diff --git a/util/util.go b/util/util.go index 1173859e..35e0908e 100644 --- a/util/util.go +++ b/util/util.go @@ -3,14 +3,44 @@ package util import ( "context" "fmt" + "github.com/nxtrace/NTrace-core/config" "log" "net" + "net/url" "os" + "runtime" + "strings" + "sync" "github.com/fatih/color" ) -// get the local ip and port based on our destination ip +var Uninterrupted = GetenvDefault("NEXTTRACE_UNINTERRUPTED", "") +var EnvToken = GetenvDefault("NEXTTRACE_TOKEN", "") +var UserAgent = fmt.Sprintf("NextTrace %s/%s/%s", config.Version, runtime.GOOS, runtime.GOARCH) +var RdnsCache sync.Map +var PowProviderParam = "" +var DisableMPLS = GetenvDefault("NEXTTRACE_DISABLEMPLS", "") + +func LookupAddr(addr string) ([]string, error) { + // 如果在缓存中找到,直接返回 + if hostname, ok := RdnsCache.Load(addr); ok { + //fmt.Println("hit RdnsCache for", addr, hostname) + return []string{hostname.(string)}, nil + } + // 如果缓存中未找到,进行 DNS 查询 + names, err := net.LookupAddr(addr) + if err != nil { + return nil, err + } + // 将查询结果存入缓存 + if len(names) > 0 { + RdnsCache.Store(addr, names[0]) + } + return names, nil +} + +// LocalIPPort get the local ip and port based on our destination ip func LocalIPPort(dstip net.IP) (net.IP, int) { serverAddr, err := net.ResolveUDPAddr("udp", dstip.String()+":12345") if err != nil { @@ -45,7 +75,8 @@ func LocalIPPortv6(dstip net.IP) (net.IP, int) { return nil, -1 } -func DomainLookUp(host string, ipv4Only bool, dotServer string) net.IP { +func DomainLookUp(host string, ipVersion string, dotServer string, disableOutput bool) net.IP { + // ipVersion: 4, 6, all var ( r *net.Resolver ips []net.IP @@ -65,8 +96,8 @@ func DomainLookUp(host string, ipv4Only bool, dotServer string) net.IP { default: r = newUDPResolver() } - ips_str, err := r.LookupHost(context.Background(), host) - for _, v := range ips_str { + ipsStr, err := r.LookupHost(context.Background(), host) + for _, v := range ipsStr { ips = append(ips, net.ParseIP(v)) } if err != nil { @@ -74,16 +105,29 @@ func DomainLookUp(host string, ipv4Only bool, dotServer string) net.IP { os.Exit(1) } - var ipv6Flag = false + //var ipv6Flag = false + //TODO: 此处代码暂无意义 + //if ipv6Flag { + // fmt.Println("[Info] IPv6 UDP Traceroute is not supported right now.") + // if len(ips) == 0 { + // os.Exit(0) + // } + //} - if ipv6Flag { - fmt.Println("[Info] IPv6 UDP Traceroute is not supported right now.") - if len(ips) == 0 { - os.Exit(0) + // Filter by IPv4/IPv6 + if ipVersion != "all" { + var filteredIPs []net.IP + for _, ip := range ips { + if ipVersion == "4" && ip.To4() != nil { + filteredIPs = append(filteredIPs, ip) + } else if ipVersion == "6" && strings.Contains(ip.String(), ":") { + filteredIPs = append(filteredIPs, ip) + } } + ips = filteredIPs } - if len(ips) == 1 { + if (len(ips) == 1) || (disableOutput) { return ips[0] } else { fmt.Println("Please Choose the IP You Want To TraceRoute") @@ -95,7 +139,10 @@ func DomainLookUp(host string, ipv4Only bool, dotServer string) net.IP { } var index int fmt.Printf("Your Option: ") - fmt.Scanln(&index) + _, err := fmt.Scanln(&index) + if err != nil { + index = 0 + } if index >= len(ips) || index < 0 { fmt.Println("Your Option is invalid") os.Exit(3) @@ -111,3 +158,65 @@ func GetenvDefault(key, defVal string) string { } return defVal } + +func GetHostAndPort() (host string, port string) { + var hostP = GetenvDefault("NEXTTRACE_HOSTPORT", "api.leo.moe") + // 解析域名 + hostArr := strings.Split(hostP, ":") + // 判断是否有指定端口 + if len(hostArr) > 1 { + // 判断是否为 IPv6 + if strings.HasPrefix(hostP, "[") { + tmp := strings.Split(hostP, "]") + host = tmp[0] + host = host[1:] + if port = tmp[1]; port != "" { + port = port[1:] + } + } else { + host, port = hostArr[0], hostArr[1] + } + } else { + host = hostP + } + if port == "" { + // 默认端口 + port = "443" + } + return +} + +func GetProxy() *url.URL { + proxyURLStr := GetenvDefault("NEXTTRACE_PROXY", "") + if proxyURLStr == "" { + return nil + } + proxyURL, err := url.Parse(proxyURLStr) + if err != nil { + log.Println("Failed to parse proxy URL:", err) + return nil + } + return proxyURL +} + +func GetPowProvider() string { + var powProvider = "" + if PowProviderParam == "" { + powProvider = GetenvDefault("NEXTTRACE_POWPROVIDER", "api.leo.moe") + } else { + powProvider = PowProviderParam + } + if powProvider == "sakura" { + return "pow.nexttrace.owo.13a.com" + } + return "" +} + +func StringInSlice(val string, list []string) bool { + for _, v := range list { + if v == val { + return true + } + } + return false +} diff --git a/wshandle/client.go b/wshandle/client.go new file mode 100644 index 00000000..de7dc582 --- /dev/null +++ b/wshandle/client.go @@ -0,0 +1,260 @@ +package wshandle + +import ( + "crypto/tls" + "github.com/nxtrace/NTrace-core/pow" + "github.com/nxtrace/NTrace-core/util" + "log" + "net" + "net/http" + "net/url" + "os" + "os/signal" + "sync" + "time" + + "github.com/gorilla/websocket" +) + +type WsConn struct { + Connecting bool + Connected bool // 连接状态 + MsgSendCh chan string // 消息发送通道 + MsgReceiveCh chan string // 消息接收通道 + Done chan struct{} // 发送结束通道 + Exit chan bool // 程序退出信号 + Interrupt chan os.Signal // 终端中止信号 + Conn *websocket.Conn // 主连接 + ConnMux sync.Mutex // 连接互斥锁 +} + +var wsconn *WsConn +var host, port, fastIp string +var envToken = util.EnvToken +var cacheToken string +var cacheTokenFailedTimes int + +func (c *WsConn) keepAlive() { + go func() { + // 开启一个定时器 + for { + <-time.After(time.Second * 54) + if c.Connected { + err := c.Conn.WriteMessage(websocket.TextMessage, []byte("ping")) + if err != nil { + log.Println(err) + c.Connected = false + return + } + } + } + }() + for { + if !c.Connected && !c.Connecting { + c.Connecting = true + c.recreateWsConn() + // log.Println("WebSocket 连接意外断开,正在尝试重连...") + // return + } + // 降低检测频率,优化 CPU 占用情况 + <-time.After(200 * time.Millisecond) + } +} + +func (c *WsConn) messageReceiveHandler() { + // defer close(c.Done) + for { + if c.Connected { + _, msg, err := c.Conn.ReadMessage() + if err != nil { + // 读取信息出错,连接已经意外断开 + // log.Println(err) + c.Connected = false + return + } + if string(msg) != "pong" { + c.MsgReceiveCh <- string(msg) + } + } + } +} + +func (c *WsConn) messageSendHandler() { + for { + // 循环监听发送 + select { + case <-c.Done: + log.Println("go-routine has been returned") + return + case t := <-c.MsgSendCh: + // log.Println(t) + if !c.Connected { + c.MsgReceiveCh <- `{"ip":"` + t + `", "asnumber":"API Server Error"}` + } else { + err := c.Conn.WriteMessage(websocket.TextMessage, []byte(t)) + if err != nil { + log.Println("write:", err) + return + } + } + // 来自终端的中断运行请求 + case <-c.Interrupt: + // 向 websocket 发起关闭连接任务 + err := c.Conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + // log.Println("write close:", err) + os.Exit(1) + } + select { + // 等到了结果,直接退出 + case <-c.Done: + // 如果等待 1s 还是拿不到结果,不再等待,超时退出 + case <-time.After(time.Second): + } + os.Exit(1) + // return + } + } +} + +func (c *WsConn) recreateWsConn() { + // 尝试重新连线 + u := url.URL{Scheme: "wss", Host: fastIp + ":" + port, Path: "/v3/ipGeoWs"} + // log.Printf("connecting to %s", u.String()) + jwtToken, ua := envToken, []string{"Privileged Client"} + err := error(nil) + if envToken == "" { + // 无环境变量 token + if cacheToken == "" { + // 无cacheToken, 重新获取 token + if util.GetPowProvider() == "" { + jwtToken, err = pow.GetToken(fastIp, host, port) + } else { + jwtToken, err = pow.GetToken(util.GetPowProvider(), util.GetPowProvider(), port) + } + if err != nil { + log.Println(err) + os.Exit(1) + } + } else { + // 使用 cacheToken + jwtToken = cacheToken + } + ua = []string{util.UserAgent} + } + cacheToken = jwtToken + requestHeader := http.Header{ + "Host": []string{host}, + "User-Agent": ua, + "Authorization": []string{"Bearer " + jwtToken}, + } + dialer := websocket.DefaultDialer + dialer.TLSClientConfig = &tls.Config{ + ServerName: host, + } + proxyUrl := util.GetProxy() + if proxyUrl != nil { + dialer.Proxy = http.ProxyURL(proxyUrl) + } + ws, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader) + c.Conn = ws + if err != nil { + log.Println("dial:", err) + // <-time.After(time.Second * 1) + c.Connected = false + c.Connecting = false + if cacheTokenFailedTimes > 3 { + cacheToken = "" + } + cacheTokenFailedTimes += 1 + //fmt.Println("重连失败", cacheTokenFailedTimes, "次") + return + } else { + c.Connected = true + } + c.Connecting = false + + c.Done = make(chan struct{}) + go c.messageReceiveHandler() +} + +func createWsConn() *WsConn { + proxyUrl := util.GetProxy() + //fmt.Println("正在连接 WS") + // 设置终端中断通道 + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + host, port = util.GetHostAndPort() + // 如果 host 是一个 IP 使用默认域名 + if valid := net.ParseIP(host); valid != nil { + host = "api.leo.moe" + } else { + // 默认配置完成,开始寻找最优 IP + fastIp = util.GetFastIP(host, port, true) + } + jwtToken, ua := envToken, []string{"Privileged Client"} + err := error(nil) + if envToken == "" { + if util.GetPowProvider() == "" { + jwtToken, err = pow.GetToken(fastIp, host, port) + } else { + jwtToken, err = pow.GetToken(util.GetPowProvider(), util.GetPowProvider(), port) + } + if err != nil { + log.Println(err) + os.Exit(1) + } + ua = []string{util.UserAgent} + } + cacheToken = jwtToken + cacheTokenFailedTimes = 0 + requestHeader := http.Header{ + "Host": []string{host}, + "User-Agent": ua, + "Authorization": []string{"Bearer " + jwtToken}, + } + dialer := websocket.DefaultDialer + dialer.TLSClientConfig = &tls.Config{ + ServerName: host, + } + if proxyUrl != nil { + dialer.Proxy = http.ProxyURL(proxyUrl) + } + u := url.URL{Scheme: "wss", Host: fastIp + ":" + port, Path: "/v3/ipGeoWs"} + // log.Printf("connecting to %s", u.String()) + + c, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader) + + wsconn = &WsConn{ + Conn: c, + Connected: true, + Connecting: false, + MsgSendCh: make(chan string, 10), + MsgReceiveCh: make(chan string, 10), + } + + if err != nil { + log.Println("dial:", err) + // <-time.After(time.Second * 1) + wsconn.Connected = false + wsconn.Done = make(chan struct{}) + go wsconn.keepAlive() + go wsconn.messageSendHandler() + return wsconn + } + // defer c.Close() + // 将连接写入WsConn,方便随时可取 + wsconn.Done = make(chan struct{}) + go wsconn.keepAlive() + go wsconn.messageReceiveHandler() + go wsconn.messageSendHandler() + return wsconn +} + +func New() *WsConn { + return createWsConn() +} + +func GetWsConn() *WsConn { + return wsconn +}