Merge branch 'bpf-add-bpf_ksock_sendto-kfunc'

Mahe Tardy says:

====================
bpf: Add bpf_ksock_sendto() kfunc

This patch series improves the bpf_ksock infrastructure in line with the
work proposed during Prague Plumbers 2026 presentation[^1] by adding
the bpf_ksock_sendto() kfunc to complement the bpf_ksock_send().

This is useful when using ksock to reply to differents peers while
reusing the same UDP socket and generally works toward the goal of ksock
to support the basic socket operations.

[^1]: https://lpc.events/event/20/contributions/2430/

v2 updates:
- remove the LSM verifier filter refinements (alexei)
- remove allowing calling connect() from the send path (alexei)
- change order of the tests patches for signature definition (sashiko)

v1: https://lore.kernel.org/bpf/DM9RCU3BDWEG.1C32ZIVU95S76@gmail.com/
====================

Link: https://patch.msgid.link/20261020155848.75711-1-mahe.tardy@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-10-20 20:56:37 +00:00
commit d244ec6d3d
No known key found for this signature in database
5 changed files with 262 additions and 28 deletions

View File

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

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

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

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