Basic NAT hairpin rule just doesn't work

Bashing my head in here.

I should note the problem I’m trying to solve. I have a dynamic WANIP, and I am self-hosting a box with ssh on it for remote access and git repository stuff. Obviously, without NAT hairpinning, if I attempt to connect to my box using my public IP within my own network, my endpoint is my router and not my box. This is terribly frustrating as one might guess.

I’ve read multiple forum posts about this topic already. Maybe it’s a lack of understanding on my part or I’m missing something obvious, but adding this following rule:

chain=srcnat action=masquerade src-address=192.168.69.0/24 dst-address=192.168.69.0/24 log=yes log-prefix="hairpindebug:"

… does not do anything. I’m using an rb750r3 with pretty much just a stock configuration, minus some other NAT rules for port forwarding. I have zero clue why this may not be working, and so I must drag myself to the dark dungeon known as the forum to try and see if any more knowledgeable people can help me out. I will be posting the rest of my filter and NAT rules just in case those are the culprits, and I will post whatever else configuration that might need to be looked at. Thank you very much in advance.

Filter Rules:

 0  D ;;; special dummy rule to show fasttrack counters
      chain=forward action=passthrough 

 1    ;;; defconf: accept established,related,untracked
      chain=input action=accept connection-state=established,related,untracked log=no 
      log-prefix="" 

 2    ;;; defconf: drop invalid
      chain=input action=drop connection-state=invalid 

 3    ;;; defconf: accept ICMP
      chain=input action=accept protocol=icmp log=no log-prefix="" 

 4    ;;; defconf: accept to local loopback (for CAPsMAN)
      chain=input action=accept dst-address=127.0.0.1 

 5    ;;; defconf: drop all not coming from LAN
      chain=input action=drop in-interface-list=!LAN 

 6    ;;; defconf: accept in ipsec policy
      chain=forward action=accept ipsec-policy=in,ipsec 

 7    ;;; defconf: accept out ipsec policy
      chain=forward action=accept ipsec-policy=out,ipsec 

 8    ;;; defconf: fasttrack
      chain=forward action=fasttrack-connection connection-state=established,related 

 9    ;;; defconf: accept established,related, untracked
      chain=forward action=accept connection-state=established,related,untracked 

10    ;;; defconf: drop invalid
      chain=forward action=drop connection-state=invalid 

11    ;;; defconf: drop all from WAN not DSTNATed
      chain=forward action=drop connection-state=new connection-nat-state=!dstnat 
      in-interface-list=WAN

NAT Rules:

 0    ;;; NAT Hairpin
      chain=srcnat action=masquerade src-address=192.168.69.0/24 
      dst-address=192.168.69.0/24 log=yes log-prefix="hairpindebug:" 

 1    ;;; defconf: masquerade
      chain=srcnat action=masquerade out-interface-list=WAN log=no log-prefix="" 
      ipsec-policy=out,none 

 2 X  ;;; debian-music.local expose http port
      chain=dstnat action=dst-nat to-addresses=192.168.69.6 to-ports=80 protocol=tcp 
      in-interface-list=WAN dst-port=80 log=no log-prefix="" 

 3    ;;; Expose debian-music.local ssh port
      chain=dstnat action=dst-nat to-addresses=192.168.69.6 to-ports=22 protocol=tcp 
      in-interface-list=WAN dst-port=22 log=yes log-prefix="ssh:debian-music.local:"

Check the very first answer from https://forum.mikrotik.com/search.php?keywords=nat+harpin

https://gregsowell.com/?p=4242

As you have dynamic IP you should use address list as a point of reference, not the particular address.
Address list should dynamicaly resolve your external address what could be monitored with IP/Cloud service based on your router sn#.

https://forum.mikrotik.com/viewtopic.php?t=179343

I’ve seen your posts! You seem fairly prolific here. I’ll read this thread and see if I can get anything working. Thank you :slight_smile:

Or you can just use some dyndns service and in local dns put static record of that dyndns host to be resolved as local ip without need to use harpin for such purpose.

(1) It should be fairly clear that you need to do one thing regardless if your WANIP is static/fixed or dynamice and that is create the hairpin nat rule, based on the subnet that the server is found in.
Note that this single rule IS ONLY required if the users are in the same subnet.

add chain=srcnat action=masquerade src-address=SUBNET dst-address=SUBNET

This provides the loopback functionality necessary for the user to get the return traffic that hits the server.
Without this rule, the originating user device sees the return traffic coming from the local IP of the server, vice the expected WANIP of the router and is thus rejected by the user device.

With respect to fixed static WAN the above rule is all that is needed.

(2) Keep in mind for fixed WAN the format of SRC NAT and DST NAT are different.

SRCNAT
dynamic IP: action=masquerade our-interface=WAN or (more common) out-interface-list=WAN
static IP: action=src-nat out-interface=etherX to-address=WANIP

DSTNAT
dynamic WANIP: in-interface-list=WAN
static WANIP: dst-address=WANIP

Now if we introduce a hairpin nat scenario, user in same subnet as server, all we need to do for fixed WANIP is add the hairpin nat rule.
Not the case for dynamic WANIP.

+++++++++++++++++++++++++

(3) First of all, if the user is coming locally, from the LAN, it should be clear that this part of the normal dst-chat rule is SUSPECT!!
add chain=dstnat action=dst-nat in-interface-list=WAN dst-port=456 protocol=tcp to-addresses=192.168.88.5

The user is not coming in from the WAN interface, so right off the bat this rule is no longer appropriate.
Secondly, and with that in mind we hve to find a way to point the traffic at the WANIP… but the WANIP is dynamic.

The simplest solution is to use the IP Cloud already existing on the router. One sticks that into a firewall address list (and the router resolves the name to an IP for you).
Hence instead of saying dst-address-WANIP, we state dst-address-list=WANIP .

There are multiple ways to tackle this issue such as bypassing the need for hairpin nat by moving either the servers or users to a different subnet, or heaven forbid let them use the lanip of the server LOL.

+++++++++++++++++++++++++++

  1. Finale note, the firewall port forwarding rule can also get in the way of port forwarding as the standard rule also assumes traffic coming in on the WAN and thus I normally recommend changing the default rule to the much clearer…
    add action=accept chain=forward comment=“allow port forwarding” connection-nat-state=dstnat

This assumes you have a drop all else rule at the end of the forward chain.

Well, the rule is there. Seeing as this is a simple home setup, I only have the one subnet, so I don’t see any need to change the rule I have there already unless I missed something. I posted my rules in my original post if you need to check them again.


I am on a dynamic WAN, and it appears the rules I have setup for NAT are properly set up for it.


This is probably what is confusing me the most so far. Honestly, all of this is fairly confusing, I barely know anything about networking. I just want my #&@! to work.

From what I’m understanding so far, the port forwarding rules are what are causing my headache. The router sees that internal connections to my WANIP from the interfaces that aren’t from the WAN interface list, and so the rule ends up not actually connecting me to my desired endpoint (the server on my local network).

The IP Cloud solution makes sense, but I don’t see why I can’t get my WANIP by extracting it from my DHCP client connected to my ISP. I wrote a ddns updater script (afraid.org) that does this, actually. In fact I think I saw a script in that thread you sent me that does just that, though it didn’t make much sense to me. Maybe I can ask to elaborate on that if I don’t figure it out myself.

What would be useful to know in this case is if there’s some kind of hook I can run a script off of when my DHCP client gets leased a new IP. It seems to exist, but I have no idea how to use it or where I can even configure it. EDIT: I found it just now, all I had to do was read the docs LOL https://wiki.mikrotik.com/wiki/Manual:IP/DHCP_Client#Properties


I’m not exactly sure what rule you are talking about here. Could you elaborate?

Yeah getting this to work can be fun. :slight_smile:
However getting it to work without understanding what you are doing ( ie a copy and paste job) is a complete waste of time.

Up to you whether to accept the advice…

As for confusion how can it be confusing?
A. you for some reason want users to use the domain name or something to force users to use a domain name associiated with the WANIP to access a local server, to me thats rather ridonkulous.
However many people do this so we have to ensure the traffic goes through…

