clifford.network / toolbox

free library

Routing Tricks

The routing you never think you will need until the day you do: sending one source out of one ISP and everything else out of the other, announcing a prefix only when the primary dies, draining a BGP session before you touch it, NAT between two VRFs. Each one is the problem, the trick, a config template with documentation addresses, and the trap that catches people.

Steer by sourceIOS XE

VPN out one ISP, everything else out the other

The problem

A router has two internet links. Site-to-site VPN traffic should use one and general internet the other, each should fail over to the other link, and NAT has to follow whichever link the traffic actually leaves by.

The trick

  • Policy-route the VPN traffic on the LAN interface with set ip next-hop verify-availability tied to an IP SLA track through ISP B. When the probe fails, the route-map stops matching and normal routing takes over.
  • Default route: a tracked static to ISP A, with a floating static (distance 100) to ISP B behind it.
  • NAT: two overload rules, each tied to a route-map that matches the inside ACL and match interface for one exit. Traffic gets the address of the link it really leaves by.
  • A deny line in the NAT ACL keeps the VPN traffic out of PAT. Without it, the traffic is translated before encryption, no longer matches the crypto ACL, and goes to the internet in clear.
  • Two plain overload rules that share one ACL do not split by exit: the first one wins and traffic leaving ISP B carries ISP A's address. That is why the route-maps match the interface.
  • After a failover, old translations stay until they time out. Clear them in the failback procedure if it matters.
  • Simpler alternative: a tracked static route for the remote VPN subnet via ISP B, no PBR. You lose per-source control. Cisco's own advice for this scenario is that two routers, one per ISP, is simpler still.
Config
ip sla 10
 icmp-echo 192.0.2.53 source-interface GigabitEthernet0/0/1
ip sla schedule 10 life forever start-time now
track 10 ip sla 10 reachability
ip sla 20
 icmp-echo 198.51.100.1 source-interface GigabitEthernet0/0/2
ip sla schedule 20 life forever start-time now
track 20 ip sla 20 reachability
!
ip access-list extended VPN-TRAFFIC
 permit ip 10.1.0.0 0.0.255.255 10.2.0.0 0.0.255.255
route-map PBR-VPN permit 10
 match ip address VPN-TRAFFIC
 set ip next-hop verify-availability 198.51.100.1 1 track 20
interface GigabitEthernet0/0/0
 description LAN
 ip nat inside
 ip policy route-map PBR-VPN
!
ip route 192.0.2.53 255.255.255.255 203.0.113.1
ip route 192.0.2.53 255.255.255.255 Null0 250
ip route 0.0.0.0 0.0.0.0 203.0.113.1 track 10
ip route 0.0.0.0 0.0.0.0 198.51.100.1 100
!
ip access-list extended PAT
 deny   ip 10.1.0.0 0.0.255.255 10.2.0.0 0.0.255.255
 permit ip 10.1.0.0 0.0.255.255 any
route-map NAT-ISP-A permit 10
 match ip address PAT
 match interface GigabitEthernet0/0/1
route-map NAT-ISP-B permit 10
 match ip address PAT
 match interface GigabitEthernet0/0/2
ip nat inside source route-map NAT-ISP-A interface GigabitEthernet0/0/1 overload
ip nat inside source route-map NAT-ISP-B interface GigabitEthernet0/0/2 overload
Verify
show route-map PBR-VPN
show track brief
show ip nat translations
show crypto session

Where this comes from: Cisco TechNote "Configure PBR" (PBR and local PBR with VPN and NAT); Cisco TechNote "IOS NAT load balancing with two ISP connections".

Steer by sourceNX-OS

Pin specific flows to specific ISP links across two cores

The problem

Hosts behind a pair of Nexus cores (vPC, peer-gateway, HSRP) must reach particular remote hosts over particular ISP links, with failover. Routing protocols cannot route by source, and the ISP links don't terminate on the cores, so the cores never see a link go down.

The trick

  • Run IP SLA probes from both cores to the far end of each ISP link, with tracks and short up and down delays.
  • Put the same PBR route-map on both cores: with peer-gateway, a host's traffic can land on either one.
  • Each sequence matches one source and destination pair and lists two next hops in order, each with verify-availability and its track, plus force-order so the first available one in the list always wins.
  • Add host routes so each probe target is reached through the right link, and mirror the policy at the far site for return traffic.
Config
feature sla sender
feature pbr
ip sla 1
  icmp-echo 192.0.2.2 source-ip 192.0.2.1
ip sla schedule 1 life forever start-time now
track 1 ip sla 1 reachability
  delay up 1 down 1
ip sla 2
  icmp-echo 198.51.100.2 source-ip 198.51.100.1
ip sla schedule 2 life forever start-time now
track 2 ip sla 2 reachability
  delay up 1 down 1
!
ip access-list A-TO-C
  10 permit ip 10.10.1.0/24 10.20.1.0/24
route-map PBR permit 10
  match ip address A-TO-C
  set ip next-hop verify-availability 192.0.2.2 track 1
  set ip next-hop verify-availability 198.51.100.2 track 2 force-order
interface Vlan10
  ip policy route-map PBR
Verify
show track
show ip sla statistics
show route-map PBR
show ip policy

Where this comes from: Cisco TechNote "Configure policy-based redirect and IP SLA" (Nexus cores with vPC).

Steer by sourceIOS XE

Choose the VRF by who sent the packet

The problem

Traffic arriving on one interface belongs to different VRFs depending on its source, for example several departments or customers sharing one access link.

The trick

  • Use Multi-VRF selection with policy routing: a route-map matches the source and sets the VRF and next hop with set ip vrf, or sends it back to the global table with set ip global next-hop.
  • Define each VRF before the route-map that names it.
  • Add ip vrf receive for each VRF on the interface. Without it, packets selected into a VRF and addressed to the router itself are dropped.
Config
ip access-list standard FROM-FINANCE
 permit 10.50.0.0 0.0.255.255
route-map VRF-SELECT permit 10
 match ip address FROM-FINANCE
 set ip vrf FINANCE next-hop 192.0.2.10
route-map VRF-SELECT permit 20
 set ip global next-hop 192.0.2.1
!
interface GigabitEthernet0/0/0
 ip policy route-map VRF-SELECT
 ip vrf receive FINANCE
Verify
show route-map VRF-SELECT
show ip vrf
show ip route vrf FINANCE

Where this comes from: Cisco configuration guide "Multi-VRF Selection Using Policy-Based Routing".

Steer by sourceIOS XE

When every PBR next hop is down, traffic does not stop

The problem

Engineers expect a policy to drop traffic when its next hops are unavailable. It doesn't: the packet quietly falls back to the routing table and leaves by a path the policy was meant to avoid.

The trick

  • Know the order IOS works through set clauses: next hop, recursive next hop, interface, default next hop, default interface.
  • A recursive next hop can be any address reachable through the routing table, not just a connected one. Only one is allowed per route-map entry.
  • If nothing in the list is available, the packet is routed normally. To force a drop, finish with set interface Null0.
Config
ip access-list extended GUEST
 permit ip 10.30.0.0 0.0.255.255 any
route-map GUEST-ONLY-ISP-B permit 10
 match ip address GUEST
 set ip next-hop recursive 198.51.100.1
 set interface Null0
Verify
show route-map GUEST-ONLY-ISP-B
show ip cef 198.51.100.1

Where this comes from: Cisco configuration guide "PBR Recursive Next Hop".

Router-generated trafficIOS XE

Steering the router's own VPN traffic: the encryption trap

The problem

A router with two ISPs must bring up a VPN, or send its own pings and management traffic, over one specific link. You add local policy routing, the tunnel comes up, and nothing passes through it.

The trick

  • Router-generated traffic: local PBR chooses the exit for the packet before encryption (and for IKE), but the encrypted ESP packet is routed by the normal routing table. If that points the other way, encapsulation fails.
  • Transit traffic: interface PBR is applied twice, before and after encryption, so no extra route is needed.
  • Interface PBR is CEF-switched; local PBR is process-switched. Keep local policies narrow.
  • Practical fix: local PBR for IKE and test traffic, plus a matching tracked host route to the VPN peer so the encrypted packets follow.
  • Watch for uRPF at the provider: traffic sourced from the "other" link's address may be dropped. Cisco mentions a loopback bounce that makes local traffic look like transit, and does not advise it.
Config
ip access-list extended IKE-LOCAL
 permit udp any any eq isakmp
 permit udp any any eq non500-isakmp
route-map LOCAL-PBR permit 10
 match ip address IKE-LOCAL
 set ip next-hop verify-availability 198.51.100.1 1 track 20
ip local policy route-map LOCAL-PBR
!
! ESP follows the routing table, so point the peer the same way
ip route 192.0.2.50 255.255.255.255 198.51.100.1 track 20
Verify
show ip local policy
show crypto session
debug ip policy (lab only)

Where this comes from: Cisco TechNote "Configure PBR" (local PBR and IPsec behaviour).

Inbound BGPIOS XE

Announce a prefix only when another one disappears

The problem

A site should announce its prefix to a backup provider only when the primary path has failed. A permanent backup announcement attracts traffic all the time, even with prepending.

The trick

  • BGP conditional advertisement: neighbor x advertise-map ADVERTISE non-exist-map WATCH. The prefixes in ADVERTISE go to the backup only while the prefix in WATCH is missing from the BGP table. exist-map does the opposite.
  • The trap: if the watched prefix can still be learned another way, for example through the backup provider from the wider internet, it never disappears and the backup announcement never happens.
  • Fix it with a match as-path in the watch map anchored on the primary peer's AS, so it only counts the prefix when it comes from that peer. The AS-path match narrows a prefix match; it can't replace it.
  • Check the state with show ip bgp neighbors: it shows the condition map, the advertise map and whether it is advertising or withdrawn.
Config
ip prefix-list OUR-BLOCK seq 5 permit 203.0.113.0/24
ip prefix-list WATCH seq 5 permit 198.51.100.0/24
ip as-path access-list 30 permit ^64500_
!
route-map ADVERTISE permit 10
 match ip address prefix-list OUR-BLOCK
route-map WATCH-PRIMARY permit 10
 match ip address prefix-list WATCH
 match as-path 30
!
router bgp 64512
 network 203.0.113.0 mask 255.255.255.0
 neighbor 192.0.2.9 remote-as 64501
 neighbor 192.0.2.9 advertise-map ADVERTISE non-exist-map WATCH-PRIMARY
Verify
show ip bgp neighbors 192.0.2.9 | include Condition-map
show ip bgp neighbors 192.0.2.9 advertised-routes

Where this comes from: Cisco TechNote "Configure and verify BGP conditional advertisement".

Inbound BGPIOS XE

Steer inbound traffic with tags and the origin attribute

The problem

Prepending is blunt, and some providers strip or ignore it. You want to choose which upstream announces a prefix, or tilt traffic between two upstreams, without a config change on every border router each time.

The trick

  • Tag at the origin: where a prefix enters BGP, give it an internal community meaning "upstream 1 only", "upstream 2 only" or none for both. Each upstream session's route-map announces, drops or alters by community. Moving a prefix becomes one tag change.
  • The soft version: announce to both, but set origin incomplete towards the less preferred upstream. Origin is compared after local preference and AS-path length, so it tilts traffic without removing the backup path.
  • Provider action communities: many providers accept communities that prepend or lower local preference further from you, which is often more effective than prepending yourself. Check each provider's community guide; not all support them.
  • Tag peering and transit routes differently inside your network, and decide deliberately whether a broken peering link should fail over to transit.
Config
ip community-list standard PREFER-UP1 permit 64512:101
ip community-list standard INTERNAL-TAGS permit 64512:101
ip community-list standard INTERNAL-TAGS permit 64512:102
!
route-map TO-UPSTREAM-2 permit 10
 match community PREFER-UP1
 set origin incomplete
 set comm-list INTERNAL-TAGS delete
route-map TO-UPSTREAM-2 permit 20
 set comm-list INTERNAL-TAGS delete
!
router bgp 64512
 neighbor 192.0.2.17 remote-as 64502
 neighbor 192.0.2.17 send-community
 neighbor 192.0.2.17 route-map TO-UPSTREAM-2 out
Verify
show ip bgp neighbors 192.0.2.17 advertised-routes
show ip bgp 203.0.113.0 (from a looking glass)

Where this comes from: ESNOG GORE 16 "BGP hacks" (practitioner write-up); Philip Smith, BGP multihoming techniques, NANOG 35; Cisco PfR inbound optimisation guide.

Inbound BGPIOS (legacy PfR)

Automated inbound steering, and why it moved on

The problem

Inbound load across several internet links shifts through the day, and manual prepending can't keep up with it.

The trick

  • Cisco Performance Routing did this automatically: it learned the busiest inside prefixes and measured each entrance link every 20 seconds.
  • When a link passed its threshold (75 percent by default) or went out of policy, it prepended, or added prepend communities, on the other entrances: one at a time up to six for delay, or all six at once for loss or unreachability.
  • PfR is legacy now (it lived on in IWAN and gave way to SD-WAN), but the method still works by hand: watch inbound utilisation per link, act on a few large prefixes, and prefer provider communities to raw prepends.

