Skip to content

Commit

Permalink
selftests/bpf: Selftest for sys_connect hooks
Browse files Browse the repository at this point in the history
Add selftest for BPF_CGROUP_INET4_CONNECT and BPF_CGROUP_INET6_CONNECT
attach types.

Try to connect(2) to specified IP:port and test that:
* remote IP:port pair is overridden;
* local end of connection is bound to specified IP.

All combinations of IPv4/IPv6 and TCP/UDP are tested.

Example:
  # tcpdump -pn -i lo -w connect.pcap 2>/dev/null &
  [1] 478
  # strace -qqf -e connect -o connect.trace ./test_sock_addr.sh
  Wait for testing IPv4/IPv6 to become available ... OK
  Load bind4 with invalid type (can pollute stderr) ... REJECTED
  Load bind4 with valid type ... OK
  Attach bind4 with invalid type ... REJECTED
  Attach bind4 with valid type ... OK
  Load connect4 with invalid type (can pollute stderr) libbpf: load bpf \
    program failed: Permission denied
  libbpf: -- BEGIN DUMP LOG ---
  libbpf:
  0: (b7) r2 = 23569
  1: (63) *(u32 *)(r1 +24) = r2
  2: (b7) r2 = 16777343
  3: (63) *(u32 *)(r1 +4) = r2
  invalid bpf_context access off=4 size=4
  [ 1518.404609] random: crng init done

  libbpf: -- END LOG --
  libbpf: failed to load program 'cgroup/connect4'
  libbpf: failed to load object './connect4_prog.o'
  ... REJECTED
  Load connect4 with valid type ... OK
  Attach connect4 with invalid type ... REJECTED
  Attach connect4 with valid type ... OK
  Test case #1 (IPv4/TCP):
          Requested: bind(192.168.1.254, 4040) ..
             Actual: bind(127.0.0.1, 4444)
          Requested: connect(192.168.1.254, 4040) from (*, *) ..
             Actual: connect(127.0.0.1, 4444) from (127.0.0.4, 56068)
  Test case #2 (IPv4/UDP):
          Requested: bind(192.168.1.254, 4040) ..
             Actual: bind(127.0.0.1, 4444)
          Requested: connect(192.168.1.254, 4040) from (*, *) ..
             Actual: connect(127.0.0.1, 4444) from (127.0.0.4, 56447)
  Load bind6 with invalid type (can pollute stderr) ... REJECTED
  Load bind6 with valid type ... OK
  Attach bind6 with invalid type ... REJECTED
  Attach bind6 with valid type ... OK
  Load connect6 with invalid type (can pollute stderr) libbpf: load bpf \
    program failed: Permission denied
  libbpf: -- BEGIN DUMP LOG ---
  libbpf:
  0: (b7) r6 = 0
  1: (63) *(u32 *)(r1 +12) = r6
  invalid bpf_context access off=12 size=4

  libbpf: -- END LOG --
  libbpf: failed to load program 'cgroup/connect6'
  libbpf: failed to load object './connect6_prog.o'
  ... REJECTED
  Load connect6 with valid type ... OK
  Attach connect6 with invalid type ... REJECTED
  Attach connect6 with valid type ... OK
  Test case #3 (IPv6/TCP):
          Requested: bind(face:b00c:1234:5678::abcd, 6060) ..
             Actual: bind(::1, 6666)
          Requested: connect(face:b00c:1234:5678::abcd, 6060) from (*, *)
             Actual: connect(::1, 6666) from (::6, 37458)
  Test case #4 (IPv6/UDP):
          Requested: bind(face:b00c:1234:5678::abcd, 6060) ..
             Actual: bind(::1, 6666)
          Requested: connect(face:b00c:1234:5678::abcd, 6060) from (*, *)
             Actual: connect(::1, 6666) from (::6, 39315)
  ### SUCCESS
  # egrep 'connect\(.*AF_INET' connect.trace | \
  > egrep -vw 'htons\(1025\)' | fold -b -s -w 72
  502   connect(7, {sa_family=AF_INET, sin_port=htons(4040),
  sin_addr=inet_addr("192.168.1.254")}, 128) = 0
  502   connect(8, {sa_family=AF_INET, sin_port=htons(4040),
  sin_addr=inet_addr("192.168.1.254")}, 128) = 0
  502   connect(9, {sa_family=AF_INET6, sin6_port=htons(6060),
  inet_pton(AF_INET6, "face:b00c:1234:5678::abcd", &sin6_addr),
  sin6_flowinfo=0, sin6_scope_id=0}, 128) = 0
  502   connect(10, {sa_family=AF_INET6, sin6_port=htons(6060),
  inet_pton(AF_INET6, "face:b00c:1234:5678::abcd", &sin6_addr),
  sin6_flowinfo=0, sin6_scope_id=0}, 128) = 0
  # fg
  tcpdump -pn -i lo -w connect.pcap 2> /dev/null
  # tcpdump -r connect.pcap -n tcp | cut -c 1-72
  reading from file connect.pcap, link-type EN10MB (Ethernet)
  17:57:40.383533 IP 127.0.0.4.56068 > 127.0.0.1.4444: Flags [S], seq 1333
  17:57:40.383566 IP 127.0.0.1.4444 > 127.0.0.4.56068: Flags [S.], seq 112
  17:57:40.383589 IP 127.0.0.4.56068 > 127.0.0.1.4444: Flags [.], ack 1, w
  17:57:40.384578 IP 127.0.0.1.4444 > 127.0.0.4.56068: Flags [R.], seq 1,
  17:57:40.403327 IP6 ::6.37458 > ::1.6666: Flags [S], seq 406513443, win
  17:57:40.403357 IP6 ::1.6666 > ::6.37458: Flags [S.], seq 2448389240, ac
  17:57:40.403376 IP6 ::6.37458 > ::1.6666: Flags [.], ack 1, win 342, opt
  17:57:40.404263 IP6 ::1.6666 > ::6.37458: Flags [R.], seq 1, ack 1, win

