A Day in the Life of a Packet on a 50G FortiGate, Part 4: Firewall Policy, NAT, and Security Profiles

A Day in the Life of a Packet on a 50G FortiGate, Part 4: Firewall Policy, NAT, and Security Profiles

Recap. Part 3 left the packet with a chosen egress interface and a next-hop. The session entry has a tentative egress=portX. What’s missing: permission to actually go there, any address translation that needs to happen, and any security inspection. That’s all of Part 4.

This is the part of the firewall everyone thinks is the whole firewall. It isn’t, but it is the part with the most operator-visible surface area, and it’s where most rule-related outages happen.

The iprope database — what “firewall policy” actually is

Every rule visible in the GUI under Policy & Objects, plus a number of internal tables you don’t see in the GUI, is stored in a kernel structure called iprope. Each iprope table has a numeric ID, and the kernel walks a specific sequence of tables for each packet.

The tables relevant to a forwarding packet, in lookup order:

IDPurpose
100002DNAT (VIPs, virtual servers) — consulted before policy match
100004Forward firewall policy — what shows up in show firewall policy
100003SNAT (central SNAT table) — consulted after policy match
100009Local-in policy — packets to the FortiGate
100010Local-out policy — packets from the FortiGate
100007Implicit deny logging
100015+Various auth, traffic shaping, and helper-policy chains

To dump:

diagnose firewall iprope list 0x100004
diagnose firewall iprope show 0x100004 <policy-id>
diagnose firewall iprope show 0x100004

(Note: 100004 and 0x100004 differ — FortiOS understands both forms in different commands. Stick with the hex form when in doubt.)

To resolve “which policy will this flow match?” without sending traffic, you can query the policy lookup engine directly:

diagnose firewall iprope lookup <src-ip> <src-port> <dst-ip> <dst-port> <proto> <ingress-intf> <vdom>

For example:

diagnose firewall iprope lookup 10.10.10.50 54321 13.107.6.152 443 6 port3 root

Returns the matching policy ID, or “no match.” This is the closest FortiOS gets to a packet-tracer style command, and it’s surprisingly useful for testing rule changes pre-deployment.

DNAT — virtual IPs first

Before policy match, FortiOS evaluates VIPs. A VIP (virtual IP) is FortiOS’s name for a destination NAT rule. Its presence in 100002 means: if the destination address of the packet matches a VIP’s external IP (and port, if defined), the destination address (and port) is rewritten to the VIP’s mapped address (and port) before policy match runs. That has a consequence many operators miss: the firewall policy must be written using the internal/mapped destination, not the public/external destination, because by the time policy match happens the destination has already been translated.

config firewall vip
    edit "vip-web"
        set extip 203.0.113.10
        set extintf "any"
        set mappedip "10.20.20.10"
        set portforward enable
        set extport 443
        set mappedport 443
        set protocol tcp
    next
end

config firewall policy
    edit 100
        set name "to-web"
        set srcintf "port4"
        set dstintf "port6"
        set srcaddr "all"
        set dstaddr "vip-web"        # <- the VIP object as dstaddr
        set service "HTTPS"
        set action accept
    next
end

The DNAT happens; the policy then matches because dstaddr is the VIP object, which the policy engine resolves to the external IP for matching purposes (yes, FortiOS does the right thing; the matching is on external IP even though the rewritten destination is internal). This is a frequent confusion: you can match on the VIP object name as your destination, and the policy will fire correctly.

To inspect VIPs:

diagnose firewall vip list
diagnose firewall vip realservers list
get firewall vip

For load-balanced VIPs (virtual servers):

diagnose firewall vip realservers list
diagnose firewall vip realservers stats

The forward policy match

