DNAT from localhost (127.0.0.1)

Solution 1:

This works for me, routing traffic from localhost:8081 to 172.17.0.1:80, where 172.17.0.1 is a veth behind the bridge interface named docker0.

sysctl -w net.ipv4.conf.docker0.route_localnet=1
iptables -t nat -A OUTPUT -o lo -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.1:80
iptables -t nat -A POSTROUTING -o docker0 -m addrtype --src-type LOCAL --dst-type UNICAST -j MASQUERADE

A key piece of the puzzle is the MASQUERADE rule.

Further inspiration:

  • How do I forward localhost traffic to a remote host with iptables?
  • iptables DNAT from loopback
  • https://github.com/docker/docker/pull/6810

Solution 2:

You will have to run the following three commands to make it work:

iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport 4242 -j DNAT --to 11.22.33.44:5353
sysctl -w net.ipv4.conf.eth0.route_localnet=1
iptables -t nat -A POSTROUTING -p tcp -s 127.0.0.1 -d 11.22.33.44 --dport 5353 -j SNAT --to $your-eth0-ip

Here is the detailed explanation.

The first command will do the DNAT as expected. However, if you try to capture packet with only this rule set, you will find you get nothing:

tcpdump -i any -n port 5353

This is because linux kernel drop this kind of packet by default, if the packet has 127.0.0.0/8 as one end, and an external ip address as the other end.

The second command changes the kernel parameter, to let this kind of packet pass (of course, you should change eth0 accordingly). After this, when you capture packet on eth0, you will see packets sent out, but with source address 127.0.0.1 and destination address 11.22.33.44. Whether this packet can reach the target server or not (the intermediate routers will drop this packet), there is no way for this packet to return. So you should add an SNAT rule, to change the source address to your eth0's. And now it should work.