I have a host1
that has a WAN interface eth0
(1.2.3.4
) and a LAN interface eth1
(10.41.82.1
). Inside the LAN is a host2
with the IP address 10.41.82.2
.
Incoming connections to 1.2.3.4:22
are forwarded to host2
using the following iptables rules on host1
:
root@host1:~# iptables -t nat -A OUTPUT -d 1.2.3.4 -p tcp --dport 22 -j DNAT --to 10.41.82.2:22
root@host1:~# iptables -t nat -A PREROUTING -d 1.2.3.4 -p tcp --dport 22 -j DNAT --to 10.41.82.2:22
This works fine, except when host2
is trying to access 1.2.3.4:22
:
root@host2:~# telnet 1.2.3.4 22
Trying 1.2.3.4...
root@host2:~# ping 1.2.3.4
PING 1.2.3.4 56(84) bytes of data.
64 bytes from 1.2.3.4: icmp_seq=1 ttl=64 time=0.125 ms
64 bytes from 1.2.3.4: icmp_seq=2 ttl=64 time=0.279 ms
root@host2:~# tcptraceroute 1.2.3.4 22
traceroute to 1.2.3.4, 30 hops max, 60 byte packets
1 * * *
2 * * *
3 * * *
4 * * *
5 * * *
(and so on)
It seems like the packets are being forwarded in a circle. As you can see, pinging works, so the routes and IP forwarding are set up properly.
What did I do wrong and how can I fix the problem?
UPDATE:
As mentioned in BillThor’s response and as is described in the frozentux tutorial on DNAT, I need to add an SNAT
rule. I have done this now (note that I have added the LAN address of host2
for -d
, as this rule is applied in POSTROUTING
where the destination of the packet has already been changed):
root@host1:~# iptables -t nat -A POSTROUTING -d 10.41.82.2 -p tcp --dport 22 -j SNAT --to 10.41.82.1
Unfortunately, this does not solve the problem, and everything is still the same as described above. Interestingly, the SNAT
rule never seems to be matched, according to the counters shown when running iptables -t nat -nvL
.
UPDATE 2:
I discovered that when I run tcpdump -i eth1
, even when I run it with parameters irrelevant to this case, such as tcpdump -i eth1 port 34238
, it suddenly works. But only while tcpdump
is running.
What I didn’t mention so far because I thought it was irrelevant is that eth1
is actually a bridge, and host2
is a Xen domain. I am starting to suspect that my problem might be related to this.
The reason why it failed turns out to be connected to the fact that
eth1
is actually a bridge interface. Actually,host2
is a Xen virtual machine running onhost1
andeth1
is a bridge that is used for the communication between the host and the guest.I solved the problem by calling
bridge link set dev vif2.0 hairpin on
(or alternativelybrctl hairpin eth1 vif2.0 on
).vif2.0
is a virtual network interface created by Xen and is part of theeth1
bridge.So, in order to make hairpin NAT work on a bridge interface, make sure to enable bridge hairpinning for the interface (that is a part of the bridge) on which the packets are coming in.
Adding an
SNAT
rule was not necessary in the end.systemd-networkd
also has a directive to enable hairpinning in a *.network file:I wasn’t sure whether to add it to
eth1.network
or tovif2.0.network
(which normally doesn’t exist) in my case, but in the end none of them worked.What you are trying to do is called hairpin NAT. It would be better for host2 just to connect to host1.
If you want this to work, use SNAT as well as DNAT for traffic originating on the local network going to the external IP.
Another solution that could work is to add a route for host2 to host1 which routes the traffic to the router rather than to the local network.