OPNsense Forum

English Forums => Tutorials and FAQs => Topic started by: Monviech on July 19, 2023, 11:21:31 am

Title: [Tutorial] - NAT Reflection/Hairpinning with OPNsense
Post by: Monviech on July 19, 2023, 11:21:31 am
LATEST VERSION: https://docs.opnsense.org/manual/how-tos/nat_reflection.html

The version below won't be updated anymore.

This is version 1.2 of a NAT reflection tutorial, being the result of this github threat: https://github.com/opnsense/core/issues/6650
I hope it helps making NAT reflection with the OPNsense a bit clearer.

Please read this warning and use manual rules as described below:
https://docs.opnsense.org/manual/firewall_settings.html#network-address-translation

Changelog:

0.1 What is NAT?

Because there aren't enough free IPv4 addresses, people invented a thing called "NAT" (Network Address Translation). It basically makes a router like the OPNsense translate IPv4 addresses to other IPv4 addresses.

For example, the external IPv4 address 203.0.113.1 is translated to the internal IPv4 address 172.16.1.1, and the other way around! Now things like "NAT Overload (Masquerading - SNAT)" are possible, allowing multiple internal IPv4 addresses to share a single external IPv4 address. But in return, you need "Port Forwarding (Destination NAT - DNAT) to make ports of an internal client accessable by external clients.

SNAT = Source Network Address Translation = Changes the source IP of a packet
DNAT = Destination Network Address Translation = Changes the destination IP of a packet
PAT = Port Address Translation = Changes the destination port of a packet

In summary, NAT saves IPv4 addresses, by enabling many devices to share the same internal IPv4 address spaces (RFC 1918, 192.168.0.0/16 - 172.16.0.0/12 - 10.0.0.0/8), and providing them only one external IPv4 address (or in some cases a few more).

If you create a Destination NAT (DNAT) rule, also known as "Port Forwarding", you give clients in the WAN the ability to access ports of the internal IPv4 address 172.16.1.1, by letting the OPNsense translate the destination from the external IPv4 address 203.0.113.1 to your internal IPv4 address 172.16.1.1. The OPNsense acts like a translator, translating IP addresses between client and server. The OPNsense writes all translations into a file, which is the NAT table. So it knows exactly how traffic should flow back and forth with the translations in place.

0.2 Why do I need NAT reflection?

Your own internal clients cant reach 203.0.113.1 with a normal port forwarding rule. The OPNsense receives the packet, checks the destination IP, checks the interface the packet is received, and drops it. That's because there is no rule in place, that tells the OPNsense where to send the packet to, and to allow it. For the OPNsense, it looks like itself should be receiving the packet, because the external IPv4 address is on its own WAN interface.

That's where NAT Reflection/Hairpin comes in play (as opposed to Split DNS which should be avoided if possible). It helps your internal clients to communicate with 203.0.113.1, by creating rules that use the OPNsense as the "translator" to the actual destination 172.16.1.1.

0.3 In my experience, there are three main combinations to do NAT reflection in the OPNsense.
Choosing ONE of those options and sticking to it will make things work as expected:


0.4 Important: The firewall rules are NEVER created automatically when ticking the checkboxes for automatic NAT reflection (Firewall: Settings: Advanced). That is indeed by design. https://github.com/opnsense/core/issues/6650#issuecomment-1632016179

0.5 There are two different types of NAT reflection:
https://github.com/opnsense/core/issues/6650#issuecomment-1625135426

0.6 My personal opinion and my best practice:

In my opinion, the best way to do NAT reflection in the OPNsense is not to use the automatic rules in (Firewall: Settings: Advanced). Creating the NAT rules manually works great, prevents mistakes, and reduces the amount of rules in the background.

Reasons:
Possible Limitations:

0.7 In my examples you always have three networks:

WAN - 203.0.113.0/24
Webserver External IP - 203.0.113.1
Opnsense - 203.0.113.254

DMZ - 172.16.1.0/24
Webserver Internal IP - 172.16.1.1
Opnsense - 172.16.1.254

LAN - 192.168.1.0/24
Internal Client - 192.168.1.1
Opnsense - 192.168.1.254


