feat: 添加IPV6路由追踪包的构建逻辑

This commit is contained in:
spiritlhl 2025-04-05 03:48:50 +00:00
parent 7efc61cbe4
commit 7a644b403e
3 changed files with 577 additions and 451 deletions

463
bk/trace_common.go Normal file
View File

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

View File

@ -1,358 +1,13 @@
package backtrace package backtrace
import ( import (
"context" "net"
"errors"
"golang.org/x/net/icmp" "golang.org/x/net/icmp"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"net"
"sort"
"sync"
"sync/atomic"
"time"
) )
// DefaultConfig is the default configuration for Tracer. func newPacketV4(id uint16, dst net.IP, ttl int) []byte {
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 {
// TODO: reuse buffers... // TODO: reuse buffers...
msg := icmp.Message{ msg := icmp.Message{
Type: ipv4.ICMPTypeEcho, Type: ipv4.ICMPTypeEcho,
@ -378,90 +33,3 @@ func newPacket(id uint16, dst net.IP, ttl int) []byte {
} }
return append(buf, p...) 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
}

View File

@ -1,30 +1,125 @@
package bk package backtrace
import ( import (
"net" "net"
"sync/atomic"
"time"
"golang.org/x/net/icmp" "golang.org/x/net/icmp"
"golang.org/x/net/ipv6" "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{ msg := icmp.Message{
Type: ipv6.ICMPTypeEchoRequest, Type: ipv6.ICMPTypeEchoRequest,
Code: 0,
Body: &icmp.Echo{ Body: &icmp.Echo{
ID: int(id), ID: int(id),
Seq: int(id), Seq: int(id),
Data: []byte("TRACEROUTE"),
}, },
} }
p, _ := msg.Marshal(nil)
ip := &ipv6.Header{ // 直接序列化ICMPv6消息
Version: ipv6.Version, // 第一个参数是协议号对于ICMPv6应该是58
NextHeader: ProtocolIPv6ICMP, return msg.Marshal(nil)
HopLimit: ttl, }
Dst: dst,
} func (t *Tracer) sendRequestV6(dst net.IP, ttl int) (*packet, error) {
buf, err := ip.Marshal() id := uint16(atomic.AddUint32(&t.seq, 1))
if err != nil { // 创建ICMPv6消息
return nil msg := icmp.Message{
} Type: ipv6.ICMPTypeEchoRequest,
return append(buf, p...) 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()})
}
}
}
} }