Skip to content

pfsync (proto 240) never transmitted from one HA node — bulk transfer fails every time, live sync one-directional #316

Description

@MikeBishop

Important notices

Before you add a new report, we ask you kindly to acknowledge the following:

If AI was used, please disclose:

  • Model used: Claude
  • Extent of AI involvement: Proposed hypotheses and suggested debug steps; authored summary after debug session

Summary

In a two-node OPNsense CARP/pfsync HA pair, one node (opnsense-primary) never transmits a single pfsync frame, in either the initial bulk state-table transfer or ongoing incremental sync. This has been confirmed at four independent layers on the affected node — kernel counters, a capture on the physical interface, a capture on the underlying Proxmox host tap, and a capture on the pfsync0 pseudo-interface itself — all showing zero output.

The other node (opnsense-backup) sends pfsync normally — its outbound frames are confirmed reaching primary via matching netstat counters and packet-for-packet matching tcpdump captures on both ends. Backup's receive path, however, has never actually been observed working: netstat -s -p pfsync on backup shows 0 packets received (IPv4), total, since boot. Given there are only two nodes in this pair, that's fully consistent with — and not independent evidence against — primary's transmit failure: backup has nothing to receive because primary has never sent it anything. So only one direction of this link (backup → primary) has actually been demonstrated healthy; the reverse direction is unconfirmed, not confirmed-and-broken-elsewhere. The failure survives a full VM restart (not just an in-OS reboot) and is not explained by anything in the pf ruleset, HA config, ARP, ARP/FDB entries, EVPN control-plane state, or per-queue NIC driver statistics, all of which check out clean and symmetric between the two nodes.

