Compare commits

...

70 Commits

Author SHA1 Message Date
sjlleo
88e69f7dc2 feat: enable pure Go and add DoT support 2023-02-20 03:49:10 +00:00
sjlleo
b1a66546a0 Update CNAME 2023-02-17 12:48:40 +08:00
sjlleo
5c46e4eddb chore: 补充配置文件默认存放位置 2023-02-16 22:39:43 +08:00
sjlleo
30d550753c chore: DN42 配置文件教程 2023-02-16 22:17:08 +08:00
Leo
50b3e442da dump golang.org/x/net -> 0.7.0 2023-02-16 21:18:11 +08:00
Leo
ba133e3c73 feat: add DN42 Mode 2023-02-16 21:16:47 +08:00
Leo
3a5184b06c dump to Go 1.20 2023-02-16 21:16:25 +08:00
Leo
e825b8d092 fix: IPv6 no message in fast_trace 2023-02-15 18:46:00 +08:00
Leo
f64934bfde Merge branch 'main' of https://github.com/sjlleo/nexttrace 2023-02-15 18:42:36 +08:00
Leo
9871449b30 chore: IPv6 TCP Tracert Support for Fast Trace 2023-02-15 18:41:59 +08:00
tsosunchia
31a9c356e1 修正readme 2023-02-15 18:41:49 +08:00
Leo
5c0c490ae8 feat: add TCP IPv6 Support 2023-02-15 18:16:09 +08:00
tsosunchia
22660143b0 更新截图 2023-02-13 23:52:56 +08:00
tsosunchia
f4b5de08ca 更换traceMap截图 2023-02-13 23:35:56 +08:00
sjlleo
b0e1329d66 simplify build.yml 2023-02-13 09:19:48 +08:00
sjlleo
805e827e45 try add: mips64 mips64le support 2023-02-13 09:00:13 +08:00
tsosunchia
ed2250f811 fix: ipv6地址分割问题 2023-02-11 01:53:30 +08:00
tsosunchia
fe18c80f83 支持直接传入未经修剪的URL 2023-02-11 01:03:02 +08:00
tsosunchia
d1c9fd6c3a 小修改 2023-02-10 00:53:53 +08:00
tsosunchia
c67e2d6384 更新MapTrace在readme的指引 2023-02-06 23:44:31 +08:00
tsosunchia
309ae9d74a fix:macOS下报错异常 2023-02-06 09:22:27 +08:00
sjlleo
cf65ff5e7e chore: translation 2023-02-01 22:12:54 +08:00
sjlleo
eae92ebfae chore: update English doc 2023-02-01 22:10:39 +08:00
Leo
afe18fc4c0 feat: add English support 2023-02-01 21:34:33 +08:00
Leo
cc1d6177ca 重构关于地理位置以及 PTR 记录获取的模块,大幅提升路由跟踪性能 2023-02-01 18:22:02 +08:00
tsosunchia
c8077919ce 优化rdns体验,默认开启traceMap 2023-02-01 15:56:12 +08:00
tsosunchia
cfc79489d4 fix: fmt error 2023-02-01 12:45:06 +08:00
Leo
f60e6fbc99 feat: LeoMoeAPI 优选 IP 2023-01-31 11:58:55 +08:00
Leo
de0f49d01b fix: homebrew git fail 2023-01-30 10:19:37 +08:00
Leo
7ae47fdb6f fix: Fast Trace Bug && Performance Improvement 2023-01-30 09:59:10 +08:00
YekongTAT
471412b740 Merge pull request #70 from haima3/main
fix: Fixed invalid test IP for Shanghai CT (163)
2023-01-29 22:57:34 +08:00
Haima
8aca06fe24 fix: Fixed invalid test IP for Shanghai CT (163)
Minor fix: Fixed invalid Shanghai CT (163) test IPv4 IP
2023-01-29 22:55:52 +08:00
Leo
003db157a9 try fix: #67 fatal error: concurrent map read and map write 2023-01-29 21:32:33 +08:00
Leo
6e88706d62 Merge branch 'main' of https://github.com/sjlleo/nexttrace 2023-01-25 18:05:17 +08:00
Leo
0940014b09 修改默认发包间隔为100 2023-01-25 18:04:48 +08:00
sjlleo
5a7d04ab1e chore: 添加 IP 骨干网准度说明 2023-01-25 14:18:16 +08:00
tsosunchia
c6b7db8d59 解决brew包总落后release一个版本的问题
以后publishnewformula集成到build中,
publishnewformula保留,但只能手动启用
2023-01-25 12:33:06 +08:00
Leo
95f8cef54c small improvement 2023-01-24 14:47:02 +08:00
Leo
99ad5649a0 fix: filter switch-case condition 2023-01-24 08:51:53 +08:00
sjlleo
4bc4f1c176 Merge pull request #64 from tsosunchia/main
improve: 在本地对部分IP做了过滤
2023-01-24 05:37:59 +08:00
tsosunchia
b663e69343 improve: 在本地对部分IP做了过滤
IANA Reserved Address Space
2023-01-24 01:37:29 +08:00
Leo
7619e74152 feat: 添加 report 模式 2023-01-23 21:58:22 +08:00
Leo
c682f90856 feat: 小彩蛋 2023-01-23 21:25:19 +08:00
Leo
68c84488ae fix a bug caused table crash when using IPv6 2023-01-23 20:55:31 +08:00
sjlleo
deaebfa383 Merge pull request #61 from tsosunchia/patch-2
减少actions报错
2023-01-22 10:27:13 +08:00
tsosunchia
b467128c24 减少actions报错
在代码没有变化时,git commit会报错:
位于分支 main
您的分支与上游分支 'origin/main' 一致。

无文件要提交,干净的工作区
2023-01-22 09:22:43 +08:00
sjlleo
de93a2b3cb Merge pull request #60 from tsosunchia/patch-1
add feature: Read ENV to replace default endpoint
2023-01-22 04:45:07 +08:00
tsosunchia
66de7df351 add feature: Read ENV to replace default endpoint
ENV:  "NEXTTRACE_HOSTPORT"
default endpoint:  "api.leo.moe:443"
2023-01-22 00:49:33 +08:00
sjlleo
9685b80820 chore: 更新一些已经与旧版本有所不同的部分 2023-01-20 20:14:06 +08:00
sjlleo
8a3a9d5e38 chore: 调整一下模板 2023-01-20 20:06:32 +08:00
sjlleo
c826dd50ec chore: 调整一下排版 2023-01-20 19:53:25 +08:00
sjlleo
4664bc727a Update README_zh_CN.md 2023-01-20 12:23:14 +08:00
sjlleo
b66b7edc46 chore: add report 2023-01-20 10:59:30 +08:00
sjlleo
457f25035c chore: 为什么说 BestTrace 是普通人更好的选择?您何时应该选择 NextTrace? 2023-01-19 20:34:32 +08:00
sjlleo
8220bd7920 Merge pull request #59 from tsosunchia/patch-1
Update README
2023-01-19 11:37:26 +08:00
tsosunchia
372ac9d383 Update README
Update README.md, README_zh_CN.md
2023-01-19 10:30:08 +08:00
sjlleo
916a5dc9d0 fix: 不能正常显示版本号 2023-01-18 19:24:15 +08:00
Leo
b9b18f5efa feat: 增加了对发包的时间管理控制 2023-01-18 19:19:19 +08:00
Leo
326034b41e fix: panic on IPv6 2023-01-18 13:44:47 +08:00
Leo
09a5b42443 fix: 在 Table 模式下,无法自动退出的问题 2023-01-18 13:26:48 +08:00
Leo
b8542489b6 fix: 修复使用 -f 导致打印卡住的问题 2023-01-18 13:19:27 +08:00
Leo
a73a306d0a chore: sync document 2023-01-17 20:30:49 +08:00
Leo
801f42801a fix: Fast Trace 无法使用 2023-01-17 20:10:35 +08:00
Leo
08c4a5ceae fix: Fast Trace 无法使用 2023-01-17 20:09:53 +08:00
Leo
ce1c773996 improve: rDNS 解析等待时间过长 2023-01-17 20:08:57 +08:00
Leo
cdec6cbd8d feat: refactor flag 2023-01-17 19:08:32 +08:00
sjlleo
aa891ebd7c Typo 2023-01-15 15:07:29 +08:00
sjlleo
5d132e73ab chore: add announcement 2023-01-15 14:13:09 +08:00
Leo
63ca4d0418 fix: 一些包合法性判断上的错误 2023-01-14 20:22:07 +08:00
Leo
a6da078eb0 feat: 重构整个 ICMP 包校验模块,增加了对 ICMP 异步发包的支持 2023-01-14 18:15:23 +08:00
47 changed files with 2530 additions and 627 deletions

View File

@@ -5,7 +5,7 @@ set -e
DIST_PREFIX="nexttrace"
DEBUG_MODE=${2}
TARGET_DIR="dist"
PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips windows/amd64 windows/arm64 openbsd/amd64 openbsd/arm64 freebsd/amd64 freebsd/arm64"
PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips linux/mips64 linux/mipsle linux/mips64le windows/amd64 windows/arm64 openbsd/amd64 openbsd/arm64 freebsd/amd64 freebsd/arm64"
BUILD_VERSION="$(git describe --tags --always)"
BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"

View File

