diff mbox series

[v2,bpf-next,5/5] selftests/bpf: Test for sk helpers in cgroup skb

Message ID 646dff71848bd93780581cf4e0f5a70f7f386966.1589405669.git.rdna@fb.com
State Changes Requested
Delegated to: BPF Maintainers
Headers show
Series bpf: sk lookup, cgroup id helpers in cgroup skb | expand

Commit Message

Andrey Ignatov May 13, 2020, 9:38 p.m. UTC
Test bpf_sk_lookup_tcp, bpf_sk_release, bpf_sk_cgroup_id and
bpf_sk_ancestor_cgroup_id helpers from cgroup skb program.

The test creates a testing cgroup, starts a TCPv6 server inside the
cgroup and creates two client sockets: one inside testing cgroup and one
outside.

Then it attaches cgroup skb program to the cgroup that checks all TCP
segments coming to the server and allows only those coming from the
cgroup of the server. If a segment comes from a peer outside of the
cgroup, it'll be dropped.

Finally the test checks that client from inside testing cgroup can
successfully connect to the server, but client outside the cgroup fails
to connect by timeout.

The main goal of the test is to check newly introduced
bpf_sk_{,ancestor_}cgroup_id helpers.

It also checks a couple of socket lookup helpers (tcp & release), but
lookup helpers were introduced much earlier and covered by other tests.
Here it's mostly checked that they can be called from cgroup skb.

Signed-off-by: Andrey Ignatov <rdna@fb.com>
---
 .../bpf/prog_tests/cgroup_skb_sk_lookup.c     |  99 +++++++++++++++++
 .../bpf/progs/cgroup_skb_sk_lookup_kern.c     | 105 ++++++++++++++++++
 2 files changed, 204 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/cgroup_skb_sk_lookup.c
 create mode 100644 tools/testing/selftests/bpf/progs/cgroup_skb_sk_lookup_kern.c

Comments

