The Ultimate FortiOS CLI Reference for the NSE 4 Exam – Part 2: Session Table & Packet Flow
The session table and the packet-level diagnostic tools are where NSE 4 candidates most commonly lose marks. The session table output is dense — six or seven lines of flags, counters, and protocol state per entry — and the debug flow trace requires you to parse a structured log format quickly and identify where in the pipeline a packet was accepted, rewritten, or dropped. This module gives you the mental model for each.
Module 3: FortiOS Session Table Internals
diagnose sys session filter <options>
Command Syntax & Architectural Impact
diagnose sys session filter src <source-IP>
diagnose sys session filter dst <destination-IP>
diagnose sys session filter proto <protocol-number>
diagnose sys session filter dport <destination-port>
diagnose sys session filter sport <source-port>
diagnose sys session filter policy-id <id>
diagnose sys session filter vd <vdom-name>
diagnose sys session filter clear
diagnose sys session filter
The session table on a production FortiGate can hold hundreds of thousands of entries. diagnose sys session list without a filter on a busy unit will flood the console, take minutes to complete, and generate so much output that the useful entries scroll off the terminal buffer. The filter commands inject selection criteria into the session daemon’s in-kernel iterator — only sessions matching all active filter criteria are returned. Filters are persistent per CLI session until you clear them or the CLI session ends.
The filter operates against the origin-side of the session (pre-NAT source IP, post-policy destination). If you are trying to find sessions sourced from a NATted client whose translated address is 203.0.113.10, you must filter on the pre-NAT source (the private address) — the translated addresses are stored in the NAT block of the session, not the match keys.
Real-World Use Case Scenario
A finance application server at 10.10.50.100 on port 8443 is intermittently refusing connections. The server team insists the application is healthy and points the finger at the firewall. You need to verify: (a) whether existing sessions to that server exist in the session table, (b) what policy matched them, and (c) whether any sessions are in a half-open or stale state consuming resources. Without a filter, diagnose sys session list on this 200,000-session firewall would be unusable. You set a destination filter for the server’s IP and port 8443, then list only those sessions.
Live Output Breakdown
FortiGate-600F # diagnose sys session filter dst 10.10.50.100
FortiGate-600F # diagnose sys session filter dport 8443
FortiGate-600F # diagnose sys session filter
session filter info:
vd(any) proto(any) timeout(no)
src(any) sport(any) dst(10.10.50.100) dport(8443)
pol(any) dir(any) state(any)
Key Exam Indicators
| Field | What to look for |
|---|---|
Running diagnose sys session filter with no args | Always confirm your active filter before running list. The confirmation output shows exactly which criteria are active. Exam questions may ask what output diagnose sys session filter (no args) produces — it shows the current filter state, not a session list. |
vd(any) | Filter applies across all VDOMs. If you are on a specific VDOM and want only its sessions, explicitly set vd root or the VDOM name. |
| Filter persistence | Filters survive the current command but are cleared when the CLI session disconnects. If you exit the diagnose session and reconnect, filters are gone. |
diagnose sys session filter clear | Clears all active filters. Run this before any new filter sequence to avoid stale criteria from a prior investigation combining with your new ones. |
diagnose sys session list
Command Syntax & Architectural Impact
diagnose sys session list
This command reads directly from the kernel session table — the same in-memory hash table that the fast path (NP offload for offloaded sessions, or the kernel firewall module for software sessions) uses to make per-packet forwarding decisions. Each session entry is a fixed-size structure in kernel memory that encodes the 5-tuple match keys, protocol state machine position, NAT translations, policy ID, associated security profile handles, TTL countdown timer, and hardware offload status.
Understanding the session output structure is a core NSE 4 skill because the exam presents you with session output and asks questions like “what is the policy ID matching this traffic?” or “has NAT been applied?” or “why is this session in the CLOSE_WAIT state?”
Real-World Use Case Scenario
After the finance application filter is set (from the previous command), you list sessions to 10.10.50.100:8443. You find several sessions in CLOSE_WAIT state with high elapsed timers. CLOSE_WAIT means the server has sent a TCP FIN but the client has not acknowledged it — the session is half-closed. The session timer keeps the entry alive for the configured tcp-halfclose-timer value (default 120 seconds). Dozens of stale CLOSE_WAIT sessions explain the “connection refused” symptoms: the server is hitting its connection limit because socket descriptors are not being released, and the FortiGate is holding corresponding session table entries that prevent new connections from being accepted cleanly.
Live Output Breakdown
FortiGate-600F # diagnose sys session list
session info: proto=6 proto_state=05 duration=48 expire=120 timeout=3600
dev=17->18 gwy=10.10.50.1
hook=post dir=org act=noop
orig src: 10.10.1.55:52341 0.0.0.0:0
orig dst: 10.10.50.100:8443 0.0.0.0:0
reply src: 10.10.50.100:8443 0.0.0.0:0
reply dst: 10.10.1.55:52341 0.0.0.0:0
misc=0 policy_id=42 auth_info=0 chk_client_info=0 vd=1
serial=003f8210 tos=ff/ff ips_bypass=0
statistic(bytes/packets/allow_err): org=8192/64/1, reply=4096/32/1
orgin->sink: org pre->post, reply pre->post
ha_id=0 orig_node=0 reply_node=0 tunnel=/ helper=none
dst_mac=00:50:56:a1:22:33
misc: stateflags=00001000 use=2
Key Exam Indicators
| Field | What to look for |
|---|---|
proto=6 | IP protocol number: 6=TCP, 17=UDP, 1=ICMP, 47=GRE, 50=ESP. The exam tests your ability to identify protocol from protocol number. |
proto_state=05 | For TCP: 01=SYN_SENT, 02=SYN_RCVD, 03=ESTABLISHED, 04=FIN_WAIT, 05=CLOSE_WAIT, 06=TIME_WAIT. 05 here confirms the half-closed session scenario. |
policy_id=42 | The firewall policy ID that matched this session. Cross-reference with config firewall policy / edit 42 to verify the correct policy matched. If this shows 0, the session matched the implicit deny — which is unusual since denied sessions are not normally held in the table unless you have logging on denied. |
orig src: 10.10.1.55:52341 0.0.0.0:0 | The 0.0.0.0:0 after the IP:port pair is the translated address. In this case no NAT is applied (0.0.0.0 = no translation). If SNAT were active, the translated source IP would appear here: orig src: 10.10.1.55:52341 203.0.113.10:45231. |
reply src / reply dst | This is the reverse direction of the same session. For a TCP connection, reply src is the server’s perspective. If reply src shows a different IP than you expected, asymmetric routing or NAT is happening in the return path. |
statistic(bytes/packets/allow_err): org=8192/64/1, reply=4096/32/1 | org = traffic from client to server. reply = traffic from server back. If org packets are climbing but reply is stuck, the return path is broken. allow_err=1 means FortiOS allowed through one packet that had a TCP state error (e.g. an out-of-window segment). |
ips_bypass=0 | IPS inspection is active for this session. ips_bypass=1 would mean IPS was skipped — either the policy has no IPS profile, the device is in conserve mode, or an admin explicitly bypassed it. |
duration vs expire | duration counts up from session creation. expire counts down to deletion. When expire reaches 0, the kernel removes the entry. For established TCP, expire resets to timeout (3600 by default) on each packet. A session with high duration and low expire with no recent traffic is about to be reaped. |
diagnose sys session clear
Command Syntax & Architectural Impact
diagnose sys session clear
This command iterates the kernel session table and deletes every entry that matches the currently active session filter. If no filter is set, it clears all sessions on the unit — an irreversible, immediate action with zero confirmation prompt. The session table is flushed synchronously; within milliseconds, all existing traffic flows lose their state tracking entry. TCP connections that were offloaded to NP hardware are de-programmed from the NP session table as well.
The cascading effects: every client will experience a connection reset. TCP connections with their SYN and SYN-ACK already exchanged but session table entries now gone will have their next packet hit the firewall as an “unsolicited” SYN-ACK or ACK, which the stateful inspection engine will either DROP (if block-land-attack or strict mode is enabled) or attempt to re-create the session for (if permissive). Applications must re-establish from scratch.
When it is appropriate: (1) After a policy change that should affect only new sessions but old sessions are bypassing the change because they are offloaded to hardware. (2) After a failover where a new primary unit has no session state from the previously active master. (3) When a single source is flooding the session table with garbage sessions — set a filter for that source first, then clear only those entries.
When it is dangerous: On a unit carrying active VoIP, database transactions, or long-lived TCP connections (e.g. SSH control planes). Always scope your filter before clearing.
Real-World Use Case Scenario
You have just modified a firewall policy to add an IPS profile to traffic that was previously uninspected. The policy change applies only to new sessions. All existing long-lived connections (SSH tunnels, active database queries) continue to bypass IPS because their session table entries were created before the policy change and are NP-offloaded. Security compliance requires that the IPS profile be applied immediately, including to in-flight sessions. You filter for the specific source subnet and the specific policy ID, confirm the filter is correct, then clear only those sessions. Clients experience a brief reconnect but IPS is now active.
Live Output Breakdown
FortiGate-600F # diagnose sys session filter src 10.10.1.0/24
FortiGate-600F # diagnose sys session filter policy-id 42
FortiGate-600F # diagnose sys session filter
session filter info:
vd(any) proto(any) timeout(no)
src(10.10.1.0/24) sport(any) dst(any) dport(any)
pol(42) dir(any) state(any)
FortiGate-600F # diagnose sys session clear
(No output — the command executes silently.)
Key Exam Indicators
| Point | What to know |
|---|---|
| No confirmation prompt | This is the most dangerous aspect. The exam tests your awareness that clear is immediate and irreversible. You must set and confirm a filter first if you do not intend to clear the entire table. |
| Behaviour without a filter | With no active filter, diagnose sys session clear clears all sessions in the current VDOM. Exam questions may present this as a production scenario to test whether you’d confirm the filter first. |
| Impact on NP-offloaded sessions | Sessions offloaded to NP7/NP6 are de-programmed from the NP session table as well as the software table. Hardware offload state is not persistent across a software session clear. |
When to use vs. diagnose sys session delete <session-id> | For targeted single-session removal on a known session serial number, diagnose sys session delete is safer. clear should be reserved for bulk operations with a confirmed filter. |
Module 4: Real-Time Packet Sniffing & Flow Tracing
diagnose sniffer packet <interface> '<filter>' <verbosity>
Command Syntax & Architectural Impact
diagnose sniffer packet <interface> '<filter>' <verbosity> [<count>] [<timestamp>]
This command attaches a BPF (Berkeley Packet Filter) listener to the named interface at the kernel network stack level — specifically, it taps the tcpdump-compatible raw socket interface on the FortiOS Linux kernel. The key architectural constraint: the sniffer captures packets as they enter or leave the kernel interface, before the firewall policy engine processes them on ingress, and after NAT rewrites on egress. This means:
- Ingress: You see the original, pre-NAT, pre-inspection packet.
- Egress: You see the post-NAT, post-rewrite packet.
- If a packet is dropped by the firewall policy, it will still appear on the ingress sniffer — the sniffer capture point is before the drop. This is intentional and extremely useful: if you see the packet arrive but it never appears on the egress interface, the firewall (or routing) dropped it between ingress capture and egress.
The <interface> can be any to capture across all interfaces simultaneously.
Verbosity levels — exact behaviour:
| Level | What is captured |
|---|---|
1 | One line per packet: timestamp, source IP, destination IP, protocol. No payload. Lowest overhead. Use for high-rate traffic to confirm whether packets are arriving. |
2 | Level 1 + IP header details (TTL, flags, fragment offset). Use when diagnosing IP-layer issues (TTL, DF-bit, fragmentation). |
3 | Level 2 + protocol details. For TCP: flags (SYN/ACK/FIN/RST), sequence numbers, window size. For UDP: length. Use for TCP handshake diagnosis. |
4 | Level 3 + interface information (which interface this packet was seen on). Useful when using any to see which interface a packet traversed. |
5 | Level 4 + Ethernet frame header (source MAC, destination MAC, EtherType). Use when diagnosing ARP, VLAN tagging, or MAC-level issues. |
6 | Level 5 + raw hex/ASCII payload dump of the packet contents. Maximum detail. Use only on very low-traffic captures; generates enormous output on a busy link. |
Real-World Use Case Scenario
A client at 10.10.1.55 cannot reach the server at 10.10.50.100. You have checked the routing table — the route exists. You have checked the session table — no session for this flow. You need to determine: (a) is the client actually sending packets that arrive at the FortiGate’s ingress interface, and (b) if yes, is the FortiGate forwarding them out the expected egress interface? You run simultaneous sniffers on both the client-facing interface (port2) and the server-facing interface (port3) with verbosity 4 so you can see the interface name alongside each packet.
Live Output Breakdown
FortiGate-100F # diagnose sniffer packet any 'host 10.10.1.55 and host 10.10.50.100' 4
interfaces=[any]
filters=[host 10.10.1.55 and host 10.10.50.100]
2.820285 port2 in 10.10.1.55 -> 10.10.50.100: tcp 0 SYN seq 1042873299
2.820291 port3 out 10.10.1.55 -> 10.10.50.100: tcp 0 SYN seq 1042873299
2.820498 port3 in 10.10.50.100 -> 10.10.1.55: tcp 0 SYN-ACK seq 2918374412 ack 1042873300
2.820501 port2 out 10.10.50.100 -> 10.10.1.55: tcp 0 SYN-ACK seq 2918374412 ack 1042873300
-- Scenario with dropped packet --
2.820285 port2 in 10.10.1.55 -> 10.10.50.100: tcp 0 SYN seq 1042873299
[no further output]
Key Exam Indicators
| Field | What to look for |
|---|---|
in / out labels | in = arriving at FortiGate on that interface. out = leaving FortiGate on that interface. A packet appearing on port2 in but not on port3 out was dropped internally — either by firewall policy, routing lookup failure, or RPF check. |
| Same packet on ingress and egress | In verbosity 4+, a forwarded packet appears twice: once with port2 in and once with port3 out. This confirms the FortiGate is forwarding it. |
| SYN visible on ingress, no SYN-ACK visible on egress from server | The SYN reached the FortiGate and left toward the server (port3 out) but the server never replied. Problem is downstream of the FortiGate. |
SYN-ACK visible on port3 in but not on port2 out | The server replied, the reply reached the FortiGate, but the FortiGate did not forward it back to the client. Either the return-path policy denies it, or no matching session exists (asymmetric routing caused the outbound SYN to be processed by a different device). |
| Verbosity 6 hex dump | The exam does not typically test hex payload interpretation, but verbosity 6 is the correct answer when asked “which verbosity shows the full packet payload?” |
diagnose debug flow Chain
Command Syntax & Architectural Impact
diagnose debug flow filter addr <IP>
diagnose debug flow filter proto <protocol-number>
diagnose debug flow filter daddr <destination-IP>
diagnose debug flow show console enable
diagnose debug flow trace start <count>
diagnose debug enable
diagnose debug flow trace stop
diagnose debug disable
This command chain instruments the kernel software forwarding path — specifically the flow module that sits between the interface receive handler and the NP offload handoff. Every packet matching the filter is traced through each functional stage: routing lookup, reverse-path forwarding check, VDOM lookup, policy match, NAT execution, UTM/security engine handoff, and egress dispatch.
The critical distinction from the packet sniffer: debug flow shows you why a packet was accepted or dropped, not just whether it arrived. Each trace line corresponds to a specific kernel function invocation. If a packet is dropped at “iprope_in_check” it hit the firewall policy engine and was denied. If it’s dropped at “find_route” the routing table had no match. This is internal state — it does not appear in any log file unless you have debug logging enabled.
Architectural note: diagnose debug flow only traces the software path — packets that have been offloaded to NP hardware will not appear. To trace an offloaded session you must first either clear it (forcing software re-establishment) or run the sniffer instead.
Real-World Use Case Scenario
The sniffer confirms that packets from 10.10.1.55 to 10.10.50.100 arrive on port2 but never appear on port3. The packet sniffer tells you the packet is being dropped internally but cannot tell you where or why. You now use the debug flow chain to trace the exact kernel function that drops it. You filter for the source address, enable console output, start the trace, reproduce the traffic from the client, then immediately stop the trace and disable debug.
Live Output Breakdown — Successful Trace
FortiGate-100F # diagnose debug flow filter addr 10.10.1.55
FortiGate-100F # diagnose debug flow show console enable
FortiGate-100F # diagnose debug flow trace start 10
FortiGate-100F # diagnose debug enable
id=20085 trace_id=1 func=print_pkt_detail line=5894 msg="vd-root:0 received a packet(proto=6, 10.10.1.55:52341->10.10.50.100:443) tun_id=0.0.0.0 vlan_id=0 ret=2"
id=20085 trace_id=1 func=init_ip_session_common line=5989 msg="allocate a new session-00000001"
id=20085 trace_id=1 func=iprope_in_check line=183 msg="check policy match, len=60, x_proto=0, vd=0"
id=20085 trace_id=1 func=iprope_in_check line=208 msg="Allowed by Policy-42: SNAT"
id=20085 trace_id=1 func=ip_session_confirm_final line=418 msg="npu_state=0x0, proto=6"
id=20085 trace_id=1 func=__ip_session_run_tuple line=3006 msg="NAT: allocated port 52999, mapped 10.10.1.55->203.0.113.10"
id=20085 trace_id=1 func=resolve_ip_tuple_fast line=5675 msg="send to IF-port3 gw-10.10.50.1"
Live Output Breakdown — Dropped Packet Trace
id=20085 trace_id=2 func=print_pkt_detail line=5894 msg="vd-root:0 received a packet(proto=6, 10.10.1.55:52341->10.10.50.100:443) tun_id=0.0.0.0 vlan_id=0 ret=2"
id=20085 trace_id=2 func=init_ip_session_common line=5989 msg="allocate a new session-00000002"
id=20085 trace_id=2 func=iprope_in_check line=183 msg="check policy match, len=60, x_proto=0, vd=0"
id=20085 trace_id=2 func=iprope_in_check line=209 msg="Denied by Policy-0(deny)"
id=20085 trace_id=2 func=ip_session_handle_no_dst line=2048 msg="denied and free session"
Key Exam Indicators
| Line / Function | What it means |
|---|---|
func=print_pkt_detail … received a packet | The packet entered the software path. If you don’t see this line, the packet never reached the kernel (either dropped at the NIC, or the filter doesn’t match). |
func=init_ip_session_common … allocate a new session | A new session entry was created for this packet. First packet of a new flow always triggers this. |
func=iprope_in_check … check policy match | The packet has entered the firewall policy evaluation engine (iprope = IP Properties). This is where policy matching happens. |
Allowed by Policy-42: SNAT | Policy 42 matched and the action was allow with Source NAT applied. The policy number cross-references directly with show firewall policy. |
Denied by Policy-0(deny) | Policy-0 means the implicit deny at the bottom of the policy table matched — no explicit policy allowed this traffic. This is the most common exam scenario: explaining why traffic is being dropped. |
func=__ip_session_run_tuple … NAT: allocated port 52999, mapped 10.10.1.55->203.0.113.10 | SNAT execution. The private IP was translated to the public IP with port allocation. If NAT is expected but this line is absent, the NAT rule is missing or the policy uses fixed IP rather than dynamic PAT. |
func=resolve_ip_tuple_fast … send to IF-port3 gw-10.10.50.1 | Routing resolved: the packet will exit port3 toward gateway 10.10.50.1. If this line is absent but policy allowed the packet, routing failed after policy — either no route exists or RPF dropped it. |
func=ip_session_handle_no_dst … denied and free session | Session was created, evaluated, denied, and immediately torn down. This line is the definitive indicator that a policy denial is the cause of a drop. |
Stopping the trace correctly:
FortiGate-100F # diagnose debug flow trace stop
FortiGate-100F # diagnose debug disable
Forgetting diagnose debug disable leaves debug output streaming to the console indefinitely, which on a busy unit will generate enough console I/O to noticeably impact management plane performance. Always disable after use. This is an exam-tested operational practice.
Part of the NSE4 Study Series. For background on IPS and security profiles referenced in the session table, see Part 6: Security Profiles.