FortiGate
FortiGate Next Generation Firewall utilizes purpose-built security processors and threat intelligence security services from FortiGuard labs to deliver top-rated protection and high performance, including encrypted traffic.
pmanak
Staff
Staff
Article Id 330306
Description This article describes the behavior of NAT Hairpin when Central NAT is configured on the FortiGate Firewall.
Scope FortiGate.
Solution

When a user behind the firewall would like to access the Server on public IP, which is also behind the same interface of the firewall, the NAT hairpin is used. The behavior of the NAT hairpin is different in cases where Central NAT is enabled on the firewall.


Consider an example where there is both a Client and a Server behind Port1 of the FortiGate firewall. The user would like to access the server over the public IP, so NAT Hairpin is used and a Port1 to Port1 policy is required to allow the traffic. Below is the IP scheme:

 

Port1: 10.115.3.57
Client: 10.115.3.125

Server: 10.115.3.129
VIP: 20.20.20.1  à 10.115.3.129

pmanak_0-1722708437200.png

 

A TCP session from the client to the server over port 5201 is initiated. When this session starts, it hits policy 4, DNAT takes place, and, consistent with known NAT hairpin behavior, it will also SNAT the outgoing traffic even if there is no Central NAT policy configured for this traffic. Below is the session and debug flow output:

 

config firewall policy

    edit 4

        set name "Nat-hairpin"

        set srcintf "port1"

        set dstintf "port1"

        set action accept

        set srcaddr "all"

        set dstaddr "10.115.3.129"

        set schedule "always"

        set service "ALL"

        set auto-asic-offload disable

    next

session info: proto=6 proto_state=01 duration=31 expire=3588 timeout=3600 refresh_dir=both flags=00000000 socktype=0 sockport=0 av_idx=0 use=5

origin-shaper=

reply-shaper=

per_ip_shaper=

class_id=0 ha_id=0 policy_dir=0 tunnel=/ vlan_cos=0/255

state=log may_dirty f00

statistic(bytes/packets/allow_err): org=456/8/1 reply=256/6/1 tuples=4

tx speed(Bps/kbps): 14/0 rx speed(Bps/kbps): 8/0

orgin->sink: org pre->post, reply pre->post dev=9->9/9->9 gwy=10.115.3.129/10.115.3.125

hook=pre dir=org act=dnat 10.115.3.125:63261->20.20.20.1:5201(10.115.3.129:5201)

hook=post dir=org act=snat 10.115.3.125:63261->10.115.3.129:5201(10.115.3.57:63261)

hook=pre dir=reply act=dnat 10.115.3.129:5201->10.115.3.57:63261(10.115.3.125:63261)

hook=post dir=reply act=snat 10.115.3.129:5201->10.115.3.125:63261(20.20.20.1:5201)

pos/(before,after) 0/(0,0), 0/(0,0)

misc=0 policy_id=4 pol_uuid_idx=662 auth_info=0 chk_client_info=0 vd=0

serial=0000af31 tos=ff/ff app_list=0 app=0 url_cat=0

rpdb_link_id=00000000 ngfwid=n/a

npu_state=0x000001 no_offload

no_ofld_reason:  disabled-by-policy

 

2024-08-03 09:20:21 id=65308 trace_id=100077 func=print_pkt_detail line=5879 msg="vd-root:0 received a packet(proto=6, 10.115.3.125:63261->20.20.20.1:5201) tun_id=0.0.0.0 from port1. flag [S], seq 2356297818, ack 0, win 64512"

2024-08-03 09:20:21 id=65308 trace_id=100077 func=init_ip_session_common line=6063 msg="allocate a new session-0000af31"

2024-08-03 09:20:21 id=65308 trace_id=100077 func=get_new_addr line=1265 msg="find DNAT: IP-10.115.3.129, port-0(fixed port)"

2024-08-03 09:20:21 id=65308 trace_id=100077 func=fw_pre_route_handler line=187 msg="VIP-10.115.3.129:5201, outdev-unknown"

2024-08-03 09:20:21 id=65308 trace_id=100077 func=__ip_session_run_tuple line=3442 msg="DNAT 20.20.20.1:5201->10.115.3.129:5201"

2024-08-03 09:20:21 id=65308 trace_id=100077 func=vf_ip_route_input_common line=2612 msg="find a route: flag=05000000 gw-10.115.3.129 via port1"

2024-08-03 09:20:21 id=65308 trace_id=100077 func=__iprope_tree_check line=528 msg="gnum-100004, use int hash, slot=81, len=2"

2024-08-03 09:20:21 id=65308 trace_id=100077 func=fw_forward_handler line=987 msg="Allowed by Policy-4: SNAT"

2024-08-03 09:20:21 id=65308 trace_id=100077 func=__ip_session_run_tuple line=3429 msg="SNAT 10.115.3.125->10.115.3.57:63261"

2024-08-03 09:20:21 id=65308 trace_id=100078 func=print_pkt_detail line=5879 msg="vd-root:0 received a packet(proto=6, 10.115.3.125:63261->20.20.20.1:5201) tun_id=0.0.0.0 from port1. flag [.], seq 2356297819, ack 2475148319, win 64512"

2024-08-03 09:20:21 id=65308 trace_id=100078 func=resolve_ip_tuple_fast line=5967 msg="Find an existing session, id-0000af31, original direction"

2024-08-03 09:20:21 id=65308 trace_id=100078 func=__ip_session_run_tuple line=3442 msg="DNAT 20.20.20.1:5201->10.115.3.129:5201"

