Control-Plane Protection
Control-Plane Protection
The data plane can forward millions of packets while a handful of exception packets kill the control plane. Control-plane protection (CoPP / CPPr / RE protect—names vary) rate-limits and classifies traffic to the device CPU. This chapter builds the model with Linux and FRR-facing labs (Containerlab, mid-2026 open stack).
Learning goals
By the end of this chapter you can:
- Distinguish transit traffic from traffic destined to the router
- List common control-plane protocols worth protecting
- Apply Linux rate limits / policers toward local services
- Design a CoPP policy matrix (class → police → action)
- Predict symptoms of over-tight vs over-loose protection
Concepts
Two paths through a router
Transit (data plane): in → FIB lookup → out
Receive (to CPU): in → "this is for me" → process
| Examples to CPU | Examples transit |
|---|---|
| BGP, OSPF, SSH, SNMP, NTP | Customer flows through the box |
| ARP to router MAC | Switched L2 through (L2 device) |
| ICMP echo to router IP | ICMP through to remote host |
| TTL expiry messages | Normal forwarded packets |
Why unprotected CPUs melt
- Routing protocol floods / session storms
- Management scans (SSH/HTTPS)
- ICMP floods to the router IP
- ARP storms
- Fragment / exception paths
Symptoms: neighbor flaps, slow CLI, missed hellos → self-inflicted outage even if bandwidth remains.
Policy matrix (start simple)
| Class | Match idea | Police | Notes |
|---|---|---|---|
| Critical routing | OSPF/BGP from known peers | generous | Never starve hellos blindly |
| Management | SSH/HTTPS from mgmt net | moderate | Jump host only |
| Monitoring | SNMP/telemetry agents | moderate | |
| Normal ICMP to RE | echo to router | low | |
| Default to RE | everything else | very low / drop |
Vendors implement class-maps + policers. Linux: nftables meter/limit, tc, or firewall rate modules.
Hardening adjacent controls
CoPP is not a substitute for:
- Interface ACLs that drop garbage early
- Management plane VRF / out-of-band
- Routing policy
- Authentication on protocols
Defense in depth: each layer different failure mode.
Lab topology
attacker ----| |---- peer (OSPF/BGP)
mgmt --------| router |
client ------| |---- transit target
name: copp-lab
topology:
nodes:
rtr:
kind: linux
image: quay.io/frrouting/frr:10.2.1
binds:
- ./config/rtr:/etc/frr
# also need iptables in image or side package
peer:
kind: linux
image: quay.io/frrouting/frr:10.2.1
binds:
- ./config/peer:/etc/frr
mgmt:
kind: linux
image: alpine:3.20
exec:
- apk add --no-cache iproute2 iputils openssh-client
attacker:
kind: linux
image: alpine:3.20
exec:
- apk add --no-cache iproute2 iputils
client:
kind: linux
image: alpine:3.20
exec:
- apk add --no-cache iproute2 iputils
target:
kind: linux
image: alpine:3.20
exec:
- apk add --no-cache iproute2 iputils
links:
- endpoints: ["rtr:eth1", "peer:eth1"]
- endpoints: ["rtr:eth2", "mgmt:eth1"]
- endpoints: ["rtr:eth3", "attacker:eth1"]
- endpoints: ["rtr:eth4", "client:eth1"]
- endpoints: ["rtr:eth5", "target:eth1"]Addressing
| Link | Subnet |
|---|---|
| rtr–peer | 10.0.0.0/30 (.1 rtr, .2 peer) |
| rtr–mgmt | 10.255.0.0/30 (.1 rtr) |
| rtr–attacker | 10.66.0.0/30 |
| rtr–client | 10.10.0.0/24 (.1 rtr, .10 client) |
| rtr–target | 10.20.0.0/24 (.1 rtr, .10 target) |
| rtr lo | 192.0.2.1/32 |
Enable forward for transit client→target; BGP or static for peer experiments.
Baseline routing (static snack)
docker exec clab-copp-lab-rtr sysctl -w net.ipv4.ip_forward=1
# addresses omitted—set on each node per table
docker exec clab-copp-lab-client ip route add default via 10.10.0.1
docker exec clab-copp-lab-target ip route add default via 10.20.0.1Optional OSPF between rtr and peer on eth1 (reuse interior-routing habits).
Linux receive-path protection (teaching CoPP)
Protect SSH to the router and ICMP to the router, without blocking transit.
# On rtr (needs iptables in container—install if FRR image lacks it)
docker exec clab-copp-lab-rtr sh -c 'command -v iptables || (apt-get update && apt-get install -y iptables)' \
2>/dev/null || trueFRR images vary; if package install is painful, run CoPP rules on an Alpine “router” with ip_forward and FRR only when needed. Pattern matters more than package manager.
docker exec clab-copp-lab-rtr sh -c '
# INPUT chain = traffic to local stack (control/management plane analogue)
iptables -N COPP 2>/dev/null || iptables -F COPP
iptables -F COPP
# BGP from peer only (TCP 179)
iptables -A COPP -p tcp -s 10.0.0.2 --dport 179 -j ACCEPT
iptables -A COPP -p tcp -s 10.0.0.2 --sport 179 -j ACCEPT
# OSPF (IP proto 89) from peer subnet
iptables -A COPP -p 89 -s 10.0.0.0/30 -j ACCEPT
# SSH from mgmt only, rate-limit new connections
iptables -A COPP -p tcp -s 10.255.0.0/30 --dport 22 -m conntrack --ctstate NEW -m limit --limit 10/min -j ACCEPT
iptables -A COPP -p tcp -s 10.255.0.0/30 --dport 22 -m conntrack --ctstate ESTABLISHED -j ACCEPT
# ICMP to self: low rate
iptables -A COPP -p icmp --icmp-type echo-request -m limit --limit 5/sec -j ACCEPT
# established to self
iptables -A COPP -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# drop other to-self
iptables -A COPP -j DROP
iptables -I INPUT 1 -j COPP
'Critical: do not put transit in INPUT. FORWARD remains separate:
docker exec clab-copp-lab-rtr sh -c '
iptables -P FORWARD ACCEPT
# or tighter ACL from previous chapter
'Predict → observe → fix
Drill 1 — Transit still works under “attack” to RE
# flood ping to router IP from attacker
docker exec clab-copp-lab-attacker sh -c 'ping -f -c 1000 10.66.0.1 || ping -c 200 10.66.0.1'
# simultaneous transit
docker exec clab-copp-lab-client ping -c 20 10.20.0.10Predict: transit mostly fine; many attacker pings dropped by limit; CLI may still respond if limits work.
Observe: iptables -L COPP -n -v counters.
Fix if transit dies: rules mistakenly in FORWARD; CPU still overloaded—tighten attacker path earlier (interface ACL).
Drill 2 — SSH from non-mgmt
docker exec clab-copp-lab-attacker sh -c 'nc -w2 10.66.0.1 22 || echo blocked'
docker exec clab-copp-lab-mgmt sh -c 'nc -w2 10.255.0.1 22 || true'Predict: attacker blocked; mgmt allowed (if sshd up).
Drill 3 — Over-tight BGP protection
Intentionally rate-limit TCP 179 too aggressively:
iptables -I COPP 1 -p tcp --dport 179 -m limit --limit 1/hour -j ACCEPT
iptables -I COPP 2 -p tcp --dport 179 -j DROPPredict: BGP session flaps or won’t establish.
Observe: vtysh -c 'show bgp summary'.
Fix: remove bad rules; document that routing classes need headroom.
Drill 4 — Peer-only OSPF
Send spoofed or neighbor attempt from attacker interface (concept): OSPF from non-peer should not be accepted.
Observe: no adjacency from wrong interface; INPUT drops proto 89 from wrong sources.
FRR-side levers (awareness)
| Lever | Role |
|---|---|
ttl-security / GTSM |
BGP hop check |
| MD5/TCP-AO (as available) | Session auth |
| Interface ACLs + passive-interface | Reduce attack surface |
| Max prefix | Limit RIB blowups |
| BFD timers sanity | Avoid micro-flap storms |
These complement packet CoPP; they do not replace it.
Building a CoPP document
Device role: lab edge
Classes:
- ROUTING_PEERS: list IPs/protos, police 1Mbps, prio high
- MGMT: 10.255.0.0/30 tcp/22, 100kbps
- ICMP_RE: 64kbps
- DEFAULT_RE: 32kbps drop excess
Exceptions: console always
Change window: test in lab before prod
Rollback: saved ruleset file in git
Store as copp-policy.md beside topology.
IPv6 notes
Same model: traffic to link-local and global router addresses hits receive path. ND/RA are control-plane-adjacent—filter carefully or you break neighbor discovery.
Optional SR Linux / NOS CoPP
Commercial and community NOS expose CoPP/CPM filter policies in YANG. Map your matrix classes to their filter entries; validate with protocol sessions + flood tests. Free path still proves the idea on Linux.
Verification checklist
| Check | Intent |
|---|---|
| Transit ping during RE flood | Data vs control separation |
| Known peer protocols up | Not over-policed |
| Unknown src management down | Surface reduced |
| Counters move on floods | Policy hit |
| Rollback file exists | Safety |
Common mistakes
| Mistake | Symptom |
|---|---|
| Policing FORWARD as “CoPP” | Kills customers, not RE |
| Starving BGP/OSPF | Random reconvergence |
| No mgmt exception | Locked out |
| Only cloud dashboards, no on-box | Outage of mgmt path blind |
| Copy-paste prod CoPP into lab scale | Lab protocols die |
Summary
- Control-plane protection polices traffic to the CPU, not transit
- Build a class matrix before touching syntax
- Linux INPUT rate limits teach the model; NOS CoPP is the same idea
- Always retest routing adjacencies after tightening
- Combine with mgmt ACLs, auth, and max-prefix for defense in depth
Next: QoS vocabulary—how queues and marks express intent without vendor exam laundry lists.