feat: validate synchronized ASN prefix snapshots

This commit is contained in:
spiritlhl 2026-07-21 03:18:31 +08:00
parent a32a1067d3
commit 569833dfea
20 changed files with 8987 additions and 2983 deletions

View File

@ -37,7 +37,7 @@ jobs:
run: | run: |
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "github-actions[bot]@users.noreply.github.com"
git add bk/prefix/*.txt git add bk/prefix/*.txt bk/prefix/*.txt.manifest.json
if git diff --cached --quiet; then if git diff --cached --quiet; then
echo "无变更,跳过提交。" echo "无变更,跳过提交。"
else else

View File

@ -1,30 +1,28 @@
2400:9380:9002 2400:9380:9002::/48
2400:9380:9115 2400:9380:9115::/48
2400:9380:9116 2400:9380:9116::/48
2400:9380:9206 2400:9380:9206::/48
2400:9380:9262 2400:9380:a003::/48
2400:9380:a003 2400:9380:a00a::/48
2400:9380:a00a 2400:9380:a00c::/48
2400:9380:a00c 2400:9380:a014::/48
2400:9380:a014 2400:9380:a016::/48
2400:9380:a016 2400:9380:a01f::/48
2400:9380:a01f 2400:9380:a022::/48
2400:9380:a022 2400:9380:a028::/48
2400:9380:a026 2400:9380:a042::/48
2400:9380:a028 2400:9380:a110::/48
2400:9380:a042 240e:97d:8000::/48
2400:9380:a110 2804:1e48:9003::/48
240e:97d:8000 2a04:f580:9001::/48
2804:1e48:9003 2a04:f580:9090::/48
2a04:f580:9001 2a04:f581:110b::/48
2a04:f580:9090 2a04:f581:a123::/48
2a04:f581:110b 2a04:f581:a125::/48
2a04:f581:a123 2c0f:f7a8:1::/48
2a04:f581:a125 2c0f:f7a8:24::/48
2c0f:f7a8:1 2c0f:f7a8:29::/48
2c0f:f7a8:2 2c0f:f7a8:2::/48
2c0f:f7a8:24 2c0f:f7a8:37::/48
2c0f:f7a8:29 2c0f:f7a8:47::/48
2c0f:f7a8:37 2c0f:f7a8:9011::/48
2c0f:f7a8:47
2c0f:f7a8:9011

View File

@ -0,0 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as23764.txt",
"count": 28,
"sha256": "d853ef48792875af80682ebfb9817bbbcfefd0f904cf057568664d3bdb9373bb",
"generated_at": "2026-07-20T19:12:53Z"
}

View File

@ -1,256 +1,628 @@
2400:9380:8001 2400:9380:8001::/48
2400:9380:8003 2400:9380:8003::/48
2400:9380:8021 2400:9380:8021::/48
2400:9380:8040 2400:9380:8040::/48
2400:9380:8140 2400:9380:8140::/48
2400:9380:8201 2400:9380:8201::/48
2400:9380:8301 2400:9380:8301::/48
240e 240e:100:8000::/34
240e:1 240e:100:c000::/34
240e:100 240e:101::/34
240e:101 240e:103:c000::/34
240e:103 240e:104:4000::/36
240e:104 240e:104:7000::/36
240e:106 240e:104:c000::/34
240e:108 240e:106:4000::/34
240e:11 240e:108:1100::/44
240e:11:8001 240e:108:1110::/44
240e:12 240e:108:1120::/44
240e:144 240e:108:1130::/44
240e:16:1001 240e:108:1140::/44
240e:16:1008 240e:108:1150::/44
240e:16:1009 240e:108:1160::/44
240e:184 240e:108:1180::/44
240e:1a 240e:108:1190::/44
240e:1c7 240e:108:11a0::/44
240e:1c:112 240e:108:11b0::/44
240e:2 240e:108:11c0::/44
240e:218 240e:108:11d0::/44
240e:219 240e:108:11e0::/44
240e:21a 240e:108:1200::/44
240e:21b 240e:108:1210::/44
240e:224 240e:108:12a0::/44
240e:225 240e:108:12c0::/44
240e:226 240e:108:12d0::/44
240e:227 240e:108:1300::/44
240e:24 240e:108:1310::/44
240e:240 240e:108:1320::/44
240e:241 240e:108:1330::/44
240e:242 240e:108:1340::/44
240e:243 240e:108:1350::/44
240e:244 240e:108:1360::/44
240e:245 240e:108:1370::/44
240e:246 240e:108:1380::/44
240e:247 240e:108:1390::/44
240e:2c 240e:108:13a0::/44
240e:318 240e:108:13b0::/44
240e:319 240e:108:13c0::/44
240e:31a 240e:108:13d0::/44
240e:31b 240e:108:13e0::/44
240e:324 240e:11:8001::/48
240e:325 240e:12::/32
240e:326 240e:144:c000::/34
240e:327 240e:14::/31
240e:340 240e:16:1001::/48
240e:341 240e:16:1008::/48
240e:342 240e:184:c000::/34
240e:343 240e:1:8000::/33
240e:350:205 240e:1::/33
240e:41 240e:1c:112::/48
240e:418 240e:1c::/31
240e:419 240e:204::/30
240e:41a 240e:208::/30
240e:41b 240e:20c::/30
240e:42 240e:210::/30
240e:424 240e:214::/30
240e:425 240e:218::/32
240e:426 240e:219::/32
240e:427 240e:21a::/32
240e:42:4000 240e:21b::/32
240e:43 240e:21c::/30
240e:43:8000 240e:220::/30
240e:44 240e:224::/32
240e:440 240e:225::/32
240e:441 240e:226::/32
240e:442 240e:227::/32
240e:443 240e:22c::/30
240e:444 240e:230::/30
240e:445 240e:234::/30
240e:446 240e:238::/30
240e:447 240e:23c::/30
240e:45 240e:240::/32
240e:45:8000 240e:241::/32
240e:46 240e:242::/32
240e:46:5008 240e:243::/32
240e:47 240e:244::/32
240e:47:4 240e:245::/32
240e:48 240e:246::/32
240e:49 240e:247::/32
240e:4a 240e:248::/30
240e:4a:4100 240e:24:8000::/34
240e:4b 240e:24c::/30
240e:4b:1 240e:250::/30
240e:4b:2 240e:254::/30
240e:4c 240e:258::/30
240e:4c:4006 240e:25c::/30
240e:4d 240e:260::/30
240e:4d:50ff 240e:264::/30
240e:4e 240e:268::/30
240e:4e:4000 240e:26c::/30
240e:4f 240e:270::/30
240e:50 240e:274::/30
240e:51 240e:278::/30
240e:518 240e:27c::/30
240e:519 240e:2::/33
240e:51a 240e:2c:8000::/34
240e:51b 240e:304::/30
240e:52 240e:308::/30
240e:524 240e:30c::/30
240e:525 240e:310::/30
240e:526 240e:314::/30
240e:527 240e:318::/32
240e:52:4802 240e:319::/32
240e:53 240e:31a::/32
240e:54 240e:31b::/32
240e:540 240e:31c::/30
240e:541 240e:320::/30
240e:542 240e:324::/32
240e:543 240e:325::/32
240e:544 240e:326::/32
240e:545 240e:327::/32
240e:546 240e:32c::/30
240e:547 240e:330::/30
240e:55 240e:334::/30
240e:56 240e:338::/30
240e:56:4807 240e:33c::/30
240e:57 240e:340::/32
240e:5a 240e:341::/32
240e:5a:6800 240e:342::/32
240e:5b 240e:343::/32
240e:5c 240e:344::/30
240e:5d 240e:348::/30
240e:5e 240e:34c::/30
240e:5f 240e:350:205::/48
240e:618 240e:350::/29
240e:619 240e:358::/29
240e:61a 240e:360::/29
240e:61b 240e:368::/29
240e:61d:1401 240e:370::/29
240e:624 240e:378::/29
240e:625 240e:380::/29
240e:626 240e:388::/29
240e:627 240e:390::/29
240e:630:b83 240e:398::/29
240e:638 240e:3a0::/28
240e:638:b00 240e:3b0::/28
240e:638:b02 240e:404::/30
240e:64 240e:408::/30
240e:640 240e:414::/30
240e:641 240e:418::/32
240e:642 240e:419::/32
240e:643 240e:41:8000::/33
240e:64c:340 240e:41::/33
240e:650:2540 240e:41a::/32
240e:650:280 240e:41b::/32
240e:650:4400 240e:41c::/30
240e:658:6c20 240e:420::/30
240e:658:6c31 240e:424::/32
240e:658:a00 240e:425::/32
240e:659:1160 240e:426::/32
240e:659:11e0 240e:427::/32
240e:659:1b0 240e:42:4000::/48
240e:661:6180 240e:42:8000::/33
240e:699 240e:42::/33
240e:6a0 240e:42c::/30
240e:6a0:10 240e:430::/30
240e:6a0:100 240e:434::/30
240e:6a0:105 240e:438::/30
240e:6a0:b0c 240e:43:8000::/33
240e:7 240e:43:8000::/48
240e:718 240e:43::/33
240e:719 240e:43c::/30
240e:71a 240e:440::/32
240e:71b 240e:441::/32
240e:724 240e:442::/32
240e:725 240e:443::/32
240e:726 240e:444::/32
240e:727 240e:445::/32
240e:740 240e:446::/32
240e:741 240e:447::/32
240e:742 240e:448::/30
240e:743 240e:44:8000::/33
240e:767 240e:44::/33
240e:840 240e:44c::/30
240e:841 240e:450::/30
240e:9 240e:454::/30
240e:918 240e:458::/30
240e:919 240e:45:8000::/33
240e:91a 240e:45:8000::/48
240e:91b 240e:45c::/30
240e:924 240e:460::/30
240e:925 240e:464::/30
240e:926 240e:468::/30
240e:927 240e:46:5008::/48
240e:940 240e:46:8000::/33
240e:941 240e:46::/33
240e:942 240e:46c::/30
240e:943 240e:470::/30
240e:944 240e:474::/30
240e:945 240e:478::/30
240e:946 240e:47::/32
240e:947 240e:47c::/30
240e:980 240e:48:8000::/33
240e:981 240e:48::/33
240e:982 240e:49:8000::/33
240e:983 240e:49::/33
240e:9f:8340 240e:4a:4100::/48
240e:9f:8400 240e:4a:8000::/33
240e:9f:8440 240e:4a::/33
240e:9f:8880 240e:4b:1::/48
240e:9f:88c0 240e:4b:8000::/33
240e:a0c 240e:4b::/33
240e:a0d 240e:4c:4006::/48
240e:a0e 240e:4c:8000::/33
240e:a0f 240e:4c::/33
240e:a18 240e:4d:50ff::/48
240e:a19 240e:4d:8000::/33
240e:a1a 240e:4d::/33
240e:a1b 240e:4e:4000::/48
240e:a24 240e:4e:8000::/33
240e:a25 240e:4e::/33
240e:a26 240e:4f:8000::/33
240e:a27 240e:4f::/33
240e:a40 240e:504::/30
240e:a41 240e:508::/30
240e:a42 240e:50:8000::/33
240e:a43 240e:50::/33
240e:a44 240e:514::/30
240e:a45 240e:518::/32
240e:a46 240e:519::/32
240e:a47 240e:51::/32
240e:a5:8000 240e:51a::/32
240e:a5:a100 240e:51b::/32
240e:a5:a140 240e:51c::/30
240e:a9:8010 240e:520::/30
240e:ab:b202 240e:524::/32
240e:b 240e:525::/32
240e:bd:8000 240e:526::/32
240e:cd:8000 240e:527::/32
2605:9d80:8001 240e:52:4802::/48
2605:9d80:8011 240e:52:8000::/33
2605:9d80:8021 240e:52::/33
2605:9d80:8031 240e:52c::/30
2605:9d80:8041 240e:530::/30
2605:9d80:8081 240e:534::/30
2a04:f580:8010 240e:538::/30
2a04:f580:8011 240e:53:8000::/33
2a04:f580:8090 240e:53::/33
2a04:f580:8210 240e:53c::/30
2a04:f580:8211 240e:540::/32
2a04:f580:8290 240e:541::/32
2c0f:f7a8:8011 240e:542::/32
2c0f:f7a8:8050 240e:543::/32
2c0f:f7a8:805f 240e:544::/32
2c0f:f7a8:8150 240e:545::/32
2c0f:f7a8:815f 240e:546::/32
2c0f:f7a8:8211 240e:547::/32
240e:548::/30
240e:54:8000::/33
240e:54::/33
240e:54c::/30
240e:550::/30
240e:554::/30
240e:558::/30
240e:55:8000::/33
240e:55::/33
240e:55c::/30
240e:560::/30
240e:564::/30
240e:568::/30
240e:56:4807::/48
240e:56:8000::/33
240e:56::/33
240e:56c::/30
240e:570::/30
240e:574::/30
240e:578::/30
240e:57:8000::/33
240e:57::/33
240e:57c::/30
240e:5a:6800::/48
240e:5a:8000::/33
240e:5a::/33
240e:5b:8000::/33
240e:5b::/33
240e:5c:8000::/33
240e:5c::/33
240e:5d:8000::/33
240e:5d::/33
240e:5e:8000::/33
240e:5e::/33
240e:5f:8000::/33
240e:5f::/33
240e:604::/30
240e:608::/30
240e:60::/30
240e:60c::/30
240e:610::/30
240e:614::/30
240e:618::/32
240e:619::/32
240e:61a::/32
240e:61b::/32
240e:61c::/30
240e:61d:1401::/48
240e:620::/30
240e:624::/32
240e:625::/32
240e:626::/32
240e:627::/32
240e:62c::/30
240e:630::/30
240e:634::/30
240e:638::/30
240e:638::/48
240e:63c::/30
240e:640::/32
240e:641::/32
240e:642::/32
240e:643::/32
240e:644::/30
240e:648::/30
240e:64::/32
240e:64c::/30
240e:650::/29
240e:658::/29
240e:659:1160::/48
240e:660::/29
240e:668::/29
240e:670::/29
240e:678::/29
240e:680::/29
240e:688::/29
240e:690::/29
240e:698::/29
240e:6a0::/28
240e:6b0::/28
240e:704::/30
240e:708::/30
240e:70c::/30
240e:710::/30
240e:714::/30
240e:718::/32
240e:719::/32
240e:71a::/32
240e:71b::/32
240e:71c::/30
240e:720::/30
240e:724::/32
240e:725::/32
240e:726::/32
240e:727::/32
240e:72c::/30
240e:730::/30
240e:734::/30
240e:738::/30
240e:73c::/30
240e:740::/32
240e:741::/32
240e:742::/32
240e:743::/32
240e:744::/30
240e:748::/30
240e:74c::/30
240e:750::/29
240e:758::/29
240e:760::/29
240e:768::/29
240e:770::/29
240e:778::/29
240e:780::/29
240e:788::/29
240e:790::/29
240e:798::/29
240e:7::/32
240e:7a0::/28
240e:7b0::/28
240e:804::/30
240e:814::/30
240e:81c::/30
240e:820::/30
240e:82c::/30
240e:830::/30
240e:834::/30
240e:838::/30
240e:83c::/30
240e:840::/32
240e:841::/32
240e:848::/30
240e:84::/31
240e:84c::/30
240e:850::/30
240e:854::/30
240e:858::/30
240e:85c::/30
240e:860::/30
240e:864::/30
240e:868::/30
240e:86::/31
240e:86c::/30
240e:870::/30
240e:874::/30
240e:878::/30
240e:87c::/30
240e:88::/31
240e:8a::/31
240e:8c::/31
240e:8e::/31
240e:904::/30
240e:908::/30
240e:90::/31
240e:90c::/30
240e:910::/30
240e:914::/30
240e:918::/32
240e:919::/32
240e:91a::/32
240e:91b::/32
240e:91c::/30
240e:920::/30
240e:924::/32
240e:925::/32
240e:926::/32
240e:927::/32
240e:92::/31
240e:92c::/30
240e:930::/30
240e:934::/30
240e:938::/30
240e:93c::/30
240e:940::/32
240e:941::/32
240e:942::/32
240e:943::/32
240e:944::/32
240e:945::/32
240e:946::/32
240e:947::/32
240e:948::/30
240e:94c::/30
240e:950::/30
240e:954::/30
240e:958::/30
240e:95c::/30
240e:960::/30
240e:964::/30
240e:968::/30
240e:96::/31
240e:96c::/30
240e:970::/30
240e:974::/30
240e:978::/30
240e:97c::/30
240e:980:9400::/40
240e:981:f000::/36
240e:982:1000::/36
240e:982:2000::/36
240e:982:3000::/36
240e:982:4000::/36
240e:982:5000::/36
240e:982:6000::/36
240e:982:7000::/36
240e:982:8000::/36
240e:982:9000::/36
240e:982::/36
240e:982:a000::/36
240e:982:b000::/36
240e:982:c000::/36
240e:982:d000::/36
240e:982:e000::/36
240e:983:1000::/40
240e:983:100::/40
240e:983:1100::/40
240e:983:1200::/40
240e:983:1300::/40
240e:983:1400::/40
240e:983:1500::/40
240e:983:1600::/40
240e:983:1700::/40
240e:983:1800::/40
240e:983:1900::/40
240e:983:1a00::/40
240e:983:1b00::/40
240e:983:1c00::/40
240e:983:1d00::/40
240e:983:1e00::/40
240e:983::/40
240e:983:a00::/40
240e:983:c00::/40
240e:983:d00::/40
240e:983:f00::/40
240e:98::/31
240e:9::/32
240e:9a::/31
240e:9c::/30
240e::/20
240e::/24
240e:a04::/30
240e:a08::/30
240e:a0::/31
240e:a0c::/32
240e:a0d::/32
240e:a0e::/32
240e:a0f::/32
240e:a14::/30
240e:a18::/32
240e:a19::/32
240e:a1a::/32
240e:a1b::/32
240e:a1c::/30
240e:a20::/30
240e:a24::/32
240e:a25::/32
240e:a26::/32
240e:a27::/32
240e:a2::/31
240e:a2c::/30
240e:a30::/30
240e:a34::/30
240e:a38::/30
240e:a40::/32
240e:a41::/32
240e:a42::/32
240e:a43::/32
240e:a44::/32
240e:a45::/32
240e:a46::/32
240e:a47::/32
240e:a48::/30
240e:a4::/31
240e:a4c::/30
240e:a50::/30
240e:a54::/30
240e:a58::/30
240e:a5:8000::/48
240e:a5c::/30
240e:a60::/30
240e:a64::/30
240e:a68::/30
240e:a6::/31
240e:a6c::/30
240e:a70::/30
240e:a74::/30
240e:a78::/30
240e:a7c::/30
240e:a8::/31
240e:aa::/31
240e:ac::/31
240e:ae::/31
240e:b04::/30
240e:b0::/31
240e:b0c::/30
240e:b14::/30
240e:b1c::/30
240e:b20::/30
240e:b2::/31
240e:b2c::/30
240e:b30::/30
240e:b34::/30
240e:b38::/30
240e:b44::/30
240e:b48::/30
240e:b4::/31
240e:b4c::/30
240e:b50::/29
240e:b60::/29
240e:b68::/29
240e:b6::/31
240e:b70::/29
240e:b78::/29
240e:b80::/29
240e:b88::/29
240e:b8::/31
240e:b90::/29
240e:b98::/29
240e:b:8000::/33
240e:b::/33
240e:ba::/31
240e:bb0::/28
240e:bc::/31
240e:bd:8000::/48
240e:be::/31
240e:c0::/31
240e:c2::/31
240e:c4::/30
240e:c60::/30
240e:c8::/31
240e:ca::/31
240e:cc::/31
240e:cd:8000::/48
240e:ce::/31
240e:d0::/31
240e:d2::/31
240e:d4::/31
240e:d6::/31
240e:d8::/31
240e:da::/31
240e:dc::/31
240e:de::/31
240e:e8::/31
240e:ea::/31
240e:ec::/31
240e:ee::/31
240e:f0::/31
240e:f2::/31
240e:f4::/31
240e:f6::/31
240e:f8::/31
240e:fa::/31
240e:fc::/31
240e:fe::/31
2605:9d80:8001::/48
2605:9d80:8011::/48
2605:9d80:8021::/48
2605:9d80:8031::/48
2605:9d80:8041::/48
2605:9d80:8081::/48
2a04:f580:8010::/48
2a04:f580:8011::/48
2a04:f580:8090::/48
2a04:f580:8210::/48
2a04:f580:8211::/48
2a04:f580:8290::/48
2c0f:f7a8:8011::/48
2c0f:f7a8:8050::/48
2c0f:f7a8:805f::/48
2c0f:f7a8:8150::/48
2c0f:f7a8:815f::/48
2c0f:f7a8:8211::/48

View File

@ -0,0 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as4134.txt",
"count": 628,
"sha256": "9e032d30479db6c5bd167681048922765c23d1a35b01ed9e6968b2099b8b916f",
"generated_at": "2026-07-20T19:12:53Z"
}

View File

@ -1,180 +1,179 @@
2400:9380:9001 2400:9380:9001::/48
2400:9380:9009 2400:9380:9009::/48
2400:9380:9020 2400:9380:9020::/48
2400:9380:9021 2400:9380:9021::/48
2400:9380:9050 2400:9380:9050::/48
2400:9380:9051 2400:9380:9051::/48
2400:9380:9060 2400:9380:9060::/48
2400:9380:9071 2400:9380:9071::/48
2400:9380:9080 2400:9380:9080::/48
2400:9380:9081 2400:9380:9081::/48
2400:9380:90b1 2400:9380:90b1::/48
2400:9380:90b2 2400:9380:90b2::/48
2400:9380:90b3 2400:9380:90b3::/48
2400:9380:90b4 2400:9380:90b4::/48
2400:9380:90b5 2400:9380:90b5::/48
2400:9380:90b6 2400:9380:90b6::/48
2400:9380:90b7 2400:9380:90b7::/48
2400:9380:9121 2400:9380:9121::/48
2400:9380:9220 2400:9380:9220::/48
2400:9380:9221 2400:9380:9221::/48
2400:9380:9250 2400:9380:9250::/48
2400:9380:9251 2400:9380:9251::/48
2400:9380:9260 2400:9380:9260::/48
2400:9380:9271 2400:9380:9271::/48
2400:9380:9280 2400:9380:9280::/48
2400:9380:9281 2400:9380:9281::/48
2400:9380:9282 2400:9380:9282::/48
2400:9380:92b1 2400:9380:92b1::/48
2400:9380:92b2 2400:9380:92b2::/48
2400:9380:92b3 2400:9380:92b3::/48
2400:9380:92b4 2400:9380:92b4::/48
2400:9380:92b5 2400:9380:92b5::/48
2400:9380:92b6 2400:9380:92b6::/48
2400:9380:92b7 2400:9380:92b7::/48
240e 240e:182:5401::/48
240e:182:5401 240e:182:5501::/48
240e:182:5501 240e:409:9000::/48
240e:409:9000 240e:409:9001::/48
240e:409:9001 240e:410:ff00::/48
240e:410:ff00 240e:411:ff00::/48
240e:411:ff00 240e:414:e710::/45
240e:414 240e:414:e718::/45
240e:43d:fff1 240e:43d:fff1::/48
240e:43d:fff3 240e:43d:fff3::/48
240e:43d:fff4 240e:43d:fff4::/48
240e:43d:fff7 240e:43d:fff7::/48
240e:440:ac00 240e:440:ac00::/48
240e:440:ac01 240e:440:ac01::/48
240e:440:ac02 240e:440:ac02::/48
240e:441:ac00 240e:441:ac00::/48
240e:441:ac01 240e:441:ac01::/48
240e:441:ac02 240e:441:ac02::/48
240e:445:3f00 240e:445:3f00::/48
240e:446:3f00 240e:446:3f00::/48
240e:451:bfc0 240e:451:bfc0::/48
240e:451:bfe0 240e:451:bfe0::/48
240e:451:ffa0 240e:451:ffa0::/48
240e:451:ffc0 240e:451:ffc0::/48
240e:451:ffe0 240e:451:ffe0::/48
240e:456:fe00 240e:456:fe00::/48
240e:457:fe00 240e:457:fe00::/48
240e:469:f400 240e:469:f400::/48
240e:476:febf 240e:476:febf::/48
240e:476:feff 240e:476:feff::/48
240e:604:314 240e:604:314::/48
240e:604:319 240e:604:319::/48
240e:60e 240e:60e:8000::/48
240e:60e:8000 240e:60e:8001::/48
240e:60e:8001 240e:60e:e000::/35
240e:615 240e:615:7000::/40
240e:61d 240e:61d:e600::/40
240e:628 240e:628:10::/45
240e:62c 240e:62c:1f0::/44
240e:62f 240e:62f::/48
240e:638:f 240e:638:f::/48
240e:63c 240e:63c:3f00::/44
240e:640:178 240e:640:178::/48
240e:640:179 240e:640:179::/48
240e:640:17a 240e:640:17a::/48
240e:640:17b 240e:640:17b::/48
240e:645 240e:645:1000::/36
240e:648:1e 240e:648:1e::/48
240e:648:c00f 240e:648:c00f::/48
240e:648:c40f 240e:648:c40f::/48
240e:648:c80f 240e:648:c80f::/48
240e:648:cc0f 240e:648:cc0f::/48
240e:648:d00f 240e:648:d00f::/48
240e:648:d40f 240e:648:d40f::/48
240e:648:d80f 240e:648:d80f::/48
240e:648:dc0f 240e:648:dc0f::/48
240e:649:c00f 240e:649:c00f::/48
240e:649:c40f 240e:649:c40f::/48
240e:649:c80f 240e:649:c80f::/48
240e:649:cc0f 240e:649:cc0f::/48
240e:649:d00f 240e:649:d00f::/48
240e:649:d40f 240e:649:d40f::/48
240e:649:d80f 240e:649:d80f::/48
240e:649:dc0f 240e:649:dc0f::/48
240e:64e 240e:64e:d00::/44
240e:64e:de0 240e:64e:de0::/48
240e:64e:dec 240e:64e:dec::/48
240e:64e:ded 240e:64e:ded::/48
240e:650 240e:650:8000::/38
240e:659:f100 240e:659:f100::/48
240e:65f 240e:65f:e000::/36
240e:669 240e:669:b000::/40
240e:670 240e:670:d000::/36
240e:679:1001 240e:679:1001::/48
240e:679:800 240e:688:ed00::/48
240e:688:ed00 240e:699:7a00::/48
240e:699 240e:699:7a01::/48
240e:699:7a00 240e:699:7b00::/48
240e:699:7a01 240e:699:7b06::/48
240e:699:7b00 240e:699:7b07::/48
240e:699:7b06 240e:699:7b08::/48
240e:699:7b07 240e:699:7b0a::/48
240e:699:7b08 240e:699:7b0c::/48
240e:699:7b0a 240e:699:7b0d::/48
240e:699:7b0c 240e:699:7b0f::/48
240e:699:7b0d 240e:699:7b11::/48
240e:699:7b0f 240e:699:7b12::/48
240e:699:7b11 240e:699:bc00::/38
240e:699:7b12 240e:6a0:d00::/48
240e:6a0:d00 240e:6a0:d03::/48
240e:6a0:d03 240e:6a0:d04::/48
240e:6a0:d04 240e:6a0:d07::/48
240e:6a0:d07 240e:6a0:d0a::/48
240e:6a0:d0a 240e:713:f020::/48
240e:713:f020 240e:713:f021::/48
240e:713:f021 240e:733:4c0::/48
240e:733:4c0 240e:733:4c1::/48
240e:733:4c1 240e:767:f000::/48
240e:767:f000 240e:787:7000::/48
240e:787:7000 240e:787:7001::/48
240e:787:7001 240e:790:ff00::/40
240e:790 240e:7b6::/31
240e:965:822 240e:965:822::/48
240e:eb 240e:eb:e800::/37
240e:f6:8002 240e:eb:f000::/37
240e:f6:8003 240e:f6:8002::/48
240e:ff:c020 240e:f6:8003::/48
240e:ff:c022 240e:ff:c020::/48
240e:ff:c024 240e:ff:c022::/48
240e:ff:c027 240e:ff:c024::/48
240e:ff:c029 240e:ff:c027::/48
2605:9d80:9003 240e:ff:c029::/48
2605:9d80:9013 2605:9d80:9003::/48
2605:9d80:9023 2605:9d80:9013::/48
2605:9d80:9033 2605:9d80:9023::/48
2605:9d80:9042 2605:9d80:9033::/48
2605:9d80:9071 2605:9d80:9042::/48
2605:9d80:9092 2605:9d80:9071::/48
2804:1e48 2605:9d80:9092::/48
2804:1e48:9001 2804:1e48:9001::/48
2804:1e48:9002 2804:1e48:9002::/48
2a04:f580:9010 2a04:f580:9010::/48
2a04:f580:9012 2a04:f580:9012::/48
2a04:f580:9013 2a04:f580:9013::/48
2a04:f580:9020 2a04:f580:9020::/48
2a04:f580:9030 2a04:f580:9030::/48
2a04:f580:9040 2a04:f580:9040::/48
2a04:f580:9050 2a04:f580:9050::/48
2a04:f580:9060 2a04:f580:9060::/48
2a04:f580:9070 2a04:f580:9070::/48
2a04:f580:9080 2a04:f580:9080::/48
2a04:f580:9210 2a04:f580:9210::/48
2a04:f580:9212 2a04:f580:9212::/48
2a04:f580:9213 2a04:f580:9213::/48
2a04:f580:9220 2a04:f580:9220::/48
2a04:f580:9230 2a04:f580:9230::/48
2a04:f580:9240 2a04:f580:9240::/48
2a04:f580:9250 2a04:f580:9250::/48
2a04:f580:9260 2a04:f580:9260::/48
2a04:f580:9270 2a04:f580:9270::/48
2a04:f580:9280 2a04:f580:9280::/48
2a04:f580:9290 2a04:f580:9290::/48
2c0f:f7a8:9020 2c0f:f7a8:9020::/48
2c0f:f7a8:9041 2c0f:f7a8:9041::/48
2c0f:f7a8:9211 2c0f:f7a8:9220::/48
2c0f:f7a8:9220

View File

@ -0,0 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as4809.txt",
"count": 179,
"sha256": "1b43123532881e6dc9b08cfd56216a9eda028ff41b8ac6dfb4e8c51cb38d873c",
"generated_at": "2026-07-20T19:12:53Z"
}

View File

@ -1,418 +1,470 @@
2001:4510 2001:4510:1480::/41
2001:4511 2001:4511:1480::/41
2402:18a0 2402:18a0::/32
2402:f140:ff13 2402:f140:ff13::/48
2402:f140:ff14 2402:f140:ff14::/48
2404:6500:dcb3 2404:6500:dcb3::/48
2405:1480 2405:1480:3000::/47
2406:1e40 2406:1e40:f012::/47
2406:cac0 2406:cac0:200::/40
2407:6c40 2406:cac0::/40
2407:6c40:1500 2407:6c40:1500::/48
2408 2407:6c40:1600::/40
2408:8000 2408:8000:10fe::/48
2408:8000:10fe 2408:8000:10ff::/48
2408:8000:10ff 2408:8000:2000::/36
2408:8000:2 2408:8000:2::/48
2408:8000:3 2408:8000:3000::/36
2408:8000:5005 2408:8000:3::/48
2408:8001 2408:8000:4000::/36
2408:802a 2408:8000:5000::/36
2408:802c 2408:8000:6000::/36
2408:803e 2408:8000:7000::/36
2408:8056 2408:8000:8000::/36
2408:80c2 2408:8000:8010::/47
2408:80c5 2408:8000::/48
2408:80e2 2408:8000:a000::/36
2408:80e9 2408:8000:b000::/36
2408:80f5 2408:8000:c000::/36
2408:80f9 2408:8000:d000::/36
2408:815f 2408:8000:e000::/36
2408:8181 2408:8000:f000::/36
2408:8182 2408:8001:1000::/36
2408:8183 2408:8001:2000::/36
2408:81a2 2408:8001:4000::/36
2408:81a3 2408:8001:5000::/36
2408:8210 2408:8001:6000::/36
2408:8211 2408:8001:7000::/36
2408:8212 2408:8001:8000::/36
2408:8213 2408:8001:9000::/36
2408:8214 2408:8001::/36
2408:8215 2408:8001:a000::/36
2408:821a 2408:8001:b000::/36
2408:821b 2408:8001:c000::/36
2408:8220 2408:8001:d000::/36
2408:8221 2408:8001:e000::/36
2408:8226 2408:8001:f000::/36
2408:822a 2408:802a:8000::/33
2408:822b 2408:802c::/32
2408:822e 2408:803e::/32
2408:822f 2408:8056::/32
2408:8230 2408:80c2::/33
2408:8231 2408:80c5::/33
2408:8232 2408:80e2::/33
2408:8233 2408:80e9:4000::/34
2408:8234 2408:80f5:4000::/34
2408:8235 2408:80f9:4000::/34
2408:8236 2408:815f:e000::/35
2408:8237 2408:8181:6000::/40
2408:8238 2408:8181:8000::/40
2408:8239 2408:8181::/40
2408:823c 2408:8181:a000::/40
2408:823d 2408:8181:a220::/44
2408:8240 2408:8181:e000::/40
2408:8248 2408:8182:c000::/40
2408:8249 2408:8183:4000::/40
2408:824a 2408:8183:8000::/40
2408:824b 2408:81a2:2000::/35
2408:824c 2408:81a2:4000::/35
2408:824e 2408:81a3:6000::/35
2408:824f 2408:8210::/32
2408:8250 2408:8211::/32
2408:8251 2408:8212::/32
2408:8252 2408:8213::/32
2408:8253 2408:8214::/32
2408:8254 2408:8215::/32
2408:825c 2408:821a::/32
2408:825d 2408:821b::/32
2408:825f 2408:8220::/32
2408:8260 2408:8221::/32
2408:8262 2408:8226::/32
2408:8263 2408:822a::/32
2408:8264 2408:822b::/32
2408:8265 2408:822e::/32
2408:8266 2408:822f::/32
2408:826a 2408:8230::/32
2408:826c 2408:8231::/32
2408:826d 2408:8232::/32
2408:826e 2408:8233::/32
2408:826f 2408:8234::/32
2408:8270 2408:8235::/32
2408:8274 2408:8236::/32
2408:8275 2408:8237::/32
2408:8276 2408:8238::/32
2408:8277 2408:8239::/32
2408:8278 2408:823c::/32
2408:8279 2408:823d::/32
2408:827a 2408:8240::/32
2408:8310 2408:8244::/30
2408:8311 2408:8248::/32
2408:8312 2408:8249::/32
2408:8313 2408:824a::/32
2408:832a 2408:824b::/32
2408:832e 2408:824c::/32
2408:832f 2408:824e::/32
2408:8330 2408:824f::/32
2408:8331 2408:8250::/32
2408:8332 2408:8251::/32
2408:8333 2408:8252::/32
2408:8338 2408:8253::/32
2408:8340 2408:8254::/32
2408:8348 2408:825c::/32
2408:8349 2408:825d::/32
2408:834a 2408:825f::/32
2408:834b 2408:8260::/32
2408:834e 2408:8262::/32
2408:834f 2408:8263::/32
2408:8350 2408:8264::/32
2408:8351 2408:8265::/32
2408:8352 2408:8266::/32
2408:8353 2408:826a::/32
2408:8354 2408:826c::/32
2408:8360 2408:826d::/32
2408:8361 2408:826e::/32
2408:8362 2408:826f::/32
2408:8363 2408:8270::/32
2408:8364 2408:8274::/32
2408:8365 2408:8275::/32
2408:836c 2408:8276::/32
2408:836d 2408:8277::/32
2408:836e 2408:8278::/32
2408:836f 2408:8279::/32
2408:8374 2408:827a::/32
2408:8375 2408:8310::/32
2408:8376 2408:8311::/32
2408:8377 2408:8312::/32
2408:8378 2408:8313::/32
2408:8379 2408:832a::/32
2408:837a 2408:832e::/32
2408:8410 2408:832f::/32
2408:8411 2408:8330::/32
2408:8412 2408:8331::/32
2408:8413 2408:8332::/32
2408:8414 2408:8333::/32
2408:8415 2408:8338::/32
2408:8417 2408:8340::/32
2408:8418 2408:8344::/30
2408:841a 2408:8348::/32
2408:841b 2408:8349::/32
2408:841c 2408:834a::/32
2408:841d 2408:834b::/32
2408:841e 2408:834e::/32
2408:8420 2408:834f::/32
2408:8421 2408:8350::/32
2408:8422 2408:8351::/32
2408:8426 2408:8352::/32
2408:8427 2408:8353::/32
2408:842a 2408:8354::/32
2408:842b 2408:8360::/32
2408:842c 2408:8361::/32
2408:842e 2408:8362::/32
2408:8430 2408:8363::/32
2408:8431 2408:8364::/32
2408:8434 2408:8365::/32
2408:8435 2408:836c::/32
2408:8436 2408:836d::/32
2408:8437 2408:836e::/32
2408:8438 2408:836f::/32
2408:8439 2408:8374::/32
2408:843c 2408:8375::/32
2408:843d 2408:8376::/32
2408:843e 2408:8377::/32
2408:843f 2408:8378::/32
2408:8440 2408:8379::/32
2408:8441 2408:837a::/32
2408:8448 2408:8410::/32
2408:844b 2408:8411::/32
2408:844c 2408:8412::/32
2408:844d 2408:8413::/32
2408:844e 2408:8414::/32
2408:844f 2408:8415::/32
2408:8452 2408:8417::/32
2408:8453 2408:8418::/32
2408:8454 2408:841a::/32
2408:8459 2408:841b::/32
2408:845c 2408:841c::/32
2408:845d 2408:841d::/32
2408:8460 2408:841e::/32
2408:8461 2408:8420::/32
2408:8462 2408:8421::/32
2408:8463 2408:8422::/32
2408:8464 2408:8426::/32
2408:8465 2408:8427::/32
2408:8466 2408:842a::/32
2408:8469 2408:842b::/32
2408:846a 2408:842c::/32
2408:846b 2408:842e::/32
2408:846c 2408:8430::/32
2408:846d 2408:8431::/32
2408:846e 2408:8434::/32
2408:846f 2408:8435::/32
2408:8470 2408:8436::/32
2408:8471 2408:8437::/32
2408:8474 2408:8438::/32
2408:8475 2408:8439::/32
2408:8476 2408:843c::/32
2408:8477 2408:843d::/32
2408:8478 2408:843e::/32
2408:8479 2408:843f::/32
2408:847a 2408:8440::/32
2408:84e3 2408:8441::/32
2408:84e4 2408:8444::/30
2408:84e5 2408:8448::/32
2408:84e6 2408:844b::/32
2408:84e7 2408:844c::/32
2408:84e9 2408:844d::/32
2408:84eb 2408:844e::/32
2408:84ec 2408:844f::/32
2408:84ed 2408:8452::/32
2408:84ee 2408:8453::/32
2408:84ef 2408:8454::/32
2408:84f0 2408:845c::/32
2408:84f1 2408:845d::/32
2408:84f2 2408:8460::/32
2408:84f4 2408:8461::/32
2408:84f5 2408:8462::/32
2408:84f6 2408:8463::/32
2408:84f7 2408:8464::/32
2408:84f8 2408:8465::/32
2408:84f9 2408:8466::/32
2408:84fa 2408:8469::/32
2408:84fb 2408:846a::/32
2408:84fc 2408:846b::/32
2408:84fd 2408:846c::/32
2408:84fe 2408:846d::/32
2408:84ff 2408:846e::/32
2408:856c 2408:846f::/32
2408:856d 2408:8470::/32
2408:8610 2408:8471::/32
2408:8611 2408:8474::/32
2408:8612 2408:8475::/32
2408:8613 2408:8476::/32
2408:8614 2408:8477::/32
2408:8614:e20 2408:8478::/32
2408:8615 2408:8479::/32
2408:861a 2408:847a::/32
2408:861b 2408:84e3::/32
2408:861c 2408:84e4::/32
2408:8620 2408:84e5::/32
2408:8621 2408:84e6::/32
2408:8624 2408:84e7::/32
2408:8625 2408:84e9::/32
2408:8626 2408:84eb::/32
2408:862a 2408:84ec::/32
2408:862b 2408:84ed::/32
2408:862d 2408:84ee::/32
2408:862e 2408:84ef::/32
2408:862f 2408:84f0::/32
2408:8630 2408:84f1::/32
2408:8631 2408:84f2::/32
2408:8632 2408:84f4::/32
2408:8633 2408:84f5::/32
2408:8634 2408:84f6::/32
2408:8635 2408:84f7::/32
2408:8636 2408:84f8::/32
2408:8637 2408:84f9::/32
2408:8638 2408:84fa::/32
2408:8639 2408:84fb::/32
2408:863c 2408:84fc::/32
2408:863d 2408:84fd::/32
2408:8640 2408:84fe::/32
2408:8642 2408:84ff::/32
2408:8648 2408:856c::/32
2408:8649 2408:856d::/32
2408:8649:5a00 2408:8610::/32
2408:864c 2408:8611::/32
2408:864e 2408:8612::/32
2408:864f 2408:8613::/32
2408:8650 2408:8614::/32
2408:8651 2408:8614:e20::/48
2408:8652 2408:8615::/32
2408:8653 2408:861a::/32
2408:865c 2408:861b::/32
2408:865d 2408:861c::/32
2408:865f 2408:8620::/32
2408:8660 2408:8621::/32
2408:8662 2408:8624::/32
2408:8663 2408:8625::/32
2408:8664 2408:8626::/32
2408:8665 2408:862a::/32
2408:8666 2408:862b::/32
2408:866a 2408:862d::/32
2408:866b 2408:862e::/32
2408:866c 2408:862f::/32
2408:866d 2408:8630::/32
2408:866e 2408:8631::/32
2408:866f 2408:8632::/32
2408:8670 2408:8633::/32
2408:8674 2408:8634::/32
2408:8675 2408:8635::/32
2408:8676 2408:8636::/32
2408:8677 2408:8637::/32
2408:8678 2408:8638::/32
2408:8679 2408:8639::/32
2408:867a 2408:863c::/32
2408:8710 2408:863d::/32
2408:8711 2408:8640::/32
2408:8712 2408:8642::/32
2408:8713 2408:8644::/30
2408:8719 2408:8648::/32
2408:871a 2408:8649::/32
2408:871b 2408:864c::/32
2408:8720 2408:864e::/32
2408:8721 2408:864f::/32
2408:8722 2408:8650::/32
2408:8723 2408:8651::/32
2408:8726 2408:8652::/32
2408:872b 2408:8653::/32
2408:872f 2408:865c::/32
2408:8730 2408:865d::/32
2408:8731 2408:865f::/32
2408:8732 2408:8660::/32
2408:8733 2408:8662::/32
2408:8734 2408:8663::/32
2408:8735 2408:8664::/32
2408:8736 2408:8665::/32
2408:8738 2408:8666::/32
2408:873c 2408:866a::/32
2408:873d 2408:866b::/32
2408:8740 2408:866c:200::/42
2408:8742 2408:866c::/32
2408:8748 2408:866d::/32
2408:8749 2408:866e::/32
2408:874a 2408:866f::/32
2408:874b 2408:8670::/32
2408:874c 2408:8674::/32
2408:874d 2408:8675::/32
2408:874e 2408:8676::/32
2408:874f 2408:8677::/32
2408:8752 2408:8678::/32
2408:875c 2408:8679::/32
2408:8760 2408:867a::/32
2408:8762 2408:8710::/32
2408:8763 2408:8711::/32
2408:8764 2408:8712::/32
2408:8765 2408:8713::/32
2408:8766 2408:8719::/32
2408:8768 2408:871a::/32
2408:876a 2408:871b::/32
2408:876c 2408:8720::/32
2408:876d 2408:8721::/32
2408:876e 2408:8722::/32
2408:876f 2408:8723::/32
2408:8770 2408:8726::/32
2408:8772 2408:872b::/32
2408:8773 2408:872b:e00::/47
2408:8774 2408:872f::/32
2408:8776 2408:8730::/32
2408:8777 2408:8731::/32
2408:8778 2408:8732::/32
2408:8779 2408:8733::/32
2408:877a 2408:8734::/32
2408:8812 2408:8735::/32
2408:8813 2408:8736::/32
2408:8814 2408:8738::/32
2408:8815 2408:873c::/32
2408:8818 2408:873d::/32
2408:8819 2408:8740::/32
2408:882c 2408:8742::/32
2408:883a 2408:8744::/30
2408:8862 2408:8748::/32
2408:8863 2408:8749::/32
2408:8864 2408:874a::/32
2408:8865 2408:874b::/32
2408:8866 2408:874c::/32
2408:886e 2408:874d::/32
2408:886f 2408:874e::/32
2408:8872 2408:874f::/32
2408:8878 2408:8752::/32
2408:8879 2408:875c::/32
2408:887e 2408:8760::/32
2408:8912 2408:8762::/32
2408:8913 2408:8763::/32
2408:8914 2408:8764::/32
2408:8915 2408:8765::/32
2408:8916 2408:8766::/32
2408:8917 2408:8768::/32
2408:891c 2408:876a::/32
2408:8920 2408:876c:1700::/44
2408:8924 2408:876c:1740::/44
2408:892c 2408:876c:17c0::/44
2408:8936 2408:876c::/32
2408:893a 2408:876d::/32
2408:8940 2408:876e::/32
2408:8948 2408:876f::/32
2408:894c 2408:8770::/32
2408:894e 2408:8772::/32
2408:8956 2408:8773::/32
2408:8957 2408:8774::/32
2408:8962 2408:8776::/32
2408:8963 2408:8777::/32
2408:8964 2408:8778::/32
2408:8965 2408:8779::/32
2408:8966 2408:877a::/32
2408:896c 2408:8812::/32
2408:896e 2408:8813::/32
2408:896f 2408:8814::/32
2408:8972 2408:8815::/32
2408:8978 2408:8818::/32
2408:8979 2408:8819::/32
2408:897a 2408:882c::/32
2408:897b 2408:883a::/32
2408:897e 2408:8862::/32
2408:8a21 2408:8863::/32
2408:8a22 2408:8864::/32
2408:8a23 2408:8865::/32
2408:8a24 2408:8866::/32
2408:8a26 2408:886e::/32
2408:8a27 2408:886f::/32
2408:8872::/32
2408:8878::/32
2408:8879::/32
2408:887e::/32
2408:8912::/32
2408:8913::/32
2408:8914::/32
2408:8915::/32
2408:8916::/32
2408:8917::/32
2408:891c::/32
2408:8920::/32
2408:8924::/32
2408:892c::/32
2408:8936::/32
2408:893a::/32
2408:8940::/32
2408:8948::/32
2408:894c::/32
2408:894e::/32
2408:8962::/32
2408:8963::/32
2408:8964::/32
2408:8965::/32
2408:8966::/32
2408:896c::/32
2408:896e::/32
2408:896f::/32
2408:8972::/32
2408:8978::/32
2408:8979::/32
2408:897a::/32
2408:897b::/32
2408:897e::/32
2408:8a21:4000::/36
2408:8a21:5000::/36
2408:8a22:1000::/36
2408:8a22::/36
2408:8a23:4000::/36
2408:8a23:5000::/36
2408:8a23:6000::/36
2408:8a23:7000::/36
2408:8a24:4000::/36
2408:8a24:5000::/36
2408:8a24:6000::/36
2408:8a24:7000::/36
2408:8a26:c000::/36
2408:8a26:d000::/36
2408:8a26:e000::/36
2408:8a26:f000::/36
2408:8a27:4000::/36
2408:8a27:5000::/36

View File

@ -0,0 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as4837.txt",
"count": 470,
"sha256": "d109b070f48e97a39e94dc831aab2923b6c440de80621a58319b1ba5926fd60c",
"generated_at": "2026-07-20T19:12:53Z"
}

View File

@ -0,0 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as58453.txt",
"count": 17,
"sha256": "e7e579da764e9dedf84145b5c4cdc51c4b2841517adbc6edb206896b40710119",
"generated_at": "2026-07-20T19:12:53Z"
}

View File

@ -1,2 +1,2 @@
2402:4f00 2402:4f00:f000::/36
2402:4f00:f000 2402:4f00:f000::/48

View File

@ -0,0 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as58807.txt",
"count": 2,
"sha256": "0765c7eefa122984fe69678b60ba99aa605d52601ef4ebf988c4e691e91c5361",
"generated_at": "2026-07-20T19:12:53Z"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
{
"schema": "backtrace.asn-prefixes/v1",
"file": "as9808.txt",
"count": 7171,
"sha256": "c362e447b515ca510b10fd37ba50189c3551a35dede88399190bc869b6fbe66b",
"generated_at": "2026-07-20T19:12:54Z"
}

View File

@ -1,93 +1,144 @@
2408:8120 2408:8120:1::/48
2408:8120:1 2408:8120:2::/48
2408:8120:2 2408:8120:7000::/36
2408:8256:226d 2408:8256:226d::/48
2408:8256:228b 2408:8256:228b::/48
2408:827a:8ff 2408:827a:8ff::/48
2408:8411:c0c0 2408:8411:c0c0::/48
2408:8411:c0c1 2408:8411:c0c1::/48
2408:8411:c0c2 2408:8411:c0c2::/48
2408:8411:c0c3 2408:8411:c0c3::/48
2408:8411:c0c4 2408:8411:c0c4::/48
2408:8411:c0c5 2408:8411:c0c5::/48
2408:8417 2408:8417:f000::/44
2408:8418 2408:8417:f010::/44
2408:841d 2408:8417:f020::/44
2408:841e 2408:8418:f000::/44
2408:8435 2408:8418:f010::/44
2408:843e:e010 2408:8418:f020::/44
2408:843e:e011 2408:841d:ef20::/44
2408:843e:e012 2408:841d:ef30::/44
2408:843e:e080 2408:841e:ef10::/44
2408:843e:e081 2408:841e:ef20::/44
2408:843e:e082 2408:841e:ef30::/44
2408:8441 2408:8435:b490::/47
2408:8444 2408:8435:b492::/47
2408:844c:cb09 2408:8435:b494::/47
2408:844c:cb0a 2408:8435:b496::/47
2408:844c:cb0b 2408:8435:b498::/47
2408:844d:cb09 2408:8435:b49a::/47
2408:844d:cb0a 2408:843e:e010::/48
2408:844d:cb0b 2408:843e:e011::/48
2408:844f 2408:843e:e012::/48
2408:8462 2408:843e:e080::/48
2408:8463 2408:843e:e081::/48
2408:8469:fc00 2408:843e:e082::/48
2408:846a 2408:8441:fe00::/44
2408:846b 2408:8441:fe10::/44
2408:8471:fa00 2408:8441:fe20::/44
2408:8471:fa01 2408:8441:ff00::/44
2408:8471:fa02 2408:8441:ff10::/44
2408:8471:fb00 2408:8441:ff20::/44
2408:8471:fb01 2408:8444:d500::/44
2408:8471:fb02 2408:8444:d580::/44
2408:8474 2408:8444:d700::/44
2408:8478 2408:8444:d780::/44
2408:847a:ff20 2408:8444:d900::/44
2408:847a:ff21 2408:8444:d980::/44
2408:847a:ff22 2408:844c:cb09::/48
2408:847a:ff40 2408:844c:cb0a::/48
2408:847a:ff41 2408:844c:cb0b::/48
2408:847a:ff42 2408:844d:cb09::/48
2408:8610:3bff 2408:844d:cb0a::/48
2408:8614:1f0 2408:844d:cb0b::/48
2408:8614:af0 2408:844f:d408::/47
2408:861c:1fff 2408:844f:d40a::/47
2408:8625:10fb 2408:844f:d40c::/47
2408:8625:fe 2408:844f:d500::/47
2408:8626:f200 2408:844f:d502::/47
2408:862b 2408:844f:d504::/47
2408:862e:2ff 2408:8462:ff00::/44
2408:8634:1015 2408:8462:ff10::/44
2408:8638:116 2408:8462:ff20::/44
2408:8640 2408:8463:ff00::/44
2408:8649:1a00 2408:8463:ff10::/44
2408:8649:2a00 2408:8463:ff20::/44
2408:864c:10ff 2408:8469:fc00::/48
2408:8651:ff00 2408:846a:b600::/44
2408:8652:ff00 2408:846a:b610::/44
2408:8656:a52 2408:846a:b620::/44
2408:865c 2408:846b:b600::/44
2408:8660:100 2408:846b:b610::/44
2408:8660:a100 2408:846b:b620::/44
2408:8660:ab00 2408:8471:fa00::/48
2408:8660:b500 2408:8471:fa01::/48
2408:8660:bf00 2408:8471:fa02::/48
2408:8666:ff00 2408:8471:fb00::/48
2408:866a:a05 2408:8471:fb01::/48
2408:866c:ff00 2408:8471:fb02::/48
2408:8670:800 2408:8474:fb10::/44
2408:8678:1400 2408:8474:fb20::/44
2408:8756:3efd 2408:8474:fb30::/44
2408:8a00 2408:8474:fc10::/44
2408:8a01 2408:8474:fc20::/44
2408:8a02 2408:8474:fc30::/44
2408:8a04 2408:8478:fb10::/44
2408:8a06 2408:8478:fb20::/44
2408:8a06:1 2408:8478:fb30::/44
2408:8a06:100 2408:8478:fc10::/44
2408:8a06:101 2408:8478:fc20::/44
2408:8a07 2408:8478:fc30::/44
2408:8a26:ee10 2408:847a:ff20::/48
2408:8a26:f020 2408:847a:ff21::/48
2408:8a26:f950 2408:847a:ff22::/48
2408:847a:ff40::/48
2408:847a:ff41::/48
2408:847a:ff42::/48
2408:8610:3bff::/48
2408:8614:1f0::/48
2408:8614:af0::/48
2408:861c:1fff::/48
2408:8625:10fb::/48
2408:8625:fe::/48
2408:8626:f200::/48
2408:862b::/48
2408:862e:2ff::/48
2408:8634:1015::/48
2408:8638:116::/48
2408:8640:f100::/40
2408:8649:1a00::/48
2408:8649:2a00::/48
2408:864c:10ff::/48
2408:8651:ff00::/48
2408:8652:ff00::/48
2408:8656:a52::/48
2408:865c:f0::/44
2408:8660:100::/48
2408:8660:a100::/48
2408:8660:ab00::/48
2408:8660:b500::/48
2408:8660:bf00::/48
2408:8666:ff00::/48
2408:866a:a05::/48
2408:866c:ff00::/48
2408:8670:800::/48
2408:8678:1400::/48
2408:8756:3efd::/48
2408:8a00:c000::/36
2408:8a00:d000::/38
2408:8a00:d400::/38
2408:8a00:e000::/36
2408:8a00:f000::/36
2408:8a01::/36
2408:8a02:b110::/44
2408:8a02:b120::/44
2408:8a04:8000::/36
2408:8a04:e000::/40
2408:8a06:100::/48
2408:8a06:101::/48
2408:8a06:1::/48
2408:8a06::/48
2408:8a07::/40
2408:8a26:ee10::/48
2408:8a26:f020::/48
2408:8a26:f950::/48

View File

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

View File

@ -2,7 +2,10 @@ package backtrace
import ( import (
"context" "context"
_ "embed" "crypto/sha256"
"embed"
"encoding/hex"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -18,6 +21,7 @@ import (
) )
const ( const (
ASNPrefixRegistrySchema = "backtrace.asn-prefixes/v1"
ASNPrefixRegistryRawBaseURL = "https://raw.githubusercontent.com/oneclickvirt/backtrace/main/bk/prefix" ASNPrefixRegistryRawBaseURL = "https://raw.githubusercontent.com/oneclickvirt/backtrace/main/bk/prefix"
ASNPrefixRegistryCDNBaseURL = "https://cdn.spiritlhl.net/" + ASNPrefixRegistryRawBaseURL ASNPrefixRegistryCDNBaseURL = "https://cdn.spiritlhl.net/" + ASNPrefixRegistryRawBaseURL
) )
@ -26,8 +30,9 @@ var knownPrefixASNs = []string{"AS23764", "AS4134", "AS4809", "AS4837", "AS58453
var prefixFragmentPattern = regexp.MustCompile(`^[0-9a-fA-F:]+$`) var prefixFragmentPattern = regexp.MustCompile(`^[0-9a-fA-F:]+$`)
type ASNPrefixRegistrySource struct { type ASNPrefixRegistrySource struct {
Name string Name string
Base string Base string
ValidateManifest bool
} }
type ASNPrefixRegistryLoadResult struct { type ASNPrefixRegistryLoadResult struct {
@ -39,14 +44,17 @@ type ASNPrefixRegistryLoadResult struct {
var activeASNPrefixes atomic.Value // stores map[string][]string; maps are immutable after publication var activeASNPrefixes atomic.Value // stores map[string][]string; maps are immutable after publication
var refreshASNPrefixesOnce sync.Once var refreshASNPrefixesOnce sync.Once
//go:embed prefix/*.txt.manifest.json
var embeddedPrefixManifestFS embed.FS
func init() { func init() {
activeASNPrefixes.Store(cloneASNPrefixes(asnPrefixes)) activeASNPrefixes.Store(cloneASNPrefixes(asnPrefixes))
} }
func DefaultASNPrefixRegistrySources() []ASNPrefixRegistrySource { func DefaultASNPrefixRegistrySources() []ASNPrefixRegistrySource {
return []ASNPrefixRegistrySource{ return []ASNPrefixRegistrySource{
{Name: "cdn", Base: ASNPrefixRegistryCDNBaseURL}, {Name: "cdn", Base: ASNPrefixRegistryCDNBaseURL, ValidateManifest: true},
{Name: "raw", Base: ASNPrefixRegistryRawBaseURL}, {Name: "raw", Base: ASNPrefixRegistryRawBaseURL, ValidateManifest: true},
} }
} }
@ -59,7 +67,7 @@ func LoadASNPrefixRegistry(ctx context.Context, client *http.Client, sources []A
} }
var lastErr error var lastErr error
for index, source := range sources { for index, source := range sources {
prefixes, err := fetchASNPrefixSource(ctx, client, source.Base) prefixes, err := fetchASNPrefixSource(ctx, client, source)
if err != nil { if err != nil {
lastErr = fmt.Errorf("load %s ASN prefixes: %w", source.Name, err) lastErr = fmt.Errorf("load %s ASN prefixes: %w", source.Name, err)
continue continue
@ -68,6 +76,9 @@ func LoadASNPrefixRegistry(ctx context.Context, client *http.Client, sources []A
} }
embedded := cloneASNPrefixes(asnPrefixes) embedded := cloneASNPrefixes(asnPrefixes)
if len(embedded) > 0 { if len(embedded) > 0 {
if err := validateEmbeddedPrefixManifests(); err != nil {
return ASNPrefixRegistryLoadResult{}, fmt.Errorf("validate embedded ASN prefix manifests: %w", err)
}
return ASNPrefixRegistryLoadResult{Prefixes: embedded, Source: "embedded", Fallback: true}, nil return ASNPrefixRegistryLoadResult{Prefixes: embedded, Source: "embedded", Fallback: true}, nil
} }
if lastErr == nil { if lastErr == nil {
@ -97,7 +108,16 @@ func StartASNPrefixRefresh() {
}) })
} }
func fetchASNPrefixSource(ctx context.Context, client *http.Client, base string) (map[string][]string, error) { type prefixManifest struct {
Schema string `json:"schema"`
File string `json:"file"`
Count int `json:"count"`
SHA256 string `json:"sha256"`
GeneratedAt string `json:"generated_at"`
}
func fetchASNPrefixSource(ctx context.Context, client *http.Client, source ASNPrefixRegistrySource) (map[string][]string, error) {
base := source.Base
parsed, err := url.Parse(strings.TrimRight(base, "/")) parsed, err := url.Parse(strings.TrimRight(base, "/"))
if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Hostname() == "" { if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Hostname() == "" {
return nil, errors.New("invalid ASN prefix source") return nil, errors.New("invalid ASN prefix source")
@ -111,27 +131,25 @@ func fetchASNPrefixSource(ctx context.Context, client *http.Client, base string)
for _, asn := range knownPrefixASNs { for _, asn := range knownPrefixASNs {
go func(asn string) { go func(asn string) {
endpoint := strings.TrimRight(base, "/") + "/" + strings.ToLower(asn) + ".txt" endpoint := strings.TrimRight(base, "/") + "/" + strings.ToLower(asn) + ".txt"
request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) var manifestData []byte
if source.ValidateManifest {
fetchedManifest, fetchErr := fetchPrefixResource(ctx, client, endpoint+".manifest.json")
if fetchErr != nil {
results <- prefixResult{asn: asn, err: fmt.Errorf("manifest: %w", fetchErr)}
return
}
manifestData = fetchedManifest
}
data, err := fetchPrefixResource(ctx, client, endpoint)
if err != nil { if err != nil {
results <- prefixResult{asn: asn, err: err} results <- prefixResult{asn: asn, err: err}
return return
} }
request.Header.Set("Accept", "text/plain") if source.ValidateManifest {
request.Header.Set("User-Agent", "oneclickvirt-backtrace/asn-prefix-registry-v1") if err := validatePrefixManifest(manifestData, data, strings.ToLower(asn)+".txt"); err != nil {
response, err := client.Do(request) results <- prefixResult{asn: asn, err: err}
if err != nil { return
results <- prefixResult{asn: asn, err: err} }
return
}
data, readErr := io.ReadAll(io.LimitReader(response.Body, 4<<20))
response.Body.Close()
if readErr != nil {
results <- prefixResult{asn: asn, err: readErr}
return
}
if response.StatusCode != http.StatusOK {
results <- prefixResult{asn: asn, err: fmt.Errorf("HTTP %d", response.StatusCode)}
return
} }
values, parseErr := parseASNPrefixLines(data) values, parseErr := parseASNPrefixLines(data)
results <- prefixResult{asn: asn, values: values, err: parseErr} results <- prefixResult{asn: asn, values: values, err: parseErr}
@ -148,6 +166,88 @@ func fetchASNPrefixSource(ctx context.Context, client *http.Client, base string)
return result, nil return result, nil
} }
func fetchPrefixResource(ctx context.Context, client *http.Client, endpoint string) ([]byte, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
request.Header.Set("Accept", "text/plain, application/json")
request.Header.Set("User-Agent", "oneclickvirt-backtrace/asn-prefix-registry-v1")
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP %d", response.StatusCode)
}
return io.ReadAll(io.LimitReader(response.Body, 4<<20))
}
func validatePrefixManifest(data, snapshot []byte, expectedFile string) error {
var manifest prefixManifest
decoder := json.NewDecoder(strings.NewReader(string(data)))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&manifest); err != nil {
return fmt.Errorf("decode manifest: %w", err)
}
var extra any
if err := decoder.Decode(&extra); err != io.EOF {
return errors.New("manifest contains trailing JSON")
}
if manifest.Schema != ASNPrefixRegistrySchema || manifest.File != expectedFile || manifest.Count < 1 {
return errors.New("manifest schema, file, or count is invalid")
}
if _, err := time.Parse(time.RFC3339, manifest.GeneratedAt); err != nil {
return fmt.Errorf("manifest generated_at is invalid: %w", err)
}
hash := sha256.Sum256(snapshot)
if !strings.EqualFold(manifest.SHA256, hex.EncodeToString(hash[:])) {
return errors.New("manifest SHA-256 does not match snapshot")
}
values, err := parseASNPrefixLines(snapshot)
if err != nil {
return err
}
if len(values) != manifest.Count {
return fmt.Errorf("manifest count %d does not match snapshot count %d", manifest.Count, len(values))
}
return nil
}
func validateEmbeddedPrefixManifests() error {
for _, asn := range knownPrefixASNs {
name := strings.ToLower(asn) + ".txt"
manifest, err := embeddedPrefixManifestFS.ReadFile("prefix/" + name + ".manifest.json")
if err != nil {
return err
}
var snapshot []byte
switch asn {
case "AS23764":
snapshot = []byte(as23764Data)
case "AS4134":
snapshot = []byte(as4134Data)
case "AS4809":
snapshot = []byte(as4809Data)
case "AS4837":
snapshot = []byte(as4837Data)
case "AS58453":
snapshot = []byte(as58453Data)
case "AS58807":
snapshot = []byte(as58807Data)
case "AS9808":
snapshot = []byte(as9808Data)
case "AS9929":
snapshot = []byte(as9929Data)
}
if err := validatePrefixManifest(manifest, snapshot, name); err != nil {
return fmt.Errorf("%s: %w", asn, err)
}
}
return nil
}
func parseASNPrefixLines(data []byte) ([]string, error) { func parseASNPrefixLines(data []byte) ([]string, error) {
seen := make(map[string]struct{}) seen := make(map[string]struct{})
values := make([]string, 0) values := make([]string, 0)

View File

@ -2,8 +2,13 @@ package backtrace
import ( import (
"context" "context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"path/filepath"
"strings"
"testing" "testing"
) )
@ -16,20 +21,61 @@ func TestParseASNPrefixLinesNormalizesAndDeduplicates(t *testing.T) {
func TestLoadASNPrefixRegistryUsesRawAfterCDNFailure(t *testing.T) { func TestLoadASNPrefixRegistryUsesRawAfterCDNFailure(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if request.URL.Path == "/bad/as23764.txt" { path := strings.TrimPrefix(request.URL.Path, "/")
http.Error(writer, "bad", http.StatusBadGateway) parts := strings.Split(path, "/")
if len(parts) != 2 {
http.NotFound(writer, request)
return return
} }
_, _ = writer.Write([]byte("2409:8000\n")) base, file := parts[0], parts[1]
snapshot := []byte("2409:8000\n")
if base == "bad" && file == "as23764.txt.manifest.json" {
_, _ = writer.Write([]byte(`{}`))
return
}
if strings.HasSuffix(file, ".manifest.json") {
hash := sha256.Sum256(snapshot)
manifest, _ := json.Marshal(prefixManifest{Schema: ASNPrefixRegistrySchema, File: strings.TrimSuffix(file, ".manifest.json"), Count: 1, SHA256: hex.EncodeToString(hash[:]), GeneratedAt: "2026-01-01T00:00:00Z"})
_, _ = writer.Write(manifest)
return
}
_, _ = writer.Write(snapshot)
})) }))
defer server.Close() defer server.Close()
sources := []ASNPrefixRegistrySource{{Name: "cdn", Base: server.URL + "/bad"}, {Name: "raw", Base: server.URL + "/good"}} sources := []ASNPrefixRegistrySource{{Name: "cdn", Base: server.URL + "/bad", ValidateManifest: true}, {Name: "raw", Base: server.URL + "/good", ValidateManifest: true}}
loaded, err := LoadASNPrefixRegistry(context.Background(), server.Client(), sources) loaded, err := LoadASNPrefixRegistry(context.Background(), server.Client(), sources)
if err != nil || loaded.Source != "raw" || !loaded.Fallback || len(loaded.Prefixes) != len(knownPrefixASNs) { if err != nil || loaded.Source != "raw" || !loaded.Fallback || len(loaded.Prefixes) != len(knownPrefixASNs) {
t.Fatalf("unexpected load: %+v, %v", loaded, err) t.Fatalf("unexpected load: %+v, %v", loaded, err)
} }
} }
func TestValidatePrefixManifestRejectsHashAndCountMismatch(t *testing.T) {
snapshot := []byte("2409:8000\n")
manifest := prefixManifest{Schema: ASNPrefixRegistrySchema, File: "as23764.txt", Count: 2, SHA256: strings.Repeat("0", 64), GeneratedAt: "2026-01-01T00:00:00Z"}
data, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
if err := validatePrefixManifest(data, snapshot, "as23764.txt"); err == nil {
t.Fatal("invalid manifest unexpectedly accepted")
}
}
func TestValidateEmbeddedPrefixManifests(t *testing.T) {
if err := validateEmbeddedPrefixManifests(); err != nil {
t.Fatal(err)
}
}
func TestEmbeddedManifestFilesArePresent(t *testing.T) {
for _, asn := range knownPrefixASNs {
name := strings.ToLower(asn) + ".txt.manifest.json"
if _, err := embeddedPrefixManifestFS.ReadFile(filepath.Join("prefix", name)); err != nil {
t.Fatalf("missing embedded manifest %s: %v", name, err)
}
}
}
func TestLoadASNPrefixRegistryEmbeddedFallback(t *testing.T) { func TestLoadASNPrefixRegistryEmbeddedFallback(t *testing.T) {
loaded, err := LoadASNPrefixRegistry(context.Background(), nil, nil) loaded, err := LoadASNPrefixRegistry(context.Background(), nil, nil)
if err != nil || loaded.Source != "embedded" || !loaded.Fallback || len(loaded.Prefixes) == 0 { if err != nil || loaded.Source != "embedded" || !loaded.Fallback || len(loaded.Prefixes) == 0 {

View File

@ -3,6 +3,8 @@ package main
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/sha256"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"flag" "flag"
@ -87,17 +89,109 @@ func updatePrefixes(ctx context.Context, client *http.Client, config updateConfi
valuesByASN[asn] = values valuesByASN[asn] = values
} }
for _, asn := range knownASNs { for _, asn := range knownASNs {
if err := writePrefixFile(filepath.Join(config.OutputDir, strings.ToLower(asn)+".txt"), valuesByASN[asn]); err != nil { path := filepath.Join(config.OutputDir, strings.ToLower(asn)+".txt")
if err := writePrefixFile(path, valuesByASN[asn]); err != nil {
if errors.Is(err, errPrefixCountDrop) { if errors.Is(err, errPrefixCountDrop) {
fmt.Fprintf(os.Stderr, "skip %s: %v\n", asn, err) fmt.Fprintf(os.Stderr, "skip %s: %v\n", asn, err)
continue current, readErr := os.ReadFile(path)
if readErr != nil {
return readErr
}
valuesByASN[asn] = prefixValuesFromFile(current)
} else {
return err
} }
}
if err := writePrefixManifest(path); err != nil {
return err return err
} }
} }
return nil return nil
} }
type prefixManifest struct {
Schema string `json:"schema"`
File string `json:"file"`
Count int `json:"count"`
SHA256 string `json:"sha256"`
GeneratedAt string `json:"generated_at"`
}
func prefixValuesFromFile(data []byte) []string {
values := make([]string, 0)
for _, line := range strings.Split(string(data), "\n") {
if value := strings.TrimSpace(line); value != "" {
values = append(values, value)
}
}
return values
}
func writePrefixManifest(snapshotPath string) error {
snapshot, err := os.ReadFile(snapshotPath)
if err != nil {
return err
}
values := prefixValuesFromFile(snapshot)
if len(values) == 0 {
return errors.New("prefix snapshot is empty")
}
hash := sha256.Sum256(snapshot)
fileName := filepath.Base(snapshotPath)
sha256Value := hex.EncodeToString(hash[:])
manifestPath := snapshotPath + ".manifest.json"
if current, readErr := os.ReadFile(manifestPath); readErr == nil {
var existing prefixManifest
decoder := json.NewDecoder(bytes.NewReader(current))
decoder.DisallowUnknownFields()
var extra any
if decoder.Decode(&existing) == nil && decoder.Decode(&extra) == io.EOF &&
existing.Schema == "backtrace.asn-prefixes/v1" && existing.File == fileName &&
existing.Count == len(values) && strings.EqualFold(existing.SHA256, sha256Value) {
if _, timeErr := time.Parse(time.RFC3339, existing.GeneratedAt); timeErr == nil {
return nil
}
}
} else if !errors.Is(readErr, os.ErrNotExist) {
return readErr
}
manifest := prefixManifest{Schema: "backtrace.asn-prefixes/v1", File: fileName, Count: len(values), SHA256: sha256Value, GeneratedAt: time.Now().UTC().Format(time.RFC3339)}
data, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return err
}
data = append(data, '\n')
return writeAtomicFile(manifestPath, data)
}
func writeAtomicFile(output string, data []byte) error {
if err := os.MkdirAll(filepath.Dir(output), 0o755); err != nil {
return err
}
temporary, err := os.CreateTemp(filepath.Dir(output), ".prefix-manifest-*")
if err != nil {
return err
}
temporaryName := temporary.Name()
defer os.Remove(temporaryName)
if err := temporary.Chmod(0o644); err != nil {
_ = temporary.Close()
return err
}
if _, err := temporary.Write(data); err != nil {
_ = temporary.Close()
return err
}
if err := temporary.Sync(); err != nil {
_ = temporary.Close()
return err
}
if err := temporary.Close(); err != nil {
return err
}
return os.Rename(temporaryName, output)
}
func extractIPv6Prefixes(data []byte) ([]string, error) { func extractIPv6Prefixes(data []byte) ([]string, error) {
var response struct { var response struct {
Status string `json:"status"` Status string `json:"status"`

View File

@ -1,6 +1,10 @@
package main package main
import ( import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors" "errors"
"os" "os"
"path/filepath" "path/filepath"
@ -30,3 +34,37 @@ func TestWritePrefixFileProtectsLargeCountDrop(t *testing.T) {
t.Fatalf("count drop error = %v", err) t.Fatalf("count drop error = %v", err)
} }
} }
func TestWritePrefixManifestPreservesGeneratedAtWhenSnapshotIsUnchanged(t *testing.T) {
directory := t.TempDir()
snapshotPath := filepath.Join(directory, "as64500.txt")
if err := os.WriteFile(snapshotPath, []byte("2001:db8::/48\n"), 0o644); err != nil {
t.Fatal(err)
}
manifestPath := snapshotPath + ".manifest.json"
old := prefixManifest{Schema: "backtrace.asn-prefixes/v1", File: "as64500.txt", Count: 1, SHA256: "", GeneratedAt: "2020-01-02T03:04:05Z"}
data, err := os.ReadFile(snapshotPath)
if err != nil {
t.Fatal(err)
}
// Seed a valid manifest with an old timestamp. The updater must retain it.
hash := sha256.Sum256(data)
old.SHA256 = hex.EncodeToString(hash[:])
encoded, err := json.Marshal(old)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(manifestPath, append(encoded, '\n'), 0o644); err != nil {
t.Fatal(err)
}
if err := writePrefixManifest(snapshotPath); err != nil {
t.Fatal(err)
}
actual, err := os.ReadFile(manifestPath)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(actual, []byte(`"generated_at":"2020-01-02T03:04:05Z"`)) {
t.Fatalf("generated_at was not preserved: %s", actual)
}
}