Note: The description in the article is based on FortiGate FG-300E with FortiOS version 7.2.8. It is configured as an FGCP cluster and uses VDOM. But for this case, it doesn't have a big impact.
NAT Hairpinning
It is a situation where a device accesses another device in an internal network through an external network. A reverse loop in NAT occurs. Traffic passes through the internal interface of FortiGate to the internet and then returns to the same interface (internet) where the external IP is connected. FortiGate redirects traffic using Virtual IP to the local destination. Traffic never leaves FortiGate (it doesn't actually go out to the internet).
Simplified, a client (in LAN with a private IP) communicates to a public IP address, which is served by FortiGate. A Destination NAT (DNAT) is set on this address to the private IP of the server (in LAN). Further in the article, we will provide a detailed description with images.
Note: This entire description has the condition that internal IP addresses and address translation (Network Address Translation) between public and private are used. If we were using routing and only public IP addresses, we obviously wouldn't be dealing with NAT complications. For FortiGate, it's communication between directly connected networks, so it can perform routing. We don't address Central NAT in this article.
Various scenarios
Generally, Hairpin NAT refers to a situation where communication leaves an internal network (LAN) and returns to the same network (LAN) through an external IP. In the case of FortiGate, a similar situation is when the destination is in a different network (like DMZ) behind FortiGate. For practice, it's important how Source NAT (SNAT) behaves in all cases.
There are many scenarios where we encounter this situation, and FortiGate may behave differently than some other Firewalls. The same case is when we have a LAN - DMZ - WAN topology. Communication from LAN goes through DMZ to the internet. We have servers located in the DMZ, and users from LAN communicate with them using a public IP address.
Years ago, when I switched from another FW to FortiGate, I discovered different behavior. When communicating to servers in DMZ from LAN (via public IP address), the DMZ address appears as the source on the servers (and not the public one to which SNAT should occur). I took it as a fact and didn't investigate.
Now a situation has arisen where I need the communication to arrive from a NATed public IP address. When communicating between two internal networks behind FortiGate through a public IP. Ondra Večl from Fortinet advised me on the correct article Technical Tip: Configuring Hairpin NAT (VIP), which describes this area. The term Hairpin NAT is used here, which I hadn't encountered before.
Related FortiGate features
At the beginning, let's recall a few (basic) features or behaviors of FortiOS that are related to the described situation.
Firewall policy selection
Firewall Policies are applied (selected) using the First Fit method. The defined policies are traversed from top to bottom (in the specified order, which we can change) and if the entire policy matches, it is used and the search does not continue further. Only one policy can be applied to each packet.
If we create a more specific policy, we must place it above the more general one for it to take effect. On FortiGate, we can move policies up and down by dragging them.
Packet processing - packet flow
How packet processing occurs, and where it passes on input and output, is described in the article Packet flow ingress and egress: FortiGates without network processor offloading.
Virtual IP and the significance of Interface (extintf)
When creating a Virtual IP, we set the Interface in the GUI. We can either use any
or select a specific interface. In CLI, the command with the parameter set extintf
is used. One would expect that when choosing a specific interface, this DNAT (use of VIP) would only be performed on traffic coming from it. The documentation doesn't explain the behavior, and clarity is brought by the article Technical Tip: Firewall VIP - difference in 'srcintf-filter' and 'extintf'.
This setting only does that in the GUI we cannot assign VIP to an FW policy where there is a different source interface. It doesn't bind VIP to a specific interface, so it accepts connections from any interface. If we really want to limit possible interfaces, we must use set srcintf-filter
.
Source NAT and Destination NAT for server
If we use Source NAT with Use Outgoing Interface Address for communication from a specific network. For another public IP address, we create a Virtual IP, where we map all ports to an internal server. Then outgoing communication from this server will leave from the public IP address used in the VIP.
If we want the communication to leave from a specific public IP address, we use Source NAT with Use Dynamic IP Pool in the policy. We create (for example) an IP Pool Overload with one IP.
Deny Policy and Virtual IP
If we have a policy with Virtual IP that allows communication from external networks to an internal server (using DNAT). And we prepend a general policy with a DENY action, which should block communication from a certain external IP. Then the traffic is not blocked and the policy with Virtual IP is applied.
We must either create a policy where the destination is the given Virtual IP and the action is DENY. Or add a CLI command to the general DENY policy.
set match-vip enable
More in Technical Note : DENY Policy for Virtual IP Firewall Policy, Technical Tip: Firewall does not block incoming (WAN to LAN) connection even though deny policy .
Thanks to Ondra Hornig for the addition. From FortiOS 7.2.4, match-vip
is automatically enabled for newly created policies with the DENY action. It's the default value, so it's not listed unless we use show full-configuration
. FortiOS Release Notes – Bug ID 819937.
Description of situations and NAT behavior
Classic Hairpin NAT
NAT Hairpinning is described on the internet when communication leaves an internal network (LAN/DMZ) and returns to the same network (LAN/DMZ) through an external IP address. It's also mentioned in RFC 5128. An example of such a situation is shown in the image.
- client
10.0.0.100
communicates to the service200.0.0.10
- through the default GW, communication arrives at the FW (DMZ interface)
- FW translates the address
200.0.0.10
to server10.0.0.10
(DNAT) - it has the address
200.0.0.10
on the WAN interface (internet), it treats the packet as if it came from WAN - FW sends communication to the server, source IP
10.0.0.100
, destination IP10.0.0.10
So, if the last step was like this, we would have a problem. The server would send the response to IP 10.0.0.100
, which is in the same subnet. The client would receive a response from IP 10.0.0.10
instead of the expected 200.0.0.10
, so it would discard the packet. Therefore, FortiGate automatically performs SNAT to the address of the outgoing interface (on another FW, we probably need to create the correct configuration).
Communication proceeds:
- FW sends communication to the server, source IP
10.0.0.1
(SNAT), destination IP10.0.0.10
- server responds to address
10.0.0.1
, source IP10.0.0.10
- FW finds the communication in the NAT table, performs SNAT and DNAT (returns the swapped original addresses) and sends from IP
200.0.0.10
to IP10.0.0.100
Hairpin NAT and two internal networks
Fortinet describes as Hairpin NAT also a situation where communication leaves an internal network (LAN/DMZ) and returns to another network behind FortiGate (LAN, DMZ, etc.) through an external IP. An example of such a situation is shown in the image.
- client
10.0.0.100
communicates to the service200.0.0.10
- through the default GW, communication arrives at the FW (DMZ interface)
- FW translates the address
200.0.0.10
to server10.1.0.10
(DNAT) - it has the address
200.0.0.10
on the WAN interface (internet), it treats the packet as if it came from WAN - FW sends communication to the server, source IP
10.0.0.100
, destination IP10.1.0.10
- server responds to address
10.0.0.100
, source IP10.1.0.10
- FW finds the communication in the NAT table, returns the original address as the source and sends from IP
200.0.0.10
to IP10.0.0.100
Configuration on FortiGate
Two policies for outgoing and incoming traffic
As standard, it seems to me the option where we create two policies (Firewall Policy). One deals with general communication to the internet and the other with communication to the server from the internet. We don't specifically address that the client and server are in a neighboring (same) network.
- allowing selected outgoing communication from internal network(s) to the internet, using Source NAT
config firewall policy edit 1 set name "DMZ to Internet Web" set srcintf "DMZzone" set dstintf "virtual-wan-link" set action accept set srcaddr "DMZnetwork" set dstaddr "all" set schedule "always" set service "HTTP" "HTTPS" set logtraffic all set nat enable next end
- allowing selected incoming communication from the internet to the internal network (server publication) through a public IP address, using Destination NAT
config firewall policy edit 2 set name "www - HTTP/S" set srcintf "virtual-wan-link" set dstintf "DMZ2zone" set action accept set srcaddr "all" set dstaddr "200.0.0.10 10.1.0.10 web" set schedule "always" set service "HTTPS" "HTTP" set logtraffic all next end
- for incoming communication, we must create a Virtual IP, which can have the Interface set to
any
or our internet interface (Internet1
)
config firewall vip edit "200.0.0.10 10.1.0.10 web" set extip 200.0.0.10 set mappedip "10.1.0.10" set extintf "Internet1" next end
Note: On FortiGate, the behavior is almost the same whether the communication is from an internal network to the same one (e.g., DMZ - DMZ) or to a neighboring one (e.g., DMZ - DMZ2). The examples given are always for one situation and can be for the other as well.
Finding policy and Source NAT behavior
If we have the configuration as described above, SNAT can function in three different ways. Depending on where we're communicating to.
- client (DMZ) communicates to the internet
- client (DMZ) communicates to a server in the same network (DMZ) via a public IP address
- client (DMZ) communicates to a server in a neighboring network (DMZ2) via a public IP address
How is the FW policy found and applied? In the first case, the outgoing policy [1]
with SNAT is applied.
The other two cases are more complex. The traffic seems to pass through FortiGate twice, but only one policy can be applied to a packet.
- communication comes from an internal interface (DMZ) and the destination is in the internet
- the policy for outgoing communication
[1]
is found, traffic must be allowed, but according to Fortinet information, SNAT is not applied because traffic processing is not yet complete - FortiGate knows that it translates the public IP address to an internal one, so it finds a second policy for incoming traffic
[2]
, performs DNAT, but SNAT is not defined (the last found policy is applied) - if the incoming and outgoing interface is the same (DMZ to DMZ), FortiGate automatically performs SNAT to the interface address (DMZ
10.0.0.1
), so we don't need to configure anything special and communication works (I haven't found this officially described, but this is how it practically behaves)
Traffic logging
Two policies are applied to the traffic in some way, but the last (second) one is fully applied. FortiGate stores information in the log only about the second policy. If the first policy doesn't exist (outgoing traffic is not allowed), then communication won't work.
Interestingly, we won't find disallowed (blocked) communication in the log. For example, when we create a policy to block outgoing communication to a given public address and set complete logging. In FortiAnalyzer, we won't find any mention of this traffic.
Impact of not performing SNAT
The practical impact is that in communication (at the destination and on the FW), our internal addresses are visible as source addresses. When we create a policy on FortiGate where we publish a service to the internet and want to allow communication only from certain sources. So for our internal networks, we must use the appropriate internal addresses instead of the simpler range of public addresses (to which we NAT communication to the internet).
Personally, it would seem logical to me that when I have two networks between which direct communication is not allowed. And we communicate through a public IP address, SNAT should be used to a public address. This doesn't happen on FortiGate.
There are many situations where we need communication to leave from a specific public IP address (just as it leaves for the internet). For example, when we have two networks, each containing a mail server and we have SPF enabled. If one server sends a message to another, we need the public IP that we use for sending to the internet to be used.
Setting up the required Source NAT translation
If we need the traffic to use a specific public IP address, we must have an incoming policy where, along with DNAT, SNAT is also set to a specific IP Pool (it can also be the address of the outgoing interface). We could add SNAT to our second policy [2]
, but we probably don't want communication from the internet to have our address instead of its public addresses.
So we need to create (duplicate) the same policy, where we adjust the source (only for communication from our network) and specify SNAT.
config firewall policy edit 3 set name "www - HTTP/S 2" set srcintf "virtual-wan-link" set dstintf "DMZ2zone" set action accept set srcaddr "DMZnetwork" set dstaddr "200.0.0.10 10.1.0.10 web" set schedule "always" set service "HTTPS" "HTTP" set logtraffic all set nat enable set ippool enable set poolname "SNAT 200.0.0.1" next end
Such a configuration is not complicated, but in practice, it can mean dozens of special policies. It also reduces the clarity of our configuration. It's important to order the policies correctly. When we create an address object for our network (DMZnetwork
), we must not set the Interface to the corresponding interface. We use it in a policy where the source interface is WAN.
If we have several servers in DMZ2 that we need to communicate with via a public address, we must duplicate their publication policy for each. If we have more networks (than 2), where communication should leave from a different public IP address each time, it adds more policies. The extreme case is when we have different servers in DMZ and DMZ2, communication from each leaves through its public IP, so we must create a policy for each pair.
Normally, we can set SNAT on one rule for outgoing communication from the entire network and everything will be translated to the specified public IP. But in this case, we set it at the destination, which means much more configuration. If possible, it's simpler to assume that communication comes from certain internal addresses rather than creating rules for SNAT.
One policy for traffic between networks
In the first case, we made policies for DMZ - WAN and WAN - DMZ2 (or WAN - DMZ). It makes sense to make a policy directly for DMZ - DMZ2 (or DMZ - DMZ). Although we communicate via a public IP address (WAN), FortiGate can handle it. The condition is that in the Virtual IP, we must have the Interface set to any
. Otherwise, we can't assign it to a policy where the source interface is DMZ instead of Internet.
Fortinet states that we can use this option only for public IP addresses that are not directly assigned to an external interface (internet). I did a few tests and it's true. According to the example, we have IP 200.0.0.1
set on the interface to the internet. When a Virtual IP is created for it and used in a policy, the communication doesn't work. We need to make two policies (DMZ - WAN and WAN - DMZ) for the communication to work. When we create a Virtual IP with address 200.0.0.10
, one policy is enough and communication works.
config firewall policy edit 4 set name "www - HTTP/S" set srcintf "DMZ2zone" set dstintf "DMZ2zone" set action accept set srcaddr "DMZ2network" set dstaddr "200.0.0.10 10.1.0.10 web" set schedule "always" set service "HTTPS" "HTTP" set logtraffic all set nat enable set ippool enable set poolname "SNAT 200.0.0.1" next end
config firewall vip edit "200.0.0.10 10.1.0.10 web" set extip 200.0.0.10 set mappedip "10.1.0.10" set extintf "any" next end
In the policy, we can set Source NAT to one of three variants:
- SNAT disabled - if the communication is to the same network, the interface address is used as outgoing (e.g., DMZ
10.0.0.1
), otherwise the original address remains - SNAT with outgoing interface address - perhaps surprisingly, the VIP address is used (
200.0.0.10
), according to the rule of using SNAT and existing VIP - SNAT with dynamic address - the specified address is used as the outgoing address (e.g.,
200.0.0.1
)
There are no comments yet.