Stateful packets being dropped by pf?

From: Polarian <polarian_at_polarian.dev>
Date: Thu, 24 Apr 2025 12:50:42 UTC
Good afternoon,

So some brief background on the issue, I have a vnet jail, one epair if
within the jail and the other on the host, and the setup is being routed
with pf. My host is typically "always on VPN" as it is my laptop and I
tunnel all packets home, but it also acts as wifi hardening at home too,
seen as I am using EOL APs which are not secure in the slightest.

When wireguard is disabled (and I have removed my pf rules to block all
non-vpn packets leaving wlan0), packets pass to and from the jail just fine,
the way I am testing this is by using pkg -j on the host to fetch the pkg
index from the FreeBSD servers, because weirdly enough ICMP passes just fine
with both wg and no-wg.

When wireguard is enabled, the tcp packet leaves via wg0 (NAT'd) hits the
router, which then it leaves via the WAN if, the WAN if then receives a
response, which is passed back via the wg if, but on my laptop (the host) it
is dropped by pf (verified by logging block all to pflog).

I have discussed this on IRC, at first I assumed I was making a stupid
mistake, but the problem seems to be more complex, hence I have brought it
to the ML.

From the debugging (see bottom of email) it appears to be a state issue, as
far as I am aware pf will never be called within the network stack if the
packet is stateful, it is passed immediately. So if this packet is stateful,
like it is with wlan0, it passes without being blocked by the default
"block all" rule.

Any ideas on what is wrong?

Thank you.

pf.conf:

# Interface macros
lbmk_if="lbmk0"
eth_if="em0"
wifi_if="wlan0"
wg_if="wg0"

# Network macros
#lbmk_net=$lbmk_if:network
lbmk_net="192.168.254.1/24"

# NAT traffic from lbmk jail
nat on $wifi_if from $lbmk_net to any -> ($wifi_if)
nat on $wg_if from $lbmk_net to any -> ($wg_if)

# Antispoof
antispoof quick for { $lbmk_if, $eth_if, $wifi_if, $wg_if }

# Block all incoming packets by default
block log all
# Ignore loopback traffic
set skip on lo

# Block all packets from lmbk jail to host
block quick from $lbmk_net to { ($lbmk_if), ($eth_if), ($wifi_if), ($wg_if) }

# Pass on traffic from lbmk, do not permit it to the host
pass from $lbmk_net to any
pass out on { $wifi_if, $wg_if }

netstat -rn:

Destination        Gateway            Flags         Netif Expire
0.0.0.0/1          link#5             US              wg0
default            192.168.2.1        UGS           wlan0
<public IP>        192.168.2.1        UGHS          wlan0
127.0.0.1          link#2             UH              lo0
128.0.0.0/1        link#5             US              wg0
192.168.2.0/24     link#3             U             wlan0
192.168.2.53       link#2             UHS             lo0
192.168.4.2        link#2             UH              lo0
192.168.254.0/24   link#6             U             lbmk0
192.168.254.1      link#2             UHS             lo0

<public IP> is the public IP, I would rather not include this on the ML,
although it is somewhat public already.

ifconfig:

em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
         options=4e524bb<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,JUMBO_MTU,VLAN_HWCSUM,LRO,WOL_MAGIC,VLAN_HWFILTER,VLAN_HWTSO,RXCSUM_IPV6,TXCSUM_IPV6,HWSTATS,MEXTPG>
         ether ba:c6:15:91:0f:09
         media: Ethernet autoselect
         status: no carrier
         nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
lo0: flags=1008049<UP,LOOPBACK,RUNNING,MULTICAST,LOWER_UP> metric 0 mtu 16384
         options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
         inet 127.0.0.1 netmask 0xff000000
         inet6 ::1 prefixlen 128
         inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
         groups: lo
         nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
wlan0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
         options=0
         ether <redacted>
         inet 192.168.2.53 netmask 0xffffff00 broadcast 192.168.2.255
         groups: wlan
         ssid... <redacted>
         parent interface: iwn0
         media: IEEE 802.11 Wireless Ethernet MCS mode 11ng
         status: associated
         nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
pflog0: flags=1000141<UP,RUNNING,PROMISC,LOWER_UP> metric 0 mtu 33152
         options=0
         groups: pflog
wg0: flags=10080c1<UP,RUNNING,NOARP,MULTICAST,LOWER_UP> metric 0 mtu 1420
         options=80000<LINKSTATE>
         inet 192.168.4.2 netmask 0xffffffff
         groups: wg
         nd6 options=109<PERFORMNUD,IFDISABLED,NO_DAD>
lbmk0: flags=1008843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,LOWER_UP> metric 0 mtu 1500
         options=8<VLAN_MTU>
         ether 02:9a:ab:73:39:0a
         inet 192.168.254.1 netmask 0xffffff00 broadcast 192.168.254.255
         groups: epair
         media: Ethernet 10Gbase-T (10Gbase-T <full-duplex>)
         status: active
         nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>

tcpdump -lnei pflog0

13:38:57.739879 rule 6/0(match): block in on wg0: 85.30.190.140.443 > 192.168.4.2.62185: Flags [.], seq 3871801137:3871802505, ack 28561907, win 1044, options [nop,nop,TS val 3930415865 ecr 416616883], length 1368
13:38:57.739894 rule 6/0(match): block in on wg0: 85.30.190.140 > 192.168.4.2: ip-proto-6
13:38:58.025984 rule 6/0(match): block in on wg0: 85.30.190.140.443 > 192.168.4.2.62185: Flags [.], seq 0:1368, ack 1, win 1044, options [nop,nop,TS val 3930416151 ecr 416616912], length 1368

pfctl -vvs rules

@0 block drop in quick on ! wg0 inet from 192.168.4.2 to any
   [ Evaluations: 31        Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@1 block drop in quick inet from 192.168.4.2 to any
   [ Evaluations: 27        Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@2 block drop in quick on ! lbmk0 inet from 192.168.254.0/24 to any
   [ Evaluations: 24        Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@3 block drop in quick inet from 192.168.254.1 to any
   [ Evaluations: 24        Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@4 block drop in quick on ! wlan0 inet from 192.168.2.0/24 to any
   [ Evaluations: 24        Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@5 block drop in quick inet from 192.168.2.53 to any
   [ Evaluations: 24        Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@6 block drop log all
   [ Evaluations: 31        Packets: 20        Bytes: 15120       States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: Thu Apr 24 13:39:41 2025 ]
@7 block drop quick inet from 192.168.254.0/24 to (lbmk0:1)
   [ Evaluations: 31        Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@8 block drop quick inet from 192.168.254.0/24 to (em0:*)
   [ Evaluations: 4         Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@9 block drop quick inet from 192.168.254.0/24 to (wlan0:1)
   [ Evaluations: 4         Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@10 block drop quick inet from 192.168.254.0/24 to (wg0:1)
   [ Evaluations: 4         Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@11 pass out on wlan0 all flags S/SA keep state
   [ Evaluations: 31        Packets: 0         Bytes: 0           States: 0     ]
   [ Inserted: uid 0 pid 75508 State Creations: 0     ]
   [ Last Active Time: N/A ]
@12 pass out on wg0 all flags S/SA keep state
   [ Evaluations: 31        Packets: 41        Bytes: 16855       States: 1     ]
   [ Inserted: uid 0 pid 75508 State Creations: 7     ]
   [ Last Active Time: Thu Apr 24 13:38:59 2025 ]
@13 pass inet from 192.168.254.0/24 to any flags S/SA keep state
   [ Evaluations: 31        Packets: 16        Bytes: 2635        States: 1     ]
   [ Inserted: uid 0 pid 75508 State Creations: 4     ]
   [ Last Active Time: Thu Apr 24 13:38:59 2025 ]

pfctl -vvs states

all tcp 85.30.190.140:443 <- 192.168.254.2:16542       FIN_WAIT_2:FIN_WAIT_2
    [1857673068 + 1291518208] wscale 6  [1348382370 + 327936] wscale 6
    age 00:00:45, expires in 00:01:15, 6:4 pkts, 873:1258 bytes, rule 13
    id: fd000a6800000000 creatorid: 4bf1244b
    origif: lbmk0
all tcp 192.168.4.2:65270 (192.168.254.2:16542) -> 85.30.190.140:443       FIN_WAIT_2:FIN_WAIT_2
    [1348382370 + 327936] wscale 6  [1857673068 + 1291518208] wscale 6
    age 00:00:45, expires in 00:01:15, 6:4 pkts, 873:1258 bytes, rule 12
    id: fe000a6800000000 creatorid: 4bf1244b
    origif: wg0
all tcp 85.30.190.140:443 <- 192.168.254.2:63806       ESTABLISHED:ESTABLISHED
    [3980477677 + 1325138176] wscale 6  [3870501212 + 327936] wscale 6
    age 00:00:15, expires in 23:59:45, 4:2 pkts, 745:1153 bytes, rule 13
    id: ff000a6800000000 creatorid: 4bf1244b
    origif: lbmk0

Polarian
Jabber/XMPP: polarian@icebound.dev