mirror of
https://github.com/nxtrace/NTrace-core.git
synced 2025-08-12 06:26:39 +00:00
Compare commits
70 Commits
v0.1.0a
...
v0.1.2-Bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
738ff949b1 | ||
|
|
b59f0eb9da | ||
|
|
c7df49ca8e | ||
|
|
4d3a52bb28 | ||
|
|
fbf7335bc8 | ||
|
|
d60c80e1ff | ||
|
|
4f622b7afb | ||
|
|
f51b07d388 | ||
|
|
679dbbf01a | ||
|
|
2a5e5572b4 | ||
|
|
974655a1b3 | ||
|
|
eb9844f924 | ||
|
|
b8a51b3c44 | ||
|
|
5d984fc3ac | ||
|
|
bcea9aa2cb | ||
|
|
ffc2c33e63 | ||
|
|
919335133e | ||
|
|
4d0e3eb93d | ||
|
|
56d2f0554f | ||
|
|
e728b6b063 | ||
|
|
8b2d2f3990 | ||
|
|
4c51b2fbbe | ||
|
|
9a6586f27a | ||
|
|
cc8e3e4838 | ||
|
|
d69b7b9acb | ||
|
|
483a90848d | ||
|
|
131a9e2e8a | ||
|
|
982e1064c2 | ||
|
|
5ff461af42 | ||
|
|
8adc98a753 | ||
|
|
937113ca33 | ||
|
|
9f0c62506e | ||
|
|
cad5f944cb | ||
|
|
0f0fb91fb6 | ||
|
|
e1c6f1ccf6 | ||
|
|
5ac811bbae | ||
|
|
dbd8ae573c | ||
|
|
8db4c5e7b8 | ||
|
|
7db2a717a4 | ||
|
|
b0ba116c91 | ||
|
|
5f993961ed | ||
|
|
ead46decf6 | ||
|
|
7712ebf953 | ||
|
|
14bbc62358 | ||
|
|
4323021f96 | ||
|
|
cbfb37f37b | ||
|
|
50d594e4df | ||
|
|
6b08727993 | ||
|
|
14730bb489 | ||
|
|
b688a1ae99 | ||
|
|
3548c47411 | ||
|
|
460e8ba78d | ||
|
|
abe4b685c4 | ||
|
|
9758a93382 | ||
|
|
6fffe31506 | ||
|
|
b7b966b93e | ||
|
|
433c8656a1 | ||
|
|
1542cb4b07 | ||
|
|
06ee8f7373 | ||
|
|
6792bafb02 | ||
|
|
70305caa1c | ||
|
|
671ad82780 | ||
|
|
e62575beba | ||
|
|
d92a1e10d3 | ||
|
|
971d68f93f | ||
|
|
f765dbafae | ||
|
|
ea7feab2f9 | ||
|
|
e941eaa167 | ||
|
|
46e32d697d | ||
|
|
97578e40f7 |
@@ -5,7 +5,7 @@ set -e
|
||||
DIST_PREFIX="nexttrace"
|
||||
DEBUG_MODE=${2}
|
||||
TARGET_DIR="dist"
|
||||
PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 linux/arm64"
|
||||
PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 linux/mips"
|
||||
|
||||
rm -rf ${TARGET_DIR}
|
||||
mkdir ${TARGET_DIR}
|
||||
|
||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -1,10 +1,11 @@
|
||||
on:
|
||||
push: # 每次 push 的时候触发
|
||||
push: # 每次带有 tag 的 push 候触发
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
name: Build Release
|
||||
jobs:
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/') # 只有这次 Commit 是 创建 Tag 时,才进行后续发布操作
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master # checkout 代码
|
||||
@@ -23,5 +24,6 @@ jobs:
|
||||
dist/nexttrace_darwin_arm64
|
||||
dist/nexttrace_linux_amd64
|
||||
dist/nexttrace_linux_arm64
|
||||
dist/nexttrace_linux_mips
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GT_Token }}
|
||||
|
||||
131
README.md
131
README.md
@@ -1,2 +1,129 @@
|
||||
# traceroute
|
||||
可视化路由跟踪工具
|
||||
<div align="center">
|
||||
|
||||
<img src="asset/logo.png" height="200px"/>
|
||||
|
||||
</div>
|
||||
|
||||
# NextTrace
|
||||
|
||||
一款开源的可视化路由跟踪工具,使用Golang开发。
|
||||
|
||||
## How To Use
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -Ls https://raw.githubusercontent.com/xgadget-lab/nexttrace/main/nt_install.sh)"
|
||||
```
|
||||
|
||||
### Get Started
|
||||
|
||||
`NextTrace`默认使用`icmp`协议发起`TraceRoute`请求,该协议同时支持`IPv4`和`IPv6`
|
||||
|
||||
```bash
|
||||
# IPv4 ICMP Trace
|
||||
nexttrace 1.0.0.1
|
||||
|
||||
# 获得 route-path
|
||||
nexttrace -report 1.0.0.1
|
||||
|
||||
# 打开IP反向解析 rdns
|
||||
nexttrace -rdns 1.0.0.1
|
||||
|
||||
# 表格打印(一次性输出全部跳数,需等待20-40秒)
|
||||
nexttrace -table 1.0.0.1
|
||||
|
||||
# IPv6 ICMP Trace
|
||||
nexttrace 2606:4700:4700::1111
|
||||
```
|
||||
|
||||
`NextTrace`也可以使用`TCP`和`UDP`协议发起`Traceroute`请求,不过目前只支持`IPv4`
|
||||
```bash
|
||||
# TCP SYN Trace
|
||||
nexttrace -T www.bing.com
|
||||
|
||||
# 可以自行指定端口[此处为443],默认80端口
|
||||
nexttrace -T -p 443 1.0.0.1
|
||||
|
||||
# UDP Trace
|
||||
nexttrace -U 1.0.0.1
|
||||
|
||||
nexttrace -U -p 53 1.0.0.1
|
||||
```
|
||||
|
||||
### IP数据库
|
||||
|
||||
目前使用的IP数据库默认为我们自己搭建的API服务,如果后期遇到滥用,我们可能会选择关闭。
|
||||
|
||||
我们也会在后期开放服务端源代码,您也可以根据该项目的源码自行搭建属于您的API服务器。
|
||||
|
||||
NextTrace所有的的IP地理位置`API DEMO`可以参考[这里](https://github.com/xgadget-lab/nexttrace/blob/main/ipgeo/)
|
||||
|
||||
### 全部用法详见Usage菜单
|
||||
|
||||
```shell
|
||||
Usage of nexttrace:
|
||||
-T Use TCP SYN for tracerouting (default port is 80)
|
||||
-U Use UDP Package for tracerouting (default port is 53 in UDP)
|
||||
-d string
|
||||
Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight] (default "LeoMoeAPI")
|
||||
-displayMode string
|
||||
Choose The Display Mode [table, classic] (default "table")
|
||||
-m int
|
||||
Set the max number of hops (max TTL to be reached). (default 30)
|
||||
-p int
|
||||
Set SYN Traceroute Port (default 80)
|
||||
-q int
|
||||
Set the number of probes per each hop. (default 3)
|
||||
-r int
|
||||
Set ParallelRequests number. It should be 1 when there is a multi-routing. (default 18)
|
||||
-rdns
|
||||
Set whether rDNS will be display
|
||||
-report
|
||||
Route Path
|
||||
```
|
||||
## 项目截图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Thanks
|
||||
|
||||
[Sam Sam](https://github.com/samleong123) (samsam123@samsam123.name.my)
|
||||
|
||||
[Vincent Young](https://github.com/missuo) (i@yyt.moe)
|
||||
|
||||
[waiting4new](https://github.com/waiting4new)
|
||||
|
||||
FFEE_CO
|
||||
|
||||
nsnnns
|
||||
|
||||
## IP Database Copyright
|
||||
|
||||
### IPv4 Database
|
||||
|
||||
#### China MainLand
|
||||
|
||||
* 项目组自行维护 ~ 御三家骨干网数据 ~ 5%
|
||||
|
||||
* 埃文科技 Paid Database ~ 95%
|
||||
|
||||
#### WorldWide
|
||||
|
||||
* 埃文科技 Paid Database ~ 15%
|
||||
|
||||
* IpInfo Free ~ 15%
|
||||
|
||||
* IPInSight Free ~ 70%
|
||||
|
||||
### IPv6 Database
|
||||
|
||||
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/logo.png
Normal file
BIN
asset/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
asset/screenshot.png
Normal file
BIN
asset/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 866 KiB |
BIN
asset/screenshot2.png
Normal file
BIN
asset/screenshot2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
BIN
asset/screenshot3.png
Normal file
BIN
asset/screenshot3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
12
go.mod
12
go.mod
@@ -5,22 +5,24 @@ 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
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/panjf2000/ants/v2 v2.5.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rodaine/table v1.0.1 // indirect
|
||||
github.com/stretchr/testify v1.7.1 // indirect
|
||||
github.com/tidwall/gjson v1.14.1 // indirect
|
||||
github.com/rodaine/table v1.0.1
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/tidwall/gjson v1.14.1
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
||||
12
go.sum
12
go.sum
@@ -1,5 +1,7 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
@@ -9,7 +11,10 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/panjf2000/ants/v2 v2.5.0 h1:1rWGWSnxCsQBga+nQbA4/iY6VMeNoOIAM0ZWh9u3q2Q=
|
||||
github.com/panjf2000/ants/v2 v2.5.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
||||
@@ -33,18 +38,21 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-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-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
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=
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package ipgeo
|
||||
|
||||
import "strings"
|
||||
|
||||
type IPGeoData struct {
|
||||
Asnumber string
|
||||
Country string
|
||||
@@ -11,3 +13,16 @@ type IPGeoData struct {
|
||||
}
|
||||
|
||||
type Source = func(ip string) (*IPGeoData, error)
|
||||
|
||||
func GetSource(s string) Source {
|
||||
switch strings.ToUpper(s) {
|
||||
case "LEOMOEAPI":
|
||||
return LeoIP
|
||||
case "IP.SB":
|
||||
return IPSB
|
||||
case "IPINSIGHT":
|
||||
return IPInSight
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,25 @@ package ipgeo
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPSB(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://api.ip.sb/geoip/" + ip)
|
||||
url := "https://api.ip.sb/geoip/" + ip
|
||||
client := &http.Client{
|
||||
// 2秒超时
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
// 设置 UA,ip.sb 默认禁止 go-client User-Agent 的 api 请求
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0")
|
||||
content, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, _ := ioutil.ReadAll(content.Body)
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
return &IPGeoData{
|
||||
|
||||
@@ -8,6 +8,6 @@ type tokenData struct {
|
||||
|
||||
var token = tokenData{
|
||||
ipinsight: "",
|
||||
ipinfo: "42764a944dabd0",
|
||||
ipinfo: "",
|
||||
ipleo: "NextTraceDemo",
|
||||
}
|
||||
|
||||
148
main.go
148
main.go
@@ -3,85 +3,109 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
"github.com/xgadget-lab/nexttrace/methods/tcp"
|
||||
"github.com/xgadget-lab/nexttrace/methods/udp"
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/printer"
|
||||
"github.com/xgadget-lab/nexttrace/reporter"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"github.com/xgadget-lab/nexttrace/util/printer"
|
||||
)
|
||||
|
||||
var tcpSYNFlag = flag.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80 in TCP, 53 in UDP)")
|
||||
var tcpSYNFlag = flag.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80)")
|
||||
var udpPackageFlag = flag.Bool("U", false, "Use UDP Package for tracerouting (default port is 53 in UDP)")
|
||||
var port = flag.Int("p", 80, "Set SYN Traceroute Port")
|
||||
var numMeasurements = flag.Int("q", 3, "Set the number of probes per each hop.")
|
||||
var parallelRequests = flag.Int("r", 18, "Set ParallelRequests number. It should be 1 when there is a multi-routing.")
|
||||
var maxHops = flag.Int("m", 30, "Set the max number of hops (max TTL to be reached).")
|
||||
var dataOrigin = flag.String("d", "LeoMoeAPI", "Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight]")
|
||||
var displayMode = flag.String("displayMode", "table", "Choose The Display Mode [table, Besttrace]")
|
||||
|
||||
func main() {
|
||||
printer.PrintCopyRight()
|
||||
domain := flagApply()
|
||||
ip := util.DomainLookUp(domain)
|
||||
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
|
||||
|
||||
if *tcpSYNFlag {
|
||||
tcpTraceroute := tcp.New(ip, methods.TracerouteConfig{
|
||||
MaxHops: uint16(*maxHops),
|
||||
NumMeasurements: uint16(*numMeasurements),
|
||||
ParallelRequests: uint16(*parallelRequests),
|
||||
Port: *port,
|
||||
Timeout: time.Second / 2,
|
||||
})
|
||||
res, err := tcpTraceroute.Start()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("请赋予 sudo (root) 权限运行本程序")
|
||||
} else {
|
||||
if *displayMode == "Besttrace" {
|
||||
printer.TraceroutePrinter(ip, *res, *dataOrigin)
|
||||
} else if *displayMode == "table" {
|
||||
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
|
||||
} else {
|
||||
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if *port == 80 {
|
||||
*port = 53
|
||||
}
|
||||
udpTraceroute := udp.New(ip, true, methods.TracerouteConfig{
|
||||
MaxHops: uint16(*maxHops),
|
||||
NumMeasurements: uint16(*numMeasurements),
|
||||
ParallelRequests: uint16(*parallelRequests),
|
||||
Port: *port,
|
||||
Timeout: 2 * time.Second,
|
||||
})
|
||||
res, err := udpTraceroute.Start()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("请赋予 sudo (root) 权限运行本程序")
|
||||
} else {
|
||||
if *displayMode == "Besttrace" {
|
||||
printer.TraceroutePrinter(ip, *res, *dataOrigin)
|
||||
} else if *displayMode == "table" {
|
||||
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
|
||||
} else {
|
||||
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var rdnsenable = flag.Bool("rdns", false, "Set whether rDNS will be display")
|
||||
var routePath = flag.Bool("report", false, "Route Path")
|
||||
var realtimePrint = flag.Bool("realtime", false, "Output trace results in runtime")
|
||||
var tablePrint = flag.Bool("table", false, "Output trace results as table")
|
||||
var version = flag.Bool("V", false, "Check Version")
|
||||
|
||||
func flagApply() string {
|
||||
flag.Parse()
|
||||
if *version {
|
||||
printer.Version()
|
||||
os.Exit(0)
|
||||
}
|
||||
ipArg := flag.Args()
|
||||
if flag.NArg() != 1 {
|
||||
fmt.Println("Args Error\nUsage : ./bettertrace [-T] [-d <dataOrigin> ] [ -m <hops> ] [ -p <port> ] [ -q <probes> ] [ -r <parallelrequests> ] <hostname>")
|
||||
fmt.Println("Args Error\nUsage : ./nexttrace [-T] [-rdns] [-displayMode <displayMode>] [-d <dataOrigin> ] [ -m <hops> ] [ -p <port> ] [ -q <probes> ] [ -r <parallelrequests> ] <hostname>")
|
||||
os.Exit(2)
|
||||
}
|
||||
return ipArg[0]
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
if os.Getuid() != 0 {
|
||||
log.Fatalln("Traceroute requires root/sudo privileges.")
|
||||
}
|
||||
|
||||
domain := flagApply()
|
||||
|
||||
ip := util.DomainLookUp(domain)
|
||||
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
|
||||
|
||||
var m trace.Method = ""
|
||||
|
||||
switch {
|
||||
case *tcpSYNFlag:
|
||||
m = trace.TCPTrace
|
||||
case *udpPackageFlag:
|
||||
m = trace.UDPTrace
|
||||
default:
|
||||
m = trace.ICMPTrace
|
||||
}
|
||||
|
||||
if !*tcpSYNFlag && *port == 80 {
|
||||
*port = 53
|
||||
}
|
||||
|
||||
var conf = trace.Config{
|
||||
DestIP: ip,
|
||||
DestPort: *port,
|
||||
MaxHops: *maxHops,
|
||||
NumMeasurements: *numMeasurements,
|
||||
ParallelRequests: *parallelRequests,
|
||||
RDns: *rdnsenable,
|
||||
IPGeoSource: ipgeo.GetSource(*dataOrigin),
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
|
||||
if m == trace.ICMPTrace && !*tablePrint {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
|
||||
res, err := trace.Traceroute(m, conf)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if *routePath {
|
||||
r := reporter.New(res, ip.String())
|
||||
r.Print()
|
||||
return
|
||||
}
|
||||
|
||||
if m == trace.ICMPTrace && *tablePrint {
|
||||
printer.TracerouteTablePrinter(res)
|
||||
}
|
||||
|
||||
if m == trace.TCPTrace || m == trace.UDPTrace {
|
||||
if *realtimePrint {
|
||||
printer.TraceroutePrinter(res)
|
||||
} else {
|
||||
printer.TracerouteTablePrinter(res)
|
||||
}
|
||||
}
|
||||
|
||||
printer.SpecialThanks()
|
||||
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package methods
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TracerouteHop type
|
||||
type TracerouteHop struct {
|
||||
Success bool
|
||||
Address net.Addr
|
||||
TTL uint16
|
||||
RTT *time.Duration
|
||||
}
|
||||
|
||||
type TracerouteConfig struct {
|
||||
MaxHops uint16
|
||||
NumMeasurements uint16
|
||||
ParallelRequests uint16
|
||||
|
||||
Port int
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func GetIPHeaderLength(data []byte) (int, error) {
|
||||
if len(data) < 1 {
|
||||
return 0, errors.New("received invalid IP header")
|
||||
}
|
||||
return int((data[0] & 0x0F) * 4), nil
|
||||
}
|
||||
|
||||
func GetICMPResponsePayload(data []byte) ([]byte, error) {
|
||||
length, err := GetIPHeaderLength(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) < length {
|
||||
return nil, errors.New("length of packet too short")
|
||||
}
|
||||
|
||||
return data[length:], nil
|
||||
}
|
||||
|
||||
func GetUDPSrcPort(data []byte) uint16 {
|
||||
srcPortBytes := data[:2]
|
||||
srcPort := binary.BigEndian.Uint16(srcPortBytes)
|
||||
return srcPort
|
||||
}
|
||||
|
||||
func GetTCPSeq(data []byte) uint32 {
|
||||
seqBytes := data[4:8]
|
||||
return binary.BigEndian.Uint32(seqBytes)
|
||||
}
|
||||
|
||||
func ReduceFinalResult(preliminary map[uint16][]TracerouteHop, maxHops uint16, destIP net.IP) map[uint16][]TracerouteHop {
|
||||
// reduce the results to remove all hops after the first encounter to final destination
|
||||
finalResults := map[uint16][]TracerouteHop{}
|
||||
for i := uint16(1); i < maxHops; i++ {
|
||||
foundFinal := false
|
||||
probes := preliminary[i]
|
||||
if probes == nil {
|
||||
break
|
||||
}
|
||||
finalResults[i] = []TracerouteHop{}
|
||||
for _, probe := range probes {
|
||||
if probe.Success && probe.Address.String() == destIP.String() {
|
||||
foundFinal = true
|
||||
}
|
||||
finalResults[i] = append(finalResults[i], probe)
|
||||
}
|
||||
if foundFinal {
|
||||
break
|
||||
}
|
||||
}
|
||||
return finalResults
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/listener_channel"
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
"github.com/xgadget-lab/nexttrace/parallel_limiter"
|
||||
"github.com/xgadget-lab/nexttrace/signal"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type inflightData struct {
|
||||
start time.Time
|
||||
ttl uint16
|
||||
}
|
||||
|
||||
type results struct {
|
||||
inflightRequests sync.Map
|
||||
|
||||
results map[uint16][]methods.TracerouteHop
|
||||
resultsMu sync.Mutex
|
||||
err error
|
||||
|
||||
concurrentRequests *parallel_limiter.ParallelLimiter
|
||||
reachedFinalHop *signal.Signal
|
||||
}
|
||||
|
||||
type Traceroute struct {
|
||||
opConfig opConfig
|
||||
trcrtConfig methods.TracerouteConfig
|
||||
results results
|
||||
}
|
||||
|
||||
type opConfig struct {
|
||||
icmpConn net.PacketConn
|
||||
tcpConn net.PacketConn
|
||||
tcpMu sync.Mutex
|
||||
|
||||
destIP net.IP
|
||||
srcIP net.IP
|
||||
|
||||
wg *sync.WaitGroup
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func New(destIP net.IP, config methods.TracerouteConfig) *Traceroute {
|
||||
return &Traceroute{
|
||||
opConfig: opConfig{
|
||||
destIP: destIP,
|
||||
},
|
||||
trcrtConfig: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) Start() (*map[uint16][]methods.TracerouteHop, error) {
|
||||
tr.opConfig.ctx, tr.opConfig.cancel = context.WithCancel(context.Background())
|
||||
|
||||
tr.opConfig.srcIP, _ = util.LocalIPPort(tr.opConfig.destIP)
|
||||
|
||||
var err error
|
||||
tr.opConfig.tcpConn, err = net.ListenPacket("ip4:tcp", tr.opConfig.srcIP.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr.opConfig.icmpConn, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
tr.opConfig.wg = &wg
|
||||
|
||||
tr.results = results{
|
||||
inflightRequests: sync.Map{},
|
||||
concurrentRequests: parallel_limiter.New(int(tr.trcrtConfig.ParallelRequests)),
|
||||
reachedFinalHop: signal.New(),
|
||||
|
||||
results: map[uint16][]methods.TracerouteHop{},
|
||||
}
|
||||
|
||||
return tr.start()
|
||||
}
|
||||
|
||||
func (tr *Traceroute) timeoutLoop() {
|
||||
ticker := time.NewTicker(tr.trcrtConfig.Timeout / 4)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
tr.results.inflightRequests.Range(func(key, value interface{}) bool {
|
||||
request := value.(inflightData)
|
||||
expired := time.Since(request.start) > tr.trcrtConfig.Timeout
|
||||
if !expired {
|
||||
return true
|
||||
}
|
||||
tr.results.inflightRequests.Delete(key)
|
||||
tr.addToResult(request.ttl, methods.TracerouteHop{
|
||||
Success: false,
|
||||
TTL: request.ttl,
|
||||
})
|
||||
tr.results.concurrentRequests.Finished()
|
||||
tr.opConfig.wg.Done()
|
||||
return true
|
||||
})
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
ticker.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) addToResult(ttl uint16, hop methods.TracerouteHop) {
|
||||
tr.results.resultsMu.Lock()
|
||||
defer tr.results.resultsMu.Unlock()
|
||||
if tr.results.results[ttl] == nil {
|
||||
tr.results.results[ttl] = []methods.TracerouteHop{}
|
||||
}
|
||||
|
||||
tr.results.results[ttl] = append(tr.results.results[ttl], hop)
|
||||
}
|
||||
|
||||
func (tr *Traceroute) handleICMPMessage(msg listener_channel.ReceivedMessage, data []byte) {
|
||||
header, err := methods.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sequenceNumber := methods.GetTCPSeq(header)
|
||||
val, ok := tr.results.inflightRequests.LoadAndDelete(sequenceNumber)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
request := val.(inflightData)
|
||||
elapsed := time.Since(request.start)
|
||||
if msg.Peer.String() == tr.opConfig.destIP.String() {
|
||||
tr.results.reachedFinalHop.Signal()
|
||||
}
|
||||
tr.addToResult(request.ttl, methods.TracerouteHop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
TTL: request.ttl,
|
||||
RTT: &elapsed,
|
||||
})
|
||||
tr.results.concurrentRequests.Finished()
|
||||
tr.opConfig.wg.Done()
|
||||
}
|
||||
|
||||
func (tr *Traceroute) icmpListener() {
|
||||
lc := listener_channel.New(tr.opConfig.icmpConn)
|
||||
|
||||
defer lc.Stop()
|
||||
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
body := rm.Body.(*icmp.TimeExceeded).Data
|
||||
tr.handleICMPMessage(msg, body)
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
body := rm.Body.(*icmp.DstUnreach).Data
|
||||
tr.handleICMPMessage(msg, body)
|
||||
default:
|
||||
log.Println("received icmp message of unknown type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) tcpListener() {
|
||||
lc := listener_channel.New(tr.opConfig.tcpConn)
|
||||
|
||||
defer lc.Stop()
|
||||
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() != tr.opConfig.destIP.String() {
|
||||
continue
|
||||
}
|
||||
// Decode a packet
|
||||
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
|
||||
// Get the TCP layer from this packet
|
||||
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
|
||||
val, ok := tr.results.inflightRequests.LoadAndDelete(tcp.Ack - 1)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
request := val.(inflightData)
|
||||
tr.results.concurrentRequests.Finished()
|
||||
elapsed := time.Since(request.start)
|
||||
if msg.Peer.String() == tr.opConfig.destIP.String() {
|
||||
tr.results.reachedFinalHop.Signal()
|
||||
}
|
||||
tr.addToResult(request.ttl, methods.TracerouteHop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
TTL: request.ttl,
|
||||
RTT: &elapsed,
|
||||
})
|
||||
tr.opConfig.wg.Done()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) sendMessage(ttl uint16) {
|
||||
_, srcPort := util.LocalIPPort(tr.opConfig.destIP)
|
||||
ipHeader := &layers.IPv4{
|
||||
SrcIP: tr.opConfig.srcIP,
|
||||
DstIP: tr.opConfig.destIP,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
TTL: uint8(ttl),
|
||||
}
|
||||
|
||||
sequenceNumber := uint32(rand.Intn(math.MaxUint32))
|
||||
|
||||
tcpHeader := &layers.TCP{
|
||||
SrcPort: layers.TCPPort(srcPort),
|
||||
DstPort: layers.TCPPort(tr.trcrtConfig.Port),
|
||||
Seq: sequenceNumber,
|
||||
SYN: true,
|
||||
Window: 14600,
|
||||
}
|
||||
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
tr.opConfig.tcpMu.Lock()
|
||||
defer tr.opConfig.tcpMu.Unlock()
|
||||
err := ipv4.NewPacketConn(tr.opConfig.tcpConn).SetTTL(int(ttl))
|
||||
if err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := tr.opConfig.tcpConn.WriteTo(buf.Bytes(), &net.IPAddr{IP: tr.opConfig.destIP}); err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
tr.results.inflightRequests.Store(sequenceNumber, inflightData{start: start, ttl: ttl})
|
||||
}
|
||||
|
||||
func (tr *Traceroute) sendLoop() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
defer tr.opConfig.wg.Done()
|
||||
|
||||
for ttl := uint16(1); ttl <= tr.trcrtConfig.MaxHops; ttl++ {
|
||||
select {
|
||||
case <-tr.results.reachedFinalHop.Chan():
|
||||
return
|
||||
default:
|
||||
}
|
||||
for i := 0; i < int(tr.trcrtConfig.NumMeasurements); i++ {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case <-tr.results.concurrentRequests.Start():
|
||||
tr.opConfig.wg.Add(1)
|
||||
go tr.sendMessage(ttl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) start() (*map[uint16][]methods.TracerouteHop, error) {
|
||||
go tr.timeoutLoop()
|
||||
go tr.icmpListener()
|
||||
go tr.tcpListener()
|
||||
|
||||
tr.opConfig.wg.Add(1)
|
||||
go tr.sendLoop()
|
||||
|
||||
tr.opConfig.wg.Wait()
|
||||
tr.opConfig.cancel()
|
||||
|
||||
if tr.results.err != nil {
|
||||
return nil, tr.results.err
|
||||
}
|
||||
|
||||
result := methods.ReduceFinalResult(tr.results.results, tr.trcrtConfig.MaxHops, tr.opConfig.destIP)
|
||||
|
||||
return &result, tr.results.err
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
package udp
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/listener_channel"
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
"github.com/xgadget-lab/nexttrace/methods/quic"
|
||||
"github.com/xgadget-lab/nexttrace/parallel_limiter"
|
||||
"github.com/xgadget-lab/nexttrace/signal"
|
||||
"github.com/xgadget-lab/nexttrace/taskgroup"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type inflightData struct {
|
||||
icmpMsg chan<- net.Addr
|
||||
}
|
||||
|
||||
type opConfig struct {
|
||||
quic bool
|
||||
destIP net.IP
|
||||
wg *taskgroup.TaskGroup
|
||||
|
||||
icmpConn net.PacketConn
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
type results struct {
|
||||
inflightRequests sync.Map
|
||||
|
||||
results map[uint16][]methods.TracerouteHop
|
||||
resultsMu sync.Mutex
|
||||
err error
|
||||
|
||||
concurrentRequests *parallel_limiter.ParallelLimiter
|
||||
reachedFinalHop *signal.Signal
|
||||
}
|
||||
|
||||
type Traceroute struct {
|
||||
trcrtConfig methods.TracerouteConfig
|
||||
opConfig opConfig
|
||||
results results
|
||||
}
|
||||
|
||||
func New(destIP net.IP, quic bool, config methods.TracerouteConfig) *Traceroute {
|
||||
return &Traceroute{
|
||||
opConfig: opConfig{
|
||||
quic: quic,
|
||||
destIP: destIP,
|
||||
},
|
||||
trcrtConfig: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) Start() (*map[uint16][]methods.TracerouteHop, error) {
|
||||
tr.opConfig.ctx, tr.opConfig.cancel = context.WithCancel(context.Background())
|
||||
|
||||
tr.results = results{
|
||||
inflightRequests: sync.Map{},
|
||||
concurrentRequests: parallel_limiter.New(int(tr.trcrtConfig.ParallelRequests)),
|
||||
results: map[uint16][]methods.TracerouteHop{},
|
||||
reachedFinalHop: signal.New(),
|
||||
}
|
||||
|
||||
var err error
|
||||
tr.opConfig.icmpConn, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tr.start()
|
||||
}
|
||||
|
||||
func (tr *Traceroute) addToResult(ttl uint16, hop methods.TracerouteHop) {
|
||||
tr.results.resultsMu.Lock()
|
||||
defer tr.results.resultsMu.Unlock()
|
||||
if tr.results.results[ttl] == nil {
|
||||
tr.results.results[ttl] = []methods.TracerouteHop{}
|
||||
}
|
||||
|
||||
tr.results.results[ttl] = append(tr.results.results[ttl], hop)
|
||||
}
|
||||
|
||||
func (tr *Traceroute) getUDPConn(try int) (net.IP, int, net.PacketConn) {
|
||||
srcIP, _ := util.LocalIPPort(tr.opConfig.destIP)
|
||||
|
||||
var ipString string
|
||||
|
||||
if srcIP == nil {
|
||||
ipString = ""
|
||||
} else {
|
||||
ipString = srcIP.String()
|
||||
}
|
||||
|
||||
udpConn, err := net.ListenPacket("udp", ipString+":0")
|
||||
if err != nil {
|
||||
if try > 3 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return tr.getUDPConn(try + 1)
|
||||
}
|
||||
|
||||
return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn
|
||||
}
|
||||
|
||||
func (tr *Traceroute) sendMessage(ttl uint16) {
|
||||
srcIP, srcPort, udpConn := tr.getUDPConn(0)
|
||||
|
||||
var payload []byte
|
||||
if tr.opConfig.quic {
|
||||
payload = quic.GenerateWithRandomIds()
|
||||
} else {
|
||||
ipHeader := &layers.IPv4{
|
||||
SrcIP: srcIP,
|
||||
DstIP: tr.opConfig.destIP,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
TTL: uint8(ttl),
|
||||
}
|
||||
|
||||
udpHeader := &layers.UDP{
|
||||
SrcPort: layers.UDPPort(srcPort),
|
||||
DstPort: layers.UDPPort(tr.trcrtConfig.Port),
|
||||
}
|
||||
_ = udpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, udpHeader, gopacket.Payload("HAJSFJHKAJSHFKJHAJKFHKASHKFHHKAFKHFAHSJK")); err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
payload = buf.Bytes()
|
||||
}
|
||||
|
||||
err := ipv4.NewPacketConn(udpConn).SetTTL(int(ttl))
|
||||
if err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
icmpMsg := make(chan net.Addr, 1)
|
||||
udpMsg := make(chan net.Addr, 1)
|
||||
|
||||
start := time.Now()
|
||||
if _, err := udpConn.WriteTo(payload, &net.UDPAddr{IP: tr.opConfig.destIP, Port: tr.trcrtConfig.Port}); err != nil {
|
||||
tr.results.err = err
|
||||
tr.opConfig.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
inflight := inflightData{
|
||||
icmpMsg: icmpMsg,
|
||||
}
|
||||
|
||||
tr.results.inflightRequests.Store(uint16(srcPort), inflight)
|
||||
|
||||
go func() {
|
||||
reply := make([]byte, 1500)
|
||||
_, peer, err := udpConn.ReadFrom(reply)
|
||||
if err != nil {
|
||||
// probably because we closed the connection
|
||||
return
|
||||
}
|
||||
udpMsg <- peer
|
||||
}()
|
||||
|
||||
select {
|
||||
case peer := <-icmpMsg:
|
||||
rtt := time.Since(start)
|
||||
if peer.(*net.IPAddr).IP.Equal(tr.opConfig.destIP) {
|
||||
tr.results.reachedFinalHop.Signal()
|
||||
}
|
||||
tr.addToResult(ttl, methods.TracerouteHop{
|
||||
Success: true,
|
||||
Address: peer,
|
||||
TTL: ttl,
|
||||
RTT: &rtt,
|
||||
})
|
||||
case peer := <-udpMsg:
|
||||
rtt := time.Since(start)
|
||||
ip := peer.(*net.UDPAddr).IP
|
||||
if ip.Equal(tr.opConfig.destIP) {
|
||||
tr.results.reachedFinalHop.Signal()
|
||||
}
|
||||
tr.addToResult(ttl, methods.TracerouteHop{
|
||||
Success: true,
|
||||
Address: &net.IPAddr{IP: ip},
|
||||
TTL: ttl,
|
||||
RTT: &rtt,
|
||||
})
|
||||
case <-time.After(tr.trcrtConfig.Timeout):
|
||||
tr.addToResult(ttl, methods.TracerouteHop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: ttl,
|
||||
RTT: nil,
|
||||
})
|
||||
}
|
||||
|
||||
tr.results.inflightRequests.Delete(uint16(srcPort))
|
||||
udpConn.Close()
|
||||
tr.results.concurrentRequests.Finished()
|
||||
tr.opConfig.wg.Done()
|
||||
}
|
||||
|
||||
func (tr *Traceroute) handleICMPMessage(msg listener_channel.ReceivedMessage, data []byte) {
|
||||
header, err := methods.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
srcPort := methods.GetUDPSrcPort(header)
|
||||
val, ok := tr.results.inflightRequests.LoadAndDelete(srcPort)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
request := val.(inflightData)
|
||||
request.icmpMsg <- msg.Peer
|
||||
}
|
||||
|
||||
func (tr *Traceroute) icmpListener() {
|
||||
lc := listener_channel.New(tr.opConfig.icmpConn)
|
||||
|
||||
defer lc.Stop()
|
||||
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
body := rm.Body.(*icmp.TimeExceeded).Data
|
||||
tr.handleICMPMessage(msg, body)
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
body := rm.Body.(*icmp.DstUnreach).Data
|
||||
tr.handleICMPMessage(msg, body)
|
||||
default:
|
||||
log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) sendLoop() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
for ttl := uint16(1); ttl <= tr.trcrtConfig.MaxHops; ttl++ {
|
||||
select {
|
||||
case <-tr.results.reachedFinalHop.Chan():
|
||||
return
|
||||
default:
|
||||
}
|
||||
for i := 0; i < int(tr.trcrtConfig.NumMeasurements); i++ {
|
||||
select {
|
||||
case <-tr.opConfig.ctx.Done():
|
||||
return
|
||||
case <-tr.results.concurrentRequests.Start():
|
||||
tr.opConfig.wg.Add()
|
||||
go tr.sendMessage(ttl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *Traceroute) start() (*map[uint16][]methods.TracerouteHop, error) {
|
||||
go tr.icmpListener()
|
||||
|
||||
wg := taskgroup.New()
|
||||
tr.opConfig.wg = wg
|
||||
|
||||
tr.sendLoop()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
tr.opConfig.cancel()
|
||||
tr.opConfig.icmpConn.Close()
|
||||
|
||||
if tr.results.err != nil {
|
||||
return nil, tr.results.err
|
||||
}
|
||||
|
||||
result := methods.ReduceFinalResult(tr.results.results, tr.trcrtConfig.MaxHops, tr.opConfig.destIP)
|
||||
|
||||
return &result, tr.results.err
|
||||
}
|
||||
266
nt_install.sh
Normal file
266
nt_install.sh
Normal file
@@ -0,0 +1,266 @@
|
||||
#!/bin/bash
|
||||
|
||||
usrPath="/usr/local/bin"
|
||||
|
||||
checkRootPermit() {
|
||||
[[ $EUID -ne 0 ]] && echo "请使用sudo/root权限运行本脚本" && exit 1
|
||||
}
|
||||
|
||||
checkSystemArch() {
|
||||
arch=$(uname -m)
|
||||
if [[ $arch == "x86_64" ]]; then
|
||||
archParam="amd64"
|
||||
fi
|
||||
|
||||
if [[ $arch == "aarch64" ]]; then
|
||||
archParam="arm64"
|
||||
fi
|
||||
|
||||
if [[ $arch == "arm64" ]]; then
|
||||
archParam="arm64"
|
||||
fi
|
||||
|
||||
if [[ $archParam == "" ]]; then
|
||||
echo "未知的系统架构,请联系作者"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
checkSystemDistribution() {
|
||||
case "$OSTYPE" in
|
||||
darwin*)
|
||||
osDistribution="darwin"
|
||||
downPath="/var/tmp/nexttrace"
|
||||
;;
|
||||
linux*)
|
||||
osDistribution="linux"
|
||||
downPath="/var/tmp/nexttrace"
|
||||
;;
|
||||
*)
|
||||
echo "unknown: $OSTYPE"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
echo "正在获取地理位置信息..."
|
||||
countryCode=$(curl -s "http://ip-api.com/line/?fields=countryCode")
|
||||
}
|
||||
|
||||
installWgetPackage() {
|
||||
# macOS should install wget originally. Nothing to do
|
||||
echo "wget 正在安装中..."
|
||||
# try apt
|
||||
apt-get -h &>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
# 先更新一下数据源,有些机器数据源比较老可能会404
|
||||
apt-get update -y &>/dev/null
|
||||
apt-get install wget -y &>/dev/null
|
||||
fi
|
||||
|
||||
# try yum
|
||||
yum -h &>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
yum -y update &>/dev/null
|
||||
yum install wget -y &>/dev/null
|
||||
fi
|
||||
|
||||
# try dnf
|
||||
dnf -h &>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
dnf check-update &>/dev/null
|
||||
dnf install wget -y &>/dev/null
|
||||
fi
|
||||
|
||||
# try pacman
|
||||
pacman -h &>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
pacman -Sy &>/dev/null
|
||||
pacman -S wget &>/dev/null
|
||||
fi
|
||||
|
||||
wget -h &>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "wget 安装失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
installJqPackage() {
|
||||
# macOS should install wget originally. Nothing to do
|
||||
echo "jq 正在安装中..."
|
||||
# try apt
|
||||
apt-get -h &>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
# 先更新一下数据源,有些机器数据源比较老可能会404
|
||||
apt-get update -y &>/dev/null
|
||||
apt-get install jq -y &>/dev/null
|
||||
fi
|
||||
|
||||
# try yum
|
||||
yum -h &>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
yum -y update &>/dev/null
|
||||
yum install jq -y &>/dev/null
|
||||
fi
|
||||
|
||||
# try dnf
|
||||
dnf -h &>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
dnf check-update &>/dev/null
|
||||
dnf install jq -y &>/dev/null
|
||||
fi
|
||||
|
||||
# try pacman
|
||||
pacman -h &>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
pacman -Sy &>/dev/null
|
||||
pacman -S jq &>/dev/null
|
||||
fi
|
||||
|
||||
jq -h &>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "jq 安装失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
checkWgetPackage() {
|
||||
wget -h &>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
read -r -p "您还没有安装wget,是否安装? (y/n)" input
|
||||
|
||||
case $input in
|
||||
[yY][eE][sS] | [yY])
|
||||
installWgetPackage
|
||||
;;
|
||||
|
||||
[nN][oO] | [nN])
|
||||
echo "您选择了取消安装,脚本即将退出"
|
||||
exit 1
|
||||
;;
|
||||
|
||||
*)
|
||||
installWgetPackage
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
checkVersion() {
|
||||
echo "正在检查版本..."
|
||||
version=$(curl -sL https://api.github.com/repos/xgadget-lab/nexttrace/releases/latest | jq -r '.tag_name')
|
||||
if [[ $version == "" ]]; then
|
||||
echo "获取版本失败,请检查网络连接"
|
||||
exit 1
|
||||
fi
|
||||
echo 当前最新release版本:${version}
|
||||
read -r -p "是否安装/更新软件? (y/n)" input
|
||||
case $input in
|
||||
[yY][eE][sS] | [yY])
|
||||
break
|
||||
;;
|
||||
[nN][oO] | [nN])
|
||||
echo "您选择了取消安装/更新,脚本即将退出"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
checkJqPackage() {
|
||||
jq -h &>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "您还没有安装jq, 当您取消安装,我们会使用awk获取当前版本号。"
|
||||
read -r -p "但是如遇Github变更API,这可能会存在问题,是否安装? (y/n)" input
|
||||
|
||||
case $input in
|
||||
[yY][eE][sS] | [yY])
|
||||
installJqPackage
|
||||
;;
|
||||
|
||||
[nN][oO] | [nN])
|
||||
echo "您选择了取消安装"
|
||||
return 0
|
||||
;;
|
||||
|
||||
*)
|
||||
installJqPackage
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
downloadBinrayFile() {
|
||||
echo "正在获取最新版的 NextTrace 发行版文件信息..."
|
||||
checkJqPackage
|
||||
# 简单说明一下,Github提供了一个API,可以获取最新发行版本的二进制文件下载地址(对应的是browser_download_url),根据刚刚测得的osDistribution、archParam,获取对应的下载地址
|
||||
if [[ $? -eq 1 ]]; then
|
||||
# 支持 jq 不回退
|
||||
# echo nexttrace_${osDistribution}_${archParam}
|
||||
latestURL=$(curl -s https://api.github.com/repos/xgadget-lab/nexttrace/releases/latest | jq ".assets[] | select(.name == \"nexttrace_${osDistribution}_${archParam}\") | .browser_download_url")
|
||||
latestURL=${latestURL:1:-1}
|
||||
else
|
||||
# 不支持 jq,用户拒绝安装,回退 awk
|
||||
latestURL=$(curl -s https://api.github.com/repos/xgadget-lab/nexttrace/releases/latest | grep -i "browser_download_url.*${osDistribution}.*${archParam}" | awk -F '"' '{print $4}')
|
||||
fi
|
||||
if [ "$countryCode" == "CN" ]; then
|
||||
read -r -p "检测到国内网络环境,是否使用镜像下载以加速(y/n)" input
|
||||
case $input in
|
||||
[yY][eE][sS] | [yY])
|
||||
latestURL="https://ghproxy.com/"$latestURL
|
||||
;;
|
||||
|
||||
[nN][oO] | [nN])
|
||||
echo "您选择了不使用镜像,下载可能会变得异常缓慢,或者失败"
|
||||
;;
|
||||
|
||||
*)
|
||||
latestURL="https://ghproxy.com/"$latestURL
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "正在下载 NextTrace 二进制文件..."
|
||||
wget -O ${downPath} ${latestURL} &>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "NextTrace 现在已经在您的系统中可用"
|
||||
changeMode
|
||||
mv ${downPath} ${usrPath}
|
||||
if [[ ${osDistribution} == "macOS" ]]; then
|
||||
xattr -r -d com.apple.quarantine ${usrPath}/nexttrace
|
||||
fi
|
||||
else
|
||||
echo "NextTrace 下载失败,请检查您的网络是否正常"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
changeMode() {
|
||||
chmod +x ${downPath} &>/dev/null
|
||||
}
|
||||
|
||||
runBinrayFileHelp() {
|
||||
if [ -e ${usrPath} ]; then
|
||||
${usrPath}/nexttrace -h
|
||||
fi
|
||||
}
|
||||
|
||||
# Check Procedure
|
||||
checkRootPermit
|
||||
checkSystemDistribution
|
||||
checkSystemArch
|
||||
checkWgetPackage
|
||||
# TODO: 检查版本并更新
|
||||
#checkVersion
|
||||
|
||||
# Download Procedure
|
||||
getLocation
|
||||
downloadBinrayFile
|
||||
|
||||
# Run Procedure
|
||||
runBinrayFileHelp
|
||||
@@ -1,52 +0,0 @@
|
||||
package parallel_limiter
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ParallelLimiter struct {
|
||||
maxCount int
|
||||
|
||||
mu sync.Mutex
|
||||
currentRunning int
|
||||
|
||||
waiting []chan struct{}
|
||||
}
|
||||
|
||||
func New(count int) *ParallelLimiter {
|
||||
return &ParallelLimiter{
|
||||
maxCount: count,
|
||||
|
||||
currentRunning: 0,
|
||||
waiting: []chan struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParallelLimiter) Start() chan struct{} {
|
||||
p.mu.Lock()
|
||||
if p.currentRunning+1 > p.maxCount {
|
||||
waitChan := make(chan struct{})
|
||||
p.waiting = append(p.waiting, waitChan)
|
||||
p.mu.Unlock()
|
||||
return waitChan
|
||||
}
|
||||
p.currentRunning++
|
||||
p.mu.Unlock()
|
||||
instantResolveChan := make(chan struct{})
|
||||
go func() {
|
||||
instantResolveChan <- struct{}{}
|
||||
}()
|
||||
return instantResolveChan
|
||||
}
|
||||
|
||||
func (p *ParallelLimiter) Finished() {
|
||||
p.mu.Lock()
|
||||
if len(p.waiting) > 0 {
|
||||
first := p.waiting[0]
|
||||
p.waiting = p.waiting[1:]
|
||||
first <- struct{}{}
|
||||
p.currentRunning++
|
||||
}
|
||||
p.currentRunning--
|
||||
p.mu.Unlock()
|
||||
}
|
||||
@@ -5,8 +5,12 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func PrintCopyRight() {
|
||||
fmt.Println("NextTrace v0.1.0 Alpha \nxgadget-lab 实验室 zhsh (xzhsh.ch) & leo (leo.moe)")
|
||||
func SpecialThanks() {
|
||||
fmt.Println("\nSpecial thanks to Vincent Young (OwO Network / vincent.moe) for his help and support for this project.")
|
||||
}
|
||||
|
||||
func Version() {
|
||||
fmt.Println("NextTrace v0.1.2-Beta.missuo-special-version")
|
||||
}
|
||||
|
||||
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) {
|
||||
89
printer/printer.go
Normal file
89
printer/printer.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
)
|
||||
|
||||
var dataOrigin string
|
||||
|
||||
func TraceroutePrinter(res *trace.Result) {
|
||||
for i, hop := range res.Hops {
|
||||
fmt.Print(i + 1)
|
||||
for _, h := range hop {
|
||||
HopPrinter(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HopPrinter(h trace.Hop) {
|
||||
if h.Address == nil {
|
||||
fmt.Println("\t*")
|
||||
} else {
|
||||
txt := "\t"
|
||||
|
||||
if h.Hostname == "" {
|
||||
txt += fmt.Sprint(h.Address, " ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
|
||||
} else {
|
||||
txt += fmt.Sprint(h.Hostname, " (", h.Address, ") ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
|
||||
}
|
||||
|
||||
if h.Geo != nil {
|
||||
txt += " " + formatIpGeoData(h.Address.String(), h.Geo)
|
||||
}
|
||||
|
||||
fmt.Println(txt)
|
||||
}
|
||||
}
|
||||
|
||||
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
|
||||
var res = make([]string, 0, 10)
|
||||
|
||||
if data.Asnumber == "" {
|
||||
res = append(res, "*")
|
||||
} else {
|
||||
res = append(res, "AS"+data.Asnumber)
|
||||
}
|
||||
|
||||
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
|
||||
// TODO: 移动IDC判断到Hop.fetchIPData函数,减少API调用
|
||||
if strings.HasPrefix(ip, "9.") {
|
||||
res = append(res, "局域网", "腾讯云")
|
||||
} else if strings.HasPrefix(ip, "11.") {
|
||||
res = append(res, "局域网", "阿里云")
|
||||
} else if data.Country == "" {
|
||||
res = append(res, "局域网")
|
||||
} else {
|
||||
// 有些IP的归属信息为空,这个时候将ISP的信息填入
|
||||
if data.Owner == "" {
|
||||
data.Owner = data.Isp
|
||||
}
|
||||
if data.District != "" {
|
||||
data.City = data.City + ", " + data.District
|
||||
}
|
||||
if data.Prov == "" && data.City == "" {
|
||||
// anyCast或是骨干网数据不应该有国家信息
|
||||
data.Owner = data.Owner + ", " + data.Owner
|
||||
} else {
|
||||
// 非骨干网正常填入IP的国家信息数据
|
||||
res = append(res, data.Country)
|
||||
}
|
||||
|
||||
if data.Prov != "" {
|
||||
res = append(res, data.Prov)
|
||||
}
|
||||
if data.City != "" {
|
||||
res = append(res, data.City)
|
||||
}
|
||||
|
||||
if data.Owner != "" {
|
||||
res = append(res, data.Owner)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(res, ", ")
|
||||
}
|
||||
15
printer/realtime_printer.go
Normal file
15
printer/realtime_printer.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
func RealtimePrinter(res *trace.Result, ttl int) {
|
||||
fmt.Print(ttl)
|
||||
for i := range res.Hops[ttl] {
|
||||
HopPrinter(res.Hops[ttl][i])
|
||||
}
|
||||
|
||||
}
|
||||
119
printer/tableprinter.go
Normal file
119
printer/tableprinter.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/rodaine/table"
|
||||
)
|
||||
|
||||
type rowData struct {
|
||||
Hop string
|
||||
IP string
|
||||
Latency string
|
||||
Asnumber string
|
||||
Country string
|
||||
Prov string
|
||||
City string
|
||||
District string
|
||||
Owner string
|
||||
}
|
||||
|
||||
func TracerouteTablePrinter(res *trace.Result) {
|
||||
// 初始化表格
|
||||
tbl := New()
|
||||
for _, hop := range res.Hops {
|
||||
for k, h := range hop {
|
||||
data := tableDataGenerator(h)
|
||||
if k > 0 {
|
||||
data.Hop = ""
|
||||
}
|
||||
if data.Country == "" && data.Prov == "" && data.City == "" {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, "", data.Owner)
|
||||
} else {
|
||||
if data.City != "" {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country+", "+data.Prov+", "+data.City, data.Owner)
|
||||
} else {
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Owner)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// 打印表格
|
||||
tbl.Print()
|
||||
}
|
||||
|
||||
func New() table.Table {
|
||||
// 初始化表格
|
||||
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
|
||||
columnFmt := color.New(color.FgYellow).SprintfFunc()
|
||||
|
||||
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Location", "Owner")
|
||||
tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
|
||||
return tbl
|
||||
}
|
||||
|
||||
func tableDataGenerator(h trace.Hop) *rowData {
|
||||
if h.Address == nil {
|
||||
return &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: "*",
|
||||
}
|
||||
} else {
|
||||
lantency := fmt.Sprintf("%.2fms", h.RTT.Seconds()*1000)
|
||||
IP := h.Address.String()
|
||||
|
||||
if strings.HasPrefix(IP, "9.") {
|
||||
return &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Country: "局域网",
|
||||
Owner: "腾讯云",
|
||||
}
|
||||
} else if strings.HasPrefix(IP, "11.") {
|
||||
return &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Country: "局域网",
|
||||
Owner: "阿里云",
|
||||
}
|
||||
}
|
||||
|
||||
if h.Hostname != "" {
|
||||
IP = fmt.Sprint(h.Hostname, " (", IP, ") ")
|
||||
}
|
||||
|
||||
r := &rowData{
|
||||
Hop: fmt.Sprint(h.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Asnumber: h.Geo.Asnumber,
|
||||
Country: h.Geo.Country,
|
||||
Prov: h.Geo.Prov,
|
||||
City: h.Geo.City,
|
||||
District: h.Geo.District,
|
||||
Owner: h.Geo.Owner,
|
||||
}
|
||||
|
||||
if h.Geo == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
if h.Geo.Owner == "" {
|
||||
h.Geo.Owner = h.Geo.Isp
|
||||
}
|
||||
r.Asnumber = h.Geo.Asnumber
|
||||
r.Country = h.Geo.Country
|
||||
r.Prov = h.Geo.Prov
|
||||
r.City = h.Geo.City
|
||||
r.District = h.Geo.District
|
||||
r.Owner = h.Geo.Owner
|
||||
return r
|
||||
}
|
||||
}
|
||||
131
reporter/reporter.go
Normal file
131
reporter/reporter.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
)
|
||||
|
||||
type Reporter interface {
|
||||
Print()
|
||||
}
|
||||
|
||||
func New(rs *trace.Result, ip string) Reporter {
|
||||
experimentTag()
|
||||
r := reporter{
|
||||
routeResult: rs,
|
||||
targetIP: ip,
|
||||
}
|
||||
return &r
|
||||
}
|
||||
|
||||
type reporter struct {
|
||||
targetIP string
|
||||
routeReport map[uint16][]routeReportNode
|
||||
routeResult *trace.Result
|
||||
}
|
||||
|
||||
type routeReportNode struct {
|
||||
asn string
|
||||
isp string
|
||||
geo []string
|
||||
ix bool
|
||||
}
|
||||
|
||||
func experimentTag() {
|
||||
fmt.Println("Route-Path 功能实验室")
|
||||
}
|
||||
|
||||
func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData) (routeReportNode, error) {
|
||||
rpn := routeReportNode{}
|
||||
go func() {
|
||||
ptr, err := net.LookupAddr(ip)
|
||||
if err == nil {
|
||||
if strings.Contains(strings.ToLower(ptr[0]), "ix") {
|
||||
rpn.ix = true
|
||||
} else {
|
||||
rpn.ix = false
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if strings.Contains(strings.ToLower(ipGeoData.Isp), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Isp), "ix") || strings.Contains(strings.ToLower(ipGeoData.Owner), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Owner), "ix") {
|
||||
rpn.ix = true
|
||||
}
|
||||
if strings.HasPrefix(ip, "59.43") {
|
||||
rpn.asn = "4809"
|
||||
} else {
|
||||
rpn.asn = ipGeoData.Asnumber
|
||||
}
|
||||
// 无论最后一跳是否为存在地理位置信息(AnyCast),都应该给予显示
|
||||
if ipGeoData.Country == "" || ipGeoData.City == "" || ipGeoData.City == "-" && ip != r.targetIP {
|
||||
return rpn, errors.New("GeoData Search Failed")
|
||||
} else {
|
||||
if ipGeoData.City == "" {
|
||||
rpn.geo = []string{ipGeoData.Country, ipGeoData.Country}
|
||||
} else {
|
||||
rpn.geo = []string{ipGeoData.Country, ipGeoData.City}
|
||||
}
|
||||
}
|
||||
if ipGeoData.Isp == "" {
|
||||
rpn.isp = ipGeoData.Owner
|
||||
} else {
|
||||
rpn.isp = ipGeoData.Isp
|
||||
}
|
||||
return rpn, nil
|
||||
}
|
||||
|
||||
func (r *reporter) InitialBaseData() Reporter {
|
||||
var nodeIndex uint16 = 1
|
||||
reportNodes := map[uint16][]routeReportNode{}
|
||||
for i := uint16(0); int(i) < len(r.routeResult.Hops); i++ {
|
||||
traceHop := r.routeResult.Hops[i][0]
|
||||
if traceHop.Success {
|
||||
currentIP := traceHop.Address.String()
|
||||
rpn, err := r.generateRouteReportNode(currentIP, *traceHop.Geo)
|
||||
if err == nil {
|
||||
reportNodes[nodeIndex] = append(reportNodes[nodeIndex], rpn)
|
||||
nodeIndex += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
r.routeReport = reportNodes
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *reporter) Print() {
|
||||
r.InitialBaseData()
|
||||
for i := uint16(1); int(i) < len(r.routeReport)+1; i++ {
|
||||
nodeReport := r.routeReport[i][0]
|
||||
if i == 1 {
|
||||
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||||
} else {
|
||||
nodeReportBefore := r.routeReport[i-1][0]
|
||||
if nodeReportBefore.asn == nodeReport.asn {
|
||||
// Same ASN but Coutry or City Changed
|
||||
if nodeReportBefore.geo[0] != nodeReport.geo[0] {
|
||||
fmt.Printf("』→ %s『%s", nodeReport.geo[0], nodeReport.geo[1])
|
||||
} else {
|
||||
if nodeReportBefore.geo[1] != nodeReport.geo[1] {
|
||||
fmt.Printf(" → %s", nodeReport.geo[1])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("』」")
|
||||
if int(i) != len(r.routeReport)+1 {
|
||||
fmt.Printf("\n ╭╯\n ╰")
|
||||
}
|
||||
if nodeReport.ix {
|
||||
fmt.Printf("AS%s \033[42;37mIXP\033[0m %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||||
} else {
|
||||
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("』」")
|
||||
}
|
||||
31
reporter/reporter_test.go
Normal file
31
reporter/reporter_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
)
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
ip := util.DomainLookUp("213.226.68.73")
|
||||
var m trace.Method = "tcp"
|
||||
var conf = trace.Config{
|
||||
DestIP: ip,
|
||||
DestPort: 80,
|
||||
MaxHops: 30,
|
||||
NumMeasurements: 1,
|
||||
ParallelRequests: 1,
|
||||
RDns: true,
|
||||
IPGeoSource: ipgeo.GetSource("LeoMoeAPI"),
|
||||
Timeout: 2 * time.Second,
|
||||
|
||||
//Quic: false,
|
||||
}
|
||||
|
||||
res, _ := trace.Traceroute(m, conf)
|
||||
r := New(res, ip.String())
|
||||
r.Print()
|
||||
}
|
||||
175
trace/icmp_ipv4.go
Normal file
175
trace/icmp_ipv4.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type ICMPTracer struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
t.icmpListen, err = net.ListenPacket("ip4:1", "0.0.0.0")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
defer t.icmpListen.Close()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.resCh = make(chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
for ttl := 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)
|
||||
}
|
||||
// 一组TTL全部退出(收到应答或者超时终止)以后,再进行下一个TTL的包发送
|
||||
t.wg.Wait()
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl-1)
|
||||
}
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) listenICMP() {
|
||||
lc := NewPacketListener(t.icmpListen, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv4.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
|
||||
t.resCh <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) send(ttl int) error {
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
icmpHeader := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
},
|
||||
}
|
||||
|
||||
ipv4.NewPacketConn(t.icmpListen).SetTTL(ttl)
|
||||
|
||||
wb, err := icmpHeader.Marshal(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := t.icmpListen.WriteTo(wb, &net.IPAddr{IP: t.DestIP}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-t.resCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
h.fetchIPData(t.Config)
|
||||
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: ttl,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
179
trace/icmp_ipv6.go
Normal file
179
trace/icmp_ipv6.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
type ICMPTracerv6 struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
t.icmpListen, err = net.ListenPacket("ip6:58", "::")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
defer t.icmpListen.Close()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.resCh = make(chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
for ttl := 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)
|
||||
}
|
||||
// 一组TTL全部退出(收到应答或者超时终止)以后,再进行下一个TTL的包发送
|
||||
t.wg.Wait()
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl-1)
|
||||
}
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) listenICMP() {
|
||||
lc := NewPacketListener(t.icmpListen, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
// log.Println(msg.Peer)
|
||||
switch rm.Type {
|
||||
case ipv6.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv6.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
|
||||
t.resCh <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) send(ttl int) error {
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
icmpHeader := icmp.Message{
|
||||
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
},
|
||||
}
|
||||
|
||||
p := ipv6.NewPacketConn(t.icmpListen)
|
||||
|
||||
icmpHeader.Body.(*icmp.Echo).Seq = ttl
|
||||
p.SetHopLimit(ttl)
|
||||
|
||||
wb, err := icmpHeader.Marshal(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := t.icmpListen.WriteTo(wb, &net.IPAddr{IP: t.DestIP}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-t.resCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
h.fetchIPData(t.Config)
|
||||
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: ttl,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
55
trace/packet_listener.go
Normal file
55
trace/packet_listener.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ReceivedMessage struct {
|
||||
N *int
|
||||
Peer net.Addr
|
||||
Msg []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
type PacketListener struct {
|
||||
ctx context.Context
|
||||
Conn net.PacketConn
|
||||
Messages chan ReceivedMessage
|
||||
}
|
||||
|
||||
func NewPacketListener(conn net.PacketConn, ctx context.Context) *PacketListener {
|
||||
results := make(chan ReceivedMessage, 50)
|
||||
|
||||
return &PacketListener{Conn: conn, ctx: ctx, Messages: results}
|
||||
}
|
||||
|
||||
func (l *PacketListener) Start() {
|
||||
for {
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
reply := make([]byte, 1500)
|
||||
err := l.Conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||
if err != nil {
|
||||
l.Messages <- ReceivedMessage{Err: err}
|
||||
continue
|
||||
}
|
||||
|
||||
n, peer, err := l.Conn.ReadFrom(reply)
|
||||
if err != nil {
|
||||
l.Messages <- ReceivedMessage{Err: err}
|
||||
continue
|
||||
}
|
||||
l.Messages <- ReceivedMessage{
|
||||
N: &n,
|
||||
Peer: peer,
|
||||
Err: nil,
|
||||
Msg: reply,
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
276
trace/tcp_ipv4.go
Normal file
276
trace/tcp_ipv4.go
Normal file
@@ -0,0 +1,276 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/listener_channel"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type TCPTracer struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestLock sync.Mutex
|
||||
SrcIP net.IP
|
||||
icmp net.PacketConn
|
||||
tcp net.PacketConn
|
||||
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
|
||||
sem *semaphore.Weighted
|
||||
}
|
||||
|
||||
func (t *TCPTracer) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
|
||||
|
||||
var err error
|
||||
t.tcp, err = net.ListenPacket("ip4:tcp", t.SrcIP.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
defer t.icmp.Close()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
go t.listenTCP()
|
||||
|
||||
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
|
||||
|
||||
for ttl := 1; ttl <= t.MaxHops; ttl++ {
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
t.wg.Wait()
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
|
||||
func (t *TCPTracer) listenICMP() {
|
||||
lc := NewPacketListener(t.icmp, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
|
||||
default:
|
||||
//log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @title listenTCP
|
||||
// @description 监听TCP的响应数据包
|
||||
func (t *TCPTracer) listenTCP() {
|
||||
lc := listener_channel.New(t.tcp)
|
||||
|
||||
defer lc.Stop()
|
||||
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() != t.DestIP.String() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 解包
|
||||
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
|
||||
// 从包中获取TCP layer信息
|
||||
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
// 取得目标主机的Sequence Number
|
||||
|
||||
if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok {
|
||||
// 最后一跳
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
|
||||
header, err := util.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sequenceNumber := util.GetTCPSeq(header)
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[int(sequenceNumber)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *TCPTracer) send(ttl int) error {
|
||||
err := t.sem.Acquire(context.Background(), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.sem.Release(1)
|
||||
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
// 随机种子
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, srcPort := util.LocalIPPort(t.DestIP)
|
||||
ipHeader := &layers.IPv4{
|
||||
SrcIP: t.SrcIP,
|
||||
DstIP: t.DestIP,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
TTL: uint8(ttl),
|
||||
}
|
||||
// 使用Uint16兼容32位系统,防止在rand的时候因使用int32而溢出
|
||||
sequenceNumber := uint32(r.Intn(math.MaxUint16))
|
||||
tcpHeader := &layers.TCP{
|
||||
SrcPort: layers.TCPPort(srcPort),
|
||||
DstPort: layers.TCPPort(t.DestPort),
|
||||
Seq: sequenceNumber,
|
||||
SYN: true,
|
||||
Window: 14600,
|
||||
}
|
||||
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipv4.NewPacketConn(t.tcp).SetTTL(ttl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := t.tcp.WriteTo(buf.Bytes(), &net.IPAddr{IP: t.DestIP}); err != nil {
|
||||
return err
|
||||
}
|
||||
t.inflightRequestLock.Lock()
|
||||
hopCh := make(chan Hop)
|
||||
t.inflightRequest[int(sequenceNumber)] = hopCh
|
||||
t.inflightRequestLock.Unlock()
|
||||
/*
|
||||
// 这里属于 2个Sender,N个Reciever的情况,在哪里关闭Channel都容易导致Panic
|
||||
defer func() {
|
||||
t.inflightRequestLock.Lock()
|
||||
close(hopCh)
|
||||
delete(t.inflightRequest, srcPort)
|
||||
t.inflightRequestLock.Unlock()
|
||||
}()
|
||||
*/
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-hopCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
h.fetchIPData(t.Config)
|
||||
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: ttl,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
269
trace/tcp_ipv6.go
Normal file
269
trace/tcp_ipv6.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/listener_channel"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv6"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type TCPTracerv6 struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestLock sync.Mutex
|
||||
SrcIP net.IP
|
||||
icmp net.PacketConn
|
||||
tcp net.PacketConn
|
||||
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
|
||||
sem *semaphore.Weighted
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
|
||||
log.Println(util.LocalIPPort(t.DestIP))
|
||||
var err error
|
||||
t.tcp, err = net.ListenPacket("ip6:tcp", t.SrcIP.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.icmp, err = icmp.ListenPacket("ip6:53", "::")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
defer t.icmp.Close()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
go t.listenTCP()
|
||||
|
||||
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
|
||||
|
||||
for ttl := 1; ttl <= t.MaxHops; ttl++ {
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
t.wg.Wait()
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) listenICMP() {
|
||||
lc := NewPacketListener(t.icmp, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(53, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
log.Println(msg.Peer)
|
||||
switch rm.Type {
|
||||
case ipv6.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv6.ICMPTypeDestinationUnreachable:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
|
||||
default:
|
||||
//log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @title listenTCP
|
||||
// @description 监听TCP的响应数据包
|
||||
func (t *TCPTracerv6) listenTCP() {
|
||||
lc := listener_channel.New(t.tcp)
|
||||
|
||||
defer lc.Stop()
|
||||
|
||||
go lc.Start()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() != t.DestIP.String() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 解包
|
||||
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
|
||||
// 从包中获取TCP layer信息
|
||||
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||
tcp, _ := tcpLayer.(*layers.TCP)
|
||||
// 取得目标主机的Sequence Number
|
||||
|
||||
if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok {
|
||||
// 最后一跳
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) handleICMPMessage(msg ReceivedMessage, data []byte) {
|
||||
header, err := util.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sequenceNumber := util.GetTCPSeq(header)
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[int(sequenceNumber)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *TCPTracerv6) send(ttl int) error {
|
||||
err := t.sem.Acquire(context.Background(), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.sem.Release(1)
|
||||
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
// 随机种子
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, srcPort := util.LocalIPPort(t.DestIP)
|
||||
ipHeader := &layers.IPv6{
|
||||
SrcIP: t.SrcIP,
|
||||
DstIP: t.DestIP,
|
||||
NextHeader: layers.IPProtocolTCP,
|
||||
HopLimit: uint8(ttl),
|
||||
}
|
||||
// 使用Uint16兼容32位系统,防止在rand的时候因使用int32而溢出
|
||||
sequenceNumber := uint32(r.Intn(math.MaxUint16))
|
||||
tcpHeader := &layers.TCP{
|
||||
SrcPort: layers.TCPPort(srcPort),
|
||||
DstPort: layers.TCPPort(t.DestPort),
|
||||
Seq: sequenceNumber,
|
||||
SYN: true,
|
||||
Window: 14600,
|
||||
}
|
||||
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ipv6.NewPacketConn(t.tcp).SetHopLimit(ttl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := t.tcp.WriteTo(buf.Bytes(), &net.IPAddr{IP: t.DestIP}); err != nil {
|
||||
return err
|
||||
}
|
||||
t.inflightRequestLock.Lock()
|
||||
hopCh := make(chan Hop)
|
||||
t.inflightRequest[int(sequenceNumber)] = hopCh
|
||||
t.inflightRequestLock.Unlock()
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-hopCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
h.fetchIPData(t.Config)
|
||||
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: ttl,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
76
trace/temp_printer.go
Normal file
76
trace/temp_printer.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
)
|
||||
|
||||
func HopPrinter(h Hop) {
|
||||
if h.Address == nil {
|
||||
fmt.Println("\t*")
|
||||
} else {
|
||||
txt := "\t"
|
||||
|
||||
if h.Hostname == "" {
|
||||
txt += fmt.Sprint(h.Address, " ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
|
||||
} else {
|
||||
txt += fmt.Sprint(h.Hostname, " (", h.Address, ") ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
|
||||
}
|
||||
|
||||
if h.Geo != nil {
|
||||
txt += " " + formatIpGeoData(h.Address.String(), h.Geo)
|
||||
}
|
||||
|
||||
fmt.Println(txt)
|
||||
}
|
||||
}
|
||||
|
||||
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
|
||||
var res = make([]string, 0, 10)
|
||||
|
||||
if data.Asnumber == "" {
|
||||
res = append(res, "*")
|
||||
} else {
|
||||
res = append(res, "AS"+data.Asnumber)
|
||||
}
|
||||
|
||||
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
|
||||
// TODO: 移动IDC判断到Hop.fetchIPData函数,减少API调用
|
||||
if strings.HasPrefix(ip, "9.") {
|
||||
res = append(res, "局域网", "腾讯云")
|
||||
} else if strings.HasPrefix(ip, "11.") {
|
||||
res = append(res, "局域网", "阿里云")
|
||||
} else if data.Country == "" {
|
||||
res = append(res, "局域网")
|
||||
} else {
|
||||
// 有些IP的归属信息为空,这个时候将ISP的信息填入
|
||||
if data.Owner == "" {
|
||||
data.Owner = data.Isp
|
||||
}
|
||||
if data.District != "" {
|
||||
data.City = data.City + ", " + data.District
|
||||
}
|
||||
if data.Prov == "" && data.City == "" {
|
||||
// anyCast或是骨干网数据不应该有国家信息
|
||||
data.Owner = data.Owner + ", " + data.Owner
|
||||
} else {
|
||||
// 非骨干网正常填入IP的国家信息数据
|
||||
res = append(res, data.Country)
|
||||
}
|
||||
|
||||
if data.Prov != "" {
|
||||
res = append(res, data.Prov)
|
||||
}
|
||||
if data.City != "" {
|
||||
res = append(res, data.City)
|
||||
}
|
||||
|
||||
if data.Owner != "" {
|
||||
res = append(res, data.Owner)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(res, ", ")
|
||||
}
|
||||
126
trace/trace.go
Normal file
126
trace/trace.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidMethod = errors.New("invalid method")
|
||||
ErrTracerouteExecuted = errors.New("traceroute already executed")
|
||||
ErrHopLimitTimeout = errors.New("hop timeout")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
MaxHops int
|
||||
NumMeasurements int
|
||||
ParallelRequests int
|
||||
Timeout time.Duration
|
||||
DestIP net.IP
|
||||
DestPort int
|
||||
Quic bool
|
||||
IPGeoSource ipgeo.Source
|
||||
RDns bool
|
||||
RealtimePrinter func(res *Result, ttl int)
|
||||
}
|
||||
|
||||
type Method string
|
||||
|
||||
const (
|
||||
ICMPTrace Method = "icmp"
|
||||
UDPTrace Method = "udp"
|
||||
TCPTrace Method = "tcp"
|
||||
)
|
||||
|
||||
type Tracer interface {
|
||||
Execute() (*Result, error)
|
||||
}
|
||||
|
||||
func Traceroute(method Method, config Config) (*Result, error) {
|
||||
var tracer Tracer
|
||||
|
||||
if config.MaxHops == 0 {
|
||||
config.MaxHops = 30
|
||||
}
|
||||
if config.NumMeasurements == 0 {
|
||||
config.NumMeasurements = 3
|
||||
}
|
||||
if config.ParallelRequests == 0 {
|
||||
config.ParallelRequests = config.NumMeasurements * 5
|
||||
}
|
||||
|
||||
switch method {
|
||||
case ICMPTrace:
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &ICMPTracer{Config: config}
|
||||
} else {
|
||||
tracer = &ICMPTracerv6{Config: config}
|
||||
}
|
||||
|
||||
case UDPTrace:
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &UDPTracer{Config: config}
|
||||
} else {
|
||||
return nil, errors.New("IPv6 UDP Traceroute is not supported")
|
||||
}
|
||||
case TCPTrace:
|
||||
if config.DestIP.To4() != nil {
|
||||
tracer = &TCPTracer{Config: config}
|
||||
} else {
|
||||
// tracer = &TCPTracerv6{Config: config}
|
||||
return nil, errors.New("IPv6 TCP Traceroute is not supported")
|
||||
}
|
||||
default:
|
||||
return &Result{}, ErrInvalidMethod
|
||||
}
|
||||
return tracer.Execute()
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Hops [][]Hop
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (s *Result) add(hop Hop) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
k := hop.TTL - 1
|
||||
for len(s.Hops) < hop.TTL {
|
||||
s.Hops = append(s.Hops, make([]Hop, 0))
|
||||
}
|
||||
s.Hops[k] = append(s.Hops[k], hop)
|
||||
|
||||
}
|
||||
|
||||
func (s *Result) reduce(final int) {
|
||||
if final > 0 && final < len(s.Hops) {
|
||||
s.Hops = s.Hops[:final]
|
||||
}
|
||||
}
|
||||
|
||||
type Hop struct {
|
||||
Success bool
|
||||
Address net.Addr
|
||||
Hostname string
|
||||
TTL int
|
||||
RTT time.Duration
|
||||
Error error
|
||||
Geo *ipgeo.IPGeoData
|
||||
}
|
||||
|
||||
func (h *Hop) fetchIPData(c Config) (err error) {
|
||||
if c.RDns && h.Hostname == "" {
|
||||
ptr, err := net.LookupAddr(h.Address.String())
|
||||
if err == nil && len(ptr) > 0 {
|
||||
h.Hostname = ptr[0]
|
||||
}
|
||||
}
|
||||
if c.IPGeoSource != nil && h.Geo == nil {
|
||||
h.Geo, err = c.IPGeoSource(h.Address.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
257
trace/udp.go
Normal file
257
trace/udp.go
Normal file
@@ -0,0 +1,257 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type UDPTracer struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestLock sync.Mutex
|
||||
|
||||
icmp net.PacketConn
|
||||
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
|
||||
sem *semaphore.Weighted
|
||||
}
|
||||
|
||||
func (t *UDPTracer) Execute() (*Result, error) {
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
|
||||
var err error
|
||||
t.icmp, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
return &t.res, err
|
||||
}
|
||||
defer t.icmp.Close()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
|
||||
for ttl := 1; ttl <= t.MaxHops; ttl++ {
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
}
|
||||
}
|
||||
|
||||
t.wg.Wait()
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
|
||||
func (t *UDPTracer) listenICMP() {
|
||||
lc := NewPacketListener(t.icmp, t.ctx)
|
||||
go lc.Start()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case msg := <-lc.Messages:
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv4.ICMPTypeDestinationUnreachable:
|
||||
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
|
||||
default:
|
||||
log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *UDPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
|
||||
header, err := util.GetICMPResponsePayload(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
srcPort := util.GetUDPSrcPort(header)
|
||||
//t.inflightRequestLock.Lock()
|
||||
//defer t.inflightRequestLock.Unlock()
|
||||
ch, ok := t.inflightRequest[int(srcPort)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ch <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *UDPTracer) getUDPConn(try int) (net.IP, int, net.PacketConn) {
|
||||
srcIP, _ := util.LocalIPPort(t.DestIP)
|
||||
|
||||
var ipString string
|
||||
if srcIP == nil {
|
||||
ipString = ""
|
||||
} else {
|
||||
ipString = srcIP.String()
|
||||
}
|
||||
|
||||
udpConn, err := net.ListenPacket("udp", ipString+":0")
|
||||
if err != nil {
|
||||
if try > 3 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return t.getUDPConn(try + 1)
|
||||
}
|
||||
return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn
|
||||
}
|
||||
|
||||
func (t *UDPTracer) send(ttl int) error {
|
||||
err := t.sem.Acquire(context.Background(), 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.sem.Release(1)
|
||||
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
srcIP, srcPort, udpConn := t.getUDPConn(0)
|
||||
|
||||
var payload []byte
|
||||
if t.Quic {
|
||||
payload = GenerateQuicPayloadWithRandomIds()
|
||||
} else {
|
||||
ipHeader := &layers.IPv4{
|
||||
SrcIP: srcIP,
|
||||
DstIP: t.DestIP,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
TTL: uint8(ttl),
|
||||
}
|
||||
|
||||
udpHeader := &layers.UDP{
|
||||
SrcPort: layers.UDPPort(srcPort),
|
||||
DstPort: layers.UDPPort(t.DestPort),
|
||||
}
|
||||
_ = udpHeader.SetNetworkLayerForChecksum(ipHeader)
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
if err := gopacket.SerializeLayers(buf, opts, udpHeader, gopacket.Payload("HAJSFJHKAJSHFKJHAJKFHKASHKFHHKAFKHFAHSJK")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload = buf.Bytes()
|
||||
}
|
||||
|
||||
err = ipv4.NewPacketConn(udpConn).SetTTL(ttl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if _, err := udpConn.WriteTo(payload, &net.UDPAddr{IP: t.DestIP, Port: t.DestPort}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 在对inflightRequest进行写操作的时候应该加锁保护,以免多个goroutine协程试图同时写入造成panic
|
||||
t.inflightRequestLock.Lock()
|
||||
hopCh := make(chan Hop)
|
||||
t.inflightRequest[srcPort] = hopCh
|
||||
t.inflightRequestLock.Unlock()
|
||||
defer func() {
|
||||
t.inflightRequestLock.Lock()
|
||||
close(hopCh)
|
||||
delete(t.inflightRequest, srcPort)
|
||||
t.inflightRequestLock.Unlock()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
reply := make([]byte, 1500)
|
||||
_, peer, err := udpConn.ReadFrom(reply)
|
||||
if err != nil {
|
||||
// probably because we closed the connection
|
||||
return
|
||||
}
|
||||
hopCh <- Hop{
|
||||
Success: true,
|
||||
Address: peer,
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-hopCh:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
} else if addr, ok := h.Address.(*net.UDPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
}
|
||||
|
||||
h.TTL = ttl
|
||||
h.RTT = rtt
|
||||
|
||||
h.fetchIPData(t.Config)
|
||||
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: ttl,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package util
|
||||
|
||||
type IPGeoData struct {
|
||||
Asnumber string `json:"asnumber"`
|
||||
Country string `json:"country"`
|
||||
Prov string `json:"prov"`
|
||||
City string `json:"city"`
|
||||
District string `json:"district"`
|
||||
Owner string `json:"owner"`
|
||||
Isp string `json:"isp"`
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
)
|
||||
|
||||
var dataOrigin string
|
||||
|
||||
func TraceroutePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string) {
|
||||
for hi := uint16(1); hi < 30; hi++ {
|
||||
fmt.Print(hi)
|
||||
for _, v := range res[hi] {
|
||||
hopPrinter(v)
|
||||
if v.Address != nil && ip.String() == v.Address.String() {
|
||||
hi = 31
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hopPrinter(v2 methods.TracerouteHop) {
|
||||
if v2.Address == nil {
|
||||
fmt.Println("\t*")
|
||||
} else {
|
||||
var iPGeoData *ipgeo.IPGeoData
|
||||
var err error
|
||||
|
||||
ipStr := v2.Address.String()
|
||||
|
||||
// TODO: 判断 err 返回,并且在CLI终端提示错误
|
||||
if dataOrigin == "LeoMoeAPI" {
|
||||
iPGeoData, err = ipgeo.LeoIP(ipStr)
|
||||
} else if dataOrigin == "IP.SB" {
|
||||
iPGeoData, err = ipgeo.IPSB(ipStr)
|
||||
} else if dataOrigin == "IPInfo" {
|
||||
iPGeoData, err = ipgeo.IPInfo(ipStr)
|
||||
} else if dataOrigin == "IPInsight" {
|
||||
iPGeoData, err = ipgeo.IPInSight(ipStr)
|
||||
} else {
|
||||
iPGeoData, err = ipgeo.LeoIP(ipStr)
|
||||
}
|
||||
|
||||
geo := ""
|
||||
if err != nil {
|
||||
geo = fmt.Sprint("Error: ", err)
|
||||
} else {
|
||||
geo = formatIpGeoData(ipStr, iPGeoData)
|
||||
}
|
||||
|
||||
ptr, err := net.LookupAddr(ipStr)
|
||||
|
||||
txt := "\t"
|
||||
if err != nil {
|
||||
txt += fmt.Sprint(ipStr, " ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
|
||||
} else {
|
||||
txt += fmt.Sprint(ptr[0], " (", ipStr, ") ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
|
||||
}
|
||||
fmt.Println(txt)
|
||||
}
|
||||
}
|
||||
|
||||
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
|
||||
var res = make([]string, 0, 10)
|
||||
|
||||
if data.Asnumber == "" {
|
||||
res = append(res, "*")
|
||||
} else {
|
||||
res = append(res, "AS"+data.Asnumber)
|
||||
}
|
||||
|
||||
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
|
||||
if strings.HasPrefix(ip, "9.31.") || strings.HasPrefix(ip, "11.72.") {
|
||||
res = append(res, "局域网", "腾讯云")
|
||||
} else if strings.HasPrefix(ip, "11.13.") {
|
||||
res = append(res, "局域网", "阿里云")
|
||||
} else if data.Country == "" {
|
||||
res = append(res, "局域网")
|
||||
} else {
|
||||
if data.Owner == "" {
|
||||
data.Owner = data.Isp
|
||||
}
|
||||
if data.District != "" {
|
||||
data.City = data.City + ", " + data.District
|
||||
}
|
||||
res = append(res, data.Country)
|
||||
if data.Prov != "" {
|
||||
res = append(res, data.Prov)
|
||||
}
|
||||
if data.City != "" {
|
||||
res = append(res, data.City)
|
||||
}
|
||||
if data.Owner != "" {
|
||||
res = append(res, data.Owner)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(res, ", ")
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/rodaine/table"
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
)
|
||||
|
||||
type rowData struct {
|
||||
Hop int64
|
||||
IP string
|
||||
Latency string
|
||||
Asnumber string
|
||||
Country string
|
||||
Prov string
|
||||
City string
|
||||
District string
|
||||
Owner string
|
||||
}
|
||||
|
||||
func TracerouteTablePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string) {
|
||||
// 初始化表格
|
||||
tbl := New()
|
||||
for hi := uint16(1); hi < 30; hi++ {
|
||||
for _, v := range res[hi] {
|
||||
data := tableDataGenerator(v)
|
||||
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Prov, data.City, data.Owner)
|
||||
if v.Address != nil && ip.String() == v.Address.String() {
|
||||
hi = 31
|
||||
}
|
||||
}
|
||||
}
|
||||
// 打印表格
|
||||
tbl.Print()
|
||||
}
|
||||
|
||||
func New() table.Table {
|
||||
// 初始化表格
|
||||
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
|
||||
columnFmt := color.New(color.FgYellow).SprintfFunc()
|
||||
|
||||
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Country", "Province", "City", "Owner")
|
||||
tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
|
||||
return tbl
|
||||
}
|
||||
|
||||
func tableDataGenerator(v2 methods.TracerouteHop) *rowData {
|
||||
if v2.Address == nil {
|
||||
return &rowData{
|
||||
Hop: int64(v2.TTL),
|
||||
IP: "*",
|
||||
}
|
||||
} else {
|
||||
// 初始化变量
|
||||
var iPGeoData *ipgeo.IPGeoData
|
||||
var err error
|
||||
var lantency, IP string
|
||||
|
||||
ipStr := v2.Address.String()
|
||||
|
||||
// TODO: 判断 err 返回,并且在CLI终端提示错误
|
||||
if dataOrigin == "LeoMoeAPI" {
|
||||
iPGeoData, err = ipgeo.LeoIP(ipStr)
|
||||
} else if dataOrigin == "IP.SB" {
|
||||
iPGeoData, err = ipgeo.IPSB(ipStr)
|
||||
} else if dataOrigin == "IPInfo" {
|
||||
iPGeoData, err = ipgeo.IPInfo(ipStr)
|
||||
} else if dataOrigin == "IPInsight" {
|
||||
iPGeoData, err = ipgeo.IPInSight(ipStr)
|
||||
} else {
|
||||
iPGeoData, err = ipgeo.LeoIP(ipStr)
|
||||
}
|
||||
|
||||
ptr, err_LookupAddr := net.LookupAddr(ipStr)
|
||||
|
||||
lantency = fmt.Sprintf("%.2fms", v2.RTT.Seconds()*1000)
|
||||
|
||||
if err_LookupAddr != nil {
|
||||
IP = fmt.Sprint(ipStr)
|
||||
} else {
|
||||
IP = fmt.Sprint(ptr[0], " (", ipStr, ") ")
|
||||
}
|
||||
|
||||
if iPGeoData.Owner == "" {
|
||||
iPGeoData.Owner = iPGeoData.Isp
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Print("Error: ", err)
|
||||
return &rowData{
|
||||
Hop: int64(v2.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
}
|
||||
} else {
|
||||
|
||||
return &rowData{
|
||||
Hop: int64(v2.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Asnumber: iPGeoData.Asnumber,
|
||||
Country: iPGeoData.Country,
|
||||
Prov: iPGeoData.Prov,
|
||||
City: iPGeoData.City,
|
||||
District: iPGeoData.District,
|
||||
Owner: iPGeoData.Owner,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
util/trace.go
Normal file
37
util/trace.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func GetIPHeaderLength(data []byte) (int, error) {
|
||||
if len(data) < 1 {
|
||||
return 0, errors.New("received invalid IP header")
|
||||
}
|
||||
return int((data[0] & 0x0F) * 4), nil
|
||||
}
|
||||
|
||||
func GetICMPResponsePayload(data []byte) ([]byte, error) {
|
||||
length, err := GetIPHeaderLength(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) < length {
|
||||
return nil, errors.New("length of packet too short")
|
||||
}
|
||||
|
||||
return data[length:], nil
|
||||
}
|
||||
|
||||
func GetUDPSrcPort(data []byte) uint16 {
|
||||
srcPortBytes := data[:2]
|
||||
srcPort := binary.BigEndian.Uint16(srcPortBytes)
|
||||
return srcPort
|
||||
}
|
||||
|
||||
func GetTCPSeq(data []byte) uint32 {
|
||||
seqBytes := data[4:8]
|
||||
return binary.BigEndian.Uint32(seqBytes)
|
||||
}
|
||||
11
util/util.go
11
util/util.go
@@ -36,12 +36,13 @@ func DomainLookUp(host string) net.IP {
|
||||
var ipv6Flag = false
|
||||
|
||||
for _, ip := range ips {
|
||||
ipSlice = append(ipSlice, ip)
|
||||
// 仅返回ipv4的ip
|
||||
if ip.To4() != nil {
|
||||
ipSlice = append(ipSlice, ip)
|
||||
} else {
|
||||
ipv6Flag = true
|
||||
}
|
||||
// if ip.To4() != nil {
|
||||
// ipSlice = append(ipSlice, ip)
|
||||
// } else {
|
||||
// ipv6Flag = true
|
||||
// }
|
||||
}
|
||||
|
||||
if ipv6Flag {
|
||||
|
||||
Reference in New Issue
Block a user