From 7a644b403ebe1d8e59f7523200ef73b4b9cf075a Mon Sep 17 00:00:00 2001 From: spiritlhl <103393591+spiritLHLS@users.noreply.github.com> Date: Sat, 5 Apr 2025 03:48:50 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0IPV6=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E8=BF=BD=E8=B8=AA=E5=8C=85=E7=9A=84=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bk/trace_common.go | 463 +++++++++++++++++++++++++++++++++++++++++++++ bk/trace_ipv4.go | 438 +----------------------------------------- bk/trace_ipv6.go | 127 +++++++++++-- 3 files changed, 577 insertions(+), 451 deletions(-) create mode 100644 bk/trace_common.go diff --git a/bk/trace_common.go b/bk/trace_common.go new file mode 100644 index 0000000..e91917a --- /dev/null +++ b/bk/trace_common.go @@ -0,0 +1,463 @@ +package backtrace + +import ( + "context" + "errors" + "net" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// DefaultConfig is the default configuration for Tracer. +var DefaultConfig = Config{ + Delay: 50 * time.Millisecond, + Timeout: 500 * time.Millisecond, + MaxHops: 15, + Count: 1, + Networks: []string{"ip4:icmp", "ip4:ip"}, +} + +// DefaultTracer is a tracer with DefaultConfig. +var DefaultTracer = &Tracer{ + Config: DefaultConfig, +} + +// Config is a configuration for Tracer. +type Config struct { + Delay time.Duration + Timeout time.Duration + MaxHops int + Count int + Networks []string + Addr *net.IPAddr +} + +// Tracer is a traceroute tool based on raw IP packets. +// It can handle multiple sessions simultaneously. +type Tracer struct { + Config + + once sync.Once + conn *net.IPConn // Ipv4连接 + ipv6conn *ipv6.PacketConn // IPv6连接 + err error + + mu sync.RWMutex + sess map[string][]*Session + seq uint32 +} + +// Trace starts sending IP packets increasing TTL until MaxHops and calls h for each reply. +func (t *Tracer) Trace(ctx context.Context, ip net.IP, h func(reply *Reply)) error { + sess, err := t.NewSession(ip) + if err != nil { + return err + } + defer sess.Close() + + delay := time.NewTicker(t.Delay) + defer delay.Stop() + + max := t.MaxHops + for n := 0; n < t.Count; n++ { + for ttl := 1; ttl <= t.MaxHops && ttl <= max; ttl++ { + err = sess.Ping(ttl) + if err != nil { + return err + } + select { + case <-delay.C: + case r := <-sess.Receive(): + if max > r.Hops && ip.Equal(r.IP) { + max = r.Hops + } + h(r) + case <-ctx.Done(): + return ctx.Err() + } + } + } + if sess.isDone(max) { + return nil + } + deadline := time.After(t.Timeout) + for { + select { + case r := <-sess.Receive(): + if max > r.Hops && ip.Equal(r.IP) { + max = r.Hops + } + h(r) + if sess.isDone(max) { + return nil + } + case <-deadline: + return nil + case <-ctx.Done(): + return ctx.Err() + } + } +} + +// NewSession returns new tracer session. +func (t *Tracer) NewSession(ip net.IP) (*Session, error) { + t.once.Do(t.init) + if t.err != nil { + return nil, t.err + } + return newSession(t, shortIP(ip)), nil +} + +func (t *Tracer) init() { + // 初始化IPv4连接 + for _, network := range t.Networks { + if strings.HasPrefix(network, "ip4") { + t.conn, t.err = t.listen(network, t.Addr) + if t.err == nil { + go t.serve(t.conn) + break + } + } + } + // 初始化IPv6 ICMP连接 + c, err := net.ListenPacket("ip6:ipv6-icmp", "") + if err == nil { + p := ipv6.NewPacketConn(c) + if err := p.SetControlMessage(ipv6.FlagHopLimit|ipv6.FlagSrc|ipv6.FlagDst|ipv6.FlagInterface, true); err == nil { + t.ipv6conn = p + go t.serveIPv6(p) + } else { + c.Close() + } + } +} + +// Close closes listening socket. +// Tracer can not be used after Close is called. +func (t *Tracer) Close() { + t.mu.Lock() + defer t.mu.Unlock() + if t.conn != nil { + t.conn.Close() + } + if t.ipv6conn != nil { + t.ipv6conn.Close() + } +} +func (t *Tracer) serve(conn *net.IPConn) error { + defer conn.Close() + buf := make([]byte, 1500) + for { + n, from, err := conn.ReadFromIP(buf) + if err != nil { + return err + } + err = t.serveData(from.IP, buf[:n]) + if err != nil { + continue + } + } +} + +func (t *Tracer) serveData(from net.IP, b []byte) error { + if from.To4() == nil { + // TODO: implement ProtocolIPv6ICMP + return errUnsupportedProtocol + } + now := time.Now() + msg, err := icmp.ParseMessage(ProtocolICMP, b) + if err != nil { + return err + } + if msg.Type == ipv4.ICMPTypeEchoReply { + echo := msg.Body.(*icmp.Echo) + return t.serveReply(from, &packet{from, uint16(echo.ID), 1, now}) + } + b = getReplyData(msg) + if len(b) < ipv4.HeaderLen { + return errMessageTooShort + } + switch b[0] >> 4 { + case ipv4.Version: + ip, err := ipv4.ParseHeader(b) + if err != nil { + return err + } + return t.serveReply(ip.Dst, &packet{from, uint16(ip.ID), ip.TTL, now}) + case ipv6.Version: + ip, err := ipv6.ParseHeader(b) + if err != nil { + return err + } + return t.serveReply(ip.Dst, &packet{from, uint16(ip.FlowLabel), ip.HopLimit, now}) + default: + return errUnsupportedProtocol + } +} + +func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) { + if dst.To4() == nil { + // IPv6 + return t.sendRequestV6(dst, ttl) + } + // Ipv4 + id := uint16(atomic.AddUint32(&t.seq, 1)) + b := newPacketV4(id, dst, ttl) + req := &packet{dst, id, ttl, time.Now()} + _, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst}) + if err != nil { + return nil, err + } + return req, nil +} + +func (t *Tracer) addSession(s *Session) { + t.mu.Lock() + defer t.mu.Unlock() + if t.sess == nil { + t.sess = make(map[string][]*Session) + } + t.sess[string(s.ip)] = append(t.sess[string(s.ip)], s) +} + +func (t *Tracer) removeSession(s *Session) { + t.mu.Lock() + defer t.mu.Unlock() + a := t.sess[string(s.ip)] + for i, it := range a { + if it == s { + t.sess[string(s.ip)] = append(a[:i], a[i+1:]...) + return + } + } +} + +func (t *Tracer) serveReply(dst net.IP, res *packet) error { + t.mu.RLock() + defer t.mu.RUnlock() + a := t.sess[string(shortIP(dst))] + for _, s := range a { + s.handle(res) + } + return nil +} + +// Session is a tracer session. +type Session struct { + t *Tracer + ip net.IP + ch chan *Reply + + mu sync.RWMutex + probes []*packet +} + +// NewSession returns new session. +func NewSession(ip net.IP) (*Session, error) { + return DefaultTracer.NewSession(ip) +} + +func newSession(t *Tracer, ip net.IP) *Session { + s := &Session{ + t: t, + ip: ip, + ch: make(chan *Reply, 64), + } + t.addSession(s) + return s +} + +// Ping sends single ICMP packet with specified TTL. +func (s *Session) Ping(ttl int) error { + req, err := s.t.sendRequest(s.ip, ttl+1) + if err != nil { + return err + } + s.mu.Lock() + s.probes = append(s.probes, req) + s.mu.Unlock() + return nil +} + +// Receive returns channel to receive ICMP replies. +func (s *Session) Receive() <-chan *Reply { + return s.ch +} + +// isDone returns true if session does not have unresponsed requests with TTL <= ttl. +func (s *Session) isDone(ttl int) bool { + s.mu.RLock() + defer s.mu.RUnlock() + for _, r := range s.probes { + if r.TTL <= ttl { + return false + } + } + return true +} + +func (s *Session) handle(res *packet) { + now := res.Time + n := 0 + var req *packet + s.mu.Lock() + for _, r := range s.probes { + if now.Sub(r.Time) > s.t.Timeout { + continue + } + if r.ID == res.ID { + req = r + continue + } + s.probes[n] = r + n++ + } + s.probes = s.probes[:n] + s.mu.Unlock() + if req == nil { + return + } + hops := req.TTL - res.TTL + 1 + if hops < 1 { + hops = 1 + } + select { + case s.ch <- &Reply{ + IP: res.IP, + RTT: res.Time.Sub(req.Time), + Hops: hops, + }: + default: + } +} + +// Close closes tracer session. +func (s *Session) Close() { + s.t.removeSession(s) +} + +type packet struct { + IP net.IP + ID uint16 + TTL int + Time time.Time +} + +func shortIP(ip net.IP) net.IP { + if v := ip.To4(); v != nil { + return v + } + return ip +} + +func getReplyData(msg *icmp.Message) []byte { + switch b := msg.Body.(type) { + case *icmp.TimeExceeded: + return b.Data + case *icmp.DstUnreach: + return b.Data + case *icmp.ParamProb: + return b.Data + } + return nil +} + +var ( + errMessageTooShort = errors.New("message too short") + errUnsupportedProtocol = errors.New("unsupported protocol") + errNoReplyData = errors.New("no reply data") +) + +// IANA Assigned Internet Protocol Numbers +const ( + ProtocolICMP = 1 + ProtocolTCP = 6 + ProtocolUDP = 17 + ProtocolIPv6ICMP = 58 +) + +// Reply is a reply packet. +type Reply struct { + IP net.IP + RTT time.Duration + Hops int +} + +// Node is a detected network node. +type Node struct { + IP net.IP + RTT []time.Duration +} + +// Hop is a set of detected nodes. +type Hop struct { + Nodes []*Node + Distance int +} + +// Add adds node from r. +func (h *Hop) Add(r *Reply) *Node { + var node *Node + for _, it := range h.Nodes { + if it.IP.Equal(r.IP) { + node = it + break + } + } + if node == nil { + node = &Node{IP: r.IP} + h.Nodes = append(h.Nodes, node) + } + node.RTT = append(node.RTT, r.RTT) + return node +} + +// Trace is a simple traceroute tool using DefaultTracer. +func Trace(ip net.IP) ([]*Hop, error) { + hops := make([]*Hop, 0, DefaultTracer.MaxHops) + touch := func(dist int) *Hop { + for _, h := range hops { + if h.Distance == dist { + return h + } + } + h := &Hop{Distance: dist} + hops = append(hops, h) + return h + } + err := DefaultTracer.Trace(context.Background(), ip, func(r *Reply) { + touch(r.Hops).Add(r) + }) + if err != nil && err != context.DeadlineExceeded { + return nil, err + } + sort.Slice(hops, func(i, j int) bool { + return hops[i].Distance < hops[j].Distance + }) + last := len(hops) - 1 + for i := last; i >= 0; i-- { + h := hops[i] + if len(h.Nodes) == 1 && ip.Equal(h.Nodes[0].IP) { + continue + } + if i == last { + break + } + i++ + node := hops[i].Nodes[0] + i++ + for _, it := range hops[i:] { + node.RTT = append(node.RTT, it.Nodes[0].RTT...) + } + hops = hops[:i] + break + } + return hops, nil +} diff --git a/bk/trace_ipv4.go b/bk/trace_ipv4.go index 6fa094b..f69f665 100644 --- a/bk/trace_ipv4.go +++ b/bk/trace_ipv4.go @@ -1,358 +1,13 @@ package backtrace import ( - "context" - "errors" + "net" + "golang.org/x/net/icmp" "golang.org/x/net/ipv4" - "golang.org/x/net/ipv6" - "net" - "sort" - "sync" - "sync/atomic" - "time" ) -// DefaultConfig is the default configuration for Tracer. -var DefaultConfig = Config{ - Delay: 50 * time.Millisecond, - Timeout: 500 * time.Millisecond, - MaxHops: 15, - Count: 1, - Networks: []string{"ip4:icmp", "ip4:ip"}, -} - -// DefaultTracer is a tracer with DefaultConfig. -var DefaultTracer = &Tracer{ - Config: DefaultConfig, -} - -// Config is a configuration for Tracer. -type Config struct { - Delay time.Duration - Timeout time.Duration - MaxHops int - Count int - Networks []string - Addr *net.IPAddr -} - -// Tracer is a traceroute tool based on raw IP packets. -// It can handle multiple sessions simultaneously. -type Tracer struct { - Config - - once sync.Once - conn *net.IPConn - err error - - mu sync.RWMutex - sess map[string][]*Session - seq uint32 -} - -// Trace starts sending IP packets increasing TTL until MaxHops and calls h for each reply. -func (t *Tracer) Trace(ctx context.Context, ip net.IP, h func(reply *Reply)) error { - sess, err := t.NewSession(ip) - if err != nil { - return err - } - defer sess.Close() - - delay := time.NewTicker(t.Delay) - defer delay.Stop() - - max := t.MaxHops - for n := 0; n < t.Count; n++ { - for ttl := 1; ttl <= t.MaxHops && ttl <= max; ttl++ { - err = sess.Ping(ttl) - if err != nil { - return err - } - select { - case <-delay.C: - case r := <-sess.Receive(): - if max > r.Hops && ip.Equal(r.IP) { - max = r.Hops - } - h(r) - case <-ctx.Done(): - return ctx.Err() - } - } - } - if sess.isDone(max) { - return nil - } - deadline := time.After(t.Timeout) - for { - select { - case r := <-sess.Receive(): - if max > r.Hops && ip.Equal(r.IP) { - max = r.Hops - } - h(r) - if sess.isDone(max) { - return nil - } - case <-deadline: - return nil - case <-ctx.Done(): - return ctx.Err() - } - } -} - -// NewSession returns new tracer session. -func (t *Tracer) NewSession(ip net.IP) (*Session, error) { - t.once.Do(t.init) - if t.err != nil { - return nil, t.err - } - return newSession(t, shortIP(ip)), nil -} - -func (t *Tracer) init() { - for _, network := range t.Networks { - t.conn, t.err = t.listen(network, t.Addr) - if t.err != nil { - continue - } - go t.serve(t.conn) - return - } -} - -// Close closes listening socket. -// Tracer can not be used after Close is called. -func (t *Tracer) Close() { - t.mu.Lock() - defer t.mu.Unlock() - if t.conn != nil { - t.conn.Close() - } -} - -func (t *Tracer) serve(conn *net.IPConn) error { - defer conn.Close() - buf := make([]byte, 1500) - for { - n, from, err := conn.ReadFromIP(buf) - if err != nil { - return err - } - err = t.serveData(from.IP, buf[:n]) - if err != nil { - continue - } - } -} - -func (t *Tracer) serveData(from net.IP, b []byte) error { - if from.To4() == nil { - // TODO: implement ProtocolIPv6ICMP - return errUnsupportedProtocol - } - now := time.Now() - msg, err := icmp.ParseMessage(ProtocolICMP, b) - if err != nil { - return err - } - if msg.Type == ipv4.ICMPTypeEchoReply { - echo := msg.Body.(*icmp.Echo) - return t.serveReply(from, &packet{from, uint16(echo.ID), 1, now}) - } - b = getReplyData(msg) - if len(b) < ipv4.HeaderLen { - return errMessageTooShort - } - switch b[0] >> 4 { - case ipv4.Version: - ip, err := ipv4.ParseHeader(b) - if err != nil { - return err - } - return t.serveReply(ip.Dst, &packet{from, uint16(ip.ID), ip.TTL, now}) - case ipv6.Version: - ip, err := ipv6.ParseHeader(b) - if err != nil { - return err - } - return t.serveReply(ip.Dst, &packet{from, uint16(ip.FlowLabel), ip.HopLimit, now}) - default: - return errUnsupportedProtocol - } -} - -func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) { - id := uint16(atomic.AddUint32(&t.seq, 1)) - b := newPacket(id, dst, ttl) - req := &packet{dst, id, ttl, time.Now()} - _, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst}) - if err != nil { - return nil, err - } - return req, nil -} - -func (t *Tracer) addSession(s *Session) { - t.mu.Lock() - defer t.mu.Unlock() - if t.sess == nil { - t.sess = make(map[string][]*Session) - } - t.sess[string(s.ip)] = append(t.sess[string(s.ip)], s) -} - -func (t *Tracer) removeSession(s *Session) { - t.mu.Lock() - defer t.mu.Unlock() - a := t.sess[string(s.ip)] - for i, it := range a { - if it == s { - t.sess[string(s.ip)] = append(a[:i], a[i+1:]...) - return - } - } -} - -func (t *Tracer) serveReply(dst net.IP, res *packet) error { - t.mu.RLock() - defer t.mu.RUnlock() - a := t.sess[string(shortIP(dst))] - for _, s := range a { - s.handle(res) - } - return nil -} - -// Session is a tracer session. -type Session struct { - t *Tracer - ip net.IP - ch chan *Reply - - mu sync.RWMutex - probes []*packet -} - -// NewSession returns new session. -func NewSession(ip net.IP) (*Session, error) { - return DefaultTracer.NewSession(ip) -} - -func newSession(t *Tracer, ip net.IP) *Session { - s := &Session{ - t: t, - ip: ip, - ch: make(chan *Reply, 64), - } - t.addSession(s) - return s -} - -// Ping sends single ICMP packet with specified TTL. -func (s *Session) Ping(ttl int) error { - req, err := s.t.sendRequest(s.ip, ttl+1) - if err != nil { - return err - } - s.mu.Lock() - s.probes = append(s.probes, req) - s.mu.Unlock() - return nil -} - -// Receive returns channel to receive ICMP replies. -func (s *Session) Receive() <-chan *Reply { - return s.ch -} - -// isDone returns true if session does not have unresponsed requests with TTL <= ttl. -func (s *Session) isDone(ttl int) bool { - s.mu.RLock() - defer s.mu.RUnlock() - for _, r := range s.probes { - if r.TTL <= ttl { - return false - } - } - return true -} - -func (s *Session) handle(res *packet) { - now := res.Time - n := 0 - var req *packet - s.mu.Lock() - for _, r := range s.probes { - if now.Sub(r.Time) > s.t.Timeout { - continue - } - if r.ID == res.ID { - req = r - continue - } - s.probes[n] = r - n++ - } - s.probes = s.probes[:n] - s.mu.Unlock() - if req == nil { - return - } - hops := req.TTL - res.TTL + 1 - if hops < 1 { - hops = 1 - } - select { - case s.ch <- &Reply{ - IP: res.IP, - RTT: res.Time.Sub(req.Time), - Hops: hops, - }: - default: - } -} - -// Close closes tracer session. -func (s *Session) Close() { - s.t.removeSession(s) -} - -type packet struct { - IP net.IP - ID uint16 - TTL int - Time time.Time -} - -func shortIP(ip net.IP) net.IP { - if v := ip.To4(); v != nil { - return v - } - return ip -} - -func getReplyData(msg *icmp.Message) []byte { - switch b := msg.Body.(type) { - case *icmp.TimeExceeded: - return b.Data - case *icmp.DstUnreach: - return b.Data - case *icmp.ParamProb: - return b.Data - } - return nil -} - -var ( - errMessageTooShort = errors.New("message too short") - errUnsupportedProtocol = errors.New("unsupported protocol") - errNoReplyData = errors.New("no reply data") -) - -func newPacket(id uint16, dst net.IP, ttl int) []byte { +func newPacketV4(id uint16, dst net.IP, ttl int) []byte { // TODO: reuse buffers... msg := icmp.Message{ Type: ipv4.ICMPTypeEcho, @@ -378,90 +33,3 @@ func newPacket(id uint16, dst net.IP, ttl int) []byte { } return append(buf, p...) } - -// IANA Assigned Internet Protocol Numbers -const ( - ProtocolICMP = 1 - ProtocolTCP = 6 - ProtocolUDP = 17 - ProtocolIPv6ICMP = 58 -) - -// Reply is a reply packet. -type Reply struct { - IP net.IP - RTT time.Duration - Hops int -} - -// Node is a detected network node. -type Node struct { - IP net.IP - RTT []time.Duration -} - -// Hop is a set of detected nodes. -type Hop struct { - Nodes []*Node - Distance int -} - -// Add adds node from r. -func (h *Hop) Add(r *Reply) *Node { - var node *Node - for _, it := range h.Nodes { - if it.IP.Equal(r.IP) { - node = it - break - } - } - if node == nil { - node = &Node{IP: r.IP} - h.Nodes = append(h.Nodes, node) - } - node.RTT = append(node.RTT, r.RTT) - return node -} - -// Trace is a simple traceroute tool using DefaultTracer. -func Trace(ip net.IP) ([]*Hop, error) { - hops := make([]*Hop, 0, DefaultTracer.MaxHops) - touch := func(dist int) *Hop { - for _, h := range hops { - if h.Distance == dist { - return h - } - } - h := &Hop{Distance: dist} - hops = append(hops, h) - return h - } - err := DefaultTracer.Trace(context.Background(), ip, func(r *Reply) { - touch(r.Hops).Add(r) - }) - if err != nil && err != context.DeadlineExceeded { - return nil, err - } - sort.Slice(hops, func(i, j int) bool { - return hops[i].Distance < hops[j].Distance - }) - last := len(hops) - 1 - for i := last; i >= 0; i-- { - h := hops[i] - if len(h.Nodes) == 1 && ip.Equal(h.Nodes[0].IP) { - continue - } - if i == last { - break - } - i++ - node := hops[i].Nodes[0] - i++ - for _, it := range hops[i:] { - node.RTT = append(node.RTT, it.Nodes[0].RTT...) - } - hops = hops[:i] - break - } - return hops, nil -} diff --git a/bk/trace_ipv6.go b/bk/trace_ipv6.go index 0253cf9..e26065e 100644 --- a/bk/trace_ipv6.go +++ b/bk/trace_ipv6.go @@ -1,30 +1,125 @@ -package bk +package backtrace import ( "net" + "sync/atomic" + "time" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" ) -func newPacketV6(id uint16, dst net.IP, ttl int) []byte { +func newPacketV6(id uint16, dst net.IP, ttl int) ([]byte, error) { + // 创建ICMPv6 Echo请求消息 msg := icmp.Message{ Type: ipv6.ICMPTypeEchoRequest, + Code: 0, Body: &icmp.Echo{ - ID: int(id), - Seq: int(id), + ID: int(id), + Seq: int(id), + Data: []byte("TRACEROUTE"), }, } - p, _ := msg.Marshal(nil) - ip := &ipv6.Header{ - Version: ipv6.Version, - NextHeader: ProtocolIPv6ICMP, - HopLimit: ttl, - Dst: dst, - } - buf, err := ip.Marshal() - if err != nil { - return nil - } - return append(buf, p...) + + // 直接序列化ICMPv6消息 + // 第一个参数是协议号,对于ICMPv6应该是58 + return msg.Marshal(nil) +} + +func (t *Tracer) sendRequestV6(dst net.IP, ttl int) (*packet, error) { + id := uint16(atomic.AddUint32(&t.seq, 1)) + // 创建ICMPv6消息 + msg := icmp.Message{ + Type: ipv6.ICMPTypeEchoRequest, + Code: 0, + Body: &icmp.Echo{ + ID: int(id), + Seq: int(id), + Data: []byte("TRACEROUTE"), + }, + } + // 序列化ICMPv6消息 + b, err := msg.Marshal(nil) + if err != nil { + return nil, err + } + // 获取底层连接 + ipv6Conn, err := t.getIPv6Conn() + if err != nil { + return nil, err + } + // 设置IPv6数据包的跳数限制(TTL) + if err := ipv6Conn.SetHopLimit(ttl); err != nil { + return nil, err + } + // 发送数据包 + if _, err := ipv6Conn.WriteTo(b, nil, &net.IPAddr{IP: dst}); err != nil { + return nil, err + } + // 创建一个数据包记录,用于后续匹配回复 + req := &packet{dst, id, ttl, time.Now()} + return req, nil +} + +// getIPv6Conn 获取IPv6的PacketConn接口 +func (t *Tracer) getIPv6Conn() (*ipv6.PacketConn, error) { + if t.ipv6conn != nil { + return t.ipv6conn, nil + } + // 创建一个UDP连接 + c, err := net.ListenPacket("udp6", "::") + if err != nil { + return nil, err + } + // 将其包装为IPv6 PacketConn + p := ipv6.NewPacketConn(c) + // 设置控制消息 + if err := p.SetControlMessage(ipv6.FlagHopLimit|ipv6.FlagSrc|ipv6.FlagDst|ipv6.FlagInterface, true); err != nil { + c.Close() + return nil, err + } + t.ipv6conn = p + return p, nil +} + +func (t *Tracer) serveIPv6(conn *ipv6.PacketConn) error { + defer conn.Close() + buf := make([]byte, 1500) + for { + n, cm, src, err := conn.ReadFrom(buf) + if err != nil { + return err + } + // 从控制消息中获取跳数限制 + hopLimit := 0 + if cm != nil { + hopLimit = cm.HopLimit + } + // 解析ICMP消息 + msg, err := icmp.ParseMessage(ProtocolIPv6ICMP, buf[:n]) + if err != nil { + continue + } + // 根据消息类型处理 + switch msg.Type { + case ipv6.ICMPTypeEchoReply: + echo := msg.Body.(*icmp.Echo) + t.serveReply(src.(*net.IPAddr).IP, &packet{src.(*net.IPAddr).IP, uint16(echo.ID), hopLimit, time.Now()}) + case ipv6.ICMPTypeTimeExceeded: + // 处理超时消息,获取原始数据包 + data := msg.Body.(*icmp.TimeExceeded).Data + // 尝试提取嵌入的原始Echo请求 + if len(data) < 8 { // 至少需要IPv6头部前8个字节 + continue + } + // 跳过IPv6头部和ICMPv6头部,简化处理,实际可能需要更复杂的解析 + innerMsg, err := icmp.ParseMessage(ProtocolIPv6ICMP, data[48:]) + if err != nil { + continue + } + if echo, ok := innerMsg.Body.(*icmp.Echo); ok { + t.serveReply(src.(*net.IPAddr).IP, &packet{src.(*net.IPAddr).IP, uint16(echo.ID), hopLimit, time.Now()}) + } + } + } }