Compare commits

...

14 Commits

Author SHA1 Message Date
github-actions[bot]
6ec85e5163 chore: 更新多个 ASN 的 IPv6 前缀 2026-08-13 02:09:16 +00:00
github-actions[bot]
618d53469b chore: 更新多个 ASN 的 IPv6 前缀 2026-08-12 02:06:59 +00:00
github-actions[bot]
1be6689d19 chore: 更新多个 ASN 的 IPv6 前缀 2026-08-11 01:54:31 +00:00
github-actions[bot]
5c9b5d7c76 chore: 更新多个 ASN 的 IPv6 前缀 2026-08-08 01:47:24 +00:00
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
spiritlhl
cf9cd92651 Merge remote-tracking branch 'origin/main'
Some checks are pending
Sync ASN metadata / sync (push) Waiting to run
2026-08-06 21:09:06 +08:00
spiritlhl
e668455b46 fix: hide remote registry endpoints 2026-08-06 21:08:55 +08:00
github-actions[bot]
b0050d1e2c chore: 更新多个 ASN 的 IPv6 前缀 2026-08-06 02:45:13 +00:00
github-actions[bot]
aabc46eaad chore: 更新多个 ASN 的 IPv6 前缀 2026-08-05 02:42:27 +00:00
github-actions[bot]
6aa60bd832 chore: 更新多个 ASN 的 IPv6 前缀 2026-08-04 02:44:45 +00:00
github-actions[bot]
3826f59662 chore: 更新多个 ASN 的 IPv6 前缀 2026-08-01 03:14:04 +00:00
spiritlhl
559880ac40
Merge pull request #3 from oneclickvirt/dependabot/github_actions/actions/setup-go-7
chore(deps): bump actions/setup-go from 6 to 7
2026-07-31 22:22:47 +08:00
dependabot[bot]
c68233c73e
chore(deps): bump actions/setup-go from 6 to 7
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6 to 7.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-23 03:23:48 +00:00
19 changed files with 335 additions and 94 deletions

View File

@ -25,7 +25,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
- name: 设置 Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version-file: go.mod

View File

@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v7
- name: Setup Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version-file: go.mod
check-latest: true

View File

@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version-file: go.mod
check-latest: true
@ -51,7 +51,7 @@ jobs:
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version-file: go.mod
check-latest: true

View File

@ -54,13 +54,13 @@ func TestStructuredReportErrorsDoNotExposeRemoteURLs(t *testing.T) {
if err != nil {
t.Fatal(err)
}
for _, forbidden := range []string{"token=", "key=", "secret", "userinfo@"} {
for _, forbidden := range []string{"private.example", "token=", "key=", "secret", "userinfo@", `"geofeed_urls"`, `"port43"`, `"server"`, `"url"`} {
if strings.Contains(string(encoded), forbidden) {
t.Fatalf("structured report leaked %q: %s", forbidden, encoded)
}
}
if report.RDAP == nil || len(report.RDAP.GeofeedURLs) != 1 || report.RDAP.GeofeedURLs[0] != "https://private.example/geofeed" || report.Geofeeds[0].URL != "https://private.example/geofeed" {
t.Fatalf("geofeed URL was not sanitized consistently: %+v", report)
if report.RDAP == nil || len(report.RDAP.GeofeedURLs) != 1 || report.Geofeeds[0].Status != ReportError {
t.Fatalf("internal geofeed processing was not preserved: %+v", report)
}
}
@ -95,3 +95,38 @@ func TestRemoteFetchErrorsDoNotExposeSourceURL(t *testing.T) {
})
}
}
func TestStructuredReportOmitsServiceLocationsButKeepsProbeEvidence(t *testing.T) {
report := IPBGPReport{
Status: ReportPartial,
RDAP: &RDAPRecord{
Port43: "whois.private.example:43",
GeofeedURLs: []string{"https://private.example/rdap-geofeed.csv"},
},
WHOIS: &WHOISRecord{
Server: "whois.private.example:43",
Status: ReportAvailable,
GeofeedURLs: []string{"https://private.example/whois-geofeed.csv"},
},
Geofeeds: []GeofeedResult{{
URL: "https://private.example/geofeed.csv",
Status: ReportAvailable,
HTTPStatus: http.StatusOK,
Bytes: 128,
}},
}
encoded, err := json.Marshal(report)
if err != nil {
t.Fatal(err)
}
for _, forbidden := range []string{"private.example", "whois.private", `"geofeed_urls"`, `"port43"`, `"server"`, `"url"`} {
if strings.Contains(string(encoded), forbidden) {
t.Fatalf("structured report leaked %q: %s", forbidden, encoded)
}
}
for _, expected := range []string{`"status":"available"`, `"http_status":200`, `"bytes":128`} {
if !strings.Contains(string(encoded), expected) {
t.Fatalf("structured report lost probe evidence %q: %s", expected, encoded)
}
}
}

