64

This is a Canonical Question about Hairpin NAT (Loopback NAT).

The generic form of this question is:

We have a network with clients, a server, and a NAT Router. There is port forwarding on the router to the server so some of it's services are available externally. We have DNS pointing to the external IP. Local network clients fail to connect, but external work.

  • Why does this fail?
  • How can I create a unified naming scheme (DNS names which work both locally and externally)?

This question has answeres merged from multiple other questions. They originally referenced FreeBSD, D-Link, Microtik, and other equipment. They're all trying to solve the same problem however.

1
  • 1
    If your purpose is to test the access from the internet, there is no point in messing with the router's routes and/or DNS settings anyway for at best, from the inside you would be verifying that the inside portion of the router works. I suggest you use a proxy server somewhere on the outside.
    – mjv
    Oct 10, 2009 at 6:42

12 Answers 12

21

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.

2
  • I ran across this thread while trying to solve the same problem, and while it's true that FreeBSD doesn't support hairpin nat out of the box, there are ways to redirect and NAT the internal -> external -> internal traffic. Jun 15, 2017 at 19:19
  • For example: no nat on $int_if proto tcp from $int_if to $int_net , nat on $int_if proto tcp from $int_net to $hairpin_int port $hairpin_ports -> $int_if , rdr on $int_if proto tcp from $int_net to $ext_if port $hairpin_ports -> $hairpin_int Jun 15, 2017 at 19:59
75

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.

13
  • I am wondering a bit about the addresses on packets exchanged between gateway and server. Would it not be more consistent if the server saw the public IP address of the router as the client IP? Technically both can work, but to remain consistent with how other servers see the clients, it would have to use the public IP.
    – kasperd
    Jun 1, 2014 at 14:40
  • 2
    I'm assuing you're referring to the last case, "proper hairpin NAT". The crucial thing is to rewrite the source address on the inbound packet in such a way that it goes back to the router, which can then reverse both the DNAT and the SNAT and thus avoid the problem. Which of its many address the router uses to do this is more a point of taste, and if you're doing this with iptables, is certainly something you can configure if you so choose.
    – MadHatter
    Jul 11, 2014 at 7:44
  • 1
    Many admins, myself included, consider split-horizon DNS a cure that is worse than disease. If one additional SNAT can be called a disease at all. A split-view DNS confuses humans while it makes routers' lives easier. This topic would be better handled by a separate ServerFault question/answer.
    – kubanczyk
    May 3, 2018 at 8:00
  • My answer is very much about hairpin NAT. I can see the pros and cons of opening a canonical "split-horizon DNS" question: pros include having a question dedicated to the uses and issues of SHDNS, but the cons are that this question has already had lots of other questions that related to it merged into it, so that might happen to your question, too. If it were me, I'd raise the issue on meta, and seek consensus. If such a question does get written, I look forward to reading your answer on it!
    – MadHatter
    May 3, 2018 at 9:45
  • 3
    This is an awesome answer. I basically always had the burning question "Why can't I make an HTTP request in my browser to my public IP after turning on port forwarding in my router, to test it?" and this answers that. It's annoying though that some ISPs don't get this right. When I switched ISPs, I stopped being able to test like this. I learned that the new ISP "doesn't support hairpin NAT". Now, I realize that it's actually that they are using hairpin NAT, and just not "proper hairpin NAT" like in your diagram. If they didn't use it at all, or used it and got it right, it would work.
    – Matt Welke
    Apr 18, 2021 at 20:54
13

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.
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.

1
  • 1
    If you have a moment, could you expand on what Split-Horizon DNS is, how it works, and the major drawbacks. This is a canonical question now and it'd be nice to have a more complete answer.
    – Chris S
    Dec 12, 2013 at 14:45
3

Recently answered a similar question: Cisco static NAT not working on LAN side and just realized that this is a Canonical Question. So permit me to summarize the solution here.

First of all: forget about NAT (if you can) - the question is not at all about configuring NAT. It's about accessing a server placed behind NAT from both the Internet and the LAN. Employing two DNS zones is a viable alternative, but not always the solution. But the solution does exist and is incredibly simple (although not perfect, probably):

(1) on the server: add the public IP address as a secondary IP address on the server's network interface with the 255.255.255.255 mask (web service or whatever you want on the server should listen on this IP address too); all modern operating systems will permit you to do this (or a loopback interface with the public IP address assigned to it can be used instead of adding a secondary IP to the primary interface).

(2) on the LAN hosts: add a host route for the public IP address, for example, for Windows hosts use the following command: route -p add 203.0.113.130 mask 255.255.255.255 192.168.1.11 (you can also use DHCP "static route" option to distribute the route). Or, if there is (a) L3 switch(es)/router(s) in between the clients and the Internet-facing router, configure that host route on this(these) intermediate switch(es)/router(s), not on the clients.

For those concerning with the TCP three-way handshake: it will work OK in the proposed configuration.

Please provide feedback (at least, vote).

1
  • 1
    Requirement #2 makes this not work well on BYOD networks...
    – Michael
    Jun 26, 2018 at 22:11
2

Ill answer to my questions just to broaden horizons for those with similar problems.

I am contacted my ISP and asked them to try solving my problems. What they had offered me is another public IP address just for server, Now I have local traffic on the WAN side of FreeBSD and We made specific pipes for faster throughput fol local traffic to public IP of Server

1
  • 3
    This solution implements a Perimeter network or DMZ and is a good alternative to both Hairpin NAT and Split-Horizon DNS.
    – Chris S
    Dec 12, 2013 at 14:47
2

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.

2

From a technical point of view the best solution to this problem is to enable IPv6 on your network. When IPv6 is enabled you need to create an AAAA record for your domain. Keep the existing A record pointing to the external IPv4 of the router. Create an AAAA record pointing to the IPv6 address of the server.

IPv6 has enough addresses to avoid NAT, so you won't need hairpin NAT for IPv6. And once you have enabled IPv6 and created AAAA records any client supporting RFC 8305 will try IPv6 before IPv4. This means you don't need hairpin NAT for IPv4 either, because the clients won't be using it.

You will still need your existing IPv4 NAT for outgoing connections and port forwarding for incoming connections until most of the world has enabled IPv6 as well.

It's faster as well.

Using IPv6 will give you a better performance than hairpin NAT.

With hairpin NAT your client will send a packet through a switch to the router, the router will then perform two rounds of translation and finally send the packet through the switch to the server. Packets from the server to the client will go through that entire path in reverse.

With IPv6 you avoid NAT, instead packets are sent directly through the switch between client and server. This means on a roundtrip you reduce the number of passes through the switch from 4 to 2, and you avoid 2 trips through the router and the 4 translations the router would have performed. This translates to better performance.

This is true even if you use a switch built into the same box as the router.

What if the ISP doesn't have IPv6?

If you are using an ISP which doesn't support IPv6 I will question whether you should be hosting servers on that network. These are my suggestions on what to do if the ISP does not currently support IPv6.

First tell the ISP that you need IPv6. And maybe remind them that the IPv6 protocol has been around for 20 years so they are long overdue in supporting it. If that's not enough for the ISP to take you seriously start looking for other ISPs.

If you find an ISP with IPv6 support you can run with both ISPs for a transition period. On the router connected to the new ISP you can disable IPv4 on the LAN side and then connect the LAN sides of both routers to the same switch. IPv4 and IPv6 are two independent protocols and as such it is no problem at all if those connections go through different routers. As a side benefit it gives you some amount of redundancy if one of the connections has an outage.

If you cannot find an ISP with IPv6 support you should consider moving your server to a hosting facility. With a server in a hosting facility you are less dependent on geographical location and for that reason there is more competition between providers which will help ensuring there is one that satisfies your needs.

