One of my clients has a small network using RIPv2 as an IGP -- because the vendor of their older layer 3 switches wanted nearly twice as much money if a license for OSPF, etc, was included.

The network has a ring of four layer 3 switches, and two firewalls; two layer 3 switches connected to each firewall, and the two layer 3 switches on each side of the firewalls connected to each other. There are also a couple of firewall-specific segments for each firewall (DMZs).

Firewalls being firewalls, they are quite keen to see symmetric routing of traffic -- ie, return traffic comes in on the same interface that the outgoing traffic went out. The ring of layer 3 switches and firewalls obviously presents several "equal cost" paths by default, so a priority of planned (symmetric!) path selection was implemented on the firewalls (as the devices that care) using RIP offset lists. There are both "out" (ie, announce) offset lists to ensure symmetric traffic flow between combinations of layer 3 switches, and also "in" (ie, receive) offset lists to attempt to ensure symmetric traffic flow between the "firewall local" network segments (DMZs).

During a recent partial outage (due to sudden hardware failure of another device) it was discovered that the remote firewall to local firewall's DMZ traffic flows were not working properly, which turned to be due to one of the firewalls having picked the wrong interface to send that DMZ traffic. At the time a quick work around was implemented using static routes to override the RIP path selection -- but once the dust settled I went looking for a reason why the configuration planned for that scenario did not just work.

The firewalls are using the ripd from Quagga -- a relatively old version as they have been in production for some years, but checking the current Quagga source the same effective behaviour still seems to apply in the current (2016-03-23) source.

The DMZ-DMZ traffic flows offset lists were implemented with something like:

access-list dmz-traffic permit A.B.C.D/N
[....]
access-list dmz-traffic deny any

router rip
    offset-list dmz-traffic in 1 bnx1

where bnx1 was the interface that was undesired -- ie with the hope that the other interface (bnx0 in this case) would be used instead (due to its metric being "1 less" than the bnx1 metric).

However it turns out with Quagga that "offset-list NAME in 1 [INTERFACE]" does not actually have any effect in Quagga, because:

  • by default RIP adds the interface metric to incoming prefixes

  • the default interface metric is 1 (in the version my client is running; see below)

  • in Quagga's ripd, the offset-list value is applied instead of adding the interface metric.

In particular ripd/ripd.c around line 556, contains the comment:

/* If offset-list does not modify the metric use interface's
   metric. */

and code that checks the return value of rip_offset_list_apply_in() and only adds the interface metric if rip_offset_list_apply_in() returned 0, indicating that the offset list did not change the metric value.

So offset-list NAME in 1 [INTERFACE] adds 1 if the offset list matches; but if the offset list did not match, then 1 (the default interface metric) would have been added anyway. So offset-list NAME in 1 [INTERFACE] makes no difference to the calculated metric -- 1 is added whether or not the offset-list matched. It is a "No-Op".

To actually affect the incoming metric by more than what would happen by default it is necessary to use:

offset-list NAME in 2 [INTERFACE]

in Quagga.

Outbound offset lists (ie, offset-list NAME out 1 [INTERFACE]) behave differently, because by default nothing is added to the out going announcements, so adding 1 to them will increase the metric beyond the default value. As intended. Fortunately the various "layer 3 switch to layer 3 switch" paths on my client network were all implemented using out offset lists, and worked fine. (The DMZ traffic paths were implemented with an incoming offset list simply because it seemed an easy way to keep the two prefix lists separate, and thus more consistent on the two firewalls.)

While I have not verified it on actual hardware (and it appears completely undocumented for both Quagga and Cisco AFAICT) Quagga's handling of incoming offset list values appears to be different behaviour from what happens on Cisco routers.

For instance routerlabs.de's RIPv4 Manipulation of the Metric with Offset Lists example, apparently using Cisco 7200s, shows the commands:

access-list 10 permit 172.17.0.10 0.0.0.0
router rip
    offset-list 10 in 5 Serial 1/0

changing the metric of 172.17.0.10 from:

R       172.17.0.10 [120/1] via 192.168.100.2, 00:00:05, Serial1/0

to:

R       172.17.0.10 [120/6] via 192.168.100.2, 00:00:00, Serial1/0

Ie, from 1 to 6. So that's 1 (for the interface) plus 5 (for the offset-list) is 6. (There's also similar behaviour in this Journey of a Network Engineer post, which also seems to be Cisco -- eg talking about CCIE, a Cisco certification. And this Cisco forum thread on RIP, which shows what appears to be a screenshot of actual Cisco routers -- with "offset-list 1 in 10" taking the metric of 2.2.2.0/24 from 1 to 11.)

Which implies that on Cisco the offset-list NAME in N value is in addition to the interface value. That is intuitively what I would expect from an offset list (ie, "adjust by N more", rather than "replace interface metric adjustment").

Possibly this is a (very longstanding?) bug in Quagga (which AFAICT has not been reported; I have not reported it either since it is possibly too late to change without breaking people's configurations -- and it would not make much difference to my client as they are just going to change their offset-list to say "2" rather than "1" rather than trying to install a more modern Quagga). But it did seem worth recording it somewhere one could find it while searching on the Internet, because if someone else had recorded it, it would have saved me a bunch of time and confusion!

Sidebar: Quagga interface metrics and RIP

A few years ago, the default interface metric was set to 0 (apparently to match Linux's defaults). This caused problems for RIP convergence. So a special case was added to the ripd/ripd.c code to treat an interface metric of 0 as if it were 1 (as suggested here, and agreed here; patch as applied).

But the special behaviour of offset-list NAME in seems to have been there since the first revision checked into git through until now, with only some minor formatting changes.