When testing new protocols on my test-bed routers, I often need to generate traffic through those routers. Since routers are meant to route, and not to generate traffic, I use my laptop to push traffic. To make it even more convenient, I like to receive the routed traffic on the very same laptop (e.g. when using iperf
or similar). But when the traffic is sent to the local host, how does one make it leave the system? The answer iptables
...
There are a two problems that must be solved:
- Outgoing packets with a local IP address as destination are sent on the loopback interface.
- Incoming packets with a local IP address as source are dropped.
To solve these issues, we "invent" two remote addresses and use iptables
to put these addresses in the IP header. The process is something like this:
- When sending to a fake address, change the source address to the fake address.
- When receiving from a fake address, change the destination address to a local address.
Let me illustrate this:
The concept might seem a bit hard to grasp, and it certainly is difficult to explain, but let me try: iptables
is used to change the source address before the packet leaves the computer, and when it arrives back again (using hard-coded neighbor entries), we use iptables
to change the fake destination address to a local one. Should the receiver choose to reply, it will read the source of the packet (which we changed to the other fake address) and the whole process is repeated with the two fake addresses exchanged.
But enough talk! Let's look at how all this is really done. As always, I cooked a script to do the work, so lets have a look:
The script is pretty long, but this is mostly because I failed to count the real way (like: 0, 1, many)... The script works like this:
After the configuration, we declare the rules for iptables, because we need these to first remove any existing rules and to create the rules afterwards. The same goes for the routing entries and the neighbor lines.
Then we check for existing rules in iptables and removes these if needed. Again, we repeat for routes and neighbors. I had to toggle the arp-switch to actually remove existing neighbor entries. Don't ask me why...
With everything cleared, we can start adding stuff. First we tell the system to use eth1
to reach 10.0.0.102
and eth2
to reach 10.0.0.101
:
sudo ip route add 10.0.0.101 dev eth2
sudo ip route add 10.0.0.102 dev eth1
But the system cannot send packets to either10.0.0.101
or 10.0.0.102
without knowing the corresponding MAC addresses. Usually we would let ARP resolve these, but since our fake addresses doesn't exist, we much tell these manually:
sudo ip neigh add 10.0.0.101 dev eth2 lladdr $mac_one
sudo ip neigh add 10.0.0.102 dev eth1 lladdr $mac_two
With this in place, we are all set to handle packets with fake addresses, so next up is to actually use these fake addresses:
sudo iptables -t nat -A POSTROUTING -s 10.0.0.1/32 -d 10.0.0.102/32 \
-o eth1 -j SNAT --to-source 10.0.0.101
sudo iptables -t nat -A POSTROUTING -s 10.0.0.2/32 -d 10.0.0.101/32 \
-o eth2 -j SNAT --to-source 10.0.0.102
These two rules works like this: after the routing subsystem has consulted the routing tables, we check for a source address matching our local IP, and a destination address matching our fake IP, and make sure we are the packet is sent on the correct interface. If these three conditions are met, we override the source address with the correct fake address.
Finally, we have to make sure that, when the mangled packet is received again, it will not be dropped This is achieved with the following rules:
sudo iptables -t nat -A PREROUTING -s 10.0.0.102/32 -d 10.0.0.101/32 \
-i eth1 -j DNAT --to-destination 10.0.0.1
sudo iptables -t nat -A PREROUTING -s 10.0.0.101/32 -d 10.0.0.102/32 \
-i eth2 -j DNAT --to-destination 10.0.0.2
Done! To use the script, plug two ethernet cables into the computer plug the cables into a switch. Then configure the interfaces with the right addresses (eth1: 10.0.0.1
and eth2: 10.0.0.2
). The run the script, and test it all by pinging one of the fake addresses:
ping 10.0.0.102
If you run tcpdump
on eth1, you should see ICMP requests with 10.0.0.101 as source and 10.0.0.102 as destination, and ICMP replies with the inverse.
Happy routing!