Skip to content
Oct 8 / Greg

Mikrotik Internal Hairpin

I’ve gotten questions about this forever and I’m just now addressing it…sorry 🙂

Anyway, the issue occurs when you have two hosts on the same subnet inside your infrastructure. One of the hosts has a service like a webserver. When another host on your subnet attempts to browse via the public NAT’d IP it fails…here’s why it does this as well as how to fix it.

Starting configuration.


Router Config

1
2
3
4
5
6
7
8
/ip address
add address=1.1.1.1/29 disabled=no interface=ether1
add address=192.168.1.1/24 disabled=no interface=ether2
 
/ip firewall nat
add action=dst-nat chain=dstnat disabled=no dst-addresses=1.1.1.2 to-addresses=192.168.1.10
add action=src-nat chain=src-nat disabled=no src-addresses=192.168.1.10 to-addresses=1.1.1.2
add action=masquerade chain=srcnat disabled=no out-interface=ether1

As you can see in our starting configuration we have two hosts on the inside of our network that sit on the same subnet.
The webserver has 1.1.1.2 NAT’d over to him.

Step 1

Our client at IP 192.168.1.2 browses to hairpin.gregsowell.com. This resolves to public IP 1.1.1.2. This should be fine, right? Our host send the traffic to his default gateway.

As you can see from the graphic we have a src-nat rule and a dst-nat rule that will translate the public to the private and the private over to the public.

Step 2


The router NATs 1.1.1.2 over to the webserver’s internal IP of 192.168.1.10. The Client PC is still sourced from his 192.168.1.2 address.

Step 3


So the webserver sees the request and responds. Since the packet was sourced from 192.168.1.2 and it is on the same subnet as his IP, he will simply send the response directly to 192.168.1.2.

Step 4


So the PC sees a response from the webserver, but he will drop this response. Remember that the Clinet PC originally made the request to 1.1.1.2. When he sees response traffic sourced from 192.168.1.10 he will drop this as unknown traffic. Our exchange fails.

Step 5


Config Adjustment

1
2
3
4
5
/ip firewall nat
add action=dst-nat chain=dstnat disabled=no dst-addresses=1.1.1.2 to-addresses=192.168.1.10
add action=src-nat chain=srcnat disabled=no src-addresses=192.168.1.10 to-addresses=1.1.1.2
add action=masquerade chain=srcnat disabled=no dst-addresses=192.168.1.0/24 src-addresses=192.168.1.0/24
add action=masquerade chain=srcnat disabled=no out-interface=ether1

To correct the issue we add a single src-nat rule to masquerade any traffic sourced from 192.168.1.0/24 destined to 192.168.1.0/24.

The new request starts the same as before.

Step 6


The traffic destination NATs again from 1.1.1.2 to 192.168.1.10. Something new you will notice is that 192.168.1.2 is now masqueraded to 192.168.1.1.

Step 7


The webserver responds to 192.168.1.1 since that is where the traffic was sourced from. The traffic heads back to the router.

Step 8


The router then translates the destination address back to 192.168.1.2 and translates the webservers 192.168.1.10 address back to 1.1.1.2. Last the traffic is sent back to our client PC.

If you guys have any questions or comments, please let me know!

24 Comments