Moving the server to a hosting facility is not going to give your clients IPv6, but moving the server means you no longer need hairpin NAT to reach it.

What you should not do

Don't turn on IPv6 and create AAAA records if you don't have a way to route the IPv6 traffic. If your ISP doesn't support IPv6 but you choose to enable IPv6 on your LAN anyway (maybe using RFC 4193 addresses) and create AAAA records it will work for clients on your LAN reaching the server on your LAN. But communication between your LAN and the outside world would first try IPv6 (which wouldn't work), and you would be relying on falling back to IPv4 which at best is a little slower or at worst doesn't happen.

1

Since I also asked this question (see How do I access a network service NATed behind a firewall from inside using its outside IP?) and was redirected here but the answers here didn't provide a solution (in contrast to generic explanations) let my provide my Linux (iptables specific) solution here to save everybody a few hours of experimentation. This file is in iptables-restore format and can be read into iptables directly (after editing the IP addresses of course). This is for a web server (port 80) and only for IPv4 - the rules for IPv6 and for SSL (port 443) are analogous.


# Port forwarding for VM / Container access with „hairpin NAT“.
*nat
:PREROUTING ACCEPT [3:205]
:INPUT ACCEPT [59:670]
:OUTPUT ACCEPT [16:172]
:POSTROUTING ACCEPT [20:257]

# This was simple port forwarding - access works from outside but not from inside
#-A PREROUTING  -4 -p tcp -i eth0 --dport 80 -j DNAT --to web.local:80

# This is real hairpin NAT which allows „web.local“ to access itself via the VM hosts external IP.
# First we need to masquerade any traffic going out the external interface:
-A POSTROUTING -o eth0 -j MASQUERADE

# Then we need to reroute incoming traffic on the public IP to the local IP:
-A PREROUTING  -4 -p tcp -d web.public.com --dport  80 -j DNAT --to web.local:80

# And finally we need to tell the router that the source IP of any traffic
# coming from the LAN must be source-rewritten when going to the web server:
-A POSTROUTING -4 -p tcp -s lan.local/24 -d web.local --dport  80 -j SNAT --to-source web.public.com:80

COMMIT

Replace lan.local , web.local and web.public.com with your local network (eg. 10.0.x.0/24), your webserver's local IP (e.g. 10.0.1.2), and your router's public IP (eg. 4.5.6.7). The -4 is just to allow IPv6 and IPv4 rules in the same file (such lines ignored by ip6tables). Also, remember to put IPv6 addresses in [brackets] when they include port declarations, e.g. [fe0a:bd52::2]:80.

Those were all the things that caused me to pull my hair out when trying to actually implement the explanations in this question. I hope I left nothing out.

1
  • MASQUERADE on the wan interface is generally useful, but is not related to the "hairpin NAT" question. The canonical answer proposes MASQUERADE on the lan interface, and you instead propose a SNAT (SNAT is approximately the same as MASQUERADE). You force somewhat weird source IP:port override.
    – kubanczyk
    May 3, 2018 at 7:34
1

As it's a canonical's question. I will answer if you have a Sonicwall router.

The expression to know is NAT loopback policy

This document describes how a host on a SonicWall LAN can access a server on the SonicWall LAN using the server's public IP address to FQDN. Imagine a NSA 4500 (SonicOS Enhanced) network in which the Primary LAN Subnet is 10.100.0.0 /24 and the Primary WAN IP is 3.3.2.1. Let's say you have a Web site for your customers, and its hostname is . You have already written the policies and rules needed so that outsiders can get to the web site, but it's really running on a private side server 10.100.0.2. Now imagine that you are a person using a laptop on the private side, with IP of 10.100.0.200. You want to reach the server using its public name, because you do the same thing when your laptop is with you on the road. If you sit on the private side, and request http://www.example.com>, loopback is what makes it possible for that to work, even though the server is actually right next to you on a local IP address.

