Compare commits

...

7 Commits

Author SHA1 Message Date
sjlleo
5f993961ed update: fix channel closed, printer imrove 2022-05-22 19:55:27 +08:00
sjlleo
ead46decf6 Add: IPv6 Traceroute 2022-05-22 15:38:05 +08:00
sjlleo
7712ebf953 Add mips 2022-05-19 14:58:09 +08:00
sjlleo
14bbc62358 Add linux mips release 2022-05-19 14:48:25 +08:00
sjlleo
4323021f96 Change Token 2022-05-19 14:31:26 +08:00
sjlleo
cbfb37f37b Update build.yml 2022-05-19 14:00:50 +08:00
sjlleo
50d594e4df Add: icmp trace 2022-05-19 13:35:50 +08:00
16 changed files with 859 additions and 40 deletions

View File

@@ -5,7 +5,7 @@ set -e
DIST_PREFIX="nexttrace"
DEBUG_MODE=${2}
TARGET_DIR="dist"
PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 linux/arm64"
PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 linux/mips"
rm -rf ${TARGET_DIR}
mkdir ${TARGET_DIR}

View File

@@ -24,5 +24,6 @@ jobs:
dist/nexttrace_darwin_arm64
dist/nexttrace_linux_amd64
dist/nexttrace_linux_arm64
dist/nexttrace_linux_mips
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GT_Token }}

View File

@@ -8,6 +8,6 @@ type tokenData struct {
var token = tokenData{
ipinsight: "",
ipinfo: "42764a944dabd0",
ipinfo: "",
ipleo: "NextTraceDemo",
}

13
main.go
View File

@@ -14,7 +14,8 @@ import (
"github.com/xgadget-lab/nexttrace/util"
)
var tcpSYNFlag = flag.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80 in TCP, 53 in UDP)")
var tcpSYNFlag = flag.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80)")
var udpPackageFlag = flag.Bool("U", false, "Use UDP Package for tracerouting (default port is 53 in UDP)")
var port = flag.Int("p", 80, "Set SYN Traceroute Port")
var numMeasurements = flag.Int("q", 3, "Set the number of probes per each hop.")
var parallelRequests = flag.Int("r", 18, "Set ParallelRequests number. It should be 1 when there is a multi-routing.")
@@ -44,10 +45,14 @@ func main() {
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
var m trace.Method = ""
if *tcpSYNFlag {
switch {
case *tcpSYNFlag:
m = trace.TCPTrace
} else {
case *udpPackageFlag:
m = trace.UDPTrace
default:
m = trace.ICMPTrace
}
if !*tcpSYNFlag && *port == 80 {
@@ -63,7 +68,7 @@ func main() {
RDns: *rdnsenable,
IPGeoSource: ipgeo.GetSource(*dataOrigin),
Timeout: 2 * time.Second,
RoutePath: *routePath,
//Quic: false,
}

View File

@@ -5,10 +5,6 @@ import (
"net"
)
func PrintCopyRight() {
fmt.Println("NextTrace v0.1.0 Alpha \nxgadget-lab zhshch (xzhsh.ch) & leo (leo.moe)")
}
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) {
fmt.Println("IP Geo Data Provider: " + dataOrigin)

View File

@@ -2,9 +2,10 @@ package printer
import (
"fmt"
"github.com/xgadget-lab/nexttrace/trace"
"strings"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/xgadget-lab/nexttrace/ipgeo"
)

View File

@@ -2,9 +2,10 @@ package printer
import (
"fmt"
"github.com/xgadget-lab/nexttrace/trace"
"strings"
"github.com/xgadget-lab/nexttrace/trace"
"github.com/fatih/color"
"github.com/rodaine/table"
)
@@ -30,7 +31,16 @@ func TracerouteTablePrinter(res *trace.Result) {
if k > 0 {
data.Hop = ""
}
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Prov, data.City, data.Owner)
if data.Country == "" && data.Prov == "" && data.City == "" {
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, "", data.Owner)
} else {
if data.City != "" {
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country+", "+data.Prov+", "+data.City, data.Owner)
} else {
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Owner)
}
}
}
}
// 打印表格
@@ -42,7 +52,7 @@ func New() table.Table {
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
columnFmt := color.New(color.FgYellow).SprintfFunc()
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Country", "Province", "City", "Owner")
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Location", "Owner")
tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
return tbl
}