leave a comment
  1. Justin M / Oct 8 2012

    I just ran into this over the weekend. I run Plex at home and it uses a web service to “locate” the server. It returns the public for my home MT. I had my nat rules set to forward 32400 when packets come in from the wan port. On the iPad, packets would come in the LAN and hit the public but not get forwarded because they didn’t come in the WAN. I simply duplicated the rule and set it to my LAN bridge instead. It worked great.

    How would you implement this with DHCP providing the WAN IP?

  2. Greg / Oct 8 2012

    @Justin
    If I had a dynamic IP on the WAN and wanted to NAT resources in I would use “dst-address-type=local” on the dst-nat rule instead of dst-address. This way it would just catch anything destined for an address bound to the router no matter what my WAN address happens to be at the time. The internal hairpin masquerade rule will still work as shown.

  3. JM / Oct 8 2012

    Sorry, but why go into all these troubles?

    1) Force all client DNS requests from LAN to router itself (always good thing to do)
    2) enable /ip DNS stuff
    3) add static entry that resolves hairpin.gregsowell.com to 192.168.1.10.
    Done!

  4. Springs / Oct 8 2012

    That would capture everything on the LAN looking for that port?

    How about this…

    Now assuming that you have a dns address that uses the updater…
    I.E.
    http://user.companyname.com

    /ip firewall address-list
    add address=192.168.88.0/24 disabled=no list=LocalNet

    /ip firewall nat
    add action=masquerade chain=srcnat comment=”Hairpin Nat MASQ” disabled=no out-interface=ether2-master-local \
    src-address-list=LocalNet
    add action=dst-nat chain=dstnat comment=”SSLWebServerOnPort443″ disabled=no dst-address=1.1.1.1 dst-port=8091 \
    protocol=tcp to-addresses=192.168.88.251 to-ports=443

    Script Hairpin Fix
    /system script
    add name=HairpinFix policy=ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive,api source=\
    “/ip firewall nat set “SSLWebServerOnPort443″ dst-address=[:resolve user.companyname.com]”

    This will fill in the IP address of the WAN into the firewall rule.

    Greg can make it a lot easier to understand with one of those pretty pictures.

    Set that script to run on some interval

    But in the end…

    If you were inside the LAN or on the WAN…
    http://user.companyname.com:8091
    Should get you to the device at
    192.168.88.251:443

  5. tom / Oct 8 2012

    I had an issue related to Hairpin NAT (some vendors call it NAT Loopback) and firewall a few weeks ago. I will share 🙂

    The customer’s router has a single public IP and dst-nat’s some services to the inside of the network. The services are also PATed (port 2121 -> 80, port 2323 -> 554, and more…)

    Then there are 20 customer subnets, 10.20.x.0/24 (x = 1 to 20). Each customer subnet has its own server inside of it, offering some services. The inside of the network is not masquaraded.

    The goal was to config the firewall so customers could NOT access the servers outside of their subnet on their private addresses, but COULD still access the servers in other subnets through the public IP using the correct port (that was then NATed to the proper server). Of course hairping NAT had to be involved.

    The problem was that the firewall filtering (forward chain) happends after dst-nat in NAT. So there is really no way to differentiate if the connection was to the server’s private IP on the native port, or to the router’s public IP on the outside port (then PATed to the servers native port on its private IP).

    So how did I solve this:
    LOT of mangle connection marking in the prerouting chain (which happens before dst-nat) and then dropping or not dropping the connections in firewall based on connection mark.

    Ehrm, if anyone knows a better and cleaner way, please do tell 🙂

  6. Greg / Oct 8 2012

    @JM
    Where’s your sense of adventure!?!?!

    Some orgs don’t like to use the MTK DNS…or if they run a windows domain with AD then they will use those. I suppose they could point their windows servers to do lookups through the MTK though.

  7. Greg / Oct 8 2012

    @Springs
    Cool use of scripting. It seems that it would accomplish the same thing just with a little additional complexity 😉

  8. Springs / Oct 8 2012

    JM.

    Do that and you can only reach one device.

    If you only have to reach one device… that is the quickest method.

  9. Greg / Oct 8 2012

    @Tom
    Sounds like a clean solution sir! I’m trying to envision what this customer could have been doing…sounds wacky 😛

  10. JM / Oct 8 2012

    @Greg
    Let me rephrase that – point clients to any DNS server that is under your control..

    @Springs
    I have my whole list of servers here using same thing. Yes, you will have to make one static DNS entry per server, but that beats the creation of NAT Loopback, that makes it harder to handle any other configuration that you might try on your router (such as Mangle,QoS)

    Also this will eliminate the need for traffic to go through the router – clients will be able to reach server directly.

  11. Justin M / Oct 9 2012

    @Springs

    It would make sense to put the public in an address list using the script and a timeout slightly higher then your script schedule.

    That way, you can have one scheduled script for many rules instead of changing the public on each rule, just use the address list. I don’t know about you all, but I don’t have just one port forward rule.

  12. Jorge B. / Oct 9 2012

    @JM
    Regarding setting up the internal DNS to point to the internal address… We tried this approach, but then realized a couple of our mobile phone apps would stop working as the users would roam from the cell phone network to the corporate wifi. The app would cache the resources’ public IP and fail to connect when on the private network.

    I will be implementing/testing this solution this coming weekend.

    Thanks Greg.

  13. Springs / Oct 9 2012

    :global previousIP
    /ip firewall nat set 2,3,4,5,6,7,8,9 dst-address=”$previousIP”

    That was the next incarnation I came up with.

  14. Rory M / Oct 17 2012

    Can anyone confirm if this works in v6 of routerOS? I have not been able to get any of the various “Hairpin NAT” rules to work as expected. My implementation was working fine in v5.

  15. Greg / Oct 17 2012

    @Rory
    I’ll give it a test and let you know.

  16. Greg / Oct 20 2012

    @Rory,

    I just tested with ROSv6rc1 and it seems to work just fine. If you are attempting to do web, ftp, ssh be sure you go to “ip services” and disable those on the router first…that or change them to an alternate port.

  17. David Carroll / Dec 8 2012

    Great article dude, enjoying your blog.

  18. Greg / Dec 10 2012

    @David
    Thank you sir!

  19. Springs / Dec 10 2012

    Needed to update 16 Cameras Ports when the WAN IP changed.

    HairyFix
    :global resolvedIP
    /ip firewall nat set [find comment=”cams”] dst-address=”$resolvedIP”

    That runs at the end of my dyndns updater.

  20. Wyzak / Feb 4 2013

    Hi there,

    Thanks for an awesome guide. Unfortunately I am hitting a snag and I haven’t had any luck trying to get around it.

    dstnat chain can not contain masquerade/snat actions

    Are you sure that there isn’t perhaps a typo in your rules? I get this error message when I try to add the following line “add action=src-nat chain=dstnat disabled=no src-addresses=192.168.1.10 to-addresses=1.1.1.2”

  21. Greg / Feb 4 2013

    @Wyzak
    When I copy/paste to make configs I sometimes make a boo boo…thanks for catching that!

  22. Matthew / Feb 9 2013

    @Greg and @JM, @Jorge B.

    (Sorry, wrote this, then saw Jorge’s reply)… but will just add to @Jorge B. pov.

    @JM, Your solution will work but I imho its actually more complex, and results in more possible resulting issues.

    I think Gregs solution is preferable, because it doesn’t rely on moving goalpost.

    The goal is that the IP be routable from inside the LAN and outside. Many applications (through possible oversight, and assumption), only look up an address once, and this makes it troublesome when the user is moving from in the LAN/VPN to outside it (3g at the office for private usage, or hibernating their PC and moving home). If you try the DNS trick it works, only to find later some apps dont support it. Sure the user can restart the app, but they can also dig secret daggers in your back for tiny annoyances like that.

    Even in a large corporate environment with static public IPs this is still an issue, because we still give our public facing servers private IP addresses.

  23. Greg / Feb 9 2013

    @Matthew
    Thanks for the insight. I like your perspective…I always love it when people agree with me and shun the others…SHUN them Matthew, SHUN THEM! 😉

  24. Jerrod / Nov 12 2020

    I host my own webserver for my WISP and this worked great for me, however one client (so far) has a problem with his old netgear WNR2000v3 whose firewall is apparently blocking the returning packets from my webserver. At least that’s what I think is happening, all other websites work, and he can access port 8443 on the same server. Nslookup returns the correct IP as well. When I put his router in AP bridge mode there was no problem. I know this post is hella old but maybe someone has some insight as to why this is happening? It would be purely academic since there’s a workaround, but maybe it would help someone, or maybe there’s a better solution.

    Also it seems the srcnat rule to/from the same subnet (my lastmile net) does nothing AFAIK, there are zero packets logged for the rule after 2 days…?

Leave a Comment

 

*