In this post I want to discuss the use and configuration of Unicast Reverse Path Forwarding. Let’s just call it uRPF from here on. I will be using a very simple topology with only a few static routes so I can easily change the direction of the packet flow when needed and easily influence the known routes in the RIB.

uRPF introduction

uRPF is a security feature for routers and multi-layer switches which helps with mitigating malicious IP traffic. It works by verifying if the source IP address of a received packet is reachable. If the source address is not known, the packet is dropped. The idea of uRPF is to function as a passive mitigation tool against Denial of Service attacks. This is passive, compared to the alternative of manually upholding ACL’s to filter your traffic. With ACL’s, if new subnets are added to the network, you would manually have to update the ACL’s. This is not the case with uRPF.

uRPF makes use of the Forwarding Information Base (FIB), which is part of Cisco Express Forwarding (CEF). Without CEF, uRPF won’t be able to work. This is why uRPF is only available on higher end multi-layer switches and routers. Because of the way it is built, CEF is less CPU intensive than traditional packet switching techniques. The table lookup performed by CEF, uses a technique called Multiway Trie. This makes it very efficient and quick.

Modes of Operation

uRPF has 3 modes of operation. These are:

  • Strict mode
  • Loose mode
  • VRF mode

The CLI commands used to activate uRPF unfortunately do not match these modes. I will explain the modes and refer to which CLI command activates that particular mode.

Strict mode

The Strict mode of uRPF works by only accepting packets where the reverse lookup of the source of the packet points to the same interface as where the packet is received on. This is the traditional operation of uRPF. Strict mode is often used on a network boundary, where you mostly have symmetrical routing. This is also called a single-homed network. With Strict mode, it is still possible to have multiple interfaces that connect to the upstream ISP, as long as they are of equal cost. Being of equal cost implies that these routes are all “best” routes, and are all added to the RIB (Routing table), which in turn is mirrored to the FIB.

The command to activate uRPF Strict mode is as follows:

Router1(config-if)#ip verify unicast source reachable-via rx

You configure uRPF under an interface or under a SVI (Interface VLAN) on multi-layer switches.

Loose mode

The loose mode of uRPF wasn’t available when uRPF was originally built. The intention of uRPF was to be used on the customer-to-internet network boundary. This was a problem for ISP-to-ISP connections, because BGP routing between different Autonomous Systems (AS) might very well be asymmetric. Meaning that you might send traffic on a certain interface, but receive return traffic of the same session on another interface. To explain this as clearly as possible: the network admins of AS 1000 think it’s best to deliver to AS 2000 over a certain connection while the network admins of AS 2000 think it’s best to respond to AS 1000 using a different connection.

When uRPF Strict mode is used, egress traffic will only be allowed when it flows through the ingress port. To support asymmetric routing, uRPF Loose mode was invented. Loose mode simply allows egress traffic belonging to the same session to be sent on another port it was received on, which is the definition of asymmetric routing.

The CLI command to enable Loose mode is as follows:

Router1(config-if)#ip verify unicast source reachable-via any

As you can see, the only difference is the use of the keyword any, instead of rx used for Strict mode. It is also clear that there is not a single reference to either Strict or Loose. Just keep that in mind.

VRF mode

The VRF mode of uRPF enables the possibility to query a dedicated Virtual Routing and Forwarding (VRF) table, instead of querying the global RIB. Either Strict or Loose mode can be used with VRF mode.

Configuration and examples

Let’s get busy with the configuration, where we’ll experiment with the different modes of operation, and see what happens when we deliberately mess up the topology

Here’s the topology we’re going to use.

uRPF

As mentioned in the beginning, I will be using a very simple topology with only static routing, so we can easily adjust the parameters of this topology.

  • Router1 has a static route pointing 10.2.2.0/30 to its neighbor, Router2.
  • Router3 has a default route point to Router2,
  • Router2 has both networks of Router1 and Router3 directly connected, so it doesn’t need any routing.
  • MLS1 is a multi-layer switch, which only needs a static route for the 10.2.2.0/30 network, since it is not directly connected.

We’re going to be testing uRPF by generating traffic from Router1 to Router3. Here’s the relevant bare configuration for this topology:

Router1:

ip route 10.2.2.0 255.255.255.252 10.1.1.2

interface Ethernet0/0
 ip address 10.1.1.1 255.255.255.252

