Netdev List
 help / color / mirror / Atom feed
From: Todd Freed <toddfreed@meta.com>
To: <netdev@vger.kernel.org>, <toddfreed@meta.com>
Cc: David Ahern <dsahern@kernel.org>,
	Stephen Hemminger <stephen@networkplumber.org>,
	Jamal Hadi Salim <jhs@mojatatu.com>,
	Jiri Pirko <jiri@resnulli.us>,
	Daniel Zahka <daniel.zahka@gmail.com>,
	Willem de Bruijn <willemdebruijn.kernel@gmail.com>,
	Jakub Kicinski <kuba@kernel.org>,
	<linux-kselftest@vger.kernel.org>
Subject: [PATCH iproute2-next v2 2/3] tc: add the psp action
Date: Thu, 12 Nov 2026 11:24:25 -0800	[thread overview]
Message-ID: <20261112192426.397604-3-toddfreed@meta.com> (raw)
In-Reply-To: <20261112192426.397604-1-toddfreed@meta.com>

The psp action applies PSP (PSP Security Protocol) encap and encryption
to packets, separately from psp socket offload:

  psp encap assoc ASSOC_ID   attach a PSP Tx association, created with
                             assoc-add over the PSP netlink family; the
                             association's device encrypts the packet
                             on transmit
  psp decap                  remove the PSP Rx state of a packet the
                             PSP device decrypted

decap takes no association. To take only those packets which belong to a
single rx-spi-alloc call, a rule applies the corresponding flower psp_spi,
psp_ver and psp_dev matches.

In JSON, the verb is psp_action. The action is documented in tc-psp(8).

Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 man/man8/tc-psp.8 |  98 ++++++++++++++++++++++++++
 tc/Makefile       |   1 +
 tc/m_psp.c        | 173 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 272 insertions(+)
 create mode 100644 man/man8/tc-psp.8
 create mode 100644 tc/m_psp.c

diff --git a/man/man8/tc-psp.8 b/man/man8/tc-psp.8
new file mode 100644
index 000000000000..a820ec291015
--- /dev/null
+++ b/man/man8/tc-psp.8
@@ -0,0 +1,98 @@
+.TH "PSP action in tc" 8 "9 Nov 2026" "iproute2" "Linux"
+
+.SH NAME
+psp - PSP transport mode action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR tc " ... " "action psp" " { "
+.BI "encap assoc" " ASSOC_ID"
+.RB " | " decap " } [ "
+.IR CONTROL " ] [ "
+.BI index " INDEX"
+.RB " ]"
+
+.ti -8
+.BR tc " ... " "action psp index"
+.I INDEX
+
+.ti -8
+.IR CONTROL " := { "
+.BR reclassify " | " pipe " | " drop " | " continue " | " pass " | " goto " " chain " " CHAIN_INDEX " | " jump " " JUMP_COUNT " }"
+.SH DESCRIPTION
+The
+.B psp
+action applies PSP (PSP Security Protocol) encapsulation to packets using keys from the PSP netlink
+family: Rx SPIs and their keys from rx-spi-alloc, and Tx associations from assoc-add (assoc-get,
+assoc-del). The PSP device encrypts and decrypts the packets.
+.SH OPTIONS
+.TP
+.BI "encap assoc" " ASSOC_ID"
+Attach the Tx association
+.IR ASSOC_ID ,
+created with the PSP netlink
+.B assoc-add
+command, to the packet. The packet must then be transmitted on the netdev of
+the association's PSP device, which encrypts it. Use encap either on that
+netdev's egress, or on another netdev's egress followed by a redirect to it
+.RB ( "mirred egress redirect" ).
+If it reaches another device the packet is dropped. Encap on ingress is
+hardware offload only
+.RB ( skip_sw ),
+and in software the action drops the packet.
+.TP
+.B decap
+Remove the PSP Rx state of a packet which the PSP device decrypted, so that
+it can be forwarded. To take only the packets corresponding to one
+rx-spi-alloc, match packets with the
+.BR flower " keys " psp_spi ", " psp_ver " and " psp_dev .
+.IP
+An SPI from
+.B rx-spi-alloc
+is valid until the PSP device's key has been rotated twice. After that, the
+device can allocate the same SPI value again, and a rule which still matches it
+takes the packets of the new allocation. Delete such rules before the second
+key rotation after the allocation; the PSP netlink family notifies each key
+rotation
+.RB ( key-rotate-ntf ).
+.TP
+.I CONTROL
+How to continue after this action, see
+.BR tc-actions (8).
+The default is
+.BR pipe .
+.TP
+.BI index " INDEX"
+Use the action of index
+.IR INDEX ,
+to create it, or to reference an existing one. Without
+.B encap
+or
+.BR decap ,
+reference an existing action.
+.SH EXAMPLES
+Encrypt the packets to 2001:db8::2 with Tx association 3, of the PSP device
+of eth0:
+
+.RS
+.EX
+tc filter add dev eth0 egress protocol ipv6 flower dst_ip 2001:db8::2 \\
+	action psp encap assoc 3
+.EE
+.RE
+
+Take the packets which eth0's PSP device (ID 1) decrypted with an SPI from
+rx-spi-alloc, and remove their PSP state:
+
+.RS
+.EX
+tc filter add dev eth0 ingress protocol ipv6 flower \\
+	psp_spi 0x80000001 psp_ver 0 psp_dev 1 \\
+	action psp decap
+.EE
+.RE
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-actions (8),
+.BR tc-flower (8),
+.BR tc-mirred (8)
diff --git a/tc/Makefile b/tc/Makefile
index 6264a772e686..f0b3ddb10155 100644
--- a/tc/Makefile
+++ b/tc/Makefile
@@ -49,6 +49,7 @@ TCMODULES += m_ctinfo.o
 TCMODULES += m_bpf.o
 TCMODULES += m_tunnel_key.o
 TCMODULES += m_sample.o
+TCMODULES += m_psp.o
 TCMODULES += m_ct.o
 TCMODULES += m_gate.o
 TCMODULES += p_ip.o
diff --git a/tc/m_psp.c b/tc/m_psp.c
new file mode 100644
index 000000000000..8356b6fa6a4f
--- /dev/null
+++ b/tc/m_psp.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * m_psp.c		PSP encryption action
+ */
+
+#include <stdio.h>
+#include "utils.h"
+#include "tc_util.h"
+#include "tc_common.h"
+#include <linux/tc_act/tc_psp.h>
+
+static void explain(void)
+{
+	fprintf(stderr,
+		"Usage: psp encap assoc ASSOC_ID [CONTROL] [index INDEX]\n"
+		"       psp decap [CONTROL] [index INDEX]\n"
+		"       psp index INDEX\n"
+		"where:\n"
+		"\tASSOC_ID := ID of a Tx association, from PSP netlink assoc-add\n"
+		"\tdecap takes no association: match the packets with flower\n"
+		"\tpsp_spi, psp_ver and psp_dev\n"
+		"\tCONTROL := reclassify | pipe | drop | continue | pass |\n"
+		"\t           goto chain <CHAIN_INDEX> | jump <JUMP_COUNT>\n");
+}
+
+static void usage(void)
+{
+	explain();
+	exit(-1);
+}
+
+static int psp_set_action(__u32 *action, __u32 a)
+{
+	if (*action && *action != a) {
+		fprintf(stderr, "psp: only one of \"encap\" and \"decap\"\n");
+		return -1;
+	}
+	*action = a;
+	return 0;
+}
+
+static int parse_psp(const struct action_util *a, int *argc_p, char ***argv_p,
+		     int tca_id, struct nlmsghdr *n)
+{
+	struct tc_psp p = { 0 };
+	bool assoc_set = false;
+	__u32 action = 0;
+	char **argv = *argv_p;
+	struct rtattr *tail;
+	int argc = *argc_p;
+	__u32 assoc;
+
+	if (strcmp(*argv, "psp") != 0)
+		return -1;
+	NEXT_ARG();
+
+	while (argc > 0) {
+		if (strcmp(*argv, "encap") == 0) {
+			if (psp_set_action(&action, TCA_PSP_ACT_ENCAP))
+				return -1;
+		} else if (strcmp(*argv, "decap") == 0) {
+			if (psp_set_action(&action, TCA_PSP_ACT_DECAP))
+				return -1;
+		} else if (strcmp(*argv, "assoc") == 0) {
+			NEXT_ARG();
+			if (assoc_set)
+				duparg("assoc", *argv);
+			if (get_u32(&assoc, *argv, 0)) {
+				fprintf(stderr, "psp: Illegal \"assoc\"\n");
+				return -1;
+			}
+			assoc_set = true;
+		} else if (strcmp(*argv, "help") == 0) {
+			usage();
+		} else {
+			break;
+		}
+		NEXT_ARG_FWD();
+	}
+
+	parse_action_control_dflt(&argc, &argv, &p.action, false, TC_ACT_PIPE);
+
+	if (argc && strcmp(*argv, "index") == 0) {
+		NEXT_ARG();
+		if (get_u32(&p.index, *argv, 10)) {
+			fprintf(stderr, "psp: Illegal \"index\"\n");
+			return -1;
+		}
+		NEXT_ARG_FWD();
+	}
+
+	/* index alone references an existing action */
+	if (!p.index && !action) {
+		fprintf(stderr, "psp: \"encap\" or \"decap\" required\n");
+		usage();
+	}
+	if (action == TCA_PSP_ACT_ENCAP && !assoc_set) {
+		fprintf(stderr, "psp: \"encap\" requires \"assoc\"\n");
+		usage();
+	}
+	if (assoc_set && action != TCA_PSP_ACT_ENCAP) {
+		fprintf(stderr, "psp: \"assoc\" requires \"encap\"\n");
+		usage();
+	}
+
+	tail = addattr_nest(n, MAX_MSG, tca_id);
+	addattr_l(n, MAX_MSG, TCA_PSP_PARMS, &p, sizeof(p));
+	if (action)
+		addattr32(n, MAX_MSG, TCA_PSP_ACTION, action);
+	if (assoc_set)
+		addattr32(n, MAX_MSG, TCA_PSP_ASSOC_ID, assoc);
+	addattr_nest_end(n, tail);
+
+	*argc_p = argc;
+	*argv_p = argv;
+	return 0;
+}
+
+static int print_psp(const struct action_util *au, FILE *f, struct rtattr *arg)
+{
+	struct rtattr *tb[TCA_PSP_MAX + 1];
+	struct tc_psp *p;
+
+	print_string(PRINT_ANY, "kind", "%s ", "psp");
+	if (!arg)
+		return 0;
+
+	parse_rtattr_nested(tb, TCA_PSP_MAX, arg);
+	if (!tb[TCA_PSP_PARMS]) {
+		fprintf(stderr, "Missing psp parameters\n");
+		return -1;
+	}
+	p = RTA_DATA(tb[TCA_PSP_PARMS]);
+	if (tb[TCA_PSP_ACTION]) {
+		__u32 action = rta_getattr_u32(tb[TCA_PSP_ACTION]);
+
+		switch (action) {
+		case TCA_PSP_ACT_ENCAP:
+			print_string(PRINT_ANY, "psp_action", "%s", "encap");
+			break;
+		case TCA_PSP_ACT_DECAP:
+			print_string(PRINT_ANY, "psp_action", "%s", "decap");
+			break;
+		default:
+			print_string(PRINT_JSON, "psp_action", NULL, "unknown");
+			print_uint(PRINT_FP, NULL, "action %u", action);
+		}
+	}
+	if (tb[TCA_PSP_ASSOC_ID])
+		print_uint(PRINT_ANY, "assoc", " assoc %u",
+			   rta_getattr_u32(tb[TCA_PSP_ASSOC_ID]));
+
+	print_action_control(" ", p->action, "");
+
+	print_nl();
+	print_uint(PRINT_ANY, "index", "\t index %u", p->index);
+	print_int(PRINT_ANY, "ref", " ref %d", p->refcnt);
+	print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt);
+
+	if (show_stats && tb[TCA_PSP_TM]) {
+		struct tcf_t *tm = RTA_DATA(tb[TCA_PSP_TM]);
+
+		print_tm(tm);
+	}
+	print_nl();
+	return 0;
+}
+
+struct action_util psp_action_util = {
+	.id = "psp",
+	.parse_aopt = parse_psp,
+	.print_aopt = print_psp,
+};
-- 
2.53.0-Meta


  parent reply	other threads:[~2026-11-12 19:24 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-12 19:24 [PATCH iproute2-next v2 0/3] tc: add the psp action and flower psp matches Todd Freed
2026-11-12 19:24 ` [PATCH iproute2-next v2 1/3] uapi: add the tc psp action and flower psp keys Todd Freed
2026-11-12 19:24 ` Todd Freed [this message]
2026-11-12 19:24 ` [PATCH iproute2-next v2 3/3] tc: flower: add matches psp_spi, psp_ver and psp_dev Todd Freed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261112192426.397604-3-toddfreed@meta.com \
    --to=toddfreed@meta.com \
    --cc=daniel.zahka@gmail.com \
    --cc=dsahern@kernel.org \
    --cc=jhs@mojatatu.com \
    --cc=jiri@resnulli.us \
    --cc=kuba@kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=stephen@networkplumber.org \
    --cc=willemdebruijn.kernel@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox