mirror of
https://github.com/nxtrace/NTrace-core.git
synced 2025-08-12 06:26:39 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9b18f5efa | ||
|
|
326034b41e | ||
|
|
09a5b42443 | ||
|
|
b8542489b6 | ||
|
|
a73a306d0a | ||
|
|
801f42801a | ||
|
|
08c4a5ceae | ||
|
|
ce1c773996 | ||
|
|
cdec6cbd8d | ||
|
|
aa891ebd7c | ||
|
|
5d132e73ab | ||
|
|
63ca4d0418 | ||
|
|
a6da078eb0 |
32
README.md
32
README.md
@@ -10,6 +10,18 @@ An open source visual routing tool that pursues light weight, developed using Go
|
||||
|
||||
2022/12/18: Due to time and effort, it is becoming more and more difficult to maintain 2 branches at the same time, so I will be phasing out support for the NextTrace Enhanced version in the near future. I will resume updating the `Enhanced` version when I have more time.
|
||||
|
||||
## 公告
|
||||
|
||||
我今天看到了一些非常难过的事情,一些用户在 BestTrace 和 WorstTrace 下面宣传 NextTrace 的完全可替代性。
|
||||
|
||||
这么做是不正确的,NextTrace 从来都不是一个从零开始的软件,NextTrace 之所以能够拥有某些功能特性,是因为吸取了 BestTrace 、WorstTrace 的一些想法。
|
||||
|
||||
我们希望您在使用的时候知晓这一点,**我们是站在巨人的肩膀上,而尊重其他软件作者,向他们或者是我们提交 Bug 或贡献代码,才是推动整个 traceroute 工具的软件多样化发展的最好方式**。
|
||||
|
||||
NextTrace 并不追求成为一个替代者,同类软件越多样化,才能满足更多人的需求,这才是我们希望看到的,而去诋毁其他软件,这违背了我们对于开发 NextTrace 的初衷。
|
||||
|
||||
我们希望看到这条公告的朋友应该主动删除自己过激的言论,如果您有任何问题或建议,请随时在我们的社区中发表。
|
||||
|
||||
## LeoMoeAPI Credit
|
||||
|
||||
NextTrace 重点在于研究 Go 语言 Traceroute 的实现,其 LeoMoeAPI 的地理位置信息并没有原始数据的支撑,故也不可能有商用版本。
|
||||
@@ -69,13 +81,13 @@ Windows users please go to [Release Page](https://github.com/sjlleo/nexttrace/re
|
||||
nexttrace 1.0.0.1
|
||||
|
||||
# Form printing (output all hops at one time, wait 20-40 seconds)
|
||||
nexttrace -table 1.0.0.1
|
||||
nexttrace --table 1.0.0.1
|
||||
|
||||
# IPv6 ICMP Trace
|
||||
nexttrace 2606:4700:4700::1111
|
||||
|
||||
# Path Visualization With the -M parameter, a map URL is returned
|
||||
nexttrace -M koreacentral.blob.core.windows.net
|
||||
nexttrace --map koreacentral.blob.core.windows.net
|
||||
# MapTrace URL: https://api.leo.moe/tracemap/html/c14e439e-3250-5310-8965-42a1e3545266.html
|
||||
```
|
||||
|
||||
@@ -89,21 +101,21 @@ The routing visualization function requires the geographical coordinates of each
|
||||
|
||||
```bash
|
||||
# IPv4 ICMP Fast Test (Beijing + Shanghai + Guangzhou + Hangzhou) in China Telecom / Unicom / Mobile / Education Network
|
||||
nexttrace -f
|
||||
nexttrace -F
|
||||
|
||||
# You can also use TCP SYN for testing
|
||||
nexttrace -f -T
|
||||
nexttrace -F -T
|
||||
```
|
||||
|
||||
`NextTrace` already supports route tracing for specified Network Devices
|
||||
|
||||
```bash
|
||||
# Use eth0 network interface
|
||||
nexttrace -D eth0 2606:4700:4700::1111
|
||||
nexttrace --dev eth0 2606:4700:4700::1111
|
||||
|
||||
# Use eth0 network interface's IP
|
||||
# When using the network interface's IP for route tracing, note that the IP type to be traced should be the same as network interface's IP type (e.g. both IPv4)
|
||||
nexttrace -S 204.98.134.56 9.9.9.9
|
||||
nexttrace --source 204.98.134.56 9.9.9.9
|
||||
```
|
||||
|
||||
`NextTrace` can also use `TCP` and `UDP` protocols to perform `Traceroute` requests, but these protocols only supports `IPv4` now
|
||||
@@ -131,7 +143,7 @@ nexttrace -q 2 www.hkix.net
|
||||
nexttrace -r 1 www.hkix.net
|
||||
|
||||
# Start Trace with TTL of 5, end at TTL of 10
|
||||
nexttrace -b 5 -m 10 www.decix.net
|
||||
nexttrace -f 5 -m 10 www.decix.net
|
||||
|
||||
# Turn off the IP reverse parsing function
|
||||
nexttrace -n www.bbix.net
|
||||
@@ -145,7 +157,7 @@ nexttrace -n www.bbix.net
|
||||
# ╰AS36776 Five9 Inc.「Philippines『Metro Manila』」
|
||||
# ╭╯
|
||||
# ╰AS37963 Aliyun「ALIDNS.COM『ALIDNS.COM』」
|
||||
nexttrace -report www.time.com.my
|
||||
nexttrace --route-path www.time.com.my
|
||||
```
|
||||
|
||||
`NextTrace` supports users to select their own IP API (currently supports: `LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`)
|
||||
@@ -163,8 +175,8 @@ nexttrace -d IP.SB
|
||||
|
||||
```bash
|
||||
Example:
|
||||
nexttrace -d IPInsight -m 20 -p 443 -q 5 -r 20 -rdns 1.1.1.1
|
||||
nexttrace -T -q 2 -r 1 -table -report 2001:4860:4860::8888
|
||||
nexttrace --data-provider LeoMoeAPI -m 20 -p 443 -q 5 -n 1.1.1.1
|
||||
nexttrace -T -q 2 -r 1 --table --route-path 2001:4860:4860::8888
|
||||
```
|
||||
|
||||
### IP Database
|
||||
|
||||
@@ -41,7 +41,7 @@ Windows 用户请直接前往 [Release](https://github.com/sjlleo/nexttrace/rele
|
||||
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
|
||||
@@ -61,10 +61,10 @@ PS: 路由可视化的绘制模块由 [@tsosunchia](https://github.com/tsosunchi
|
||||
|
||||
```bash
|
||||
# 北上广(电信+联通+移动+教育网)IPv4 ICMP 快速测试
|
||||
nexttrace -f
|
||||
nexttrace -F
|
||||
|
||||
# 也可以使用 TCP SYN 而非 ICMP 进行测试
|
||||
nexttrace -f -T
|
||||
nexttrace -F -T
|
||||
```
|
||||
|
||||
`NextTrace` 已支持指定网卡进行路由跟踪
|
||||
@@ -119,7 +119,7 @@ nexttrace -n www.bbix.net
|
||||
# ╰AS36776 Five9 Inc.「Philippines『Metro Manila』」
|
||||
# ╭╯
|
||||
# ╰AS37963 阿里云「ALIDNS.COM『ALIDNS.COM』」
|
||||
nexttrace -report www.time.com.my
|
||||
nexttrace --route-path www.time.com.my
|
||||
```
|
||||
|
||||
`NextTrace`支持用户自主选择 IP 数据库(目前支持:`LeoMoeAPI`, `IP.SB`, `IPInfo`, `IPInsight`, `IPAPI.com`)
|
||||
@@ -137,8 +137,8 @@ nexttrace -d IP.SB
|
||||
|
||||
```bash
|
||||
Example:
|
||||
nexttrace -d IPInsight -m 20 -p 443 -q 5 -r 20 -rdns 1.1.1.1
|
||||
nexttrace -T -q 2 -r 1 -table -report 2001:4860:4860::8888
|
||||
nexttrace --data-provider LeoMoeAPI -m 20 -p 443 -q 5 --parallel-requests 20 -n 1.1.1.1
|
||||
nexttrace -T -q 2 --table --route-path 2001:4860:4860::8888
|
||||
```
|
||||
|
||||
### IP 数据库
|
||||
|
||||
242
cmd/cmd.go
Normal file
242
cmd/cmd.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/akamensky/argparse"
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
fastTrace "github.com/xgadget-lab/nexttrace/fast_trace"
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/printer"
|
||||
"github.com/xgadget-lab/nexttrace/reporter"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/tracelog"
|
||||
"github.com/xgadget-lab/nexttrace/tracemap"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
)
|
||||
|
||||
func Excute() {
|
||||
parser := argparse.NewParser("nexttrace", "An open source visual route tracking CLI tool")
|
||||
// Create string flag
|
||||
tcp := parser.Flag("T", "tcp", &argparse.Options{Help: "Use TCP SYN for tracerouting (default port is 80)"})
|
||||
udp := parser.Flag("U", "udp", &argparse.Options{Help: "Use UDP SYN for tracerouting (default port is 53)"})
|
||||
fast_trace := parser.Flag("F", "fast-trace", &argparse.Options{Help: "One-Key Fast Trace to China ISPs"})
|
||||
port := parser.Int("p", "port", &argparse.Options{Help: "Set the destination port to use. It is either initial udp port value for \"default\"" +
|
||||
"method (incremented by each probe, default is 33434), or initial seq for \"icmp\" (incremented as well, default from 1), or some constant" +
|
||||
"destination port for other methods (with default of 80 for \"tcp\", 53 for \"udp\", etc.)"})
|
||||
numMeasurements := parser.Int("q", "queries", &argparse.Options{Default: 3, Help: "Set the number of probes per each hop"})
|
||||
parallelRequests := parser.Int("", "parallel-requests", &argparse.Options{Default: 18, Help: "Set ParallelRequests number. It should be 1 when there is a multi-routing"})
|
||||
maxHops := parser.Int("m", "max-hops", &argparse.Options{Default: 30, Help: "Set the max number of hops (max TTL to be reached)"})
|
||||
dataOrigin := parser.Selector("d", "data-provider", []string{"IP.SB", "IPInfo", "IPInsight", "IPAPI.com"}, &argparse.Options{Default: "LeoMoeAPI",
|
||||
Help: "Choose IP Geograph Data Provider [LeoMoeAPI,IP.SB, IPInfo, IPInsight, IPAPI.com]"})
|
||||
noRdns := parser.Flag("n", "no-rdns", &argparse.Options{Help: " Do not resolve IP addresses to their domain names"})
|
||||
routePath := parser.Flag("r", "route-path", &argparse.Options{Help: "Print traceroute hop path by ASN and location"})
|
||||
output := parser.Flag("o", "output", &argparse.Options{Help: "Write trace result to file (RealTimePrinter ONLY)"})
|
||||
tablePrint := parser.Flag("t", "table", &argparse.Options{Help: "Output trace results as table"})
|
||||
classicPrint := parser.Flag("c", "classic", &argparse.Options{Help: "Classic Output trace results like BestTrace"})
|
||||
beginHop := parser.Int("f", "first", &argparse.Options{Default: 1, Help: "Start from the first_ttl hop (instead from 1)"})
|
||||
maptrace := parser.Flag("M", "map", &argparse.Options{Help: "Print Trace Map. This will return a Trace Map URL"})
|
||||
ver := parser.Flag("v", "version", &argparse.Options{Help: "Print version info and exit"})
|
||||
src_addr := parser.String("s", "source", &argparse.Options{Help: "Use source src_addr for outgoing packets"})
|
||||
src_dev := parser.String("D", "dev", &argparse.Options{Help: "Use the following Network Devices as the source address in outgoing packets"})
|
||||
router := parser.Flag("R", "route", &argparse.Options{Help: "Show Routing Table [Provided By BGP.Tools]"})
|
||||
packet_interval := parser.Int("z", "send-time", &argparse.Options{Default: 0, Help: "Set the time interval for sending every packet. Useful when some routers use rate-limit for ICMP messages."})
|
||||
ttl_interval := parser.Int("i", "ttl-time", &argparse.Options{Default: 500, Help: "Set the time interval for sending packets groups by TTL. Useful when some routers use rate-limit for ICMP messages."})
|
||||
str := parser.StringPositional(&argparse.Options{Help: "IP Address or domain name"})
|
||||
|
||||
err := parser.Parse(os.Args)
|
||||
if err != nil {
|
||||
// In case of error print error and print usage
|
||||
// This can also be done by passing -h or --help flags
|
||||
fmt.Print(parser.Usage(err))
|
||||
return
|
||||
}
|
||||
printer.Version()
|
||||
if *ver {
|
||||
printer.CopyRight()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
domain := *str
|
||||
|
||||
if *port == 0 {
|
||||
*port = 80
|
||||
}
|
||||
|
||||
if *fast_trace {
|
||||
fastTrace.FastTest(*tcp, *output)
|
||||
if *output {
|
||||
fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中")
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if domain == "" {
|
||||
fmt.Print(parser.Usage(err))
|
||||
return
|
||||
}
|
||||
|
||||
capabilities_check()
|
||||
// return
|
||||
|
||||
var ip net.IP
|
||||
|
||||
if runtime.GOOS == "windows" && (*tcp || *udp) {
|
||||
fmt.Println("NextTrace 基于 Windows 的路由跟踪还在早期开发阶段,目前还存在诸多问题,TCP/UDP SYN 包请求可能不能正常运行")
|
||||
}
|
||||
|
||||
if *tcp || *udp {
|
||||
ip = util.DomainLookUp(domain, true)
|
||||
} else {
|
||||
ip = util.DomainLookUp(domain, false)
|
||||
}
|
||||
|
||||
if *src_dev != "" {
|
||||
dev, _ := net.InterfaceByName(*src_dev)
|
||||
|
||||
if addrs, err := dev.Addrs(); err == nil {
|
||||
for _, addr := range addrs {
|
||||
if (addr.(*net.IPNet).IP.To4() == nil) == (ip.To4() == nil) {
|
||||
*src_addr = addr.(*net.IPNet).IP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" {
|
||||
w := wshandle.New()
|
||||
w.Interrupt = make(chan os.Signal, 1)
|
||||
signal.Notify(w.Interrupt, os.Interrupt)
|
||||
defer func() {
|
||||
w.Conn.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
|
||||
|
||||
var m trace.Method = ""
|
||||
|
||||
switch {
|
||||
case *tcp:
|
||||
m = trace.TCPTrace
|
||||
case *udp:
|
||||
m = trace.UDPTrace
|
||||
default:
|
||||
m = trace.ICMPTrace
|
||||
}
|
||||
|
||||
if !*tcp && *port == 80 {
|
||||
*port = 53
|
||||
}
|
||||
|
||||
var conf = trace.Config{
|
||||
SrcAddr: *src_addr,
|
||||
BeginHop: *beginHop,
|
||||
DestIP: ip,
|
||||
DestPort: *port,
|
||||
MaxHops: *maxHops,
|
||||
PacketInterval: *packet_interval,
|
||||
TTLInterval: *ttl_interval,
|
||||
NumMeasurements: *numMeasurements,
|
||||
ParallelRequests: *parallelRequests,
|
||||
RDns: !*noRdns,
|
||||
IPGeoSource: ipgeo.GetSource(*dataOrigin),
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
if !*tablePrint {
|
||||
if *classicPrint {
|
||||
conf.RealtimePrinter = printer.ClassicPrinter
|
||||
} else {
|
||||
if *output {
|
||||
conf.RealtimePrinter = tracelog.RealtimePrinter
|
||||
} else if *router {
|
||||
conf.RealtimePrinter = printer.RealtimePrinterWithRouter
|
||||
fmt.Println("路由表数据源由 BGP.Tools 提供,在此特表感谢")
|
||||
} else {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
}
|
||||
} else {
|
||||
conf.AsyncPrinter = printer.TracerouteTablePrinter
|
||||
}
|
||||
|
||||
res, err := trace.Traceroute(m, conf)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if *tablePrint {
|
||||
printer.TracerouteTablePrinter(res)
|
||||
}
|
||||
|
||||
if *routePath {
|
||||
r := reporter.New(res, ip.String())
|
||||
r.Print()
|
||||
}
|
||||
|
||||
if *maptrace {
|
||||
r, _ := json.Marshal(res)
|
||||
tracemap.GetMapUrl(string(r))
|
||||
}
|
||||
}
|
||||
|
||||
func capabilities_check() {
|
||||
|
||||
// Windows 判断放在前面,防止遇到一些奇奇怪怪的问题
|
||||
if runtime.GOOS == "windows" {
|
||||
// Running on Windows, skip checking capabilities
|
||||
return
|
||||
}
|
||||
|
||||
uid := os.Getuid()
|
||||
if uid == 0 {
|
||||
// Running as root, skip checking capabilities
|
||||
return
|
||||
}
|
||||
|
||||
/***
|
||||
* 检查当前进程是否有两个关键的权限
|
||||
==== 看不到我 ====
|
||||
* 没办法啦
|
||||
* 自己之前承诺的坑补全篇
|
||||
* 被迫填坑系列 qwq
|
||||
==== 看不到我 ====
|
||||
***/
|
||||
|
||||
// NewPid 已经被废弃了,这里改用 NewPid2 方法
|
||||
caps, err := capability.NewPid2(0)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// load 获取全部的 caps 信息
|
||||
err = caps.Load()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 判断一下权限有木有
|
||||
if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) && caps.Get(capability.EFFECTIVE, capability.CAP_NET_ADMIN) {
|
||||
// 有权限啦
|
||||
return
|
||||
} else {
|
||||
// 没权限啦
|
||||
log.Println("您正在以普通用户权限运行 NextTrace,但 NextTrace 未被赋予监听网络套接字的ICMP消息包、修改IP头信息(TTL)等路由跟踪所需的权限")
|
||||
log.Println("请使用管理员用户执行 `sudo setcap cap_net_raw,cap_net_admin+eip ${your_nexttrace_path}/nexttrace` 命令,赋予相关权限后再运行~")
|
||||
log.Fatalln("什么?为什么 ping 普通用户执行不要 root 权限?因为这些工具在管理员安装时就已经被赋予了一些必要的权限,具体请使用 `getcap /usr/bin/ping` 查看")
|
||||
}
|
||||
}
|
||||
7
cmd/cmd_test.go
Normal file
7
cmd/cmd_test.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCMD(t *testing.T) {
|
||||
Excute()
|
||||
}
|
||||
1
go.mod
1
go.mod
@@ -3,6 +3,7 @@ module github.com/xgadget-lab/nexttrace
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/akamensky/argparse v1.4.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
|
||||
golang.org/x/net v0.5.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
|
||||
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
|
||||
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=
|
||||
|
||||
@@ -1,11 +1,72 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
|
||||
func TestXxx(t *testing.T) {
|
||||
const ID_FIXED_HEADER = "10"
|
||||
var processID = fmt.Sprintf("%07b", os.Getpid()&0x7f) //取进程ID的前7位
|
||||
var ttl = fmt.Sprintf("%06b", 95) //取TTL的后6位
|
||||
fmt.Println(os.Getpid()&0x7f, 95)
|
||||
|
||||
var parity int
|
||||
id := ID_FIXED_HEADER + processID + ttl
|
||||
for _, c := range id {
|
||||
if c == '1' {
|
||||
parity++
|
||||
}
|
||||
}
|
||||
if parity%2 == 0 {
|
||||
id += "1"
|
||||
} else {
|
||||
id += "0"
|
||||
}
|
||||
process_id, ttl_r, _ := reverseID(id)
|
||||
log.Println(process_id, ttl_r)
|
||||
}
|
||||
|
||||
func reverseID(id string) (int64, int64, error) {
|
||||
ttl, _ := strconv.ParseInt(id[9:15], 2, 32)
|
||||
//process ID
|
||||
processID, _ := strconv.ParseInt(id[2:9], 2, 32)
|
||||
|
||||
parity := 0
|
||||
for i := 0; i < len(id)-1; i++ {
|
||||
if id[i] == '1' {
|
||||
parity++
|
||||
}
|
||||
}
|
||||
|
||||
if parity%2 == 1 {
|
||||
if id[len(id)-1] == '0' {
|
||||
fmt.Println("Parity check passed.")
|
||||
} else {
|
||||
fmt.Println("Parity check failed.")
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
} else {
|
||||
if id[len(id)-1] == '1' {
|
||||
fmt.Println("Parity check passed.")
|
||||
} else {
|
||||
fmt.Println("Parity check failed.")
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
}
|
||||
return processID, ttl, nil
|
||||
}
|
||||
|
||||
// func TestLeoIP(t *testing.T) {
|
||||
// // res, err := LeoIP("1.1.1.1")
|
||||
// // assert.Nil(t, err)
|
||||
|
||||
244
main.go
244
main.go
@@ -1,249 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/gocapability/capability"
|
||||
fastTrace "github.com/xgadget-lab/nexttrace/fast_trace"
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/printer"
|
||||
"github.com/xgadget-lab/nexttrace/reporter"
|
||||
"github.com/xgadget-lab/nexttrace/trace"
|
||||
"github.com/xgadget-lab/nexttrace/tracelog"
|
||||
"github.com/xgadget-lab/nexttrace/tracemap"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"github.com/xgadget-lab/nexttrace/wshandle"
|
||||
"github.com/xgadget-lab/nexttrace/cmd"
|
||||
)
|
||||
|
||||
var fSet = flag.NewFlagSet("", flag.ExitOnError)
|
||||
var fastTest = fSet.Bool("f", false, "One-Key Fast Traceroute")
|
||||
var tcpSYNFlag = fSet.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80)")
|
||||
var udpPackageFlag = fSet.Bool("U", false, "Use UDP Package for tracerouting (default port is 53 in UDP)")
|
||||
var port = fSet.Int("p", 80, "Set SYN Traceroute Port")
|
||||
var numMeasurements = fSet.Int("q", 3, "Set the number of probes per each hop.")
|
||||
var parallelRequests = fSet.Int("r", 18, "Set ParallelRequests number. It should be 1 when there is a multi-routing.")
|
||||
var maxHops = fSet.Int("m", 30, "Set the max number of hops (max TTL to be reached).")
|
||||
var dataOrigin = fSet.String("d", "LeoMoeAPI", "Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight, IPAPI.com]")
|
||||
var noRdns = fSet.Bool("n", false, "Disable IP Reverse DNS lookup")
|
||||
var routePath = fSet.Bool("report", false, "Route Path")
|
||||
var output = fSet.Bool("o", false, "Ouput trace result to file (RealTimePrinter ONLY")
|
||||
var tablePrint = fSet.Bool("table", false, "Output trace results as table")
|
||||
var classicPrint = fSet.Bool("classic", false, "Classic Output trace results like BestTrace")
|
||||
var beginHop = fSet.Int("b", 1, "Set The Begin TTL")
|
||||
var maptrace = fSet.Bool("M", false, "Print Trace Map")
|
||||
var ver = fSet.Bool("V", false, "Print Version")
|
||||
var src_addr = fSet.String("S", "", "Use the following IP address as the source address in outgoing packets")
|
||||
var src_dev = fSet.String("D", "", "Use the following Network Devices as the source address in outgoing packets")
|
||||
var router = fSet.Bool("R", false, "Show Routing Table [Provided By BGP.Tools]")
|
||||
|
||||
func printArgHelp() {
|
||||
fmt.Println("\nArgs Error\nUsage : 'nexttrace [option...] HOSTNAME' or 'nexttrace HOSTNAME [option...]'\nOPTIONS: [-VTU] [-d DATAORIGIN.STR ] [ -m TTL ] [ -p PORT ] [ -q PROBES.COUNT ] [ -r PARALLELREQUESTS.COUNT ] [-rdns] [ -table ] -report")
|
||||
fSet.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func flagApply() string {
|
||||
printer.Version()
|
||||
|
||||
target := ""
|
||||
if len(os.Args) < 2 {
|
||||
printArgHelp()
|
||||
}
|
||||
|
||||
// flag parse
|
||||
if !strings.HasPrefix(os.Args[1], "-") {
|
||||
target = os.Args[1]
|
||||
fSet.Parse(os.Args[2:])
|
||||
} else {
|
||||
fSet.Parse(os.Args[1:])
|
||||
target = fSet.Arg(0)
|
||||
}
|
||||
|
||||
// Print Version
|
||||
if *ver {
|
||||
printer.CopyRight()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// -f Fast Test
|
||||
if *fastTest {
|
||||
fastTrace.FastTest(*tcpSYNFlag, *output)
|
||||
if *output {
|
||||
fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中")
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if target == "" {
|
||||
printArgHelp()
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func capabilities_check() {
|
||||
|
||||
// Windows 判断放在前面,防止遇到一些奇奇怪怪的问题
|
||||
if runtime.GOOS == "windows" {
|
||||
// Running on Windows, skip checking capabilities
|
||||
return
|
||||
}
|
||||
|
||||
uid := os.Getuid()
|
||||
if uid == 0 {
|
||||
// Running as root, skip checking capabilities
|
||||
return
|
||||
}
|
||||
|
||||
/***
|
||||
* 检查当前进程是否有两个关键的权限
|
||||
==== 看不到我 ====
|
||||
* 没办法啦
|
||||
* 自己之前承诺的坑补全篇
|
||||
* 被迫填坑系列 qwq
|
||||
==== 看不到我 ====
|
||||
***/
|
||||
|
||||
// NewPid 已经被废弃了,这里改用 NewPid2 方法
|
||||
caps, err := capability.NewPid2(0)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// load 获取全部的 caps 信息
|
||||
err = caps.Load()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 判断一下权限有木有
|
||||
if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) && caps.Get(capability.EFFECTIVE, capability.CAP_NET_ADMIN) {
|
||||
// 有权限啦
|
||||
return
|
||||
} else {
|
||||
// 没权限啦
|
||||
log.Println("您正在以普通用户权限运行 NextTrace,但 NextTrace 未被赋予监听网络套接字的ICMP消息包、修改IP头信息(TTL)等路由跟踪所需的权限")
|
||||
log.Println("请使用管理员用户执行 `sudo setcap cap_net_raw,cap_net_admin+eip ${your_nexttrace_path}/nexttrace` 命令,赋予相关权限后再运行~")
|
||||
log.Fatalln("什么?为什么 ping 普通用户执行不要 root 权限?因为这些工具在管理员安装时就已经被赋予了一些必要的权限,具体请使用 `getcap /usr/bin/ping` 查看")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
domain := flagApply()
|
||||
|
||||
capabilities_check()
|
||||
// return
|
||||
|
||||
var ip net.IP
|
||||
|
||||
if runtime.GOOS == "windows" && (*tcpSYNFlag || *udpPackageFlag) {
|
||||
fmt.Println("NextTrace 基于 Windows 的路由跟踪还在早期开发阶段,目前还存在诸多问题,TCP/UDP SYN 包请求可能不能正常运行")
|
||||
}
|
||||
|
||||
if *tcpSYNFlag || *udpPackageFlag {
|
||||
ip = util.DomainLookUp(domain, true)
|
||||
} else {
|
||||
ip = util.DomainLookUp(domain, false)
|
||||
}
|
||||
|
||||
if *src_dev != "" {
|
||||
dev, _ := net.InterfaceByName(*src_dev)
|
||||
|
||||
if addrs, err := dev.Addrs(); err == nil {
|
||||
for _, addr := range addrs {
|
||||
if (addr.(*net.IPNet).IP.To4() == nil) == (ip.To4() == nil) {
|
||||
*src_addr = addr.(*net.IPNet).IP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" {
|
||||
w := wshandle.New()
|
||||
w.Interrupt = make(chan os.Signal, 1)
|
||||
signal.Notify(w.Interrupt, os.Interrupt)
|
||||
defer func() {
|
||||
w.Conn.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
|
||||
|
||||
var m trace.Method = ""
|
||||
|
||||
switch {
|
||||
case *tcpSYNFlag:
|
||||
m = trace.TCPTrace
|
||||
case *udpPackageFlag:
|
||||
m = trace.UDPTrace
|
||||
default:
|
||||
m = trace.ICMPTrace
|
||||
}
|
||||
|
||||
if !*tcpSYNFlag && *port == 80 {
|
||||
*port = 53
|
||||
}
|
||||
|
||||
var conf = trace.Config{
|
||||
SrcAddr: *src_addr,
|
||||
BeginHop: *beginHop,
|
||||
DestIP: ip,
|
||||
DestPort: *port,
|
||||
MaxHops: *maxHops,
|
||||
NumMeasurements: *numMeasurements,
|
||||
ParallelRequests: *parallelRequests,
|
||||
RDns: !*noRdns,
|
||||
IPGeoSource: ipgeo.GetSource(*dataOrigin),
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
if !*tablePrint {
|
||||
if *classicPrint {
|
||||
conf.RealtimePrinter = printer.ClassicPrinter
|
||||
} else {
|
||||
if *output {
|
||||
conf.RealtimePrinter = tracelog.RealtimePrinter
|
||||
} else if *router {
|
||||
conf.RealtimePrinter = printer.RealtimePrinterWithRouter
|
||||
fmt.Println("路由表数据源由 BGP.Tools 提供,在此特表感谢")
|
||||
} else {
|
||||
conf.RealtimePrinter = printer.RealtimePrinter
|
||||
}
|
||||
}
|
||||
} else {
|
||||
conf.AsyncPrinter = printer.TracerouteTablePrinter
|
||||
}
|
||||
|
||||
res, err := trace.Traceroute(m, conf)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if *tablePrint {
|
||||
printer.TracerouteTablePrinter(res)
|
||||
}
|
||||
|
||||
if *routePath {
|
||||
r := reporter.New(res, ip.String())
|
||||
r.Print()
|
||||
}
|
||||
|
||||
if *maptrace {
|
||||
r, _ := json.Marshal(res)
|
||||
tracemap.GetMapUrl(string(r))
|
||||
}
|
||||
|
||||
cmd.Excute()
|
||||
}
|
||||
|
||||
@@ -79,8 +79,7 @@ func RealtimePrinter(res *trace.Result, ttl int) {
|
||||
}
|
||||
fmt.Fprintf(color.Output, " %s", color.New(color.FgHiGreen, color.Bold).Sprintf("%-16s", whoisFormat[0]))
|
||||
}
|
||||
|
||||
if res.Hops[ttl][i].Geo.Country == "" {
|
||||
if len(res.Hops[ttl][i].Geo.Country) <= 1 {
|
||||
res.Hops[ttl][i].Geo.Country = "LAN Address"
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@ package trace
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -15,16 +18,42 @@ import (
|
||||
|
||||
type ICMPTracer struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestLock sync.Mutex
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) PrintFunc() {
|
||||
defer t.wg.Done()
|
||||
var ttl = t.Config.BeginHop - 1
|
||||
for {
|
||||
if t.AsyncPrinter != nil {
|
||||
t.AsyncPrinter(&t.res)
|
||||
}
|
||||
if len(t.res.Hops)-1 > ttl {
|
||||
if len(t.res.Hops[ttl]) == t.NumMeasurements {
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl)
|
||||
}
|
||||
ttl++
|
||||
|
||||
if ttl == t.final-1 || ttl >= t.MaxHops-1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
<-time.After(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) Execute() (*Result, error) {
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
@@ -40,30 +69,46 @@ func (t *ICMPTracer) Execute() (*Result, error) {
|
||||
var cancel context.CancelFunc
|
||||
t.ctx, cancel = context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
t.resCh = make(chan Hop)
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
t.wg.Add(1)
|
||||
go t.PrintFunc()
|
||||
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
|
||||
t.inflightRequestLock.Lock()
|
||||
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
|
||||
t.inflightRequestLock.Unlock()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
break
|
||||
}
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
|
||||
}
|
||||
// 一组TTL全部退出(收到应答或者超时终止)以后,再进行下一个TTL的包发送
|
||||
t.wg.Wait()
|
||||
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
|
||||
}
|
||||
|
||||
t.wg.Wait()
|
||||
t.res.reduce(t.final)
|
||||
if t.final != -1 {
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl-1)
|
||||
t.RealtimePrinter(&t.res, t.final-1)
|
||||
}
|
||||
if t.AsyncPrinter != nil {
|
||||
t.AsyncPrinter(&t.res)
|
||||
} else {
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: 30,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, t.MaxHops-1)
|
||||
}
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
|
||||
@@ -78,55 +123,136 @@ func (t *ICMPTracer) listenICMP() {
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if binary.BigEndian.Uint16(msg.Msg[32:34]) != uint16(os.Getpid()&0xffff) {
|
||||
// 如果类型为应答消息,且应答消息包的进程ID与主进程相同时不跳过
|
||||
if msg.Msg[0] != 0 || binary.BigEndian.Uint16(msg.Msg[4:6]) != uint16(os.Getpid()&0xffff) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
dstip := net.IP(msg.Msg[24:28])
|
||||
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv4zero) {
|
||||
// 匹配再继续解析包,否则直接丢弃
|
||||
// log.Println(msg.Msg)
|
||||
if msg.Msg[0] == 0 {
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv4.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
echoReply := rm.Body.(*icmp.Echo)
|
||||
ttl := echoReply.Seq // This is the TTL value
|
||||
if ttl > 100 {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() == t.DestIP.String() {
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, ttl)
|
||||
}
|
||||
continue
|
||||
}
|
||||
packet_id := strconv.FormatInt(int64(binary.BigEndian.Uint16(msg.Msg[32:34])), 2)
|
||||
if process_id, ttl, err := reverseID(packet_id); err == nil {
|
||||
if process_id == int64(os.Getpid()&0x7f) {
|
||||
dstip := net.IP(msg.Msg[24:28])
|
||||
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv4zero) {
|
||||
// 匹配再继续解析包,否则直接丢弃
|
||||
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch rm.Type {
|
||||
case ipv4.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data, int(ttl))
|
||||
case ipv4.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, int(ttl))
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
|
||||
t.resCh <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
if _, ok := t.inflightRequest[ttl]; ok {
|
||||
t.inflightRequest[ttl] <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func gernerateID(ttl_int int) int {
|
||||
const ID_FIXED_HEADER = "10"
|
||||
var processID = fmt.Sprintf("%07b", os.Getpid()&0x7f) //取进程ID的前7位
|
||||
var ttl = fmt.Sprintf("%06b", ttl_int) //取TTL的后6位
|
||||
|
||||
var parity int
|
||||
id := ID_FIXED_HEADER + processID + ttl
|
||||
for _, c := range id {
|
||||
if c == '1' {
|
||||
parity++
|
||||
}
|
||||
}
|
||||
if parity%2 == 0 {
|
||||
id += "1"
|
||||
} else {
|
||||
id += "0"
|
||||
}
|
||||
|
||||
res, _ := strconv.ParseInt(id, 2, 64)
|
||||
return int(res)
|
||||
}
|
||||
|
||||
func reverseID(id string) (int64, int64, error) {
|
||||
if len(id) < 16 {
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
ttl, err := strconv.ParseInt(id[9:15], 2, 32)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
//process ID
|
||||
processID, _ := strconv.ParseInt(id[2:9], 2, 32)
|
||||
|
||||
parity := 0
|
||||
for i := 0; i < len(id)-1; i++ {
|
||||
if id[i] == '1' {
|
||||
parity++
|
||||
}
|
||||
}
|
||||
|
||||
if parity%2 == 1 {
|
||||
if id[len(id)-1] == '0' {
|
||||
// fmt.Println("Parity check passed.")
|
||||
} else {
|
||||
// fmt.Println("Parity check failed.")
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
} else {
|
||||
if id[len(id)-1] == '1' {
|
||||
// fmt.Println("Parity check passed.")
|
||||
} else {
|
||||
// fmt.Println("Parity check failed.")
|
||||
return 0, 0, errors.New("err")
|
||||
}
|
||||
}
|
||||
return processID, ttl, nil
|
||||
}
|
||||
|
||||
func (t *ICMPTracer) send(ttl int) error {
|
||||
|
||||
defer t.wg.Done()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
|
||||
id := gernerateID(ttl)
|
||||
// log.Println("发送的", id)
|
||||
|
||||
icmpHeader := icmp.Message{
|
||||
Type: ipv4.ICMPTypeEcho, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
ID: id,
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
Seq: ttl,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -148,7 +274,7 @@ func (t *ICMPTracer) send(ttl int) error {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-t.resCh:
|
||||
case h := <-t.inflightRequest[ttl]:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
@@ -156,6 +282,7 @@ func (t *ICMPTracer) send(ttl int) error {
|
||||
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
|
||||
t.finalLock.Lock()
|
||||
if t.final == -1 || ttl < t.final {
|
||||
|
||||
t.final = ttl
|
||||
}
|
||||
t.finalLock.Unlock()
|
||||
@@ -173,7 +300,6 @@ func (t *ICMPTracer) send(ttl int) error {
|
||||
h.fetchIPData(t.Config)
|
||||
|
||||
t.res.add(h)
|
||||
|
||||
case <-time.After(t.Timeout):
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
@@ -186,6 +312,7 @@ func (t *ICMPTracer) send(ttl int) error {
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -15,16 +16,45 @@ import (
|
||||
|
||||
type ICMPTracerv6 struct {
|
||||
Config
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
res Result
|
||||
ctx context.Context
|
||||
resCh chan Hop
|
||||
inflightRequest map[int]chan Hop
|
||||
inflightRequestLock sync.Mutex
|
||||
icmpListen net.PacketConn
|
||||
final int
|
||||
finalLock sync.Mutex
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) PrintFunc() {
|
||||
// defer t.wg.Done()
|
||||
var ttl = t.Config.BeginHop - 1
|
||||
for {
|
||||
if t.AsyncPrinter != nil {
|
||||
t.AsyncPrinter(&t.res)
|
||||
}
|
||||
|
||||
// 接收的时候检查一下是不是 3 跳都齐了
|
||||
if len(t.res.Hops)-1 > ttl {
|
||||
if len(t.res.Hops[ttl]) == t.NumMeasurements {
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl)
|
||||
}
|
||||
ttl++
|
||||
if ttl == t.final {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<-time.After(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) Execute() (*Result, error) {
|
||||
t.inflightRequest = make(map[int]chan Hop)
|
||||
|
||||
if len(t.res.Hops) > 0 {
|
||||
return &t.res, ErrTracerouteExecuted
|
||||
}
|
||||
@@ -44,26 +74,57 @@ func (t *ICMPTracerv6) Execute() (*Result, error) {
|
||||
t.final = -1
|
||||
|
||||
go t.listenICMP()
|
||||
|
||||
go t.PrintFunc()
|
||||
for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
|
||||
t.inflightRequestLock.Lock()
|
||||
t.inflightRequest[ttl] = make(chan Hop, t.NumMeasurements)
|
||||
t.inflightRequestLock.Unlock()
|
||||
if t.final != -1 && ttl > t.final {
|
||||
break
|
||||
}
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.wg.Add(1)
|
||||
go t.send(ttl)
|
||||
<-time.After(time.Millisecond * time.Duration(t.Config.PacketInterval))
|
||||
}
|
||||
// 一组TTL全部退出(收到应答或者超时终止)以后,再进行下一个TTL的包发送
|
||||
t.wg.Wait()
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, ttl-1)
|
||||
}
|
||||
<-time.After(time.Millisecond * time.Duration(t.Config.TTLInterval))
|
||||
}
|
||||
// for ttl := t.BeginHop; ttl <= t.MaxHops; ttl++ {
|
||||
// if t.final != -1 && ttl > t.final {
|
||||
// break
|
||||
// }
|
||||
// for i := 0; i < t.NumMeasurements; i++ {
|
||||
// t.wg.Add(1)
|
||||
// go t.send(ttl)
|
||||
// }
|
||||
// // 一组TTL全部退出(收到应答或者超时终止)以后,再进行下一个TTL的包发送
|
||||
// t.wg.Wait()
|
||||
// if t.RealtimePrinter != nil {
|
||||
// t.RealtimePrinter(&t.res, ttl-1)
|
||||
// }
|
||||
|
||||
if t.AsyncPrinter != nil {
|
||||
t.AsyncPrinter(&t.res)
|
||||
// if t.AsyncPrinter != nil {
|
||||
// t.AsyncPrinter(&t.res)
|
||||
// }
|
||||
// }
|
||||
t.wg.Wait()
|
||||
t.res.reduce(t.final)
|
||||
if t.final != -1 {
|
||||
t.RealtimePrinter(&t.res, t.final-1)
|
||||
} else {
|
||||
for i := 0; i < t.NumMeasurements; i++ {
|
||||
t.res.add(Hop{
|
||||
Success: false,
|
||||
Address: nil,
|
||||
TTL: 30,
|
||||
RTT: 0,
|
||||
Error: ErrHopLimitTimeout,
|
||||
})
|
||||
}
|
||||
if t.RealtimePrinter != nil {
|
||||
t.RealtimePrinter(&t.res, t.MaxHops-1)
|
||||
}
|
||||
}
|
||||
t.res.reduce(t.final)
|
||||
|
||||
return &t.res, nil
|
||||
}
|
||||
@@ -79,46 +140,98 @@ func (t *ICMPTracerv6) listenICMP() {
|
||||
if msg.N == nil {
|
||||
continue
|
||||
}
|
||||
dstip := net.IP(msg.Msg[32:48])
|
||||
if binary.BigEndian.Uint16(msg.Msg[52:54]) != uint16(os.Getpid()&0xffff) {
|
||||
// // 如果类型为应答消息,且应答消息包的进程ID与主进程相同时不跳过
|
||||
if binary.BigEndian.Uint16(msg.Msg[52:54]) != 0 {
|
||||
continue
|
||||
} else {
|
||||
if dstip.String() != "::" {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() != t.DestIP.String() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
|
||||
if msg.Msg[0] == 129 {
|
||||
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
// log.Println(msg.Peer)
|
||||
switch rm.Type {
|
||||
case ipv6.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
case ipv6.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
|
||||
echoReply, ok := rm.Body.(*icmp.Echo)
|
||||
|
||||
if ok {
|
||||
ttl := echoReply.Seq // This is the TTL value
|
||||
|
||||
if ttl > 100 {
|
||||
continue
|
||||
}
|
||||
if msg.Peer.String() == t.DestIP.String() {
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
packet_id := strconv.FormatInt(int64(binary.BigEndian.Uint16(msg.Msg[52:54])), 2)
|
||||
if process_id, ttl, err := reverseID(packet_id); err == nil {
|
||||
if process_id == int64(os.Getpid()&0x7f) {
|
||||
dstip := net.IP(msg.Msg[32:48])
|
||||
// 无效包本地环回包
|
||||
if dstip.String() == "::" {
|
||||
continue
|
||||
}
|
||||
if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
|
||||
// 匹配再继续解析包,否则直接丢弃
|
||||
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch rm.Type {
|
||||
case ipv6.ICMPTypeTimeExceeded:
|
||||
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data, int(ttl))
|
||||
case ipv6.ICMPTypeEchoReply:
|
||||
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data, int(ttl))
|
||||
default:
|
||||
// log.Println("received icmp message of unknown type", rm.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// dstip := net.IP(msg.Msg[32:48])
|
||||
// if binary.BigEndian.Uint16(msg.Msg[52:54]) != uint16(os.Getpid()&0xffff) {
|
||||
// // // 如果类型为应答消息,且应答消息包的进程ID与主进程相同时不跳过
|
||||
// if binary.BigEndian.Uint16(msg.Msg[52:54]) != 0 {
|
||||
// continue
|
||||
// } else {
|
||||
// if dstip.String() != "::" {
|
||||
// continue
|
||||
// }
|
||||
// if msg.Peer.String() != t.DestIP.String() {
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if dstip.Equal(t.DestIP) || dstip.Equal(net.IPv6zero) {
|
||||
// rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// continue
|
||||
// }
|
||||
// // log.Println(msg.Peer)
|
||||
// switch rm.Type {
|
||||
// case ipv6.ICMPTypeTimeExceeded:
|
||||
// t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
|
||||
// case ipv6.ICMPTypeEchoReply:
|
||||
// t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
|
||||
// default:
|
||||
// // log.Println("received icmp message of unknown type", rm.Type)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
|
||||
t.resCh <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte, ttl int) {
|
||||
t.inflightRequestLock.Lock()
|
||||
defer t.inflightRequestLock.Unlock()
|
||||
if _, ok := t.inflightRequest[ttl]; ok {
|
||||
t.inflightRequest[ttl] <- Hop{
|
||||
Success: true,
|
||||
Address: msg.Peer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,12 +240,14 @@ func (t *ICMPTracerv6) send(ttl int) error {
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
}
|
||||
id := gernerateID(ttl)
|
||||
|
||||
icmpHeader := icmp.Message{
|
||||
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: os.Getpid() & 0xffff,
|
||||
ID: id,
|
||||
Data: []byte("HELLO-R-U-THERE"),
|
||||
Seq: ttl,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -157,7 +272,7 @@ func (t *ICMPTracerv6) send(ttl int) error {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil
|
||||
case h := <-t.resCh:
|
||||
case h := <-t.inflightRequest[ttl]:
|
||||
rtt := time.Since(start)
|
||||
if t.final != -1 && ttl > t.final {
|
||||
return nil
|
||||
|
||||
@@ -90,7 +90,7 @@ func (t *TCPTracer) Execute() (*Result, error) {
|
||||
if t.AsyncPrinter != nil {
|
||||
for {
|
||||
t.AsyncPrinter(&t.res)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ type Config struct {
|
||||
Quic bool
|
||||
IPGeoSource ipgeo.Source
|
||||
RDns bool
|
||||
PacketInterval int
|
||||
TTLInterval int
|
||||
RealtimePrinter func(res *Result, ttl int)
|
||||
AsyncPrinter func(res *Result)
|
||||
}
|
||||
@@ -116,10 +118,25 @@ type Hop struct {
|
||||
}
|
||||
|
||||
func (h *Hop) fetchIPData(c Config) (err error) {
|
||||
timeout := time.Millisecond * 800
|
||||
if c.RDns && h.Hostname == "" {
|
||||
ptr, err := net.LookupAddr(h.Address.String())
|
||||
if err == nil && len(ptr) > 0 {
|
||||
h.Hostname = ptr[0]
|
||||
result := make(chan []string)
|
||||
go func() {
|
||||
r, err := net.LookupAddr(h.Address.String())
|
||||
if err != nil {
|
||||
result <- nil
|
||||
} else {
|
||||
result <- r
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case ptr := <-result:
|
||||
// process result
|
||||
if err == nil && len(ptr) > 0 {
|
||||
h.Hostname = ptr[0]
|
||||
}
|
||||
case <-time.After(timeout):
|
||||
// handle timeout
|
||||
}
|
||||
}
|
||||
if c.IPGeoSource != nil && h.Geo == nil {
|
||||
|
||||
@@ -73,7 +73,7 @@ func (t *UDPTracer) Execute() (*Result, error) {
|
||||
if t.AsyncPrinter != nil {
|
||||
for {
|
||||
t.AsyncPrinter(&t.res)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
Reference in New Issue
Block a user