View File

@@ -42,14 +42,16 @@ func experimentTag() {
func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData) (routeReportNode, error) {
rpn := routeReportNode{}
ptr, err := net.LookupAddr(ip)
if err == nil {
if strings.Contains(strings.ToLower(ptr[0]), "ix") {
rpn.ix = true
} else {
rpn.ix = false
go func() {
ptr, err := net.LookupAddr(ip)
if err == nil {
if strings.Contains(strings.ToLower(ptr[0]), "ix") {
rpn.ix = true
} else {
rpn.ix = false
}
}
}
}()
if strings.Contains(strings.ToLower(ipGeoData.Isp), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Isp), "ix") || strings.Contains(strings.ToLower(ipGeoData.Owner), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Owner), "ix") {
rpn.ix = true
@@ -60,7 +62,7 @@ func (r *reporter) generateRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData)
rpn.asn = ipGeoData.Asnumber
}
// 无论最后一跳是否为存在地理位置信息AnyCast都应该给予显示
if ipGeoData.Country == "" || ipGeoData.City == "" && ip != r.targetIP {
if ipGeoData.Country == "" || ipGeoData.City == "" || ipGeoData.City == "-" && ip != r.targetIP {
return rpn, errors.New("GeoData Search Failed")
} else {
if ipGeoData.City == "" {

219
trace/icmp_ipv4.go Normal file
View File

@@ -0,0 +1,219 @@
package trace
import (
"fmt"
"log"
"net"
"os"
"strconv"
"sync"
"time"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/sync/semaphore"
)
type ICMPTracer struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
inflightRequest map[int]chan Hop
inflightRequestLock sync.Mutex
icmpListen net.PacketConn
workFork workFork
final int
finalLock sync.Mutex
sem *semaphore.Weighted
}
type workFork struct {
ttl int
num int
}
func (t *ICMPTracer) Execute() (*Result, error) {
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
var err error
t.icmpListen, err = net.ListenPacket("ip4:1", "0.0.0.0")
if err != nil {
return &t.res, err
}
defer t.icmpListen.Close()
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.inflightRequest = make(map[int]chan Hop)
t.final = -1
go t.listenICMP()
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
for t.workFork.ttl = 1; t.workFork.ttl <= t.MaxHops; t.workFork.ttl++ {
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(workFork{t.workFork.ttl, i})
}
// 一组TTL全部退出收到应答或者超时终止以后再进行下一个TTL的包发送
t.wg.Wait()
t.workFork.num = 0
}
t.res.reduce(t.final)
return &t.res, nil
}
func (t *ICMPTracer) listenICMP() {
lc := NewPacketListener(t.icmpListen, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(1, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
switch rm.Type {
case ipv4.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
case ipv4.ICMPTypeEchoReply:
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
default:
// log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
func (t *ICMPTracer) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
ch, ok := t.inflightRequest[t.workFork.num]
t.workFork.num += 1
if !ok {
return
}
ch <- Hop{
Success: true,
Address: msg.Peer,
}
}
func (t *ICMPTracer) send(fork workFork) error {
err := t.sem.Acquire(context.Background(), 1)
if err != nil {
return err
}
defer t.sem.Release(1)
defer t.wg.Done()
if t.final != -1 && fork.ttl > t.final {
return nil
}
icmpHeader := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff,
Data: []byte("HELLO-R-U-THERE"),
},
}
ipv4.NewPacketConn(t.icmpListen).SetTTL(fork.ttl)
wb, err := icmpHeader.Marshal(nil)
if err != nil {
log.Fatal(err)
}
start := time.Now()
if _, err := t.icmpListen.WriteTo(wb, &net.IPAddr{IP: t.DestIP}); err != nil {
log.Fatal(err)
}
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
log.Fatal(err)
}
t.inflightRequestLock.Lock()
hopCh := make(chan Hop)
t.inflightRequest[fork.num] = hopCh
t.inflightRequestLock.Unlock()
// defer func() {
// t.inflightRequestLock.Lock()
// close(hopCh)
// delete(t.inflightRequest, fork.ttl)
// t.inflightRequestLock.Unlock()
// }()
if fork.num == 0 && t.Config.RoutePath {
fmt.Print(strconv.Itoa(fork.ttl))
}
select {
case <-t.ctx.Done():
return nil
case h := <-hopCh:
rtt := time.Since(start)
if t.final != -1 && fork.ttl > t.final {
return nil
}
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || fork.ttl < t.final {
t.final = fork.ttl
}
t.finalLock.Unlock()
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || fork.ttl < t.final {
t.final = fork.ttl
}
t.finalLock.Unlock()
}
h.TTL = fork.ttl
h.RTT = rtt
h.fetchIPData(t.Config)
if t.Config.RoutePath {
HopPrinter(h)
}
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && fork.ttl > t.final {
return nil
}
t.res.add(Hop{
Success: false,
Address: nil,
TTL: fork.ttl,
RTT: 0,
Error: ErrHopLimitTimeout,
})
if t.Config.RoutePath {
fmt.Println("\t" + "*")
}
}
return nil
}

