diff --git a/README.md b/README.md index fd0579e..f07727e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - [x] 支持对```CMIN2```和```CMI```线路的判断 - [x] 支持对整个回程路由进行线路分析,一个目标IP可能会分析出多种线路 - [x] 支持对主流接入点的线路检测,方便分析国际互联能力 +- [x] 多次并发路由检测以分析平均多次路由,避免单次路由因网络波动未能准确检测 - [x] 增加对全平台的编译支持,原版[backtrace](https://github.com/zhanghanyun/backtrace)仅支持linux平台的amd64和arm64架构 - [x] 兼容额外的ICMP地址获取,若当前目标IP无法查询路由尝试额外的IP地址 diff --git a/bk/backtrace.go b/bk/backtrace.go index d4f20be..31e16d8 100644 --- a/bk/backtrace.go +++ b/bk/backtrace.go @@ -1,13 +1,13 @@ package backtrace import ( - "fmt" + "strings" "time" "github.com/oneclickvirt/backtrace/model" ) -func BackTrace(enableIpv6 bool) { +func BackTrace(enableIpv6 bool) string { if model.CachedIcmpData == "" || model.ParsedIcmpTargets == nil || time.Since(model.CachedIcmpDataFetchTime) > time.Hour { model.CachedIcmpData = getData(model.IcmpTargets) model.CachedIcmpDataFetchTime = time.Now() @@ -15,6 +15,7 @@ func BackTrace(enableIpv6 bool) { model.ParsedIcmpTargets = parseIcmpTargets(model.CachedIcmpData) } } + var builder strings.Builder if enableIpv6 { ipv4Count := len(model.Ipv4s) ipv6Count := len(model.Ipv6s) @@ -39,14 +40,18 @@ func BackTrace(enableIpv6 bool) { break loopIPv4v6 } } + // 收集 IPv4 结果 for i := 0; i < ipv4Count; i++ { if s[i] != "" { - fmt.Println(s[i]) + builder.WriteString(s[i]) + builder.WriteString("\n") } } + // 收集 IPv6 结果 for i := ipv4Count; i < totalCount; i++ { if s[i] != "" { - fmt.Println(s[i]) + builder.WriteString(s[i]) + builder.WriteString("\n") } } } else { @@ -68,10 +73,15 @@ func BackTrace(enableIpv6 bool) { break loopIPv4 } } + // 收集结果 for _, r := range s { if r != "" { - fmt.Println(r) + builder.WriteString(r) + builder.WriteString("\n") } } } + // 返回完整结果,去掉末尾的换行符 + result := builder.String() + return strings.TrimSuffix(result, "\n") } diff --git a/bk/trace_common.go b/bk/trace_common.go index b958d34..32a8299 100644 --- a/bk/trace_common.go +++ b/bk/trace_common.go @@ -194,14 +194,15 @@ func (t *Tracer) serveData(from net.IP, b []byte) error { Logger.Info(fmt.Sprintf("收到IPv6 ICMP消息: 类型=%v, 代码=%v", msg.Type, msg.Code)) } // 处理不同类型的ICMP消息 - if msg.Type == ipv6.ICMPTypeEchoReply { + switch msg.Type { + case 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()}) } - } else if msg.Type == ipv6.ICMPTypeTimeExceeded { + case ipv6.ICMPTypeTimeExceeded: b = getReplyData(msg) if len(b) < ipv6.HeaderLen { if model.EnableLoger { diff --git a/bk/trace_ipv4.go b/bk/trace_ipv4.go index 4bd1ed7..e899663 100644 --- a/bk/trace_ipv4.go +++ b/bk/trace_ipv4.go @@ -62,46 +62,56 @@ func trace(ch chan Result, i int) { defer Logger.Sync() Logger.Info(fmt.Sprintf("开始追踪 %s (%s)", 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地址")) + var allHops [][]*Hop + var successfulTraces int + // 尝试3次trace + for attempt := 1; attempt <= 3; attempt++ { if model.EnableLoger { - Logger.Error(fmt.Sprintf("追踪 %s (%s) 失败: %v", model.Ipv4Names[i], model.Ipv4s[i], err)) + Logger.Info(fmt.Sprintf("第%d次尝试追踪 %s (%s)", attempt, model.Ipv4Names[i], model.Ipv4s[i])) } - 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("尝试备选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 { + // 先尝试原始IP地址 + hops, err := Trace(net.ParseIP(model.Ipv4s[i])) + if err != nil { + if model.EnableLoger { + Logger.Warn(fmt.Sprintf("第%d次追踪 %s (%s) 失败: %v", attempt, model.Ipv4Names[i], model.Ipv4s[i], err)) + } + // 如果原始IP失败,尝试备选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", attempt, altIP, model.Ipv4Names[i])) + } + hops, err = Trace(net.ParseIP(altIP)) + if err == nil && len(hops) > 0 { break // 成功找到可用IP } } } } + if err == nil && len(hops) > 0 { + allHops = append(allHops, hops) + successfulTraces++ + if model.EnableLoger { + Logger.Info(fmt.Sprintf("第%d次追踪 %s (%s) 成功,获得%d个hop", attempt, model.Ipv4Names[i], model.Ipv4s[i], len(hops))) + } + } } - 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)) - // } - // } - // } + // 如果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) // 处理不同线路 if len(asns) > 0 { var tempText string @@ -167,18 +177,16 @@ 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])) } diff --git a/bk/trace_ipv6.go b/bk/trace_ipv6.go index 81915c3..682f034 100644 --- a/bk/trace_ipv6.go +++ b/bk/trace_ipv6.go @@ -11,7 +11,7 @@ import ( "golang.org/x/net/ipv6" ) -func newPacketV6(id uint16, dst net.IP, ttl int) []byte { +func newPacketV6(id uint16, _ net.IP, _ int) []byte { // 使用ipv6包的Echo请求 msg := icmp.Message{ Type: ipv6.ICMPTypeEchoRequest, @@ -77,46 +77,56 @@ func traceIPv6(ch chan Result, i int, offset int) { defer Logger.Sync() Logger.Info(fmt.Sprintf("开始追踪 %s (%s)", 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地址")) + var allHops [][]*Hop + var successfulTraces int + // 尝试3次trace + for attempt := 1; attempt <= 3; attempt++ { if model.EnableLoger { - Logger.Warn(fmt.Sprintf("%s (%s) 检测不到回程路由节点的IP地址", model.Ipv6Names[i], model.Ipv6s[i])) + Logger.Info(fmt.Sprintf("第%d次尝试追踪 %s (%s)", attempt, model.Ipv6Names[i], model.Ipv6s[i])) } - 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("尝试备选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 { + // 先尝试原始IP地址 + hops, err := Trace(net.ParseIP(model.Ipv6s[i])) + if err != nil { + if model.EnableLoger { + Logger.Warn(fmt.Sprintf("第%d次追踪 %s (%s) 失败: %v", attempt, model.Ipv6Names[i], model.Ipv6s[i], err)) + } + // 如果原始IP失败,尝试备选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", attempt, altIP, model.Ipv6Names[i])) + } + hops, err = Trace(net.ParseIP(altIP)) + if err == nil && len(hops) > 0 { break // 成功找到可用IP } } } } + if err == nil && len(hops) > 0 { + allHops = append(allHops, hops) + successfulTraces++ + if model.EnableLoger { + Logger.Info(fmt.Sprintf("第%d次追踪 %s (%s) 成功,获得%d个hop", attempt, model.Ipv6Names[i], model.Ipv6s[i], len(hops))) + } + } } - 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)) - // } - // } - // } + // 如果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) // 处理不同线路 if len(asns) > 0 { var tempText string @@ -187,7 +197,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 { diff --git a/bk/utils.go b/bk/utils.go index da6640f..37f4be4 100644 --- a/bk/utils.go +++ b/bk/utils.go @@ -140,3 +140,76 @@ 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 +} diff --git a/cmd/main.go b/cmd/main.go index 1efc9bc..2522538 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,4 +1,5 @@ package main + import ( "encoding/json" "flag" @@ -7,12 +8,14 @@ import ( "os" "runtime" "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"` @@ -20,6 +23,7 @@ type IpInfo struct { Country string `json:"country"` Org string `json:"org"` } + 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") @@ -48,11 +52,11 @@ func main() { if showIpInfo { rsp, err := http.Get("http://ipinfo.io") if err != nil { - fmt.Errorf("Get ip info err %v \n", err.Error()) + fmt.Printf("get ip info err %v \n", err.Error()) } else { err = json.NewDecoder(rsp.Body).Decode(&info) if err != nil { - fmt.Errorf("json decode err %v \n", err.Error()) + fmt.Printf("json decode err %v \n", err.Error()) } else { fmt.Println(Green("国家: ") + White(info.Country) + Green(" 城市: ") + White(info.City) + Green(" 服务商: ") + Blue(info.Org)) @@ -70,16 +74,19 @@ func main() { if targetIP != "" { result, err := bgptools.GetPoPInfo(targetIP) if err == nil { - fmt.Print(result.Result) + fmt.Print(result.Result) } } } if preCheck.Connected && preCheck.StackType == "DualStack" { - backtrace.BackTrace(ipv6) + result := backtrace.BackTrace(ipv6) + fmt.Printf("%s\n", result) } else if preCheck.Connected && preCheck.StackType == "IPv4" { - backtrace.BackTrace(false) + result := backtrace.BackTrace(false) + fmt.Printf("%s\n", result) } else if preCheck.Connected && preCheck.StackType == "IPv6" { - backtrace.BackTrace(true) + result := backtrace.BackTrace(true) + fmt.Printf("%s\n", result) } else { fmt.Println(Red("PreCheck IP Type Failed")) }