B. you have a destination nat rule that says I want to destination nat traffic coming in from the WAN… Sorry to burst your bubble but the local LAN users cannot possibly come in on the WAN SIDE. They can reach the WANIP of the router from an internal location traveling through internal known interfaces on the router, but that is different then physically arriving at the WAN interface from the ISPs flow of traffic. Therefore one as to reach the WANIP as in the static/fixed case, in an inventive way. One method is to use dst-address-list which is the same as dst-address for our purposes.
The nice thing is that by using the IP cloud service on the router, the router can resolve the associated domain name to the correct wanip everytime and thus perfectly mimicking the fixed WANIP that does work.

Elaborating on why I marked anav’s reply as the solution:

Based on what they said, it was apparent to me that my rule was correct, but the way I was forwarding my port was not since I was limiting my connections on the interface level, rather than explicitly referencing my WANIP.

Using the DHCP Client script in this post https://forum.mikrotik.com/viewtopic.php?t=179343#p885249, I changed from specifying a specific port (in my case it was my default “WAN” interface list) to specifying an address list that gets dynamically updated to my WANIP by the previously linked DHCP script. This is a fairly complicated solution though. Probably stick to using the IP Cloud solution instead.

I completely agree. Thankfully it clicked and I figured it out! My problem is that I still don’t see a very clear big picture since I don’t have all of the details, but the reasoning makes sense to me. I guess I should try taking some networking lessons to get a better understanding of all of this.

I would certainly like to thank you for helping me figure it out though! I didn’t use the IP cloud, but I did use the DHCP Client script. It’s quite clever, I must admit.

There’s always the simple and (almost) foolproof dst-address-type=local. The “almost” part is when you use it with port that you also use to manage router, e.g. 80 when you use WebFig on default port, that will lock you out. But you can combine it with dst-address=!192.168.69.1 to exclude internal address (! means not).

What sob is referring to is described in the link provided earlier.
Think of it as another approach that works…typically easy for a one subnet only approach.
Basically what you tell the router is destination nat to a local interface ( meaning an interface on the router ). but deny the router the use of the local subnet interface.
This leaves the only other local interface available, the wan interface

Let assume the subnet is 192.168.88.0/24 and the gateway of the subnet 192.168.88.1, and the server is .68 using port 12566.
add chain=dstnat action=dst-nat dst-address-type=local dst-address=!192.168.88.1
protocol=tcp dst-port=12566 to-addresses=192.168.88.68

The router can only find the local WAN interface because you have denied the single local subnet interface. Now this wont work if you have two subnets as the router will then select the other subnet so now you have to make a dst-address-list for two subnets and so on…

It all depends on what you want. Even with multiple subnets, you can use dst-address=!192.168.0.0/16 to exclude all internal addresses from this range.

Or you can simply not exclude some. E.g. if you have primary LAN and separate LAN for guests, and you want to use WebFig from main LAN only, then if you do dst-address=!192.168.88.1 (main LAN), then dstnat will work for 192.168.99.1 (guest LAN). It’s wrong because you don’t actively want dstnat from 192.168.99.1, but at the same time it should be harmless, because if the service you have dstnat for is public, then guests could access it using public address anyway, and who cares if another one also works.

But be careful, addresses and interfaces are independent. Previous example wouldn’t work as security solution, if you’d want to prevent guests from accessing WebFig. Because even guests could simply connect to 192.168.88.1. Solution for this would be proper firewall filter (in input chain) that would not allow any access to router from guest interface (except DNS, if the have router as resolver).

Yup one can go down many rabbit holes, stick to the simple that you understand… I usually throw carrots down them and sob ends up burrowing into the ground throwing up lots of good dirt :slight_smile:

This is actually pretty clever too. I could have saved a little trouble doing that instead, but I’m also using the DHCP client script-hook-whatever to update my DDNS, so I found it more intuitive to just do it all in one go while I was messing around with that. Not having to depend on the scheduler is nice :slight_smile:. But, again, thank you everyone.