One thing that’s always bugged me about IPTables is the lack of a way to use groups when writing rules, which can complicate things if you’ve got a potentially large rulebase. One way round this is to use something like fwbuilder, which gives you a graphical interface not unlike Checkpoint‘s SmartDashboard GUI for their Firewall-1 devices. The downside to this, though, is that the resulting IPTables ruleset is far from legible – which, to be fair, isn’t the goal of fwbuilder – and this makes hacking about with the rules nearly impossible.
So what options are there? One way is to repeat the same rule for different sources or destinations, but this can quickly get messy, especially if there’s multiple ports involved. If there was a way we could group things together and keep them tidy, maintaining the rulebase would be a lot easier. This is where MARK comes in.
The MARK target lets us set a 32-bit value (or 0xFFFFFFFF) on a packet, which we can then look for later with the mark match. This in itself can be useful, but where it gets really handy is adding the values together.
To start off with, here’s an example
# Create a new chain, which will in effect be our source 'group' iptables -N S-TRUSTED # Add our sources iptables -A S-TRUSTED -s 192.168.1.1/32 -j MARK --set-xmark 0x8/0x0 iptables -A S-TRUSTED -s 192.168.2.0/24 -j MARK --set-xmark 0x8/0x0 iptables -A S-TRUSTED -s 192.168.3.3/29 -j MARK --set-xmark 0x8/0x0 # Create a chain for the destination group iptables -N D-DMZ # Add our DMZ machines iptables -A D-DMZ -d 10.1.1.1/32 -j MARK --set-xmark 0x4/0x0 iptables -A D-DMZ -d 10.1.2.0/24 -j MARK --set-xmark 0x4/0x0 # Create a chain for the services iptables -N D-SRV-MGMT # Add our service iptables -A D-SRV-MGMT -p tcp -m tcp --dport 22 -j MARK --set-xmark 0x2/0x0 iptables -A D-SRV-MGMT -p tcp -m tcp --dport 80 -j MARK --set-xmark 0x2/0x0
So what we have now are three chains, which do the following
- If the source matches, add 0x8 to the packet mark
- If the destination matches, add 0x4 to the packet mark
- If the service matches, add 0x2 to the packet mark
If you know your hexadecimal, you’ll already know that if all of these are true, we’ll come out with 0xE (or 14, in decimal).
Making the rule
Hopefully you’ll see where we’re going with this with the next example, which is our actual rule
# Create a new chain for our rule and add it to our FORWARD chain iptables -N R-ALLOW-DMZ-MGMT iptables -A FORWARD -j R-ALLOW-DMZ-MGMT # Zero out the packet mark to make sure no previous rules interfere iptables -A R-ALLOW-DMZ-MGMT -j MARK --set-xmark 0x0/0x0 # Jump to our 'source group' chain iptables -A R-ALLOW-DMZ-MGMT -j S-TRUSTED # Jump to our 'destination group' chain iptables -A R-ALLOW-DMZ-MGMT -j D-DMZ # Jump to our 'service group' chain iptables -A R-ALLOW-DMZ-MGMT -j D-SRV-MGMT # If the packet mark matches 0xE, then ACCEPT iptables -A R-ALLOW-DMZ-MGMT -m mark --mark 0xE -j ACCEPT
And there we have it – if the packet matches the source, destination and service, the packet mark will be 0xE. If, say, it matches everything except the destination, it’ll come out as 0xC, which won’t match and so netfilter will carry on along the rest of the rules. If you want processing to stop here, you could always add a LOG and REJECT/DROP target at the end of the R-ALLOW-DMZ-MGMT chain.
Sometimes we want to be able to say ‘everything but that particular network’, whether it be for accepting or dropping packets. We can do that with this, too
# Add a new chain for negating the source iptables -N S-NEGATE # XOR the current packet mark with 0x8 - our 'source match' identifier iptables -A S-NEGATE -j MARK --xor-mark 0x8
To use it, simply drop it in the R-ALLOW-DMZ-MGMT rule above after the jump to the S-TRUSTED chain, and if S-TRUSTED matched, it won’t any more, and vice versa. To negate the destination and service matches, you’ll need to create similar chains for (for example) D-NEGATE and D-SRV-NEGATE, replacing the 0x8 with 0x4 and 0x2 respectively.
Things to note
One downside of this method is that because of the way IPTables works, if you want to use the same set of networks and hosts as a source and a destination, you’ll need to duplicate them, but match on the source or destination as appropriate. Using the example given above, if we wanted a group with the same entries as S-TRUSTED, but matching on traffic going to them, we’d need to create another group (for example, D-TRUSTED), which will be identical save for the IP matches (which will need changing to -d) and the mask (which will need setting to 0x4 instead of 0x8).
Also, be careful if you’re using packet marks to do something outside of netfilter (say, for traffic control – which I’ll cover in a future post). One way round this is the facility to save the current packet mark to the current connection mark, or vice versa – if you go down this path then having a look at the iptables manpage for the MARK and CONNMARK targets will be useful.
This is an effective way of grouping hosts, networks and services within IPTables. It can be quite a bit of work to start with to add all the groups, but once in place it makes writing rules a lot more logical.
Taking things further
One way which you could take this further would be to group interfaces together in a similar fashion, say by adding 0x10 to the packet, and then matching on 0x1E rather than 0xE.
Usefully, if you send the packet out to syslog with the LOG target, netfilter will print out the current packet mark at the end of the log message as MARK=0xN, which can be useful when debugging.