219
trace/icmp_ipv6.go Normal file
View File

@@ -0,0 +1,219 @@
package trace
import (
"fmt"
"log"
"net"
"os"
"strconv"
"sync"
"time"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
"golang.org/x/sync/semaphore"
)
type ICMPTracerv6 struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
inflightRequest map[int]chan Hop
inflightRequestLock sync.Mutex
icmpListen net.PacketConn
workFork workFork
final int
finalLock sync.Mutex
sem *semaphore.Weighted
}
func (t *ICMPTracerv6) Execute() (*Result, error) {
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
var err error
t.icmpListen, err = net.ListenPacket("ip6:58", "::")
if err != nil {
return &t.res, err
}
defer t.icmpListen.Close()
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.inflightRequest = make(map[int]chan Hop)
t.final = -1
go t.listenICMP()
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
for t.workFork.ttl = 1; t.workFork.ttl <= t.MaxHops; t.workFork.ttl++ {
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(workFork{t.workFork.ttl, i})
}
// 一组TTL全部退出收到应答或者超时终止以后再进行下一个TTL的包发送
t.wg.Wait()
t.workFork.num = 0
}
t.res.reduce(t.final)
return &t.res, nil
}
func (t *ICMPTracerv6) listenICMP() {
lc := NewPacketListener(t.icmpListen, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(58, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
// log.Println(msg.Peer)
switch rm.Type {
case ipv6.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, 0, rm.Body.(*icmp.TimeExceeded).Data)
case ipv6.ICMPTypeEchoReply:
t.handleICMPMessage(msg, 1, rm.Body.(*icmp.Echo).Data)
default:
// log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
func (t *ICMPTracerv6) handleICMPMessage(msg ReceivedMessage, icmpType int8, data []byte) {
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
ch, ok := t.inflightRequest[t.workFork.num]
t.workFork.num += 1
if !ok {
return
}
ch <- Hop{
Success: true,
Address: msg.Peer,
}
}
func (t *ICMPTracerv6) send(fork workFork) error {
err := t.sem.Acquire(context.Background(), 1)
if err != nil {
return err
}
defer t.sem.Release(1)
defer t.wg.Done()
if t.final != -1 && fork.ttl > t.final {
return nil
}
icmpHeader := icmp.Message{
Type: ipv6.ICMPTypeEchoRequest, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff,
Data: []byte("HELLO-R-U-THERE"),
},
}
p := ipv6.NewPacketConn(t.icmpListen)
icmpHeader.Body.(*icmp.Echo).Seq = fork.ttl
p.SetHopLimit(fork.ttl)
wb, err := icmpHeader.Marshal(nil)
if err != nil {
log.Fatal(err)
}
start := time.Now()
if _, err := t.icmpListen.WriteTo(wb, &net.IPAddr{IP: t.DestIP}); err != nil {
log.Fatal(err)
}
if err := t.icmpListen.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
log.Fatal(err)
}
t.inflightRequestLock.Lock()
hopCh := make(chan Hop)
t.inflightRequest[fork.num] = hopCh
t.inflightRequestLock.Unlock()
// defer func() {
// t.inflightRequestLock.Lock()
// close(hopCh)
// delete(t.inflightRequest, fork.ttl)
// t.inflightRequestLock.Unlock()
// }()
if fork.num == 0 && t.Config.RoutePath {
fmt.Print(strconv.Itoa(fork.ttl))
}
select {
case <-t.ctx.Done():
return nil
case h := <-hopCh:
rtt := time.Since(start)
if t.final != -1 && fork.ttl > t.final {
return nil
}
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || fork.ttl < t.final {
t.final = fork.ttl
}
t.finalLock.Unlock()
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || fork.ttl < t.final {
t.final = fork.ttl
}
t.finalLock.Unlock()
}
h.TTL = fork.ttl
h.RTT = rtt
h.fetchIPData(t.Config)
if t.Config.RoutePath {
HopPrinter(h)
}
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && fork.ttl > t.final {
return nil
}
t.res.add(Hop{
Success: false,
Address: nil,
TTL: fork.ttl,
RTT: 0,
Error: ErrHopLimitTimeout,
})
if t.Config.RoutePath {
fmt.Println("\t" + "*")
}
}
return nil
}