Signed-off-by: Andrey Ignatov <rdna@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
  • Loading branch information
rdna authored and borkmann committed Mar 31, 2018
1 parent d74bad4 commit 622adaf
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 4 deletions.
12 changes: 11 additions & 1 deletion tools/include/uapi/linux/bpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ enum bpf_attach_type {
BPF_SK_MSG_VERDICT,
BPF_CGROUP_INET4_BIND,
BPF_CGROUP_INET6_BIND,
BPF_CGROUP_INET4_CONNECT,
BPF_CGROUP_INET6_CONNECT,
__MAX_BPF_ATTACH_TYPE
};

Expand Down Expand Up @@ -744,6 +746,13 @@ union bpf_attr {
* @flags: reserved for future use
* Return: SK_PASS
*
* int bpf_bind(ctx, addr, addr_len)
* Bind socket to address. Only binding to IP is supported, no port can be
* set in addr.
* @ctx: pointer to context of type bpf_sock_addr
* @addr: pointer to struct sockaddr to bind socket to
* @addr_len: length of sockaddr structure
* Return: 0 on success or negative error code
*/
#define __BPF_FUNC_MAPPER(FN) \
FN(unspec), \
Expand Down Expand Up @@ -809,7 +818,8 @@ union bpf_attr {
FN(msg_redirect_map), \
FN(msg_apply_bytes), \
FN(msg_cork_bytes), \
FN(msg_pull_data),
FN(msg_pull_data), \
FN(bind),

/* integer value in 'imm' field of BPF_CALL instruction selects which helper
* function eBPF program intends to call
Expand Down
2 changes: 2 additions & 0 deletions tools/lib/bpf/libbpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -1887,6 +1887,8 @@ static const struct {
BPF_PROG_SEC("sk_msg", BPF_PROG_TYPE_SK_MSG),
BPF_SA_PROG_SEC("cgroup/bind4", BPF_CGROUP_INET4_BIND),
BPF_SA_PROG_SEC("cgroup/bind6", BPF_CGROUP_INET6_BIND),
BPF_SA_PROG_SEC("cgroup/connect4", BPF_CGROUP_INET4_CONNECT),
BPF_SA_PROG_SEC("cgroup/connect6", BPF_CGROUP_INET6_CONNECT),
};

#undef BPF_PROG_SEC
Expand Down
5 changes: 3 additions & 2 deletions tools/testing/selftests/bpf/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test
sockmap_verdict_prog.o dev_cgroup.o sample_ret0.o test_tracepoint.o \
test_l4lb_noinline.o test_xdp_noinline.o test_stacktrace_map.o \
sample_map_ret0.o test_tcpbpf_kern.o test_stacktrace_build_id.o \
sockmap_tcp_msg_prog.o
sockmap_tcp_msg_prog.o connect4_prog.o connect6_prog.o

# Order correspond to 'make run_tests' order
TEST_PROGS := test_kmod.sh \
test_libbpf.sh \
test_xdp_redirect.sh \
test_xdp_meta.sh \
test_offload.py
test_offload.py \
test_sock_addr.sh

# Compile but not part of 'make run_tests'
TEST_GEN_PROGS_EXTENDED = test_libbpf_open
Expand Down
2 changes: 2 additions & 0 deletions tools/testing/selftests/bpf/bpf_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ static int (*bpf_msg_cork_bytes)(void *ctx, int len) =
(void *) BPF_FUNC_msg_cork_bytes;
static int (*bpf_msg_pull_data)(void *ctx, int start, int end, int flags) =
(void *) BPF_FUNC_msg_pull_data;
static int (*bpf_bind)(void *ctx, void *addr, int addr_len) =
(void *) BPF_FUNC_bind;

/* llvm builtin functions that eBPF C program may use to
* emit BPF_LD_ABS and BPF_LD_IND instructions
Expand Down
45 changes: 45 additions & 0 deletions tools/testing/selftests/bpf/connect4_prog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Facebook

#include <string.h>

#include <linux/stddef.h>
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <sys/socket.h>

#include "bpf_helpers.h"
#include "bpf_endian.h"

#define SRC_REWRITE_IP4 0x7f000004U
#define DST_REWRITE_IP4 0x7f000001U
#define DST_REWRITE_PORT4 4444

int _version SEC("version") = 1;

SEC("cgroup/connect4")
int connect_v4_prog(struct bpf_sock_addr *ctx)
{
struct sockaddr_in sa;

/* Rewrite destination. */
ctx->user_ip4 = bpf_htonl(DST_REWRITE_IP4);
ctx->user_port = bpf_htons(DST_REWRITE_PORT4);

if (ctx->type == SOCK_DGRAM || ctx->type == SOCK_STREAM) {
///* Rewrite source. */
memset(&sa, 0, sizeof(sa));

sa.sin_family = AF_INET;
sa.sin_port = bpf_htons(0);
sa.sin_addr.s_addr = bpf_htonl(SRC_REWRITE_IP4);

if (bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)) != 0)
return 0;
}

