Simple hairpin not working

I’ve read this thread: https://forum.mikrotik.com/viewtopic.php?t=179343 which is most useful in explaining the hairpin issue. Whilst I won’t say my knowledge of NAT is deep, I understand the basic principals. I originally solved via the DNS approach as that, well, worked. My home dev setup is a Windows domain with DNS server so I simply added a zone for my external domain and added the handful of DNS entries.

However, I sometimes have a client laptop visiting that uses NextDNS and therefore effectively DoH. As pointed out in the above head-numbing (but welcome) post, DNS method fails with DoH clients.

So I tried the simple hairpin option described in option #1. Except it doesn’t work. Config below. When I read the post and it talked about dst-net kicking in, I guessed it wouldn’t work because my dst-nat rules specify in-interface-list=WAN. When this hairpin situation occurs, do these rules apply? If remove this parameter, I can access the resource EXCEPT it triggers for every (say) port 80 access, i.e. external web pages stop.

/ip firewall nat
add action=masquerade chain=srcnat comment=hairpin dst-address=192.168.0.0/24 src-address=192.168.0.0/24
add action=masquerade chain=srcnat comment=“outgoing nat” ipsec-policy=out,none out-interface-list=WAN
add action=dst-nat chain=dstnat comment=“simplehelp tcp” dst-port=8008 in-interface-list=WAN protocol=tcp to-addresses=192.168.0.7 to-ports=8008
add action=dst-nat chain=dstnat comment=“simplehelp udp” dst-port=8008 in-interface-list=WAN protocol=udp to-addresses=192.168.0.7 to-ports=8008
add action=dst-nat chain=dstnat comment=http dst-port=80 in-interface-list=WAN protocol=tcp to-addresses=192.168.0.14 to-ports=80
add action=dst-nat chain=dstnat comment=https dst-port=443 in-interface-list=WAN protocol=tcp to-addresses=192.168.0.14 to-ports=443

Just in case the entire firewall configuration is needed:

/ip firewall filter
add action=accept chain=input comment=“Accept established,related,untracked” connection-state=established,related,untracked
add action=drop chain=input comment=“Drop invalid packets” connection-state=invalid
add action=accept chain=input comment=“Accept ICMP (ping)” protocol=icmp
add action=accept chain=input comment=“Accept to local loopback (for CAPsMAN)” dst-address=127.0.0.1
add action=accept chain=input disabled=yes in-interface-list=VLAN
add action=drop chain=input comment=“Only allow access to router from LAN” in-interface-list=!LAN
add action=accept chain=forward comment=“Accept in ipsec policy” ipsec-policy=in,ipsec
add action=accept chain=forward comment=“Accept out ipsec policy” ipsec-policy=out,ipsec
add action=fasttrack-connection chain=forward comment=“Fasttrack existing connections through firewall (no rules applied)” connection-state=established,related
hw-offload=yes
add action=accept chain=forward comment=“Accept established,related, untracked” connection-state=established,related,untracked
add action=drop chain=forward comment=“Drop invalid” connection-state=invalid
add action=drop chain=forward comment=“Drop all from WAN not DSTNATed (block incoming connections)” connection-nat-state=!dstnat connection-state=new
in-interface-list=WAN
/ip firewall nat
add action=masquerade chain=srcnat comment=hairpin dst-address=192.168.0.0/24 src-address=192.168.0.0/24
add action=masquerade chain=srcnat comment=“outgoing nat” ipsec-policy=out,none out-interface-list=WAN
add action=dst-nat chain=dstnat comment=“simplehelp tcp” dst-port=8008 in-interface-list=WAN protocol=tcp to-addresses=192.168.0.7 to-ports=8008
add action=dst-nat chain=dstnat comment=“simplehelp udp” dst-port=8008 in-interface-list=WAN protocol=udp to-addresses=192.168.0.7 to-ports=8008
add action=dst-nat chain=dstnat comment=http dst-port=80 in-interface-list=WAN protocol=tcp to-addresses=192.168.0.14 to-ports=80
add action=dst-nat chain=dstnat comment=https dst-port=443 in-interface-list=WAN protocol=tcp to-addresses=192.168.0.14 to-ports=443