@@ -34,18 +34,43 @@ jobs:
with: # 将下述可执行文件 release 上去
draft: false # Release草稿
files: |
dist/nexttrace_darwin_amd64
dist/nexttrace_darwin_arm64
dist/nexttrace_linux_386
dist/nexttrace_linux_amd64
dist/nexttrace_linux_arm64
dist/nexttrace_linux_armv7
dist/nexttrace_linux_mips
dist/nexttrace_windows_amd64.exe
dist/nexttrace_windows_arm64.exe
dist/nexttrace_openbsd_amd64
dist/nexttrace_openbsd_arm64
dist/nexttrace_freebsd_amd64
dist/nexttrace_freebsd_arm64
dist/*
env:
GITHUB_TOKEN: ${{ secrets.GT_Token }}
GITHUB_TOKEN: ${{ secrets.GT_Token }}
publish-new-formula:
needs: build
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Runs a single command using the runners shell
- name: config git
run: |
git config --global user.email "${{ secrets.git_mail }}"
git config --global user.name "${{ secrets.git_name }}"
- name: Clone repo
run: |
git clone https://github.com/xgadget-lab/homebrew-nexttrace.git
- name: Exec scipt
run: |
cd homebrew-nexttrace
bash genFormula.sh
# - name: setup SSH keys and known_hosts
# run: |
# mkdir -p ~/.ssh
# ssh-keyscan github.com >> ~/.ssh/known_hosts
# ssh-agent -a $SSH_AUTH_SOCK > /dev/null
# ssh-add - <<< "${{ secrets.ID_RSA }}"
# env:
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
- name: Git Push
run: |
cd homebrew-nexttrace
git commit -am 'Publish a new version with Formula' || true
git remote set-url origin https://${{ secrets.gt_token }}@github.com/xgadget-lab/homebrew-nexttrace.git
git push
# env:
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
- run: echo "🍏 This job's status is ${{ job.status }}."

View File

@@ -3,7 +3,6 @@ name: Publish New Formula
# Controls when the action will run. Workflow runs when manually triggered using the UI
# or API.
on:
push:
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
@@ -22,7 +21,7 @@ jobs:
git config --global user.name "${{ secrets.git_name }}"
- name: Clone repo
run: |
git clone https://github.com/xgadget-lab/homebrew-nexttrace.git
git clone https://github.com/sjlleo/homebrew-nexttrace.git
- name: Exec scipt
run: |
cd homebrew-nexttrace
@@ -38,9 +37,9 @@ jobs:
- name: Git Push
run: |
cd homebrew-nexttrace
git commit -am 'Publish a new version with Formula'
git remote set-url origin https://${{ secrets.gt_token }}@github.com/xgadget-lab/homebrew-nexttrace.git
git push || 1
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 push
# env:
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
- run: echo "🍏 This job's status is ${{ job.status }}."

2
.gitignore vendored
View File

@@ -161,3 +161,5 @@ Network Trash Folder
Temporary Items
.apdisk
# compile target directory
dist/

2
CNAME
View File

@@ -1 +1 @@
trace.ac
mtr.moe

203
README.md
View File

@@ -8,39 +8,6 @@
An open source visual routing tool that pursues light weight, developed using Golang.
2022/12/18: Due to time and effort, it is becoming more and more difficult to maintain 2 branches at the same time, so I will be phasing out support for the NextTrace Enhanced version in the near future. I will resume updating the `Enhanced` version when I have more time.
## LeoMoeAPI Credit
NextTrace 重点在于研究 Go 语言 Traceroute 的实现,其 LeoMoeAPI 的地理位置信息并没有原始数据的支撑,故也不可能有商用版本。
LeoMoeAPI 存在部分社区贡献者校准的数据,也包含了部分其他第三方数据库的数据,这些数据的所有权归校准者、第三方数据库所有,**仅供路由跟踪地理位置的展示参考使用**,我们不对数据提供准度做任何保证,请尊重他们的成果,如用于其他用途后果自负,特此告知。
1. 对于辛勤提供马来西亚地区节点的 samleong123、全球节点的 TOHUNET Looking Glass 以及来自 Misaka 的 Ping.sx 表示感谢,目前 80% 以上的可靠校准数据出自这些节点的 ping / mtr 报告。
2. 同时感谢 isyekong 在基于 rDNS 校准上思路以及数据上做出的贡献LeoMoeAPI 正在加快对 rDNS 的解析功能研发,目前已经做到部分骨干网的地理位置自动化解析,但存在一定误判。
我们希望 NextTrace 在未来能成为对 One-Man ISP 友好的 Traceroute 工具,我们也在尽可能完善对这些 ASN 的微型骨干网的校准。
3. 在开发上,我要由衷感谢 missuo 以及 zhshch 在 Go 交叉编译、设计理念以及 TCP/UDP Traceroute 重构上的帮助、tsosunchia 在 TraceMap 上的倾力支持。
4. 我还要感谢 FFEE_CO、TheresaQWQ、stydxm 和其他朋友的帮助。LeoMoeAPI自首次发布以来得到了很多各方面的支持所以我想把他们都归功于此。
我们希望您能够在使用时尽可能多多反馈 IP 地理位置错误(详见 issue这样它就能够在第一时间得到校准他人也会因此而受益。
NextTrace focuses on Golang Traceroute implementations, and its LeoMoeAPI geolocation information is not supported by raw data, so a commercial version is not possible.
The LeoMoeAPI data is subject to copyright restrictions from multiple data sources, and is only used for the purpose of displaying the geolocation of route tracing.
1. We would like to credit samleong123 for providing nodes in Malaysia, TOHUNET Looking Glass for global nodes, and Ping.sx from Misaka, where more than 80% of reliable calibration data comes from ping/mtr reports.
2. At the same time, we would like to credit isyekong for their contribution on rDNS-based calibration ideas and data. LeoMoeAPI is accelerating the development of rDNS resolution function, and has already achieved automated geolocation resolution for some backbone networks, but there are some misjudgments. We hope that NextTrace will become a One-Man ISP-friendly traceroute tool in the future, and we are working on improving the calibration of these ASN micro-backbones as much as possible.
3. In terms of development, I would like to credit missuo and zhshch for their help with Go cross-compilation, design concepts and TCP/UDP Traceroute refactoring, and tsosunchia for their support on TraceMap.
4. I would also like to credit FFEE_CO, TheresaQWQ, stydxm and others for their help. leoMoeAPI has received a lot of support since its first release, so I would like to credit them all!
We hope you can give us as much feedback as possible on IP geolocation errors (see issue) so that it can be calibrated in the first place and others can benefit from it.
## How To Use
Document Language: English | [简体中文](README_zh_CN.md)
@@ -53,6 +20,9 @@ bash <(curl -Ls https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_insta
# macOS brew install command
brew tap xgadget-lab/nexttrace && brew install nexttrace
# GHProxy Mirror (For China Mainland User)
bash -c "$(curl -Ls https://ghproxy.com/https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)"
```
Windows users please go to [Release Page](https://github.com/sjlleo/nexttrace/releases/latest) directly and download exe file.
@@ -67,15 +37,17 @@ Windows users please go to [Release Page](https://github.com/sjlleo/nexttrace/re
```bash
# IPv4 ICMP Trace
nexttrace 1.0.0.1
# URL
nexttrace http://example.com:8080/index.html?q=1
# Form printing (output all hops at one time, wait 20-40 seconds)
nexttrace -table 1.0.0.1
nexttrace --table 1.0.0.1
# IPv6 ICMP Trace
nexttrace 2606:4700:4700::1111
# Path Visualization With the -M parameter, a map URL is returned
nexttrace -M koreacentral.blob.core.windows.net
# 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
```
@@ -89,52 +61,52 @@ The routing visualization function requires the geographical coordinates of each
```bash
# IPv4 ICMP Fast Test (Beijing + Shanghai + Guangzhou + Hangzhou) in China Telecom / Unicom / Mobile / Education Network
nexttrace -f
nexttrace --fast-trace
# You can also use TCP SYN for testing
nexttrace -f -T
nexttrace --fast-trace --tcp
```
`NextTrace` already supports route tracing for specified Network Devices
```bash
# Use eth0 network interface
nexttrace -D eth0 2606:4700:4700::1111
nexttrace --dev eth0 2606:4700:4700::1111
# Use eth0 network interface's IP
# When using the network interface's IP for route tracing, note that the IP type to be traced should be the same as network interface's IP type (e.g. both IPv4)
nexttrace -S 204.98.134.56 9.9.9.9
nexttrace --source 204.98.134.56 9.9.9.9
```
`NextTrace` can also use `TCP` and `UDP` protocols to perform `Traceroute` requests, but these protocols only supports `IPv4` now
`NextTrace` can also use `TCP` and `UDP` protocols to perform `Traceroute` requests, but `UDP` protocols only supports `IPv4` now
```bash
# TCP SYN Trace
nexttrace -T www.bing.com
nexttrace --tcp www.bing.com
# You can specify the port by yourself [here is 443], the default port is 80
nexttrace -T -p 443 1.0.0.1
nexttrace --tcp --port 443 2001:4860:4860::8888
# UDP Trace
nexttrace -U 1.0.0.1
nexttrace --udp 1.0.0.1
nexttrace -U -p 53 1.0.0.1
nexttrace --udp --port 53 1.0.0.1
```
`NextTrace` also supports some advanced functions, such as ttl control, concurrent probe packet count control, mode switching, etc.
```bash
# Send 2 probe packets per hop
nexttrace -q 2 www.hkix.net
nexttrace --queries 2 www.hkix.net
# No concurrent probe packets, only one probe packet is sent at a time
nexttrace -r 1 www.hkix.net
nexttrace --parallel-requests 1 www.hkix.net
# Start Trace with TTL of 5, end at TTL of 10
nexttrace -b 5 -m 10 www.decix.net
nexttrace --first 5 --max-hops 10 www.decix.net
# Turn off the IP reverse parsing function
nexttrace -n www.bbix.net
nexttrace --no-rdns www.bbix.net
# Feature: print Route-Path diagram
# Route-Path diagram example:
@@ -145,26 +117,30 @@ nexttrace -n www.bbix.net
# ╰AS36776 Five9 Inc.「Philippines『Metro Manila』」
# ╭╯
# ╰AS37963 Aliyun「ALIDNS.COM『ALIDNS.COM』」
nexttrace -report www.time.com.my
nexttrace --route-path www.time.com.my
```
`NextTrace` supports users to select their own IP API (currently supports: `LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`)
```bash
# You can specify the IP database by yourself [IP.SB here], if not specified, LeoMoeAPI will be used
nexttrace -d IP.SB
nexttrace --data-provider IP.SB
## Note that the ipinfo API needs users to purchase services from ipinfo. If necessary, you can clone this project, add the token provided by ipinfo and compile it yourself
## Fill the token to: ipgeo/tokens.go
## Please be aware: Due to the serious abuse of IP.SB, you will often be not able to query IP data from this source
## IPAPI.com has a stricter restiction on API calls, if you can't query IP data from this source, please try again in a few minutes.
```
`NextTrace` supports mixed parameters
`NextTrace` supports mixed parameters and shortened parameters
```bash
Example:
nexttrace -d IPInsight -m 20 -p 443 -q 5 -r 20 -rdns 1.1.1.1
nexttrace -T -q 2 -r 1 -table -report 2001:4860:4860::8888
nexttrace --data-provider IPAPI.com --max-hops 20 --tcp --port 443 --queries 5 --no-rdns 1.1.1.1
nexttrace -tcp --queries 2 --parallel-requests 1 --table --route-path 2001:4860:4860::8888
Equivalent to:
nexttrace -d IPAPI.com -m 20 -T -p 443 -q 5 -n 1.1.1.1
nexttrace -T -q 2 --parallel-requests 1 -t -R 2001:4860:4860::8888
```
### IP Database
@@ -180,57 +156,106 @@ All NextTrace IP geolocation `API DEMO` can refer to [here](https://github.com/x
### For full usage list, please refer to the usage menu
```shell
Usage of nexttrace:
'nexttrace [options] <hostname>' or 'nexttrace <hostname> [option...]'
Options:
-T Use TCP SYN for tracerouting (default port is 80)
-U Use UDP Package for tracerouting (default port is 53 in UDP)
-V Print Version
-b int
Set The Begin TTL (default 1)
-d string
Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight, IPAPI.com] (default "LeoMoeAPI")
-f One-Key Fast Traceroute
-m int
Set the max number of hops (max TTL to be reached). (default 30)
-n Disable IP Reverse DNS lookup
-p int
Set SYN Traceroute Port (default 80)
-q int
Set the number of probes per each hop. (default 3)
-r int
Set ParallelRequests number. It should be 1 when there is a multi-routing. (default 18)
-report
Route Path
-table
Output trace results as table
usage: nexttrace [-h|--help] [-T|--tcp] [-U|--udp] [-F|--fast-trace] [-p|--port
<integer>] [-q|--queries <integer>] [--parallel-requests
<integer>] [-m|--max-hops <integer>] [-d|--data-provider
(IP.SB|IPInfo|IPInsight|IPAPI.com)] [-n|--no-rdns]
[-a|--always-rdns] [-P|--route-path] [-r|--report]
[-o|--output] [-t|--table] [-c|--classic] [-f|--first
<integer>] [-M|--map] [-v|--version] [-s|--source "<value>"]
[-D|--dev "<value>"] [-R|--route] [-z|--send-time <integer>]
[-i|--ttl-time <integer>] [-g|--language (en|cn)] [IP Address
or Domain]
An open source visual route tracking CLI tool
Arguments:
-h --help Print help information
-T --tcp Use TCP SYN for tracerouting (default port
is 80)
-U --udp Use UDP SYN for tracerouting (default port
is 53)
-F --fast-trace One-Key Fast Trace to China ISPs
-p --port Set the destination port to use. It is
either initial udp port value for
"default"method (incremented by each
probe, default is 33434), or initial seq
for "icmp" (incremented as well, default
from 1), or some constantdestination port
for other methods (with default of 80 for
"tcp", 53 for "udp", etc.)
-q --queries Set the number of probes per each hop.
Default: 3
--parallel-requests Set ParallelRequests number. It should be
1 when there is a multi-routing. Default:
18
-m --max-hops Set the max number of hops (max TTL to be
reached). Default: 30
-d --data-provider Choose IP Geograph Data Provider
[LeoMoeAPI,IP.SB, IPInfo, IPInsight,
IPAPI.com]. Default: LeoMoeAPI
-n --no-rdns Do not resolve IP addresses to their
domain names
-a --always-rdns Always resolve IP addresses to their
domain names
-P --route-path Print traceroute hop path by ASN and
location
-r --report output using report mode
-o --output Write trace result to file
(RealTimePrinter ONLY)
-t --table Output trace results as table
-c --classic Classic Output trace results like
BestTrace
-f --first Start from the first_ttl hop (instead from
1). Default: 1
-M --map Disable Print Trace Map Function
-v --version Print version info and exit
-s --source Use source src_addr for outgoing packets
-D --dev Use the following Network Devices as the
source address in outgoing packets
-R --route Show Routing Table [Provided By BGP.Tools]
-z --send-time Set the time interval for sending every
packet. Useful when some routers use
rate-limit for ICMP messages. Default: 100
-i --ttl-time Set the time interval for sending packets
groups by TTL. Useful when some routers
use rate-limit for ICMP messages. Default:
500
-g --language Choose the language for displaying [en,
cn]. Default: cn
```
## Project screenshot
![image](https://user-images.githubusercontent.com/13616352/208289553-7f633f9c-7356-40d1-bbc4-cc2687419cca.png)
![image](https://user-images.githubusercontent.com/13616352/208289568-2a135c2d-ae4a-4a3e-8a43-f5a9a87ade4a.png)
![image](https://user-images.githubusercontent.com/13616352/216064486-5e0a4ad5-01d6-4b3c-85e9-2e6d2519dc5d.png)
![image](https://user-images.githubusercontent.com/59512455/218501311-1ceb9b79-79e6-4eb6-988a-9d38f626cdb8.png)
## NextTrace Enhanced
`NextTrace Enhanced` is an enhanced version for enthusiasts, `Enhanced` provides trace route calls in the form of Web API and a simple Looking Glass webpage with built-in visualization.
The `Enhanced` version supports many functions that the `lite` version does not have, such as the ability to customize the timeout period, and the ability to specify TTL as the starting point for route tracking, etc. For ordinary users, the `lite` version is usually enough.
Please Notice that `NextTrace Enhanced` is currently not supported in English.
https://github.com/OwO-Network/nexttrace-enhanced
#
## LeoMoeAPI Credit
## FAQ Frequently Asked Questions
NextTrace focuses on Golang Traceroute implementations, and its LeoMoeAPI geolocation information is not supported by raw data, so a commercial version is not possible.
If you encounter problems while installing or using it, we do not recommend you to choose creating an `issue` as a preference
The LeoMoeAPI data is subject to copyright restrictions from multiple data sources, and is only used for the purpose of displaying the geolocation of route tracing.
Here is our recommended troubleshooting process:
1. We would like to credit samleong123 for providing nodes in Malaysia, TOHUNET Looking Glass for global nodes, and Ping.sx from Misaka, where more than 80% of reliable calibration data comes from ping/mtr reports.
2. At the same time, we would like to credit isyekong for their contribution on rDNS-based calibration ideas and data. LeoMoeAPI is accelerating the development of rDNS resolution function, and has already achieved automated geolocation resolution for some backbone networks, but there are some misjudgments. We hope that NextTrace will become a One-Man ISP-friendly traceroute tool in the future, and we are working on improving the calibration of these ASN micro-backbones as much as possible.
3. In terms of development, I would like to credit missuo and zhshch for their help with Go cross-compilation, design concepts and TCP/UDP Traceroute refactoring, and tsosunchia for their support on TraceMap.
4. I would also like to credit FFEE_CO, TheresaQWQ, stydxm and others for their help. leoMoeAPI has received a lot of support since its first release, so I would like to credit them all!
We hope you can give us as much feedback as possible on IP geolocation errors (see issue) so that it can be calibrated in the first place and others can benefit from it.
1. Check if it is already in FAQ -> [Go to Github Wiki](https://github.com/xgadget-lab/nexttrace/wiki/FAQ---%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94)
2. Suspected bug or feature suggestion -> [Go to Github Issues](https://github.com/xgadget-lab/nexttrace/issues)
## JetBrain Support
@@ -258,4 +283,4 @@ Although other third-party APIs are integrated in this project, please refer to
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=xgadget-lab/nexttrace&type=Date)](https://star-history.com/#xgadget-lab/nexttrace&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=sjlleo/nexttrace&type=Date)](https://star-history.com/#sjlleo/nexttrace&Date)

View File

@@ -4,16 +4,32 @@
</div>
## NextTrace
<h1 align="center">
<br>NextTrace<br>
</h1>
一款追求轻量的开源可视化路由跟踪工具,使用 Golang 开发。
如果您对 NextTrace 项目本身感兴趣,可以阅读 [有关 NextTrace 的一些碎碎念](https://leo.moe/annoucement/nexttrace.html) 或许可以帮您解决疑惑。
<h4 align="center">一款追求轻量化的开源可视化路由跟踪工具。</h4>
<p align="center">
<a href="https://github.com/sjlleo/nexttrace/actions">
<img src="https://img.shields.io/github/actions/workflow/status/sjlleo/nexttrace/build.yml?branch=main&style=flat-square" alt="Github Actions">
</a>
<a href="https://goreportcard.com/report/github.com/sjlleo/nexttrace">
<img src="https://goreportcard.com/badge/github.com/sjlleo/nexttrace?style=flat-square">
</a>
<a href="https://github.com/sjlleo/nexttrace/releases">
<img src="https://img.shields.io/github/release/sjlleo/nexttrace/all.svg?style=flat-square">
</a>
</p>
2022/12/18: 由于时间精力的关系同时维护2个分支变得愈发困难近期我将逐步停止 NextTrace Enhanced 版本的支持,如有觉得 NextTrace Enhanced 一些不错的功能,可以在 issue 里面提出来。待我重新有富裕时间时重新恢复对 `Enhanced` 版本的更新。
## How To Use
### Before Using
使用 NextTrace 之前,我们建议您先阅读 [#IP 数据以及精准度说明](https://github.com/sjlleo/nexttrace/blob/main/README_zh_CN.md#ip-%E6%95%B0%E6%8D%AE%E4%BB%A5%E5%8F%8A%E7%B2%BE%E5%87%86%E5%BA%A6%E8%AF%B4%E6%98%8E),在了解您自己的对数据精准度需求以后再进行抉择。
### Automated Install
```bash
@@ -39,15 +55,17 @@ Windows 用户请直接前往 [Release](https://github.com/sjlleo/nexttrace/rele
```bash
# IPv4 ICMP Trace
nexttrace 1.0.0.1
# URL
nexttrace http://example.com:8080/index.html?q=1
# 表格打印一次性输出全部跳数需等待20-40秒
nexttrace -table 1.0.0.1
# 表格打印,使用 --table / -t 参数,将实时显示结果
nexttrace --table 1.0.0.1
# IPv6 ICMP Trace
nexttrace 2606:4700:4700::1111
# 路径可视化 使用 -M 参数,将返回一个地图 URL
nexttrace -M koreacentral.blob.core.windows.net
# 禁用路径可视化 使用 --map / -M 参数
nexttrace koreacentral.blob.core.windows.net
# MapTrace URL: https://api.leo.moe/tracemap/html/c14e439e-3250-5310-8965-42a1e3545266.html
```
@@ -60,11 +78,11 @@ PS: 路由可视化的绘制模块由 [@tsosunchia](https://github.com/tsosunchi
`NextTrace` 现已经支持快速测试,有一次性测试回程路由需求的朋友可以使用
```bash
# 北上广(电信+联通+移动+教育网IPv4 ICMP 快速测试
nexttrace -f
# 北上广(电信+联通+移动+教育网IPv4 / IPv6 ICMP 快速测试
nexttrace --fast-trace
# 也可以使用 TCP SYN 而非 ICMP 进行测试
nexttrace -f -T
nexttrace --fast-trace --tcp
```
`NextTrace` 已支持指定网卡进行路由跟踪
@@ -72,43 +90,44 @@ nexttrace -f -T
```bash
# 请注意 Lite 版本此参数不能和快速测试联用,如有需要请使用 enhanced 版本
# 使用 eth0 网卡
nexttrace -D eth0 2606:4700:4700::1111
nexttrace --dev eth0 2606:4700:4700::1111
# 使用 eth0 网卡IP
# 网卡 IP 可以使用 ip a 或者 ifconfig 获取
# 使用网卡IP进行路由跟踪时需要注意跟踪的IP类型应该和网卡IP类型一致如都为 IPv4
nexttrace -S 204.98.134.56 9.9.9.9
nexttrace --source 204.98.134.56 9.9.9.9
```
`NextTrace` 也可以使用`TCP``UDP`协议发起`Traceroute`请求,不过目前只支持`IPv4`
`NextTrace` 也可以使用`TCP``UDP`协议发起`Traceroute`请求,不过目前`UDP`只支持`IPv4`
```bash
# TCP SYN Trace
nexttrace -T www.bing.com
nexttrace --tcp www.bing.com
# 可以自行指定端口[此处为443]默认80端口
nexttrace -T -p 443 1.0.0.1
nexttrace --tcp --port 443 2001:4860:4860::8888
# UDP Trace
nexttrace -U 1.0.0.1
nexttrace --udp 1.0.0.1
nexttrace -U -p 53 1.0.0.1
# 可以自行指定端口[此处为5353]默认53端口
nexttrace --udp --port 5353 1.0.0.1
```
`NextTrace`也同样支持一些进阶功能,如 TTL 控制、并发数控制、模式切换等
```bash
# 每一跳发送2个探测包
nexttrace -q 2 www.hkix.net
nexttrace --queries 2 www.hkix.net
# 无并发,每次只发送一个探测包
nexttrace -r 1 www.hkix.net
nexttrace --parallel-requests 1 www.hkix.net
# 从TTL为5开始发送探测包直到TTL为10结束
nexttrace -b 5 -m 10 www.decix.net
nexttrace --first 5 --max-hops 10 www.decix.net
# 关闭IP反向解析功能
nexttrace -n www.bbix.net
nexttrace --no-rdns www.bbix.net
# 特色功能打印Route-Path图
# Route-Path图示例
@@ -119,88 +138,120 @@ nexttrace -n www.bbix.net
# ╰AS36776 Five9 Inc.「Philippines『Metro Manila』」
# ╭╯
# ╰AS37963 阿里云「ALIDNS.COM『ALIDNS.COM』」
nexttrace -report www.time.com.my
nexttrace --route-path www.time.com.my
```
`NextTrace`支持用户自主选择 IP 数据库(目前支持:`LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`
```bash
# 可以自行指定IP数据库[此处为IP.SB]不指定则默认为LeoMoeAPI
nexttrace -d IP.SB
## 特别的:其中 ipinfo API 需要从ipinfo自行购买服务如有需要可以clone本项目添加其提供的token自行编译
nexttrace --data-provider IP.SB
## 特别的:其中 ipinfo API 需要从 ipinfo 自行购买服务,如有需要可以 clone 本项目添加其提供的 token 自行编译
## TOKEN填写路径ipgeo/tokens.go
## 另外由于IP.SB被滥用比较严重会经常出现无法查询的问题请知悉。
## IPAPI.com限制调用较为严格如有查询不到的情况请几分钟后再试。
```
`NextTrace`支持参数混合使用
`NextTrace`支持使用混合参数和简略参数
```bash
Example:
nexttrace -d IPInsight -m 20 -p 443 -q 5 -r 20 -rdns 1.1.1.1
nexttrace -T -q 2 -r 1 -table -report 2001:4860:4860::8888
nexttrace --data-provider IPAPI.com --max-hops 20 --tcp --port 443 --queries 5 --no-rdns 1.1.1.1
nexttrace -tcp --queries 2 --parallel-requests 1 --table --route-path 2001:4860:4860::8888
Equivalent to:
nexttrace -d IPAPI.com -m 20 -T -p 443 -q 5 -n 1.1.1.1
nexttrace -T -q 2 --parallel-requests 1 -t -R 2001:4860:4860::8888
```
### IP 数据库
我们使用[bgp.tools](https://bgp.tools)作为路由表功能的数据提供者。
✨NextTrace `LeoMoeAPI` 的后端也开源啦
[GitHub - sjlleo/nexttrace-backend: NextTrace BackEnd](https://github.com/sjlleo/nexttrace-backend)
NextTrace 所有的的 IP 地理位置`API DEMO`可以参考[这里](https://github.com/xgadget-lab/nexttrace/blob/main/ipgeo/)
### 全部用法详见 Usage 菜单
```shell
Usage of nexttrace:
'nexttrace [options] <hostname>' or 'nexttrace <hostname> [option...]'
Options:
-D string
Use the following Network Devices as the source address in outgoing packets
-S string
Use the following IP address as the source address in outgoing packets
-T Use TCP SYN for tracerouting (default port is 80)
-U Use UDP Package for tracerouting (default port is 53 in UDP)
-V Print Version
-b int
Set The Begin TTL (default 1)
-d string
Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight, IPAPI.com] (default "LeoMoeAPI")
-f One-Key Fast Traceroute
-m int
Set the max number of hops (max TTL to be reached). (default 30)
-n Disable IP Reverse DNS lookup
-p int
Set SYN Traceroute Port (default 80)
-q int
Set the number of probes per each hop. (default 3)
-r int
Set ParallelRequests number. It should be 1 when there is a multi-routing. (default 18)
-report
Route Path
-table
Output trace results as table
Usage: nexttrace [-h|--help] [-T|--tcp] [-U|--udp] [-F|--fast-trace] [-p|--port
<integer>] [-q|--queries <integer>] [--parallel-requests
<integer>] [-m|--max-hops <integer>] [-d|--data-provider
(IP.SB|IPInfo|IPInsight|IPAPI.com)] [-n|--no-rdns]
[-r|--route-path] [-o|--output] [-t|--table] [-c|--classic]
[-f|--first <integer>] [-M|--map] [-v|--version] [-s|--source
"<value>"] [-D|--dev "<value>"] [-R|--route] [-z|--send-time
<integer>] [-i|--ttl-time <integer>]
[IP Address or Domain name]
Arguments:
-h --help Print help information
-T --tcp Use TCP SYN for tracerouting (default port
is 80)
-U --udp Use UDP SYN for tracerouting (default port
is 53)
-F --fast-trace One-Key Fast Trace to China ISPs
-p --port Set the destination port to use. It is
either initial udp port value for
"default"method (incremented by each
probe, default is 33434), or initial seq
for "icmp" (incremented as well, default
from 1), or some constantdestination port
for other methods (with default of 80 for
"tcp", 53 for "udp", etc.)
-q --queries Set the number of probes per each hop.
Default: 3
--parallel-requests Set ParallelRequests number. It should be
1 when there is a multi-routing. Default:
18
-m --max-hops Set the max number of hops (max TTL to be
reached). Default: 30
-d --data-provider Choose IP Geograph Data Provider
[LeoMoeAPI,IP.SB, IPInfo, IPInsight,
IPAPI.com]. Default: LeoMoeAPI
-n --no-rdns Do not resolve IP addresses to their
domain names
-r --route-path Print traceroute hop path by ASN and
location
-o --output Write trace result to file
(RealTimePrinter ONLY)
-t --table Output trace results as table
-c --classic Classic Output trace results like
BestTrace
-f --first Start from the first_ttl hop (instead from
1). Default: 1
-M --map Disable Print Trace Map Function
-v --version Print version info and exit
-s --source Use source src_addr for outgoing packets
-D --dev Use the following Network Devices as the
source address in outgoing packets
-R --route Show Routing Table [Provided By BGP.Tools]
-z --send-time Set the time interval for sending every
packet. Useful when some routers use
rate-limit for ICMP messages.. Default: 0
-i --ttl-time Set the time interval for sending packets
groups by TTL. Useful when some routers
use rate-limit for ICMP messages..
Default: 500
-g --language Choose the language for displaying [en,
cn]. Default: cn
```
## 项目截图
![image](https://user-images.githubusercontent.com/13616352/208289553-7f633f9c-7356-40d1-bbc4-cc2687419cca.png)
![image](https://user-images.githubusercontent.com/59512455/218505939-287727ce-7207-43c4-8e31-fcda7df0b872.png)
![image](https://user-images.githubusercontent.com/13616352/208289568-2a135c2d-ae4a-4a3e-8a43-f5a9a87ade4a.png)
![image](https://user-images.githubusercontent.com/59512455/218504874-06b9fa4b-48e0-420a-a195-08a1200d65a7.png)
## 第三方 IP 数据库 API 开发接口
NextTrace 所有的的 IP 地理位置 `API DEMO` 可以参考[这里](https://github.com/sjlleo/nexttrace/blob/main/ipgeo/)
你可以在这里添加你自己的 API 接口,为了 NextTrace 能够正确显示你接口中的内容,请参考 `leo.go` 中所需要的信息
✨NextTrace `LeoMoeAPI` 的后端 Demo
[GitHub - sjlleo/nexttrace-backend: NextTrace BackEnd](https://github.com/sjlleo/nexttrace-backend)
## NextTrace Enhanced
`NextTrace Enhanced` 是面向发烧友的增强版,`Enhanced`提供Web API形式的路由跟踪调用以及一个简单的自带可视化的Looking Glass网页。
`Enhanced` 版本支持很多`lite`版本没有的功能如能够自定义设置超时时间也能指定TTL作为起点进行路由跟踪等对于普通用户来说通常`lite`版本已经足够完成大部分需要。
**很遗憾,由于时间精力的关系,`Enhanced` 版本将在很长一段时间不会再收到新的更新补丁,我们将继续维护 `Standard` 版本。**
https://github.com/OwO-Network/nexttrace-enhanced
## Thanks
## Credits
BGP.TOOLS 提供了本项目的一些数据支持,在此表示由衷地感谢。
@@ -214,6 +265,60 @@ BGP.TOOLS 提供了本项目的一些数据支持,在此表示由衷地感谢
[FFEE_CO](https://github.com/fkx4-p)
### Others
## Others
其他第三方 API 尽管集成在本项目内,但是具体的 TOS 以及 AUP请详见第三方 API 官网。如遇到 IP 数据错误,也请直接联系他们纠错。
## IP 数据以及精准度说明
NextTrace 有多个数据源可以选择,目前默认使用的 LeoMoeAPI 为我们项目维护的数据源。
LeoMoeAPI 早期数据主要来自 IPInsight、IPInfo随着项目发展越来越多的志愿者参与进了这个项目。目前 LeoMoeAPI 有近一半的数据是社区提供的,而另外一半主要来自于包含 IPInfo、IPData、BigDataCloud、IPGeoLocation 在内的多个第三方数据。
LeoMoeAPI 的骨干网数据有近 70% 是社区自发反馈又或者是项目组成员校准的,这给本项目的路由跟踪基础功能带来了一定的保证,但是全球骨干网的体量庞大,我们并无能力如 IPIP 等商业公司拥有海量监测节点,这使得 LeoMoeAPI 的数据精准度无法和形如 BestTraceIPIP相提并论。
LeoMoeAPI 已经尽力校准了比较常见的骨干网路由,这部分在测试的时候经常会命中,但是如果遇到封闭型 ISP 的路由,大概率可以遇到错误,此类数据不仅是我们,哪怕 IPInsight、IPInfo 也无法正确定位,目前只有 IPIP 能够标记正确,如对此类数据的精确性有着非常高的要求,请务必使用 BestTrace 作为首选。
我们不保证我们的数据一定会及时更新,也不保证数据的精确性,我们希望您在发现数据错误的时候可以前往 issue 页面提交错误报告,谢谢。
当您使用 LeoMoeAPI 即视为您已经完全了解 NextTrace LeoMoeAPI 的数据精确性,并且同意如果您引用 LeoMoeAPI 其中的数据从而引发的一切问题,均由您自己承担。
## DN42 模式使用说明
使用这个模式需要您配置 2 个文件,分别是 geofeed.csv 以及 ptr.csv
当您初次运行 DN42 模式NT 会为您生成 nt_config.yaml 文件,您可以自定义 2 个文件的存放位置,默认应该存放在 NT 的运行目录下
### GeoFeed
对于 geofeed.csv 来说,格式如下:
```
IP_CDIR,LtdCode,ISO3166-2,CityName,ASN,IPWhois
```
比如,您可以这么写:
```
58.215.96.0/20,CN,CN-JS,Wuxi,23650,CHINANET-JS
```
如果您有一个大段作为骨干网使用,您也可以不写地理位置信息,如下:
```
202.97.0.0/16,,,4134,CHINANET-BACKBONE
```
### PTR
对于 ptr.csv 来说,格式如下:
```
IATA_CODE,LtdCode,RegionName,CityName
```
比如对于美国洛杉矶,您可以这么写
```
LAX,US,California,Los Anegles
```
需要注意的是NextTrace 支持自动匹配 CSV 中的城市名,如果您的 PTR 记录中有 `losangeles`,您可以只添加上面一条记录就可以正常识别并读取。

279
cmd/cmd.go Normal file
View File

@@ -0,0 +1,279 @@
package cmd
import (
"encoding/json"
"fmt"
"log"
"net"
"os"
"os/signal"
"runtime"
"strings"
"time"
"github.com/akamensky/argparse"
"github.com/syndtr/gocapability/capability"
"github.com/xgadget-lab/nexttrace/config"
fastTrace "github.com/xgadget-lab/nexttrace/fast_trace"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/printer"
"github.com/xgadget-lab/nexttrace/reporter"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/tracelog"
"github.com/xgadget-lab/nexttrace/tracemap"
"github.com/xgadget-lab/nexttrace/util"
"github.com/xgadget-lab/nexttrace/wshandle"
)
func Excute() {
parser := argparse.NewParser("nexttrace", "An open source visual route tracking CLI tool")
// Create string flag
tcp := parser.Flag("T", "tcp", &argparse.Options{Help: "Use TCP SYN for tracerouting (default port is 80)"})
udp := parser.Flag("U", "udp", &argparse.Options{Help: "Use UDP SYN for tracerouting (default port is 53)"})
fast_trace := parser.Flag("F", "fast-trace", &argparse.Options{Help: "One-Key Fast Trace to China ISPs"})
port := parser.Int("p", "port", &argparse.Options{Help: "Set the destination port to use. It is either initial udp port value for \"default\"" +
"method (incremented by each probe, default is 33434), or initial seq for \"icmp\" (incremented as well, default from 1), or some constant" +
"destination port for other methods (with default of 80 for \"tcp\", 53 for \"udp\", etc.)"})
numMeasurements := parser.Int("q", "queries", &argparse.Options{Default: 3, Help: "Set the number of probes per each hop"})
parallelRequests := parser.Int("", "parallel-requests", &argparse.Options{Default: 18, Help: "Set ParallelRequests number. It should be 1 when there is a multi-routing"})
maxHops := parser.Int("m", "max-hops", &argparse.Options{Default: 30, Help: "Set the max number of hops (max TTL to be reached)"})
dataOrigin := parser.Selector("d", "data-provider", []string{"IP.SB", "IPInfo", "IPInsight", "IPAPI.com"}, &argparse.Options{Default: "LeoMoeAPI",
Help: "Choose IP Geograph Data Provider [LeoMoeAPI,IP.SB, IPInfo, IPInsight, IPAPI.com]"})
noRdns := parser.Flag("n", "no-rdns", &argparse.Options{Help: "Do not resolve IP addresses to their domain names"})
alwaysRdns := parser.Flag("a", "always-rdns", &argparse.Options{Help: "Always resolve IP addresses to their domain names"})
routePath := parser.Flag("P", "route-path", &argparse.Options{Help: "Print traceroute hop path by ASN and location"})
report := parser.Flag("r", "report", &argparse.Options{Help: "output using report mode"})
dn42 := parser.Flag("", "dn42", &argparse.Options{Help: "DN42 Mode"})
output := parser.Flag("o", "output", &argparse.Options{Help: "Write trace result to file (RealTimePrinter ONLY)"})
tablePrint := parser.Flag("t", "table", &argparse.Options{Help: "Output trace results as table"})
classicPrint := parser.Flag("c", "classic", &argparse.Options{Help: "Classic Output trace results like BestTrace"})
beginHop := parser.Int("f", "first", &argparse.Options{Default: 1, Help: "Start from the first_ttl hop (instead from 1)"})
maptrace := parser.Flag("M", "map", &argparse.Options{Help: "Disable Print Trace Map"})
ver := parser.Flag("v", "version", &argparse.Options{Help: "Print version info and exit"})
src_addr := parser.String("s", "source", &argparse.Options{Help: "Use source src_addr for outgoing packets"})
src_dev := parser.String("D", "dev", &argparse.Options{Help: "Use the following Network Devices as the source address in outgoing packets"})
router := parser.Flag("R", "route", &argparse.Options{Help: "Show Routing Table [Provided By BGP.Tools]"})
packet_interval := parser.Int("z", "send-time", &argparse.Options{Default: 100, Help: "Set the time interval for sending every packet. Useful when some routers use rate-limit for ICMP messages"})
ttl_interval := parser.Int("i", "ttl-time", &argparse.Options{Default: 500, Help: "Set the time interval for sending packets groups by TTL. Useful when some routers use rate-limit for ICMP messages"})
str := parser.StringPositional(&argparse.Options{Help: "IP Address or domain name"})
dot := parser.Selector("", "dot-server", []string{"dnssb", "aliyun", "dnspod", "google", "cloudflare"}, &argparse.Options{Default: "dnssb",
Help: "Use DoT Server for DNS Parse [dnssb, aliyun, dnspod, google, cloudflare]"})
lang := parser.Selector("g", "language", []string{"en", "cn"}, &argparse.Options{Default: "cn",
Help: "Choose the language for displaying [en, cn]"})
err := parser.Parse(os.Args)
if err != nil {
// In case of error print error and print usage
// This can also be done by passing -h or --help flags
fmt.Print(parser.Usage(err))
return
}
printer.Version()
if *ver {
printer.CopyRight()
os.Exit(0)
}
domain := *str
if *port == 0 {
*port = 80
}
if *fast_trace {
fastTrace.FastTest(*tcp, *output)
if *output {
fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中")
}
os.Exit(0)
}
if domain == "" {
fmt.Print(parser.Usage(err))
return
}
if strings.Contains(domain, "/") {
domain = strings.Split(domain, "/")[2]
}
if strings.Contains(domain, "]") {
domain = strings.Split(strings.Split(domain, "]")[0], "[")[1]
} else if strings.Contains(domain, ":") {
if strings.Count(domain, ":") == 1 {
domain = strings.Split(domain, ":")[0]
}
}
capabilities_check()
// return
var ip net.IP
if runtime.GOOS == "windows" && (*tcp || *udp) {
fmt.Println("NextTrace 基于 Windows 的路由跟踪还在早期开发阶段目前还存在诸多问题TCP/UDP SYN 包请求可能不能正常运行")
}
if *udp {
ip = util.DomainLookUp(domain, true, *dot)
} else {
ip = util.DomainLookUp(domain, false, *dot)
}
if *src_dev != "" {
dev, _ := net.InterfaceByName(*src_dev)
if addrs, err := dev.Addrs(); err == nil {
for _, addr := range addrs {
if (addr.(*net.IPNet).IP.To4() == nil) == (ip.To4() == nil) {
*src_addr = addr.(*net.IPNet).IP.String()
}
}
}
}
if *dn42 {
// 初始化配置
config.InitConfig()
*dataOrigin = "DN42"
*maptrace = true
}
if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" {
w := wshandle.New()
w.Interrupt = make(chan os.Signal, 1)
signal.Notify(w.Interrupt, os.Interrupt)
defer func() {
w.Conn.Close()
}()
}
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
var m trace.Method = ""
switch {
case *tcp:
m = trace.TCPTrace
case *udp:
m = trace.UDPTrace
default:
m = trace.ICMPTrace
}
if !*tcp && *port == 80 {
*port = 53
}
var conf = trace.Config{
DN42: *dn42,
SrcAddr: *src_addr,
BeginHop: *beginHop,
DestIP: ip,
DestPort: *port,
MaxHops: *maxHops,
PacketInterval: *packet_interval,
TTLInterval: *ttl_interval,
NumMeasurements: *numMeasurements,
ParallelRequests: *parallelRequests,
Lang: *lang,
RDns: !*noRdns,
AlwaysWaitRDNS: *alwaysRdns,
IPGeoSource: ipgeo.GetSource(*dataOrigin),
Timeout: 1 * time.Second,
}
if !*tablePrint {
if *classicPrint {
conf.RealtimePrinter = printer.ClassicPrinter
} else {
if *output {
conf.RealtimePrinter = tracelog.RealtimePrinter
} else if *router {
conf.RealtimePrinter = printer.RealtimePrinterWithRouter
fmt.Println("路由表数据源由 BGP.Tools 提供,在此特表感谢")
} else {
conf.RealtimePrinter = printer.RealtimePrinter
}
}
} else {
if !*report {
conf.AsyncPrinter = printer.TracerouteTablePrinter
}
}
res, err := trace.Traceroute(m, conf)
if err != nil {
log.Fatalln(err)
}
if *tablePrint {
printer.TracerouteTablePrinter(res)
}
if *routePath {
r := reporter.New(res, ip.String())
r.Print()
}
if !*maptrace {
r, _ := json.Marshal(res)
tracemap.GetMapUrl(string(r))
}
}
func capabilities_check() {
// Windows 判断放在前面,防止遇到一些奇奇怪怪的问题
if runtime.GOOS == "windows" {
// Running on Windows, skip checking capabilities
return
}
uid := os.Getuid()
if uid == 0 {
// Running as root, skip checking capabilities
return
}
/***
* 检查当前进程是否有两个关键的权限
==== 看不到我 ====
* 没办法啦
* 自己之前承诺的坑补全篇
* 被迫填坑系列 qwq
==== 看不到我 ====
***/
// NewPid 已经被废弃了,这里改用 NewPid2 方法
caps, err := capability.NewPid2(0)
if err != nil {
// 判断是否为macOS
if runtime.GOOS == "darwin" {
// macOS下报错有问题
} else {
fmt.Println(err)
}
return
}
// load 获取全部的 caps 信息
err = caps.Load()
if err != nil {
fmt.Println(err)
return
}
// 判断一下权限有木有
if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) && caps.Get(capability.EFFECTIVE, capability.CAP_NET_ADMIN) {
// 有权限啦
return
} else {
// 没权限啦
fmt.Println("您正在以普通用户权限运行 NextTrace但 NextTrace 未被赋予监听网络套接字的ICMP消息包、修改IP头信息TTL等路由跟踪所需的权限")
fmt.Println("请使用管理员用户执行 `sudo setcap cap_net_raw,cap_net_admin+eip ${your_nexttrace_path}/nexttrace` 命令,赋予相关权限后再运行~")
fmt.Println("什么?为什么 ping 普通用户执行不要 root 权限?因为这些工具在管理员安装时就已经被赋予了一些必要的权限,具体请使用 `getcap /usr/bin/ping` 查看")
}
}