To allow this functionality you would need to create a NAT loopback policy, also known as NAT reflection or hairpin.

Loopback Policy using WAN Interface's IP Address

Login to the SonicWall Management GUI.
Navigate to Manage | Rules | NAT Policies submenu.
Click on the Add button.
Create the following NAT Policy.
Original Source: LAN Subnets (or Firewalled Subnets if you want hosts in other zones to be included)
Translated Source: WAN Interface IP
Original Destination: WAN Interface IP
Translated Destination: (LAN server object)
Original Service: Any
Translated Service: Original
Inbound Interface: Any
Outbound Interface: Any

The sonicwall will recognize the external service you try to contact, and will rewrite the destination address to fit the server internal's address, thus it will be transparant to the computer.

0

I'll add an answer here as the comments here didn't address my particular problem. I suspect that's because I hit a nasty linux kernel bug. The setup is:

internet <--> modem 1.1.1.1/30 <--> switch <---> LAN 10.1.1.0/24
                                      ^
        +----------------------+      |
        |              /--eth0 o <----/
        |              |       |           
        | 10.1.1.1/24 br0      |           v (antenna)
        |  1.1.1.2/30  |       |           |
        |              \-wlan0 o ----------/
        +----------------------+ 

Despite the complex looking picture the only relevant change to situations covered in other comments is the addition of the software bridge, br0. It's there because the gateway box is also a wireless access point for the LAN.

Our gateway box is still performing NAT duties for the machines on the LAN. Because it has only 1 ethernet port it is forced to do hairpin NAT. I suspect it should just work with the iptables rules given in other comments here, but on the Linux kernel 4.9 at least it doesn't. Under 4.9 our while our gateway box can access the internet the machines on the LAN trying to access it via NAT can't.

tcpdump shows responses to incoming packets hitting eth0, but they don't make it out of br0. Running this command fixes that:

ebtables -t brouter -A BROUTING -d 01:00:00:00:00:00/01:00:00:00:00:00 -j ACCEPT
ebtables -t brouter -A BROUTING -p IPv4 --ip-dst 10.1.1.0/24 -j ACCEPT
ebtables -t brouter -A BROUTING -p IPv4 --ip-src 10.1.1.0/24 -j ACCEPT
ebtables -t brouter -A BROUTING -p IPv4 -j DROP

Before that command is run incoming packets are processed according to the kernels default behaviour, which is to give them to the bridge then pass them kernel's routing modules. The command forces packets that aren't from the LAN to bypass the bridge and go directly to routing, which means the bridge doesn't get a chance to drop them. Broadcast and multicast addresses must be bridged, otherwise things like DHCP and mDNS won't work. if you are using IPv6, you must add rules for it as well.

You might be tempted to fix the problem using this:

brctl hairpin br0 eth0 on
brctl hairpin br0 wlan0 on

I certainly was so tempted - it was my first attempt. As soon as I did it machines on the LAN gained access the internet, so it works for a while. Then the following occurred (and I didn't care to repeat the experiment):

  1. Ping times across the LAN to the gateway doubled at around 10 second intervals, going up from 0.1ms, to 0.2ms, 0.4ms, 0.8ms, 2ms and so on until the gateway box was inaccessible from the LAN. It smelt like a packet storm, but STP was switched on everywhere.
  2. Not long after all wireless access points died.
  3. While attempting to diagnose what was happening with the wireless, all IP phones rebooted.
  4. Not long thereafter, wired machines lost all contact with the LAN.

The only way out was to reboot every machine in the building. The one exception was the hardware switches, which could not be rebooted. They had to be power cycled.

-2

In FreeBSD using PF it's easy as (in your pf.conf file):

extif = "tun0"
intif = "em0"
{other rules}...
nat on $intif from any to 192.168.20.8 port 80 -> ($extif)

192.168.20.8 would be the internal web server.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .