These tests are covering that: 1. sendto() on a unconnected UDP socket works (and that the socket stays unconnected, i.e. send() on it fails with -EDESTADDRREQ). 2. sendto() on a connected UDP socket with a NULL address is equivalent to send(). 3. sendto() on a connected socket with an explicit address doesn't change the peer of the socket (send() after sendto() still sends to the original connected address). Signed-off-by: Mahe Tardy --- .../selftests/bpf/prog_tests/ksock_sendto.c | 108 ++++++++++++++++++ .../selftests/bpf/progs/ksock_common.h | 2 + .../selftests/bpf/progs/ksock_sendto.c | 59 ++++++++++ 3 files changed, 169 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_sendto.c create mode 100644 tools/testing/selftests/bpf/progs/ksock_sendto.c 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_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"; -- 2.43.0