return 1;
}

char _license[] SEC("license") = "GPL";
61 changes: 61 additions & 0 deletions tools/testing/selftests/bpf/connect6_prog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Facebook

#include <string.h>

#include <linux/stddef.h>
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <sys/socket.h>

#include "bpf_helpers.h"
#include "bpf_endian.h"

#define SRC_REWRITE_IP6_0 0
#define SRC_REWRITE_IP6_1 0
#define SRC_REWRITE_IP6_2 0
#define SRC_REWRITE_IP6_3 6

#define DST_REWRITE_IP6_0 0
#define DST_REWRITE_IP6_1 0
#define DST_REWRITE_IP6_2 0
#define DST_REWRITE_IP6_3 1

#define DST_REWRITE_PORT6 6666

int _version SEC("version") = 1;

SEC("cgroup/connect6")
int connect_v6_prog(struct bpf_sock_addr *ctx)
{
struct sockaddr_in6 sa;

/* Rewrite destination. */
ctx->user_ip6[0] = bpf_htonl(DST_REWRITE_IP6_0);
ctx->user_ip6[1] = bpf_htonl(DST_REWRITE_IP6_1);
ctx->user_ip6[2] = bpf_htonl(DST_REWRITE_IP6_2);
ctx->user_ip6[3] = bpf_htonl(DST_REWRITE_IP6_3);

ctx->user_port = bpf_htons(DST_REWRITE_PORT6);

if (ctx->type == SOCK_DGRAM || ctx->type == SOCK_STREAM) {
/* Rewrite source. */
memset(&sa, 0, sizeof(sa));

sa.sin6_family = AF_INET6;
sa.sin6_port = bpf_htons(0);

sa.sin6_addr.s6_addr32[0] = bpf_htonl(SRC_REWRITE_IP6_0);
sa.sin6_addr.s6_addr32[1] = bpf_htonl(SRC_REWRITE_IP6_1);
sa.sin6_addr.s6_addr32[2] = bpf_htonl(SRC_REWRITE_IP6_2);
sa.sin6_addr.s6_addr32[3] = bpf_htonl(SRC_REWRITE_IP6_3);

if (bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)) != 0)
return 0;
}

