Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next 02/13] netfilter: nf_tables: Fix typo in enum description
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

From: Jelle van der Waa <jelle@vdwaa.nl>

Fix the spelling of "options".

Signed-off-by: Jelle van der Waa <jelle@vdwaa.nl>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/uapi/linux/netfilter/nf_tables.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h
index 45c71f7d21c2..dca9e72b0558 100644
--- a/include/uapi/linux/netfilter/nf_tables.h
+++ b/include/uapi/linux/netfilter/nf_tables.h
@@ -884,7 +884,7 @@ enum nft_exthdr_flags {
  * @NFT_EXTHDR_OP_TCPOPT: match against tcp options
  * @NFT_EXTHDR_OP_IPV4: match against ipv4 options
  * @NFT_EXTHDR_OP_SCTP: match against sctp chunks
- * @NFT_EXTHDR_OP_DCCP: match against dccp otions
+ * @NFT_EXTHDR_OP_DCCP: match against dccp options
  */
 enum nft_exthdr_op {
 	NFT_EXTHDR_OP_IPV6,
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 03/13] netfilter: nf_conntrack_sip: remove net variable shadowing
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

net is already set, derived from nf_conn.
I don't see how the device could be living in a different netns
than the conntrack entry.

Remove the extra variable and re-use existing one.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/nf_conntrack_sip.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c
index 939502ff7c87..182cfb119448 100644
--- a/net/netfilter/nf_conntrack_sip.c
+++ b/net/netfilter/nf_conntrack_sip.c
@@ -869,9 +869,8 @@ static int set_expected_rtp_rtcp(struct sk_buff *skb, unsigned int protoff,
 		saddr = &ct->tuplehash[!dir].tuple.src.u3;
 	} else if (sip_external_media) {
 		struct net_device *dev = skb_dst(skb)->dev;
-		struct net *net = dev_net(dev);
-		struct flowi fl;
 		struct dst_entry *dst = NULL;
+		struct flowi fl;
 
 		memset(&fl, 0, sizeof(fl));
 
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 04/13] netfilter: add deprecation warning for dccp support
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

Add a deprecation warning for the xt_dccp match and the
nft exthdr code.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/nft_exthdr.c | 3 +++
 net/netfilter/xt_dccp.c    | 3 +++
 2 files changed, 6 insertions(+)

diff --git a/net/netfilter/nft_exthdr.c b/net/netfilter/nft_exthdr.c
index 5f01269a49bd..14d4ad7f518c 100644
--- a/net/netfilter/nft_exthdr.c
+++ b/net/netfilter/nft_exthdr.c
@@ -796,6 +796,9 @@ nft_exthdr_select_ops(const struct nft_ctx *ctx,
 		break;
 #ifdef CONFIG_NFT_EXTHDR_DCCP
 	case NFT_EXTHDR_OP_DCCP:
+		pr_warn_once("The dccp option matching is deprecated and scheduled to be removed in 2027.\n"
+			     "Please contact the netfilter-devel mailing list or update your nftables rules.\n");
+
 		if (tb[NFTA_EXTHDR_DREG])
 			return &nft_exthdr_dccp_ops;
 		break;
diff --git a/net/netfilter/xt_dccp.c b/net/netfilter/xt_dccp.c
index 037ab93e25d0..3db81e041af9 100644
--- a/net/netfilter/xt_dccp.c
+++ b/net/netfilter/xt_dccp.c
@@ -159,6 +159,9 @@ static int __init dccp_mt_init(void)
 {
 	int ret;
 
+	pr_warn_once("The DCCP match is deprecated and scheduled to be removed in 2027.\n"
+		     "Please contact the netfilter-devel mailing list or update your iptables rules\n");
+
 	/* doff is 8 bits, so the maximum option size is (4*256).  Don't put
 	 * this in BSS since DaveM is worried about locked TLB's for kernel
 	 * BSS. */
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 05/13] netfilter: nf_conntrack_h323: remove unreliable debug code in decode_octstr
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

The debug code (not enabled in any build) reads up to 6 octets of
the inpt buffer, but does so without bound checks.  Zap this.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/nf_conntrack_h323_asn1.c | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/net/netfilter/nf_conntrack_h323_asn1.c b/net/netfilter/nf_conntrack_h323_asn1.c
index 7b1497ed97d2..09e0f724644f 100644
--- a/net/netfilter/nf_conntrack_h323_asn1.c
+++ b/net/netfilter/nf_conntrack_h323_asn1.c
@@ -21,7 +21,6 @@
 
 #if H323_TRACE
 #define TAB_SIZE 4
-#define IFTHEN(cond, act) if(cond){act;}
 #ifdef __KERNEL__
 #define PRINT printk
 #else
@@ -29,7 +28,6 @@
 #endif
 #define FNAME(name) name,
 #else
-#define IFTHEN(cond, act)
 #define PRINT(fmt, args...)
 #define FNAME(name)
 #endif
@@ -445,11 +443,6 @@ static int decode_octstr(struct bitstr *bs, const struct field_t *f,
 			BYTE_ALIGN(bs);
 			if (base && (f->attr & DECODE)) {
 				/* The IP Address */
-				IFTHEN(f->lb == 4,
-				       PRINT(" = %d.%d.%d.%d:%d",
-					     bs->cur[0], bs->cur[1],
-					     bs->cur[2], bs->cur[3],
-					     bs->cur[4] * 256 + bs->cur[5]));
 				*((unsigned int *)(base + f->offset)) =
 				    bs->cur - bs->buf;
 			}
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 06/13] netfilter: add more netlink-based policy range checks
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

These spots either already check the attribute range manually
before use or the consuming functions tolerate unexpected values.

Nevertheless, add more range checks via netlink policy so we gain
more users and avoid possible re-use in other places that might
not have the required manual checks.  This also improves error
reporting: netlink core can generate extack errors.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/ipset/ip_set_core.c  |  2 +-
 net/netfilter/nf_tables_api.c      | 20 +++++++++++++++-----
 net/netfilter/nfnetlink_acct.c     |  2 +-
 net/netfilter/nfnetlink_cthelper.c |  2 +-
 net/netfilter/nfnetlink_hook.c     |  2 +-
 net/netfilter/nfnetlink_log.c      |  4 +++-
 net/netfilter/nfnetlink_osf.c      |  2 +-
 net/netfilter/nfnetlink_queue.c    |  2 +-
 net/netfilter/nft_compat.c         |  2 +-
 net/netfilter/nft_connlimit.c      |  2 +-
 net/netfilter/nft_ct.c             |  2 +-
 net/netfilter/nft_dynset.c         |  3 ++-
 net/netfilter/nft_exthdr.c         |  2 +-
 net/netfilter/nft_inner.c          |  2 +-
 net/netfilter/nft_limit.c          |  2 +-
 net/netfilter/nft_log.c            |  2 +-
 net/netfilter/nft_osf.c            |  2 +-
 net/netfilter/nft_payload.c        |  2 +-
 net/netfilter/nft_queue.c          |  2 +-
 net/netfilter/nft_quota.c          |  2 +-
 net/netfilter/nft_synproxy.c       |  4 ++--
 net/netfilter/nft_tunnel.c         |  4 ++--
 net/netfilter/nft_xfrm.c           |  4 ++--
 23 files changed, 43 insertions(+), 30 deletions(-)

diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c
index d0c9fe59c67d..c5a26236a0bb 100644
--- a/net/netfilter/ipset/ip_set_core.c
+++ b/net/netfilter/ipset/ip_set_core.c
@@ -985,7 +985,7 @@ static const struct nla_policy ip_set_create_policy[IPSET_ATTR_CMD_MAX + 1] = {
 				    .len = IPSET_MAXNAMELEN - 1 },
 	[IPSET_ATTR_TYPENAME]	= { .type = NLA_NUL_STRING,
 				    .len = IPSET_MAXNAMELEN - 1},
-	[IPSET_ATTR_REVISION]	= { .type = NLA_U8 },
+	[IPSET_ATTR_REVISION]	= NLA_POLICY_MAX(NLA_U8, IPSET_REVISION_MAX),
 	[IPSET_ATTR_FAMILY]	= { .type = NLA_U8 },
 	[IPSET_ATTR_DATA]	= { .type = NLA_NESTED },
 };
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 8ed8d5384b97..8537b94653d3 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -1112,7 +1112,7 @@ static __be16 nft_base_seq_be16(const struct net *net)
 static const struct nla_policy nft_table_policy[NFTA_TABLE_MAX + 1] = {
 	[NFTA_TABLE_NAME]	= { .type = NLA_STRING,
 				    .len = NFT_TABLE_MAXNAMELEN - 1 },
-	[NFTA_TABLE_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_TABLE_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_TABLE_F_MASK),
 	[NFTA_TABLE_HANDLE]	= { .type = NLA_U64 },
 	[NFTA_TABLE_USERDATA]	= { .type = NLA_BINARY,
 				    .len = NFT_USERDATA_MAXLEN }
@@ -1878,7 +1878,7 @@ static const struct nla_policy nft_chain_policy[NFTA_CHAIN_MAX + 1] = {
 	[NFTA_CHAIN_TYPE]	= { .type = NLA_STRING,
 				    .len = NFT_MODULE_AUTOLOAD_LIMIT },
 	[NFTA_CHAIN_COUNTERS]	= { .type = NLA_NESTED },
-	[NFTA_CHAIN_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_CHAIN_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_CHAIN_FLAGS),
 	[NFTA_CHAIN_ID]		= { .type = NLA_U32 },
 	[NFTA_CHAIN_USERDATA]	= { .type = NLA_BINARY,
 				    .len = NFT_USERDATA_MAXLEN },
@@ -4597,7 +4597,16 @@ static const struct nla_policy nft_set_policy[NFTA_SET_MAX + 1] = {
 					    .len = NFT_TABLE_MAXNAMELEN - 1 },
 	[NFTA_SET_NAME]			= { .type = NLA_STRING,
 					    .len = NFT_SET_MAXNAMELEN - 1 },
-	[NFTA_SET_FLAGS]		= { .type = NLA_U32 },
+	[NFTA_SET_FLAGS]		= NLA_POLICY_MASK(NLA_BE32,
+							  NFT_SET_ANONYMOUS |
+							  NFT_SET_CONSTANT |
+							  NFT_SET_INTERVAL |
+							  NFT_SET_MAP |
+							  NFT_SET_TIMEOUT |
+							  NFT_SET_EVAL |
+							  NFT_SET_OBJECT |
+							  NFT_SET_CONCAT |
+							  NFT_SET_EXPR),
 	[NFTA_SET_KEY_TYPE]		= { .type = NLA_U32 },
 	[NFTA_SET_KEY_LEN]		= { .type = NLA_U32 },
 	[NFTA_SET_DATA_TYPE]		= { .type = NLA_U32 },
@@ -5929,7 +5938,8 @@ const struct nft_set_ext_type nft_set_ext_types[] = {
 static const struct nla_policy nft_set_elem_policy[NFTA_SET_ELEM_MAX + 1] = {
 	[NFTA_SET_ELEM_KEY]		= { .type = NLA_NESTED },
 	[NFTA_SET_ELEM_DATA]		= { .type = NLA_NESTED },
-	[NFTA_SET_ELEM_FLAGS]		= { .type = NLA_U32 },
+	[NFTA_SET_ELEM_FLAGS]		= NLA_POLICY_MASK(NLA_BE32, NFT_SET_ELEM_INTERVAL_END |
+								    NFT_SET_ELEM_CATCHALL),
 	[NFTA_SET_ELEM_TIMEOUT]		= { .type = NLA_U64 },
 	[NFTA_SET_ELEM_EXPIRATION]	= { .type = NLA_U64 },
 	[NFTA_SET_ELEM_USERDATA]	= { .type = NLA_BINARY,
@@ -8649,7 +8659,7 @@ static const struct nla_policy nft_flowtable_policy[NFTA_FLOWTABLE_MAX + 1] = {
 					    .len = NFT_NAME_MAXLEN - 1 },
 	[NFTA_FLOWTABLE_HOOK]		= { .type = NLA_NESTED },
 	[NFTA_FLOWTABLE_HANDLE]		= { .type = NLA_U64 },
-	[NFTA_FLOWTABLE_FLAGS]		= { .type = NLA_U32 },
+	[NFTA_FLOWTABLE_FLAGS]		= NLA_POLICY_MASK(NLA_BE32, NFT_FLOWTABLE_MASK),
 };
 
 struct nft_flowtable *nft_flowtable_lookup(const struct net *net,
diff --git a/net/netfilter/nfnetlink_acct.c b/net/netfilter/nfnetlink_acct.c
index 2bfaa773d82f..8ff1e0ad5cb0 100644
--- a/net/netfilter/nfnetlink_acct.c
+++ b/net/netfilter/nfnetlink_acct.c
@@ -373,7 +373,7 @@ static const struct nla_policy nfnl_acct_policy[NFACCT_MAX+1] = {
 	[NFACCT_NAME] = { .type = NLA_NUL_STRING, .len = NFACCT_NAME_MAX-1 },
 	[NFACCT_BYTES] = { .type = NLA_U64 },
 	[NFACCT_PKTS] = { .type = NLA_U64 },
-	[NFACCT_FLAGS] = { .type = NLA_U32 },
+	[NFACCT_FLAGS] = NLA_POLICY_MASK(NLA_BE32, NFACCT_F_QUOTA),
 	[NFACCT_QUOTA] = { .type = NLA_U64 },
 	[NFACCT_FILTER] = {.type = NLA_NESTED },
 };
diff --git a/net/netfilter/nfnetlink_cthelper.c b/net/netfilter/nfnetlink_cthelper.c
index d545fa459455..0d16ad82d70c 100644
--- a/net/netfilter/nfnetlink_cthelper.c
+++ b/net/netfilter/nfnetlink_cthelper.c
@@ -165,7 +165,7 @@ nfnl_cthelper_expect_policy(struct nf_conntrack_expect_policy *expect_policy,
 
 static const struct nla_policy
 nfnl_cthelper_expect_policy_set[NFCTH_POLICY_SET_MAX+1] = {
-	[NFCTH_POLICY_SET_NUM] = { .type = NLA_U32, },
+	[NFCTH_POLICY_SET_NUM] = NLA_POLICY_MAX(NLA_BE32, NF_CT_MAX_EXPECT_CLASSES),
 };
 
 static int
diff --git a/net/netfilter/nfnetlink_hook.c b/net/netfilter/nfnetlink_hook.c
index 531706982859..5623c18fcd12 100644
--- a/net/netfilter/nfnetlink_hook.c
+++ b/net/netfilter/nfnetlink_hook.c
@@ -24,7 +24,7 @@
 #include <net/sock.h>
 
 static const struct nla_policy nfnl_hook_nla_policy[NFNLA_HOOK_MAX + 1] = {
-	[NFNLA_HOOK_HOOKNUM]	= { .type = NLA_U32 },
+	[NFNLA_HOOK_HOOKNUM]	= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFNLA_HOOK_PRIORITY]	= { .type = NLA_U32 },
 	[NFNLA_HOOK_DEV]	= { .type = NLA_STRING,
 				    .len = IFNAMSIZ - 1 },
diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c
index 91aa210b3e53..9497ebeedd55 100644
--- a/net/netfilter/nfnetlink_log.c
+++ b/net/netfilter/nfnetlink_log.c
@@ -879,7 +879,9 @@ static const struct nla_policy nfula_cfg_policy[NFULA_CFG_MAX+1] = {
 	[NFULA_CFG_TIMEOUT]	= { .type = NLA_U32 },
 	[NFULA_CFG_QTHRESH]	= { .type = NLA_U32 },
 	[NFULA_CFG_NLBUFSIZ]	= { .type = NLA_U32 },
-	[NFULA_CFG_FLAGS]	= { .type = NLA_U16 },
+	[NFULA_CFG_FLAGS]	= NLA_POLICY_MASK(NLA_BE16, NFULNL_CFG_F_SEQ |
+						  NFULNL_CFG_F_SEQ_GLOBAL |
+						  NFULNL_CFG_F_CONNTRACK),
 };
 
 static int nfulnl_recv_config(struct sk_buff *skb, const struct nfnl_info *info,
diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c
index 45d9ad231a92..d64ce21c7b55 100644
--- a/net/netfilter/nfnetlink_osf.c
+++ b/net/netfilter/nfnetlink_osf.c
@@ -293,7 +293,7 @@ bool nf_osf_find(const struct sk_buff *skb,
 EXPORT_SYMBOL_GPL(nf_osf_find);
 
 static const struct nla_policy nfnl_osf_policy[OSF_ATTR_MAX + 1] = {
-	[OSF_ATTR_FINGER]	= { .len = sizeof(struct nf_osf_user_finger) },
+	[OSF_ATTR_FINGER]	= NLA_POLICY_EXACT_LEN(sizeof(struct nf_osf_user_finger)),
 };
 
 static int nfnl_osf_add_callback(struct sk_buff *skb,
diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c
index 2aa2380d976a..ac0c19233681 100644
--- a/net/netfilter/nfnetlink_queue.c
+++ b/net/netfilter/nfnetlink_queue.c
@@ -1599,7 +1599,7 @@ static const struct nla_policy nfqa_cfg_policy[NFQA_CFG_MAX+1] = {
 	[NFQA_CFG_PARAMS]	= { .len = sizeof(struct nfqnl_msg_config_params) },
 	[NFQA_CFG_QUEUE_MAXLEN]	= { .type = NLA_U32 },
 	[NFQA_CFG_MASK]		= { .type = NLA_U32 },
-	[NFQA_CFG_FLAGS]	= { .type = NLA_U32 },
+	[NFQA_CFG_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFQA_CFG_F_MAX - 1),
 };
 
 static const struct nf_queue_handler nfqh = {
diff --git a/net/netfilter/nft_compat.c b/net/netfilter/nft_compat.c
index 53a614a0e3cd..decc725a33c2 100644
--- a/net/netfilter/nft_compat.c
+++ b/net/netfilter/nft_compat.c
@@ -195,7 +195,7 @@ static void target_compat_from_user(struct xt_target *t, void *in, void *out)
 
 static const struct nla_policy nft_rule_compat_policy[NFTA_RULE_COMPAT_MAX + 1] = {
 	[NFTA_RULE_COMPAT_PROTO]	= { .type = NLA_U32 },
-	[NFTA_RULE_COMPAT_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_RULE_COMPAT_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_RULE_COMPAT_F_MASK),
 };
 
 static int nft_parse_compat(const struct nlattr *attr, u16 *proto, bool *inv)
diff --git a/net/netfilter/nft_connlimit.c b/net/netfilter/nft_connlimit.c
index 09ac4f77e389..46b31d78abc6 100644
--- a/net/netfilter/nft_connlimit.c
+++ b/net/netfilter/nft_connlimit.c
@@ -159,7 +159,7 @@ static int nft_connlimit_obj_dump(struct sk_buff *skb,
 
 static const struct nla_policy nft_connlimit_policy[NFTA_CONNLIMIT_MAX + 1] = {
 	[NFTA_CONNLIMIT_COUNT]	= { .type = NLA_U32 },
-	[NFTA_CONNLIMIT_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_CONNLIMIT_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_CONNLIMIT_F_INV),
 };
 
 static struct nft_object_type nft_connlimit_obj_type;
diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c
index a8fcb4b6ea1a..00dabd985883 100644
--- a/net/netfilter/nft_ct.c
+++ b/net/netfilter/nft_ct.c
@@ -338,7 +338,7 @@ static void nft_ct_set_eval(const struct nft_expr *expr,
 static const struct nla_policy nft_ct_policy[NFTA_CT_MAX + 1] = {
 	[NFTA_CT_DREG]		= { .type = NLA_U32 },
 	[NFTA_CT_KEY]		= NLA_POLICY_MAX(NLA_BE32, 255),
-	[NFTA_CT_DIRECTION]	= { .type = NLA_U8 },
+	[NFTA_CT_DIRECTION]	= NLA_POLICY_MAX(NLA_U8, IP_CT_DIR_REPLY),
 	[NFTA_CT_SREG]		= { .type = NLA_U32 },
 };
 
diff --git a/net/netfilter/nft_dynset.c b/net/netfilter/nft_dynset.c
index 57bf94ae8724..ee9d3e7b1ecf 100644
--- a/net/netfilter/nft_dynset.c
+++ b/net/netfilter/nft_dynset.c
@@ -163,7 +163,8 @@ static const struct nla_policy nft_dynset_policy[NFTA_DYNSET_MAX + 1] = {
 	[NFTA_DYNSET_SREG_DATA]	= { .type = NLA_U32 },
 	[NFTA_DYNSET_TIMEOUT]	= { .type = NLA_U64 },
 	[NFTA_DYNSET_EXPR]	= { .type = NLA_NESTED },
-	[NFTA_DYNSET_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_DYNSET_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_DYNSET_F_INV |
+						  NFT_DYNSET_F_EXPR),
 	[NFTA_DYNSET_EXPRESSIONS] = { .type = NLA_NESTED },
 };
 
diff --git a/net/netfilter/nft_exthdr.c b/net/netfilter/nft_exthdr.c
index 14d4ad7f518c..b997307d94f9 100644
--- a/net/netfilter/nft_exthdr.c
+++ b/net/netfilter/nft_exthdr.c
@@ -490,7 +490,7 @@ static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
 	[NFTA_EXTHDR_TYPE]		= { .type = NLA_U8 },
 	[NFTA_EXTHDR_OFFSET]		= { .type = NLA_U32 },
 	[NFTA_EXTHDR_LEN]		= NLA_POLICY_MAX(NLA_BE32, 255),
-	[NFTA_EXTHDR_FLAGS]		= { .type = NLA_U32 },
+	[NFTA_EXTHDR_FLAGS]		= NLA_POLICY_MASK(NLA_BE32, NFT_EXTHDR_F_PRESENT),
 	[NFTA_EXTHDR_OP]		= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_EXTHDR_SREG]		= { .type = NLA_U32 },
 };
diff --git a/net/netfilter/nft_inner.c b/net/netfilter/nft_inner.c
index c4569d4b9228..03ffb1159fc1 100644
--- a/net/netfilter/nft_inner.c
+++ b/net/netfilter/nft_inner.c
@@ -321,7 +321,7 @@ static void nft_inner_eval(const struct nft_expr *expr, struct nft_regs *regs,
 
 static const struct nla_policy nft_inner_policy[NFTA_INNER_MAX + 1] = {
 	[NFTA_INNER_NUM]	= { .type = NLA_U32 },
-	[NFTA_INNER_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_INNER_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_INNER_MASK),
 	[NFTA_INNER_HDRSIZE]	= { .type = NLA_U32 },
 	[NFTA_INNER_TYPE]	= { .type = NLA_U32 },
 	[NFTA_INNER_EXPR]	= { .type = NLA_NESTED },
diff --git a/net/netfilter/nft_limit.c b/net/netfilter/nft_limit.c
index f6830621c471..167d99b1447f 100644
--- a/net/netfilter/nft_limit.c
+++ b/net/netfilter/nft_limit.c
@@ -189,7 +189,7 @@ static const struct nla_policy nft_limit_policy[NFTA_LIMIT_MAX + 1] = {
 	[NFTA_LIMIT_UNIT]	= { .type = NLA_U64 },
 	[NFTA_LIMIT_BURST]	= { .type = NLA_U32 },
 	[NFTA_LIMIT_TYPE]	= { .type = NLA_U32 },
-	[NFTA_LIMIT_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_LIMIT_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_LIMIT_F_INV),
 };
 
 static int nft_limit_pkts_init(const struct nft_ctx *ctx,
diff --git a/net/netfilter/nft_log.c b/net/netfilter/nft_log.c
index da0c0d1c9cea..0d868eea6257 100644
--- a/net/netfilter/nft_log.c
+++ b/net/netfilter/nft_log.c
@@ -69,7 +69,7 @@ static const struct nla_policy nft_log_policy[NFTA_LOG_MAX + 1] = {
 	[NFTA_LOG_SNAPLEN]	= { .type = NLA_U32 },
 	[NFTA_LOG_QTHRESHOLD]	= { .type = NLA_U16 },
 	[NFTA_LOG_LEVEL]	= { .type = NLA_U32 },
-	[NFTA_LOG_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_LOG_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NF_LOG_MASK),
 };
 
 static int nft_log_modprobe(struct net *net, enum nf_log_type t)
diff --git a/net/netfilter/nft_osf.c b/net/netfilter/nft_osf.c
index 39ccd67ed265..b2f44bc6bd3f 100644
--- a/net/netfilter/nft_osf.c
+++ b/net/netfilter/nft_osf.c
@@ -14,7 +14,7 @@ struct nft_osf {
 static const struct nla_policy nft_osf_policy[NFTA_OSF_MAX + 1] = {
 	[NFTA_OSF_DREG]		= { .type = NLA_U32 },
 	[NFTA_OSF_TTL]		= { .type = NLA_U8 },
-	[NFTA_OSF_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_OSF_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_OSF_F_VERSION),
 };
 
 static void nft_osf_eval(const struct nft_expr *expr, struct nft_regs *regs,
diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c
index 973d56af03ff..91b62083d942 100644
--- a/net/netfilter/nft_payload.c
+++ b/net/netfilter/nft_payload.c
@@ -216,7 +216,7 @@ static const struct nla_policy nft_payload_policy[NFTA_PAYLOAD_MAX + 1] = {
 	[NFTA_PAYLOAD_LEN]		= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_PAYLOAD_CSUM_TYPE]	= { .type = NLA_U32 },
 	[NFTA_PAYLOAD_CSUM_OFFSET]	= NLA_POLICY_MAX(NLA_BE32, 255),
-	[NFTA_PAYLOAD_CSUM_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_PAYLOAD_CSUM_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_PAYLOAD_L4CSUM_PSEUDOHDR),
 };
 
 static int nft_payload_init(const struct nft_ctx *ctx,
diff --git a/net/netfilter/nft_queue.c b/net/netfilter/nft_queue.c
index 8eb13a02942e..b83d209db886 100644
--- a/net/netfilter/nft_queue.c
+++ b/net/netfilter/nft_queue.c
@@ -95,7 +95,7 @@ static int nft_queue_validate(const struct nft_ctx *ctx,
 static const struct nla_policy nft_queue_policy[NFTA_QUEUE_MAX + 1] = {
 	[NFTA_QUEUE_NUM]	= { .type = NLA_U16 },
 	[NFTA_QUEUE_TOTAL]	= { .type = NLA_U16 },
-	[NFTA_QUEUE_FLAGS]	= { .type = NLA_U16 },
+	[NFTA_QUEUE_FLAGS]	= NLA_POLICY_MASK(NLA_BE16, NFT_QUEUE_FLAG_MASK),
 	[NFTA_QUEUE_SREG_QNUM]	= { .type = NLA_U32 },
 };
 
diff --git a/net/netfilter/nft_quota.c b/net/netfilter/nft_quota.c
index 3be788e5223c..6ed7c4409706 100644
--- a/net/netfilter/nft_quota.c
+++ b/net/netfilter/nft_quota.c
@@ -46,7 +46,7 @@ static inline void nft_quota_do_eval(struct nft_quota *priv,
 
 static const struct nla_policy nft_quota_policy[NFTA_QUOTA_MAX + 1] = {
 	[NFTA_QUOTA_BYTES]	= { .type = NLA_U64 },
-	[NFTA_QUOTA_FLAGS]	= { .type = NLA_U32 },
+	[NFTA_QUOTA_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_QUOTA_F_INV),
 	[NFTA_QUOTA_CONSUMED]	= { .type = NLA_U64 },
 };
 
diff --git a/net/netfilter/nft_synproxy.c b/net/netfilter/nft_synproxy.c
index 8e452a874969..7641f249614c 100644
--- a/net/netfilter/nft_synproxy.c
+++ b/net/netfilter/nft_synproxy.c
@@ -17,8 +17,8 @@ struct nft_synproxy {
 
 static const struct nla_policy nft_synproxy_policy[NFTA_SYNPROXY_MAX + 1] = {
 	[NFTA_SYNPROXY_MSS]		= { .type = NLA_U16 },
-	[NFTA_SYNPROXY_WSCALE]		= { .type = NLA_U8 },
-	[NFTA_SYNPROXY_FLAGS]		= { .type = NLA_U32 },
+	[NFTA_SYNPROXY_WSCALE]		= NLA_POLICY_MAX(NLA_U8, TCP_MAX_WSCALE),
+	[NFTA_SYNPROXY_FLAGS]		= NLA_POLICY_MASK(NLA_BE32, NF_SYNPROXY_OPT_MASK),
 };
 
 static void nft_synproxy_tcp_options(struct synproxy_options *opts,
diff --git a/net/netfilter/nft_tunnel.c b/net/netfilter/nft_tunnel.c
index f5cadba91417..65d06300f48a 100644
--- a/net/netfilter/nft_tunnel.c
+++ b/net/netfilter/nft_tunnel.c
@@ -68,7 +68,7 @@ static void nft_tunnel_get_eval(const struct nft_expr *expr,
 static const struct nla_policy nft_tunnel_policy[NFTA_TUNNEL_MAX + 1] = {
 	[NFTA_TUNNEL_KEY]	= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_TUNNEL_DREG]	= { .type = NLA_U32 },
-	[NFTA_TUNNEL_MODE]	= NLA_POLICY_MAX(NLA_BE32, 255),
+	[NFTA_TUNNEL_MODE]	= NLA_POLICY_MAX(NLA_BE32, NFT_TUNNEL_MODE_MAX),
 };
 
 static int nft_tunnel_get_init(const struct nft_ctx *ctx,
@@ -408,7 +408,7 @@ static const struct nla_policy nft_tunnel_key_policy[NFTA_TUNNEL_KEY_MAX + 1] =
 	[NFTA_TUNNEL_KEY_IP]	= { .type = NLA_NESTED, },
 	[NFTA_TUNNEL_KEY_IP6]	= { .type = NLA_NESTED, },
 	[NFTA_TUNNEL_KEY_ID]	= { .type = NLA_U32, },
-	[NFTA_TUNNEL_KEY_FLAGS]	= { .type = NLA_U32, },
+	[NFTA_TUNNEL_KEY_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_TUNNEL_F_MASK),
 	[NFTA_TUNNEL_KEY_TOS]	= { .type = NLA_U8, },
 	[NFTA_TUNNEL_KEY_TTL]	= { .type = NLA_U8, },
 	[NFTA_TUNNEL_KEY_SPORT]	= { .type = NLA_U16, },
diff --git a/net/netfilter/nft_xfrm.c b/net/netfilter/nft_xfrm.c
index 7ffe6a2690d1..6858cd2d16a4 100644
--- a/net/netfilter/nft_xfrm.c
+++ b/net/netfilter/nft_xfrm.c
@@ -17,8 +17,8 @@
 
 static const struct nla_policy nft_xfrm_policy[NFTA_XFRM_MAX + 1] = {
 	[NFTA_XFRM_KEY]		= NLA_POLICY_MAX(NLA_BE32, 255),
-	[NFTA_XFRM_DIR]		= { .type = NLA_U8 },
-	[NFTA_XFRM_SPNUM]	= NLA_POLICY_MAX(NLA_BE32, 255),
+	[NFTA_XFRM_DIR]		= NLA_POLICY_MAX(NLA_U8, XFRM_POLICY_OUT),
+	[NFTA_XFRM_SPNUM]	= NLA_POLICY_MAX(NLA_BE32, XFRM_MAX_DEPTH - 1),
 	[NFTA_XFRM_DREG]	= { .type = NLA_U32 },
 };
 
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 07/13] netfilter: nf_tables: add netlink policy based cap on registers
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

Should have no effect in practice; all of these use the
nft_parse_register_load/store apis which is mandatory anyway due
to the need to further validate the register load/store, e.g.
that the size argument doesn't result in out-of-bounds load/store.

OTOH this is a simple method to reject obviously wrong input
at earlier stage.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/uapi/linux/netfilter/nf_tables.h | 4 ++++
 net/netfilter/nft_bitwise.c              | 6 +++---
 net/netfilter/nft_byteorder.c            | 4 ++--
 net/netfilter/nft_cmp.c                  | 2 +-
 net/netfilter/nft_ct.c                   | 4 ++--
 net/netfilter/nft_exthdr.c               | 4 ++--
 net/netfilter/nft_fib.c                  | 2 +-
 net/netfilter/nft_hash.c                 | 4 ++--
 net/netfilter/nft_immediate.c            | 2 +-
 net/netfilter/nft_lookup.c               | 4 ++--
 net/netfilter/nft_meta.c                 | 4 ++--
 net/netfilter/nft_numgen.c               | 2 +-
 net/netfilter/nft_objref.c               | 2 +-
 net/netfilter/nft_osf.c                  | 2 +-
 net/netfilter/nft_payload.c              | 4 ++--
 net/netfilter/nft_range.c                | 2 +-
 net/netfilter/nft_rt.c                   | 2 +-
 net/netfilter/nft_socket.c               | 2 +-
 net/netfilter/nft_tunnel.c               | 2 +-
 net/netfilter/nft_xfrm.c                 | 2 +-
 20 files changed, 32 insertions(+), 28 deletions(-)

diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h
index dca9e72b0558..0b708153469c 100644
--- a/include/uapi/linux/netfilter/nf_tables.h
+++ b/include/uapi/linux/netfilter/nf_tables.h
@@ -46,6 +46,10 @@ enum nft_registers {
 };
 #define NFT_REG_MAX	(__NFT_REG_MAX - 1)
 
+#ifdef __KERNEL__
+#define NFT_REG32_MAX	NFT_REG32_15
+#endif
+
 #define NFT_REG_SIZE	16
 #define NFT_REG32_SIZE	4
 #define NFT_REG32_COUNT	(NFT_REG32_15 - NFT_REG32_00 + 1)
diff --git a/net/netfilter/nft_bitwise.c b/net/netfilter/nft_bitwise.c
index a4ff781f334d..13808e9cd999 100644
--- a/net/netfilter/nft_bitwise.c
+++ b/net/netfilter/nft_bitwise.c
@@ -125,9 +125,9 @@ void nft_bitwise_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_bitwise_policy[NFTA_BITWISE_MAX + 1] = {
-	[NFTA_BITWISE_SREG]	= { .type = NLA_U32 },
-	[NFTA_BITWISE_SREG2]	= { .type = NLA_U32 },
-	[NFTA_BITWISE_DREG]	= { .type = NLA_U32 },
+	[NFTA_BITWISE_SREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
+	[NFTA_BITWISE_SREG2]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
+	[NFTA_BITWISE_DREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_BITWISE_LEN]	= { .type = NLA_U32 },
 	[NFTA_BITWISE_MASK]	= { .type = NLA_NESTED },
 	[NFTA_BITWISE_XOR]	= { .type = NLA_NESTED },
diff --git a/net/netfilter/nft_byteorder.c b/net/netfilter/nft_byteorder.c
index 744878773dac..e00dddfa2fc0 100644
--- a/net/netfilter/nft_byteorder.c
+++ b/net/netfilter/nft_byteorder.c
@@ -87,8 +87,8 @@ void nft_byteorder_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_byteorder_policy[NFTA_BYTEORDER_MAX + 1] = {
-	[NFTA_BYTEORDER_SREG]	= { .type = NLA_U32 },
-	[NFTA_BYTEORDER_DREG]	= { .type = NLA_U32 },
+	[NFTA_BYTEORDER_SREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
+	[NFTA_BYTEORDER_DREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_BYTEORDER_OP]	= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_BYTEORDER_LEN]	= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_BYTEORDER_SIZE]	= NLA_POLICY_MAX(NLA_BE32, 255),
diff --git a/net/netfilter/nft_cmp.c b/net/netfilter/nft_cmp.c
index b61dc9c3383e..e085c2a00b70 100644
--- a/net/netfilter/nft_cmp.c
+++ b/net/netfilter/nft_cmp.c
@@ -64,7 +64,7 @@ void nft_cmp_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_cmp_policy[NFTA_CMP_MAX + 1] = {
-	[NFTA_CMP_SREG]		= { .type = NLA_U32 },
+	[NFTA_CMP_SREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_CMP_OP]		= { .type = NLA_U32 },
 	[NFTA_CMP_DATA]		= { .type = NLA_NESTED },
 };
diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c
index 00dabd985883..afa7142c529a 100644
--- a/net/netfilter/nft_ct.c
+++ b/net/netfilter/nft_ct.c
@@ -336,10 +336,10 @@ static void nft_ct_set_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_ct_policy[NFTA_CT_MAX + 1] = {
-	[NFTA_CT_DREG]		= { .type = NLA_U32 },
+	[NFTA_CT_DREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_CT_KEY]		= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_CT_DIRECTION]	= NLA_POLICY_MAX(NLA_U8, IP_CT_DIR_REPLY),
-	[NFTA_CT_SREG]		= { .type = NLA_U32 },
+	[NFTA_CT_SREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 };
 
 #ifdef CONFIG_NF_CONNTRACK_ZONES
diff --git a/net/netfilter/nft_exthdr.c b/net/netfilter/nft_exthdr.c
index b997307d94f9..0407d6f708ae 100644
--- a/net/netfilter/nft_exthdr.c
+++ b/net/netfilter/nft_exthdr.c
@@ -486,13 +486,13 @@ static void nft_exthdr_dccp_eval(const struct nft_expr *expr,
 #endif
 
 static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
-	[NFTA_EXTHDR_DREG]		= { .type = NLA_U32 },
+	[NFTA_EXTHDR_DREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_EXTHDR_TYPE]		= { .type = NLA_U8 },
 	[NFTA_EXTHDR_OFFSET]		= { .type = NLA_U32 },
 	[NFTA_EXTHDR_LEN]		= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_EXTHDR_FLAGS]		= NLA_POLICY_MASK(NLA_BE32, NFT_EXTHDR_F_PRESENT),
 	[NFTA_EXTHDR_OP]		= NLA_POLICY_MAX(NLA_BE32, 255),
-	[NFTA_EXTHDR_SREG]		= { .type = NLA_U32 },
+	[NFTA_EXTHDR_SREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 };
 
 static int nft_exthdr_init(const struct nft_ctx *ctx,
diff --git a/net/netfilter/nft_fib.c b/net/netfilter/nft_fib.c
index f7dc0e54375f..327a5f33659c 100644
--- a/net/netfilter/nft_fib.c
+++ b/net/netfilter/nft_fib.c
@@ -19,7 +19,7 @@
 			NFTA_FIB_F_PRESENT)
 
 const struct nla_policy nft_fib_policy[NFTA_FIB_MAX + 1] = {
-	[NFTA_FIB_DREG]		= { .type = NLA_U32 },
+	[NFTA_FIB_DREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_FIB_RESULT]	= { .type = NLA_U32 },
 	[NFTA_FIB_FLAGS]	=
 		NLA_POLICY_MASK(NLA_BE32, NFTA_FIB_F_ALL),
diff --git a/net/netfilter/nft_hash.c b/net/netfilter/nft_hash.c
index 1cf41e0a0e0c..3bacc9b53789 100644
--- a/net/netfilter/nft_hash.c
+++ b/net/netfilter/nft_hash.c
@@ -58,8 +58,8 @@ static void nft_symhash_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_hash_policy[NFTA_HASH_MAX + 1] = {
-	[NFTA_HASH_SREG]	= { .type = NLA_U32 },
-	[NFTA_HASH_DREG]	= { .type = NLA_U32 },
+	[NFTA_HASH_SREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
+	[NFTA_HASH_DREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_HASH_LEN]		= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_HASH_MODULUS]	= { .type = NLA_U32 },
 	[NFTA_HASH_SEED]	= { .type = NLA_U32 },
diff --git a/net/netfilter/nft_immediate.c b/net/netfilter/nft_immediate.c
index 37c29947b380..1b733c7b1b0e 100644
--- a/net/netfilter/nft_immediate.c
+++ b/net/netfilter/nft_immediate.c
@@ -25,7 +25,7 @@ void nft_immediate_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_immediate_policy[NFTA_IMMEDIATE_MAX + 1] = {
-	[NFTA_IMMEDIATE_DREG]	= { .type = NLA_U32 },
+	[NFTA_IMMEDIATE_DREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_IMMEDIATE_DATA]	= { .type = NLA_NESTED },
 };
 
diff --git a/net/netfilter/nft_lookup.c b/net/netfilter/nft_lookup.c
index e4e619027542..9fafe5afc490 100644
--- a/net/netfilter/nft_lookup.c
+++ b/net/netfilter/nft_lookup.c
@@ -125,8 +125,8 @@ static const struct nla_policy nft_lookup_policy[NFTA_LOOKUP_MAX + 1] = {
 	[NFTA_LOOKUP_SET]	= { .type = NLA_STRING,
 				    .len = NFT_SET_MAXNAMELEN - 1 },
 	[NFTA_LOOKUP_SET_ID]	= { .type = NLA_U32 },
-	[NFTA_LOOKUP_SREG]	= { .type = NLA_U32 },
-	[NFTA_LOOKUP_DREG]	= { .type = NLA_U32 },
+	[NFTA_LOOKUP_SREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
+	[NFTA_LOOKUP_DREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_LOOKUP_FLAGS]	=
 		NLA_POLICY_MASK(NLA_BE32, NFT_LOOKUP_F_INV),
 };
diff --git a/net/netfilter/nft_meta.c b/net/netfilter/nft_meta.c
index d0df6cf374d1..7478063339d4 100644
--- a/net/netfilter/nft_meta.c
+++ b/net/netfilter/nft_meta.c
@@ -460,9 +460,9 @@ void nft_meta_set_eval(const struct nft_expr *expr,
 EXPORT_SYMBOL_GPL(nft_meta_set_eval);
 
 const struct nla_policy nft_meta_policy[NFTA_META_MAX + 1] = {
-	[NFTA_META_DREG]	= { .type = NLA_U32 },
+	[NFTA_META_DREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_META_KEY]		= NLA_POLICY_MAX(NLA_BE32, 255),
-	[NFTA_META_SREG]	= { .type = NLA_U32 },
+	[NFTA_META_SREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 };
 EXPORT_SYMBOL_GPL(nft_meta_policy);
 
diff --git a/net/netfilter/nft_numgen.c b/net/netfilter/nft_numgen.c
index 4d69b3399195..b0c802370159 100644
--- a/net/netfilter/nft_numgen.c
+++ b/net/netfilter/nft_numgen.c
@@ -43,7 +43,7 @@ static void nft_ng_inc_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_ng_policy[NFTA_NG_MAX + 1] = {
-	[NFTA_NG_DREG]		= { .type = NLA_U32 },
+	[NFTA_NG_DREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_NG_MODULUS]	= { .type = NLA_U32 },
 	[NFTA_NG_TYPE]		= { .type = NLA_U32 },
 	[NFTA_NG_OFFSET]	= { .type = NLA_U32 },
diff --git a/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c
index 633cce69568f..249ded517446 100644
--- a/net/netfilter/nft_objref.c
+++ b/net/netfilter/nft_objref.c
@@ -265,7 +265,7 @@ static const struct nla_policy nft_objref_policy[NFTA_OBJREF_MAX + 1] = {
 	[NFTA_OBJREF_IMM_NAME]	= { .type = NLA_STRING,
 				    .len = NFT_OBJ_MAXNAMELEN - 1 },
 	[NFTA_OBJREF_IMM_TYPE]	= { .type = NLA_U32 },
-	[NFTA_OBJREF_SET_SREG]	= { .type = NLA_U32 },
+	[NFTA_OBJREF_SET_SREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_OBJREF_SET_NAME]	= { .type = NLA_STRING,
 				    .len = NFT_SET_MAXNAMELEN - 1 },
 	[NFTA_OBJREF_SET_ID]	= { .type = NLA_U32 },
diff --git a/net/netfilter/nft_osf.c b/net/netfilter/nft_osf.c
index b2f44bc6bd3f..18003433476c 100644
--- a/net/netfilter/nft_osf.c
+++ b/net/netfilter/nft_osf.c
@@ -12,7 +12,7 @@ struct nft_osf {
 };
 
 static const struct nla_policy nft_osf_policy[NFTA_OSF_MAX + 1] = {
-	[NFTA_OSF_DREG]		= { .type = NLA_U32 },
+	[NFTA_OSF_DREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_OSF_TTL]		= { .type = NLA_U8 },
 	[NFTA_OSF_FLAGS]	= NLA_POLICY_MASK(NLA_BE32, NFT_OSF_F_VERSION),
 };
diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c
index 91b62083d942..3fa3c6c835be 100644
--- a/net/netfilter/nft_payload.c
+++ b/net/netfilter/nft_payload.c
@@ -209,8 +209,8 @@ void nft_payload_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_payload_policy[NFTA_PAYLOAD_MAX + 1] = {
-	[NFTA_PAYLOAD_SREG]		= { .type = NLA_U32 },
-	[NFTA_PAYLOAD_DREG]		= { .type = NLA_U32 },
+	[NFTA_PAYLOAD_SREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
+	[NFTA_PAYLOAD_DREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_PAYLOAD_BASE]		= { .type = NLA_U32 },
 	[NFTA_PAYLOAD_OFFSET]		= { .type = NLA_BE32 },
 	[NFTA_PAYLOAD_LEN]		= NLA_POLICY_MAX(NLA_BE32, 255),
diff --git a/net/netfilter/nft_range.c b/net/netfilter/nft_range.c
index cbb02644b836..f8a1641afccf 100644
--- a/net/netfilter/nft_range.c
+++ b/net/netfilter/nft_range.c
@@ -41,7 +41,7 @@ void nft_range_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_range_policy[NFTA_RANGE_MAX + 1] = {
-	[NFTA_RANGE_SREG]		= { .type = NLA_U32 },
+	[NFTA_RANGE_SREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_RANGE_OP]			= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_RANGE_FROM_DATA]		= { .type = NLA_NESTED },
 	[NFTA_RANGE_TO_DATA]		= { .type = NLA_NESTED },
diff --git a/net/netfilter/nft_rt.c b/net/netfilter/nft_rt.c
index ad527f3596c0..e23cd4759851 100644
--- a/net/netfilter/nft_rt.c
+++ b/net/netfilter/nft_rt.c
@@ -103,7 +103,7 @@ void nft_rt_get_eval(const struct nft_expr *expr,
 }
 
 static const struct nla_policy nft_rt_policy[NFTA_RT_MAX + 1] = {
-	[NFTA_RT_DREG]		= { .type = NLA_U32 },
+	[NFTA_RT_DREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_RT_KEY]		= NLA_POLICY_MAX(NLA_BE32, 255),
 };
 
diff --git a/net/netfilter/nft_socket.c b/net/netfilter/nft_socket.c
index c55a1310226a..a146a45d7531 100644
--- a/net/netfilter/nft_socket.c
+++ b/net/netfilter/nft_socket.c
@@ -163,7 +163,7 @@ static void nft_socket_eval(const struct nft_expr *expr,
 
 static const struct nla_policy nft_socket_policy[NFTA_SOCKET_MAX + 1] = {
 	[NFTA_SOCKET_KEY]		= NLA_POLICY_MAX(NLA_BE32, 255),
-	[NFTA_SOCKET_DREG]		= { .type = NLA_U32 },
+	[NFTA_SOCKET_DREG]		= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_SOCKET_LEVEL]		= NLA_POLICY_MAX(NLA_BE32, 255),
 };
 
diff --git a/net/netfilter/nft_tunnel.c b/net/netfilter/nft_tunnel.c
index 65d06300f48a..0b987bc2132a 100644
--- a/net/netfilter/nft_tunnel.c
+++ b/net/netfilter/nft_tunnel.c
@@ -67,7 +67,7 @@ static void nft_tunnel_get_eval(const struct nft_expr *expr,
 
 static const struct nla_policy nft_tunnel_policy[NFTA_TUNNEL_MAX + 1] = {
 	[NFTA_TUNNEL_KEY]	= NLA_POLICY_MAX(NLA_BE32, 255),
-	[NFTA_TUNNEL_DREG]	= { .type = NLA_U32 },
+	[NFTA_TUNNEL_DREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 	[NFTA_TUNNEL_MODE]	= NLA_POLICY_MAX(NLA_BE32, NFT_TUNNEL_MODE_MAX),
 };
 
diff --git a/net/netfilter/nft_xfrm.c b/net/netfilter/nft_xfrm.c
index 6858cd2d16a4..65a75d88e5f0 100644
--- a/net/netfilter/nft_xfrm.c
+++ b/net/netfilter/nft_xfrm.c
@@ -19,7 +19,7 @@ static const struct nla_policy nft_xfrm_policy[NFTA_XFRM_MAX + 1] = {
 	[NFTA_XFRM_KEY]		= NLA_POLICY_MAX(NLA_BE32, 255),
 	[NFTA_XFRM_DIR]		= NLA_POLICY_MAX(NLA_U8, XFRM_POLICY_OUT),
 	[NFTA_XFRM_SPNUM]	= NLA_POLICY_MAX(NLA_BE32, XFRM_MAX_DEPTH - 1),
-	[NFTA_XFRM_DREG]	= { .type = NLA_U32 },
+	[NFTA_XFRM_DREG]	= NLA_POLICY_MAX(NLA_BE32, NFT_REG32_MAX),
 };
 
 struct nft_xfrm {
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 08/13] netfilter: nft_set_pipapo: increment data in one step
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

Since commit e807b13cb3e3 ("nft_set_pipapo: Generalise group size for buckets")
there is no longer a need to increment the data pointer in two steps.
Switch to a single invocation of NFT_PIPAPO_GROUPS_PADDED_SIZE() helper,
like the avx2 implementation.

[ Stefano: Improve commit message ]

Reviewed-by: Stefano Brivio <sbrivio@redhat.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/nft_set_pipapo.c | 4 +---
 net/netfilter/nft_set_pipapo.h | 3 ---
 2 files changed, 1 insertion(+), 6 deletions(-)

diff --git a/net/netfilter/nft_set_pipapo.c b/net/netfilter/nft_set_pipapo.c
index 7fd24e0cc428..50d4a4f04309 100644
--- a/net/netfilter/nft_set_pipapo.c
+++ b/net/netfilter/nft_set_pipapo.c
@@ -452,8 +452,6 @@ static struct nft_pipapo_elem *pipapo_get_slow(const struct nft_pipapo_match *m,
 			pipapo_and_field_buckets_4bit(f, res_map, data);
 		NFT_PIPAPO_GROUP_BITS_ARE_8_OR_4;
 
-		data += f->groups / NFT_PIPAPO_GROUPS_PER_BYTE(f);
-
 		/* Now populate the bitmap for the next field, unless this is
 		 * the last field, in which case return the matched 'ext'
 		 * pointer if any.
@@ -498,7 +496,7 @@ static struct nft_pipapo_elem *pipapo_get_slow(const struct nft_pipapo_match *m,
 		map_index = !map_index;
 		swap(res_map, fill_map);
 
-		data += NFT_PIPAPO_GROUPS_PADDING(f);
+		data += NFT_PIPAPO_GROUPS_PADDED_SIZE(f);
 	}
 
 	__local_unlock_nested_bh(&scratch->bh_lock);
diff --git a/net/netfilter/nft_set_pipapo.h b/net/netfilter/nft_set_pipapo.h
index 9aee9a9eaeb7..b82abb03576e 100644
--- a/net/netfilter/nft_set_pipapo.h
+++ b/net/netfilter/nft_set_pipapo.h
@@ -42,9 +42,6 @@
 /* Fields are padded to 32 bits in input registers */
 #define NFT_PIPAPO_GROUPS_PADDED_SIZE(f)				\
 	(round_up((f)->groups / NFT_PIPAPO_GROUPS_PER_BYTE(f), sizeof(u32)))
-#define NFT_PIPAPO_GROUPS_PADDING(f)					\
-	(NFT_PIPAPO_GROUPS_PADDED_SIZE(f) - (f)->groups /		\
-					    NFT_PIPAPO_GROUPS_PER_BYTE(f))
 
 /* Number of buckets given by 2 ^ n, with n bucket bits */
 #define NFT_PIPAPO_BUCKETS(bb)		(1 << (bb))
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 09/13] netfilter: nft_set_pipapo_avx2: remove redundant loop in lookup_slow
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

nft_pipapo_avx2_lookup_slow will never be used in reality, because the
common sizes are handled by avx2 optimized versions.

However, nft_pipapo_avx2_lookup_slow loops over the data just like the
avx2 functions.  However, _slow doesn't need to do that.

As-is, first loop sets all the right result bits and the next iterations
boil down to 'x = x & x'.  Remove the loop.

Reviewed-by: Stefano Brivio <sbrivio@redhat.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/nft_set_pipapo_avx2.c | 32 ++++++++---------------------
 1 file changed, 9 insertions(+), 23 deletions(-)

diff --git a/net/netfilter/nft_set_pipapo_avx2.c b/net/netfilter/nft_set_pipapo_avx2.c
index 6395982e4d95..dad265807b8b 100644
--- a/net/netfilter/nft_set_pipapo_avx2.c
+++ b/net/netfilter/nft_set_pipapo_avx2.c
@@ -1041,7 +1041,6 @@ static int nft_pipapo_avx2_lookup_8b_16(unsigned long *map, unsigned long *fill,
  * @map:	Previous match result, used as initial bitmap
  * @fill:	Destination bitmap to be filled with current match result
  * @f:		Field, containing lookup and mapping tables
- * @offset:	Ignore buckets before the given index, no bits are filled there
  * @pkt:	Packet data, pointer to input nftables register
  * @first:	If this is the first field, don't source previous result
  * @last:	Last field: stop at the first match and return bit index
@@ -1056,32 +1055,19 @@ static int nft_pipapo_avx2_lookup_8b_16(unsigned long *map, unsigned long *fill,
 static int nft_pipapo_avx2_lookup_slow(const struct nft_pipapo_match *mdata,
 					unsigned long *map, unsigned long *fill,
 					const struct nft_pipapo_field *f,
-					int offset, const u8 *pkt,
+					const u8 *pkt,
 					bool first, bool last)
 {
-	unsigned long bsize = f->bsize;
-	int i, ret = -1, b;
-
 	if (first)
 		pipapo_resmap_init(mdata, map);
 
-	for (i = offset; i < bsize; i++) {
-		if (f->bb == 8)
-			pipapo_and_field_buckets_8bit(f, map, pkt);
-		else
-			pipapo_and_field_buckets_4bit(f, map, pkt);
-		NFT_PIPAPO_GROUP_BITS_ARE_8_OR_4;
-
-		b = pipapo_refill(map, bsize, f->rules, fill, f->mt, last);
+	if (f->bb == 8)
+		pipapo_and_field_buckets_8bit(f, map, pkt);
+	else
+		pipapo_and_field_buckets_4bit(f, map, pkt);
+	NFT_PIPAPO_GROUP_BITS_ARE_8_OR_4;
 
-		if (last)
-			return b;
-
-		if (ret == -1)
-			ret = b / XSAVE_YMM_SIZE;
-	}
-
-	return ret;
+	return pipapo_refill(map, f->bsize, f->rules, fill, f->mt, last);
 }
 
 /**
@@ -1201,7 +1187,7 @@ struct nft_pipapo_elem *pipapo_get_avx2(const struct nft_pipapo_match *m,
 				NFT_SET_PIPAPO_AVX2_LOOKUP(8, 16);
 			} else {
 				ret = nft_pipapo_avx2_lookup_slow(m, res, fill, f,
-								  ret, data,
+								  data,
 								  first, last);
 			}
 		} else {
@@ -1217,7 +1203,7 @@ struct nft_pipapo_elem *pipapo_get_avx2(const struct nft_pipapo_match *m,
 				NFT_SET_PIPAPO_AVX2_LOOKUP(4, 32);
 			} else {
 				ret = nft_pipapo_avx2_lookup_slow(m, res, fill, f,
-								  ret, data,
+								  data,
 								  first, last);
 			}
 		}
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 10/13] netfilter: nft_meta: add double-tagged vlan and pppoe support
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

From: Pablo Neira Ayuso <pablo@netfilter.org>

Currently:

  add rule netdev x y ip saddr 1.1.1.1

does not work with neither double-tagged vlan nor pppoe packets. This is
because the network and transport header offset are not pointing to the
IP and transport protocol headers in the stack.

This patch expands NFT_META_PROTOCOL and NFT_META_L4PROTO to parse
double-tagged vlan and pppoe packets so matching network and transport
header fields becomes possible with the existing userspace generated
bytecode. Note that this parser only supports double-tagged vlan which
is composed of vlan offload + vlan header in the skb payload area for
simplicity.

NFT_META_PROTOCOL is used by bridge and netdev family as an implicit
dependency in the bytecode to match on network header fields.
Similarly, there is also NFT_META_L4PROTO, which is also used as an
implicit dependency when matching on the transport protocol header
fields.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/net/netfilter/nf_tables.h      |  4 ++
 include/net/netfilter/nf_tables_ipv4.h | 17 +++++---
 include/net/netfilter/nf_tables_ipv6.h | 16 +++++---
 net/netfilter/nf_tables_core.c         |  2 +-
 net/netfilter/nft_meta.c               | 54 +++++++++++++++++++++++++-
 net/netfilter/nft_payload.c            |  2 +-
 6 files changed, 82 insertions(+), 13 deletions(-)

diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 66cedcfa338e..2c0173d9309c 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -31,7 +31,9 @@ struct nft_pktinfo {
 	const struct nf_hook_state	*state;
 	u8				flags;
 	u8				tprot;
+	__be16				ethertype;
 	u16				fragoff;
+	u16				nhoff;
 	u16				thoff;
 	u16				inneroff;
 };
@@ -83,6 +85,8 @@ static inline void nft_set_pktinfo_unspec(struct nft_pktinfo *pkt)
 {
 	pkt->flags = 0;
 	pkt->tprot = 0;
+	pkt->ethertype = pkt->skb->protocol;
+	pkt->nhoff = 0;
 	pkt->thoff = 0;
 	pkt->fragoff = 0;
 }
diff --git a/include/net/netfilter/nf_tables_ipv4.h b/include/net/netfilter/nf_tables_ipv4.h
index fcf967286e37..e715405a73cb 100644
--- a/include/net/netfilter/nf_tables_ipv4.h
+++ b/include/net/netfilter/nf_tables_ipv4.h
@@ -12,16 +12,19 @@ static inline void nft_set_pktinfo_ipv4(struct nft_pktinfo *pkt)
 	ip = ip_hdr(pkt->skb);
 	pkt->flags = NFT_PKTINFO_L4PROTO;
 	pkt->tprot = ip->protocol;
+	pkt->ethertype = pkt->skb->protocol;
+	pkt->nhoff = 0;
 	pkt->thoff = ip_hdrlen(pkt->skb);
 	pkt->fragoff = ntohs(ip->frag_off) & IP_OFFSET;
 }
 
-static inline int __nft_set_pktinfo_ipv4_validate(struct nft_pktinfo *pkt)
+static inline int __nft_set_pktinfo_ipv4_validate(struct nft_pktinfo *pkt,
+						  int nhoff)
 {
 	struct iphdr *iph, _iph;
 	u32 len, thoff, skb_len;
 
-	iph = skb_header_pointer(pkt->skb, skb_network_offset(pkt->skb),
+	iph = skb_header_pointer(pkt->skb, skb_network_offset(pkt->skb) + nhoff,
 				 sizeof(*iph), &_iph);
 	if (!iph)
 		return -1;
@@ -31,7 +34,7 @@ static inline int __nft_set_pktinfo_ipv4_validate(struct nft_pktinfo *pkt)
 
 	len = iph_totlen(pkt->skb, iph);
 	thoff = iph->ihl * 4;
-	skb_len = pkt->skb->len - skb_network_offset(pkt->skb);
+	skb_len = pkt->skb->len - skb_network_offset(pkt->skb) - nhoff;
 
 	if (skb_len < len)
 		return -1;
@@ -42,7 +45,9 @@ static inline int __nft_set_pktinfo_ipv4_validate(struct nft_pktinfo *pkt)
 
 	pkt->flags = NFT_PKTINFO_L4PROTO;
 	pkt->tprot = iph->protocol;
-	pkt->thoff = skb_network_offset(pkt->skb) + thoff;
+	pkt->ethertype = pkt->skb->protocol;
+	pkt->nhoff = nhoff;
+	pkt->thoff = skb_network_offset(pkt->skb) + nhoff + thoff;
 	pkt->fragoff = ntohs(iph->frag_off) & IP_OFFSET;
 
 	return 0;
@@ -50,7 +55,7 @@ static inline int __nft_set_pktinfo_ipv4_validate(struct nft_pktinfo *pkt)
 
 static inline void nft_set_pktinfo_ipv4_validate(struct nft_pktinfo *pkt)
 {
-	if (__nft_set_pktinfo_ipv4_validate(pkt) < 0)
+	if (__nft_set_pktinfo_ipv4_validate(pkt, 0) < 0)
 		nft_set_pktinfo_unspec(pkt);
 }
 
@@ -78,6 +83,8 @@ static inline int nft_set_pktinfo_ipv4_ingress(struct nft_pktinfo *pkt)
 	}
 
 	pkt->flags = NFT_PKTINFO_L4PROTO;
+	pkt->ethertype = pkt->skb->protocol;
+	pkt->nhoff = 0;
 	pkt->tprot = iph->protocol;
 	pkt->thoff = thoff;
 	pkt->fragoff = ntohs(iph->frag_off) & IP_OFFSET;
diff --git a/include/net/netfilter/nf_tables_ipv6.h b/include/net/netfilter/nf_tables_ipv6.h
index c53ac00bb974..d7b8c559b795 100644
--- a/include/net/netfilter/nf_tables_ipv6.h
+++ b/include/net/netfilter/nf_tables_ipv6.h
@@ -20,21 +20,23 @@ static inline void nft_set_pktinfo_ipv6(struct nft_pktinfo *pkt)
 
 	pkt->flags = NFT_PKTINFO_L4PROTO;
 	pkt->tprot = protohdr;
+	pkt->ethertype = pkt->skb->protocol;
+	pkt->nhoff = 0;
 	pkt->thoff = thoff;
 	pkt->fragoff = frag_off;
 }
 
-static inline int __nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt)
+static inline int __nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt, int nhoff)
 {
 #if IS_ENABLED(CONFIG_IPV6)
 	unsigned int flags = IP6_FH_F_AUTH;
 	struct ipv6hdr *ip6h, _ip6h;
-	unsigned int thoff = 0;
+	unsigned int thoff = nhoff;
 	unsigned short frag_off;
 	u32 pkt_len, skb_len;
 	int protohdr;
 
-	ip6h = skb_header_pointer(pkt->skb, skb_network_offset(pkt->skb),
+	ip6h = skb_header_pointer(pkt->skb, skb_network_offset(pkt->skb) + nhoff,
 				  sizeof(*ip6h), &_ip6h);
 	if (!ip6h)
 		return -1;
@@ -43,7 +45,7 @@ static inline int __nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt)
 		return -1;
 
 	pkt_len = ipv6_payload_len(pkt->skb, ip6h);
-	skb_len = pkt->skb->len - skb_network_offset(pkt->skb);
+	skb_len = pkt->skb->len - skb_network_offset(pkt->skb) - nhoff;
 	if (pkt_len + sizeof(*ip6h) > skb_len)
 		return -1;
 
@@ -53,6 +55,8 @@ static inline int __nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt)
 
 	pkt->flags = NFT_PKTINFO_L4PROTO;
 	pkt->tprot = protohdr;
+	pkt->ethertype = pkt->skb->protocol;
+	pkt->nhoff = nhoff;
 	pkt->thoff = thoff;
 	pkt->fragoff = frag_off;
 
@@ -64,7 +68,7 @@ static inline int __nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt)
 
 static inline void nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt)
 {
-	if (__nft_set_pktinfo_ipv6_validate(pkt) < 0)
+	if (__nft_set_pktinfo_ipv6_validate(pkt, 0) < 0)
 		nft_set_pktinfo_unspec(pkt);
 }
 
@@ -99,6 +103,8 @@ static inline int nft_set_pktinfo_ipv6_ingress(struct nft_pktinfo *pkt)
 
 	pkt->flags = NFT_PKTINFO_L4PROTO;
 	pkt->tprot = protohdr;
+	pkt->ethertype = pkt->skb->protocol;
+	pkt->nhoff = 0;
 	pkt->thoff = thoff;
 	pkt->fragoff = frag_off;
 
diff --git a/net/netfilter/nf_tables_core.c b/net/netfilter/nf_tables_core.c
index 6557a4018c09..5ddd5b6e135f 100644
--- a/net/netfilter/nf_tables_core.c
+++ b/net/netfilter/nf_tables_core.c
@@ -151,7 +151,7 @@ static bool nft_payload_fast_eval(const struct nft_expr *expr,
 	unsigned char *ptr;
 
 	if (priv->base == NFT_PAYLOAD_NETWORK_HEADER)
-		ptr = skb_network_header(skb);
+		ptr = skb_network_header(skb) + pkt->nhoff;
 	else {
 		if (!(pkt->flags & NFT_PKTINFO_L4PROTO))
 			return false;
diff --git a/net/netfilter/nft_meta.c b/net/netfilter/nft_meta.c
index 7478063339d4..5b25851381e5 100644
--- a/net/netfilter/nft_meta.c
+++ b/net/netfilter/nft_meta.c
@@ -23,6 +23,8 @@
 #include <net/tcp_states.h> /* for TCP_TIME_WAIT */
 #include <net/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
+#include <net/netfilter/nf_tables_ipv4.h>
+#include <net/netfilter/nf_tables_ipv6.h>
 #include <net/netfilter/nft_meta.h>
 #include <net/netfilter/nf_tables_offload.h>
 
@@ -309,6 +311,54 @@ nft_meta_get_eval_sdifname(u32 *dest, const struct nft_pktinfo *pkt)
 	nft_meta_store_ifname(dest, dev);
 }
 
+static void nft_meta_pktinfo_may_update(struct nft_pktinfo *pkt)
+{
+	struct sk_buff *skb = pkt->skb;
+	struct vlan_ethhdr *veth;
+	__be16 ethertype;
+	int nhoff;
+
+	/* Is this an IP packet? Then, skip. */
+	if (pkt->flags)
+		return;
+
+	/* ... else maybe an IP packet over PPPoE or Q-in-Q? */
+	switch (skb->protocol) {
+	case htons(ETH_P_8021Q):
+		if (!pskb_may_pull(skb, skb_mac_offset(skb) + sizeof(*veth)))
+			return;
+
+		veth = (struct vlan_ethhdr *)skb_mac_header(skb);
+		nhoff = VLAN_HLEN;
+		ethertype = veth->h_vlan_encapsulated_proto;
+		break;
+	case htons(ETH_P_PPP_SES):
+		if (!nf_flow_pppoe_proto(skb, &ethertype))
+			return;
+
+		nhoff = PPPOE_SES_HLEN;
+		break;
+	default:
+		return;
+	}
+
+	nhoff += skb_network_offset(skb);
+	switch (ethertype) {
+	case htons(ETH_P_IP):
+		if (__nft_set_pktinfo_ipv4_validate(pkt, nhoff))
+			nft_set_pktinfo_unspec(pkt);
+		break;
+	case htons(ETH_P_IPV6):
+		if (__nft_set_pktinfo_ipv6_validate(pkt, nhoff))
+			nft_set_pktinfo_unspec(pkt);
+		break;
+	default:
+		break;
+	}
+
+	pkt->ethertype = ethertype;
+}
+
 void nft_meta_get_eval(const struct nft_expr *expr,
 		       struct nft_regs *regs,
 		       const struct nft_pktinfo *pkt)
@@ -322,12 +372,14 @@ void nft_meta_get_eval(const struct nft_expr *expr,
 		*dest = skb->len;
 		break;
 	case NFT_META_PROTOCOL:
-		nft_reg_store16(dest, (__force u16)skb->protocol);
+		nft_meta_pktinfo_may_update((struct nft_pktinfo *)pkt);
+		nft_reg_store16(dest, (__force u16)pkt->ethertype);
 		break;
 	case NFT_META_NFPROTO:
 		nft_reg_store8(dest, nft_pf(pkt));
 		break;
 	case NFT_META_L4PROTO:
+		nft_meta_pktinfo_may_update((struct nft_pktinfo *)pkt);
 		if (!(pkt->flags & NFT_PKTINFO_L4PROTO))
 			goto err;
 		nft_reg_store8(dest, pkt->tprot);
diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c
index 3fa3c6c835be..01e13e5255a9 100644
--- a/net/netfilter/nft_payload.c
+++ b/net/netfilter/nft_payload.c
@@ -183,7 +183,7 @@ void nft_payload_eval(const struct nft_expr *expr,
 		offset = skb_mac_header(skb) - skb->data;
 		break;
 	case NFT_PAYLOAD_NETWORK_HEADER:
-		offset = skb_network_offset(skb);
+		offset = skb_network_offset(skb) + pkt->nhoff;
 		break;
 	case NFT_PAYLOAD_TRANSPORT_HEADER:
 		if (!(pkt->flags & NFT_PKTINFO_L4PROTO) || pkt->fragoff)
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 11/13] netfilter: nf_conntrack_h323: Correct indentation when H323_TRACE defined
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

From: David Laight <david.laight.linux@gmail.com>

The trace lines are indented using PRINT("%*.s", xx, " ").
Userspace will treat this as "%*.0s" and will output no characters
when 'xx' is zero, the kernel treats it as "%*s" and will output
a single ' ' - which is probably what is intended.

Change all the formats to "%*s" removing the default precision.
This gives a single space indent when level is zero.

Signed-off-by: David Laight <david.laight.linux@gmail.com>
Reviewed-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/nf_conntrack_h323_asn1.c | 38 +++++++++++++-------------
 1 file changed, 19 insertions(+), 19 deletions(-)

diff --git a/net/netfilter/nf_conntrack_h323_asn1.c b/net/netfilter/nf_conntrack_h323_asn1.c
index 09e0f724644f..6830c9da3507 100644
--- a/net/netfilter/nf_conntrack_h323_asn1.c
+++ b/net/netfilter/nf_conntrack_h323_asn1.c
@@ -274,7 +274,7 @@ static unsigned int get_uint(struct bitstr *bs, int b)
 static int decode_nul(struct bitstr *bs, const struct field_t *f,
                       char *base, int level)
 {
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	return H323_ERROR_NONE;
 }
@@ -282,7 +282,7 @@ static int decode_nul(struct bitstr *bs, const struct field_t *f,
 static int decode_bool(struct bitstr *bs, const struct field_t *f,
                        char *base, int level)
 {
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	INC_BIT(bs);
 	if (nf_h323_error_boundary(bs, 0, 0))
@@ -295,7 +295,7 @@ static int decode_oid(struct bitstr *bs, const struct field_t *f,
 {
 	int len;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	BYTE_ALIGN(bs);
 	if (nf_h323_error_boundary(bs, 1, 0))
@@ -314,7 +314,7 @@ static int decode_int(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s", level * TAB_SIZE, " ", f->name);
 
 	switch (f->sz) {
 	case BYTE:		/* Range == 256 */
@@ -361,7 +361,7 @@ static int decode_int(struct bitstr *bs, const struct field_t *f,
 static int decode_enum(struct bitstr *bs, const struct field_t *f,
                        char *base, int level)
 {
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	if ((f->attr & EXT) && get_bit(bs)) {
 		INC_BITS(bs, 7);
@@ -379,7 +379,7 @@ static int decode_bitstr(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	BYTE_ALIGN(bs);
 	switch (f->sz) {
@@ -415,7 +415,7 @@ static int decode_numstr(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	/* 2 <= Range <= 255 */
 	if (nf_h323_error_boundary(bs, 0, f->sz))
@@ -435,7 +435,7 @@ static int decode_octstr(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s", level * TAB_SIZE, " ", f->name);
 
 	switch (f->sz) {
 	case FIXD:		/* Range == 1 */
@@ -483,7 +483,7 @@ static int decode_bmpstr(struct bitstr *bs, const struct field_t *f,
 {
 	unsigned int len;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	switch (f->sz) {
 	case BYTE:		/* Range == 256 */
@@ -515,7 +515,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 	const struct field_t *son;
 	unsigned char *beg = NULL;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	/* Decode? */
 	base = (base && (f->attr & DECODE)) ? base + f->offset : NULL;
@@ -537,7 +537,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 	/* Decode the root components */
 	for (i = opt = 0, son = f->fields; i < f->lb; i++, son++) {
 		if (son->attr & STOP) {
-			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
+			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
 			      son->name);
 			return H323_ERROR_STOP;
 		}
@@ -555,7 +555,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 			if (nf_h323_error_boundary(bs, len, 0))
 				return H323_ERROR_BOUND;
 			if (!base || !(son->attr & DECODE)) {
-				PRINT("%*.s%s\n", (level + 1) * TAB_SIZE,
+				PRINT("%*s%s\n", (level + 1) * TAB_SIZE,
 				      " ", son->name);
 				bs->cur += len;
 				continue;
@@ -608,7 +608,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 		}
 
 		if (son->attr & STOP) {
-			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
+			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
 			      son->name);
 			return H323_ERROR_STOP;
 		}
@@ -622,7 +622,7 @@ static int decode_seq(struct bitstr *bs, const struct field_t *f,
 		if (nf_h323_error_boundary(bs, len, 0))
 			return H323_ERROR_BOUND;
 		if (!base || !(son->attr & DECODE)) {
-			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
+			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
 			      son->name);
 			bs->cur += len;
 			continue;
@@ -648,7 +648,7 @@ static int decode_seqof(struct bitstr *bs, const struct field_t *f,
 	const struct field_t *son;
 	unsigned char *beg = NULL;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	/* Decode? */
 	base = (base && (f->attr & DECODE)) ? base + f->offset : NULL;
@@ -703,7 +703,7 @@ static int decode_seqof(struct bitstr *bs, const struct field_t *f,
 			if (nf_h323_error_boundary(bs, len, 0))
 				return H323_ERROR_BOUND;
 			if (!base || !(son->attr & DECODE)) {
-				PRINT("%*.s%s\n", (level + 1) * TAB_SIZE,
+				PRINT("%*s%s\n", (level + 1) * TAB_SIZE,
 				      " ", son->name);
 				bs->cur += len;
 				continue;
@@ -744,7 +744,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f,
 	const struct field_t *son;
 	unsigned char *beg = NULL;
 
-	PRINT("%*.s%s\n", level * TAB_SIZE, " ", f->name);
+	PRINT("%*s%s\n", level * TAB_SIZE, " ", f->name);
 
 	/* Decode? */
 	base = (base && (f->attr & DECODE)) ? base + f->offset : NULL;
@@ -785,7 +785,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f,
 	/* Transfer to son level */
 	son = &f->fields[type];
 	if (son->attr & STOP) {
-		PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ", son->name);
+		PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ", son->name);
 		return H323_ERROR_STOP;
 	}
 
@@ -797,7 +797,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f,
 		if (nf_h323_error_boundary(bs, len, 0))
 			return H323_ERROR_BOUND;
 		if (!base || !(son->attr & DECODE)) {
-			PRINT("%*.s%s\n", (level + 1) * TAB_SIZE, " ",
+			PRINT("%*s%s\n", (level + 1) * TAB_SIZE, " ",
 			      son->name);
 			bs->cur += len;
 			return H323_ERROR_NONE;
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 12/13] netfilter: nf_tables_offload: add nft_flow_action_entry_next() and use it
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

From: Pablo Neira Ayuso <pablo@netfilter.org>

Add a new helper function to retrieve the next action entry in flow
rule, check if the maximum number of actions is reached, bail out in
such case.

Replace existing opencoded iteration on the action array by this
helper function.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/net/netfilter/nf_tables_offload.h | 10 ++++++++++
 net/netfilter/nf_dup_netdev.c             |  5 ++++-
 net/netfilter/nft_immediate.c             |  4 +++-
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/include/net/netfilter/nf_tables_offload.h b/include/net/netfilter/nf_tables_offload.h
index 3568b6a2f5f0..14c427891ee6 100644
--- a/include/net/netfilter/nf_tables_offload.h
+++ b/include/net/netfilter/nf_tables_offload.h
@@ -67,6 +67,16 @@ struct nft_flow_rule {
 	struct flow_rule	*rule;
 };
 
+static inline struct flow_action_entry *
+nft_flow_action_entry_next(struct nft_offload_ctx *ctx,
+			   struct nft_flow_rule *flow)
+{
+	if (unlikely(ctx->num_actions >= flow->rule->action.num_entries))
+		return NULL;
+
+	return &flow->rule->action.entries[ctx->num_actions++];
+}
+
 void nft_flow_rule_set_addr_type(struct nft_flow_rule *flow,
 				 enum flow_dissector_key_id addr_type);
 
diff --git a/net/netfilter/nf_dup_netdev.c b/net/netfilter/nf_dup_netdev.c
index fab8b9011098..e348fb90b8dc 100644
--- a/net/netfilter/nf_dup_netdev.c
+++ b/net/netfilter/nf_dup_netdev.c
@@ -95,7 +95,10 @@ int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
 	if (!dev)
 		return -EOPNOTSUPP;
 
-	entry = &flow->rule->action.entries[ctx->num_actions++];
+	entry = nft_flow_action_entry_next(ctx, flow);
+	if (!entry)
+		return -E2BIG;
+
 	entry->id = id;
 	entry->dev = dev;
 
diff --git a/net/netfilter/nft_immediate.c b/net/netfilter/nft_immediate.c
index 1b733c7b1b0e..d00eb2eb30e4 100644
--- a/net/netfilter/nft_immediate.c
+++ b/net/netfilter/nft_immediate.c
@@ -279,7 +279,9 @@ static int nft_immediate_offload_verdict(struct nft_offload_ctx *ctx,
 	struct flow_action_entry *entry;
 	const struct nft_data *data;
 
-	entry = &flow->rule->action.entries[ctx->num_actions++];
+	entry = nft_flow_action_entry_next(ctx, flow);
+	if (!entry)
+		return -E2BIG;
 
 	data = &priv->data;
 	switch (data->verdict.code) {
-- 
2.52.0


^ permalink raw reply related

* [PATCH net-next 13/13] netfilter: ctnetlink: restrict expectfn to helper
From: Florian Westphal @ 2026-04-07 14:15 UTC (permalink / raw)
  To: netdev
  Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
	netfilter-devel, pablo
In-Reply-To: <20260407141540.11549-1-fw@strlen.de>

From: Pablo Neira Ayuso <pablo@netfilter.org>

The expectfn callback determines how nf_nat_setup_info() is invoked for
this expectation.

This patch restricts expectfn to the master conntrack helper, there is
nf_nat_follow_master() that is used by most expectations to deal with
nat. However, sip and h.323 helpers still offer their own variants for
different purpose.

Add a new helper field to struct nf_ct_helper_expectfn to restrict the
expectfn to its helper. If NULL, then this can be used by any
expectation, which is the case nf_nat_follow_master().

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/net/netfilter/nf_conntrack_helper.h | 3 ++-
 net/ipv4/netfilter/nf_nat_h323.c            | 2 ++
 net/netfilter/nf_conntrack_helper.c         | 5 +++--
 net/netfilter/nf_conntrack_netlink.c        | 2 +-
 net/netfilter/nf_nat_sip.c                  | 1 +
 5 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h
index de2f956abf34..dc566921cc73 100644
--- a/include/net/netfilter/nf_conntrack_helper.h
+++ b/include/net/netfilter/nf_conntrack_helper.h
@@ -145,6 +145,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, struct nf_conn *ct,
 
 struct nf_ct_helper_expectfn {
 	struct list_head head;
+	const char *helper;
 	const char *name;
 	void (*expectfn)(struct nf_conn *ct, struct nf_conntrack_expect *exp);
 };
@@ -156,7 +157,7 @@ void nf_ct_helper_log(struct sk_buff *skb, const struct nf_conn *ct,
 void nf_ct_helper_expectfn_register(struct nf_ct_helper_expectfn *n);
 void nf_ct_helper_expectfn_unregister(struct nf_ct_helper_expectfn *n);
 struct nf_ct_helper_expectfn *
-nf_ct_helper_expectfn_find_by_name(const char *name);
+nf_ct_helper_expectfn_find_by_name(const char *helper, const char *name);
 struct nf_ct_helper_expectfn *
 nf_ct_helper_expectfn_find_by_symbol(const void *symbol);
 
diff --git a/net/ipv4/netfilter/nf_nat_h323.c b/net/ipv4/netfilter/nf_nat_h323.c
index faee20af4856..21353623130c 100644
--- a/net/ipv4/netfilter/nf_nat_h323.c
+++ b/net/ipv4/netfilter/nf_nat_h323.c
@@ -518,11 +518,13 @@ static int nat_callforwarding(struct sk_buff *skb, struct nf_conn *ct,
 }
 
 static struct nf_ct_helper_expectfn q931_nat = {
+	.helper		= "RAS",
 	.name		= "Q.931",
 	.expectfn	= ip_nat_q931_expect,
 };
 
 static struct nf_ct_helper_expectfn callforwarding_nat = {
+	.helper		= "Q.931",
 	.name		= "callforwarding",
 	.expectfn	= ip_nat_callforwarding_expect,
 };
diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c
index a715304a53d8..5e6d2687a558 100644
--- a/net/netfilter/nf_conntrack_helper.c
+++ b/net/netfilter/nf_conntrack_helper.c
@@ -285,13 +285,14 @@ EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_unregister);
 
 /* Caller should hold the rcu lock */
 struct nf_ct_helper_expectfn *
-nf_ct_helper_expectfn_find_by_name(const char *name)
+nf_ct_helper_expectfn_find_by_name(const char *helper, const char *name)
 {
 	struct nf_ct_helper_expectfn *cur;
 	bool found = false;
 
 	list_for_each_entry_rcu(cur, &nf_ct_helper_expectfn_list, head) {
-		if (!strcmp(cur->name, name)) {
+		if ((cur->helper && !strcmp(cur->helper, helper)) ||
+		    !strcmp(cur->name, name)) {
 			found = true;
 			break;
 		}
diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
index eda5fe4a75c8..7744f67a0fbe 100644
--- a/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -3552,7 +3552,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
 		const char *name = nla_data(cda[CTA_EXPECT_FN]);
 		struct nf_ct_helper_expectfn *expfn;
 
-		expfn = nf_ct_helper_expectfn_find_by_name(name);
+		expfn = nf_ct_helper_expectfn_find_by_name(helper->name, name);
 		if (expfn == NULL) {
 			err = -EINVAL;
 			goto err_out;
diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c
index cf4aeb299bde..047733012963 100644
--- a/net/netfilter/nf_nat_sip.c
+++ b/net/netfilter/nf_nat_sip.c
@@ -641,6 +641,7 @@ static unsigned int nf_nat_sdp_media(struct sk_buff *skb, unsigned int protoff,
 }
 
 static struct nf_ct_helper_expectfn sip_nat = {
+	.helper		= "sip",
 	.name		= "sip",
 	.expectfn	= nf_nat_sip_expected,
 };
-- 
2.52.0


^ permalink raw reply related

* Re: [PATCH net-next 13/13] netfilter: ctnetlink: restrict expectfn to helper
From: Florian Westphal @ 2026-04-07 14:27 UTC (permalink / raw)
  To: pablo; +Cc: netdev
In-Reply-To: <20260407141540.11549-14-fw@strlen.de>

Florian Westphal <fw@strlen.de> wrote:
>  	list_for_each_entry_rcu(cur, &nf_ct_helper_expectfn_list, head) {
> -		if (!strcmp(cur->name, name)) {
> +		if ((cur->helper && !strcmp(cur->helper, helper)) ||
> +		    !strcmp(cur->name, name)) {

Sigh, I don't know why I did not see this earlier.  It looks wrong.

Should this be:

if ((cur->helper && strcmp(cur->helper, helper))
	continue; // skip, name doesn't match

if (!strcmp(cur->name, name)) {
 ...

as is, this restriction has no effect in case the requested
name matches?

AI suggests

if ((cur->helper && !strcmp(cur->helper, helper)) &&
   !strcmp(cur->name, name)) {

... but i think thats bogus too.  What to do?

Send v2 or do you want to followup later?

^ permalink raw reply

* [PATCH net-next] codel: annotate data-races in codel_dump_stats()
From: Eric Dumazet @ 2026-04-07 14:30 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, Jamal Hadi Salim, Jiri Pirko, netdev, eric.dumazet,
	Eric Dumazet

codel_dump_stats() only runs with RTNL held,
reading fields that can be changed in qdisc fast path.

Add READ_ONCE()/WRITE_ONCE() annotations.

Alternative would be to acquire the qdisc spinlock, but our long-term
goal is to make qdisc dump operations lockless as much as we can.

tc_codel_xstats fields don't need to be latched atomically,
otherwise this bug would have been caught earlier.

No change in kernel size:

$ scripts/bloat-o-meter -t vmlinux.0 vmlinux
add/remove: 0/0 grow/shrink: 1/1 up/down: 3/-1 (2)
Function                                     old     new   delta
codel_qdisc_dequeue                         2462    2465      +3
codel_dump_stats                             250     249      -1
Total: Before=29739919, After=29739921, chg +0.00%

Fixes: 76e3cc126bb2 ("codel: Controlled Delay AQM")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
net-next tree has been chosen to avoid a merge conflict.
I will handle stable submission, this bug is minor.

 include/net/codel_impl.h | 45 +++++++++++++++++++++-------------------
 net/sched/sch_codel.c    | 22 ++++++++++----------
 2 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/include/net/codel_impl.h b/include/net/codel_impl.h
index b2c359c6dd1b84e3aeb77834272641bac73f8e6a..2c1f0ec309e9fe02e79d377129da6c5c0cbce19b 100644
--- a/include/net/codel_impl.h
+++ b/include/net/codel_impl.h
@@ -120,10 +120,10 @@ static bool codel_should_drop(const struct sk_buff *skb,
 	}
 
 	skb_len = skb_len_func(skb);
-	vars->ldelay = now - skb_time_func(skb);
+	WRITE_ONCE(vars->ldelay, now - skb_time_func(skb));
 
 	if (unlikely(skb_len > stats->maxpacket))
-		stats->maxpacket = skb_len;
+		WRITE_ONCE(stats->maxpacket, skb_len);
 
 	if (codel_time_before(vars->ldelay, params->target) ||
 	    *backlog <= params->mtu) {
@@ -159,7 +159,7 @@ static struct sk_buff *codel_dequeue(void *ctx,
 
 	if (!skb) {
 		vars->first_above_time = 0;
-		vars->dropping = false;
+		WRITE_ONCE(vars->dropping, false);
 		return skb;
 	}
 	now = codel_get_time();
@@ -168,7 +168,7 @@ static struct sk_buff *codel_dequeue(void *ctx,
 	if (vars->dropping) {
 		if (!drop) {
 			/* sojourn time below target - leave dropping state */
-			vars->dropping = false;
+			WRITE_ONCE(vars->dropping, false);
 		} else if (codel_time_after_eq(now, vars->drop_next)) {
 			/* It's time for the next drop. Drop the current
 			 * packet and dequeue the next. The dequeue might
@@ -180,16 +180,18 @@ static struct sk_buff *codel_dequeue(void *ctx,
 			 */
 			while (vars->dropping &&
 			       codel_time_after_eq(now, vars->drop_next)) {
-				vars->count++; /* dont care of possible wrap
-						* since there is no more divide
-						*/
+				/* dont care of possible wrap
+				 * since there is no more divide.
+				 */
+				WRITE_ONCE(vars->count, vars->count + 1);
 				codel_Newton_step(vars);
 				if (params->ecn && INET_ECN_set_ce(skb)) {
-					stats->ecn_mark++;
-					vars->drop_next =
+					WRITE_ONCE(stats->ecn_mark,
+						   stats->ecn_mark + 1);
+					WRITE_ONCE(vars->drop_next,
 						codel_control_law(vars->drop_next,
 								  params->interval,
-								  vars->rec_inv_sqrt);
+								  vars->rec_inv_sqrt));
 					goto end;
 				}
 				stats->drop_len += skb_len_func(skb);
@@ -202,13 +204,13 @@ static struct sk_buff *codel_dequeue(void *ctx,
 						       skb_time_func,
 						       backlog, now)) {
 					/* leave dropping state */
-					vars->dropping = false;
+					WRITE_ONCE(vars->dropping, false);
 				} else {
 					/* and schedule the next drop */
-					vars->drop_next =
+					WRITE_ONCE(vars->drop_next,
 						codel_control_law(vars->drop_next,
 								  params->interval,
-								  vars->rec_inv_sqrt);
+								  vars->rec_inv_sqrt));
 				}
 			}
 		}
@@ -216,7 +218,7 @@ static struct sk_buff *codel_dequeue(void *ctx,
 		u32 delta;
 
 		if (params->ecn && INET_ECN_set_ce(skb)) {
-			stats->ecn_mark++;
+			WRITE_ONCE(stats->ecn_mark, stats->ecn_mark + 1);
 		} else {
 			stats->drop_len += skb_len_func(skb);
 			drop_func(skb, ctx);
@@ -227,7 +229,7 @@ static struct sk_buff *codel_dequeue(void *ctx,
 						 stats, skb_len_func,
 						 skb_time_func, backlog, now);
 		}
-		vars->dropping = true;
+		WRITE_ONCE(vars->dropping, true);
 		/* if min went above target close to when we last went below it
 		 * assume that the drop rate that controlled the queue on the
 		 * last cycle is a good starting point to control it now.
@@ -236,19 +238,20 @@ static struct sk_buff *codel_dequeue(void *ctx,
 		if (delta > 1 &&
 		    codel_time_before(now - vars->drop_next,
 				      16 * params->interval)) {
-			vars->count = delta;
+			WRITE_ONCE(vars->count, delta);
 			/* we dont care if rec_inv_sqrt approximation
 			 * is not very precise :
 			 * Next Newton steps will correct it quadratically.
 			 */
 			codel_Newton_step(vars);
 		} else {
-			vars->count = 1;
+			WRITE_ONCE(vars->count, 1);
 			vars->rec_inv_sqrt = ~0U >> REC_INV_SQRT_SHIFT;
 		}
-		vars->lastcount = vars->count;
-		vars->drop_next = codel_control_law(now, params->interval,
-						    vars->rec_inv_sqrt);
+		WRITE_ONCE(vars->lastcount, vars->count);
+		WRITE_ONCE(vars->drop_next,
+			   codel_control_law(now, params->interval,
+					     vars->rec_inv_sqrt));
 	}
 end:
 	if (skb && codel_time_after(vars->ldelay, params->ce_threshold)) {
@@ -262,7 +265,7 @@ static struct sk_buff *codel_dequeue(void *ctx,
 				   params->ce_threshold_selector));
 		}
 		if (set_ce && INET_ECN_set_ce(skb))
-			stats->ce_mark++;
+			WRITE_ONCE(stats->ce_mark, stats->ce_mark + 1);
 	}
 	return skb;
 }
diff --git a/net/sched/sch_codel.c b/net/sched/sch_codel.c
index dc2be90666ffbd715550800790a0091acd28701d..317aae0ec7bd6aedb4bae09b18423c981fed16e7 100644
--- a/net/sched/sch_codel.c
+++ b/net/sched/sch_codel.c
@@ -85,7 +85,7 @@ static int codel_qdisc_enqueue(struct sk_buff *skb, struct Qdisc *sch,
 		return qdisc_enqueue_tail(skb, sch);
 	}
 	q = qdisc_priv(sch);
-	q->drop_overlimit++;
+	WRITE_ONCE(q->drop_overlimit, q->drop_overlimit + 1);
 	return qdisc_drop_reason(skb, sch, to_free, QDISC_DROP_OVERLIMIT);
 }
 
@@ -221,18 +221,18 @@ static int codel_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
 {
 	const struct codel_sched_data *q = qdisc_priv(sch);
 	struct tc_codel_xstats st = {
-		.maxpacket	= q->stats.maxpacket,
-		.count		= q->vars.count,
-		.lastcount	= q->vars.lastcount,
-		.drop_overlimit = q->drop_overlimit,
-		.ldelay		= codel_time_to_us(q->vars.ldelay),
-		.dropping	= q->vars.dropping,
-		.ecn_mark	= q->stats.ecn_mark,
-		.ce_mark	= q->stats.ce_mark,
+		.maxpacket	= READ_ONCE(q->stats.maxpacket),
+		.count		= READ_ONCE(q->vars.count),
+		.lastcount	= READ_ONCE(q->vars.lastcount),
+		.drop_overlimit = READ_ONCE(q->drop_overlimit),
+		.ldelay		= codel_time_to_us(READ_ONCE(q->vars.ldelay)),
+		.dropping	= READ_ONCE(q->vars.dropping),
+		.ecn_mark	= READ_ONCE(q->stats.ecn_mark),
+		.ce_mark	= READ_ONCE(q->stats.ce_mark),
 	};
 
-	if (q->vars.dropping) {
-		codel_tdiff_t delta = q->vars.drop_next - codel_get_time();
+	if (st.dropping) {
+		codel_tdiff_t delta = READ_ONCE(q->vars.drop_next) - codel_get_time();
 
 		if (delta >= 0)
 			st.drop_next = codel_time_to_us(delta);
-- 
2.53.0.1213.gd9a14994de-goog


^ permalink raw reply related

* [PATCH net-next] vsock/virtio: remove unnecessary call to `virtio_transport_get_ops`
From: Luigi Leonardi @ 2026-04-07 14:31 UTC (permalink / raw)
  To: Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
	Stefan Hajnoczi, Stefano Garzarella, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Arseniy Krasnov
  Cc: kvm, virtualization, netdev, linux-kernel, Luigi Leonardi

`virtio_transport_send_pkt_info` gets all the transport information
from the parameter `t_ops`. There is no need to call
`virtio_transport_get_ops()`.

Remove it.

Fixes: 581512a6dc93 ("vsock/virtio: MSG_ZEROCOPY flag support")
Signed-off-by: Luigi Leonardi <leonardi@redhat.com>
---
I marked this as net-next material, but honsetly I'm not sure if I
should have targeted net. It's not a bug after all, it's just a cleanup.
---
 net/vmw_vsock/virtio_transport_common.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 8a9fb23c6e85..a152a9e208d0 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -60,8 +60,6 @@ static bool virtio_transport_can_zcopy(const struct virtio_transport *t_ops,
 		return false;
 
 	/* Check that transport can send data in zerocopy mode. */
-	t_ops = virtio_transport_get_ops(info->vsk);
-
 	if (t_ops->can_msgzerocopy) {
 		int pages_to_send = iov_iter_npages(iov_iter, MAX_SKB_FRAGS);
 

---
base-commit: bfe62a454542cfad3379f6ef5680b125f41e20f4
change-id: 20260407-remove_parameter-f61a3e40cf90

Best regards,
-- 
Luigi Leonardi <leonardi@redhat.com>


^ permalink raw reply related

* Re: [RFC bpf] Ingress RX queue provenance across cpumap redirect
From: Alexei Starovoitov @ 2026-04-07 14:47 UTC (permalink / raw)
  To: Adith-Joshua
  Cc: bpf, Network Development, Alexei Starovoitov, Daniel Borkmann,
	Andrii Nakryiko, Jakub Kicinski, Jesper Dangaard Brouer,
	John Fastabend
In-Reply-To: <20260407060203.3391-1-adithalex29@gmail.com>

On Mon, Apr 6, 2026 at 11:02 PM Adith-Joshua <adithalex29@gmail.com> wrote:
>
>
> ## Follow-up
>
> If this aligns with intended design, I would be happy to explore a concrete
> proposal or implementation.

No "follow up" please.
STOP sending AI slop to the mailing list.

^ permalink raw reply

* [PATCH 5/8] net: define IPPROTO_SMBDIRECT and SOL_SMBDIRECT constants
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
  To: linux-cifs, samba-technical
  Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
	David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
	Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
	linux-kernel
In-Reply-To: <cover.1775571957.git.metze@samba.org>

This patch adds IPPROTO_SMBDIRECT and SOL_SMBDIRECT constants to the
networking subsystem. These definitions are essential for applications
to set socket options and protocol identifiers related to the SMBDIRECT
protocol, defined in [MS-SMBD] by Microsoft. It is used as wrapper
around RDMA in order to provide a transport for SMB3, but Microsoft also
uses it as transport for other protocols.

SMBDIRECT works over Infiniband, RoCE and iWarp.
RoCEv2 is based on IP/UDP and iWarp is based on IP/TCP,
so these use IP addresses natively.
Infiniband and RoCEv1 require IPOIB in order to be used for
SMBDIRECT.

So instead of adding a PF_SMBDIRECT, which would only use AF_INET[6],
we use IPPROTO_SMBDIRECT instead, this uses a number not
allocated from IANA, as it would not appear in an IP header.

This is similar to IPPROTO_SMC, IPPROTO_MPTCP and IPPROTO_QUIC,
which are linux specific values for the socket() syscall.

  socket(AF_INET, SOCK_STREAM, IPPROTO_SMBDIRECT);
  socket(AF_INET6, SOCK_STREAM, IPPROTO_SMBDIRECT);

This will allow the existing smbdirect code used by
cifs.ko and ksmbd.ko to be moved behind the socket layer,
so that there's less special handling. Only sock_sendmsg()
sock_recvmsg() are used, so that the main stream handling
is done all the same for tcp, smbdirect and later also quic.

The special RDMA read/write handling will be via direct
function calls as they are currently done for the in kernel
consumers.

As a start __sock_create(kern=0)/sk->sk_kern_sock == 0 will
still cause a -EPROTONOSUPPORT. So only in kernel consumers
will be supported for now.

Once I have developed a stable interface for the RDMA read/write
handling using sendmsg/recvmsg with MSG_OOB and msg_control,
it will also exposed to userspace in order to allow Samba to
use it.

As the numbers of IPPROTO_QUIC (261) and SOL_QUIC (288) [1]
are already used in various (released) userspace applications,
I used 289 for SOL_SMBDIRECT instead of 288.

[1]
https://lore.kernel.org/quic/0cb58f6fcf35ac988660e42704dae9960744a0a7.1763994509.git.lucien.xin@gmail.com/T/#u

Cc: Steve French <smfrench@gmail.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Long Li <longli@microsoft.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Henrique Carvalho <henrique.carvalho@suse.com>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: David S. Miller <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Cc: Willem de Bruijn <willemb@google.com>
Cc: netdev@vger.kernel.org
Cc: Xin Long <lucien.xin@gmail.com>
Cc: quic@lists.linux.dev
Cc: linux-rdma@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
 include/linux/socket.h  | 2 ++
 include/uapi/linux/in.h | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/include/linux/socket.h b/include/linux/socket.h
index ec715ad4bf25..e00cbfdaa8d6 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -401,6 +401,8 @@ struct ucred {
 #define SOL_MCTP	285
 #define SOL_SMC		286
 #define SOL_VSOCK	287
+/* 288 reserved for SOL_QUIC */
+#define SOL_SMBDIRECT	289
 
 /* IPX options */
 #define IPX_TYPE	1
diff --git a/include/uapi/linux/in.h b/include/uapi/linux/in.h
index ced0fc3c3aa5..933d243b1780 100644
--- a/include/uapi/linux/in.h
+++ b/include/uapi/linux/in.h
@@ -85,6 +85,8 @@ enum {
 #define IPPROTO_RAW		IPPROTO_RAW
   IPPROTO_SMC = 256,		/* Shared Memory Communications		*/
 #define IPPROTO_SMC		IPPROTO_SMC
+  IPPROTO_SMBDIRECT = 257,	/* RDMA based transport (mostly used by SMB3) */
+#define IPPROTO_SMBDIRECT	IPPROTO_SMBDIRECT
   IPPROTO_MPTCP = 262,		/* Multipath TCP connection		*/
 #define IPPROTO_MPTCP		IPPROTO_MPTCP
   IPPROTO_MAX
-- 
2.43.0


^ permalink raw reply related

* [PATCH 6/8] smb: smbdirect: add in kernel only support for IPPROTO_SMBDIRECT
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
  To: linux-cifs, samba-technical
  Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
	David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
	Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
	linux-kernel
In-Reply-To: <cover.1775571957.git.metze@samba.org>

For userspace callers of socket() still get -EPROTONOSUPPORT,
so we are sure we'll only have in kernel callers, cifs.ko and
ksmbd.ko, for now. This makes it possible to relax the
constrains generic stream socket consumers would otherwise
assume.

There's a prototype for userspace sockets on top of
this and there's working userspace code for Samba as
client and server, so this is just the first step,
but a very important one.

The SMBDIRECT protocol is defined in [MS-SMBD] by Microsoft.
It is used as wrapper around RDMA in order to provide a transport for SMB3,
but Microsoft also uses it as transport for other protocols.

SMBDIRECT works over Infiniband, RoCE and iWarp.
RoCEv2 is based on IP/UDP and iWarp is based on IP/TCP,
so these use IP addresses natively.
Infiniband and RoCEv1 require IPOIB in order to be used for
SMBDIRECT.

So instead of adding a PF_SMBDIRECT, which would only use AF_INET[6],
we use IPPROTO_SMBDIRECT instead, this uses a number not
allocated from IANA, as it would not appear in an IP header.

This is similar to IPPROTO_SMC, IPPROTO_MPTCP and IPPROTO_QUIC,
which are linux specific values for the socket() syscall.

  socket(AF_INET, SOCK_STREAM, IPPROTO_SMBDIRECT);
  socket(AF_INET6, SOCK_STREAM, IPPROTO_SMBDIRECT);

This will allow the existing smbdirect code used by
cifs.ko and ksmbd.ko to be moved behind the socket layer,
so that there's less special handling. Only sock_sendmsg()
sock_recvmsg() are used, so that the main stream handling
is done all the same for tcp, smbdirect and later also quic.

The special RDMA read/write handling will be via direct
function calls as they are currently done for the in kernel
consumers.

For now the core smbdirect code still supports both
modes, direct calls in indirect via the socket layer.
The core code uses if (sc->sk.sk_family) as indication
for the new socket mode. Once cifs.ko and ksmbd.ko
are converted we can remove the old mode slowly,
but I'll deferr that to a future patchset.

There's still a way to go in order to make this
as generic as tcp and quic e.g. adding MSG_SPLICE_PAGES support or
splice_read/read_sock/read_skb.

But it's a good start, which will make changes
much easier.

Cc: Steve French <smfrench@gmail.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Long Li <longli@microsoft.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Henrique Carvalho <henrique.carvalho@suse.com>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: David S. Miller <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Cc: Willem de Bruijn <willemb@google.com>
Cc: netdev@vger.kernel.org
Cc: Xin Long <lucien.xin@gmail.com>
Cc: quic@lists.linux.dev
Cc: linux-rdma@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
 fs/smb/common/smbdirect/Makefile              |    1 +
 fs/smb/common/smbdirect/smbdirect.h           |   62 +
 fs/smb/common/smbdirect/smbdirect_accept.c    |   14 +-
 .../common/smbdirect/smbdirect_connection.c   |   58 +
 fs/smb/common/smbdirect/smbdirect_devices.c   |    2 +-
 fs/smb/common/smbdirect/smbdirect_internal.h  |   59 +-
 fs/smb/common/smbdirect/smbdirect_listen.c    |   49 +-
 fs/smb/common/smbdirect/smbdirect_main.c      |   45 +
 fs/smb/common/smbdirect/smbdirect_mr.c        |   10 +
 fs/smb/common/smbdirect/smbdirect_proto.c     | 1549 +++++++++++++++++
 fs/smb/common/smbdirect/smbdirect_public.h    |    3 +
 fs/smb/common/smbdirect/smbdirect_rw.c        |   29 +-
 fs/smb/common/smbdirect/smbdirect_socket.c    |  147 ++
 fs/smb/common/smbdirect/smbdirect_socket.h    |   26 +-
 14 files changed, 2039 insertions(+), 15 deletions(-)
 create mode 100644 fs/smb/common/smbdirect/smbdirect_proto.c

diff --git a/fs/smb/common/smbdirect/Makefile b/fs/smb/common/smbdirect/Makefile
index 423f533e1002..fcff485d7c45 100644
--- a/fs/smb/common/smbdirect/Makefile
+++ b/fs/smb/common/smbdirect/Makefile
@@ -10,6 +10,7 @@ smbdirect-y := \
 	smbdirect_connection.o	\
 	smbdirect_mr.o		\
 	smbdirect_rw.o		\
+	smbdirect_proto.o	\
 	smbdirect_debug.o	\
 	smbdirect_connect.o	\
 	smbdirect_listen.o	\
diff --git a/fs/smb/common/smbdirect/smbdirect.h b/fs/smb/common/smbdirect/smbdirect.h
index bbab5f7f7cc9..cf3d4957f94c 100644
--- a/fs/smb/common/smbdirect/smbdirect.h
+++ b/fs/smb/common/smbdirect/smbdirect.h
@@ -6,7 +6,10 @@
 #ifndef __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__
 #define __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__
 
+#include <linux/stddef.h>
 #include <linux/types.h>
+#include <linux/socket.h>
+#include <asm/ioctls.h>
 
 /* SMB-DIRECT buffer descriptor V1 structure [MS-SMBD] 2.2.3.1 */
 struct smbdirect_buffer_descriptor_v1 {
@@ -49,4 +52,63 @@ struct smbdirect_socket_parameters {
 		SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB | \
 		SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW)
 
+enum {
+	__SMBDIRECT_BUFFER_REMOTE_INVALIDATE = 0x20,
+};
+
+struct smbdirect_cmsg_buffer {
+	uint8_t msg_control[CMSG_SPACE(24)];
+};
+
+static __always_inline
+void __smbdirect_cmsg_prepare(struct msghdr *msg,
+			      struct smbdirect_cmsg_buffer *cbuffer,
+			      int cmsg_type,
+			      const void *payload,
+			      size_t payloadlen)
+{
+	size_t cmsg_space = CMSG_SPACE(payloadlen);
+	size_t cmsg_len = CMSG_LEN(payloadlen);
+	struct cmsghdr *cmsg = NULL;
+	void *dataptr = NULL;
+
+	BUILD_BUG_ON(cmsg_space > sizeof(cbuffer->msg_control));
+
+	memset(cbuffer, 0, sizeof(*cbuffer));
+
+	msg->msg_control = cbuffer->msg_control;
+	msg->msg_controllen = cmsg_space;
+
+	cmsg = CMSG_FIRSTHDR(msg);
+	cmsg->cmsg_level = SOL_SMBDIRECT;
+	cmsg->cmsg_type = cmsg_type;
+	cmsg->cmsg_len = cmsg_len;
+	dataptr = CMSG_DATA(cmsg);
+	memcpy(dataptr, payload, payloadlen);
+	msg->msg_controllen = cmsg->cmsg_len;
+}
+
+struct smbdirect_buffer_remote_invalidate_args {
+	__u32 remote_token;
+} __packed;
+#define SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE \
+	_IOW('S', __SMBDIRECT_BUFFER_REMOTE_INVALIDATE, \
+		struct smbdirect_buffer_remote_invalidate_args)
+
+static __always_inline
+void smbdirect_buffer_remote_invalidate_cmsg_prepare(struct msghdr *msg,
+						     struct smbdirect_cmsg_buffer *cbuffer,
+						     const __u32 *remote_token)
+{
+	if (remote_token) {
+		struct smbdirect_buffer_remote_invalidate_args args = {
+			.remote_token = *remote_token,
+		};
+
+		__smbdirect_cmsg_prepare(msg, cbuffer,
+					 SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE,
+					 &args, sizeof(args));
+	}
+}
+
 #endif /* __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_H__ */
diff --git a/fs/smb/common/smbdirect/smbdirect_accept.c b/fs/smb/common/smbdirect/smbdirect_accept.c
index d6d5e6a3f5de..6d7d869cdbc3 100644
--- a/fs/smb/common/smbdirect/smbdirect_accept.c
+++ b/fs/smb/common/smbdirect/smbdirect_accept.c
@@ -6,7 +6,6 @@
  */
 
 #include "smbdirect_internal.h"
-#include <net/sock.h>
 #include "../../common/smb2status.h"
 
 static int smbdirect_accept_rdma_event_handler(struct rdma_cm_id *id,
@@ -460,6 +459,12 @@ static void smbdirect_accept_negotiate_recv_work(struct work_struct *work)
 		spin_lock_irqsave(&lsc->listen.lock, flags);
 		list_del(&sc->accept.list);
 		list_add_tail(&sc->accept.list, &lsc->listen.ready);
+		if (lsc->sk.sk_family) {
+			struct sock *lsk = &lsc->sk;
+
+			if (!sock_flag(lsk, SOCK_DEAD) && lsk->sk_socket)
+				lsk->sk_data_ready(lsk);
+		}
 		wake_up(&lsc->listen.wait_queue);
 		spin_unlock_irqrestore(&lsc->listen.lock, flags);
 
@@ -774,11 +779,13 @@ static long smbdirect_socket_wait_for_accept(struct smbdirect_socket *lsc, long
 {
 	long ret;
 
+	smbdirect_socket_sk_unlock(lsc);
 	ret = wait_event_interruptible_timeout(lsc->listen.wait_queue,
 					       !list_empty_careful(&lsc->listen.ready) ||
 					       lsc->status != SMBDIRECT_SOCKET_LISTENING ||
 					       lsc->first_error,
 					       timeo);
+	smbdirect_socket_sk_lock(lsc);
 	if (lsc->status != SMBDIRECT_SOCKET_LISTENING)
 		return -EINVAL;
 	if (lsc->first_error)
@@ -850,6 +857,11 @@ struct smbdirect_socket *smbdirect_socket_accept(struct smbdirect_socket *lsc,
 	 * order to grant credits to the peer.
 	 */
 	nsc->status = SMBDIRECT_SOCKET_CONNECTED;
+	if (nsc->sk.sk_family) {
+		struct sock *nsk = &nsc->sk;
+
+		inet_sk_set_state(nsk, TCP_ESTABLISHED);
+	}
 	smbdirect_accept_negotiate_finish(nsc, 0);
 
 	return nsc;
diff --git a/fs/smb/common/smbdirect/smbdirect_connection.c b/fs/smb/common/smbdirect/smbdirect_connection.c
index 1e946f78e935..2c426aefd16d 100644
--- a/fs/smb/common/smbdirect/smbdirect_connection.c
+++ b/fs/smb/common/smbdirect/smbdirect_connection.c
@@ -153,6 +153,15 @@ void smbdirect_connection_rdma_established(struct smbdirect_socket *sc)
 
 	sc->rdma.cm_id->event_handler = smbdirect_connection_rdma_event_handler;
 	sc->rdma.expected_event = RDMA_CM_EVENT_DISCONNECTED;
+
+	if (sc->sk.sk_family) {
+		struct sock *sk = &sc->sk;
+
+		smbdirect_socket_sync_saddr_to_sk(sc, NULL);
+		smbdirect_socket_sync_daddr_to_sk(sc);
+
+		inet_sk_set_state(sk, TCP_SYN_RECV);
+	}
 }
 
 void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
@@ -189,6 +198,13 @@ void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
 		  smbdirect_socket_status_string(sc->status),
 		  SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
 	sc->status = SMBDIRECT_SOCKET_CONNECTED;
+	if (sc->sk.sk_family) {
+		struct sock *sk = &sc->sk;
+
+		inet_sk_set_state(sk, TCP_ESTABLISHED);
+		if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+			sk->sk_socket->state = SS_CONNECTED;
+	}
 
 	/*
 	 * We need to setup the refill and send immediate work
@@ -203,6 +219,13 @@ void smbdirect_connection_negotiation_done(struct smbdirect_socket *sc)
 		&sc->rdma.cm_id->route.addr.src_addr,
 		&sc->rdma.cm_id->route.addr.dst_addr);
 
+	if (sc->sk.sk_family) {
+		struct sock *sk = &sc->sk;
+
+		if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+			sk->sk_state_change(sk);
+	}
+
 	wake_up(&sc->status_wait);
 }
 
@@ -739,10 +762,12 @@ int smbdirect_connection_wait_for_connected(struct smbdirect_socket *sc)
 		"waiting for connection: device: %.*s local: %pISpsfc remote: %pISpsfc\n",
 		IB_DEVICE_NAME_MAX, devname, src, dst);
 
+	smbdirect_socket_sk_unlock(sc);
 	ret = wait_event_interruptible_timeout(sc->status_wait,
 					       sc->status == SMBDIRECT_SOCKET_CONNECTED ||
 					       sc->first_error,
 					       msecs_to_jiffies(sp->negotiate_timeout_msec));
+	smbdirect_socket_sk_lock(sc);
 	if (sc->rdma.cm_id) {
 		/*
 		 * Maybe src and dev are updated in the meantime.
@@ -954,6 +979,12 @@ int smbdirect_connection_send_batch_flush(struct smbdirect_socket *sc,
 		atomic_add(batch->credit, &sc->send_io.bcredits.count);
 		batch->credit = 0;
 		wake_up(&sc->send_io.bcredits.wait_queue);
+		if (sc->sk.sk_family) {
+			struct sock *sk = &sc->sk;
+
+			if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+				sk->sk_write_space(sk);
+		}
 	}
 
 	return ret;
@@ -1091,6 +1122,8 @@ int smbdirect_connection_send_single_iter(struct smbdirect_socket *sc,
 	u32 data_length = 0;
 	int ret;
 
+	smbdirect_socket_sk_owned_by_me(sc);
+
 	if (WARN_ON_ONCE(flags))
 		return -EINVAL; /* no flags support for now */
 
@@ -1150,10 +1183,12 @@ int smbdirect_connection_send_single_iter(struct smbdirect_socket *sc,
 		 * wait until either the refill work or the peer
 		 * granted new credits
 		 */
+		smbdirect_socket_sk_unlock(sc);
 		ret = wait_event_interruptible(sc->send_io.credits.wait_queue,
 					       atomic_read(&sc->send_io.credits.count) >= 1 ||
 					       atomic_read(&sc->recv_io.credits.available) >= 1 ||
 					       sc->status != SMBDIRECT_SOCKET_CONNECTED);
+		smbdirect_socket_sk_lock(sc);
 		if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
 			ret = -ENOTCONN;
 		if (ret < 0)
@@ -1268,9 +1303,11 @@ int smbdirect_connection_send_wait_zero_pending(struct smbdirect_socket *sc)
 	 * that means all the I/Os have been out and we are good to return
 	 */
 
+	smbdirect_socket_sk_unlock(sc);
 	wait_event(sc->send_io.pending.zero_wait_queue,
 		   atomic_read(&sc->send_io.pending.count) == 0 ||
 		   sc->status != SMBDIRECT_SOCKET_CONNECTED);
+	smbdirect_socket_sk_lock(sc);
 	if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
 		smbdirect_log_write(sc, SMBDIRECT_LOG_ERR,
 			"status=%s first_error=%1pe => %1pe\n",
@@ -1297,6 +1334,8 @@ int smbdirect_connection_send_iter(struct smbdirect_socket *sc,
 	int error = 0;
 	__be32 hdr;
 
+	smbdirect_socket_sk_owned_by_me(sc);
+
 	if (WARN_ONCE(flags, "unexpected flags=0x%x\n", flags))
 		return -EINVAL; /* no flags support for now */
 
@@ -1448,7 +1487,9 @@ static void smbdirect_connection_send_immediate_work(struct work_struct *work)
 	smbdirect_log_keep_alive(sc, SMBDIRECT_LOG_INFO,
 		"send an empty message\n");
 	sc->statistics.send_empty++;
+	smbdirect_socket_sk_lock(sc);
 	ret = smbdirect_connection_send_single_iter(sc, NULL, NULL, 0, 0);
+	smbdirect_socket_sk_unlock(sc);
 	if (ret < 0) {
 		smbdirect_log_write(sc, SMBDIRECT_LOG_ERR,
 			"smbdirect_connection_send_single_iter ret=%1pe\n",
@@ -1632,6 +1673,12 @@ void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc)
 		 * If any sender is waiting for credits, unblock it
 		 */
 		wake_up(&sc->send_io.credits.wait_queue);
+		if (sc->sk.sk_family) {
+			struct sock *sk = &sc->sk;
+
+			if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+				sk->sk_write_space(sk);
+		}
 	}
 
 	/* Send an immediate response right away if requested */
@@ -1652,6 +1699,12 @@ void smbdirect_connection_recv_io_done(struct ib_cq *cq, struct ib_wc *wc)
 
 		smbdirect_connection_reassembly_append_recv_io(sc, recv_io, data_length);
 		wake_up(&sc->recv_io.reassembly.wait_queue);
+		if (sc->sk.sk_family) {
+			struct sock *sk = &sc->sk;
+
+			if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+				sk->sk_data_ready(sk);
+		}
 	} else
 		smbdirect_connection_put_recv_io(recv_io);
 
@@ -1735,6 +1788,9 @@ int smbdirect_connection_recv_io_refill(struct smbdirect_socket *sc)
 	/*
 	 * If the last send credit is waiting for credits
 	 * it can grant we need to wake it up
+	 *
+	 * This needs to wake up smbdirect_connection_send_single_iter()
+	 * only, so we don't need sk->sk_write_space() here.
 	 */
 	if (atomic_read(&sc->send_io.bcredits.count) == 0 &&
 	    atomic_read(&sc->send_io.credits.count) == 0)
@@ -1922,9 +1978,11 @@ int smbdirect_connection_recvmsg(struct smbdirect_socket *sc,
 
 	smbdirect_log_read(sc, SMBDIRECT_LOG_INFO,
 		"wait_event on more data\n");
+	smbdirect_socket_sk_unlock(sc);
 	ret = wait_event_interruptible(sc->recv_io.reassembly.wait_queue,
 				       sc->recv_io.reassembly.data_length >= size ||
 				       sc->status != SMBDIRECT_SOCKET_CONNECTED);
+	smbdirect_socket_sk_lock(sc);
 	/* Don't return any data if interrupted */
 	if (ret)
 		return ret;
diff --git a/fs/smb/common/smbdirect/smbdirect_devices.c b/fs/smb/common/smbdirect/smbdirect_devices.c
index aaab99e9c045..da0edc104e48 100644
--- a/fs/smb/common/smbdirect/smbdirect_devices.c
+++ b/fs/smb/common/smbdirect/smbdirect_devices.c
@@ -257,7 +257,7 @@ __init int smbdirect_devices_init(void)
 	return 0;
 }
 
-__exit void smbdirect_devices_exit(void)
+__cold void smbdirect_devices_exit(void)
 {
 	struct smbdirect_device *sdev, *tmp;
 
diff --git a/fs/smb/common/smbdirect/smbdirect_internal.h b/fs/smb/common/smbdirect/smbdirect_internal.h
index 30a1b8643657..517ff0533032 100644
--- a/fs/smb/common/smbdirect/smbdirect_internal.h
+++ b/fs/smb/common/smbdirect/smbdirect_internal.h
@@ -12,8 +12,6 @@
 #include "smbdirect_pdu.h"
 #include "smbdirect_public.h"
 
-#include <linux/mutex.h>
-
 struct smbdirect_module_state {
 	struct mutex mutex;
 
@@ -30,6 +28,8 @@ struct smbdirect_module_state {
 		rwlock_t lock;
 		struct list_head list;
 	} devices;
+
+	struct smbdirect_socket_parameters default_parameters;
 };
 
 extern struct smbdirect_module_state smbdirect_globals;
@@ -46,10 +46,58 @@ struct smbdirect_device {
 	char ib_name[IB_DEVICE_NAME_MAX];
 };
 
+static __always_inline void smbdirect_socket_sk_owned_by_me(struct smbdirect_socket *sc)
+{
+	if (sc->sk.sk_family) {
+		struct sock *sk = &sc->sk;
+
+		/* assert it is already locked */
+		sock_owned_by_me(sk);
+	}
+}
+
+static __always_inline void smbdirect_socket_sk_not_owned_by_me(struct smbdirect_socket *sc)
+{
+	if (sc->sk.sk_family) {
+		struct sock *sk = &sc->sk;
+
+		/* assert it is not already locked */
+		sock_not_owned_by_me(sk);
+	}
+}
+
+static __always_inline void smbdirect_socket_sk_lock(struct smbdirect_socket *sc)
+{
+	if (sc->sk.sk_family) {
+		struct sock *sk = &sc->sk;
+
+		/* assert it is not already locked */
+		sock_not_owned_by_me(sk);
+
+		lock_sock(sk);
+	}
+}
+
+static __always_inline void smbdirect_socket_sk_unlock(struct smbdirect_socket *sc)
+{
+	if (sc->sk.sk_family) {
+		struct sock *sk = &sc->sk;
+
+		/* assert it is already locked */
+		sock_owned_by_me(sk);
+
+		release_sock(sk);
+	}
+}
+
 int smbdirect_socket_init_new(struct net *net, struct smbdirect_socket *sc);
 
 int smbdirect_socket_init_accepting(struct rdma_cm_id *id, struct smbdirect_socket *sc);
 
+int smbdirect_socket_sync_saddr_to_sk(struct smbdirect_socket *sc, bool *_is_any_addr);
+
+int smbdirect_socket_sync_daddr_to_sk(struct smbdirect_socket *sc);
+
 void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
 					 const char *macro_name,
 					 unsigned int lvl,
@@ -135,7 +183,12 @@ int smbdirect_accept_connect_request(struct smbdirect_socket *sc,
 
 void smbdirect_accept_negotiate_finish(struct smbdirect_socket *sc, u32 ntstatus);
 
+void smbdirect_sk_reclassify(struct sock *sk);
+
 __init int smbdirect_devices_init(void);
-__exit void smbdirect_devices_exit(void);
+__cold void smbdirect_devices_exit(void);
+
+__init int smbdirect_proto_init(void);
+__exit void smbdirect_proto_exit(void);
 
 #endif /* __FS_SMB_COMMON_SMBDIRECT_INTERNAL_H__ */
diff --git a/fs/smb/common/smbdirect/smbdirect_listen.c b/fs/smb/common/smbdirect/smbdirect_listen.c
index 05c7902e7020..a6e08d82dc73 100644
--- a/fs/smb/common/smbdirect/smbdirect_listen.c
+++ b/fs/smb/common/smbdirect/smbdirect_listen.c
@@ -74,6 +74,12 @@ int smbdirect_socket_listen(struct smbdirect_socket *sc, int backlog)
 	 */
 	sc->listen.backlog = backlog;
 
+	if (sc->sk.sk_family) {
+		struct sock *sk = &sc->sk;
+
+		inet_sk_set_state(sk, TCP_LISTEN);
+	}
+
 	if (sc->rdma.cm_id->device)
 		smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
 			"listening on addr: %pISpsfc dev: %.*s\n",
@@ -209,6 +215,7 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
 					    const struct rdma_cm_event *event)
 {
 	const struct smbdirect_socket_parameters *lsp = &lsc->parameters;
+	struct sock *nsk = NULL;
 	struct smbdirect_socket *nsc;
 	unsigned long flags;
 	size_t backlog = max_t(size_t, 1, lsc->listen.backlog);
@@ -265,9 +272,39 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
 		return -EBUSY;
 	}
 
-	ret = smbdirect_socket_create_accepting(new_id, &nsc);
-	if (ret)
-		goto socket_init_failed;
+	if (lsc->sk.sk_family) {
+		struct sock *lsk = &lsc->sk;
+
+		ret = -ENOMEM;
+		nsk = sk_clone(lsk, lsk->sk_allocation, false);
+		if (!nsk)
+			goto sk_clone_failed;
+		/* sk_clone_lock() increments refcnt to 2; drop the extra. */
+		__sock_put(nsk);
+		/* sk_clone() already called sk_sockets_allocated_inc(sk); */
+		sock_prot_inuse_add(sock_net(nsk), nsk->sk_prot, 1);
+
+		smbdirect_sk_reclassify(nsk);
+		inet_sk_set_state(nsk, TCP_SYN_RECV);
+		nsc = smbdirect_socket_from_sk(nsk);
+
+		ret = smbdirect_socket_init_accepting(new_id, nsc);
+		if (ret)
+			goto socket_init_failed;
+
+		/*
+		 * Note that smbdirect_sock_accept() will set
+		 * SOCK_CUSTOM_SOCKOPT once [__]inet_accept()
+		 * called sk_set_socket() via sock_graft().
+		 */
+		WARN_ON_ONCE(nsc->orig_sk_destruct != lsc->orig_sk_destruct);
+		WARN_ON_ONCE(nsk->sk_destruct != lsk->sk_destruct);
+		WARN_ON_ONCE(nsk->sk_ipv6only != lsk->sk_ipv6only);
+	} else {
+		ret = smbdirect_socket_create_accepting(new_id, &nsc);
+		if (ret)
+			goto socket_init_failed;
+	}
 
 	nsc->logging = lsc->logging;
 	ret = smbdirect_socket_set_initial_parameters(nsc, &lsc->parameters);
@@ -302,7 +339,11 @@ static int smbdirect_listen_connect_request(struct smbdirect_socket *lsc,
 	 */
 	nsc->ib.dev = NULL;
 	nsc->rdma.cm_id = NULL;
-	smbdirect_socket_release(nsc);
+	if (!nsk)
+		smbdirect_socket_release(nsc);
 socket_init_failed:
+	if (nsk)
+		sk_free(nsk);
+sk_clone_failed:
 	return ret;
 }
diff --git a/fs/smb/common/smbdirect/smbdirect_main.c b/fs/smb/common/smbdirect/smbdirect_main.c
index fe6e8d93c34c..ccbe979332af 100644
--- a/fs/smb/common/smbdirect/smbdirect_main.c
+++ b/fs/smb/common/smbdirect/smbdirect_main.c
@@ -12,6 +12,7 @@ struct smbdirect_module_state smbdirect_globals = {
 
 static __init int smbdirect_module_init(void)
 {
+	struct smbdirect_socket_parameters *sp;
 	int ret = -ENOMEM;
 
 	pr_notice("subsystem loading...\n");
@@ -73,10 +74,52 @@ static __init int smbdirect_module_init(void)
 	if (ret)
 		goto devices_init_failed;
 
+	/*
+	 * Create the global default parameters
+	 */
+	sp = &smbdirect_globals.default_parameters;
+	sp->resolve_addr_timeout_msec = 5 * 1000;
+	sp->resolve_route_timeout_msec = 5 * 1000;
+	sp->rdma_connect_timeout_msec = 5 * 1000;
+	sp->negotiate_timeout_msec = 120 * 1000;
+	sp->initiator_depth = 1; /* the server should change this */
+	sp->responder_resources = 1; /* the client should change this */
+	sp->recv_credit_max = 255;
+	sp->send_credit_target = 255;
+	sp->max_send_size = 1364;
+	/*
+	 * The maximum fragmented upper-layer payload receive size supported
+	 *
+	 * Assume max_payload_per_credit is
+	 * smbd_max_receive_size - 24 = 1340
+	 *
+	 * The maximum number would be
+	 * smbd_receive_credit_max * max_payload_per_credit
+	 *
+	 *                       1340 * 255 = 341700 (0x536C4)
+	 *
+	 * The minimum value from the spec is 131072 (0x20000)
+	 *
+	 * For now we use the logic we used before:
+	 *                 (1364 * 255) / 2 = 173910 (0x2A756)
+	 */
+	sp->max_fragmented_recv_size = (1364 * 255) / 2;
+	sp->max_recv_size = 1364;
+	sp->max_read_write_size = 0; /* the server should change this */
+	sp->max_frmr_depth = 0; /* the client should change this */
+	sp->keepalive_interval_msec = 120 * 1000;
+	sp->keepalive_timeout_msec = 5 * 1000;
+
+	ret = smbdirect_proto_init();
+	if (ret)
+		goto proto_init_failed;
+
 	mutex_unlock(&smbdirect_globals.mutex);
 	pr_notice("subsystem loaded\n");
 	return 0;
 
+proto_init_failed:
+	smbdirect_devices_exit();
 devices_init_failed:
 	destroy_workqueue(smbdirect_globals.workqueues.cleanup);
 alloc_cleanup_wq_failed:
@@ -101,6 +144,8 @@ static __exit void smbdirect_module_exit(void)
 	pr_notice("subsystem unloading...\n");
 	mutex_lock(&smbdirect_globals.mutex);
 
+	smbdirect_proto_exit();
+
 	smbdirect_devices_exit();
 
 	destroy_workqueue(smbdirect_globals.workqueues.accept);
diff --git a/fs/smb/common/smbdirect/smbdirect_mr.c b/fs/smb/common/smbdirect/smbdirect_mr.c
index fa9be8089925..86bb72ed10ae 100644
--- a/fs/smb/common/smbdirect/smbdirect_mr.c
+++ b/fs/smb/common/smbdirect/smbdirect_mr.c
@@ -167,9 +167,11 @@ smbdirect_connection_get_mr_io(struct smbdirect_socket *sc)
 	int ret;
 
 again:
+	smbdirect_socket_sk_unlock(sc);
 	ret = wait_event_interruptible(sc->mr_io.ready.wait_queue,
 				       atomic_read(&sc->mr_io.ready.count) ||
 				       sc->status != SMBDIRECT_SOCKET_CONNECTED);
+	smbdirect_socket_sk_lock(sc);
 	if (ret) {
 		smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
 			"wait_event_interruptible ret=%d (%1pe)\n",
@@ -281,7 +283,9 @@ smbdirect_connection_register_mr_io(struct smbdirect_socket *sc,
 		return NULL;
 	}
 
+	smbdirect_socket_sk_lock(sc);
 	mr = smbdirect_connection_get_mr_io(sc);
+	smbdirect_socket_sk_unlock(sc);
 	if (!mr) {
 		smbdirect_log_rdma_mr(sc, SMBDIRECT_LOG_ERR,
 			"smbdirect_connection_get_mr_io returning NULL\n");
@@ -415,6 +419,12 @@ void smbdirect_connection_deregister_mr_io(struct smbdirect_mr_io *mr)
 	if (mr->state == SMBDIRECT_MR_DISABLED)
 		goto put_kref;
 
+	/*
+	 * We are protected by mr->mutex
+	 * without lock_sock().
+	 */
+	smbdirect_socket_sk_not_owned_by_me(sc);
+
 	if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
 		smbdirect_mr_io_disable_locked(mr);
 		goto put_kref;
diff --git a/fs/smb/common/smbdirect/smbdirect_proto.c b/fs/smb/common/smbdirect/smbdirect_proto.c
new file mode 100644
index 000000000000..1a832d52eb89
--- /dev/null
+++ b/fs/smb/common/smbdirect/smbdirect_proto.c
@@ -0,0 +1,1549 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (c) 2025 Stefan Metzmacher
+ */
+
+#include "smbdirect_internal.h"
+#include <net/protocol.h>
+#include <net/inet_common.h>
+#include <linux/bpf-cgroup.h>
+#include <linux/errname.h>
+
+#define SMBDIRECT_FN_GENERIC(__sk, __fmt, __args...) do { \
+	struct smbdirect_socket *__sc = smbdirect_socket_from_sk(__sk); \
+	__smbdirect_log_generic(__sc, SMBDIRECT_LOG_INFO, SMBDIRECT_LOG_SK, \
+		__fmt " sc=%p %s first_error=%1pe kern=%u locked=%u refs=%u dead=%u mrefs=%u\n", \
+		##__args, __sc, \
+		smbdirect_socket_status_string(__sc->status), \
+		SMBDIRECT_DEBUG_ERR_PTR(__sc->first_error), \
+		(__sk)->sk_kern_sock, \
+		sock_owned_by_user_nocheck(__sk), \
+		refcount_read(&((__sk)->sk_refcnt)), \
+		sock_flag(__sk, SOCK_DEAD), \
+		module_refcount(THIS_MODULE)); \
+} while (0)
+
+#define SMBDIRECT_FN_COMMENT(__sk, __comment) \
+	SMBDIRECT_FN_GENERIC(__sk, "%s with", __comment)
+
+#define SMBDIRECT_FN_CALLED(__sk) \
+	SMBDIRECT_FN_GENERIC(__sk, "Called for")
+
+#define SMBDIRECT_FN_RETURN_VOID(__sk) \
+	SMBDIRECT_FN_GENERIC(__sk, "Returning for")
+
+#define SMBDIRECT_FN_RETURN_POLL(__sk, __mask) \
+	SMBDIRECT_FN_GENERIC(__sk, "Returning mask=0x%x for", __mask)
+
+#define SMBDIRECT_FN_RETURN_INT(__sk, __ret) do { \
+	bool __is_err = IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(__ret)); \
+	SMBDIRECT_FN_GENERIC(__sk, "Returning ret=%d%s%s%s for", \
+		(__ret), \
+		__is_err ? " (" : "", \
+		__is_err ? errname(__ret) : "", \
+		__is_err ? ")" : ""); \
+} while (0)
+
+static bool smbdirect_sk_logging_needed(struct smbdirect_socket *sc,
+					void *private_ptr,
+					unsigned int lvl,
+					unsigned int cls)
+{
+	/*
+	 * Only errors by default.
+	 */
+	if (lvl <= SMBDIRECT_LOG_ERR)
+		return true;
+	return false;
+}
+
+static void smbdirect_sk_logging_vaprintf(struct smbdirect_socket *sc,
+					  const char *func,
+					  unsigned int line,
+					  void *private_ptr,
+					  unsigned int lvl,
+					  unsigned int cls,
+					  struct va_format *vaf)
+{
+	if (lvl <= SMBDIRECT_LOG_ERR)
+		pr_err("%s:%u %pV", func, line, vaf);
+	else
+		pr_info("%s:%u %pV", func, line, vaf);
+}
+
+void smbdirect_sk_reclassify(struct sock *sk)
+{
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+	static struct lock_class_key sk_key[2];
+	static struct lock_class_key slock_key[2];
+
+	if (WARN_ON_ONCE(!sock_allow_reclassification(sk)))
+		return;
+
+	switch (sk->sk_family) {
+	case AF_INET:
+		/*
+		 * Before we reset the owner we
+		 * need to drop the reference of the
+		 * existing module, this is only
+		 * really relevant for AF_INET,
+		 * as that is always builtin
+		 * there's no potential leak
+		 * of module references. We do it
+		 * mainly in order to match the
+		 * AF_INET6 case.
+		 */
+		sk_owner_put(sk);
+		sk_owner_clear(sk);
+
+		sock_lock_init_class_and_name(sk,
+					      "slock-AF_INET-IPPROTO-SMBDIRECT",
+					      &slock_key[0],
+					      "sk_lock-AF_INET-IPPROTO-SMBDIRECT",
+					      &sk_key[0]);
+
+		/*
+		 * Now that we reclassified the socket
+		 * we're also the new sk_owner, but that's
+		 * not needed as there's still a reference
+		 * on sk->sk_prot->owner, which is dropped
+		 * in sk_prot_free(). But in order to
+		 * avoid module reference leaks to our
+		 * own module we need to put and clear
+		 * sk_owner, in order to allow callers
+		 * to do their own reclassification.
+		 */
+		sk_owner_put(sk);
+		sk_owner_clear(sk);
+		break;
+	case AF_INET6:
+		/*
+		 * Before we reset the owner we
+		 * need to drop the reference of the
+		 * existing module.
+		 *
+		 * As we also use inet6_register_protosw()
+		 * and other symbols from a possible
+		 * ipv6.ko, we already have enough
+		 * module references in order to avoid
+		 * unloading of ipv6.ko, while smbdirect.ko
+		 * is loaded.
+		 *
+		 * However when smbdirect.ko is unloaded
+		 * we should not leak references in order
+		 * to allow ipv6.ko to be unloaded as well.
+		 */
+		sk_owner_put(sk);
+		sk_owner_clear(sk);
+
+		sock_lock_init_class_and_name(sk,
+					      "slock-AF_INET6-IPPROTO-SMBDIRECT",
+					      &slock_key[1],
+					      "sk_lock-AF_INET6-IPPROTO-SMBDIRECT",
+					      &sk_key[1]);
+
+		/*
+		 * Now that we reclassified the socket
+		 * we're also the new sk_owner, but that's
+		 * not needed as there's still a reference
+		 * on sk->sk_prot->owner, which is dropped
+		 * in sk_prot_free(). But in order to
+		 * avoid module reference leaks to our
+		 * own module we need to put and clear
+		 * sk_owner, in order to allow callers
+		 * to do their own reclassification.
+		 */
+		sk_owner_put(sk);
+		sk_owner_clear(sk);
+		break;
+	default:
+		WARN_ON_ONCE(1);
+	}
+#endif /* CONFIG_DEBUG_LOCK_ALLOC */
+}
+
+static void smbdirect_sk_destruct(struct sock *sk)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+
+	/*
+	 * Called by sk_free()
+	 */
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	if (WARN_ON_ONCE(sc->status != SMBDIRECT_SOCKET_DESTROYED)) {
+		pr_err("Attempt to release SMBDIRECT socket in status %s sc %p\n",
+		       smbdirect_socket_status_string(sc->status), sc);
+		SMBDIRECT_FN_RETURN_VOID(sk);
+		return;
+	}
+
+	SMBDIRECT_FN_COMMENT(sk, "calling orig_sk_destruct");
+	smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+		"sc[%p]->orig_sk_destruct[%ps]\n",
+		sc, sc->orig_sk_destruct);
+	sc->orig_sk_destruct(sk);
+	SMBDIRECT_FN_RETURN_VOID(sk);
+}
+
+static int smbdirect_sk_init(struct sock *sk)
+{
+	struct socket *sock = sk->sk_socket;
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	const struct smbdirect_socket_parameters *sp = &smbdirect_globals.default_parameters;
+	void (*orig_sk_destruct)(struct sock *sk);
+	int ret;
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	smbdirect_sk_reclassify(sk);
+
+	smbdirect_socket_init(sc);
+	smbdirect_socket_set_logging(sc,
+				     NULL,
+				     smbdirect_sk_logging_needed,
+				     smbdirect_sk_logging_vaprintf);
+
+	smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+		"Called for sk=%p family=%u protocol=%u type=%u\n",
+		sk, sk->sk_family, sk->sk_protocol, sk->sk_type);
+
+	sk_sockets_allocated_inc(sk);
+	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
+
+	orig_sk_destruct = sk->sk_destruct;
+	SMBDIRECT_FN_COMMENT(sk, "remembered orig_sk_destruct");
+	smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+		"sc[%p]->orig_sk_destruct[%ps]\n",
+		sc, orig_sk_destruct);
+	sc->orig_sk_destruct = orig_sk_destruct;
+	sk->sk_destruct = smbdirect_sk_destruct;
+
+	/*
+	 * We want to handle all sockopts explicitly
+	 * and only support what we really support.
+	 */
+	set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags);
+	/*
+	 * There are no legacy callers, so we are strict
+	 * regarding ipv4 vs. ipv6.
+	 */
+	sk->sk_ipv6only = true;
+
+	/*
+	 * No userspace sockets yet...
+	 */
+	if (!sk->sk_kern_sock) {
+		sc->first_error = -EPROTONOSUPPORT;
+		SMBDIRECT_FN_COMMENT(sk, "No userspace sockets");
+		return -EPROTONOSUPPORT;
+	}
+
+	ret = smbdirect_socket_init_new(sock_net(sk), sc);
+	if (ret)
+		goto socket_init_failed;
+	/*
+	 * smbdirect_socket_init_new() called smbdirect_socket_init() again,
+	 * so we need to call smbdirect_socket_set_logging() again!
+	 */
+	smbdirect_socket_set_logging(sc,
+				     NULL,
+				     smbdirect_sk_logging_needed,
+				     smbdirect_sk_logging_vaprintf);
+
+	WARN_ON_ONCE(sc->orig_sk_destruct != orig_sk_destruct);
+	WARN_ON_ONCE(sk->sk_destruct != smbdirect_sk_destruct);
+
+	ret = smbdirect_socket_set_initial_parameters(sc, sp);
+	if (ret)
+		goto set_params_failed;
+
+	ret = smbdirect_socket_set_kernel_settings(sc, IB_POLL_SOFTIRQ, sk->sk_allocation);
+	if (ret)
+		goto set_settings_failed;
+
+	SMBDIRECT_FN_RETURN_INT(sk, 0);
+	return 0;
+
+set_settings_failed:
+set_params_failed:
+socket_init_failed:
+	sc->first_error = ret;
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static void smbdirect_sk_destroy(struct sock *sk)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is already locked */
+	sock_owned_by_me(sk);
+
+	/*
+	 * For now do a sync disconnect/destroy
+	 *
+	 * SMBDIRECT_LOG_INFO is enough here
+	 * as this is the typical case where
+	 * we terminate the connection ourself.
+	 */
+	smbdirect_socket_schedule_cleanup_lvl(sc,
+					      SMBDIRECT_LOG_INFO,
+					      -ESHUTDOWN);
+	smbdirect_socket_destroy_sync(sc);
+
+	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
+	sk_sockets_allocated_dec(sk);
+
+	SMBDIRECT_FN_RETURN_VOID(sk);
+}
+
+static int smbdirect_sk_hash(struct sock *sk)
+{
+	SMBDIRECT_FN_CALLED(sk);
+	return 0;
+}
+
+static void smbdirect_sk_unhash(struct sock *sk)
+{
+	SMBDIRECT_FN_CALLED(sk);
+}
+
+static void smbdirect_sk_release_cb(struct sock *sk)
+{
+	/*
+	 * Called from release_sock()
+	 */
+	SMBDIRECT_FN_CALLED(sk);
+}
+
+static int smbdirect_sk_pre_bind(struct sock *sk,
+				 struct sockaddr_unsized *uaddr,
+				 int *addr_len,
+				 u32 *flags,
+				 u16 *port)
+{
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	if (*addr_len < sizeof(uaddr->sa_family))
+		return -EINVAL;
+
+	/* AF_UNSPEC is not allowed */
+	if (sk->sk_family != uaddr->sa_family)
+		return -EAFNOSUPPORT;
+
+	/*
+	 * BPF prog is run before any checks are done so that if the prog
+	 * changes context in a wrong way it will be caught.
+	 */
+	switch (sk->sk_family) {
+	case AF_INET:
+		if (*addr_len < sizeof(struct sockaddr_in))
+			return -EINVAL;
+
+		*port = ntohs(((struct sockaddr_in *)uaddr)->sin_port);
+
+		return BPF_CGROUP_RUN_PROG_INET_BIND_LOCK(sk, uaddr, addr_len,
+							  CGROUP_INET4_BIND,
+							  flags);
+	case AF_INET6:
+		/*
+		 * We require a full struct sockaddr_in6 (28 bytes) instead of a
+		 * minimal size of SIN6_LEN_RFC2133 (24 bytes), as we don't
+		 * have any legacy callers in userspace and the
+		 * rdma layer also expects that.
+		 */
+		if (*addr_len < sizeof(struct sockaddr_in6))
+			return -EINVAL;
+
+		*port = ntohs(((struct sockaddr_in6 *)uaddr)->sin6_port);
+
+		return BPF_CGROUP_RUN_PROG_INET_BIND_LOCK(sk, uaddr, addr_len,
+							  CGROUP_INET6_BIND,
+							  flags);
+	}
+
+	return -EAFNOSUPPORT;
+}
+
+static int smbdirect_sk_do_bind(struct sock *sk,
+				struct sockaddr_unsized *uaddr,
+				const u32 flags,
+				const u16 port)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	bool is_any_addr = true;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	if (flags & BIND_WITH_LOCK)
+		sock_owned_by_me(sk);
+	else
+		sock_not_owned_by_me(sk);
+
+	ret = smbdirect_socket_bind(sc, (struct sockaddr *)uaddr);
+	if (ret) {
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	ret = smbdirect_socket_sync_saddr_to_sk(sc, &is_any_addr);
+	if (ret) {
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	/* Make sure we are allowed to bind here. */
+	if (sk->sk_num && !(flags & BIND_FROM_BPF)) {
+		switch (sk->sk_family) {
+		case AF_INET:
+			ret = BPF_CGROUP_RUN_PROG_INET4_POST_BIND(sk);
+			if (ret) {
+				SMBDIRECT_FN_RETURN_INT(sk, ret);
+				return ret;
+			}
+			break;
+
+		case AF_INET6:
+			ret = BPF_CGROUP_RUN_PROG_INET6_POST_BIND(sk);
+			if (ret) {
+				SMBDIRECT_FN_RETURN_INT(sk, ret);
+				return ret;
+			}
+			break;
+		}
+	}
+
+	if (!is_any_addr)
+		sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
+	if (port)
+		sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
+
+	ret = 0;
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sk_bind(struct sock *sk, struct sockaddr_unsized *addr, int addr_len)
+{
+	struct net *net = sock_net(sk);
+	u32 flags = BIND_WITH_LOCK;
+	u16 port = 0;
+	u16 check_port = 0;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	ret = smbdirect_sk_pre_bind(sk, addr, &addr_len, &flags, &port);
+	if (ret) {
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	/*
+	 * treat the iwarp tcp port for
+	 * smb (5445) as the main smb port (445)
+	 * and only allow the bind if 445
+	 * would be allowed.
+	 */
+	if (port == 5445)
+		check_port = 445;
+	else
+		check_port = port;
+
+	if (!(flags & BIND_NO_CAP_NET_BIND_SERVICE) &&
+	    check_port && inet_port_requires_bind_service(net, check_port) &&
+	    !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE)) {
+		ret = -EACCES;
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	if (flags & BIND_WITH_LOCK)
+		lock_sock(sk);
+
+	ret = smbdirect_sk_do_bind(sk, addr, flags, port);
+
+	if (flags & BIND_WITH_LOCK)
+		release_sock(sk);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static struct sock *smbdirect_sk_accept(struct sock *lsk, struct proto_accept_arg *arg)
+{
+	struct smbdirect_socket *lsc = smbdirect_socket_from_sk(lsk);
+	long timeo = sock_rcvtimeo(lsk, arg->flags & O_NONBLOCK);
+	struct smbdirect_socket *nsc;
+	struct sock *nsk;
+
+	SMBDIRECT_FN_CALLED(lsk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(lsk);
+
+	lock_sock(lsk);
+	nsc = smbdirect_socket_accept(lsc, timeo, arg);
+	release_sock(lsk);
+	if (!nsc) {
+		SMBDIRECT_FN_RETURN_INT(lsk, arg->err);
+		return NULL;
+	}
+	nsk = &nsc->sk;
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(nsk);
+
+	SMBDIRECT_FN_RETURN_INT(lsk, 0);
+	return nsk;
+}
+
+static int smbdirect_sk_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len)
+{
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is already locked */
+	sock_owned_by_me(sk);
+
+	if (addr_len < sizeof(uaddr->sa_family))
+		return -EINVAL;
+
+	if (sk->sk_family != uaddr->sa_family)
+		return -EAFNOSUPPORT;
+
+	switch (sk->sk_family) {
+	case AF_INET:
+		if (addr_len < sizeof(struct sockaddr_in))
+			return -EINVAL;
+
+		return BPF_CGROUP_RUN_PROG_INET4_CONNECT(sk, uaddr, &addr_len);
+	case AF_INET6:
+		/*
+		 * We require a full struct sockaddr_in6 (28 bytes) instead of a
+		 * minimal size of SIN6_LEN_RFC2133 (24 bytes), as we don't
+		 * have any legacy callers in userspace and the
+		 * rdma layer also expects that.
+		 */
+		if (addr_len < sizeof(struct sockaddr_in6))
+			return -EINVAL;
+
+		return BPF_CGROUP_RUN_PROG_INET6_CONNECT(sk, uaddr, &addr_len);
+	}
+
+	return -EAFNOSUPPORT;
+}
+
+static int smbdirect_sk_connect(struct sock *sk, struct sockaddr_unsized *addr, int addr_len)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is already locked */
+	sock_owned_by_me(sk);
+
+	ret = smbdirect_connect(sc, (struct sockaddr *)addr);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sk_setsockopt(struct sock *sk, int level, int optname,
+				   sockptr_t optval, unsigned int optlen)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	switch (level) {
+	default:
+		SMBDIRECT_FN_COMMENT(sk, "default");
+		smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+			"level=%d optname=%d for sk=%p\n",
+			level, optname, sk);
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sk_getsockopt(struct sock *sk, int level, int optname,
+				   char __user *optval, int __user *optlen)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	switch (level) {
+	default:
+		SMBDIRECT_FN_COMMENT(sk, "default");
+		smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+			"level=%d optname=%d for sk=%p\n",
+			level, optname, sk);
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sk_ioctl(struct sock *sk, int cmd, int *karg)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	switch (cmd) {
+	default:
+		SMBDIRECT_FN_COMMENT(sk, "default");
+		smbdirect_log_sk(sc, SMBDIRECT_LOG_INFO,
+			"cmd=%d (0x%x) for sk=%p\n",
+			cmd, cmd, sk);
+		ret = -ENOIOCTLCMD;
+		break;
+	}
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static inline size_t smbdirect_cmsg_count(const struct msghdr *_msg,
+					  int *first_sol_smbdirect_type)
+{
+	struct msghdr *msg = (struct msghdr *)(uintptr_t)(const void *)_msg;
+	struct cmsghdr *cmsg = NULL;
+	size_t count = 0;
+
+	if (first_sol_smbdirect_type != NULL)
+		*first_sol_smbdirect_type = -1;
+
+	for (cmsg = CMSG_FIRSTHDR(msg);
+	     cmsg != NULL;
+	     cmsg = CMSG_NXTHDR(msg, cmsg)) {
+		count++;
+		if (cmsg->cmsg_level != SOL_SMBDIRECT)
+			continue;
+		if (first_sol_smbdirect_type != NULL) {
+			*first_sol_smbdirect_type = cmsg->cmsg_type;
+			first_sol_smbdirect_type = NULL;
+		}
+	}
+
+	return count;
+}
+
+static __always_inline
+ssize_t __smbdirect_cmsg_extract(const struct msghdr *_msg,
+				 int cmsg_type,
+				 void *_payload,
+				 size_t payloadmin,
+				 size_t payloadmax)
+{
+	struct msghdr *msg = (struct msghdr *)(uintptr_t)(const void *)_msg;
+	size_t cmsg_len_min = CMSG_LEN(payloadmin);
+	size_t cmsg_len_max = CMSG_LEN(payloadmax);
+	const size_t cmsg_len_hdr = CMSG_LEN(0);
+	uint8_t *payload = (uint8_t *)_payload;
+	struct cmsghdr *cmsg = NULL;
+	size_t payloadlen;
+
+	BUILD_BUG_ON(cmsg_len_min > cmsg_len_max);
+	if (WARN_ON_ONCE(cmsg_len_min > cmsg_len_max))
+		return -EBADMSG;
+
+	for (cmsg = CMSG_FIRSTHDR(msg);
+	     cmsg != NULL;
+	     cmsg = CMSG_NXTHDR(msg, cmsg)) {
+		if (cmsg->cmsg_level != SOL_SMBDIRECT)
+			continue;
+
+		if (cmsg->cmsg_type != cmsg_type)
+			continue;
+
+		if (cmsg->cmsg_len < cmsg_len_min)
+			return -EBADMSG;
+
+		if (cmsg->cmsg_len > cmsg_len_max)
+			return -EMSGSIZE;
+
+		payloadlen = cmsg->cmsg_len - cmsg_len_hdr;
+		if (payloadlen > 0)
+			memcpy(payload, CMSG_DATA(cmsg), payloadlen);
+		if (payloadlen < payloadmax)
+			memset(payload + payloadlen, 0, payloadmax - payloadlen);
+		return payloadlen;
+	}
+
+	return -ENOMSG;
+}
+
+static __always_inline
+int smbdirect_buffer_remote_invalidate_cmsg_extract(const struct msghdr *msg,
+						    u32 *remote_token)
+{
+	struct smbdirect_buffer_remote_invalidate_args args = {
+		.remote_token = 0,
+	};
+	ssize_t ret;
+
+	ret = __smbdirect_cmsg_extract(msg,
+				       SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE,
+				       &args, sizeof(args), sizeof(args));
+	if (ret < 0)
+		return ret;
+
+	*remote_token = args.remote_token;
+	return 0;
+}
+
+static int smbdirect_sk_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t msg_len)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	struct iov_iter *iter = &msg->msg_iter;
+	unsigned int flags = msg->msg_flags;
+	size_t cmsg_count = 0;
+	int cmsg_type = -1;
+	bool need_invalidate = false;
+	u32 remote_key = 0;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is already locked */
+	sock_owned_by_me(sk);
+
+	cmsg_count = smbdirect_cmsg_count(msg, &cmsg_type);
+	if (cmsg_count > 1) {
+		ret = -EINVAL;
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	if (flags & ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL)) {
+		ret = -EINVAL;
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	if (cmsg_type == SMBDIRECT_BUFFER_REMOTE_INVALIDATE_CMSG_TYPE) {
+		ret = smbdirect_buffer_remote_invalidate_cmsg_extract(msg, &remote_key);
+		if (!ret)
+			need_invalidate = true; /* remote_key is valid */
+		else if (ret != -ENOMSG) {
+			SMBDIRECT_FN_RETURN_INT(sk, ret);
+			return ret;
+		}
+	} else if (cmsg_count) {
+		ret = -EINVAL;
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	if (WARN_ON_ONCE(iov_iter_rw(iter) != ITER_SOURCE)) {
+		ret = -EINVAL;
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	if (WARN_ON_ONCE(iov_iter_count(iter) != msg_len)) {
+		ret = -EINVAL;
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	if (flags & MSG_DONTWAIT) {
+		if (!sc->first_error && msg_len && atomic_read(&sc->send_io.credits.count) == 0) {
+			ret = -EAGAIN;
+			SMBDIRECT_FN_RETURN_INT(sk, ret);
+			return ret;
+		}
+	}
+	flags &= ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL);
+
+	ret = smbdirect_connection_send_iter(sc,
+					     iter,
+					     flags,
+					     need_invalidate,
+					     remote_key);
+	if (ret < 0)
+		/* Handle error and possibly send SIGPIPE. */
+		ret = sk_stream_error(sk, msg->msg_flags, ret);
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sk_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len)
+{
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	lock_sock(sk);
+	ret = smbdirect_sk_sendmsg_locked(sk, msg, msg_len);
+	release_sock(sk);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sk_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	struct iov_iter *iter = &msg->msg_iter;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	if (flags & ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL)) {
+		ret = -EINVAL;
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	if (WARN_ON_ONCE(iov_iter_rw(iter) != ITER_DEST)) {
+		ret = -EINVAL;
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	/*
+	 * For now smbdirect_connection_recvmsg() relies
+	 * on this assertion and the current in kernel
+	 * users are working that way.
+	 */
+	if (WARN_ON_ONCE(iov_iter_count(iter) != len)) {
+		ret = -EINVAL;
+		SMBDIRECT_FN_RETURN_INT(sk, ret);
+		return ret;
+	}
+
+	lock_sock(sk);
+	if (flags & MSG_DONTWAIT) {
+		if (!sc->first_error && len && sc->recv_io.reassembly.data_length == 0) {
+			ret = -EAGAIN;
+			release_sock(sk);
+			SMBDIRECT_FN_RETURN_INT(sk, ret);
+			return ret;
+		}
+	}
+	flags &= ~(MSG_DONTWAIT|MSG_WAITALL|MSG_NOSIGNAL);
+	ret = smbdirect_connection_recvmsg(sc, msg, flags);
+	if (msg->msg_get_inq && ret >= 0)
+		msg->msg_inq = sc->recv_io.reassembly.data_length;
+	release_sock(sk);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static void smbdirect_sk_shutdown(struct sock *sk, int how)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is already locked */
+	sock_owned_by_me(sk);
+
+	smbdirect_socket_schedule_cleanup(sc, -ESHUTDOWN);
+
+	SMBDIRECT_FN_RETURN_VOID(sk);
+}
+
+static int smbdirect_sk_disconnect(struct sock *sk, int flags)
+{
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is already locked */
+	sock_owned_by_me(sk);
+
+	smbdirect_socket_schedule_cleanup(sc, -ESHUTDOWN);
+
+	if (flags & O_NONBLOCK) {
+		if (sc->status >= SMBDIRECT_SOCKET_DISCONNECTED) {
+			SMBDIRECT_FN_RETURN_INT(sk, 0);
+			return 0;
+		}
+
+		/*
+		 * This will cause SS_DISCONNECTING in
+		 * smbdirect_sock_connect_locked().
+		 */
+		SMBDIRECT_FN_RETURN_INT(sk, sc->first_error);
+		return sc->first_error;
+	}
+
+	smbdirect_socket_destroy_sync(sc);
+
+	SMBDIRECT_FN_RETURN_INT(sk, 0);
+	return 0;
+}
+
+static void smbdirect_sk_close(struct sock *sk, long timeout)
+{
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	/*
+	 * We hold an additional reference so
+	 * that the sock_put() in sk_common_release()
+	 * doesn't call sk_free(), that is potentially
+	 * deferred to our sock_put() after release_sock().
+	 *
+	 * Note that sk_common_release() calls
+	 * smbdirect_sk_destroy() as the first thing.
+	 */
+	sock_hold(sk);
+	lock_sock(sk);
+	sk_common_release(sk);
+	release_sock(sk);
+	SMBDIRECT_FN_COMMENT(sk, "before sock_put()");
+	sock_put(sk);
+}
+
+static struct percpu_counter smbdirect_sockets_allocated;
+
+static struct proto smbdirect_prot = {
+	.name			= "smbdirect",
+	.owner			= THIS_MODULE,
+	.obj_size		= sizeof(struct smbdirect_socket),
+	.ipv6_pinfo_offset	= offsetof(struct smbdirect_socket, inet6),
+	.init			= smbdirect_sk_init,
+	.destroy		= smbdirect_sk_destroy,
+	.hash			= smbdirect_sk_hash,
+	.unhash			= smbdirect_sk_unhash,
+	.release_cb		= smbdirect_sk_release_cb,
+	.bind			= smbdirect_sk_bind,
+	.accept			= smbdirect_sk_accept,
+	.pre_connect		= smbdirect_sk_pre_connect,
+	.connect		= smbdirect_sk_connect,
+	.setsockopt		= smbdirect_sk_setsockopt,
+	.getsockopt		= smbdirect_sk_getsockopt,
+	.ioctl			= smbdirect_sk_ioctl,
+	.sendmsg		= smbdirect_sk_sendmsg,
+	.recvmsg		= smbdirect_sk_recvmsg,
+	.shutdown		= smbdirect_sk_shutdown,
+	.disconnect		= smbdirect_sk_disconnect,
+	.close			= smbdirect_sk_close,
+	.sockets_allocated	= &smbdirect_sockets_allocated,
+};
+
+static int smbdirect_sock_release(struct socket *sock)
+{
+	struct sock *sk = sock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not locked */
+	sock_not_owned_by_me(sk);
+	WARN_ON_ONCE(sock_owned_by_user_nocheck(sk));
+
+	switch (sk->sk_family) {
+	case AF_INET:
+		SMBDIRECT_FN_COMMENT(sk, "calling inet_release()");
+		ret = inet_release(sock);
+		break;
+	case AF_INET6:
+#if IS_ENABLED(CONFIG_IPV6)
+		SMBDIRECT_FN_COMMENT(sk, "calling inet6_release()");
+		ret = inet6_release(sock);
+#else
+		ret = -EAFNOSUPPORT;
+#endif
+		break;
+	default:
+		ret = -EAFNOSUPPORT;
+		break;
+	}
+
+	return ret;
+}
+
+static int smbdirect_sock_bind(struct socket *sock, struct sockaddr_unsized *saddr, int len)
+{
+	struct sock *sk = sock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	switch (sk->sk_family) {
+	case AF_INET:
+		ret = inet_bind(sock, saddr, len);
+		break;
+	case AF_INET6:
+#if IS_ENABLED(CONFIG_IPV6)
+		ret = inet6_bind(sock, saddr, len);
+#else
+		ret = -EAFNOSUPPORT;
+#endif
+		break;
+	default:
+		ret = -EAFNOSUPPORT;
+		break;
+	}
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sock_connect_locked(struct socket *sock,
+					 struct sockaddr_unsized *uaddr,
+					 int addr_len, int flags)
+{
+	struct sock *sk = sock->sk;
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is already locked */
+	sock_owned_by_me(sk);
+
+	if (addr_len < sizeof(uaddr->sa_family))
+		return -EINVAL;
+
+	if (sk->sk_family != uaddr->sa_family)
+		return -EAFNOSUPPORT;
+
+	switch (sk->sk_family) {
+	case AF_INET:
+		if (addr_len < sizeof(struct sockaddr_in))
+			return -EINVAL;
+		break;
+	case AF_INET6:
+		/*
+		 * We require a full struct sockaddr_in6 (28 bytes) instead of a
+		 * minimal size of SIN6_LEN_RFC2133 (24 bytes), as we don't
+		 * have any legacy callers in userspace and the
+		 * rdma layer also expects that.
+		 */
+		if (addr_len < sizeof(struct sockaddr_in6))
+			return -EINVAL;
+		break;
+	default:
+		return -EAFNOSUPPORT;
+	}
+
+	switch (sock->state) {
+	case SS_CONNECTED:
+		return -EISCONN;
+	case SS_CONNECTING:
+		return -EALREADY;
+	case SS_UNCONNECTED:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (sc->status == SMBDIRECT_SOCKET_CONNECTED)
+		return -EISCONN;
+
+	if (sc->status != SMBDIRECT_SOCKET_CREATED)
+		return -EINVAL;
+
+	if (BPF_CGROUP_PRE_CONNECT_ENABLED(sk)) {
+		ret = sk->sk_prot->pre_connect(sk, uaddr, addr_len);
+		if (ret)
+			return ret;
+	}
+
+	ret = sk->sk_prot->connect(sk, uaddr, addr_len);
+	if (ret < 0)
+		return ret;
+
+	inet_sk_set_state(sk, TCP_SYN_SENT);
+	sock->state = SS_CONNECTING;
+
+	if (flags & O_NONBLOCK)
+		return -EINPROGRESS;
+
+	ret = smbdirect_connection_wait_for_connected(sc);
+	if (ret)
+		goto sock_error;
+
+	return 0;
+
+sock_error:
+	sock->state = SS_UNCONNECTED;
+	sk->sk_disconnects++;
+	if (sk->sk_prot->disconnect(sk, flags))
+		sock->state = SS_DISCONNECTING;
+	return ret;
+}
+
+static int smbdirect_sock_connect(struct socket *sock,
+				  struct sockaddr_unsized *uaddr,
+				  int addr_len, int flags)
+{
+	struct sock *sk = sock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	lock_sock(sk);
+	ret = smbdirect_sock_connect_locked(sock, uaddr, addr_len, flags);
+	release_sock(sk);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sock_listen(struct socket *sock, int backlog)
+{
+	struct sock *sk = sock->sk;
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	lock_sock(sk);
+	ret = smbdirect_socket_listen(sc, backlog);
+	release_sock(sk);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sock_accept(struct socket *lsock, struct socket *nsock,
+				 struct proto_accept_arg *arg)
+{
+	struct sock *lsk = lsock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(lsk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(lsk);
+
+	ret = inet_accept(lsock, nsock, arg);
+	if (!ret)
+		/*
+		 * We want to handle all sockopts explicitly
+		 * and only support what we really support.
+		 */
+		set_bit(SOCK_CUSTOM_SOCKOPT, &nsock->flags);
+
+	SMBDIRECT_FN_RETURN_INT(lsk, ret);
+	return ret;
+}
+
+static int smbdirect_sock_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
+{
+	struct sock *sk = sock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	switch (sk->sk_family) {
+	case AF_INET:
+		ret = inet_getname(sock, uaddr, peer);
+		break;
+	case AF_INET6:
+#if IS_ENABLED(CONFIG_IPV6)
+		ret = inet6_getname(sock, uaddr, peer);
+#else
+		ret = -EAFNOSUPPORT;
+#endif
+		break;
+	default:
+		ret = -EAFNOSUPPORT;
+		break;
+	}
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static __poll_t smbdirect_sock_poll(struct file *file, struct socket *sock, poll_table *wait)
+{
+	struct sock *sk = sock->sk;
+	struct smbdirect_socket *sc = smbdirect_socket_from_sk(sk);
+	__poll_t mask = 0;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	sock_poll_wait(file, sock, wait);
+
+	if (sc->status == SMBDIRECT_SOCKET_LISTENING) {
+		if (!list_empty_careful(&sc->listen.ready))
+			mask |= EPOLLIN | EPOLLRDNORM;
+		SMBDIRECT_FN_RETURN_POLL(sk, mask);
+		return mask;
+	}
+
+	if (sc->first_error) {
+		/*
+		 * A broken connection should report almost everything in order to let
+		 * applications to detect it reliable.
+		 */
+		mask |= EPOLLHUP;
+		mask |= EPOLLERR;
+		mask |= EPOLLIN | EPOLLRDNORM | EPOLLRDHUP;
+		mask |= EPOLLOUT | EPOLLWRNORM;
+		SMBDIRECT_FN_RETURN_POLL(sk, mask);
+		return mask;
+	}
+
+	if (sc->status != SMBDIRECT_SOCKET_CONNECTED) {
+		/*
+		 * A just created socket.
+		 */
+		SMBDIRECT_FN_RETURN_POLL(sk, mask);
+		return mask;
+	}
+
+	if (sc->recv_io.reassembly.data_length > 0)
+		mask |= EPOLLIN | EPOLLRDNORM;
+
+	if (atomic_read(&sc->send_io.bcredits.count) > 0 &&
+	    atomic_read(&sc->send_io.lcredits.count) > 0 &&
+	    atomic_read(&sc->send_io.credits.count) > 0)
+		mask |= EPOLLOUT | EPOLLWRNORM;
+	else {
+		sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
+		set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
+
+		/*
+		 * Race breaker. If space is freed after
+		 * wspace test but before the flags are set,
+		 * IO signal will be lost. Memory barrier
+		 * pairs with the input side.
+		 */
+		smp_mb__after_atomic();
+		if (atomic_read(&sc->send_io.bcredits.count) > 0 &&
+		    atomic_read(&sc->send_io.lcredits.count) > 0 &&
+		    atomic_read(&sc->send_io.credits.count) > 0)
+			mask |= EPOLLOUT | EPOLLWRNORM;
+	}
+
+	SMBDIRECT_FN_RETURN_POLL(sk, mask);
+	return mask;
+}
+
+static int smbdirect_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+	struct sock *sk = sock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	/*
+	 * We may need to handle some here as
+	 * smbirect_sk_ioctl() only gets a kernel
+	 * int pointer as arg, but we may
+	 * need to the whole struct
+	 */
+	switch (cmd) {
+	default:
+		/*
+		 * Note this has some special handling for
+		 * sk->sk_type == SOCK_RAW, in case we ever
+		 * implement SOCK_RAW...
+		 *
+		 * It calls smbdirect_sk_ioctl()...
+		 */
+		ret = sk_ioctl(sk, cmd, (void __user *)arg);
+		break;
+	}
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sock_shutdown(struct socket *sock, int how)
+{
+	struct sock *sk = sock->sk;
+	int ret = 0;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	/*
+	 * We have these from userspace:
+	 * SHUT_RD = 0, SHUT_WR = 1 and SHUT_RDWR = 2
+	 *
+	 * And we map them to SHUTDOWN_MASK = 3
+	 * RCV_SHUTDOWN = 1, SEND_SHUTDOWN = 2, BOTH = 3
+	 */
+	how++;
+	if ((how & ~SHUTDOWN_MASK) || !how)	/* MAXINT->0 */
+		return -EINVAL;
+
+	lock_sock(sk);
+
+	switch (sk->sk_state) {
+	case TCP_CLOSE:
+		ret = -ENOTCONN;
+		fallthrough;
+	default:
+		WRITE_ONCE(sk->sk_shutdown, sk->sk_shutdown | how);
+		sk->sk_prot->shutdown(sk, how);
+		break;
+
+	case TCP_SYN_SENT:
+	case TCP_SYN_RECV:
+		ret = sk->sk_prot->disconnect(sk, O_NONBLOCK);
+		break;
+	}
+
+	/* Wake up anyone sleeping in poll. */
+	sk->sk_state_change(sk);
+	release_sock(sk);
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sock_setsockopt(struct socket *sock, int level, int optname,
+				     sockptr_t optval, unsigned int optlen)
+{
+	struct sock *sk = sock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	ret = sock_common_setsockopt(sock, level, optname, optval, optlen);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sock_getsockopt(struct socket *sock, int level, int optname,
+				     char __user *optval, int __user *optlen)
+{
+	struct sock *sk = sock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	ret = sock_common_getsockopt(sock, level, optname, optval, optlen);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
+{
+	struct sock *sk = sock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	ret = sk->sk_prot->sendmsg(sk, msg, len);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static int smbdirect_sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
+				  int flags)
+{
+	struct sock *sk = sock->sk;
+	int ret;
+
+	SMBDIRECT_FN_CALLED(sk);
+
+	/* assert it is not already locked */
+	sock_not_owned_by_me(sk);
+
+	ret = sock_common_recvmsg(sock, msg, size, flags);
+
+	SMBDIRECT_FN_RETURN_INT(sk, ret);
+	return ret;
+}
+
+static const struct proto_ops smbdirect_inet_proto_ops = {
+	.family			= PF_INET,
+	.owner			= THIS_MODULE,
+	.release		= smbdirect_sock_release,
+	.bind			= smbdirect_sock_bind,
+	.connect		= smbdirect_sock_connect,
+	.socketpair		= sock_no_socketpair,
+	.listen			= smbdirect_sock_listen,
+	.accept			= smbdirect_sock_accept,
+	.getname		= smbdirect_sock_getname,
+	.poll			= smbdirect_sock_poll,
+	.ioctl			= smbdirect_sock_ioctl,
+	.shutdown		= smbdirect_sock_shutdown,
+	.setsockopt		= smbdirect_sock_setsockopt,
+	.getsockopt		= smbdirect_sock_getsockopt,
+	.sendmsg		= smbdirect_sock_sendmsg,
+	.sendmsg_locked		= smbdirect_sk_sendmsg_locked,
+	.recvmsg		= smbdirect_sock_recvmsg,
+	.mmap			= sock_no_mmap,
+};
+
+#if IS_ENABLED(CONFIG_IPV6)
+static const struct proto_ops smbdirect_inet6_proto_ops = {
+	.family			= PF_INET6,
+	.owner			= THIS_MODULE,
+	.release		= smbdirect_sock_release,
+	.bind			= smbdirect_sock_bind,
+	.connect		= smbdirect_sock_connect,
+	.socketpair		= sock_no_socketpair,
+	.listen			= smbdirect_sock_listen,
+	.accept			= smbdirect_sock_accept,
+	.getname		= smbdirect_sock_getname,
+	.poll			= smbdirect_sock_poll,
+	.ioctl			= smbdirect_sock_ioctl,
+	.shutdown		= smbdirect_sock_shutdown,
+	.setsockopt		= smbdirect_sock_setsockopt,
+	.getsockopt		= smbdirect_sock_getsockopt,
+	.sendmsg		= smbdirect_sock_sendmsg,
+	.sendmsg_locked		= smbdirect_sk_sendmsg_locked,
+	.recvmsg		= smbdirect_sock_recvmsg,
+	.mmap			= sock_no_mmap,
+};
+#endif
+
+static struct inet_protosw smbdirect_inet_stream_protosw = {
+	.type		= SOCK_STREAM,
+	.protocol	= IPPROTO_SMBDIRECT,
+	.prot		= &smbdirect_prot,
+	.ops		= &smbdirect_inet_proto_ops,
+};
+
+#if IS_ENABLED(CONFIG_IPV6)
+static struct inet_protosw smbdirect_inet6_stream_protosw = {
+	.type		= SOCK_STREAM,
+	.protocol	= IPPROTO_SMBDIRECT,
+	.prot		= &smbdirect_prot,
+	.ops		= &smbdirect_inet6_proto_ops,
+};
+#endif
+
+struct smbdirect_socket *smbdirect_socket_from_sock(const struct socket *sock)
+{
+	if (!sock ||
+	    !sock->sk ||
+	    sock->sk->sk_protocol != IPPROTO_SMBDIRECT)
+		return NULL;
+
+	if (WARN_ON_ONCE(sock->sk->sk_destruct != smbdirect_sk_destruct))
+		return NULL;
+
+	return smbdirect_socket_from_sk(sock->sk);
+}
+__SMBDIRECT_EXPORT_SYMBOL__(smbdirect_socket_from_sock);
+
+static __init int smbdirect_protosw_init(void)
+{
+	int err;
+
+	err = proto_register(&smbdirect_prot, 1);
+	if (err)
+		return err;
+
+	inet_register_protosw(&smbdirect_inet_stream_protosw);
+#if IS_ENABLED(CONFIG_IPV6)
+	inet6_register_protosw(&smbdirect_inet6_stream_protosw);
+#endif
+
+	return 0;
+}
+
+static __exit void smbdirect_protosw_exit(void)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+	inet6_unregister_protosw(&smbdirect_inet6_stream_protosw);
+#endif
+	inet_unregister_protosw(&smbdirect_inet_stream_protosw);
+
+	proto_unregister(&smbdirect_prot);
+}
+
+__init int smbdirect_proto_init(void)
+{
+	int err;
+
+	err = percpu_counter_init(&smbdirect_sockets_allocated, 0, GFP_KERNEL);
+	if (err)
+		goto err_percpu_counter;
+
+	err = smbdirect_protosw_init();
+	if (err)
+		goto err_protosw;
+
+	return 0;
+
+err_protosw:
+	percpu_counter_destroy(&smbdirect_sockets_allocated);
+err_percpu_counter:
+	return err;
+}
+
+__exit void smbdirect_proto_exit(void)
+{
+	smbdirect_protosw_exit();
+	percpu_counter_destroy(&smbdirect_sockets_allocated);
+}
+
+MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_INET, 257 /* IPPROTO_SMBDIRECT */, SOCK_STREAM);
+MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_INET6, 257 /* IPPROTO_SMBDIRECT */, SOCK_STREAM);
diff --git a/fs/smb/common/smbdirect/smbdirect_public.h b/fs/smb/common/smbdirect/smbdirect_public.h
index 50088155e7c3..9f96c66bbe32 100644
--- a/fs/smb/common/smbdirect/smbdirect_public.h
+++ b/fs/smb/common/smbdirect/smbdirect_public.h
@@ -49,6 +49,7 @@ int smbdirect_socket_set_kernel_settings(struct smbdirect_socket *sc,
 #define SMBDIRECT_LOG_RDMA_MR			0x100
 #define SMBDIRECT_LOG_RDMA_RW			0x200
 #define SMBDIRECT_LOG_NEGOTIATE			0x400
+#define SMBDIRECT_LOG_SK			0x800
 void smbdirect_socket_set_logging(struct smbdirect_socket *sc,
 				  void *private_ptr,
 				  bool (*needed)(struct smbdirect_socket *sc,
@@ -145,4 +146,6 @@ void smbdirect_connection_legacy_debug_proc_show(struct smbdirect_socket *sc,
 						 unsigned int rdma_readwrite_threshold,
 						 struct seq_file *m);
 
+struct smbdirect_socket *smbdirect_socket_from_sock(const struct socket *sock);
+
 #endif /* __FS_SMB_COMMON_SMBDIRECT_SMBDIRECT_PUBLIC_H__ */
diff --git a/fs/smb/common/smbdirect/smbdirect_rw.c b/fs/smb/common/smbdirect/smbdirect_rw.c
index 3b2eb8c48efc..154339955617 100644
--- a/fs/smb/common/smbdirect/smbdirect_rw.c
+++ b/fs/smb/common/smbdirect/smbdirect_rw.c
@@ -105,11 +105,11 @@ static void smbdirect_connection_rdma_write_done(struct ib_cq *cq, struct ib_wc
 	smbdirect_connection_rdma_rw_done(cq, wc, DMA_TO_DEVICE);
 }
 
-int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
-				   void *buf, size_t buf_len,
-				   struct smbdirect_buffer_descriptor_v1 *desc,
-				   size_t desc_len,
-				   bool is_read)
+static int smbdirect_connection_rdma_xmit_locked(struct smbdirect_socket *sc,
+						 void *buf, size_t buf_len,
+						 struct smbdirect_buffer_descriptor_v1 *desc,
+						 size_t desc_len,
+						 bool is_read)
 {
 	const struct smbdirect_socket_parameters *sp = &sc->parameters;
 	enum dma_data_direction direction = is_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
@@ -123,6 +123,8 @@ int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
 	int credits_needed;
 	size_t desc_buf_len, desc_num = 0;
 
+	smbdirect_socket_sk_owned_by_me(sc);
+
 	if (sc->status != SMBDIRECT_SOCKET_CONNECTED)
 		return -ENOTCONN;
 
@@ -235,7 +237,9 @@ int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
 	}
 
 	msg = list_last_entry(&msg_list, struct smbdirect_rw_io, list);
+	smbdirect_socket_sk_unlock(sc);
 	wait_for_completion(&completion);
+	smbdirect_socket_sk_lock(sc);
 	ret = msg->error;
 out:
 	list_for_each_entry_safe(msg, next_msg, &msg_list, list) {
@@ -252,4 +256,19 @@ int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
 	kfree(msg);
 	goto out;
 }
+
+int smbdirect_connection_rdma_xmit(struct smbdirect_socket *sc,
+				   void *buf, size_t buf_len,
+				   struct smbdirect_buffer_descriptor_v1 *desc,
+				   size_t desc_len,
+				   bool is_read)
+{
+	int ret;
+
+	smbdirect_socket_sk_lock(sc);
+	ret = smbdirect_connection_rdma_xmit_locked(sc, buf, buf_len, desc, desc_len, is_read);
+	smbdirect_socket_sk_unlock(sc);
+
+	return ret;
+}
 __SMBDIRECT_EXPORT_SYMBOL__(smbdirect_connection_rdma_xmit);
diff --git a/fs/smb/common/smbdirect/smbdirect_socket.c b/fs/smb/common/smbdirect/smbdirect_socket.c
index 9153e1dbf53d..76e406999588 100644
--- a/fs/smb/common/smbdirect/smbdirect_socket.c
+++ b/fs/smb/common/smbdirect/smbdirect_socket.c
@@ -5,6 +5,7 @@
  */
 
 #include "smbdirect_internal.h"
+#include <net/transp_v6.h>
 
 bool smbdirect_frwr_is_supported(const struct ib_device_attr *attrs)
 {
@@ -217,6 +218,7 @@ int smbdirect_socket_set_kernel_settings(struct smbdirect_socket *sc,
 	sc->send_io.mem.gfp_mask = gfp_mask;
 	sc->recv_io.mem.gfp_mask = gfp_mask;
 	sc->rw_io.mem.gfp_mask = gfp_mask;
+	sc->sk.sk_allocation = gfp_mask;
 
 	return 0;
 }
@@ -242,6 +244,106 @@ void smbdirect_socket_set_logging(struct smbdirect_socket *sc,
 }
 __SMBDIRECT_EXPORT_SYMBOL__(smbdirect_socket_set_logging);
 
+int smbdirect_socket_sync_saddr_to_sk(struct smbdirect_socket *sc, bool *_is_any_addr)
+{
+	struct sock *sk = &sc->sk;
+	const struct sockaddr_storage *saddr;
+	const struct sockaddr_in *sin;
+	const struct sockaddr_in6 *sin6;
+	struct in_addr sin_addr = { .s_addr = htonl(INADDR_ANY), };
+	struct in6_addr sin6_addr = in6addr_any;
+	__be32 sin6_flowinfo = 0;
+	bool is_any_addr = true;
+	u16 sport = 0;
+	int ret;
+
+	saddr = &sc->rdma.cm_id->route.addr.src_addr;
+
+	if (WARN_ON_ONCE(saddr->ss_family != sk->sk_family)) {
+		ret = -EINVAL;
+		return ret;
+	}
+
+	switch (saddr->ss_family) {
+	case AF_INET:
+		sin = (struct sockaddr_in *)saddr;
+		sport = ntohs(sin->sin_port);
+		sin_addr = sin->sin_addr;
+		is_any_addr = (sin_addr.s_addr == htonl(INADDR_ANY));
+		break;
+
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *)saddr;
+		sport = ntohs(sin6->sin6_port);
+		sin_addr.s_addr = LOOPBACK4_IPV6;
+		sin6_addr = sin6->sin6_addr;
+		is_any_addr = ipv6_addr_any(&sin6_addr);
+		sin6_flowinfo = sin6->sin6_flowinfo;
+		break;
+	}
+
+	sk->sk_bound_dev_if = sc->rdma.cm_id->route.addr.dev_addr.bound_dev_if;
+	sk->sk_rcv_saddr = sc->inet.inet_saddr = sin_addr.s_addr;
+#if IS_ENABLED(CONFIG_IPV6)
+	sk->sk_v6_rcv_saddr = sc->inet6.saddr = sin6_addr;
+#else
+	sc->inet6.saddr = sin6_addr;
+#endif
+	sc->inet6.flow_label = sin6_flowinfo;
+	sk->sk_num = sport;
+	sc->inet.inet_sport = htons(sport);
+
+	if (_is_any_addr)
+		*_is_any_addr = is_any_addr;
+	return 0;
+}
+
+int smbdirect_socket_sync_daddr_to_sk(struct smbdirect_socket *sc)
+{
+	struct sock *sk = &sc->sk;
+	const struct sockaddr_storage *daddr;
+	const struct sockaddr_in *sin;
+	const struct sockaddr_in6 *sin6;
+	struct in_addr sin_addr = { .s_addr = htonl(INADDR_ANY), };
+#if IS_ENABLED(CONFIG_IPV6)
+	struct in6_addr sin6_addr = in6addr_any;
+#endif
+	u16 dport = 0;
+	int ret;
+
+	daddr = &sc->rdma.cm_id->route.addr.dst_addr;
+
+	if (WARN_ON_ONCE(daddr->ss_family != sk->sk_family)) {
+		ret = -EINVAL;
+		return ret;
+	}
+
+	switch (daddr->ss_family) {
+	case AF_INET:
+		sin = (struct sockaddr_in *)daddr;
+		dport = ntohs(sin->sin_port);
+		sin_addr = sin->sin_addr;
+		break;
+
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *)daddr;
+		dport = ntohs(sin6->sin6_port);
+		sin_addr.s_addr = LOOPBACK4_IPV6;
+#if IS_ENABLED(CONFIG_IPV6)
+		sin6_addr = sin6->sin6_addr;
+#endif
+		break;
+	}
+
+	sk->sk_daddr = sc->inet.inet_daddr = sin_addr.s_addr;
+#if IS_ENABLED(CONFIG_IPV6)
+	sk->sk_v6_daddr = sin6_addr;
+#endif
+	sk->sk_dport = sc->inet.inet_dport = htons(dport);
+
+	return 0;
+}
+
 static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
 {
 	/*
@@ -257,6 +359,38 @@ static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
 	wake_up_all(&sc->recv_io.reassembly.wait_queue);
 	wake_up_all(&sc->rw_io.credits.wait_queue);
 	wake_up_all(&sc->mr_io.ready.wait_queue);
+
+	if (sc->sk.sk_family) {
+		struct sock *sk = &sc->sk;
+
+		WRITE_ONCE(sk->sk_shutdown, SHUTDOWN_MASK);
+
+		WARN_ON_ONCE(sc->first_error == 0);
+		if (sc->first_error < 0)
+			WRITE_ONCE(sk->sk_err, -sc->first_error);
+		else
+			WRITE_ONCE(sk->sk_err, sc->first_error);
+
+		if (sc->status >= SMBDIRECT_SOCKET_DISCONNECTED) {
+			inet_sk_set_state(sk, TCP_CLOSE);
+			if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+				sk->sk_socket->state = SS_UNCONNECTED;
+		} else {
+			inet_sk_set_state(sk, TCP_CLOSING);
+			if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+				sk->sk_socket->state = SS_DISCONNECTING;
+		}
+
+		/*
+		 * Note tcp_done_with_error() also calls both
+		 * sk->sk_state_change(sk) via tcp_done()
+		 * and sk_error_report() directly.
+		 */
+		if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+			sk->sk_state_change(sk);
+		if (!sock_flag(sk, SOCK_DEAD) && sk->sk_socket)
+			sk_error_report(sk);
+	}
 }
 
 void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
@@ -510,11 +644,13 @@ static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
 	 */
 	smbdirect_socket_wake_up_all(sc);
 
+	smbdirect_socket_sk_unlock(sc);
 	disable_work_sync(&sc->disconnect_work);
 	disable_work_sync(&sc->connect.work);
 	disable_work_sync(&sc->recv_io.posted.refill_work);
 	disable_work_sync(&sc->idle.immediate_work);
 	disable_delayed_work_sync(&sc->idle.timer_work);
+	smbdirect_socket_sk_lock(sc);
 
 	if (sc->rdma.cm_id)
 		rdma_lock_handler(sc->rdma.cm_id);
@@ -600,6 +736,8 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
 	 */
 	WARN_ON_ONCE(in_interrupt());
 
+	smbdirect_socket_sk_owned_by_me(sc);
+
 	/*
 	 * First we try to disable the work
 	 * without disable_work_sync() in a
@@ -625,7 +763,9 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
 
 	smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
 		"cancelling and disable disconnect_work\n");
+	smbdirect_socket_sk_unlock(sc);
 	disable_work_sync(&sc->disconnect_work);
+	smbdirect_socket_sk_lock(sc);
 
 	smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
 		"destroying rdma session\n");
@@ -634,7 +774,9 @@ void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
 	if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) {
 		smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
 			"wait for transport being disconnected\n");
+		smbdirect_socket_sk_unlock(sc);
 		wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
+		smbdirect_socket_sk_lock(sc);
 		smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
 			"waited for transport being disconnected\n");
 	}
@@ -723,6 +865,8 @@ int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
 {
 	int ret;
 
+	smbdirect_socket_sk_owned_by_me(sc);
+
 	if (WARN_ON_ONCE(needed < 0))
 		return -EINVAL;
 
@@ -731,9 +875,12 @@ int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
 			return 0;
 
 		atomic_add(needed, total_credits);
+
+		smbdirect_socket_sk_unlock(sc);
 		ret = wait_event_interruptible(*waitq,
 					       atomic_read(total_credits) >= needed ||
 					       sc->status != expected_status);
+		smbdirect_socket_sk_lock(sc);
 
 		if (sc->status != expected_status)
 			return unexpected_errno;
diff --git a/fs/smb/common/smbdirect/smbdirect_socket.h b/fs/smb/common/smbdirect/smbdirect_socket.h
index c09eddd8ad16..6bb201683259 100644
--- a/fs/smb/common/smbdirect/smbdirect_socket.h
+++ b/fs/smb/common/smbdirect/smbdirect_socket.h
@@ -104,6 +104,18 @@ enum smbdirect_keepalive_status {
 };
 
 struct smbdirect_socket {
+	union {
+		struct sock sk;
+		struct inet_sock inet;
+	};
+	/* needed by inet6_create() */
+	struct ipv6_pinfo inet6;
+	void (*orig_sk_destruct)(struct sock *sk);
+
+	/*
+	 * This is the first element that is
+	 * initialized in smbdirect_socket_init()
+	 */
 	enum smbdirect_socket_status status;
 	wait_queue_head_t status_wait;
 	int first_error;
@@ -548,14 +560,18 @@ static void __smbdirect_log_printf(struct smbdirect_socket *sc,
 		__smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_RDMA_RW, fmt, ##args)
 #define smbdirect_log_negotiate(sc, lvl, fmt, args...) \
 		__smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_NEGOTIATE, fmt, ##args)
+#define smbdirect_log_sk(sc, lvl, fmt, args...) \
+		__smbdirect_log_generic(sc, lvl, SMBDIRECT_LOG_SK, fmt, ##args)
 
 static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
 {
+	const size_t status_offset = offsetof(struct smbdirect_socket, status);
+
 	/*
 	 * This also sets status = SMBDIRECT_SOCKET_CREATED
 	 */
 	BUILD_BUG_ON(SMBDIRECT_SOCKET_CREATED != 0);
-	memset(sc, 0, sizeof(*sc));
+	memset(((u8 *)sc)+status_offset, 0, sizeof(*sc)-status_offset);
 
 	init_waitqueue_head(&sc->status_wait);
 
@@ -700,6 +716,14 @@ static __always_inline void smbdirect_socket_init(struct smbdirect_socket *sc)
 	__SMBDIRECT_CHECK_STATUS_WARN(__sc, __expected_status, \
 		__SMBDIRECT_SOCKET_DISCONNECT(__sc);)
 
+static __always_inline struct smbdirect_socket *
+smbdirect_socket_from_sk(const struct sock *sk)
+{
+	WARN_ON_ONCE(!sk);
+	BUILD_BUG_ON(offsetof(struct smbdirect_socket, sk) != 0);
+	return container_of(sk, struct smbdirect_socket, sk);
+}
+
 struct smbdirect_send_io {
 	struct smbdirect_socket *socket;
 	struct ib_cqe cqe;
-- 
2.43.0


^ permalink raw reply related

* [PATCH 7/8] smb: client: make use of IPPROTO_SMBDIRECT sockets
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
  To: linux-cifs, samba-technical
  Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
	David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
	Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
	linux-kernel
In-Reply-To: <cover.1775571957.git.metze@samba.org>

This reduces the function calls for special smbdirect features to
- smbdirect_socket_from_sock()
- smbdirect_socket_set_logging()
- smbdirect_socket_set_initial_parameters()
- smbdirect_socket_set_kernel_settings()
- smbdirect_socket_get_current_parameters()
- smbdirect_connection_is_connected()
- smbdirect_connection_send_single_iter()
- smbdirect_connection_send_wait_zero_pending()
- smbdirect_connection_register_mr_io()
- smbdirect_mr_io_fill_buffer_descriptor()
- smbdirect_connection_deregister_mr_io()
- smbdirect_connection_legacy_debug_proc_show()

In future with modifications from David Howells
we'll also be able to use sock_sendmsg() and avoid
direct calls to smbdirect_connection_send_single_iter() and
smbdirect_connection_send_wait_zero_pending().

This will also make it easier to implement the QUIC
transport Henrique Carvalho is working on.

In the end the core smb handling should just make
use of a SOCK_STREAM socket, after the initial setup.

There's still a way to go, but almost all changes
will be done within the smbdirect code, e.g.
adding MSG_SPLICE_PAGES support or
splice_read/read_sock/read_skb.

But it's a good start, which will make changes
much easier.

Cc: Steve French <smfrench@gmail.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Long Li <longli@microsoft.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Henrique Carvalho <henrique.carvalho@suse.com>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: David S. Miller <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Cc: Willem de Bruijn <willemb@google.com>
Cc: netdev@vger.kernel.org
Cc: Xin Long <lucien.xin@gmail.com>
Cc: quic@lists.linux.dev
Cc: linux-rdma@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
 fs/smb/client/cifs_debug.c |   2 +-
 fs/smb/client/cifsfs.c     |   2 +-
 fs/smb/client/cifsglob.h   |   7 +-
 fs/smb/client/connect.c    | 123 +++++++++--------
 fs/smb/client/file.c       |   8 +-
 fs/smb/client/sess.c       |   4 +-
 fs/smb/client/smb2ops.c    |   8 +-
 fs/smb/client/smb2pdu.c    |  10 +-
 fs/smb/client/smbdirect.c  | 275 +++++++++++--------------------------
 fs/smb/client/smbdirect.h  |  27 +---
 fs/smb/client/transport.c  |   2 +-
 11 files changed, 170 insertions(+), 298 deletions(-)

diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index 0691d2a3e04b..70f30b1f8cda 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -481,7 +481,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
 
 		seq_printf(m, "\nServer capabilities: 0x%x", server->capabilities);
 
-		if (server->rdma)
+		if (cifs_rdma_enabled(server))
 			seq_printf(m, "\nRDMA ");
 		seq_printf(m, "\nTCP status: %d Instance: %d"
 				"\nLocal Users To Server: %d SecMode: 0x%x Req On Wire: %d",
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index 32d0305a1239..12986050e16c 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -529,7 +529,7 @@ cifs_show_address(struct seq_file *s, struct TCP_Server_Info *server)
 	default:
 		seq_puts(s, "(unknown)");
 	}
-	if (server->rdma)
+	if (cifs_rdma_enabled(server))
 		seq_puts(s, ",rdma");
 }
 
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 709e96e07791..f7b62bcaadd9 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -35,6 +35,7 @@
 #define SMB_PATH_MAX 260
 #define CIFS_PORT 445
 #define RFC1001_PORT 139
+#define SMBD_IWARP_PORT 5445
 
 /*
  * The sizes of various internal tables and strings
@@ -759,10 +760,8 @@ struct TCP_Server_Info {
 	bool	sec_mskerberos;		/* supports legacy MS Kerberos */
 	bool	sec_iakerb;		/* supports pass-through auth for Kerberos (krb5 proxy) */
 	bool	large_buf;		/* is current buffer large? */
-	/* use SMBD connection instead of socket */
-	bool	rdma;
-	/* point to the SMBD connection if RDMA is used instead of socket */
-	struct smbd_connection *smbd_conn;
+	int	ipproto; /* IPPROTO_TCP or IPOROTO_SMBDIRECT */
+#define cifs_rdma_enabled(__server)	((__server)->ipproto == IPPROTO_SMBDIRECT)
 	struct delayed_work	echo; /* echo ping workqueue job */
 	char	*smallbuf;	/* pointer to current "small" buffer */
 	char	*bigbuf;	/* pointer to current "big" buffer */
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
index 69b38f0ccf2b..d42abe1d3772 100644
--- a/fs/smb/client/connect.c
+++ b/fs/smb/client/connect.c
@@ -313,8 +313,6 @@ cifs_abort_connection(struct TCP_Server_Info *server)
 			 server->ssocket->flags);
 		sock_release(server->ssocket);
 		server->ssocket = NULL;
-	} else if (cifs_rdma_enabled(server)) {
-		smbd_destroy(server);
 	}
 	server->sequence_number = 0;
 	server->session_estab = false;
@@ -408,10 +406,7 @@ static int __cifs_reconnect(struct TCP_Server_Info *server,
 			cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
 		}
 
-		if (cifs_rdma_enabled(server))
-			rc = smbd_reconnect(server);
-		else
-			rc = generic_ip_connect(server);
+		rc = generic_ip_connect(server);
 		if (rc) {
 			cifs_server_unlock(server);
 			cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
@@ -468,10 +463,7 @@ static int __reconnect_target_locked(struct TCP_Server_Info *server,
 		cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
 	}
 	/* Reconnect the socket */
-	if (cifs_rdma_enabled(server))
-		rc = smbd_reconnect(server);
-	else
-		rc = generic_ip_connect(server);
+	rc = generic_ip_connect(server);
 
 	return rc;
 }
@@ -746,10 +738,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg)
 
 		if (server_unresponsive(server))
 			return -ECONNABORTED;
-		if (cifs_rdma_enabled(server) && server->smbd_conn)
-			length = smbd_recv(server->smbd_conn, smb_msg);
-		else
-			length = sock_recvmsg(server->ssocket, smb_msg, 0);
+		length = sock_recvmsg(server->ssocket, smb_msg, 0);
 
 		spin_lock(&server->srv_lock);
 		if (server->tcpStatus == CifsExiting) {
@@ -1088,8 +1077,6 @@ clean_demultiplex_info(struct TCP_Server_Info *server)
 	wake_up_all(&server->request_q);
 	/* give those requests time to exit */
 	msleep(125);
-	if (cifs_rdma_enabled(server))
-		smbd_destroy(server);
 	if (server->ssocket) {
 		sock_release(server->ssocket);
 		server->ssocket = NULL;
@@ -1542,7 +1529,7 @@ match_port(struct TCP_Server_Info *server, struct sockaddr *addr)
 	__be16 port, *sport;
 
 	/* SMBDirect manages its own ports, don't match it here */
-	if (server->rdma)
+	if (cifs_rdma_enabled(server))
 		return true;
 
 	switch (addr->sa_family) {
@@ -1649,7 +1636,7 @@ static int match_server(struct TCP_Server_Info *server,
 	if (server->echo_interval != ctx->echo_interval * HZ)
 		return 0;
 
-	if (server->rdma != ctx->rdma)
+	if (cifs_rdma_enabled(server) != ctx->rdma)
 		return 0;
 
 	if (server->ignore_signature != ctx->ignore_signature)
@@ -1791,7 +1778,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
 	tcp_ses->noblocksnd = ctx->noblocksnd || ctx->rootfs;
 	tcp_ses->noautotune = ctx->noautotune;
 	tcp_ses->tcp_nodelay = ctx->sockopt_tcp_nodelay;
-	tcp_ses->rdma = ctx->rdma;
+	tcp_ses->ipproto = ctx->rdma ? IPPROTO_SMBDIRECT : IPPROTO_TCP;
 	tcp_ses->in_flight = 0;
 	tcp_ses->max_in_flight = 0;
 	tcp_ses->credits = 1;
@@ -1844,29 +1831,18 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
 	++tcp_ses->srv_count;
 	tcp_ses->echo_interval = ctx->echo_interval * HZ;
 
-	if (tcp_ses->rdma) {
-#ifndef CONFIG_CIFS_SMB_DIRECT
-		cifs_dbg(VFS, "CONFIG_CIFS_SMB_DIRECT is not enabled\n");
-		rc = -ENOENT;
-		goto out_err_crypto_release;
-#endif
-		tcp_ses->smbd_conn = smbd_get_connection(
-			tcp_ses, (struct sockaddr *)&ctx->dstaddr);
-		if (tcp_ses->smbd_conn) {
-			cifs_dbg(VFS, "RDMA transport established\n");
-			rc = 0;
-			goto smbd_connected;
-		} else {
+	rc = ip_connect(tcp_ses);
+	if (rc < 0) {
+		if (cifs_rdma_enabled(tcp_ses) && rc == -EPROTONOSUPPORT) {
+			cifs_dbg(VFS, "CONFIG_CIFS_SMB_DIRECT is not enabled\n");
 			rc = -ENOENT;
 			goto out_err_crypto_release;
 		}
-	}
-	rc = ip_connect(tcp_ses);
-	if (rc < 0) {
 		cifs_dbg(VFS, "Error connecting to socket. Aborting operation.\n");
 		goto out_err_crypto_release;
 	}
-smbd_connected:
+	if (cifs_rdma_enabled(tcp_ses))
+		cifs_dbg(VFS, "RDMA transport established\n");
 	/*
 	 * since we're in a cifs function already, we know that
 	 * this will succeed. No need for try_module_get().
@@ -3331,6 +3307,7 @@ generic_ip_connect(struct TCP_Server_Info *server)
 	struct sockaddr *saddr;
 	struct socket *socket;
 	int slen, sfamily;
+	const char *pname = cifs_rdma_enabled(server) ? "smbdirect" : "tcp";
 	__be16 sport;
 	int rc = 0;
 
@@ -3342,28 +3319,32 @@ generic_ip_connect(struct TCP_Server_Info *server)
 		sport = ipv6->sin6_port;
 		slen = sizeof(struct sockaddr_in6);
 		sfamily = AF_INET6;
-		cifs_dbg(FYI, "%s: connecting to [%pI6]:%d\n", __func__, &ipv6->sin6_addr,
-				ntohs(sport));
+		cifs_dbg(FYI, "%s: connecting with %s to [%pI6]:%d\n",
+			 __func__, pname, &ipv6->sin6_addr, ntohs(sport));
 	} else {
 		struct sockaddr_in *ipv4 = (struct sockaddr_in *)&server->dstaddr;
 
 		sport = ipv4->sin_port;
 		slen = sizeof(struct sockaddr_in);
 		sfamily = AF_INET;
-		cifs_dbg(FYI, "%s: connecting to %pI4:%d\n", __func__, &ipv4->sin_addr,
-				ntohs(sport));
+		cifs_dbg(FYI, "%s: connecting with %s to %pI4:%d\n",
+			 __func__, pname, &ipv4->sin_addr, ntohs(sport));
 	}
 
 	if (server->ssocket) {
-		socket = server->ssocket;
-	} else {
+		sock_release(server->ssocket);
+		server->ssocket = NULL;
+	}
+
+	{
 		struct net *net = cifs_net_ns(server);
 		struct sock *sk;
 
 		rc = sock_create_kern(net, sfamily, SOCK_STREAM,
-				      IPPROTO_TCP, &server->ssocket);
+				      server->ipproto, &server->ssocket);
 		if (rc < 0) {
-			cifs_server_dbg(VFS, "Error %d creating socket\n", rc);
+			cifs_server_dbg(VFS, "Error %d creating %s socket\n",
+					rc, pname);
 			return rc;
 		}
 
@@ -3371,7 +3352,7 @@ generic_ip_connect(struct TCP_Server_Info *server)
 		sk_net_refcnt_upgrade(sk);
 
 		/* BB other socket options to set KEEPALIVE, NODELAY? */
-		cifs_dbg(FYI, "Socket created\n");
+		cifs_dbg(FYI, "%s socket created\n", pname);
 		socket = server->ssocket;
 		socket->sk->sk_allocation = GFP_NOFS;
 		socket->sk->sk_use_task_frag = false;
@@ -3382,8 +3363,10 @@ generic_ip_connect(struct TCP_Server_Info *server)
 	}
 
 	rc = bind_socket(server);
-	if (rc < 0)
-		return rc;
+	if (rc < 0) {
+		cifs_dbg(FYI, "Error %d bind_socket() %s\n", rc, pname);
+		goto close_socket;
+	}
 
 	/*
 	 * Eventually check for other socket options to change from
@@ -3404,6 +3387,17 @@ generic_ip_connect(struct TCP_Server_Info *server)
 	if (server->tcp_nodelay)
 		tcp_sock_set_nodelay(socket->sk);
 
+	switch (server->ipproto) {
+	case IPPROTO_SMBDIRECT:
+		rc = smbd_prepare_socket(server, ntohs(sport));
+		if (rc < 0) {
+			cifs_dbg(FYI, "Error %d from smbd_prepare_socket(port=%u)\n",
+				 rc, ntohs(sport));
+			goto close_socket;
+		}
+		break;
+	}
+
 	cifs_dbg(FYI, "sndbuf %d rcvbuf %d rcvtimeo 0x%lx\n",
 		 socket->sk->sk_sndbuf,
 		 socket->sk->sk_rcvbuf, socket->sk->sk_rcvtimeo);
@@ -3418,11 +3412,8 @@ generic_ip_connect(struct TCP_Server_Info *server)
 	if (server->noblockcnt && rc == -EINPROGRESS)
 		rc = 0;
 	if (rc < 0) {
-		cifs_dbg(FYI, "Error %d connecting to server\n", rc);
-		trace_smb3_connect_err(server->hostname, server->conn_id, &server->dstaddr, rc);
-		sock_release(socket);
-		server->ssocket = NULL;
-		return rc;
+		cifs_dbg(FYI, "Error %d connecting with %s to server\n", rc, pname);
+		goto close_socket;
 	}
 	trace_smb3_connect_done(server->hostname, server->conn_id, &server->dstaddr);
 
@@ -3434,9 +3425,20 @@ generic_ip_connect(struct TCP_Server_Info *server)
 	 */
 	if (server->with_rfc1001 ||
 	    server->rfc1001_sessinit == 1 ||
-	    (server->rfc1001_sessinit == -1 && sport == htons(RFC1001_PORT)))
+	    (server->rfc1001_sessinit == -1 && sport == htons(RFC1001_PORT))) {
 		rc = ip_rfc1001_connect(server);
+		if (rc < 0) {
+			cifs_dbg(FYI, "Error %d from ip_rfc1001_connect\n", rc);
+			goto close_socket;
+		}
+	}
+
+	return rc;
 
+close_socket:
+	trace_smb3_connect_err(server->hostname, server->conn_id, &server->dstaddr, rc);
+	sock_release(socket);
+	server->ssocket = NULL;
 	return rc;
 }
 
@@ -3446,6 +3448,13 @@ ip_connect(struct TCP_Server_Info *server)
 	__be16 *sport;
 	struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr;
 	struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr;
+	u16 port1 = CIFS_PORT;
+	u16 port2 = RFC1001_PORT;
+
+	if (cifs_rdma_enabled(server)) {
+		port1 = SMBD_IWARP_PORT;
+		port2 = CIFS_PORT;
+	}
 
 	if (server->dstaddr.ss_family == AF_INET6)
 		sport = &addr6->sin6_port;
@@ -3455,15 +3464,15 @@ ip_connect(struct TCP_Server_Info *server)
 	if (*sport == 0) {
 		int rc;
 
-		/* try with 445 port at first */
-		*sport = htons(CIFS_PORT);
+		/* try with port1 at first */
+		*sport = htons(port1);
 
 		rc = generic_ip_connect(server);
 		if (rc >= 0)
 			return rc;
 
-		/* if it failed, try with 139 port */
-		*sport = htons(RFC1001_PORT);
+		/* if it failed, try with port2 */
+		*sport = htons(port2);
 	}
 
 	return generic_ip_connect(server);
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index a69e05f86d7e..8ca9f60ff429 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -97,9 +97,9 @@ static void cifs_prepare_write(struct netfs_io_subrequest *subreq)
 			      cifs_trace_rw_credits_write_prepare);
 
 #ifdef CONFIG_CIFS_SMB_DIRECT
-	if (server->smbd_conn) {
+	if (cifs_rdma_enabled(server)) {
 		const struct smbdirect_socket_parameters *sp =
-			smbd_get_parameters(server->smbd_conn);
+			smbd_get_parameters(server);
 
 		stream->sreq_max_segs = sp->max_frmr_depth;
 	}
@@ -191,9 +191,9 @@ static int cifs_prepare_read(struct netfs_io_subrequest *subreq)
 			      cifs_trace_rw_credits_read_submit);
 
 #ifdef CONFIG_CIFS_SMB_DIRECT
-	if (server->smbd_conn) {
+	if (cifs_rdma_enabled(server)) {
 		const struct smbdirect_socket_parameters *sp =
-			smbd_get_parameters(server->smbd_conn);
+			smbd_get_parameters(server);
 
 		rreq->io_streams[0].sreq_max_segs = sp->max_frmr_depth;
 	}
diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c
index 698bd27119ae..9e79439002e6 100644
--- a/fs/smb/client/sess.c
+++ b/fs/smb/client/sess.c
@@ -207,7 +207,7 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
 		list_for_each_entry_safe_from(iface, niface, &ses->iface_list,
 				    iface_head) {
 			/* do not mix rdma and non-rdma interfaces */
-			if (iface->rdma_capable != ses->server->rdma)
+			if (iface->rdma_capable != cifs_rdma_enabled(ses->server))
 				continue;
 
 			/* skip ifaces that are unusable */
@@ -395,7 +395,7 @@ cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server)
 		}
 
 		/* do not mix rdma and non-rdma interfaces */
-		if (iface->rdma_capable != server->rdma)
+		if (iface->rdma_capable != cifs_rdma_enabled(server))
 			continue;
 
 		if (!iface->is_active ||
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 509fcea28a42..208797de0c31 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -503,9 +503,9 @@ smb3_negotiate_wsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
 	wsize = ctx->got_wsize ? ctx->vol_wsize : SMB3_DEFAULT_IOSIZE;
 	wsize = min_t(unsigned int, wsize, server->max_write);
 #ifdef CONFIG_CIFS_SMB_DIRECT
-	if (server->rdma) {
+	if (cifs_rdma_enabled(server)) {
 		const struct smbdirect_socket_parameters *sp =
-			smbd_get_parameters(server->smbd_conn);
+			smbd_get_parameters(server);
 
 		if (server->sign)
 			/*
@@ -554,9 +554,9 @@ smb3_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
 	rsize = ctx->got_rsize ? ctx->vol_rsize : SMB3_DEFAULT_IOSIZE;
 	rsize = min_t(unsigned int, rsize, server->max_read);
 #ifdef CONFIG_CIFS_SMB_DIRECT
-	if (server->rdma) {
+	if (cifs_rdma_enabled(server)) {
 		const struct smbdirect_socket_parameters *sp =
-			smbd_get_parameters(server->smbd_conn);
+			smbd_get_parameters(server);
 
 		if (server->sign)
 			/*
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index 957aca2222b5..e954c75b36da 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -4472,7 +4472,7 @@ static inline bool smb3_use_rdma_offload(struct cifs_io_parms *io_parms)
 		return false;
 
 	/* we can only offload on an rdma connection */
-	if (!server->rdma || !server->smbd_conn)
+	if (!cifs_rdma_enabled(server))
 		return false;
 
 	/* we don't support signed offload yet */
@@ -4483,6 +4483,10 @@ static inline bool smb3_use_rdma_offload(struct cifs_io_parms *io_parms)
 	if (smb3_encryption_required(tcon))
 		return false;
 
+	/* this implicitly sets up server->rdma_readwrite_threshold */
+	if (!smbd_get_parameters(server))
+		return false;
+
 	/* offload also has its overhead, so only do it if desired */
 	if (io_parms->length < server->rdma_readwrite_threshold)
 		return false;
@@ -4540,7 +4544,7 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
 		struct smbdirect_buffer_descriptor_v1 *v1;
 		bool need_invalidate = server->dialect == SMB30_PROT_ID;
 
-		rdata->mr = smbd_register_mr(server->smbd_conn, &rdata->subreq.io_iter,
+		rdata->mr = smbd_register_mr(server, &rdata->subreq.io_iter,
 					     true, need_invalidate);
 		if (!rdata->mr)
 			return -EAGAIN;
@@ -5134,7 +5138,7 @@ smb2_async_writev(struct cifs_io_subrequest *wdata)
 		struct smbdirect_buffer_descriptor_v1 *v1;
 		bool need_invalidate = server->dialect == SMB30_PROT_ID;
 
-		wdata->mr = smbd_register_mr(server->smbd_conn, &wdata->subreq.io_iter,
+		wdata->mr = smbd_register_mr(server, &wdata->subreq.io_iter,
 					     false, need_invalidate);
 		if (!wdata->mr) {
 			rc = -EAGAIN;
diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c
index 9e67adcdc7d3..7d4fa896efc0 100644
--- a/fs/smb/client/smbdirect.c
+++ b/fs/smb/client/smbdirect.c
@@ -11,10 +11,6 @@
 #include "smb2proto.h"
 #include "../common/smbdirect/smbdirect_public.h"
 
-/* Port numbers for SMBD transport */
-#define SMB_PORT	445
-#define SMBD_PORT	5445
-
 /* Address lookup and resolve timeout in ms */
 #define RDMA_RESOLVE_TIMEOUT	5000
 
@@ -81,18 +77,9 @@ int rdma_readwrite_threshold = 4096;
 /* Transport logging functions
  * Logging are defined as classes. They can be OR'ed to define the actual
  * logging level via module parameter smbd_logging_class
- * e.g. cifs.smbd_logging_class=0xa0 will log all log_rdma_recv() and
- * log_rdma_event()
+ * e.g. cifs.smbd_logging_class=0xa0 will log all SMBDIRECT_LOG_RDMA_RECV and
+ * SMBDIRECT_LOG_RDMA_EVENT
  */
-#define LOG_OUTGOING			0x1
-#define LOG_INCOMING			0x2
-#define LOG_READ			0x4
-#define LOG_WRITE			0x8
-#define LOG_RDMA_SEND			0x10
-#define LOG_RDMA_RECV			0x20
-#define LOG_KEEP_ALIVE			0x40
-#define LOG_RDMA_EVENT			0x80
-#define LOG_RDMA_MR			0x100
 static unsigned int smbd_logging_class;
 module_param(smbd_logging_class, uint, 0644);
 MODULE_PARM_DESC(smbd_logging_class,
@@ -114,17 +101,6 @@ static bool smbd_logging_needed(struct smbdirect_socket *sc,
 	BUILD_BUG_SAME(ERR);
 	BUILD_BUG_SAME(INFO);
 #undef BUILD_BUG_SAME
-#define BUILD_BUG_SAME(x) BUILD_BUG_ON(x != SMBDIRECT_ ##x)
-	BUILD_BUG_SAME(LOG_OUTGOING);
-	BUILD_BUG_SAME(LOG_INCOMING);
-	BUILD_BUG_SAME(LOG_READ);
-	BUILD_BUG_SAME(LOG_WRITE);
-	BUILD_BUG_SAME(LOG_RDMA_SEND);
-	BUILD_BUG_SAME(LOG_RDMA_RECV);
-	BUILD_BUG_SAME(LOG_KEEP_ALIVE);
-	BUILD_BUG_SAME(LOG_RDMA_EVENT);
-	BUILD_BUG_SAME(LOG_RDMA_MR);
-#undef BUILD_BUG_SAME
 
 	if (lvl <= smbd_logging_level || cls & smbd_logging_class)
 		return true;
@@ -148,22 +124,7 @@ do {									\
 		cifs_dbg(VFS, "%s:%d " fmt, __func__, __LINE__, ##args);\
 } while (0)
 
-#define log_outgoing(level, fmt, args...) \
-		log_rdma(level, LOG_OUTGOING, fmt, ##args)
-#define log_incoming(level, fmt, args...) \
-		log_rdma(level, LOG_INCOMING, fmt, ##args)
-#define log_read(level, fmt, args...)	log_rdma(level, LOG_READ, fmt, ##args)
-#define log_write(level, fmt, args...)	log_rdma(level, LOG_WRITE, fmt, ##args)
-#define log_rdma_send(level, fmt, args...) \
-		log_rdma(level, LOG_RDMA_SEND, fmt, ##args)
-#define log_rdma_recv(level, fmt, args...) \
-		log_rdma(level, LOG_RDMA_RECV, fmt, ##args)
-#define log_keep_alive(level, fmt, args...) \
-		log_rdma(level, LOG_KEEP_ALIVE, fmt, ##args)
-#define log_rdma_event(level, fmt, args...) \
-		log_rdma(level, LOG_RDMA_EVENT, fmt, ##args)
-#define log_rdma_mr(level, fmt, args...) \
-		log_rdma(level, LOG_RDMA_MR, fmt, ##args)
+#define log_write(level, fmt, args...)	log_rdma(level, SMBDIRECT_LOG_WRITE, fmt, ##args)
 
 static int smbd_post_send_full_iter(struct smbdirect_socket *sc,
 				    struct smbdirect_send_batch *batch,
@@ -195,82 +156,24 @@ static int smbd_post_send_full_iter(struct smbdirect_socket *sc,
 	return bytes;
 }
 
-/*
- * Destroy the transport and related RDMA and memory resources
- * Need to go through all the pending counters and make sure on one is using
- * the transport while it is destroyed
- */
-void smbd_destroy(struct TCP_Server_Info *server)
-{
-	struct smbd_connection *info = server->smbd_conn;
-
-	if (!info) {
-		log_rdma_event(INFO, "rdma session already destroyed\n");
-		return;
-	}
-
-	smbdirect_socket_release(info->socket);
-
-	kfree(info);
-	server->smbd_conn = NULL;
-}
-
-/*
- * Reconnect this SMBD connection, called from upper layer
- * return value: 0 on success, or actual error code
- */
-int smbd_reconnect(struct TCP_Server_Info *server)
-{
-	log_rdma_event(INFO, "reconnecting rdma session\n");
-
-	if (!server->smbd_conn) {
-		log_rdma_event(INFO, "rdma session already destroyed\n");
-		goto create_conn;
-	}
-
-	/*
-	 * This is possible if transport is disconnected and we haven't received
-	 * notification from RDMA, but upper layer has detected timeout
-	 */
-	log_rdma_event(INFO, "disconnecting transport\n");
-	smbd_destroy(server);
-
-create_conn:
-	log_rdma_event(INFO, "creating rdma session\n");
-	server->smbd_conn = smbd_get_connection(
-		server, (struct sockaddr *) &server->dstaddr);
-
-	if (server->smbd_conn) {
-		cifs_dbg(VFS, "RDMA transport re-established\n");
-		trace_smb3_smbd_connect_done(server->hostname, server->conn_id, &server->dstaddr);
-		return 0;
-	}
-	trace_smb3_smbd_connect_err(server->hostname, server->conn_id, &server->dstaddr);
-	return -ENOENT;
-}
-
 /* Create a SMBD connection, called by upper layer */
-static struct smbd_connection *_smbd_get_connection(
-	struct TCP_Server_Info *server, struct sockaddr *dstaddr, int port)
+int smbd_prepare_socket(struct TCP_Server_Info *server, int port)
 {
-	struct net *net = cifs_net_ns(server);
-	struct smbd_connection *info;
-	struct smbdirect_socket *sc;
+	struct smbdirect_socket *sc = NULL;
 	struct smbdirect_socket_parameters init_params = {};
 	struct smbdirect_socket_parameters *sp;
-	__be16 *sport;
 	u64 port_flags = 0;
 	int ret;
 
 	switch (port) {
-	case SMBD_PORT:
+	case SMBD_IWARP_PORT:
 		/*
 		 * only allow iWarp devices
 		 * for port 5445.
 		 */
 		port_flags |= SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW;
 		break;
-	case SMB_PORT:
+	case CIFS_PORT:
 		/*
 		 * only allow InfiniBand, RoCEv1 or RoCEv2
 		 * devices for port 445.
@@ -281,6 +184,12 @@ static struct smbd_connection *_smbd_get_connection(
 		break;
 	}
 
+	if (server && cifs_rdma_enabled(server) && server->ssocket)
+		sc = smbdirect_socket_from_sock(server->ssocket);
+
+	if (WARN_ON_ONCE(!sc))
+		return -ENOTCONN;
+
 	/*
 	 * Create the initial parameters
 	 */
@@ -301,107 +210,40 @@ static struct smbd_connection *_smbd_get_connection(
 	sp->keepalive_interval_msec = smbd_keep_alive_interval * 1000;
 	sp->keepalive_timeout_msec = KEEPALIVE_RECV_TIMEOUT * 1000;
 
-	info = kzalloc_obj(*info);
-	if (!info)
-		return NULL;
-	ret = smbdirect_socket_create_kern(net, &sc);
-	if (ret)
-		goto socket_init_failed;
 	smbdirect_socket_set_logging(sc, NULL, smbd_logging_needed, smbd_logging_vaprintf);
 	ret = smbdirect_socket_set_initial_parameters(sc, sp);
 	if (ret)
-		goto set_params_failed;
+		return ret;
 	ret = smbdirect_socket_set_kernel_settings(sc, IB_POLL_SOFTIRQ, GFP_KERNEL);
 	if (ret)
-		goto set_settings_failed;
-
-	if (dstaddr->sa_family == AF_INET6)
-		sport = &((struct sockaddr_in6 *)dstaddr)->sin6_port;
-	else
-		sport = &((struct sockaddr_in *)dstaddr)->sin_port;
-
-	*sport = htons(port);
+		return ret;
 
-	ret = smbdirect_connect_sync(sc, dstaddr);
-	if (ret) {
-		log_rdma_event(ERR, "connect to %pISpsfc failed: %1pe\n",
-			       dstaddr, ERR_PTR(ret));
-		goto connect_failed;
-	}
-
-	info->socket = sc;
-	return info;
-
-connect_failed:
-set_settings_failed:
-set_params_failed:
-	smbdirect_socket_release(sc);
-socket_init_failed:
-	kfree(info);
-	return NULL;
+	return 0;
 }
 
-const struct smbdirect_socket_parameters *smbd_get_parameters(struct smbd_connection *conn)
+const struct smbdirect_socket_parameters *smbd_get_parameters(struct TCP_Server_Info *server)
 {
-	if (unlikely(!conn->socket)) {
-		static const struct smbdirect_socket_parameters zero_params;
+	struct smbdirect_socket *sc = NULL;
+	const struct smbdirect_socket_parameters *sp = NULL;
 
-		return &zero_params;
-	}
-
-	return smbdirect_socket_get_current_parameters(conn->socket);
-}
+	if (server && cifs_rdma_enabled(server) && server->ssocket)
+		sc = smbdirect_socket_from_sock(server->ssocket);
 
-struct smbd_connection *smbd_get_connection(
-	struct TCP_Server_Info *server, struct sockaddr *dstaddr)
-{
-	struct smbd_connection *ret;
-	const struct smbdirect_socket_parameters *sp;
-	int port = SMBD_PORT;
-
-try_again:
-	ret = _smbd_get_connection(server, dstaddr, port);
+	if (!sc) {
+		static const struct smbdirect_socket_parameters zero_params;
 
-	/* Try SMB_PORT if SMBD_PORT doesn't work */
-	if (!ret && port == SMBD_PORT) {
-		port = SMB_PORT;
-		goto try_again;
+		return &zero_params;
 	}
-	if (!ret)
-		return NULL;
 
-	sp = smbd_get_parameters(ret);
+	sp = smbdirect_socket_get_current_parameters(sc);
 
-	server->rdma_readwrite_threshold =
-		rdma_readwrite_threshold > sp->max_fragmented_send_size ?
-		sp->max_fragmented_send_size :
-		rdma_readwrite_threshold;
+	if (server->rdma_readwrite_threshold == 0)
+		server->rdma_readwrite_threshold =
+			rdma_readwrite_threshold > sp->max_fragmented_send_size ?
+			sp->max_fragmented_send_size :
+			rdma_readwrite_threshold;
 
-	return ret;
-}
-
-/*
- * Receive data from the transport's receive reassembly queue
- * All the incoming data packets are placed in reassembly queue
- * iter: the buffer to read data into
- * size: the length of data to read
- * return value: actual data read
- *
- * Note: this implementation copies the data from reassembly queue to receive
- * buffers used by upper layer. This is not the optimal code path. A better way
- * to do it is to not have upper layer allocate its receive buffers but rather
- * borrow the buffer from reassembly queue, and return it after data is
- * consumed. But this will require more changes to upper layer code, and also
- * need to consider packet boundaries while they still being reassembled.
- */
-int smbd_recv(struct smbd_connection *info, struct msghdr *msg)
-{
-	struct smbdirect_socket *sc = info->socket;
-
-	if (!smbdirect_connection_is_connected(sc))
-		return -ENOTCONN;
-
-	return smbdirect_connection_recvmsg(sc, msg, 0);
+	return sp;
 }
 
 /*
@@ -410,12 +252,11 @@ int smbd_recv(struct smbd_connection *info, struct msghdr *msg)
  * rqst: the data to write
  * return value: 0 if successfully write, otherwise error code
  */
-int smbd_send(struct TCP_Server_Info *server,
+static int smbd_send_locked(struct TCP_Server_Info *server,
 	int num_rqst, struct smb_rqst *rqst_array)
 {
-	struct smbd_connection *info = server->smbd_conn;
-	struct smbdirect_socket *sc = info->socket;
-	const struct smbdirect_socket_parameters *sp = smbd_get_parameters(info);
+	struct smbdirect_socket *sc = NULL;
+	const struct smbdirect_socket_parameters *sp = smbd_get_parameters(server);
 	struct smb_rqst *rqst;
 	struct iov_iter iter;
 	struct smbdirect_send_batch_storage bstorage;
@@ -424,6 +265,9 @@ int smbd_send(struct TCP_Server_Info *server,
 	int rc, i, rqst_idx;
 	int error = 0;
 
+	if (server && cifs_rdma_enabled(server) && server->ssocket)
+		sc = smbdirect_socket_from_sock(server->ssocket);
+
 	if (!smbdirect_connection_is_connected(sc))
 		return -EAGAIN;
 
@@ -508,6 +352,28 @@ int smbd_send(struct TCP_Server_Info *server,
 	return 0;
 }
 
+int smbd_send(struct TCP_Server_Info *server,
+	int num_rqst, struct smb_rqst *rqst_array)
+{
+	struct smbdirect_socket *sc = NULL;
+	struct sock *sk = NULL;
+	int ret;
+
+	if (server && cifs_rdma_enabled(server) && server->ssocket) {
+		sc = smbdirect_socket_from_sock(server->ssocket);
+		sk = server->ssocket->sk;
+	}
+
+	if (!smbdirect_connection_is_connected(sc))
+		return -EAGAIN;
+
+	lock_sock(sk);
+	ret = smbd_send_locked(server, num_rqst, rqst_array);
+	release_sock(sk);
+
+	return ret;
+}
+
 /*
  * Register memory for RDMA read/write
  * iter: the buffer to register memory with
@@ -515,11 +381,14 @@ int smbd_send(struct TCP_Server_Info *server,
  * need_invalidate: true if this MR needs to be locally invalidated after I/O
  * return value: the MR registered, NULL if failed.
  */
-struct smbdirect_mr_io *smbd_register_mr(struct smbd_connection *info,
+struct smbdirect_mr_io *smbd_register_mr(struct TCP_Server_Info *server,
 				 struct iov_iter *iter,
 				 bool writing, bool need_invalidate)
 {
-	struct smbdirect_socket *sc = info->socket;
+	struct smbdirect_socket *sc = NULL;
+
+	if (server && cifs_rdma_enabled(server) && server->ssocket)
+		sc = smbdirect_socket_from_sock(server->ssocket);
 
 	if (!smbdirect_connection_is_connected(sc))
 		return NULL;
@@ -546,15 +415,25 @@ void smbd_deregister_mr(struct smbdirect_mr_io *mr)
 
 void smbd_debug_proc_show(struct TCP_Server_Info *server, struct seq_file *m)
 {
-	if (!server->rdma)
+	struct smbdirect_socket *sc = NULL;
+
+	if (!cifs_rdma_enabled(server))
 		return;
 
-	if (!server->smbd_conn) {
+	if (server->ssocket) {
+		sc = smbdirect_socket_from_sock(server->ssocket);
+		/*
+		 * setup server->rdma_readwrite_threshold is required
+		 */
+		(void) smbd_get_parameters(server);
+	}
+
+	if (!sc) {
 		seq_puts(m, "\nSMBDirect transport not available");
 		return;
 	}
 
-	smbdirect_connection_legacy_debug_proc_show(server->smbd_conn->socket,
+	smbdirect_connection_legacy_debug_proc_show(sc,
 						    server->rdma_readwrite_threshold,
 						    m);
 }
diff --git a/fs/smb/client/smbdirect.h b/fs/smb/client/smbdirect.h
index 0017d5b2de44..44b37234d87c 100644
--- a/fs/smb/client/smbdirect.h
+++ b/fs/smb/client/smbdirect.h
@@ -8,7 +8,6 @@
 #define _SMBDIRECT_H
 
 #ifdef CONFIG_CIFS_SMB_DIRECT
-#define cifs_rdma_enabled(server)	((server)->rdma)
 
 #include "cifsglob.h"
 
@@ -23,29 +22,18 @@ extern int smbd_max_send_size;
 extern int smbd_send_credit_target;
 extern int smbd_receive_credit_max;
 
-struct smbd_connection {
-	struct smbdirect_socket *socket;
-};
+/* prepare a SMBDirect session */
+int smbd_prepare_socket(struct TCP_Server_Info *server, int port);
 
-/* Create a SMBDirect session */
-struct smbd_connection *smbd_get_connection(
-	struct TCP_Server_Info *server, struct sockaddr *dstaddr);
-
-const struct smbdirect_socket_parameters *smbd_get_parameters(struct smbd_connection *conn);
-
-/* Reconnect SMBDirect session */
-int smbd_reconnect(struct TCP_Server_Info *server);
-/* Destroy SMBDirect session */
-void smbd_destroy(struct TCP_Server_Info *server);
+const struct smbdirect_socket_parameters *smbd_get_parameters(struct TCP_Server_Info *server);
 
 /* Interface for carrying upper layer I/O through send/recv */
-int smbd_recv(struct smbd_connection *info, struct msghdr *msg);
 int smbd_send(struct TCP_Server_Info *server,
 	int num_rqst, struct smb_rqst *rqst);
 
 /* Interfaces to register and deregister MR for RDMA read/write */
 struct smbdirect_mr_io *smbd_register_mr(
-	struct smbd_connection *info, struct iov_iter *iter,
+	struct TCP_Server_Info *server, struct iov_iter *iter,
 	bool writing, bool need_invalidate);
 void smbd_mr_fill_buffer_descriptor(struct smbdirect_mr_io *mr,
 				    struct smbdirect_buffer_descriptor_v1 *v1);
@@ -54,13 +42,6 @@ void smbd_deregister_mr(struct smbdirect_mr_io *mr);
 void smbd_debug_proc_show(struct TCP_Server_Info *server, struct seq_file *m);
 
 #else
-#define cifs_rdma_enabled(server)	0
-struct smbd_connection {};
-static inline void *smbd_get_connection(
-	struct TCP_Server_Info *server, struct sockaddr *dstaddr) {return NULL;}
-static inline int smbd_reconnect(struct TCP_Server_Info *server) {return -1; }
-static inline void smbd_destroy(struct TCP_Server_Info *server) {}
-static inline int smbd_recv(struct smbd_connection *info, struct msghdr *msg) {return -1; }
 static inline int smbd_send(struct TCP_Server_Info *server, int num_rqst, struct smb_rqst *rqst) {return -1; }
 #endif
 
diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c
index 05f8099047e1..e64becee928f 100644
--- a/fs/smb/client/transport.c
+++ b/fs/smb/client/transport.c
@@ -253,7 +253,7 @@ int __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst,
 	if (cifs_rdma_enabled(server)) {
 		/* return -EAGAIN when connecting or reconnecting */
 		rc = -EAGAIN;
-		if (server->smbd_conn)
+		if (ssocket)
 			rc = smbd_send(server, num_rqst, rqst);
 		goto smbd_done;
 	}
-- 
2.43.0


^ permalink raw reply related

* [PATCH 8/8] smb: server: make use of IPPROTO_SMBDIRECT sockets
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
  To: linux-cifs, samba-technical
  Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
	David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
	Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
	linux-kernel
In-Reply-To: <cover.1775571957.git.metze@samba.org>

This reduces the function calls for special smbdirect features to
- smbdirect_socket_from_sock()
- smbdirect_socket_set_logging()
- smbdirect_socket_set_initial_parameters()
- smbdirect_socket_set_kernel_settings()
- smbdirect_socket_get_current_parameters()
- smbdirect_connection_is_connected()
- smbdirect_connection_rdma_xmit()

This will also make it easier to implement the QUIC
transport Henrique Carvalho is working on.

In the end the core smb handling should just make
use of a SOCK_STREAM socket, after the initial setup.

There's still a way to go, but almost all changes
will be done within the smbdirect code, e.g.
adding MSG_SPLICE_PAGES support or
splice_read/read_sock/read_skb.

But it's a good start, which will make changes
much easier.

Cc: Steve French <smfrench@gmail.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Long Li <longli@microsoft.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>
Cc: David Howells <dhowells@redhat.com>
Cc: Henrique Carvalho <henrique.carvalho@suse.com>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: David S. Miller <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>
Cc: Kuniyuki Iwashima <kuniyu@google.com>
Cc: Willem de Bruijn <willemb@google.com>
Cc: netdev@vger.kernel.org
Cc: Xin Long <lucien.xin@gmail.com>
Cc: quic@lists.linux.dev
Cc: linux-rdma@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
---
 fs/smb/server/transport_rdma.c | 119 +++++++++++++++++++--------------
 1 file changed, 70 insertions(+), 49 deletions(-)

diff --git a/fs/smb/server/transport_rdma.c b/fs/smb/server/transport_rdma.c
index 706a2c897948..64be48165cda 100644
--- a/fs/smb/server/transport_rdma.c
+++ b/fs/smb/server/transport_rdma.c
@@ -82,13 +82,13 @@ static struct smb_direct_listener {
 
 	struct task_struct	*thread;
 
-	struct smbdirect_socket *socket;
+	struct socket		*sock;
 } smb_direct_ib_listener, smb_direct_iw_listener;
 
 struct smb_direct_transport {
 	struct ksmbd_transport	transport;
 
-	struct smbdirect_socket *socket;
+	struct socket		*sock;
 };
 
 static bool smb_direct_logging_needed(struct smbdirect_socket *sc,
@@ -165,18 +165,23 @@ void init_smbd_max_io_size(unsigned int sz)
 unsigned int get_smbd_max_read_write_size(struct ksmbd_transport *kt)
 {
 	struct smb_direct_transport *t;
+	struct smbdirect_socket *sc;
 	const struct smbdirect_socket_parameters *sp;
 
 	if (kt->ops != &ksmbd_smb_direct_transport_ops)
 		return 0;
 
 	t = SMBD_TRANS(kt);
-	sp = smbdirect_socket_get_current_parameters(t->socket);
+	sc = smbdirect_socket_from_sock(t->sock);
+
+	if (!smbdirect_connection_is_connected(sc))
+		return 0;
+	sp = smbdirect_socket_get_current_parameters(sc);
 
 	return sp->max_read_write_size;
 }
 
-static struct smb_direct_transport *alloc_transport(struct smbdirect_socket *sc)
+static struct smb_direct_transport *alloc_transport(struct socket *client_sock)
 {
 	struct smb_direct_transport *t;
 	struct ksmbd_conn *conn;
@@ -184,7 +189,7 @@ static struct smb_direct_transport *alloc_transport(struct smbdirect_socket *sc)
 	t = kzalloc_obj(*t, KSMBD_DEFAULT_GFP);
 	if (!t)
 		return NULL;
-	t->socket = sc;
+	t->sock = client_sock;
 
 	conn = ksmbd_conn_alloc();
 	if (!conn)
@@ -209,13 +214,13 @@ static void smb_direct_free_transport(struct ksmbd_transport *kt)
 {
 	struct smb_direct_transport *t = SMBD_TRANS(kt);
 
-	smbdirect_socket_release(t->socket);
+	sock_release(t->sock);
 	kfree(t);
 }
 
 static void free_transport(struct smb_direct_transport *t)
 {
-	smbdirect_socket_shutdown(t->socket);
+	kernel_sock_shutdown(t->sock, SHUT_RDWR);
 	ksmbd_conn_free(KSMBD_TRANS(t)->conn);
 }
 
@@ -223,7 +228,6 @@ static int smb_direct_read(struct ksmbd_transport *t, char *buf,
 			   unsigned int size, int unused)
 {
 	struct smb_direct_transport *st = SMBD_TRANS(t);
-	struct smbdirect_socket *sc = st->socket;
 	struct msghdr msg = { .msg_flags = 0, };
 	struct kvec iov = {
 		.iov_base = buf,
@@ -231,9 +235,7 @@ static int smb_direct_read(struct ksmbd_transport *t, char *buf,
 	};
 	int ret;
 
-	iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, size);
-
-	ret = smbdirect_connection_recvmsg(sc, &msg, 0);
+	ret = kernel_recvmsg(st->sock, &msg, &iov, 1, size, 0);
 	if (ret == -ERESTARTSYS)
 		ret = -EINTR;
 	return ret;
@@ -244,13 +246,14 @@ static int smb_direct_writev(struct ksmbd_transport *t,
 			     bool need_invalidate, unsigned int remote_key)
 {
 	struct smb_direct_transport *st = SMBD_TRANS(t);
-	struct smbdirect_socket *sc = st->socket;
-	struct iov_iter iter;
+	struct msghdr msg = { .msg_flags = MSG_NOSIGNAL, };
+	struct smbdirect_cmsg_buffer cbuffer = { .msg_control = {0, }, };
+	u32 _remote_token = remote_key;
+	const u32 *remote_token = need_invalidate ? &_remote_token : NULL;
 
-	iov_iter_kvec(&iter, ITER_SOURCE, iov, niovs, buflen);
+	smbdirect_buffer_remote_invalidate_cmsg_prepare(&msg, &cbuffer, remote_token);
 
-	return smbdirect_connection_send_iter(sc, &iter, 0,
-					      need_invalidate, remote_key);
+	return kernel_sendmsg(st->sock, &msg, iov, niovs, buflen);
 }
 
 static int smb_direct_rdma_write(struct ksmbd_transport *t,
@@ -259,7 +262,10 @@ static int smb_direct_rdma_write(struct ksmbd_transport *t,
 				 unsigned int desc_len)
 {
 	struct smb_direct_transport *st = SMBD_TRANS(t);
-	struct smbdirect_socket *sc = st->socket;
+	struct smbdirect_socket *sc = smbdirect_socket_from_sock(st->sock);
+
+	if (!smbdirect_connection_is_connected(sc))
+		return -ENOTCONN;
 
 	return smbdirect_connection_rdma_xmit(sc, buf, buflen,
 					      desc, desc_len, false);
@@ -271,7 +277,10 @@ static int smb_direct_rdma_read(struct ksmbd_transport *t,
 				unsigned int desc_len)
 {
 	struct smb_direct_transport *st = SMBD_TRANS(t);
-	struct smbdirect_socket *sc = st->socket;
+	struct smbdirect_socket *sc = smbdirect_socket_from_sock(st->sock);
+
+	if (!smbdirect_connection_is_connected(sc))
+		return -ENOTCONN;
 
 	return smbdirect_connection_rdma_xmit(sc, buf, buflen,
 					      desc, desc_len, true);
@@ -280,7 +289,7 @@ static int smb_direct_rdma_read(struct ksmbd_transport *t,
 static void smb_direct_disconnect(struct ksmbd_transport *t)
 {
 	struct smb_direct_transport *st = SMBD_TRANS(t);
-	struct smbdirect_socket *sc = st->socket;
+	struct smbdirect_socket *sc = smbdirect_socket_from_sock(st->sock);
 
 	ksmbd_debug(RDMA, "Disconnecting sc=%p\n", sc);
 
@@ -290,23 +299,23 @@ static void smb_direct_disconnect(struct ksmbd_transport *t)
 static void smb_direct_shutdown(struct ksmbd_transport *t)
 {
 	struct smb_direct_transport *st = SMBD_TRANS(t);
-	struct smbdirect_socket *sc = st->socket;
+	struct smbdirect_socket *sc = smbdirect_socket_from_sock(st->sock);
 
 	ksmbd_debug(RDMA, "smb-direct shutdown sc=%p\n", sc);
 
-	smbdirect_socket_shutdown(sc);
+	kernel_sock_shutdown(st->sock, SHUT_RDWR);
 }
 
 static int smb_direct_new_connection(struct smb_direct_listener *listener,
-				     struct smbdirect_socket *client_sc)
+				     struct socket *client_sock)
 {
 	struct smb_direct_transport *t;
 	struct task_struct *handler;
 	int ret;
 
-	t = alloc_transport(client_sc);
+	t = alloc_transport(client_sock);
 	if (!t) {
-		smbdirect_socket_release(client_sc);
+		sock_release(client_sock);
 		return -ENOMEM;
 	}
 
@@ -328,22 +337,21 @@ static int smb_direct_new_connection(struct smb_direct_listener *listener,
 static int smb_direct_listener_kthread_fn(void *p)
 {
 	struct smb_direct_listener *listener = (struct smb_direct_listener *)p;
-	struct smbdirect_socket *client_sc = NULL;
+	struct socket *client_sock = NULL;
 
 	while (!kthread_should_stop()) {
-		struct proto_accept_arg arg = { .err = -EINVAL, };
-		long timeo = MAX_SCHEDULE_TIMEOUT;
+		int ret;
 
-		if (!listener->socket)
+		if (!listener->sock)
 			break;
-		client_sc = smbdirect_socket_accept(listener->socket, timeo, &arg);
-		if (!client_sc && arg.err == -EINVAL)
+		ret = kernel_accept(listener->sock, &client_sock, 0);
+		if (ret == -EINVAL)
 			break;
-		if (!client_sc)
+		if (ret)
 			continue;
 
 		ksmbd_debug(CONN, "connect success: accepted new connection\n");
-		smb_direct_new_connection(listener, client_sc);
+		smb_direct_new_connection(listener, client_sock);
 	}
 
 	ksmbd_debug(CONN, "releasing socket\n");
@@ -354,8 +362,12 @@ static void smb_direct_listener_destroy(struct smb_direct_listener *listener)
 {
 	int ret;
 
-	if (listener->socket)
-		smbdirect_socket_shutdown(listener->socket);
+	if (listener->sock) {
+		ret = kernel_sock_shutdown(listener->sock, SHUT_RDWR);
+		if (ret)
+			pr_err("Failed to shutdown socket: %d %1pe\n",
+			       ret, ERR_PTR(ret));
+	}
 
 	if (listener->thread) {
 		ret = kthread_stop(listener->thread);
@@ -364,9 +376,9 @@ static void smb_direct_listener_destroy(struct smb_direct_listener *listener)
 		listener->thread = NULL;
 	}
 
-	if (listener->socket) {
-		smbdirect_socket_release(listener->socket);
-		listener->socket = NULL;
+	if (listener->sock) {
+		sock_release(listener->sock);
+		listener->sock = NULL;
 	}
 
 	listener->port = 0;
@@ -376,6 +388,7 @@ static int smb_direct_listen(struct smb_direct_listener *listener,
 			     int port)
 {
 	struct net *net = current->nsproxy->net_ns;
+	struct socket *ksmbd_socket;
 	struct task_struct *kthread;
 	struct sockaddr_in sin = {
 		.sin_family		= AF_INET,
@@ -410,13 +423,21 @@ static int smb_direct_listen(struct smb_direct_listener *listener,
 		return -ENODEV;
 	}
 
-	ret = smbdirect_socket_create_kern(net, &sc);
+	ret = sock_create_kern(net, PF_INET, SOCK_STREAM, IPPROTO_SMBDIRECT,
+			       &ksmbd_socket);
 	if (ret) {
-		pr_err("smbdirect_socket_create_kern() failed: %d %1pe\n",
+		pr_err("Can't create IPPROTO_SMBDIRECT socket: %d %1pe\n",
 		       ret, ERR_PTR(ret));
 		return ret;
 	}
 
+	sc = smbdirect_socket_from_sock(ksmbd_socket);
+	if (WARN_ON_ONCE(!sc)) {
+		ret = -EINVAL;
+		pr_err("smbdirect_socket_from_sock returned NULL\n");
+		goto err;
+	}
+
 	/*
 	 * Create the initial parameters
 	 */
@@ -450,22 +471,22 @@ static int smb_direct_listen(struct smb_direct_listener *listener,
 		goto err;
 	}
 
-	ret = smbdirect_socket_bind(sc, (struct sockaddr *)&sin);
+	ret = kernel_bind(ksmbd_socket, (struct sockaddr_unsized *)&sin, sizeof(sin));
 	if (ret) {
-		pr_err("smbdirect_socket_bind() failed: %d %1pe\n",
+		pr_err("Failed to bind IPPROTO_SMBDIRECT socket: %d %1pe\n",
 		       ret, ERR_PTR(ret));
 		goto err;
 	}
 
-	ret = smbdirect_socket_listen(sc, 10);
+	ret = kernel_listen(ksmbd_socket, 10);
 	if (ret) {
-		pr_err("Port[%d] smbdirect_socket_listen() failed: %d %1pe\n",
+		pr_err("Port[%d] kernel_listen() error: %d %1pe\n",
 		       port, ret, ERR_PTR(ret));
 		goto err;
 	}
 
 	listener->port = port;
-	listener->socket = sc;
+	listener->sock = ksmbd_socket;
 
 	kthread = kthread_run(smb_direct_listener_kthread_fn,
 			      listener,
@@ -489,7 +510,7 @@ int ksmbd_rdma_init(void)
 	int ret;
 
 	smb_direct_ib_listener = smb_direct_iw_listener = (struct smb_direct_listener) {
-		.socket = NULL,
+		.sock = NULL,
 	};
 
 	ret = smb_direct_listen(&smb_direct_ib_listener,
@@ -499,8 +520,8 @@ int ksmbd_rdma_init(void)
 		goto err;
 	}
 
-	ksmbd_debug(RDMA, "InfiniBand/RoCEv1/RoCEv2 RDMA listener. socket=%p\n",
-		    smb_direct_ib_listener.socket);
+	ksmbd_debug(RDMA, "InfiniBand/RoCEv1/RoCEv2 RDMA listener. sock=%p\n",
+		    smb_direct_ib_listener.sock);
 
 	ret = smb_direct_listen(&smb_direct_iw_listener,
 				SMB_DIRECT_PORT_IWARP);
@@ -509,8 +530,8 @@ int ksmbd_rdma_init(void)
 		goto err;
 	}
 
-	ksmbd_debug(RDMA, "iWarp RDMA listener. socket=%p\n",
-		    smb_direct_iw_listener.socket);
+	ksmbd_debug(RDMA, "iWarp RDMA listener. sock=%p\n",
+		    smb_direct_iw_listener.sock);
 
 	return 0;
 err:
-- 
2.43.0


^ permalink raw reply related

* [PATCH 0/8] smb: add kernel internal IPPROTO_SMBDIRECT
From: Stefan Metzmacher @ 2026-04-07 14:46 UTC (permalink / raw)
  To: linux-cifs, samba-technical
  Cc: metze, Steve French, Tom Talpey, Long Li, Namjae Jeon,
	David Howells, Henrique Carvalho, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
	Willem de Bruijn, netdev, Xin Long, quic, linux-rdma,
	linux-kernel, Mark Brown, linux-next

Hi,

as the work to unify the smbdirect code
between cifs.ko and ksmbd.ko into an smbdirect.ko
is in linux-next for a while this is the next
step to also share the code with userspace
e.g. Samba as client and server.

The SMBDIRECT protocol, defined in [MS-SMBD] by Microsoft.
It is used as wrapper around RDMA in order to provide a transport for SMB3,
but Microsoft also uses it as transport for other protocols.

SMBDIRECT works over Infiniband, RoCE and iWarp.  RoCEv2 is based on IP/UDP
and iWarp is based on IP/TCP, so these use IP addresses natively.
Infiniband and RoCEv1 require IPOIB in order to be used for SMBDIRECT.

So instead of adding a PF_SMBDIRECT, which would only use AF_INET[6],
we use IPPROTO_SMBDIRECT instead, this uses a number not
allocated from IANA, as it would not appear in an IP header.

This is similar to IPPROTO_SMC, IPPROTO_MPTCP and IPPROTO_QUIC,
which are linux specific values for the socket() syscall.

  socket(AF_INET, SOCK_STREAM, IPPROTO_SMBDIRECT);
  socket(AF_INET6, SOCK_STREAM, IPPROTO_SMBDIRECT);

This will allow the existing smbdirect code used by
cifs.ko and ksmbd.ko to be moved behind the socket layer [1],
so that there's less special handling. Only sock_sendmsg()
sock_recvmsg() are used, so that the main stream handling
is done all the same for tcp, smbdirect and later also quic.

The special RDMA read/write handling will be via direct
function calls as they are currently done for the in kernel
consumers.

As a start __sock_create(kern=0)/sk->sk_kern_sock == 0 will
still cause a -EPROTONOSUPPORT. So only in kernel consumers
will be supported for now.

For now the core smbdirect code still supports both
modes, direct calls in indirect via the socket layer.
The core code uses if (sc->sk.sk_family) as indication
for the new socket mode. Once cifs.ko and ksmbd.ko
are converted we can remove the old mode slowly,
but I'll deferr that to a future patchset.

There's still a way to go in order to make this
as generic as tcp and quic e.g. adding MSG_SPLICE_PAGES support or
splice_read/read_sock/read_skb.

But it's a good start, which will make changes much easier.

This patchset is based on top of the smbdirect.ko patches
in ksmbd-for-next and it's also based on netdev-next
because it needs this commit from there:
8341c989ac77d712c7d6e2bce29e8a4bcb2eeae4
"net: remove addr_len argument of recvmsg() handlers"

Patches 1-4 are some preparation fixes for the
existing smbdirect.ko patchset. We may
squash them into the existing patches before
sending them to Linus. But for now I kept
them separately. I also don't cc the network and rdma
people on these...

Patch 5 defines IPPROTO_SMBDIRECT and SOL_SMBDIRECT
constants (and also reserve the number 288 for SOL_QUIC
as applications are already using that and we don't
want to conflict)

Patch 6 adds basic IPPROTO_SMBDIRECT layering
on top of the existing smbdirect code.
This is just enough in order to let cifs.ko
and ksmbd.ko use it in the following commits,
so userspace still sees -EPROTONOSUPPORT.

Patch 7 converts cifs.ko to use IPPROTO_SMBDIRECT
as much as currently possible.
Using sock_sendmsg is not yet possible, because of the
tcp_sock_set_cork usage, but that will change in future.

Patch 8 converts ksmbd.ko to use IPPROTO_SMBDIRECT.

Because of the need for netdev-next commit
8341c989ac77d712c7d6e2bce29e8a4bcb2eeae4
"net: remove addr_len argument of recvmsg() handlers"
it's a bit tricky to prepare something that would not
cause problems in linux-next.

We could merge in netdev-next completely,
but I saw commit 00f03539e3d97af925abf42992d8c46167d54243
in next-20260406, indicates some resolved conflicts
with (at least) the rdma tree, which comes
just before netdev-next, while ksmbd-for-next is merged
before all of them. So merging netdev-next into
ksmbd-for-next changes the order of netdev-next vs. rdma.

Or we add a new branch smbdirect-for-next that's
merged into linux-next after netdev-next.

Or a bit hacky but likely easier, ksmbd-for-next
just cherry-picks 8341c989ac77d712c7d6e2bce29e8a4bcb2eeae4
"net: remove addr_len argument of recvmsg() handlers"
and that cherry picked commit is reverted as
last commit in ksmbd-for-next, or only in linux-next
right before netdev-next is merged.

For now I have the patches in a branch with a
cherry picked version of the needed commit only, see
for-7.1/ipproto-smbdirect-20260407-v1 at commit:
e1972e6f1fda9842c5724b7daf4a2aa7779901a5
git fetch https://git.samba.org/metze/linux/wip.git for-7.1/ipproto-smbdirect-20260407-v1
https://git.samba.org/?p=metze/linux/wip.git;a=shortlog;h=refs/heads/for-7.1/ipproto-smbdirect-20260407-v1

It would be great to get this somehow into linux-next soon :-)

Stefan Metzmacher (8):
  smb: smbdirect: change
    smbdirect_socket_parameters.{initiator_depth,responder_resources} to
    __u16
  smb: smbdirect: fix copyright header of smbdirect.h
  smb: smbdirect: fix the logic in smbdirect_socket_destroy_sync()
    without an error
  smb: smbdirect: let smbdirect_connection_deregister_mr_io unlock while
    waiting
  net: define IPPROTO_SMBDIRECT and SOL_SMBDIRECT constants
  smb: smbdirect: add in kernel only support for IPPROTO_SMBDIRECT
  smb: client: make use of IPPROTO_SMBDIRECT sockets
  smb: server: make use of IPPROTO_SMBDIRECT sockets

 fs/smb/client/cifs_debug.c                    |    2 +-
 fs/smb/client/cifsfs.c                        |    2 +-
 fs/smb/client/cifsglob.h                      |    7 +-
 fs/smb/client/connect.c                       |  123 +-
 fs/smb/client/file.c                          |    8 +-
 fs/smb/client/sess.c                          |    4 +-
 fs/smb/client/smb2ops.c                       |    8 +-
 fs/smb/client/smb2pdu.c                       |   10 +-
 fs/smb/client/smbdirect.c                     |  275 +--
 fs/smb/client/smbdirect.h                     |   27 +-
 fs/smb/client/transport.c                     |    2 +-
 fs/smb/common/smbdirect/Makefile              |    1 +
 fs/smb/common/smbdirect/smbdirect.h           |   69 +-
 fs/smb/common/smbdirect/smbdirect_accept.c    |   14 +-
 .../common/smbdirect/smbdirect_connection.c   |   58 +
 fs/smb/common/smbdirect/smbdirect_devices.c   |    2 +-
 fs/smb/common/smbdirect/smbdirect_internal.h  |   59 +-
 fs/smb/common/smbdirect/smbdirect_listen.c    |   49 +-
 fs/smb/common/smbdirect/smbdirect_main.c      |   45 +
 fs/smb/common/smbdirect/smbdirect_mr.c        |   18 +
 fs/smb/common/smbdirect/smbdirect_proto.c     | 1549 +++++++++++++++++
 fs/smb/common/smbdirect/smbdirect_public.h    |    3 +
 fs/smb/common/smbdirect/smbdirect_rw.c        |   29 +-
 fs/smb/common/smbdirect/smbdirect_socket.c    |  180 +-
 fs/smb/common/smbdirect/smbdirect_socket.h    |   26 +-
 fs/smb/server/transport_rdma.c                |  119 +-
 include/linux/socket.h                        |    2 +
 include/uapi/linux/in.h                       |    2 +
 28 files changed, 2320 insertions(+), 373 deletions(-)
 create mode 100644 fs/smb/common/smbdirect/smbdirect_proto.c

-- 
2.43.0


^ permalink raw reply

* Re: [PATCH net-next v4 1/2] net: hsr: require valid EOT supervision TLV
From: Felix Maurer @ 2026-04-07 15:04 UTC (permalink / raw)
  To: Fernando Fernandez Mancera, Luka Gejak
  Cc: davem, edumazet, kuba, pabeni, netdev, horms
In-Reply-To: <4cce92eb-61a0-4869-8a30-b86d65e8a675@suse.de>

On Thu, Apr 02, 2026 at 01:53:14AM +0200, Fernando Fernandez Mancera wrote:
> On 4/1/26 6:59 PM, Luka Gejak wrote:
> > On Wed Apr 1, 2026 at 4:47 PM CEST, Fernando Fernandez Mancera wrote:
> > > On 4/1/26 11:23 AM, luka.gejak@linux.dev wrote:
> > > > From: Luka Gejak <luka.gejak@linux.dev>
> > > >
> > > > Supervision frames are only valid if terminated with a zero-length EOT
> > > > TLV. The current check fails to reject non-EOT entries as the terminal
> > > > TLV, potentially allowing malformed supervision traffic.
> > > >
> > > > Fix this by strictly requiring the terminal TLV to be HSR_TLV_EOT
> > > > with a length of zero.
> > > >
> > > > Reviewed-by: Felix Maurer <fmaurer@redhat.com>
> > > > Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
> > > > ---
> > > >    net/hsr/hsr_forward.c | 41 ++++++++++++++++++++++-------------------
> > > >    1 file changed, 22 insertions(+), 19 deletions(-)
> > > >
> > > > diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
> > > > index 0aca859c88cb..17b705235c4a 100644
> > > > --- a/net/hsr/hsr_forward.c
> > > > +++ b/net/hsr/hsr_forward.c
> > > > @@ -82,39 +82,42 @@ static bool is_supervision_frame(struct hsr_priv *hsr, struct sk_buff *skb)
> > > >    	    hsr_sup_tag->tlv.HSR_TLV_length != sizeof(struct hsr_sup_payload))
> > > >    		return false;
> > > > -	/* Get next tlv */
> > > > +	/* Advance past the first TLV payload to reach next TLV header */
> > > >    	total_length += hsr_sup_tag->tlv.HSR_TLV_length;
> > > > -	if (!pskb_may_pull(skb, total_length))
> > > > +	/* Linearize next TLV header before access */
> > > > +	if (!pskb_may_pull(skb, total_length + sizeof(struct hsr_sup_tlv)))
> > > >    		return false;
> > > >    	skb_pull(skb, total_length);
> > > >    	hsr_sup_tlv = (struct hsr_sup_tlv *)skb->data;
> > > >    	skb_push(skb, total_length);
> > > > -	/* if this is a redbox supervision frame we need to verify
> > > > -	 * that more data is available
> > > > +	/* Walk through TLVs to find end-of-TLV marker, skipping any unknown
> > > > +	 * extension TLVs to maintain forward compatibility.
> > > >    	 */
> > > > -	if (hsr_sup_tlv->HSR_TLV_type == PRP_TLV_REDBOX_MAC) {
> > > > -		/* tlv length must be a length of a mac address */
> > > > -		if (hsr_sup_tlv->HSR_TLV_length != sizeof(struct hsr_sup_payload))
> > > > -			return false;
> > > > +	for (;;) {
> > > > +		if (hsr_sup_tlv->HSR_TLV_type == HSR_TLV_EOT &&
> > > > +		    hsr_sup_tlv->HSR_TLV_length == 0)
> > > > +			return true;
> > >
> > > I do not follow this approach, why a loop? From IEC 62439-3, I do not
> > > understand that supervision frames could have multiple
> > > PRP_TLV_REDBOX_MAC TLVs. The current code handles the TLVs correctly.
> > >
> > > Which makes me wonder, how are you testing this? Do you have some
> > > hardware with HSR/PRP support that is sending these frames? If so, which
> > > one? Are you testing this using a HSR/PRP environment with purely Linux
> > > devices?
> > >
> > > Thanks,
> > > Fernando.
> > >
> > > > -		/* make sure another tlv follows */
> > > > -		total_length += sizeof(struct hsr_sup_tlv) + hsr_sup_tlv->HSR_TLV_length;
> > > > -		if (!pskb_may_pull(skb, total_length))
> > > > +		/* Validate known TLV types */
> > > > +		if (hsr_sup_tlv->HSR_TLV_type == PRP_TLV_REDBOX_MAC) {
> > > > +			if (hsr_sup_tlv->HSR_TLV_length !=
> > > > +			    sizeof(struct hsr_sup_payload))
> > > > +				return false;
> > > > +		}
> > > > +
> > > > +		/* Advance past current TLV: header + payload */
> > > > +		total_length += sizeof(struct hsr_sup_tlv) +
> > > > +				hsr_sup_tlv->HSR_TLV_length;
> > > > +		/* Linearize next TLV header before access */
> > > > +		if (!pskb_may_pull(skb,
> > > > +				   total_length + sizeof(struct hsr_sup_tlv)))
> > > >    			return false;
> > > > -		/* get next tlv */
> > > >    		skb_pull(skb, total_length);
> > > >    		hsr_sup_tlv = (struct hsr_sup_tlv *)skb->data;
> > > >    		skb_push(skb, total_length);
> > > >    	}
> >
> > Hi Fernando,
> >
> > You are right that IEC 62439-3 does not specify multiple
> > PRP_TLV_REDBOX_MAC TLVs. My intention with the loop was not to handle
> > multiple RedBox MACs, but rather to make the parser robust against
> > unknown TLV types. If a future revision of the standard or a vendor
> > extension introduces a new TLV, the loop allows the kernel to safely
> > skip over unrecognized TLVs by reading their length, ensuring it can
> > still validate the HSR_TLV_EOT marker at the end.

Hi Fernando, Luka,

> AFAIU, the TLVs must be in the right order. I don't know, it doesn't sound
> very convincing to me that we are anticipating to new TLVs. HSR/PRP isn't a
> very active protocol and it has few users in Kernel probably compare to
> other protocols because it is used in a very specific industry domain.

I agree with this. IMHO, the supervision frames should look the way they
are described in the standard, i.e., the TLVs ordered as the current
code expects them.

Luka, I'm not sure what your stance is to that? On one hand, you are
proposing a patch making the checks more strict (TLV_END && length ==
0), on the other hand you are proposing a patch accepting supervision
frames with random new or proprietary TLVs.

> If a new revision of the protocol specs is released we can always update our
> implementation.

This, especially because the supervision frames have an explicit version
number field (something we don't check at the moment, but probably
should in the future).

> Anyway, since Felix reviewed the initial patch let's wait for his review.

I'll add more notes in another reply, directly to the patch.

> > However, if the preference for the HSR subsystem is strict adherence to
> > only currently defined TLVs over forward compatibility, I completely
> > understand.
> >
> > Furthermore, I am testing this using a purely Linux environment by
> > using a virtual HSR environment on Arch Linux. I set up two network
> > namespaces connected via veth pairs and instantiated HSR interfaces.
> >
> > The nodes successfully synchronized and maintained the connection. I
> > confirmed this by observing the expected duplicate packets (DUP!)
> > during ping tests between namespaces and by verifying that supervision
> > frames were correctly parsed, allowing the nodes to populate their
> > remote node tables.

Side note: hsr exposes the node table through debugfs, that's maybe a
better way to test/verify such things.

Thanks,
   Felix


^ permalink raw reply

* [PATCH v2 net-next] net: dropreason: add MACVLAN_BROADCAST_BACKLOG and IPVLAN_MULTICAST_BACKLOG
From: Eric Dumazet @ 2026-04-07 15:07 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, Joe Damato, netdev, eric.dumazet, Eric Dumazet

ipvlan and macvlan use queues to process broadcast/multicast packets
from a work queue.

Under attack these queues can drop packets.

Add MACVLAN_BROADCAST_BACKLOG drop_reason for macvlan broadcast queue.

Add IPVLAN_MULTICAST_BACKLOG drop_reason for ipvlan multicast queue.

Use different reasons as some deployments use both ipvlan and macvlan.

Also change ipvlan_rcv_frame() to use SKB_DROP_REASON_DEV_READY
when the device is not UP.

Signed-off-by: Eric Dumazet <edumazet@google.com>
---
 drivers/net/ipvlan/ipvlan_core.c |  4 ++--
 drivers/net/macvlan.c            |  2 +-
 include/net/dropreason-core.h    | 12 ++++++++++++
 3 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c
index 68fdb42068d0060c67d751b869a059e8cb1b6bd1..5131d722c476eaa6149c114bcfc7267ea31d7242 100644
--- a/drivers/net/ipvlan/ipvlan_core.c
+++ b/drivers/net/ipvlan/ipvlan_core.c
@@ -337,7 +337,7 @@ static int ipvlan_rcv_frame(struct ipvl_addr *addr, struct sk_buff **pskb,
 	 */
 	if (local) {
 		if (unlikely(!(dev->flags & IFF_UP))) {
-			kfree_skb(skb);
+			kfree_skb_reason(skb, SKB_DROP_REASON_DEV_READY);
 			goto out;
 		}
 
@@ -596,7 +596,7 @@ static void ipvlan_multicast_enqueue(struct ipvl_port *port,
 	} else {
 		spin_unlock(&port->backlog.lock);
 		dev_core_stats_rx_dropped_inc(skb->dev);
-		kfree_skb(skb);
+		kfree_skb_reason(skb, SKB_DROP_REASON_IPVLAN_MULTICAST_BACKLOG);
 	}
 }
 
diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
index 54c514acacc5e24fcdb88904f41fd1d29b1e34d0..9f90c598649d548ee12b0a9806c825f96a4a4d63 100644
--- a/drivers/net/macvlan.c
+++ b/drivers/net/macvlan.c
@@ -386,7 +386,7 @@ static void macvlan_broadcast_enqueue(struct macvlan_port *port,
 	return;
 
 free_nskb:
-	kfree_skb(nskb);
+	kfree_skb_reason(nskb, SKB_DROP_REASON_MACVLAN_BROADCAST_BACKLOG);
 err:
 	dev_core_stats_rx_dropped_inc(skb->dev);
 }
diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index de61dd5dbfd9dc7d91d22d79a510d42fb69eb60a..5ad4a34b527486d3302238634f5a1a0af26db3db 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -69,6 +69,8 @@
 	FN(QDISC_DROP)			\
 	FN(QDISC_BURST_DROP)		\
 	FN(CPU_BACKLOG)			\
+	FN(MACVLAN_BROADCAST_BACKLOG)		\
+	FN(IPVLAN_MULTICAST_BACKLOG)		\
 	FN(XDP)				\
 	FN(TC_INGRESS)			\
 	FN(UNHANDLED_PROTO)		\
@@ -382,6 +384,16 @@ enum skb_drop_reason {
 	 * netdev_max_backlog in net.rst) or RPS flow limit
 	 */
 	SKB_DROP_REASON_CPU_BACKLOG,
+	/**
+	 * @SKB_DROP_REASON_MACVLAN_BROADCAST_BACKLOG: failed to enqueue the skb
+	 * to macvlan broadcast queue.
+	 */
+	SKB_DROP_REASON_MACVLAN_BROADCAST_BACKLOG,
+	/**
+	 * @SKB_DROP_REASON_IPVLAN_MULTICAST_BACKLOG: failed to enqueue the skb
+	 * to ipvlan multicast queue.
+	 */
+	SKB_DROP_REASON_IPVLAN_MULTICAST_BACKLOG,
 	/** @SKB_DROP_REASON_XDP: dropped by XDP in input path */
 	SKB_DROP_REASON_XDP,
 	/** @SKB_DROP_REASON_TC_INGRESS: dropped in TC ingress HOOK */
-- 
2.53.0.1213.gd9a14994de-goog


^ permalink raw reply related

* Re: [PATCH net v6 4/4] macsec: Support VLAN-filtering lower devices
From: Cosmin Ratiu @ 2026-04-07 15:07 UTC (permalink / raw)
  To: sd@queasysnail.net, pabeni@redhat.com
  Cc: andrew+netdev@lunn.ch, davem@davemloft.net,
	linux-kselftest@vger.kernel.org, Dragos Tatulea, shuah@kernel.org,
	sdf@fomichev.me, kuba@kernel.org, horms@kernel.org,
	edumazet@google.com, netdev@vger.kernel.org
In-Reply-To: <ac6BsG4IhAajip1s@krikkit>

On Thu, 2026-04-02 at 16:48 +0200, Sabrina Dubroca wrote:
> 2026-03-30, 16:01:30 +0300, Cosmin Ratiu wrote:
> > @@ -2616,14 +2616,22 @@ static int macsec_update_offload(struct
> > net_device *dev, enum macsec_offload off
> >  	if (!ops)
> >  		return -EOPNOTSUPP;
> >  
> > -	macsec->offload = offload;
> > -
> >  	ctx.secy = &macsec->secy;
> >  	ret = offload == MACSEC_OFFLOAD_OFF ? macsec_offload(ops-
> > >mdo_del_secy, &ctx)
> >  					    : macsec_offload(ops-
> > >mdo_add_secy, &ctx);
> > -	if (ret) {
> > -		macsec->offload = prev_offload;
> > +	if (ret)
> >  		return ret;
> > +
> > +	/* Remove VLAN filters when disabling offload. */
> > +	if (offload == MACSEC_OFFLOAD_OFF) {
> > +		vlan_drop_rx_ctag_filter_info(dev);
> > +		vlan_drop_rx_stag_filter_info(dev);
> > +	}
> > +	macsec->offload = offload;
> > +	/* Add VLAN filters when enabling offload. */
> > +	if (prev_offload == MACSEC_OFFLOAD_OFF) {
> > +		vlan_get_rx_ctag_filter_info(dev);
> > +		vlan_get_rx_stag_filter_info(dev);
> 
> Paolo pointed me to the sashiko review for this patch
> https://sashiko.dev/#/patchset/20260330130130.989236-1-cratiu%40nvidia.com

I should make it a habit to go and look up these reports myself.

> 
> A simple way to trigger this is to do s/VLAN_N_VID/500/ in
> nsim_vlan_rx_*_vid.
> 
> For example:
> 
> echo 1 > /sys/bus/netdevsim/new_device
> ip link add link eni1np1 macsec0 type macsec
> ip link add link macsec0 macsec0.1 type vlan id 1
> ip link add link macsec0 macsec0.1000 type vlan id 1000
> ip link set macsec0 type macsec offload mac
> cat /sys/kernel/debug/netdevsim/netdevsim1/ports/0/vlan      # empty
> 
> 
> If this happens on a real device, the VLAN filters will be broken.
> I'm
> not sure what the right behavior would be:
> 
> 1. reject the request to enable offload
> 2. switch to promiscuous mode

I implemented and tested option 1. In the unlikely scenario adding VLAN
filters prevents offloading, it's better for the driver to be explicit
and let the user turn on promisc mode themselves. Keeping track of
whether VLAN filters failed and promisc was used as a fallback adds
some extra complexity. Plus, I am not sure it is the right place for
this decision. What would be the point of IFF_UNICAST_FLT then?

Please let me know if you agree with this approach, so I can send v8
with it.

> OTOH maybe we don't need to care, since __netdev_update_features also
> (kind of) ignores those errors:
> 
> echo 1 > /sys/bus/netdevsim/new_device
> ethtool -K eni1np1 rx-vlan-filter off
> ip link add link eni1np1 eni1np1.1 type vlan id 1
> ip link add link eni1np1 eni1np1.1000 type vlan id 1000
> cat /sys/kernel/debug/netdevsim/netdevsim1/ports/0/vlan      # empty
> as expected
> ethtool -K eni1np1 rx-vlan-filter on                         #
> succeeds
> ethtool -k eni1np1 | grep rx-vlan-filter                     # "rx-
> vlan-filter: on"
> cat /sys/kernel/debug/netdevsim/netdevsim1/ports/0/vlan      # still
> empty because id=1000 was rejected
>                                                              # and
> everything got rolled back
> ip link add link eni1np1 eni1np1.123 type vlan id 123        #
> succeeds
> cat /sys/kernel/debug/netdevsim/netdevsim1/ports/0/vlan      # only
> "ctag 123"
> 
> 
> [at this point running
>     ip link del eni1np1.1
> or
>     ethtool -K eni1np1 rx-vlan-filter off
> will splat because vlan_filter_push_vids did a rollback/never added
> id=1, and now we call vlan_kill_rx_filter_info, but that's specific
> to
> this vid limit]
> 

Well, in this case we have the chance to do something nicer (even
proper error message back to the user via extack) for a small
complexity cost. Perhaps the VLAN filter handling could be improved
separately.

Cosmin.

^ permalink raw reply

* [PATCH v4 net-next] net: use get_random_u{16,32,64}() where appropriate
From: David Carlier @ 2026-04-07 15:07 UTC (permalink / raw)
  To: Jakub Kicinski, David S . Miller, Eric Dumazet, Paolo Abeni
  Cc: Andrew Lunn, Simon Horman, Ilya Dryomov, Johannes Berg,
	Matthieu Baerts, Mat Martineau, Geliang Tang, Aaron Conole,
	Ilya Maximets, Marcelo Ricardo Leitner, Xin Long, Jon Maloy,
	netdev, linux-wireless, mptcp, dev, linux-sctp, tipc-discussion,
	linux-kernel, David Carlier

Use the typed random integer helpers instead of
get_random_bytes() when filling a single integer variable.
The helpers return the value directly, require no pointer
or size argument, and better express intent.

Skipped sites writing into __be16 (netdevsim) and __le64
(ceph) fields where a direct assignment would trigger
sparse endianness warnings.

Signed-off-by: David Carlier <devnexen@gmail.com>
Reviewed-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---

Notes:
    Changes v3 -> v4:
    - Dropped net/ceph/auth_x.c site: client_challenge is __le64,
      direct assignment triggers sparse endianness warning
      (Matthieu Baerts)
    - Added Reviewed-by from Matthieu Baerts for net/mptcp changes
    
    v3: https://lore.kernel.org/netdev/20260405154816.4774-1-devnexen@gmail.com/
    v2: https://lore.kernel.org/netdev/Z/BfE0zn+DJxhBH7@debian/

 drivers/net/netdevsim/psample.c | 4 ++--
 net/core/net_namespace.c        | 2 +-
 net/mac80211/mesh_plink.c       | 2 +-
 net/mptcp/subflow.c             | 4 ++--
 net/openvswitch/flow_table.c    | 2 +-
 net/sctp/sm_make_chunk.c        | 4 ++--
 net/tipc/node.c                 | 2 +-
 7 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/drivers/net/netdevsim/psample.c b/drivers/net/netdevsim/psample.c
index 47d24bc64ee4..717d157c3ae2 100644
--- a/drivers/net/netdevsim/psample.c
+++ b/drivers/net/netdevsim/psample.c
@@ -94,7 +94,7 @@ static void nsim_dev_psample_md_prepare(const struct nsim_dev_psample *psample,
 	if (psample->out_tc_occ_max) {
 		u64 out_tc_occ;
 
-		get_random_bytes(&out_tc_occ, sizeof(u64));
+		out_tc_occ = get_random_u64();
 		md->out_tc_occ = out_tc_occ & (psample->out_tc_occ_max - 1);
 		md->out_tc_occ_valid = 1;
 	}
@@ -102,7 +102,7 @@ static void nsim_dev_psample_md_prepare(const struct nsim_dev_psample *psample,
 	if (psample->latency_max) {
 		u64 latency;
 
-		get_random_bytes(&latency, sizeof(u64));
+		latency = get_random_u64();
 		md->latency = latency & (psample->latency_max - 1);
 		md->latency_valid = 1;
 	}
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index 1057d16d5dd2..deb8b2ec5674 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -411,7 +411,7 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n
 	ref_tracker_dir_init(&net->refcnt_tracker, 128, "net_refcnt");
 	ref_tracker_dir_init(&net->notrefcnt_tracker, 128, "net_notrefcnt");
 
-	get_random_bytes(&net->hash_mix, sizeof(u32));
+	net->hash_mix = get_random_u32();
 	net->dev_base_seq = 1;
 	net->user_ns = user_ns;
 
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 803106fc3134..7cbab90c8784 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -712,7 +712,7 @@ void mesh_plink_timer(struct timer_list *t)
 				"Mesh plink for %pM (retry, timeout): %d %d\n",
 				sta->sta.addr, sta->mesh->plink_retries,
 				sta->mesh->plink_timeout);
-			get_random_bytes(&rand, sizeof(u32));
+			rand = get_random_u32();
 			sta->mesh->plink_timeout = sta->mesh->plink_timeout +
 					     rand % sta->mesh->plink_timeout;
 			++sta->mesh->plink_retries;
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index 5cfe19990f31..1a7736145dbc 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -72,7 +72,7 @@ static void subflow_req_create_thmac(struct mptcp_subflow_request_sock *subflow_
 	struct mptcp_sock *msk = subflow_req->msk;
 	u8 hmac[SHA256_DIGEST_SIZE];
 
-	get_random_bytes(&subflow_req->local_nonce, sizeof(u32));
+	subflow_req->local_nonce = get_random_u32();
 
 	subflow_generate_hmac(READ_ONCE(msk->local_key),
 			      READ_ONCE(msk->remote_key),
@@ -1639,7 +1639,7 @@ int __mptcp_subflow_connect(struct sock *sk, const struct mptcp_pm_local *local,
 	ssk = sf->sk;
 	subflow = mptcp_subflow_ctx(ssk);
 	do {
-		get_random_bytes(&subflow->local_nonce, sizeof(u32));
+		subflow->local_nonce = get_random_u32();
 	} while (!subflow->local_nonce);
 
 	/* if 'IPADDRANY', the ID will be set later, after the routing */
diff --git a/net/openvswitch/flow_table.c b/net/openvswitch/flow_table.c
index 61c6a5f77c2e..67d5b8c0fe79 100644
--- a/net/openvswitch/flow_table.c
+++ b/net/openvswitch/flow_table.c
@@ -167,7 +167,7 @@ static struct table_instance *table_instance_alloc(int new_size)
 
 	ti->n_buckets = new_size;
 	ti->node_ver = 0;
-	get_random_bytes(&ti->hash_seed, sizeof(u32));
+	ti->hash_seed = get_random_u32();
 
 	return ti;
 }
diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c
index 2c0017d058d4..de86ac088289 100644
--- a/net/sctp/sm_make_chunk.c
+++ b/net/sctp/sm_make_chunk.c
@@ -2727,7 +2727,7 @@ __u32 sctp_generate_tag(const struct sctp_endpoint *ep)
 	__u32 x;
 
 	do {
-		get_random_bytes(&x, sizeof(__u32));
+		x = get_random_u32();
 	} while (x == 0);
 
 	return x;
@@ -2738,7 +2738,7 @@ __u32 sctp_generate_tsn(const struct sctp_endpoint *ep)
 {
 	__u32 retval;
 
-	get_random_bytes(&retval, sizeof(__u32));
+	retval = get_random_u32();
 	return retval;
 }
 
diff --git a/net/tipc/node.c b/net/tipc/node.c
index af442a5ef8f3..97aa970a0d83 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -1275,7 +1275,7 @@ void tipc_node_check_dest(struct net *net, u32 addr,
 			goto exit;
 
 		if_name = strchr(b->name, ':') + 1;
-		get_random_bytes(&session, sizeof(u16));
+		session = get_random_u16();
 		if (!tipc_link_create(net, if_name, b->identity, b->tolerance,
 				      b->net_plane, b->mtu, b->priority,
 				      b->min_win, b->max_win, session,
-- 
2.53.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox