nanog mailing list archives
verifying the entire AS path against the customer cone on a route server
From: Martin Tonusoo via NANOG <nanog () lists nanog org>
Date: Sun, 17 May 2026 21:58:20 +0300
Hi.
I was working on a route server configuration for BIRD and while it's
commonplace and often publicly documented [1, 2] that a route server
checks that the origin AS number is in the as-set, then I didn't find
a single route server documentation which states that the entire
AS-path is checked. Same is true for software [3] used for building
the route servers configuration.
I mean that in principle, route servers running BIRD and checking that
the origin AS is in as-set may call a similar function in their
ingress filters:
function origin_as_in_asset(int peer_as) -> bool {
case peer_as {
# FOO (as-set: AS-FOO)
# Content of AS-FOO_as_numbers.conf file:
# [65536,65537,
# 65538,65539]
64501: return bgp_path.last ~
include "/etc/bird/AS-FOO_as_numbers.conf";;
# BAR (as-set: AS-BAR)
64502: return bgp_path.last ~
include "/etc/bird/AS-BAR_as_numbers.conf";;
else: return false;
}
};
A stricter approach checking the entire AS path would be something like this:
function as_in_asset(int peer_as) -> bool {
case peer_as {
# FOO (as-set: AS-FOO)
64501:
for int asn in bgp_path do {
if asn !~
include "/etc/bird/AS-FOO_as_numbers.conf"; then {
#printn "Invalid AS number: ", asn, "; ";
return false;
}
}
# BAR (as-set: AS-BAR)
64502:
for int asn in bgp_path do {
if asn !~
include "/etc/bird/AS-BAR_as_numbers.conf"; then {
#printn "Invalid AS number: ", asn, "; ";
return false;
}
}
# Exception. For AS 64503 check only the origin AS number.
64503: return bgp_path.last ~
include "/etc/bird/AS-BAZ_as_numbers.conf";;
else: return false;
}
return true;
};
For example, the second approach would have rejected those two route leaks:
martin@lab-svr:~$ # AS 21217 announces a prefix from its upstream AS
25091 to its other upstream AS 47176 which accepts the prefix
martin@lab-svr:~$ # 91.206.52.252 is rs1 of SwissIX
martin@lab-svr:~$ curl -s
https://data.ris.ripe.net/rrc20/2026.01/updates.20260102.1440.gz |
bgpdump -m -v - | awk -F '|' '$4 == "91.206.52.252" && $7 == "47176
21217 25091 3303 50350"'
BGP4MP|1767364961|A|91.206.52.252|42476|193.104.238.0/24|47176 21217
25091 3303 50350|IGP|91.206.52.39|0|0||AG|50350 193.104.238.209|
martin@lab-svr:~$
martin@lab-svr:~$ # AS 56635 announces a prefix from its upstream AS
12778 to its other upstream AS 3212 which accepts the prefix
martin@lab-svr:~$ curl -s
https://data.ris.ripe.net/rrc20/2026.01/updates.20260111.1950.gz |
bgpdump -m -v - | awk -F '|' '$4 == "91.206.52.252" && $7 == "21215
3212 56635 12778 197944"'
BGP4MP|1768161244|A|91.206.52.252|42476|91.230.90.0/24|21215 3212
56635 12778 197944|IGP|91.206.52.96|0|0||NAG||
martin@lab-svr:~$
Or those ones, which seem to be a typo in AS path prepending:
martin@lab-svr:~$ curl -s
https://data.ris.ripe.net/rrc20/2026.01/updates.20260110.2020.gz |
bgpdump -m -v - | awk -F '|' '$4 == "91.206.52.252" && $7 == "24961
50873 41451 45415 41451"'
BGP4MP|1768076675|A|91.206.52.252|42476|195.43.80.0/23|24961 50873
41451 45415 41451|IGP|91.206.52.217|0|100030|24961:4 24961:100
24961:200 24961:300|NAG|41451 185.46.172.254|
BGP4MP|1768076675|A|91.206.52.252|42476|195.43.80.0/23|24961 50873
41451 45415 41451|IGP|91.206.53.33|0|100030|24961:4 24961:100
24961:200 24961:300|NAG|41451 185.46.172.254|
BGP4MP|1768076675|A|91.206.52.252|42476|195.43.80.0/23|24961 50873
41451 45415 41451|IGP|91.206.52.217|0|100030|24961:4 24961:100
24961:200 24961:300 24961:9001|NAG|41451 185.46.172.254|
martin@lab-svr:~$
Performance-wise this for-loop approach would take around 90 us per
prefix if the AS path contains excessive 32 AS numbers and the as-set
file contains 100k AS numbers. Or 8 - 10 us per prefix if the AS path
contains 10 AS numbers and the as-set file contains 1k AS numbers. I
tested using the clock_gettime() in BIRD version 3.2.1.
In summary, I simply wanted to share this idea. Perhaps such stricter
checking is applicable for small and regional exchanges. However, it
requires that both route servers use BIRD, as to my knowledge,
OpenBGPD or any NOS like Junos does not support checking each AS path
element against some dataset. Also, wider adoption of ASPA on route
servers would prevent such route leaks.
[1] https://www.swissix.ch/resources/route-server-guide/
[2] https://www.lonap.net/tech/route-servers2#filterpolicy
[3] https://github.com/pierky/arouteserver
Martin
_______________________________________________
NANOG mailing list
https://lists.nanog.org/archives/list/nanog () lists nanog org/message/W7OMPSYMCHFR5N7VZFX2Y4JR7D4AU4ID/
Current thread:
- verifying the entire AS path against the customer cone on a route server Martin Tonusoo via NANOG (May 17)
