Hello,
Thanks for sharing this incident with the community. On May 17, because of an
incorrect filter configuration, routers of AS202734 exported a number of
prefixes from its internal routing table to the route collector of the
Hurricane Electric BGP looking glass (bgp.he.net).
For BGP sessions with upstream transit providers (real routers), AS202734 uses
a tighter set of filters, and export only prefixes that are within a small
number of whitelisted "ANNOUNCED_v4/v6" subnets. Thus, none of the said
prefixes were ever exported to real routers on the internet, and this incident
did not affect any public routing infrastructure. However, this incident might
have triggered a number of automated emails from the HE.net looking glass to
those who subscribed to updates about the affected prefixes. These are truly
what one would describe as "false alarms". If you did receive one of these
emails and was alerted, you have my apologies.
For the benefit of the NANOG community, here is a detailed description of what
triggered this "false alarm" incident.
AS202734/AS402333 runs a custom fork of BIRD3 as the routing control plane for
its routers. The configuration files for these routers are rendered
automatically from a Jinja template- and the aforementioned export filter is no
exception. The same filter template controls both routes exported to transit
providers and public looking glass route collectors such as bgp.he.net.
When AS202734 receives a prefix from its upstreams or IX peers, the import
filter would automatically tag the route with a large community of the form ({{
cold_potato_asn }}, {{ router_ld2_id }}, {{ neighbor_asn }})). This community
identifies the source of the route, as well as the AS202734 router where the
prefix was learned.
BGP looking glasses- bgp.he.net, bgptools, etc.- typically require a full table
to be exported- not just routes whitelisted for announcement to upstreams.
Since the whitelist filter no longer applies, some alternative method would be
necessary to filter out routes that are in the iBGP table, but are not learned
from peers or upstreams- these include internal routing infrastructure
prefixes, as well as other types of routing optimizations that should not be
exported to the public internet. It seems like a natural option to build this
filter around the aforementioned BGP Large Community, which is supposedly
applied only to routes learned from other networks, and not from the internal
infrastructure. As seen in line 30 of the Jinja template below,
"session_export_non_local" evaluates to True for route collectors (but not for
upstream transit providers,) and routes would be accepted for export to the
collectors even if they are not in the "announced" whitelist- as long as they
are tagged
with this large community when learned from peers or upstream. In the filter
for regular upstream transit providers, the original whitelist still applies.
This setup worked just as expected for months, and routes observed at AS202734
were visible through various public looking glasses, helping other networks
understand the reachability of their prefixes. However, up to this point, there
is no clear way to determine from which session or geographic region was a
particular prefix exported- either to the route collector or to a real
upstream. To resolve this, it seems natural to reuse the same large community-
tagging exported routes with geographical router info and session info in the
export filter. See line 15 of the code snippet below:
# v6 version of the filter is shown; the filter for v4 follows the same
logic
1 filter ebgp_export_v6_{{ session_id }} {
2 {% for community in session_communities %}
3 {% set community_value = community if community is string else
(community | join(', ')) %}
4 bgp_community.add(({{ community_value }}));
5 {% endfor %}
6 {% for community in session_large_communities %}
7 {% set community_value = community if community is string else
(community | join(', ')) %}
8 bgp_large_community.add(({{ community_value }}));
9 {% endfor %}
10 {% for community in session_ext_communities %}
11 {% set community_value = community if community is string else
(community | join(', ')) %}
12 bgp_ext_community.add(({{ community_value }}));
13 {% endfor %}
14
15 # Added on the day of the incident:
# bgp_large_community.add(({{ cold_potato_asn }}, {{ router_ld2_id
}}, {{ neighbor_asn }}));
16
17 {% for prepend in session_prepend %}
18 bgp_path.prepend({{ prepend }});
19 {% endfor %}
20
21 if net = ::/0 then {
22 {% if session_export_default %}
23 accept;
24 {% else %}
25 reject;
26 {% endif %}
27 }
28
29 if (net ~ ANNOUNCED_v6) then accept;
30 {% if session_export_non_local %}
31 if (bgp_large_community ~ [ ({{ cold_potato_asn }}, *, *) ]) then
accept;
32 {% endif %}
33 reject;
As you might have anticipated, the issue is that this newly-added BGP large
community applies to all prefixes routes in the table, and not just those
directly learned from upstream/peers. As a result, a large number of
internal-only prefixes (4,622 to be exact) with missing BGP PATH info also
received this large community. While the whitelisting mechanism prevented these
prefixes from being announced to upstream transit providers, these prefixes
were announced to route collectors of BGP.he.net from two of the AS202734
routers.
The fix, as you might expect, is to move the filter "accept" logic into its own
function, and apply any information communities only after if the "accept"
function returned true and no "reject" filter was triggered. After deploying
this fix and testing extensively, AS202734 is now once again contributing data
to the public looking glasses- with the BGP large community included to help
engineers of other networks better understand the reachability of their
prefixes across geographies. To avoid similar incidents in the future, a
staging environment paired with extensive code reviews might be helpful.
I hereby thank the NANOG community and in particular Zhong Miao, the
Independent Security Researcher, for bringing this issue to my attention so
swiftly. I also appreciate comments from the community regarding best practices
to make the deployment process more reliable and secure.
Regards,
Jacob-Junqi Tian
May. 23, 2026.
_______________________________________________
NANOG mailing list
https://lists.nanog.org/archives/list/[email protected]/message/DOAA6GMU234E5XXMNYR53F7MMGBHHILD/