Skip to content

Commit

Permalink
selftests/bpf: Add getsockopt to inspect mptcp subflow
Browse files Browse the repository at this point in the history
This patch adds a "cgroup/getsockopt" way to inspect the subflows of an
MPTCP socket, and verify the modifications done by the same BPF program
in the previous commit: a different mark per subflow, and a different
TCP CC set on the second one. This new hook will be used by the next
commit to verify the socket options set on each subflow.

This extra "cgroup/getsockopt" prog walks the msk->conn_list and use
bpf_core_cast to cast a pointer for readonly. It allows to inspect all
the fields of a structure.

Note that on the kernel side, the MPTCP socket stores a list of subflows
under 'msk->conn_list'. They can be iterated using the generic 'list'
helpers. They have been imported here, with a small difference:
list_for_each_entry() uses 'can_loop' to limit the number of iterations,
and ease its use. Because only data need to be read here, it is enough
to use this technique. It is planned to use bpf_iter, when BPF programs
will be used to modify data from the different subflows.
mptcp_subflow_tcp_sock() and mptcp_for_each_stubflow() helpers have also
be imported.

Suggested-by: Martin KaFai Lau <[email protected]>
Signed-off-by: Geliang Tang <[email protected]>
Reviewed-by: Matthieu Baerts (NGI0) <[email protected]>
Signed-off-by: Matthieu Baerts (NGI0) <[email protected]>
Link: https://lore.kernel.org/r/20240926-upstream-bpf-next-20240506-mptcp-subflow-test-v7-2-d26029e15cdd@kernel.org
Signed-off-by: Martin KaFai Lau <[email protected]>
  • Loading branch information
Geliang Tang authored and Martin KaFai Lau committed Oct 1, 2024
1 parent 83752e1 commit cd19b88
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 1 deletion.
2 changes: 1 addition & 1 deletion MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -16281,7 +16281,7 @@ F: include/net/mptcp.h
F: include/trace/events/mptcp.h
F: include/uapi/linux/mptcp*.h
F: net/mptcp/
F: tools/testing/selftests/bpf/*/*mptcp*.c
F: tools/testing/selftests/bpf/*/*mptcp*.[ch]
F: tools/testing/selftests/net/mptcp/

NETWORKING [TCP]
Expand Down
42 changes: 42 additions & 0 deletions tools/testing/selftests/bpf/progs/mptcp_bpf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __MPTCP_BPF_H__
#define __MPTCP_BPF_H__

#include "bpf_experimental.h"

/* list helpers from include/linux/list.h */
static inline int list_is_head(const struct list_head *list,
const struct list_head *head)
{
return list == head;
}

#define list_entry(ptr, type, member) \
container_of(ptr, type, member)

#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)

#define list_next_entry(pos, member) \
list_entry((pos)->member.next, typeof(*(pos)), member)

#define list_entry_is_head(pos, head, member) \
list_is_head(&pos->member, (head))

/* small difference: 'can_loop' has been added in the conditions */
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, typeof(*pos), member); \
!list_entry_is_head(pos, head, member) && can_loop; \
pos = list_next_entry(pos, member))

/* mptcp helpers from protocol.h */
#define mptcp_for_each_subflow(__msk, __subflow) \
list_for_each_entry(__subflow, &((__msk)->conn_list), node)

static __always_inline struct sock *
mptcp_subflow_tcp_sock(const struct mptcp_subflow_context *subflow)
{
return subflow->tcp_sock;
}

#endif
69 changes: 69 additions & 0 deletions tools/testing/selftests/bpf/progs/mptcp_subflow.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

/* vmlinux.h, bpf_helpers.h and other 'define' */
#include "bpf_tracing_net.h"
#include "mptcp_bpf.h"

char _license[] SEC("license") = "GPL";

char cc[TCP_CA_NAME_MAX] = "reno";
int pid;

/* Associate a subflow counter to each token */
struct {
Expand Down Expand Up @@ -57,3 +59,70 @@ int mptcp_subflow(struct bpf_sock_ops *skops)

return 1;
}

static int _check_getsockopt_subflow_mark(struct mptcp_sock *msk, struct bpf_sockopt *ctx)
{
struct mptcp_subflow_context *subflow;
int i = 0;

mptcp_for_each_subflow(msk, subflow) {
struct sock *ssk;

ssk = mptcp_subflow_tcp_sock(bpf_core_cast(subflow,
struct mptcp_subflow_context));

if (ssk->sk_mark != ++i) {
ctx->retval = -2;
break;
}
}

return 1;
}

static int _check_getsockopt_subflow_cc(struct mptcp_sock *msk, struct bpf_sockopt *ctx)
{
struct mptcp_subflow_context *subflow;

mptcp_for_each_subflow(msk, subflow) {
struct inet_connection_sock *icsk;
struct sock *ssk;

ssk = mptcp_subflow_tcp_sock(bpf_core_cast(subflow,
struct mptcp_subflow_context));
icsk = bpf_core_cast(ssk, struct inet_connection_sock);

if (ssk->sk_mark == 2 &&
__builtin_memcmp(icsk->icsk_ca_ops->name, cc, TCP_CA_NAME_MAX)) {
ctx->retval = -2;
break;
}
}

return 1;
}

SEC("cgroup/getsockopt")
int _getsockopt_subflow(struct bpf_sockopt *ctx)
{
struct bpf_sock *sk = ctx->sk;
struct mptcp_sock *msk;

if (bpf_get_current_pid_tgid() >> 32 != pid)
return 1;

if (!sk || sk->protocol != IPPROTO_MPTCP ||
(!(ctx->level == SOL_SOCKET && ctx->optname == SO_MARK) &&
!(ctx->level == SOL_TCP && ctx->optname == TCP_CONGESTION)))
return 1;

msk = bpf_core_cast(sk, struct mptcp_sock);
if (msk->pm.subflows != 1) {
ctx->retval = -1;
return 1;
}

if (ctx->optname == SO_MARK)
return _check_getsockopt_subflow_mark(msk, ctx);
return _check_getsockopt_subflow_cc(msk, ctx);
}

0 comments on commit cd19b88

Please sign in to comment.