Where this comes from: Cisco configuration guide "BGP Inbound Optimization Using Performance Routing". Legacy feature, included for the technique.

Outbound BGPIOS XE

Shift outbound traffic in predictable amounts

The problem

An edge link is congested. Changing local preference prefix by prefix moves unpredictable amounts of traffic, and what worked on Monday is wrong by Friday.

The trick

  • Move groups, not single prefixes: match an origin AS or an AS-path pattern and lower local preference for that group on the congested exit. Big, popular origin ASes carry stable volumes; single prefixes swing week to week.
  • Use NetFlow to find which destinations fill the link, and pick large, stable ones with one best path across all exits.
  • Make path selection deterministic with bgp deterministic-med. Use bgp always-compare-med only if you understand what it does across different neighbour ASes.
  • Change policy without resetting sessions: route refresh (clear ip bgp x soft in), or inbound soft reconfiguration if you also want to inspect what was received.
Config
ip as-path access-list 40 permit _64496$
route-map FROM-ISP-A permit 10
 match as-path 40
 set local-preference 90
route-map FROM-ISP-A permit 20
!
router bgp 64512
 bgp deterministic-med
 neighbor 192.0.2.1 route-map FROM-ISP-A in
!
clear ip bgp 192.0.2.1 soft in
Verify
show ip bgp regexp _64496$
show ip bgp neighbors 192.0.2.1 | include refresh

Where this comes from: Feamster, Borkenhagen and Rexford, "Guidelines for Interdomain Traffic Engineering" (research paper with operator data).

Maintenance and migrationIOS XE, IOS XR

Drain an eBGP session before you shut it

The problem

Shutting a BGP session for maintenance drops traffic while the internet reconverges, because the routes are withdrawn before alternatives have spread.

The trick

  • Use the well-known GRACEFUL_SHUTDOWN community, 65535:0 (RFC 8326). Before the work, tag routes you send on that session with it and set local preference 0 on routes you receive over it. Wait for convergence, then shut the session.
  • Receiving side: an inbound policy on every eBGP session that sets local preference 0 for routes carrying 65535:0, so neighbours can drain you the same way.
  • Many large networks honour it, including NTT, Telia, GTT, Cogent and PCCW on the NLNOG list. Check your provider.
  • Raising MED or the IGP metric only works when the alternatives are otherwise equal, which is why the community is preferred.
  • Check whether your IOS XE release has a built-in graceful-shutdown command; otherwise use route-maps as shown.
Config
ip community-list standard GSHUT permit 65535:0
route-map FROM-PEER permit 5
 match community GSHUT
 set local-preference 0
route-map FROM-PEER permit 10
!
! at maintenance time, on the session being drained
route-map TO-PEER-DRAIN permit 10
 set community 65535:0 additive
router bgp 64512
 neighbor 192.0.2.1 send-community
 neighbor 192.0.2.1 route-map TO-PEER-DRAIN out
!
clear ip bgp 192.0.2.1 soft out
Verify
show ip bgp neighbors 192.0.2.1 advertised-routes
show ip bgp community 65535:0

Where this comes from: RFC 8326; NLNOG BGP filter guide.

Maintenance and migrationIOS XE

Change AS number without touching the neighbours

The problem

After a merger or a redesign, a router must move to a new AS number, but its eBGP neighbours can't all change their configuration on the same night.

The trick

  • Configure neighbor x local-as <old AS> on each eBGP session. Neighbours keep peering with the old AS while the router runs the new one.
  • The options no-prepend, replace-as and dual-as control whether the old AS appears in paths and whether the neighbour can use either AS during the cutover.
  • Plan the inside separately: in the worked example, the old eBGP link between the merging networks becomes iBGP between loopbacks, with the IGP extended over it and next-hop-self on the edge.
Config
router bgp 64520
 neighbor 192.0.2.13 remote-as 65100
 neighbor 192.0.2.13 local-as 64510 no-prepend replace-as dual-as
Verify
show ip bgp neighbors 192.0.2.13 | include local AS
show ip bgp neighbors 192.0.2.13 advertised-routes

Where this comes from: ipSpace, "Network Migration with BGP Local-AS" (practitioner write-up with Cisco IOS configs).

Dropping on purposeIOS XE

Black-hole an attack by destination or by source

The problem

A DDoS attack needs dropping at every edge router within a minute, faster than an access list can be written and pushed.

