On Sun 01/11/2026 08:50, Pim van Pelt wrote: > >Synopsis: pf af-to states count both directions in packets[0]/bytes[0] > >Category: kernel > >Environment: > System : OpenBSD 8.0 > Details : OpenBSD 8.0 (GENERIC.MP) #84: Tue Oct 6 23:13:49 MDT 2026 > [email protected]:/usr/src/sys/arch/amd64/compile/GENERIC.MP > Architecture: OpenBSD.amd64 > Machine : amd64 > >Description: > For states created by an af-to rule, all traffic is counted > in the first of the two state counters. The second one > stays at zero, so pfctl -vvss shows "N:0 pkts, N:0 bytes". > > >How-To-Repeat: > Taking three VMM instances inside <-> router <-> outside > Outside is configured with IPv4-only > Inside is configured with IPv6-only > Router performs NAT64 between them. > > router# uname -a > OpenBSD router.example.com 8.0 GENERIC#82 amd64 > > router# cat /etc/hostname.vio0 > inet 10.0.1.1 255.255.255.0 > > router# cat /etc/hostname.vio1 > inet6 fec0::1 64 > > router# grep forwarding /etc/sysctl.conf > net.inet.ip.forwarding=1 > net.inet6.ip6.forwarding=1 > > router# pfctl -sr > block return all > pass all flags S/SA > pass in on vio1 inet6 from fec0::/64 to 64:ff9b::/96 flags S/SA af-to inet > from (vio0) round-robin > > router# pfctl -z > > inside# ping6 -c 5 64:ff9b::10.0.1.2 > PING 64:ff9b::10.0.1.2 (64:ff9b::a00:102): 56 data bytes > 64 bytes from 64:ff9b::a00:102: icmp_seq=0 hlim=63 time=27.565 ms > 64 bytes from 64:ff9b::a00:102: icmp_seq=1 hlim=63 time=49.198 ms > 64 bytes from 64:ff9b::a00:102: icmp_seq=2 hlim=63 time=44.837 ms > 64 bytes from 64:ff9b::a00:102: icmp_seq=3 hlim=63 time=56.343 ms > 64 bytes from 64:ff9b::a00:102: icmp_seq=4 hlim=63 time=57.229 ms > > --- 64:ff9b::10.0.1.2 ping statistics --- > 5 packets transmitted, 5 packets received, 0.0% packet loss > round-trip min/avg/max/std-dev = 27.565/47.034/57.229/10.762 ms > > router# pfctl -vsr > block return all > [ Evaluations: 1 Packets: 0 Bytes: 0 States: 0 ] > [ Inserted: uid 0 pid 70082 State Creations: 0 ] > pass all flags S/SA > [ Evaluations: 1 Packets: 0 Bytes: 0 States: 0 ] > [ Inserted: uid 0 pid 70082 State Creations: 0 ] > pass in on vio1 inet6 from fec0::/64 to 64:ff9b::/96 flags S/SA af-to inet > from (vio0) round-robin > [ Evaluations: 1 Packets: 10 Bytes: 940 States: 1 ] > [ Inserted: uid 0 pid 70082 State Creations: 1 ] > > router# pfctl -vvss > all ipv6-icmp 10.0.1.1:53742 (fec0::2[42513]) -> 10.0.1.2:8 > (64:ff9b::a00:102[53742]) 0:0 > age 00:00:05, expires in 00:00:09, 10:0 pkts, 940:0 bytes, rule 2 > id: 6ae66863000000e1 creatorid: 70d89c11 > > >Fix: > This reporter is not fluent in OpenBSD kernel, so he consulted a tool from > Anthropic. > Feel free to disregard its advice, pasted below: > Choose the counter by address family for af-to states, as > pf_icmp_state_lookup() > already does in sys/net/pf.c:6303-6317. Diff against -current: > --- sys/net/pf.c > +++ sys/net/pf.c > @@ -8278,7 +8278,15 @@ > sni->sn->packets[dirndx]++; > sni->sn->bytes[dirndx] += pd->tot_len; > } > - dirndx = (pd->dir == st->direction) ? 0 : 1; > + /* > + * With af-to both directions enter on the rule's interface > + * side, so pd->dir always equals st->direction; use the > + * address family to tell the directions apart instead. > + */ > + if (st->key[PF_SK_WIRE]->af != st->key[PF_SK_STACK]->af) > + dirndx = (pd->af == st->key[PF_SK_WIRE]->af) ? 0 : > 1; > + else > + dirndx = (pd->dir == st->direction) ? 0 : 1; > st->packets[dirndx]++; > st->bytes[dirndx] += pd->tot_len; > > pf_counters_inc() runs before pf_translate_af(), so pd->af > is still the family the packet arrived with. With this > change, pfctl shows the counts split by direction, and > pflow exports the second (reverse) record for af-to states > without any change to if_pflow.c. > > -- > Pim van Pelt > PBVP1-RIPE - https://ipng.ch/ >
Reading the code, isn't dirndx also used immediately afterward to update the counters for st->match_rules? Would this change therefore split an attached match rule's counters into, e.g., 5:5, while the pass rule still counts, e.g., 10:0? Since both flow directions are processed as PF_IN, and `pfctl -s labels` presents the rule counters as in/out, should the address-family-based index apply only to the state counters? Also, should the comment say both directions are processed as inbound, rather than entering on the same interface? In your example, don't requests enter on vio1 and replies on vio0?
