Skip to main content
RunBook Academy

OPNsenseXXXVII · Packet Flow MethodologyPacket flow overview

Packet flow overview — the journey of a packet through OPNsense

Intermediate⏱ ~14 mintcpdumppfctlnetstat

What you'll learn

  • Describe the full path of a packet through OPNsense
  • Identify the role of each subsystem in the path
  • Explain why the firewall operator must understand the whole path
  • Recognise the common points where a packet can be dropped

Prerequisites

Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14

Not yet marked complete on this device.

Every packet that traverses an OPNsense firewall follows a path. The packet enters on a NIC, is processed by the FreeBSD kernel, evaluated by PF, routed, NAT-translated if needed, and sent out on another NIC. The operator who knows the path can diagnose any part of it; the operator who only knows one subsystem sees only one part.

This lesson covers the end-to-end path, the role of each subsystem, the common drop points, and the discipline of tracing a packet through the whole flow.

The packet path

A packet that arrives on the LAN destined for the Internet traverses this path:

[Client] → [LAN switch] → [LAN NIC] → [Kernel: IP] → [PF in on LAN] → [Kernel: routing] → [nat on WAN] → [PF out on WAN] → [WAN NIC] → [ISP router] → [Internet]

A packet arriving on the WAN destined for a LAN host (port-forwarded) traverses:

[Internet] → [ISP router] → [WAN NIC] → [Kernel: IP] → [rdr on WAN: destination rewritten] → [PF in on WAN] → [Kernel: routing] → [PF out on LAN] → [LAN NIC] → [LAN switch] → [Host]

Note the two PF boxes on each path. PF registers with pfil(9) in both directions on every interface, so a forwarded packet is filtered twice: inbound on the interface it arrived on, then — after the routing decision has chosen an egress — outbound on the interface it leaves by. Note also where the translation boxes sit. Each is bound to one interface and fires on that interface’s hook, immediately before the filter rules there: rdr on the ingress interface, nat on the egress interface.

A packet destined to the firewall itself (e.g. SSH to the firewall’s IP) traverses:

[Client] → [LAN NIC] → [Kernel: IP] → [PF in on LAN] → [Kernel: local delivery] → [SSH daemon]

The path is similar but the destination is different: instead of being routed out and presented to a second hook, the packet is delivered to a local daemon, so only the inbound hook applies. The reply the daemon generates crosses the outbound hook on its way back out, where it matches the state the inbound pass created.

The subsystems

Each subsystem in the path has a specific role:

  • NIC driver. Receives the frame from the wire; raises an interrupt; the kernel’s ISR copies the frame into memory.
  • ARP / NDP. Resolves the next-hop IP to a MAC address. If the next hop is unknown, the kernel sends an ARP request and queues the packet.
  • IP layer. Validates the IP header (version, length, checksum); checks for fragmented or malformed packets; decrements TTL.
  • rdr (inbound NAT), if bound to this interface. Rewrites the destination before the filter rules on this hook see the packet.
  • PF (firewall), inbound pass. Evaluates the packet against the ruleset with direction in and the ingress interface. If a pass rule matches, the packet continues; if a block rule matches, it is dropped. PF’s own behaviour when no rule matches is to pass — the default deny on OPNsense is an explicit generated rule, not a property of PF.
  • Routing table. Decides the egress interface and next hop. netstat -rn shows the table.
  • nat (outbound NAT), if bound to the egress interface. Rewrites the source. It cannot run any earlier, because until routing has run there is no egress interface to bind to.
  • PF (firewall), outbound pass. Evaluates the same packet again with direction out and the egress interface, and — because translation precedes filtering on each hook — with whatever addresses the nat rule left it with. This is where a pass out or block out rule takes effect, and it is the reason PF can be made to match on the egress interface.
  • Egress NIC driver. Queues the packet for transmission; raises an interrupt; the NIC sends the frame.

The order matters, and it has two rules rather than one. Across the path: the inbound PF pass happens before routing, the outbound PF pass after it. Within each hook: translation first, filtering second — pf.conf(5) puts it as “Since translation occurs before filtering the filter engine will see packets as they look after any addresses and ports have been translated.” The operator who assumes the wrong order misdiagnoses.

The drop points

The packet can be dropped at several points:

Drop pointSymptomHow to diagnose
NIC driverPacket never enters the kernelnetstat -I <iface> -d — input drops climbing
ARP / NDPPacket queued waiting for ARP replyarp -an — no entry for next hop
IP layerMalformed packet, TTL zero, fragment issuesnetstat -s — IP errors
PF (inbound pass)Block rule matches on the ingress interfacePF log shows block in on the ingress interface
PF (outbound pass)Block rule matches on the egress interfacePF log shows block out on the egress interface; tcpdump sees ingress but not egress
RoutingNo route to destinationnetstat -rn — no entry; packet blackholed
NATNAT rule missing or wrongpfctl -s nat lists the active translation rules; captures on both interfaces show whether the address was rewritten
Egress NICPacket queued but not sentnetstat -I <egress> -d — output drops

