Using Cisco ACLs to match routing prefixes or just to mystify the configuration?

A decade ago route classification or filtering in Cisco IOS was commonly done with the help of access control lists (ACLs). You will still find this method in some (very) old configurations and Cisco trainings (no matter how advanced they are ;-)). I’ve run into this legacy stuff recently and I was forced once again to understand how it works. It is confusing but rather simple.

An entry in the extended IPv4 ACL has the following meaning:

permit|deny ip <network> <wildcard mask of network> <subnet mask> <wildcard mask of subnet mask>

The “source” part of the rule selects the prefixes and the “destination” part selects prefix masks (prefix lengths).

Here is an excerpt from Cisco IOS configuration where inbound BGP filtering is accomplished by a route-map:

router bgp 2
 neighbor 10.0.0.1 remote-as 1
 address-family ipv4
  neighbor 10.0.0.1 route-map FilterAnnoucements in

In the route-map an extended ACL can be used to match the routes:

route-map FilterAnnoucements permit 10
 match ip address 199

…or, preferable by all means, a prefix-list does the job:

route-map FilterAnnoucements permit 10
 match ip address prefix-list List9

Well, there is another but very obscure option – a standard ACL can be used to match the routes, but since there is only a source part in the standard ACL, only networks can be matched and prefix length is not checked whatsoever.

Let us walk through some examples which will might convince you to always use prefix-lists instead of this enigmatic ACL features.

Accept the default route only
First try with a simple standard ACL,
access-list 9 permit 0.0.0.0
It will work but 0.0.0.0 permits not only the default, but also all zero prefixes like 0.0.0.0/1, 0.0.0.0/2 up to the host route 0.0.0.0/32. So, correct answer is:
access-list 199 permit ip host 0.0.0.0 host 0.0.0.0
! ...or with prefix-list:
ip prefix-list List9 seq 5 permit 0.0.0.0/0

Allow 100.0.0.0/8 and more specifics
This ACL actually permits 100.0.0.0/6 and more specifics
access-list 9 permit 100.0.0.0 0.255.255.255
And this is the right way:
access-list 199 permit ip 100.0.0.0 0.255.255.255 255.0.0.0 0.255.255.255
By the way, this is wrong:
access-list 199 permit ip host 100.0.0.0 255.0.0.0 0.255.255.255
…cause it matches 100.0.0.0/n’s with all-zeroes in the remaining three octects only, but not 100.0.3.0/24, for example. Say, isn’t this much clearer:
ip prefix-list List9 permit 100.0.0.0/8
ip prefix-list List9 permit 100.0.0.0/8 ge 9

Allow 100.0.0.0/8 and more specifics up to /16 (/16 inclusive)
access-list 199 permit ip 100.0.0.0 0.255.255.255 255.0.0.0 0.255.0.0
!
ip prefix-list List9 permit 100.0.0.0/8
ip prefix-list List9 permit 100.0.0.0/8 le 16

Allow only 100.0.0.0/8 and 200.0.0.0/24
access-list 199 permit ip host 100.0.0.0 host 255.0.0.0
access-list 199 permit ip host 200.0.0.0 host 255.255.255.0
!
ip prefix-list List9 permit 100.0.0.0/8
ip prefix-list List9 permit 200.0.0.0/24

Allow only 200.0.0.0/24 and 200.0.1.0/24
access-list 199 permit ip 200.0.0.0 0.0.1.255 host 255.255.255.0
!
ip prefix-list List9 permit 200.0.0.0/24
ip prefix-list List9 permit 200.0.1.0/24

Reject more specifics than /24 (allow /24s), allow others
access-list 199 deny ip any 255.255.255.0 0.0.0.255
access-list 199 permit ip any any
!
ip prefix-list List9 deny 0.0.0.0/0 ge 24
ip prefix-list List9 permit 0.0.0.0/0 le 32


References:

Use Extended Access-list to filter BGP Updates by Ivan Pepelnjak
Using Extended ACLs for BGP Filtering by Brian Dennis
How do prefix-lists work? by Brian McGahan


Wanna try? Just set up a simple BGP session between two routers in your LAB and use a script that fills one of them with all the routes you’d like to play with. The following snippet of Perl code will print the static routes and the BGP networks statements that you can put on one of the BGP peers and then test your filtering skills on the other 😉

sub dd2n { unpack 'N', pack 'C4', split '\.', $_[0] }
sub n2dd { join '.', unpack 'C4', pack 'N', $_[0] }
sub subn { 1<<(32-$_[0]) }
sub mask {
   my $m = 0;
   for (my $i=32; $i>$_[0]; $i--) {
      $m <<= 1; $m += 1;
   }
   return ~$m;
}

sub f {
   my $n = $_[0]; my $l = $_[1]; my $m = $_[2];
   push (@Routes, 'ip route '.n2dd($n).' '.n2dd(mask($l))." null 0");
   push (@BgpNets, 'network '.n2dd($n).' mask '.n2dd(mask($l)));
   if ($l < $m) {
      f($n, $l+1, $m);
      f($n + subn($l+1), $l+1, $m);
   };
}

@Routes = (); @BgpNets = ();

# generate all prefixes within 192.168.0.0/16
# ...with prefix length between /16 and /24
$n = dd2n('192.168.0.0');
f($n, 16, 24); 

print join("\n", @Routes), "\n";
print join("\n", @BgpNets), "\n";

Advertisements