View File

@@ -163,6 +163,7 @@ func (t *TCPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
Success: true,
Address: msg.Peer,
}
}
func (t *TCPTracer) send(ttl int) error {
@@ -219,7 +220,7 @@ func (t *TCPTracer) send(ttl int) error {
t.inflightRequest[int(sequenceNumber)] = hopCh
t.inflightRequestLock.Unlock()
/*
// 关了会有问题,偶见 panic: send on closed channel 报错
// 这里属于 2个SenderN个Reciever的情况在哪里关闭Channel都容易导致Panic
defer func() {
t.inflightRequestLock.Lock()
close(hopCh)

269
trace/tcp_ipv6.go Normal file
View File

@@ -0,0 +1,269 @@
package trace
import (
"log"
"math"
"math/rand"
"net"
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/xgadget-lab/nexttrace/listener_channel"
"github.com/xgadget-lab/nexttrace/util"
"golang.org/x/net/context"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
"golang.org/x/sync/semaphore"
)
type TCPTracerv6 struct {
Config
wg sync.WaitGroup
res Result
ctx context.Context
inflightRequest map[int]chan Hop
inflightRequestLock sync.Mutex
SrcIP net.IP
icmp net.PacketConn
tcp net.PacketConn
final int
finalLock sync.Mutex
sem *semaphore.Weighted
}
func (t *TCPTracerv6) Execute() (*Result, error) {
if len(t.res.Hops) > 0 {
return &t.res, ErrTracerouteExecuted
}
t.SrcIP, _ = util.LocalIPPort(t.DestIP)
log.Println(util.LocalIPPort(t.DestIP))
var err error
t.tcp, err = net.ListenPacket("ip6:tcp", t.SrcIP.String())
if err != nil {
return nil, err
}
t.icmp, err = icmp.ListenPacket("ip6:53", "::")
if err != nil {
return &t.res, err
}
defer t.icmp.Close()
var cancel context.CancelFunc
t.ctx, cancel = context.WithCancel(context.Background())
defer cancel()
t.inflightRequest = make(map[int]chan Hop)
t.final = -1
go t.listenICMP()
go t.listenTCP()
t.sem = semaphore.NewWeighted(int64(t.ParallelRequests))
for ttl := 1; ttl <= t.MaxHops; ttl++ {
for i := 0; i < t.NumMeasurements; i++ {
t.wg.Add(1)
go t.send(ttl)
}
time.Sleep(1 * time.Millisecond)
}
t.wg.Wait()
t.res.reduce(t.final)
return &t.res, nil
}
func (t *TCPTracerv6) listenICMP() {
lc := NewPacketListener(t.icmp, t.ctx)
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
rm, err := icmp.ParseMessage(53, msg.Msg[:*msg.N])
if err != nil {
log.Println(err)
continue
}
log.Println(msg.Peer)
switch rm.Type {
case ipv6.ICMPTypeTimeExceeded:
t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data)
case ipv6.ICMPTypeDestinationUnreachable:
t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data)
default:
//log.Println("received icmp message of unknown type", rm.Type)
}
}
}
}
// @title listenTCP
// @description 监听TCP的响应数据包
func (t *TCPTracerv6) listenTCP() {
lc := listener_channel.New(t.tcp)
defer lc.Stop()
go lc.Start()
for {
select {
case <-t.ctx.Done():
return
case msg := <-lc.Messages:
if msg.N == nil {
continue
}
if msg.Peer.String() != t.DestIP.String() {
continue
}
// 解包
packet := gopacket.NewPacket(msg.Msg[:*msg.N], layers.LayerTypeTCP, gopacket.Default)
// 从包中获取TCP layer信息
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
// 取得目标主机的Sequence Number
if ch, ok := t.inflightRequest[int(tcp.Ack-1)]; ok {
// 最后一跳
ch <- Hop{
Success: true,
Address: msg.Peer,
}
}
}
}
}
}
func (t *TCPTracerv6) handleICMPMessage(msg ReceivedMessage, data []byte) {
header, err := util.GetICMPResponsePayload(data)
if err != nil {
return
}
sequenceNumber := util.GetTCPSeq(header)
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
ch, ok := t.inflightRequest[int(sequenceNumber)]
if !ok {
return
}
ch <- Hop{
Success: true,
Address: msg.Peer,
}
}
func (t *TCPTracerv6) send(ttl int) error {
err := t.sem.Acquire(context.Background(), 1)
if err != nil {
return err
}
defer t.sem.Release(1)
defer t.wg.Done()
if t.final != -1 && ttl > t.final {
return nil
}
// 随机种子
r := rand.New(rand.NewSource(time.Now().UnixNano()))
_, srcPort := util.LocalIPPort(t.DestIP)
ipHeader := &layers.IPv6{
SrcIP: t.SrcIP,
DstIP: t.DestIP,
NextHeader: layers.IPProtocolTCP,
HopLimit: uint8(ttl),
}
// 使用Uint16兼容32位系统防止在rand的时候因使用int32而溢出
sequenceNumber := uint32(r.Intn(math.MaxUint16))
tcpHeader := &layers.TCP{
SrcPort: layers.TCPPort(srcPort),
DstPort: layers.TCPPort(t.DestPort),
Seq: sequenceNumber,
SYN: true,
Window: 14600,
}
_ = tcpHeader.SetNetworkLayerForChecksum(ipHeader)
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
if err := gopacket.SerializeLayers(buf, opts, tcpHeader); err != nil {
return err
}
ipv6.NewPacketConn(t.tcp).SetHopLimit(ttl)
if err != nil {
return err
}
start := time.Now()
if _, err := t.tcp.WriteTo(buf.Bytes(), &net.IPAddr{IP: t.DestIP}); err != nil {
return err
}
t.inflightRequestLock.Lock()
hopCh := make(chan Hop)
t.inflightRequest[int(sequenceNumber)] = hopCh
t.inflightRequestLock.Unlock()
select {
case <-t.ctx.Done():
return nil
case h := <-hopCh:
rtt := time.Since(start)
if t.final != -1 && ttl > t.final {
return nil
}
if addr, ok := h.Address.(*net.IPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
} else if addr, ok := h.Address.(*net.TCPAddr); ok && addr.IP.Equal(t.DestIP) {
t.finalLock.Lock()
if t.final == -1 || ttl < t.final {
t.final = ttl
}
t.finalLock.Unlock()
}
h.TTL = ttl
h.RTT = rtt
h.fetchIPData(t.Config)
t.res.add(h)
case <-time.After(t.Timeout):
if t.final != -1 && ttl > t.final {
return nil
}
t.res.add(Hop{
Success: false,
Address: nil,
TTL: ttl,
RTT: 0,
Error: ErrHopLimitTimeout,
})
}
return nil
}

76
trace/temp_printer.go Normal file
View File

@@ -0,0 +1,76 @@
package trace
import (
"fmt"
"strings"
"github.com/xgadget-lab/nexttrace/ipgeo"
)
func HopPrinter(h Hop) {
if h.Address == nil {
fmt.Println("\t*")
} else {
txt := "\t"
if h.Hostname == "" {
txt += fmt.Sprint(h.Address, " ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
} else {
txt += fmt.Sprint(h.Hostname, " (", h.Address, ") ", fmt.Sprintf("%.2f", h.RTT.Seconds()*1000), "ms")
}
if h.Geo != nil {
txt += " " + formatIpGeoData(h.Address.String(), h.Geo)
}
fmt.Println(txt)
}
}
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
var res = make([]string, 0, 10)
if data.Asnumber == "" {
res = append(res, "*")
} else {
res = append(res, "AS"+data.Asnumber)
}
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
// TODO: 移动IDC判断到Hop.fetchIPData函数减少API调用
if strings.HasPrefix(ip, "9.") {
res = append(res, "局域网", "腾讯云")
} else if strings.HasPrefix(ip, "11.") {
res = append(res, "局域网", "阿里云")
} else if data.Country == "" {
res = append(res, "局域网")
} else {
// 有些IP的归属信息为空这个时候将ISP的信息填入
if data.Owner == "" {
data.Owner = data.Isp
}
if data.District != "" {
data.City = data.City + ", " + data.District
}
if data.Prov == "" && data.City == "" {
// anyCast或是骨干网数据不应该有国家信息
data.Owner = data.Owner + ", " + data.Owner
} else {
// 非骨干网正常填入IP的国家信息数据
res = append(res, data.Country)
}
if data.Prov != "" {
res = append(res, data.Prov)
}
if data.City != "" {
res = append(res, data.City)
}
if data.Owner != "" {
res = append(res, data.Owner)
}
}
return strings.Join(res, ", ")
}

View File

@@ -25,13 +25,15 @@ type Config struct {
Quic bool
IPGeoSource ipgeo.Source
RDns bool
RoutePath bool
}
type Method string
const (
UDPTrace Method = "udp"
TCPTrace Method = "tcp"
ICMPTrace Method = "icmp"
UDPTrace Method = "udp"
TCPTrace Method = "tcp"
)
type Tracer interface {
@@ -52,10 +54,26 @@ func Traceroute(method Method, config Config) (*Result, error) {
}
switch method {
case ICMPTrace:
if config.DestIP.To4() != nil {
tracer = &ICMPTracer{Config: config}
} else {
tracer = &ICMPTracerv6{Config: config}
}
case UDPTrace:
tracer = &UDPTracer{Config: config}
if config.DestIP.To4() != nil {
tracer = &UDPTracer{Config: config}
} else {
return nil, errors.New("IPv6 UDP Traceroute is not supported")
}
case TCPTrace:
tracer = &TCPTracer{Config: config}
if config.DestIP.To4() != nil {
tracer = &TCPTracer{Config: config}
} else {
// tracer = &TCPTracerv6{Config: config}
return nil, errors.New("IPv6 TCP Traceroute is not supported")
}
default:
return &Result{}, ErrInvalidMethod
}

View File

@@ -1,6 +1,11 @@
package trace
import (
"log"
"net"
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/xgadget-lab/nexttrace/util"
@@ -8,10 +13,6 @@ import (
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/sync/semaphore"
"log"
"net"
"sync"
"time"
)
type UDPTracer struct {
@@ -99,8 +100,8 @@ func (t *UDPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) {
return
}
srcPort := util.GetUDPSrcPort(header)
t.inflightRequestLock.Lock()
defer t.inflightRequestLock.Unlock()
//t.inflightRequestLock.Lock()
//defer t.inflightRequestLock.Unlock()
ch, ok := t.inflightRequest[int(srcPort)]
if !ok {
return
@@ -128,7 +129,6 @@ func (t *UDPTracer) getUDPConn(try int) (net.IP, int, net.PacketConn) {
}
return t.getUDPConn(try + 1)
}
return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn
}
@@ -184,6 +184,7 @@ func (t *UDPTracer) send(ttl int) error {
return err
}
// 在对inflightRequest进行写操作的时候应该加锁保护以免多个goroutine协程试图同时写入造成panic
t.inflightRequestLock.Lock()
hopCh := make(chan Hop)
t.inflightRequest[srcPort] = hopCh

View File

@@ -36,12 +36,13 @@ func DomainLookUp(host string) net.IP {
var ipv6Flag = false
for _, ip := range ips {
ipSlice = append(ipSlice, ip)
// 仅返回ipv4的ip
if ip.To4() != nil {
ipSlice = append(ipSlice, ip)
} else {
ipv6Flag = true
}
// if ip.To4() != nil {
// ipSlice = append(ipSlice, ip)
// } else {
// ipv6Flag = true
// }
}
if ipv6Flag {