Page MenuHomeFreeBSD

D61194.1794684032.diff
No OneTemporary

Authored By
Unknown
Size
11 KB
Referenced Files
None
Subscribers
None

D61194.1794684032.diff

diff --git a/share/man/man4/ipsec.4 b/share/man/man4/ipsec.4
--- a/share/man/man4/ipsec.4
+++ b/share/man/man4/ipsec.4
@@ -27,7 +27,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 7, 2026
+.Dd November 4, 2026
.Dt IPSEC 4
.Os
.Sh NAME
@@ -240,6 +240,7 @@
.It "net.inet.ipsec.natt_cksum_policy integer yes"
.It "net.inet.ipsec.check_policy_history integer yes"
.It "net.inet.ipsec.random_id integer yes"
+.It "net.inet.ipsec.sa_flowid integer yes"
.It "net.inet6.ipsec6.ecn integer yes"
.It "net.inet6.ipsec6.debug integer yes"
.El
@@ -292,6 +293,27 @@
by the IKE daemon, the kernel incrementally recomputes checksums for
inbound TCP segments and UDP datagrams.
If addresses were not configured, the checksums are ignored.
+.It Li ipsec.sa_flowid
+If set to non-zero (the default), packets leaving the kernel's IPsec
+transforms carry a flow ID derived from the SPI of their security association
+instead of that of the inner traffic.
+An interface, a
+.Xr lagg 4
+with
+.Cm use_flowid
+or a multipath route that selects a transmit queue, port or path by flow ID
+then keeps the packets of one SA together, which avoids the reordering of
+their sequence numbers between independently draining queues.
+Concurrent processing and asynchronous crypto completion can still reorder
+them before they are queued.
+If set to 0, the packets keep the flow ID of the inner traffic, so one SA may
+spread over several queues and its sequence numbers interleave, which a peer
+with a small anti-replay window drops.
+The ID is set before the packet is handed to a network interface that
+implements the security association itself, so such SAs select their queue
+the same way.
+To spread a tunnel over several queues, ports or paths, use several SAs;
+distinct SAs may still select the same one.
.It Li ipsec.check_policy_history
Enables strict policy checking for inbound packets.
By default, inbound security policies check that packets handled by IPsec
diff --git a/sys/netipsec/ipsec.h b/sys/netipsec/ipsec.h
--- a/sys/netipsec/ipsec.h
+++ b/sys/netipsec/ipsec.h
@@ -297,6 +297,7 @@
VNET_DECLARE(int, ip4_ipsec_random_id);
VNET_DECLARE(int, crypto_support);
VNET_DECLARE(int, async_crypto);
+VNET_DECLARE(int, sa_flowid);
VNET_DECLARE(int, natt_cksum_policy);
#define IPSECSTAT_INC(name) \
@@ -315,6 +316,7 @@
#define V_ip4_ipsec_random_id VNET(ip4_ipsec_random_id)
#define V_crypto_support VNET(crypto_support)
#define V_async_crypto VNET(async_crypto)
+#define V_sa_flowid VNET(sa_flowid)
#define V_natt_cksum_policy VNET(natt_cksum_policy)
#define ipseclog(x) do { if (V_ipsec_debug) log x; } while (0)
diff --git a/sys/netipsec/ipsec.c b/sys/netipsec/ipsec.c
--- a/sys/netipsec/ipsec.c
+++ b/sys/netipsec/ipsec.c
@@ -159,6 +159,8 @@
* 1 - enabled
*/
VNET_DEFINE(int, async_crypto) = 0;
+/* Derive the flow ID of outbound packets from the SA: one SA, one queue. */
+VNET_DEFINE(int, sa_flowid) = 1;
/*
* TCP/UDP checksum handling policy for transport mode NAT-T (RFC3948)
@@ -211,6 +213,9 @@
SYSCTL_INT(_net_inet_ipsec, OID_AUTO, async_crypto,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(async_crypto), 0,
"Use asynchronous mode to parallelize crypto jobs.");
+SYSCTL_INT(_net_inet_ipsec, OID_AUTO, sa_flowid,
+ CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(sa_flowid), 0,
+ "Derive the flow ID of outbound packets from the SA.");
SYSCTL_INT(_net_inet_ipsec, OID_AUTO, check_policy_history,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(check_policy_history), 0,
"Use strict check of inbound packets to security policy compliance.");
diff --git a/sys/netipsec/ipsec_output.c b/sys/netipsec/ipsec_output.c
--- a/sys/netipsec/ipsec_output.c
+++ b/sys/netipsec/ipsec_output.c
@@ -109,6 +109,22 @@
static int ipsec_encap(struct mbuf **mp, struct secasindex *saidx);
static size_t ipsec_get_pmtu(struct secasvar *sav);
+/*
+ * Select the transmit queue by SA: its packets then do not reorder between
+ * independently draining queues. Done before the transforms, software or
+ * offloaded, so that the SA decides the queue either way; in a bundle the
+ * last SA decides. Concurrent processing and crypto completions can still
+ * reorder the packets before they are queued.
+ */
+static void
+ipsec_set_flowid(struct mbuf *m, struct secasvar *sav)
+{
+ if (V_sa_flowid) {
+ m->m_pkthdr.flowid = sav->flowid;
+ M_HASHTYPE_SET(m, M_HASHTYPE_OPAQUE_HASH);
+ }
+}
+
#ifdef INET
static struct secasvar *
ipsec4_allocsa(struct ifnet *ifp, struct mbuf *m, const struct ip *ip,
@@ -227,6 +243,7 @@
/* Re-calculate *ip1 after potential change of m in the hook. */
m_copydata(m, 0, sizeof(*ip1), (char *)ip1);
+ ipsec_set_flowid(m, sav);
hwassist = 0;
accel = ipsec_accel_output(ifp, m, inp, sp, sav, AF_INET, mtu,
&hwassist);
@@ -656,6 +673,7 @@
if ((error = ipsec_run_hhooks(&ctx, HHOOK_TYPE_IPSEC_OUT)) != 0)
goto bad;
+ ipsec_set_flowid(m, sav);
hwassist = 0;
accel = ipsec_accel_output(ifp, m, inp, sp, sav, AF_INET6, mtu,
&hwassist);
diff --git a/sys/netipsec/key.c b/sys/netipsec/key.c
--- a/sys/netipsec/key.c
+++ b/sys/netipsec/key.c
@@ -42,6 +42,7 @@
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
+#include <sys/hash.h>
#include <sys/kernel.h>
#include <sys/fnv_hash.h>
#include <sys/lock.h>
@@ -3083,6 +3084,9 @@
}
sav->spi = spi;
+ /* Hash the SPI in host order: the same flow ID on every architecture. */
+ spi = ntohl(spi);
+ sav->flowid = jenkins_hash32(&spi, 1, 0);
sav->seq = mhp->msg->sadb_msg_seq;
sav->state = SADB_SASTATE_LARVAL;
sav->pid = (pid_t)mhp->msg->sadb_msg_pid;
diff --git a/sys/netipsec/keydb.h b/sys/netipsec/keydb.h
--- a/sys/netipsec/keydb.h
+++ b/sys/netipsec/keydb.h
@@ -150,6 +150,7 @@
*/
struct secasvar {
uint32_t spi; /* SPI Value, network byte order */
+ uint32_t flowid; /* flow ID of its packets, from the SPI */
uint32_t flags; /* holder for SADB_KEY_FLAGS */
uint32_t seq; /* sequence number */
pid_t pid; /* message's pid */
diff --git a/tests/sys/netipsec/tunnel/Makefile b/tests/sys/netipsec/tunnel/Makefile
--- a/tests/sys/netipsec/tunnel/Makefile
+++ b/tests/sys/netipsec/tunnel/Makefile
@@ -11,7 +11,8 @@
aesni_aes_cbc_256_hmac_sha2_256 \
aesni_aes_gcm_128 \
aesni_aes_gcm_256 \
- chacha20_poly1305
+ chacha20_poly1305 \
+ sa_flowid
TEST_METADATA+= execenv="jail" \
execenv_jail_params="vnet allow.raw_sockets"
diff --git a/tests/sys/netipsec/tunnel/sa_flowid.sh b/tests/sys/netipsec/tunnel/sa_flowid.sh
new file mode 100644
--- /dev/null
+++ b/tests/sys/netipsec/tunnel/sa_flowid.sh
@@ -0,0 +1,165 @@
+
+. $(atf_get_srcdir)/utils.subr
+
+# Outbound packets of a tunnel mode SA carry the SA's flow ID,
+# jenkins_hash32() of the SPI in host byte order with seed 0, in place of
+# the inner packet's. A lagg(4) with use_flowid picks its port from
+# (flowid >> flowid_shift) % ports, so with two ports and the default shift
+# of 16 the SAs below group as
+# 0x1000 (a09625cd) and 0x1001 (e1fead43): bit 16 clear
+# 0x1003 (a7bb1c69) and 0x1004 (1cdb9da5): bit 16 set
+# while lagg's own header hash, restricted to layer 2, puts every SA on the
+# same port.
+#
+# Each SA carries NFLOW inner flows, 172.16.K.1 .. 172.16.K.NFLOW to
+# 172.16.K.254, which have flow IDs of their own: the kernel hashes locally
+# generated packets once a multipath route exists. The hash key is a
+# constant, so the inner flows of every SA spread over both ports whenever
+# their flow IDs reach the lagg.
+NFLOW=8
+KEY="12345678901234567890"
+
+sf_setup()
+{
+ ist_init
+ _vnet_check_req lagg
+ epair0=$(vnet_mkepair)
+ epair1=$(vnet_mkepair)
+ vnet_mkjail a ${epair0}a ${epair1}a
+ vnet_mkjail b ${epair0}b ${epair1}b
+ for j in a b; do
+ [ ${j} = a ] && s=a || s=b
+ jexec ${j} ifconfig ${epair0}${s} up
+ jexec ${j} ifconfig ${epair1}${s} up
+ jexec ${j} ifconfig lagg0 create
+ jexec ${j} ifconfig lagg0 laggproto loadbalance \
+ laggport ${epair0}${s} laggport ${epair1}${s}
+ jexec ${j} ifconfig lo0 up
+ done
+ jexec a ifconfig lagg0 inet 10.0.0.1/24 up
+ jexec b ifconfig lagg0 inet 10.0.0.2/24 up
+ for k in 0 1 3 4; do
+ n=1
+ while [ ${n} -le ${NFLOW} ]; do
+ jexec a ifconfig lo0 alias 172.16.${k}.${n}/32
+ n=$((n + 1))
+ done
+ jexec b ifconfig lo0 alias 172.16.${k}.254/32
+ done
+ jexec a route -q add -net 172.16.0.0/16 10.0.0.2
+ jexec b route -q add -net 172.16.0.0/16 10.0.0.1
+ # Give the inner packets flow IDs: net.route.hash_outbound is read-only
+ # and turns on with the first multipath route. This one carries nothing.
+ jexec a route -q add -net 192.0.2.0/24 10.0.0.2
+ jexec a route -q add -net 192.0.2.0/24 10.0.0.3
+ if [ "$(jexec a sysctl -n net.route.hash_outbound)" != 1 ]; then
+ atf_skip "This test requires net.route.hash_outbound"
+ fi
+ for j in a b; do
+ [ ${j} = a ] && dir=out || dir=in
+ [ ${j} = a ] && rdir=in || rdir=out
+ (
+ printf "flush;\nspdflush;\n"
+ for k in 0 1 3 4; do
+ spi=$((0x1000 + k)); rspi=$((0x2000 + k))
+ printf "add 10.0.0.1 10.0.0.2 esp ${spi} -m tunnel -u ${spi} -E aes-gcm-16 \"${KEY}\";\n"
+ printf "add 10.0.0.2 10.0.0.1 esp ${rspi} -m tunnel -u ${rspi} -E aes-gcm-16 \"${KEY}\";\n"
+ printf "spdadd 172.16.${k}.0/24 172.16.${k}.254/32 any -P ${dir} ipsec esp/tunnel/10.0.0.1-10.0.0.2/unique:${spi};\n"
+ printf "spdadd 172.16.${k}.254/32 172.16.${k}.0/24 any -P ${rdir} ipsec esp/tunnel/10.0.0.2-10.0.0.1/unique:${rspi};\n"
+ done
+ ) > ${TMPDIR}/ipsec.${j}.conf
+ atf_check -s exit:0 -o ignore jexec ${j} setkey -f ${TMPDIR}/ipsec.${j}.conf
+ done
+ # Resolve ARP and bring the SAs up before counting.
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 10.0.0.2
+ for k in 0 1 3 4; do
+ atf_check -s exit:0 -o ignore jexec a ping -c 1 -S 172.16.${k}.1 172.16.${k}.254
+ done
+ # Header hashing alone (layer 2 only) cannot tell the SAs apart.
+ jexec a ifconfig lagg0 use_flowid lagghash l2
+}
+
+# sf_opackets IF: packets jail a has sent on IF
+sf_opackets()
+{
+ jexec a netstat -ibn -I $1 | awk '$3 ~ /^<Link/ { print $9 }'
+}
+
+# sf_send K: ping over every inner flow of SA 0x100K, 10 packets each, and
+# set d0 and d1 to the packets that left on lagg port 0 and 1. Not a
+# command substitution: atf_fail must end the test.
+sf_send()
+{
+ b0=$(sf_opackets ${epair0}a); b1=$(sf_opackets ${epair1}a)
+ n=1
+ while [ ${n} -le ${NFLOW} ]; do
+ jexec a ping -q -c 10 -i 0.02 -S 172.16.$1.${n} 172.16.$1.254 \
+ > /dev/null 2>&1 ||
+ atf_fail "ping from 172.16.$1.${n} over SA 0x100$1 failed"
+ n=$((n + 1))
+ done
+ d0=$(( $(sf_opackets ${epair0}a) - b0 )); d1=$(( $(sf_opackets ${epair1}a) - b1 ))
+ echo "SA 0x100$1: ${d0} packets on port 0, ${d1} on port 1"
+}
+
+# sf_member K VAR: set VAR to the lagg port (0 or 1) that carries every
+# inner flow of SA 0x100K.
+sf_member()
+{
+ sf_send $1
+ if [ ${d0} -ge $((10 * NFLOW)) ] && [ ${d1} -le 2 ]; then
+ eval $2=0
+ elif [ ${d1} -ge $((10 * NFLOW)) ] && [ ${d0} -le 2 ]; then
+ eval $2=1
+ else
+ atf_fail "SA 0x100$1 did not use one port for all of its flows"
+ fi
+}
+
+atf_test_case "select" "cleanup"
+select_head()
+{
+ atf_set descr 'lagg(4) use_flowid selects the port of an SA from its flow ID'
+ atf_set require.user root
+}
+select_body()
+{
+ sf_setup
+ sf_member 0 m0; sf_member 1 m1; sf_member 3 m3; sf_member 4 m4
+ echo "ports: 0x1000 -> ${m0}, 0x1001 -> ${m1}, 0x1003 -> ${m3}, 0x1004 -> ${m4}"
+ if [ "${m0}" != "${m1}" ] || [ "${m3}" != "${m4}" ] || [ "${m0}" = "${m3}" ]; then
+ atf_fail "SAs did not group by bit 16 of their flow ID"
+ fi
+}
+select_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "off" "cleanup"
+off_head()
+{
+ atf_set descr 'With net.inet.ipsec.sa_flowid=0 packets keep the inner flow ID'
+ atf_set require.user root
+}
+off_body()
+{
+ sf_setup
+ atf_check -s exit:0 -o ignore jexec a sysctl net.inet.ipsec.sa_flowid=0
+ for k in 0 1 3 4; do
+ sf_send ${k}
+ if [ ${d0} -lt 10 ] || [ ${d1} -lt 10 ]; then
+ atf_fail "the inner flows of SA 0x100${k} did not spread over the ports"
+ fi
+ done
+}
+off_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "select"
+ atf_add_test_case "off"
+}

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 14, 7:20 PM (5 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41690255
Default Alt Text
D61194.1794684032.diff (11 KB)

Event Timeline