Compare commits

...

6 Commits

Author SHA1 Message Date
spiritlhl
7a644b403e feat: 添加IPV6路由追踪包的构建逻辑 2025-04-05 03:48:50 +00:00
spiritlhl
7efc61cbe4 feat: 尝试添加IPV6路由追踪 2025-04-05 03:24:04 +00:00
spiritlhl
0c6b11883f
feat: 添加参考资料 2025-04-05 11:09:27 +08:00
spiritlhl
4f9603bee7 feat: 修复错误命名 2025-04-05 02:56:13 +00:00
spiritlhl
509f6a3dd6 feat: 支持CTG线路检测,优化系统适配性添加占位符 2025-04-05 02:52:00 +00:00
spiritlhl
8d69825c32 fix: CTG测不了一点,和CN2GIA差别不大 2025-04-05 02:26:31 +00:00
10 changed files with 218 additions and 153 deletions

View File

@ -10,17 +10,15 @@
## 功能 ## 功能
- [x] 检测回程显示IPV4地址时的线路(使用1500字节的包)不显示IP地址时显示ASN检测不到原版[backtrace](https://github.com/zhanghanyun/backtrace)也支持 - [x] 检测回程显示IPV4地址时的线路(使用1500字节的包)不显示IP地址时显示ASN检测不到
- [x] 支持对```4837```、```9929```和```163```线路的判断,原版[backtrace](https://github.com/zhanghanyun/backtrace)也支持 - [x] 支持对```9929```、```4837```和```163```线路的判断
- [x] 支持对```CN2GT```和```CN2GIA```线路的判断,原版[backtrace](https://github.com/zhanghanyun/backtrace)不支持,原版全部识别为```CN2```了 - [x] 支持对```CTGNET```、```CN2GIA```和```CN2GT```线路的判断
- [x] 支持对```CMIN2```和```CMI```线路的判断,原版[backtrace](https://github.com/zhanghanyun/backtrace)也支持但所支持的IP区间不一样本项目更多 - [x] 支持对```CMIN2```和```CMI```线路的判断
- [x] 支持对整个回程路由进行线路分析,与原版[backtrace](https://github.com/zhanghanyun/backtrace)仅进行一次判断不同 - [x] 支持对整个回程路由进行线路分析一个目标IP可能会分析出多种线路
- [x] 修复原版[backtrace](https://github.com/zhanghanyun/backtrace)对IPV4地址信息获取时json解析失败依然打印信息的问题本项目忽略错误继续执行路由线路查询
- [x] 增加对全平台的编译支持,原版[backtrace](https://github.com/zhanghanyun/backtrace)仅支持linux平台的amd64和arm64架构 - [x] 增加对全平台的编译支持,原版[backtrace](https://github.com/zhanghanyun/backtrace)仅支持linux平台的amd64和arm64架构
## TODO ## TODO
- [ ] 增加对CTG回程的判断
- [ ] 自动检测汇聚层,裁剪结果不输出汇聚层后的线路(区分境内外段) - [ ] 自动检测汇聚层,裁剪结果不输出汇聚层后的线路(区分境内外段)
- [ ] 添加对主流ISP的POP点检测区分国际互联能力 - [ ] 添加对主流ISP的POP点检测区分国际互联能力
- [ ] 增加IPV6路由能力检测 - [ ] 增加IPV6路由能力检测

4
back/README.md Normal file
View File

@ -0,0 +1,4 @@
# 部分参考资料
https://blog.sunflyer.cn/archives/594

View File

@ -29,6 +29,7 @@ var (
} }
m = map[string]string{ m = map[string]string{
// [] 前的字符串个数中文占2个字符串 // [] 前的字符串个数中文占2个字符串
"AS23764": "电信CTGNET [精品线路]",
"AS4809a": "电信CN2GIA [精品线路]", "AS4809a": "电信CN2GIA [精品线路]",
"AS4809b": "电信CN2GT [优质线路]", "AS4809b": "电信CN2GT [优质线路]",
"AS4134": "电信163 [普通线路]", "AS4134": "电信163 [普通线路]",
@ -109,6 +110,10 @@ func trace(ch chan Result, i int) {
if !strings.Contains(tempText, asnDescription) { if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " " tempText += DarkGreen(asnDescription) + " "
} }
case "AS23764":
if !strings.Contains(tempText, asnDescription) {
tempText += DarkGreen(asnDescription) + " "
}
case "AS4809b": case "AS4809b":
if !strings.Contains(tempText, asnDescription) { if !strings.Contains(tempText, asnDescription) {
tempText += Green(asnDescription) + " " tempText += Green(asnDescription) + " "
@ -149,6 +154,8 @@ func ipAsn(ip string) string {
return "AS58807" return "AS58807"
case strings.HasPrefix(ip, "223.118") || strings.HasPrefix(ip, "223.119") || strings.HasPrefix(ip, "223.120") || strings.HasPrefix(ip, "223.121"): case strings.HasPrefix(ip, "223.118") || strings.HasPrefix(ip, "223.119") || strings.HasPrefix(ip, "223.120") || strings.HasPrefix(ip, "223.121"):
return "AS58453" return "AS58453"
case strings.HasPrefix(ip, "69.194") || strings.HasPrefix(ip, "203.22"):
return "AS23764"
default: default:
return "" return ""
} }

View File

@ -1,3 +1,6 @@
//go:build linux || freebsd || openbsd || darwin
// +build linux freebsd openbsd darwin
package backtrace package backtrace
import ( import (

View File

@ -1,53 +0,0 @@
package backtrace
import (
"net"
"syscall"
. "github.com/oneclickvirt/defaultset"
)
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
if EnableLoger {
InitLogger()
defer Logger.Sync()
conn, err := net.ListenIP(network, laddr)
if err != nil {
Logger.Info(err.Error())
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
return conn, nil
} else {
conn, err := net.ListenIP(network, laddr)
if err != nil {
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
}

View File

@ -1,53 +0,0 @@
package backtrace
import (
"net"
"syscall"
. "github.com/oneclickvirt/defaultset"
)
func (t *Tracer) listen(network string, laddr *net.IPAddr) (*net.IPConn, error) {
if EnableLoger {
InitLogger()
defer Logger.Sync()
conn, err := net.ListenIP(network, laddr)
if err != nil {
Logger.Info(err.Error())
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
Logger.Info(err.Error())
conn.Close()
return nil, err
}
return conn, nil
} else {
conn, err := net.ListenIP(network, laddr)
if err != nil {
return nil, err
}
raw, err := conn.SyscallConn()
if err != nil {
conn.Close()
return nil, err
}
_ = raw.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
})
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
}

View File

@ -1,3 +1,6 @@
//go:build !linux && !freebsd && !openbsd && !darwin && !windows
// +build !linux,!freebsd,!openbsd,!darwin,!windows
package backtrace package backtrace
import ( import (

View File

@ -3,14 +3,16 @@ package backtrace
import ( import (
"context" "context"
"errors" "errors"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"net" "net"
"sort" "sort"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
) )
// DefaultConfig is the default configuration for Tracer. // DefaultConfig is the default configuration for Tracer.
@ -43,7 +45,8 @@ type Tracer struct {
Config Config
once sync.Once once sync.Once
conn *net.IPConn conn *net.IPConn // Ipv4连接
ipv6conn *ipv6.PacketConn // IPv6连接
err error err error
mu sync.RWMutex mu sync.RWMutex
@ -113,13 +116,26 @@ func (t *Tracer) NewSession(ip net.IP) (*Session, error) {
} }
func (t *Tracer) init() { func (t *Tracer) init() {
// 初始化IPv4连接
for _, network := range t.Networks { for _, network := range t.Networks {
if strings.HasPrefix(network, "ip4") {
t.conn, t.err = t.listen(network, t.Addr) t.conn, t.err = t.listen(network, t.Addr)
if t.err != nil { if t.err == nil {
continue
}
go t.serve(t.conn) go t.serve(t.conn)
return 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()
}
} }
} }
@ -131,8 +147,10 @@ func (t *Tracer) Close() {
if t.conn != nil { if t.conn != nil {
t.conn.Close() t.conn.Close()
} }
if t.ipv6conn != nil {
t.ipv6conn.Close()
}
} }
func (t *Tracer) serve(conn *net.IPConn) error { func (t *Tracer) serve(conn *net.IPConn) error {
defer conn.Close() defer conn.Close()
buf := make([]byte, 1500) buf := make([]byte, 1500)
@ -185,8 +203,13 @@ func (t *Tracer) serveData(from net.IP, b []byte) error {
} }
func (t *Tracer) sendRequest(dst net.IP, ttl int) (*packet, error) { 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)) id := uint16(atomic.AddUint32(&t.seq, 1))
b := newPacket(id, dst, ttl) 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 {
@ -352,33 +375,6 @@ var (
errNoReplyData = errors.New("no reply data") errNoReplyData = errors.New("no reply data")
) )
func newPacket(id uint16, dst net.IP, ttl int) []byte {
// TODO: reuse buffers...
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Body: &icmp.Echo{
ID: int(id),
Seq: int(id),
},
}
p, _ := msg.Marshal(nil)
ip := &ipv4.Header{
Version: ipv4.Version,
Len: ipv4.HeaderLen,
TotalLen: ipv4.HeaderLen + len(p),
TOS: 16,
ID: int(id),
Dst: dst,
Protocol: ProtocolICMP,
TTL: ttl,
}
buf, err := ip.Marshal()
if err != nil {
return nil
}
return append(buf, p...)
}
// IANA Assigned Internet Protocol Numbers // IANA Assigned Internet Protocol Numbers
const ( const (
ProtocolICMP = 1 ProtocolICMP = 1

35
bk/trace_ipv4.go Normal file
View File

@ -0,0 +1,35 @@
package backtrace
import (
"net"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
func newPacketV4(id uint16, dst net.IP, ttl int) []byte {
// TODO: reuse buffers...
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Body: &icmp.Echo{
ID: int(id),
Seq: int(id),
},
}
p, _ := msg.Marshal(nil)
ip := &ipv4.Header{
Version: ipv4.Version,
Len: ipv4.HeaderLen,
TotalLen: ipv4.HeaderLen + len(p),
TOS: 16,
ID: int(id),
Dst: dst,
Protocol: ProtocolICMP,
TTL: ttl,
}
buf, err := ip.Marshal()
if err != nil {
return nil
}
return append(buf, p...)
}

125
bk/trace_ipv6.go Normal file
View File

@ -0,0 +1,125 @@
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, error) {
// 创建ICMPv6 Echo请求消息
msg := icmp.Message{
Type: ipv6.ICMPTypeEchoRequest,
Code: 0,
Body: &icmp.Echo{
ID: int(id),
Seq: int(id),
Data: []byte("TRACEROUTE"),
},
}
// 直接序列化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()})
}
}
}
}