Concerning the structure of ASPA tables and AS0
Hello Bird Team! My name is Ralph. I'm a network engineer and C programmer for Hurricane Electric. I am a long time fan of the Bird project! Keep up the great work!! Earlier this year I was tasked with implementing reactive ASPA in our network. My code was based off your older implementation of static ASPA tables here: https://gitlab.nic.cz/labs/bird/-/tree/aspa This older implementation is based off of customer-provider pairs: typedef struct net_addr_aspa { u8 type; u8 padding; u16 length; u32 customer_asn; u32 provider_asn; } net_addr_aspa; I've attached the patch as "bird-2.15.1-aspa-asn-pairs.patch". I took a look at 2.16 and ran into 2 problems. Respectfully, I would like to report two issues with the ASPA code in 2.16. Issue #1) There is no way to tell the difference between a transit entry and an "AS0" entry. $ cat bird-aspa.conf aspa table at; protocol static { aspa; route aspa 12345 transit; route aspa 970 provider 43, 56; route aspa 43970 provider 0; } --- $ ./sbin/birdc BIRD 2.16 ready. bird> show route table at all Table at: 43970 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 970 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 43 56 12345 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 bird> --- The treatment of AS0 providers is mentioned in section 5 of draft-ietf-sidrops-aspa-verification-19. It is a mechanism for people to announce that "no one should announce this AS". I've attached a snapshot of the global ASPA table as "bird-aspa-v2.16.conf". There is one AS0 announcement as of today. Issue #2) Changes in static ASPA tables are not reflected until entries are removed and re-added. $ cat bird-aspa.conf aspa table at; protocol static { aspa; route aspa 12345 transit; route aspa 970 provider 43, 56; route aspa 43970 provider 0; } bird> show route table at all Table at: 43970 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 970 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 43 56 12345 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 bird> $ cat bird-aspa.conf aspa table at; protocol static { aspa; route aspa 12345 transit; route aspa 970 provider 43, 56, 78; <---- added AS78 route aspa 43970 provider 0; } $ ./sbin/birdc BIRD 2.16 ready. bird> configure Reading configuration from /home/rpki/bird/etc/bird.conf Reconfigured bird> show route table at all Table at: 43970 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 970 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 43 56 <-------- changes not reflected *** 12345 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 bird> $ cat bird-aspa.conf aspa table at; protocol static { aspa; route aspa 12345 transit; #route aspa 970 provider 43, 56, 78; <----- remove entries altogether route aspa 43970 provider 0; } $ ./sbin/birdc BIRD 2.16 ready. bird> configure Reading configuration from /home/rpki/bird/etc/bird.conf Reconfigured bird> show route table at all Table at: 43970 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 12345 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 bird> $ cat bird-aspa.conf aspa table at; protocol static { aspa; route aspa 12345 transit; route aspa 970 provider 43, 56, 78; <------- add entry again route aspa 43970 provider 0; } $ ./sbin/birdc BIRD 2.16 ready. bird> configure Reading configuration from /home/rpki/bird/etc/bird.conf Reconfigured bird> show route table at all Table at: 43970 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 970 [static1 20:17:30.142] * (200) Type: static univ aspa_providers: 43 56 78 <--------- changes reflected correctly 12345 [static1 19:38:32.125] * (200) Type: static univ aspa_providers: 0 bird> --- This problem does not occur when the ASPA elements are customer-provider pairs. I believe this is an overall design issue, not a simple bug. I will be bringing this issue up with ietf-sidrops. Thanks! -- Ralph Covelli Hurricane Electric / AS6939 Network Engineer
Hello Ralph,
Issue #1) There is no way to tell the difference between a transit entry and an "AS0" entry.
There is no difference between a transit entry and an AS0 entry. The `transit` keyword is just a syntactic shortcut for `provider 0`.
The treatment of AS0 providers is mentioned in section 5 of draft-ietf-sidrops-aspa-verification-19. It is a mechanism for people to announce that "no one should announce this AS". I've attached a snapshot of the global ASPA table as "bird-aspa-v2.16.conf". There is one AS0 announcement as of today.
No, it isn't. It's a mechanism to say "I have no providers", as written in section 5 of the draft:
Registering as AS0 ASPA is a statement by the registering AS that it has no transit providers (…)
This effectively means that in a valid AS Path, there may be: - no AS with AS0 ASPA - a single AS with AS0 ASPA - two adjacent AS's with AS0 ASPA I fell probably exactly into the same trap when reading the drafts first time, and the draft authors had to explain the principles to me several times in person for me to get it right. I tried to fix that by suggesting a different wording, please check this (long) message in sidrops: https://mailarchive.ietf.org/arch/msg/sidrops/LE_nPFL6XFnIKE-25O9WIfZ-YFI/
Issue #2) Changes in static ASPA tables are not reflected until entries are removed and re-added.
Thanks, fixed: https://gitlab.nic.cz/labs/bird/-/commit/1e685bbc2a7411c09b6c80404c49a410d6c...
This problem does not occur when the ASPA elements are customer-provider pairs. I believe this is an overall design issue, not a simple bug. I will be bringing this issue up with ietf-sidrops.
There were massive data consistency problems in BIRD 3 when the ASPA elements were customer-provider pairs, and we won't go back there. Also, both the configuration and the in-table storage is more memory-greedy with the pairs storage. Have a nice end-of-the-year! Maria -- Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.
Hello Maria! Thank you for taking the time to read my email. Yes, "I have no providers" is a much more accurate description of AS 0. It can be used by tier 1 networks as well as people trying to depreciate their old ASN. It looks like the source of my confusion was that I was under the assumption that the transit ASPA entries could be used to auto-detect upstream vs downstream as opposed to doing the check in the filter script. Sorry about that! Thank you for fixing the problem with the static ASPA changes!! I noticed in aspa_check() you check for confeds but AS_PATH_SET is never checked for. The specs say they should return ASPA_INVALID however I noticed when I did that I lost about 64 routes which caused some customer complaints. I had to end up slightly changing the code to return ASPA_INVALID if upstream and ASPA_UNKNOWN if downstream. Thank you! Ralph On 12/26/2024 6:04 AM, Maria Matejka via Bird-users wrote:
Hello Ralph,
Issue #1) There is no way to tell the difference between a transit entry and an “AS0” entry.
There is no difference between a transit entry and an AS0 entry. The |transit| keyword is just a syntactic shortcut for |provider 0|.
The treatment of AS0 providers is mentioned in section 5 of draft-ietf-sidrops-aspa-verification-19. It is a mechanism for people to announce that “no one should announce this AS”. I’ve attached a snapshot of the global ASPA table as “bird-aspa-v2.16.conf”. There is one AS0 announcement as of today.
No, it isn’t. It’s a mechanism to say “I have no providers”, as written in section 5 of the draft:
Registering as AS0 ASPA is a statement by the registering AS that it has no transit providers (…)
This effectively means that in a valid AS Path, there may be:
* no AS with AS0 ASPA * a single AS with AS0 ASPA * two adjacent AS’s with AS0 ASPA
I fell probably exactly into the same trap when reading the drafts first time, and the draft authors had to explain the principles to me several times in person for me to get it right. I tried to fix that by suggesting a different wording, please check this (long) message in sidrops:
https://mailarchive.ietf.org/arch/msg/sidrops/LE_nPFL6XFnIKE-25O9WIfZ-YFI/
Issue #2) Changes in static ASPA tables are not reflected until entries are removed and re-added.
Thanks, fixed: https://gitlab.nic.cz/labs/bird/-/commit/1e685bbc2a7411c09b6c80404c49a410d6c...
This problem does not occur when the ASPA elements are customer-provider pairs. I believe this is an overall design issue, not a simple bug. I will be bringing this issue up with ietf-sidrops.
There were massive data consistency problems in BIRD 3 when the ASPA elements were customer-provider pairs, and we won’t go back there.
Also, both the configuration and the in-table storage is more memory-greedy with the pairs storage.
Have a nice end-of-the-year! Maria
– Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.
Hello Ralph,
Yes, "I have no providers" is a much more accurate description of AS 0. It can be used by tier 1 networks as well as people trying to depreciate their old ASN.
Well, yes, a deprecated ASN has also no providers, yet it can still be (maliciously) placed into a valid AS path if it is on the top place. OTOH, with that, the attack surface is limited only to your downstream networks.
It looks like the source of my confusion was that I was under the assumption that the transit ASPA entries could be used to auto-detect upstream vs downstream as opposed to doing the check in the filter script. Sorry about that!
No problem, everybody is confused by ASPA. It's hard to get it right.
I noticed in aspa_check() you check for confeds but AS_PATH_SET is never checked for.
Well, that looks like another oversight, thank you for reporting.
The specs say they should return ASPA_INVALID however I noticed when I did that I lost about 64 routes which caused some customer complaints. I had to end up slightly changing the code to return ASPA_INVALID if upstream and ASPA_UNKNOWN if downstream.
Mhmmm. That's definitely a problem. We can do various things with and around that. First of all, the default behavior of `aspa_check()` must conform to the RFC. Brainstorming: - something like `if bgp_path.contains_sets` - allowing a more precise for-cycle over `bgp_path`, e.g. ``` for bgppath_segment bs in bgp_path do { case bgppath_segment.type { AS_PATH_SEQUENCE: for int a in bs do { ... } AS_PATH_SET: for int a in bs do { ... } AS_PATH_CONFED_SEQUENCE: ... } } ``` - adding an optional argument to `aspa_check()` to allow sets, treting them as "any of the ASNs in the set" - adding an `aspa_is_customer(table, A, B)` function, returning whether A can be a custormer of B according to the given table Any other thoughts on that? Thanks, Maria -- Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.
Hi Maria! Interesting stuff! I suppose you could consider the different AS's in the SET as "lateral" from each other on "the mountain". It looks like the newest (aspa-19) wording of the standard demands that you throw them all out as invalid anyway. https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ 7.2. Algorithm for Upstream Paths 3. If the AS_PATH has an AS_SET, then the procedure halts with the outcome "Invalid". 7.3. Algorithm for Downstream Paths 3. If the AS_PATH has an AS_SET, then the procedure halts with the outcome "Invalid". I am going to see if I can convince the powers that be to change 7.3.3 to "Unknown". Thanks again! Ralph Covelli Network Engineer Hurricane Electric / AS6939 On 12/27/2024 12:31 PM, Maria Matejka via Bird-users wrote:
Hello Ralph,
Yes, “I have no providers” is a much more accurate description of AS 0. It can be used by tier 1 networks as well as people trying to depreciate their old ASN.
Well, yes, a deprecated ASN has also no providers, yet it can still be (maliciously) placed into a valid AS path if it is on the top place. OTOH, with that, the attack surface is limited only to your downstream networks.
It looks like the source of my confusion was that I was under the assumption that the transit ASPA entries could be used to auto-detect upstream vs downstream as opposed to doing the check in the filter script. Sorry about that!
No problem, everybody is confused by ASPA. It’s hard to get it right.
I noticed in aspa_check() you check for confeds but AS_PATH_SET is never checked for.
Well, that looks like another oversight, thank you for reporting.
The specs say they should return ASPA_INVALID however I noticed when I did that I lost about 64 routes which caused some customer complaints. I had to end up slightly changing the code to return ASPA_INVALID if upstream and ASPA_UNKNOWN if downstream.
Mhmmm. That’s definitely a problem. We can do various things with and around that. First of all, the default behavior of |aspa_check()| must conform to the RFC.
Brainstorming:
*
something like |if bgp_path.contains_sets|
*
allowing a more precise for-cycle over |bgp_path|, e.g.
|for bgppath_segment bs in bgp_path do { case bgppath_segment.type { AS_PATH_SEQUENCE: for int a in bs do { ... } AS_PATH_SET: for int a in bs do { ... } AS_PATH_CONFED_SEQUENCE: ... } }| *
adding an optional argument to |aspa_check()| to allow sets, treting them as “any of the ASNs in the set”
*
adding an |aspa_is_customer(table, A, B)| function, returning whether A can be a custormer of B according to the given table
Any other thoughts on that?
Thanks, Maria
– Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.
Okay! I talked to Job. It looks like they have no interest in easing the transition for stragglers who are still announcing AS_SETs in their AS_PATHs. All AS_SETs should result in ASPA_INVALID. I also just learned the Dutch have a saying... "soft doctors make wounds stink". Haha! Thanks again for all your help. Ralph Covelli Network Engineer Hurricane Electric / AS6939 On 12/27/2024 4:40 PM, Ralph Covelli via Bird-users wrote:
Hi Maria!
Interesting stuff! I suppose you could consider the different AS's in the SET as "lateral" from each other on "the mountain".
It looks like the newest (aspa-19) wording of the standard demands that you throw them all out as invalid anyway.
https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/
7.2. Algorithm for Upstream Paths
3. If the AS_PATH has an AS_SET, then the procedure halts with the outcome "Invalid".
7.3. Algorithm for Downstream Paths
3. If the AS_PATH has an AS_SET, then the procedure halts with the outcome "Invalid".
I am going to see if I can convince the powers that be to change 7.3.3 to "Unknown".
Thanks again!
Ralph Covelli Network Engineer Hurricane Electric / AS6939 On 12/27/2024 12:31 PM, Maria Matejka via Bird-users wrote:
Hello Ralph,
Yes, “I have no providers” is a much more accurate description of AS 0. It can be used by tier 1 networks as well as people trying to depreciate their old ASN.
Well, yes, a deprecated ASN has also no providers, yet it can still be (maliciously) placed into a valid AS path if it is on the top place. OTOH, with that, the attack surface is limited only to your downstream networks.
It looks like the source of my confusion was that I was under the assumption that the transit ASPA entries could be used to auto-detect upstream vs downstream as opposed to doing the check in the filter script. Sorry about that!
No problem, everybody is confused by ASPA. It’s hard to get it right.
I noticed in aspa_check() you check for confeds but AS_PATH_SET is never checked for.
Well, that looks like another oversight, thank you for reporting.
The specs say they should return ASPA_INVALID however I noticed when I did that I lost about 64 routes which caused some customer complaints. I had to end up slightly changing the code to return ASPA_INVALID if upstream and ASPA_UNKNOWN if downstream.
Mhmmm. That’s definitely a problem. We can do various things with and around that. First of all, the default behavior of |aspa_check()| must conform to the RFC.
Brainstorming:
*
something like |if bgp_path.contains_sets|
*
allowing a more precise for-cycle over |bgp_path|, e.g.
|for bgppath_segment bs in bgp_path do { case bgppath_segment.type { AS_PATH_SEQUENCE: for int a in bs do { ... } AS_PATH_SET: for int a in bs do { ... } AS_PATH_CONFED_SEQUENCE: ... } }| *
adding an optional argument to |aspa_check()| to allow sets, treting them as “any of the ASNs in the set”
*
adding an |aspa_is_customer(table, A, B)| function, returning whether A can be a custormer of B according to the given table
Any other thoughts on that?
Thanks, Maria
– Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.
Hello! At one point it seems like there was a helper function already written for this that has been lost in time. It used to be right next to as_path_contains_confed() in the code. Maybe its time to bring it back? :-) Thanks! int as_path_contains_set(const struct adata *path) { const byte *pos = path->data; const byte *end = pos + path->length; while (pos < end) { uint type = pos[0]; uint slen = 2 + BS * pos[1]; if ((type == AS_PATH_SET) || (type == AS_PATH_CONFED_SET)) return 1; pos += slen; } return 0; } On 12/27/2024 5:29 PM, Ralph Covelli via Bird-users wrote:
Okay!
I talked to Job. It looks like they have no interest in easing the transition for stragglers who are still announcing AS_SETs in their AS_PATHs.
All AS_SETs should result in ASPA_INVALID.
I also just learned the Dutch have a saying... "soft doctors make wounds stink".
Haha! Thanks again for all your help.
Ralph Covelli Network Engineer Hurricane Electric / AS6939 On 12/27/2024 4:40 PM, Ralph Covelli via Bird-users wrote:
Hi Maria!
Interesting stuff! I suppose you could consider the different AS's in the SET as "lateral" from each other on "the mountain".
It looks like the newest (aspa-19) wording of the standard demands that you throw them all out as invalid anyway.
https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/
7.2. Algorithm for Upstream Paths
3. If the AS_PATH has an AS_SET, then the procedure halts with the outcome "Invalid".
7.3. Algorithm for Downstream Paths
3. If the AS_PATH has an AS_SET, then the procedure halts with the outcome "Invalid".
I am going to see if I can convince the powers that be to change 7.3.3 to "Unknown".
Thanks again!
Ralph Covelli Network Engineer Hurricane Electric / AS6939 On 12/27/2024 12:31 PM, Maria Matejka via Bird-users wrote:
Hello Ralph,
Yes, “I have no providers” is a much more accurate description of AS 0. It can be used by tier 1 networks as well as people trying to depreciate their old ASN.
Well, yes, a deprecated ASN has also no providers, yet it can still be (maliciously) placed into a valid AS path if it is on the top place. OTOH, with that, the attack surface is limited only to your downstream networks.
It looks like the source of my confusion was that I was under the assumption that the transit ASPA entries could be used to auto-detect upstream vs downstream as opposed to doing the check in the filter script. Sorry about that!
No problem, everybody is confused by ASPA. It’s hard to get it right.
I noticed in aspa_check() you check for confeds but AS_PATH_SET is never checked for.
Well, that looks like another oversight, thank you for reporting.
The specs say they should return ASPA_INVALID however I noticed when I did that I lost about 64 routes which caused some customer complaints. I had to end up slightly changing the code to return ASPA_INVALID if upstream and ASPA_UNKNOWN if downstream.
Mhmmm. That’s definitely a problem. We can do various things with and around that. First of all, the default behavior of |aspa_check()| must conform to the RFC.
Brainstorming:
*
something like |if bgp_path.contains_sets|
*
allowing a more precise for-cycle over |bgp_path|, e.g.
|for bgppath_segment bs in bgp_path do { case bgppath_segment.type { AS_PATH_SEQUENCE: for int a in bs do { ... } AS_PATH_SET: for int a in bs do { ... } AS_PATH_CONFED_SEQUENCE: ... } }| *
adding an optional argument to |aspa_check()| to allow sets, treting them as “any of the ASNs in the set”
*
adding an |aspa_is_customer(table, A, B)| function, returning whether A can be a custormer of B according to the given table
Any other thoughts on that?
Thanks, Maria
– Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.
Hello Ralph,
I talked to Job. It looks like they have no interest in easing the transition for stragglers who are still announcing AS_SETs in their AS_PATHs.
All AS_SETs should result in ASPA_INVALID.
I also just learned the Dutch have a saying... "soft doctors make wounds stink".
At one point it seems like there was a helper function already written for this that has been lost in time. It used to be right next to as_path_contains_confed() in the code.
Maybe its time to bring it back? :-)
Well, maybe as a method for an AS Path in filters, to allow people reject and log these paths. Definitely not to be used in the ASPA check. With that, we are on the same page as Job. Also, there is an upcoming RFC draft banning AS_SETs altogether, and we are very much looking forward to implementing it. <https://datatracker.ietf.org/doc/draft-ietf-idr-deprecate-as-set-confed-set/> Thank you for your understanding. Maria -- Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.
Hello Maria! Correct! The AS_PATH_SET's are being depreciated. In the meantime they are slipping into the algorithm which they should not be. The bird code correctly checks for the confed but skips the ckeck for the non-confed sets. Below is what I proposed adding: enum aspa_result aspa_check(rtable *tab, const adata *path, bool force_upstream) { struct lp_state lps; lp_save(tmp_linpool, &lps); /* No support for confed paths */ if (as_path_contains_confed(path)) return ASPA_INVALID; /* No support for as sets */ if (as_path_contains_set(path)) <--- missing in 2.16 return ASPA_INVALID; /* Check path length */ uint len = as_path_getlen(path); if (len == 0) return ASPA_INVALID; Thanks! Ralph Covelli Network Engineer Hurricane Electric / AS6939 On 1/9/2025 9:35 AM, Maria Matejka via Bird-users wrote:
Hello Ralph,
I talked to Job. It looks like they have no interest in easing the transition for stragglers who are still announcing AS_SETs in their AS_PATHs.
All AS_SETs should result in ASPA_INVALID.
I also just learned the Dutch have a saying… “soft doctors make wounds stink”.
At one point it seems like there was a helper function already written for this that has been lost in time. It used to be right next to as_path_contains_confed() in the code.
Maybe its time to bring it back? :-)
Well, maybe as a method for an AS Path in filters, to allow people reject and log these paths.
Definitely not to be used in the ASPA check. With that, we are on the same page as Job. Also, there is an upcoming RFC draft banning AS_SETs altogether, and we are very much looking forward to implementing it.
https://datatracker.ietf.org/doc/draft-ietf-idr-deprecate-as-set-confed-set/ <https://datatracker.ietf.org/doc/draft-ietf-idr-deprecate-as-set-confed-set/>
Thank you for your understanding.
Maria
– Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.
participants (2)
-
Maria Matejka -
Ralph Covelli