selftests/bpf: Test ksock sendto()

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 <mahe.tardy@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261020155848.75711-3-mahe.tardy@gmail.com
This commit is contained in:
Mahe Tardy 2026-10-20 15:58:47 +00:00 committed by Alexei Starovoitov
parent 74be619845
commit 7d563ae3ed
No known key found for this signature in database
3 changed files with 169 additions and 0 deletions

View File

@ -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]);
}
}

View File

@ -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;

View File

@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Isovalent */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#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";