View File

@ -36,9 +36,9 @@ type RDAPRecord struct {
Prefixes []string `json:"prefixes,omitempty"`
RegistrationDate *time.Time `json:"registration_date,omitempty"`
LastChangedDate *time.Time `json:"last_changed_date,omitempty"`
GeofeedURLs []string `json:"geofeed_urls,omitempty"`
GeofeedURLs []string `json:"-"`
Entities []RDAPEntity `json:"entities,omitempty"`
Port43 string `json:"port43,omitempty"`
Port43 string `json:"-"`
Status []string `json:"status,omitempty"`
Source string `json:"source"`
}

View File

@ -46,10 +46,10 @@ type RIRInfo struct {
Status ReportStatus `json:"status"`
}
// GeofeedResult contains an RDAP/WHOIS geofeed URL and, when requested, the
// bounded fetch result. The payload itself is not retained in the report.
// GeofeedResult contains the bounded fetch result. URL remains internal so
// reports expose the probe outcome without disclosing the service location.
type GeofeedResult struct {
URL string `json:"url"`
URL string `json:"-"`
Status ReportStatus `json:"status"`
HTTPStatus int `json:"http_status,omitempty"`
Bytes int64 `json:"bytes,omitempty"`
@ -59,11 +59,11 @@ type GeofeedResult struct {
// WHOISRecord is the small, structured subset used when RDAP is unavailable
// or missing required fields. Raw port-43 text is never returned.
type WHOISRecord struct {
Server string `json:"server"`
Server string `json:"-"`
Status ReportStatus `json:"status"`
Prefixes []string `json:"prefixes,omitempty"`
RegistrationDate *time.Time `json:"registration_date,omitempty"`
GeofeedURLs []string `json:"geofeed_urls,omitempty"`
GeofeedURLs []string `json:"-"`
RIR RIRInfo `json:"rir"`
}

View File

@ -53,9 +53,11 @@
240e:14::/31
240e:16:1001::/48
240e:16:1008::/48
240e:16:1009::/48
240e:184:c000::/34
240e:1:8000::/33
240e:1::/33
240e:1c7:c00::/40
240e:1c:112::/48
240e:1c::/31
240e:204::/30
@ -197,6 +199,7 @@
240e:470::/30
240e:474::/30
240e:478::/30
240e:47:4::/48
240e:47::/32
240e:47c::/30
240e:48:8000::/33
@ -207,6 +210,7 @@
240e:4a:8000::/33
240e:4a::/33
240e:4b:1::/48
240e:4b:2::/48
240e:4b:8000::/33
240e:4b::/33
240e:4c:4006::/48
@ -218,6 +222,7 @@
240e:4d::/33
240e:4e:4000::/48
240e:4e:8000::/33
240e:4e:800::/48
240e:4e::/33
240e:4f:8000::/33
240e:4f::/33
@ -365,6 +370,7 @@
240e:750::/29
240e:758::/29
240e:760::/29
240e:767:7180::/41
240e:768::/29
240e:770::/29
240e:778::/29

View File

@ -1,7 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as4134.txt",
"count": 629,
"sha256": "00941f76c022566166b926670ee9358084c53465cbaaf75a3da7b73d997d1947",
"generated_at": "2026-07-30T15:51:08Z"
"count": 635,
"sha256": "7c3ac225b46f7aa34446402b50d2f1977e942c2968e8b3e9a31960b0ec2b6a4c",
"generated_at": "2026-08-06T02:45:13Z"
}

View File

@ -74,6 +74,7 @@
240e:62c:1f0::/44
240e:62f::/48
240e:638:c301::/48
240e:638:c303::/48
240e:638:f::/48
240e:63c:3f00::/44
240e:640:178::/48
@ -113,10 +114,12 @@
240e:669:b000::/40
240e:670:d000::/36
240e:679:1001::/48
240e:67c:2a80::/48
240e:688:ed00::/48
240e:699:7a00::/48
240e:699:7a01::/48
240e:699:7b00::/48
240e:699:7b03::/48
240e:699:7b06::/48
240e:699:7b07::/48
240e:699:7b08::/48
@ -136,11 +139,14 @@
240e:713:f021::/48
240e:733:4c0::/48
240e:733:4c1::/48
240e:737:2ee0::/48
240e:767:f000::/48
240e:787:7000::/48
240e:787:7001::/48
240e:790:fe10::/48
240e:790:fe20::/48
240e:790:fe30::/48
240e:790:fe70::/48
240e:790:ff00::/40
240e:7b6::/31
240e:965:822::/48
@ -153,6 +159,8 @@
240e:ff:c024::/48
240e:ff:c027::/48
240e:ff:c029::/48
240e:ff:c032::/48
240e:ff:c034::/48
2605:9d80:9003::/48
2605:9d80:9013::/48
2605:9d80:9023::/48

View File

@ -1,7 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as4809.txt",
"count": 188,
"sha256": "a43638fa67e5057d237988233101ef0bfd4f5557001325289e0846af642670ae",
"generated_at": "2026-07-30T15:51:08Z"
"count": 196,
"sha256": "44427b38edfd4cbd6ebefedccc7af0bb2332f923472bc5e67764951fbe20441e",
"generated_at": "2026-08-12T02:06:59Z"
}