0.8 The goal is the following:
Access the Webserver 172.16.1.1 port 443 with it's external IP 203.0.113.1 from a client in WAN, LAN and DMZ.
You can use aliases for everything, but I keep this as simple as possible here.

CHOOSE ONLY ONE OPTION, THEY ARE EXCLUSIVE TO EACH OTHER:

1.0 OPTION 1 - Creating manual Port-Forward NAT (DNAT), manual Outbound NAT (SNAT), and automatic firewall rules

1.1 Firewall: NAT: Port Forward
In verbose, the DNAT rule can be read like this:

If a packet is received by the OPNsense on the interfaces WAN,DMZ,LAN with protocol TCP from the source ip ANY and the source port range 1024-65535 to destination
ip 203.0.113.1 and destination port 443 -> rewrite the destination ip to 172.16.1.1 and the destination port to 443.


The automatic linked floating firewall rule will allow traffic to the destination IP 172.16.1.1 because NAT rules match before Firewall rules. That means the firewall receives the packet and the NAT rule converts the destination from 203.0.113.1 to 172.16.1.1 first, before passing the packet to the firewall filter.

Now, the traffic from the Client 192.168.1.1 in the LAN, and the Client in the WAN can access the Webserver.
But somehow... the Webserver 172.16.1.1 in the DMZ can't reach it's own NATed external IP 203.0.113.1 and it's own HTTPS port 443. Thats where the next rule comes in:

1.2 Firewall: NAT: Outbound
Now the Webserver and all other Clients in DMZ can reach the webserver with it's external IP. This kind of NAT is called "Hairpin NAT" and a combination of DNAT and SNAT. You need this because otherwise the Webserver wouldnt communicate "Webserver <-> OPNsense <-> Webserver" but "Webserver -> Opnsense -> Webserver <-> Webserver", making any traffic asynchronous and HTTPS will fail to work.

In verbose, the SNAT rule can be read like this:

If a packet is received by the OPNsense on the interfaces DMZ with protocol TCP from the source net 172.16.1.0/24 and the source port ANY to destination ip 172.16.1.1 and destination port 443 -> rewrite the source ip to 172.16.1.254 and answer from the OPNsense firewall interface.

2.0 OPTION 2 - Creating automatic Port-Forward NAT (DNAT), manual Outbound NAT (SNAT), and manual firewall rules

2.1 Firewall: Settings: Advanced:
2.2 Firewall: NAT: Port Forward
2.3 Firewall: Rules: Floating
2.4 Firewall: NAT: Outbound

3.0 OPTION 3 - Creating automatic Port-Forward NAT (DNAT), automatic Outbound NAT (SNAT), and manual firewall rules

3.1 Firewall: Settings: Advanced:
3.2 Firewall: NAT: Port Forward
3.3 Firewall: Rules: Floating
4.0 Troubleshooting NAT Rules
Feedback is welcome if aspects of the tutorial can be improved, or if there are mistakes.
Title: Re: [Tutorial] - NAT Reflection/Hairpinning with OPNsense
Post by: lilsense on August 24, 2023, 01:38:26 am
So now that the new version is out, is this contiuing to be the same? I.e. is this the nature of the beastie (FreeBSD). :)
Title: Re: [Tutorial] - NAT Reflection/Hairpinning with OPNsense
Post by: Monviech on August 24, 2023, 06:12:32 am
I'm not sure if I understand your question the right way.
- How this kind of NAT works is not exclusive to FreeBSD.
- How the different options in the GUI of the Opnsense influence which rules are generated in the underlying OS is whats explained in this tutorial. And if there arent any commits that change this behavior, this tutorial stays valid.

Maybe I'll work on including this tutorial into the official docs, in a less opinionated way of course.
Title: Re: [Tutorial] - NAT Reflection/Hairpinning with OPNsense
Post by: cookiemonster on October 01, 2023, 10:35:49 pm
this is very informative, thanks.
Title: Re: [Tutorial] - NAT Reflection/Hairpinning with OPNsense
Post by: Monviech on October 02, 2023, 05:07:01 pm
this is very informative, thanks.

Thank you very much. If you find anything you would add or improve let me know. Just today it was merged into the Opnsense docs. :)