:p
atchew
Login
This small series makes the MPTCP sockopt codepaths consistent with TCP and the core socket layer by using the BPF-aware sockopt_lock_sock()/sockopt_release_sock() helpers introduced in commit 24426654ed3a ("bpf: net: Avoid sk_setsockopt() taking sk lock when called from bpf"). Patch 1 switches all lock_sock()/release_sock()/lock_sock_fast() calls in MPTCP sockopt handlers to use the BPF-aware wrappers, avoiding the risk of sleeping in atomic context when lock_sock_fast() is used. Patch 2 switches ns_capable() to sockopt_ns_capable() in the congestion control setsockopt path, properly handling the case where BPF programs invoke setsockopt from softirq context. Both patches are fixes that should have been part of the original BPF sockopt series. Changelog: v3: - Remove the special symbols in v2. - Use sockopt_ns_capable to replace ns_capable. v2: Link: https://patchwork.kernel.org/project/mptcp/patch/20260422091927.77770-3-gang.yan@linux.dev/ Signed-off-by: Gang Yan <yangang@kylinos.cn> --- Gang Yan (2): mptcp: use sockopt_lock(release)_sock in sockopt mptcp: use sockopt_ns_capable() in setsockopt congestion control net/mptcp/sockopt.c | 123 ++++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 62 deletions(-) --- base-commit: aa15c271d79edde595fb6f4eedb52fbc16325a83 change-id: 20260506-sockopt_lock-c46837d6d9d7 Best regards, -- Gang Yan <yangang@kylinos.cn>
From: Gang Yan <yangang@kylinos.cn> TCP and the core socket layer all use sockopt_lock_sock() sockopt_release_sock() in their setsockopt and getsockopt handlers. It is a BPF-aware wrapper that skips lock acquisition when invoked from a BPF program, where the socket lock is already held. Using lock_sock_fast() on subflows requires extra care: the fast path holds the socket spinlock with BH disabled, creating an atomic context where sleeping is not allowed. Switching to sockopt_lock_sock() avoids the risk of accidentally introducing sleeping operations inside the lock_sock_fast() critical section. Fixes: 24426654ed3a ("bpf: net: Avoid sk_setsockopt() taking sk lock when called from bpf") Signed-off-by: Gang Yan <yangang@kylinos.cn> --- net/mptcp/sockopt.c | 121 ++++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c index XXXXXXX..XXXXXXX 100644 --- a/net/mptcp/sockopt.c +++ b/net/mptcp/sockopt.c @@ -XXX,XX +XXX,XX @@ static void mptcp_sol_socket_sync_intval(struct mptcp_sock *msk, int optname, in struct mptcp_subflow_context *subflow; struct sock *sk = (struct sock *)msk; - lock_sock(sk); + sockopt_lock_sock(sk); sockopt_seq_inc(msk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - bool slow = lock_sock_fast(ssk); + sockopt_lock_sock(ssk); switch (optname) { case SO_DEBUG: @@ -XXX,XX +XXX,XX @@ static void mptcp_sol_socket_sync_intval(struct mptcp_sock *msk, int optname, in } subflow->setsockopt_seq = msk->setsockopt_seq; - unlock_sock_fast(ssk, slow); + sockopt_release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); } static int mptcp_sol_socket_intval(struct mptcp_sock *msk, int optname, int val) @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_tstamp(struct mptcp_sock *msk, int optnam if (ret) return ret; - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - lock_sock(ssk); + sockopt_lock_sock(ssk); sock_set_timestamp(ssk, optname, !!val); - release_sock(ssk); + sockopt_release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_timestamping(struct mptcp_sock *msk, if (ret) return ret; - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - lock_sock(ssk); + sockopt_lock_sock(ssk); sock_set_timestamping(ssk, optname, timestamping); - release_sock(ssk); + sockopt_release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_linger(struct mptcp_sock *msk, sockptr_t if (ret) return ret; - lock_sock(sk); + sockopt_lock_sock(sk); sockopt_seq_inc(msk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - bool slow = lock_sock_fast(ssk); + sockopt_lock_sock(ssk); if (!ling.l_onoff) { sock_reset_flag(ssk, SOCK_LINGER); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_linger(struct mptcp_sock *msk, sockptr_t } subflow->setsockopt_seq = msk->setsockopt_seq; - unlock_sock_fast(ssk, slow); + sockopt_release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket(struct mptcp_sock *msk, int optname, case SO_REUSEADDR: case SO_BINDTODEVICE: case SO_BINDTOIFINDEX: - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_nmpc_sk(msk); if (IS_ERR(ssk)) { - release_sock(sk); + sockopt_release_sock(sk); return PTR_ERR(ssk); } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket(struct mptcp_sock *msk, int optname, else if (optname == SO_BINDTOIFINDEX) sk->sk_bound_dev_if = ssk->sk_bound_dev_if; } - release_sock(sk); + sockopt_release_sock(sk); return ret; case SO_KEEPALIVE: case SO_PRIORITY: @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_v6(struct mptcp_sock *msk, int optname, case IPV6_V6ONLY: case IPV6_TRANSPARENT: case IPV6_FREEBIND: - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_nmpc_sk(msk); if (IS_ERR(ssk)) { - release_sock(sk); + sockopt_release_sock(sk); return PTR_ERR(ssk); } ret = tcp_setsockopt(ssk, SOL_IPV6, optname, optval, optlen); if (ret != 0) { - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_v6(struct mptcp_sock *msk, int optname, break; } - release_sock(sk); + sockopt_release_sock(sk); break; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp_congestion(struct mptcp_sock *msk, sockptr_t cap_net_admin = ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN); ret = 0; - lock_sock(sk); + sockopt_lock_sock(sk); sockopt_seq_inc(msk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); int err; - lock_sock(ssk); + sockopt_lock_sock(ssk); err = tcp_set_congestion_control(ssk, name, true, cap_net_admin); if (err < 0 && ret == 0) ret = err; subflow->setsockopt_seq = msk->setsockopt_seq; - release_sock(ssk); + sockopt_release_sock(ssk); } if (ret == 0) strscpy(msk->ca_name, name, sizeof(msk->ca_name)); - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ static int __mptcp_setsockopt_set_val(struct mptcp_sock *msk, int max, struct sock *ssk = mptcp_subflow_tcp_sock(subflow); int ret; - lock_sock(ssk); + sockopt_lock_sock(ssk); ret = set_val(ssk, val); err = err ? : ret; - release_sock(ssk); + sockopt_release_sock(ssk); } if (!err) { @@ -XXX,XX +XXX,XX @@ static int __mptcp_setsockopt_sol_tcp_cork(struct mptcp_sock *msk, int val) mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - lock_sock(ssk); + sockopt_lock_sock(ssk); __tcp_sock_set_cork(ssk, !!val); - release_sock(ssk); + sockopt_release_sock(ssk); } if (!val) mptcp_check_and_set_pending(sk); @@ -XXX,XX +XXX,XX @@ static int __mptcp_setsockopt_sol_tcp_nodelay(struct mptcp_sock *msk, int val) mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - lock_sock(ssk); + sockopt_lock_sock(ssk); __tcp_sock_set_nodelay(ssk, !!val); - release_sock(ssk); + sockopt_release_sock(ssk); } if (val) mptcp_check_and_set_pending(sk); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_ip_set(struct mptcp_sock *msk, int optname, if (err != 0) return err; - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_nmpc_sk(msk); if (IS_ERR(ssk)) { - release_sock(sk); + sockopt_release_sock(sk); return PTR_ERR(ssk); } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_ip_set(struct mptcp_sock *msk, int optname, READ_ONCE(inet_sk(sk)->local_port_range)); break; default: - release_sock(sk); + sockopt_release_sock(sk); WARN_ON_ONCE(1); return -EOPNOTSUPP; } sockopt_seq_inc(msk); - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_v4_set_tos(struct mptcp_sock *msk, int optname, if (err != 0) return err; - lock_sock(sk); + sockopt_lock_sock(sk); sockopt_seq_inc(msk); val = READ_ONCE(inet_sk(sk)->tos); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - bool slow; - slow = lock_sock_fast(ssk); + sockopt_lock_sock(ssk); __ip_sock_set_tos(ssk, val); - unlock_sock_fast(ssk, slow); + sockopt_release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_first_sf_only(struct mptcp_sock *msk, int level, int int ret; /* Limit to first subflow, before the connection establishment */ - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_nmpc_sk(msk); if (IS_ERR(ssk)) { ret = PTR_ERR(ssk); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_first_sf_only(struct mptcp_sock *msk, int level, int ret = tcp_setsockopt(ssk, level, optname, optval, optlen); unlock: - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp(struct mptcp_sock *msk, int optname, if (ret) return ret; - lock_sock(sk); + sockopt_lock_sock(sk); switch (optname) { case TCP_INQ: if (val < 0 || val > 1) @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp(struct mptcp_sock *msk, int optname, ret = -ENOPROTOOPT; } - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ int mptcp_setsockopt(struct sock *sk, int level, int optname, * is in TCP fallback, when TCP socket options are passed through * to the one remaining subflow. */ - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_tcp_fallback(msk); - release_sock(sk); + sockopt_release_sock(sk); if (ssk) return tcp_setsockopt(ssk, level, optname, optval, optlen); @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_first_sf_only(struct mptcp_sock *msk, int level, int struct sock *ssk; int ret; - lock_sock(sk); + sockopt_lock_sock(sk); ssk = msk->first; if (ssk) goto get; @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_first_sf_only(struct mptcp_sock *msk, int level, int ret = tcp_getsockopt(ssk, level, optname, optval, optlen); out: - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_tcpinfo(struct mptcp_sock *msk, char __user *optval, infoptr = optval + sfd.size_subflow_data; - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_tcpinfo(struct mptcp_sock *msk, char __user *optval, tcp_get_info(ssk, &info); if (copy_to_user(infoptr, &info, sfd.size_user)) { - release_sock(sk); + sockopt_release_sock(sk); return -EFAULT; } @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_tcpinfo(struct mptcp_sock *msk, char __user *optval, } } - release_sock(sk); + sockopt_release_sock(sk); sfd.num_subflows = sfcount; @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_subflow_addrs(struct mptcp_sock *msk, char __user *o addrptr = optval + sfd.size_subflow_data; - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_subflow_addrs(struct mptcp_sock *msk, char __user *o mptcp_get_sub_addrs(ssk, &a); if (copy_to_user(addrptr, &a, sfd.size_user)) { - release_sock(sk); + sockopt_release_sock(sk); return -EFAULT; } @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_subflow_addrs(struct mptcp_sock *msk, char __user *o } } - release_sock(sk); + sockopt_release_sock(sk); sfd.num_subflows = sfcount; @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_full_info(struct mptcp_sock *msk, char __user *optva sizeof(struct mptcp_subflow_info)); tcpinfoptr = u64_to_user_ptr(mfi.tcp_info); - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); struct mptcp_subflow_info sfinfo; @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_full_info(struct mptcp_sock *msk, char __user *optva tcpinfoptr += mfi.size_tcpinfo_user; sfinfoptr += mfi.size_sfinfo_user; } - release_sock(sk); + sockopt_release_sock(sk); mfi.num_subflows = sfcount; if (mptcp_put_full_info(&mfi, optval, copylen, optlen)) @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_full_info(struct mptcp_sock *msk, char __user *optva return 0; fail_release: - release_sock(sk); + sockopt_release_sock(sk); return -EFAULT; } @@ -XXX,XX +XXX,XX @@ int mptcp_getsockopt(struct sock *sk, int level, int optname, * is in TCP fallback, when socket options are passed through * to the one remaining subflow. */ - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_tcp_fallback(msk); - release_sock(sk); + sockopt_release_sock(sk); if (ssk) return tcp_getsockopt(ssk, level, optname, optval, option); -- 2.43.0
From: Gang Yan <yangang@kylinos.cn> When a BPF program calls bpf_setsockopt(), it may run in softirq context where ns_capable() is not appropriate as there is no valid credential context. Use sockopt_ns_capable() instead, which skips the capability check when invoked from a BPF program. Fixes: e42c7beee71d ("bpf: net: Consider has_current_bpf_ctx() when testing capable() in sk_setsockopt()") Signed-off-by: Gang Yan <yangang@kylinos.cn> --- net/mptcp/sockopt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c index XXXXXXX..XXXXXXX 100644 --- a/net/mptcp/sockopt.c +++ b/net/mptcp/sockopt.c @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp_congestion(struct mptcp_sock *msk, sockptr_t name[ret] = 0; - cap_net_admin = ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN); + cap_net_admin = sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN); ret = 0; sockopt_lock_sock(sk); -- 2.43.0
Hi Maintainers, This patch series fixes locking issues in the sockopt path and adds support for setting MPTCP sockopts in BPF context. Patch 1 can avoid accidentally introducing sleeping operations inside the lock_sock_fast() critical section like b5c52908d5("mptcp: fix scheduling with atomic in timestamp sockopt") does. Although patches 4 and 5 are marked DO-NOT-MERGE, I think they are ready for upstream submission, reviews are welcome. Thanks Gang Signed-off-by: Gang Yan <yangang@kylinos.cn> --- Changelog: v5: - Split patch 1 into two patches, one is to replace lock_sock_fast with lock_sock to avoid 'sleeping in atomic context' issue in regular path, and another one is using 'sockopt_lock_sock' for mptcp socket like TCP does. - Patch 3 prevents the sleeping issue from requiring ssks' lock in BPF context, It will return -EOPNOTSUPP when application/BPF programs trip on this. - Patch 4 and 5 are temporary patches used to verify the validity of the above functions. v4: - As sashiko said, when processing BPF setsockopt requests, the msk is already locked, but we need to use lock_sock() to protect ssk. If we use sockopt_lock_sock(ssk), it will return without acquiring the lock. - In 'mptcp_setsockopt_sol_tcp_congestion', the load of 'tcp_set_congestion_control' is changed from 'true' to '!has_current_bpf_ctx()' like tcp does. This determines whether tcp_ca_find() or tcp_ca_find_autoload() is called. I agree we should keep consistent with the TCP implementation. Link: https://patch.msgid.link/20260509-sockopt_lock-v4-0-33f3a1c4d7a0@kylinos.cn v3: - Remove the special symbols in v2. - Use sockopt_ns_capable to replace ns_capable. Link: https://lore.kernel.org/r/20260506-sockopt_lock-v3-0-06bd417c6d63@kylinos.cn v2: Link: https://patchwork.kernel.org/project/mptcp/patch/20260422091927.77770-3-gang.yan@linux.dev/ --- Gang Yan (6): mptcp: replace lock_sock_fast with lock_sock in sockopt mptcp: use sockopt_lock/release_sock in sockopt mptcp: use sockopt_ns_capable in congestion control mptcp: reject sockopt requiring ssks' lock in BPF context DO-NOT-MERGE: mptcp: allow set some sockopt in BPF context DO-NOT-MERGE: selftest: bpf: set mptcp sockopt in BPF context net/core/filter.c | 6 + net/mptcp/sockopt.c | 133 ++++++++++++--------- tools/testing/selftests/bpf/prog_tests/mptcp.c | 64 ++++++++++ .../testing/selftests/bpf/progs/mptcp_setsockopt.c | 71 +++++++++++ 4 files changed, 220 insertions(+), 54 deletions(-) --- base-commit: aa15c271d79edde595fb6f4eedb52fbc16325a83 change-id: 20260506-sockopt_lock-c46837d6d9d7 Best regards, -- Gang Yan <yangang@kylinos.cn>
From: Gang Yan <yangang@kylinos.cn> Replace lock_sock_fast()/unlock_sock_fast() with lock_sock()/ release_sock() in the MPTCP sockopt handlers to avoid accidentally introducing sleeping operations inside the lock_sock_fast() critical section. This is consistent with how other sockopt handlers in the Net code already use lock_sock()/release_sock(). Signed-off-by: Gang Yan <yangang@kylinos.cn> --- net/mptcp/sockopt.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c index XXXXXXX..XXXXXXX 100644 --- a/net/mptcp/sockopt.c +++ b/net/mptcp/sockopt.c @@ -XXX,XX +XXX,XX @@ static void mptcp_sol_socket_sync_intval(struct mptcp_sock *msk, int optname, in mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - bool slow = lock_sock_fast(ssk); + lock_sock(ssk); switch (optname) { case SO_DEBUG: sock_valbool_flag(ssk, SOCK_DBG, !!val); @@ -XXX,XX +XXX,XX @@ static void mptcp_sol_socket_sync_intval(struct mptcp_sock *msk, int optname, in } subflow->setsockopt_seq = msk->setsockopt_seq; - unlock_sock_fast(ssk, slow); + release_sock(ssk); } release_sock(sk); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_linger(struct mptcp_sock *msk, sockptr_t sockopt_seq_inc(msk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - bool slow = lock_sock_fast(ssk); + lock_sock(ssk); if (!ling.l_onoff) { sock_reset_flag(ssk, SOCK_LINGER); } else { @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_linger(struct mptcp_sock *msk, sockptr_t } subflow->setsockopt_seq = msk->setsockopt_seq; - unlock_sock_fast(ssk, slow); + release_sock(ssk); } release_sock(sk); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_v4_set_tos(struct mptcp_sock *msk, int optname, val = READ_ONCE(inet_sk(sk)->tos); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - bool slow; - slow = lock_sock_fast(ssk); + lock_sock(ssk); __ip_sock_set_tos(ssk, val); - unlock_sock_fast(ssk, slow); + release_sock(ssk); } release_sock(sk); @@ -XXX,XX +XXX,XX @@ int mptcp_set_rcvlowat(struct sock *sk, int val) WRITE_ONCE(sk->sk_rcvbuf, space); mptcp_for_each_subflow(mptcp_sk(sk), subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); - bool slow; - slow = lock_sock_fast(ssk); + lock_sock(ssk); WRITE_ONCE(ssk->sk_rcvbuf, space); WRITE_ONCE(tcp_sk(ssk)->window_clamp, val); - unlock_sock_fast(ssk, slow); + release_sock(ssk); } return 0; } -- 2.43.0
From: Gang Yan <yangang@kylinos.cn> TCP and the core socket layer use sockopt_lock_sock() / sockopt_release_sock() in their setsockopt and getsockopt handlers. Switch the MPTCP socket (msk) level lock_sock()/release_sock() calls to use the BPF-aware wrappers, making the MPTCP sockopt codepaths consistent with the rest of the networking stack. Signed-off-by: Gang Yan <yangang@kylinos.cn> --- net/mptcp/sockopt.c | 84 ++++++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c index XXXXXXX..XXXXXXX 100644 --- a/net/mptcp/sockopt.c +++ b/net/mptcp/sockopt.c @@ -XXX,XX +XXX,XX @@ static void mptcp_sol_socket_sync_intval(struct mptcp_sock *msk, int optname, in struct mptcp_subflow_context *subflow; struct sock *sk = (struct sock *)msk; - lock_sock(sk); + sockopt_lock_sock(sk); sockopt_seq_inc(msk); mptcp_for_each_subflow(msk, subflow) { @@ -XXX,XX +XXX,XX @@ static void mptcp_sol_socket_sync_intval(struct mptcp_sock *msk, int optname, in release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); } static int mptcp_sol_socket_intval(struct mptcp_sock *msk, int optname, int val) @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_tstamp(struct mptcp_sock *msk, int optnam if (ret) return ret; - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_tstamp(struct mptcp_sock *msk, int optnam release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_timestamping(struct mptcp_sock *msk, if (ret) return ret; - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_timestamping(struct mptcp_sock *msk, release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_linger(struct mptcp_sock *msk, sockptr_t if (ret) return ret; - lock_sock(sk); + sockopt_lock_sock(sk); sockopt_seq_inc(msk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_linger(struct mptcp_sock *msk, sockptr_t release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket(struct mptcp_sock *msk, int optname, case SO_REUSEADDR: case SO_BINDTODEVICE: case SO_BINDTOIFINDEX: - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_nmpc_sk(msk); if (IS_ERR(ssk)) { - release_sock(sk); + sockopt_release_sock(sk); return PTR_ERR(ssk); } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket(struct mptcp_sock *msk, int optname, else if (optname == SO_BINDTOIFINDEX) sk->sk_bound_dev_if = ssk->sk_bound_dev_if; } - release_sock(sk); + sockopt_release_sock(sk); return ret; case SO_KEEPALIVE: case SO_PRIORITY: @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_v6(struct mptcp_sock *msk, int optname, case IPV6_V6ONLY: case IPV6_TRANSPARENT: case IPV6_FREEBIND: - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_nmpc_sk(msk); if (IS_ERR(ssk)) { - release_sock(sk); + sockopt_release_sock(sk); return PTR_ERR(ssk); } ret = tcp_setsockopt(ssk, SOL_IPV6, optname, optval, optlen); if (ret != 0) { - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_v6(struct mptcp_sock *msk, int optname, break; } - release_sock(sk); + sockopt_release_sock(sk); break; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp_congestion(struct mptcp_sock *msk, sockptr_t cap_net_admin = ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN); ret = 0; - lock_sock(sk); + sockopt_lock_sock(sk); sockopt_seq_inc(msk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp_congestion(struct mptcp_sock *msk, sockptr_t if (ret == 0) strscpy(msk->ca_name, name, sizeof(msk->ca_name)); - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_ip_set(struct mptcp_sock *msk, int optname, if (err != 0) return err; - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_nmpc_sk(msk); if (IS_ERR(ssk)) { - release_sock(sk); + sockopt_release_sock(sk); return PTR_ERR(ssk); } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_ip_set(struct mptcp_sock *msk, int optname, READ_ONCE(inet_sk(sk)->local_port_range)); break; default: - release_sock(sk); + sockopt_release_sock(sk); WARN_ON_ONCE(1); return -EOPNOTSUPP; } sockopt_seq_inc(msk); - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_v4_set_tos(struct mptcp_sock *msk, int optname, if (err != 0) return err; - lock_sock(sk); + sockopt_lock_sock(sk); sockopt_seq_inc(msk); val = READ_ONCE(inet_sk(sk)->tos); mptcp_for_each_subflow(msk, subflow) { @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_v4_set_tos(struct mptcp_sock *msk, int optname, __ip_sock_set_tos(ssk, val); release_sock(ssk); } - release_sock(sk); + sockopt_release_sock(sk); return 0; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_first_sf_only(struct mptcp_sock *msk, int level, int int ret; /* Limit to first subflow, before the connection establishment */ - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_nmpc_sk(msk); if (IS_ERR(ssk)) { ret = PTR_ERR(ssk); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_first_sf_only(struct mptcp_sock *msk, int level, int ret = tcp_setsockopt(ssk, level, optname, optval, optlen); unlock: - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp(struct mptcp_sock *msk, int optname, if (ret) return ret; - lock_sock(sk); + sockopt_lock_sock(sk); switch (optname) { case TCP_INQ: if (val < 0 || val > 1) @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp(struct mptcp_sock *msk, int optname, ret = -ENOPROTOOPT; } - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ int mptcp_setsockopt(struct sock *sk, int level, int optname, * is in TCP fallback, when TCP socket options are passed through * to the one remaining subflow. */ - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_tcp_fallback(msk); - release_sock(sk); + sockopt_release_sock(sk); if (ssk) return tcp_setsockopt(ssk, level, optname, optval, optlen); @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_first_sf_only(struct mptcp_sock *msk, int level, int struct sock *ssk; int ret; - lock_sock(sk); + sockopt_lock_sock(sk); ssk = msk->first; if (ssk) goto get; @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_first_sf_only(struct mptcp_sock *msk, int level, int ret = tcp_getsockopt(ssk, level, optname, optval, optlen); out: - release_sock(sk); + sockopt_release_sock(sk); return ret; } @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_tcpinfo(struct mptcp_sock *msk, char __user *optval, infoptr = optval + sfd.size_subflow_data; - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_tcpinfo(struct mptcp_sock *msk, char __user *optval, tcp_get_info(ssk, &info); if (copy_to_user(infoptr, &info, sfd.size_user)) { - release_sock(sk); + sockopt_release_sock(sk); return -EFAULT; } @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_tcpinfo(struct mptcp_sock *msk, char __user *optval, } } - release_sock(sk); + sockopt_release_sock(sk); sfd.num_subflows = sfcount; @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_subflow_addrs(struct mptcp_sock *msk, char __user *o addrptr = optval + sfd.size_subflow_data; - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_subflow_addrs(struct mptcp_sock *msk, char __user *o mptcp_get_sub_addrs(ssk, &a); if (copy_to_user(addrptr, &a, sfd.size_user)) { - release_sock(sk); + sockopt_release_sock(sk); return -EFAULT; } @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_subflow_addrs(struct mptcp_sock *msk, char __user *o } } - release_sock(sk); + sockopt_release_sock(sk); sfd.num_subflows = sfcount; @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_full_info(struct mptcp_sock *msk, char __user *optva sizeof(struct mptcp_subflow_info)); tcpinfoptr = u64_to_user_ptr(mfi.tcp_info); - lock_sock(sk); + sockopt_lock_sock(sk); mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); struct mptcp_subflow_info sfinfo; @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_full_info(struct mptcp_sock *msk, char __user *optva tcpinfoptr += mfi.size_tcpinfo_user; sfinfoptr += mfi.size_sfinfo_user; } - release_sock(sk); + sockopt_release_sock(sk); mfi.num_subflows = sfcount; if (mptcp_put_full_info(&mfi, optval, copylen, optlen)) @@ -XXX,XX +XXX,XX @@ static int mptcp_getsockopt_full_info(struct mptcp_sock *msk, char __user *optva return 0; fail_release: - release_sock(sk); + sockopt_release_sock(sk); return -EFAULT; } @@ -XXX,XX +XXX,XX @@ int mptcp_getsockopt(struct sock *sk, int level, int optname, * is in TCP fallback, when socket options are passed through * to the one remaining subflow. */ - lock_sock(sk); + sockopt_lock_sock(sk); ssk = __mptcp_tcp_fallback(msk); - release_sock(sk); + sockopt_release_sock(sk); if (ssk) return tcp_getsockopt(ssk, level, optname, optval, option); -- 2.43.0
From: Gang Yan <yangang@kylinos.cn> When a BPF program calls bpf_setsockopt(), it may run in softirq context where ns_capable() is not appropriate as there is no valid credential context. Use sockopt_ns_capable() instead, which skips the capability check when invoked from a BPF program. Additionally, the load parameter of tcp_set_congestion_control() is changed from 'true' to '!has_current_bpf_ctx()' to match what TCP does: when called from BPF context, use tcp_ca_find() instead of tcp_ca_find_autoload() to avoid module loading in atomic context. Signed-off-by: Gang Yan <yangang@kylinos.cn> --- net/mptcp/sockopt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c index XXXXXXX..XXXXXXX 100644 --- a/net/mptcp/sockopt.c +++ b/net/mptcp/sockopt.c @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp_congestion(struct mptcp_sock *msk, sockptr_t name[ret] = 0; - cap_net_admin = ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN); + cap_net_admin = sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN); ret = 0; sockopt_lock_sock(sk); @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp_congestion(struct mptcp_sock *msk, sockptr_t int err; lock_sock(ssk); - err = tcp_set_congestion_control(ssk, name, true, cap_net_admin); + err = tcp_set_congestion_control(ssk, name, !has_current_bpf_ctx(), cap_net_admin); if (err < 0 && ret == 0) ret = err; subflow->setsockopt_seq = msk->setsockopt_seq; -- 2.43.0
From: Gang Yan <yangang@kylinos.cn> Several MPTCP setsockopt handlers need to acquire the subflow lock via lock_sock(ssk) to propagate settings to each subflow. This lock can sleep and is therefore not usable in BPF context where sleeping is forbidden. The short-term solution is to make any sockopt operation that requires subflow-level lock fail with -EOPNOTSUPP when called from BPF context. Signed-off-by: Gang Yan <yangang@kylinos.cn> --- net/mptcp/sockopt.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c index XXXXXXX..XXXXXXX 100644 --- a/net/mptcp/sockopt.c +++ b/net/mptcp/sockopt.c @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_int(struct mptcp_sock *msk, int optname, if (ret) return ret; + if (has_current_bpf_ctx()) + return -EOPNOTSUPP; + switch (optname) { case SO_KEEPALIVE: case SO_DEBUG: @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_timestamping(struct mptcp_sock *msk, struct so_timestamping timestamping; int ret; + if (has_current_bpf_ctx()) + return -EOPNOTSUPP; + if (optlen == sizeof(timestamping)) { if (copy_from_sockptr(×tamping, optval, sizeof(timestamping))) @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_socket_linger(struct mptcp_sock *msk, sockptr_t sockptr_t kopt; int ret; + if (has_current_bpf_ctx()) + return -EOPNOTSUPP; + if (optlen < sizeof(ling)) return -EINVAL; @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_sol_tcp_congestion(struct mptcp_sock *msk, sockptr_t cap_net_admin = sockopt_ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN); + if (has_current_bpf_ctx()) + return -EOPNOTSUPP; + ret = 0; sockopt_lock_sock(sk); sockopt_seq_inc(msk); @@ -XXX,XX +XXX,XX @@ static int __mptcp_setsockopt_set_val(struct mptcp_sock *msk, int max, struct mptcp_subflow_context *subflow; int err = 0; + if (has_current_bpf_ctx()) + return -EOPNOTSUPP; + mptcp_for_each_subflow(msk, subflow) { struct sock *ssk = mptcp_subflow_tcp_sock(subflow); int ret; @@ -XXX,XX +XXX,XX @@ static int __mptcp_setsockopt_sol_tcp_cork(struct mptcp_sock *msk, int val) struct mptcp_subflow_context *subflow; struct sock *sk = (struct sock *)msk; + if (has_current_bpf_ctx()) + return -EOPNOTSUPP; + sockopt_seq_inc(msk); msk->cork = !!val; mptcp_for_each_subflow(msk, subflow) { @@ -XXX,XX +XXX,XX @@ static int __mptcp_setsockopt_sol_tcp_nodelay(struct mptcp_sock *msk, int val) struct mptcp_subflow_context *subflow; struct sock *sk = (struct sock *)msk; + if (has_current_bpf_ctx()) + return -EOPNOTSUPP; + sockopt_seq_inc(msk); msk->nodelay = !!val; mptcp_for_each_subflow(msk, subflow) { @@ -XXX,XX +XXX,XX @@ static int mptcp_setsockopt_v4_set_tos(struct mptcp_sock *msk, int optname, struct sock *sk = (struct sock *)msk; int err, val; + if (has_current_bpf_ctx()) + return -EOPNOTSUPP; + err = ip_setsockopt(sk, SOL_IP, optname, optval, optlen); if (err != 0) @@ -XXX,XX +XXX,XX @@ int mptcp_set_rcvlowat(struct sock *sk, int val) if (space <= sk->sk_rcvbuf) return 0; + if (has_current_bpf_ctx()) + return -EOPNOTSUPP; + /* propagate the rcvbuf changes to all the subflows */ WRITE_ONCE(sk->sk_rcvbuf, space); mptcp_for_each_subflow(mptcp_sk(sk), subflow) { -- 2.43.0
From: Gang Yan <yangang@kylinos.cn> When a cgroup/setsockopt BPF program calls bpf_setsockopt() on an MPTCP socket, __bpf_setsockopt() currently handles the option through sol_socket_sockopt()/sol_tcp_sockopt()/sol_ip_sockopt() directly, bypassing the MPTCP setsockopt handler entirely. This means options are applied to the msk only, without being propagated to first ssk. Signed-off-by: Gang Yan <yangang@kylinos.cn> --- net/core/filter.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/core/filter.c b/net/core/filter.c index XXXXXXX..XXXXXXX 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -XXX,XX +XXX,XX @@ static int __bpf_setsockopt(struct sock *sk, int level, int optname, { if (!sk_fullsock(sk)) return -EINVAL; + if (sk->sk_protocol == IPPROTO_MPTCP) { + struct socket *sock = sk->sk_socket; + + return sock->ops->setsockopt(sock, level, optname, + KERNEL_SOCKPTR(optval), optlen); + } if (level == SOL_SOCKET) return sol_socket_sockopt(sk, optname, optval, &optlen, false); -- 2.43.0
From: Gang Yan <yangang@kylinos.cn> Add a test to verify that bpf_setsockopt() called from a cgroup/setsockopt BPF program on an MPTCP socket correctly returns -EOPNOTSUPP when the target option requires subflow-level locking. Assisted-by: Claude:glm-5.1 Signed-off-by: Gang Yan <yangang@kylinos.cn> --- tools/testing/selftests/bpf/prog_tests/mptcp.c | 64 +++++++++++++++++++ .../testing/selftests/bpf/progs/mptcp_setsockopt.c | 71 ++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/mptcp.c b/tools/testing/selftests/bpf/prog_tests/mptcp.c index XXXXXXX..XXXXXXX 100644 --- a/tools/testing/selftests/bpf/prog_tests/mptcp.c +++ b/tools/testing/selftests/bpf/prog_tests/mptcp.c @@ -XXX,XX +XXX,XX @@ #include "mptcp_bpf_rr.skel.h" #include "mptcp_bpf_red.skel.h" #include "mptcp_bpf_burst.skel.h" +#include "mptcp_setsockopt.skel.h" #define NS_TEST "mptcp_ns" #define ADDR_1 "10.0.1.1" @@ -XXX,XX +XXX,XX @@ static void test_burst(void) mptcp_bpf_burst__destroy(skel); } +static void test_setsockopt(void) +{ + struct mptcp_setsockopt *skel; + struct netns_obj *netns; + int cgroup_fd, server_fd, client_fd; + int map_fd, err; + __s32 val = 1024 * 1024, result; + __u32 key = 0; + + cgroup_fd = test__join_cgroup("/mptcp_setsockopt"); + if (!ASSERT_OK_FD(cgroup_fd, "join_cgroup")) + return; + + skel = mptcp_setsockopt__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open_load")) + goto close_cgroup; + + err = bpf_prog_attach(bpf_program__fd(skel->progs.mptcp_setsockopt), + cgroup_fd, BPF_CGROUP_SETSOCKOPT, 0); + if (!ASSERT_OK(err, "bpf_prog_attach")) + goto skel_destroy; + + netns = netns_new(NS_TEST, true); + if (!ASSERT_OK_PTR(netns, "netns_new")) + goto skel_destroy; + + server_fd = start_mptcp_server(AF_INET, NULL, 0, 0); + if (!ASSERT_OK_FD(server_fd, "start_mptcp_server")) + goto close_netns; + + client_fd = connect_to_fd(server_fd, 0); + if (!ASSERT_OK_FD(client_fd, "connect_to_fd")) + goto close_server; + + /* Trigger cgroup/setsockopt BPF program by calling setsockopt. + * The BPF program calls bpf_setsockopt(sk, SO_RCVLOWAT, ...) which + * reaches mptcp_set_rcvlowat(). There has_current_bpf_ctx() is true, + * so it should return -EOPNOTSUPP. + */ + err = setsockopt(client_fd, SOL_SOCKET, SO_RCVLOWAT, &val, sizeof(val)); + ASSERT_OK(err, "setsockopt(SO_RCVLOWAT)"); + + map_fd = bpf_map__fd(skel->maps.results); + err = bpf_map_lookup_elem(map_fd, &key, &result); + if (!ASSERT_OK(err, "bpf_map_lookup_elem")) + goto close_client; + + ASSERT_EQ(result, -EOPNOTSUPP, "bpf_setsockopt(SO_RCVLOWAT)"); + +close_client: + close(client_fd); +close_server: + close(server_fd); +close_netns: + netns_free(netns); +skel_destroy: + mptcp_setsockopt__destroy(skel); +close_cgroup: + close(cgroup_fd); +} + void test_mptcp(void) { if (test__start_subtest("base")) @@ -XXX,XX +XXX,XX @@ void test_mptcp(void) test_red(); if (test__start_subtest("burst")) test_burst(); + if (test__start_subtest("setsockopt")) + test_setsockopt(); } diff --git a/tools/testing/selftests/bpf/progs/mptcp_setsockopt.c b/tools/testing/selftests/bpf/progs/mptcp_setsockopt.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tools/testing/selftests/bpf/progs/mptcp_setsockopt.c @@ -XXX,XX +XXX,XX @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025, SUSE. */ + +/* Test that bpf_setsockopt() called from a cgroup/setsockopt BPF program + * on an MPTCP socket returns -EOPNOTSUPP when the target option requires + * subflow-level locking (e.g. SO_RCVLOWAT via mptcp_set_rcvlowat). + * + * Flow: + * userspace: setsockopt(mptcp_fd, SOL_SOCKET, SO_RCVLOWAT, &large_val, ...) + * -> do_sock_setsockopt() + * -> BPF_CGROUP_RUN_PROG_SETSOCKOPT(sk) // sk = MPTCP meta socket + * BPF prog: read val from ctx->optval, + * bpf_setsockopt(sk, SOL_SOCKET, SO_RCVLOWAT, &val, 4) + * -> sk_setsockopt() + * -> ops->set_rcvlowat = mptcp_set_rcvlowat() + * -> has_current_bpf_ctx() == true + * -> return -EOPNOTSUPP + */ + +#include "bpf_tracing_net.h" +#include <bpf/bpf_helpers.h> + +#ifndef IPPROTO_MPTCP +#define IPPROTO_MPTCP 262 +#endif + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __s32); +} results SEC(".maps"); + +SEC("cgroup/setsockopt") +int mptcp_setsockopt(struct bpf_sockopt *ctx) +{ + struct bpf_sock *sk = ctx->sk; + __s32 val, ret; + __u32 key = 0; + + /* Only interested in MPTCP + SO_RCVLOWAT */ + if (!sk || sk->protocol != IPPROTO_MPTCP) + return 1; + + if (ctx->level != SOL_SOCKET || ctx->optname != SO_RCVLOWAT) + return 1; + + /* Read value from ctx, verifier needs bounds check. + * Save optval pointer to local var so the verifier tracks + * the same register through bounds check and dereference. + */ + void *optval = ctx->optval; + + if (ctx->optlen < sizeof(val)) + return 1; + if (optval + sizeof(val) > ctx->optval_end) + return 1; + val = *(__s32 *)optval; + + /* Forward the setsockopt via bpf_setsockopt. + * This reaches mptcp_set_rcvlowat() which checks has_current_bpf_ctx() + * and should return -EOPNOTSUPP. + */ + ret = bpf_setsockopt(sk, SOL_SOCKET, SO_RCVLOWAT, &val, sizeof(val)); + bpf_map_update_elem(&results, &key, &ret, BPF_ANY); + + /* BPF handled this, don't invoke kernel handler */ + return 1; +} + +char _license[] SEC("license") = "GPL"; -- 2.43.0