|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | #include <test_progs.h> | 
|  | #include "cgroup_helpers.h" | 
|  |  | 
|  | #include <linux/tcp.h> | 
|  | #include "sockopt_sk.skel.h" | 
|  |  | 
|  | #ifndef SOL_TCP | 
|  | #define SOL_TCP IPPROTO_TCP | 
|  | #endif | 
|  |  | 
|  | #define SOL_CUSTOM			0xdeadbeef | 
|  |  | 
|  | static int getsetsockopt(void) | 
|  | { | 
|  | int fd, err; | 
|  | union { | 
|  | char u8[4]; | 
|  | __u32 u32; | 
|  | char cc[16]; /* TCP_CA_NAME_MAX */ | 
|  | struct tcp_zerocopy_receive zc; | 
|  | } buf = {}; | 
|  | socklen_t optlen; | 
|  | char *big_buf = NULL; | 
|  |  | 
|  | fd = socket(AF_INET, SOCK_STREAM, 0); | 
|  | if (fd < 0) { | 
|  | log_err("Failed to create socket"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* IP_TOS - BPF bypass */ | 
|  |  | 
|  | optlen = getpagesize() * 2; | 
|  | big_buf = calloc(1, optlen); | 
|  | if (!big_buf) { | 
|  | log_err("Couldn't allocate two pages"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | *(int *)big_buf = 0x08; | 
|  | err = setsockopt(fd, SOL_IP, IP_TOS, big_buf, optlen); | 
|  | if (err) { | 
|  | log_err("Failed to call setsockopt(IP_TOS)"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | memset(big_buf, 0, optlen); | 
|  | optlen = 1; | 
|  | err = getsockopt(fd, SOL_IP, IP_TOS, big_buf, &optlen); | 
|  | if (err) { | 
|  | log_err("Failed to call getsockopt(IP_TOS)"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (*big_buf != 0x08) { | 
|  | log_err("Unexpected getsockopt(IP_TOS) optval 0x%x != 0x08", | 
|  | (int)*big_buf); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | /* IP_TTL - EPERM */ | 
|  |  | 
|  | buf.u8[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.u8[0] = 0x01; | 
|  | err = setsockopt(fd, SOL_CUSTOM, 0, &buf, 1); | 
|  | if (err) { | 
|  | log_err("Failed to call setsockopt"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | buf.u32 = 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.u8[0] != 0x01) { | 
|  | log_err("Unexpected buf[0] 0x%02x != 0x01", buf.u8[0]); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | /* IP_FREEBIND - BPF can't access optval past PAGE_SIZE */ | 
|  |  | 
|  | optlen = getpagesize() * 2; | 
|  | memset(big_buf, 0, optlen); | 
|  |  | 
|  | err = setsockopt(fd, SOL_IP, IP_FREEBIND, big_buf, optlen); | 
|  | if (err != 0) { | 
|  | log_err("Failed to call setsockopt, ret=%d", err); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | err = getsockopt(fd, SOL_IP, IP_FREEBIND, big_buf, &optlen); | 
|  | if (err != 0) { | 
|  | log_err("Failed to call getsockopt, ret=%d", err); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (optlen != 1 || *(__u8 *)big_buf != 0x55) { | 
|  | log_err("Unexpected IP_FREEBIND getsockopt, optlen=%d, optval=0x%x", | 
|  | optlen, *(__u8 *)big_buf); | 
|  | } | 
|  |  | 
|  | /* SO_SNDBUF is overwritten */ | 
|  |  | 
|  | buf.u32 = 0x01010101; | 
|  | err = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buf, 4); | 
|  | if (err) { | 
|  | log_err("Failed to call setsockopt(SO_SNDBUF)"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | buf.u32 = 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 (buf.u32 != 0x55AA*2) { | 
|  | log_err("Unexpected getsockopt(SO_SNDBUF) 0x%x != 0x55AA*2", | 
|  | buf.u32); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | /* TCP_CONGESTION can extend the string */ | 
|  |  | 
|  | strcpy(buf.cc, "nv"); | 
|  | err = setsockopt(fd, SOL_TCP, TCP_CONGESTION, &buf, strlen("nv")); | 
|  | if (err) { | 
|  | log_err("Failed to call setsockopt(TCP_CONGESTION)"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  |  | 
|  | optlen = sizeof(buf.cc); | 
|  | err = getsockopt(fd, SOL_TCP, TCP_CONGESTION, &buf, &optlen); | 
|  | if (err) { | 
|  | log_err("Failed to call getsockopt(TCP_CONGESTION)"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (strcmp(buf.cc, "cubic") != 0) { | 
|  | log_err("Unexpected getsockopt(TCP_CONGESTION) %s != %s", | 
|  | buf.cc, "cubic"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | /* TCP_ZEROCOPY_RECEIVE triggers */ | 
|  | memset(&buf, 0, sizeof(buf)); | 
|  | optlen = sizeof(buf.zc); | 
|  | err = getsockopt(fd, SOL_TCP, TCP_ZEROCOPY_RECEIVE, &buf, &optlen); | 
|  | if (err) { | 
|  | log_err("Unexpected getsockopt(TCP_ZEROCOPY_RECEIVE) err=%d errno=%d", | 
|  | err, errno); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | memset(&buf, 0, sizeof(buf)); | 
|  | buf.zc.address = 12345; /* rejected by BPF */ | 
|  | optlen = sizeof(buf.zc); | 
|  | errno = 0; | 
|  | err = getsockopt(fd, SOL_TCP, TCP_ZEROCOPY_RECEIVE, &buf, &optlen); | 
|  | if (errno != EPERM) { | 
|  | log_err("Unexpected getsockopt(TCP_ZEROCOPY_RECEIVE) err=%d errno=%d", | 
|  | err, errno); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | free(big_buf); | 
|  | close(fd); | 
|  | return 0; | 
|  | err: | 
|  | free(big_buf); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static void run_test(int cgroup_fd) | 
|  | { | 
|  | struct sockopt_sk *skel; | 
|  |  | 
|  | skel = sockopt_sk__open_and_load(); | 
|  | if (!ASSERT_OK_PTR(skel, "skel_load")) | 
|  | goto cleanup; | 
|  |  | 
|  | skel->bss->page_size = getpagesize(); | 
|  |  | 
|  | skel->links._setsockopt = | 
|  | bpf_program__attach_cgroup(skel->progs._setsockopt, cgroup_fd); | 
|  | if (!ASSERT_OK_PTR(skel->links._setsockopt, "setsockopt_link")) | 
|  | goto cleanup; | 
|  |  | 
|  | skel->links._getsockopt = | 
|  | bpf_program__attach_cgroup(skel->progs._getsockopt, cgroup_fd); | 
|  | if (!ASSERT_OK_PTR(skel->links._getsockopt, "getsockopt_link")) | 
|  | goto cleanup; | 
|  |  | 
|  | ASSERT_OK(getsetsockopt(), "getsetsockopt"); | 
|  |  | 
|  | cleanup: | 
|  | sockopt_sk__destroy(skel); | 
|  | } | 
|  |  | 
|  | void test_sockopt_sk(void) | 
|  | { | 
|  | int cgroup_fd; | 
|  |  | 
|  | cgroup_fd = test__join_cgroup("/sockopt_sk"); | 
|  | if (CHECK_FAIL(cgroup_fd < 0)) | 
|  | return; | 
|  |  | 
|  | run_test(cgroup_fd); | 
|  | close(cgroup_fd); | 
|  | } |