Skip to main content
New Member
September 10, 2026
Question

FortiGate-VM: local-in traffic from VTI tunnel silently dropped (fw_local_in_handler, policy 0) — everything else works

  • September 10, 2026
  • 5 replies
  • 112 views

 

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.1 run 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-policy entries) 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?

    5 replies

    funkylicious
    SuperUser
    SuperUser
    September 10, 2026

    is the debug flow capture from above where policy=0 being hit , captured on the destination ?

    i would try and disable net-device, flush/re-establish the tunnels and give it another go.


    L.E. would this be a correct diagram of your lab?

    make sure that firewall rules from the VTI zones to LAN and reverse on each one are also configured/present.

    also, make sure that ping is enabled on the interface of the FGT that you are trying to reach.

    "jack of all trades, master of none"
    New Member
    September 10, 2026

    THANK YOU!

    Firewall rule from VTI zones to LAN was missing! I only have LAN to VTI zone. 

    THANK YOU VERY MUCH! I can test failover! 

    New Member
    September 11, 2026

    Update — narrowed the issue further, sharing in case it helps identify the cause

    Since posting, I made progress on one part of this (a genuinely missing forward policy on the receiving firewall fixed firewall-to-firewall traffic through the tunnel), but LAN-to-LAN traffic through the same tunnel still fails, and I've now isolated the exact boundary far more precisely than in my original post.

    New, much more precise isolation

    Same topology as before (2x FortiGate-VM 8.0.0, permanent-trial/unlicensed, VTI + SD-WAN zone, both tunnels healthy, correct forward policies confirmed present on both ends, diagnose firewall iprope lookup confirms a matching accept policy).

    Test 1 — real LAN device: Ping 192.168.1.1 from a router behind the firewall (192.168.2.2, a MikroTik CHR) → 100% loss.

    Test 2 — same firewall, same destination, same policy/route, but sourced by the FortiGate itself using the LAN interface's own IP:

    execute ping-options source 192.168.2.1
    execute ping 192.168.1.1

    → 0% loss, clean round trip, every time.

    Both tests:

    • Same source subnet (192.168.2.0/24)
    • Same destination (192.168.1.1)
    • Same egress interface, same SD-WAN zone, same tunnel (VTI-A)
    • Same firewall policy match (confirmed via iprope lookup for both)
    • Same routing (BGP-learned route to 192.168.1.0/24, confirmed installed)

    The only variable that changes is whether the packet is genuinely forwarded (arrived on port4 from a separate device, then routed out via the tunnel) versus locally generated by the FortiGate itself (even when explicitly sourced with the LAN interface's own IP via ping-options source).

    Already ruled out (from extensive earlier testing, see original post)

    • src-check / RPF on the LAN interface (tested disabled, no change)
    • Encryption/cipher (GRE tunnel instead of IPsec VTI — identical failure)
    • Protocol (ICMP and TCP both fail identically)
    • Routing method (plain static route, sdwan-zone static route, and full eBGP all fail identically)
    • local-in-policy (tested with rules present, absent, disabled, reordered, scoped to different interfaces — no change; though this may be a red herring now since it doesn't explain the LAN-forwarded-vs-locally-generated split specifically)
    • NPU offload settings, session state, memory/CPU, conserve mode

    Question

    Given identical policy/route/interface/tunnel state, what would cause FortiOS to treat a genuinely forwarded packet differently from a locally-generated packet using the same source IP on the same interface, specifically for traffic egressing through a VTI/SD-WAN-zone member? Is there a forwarding-specific check (distinct from local-in, distinct from the regular policy match already confirmed) that only applies to traffic the firewall itself didn't generate — and could this be tied to the permanent-trial/unlicensed VM state specifically?

    funkylicious
    SuperUser
    SuperUser
    September 11, 2026

    for your test 1 - i would do some basic checks; confirm that on both sides when you do a packet sniffer you can see the traffic going out ( icmp-request ) and if a response is coming back ( icmp-reply ) ; if it reaches the FW on the dst side but no reply, then check the destination if it has any kind of restrictions for this

    without some outputs from the routing table/bgp config and some more outputs from debug flow to see how the traffic between the devices is treated, i would say that you are still missing some things.

    also, you might be wasting some time with FortiOS 8 and highly suggest using a previous and more stable version.

    "jack of all trades, master of none"
    New Member
    September 16, 2026

    Thank you! I’ll try to use a previous version and see if it will have a different outcome.

    Thought Leadership. Security Summit. Thursday, November 12th, PGA National Resort, Palm Beach Gardens, FL.
    Thought Leadership. Security Summit. Thursday, October 8th. Disney's Grand Californian Hotel & SPA, Anaheim, CA.