-
Notifications
You must be signed in to change notification settings - Fork 0
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 sockopt test that exercises sk helpers
socktop test that introduces new SOL_CUSTOM sockopt level and stores whatever users sets in sk storage. Whenever getsockopt is called, the original value is retrieved. v9: * SO_SNDBUF example to override user-supplied buffer v7: * use retval=0 and optlen-1 v6: * test 'ret=1' use-case as well (Alexei Starovoitov) v4: * don't call bpf_sk_fullsock helper v3: * drop (__u8 *)(long) casts for optval{,_end} v2: * new test Cc: Andrii Nakryiko <andriin@fb.com> Cc: Martin Lau <kafai@fb.com> Signed-off-by: Stanislav Fomichev <sdf@google.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
- Loading branch information
Stanislav Fomichev
authored and
Alexei Starovoitov
committed
Jun 27, 2019
1 parent
9ec8a4c
commit 8a027dc
Showing
4 changed files
with
328 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,3 +40,4 @@ test_hashmap | |
test_btf_dump | ||
xdping | ||
test_sockopt | ||
test_sockopt_sk |
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,111 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
#include <netinet/in.h> | ||
#include <linux/bpf.h> | ||
#include "bpf_helpers.h" | ||
|
||
char _license[] SEC("license") = "GPL"; | ||
__u32 _version SEC("version") = 1; | ||
|
||
#define SOL_CUSTOM 0xdeadbeef | ||
|
||
struct sockopt_sk { | ||
__u8 val; | ||
}; | ||
|
||
struct bpf_map_def SEC("maps") socket_storage_map = { | ||
.type = BPF_MAP_TYPE_SK_STORAGE, | ||
.key_size = sizeof(int), | ||
.value_size = sizeof(struct sockopt_sk), | ||
.map_flags = BPF_F_NO_PREALLOC, | ||
}; | ||
BPF_ANNOTATE_KV_PAIR(socket_storage_map, int, struct sockopt_sk); | ||
|
||
SEC("cgroup/getsockopt") | ||
int _getsockopt(struct bpf_sockopt *ctx) | ||
{ | ||
__u8 *optval_end = ctx->optval_end; | ||
__u8 *optval = ctx->optval; | ||
struct sockopt_sk *storage; | ||
|
||
if (ctx->level == SOL_IP && ctx->optname == IP_TOS) | ||
/* Not interested in SOL_IP:IP_TOS; | ||
* let next BPF program in the cgroup chain or kernel | ||
* handle it. | ||
*/ | ||
return 1; | ||
|
||
if (ctx->level == SOL_SOCKET && ctx->optname == SO_SNDBUF) { | ||
/* Not interested in SOL_SOCKET:SO_SNDBUF; | ||
* let next BPF program in the cgroup chain or kernel | ||
* handle it. | ||
*/ | ||
return 1; | ||
} | ||
|
||
if (ctx->level != SOL_CUSTOM) | ||
return 0; /* EPERM, deny everything except custom level */ | ||
|
||
if (optval + 1 > optval_end) | ||
return 0; /* EPERM, bounds check */ | ||
|
||
storage = bpf_sk_storage_get(&socket_storage_map, ctx->sk, 0, | ||
BPF_SK_STORAGE_GET_F_CREATE); | ||
if (!storage) | ||
return 0; /* EPERM, couldn't get sk storage */ | ||
|
||
if (!ctx->retval) | ||
return 0; /* EPERM, kernel should not have handled | ||
* SOL_CUSTOM, something is wrong! | ||
*/ | ||
ctx->retval = 0; /* Reset system call return value to zero */ | ||
|
||
optval[0] = storage->val; | ||
ctx->optlen = 1; | ||
|
||
return 1; | ||
} | ||
|
||
SEC("cgroup/setsockopt") | ||
int _setsockopt(struct bpf_sockopt *ctx) | ||
{ | ||
__u8 *optval_end = ctx->optval_end; | ||
__u8 *optval = ctx->optval; | ||
struct sockopt_sk *storage; | ||
|
||
if (ctx->level == SOL_IP && ctx->optname == IP_TOS) | ||
/* Not interested in SOL_IP:IP_TOS; | ||
* let next BPF program in the cgroup chain or kernel | ||
* handle it. | ||
*/ | ||
return 1; | ||
|
||
if (ctx->level == SOL_SOCKET && ctx->optname == SO_SNDBUF) { | ||
/* Overwrite SO_SNDBUF value */ | ||
|
||
if (optval + sizeof(__u32) > optval_end) | ||
return 0; /* EPERM, bounds check */ | ||
|
||
*(__u32 *)optval = 0x55AA; | ||
ctx->optlen = 4; | ||
|
||
return 1; | ||
} | ||
|
||
if (ctx->level != SOL_CUSTOM) | ||
return 0; /* EPERM, deny everything except custom level */ | ||
|
||
if (optval + 1 > optval_end) | ||
return 0; /* EPERM, bounds check */ | ||
|
||
storage = bpf_sk_storage_get(&socket_storage_map, ctx->sk, 0, | ||
BPF_SK_STORAGE_GET_F_CREATE); | ||
if (!storage) | ||
return 0; /* EPERM, couldn't get sk storage */ | ||
|
||
storage->val = optval[0]; | ||
ctx->optlen = -1; /* BPF has consumed this option, don't call kernel | ||
* setsockopt handler. | ||
*/ | ||
|
||
return 1; | ||
} |
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,214 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
|
||
#include <errno.h> | ||
#include <stdio.h> | ||
#include <unistd.h> | ||
#include <sys/types.h> | ||
#include <sys/socket.h> | ||
#include <netinet/in.h> | ||
|
||
#include <linux/filter.h> | ||
#include <bpf/bpf.h> | ||
#include <bpf/libbpf.h> | ||
|
||
#include "bpf_rlimit.h" | ||
#include "bpf_util.h" | ||
#include "cgroup_helpers.h" | ||
|
||
#define CG_PATH "/sockopt" | ||
|
||
#define SOL_CUSTOM 0xdeadbeef | ||
|
||
static int getsetsockopt(void) | ||
{ | ||
int fd, err; | ||
char buf[4] = {}; | ||
socklen_t optlen; | ||
|
||
fd = socket(AF_INET, SOCK_STREAM, 0); | ||
if (fd < 0) { | ||
log_err("Failed to create socket"); | ||
return -1; | ||
} | ||
|
||
/* IP_TOS - BPF bypass */ | ||
|
||
buf[0] = 0x08; | ||
err = setsockopt(fd, SOL_IP, IP_TOS, buf, 1); | ||
if (err) { | ||
log_err("Failed to call setsockopt(IP_TOS)"); | ||
goto err; | ||
} | ||
|
||
buf[0] = 0x00; | ||
optlen = 1; | ||
err = getsockopt(fd, SOL_IP, IP_TOS, buf, &optlen); | ||
if (err) { | ||
log_err("Failed to call getsockopt(IP_TOS)"); | ||
goto err; | ||
} | ||
|
||
if (buf[0] != 0x08) { | ||
log_err("Unexpected getsockopt(IP_TOS) buf[0] 0x%02x != 0x08", | ||
buf[0]); | ||
goto err; | ||
} | ||
|
||
/* IP_TTL - EPERM */ | ||
|
||
buf[0] = 1; | ||
err = setsockopt(fd, SOL_IP, IP_TTL, buf, 1); | ||
if (!err || errno != EPERM) { | ||
log_err("Unexpected success from setsockopt(IP_TTL)"); | ||
goto err; | ||
} | ||
|
||
/* SOL_CUSTOM - handled by BPF */ | ||
|
||
buf[0] = 0x01; | ||
err = setsockopt(fd, SOL_CUSTOM, 0, buf, 1); | ||
if (err) { | ||
log_err("Failed to call setsockopt"); | ||
goto err; | ||
} | ||
|
||
buf[0] = 0x00; | ||
optlen = 4; | ||
err = getsockopt(fd, SOL_CUSTOM, 0, buf, &optlen); | ||
if (err) { | ||
log_err("Failed to call getsockopt"); | ||
goto err; | ||
} | ||
|
||
if (optlen != 1) { | ||
log_err("Unexpected optlen %d != 1", optlen); | ||
goto err; | ||
} | ||
if (buf[0] != 0x01) { | ||
log_err("Unexpected buf[0] 0x%02x != 0x01", buf[0]); | ||
goto err; | ||
} | ||
|
||
/* SO_SNDBUF is overwritten */ | ||
|
||
buf[0] = 0x01; | ||
buf[1] = 0x01; | ||
buf[2] = 0x01; | ||
buf[3] = 0x01; | ||
err = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, buf, 4); | ||
if (err) { | ||
log_err("Failed to call setsockopt(SO_SNDBUF)"); | ||
goto err; | ||
} | ||
|
||
buf[0] = 0x00; | ||
buf[1] = 0x00; | ||
buf[2] = 0x00; | ||
buf[3] = 0x00; | ||
optlen = 4; | ||
err = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, buf, &optlen); | ||
if (err) { | ||
log_err("Failed to call getsockopt(SO_SNDBUF)"); | ||
goto err; | ||
} | ||
|
||
if (*(__u32 *)buf != 0x55AA*2) { | ||
log_err("Unexpected getsockopt(SO_SNDBUF) 0x%x != 0x55AA*2", | ||
*(__u32 *)buf); | ||
goto err; | ||
} | ||
|
||
close(fd); | ||
return 0; | ||
err: | ||
close(fd); | ||
return -1; | ||
} | ||
|
||
static int prog_attach(struct bpf_object *obj, int cgroup_fd, const char *title) | ||
{ | ||
enum bpf_attach_type attach_type; | ||
enum bpf_prog_type prog_type; | ||
struct bpf_program *prog; | ||
int err; | ||
|
||
err = libbpf_prog_type_by_name(title, &prog_type, &attach_type); | ||
if (err) { | ||
log_err("Failed to deduct types for %s BPF program", title); | ||
return -1; | ||
} | ||
|
||
prog = bpf_object__find_program_by_title(obj, title); | ||
if (!prog) { | ||
log_err("Failed to find %s BPF program", title); | ||
return -1; | ||
} | ||
|
||
err = bpf_prog_attach(bpf_program__fd(prog), cgroup_fd, | ||
attach_type, 0); | ||
if (err) { | ||
log_err("Failed to attach %s BPF program", title); | ||
return -1; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int run_test(int cgroup_fd) | ||
{ | ||
struct bpf_prog_load_attr attr = { | ||
.file = "./sockopt_sk.o", | ||
}; | ||
struct bpf_object *obj; | ||
int ignored; | ||
int err; | ||
|
||
err = bpf_prog_load_xattr(&attr, &obj, &ignored); | ||
if (err) { | ||
log_err("Failed to load BPF object"); | ||
return -1; | ||
} | ||
|
||
err = prog_attach(obj, cgroup_fd, "cgroup/getsockopt"); | ||
if (err) | ||
goto close_bpf_object; | ||
|
||
err = prog_attach(obj, cgroup_fd, "cgroup/setsockopt"); | ||
if (err) | ||
goto close_bpf_object; | ||
|
||
err = getsetsockopt(); | ||
|
||
close_bpf_object: | ||
bpf_object__close(obj); | ||
return err; | ||
} | ||
|
||
int main(int args, char **argv) | ||
{ | ||
int cgroup_fd; | ||
int err = EXIT_SUCCESS; | ||
|
||
if (setup_cgroup_environment()) | ||
goto cleanup_obj; | ||
|
||
cgroup_fd = create_and_get_cgroup(CG_PATH); | ||
if (cgroup_fd < 0) | ||
goto cleanup_cgroup_env; | ||
|
||
if (join_cgroup(CG_PATH)) | ||
goto cleanup_cgroup; | ||
|
||
if (run_test(cgroup_fd)) | ||
err = EXIT_FAILURE; | ||
|
||
printf("test_sockopt_sk: %s\n", | ||
err == EXIT_SUCCESS ? "PASSED" : "FAILED"); | ||
|
||
cleanup_cgroup: | ||
close(cgroup_fd); | ||
cleanup_cgroup_env: | ||
cleanup_cgroup_environment(); | ||
cleanup_obj: | ||
return err; | ||
} |