Yonghong Song May 14, 2020, 4:07 p.m. UTC | #1
On 5/13/20 2:38 PM, Andrey Ignatov wrote:
> Test bpf_sk_lookup_tcp, bpf_sk_release, bpf_sk_cgroup_id and
> bpf_sk_ancestor_cgroup_id helpers from cgroup skb program.
> 
> The test creates a testing cgroup, starts a TCPv6 server inside the
> cgroup and creates two client sockets: one inside testing cgroup and one
> outside.
> 
> Then it attaches cgroup skb program to the cgroup that checks all TCP
> segments coming to the server and allows only those coming from the
> cgroup of the server. If a segment comes from a peer outside of the
> cgroup, it'll be dropped.
> 
> Finally the test checks that client from inside testing cgroup can
> successfully connect to the server, but client outside the cgroup fails
> to connect by timeout.
> 
> The main goal of the test is to check newly introduced
> bpf_sk_{,ancestor_}cgroup_id helpers.
> 
> It also checks a couple of socket lookup helpers (tcp & release), but
> lookup helpers were introduced much earlier and covered by other tests.
> Here it's mostly checked that they can be called from cgroup skb.
> 
> Signed-off-by: Andrey Ignatov <rdna@fb.com>
> ---
>   .../bpf/prog_tests/cgroup_skb_sk_lookup.c     |  99 +++++++++++++++++
>   .../bpf/progs/cgroup_skb_sk_lookup_kern.c     | 105 ++++++++++++++++++
>   2 files changed, 204 insertions(+)
>   create mode 100644 tools/testing/selftests/bpf/prog_tests/cgroup_skb_sk_lookup.c
>   create mode 100644 tools/testing/selftests/bpf/progs/cgroup_skb_sk_lookup_kern.c
> 
> diff --git a/tools/testing/selftests/bpf/prog_tests/cgroup_skb_sk_lookup.c b/tools/testing/selftests/bpf/prog_tests/cgroup_skb_sk_lookup.c
> new file mode 100644
> index 000000000000..7ae0f49a2118
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/cgroup_skb_sk_lookup.c
> @@ -0,0 +1,99 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// Copyright (c) 2020 Facebook
> +
> +#include <test_progs.h>
> +
> +#include "network_helpers.h"
> +#include "cgroup_skb_sk_lookup_kern.skel.h"
> +
> +static void run_lookup_test(int map_fd, int out_sk)
> +{
> +	int serv_sk = -1, in_sk = -1, serv_in_sk = -1, err;
> +	__u32 serv_port_key = 0, duration = 0;
> +	struct sockaddr_in6 addr = {};
> +	socklen_t addr_len = sizeof(addr);
> +
> +	serv_sk = start_server(AF_INET6, SOCK_STREAM);
> +	if (CHECK(serv_sk < 0, "start_server", "failed to start server\n"))
> +		return;
> +
> +	err = getsockname(serv_sk, (struct sockaddr *)&addr, &addr_len);
> +	if (CHECK(err, "getsockname", "errno %d\n", errno))
> +		goto cleanup;
> +
> +	err = bpf_map_update_elem(map_fd, &serv_port_key, &addr.sin6_port, 0);
> +	if (CHECK(err < 0, "map_update", "errno %d", errno))
> +		goto cleanup;
> +
> +	/* Client outside of test cgroup should fail to connect by timeout. */
> +	err = connect_fd_to_fd(out_sk, serv_sk);
> +	if (CHECK(!err || errno != EINPROGRESS, "connect_fd_to_fd",
> +		  "unexpected result err %d errno %d\n", err, errno))
> +		goto cleanup;
> +
> +	err = connect_wait(out_sk);
> +	if (CHECK(err, "connect_wait", "unexpected result %d\n", err))
> +		goto cleanup;
> +
> +	/* Client inside test cgroup should connect just fine. */
> +	in_sk = connect_to_fd(AF_INET6, SOCK_STREAM, serv_sk);
> +	if (CHECK(in_sk < 0, "connect_to_fd", "errno %d\n", errno))
> +		goto cleanup;
> +
> +	serv_in_sk = accept(serv_sk, NULL, NULL);
> +	if (CHECK(serv_in_sk < 0, "accept", "errno %d\n", errno))
> +		goto cleanup;
> +
> +cleanup:
> +	close(serv_in_sk);
> +	close(in_sk);
> +	close(serv_sk);
> +}
> +
> +static void run_cgroup_bpf_test(const char *cg_path, const char *bpf_file,

You are using skeleton, bpf_file parameter is not needed any more.

> +				int out_sk)
> +{
> +	struct cgroup_skb_sk_lookup_kern *skel;
> +	struct bpf_link *link;
> +	__u32 duration = 0;
> +	int cgfd = -1;
> +
> +	skel = cgroup_skb_sk_lookup_kern__open_and_load();
> +	if (CHECK(!skel, "skel_open_load", "open_load failed\n"))
> +		return;
> +
> +	cgfd = test__join_cgroup(cg_path);
> +	if (CHECK(cgfd < 0, "cgroup_join", "cgroup setup failed\n"))
> +		goto cleanup;
> +
> +	link = bpf_program__attach_cgroup(skel->progs.ingress_lookup, cgfd);
> +	if (CHECK(IS_ERR(link), "cgroup_attach", "err: %ld\n", PTR_ERR(link)))
> +		goto cleanup;
> +
> +	run_lookup_test(bpf_map__fd(skel->maps.serv_port), out_sk);
> +
> +	bpf_link__destroy(link);
> +
> +cleanup:
> +	close(cgfd);
> +	cgroup_skb_sk_lookup_kern__destroy(skel);
> +}
> +
[...]
> +
> +struct {
> +	__uint(type, BPF_MAP_TYPE_ARRAY);
> +	__uint(max_entries, 1);
> +	__type(key, __u32);
> +	__type(value, __u16);
> +} serv_port SEC(".maps");

This can be simplified as a global variable, e.g.,

__u16 serv_port = 0;

and use skeleton to access this variable in user space
and in kernel you can directly use this variable.

> +
> +
> +static inline void set_ip(__u32 *dst, const struct in6_addr *src)
> +{
> +	dst[0] = src->in6_u.u6_addr32[0];
> +	dst[1] = src->in6_u.u6_addr32[1];
> +	dst[2] = src->in6_u.u6_addr32[2];
> +	dst[3] = src->in6_u.u6_addr32[3];
> +}
[...]
Andrey Ignatov May 14, 2020, 5:46 p.m. UTC | #2
Yonghong Song <yhs@fb.com> [Thu, 2020-05-14 09:07 -0700]:
> On 5/13/20 2:38 PM, Andrey Ignatov wrote:

> > @@ -0,0 +1,99 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +// Copyright (c) 2020 Facebook
> > +
> > +#include <test_progs.h>
> > +
> > +#include "network_helpers.h"
> > +#include "cgroup_skb_sk_lookup_kern.skel.h"
> > +
> > +static void run_lookup_test(int map_fd, int out_sk)
> > +{
> > +	int serv_sk = -1, in_sk = -1, serv_in_sk = -1, err;
> > +	__u32 serv_port_key = 0, duration = 0;
> > +	struct sockaddr_in6 addr = {};
> > +	socklen_t addr_len = sizeof(addr);
> > +
> > +	serv_sk = start_server(AF_INET6, SOCK_STREAM);
> > +	if (CHECK(serv_sk < 0, "start_server", "failed to start server\n"))
> > +		return;
> > +
> > +	err = getsockname(serv_sk, (struct sockaddr *)&addr, &addr_len);
> > +	if (CHECK(err, "getsockname", "errno %d\n", errno))
> > +		goto cleanup;
> > +
> > +	err = bpf_map_update_elem(map_fd, &serv_port_key, &addr.sin6_port, 0);
> > +	if (CHECK(err < 0, "map_update", "errno %d", errno))
> > +		goto cleanup;
> > +
> > +	/* Client outside of test cgroup should fail to connect by timeout. */
> > +	err = connect_fd_to_fd(out_sk, serv_sk);
> > +	if (CHECK(!err || errno != EINPROGRESS, "connect_fd_to_fd",
> > +		  "unexpected result err %d errno %d\n", err, errno))
> > +		goto cleanup;
> > +
> > +	err = connect_wait(out_sk);
> > +	if (CHECK(err, "connect_wait", "unexpected result %d\n", err))
> > +		goto cleanup;
> > +
> > +	/* Client inside test cgroup should connect just fine. */
> > +	in_sk = connect_to_fd(AF_INET6, SOCK_STREAM, serv_sk);
> > +	if (CHECK(in_sk < 0, "connect_to_fd", "errno %d\n", errno))
> > +		goto cleanup;
> > +
> > +	serv_in_sk = accept(serv_sk, NULL, NULL);
> > +	if (CHECK(serv_in_sk < 0, "accept", "errno %d\n", errno))
> > +		goto cleanup;
> > +
> > +cleanup:
> > +	close(serv_in_sk);
> > +	close(in_sk);
> > +	close(serv_sk);
> > +}
> > +
> > +static void run_cgroup_bpf_test(const char *cg_path, const char *bpf_file,
> 
> You are using skeleton, bpf_file parameter is not needed any more.

Oops, forgot to remove it while switching to skeleton. Will fix. Thanks.

> > +struct {
> > +	__uint(type, BPF_MAP_TYPE_ARRAY);
> > +	__uint(max_entries, 1);
> > +	__type(key, __u32);
> > +	__type(value, __u16);
> > +} serv_port SEC(".maps");
> 
> This can be simplified as a global variable, e.g.,
> 
> __u16 serv_port = 0;
> 
> and use skeleton to access this variable in user space
> and in kernel you can directly use this variable.

No strong preference here. Ok, will try global var.
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/prog_tests/cgroup_skb_sk_lookup.c b/tools/testing/selftests/bpf/prog_tests/cgroup_skb_sk_lookup.c
new file mode 100644
index 000000000000..7ae0f49a2118
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/cgroup_skb_sk_lookup.c
@@ -0,0 +1,99 @@ 
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020 Facebook
+
+#include <test_progs.h>
+
+#include "network_helpers.h"
+#include "cgroup_skb_sk_lookup_kern.skel.h"
+
+static void run_lookup_test(int map_fd, int out_sk)
+{
+	int serv_sk = -1, in_sk = -1, serv_in_sk = -1, err;
+	__u32 serv_port_key = 0, duration = 0;
+	struct sockaddr_in6 addr = {};
+	socklen_t addr_len = sizeof(addr);
+
+	serv_sk = start_server(AF_INET6, SOCK_STREAM);
+	if (CHECK(serv_sk < 0, "start_server", "failed to start server\n"))
+		return;
+
+	err = getsockname(serv_sk, (struct sockaddr *)&addr, &addr_len);
+	if (CHECK(err, "getsockname", "errno %d\n", errno))
+		goto cleanup;
+
+	err = bpf_map_update_elem(map_fd, &serv_port_key, &addr.sin6_port, 0);
+	if (CHECK(err < 0, "map_update", "errno %d", errno))
+		goto cleanup;
+
+	/* Client outside of test cgroup should fail to connect by timeout. */
+	err = connect_fd_to_fd(out_sk, serv_sk);
+	if (CHECK(!err || errno != EINPROGRESS, "connect_fd_to_fd",
+		  "unexpected result err %d errno %d\n", err, errno))
+		goto cleanup;
+
+	err = connect_wait(out_sk);
+	if (CHECK(err, "connect_wait", "unexpected result %d\n", err))
+		goto cleanup;
+
+	/* Client inside test cgroup should connect just fine. */
+	in_sk = connect_to_fd(AF_INET6, SOCK_STREAM, serv_sk);
+	if (CHECK(in_sk < 0, "connect_to_fd", "errno %d\n", errno))
+		goto cleanup;
+
+	serv_in_sk = accept(serv_sk, NULL, NULL);
+	if (CHECK(serv_in_sk < 0, "accept", "errno %d\n", errno))
+		goto cleanup;
+
+cleanup:
+	close(serv_in_sk);
+	close(in_sk);
+	close(serv_sk);
+}
+
+static void run_cgroup_bpf_test(const char *cg_path, const char *bpf_file,
+				int out_sk)
+{
+	struct cgroup_skb_sk_lookup_kern *skel;
+	struct bpf_link *link;
+	__u32 duration = 0;
+	int cgfd = -1;
+
+	skel = cgroup_skb_sk_lookup_kern__open_and_load();
+	if (CHECK(!skel, "skel_open_load", "open_load failed\n"))
+		return;
+
+	cgfd = test__join_cgroup(cg_path);
+	if (CHECK(cgfd < 0, "cgroup_join", "cgroup setup failed\n"))
+		goto cleanup;
+
+	link = bpf_program__attach_cgroup(skel->progs.ingress_lookup, cgfd);
+	if (CHECK(IS_ERR(link), "cgroup_attach", "err: %ld\n", PTR_ERR(link)))
+		goto cleanup;
+
+	run_lookup_test(bpf_map__fd(skel->maps.serv_port), out_sk);
+
+	bpf_link__destroy(link);
+
+cleanup:
+	close(cgfd);
+	cgroup_skb_sk_lookup_kern__destroy(skel);
+}
+
+void test_cgroup_skb_sk_lookup(void)
+{
+	const char *bpf_file = "cgroup_skb_sk_lookup_kern.o";
+	const char *cg_path = "/foo";
+	int out_sk;
+
+	/* Create a socket before joining testing cgroup so that its cgroup id
+	 * differs from that of testing cgroup. Moving selftests process to
+	 * testing cgroup won't change cgroup id of an already created socket.
+	 */
+	out_sk = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	if (CHECK_FAIL(out_sk < 0))
+		return;
+
+	run_cgroup_bpf_test(cg_path, bpf_file, out_sk);
+
+	close(out_sk);
+}
diff --git a/tools/testing/selftests/bpf/progs/cgroup_skb_sk_lookup_kern.c b/tools/testing/selftests/bpf/progs/cgroup_skb_sk_lookup_kern.c
new file mode 100644
index 000000000000..759ee1ff8bd5
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/cgroup_skb_sk_lookup_kern.c
@@ -0,0 +1,105 @@ 
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020 Facebook
+
+#include <linux/bpf.h>
+#include <bpf/bpf_endian.h>
+#include <bpf/bpf_helpers.h>
+
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+int _version SEC("version") = 1;
+char _license[] SEC("license") = "GPL";
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, __u32);
+	__type(value, __u16);
+} serv_port SEC(".maps");
+
+
+static inline void set_ip(__u32 *dst, const struct in6_addr *src)
+{
+	dst[0] = src->in6_u.u6_addr32[0];
+	dst[1] = src->in6_u.u6_addr32[1];
+	dst[2] = src->in6_u.u6_addr32[2];
+	dst[3] = src->in6_u.u6_addr32[3];
+}
+
+static inline void set_tuple(struct bpf_sock_tuple *tuple,
+			     const struct ipv6hdr *ip6h,
+			     const struct tcphdr *tcph)
+{
+	set_ip(tuple->ipv6.saddr, &ip6h->daddr);
+	set_ip(tuple->ipv6.daddr, &ip6h->saddr);
+	tuple->ipv6.sport = tcph->dest;
+	tuple->ipv6.dport = tcph->source;
+}
+
+static inline int is_allowed_peer_cg(struct __sk_buff *skb,
+				     const struct ipv6hdr *ip6h,
+				     const struct tcphdr *tcph)
+{
+	__u64 cgid, acgid, peer_cgid, peer_acgid;
+	struct bpf_sock_tuple tuple;
+	size_t tuple_len = sizeof(tuple.ipv6);
+	struct bpf_sock *peer_sk;
+
+	set_tuple(&tuple, ip6h, tcph);
+
+	peer_sk = bpf_sk_lookup_tcp(skb, &tuple, tuple_len,
+				    BPF_F_CURRENT_NETNS, 0);
+	if (!peer_sk)
+		return 0;
+
+	cgid = bpf_skb_cgroup_id(skb);
+	peer_cgid = bpf_sk_cgroup_id(peer_sk);
+
+	acgid = bpf_skb_ancestor_cgroup_id(skb, 2);
+	peer_acgid = bpf_sk_ancestor_cgroup_id(peer_sk, 2);
+
+	bpf_sk_release(peer_sk);
+
+	return cgid && cgid == peer_cgid && acgid && acgid == peer_acgid;
+}
+
+SEC("cgroup_skb/ingress")
+int ingress_lookup(struct __sk_buff *skb)
+{
+	__u32 serv_port_key = 0;
+	__u16 *serv_port_val;
+	struct ipv6hdr ip6h;
+	struct tcphdr tcph;
+
+	if (skb->protocol != bpf_htons(ETH_P_IPV6))
+		return 1;
+
+	/* For SYN packets coming to listening socket skb->remote_port will be
+	 * zero, so IPv6/TCP headers are loaded to identify remote peer
+	 * instead.
+	 */
+	if (bpf_skb_load_bytes(skb, 0, &ip6h, sizeof(ip6h)))
+		return 1;
+
+	if (ip6h.nexthdr != IPPROTO_TCP)
+		return 1;
+
+	if (bpf_skb_load_bytes(skb, sizeof(ip6h), &tcph, sizeof(tcph)))
+		return 1;
+
+	serv_port_val = bpf_map_lookup_elem(&serv_port, &serv_port_key);
+	if (!serv_port_val || !*serv_port_val)
+		return 0;
+
+	if (tcph.dest != *serv_port_val)
+		return 1;
+
+	return is_allowed_peer_cg(skb, &ip6h, &tcph);
+}