View File

@ -13,22 +13,81 @@
2408:8000:10fe::/48
2408:8000:10ff::/48
2408:8000:2000::/36
2408:8000:2009::/48
2408:8000:200e::/48
2408:8000:2::/48
2408:8000:3000::/36
2408:8000:3100::/48
2408:8000:3210::/48
2408:8000:3211::/48
2408:8000:3317::/48
2408:8000:3410::/48
2408:8000:3500::/48
2408:8000:3610::/48
2408:8000:3700::/48
2408:8000:3810::/48
2408:8000:3811::/48
2408:8000:3910::/48
2408:8000:3::/48
2408:8000:3a13::/48
2408:8000:3b00::/48
2408:8000:3c10::/48
2408:8000:3c11::/48
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: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:8000::/36
2408:8000:8010::/47
2408:8000:8010::/48
2408:8000:8040::/48
2408:8000:8090::/48
2408:8000:80c0::/48
2408:8000::/48
2408:8000:a000::/36
2408:8000:a002::/48
2408:8000:a00d::/48
2408:8000:b000::/36
2408:8000:c000::/36
2408:8000:c038::/48
2408:8000:c03b::/48
2408:8000:c03c::/48
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:eeee::/48
2408:8000:f000::/36
2408:8000:f000::/48
2408:8000:f100::/48
2408:8000:f200::/48
2408:8001:1000::/36
2408:8001:2000::/36
2408:8001:3002::/48
@ -54,18 +113,34 @@
2408:8001:3142::/48
2408:8001:3152::/48
2408:8001:4000::/36
2408:8001:4000::/48
2408:8001:4010::/48
2408:8001:4020::/48
2408:8001:5000::/36
2408:8001:5000::/48
2408:8001:6000::/36
2408:8001:7000::/36
2408:8001:7030::/48
2408:8001:8000::/36
2408:8001:9000::/36
2408:8001::/36
2408:8001:a000::/36
2408:8001:b000::/36
2408:8001:b013::/48
2408:8001:b033::/48
2408:8001:b043::/48
2408:8001:b063::/48
2408:8001:b083::/48
2408:8001:c000::/36
2408:8001:c000::/48
2408:8001:c001::/48
2408:8001:d000::/36
2408:8001:d000::/48
2408:8001:e000::/36
2408:8001:e001::/48
2408:8001:e003::/48
2408:8001:f000::/36
2408:8026:480::/48
2408:802a:8000::/33
2408:802c::/32
2408:803e::/32
@ -323,7 +398,23 @@
2408:8637::/32
2408:8638::/32
2408:8639::/32
2408:863c:2ef0::/48
2408:863c:3cf1::/48
2408:863c:40f1::/48
2408:863c:4cf1::/48
2408:863c:6ef1::/48
2408:863c:7ee0::/48
2408:863c:7ef0::/48
2408:863c:88e0::/48
2408:863c:9ce0::/48
2408:863c:9cf0::/48
2408:863c::/32
2408:863c:a6e1::/48
2408:863c:a6f1::/48
2408:863c:b2f0::/48
2408:863d:5ae1::/48
2408:863d:6ef1::/48
2408:863d:88f0::/48
2408:863d::/32
2408:8640::/32
2408:8642::/32
@ -342,7 +433,9 @@
2408:865f::/32
2408:8660::/32
2408:8662::/32
2408:8662::/48
2408:8663::/32
2408:8663::/48
2408:8664::/32
2408:8665::/32
2408:8666::/32
@ -386,7 +479,18 @@
2408:8738::/32
2408:873c::/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:afc0::/48
2408:8740:bfc0::/48
2408:8742::/32
2408:8744::/30
2408:8748::/32

View File

@ -1,7 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as4837.txt",
"count": 492,
"sha256": "40ece28148fc843764a9b1cec48d07013d6e6fdc8697b601af93a45093e1908c",
"generated_at": "2026-07-30T15:51:08Z"
"count": 596,
"sha256": "903d9b20db80f5696a3c124cda2c73072cd5d8acb992d9b55f369d8b435a65d0",
"generated_at": "2026-08-13T02:09:16Z"
}

View File

@ -167,6 +167,7 @@
2409:802e:2905::/48
2409:802e:2906::/48
2409:802e:2909::/48
2409:802e:290b::/48
2409:802e:290d::/48
2409:802e:2a05::/48
2409:802e:2a06::/48
@ -203,6 +204,7 @@
2409:802f:290e::/48
2409:802f:290f::/48
2409:802f:2913::/48
2409:802f:2914::/48
2409:802f:2915::/48
2409:802f:2916::/48
2409:802f:2a05::/48
@ -329,6 +331,7 @@
2409:804b:290c::/48
2409:804b:2910::/48
2409:804b:2911::/48
2409:804b:2912::/48
2409:804b:29ff::/48
2409:804b:2b05::/48
2409:804b:2b06::/48
@ -337,6 +340,7 @@
2409:804b:2b0b::/48
2409:804b:2b10::/48
2409:804b:2b11::/48
2409:804b:2b12::/48
2409:804b:2c06::/48
2409:804b:2c0b::/48
2409:804b:3f13::/48
@ -547,7 +551,9 @@
2409:805b:2b0d::/48
2409:805b:2b0e::/48
2409:805b:2b0f::/48
2409:805b:2b10::/48
2409:805b:2c04::/48
2409:805b:2c11::/48
2409:805b:3ff0::/48
2409:805c:1000::/40
2409:805c:11::/48
@ -696,16 +702,21 @@
2409:8069:290d::/48
2409:8069:290e::/48
2409:8069:290f::/48
2409:8069:2910::/48
2409:8069:2a00::/40
2409:8069:2a05::/48
2409:8069:2a07::/48
2409:8069:2a0b::/48
2409:8069:2a0e::/48
2409:8069:2a10::/48
2409:8069:2b00::/40
2409:8069:2b05::/48
2409:8069:2b07::/48
2409:8069:2b0b::/48
2409:8069:2b0c::/48
2409:8069:2b0e::/48
2409:8069:2b0f::/48
2409:8069:2b10::/48
2409:8069:2c00::/40
2409:8069:2c05::/48
2409:8069:2c06::/48
@ -1028,7 +1039,6 @@
2409:8087:7b00::/40
2409:8087:7c00::/40
2409:8087:7e00::/40
2409:8087:8000::/48
2409:8087:8001::/48
2409:8087:8003::/48
2409:8087:8004::/48
@ -1054,7 +1064,6 @@
2409:8087:801a::/48
2409:8087:801b::/48
2409:8087:8106::/48
2409:8089:1020::/48
2409:815c::/32
2409:8234::/32
2409:826c::/32
@ -4137,45 +4146,8 @@
2409:896a:fffd::/48
2409:896a:fffe::/48
2409:896b::/32
2409:896c:100::/42
2409:896c:1760::/43
2409:896c:1860::/43
2409:896c:1960::/43
2409:896c:1a60::/43
2409:896c:1b60::/43
2409:896c:1c60::/43
2409:896c:1d60::/43
2409:896c:1e60::/43
2409:896c:1f60::/43
2409:896c:200::/42
2409:896c:300::/42
2409:896c:3560::/43
2409:896c:3660::/43
2409:896c:3760::/43
2409:896c:3860::/43
2409:896c:3960::/43
2409:896c:3a60::/43
2409:896c:3b60::/43
2409:896c:3c60::/43
2409:896c:3d60::/43
2409:896c:3e60::/43
2409:896c:3f60::/43
2409:896c:400::/42
2409:896c:4060::/43
2409:896c:4160::/43
2409:896c:500::/42
2409:896c:600::/42
2409:896c:700::/42
2409:896c:800::/42
2409:896c:900::/42
2409:896c::/32
2409:896c::/42
2409:896c:a00::/42
2409:896c:b00::/42
2409:896c:c00::/42
2409:896c:d00::/42
2409:896d:1000::/40
2409:896d:100::/40
2409:896d:1100::/40
2409:896d:1200::/40
2409:896d:1300::/40
@ -4192,7 +4164,6 @@
2409:896d:1e00::/40
2409:896d:1f00::/40
2409:896d:2000::/40
2409:896d:200::/40
2409:896d:2100::/40
2409:896d:2200::/40
2409:896d:2300::/40
@ -4209,7 +4180,6 @@
2409:896d:2e00::/40
2409:896d:2f00::/40
2409:896d:3000::/40
2409:896d:300::/40
2409:896d:3100::/40
2409:896d:3200::/40
2409:896d:3300::/40
@ -4226,7 +4196,6 @@
2409:896d:3e00::/40
2409:896d:3f00::/40
2409:896d:4000::/40
2409:896d:400::/40
2409:896d:4100::/40
2409:896d:4200::/40
2409:896d:4300::/40
@ -4243,7 +4212,6 @@
2409:896d:4e00::/40
2409:896d:4f00::/40
2409:896d:5000::/40
2409:896d:500::/40
2409:896d:5100::/40
2409:896d:5200::/40
2409:896d:5300::/40
@ -4260,7 +4228,6 @@
2409:896d:5e00::/40
2409:896d:5f00::/40
2409:896d:6000::/40
2409:896d:600::/40
2409:896d:6100::/40
2409:896d:6200::/40
2409:896d:6300::/40
@ -4277,7 +4244,6 @@
2409:896d:6e00::/40
2409:896d:6f00::/40
2409:896d:7000::/40
2409:896d:700::/40
2409:896d:7100::/40
2409:896d:7200::/46
2409:896d:7204::/46
@ -4302,14 +4268,7 @@
2409:896d:7e00::/40
2409:896d:7f00::/40
2409:896d:8000::/40
2409:896d:800::/40
2409:896d:900::/40
2409:896d::/32
2409:896d::/40
2409:896d:a00::/40
2409:896d:b00::/40
2409:896d:c00::/40
2409:896d:d00::/40
2409:896d:e00::/40
2409:896d:f00::/40
2409:896d:fff7::/48
@ -6558,6 +6517,7 @@
2409:8c5e:a0::/48
2409:8c5f::/32
2409:8c60:1200::/40
2409:8c60:1e00::/40
2409:8c60:2400::/40
2409:8c60:2400::/48
2409:8c60:2500::/40

