[PATCH net-next v1 10/15] net/sched: flower: add psp_spi, psp_ver, and psp_dev matches

From: Todd Freed

Date: Tue Nov 10 2026 - 19:58:09 EST


Add a flow dissector key filled from the PSP Rx state (SKB_EXT_PSP) the
device attached to a decrypted packet, and corresponding flower matches.
SPI the packet was decrypted with (psp_spi, with a mask), PSP version
(psp_ver), and the ID of the PSP device which decrypted it (psp_dev).

For example, to take the packets of one Rx association, (created with
rx-spi-alloc), and remove their PSP state so that they can be forwarded,

tc filter add dev eth0 ingress protocol ipv6 flower
psp_spi 0x80000001 psp_ver 0 psp_dev 1
action psp decap

Together these matches tie a rule to one association, in a form which
hw can match on (offload comes in a follow-up series). The SPI is only
unique on its PSP device, and an association has one PSP version. This
matches the per-packet check that psp sockets have against its
association, with one exception - the generation of the device key is
not checked.

this means that a rule matching packets that correspond to an rx
association which is stale (two rotations have passed), can still match.

It is therefore important that a rule which matches a SPI should be
replaced before the device key has had two rotations. follow up work
will bring enforcement of this based on key rotation events.

Note that this only ever matches packets which were decrypted, even if
the mask is 0. So "psp_spi 0/0" matches any decrypted PSP packet.

Signed-off-by: Todd Freed <toddfreed@xxxxxxxx>
---
Documentation/netlink/specs/tc.yaml | 14 +++++++
include/linux/skbuff.h | 4 ++
include/net/flow_dissector.h | 18 ++++++++
include/uapi/linux/pkt_cls.h | 5 +++
net/core/flow_dissector.c | 27 ++++++++++++
net/sched/cls_flower.c | 64 +++++++++++++++++++++++++++++
6 files changed, 132 insertions(+)

diff --git a/Documentation/netlink/specs/tc.yaml b/Documentation/netlink/specs/tc.yaml
index b6ccc645b0e3..044938316ffd 100644
--- a/Documentation/netlink/specs/tc.yaml
+++ b/Documentation/netlink/specs/tc.yaml
@@ -2908,6 +2908,20 @@ attribute-sets:
byte-order: big-endian
enum: flower-key-ctrl-flags
enum-as-flags: true
+ -
+ name: key-psp-spi
+ type: u32
+ byte-order: big-endian
+ -
+ name: key-psp-spi-mask
+ type: u32
+ byte-order: big-endian
+ -
+ name: key-psp-version
+ type: u8
+ -
+ name: key-psp-dev
+ type: u32
-
name: flower-key-enc-opts-attrs
name-prefix: tca-flower-key-enc-opts-
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 73365c5de6f9..e65de3f5c136 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -1660,6 +1660,10 @@ void skb_flow_dissect_hash(const struct sk_buff *skb,
struct flow_dissector *flow_dissector,
void *target_container);

