Compare commits

..

No commits in common. "6c92bf5f27bb469af6637eb1d402f41ad0477255" and "c7a4dd9475ad4734b71e784516163d6cce59abd7" have entirely different histories.

9 changed files with 102 additions and 278 deletions

View File

@ -27,7 +27,7 @@ jobs:
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
TAG="v0.0.7-$(date +'%Y%m%d%H%M%S')"
TAG="v0.0.6-$(date +'%Y%m%d%H%M%S')"
git tag $TAG
git push origin $TAG
echo "TAG=$TAG" >> $GITHUB_ENV

View File

@ -16,7 +16,6 @@
- [x] 支持对```CMIN2```和```CMI```线路的判断
- [x] 支持对整个回程路由进行线路分析一个目标IP可能会分析出多种线路
- [x] 支持对主流接入点的线路检测,方便分析国际互联能力
- [x] 多次并发路由检测以分析平均多次路由,避免单次路由因网络波动未能准确检测
- [x] 增加对全平台的编译支持,原版[backtrace](https://github.com/zhanghanyun/backtrace)仅支持linux平台的amd64和arm64架构
- [x] 兼容额外的ICMP地址获取若当前目标IP无法查询路由尝试额外的IP地址
@ -75,7 +74,7 @@ rm -rf /usr/bin/backtrace
## 在Golang中使用
```
go get github.com/oneclickvirt/backtrace@v0.0.7-20250811023541
go get github.com/oneclickvirt/backtrace@v0.0.6-20250805091811
```
## 概览图

View File

@ -1,13 +1,13 @@
package backtrace
import (
"strings"
"fmt"
"time"
"github.com/oneclickvirt/backtrace/model"
)
func BackTrace(enableIpv6 bool) string {
func BackTrace(enableIpv6 bool) {
if model.CachedIcmpData == "" || model.ParsedIcmpTargets == nil || time.Since(model.CachedIcmpDataFetchTime) > time.Hour {
model.CachedIcmpData = getData(model.IcmpTargets)
model.CachedIcmpDataFetchTime = time.Now()
@ -15,7 +15,6 @@ func BackTrace(enableIpv6 bool) string {
model.ParsedIcmpTargets = parseIcmpTargets(model.CachedIcmpData)
}
}
var builder strings.Builder
if enableIpv6 {
ipv4Count := len(model.Ipv4s)
ipv6Count := len(model.Ipv6s)
@ -40,18 +39,14 @@ func BackTrace(enableIpv6 bool) string {
break loopIPv4v6
}
}
// 收集 IPv4 结果
for i := 0; i < ipv4Count; i++ {
if s[i] != "" {
builder.WriteString(s[i])
builder.WriteString("\n")
fmt.Println(s[i])
}
}
// 收集 IPv6 结果
for i := ipv4Count; i < totalCount; i++ {
if s[i] != "" {
builder.WriteString(s[i])
builder.WriteString("\n")
fmt.Println(s[i])
}
}
} else {
@ -73,15 +68,10 @@ func BackTrace(enableIpv6 bool) string {
break loopIPv4
}
}
// 收集结果
for _, r := range s {
if r != "" {
builder.WriteString(r)
builder.WriteString("\n")
fmt.Println(r)
}
}
}
// 返回完整结果,去掉末尾的换行符
result := builder.String()
return strings.TrimSuffix(result, "\n")
}

View File