return 1;
}

char _license[] SEC("license") = "GPL";
104 changes: 103 additions & 1 deletion tools/testing/selftests/bpf/test_sock_addr.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
#include <linux/filter.h>

#include <bpf/bpf.h>
#include <bpf/libbpf.h>

#include "cgroup_helpers.h"

#define CG_PATH "/foo"
#define CONNECT4_PROG_PATH "./connect4_prog.o"
#define CONNECT6_PROG_PATH "./connect6_prog.o"

#define SERV4_IP "192.168.1.254"
#define SERV4_REWRITE_IP "127.0.0.1"
Expand Down Expand Up @@ -254,6 +257,41 @@ static int bind6_prog_load(enum bpf_attach_type attach_type,
sizeof(insns) / sizeof(struct bpf_insn), comment);
}

static int connect_prog_load_path(const char *path,
enum bpf_attach_type attach_type,
const char *comment)
{
struct bpf_prog_load_attr attr;
struct bpf_object *obj;
int prog_fd;

memset(&attr, 0, sizeof(struct bpf_prog_load_attr));
attr.file = path;
attr.prog_type = BPF_PROG_TYPE_CGROUP_SOCK_ADDR;
attr.expected_attach_type = attach_type;

if (bpf_prog_load_xattr(&attr, &obj, &prog_fd)) {
if (comment)
log_err(">>> Loading %s program at %s error.\n",
comment, path);
return -1;
}

return prog_fd;
}

static int connect4_prog_load(enum bpf_attach_type attach_type,
const char *comment)
{
return connect_prog_load_path(CONNECT4_PROG_PATH, attach_type, comment);
}

static int connect6_prog_load(enum bpf_attach_type attach_type,
const char *comment)
{
return connect_prog_load_path(CONNECT6_PROG_PATH, attach_type, comment);
}

static void print_ip_port(int sockfd, info_fn fn, const char *fmt)
{
char addr_buf[INET_NTOP_BUF];
Expand Down Expand Up @@ -290,6 +328,11 @@ static void print_local_ip_port(int sockfd, const char *fmt)
print_ip_port(sockfd, getsockname, fmt);
}

static void print_remote_ip_port(int sockfd, const char *fmt)
{
print_ip_port(sockfd, getpeername, fmt);
}