7
cmd/cmd_test.go Normal file
View File

@@ -0,0 +1,7 @@
package cmd
import "testing"
func TestCMD(t *testing.T) {
Excute()
}

34
config/viper.go Normal file
View File

@@ -0,0 +1,34 @@
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 默认配置")
viper.SafeWriteConfigAs("./nt_config.yaml")
}
viper.ReadInConfig()
}

7
dn42/dn42.go Normal file
View File

@@ -0,0 +1,7 @@
package dn42
/***
[DN42 Package]
谨献给 DN42 所有的小伙伴们,祝你们终有一天能有自己的公网 ASN ~
By Leo
***/

101
dn42/geofeed.go Normal file
View File

@@ -0,0 +1,101 @@
package dn42
import (
"encoding/csv"
"net"
"os"
"sort"
"github.com/spf13/viper"
)
type GeoFeedRow struct {
IPNet *net.IPNet
CIDR string
LtdCode string
ISO3166 string
City string
ASN string
IPWhois string
}
func GetGeoFeed(ip string) (GeoFeedRow, bool) {
rows, err := ReadGeoFeed()
if err != nil {
// 处理错误
panic(err)
}
row, find := FindGeoFeedRow(ip, rows)
return row, find
}
func ReadGeoFeed() ([]GeoFeedRow, error) {
path := viper.Get("geoFeedPath").(string)
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
r := csv.NewReader(f)
rows, err := r.ReadAll()
if err != nil {
return nil, err
}
// 将 CSV 中的每一行转换为 GeoFeedRow 类型,并保存到 rowsSlice 中
var rowsSlice []GeoFeedRow
for _, row := range rows {
cidr := row[0] // 假设第一列是 CIDR 字段
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
// 如果解析 CIDR 失败,跳过这一行
continue
}
if len(row) == 4 {
rowsSlice = append(rowsSlice, GeoFeedRow{
IPNet: ipnet,
CIDR: cidr,
LtdCode: row[1],
ISO3166: row[2],
City: row[3],
})
} else {
rowsSlice = append(rowsSlice, GeoFeedRow{
IPNet: ipnet,
CIDR: cidr,
LtdCode: row[1],
ISO3166: row[2],
City: row[3],
ASN: row[4],
IPWhois: row[5],
})
}
}
// 根据 CIDR 范围从小到大排序,方便后面查找
sort.Slice(rowsSlice, func(i, j int) bool {
return rowsSlice[i].IPNet.Mask.String() > rowsSlice[j].IPNet.Mask.String()
})
return rowsSlice, nil
}
func FindGeoFeedRow(ipStr string, rows []GeoFeedRow) (GeoFeedRow, bool) {
ip := net.ParseIP(ipStr)
if ip == nil {
// 如果传入的 IP 无效,直接返回
return GeoFeedRow{}, false
}
// 遍历每个 CIDR 范围,找到第一个包含传入的 IP 的 CIDR
for _, row := range rows {
if row.IPNet.Contains(ip) {
return row, true
}
}
return GeoFeedRow{}, false
}