@ -194,15 +194,14 @@ func (t *Tracer) serveData(from net.IP, b []byte) error {
Logger.Info(fmt.Sprintf("收到IPv6 ICMP消息: 类型=%v, 代码=%v", msg.Type, msg.Code))
}
// 处理不同类型的ICMP消息
switch msg.Type {
case ipv6.ICMPTypeEchoReply:
if msg.Type == ipv6.ICMPTypeEchoReply {
if echo, ok := msg.Body.(*icmp.Echo); ok {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("处理IPv6回显应答: ID=%d, Seq=%d", echo.ID, echo.Seq))
}
return t.serveReply(from, &packet{from, uint16(echo.ID), 1, time.Now()})
}
case ipv6.ICMPTypeTimeExceeded:
} else if msg.Type == ipv6.ICMPTypeTimeExceeded {
b = getReplyData(msg)
if len(b) < ipv6.HeaderLen {
if model.EnableLoger {

View File

@ -4,7 +4,6 @@ import (
"fmt"
"net"
"strings"
"sync"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset"
@ -63,66 +62,46 @@ func trace(ch chan Result, i int) {
defer Logger.Sync()
Logger.Info(fmt.Sprintf("开始追踪 %s (%s)", model.Ipv4Names[i], model.Ipv4s[i]))
}
var allHops [][]*Hop
var successfulTraces int
var mu sync.Mutex
var wg sync.WaitGroup
// 并发执行3次trace
for attempt := 1; attempt <= 3; attempt++ {
wg.Add(1)
go func(attemptNum int) {
defer wg.Done()
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次尝试追踪 %s (%s)", attemptNum, model.Ipv4Names[i], model.Ipv4s[i]))
}
// 先尝试原始IP地址
hops, err := Trace(net.ParseIP(model.Ipv4s[i]))
if err != nil {
s := fmt.Sprintf("%v %-15s %v", model.Ipv4Names[i], model.Ipv4s[i], Red("检测不到回程路由节点的IP地址"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("第%d次追踪 %s (%s) 失败: %v", attemptNum, model.Ipv4Names[i], model.Ipv4s[i], err))
Logger.Error(fmt.Sprintf("追踪 %s (%s) 失败: %v", model.Ipv4Names[i], model.Ipv4s[i], err))
}
// 如果原始IP失败尝试备选IP
ch <- Result{i, s}
return
}
asns := extractIpv4ASNsFromHops(hops, model.EnableLoger)
// 如果没有找到ASN尝试备选IP
if len(asns) == 0 {
// 尝试从IcmpTargets获取备选IP
if tryAltIPs := tryAlternativeIPs(model.Ipv4Names[i], "v4"); len(tryAltIPs) > 0 {
for _, altIP := range tryAltIPs {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次尝试备选IP %s 追踪 %s", attemptNum, altIP, model.Ipv4Names[i]))
Logger.Info(fmt.Sprintf("尝试备选IP %s 追踪 %s", altIP, model.Ipv4Names[i]))
}
hops, err = Trace(net.ParseIP(altIP))
if err == nil && len(hops) > 0 {
newAsns := extractIpv4ASNsFromHops(hops, model.EnableLoger)
asns = append(asns, newAsns...)
if len(newAsns) > 0 {
break // 成功找到可用IP
}
}
}
}
if err == nil && len(hops) > 0 {
mu.Lock()
allHops = append(allHops, hops)
successfulTraces++
mu.Unlock()
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次追踪 %s (%s) 成功,获得%d个hop", attemptNum, model.Ipv4Names[i], model.Ipv4s[i], len(hops)))
}
}
}(attempt)
}
// 等待所有goroutine完成
wg.Wait()
// 如果3次都失败
if successfulTraces == 0 {
s := fmt.Sprintf("%v %-15s %v", model.Ipv4Names[i], model.Ipv4s[i], Red("检测不到回程路由节点的IP地址"))
if model.EnableLoger {
Logger.Error(fmt.Sprintf("%s (%s) 3次尝试都失败检测不到回程路由节点的IP地址", model.Ipv4Names[i], model.Ipv4s[i]))
}
ch <- Result{i, s}
return
}
// 合并hops结果
mergedHops := mergeHops(allHops)
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 完成%d次成功追踪合并后获得%d个hop", model.Ipv4Names[i], model.Ipv4s[i], successfulTraces, len(mergedHops)))
}
// 从合并后的hops提取ASN
asns := extractIpv4ASNsFromHops(mergedHops, model.EnableLoger)
asns = removeDuplicates(asns)
// // 记录每个hop的信息
// if model.EnableLoger {
// for hopNum, hop := range hops {
// for nodeNum, node := range hop.Nodes {
// Logger.Info(fmt.Sprintf("追踪 %s (%s) - Hop %d, Node %d: %s (RTT: %v)",
// model.Ipv4Names[i], model.Ipv4s[i], hopNum+1, nodeNum+1, node.IP.String(), node.RTT))
// }
// }
// }
// 处理不同线路
if len(asns) > 0 {
var tempText string
@ -188,16 +167,18 @@ func trace(ch chan Result, i int) {
}
if tempText == (fmt.Sprintf("%v ", model.Ipv4Names[i]) + fmt.Sprintf("%-15s ", model.Ipv4s[i])) {
tempText += fmt.Sprintf("%v", Red("检测不到已知线路的ASN"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("%s (%s) 检测不到已知线路的ASN", model.Ipv4Names[i], model.Ipv4s[i]))
}
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 追踪完成,最终结果: %s", model.Ipv4Names[i], model.Ipv4s[i], tempText))
Logger.Info(fmt.Sprintf("%s (%s) 追踪完成,结果: %s", model.Ipv4Names[i], model.Ipv4s[i], tempText))
}
ch <- Result{i, tempText}
} else {
s := fmt.Sprintf("%v %-15s %v", model.Ipv4Names[i], model.Ipv4s[i], Red("检测不到回程路由节点的IPV4地址"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("%s (%s) 检测不到回程路由节点的IPV4地址", model.Ipv4Names[i], model.Ipv4s[i]))
}

View File

@ -4,7 +4,6 @@ import (
"fmt"
"net"
"strings"
"sync"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset"
@ -12,7 +11,7 @@ import (
"golang.org/x/net/ipv6"
)
func newPacketV6(id uint16, _ net.IP, _ int) []byte {
func newPacketV6(id uint16, dst net.IP, ttl int) []byte {
// 使用ipv6包的Echo请求
msg := icmp.Message{
Type: ipv6.ICMPTypeEchoRequest,
@ -78,66 +77,46 @@ func traceIPv6(ch chan Result, i int, offset int) {
defer Logger.Sync()
Logger.Info(fmt.Sprintf("开始追踪 %s (%s)", model.Ipv6Names[i], model.Ipv6s[i]))
}
var allHops [][]*Hop
var successfulTraces int
var mu sync.Mutex
var wg sync.WaitGroup
// 并发执行3次trace
for attempt := 1; attempt <= 3; attempt++ {
wg.Add(1)
go func(attemptNum int) {
defer wg.Done()
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次尝试追踪 %s (%s)", attemptNum, model.Ipv6Names[i], model.Ipv6s[i]))
}
// 先尝试原始IP地址
hops, err := Trace(net.ParseIP(model.Ipv6s[i]))
if err != nil {
s := fmt.Sprintf("%v %-24s %v", model.Ipv6Names[i], model.Ipv6s[i], Red("检测不到回程路由节点的IP地址"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("第%d次追踪 %s (%s) 失败: %v", attemptNum, model.Ipv6Names[i], model.Ipv6s[i], err))
Logger.Warn(fmt.Sprintf("%s (%s) 检测不到回程路由节点的IP地址", model.Ipv6Names[i], model.Ipv6s[i]))
}
// 如果原始IP失败尝试备选IP
ch <- Result{i + offset, s}
return
}
asns := extractIpv6ASNsFromHops(hops, model.EnableLoger)
// 如果没有找到ASN尝试备选IP
if len(asns) == 0 {
// 尝试从IcmpTargets获取备选IP
if tryAltIPs := tryAlternativeIPs(model.Ipv6Names[i], "v6"); len(tryAltIPs) > 0 {
for _, altIP := range tryAltIPs {
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次尝试备选IP %s 追踪 %s", attemptNum, altIP, model.Ipv6Names[i]))
Logger.Info(fmt.Sprintf("尝试备选IP %s 追踪 %s", altIP, model.Ipv6Names[i]))
}
hops, err = Trace(net.ParseIP(altIP))
if err == nil && len(hops) > 0 {
newAsns := extractIpv6ASNsFromHops(hops, model.EnableLoger)
asns = append(asns, newAsns...)
if len(newAsns) > 0 {
break // 成功找到可用IP
}
}
}
}
if err == nil && len(hops) > 0 {
mu.Lock()
allHops = append(allHops, hops)
successfulTraces++
mu.Unlock()
if model.EnableLoger {
Logger.Info(fmt.Sprintf("第%d次追踪 %s (%s) 成功,获得%d个hop", attemptNum, model.Ipv6Names[i], model.Ipv6s[i], len(hops)))
}
}
}(attempt)
}
// 等待所有goroutine完成
wg.Wait()
// 如果3次都失败
if successfulTraces == 0 {
s := fmt.Sprintf("%v %-24s %v", model.Ipv6Names[i], model.Ipv6s[i], Red("检测不到回程路由节点的IP地址"))
if model.EnableLoger {
Logger.Warn(fmt.Sprintf("%s (%s) 3次尝试都失败检测不到回程路由节点的IP地址", model.Ipv6Names[i], model.Ipv6s[i]))
}
ch <- Result{i + offset, s}
return
}
// 合并hops结果
mergedHops := mergeHops(allHops)
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 完成%d次成功追踪合并后获得%d个hop", model.Ipv6Names[i], model.Ipv6s[i], successfulTraces, len(mergedHops)))
}
// 从合并后的hops提取ASN
asns := extractIpv6ASNsFromHops(mergedHops, model.EnableLoger)
asns = removeDuplicates(asns)
// // 记录每个hop的信息
// if model.EnableLoger {
// for hopNum, hop := range hops {
// for nodeNum, node := range hop.Nodes {
// Logger.Info(fmt.Sprintf("追踪 %s (%s) - Hop %d, Node %d: %s (RTT: %v)",
// model.Ipv6Names[i], model.Ipv6s[i], hopNum+1, nodeNum+1, node.IP.String(), node.RTT))
// }
// }
// }
// 处理不同线路
if len(asns) > 0 {
var tempText string
@ -208,7 +187,7 @@ func traceIPv6(ch chan Result, i int, offset int) {
}
}
if model.EnableLoger {
Logger.Info(fmt.Sprintf("%s (%s) 追踪完成,最终结果: %s", model.Ipv6Names[i], model.Ipv6s[i], tempText))
Logger.Info(fmt.Sprintf("%s (%s) 追踪完成,结果: %s", model.Ipv6Names[i], model.Ipv6s[i], tempText))
}
ch <- Result{i + offset, tempText}
} else {

View File

@ -140,76 +140,3 @@ func tryAlternativeIPs(targetName string, ipVersion string) []string {
}
return nil
}
// mergeHops 合并多个hops结果
func mergeHops(allHops [][]*Hop) []*Hop {
if len(allHops) == 0 {
return nil
}
if len(allHops) == 1 {
return allHops[0]
}
// 找到最大长度
maxLen := 0
for _, hops := range allHops {
if len(hops) > maxLen {
maxLen = len(hops)
}
}
var mergedHops []*Hop
// 逐位置合并
for pos := 0; pos < maxLen; pos++ {
var availableHops []*Hop
// 收集当前位置所有可用的hop
for _, hops := range allHops {
if pos < len(hops) {
availableHops = append(availableHops, hops[pos])
}
}
if len(availableHops) == 0 {
continue
}
// 如果只有一个可用hop直接使用
if len(availableHops) == 1 {
mergedHops = append(mergedHops, availableHops[0])
continue
}
// 统计相同的hop通过比较第一个node的IP
hopCount := make(map[string][]*Hop)
for _, hop := range availableHops {
var key string
if len(hop.Nodes) > 0 && hop.Nodes[0].IP != nil {
key = hop.Nodes[0].IP.String()
} else {
key = "*" // 超时或无响应
}
if _, exists := hopCount[key]; !exists {
hopCount[key] = make([]*Hop, 0)
}
hopCount[key] = append(hopCount[key], hop)
}
// 按多数原则选择hop
if len(hopCount) == 1 {
// 所有hop都相同选择第一个
mergedHops = append(mergedHops, availableHops[0])
} else {
// 找出最多的hop类型
maxCount := 0
var majorityHops []*Hop
for _, hops := range hopCount {
if len(hops) > maxCount {
maxCount = len(hops)
majorityHops = hops
}
}
// 如果有多数派,使用多数派的第一个
if maxCount > 1 || len(hopCount) == 2 {
mergedHops = append(mergedHops, majorityHops[0])
} else {
// 三个都不同,按请求早晚顺序选择第一个
mergedHops = append(mergedHops, availableHops[0])
}
}
}
return mergedHops
}

View File

@ -1,5 +1,4 @@
package main
import (
"encoding/json"
"flag"
@ -7,16 +6,13 @@ import (
"net/http"
"os"
"runtime"
"sync"
"time"
"github.com/oneclickvirt/backtrace/bgptools"
backtrace "github.com/oneclickvirt/backtrace/bk"
"github.com/oneclickvirt/backtrace/model"
"github.com/oneclickvirt/backtrace/utils"
. "github.com/oneclickvirt/defaultset"
)
type IpInfo struct {
Ip string `json:"ip"`
City string `json:"city"`
@ -24,14 +20,6 @@ type IpInfo struct {
Country string `json:"country"`
Org string `json:"org"`
}
type ConcurrentResults struct {
bgpResult string
backtraceResult string
bgpError error
// backtraceError error
}
func main() {
go func() {
http.Get("https://hits.spiritlhl.net/backtrace.svg?action=hit&title=Hits&title_bg=%23555555&count_bg=%230eecf8&edge_flat=false")
@ -60,11 +48,11 @@ func main() {
if showIpInfo {
rsp, err := http.Get("http://ipinfo.io")
if err != nil {
fmt.Printf("get ip info err %v \n", err.Error())
fmt.Errorf("Get ip info err %v \n", err.Error())
} else {
err = json.NewDecoder(rsp.Body).Decode(&info)
if err != nil {
fmt.Printf("json decode err %v \n", err.Error())
fmt.Errorf("json decode err %v \n", err.Error())
} else {
fmt.Println(Green("国家: ") + White(info.Country) + Green(" 城市: ") + White(info.City) +
Green(" 服务商: ") + Blue(info.Org))
@ -72,32 +60,7 @@ func main() {
}
}
preCheck := utils.CheckPublicAccess(3 * time.Second)
if !preCheck.Connected {
fmt.Println(Red("PreCheck IP Type Failed"))
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
fmt.Println("Press Enter to exit...")
fmt.Scanln()
}
return
}
var useIPv6 bool
switch preCheck.StackType {
case "DualStack":
useIPv6 = ipv6
case "IPv4":
useIPv6 = false
case "IPv6":
useIPv6 = true
default:
fmt.Println(Red("PreCheck IP Type Failed"))
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
fmt.Println("Press Enter to exit...")
fmt.Scanln()
}
return
}
results := ConcurrentResults{}
var wg sync.WaitGroup
if preCheck.Connected {
var targetIP string
if specifiedIP != "" {
targetIP = specifiedIP
@ -105,34 +68,20 @@ func main() {
targetIP = info.Ip
}
if targetIP != "" {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 2; i++ {
result, err := bgptools.GetPoPInfo(targetIP)
results.bgpError = err
if err == nil && result.Result != "" {
results.bgpResult = result.Result
return
}
if i == 0 {
time.Sleep(3 * time.Second)
if err == nil {
fmt.Print(result.Result)
}
}
}()
}
wg.Add(1)
go func() {
defer wg.Done()
result := backtrace.BackTrace(useIPv6)
results.backtraceResult = result
}()
wg.Wait()
if results.bgpResult != "" {
fmt.Print(results.bgpResult)
}
if results.backtraceResult != "" {
fmt.Printf("%s\n", results.backtraceResult)
if preCheck.Connected && preCheck.StackType == "DualStack" {
backtrace.BackTrace(ipv6)
} else if preCheck.Connected && preCheck.StackType == "IPv4" {
backtrace.BackTrace(false)
} else if preCheck.Connected && preCheck.StackType == "IPv6" {
backtrace.BackTrace(true)
} else {
fmt.Println(Red("PreCheck IP Type Failed"))
}
fmt.Println(Yellow("准确线路自行查看详细路由,本测试结果仅作参考"))
fmt.Println(Yellow("同一目标地址多个线路时,检测可能已越过汇聚层,除第一个线路外,后续信息可能无效"))

View File

@ -2,7 +2,7 @@ package model
import "time"
const BackTraceVersion = "v0.0.7"
const BackTraceVersion = "v0.0.6"
var EnableLoger = false