Policy match happens against iprope table 100004. The match criteria are, in order of stringency:

  • VDOM (implicit — each VDOM has its own policy table)
  • srcintf — ingress interface(s) or zone
  • dstintf — egress interface(s) or zone (the one we computed in Part 3)
  • srcaddr — address objects (subnet, FQDN, address group, geo, ISDB)
  • dstaddr — address objects (subnet, FQDN, VIP, address group, geo, ISDB)
  • service — service or service group (TCP/UDP port ranges, protocol numbers, named services)
  • schedule — recurring or one-off
  • groups / users — for FSSO/SSO authenticated flows
  • internet-service-name / internet-service-id — destination as ISDB entry

Match logic is first match wins. There is no “best match” or longest-prefix match. The order of policies in the policy list is everything.

The policy table has an implicit deny at the end. Unmatched traffic is dropped and (if logging is on) appears as policy ID 0 in logs.

To watch a match:

diagnose debug flow filter saddr 10.10.10.50
diagnose debug flow filter daddr 13.107.6.152
diagnose debug flow filter dport 443
diagnose debug flow show iprope enable
diagnose debug flow show function-name enable
diagnose debug enable
diagnose debug flow trace start 50

You’ll see lines like:

trace_id=1 ... msg="iprope_in_check() check failed on policy 0, drop"        # implicit deny
trace_id=1 ... msg="Allowed by Policy-12: SNAT"                              # success, policy ID 12, doing SNAT
trace_id=1 ... msg="Denied by forward policy check (policy 12)"              # match but action=deny

diagnose firewall packet-loss and the per-policy hit counter:

diagnose firewall iprope-count show

show how many packets each policy is dispatching. A policy with zero hit count for weeks is either dead or sitting below another policy that’s eating its traffic.

Policy NAT vs central NAT

FortiOS has two NAT models, and they’re mutually exclusive per VDOM:

Policy NAT (default)

Each forward policy has its own nat enable toggle, optional ippool enable and poolname, and optional fixedport. The NAT decision is “baked into” the policy. Simple, explicit, and works well for small rule sets.

config firewall policy
    edit 12
        set nat enable
        set ippool enable
        set poolname "wan-pool"
    next
end

Central NAT

A separate top-level table evaluates NAT independent of policy. The forward policy still controls whether the packet is forwarded; the central SNAT table controls what the source becomes.

config system settings
    set central-nat enable
end

config firewall central-snat-map
    edit 1
        set srcintf "port3"
        set dstintf "port4"
        set orig-addr "internal-net"
        set dst-addr "all"
        set nat-ippool "wan-pool"
        set protocol 6
    next
end

Central NAT scales better for environments with many overlapping NAT decisions, multi-WAN scenarios, and scripted automation. It’s also what FortiManager prefers when normalising NAT across many devices.

To inspect:

diagnose firewall central-snat list
diagnose firewall ippool list
diagnose firewall ippool list <pool-name>
get firewall central-snat-map

Central NAT and policy NAT are not both active at once on the same VDOM. If you flip central-nat enable, all policy-level NAT toggles are ignored and central NAT takes over. If you’ve ever enabled it on a busy box without realising, you’ll have an interesting hour.

IP pools

IP pools control what source address a SNAT’d flow uses. Types:

  • overload — PAT, many-to-one, default
  • one-to-one — strict 1:1 mapping, no port translation
  • fixed-port-range — preserves source port within a contiguous range
  • port-block-allocation — gives each internal IP a block of source ports on the external IP, useful for CGNAT-style scenarios and for logging reconstructibility
config firewall ippool
    edit "wan-pool"
        set type overload
        set startip 203.0.113.20
        set endip 203.0.113.29
    next
end

port-block-allocation is the right answer for any environment where you ever need to map a session log line back to an internal client. Without it, after PAT, you have only a (public IP, port, timestamp) tuple — and on a busy box that maps back to many possible internal IPs.

Authentication

If the matched policy has groups or users set, the flow is held until authentication completes. Mechanisms include:

  • FSSO — agent-based and agentless (DC poller); maps user identity to source IP transparently.
  • Captive portal — HTTP redirect to a FortiOS-hosted login page.
  • Kerberos / SAML — for SSO with web traffic.
  • RADIUS / TACACS+ for admin access (covered in another series on this blog).