Hi,

Your dstnat rules need to be changed (Hairpin isn’t coming in from a WAN port)

add action=dst-nat chain=dstnat comment=https dst-port=443 in-interface-list=WAN protocol=tcp to-addresses=192.168.0.14 to-ports=443

instead of using in-interface-list=WAN, perhaps use dst-address-type=local

Its impossible to block or control DNS from an encrypted methodology to my knowledge.
In other words there are limits to what one can do with adguard/piehole/doh etc… if the user is savvy enough.

Your dstnat rules need to be changed (Hairpin isn’t coming in from a WAN port)

That’s what I suspected which is why removing it (WAN interface) fixed for these specific cases but broke everything else. This dstnat rule format was created by the wizard in quick start.

instead of using in-interface-list=WAN, perhaps use dst-address-type=local

I’ll give that a go later.

I’m not trying to block them using DoH as such but solve the classic hairpin issue. Not problem with them using DoH but this is what happens when you use DNS to workaround the hairpin issue:

  • Clients on LAN using LAN DNS servers resolve myserver.mydomain.co.uk to the internal IP of the server. No problem.
  • Clients using DoH bypass the LAN DNS servers and resolve to the public external IP address. Classic hairpin issue.

So using DNS to workaround hairpin doesn’t work in this case. To be honest, this is a really edge case as it’s my home dev network and it’s very rarely that clients visit and need to access my internal server. Mainly SimpleHelp remote control.

I’m using this opportunity to learn more about firewalls and NAT. As I said, I have the basic understanding and as a programmer, I can envisage how the logic is applied. But devil is in the detail.

Be careful that this will translate ALL connections to tcp/443, not just to your external interface.

Ahh, I did wonder about that as that’s effectively what happens if I remove the WAN interface list. In the article linked above, it states:

To fix the scenario where the LAN users and the Server are on the same subnet, > all that is required > is the following generic source NAT rule, often called the HAIRPIN NAT Rule placed as the first source NAT rule (although I have been told order here is not important). This required source NAT rule is independent of the type of WANIP (static/dynamic).

The key addition to the NAT rules is the addition of a source-nat rule placed before the default src-nat rule (substitute with your server subnet).
add chain=srcnat action=masquerade dst-address=192.168.88.0/24 src-address=192.168.88.0/24

I suspect the bit “all that’s required” isn’t quite true. Another suggestion is to use another subnet. This might work for me as I have the main private Wi-Fi interface but also a second 10.0.0.0/24 VLAN used for guests. Will try connecting client DoH laptop to that. Later!

Rule in forward chain needs to be
add chain=forward action=accept connection-nat-state=dstnat

The old default rule can be deleted but you need to add two more rules. THis one above it…
add chain=forward action=accept in-interface-list=LAN out-interface-list=WAN comment=“internet traffic”

and this one as the last rule in the forward chain
add chain=forward action=drop comment=“Drop All Else”

Thanks for that - will have a look at that later too.

Another suggestion is to use another subnet. This might work for me as I have the main private Wi-Fi interface but also a second 10.0.0.0/24 VLAN used for guests. Will try connecting client DoH laptop to that.

That didn’t work either although I suspect when the article talks about putting the server on a different subnet, it means one behind another bridge/router. My 10.0.0.0/24 network is accessed via a virtual wireless interface connected via VLAN to the same router/switch.

Here is what it worked for me:

