My general question is this: What's the best way (simplest, easiest, quickest, least error-prone, etc.) to verify iptables NAT rules locally on a single host (i.e. without a network connection) at the command-line?
What follows are the details of specific (failed) attempts at checking a simple DNAT rule using NetCat. I am hoping for a resolution of my specific issue in this case, but also for an answer to my general question.
I'm working on a VirtualBox virtual machine running Debian 8 (Jessie). I want to use netcat to perform a basic test of a simple DNAT rule.
For my test, all I want to do is send some data to one local address (e.g. 192.168.0.1
) and have it arrive at another local address (e.g. 192.168.0.2
).
I've tried several different approaches so far:
Dummy interfaces and the PREROUTING chain
Virtual interfaces and the PREROUTING chain
Using the OUTPUT chain instead of PREROUTING
Dummy interfaces and the PREROUTING chain
My first attempt was to add a DNAT rule to the PREROUTING chain and add two dummy interfaces with the appropriate addresses.
Here is my rule:
sudo iptables \
-t nat \
-A PREROUTING \
-d 192.168.0.1 \
-j DNAT --to-destination 192.168.0.2
There are no other netfilter rules in my firewall. But just to be sure, here is the output from iptables-save
:
# Generated by iptables-save v1.4.21 *nat :PREROUTING ACCEPT [0:0] :INPUT ACCEPT [0:0] :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] -A PREROUTING -d 192.168.0.1/32 -j DNAT --to-destination 192.168.0.2 COMMIT *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] COMMIT
To reiterate, all I want to do is send some data to the 192.168.0.1
address and have it arrive at the 192.168.0.2
address.
It's probably worth mentioning that the 192.168.0.0/24
subnetwork is unused on my VM. First I add a couple of dummy interfaces:
sudo ip link add dummy1 type dummy
sudo ip link add dummy2 type dummy
Next I assign assign the IP addresses to the dummy interfaces on the desired subnetwork range:
sudo ip addr add 192.168.0.1/24 dev dummy1
sudo ip addr add 192.168.0.2/24 dev dummy2
And then I bring the interfaces up:
sudo ip link set dummy1 up
sudo ip link set dummy2 up
Here is what my routing table looks like now:
default via 10.0.2.2 dev eth0 10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 192.168.0.0/24 dev dummy1 proto kernel scope link src 192.168.0.1 192.168.0.0/24 dev dummy2 proto kernel scope link src 192.168.0.2 192.168.56.0/24 dev eth1 proto kernel scope link src 192.168.56.100
Now I listen at the first (source) address using netcat:
nc -l -p 1234 -s 192.168.0.1
And I connect to the netcat server with a netcat client (in a separate terminal window):
nc 192.168.0.1 1234
Text entered in one window appears in the other - just as expected.
I do the same thing with the second address as well:
nc -l -p 1234 -s 192.168.0.2
nc 192.168.0.2 1234
Again, text entered in one window appears in the other - as expected.
Finally, I try to listen on the target (DNAT) address and connect via the source (DNAT) address:
nc -l -p 1234 -s 192.168.0.2
nc 192.168.0.1 1234
Unfortunately the connection fails with the following error:
(UNKNOWN) [192.168.0.1] 1234 (?) : Connection refused
I also tried using ping -c 1 -R 192.168.0.1
to see if the DNAT was taking effect, but it does not look like that's the case:
PING 192.168.0.1 (192.168.0.1) 56(124) bytes of data. 64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=0.047 ms RR: 192.168.0.1 192.168.0.1 192.168.0.1 192.168.0.1 --- 192.168.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.047/0.047/0.047/0.000 ms
Why isn't this working? What am I doing wrong?
Diagnosis with tcpdump
To diagnose this issue, I tried using tcpdump
to listen for traffic on the dummy interfaces. I tried listening to all interfaces (and filtering out SSH and DNS):.
sudo tcpdump -i any -e port not 22 and port not 53
Then I pinged the dummy1
interface:
ping -n -c 1 -I dummy1 192.168.0.1
This yielded the following results:
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
In 00:00:00:00:00:00 (oui Ethernet) ethertype IPv4 (0x0800), length 100: 192.168.0.1 > 192.168.0.1: ICMP echo request, id 8071, seq 1, length 64
In 00:00:00:00:00:00 (oui Ethernet) ethertype IPv4 (0x0800), length 100: 192.168.0.1 > 192.168.0.1: ICMP echo reply, id 8071, seq 1, length 64
So it looks like the dummy interfaces are attached to the loopback interface. This might mean that the iptables rules are being totally circumvented.
Virtual interfaces and the PREROUTING chain
As a second attempt, I tried using so-called virtual IP addresses instead of dummy interfaces.
Here is how I added the "virtual" IP addresses to the eth0 and eth1 interfaces:
sudo ip addr add 192.168.0.100/24 dev eth0
sudo ip addr add 192.168.0.101/24 dev eth1
NOTE: I used different IP addresses for these than I did for the dummy interface.
Then I flushed and updated the iptables NAT rules:
sudo iptables -F -t nat
sudo iptables \
-t nat \
-A PREROUTING \
-d 192.168.0.100 \
-j DNAT --to-destination 192.168.0.101
The I retried the ping test:
ping -n -c 1 -R 192.168.0.100
No dice:
PING 192.168.0.100 (192.168.0.100) 56(124) bytes of data. 64 bytes from 192.168.0.100: icmp_seq=1 ttl=64 time=0.023 ms RR: 192.168.0.100 192.168.0.100 192.168.0.100 192.168.0.100 --- 192.168.0.100 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.023/0.023/0.023/0.000 ms
Then the netcat test again. Start the server:
nc -l -p 1234 -s 192.168.0.101
Try to connect the client:
nc 192.168.0.100 1234
Also no dice:
(UNKNOWN) [192.168.0.100] 1234 (?) : Connection refused
Using the OUTPUT chain instead of PREROUTING
Then I tried moving both DNAT rules from the PREROUTING chain to the OUTPUT chain:
sudo iptables -F -t nat
sudo iptables \
-t nat \
-A OUTPUT \
-d 192.168.0.1 \
-j DNAT --to-destination 192.168.0.2
sudo iptables \
-t nat \
-A OUTPUT \
-d 192.168.0.100 \
-j DNAT --to-destination 192.168.0.101
Now I try ping on both the dummy and virtual interfaces:
user@host:~$ ping -c 1 -R 192.168.0.1 PING 192.168.0.1 (192.168.0.1) 56(124) bytes of data. 64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=0.061 ms RR: 192.168.0.1 192.168.0.2 192.168.0.2 192.168.0.1 --- 192.168.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.061/0.061/0.061/0.000 ms user@host:~$ ping -c 1 -R 192.168.0.100 PING 192.168.0.100 (192.168.0.100) 56(124) bytes of data. 64 bytes from 192.168.0.100: icmp_seq=1 ttl=64 time=0.058 ms RR: 192.168.0.100 192.168.0.101 192.168.0.101 192.168.0.100 --- 192.168.0.100 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.058/0.058/0.058/0.000 ms
And I also try the netcat client-server test for each pair of IP addresses:
nc -l -p 1234 -s 192.168.0.2
nc 192.168.0.1 1234
and:
nc -l -p 1234 -s 192.168.0.101
nc 192.168.0.100 1234
This test succeeds as well.
So it looks like both the dummy and virtual interfaces work when the DNAT rule is in the OUTPUT chain instead of the PREROUTING chain.
It seems that part of my problem is that I'm unclear on which packets traverse which chains.