Commit 0493260115db ("pf: store state creation/expiration timestamps with millisecond precision") replaced time_uptime reads with microuptime() calls via pf_get_uptime() in pf's per-packet state expire update path. On systems using HPET (common in KVM guests without ITSC), this is extremely expensive. https://github.com/freebsd/freebsd-src/commit/0493260115db10164246762d6d4923880575e529 Measurements (FreeBSD/KVM guest, iperf3, pf enabled with stateful rules): vtnet, HPET: ~2.3 Gbit/s vtnet, TSC-low: ~9 Gbit/s ixl passthrough + VLANs, HPET: <1 Gbit/s ixl passthrough + VLANs, TSC-low: ~9 Gbit/s pf disabled, any timecounter: ~9 Gbit/s kern.timecounter.choice: TSC-low(-100) i8254(0) ACPI-fast(900) HPET(950) dummy(-1000000) TSC-low is distrusted because the hypervisor doesn't expose ITSC to the guest, which is common in many KVM configurations. How to reproduce: In a VM with pf enabled and stateful rules: sysctl kern.timecounter.hardware=HPET iperf3 -c <target> # observe reduced throughput sysctl kern.timecounter.hardware=TSC-low iperf3 -c <target> # throughput restored Possible approaches: The millisecond precision is primarily needed by pflow(4) at export time, not per packet. Defaulting pf_get_uptime() to (uint64_t)time_uptime * 1000 would preserve the data model changes while eliminating the per-packet timecounter read. The precise microuptime() path could be gated behind a sysctl for pflow users who need it. Alternatively, the patch could use getmicrouptime() instead of microuptime(). The former returns a cached value without querying the hardware timecounter, which is what the man page recommends when execution time matters more than precision.
A commit in branch main references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=e4130ac13d2fe9c17b8f8e86d39a7b5223fe7b79 commit e4130ac13d2fe9c17b8f8e86d39a7b5223fe7b79 Author: Kristof Provost <kp@FreeBSD.org> AuthorDate: 2026-05-21 11:31:11 +0000 Commit: Kristof Provost <kp@FreeBSD.org> CommitDate: 2026-05-21 21:19:50 +0000 pf: switch to getmicro(up)time() It is reported that micro(up)time() performs poorly in certain virtualisation scenarios. Absolute accuracy isn't required here, so switch to the slightly less accurate (as per the man page) get-variants. PR: 295043 MFC after: 3 weeks Sponsored by: Rubicon Communications, LLC ("Netgate") sys/net/pfvar.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
A commit in branch stable/15 references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=a9ebef44a473b1262abad53a22855317c9406948 commit a9ebef44a473b1262abad53a22855317c9406948 Author: Kristof Provost <kp@FreeBSD.org> AuthorDate: 2026-05-21 11:31:11 +0000 Commit: Kristof Provost <kp@FreeBSD.org> CommitDate: 2026-06-11 20:21:57 +0000 pf: switch to getmicro(up)time() It is reported that micro(up)time() performs poorly in certain virtualisation scenarios. Absolute accuracy isn't required here, so switch to the slightly less accurate (as per the man page) get-variants. PR: 295043 MFC after: 3 weeks Sponsored by: Rubicon Communications, LLC ("Netgate") (cherry picked from commit e4130ac13d2fe9c17b8f8e86d39a7b5223fe7b79) sys/net/pfvar.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)