0

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.

cdauth
  • 861
  • 9
  • 18

2 Answers2

2

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 on host1 and eth1 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 alternatively brctl hairpin eth1 vif2.0 on). vif2.0 is a virtual network interface created by Xen and is part of the eth1 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:

[Bridge]
HairPin=true

I wasn’t sure whether to add it to eth1.network or to vif2.0.network (which normally doesn’t exist) in my case, but in the end none of them worked.

cdauth
  • 861
  • 9
  • 18
1

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.

BillThor
  • 27,354
  • 3
  • 35
  • 69
  • Thanks for your response. Indeed I have come across the SNAT thing myself just now, but it doesn’t seem to solve my problem. I have updated my question with the information about what I’ve done. – cdauth Dec 16 '15 at 02:20
  • Also, interesting idea with the route. But I think it doesn’t work in my setting, as `host1` *is* the router (unless I’m totally confused now). – cdauth Dec 16 '15 at 02:23
  • @cdauth I usually keep my bridge interface for virtual machines separated from the real interfaces. I believe the SNAT rule -d value should have been for the external IP address. By default `tcpdump` puts the interface in promiscuous mode so the interface accepts packets it would normally ignore. – BillThor Dec 16 '15 at 13:32