Compare commits

...

13 Commits

Author SHA1 Message Date
Leo
b9b18f5efa feat: 增加了对发包的时间管理控制 2023-01-18 19:19:19 +08:00
Leo
326034b41e fix: panic on IPv6 2023-01-18 13:44:47 +08:00
Leo
09a5b42443 fix: 在 Table 模式下,无法自动退出的问题 2023-01-18 13:26:48 +08:00
Leo
b8542489b6 fix: 修复使用 -f 导致打印卡住的问题 2023-01-18 13:19:27 +08:00
Leo
a73a306d0a chore: sync document 2023-01-17 20:30:49 +08:00
Leo
801f42801a fix: Fast Trace 无法使用 2023-01-17 20:10:35 +08:00
Leo
08c4a5ceae fix: Fast Trace 无法使用 2023-01-17 20:09:53 +08:00
Leo
ce1c773996 improve: rDNS 解析等待时间过长 2023-01-17 20:08:57 +08:00
Leo
cdec6cbd8d feat: refactor flag 2023-01-17 19:08:32 +08:00
sjlleo
aa891ebd7c Typo 2023-01-15 15:07:29 +08:00
sjlleo
5d132e73ab chore: add announcement 2023-01-15 14:13:09 +08:00
Leo
63ca4d0418 fix: 一些包合法性判断上的错误 2023-01-14 20:22:07 +08:00
Leo
a6da078eb0 feat: 重构整个 ICMP 包校验模块,增加了对 ICMP 异步发包的支持 2023-01-14 18:15:23 +08:00
14 changed files with 696 additions and 353 deletions

View File

@@ -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

View File

@@ -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
View 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
View File

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

1
go.mod
View File

@@ -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
View File

@@ -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=

View File

@@ -1,11 +1,72 @@
package ipgeo
import (
"errors"
"fmt"
"log"
"os"
"strconv"
"testing"
)
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
func TestXxx(t *testing.T) {
const ID_FIXED_HEADER = "10"
var processID = fmt.Sprintf("%07b", os.Getpid()&0x7f) //取进程ID的前7位
var ttl = fmt.Sprintf("%06b", 95) //取TTL的后6位
fmt.Println(os.Getpid()&0x7f, 95)
var parity int
id := ID_FIXED_HEADER + processID + ttl
for _, c := range id {
if c == '1' {
parity++
}
}
if parity%2 == 0 {
id += "1"
} else {
id += "0"
}
process_id, ttl_r, _ := reverseID(id)
log.Println(process_id, ttl_r)
}
func reverseID(id string) (int64, int64, error) {
ttl, _ := strconv.ParseInt(id[9:15], 2, 32)
//process ID
processID, _ := strconv.ParseInt(id[2:9], 2, 32)
parity := 0
for i := 0; i < len(id)-1; i++ {
if id[i] == '1' {
parity++
}
}
if parity%2 == 1 {
if id[len(id)-1] == '0' {
fmt.Println("Parity check passed.")
} else {
fmt.Println("Parity check failed.")
return 0, 0, errors.New("err")
}
} else {
if id[len(id)-1] == '1' {
fmt.Println("Parity check passed.")
} else {
fmt.Println("Parity check failed.")
return 0, 0, errors.New("err")
}
}
return processID, ttl, nil
}
// func TestLeoIP(t *testing.T) {
// // res, err := LeoIP("1.1.1.1")
// // assert.Nil(t, err)

244
main.go
View File

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

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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 {

View File

@@ -73,7 +73,7 @@ func (t *UDPTracer) Execute() (*Result, error) {
if t.AsyncPrinter != nil {
for {
t.AsyncPrinter(&t.res)
time.Sleep(50 * time.Millisecond)
time.Sleep(200 * time.Millisecond)
}
}
}()