BGP Route Filtering: Prefix Lists, AS-Path Filters, Bogon Rejection, and GTSM
A practical reference for hardening BGP sessions on Linux with layered filters. Covers prefix-lists, bogon rejection, AS-path filtering, max-prefix limits, and GTSM in both BIRD2 and FRR syntax with verification steps.
RPKI validates that an AS is authorized to originate a prefix. It does not protect against route leaks, bogon injection, table explosion from a misconfigured peer, or spoofed BGP packets from non-adjacent hosts. This article covers the filters that handle everything RPKI does not.
Every filter is shown in both BIRD2 and FRR syntax, with IPv4 and IPv6 examples. Each section explains what the filter prevents, gives the configuration, then shows how to verify it works.
If you have not set up RPKI origin validation yet, do that first. See RPKI ROA Setup for BGP.
For basic BGP session setup, see BIRD2 BGP Configuration on Linux (BIRD2) or FRR BGP Configuration on Linux (FRR). This article assumes you already have a working BGP session and want to harden it.
Why does BGP need route filtering beyond RPKI?
BGP route filtering is the practice of accepting or rejecting BGP announcements based on prefix, AS-path, or origin attributes. It prevents route leaks, prefix hijacks, bogon injection, and routing table explosion. RPKI covers origin validation only. Without additional filters, your router is exposed to every other category of BGP incident.
Here is what each layer prevents:
| Filter type | Threat blocked | What happens without it |
|---|---|---|
| Bogon prefix rejection | Private/reserved space in DFZ | Your router forwards traffic to RFC 1918 space. Black hole. |
| Small prefix rejection | More-specific hijacks (/25+, /49+) | Attacker advertises a /32 covering part of a /24 you accept. Their route wins by longest match. |
| AS-path bogon filtering | Private/reserved ASNs in paths | Routes with AS 65535 or AS 4200000000 leak into your table. You forward traffic based on garbage paths. |
| AS-path length limit | Path inflation attacks, table bloat | A peer sends routes with 50+ AS hops. Your memory fills up with useless entries. |
| Max-prefix limit | Route leaks, session overload | A peer leaks a full table (1M+ prefixes) into your session. Your router runs out of memory. |
| GTSM (TTL security) | Spoofed BGP packets from remote hosts | An attacker several hops away injects BGP OPEN or UPDATE packets into your session. |
| RPKI origin validation | Origin hijacks | Someone originates your prefix from their AS. Already covered in RPKI ROA Setup for BGP. |
Real incidents show why each layer matters. In 2008, Pakistan Telecom announced more-specific routes for YouTube's prefixes to implement a domestic censorship order. Those routes leaked to international transit providers and black-holed YouTube globally for hours. A bogon or small-prefix filter at transit providers would have dropped those announcements. In June 2019, a small ISP in Pennsylvania (AS396531) accidentally leaked routes for Cloudflare, Amazon, and Linode to Verizon, which propagated them globally. A max-prefix limit would have torn down the session before the leak spread.
How do you filter bogon prefixes in BIRD2 and FRR?
Bogon prefixes are address ranges that must never appear in the global routing table. They include RFC 1918 private space, link-local addresses, documentation ranges, and reserved blocks. Accepting them means your router will try to forward traffic to addresses that have no legitimate global destination, creating black holes.
IPv4 bogon prefixes
| Prefix | Reference | Purpose |
|---|---|---|
| 0.0.0.0/8 | RFC 1122 | "This" network |
| 10.0.0.0/8 | RFC 1918 | Private space |
| 100.64.0.0/10 | RFC 6598 | Carrier-grade NAT |
| 127.0.0.0/8 | RFC 1122 | Loopback |
| 169.254.0.0/16 | RFC 3927 | Link-local |
| 172.16.0.0/12 | RFC 1918 | Private space |
| 192.0.2.0/24 | RFC 5737 | TEST-NET-1 |
| 192.88.99.0/24 | RFC 7526 | Deprecated 6to4 relay |
| 192.168.0.0/16 | RFC 1918 | Private space |
| 198.18.0.0/15 | RFC 2544 | Benchmarking |
| 198.51.100.0/24 | RFC 5737 | TEST-NET-2 |
| 203.0.113.0/24 | RFC 5737 | TEST-NET-3 |
| 224.0.0.0/4 | RFC 5771 | Multicast |
| 240.0.0.0/4 | RFC 1112 | Reserved for future use |
IPv6 bogon prefixes
| Prefix | Reference | Purpose |
|---|---|---|
| ::/8 | Various | IPv4-compatible, loopback, mapped |
| 100::/64 | RFC 6666 | Discard-only |
| 2001:2::/48 | RFC 5180 | BMWG benchmarking |
| 2001:10::/28 | RFC 4843 | ORCHID |
| 2001:db8::/32 | RFC 3849 | Documentation |
| 3fff::/20 | RFC 9637 | Documentation |
| 2002::/16 | RFC 7526 | Deprecated 6to4 |
| 3ffe::/16 | RFC 3701 | Former 6bone |
| 5f00::/16 | RFC 9602 | SRv6 SIDs |
| fc00::/7 | RFC 4193 | Unique local unicast |
| fe80::/10 | RFC 4291 | Link-local unicast |
| fec0::/10 | RFC 3879 | Deprecated site-local |
| ff00::/8 | RFC 4291 | Multicast |
These lists change when IANA allocates new blocks or deprecates old ones. The Team Cymru bogon reference provides a BGP feed of fullbogons (unallocated + reserved space) that updates automatically. For static lists, check the NLNOG BGP Filter Guide periodically.
BIRD2 bogon filter
# /etc/bird/bogons.conf - include this from your main bird.conf
define BOGON_PREFIXES_V4 = [
0.0.0.0/8+,
10.0.0.0/8+,
100.64.0.0/10+,
127.0.0.0/8+,
169.254.0.0/16+,
172.16.0.0/12+,
192.0.2.0/24+,
192.88.99.0/24+,
192.168.0.0/16+,
198.18.0.0/15+,
198.51.100.0/24+,
203.0.113.0/24+,
224.0.0.0/4+,
240.0.0.0/4+
];
define BOGON_PREFIXES_V6 = [
::/8+,
0100::/64+,
2001:2::/48+,
2001:10::/28+,
2001:db8::/32+,
3fff::/20+,
2002::/16+,
3ffe::/16+,
5f00::/16+,
fc00::/7+,
fe80::/10+,
fec0::/10+,
ff00::/8+
];
function reject_bogon_prefixes_v4() {
if (net ~ BOGON_PREFIXES_V4) then {
print "REJECTED bogon prefix: ", net, " path: ", bgp_path;
reject;
}
}
function reject_bogon_prefixes_v6() {
if (net ~ BOGON_PREFIXES_V6) then {
print "REJECTED bogon prefix: ", net, " path: ", bgp_path;
reject;
}
}
The + after each prefix means "this prefix and all more-specifics." 10.0.0.0/8+ matches 10.0.0.0/8, 10.0.0.0/9, 10.1.0.0/16, and so on. This catches an attacker announcing a /24 inside RFC 1918 space.
Call these functions in your import filter:
filter import_from_upstream_v4 {
reject_bogon_prefixes_v4();
# ... other filters ...
accept;
}
protocol bgp upstream_v4 {
local as 64500;
neighbor 198.51.100.1 as 64501;
ipv4 {
import filter import_from_upstream_v4;
export none;
};
}
FRR bogon filter
! IPv4 bogon prefix-list
ip prefix-list BOGONS_v4 seq 10 deny 0.0.0.0/8 le 32
ip prefix-list BOGONS_v4 seq 20 deny 10.0.0.0/8 le 32
ip prefix-list BOGONS_v4 seq 30 deny 100.64.0.0/10 le 32
ip prefix-list BOGONS_v4 seq 40 deny 127.0.0.0/8 le 32
ip prefix-list BOGONS_v4 seq 50 deny 169.254.0.0/16 le 32
ip prefix-list BOGONS_v4 seq 60 deny 172.16.0.0/12 le 32
ip prefix-list BOGONS_v4 seq 70 deny 192.0.2.0/24 le 32
ip prefix-list BOGONS_v4 seq 80 deny 192.88.99.0/24 le 32
ip prefix-list BOGONS_v4 seq 90 deny 192.168.0.0/16 le 32
ip prefix-list BOGONS_v4 seq 100 deny 198.18.0.0/15 le 32
ip prefix-list BOGONS_v4 seq 110 deny 198.51.100.0/24 le 32
ip prefix-list BOGONS_v4 seq 120 deny 203.0.113.0/24 le 32
ip prefix-list BOGONS_v4 seq 130 deny 224.0.0.0/4 le 32
ip prefix-list BOGONS_v4 seq 140 deny 240.0.0.0/4 le 32
ip prefix-list BOGONS_v4 seq 999 permit 0.0.0.0/0 le 32
! IPv6 bogon prefix-list
ipv6 prefix-list BOGONS_v6 seq 10 deny ::/8 le 128
ipv6 prefix-list BOGONS_v6 seq 20 deny 100::/64 le 128
ipv6 prefix-list BOGONS_v6 seq 30 deny 2001:2::/48 le 128
ipv6 prefix-list BOGONS_v6 seq 40 deny 2001:10::/28 le 128
ipv6 prefix-list BOGONS_v6 seq 50 deny 2001:db8::/32 le 128
ipv6 prefix-list BOGONS_v6 seq 60 deny 3fff::/20 le 128
ipv6 prefix-list BOGONS_v6 seq 70 deny 2002::/16 le 128
ipv6 prefix-list BOGONS_v6 seq 80 deny 3ffe::/16 le 128
ipv6 prefix-list BOGONS_v6 seq 90 deny 5f00::/16 le 128
ipv6 prefix-list BOGONS_v6 seq 100 deny fc00::/7 le 128
ipv6 prefix-list BOGONS_v6 seq 110 deny fe80::/10 le 128
ipv6 prefix-list BOGONS_v6 seq 120 deny fec0::/10 le 128
ipv6 prefix-list BOGONS_v6 seq 130 deny ff00::/8 le 128
ipv6 prefix-list BOGONS_v6 seq 999 permit ::/0 le 128
The le 32 (IPv4) and le 128 (IPv6) clauses match the prefix and all more-specifics, same as the + operator in BIRD2. The final permit line at seq 999 allows everything that was not denied above.
Apply the prefix-list to your neighbor:
router bgp 64500
neighbor 198.51.100.1 remote-as 64501
address-family ipv4 unicast
neighbor 198.51.100.1 prefix-list BOGONS_v4 in
address-family ipv6 unicast
neighbor 2001:db8::1 prefix-list BOGONS_v6 in
Wait. That 2001:db8::/32 entry is in the bogon list. Do not use documentation addresses in production. Replace the neighbor addresses above with your actual peer IPs.
Rejecting too-small prefixes
Routes smaller than /24 (IPv4) or /48 (IPv6) will not propagate reliably in the global DFZ. More importantly, accepting them opens you to more-specific hijack attacks. Filter them on inbound.
BIRD2:
function reject_small_prefixes_v4() {
if (net.len > 24) then {
print "REJECTED too-small prefix: ", net, " path: ", bgp_path;
reject;
}
}
function reject_small_prefixes_v6() {
if (net.len > 48) then {
print "REJECTED too-small prefix: ", net, " path: ", bgp_path;
reject;
}
}
FRR:
! Add to the same prefix-lists, before the final permit
ip prefix-list BOGONS_v4 seq 150 deny 0.0.0.0/0 ge 25 le 32
ipv6 prefix-list BOGONS_v6 seq 140 deny ::/0 ge 49 le 128
This catches any prefix more specific than /24 (IPv4) or /48 (IPv6). Place these lines before the seq 999 permit entries.
How do AS-path filters prevent route leaks?
AS-path filters inspect the sequence of autonomous systems a route has traversed. They catch three problems: bogon ASNs that should never appear in a path, excessively long paths that indicate leaks or manipulation, and private ASNs that were not stripped before advertisement.
Filtering bogon ASNs
Bogon ASNs are reserved numbers that should never appear in a BGP path on the public internet:
| ASN range | Reference | Purpose |
|---|---|---|
| 0 | RFC 7607 | Reserved |
| 23456 | RFC 4893 | AS_TRANS (4-byte AS transition) |
| 64496-64511 | RFC 5398 | Documentation/examples |
| 64512-65534 | RFC 6996 | Private use (16-bit) |
| 65535 | RFC 7300 | Last 16-bit ASN |
| 65536-65551 | RFC 5398 | Documentation/examples (32-bit) |
| 65552-131071 | IANA | Reserved |
| 4200000000-4294967294 | RFC 6996 | Private use (32-bit) |
| 4294967295 | RFC 7300 | Last 32-bit ASN |
BIRD2:
define BOGON_ASNS = [
0,
23456,
64496..64511,
64512..65534,
65535,
65536..65551,
65552..131071,
4200000000..4294967294,
4294967295
];
function reject_bogon_asns()
int set bogon_asns;
{
bogon_asns = BOGON_ASNS;
if (bgp_path ~ bogon_asns) then {
print "REJECTED bogon ASN in path: ", net, " path: ", bgp_path;
reject;
}
}
The bgp_path ~ bogon_asns operator checks whether any ASN in the path is a member of the set. A single bogon ASN anywhere in the path triggers rejection.
FRR:
bgp as-path access-list BOGON_ASNS deny _0_
bgp as-path access-list BOGON_ASNS deny _23456_
bgp as-path access-list BOGON_ASNS deny _6449[6-9]_
bgp as-path access-list BOGON_ASNS deny _6450[0-9]_
bgp as-path access-list BOGON_ASNS deny _6451[01]_
bgp as-path access-list BOGON_ASNS deny _64[5-9][1-9][2-9]_
bgp as-path access-list BOGON_ASNS deny _6[5-9][0-9][0-9][0-9]_
bgp as-path access-list BOGON_ASNS deny _[1-9][0-9][0-9][0-9][0-9]_
bgp as-path access-list BOGON_ASNS deny _[1-3][0-9][0-9][0-9][0-9][0-9]_
bgp as-path access-list BOGON_ASNS permit .*
FRR uses regex matching on the AS-path string. The underscores _ match AS-path delimiters (space, start, end). This regex approach is less precise than BIRD2's integer set matching. For a simpler but less granular approach, use a route-map with the as-path access-list:
route-map IMPORT_FILTER deny 10
match as-path BOGON_ASNS
route-map IMPORT_FILTER permit 100
router bgp 64500
address-family ipv4 unicast
neighbor 198.51.100.1 route-map IMPORT_FILTER in
Note: the regex patterns above are simplified. Matching the full range of 32-bit private ASNs (4200000000-4294967294) with regex is error-prone. For production deployments, use bgpq4 to generate prefix-lists and AS-path filters from IRR data, which is both more accurate and automatable.
Limiting AS-path length
A legitimate BGP path rarely exceeds 10-15 ASNs. Paths longer than that typically indicate a route leak, path manipulation, or prepending gone wrong. Set a hard limit.
BIRD2:
function reject_long_paths() {
if (bgp_path.len > 25) then {
print "REJECTED long AS-path (", bgp_path.len, "): ", net, " path: ", bgp_path;
reject;
}
}
FRR:
bgp as-path access-list LONG_PATHS deny ^([0-9]+_){25,}
bgp as-path access-list LONG_PATHS permit .*
route-map IMPORT_FILTER deny 20
match as-path LONG_PATHS
A limit of 25 is conservative. Most legitimate routes have paths under 10. If you receive a full table, inspect the longest paths in your RIB before picking a threshold:
# BIRD2
birdc 'show route where bgp_path.len > 15' | head -20
# FRR
vtysh -c "show ip bgp regexp ^([0-9]+_){15,}"
Stripping private ASNs on outbound
If you run iBGP with private ASNs internally, make sure they do not leak to your upstreams.
BIRD2:
filter export_to_upstream_v4 {
# Strip private ASNs before advertising
bgp_path.delete([64512..65534, 4200000000..4294967294]);
# ... your export policy ...
accept;
}
FRR:
router bgp 64500
address-family ipv4 unicast
neighbor 198.51.100.1 remove-private-AS all
The all keyword removes every private ASN even when public ASNs are present in the path. Without all, FRR only strips private ASNs if the entire path contains nothing but private ASNs.
What is the correct max-prefix limit for a BGP session?
A max-prefix limit is a safety valve that tears down a BGP session if a peer advertises more prefixes than the configured threshold. It prevents routing table explosion when a peer accidentally leaks a full table or gets hijacked. Without it, a single misconfigured peer can push your router out of memory.
Set the limit based on what you expect from each peer:
| Peer type | Expected prefixes (IPv4) | Suggested limit | Suggested limit (IPv6) |
|---|---|---|---|
| Full table upstream | ~1,200,000 (March 2026) | 1,500,000 | 300,000 |
| Partial table / IXP peer | Varies | 1.5x current count | 1.5x current count |
| Customer single-homed | 1-10 | 50 | 50 |
| Customer multi-homed | 10-100 | 200 | 200 |
Check the current full-table size at bgp.potaroo.net before setting upstream limits. Set the limit at roughly 1.2x the expected count to absorb normal growth without false triggers.
BIRD2:
protocol bgp upstream_v4 {
local as 64500;
neighbor 198.51.100.1 as 64501;
ipv4 {
import limit 1500000 action restart;
import filter import_from_upstream_v4;
export none;
};
}
protocol bgp customer_v4 {
local as 64500;
neighbor 203.0.113.10 as 64502;
ipv4 {
import limit 50 action disable;
import filter import_from_customer_v4;
export filter export_to_customer_v4;
};
}
BIRD2 offers three actions when the limit is hit:
action restart- tears down the session and restarts after a delay. Best for upstreams.action disable- tears down and disables the protocol. Requires manualbirdc enableto restore. Best for customers where a limit hit means something is seriously wrong.action block- stops importing new routes but keeps the session up. Useful if you want to keep existing routes while investigating.
FRR:
router bgp 64500
neighbor 198.51.100.1 remote-as 64501
address-family ipv4 unicast
neighbor 198.51.100.1 maximum-prefix 1500000 90 restart 5
neighbor 203.0.113.10 remote-as 64502
address-family ipv4 unicast
neighbor 203.0.113.10 maximum-prefix 50 80
In FRR, maximum-prefix 1500000 90 restart 5 means: warn at 90% (1,350,000 prefixes), tear down at 1,500,000, and restart the session after 5 minutes. Without the restart keyword, FRR tears down the session and requires a manual clear bgp neighbor to restore it.
Verify max-prefix configuration
# BIRD2 - check current prefix count and limit
birdc show protocols all upstream_v4 | grep -E "Routes|Limit"
# FRR - check prefix count per neighbor
vtysh -c "show ip bgp summary"
The output shows the current prefix count next to the configured limit. If the count is close to the limit, increase the threshold before it triggers.
How does GTSM protect BGP sessions from spoofed packets?
GTSM (Generalized TTL Security Mechanism, RFC 5082) restricts accepted BGP packets to directly connected peers by checking the TTL/Hop Limit field. When enabled, BGP packets are sent with TTL 255. The receiver drops any BGP packet with a TTL below 254 (for a directly connected peer). Since routers decrement TTL on each hop, an attacker more than one hop away cannot send packets that arrive with TTL 255.
This blocks remote TCP RST injection, SYN floods targeting BGP port 179, and crafted BGP OPEN/UPDATE packets from non-adjacent attackers. GTSM is mutually exclusive with eBGP multihop on the same session. Both peers must enable it.
BIRD2:
protocol bgp upstream_v4 {
local as 64500;
neighbor 198.51.100.1 as 64501;
ttl security yes;
ipv4 {
import limit 1500000 action restart;
import filter import_from_upstream_v4;
export none;
};
}
FRR:
router bgp 64500
neighbor 198.51.100.1 remote-as 64501
neighbor 198.51.100.1 ttl-security hops 1
The hops 1 means the peer must be exactly 1 hop away (directly connected). Set this to match the actual hop count. For eBGP peers over an IXP switching fabric, hops 1 is correct. For multihop sessions (e.g., BGP over a GRE tunnel), you cannot use GTSM. Use MD5 authentication instead:
BIRD2 MD5 alternative:
protocol bgp multihop_peer {
local as 64500;
neighbor 198.51.100.5 as 64503;
multihop 2;
password "your-md5-secret";
# ...
}
FRR MD5 alternative:
router bgp 64500
neighbor 198.51.100.5 remote-as 64503
neighbor 198.51.100.5 ebgp-multihop 2
neighbor 198.51.100.5 password your-md5-secret
Store the MD5 password in a restricted file (chmod 600) rather than in the main config. For FRR, reference it from /etc/frr/frr.conf which should already be 640 owned by frr:frr. For BIRD2, keep /etc/bird/bird.conf at 640 owned by bird:bird.
Which BGP filters does MANRS require?
MANRS (Mutually Agreed Norms for Routing Security) defines baseline actions for network operators. Actions 1, 3, and 4 are required to become a MANRS participant. Here is how the filters in this article map to MANRS actions.
Action 1: Prevent propagation of incorrect routing information.
This is the core filtering action. It requires explicit prefix-level filters on customer connections and recommends AS-path filters to prevent route leaks.
| Filter from this article | MANRS Action 1 coverage |
|---|---|
| Prefix-list filtering (bogons) | Prevents advertising/accepting reserved space |
| Small prefix rejection | Blocks more-specific routes that indicate hijacks |
| AS-path bogon filtering | Rejects routes with private/reserved ASNs |
| Max-prefix limits | Stops propagation of leaked full tables |
| Customer prefix filters | Not in this article. Build these per-customer from IRR using bgpq4. |
Action 2 (recommended): Prevent traffic with spoofed source addresses.
Not a BGP filter. This is BCP 38/84 source address validation (uRPF). Outside the scope of this article but equally important.
Action 3: Facilitate global operational communication.
Keep your contact details current in PeeringDB and your RIR database. Not a filter, but operators who run proper filters tend to also maintain their contact info.
Action 4: Facilitate routing information on a global scale.
Publish your routing policy in IRR (RIPE, RADB) using RPSL objects. Create ROAs for RPKI. This makes it possible for your peers to build accurate prefix filters for your announcements.
The combination of bogon filtering (prefixes + ASNs) + small prefix rejection + max-prefix limits + customer prefix-lists covers MANRS Action 1. Add RPKI (RPKI ROA Setup for BGP) and IRR registration for Action 4.
Putting it all together: a complete import filter
Here is a combined import filter using all the techniques above.
BIRD2 complete import filter
# /etc/bird/filters.conf
include "/etc/bird/bogons.conf"; # BOGON_PREFIXES_V4, BOGON_PREFIXES_V6, BOGON_ASNS
function import_checks_v4() {
# 1. Reject bogon prefixes
if (net ~ BOGON_PREFIXES_V4) then {
print "REJECT bogon prefix: ", net, " ", bgp_path;
reject;
}
# 2. Reject too-small prefixes
if (net.len > 24) then {
print "REJECT small prefix: ", net, " ", bgp_path;
reject;
}
# 3. Reject bogon ASNs in path
if (bgp_path ~ [0, 23456, 64496..64511, 64512..65534, 65535,
65536..65551, 65552..131071,
4200000000..4294967294, 4294967295]) then {
print "REJECT bogon ASN: ", net, " ", bgp_path;
reject;
}
# 4. Reject excessively long paths
if (bgp_path.len > 25) then {
print "REJECT long path: ", net, " ", bgp_path;
reject;
}
# 5. Reject RPKI invalid (if RPKI is configured)
if (roa_check(rpki4, net, bgp_path.last) = ROA_INVALID) then {
print "REJECT RPKI invalid: ", net, " ", bgp_path;
reject;
}
}
filter import_upstream_v4 {
import_checks_v4();
accept;
}
protocol bgp upstream_v4 {
local as 64500;
neighbor 198.51.100.1 as 64501;
ttl security yes;
ipv4 {
import limit 1500000 action restart;
import filter import_upstream_v4;
export none;
};
}
FRR complete import filter
! /etc/frr/frr.conf
! --- Prefix lists (bogons + small prefix rejection) ---
ip prefix-list IMPORT_V4 seq 10 deny 0.0.0.0/8 le 32
ip prefix-list IMPORT_V4 seq 20 deny 10.0.0.0/8 le 32
ip prefix-list IMPORT_V4 seq 30 deny 100.64.0.0/10 le 32
ip prefix-list IMPORT_V4 seq 40 deny 127.0.0.0/8 le 32
ip prefix-list IMPORT_V4 seq 50 deny 169.254.0.0/16 le 32
ip prefix-list IMPORT_V4 seq 60 deny 172.16.0.0/12 le 32
ip prefix-list IMPORT_V4 seq 70 deny 192.0.2.0/24 le 32
ip prefix-list IMPORT_V4 seq 80 deny 192.88.99.0/24 le 32
ip prefix-list IMPORT_V4 seq 90 deny 192.168.0.0/16 le 32
ip prefix-list IMPORT_V4 seq 100 deny 198.18.0.0/15 le 32
ip prefix-list IMPORT_V4 seq 110 deny 198.51.100.0/24 le 32
ip prefix-list IMPORT_V4 seq 120 deny 203.0.113.0/24 le 32
ip prefix-list IMPORT_V4 seq 130 deny 224.0.0.0/4 le 32
ip prefix-list IMPORT_V4 seq 140 deny 240.0.0.0/4 le 32
ip prefix-list IMPORT_V4 seq 150 deny 0.0.0.0/0 ge 25 le 32
ip prefix-list IMPORT_V4 seq 999 permit 0.0.0.0/0 le 32
! --- AS-path filters (bogon ASNs + path length) ---
bgp as-path access-list BOGON_ASNS deny _0_
bgp as-path access-list BOGON_ASNS deny _23456_
bgp as-path access-list BOGON_ASNS deny _6449[6-9]_
bgp as-path access-list BOGON_ASNS deny _6450[0-9]_
bgp as-path access-list BOGON_ASNS deny _6451[01]_
bgp as-path access-list BOGON_ASNS permit .*
bgp as-path access-list LONG_PATHS deny ^([0-9]+_){25,}
bgp as-path access-list LONG_PATHS permit .*
! --- Route-map combining all checks ---
route-map IMPORT_UPSTREAM deny 10
match as-path BOGON_ASNS
route-map IMPORT_UPSTREAM deny 20
match as-path LONG_PATHS
route-map IMPORT_UPSTREAM permit 100
! --- BGP neighbor configuration ---
router bgp 64500
neighbor 198.51.100.1 remote-as 64501
neighbor 198.51.100.1 ttl-security hops 1
address-family ipv4 unicast
neighbor 198.51.100.1 prefix-list IMPORT_V4 in
neighbor 198.51.100.1 route-map IMPORT_UPSTREAM in
neighbor 198.51.100.1 maximum-prefix 1500000 90 restart 5
In FRR, both the prefix-list and route-map are evaluated. The prefix-list runs first and drops bogon prefixes and small prefixes. Routes that pass the prefix-list then hit the route-map, which checks AS-path filters. Both must permit the route for it to be accepted.
How do you verify BGP route filters are working?
Filters are only useful if they actually reject what they should. Verify after every change.
BIRD2 verification
# Check what routes were rejected (requires print statements in filters)
grep "REJECT" /var/log/bird.log | tail -20
# Show the routing table with filter details
birdc show route filtered
# Show routes from a specific protocol
birdc show route protocol upstream_v4
# Count accepted routes per protocol
birdc show protocols all upstream_v4 | grep "Routes:"
# Test a specific prefix against your import filter
birdc show route for 10.0.0.0/8 all
The show route filtered command shows routes that were received but rejected by the import filter. If your bogon filter is working, you should see zero bogon prefixes in the accepted table and any received bogons in the filtered table.
FRR verification
# Show accepted routes from a neighbor
vtysh -c "show ip bgp neighbors 198.51.100.1 received-routes"
# Show routes filtered by inbound policy
vtysh -c "show ip bgp neighbors 198.51.100.1 filtered-routes"
# Check the prefix count per neighbor
vtysh -c "show ip bgp summary"
# Test if a specific prefix is accepted
vtysh -c "show ip bgp 10.0.0.0/8"
# Check max-prefix status
vtysh -c "show ip bgp neighbors 198.51.100.1" | grep -A2 "Maximum prefix"
For received-routes and filtered-routes to work, you must enable soft-reconfiguration inbound on the neighbor:
router bgp 64500
address-family ipv4 unicast
neighbor 198.51.100.1 soft-reconfiguration inbound
This uses extra memory (stores all received routes before filtering). On a full-table session that is roughly 2x the memory for the RIB. On production routers with limited RAM, use it selectively.
External verification
Your filters protect your own RIB. To verify what you announce to others, use external looking glasses:
- bgp.tools - search your ASN to see what prefixes you are announcing globally
- RIPE RIS - BGP routing information service, shows route visibility across collectors
- Hurricane Electric BGP Toolkit - prefix and ASN lookup
Check these after making filter changes to confirm you are not accidentally filtering legitimate routes or leaking routes you should not be advertising.
For automated monitoring, see .
BIRD2 vs FRR syntax comparison
Quick reference for translating between the two daemons:
| Filter type | BIRD2 | FRR |
|---|---|---|
| Bogon prefix | if (net ~ BOGON_PREFIXES) then reject |
ip prefix-list BOGONS deny 10.0.0.0/8 le 32 |
| Small prefix | if (net.len > 24) then reject |
ip prefix-list X deny 0.0.0.0/0 ge 25 le 32 |
| Bogon ASN | if (bgp_path ~ [64512..65534]) then reject |
bgp as-path access-list X deny _64[5-9][1-9][2-9]_ |
| Path length | if (bgp_path.len > 25) then reject |
bgp as-path access-list X deny ^([0-9]+_){25,} |
| Max-prefix | import limit 50 action disable |
neighbor X maximum-prefix 50 |
| GTSM | ttl security yes |
neighbor X ttl-security hops 1 |
| MD5 auth | password "secret" |
neighbor X password secret |
| Strip private AS | bgp_path.delete([64512..65534]) |
neighbor X remove-private-AS all |
| Apply filter | import filter name |
neighbor X prefix-list/route-map name in |
BIRD2 uses a single filter function that combines all checks. FRR splits checks across prefix-lists (prefix matching), as-path access-lists (path matching), and route-maps (combining multiple match conditions). Both approaches work. BIRD2's approach is more readable for complex policies. FRR's approach is more familiar to Cisco operators.
Something went wrong?
Session torn down by max-prefix: Check journalctl -u bird or journalctl -u frr for the limit hit message. Increase the limit if the peer legitimately grew, or investigate if it is a leak. In BIRD2, re-enable with birdc enable upstream_v4. In FRR, clear with vtysh -c "clear bgp 198.51.100.1".
Legitimate routes being filtered: Check birdc show route filtered or vtysh -c "show ip bgp neighbors X filtered-routes" to see what was dropped. Common cause: your bogon list is too aggressive, or your small-prefix threshold is rejecting legitimate /25s from a customer. Adjust the filter and reload: birdc configure or vtysh -c "write memory" then systemctl reload frr.
GTSM rejecting a valid peer: Both sides must enable GTSM. If one side has it on and the other does not, packets arrive with TTL 1 and get dropped by the GTSM-enabled side. Check with tcpdump -i eth0 port 179 -v and look at the TTL value.
Filters not taking effect after change: In BIRD2, run birdc configure to reload. In FRR, if you change a prefix-list or route-map, trigger a soft reset: vtysh -c "clear bgp 198.51.100.1 in". Without this, existing routes in the RIB are not re-evaluated against the new filter.
Logs: Both daemons log through systemd journal by default.
# BIRD2
journalctl -u bird -f
# FRR
journalctl -u frr -f
The print statements in BIRD2 filter functions write to the bird log. In FRR, enable debug logging with debug bgp updates in vtysh for temporary troubleshooting. Disable it when done because it generates large amounts of log output.
This article is part of the BGP Bring Your Own IP on a VPS series.
Copyright 2026 Virtua.Cloud. All rights reserved. This content is original work by the Virtua.Cloud team. Reproduction, republication, or redistribution without written permission is prohibited.
Ready to try it yourself?
Deploy your own server in seconds. Linux, Windows, or FreeBSD.
See VPS Plans