Network Security

Anti-Spoofing on the Edge: ACLs, uRPF, and RFC 2827 in Practice

Build RFC 2827 (BCP 38) anti-spoofing with an ingress ACL and uRPF strict mode, tested with real spoofed packets and live Cisco ACL log evidence.
RFC 2827 ACL catching spoofed source 1.2.3.4 with a syslog deny
In: Network Security, CCIE, Labs

A spoofed source address is the oldest trick in the network attacker's book, and it still works because most networks never check. If a packet arrives at your edge claiming to be from an address that could never legitimately live out there, your router will happily forward it - unless you told it not to. Anti-spoofing is the practice of dropping those packets at the boundary, and it is one of the highest-value, lowest-cost controls you can deploy. This article, part of the infrastructure security cluster, builds two anti-spoofing controls in a real CML lab - a uRPF strict-mode check and an RFC 2827 (BCP 38) ingress ACL - fires actual spoofed packets at them from a Debian attacker, and shows you which one gave us countable, provable evidence and which one silently did its job.

Why spoofing works, and what RFC 2827 asks of you

Source address validation is not the default. A router's job is to forward toward the destination; it does not, on its own, care whether the source address makes sense. That is what lets an attacker forge a source - to hide their origin, to bounce a reflection or amplification attack off a third party, or to impersonate a trusted internal host. RFC 2827, better known as BCP 38, is the internet community's standing request that every network filter ingress traffic so it can only source addresses that legitimately belong to it. If everyone did it, whole classes of DDoS reflection would dry up. Most do not, which is exactly why doing it at your own edge still matters.

There are two practical ways to enforce it on a Cisco router: an ingress ACL that permits only your real source prefixes, or Unicast Reverse Path Forwarding (uRPF), which checks each packet's source against the routing table automatically. We built both.

uRPF strict mode: configured, active, and honestly imperfect on virtual gear

uRPF strict mode asks a simple question of every inbound packet: if I had to send a reply to this source address, would I send it back out the same interface this packet arrived on? If yes, the source is plausible and the packet passes. If the best route to that source points out a different interface (or there is no route at all), the source is considered spoofed and the packet is dropped. The config is a single interface line:

interface Ethernet0/0
 ip verify unicast source reachable-via rx

On EDGE1 the feature is unmistakably active - it shows up in the interface's input feature list and reports the reachable-via RX mode:

EDGE1#show ip interface Ethernet0/0
  Input features: uRPF, MCI Check
  IP verify source reachable-via RX
   0 verification drops

Here is the honest finding, and we are not going to hide it: the verification drops counter stayed at 0 even when we sent spoofed packets that had no matching route. The feature is enforced - "Input features: uRPF" is real and the check is running - but the software drop counter on this Cisco IOL-XE image does not increment. This is the same virtual-platform quirk we have documented before with WRED counters on IOL: the behaviour is present, the counter is a no-op. On physical ASR or ISR hardware that counter ticks and gives you a clean number to alert on. In our lab it does not, so we could show that uRPF is on, but we could not prove a drop count with it. For that, we turned to the ACL, which counts reliably. If you want the deeper mechanics of strict versus loose mode, our dedicated article on uRPF strict vs loose goes further.

The RFC 2827 ingress ACL: the reliable proof

Where uRPF validates dynamically against the routing table, the ingress ACL takes the explicit approach: you list the source prefixes that are legitimately allowed to originate on this edge, permit them, and deny (and log) everything else. On our edge the only legitimate source subnet is 192.168.99.0/24, so:

ip access-list extended PLZ-ANTISPOOF
 permit ip 192.168.99.0 0.0.0.255 any
 deny ip any any log
interface Ethernet0/0
 ip access-group PLZ-ANTISPOOF in

Line 10 permits our real edge subnet as a source. Line 20 denies everything else and logs it. Any packet arriving on Ethernet0/0 with a source outside 192.168.99.0/24 is, by definition on this edge, spoofed - and it gets dropped and named in the log.

Firing real spoofed packets at it

To test it properly we needed genuinely spoofed traffic. There is a wrinkle worth knowing: the Linux kernel on the attacker VM runs its own reverse-path filter, so a normally-sent packet with a forged source is dropped by the host before it ever leaves the NIC. To get spoofed frames onto the wire we injected at Layer 2 with scapy, addressing the frame directly to the router's real MAC:

j@llmbits$ sudo python3 -c "from scapy.all import *; \
  sendp(Ether(dst='aa:bb:cc:00:72:00')/IP(src='1.2.3.4',dst='10.255.0.2')/ICMP(), \
  iface='ens224', count=30)"

Thirty packets, each claiming to be from 1.2.3.4 - an address that could never legitimately arrive on this edge - aimed at 10.255.0.2 behind the router. Exactly the kind of forged traffic BCP 38 exists to stop.

The proof: the deny rule catches every spoofed packet

The ACL counters tell the story cleanly. The deny line racked up matches, and the syslog named the forged source by address:

EDGE1#show ip access-lists PLZ-ANTISPOOF
Extended IP access list PLZ-ANTISPOOF
    10 permit ip 192.168.99.0 0.0.0.255 any
    20 deny ip any any log (26 matches)

EDGE1#show logging
%SEC-6-IPACCESSLOGDP: list PLZ-ANTISPOOF denied icmp 1.2.3.4 -> 10.255.0.2 (8/0), 1 packet