Each drop point has a different signature. The operator must read the signature to know where to look.

Tracing a packet

The discipline for diagnosing “the packet isn’t getting through”:

  1. Capture on ingress. If the packet is not on the wire, the host never sent it (or the switch dropped it).
  2. Check the PF log. If PF logged a block, the rule is the problem; investigate the rule. Read the direction and interface in the log entry — block in on igb0 and block out on igb1 are different rules with different fixes.
  3. Capture on egress. If the packet is on ingress but not egress, PF dropped it on either pass (or the kernel routed it elsewhere).
  4. Check the routing table. If the egress capture shows the packet going out a different interface than expected, the routing table is the problem.
  5. Check NAT. If the source IP on the egress capture is unexpected, outbound NAT is misconfigured.
  6. Check the next hop. If the egress capture shows the packet but no return traffic, the next hop is unreachable.

The trap is to skip step 1. The operator who starts at step 2 (the firewall log) without verifying the packet arrived at the firewall wastes time chasing a phantom problem.

Read-only / Safenetstat -I interface drop counters
$ netstat -I igb0 -d
Name    Mtu Network       Address            Ipkts Ierrs    Idrops    Opkts Oerrs  Coll
igb0    1500 <Link#1>      aa:bb:cc:11:22:33 12345678 12  3456  12345678     0     0

Illustrative output

The two-direction packet flow

The packet flow is bidirectional. A TCP connection involves packets in both directions, and the firewall treats them differently:

  • Outbound SYN (LAN → WAN). Matches a LAN inbound rule, creates one state entry, is routed, passes the outbound hook on WAN, and NAT translates the source to the WAN IP.
  • Inbound SYN-ACK (WAN → LAN). Matches the state created by the outbound SYN on both hooks; no rule evaluation is needed in either direction, and the translation is undone from the same state entry.

One connection, one state entry, two hook crossings per packet.

The state table is the bridge: it lets PF skip rule evaluation for return packets. The operator who clears state mid-flow (with pfctl -k) forces every subsequent packet to be evaluated against the ruleset again. The operator who clears state on a port-forwarded flow breaks the flow until a new SYN arrives.

Local vs forwarded packets

OPNsense makes a distinction between packets destined to the firewall itself and packets to be forwarded. The path is the same until the routing decision; then the kernel either delivers the packet to a local socket (local) or forwards it out an egress interface (forwarded).

The implication for the operator:

  • Local traffic is on the firewall’s IP. Captures show the firewall’s IP as either source or destination. PF rules can apply, but routing does not.
  • Forwarded traffic has the firewall’s IP only in NAT translations. The original source and destination are different IPs.

The operator who sees a packet to the firewall’s IP must check whether it is local traffic (e.g. web UI access) or forwarded traffic (e.g. a port-forward to a LAN host). The capture destination IP tells the answer.

Summary

  • A packet traverses: NIC → BPF → rdr → PF in → routing → nat → PF out → egress NIC. Translation is bound to an interface and runs before the filter rules on that interface’s hook, in both directions.
  • PF hooks pfil in both directions on every interface, so a forwarded packet crosses two hooks: inbound on the ingress interface, outbound on the egress interface.
  • Crossing a hook is not the same as walking the ruleset. The inbound pass creates state; the outbound hook matches that state — floating by default, so the different interface does not matter — and passes the packet without evaluating rules. Policy therefore lives on the inbound hook of the interface traffic enters by.
  • During the inbound pass the egress interface has not been chosen yet; during the outbound pass it is the interface the rule is bound to.
  • Where the ruleset is walked outbound, the generated non-quick pass out is the last match for anything no rule claimed — the outbound default is pass, not deny.
  • Drop points: NIC, ARP, IP layer, PF, routing, NAT, egress NIC.
  • Tracing a packet: capture ingress, check PF log, capture egress, check routing, check NAT, check next hop.
  • State is the bridge between directions; clearing state mid-flow can cause apparent outages.
  • Local traffic has the firewall’s IP as destination; forwarded traffic has different source and destination.

Knowledge check · 4 questions

  1. Q1. A packet arrives on the LAN destined for an Internet host. Which subsystem decides the egress interface?

  2. Q2. A forwarded packet crosses two pfil hooks — in on the ingress interface, out on the egress interface — but on a default OPNsense configuration the ruleset is walked only on the first of them.

  3. Q3. A packet arrives on the LAN interface but never appears on the WAN. Which subsystems could be responsible? Select all that apply.

  4. Q4. You want to verify that a packet arrived on the firewall. Where do you capture first?

Passing score: 75%. Answers are checked in this browser.