Outbound IPsec packets retain the flow ID of the inner traffic. Multi-queue
interfaces use this ID to select a transmit queue (iflib's QIDX()) or a member
port (lagg(4) with use_flowid). Consequently, packets belonging to the same
security association (SA) can be distributed across independently draining
queues.
ESP and AH sequence numbers are assigned before encryption, but the packets
can be transmitted out of order when different queues drain at different
rates. This can cause legitimate packets to fall outside the receiver's
anti-replay window and be dropped as possible replays. RFC 4303, Section
3.4.3, makes 32 packets the smallest window a receiver must support, and
strongSwan installs that minimum by default.
Assign each SA a flow ID derived from a Jenkins hash of its SPI, computed once
when the SA is created, from the SPI in host byte order so that the value is
the same on every architecture. Set this ID on outbound packets in
ipsec{4,6}_perform_request(), with M_HASHTYPE_OPAQUE_HASH, once the SA is
known and before ipsec_accel_output(): it then applies whether the SA is
processed in software (ESP, AH, and IPCOMP) or by a network interface that
implements it, since the offload path keeps the packet header and hands the
packet to the interface's own transmit queue selection, and in a bundle the
last SA decides. Whether the flow ID selects the queue is up to the consumer:
a driver with its own selection (iflib's isc_txq_select) may ignore it.
Hashing the SPI distributes SAs across transmit queues regardless of whether
the queue-selection mechanism uses low-order bits (iflib) or shifted
high-order bits (lagg(4) with flowid_shift). It also avoids the poor
distribution that consecutive manually assigned SPIs would produce if used
directly.
All packets belonging to an SA now select the same transmit queue,
eliminating reordering caused by independently draining queues. Reordering
before enqueue, including skew in asynchronous crypto completion, remains
possible and is addressed separately.
Binding an SA to one transmit queue also binds it to one port or path when
using lagg(4) with use_flowid or flow-ID-based multipath routing. This trades
parallelism within an SA for packet ordering. Scaling a tunnel across
multiple queues is instead possible with multiple SAs, each with its own
sequence-number space, as supported by RFC 9611's per-resource Child SAs or
by separate policies or interfaces.
The net.inet.ipsec.sa_flowid sysctl restores the previous inner-flow-based
selection.
MFC after: 2 weeks
Sponsored by: Rubicon Communications, LLC ("Netgate")