mirror of
https://github.com/oneclickvirt/backtrace.git
synced 2025-04-22 04:02:07 +08:00
Compare commits
6 Commits
153787b112
...
7a644b403e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7a644b403e | ||
![]() |
7efc61cbe4 | ||
![]() |
0c6b11883f | ||
![]() |
4f9603bee7 | ||
![]() |
509f6a3dd6 | ||
![]() |
8d69825c32 |
12
README.md
12
README.md
@ -10,17 +10,15 @@
|
||||
|
||||
## 功能
|
||||
|
||||
- [x] 检测回程显示IPV4地址时的线路(使用1500字节的包),不显示IP地址时显示ASN检测不到,原版[backtrace](https://github.com/zhanghanyun/backtrace)也支持
|
||||
- [x] 支持对```4837```、```9929```和```163```线路的判断,原版[backtrace](https://github.com/zhanghanyun/backtrace)也支持
|
||||
- [x] 支持对```CN2GT```和```CN2GIA```线路的判断,原版[backtrace](https://github.com/zhanghanyun/backtrace)不支持,原版全部识别为```CN2```了
|
||||
- [x] 支持对```CMIN2```和```CMI```线路的判断,原版[backtrace](https://github.com/zhanghanyun/backtrace)也支持,但所支持的IP区间不一样,本项目更多
|
||||
- [x] 支持对整个回程路由进行线路分析,与原版[backtrace](https://github.com/zhanghanyun/backtrace)仅进行一次判断不同
|
||||
- [x] 修复原版[backtrace](https://github.com/zhanghanyun/backtrace)对IPV4地址信息获取时json解析失败依然打印信息的问题,本项目忽略错误继续执行路由线路查询
|
||||
- [x] 检测回程显示IPV4地址时的线路(使用1500字节的包),不显示IP地址时显示ASN检测不到
|
||||
- [x] 支持对```9929```、```4837```和```163```线路的判断
|
||||
- [x] 支持对```CTGNET```、```CN2GIA```和```CN2GT```线路的判断
|
||||
- [x] 支持对```CMIN2```和```CMI```线路的判断
|
||||
- [x] 支持对整个回程路由进行线路分析,一个目标IP可能会分析出多种线路
|
||||
- [x] 增加对全平台的编译支持,原版[backtrace](https://github.com/zhanghanyun/backtrace)仅支持linux平台的amd64和arm64架构
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] 增加对CTG回程的判断
|
||||
- [ ] 自动检测汇聚层,裁剪结果不输出汇聚层后的线路(区分境内外段)
|
||||
- [ ] 添加对主流ISP的POP点检测,区分国际互联能力
|
||||
- [ ] 增加IPV6路由能力检测
|
||||
|
4
back/README.md
Normal file
4
back/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# 部分参考资料
|
||||
|
||||
https://blog.sunflyer.cn/archives/594
|
||||
|
@ -29,6 +29,7 @@ var (
|
||||
}
|
||||
m = map[string]string{
|
||||
// [] 前的字符串个数,中文占2个字符串
|
||||
"AS23764": "电信CTGNET [精品线路]",
|
||||
"AS4809a": "电信CN2GIA [精品线路]",
|
||||
"AS4809b": "电信CN2GT [优质线路]",
|
||||
"AS4134": "电信163 [普通线路]",
|
||||
@ -109,6 +110,10 @@ func trace(ch chan Result, i int) {
|
||||
if !strings.Contains(tempText, asnDescription) {
|
||||
tempText += DarkGreen(asnDescription) + " "
|
||||
}
|
||||
case "AS23764":
|
||||
if !strings.Contains(tempText, asnDescription) {
|
||||
tempText += DarkGreen(asnDescription) + " "
|
||||
}
|
||||
case "AS4809b":
|
||||
if !strings.Contains(tempText, asnDescription) {
|
||||
tempText += Green(asnDescription) + " "
|
||||
@ -149,6 +154,8 @@ func ipAsn(ip string) string {
|
||||
return "AS58807"
|
||||
case strings.HasPrefix(ip, "223.118") || strings.HasPrefix(ip, "223.119") || strings.HasPrefix(ip, "223.120") || strings.HasPrefix(ip, "223.121"):
|
||||
return "AS58453"
|
||||
case strings.HasPrefix(ip, "69.194") || strings.HasPrefix(ip, "203.22"):
|
||||
return "AS23764"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
//go:build linux || freebsd || openbsd || darwin
|
||||
// +build linux freebsd openbsd darwin
|
||||
|
||||
package backtrace
|
||||
|
||||
import (
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
//go:build !linux && !freebsd && !openbsd && !darwin && !windows
|
||||
// +build !linux,!freebsd,!openbsd,!darwin,!windows
|
||||
|
||||
package backtrace
|
||||
|
||||
import (
|
@ -3,14 +3,16 @@ package backtrace
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
"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.
|
||||
@ -42,9 +44,10 @@ type Config struct {
|
||||
type Tracer struct {
|
||||
Config
|
||||
|
||||
once sync.Once
|
||||
conn *net.IPConn
|
||||
err error
|
||||
once sync.Once
|
||||
conn *net.IPConn // Ipv4连接
|
||||
ipv6conn *ipv6.PacketConn // IPv6连接
|
||||
err error
|
||||
|
||||
mu sync.RWMutex
|
||||
sess map[string][]*Session
|
||||
@ -113,13 +116,26 @@ func (t *Tracer) NewSession(ip net.IP) (*Session, error) {
|
||||
}
|
||||
|
||||
func (t *Tracer) init() {
|
||||
// 初始化IPv4连接
|
||||
for _, network := range t.Networks {
|
||||
t.conn, t.err = t.listen(network, t.Addr)
|
||||
if t.err != nil {
|
||||
continue
|
||||
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()
|
||||
}
|
||||
go t.serve(t.conn)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,8 +147,10 @@ func (t *Tracer) Close() {
|
||||
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)
|
||||
@ -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) {
|
||||
if dst.To4() == nil {
|
||||
// IPv6
|
||||
return t.sendRequestV6(dst, ttl)
|
||||
}
|
||||
// Ipv4
|
||||
id := uint16(atomic.AddUint32(&t.seq, 1))
|
||||
b := newPacket(id, dst, ttl)
|
||||
b := newPacketV4(id, dst, ttl)
|
||||
req := &packet{dst, id, ttl, time.Now()}
|
||||
_, err := t.conn.WriteToIP(b, &net.IPAddr{IP: dst})
|
||||
if err != nil {
|
||||
@ -352,33 +375,6 @@ var (
|
||||
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
|
||||
const (
|
||||
ProtocolICMP = 1
|
35
bk/trace_ipv4.go
Normal file
35
bk/trace_ipv4.go
Normal 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
125
bk/trace_ipv6.go
Normal 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()})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user