/ip firewall nat
add action=masquerade chain=srcnat comment=“Hairpin NAT” connection-mark=“Hairpin NAT” log-prefix=“Hairpin NAT Masquerade”
add action=masquerade chain=srcnat comment=“Default NAT Masquerade” out-interface=ether1.12 (VLAN for my ONT)
add action=dst-nat chain=dstnat comment=“Port forward NPM from Outside” dst-address-list=WAN dst-port=443 log-prefix=“HTTPS NPM” protocol=tcp src-address-list=!LAN to-addresses=INSERT-NGINX/NPM-IP-HERE to-ports=443
add action=dst-nat chain=dstnat dst-address-list=WAN dst-port=80 protocol=tcp src-address-list=!LAN to-addresses=INSERT-NGINX/NPM-IP-HERE to-ports=80
add action=dst-nat chain=dstnat comment=“Port forward NPM from Inside” dst-address-list=WAN dst-port=443 protocol=tcp src-address-list=LAN to-addresses=INSERT-NGINX/NPM-IP-HERE to-ports=443
add action=dst-nat chain=dstnat dst-address-list=WAN dst-port=80 protocol=tcp src-address-list=LAN to-addresses=INSERT-NGINX/NPM-IP-HERE to-ports=80

/ip firewall mangle
add action=mark-connection chain=prerouting comment=“Mark connections for hairpin NAT” dst-address-list=WAN log-prefix=“Hairpin NAT Pre Routing” new-connection-mark=“Hairpin NAT” passthrough=yes src-address-list=LAN

Please take into account that I am not a network expert :stuck_out_tongue: Maybe the experts in this thread will confirm if this is actually properly configured or not and if it suits your scenario. At least it fixed my split-brain DNS issues, which seems to be exactly what you are trying to solve.

Hth,
anthonws.

I was about to ask if somebody minded posting their firewall config that’s using hairpin :smiley:

Here is what it worked for me:

And it worked for me on the DoH laptop… I can see exactly what it’s doing. Using mangle to mark packets from the LAN to the WAN which is then processed by the srcnat rule. Learning a lot here. Just need to zap my internal DNS zone record to make sure it works for computers on the LAN.

Later… except it broke external access. I might have got something wrong on converting to my environment. Getter there though.

Actually you don’t need the mangle rule at all. The crux of the hairpin is to dstnat what goes to the IP assigned to the external interface and just that, regardless of where the connection comes from, and NAT the source ONLY IF the connection does a 180°.

You can have the following, provided your address-list WAN contains the public IP of your router and LANSUBNET the subnet assigned to your local network. For WAN, your group can do it for example through resolution or scripting.

/ip firewall nat
# Hairpin
add action=masquerade chain=srcnat comment="Hairpin NAT" src-address-list=LANSUBNET dst-address-list=LANSUBNET
# Exposing the server
add action=dst-nat chain=dstnat comment="Port forward 80" dst-address-list=WAN dst-port=80  protocol=tcp to-addresses=<INTERNAL ADDRESS>
add action=dst-nat chain=dstnat comment="Port forward 443" dst-address-list=WAN dst-port=443  protocol=tcp to-addresses=<INTERNAL ADDRESS>

Instead of doing a hairpin NAT, it is possible to add a static DNS entry that will resolve internally the name you have on the internet but with the internal IP. This requires all the internal machines to use the Mikrotik as their DNS server.

/ip dns static
add address=<INTERNAL ADDRESS> comment="My exposed router but from the inside" name=<EXTERNAL NAME>

Lastly, You may opt to isolate the router on its own subnet. In that case, only the dstnat is needed as you are no longer doing a hairpin NAT. This would be my recommended solution as currently, you have a server directly reachable from the internet sitting in the middle of your workstations and other servers.

Well stated vinfgjfg!! ( all that info was on the link provided, not sure how mangling got into the mix either )
The only issue is your last sentence has a typo…
Lastly, You may opt to isolate the router on its own subnet. In that case, only the dstnat is needed as you are no longer doing a hairpin NAT. This would be my recommended solution as currently, you have a server directly reachable from the internet sitting in the middle of your workstations and other servers.

I think you meant SERVER!

Thanks everyone for their input here. Fascinating subject. I will return to this soon as I’m like a dog with a bone on things like this. Probably spend hours learning (which is good) for a real edge case. But then again, I spent hours bringing a 10 year old laptop back to life!

Correct, server not router.