A single hijacked prefix can route a chunk of payment traffic into a stranger's network for half an hour before anyone notices. For a payment provider, that is not a routing incident. It is a regulatory event, an exposed-traffic incident, and an auditor knocking on Monday morning.
This post walks through the BGP edge hygiene we ran in production at a national fintech: what we filtered, how we automated it, what broke, and a copy-paste checklist at the end.
The threat model in 200 words
If you run a public-facing AS, the internet routing system trusts you and your peers to announce only what you should announce. That trust is not enforced by default. Five classes of problem will hurt you:
- Route hijacks where a remote AS originates your prefix and pulls traffic away.
- Route leaks where a transit customer accidentally re-announces full tables to a peer.
- Sub-prefix hijacks, more-specific announcements that win longest-prefix-match.
- BGP optimizer leaks, a known class of incident where a vendor box generates synthetic more-specifics and a misconfigured peer re-advertises them.
- Static fat-fingers from your own ops team.
For a payment provider the consequences are not just availability. They include confidentiality risk on traffic that touches the cardholder data environment, PCI DSS scope expansion, and customer trust damage that lasts longer than any outage.
We treated the BGP edge as a perimeter control, not a routing function. That framing pulled the security team, the compliance team, and the network team into the same review cycle.
Five layers of edge hygiene
We layered five filters on every external eBGP session. Each one alone is insufficient. Together they cut routing-related incidents to near zero over the year we measured.
Layer 1: max-prefix per session
The cheapest, most effective control. If a peer accidentally leaks full tables to us, we want to tear the session down, not crash the box.
Cisco IOS-XR:
router bgp 65000
neighbor 203.0.113.1
remote-as 64500
address-family ipv4 unicast
maximum-prefix 5000 80 restart 5
!
Junos:
protocols {
bgp {
group EBGP-PEERS {
neighbor 203.0.113.1 {
family inet {
unicast {
prefix-limit {
maximum 5000;
teardown 80 idle-timeout 5;
}
}
}
}
}
}
}
The 80% warning threshold is the important part. You want a syslog message before you go down, not after.
Layer 2: AS-path filters
Reject any prefix whose AS-path contains your own ASN. This catches the surprisingly common case where a peer somewhere has a stale route through your AS and tries to send it back to you.
Junos:
policy-options {
as-path OWN-AS-IN-PATH ".* 65000 .*";
policy-statement DENY-OWN-AS {
from as-path OWN-AS-IN-PATH;
then reject;
}
}
Also reject paths containing private ASNs (64512 to 65534 and 4200000000 to 4294967294) on peering sessions where they have no business appearing.
Layer 3: IRR-based prefix filters
For every peer that is not a Tier 1 transit, build a per-peer prefix filter from their published as-set in IRR. If a prefix is not in their published origin set, drop it.
Generate with bgpq4:
bgpq4 -h whois.radb.net -4 -A -l PEER-EXAMPLE-IN AS-EXAMPLE
This produces a prefix-list ready to paste into Cisco IOS-XR or Junos config. Pin to a specific IRR source (RADb, RIPE, ARIN) per peer to avoid junk from less-trusted databases.
Layer 4: RPKI Route Origin Validation
RPKI ROV is the only one of these five layers that gives you cryptographic origin authentication. Configure a local validator (Routinator, rpki-client, or OctoRPKI), feed it to the routers, and drop invalid at the edge.
Cisco IOS-XR:
router bgp 65000
rpki server 192.0.2.10
transport tcp port 3323
refresh-time 600
!
address-family ipv4 unicast
bgp bestpath origin-as use validity
!
route-policy RPKI-DROP-INVALID
if validation-state is invalid then
drop
else
pass
endif
end-policy
Apply the policy in on every eBGP session. Do not "prefer valid", drop invalid. Halfway measures here are how partial hijacks slip through.
Run at least two validators. We ran three (Routinator + rpki-client + a vendor appliance) and reconciled them in monitoring. If one validator goes stale, you do not want it silently flipping prefixes to NotFound.
Layer 5: bogons, reserved, and martians
Drop RFC1918, RFC6598 (100.64.0.0/10), documentation prefixes (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24), default route (unless you genuinely accept it), and anything with a prefix length above your accepted maximum (typically /24 for IPv4 and /48 for IPv6).
This catches misconfigurations from your peers more often than malice. It is also what auditors look for in PCI DSS section 1 reviews.
Automating it with Ansible
Manual prefix-list updates do not scale past a handful of peers and they rot fast. IRR data changes daily. We built a small Ansible role that does the boring part.
Pipeline:
- Pull each peer's
as-setfrom RADb or RIPE usingbgpq4. - Render the per-peer prefix-list with a Jinja2 template.
- Diff against the running config. If the delta exceeds a threshold (say, plus or minus 10%), flag for human review instead of pushing.
- Push via Netmiko or NAPALM, with
commit confirmrollback on Junos and acommit replacewith diff preview on IOS-XR. - Re-run nightly via cron. IRR data drifts continuously.
Minimal Ansible task:
- name: Generate prefix-list for peer
ansible.builtin.command: >
bgpq4 -h whois.radb.net -4 -A
-l "{{ peer.name }}-IN"
{{ peer.as_set }}
register: prefix_list
changed_when: false
- name: Render config snippet
ansible.builtin.template:
src: peer_filter.j2
dest: "/var/network-configs/{{ peer.name }}.conf"
vars:
prefix_list_body: "{{ prefix_list.stdout }}"
- name: Sanity check delta
ansible.builtin.script: scripts/diff_check.py "{{ peer.name }}.conf"
register: delta
failed_when: delta.rc not in [0, 2]
- name: Apply config
when: delta.rc == 0
junipernetworks.junos.junos_config:
src: "/var/network-configs/{{ peer.name }}.conf"
comment: "auto-update prefix-list for {{ peer.name }}"
confirm: 5
The confirm: 5 on Junos is the safety net. If the commit breaks the session and you cannot re-commit within 5 minutes, the box rolls back automatically.
Store every generated config in git. When something goes sideways at 03:00, you want to see what changed in the last 24 hours, not guess.
Three things production taught us
Lesson 1: max-prefix saves you from your peers
A regional peer added a new aggregate to their as-set in IRR. Our nightly job picked it up and added it to the filter. The following morning their upstream withdrew the route entirely. Our session would have accepted the larger filter and idled around the new boundary. The 80% warning threshold on max-prefix fired an alert hours before the session reached the actual limit. We caught the IRR drift before anyone noticed at the routing layer.
The lesson: IRR data is necessary but not authoritative. Always wrap it in a max-prefix that reflects the peer's actual size plus some headroom, not their theoretical max.
Lesson 2: "prefer valid" is not "drop invalid"
Early in the rollout we configured RPKI to prefer valid over invalid rather than dropping invalid outright. The reasoning seemed sound at the time: do not break things on day one.
Then a partial hijack happened. A misconfigured AS announced an invalid origin for a /24 that overlapped a covered /22. "Prefer valid" picked the valid /22, but the more-specific /24 still won longest-prefix match because no valid /24 existed in the BGP table. Traffic for that /24 went to the hijacker for about 12 minutes.
We flipped to drop-invalid on every session that night. We lost a small handful of legitimate prefixes whose ROAs were misconfigured by their owners. We sent them email. They fixed it. Net incident count went down, not up.
Lesson 3: pre-commit diff validation catches typos
An AS-path regex update almost dropped a legitimate transit prefix because the regex matched too aggressively. The Ansible diff-check script flagged a delta of minus 1,200 prefixes against the previous run. We caught it before push.
If your automation does not have a "this change looks too big, ask a human" gate, build one. The cost is one extra step per change. The benefit is not paging your team at 02:00 because a regex ate the internet.
How this maps to PCI DSS
If you operate in a PCI environment, the BGP edge is in scope, even if you sometimes argue otherwise. The relevant controls map cleanly:
- Requirement 1.2 (network segmentation): A clean edge defines your perimeter. RPKI drop-invalid is the cleanest possible argument that you only accept authenticated origins.
- Requirement 10 (logging): Every RPKI validation state transition, every max-prefix warning, and every prefix-list change must reach your SIEM. Auditors will ask.
- Requirement 11.4 (intrusion detection): Anomalous prefix-count deltas, sudden session flaps, and unexpected origin AS changes are network IDS signals. Wire them to alerting.
- Requirement 6.4 (change management): Git-backed configs, pre-commit diff validation, and commit-confirm rollback are change management. Show the auditor the commit log and the rollback playbook.
Done right, network hygiene becomes compliance-by-design, not a separate workstream.
The take-home checklist
If you are setting this up in your own environment:
[ ] max-prefix per eBGP session, with 80% warning threshold
[ ] AS-path filter rejecting your own ASN in the path
[ ] AS-path filter rejecting private ASNs on public sessions
[ ] IRR prefix filter per non-transit peer, regenerated daily
[ ] RPKI ROV with drop-invalid (not prefer-valid)
[ ] At least two RPKI validators, reconciled in monitoring
[ ] Bogon and reserved prefix drops at the edge
[ ] Maximum prefix length filter (typically /24 v4, /48 v6)
[ ] Pre-commit diff validation with a "too big" abort
[ ] commit-confirm or equivalent rollback on push
[ ] Git-backed configs with audit log
[ ] Logging RPKI state transitions to SIEM
[ ] Alerting on prefix-count delta over 10 percent
[ ] Quarterly review of every peer as-set
[ ] Runbook for sudden BGP session flap
If you implement the first six items, you have removed the largest sources of BGP edge risk for your AS. Everything else is operational polish.
Final note
None of this is novel work. IRR has been around since the 1990s, RPKI since the early 2010s. What is novel is that for most payment providers, fintechs, and regulated networks, this is still not the default configuration. The gap between "everyone should be doing this" and "everyone is doing this" is where edge incidents come from.
If your AS is announced on the public internet and you cannot tick off most of the checklist above, you have homework.
I write about production network reliability, BGP edge security, and infrastructure automation. Find me on LinkedIn or reach out at berik@ashimov.com.
Top comments (0)