| The client (10.10.10.10) initiates the traffic. FortiGate allows the traffic and also receives the reply from the server, however the FortiGate does not forward the reply to the client. Sniffer shows the behavior: 2025-04-20 14:34:09.461964 VLAN 321 in 10.10.10.10 -> 8.8.4.4: icmp: echo request 2025-04-20 14:34:09.461993 port1 out 12.106.165.50 -> 8.8.4.4: icmp: echo request 2025-04-20 14:34:09.469598 port1 in 8.8.4.4 -> 12.106.165.50: icmp: echo reply 2025-04-20 14:34:14.162414 VLAN 321 in 10.10.10.10 -> 8.8.4.4: icmp: echo request 2025-04-20 14:34:14.162426 port1 out 12.106.165.50 -> 8.8.4.4: icmp: echo request 2025-04-20 14:34:14.169869 port1 in 8.8.4.4 -> 12.106.165.50: icmp: echo reply This is because the FortiGate has an IP Pool that includes the client IP address, with arp-reply enabled. config firewall ippool edit "NAT" set type overload set startip 10.10.10.2 set endip 10.10.10.10 set arp-reply enable set arp-intf '' set associated-interface '' set comments '' set nat64 disable next end Note: By default, arp-reply is enabled. When the arp-reply is enabled, FortiGate considers the IP included in that IP Pool as a local address: Technical Tip: ARP reply setting in Virtual IP/IP Pool For this reason, the reply will be forwarded to the root, and as a result, the client will not receive the reply from the server. The below debug flow shows the behavior. Traffic initiated by the client and processed by FortiGate: 2025-04-20 14:39:45 id=20085 trace_id=1000 func=print_pkt_detail line=5953 msg="vd-root:0 received a packet(proto=1, 10.10.10.10:1->8.8.4.4:2048) tun_id=0.0.0.0 from VLAN 321. type=8, code=0, id=1, seq=53." 2025-04-20 14:39:45 id=20085 trace_id=1000 func=init_ip_session_common line=6133 msg="allocate a new session-000045fe, tun_id=0.0.0.0" 2025-04-20 14:39:45 id=20085 trace_id=1000 func=rpdb_srv_match_input line=1031 msg="Match policy routing id=2130771969: to 8.8.4.4 via ifindex-5" 2025-04-20 14:39:45 id=20085 trace_id=1000 func=__vf_ip_route_input_rcu line=1999 msg="find a route: flag=00000000 gw-12.106.165.49 via port1" 2025-04-20 14:39:45 id=20085 trace_id=1000 func=get_new_addr line=1227 msg="find SNAT: IP-12.106.165.50(from IPPOOL), port-60417" 2025-04-20 14:39:45 id=20085 trace_id=1000 func=fw_forward_handler line=888 msg="Allowed by Policy-24: SNAT" 2025-04-20 14:39:45 id=20085 trace_id=1000 func=ip_session_confirm_final line=3187 msg="npu_state=0x4000101, hook=4" 2025-04-20 14:39:45 id=20085 trace_id=1000 func=__ip_session_run_tuple line=3546 msg="SNAT 10.10.10.10->12.106.165.50:60417" Note: Double SNAT due to the client IP being included in the IP Pool. The reply from the server is received by the FortiGate and forwarded to the root instead of the client. 2025-04-20 14:39:45 id=20085 trace_id=1001 func=print_pkt_detail line=5953 msg="vd-root:0 received a packet(proto=1, 8.8.4.4:60417->12.106.165.50:0) tun_id=0.0.0.0 from port1. type=0, code=0, id=60417, seq=53." 2025-04-20 14:39:45 id=20085 trace_id=1001 func=resolve_ip_tuple_fast line=6039 msg="Find an existing session, id-000045fe, reply direction" 2025-04-20 14:39:45 id=20085 trace_id=1001 func=__ip_session_run_tuple line=3559 msg="DNAT 12.106.165.50:0->10.10.10.10:1" 2025-04-20 14:39:45 id=20085 trace_id=1001 func=__vf_ip_route_input_rcu line=1999 msg="find a route: flag=80000000 gw-10.10.10.10 via root" The scenario below shows when the arp-reply is disabled, and the traffic is sent directly to the client: config firewall ippool edit "NAT" set startip 10.10.10.2 set endip 10.10.10.10 set arp-reply disable next end Sniffer: 2025-04-20 15:17:05.733839 VLAN 321 in 10.10.10.10 -> 8.8.4.4: icmp: echo request 2025-04-20 15:17:05.733877 port1 out 12.106.165.50 -> 8.8.4.4: icmp: echo request 2025-04-20 15:17:05.741296 port1 in 8.8.4.4 -> 12.106.165.50: icmp: echo reply 2025-04-20 15:17:05.741316 VLAN 321 out 8.8.4.4 -> 10.10.10.10: icmp: echo reply Debug flow: 2025-04-20 14:40:16 id=20085 trace_id=1008 func=print_pkt_detail line=5953 msg="vd-root:0 received a packet(proto=1, 10.10.10.10:1->8.8.4.4:2048) tun_id=0.0.0.0 from VLAN 321. type=8, code=0, id=1, seq=57." 2025-04-20 14:40:16 id=20085 trace_id=1008 func=resolve_ip_tuple_fast line=6039 msg="Find an existing session, id-000045fe, original direction" 2025-04-20 14:40:16 id=20085 trace_id=1008 func=rpdb_srv_match_input line=1031 msg="Match policy routing id=2130771969: to 8.8.4.4 via ifindex-5" 2025-04-20 14:40:16 id=20085 trace_id=1008 func=__vf_ip_route_input_rcu line=1999 msg="find a route: flag=00000000 gw-12.106.165.49 via port1" 2025-04-20 14:40:16 id=20085 trace_id=1008 func=get_new_addr line=1227 msg="find SNAT: IP-12.106.165.50(from IPPOOL), port-60423" 2025-04-20 14:40:16 id=20085 trace_id=1008 func=__ip_session_run_tuple line=3546 msg="SNAT 10.10.10.10->12.106.165.50:60417" Reply from server: 2025-04-20 14:40:16 id=20085 trace_id=1009 func=print_pkt_detail line=5953 msg="vd-root:0 received a packet(proto=1, 8.8.4.4:60417->12.106.165.50:0) tun_id=0.0.0.0 from port1. type=0, code=0, id=60417, seq=57." 2025-04-20 14:40:16 id=20085 trace_id=1009 func=resolve_ip_tuple_fast line=6039 msg="Find an existing session, id-000045fe, reply direction" 2025-04-20 14:40:16 id=20085 trace_id=1009 func=__ip_session_run_tuple line=3559 msg="DNAT 12.106.165.50:0->10.10.10.10:1" 2025-04-20 14:40:16 id=20085 trace_id=1009 func=__vf_ip_route_input_rcu line=1999 msg="find a route: flag=00000000 gw-0.0.0.0 via VLAN 321" 2025-04-20 14:40:16 id=20085 trace_id=1009 func=npu_handle_session44 line=1246 msg="Trying to offloading session from port1 to VLAN 321, skb.npu_flag=00000000 ses.state=00000204 ses.npu_state=0x04000101" 2025-04-20 14:40:16 id=20085 trace_id=1009 func=fw_forward_dirty_handler line=412 msg="state=00000204, state2=00000001, npu_state=04000101" The solution is to remove the client IP from the IP Pool if it is not needed; otherwise, disable the arp-reply for the IP Pool. Related article: Technical Tip: IP pool and virtual IP behavior changes in FortiOS 6.4, 7.0, 7.2, and 7.4 |