Compare commits

...

2 Commits

Author SHA1 Message Date
github-actions[bot]
d6bd866d4b chore: 更新多个 ASN 的 IPv6 前缀
Some checks failed
Sync ASN metadata / sync (push) Has been cancelled
2026-08-07 02:46:17 +00:00
spiritlhl
28e6e39718 fix: restore classic route labels 2026-08-07 10:19:13 +08:00
5 changed files with 187 additions and 16 deletions

View File

@ -13,12 +13,39 @@
2408:8000:10fe::/48 2408:8000:10fe::/48
2408:8000:10ff::/48 2408:8000:10ff::/48
2408:8000:2000::/36 2408:8000:2000::/36
2408:8000:2009::/48
2408:8000:200e::/48
2408:8000:2::/48 2408:8000:2::/48
2408:8000:3000::/36 2408:8000:3000::/36
2408:8000:3::/48 2408:8000:3::/48
2408:8000:4000::/36 2408:8000:4000::/36
2408:8000:4000::/48
2408:8000:4002::/48
2408:8000:4003::/48
2408:8000:4004::/48
2408:8000:4005::/48
2408:8000:4006::/48
2408:8000:4007::/48
2408:8000:4008::/48
2408:8000:4009::/48
2408:8000:400a::/48
2408:8000:400b::/48
2408:8000:5000::/36 2408:8000:5000::/36
2408:8000:6000::/36 2408:8000:6000::/36
2408:8000:6001::/48
2408:8000:6002::/48
2408:8000:6003::/48
2408:8000:6004::/48
2408:8000:6005::/48
2408:8000:6006::/48
2408:8000:6007::/48
2408:8000:6008::/48
2408:8000:6009::/48
2408:8000:600a::/48
2408:8000:600b::/48
2408:8000:600c::/48
2408:8000:600d::/48
2408:8000:600e::/48
2408:8000:7000::/36 2408:8000:7000::/36
2408:8000:8000::/36 2408:8000:8000::/36
2408:8000:8010::/47 2408:8000:8010::/47
@ -28,6 +55,10 @@
2408:8000:b000::/36 2408:8000:b000::/36
2408:8000:c000::/36 2408:8000:c000::/36
2408:8000:d000::/36 2408:8000:d000::/36
2408:8000:d101::/48
2408:8000:d201::/48
2408:8000:d501::/48
2408:8000:da01::/48
2408:8000:e000::/36 2408:8000:e000::/36
2408:8000:eeee::/48 2408:8000:eeee::/48
2408:8000:f000::/36 2408:8000:f000::/36
@ -68,9 +99,11 @@
2408:8001::/36 2408:8001::/36
2408:8001:a000::/36 2408:8001:a000::/36
2408:8001:b000::/36 2408:8001:b000::/36
2408:8001:b063::/48
2408:8001:c000::/36 2408:8001:c000::/36
2408:8001:c000::/48 2408:8001:c000::/48
2408:8001:d000::/36 2408:8001:d000::/36
2408:8001:d000::/48
2408:8001:e000::/36 2408:8001:e000::/36
2408:8001:f000::/36 2408:8001:f000::/36
2408:802a:8000::/33 2408:802a:8000::/33
@ -395,7 +428,18 @@
2408:8738::/32 2408:8738::/32
2408:873c::/32 2408:873c::/32
2408:873d::/32 2408:873d::/32
2408:8740:1fc0::/48
2408:8740:2fc0::/48
2408:8740:3fc0::/48
2408:8740:4fc0::/48
2408:8740:5fc0::/48
2408:8740:6fc0::/48
2408:8740:7fc0::/48
2408:8740:8fc0::/48
2408:8740:9fc0::/48
2408:8740::/32 2408:8740::/32
2408:8740:afc0::/48
2408:8740:bfc0::/48
2408:8742::/32 2408:8742::/32
2408:8744::/30 2408:8744::/30
2408:8748::/32 2408:8748::/32

View File

