Control-Plane Protection

Updated

September 4, 2026

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.1

Optional 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 || true

FRR 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.10

Predict: 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 DROP

Predict: 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.