+void skb_flow_dissect_psp(const struct sk_buff *skb,
+ struct flow_dissector *flow_dissector,
+ void *target_container);
+
static inline __u32 skb_get_hash_net(const struct net *net, struct sk_buff *skb)
{
if (!skb->l4_hash && !skb->sw_hash)
diff --git a/include/net/flow_dissector.h b/include/net/flow_dissector.h
index ced79dc8e856..151ae03d5ad5 100644
--- a/include/net/flow_dissector.h
+++ b/include/net/flow_dissector.h
@@ -339,6 +339,23 @@ struct flow_dissector_key_cfm {
u8 opcode;
};

+/**
+ * struct flow_dissector_key_psp - PSP Rx state of a decrypted packet
+ * @spi: SPI of the PSP association the packet was decrypted with
+ * @dev_id: ID of the PSP device which decrypted the packet
+ * @version: PSP version (enum psp_version) the packet was decrypted with
+ * @present: packet was decrypted by a PSP device
+ *
+ * Taken from the PSP Rx state the device attached to the skb, not from the
+ * packet, which no longer carries the PSP header.
+ */
+struct flow_dissector_key_psp {
+ __be32 spi;
+ u16 dev_id;
+ u8 version;
+ u8 present;
+};
+
#define FLOW_DIS_CFM_MDL_MASK GENMASK(7, 5)
#define FLOW_DIS_CFM_MDL_MAX 7

@@ -376,6 +393,7 @@ enum flow_dissector_key_id {
FLOW_DISSECTOR_KEY_L2TPV3, /* struct flow_dissector_key_l2tpv3 */
FLOW_DISSECTOR_KEY_CFM, /* struct flow_dissector_key_cfm */
FLOW_DISSECTOR_KEY_IPSEC, /* struct flow_dissector_key_ipsec */
+ FLOW_DISSECTOR_KEY_PSP, /* struct flow_dissector_key_psp */

FLOW_DISSECTOR_KEY_MAX,
};
diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h
index 14cff8f5da74..0cfa7425404b 100644
--- a/include/uapi/linux/pkt_cls.h
+++ b/include/uapi/linux/pkt_cls.h
@@ -561,6 +561,11 @@ enum {
TCA_FLOWER_KEY_ENC_FLAGS, /* be32 */
TCA_FLOWER_KEY_ENC_FLAGS_MASK, /* be32 */

+ TCA_FLOWER_KEY_PSP_SPI, /* be32 */
+ TCA_FLOWER_KEY_PSP_SPI_MASK, /* be32 */
+ TCA_FLOWER_KEY_PSP_VERSION, /* u8, enum psp_version */
+ TCA_FLOWER_KEY_PSP_DEV, /* u32, PSP device ID */
+
__TCA_FLOWER_MAX,
};

diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c
index 1be6c739e3c7..95d3fa3b1568 100644
--- a/net/core/flow_dissector.c
+++ b/net/core/flow_dissector.c
@@ -28,6 +28,7 @@
#include <linux/ptp_classify.h>
#include <net/flow_dissector.h>
#include <net/pkt_cls.h>
+#include <net/psp/types.h>
#include <scsi/fc/fc_fcoe.h>
#include <uapi/linux/batadv_packet.h>
#include <linux/bpf.h>
@@ -511,6 +512,32 @@ void skb_flow_dissect_hash(const struct sk_buff *skb,
}
EXPORT_SYMBOL(skb_flow_dissect_hash);

+void skb_flow_dissect_psp(const struct sk_buff *skb,
+ struct flow_dissector *flow_dissector,
+ void *target_container)
+{
+#if IS_ENABLED(CONFIG_INET_PSP)
+ struct flow_dissector_key_psp *key;
+ struct psp_skb_ext *pse;
+
+ if (!dissector_uses_key(flow_dissector, FLOW_DISSECTOR_KEY_PSP))
+ return;
+
+ pse = skb_ext_find(skb, SKB_EXT_PSP);
+ if (!pse)
+ return;
+
+ key = skb_flow_dissector_target(flow_dissector,
+ FLOW_DISSECTOR_KEY_PSP,
+ target_container);
+ key->spi = pse->spi;
+ key->dev_id = pse->dev_id;
+ key->version = pse->version;
+ key->present = 1;
+#endif
+}
+EXPORT_SYMBOL(skb_flow_dissect_psp);
+
static enum flow_dissect_ret
__skb_flow_dissect_mpls(const struct sk_buff *skb,
struct flow_dissector *flow_dissector,
diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c
index 23eba4a00a94..f983deab1a6d 100644
--- a/net/sched/cls_flower.c
+++ b/net/sched/cls_flower.c
@@ -81,6 +81,7 @@ struct fl_flow_key {
struct flow_dissector_key_l2tpv3 l2tpv3;
struct flow_dissector_key_ipsec ipsec;
struct flow_dissector_key_cfm cfm;
+ struct flow_dissector_key_psp psp;
} __aligned(BITS_PER_LONG / 8); /* Ensure that we can do comparisons as longs. */

struct fl_flow_mask_range {
@@ -343,6 +344,7 @@ TC_INDIRECT_SCOPE int fl_classify(struct sk_buff *skb,
ARRAY_SIZE(fl_ct_info_to_flower_map),
post_ct, zone);
skb_flow_dissect_hash(skb, &mask->dissector, &skb_key);
+ skb_flow_dissect_psp(skb, &mask->dissector, &skb_key);
skb_flow_dissect(skb, &mask->dissector, &skb_key,
FLOW_DISSECTOR_F_STOP_BEFORE_ENCAP);

@@ -633,6 +635,11 @@ static void *fl_get(struct tcf_proto *tp, u32 handle)
return __fl_get(head, handle);
}

+/* SKB_EXT_PSP holds the PSP device ID in 16 bits */
+static const struct netlink_range_validation fl_psp_dev_range = {
+ .max = U16_MAX,
+};
+
static const struct nla_policy fl_policy[TCA_FLOWER_MAX + 1] = {
[TCA_FLOWER_UNSPEC] = { .strict_start_type =
TCA_FLOWER_L2_MISS },
@@ -745,6 +752,12 @@ static const struct nla_policy fl_policy[TCA_FLOWER_MAX + 1] = {
[TCA_FLOWER_KEY_SPI] = { .type = NLA_U32 },
[TCA_FLOWER_KEY_SPI_MASK] = { .type = NLA_U32 },
[TCA_FLOWER_L2_MISS] = NLA_POLICY_MAX(NLA_U8, 1),
+ [TCA_FLOWER_KEY_PSP_SPI] = { .type = NLA_BE32 },
+ [TCA_FLOWER_KEY_PSP_SPI_MASK] = { .type = NLA_BE32 },
+ /* the version field of the PSP header has 4 bits */
+ [TCA_FLOWER_KEY_PSP_VERSION] = NLA_POLICY_MAX(NLA_U8, 15),
+ [TCA_FLOWER_KEY_PSP_DEV] = NLA_POLICY_FULL_RANGE(NLA_U32,
+ &fl_psp_dev_range),
[TCA_FLOWER_KEY_CFM] = { .type = NLA_NESTED },
[TCA_FLOWER_KEY_ENC_FLAGS] = NLA_POLICY_MASK(NLA_BE32,
TCA_FLOWER_KEY_ENC_FLAGS_POLICY_MASK),
@@ -2077,6 +2090,33 @@ static int fl_set_key(struct net *net, struct nlattr *tca_opts,
return ret;
}

+ if (tb[TCA_FLOWER_KEY_PSP_SPI_MASK] && !tb[TCA_FLOWER_KEY_PSP_SPI]) {
+ NL_SET_ERR_ATTR_MISS(extack, tca_opts, TCA_FLOWER_KEY_PSP_SPI);
+ return -EINVAL;
+ }
+ if (tb[TCA_FLOWER_KEY_PSP_SPI] || tb[TCA_FLOWER_KEY_PSP_VERSION] ||
+ tb[TCA_FLOWER_KEY_PSP_DEV]) {
+ if (!IS_ENABLED(CONFIG_INET_PSP)) {
+ NL_SET_ERR_MSG(extack, "PSP isn't enabled");
+ return -EOPNOTSUPP;
+ }
+ fl_set_key_val(tb, &key->psp.spi, TCA_FLOWER_KEY_PSP_SPI,
+ &mask->psp.spi, TCA_FLOWER_KEY_PSP_SPI_MASK,
+ sizeof(key->psp.spi));
+ if (tb[TCA_FLOWER_KEY_PSP_VERSION]) {
+ key->psp.version =
+ nla_get_u8(tb[TCA_FLOWER_KEY_PSP_VERSION]);
+ mask->psp.version = 0xff;
+ }
+ if (tb[TCA_FLOWER_KEY_PSP_DEV]) {
+ key->psp.dev_id = nla_get_u32(tb[TCA_FLOWER_KEY_PSP_DEV]);
+ mask->psp.dev_id = 0xffff;
+ }
+ /* Only match packets which were actually decrypted */
+ key->psp.present = 1;
+ mask->psp.present = 0xff;
+ }
+
if (tb[TCA_FLOWER_KEY_ENC_IPV4_SRC] ||
tb[TCA_FLOWER_KEY_ENC_IPV4_DST]) {
key->enc_control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
@@ -2263,6 +2303,8 @@ static void fl_init_dissector(struct flow_dissector *dissector,
FLOW_DISSECTOR_KEY_IPSEC, ipsec);
FL_KEY_SET_IF_MASKED(mask, keys, cnt,
FLOW_DISSECTOR_KEY_CFM, cfm);
+ FL_KEY_SET_IF_MASKED(mask, keys, cnt,
+ FLOW_DISSECTOR_KEY_PSP, psp);

skb_flow_dissector_init(dissector, keys, cnt);
}
@@ -3651,6 +3693,28 @@ static int fl_dump_key(struct sk_buff *skb, struct net *net,
sizeof(key->ipsec.spi)))
goto nla_put_failure;

+ if (mask->psp.present &&
+ fl_dump_key_val(skb, &key->psp.spi, TCA_FLOWER_KEY_PSP_SPI,
+ &mask->psp.spi, TCA_FLOWER_KEY_PSP_SPI_MASK,
+ sizeof(key->psp.spi)))
+ goto nla_put_failure;
+ /* With a 0 SPI mask and no version or device, the SPI still carries
+ * the match on a decrypted packet: dump it with its mask
+ */
+ if (mask->psp.present && !mask->psp.spi && !mask->psp.version &&
+ !mask->psp.dev_id &&
+ (nla_put(skb, TCA_FLOWER_KEY_PSP_SPI, sizeof(key->psp.spi),
+ &key->psp.spi) ||
+ nla_put(skb, TCA_FLOWER_KEY_PSP_SPI_MASK, sizeof(mask->psp.spi),
+ &mask->psp.spi)))
+ goto nla_put_failure;
+ if (mask->psp.version &&
+ nla_put_u8(skb, TCA_FLOWER_KEY_PSP_VERSION, key->psp.version))
+ goto nla_put_failure;
+ if (mask->psp.dev_id &&
+ nla_put_u32(skb, TCA_FLOWER_KEY_PSP_DEV, key->psp.dev_id))
+ goto nla_put_failure;
+
if ((key->basic.ip_proto == IPPROTO_TCP ||
key->basic.ip_proto == IPPROTO_UDP ||
key->basic.ip_proto == IPPROTO_SCTP) &&
--
2.53.0-Meta