FortiGate-VM: local-in traffic from VTI tunnel silently dropped (fw_local_in_handler, policy 0) — everything else works
Â
Environment
- 2x FortiGate-VM64-KVM, v8.0.0, build0167 (GA.F)
- License Status: Invalid (permanent-trial / unlicensed mode — not a normal 15-day FortiCloud eval)
- Lab topology: two sites connected via two independent ISP paths, each carrying one IPsec VTI tunnel (VTI-A over path 1, VTI-B over path 2), both VTI interfaces as members of a single SD-WAN zone
gw-site-01 (192.168.1.2/24) — fw-site-01 (port4: 192.168.1.1/24) — [ISP1/ISP2] — fw-site-02 (port4: 192.168.2.1/24) — gw-site-02 (192.168.2.2/24)
VTI-A: 172.16.1.1 (fw-site-01) ↔ 172.16.1.2 (fw-site-02) VTI-B: 172.16.2.1 (fw-site-01) ↔ 172.16.2.2 (fw-site-02)
IKEv2, proposal des-sha512 (forced by the eval-mode low-encryption restriction), dhgrp 29, net-device enable. Both tunnels status=up with active SAs (diagnose vpn tunnel list), real traffic counters climbing.
Goal
Simple: execute ping 192.168.1.1 from fw-site-02, reaching fw-site-01's LAN-facing interface (port4) through the tunnel. Not even LAN-to-LAN — just firewall-to-firewall, locally-originated on both ends.
Symptom
100% packet loss, every time, regardless of which tunnel or routing method is used. diagnose debug flow consistently shows:
id=65308 trace_id=4 func=print_pkt_detail line=6405 msg="vd-root:0 received a packet(proto=1, 172.16.2.2:3->192.168.1.1:2048) len=84 tun_id=203.0.103.3 from VTI-B. type=8, code=0, id=3, seq=3."
id=65308 trace_id=4 func=ipsec_match_selector4 line=212 msg="src ip 172.16.2.2 match selector range 0.0.0.0-255.255.255.255"
id=65308 trace_id=4 func=init_ip_session_common line=6620 msg="allocate a new session-000002bf"
id=65308 trace_id=4 func=__vf_ip_route_input_rcu line=2116 msg="find a route: flag=80000000 gw-0.0.0.0 via root"
id=65308 trace_id=4 func=fw_local_in_handler line=629 msg="iprope_in_check() check failed on policy 0, drop"
Note the sequence: packet is received, IPsec selector match succeeds (so decryption/authentication of the ESP payload is confirmed successful — this is not an auth/crypto failure), a session is allocated, a route is found — and then it's dropped at fw_local_in_handler against "policy 0" (implicit deny), with no explanation of which check actually failed.
The one clean, repeatable distinguishing fact
execute ping 192.168.1.1run locally on fw-site-01 itself → works perfectly, 0% loss.- The exact same destination, pinged from fw-site-02 through the tunnel → 100% loss, every time, dropped at local-in.
Same destination IP, same "locally-originated on the firewall itself" traffic class either way — the only variable that changes is whether the packet arrives via a physical/normal path or via IPsec/VTI decapsulation.
Exhaustively ruled out (with evidence)
| Ruled out | How verified |
|---|---|
| Tunnel health / SA state | diagnose vpn tunnel list shows status=up, active SA, real packet counters on both VTI-A and VTI-B |
| IPsec authentication/decryption failure | Trace shows ipsec_match_selector4 succeeding — packet is fully decrypted and readable as plain IP before the drop |
| Regular firewall policy (not local-in) | diagnose firewall iprope lookup <src> 0 <dst> 0 1/ port4 returns matches policy id: 1 (a real accept policy) — but traffic never reaches this stage per the trace |
| Custom local-in-policy | Tried accept-all local-in-policy rules scoped to "any", "sdwan_zone", and "port2" individually, in multiple orderings, with the only other local-in-policy entry (a narrow Tor/malicious-source-only rule) fully set status disable — identical failure every time |
| Encryption/cipher (DES-eval-restriction theory) | Tested with a plain unencrypted GRE tunnel instead of IPsec/VTI, same topology, same policies — identical failure signature |
| Protocol-specific (ICMP-only block) | Tested TCP (execute ssh admin@192.168.1.1) — hangs identically to ICMP |
| Routing method | Tested static route (plain device), static route via sdwan-zone, and full eBGP (config router bgp, real neighbor adjacency established, PfxRcd confirmed, route installed as B in get router info routing-table all with correct [20/0] distance) — all three route the packet correctly, all three fail identically at the same local-in point |
| src-check / RPF | src-check disable already set on both VTI-A and VTI-B |
| Stale session/cache | diagnose sys session clear + full execute reboot on the firewall — no change |
| Global IPsec/asymroute settings | show system global | grep -i ipsec and show system settings | grep -i asymroute both return empty (default) |
| Memory/CPU/conserve mode | diagnose hardware sysinfo memory and get system performance status both healthy, no conserve mode |
| Interface count / policy count eval caps | Aware of and already hit the documented 3-policy cap separately (different symptom, clean CLI error) — this local-in drop produces no such error, it's silent |
What I haven't been able to check (need community/TAC insight)
- Whether built-in local-in policy groups (Fortinet docs reference "Admin-In" and "Implicit-In" groups that are evaluated as part of local-in alongside user-defined
config firewall local-in-policyentries) could be silently overriding a custom accept-all rule for IPsec-tunnel-sourced traffic specifically, and if so, how to inspect or modify them via CLI - Whether the unlicensed/permanent-trial mode on FortiGate-VM has an undocumented restriction specifically on local-in evaluation for traffic terminated from an IPsec tunnel (as distinct from the documented low-encryption and 3-item interface/policy/route caps, which are separate and already accounted for)
- Any way to get more detail out of
fw_local_in_handler's "policy 0" drop than the generic implicit-deny message — is there a more verbose debug flag specifically for local-in evaluation?
Question for the community
Given IPsec authentication succeeds, routing succeeds (via three independent methods), regular firewall policy would accept it (confirmed via iprope lookup), and no user-defined local-in-policy explains the drop even when the only other rule is disabled — what determines the outcome of fw_local_in_handler's policy 0 check for VTI-decapsulated traffic specifically, and is this behavior different (or restricted) in FortiOS 8.0.0's unlicensed/permanent-trial VM mode versus a normally licensed unit?
