diff --git a/go.mod b/go.mod index 03b6273..659eb2a 100644 --- a/go.mod +++ b/go.mod @@ -10,10 +10,12 @@ require ( require ( github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/panjf2000/ants/v2 v2.5.0 // indirect + golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect ) require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rodaine/table v1.0.1 @@ -22,5 +24,5 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 45acf67..4e9638a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= @@ -11,6 +12,8 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/panjf2000/ants/v2 v2.5.0 h1:1rWGWSnxCsQBga+nQbA4/iY6VMeNoOIAM0ZWh9u3q2Q= +github.com/panjf2000/ants/v2 v2.5.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ= @@ -34,6 +37,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -48,3 +53,4 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ipgeo/ipgeo.go b/ipgeo/ipgeo.go index d07df90..f9762a8 100644 --- a/ipgeo/ipgeo.go +++ b/ipgeo/ipgeo.go @@ -11,3 +11,16 @@ type IPGeoData struct { } type Source = func(ip string) (*IPGeoData, error) + +func GetSource(s string) Source { + switch s { + case "LeoMoeAPI": + return LeoIP + case "IP.SB": + return IPSB + case "IPInsight": + return IPInSight + default: + return nil + } +} diff --git a/main.go b/main.go index 02ecc1e..2978f2a 100644 --- a/main.go +++ b/main.go @@ -3,14 +3,13 @@ package main import ( "flag" "fmt" + "github.com/xgadget-lab/nexttrace/ipgeo" + "github.com/xgadget-lab/nexttrace/printer" + "github.com/xgadget-lab/nexttrace/trace" + "github.com/xgadget-lab/nexttrace/util" + "log" "os" "time" - - "github.com/xgadget-lab/nexttrace/methods" - "github.com/xgadget-lab/nexttrace/methods/tcp" - "github.com/xgadget-lab/nexttrace/methods/udp" - "github.com/xgadget-lab/nexttrace/util" - "github.com/xgadget-lab/nexttrace/util/printer" ) var tcpSYNFlag = flag.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80 in TCP, 53 in UDP)") @@ -22,61 +21,6 @@ var dataOrigin = flag.String("d", "LeoMoeAPI", "Choose IP Geograph Data Provider var displayMode = flag.String("displayMode", "table", "Choose The Display Mode [table, classic]") var rdnsenable = flag.Bool("rdns", false, "Set whether rDNS will be display") -func main() { - printer.PrintCopyRight() - domain := flagApply() - ip := util.DomainLookUp(domain) - printer.PrintTraceRouteNav(ip, domain, *dataOrigin) - - if *tcpSYNFlag { - tcpTraceroute := tcp.New(ip, methods.TracerouteConfig{ - MaxHops: uint16(*maxHops), - NumMeasurements: uint16(*numMeasurements), - ParallelRequests: uint16(*parallelRequests), - Port: *port, - Timeout: time.Second / 2, - }) - res, err := tcpTraceroute.Start() - - if err != nil { - fmt.Println("请赋予 sudo (root) 权限运行本程序") - } else { - util.Printer(&util.PrinterConfig{ - IP: ip, - DisplayMode: *displayMode, - DataOrigin: *dataOrigin, - Rdnsenable: *rdnsenable, - Results: *res, - }) - } - - } else { - if *port == 80 { - *port = 53 - } - udpTraceroute := udp.New(ip, true, methods.TracerouteConfig{ - MaxHops: uint16(*maxHops), - NumMeasurements: uint16(*numMeasurements), - ParallelRequests: uint16(*parallelRequests), - Port: *port, - Timeout: 2 * time.Second, - }) - res, err := udpTraceroute.Start() - - if err != nil { - fmt.Println("请赋予 sudo (root) 权限运行本程序") - } else { - util.Printer(&util.PrinterConfig{ - IP: ip, - DisplayMode: *displayMode, - DataOrigin: *dataOrigin, - Rdnsenable: *rdnsenable, - Results: *res, - }) - } - } -} - func flagApply() string { flag.Parse() ipArg := flag.Args() @@ -86,3 +30,49 @@ func flagApply() string { } return ipArg[0] } + +func main() { + if os.Getuid() != 0 { + log.Fatalln("Traceroute requires root/sudo privileges.") + } + + domain := flagApply() + ip := util.DomainLookUp(domain) + printer.PrintTraceRouteNav(ip, domain, *dataOrigin) + + var m trace.Method = "" + if *tcpSYNFlag { + m = trace.TCPTrace + } else { + m = trace.UDPTrace + } + + if !*tcpSYNFlag && *port == 80 { + *port = 53 + } + + var conf = trace.Config{ + DestIP: ip, + DestPort: *port, + MaxHops: *maxHops, + NumMeasurements: *numMeasurements, + ParallelRequests: *parallelRequests, + RDns: *rdnsenable, + IPGeoSource: ipgeo.GetSource(*dataOrigin), + Timeout: 2 * time.Second, + + //Quic: false, + } + + res, err := trace.Traceroute(m, conf) + + if err != nil { + log.Fatalln(err) + } + + if *displayMode == "table" { + printer.TracerouteTablePrinter(res) + } else { + printer.TraceroutePrinter(res) + } +} diff --git a/methods/methods.go b/methods/methods.go index 7fb5aa1..c5e813b 100644 --- a/methods/methods.go +++ b/methods/methods.go @@ -1,8 +1,6 @@ package methods import ( - "encoding/binary" - "errors" "net" "time" ) @@ -24,37 +22,6 @@ type TracerouteConfig struct { Timeout time.Duration } -func GetIPHeaderLength(data []byte) (int, error) { - if len(data) < 1 { - return 0, errors.New("received invalid IP header") - } - return int((data[0] & 0x0F) * 4), nil -} - -func GetICMPResponsePayload(data []byte) ([]byte, error) { - length, err := GetIPHeaderLength(data) - if err != nil { - return nil, err - } - - if len(data) < length { - return nil, errors.New("length of packet too short") - } - - return data[length:], nil -} - -func GetUDPSrcPort(data []byte) uint16 { - srcPortBytes := data[:2] - srcPort := binary.BigEndian.Uint16(srcPortBytes) - return srcPort -} - -func GetTCPSeq(data []byte) uint32 { - seqBytes := data[4:8] - return binary.BigEndian.Uint32(seqBytes) -} - func ReduceFinalResult(preliminary map[uint16][]TracerouteHop, maxHops uint16, destIP net.IP) map[uint16][]TracerouteHop { // reduce the results to remove all hops after the first encounter to final destination finalResults := map[uint16][]TracerouteHop{} diff --git a/methods/tcp/tcp.go b/methods/tcp/tcp.go index 929ecca..469a328 100644 --- a/methods/tcp/tcp.go +++ b/methods/tcp/tcp.go @@ -133,11 +133,11 @@ func (tr *Traceroute) addToResult(ttl uint16, hop methods.TracerouteHop) { } func (tr *Traceroute) handleICMPMessage(msg listener_channel.ReceivedMessage, data []byte) { - header, err := methods.GetICMPResponsePayload(data) + header, err := util.GetICMPResponsePayload(data) if err != nil { return } - sequenceNumber := methods.GetTCPSeq(header) + sequenceNumber := util.GetTCPSeq(header) val, ok := tr.results.inflightRequests.LoadAndDelete(sequenceNumber) if !ok { return diff --git a/methods/udp/udp.go b/methods/udp/udp.go deleted file mode 100644 index fb029e9..0000000 --- a/methods/udp/udp.go +++ /dev/null @@ -1,311 +0,0 @@ -package udp - -import ( - "log" - "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/methods" - "github.com/xgadget-lab/nexttrace/methods/quic" - "github.com/xgadget-lab/nexttrace/parallel_limiter" - "github.com/xgadget-lab/nexttrace/signal" - "github.com/xgadget-lab/nexttrace/taskgroup" - "github.com/xgadget-lab/nexttrace/util" - "golang.org/x/net/context" - "golang.org/x/net/icmp" - "golang.org/x/net/ipv4" -) - -type inflightData struct { - icmpMsg chan<- net.Addr -} - -type opConfig struct { - quic bool - destIP net.IP - wg *taskgroup.TaskGroup - - icmpConn net.PacketConn - - ctx context.Context - cancel context.CancelFunc -} - -type results struct { - inflightRequests sync.Map - - results map[uint16][]methods.TracerouteHop - resultsMu sync.Mutex - err error - - concurrentRequests *parallel_limiter.ParallelLimiter - reachedFinalHop *signal.Signal -} - -type Traceroute struct { - trcrtConfig methods.TracerouteConfig - opConfig opConfig - results results -} - -func New(destIP net.IP, quic bool, config methods.TracerouteConfig) *Traceroute { - return &Traceroute{ - opConfig: opConfig{ - quic: quic, - destIP: destIP, - }, - trcrtConfig: config, - } -} - -func (tr *Traceroute) Start() (*map[uint16][]methods.TracerouteHop, error) { - tr.opConfig.ctx, tr.opConfig.cancel = context.WithCancel(context.Background()) - - tr.results = results{ - inflightRequests: sync.Map{}, - concurrentRequests: parallel_limiter.New(int(tr.trcrtConfig.ParallelRequests)), - results: map[uint16][]methods.TracerouteHop{}, - reachedFinalHop: signal.New(), - } - - var err error - tr.opConfig.icmpConn, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0") - if err != nil { - return nil, err - } - - return tr.start() -} - -func (tr *Traceroute) addToResult(ttl uint16, hop methods.TracerouteHop) { - tr.results.resultsMu.Lock() - defer tr.results.resultsMu.Unlock() - if tr.results.results[ttl] == nil { - tr.results.results[ttl] = []methods.TracerouteHop{} - } - - tr.results.results[ttl] = append(tr.results.results[ttl], hop) -} - -func (tr *Traceroute) getUDPConn(try int) (net.IP, int, net.PacketConn) { - srcIP, _ := util.LocalIPPort(tr.opConfig.destIP) - - var ipString string - - if srcIP == nil { - ipString = "" - } else { - ipString = srcIP.String() - } - - udpConn, err := net.ListenPacket("udp", ipString+":0") - if err != nil { - if try > 3 { - log.Fatal(err) - } - return tr.getUDPConn(try + 1) - } - - return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn -} - -func (tr *Traceroute) sendMessage(ttl uint16) { - srcIP, srcPort, udpConn := tr.getUDPConn(0) - - var payload []byte - if tr.opConfig.quic { - payload = quic.GenerateWithRandomIds() - } else { - ipHeader := &layers.IPv4{ - SrcIP: srcIP, - DstIP: tr.opConfig.destIP, - Protocol: layers.IPProtocolTCP, - TTL: uint8(ttl), - } - - udpHeader := &layers.UDP{ - SrcPort: layers.UDPPort(srcPort), - DstPort: layers.UDPPort(tr.trcrtConfig.Port), - } - _ = udpHeader.SetNetworkLayerForChecksum(ipHeader) - buf := gopacket.NewSerializeBuffer() - opts := gopacket.SerializeOptions{ - ComputeChecksums: true, - FixLengths: true, - } - if err := gopacket.SerializeLayers(buf, opts, udpHeader, gopacket.Payload("HAJSFJHKAJSHFKJHAJKFHKASHKFHHKAFKHFAHSJK")); err != nil { - tr.results.err = err - tr.opConfig.cancel() - return - } - - payload = buf.Bytes() - } - - err := ipv4.NewPacketConn(udpConn).SetTTL(int(ttl)) - if err != nil { - tr.results.err = err - tr.opConfig.cancel() - return - } - - icmpMsg := make(chan net.Addr, 1) - udpMsg := make(chan net.Addr, 1) - - start := time.Now() - if _, err := udpConn.WriteTo(payload, &net.UDPAddr{IP: tr.opConfig.destIP, Port: tr.trcrtConfig.Port}); err != nil { - tr.results.err = err - tr.opConfig.cancel() - return - } - - inflight := inflightData{ - icmpMsg: icmpMsg, - } - - tr.results.inflightRequests.Store(uint16(srcPort), inflight) - - go func() { - reply := make([]byte, 1500) - _, peer, err := udpConn.ReadFrom(reply) - if err != nil { - // probably because we closed the connection - return - } - udpMsg <- peer - }() - - select { - case peer := <-icmpMsg: - rtt := time.Since(start) - if peer.(*net.IPAddr).IP.Equal(tr.opConfig.destIP) { - tr.results.reachedFinalHop.Signal() - } - tr.addToResult(ttl, methods.TracerouteHop{ - Success: true, - Address: peer, - TTL: ttl, - RTT: &rtt, - }) - case peer := <-udpMsg: - rtt := time.Since(start) - ip := peer.(*net.UDPAddr).IP - if ip.Equal(tr.opConfig.destIP) { - tr.results.reachedFinalHop.Signal() - } - tr.addToResult(ttl, methods.TracerouteHop{ - Success: true, - Address: &net.IPAddr{IP: ip}, - TTL: ttl, - RTT: &rtt, - }) - case <-time.After(tr.trcrtConfig.Timeout): - tr.addToResult(ttl, methods.TracerouteHop{ - Success: false, - Address: nil, - TTL: ttl, - RTT: nil, - }) - } - - tr.results.inflightRequests.Delete(uint16(srcPort)) - udpConn.Close() - tr.results.concurrentRequests.Finished() - tr.opConfig.wg.Done() -} - -func (tr *Traceroute) handleICMPMessage(msg listener_channel.ReceivedMessage, data []byte) { - header, err := methods.GetICMPResponsePayload(data) - if err != nil { - return - } - srcPort := methods.GetUDPSrcPort(header) - val, ok := tr.results.inflightRequests.LoadAndDelete(srcPort) - if !ok { - return - } - request := val.(inflightData) - request.icmpMsg <- msg.Peer -} - -func (tr *Traceroute) icmpListener() { - lc := listener_channel.New(tr.opConfig.icmpConn) - - defer lc.Stop() - - go lc.Start() - - for { - select { - case <-tr.opConfig.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: - body := rm.Body.(*icmp.TimeExceeded).Data - tr.handleICMPMessage(msg, body) - case ipv4.ICMPTypeDestinationUnreachable: - body := rm.Body.(*icmp.DstUnreach).Data - tr.handleICMPMessage(msg, body) - default: - log.Println("received icmp message of unknown type", rm.Type) - } - } - } -} - -func (tr *Traceroute) sendLoop() { - rand.Seed(time.Now().UTC().UnixNano()) - - for ttl := uint16(1); ttl <= tr.trcrtConfig.MaxHops; ttl++ { - select { - case <-tr.results.reachedFinalHop.Chan(): - return - default: - } - for i := 0; i < int(tr.trcrtConfig.NumMeasurements); i++ { - select { - case <-tr.opConfig.ctx.Done(): - return - case <-tr.results.concurrentRequests.Start(): - tr.opConfig.wg.Add() - go tr.sendMessage(ttl) - } - } - } -} - -func (tr *Traceroute) start() (*map[uint16][]methods.TracerouteHop, error) { - go tr.icmpListener() - - wg := taskgroup.New() - tr.opConfig.wg = wg - - tr.sendLoop() - - wg.Wait() - - tr.opConfig.cancel() - tr.opConfig.icmpConn.Close() - - if tr.results.err != nil { - return nil, tr.results.err - } - - result := methods.ReduceFinalResult(tr.results.results, tr.trcrtConfig.MaxHops, tr.opConfig.destIP) - - return &result, tr.results.err -} diff --git a/util/printer/basic.go b/printer/basic.go similarity index 100% rename from util/printer/basic.go rename to printer/basic.go diff --git a/printer/printer.go b/printer/printer.go new file mode 100644 index 0000000..ce90358 --- /dev/null +++ b/printer/printer.go @@ -0,0 +1,79 @@ +package printer + +import ( + "fmt" + "github.com/xgadget-lab/nexttrace/trace" + "strings" + + "github.com/xgadget-lab/nexttrace/ipgeo" +) + +var dataOrigin string + +func TraceroutePrinter(res *trace.Result) { + for i, hop := range res.Hops { + fmt.Print(i + 1) + for _, h := range hop { + hopPrinter(h) + } + } +} + +func hopPrinter(h trace.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 { + if data.Owner == "" { + data.Owner = data.Isp + } + if data.District != "" { + data.City = data.City + ", " + data.District + } + 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, ", ") +} diff --git a/printer/tableprinter.go b/printer/tableprinter.go new file mode 100644 index 0000000..c91b282 --- /dev/null +++ b/printer/tableprinter.go @@ -0,0 +1,109 @@ +package printer + +import ( + "fmt" + "github.com/xgadget-lab/nexttrace/trace" + "strings" + + "github.com/fatih/color" + "github.com/rodaine/table" +) + +type rowData struct { + Hop string + IP string + Latency string + Asnumber string + Country string + Prov string + City string + District string + Owner string +} + +func TracerouteTablePrinter(res *trace.Result) { + // 初始化表格 + tbl := New() + for _, hop := range res.Hops { + for k, h := range hop { + data := tableDataGenerator(h) + if k > 0 { + data.Hop = "" + } + tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Prov, data.City, data.Owner) + } + } + // 打印表格 + tbl.Print() +} + +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.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) + return tbl +} + +func tableDataGenerator(h trace.Hop) *rowData { + if h.Address == nil { + return &rowData{ + Hop: fmt.Sprint(h.TTL), + IP: "*", + } + } else { + lantency := fmt.Sprintf("%.2fms", h.RTT.Seconds()*1000) + IP := h.Address.String() + + if strings.HasPrefix(IP, "9.") { + return &rowData{ + Hop: fmt.Sprint(h.TTL), + IP: IP, + Latency: lantency, + Country: "局域网", + Owner: "腾讯云", + } + } else if strings.HasPrefix(IP, "11.") { + return &rowData{ + Hop: fmt.Sprint(h.TTL), + IP: IP, + Latency: lantency, + Country: "局域网", + Owner: "阿里云", + } + } + + if h.Hostname != "" { + IP = fmt.Sprint(h.Hostname, " (", IP, ") ") + } + + r := &rowData{ + Hop: fmt.Sprint(h.TTL), + IP: IP, + Latency: lantency, + Asnumber: h.Geo.Asnumber, + Country: h.Geo.Country, + Prov: h.Geo.Prov, + City: h.Geo.City, + District: h.Geo.District, + Owner: h.Geo.Owner, + } + + if h.Geo == nil { + return r + } + + if h.Geo.Owner == "" { + h.Geo.Owner = h.Geo.Isp + } + r.Asnumber = h.Geo.Asnumber + r.Country = h.Geo.Country + r.Prov = h.Geo.Prov + r.City = h.Geo.City + r.District = h.Geo.District + r.Owner = h.Geo.Owner + return r + } +} diff --git a/trace/packet_listener.go b/trace/packet_listener.go new file mode 100644 index 0000000..6bc9184 --- /dev/null +++ b/trace/packet_listener.go @@ -0,0 +1,55 @@ +package trace + +import ( + "golang.org/x/net/context" + "net" + "time" +) + +type ReceivedMessage struct { + N *int + Peer net.Addr + Msg []byte + Err error +} + +type PacketListener struct { + ctx context.Context + Conn net.PacketConn + Messages chan ReceivedMessage +} + +func NewPacketListener(conn net.PacketConn, ctx context.Context) *PacketListener { + results := make(chan ReceivedMessage, 50) + + return &PacketListener{Conn: conn, ctx: ctx, Messages: results} +} + +func (l *PacketListener) Start() { + for { + select { + case <-l.ctx.Done(): + return + default: + } + + reply := make([]byte, 1500) + err := l.Conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + if err != nil { + l.Messages <- ReceivedMessage{Err: err} + continue + } + + n, peer, err := l.Conn.ReadFrom(reply) + if err != nil { + l.Messages <- ReceivedMessage{Err: err} + continue + } + l.Messages <- ReceivedMessage{ + N: &n, + Peer: peer, + Err: nil, + Msg: reply, + } + } +} diff --git a/methods/quic/quic.go b/trace/quic.go similarity index 51% rename from methods/quic/quic.go rename to trace/quic.go index 12f6569..3af8c50 100644 --- a/methods/quic/quic.go +++ b/trace/quic.go @@ -1,16 +1,18 @@ -package quic +package trace import ( "math/rand" + "time" ) -var Packet = []byte{202, 255, 255, 255, 255, 10, 253, 187, 139, 161, 69, 45, 65, 177, 68, 23, 4, 83, 226, 136, 32, 0, 68, 204, 168, 172, 81, 54, 106, 24, 100, 29, 160, 51, 70, 95, 107, 100, 4, 127, 168, 161, 43, 243, 194, 192, 4, 192, 99, 149, 103, 193, 233, 86, 254, 220, 167, 6, 45, 209, 193, 11, 77, 123, 131, 80, 17, 201, 248, 246, 246, 45, 153, 229, 169, 191, 76, 131, 162, 109, 188, 151, 22, 36, 1, 229, 201, 194, 26, 8, 63, 197, 207, 43, 190, 55, 224, 59, 247, 19, 142, 34, 186, 122, 108, 162, 110, 221, 15, 36, 224, 90, 73, 182, 177, 119, 26, 226, 16, 13, 18, 201, 96, 249, 192, 162, 13, 8, 132, 41, 63, 221, 43, 62, 143, 236, 219, 207, 10, 4, 115, 117, 214, 53, 64, 25, 180, 81, 144, 173, 155, 11, 32, 239, 253, 131, 115, 136, 227, 77, 132, 144, 48, 135, 137, 138, 214, 14, 67, 63, 187, 54, 197, 18, 191, 243, 128, 157, 74, 27, 225, 33, 12, 163, 0, 249, 126, 252, 242, 2, 33, 70, 255, 204, 200, 7, 4, 65, 154, 157, 83, 125, 197, 130, 53, 187, 254, 96, 54, 114, 93, 108, 198, 218, 198, 86, 33, 50, 74, 131, 154, 26, 206, 122, 148, 212, 177, 163, 99, 0, 138, 68, 235, 222, 55, 252, 232, 32, 103, 68, 119, 33, 188, 227, 71, 13, 123, 229, 99, 206, 245, 115, 21, 216, 31, 61, 30, 174, 97, 36, 96, 28, 66, 173, 89, 139, 37, 102, 81, 232, 158, 212, 8, 33, 123, 216, 191, 133, 200, 13, 197, 94, 223, 246, 29, 246, 19, 44, 135, 113, 59, 231, 36, 186, 53, 228, 33, 206, 85, 59, 105, 6, 225, 227, 111, 146, 211, 195, 73, 108, 200, 70, 73, 220, 163, 120, 47, 167, 139, 24, 149, 53, 11, 224, 22, 241, 52, 214, 38, 17, 137, 96, 134, 144, 16, 44, 116, 151, 15, 57, 87, 167, 137, 194, 63, 101, 195, 31, 21, 51, 7, 95, 101, 114, 195, 75, 211, 137, 149, 2, 80, 183, 82, 120, 185, 39, 192, 82, 102, 141, 153, 156, 118, 137, 99, 59, 4, 93, 74, 96, 164, 80, 142, 16, 197, 111, 178, 230, 250, 189, 26, 184, 19, 236, 249, 170, 68, 11, 41, 37, 243, 176, 208, 209, 208, 108, 74, 205, 55, 165, 30, 182, 170, 62, 184, 89, 109, 34, 135, 174, 237, 207, 52, 18, 32, 229, 131, 237, 204, 33, 77, 67, 242, 248, 72, 137, 32, 173, 226, 234, 146, 203, 35, 208, 156, 107, 190, 255, 29, 163, 167, 25, 187, 27, 26, 31, 227, 202, 135, 50, 248, 122, 68, 80, 218, 58, 142, 193, 71, 154, 27, 255, 109, 253, 216, 250, 219, 112, 89, 56, 23, 47, 98, 104, 163, 17, 152, 102, 16, 139, 157, 172, 39, 191, 129, 132, 29, 189, 68, 216, 138, 3, 163, 101, 165, 200, 61, 56, 197, 142, 226, 156, 172, 62, 251, 193, 100, 94, 96, 166, 96, 19, 116, 5, 237, 177, 39, 188, 32, 177, 9, 171, 16, 224, 209, 53, 217, 144, 245, 215, 217, 196, 55, 232, 16, 121, 217, 58, 44, 34, 213, 5, 184, 171, 81, 167, 10, 34, 5, 116, 239, 179, 206, 83, 181, 235, 35, 121, 196, 234, 191, 119, 189, 238, 103, 235, 147, 155, 47, 151, 29, 56, 35, 72, 251, 107, 121, 21, 188, 184, 183, 72, 94, 191, 230, 100, 214, 161, 143, 235, 10, 236, 25, 172, 110, 95, 99, 99, 55, 25, 217, 131, 113, 57, 124, 195, 165, 204, 242, 109, 156, 106, 194, 233, 42, 175, 241, 213, 164, 207, 131, 207, 74, 52, 207, 155, 210, 227, 136, 41, 251, 47, 81, 192, 96, 26, 133, 85, 24, 57, 75, 188, 214, 146, 17, 209, 229, 217, 151, 71, 166, 174, 232, 162, 69, 147, 116, 247, 172, 154, 110, 7, 170, 78, 185, 18, 43, 70, 23, 215, 105, 118, 125, 64, 222, 16, 149, 205, 159, 138, 194, 46, 99, 228, 115, 205, 30, 92, 223, 29, 72, 86, 57, 14, 209, 176, 244, 200, 26, 208, 198, 153, 82, 154, 233, 32, 55, 219, 161, 243, 150, 233, 70, 142, 96, 52, 254, 200, 133, 56, 246, 80, 153, 59, 74, 245, 93, 30, 241, 70, 192, 93, 32, 253, 254, 181, 9, 179, 152, 218, 173, 216, 175, 3, 69, 249, 82, 145, 41, 96, 162, 241, 83, 245, 162, 71, 128, 58, 116, 200, 236, 93, 207, 174, 173, 68, 184, 61, 172, 100, 39, 118, 250, 136, 66, 154, 165, 178, 153, 156, 116, 230, 10, 33, 237, 91, 132, 57, 11, 111, 141, 162, 242, 159, 124, 255, 142, 101, 205, 127, 214, 97, 17, 146, 130, 151, 37, 222, 140, 227, 152, 92, 217, 210, 64, 198, 97, 72, 11, 2, 182, 114, 101, 68, 246, 183, 55, 46, 227, 62, 163, 65, 123, 208, 223, 132, 66, 202, 82, 126, 31, 166, 92, 126, 105, 239, 255, 25, 34, 249, 205, 67, 217, 139, 129, 147, 178, 81, 251, 226, 194, 8, 69, 208, 67, 243, 210, 181, 121, 59, 22, 186, 120, 31, 27, 186, 54, 81, 145, 89, 53, 152, 72, 107, 125, 202, 124, 112, 134, 124, 226, 95, 3, 148, 225, 191, 107, 209, 139, 58, 200, 72, 213, 100, 164, 187, 164, 86, 61, 65, 45, 167, 253, 128, 253, 133, 52, 21, 31, 67, 178, 173, 68, 186, 82, 135, 50, 123, 122, 56, 178, 37, 233, 82, 83, 61, 229, 47, 195, 2, 169, 129, 154, 226, 71, 163, 211, 232, 156, 97, 67, 125, 104, 61, 0, 81, 253, 234, 225, 1, 164, 113, 33, 229, 24, 101, 238, 128, 227, 219, 40, 218, 221, 78, 213, 172, 12, 69, 179, 142, 97, 156, 138, 54, 50, 145, 14, 191, 120, 37, 128, 171, 75, 201, 79, 78, 144, 21, 163, 233, 54, 107, 234, 134, 11, 204, 233, 156, 77, 200, 9, 26, 32, 156, 132, 116, 81, 161, 218, 131, 110, 30, 175, 118, 48, 150, 146, 234, 195, 109, 228, 74, 43, 247, 114, 31, 139, 235, 214, 147, 206, 145, 170, 54, 83, 160, 48, 59, 65, 250, 192, 13, 87, 240, 234, 215, 209, 158, 10, 91, 208, 151, 177, 71, 96, 209, 184, 125, 82, 156, 34, 74, 15, 97, 73, 249, 185, 79, 34, 42, 157, 175, 52, 234, 131, 51, 144, 203, 6, 81, 245, 215, 107, 71, 68, 113, 82, 210, 116, 18, 88, 92, 141, 68, 227, 185, 55, 114, 54, 243, 152, 47, 117, 250, 87, 180, 30, 57, 187, 98, 90, 127, 243, 94, 122, 48, 23, 200, 40, 89, 89, 53, 83, 221, 56, 231, 117, 200, 201, 101, 159, 147, 25, 194, 236, 249, 5, 0, 70, 209, 122, 162, 103, 178, 217, 242, 36, 226, 16, 215, 144, 98, 198, 173, 134, 89, 115, 171, 81, 0, 112, 6, 152, 154, 119, 28, 36, 209, 174, 21, 93, 62, 33, 39, 57, 67, 71, 239, 11, 158, 53, 79, 103, 157, 100, 234, 28, 222, 212, 196, 216, 162, 49, 104, 49, 18, 175, 140, 255, 152, 175, 25, 233, 200, 60, 203, 225, 232, 121, 46, 220, 243, 13, 125, 255, 158, 2, 153, 203, 203, 223, 159, 65, 2, 219, 105, 235, 109, 126, 124, 53, 122} - +//var Packet = []byte{202, 255, 255, 255, 255, 10, 253, 187, 139, 161, 69, 45, 65, 177, 68, 23, 4, 83, 226, 136, 32, 0, 68, 204, 168, 172, 81, 54, 106, 24, 100, 29, 160, 51, 70, 95, 107, 100, 4, 127, 168, 161, 43, 243, 194, 192, 4, 192, 99, 149, 103, 193, 233, 86, 254, 220, 167, 6, 45, 209, 193, 11, 77, 123, 131, 80, 17, 201, 248, 246, 246, 45, 153, 229, 169, 191, 76, 131, 162, 109, 188, 151, 22, 36, 1, 229, 201, 194, 26, 8, 63, 197, 207, 43, 190, 55, 224, 59, 247, 19, 142, 34, 186, 122, 108, 162, 110, 221, 15, 36, 224, 90, 73, 182, 177, 119, 26, 226, 16, 13, 18, 201, 96, 249, 192, 162, 13, 8, 132, 41, 63, 221, 43, 62, 143, 236, 219, 207, 10, 4, 115, 117, 214, 53, 64, 25, 180, 81, 144, 173, 155, 11, 32, 239, 253, 131, 115, 136, 227, 77, 132, 144, 48, 135, 137, 138, 214, 14, 67, 63, 187, 54, 197, 18, 191, 243, 128, 157, 74, 27, 225, 33, 12, 163, 0, 249, 126, 252, 242, 2, 33, 70, 255, 204, 200, 7, 4, 65, 154, 157, 83, 125, 197, 130, 53, 187, 254, 96, 54, 114, 93, 108, 198, 218, 198, 86, 33, 50, 74, 131, 154, 26, 206, 122, 148, 212, 177, 163, 99, 0, 138, 68, 235, 222, 55, 252, 232, 32, 103, 68, 119, 33, 188, 227, 71, 13, 123, 229, 99, 206, 245, 115, 21, 216, 31, 61, 30, 174, 97, 36, 96, 28, 66, 173, 89, 139, 37, 102, 81, 232, 158, 212, 8, 33, 123, 216, 191, 133, 200, 13, 197, 94, 223, 246, 29, 246, 19, 44, 135, 113, 59, 231, 36, 186, 53, 228, 33, 206, 85, 59, 105, 6, 225, 227, 111, 146, 211, 195, 73, 108, 200, 70, 73, 220, 163, 120, 47, 167, 139, 24, 149, 53, 11, 224, 22, 241, 52, 214, 38, 17, 137, 96, 134, 144, 16, 44, 116, 151, 15, 57, 87, 167, 137, 194, 63, 101, 195, 31, 21, 51, 7, 95, 101, 114, 195, 75, 211, 137, 149, 2, 80, 183, 82, 120, 185, 39, 192, 82, 102, 141, 153, 156, 118, 137, 99, 59, 4, 93, 74, 96, 164, 80, 142, 16, 197, 111, 178, 230, 250, 189, 26, 184, 19, 236, 249, 170, 68, 11, 41, 37, 243, 176, 208, 209, 208, 108, 74, 205, 55, 165, 30, 182, 170, 62, 184, 89, 109, 34, 135, 174, 237, 207, 52, 18, 32, 229, 131, 237, 204, 33, 77, 67, 242, 248, 72, 137, 32, 173, 226, 234, 146, 203, 35, 208, 156, 107, 190, 255, 29, 163, 167, 25, 187, 27, 26, 31, 227, 202, 135, 50, 248, 122, 68, 80, 218, 58, 142, 193, 71, 154, 27, 255, 109, 253, 216, 250, 219, 112, 89, 56, 23, 47, 98, 104, 163, 17, 152, 102, 16, 139, 157, 172, 39, 191, 129, 132, 29, 189, 68, 216, 138, 3, 163, 101, 165, 200, 61, 56, 197, 142, 226, 156, 172, 62, 251, 193, 100, 94, 96, 166, 96, 19, 116, 5, 237, 177, 39, 188, 32, 177, 9, 171, 16, 224, 209, 53, 217, 144, 245, 215, 217, 196, 55, 232, 16, 121, 217, 58, 44, 34, 213, 5, 184, 171, 81, 167, 10, 34, 5, 116, 239, 179, 206, 83, 181, 235, 35, 121, 196, 234, 191, 119, 189, 238, 103, 235, 147, 155, 47, 151, 29, 56, 35, 72, 251, 107, 121, 21, 188, 184, 183, 72, 94, 191, 230, 100, 214, 161, 143, 235, 10, 236, 25, 172, 110, 95, 99, 99, 55, 25, 217, 131, 113, 57, 124, 195, 165, 204, 242, 109, 156, 106, 194, 233, 42, 175, 241, 213, 164, 207, 131, 207, 74, 52, 207, 155, 210, 227, 136, 41, 251, 47, 81, 192, 96, 26, 133, 85, 24, 57, 75, 188, 214, 146, 17, 209, 229, 217, 151, 71, 166, 174, 232, 162, 69, 147, 116, 247, 172, 154, 110, 7, 170, 78, 185, 18, 43, 70, 23, 215, 105, 118, 125, 64, 222, 16, 149, 205, 159, 138, 194, 46, 99, 228, 115, 205, 30, 92, 223, 29, 72, 86, 57, 14, 209, 176, 244, 200, 26, 208, 198, 153, 82, 154, 233, 32, 55, 219, 161, 243, 150, 233, 70, 142, 96, 52, 254, 200, 133, 56, 246, 80, 153, 59, 74, 245, 93, 30, 241, 70, 192, 93, 32, 253, 254, 181, 9, 179, 152, 218, 173, 216, 175, 3, 69, 249, 82, 145, 41, 96, 162, 241, 83, 245, 162, 71, 128, 58, 116, 200, 236, 93, 207, 174, 173, 68, 184, 61, 172, 100, 39, 118, 250, 136, 66, 154, 165, 178, 153, 156, 116, 230, 10, 33, 237, 91, 132, 57, 11, 111, 141, 162, 242, 159, 124, 255, 142, 101, 205, 127, 214, 97, 17, 146, 130, 151, 37, 222, 140, 227, 152, 92, 217, 210, 64, 198, 97, 72, 11, 2, 182, 114, 101, 68, 246, 183, 55, 46, 227, 62, 163, 65, 123, 208, 223, 132, 66, 202, 82, 126, 31, 166, 92, 126, 105, 239, 255, 25, 34, 249, 205, 67, 217, 139, 129, 147, 178, 81, 251, 226, 194, 8, 69, 208, 67, 243, 210, 181, 121, 59, 22, 186, 120, 31, 27, 186, 54, 81, 145, 89, 53, 152, 72, 107, 125, 202, 124, 112, 134, 124, 226, 95, 3, 148, 225, 191, 107, 209, 139, 58, 200, 72, 213, 100, 164, 187, 164, 86, 61, 65, 45, 167, 253, 128, 253, 133, 52, 21, 31, 67, 178, 173, 68, 186, 82, 135, 50, 123, 122, 56, 178, 37, 233, 82, 83, 61, 229, 47, 195, 2, 169, 129, 154, 226, 71, 163, 211, 232, 156, 97, 67, 125, 104, 61, 0, 81, 253, 234, 225, 1, 164, 113, 33, 229, 24, 101, 238, 128, 227, 219, 40, 218, 221, 78, 213, 172, 12, 69, 179, 142, 97, 156, 138, 54, 50, 145, 14, 191, 120, 37, 128, 171, 75, 201, 79, 78, 144, 21, 163, 233, 54, 107, 234, 134, 11, 204, 233, 156, 77, 200, 9, 26, 32, 156, 132, 116, 81, 161, 218, 131, 110, 30, 175, 118, 48, 150, 146, 234, 195, 109, 228, 74, 43, 247, 114, 31, 139, 235, 214, 147, 206, 145, 170, 54, 83, 160, 48, 59, 65, 250, 192, 13, 87, 240, 234, 215, 209, 158, 10, 91, 208, 151, 177, 71, 96, 209, 184, 125, 82, 156, 34, 74, 15, 97, 73, 249, 185, 79, 34, 42, 157, 175, 52, 234, 131, 51, 144, 203, 6, 81, 245, 215, 107, 71, 68, 113, 82, 210, 116, 18, 88, 92, 141, 68, 227, 185, 55, 114, 54, 243, 152, 47, 117, 250, 87, 180, 30, 57, 187, 98, 90, 127, 243, 94, 122, 48, 23, 200, 40, 89, 89, 53, 83, 221, 56, 231, 117, 200, 201, 101, 159, 147, 25, 194, 236, 249, 5, 0, 70, 209, 122, 162, 103, 178, 217, 242, 36, 226, 16, 215, 144, 98, 198, 173, 134, 89, 115, 171, 81, 0, 112, 6, 152, 154, 119, 28, 36, 209, 174, 21, 93, 62, 33, 39, 57, 67, 71, 239, 11, 158, 53, 79, 103, 157, 100, 234, 28, 222, 212, 196, 216, 162, 49, 104, 49, 18, 175, 140, 255, 152, 175, 25, 233, 200, 60, 203, 225, 232, 121, 46, 220, 243, 13, 125, 255, 158, 2, 153, 203, 203, 223, 159, 65, 2, 219, 105, 235, 109, 126, 124, 53, 122} var beginHeader = []byte{202, 255, 255, 255, 255} //var endPacket = []byte{0, 68, 204, 168, 172, 81, 54, 106, 24, 100, 29, 160, 51, 70, 95, 107, 100, 4, 127, 168, 161, 43, 243, 194, 192, 4, 192, 99, 149, 103, 193, 233, 86, 254, 220, 167, 6, 45, 209, 193, 11, 77, 123, 131, 80, 17, 201, 248, 246, 246, 45, 153, 229, 169, 191, 76, 131, 162, 109, 188, 151, 22, 36, 1, 229, 201, 194, 26, 8, 63, 197, 207, 43, 190, 55, 224, 59, 247, 19, 142, 34, 186, 122, 108, 162, 110, 221, 15, 36, 224, 90, 73, 182, 177, 119, 26, 226, 16, 13, 18, 201, 96, 249, 192, 162, 13, 8, 132, 41, 63, 221, 43, 62, 143, 236, 219, 207, 10, 4, 115, 117, 214, 53, 64, 25, 180, 81, 144, 173, 155, 11, 32, 239, 253, 131, 115, 136, 227, 77, 132, 144, 48, 135, 137, 138, 214, 14, 67, 63, 187, 54, 197, 18, 191, 243, 128, 157, 74, 27, 225, 33, 12, 163, 0, 249, 126, 252, 242, 2, 33, 70, 255, 204, 200, 7, 4, 65, 154, 157, 83, 125, 197, 130, 53, 187, 254, 96, 54, 114, 93, 108, 198, 218, 198, 86, 33, 50, 74, 131, 154, 26, 206, 122, 148, 212, 177, 163, 99, 0, 138, 68, 235, 222, 55, 252, 232, 32, 103, 68, 119, 33, 188, 227, 71, 13, 123, 229, 99, 206, 245, 115, 21, 216, 31, 61, 30, 174, 97, 36, 96, 28, 66, 173, 89, 139, 37, 102, 81, 232, 158, 212, 8, 33, 123, 216, 191, 133, 200, 13, 197, 94, 223, 246, 29, 246, 19, 44, 135, 113, 59, 231, 36, 186, 53, 228, 33, 206, 85, 59, 105, 6, 225, 227, 111, 146, 211, 195, 73, 108, 200, 70, 73, 220, 163, 120, 47, 167, 139, 24, 149, 53, 11, 224, 22, 241, 52, 214, 38, 17, 137, 96, 134, 144, 16, 44, 116, 151, 15, 57, 87, 167, 137, 194, 63, 101, 195, 31, 21, 51, 7, 95, 101, 114, 195, 75, 211, 137, 149, 2, 80, 183, 82, 120, 185, 39, 192, 82, 102, 141, 153, 156, 118, 137, 99, 59, 4, 93, 74, 96, 164, 80, 142, 16, 197, 111, 178, 230, 250, 189, 26, 184, 19, 236, 249, 170, 68, 11, 41, 37, 243, 176, 208, 209, 208, 108, 74, 205, 55, 165, 30, 182, 170, 62, 184, 89, 109, 34, 135, 174, 237, 207, 52, 18, 32, 229, 131, 237, 204, 33, 77, 67, 242, 248, 72, 137, 32, 173, 226, 234, 146, 203, 35, 208, 156, 107, 190, 255, 29, 163, 167, 25, 187, 27, 26, 31, 227, 202, 135, 50, 248, 122, 68, 80, 218, 58, 142, 193, 71, 154, 27, 255, 109, 253, 216, 250, 219, 112, 89, 56, 23, 47, 98, 104, 163, 17, 152, 102, 16, 139, 157, 172, 39, 191, 129, 132, 29, 189, 68, 216, 138, 3, 163, 101, 165, 200, 61, 56, 197, 142, 226, 156, 172, 62, 251, 193, 100, 94, 96, 166, 96, 19, 116, 5, 237, 177, 39, 188, 32, 177, 9, 171, 16, 224, 209, 53, 217, 144, 245, 215, 217, 196, 55, 232, 16, 121, 217, 58, 44, 34, 213, 5, 184, 171, 81, 167, 10, 34, 5, 116, 239, 179, 206, 83, 181, 235, 35, 121, 196, 234, 191, 119, 189, 238, 103, 235, 147, 155, 47, 151, 29, 56, 35, 72, 251, 107, 121, 21, 188, 184, 183, 72, 94, 191, 230, 100, 214, 161, 143, 235, 10, 236, 25, 172, 110, 95, 99, 99, 55, 25, 217, 131, 113, 57, 124, 195, 165, 204, 242, 109, 156, 106, 194, 233, 42, 175, 241, 213, 164, 207, 131, 207, 74, 52, 207, 155, 210, 227, 136, 41, 251, 47, 81, 192, 96, 26, 133, 85, 24, 57, 75, 188, 214, 146, 17, 209, 229, 217, 151, 71, 166, 174, 232, 162, 69, 147, 116, 247, 172, 154, 110, 7, 170, 78, 185, 18, 43, 70, 23, 215, 105, 118, 125, 64, 222, 16, 149, 205, 159, 138, 194, 46, 99, 228, 115, 205, 30, 92, 223, 29, 72, 86, 57, 14, 209, 176, 244, 200, 26, 208, 198, 153, 82, 154, 233, 32, 55, 219, 161, 243, 150, 233, 70, 142, 96, 52, 254, 200, 133, 56, 246, 80, 153, 59, 74, 245, 93, 30, 241, 70, 192, 93, 32, 253, 254, 181, 9, 179, 152, 218, 173, 216, 175, 3, 69, 249, 82, 145, 41, 96, 162, 241, 83, 245, 162, 71, 128, 58, 116, 200, 236, 93, 207, 174, 173, 68, 184, 61, 172, 100, 39, 118, 250, 136, 66, 154, 165, 178, 153, 156, 116, 230, 10, 33, 237, 91, 132, 57, 11, 111, 141, 162, 242, 159, 124, 255, 142, 101, 205, 127, 214, 97, 17, 146, 130, 151, 37, 222, 140, 227, 152, 92, 217, 210, 64, 198, 97, 72, 11, 2, 182, 114, 101, 68, 246, 183, 55, 46, 227, 62, 163, 65, 123, 208, 223, 132, 66, 202, 82, 126, 31, 166, 92, 126, 105, 239, 255, 25, 34, 249, 205, 67, 217, 139, 129, 147, 178, 81, 251, 226, 194, 8, 69, 208, 67, 243, 210, 181, 121, 59, 22, 186, 120, 31, 27, 186, 54, 81, 145, 89, 53, 152, 72, 107, 125, 202, 124, 112, 134, 124, 226, 95, 3, 148, 225, 191, 107, 209, 139, 58, 200, 72, 213, 100, 164, 187, 164, 86, 61, 65, 45, 167, 253, 128, 253, 133, 52, 21, 31, 67, 178, 173, 68, 186, 82, 135, 50, 123, 122, 56, 178, 37, 233, 82, 83, 61, 229, 47, 195, 2, 169, 129, 154, 226, 71, 163, 211, 232, 156, 97, 67, 125, 104, 61, 0, 81, 253, 234, 225, 1, 164, 113, 33, 229, 24, 101, 238, 128, 227, 219, 40, 218, 221, 78, 213, 172, 12, 69, 179, 142, 97, 156, 138, 54, 50, 145, 14, 191, 120, 37, 128, 171, 75, 201, 79, 78, 144, 21, 163, 233, 54, 107, 234, 134, 11, 204, 233, 156, 77, 200, 9, 26, 32, 156, 132, 116, 81, 161, 218, 131, 110, 30, 175, 118, 48, 150, 146, 234, 195, 109, 228, 74, 43, 247, 114, 31, 139, 235, 214, 147, 206, 145, 170, 54, 83, 160, 48, 59, 65, 250, 192, 13, 87, 240, 234, 215, 209, 158, 10, 91, 208, 151, 177, 71, 96, 209, 184, 125, 82, 156, 34, 74, 15, 97, 73, 249, 185, 79, 34, 42, 157, 175, 52, 234, 131, 51, 144, 203, 6, 81, 245, 215, 107, 71, 68, 113, 82, 210, 116, 18, 88, 92, 141, 68, 227, 185, 55, 114, 54, 243, 152, 47, 117, 250, 87, 180, 30, 57, 187, 98, 90, 127, 243, 94, 122, 48, 23, 200, 40, 89, 89, 53, 83, 221, 56, 231, 117, 200, 201, 101, 159, 147, 25, 194, 236, 249, 5, 0, 70, 209, 122, 162, 103, 178, 217, 242, 36, 226, 16, 215, 144, 98, 198, 173, 134, 89, 115, 171, 81, 0, 112, 6, 152, 154, 119, 28, 36, 209, 174, 21, 93, 62, 33, 39, 57, 67, 71, 239, 11, 158, 53, 79, 103, 157, 100, 234, 28, 222, 212, 196, 216, 162, 49, 104, 49, 18, 175, 140, 255, 152, 175, 25, 233, 200, 60, 203, 225, 232, 121, 46, 220, 243, 13, 125, 255, 158, 2, 153, 203, 203, 223, 159, 65, 2, 219, 105, 235, 109, 126, 124, 53, 122} -func GenerateWithRandomIds() []byte { +var r = rand.New(rand.NewSource(time.Now().UnixNano())) + +func GenerateQuicPayloadWithRandomIds() []byte { var packet []byte packet = append(packet, beginHeader...) // append the length of destConnectionID @@ -18,7 +20,7 @@ func GenerateWithRandomIds() []byte { // generate random destConnectionId and append destConnectionId := make([]byte, 10) - rand.Read(destConnectionId) + r.Read(destConnectionId) packet = append(packet, destConnectionId...) // append the length of destConnectionID @@ -26,7 +28,7 @@ func GenerateWithRandomIds() []byte { // generate random srcConnectionId and append srcConnectionId := make([]byte, 4) - rand.Read(srcConnectionId) + r.Read(srcConnectionId) packet = append(packet, srcConnectionId...) for i := len(packet); i < 1200; i++ { diff --git a/trace/trace.go b/trace/trace.go new file mode 100644 index 0000000..5fca0e0 --- /dev/null +++ b/trace/trace.go @@ -0,0 +1,105 @@ +package trace + +import ( + "errors" + "github.com/xgadget-lab/nexttrace/ipgeo" + "net" + "sync" + "time" +) + +var ( + ErrInvalidMethod = errors.New("invalid method") + ErrTracerouteExecuted = errors.New("traceroute already executed") + ErrHopLimitTimeout = errors.New("hop timeout") +) + +type Config struct { + MaxHops int + NumMeasurements int + ParallelRequests int + Timeout time.Duration + DestIP net.IP + DestPort int + Quic bool + IPGeoSource ipgeo.Source + RDns bool +} + +type Method string + +const ( + UDPTrace Method = "udp" + TCPTrace Method = "tcp" +) + +type Tracer interface { + Execute() (*Result, error) +} + +func Traceroute(method Method, config Config) (*Result, error) { + var tracer Tracer + + if config.MaxHops == 0 { + config.MaxHops = 30 + } + if config.NumMeasurements == 0 { + config.NumMeasurements = 3 + } + if config.ParallelRequests == 0 { + config.ParallelRequests = config.NumMeasurements * 5 + } + + switch method { + case UDPTrace: + tracer = &UDPTracer{Config: config} + default: + return &Result{}, ErrInvalidMethod + } + return tracer.Execute() +} + +type Result struct { + Hops [][]Hop + lock sync.Mutex +} + +func (s *Result) add(hop Hop) { + s.lock.Lock() + defer s.lock.Unlock() + k := hop.TTL - 1 + for len(s.Hops) < hop.TTL { + s.Hops = append(s.Hops, make([]Hop, 0)) + } + s.Hops[k] = append(s.Hops[k], hop) + +} + +func (s *Result) reduce(final int) { + if final > 0 && final < len(s.Hops) { + s.Hops = s.Hops[:final] + } +} + +type Hop struct { + Success bool + Address net.Addr + Hostname string + TTL int + RTT time.Duration + Error error + Geo *ipgeo.IPGeoData +} + +func (h *Hop) fetchIPData(c Config) (err error) { + if c.RDns && h.Hostname == "" { + ptr, err := net.LookupAddr(h.Address.String()) + if err == nil && len(ptr) > 0 { + h.Hostname = ptr[0] + } + } + if c.IPGeoSource != nil && h.Geo == nil { + h.Geo, err = c.IPGeoSource(h.Address.String()) + } + return +} diff --git a/trace/udp.go b/trace/udp.go new file mode 100644 index 0000000..f7ff0ce --- /dev/null +++ b/trace/udp.go @@ -0,0 +1,256 @@ +package trace + +import ( + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/xgadget-lab/nexttrace/util" + "golang.org/x/net/context" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "golang.org/x/sync/semaphore" + "log" + "net" + "sync" + "time" +) + +type UDPTracer struct { + Config + wg sync.WaitGroup + res Result + ctx context.Context + inflightRequest map[int]chan Hop + inflightRequestLock sync.Mutex + + icmp net.PacketConn + + final int + finalLock sync.Mutex + + sem *semaphore.Weighted +} + +func (t *UDPTracer) Execute() (*Result, error) { + if len(t.res.Hops) > 0 { + return &t.res, ErrTracerouteExecuted + } + + var err error + t.icmp, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0") + 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() + + 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) + } + } + + t.wg.Wait() + t.res.reduce(t.final) + + return &t.res, nil +} + +func (t *UDPTracer) 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(1, msg.Msg[:*msg.N]) + if err != nil { + log.Println(err) + continue + } + switch rm.Type { + case ipv4.ICMPTypeTimeExceeded: + t.handleICMPMessage(msg, rm.Body.(*icmp.TimeExceeded).Data) + case ipv4.ICMPTypeDestinationUnreachable: + t.handleICMPMessage(msg, rm.Body.(*icmp.DstUnreach).Data) + default: + log.Println("received icmp message of unknown type", rm.Type) + } + } + } + +} + +func (t *UDPTracer) handleICMPMessage(msg ReceivedMessage, data []byte) { + header, err := util.GetICMPResponsePayload(data) + if err != nil { + return + } + srcPort := util.GetUDPSrcPort(header) + t.inflightRequestLock.Lock() + defer t.inflightRequestLock.Unlock() + ch, ok := t.inflightRequest[int(srcPort)] + if !ok { + return + } + ch <- Hop{ + Success: true, + Address: msg.Peer, + } +} + +func (t *UDPTracer) getUDPConn(try int) (net.IP, int, net.PacketConn) { + srcIP, _ := util.LocalIPPort(t.DestIP) + + var ipString string + if srcIP == nil { + ipString = "" + } else { + ipString = srcIP.String() + } + + udpConn, err := net.ListenPacket("udp", ipString+":0") + if err != nil { + if try > 3 { + log.Fatal(err) + } + return t.getUDPConn(try + 1) + } + + return srcIP, udpConn.LocalAddr().(*net.UDPAddr).Port, udpConn +} + +func (t *UDPTracer) 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 + } + + srcIP, srcPort, udpConn := t.getUDPConn(0) + + var payload []byte + if t.Quic { + payload = GenerateQuicPayloadWithRandomIds() + } else { + ipHeader := &layers.IPv4{ + SrcIP: srcIP, + DstIP: t.DestIP, + Protocol: layers.IPProtocolTCP, + TTL: uint8(ttl), + } + + udpHeader := &layers.UDP{ + SrcPort: layers.UDPPort(srcPort), + DstPort: layers.UDPPort(t.DestPort), + } + _ = udpHeader.SetNetworkLayerForChecksum(ipHeader) + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + ComputeChecksums: true, + FixLengths: true, + } + if err := gopacket.SerializeLayers(buf, opts, udpHeader, gopacket.Payload("HAJSFJHKAJSHFKJHAJKFHKASHKFHHKAFKHFAHSJK")); err != nil { + return err + } + + payload = buf.Bytes() + } + + err = ipv4.NewPacketConn(udpConn).SetTTL(ttl) + if err != nil { + return err + } + + start := time.Now() + if _, err := udpConn.WriteTo(payload, &net.UDPAddr{IP: t.DestIP, Port: t.DestPort}); err != nil { + return err + } + + t.inflightRequestLock.Lock() + hopCh := make(chan Hop) + t.inflightRequest[srcPort] = hopCh + t.inflightRequestLock.Unlock() + defer func() { + t.inflightRequestLock.Lock() + close(hopCh) + delete(t.inflightRequest, srcPort) + t.inflightRequestLock.Unlock() + }() + + go func() { + reply := make([]byte, 1500) + _, peer, err := udpConn.ReadFrom(reply) + if err != nil { + // probably because we closed the connection + return + } + hopCh <- Hop{ + Success: true, + Address: peer, + } + }() + + 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.UDPAddr); 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 +} diff --git a/util/methods.go b/util/methods.go deleted file mode 100644 index 92d682d..0000000 --- a/util/methods.go +++ /dev/null @@ -1,39 +0,0 @@ -package util - -import ( - "net" - - "github.com/xgadget-lab/nexttrace/methods" - "github.com/xgadget-lab/nexttrace/util/printer" -) - -type IPGeoData struct { - Asnumber string `json:"asnumber"` - Country string `json:"country"` - Prov string `json:"prov"` - City string `json:"city"` - District string `json:"district"` - Owner string `json:"owner"` - Isp string `json:"isp"` -} - -type PrinterConfig struct { - IP net.IP - DataOrigin string - DisplayMode string - Rdnsenable bool - Results map[uint16][]methods.TracerouteHop -} - -func Printer(config *PrinterConfig) { - switch config.DisplayMode { - case "table": - printer.TracerouteTablePrinter(config.IP, config.Results, config.DataOrigin, config.Rdnsenable) - case "classic": - printer.TraceroutePrinter(config.IP, config.Results, config.DataOrigin, config.Rdnsenable) - case "json": - //TracerouteJSONPrinter(config.Results, config.DataOrigin) - default: - printer.TraceroutePrinter(config.IP, config.Results, config.DataOrigin, config.Rdnsenable) - } -} diff --git a/util/printer/printer.go b/util/printer/printer.go deleted file mode 100644 index d018a12..0000000 --- a/util/printer/printer.go +++ /dev/null @@ -1,109 +0,0 @@ -package printer - -import ( - "fmt" - "net" - "strings" - - "github.com/xgadget-lab/nexttrace/ipgeo" - "github.com/xgadget-lab/nexttrace/methods" -) - -var dataOrigin string - -func TraceroutePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string, rdnsenable bool) { - for hi := uint16(1); hi < 30; hi++ { - fmt.Print(hi) - for _, v := range res[hi] { - hopPrinter(v, rdnsenable) - if v.Address != nil && ip.String() == v.Address.String() { - hi = 31 - } - } - } -} - -func hopPrinter(v2 methods.TracerouteHop, rdnsenable bool) { - if v2.Address == nil { - fmt.Println("\t*") - } else { - var iPGeoData *ipgeo.IPGeoData - var err error - - ipStr := v2.Address.String() - - // 判断 err 返回,并且在CLI终端提示错误 - switch dataOrigin { - case "LeoMoeAPI": - iPGeoData, err = ipgeo.LeoIP(ipStr) - case "IP.SB": - iPGeoData, err = ipgeo.IPSB(ipStr) - case "IPInfo": - iPGeoData, err = ipgeo.IPInfo(ipStr) - case "IPInsight": - iPGeoData, err = ipgeo.IPInSight(ipStr) - default: - iPGeoData, err = ipgeo.LeoIP(ipStr) - } - - geo := "" - if err != nil { - geo = fmt.Sprint("Error: ", err) - } else { - geo = formatIpGeoData(ipStr, iPGeoData) - } - - txt := "\t" - - if rdnsenable { - ptr, err := net.LookupAddr(ipStr) - if err != nil { - txt += fmt.Sprint(ipStr, " ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo) - } else { - txt += fmt.Sprint(ptr[0], " (", ipStr, ") ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo) - } - } else { - txt += fmt.Sprint(ipStr, " ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", 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: 判断阿里云和腾讯云内网,数据不足,有待进一步完善 - 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 { - if data.Owner == "" { - data.Owner = data.Isp - } - if data.District != "" { - data.City = data.City + ", " + data.District - } - 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, ", ") -} diff --git a/util/printer/tableprinter.go b/util/printer/tableprinter.go deleted file mode 100644 index a2035ba..0000000 --- a/util/printer/tableprinter.go +++ /dev/null @@ -1,137 +0,0 @@ -package printer - -import ( - "fmt" - "net" - "strings" - - "github.com/fatih/color" - "github.com/rodaine/table" - "github.com/xgadget-lab/nexttrace/ipgeo" - "github.com/xgadget-lab/nexttrace/methods" -) - -type rowData struct { - Hop int64 - IP string - Latency string - Asnumber string - Country string - Prov string - City string - District string - Owner string -} - -func TracerouteTablePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string, rdnsenable bool) { - // 初始化表格 - tbl := New() - for hi := uint16(1); hi < 30; hi++ { - for _, v := range res[hi] { - data := tableDataGenerator(v, rdnsenable) - tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Prov, data.City, data.Owner) - if v.Address != nil && ip.String() == v.Address.String() { - hi = 31 - } - } - } - // 打印表格 - tbl.Print() -} - -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.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) - return tbl -} - -func tableDataGenerator(v2 methods.TracerouteHop, rdnsenable bool) *rowData { - if v2.Address == nil { - return &rowData{ - Hop: int64(v2.TTL), - IP: "*", - } - } else { - // 初始化变量 - var iPGeoData *ipgeo.IPGeoData - var err error - var lantency, IP string - - ipStr := v2.Address.String() - - if strings.HasPrefix(ipStr, "9.") { - return &rowData{ - Hop: int64(v2.TTL), - IP: ipStr, - Latency: lantency, - Country: "局域网", - Owner: "腾讯云", - } - } else if strings.HasPrefix(ipStr, "11.") { - return &rowData{ - Hop: int64(v2.TTL), - IP: ipStr, - Latency: lantency, - Country: "局域网", - Owner: "阿里云", - } - } - - // TODO: 判断 err 返回,并且在CLI终端提示错误 - switch dataOrigin { - case "LeoMoeAPI": - iPGeoData, err = ipgeo.LeoIP(ipStr) - case "IP.SB": - iPGeoData, err = ipgeo.IPSB(ipStr) - case "IPInfo": - iPGeoData, err = ipgeo.IPInfo(ipStr) - case "IPInsight": - iPGeoData, err = ipgeo.IPInSight(ipStr) - default: - iPGeoData, err = ipgeo.LeoIP(ipStr) - } - - if rdnsenable { - ptr, err_LookupAddr := net.LookupAddr(ipStr) - if err_LookupAddr != nil { - IP = fmt.Sprint(ipStr) - } else { - IP = fmt.Sprint(ptr[0], " (", ipStr, ") ") - } - } else { - IP = fmt.Sprint(ipStr) - } - - lantency = fmt.Sprintf("%.2fms", v2.RTT.Seconds()*1000) - - if iPGeoData.Owner == "" { - iPGeoData.Owner = iPGeoData.Isp - } - - if err != nil { - fmt.Print("Error: ", err) - return &rowData{ - Hop: int64(v2.TTL), - IP: IP, - Latency: lantency, - } - } else { - - return &rowData{ - Hop: int64(v2.TTL), - IP: IP, - Latency: lantency, - Asnumber: iPGeoData.Asnumber, - Country: iPGeoData.Country, - Prov: iPGeoData.Prov, - City: iPGeoData.City, - District: iPGeoData.District, - Owner: iPGeoData.Owner, - } - } - } -} diff --git a/util/trace.go b/util/trace.go new file mode 100644 index 0000000..63b8153 --- /dev/null +++ b/util/trace.go @@ -0,0 +1,37 @@ +package util + +import ( + "encoding/binary" + "errors" +) + +func GetIPHeaderLength(data []byte) (int, error) { + if len(data) < 1 { + return 0, errors.New("received invalid IP header") + } + return int((data[0] & 0x0F) * 4), nil +} + +func GetICMPResponsePayload(data []byte) ([]byte, error) { + length, err := GetIPHeaderLength(data) + if err != nil { + return nil, err + } + + if len(data) < length { + return nil, errors.New("length of packet too short") + } + + return data[length:], nil +} + +func GetUDPSrcPort(data []byte) uint16 { + srcPortBytes := data[:2] + srcPort := binary.BigEndian.Uint16(srcPortBytes) + return srcPort +} + +func GetTCPSeq(data []byte) uint32 { + seqBytes := data[4:8] + return binary.BigEndian.Uint32(seqBytes) +}