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

bpf: add selftest for tcpbpf

Added a selftest for tcpbpf (sock_ops) that checks that the appropriate
callbacks occured and that it can access tcp_sock fields and that their
values are correct.

Run with command: ./test_tcpbpf_user
Adding the flag "-d" will show why it did not pass.

Signed-off-by: Lawrence Brakmo <brakmo@fb.com>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Lawrence Brakmo and committed by
Alexei Starovoitov
d6d4f60c d4487491

+480 -6
+82 -4
tools/include/uapi/linux/bpf.h
··· 17 17 #define BPF_ALU64 0x07 /* alu mode in double word width */ 18 18 19 19 /* ld/ldx fields */ 20 - #define BPF_DW 0x18 /* double word */ 20 + #define BPF_DW 0x18 /* double word (64-bit) */ 21 21 #define BPF_XADD 0xc0 /* exclusive add */ 22 22 23 23 /* alu/jmp fields */ ··· 642 642 * @optlen: length of optval in bytes 643 643 * Return: 0 or negative error 644 644 * 645 + * int bpf_sock_ops_cb_flags_set(bpf_sock_ops, flags) 646 + * Set callback flags for sock_ops 647 + * @bpf_sock_ops: pointer to bpf_sock_ops_kern struct 648 + * @flags: flags value 649 + * Return: 0 for no error 650 + * -EINVAL if there is no full tcp socket 651 + * bits in flags that are not supported by current kernel 652 + * 645 653 * int bpf_skb_adjust_room(skb, len_diff, mode, flags) 646 654 * Grow or shrink room in sk_buff. 647 655 * @skb: pointer to skb ··· 756 748 FN(perf_event_read_value), \ 757 749 FN(perf_prog_read_value), \ 758 750 FN(getsockopt), \ 759 - FN(override_return), 751 + FN(override_return), \ 752 + FN(sock_ops_cb_flags_set), 760 753 761 754 /* integer value in 'imm' field of BPF_CALL instruction selects which helper 762 755 * function eBPF program intends to call ··· 961 952 struct bpf_sock_ops { 962 953 __u32 op; 963 954 union { 964 - __u32 reply; 965 - __u32 replylong[4]; 955 + __u32 args[4]; /* Optionally passed to bpf program */ 956 + __u32 reply; /* Returned by bpf program */ 957 + __u32 replylong[4]; /* Optionally returned by bpf prog */ 966 958 }; 967 959 __u32 family; 968 960 __u32 remote_ip4; /* Stored in network byte order */ ··· 978 968 */ 979 969 __u32 snd_cwnd; 980 970 __u32 srtt_us; /* Averaged RTT << 3 in usecs */ 971 + __u32 bpf_sock_ops_cb_flags; /* flags defined in uapi/linux/tcp.h */ 972 + __u32 state; 973 + __u32 rtt_min; 974 + __u32 snd_ssthresh; 975 + __u32 rcv_nxt; 976 + __u32 snd_nxt; 977 + __u32 snd_una; 978 + __u32 mss_cache; 979 + __u32 ecn_flags; 980 + __u32 rate_delivered; 981 + __u32 rate_interval_us; 982 + __u32 packets_out; 983 + __u32 retrans_out; 984 + __u32 total_retrans; 985 + __u32 segs_in; 986 + __u32 data_segs_in; 987 + __u32 segs_out; 988 + __u32 data_segs_out; 989 + __u32 lost_out; 990 + __u32 sacked_out; 991 + __u32 sk_txhash; 992 + __u64 bytes_received; 993 + __u64 bytes_acked; 981 994 }; 995 + 996 + /* Definitions for bpf_sock_ops_cb_flags */ 997 + #define BPF_SOCK_OPS_RTO_CB_FLAG (1<<0) 998 + #define BPF_SOCK_OPS_RETRANS_CB_FLAG (1<<1) 999 + #define BPF_SOCK_OPS_STATE_CB_FLAG (1<<2) 1000 + #define BPF_SOCK_OPS_ALL_CB_FLAGS 0x7 /* Mask of all currently 1001 + * supported cb flags 1002 + */ 982 1003 983 1004 /* List of known BPF sock_ops operators. 984 1005 * New entries can only be added at the end ··· 1044 1003 * a congestion threshold. RTTs above 1045 1004 * this indicate congestion 1046 1005 */ 1006 + BPF_SOCK_OPS_RTO_CB, /* Called when an RTO has triggered. 1007 + * Arg1: value of icsk_retransmits 1008 + * Arg2: value of icsk_rto 1009 + * Arg3: whether RTO has expired 1010 + */ 1011 + BPF_SOCK_OPS_RETRANS_CB, /* Called when skb is retransmitted. 1012 + * Arg1: sequence number of 1st byte 1013 + * Arg2: # segments 1014 + * Arg3: return value of 1015 + * tcp_transmit_skb (0 => success) 1016 + */ 1017 + BPF_SOCK_OPS_STATE_CB, /* Called when TCP changes state. 1018 + * Arg1: old_state 1019 + * Arg2: new_state 1020 + */ 1021 + }; 1022 + 1023 + /* List of TCP states. There is a build check in net/ipv4/tcp.c to detect 1024 + * changes between the TCP and BPF versions. Ideally this should never happen. 1025 + * If it does, we need to add code to convert them before calling 1026 + * the BPF sock_ops function. 1027 + */ 1028 + enum { 1029 + BPF_TCP_ESTABLISHED = 1, 1030 + BPF_TCP_SYN_SENT, 1031 + BPF_TCP_SYN_RECV, 1032 + BPF_TCP_FIN_WAIT1, 1033 + BPF_TCP_FIN_WAIT2, 1034 + BPF_TCP_TIME_WAIT, 1035 + BPF_TCP_CLOSE, 1036 + BPF_TCP_CLOSE_WAIT, 1037 + BPF_TCP_LAST_ACK, 1038 + BPF_TCP_LISTEN, 1039 + BPF_TCP_CLOSING, /* Now a valid state */ 1040 + BPF_TCP_NEW_SYN_RECV, 1041 + 1042 + BPF_TCP_MAX_STATES /* Leave at the end! */ 1047 1043 }; 1048 1044 1049 1045 #define TCP_BPF_IW 1001 /* Set TCP initial congestion window */
+2 -2
tools/testing/selftests/bpf/Makefile
··· 14 14 LDLIBS += -lcap -lelf -lrt 15 15 16 16 TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test_progs \ 17 - test_align test_verifier_log test_dev_cgroup 17 + test_align test_verifier_log test_dev_cgroup test_tcpbpf_user 18 18 19 19 TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \ 20 20 test_pkt_md_access.o test_xdp_redirect.o test_xdp_meta.o sockmap_parse_prog.o \ 21 21 sockmap_verdict_prog.o dev_cgroup.o sample_ret0.o test_tracepoint.o \ 22 22 test_l4lb_noinline.o test_xdp_noinline.o test_stacktrace_map.o \ 23 - sample_map_ret0.o 23 + sample_map_ret0.o test_tcpbpf_kern.o 24 24 25 25 TEST_PROGS := test_kmod.sh test_xdp_redirect.sh test_xdp_meta.sh \ 26 26 test_offload.py
+2
tools/testing/selftests/bpf/bpf_helpers.h
··· 71 71 static int (*bpf_getsockopt)(void *ctx, int level, int optname, void *optval, 72 72 int optlen) = 73 73 (void *) BPF_FUNC_getsockopt; 74 + static int (*bpf_sock_ops_cb_flags_set)(void *ctx, int flags) = 75 + (void *) BPF_FUNC_sock_ops_cb_flags_set; 74 76 static int (*bpf_sk_redirect_map)(void *ctx, void *map, int key, int flags) = 75 77 (void *) BPF_FUNC_sk_redirect_map; 76 78 static int (*bpf_sock_map_update)(void *map, void *key, void *value,
+51
tools/testing/selftests/bpf/tcp_client.py
··· 1 + #!/usr/bin/env python2 2 + # 3 + # SPDX-License-Identifier: GPL-2.0 4 + # 5 + 6 + import sys, os, os.path, getopt 7 + import socket, time 8 + import subprocess 9 + import select 10 + 11 + def read(sock, n): 12 + buf = '' 13 + while len(buf) < n: 14 + rem = n - len(buf) 15 + try: s = sock.recv(rem) 16 + except (socket.error), e: return '' 17 + buf += s 18 + return buf 19 + 20 + def send(sock, s): 21 + total = len(s) 22 + count = 0 23 + while count < total: 24 + try: n = sock.send(s) 25 + except (socket.error), e: n = 0 26 + if n == 0: 27 + return count; 28 + count += n 29 + return count 30 + 31 + 32 + serverPort = int(sys.argv[1]) 33 + HostName = socket.gethostname() 34 + 35 + # create active socket 36 + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 37 + try: 38 + sock.connect((HostName, serverPort)) 39 + except socket.error as e: 40 + sys.exit(1) 41 + 42 + buf = '' 43 + n = 0 44 + while n < 1000: 45 + buf += '+' 46 + n += 1 47 + 48 + sock.settimeout(1); 49 + n = send(sock, buf) 50 + n = read(sock, 500) 51 + sys.exit(0)
+83
tools/testing/selftests/bpf/tcp_server.py
··· 1 + #!/usr/bin/env python2 2 + # 3 + # SPDX-License-Identifier: GPL-2.0 4 + # 5 + 6 + import sys, os, os.path, getopt 7 + import socket, time 8 + import subprocess 9 + import select 10 + 11 + def read(sock, n): 12 + buf = '' 13 + while len(buf) < n: 14 + rem = n - len(buf) 15 + try: s = sock.recv(rem) 16 + except (socket.error), e: return '' 17 + buf += s 18 + return buf 19 + 20 + def send(sock, s): 21 + total = len(s) 22 + count = 0 23 + while count < total: 24 + try: n = sock.send(s) 25 + except (socket.error), e: n = 0 26 + if n == 0: 27 + return count; 28 + count += n 29 + return count 30 + 31 + 32 + SERVER_PORT = 12877 33 + MAX_PORTS = 2 34 + 35 + serverPort = SERVER_PORT 36 + serverSocket = None 37 + 38 + HostName = socket.gethostname() 39 + 40 + # create passive socket 41 + serverSocket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 42 + host = socket.gethostname() 43 + 44 + try: serverSocket.bind((host, 0)) 45 + except socket.error as msg: 46 + print 'bind fails: ', msg 47 + 48 + sn = serverSocket.getsockname() 49 + serverPort = sn[1] 50 + 51 + cmdStr = ("./tcp_client.py %d &") % (serverPort) 52 + os.system(cmdStr) 53 + 54 + buf = '' 55 + n = 0 56 + while n < 500: 57 + buf += '.' 58 + n += 1 59 + 60 + serverSocket.listen(MAX_PORTS) 61 + readList = [serverSocket] 62 + 63 + while True: 64 + readyRead, readyWrite, inError = \ 65 + select.select(readList, [], [], 2) 66 + 67 + if len(readyRead) > 0: 68 + waitCount = 0 69 + for sock in readyRead: 70 + if sock == serverSocket: 71 + (clientSocket, address) = serverSocket.accept() 72 + address = str(address[0]) 73 + readList.append(clientSocket) 74 + else: 75 + sock.settimeout(1); 76 + s = read(sock, 1000) 77 + n = send(sock, buf) 78 + sock.close() 79 + serverSocket.close() 80 + sys.exit(0) 81 + else: 82 + print 'Select timeout!' 83 + sys.exit(1)
+16
tools/testing/selftests/bpf/test_tcpbpf.h
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + #ifndef _TEST_TCPBPF_H 4 + #define _TEST_TCPBPF_H 5 + 6 + struct tcpbpf_globals { 7 + __u32 event_map; 8 + __u32 total_retrans; 9 + __u32 data_segs_in; 10 + __u32 data_segs_out; 11 + __u32 bad_cb_test_rv; 12 + __u32 good_cb_test_rv; 13 + __u64 bytes_received; 14 + __u64 bytes_acked; 15 + }; 16 + #endif
+118
tools/testing/selftests/bpf/test_tcpbpf_kern.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + #include <stddef.h> 3 + #include <string.h> 4 + #include <linux/bpf.h> 5 + #include <linux/if_ether.h> 6 + #include <linux/if_packet.h> 7 + #include <linux/ip.h> 8 + #include <linux/in6.h> 9 + #include <linux/types.h> 10 + #include <linux/socket.h> 11 + #include <linux/tcp.h> 12 + #include <netinet/in.h> 13 + #include "bpf_helpers.h" 14 + #include "bpf_endian.h" 15 + #include "test_tcpbpf.h" 16 + 17 + struct bpf_map_def SEC("maps") global_map = { 18 + .type = BPF_MAP_TYPE_ARRAY, 19 + .key_size = sizeof(__u32), 20 + .value_size = sizeof(struct tcpbpf_globals), 21 + .max_entries = 2, 22 + }; 23 + 24 + static inline void update_event_map(int event) 25 + { 26 + __u32 key = 0; 27 + struct tcpbpf_globals g, *gp; 28 + 29 + gp = bpf_map_lookup_elem(&global_map, &key); 30 + if (gp == NULL) { 31 + struct tcpbpf_globals g = {0}; 32 + 33 + g.event_map |= (1 << event); 34 + bpf_map_update_elem(&global_map, &key, &g, 35 + BPF_ANY); 36 + } else { 37 + g = *gp; 38 + g.event_map |= (1 << event); 39 + bpf_map_update_elem(&global_map, &key, &g, 40 + BPF_ANY); 41 + } 42 + } 43 + 44 + int _version SEC("version") = 1; 45 + 46 + SEC("sockops") 47 + int bpf_testcb(struct bpf_sock_ops *skops) 48 + { 49 + int rv = -1; 50 + int bad_call_rv = 0; 51 + int good_call_rv = 0; 52 + int op; 53 + int v = 0; 54 + 55 + op = (int) skops->op; 56 + 57 + update_event_map(op); 58 + 59 + switch (op) { 60 + case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: 61 + /* Test failure to set largest cb flag (assumes not defined) */ 62 + bad_call_rv = bpf_sock_ops_cb_flags_set(skops, 0x80); 63 + /* Set callback */ 64 + good_call_rv = bpf_sock_ops_cb_flags_set(skops, 65 + BPF_SOCK_OPS_STATE_CB_FLAG); 66 + /* Update results */ 67 + { 68 + __u32 key = 0; 69 + struct tcpbpf_globals g, *gp; 70 + 71 + gp = bpf_map_lookup_elem(&global_map, &key); 72 + if (!gp) 73 + break; 74 + g = *gp; 75 + g.bad_cb_test_rv = bad_call_rv; 76 + g.good_cb_test_rv = good_call_rv; 77 + bpf_map_update_elem(&global_map, &key, &g, 78 + BPF_ANY); 79 + } 80 + break; 81 + case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: 82 + /* Set callback */ 83 + // good_call_rv = bpf_sock_ops_cb_flags_set(skops, 84 + // BPF_SOCK_OPS_STATE_CB_FLAG); 85 + skops->sk_txhash = 0x12345f; 86 + v = 0xff; 87 + rv = bpf_setsockopt(skops, SOL_IPV6, IPV6_TCLASS, &v, 88 + sizeof(v)); 89 + break; 90 + case BPF_SOCK_OPS_RTO_CB: 91 + break; 92 + case BPF_SOCK_OPS_RETRANS_CB: 93 + break; 94 + case BPF_SOCK_OPS_STATE_CB: 95 + if (skops->args[1] == BPF_TCP_CLOSE) { 96 + __u32 key = 0; 97 + struct tcpbpf_globals g, *gp; 98 + 99 + gp = bpf_map_lookup_elem(&global_map, &key); 100 + if (!gp) 101 + break; 102 + g = *gp; 103 + g.total_retrans = skops->total_retrans; 104 + g.data_segs_in = skops->data_segs_in; 105 + g.data_segs_out = skops->data_segs_out; 106 + g.bytes_received = skops->bytes_received; 107 + g.bytes_acked = skops->bytes_acked; 108 + bpf_map_update_elem(&global_map, &key, &g, 109 + BPF_ANY); 110 + } 111 + break; 112 + default: 113 + rv = -1; 114 + } 115 + skops->reply = rv; 116 + return 1; 117 + } 118 + char _license[] SEC("license") = "GPL";
+126
tools/testing/selftests/bpf/test_tcpbpf_user.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + #include <stdio.h> 3 + #include <stdlib.h> 4 + #include <stdio.h> 5 + #include <unistd.h> 6 + #include <errno.h> 7 + #include <signal.h> 8 + #include <string.h> 9 + #include <assert.h> 10 + #include <linux/perf_event.h> 11 + #include <linux/ptrace.h> 12 + #include <linux/bpf.h> 13 + #include <sys/ioctl.h> 14 + #include <sys/types.h> 15 + #include <sys/stat.h> 16 + #include <fcntl.h> 17 + #include <bpf/bpf.h> 18 + #include <bpf/libbpf.h> 19 + #include "bpf_util.h" 20 + #include <linux/perf_event.h> 21 + #include "test_tcpbpf.h" 22 + 23 + static int bpf_find_map(const char *test, struct bpf_object *obj, 24 + const char *name) 25 + { 26 + struct bpf_map *map; 27 + 28 + map = bpf_object__find_map_by_name(obj, name); 29 + if (!map) { 30 + printf("%s:FAIL:map '%s' not found\n", test, name); 31 + return -1; 32 + } 33 + return bpf_map__fd(map); 34 + } 35 + 36 + #define SYSTEM(CMD) \ 37 + do { \ 38 + if (system(CMD)) { \ 39 + printf("system(%s) FAILS!\n", CMD); \ 40 + } \ 41 + } while (0) 42 + 43 + int main(int argc, char **argv) 44 + { 45 + const char *file = "test_tcpbpf_kern.o"; 46 + struct tcpbpf_globals g = {0}; 47 + int cg_fd, prog_fd, map_fd; 48 + bool debug_flag = false; 49 + int error = EXIT_FAILURE; 50 + struct bpf_object *obj; 51 + char cmd[100], *dir; 52 + struct stat buffer; 53 + __u32 key = 0; 54 + int pid; 55 + int rv; 56 + 57 + if (argc > 1 && strcmp(argv[1], "-d") == 0) 58 + debug_flag = true; 59 + 60 + dir = "/tmp/cgroupv2/foo"; 61 + 62 + if (stat(dir, &buffer) != 0) { 63 + SYSTEM("mkdir -p /tmp/cgroupv2"); 64 + SYSTEM("mount -t cgroup2 none /tmp/cgroupv2"); 65 + SYSTEM("mkdir -p /tmp/cgroupv2/foo"); 66 + } 67 + pid = (int) getpid(); 68 + sprintf(cmd, "echo %d >> /tmp/cgroupv2/foo/cgroup.procs", pid); 69 + SYSTEM(cmd); 70 + 71 + cg_fd = open(dir, O_DIRECTORY, O_RDONLY); 72 + if (bpf_prog_load(file, BPF_PROG_TYPE_SOCK_OPS, &obj, &prog_fd)) { 73 + printf("FAILED: load_bpf_file failed for: %s\n", file); 74 + goto err; 75 + } 76 + 77 + rv = bpf_prog_attach(prog_fd, cg_fd, BPF_CGROUP_SOCK_OPS, 0); 78 + if (rv) { 79 + printf("FAILED: bpf_prog_attach: %d (%s)\n", 80 + error, strerror(errno)); 81 + goto err; 82 + } 83 + 84 + SYSTEM("./tcp_server.py"); 85 + 86 + map_fd = bpf_find_map(__func__, obj, "global_map"); 87 + if (map_fd < 0) 88 + goto err; 89 + 90 + rv = bpf_map_lookup_elem(map_fd, &key, &g); 91 + if (rv != 0) { 92 + printf("FAILED: bpf_map_lookup_elem returns %d\n", rv); 93 + goto err; 94 + } 95 + 96 + if (g.bytes_received != 501 || g.bytes_acked != 1002 || 97 + g.data_segs_in != 1 || g.data_segs_out != 1 || 98 + (g.event_map ^ 0x47e) != 0 || g.bad_cb_test_rv != 0x80 || 99 + g.good_cb_test_rv != 0) { 100 + printf("FAILED: Wrong stats\n"); 101 + if (debug_flag) { 102 + printf("\n"); 103 + printf("bytes_received: %d (expecting 501)\n", 104 + (int)g.bytes_received); 105 + printf("bytes_acked: %d (expecting 1002)\n", 106 + (int)g.bytes_acked); 107 + printf("data_segs_in: %d (expecting 1)\n", 108 + g.data_segs_in); 109 + printf("data_segs_out: %d (expecting 1)\n", 110 + g.data_segs_out); 111 + printf("event_map: 0x%x (at least 0x47e)\n", 112 + g.event_map); 113 + printf("bad_cb_test_rv: 0x%x (expecting 0x80)\n", 114 + g.bad_cb_test_rv); 115 + printf("good_cb_test_rv:0x%x (expecting 0)\n", 116 + g.good_cb_test_rv); 117 + } 118 + goto err; 119 + } 120 + printf("PASSED!\n"); 121 + error = 0; 122 + err: 123 + bpf_prog_detach(cg_fd, BPF_CGROUP_SOCK_OPS); 124 + return error; 125 + 126 + }