Isawan Millican

Atomic iptables scripts with network namespaces

An issue faced by shell script based firewalls calling iptables is that it can take several minutes to rebuild the firewall, during this time the firewall may drop/accept packets incorrectly. To get around this issue, the iptables-restore command can be used which provides atomic firewall rule changes. The sysadmin then simply writes/generates the firewall rules in the iptables-restore format and uses iptables-restore to apply the updates. Alternatively, nftables can be used which provides atomic transactions built-in. However, significant work may need to be done to convert existing firewall scripts. In this article, I'll show a quick way of making the execution of iptables-based firewall scripts an atomic operation by using a network namespace as a staging area.

Here is an example of the usage.

$ cat big_firewall_script
iptables -N TEST
for i in `seq 5000`;
    iptables -A TEST --dest

$ sudo ./iptables-atom ./big_firewall_script

By wrapping the big_firewall_script with ./iptables-atom, the firewall firewall update is now atomic without any modifications to the firewall script.

Here is the source for iptables-atom. [1]

$ cat iptables-atom

ip netns add staging
ip netns exec staging $@

ip netns exec staging iptables-save >/tmp/iptables_dump
iptables-restore /tmp/iptables_dump
ip netns exec staging ip6tables-save >/tmp/ip6tables_dump
ip6tables-restore /tmp/ip6tables_dump

ip netns delete staging

A network namespace is created; a network namespace allows the creation of network objects such as chains and rules in an isolated environment from the rest of the system. This provides a clean environment for the creation of firewall rules. The firewall script is then executed inside the network namespace which produces all the network objects inside the namespace. The IPv4 and IPv6 tables are then dumped to a file in a format that can be read by iptables-restore. Rules for each IPv4 and IPv6 tables are then each restored atomically to the system's firewall.

Current limitations with this technique is the interaction with ipsets. To the best of my understanding, there is no way of atomically reloading ipsets in this way. The closest thing that ipsets provides is the ipset swap command which is atomic but need to be applied to each set individually.

We can extend this approach by running the staging part on a separate, more powerful machine as part of your CI/CD pipeline; obviously ensuring that the same kernel and iptables userspace version is used. In addition, this technique should also work for arptables and ebtables, although you may need to add additional save-restore pairs.

[1]Just a note, you probably shouldn't use this directly. Consider error handling around failure cases to ensure the namespace is always deleted on completion. I have avoided this here to keep the core logic nice and clean.