mirror of
https://github.com/nxtrace/NTrace-core.git
synced 2025-08-12 06:26:39 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d561063a7c | ||
|
|
473ce3c5f2 | ||
|
|
a1783e3563 | ||
|
|
7b9912f23f | ||
|
|
fdeaf112f5 | ||
|
|
07a2aac7c7 | ||
|
|
08f8daf9ce | ||
|
|
7872e9ee0f | ||
|
|
991f66cfe4 | ||
|
|
5b91fac860 | ||
|
|
62e877e248 | ||
|
|
9e4b2ae577 | ||
|
|
b02572d6ff | ||
|
|
e5742e1603 | ||
|
|
70a727bee6 | ||
|
|
1261e243f2 | ||
|
|
49ce0cba8e | ||
|
|
9764533c8e | ||
|
|
d752385c29 | ||
|
|
bf005fb37a | ||
|
|
8dc5960d98 | ||
|
|
ef104673b8 | ||
|
|
46545bd8d9 | ||
|
|
a4124b50aa | ||
|
|
b09d4bab74 | ||
|
|
09e493ebc3 | ||
|
|
3d665ee03c | ||
|
|
493328c7be | ||
|
|
e2d778c34d | ||
|
|
bf54b61eb8 | ||
|
|
9cec64b207 | ||
|
|
23006acd9f | ||
|
|
0210c94651 | ||
|
|
0ccdae851d | ||
|
|
ddffdb389a | ||
|
|
c533dd34ab | ||
|
|
e690ad85d9 | ||
|
|
00e4f9391e | ||
|
|
2084f2c316 | ||
|
|
c9ebf7465a | ||
|
|
f11f9c8234 | ||
|
|
36315c6d9d | ||
|
|
548839f564 |
@@ -5,7 +5,7 @@ set -e
|
||||
DIST_PREFIX="nexttrace"
|
||||
DEBUG_MODE=${2}
|
||||
TARGET_DIR="dist"
|
||||
PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips openbsd/amd64 openbsd/arm64 freebsd/amd64 freebsd/arm64"
|
||||
PLATFORMS="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm64 linux/mips windows/amd64 windows/arm64 openbsd/amd64 openbsd/arm64 freebsd/amd64 freebsd/arm64"
|
||||
|
||||
BUILD_VERSION="$(git describe --tags --always)"
|
||||
BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
@@ -15,6 +15,7 @@ rm -rf ${TARGET_DIR}
|
||||
mkdir ${TARGET_DIR}
|
||||
|
||||
for pl in ${PLATFORMS}; do
|
||||
export CGO_ENABLED=0
|
||||
export GOOS=$(echo ${pl} | cut -d'/' -f1)
|
||||
export GOARCH=$(echo ${pl} | cut -d'/' -f2)
|
||||
export TARGET=${TARGET_DIR}/${DIST_PREFIX}_${GOOS}_${GOARCH}
|
||||
@@ -37,7 +38,7 @@ for pl in ${PLATFORMS}; do
|
||||
-w -s"
|
||||
fi
|
||||
done
|
||||
|
||||
export CGO_ENABLED=0
|
||||
export GOOS='linux'
|
||||
export GOARCH='arm'
|
||||
export GOARM='7'
|
||||
|
||||
39
.github/workflows/build.yml
vendored
39
.github/workflows/build.yml
vendored
@@ -41,44 +41,11 @@ jobs:
|
||||
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
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GT_Token }}
|
||||
publish-new-formula:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Runs a single command using the runners shell
|
||||
- name: config git
|
||||
run: |
|
||||
git config --global user.email "${{ secrets.git_mail }}"
|
||||
git config --global user.name "${{ secrets.git_name }}"
|
||||
- name: Clone repo
|
||||
run: |
|
||||
git clone https://github.com/xgadget-lab/homebrew-nexttrace.git
|
||||
- name: Exec scipt
|
||||
run: |
|
||||
cd homebrew-nexttrace
|
||||
bash genFormula.sh
|
||||
# - name: setup SSH keys and known_hosts
|
||||
# run: |
|
||||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
# ssh-agent -a $SSH_AUTH_SOCK > /dev/null
|
||||
# ssh-add - <<< "${{ secrets.ID_RSA }}"
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- name: Git Push
|
||||
run: |
|
||||
cd homebrew-nexttrace
|
||||
git commit -am 'Publish a new version with Formula'
|
||||
git remote set-url origin https://${{ secrets.gt_token }}@github.com/xgadget-lab/homebrew-nexttrace.git
|
||||
git push
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
GITHUB_TOKEN: ${{ secrets.GT_Token }}
|
||||
3
.github/workflows/publishNewFormula.yml
vendored
3
.github/workflows/publishNewFormula.yml
vendored
@@ -3,6 +3,7 @@ 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
|
||||
@@ -39,7 +40,7 @@ jobs:
|
||||
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
|
||||
git push || 1
|
||||
# env:
|
||||
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
|
||||
99
README.md
99
README.md
@@ -12,20 +12,24 @@ An open source visual routing tool that pursues light weight, developed using Go
|
||||
|
||||
NextTrace has a total of 2 versions, the Lite version focusing on lightweight and the [Enhanced version](#nexttrace-enhanced) which is more enthusiast-oriented.
|
||||
|
||||
PS: Our Lite version does not provide OSM based geolocation visualization, we provide this parameter in the enhanced version if needed.
|
||||
|
||||
## How To Use
|
||||
|
||||
### Automated Installation
|
||||
|
||||
```bash
|
||||
# Linux one-click install script
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/xgadget-lab/nexttrace/main/nt_install.sh)
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)
|
||||
|
||||
# macOS brew install command
|
||||
brew tap xgadget-lab/nexttrace && brew install nexttrace
|
||||
```
|
||||
|
||||
Windows users please go to [Release Page](https://github.com/sjlleo/nexttrace/releases/latest) directly and download exe file.
|
||||
|
||||
- `Release` provides compiled executables for many systems and architectures, if not, you can compile it yourself.
|
||||
- Some of the necessary dependencies of this project are not fully implemented in `Golang` on `Windows`, so currently `NextTrace` is not available on `Windows` platform.
|
||||
- Some of the necessary dependencies of this project are not fully implemented in `Golang` on `Windows`, so currently `NextTrace` is experimental on `Windows` platform.
|
||||
|
||||
### Get Started
|
||||
|
||||
@@ -52,6 +56,17 @@ nexttrace -f
|
||||
nexttrace -f -T
|
||||
```
|
||||
|
||||
`NextTrace` already supports route tracing for specified Network Devices
|
||||
|
||||
```bash
|
||||
# Use eth0 network interface
|
||||
nexttrace -D 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` can also use `TCP` and `UDP` protocols to perform `Traceroute` requests, but these protocols only supports `IPv4` now
|
||||
|
||||
```bash
|
||||
@@ -67,7 +82,7 @@ nexttrace -U 1.0.0.1
|
||||
nexttrace -U -p 53 1.0.0.1
|
||||
```
|
||||
|
||||
`NextTrace` also supports some advanced functions, such as IP reverse parsing, concurrent probe packet count control, mode switching, etc.
|
||||
`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
|
||||
@@ -76,8 +91,11 @@ nexttrace -q 2 www.hkix.net
|
||||
# No concurrent probe packets, only one probe packet is sent at a time
|
||||
nexttrace -r 1 www.hkix.net
|
||||
|
||||
# Turn on the IP reverse parsing function, which is very helpful in positioning the IPv6 backbone network
|
||||
nexttrace -rdns www.bbix.net
|
||||
# Start Trace with TTL of 5, end at TTL of 10
|
||||
nexttrace -b 5 -m 10 www.decix.net
|
||||
|
||||
# Turn off the IP reverse parsing function
|
||||
nexttrace -n www.bbix.net
|
||||
|
||||
# Feature: print Route-Path diagram
|
||||
# Route-Path diagram example:
|
||||
@@ -107,14 +125,16 @@ nexttrace -d IP.SB
|
||||
```bash
|
||||
Example:
|
||||
nexttrace -d IPInsight -m 20 -p 443 -q 5 -r 20 -rdns 1.1.1.1
|
||||
nexttrace -T -q 2 -r 1 -rdns -table -report 2001:4860:4860::8888
|
||||
nexttrace -T -q 2 -r 1 -table -report 2001:4860:4860::8888
|
||||
```
|
||||
|
||||
### IP Database
|
||||
|
||||
The IP database is set to our own API service by default. If we encounter abuse, we may choose to close it.
|
||||
#### We use [bgp.tools](https://bgp.tools) as a data provider for routing tables.
|
||||
|
||||
We will also open-source the source code of the server in the near future, therefore you can also build your own API server according to the source code of the project by then.
|
||||
NextTrace BackEnd is now open-source.
|
||||
|
||||
https://github.com/sjlleo/nexttrace-backend
|
||||
|
||||
All NextTrace IP geolocation `API DEMO` can refer to [here](https://github.com/xgadget-lab/nexttrace/blob/main/ipgeo/)
|
||||
|
||||
@@ -126,28 +146,30 @@ Usage of nexttrace:
|
||||
Options:
|
||||
-T Use TCP SYN for tracerouting (default port is 80)
|
||||
-U Use UDP Package for tracerouting (default port is 53 in UDP)
|
||||
-V Check Version
|
||||
-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)
|
||||
-rdns
|
||||
Set whether rDNS will be display
|
||||
-table
|
||||
Output trace results as table
|
||||
-report
|
||||
Route Path
|
||||
-table
|
||||
Output trace results as table
|
||||
```
|
||||
|
||||
## Project screenshot
|
||||
|
||||

|
||||

|
||||
|
||||
## NextTrace Enhanced
|
||||
|
||||
@@ -157,6 +179,8 @@ The `Enhanced` version supports many functions that the `lite` version does not
|
||||
|
||||
https://github.com/OwO-Network/nexttrace-enhanced
|
||||
|
||||
#
|
||||
|
||||
## FAQ Frequently Asked Questions
|
||||
|
||||
If you encounter problems while installing or using it, we do not recommend you to choose creating an `issue` as a preference
|
||||
@@ -166,8 +190,16 @@ Here is our recommended troubleshooting process:
|
||||
1. Check if it is already in FAQ -> [Go to Github Wiki](https://github.com/xgadget-lab/nexttrace/wiki/FAQ---%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94)
|
||||
2. Suspected bug or feature suggestion -> [Go to Github Issues](https://github.com/xgadget-lab/nexttrace/issues)
|
||||
|
||||
## JetBrain Support
|
||||
|
||||
#### This Project uses [JetBrain Open-Source Project License](https://jb.gg/OpenSourceSupport). We Proudly Develop By Goland.
|
||||
|
||||
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/GoLand.png" title="" alt="GoLand logo" width="331">
|
||||
|
||||
## Credits
|
||||
|
||||
BGP.TOOLS provided some data support for this project and we would like to express our sincere gratitude.
|
||||
|
||||
[Vincent Young](https://github.com/missuo) (i@yyt.moe)
|
||||
|
||||
[Sam Sam](https://github.com/samleong123) (samsam123@samsam123.name.my)
|
||||
@@ -176,44 +208,7 @@ Here is our recommended troubleshooting process:
|
||||
|
||||
[waiting4new](https://github.com/waiting4new)
|
||||
|
||||
[FFEE_CO](https://github.com/fkx4-p)
|
||||
|
||||
## IP Database Copyright
|
||||
|
||||
### IPv4 Database
|
||||
|
||||
#### China
|
||||
|
||||
| ISP | Type | Data Source | Proportion |
|
||||
|:---------------------------:|:--------:|:--------------------:|:----------:|
|
||||
| China Telecom/Unicom/Mobile | Backbone | Internet Enthusiasts | 10% |
|
||||
| China Telecom/Unicom/Mobile | Local | Avon Technology | 90% |
|
||||
|
||||
#### WorldWide
|
||||
|
||||
##### Tier 1
|
||||
|
||||
| ISP | Type | Data Source | Proportion |
|
||||
|:-------:|:--------:|:---------------:|:----------:|
|
||||
| Tier 1 | Backbone | IPInfo | 2% |
|
||||
| Tier 1 | Backbone | Avon Technology | 3% |
|
||||
| Tier 1 | Backbone | IPInSight | 5% |
|
||||
| Tier 1 | Local | IPInSight | 90% |
|
||||
|
||||
##### General
|
||||
|
||||
| ISP | Type | Data Source | Proportion |
|
||||
|:------:|:--------:|:-----------:|:----------:|
|
||||
| General | Backbone | IPInSight | 5% |
|
||||
| General | Local | IPInSight | 95% |
|
||||
|
||||
### IPv6 Database
|
||||
|
||||
| ISP | Type | Data Source | Proportion |
|
||||
|:---:|:----:|:----------------:|:----------:|
|
||||
| All | All | IP2Location Lite | 100% |
|
||||
|
||||
This product includes IP2Location LITE data available from <a href="https://lite.ip2location.com">https://lite.ip2location.com</a>.
|
||||
[FFEE_CO](https://github.com/fkx4-p)
|
||||
|
||||
### Others
|
||||
|
||||
|
||||
121
README_zh_CN.md
121
README_zh_CN.md
@@ -10,23 +10,27 @@
|
||||
|
||||
NextTrace 一共有2个版本,专注于轻量的 Lite 版本以及更面向发烧友的 [Enhanced 版本](#nexttrace-enhanced)。
|
||||
|
||||
PS: Lite 版本追求轻量化,并不提供基于高德地图 / OpenStreetMap 的路由可视化功能,如有需要,请使用 Enhanced 版本。
|
||||
|
||||
## How To Use
|
||||
|
||||
### Automated Install
|
||||
|
||||
```bash
|
||||
# Linux 一键安装脚本
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/xgadget-lab/nexttrace/main/nt_install.sh)
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)
|
||||
|
||||
# GHPROXY 镜像(国内使用)
|
||||
bash <(curl -Ls https://ghproxy.com/https://raw.githubusercontent.com/xgadget-lab/nexttrace/main/nt_install.sh)
|
||||
bash <(curl -Ls https://ghproxy.com/https://raw.githubusercontent.com/sjlleo/nexttrace/main/nt_install.sh)
|
||||
|
||||
# macOS brew 安装命令
|
||||
brew tap xgadget-lab/nexttrace && brew install nexttrace
|
||||
```
|
||||
|
||||
Windows 用户请直接前往 [Release](https://github.com/sjlleo/nexttrace/releases/latest) 下载编译后的二进制 exe 文件。
|
||||
|
||||
- `Release`里面为很多系统以及不同架构提供了编译好的二进制可执行文件,如果没有可以自行编译。
|
||||
- 一些本项目的必要依赖在`Windows`上`Golang`底层实现不完全,所以目前`NextTrace`在`Windows`平台不可用。
|
||||
- 一些本项目的必要依赖在`Windows`上`Golang`底层实现不完全,所以目前`NextTrace`在`Windows`平台出于实验性支持阶段。
|
||||
|
||||
### Get Started
|
||||
|
||||
@@ -44,6 +48,7 @@ nexttrace 2606:4700:4700::1111
|
||||
```
|
||||
|
||||
`NextTrace` 现已经支持快速测试,有一次性测试回程路由需求的朋友可以使用
|
||||
|
||||
```bash
|
||||
# 北上广(电信+联通+移动+教育网)IPv4 ICMP 快速测试
|
||||
nexttrace -f
|
||||
@@ -52,6 +57,19 @@ nexttrace -f
|
||||
nexttrace -f -T
|
||||
```
|
||||
|
||||
`NextTrace` 已支持指定网卡进行路由跟踪
|
||||
|
||||
```bash
|
||||
# 请注意 Lite 版本此参数不能和快速测试联用,如有需要请使用 enhanced 版本
|
||||
# 使用 eth0 网卡
|
||||
nexttrace -D 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` 也可以使用`TCP`和`UDP`协议发起`Traceroute`请求,不过目前只支持`IPv4`
|
||||
|
||||
```bash
|
||||
@@ -67,7 +85,7 @@ nexttrace -U 1.0.0.1
|
||||
nexttrace -U -p 53 1.0.0.1
|
||||
```
|
||||
|
||||
`NextTrace`也同样支持一些进阶功能,如 IP 反向解析、并发数控制、模式切换等
|
||||
`NextTrace`也同样支持一些进阶功能,如 TTL 控制、并发数控制、模式切换等
|
||||
|
||||
```bash
|
||||
# 每一跳发送2个探测包
|
||||
@@ -76,8 +94,11 @@ nexttrace -q 2 www.hkix.net
|
||||
# 无并发,每次只发送一个探测包
|
||||
nexttrace -r 1 www.hkix.net
|
||||
|
||||
# 打开IP反向解析功能,在IPv6的骨干网定位辅助有较大帮助
|
||||
nexttrace -rdns www.bbix.net
|
||||
# 从TTL为5开始发送探测包,直到TTL为10结束
|
||||
nexttrace -b 5 -m 10 www.decix.net
|
||||
|
||||
# 关闭IP反向解析功能
|
||||
nexttrace -n www.bbix.net
|
||||
|
||||
# 特色功能:打印Route-Path图
|
||||
# Route-Path图示例:
|
||||
@@ -107,14 +128,16 @@ nexttrace -d IP.SB
|
||||
```bash
|
||||
Example:
|
||||
nexttrace -d IPInsight -m 20 -p 443 -q 5 -r 20 -rdns 1.1.1.1
|
||||
nexttrace -T -q 2 -r 1 -rdns -table -report 2001:4860:4860::8888
|
||||
nexttrace -T -q 2 -r 1 -table -report 2001:4860:4860::8888
|
||||
```
|
||||
|
||||
### IP 数据库
|
||||
|
||||
目前使用的 IP 数据库默认为我们自己搭建的 API 服务,如果后期遇到滥用,我们可能会选择关闭。
|
||||
我们使用[bgp.tools](https://bgp.tools)作为路由表功能的数据提供者。
|
||||
|
||||
我们也会在后期开放服务端源代码,您也可以根据该项目的源码自行搭建属于您的 API 服务器。
|
||||
✨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/)
|
||||
|
||||
@@ -124,31 +147,36 @@ NextTrace 所有的的 IP 地理位置`API DEMO`可以参考[这里](https://git
|
||||
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 Check Version
|
||||
-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)
|
||||
-rdns
|
||||
Set whether rDNS will be display
|
||||
-table
|
||||
Output trace results as table
|
||||
-report
|
||||
Route Path
|
||||
|
||||
-table
|
||||
Output trace results as table
|
||||
```
|
||||
|
||||
## 项目截图
|
||||
|
||||

|
||||

|
||||
|
||||
## NextTrace Enhanced
|
||||
|
||||
@@ -158,17 +186,10 @@ Options:
|
||||
|
||||
https://github.com/OwO-Network/nexttrace-enhanced
|
||||
|
||||
## FAQ 常见问题
|
||||
|
||||
如果你在安装或者使用的时候遇到了问题,我们建议你不要把新建一个 `issue` 作为首选项
|
||||
|
||||
以下是我们推荐的排错流程:
|
||||
|
||||
1. 查看是否为常见问题 -> [前往 Github Wiki](https://github.com/xgadget-lab/nexttrace/wiki/FAQ---%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94)
|
||||
2. 疑似 BUG、或者功能建议 -> [前往 Github Issues](https://github.com/xgadget-lab/nexttrace/issues)
|
||||
|
||||
## Thanks
|
||||
|
||||
BGP.TOOLS 提供了本项目的一些数据支持,在此表示由衷地感谢。
|
||||
|
||||
[Vincent Young](https://github.com/missuo) (i@yyt.moe)
|
||||
|
||||
[Sam Sam](https://github.com/samleong123) (samsam123@samsam123.name.my)
|
||||
@@ -179,56 +200,6 @@ https://github.com/OwO-Network/nexttrace-enhanced
|
||||
|
||||
[FFEE_CO](https://github.com/fkx4-p)
|
||||
|
||||
## IP Database Copyright
|
||||
|
||||
### IPv4 Database
|
||||
|
||||
#### China
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :------------: | :----: | :-------: | :--: |
|
||||
| 电信/联通/移动 | 骨干网 | 网络爱好者 | 10% |
|
||||
| 电信/联通/移动 | 城域网 | 埃文科技 | 90% |
|
||||
|
||||
|
||||
- 参与骨干网维护的朋友都是网络爱好者群体,尽管我们多名志愿者通过自己的网络进行了大量的勘测,但是由于信息不足,依旧存在很多错误。
|
||||
- 对于更高精度的朋友,我们依旧强烈推荐IPIP.NET,他们开发的Besttrace是目前质量最好的路由可视化软件,我们多数爱好者能有今天这样的骨干网初步认知都是归功于他们,在此特表感谢。
|
||||
- 埃文科技最大的优点就是灵活计费,提供小额度的IP地理位置API服务,至少是我们学生党可以承担的起的价格,可惜骨干网数据可靠程度较低,大部分情况下不能直接使用。这些数据目前是通过志愿者一次次路由跟踪,根据TTL批量更正它们,但是容易因多拓扑的情况,把一部分IP标记错了位置(如上海到长春,有上海-石家庄-长春、上海-北京-长春,2个走法,如果直接根据TTL都标记为北京,会把明明应该在石家庄的都标记到北京去了)。
|
||||
|
||||
当然,如果你比较好奇,很想知道埃文科技标注的骨干网地理位置数据长啥样子的话...
|
||||
|
||||

|
||||
|
||||
> 这是江苏南京到安徽合肥的2跳骨干网,第10跳都在南京,第11跳都在安徽,没能标对,第12跳开始就进入合肥的城域网了
|
||||
|
||||
当然,埃文科技的移动骨干网可靠性比电信、联通高很多。对于城域网的IP数据来说,埃文科技错误更少一些,已经足够可以使用,所以对于城域网数据,我们基本上直接使用埃文的了。
|
||||
|
||||
#### WorldWide
|
||||
|
||||
##### Tier 01
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :-----: | :----: | :-------: | :--: |
|
||||
| Tier-01 | 骨干网 | IPInfo | 2% |
|
||||
| Tier-01 | 骨干网 | 埃文科技 | 3% |
|
||||
| Tier-01 | 骨干网 | IPInSight | 5% |
|
||||
| Tier-01 | 城域网 | IPInSight | 90% |
|
||||
|
||||
##### Other ISP
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :----: | :----: | :-------: | :--: |
|
||||
| Others | 骨干网 | IPInSight | 5% |
|
||||
| Others | 城域网 | IPInSight | 95% |
|
||||
|
||||
### IPv6 Database
|
||||
|
||||
| ISP | 类型 | 数据源 | 占比 |
|
||||
| :-: | :--: | :--------------: | :--: |
|
||||
| All | 全部 | IP2Location Lite | 100% |
|
||||
|
||||
This product includes IP2Location LITE data available from <a href="https://lite.ip2location.com">https://lite.ip2location.com</a>.
|
||||
|
||||
### Others
|
||||
|
||||
其他第三方 API 尽管集成在本项目内,但是具体的 TOS 以及 AUP,请详见第三方 API 官网。如遇到 IP 数据错误,也请直接联系他们纠错。
|
||||
|
||||
BIN
asset/nexttrace021.png
Normal file
BIN
asset/nexttrace021.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
@@ -23,6 +23,7 @@ type BackBoneCollection struct {
|
||||
type ISPCollection struct {
|
||||
ISPName string
|
||||
IP string
|
||||
IPv6 string
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -47,6 +48,7 @@ var Beijing = BackBoneCollection{
|
||||
CT163: ISPCollection{
|
||||
ISPName: CT163,
|
||||
IP: "106.37.67.1",
|
||||
IPv6: "240e:40:e002:1:a:3ee3:c00:0",
|
||||
},
|
||||
|
||||
CU169: ISPCollection{
|
||||
@@ -102,7 +104,8 @@ var Guangzhou = BackBoneCollection{
|
||||
Location: "广州",
|
||||
CT163: ISPCollection{
|
||||
ISPName: CT163,
|
||||
IP: "106.37.67.1",
|
||||
IP: "14.116.225.60",
|
||||
IPv6: "240e:f9:8010::3:110:1",
|
||||
},
|
||||
|
||||
CU169: ISPCollection{
|
||||
|
||||
@@ -4,20 +4,36 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/printer"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/tracelog"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
type FastTracer struct {
|
||||
TracerouteMethod trace.Method
|
||||
}
|
||||
|
||||
var oe bool = false
|
||||
|
||||
func (f *FastTracer) tracert(location string, ispCollection ISPCollection) {
|
||||
fp, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
log.SetOutput(fp)
|
||||
log.SetFlags(0)
|
||||
fmt.Printf("%s『%s %s 』%s\n", printer.YELLOW_PREFIX, location, ispCollection.ISPName, printer.RESET_PREFIX)
|
||||
log.Printf("『%s %s 』\n", location, ispCollection.ISPName)
|
||||
fmt.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ispCollection.IP)
|
||||
log.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ispCollection.IP)
|
||||
ip := net.ParseIP(ispCollection.IP)
|
||||
var conf = trace.Config{
|
||||
BeginHop: 1,
|
||||
@@ -32,7 +48,12 @@ func (f *FastTracer) tracert(location string, ispCollection ISPCollection) {
|
||||
}
|
||||
|
||||
if f.TracerouteMethod == trace.ICMPTrace {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
if oe {
|
||||
conf.RealtimePrinter = tracelog.RealtimePrinter
|
||||
} else {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
res, err := trace.Traceroute(f.TracerouteMethod, conf)
|
||||
@@ -90,15 +111,25 @@ func (f *FastTracer) testEDU() {
|
||||
f.tracert(TestIPsCollection.Hefei.Location, TestIPsCollection.Hefei.CST)
|
||||
}
|
||||
|
||||
func FastTest(tm bool) {
|
||||
func FastTest(tm bool, outEnable bool) {
|
||||
var c string
|
||||
|
||||
oe = outEnable
|
||||
|
||||
fmt.Println("您想测试哪些ISP的路由?\n1. 国内四网\n2. 电信\n3. 联通\n4. 移动\n5. 教育网")
|
||||
fmt.Print("请选择选项:")
|
||||
fmt.Scanln(&c)
|
||||
|
||||
ft := FastTracer{}
|
||||
|
||||
// 建立 WebSocket 连接
|
||||
w := wshandle.New()
|
||||
w.Interrupt = make(chan os.Signal, 1)
|
||||
signal.Notify(w.Interrupt, os.Interrupt)
|
||||
defer func() {
|
||||
w.Conn.Close()
|
||||
}()
|
||||
|
||||
if !tm {
|
||||
ft.TracerouteMethod = trace.ICMPTrace
|
||||
fmt.Println("您将默认使用ICMP协议进行路由跟踪,如果您想使用TCP SYN进行路由跟踪,可以加入 -T 参数")
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
package fastTrace
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"testing"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
// ICMP Use Too Many Time to Wait So we don't test it.
|
||||
func TestTCPTrace(t *testing.T) {
|
||||
ft := FastTracer{}
|
||||
// 建立 WebSocket 连接
|
||||
w := wshandle.New()
|
||||
w.Interrupt = make(chan os.Signal, 1)
|
||||
signal.Notify(w.Interrupt, os.Interrupt)
|
||||
defer func() {
|
||||
w.Conn.Close()
|
||||
}()
|
||||
ft.TracerouteMethod = trace.TCPTrace
|
||||
ft.testCM()
|
||||
ft.testEDU()
|
||||
|
||||
16
go.mod
16
go.mod
@@ -4,25 +4,25 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/google/gopacket v1.1.19
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // direct
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/lionsoul2014/ip2region v2.10.0+incompatible
|
||||
github.com/rodaine/table v1.0.1
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/tidwall/gjson v1.14.1
|
||||
github.com/stretchr/testify v1.7.1 // indirect
|
||||
github.com/tidwall/gjson v1.14.2
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
27
go.sum
27
go.sum
@@ -5,8 +5,13 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/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.10.0+incompatible h1:HpgN+54Korm/I0xXNX6I6owmvAwtPxrcI6cHYqXKtLw=
|
||||
github.com/lionsoul2014/ip2region v2.10.0+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
@@ -20,8 +25,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
@@ -32,25 +37,23 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770 h1:dIi4qVdvjZEjiMDv7vhokAZNGnz3kepwuXqFKYDdDMs=
|
||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs=
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
77
ipgeo/ip2region.go
Normal file
77
ipgeo/ip2region.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/v1.0/binding/golang/ip2region"
|
||||
)
|
||||
|
||||
const (
|
||||
ipDataBasePath = "./ip2region.db"
|
||||
defaultDownURL = "1"
|
||||
originURL = "https://ghproxy.com/?q=https://github.com/bqf9979/ip2region/blob/master/data/ip2region.db?raw=true"
|
||||
)
|
||||
|
||||
func downloadDataBase() error {
|
||||
fmt.Println("Downloading DataBase...")
|
||||
resp, err := http.Get(originURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Create the file
|
||||
out, err := os.Create(ipDataBasePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
func IP2Region(ip string) (*IPGeoData, error) {
|
||||
if _, err := os.Stat(ipDataBasePath); os.IsNotExist(err) {
|
||||
if err = downloadDataBase(); err != nil {
|
||||
panic("Download Failed!")
|
||||
}
|
||||
}
|
||||
region, err := ip2region.New(ipDataBasePath)
|
||||
if err != nil {
|
||||
panic("Cannot find ip2region.db")
|
||||
}
|
||||
defer region.Close()
|
||||
info, searchErr := region.MemorySearch(ip)
|
||||
if searchErr != nil {
|
||||
return &IPGeoData{}, errors.New("no results")
|
||||
}
|
||||
|
||||
if info.Country == "0" {
|
||||
info.Country = ""
|
||||
}
|
||||
|
||||
if info.Province == "0" {
|
||||
info.Province = ""
|
||||
}
|
||||
|
||||
if info.City == "0" {
|
||||
info.City = ""
|
||||
}
|
||||
|
||||
if info.ISP == "0" {
|
||||
info.ISP = ""
|
||||
}
|
||||
|
||||
return &IPGeoData{
|
||||
Owner: info.ISP,
|
||||
Country: info.Country,
|
||||
Prov: info.Province,
|
||||
City: info.City,
|
||||
}, nil
|
||||
}
|
||||
@@ -38,6 +38,6 @@ func IPApiCom(ip string) (*IPGeoData, error) {
|
||||
Country: res.Get("country").String(),
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("regionName").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
Owner: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -5,13 +5,19 @@ import (
|
||||
)
|
||||
|
||||
type IPGeoData struct {
|
||||
Asnumber string
|
||||
Country string
|
||||
Prov string
|
||||
City string
|
||||
District string
|
||||
Owner string
|
||||
Isp string
|
||||
IP string `json:"ip"`
|
||||
Asnumber string `json:"asnumber"`
|
||||
Country string `json:"country"`
|
||||
Prov string `json:"prov"`
|
||||
City string `json:"city"`
|
||||
District string `json:"district"`
|
||||
Owner string `json:"owner"`
|
||||
Isp string `json:"isp"`
|
||||
Domain string `json:"domain"`
|
||||
Whois string `json:"whois"`
|
||||
Prefix string `json:"prefix"`
|
||||
Router map[string][]string `json:"router"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
type Source = func(ip string) (*IPGeoData, error)
|
||||
@@ -28,6 +34,8 @@ func GetSource(s string) Source {
|
||||
return IPApiCom
|
||||
case "IPINFO":
|
||||
return IPInfo
|
||||
case "IP2REGION":
|
||||
return IP2Region
|
||||
default:
|
||||
return LeoIP
|
||||
}
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
// import (
|
||||
// "testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
|
||||
func TestLeoIP(t *testing.T) {
|
||||
res, err := LeoIP("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Asnumber)
|
||||
assert.NotEmpty(t, res.Isp)
|
||||
}
|
||||
// func TestLeoIP(t *testing.T) {
|
||||
// // res, err := LeoIP("1.1.1.1")
|
||||
// // assert.Nil(t, err)
|
||||
// // assert.NotNil(t, res)
|
||||
// // assert.NotEmpty(t, res.Asnumber)
|
||||
// // assert.NotEmpty(t, res.Isp)
|
||||
// }
|
||||
|
||||
func TestIPSB(t *testing.T) {
|
||||
// Not available
|
||||
//res, err := IPSB("1.1.1.1")
|
||||
//assert.Nil(t, err)
|
||||
//assert.NotNil(t, res)
|
||||
//assert.NotEmpty(t, res.Asnumber)
|
||||
//assert.NotEmpty(t, res.Isp)
|
||||
}
|
||||
// func TestIPSB(t *testing.T) {
|
||||
// // Not available
|
||||
// //res, err := IPSB("1.1.1.1")
|
||||
// //assert.Nil(t, err)
|
||||
// //assert.NotNil(t, res)
|
||||
// //assert.NotEmpty(t, res.Asnumber)
|
||||
// //assert.NotEmpty(t, res.Isp)
|
||||
// }
|
||||
|
||||
func TestIPInfo(t *testing.T) {
|
||||
res, err := IPInfo("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
// assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.City)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
}
|
||||
// func TestIPInfo(t *testing.T) {
|
||||
// res, err := IPInfo("1.1.1.1")
|
||||
// assert.Nil(t, err)
|
||||
// assert.NotNil(t, res)
|
||||
// // assert.NotEmpty(t, res.Country)
|
||||
// assert.NotEmpty(t, res.City)
|
||||
// assert.NotEmpty(t, res.Prov)
|
||||
// }
|
||||
|
||||
func TestIPInSight(t *testing.T) {
|
||||
res, err := IPInSight("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
// 这个库有时候不提供城市信息,返回值为""
|
||||
//assert.NotEmpty(t, res.City)
|
||||
}
|
||||
// func TestIPInSight(t *testing.T) {
|
||||
// // res, err := IPInSight("1.1.1.1")
|
||||
// // assert.Nil(t, err)
|
||||
// // assert.NotNil(t, res)
|
||||
// // assert.NotEmpty(t, res.Country)
|
||||
// // assert.NotEmpty(t, res.Prov)
|
||||
// // 这个库有时候不提供城市信息,返回值为""
|
||||
// //assert.NotEmpty(t, res.City)
|
||||
// }
|
||||
|
||||
func TestIPApiCom(t *testing.T) {
|
||||
res, err := IPApiCom("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.City)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
}
|
||||
// func TestIPApiCom(t *testing.T) {
|
||||
// res, err := IPApiCom("1.1.1.1")
|
||||
// assert.Nil(t, err)
|
||||
// assert.NotNil(t, res)
|
||||
// assert.NotEmpty(t, res.Country)
|
||||
// assert.NotEmpty(t, res.City)
|
||||
// assert.NotEmpty(t, res.Prov)
|
||||
// }
|
||||
|
||||
@@ -31,6 +31,6 @@ func IPInfo(ip string) (*IPGeoData, error) {
|
||||
Country: country,
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("region").String(),
|
||||
Isp: res.Get("asn").Get("domain").String(),
|
||||
Owner: res.Get("asn").Get("domain").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -37,6 +37,6 @@ func IPSB(ip string) (*IPGeoData, error) {
|
||||
Country: res.Get("country").String(),
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("region").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
Owner: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
118
ipgeo/leo.go
118
ipgeo/leo.go
@@ -1,37 +1,99 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
func LeoIP(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://api.leo.moe/ip/?ip=" + ip + "&token=" + token.ipleo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := gjson.ParseBytes(body)
|
||||
/***
|
||||
* 原理介绍 By Leo
|
||||
* WebSocket 一共开启了一个发送和一个接收协程,在 New 了一个连接的实例对象后,不给予关闭,持续化连接
|
||||
* 当有新的IP请求时,一直在等待IP数据的发送协程接收到从 leo.go 的 sendIPRequest 函数发来的IP数据,向服务端发送数据
|
||||
* 由于实际使用时有大量并发,但是 ws 在同一时刻每次有且只能处理一次发送一条数据,所以必须给 ws 连接上互斥锁,保证每次只有一个协程访问
|
||||
* 运作模型可以理解为一个 Node 一直在等待数据,当获得一个新的任务后,转交给下一个协程,不再关注这个 Node 的下一步处理过程,并且回到空闲状态继续等待新的任务
|
||||
***/
|
||||
|
||||
// IP 查询池 map - ip - ip channel
|
||||
type IPPool struct {
|
||||
pool map[string]chan IPGeoData
|
||||
poolMux sync.Mutex
|
||||
}
|
||||
|
||||
var IPPools = IPPool{
|
||||
pool: make(map[string]chan IPGeoData),
|
||||
}
|
||||
|
||||
func sendIPRequest(ip string) {
|
||||
wsConn := wshandle.GetWsConn()
|
||||
wsConn.MsgSendCh <- ip
|
||||
}
|
||||
|
||||
func receiveParse() {
|
||||
// 获得连接实例
|
||||
wsConn := wshandle.GetWsConn()
|
||||
// 防止多协程抢夺一个ws连接,导致死锁,当一个协程获得ws的控制权后上锁
|
||||
wsConn.ConnMux.Lock()
|
||||
// 函数退出时解锁,给其他协程使用
|
||||
defer wsConn.ConnMux.Unlock()
|
||||
for {
|
||||
// 接收到了一条IP信息
|
||||
data := <-wsConn.MsgReceiveCh
|
||||
|
||||
// json解析 -> data
|
||||
res := gjson.Parse(data)
|
||||
// 根据返回的IP信息,发送给对应等待回复的IP通道上
|
||||
var domain string = res.Get("domain").String()
|
||||
|
||||
if res.Get("domain").String() == "" {
|
||||
domain = res.Get("owner").String()
|
||||
}
|
||||
|
||||
m := make(map[string][]string)
|
||||
json.Unmarshal([]byte(res.Get("router").String()), &m)
|
||||
|
||||
IPPools.pool[gjson.Parse(data).Get("ip").String()] <- IPGeoData{
|
||||
Asnumber: res.Get("asnumber").String(),
|
||||
Country: res.Get("country").String(),
|
||||
Prov: res.Get("prov").String(),
|
||||
City: res.Get("city").String(),
|
||||
District: res.Get("district").String(),
|
||||
Owner: domain,
|
||||
Isp: res.Get("isp").String(),
|
||||
Whois: res.Get("whois").String(),
|
||||
Prefix: res.Get("prefix").String(),
|
||||
Router: m,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func LeoIP(ip string) (*IPGeoData, error) {
|
||||
// 初始化通道 - 向池子里添加IP的Channel,返回IP数据是通过字典中对应键为IP的Channel来获取的
|
||||
IPPools.poolMux.Lock()
|
||||
defer IPPools.poolMux.Unlock()
|
||||
// 如果之前已经被别的协程初始化过了就不用初始化了
|
||||
if IPPools.pool[ip] == nil {
|
||||
IPPools.pool[ip] = make(chan IPGeoData)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
sendIPRequest(ip)
|
||||
// 同步开启监听
|
||||
go receiveParse()
|
||||
|
||||
// 拥塞,等待数据返回
|
||||
select {
|
||||
case res := <-IPPools.pool[ip]:
|
||||
return &res, nil
|
||||
// 5秒后依旧没有接收到返回的IP数据,不再等待,超时异常处理
|
||||
case <-time.After(5 * time.Second):
|
||||
// default:
|
||||
// 这里不可以返回一个 nil,否则在访问对象内部的键值的时候会报空指针的 Fatal Error
|
||||
return &IPGeoData{}, errors.New("TimeOut")
|
||||
}
|
||||
|
||||
if res.Get("Message").String() != "" {
|
||||
return &IPGeoData{
|
||||
Asnumber: res.Get("Message").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &IPGeoData{
|
||||
Asnumber: res.Get("asnumber").String(),
|
||||
Country: res.Get("country").String(),
|
||||
Prov: res.Get("prov").String(),
|
||||
City: res.Get("city").String(),
|
||||
District: res.Get("district").String(),
|
||||
Owner: res.Get("owner").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
57
main.go
57
main.go
@@ -6,6 +6,8 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -14,7 +16,9 @@ import (
|
||||
"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/util"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
var fSet = flag.NewFlagSet("", flag.ExitOnError)
|
||||
@@ -28,9 +32,14 @@ var maxHops = fSet.Int("m", 30, "Set the max number of hops (max TTL to be reach
|
||||
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 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")
|
||||
@@ -57,12 +66,17 @@ func flagApply() string {
|
||||
|
||||
// Print Version
|
||||
if *ver {
|
||||
printer.CopyRight()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// -f Fast Test
|
||||
if *fastTest {
|
||||
fastTrace.FastTest(*tcpSYNFlag)
|
||||
fastTrace.FastTest(*tcpSYNFlag, *output)
|
||||
if *output {
|
||||
fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中")
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -76,18 +90,43 @@ func main() {
|
||||
|
||||
domain := flagApply()
|
||||
|
||||
if os.Getuid() != 0 {
|
||||
if os.Getuid() != 0 && runtime.GOOS != "windows" {
|
||||
log.Fatalln("Traceroute requires root/sudo privileges.")
|
||||
}
|
||||
|
||||
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 = ""
|
||||
@@ -106,6 +145,7 @@ func main() {
|
||||
}
|
||||
|
||||
var conf = trace.Config{
|
||||
SrcAddr: *src_addr,
|
||||
BeginHop: *beginHop,
|
||||
DestIP: ip,
|
||||
DestPort: *port,
|
||||
@@ -118,7 +158,18 @@ func main() {
|
||||
}
|
||||
|
||||
if !*tablePrint {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res, err := trace.Traceroute(m, conf)
|
||||
|
||||
@@ -82,7 +82,7 @@ checkWgetPackage() {
|
||||
downloadBinrayFile() {
|
||||
echo -e "${Info} 获取最新版的 NextTrace 发行版文件信息"
|
||||
# 简单说明一下,Github提供了一个API,可以获取最新发行版本的二进制文件下载地址(对应的是browser_download_url),根据刚刚测得的osDistribution、archParam,获取对应的下载地址
|
||||
latestURL=$(curl -s https://api.github.com/repos/xgadget-lab/nexttrace/releases/latest | grep -i "browser_download_url.*${osDistribution}.*${archParam}" | awk -F '"' '{print $4}')
|
||||
latestURL=$(curl -s https://api.github.com/repos/sjlleo/nexttrace/releases/latest | grep -i "browser_download_url.*${osDistribution}.*${archParam}" | awk -F '"' '{print $4}')
|
||||
|
||||
if [ "$countryCode" == "CN" ]; then
|
||||
echo -e "${Info} 检测到国内环境,正在使用镜像下载"
|
||||
|
||||
@@ -3,6 +3,8 @@ package printer
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var version = "v0.0.0.alpha"
|
||||
@@ -10,8 +12,16 @@ var buildDate = ""
|
||||
var commitID = ""
|
||||
|
||||
func Version() {
|
||||
fmt.Println("NextTrace", version, buildDate, commitID)
|
||||
fmt.Println("XGadget-lab Leo (leo.moe) & Vincent (vincent.moe) & zhshch (xzhsh.ch)")
|
||||
fmt.Fprintf(color.Output, "%s %s %s %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", "NextTrace"),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", version),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", buildDate),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%s", commitID),
|
||||
)
|
||||
}
|
||||
|
||||
func CopyRight() {
|
||||
fmt.Println("XGadget-lab Leo (leo.moe) & Tso (tsosunchia@gmail.com) & Vincent (vincent.moe) & zhshch (xzhsh.ch)")
|
||||
}
|
||||
|
||||
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) {
|
||||
|
||||
104
printer/classic_printer.go
Normal file
104
printer/classic_printer.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
type HopInfo int
|
||||
|
||||
const (
|
||||
General HopInfo = 0
|
||||
IXP HopInfo = 1
|
||||
Peer HopInfo = 2
|
||||
PoP HopInfo = 3
|
||||
Aboard HopInfo = 4
|
||||
)
|
||||
|
||||
func findLatestAvailableHop(res *trace.Result, ttl int, probesIndex int) int {
|
||||
for ttl > 0 {
|
||||
// 查找上一个跃点是不是有效结果
|
||||
ttl--
|
||||
// 判断此TTL跃点是否有效并判断地理位置结构体是否已经初始化
|
||||
if len(res.Hops[ttl]) != 0 && res.Hops[ttl][probesIndex].Success && res.Hops[ttl][probesIndex].Geo != nil {
|
||||
// TTL虽有效,但地理位置API没有能够正确返回数据,依旧不能视为有效数据
|
||||
if res.Hops[ttl][probesIndex].Geo.Country == "" {
|
||||
// 跳过继续寻找上一个有效跃点
|
||||
continue
|
||||
}
|
||||
return ttl
|
||||
}
|
||||
}
|
||||
// 没找到
|
||||
return -1
|
||||
}
|
||||
|
||||
func unifyName(name string) string {
|
||||
if name == "China" || name == "CN" {
|
||||
return "中国"
|
||||
} else if name == "Hong kong" || name == "香港" || name == "Central and Western" {
|
||||
return "中国香港"
|
||||
} else if name == "Taiwan" || name == "台湾" {
|
||||
return "中国台湾"
|
||||
} else {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
func chinaISPPeer(hostname string) bool {
|
||||
var keyWords = []string{"china", "ct", "cu", "cm", "cnc", "4134", "4837", "4809", "9929"}
|
||||
for _, k := range keyWords {
|
||||
if strings.Contains(strings.ToLower(hostname), k) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func chinaMainland(h trace.Hop) bool {
|
||||
if unifyName(h.Geo.Country) == "中国" && unifyName(h.Geo.Prov) != "中国香港" && unifyName(h.Geo.Prov) != "中国台湾" {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func makeHopsType(res *trace.Result, ttl int) map[int]HopInfo {
|
||||
// 创建一个字典,存放所有当前TTL的跃点类型集合
|
||||
hopProbesMap := make(map[int]HopInfo)
|
||||
for i := range res.Hops[ttl] {
|
||||
// 判断是否res.Hops[ttl][i]是一个有效的跃点并且地理位置信息已经初始化
|
||||
if res.Hops[ttl][i].Success && res.Hops[ttl][i].Geo != nil {
|
||||
if availableTTL := findLatestAvailableHop(res, ttl, i); availableTTL != -1 {
|
||||
switch {
|
||||
case strings.Contains(res.Hops[ttl][i].Geo.District, "IXP") || strings.Contains(strings.ToLower(res.Hops[ttl][i].Hostname), "ix"):
|
||||
hopProbesMap[i] = IXP
|
||||
case strings.Contains(res.Hops[ttl][i].Geo.District, "Peer") || chinaISPPeer(res.Hops[ttl][i].Hostname):
|
||||
hopProbesMap[i] = Peer
|
||||
case strings.Contains(res.Hops[ttl][i].Geo.District, "PoP"):
|
||||
hopProbesMap[i] = PoP
|
||||
// 2个有效跃点必须都为有效数据,如果当前跳没有地理位置信息或者为局域网,不能视为有效节点
|
||||
case res.Hops[availableTTL][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "" &&
|
||||
// 一个跃点在中国大陆,另外一个跃点在其他地区,则可以推断出数据包跨境
|
||||
chinaMainland(res.Hops[availableTTL][i]) != chinaMainland(res.Hops[ttl][i]):
|
||||
// TODO: 将先后2跳跃点信息汇报给API,以完善相关数据
|
||||
hopProbesMap[i] = Aboard
|
||||
}
|
||||
} else {
|
||||
hopProbesMap[i] = General
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hopProbesMap
|
||||
}
|
||||
|
||||
func ClassicPrinter(res *trace.Result, ttl int) {
|
||||
fmt.Print(ttl + 1)
|
||||
hopsTypeMap := makeHopsType(res, ttl)
|
||||
for i := range res.Hops[ttl] {
|
||||
HopPrinter(res.Hops[ttl][i], hopsTypeMap[i])
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,6 @@ func TestTracerouteTablePrinter(t *testing.T) {
|
||||
|
||||
func TestRealtimePrinter(t *testing.T) {
|
||||
RealtimePrinter(testResult, 0)
|
||||
RealtimePrinter(testResult, 1)
|
||||
RealtimePrinter(testResult, 2)
|
||||
// RealtimePrinter(testResult, 1)
|
||||
// RealtimePrinter(testResult, 2)
|
||||
}
|
||||
|
||||
@@ -2,103 +2,121 @@ package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
type HopInfo int
|
||||
|
||||
const (
|
||||
General HopInfo = 0
|
||||
IXP HopInfo = 1
|
||||
Peer HopInfo = 2
|
||||
PoP HopInfo = 3
|
||||
Aboard HopInfo = 4
|
||||
)
|
||||
|
||||
func findLatestAvailableHop(res *trace.Result, ttl int, probesIndex int) int {
|
||||
for ttl > 0 {
|
||||
// 查找上一个跃点是不是有效结果
|
||||
ttl--
|
||||
// 判断此TTL跃点是否有效并判断地理位置结构体是否已经初始化
|
||||
if res.Hops[ttl][probesIndex].Success && res.Hops[ttl][probesIndex].Geo != nil {
|
||||
// TTL虽有效,但地理位置API没有能够正确返回数据,依旧不能视为有效数据
|
||||
if res.Hops[ttl][probesIndex].Geo.Country == "" {
|
||||
// 跳过继续寻找上一个有效跃点
|
||||
continue
|
||||
}
|
||||
return ttl
|
||||
}
|
||||
}
|
||||
// 没找到
|
||||
return -1
|
||||
}
|
||||
|
||||
func unifyName(name string) string {
|
||||
if name == "China" || name == "CN" {
|
||||
return "中国"
|
||||
} else if name == "Hong kong" || name == "香港" || name == "Central and Western" {
|
||||
return "中国香港"
|
||||
} else if name == "Taiwan" || name == "台湾" {
|
||||
return "中国台湾"
|
||||
} else {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
func chinaISPPeer(hostname string) bool {
|
||||
var keyWords = []string{"china", "ct", "cu", "cm", "cnc", "4134", "4837", "4809", "9929"}
|
||||
for _, k := range keyWords {
|
||||
if strings.Contains(strings.ToLower(hostname), k) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func chinaMainland(h trace.Hop) bool {
|
||||
if unifyName(h.Geo.Country) == "中国" && unifyName(h.Geo.Prov) != "中国香港" && unifyName(h.Geo.Prov) != "中国台湾" {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func makeHopsType(res *trace.Result, ttl int) map[int]HopInfo {
|
||||
// 创建一个字典,存放所有当前TTL的跃点类型集合
|
||||
hopProbesMap := make(map[int]HopInfo)
|
||||
for i := range res.Hops[ttl] {
|
||||
// 判断是否res.Hops[ttl][i]是一个有效的跃点并且地理位置信息已经初始化
|
||||
if res.Hops[ttl][i].Success && res.Hops[ttl][i].Geo != nil {
|
||||
if availableTTL := findLatestAvailableHop(res, ttl, i); availableTTL != -1 {
|
||||
switch {
|
||||
case strings.Contains(res.Hops[ttl][i].Geo.District, "IXP") || strings.Contains(strings.ToLower(res.Hops[ttl][i].Hostname), "ix"):
|
||||
hopProbesMap[i] = IXP
|
||||
case strings.Contains(res.Hops[ttl][i].Geo.District, "Peer") || chinaISPPeer(res.Hops[ttl][i].Hostname):
|
||||
hopProbesMap[i] = Peer
|
||||
case strings.Contains(res.Hops[ttl][i].Geo.District, "PoP"):
|
||||
hopProbesMap[i] = PoP
|
||||
// 2个有效跃点必须都为有效数据,如果当前跳没有地理位置信息或者为局域网,不能视为有效节点
|
||||
case res.Hops[availableTTL][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "LAN Address" && res.Hops[ttl][i].Geo.Country != "" &&
|
||||
// 一个跃点在中国大陆,另外一个跃点在其他地区,则可以推断出数据包跨境
|
||||
chinaMainland(res.Hops[availableTTL][i]) != chinaMainland(res.Hops[ttl][i]):
|
||||
// TODO: 将先后2跳跃点信息汇报给API,以完善相关数据
|
||||
hopProbesMap[i] = Aboard
|
||||
}
|
||||
} else {
|
||||
hopProbesMap[i] = General
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hopProbesMap
|
||||
}
|
||||
|
||||
func RealtimePrinter(res *trace.Result, ttl int) {
|
||||
fmt.Print(ttl + 1)
|
||||
hopsTypeMap := makeHopsType(res, ttl)
|
||||
for i := range res.Hops[ttl] {
|
||||
HopPrinter(res.Hops[ttl][i], hopsTypeMap[i])
|
||||
fmt.Printf("%s ", color.New(color.FgHiYellow, color.Bold).Sprintf("%-2d", ttl+1))
|
||||
|
||||
// 去重
|
||||
var latestIP string
|
||||
tmpMap := make(map[string][]string)
|
||||
for i, v := range res.Hops[ttl] {
|
||||
if v.Address == nil && latestIP != "" {
|
||||
tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*"))
|
||||
continue
|
||||
} else if v.Address == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exist := tmpMap[v.Address.String()]; !exist {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i))
|
||||
// 首次进入
|
||||
if latestIP == "" {
|
||||
for j := 0; j < i; j++ {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*"))
|
||||
}
|
||||
}
|
||||
latestIP = v.Address.String()
|
||||
}
|
||||
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000))
|
||||
}
|
||||
|
||||
if latestIP == "" {
|
||||
fmt.Fprintf(color.Output, "%s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("*"),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var blockDisplay = false
|
||||
for ip, v := range tmpMap {
|
||||
if blockDisplay {
|
||||
fmt.Printf("%4s", "")
|
||||
}
|
||||
if net.ParseIP(ip).To4() == nil {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%-25s", ip),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip),
|
||||
)
|
||||
}
|
||||
|
||||
i, _ := strconv.Atoi(v[0])
|
||||
|
||||
if res.Hops[ttl][i].Geo.Asnumber != "" {
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
|
||||
} else {
|
||||
fmt.Printf(" %-8s", "*")
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-")
|
||||
if len(whoisFormat) > 1 {
|
||||
whoisFormat[0] = strings.Join(whoisFormat[:2], "-")
|
||||
}
|
||||
|
||||
if whoisFormat[0] != "" {
|
||||
whoisFormat[0] = "[" + whoisFormat[0] + "]"
|
||||
}
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0]))
|
||||
}
|
||||
|
||||
if res.Hops[ttl][i].Geo.Country == "" {
|
||||
res.Hops[ttl][i].Geo.Country = "LAN Address"
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
|
||||
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
|
||||
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%-39s", res.Hops[ttl][i].Hostname),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
|
||||
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%-32s", res.Hops[ttl][i].Hostname),
|
||||
)
|
||||
}
|
||||
|
||||
for j := 1; j < len(v); j++ {
|
||||
if len(v) == 2 || j == 1 {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " / %s",
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
|
||||
)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
blockDisplay = true
|
||||
}
|
||||
}
|
||||
|
||||
152
printer/realtime_printer_router.go
Normal file
152
printer/realtime_printer_router.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
func RealtimePrinterWithRouter(res *trace.Result, ttl int) {
|
||||
fmt.Printf("%s ", color.New(color.FgHiYellow, color.Bold).Sprintf("%-2d", ttl+1))
|
||||
|
||||
// 去重
|
||||
var latestIP string
|
||||
tmpMap := make(map[string][]string)
|
||||
for i, v := range res.Hops[ttl] {
|
||||
if v.Address == nil && latestIP != "" {
|
||||
tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*"))
|
||||
continue
|
||||
} else if v.Address == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exist := tmpMap[v.Address.String()]; !exist {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i))
|
||||
// 首次进入
|
||||
if latestIP == "" {
|
||||
for j := 0; j < i; j++ {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*"))
|
||||
}
|
||||
}
|
||||
latestIP = v.Address.String()
|
||||
}
|
||||
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000))
|
||||
}
|
||||
|
||||
if latestIP == "" {
|
||||
fmt.Fprintf(color.Output, "%s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("*"),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var blockDisplay = false
|
||||
for ip, v := range tmpMap {
|
||||
if blockDisplay {
|
||||
fmt.Printf("%4s", "")
|
||||
}
|
||||
if net.ParseIP(ip).To4() == nil {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%-25s", ip),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip),
|
||||
)
|
||||
}
|
||||
|
||||
i, _ := strconv.Atoi(v[0])
|
||||
|
||||
if res.Hops[ttl][i].Geo.Asnumber != "" {
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("AS%-6s", res.Hops[ttl][i].Geo.Asnumber))
|
||||
} else {
|
||||
fmt.Printf(" %-8s", "*")
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-")
|
||||
if len(whoisFormat) > 1 {
|
||||
whoisFormat[0] = strings.Join(whoisFormat[:2], "-")
|
||||
}
|
||||
|
||||
if whoisFormat[0] != "" {
|
||||
whoisFormat[0] = "[" + whoisFormat[0] + "]"
|
||||
}
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0]))
|
||||
}
|
||||
|
||||
if res.Hops[ttl][i].Geo.Country == "" {
|
||||
res.Hops[ttl][i].Geo.Country = "LAN Address"
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
|
||||
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
|
||||
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%-39s", res.Hops[ttl][i].Hostname),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " %s %s %s %s %s\n %s ",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Country),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prov),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.City),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.District),
|
||||
fmt.Sprintf("%-6s", res.Hops[ttl][i].Geo.Owner),
|
||||
color.New(color.FgHiBlack, color.Bold).Sprintf("%-32s", res.Hops[ttl][i].Hostname),
|
||||
)
|
||||
}
|
||||
|
||||
for j := 1; j < len(v); j++ {
|
||||
if len(v) == 2 || j == 1 {
|
||||
fmt.Fprintf(color.Output, "%s",
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " / %s",
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("%s", v[j]),
|
||||
)
|
||||
}
|
||||
}
|
||||
i = 0
|
||||
fmt.Println()
|
||||
if res.Hops[ttl][i].Geo != nil && !blockDisplay {
|
||||
fmt.Fprintf(color.Output, "%s %s %s %s %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("-"),
|
||||
color.New(color.FgHiYellow, color.Bold).Sprintf("%s", res.Hops[ttl][i].Geo.Prefix),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("路由表"),
|
||||
color.New(color.FgHiCyan, color.Bold).Sprintf("Beta"),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("-"),
|
||||
)
|
||||
GetRouter(&res.Hops[ttl][i].Geo.Router, "AS"+res.Hops[ttl][i].Geo.Asnumber)
|
||||
}
|
||||
blockDisplay = true
|
||||
}
|
||||
}
|
||||
|
||||
func GetRouter(r *map[string][]string, node string) {
|
||||
routeMap := *r
|
||||
for _, v := range routeMap[node] {
|
||||
if len(routeMap[v]) != 0 {
|
||||
fmt.Fprintf(color.Output, " %s %s %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", routeMap[v][0]),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", v),
|
||||
color.New(color.FgHiBlue, color.Bold).Sprintf("%s", node),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(color.Output, " %s %s\n",
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", v),
|
||||
color.New(color.FgHiBlue, color.Bold).Sprintf("%s", node),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ func (t *ICMPTracer) Execute() (*Result, error) {
|
||||
|
||||
var err error
|
||||
|
||||
t.icmpListen, err = net.ListenPacket("ip4:1", "0.0.0.0")
|
||||
t.icmpListen, err = net.ListenPacket("ip4:1", t.SrcAddr)
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (t *ICMPTracerv6) Execute() (*Result, error) {
|
||||
|
||||
var err error
|
||||
|
||||
t.icmpListen, err = net.ListenPacket("ip6:58", "::")
|
||||
t.icmpListen, err = net.ListenPacket("ip6:58", t.SrcAddr)
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
|
||||
@@ -42,11 +42,16 @@ func (t *TCPTracer) Execute() (*Result, error) {
|
||||
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
|
||||
|
||||
var err error
|
||||
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcIP.String())
|
||||
if t.SrcAddr != "" {
|
||||
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcAddr)
|
||||
} else {
|
||||
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcIP.String())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", t.SrcAddr)
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ var (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SrcAddr string
|
||||
BeginHop int
|
||||
MaxHops int
|
||||
NumMeasurements int
|
||||
|
||||
@@ -37,7 +37,7 @@ func (t *UDPTracer) Execute() (*Result, error) {
|
||||
}
|
||||
|
||||
var err error
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", t.SrcAddr)
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
|
||||
111
tracelog/log.go
Normal file
111
tracelog/log.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package tracelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
func RealtimePrinter(res *trace.Result, ttl int) {
|
||||
f, err := os.OpenFile("/tmp/trace.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
multiWriter := io.MultiWriter(os.Stdout, f)
|
||||
log.SetOutput(multiWriter)
|
||||
log.SetFlags(0)
|
||||
var res_str string
|
||||
res_str += fmt.Sprintf("%-2d ", ttl+1)
|
||||
|
||||
// 去重
|
||||
var latestIP string
|
||||
tmpMap := make(map[string][]string)
|
||||
for i, v := range res.Hops[ttl] {
|
||||
if v.Address == nil && latestIP != "" {
|
||||
tmpMap[latestIP] = append(tmpMap[latestIP], fmt.Sprintf("%s ms", "*"))
|
||||
continue
|
||||
} else if v.Address == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exist := tmpMap[v.Address.String()]; !exist {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], strconv.Itoa(i))
|
||||
// 首次进入
|
||||
if latestIP == "" {
|
||||
for j := 0; j < i; j++ {
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%s ms", "*"))
|
||||
}
|
||||
}
|
||||
latestIP = v.Address.String()
|
||||
}
|
||||
|
||||
tmpMap[v.Address.String()] = append(tmpMap[v.Address.String()], fmt.Sprintf("%.2f ms", v.RTT.Seconds()*1000))
|
||||
}
|
||||
|
||||
if latestIP == "" {
|
||||
res_str += fmt.Sprintf("%s\n", "*")
|
||||
log.Print(res_str)
|
||||
return
|
||||
}
|
||||
|
||||
var blockDisplay = false
|
||||
for ip, v := range tmpMap {
|
||||
if blockDisplay {
|
||||
res_str += fmt.Sprintf("%4s", "")
|
||||
}
|
||||
if net.ParseIP(ip).To4() == nil {
|
||||
res_str += fmt.Sprintf("%-25s ", ip)
|
||||
} else {
|
||||
res_str += fmt.Sprintf("%-15s ", ip)
|
||||
}
|
||||
|
||||
i, _ := strconv.Atoi(v[0])
|
||||
|
||||
if res.Hops[ttl][i].Geo.Asnumber != "" {
|
||||
res_str += fmt.Sprintf("AS%-7s", res.Hops[ttl][i].Geo.Asnumber)
|
||||
} else {
|
||||
res_str += fmt.Sprintf(" %-8s", "*")
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
whoisFormat := strings.Split(res.Hops[ttl][i].Geo.Whois, "-")
|
||||
if len(whoisFormat) > 1 {
|
||||
whoisFormat[0] = strings.Join(whoisFormat[:2], "-")
|
||||
}
|
||||
|
||||
if whoisFormat[0] != "" {
|
||||
whoisFormat[0] = "[" + whoisFormat[0] + "]"
|
||||
}
|
||||
res_str += fmt.Sprintf("%-16s", whoisFormat[0])
|
||||
}
|
||||
|
||||
if res.Hops[ttl][i].Geo.Country == "" {
|
||||
res.Hops[ttl][i].Geo.Country = "LAN Address"
|
||||
}
|
||||
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
|
||||
res_str += fmt.Sprintf(" %s %s %s %s %-6s\n %-39s ", res.Hops[ttl][i].Geo.Country, res.Hops[ttl][i].Geo.Prov, res.Hops[ttl][i].Geo.City, res.Hops[ttl][i].Geo.District, res.Hops[ttl][i].Geo.Owner, res.Hops[ttl][i].Hostname)
|
||||
} else {
|
||||
res_str += fmt.Sprintf(" %s %s %s %s %-6s\n %-35s ", res.Hops[ttl][i].Geo.Country, res.Hops[ttl][i].Geo.Prov, res.Hops[ttl][i].Geo.City, res.Hops[ttl][i].Geo.District, res.Hops[ttl][i].Geo.Owner, res.Hops[ttl][i].Hostname)
|
||||
}
|
||||
|
||||
for j := 1; j < len(v); j++ {
|
||||
if len(v) == 2 || j == 1 {
|
||||
res_str += v[j]
|
||||
} else {
|
||||
res_str += fmt.Sprintf("/ %s", v[j])
|
||||
}
|
||||
}
|
||||
log.Print(res_str)
|
||||
blockDisplay = true
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
// get the local ip and port based on our destination ip
|
||||
@@ -60,7 +62,10 @@ func DomainLookUp(host string, ipv4Only bool) net.IP {
|
||||
} else {
|
||||
fmt.Println("Please Choose the IP You Want To TraceRoute")
|
||||
for i, ip := range ipSlice {
|
||||
fmt.Printf("%d. %s\n", i, ip)
|
||||
fmt.Fprintf(color.Output, "%s %s\n",
|
||||
color.New(color.FgHiYellow, color.Bold).Sprintf("%d.", i),
|
||||
color.New(color.FgWhite, color.Bold).Sprintf("%s", ip),
|
||||
)
|
||||
}
|
||||
var index int
|
||||
fmt.Printf("Your Option: ")
|
||||
|
||||
169
wshandle/client.go
Normal file
169
wshandle/client.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package wshandle
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WsConn struct {
|
||||
Connecting bool
|
||||
Connected bool // 连接状态
|
||||
MsgSendCh chan string // 消息发送通道
|
||||
MsgReceiveCh chan string // 消息接收通道
|
||||
Done chan struct{} // 发送结束通道
|
||||
Exit chan bool // 程序退出信号
|
||||
Interrupt chan os.Signal // 终端中止信号
|
||||
Conn *websocket.Conn // 主连接
|
||||
ConnMux sync.Mutex // 连接互斥锁
|
||||
}
|
||||
|
||||
var wsconn *WsConn
|
||||
|
||||
func (c *WsConn) keepAlive() {
|
||||
go func() {
|
||||
// 开启一个定时器
|
||||
for {
|
||||
<-time.After(time.Second * 54)
|
||||
if c.Connected {
|
||||
c.Conn.WriteMessage(websocket.TextMessage, []byte("ping"))
|
||||
}
|
||||
}
|
||||
}()
|
||||
for {
|
||||
if !c.Connected && !c.Connecting {
|
||||
c.Connecting = true
|
||||
c.recreateWsConn()
|
||||
// log.Println("WebSocket 连接意外断开,正在尝试重连...")
|
||||
// return
|
||||
}
|
||||
// 降低检测频率,优化 CPU 占用情况
|
||||
<-time.After(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WsConn) messageReceiveHandler() {
|
||||
// defer close(c.Done)
|
||||
for {
|
||||
if c.Connected {
|
||||
_, msg, err := c.Conn.ReadMessage()
|
||||
if err != nil {
|
||||
// 读取信息出错,连接已经意外断开
|
||||
// log.Println(err)
|
||||
c.Connected = false
|
||||
return
|
||||
}
|
||||
if string(msg) != "pong" {
|
||||
c.MsgReceiveCh <- string(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WsConn) messageSendHandler() {
|
||||
for {
|
||||
// 循环监听发送
|
||||
select {
|
||||
case <-c.Done:
|
||||
log.Println("发送协程已经退出")
|
||||
return
|
||||
case t := <-c.MsgSendCh:
|
||||
// log.Println(t)
|
||||
if !c.Connected {
|
||||
c.MsgReceiveCh <- `{"ip":"` + t + `", "asnumber":"API服务端异常"}`
|
||||
} else {
|
||||
err := c.Conn.WriteMessage(websocket.TextMessage, []byte(t))
|
||||
if err != nil {
|
||||
log.Println("write:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 来自终端的中断运行请求
|
||||
case <-c.Interrupt:
|
||||
// 向 websocket 发起关闭连接任务
|
||||
err := c.Conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
if err != nil {
|
||||
// log.Println("write close:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
select {
|
||||
// 等到了结果,直接退出
|
||||
case <-c.Done:
|
||||
// 如果等待 1s 还是拿不到结果,不再等待,超时退出
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
os.Exit(1)
|
||||
// return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WsConn) recreateWsConn() {
|
||||
u := url.URL{Scheme: "wss", Host: "api.leo.moe", Path: "/v2/ipGeoWs"}
|
||||
// log.Printf("connecting to %s", u.String())
|
||||
|
||||
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
c.Conn = ws
|
||||
if err != nil {
|
||||
log.Println("dial:", err)
|
||||
// <-time.After(time.Second * 1)
|
||||
c.Connected = false
|
||||
c.Connecting = false
|
||||
return
|
||||
} else {
|
||||
c.Connected = true
|
||||
}
|
||||
c.Connecting = false
|
||||
|
||||
c.Done = make(chan struct{})
|
||||
go c.messageReceiveHandler()
|
||||
}
|
||||
|
||||
func createWsConn() *WsConn {
|
||||
// 设置终端中断通道
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
|
||||
u := url.URL{Scheme: "wss", Host: "api.leo.moe", Path: "/v2/ipGeoWs"}
|
||||
// log.Printf("connecting to %s", u.String())
|
||||
|
||||
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
|
||||
wsconn = &WsConn{
|
||||
Conn: c,
|
||||
Connected: true,
|
||||
Connecting: false,
|
||||
MsgSendCh: make(chan string, 10),
|
||||
MsgReceiveCh: make(chan string, 10),
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Println("dial:", err)
|
||||
// <-time.After(time.Second * 1)
|
||||
wsconn.Connected = false
|
||||
wsconn.Done = make(chan struct{})
|
||||
go wsconn.keepAlive()
|
||||
go wsconn.messageSendHandler()
|
||||
return wsconn
|
||||
}
|
||||
// defer c.Close()
|
||||
// 将连接写入WsConn,方便随时可取
|
||||
wsconn.Done = make(chan struct{})
|
||||
go wsconn.keepAlive()
|
||||
go wsconn.messageReceiveHandler()
|
||||
go wsconn.messageSendHandler()
|
||||
return wsconn
|
||||
}
|
||||
|
||||
func New() *WsConn {
|
||||
return createWsConn()
|
||||
}
|
||||
|
||||
func GetWsConn() *WsConn {
|
||||
return wsconn
|
||||
}
|
||||
Reference in New Issue
Block a user