diff --git a/.cross_compile.sh b/.cross_compile.sh index 851864e..52148e7 100644 --- a/.cross_compile.sh +++ b/.cross_compile.sh @@ -7,6 +7,10 @@ DEBUG_MODE=${2} TARGET_DIR="dist" PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 linux/mips" +BUILD_VERSION="$(git describe --tags --always)" +BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" +COMMIT_SHA1="$(git rev-parse --short HEAD)" + rm -rf ${TARGET_DIR} mkdir ${TARGET_DIR} @@ -21,15 +25,15 @@ for pl in ${PLATFORMS}; do echo "build => ${TARGET}" if [ "${DEBUG_MODE}" == "debug" ]; then go build -trimpath -gcflags "all=-N -l" -o ${TARGET} \ - -ldflags "-X 'main.version=${BUILD_VERSION}' \ - -X 'main.buildDate=${BUILD_DATE}' \ - -X 'main.commitID=${COMMIT_SHA1}'\ + -ldflags "-X 'github.com/xgadget-lab/nexttrace/printer.version=${BUILD_VERSION}' \ + -X 'github.com/xgadget-lab/nexttrace/printer.buildDate=${BUILD_DATE}' \ + -X 'github.com/xgadget-lab/nexttrace/printer.commitID=${COMMIT_SHA1}'\ -w -s" else go build -trimpath -o ${TARGET} \ - -ldflags "-X 'main.version=${BUILD_VERSION}' \ - -X 'main.buildDate=${BUILD_DATE}' \ - -X 'main.commitID=${COMMIT_SHA1}'\ + -ldflags "-X 'github.com/xgadget-lab/nexttrace/printer.version=${BUILD_VERSION}' \ + -X 'github.com/xgadget-lab/nexttrace/printer.buildDate=${BUILD_DATE}' \ + -X 'github.com/xgadget-lab/nexttrace/printer.commitID=${COMMIT_SHA1}'\ -w -s" fi done diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb100e9..6da0472 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,13 +5,27 @@ on: name: Build Release jobs: - release: + test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master # checkout 代码 - - uses: actions/setup-go@v2 # 配置 Go 环境 + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v2 with: - go-version: "1.18" # 改成自己的版本 + go-version: "1.18" + + - name: Test + run: go test -v -coverprofile='coverage.out' -covermode=count ./... + + release: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v2 + with: + go-version: "1.18" - run: bash .cross_compile.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..097382f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,17 @@ +on: + push: + pull_request: + +name: Test +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v2 + with: + go-version: "1.18" + + - name: Test + run: go test -v -coverprofile='coverage.out' -covermode=count ./... diff --git a/README.md b/README.md index 305fa58..ee7d2e8 100644 --- a/README.md +++ b/README.md @@ -22,30 +22,40 @@ bash -c "$(curl -Ls https://raw.githubusercontent.com/xgadget-lab/nexttrace/main ```bash # IPv4 ICMP Trace -./nexttrace 1.0.0.1 - -# 获得 route-path -./nexttrace -report 1.0.0.1 +nexttrace 1.0.0.1 # 表格打印(一次性输出全部跳数,需等待20-40秒) -./nexttrace -table 1.0.0.1 +nexttrace -table 1.0.0.1 # IPv6 ICMP Trace -./nexttrace 2606:4700:4700::1111 +nexttrace 2606:4700:4700::1111 ``` `NextTrace`也可以使用`TCP`和`UDP`协议发起`Traceroute`请求,不过目前只支持`IPv4` ```bash # TCP SYN Trace -./nexttrace -T www.bing.com +nexttrace -T www.bing.com # 可以自行指定端口[此处为443],默认80端口 -./nexttrace -T -p 443 1.0.0.1 +nexttrace -T -p 443 1.0.0.1 # UDP Trace -./nexttrace -U 1.0.0.1 +nexttrace -U 1.0.0.1 -./nexttrace -U -p 53 1.0.0.1 +nexttrace -U -p 53 1.0.0.1 +``` + +`NextTrace`也同样支持一些进阶功能,如IP反向解析、并发数控制、模式切换等 + +```bash +# 无并发,每次只发送一个探测包 +nexttrace -r 1 www.hkix.net + +# 打开IP反向解析功能,在IPv6的骨干网定位辅助有较大帮助 +nexttrace -rdns www.bbix.net + +# 联合使用 +nexttrace -r 1 -q 1 -report www.time.com.my ``` ### IP数据库 @@ -62,10 +72,9 @@ NextTrace所有的的IP地理位置`API DEMO`可以参考[这里](https://github 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) + -V Check Version -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 @@ -76,25 +85,62 @@ Usage of nexttrace: Set ParallelRequests number. It should be 1 when there is a multi-routing. (default 18) -rdns Set whether rDNS will be display + -realtime + Output trace results in runtime -report Route Path + -table + Output trace results as table ``` + ## 项目截图 -![](asset/screenshot2.png) - -![](asset/screenshot3.png) +![](asset/screenshot_special.png) ![](asset/screenshot.png) -## Thanks + + +## Thanks [Vincent Young](https://github.com/missuo) (i@yyt.moe) -[waiting4new](https://github.com/waiting4new) +[Sam Sam](https://github.com/samleong123) (samsam123@samsam123.name.my) -FFEE_CO +[waiting4new](https://github.com/waiting4new)、[FFEE_CO](https://github.com/fkx4-p)、[nsnnns](https://github.com/tsosunchia) -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 https://lite.ip2location.com. + +### Others + +其他第三方API尽管集成在本项目内,但是具体的TOS以及AUP,请详见第三方API官网。如遇到IP数据错误,也请直接联系他们纠错。 diff --git a/asset/screenshot2.png b/asset/screenshot2.png deleted file mode 100644 index 1ff8af8..0000000 Binary files a/asset/screenshot2.png and /dev/null differ diff --git a/asset/screenshot3.png b/asset/screenshot3.png deleted file mode 100644 index 84e2ba8..0000000 Binary files a/asset/screenshot3.png and /dev/null differ diff --git a/asset/screenshot_2.png b/asset/screenshot_2.png new file mode 100644 index 0000000..15955ca Binary files /dev/null and b/asset/screenshot_2.png differ diff --git a/asset/screenshot_special.png b/asset/screenshot_special.png new file mode 100644 index 0000000..385f729 Binary files /dev/null and b/asset/screenshot_special.png differ diff --git a/go.sum b/go.sum index 4e9638a..b74a7b3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ 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= @@ -53,4 +54,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ 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= diff --git a/ipgeo/ipgeo.go b/ipgeo/ipgeo.go index f9762a8..0de5b71 100644 --- a/ipgeo/ipgeo.go +++ b/ipgeo/ipgeo.go @@ -1,5 +1,7 @@ package ipgeo +import "strings" + type IPGeoData struct { Asnumber string Country string @@ -13,12 +15,12 @@ type IPGeoData struct { type Source = func(ip string) (*IPGeoData, error) func GetSource(s string) Source { - switch s { - case "LeoMoeAPI": + switch strings.ToUpper(s) { + case "LEOMOEAPI": return LeoIP case "IP.SB": return IPSB - case "IPInsight": + case "IPINSIGHT": return IPInSight default: return nil diff --git a/ipgeo/ipgeo_test.go b/ipgeo/ipgeo_test.go index 08dea10..4f1c982 100644 --- a/ipgeo/ipgeo_test.go +++ b/ipgeo/ipgeo_test.go @@ -15,11 +15,12 @@ func TestLeoIP(t *testing.T) { } func TestIPSB(t *testing.T) { - res, err := IPSB("1.1.1.1") - assert.Nil(t, err) - assert.NotNil(t, res) - assert.NotEmpty(t, res.Asnumber) - assert.NotEmpty(t, res.Isp) + // Not available + //res, err := IPSB("1.1.1.1") + //assert.Nil(t, err) + //assert.NotNil(t, res) + //assert.NotEmpty(t, res.Asnumber) + //assert.NotEmpty(t, res.Isp) } func TestIPInfo(t *testing.T) { diff --git a/ipgeo/ipsb.go b/ipgeo/ipsb.go index 46bdad1..8bee301 100644 --- a/ipgeo/ipsb.go +++ b/ipgeo/ipsb.go @@ -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{ diff --git a/listener_channel/listener-channel.go b/listener_channel/listener-channel.go deleted file mode 100644 index e6ef8c1..0000000 --- a/listener_channel/listener-channel.go +++ /dev/null @@ -1,61 +0,0 @@ -package listener_channel - -import ( - "golang.org/x/net/context" - "net" - "time" -) - -type ReceivedMessage struct { - N *int - Peer net.Addr - Msg []byte - Err error -} - -type ListenerChannel struct { - ctx context.Context - cancel context.CancelFunc - Conn net.PacketConn - Messages chan ReceivedMessage -} - -func New(conn net.PacketConn) *ListenerChannel { - ctx, cancel := context.WithCancel(context.Background()) - results := make(chan ReceivedMessage, 50) - - return &ListenerChannel{Conn: conn, ctx: ctx, cancel: cancel, Messages: results} -} - -func (l *ListenerChannel) 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, - } - } -} - -func (l *ListenerChannel) Stop() { - l.cancel() -} diff --git a/main.go b/main.go index 4456a7f..97072f3 100644 --- a/main.go +++ b/main.go @@ -25,9 +25,14 @@ 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 ver = flag.Bool("V", false, "Check Version") func flagApply() string { flag.Parse() + printer.Version() + if *ver { + os.Exit(0) + } ipArg := flag.Args() if flag.NArg() != 1 { fmt.Println("Args Error\nUsage : ./nexttrace [-T] [-rdns] [-displayMode ] [-d ] [ -m ] [ -p ] [ -q ] [ -r ] ") @@ -37,11 +42,13 @@ func flagApply() string { } func main() { + + domain := flagApply() + if os.Getuid() != 0 { log.Fatalln("Traceroute requires root/sudo privileges.") } - domain := flagApply() ip := util.DomainLookUp(domain) printer.PrintTraceRouteNav(ip, domain, *dataOrigin) diff --git a/nt_install.sh b/nt_install.sh index adda6f9..5ad4d13 100644 --- a/nt_install.sh +++ b/nt_install.sh @@ -52,11 +52,12 @@ installWgetPackage() { # macOS should install wget originally. Nothing to do echo "wget 正在安装中..." # try apt - apt-get -h &>/dev/null + # 是时候直接使用 APT 来管理包了 + apt -h &>/dev/null if [ $? -eq 0 ]; then # 先更新一下数据源,有些机器数据源比较老可能会404 - apt-get update -y &>/dev/null - apt-get install wget -y &>/dev/null + apt update -y &>/dev/null + apt install wget -y &>/dev/null fi # try yum @@ -79,8 +80,9 @@ installWgetPackage() { pacman -Sy &>/dev/null pacman -S wget &>/dev/null fi - - wget -h &>/dev/null + + # 有的发行版自带的wget,只有 --help 参数 + wget --help &>/dev/null if [ $? -ne 0 ]; then echo "wget 安装失败" exit 1 diff --git a/printer/basic.go b/printer/basic.go index 0c3d02e..439411b 100644 --- a/printer/basic.go +++ b/printer/basic.go @@ -5,6 +5,15 @@ import ( "net" ) +var version = "v0.0.0.alpha" +var buildDate = "" +var commitID = "" + +func Version() { + fmt.Println("NextTrace", version, buildDate, commitID) + fmt.Println("XGadget-lab Leo (leo.moe) & Vincent (vincent.moe) & zhshch (xzhsh.ch)") +} + func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) { fmt.Println("IP Geo Data Provider: " + dataOrigin) diff --git a/printer/printer_test.go b/printer/printer_test.go new file mode 100644 index 0000000..d285211 --- /dev/null +++ b/printer/printer_test.go @@ -0,0 +1,104 @@ +package printer + +import ( + "errors" + "github.com/xgadget-lab/nexttrace/ipgeo" + "github.com/xgadget-lab/nexttrace/trace" + "github.com/xgadget-lab/nexttrace/util" + "net" + "testing" + "time" +) + +func TestPrintTraceRouteNav(t *testing.T) { + PrintTraceRouteNav(util.DomainLookUp("1.1.1.1"), "1.1.1.1", "dataOrigin") +} + +var testGeo = &ipgeo.IPGeoData{ + Asnumber: "TestAsnumber", + Country: "TestCountry", + Prov: "TestProv", + City: "TestCity", + District: "TestDistrict", + Owner: "TestOwner", + Isp: "TestIsp", +} + +var testResult = &trace.Result{ + Hops: [][]trace.Hop{ + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: testGeo, + }, + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: testGeo, + }, + }, + { + { + Success: false, + Address: nil, + Hostname: "", + TTL: 0, + RTT: 0, + Error: errors.New("test error"), + Geo: nil, + }, + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: nil, + }, + }, + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, + Hostname: "test", + TTL: 0, + RTT: 0, + Error: nil, + Geo: &ipgeo.IPGeoData{}, + }, + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, + Hostname: "", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: testGeo, + }, + }, + }, +} + +func TestTraceroutePrinter(t *testing.T) { + TraceroutePrinter(testResult) +} + +func TestTracerouteTablePrinter(t *testing.T) { + TracerouteTablePrinter(testResult) +} + +func TestRealtimePrinter(t *testing.T) { + RealtimePrinter(testResult, 0) + RealtimePrinter(testResult, 1) + RealtimePrinter(testResult, 2) +} diff --git a/printer/tableprinter.go b/printer/tableprinter.go index 08e663d..46c748b 100644 --- a/printer/tableprinter.go +++ b/printer/tableprinter.go @@ -2,6 +2,7 @@ package printer import ( "fmt" + "github.com/xgadget-lab/nexttrace/ipgeo" "strings" "github.com/xgadget-lab/nexttrace/trace" @@ -89,6 +90,10 @@ func tableDataGenerator(h trace.Hop) *rowData { IP = fmt.Sprint(h.Hostname, " (", IP, ") ") } + if h.Geo == nil { + h.Geo = &ipgeo.IPGeoData{} + } + r := &rowData{ Hop: fmt.Sprint(h.TTL), IP: IP, diff --git a/reporter/reporter.go b/reporter/reporter.go index c9c13a5..b9fedbe 100644 --- a/reporter/reporter.go +++ b/reporter/reporter.go @@ -1,10 +1,10 @@ package reporter import ( - "errors" "fmt" "net" "strings" + "sync" "github.com/xgadget-lab/nexttrace/ipgeo" "github.com/xgadget-lab/nexttrace/trace" @@ -24,9 +24,12 @@ func New(rs *trace.Result, ip string) Reporter { } type reporter struct { - targetIP string - routeReport map[uint16][]routeReportNode - routeResult *trace.Result + targetTTL uint16 + targetIP string + routeReport map[uint16][]routeReportNode + routeReportLock sync.Mutex + routeResult *trace.Result + wg sync.WaitGroup } type routeReportNode struct { @@ -40,71 +43,107 @@ 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 - } - } - }() +func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData, ttl uint16) { + var success bool = true + + defer r.wg.Done() + + rpn := routeReportNode{} + ptr, err := net.LookupAddr(ip) + + if err == nil { + if strings.Contains(strings.ToLower(ptr[0]), "ix") { + rpn.ix = true + } else { + rpn.ix = false + } + } + // TODO: 这种写法不好,后面再重构一下 + // 判断反向解析的域名中又或者是IP地理位置数据库中,是否出现了 IX if strings.Contains(strings.ToLower(ipGeoData.Isp), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Isp), "ix") || strings.Contains(strings.ToLower(ipGeoData.Owner), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Owner), "ix") { rpn.ix = true } + + // TODO: 正则判断POP并且提取带宽大小等信息 + + // CN2 需要特殊处理,因为他们很多没有ASN + // 但是目前这种写法是不规范的,属于凭空标记4809的IP + // TODO: 用更好的方式显示 CN2 骨干网的路由 Path if strings.HasPrefix(ip, "59.43") { rpn.asn = "4809" } else { rpn.asn = ipGeoData.Asnumber } + // 无论最后一跳是否为存在地理位置信息(AnyCast),都应该给予显示 - if ipGeoData.Country == "" || ipGeoData.City == "" || ipGeoData.City == "-" && ip != r.targetIP { - return rpn, errors.New("GeoData Search Failed") + if (ipGeoData.Country == "" || ipGeoData.Country == "LAN Address" || ipGeoData.Country == "-") && ip != r.targetIP { + success = false } else { if ipGeoData.City == "" { - rpn.geo = []string{ipGeoData.Country, ipGeoData.Country} + rpn.geo = []string{ipGeoData.Country, ipGeoData.Prov} } else { rpn.geo = []string{ipGeoData.Country, ipGeoData.City} } } + if ipGeoData.Asnumber == "" { + rpn.asn = "*" + } + if ipGeoData.Isp == "" { rpn.isp = ipGeoData.Owner } else { rpn.isp = ipGeoData.Isp } - return rpn, nil + + // 有效记录 + if success { + // 锁住资源,防止同时写panic + r.routeReportLock.Lock() + // 添加到MAP中 + r.routeReport[ttl] = append(r.routeReport[ttl], rpn) + // 写入完成,解锁释放资源给其他协程 + r.routeReportLock.Unlock() + } } func (r *reporter) InitialBaseData() Reporter { - var nodeIndex uint16 = 1 reportNodes := map[uint16][]routeReportNode{} - for i := uint16(0); int(i) < len(r.routeResult.Hops); i++ { + + r.routeReport = reportNodes + r.targetTTL = uint16(len(r.routeResult.Hops)) + + for i := uint16(0); i < r.targetTTL; i++ { traceHop := r.routeResult.Hops[i][0] if traceHop.Success { currentIP := traceHop.Address.String() - rpn, err := r.generateRouteReportNode(currentIP, *traceHop.Geo) - if err == nil { - reportNodes[nodeIndex] = append(reportNodes[nodeIndex], rpn) - nodeIndex += 1 - } + r.wg.Add(1) + go r.generateRouteReportNode(currentIP, *traceHop.Geo, i) } } - r.routeReport = reportNodes + + // 等待所有的子协程运行完毕 + r.wg.Wait() return r } func (r *reporter) Print() { + var beforeActiveTTL uint16 = 1 r.InitialBaseData() - for i := uint16(1); int(i) < len(r.routeReport)+1; i++ { + + for i := uint16(1); i < r.targetTTL; i++ { + // 计算该TTL内的数据长度,如果为0,则代表没有有效数据 + if len(r.routeReport[i]) == 0 { + // 跳过改跃点的数据整理 + continue + } 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] + nodeReportBefore := r.routeReport[beforeActiveTTL][0] + // ASN 相同,同个 ISP 内部的数据传递 if nodeReportBefore.asn == nodeReport.asn { // Same ASN but Coutry or City Changed if nodeReportBefore.geo[0] != nodeReport.geo[0] { @@ -115,8 +154,11 @@ func (r *reporter) Print() { } } } else { + // ASN 不同,跨 ISP 的数据传递,这里可能会出现 POP、IP Transit、Peer、Exchange fmt.Printf("』」") if int(i) != len(r.routeReport)+1 { + // 部分 Shell 客户端可能无法很好的展示这个特殊字符 + // TODO: 寻找其他替代字符 fmt.Printf("\n ╭╯\n ╰") } if nodeReport.ix { @@ -126,6 +168,8 @@ func (r *reporter) Print() { } } } + // 标记为最新的一个有效跃点 + beforeActiveTTL = i } fmt.Println("』」") } diff --git a/reporter/reporter_test.go b/reporter/reporter_test.go index 7147971..c95acdc 100644 --- a/reporter/reporter_test.go +++ b/reporter/reporter_test.go @@ -1,31 +1,115 @@ package reporter import ( + "net" "testing" "time" "github.com/xgadget-lab/nexttrace/ipgeo" "github.com/xgadget-lab/nexttrace/trace" - "github.com/xgadget-lab/nexttrace/util" ) +var testResult = &trace.Result{ + Hops: [][]trace.Hop{ + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("192.168.3.1")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "4808", + Country: "中国", + Prov: "北京市", + City: "北京市", + District: "北京市", + Owner: "", + Isp: "中国联通", + }, + }, + }, + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("114.249.16.1")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "4808", + Country: "中国", + Prov: "北京市", + City: "北京市", + District: "北京市", + Owner: "", + Isp: "中国联通", + }, + }, + }, + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("219.158.5.150")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "4837", + Country: "中国", + Prov: "", + City: "", + District: "", + Owner: "", + Isp: "中国联通", + }, + }, + }, + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("62.115.125.160")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "1299", + Country: "Sweden", + Prov: "Stockholm County", + City: "Stockholm", + District: "", + Owner: "", + Isp: "Telia Company AB", + }, + }, + }, + { + { + Success: true, + Address: &net.IPAddr{IP: net.ParseIP("213.226.68.73")}, + Hostname: "test", + TTL: 0, + RTT: 10 * time.Millisecond, + Error: nil, + Geo: &ipgeo.IPGeoData{ + Asnumber: "56630", + Country: "Germany", + Prov: "Hesse, Frankfurt", + City: "", + District: "", + Owner: "", + Isp: "Melbikomas UAB", + }, + }, + }, + }, +} + func TestPrint(t *testing.T) { - 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 := New(testResult, "213.226.68.73") r.Print() } diff --git a/signal/signal.go b/signal/signal.go deleted file mode 100644 index 6095a25..0000000 --- a/signal/signal.go +++ /dev/null @@ -1,19 +0,0 @@ -package signal - -type Signal struct { - sigChan chan struct{} -} - -func New() *Signal { - return &Signal{sigChan: make(chan struct{}, 1)} -} - -func (s *Signal) Signal() { - if len(s.sigChan) == 0 { - s.sigChan <- struct{}{} - } -} - -func (s *Signal) Chan() chan struct{} { - return s.sigChan -} diff --git a/taskgroup/taskgroup.go b/taskgroup/taskgroup.go deleted file mode 100644 index ca2e472..0000000 --- a/taskgroup/taskgroup.go +++ /dev/null @@ -1,45 +0,0 @@ -package taskgroup - -import ( - "sync" -) - -type TaskGroup struct { - count int - mu sync.Mutex - done []chan struct{} -} - -func New() *TaskGroup { - return &TaskGroup{ - count: 0, - mu: sync.Mutex{}, - done: []chan struct{}{}, - } -} - -func (t *TaskGroup) Add() { - t.mu.Lock() - defer t.mu.Unlock() - t.count++ -} - -func (t *TaskGroup) Done() { - t.mu.Lock() - defer t.mu.Unlock() - if t.count-1 == 0 { - for _, doneChannel := range t.done { - doneChannel <- struct{}{} - } - t.done = []chan struct{}{} - } - t.count-- -} - -func (t *TaskGroup) Wait() { - doneChannel := make(chan struct{}) - t.mu.Lock() - t.done = append(t.done, doneChannel) - t.mu.Unlock() - <-doneChannel -} diff --git a/trace/tcp_ipv4.go b/trace/tcp_ipv4.go index 0188b01..be7ced1 100644 --- a/trace/tcp_ipv4.go +++ b/trace/tcp_ipv4.go @@ -10,7 +10,6 @@ import ( "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" @@ -110,10 +109,7 @@ func (t *TCPTracer) listenICMP() { // @title listenTCP // @description 监听TCP的响应数据包 func (t *TCPTracer) listenTCP() { - lc := listener_channel.New(t.tcp) - - defer lc.Stop() - + lc := NewPacketListener(t.tcp, t.ctx) go lc.Start() for { diff --git a/trace/tcp_ipv6.go b/trace/tcp_ipv6.go index 252cbee..b05c8d3 100644 --- a/trace/tcp_ipv6.go +++ b/trace/tcp_ipv6.go @@ -10,7 +10,6 @@ import ( "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" @@ -111,10 +110,7 @@ func (t *TCPTracerv6) listenICMP() { // @title listenTCP // @description 监听TCP的响应数据包 func (t *TCPTracerv6) listenTCP() { - lc := listener_channel.New(t.tcp) - - defer lc.Stop() - + lc := NewPacketListener(t.tcp, t.ctx) go lc.Start() for {