static int start_server(int type, const struct sockaddr_storage *addr,
socklen_t addr_len)
{
Expand Down Expand Up @@ -324,6 +367,39 @@ static int start_server(int type, const struct sockaddr_storage *addr,
return fd;
}

static int connect_to_server(int type, const struct sockaddr_storage *addr,
socklen_t addr_len)
{
int domain;
int fd;

domain = addr->ss_family;

if (domain != AF_INET && domain != AF_INET6) {
log_err("Unsupported address family");
return -1;
}

fd = socket(domain, type, 0);
if (fd == -1) {
log_err("Failed to creating client socket");
return -1;
}

if (connect(fd, (const struct sockaddr *)addr, addr_len) == -1) {
log_err("Fail to connect to server");
goto err;
}

print_remote_ip_port(fd, "\t Actual: connect(%s, %d)");
print_local_ip_port(fd, " from (%s, %d)\n");

return 0;
err:
close(fd);
return -1;
}

static void print_test_case_num(int domain, int type)
{
static int test_num;
Expand Down Expand Up @@ -356,6 +432,10 @@ static int run_test_case(int domain, int type, const char *ip,
if (servfd == -1)
goto err;

printf("\tRequested: connect(%s, %d) from (*, *) ..\n", ip, port);
if (connect_to_server(type, &addr, addr_len))
goto err;

goto out;
err:
err = -1;
Expand All @@ -380,29 +460,41 @@ static int load_and_attach_progs(int cgfd, struct program *progs,
size_t i;

for (i = 0; i < prog_cnt; ++i) {
printf("Load %s with invalid type (can pollute stderr) ",
progs[i].name);
fflush(stdout);
progs[i].fd = progs[i].loadfn(progs[i].invalid_type, NULL);
if (progs[i].fd != -1) {
log_err("Load with invalid type accepted for %s",
progs[i].name);
goto err;
}
printf("... REJECTED\n");

printf("Load %s with valid type", progs[i].name);
progs[i].fd = progs[i].loadfn(progs[i].type, progs[i].name);
if (progs[i].fd == -1) {
log_err("Failed to load program %s", progs[i].name);
goto err;
}
printf(" ... OK\n");

printf("Attach %s with invalid type", progs[i].name);
if (bpf_prog_attach(progs[i].fd, cgfd, progs[i].invalid_type,
BPF_F_ALLOW_OVERRIDE) != -1) {
log_err("Attach with invalid type accepted for %s",
progs[i].name);
goto err;
}
printf(" ... REJECTED\n");

printf("Attach %s with valid type", progs[i].name);
if (bpf_prog_attach(progs[i].fd, cgfd, progs[i].type,
BPF_F_ALLOW_OVERRIDE) == -1) {
log_err("Failed to attach program %s", progs[i].name);
goto err;
}
printf("Attached %s program.\n", progs[i].name);
printf(" ... OK\n");
}

return 0;
Expand Down Expand Up @@ -443,12 +535,16 @@ static int run_test(void)
struct program inet6_progs[] = {
{BPF_CGROUP_INET6_BIND, bind6_prog_load, -1, "bind6",
BPF_CGROUP_INET4_BIND},
{BPF_CGROUP_INET6_CONNECT, connect6_prog_load, -1, "connect6",
BPF_CGROUP_INET4_CONNECT},
};
inet6_prog_cnt = sizeof(inet6_progs) / sizeof(struct program);

struct program inet_progs[] = {
{BPF_CGROUP_INET4_BIND, bind4_prog_load, -1, "bind4",
BPF_CGROUP_INET6_BIND},
{BPF_CGROUP_INET4_CONNECT, connect4_prog_load, -1, "connect4",
BPF_CGROUP_INET6_CONNECT},
};
inet_prog_cnt = sizeof(inet_progs) / sizeof(struct program);

Expand Down Expand Up @@ -482,5 +578,11 @@ static int run_test(void)

int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr,
"%s has to be run via %s.sh. Skip direct run.\n",
argv[0], argv[0]);
exit(0);
}
return run_test();
}
Loading

0 comments on commit 622adaf

Please sign in to comment.