diagnose debug authd fsso list
diagnose debug authd fsso server-status
diagnose firewall auth list
diagnose firewall auth filter

Authenticated sessions get a user_info field on the session entry. The session is otherwise normal.

Security profiles — the UTM dispatch

If the matched policy has any of av-profile, webfilter-profile, ips-sensor, application-list, ssl-ssh-profile, dnsfilter-profile, file-filter-profile, dlp-sensor, emailfilter-profile configured, the packet is now subject to inspection. Two pipelines:

Flow-based inspection

The default for IPS, application control, web filter, DNS filter, antivirus (in flow mode), and SSL/SSH inspection in deep mode. The packet is examined as it flows; payload is reassembled in memory, signatures are matched (often via CP9 IPSA hardware acceleration), verdict is rendered, packet is forwarded or dropped.

Flow-based UTM is NPU-compatible via NTurbo (covered in Part 1). A session with flow-based UTM can still be hardware-accelerated — the NP7 hands packets to the IPS engine, the IPS engine to the CP9, and the verdict comes back fast enough that throughput stays high.

To inspect:

diagnose test application ipsmonitor 5     # IPS engine status
diagnose test application ipsmonitor 13    # NTurbo
diagnose ips ssl conn
diagnose ips anomaly list
diagnose webfilter fortiguard statistics
diagnose dns query
diagnose application list

Proxy-based inspection

Web filter, antivirus, email filter, and DLP can run in proxy mode. The kernel transparently inserts a proxy (the WAD daemon) between client and server, terminating the connection at the FortiGate, inspecting fully reconstructed objects (entire HTTP responses, entire emails), and then re-originating to the destination.

Proxy-based UTM is not NPU-offloadable. The session shows no_ofld_reason: redir-to-ips or redir-to-av or proxy. This is a deliberate trade: you get deeper inspection at the cost of CPU and concurrency. WAD is single-process-pool-per-CPU, and on busy boxes WAD CPU is the first thing to climb.

diagnose test application wad 1000
diagnose test application wad 2000     # session count
diagnose wad worker stat
diagnose wad debug enable category webfilter

SSL/SSH inspection

Without SSL inspection, “deep packet inspection” of HTTPS traffic is severely limited — you see the SNI, the server cert, and the size and timing of TLS records, and that’s about it. With SSL inspection (deep), the FortiGate is doing transparent TLS interception:

  1. Client opens TLS to server.
  2. FortiGate intercepts; presents its own cert (signed by the FortiGate’s CA) as the server.
  3. FortiGate opens its own TLS to the real server, validates the real cert, fetches the response.
  4. FortiGate re-encrypts to the client using its own cert.
  5. In between (1) and (4), the cleartext content is available for AV / IPS / web filter / DLP / file filter.

The crypto is offloaded to the CP9 on platforms that have one. On a 1800F-class box, deep SSL inspection is real-time — but at scale it’s CP9-bound, and you can run out of CP9 horsepower before you run out of NP7 throughput.

diagnose firewall ssl-cert-cache list
diagnose ips ssl conn
diagnose test application sslvpnd ...    # different feature, but adjacent

The trickiest part of SSL inspection is certificate trust. Clients must trust the FortiGate’s CA, and certain destinations (banks, app stores, cert-pinned APIs, Apple Push Notification, etc.) must be exempt because they pin certificates and will reject the FortiGate’s reissued cert. The ssl-exempt list and the ssl-server per-server settings are how you carve those exemptions:

config firewall ssl-ssh-profile
    edit "deep-inspection"
        config ssl
            set inspect-all deep-inspection
        end
        config ssl-exempt
            edit 1
                set type fortiguard-category
                set fortiguard-category 31      # banks, finance
            next
            edit 2
                set type address
                set address "exempt-domains"
            next
        end
    next