The trick

  • Destination RTBH: every edge router routes a reserved address to Null0. A trigger router announces the victim /32 by iBGP with a black-hole community and that reserved next hop, so every edge router forwards it into Null0.
  • Source RTBH: announce the attacker's source address the same way, and enable uRPF loose mode on the edge interfaces. The source now resolves to Null0, so uRPF drops its packets.
  • Lock down acceptance on the edge routers: /32s only, empty AS path (^$), add no-export, and drop anything else carrying the black-hole community.
  • Customer-triggered RTBH is possible, but only for prefixes each customer is authorised to announce.
  • Know uRPF: strict mode drops legitimate traffic on asymmetric paths; loose mode drops traffic from sources with no route at all.
  • History: uRPF loose mode was created for this in 2000, to drop by source on 60 routers in 60 seconds.
Config
! every edge router
ip route 192.0.2.1 255.255.255.255 Null0
interface GigabitEthernet0/0/0
 description Internet-facing
 ip verify unicast source reachable-via any
!
! trigger router
route-map RTBH-TRIGGER permit 10
 match tag 666
 set ip next-hop 192.0.2.1
 set community 64512:666 no-export
 set origin igp
router bgp 64512
 redistribute static route-map RTBH-TRIGGER
!
! to drop an attacker by source:
ip route 198.51.100.66 255.255.255.255 Null0 tag 666
Verify
show ip bgp 198.51.100.66
show ip route 198.51.100.66
show ip interface Gi0/0/0 | include verify

Where this comes from: RFC 5635 (includes a Cisco IOS configuration).

VRFs and NATIOS XE (ASR 1000, Catalyst 8000)

NAT between two VRFs

The problem

Traffic must pass from one VRF to another with NAT in between, for example a customer VRF with overlapping addresses reaching a shared WAN or services VRF.

The trick

  • Create a VASI pair: vasileft1 in one VRF and vasiright1 in the other, on a small shared subnet. Whatever leaves vasileft enters vasiright, so each VRF routes into the other with a static route via its VASI interface.
  • Apply NAT where it fits: inside on vasiright with the WAN interface as outside (the usual case), or entirely in the left VRF with vasileft as outside.
  • The TTL drops by two: once in each VRF.
  • The trap: don't translate to the WAN interface's own address. The router treats the return traffic as its own and never forwards it back through the VASI pair. Use a separate address or pool.
Config
interface vasileft1
 vrf forwarding CUST
 ip address 10.255.0.1 255.255.255.252
interface vasiright1
 vrf forwarding WAN
 ip address 10.255.0.2 255.255.255.252
 ip nat inside
interface GigabitEthernet0/0/1
 vrf forwarding WAN
 ip address 203.0.113.2 255.255.255.0
 ip nat outside
!
ip route vrf CUST 0.0.0.0 0.0.0.0 vasileft1 10.255.0.2
ip route vrf WAN 10.60.0.0 255.255.0.0 vasiright1 10.255.0.1
!
ip access-list standard CUST-NETS
 permit 10.60.0.0 0.0.255.255
ip nat pool CUST-OUT 203.0.113.10 203.0.113.10 prefix-length 24
ip nat inside source list CUST-NETS pool CUST-OUT vrf WAN overload
Verify
show ip nat translations vrf WAN
show ip route vrf CUST
show interfaces vasileft1

Where this comes from: Cisco TechNote "Configure VRF-Aware Software Infrastructure (VASI) NAT" (recertified 2026).

VRFs and NATIOS XE SD-WAN

Let a public server out of a VRF without NAT

The problem

A server with a public address sits in a service VRF (an SD-WAN service VPN, say) but must be reachable from the internet in the global table, bypassing the NAT everything else uses.

The trick

  • Replicate routes both ways: in the VRF, route-replicate from vrf global unicast connected; in the global address family, route-replicate from vrf <VRF> unicast connected. Each table then holds the other's connected routes.
  • Pair it with a data policy that matches the server's address and sets NAT bypass, so it keeps its real address.
  • In the same policy, other groups can use different internet NAT methods: a pool shared across both ISPs for one group, one ISP's interface address for another.
Config
vrf definition WEB
 rd 64512:30
 address-family ipv4
  route-replicate from vrf global unicast connected
  route-target export 64512:30
  route-target import 64512:30
!
global-address-family ipv4
 route-replicate from vrf WEB unicast connected
Verify
show ip route vrf WEB
show ip route 203.0.113.80

Where this comes from: Cisco Live APJC 2024 BRKENS-2720.