2024-08-03 09:20:21 id=65308 trace_id=100078 func=npu_handle_session44 line=1224 msg="Trying to offloading session from port1 to port1, skb.npu_flag=00000400 ses.state=00000204 ses.npu_state=0x00000001"


Now, if there is any change in policy or routing table, this session is re-evaluated. In this case, however, it would not match to any policy and would drop with the error 'msg="SNAT mismatch policy 4 (old nat 1 ! = new nat 0), drop'. This happens because SNAT applied under NAT Hairpin was not being applied in a dirty session check. This causes an SNAT mismatch in strict-dirty-session-check and packets are dropped.

session info: proto=6 proto_state=01 duration=44 expire=3575 timeout=3600 refresh_dir=both flags=00000000 socktype=0 sockport=0 av_idx=0 use=5

origin-shaper=

reply-shaper=

per_ip_shaper=

class_id=0 ha_id=0 policy_dir=0 tunnel=/ vlan_cos=0/255

state=log dirty may_dirty f00

statistic(bytes/packets/allow_err): org=456/8/1 reply=256/6/1 tuples=4

tx speed(Bps/kbps): 0/0 rx speed(Bps/kbps): 0/0

orgin->sink: org pre->post, reply pre->post dev=9->9/9->9 gwy=10.115.3.129/10.115.3.125

hook=pre dir=org act=dnat 10.115.3.125:63261->20.20.20.1:5201(10.115.3.129:5201)

hook=post dir=org act=snat 10.115.3.125:63261->10.115.3.129:5201(10.115.3.57:63261)

hook=pre dir=reply act=dnat 10.115.3.129:5201->10.115.3.57:63261(10.115.3.125:63261)

hook=post dir=reply act=snat 10.115.3.129:5201->10.115.3.125:63261(20.20.20.1:5201)

pos/(before,after) 0/(0,0), 0/(0,0)

misc=0 policy_id=4 pol_uuid_idx=662 auth_info=0 chk_client_info=0 vd=0

serial=0000af31 tos=ff/ff app_list=0 app=0 url_cat=0

rpdb_link_id=00000000 ngfwid=n/a

npu_state=0x000001 no_offload

no_ofld_reason:  disabled-by-policy

2024-08-03 09:21:19 id=65308 trace_id=116176 func=resolve_ip_tuple_fast line=5967 msg="Find an existing session, id-0000af31, original direction"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=iprope_dnat_check line=5474 msg="in-[port1], out-[]"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=iprope_dnat_tree_check line=834 msg="len=1"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_dnat_policy line=5337 msg="checking gnum-100000 policy-1"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=get_new_addr line=1265 msg="find DNAT: IP-10.115.3.129, port-0(fixed port)"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_dnat_policy line=5429 msg="matched policy-1, act=accept, vip=1, flag=104, sflag=2000002"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=iprope_dnat_check line=5499 msg="result: skb_flags-02000002, vid-1, ret-matched, act-accept, flag-00000104"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__ip_session_run_tuple line=3442 msg="DNAT 20.20.20.1:5201->10.115.3.129:5201"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_fwd_check line=807 msg="in-[port1], out-[port1], skb_flags-020000c2, vid-1, app_id: 0, url_cat_id: 0"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_tree_check line=528 msg="gnum-100004, use int hash, slot=81, len=2"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_policy line=2131 msg="checked gnum-100004 policy-4, ret-matched, act-accept"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_user_identity_check line=1894 msg="ret-matched"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check line=2395 msg="gnum-4e21, check-ffffffffa002f0d0"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_policy line=2131 msg="checked gnum-4e21 policy-6, ret-no-match, act-accept"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_policy line=2131 msg="checked gnum-4e21 policy-6, ret-no-match, act-accept"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_policy line=2131 msg="checked gnum-4e21 policy-6, ret-no-match, act-accept"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check line=2412 msg="gnum-4e21 check result: ret-no-match, act-accept, flag-00000000, flag2-00000000"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_policy line=2365 msg="policy-4 is matched, act-accept"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_fwd_check line=844 msg="after iprope_captive_check(): is_captive-0, ret-matched, act-accept, idx-4"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=iprope_reverse_dnat_check line=1344 msg="in-[port1], out-[port1], skb_flags-020000c2, vid-1"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=iprope_reverse_dnat_tree_check line=926 msg="len=0"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=iprope_central_nat_check line=1367 msg="in-[port1], out-[port1], skb_flags-020000c2, vid-1"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_policy line=2131 msg="checked gnum-10000d policy-1, ret-no-match, act-accept"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_policy line=2131 msg="checked gnum-10000d policy-0, ret-matched, act-accept"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=__iprope_check_one_policy line=2365 msg="policy-0 is matched, act-accept"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=fw_snat_check line=679 msg="NAT disabled by central SNAT policy!"

2024-08-03 09:21:19 id=65308 trace_id=116176 func=fw_strict_dirty_session_check line=292 msg="SNAT mismatch policy 4 (old nat 1 ! = new nat 0), drop"

To resolve this issue, manually configure the Central NAT policy for this traffic so that when session re-evaluation happens, it will match this Central NAT policy to forward the traffic.

 

config firewall central-snat-map

    edit 0

        set srcintf "port1"

        set dstintf "port1"

        set orig-addr "all"

        set dst-addr "all"

    next

end

 

When the traffic is ingressing and egressing from the same interfaces, disable the automatic SNAT that would be performed. That can be achieved by using the following command:

 

config system setting

    set snat-hairpin-traffic disable

end

 

Related article: 

Technical Tip: How to disable source NAT to enable a hairpin policy or one-arm firewall