[PATCH net-next 3/4] net/smc: Introduce smc_bpf_ops

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: "D. Wythe" <alibuda@xxxxxxxxxxxxxxxxx>

The introduction of IPPROTO_SMC enables eBPF programs to determine
whether to use SMC based on the context of socket creation, such as
network namespaces, PID and comm name, etc.

As a subsequent enhancement, this patch introduces a new hook for eBPF
programs that allows decisions on whether to use SMC or not at runtime,
including but not limited to local/remote IP address or ports. In
simpler words, this feature allows modifications to syn_smc through eBPF
programs before the TCP three-way handshake got established.

Signed-off-by: D. Wythe <alibuda@xxxxxxxxxxxxxxxxx>
---
 include/linux/tcp.h   |   2 +-
 include/net/smc.h     |  47 +++++++++++
 include/net/tcp.h     |   6 ++
 net/ipv4/tcp_input.c  |   3 +-
 net/ipv4/tcp_output.c |  14 +++-
 net/smc/Kconfig       |  12 +++
 net/smc/Makefile      |   1 +
 net/smc/af_smc.c      |  38 ++++++---
 net/smc/smc.h         |   4 +
 net/smc/smc_bpf.c     | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++
 net/smc/smc_bpf.h     |  34 ++++++++
 11 files changed, 357 insertions(+), 16 deletions(-)
 create mode 100644 net/smc/smc_bpf.c
 create mode 100644 net/smc/smc_bpf.h

