1

I got lo (127.0.0.1) and eth0 (172.17.0.8). I want to redirect packets that land on 127.0.0.1:80 to 172.17.42.1:80 (route from eth0).

I tried

iptables -t nat -A OUTPUT -p tcp --dport 80 -d 127.0.0.1 -j DNAT --to 172.17.42.1:80

But when I do curl localhost:80 I get no response, when I do curl 172.17.42.1:80 it just works.

svobol13
  • 163
  • 3
  • 6
  • Shouldn't that be `PREROUTING` instead of `OUTPUT`? – h0tw1r3 Nov 24 '14 at 02:01
  • No, because packets that are generated locally don't go through a PREROUTING at all. First routing decision is taken before the packet enters OUTPUT chain, and second (final) routing decision is taken after the packet exits OUTPUT and before it enters POSTROUTING chain. – Jakov Sosic Nov 24 '14 at 03:41
  • You turned on ipv4 forwarding in your sysctl.conf (and then reloaded) or in the appropriate location in /proc, yes? – Magellan Nov 24 '14 at 03:51

2 Answers2

10

When you are trying to reach localhost, your source address is 127.0.0.1, as is your destination address. So, packet looks something like this:

 | SRC        |  DST       |
 | 127.0.0.1  |  127.0.0.1 |

Since packets that are locally generated first traverse OUTPUT chain, you modify the packet with DNAT rule:

iptables -t nat -A OUTPUT \
         -d 127.0.0.1 \
         -p tcp --dport 80 \
         -j DNAT \
         --to 172.17.42.1:80

After OUTPUT chain, packet looks like this:

 | SRC        |  DST        |
 | 127.0.0.1  |  172.17.42.1 |

So, the first error you see is that even if this packet gets routed outside the source device, destination will not know how to correctly return it. Thus, you also need to add additional SNAT rule:

iptables -t nat -A POSTROUTING \
         -d 172.17.42.1 \
         -p tcp --dport 80 \
         -j SNAT \
         --to-source <your_ip_addr>

Now, packet will look pretty much correct to exit on the network (source address will be the public address of this device and not localhost address).

But, this won't solve your pain just yet.

Routing decisions for packets generated on a local machine are taken in two places.

  • before the OUTPUT chain
  • after the OUTPUT chain and before POSTROUTING

Decision for this packet will be made in the second stage, before the source IP is rewritten in POSTROUTING chain...

So, kernel security mechanism will drop the packet because by default kernel refuses to route packets with src of 127.0.0.1. That means that even mangle coupled with fwmark wouldn't help us here. What's needed for this to work is to enable route_localnet. That's the per-iface variable that enables the use of 127/8 for local routing purposes (default is 0 - aka disabled).

sysctl -w net.ipv4.conf.all.route_localnet=1

You can safely change 'all' for the name of the outgoing interface in your case.

Now, packets will get routed by the previous rules in the table, and since we had the POSTROUTING SNAT, we will fix the source ip of 127.0.0.1 and rewrite it.

Hope this helps.

PS. sorry before 2-3 edits, it's 5AM here and I'm still awake :)

Jakov Sosic
  • 5,267
  • 4
  • 24
  • 35
  • This is fantastic thanks. I really appreciated how you explained each part of the routing and why it won't work without an extra step. – rjh Apr 14 '17 at 08:34
  • `net.ipv4.conf.all.route_localnet=1` has SECURITY implications because it could allow external traffic to reach localhost-only services. Here is a PoC: https://github.com/kubernetes/kubernetes/issues/90259 – famzah May 18 '23 at 10:42
2

Before one jumps straight into firewall rules, one should also perform a simple forwarding check. Rather like when one checks the power cord is plugged in before taking apart the hardware.

Run:

cat /proc/sys/net/ipv4/ip_forward

If you get a zero, IPv4 will not forward. You'll need to turn this on.

To turn it on immediately and ephemerally to verify behavior:

echo 1 > /proc/sys/net/ipv4/ip_forward

The above turns it on for the machine but is simply modifying a kernel setting on the fly and will not be "saved".

Edit the sysctl.conf file to make the proper permanent change and ensure the following setting:

net.ipv4.ip_forward = 1
Magellan
  • 4,451
  • 3
  • 30
  • 53
  • ACtually you don't need ip_forward for locally generated packets. ip_forward only allows incoming packets to be forwarded to other machines, it doesn't do anything for packets originating from localhost. – Jakov Sosic Nov 24 '14 at 12:18
  • You are correct. You'd probably also be surprised how many people would expect traffic from outside the box to work identically to internal traffic though, and would misunderstand that concept from reading these forwarding questions. – Magellan Nov 25 '14 at 04:40