@ -1,7 +1,7 @@
{ {
"schema": "backtrace.asn-prefixes/v1", "schema": "backtrace.asn-prefixes/v1",
"file": "as4837.txt", "file": "as4837.txt",
"count": 501, "count": 545,
"sha256": "ba29c098dbf8c87ab118cb206b59fa6bd093dbf03ccaf3342959766a8adf6b48", "sha256": "d1340a73e12014cb103b3e6017d8132b71fe8ed145854164d443bd72359d4722",
"generated_at": "2026-08-06T02:45:13Z" "generated_at": "2026-08-07T02:46:17Z"
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset" . "github.com/oneclickvirt/defaultset"
) )
@ -13,22 +14,12 @@ import (
func RenderRouteReport(report RouteReport) string { func RenderRouteReport(report RouteReport) string {
var builder strings.Builder var builder strings.Builder
for _, target := range report.Targets { for _, target := range report.Targets {
label := target.Classification.Label
if label == "" {
label = "线路证据不足"
}
var rendered string var rendered string
switch { switch {
case target.Status != RouteProbeAvailable: case target.Status != RouteProbeAvailable:
rendered = Red("检测不到回程路由节点的IP地址") rendered = Red("检测不到回程路由节点的IP地址")
case target.Classification.Confidence == routeConfidenceInconclusive:
rendered = Red(label)
case target.Classification.Rank >= 4:
rendered = DarkGreen(label)
case target.Classification.Rank == 3:
rendered = Green(label)
default: default:
rendered = White(label) rendered = renderLegacyRouteLabels(target)
} }
addressWidth := 15 addressWidth := 15
if target.Target.IPVersion == "v6" { if target.Target.IPVersion == "v6" {
@ -38,3 +29,89 @@ func RenderRouteReport(report RouteReport) string {
} }
return strings.TrimSuffix(builder.String(), "\n") return strings.TrimSuffix(builder.String(), "\n")
} }
// renderLegacyRouteLabels keeps the original terminal contract: every known
// carrier ASN observed in the trace remains visible. The conservative
// Classification is retained for JSON consumers, but its evidence-oriented
// fallback labels must not replace the classic human-readable route result.
func renderLegacyRouteLabels(target RouteTargetReport) string {
asns := uniqueStrings(target.ObservedASNs)
hasAS4134 := containsString(asns, "AS4134")
hasAS4809 := containsString(asns, "AS4809")
ordered := make([]string, 0, len(asns)+1)
if hasAS4809 {
if hasAS4134 {
ordered = append(ordered, "AS4809b")
} else {
ordered = append(ordered, "AS4809a")
}
}
ordered = append(ordered, asns...)
seenLabels := make(map[string]struct{})
labels := make([]string, 0, len(ordered))
for _, asn := range ordered {
if asn == "AS4809" {
continue
}
label := model.M[asn]
if label == "" {
continue
}
if _, exists := seenLabels[label]; exists {
continue
}
seenLabels[label] = struct{}{}
switch asn {
case "AS9929", "AS4809a", "AS23764":
labels = append(labels, DarkGreen(label))
case "AS4809b", "AS58807":
labels = append(labels, Green(label))
default:
labels = append(labels, White(label))
}
}
if len(labels) > 0 {
return strings.Join(labels, " ")
}
// Reports produced by older API clients may contain only Classification.
if target.Classification.Confidence != routeConfidenceInconclusive && target.Classification.Label != "" {
switch {
case target.Classification.Rank >= 4:
return DarkGreen(target.Classification.Label)
case target.Classification.Rank == 3:
return Green(target.Classification.Label)
default:
return White(target.Classification.Label)
}
}
return Red("检测不到已知线路的ASN")
}
func uniqueStrings(values []string) []string {
seen := make(map[string]struct{}, len(values))
result := make([]string, 0, len(values))
for _, value := range values {
value = strings.ToUpper(strings.TrimSpace(value))
if value == "" {
continue
}
if _, exists := seen[value]; exists {
continue
}
seen[value] = struct{}{}
result = append(result, value)
}
return result
}
func containsString(values []string, target string) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}

View File

@ -39,7 +39,8 @@ func TestRunRouteReportOfflineFixture(t *testing.T) {
if target.Classification.Code != "ct_cn2_gia" || target.Latency.Samples != 6 || target.Latency.P95MS != 30 { if target.Classification.Code != "ct_cn2_gia" || target.Latency.Samples != 6 || target.Latency.P95MS != 30 {
t.Fatalf("unexpected route classification or latency: %+v", target) t.Fatalf("unexpected route classification or latency: %+v", target)
} }
if strings.Contains(RenderRouteReport(report), "P95") || !strings.Contains(RenderRouteReport(report), "电信CN2GIA") { rendered := RenderRouteReport(report)
if strings.Contains(rendered, "P95") || !strings.Contains(rendered, "电信CN2GT") || !strings.Contains(rendered, "电信163") {
t.Fatalf("legacy rendering is not compact: %q", RenderRouteReport(report)) t.Fatalf("legacy rendering is not compact: %q", RenderRouteReport(report))
} }
} }
@ -88,6 +89,55 @@ func TestRenderRouteReportPreservesLegacyAddressWidths(t *testing.T) {
} }
} }
func TestRenderRouteReportUsesClassicASNLabels(t *testing.T) {
report := RouteReport{Targets: []RouteTargetReport{
{
Target: RouteTarget{Name: "北京电信v4", Address: "219.141.140.10", IPVersion: "v4", Carrier: "CT"},
Status: RouteProbeAvailable,
ObservedASNs: []string{"AS6453", "AS4134"},
Classification: inconclusiveClassification("ct_destination_only", "仅见电信目的网", "only one AS4134 hop"),
},
{
Target: RouteTarget{Name: "上海电信v4", Address: "202.96.209.133", IPVersion: "v4", Carrier: "CT"},
Status: RouteProbeAvailable,
ObservedASNs: []string{"AS4809", "AS4134"},
Classification: RouteClassification{Code: "ct_cn2_mixed", Label: "电信CN2混合 [优质线路]", Confidence: routeConfidenceMixed, Rank: 3},
},
{
Target: RouteTarget{Name: "广州联通v4", Address: "210.21.196.6", IPVersion: "v4", Carrier: "CU"},
Status: RouteProbeAvailable,
ObservedASNs: []string{"AS4837"},
Classification: inconclusiveClassification("cu_destination_only", "仅见联通目的网", "only one AS4837 hop"),
},
}}
rendered := regexp.MustCompile(`\x1b\[[0-9;]*m`).ReplaceAllString(RenderRouteReport(report), "")
for _, want := range []string{
"北京电信v4 219.141.140.10 电信163 [普通线路]",
"上海电信v4 202.96.209.133 电信CN2GT [优质线路] 电信163 [普通线路]",
"广州联通v4 210.21.196.6 联通4837 [普通线路]",
} {
if !strings.Contains(rendered, want) {
t.Fatalf("classic route label missing %q from %q", want, rendered)
}
}
if strings.Contains(rendered, "仅见") || strings.Contains(rendered, "未见") {
t.Fatalf("structured fallback label leaked into classic output: %q", rendered)
}
}
func TestRenderRouteReportUsesClassicUnknownMessage(t *testing.T) {
report := RouteReport{Targets: []RouteTargetReport{{
Target: RouteTarget{Name: "北京电信v4", Address: "219.141.140.10", IPVersion: "v4", Carrier: "CT"},
Status: RouteProbeAvailable,
ObservedASNs: []string{"AS6453"},
Classification: inconclusiveClassification("ct_unknown", "未见电信骨干", "known carrier ASNs are absent"),
}}}
rendered := regexp.MustCompile(`\x1b\[[0-9;]*m`).ReplaceAllString(RenderRouteReport(report), "")
if !strings.Contains(rendered, "检测不到已知线路的ASN") || strings.Contains(rendered, "未见电信骨干") {
t.Fatalf("unexpected classic unknown route output: %q", rendered)
}
}
func TestRunRouteReportHonorsContext(t *testing.T) { func TestRunRouteReportHonorsContext(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
cancel() cancel()

View File

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