diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c index 48a2e2327ace..37168f43c16c 100644 --- a/net/core/bpf_ksock.c +++ b/net/core/bpf_ksock.c @@ -37,6 +37,49 @@ static void ksock_release_work_fn(struct work_struct *work) kfree(ks); } +static int bpf_ksock_addr_to_sockaddr(const union bpf_ksock_addr *addr, + u32 addr_sz, struct sockaddr_storage *sa) +{ + if (!addr || addr_sz != sizeof(*addr)) + return -EINVAL; + + /* Kfunc memory arguments may be unaligned. */ + memcpy(sa, addr, sizeof(*addr)); + + switch (sa->ss_family) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + default: + return -EAFNOSUPPORT; + } +} + +static int ksock_sendto(struct bpf_ksock *ks, const void *data, u32 data_sz, + const union bpf_ksock_addr *addr, u32 addr_sz) +{ + struct sockaddr_storage sa; + struct msghdr msg = { + .msg_flags = MSG_DONTWAIT, + }; + struct kvec iov = { + .iov_base = (void *)data, + .iov_len = data_sz, + }; + int addrlen; + + if (addr) { + addrlen = bpf_ksock_addr_to_sockaddr(addr, addr_sz, &sa); + if (addrlen < 0) + return addrlen; + msg.msg_name = &sa; + msg.msg_namelen = addrlen; + } + + return kernel_sendmsg(ks->sock, &msg, &iov, 1, data_sz); +} + __bpf_kfunc_start_defs(); /** @@ -155,22 +198,9 @@ __bpf_kfunc int bpf_ksock_connect(struct bpf_ksock *ks, struct sockaddr_storage sa; int addrlen; - if (!addr || addr__sz != sizeof(*addr)) - return -EINVAL; - - /* Kfunc memory arguments may be unaligned. */ - memcpy(&sa, addr, sizeof(*addr)); - - switch (sa.ss_family) { - case AF_INET: - addrlen = sizeof(struct sockaddr_in); - break; - case AF_INET6: - addrlen = sizeof(struct sockaddr_in6); - break; - default: - return -EAFNOSUPPORT; - } + addrlen = bpf_ksock_addr_to_sockaddr(addr, addr__sz, &sa); + if (addrlen < 0) + return addrlen; return connect_socket(ks->sock, &sa, addrlen, 0); } @@ -225,18 +255,29 @@ CFI_NOSEAL(bpf_ksock_release_dtor); __bpf_kfunc int bpf_ksock_send(struct bpf_ksock *ks, const void *data, u32 data__sz) { - struct msghdr msg = { - .msg_flags = MSG_DONTWAIT, - }; - struct kvec iov = { - .iov_base = (void *)data, - .iov_len = data__sz, - }; - int ret; + return ksock_sendto(ks, data, data__sz, NULL, 0); +} - ret = kernel_sendmsg(ks->sock, &msg, &iov, 1, data__sz); - - return ret; +/** + * bpf_ksock_sendto() - Send data through a BPF kernel socket to a remote address. + * @ks: The BPF kernel socket context. Must be an acquired reference. + * @data: Pointer to the data to send. + * @data__sz: Size of the data to send. + * @addr__nullable: Pointer to an IPv4 or IPv6 socket address, or NULL to use + * the connected destination. + * @addr__sz: Size of the address union, ignored when the address is NULL. + * + * Sends data to the specified address, or to the connected destination when + * the address is NULL, best-effort and nonblocking. This may sleep + * (kernel_sendmsg), so it can only be called from sleepable BPF programs. + * + * Return: Number of bytes sent on success, negative errno on error. + */ +__bpf_kfunc int bpf_ksock_sendto(struct bpf_ksock *ks, const void *data, u32 data__sz, + const union bpf_ksock_addr *addr__nullable, + u32 addr__sz) +{ + return ksock_sendto(ks, data, data__sz, addr__nullable, addr__sz); } __bpf_kfunc_end_defs(); @@ -255,6 +296,7 @@ BTF_KFUNCS_START(ksock_kfunc_btf_ids) BTF_ID_FLAGS(func, bpf_ksock_release, KF_RELEASE) BTF_ID_FLAGS(func, bpf_ksock_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_ksock_send, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_ksock_sendto, KF_SLEEPABLE) BTF_KFUNCS_END(ksock_kfunc_btf_ids) #ifdef CONFIG_BPF_LSM diff --git a/tools/testing/selftests/bpf/prog_tests/ksock_sendto.c b/tools/testing/selftests/bpf/prog_tests/ksock_sendto.c new file mode 100644 index 000000000000..498f0e1be6ee --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/ksock_sendto.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Isovalent */ + +#include "test_progs.h" +#include "network_helpers.h" +#include "ksock_sendto.skel.h" + +struct sendto_test_case { + const char *name; + bool connected; + bool use_sendto_addr; +}; + +static bool recv_payload(int fd, const void *expected, size_t len) +{ + char buf[64]; + ssize_t n; + + n = recv(fd, buf, sizeof(buf), MSG_TRUNC); + if (!ASSERT_EQ(n, len, "recv length")) + return false; + return ASSERT_MEMEQ(buf, expected, len, "recv payload"); +} + +static void test_sendto_case(const struct sendto_test_case *test) +{ + LIBBPF_OPTS(bpf_test_run_opts, opts); + struct ksock_sendto *skel; + int receivers[2] = { -1, -1 }; + int err, i, port, sendto_fd; + char buf[64]; + ssize_t n; + + skel = ksock_sendto__open_and_load(); + if (!ASSERT_OK_PTR(skel, "ksock_sendto open and load")) + return; + + /* Use separate receivers for the default peer and explicit destination. */ + for (i = 0; i < ARRAY_SIZE(receivers); i++) { + receivers[i] = start_server(AF_INET, SOCK_DGRAM, "127.0.0.1", 0, 1000); + if (!ASSERT_OK_FD(receivers[i], "receiver socket")) + goto out; + } + + port = get_socket_local_port(receivers[0]); + if (!ASSERT_GE(port, 0, "connected port")) + goto out; + skel->bss->connected_port = port; + port = get_socket_local_port(receivers[1]); + if (!ASSERT_GE(port, 0, "sendto port")) + goto out; + skel->bss->sendto_port = port; + skel->bss->connected = test->connected; + skel->bss->use_sendto_addr = test->use_sendto_addr; + + err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.test_sendto), &opts); + if (!ASSERT_OK(err, "run test_sendto") || !ASSERT_OK(opts.retval, "test_sendto retval")) + goto out; + + /* Check sendto() outcome */ + if (!ASSERT_EQ(skel->data->sendto_ret, sizeof(skel->data->sendto_data), "sendto bytes")) + goto out; + sendto_fd = receivers[test->use_sendto_addr ? 1 : 0]; + if (!recv_payload(sendto_fd, skel->data->sendto_data, sizeof(skel->data->sendto_data))) + goto out; + + /* Check send() outcome */ + if (test->connected) { + if (!ASSERT_EQ(skel->data->send_ret, sizeof(skel->data->send_data), "send bytes")) + goto out; + if (!recv_payload(receivers[0], skel->data->send_data, + sizeof(skel->data->send_data))) + goto out; + } else if (!ASSERT_EQ(skel->data->send_ret, -EDESTADDRREQ, + "send after sendto remains unconnected")) { + goto out; + } + + /* Catch unexpected deliveries to either destination. */ + for (i = 0; i < ARRAY_SIZE(receivers); i++) { + n = recv(receivers[i], buf, sizeof(buf), MSG_DONTWAIT); + if (!ASSERT_EQ(n, -1, "no extra datagram") || + !ASSERT_EQ(errno, EAGAIN, "recv errno")) + goto out; + } + +out: + for (i = 0; i < ARRAY_SIZE(receivers); i++) { + if (receivers[i] >= 0) + close(receivers[i]); + } + ksock_sendto__destroy(skel); +} + +void test_ksock_sendto(void) +{ + static const struct sendto_test_case tests[] = { + { .name = "unconnected", .use_sendto_addr = true }, + { .name = "connected_null_addr", .connected = true }, + { .name = "connected_explicit_addr", .connected = true, .use_sendto_addr = true }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + if (test__start_subtest(tests[i].name)) + test_sendto_case(&tests[i]); + } +} diff --git a/tools/testing/selftests/bpf/progs/ksock_common.h b/tools/testing/selftests/bpf/progs/ksock_common.h index 01edaeb9fdd4..2a32cff2fbe9 100644 --- a/tools/testing/selftests/bpf/progs/ksock_common.h +++ b/tools/testing/selftests/bpf/progs/ksock_common.h @@ -16,6 +16,8 @@ int bpf_ksock_connect(struct bpf_ksock *ks, const union bpf_ksock_addr *addr, struct bpf_ksock *bpf_ksock_acquire(struct bpf_ksock *ks) __ksym; void bpf_ksock_release(struct bpf_ksock *ks) __ksym; int bpf_ksock_send(struct bpf_ksock *ks, const void *data, u32 data__sz) __ksym; +int bpf_ksock_sendto(struct bpf_ksock *ks, const void *data, u32 data__sz, + const union bpf_ksock_addr *addr__nullable, u32 addr__sz) __ksym; void bpf_rcu_read_lock(void) __ksym; void bpf_rcu_read_unlock(void) __ksym; diff --git a/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c index fd2ccfdb5802..9533570eb340 100644 --- a/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c +++ b/tools/testing/selftests/bpf/progs/ksock_lsm_verifier.c @@ -12,7 +12,7 @@ char send_data[11] = "dummy data"; SEC("lsm.s/socket_sendmsg") __description("bpf_ksock_send is rejected from socket_sendmsg LSM hook") __failure __msg("calling kernel function bpf_ksock_send is not allowed") -int BPF_PROG(ksock_socket_sendmsg, struct socket *sock, struct msghdr *msg, +int BPF_PROG(socket_sendmsg_ksock_send, struct socket *sock, struct msghdr *msg, int size, int ret) { struct __ksock_ctx_value *v; @@ -32,4 +32,27 @@ int BPF_PROG(ksock_socket_sendmsg, struct socket *sock, struct msghdr *msg, return ret; } +SEC("lsm.s/socket_sendmsg") +__description("bpf_ksock_sendto is rejected from socket_sendmsg LSM hook") +__failure __msg("calling kernel function bpf_ksock_sendto is not allowed") +int BPF_PROG(socket_sendmsg_ksock_sendto, struct socket *sock, + struct msghdr *msg, int size, int ret) +{ + struct __ksock_ctx_value *v; + struct bpf_ksock *ks; + + v = ksock_ctx_value_lookup(); + if (!v) + return ret; + + ks = bpf_kptr_xchg(&v->ctx, NULL); + if (!ks) + return ret; + + bpf_ksock_sendto(ks, send_data, sizeof(send_data), NULL, 0); + bpf_ksock_release(ks); + + return ret; +} + char __license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/ksock_sendto.c b/tools/testing/selftests/bpf/progs/ksock_sendto.c new file mode 100644 index 000000000000..b6eacf277fcb --- /dev/null +++ b/tools/testing/selftests/bpf/progs/ksock_sendto.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Isovalent */ + +#include "vmlinux.h" +#include +#include +#include "bpf_tracing_net.h" +#include "ksock_common.h" + +char sendto_data[] = "hello from bpf sendto"; +char send_data[] = "hello from bpf send"; + +__be16 connected_port; +__be16 sendto_port; +bool connected; +bool use_sendto_addr; +int sendto_ret = -1; +int send_ret = -1; + +SEC("syscall") +int test_sendto(void *ctx) +{ + struct bpf_ksock_create_opts opts = { + .family = AF_INET, + .type = SOCK_DGRAM, + .protocol = IPPROTO_UDP, + }; + union bpf_ksock_addr addr = {}; + struct bpf_ksock *ks; + int err = 0; + + ks = bpf_ksock_create(&opts, sizeof(opts), &err); + if (!ks) + return err; + + addr.sin.sin_family = AF_INET; + addr.sin.sin_addr.s_addr = bpf_htonl(0x7f000001); + if (connected) { + addr.sin.sin_port = connected_port; + err = bpf_ksock_connect(ks, &addr, sizeof(addr)); + if (err) + goto out; + } + + addr.sin.sin_port = sendto_port; + if (use_sendto_addr) + sendto_ret = bpf_ksock_sendto(ks, sendto_data, sizeof(sendto_data), + &addr, sizeof(addr)); + else + sendto_ret = bpf_ksock_sendto(ks, sendto_data, sizeof(sendto_data), NULL, 0); + + /* sendto() must neither connect the socket nor change its default peer. */ + send_ret = bpf_ksock_send(ks, send_data, sizeof(send_data)); +out: + bpf_ksock_release(ks); + return err; +} + +char __license[] SEC("license") = "GPL";