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 2/3] tc: add the psp action
Date: Wed, 11 Nov 2026 16:15:11 -0800 [thread overview]
Message-ID: <20261112001512.3598626-3-toddfreed@meta.com> (raw)
In-Reply-To: <20261112001512.3598626-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 | 90 +++++++++++++++++++++++++
tc/Makefile | 1 +
tc/m_psp.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 257 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..4973bbbe05fa
--- /dev/null
+++ b/man/man8/tc-psp.8
@@ -0,0 +1,90 @@
+.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
+.IR CONTROL " := { "
+.BR reclassify " | " pipe " | " drop " | " continue " | " pass " | " goto " " chain " " CHAIN_INDEX " }"
+.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: encap 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. In software, encap runs on
+egress only: 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. It takes no association. 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.
+.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..37ddff25ed14
--- /dev/null
+++ b/tc/m_psp.c
@@ -0,0 +1,166 @@
+// 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"
+ "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>\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 (matches(*argv, "psp") != 0)
+ return -1;
+ NEXT_ARG();
+
+ while (argc > 0) {
+ if (matches(*argv, "encap") == 0) {
+ if (psp_set_action(&action, TCA_PSP_ACT_ENCAP))
+ return -1;
+ } else if (matches(*argv, "decap") == 0) {
+ if (psp_set_action(&action, TCA_PSP_ACT_DECAP))
+ return -1;
+ } else if (matches(*argv, "assoc") == 0) {
+ NEXT_ARG();
+ if (get_u32(&assoc, *argv, 0)) {
+ fprintf(stderr, "psp: Illegal \"assoc\"\n");
+ return -1;
+ }
+ assoc_set = true;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ break;
+ }
+ NEXT_ARG_FWD();
+ }
+
+ parse_action_control_dflt(&argc, &argv, &p.action, false, TC_ACT_PIPE);
+
+ if (argc && matches(*argv, "index") == 0) {
+ NEXT_ARG();
+ if (get_u32(&p.index, *argv, 10)) {
+ fprintf(stderr, "psp: Illegal \"index\"\n");
+ return -1;
+ }
+ NEXT_ARG_FWD();
+ }
+
+ if (!p.index) {
+ if (!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();
+ }
+ }
+
+ 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;
+ __u32 action = 0;
+
+ 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])
+ 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_uint(PRINT_ANY, "psp_action", "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
next prev parent reply other threads:[~2026-11-12 0:15 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 0:15 [PATCH iproute2-next 0/3] tc: add the psp action and flower psp matches Todd Freed
2026-11-12 0:15 ` [PATCH iproute2-next 1/3] uapi: add the tc psp action and flower psp keys Todd Freed
2026-11-12 0:15 ` Todd Freed [this message]
2026-11-12 1:05 ` [PATCH iproute2-next 2/3] tc: add the psp action Stephen Hemminger
2026-11-12 0:15 ` [PATCH iproute2-next 3/3] tc: flower: add matches psp_spi, psp_ver and psp_dev Todd Freed
2026-11-12 1:06 ` Stephen Hemminger
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=20261112001512.3598626-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