diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7e76dadb..32f2f445 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -17,6 +17,11 @@ copyright: [v2fly](https://github.com/v2fly) 除非特殊情况,请完整填写所有问题。不按模板发的 issue 将直接被关闭。 如果你遇到的问题不是 nexttrace 的 bug,比如你不清楚如何配置,请在 https://github.com/nxtrace/NTrace-core/discussions 进行讨论。 --> +## 本项目是基于Linux/macOS的,请确认您遇到的问题是否在Linux或macOS上存在。 + + + + ## 你正在使用哪个版本的 nexttrace? @@ -39,6 +44,8 @@ copyright: [v2fly](https://github.com/v2fly) ## 请附上出错时软件输出的错误信息 + + ## 是否查询过本仓库wiki有没有类似错误 - + diff --git a/README.md b/README.md index b7bc3ee6..b82cefa6 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,8 @@ nexttrace --parallel-requests 1 www.hkix.net # Start Trace with TTL of 5, end at TTL of 10 nexttrace --first 5 --max-hops 10 www.decix.net +# In addition, an ENV is provided to set whether to hide the destination IP +export NEXTTRACE_ENABLEHIDDENDSTIP=1 # Turn off the IP reverse parsing function nexttrace --no-rdns www.bbix.net @@ -275,8 +277,8 @@ nexttrace --data-provider IPAPI.com --max-hops 20 --tcp --port 443 --queries 5 - nexttrace -tcp --queries 2 --parallel-requests 1 --table --route-path 2001:4860:4860::8888 Equivalent to: -nexttrace -d IPAPI.com -m 20 -T -p 443 -q 5 -n 1.1.1.1 -nexttrace -T -q 2 --parallel-requests 1 -t -R 2001:4860:4860::8888 +nexttrace -d ip-api.com -m 20 -T -p 443 -q 5 -n 1.1.1.1 +nexttrace -T -q 2 --parallel-requests 1 -t -P 2001:4860:4860::8888 ``` ### IP Database @@ -302,15 +304,16 @@ Usage: nexttrace [-h|--help] [-4|--ipv4] [-6|--ipv6] [-T|--tcp] [-U|--udp] ] [--parallel-requests ] [-m|--max-hops ] [-d|--data-provider (Ip2region|ip2region|IP.SB|ip.sb|IPInfo|ipinfo|IPInsight|ipinsight|IPAPI.com|ip-api.com|IPInfoLocal|ipinfolocal|chunzhen|LeoMoeAPI|leomoeapi|disable-geoip)] - [-n|--no-rdns] [-a|--always-rdns] [-P|--route-path] - [-r|--report] [--dn42] [-o|--output] [-t|--table] [--raw] - [-j|--json] [-c|--classic] [-f|--first ] [-M|--map] + [--pow-provider (api.leo.moe|sakura)] [-n|--no-rdns] + [-a|--always-rdns] [-P|--route-path] [-r|--report] [--dn42] + [-o|--output] [-t|--table] [--raw] [-j|--json] [-c|--classic] + [-f|--first ] [-M|--map] [-e|--disable-mpls] [-v|--version] [-s|--source ""] [-D|--dev ""] - [-R|--route] [-z|--send-time ] [-i|--ttl-time - ] [--timeout ] [--psize ] - [_positionalArg_nexttrace_31 ""] [--dot-server + [-z|--send-time ] [-i|--ttl-time ] + [--timeout ] [--psize ] + [_positionalArg_nexttrace_32 ""] [--dot-server (dnssb|aliyun|dnspod|google|cloudflare)] [-g|--language - (en|cn)] + (en|cn)] [--file ""] Arguments: @@ -367,7 +370,6 @@ Arguments: -s --source Use source src_addr for outgoing packets -D --dev Use the following Network Devices as the source address in outgoing packets - -R --route Show Routing Table [Provided By BGP.Tools] -z --send-time Set how many [milliseconds] between sending each packet.. Useful when some routers use rate-limit for ICMP messages. @@ -381,7 +383,7 @@ Arguments: connection.. Default: 1000 --psize Set the packet size (payload size). Default: 52 - --_positionalArg_nexttrace_31 IP Address or domain name + --_positionalArg_nexttrace_32 IP Address or domain name --dot-server Use DoT Server for DNS Parse [dnssb, aliyun, dnspod, google, cloudflare] -g --language Choose the language for displaying [en, diff --git a/README_zh_CN.md b/README_zh_CN.md index f03fae35..9543fe33 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -243,6 +243,8 @@ nexttrace --parallel-requests 1 www.hkix.net # 从TTL为5开始发送探测包,直到TTL为10结束 nexttrace --first 5 --max-hops 10 www.decix.net +# 此外还提供了一个ENV,可以设置是否隐匿目的IP +export NEXTTRACE_ENABLEHIDDENDSTIP=1 # 关闭IP反向解析功能 nexttrace --no-rdns www.bbix.net @@ -291,7 +293,7 @@ nexttrace -tcp --queries 2 --parallel-requests 1 --table --route-path 2001:4860: Equivalent to: nexttrace -d ip-api.com -m 20 -T -p 443 -q 5 -n 1.1.1.1 -nexttrace -T -q 2 --parallel-requests 1 -t -R 2001:4860:4860::8888 +nexttrace -T -q 2 --parallel-requests 1 -t -P 2001:4860:4860::8888 ``` ### 全部用法详见 Usage 菜单 @@ -302,15 +304,16 @@ Usage: nexttrace [-h|--help] [-4|--ipv4] [-6|--ipv6] [-T|--tcp] [-U|--udp] ] [--parallel-requests ] [-m|--max-hops ] [-d|--data-provider (Ip2region|ip2region|IP.SB|ip.sb|IPInfo|ipinfo|IPInsight|ipinsight|IPAPI.com|ip-api.com|IPInfoLocal|ipinfolocal|chunzhen|LeoMoeAPI|leomoeapi|disable-geoip)] - [-n|--no-rdns] [-a|--always-rdns] [-P|--route-path] - [-r|--report] [--dn42] [-o|--output] [-t|--table] [--raw] - [-j|--json] [-c|--classic] [-f|--first ] [-M|--map] + [--pow-provider (api.leo.moe|sakura)] [-n|--no-rdns] + [-a|--always-rdns] [-P|--route-path] [-r|--report] [--dn42] + [-o|--output] [-t|--table] [--raw] [-j|--json] [-c|--classic] + [-f|--first ] [-M|--map] [-e|--disable-mpls] [-v|--version] [-s|--source ""] [-D|--dev ""] - [-R|--route] [-z|--send-time ] [-i|--ttl-time - ] [--timeout ] [--psize ] - [_positionalArg_nexttrace_31 ""] [--dot-server + [-z|--send-time ] [-i|--ttl-time ] + [--timeout ] [--psize ] + [_positionalArg_nexttrace_32 ""] [--dot-server (dnssb|aliyun|dnspod|google|cloudflare)] [-g|--language - (en|cn)] + (en|cn)] [--file ""] Arguments: @@ -367,7 +370,6 @@ Arguments: -s --source Use source src_addr for outgoing packets -D --dev Use the following Network Devices as the source address in outgoing packets - -R --route Show Routing Table [Provided By BGP.Tools] -z --send-time Set how many [milliseconds] between sending each packet.. Useful when some routers use rate-limit for ICMP messages. @@ -381,7 +383,7 @@ Arguments: connection.. Default: 1000 --psize Set the packet size (payload size). Default: 52 - --_positionalArg_nexttrace_31 IP Address or domain name + --_positionalArg_nexttrace_32 IP Address or domain name --dot-server Use DoT Server for DNS Parse [dnssb, aliyun, dnspod, google, cloudflare] -g --language Choose the language for displaying [en, diff --git a/cmd/cmd.go b/cmd/cmd.go index b380ffdb..3d8f8bef 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -59,7 +59,7 @@ func Excute() { ver := parser.Flag("v", "version", &argparse.Options{Help: "Print version info and exit"}) srcAddr := parser.String("s", "source", &argparse.Options{Help: "Use source src_addr for outgoing packets"}) srcDev := 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]"}) + //router := parser.Flag("R", "route", &argparse.Options{Help: "Show Routing Table [Provided By BGP.Tools]"}) packetInterval := parser.Int("z", "send-time", &argparse.Options{Default: 100, Help: "Set how many [milliseconds] between sending each packet.. Useful when some routers use rate-limit for ICMP messages"}) ttlInterval := parser.Int("i", "ttl-time", &argparse.Options{Default: 500, Help: "Set how many [milliseconds] between sending packets groups by TTL. Useful when some routers use rate-limit for ICMP messages"}) timeout := parser.Int("", "timeout", &argparse.Options{Default: 1000, Help: "The number of [milliseconds] to keep probe sockets open before giving up on the connection."}) @@ -245,6 +245,7 @@ func Excute() { *port = 53 } + util.DestIP = ip.String() var conf = trace.Config{ DN42: *dn42, SrcAddr: *srcAddr, @@ -264,6 +265,9 @@ func Excute() { PktSize: *packetSize, } + // 暂时弃用 + var router *bool + *router = false if !*tablePrint { if *classicPrint { conf.RealtimePrinter = printer.ClassicPrinter diff --git a/go.mod b/go.mod index 2c2f4ae3..06971687 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 github.com/tsosunchia/powclient v0.1.4 - golang.org/x/net v0.17.0 + golang.org/x/net v0.18.0 golang.org/x/sync v0.5.0 ) @@ -32,7 +32,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index d0287440..a89d38cc 100644 --- a/go.sum +++ b/go.sum @@ -239,8 +239,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -296,8 +296,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/printer/basic.go b/printer/basic.go index abfe223f..85b92f6f 100644 --- a/printer/basic.go +++ b/printer/basic.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/nxtrace/NTrace-core/config" "github.com/nxtrace/NTrace-core/trace" + "github.com/nxtrace/NTrace-core/util" "net" "github.com/fatih/color" @@ -78,11 +79,14 @@ func sponsor() { func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string, maxHops int, packetSize int) { fmt.Println("IP Geo Data Provider: " + dataOrigin) - - if ip.String() == domain { - fmt.Printf("traceroute to %s, %d hops max, %d bytes packets\n", ip.String(), maxHops, packetSize) + if util.EnableHidDstIP == "" { + if ip.String() == domain { + fmt.Printf("traceroute to %s, %d hops max, %d bytes packets\n", ip.String(), maxHops, packetSize) + } else { + fmt.Printf("traceroute to %s (%s), %d hops max, %d bytes packets\n", ip.String(), domain, maxHops, packetSize) + } } else { - fmt.Printf("traceroute to %s (%s), %d hops max, %d bytes packets\n", ip.String(), domain, maxHops, packetSize) + fmt.Printf("traceroute to %s, %d hops max, %d bytes packets\n", util.HideIPPart(ip.String()), maxHops, packetSize) } } diff --git a/printer/realtime_printer.go b/printer/realtime_printer.go index f7bf1101..9b7566a9 100644 --- a/printer/realtime_printer.go +++ b/printer/realtime_printer.go @@ -2,6 +2,7 @@ package printer import ( "fmt" + "github.com/nxtrace/NTrace-core/util" "net" "strconv" "strings" @@ -12,7 +13,6 @@ import ( func RealtimePrinter(res *trace.Result, ttl int) { fmt.Printf("%s ", color.New(color.FgHiYellow, color.Bold).Sprintf("%-2d", ttl+1)) - // 去重 var latestIP string tmpMap := make(map[string][]string) @@ -51,13 +51,25 @@ func RealtimePrinter(res *trace.Result, ttl int) { fmt.Printf("%4s", "") } if net.ParseIP(ip).To4() == nil { - fmt.Fprintf(color.Output, "%s", - color.New(color.FgWhite, color.Bold).Sprintf("%-25s", ip), - ) + if util.EnableHidDstIP == "" || ip != util.DestIP { + fmt.Fprintf(color.Output, "%s", + color.New(color.FgWhite, color.Bold).Sprintf("%-25s", ip), + ) + } else { + fmt.Fprintf(color.Output, "%s", + color.New(color.FgWhite, color.Bold).Sprintf("%-25s", util.HideIPPart(ip)), + ) + } } else { - fmt.Fprintf(color.Output, "%s", - color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip), - ) + if util.EnableHidDstIP == "" || ip != util.DestIP { + fmt.Fprintf(color.Output, "%s", + color.New(color.FgWhite, color.Bold).Sprintf("%-15s", ip), + ) + } else { + fmt.Fprintf(color.Output, "%s", + color.New(color.FgWhite, color.Bold).Sprintf("%-15s", util.HideIPPart(ip)), + ) + } } i, _ := strconv.Atoi(v[0]) diff --git a/trace/icmp_ipv4.go b/trace/icmp_ipv4.go index c5bc170c..54340725 100644 --- a/trace/icmp_ipv4.go +++ b/trace/icmp_ipv4.go @@ -15,6 +15,8 @@ import ( "golang.org/x/net/context" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" + + "github.com/nxtrace/NTrace-core/trace/internal" ) type ICMPTracer struct { @@ -67,7 +69,7 @@ func (t *ICMPTracer) Execute() (*Result, error) { var err error - t.icmpListen, err = net.ListenPacket("ip4:1", t.SrcAddr) + t.icmpListen, err = internal.ListenICMP("ip4:1", t.SrcAddr) if err != nil { return &t.res, err } diff --git a/trace/icmp_ipv6.go b/trace/icmp_ipv6.go index 3dc26ba0..7202788e 100644 --- a/trace/icmp_ipv6.go +++ b/trace/icmp_ipv6.go @@ -13,6 +13,8 @@ import ( "golang.org/x/net/context" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" + + "github.com/nxtrace/NTrace-core/trace/internal" ) type ICMPTracerv6 struct { @@ -65,7 +67,7 @@ func (t *ICMPTracerv6) Execute() (*Result, error) { var err error - t.icmpListen, err = net.ListenPacket("ip6:58", t.SrcAddr) + t.icmpListen, err = internal.ListenICMP("ip6:58", t.SrcAddr) if err != nil { return &t.res, err } diff --git a/trace/internal/icmp_darwin.go b/trace/internal/icmp_darwin.go index 1ae6649e..e919bc12 100644 --- a/trace/internal/icmp_darwin.go +++ b/trace/internal/icmp_darwin.go @@ -12,13 +12,14 @@ import ( ) //go:linkname internetSocket net.internetSocket -func internetSocket(ctx context.Context, net string, laddr, raddr interface{}, sotype, proto int, mode string, ctrlCtxFn func(context.Context, string, string, syscall.RawConn) error) (fd unsafe.Pointer, err error) +func internetSocket(ctx context.Context, net string, laddr, raddr any, sotype, proto int, mode string, ctrlCtxFn func(context.Context, string, string, syscall.RawConn) error) (fd unsafe.Pointer, err error) //go:linkname newIPConn net.newIPConn func newIPConn(fd unsafe.Pointer) *net.IPConn var ( errUnknownNetwork = errors.New("unknown network type") + errUnknownIface = errors.New("unknown network interface") networkMap = map[string]string{ "ip4:icmp": "udp4", @@ -28,7 +29,6 @@ var ( } ) -// ListenICMP 会造成指定出口IP功能不可使用 func ListenICMP(network string, laddr string) (net.PacketConn, error) { if os.Getuid() == 0 { // root return net.ListenPacket(network, laddr) @@ -38,7 +38,48 @@ func ListenICMP(network string, laddr string) (net.PacketConn, error) { if nw == "udp6" { proto = syscall.IPPROTO_ICMPV6 } - isock, err := internetSocket(context.Background(), nw, nil, nil, syscall.SOCK_DGRAM, proto, "listen", nil) + + var ifIndex int = -1 + if laddr != "" { + la := net.ParseIP(laddr) + if ifaces, err := net.Interfaces(); err == nil { + for _, iface := range ifaces { + addrs, err := iface.Addrs() + if err != nil { + continue + } + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok { + if ipnet.IP.Equal(la) { + ifIndex = iface.Index + break + } + } + } + } + if ifIndex == -1 { + return nil, errUnknownIface + } + } else { + return nil, err + } + } + + isock, err := internetSocket(context.Background(), nw, nil, nil, syscall.SOCK_DGRAM, proto, "listen", + func(ctx context.Context, network, address string, c syscall.RawConn) error { + if ifIndex != -1 { + if proto == syscall.IPPROTO_ICMP { + return c.Control(func(fd uintptr) { + syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifIndex) + }) + } else { + return c.Control(func(fd uintptr) { + syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifIndex) + }) + } + } + return nil + }) if err != nil { panic(err) } diff --git a/trace/trace.go b/trace/trace.go index d58f3a1c..f8d48913 100644 --- a/trace/trace.go +++ b/trace/trace.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" "sync" + "syscall" "time" "github.com/nxtrace/NTrace-core/ipgeo" @@ -92,7 +93,11 @@ func Traceroute(method Method, config Config) (*Result, error) { default: return &Result{}, ErrInvalidMethod } - return tracer.Execute() + result, err := tracer.Execute() + if err != nil && errors.Is(err, syscall.EPERM) { + err = fmt.Errorf("%w, please run as root", err) + } + return result, err } type Result struct { diff --git a/util/util.go b/util/util.go index 81c7fcca..15a08398 100644 --- a/util/util.go +++ b/util/util.go @@ -22,6 +22,8 @@ var UserAgent = fmt.Sprintf("NextTrace %s/%s/%s", config.Version, runtime.GOOS, var RdnsCache sync.Map var PowProviderParam = "" var DisableMPLS = GetenvDefault("NEXTTRACE_DISABLEMPLS", "") +var EnableHidDstIP = GetenvDefault("NEXTTRACE_ENABLEHIDDENDSTIP", "") +var DestIP string func LookupAddr(addr string) ([]string, error) { // 如果在缓存中找到,直接返回 @@ -226,3 +228,17 @@ func StringInSlice(val string, list []string) bool { } return false } + +func HideIPPart(ip string) string { + parsedIP := net.ParseIP(ip) + if parsedIP == nil { + return "" + } + + if parsedIP.To4() != nil { + // IPv4: 隐藏后16位 + return strings.Join(strings.Split(ip, ".")[:2], ".") + ".0.0/16" + } + // IPv6: 隐藏后96位 + return parsedIP.Mask(net.CIDRMask(32, 128)).String() + "/32" +}