Compare commits

...

2 Commits

Author SHA1 Message Date
spiritlhl
5b416aa419 fix: 修复IPV6包构建 2025-04-05 08:52:42 +00:00
spiritlhl
b93b8fbb4a feat: 添加IPV6路由追踪包的构建逻辑 2025-04-05 05:13:56 +00:00
5 changed files with 177 additions and 199 deletions

View File

@ -14,19 +14,30 @@ type Result struct {
} }
var ( var (
ips = []string{ ipv4s = []string{
// "219.141.136.12", "202.106.50.1", // "219.141.136.12", "202.106.50.1",
"219.141.140.10", "202.106.195.68", "221.179.155.161", "219.141.140.10", "202.106.195.68", "221.179.155.161",
"202.96.209.133", "210.22.97.1", "211.136.112.200", "202.96.209.133", "210.22.97.1", "211.136.112.200",
"58.60.188.222", "210.21.196.6", "120.196.165.24", "58.60.188.222", "210.21.196.6", "120.196.165.24",
"61.139.2.69", "119.6.6.6", "211.137.96.205", "61.139.2.69", "119.6.6.6", "211.137.96.205",
} }
names = []string{ ipv6s = []string{
"2408:80f0:4100:2005::10", // 北京电信 IPv6
"2408:8000:1010:1::6", // 北京联通 IPv6
"2409:8000:1003:5::5", // 北京移动 IPv6
"2408:8026:1:1::6", // 上海联通 IPv6
"2409:8089:1020:50::6", // 上海移动 IPv6
}
ipv4Names = []string{
"北京电信", "北京联通", "北京移动", "北京电信", "北京联通", "北京移动",
"上海电信", "上海联通", "上海移动", "上海电信", "上海联通", "上海移动",
"广州电信", "广州联通", "广州移动", "广州电信", "广州联通", "广州移动",
"成都电信", "成都联通", "成都移动", "成都电信", "成都联通", "成都移动",
} }
ipv6Names = []string{
"北京电信v6", "北京联通v6", "北京移动v6",
"上海联通v6", "上海移动v6",
}
m = map[string]string{ m = map[string]string{
// [] 前的字符串个数中文占2个字符串 // [] 前的字符串个数中文占2个字符串
"AS23764": "电信CTGNET [精品线路]", "AS23764": "电信CTGNET [精品线路]",
@ -56,9 +67,9 @@ func removeDuplicates(elements []string) []string {
} }
func trace(ch chan Result, i int) { func trace(ch chan Result, i int) {
hops, err := Trace(net.ParseIP(ips[i])) hops, err := Trace(net.ParseIP(ipv4s[i]))
if err != nil { if err != nil {
s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], err) s := fmt.Sprintf("%v %-15s %v", ipv4Names[i], ipv4s[i], err)
ch <- Result{i, s} ch <- Result{i, s}
return return
} }
@ -75,7 +86,7 @@ func trace(ch chan Result, i int) {
if asns != nil && len(asns) > 0 { if asns != nil && len(asns) > 0 {
var tempText string var tempText string
asns = removeDuplicates(asns) asns = removeDuplicates(asns)
tempText += fmt.Sprintf("%v ", names[i]) tempText += fmt.Sprintf("%v ", ipv4Names[i])
hasAS4134 := false hasAS4134 := false
hasAS4809 := false hasAS4809 := false
for _, asn := range asns { for _, asn := range asns {
@ -94,7 +105,7 @@ func trace(ch chan Result, i int) {
// 仅包含 AS4809 属于 CN2GIA // 仅包含 AS4809 属于 CN2GIA
asns = append([]string{"AS4809a"}, asns...) asns = append([]string{"AS4809a"}, asns...)
} }
tempText += fmt.Sprintf("%-15s ", ips[i]) tempText += fmt.Sprintf("%-15s ", ipv4s[i])
for _, asn := range asns { for _, asn := range asns {
asnDescription := m[asn] asnDescription := m[asn]
switch asn { switch asn {
@ -128,17 +139,20 @@ func trace(ch chan Result, i int) {
} }
} }
} }
if tempText == (fmt.Sprintf("%v ", names[i]) + fmt.Sprintf("%-15s ", ips[i])) { if tempText == (fmt.Sprintf("%v ", ipv4Names[i]) + fmt.Sprintf("%-15s ", ipv4s[i])) {
tempText += fmt.Sprintf("%v", Red("检测不到已知线路的ASN")) tempText += fmt.Sprintf("%v", Red("检测不到已知线路的ASN"))
} }
ch <- Result{i, tempText} ch <- Result{i, tempText}
} else { } else {
s := fmt.Sprintf("%v %-15s %v", names[i], ips[i], Red("检测不到回程路由节点的IP地址")) s := fmt.Sprintf("%v %-15s %v", ipv4Names[i], ipv4s[i], Red("检测不到回程路由节点的IP地址"))
ch <- Result{i, s} ch <- Result{i, s}
} }
} }
func ipAsn(ip string) string { func ipAsn(ip string) string {
if strings.Contains(ip, ":") {
return ipv6Asn(ip)
}
switch { switch {
case strings.HasPrefix(ip, "59.43"): case strings.HasPrefix(ip, "59.43"):
return "AS4809" return "AS4809"

View File

@ -6,14 +6,17 @@ import (
) )
func BackTrace() { func BackTrace() {
// 获取IP地址数量
ipCount := len(ipv4s)
var ( var (
s [12]string // 对应 ips 目标地址数量 s = make([]string, ipCount) // 动态分配切片大小
c = make(chan Result) c = make(chan Result)
t = time.After(time.Second * 10) t = time.After(time.Second * 10)
) )
for i := range ips { for i := range ipv4s {
go trace(c, i) go trace(c, i)
} }
loop: loop:
for range s { for range s {
select { select {
@ -23,7 +26,10 @@ loop:
break loop break loop
} }
} }
for _, r := range s { for _, r := range s {
if r != "" {
fmt.Println(r) fmt.Println(r)
} }
} }
}

40
bk/ipv6_asn.go Normal file
View File

@ -0,0 +1,40 @@
package backtrace
import (
"strings"
)
// 识别IPv6地址的ASN
func ipv6Asn(ip string) string {
switch {
// 电信CN2GIA
case strings.HasPrefix(ip, "2408:80"):
return "AS4809a"
// 电信CN2GT
case strings.HasPrefix(ip, "2408:8000"):
return "AS4809b"
// 电信163
case strings.HasPrefix(ip, "240e:") || strings.HasPrefix(ip, "2408:8"):
return "AS4134"
// 联通9929
case strings.HasPrefix(ip, "2408:8026:"):
return "AS9929"
// 联通4837
case strings.HasPrefix(ip, "2408:8000:"):
return "AS4837"
// 移动CMIN2
case strings.HasPrefix(ip, "2409:8880:"):
return "AS58807"
// 移动CMI
case strings.HasPrefix(ip, "2409:8000:") || strings.HasPrefix(ip, "2409:"):
return "AS9808"
// 移动CMI
case strings.HasPrefix(ip, "2407:") || strings.HasPrefix(ip, "2401:"):
return "AS58453"
// 电信CTGNET
case strings.HasPrefix(ip, "2402:0:") || strings.HasPrefix(ip, "2400:8:"):
return "AS23764"
default:
return ""
}
}

View File

@ -21,7 +21,7 @@ var DefaultConfig = Config{
Timeout: 500 * time.Millisecond, Timeout: 500 * time.Millisecond,
MaxHops: 15, MaxHops: 15,
Count: 1, Count: 1,
Networks: []string{"ip4:icmp", "ip4:ip"}, Networks: []string{"ip4:icmp", "ip4:ip", "ip6:ipv6-icmp", "ip6:ip"},
} }
// DefaultTracer is a tracer with DefaultConfig. // DefaultTracer is a tracer with DefaultConfig.
@ -126,17 +126,6 @@ func (t *Tracer) init() {
} }
} }
} }
// 初始化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. // Close closes listening socket.
@ -168,17 +157,38 @@ func (t *Tracer) serve(conn *net.IPConn) error {
func (t *Tracer) serveData(from net.IP, b []byte) error { func (t *Tracer) serveData(from net.IP, b []byte) error {
if from.To4() == nil { if from.To4() == nil {
// TODO: implement ProtocolIPv6ICMP // IPv6 处理
msg, err := icmp.ParseMessage(ProtocolIPv6ICMP, b)
if err != nil {
return err
}
if msg.Type == ipv6.ICMPTypeEchoReply {
echo := msg.Body.(*icmp.Echo)
return t.serveReply(from, &packet{from, uint16(echo.ID), 1, time.Now()})
}
b = getReplyData(msg)
if len(b) < ipv6.HeaderLen {
return errMessageTooShort
}
switch b[0] >> 4 {
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, time.Now()})
default:
return errUnsupportedProtocol return errUnsupportedProtocol
} }
now := time.Now() } else {
// 原有的IPv4处理逻辑
msg, err := icmp.ParseMessage(ProtocolICMP, b) msg, err := icmp.ParseMessage(ProtocolICMP, b)
if err != nil { if err != nil {
return err return err
} }
if msg.Type == ipv4.ICMPTypeEchoReply { if msg.Type == ipv4.ICMPTypeEchoReply {
echo := msg.Body.(*icmp.Echo) echo := msg.Body.(*icmp.Echo)
return t.serveReply(from, &packet{from, uint16(echo.ID), 1, now}) return t.serveReply(from, &packet{from, uint16(echo.ID), 1, time.Now()})
} }
b = getReplyData(msg) b = getReplyData(msg)
if len(b) < ipv4.HeaderLen { if len(b) < ipv4.HeaderLen {
@ -190,26 +200,23 @@ func (t *Tracer) serveData(from net.IP, b []byte) error {
if err != nil { if err != nil {
return err return err
} }
return t.serveReply(ip.Dst, &packet{from, uint16(ip.ID), ip.TTL, now}) return t.serveReply(ip.Dst, &packet{from, uint16(ip.ID), ip.TTL, time.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: default:
return errUnsupportedProtocol return errUnsupportedProtocol
} }
} }
}
func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) { func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) {
id := uint16(atomic.AddUint32(&t.seq, 1))
var b []byte
if dst.To4() == nil { if dst.To4() == nil {
// IPv6 // IPv6
return t.sendRequestV6(dst, ttl) b = newPacketV6(id, dst, ttl)
} else {
// IPv4
b = newPacketV4(id, dst, ttl)
} }
// Ipv4
id := uint16(atomic.AddUint32(&t.seq, 1))
b := newPacketV4(id, dst, ttl)
req := &packet{dst, id, ttl, time.Now()} req := &packet{dst, id, ttl, time.Now()}
_, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst}) _, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst})
if err != nil { if err != nil {

View File

@ -1,125 +1,36 @@
package backtrace package backtrace
import ( import (
"encoding/binary"
"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, error) { func newPacketV6(id uint16, dst net.IP, ttl int) []byte {
// 创建ICMPv6 Echo请求消息 // 创建ICMP消息(回显请求)
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"),
}, },
} }
// 序列化ICMP消息
// 直接序列化ICMPv6消息 icmpData, _ := msg.Marshal(nil)
// 第一个参数是协议号对于ICMPv6应该是58 // 手动创建原始IPv6数据包头部
return msg.Marshal(nil) ipHeaderBytes := make([]byte, ipv6.HeaderLen)
} // 设置版本和流量类别(第一个字节)
ipHeaderBytes[0] = (ipv6.Version << 4)
func (t *Tracer) sendRequestV6(dst net.IP, ttl int) (*packet, error) { // 设置下一个头部(协议)
id := uint16(atomic.AddUint32(&t.seq, 1)) ipHeaderBytes[6] = ProtocolIPv6ICMP
// 创建ICMPv6消息 // 设置跳数限制
msg := icmp.Message{ ipHeaderBytes[7] = byte(ttl)
Type: ipv6.ICMPTypeEchoRequest, // 设置有效载荷长度2字节字段
Code: 0, binary.BigEndian.PutUint16(ipHeaderBytes[4:6], uint16(len(icmpData)))
Body: &icmp.Echo{ // 设置目标地址最后16个字节
ID: int(id), copy(ipHeaderBytes[24:40], dst.To16())
Seq: int(id), // 合并头部和ICMP数据
Data: []byte("TRACEROUTE"), return append(ipHeaderBytes, icmpData...)
},
}
// 序列化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()})
}
}
}
} }