Loopback to forwarded Public IP address from local network - Hairpin NAT

Solution 1:

Since this has been elevated to be the canonical question on hairpin NAT, I thought it should probably have an answer that was more generally-valid than the currently-accepted one, which (though excellent) relates specifically to FreeBSD.

This question applies to services provided by servers on RFC1918-addressed IPv4 networks, which are made available to external users by introducing destination NAT (DNAT) at the gateway. Internal users then try to access those services via the external address. Their packet goes out from the client to the gateway device, which rewrites the destination address and immediately injects it back into the internal network. It is this sharp about-turn the packet makes at the gateway that gives rise to the name hairpin NAT, by analogy with the hairpin turn.

The problem arises when the gateway device rewrites the destination address, but not the source address. The server then receives a packet with an internal destination address (its own), and an internal source address (the client's); it knows it can reply directly to such an address, so it does so. Since that reply is direct, it doesn't go via the gateway, which therefore never gets a chance to balance the effect of inbound destination NAT on the initial packet by rewriting the source address of the return packet.

The client thus sends a packet to an external IP address, but gets a reply from an internal IP address. It has no idea that the two packets are part of the same conversation, so no conversation happens.

The solution is that for packets which require such destination NAT, and which reach the gateway from the internal network, to also perform source NAT (SNAT) on the inbound packet, usually by rewriting the source address to be that of the gateway. The server then thinks the client is the gateway itself, and replies directly to it. That in turn gives the gateway a chance to balance the effects of both DNAT and SNAT on the inbound packet by rewriting both source and destination addresses on the return packet.

The client thinks it's talking to an external server. The server thinks it's talking to the gateway device. All parties are happy. A diagram may be helpful at this point:

enter image description here

Some consumer gateway devices are bright enough to recognise those packets for which the second NAT step is needed, and those will probably work out-of-the-box in a hairpin NAT scenario. Others aren't, and so won't, and it is unlikely that they can be made to work. A discussion of which consumer-grade devices are which is off-topic for Server Fault.

Proper networking devices can generally be told to work, but - because they are not in the business of second-guessing their admins - they do have to be told do so. Linux uses iptables to do the DNAT thus:

iptables -t nat -A PREROUTING  -p tcp --dport 80 -j DNAT --to-destination 192.168.3.11

which will enable simple DNAT for the HTTP port, to an internal server on 192.168.3.11. But to enable hairpin NAT, one would also need a rule such as:

iptables -t nat -A POSTROUTING -d 192.168.3.11 -p tcp --dport 80 -j MASQUERADE

Note that such rules need to be in the right place in the relevant chains in order to work properly, and depending on settings in the filter chain, additional rules may be needed to permit the NATted traffic to flow. All such discussions are outside the scope of this answer.

But as others have said, properly-enabling hairpin NAT isn't the best way to handle the problem. The best is split-horizon DNS, where your organisation serves different answers for the original lookup depending on where the requesting client is, either by having different physical servers for internal vs. external users, or by configuring the DNS server to respond differently according to the address of the requesting client.

Solution 2:

What you're looking for is called "hairpin NAT". Requests from the internal interface for an IP address assigned to the external interface should be NAT'ted as though they came in from the external-side interface.

I don't have any FreeBSD familiarity at all, but reading the "pf" manual for OpenBSD (http://www.openbsd.org/faq/pf/rdr.html) the proposed solutions of split-horizon DNS, using a DMZ network, or TCP proxying lead me to believe that "pf" doesn't support hairpin NAT.

I'd look at going the route of split-horizon DNS and not using IP addresses in URLs internally but, instead, using names.


Solution 3:

The problem here is, that your router does not NAT your internal client's address. Thus, the TCP handshake fails.

Let's assume following IPs

  • Client: 192.168.1.3
  • Server: 192.168.1.2
  • Router internal: 192.168.1
  • Router external: 123.123.123.1

Here is what is happening:

  1. Client (192.168.1.3) sends TCP-SYN to your external IP, Port 80 (123.123.123.1:80)
  2. Router sees port forwarding rule and forwards the packet to the server (192.168.1.2:80) without changing the source IP (192.168.1.3)
  3. Client waits for a SYN-ACK from the external IP
  4. Server send his answer back to the client directly, because it's on the same subnet. It does not send the packet to the router, which would reverse the NAT.
  5. Client recieves a SYN-ACK from 192.168.1.2 instead of 123.123.123.1. And discards it.
  6. Client still waits for a SYN-ACK from 123.123.123.1 and times out.

Solution 4:

Why not use split horizon dns instead of hardcoding IP addresses everywhere? You would have ext.yourdomain pointing to 217.x.x.x on the outside, and then 192.x.x.x on the inside.


Solution 5:

If it's an original D-Link router (i.e., not Rev. D / Firmware Version 1.00VG from Virgin Media), you should be able to adjust the settings to work around this. (However, I agree with the previous poster's suggestion of DD-WRT for many other reasons!)

  1. Log into the router's web interface
  2. Click the Advanced tab at the top
  3. Click the Firewall Settings tab at the left
  4. Click the Endpoint Independent radio button under TCP Endpoint Filtering, as shown in the screenshot below (or see the router emulator at D-Link's website)
  5. Save changes; you're done

D-Link router web UI screenshot

This screenshot is from the Rev. C model; yours may be slightly different.