interface Ethernet1/1.300
 encapsulation dot1Q 300
 ip address 10.3.3.1 255.255.255.252

Router2:

interface Ethernet0/0
 ip address 10.1.1.2 255.255.255.252

interface Ethernet0/1
 ip address 10.2.2.2 255.255.255.252

Router3:

ip route 0.0.0.0 0.0.0.0 10.2.2.2

interface Ethernet0/1
 ip address 10.2.2.1 255.255.255.252

interface Ethernet1/2
 ip address 10.4.4.1 255.255.255.252

MLS1:

ip route 10.1.1.0 255.255.255.252 10.3.3.1

vlan 300

vlan 400

interface Vlan300
 ip address 10.3.3.2 255.255.255.252
!
interface Vlan400
 ip address 10.4.4.2 255.255.255.252

interface Ethernet1/1
 switchport trunk encapsulation dot1q
 switchport trunk allowed vlan 300
 switchport mode trunk

interface Ethernet1/2
 switchport access vlan 400
 switchport mode access

You will notice that the connection from Router1 to MLS 1 uses a sub-interface on Router1 connected to a trunked port on MLS1, whereas Router3 uses normal interface to MLS1, connected to an access port. There is of course no particular reason for this, I thought it would be fun to switch it up a bit.

Just to make sure everything is working in order before we enable uRPF:

Router1#ping 10.2.2.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.2.2.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 3/7/11 ms
Router3#ping 10.1.1.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.1.1.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 4/6/9 ms

Now I’ll enable uRPF Strict mode on Router1 interfaces Ethernet0/0 and Ethernet1/1.300:

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

interface Ethernet1/1.300
 encapsulation dot1Q 300
 ip address 10.3.3.1 255.255.255.252
 ip verify unicast source reachable-via rx

We can check if it enabled and working properly with the following command:

Router1#sh cef int e0/0 | i RPF|CEF
IP unicast RPF check is enabled
Input features: uRPF
IP CEF switching enabled
IP CEF switching turbo vector

With the current setup, we have symmetric routing. uRPF should allow the packets. Let’s verify that:

Router1#ping 10.2.2.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.2.2.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 2/8/31 ms

Sure enough, it is still working. Let’s also verify what is stored in the FIB:

Router1#sh ip cef 10.2.2.0
10.2.2.0/30
 nexthop 10.1.1.2 Ethernet0/0

Let’s now change the routing path of Router3, by setting the next-hop to MLS1. The topology will then look like the following.

uRPF2

Router3(config)#no ip route 0.0.0.0 0.0.0.0 10.2.2.2
Router3(config)#ip route 0.0.0.0 0.0.0.0 10.4.4.2

Before we test this, let’s set the following command on Router1 to see what happens:

Router1#debug ip cef drops rpf 
IP CEF drops for RPF debugging is on

Let’s ping again:

nRouter1#ping 10.2.2.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.2.2.1, timeout is 2 seconds:

*Apr 30 23:25:46.748: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx.
*Apr 30 23:25:48.749: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx.
*Apr 30 23:25:50.755: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx.
*Apr 30 23:25:52.758: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx.
*Apr 30 23:25:54.770: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx.
Success rate is 0 percent (0/5)

As is to be expected, uRPF will block this with Strict mode on. The ping packets from Router1 will be received by Router3, but the return packets enter on Ethernet1/1.300, which does not follow the FIB.

Always be careful with debugging on live equipment, since Cisco devices give high priority to debugging, which in turn could cause hogging of system resources to the point where the device no longer operates in a normal manner. An alternative command to check if uRPF is dropping packets is:

Router1#sh ip traffic | i Drop|RPF
Drop: 0 encapsulation failed, 0 unresolved, 0 no adjacency
0 no route, 5 unicast RPF, 0 forced drop

Right, in order to make sure we have working traffic again, whilst using asymmetric routing, is by enabling uRPF Loose mode. In our topology, we effectively only have to change the uRPF mode on interface Ethernet1/1.300, since that is where the return traffic will come from. Let’s do that first to see what happens then:

Router1(config)#int eth1/1.300
Router1(config-subif)#ip verify unicast source reachable-via any

We’ll fire off the ping again:

