Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: Selftest for sys_connect hooks

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>

authored by

Andrey Ignatov and committed by
Daniel Borkmann
622adafb d74bad4e

+284 -4
+11 -1
tools/include/uapi/linux/bpf.h
··· 150 150 BPF_SK_MSG_VERDICT, 151 151 BPF_CGROUP_INET4_BIND, 152 152 BPF_CGROUP_INET6_BIND, 153 + BPF_CGROUP_INET4_CONNECT, 154 + BPF_CGROUP_INET6_CONNECT, 153 155 __MAX_BPF_ATTACH_TYPE 154 156 }; 155 157 ··· 746 744 * @flags: reserved for future use 747 745 * Return: SK_PASS 748 746 * 747 + * int bpf_bind(ctx, addr, addr_len) 748 + * Bind socket to address. Only binding to IP is supported, no port can be 749 + * set in addr. 750 + * @ctx: pointer to context of type bpf_sock_addr 751 + * @addr: pointer to struct sockaddr to bind socket to 752 + * @addr_len: length of sockaddr structure 753 + * Return: 0 on success or negative error code 749 754 */ 750 755 #define __BPF_FUNC_MAPPER(FN) \ 751 756 FN(unspec), \ ··· 818 809 FN(msg_redirect_map), \ 819 810 FN(msg_apply_bytes), \ 820 811 FN(msg_cork_bytes), \ 821 - FN(msg_pull_data), 812 + FN(msg_pull_data), \ 813 + FN(bind), 822 814 823 815 /* integer value in 'imm' field of BPF_CALL instruction selects which helper 824 816 * function eBPF program intends to call
+2
tools/lib/bpf/libbpf.c
··· 1887 1887 BPF_PROG_SEC("sk_msg", BPF_PROG_TYPE_SK_MSG), 1888 1888 BPF_SA_PROG_SEC("cgroup/bind4", BPF_CGROUP_INET4_BIND), 1889 1889 BPF_SA_PROG_SEC("cgroup/bind6", BPF_CGROUP_INET6_BIND), 1890 + BPF_SA_PROG_SEC("cgroup/connect4", BPF_CGROUP_INET4_CONNECT), 1891 + BPF_SA_PROG_SEC("cgroup/connect6", BPF_CGROUP_INET6_CONNECT), 1890 1892 }; 1891 1893 1892 1894 #undef BPF_PROG_SEC
+3 -2
tools/testing/selftests/bpf/Makefile
··· 30 30 sockmap_verdict_prog.o dev_cgroup.o sample_ret0.o test_tracepoint.o \ 31 31 test_l4lb_noinline.o test_xdp_noinline.o test_stacktrace_map.o \ 32 32 sample_map_ret0.o test_tcpbpf_kern.o test_stacktrace_build_id.o \ 33 - sockmap_tcp_msg_prog.o 33 + sockmap_tcp_msg_prog.o connect4_prog.o connect6_prog.o 34 34 35 35 # Order correspond to 'make run_tests' order 36 36 TEST_PROGS := test_kmod.sh \ 37 37 test_libbpf.sh \ 38 38 test_xdp_redirect.sh \ 39 39 test_xdp_meta.sh \ 40 - test_offload.py 40 + test_offload.py \ 41 + test_sock_addr.sh 41 42 42 43 # Compile but not part of 'make run_tests' 43 44 TEST_GEN_PROGS_EXTENDED = test_libbpf_open
+2
tools/testing/selftests/bpf/bpf_helpers.h
··· 94 94 (void *) BPF_FUNC_msg_cork_bytes; 95 95 static int (*bpf_msg_pull_data)(void *ctx, int start, int end, int flags) = 96 96 (void *) BPF_FUNC_msg_pull_data; 97 + static int (*bpf_bind)(void *ctx, void *addr, int addr_len) = 98 + (void *) BPF_FUNC_bind; 97 99 98 100 /* llvm builtin functions that eBPF C program may use to 99 101 * emit BPF_LD_ABS and BPF_LD_IND instructions
+45
tools/testing/selftests/bpf/connect4_prog.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // Copyright (c) 2018 Facebook 3 + 4 + #include <string.h> 5 + 6 + #include <linux/stddef.h> 7 + #include <linux/bpf.h> 8 + #include <linux/in.h> 9 + #include <linux/in6.h> 10 + #include <sys/socket.h> 11 + 12 + #include "bpf_helpers.h" 13 + #include "bpf_endian.h" 14 + 15 + #define SRC_REWRITE_IP4 0x7f000004U 16 + #define DST_REWRITE_IP4 0x7f000001U 17 + #define DST_REWRITE_PORT4 4444 18 + 19 + int _version SEC("version") = 1; 20 + 21 + SEC("cgroup/connect4") 22 + int connect_v4_prog(struct bpf_sock_addr *ctx) 23 + { 24 + struct sockaddr_in sa; 25 + 26 + /* Rewrite destination. */ 27 + ctx->user_ip4 = bpf_htonl(DST_REWRITE_IP4); 28 + ctx->user_port = bpf_htons(DST_REWRITE_PORT4); 29 + 30 + if (ctx->type == SOCK_DGRAM || ctx->type == SOCK_STREAM) { 31 + ///* Rewrite source. */ 32 + memset(&sa, 0, sizeof(sa)); 33 + 34 + sa.sin_family = AF_INET; 35 + sa.sin_port = bpf_htons(0); 36 + sa.sin_addr.s_addr = bpf_htonl(SRC_REWRITE_IP4); 37 + 38 + if (bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)) != 0) 39 + return 0; 40 + } 41 + 42 + return 1; 43 + } 44 + 45 + char _license[] SEC("license") = "GPL";
+61
tools/testing/selftests/bpf/connect6_prog.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // Copyright (c) 2018 Facebook 3 + 4 + #include <string.h> 5 + 6 + #include <linux/stddef.h> 7 + #include <linux/bpf.h> 8 + #include <linux/in.h> 9 + #include <linux/in6.h> 10 + #include <sys/socket.h> 11 + 12 + #include "bpf_helpers.h" 13 + #include "bpf_endian.h" 14 + 15 + #define SRC_REWRITE_IP6_0 0 16 + #define SRC_REWRITE_IP6_1 0 17 + #define SRC_REWRITE_IP6_2 0 18 + #define SRC_REWRITE_IP6_3 6 19 + 20 + #define DST_REWRITE_IP6_0 0 21 + #define DST_REWRITE_IP6_1 0 22 + #define DST_REWRITE_IP6_2 0 23 + #define DST_REWRITE_IP6_3 1 24 + 25 + #define DST_REWRITE_PORT6 6666 26 + 27 + int _version SEC("version") = 1; 28 + 29 + SEC("cgroup/connect6") 30 + int connect_v6_prog(struct bpf_sock_addr *ctx) 31 + { 32 + struct sockaddr_in6 sa; 33 + 34 + /* Rewrite destination. */ 35 + ctx->user_ip6[0] = bpf_htonl(DST_REWRITE_IP6_0); 36 + ctx->user_ip6[1] = bpf_htonl(DST_REWRITE_IP6_1); 37 + ctx->user_ip6[2] = bpf_htonl(DST_REWRITE_IP6_2); 38 + ctx->user_ip6[3] = bpf_htonl(DST_REWRITE_IP6_3); 39 + 40 + ctx->user_port = bpf_htons(DST_REWRITE_PORT6); 41 + 42 + if (ctx->type == SOCK_DGRAM || ctx->type == SOCK_STREAM) { 43 + /* Rewrite source. */ 44 + memset(&sa, 0, sizeof(sa)); 45 + 46 + sa.sin6_family = AF_INET6; 47 + sa.sin6_port = bpf_htons(0); 48 + 49 + sa.sin6_addr.s6_addr32[0] = bpf_htonl(SRC_REWRITE_IP6_0); 50 + sa.sin6_addr.s6_addr32[1] = bpf_htonl(SRC_REWRITE_IP6_1); 51 + sa.sin6_addr.s6_addr32[2] = bpf_htonl(SRC_REWRITE_IP6_2); 52 + sa.sin6_addr.s6_addr32[3] = bpf_htonl(SRC_REWRITE_IP6_3); 53 + 54 + if (bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)) != 0) 55 + return 0; 56 + } 57 + 58 + return 1; 59 + } 60 + 61 + char _license[] SEC("license") = "GPL";
+103 -1
tools/testing/selftests/bpf/test_sock_addr.c
··· 12 12 #include <linux/filter.h> 13 13 14 14 #include <bpf/bpf.h> 15 + #include <bpf/libbpf.h> 15 16 16 17 #include "cgroup_helpers.h" 17 18 18 19 #define CG_PATH "/foo" 20 + #define CONNECT4_PROG_PATH "./connect4_prog.o" 21 + #define CONNECT6_PROG_PATH "./connect6_prog.o" 19 22 20 23 #define SERV4_IP "192.168.1.254" 21 24 #define SERV4_REWRITE_IP "127.0.0.1" ··· 257 254 sizeof(insns) / sizeof(struct bpf_insn), comment); 258 255 } 259 256 257 + static int connect_prog_load_path(const char *path, 258 + enum bpf_attach_type attach_type, 259 + const char *comment) 260 + { 261 + struct bpf_prog_load_attr attr; 262 + struct bpf_object *obj; 263 + int prog_fd; 264 + 265 + memset(&attr, 0, sizeof(struct bpf_prog_load_attr)); 266 + attr.file = path; 267 + attr.prog_type = BPF_PROG_TYPE_CGROUP_SOCK_ADDR; 268 + attr.expected_attach_type = attach_type; 269 + 270 + if (bpf_prog_load_xattr(&attr, &obj, &prog_fd)) { 271 + if (comment) 272 + log_err(">>> Loading %s program at %s error.\n", 273 + comment, path); 274 + return -1; 275 + } 276 + 277 + return prog_fd; 278 + } 279 + 280 + static int connect4_prog_load(enum bpf_attach_type attach_type, 281 + const char *comment) 282 + { 283 + return connect_prog_load_path(CONNECT4_PROG_PATH, attach_type, comment); 284 + } 285 + 286 + static int connect6_prog_load(enum bpf_attach_type attach_type, 287 + const char *comment) 288 + { 289 + return connect_prog_load_path(CONNECT6_PROG_PATH, attach_type, comment); 290 + } 291 + 260 292 static void print_ip_port(int sockfd, info_fn fn, const char *fmt) 261 293 { 262 294 char addr_buf[INET_NTOP_BUF]; ··· 328 290 print_ip_port(sockfd, getsockname, fmt); 329 291 } 330 292 293 + static void print_remote_ip_port(int sockfd, const char *fmt) 294 + { 295 + print_ip_port(sockfd, getpeername, fmt); 296 + } 297 + 331 298 static int start_server(int type, const struct sockaddr_storage *addr, 332 299 socklen_t addr_len) 333 300 { ··· 367 324 return fd; 368 325 } 369 326 327 + static int connect_to_server(int type, const struct sockaddr_storage *addr, 328 + socklen_t addr_len) 329 + { 330 + int domain; 331 + int fd; 332 + 333 + domain = addr->ss_family; 334 + 335 + if (domain != AF_INET && domain != AF_INET6) { 336 + log_err("Unsupported address family"); 337 + return -1; 338 + } 339 + 340 + fd = socket(domain, type, 0); 341 + if (fd == -1) { 342 + log_err("Failed to creating client socket"); 343 + return -1; 344 + } 345 + 346 + if (connect(fd, (const struct sockaddr *)addr, addr_len) == -1) { 347 + log_err("Fail to connect to server"); 348 + goto err; 349 + } 350 + 351 + print_remote_ip_port(fd, "\t Actual: connect(%s, %d)"); 352 + print_local_ip_port(fd, " from (%s, %d)\n"); 353 + 354 + return 0; 355 + err: 356 + close(fd); 357 + return -1; 358 + } 359 + 370 360 static void print_test_case_num(int domain, int type) 371 361 { 372 362 static int test_num; ··· 432 356 if (servfd == -1) 433 357 goto err; 434 358 359 + printf("\tRequested: connect(%s, %d) from (*, *) ..\n", ip, port); 360 + if (connect_to_server(type, &addr, addr_len)) 361 + goto err; 362 + 435 363 goto out; 436 364 err: 437 365 err = -1; ··· 460 380 size_t i; 461 381 462 382 for (i = 0; i < prog_cnt; ++i) { 383 + printf("Load %s with invalid type (can pollute stderr) ", 384 + progs[i].name); 385 + fflush(stdout); 463 386 progs[i].fd = progs[i].loadfn(progs[i].invalid_type, NULL); 464 387 if (progs[i].fd != -1) { 465 388 log_err("Load with invalid type accepted for %s", 466 389 progs[i].name); 467 390 goto err; 468 391 } 392 + printf("... REJECTED\n"); 393 + 394 + printf("Load %s with valid type", progs[i].name); 469 395 progs[i].fd = progs[i].loadfn(progs[i].type, progs[i].name); 470 396 if (progs[i].fd == -1) { 471 397 log_err("Failed to load program %s", progs[i].name); 472 398 goto err; 473 399 } 400 + printf(" ... OK\n"); 401 + 402 + printf("Attach %s with invalid type", progs[i].name); 474 403 if (bpf_prog_attach(progs[i].fd, cgfd, progs[i].invalid_type, 475 404 BPF_F_ALLOW_OVERRIDE) != -1) { 476 405 log_err("Attach with invalid type accepted for %s", 477 406 progs[i].name); 478 407 goto err; 479 408 } 409 + printf(" ... REJECTED\n"); 410 + 411 + printf("Attach %s with valid type", progs[i].name); 480 412 if (bpf_prog_attach(progs[i].fd, cgfd, progs[i].type, 481 413 BPF_F_ALLOW_OVERRIDE) == -1) { 482 414 log_err("Failed to attach program %s", progs[i].name); 483 415 goto err; 484 416 } 485 - printf("Attached %s program.\n", progs[i].name); 417 + printf(" ... OK\n"); 486 418 } 487 419 488 420 return 0; ··· 535 443 struct program inet6_progs[] = { 536 444 {BPF_CGROUP_INET6_BIND, bind6_prog_load, -1, "bind6", 537 445 BPF_CGROUP_INET4_BIND}, 446 + {BPF_CGROUP_INET6_CONNECT, connect6_prog_load, -1, "connect6", 447 + BPF_CGROUP_INET4_CONNECT}, 538 448 }; 539 449 inet6_prog_cnt = sizeof(inet6_progs) / sizeof(struct program); 540 450 541 451 struct program inet_progs[] = { 542 452 {BPF_CGROUP_INET4_BIND, bind4_prog_load, -1, "bind4", 543 453 BPF_CGROUP_INET6_BIND}, 454 + {BPF_CGROUP_INET4_CONNECT, connect4_prog_load, -1, "connect4", 455 + BPF_CGROUP_INET6_CONNECT}, 544 456 }; 545 457 inet_prog_cnt = sizeof(inet_progs) / sizeof(struct program); 546 458 ··· 578 482 579 483 int main(int argc, char **argv) 580 484 { 485 + if (argc < 2) { 486 + fprintf(stderr, 487 + "%s has to be run via %s.sh. Skip direct run.\n", 488 + argv[0], argv[0]); 489 + exit(0); 490 + } 581 491 return run_test(); 582 492 }
+57
tools/testing/selftests/bpf/test_sock_addr.sh
··· 1 + #!/bin/sh 2 + 3 + set -eu 4 + 5 + ping_once() 6 + { 7 + ping -q -c 1 -W 1 ${1%%/*} >/dev/null 2>&1 8 + } 9 + 10 + wait_for_ip() 11 + { 12 + local _i 13 + echo -n "Wait for testing IPv4/IPv6 to become available " 14 + for _i in $(seq ${MAX_PING_TRIES}); do 15 + echo -n "." 16 + if ping_once ${TEST_IPv4} && ping_once ${TEST_IPv6}; then 17 + echo " OK" 18 + return 19 + fi 20 + done 21 + echo 1>&2 "ERROR: Timeout waiting for test IP to become available." 22 + exit 1 23 + } 24 + 25 + setup() 26 + { 27 + # Create testing interfaces not to interfere with current environment. 28 + ip link add dev ${TEST_IF} type veth peer name ${TEST_IF_PEER} 29 + ip link set ${TEST_IF} up 30 + ip link set ${TEST_IF_PEER} up 31 + 32 + ip -4 addr add ${TEST_IPv4} dev ${TEST_IF} 33 + ip -6 addr add ${TEST_IPv6} dev ${TEST_IF} 34 + wait_for_ip 35 + } 36 + 37 + cleanup() 38 + { 39 + ip link del ${TEST_IF} 2>/dev/null || : 40 + ip link del ${TEST_IF_PEER} 2>/dev/null || : 41 + } 42 + 43 + main() 44 + { 45 + trap cleanup EXIT 2 3 6 15 46 + setup 47 + ./test_sock_addr setup_done 48 + } 49 + 50 + BASENAME=$(basename $0 .sh) 51 + TEST_IF="${BASENAME}1" 52 + TEST_IF_PEER="${BASENAME}2" 53 + TEST_IPv4="127.0.0.4/8" 54 + TEST_IPv6="::6/128" 55 + MAX_PING_TRIES=5 56 + 57 + main