Twenty-six of the thirty spoofed packets hit the deny (ACL logging rate-limits and samples, which is why the count is not a perfect 30), and the %SEC-6-IPACCESSLOGDP message spells out the spoofed source, 1.2.3.4, the target, and the protocol. That is exactly what you want from an anti-spoofing control: not just a drop, but a countable, attributable, alertable record that you can feed to a SIEM. This is the reliable proof uRPF's dead counter could not give us on virtual gear.

ACL versus uRPF: the real comparison

Both enforce RFC 2827, but they have genuinely different strengths, and the choice depends on where you are deploying:

Ingress ACL (RFC 2827)
ModelExplicit - you list legitimate source prefixes
EvidenceCountable + logged (proven here)
Best forA stable edge with a known, small source range
CostMaintenance on every prefix change
uRPF
ModelDynamic - validates against the routing table
EvidenceDrop counter (a no-op on our IOL image)
Best forA routed core where prefixes change often
CostStrict mode breaks asymmetric routing

The ACL is bulletproof and countable where the legitimate source range is fixed and small - your single-homed customer edge, a server VLAN, a stub site. Its weakness is maintenance: every time the addressing changes, someone has to remember to update the ACL, and a stale anti-spoof ACL either leaks or blackholes.

uRPF is dynamic and self-maintaining - it always reflects the current routing table, so it scales to a core where prefixes come and go. Its weakness is asymmetric routing: strict mode drops legitimate traffic whose return path differs from its arrival path, which is common on a multi-homed core. That is why loose mode exists (it only checks that a route to the source exists at all, not that it points back out the same interface), trading precision for tolerance of asymmetry.

Use both at the edge

These are not mutually exclusive, and on a real edge the right answer is to run them together. On a single-homed edge, uRPF strict mode is the cleaner, lower-maintenance default - one line, no prefix list to babysit. Where the source range is genuinely fixed and you want countable, logged evidence for your SOC, add the RFC 2827 ACL alongside it. uRPF gives you the dynamic, no-maintenance baseline; the ACL gives you the explicit, auditable proof. Belt and braces at the boundary, which is precisely where spoofed traffic has to be stopped.

Ingress and egress: filter in both directions

RFC 2827 is usually discussed as ingress filtering, but the same principle applies on the way out, and deploying both is what makes you a good internet citizen rather than just a well-defended one. Ingress filtering on your edge stops forged sources arriving from outside. Egress filtering stops your own network sourcing forged packets toward the rest of the world - which is exactly the traffic that fuels reflection attacks launched from compromised hosts inside your perimeter. A host on 192.168.99.0/24 should only ever send packets sourced from that range; anything else leaving your edge is either misconfiguration or a compromised machine, and both are worth catching. The ACL model makes egress filtering just as explicit: permit your own prefixes as a source outbound, deny and log the rest.

Where to deploy, and common mistakes

Anti-spoofing belongs at the boundary between trust levels - the customer-facing edge, the link to an untrusted partner, the demarcation to the internet. It is far less useful buried in the core, where legitimate traffic from many sources transits and a strict check either has nothing to add or actively breaks asymmetric flows. A few mistakes recur:

  • Deploying uRPF strict on an asymmetric link. If return traffic for a source can legitimately arrive on a different interface than it leaves, strict mode drops it. Use loose mode there, or an ACL.
  • Forgetting the log keyword. A deny without log still drops, but you lose the attributable evidence - the whole point of the exercise for a security team. Our capture only named 1.2.3.4 because the deny line carried log.
  • Letting the ACL go stale. An anti-spoof ACL is only as good as its prefix list. When addressing changes and nobody updates the ACL, you either blackhole legitimate new sources or leave a gap. This is precisely the maintenance burden uRPF avoids.
  • Testing with kernel reverse-path filtering left on. As we hit above, a normal spoofed send never leaves a Linux host with rp_filter enabled. If your spoof test seems to "pass" with no packets arriving, the host dropped them, not the router. Inject at Layer 2 to test the router's control specifically.

Key Takeaways

  • Anti-spoofing enforces RFC 2827 / BCP 38 - drop packets whose source could never legitimately arrive on this edge, and you kill a whole class of reflection and impersonation attacks.
  • uRPF strict mode validates dynamically against the routing table (ip verify unicast source reachable-via rx). It was active on our lab ("Input features: uRPF"), but the drop counter is a no-op on virtual IOL - the feature works, the count does not tick.
  • The RFC 2827 ACL gave the reliable proof - real spoofed packets from 1.2.3.4 hit deny ip any any log (26 matches) and produced a named syslog record pointing at the forged source.
  • ACL = explicit, countable, great on a stable edge; uRPF = dynamic, self-maintaining, better on a routed core - but strict mode breaks asymmetric routing.
  • Use both at the edge - uRPF for the maintenance-free baseline, the ACL for auditable evidence.

Edge anti-spoofing is one layer of a hardened device. Continue through the infrastructure security cluster to combine it with control-plane policing, management-plane lockdown, and authenticated logging.

Written by
More from Ping Labz
Private VLAN association with isolated and community secondaries
VLAN

Private VLANs: Isolating Hosts That Share a Subnet

Private VLANs isolate hosts that share a subnet, at Layer 2, without a subnet per host. Primary, isolated, and community secondary VLANs on Cisco IOS XE, with the classic DMZ use case and real show output.
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Ping Labz.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.