Router1#ping 10.2.2.1 
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.2.2.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 4/7/10 ms
Router1#
*Apr 30 23:44:52.422: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*Apr 30 23:44:52.422: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ip verify check (via-any)
*Apr 30 23:44:52.429: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*Apr 30 23:44:52.429: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ip verify check (via-any)
*Apr 30 23:44:52.440: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*Apr 30 23:44:52.440: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ip verify check (via-any)
*Apr 30 23:44:52.450: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*Apr 30 23:44:52.450: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ip verify check (via-any)
*Apr 30 23:44:52.459: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*Apr 30 23:44:52.459: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ip verify check (via-any)

Now, ping works again, using asymmetric routing. I still have the debugging for CEF RPF drops on. Take note of what happens now. Even though the packets are allowed, CEF still registers a form of dropping, the Drop-Suppress. Just keep in mind that it will log a drop, even though the packets are allowed.

uRPF with an ACL

It is possible to use an ACL in combination of uRPF to allow traffic, even though uRPF has identified the traffic to be invalid and would have dropped it. If configured to do so, uRPF will check the ACL and look for permit statements for the IP address of which an invalid RPF check has been identified and then allow it. On the other hand, you can use a deny statement to explicitely deny traffic, even though it passed the uRPF test.

To test this, let’s enable Strict mode again with our asymmetric routing example. We only have to do this on Ethernet1/1.300 on Router1:

Router1(config)#int eth1/1.300
Router1(config-subif)#ip verify unicast source reachable-via rx

In this scenario, we have broken the connectivity again. Now let’s create the ACL and apply it:

Router1(config)#ip access-list extended 100
Router1(config-ext-nacl)#permit ip 10.2.2.0 0.0.0.3 any

Router1(config)#int e1/1.300
Router1(config-subif)#ip verify unicast source reachable-via rx 100

We’ll start the ping again, and also observe the debugging.

Router1#ping 10.2.2.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.2.2.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 2/4/8 ms
Router1#
*May 1 00:11:25.892: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*May 1 00:11:25.892: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ACL check
*May 1 00:11:25.900: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*May 1 00:11:25.900: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ACL check
*May 1 00:11:25.907: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*May 1 00:11:25.907: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ACL check
*May 1 00:11:25.916: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*May 1 00:11:25.916: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ACL check
*May 1 00:11:25.918: CEF-Drop: Packet from 10.2.2.1 via Ethernet1/1.300 -- via-rx
*May 1 00:11:25.918: CEF-Drop-Suppress: Packet from 10.2.2.1 via Ethernet1/1.300 -- ACL check

The debugging will now reflect a Drop-Suppress, but remark that it went through an ACL check. The deny statements in your ACL will only come into play when the uRPF check has been invalid.

Allow default routes with uRPF

There’s one more trick within the uRPF toolbox that we need to go through. Inherently to the way uRPF works by utilizing the FIB, default routes do not count as a valid route. It is however possible to explicitly allow a default route. Let’s configure this by first reverting our configuration back to our original topology again, with one change though. We’re going to change the specific routing of 10.2.2.0/30 on Router1 to a default route point to Router2 as follows:

uRPF3

Here’s what happens when I use Strict mode on Router1 interface Ethernet0/0:

Router1#ping 10.2.2.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.2.2.1, timeout is 2 seconds:

*May 1 00:38:43.847: CEF-Drop: Packet from 10.2.2.1 via Ethernet0/0 -- uses default route.
*May 1 00:38:45.850: CEF-Drop: Packet from 10.2.2.1 via Ethernet0/0 -- uses default route.
*May 1 00:38:47.854: CEF-Drop: Packet from 10.2.2.1 via Ethernet0/0 -- uses default route.
*May 1 00:38:49.859: CEF-Drop: Packet from 10.2.2.1 via Ethernet0/0 -- uses default route.
*May 1 00:38:51.863: CEF-Drop: Packet from 10.2.2.1 via Ethernet0/0 -- uses default route.
Success rate is 0 percent (0/5)

Again, the debugging has a very nice way of telling us what’s up. In this case it detected the default route, which is not allowed without us explicitly specifying it. This works the exact same way with Loose mode.

Let’s now allow the use of a default route:

Router1(config)#int e0/0
Router1(config-if)#ip verify unicast source reachable-via rx allow-default

The ping test will now reflect the following output:

Router1#ping 10.2.2.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.2.2.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 2/2/4 ms

In this case there is no debugging output, because nothing has been dropped or has been tagged as Drop-suppress.

Well, that’s it for my explanation of uRPF! Time for bed now, since I’ve been at it for way too long. I need to wake up in 4 hours 🙂