Introduction
An unsecured BGP session means:
- An attacker hijacking TCP/179 and injecting routes
- A peer accidentally sending you 800k prefixes and OOM-ing your router
- You accepting prefixes that should never exist on the Internet (RFC 1918, bogons)
- A neighbor announcing a prefix it doesn't own, and you sending traffic there
This tutorial gives you the 5 layers every transit operator (and every transit customer, like you with VeryCloud) should put in place. From minimum-viable to serious-pro.
Prerequisites
- A working BGP session to VeryCloud (see BIRD/FRR/Cisco/Arista tutorials)
- Your IPv4/IPv6 prefixes and an account at your RIR
- Access to your BGP daemon config
Layer 1: RPKI ROAs (Route Origin Authorization)
RPKI (RFC 6480) cryptographically links a prefix to an ASN authorized to announce it. The #1 defense against BGP hijacks.
You publish a ROA at your RIR saying:
"Prefix 192.0.2.0/24 may be announced by AS198825, max-length 24"
VeryCloud (and all serious operators) filter RPKI-invalid announcements.
Create a ROA at RIPE NCC
- Sign in at https://my.ripe.net
- Go to Resources → ROAs
- Click Create ROA
- Fill in:
- Prefix:
192.0.2.0/24 - Max length:
24(only /24 allowed) or32(all sub-divisions allowed) - Authorized ASN:
198825(the AS announcing, here VeryCloud)
- Prefix:
- Validate
⚠️ If you use your own public ASN instead of AS198825, put your ASN in the ROA. With a private AS (64512-65534), no ROA possible — rely solely on IRR.
Verification (propagation ~30 min):
curl -s "https://stat.ripe.net/data/rpki-validation/data.json?resource=AS198825&prefix=192.0.2.0/24" | jq
Your router side: RPKI validation
If you want to filter RPKI-invalid routes yourself (in addition to VeryCloud's filtering), you need a local validator. Simplest: routinator (NLnet Labs).
sudo apt install -y routinator
sudo routinator init --accept-arin-rpa
sudo systemctl enable --now routinator
Routinator exposes an RTR endpoint on TCP/3323.
BIRD 2:
roa4 table r4;
roa6 table r6;
protocol rpki rpki1 {
roa4 { table r4; };
roa6 { table r6; };
remote "127.0.0.1" port 3323;
retry keep 90;
}
filter bgp_in_v4 {
if (roa_check(r4, net, bgp_path.last) = ROA_INVALID) then reject;
accept;
}
FRR:
rpki
rpki cache 127.0.0.1 3323 preference 1
exit
router bgp 65000
address-family ipv4 unicast
neighbor 169.254.X.1 route-map RPKI-CHECK in
exit-address-family
exit
route-map RPKI-CHECK deny 10
match rpki invalid
route-map RPKI-CHECK permit 20
Layer 2: TCP-MD5 authentication
TCP-MD5 (RFC 2385) adds a MAC in every TCP segment of the BGP session. Without the shared password, impossible to inject traffic.
It's old (1998, MD5 collision-broken) but enough to block attackers trying to spoof the BGP session — they'd need to break MD5 in seconds while spoofing an exact TCP port, operationally unfeasible.
Modern successor is TCP-AO (RFC 5925), much less deployed. MD5 remains de-facto standard.
protocol bgp verycloud_v4 {
neighbor 169.254.X.1 as 198825;
password "YOUR_MD5_SECRET";
}
router bgp 65000
neighbor 169.254.X.1 password YOUR_MD5_SECRET
💡 Password must be identical byte-for-byte both sides. VeryCloud provides MD5 in the technical email.
Layer 3: Max-prefix / maximum-prefix / import limit
Hard cap on accepted prefixes. If VeryCloud accidentally sends 600k routes, your router drops the session instead of exploding.
Tuning the value:
- Default route only:
5is enough - Full BGP table v4 (~950k routes May 2026): set
1100000 - Full BGP table v6 (~200k routes May 2026): set
250000
ipv4 {
import limit 5000 action restart;
};
neighbor 169.254.X.1 maximum-prefix 5000 80 restart 30
neighbor VC-V4 maximum-routes 5000 warning-limit 4000
Layer 4: GTSM (Generalized TTL Security Mechanism)
GTSM (RFC 5082) checks the TTL of BGP packets. A BGP session between two directly-connected peers should have TTL=255. A distant attacker spoofing TCP/179 will have a lower TTL → drop.
Inapplicable simply if peer is several hops away, but usable for tunnels: it's always 1 hop in the tunnel, so TTL=255 expected.
protocol bgp verycloud_v4 {
ttl security on;
}
neighbor 169.254.X.1 ttl-security hops 1
neighbor 169.254.X.1 ttl maximum-hops 1
⚠️ Activating
ttl securityrequires disablingebgp-multihop(incompatible in some implementations). Check with VeryCloud first.
Layer 5: Prefix-list / anti-bogon filter
Explicitly reject any prefix that should never transit on the Internet: RFC 1918, link-local, multicast, doc (RFC 5737, RFC 3849).
Minimum IPv4 list:
0.0.0.0/8 # this network
10.0.0.0/8 # RFC 1918
100.64.0.0/10 # CGNAT (RFC 6598)
127.0.0.0/8 # loopback
169.254.0.0/16 # link-local
172.16.0.0/12 # RFC 1918
192.0.0.0/24 # IETF protocol assignments
192.0.2.0/24 # TEST-NET-1
192.168.0.0/16 # RFC 1918
198.18.0.0/15 # benchmark
198.51.100.0/24 # TEST-NET-2
203.0.113.0/24 # TEST-NET-3
224.0.0.0/4 # multicast
240.0.0.0/4 # reserved
Minimum IPv6 list:
::/8 # incl. ::1 loopback
64:ff9b::/96 # NAT64
100::/64 # discard prefix
2001::/23 # IETF protocol
2001:db8::/32 # documentation
fc00::/7 # ULA
fe80::/10 # link-local
ff00::/8 # multicast
💡 You can automate the list with Team Cymru Bogon Reference: a free BGP service flushing bogons in real-time.
Bonus: IRR (Internet Routing Registry)
Beyond RPKI, many operators filter on the IRR: route(6)-object in RIPE/RADB database.
To announce 192.0.2.0/24 via AS65000, create in RIPE:
route: 192.0.2.0/24
origin: AS65000
mnt-by: YOUR-MNT
source: RIPE
VeryCloud (and Hurricane Electric, Cogent, etc.) check IRR. Without route-object, many operators drop your announcements.
Minimum security checklist
| # | Layer | Mandatory? |
|---|---|---|
| 1 | ROA RPKI on your prefixes | ✅ YES |
| 2 | TCP-MD5 on the session | ✅ YES |
| 3 | maximum-prefix / import limit | ✅ YES |
| 4 | GTSM (if both sides support) | Recommended |
| 5 | Anti-bogon prefix-list inbound | ✅ YES |
| 6 | Route-object IRR | ✅ YES |
| 7 | Strict outbound filter (only your prefixes) | ✅ YES |
Without 1, 2, 3, 5, 6, 7 → don't put the session in prod. Everything else is bonus.
Troubleshooting
ROA published but routes still rejected
- Slow propagation: wait 30-60 min
- Local validator stale cache:
routinator vrps - Incorrect max-length in ROA (often forgotten)
MD5 password failing
- Invisible whitespace in password (very common when copy-pasted)
- Encoding: avoid non-ASCII chars
maximum-prefix exceeded
- Receiving more than expected: tune value
- VeryCloud side sending full table when you wanted default
Useful commands
sudo birdc show route protocol verycloud_v4
sudo vtysh -c "show rpki cache-server"
sudo vtysh -c "show rpki prefix-table"
sudo routinator vrps
Conclusion
5 layers: RPKI, MD5, max-prefix, GTSM, prefix-list. Plus IRR bonus. Sounds like a lot, but it's 30 minutes of clean config for years of peace. Every serious operator does this. Skip a layer, you take proportional risk.
Going further: ASPA (RFC 9286) to validate AS paths, BGPsec (RFC 8205) to sign announcements, MANRS routing security framework.


