end

To audit which categories and addresses are exempt:

diagnose test application urlfilter 5
diagnose firewall ssl-exempt list

NAT happens after policy match

Order of operations (slow path):

  1. DNAT (already happened, before policy match)
  2. Policy match
  3. UTM dispatch
  4. SNAT (policy NAT or central NAT)
  5. Session install
  6. NPU offload eligibility evaluation

The session entry’s hook=post dir=org act=snat line shows the SNAT decision. The reverse direction is automatic — FortiOS rewrites the destination of return packets to the original source.

For VIPs (DNAT), the symmetrical reverse is also automatic. You don’t need a “policy in the other direction” — the session table handles it.

Putting it together — the canonical example

Internal client 10.10.10.50:54321 → public web server via SaaS:

  1. Session miss in NP7; punt to kernel.
  2. RPF passes (route to 10.10.10.50/24 via port3 ✓).
  3. DNAT lookup (100002): no VIP matches 13.107.6.152:443. No DNAT.
  4. Policy route lookup: nothing.
  5. SD-WAN service rule lookup: rule 2 matches (M365). Member port5 picked (Part 3).
  6. FIB resolve: best route to 13.107.6.152 via port5, gateway 198.51.100.1. ✓
  7. Forward policy match (100004): srcintf=port3, dstintf=port5, srcaddr=internal-net, dstaddr=all, service=HTTPS. Policy 12 matches. Action accept, NAT enabled, ippool=wan-pool.
  8. UTM dispatch: policy 12 has IPS sensor default and SSL/SSH profile deep-inspection. Flow-based IPS via NTurbo; SSL deep inspection via WAD/CP9. Session marked redir-to-ips.
  9. SNAT decision: ippool wan-pool is overload type. Pick external IP 203.0.113.20, ephemeral port 47331.
  10. Session install: state=ESTABLISHED-pending-SYNACK, original=10.10.10.50:54321→13.107.6.152:443, translated=203.0.113.20:47331→13.107.6.152:443, ingress=port3, egress=port5, policy=12.
  11. NPU offload: not fully offloaded (UTM redirected to IPS); NTurbo-eligible.
  12. Egress: ARP for 198.51.100.1 (already cached), L2 rewrite, transmit.

Every one of those steps is visible in diag debug flow show iprope enable; diag debug flow show function-name enable; diag debug flow trace start 50. If something is failing, the trace will tell you which step.

Diagnostics summary for policy / NAT / UTM

# Which policy will match this flow?
diagnose firewall iprope lookup <src> <sport> <dst> <dport> <proto> <iif> <vdom>

# Inspect the forward table
diagnose firewall iprope list 0x100004
diagnose firewall iprope show 0x100004 <id>
diagnose firewall iprope-count show

# VIP / DNAT
diagnose firewall vip list
diagnose firewall vip realservers list
diagnose firewall vip realservers stats

# Central / policy NAT
diagnose firewall central-snat list
diagnose firewall ippool list

# Authentication
diagnose firewall auth list
diagnose debug authd fsso list

# IPS
diagnose test application ipsmonitor 5
diagnose test application ipsmonitor 13
diagnose ips anomaly list
diagnose ips ssl conn

# Proxy / WAD
diagnose test application wad 1000
diagnose wad worker stat

# SSL inspection
diagnose firewall ssl-cert-cache list
diagnose firewall ssl-exempt list

# Internet Service / app classification
diagnose internet-service match root <ip> <port> <proto>
diagnose application list

Where we are

The packet is allowed, translated, dispatched to UTM, and the session entry is fully populated. The remaining decision is whether this session is going to be pushed down to the NP7 for hardware forwarding (express lane for the rest of its life) or stay on the slow path — and then how the packet actually leaves the box.

That’s Part 5: egress, NPU offload re-evaluation, IPSec encap, ARP, transmit, and a complete troubleshooting cookbook of every command in this series in one place.