From: Mahe Tardy <mahe.tardy@gmail.com>
To: bpf@vger.kernel.org
Cc: andrew+netdev@lunn.ch, andrii@kernel.org, ast@kernel.org,
daniel@iogearbox.net, davem@davemloft.net, eddyz87@gmail.com,
edumazet@google.com, john.fastabend@gmail.com, kuba@kernel.org,
liamwisehart@meta.com, martin.lau@linux.dev, pabeni@redhat.com,
song@kernel.org, netdev@vger.kernel.org,
Mahe Tardy <mahe.tardy@gmail.com>
Subject: [PATCH bpf-next 5/6] selftests/bpf: Add ksock net ns quota tests
Date: Mon, 6 Jul 2026 09:35:24 +0000 [thread overview]
Message-ID: <20260706093525.13030-6-mahe.tardy@gmail.com> (raw)
In-Reply-To: <20260706093525.13030-1-mahe.tardy@gmail.com>
The new bpf_ksock_create() kfunc is limited by quota enforced at the
network namespace layer. This test verifies that the quota are enforced
and the acquire/release patch increase/reduce the counters.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
.../selftests/bpf/prog_tests/ksock_quota.c | 139 +++++++++++++++
.../testing/selftests/bpf/progs/ksock_quota.c | 167 ++++++++++++++++++
2 files changed, 306 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/ksock_quota.c
create mode 100644 tools/testing/selftests/bpf/progs/ksock_quota.c
diff --git a/tools/testing/selftests/bpf/prog_tests/ksock_quota.c b/tools/testing/selftests/bpf/prog_tests/ksock_quota.c
new file mode 100644
index 000000000000..c86d9b0a9513
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ksock_quota.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include <sys/stat.h>
+
+#include "test_progs.h"
+#include "network_helpers.h"
+#include "sysctl_helpers.h"
+#include "ksock_quota.skel.h"
+
+#define NS_NET_QUOTA_TEST1 "ksock_net_quota_ns1"
+#define NS_NET_QUOTA_TEST2 "ksock_net_quota_ns2"
+#define KSOCK_MAX_SYSCTL "/proc/sys/net/core/bpf_ksock_max"
+#define KSOCK_QUOTA_NS_SLOTS 3
+#define KSOCK_QUOTA_NS1_OFFSET 0
+#define KSOCK_QUOTA_NS2_OFFSET KSOCK_QUOTA_NS_SLOTS
+
+static int ksock_run_prog(struct bpf_program *prog)
+{
+ LIBBPF_OPTS(bpf_test_run_opts, opts);
+ int err;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(prog), &opts);
+ if (err)
+ return err;
+ return opts.retval;
+}
+
+static __u64 ksock_quota_release_completed(struct ksock_quota *skel)
+{
+ return __atomic_load_n(&skel->bss->quota_release_completed,
+ __ATOMIC_ACQUIRE);
+}
+
+static int ksock_release_quota_slots(struct ksock_quota *skel, int slot_offset)
+{
+ __u64 completed;
+ int err;
+
+ completed = ksock_quota_release_completed(skel);
+ skel->bss->quota_slot_offset = slot_offset;
+ err = ksock_run_prog(skel->progs.ksock_quota_release);
+ if (!ASSERT_OK(err, "ksock_quota_release"))
+ return -1;
+
+ while (ksock_quota_release_completed(skel) - completed <
+ skel->bss->quota_released)
+ usleep(1000);
+
+ return skel->bss->quota_released;
+}
+
+static bool ksock_expect_quota(struct ksock_quota *skel, int slot_offset,
+ const char *name)
+{
+ int err;
+
+ skel->bss->quota_slot_offset = slot_offset;
+ err = ksock_run_prog(skel->progs.ksock_quota_create);
+ if (!ASSERT_OK(err, name))
+ return false;
+ if (!ASSERT_EQ(skel->bss->quota_created, 2, name))
+ return false;
+ return ASSERT_EQ(skel->bss->quota_err, -ENOSPC, name);
+}
+
+static bool record_current_ns(struct ksock_quota *skel, int target)
+{
+ struct stat st;
+ int err;
+
+ err = stat("/proc/self/ns/net", &st);
+ if (!ASSERT_OK(err, "stat netns"))
+ return false;
+ skel->bss->target_netns_inum[target] = (__u32)st.st_ino;
+ return true;
+}
+
+void serial_test_ksock_net_quota(void)
+{
+ char old_max[16] = {};
+ struct netns_obj *netns1 = NULL, *netns2 = NULL;
+ struct ksock_quota *skel;
+ int released;
+
+ skel = ksock_quota__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "ksock quota skeleton"))
+ return;
+ skel->links.ksock_release_work_enter =
+ bpf_program__attach_trace(skel->progs.ksock_release_work_enter);
+ if (!ASSERT_OK_PTR(skel->links.ksock_release_work_enter,
+ "attach ksock release work entry"))
+ goto out;
+ skel->links.ksock_release_work_exit =
+ bpf_program__attach_trace(skel->progs.ksock_release_work_exit);
+ if (!ASSERT_OK_PTR(skel->links.ksock_release_work_exit,
+ "attach ksock release work exit"))
+ goto out;
+
+ if (sysctl_set_or_fail(KSOCK_MAX_SYSCTL, old_max, "2"))
+ goto out;
+
+ netns1 = netns_new(NS_NET_QUOTA_TEST1, true);
+ if (!ASSERT_OK_PTR(netns1, "create first netns"))
+ goto out;
+ if (!record_current_ns(skel, 0))
+ goto out;
+
+ if (!ksock_expect_quota(skel, KSOCK_QUOTA_NS1_OFFSET,
+ "first netns quota"))
+ goto out;
+
+ /* The host-wide setting grants the full allowance to each netns. */
+ netns2 = netns_new(NS_NET_QUOTA_TEST2, true);
+ if (!ASSERT_OK_PTR(netns2, "create second netns"))
+ goto out;
+ if (!record_current_ns(skel, 1))
+ goto out;
+
+ if (!ksock_expect_quota(skel, KSOCK_QUOTA_NS2_OFFSET,
+ "second netns quota"))
+ goto out;
+
+ released = ksock_release_quota_slots(skel, KSOCK_QUOTA_NS2_OFFSET);
+ if (!ASSERT_EQ(released, 2, "second netns quota released"))
+ goto out;
+
+ /* Both released slots must become available again. */
+ ksock_expect_quota(skel, KSOCK_QUOTA_NS2_OFFSET, "recovered quota");
+
+out:
+ ksock_release_quota_slots(skel, KSOCK_QUOTA_NS2_OFFSET);
+ ksock_release_quota_slots(skel, KSOCK_QUOTA_NS1_OFFSET);
+ netns_free(netns2);
+ netns_free(netns1);
+ if (old_max[0])
+ sysctl_set_or_fail(KSOCK_MAX_SYSCTL, NULL, old_max);
+ ksock_quota__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/ksock_quota.c b/tools/testing/selftests/bpf/progs/ksock_quota.c
new file mode 100644
index 000000000000..91854ea333c1
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/ksock_quota.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Isovalent */
+
+#include "vmlinux.h"
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_tracing_net.h"
+#include "ksock_common.h"
+
+#define KSOCK_QUOTA_NS_SLOTS 3
+#define KSOCK_QUOTA_NETNS 2
+#define KSOCK_QUOTA_SLOTS (KSOCK_QUOTA_NS_SLOTS * KSOCK_QUOTA_NETNS)
+
+struct ksock_quota_value {
+ struct bpf_ksock __kptr * ctx;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __type(key, u32);
+ __type(value, struct ksock_quota_value);
+ __uint(max_entries, KSOCK_QUOTA_SLOTS);
+} quota_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, u64);
+ __type(value, u8);
+ __uint(max_entries, KSOCK_QUOTA_SLOTS);
+} release_workers SEC(".maps");
+
+u32 quota_created;
+u32 quota_released;
+u32 quota_slot_offset;
+u32 target_netns_inum[KSOCK_QUOTA_NETNS];
+u64 quota_release_completed;
+int quota_err;
+
+static __always_inline bool
+ksock_quota_create_one(u32 i,
+ const struct bpf_ksock_create_opts *create_opts)
+{
+ struct ksock_quota_value *v;
+ struct bpf_ksock *ks, *old;
+ int err = 0;
+
+ i += quota_slot_offset;
+ if (i >= KSOCK_QUOTA_SLOTS) {
+ quota_err = -ERANGE;
+ return true;
+ }
+
+ barrier_var(err);
+ ks = bpf_ksock_create(create_opts, sizeof(*create_opts), &err);
+ if (!ks) {
+ quota_err = err;
+ return true;
+ }
+
+ v = bpf_map_lookup_elem("a_map, &i);
+ if (!v) {
+ bpf_ksock_release(ks);
+ quota_err = -ENOENT;
+ return true;
+ }
+
+ old = bpf_kptr_xchg(&v->ctx, ks);
+ if (old)
+ bpf_ksock_release(old);
+ quota_created++;
+ return false;
+}
+
+static __always_inline void ksock_quota_release_one(u32 i)
+{
+ struct ksock_quota_value *v;
+ struct bpf_ksock *ks;
+
+ i += quota_slot_offset;
+ if (i >= KSOCK_QUOTA_SLOTS)
+ return;
+
+ v = bpf_map_lookup_elem("a_map, &i);
+ if (!v)
+ return;
+
+ ks = bpf_kptr_xchg(&v->ctx, NULL);
+ if (ks) {
+ bpf_ksock_release(ks);
+ quota_released++;
+ }
+}
+
+SEC("syscall")
+int ksock_quota_create(void *ctx)
+{
+ struct bpf_ksock_create_opts create_opts = {};
+
+ create_opts.family = AF_INET;
+ create_opts.type = SOCK_DGRAM;
+ create_opts.protocol = IPPROTO_UDP;
+ quota_created = 0;
+ quota_err = 0;
+
+ if (ksock_quota_create_one(0, &create_opts))
+ return 0;
+ if (ksock_quota_create_one(1, &create_opts))
+ return 0;
+ ksock_quota_create_one(2, &create_opts);
+
+ return 0;
+}
+
+SEC("syscall")
+int ksock_quota_release(void *ctx)
+{
+ quota_released = 0;
+ ksock_quota_release_one(0);
+ ksock_quota_release_one(1);
+ ksock_quota_release_one(2);
+
+ return 0;
+}
+
+SEC("fentry/ksock_release_work_fn")
+int BPF_PROG(ksock_release_work_enter, struct work_struct *work)
+{
+ struct rcu_work *rwork = container_of(work, struct rcu_work, work);
+ struct bpf_ksock *ks = container_of(rwork, struct bpf_ksock, rwork);
+ u64 pid_tgid = bpf_get_current_pid_tgid();
+ struct net *net;
+ u32 inum;
+ u8 tracked = 1;
+
+ /* ksock_release_work_fn() frees ks before the fexit program runs. */
+ net = BPF_CORE_READ(ks, sock, sk, __sk_common.skc_net.net);
+ if (!net)
+ return 0;
+
+ inum = BPF_CORE_READ(net, ns.inum);
+ if (inum != target_netns_inum[0] &&
+ inum != target_netns_inum[1])
+ return 0;
+
+ bpf_map_update_elem(&release_workers, &pid_tgid, &tracked, BPF_ANY);
+ return 0;
+}
+
+SEC("fexit/ksock_release_work_fn")
+int BPF_PROG(ksock_release_work_exit, struct work_struct *work)
+{
+ u64 pid_tgid = bpf_get_current_pid_tgid();
+ u8 *tracked;
+
+ tracked = bpf_map_lookup_elem(&release_workers, &pid_tgid);
+ if (!tracked)
+ return 0;
+ bpf_map_delete_elem(&release_workers, &pid_tgid);
+
+ /* The worker has released the socket and returned its quota charge. */
+ __sync_fetch_and_add("a_release_completed, 1);
+
+ return 0;
+}
+
+char __license[] SEC("license") = "GPL";
--
2.34.1
next prev parent reply other threads:[~2026-07-06 9:35 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 9:35 [PATCH bpf-next 0/6] Introduce bpf_ksock Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 1/6] net: Add __sys_connect_socket() helper Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 2/6] bpf: Add ksock kfuncs Mahe Tardy
2026-07-06 10:01 ` sashiko-bot
2026-07-06 15:42 ` Mahe Tardy
2026-07-06 10:30 ` bot+bpf-ci
2026-07-06 15:28 ` Mahe Tardy
2026-07-06 16:58 ` Stanislav Fomichev
2026-07-06 17:26 ` Mahe Tardy
2026-07-06 20:21 ` Amery Hung
2026-07-06 21:02 ` Stanislav Fomichev
2026-07-06 22:50 ` Kuniyuki Iwashima
2026-07-07 9:41 ` Mahe Tardy
2026-07-06 21:33 ` Amery Hung
2026-07-07 9:48 ` Mahe Tardy
2026-07-06 23:01 ` Kuniyuki Iwashima
2026-07-07 10:06 ` Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 3/6] selftests/bpf: Add ksock kfunc test Mahe Tardy
2026-07-06 10:10 ` sashiko-bot
2026-07-06 16:11 ` Mahe Tardy
2026-07-06 10:16 ` bot+bpf-ci
2026-07-06 15:27 ` Mahe Tardy
2026-07-06 9:35 ` [PATCH bpf-next 4/6] selftests/bpf: Add ksock LSM recursion test Mahe Tardy
2026-07-06 9:35 ` Mahe Tardy [this message]
2026-07-06 9:35 ` [PATCH bpf-next 6/6] selftests/bpf: Add ksock test for async callback guard Mahe Tardy
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260706093525.13030-6-mahe.tardy@gmail.com \
--to=mahe.tardy@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=eddyz87@gmail.com \
--cc=edumazet@google.com \
--cc=john.fastabend@gmail.com \
--cc=kuba@kernel.org \
--cc=liamwisehart@meta.com \
--cc=martin.lau@linux.dev \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=song@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.