selftests/bpf: Test for sk helpers in cgroup skb

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>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Yonghong Song <yhs@fb.com>
Link: https://lore.kernel.org/bpf/171f4c5d75e8ff4fe1c4e8c1c12288b5240a4549.1589486450.git.rdna@fb.com
This commit is contained in:
Andrey Ignatov 2020-05-14 13:03:49 -07:00 committed by Alexei Starovoitov
parent 383724e17a
commit 68e916bc8d
2 changed files with 192 additions and 0 deletions

View File

@ -0,0 +1,95 @@
// 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(__u16 *g_serv_port, int out_sk)
{
int serv_sk = -1, in_sk = -1, serv_in_sk = -1, err;
struct sockaddr_in6 addr = {};
socklen_t addr_len = sizeof(addr);
__u32 duration = 0;
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;
*g_serv_port = addr.sin6_port;
/* 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, 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(&skel->bss->g_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 *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, out_sk);
close(out_sk);
}

View File

@ -0,0 +1,97 @@
// 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";
__u16 g_serv_port = 0;
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;
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;
if (!g_serv_port)
return 0;
if (tcph.dest != g_serv_port)
return 1;
return is_allowed_peer_cg(skb, &ip6h, &tcph);
}