View File

@ -1,7 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as9808.txt",
"count": 7171,
"sha256": "ba623bf5d30f2c69340ffdd2c4d50bfc79506b00ebdbc8a2f0448b7ce21148e1",
"generated_at": "2026-07-30T15:51:08Z"
"count": 7131,
"sha256": "9b66caccde8e09b1e167de1d0e7a05b29b203c0ed7032361002fec052fe9d096",
"generated_at": "2026-08-08T01:47:24Z"
}

View File

@ -111,6 +111,7 @@
2408:864c:10ff::/48
2408:8651:ff00::/48
2408:8652:ff00::/48
2408:8656:22cf::/48
2408:8656:a52::/48
2408:865c:f0::/44
2408:8660:100::/48

View File

@ -1,7 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as9929.txt",
"count": 144,
"sha256": "a5f65f08a447982cd2c8611005261deab3400f1232ae594fb98f6bfb3fe09084",
"generated_at": "2026-07-20T19:12:54Z"
"count": 145,
"sha256": "0764b222d3f24c65be191b0300eaf9942416dda766bb9b70ec21c5836f9bcac9",
"generated_at": "2026-08-08T01:47:24Z"
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/oneclickvirt/backtrace/model"
. "github.com/oneclickvirt/defaultset"
)
@ -13,22 +14,12 @@ import (
func RenderRouteReport(report RouteReport) string {
var builder strings.Builder
for _, target := range report.Targets {
label := target.Classification.Label
if label == "" {
label = "线路证据不足"
}
var rendered string
switch {
case target.Status != RouteProbeAvailable:
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:
rendered = White(label)
rendered = renderLegacyRouteLabels(target)
}
addressWidth := 15
if target.Target.IPVersion == "v6" {
@ -38,3 +29,89 @@ func RenderRouteReport(report RouteReport) string {
}
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 {
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))
}
}
@ -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) {
ctx, cancel := context.WithCancel(context.Background())
cancel()

View File

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