Because the bulk transfer fails every time, CARP demotion never fully clears and accumulates a large stuck negative value on the master node, which in turn caused CARP Maintenance Mode to fail to force a failover (consistent with the existing report at opnsense/core#5930 re: maintenance mode not reliably working in virtualized environments) — a manual CARP disable was required to force mastership over to the healthy node.

Environment

  • OPNsense version: OPNsense 26.1.11_10 (amd64)
  • Platform: Proxmox VE, QEMU/KVM (meta: creation-qemu=11.0.2)
  • Two single-socket, 8-core, 8GB VMs, one per physical Proxmox host (pve-a, pve-b)
  • NIC model: virtio, queues=8 on every interface, including the pfsync-dedicated NIC
  • pfsync interface: vtnet5 (OPNsense-internal name opt3), dedicated point-to-point /30 (169.254.10.0/30, primary=.1, backup=.2), MTU 8930
  • The sync bridge on the Proxmox side is not a plain Linux bridge — it's a VXLAN/EVPN overlay (VNI 3) under a VRF, configured nolearning, with FRR handling MAC/IP route distribution via BGP EVPN
  • pfsyncversion (config token) / protocol version: 1400 (PFSYNCv5 in captures)
  • hasync config block matches exactly between nodes apart from the peer IP (verified via /conf/config.xml)

Symptom

  • Master node (opnsense-primary) accumulates a large state table over time (thousands of states); backup accumulates only what it creates for its own local services (dozens).
  • netstat -s -p pfsync on primary shows normal, climbing receive counters (states arriving from backup) but completely static send countersstate inserts sent, end of frame marks sent, etc. do not move at all across a full working session: multiple curl-triggered new states, a pfctl/HA config reapply, an ifconfig pfsync0 create re-parameterization, and a full restart of the VM.
  • Every pfsync bulk transfer attempt (triggered at boot, at manual pfsync0 reconfiguration, and at HA config reapply) fails with carp: demoted by -240 to X (pfsync bulk fail) in dmesg. The interval between pfsync bulk start and pfsync bulk fail is consistently ~65–90 kernel ticks, observed identically across multiple independent trigger events on both the master role and — earlier in the pair's life, on the other node while it briefly held the requester role — meaning the ~65–90 tick timeout itself is not new or unique to this node; what's unique to this node is that it never actually gets a frame onto the wire in the first place, request or otherwise.

Reproduction

  1. Bring up a two-node OPNsense 25.x HA/CARP pair on Proxmox VMs, one VM per physical host, connected via a dedicated pfsync interface on a VXLAN/EVPN-backed bridge.
  2. Let the pair run under normal traffic for some time (state churn on both nodes).
  3. On the node that will exhibit the bug (in our case, whichever node was master at initial CARP bring-up): netstat -s -p pfsync | grep "state inserts sent".
  4. Trigger new state creation (e.g. curl to any external host).
  5. Re-check the same counter. Expected: it increases. Observed: no change, ever, regardless of how the state was created or how much time passes.
  6. Confirm nothing crosses the wire for proto 240 from this node's pfsync IP, at any of: the guest's physical interface (tcpdump -ni vtnet5 -n proto 240 and src host <this-node-ip>), the Proxmox host's tap device for that NIC (tcpdump -ni tapNNNiM proto 240), and the pfsync0 pseudo-interface itself (tcpdump -ni pfsync0 -n).
  7. Force a fresh bulk attempt (ifconfig pfsync0 create syncdev <if> syncpeer <peer-ip> maxupd 128 up, or toggle/reapply HA settings) — dmesg logs a pfsync bulk start followed ~65–90 ticks later by pfsync bulk fail, with zero packets observed in any of the three captures above during that window.
  8. Fully restart the VM (host-level qm stop/qm start, not an in-guest reboot) — the behavior is identical post-restart. Send counters remain frozen from first boot.

What's been ruled out

Working through this collaboratively (see note on process below), the following were checked and are not the cause:

  • ARP — resolves correctly and is cached (arp -an shows a valid, current entry for the peer).
  • Physical interface healthnetstat -i shows 0 input/output errors on the pfsync NIC across millions of packets of other traffic.
  • pfsync0 bindingifconfig pfsync0 reports the correct syncdev, syncpeer, maxupd, and version, matching the saved config exactly (syncok: 1 throughout).
  • HA/pfsync config file<hasync> block in /conf/config.xml is symmetric between both nodes (same pfsyncinterface, pfsyncversion; peer IPs correctly cross-referenced).
  • pf ruleset on the pfsync interface — no block/quick rule shadows the interface; all pass rules for that interface show normal, expected evaluation counts unrelated to pfsync.
  • Multiqueue/RSS on the virtio NIC — per-queue sysctl dev.vtnet.N.txqM.opackets shows all 8 transmit queues actively moving packets for other traffic sharing the same physical interface (an rsync process, unrelated to pfsync), ruling out a stuck-queue/hash-collision explanation for why proto 240 specifically never appears.
  • EVPN control plane / bridge FDBbridge fdb show on both Proxmox hosts shows correct, mirrored extern_learn entries for each node's pfsync MAC pointing at the correct peer VTEP address; the overlay's control plane is healthy and symmetric.
  • VM/NIC configuration driftqm config for both VMs shows identical NIC model, queue count, and bridge attachment for the pfsync interface; the only structural difference is which physical Proxmox host each VM runs on.
  • A one-off wedged runtime state — ruled out by the VM restart test; the exact same failure signature (frozen send counters, identical bulk-fail timing) reappeared from first boot.
  • CARP role / demotion arithmetic causing an apparent "won't fail over" symptom — this part is expected behavior, not a bug: the affected node's stuck demotion value (advskew + demotion) was large enough to keep it winning CARP elections regardless of Maintenance Mode, consistent with Enter Persistent CARP Maintenance Mode doesn't do anything core#5930's notes on maintenance mode being unreliable in virtualized environments; a manual disable CARP was needed to force failover. This is a downstream consequence of the pfsync bug, not a separate issue.

Current understanding / ask

At this point the failure appears to be entirely inside the FreeBSD kernel's carp/pfsync send path on the affected node — something prevents pfsync_out/the carp bulk-request logic from ever constructing or queuing an outbound frame, upstream of the network stack, the driver, and everything below it. We don't have kernel-debugging tooling (DTrace/ktrace on the relevant kqueue functions) applied yet, and would appreciate guidance on:

  • Any known FreeBSD/OPNsense issue matching "pfsync send counters frozen from boot, receive path unaffected, survives full VM restart"
  • Whether there's a known interaction between pfsync's output path and any of: VXLAN/EVPN-backed bridges specifically (as opposed to a plain L2 bridge or physical NIC), the nolearning bridge flag on the underlying overlay, or virtio-net on recent FreeBSD/OPNsense releases
  • What kernel-level tracing (ktrace, DTrace probes on carp/pfsync, or a debug sysctl) would help pin down where in the send path the frame is being dropped or never generated

Happy to gather ktrace/DTrace output, pfsync_undefer/related sysctl values, or anything else that would help narrow this further.


Note on how this report was produced

This write-up documents a diagnostic session conducted collaboratively between the system owner (who ran every command and supplied all raw output below) and Claude (Anthropic's AI assistant), which proposed hypotheses, suggested each diagnostic command, and interpreted results in real time. In the interest of full transparency:

  • All commands were run by the human operator, on real production hardware, and all output quoted or summarized above is genuine, unedited system output.
  • The AI proposed the diagnostic sequence and several intermediate theories, some of which were wrong and were later disproven by further testing — including, at various points, an incorrect read of packet-capture directionality, a since-abandoned theory that the OPNsense virtio NIC's multiqueue/RSS hashing was dropping proto 240 traffic onto a stalled queue, and an incorrect prediction that a full VM restart would resolve the issue. Each of these was corrected once contradicting evidence came in, and this report reflects only the conclusions that survived that process, not the discarded ones.
  • The final conclusion — a primary-side, boot-persistent, kernel-internal pfsync transmit failure — is the product of that iterative process, not a single diagnosis. Anyone acting on this report should treat the ruled-out list as reasonably solid (each item was checked against direct command output) but should independently verify anything critical before relying on it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    upstreamThird party issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions