-
Notifications
You must be signed in to change notification settings - Fork 55.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
selftests/bpf: add a test case for sock_ops perf-event notification
This patch provides a tcp_bpf based eBPF sample. The test - ncat(1) as the TCP client program to connect() to a port with the intention of triggerring SYN retransmissions: we first install an iptables DROP rule to make sure ncat SYNs are resent (instead of aborting instantly after a TCP RST) - has a bpf kernel module that sends a perf-event notification for each TCP retransmit, and also tracks the number of such notifications sent in the global_map The test passes when the number of event notifications intercepted in user-space matches the value in the global_map. Signed-off-by: Sowmini Varadhan <sowmini.varadhan@oracle.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
- Loading branch information
Showing
4 changed files
with
303 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
|
||
#ifndef _TEST_TCPBPF_H | ||
#define _TEST_TCPBPF_H | ||
|
||
struct tcpnotify_globals { | ||
__u32 total_retrans; | ||
__u32 ncalls; | ||
}; | ||
|
||
struct tcp_notifier { | ||
__u8 type; | ||
__u8 subtype; | ||
__u8 source; | ||
__u8 hash; | ||
}; | ||
|
||
#define TESTPORT 12877 | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
#include <stddef.h> | ||
#include <string.h> | ||
#include <linux/bpf.h> | ||
#include <linux/if_ether.h> | ||
#include <linux/if_packet.h> | ||
#include <linux/ip.h> | ||
#include <linux/ipv6.h> | ||
#include <linux/types.h> | ||
#include <linux/socket.h> | ||
#include <linux/tcp.h> | ||
#include <netinet/in.h> | ||
#include "bpf_helpers.h" | ||
#include "bpf_endian.h" | ||
#include "test_tcpnotify.h" | ||
|
||
struct bpf_map_def SEC("maps") global_map = { | ||
.type = BPF_MAP_TYPE_ARRAY, | ||
.key_size = sizeof(__u32), | ||
.value_size = sizeof(struct tcpnotify_globals), | ||
.max_entries = 4, | ||
}; | ||
|
||
struct bpf_map_def SEC("maps") perf_event_map = { | ||
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, | ||
.key_size = sizeof(int), | ||
.value_size = sizeof(__u32), | ||
.max_entries = 2, | ||
}; | ||
|
||
int _version SEC("version") = 1; | ||
|
||
SEC("sockops") | ||
int bpf_testcb(struct bpf_sock_ops *skops) | ||
{ | ||
int rv = -1; | ||
int op; | ||
|
||
op = (int) skops->op; | ||
|
||
if (bpf_ntohl(skops->remote_port) != TESTPORT) { | ||
skops->reply = -1; | ||
return 0; | ||
} | ||
|
||
switch (op) { | ||
case BPF_SOCK_OPS_TIMEOUT_INIT: | ||
case BPF_SOCK_OPS_RWND_INIT: | ||
case BPF_SOCK_OPS_NEEDS_ECN: | ||
case BPF_SOCK_OPS_BASE_RTT: | ||
case BPF_SOCK_OPS_RTO_CB: | ||
rv = 1; | ||
break; | ||
|
||
case BPF_SOCK_OPS_TCP_CONNECT_CB: | ||
case BPF_SOCK_OPS_TCP_LISTEN_CB: | ||
case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: | ||
case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: | ||
bpf_sock_ops_cb_flags_set(skops, (BPF_SOCK_OPS_RETRANS_CB_FLAG| | ||
BPF_SOCK_OPS_RTO_CB_FLAG)); | ||
rv = 1; | ||
break; | ||
case BPF_SOCK_OPS_RETRANS_CB: { | ||
__u32 key = 0; | ||
struct tcpnotify_globals g, *gp; | ||
struct tcp_notifier msg = { | ||
.type = 0xde, | ||
.subtype = 0xad, | ||
.source = 0xbe, | ||
.hash = 0xef, | ||
}; | ||
|
||
rv = 1; | ||
|
||
/* Update results */ | ||
gp = bpf_map_lookup_elem(&global_map, &key); | ||
if (!gp) | ||
break; | ||
g = *gp; | ||
g.total_retrans = skops->total_retrans; | ||
g.ncalls++; | ||
bpf_map_update_elem(&global_map, &key, &g, | ||
BPF_ANY); | ||
bpf_perf_event_output(skops, &perf_event_map, | ||
BPF_F_CURRENT_CPU, | ||
&msg, sizeof(msg)); | ||
} | ||
break; | ||
default: | ||
rv = -1; | ||
} | ||
skops->reply = rv; | ||
return 1; | ||
} | ||
char _license[] SEC("license") = "GPL"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
#define _GNU_SOURCE | ||
#include <pthread.h> | ||
#include <inttypes.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <unistd.h> | ||
#include <asm/types.h> | ||
#include <sys/syscall.h> | ||
#include <errno.h> | ||
#include <string.h> | ||
#include <linux/bpf.h> | ||
#include <sys/socket.h> | ||
#include <bpf/bpf.h> | ||
#include <bpf/libbpf.h> | ||
#include <sys/ioctl.h> | ||
#include <linux/rtnetlink.h> | ||
#include <signal.h> | ||
#include <linux/perf_event.h> | ||
|
||
#include "bpf_rlimit.h" | ||
#include "bpf_util.h" | ||
#include "cgroup_helpers.h" | ||
|
||
#include "test_tcpnotify.h" | ||
#include "trace_helpers.h" | ||
|
||
#define SOCKET_BUFFER_SIZE (getpagesize() < 8192L ? getpagesize() : 8192L) | ||
|
||
pthread_t tid; | ||
int rx_callbacks; | ||
|
||
static int dummyfn(void *data, int size) | ||
{ | ||
struct tcp_notifier *t = data; | ||
|
||
if (t->type != 0xde || t->subtype != 0xad || | ||
t->source != 0xbe || t->hash != 0xef) | ||
return 1; | ||
rx_callbacks++; | ||
return 0; | ||
} | ||
|
||
void tcp_notifier_poller(int fd) | ||
{ | ||
while (1) | ||
perf_event_poller(fd, dummyfn); | ||
} | ||
|
||
static void *poller_thread(void *arg) | ||
{ | ||
int fd = *(int *)arg; | ||
|
||
tcp_notifier_poller(fd); | ||
return arg; | ||
} | ||
|
||
int verify_result(const struct tcpnotify_globals *result) | ||
{ | ||
return (result->ncalls > 0 && result->ncalls == rx_callbacks ? 0 : 1); | ||
} | ||
|
||
static int bpf_find_map(const char *test, struct bpf_object *obj, | ||
const char *name) | ||
{ | ||
struct bpf_map *map; | ||
|
||
map = bpf_object__find_map_by_name(obj, name); | ||
if (!map) { | ||
printf("%s:FAIL:map '%s' not found\n", test, name); | ||
return -1; | ||
} | ||
return bpf_map__fd(map); | ||
} | ||
|
||
static int setup_bpf_perf_event(int mapfd) | ||
{ | ||
struct perf_event_attr attr = { | ||
.sample_type = PERF_SAMPLE_RAW, | ||
.type = PERF_TYPE_SOFTWARE, | ||
.config = PERF_COUNT_SW_BPF_OUTPUT, | ||
}; | ||
int key = 0; | ||
int pmu_fd; | ||
|
||
pmu_fd = syscall(__NR_perf_event_open, &attr, -1, 0, -1, 0); | ||
if (pmu_fd < 0) | ||
return pmu_fd; | ||
bpf_map_update_elem(mapfd, &key, &pmu_fd, BPF_ANY); | ||
|
||
ioctl(pmu_fd, PERF_EVENT_IOC_ENABLE, 0); | ||
return pmu_fd; | ||
} | ||
|
||
int main(int argc, char **argv) | ||
{ | ||
const char *file = "test_tcpnotify_kern.o"; | ||
int prog_fd, map_fd, perf_event_fd; | ||
struct tcpnotify_globals g = {0}; | ||
const char *cg_path = "/foo"; | ||
int error = EXIT_FAILURE; | ||
struct bpf_object *obj; | ||
int cg_fd = -1; | ||
__u32 key = 0; | ||
int rv; | ||
char test_script[80]; | ||
int pmu_fd; | ||
cpu_set_t cpuset; | ||
|
||
CPU_ZERO(&cpuset); | ||
CPU_SET(0, &cpuset); | ||
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); | ||
|
||
if (setup_cgroup_environment()) | ||
goto err; | ||
|
||
cg_fd = create_and_get_cgroup(cg_path); | ||
if (!cg_fd) | ||
goto err; | ||
|
||
if (join_cgroup(cg_path)) | ||
goto err; | ||
|
||
if (bpf_prog_load(file, BPF_PROG_TYPE_SOCK_OPS, &obj, &prog_fd)) { | ||
printf("FAILED: load_bpf_file failed for: %s\n", file); | ||
goto err; | ||
} | ||
|
||
rv = bpf_prog_attach(prog_fd, cg_fd, BPF_CGROUP_SOCK_OPS, 0); | ||
if (rv) { | ||
printf("FAILED: bpf_prog_attach: %d (%s)\n", | ||
error, strerror(errno)); | ||
goto err; | ||
} | ||
|
||
perf_event_fd = bpf_find_map(__func__, obj, "perf_event_map"); | ||
if (perf_event_fd < 0) | ||
goto err; | ||
|
||
map_fd = bpf_find_map(__func__, obj, "global_map"); | ||
if (map_fd < 0) | ||
goto err; | ||
|
||
pmu_fd = setup_bpf_perf_event(perf_event_fd); | ||
if (pmu_fd < 0 || perf_event_mmap(pmu_fd) < 0) | ||
goto err; | ||
|
||
pthread_create(&tid, NULL, poller_thread, (void *)&pmu_fd); | ||
|
||
sprintf(test_script, | ||
"/usr/sbin/iptables -A INPUT -p tcp --dport %d -j DROP", | ||
TESTPORT); | ||
system(test_script); | ||
|
||
sprintf(test_script, | ||
"/usr/bin/nc 127.0.0.1 %d < /etc/passwd > /dev/null 2>&1 ", | ||
TESTPORT); | ||
system(test_script); | ||
|
||
sprintf(test_script, | ||
"/usr/sbin/iptables -D INPUT -p tcp --dport %d -j DROP", | ||
TESTPORT); | ||
system(test_script); | ||
|
||
rv = bpf_map_lookup_elem(map_fd, &key, &g); | ||
if (rv != 0) { | ||
printf("FAILED: bpf_map_lookup_elem returns %d\n", rv); | ||
goto err; | ||
} | ||
|
||
sleep(10); | ||
|
||
if (verify_result(&g)) { | ||
printf("FAILED: Wrong stats Expected %d calls, got %d\n", | ||
g.ncalls, rx_callbacks); | ||
goto err; | ||
} | ||
|
||
printf("PASSED!\n"); | ||
error = 0; | ||
err: | ||
bpf_prog_detach(cg_fd, BPF_CGROUP_SOCK_OPS); | ||
close(cg_fd); | ||
cleanup_cgroup_environment(); | ||
return error; | ||
} |