diff --git a/include/linux/tcp.h b/include/linux/tcp.h
index 6a5e08b..4ef160a 100644
--- a/include/linux/tcp.h
+++ b/include/linux/tcp.h
@@ -478,7 +478,7 @@ struct tcp_sock {
 #endif
 #if IS_ENABLED(CONFIG_SMC)
 	bool	syn_smc;	/* SYN includes SMC */
-	bool	(*smc_hs_congested)(const struct sock *sk);
+	struct tcpsmc_ctx *smc;
 #endif
 
 #if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
diff --git a/include/net/smc.h b/include/net/smc.h
index db84e4e..34ab2c6 100644
--- a/include/net/smc.h
+++ b/include/net/smc.h
@@ -18,6 +18,8 @@
 #include "linux/ism.h"
 
 struct sock;
+struct tcp_sock;
+struct inet_request_sock;
 
 #define SMC_MAX_PNETID_LEN	16	/* Max. length of PNET id */
 
@@ -97,4 +99,49 @@ struct smcd_dev {
 	u8 going_away : 1;
 };
 
+/*
+ * This structure is used to store the parameters passed to the member of struct_ops.
+ * Due to the BPF verifier cannot restrict the writing of bit fields, such as limiting
+ * it to only write ireq->smc_ok. Using kfunc can solve this issue, but we don't want
+ * to introduce a kfunc with such a narrow function.
+ *
+ * Moreover, using this structure for unified parameters also addresses another
+ * potential issue. Currently, kfunc cannot recognize the calling context
+ * through BPF's existing structure. In the future, we can solve this problem
+ * by passing this ctx to kfunc.
+ */
+struct smc_bpf_ops_ctx {
+	struct {
+		struct tcp_sock *tp;
+	} set_option;
+	struct {
+		const struct tcp_sock *tp;
+		struct inet_request_sock *ireq;
+		int smc_ok;
+	} set_option_cond;
+};
+
+struct smc_bpf_ops {
+	/* priavte */
+
+	struct list_head	list;
+
+	/* public */
+
+	/* Invoked before computing SMC option for SYN packets.
+	 * We can control whether to set SMC options by modifying
+	 * ctx->set_option->tp->syn_smc.
+	 * This's also the only member that can be modified now.
+	 * Only member in ctx->set_option is valid for this callback.
+	 */
+	void (*set_option)(struct smc_bpf_ops_ctx *ctx);
+
+	/* Invoked before Set up SMC options for SYN-ACK packets
+	 * We can control whether to respond SMC options by modifying
+	 * ctx->set_option_cond.smc_ok.
+	 * Only member in ctx->set_option_cond is valid for this callback.
+	 */
+	void (*set_option_cond)(struct smc_bpf_ops_ctx *ctx);
+};
+
 #endif	/* _SMC_H */
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 739a9fb..c322443 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -2730,6 +2730,12 @@ static inline void tcp_bpf_rtt(struct sock *sk, long mrtt, u32 srtt)
 
 #if IS_ENABLED(CONFIG_SMC)
 extern struct static_key_false tcp_have_smc;
+struct tcpsmc_ctx {
+	/* Invoked before computing SMC option for SYN packets. */
+	void (*set_option)(struct tcp_sock *tp);
+	/* Invoked before Set up SMC options for SYN-ACK packets */
+	void (*set_option_cond)(const struct tcp_sock *tp, struct inet_request_sock *ireq);
+};
 #endif
 
 #if IS_ENABLED(CONFIG_TLS_DEVICE)
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 2d844e1..8ebd529 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -7070,8 +7070,7 @@ static void tcp_openreq_init(struct request_sock *req,
 	ireq->ir_num = ntohs(tcp_hdr(skb)->dest);
 	ireq->ir_mark = inet_request_mark(sk, skb);
 #if IS_ENABLED(CONFIG_SMC)
-	ireq->smc_ok = rx_opt->smc_ok && !(tcp_sk(sk)->smc_hs_congested &&
-			tcp_sk(sk)->smc_hs_congested(sk));
+	ireq->smc_ok = rx_opt->smc_ok;
 #endif
 }
 
diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index 054244ce..5ab47dd 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -759,14 +759,17 @@ static void tcp_options_write(struct tcphdr *th, struct tcp_sock *tp,
 	mptcp_options_write(th, ptr, tp, opts);
 }
 
-static void smc_set_option(const struct tcp_sock *tp,
+static void smc_set_option(struct tcp_sock *tp,
 			   struct tcp_out_options *opts,
 			   unsigned int *remaining)
 {
 #if IS_ENABLED(CONFIG_SMC)
 	if (static_branch_unlikely(&tcp_have_smc)) {
 		if (tp->syn_smc) {
-			if (*remaining >= TCPOLEN_EXP_SMC_BASE_ALIGNED) {
+			if (tp->smc && tp->smc->set_option)
+				tp->smc->set_option(tp);
+			/* set_option may modify syn_smc, so it needs to be checked again */
+			if (tp->syn_smc && *remaining >= TCPOLEN_EXP_SMC_BASE_ALIGNED) {
 				opts->options |= OPTION_SMC;
 				*remaining -= TCPOLEN_EXP_SMC_BASE_ALIGNED;
 			}
@@ -776,14 +779,17 @@ static void smc_set_option(const struct tcp_sock *tp,
 }
 
 static void smc_set_option_cond(const struct tcp_sock *tp,
-				const struct inet_request_sock *ireq,
+				struct inet_request_sock *ireq,
 				struct tcp_out_options *opts,
 				unsigned int *remaining)
 {
 #if IS_ENABLED(CONFIG_SMC)
 	if (static_branch_unlikely(&tcp_have_smc)) {
 		if (tp->syn_smc && ireq->smc_ok) {
-			if (*remaining >= TCPOLEN_EXP_SMC_BASE_ALIGNED) {
+			if (tp->smc && tp->smc->set_option_cond)
+				tp->smc->set_option_cond(tp, ireq);
+			/* set_option_cond may modify smc_ok, so it needs to be checked again */
+			if (ireq->smc_ok && *remaining >= TCPOLEN_EXP_SMC_BASE_ALIGNED) {
 				opts->options |= OPTION_SMC;
 				*remaining -= TCPOLEN_EXP_SMC_BASE_ALIGNED;
 			}
diff --git a/net/smc/Kconfig b/net/smc/Kconfig
index ba5e6a2..1eca835 100644
--- a/net/smc/Kconfig
+++ b/net/smc/Kconfig
@@ -33,3 +33,15 @@ config SMC_LO
 	  of architecture or hardware.
 
 	  if unsure, say N.
+
+config SMC_BPF
+	bool "eBPF support for SMC subsystem"
+	depends on SMC && BPF_SYSCALL
+	default n
+	help
+	  This option enables support for eBPF programs for SMC
+	  subsystem. eBPF programs offer much greater flexibility
+	  in modifying the behavior of the SMC protocol stack compared
+	  to a complete kernel-based approach.
+
+	  if unsure, say N.
diff --git a/net/smc/Makefile b/net/smc/Makefile
index 60f1c87..1c04906 100644
--- a/net/smc/Makefile
+++ b/net/smc/Makefile
@@ -7,3 +7,4 @@ smc-y += smc_cdc.o smc_tx.o smc_rx.o smc_close.o smc_ism.o smc_netlink.o smc_sta
 smc-y += smc_tracepoint.o smc_inet.o
 smc-$(CONFIG_SYSCTL) += smc_sysctl.o
 smc-$(CONFIG_SMC_LO) += smc_loopback.o
+smc-$(CONFIG_SMC_BPF) += smc_bpf.o
\ No newline at end of file
diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
index 0316217..316c8a1 100644
--- a/net/smc/af_smc.c
+++ b/net/smc/af_smc.c
@@ -55,6 +55,7 @@
 #include "smc_sysctl.h"
 #include "smc_loopback.h"
 #include "smc_inet.h"
+#include "smc_bpf.h"
 
 static DEFINE_MUTEX(smc_server_lgr_pending);	/* serialize link group
 						 * creation on server
@@ -156,19 +157,25 @@ static struct sock *smc_tcp_syn_recv_sock(const struct sock *sk,
 	return NULL;
 }
 
-static bool smc_hs_congested(const struct sock *sk)
+static void smc_set_tcp_option_cond(const struct tcp_sock *tp, struct inet_request_sock *ireq)
 {
 	const struct smc_sock *smc;
 
-	smc = smc_clcsock_user_data(sk);
+	smc = smc_clcsock_user_data(&tp->inet_conn.icsk_inet.sk);
 
 	if (!smc)
-		return true;
+		goto no_smc;
 
-	if (workqueue_congested(WORK_CPU_UNBOUND, smc_hs_wq))
-		return true;
+	if (smc->limit_smc_hs && workqueue_congested(WORK_CPU_UNBOUND, smc_hs_wq))
+		goto no_smc;
 
-	return false;
+#if IS_ENABLED(CONFIG_SMC_BPF)
+	bpf_smc_set_tcp_option_cond(tp, ireq);
+#endif /* CONFIG_SMC_BPF */
+
+	return;
+no_smc:
+	ireq->smc_ok = 0;
 }
 
 struct smc_hashinfo smc_v4_hashinfo = {
@@ -2650,9 +2657,6 @@ int smc_listen(struct socket *sock, int backlog)
 
 	inet_csk(smc->clcsock->sk)->icsk_af_ops = &smc->af_ops;
 
-	if (smc->limit_smc_hs)
-		tcp_sk(smc->clcsock->sk)->smc_hs_congested = smc_hs_congested;
-
 	rc = kernel_listen(smc->clcsock, backlog);
 	if (rc) {
 		write_lock_bh(&smc->clcsock->sk->sk_callback_lock);
@@ -3324,6 +3328,13 @@ int smc_create_clcsk(struct net *net, struct sock *sk, int family)
 	sk->sk_net_refcnt = 1;
 	get_net_track(net, &sk->ns_tracker, GFP_KERNEL);
 	sock_inuse_add(net, 1);
+
+	/* init tcp_smc_ctx */
+#if IS_ENABLED(CONFIG_SMC_BPF)
+	smc->tcp_smc_ctx.set_option = bpf_smc_set_tcp_option;
+#endif /* CONFIG_SMC_BPF */
+	smc->tcp_smc_ctx.set_option_cond = smc_set_tcp_option_cond;
+	tcp_sk(sk)->smc = &smc->tcp_smc_ctx;
 	return 0;
 }
 
@@ -3574,8 +3585,17 @@ static int __init smc_init(void)
 		pr_err("%s: smc_inet_init fails with %d\n", __func__, rc);
 		goto out_ulp;
 	}
+
+	rc = smc_bpf_struct_ops_init();
+	if (rc) {
+		pr_err("%s: smc_bpf_struct_ops_init fails with %d\n", __func__, rc);
+		goto out_inet;
+	}
+
 	static_branch_enable(&tcp_have_smc);
 	return 0;
+out_inet:
+	smc_inet_exit();
 out_ulp:
 	tcp_unregister_ulp(&smc_ulp_ops);
 out_lo:
diff --git a/net/smc/smc.h b/net/smc/smc.h
index 78ae10d..a9794fb 100644
--- a/net/smc/smc.h
+++ b/net/smc/smc.h
@@ -16,6 +16,7 @@
 #include <linux/compiler.h> /* __aligned */
 #include <net/genetlink.h>
 #include <net/sock.h>
+#include <net/tcp.h>
 
 #include "smc_ib.h"
 
@@ -328,6 +329,9 @@ struct smc_sock {				/* smc sock container */
 						/* protects clcsock of a listen
 						 * socket
 						 * */
+
+	/* smc context for tcp stack */
+	struct tcpsmc_ctx	tcp_smc_ctx;
 };
 
 #define smc_sk(ptr) container_of_const(ptr, struct smc_sock, sk)
diff --git a/net/smc/smc_bpf.c b/net/smc/smc_bpf.c
new file mode 100644
index 00000000..fa90406
--- /dev/null
+++ b/net/smc/smc_bpf.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  Shared Memory Communications over RDMA (SMC-R) and RoCE
+ *
+ *  support for eBPF programs in SMC subsystem.
+ *
+ *  Copyright IBM Corp. 2016
+ *  Copyright (c) 2024, Alibaba Inc.
+ *
+ *  Author: D. Wythe <alibuda@xxxxxxxxxxxxxxxxx>
+ */
+
+#include <linux/bpf_verifier.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/bpf.h>
+#include <linux/btf.h>
+#include <net/smc.h>
+
+#include "smc_bpf.h"
+
+static DEFINE_SPINLOCK(smc_bpf_ops_list_lock);
+static LIST_HEAD(smc_bpf_ops_list);
+
+static u32 tcp_sock_id, smc_bpf_ops_ctx_id;
+static const struct btf_type *smc_bpf_ops_type;
+static const struct btf *saved_btf;
+
+static int smc_bpf_ops_init(struct btf *btf)
+{
+	s32 type_id;
+
+	type_id = btf_find_by_name_kind(btf, "tcp_sock", BTF_KIND_STRUCT);
+	if (type_id < 0)
+		return -EINVAL;
+	tcp_sock_id = type_id;
+
+	type_id = btf_find_by_name_kind(btf, "smc_bpf_ops_ctx", BTF_KIND_STRUCT);
+	if (type_id < 0)
+		return -EINVAL;
+	smc_bpf_ops_ctx_id = type_id;
+
+	type_id = btf_find_by_name_kind(btf, "smc_bpf_ops", BTF_KIND_STRUCT);
+	if (type_id < 0)
+		return -EINVAL;
+	smc_bpf_ops_type = btf_type_by_id(btf, type_id);
+
+	saved_btf = btf;
+	return 0;
+}
+
+static int smc_bpf_ops_init_member(const struct btf_type *t,
+				   const struct btf_member *member,
+				   void *kdata, const void *udata)
+{
+	struct smc_bpf_ops *k_ops;
+	u32 moff;
+
+	k_ops = (struct smc_bpf_ops *)kdata;
+
+	moff = __btf_member_bit_offset(t, member) / 8;
+	switch (moff) {
+	case offsetof(struct smc_bpf_ops, list):
+		INIT_LIST_HEAD(&k_ops->list);
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int smc_bpf_ops_check_member(const struct btf_type *t,
+				    const struct btf_member *member,
+				    const struct bpf_prog *prog)
+{
+	u32 moff = __btf_member_bit_offset(t, member) / 8;
+
+	switch (moff) {
+	case offsetof(struct smc_bpf_ops, set_option):
+	case offsetof(struct smc_bpf_ops, set_option_cond):
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smc_bpf_ops_reg(void *kdata, struct bpf_link *link)
+{
+	struct smc_bpf_ops *ops = kdata;
+
+	/* Prevent the same ops from being registered repeatedly. */
+	if (!list_empty(&ops->list))
+		return -EINVAL;
+
+	spin_lock(&smc_bpf_ops_list_lock);
+	list_add_tail_rcu(&ops->list, &smc_bpf_ops_list);
+	spin_unlock(&smc_bpf_ops_list_lock);
+
+	return 0;
+}
+
+static void smc_bpf_ops_unreg(void *kdata, struct bpf_link *link)
+{
+	struct smc_bpf_ops *ops = kdata;
+
+	spin_lock(&smc_bpf_ops_list_lock);
+	list_del_rcu(&ops->list);
+	spin_unlock(&smc_bpf_ops_list_lock);
+
+	/* Ensure that all readers to complete */
+	synchronize_rcu();
+}
+
+static void __bpf_smc_stub_set_tcp_option(struct smc_bpf_ops_ctx *ops_ctx) {}
+static void __bpf_smc_stub_set_tcp_option_cond(struct smc_bpf_ops_ctx *ops_ctx) {}
+
+static struct smc_bpf_ops __bpf_smc_bpf_ops = {
+	.set_option = __bpf_smc_stub_set_tcp_option,
+	.set_option_cond = __bpf_smc_stub_set_tcp_option_cond,
+};
+
+static int smc_bpf_ops_btf_struct_access(struct bpf_verifier_log *log,
+					 const struct bpf_reg_state *reg,
+					 const struct bpf_prog *prog,
+					 int off, int size)
+{
+	const struct btf_member *member;
+	const char *mname;
+	int member_idx;
+
+	member_idx = prog->expected_attach_type;
+	if (member_idx >= btf_type_vlen(smc_bpf_ops_type))
+		goto out_err;
+
+	member = &btf_type_member(smc_bpf_ops_type)[member_idx];
+	mname = btf_str_by_offset(saved_btf, member->name_off);
+
+	if (!strcmp(mname, "set_option")) {
+		/* only support to modify tcp_sock->syn_smc */
+		if (reg->btf_id == tcp_sock_id &&
+		    off == offsetof(struct tcp_sock, syn_smc) &&
+		    off + size == offsetofend(struct tcp_sock, syn_smc))
+			return 0;
+	} else if (!strcmp(mname, "set_option_cond")) {
+		/* only support to modify smc_bpf_ops_ctx->smc_ok */
+		if (reg->btf_id == smc_bpf_ops_ctx_id &&
+		    off == offsetof(struct smc_bpf_ops_ctx, set_option_cond.smc_ok) &&
+		    off + size == offsetofend(struct smc_bpf_ops_ctx, set_option_cond.smc_ok))
+			return 0;
+	}
+
+out_err:
+	return -EACCES;
+}
+
+static const struct bpf_verifier_ops smc_bpf_verifier_ops = {
+	.get_func_proto = bpf_base_func_proto,
+	.is_valid_access = bpf_tracing_btf_ctx_access,
+	.btf_struct_access = smc_bpf_ops_btf_struct_access,
+};
+
+static struct bpf_struct_ops bpf_smc_bpf_ops = {
+	.init = smc_bpf_ops_init,
+	.name = "smc_bpf_ops",
+	.reg = smc_bpf_ops_reg,
+	.unreg = smc_bpf_ops_unreg,
+	.cfi_stubs = &__bpf_smc_bpf_ops,
+	.verifier_ops = &smc_bpf_verifier_ops,
+	.init_member = smc_bpf_ops_init_member,
+	.check_member = smc_bpf_ops_check_member,
+	.owner = THIS_MODULE,
+};
+
+int smc_bpf_struct_ops_init(void)
+{
+	return register_bpf_struct_ops(&bpf_smc_bpf_ops, smc_bpf_ops);
+}
+
+void bpf_smc_set_tcp_option(struct tcp_sock *tp)
+{
+	struct smc_bpf_ops_ctx ops_ctx = {};
+	struct smc_bpf_ops *ops;
+
+	ops_ctx.set_option.tp = tp;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(ops, &smc_bpf_ops_list, list) {
+		ops->set_option(&ops_ctx);
+	}
+	rcu_read_unlock();
+}
+
+void bpf_smc_set_tcp_option_cond(const struct tcp_sock *tp, struct inet_request_sock *ireq)
+{
+	struct smc_bpf_ops_ctx ops_ctx = {};
+	struct smc_bpf_ops *ops;
+
+	ops_ctx.set_option_cond.tp = tp;
+	ops_ctx.set_option_cond.ireq = ireq;
+	ops_ctx.set_option_cond.smc_ok = ireq->smc_ok;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(ops, &smc_bpf_ops_list, list) {
+		ops->set_option_cond(&ops_ctx);
+	}
+	rcu_read_unlock();
+
+	ireq->smc_ok = ops_ctx.set_option_cond.smc_ok;
+}
diff --git a/net/smc/smc_bpf.h b/net/smc/smc_bpf.h
new file mode 100644
index 00000000..a5ed0fc
--- /dev/null
+++ b/net/smc/smc_bpf.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ *  Shared Memory Communications over RDMA (SMC-R) and RoCE
+ *
+ *  support for eBPF programs in SMC subsystem.
+ *
+ *  Copyright IBM Corp. 2016
+ *  Copyright (c) 2024, Alibaba Inc.
+ *
+ *  Author: D. Wythe <alibuda@xxxxxxxxxxxxxxxxx>
+ */
+#ifndef __SMC_BPF
+#define __SMC_BPF
+
+#include <linux/types.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+
+#if IS_ENABLED(CONFIG_SMC_BPF)
+
+/* Initialize struct_ops registration. It will automatically unload
+ * when module is unloaded.
+ * @return 0 on success
+ */
+int smc_bpf_struct_ops_init(void);
+
+void bpf_smc_set_tcp_option(struct tcp_sock *sk);
+void bpf_smc_set_tcp_option_cond(const struct tcp_sock *tp, struct inet_request_sock *ireq);
+
+#else
+static inline int smc_bpf_struct_ops_init(void) { return 0; }
+#endif /* CONFIG_SMC_BPF */
+
+#endif /* __SMC_BPF */
-- 
1.8.3.1





[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Kernel Development]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite Info]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Linux Media]     [Device Mapper]

  Powered by Linux