17
dn42/geofeed_test.go Normal file
View File

@@ -0,0 +1,17 @@
package dn42
// func TestGeoFeed(t *testing.T) {
// rows, err := ReadGeoFeed()
// if err != nil {
// // 处理错误
// }
// row, found := FindGeoFeedRow("2001:0418:1403:8080::6fff", rows)
// if found {
// // 处理符合条件的 row
// log.Println(row)
// } else {
// // 处理未找到的情况
// }
// }

83
dn42/ptr.go Normal file
View File

@@ -0,0 +1,83 @@
package dn42
import (
"encoding/csv"
"errors"
"fmt"
"os"
"regexp"
"strings"
"github.com/spf13/viper"
)
type PtrRow struct {
IATACode string
LtdCode string
Region string
City string
}
func matchesPattern(prefix string, s string) bool {
pattern := fmt.Sprintf(`^(.*[-.\d]|^)%s[-.\d].*$`, prefix)
r, err := regexp.Compile(pattern)
if err != nil {
fmt.Println("Invalid regular expression:", err)
return false
}
return r.MatchString(s)
}
func FindPtrRecord(ptr string) (PtrRow, error) {
path := viper.Get("ptrPath").(string)
f, err := os.Open(path)
if err != nil {
return PtrRow{}, err
}
defer f.Close()
r := csv.NewReader(f)
rows, err := r.ReadAll()
if err != nil {
return PtrRow{}, err
}
// 转小写
ptr = strings.ToLower(ptr)
// 先查城市名
for _, row := range rows {
city := row[3]
if city == "" {
continue
}
city = strings.ReplaceAll(city, " ", "")
city = strings.ToLower(city)
if matchesPattern(city, ptr) {
return PtrRow{
LtdCode: row[1],
Region: row[2],
City: row[3],
}, nil
}
}
// 查 IATA Code
for _, row := range rows {
iata := row[0]
if iata == "" {
continue
}
iata = strings.ToLower(iata)
if matchesPattern(iata, ptr) {
return PtrRow{
IATACode: iata,
LtdCode: row[1],
Region: row[2],
City: row[3],
}, nil
}
}
return PtrRow{}, errors.New("ptr not found")
}

50
dn42/ptr_test.go Normal file
View File

@@ -0,0 +1,50 @@
package dn42
import (
"testing"
)
func TestPTR(t *testing.T) {
// example_mis := []string{
// "sloutravel.com",
// "memeslou.org",
// "followsloucity.net",
// "slouslou.slou",
// "slouslou8.slou",
// }
// examples := []string{
// "1ge.slou.as1299.net",
// "1ge.slou2.as1299.net",
// "1ge-slou.as1299.net",
// "slou-1.as1299.net",
// "slou.as1299.com",
// "1ge-snge-6.as1299.net",
// "c-1.sin.sg.atlas.moeqing.com",
// "core.hkg1.hk.atlas.moeqing.com",
// "core.losangles.us.atlas.moeqing.com",
// }
// fmt.Println("容易误匹配的 PTR")
// for _, s := range example_mis {
// if r, err := FindPtrRecord("ptr.csv"); err == nil {
// fmt.Println(s, r)
// } else {
// fmt.Println(s, err)
// }
// }
// fmt.Println("\n应该正常匹配的 PTR")
// for _, s := range examples {
// if r, err := FindPtrRecord("ptr.csv"); err == nil {
// fmt.Println(s, r)
// } else {
// fmt.Println(s, err)
// }
// }
}

View File

@@ -16,6 +16,7 @@ type BackBoneCollection struct {
CU169 ISPCollection
CU9929 ISPCollection
CM ISPCollection
CMIN2 ISPCollection
EDU ISPCollection
CST ISPCollection
}
@@ -32,6 +33,7 @@ const (
CU169 string = "联通 169 AS4837"
CU9929 string = "联通 A网 AS9929"
CM string = "移动 骨干网 AS9808"
CMIN2 string = "移动 CMIN2 AS58807"
EDU string = "教育网 CERNET AS4538"
)
@@ -57,12 +59,22 @@ var Beijing = BackBoneCollection{
IPv6: "2408:8000:1010:2::10",
},
CU9929: ISPCollection{
ISPName: CU9929,
IP: "218.105.131.125",
},
CM: ISPCollection{
ISPName: CM,
IP: "211.136.25.153",
IPv6: "2409:8000:3800:8::3",
},
CMIN2: ISPCollection{
ISPName: CMIN2,
IP: "223.70.155.55",
},
EDU: ISPCollection{
ISPName: EDU,
IP: "101.6.15.130",
@@ -74,7 +86,7 @@ var Shanghai = BackBoneCollection{
Location: "上海",
CT163: ISPCollection{
ISPName: CT163,
IP: "101.226.28.198",
IP: "202.101.21.178",
IPv6: "240e:18:2:153::89",
},
@@ -101,6 +113,11 @@ var Shanghai = BackBoneCollection{
IPv6: "2409:801e:f0:1::4e1",
},
CMIN2: ISPCollection{
ISPName: CMIN2,
IP: "183.194.134.1",
},
EDU: ISPCollection{
ISPName: EDU,
IP: "202.120.58.155",

View File

@@ -41,13 +41,10 @@ func (f *FastTracer) tracert_v6(location string, ispCollection ISPCollection) {
Timeout: 1 * time.Second,
}
if f.TracerouteMethod == trace.ICMPTrace {
if oe {
conf.RealtimePrinter = tracelog.RealtimePrinter
} else {
conf.RealtimePrinter = printer.RealtimePrinter
}
if oe {
conf.RealtimePrinter = tracelog.RealtimePrinter
} else {
conf.RealtimePrinter = printer.RealtimePrinter
}
_, err = trace.Traceroute(f.TracerouteMethod, conf)
@@ -115,7 +112,12 @@ func FastTestv6(tm bool, outEnable bool) {
w.Conn.Close()
}()
ft.TracerouteMethod = trace.ICMPTrace
if !tm {
ft.TracerouteMethod = trace.ICMPTrace
fmt.Println("您将默认使用ICMP协议进行路由跟踪如果您想使用TCP SYN进行路由跟踪可以加入 -T 参数")
} else {
ft.TracerouteMethod = trace.TCPTrace
}
switch c {
case "1":

View File

@@ -43,6 +43,8 @@ func (f *FastTracer) tracert(location string, ispCollection ISPCollection) {
NumMeasurements: 3,
ParallelRequests: 18,
RDns: true,
PacketInterval: 100,
TTLInterval: 500,
IPGeoSource: ipgeo.GetSource("LeoMoeAPI"),
Timeout: 1 * time.Second,
}
@@ -81,6 +83,7 @@ func (f *FastTracer) testCT() {
func (f *FastTracer) testCU() {
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU169)
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CU9929)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU169)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CU9929)
f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CU169)
@@ -89,7 +92,9 @@ func (f *FastTracer) testCU() {
func (f *FastTracer) testCM() {
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CM)
f.tracert(TestIPsCollection.Beijing.Location, TestIPsCollection.Beijing.CMIN2)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CM)
f.tracert(TestIPsCollection.Shanghai.Location, TestIPsCollection.Shanghai.CMIN2)
f.tracert(TestIPsCollection.Hangzhou.Location, TestIPsCollection.Hangzhou.CM)
f.tracert(TestIPsCollection.Guangzhou.Location, TestIPsCollection.Guangzhou.CM)
}
@@ -106,7 +111,7 @@ func FastTest(tm bool, outEnable bool) {
var c string
oe = outEnable
fmt.Println("Hi欢迎使用 Fast Trace 功能,请注意 Fast Trace 功能只适合新手使用\n因为国内网络复杂我们设置的测试目标有限建议普通用户自测以获得更加精准的路由情况")
fmt.Println("请您选择要测试的 IP 类型\n1. IPv4\n2. IPv6")
fmt.Print("请选择选项:")
fmt.Scanln(&c)

3
geofeed.example.csv Normal file
View File

@@ -0,0 +1,3 @@
154.48.0.0/12,,,,174,COGENT-NET
200.15.12.0/22,BR,BR-SP,Sao Paulo,2914,NTT-BACKBONE
2001:0418:1403::/48,US,US-VA,Ashburn,2914,NTT-BACKBONE
1 154.48.0.0/12 174 COGENT-NET
2 200.15.12.0/22 BR BR-SP Sao Paulo 2914 NTT-BACKBONE
3 2001:0418:1403::/48 US US-VA Ashburn 2914 NTT-BACKBONE

28
go.mod
View File

@@ -1,29 +1,41 @@
module github.com/xgadget-lab/nexttrace
go 1.19
go 1.20
require (
github.com/akamensky/argparse v1.4.0
github.com/google/gopacket v1.1.19
github.com/spf13/viper v1.15.0
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
golang.org/x/net v0.5.0
golang.org/x/net v0.7.0
golang.org/x/sync v0.1.0
)
require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.13.0
github.com/fatih/color v1.14.1
github.com/gorilla/websocket v1.5.0
github.com/lionsoul2014/ip2region v2.11.0+incompatible
github.com/lionsoul2014/ip2region v2.11.1+incompatible
github.com/rodaine/table v1.1.0
github.com/stretchr/testify v1.7.1 // indirect
github.com/tidwall/gjson v1.14.4
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
golang.org/x/sys v0.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
golang.org/x/sys v0.5.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

478
go.sum
View File

@@ -1,34 +1,193 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/lionsoul2014/ip2region v2.11.0+incompatible h1:+L3q50qdDztfPfwKFukSWoOFTpbPhiYRXMmr2cYPPdM=
github.com/lionsoul2014/ip2region v2.11.0+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lionsoul2014/ip2region v2.11.1+incompatible h1:FEqj5PvIP6YwnGt3RYndGQaiftEIJJGpw4BCB76nvfM=
github.com/lionsoul2014/ip2region v2.11.1+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
@@ -38,29 +197,316 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

62
ipgeo/dn42.go Normal file
View File

@@ -0,0 +1,62 @@
package ipgeo
import (
"strings"
"github.com/xgadget-lab/nexttrace/dn42"
)
func LtdCodeToCountryOrAreaName(Code string) string {
countryName := []string{"United States", "Afghanistan", "Åland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", " Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada ", "Cape Verde", "Cayman Islands", "Central Africa", "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo (Brazzaville)", "DRC", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", " Denmark", "Djibouti", "Dominica", "Dominica", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)", "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", " French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Vatican", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "British Isles of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "North Korea", "South Korea", " Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "FYROM", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", " Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia (Federated States of)", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", "Rwanda", "St. Helena", "St. Kitts and Nevis", "St. Lucia", "St. Pierre and Miquelon", "St. Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen Islands", "Swaziland", "Sweden ", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", " United Kingdom", "U.S. Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "British Virgin Islands", "U.S. Virgin Islands", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"}
countryCode := []string{"us", "af", "ax", "al", "dz", "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "au", "at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi", "kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cn", "cx", "cc", "co", "km", "cg", "cd", "ck", "cr", "ci", "hr", "cu", "cy", "cz", "dk", "dj", "dm", "do", "ec", "eg", "sv", "gq", "er", "ee", "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm", "ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg", "gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in", "id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo", "kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls", "lr", "ly", "li", "lt", "lu", "mo", "mk", "mg", "mw", "my", "mv", "ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc", "mn", "me", "ms", "ma", "mz", "mm", "na", "nr", "np", "nl", "an", "nc", "nz", "ni", "ne", "ng", "nu", "nf", "mp", "no", "om", "pk", "pw", "ps", "pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re", "ro", "ru", "rw", "sh", "kn", "lc", "pm", "vc", "ws", "sm", "st", "sa", "sn", "rs", "sc", "sl", "sg", "sk", "si", "sb", "so", "za", "gs", "es", "lk", "sd", "sr", "sj", "sz", "se", "ch", "sy", "tw", "tj", "tz", "th", "tl", "tg", "tk", "to", "tt", "tn", "tr", "tm", "tc", "tv", "ug", "ua", "ae", "gb", "um", "uy", "uz", "vu", "ve", "vn", "vg", "vi", "wf", "eh", "ye", "zm", "zw"}
Code = strings.ToLower(Code)
for i, v := range countryCode {
if strings.Contains(Code, v) {
return countryName[i]
}
}
return Code
}
func DN42(ip string) (*IPGeoData, error) {
data := &IPGeoData{}
// 先解析传入过来的数据
ipTmp := strings.Split(ip, ",")
if len(ipTmp) > 1 {
ip = ipTmp[0]
}
// 先查找 GeoFeed
if geo, find := dn42.GetGeoFeed(ip); find {
data.Country = geo.LtdCode
data.City = geo.City
data.Asnumber = geo.ASN
data.Whois = geo.IPWhois
}
// 如果没找到,查找 PTR
if len(ipTmp) > 1 {
// 存在 PTR 记录
if res, err := dn42.FindPtrRecord(ipTmp[1]); err == nil && res.LtdCode != "" {
data.Country = res.LtdCode
data.Prov = res.Region
data.City = res.City
}
}
data.Country = LtdCodeToCountryOrAreaName(data.Country)
switch data.Country {
case "Hong Kong":
data.Country = "China"
data.Prov = "Hong Kong"
case "Taiwan":
data.Country = "China"
data.Prov = "Taiwan"
case "Macao":
data.Country = "China"
data.Prov = "Macao"
case "":
data.Country = "Unknown"
}
return data, nil
}

5
ipgeo/dn42_test.go Normal file
View File

@@ -0,0 +1,5 @@
package ipgeo
// func TestDN42(t *testing.T) {
// DN42("")
// }

86
ipgeo/ipfilter.go Normal file
View File

@@ -0,0 +1,86 @@
package ipgeo
import (
"net"
)
func cidrRangeContains(cidrRange string, checkIP string) bool {
_, ipNet, err := net.ParseCIDR(cidrRange)
if err != nil {
return false
}
secondIP := net.ParseIP(checkIP)
return ipNet.Contains(secondIP)
}
// 被选到的返回 geodata, true 否则返回 nil, false
func Filter(ip string) (*IPGeoData, bool) {
//geodata := &IPGeoData{}
asn := ""
whois := ""
isFiltered := false
switch {
//rfc1918
case net.ParseIP(ip).IsPrivate():
asn = ""
whois = "RFC1918"
isFiltered = true
//IANA Reserved Address Space
case cidrRangeContains("100.64.0.0/10", ip):
asn = ""
whois = "RFC6598"
isFiltered = true
case cidrRangeContains("198.18.0.0/15", ip):
asn = ""
whois = "RFC2544"
isFiltered = true
case cidrRangeContains("198.51.100.0/24", ip):
fallthrough
case cidrRangeContains("203.0.113.0/24", ip):
asn = ""
whois = "RFC5737"
isFiltered = true
case cidrRangeContains("240.0.0.0/4", ip):
asn = ""
whois = "RFC1112"
isFiltered = true
//Defense Information System Network
case cidrRangeContains("6.0.0.0/8", ip):
fallthrough
case cidrRangeContains("7.0.0.0/8", ip):
fallthrough
case cidrRangeContains("11.0.0.0/8", ip):
fallthrough
case cidrRangeContains("21.0.0.0/8", ip):
fallthrough
case cidrRangeContains("22.0.0.0/8", ip):
fallthrough
case cidrRangeContains("26.0.0.0/8", ip):
fallthrough
case cidrRangeContains("28.0.0.0/8", ip):
fallthrough
case cidrRangeContains("29.0.0.0/8", ip):
fallthrough
case cidrRangeContains("30.0.0.0/8", ip):
fallthrough
case cidrRangeContains("33.0.0.0/8", ip):
fallthrough
case cidrRangeContains("55.0.0.0/8", ip):
fallthrough
case cidrRangeContains("214.0.0.0/8", ip):
fallthrough
case cidrRangeContains("215.0.0.0/8", ip):
asn = ""
whois = "DOD"
isFiltered = true
default:
}
if !isFiltered {
return nil, false
} else {
return &IPGeoData{
Asnumber: asn,
Whois: whois,
}, true
}
}

View File

@@ -29,6 +29,8 @@ type Source = func(ip string) (*IPGeoData, error)
func GetSource(s string) Source {
switch strings.ToUpper(s) {
case "DN42":
return DN42
case "LEOMOEAPI":
return LeoIP
case "IP.SB":

View File

@@ -1,11 +1,72 @@
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 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)

244
main.go
View File

@@ -1,249 +1,9 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"runtime"
"strings"
"time"
"github.com/syndtr/gocapability/capability"
fastTrace "github.com/xgadget-lab/nexttrace/fast_trace"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/printer"
"github.com/xgadget-lab/nexttrace/reporter"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/tracelog"
"github.com/xgadget-lab/nexttrace/tracemap"
"github.com/xgadget-lab/nexttrace/util"
"github.com/xgadget-lab/nexttrace/wshandle"
"github.com/xgadget-lab/nexttrace/cmd"
)
var fSet = flag.NewFlagSet("", flag.ExitOnError)
var fastTest = fSet.Bool("f", false, "One-Key Fast Traceroute")
var tcpSYNFlag = fSet.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80)")
var udpPackageFlag = fSet.Bool("U", false, "Use UDP Package for tracerouting (default port is 53 in UDP)")
var port = fSet.Int("p", 80, "Set SYN Traceroute Port")
var numMeasurements = fSet.Int("q", 3, "Set the number of probes per each hop.")
var parallelRequests = fSet.Int("r", 18, "Set ParallelRequests number. It should be 1 when there is a multi-routing.")
var maxHops = fSet.Int("m", 30, "Set the max number of hops (max TTL to be reached).")
var dataOrigin = fSet.String("d", "LeoMoeAPI", "Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight, IPAPI.com]")
var noRdns = fSet.Bool("n", false, "Disable IP Reverse DNS lookup")
var routePath = fSet.Bool("report", false, "Route Path")
var output = fSet.Bool("o", false, "Ouput trace result to file (RealTimePrinter ONLY")
var tablePrint = fSet.Bool("table", false, "Output trace results as table")
var classicPrint = fSet.Bool("classic", false, "Classic Output trace results like BestTrace")
var beginHop = fSet.Int("b", 1, "Set The Begin TTL")
var maptrace = fSet.Bool("M", false, "Print Trace Map")
var ver = fSet.Bool("V", false, "Print Version")
var src_addr = fSet.String("S", "", "Use the following IP address as the source address in outgoing packets")
var src_dev = fSet.String("D", "", "Use the following Network Devices as the source address in outgoing packets")
var router = fSet.Bool("R", false, "Show Routing Table [Provided By BGP.Tools]")
func printArgHelp() {
fmt.Println("\nArgs Error\nUsage : 'nexttrace [option...] HOSTNAME' or 'nexttrace HOSTNAME [option...]'\nOPTIONS: [-VTU] [-d DATAORIGIN.STR ] [ -m TTL ] [ -p PORT ] [ -q PROBES.COUNT ] [ -r PARALLELREQUESTS.COUNT ] [-rdns] [ -table ] -report")
fSet.PrintDefaults()
os.Exit(2)
}
func flagApply() string {
printer.Version()
target := ""
if len(os.Args) < 2 {
printArgHelp()
}
// flag parse
if !strings.HasPrefix(os.Args[1], "-") {
target = os.Args[1]
fSet.Parse(os.Args[2:])
} else {
fSet.Parse(os.Args[1:])
target = fSet.Arg(0)
}
// Print Version
if *ver {
printer.CopyRight()
os.Exit(0)
}
// -f Fast Test
if *fastTest {
fastTrace.FastTest(*tcpSYNFlag, *output)
if *output {
fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中")
}
os.Exit(0)
}
if target == "" {
printArgHelp()
}
return target
}
func capabilities_check() {
// Windows 判断放在前面,防止遇到一些奇奇怪怪的问题
if runtime.GOOS == "windows" {
// Running on Windows, skip checking capabilities
return
}
uid := os.Getuid()
if uid == 0 {
// Running as root, skip checking capabilities
return
}
/***
* 检查当前进程是否有两个关键的权限
==== 看不到我 ====
* 没办法啦
* 自己之前承诺的坑补全篇
* 被迫填坑系列 qwq
==== 看不到我 ====
***/
// NewPid 已经被废弃了,这里改用 NewPid2 方法
caps, err := capability.NewPid2(0)
if err != nil {
fmt.Println(err)
return
}
// load 获取全部的 caps 信息
err = caps.Load()
if err != nil {
fmt.Println(err)
return
}
// 判断一下权限有木有
if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) && caps.Get(capability.EFFECTIVE, capability.CAP_NET_ADMIN) {
// 有权限啦
return
} else {
// 没权限啦
log.Println("您正在以普通用户权限运行 NextTrace但 NextTrace 未被赋予监听网络套接字的ICMP消息包、修改IP头信息TTL等路由跟踪所需的权限")
log.Println("请使用管理员用户执行 `sudo setcap cap_net_raw,cap_net_admin+eip ${your_nexttrace_path}/nexttrace` 命令,赋予相关权限后再运行~")
log.Fatalln("什么?为什么 ping 普通用户执行不要 root 权限?因为这些工具在管理员安装时就已经被赋予了一些必要的权限,具体请使用 `getcap /usr/bin/ping` 查看")
}
}
func main() {
domain := flagApply()
capabilities_check()
// return
var ip net.IP
if runtime.GOOS == "windows" && (*tcpSYNFlag || *udpPackageFlag) {
fmt.Println("NextTrace 基于 Windows 的路由跟踪还在早期开发阶段目前还存在诸多问题TCP/UDP SYN 包请求可能不能正常运行")
}
if *tcpSYNFlag || *udpPackageFlag {
ip = util.DomainLookUp(domain, true)
} else {
ip = util.DomainLookUp(domain, false)
}
if *src_dev != "" {
dev, _ := net.InterfaceByName(*src_dev)
if addrs, err := dev.Addrs(); err == nil {
for _, addr := range addrs {
if (addr.(*net.IPNet).IP.To4() == nil) == (ip.To4() == nil) {
*src_addr = addr.(*net.IPNet).IP.String()
}
}
}
}
if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" {
w := wshandle.New()
w.Interrupt = make(chan os.Signal, 1)
signal.Notify(w.Interrupt, os.Interrupt)
defer func() {
w.Conn.Close()
}()
}
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
var m trace.Method = ""
switch {
case *tcpSYNFlag:
m = trace.TCPTrace
case *udpPackageFlag:
m = trace.UDPTrace
default:
m = trace.ICMPTrace
}
if !*tcpSYNFlag && *port == 80 {
*port = 53
}
var conf = trace.Config{
SrcAddr: *src_addr,
BeginHop: *beginHop,
DestIP: ip,
DestPort: *port,
MaxHops: *maxHops,
NumMeasurements: *numMeasurements,
ParallelRequests: *parallelRequests,
RDns: !*noRdns,
IPGeoSource: ipgeo.GetSource(*dataOrigin),
Timeout: 1 * time.Second,
}
if !*tablePrint {
if *classicPrint {
conf.RealtimePrinter = printer.ClassicPrinter
} else {
if *output {
conf.RealtimePrinter = tracelog.RealtimePrinter
} else if *router {
conf.RealtimePrinter = printer.RealtimePrinterWithRouter
fmt.Println("路由表数据源由 BGP.Tools 提供,在此特表感谢")
} else {
conf.RealtimePrinter = printer.RealtimePrinter
}
}
} else {
conf.AsyncPrinter = printer.TracerouteTablePrinter
}
res, err := trace.Traceroute(m, conf)
if err != nil {
log.Fatalln(err)
}
if *tablePrint {
printer.TracerouteTablePrinter(res)
}
if *routePath {
r := reporter.New(res, ip.String())
r.Print()
}
if *maptrace {
r, _ := json.Marshal(res)
tracemap.GetMapUrl(string(r))
}
cmd.Excute()
}

2
nt_config.yaml Normal file
View File

@@ -0,0 +1,2 @@
geofeedpath: ./geofeed.csv
ptrpath: ./ptr.csv

View File

@@ -119,7 +119,7 @@ changeMode() {
runBinrayFileHelp() {
if [ -e ${downPath} ]; then
${downPath} -V
${downPath} --version
echo -e "${Tips} 一切准备就绪!使用命令 nexttrace 1.1.1.1 开始您的第一次路由测试吧~ 更多进阶命令玩法可以用 nexttrace -h 查看哦\n 关于软件卸载因为nexttrace是绿色版单文件卸载只需输入命令 rm ${downPath} 即可"
fi
}

View File

@@ -21,7 +21,44 @@ func Version() {
}
func CopyRight() {
fmt.Println("XGadget-lab Leo (leo.moe) & Tso (tsosunchia@gmail.com) & Vincent (vincent.moe) & zhshch (xzhsh.ch)")
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) {

View File

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

View File

@@ -61,9 +61,27 @@ func RealtimePrinter(res *trace.Result, ttl int) {
}
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))
// CMIN2, CUII, CN2, CUG 改为壕金色高亮
switch {
case res.Hops[ttl][i].Geo.Asnumber == "58807":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "10099":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "4809":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "9929":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "23764":
fallthrough
case res.Hops[ttl][i].Geo.Whois == "CMIN2-NET":
fallthrough
case strings.HasPrefix(res.Hops[ttl][i].Address.String(), "59.43."):
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiYellow, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
default:
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
}
} else {
fmt.Printf(" %-8s", "*")
}
@@ -77,11 +95,55 @@ func RealtimePrinter(res *trace.Result, ttl int) {
if whoisFormat[0] != "" {
whoisFormat[0] = "[" + whoisFormat[0] + "]"
}
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0]))
// CMIN2, CUII, CN2, CUG 改为壕金色高亮
switch {
case res.Hops[ttl][i].Geo.Asnumber == "58807":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "10099":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "4809":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "9929":
fallthrough
case res.Hops[ttl][i].Geo.Asnumber == "23764":
fallthrough
case whoisFormat[0] == "[CNC-BACKBONE]":
fallthrough
case whoisFormat[0] == "[CUG-BACKBONE]":
fallthrough
case whoisFormat[0] == "[CMIN2-NET]":
fallthrough
case strings.HasPrefix(res.Hops[ttl][i].Address.String(), "59.43."):
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiYellow, color.Bold).Sprintf("%-16s", whoisFormat[0]))
default:
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0]))
}
}
if res.Hops[ttl][i].Geo.Country == "" {
res.Hops[ttl][i].Geo.Country = "LAN Address"
if len(res.Hops[ttl][i].Geo.Country) <= 1 {
res.Hops[ttl][i].Geo.Country = "局域网"
res.Hops[ttl][i].Geo.CountryEn = "LAN Address"
}
if res.Hops[ttl][i].Lang == "en" {
if res.Hops[ttl][i].Geo.Country == "Anycast" {
} else if res.Hops[ttl][i].Geo.Prov == "骨干网" {
res.Hops[ttl][i].Geo.Prov = "BackBone"
} else if res.Hops[ttl][i].Geo.ProvEn == "" {
res.Hops[ttl][i].Geo.Country = res.Hops[ttl][i].Geo.CountryEn
} else {
if res.Hops[ttl][i].Geo.CityEn == "" {
res.Hops[ttl][i].Geo.Country = res.Hops[ttl][i].Geo.ProvEn
res.Hops[ttl][i].Geo.Prov = res.Hops[ttl][i].Geo.CountryEn
res.Hops[ttl][i].Geo.City = ""
} else {
res.Hops[ttl][i].Geo.Country = res.Hops[ttl][i].Geo.CityEn
res.Hops[ttl][i].Geo.Prov = res.Hops[ttl][i].Geo.ProvEn
res.Hops[ttl][i].Geo.City = res.Hops[ttl][i].Geo.CountryEn
}
}
}
if net.ParseIP(ip).To4() != nil {

4
ptr.example.csv Normal file
View File

@@ -0,0 +1,4 @@
snge,SG,,Singapore
CXS,CN,Hunan,Changsha
LAX,US,California,Los Angeles
SJC,US,California,San Jose
1 snge SG Singapore
2 CXS CN Hunan Changsha
3 LAX US California Los Angeles
4 SJC US California San Jose

View File

@@ -2,9 +2,12 @@ package trace
import (
"encoding/binary"
"errors"
"fmt"
"log"
"net"
"os"
"strconv"
"sync"
"time"
@@ -15,16 +18,44 @@ import (
type ICMPTracer struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
resCh chan Hop
icmpListen net.PacketConn
final int
finalLock sync.Mutex
wg sync.WaitGroup
res Result
ctx context.Context
inflightRequest map[int]chan Hop
inflightRequestRWLock sync.RWMutex
icmpListen net.PacketConn
final int
finalLock sync.Mutex
}
func (t *ICMPTracer) PrintFunc() {
defer t.wg.Done()
var ttl = t.Config.BeginHop - 1
for {
if t.AsyncPrinter != nil {
t.AsyncPrinter(&t.res)
}
if len(t.res.Hops)-1 > ttl {
if len(t.res.Hops[ttl]) == t.NumMeasurements {
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, ttl)
}
ttl++
if ttl == t.final-1 || ttl >= t.MaxHops-1 {
return
}
}
}
<-time.After(200 * time.Millisecond)
}
}
func (t *ICMPTracer) Execute() (*Result, error) {
t.inflightRequestRWLock.Lock()
t.inflightRequest = make(map[int]chan Hop)
t.inflightRequestRWLock.Unlock()
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
@@ -40,30 +71,46 @@ func (t *ICMPTracer) Execute() (*Result, error) {
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.resCh = make(chan Hop)
t.final = -1
go t.listenICMP()
t.wg.Add(1)
go t.PrintFunc()
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
t.inflightRequestRWLock.Lock()
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
t.inflightRequestRWLock.Unlock()
if t.final != -1 && ttl > t.final {
break
}
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(ttl)
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
}
// 一组TTL全部退出收到应答或者超时终止以后再进行下一个TTL的包发送
t.wg.Wait()
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
}
t.wg.Wait()
t.res.reduce(t.final)
if t.final != -1 {
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, ttl-1)
t.RealtimePrinter(&t.res, t.final-1)
}
if t.AsyncPrinter != nil {
t.AsyncPrinter(&t.res)
} else {
for i := 0; i < t.NumMeasurements; i++ {
t.res.add(Hop{
Success: false,
Address: nil,
TTL: 30,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, t.MaxHops-1)
}
}
t.res.reduce(t.final)
return &t.res, nil
}
@@ -78,55 +125,136 @@ func (t *ICMPTracer) listenICMP() {
if msg.N == nil {
continue
}
if binary.BigEndian.Uint16(msg.Msg[32:34]) != uint16(os.Getpid()&0xffff) {
// 如果类型为应答消息且应答消息包的进程ID与主进程相同时不跳过
if msg.Msg[0] != 0 || binary.BigEndian.Uint16(msg.Msg[4:6]) != uint16(os.Getpid()&0xffff) {
continue
}
}
dstip := net.IP(msg.Msg[24:28])
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv4zero) {
// 匹配再继续解析包,否则直接丢弃
// log.Println(msg.Msg)
if msg.Msg[0] == 0 {
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv4.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
case ipv4.ICMPTypeEchoReply:
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
default:
// log.Println("received icmp message of unknown type", rm.Type)
echoReply := rm.Body.(*icmp.Echo)
ttl := echoReply.Seq // This is the TTL value
if ttl > 100 {
continue
}
if msg.Peer.String() == t.DestIP.String() {
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, ttl)
}
continue
}
packet_id := strconv.FormatInt(int64(binary.BigEndian.Uint16(msg.Msg[32:34])), 2)
if process_id, ttl, err := reverseID(packet_id); err == nil {
if process_id == int64(os.Getpid()&0x7f) {
dstip := net.IP(msg.Msg[24:28])
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv4zero) {
// 匹配再继续解析包,否则直接丢弃
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv4.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data, int(ttl))
case ipv4.ICMPTypeEchoReply:
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, int(ttl))
default:
// log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
}
}
}
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
t.resCh <- Hop{
Success: true,
Address: msg.Peer,
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
t.inflightRequestRWLock.RLock()
defer t.inflightRequestRWLock.RUnlock()
if _, ok := t.inflightRequest[ttl]; ok {
t.inflightRequest[ttl] <- Hop{
Success: true,
Address: msg.Peer,
}
}
}
func gernerateID(ttl_int int) int {
const ID_FIXED_HEADER = "10"
var processID = fmt.Sprintf("%07b", os.Getpid()&0x7f) //取进程ID的前7位
var ttl = fmt.Sprintf("%06b", ttl_int) //取TTL的后6位
var parity int
id := ID_FIXED_HEADER + processID + ttl
for _, c := range id {
if c == '1' {
parity++
}
}
if parity%2 == 0 {
id += "1"
} else {
id += "0"
}
res, _ := strconv.ParseInt(id, 2, 64)
return int(res)
}
func reverseID(id string) (int64, int64, error) {
if len(id) < 16 {
return 0, 0, errors.New("err")
}
ttl, err := strconv.ParseInt(id[9:15], 2, 32)
if err != nil {
return 0, 0, err
}
//process ID
processID, _ := strconv.ParseInt(id[2:9], 2, 32)
parity := 0
for i := 0; i < len(id)-1; i++ {
if id[i] == '1' {
parity++
}
}
if parity%2 == 1 {
if id[len(id)-1] == '0' {
// fmt.Println("Parity check passed.")
} else {
// fmt.Println("Parity check failed.")
return 0, 0, errors.New("err")
}
} else {
if id[len(id)-1] == '1' {
// fmt.Println("Parity check passed.")
} else {
// fmt.Println("Parity check failed.")
return 0, 0, errors.New("err")
}
}
return processID, ttl, nil
}
func (t *ICMPTracer) send(ttl int) error {
defer t.wg.Done()
if t.final != -1 && ttl > t.final {
return nil
}
id := gernerateID(ttl)
// log.Println("发送的", id)
icmpHeader := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff,
ID: id,
Data: []byte("HELLO-R-U-THERE"),
Seq: ttl,
},
}
@@ -144,11 +272,10 @@ func (t *ICMPTracer) send(ttl int) error {
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
log.Fatal(err)
}
select {
case <-t.ctx.Done():
return nil
case h := <-t.resCh:
case h := <-t.inflightRequest[ttl]:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil
@@ -156,6 +283,7 @@ func (t *ICMPTracer) send(ttl int) error {
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
@@ -173,7 +301,6 @@ func (t *ICMPTracer) send(ttl int) error {
h.fetchIPData(t.Config)
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && ttl > t.final {
return nil
@@ -186,6 +313,7 @@ func (t *ICMPTracer) send(ttl int) error {
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
return nil

View File

@@ -5,6 +5,7 @@ import (
"log"
"net"
"os"
"strconv"
"sync"
"time"
@@ -15,16 +16,47 @@ import (
type ICMPTracerv6 struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
resCh chan Hop
icmpListen net.PacketConn
final int
finalLock sync.Mutex
wg sync.WaitGroup
res Result
ctx context.Context
resCh chan Hop
inflightRequest map[int]chan Hop
inflightRequestRWLock sync.RWMutex
icmpListen net.PacketConn
final int
finalLock sync.Mutex
}
func (t *ICMPTracerv6) PrintFunc() {
// defer t.wg.Done()
var ttl = t.Config.BeginHop - 1
for {
if t.AsyncPrinter != nil {
t.AsyncPrinter(&t.res)
}
// 接收的时候检查一下是不是 3 跳都齐了
if len(t.res.Hops)-1 > ttl {
if len(t.res.Hops[ttl]) == t.NumMeasurements {
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, ttl)
}
ttl++
if ttl == t.final {
return
}
}
}
<-time.After(200 * time.Millisecond)
}
}
func (t *ICMPTracerv6) Execute() (*Result, error) {
t.inflightRequestRWLock.Lock()
t.inflightRequest = make(map[int]chan Hop)
t.inflightRequestRWLock.Unlock()
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
@@ -44,26 +76,59 @@ func (t *ICMPTracerv6) Execute() (*Result, error) {
t.final = -1
go t.listenICMP()
go t.PrintFunc()
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
t.inflightRequestRWLock.Lock()
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
t.inflightRequestRWLock.Unlock()
if t.final != -1 && ttl > t.final {
break
}
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(ttl)
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
}
// 一组TTL全部退出收到应答或者超时终止以后再进行下一个TTL的包发送
t.wg.Wait()
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, ttl-1)
}
<-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)
// if t.AsyncPrinter != nil {
// t.AsyncPrinter(&t.res)
// }
// }
t.wg.Wait()
t.res.reduce(t.final)
if t.final != -1 {
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, t.final-1)
}
} else {
for i := 0; i < t.NumMeasurements; i++ {
t.res.add(Hop{
Success: false,
Address: nil,
TTL: 30,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
if t.RealtimePrinter != nil {
t.RealtimePrinter(&t.res, t.MaxHops-1)
}
}
t.res.reduce(t.final)
return &t.res, nil
}
@@ -79,46 +144,98 @@ func (t *ICMPTracerv6) listenICMP() {
if msg.N == nil {
continue
}
dstip := net.IP(msg.Msg[32:48])
if binary.BigEndian.Uint16(msg.Msg[52:54]) != uint16(os.Getpid()&0xffff) {
// // 如果类型为应答消息且应答消息包的进程ID与主进程相同时不跳过
if binary.BigEndian.Uint16(msg.Msg[52:54]) != 0 {
continue
} else {
if dstip.String() != "::" {
continue
}
if msg.Peer.String() != t.DestIP.String() {
continue
}
}
}
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
if msg.Msg[0] == 129 {
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
// log.Println(msg.Peer)
switch rm.Type {
case ipv6.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
case ipv6.ICMPTypeEchoReply:
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
default:
// log.Println("received icmp message of unknown type", rm.Type)
echoReply, ok := rm.Body.(*icmp.Echo)
if ok {
ttl := echoReply.Seq // This is the TTL value
if ttl > 100 {
continue
}
if msg.Peer.String() == t.DestIP.String() {
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, ttl)
}
}
}
packet_id := strconv.FormatInt(int64(binary.BigEndian.Uint16(msg.Msg[52:54])), 2)
if process_id, ttl, err := reverseID(packet_id); err == nil {
if process_id == int64(os.Getpid()&0x7f) {
dstip := net.IP(msg.Msg[32:48])
// 无效包本地环回包
if dstip.String() == "::" {
continue
}
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
// 匹配再继续解析包,否则直接丢弃
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv6.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data, int(ttl))
case ipv6.ICMPTypeEchoReply:
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, int(ttl))
default:
// log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
// dstip := net.IP(msg.Msg[32:48])
// if binary.BigEndian.Uint16(msg.Msg[52:54]) != uint16(os.Getpid()&0xffff) {
// // // 如果类型为应答消息且应答消息包的进程ID与主进程相同时不跳过
// if binary.BigEndian.Uint16(msg.Msg[52:54]) != 0 {
// continue
// } else {
// if dstip.String() != "::" {
// continue
// }
// if msg.Peer.String() != t.DestIP.String() {
// continue
// }
// }
// }
// if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
// rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
// if err != nil {
// log.Println(err)
// continue
// }
// // log.Println(msg.Peer)
// switch rm.Type {
// case ipv6.ICMPTypeTimeExceeded:
// t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
// case ipv6.ICMPTypeEchoReply:
// t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
// default:
// // log.Println("received icmp message of unknown type", rm.Type)
// }
// }
}
}
}
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
t.resCh <- Hop{
Success: true,
Address: msg.Peer,
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
t.inflightRequestRWLock.RLock()
defer t.inflightRequestRWLock.RUnlock()
if _, ok := t.inflightRequest[ttl]; ok {
t.inflightRequest[ttl] <- Hop{
Success: true,
Address: msg.Peer,
}
}
}
@@ -127,12 +244,14 @@ func (t *ICMPTracerv6) send(ttl int) error {
if t.final != -1 && ttl > t.final {
return nil
}
id := gernerateID(ttl)
icmpHeader := icmp.Message{
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff,
ID: id,
Data: []byte("HELLO-R-U-THERE"),
Seq: ttl,
},
}
@@ -157,7 +276,7 @@ func (t *ICMPTracerv6) send(ttl int) error {
select {
case <-t.ctx.Done():
return nil
case h := <-t.resCh:
case h := <-t.inflightRequest[ttl]:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil

View File

@@ -60,7 +60,10 @@ func (t *TCPTracer) Execute() (*Result, error) {
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.inflightRequestLock.Lock()
t.inflightRequest = make(map[int]chan Hop)
t.inflightRequestLock.Unlock()
t.final = -1
go t.listenICMP()
@@ -90,7 +93,7 @@ func (t *TCPTracer) Execute() (*Result, error) {
if t.AsyncPrinter != nil {
for {
t.AsyncPrinter(&t.res)
time.Sleep(50 * time.Millisecond)
time.Sleep(200 * time.Millisecond)
}
}
@@ -161,7 +164,7 @@ func (t *TCPTracer) listenTCP() {
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
// 取得目标主机的Sequence Number
t.inflightRequestLock.Lock()
if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok {
// 最后一跳
ch <- Hop{
@@ -169,6 +172,7 @@ func (t *TCPTracer) listenTCP() {
Address: msg.Peer,
}
}
t.inflightRequestLock.Unlock()
}
}
}

View File

@@ -1,7 +1,7 @@
package trace
import (
"log"
"encoding/binary"
"math"
"math/rand"
"net"
@@ -39,14 +39,14 @@ func (t *TCPTracerv6) Execute() (*Result, error) {
return &t.res, ErrTracerouteExecuted
}
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
log.Println(util.LocalIPPort(t.DestIP))
t.SrcIP, _ = util.LocalIPPortv6(t.DestIP)
// log.Println(util.LocalIPPortv6(t.DestIP))
var err error
t.tcp, err = net.ListenPacket("ip6:tcp", t.SrcIP.String())
if err != nil {
return nil, err
}
t.icmp, err = icmp.ListenPacket("ip6:53", "::")
t.icmp, err = icmp.ListenPacket("ip6:58", "::")
if err != nil {
return &t.res, err
}
@@ -64,14 +64,36 @@ func (t *TCPTracerv6) Execute() (*Result, error) {
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
for ttl := 1; ttl <= t.MaxHops; ttl++ {
// 如果到达最终跳,则退出
if t.final != -1 && ttl > t.final {
break
}
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(ttl)
}
if t.RealtimePrinter != nil {
// 对于实时模式应该按照TTL进行并发请求
t.wg.Wait()
t.RealtimePrinter(&t.res, ttl-1)
}
time.Sleep(1 * time.Millisecond)
}
go func() {
if t.AsyncPrinter != nil {
for {
t.AsyncPrinter(&t.res)
time.Sleep(200 * time.Millisecond)
}
}
}()
if t.RealtimePrinter == nil {
t.wg.Wait()
}
t.wg.Wait()
t.res.reduce(t.final)
return &t.res, nil
@@ -85,20 +107,21 @@ func (t *TCPTracerv6) listenICMP() {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
// log.Println(msg)
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(53, msg.Msg[:*msg.N])
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
// log.Println(err)
continue
}
log.Println(msg.Peer)
switch rm.Type {
case ipv6.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
t.handleICMPMessage(msg)
case ipv6.ICMPTypeDestinationUnreachable:
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
t.handleICMPMessage(msg)
default:
//log.Println("received icmp message of unknown type", rm.Type)
}
@@ -118,6 +141,8 @@ func (t *TCPTracerv6) listenTCP() {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
// log.Println(msg)
// return
if msg.N == nil {
continue
}
@@ -144,23 +169,21 @@ func (t *TCPTracerv6) listenTCP() {
}
}
func (t *TCPTracerv6) handleICMPMessage(msg ReceivedMessage, data []byte) {
header, err := util.GetICMPResponsePayload(data)
if err != nil {
return
}
sequenceNumber := util.GetTCPSeq(header)
func (t *TCPTracerv6) handleICMPMessage(msg ReceivedMessage) {
var sequenceNumber = binary.BigEndian.Uint32(msg.Msg[52:56])
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
ch, ok := t.inflightRequest[int(sequenceNumber)]
if !ok {
return
}
// log.Println("发送数据", sequenceNumber)
ch <- Hop{
Success: true,
Address: msg.Peer,
}
// log.Println("发送成功")
}
func (t *TCPTracerv6) send(ttl int) error {
@@ -176,7 +199,7 @@ func (t *TCPTracerv6) send(ttl int) error {
}
// 随机种子
r := rand.New(rand.NewSource(time.Now().UnixNano()))
_, srcPort := util.LocalIPPort(t.DestIP)
_, srcPort := util.LocalIPPortv6(t.DestIP)
ipHeader := &layers.IPv6{
SrcIP: t.SrcIP,
DstIP: t.DestIP,
@@ -185,6 +208,7 @@ func (t *TCPTracerv6) send(ttl int) error {
}
// 使用Uint16兼容32位系统防止在rand的时候因使用int32而溢出
sequenceNumber := uint32(r.Intn(math.MaxUint16))
tcpHeader := &layers.TCP{
SrcPort: layers.TCPPort(srcPort),
DstPort: layers.TCPPort(t.DestPort),
@@ -212,6 +236,7 @@ func (t *TCPTracerv6) send(ttl int) error {
if _, err := t.tcp.WriteTo(buf.Bytes(), &net.IPAddr{IP: t.DestIP}); err != nil {
return err
}
// log.Println(ttl, sequenceNumber)
t.inflightRequestLock.Lock()
hopCh := make(chan Hop)
t.inflightRequest[int(sequenceNumber)] = hopCh

View File

@@ -27,6 +27,11 @@ type Config struct {
Quic bool
IPGeoSource ipgeo.Source
RDns bool
AlwaysWaitRDNS bool
PacketInterval int
TTLInterval int
Lang string
DN42 bool
RealtimePrinter func(res *Result, ttl int)
AsyncPrinter func(res *Result)
}
@@ -74,8 +79,8 @@ func Traceroute(method Method, config Config) (*Result, error) {
if config.DestIP.To4() != nil {
tracer = &TCPTracer{Config: config}
} else {
// tracer = &TCPTracerv6{Config: config}
return nil, errors.New("IPv6 TCP Traceroute is not supported")
tracer = &TCPTracerv6{Config: config}
// return nil, errors.New("IPv6 TCP Traceroute is not supported")
}
default:
return &Result{}, ErrInvalidMethod
@@ -113,17 +118,90 @@ type Hop struct {
RTT time.Duration
Error error
Geo *ipgeo.IPGeoData
Lang string
}
func (h *Hop) fetchIPData(c Config) (err error) {
if c.RDns && h.Hostname == "" {
ptr, err := net.LookupAddr(h.Address.String())
if err == nil && len(ptr) > 0 {
h.Hostname = ptr[0]
// DN42
if c.DN42 {
var ip string
// 首先查找 PTR 记录
r, err := net.LookupAddr(h.Address.String())
if err == nil && len(r) > 0 {
h.Hostname = r[0][:len(r[0])-1]
ip = h.Address.String() + "," + h.Hostname
}
h.Geo, err = c.IPGeoSource(ip)
return nil
}
if c.IPGeoSource != nil && h.Geo == nil {
h.Geo, err = c.IPGeoSource(h.Address.String())
// Debug Area
// c.AlwaysWaitRDNS = true
// Initialize a rDNS Channel
rDNSChan := make(chan []string)
fetchDoneChan := make(chan bool)
if c.RDns && h.Hostname == "" {
// Create a rDNS Query go-routine
go func() {
r, err := net.LookupAddr(h.Address.String())
if err != nil {
// No PTR Record
rDNSChan <- nil
} else {
// One PTR Record is found
rDNSChan <- r
}
}()
}
// Create Data Fetcher go-routine
go func() {
// Start to fetch IP Geolocation data
if c.IPGeoSource != nil && h.Geo == nil {
res := false
h.Lang = c.Lang
h.Geo, res = ipgeo.Filter(h.Address.String())
if !res {
h.Geo, err = c.IPGeoSource(h.Address.String())
}
}
// Fetch Done
fetchDoneChan <- true
}()
// Select Close Flag
var selectClose bool
if !c.AlwaysWaitRDNS {
select {
case <-fetchDoneChan:
// When fetch done signal recieved, stop waiting PTR record
case ptr := <-rDNSChan:
// process result
if err == nil && len(ptr) > 0 {
h.Hostname = ptr[0][:len(ptr[0])-1]
}
selectClose = true
}
} else {
select {
case ptr := <-rDNSChan:
// process result
if err == nil && len(ptr) > 0 {
h.Hostname = ptr[0]
}
// 1 second timeout
case <-time.After(time.Second * 1):
}
selectClose = true
}
// When Select Close, fetchDoneChan Reciever will also be closed
if selectClose {
// New a reciever to prevent channel congestion
<-fetchDoneChan
}
return
}

View File

@@ -73,7 +73,7 @@ func (t *UDPTracer) Execute() (*Result, error) {
if t.AsyncPrinter != nil {
for {
t.AsyncPrinter(&t.res)
time.Sleep(50 * time.Millisecond)
time.Sleep(200 * time.Millisecond)
}
}
}()
@@ -121,8 +121,8 @@ func (t *UDPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
return
}
srcPort := util.GetUDPSrcPort(header)
//t.inflightRequestLock.Lock()
//defer t.inflightRequestLock.Unlock()
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
ch, ok := t.inflightRequest[int(srcPort)]
if !ok {
return

23
util/common.go Normal file
View File

@@ -0,0 +1,23 @@
package util
import "net"
func Dnspod() *net.Resolver {
return newDoTResolver("dot.pub", "dot.pub:853")
}
func Aliyun() *net.Resolver {
return newDoTResolver("dns.alidns.com", "dns.alidns.com:853")
}
func DNSSB() *net.Resolver {
return newDoTResolver("45.11.45.11", "dot.sb:853")
}
func Cloudflare() *net.Resolver {
return newDoTResolver("one.one.one.one", "one.one.one.one:853")
}
func Google() *net.Resolver {
return newDoTResolver("dns.google", "dns.google:853")
}

13
util/dns_test.go Normal file
View File

@@ -0,0 +1,13 @@
package util
import (
"context"
"fmt"
"testing"
)
func TestDNS(t *testing.T) {
resolver := DNSSB()
ips, _ := resolver.LookupHost(context.Background(), "www.google.com")
fmt.Println(ips)
}

32
util/dot.go Normal file
View File

@@ -0,0 +1,32 @@
package util
import (
"context"
"crypto/tls"
"net"
"time"
)
func newDoTResolver(serverName string, addrs string) *net.Resolver {
d := &net.Dialer{
// 设置超时时间
Timeout: 1000 * time.Millisecond,
}
tls_config := &tls.Config{
// 设置 TLS Server Name 以确保证书能和域名对应
ServerName: serverName,
}
return &net.Resolver{
// 指定使用 Go Build-in 的 DNS Resolver 来解析
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
conn, err := d.DialContext(ctx, "tcp", addrs)
if err != nil {
return nil, err
}
return tls.Client(conn, tls_config), nil
},
}
}

12
util/udp.go Normal file
View File

@@ -0,0 +1,12 @@
package util
import (
"net"
)
func newUDPResolver() *net.Resolver {
return &net.Resolver{
// 指定使用 Go Build-in 的 DNS Resolver 来解析
PreferGo: true,
}
}

View File

@@ -1,6 +1,7 @@
package util
import (
"context"
"fmt"
"log"
"net"
@@ -27,41 +28,66 @@ func LocalIPPort(dstip net.IP) (net.IP, int) {
return nil, -1
}
func DomainLookUp(host string, ipv4Only bool) net.IP {
ips, err := net.LookupIP(host)
func LocalIPPortv6(dstip net.IP) (net.IP, int) {
serverAddr, err := net.ResolveUDPAddr("udp", "["+dstip.String()+"]:12345")
if err != nil {
log.Fatal(err)
}
// We don't actually connect to anything, but we can determine
// based on our destination ip what source ip we should use.
if con, err := net.DialUDP("udp", nil, serverAddr); err == nil {
defer con.Close()
if udpaddr, ok := con.LocalAddr().(*net.UDPAddr); ok {
return udpaddr.IP, udpaddr.Port
}
}
return nil, -1
}
func DomainLookUp(host string, ipv4Only bool, dotServer string) net.IP {
var (
r *net.Resolver
ips []net.IP
)
switch dotServer {
case "dnssb":
r = DNSSB()
case "aliyun":
r = Aliyun()
case "dnspod":
r = Dnspod()
case "google":
r = Google()
case "cloudflare":
r = Cloudflare()
default:
r = newUDPResolver()
}
ips_str, err := r.LookupHost(context.Background(), host)
for _, v := range ips_str {
ips = append(ips, net.ParseIP(v))
}
if err != nil {
fmt.Println("Domain " + host + " Lookup Fail.")
os.Exit(1)
}
var ipSlice = []net.IP{}
var ipv6Flag = false
for _, ip := range ips {
if ipv4Only {
// 仅返回ipv4的ip
if ip.To4() != nil {
ipSlice = append(ipSlice, ip)
} else {
ipv6Flag = true
}
} else {
ipSlice = append(ipSlice, ip)
}
}
if ipv6Flag {
fmt.Println("[Info] IPv6 TCP/UDP Traceroute is not supported right now.")
if len(ipSlice) == 0 {
fmt.Println("[Info] IPv6 UDP Traceroute is not supported right now.")
if len(ips) == 0 {
os.Exit(0)
}
}
if len(ipSlice) == 1 {
return ipSlice[0]
if len(ips) == 1 {
return ips[0]
} else {
fmt.Println("Please Choose the IP You Want To TraceRoute")
for i, ip := range ipSlice {
for i, ip := range ips {
fmt.Fprintf(color.Output, "%s %s\n",
color.New(color.FgHiYellow, color.Bold).Sprintf("%d.", i),
color.New(color.FgWhite, color.Bold).Sprintf("%s", ip),
@@ -70,10 +96,18 @@ func DomainLookUp(host string, ipv4Only bool) net.IP {
var index int
fmt.Printf("Your Option: ")
fmt.Scanln(&index)
if index >= len(ipSlice) || index < 0 {
if index >= len(ips) || index < 0 {
fmt.Println("Your Option is invalid")
os.Exit(3)
}
return ipSlice[index]
return ips[index]
}
}
func GetenvDefault(key, defVal string) string {
val, ok := os.LookupEnv(key)
if ok {
return val
}
return defVal
}

View File

@@ -1,14 +1,19 @@
package wshandle
import (
"crypto/tls"
"log"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/xgadget-lab/nexttrace/util"
)
type WsConn struct {
@@ -24,6 +29,8 @@ type WsConn struct {
}
var wsconn *WsConn
var hostP = util.GetenvDefault("NEXTTRACE_HOSTPORT", "api.leo.moe")
var host, port, fast_ip string
func (c *WsConn) keepAlive() {
go func() {
@@ -31,7 +38,12 @@ func (c *WsConn) keepAlive() {
for {
<-time.After(time.Second * 54)
if c.Connected {
c.Conn.WriteMessage(websocket.TextMessage, []byte("ping"))
err := c.Conn.WriteMessage(websocket.TextMessage, []byte("ping"))
if err != nil {
log.Println(err)
c.Connected = false
return
}
}
}
}()
@@ -70,12 +82,12 @@ func (c *WsConn) messageSendHandler() {
// 循环监听发送
select {
case <-c.Done:
log.Println("发送协程已经退出")
log.Println("go-routine has been returned")
return
case t := <-c.MsgSendCh:
// log.Println(t)
if !c.Connected {
c.MsgReceiveCh <- `{"ip":"` + t + `", "asnumber":"API服务端异常"}`
c.MsgReceiveCh <- `{"ip":"` + t + `", "asnumber":"API Server Error"}`
} else {
err := c.Conn.WriteMessage(websocket.TextMessage, []byte(t))
if err != nil {
@@ -104,10 +116,16 @@ func (c *WsConn) messageSendHandler() {
}
func (c *WsConn) recreateWsConn() {
u := url.URL{Scheme: "wss", Host: "api.leo.moe", Path: "/v2/ipGeoWs"}
u := url.URL{Scheme: "wss", Host: fast_ip + ":" + port, Path: "/v2/ipGeoWs"}
// log.Printf("connecting to %s", u.String())
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
requestHeader := http.Header{
"Host": []string{host},
}
dialer := websocket.DefaultDialer
dialer.TLSClientConfig = &tls.Config{
ServerName: host,
}
ws, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader)
c.Conn = ws
if err != nil {
log.Println("dial:", err)
@@ -128,11 +146,47 @@ func createWsConn() *WsConn {
// 设置终端中断通道
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
// 解析域名
hostArr := strings.Split(hostP, ":")
// 判断是否有指定端口
if len(hostArr) > 1 {
// 判断是否为 IPv6
if strings.HasPrefix(hostP, "[") {
tmp := strings.Split(hostP, "]")
host = tmp[0]
host = host[1:]
if port = tmp[1]; port != "" {
port = port[1:]
}
} else {
host, port = hostArr[0], hostArr[1]
}
} else {
host = hostP
}
if port == "" {
// 默认端口
port = "443"
}
// 默认配置完成,开始寻找最优 IP
fast_ip = GetFastIP(host, port)
u := url.URL{Scheme: "wss", Host: "api.leo.moe", Path: "/v2/ipGeoWs"}
// 如果 host 是一个 IP 使用默认域名
if valid := net.ParseIP(host); valid != nil {
host = "api.leo.moe"
}
// 判断是否是一个 IP
requestHeader := http.Header{
"Host": []string{host},
}
dialer := websocket.DefaultDialer
dialer.TLSClientConfig = &tls.Config{
ServerName: host,
}
u := url.URL{Scheme: "wss", Host: fast_ip + ":" + port, Path: "/v2/ipGeoWs"}
// log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
c, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader)
wsconn = &WsConn{
Conn: c,

71
wshandle/latency.go Normal file
View File

@@ -0,0 +1,71 @@
package wshandle
import (
"fmt"
"log"
"net"
"strings"
"time"
"github.com/fatih/color"
)
var (
result string
results = make(chan string)
)
func GetFastIP(domain string, port string) string {
ips, err := net.LookupIP(domain)
if err != nil {
log.Fatal("DNS resolution failed, please check your system DNS Settings")
return ""
}
for _, ip := range ips {
go checkLatency(ip.String(), port)
}
select {
case result = <-results:
case <-time.After(1 * time.Second):
}
if result == "" {
log.Fatal("IP connection has been timeout, please check your network")
}
res := strings.Split(result, "-")
if len(ips) > 1 {
_, _ = fmt.Fprintf(color.Output, "%s prefered API IP - %s - %s\n",
color.New(color.FgWhite, color.Bold).Sprintf("[NextTrace API]"),
color.New(color.FgGreen, color.Bold).Sprintf("%s", res[0]),
color.New(color.FgCyan, color.Bold).Sprintf("%sms", res[1]),
)
}
return res[0]
}
func checkLatency(ip string, port string) {
start := time.Now()
if !strings.Contains(ip, ".") {
ip = "[" + ip + "]"
}
conn, err := net.DialTimeout("tcp", ip+":"+port, time.Second*1)
if err != nil {
return
}
defer func(conn net.Conn) {
err := conn.Close()
if err != nil {
return
}
}(conn)
if result == "" {
result = fmt.Sprintf("%s-%.2f", ip, float64(time.Since(start))/float64(time.Millisecond))
results <- result
return
}
}

9
wshandle/latency_test.go Normal file
View File

@@ -0,0 +1,9 @@
package wshandle
import (
"testing"
)
func TestGetFastIP(t *testing.T) {
GetFastIP("api.leo.moe", "443")
}