How can I write automated tests for iptables?

I am configuring a Linux router with iptables. I want to write acceptance tests for the configuration that assert things like:

  • traffic from some guy on the internet is not forwarded, and
  • TCP to port 80 on the webserver in the DMZ from hosts on the corporate LAN is forwarded.

An ancient FAQ alludes to a iptables -C option which allows one to ask something like, "given a packet from X, to Y, on port Z, would it be accepted or dropped?" Although the FAQ suggests it works like this, for iptables (but maybe not ipchains as it uses in the examples) the -C option seems to not simulate a test packet running through all the rules, but rather checks for the existence for an exactly matching rule. This has little value as a test. I want to assert that the rules have the desired effect, not just that they exist.

I've considered creating yet more test VMs and a virtual network, then probing with tools like nmap for effects. However, I'm avoiding this solution due to the complexity of creating all those additional virtual machines, which is really quite a heavy way to generate some test traffic. It would also be nice to have an automated testing methodology which can also work on a real server in production.

How else might I solve this problem? Is there some mechanism I might use to generate or simulate arbitrary traffic, then know if it was (or would be) dropped or accepted by iptables?


Solution 1:

If you are prepared to do a bit of coding, you could try this:

  • Create a new network namespace by using the CLONE_NEWNET flag for the clone system call, or by using ip netns add and ip netns exec commands.
  • In this namespace crate a set of virtual network interfaces using the tun driver or ip link add.
  • Load the configuration using iptables-restore
  • Feed a number of packets to the rules through the tun or veth devices
  • Observe that you get the correct packets out of the tun or veth devices

This approach is likely going to work best for testing rules for forwarded packets. Connections to/from the machine where iptables is used requires a bit more effort, but should be feasible to test this way as well.

Here is an example of a sequence of commands, that could be used:

ip netns add test-iptables
ip netns add test-iptables-some-guy-on-the-internet
ip netns add test-iptables-lan-host

ip netns exec test-iptables ip link add eth0 type veth peer name eth0-peer
ip netns exec test-iptables ip link set eth0-peer netns test-iptables-some-guy-on-the-internet
ip netns exec test-iptables ip link add eth1 type veth peer name eth1-peer
ip netns exec test-iptables ip link set eth1-peer netns test-iptables-lan-host

ip netns exec test-iptables ifconfig eth0 192.0.2.2 netmask 255.255.255.0
ip netns exec test-iptables ifconfig eth1 203.0.113.1 netmask 255.255.255.0

ip netns exec test-iptables-some-guy-on-the-internet ifconfig eth0-peer 192.0.2.1 netmask 255.255.255.0
ip netns exec test-iptables-some-guy-on-the-internet route add default gateway 192.0.2.2

ip netns exec test-iptables-lan-host ifconfig eth1-peer 203.0.113.2 netmask 255.255.255.0
ip netns exec test-iptables-lan-host route add default gateway 203.0.113.1

ip netns exec test-iptables-some-guy-on-the-internet telnet 203.0.113.2
ip netns exec test-iptables iptables-restore < /tmp/iptables-test
ip netns exec test-iptables-some-guy-on-the-internet telnet 203.0.113.2

ip netns del test-iptables
ip netns del test-iptables-some-guy-on-the-internet
ip netns del test-iptables-lan-host

This would create three network namespaces for testing and load a set of iptables rules in one of them. The other two serve as the role of some guy on the internet and a host on the LAN.

In the above example the first telnet command get connection refused from the test-iptables-lan-host namespace, the second telnet command get a timeout if the ruleset drops the packet.

This doesn't touch the initial network namespace, which is the one all your production will be happening in by default. The only way to get more separation from your production is to run it on a separate host (physical or virtual).