Interesting Observations of IOS(-XE) ACL CLI and Command Syntax
2020-11-26
My initial contact for the things shown in this article comes from trying to parse and generate ACLs. Many of the things here may not bother you, if you are a pure CLI user and are not generating configs. This article does not claim to be the ultimate source for all the weird details of ACL syntax behaviour. This is based on my experience which primarily comes from IOS 15.7, IOS-XE 03.16, IOS-XE 16.09 and 16.12. IOS-XE 16.12 changed a lot in regards to sequence numbers. So there will be several sections that will be divided into a pre IOS-XE 16.12 part and a post IOS-XE 16.12 part. the regular IOS can be considered a part of the pre IOS-XE 16.12 sections, because I have not found differences between those.
General things
An Access Control List is a series of entries. Each entry matches some parts of a the packet headers. Each entry is either a remark or a permit or deny action. (I will ignore reflexive ACLs and the like here.)
Sequence Numbers
Each entry in an ACL has a sequence number. By default the first entry starts at 10 and every further entry has a number that is 10 higher.
When modifying entries you can specify a sequence number at which position you want to add something. So if you want to insert something between entry 10 and 20 you could choose a unused number between 10 and 20, e.g. 15.
Resequencing
But what happens if you want to insert between two entries where there is no
free sequence number? For Legacy IP ACLs you can do a
ip access-list <type> <name> resequence
which renumbers the entries so that
each entry is now 10 numbers apart again.
But that is not implemented for IPv6... A workaround is to recreate the ACL. But that is annoying.
IPv4
Pre IOS-XE 16.12
On IOS and IOS-XE the IPv4 sequence numbers are not a part the config.
This means that you have to do show ip access-list ...
to see the sequence numbers.
They are generated at runtime.
This also means that the sequence numbers change after a reboot.
Post IOS-XE 16.12
Sequence numbers are now a part of the configuration. The implications will be discussed later.
IPv6
Pre IOS-XE 16.12
IPv6 sequence numbers can be a part of the config. They are shown on a per entry basis if the sequence number is not exactly 10 bigger than the previous one.
If you enter the commands:
ipv6 access-list test
sequence 10 permit ipv6 host 2001:db8::1 any
sequence 15 permit ipv6 host 2001:db8::2 any
sequence 20 permit ipv6 host 2001:db8::3 any
sequence 25 permit ipv6 host 2001:db8::4 any
sequence 35 permit ipv6 host 2001:db8::5 any
sequence 40 permit ipv6 host 2001:db8::6 any
Then the ACL in the config is this:
ipv6 access-list test
permit ipv6 host 2001:DB8::1 any
sequence 15 permit ipv6 host 2001:DB8::2 any
sequence 20 permit ipv6 host 2001:DB8::3 any
sequence 25 permit ipv6 host 2001:DB8::4 any
permit ipv6 host 2001:DB8::5 any
sequence 40 permit ipv6 host 2001:DB8::6 any
The 2001:db8::2
, 2001:db8::3
and 2001:db8::4
have numbers because their
distance to the sequence number of the previous entry is 5.
2001:db8::5
has no number because the distance is 10, and 2001:db8::6
has a
distance of 5 and therefore has a sequence number.
The show ipv6 access-list test
command shows you all sequence numbers,
but at the end of the entry, not at the beginning like your config file does it.
IPv6 access list test
permit ipv6 host 2001:DB8::1 any sequence 10
permit ipv6 host 2001:DB8::2 any sequence 15
permit ipv6 host 2001:DB8::3 any sequence 20
permit ipv6 host 2001:DB8::4 any sequence 25
permit ipv6 host 2001:DB8::5 any sequence 35
permit ipv6 host 2001:DB8::6 any sequence 40
Post IOS-XE 16.12
In IOS-XE 16.12 the sequence numbers are always shown in the config. So there is at least one point where IOS got more consistent.
ipv6 access-list test
sequence 10 permit ipv6 host 2001:DB8::1 any
sequence 15 permit ipv6 host 2001:DB8::2 any
sequence 20 permit ipv6 host 2001:DB8::3 any
sequence 25 permit ipv6 host 2001:DB8::4 any
sequence 35 permit ipv6 host 2001:DB8::5 any
sequence 40 permit ipv6 host 2001:DB8::6 any
But sadly the output of the show ipv6 access-list
command still puts the
sequence number at the end of the entries.
IPv4 Remarks
IPv4 ACL Remarks do not have their own sequence numbers.
Pre IOS-XE 16.12
This means inserting remarks in IPv4 ACLs at a specific position does not work.
Only permit
and deny
are allowed.
asr920(config-ext-nacl)#42 ?
deny Specify packets to reject
permit Specify packets to forward
If you want to add a remark somewhere in the middle you have to delete the whole ACL, and insert the remark at the correct position while recreating the ACL.
But if you want to insert a new entry with a remark you can work your way around this issue. First you insert a remark without a sequence number and insert an entry with a sequence number and that remark will end up at the same position as the other entry.
ip access-list extended abcd
10 permit ip host 10.0.0.0 any
20 permit ip host 10.0.0.1 any
remark test
15 permit ip host 10.0.0.2 any
results in
ip access-list extended abcd
permit ip host 10.0.0.0 any
remark test
permit ip host 10.0.0.2 any
permit ip host 10.0.0.1 any
Post IOS-XE 16.12
In IOS-XE 16.12 every entry got a sequence number. But as mentioned earlier, IPv4 remarks did not have sequence numbers. So Cisco "solved" that. An ACL now looks like this in the config:
ip access-list extended remark-seq-numbers
10 remark foo
10 permit ip host 10.0.0.0 any
20 remark bar
20 permit ip host 10.0.0.42 any
Also inserting remarks at a specific sequence number works now (to some degree).
If you enter 15 remark test
for the ACL above you get this:
ip access-list extended remark-seq-numbers
10 remark foo
10 permit ip host 10.0.0.0 any
20 remark bar
20 remark asdf
20 permit ip host 10.0.0.42 any
15 remark test
Our remark is now at the end, where it does not belong. This change in IOS-XE 16.12 does result in sequence numbers appearing multiple times and being out of order.
Reusing the same sequence number
When you want to replace an entry in an IPv6 ACL with an other one you can just use the same sequence number and the entry will be replaced.
If you try that with an IPv4 permit/deny entry you get this response:
% Duplicate sequence number
%Failed to add ace to access-list
(yes, the space after the %
in the first line and the lack thereof in the
second is not a copy+paste error.)
However if you do this in IOS-XE 16.12 with a remark line it works just fine and it replaces the remark.
Singular/Plural in the ACL show commands
While the config commands for ACLs are ip[v6] access-list
the show
-commands are
show ip access-lists
with a s
at the end and show ipv6 access-list
without
a s
at the end.
c1111-lab#show ip access-lists
Standard IP access list 2
...
c1111-lab#show ipv6 access-list
IPv6 access list test
...
c1111-lab#show ipv6 access-lists
^
% Invalid input detected at '^' marker.
ACL naming: numbers vs names
There is not only the difference between standard and extended ACLs, there is also a difference between ACLs with a name and ACLs with a number. Which numbers can be given to an ACL depends on their type.
asr920(config)#access-list ?
<1-99> IP standard access list
<100-199> IP extended access list
<1300-1999> IP standard access list (expanded range)
<2000-2699> IP extended access list (expanded range)
<2700-2799> MPLS access list
<snip>
To make things more complicated the numbers for standard and extended ACLs each have 2 seperate ranges.
A little tip at this point: do not use numbered ACLs. Use ACLs with names,
because you can give them a name that hopefully tells you and others what this
ACL is for. Or can you remember what ACL 42 was for? and where it is used?
(good luck with sh run | incl 42
for large configs)
Standard vs Extended Numbered ACL Config format
Pre IOS-XE 16.12
If you use an extended ACL with a number (please don't) you probably enter
ip access-list extended 101
permit ip host 10.0.0.0 any
and that is exactly what will end up in your config. However if you want to use a standard ACL with a number (pls dont) you enter
ip access-list standard 1
permit host 10.0.0.0
And you will end up with this in your config:
access-list 1 permit 10.0.0.0
post IOS-XE 16.12
This format is no longer used in IOS XE 16.12, it has been changed to the same format as named ACLs are using and it looks like this:
ip access-list standard 1
10 permit 10.0.0.0
This is nice, because it is less inconsistent. But unless you have absolutly no legacy gear you have to support it anyways.
Remarks
Remarks are very handy when trying to understand lengthy ACLs (lengthy in my case
sometimes means hundreds of lines, but others might laught at that).
Sadly IOS does not show remarks with show ip access-list
, for those you have
to take a look into your config.
Interface-ACLs that do not exist
If you delete an ACL that is configured on an interface, the config line is not deleted from the interface. But the interface now accepts all traffic.
asr920(config)#ip access-list extended delete-test
asr920(config-ext-nacl)#deny ip any any
asr920(config-ext-nacl)#do sh run int Gi0/0/0
interface GigabitEthernet0/0/0
ip address dhcp
ip access-group delete-test in
negotiation auto
end
asr920(config-ext-nacl)#do sh ip access-list delete-test
Extended IP access list delete-test
10 deny ip any any
asr920(config-ext-nacl)#do ping 10.0.0.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.0.1, timeout is 2 seconds:
.....
Success rate is 0 percent (0/5)
asr920(config-ext-nacl)#exit
asr920(config)#no ip access-list extended delete-test
asr920(config)#do sh ip access-list delete-test
asr920(config)#do sh run int Gi0/0/0
interface GigabitEthernet0/0/0
ip address dhcp
ip access-group delete-test in
negotiation auto
end
asr920(config)#do ping 10.0.0.1
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.0.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/4 ms
IPv4 ACLs that only consist of remarks
But what if the ACL exists but there is no action statement in an ACL at all? We can build such an ACL that is only a remark. The ACL shows up in the config, but the default deny does not deny all traffic. But when we add an action the implicit deny suddenly works.
asr920(config)#ip access-list extended noentry
asr920(config-ext-nacl)#remark test1
asr920(config)#do sh run | section ip access-list extended noentry
ip access-list extended noentry
remark test1
asr920(config-std-nacl)#in Gi0/0/0
asr920(config-if)#ip access-group noentry in
asr920(config-if)#do ping 10.0.0.42
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.0.42, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/2/4 ms
asr920(config-if)#exit
asr920(config)#ip access-list extended noentry
asr920(config-ext-nacl)#permit ip host 192.168.0.0 any
asr920(config-ext-nacl)#do ping 10.0.0.42
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.0.0.42, timeout is 2 seconds:
.....
Success rate is 0 percent (0/5)
Default settings for config sections that can have an ACL
Things like SSH, NTP, SNMP, NETCONF, ... may (and probably should) be secured by ACLs. But by default they are not protected and answer to everything. This means you want to have an ACL for all of them. For IPv4 and IPv6. If you add the first IPv6 address to a device and you dont have an IPv6 ACL for those services configured then your device might be reachable via IPv6. So even if you haven't started with IPv6 for management and monitoring you have to keep in mind that your router listens on every address and if you have IPv6 enabled on any interface your router might be reachable via IPv6.
Port numbers
The router converts some port numbers to names. This is annoying if you
want to parse and compare that. At least they match up with the services list
published by IANA, altough in some cases the service-name column does not work
and you have to use one of the aliases (e.g. port 80 is www
instead of http
)
It's just an other piece that makes your parser a bit more complex.
Order of entries in standard ACLs
Standard ACLS are funny. They automagically put single addresses in front of larger prefixes. It looks like those changes are done in a way that does not influence what is permitted and denied by the ACL, but it makes some things hard to read, because now everything is out of order.
Entering
ip access-list standard order1
permit 10.0.0.0 0.0.0.255
permit host 10.0.42.0
results in this config:
ip access-list standard order1
permit 10.0.42.0
permit 10.0.0.0 0.0.0.255
But because that is not confusing enough, you can always add remarks to make things easier to understand...
These commands
ip access-list standard order2
permit 10.0.0.0 0.0.0.255
remark test1
Result in the expected config:
ip access-list standard order2
permit 10.0.0.0 0.0.0.255
remark test1
Add an other address, e.g. this:
permit 10.0.42.0
And your config now looks like this:
ip access-list standard order2
remark test1
permit 10.0.42.0
permit 10.0.0.0 0.0.0.255
This is because the remark is always attached to the line after it (not adding a line after it is probably a corner case) and when the single address is added it is attached to this line. But single addresses are put to the top and the remark with them.
Putting one remark in front of several entries can screw you up beautifully.
In this example we have 2 groups, called group1
and group2
Each group has a single address and a prefix in it.
One could now build an ACL that looks like this.
ip access-list standard out-of-order
remark group1
permit 10.0.1.0 0.0.0.255
permit 10.0.0.0
remark group2
permit 10.0.2.0
permit 10.0.3.0 0.0.0.255
What ends up in the config is this:
ip access-list standard out-of-order
remark group2
permit 10.0.2.0
permit 10.0.0.0
remark group1
permit 10.0.1.0 0.0.0.255
permit 10.0.3.0 0.0.0.255
It looks like the groups have switched positions and 10.0.0.0
and
10.0.3.0 0.0.0.255
have changed groups.
Good luck reverse engineering ancient firewall rules...
The sequence numbers in IOS-XE 16.12 can explain what happens here.
ip access-list standard out-of-order
30 remark group2
30 permit 10.0.2.0
20 permit 10.0.0.0
10 remark group1
10 permit 10.0.1.0 0.0.0.255
40 permit 10.0.3.0 0.0.0.255