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: 22+ 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-06 21:33 ` Amery Hung
2026-07-06 23:01 ` Kuniyuki Iwashima
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox