* [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration
@ 2025-05-02 16:15 Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 1/7] bpf: udp: Make mem flags configurable through bpf_iter_udp_realloc_batch Jordan Rife
` (7 more replies)
0 siblings, 8 replies; 13+ messages in thread
From: Jordan Rife @ 2025-05-02 16:15 UTC (permalink / raw)
To: netdev, bpf
Cc: Jordan Rife, Aditi Ghag, Daniel Borkmann, Martin KaFai Lau,
Willem de Bruijn, Kuniyuki Iwashima, Alexei Starovoitov
Both UDP and TCP socket iterators use iter->offset to track progress
through a bucket, which is a measure of the number of matching sockets
from the current bucket that have been seen or processed by the
iterator. On subsequent iterations, if the current bucket has
unprocessed items, we skip at least iter->offset matching items in the
bucket before adding any remaining items to the next batch. However,
iter->offset isn't always an accurate measure of "things already seen"
when the underlying bucket changes between reads which can lead to
repeated or skipped sockets. Instead, this series remembers the cookies
of the sockets we haven't seen yet in the current bucket and resumes
from the first cookie in that list that we can find on the next
iteration. This series focuses on UDP socket iterators, but a later
series will apply a similar approach to TCP socket iterators.
To be more specific, this series replaces struct sock **batch inside
struct bpf_udp_iter_state with union bpf_udp_iter_batch_item *batch,
where union bpf_udp_iter_batch_item can contain either a pointer to a
socket or a socket cookie. During reads, batch contains pointers to all
sockets in the current batch while between reads batch contains all the
cookies of the sockets in the current bucket that have yet to be
processed. On subsequent reads, when iteration resumes,
bpf_iter_udp_batch finds the first saved cookie that matches a socket in
the bucket's socket list and picks up from there to construct the next
batch. On average, assuming it's rare that the next socket disappears
before the next read occurs, we should only need to scan as much as we
did with the offset-based approach to find the starting point. In the
case that the next socket is no longer there, we keep scanning through
the saved cookies list until we find a match. The worst case is when
none of the sockets from last time exist anymore, but again, this should
be rare.
CHANGES
=======
v6 -> v7:
* Move initialization of iter->state.bucket to -1 from patch five ("bpf:
udp: Avoid socket skips and repeats during iteration") to patch three
("bpf: udp: Get rid of st_bucket_done") to avoid skipping the first
bucket in the patch three and four (Martin).
* Rename sock to sk in bpf_iter_batch_item (Martin).
* Use ASSERT_OK_PTR in do_resume_test to check if counts is NULL
(Martin).
* goto done in do_resume_test when calloc or sock_iter_batch__open fails
to make sure things are cleaned up properly, and initialize pointers
to NULL explicitly to silence warnings from llvm 20 in CI.
v5 -> v6:
* Rework the logic in patch two ("bpf: udp: Make sure iter->batch
always contains a full bucket snapshot") again to simplify it:
* Only try realloc with GFP_USER one time instead of two (Alexei).
* v5 introduced a second call to bpf_iter_udp_realloc_batch inside the
loop to handle the GFP_ATOMIC case. In v6, move the GFP_USER case
inside the loop as well, so it's all in once place. This, I feel,
makes it a bit easier to understand the control flow. Consequently,
it also simplifies the logic outside the loop.
* Use GFP_NOWAIT instead of GFP_ATOMIC to avoid depleting memory
reserves, since iterators are not critical operation (Alexei). Alexei
suggested using __GFP_NOWARN as well with GFP_NOWAIT, but this is
already set inside bpf_iter_udp_realloc_batch, so no change was needed
there.
* Introduce patch three ("bpf: udp: Get rid of st_bucket_done") to
simplify things further, since with patch two, st_bucket_done == true
is equivalent to iter->cur_sk == iter->end_sk.
* In patch five ("bpf: udp: Avoid socket skips and repeats during
iteration"), initialize iter->state.bucket to -1 so that on the first
call to bpf_iter_udp_batch, the resume_bucket condition is not hit.
This avoids adding a special case to the condition around
bpf_iter_udp_resume for bucket zero.
v4 -> v5:
* Rework the logic from patch two ("bpf: udp: Make sure iter->batch
always contains a full bucket snapshot") to move the handling of the
GFP_ATOMIC case inside the main loop and get rid of the extra lock
variable. This makes the logic clearer and makes it clearer that the
bucket lock is always released (Martin).
* Introduce udp_portaddr_for_each_entry_from in patch two instead of
patch four ("bpf: udp: Avoid socket skips and repeats during
iteration"), since patch two now needs to be able to resume list
iteration from an arbitrary point in the GFP_ATOMIC case.
* Similarly, introduce the memcpy inside bpf_iter_udp_realloc_batch in
patch two instead of patch four, since in the GFP_ATOMIC case the new
batch needs to remember the sockets from the old batch.
* Use sock_gen_cookie instead of __sock_gen_cookie inside
bpf_iter_udp_put_batch, since it can be called from a preemptible
context (Martin).
v3 -> v4:
* Explicitly assign sk = NULL on !iter->end_sk exit condition
(Kuniyuki).
* Reword the commit message of patch two ("bpf: udp: Make sure
iter->batch always contains a full bucket snapshot") to make the
reasoning for GFP_ATOMIC more clear.
v2 -> v3:
* Guarantee that iter->batch is always a full snapshot of a bucket to
prevent socket repeat scenarios [3]. This supercedes the patch from v2
that simply propagated ENOMEM up from bpf_iter_udp_batch and covers
the scenario where the batch size is still too small after a realloc.
* Fix up self tests (Martin)
* ASSERT_EQ(nread, sizeof(out), "nread") instead of
ASSERT_GE(nread, 1, "nread) in read_n.
* Use ASSERT_OK and ASSERT_OK_FD in several places.
* Add missing free(counts) to do_resume_test.
* Move int local_port declaration to the top of do_resume_test.
* Remove unnecessary guards before close and free.
v1 -> v2:
* Drop WARN_ON_ONCE from bpf_iter_udp_realloc_batch (Kuniyuki).
* Fixed memcpy size parameter in bpf_iter_udp_realloc_batch; before it
was missing sizeof(elem) * (Kuniyuki).
* Move "bpf: udp: Propagate ENOMEM up from bpf_iter_udp_batch" to patch
two in the series (Kuniyuki).
rfc [1] -> v1:
* Use hlist_entry_safe directly to retrieve the first socket in the
current bucket's linked list instead of immediately breaking from
udp_portaddr_for_each_entry (Martin).
* Cancel iteration if bpf_iter_udp_realloc_batch() can't grab enough
memory to contain a full snapshot of the current bucket to prevent
unwanted skips or repeats [2].
[1]: https://lore.kernel.org/bpf/20250404220221.1665428-1-jordan@jrife.io/
[2]: https://lore.kernel.org/bpf/CABi4-ogUtMrH8-NVB6W8Xg_F_KDLq=yy-yu-tKr2udXE2Mu1Lg@mail.gmail.com/
[3]: https://lore.kernel.org/bpf/d323d417-3e8b-48af-ae94-bc28469ac0c1@linux.dev/
Jordan Rife (7):
bpf: udp: Make mem flags configurable through
bpf_iter_udp_realloc_batch
bpf: udp: Make sure iter->batch always contains a full bucket snapshot
bpf: udp: Get rid of st_bucket_done
bpf: udp: Use bpf_udp_iter_batch_item for bpf_udp_iter_state batch
items
bpf: udp: Avoid socket skips and repeats during iteration
selftests/bpf: Return socket cookies from sock_iter_batch progs
selftests/bpf: Add tests for bucket resume logic in UDP socket
iterators
include/linux/udp.h | 3 +
net/ipv4/udp.c | 173 ++++---
.../bpf/prog_tests/sock_iter_batch.c | 447 +++++++++++++++++-
.../selftests/bpf/progs/bpf_tracing_net.h | 1 +
.../selftests/bpf/progs/sock_iter_batch.c | 24 +-
5 files changed, 575 insertions(+), 73 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v7 bpf-next 1/7] bpf: udp: Make mem flags configurable through bpf_iter_udp_realloc_batch
2025-05-02 16:15 [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration Jordan Rife
@ 2025-05-02 16:15 ` Jordan Rife
2025-05-02 21:31 ` Martin KaFai Lau
2025-05-02 16:15 ` [PATCH v7 bpf-next 2/7] bpf: udp: Make sure iter->batch always contains a full bucket snapshot Jordan Rife
` (6 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Jordan Rife @ 2025-05-02 16:15 UTC (permalink / raw)
To: netdev, bpf
Cc: Jordan Rife, Aditi Ghag, Daniel Borkmann, Martin KaFai Lau,
Willem de Bruijn, Kuniyuki Iwashima, Alexei Starovoitov
Prepare for the next patch which needs to be able to choose either
GFP_USER or GFP_NOWAIT for calls to bpf_iter_udp_realloc_batch.
Signed-off-by: Jordan Rife <jordan@jrife.io>
Reviewed-by: Kuniyuki Iwashima <kuniyu@amazon.com>
---
net/ipv4/udp.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 2742cc7602bb..6a3c351aa06e 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -3401,7 +3401,7 @@ struct bpf_udp_iter_state {
};
static int bpf_iter_udp_realloc_batch(struct bpf_udp_iter_state *iter,
- unsigned int new_batch_sz);
+ unsigned int new_batch_sz, int flags);
static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
{
struct bpf_udp_iter_state *iter = seq->private;
@@ -3477,7 +3477,8 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
iter->st_bucket_done = true;
goto done;
}
- if (!resized && !bpf_iter_udp_realloc_batch(iter, batch_sks * 3 / 2)) {
+ if (!resized && !bpf_iter_udp_realloc_batch(iter, batch_sks * 3 / 2,
+ GFP_USER)) {
resized = true;
/* After allocating a larger batch, retry one more time to grab
* the whole bucket.
@@ -3831,12 +3832,12 @@ DEFINE_BPF_ITER_FUNC(udp, struct bpf_iter_meta *meta,
struct udp_sock *udp_sk, uid_t uid, int bucket)
static int bpf_iter_udp_realloc_batch(struct bpf_udp_iter_state *iter,
- unsigned int new_batch_sz)
+ unsigned int new_batch_sz, int flags)
{
struct sock **new_batch;
new_batch = kvmalloc_array(new_batch_sz, sizeof(*new_batch),
- GFP_USER | __GFP_NOWARN);
+ flags | __GFP_NOWARN);
if (!new_batch)
return -ENOMEM;
@@ -3859,7 +3860,7 @@ static int bpf_iter_init_udp(void *priv_data, struct bpf_iter_aux_info *aux)
if (ret)
return ret;
- ret = bpf_iter_udp_realloc_batch(iter, INIT_BATCH_SZ);
+ ret = bpf_iter_udp_realloc_batch(iter, INIT_BATCH_SZ, GFP_USER);
if (ret)
bpf_iter_fini_seq_net(priv_data);
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 bpf-next 2/7] bpf: udp: Make sure iter->batch always contains a full bucket snapshot
2025-05-02 16:15 [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 1/7] bpf: udp: Make mem flags configurable through bpf_iter_udp_realloc_batch Jordan Rife
@ 2025-05-02 16:15 ` Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 3/7] bpf: udp: Get rid of st_bucket_done Jordan Rife
` (5 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Jordan Rife @ 2025-05-02 16:15 UTC (permalink / raw)
To: netdev, bpf
Cc: Jordan Rife, Aditi Ghag, Daniel Borkmann, Martin KaFai Lau,
Willem de Bruijn, Kuniyuki Iwashima, Alexei Starovoitov
Require that iter->batch always contains a full bucket snapshot. This
invariant is important to avoid skipping or repeating sockets during
iteration when combined with the next few patches. Before, there were
two cases where a call to bpf_iter_udp_batch may only capture part of a
bucket:
1. When bpf_iter_udp_realloc_batch() returns -ENOMEM [1].
2. When more sockets are added to the bucket while calling
bpf_iter_udp_realloc_batch(), making the updated batch size
insufficient [2].
In cases where the batch size only covers part of a bucket, it is
possible to forget which sockets were already visited, especially if we
have to process a bucket in more than two batches. This forces us to
choose between repeating or skipping sockets, so don't allow this:
1. Stop iteration and propagate -ENOMEM up to userspace if reallocation
fails instead of continuing with a partial batch.
2. Try bpf_iter_udp_realloc_batch() with GFP_USER just as before, but if
we still aren't able to capture the full bucket, call
bpf_iter_udp_realloc_batch() again while holding the bucket lock to
guarantee the bucket does not change. On the second attempt use
GFP_NOWAIT since we hold onto the spin lock.
Introduce the udp_portaddr_for_each_entry_from macro and use it instead
of udp_portaddr_for_each_entry to make it possible to continue iteration
from an arbitrary socket. This is required for this patch in the
GFP_NOWAIT case to allow us to fill the rest of a batch starting from
the middle of a bucket and the later patch which skips sockets that were
already seen.
Testing all scenarios directly is a bit difficult, but I did some manual
testing to exercise the code paths where GFP_NOWAIT is used and where
ERR_PTR(err) is returned. I used the realloc test case included later
in this series to trigger a scenario where a realloc happens inside
bpf_iter_udp_batch and made a small code tweak to force the first
realloc attempt to allocate a too-small batch, thus requiring
another attempt with GFP_NOWAIT. Some printks showed both reallocs with
the tests passing:
Apr 25 23:16:24 crow kernel: go again GFP_USER
Apr 25 23:16:24 crow kernel: go again GFP_NOWAIT
With this setup, I also forced each of the bpf_iter_udp_realloc_batch
calls to return -ENOMEM to ensure that iteration ends and that the
read() in userspace fails.
[1]: https://lore.kernel.org/bpf/CABi4-ogUtMrH8-NVB6W8Xg_F_KDLq=yy-yu-tKr2udXE2Mu1Lg@mail.gmail.com/
[2]: https://lore.kernel.org/bpf/7ed28273-a716-4638-912d-f86f965e54bb@linux.dev/
Signed-off-by: Jordan Rife <jordan@jrife.io>
---
include/linux/udp.h | 3 ++
net/ipv4/udp.c | 81 ++++++++++++++++++++++++++++++---------------
2 files changed, 58 insertions(+), 26 deletions(-)
diff --git a/include/linux/udp.h b/include/linux/udp.h
index 0807e21cfec9..a69da9c4c1c5 100644
--- a/include/linux/udp.h
+++ b/include/linux/udp.h
@@ -209,6 +209,9 @@ static inline void udp_allow_gso(struct sock *sk)
#define udp_portaddr_for_each_entry(__sk, list) \
hlist_for_each_entry(__sk, list, __sk_common.skc_portaddr_node)
+#define udp_portaddr_for_each_entry_from(__sk) \
+ hlist_for_each_entry_from(__sk, __sk_common.skc_portaddr_node)
+
#define udp_portaddr_for_each_entry_rcu(__sk, list) \
hlist_for_each_entry_rcu(__sk, list, __sk_common.skc_portaddr_node)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 6a3c351aa06e..5fe22f4f43d7 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -3410,8 +3410,9 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
int resume_bucket, resume_offset;
struct udp_table *udptable;
unsigned int batch_sks = 0;
- bool resized = false;
+ int resizes = 0;
struct sock *sk;
+ int err = 0;
resume_bucket = state->bucket;
resume_offset = iter->offset;
@@ -3432,18 +3433,21 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
*/
iter->cur_sk = 0;
iter->end_sk = 0;
- iter->st_bucket_done = false;
+ iter->st_bucket_done = true;
batch_sks = 0;
for (; state->bucket <= udptable->mask; state->bucket++) {
struct udp_hslot *hslot2 = &udptable->hash2[state->bucket].hslot;
if (hlist_empty(&hslot2->head))
- continue;
+ goto next_bucket;
iter->offset = 0;
spin_lock_bh(&hslot2->lock);
- udp_portaddr_for_each_entry(sk, &hslot2->head) {
+ sk = hlist_entry_safe(hslot2->head.first, struct sock,
+ __sk_common.skc_portaddr_node);
+fill_batch:
+ udp_portaddr_for_each_entry_from(sk) {
if (seq_sk_match(seq, sk)) {
/* Resume from the last iterated socket at the
* offset in the bucket before iterator was stopped.
@@ -3460,33 +3464,55 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
batch_sks++;
}
}
+
+ /* Allocate a larger batch and try again. */
+ if (unlikely(resizes <= 1 && iter->end_sk &&
+ iter->end_sk != batch_sks)) {
+ resizes++;
+
+ /* First, try with GFP_USER to maximize the chances of
+ * grabbing more memory.
+ */
+ if (resizes == 1) {
+ spin_unlock_bh(&hslot2->lock);
+ err = bpf_iter_udp_realloc_batch(iter,
+ batch_sks * 3 / 2,
+ GFP_USER);
+ if (err)
+ return ERR_PTR(err);
+ /* Start over. */
+ goto again;
+ }
+
+ /* Next, hold onto the lock, so the bucket doesn't
+ * change while we get the rest of the sockets.
+ */
+ err = bpf_iter_udp_realloc_batch(iter, batch_sks,
+ GFP_NOWAIT);
+ if (err) {
+ spin_unlock_bh(&hslot2->lock);
+ return ERR_PTR(err);
+ }
+
+ /* Pick up where we left off. */
+ sk = iter->batch[iter->end_sk - 1];
+ sk = hlist_entry_safe(sk->__sk_common.skc_portaddr_node.next,
+ struct sock,
+ __sk_common.skc_portaddr_node);
+ batch_sks = iter->end_sk;
+ goto fill_batch;
+ }
+
spin_unlock_bh(&hslot2->lock);
if (iter->end_sk)
break;
+next_bucket:
+ resizes = 0;
}
- /* All done: no batch made. */
- if (!iter->end_sk)
- return NULL;
-
- if (iter->end_sk == batch_sks) {
- /* Batching is done for the current bucket; return the first
- * socket to be iterated from the batch.
- */
- iter->st_bucket_done = true;
- goto done;
- }
- if (!resized && !bpf_iter_udp_realloc_batch(iter, batch_sks * 3 / 2,
- GFP_USER)) {
- resized = true;
- /* After allocating a larger batch, retry one more time to grab
- * the whole bucket.
- */
- goto again;
- }
-done:
- return iter->batch[0];
+ WARN_ON_ONCE(iter->end_sk != batch_sks);
+ return iter->end_sk ? iter->batch[0] : NULL;
}
static void *bpf_iter_udp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
@@ -3841,7 +3867,10 @@ static int bpf_iter_udp_realloc_batch(struct bpf_udp_iter_state *iter,
if (!new_batch)
return -ENOMEM;
- bpf_iter_udp_put_batch(iter);
+ if (flags != GFP_NOWAIT)
+ bpf_iter_udp_put_batch(iter);
+
+ memcpy(new_batch, iter->batch, sizeof(*iter->batch) * iter->end_sk);
kvfree(iter->batch);
iter->batch = new_batch;
iter->max_sk = new_batch_sz;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 bpf-next 3/7] bpf: udp: Get rid of st_bucket_done
2025-05-02 16:15 [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 1/7] bpf: udp: Make mem flags configurable through bpf_iter_udp_realloc_batch Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 2/7] bpf: udp: Make sure iter->batch always contains a full bucket snapshot Jordan Rife
@ 2025-05-02 16:15 ` Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 4/7] bpf: udp: Use bpf_udp_iter_batch_item for bpf_udp_iter_state batch items Jordan Rife
` (4 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Jordan Rife @ 2025-05-02 16:15 UTC (permalink / raw)
To: netdev, bpf
Cc: Jordan Rife, Aditi Ghag, Daniel Borkmann, Martin KaFai Lau,
Willem de Bruijn, Kuniyuki Iwashima, Alexei Starovoitov
Get rid of the st_bucket_done field to simplify UDP iterator state and
logic. Before, st_bucket_done could be false if bpf_iter_udp_batch
returned a partial batch; however, with the last patch ("bpf: udp: Make
sure iter->batch always contains a full bucket snapshot"),
st_bucket_done == true is equivalent to iter->cur_sk == iter->end_sk.
Signed-off-by: Jordan Rife <jordan@jrife.io>
---
net/ipv4/udp.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 5fe22f4f43d7..bc93d2786843 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -3397,7 +3397,6 @@ struct bpf_udp_iter_state {
unsigned int max_sk;
int offset;
struct sock **batch;
- bool st_bucket_done;
};
static int bpf_iter_udp_realloc_batch(struct bpf_udp_iter_state *iter,
@@ -3418,7 +3417,7 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
resume_offset = iter->offset;
/* The current batch is done, so advance the bucket. */
- if (iter->st_bucket_done)
+ if (iter->cur_sk == iter->end_sk)
state->bucket++;
udptable = udp_get_table_seq(seq, net);
@@ -3433,7 +3432,6 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
*/
iter->cur_sk = 0;
iter->end_sk = 0;
- iter->st_bucket_done = true;
batch_sks = 0;
for (; state->bucket <= udptable->mask; state->bucket++) {
@@ -3596,8 +3594,10 @@ static int bpf_iter_udp_seq_show(struct seq_file *seq, void *v)
static void bpf_iter_udp_put_batch(struct bpf_udp_iter_state *iter)
{
- while (iter->cur_sk < iter->end_sk)
- sock_put(iter->batch[iter->cur_sk++]);
+ unsigned int cur_sk = iter->cur_sk;
+
+ while (cur_sk < iter->end_sk)
+ sock_put(iter->batch[cur_sk++]);
}
static void bpf_iter_udp_seq_stop(struct seq_file *seq, void *v)
@@ -3613,10 +3613,8 @@ static void bpf_iter_udp_seq_stop(struct seq_file *seq, void *v)
(void)udp_prog_seq_show(prog, &meta, v, 0, 0);
}
- if (iter->cur_sk < iter->end_sk) {
+ if (iter->cur_sk < iter->end_sk)
bpf_iter_udp_put_batch(iter);
- iter->st_bucket_done = false;
- }
}
static const struct seq_operations bpf_iter_udp_seq_ops = {
@@ -3893,6 +3891,8 @@ static int bpf_iter_init_udp(void *priv_data, struct bpf_iter_aux_info *aux)
if (ret)
bpf_iter_fini_seq_net(priv_data);
+ iter->state.bucket = -1;
+
return ret;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 bpf-next 4/7] bpf: udp: Use bpf_udp_iter_batch_item for bpf_udp_iter_state batch items
2025-05-02 16:15 [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration Jordan Rife
` (2 preceding siblings ...)
2025-05-02 16:15 ` [PATCH v7 bpf-next 3/7] bpf: udp: Get rid of st_bucket_done Jordan Rife
@ 2025-05-02 16:15 ` Jordan Rife
2025-05-02 21:22 ` Martin KaFai Lau
2025-05-02 16:15 ` [PATCH v7 bpf-next 5/7] bpf: udp: Avoid socket skips and repeats during iteration Jordan Rife
` (3 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Jordan Rife @ 2025-05-02 16:15 UTC (permalink / raw)
To: netdev, bpf
Cc: Jordan Rife, Aditi Ghag, Daniel Borkmann, Martin KaFai Lau,
Willem de Bruijn, Kuniyuki Iwashima, Alexei Starovoitov
Prepare for the next patch that tracks cookies between iterations by
converting struct sock **batch to union bpf_udp_iter_batch_item *batch
inside struct bpf_udp_iter_state.
Signed-off-by: Jordan Rife <jordan@jrife.io>
Reviewed-by: Kuniyuki Iwashima <kuniyu@amazon.com>
---
net/ipv4/udp.c | 22 +++++++++++++---------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index bc93d2786843..9cbd43a69509 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -3390,13 +3390,17 @@ struct bpf_iter__udp {
int bucket __aligned(8);
};
+union bpf_udp_iter_batch_item {
+ struct sock *sk;
+};
+
struct bpf_udp_iter_state {
struct udp_iter_state state;
unsigned int cur_sk;
unsigned int end_sk;
unsigned int max_sk;
int offset;
- struct sock **batch;
+ union bpf_udp_iter_batch_item *batch;
};
static int bpf_iter_udp_realloc_batch(struct bpf_udp_iter_state *iter,
@@ -3457,7 +3461,7 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
}
if (iter->end_sk < iter->max_sk) {
sock_hold(sk);
- iter->batch[iter->end_sk++] = sk;
+ iter->batch[iter->end_sk++].sk = sk;
}
batch_sks++;
}
@@ -3493,7 +3497,7 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
}
/* Pick up where we left off. */
- sk = iter->batch[iter->end_sk - 1];
+ sk = iter->batch[iter->end_sk - 1].sk;
sk = hlist_entry_safe(sk->__sk_common.skc_portaddr_node.next,
struct sock,
__sk_common.skc_portaddr_node);
@@ -3510,7 +3514,7 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
}
WARN_ON_ONCE(iter->end_sk != batch_sks);
- return iter->end_sk ? iter->batch[0] : NULL;
+ return iter->end_sk ? iter->batch[0].sk : NULL;
}
static void *bpf_iter_udp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
@@ -3522,7 +3526,7 @@ static void *bpf_iter_udp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
* done with seq_show(), so unref the iter->cur_sk.
*/
if (iter->cur_sk < iter->end_sk) {
- sock_put(iter->batch[iter->cur_sk++]);
+ sock_put(iter->batch[iter->cur_sk++].sk);
++iter->offset;
}
@@ -3530,7 +3534,7 @@ static void *bpf_iter_udp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
* available in the current bucket batch.
*/
if (iter->cur_sk < iter->end_sk)
- sk = iter->batch[iter->cur_sk];
+ sk = iter->batch[iter->cur_sk].sk;
else
/* Prepare a new batch. */
sk = bpf_iter_udp_batch(seq);
@@ -3596,8 +3600,8 @@ static void bpf_iter_udp_put_batch(struct bpf_udp_iter_state *iter)
{
unsigned int cur_sk = iter->cur_sk;
- while (cur_sk < iter->end_sk)
- sock_put(iter->batch[cur_sk++]);
+ while (iter->cur_sk < iter->end_sk)
+ sock_put(iter->batch[cur_sk++].sk);
}
static void bpf_iter_udp_seq_stop(struct seq_file *seq, void *v)
@@ -3858,7 +3862,7 @@ DEFINE_BPF_ITER_FUNC(udp, struct bpf_iter_meta *meta,
static int bpf_iter_udp_realloc_batch(struct bpf_udp_iter_state *iter,
unsigned int new_batch_sz, int flags)
{
- struct sock **new_batch;
+ union bpf_udp_iter_batch_item *new_batch;
new_batch = kvmalloc_array(new_batch_sz, sizeof(*new_batch),
flags | __GFP_NOWARN);
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 bpf-next 5/7] bpf: udp: Avoid socket skips and repeats during iteration
2025-05-02 16:15 [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration Jordan Rife
` (3 preceding siblings ...)
2025-05-02 16:15 ` [PATCH v7 bpf-next 4/7] bpf: udp: Use bpf_udp_iter_batch_item for bpf_udp_iter_state batch items Jordan Rife
@ 2025-05-02 16:15 ` Jordan Rife
2025-05-02 21:29 ` Martin KaFai Lau
2025-05-02 16:15 ` [PATCH v7 bpf-next 6/7] selftests/bpf: Return socket cookies from sock_iter_batch progs Jordan Rife
` (2 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Jordan Rife @ 2025-05-02 16:15 UTC (permalink / raw)
To: netdev, bpf
Cc: Jordan Rife, Aditi Ghag, Daniel Borkmann, Martin KaFai Lau,
Willem de Bruijn, Kuniyuki Iwashima, Alexei Starovoitov
Replace the offset-based approach for tracking progress through a bucket
in the UDP table with one based on socket cookies. Remember the cookies
of unprocessed sockets from the last batch and use this list to
pick up where we left off or, in the case that the next socket
disappears between reads, find the first socket after that point that
still exists in the bucket and resume from there.
This approach guarantees that all sockets that existed when iteration
began and continue to exist throughout will be visited exactly once.
Sockets that are added to the table during iteration may or may not be
seen, but if they are they will be seen exactly once.
Signed-off-by: Jordan Rife <jordan@jrife.io>
---
net/ipv4/udp.c | 61 ++++++++++++++++++++++++++++++++++++--------------
1 file changed, 44 insertions(+), 17 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 9cbd43a69509..cf6285bae4f5 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -93,6 +93,7 @@
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/slab.h>
+#include <linux/sock_diag.h>
#include <net/tcp_states.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
@@ -3392,6 +3393,7 @@ struct bpf_iter__udp {
union bpf_udp_iter_batch_item {
struct sock *sk;
+ __u64 cookie;
};
struct bpf_udp_iter_state {
@@ -3399,26 +3401,42 @@ struct bpf_udp_iter_state {
unsigned int cur_sk;
unsigned int end_sk;
unsigned int max_sk;
- int offset;
union bpf_udp_iter_batch_item *batch;
};
static int bpf_iter_udp_realloc_batch(struct bpf_udp_iter_state *iter,
unsigned int new_batch_sz, int flags);
+static struct sock *bpf_iter_udp_resume(struct sock *first_sk,
+ union bpf_udp_iter_batch_item *cookies,
+ int n_cookies)
+{
+ struct sock *sk = NULL;
+ int i = 0;
+
+ for (; i < n_cookies; i++) {
+ sk = first_sk;
+ udp_portaddr_for_each_entry_from(sk)
+ if (cookies[i].cookie == atomic64_read(&sk->sk_cookie))
+ goto done;
+ }
+done:
+ return sk;
+}
+
static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
{
struct bpf_udp_iter_state *iter = seq->private;
struct udp_iter_state *state = &iter->state;
+ unsigned int find_cookie, end_cookie = 0;
struct net *net = seq_file_net(seq);
- int resume_bucket, resume_offset;
struct udp_table *udptable;
unsigned int batch_sks = 0;
+ int resume_bucket;
int resizes = 0;
struct sock *sk;
int err = 0;
resume_bucket = state->bucket;
- resume_offset = iter->offset;
/* The current batch is done, so advance the bucket. */
if (iter->cur_sk == iter->end_sk)
@@ -3434,6 +3452,8 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
* before releasing the bucket lock. This allows BPF programs that are
* called in seq_show to acquire the bucket lock if needed.
*/
+ find_cookie = iter->cur_sk;
+ end_cookie = iter->end_sk;
iter->cur_sk = 0;
iter->end_sk = 0;
batch_sks = 0;
@@ -3444,21 +3464,21 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
if (hlist_empty(&hslot2->head))
goto next_bucket;
- iter->offset = 0;
spin_lock_bh(&hslot2->lock);
sk = hlist_entry_safe(hslot2->head.first, struct sock,
__sk_common.skc_portaddr_node);
+ /* Resume from the first (in iteration order) unseen socket from
+ * the last batch that still exists in resume_bucket. Most of
+ * the time this will just be where the last iteration left off
+ * in resume_bucket unless that socket disappeared between
+ * reads.
+ */
+ if (state->bucket == resume_bucket)
+ sk = bpf_iter_udp_resume(sk, &iter->batch[find_cookie],
+ end_cookie - find_cookie);
fill_batch:
udp_portaddr_for_each_entry_from(sk) {
if (seq_sk_match(seq, sk)) {
- /* Resume from the last iterated socket at the
- * offset in the bucket before iterator was stopped.
- */
- if (state->bucket == resume_bucket &&
- iter->offset < resume_offset) {
- ++iter->offset;
- continue;
- }
if (iter->end_sk < iter->max_sk) {
sock_hold(sk);
iter->batch[iter->end_sk++].sk = sk;
@@ -3525,10 +3545,8 @@ static void *bpf_iter_udp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
/* Whenever seq_next() is called, the iter->cur_sk is
* done with seq_show(), so unref the iter->cur_sk.
*/
- if (iter->cur_sk < iter->end_sk) {
+ if (iter->cur_sk < iter->end_sk)
sock_put(iter->batch[iter->cur_sk++].sk);
- ++iter->offset;
- }
/* After updating iter->cur_sk, check if there are more sockets
* available in the current bucket batch.
@@ -3598,10 +3616,19 @@ static int bpf_iter_udp_seq_show(struct seq_file *seq, void *v)
static void bpf_iter_udp_put_batch(struct bpf_udp_iter_state *iter)
{
+ union bpf_udp_iter_batch_item *item;
unsigned int cur_sk = iter->cur_sk;
+ __u64 cookie;
- while (iter->cur_sk < iter->end_sk)
- sock_put(iter->batch[cur_sk++].sk);
+ /* Remember the cookies of the sockets we haven't seen yet, so we can
+ * pick up where we left off next time around.
+ */
+ while (cur_sk < iter->end_sk) {
+ item = &iter->batch[cur_sk++];
+ cookie = sock_gen_cookie(item->sk);
+ sock_put(item->sk);
+ item->cookie = cookie;
+ }
}
static void bpf_iter_udp_seq_stop(struct seq_file *seq, void *v)
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 bpf-next 6/7] selftests/bpf: Return socket cookies from sock_iter_batch progs
2025-05-02 16:15 [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration Jordan Rife
` (4 preceding siblings ...)
2025-05-02 16:15 ` [PATCH v7 bpf-next 5/7] bpf: udp: Avoid socket skips and repeats during iteration Jordan Rife
@ 2025-05-02 16:15 ` Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 7/7] selftests/bpf: Add tests for bucket resume logic in UDP socket iterators Jordan Rife
2025-05-02 21:20 ` [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration patchwork-bot+netdevbpf
7 siblings, 0 replies; 13+ messages in thread
From: Jordan Rife @ 2025-05-02 16:15 UTC (permalink / raw)
To: netdev, bpf
Cc: Jordan Rife, Aditi Ghag, Daniel Borkmann, Martin KaFai Lau,
Willem de Bruijn, Kuniyuki Iwashima, Alexei Starovoitov
Extend the iter_udp_soreuse and iter_tcp_soreuse programs to write the
cookie of the current socket, so that we can track the identity of the
sockets that the iterator has seen so far. Update the existing do_test
function to account for this change to the iterator program output. At
the same time, teach both programs to work with AF_INET as well.
Signed-off-by: Jordan Rife <jordan@jrife.io>
---
.../bpf/prog_tests/sock_iter_batch.c | 33 +++++++++++--------
.../selftests/bpf/progs/bpf_tracing_net.h | 1 +
.../selftests/bpf/progs/sock_iter_batch.c | 24 +++++++++++---
3 files changed, 41 insertions(+), 17 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/sock_iter_batch.c b/tools/testing/selftests/bpf/prog_tests/sock_iter_batch.c
index d56e18b25528..74dbe91806a0 100644
--- a/tools/testing/selftests/bpf/prog_tests/sock_iter_batch.c
+++ b/tools/testing/selftests/bpf/prog_tests/sock_iter_batch.c
@@ -9,12 +9,18 @@
static const int nr_soreuse = 4;
+struct iter_out {
+ int idx;
+ __u64 cookie;
+} __packed;
+
static void do_test(int sock_type, bool onebyone)
{
int err, i, nread, to_read, total_read, iter_fd = -1;
- int first_idx, second_idx, indices[nr_soreuse];
+ struct iter_out outputs[nr_soreuse];
struct bpf_link *link = NULL;
struct sock_iter_batch *skel;
+ int first_idx, second_idx;
int *fds[2] = {};
skel = sock_iter_batch__open();
@@ -34,6 +40,7 @@ static void do_test(int sock_type, bool onebyone)
goto done;
skel->rodata->ports[i] = ntohs(local_port);
}
+ skel->rodata->sf = AF_INET6;
err = sock_iter_batch__load(skel);
if (!ASSERT_OK(err, "sock_iter_batch__load"))
@@ -55,38 +62,38 @@ static void do_test(int sock_type, bool onebyone)
* from a bucket and leave one socket out from
* that bucket on purpose.
*/
- to_read = (nr_soreuse - 1) * sizeof(*indices);
+ to_read = (nr_soreuse - 1) * sizeof(*outputs);
total_read = 0;
first_idx = -1;
do {
- nread = read(iter_fd, indices, onebyone ? sizeof(*indices) : to_read);
- if (nread <= 0 || nread % sizeof(*indices))
+ nread = read(iter_fd, outputs, onebyone ? sizeof(*outputs) : to_read);
+ if (nread <= 0 || nread % sizeof(*outputs))
break;
total_read += nread;
if (first_idx == -1)
- first_idx = indices[0];
- for (i = 0; i < nread / sizeof(*indices); i++)
- ASSERT_EQ(indices[i], first_idx, "first_idx");
+ first_idx = outputs[0].idx;
+ for (i = 0; i < nread / sizeof(*outputs); i++)
+ ASSERT_EQ(outputs[i].idx, first_idx, "first_idx");
} while (total_read < to_read);
- ASSERT_EQ(nread, onebyone ? sizeof(*indices) : to_read, "nread");
+ ASSERT_EQ(nread, onebyone ? sizeof(*outputs) : to_read, "nread");
ASSERT_EQ(total_read, to_read, "total_read");
free_fds(fds[first_idx], nr_soreuse);
fds[first_idx] = NULL;
/* Read the "whole" second bucket */
- to_read = nr_soreuse * sizeof(*indices);
+ to_read = nr_soreuse * sizeof(*outputs);
total_read = 0;
second_idx = !first_idx;
do {
- nread = read(iter_fd, indices, onebyone ? sizeof(*indices) : to_read);
- if (nread <= 0 || nread % sizeof(*indices))
+ nread = read(iter_fd, outputs, onebyone ? sizeof(*outputs) : to_read);
+ if (nread <= 0 || nread % sizeof(*outputs))
break;
total_read += nread;
- for (i = 0; i < nread / sizeof(*indices); i++)
- ASSERT_EQ(indices[i], second_idx, "second_idx");
+ for (i = 0; i < nread / sizeof(*outputs); i++)
+ ASSERT_EQ(outputs[i].idx, second_idx, "second_idx");
} while (total_read <= to_read);
ASSERT_EQ(nread, 0, "nread");
/* Both so_reuseport ports should be in different buckets, so
diff --git a/tools/testing/selftests/bpf/progs/bpf_tracing_net.h b/tools/testing/selftests/bpf/progs/bpf_tracing_net.h
index 659694162739..17db400f0e0d 100644
--- a/tools/testing/selftests/bpf/progs/bpf_tracing_net.h
+++ b/tools/testing/selftests/bpf/progs/bpf_tracing_net.h
@@ -128,6 +128,7 @@
#define sk_refcnt __sk_common.skc_refcnt
#define sk_state __sk_common.skc_state
#define sk_net __sk_common.skc_net
+#define sk_rcv_saddr __sk_common.skc_rcv_saddr
#define sk_v6_daddr __sk_common.skc_v6_daddr
#define sk_v6_rcv_saddr __sk_common.skc_v6_rcv_saddr
#define sk_flags __sk_common.skc_flags
diff --git a/tools/testing/selftests/bpf/progs/sock_iter_batch.c b/tools/testing/selftests/bpf/progs/sock_iter_batch.c
index 96531b0d9d55..8f483337e103 100644
--- a/tools/testing/selftests/bpf/progs/sock_iter_batch.c
+++ b/tools/testing/selftests/bpf/progs/sock_iter_batch.c
@@ -17,6 +17,12 @@ static bool ipv6_addr_loopback(const struct in6_addr *a)
a->s6_addr32[2] | (a->s6_addr32[3] ^ bpf_htonl(1))) == 0;
}
+static bool ipv4_addr_loopback(__be32 a)
+{
+ return a == bpf_ntohl(0x7f000001);
+}
+
+volatile const unsigned int sf;
volatile const __u16 ports[2];
unsigned int bucket[2];
@@ -26,16 +32,20 @@ int iter_tcp_soreuse(struct bpf_iter__tcp *ctx)
struct sock *sk = (struct sock *)ctx->sk_common;
struct inet_hashinfo *hinfo;
unsigned int hash;
+ __u64 sock_cookie;
struct net *net;
int idx;
if (!sk)
return 0;
+ sock_cookie = bpf_get_socket_cookie(sk);
sk = bpf_core_cast(sk, struct sock);
- if (sk->sk_family != AF_INET6 ||
+ if (sk->sk_family != sf ||
sk->sk_state != TCP_LISTEN ||
- !ipv6_addr_loopback(&sk->sk_v6_rcv_saddr))
+ sk->sk_family == AF_INET6 ?
+ !ipv6_addr_loopback(&sk->sk_v6_rcv_saddr) :
+ !ipv4_addr_loopback(sk->sk_rcv_saddr))
return 0;
if (sk->sk_num == ports[0])
@@ -52,6 +62,7 @@ int iter_tcp_soreuse(struct bpf_iter__tcp *ctx)
hinfo = net->ipv4.tcp_death_row.hashinfo;
bucket[idx] = hash & hinfo->lhash2_mask;
bpf_seq_write(ctx->meta->seq, &idx, sizeof(idx));
+ bpf_seq_write(ctx->meta->seq, &sock_cookie, sizeof(sock_cookie));
return 0;
}
@@ -63,14 +74,18 @@ int iter_udp_soreuse(struct bpf_iter__udp *ctx)
{
struct sock *sk = (struct sock *)ctx->udp_sk;
struct udp_table *udptable;
+ __u64 sock_cookie;
int idx;
if (!sk)
return 0;
+ sock_cookie = bpf_get_socket_cookie(sk);
sk = bpf_core_cast(sk, struct sock);
- if (sk->sk_family != AF_INET6 ||
- !ipv6_addr_loopback(&sk->sk_v6_rcv_saddr))
+ if (sk->sk_family != sf ||
+ sk->sk_family == AF_INET6 ?
+ !ipv6_addr_loopback(&sk->sk_v6_rcv_saddr) :
+ !ipv4_addr_loopback(sk->sk_rcv_saddr))
return 0;
if (sk->sk_num == ports[0])
@@ -84,6 +99,7 @@ int iter_udp_soreuse(struct bpf_iter__udp *ctx)
udptable = sk->sk_net.net->ipv4.udp_table;
bucket[idx] = udp_sk(sk)->udp_portaddr_hash & udptable->mask;
bpf_seq_write(ctx->meta->seq, &idx, sizeof(idx));
+ bpf_seq_write(ctx->meta->seq, &sock_cookie, sizeof(sock_cookie));
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 bpf-next 7/7] selftests/bpf: Add tests for bucket resume logic in UDP socket iterators
2025-05-02 16:15 [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration Jordan Rife
` (5 preceding siblings ...)
2025-05-02 16:15 ` [PATCH v7 bpf-next 6/7] selftests/bpf: Return socket cookies from sock_iter_batch progs Jordan Rife
@ 2025-05-02 16:15 ` Jordan Rife
2025-05-02 21:20 ` [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration patchwork-bot+netdevbpf
7 siblings, 0 replies; 13+ messages in thread
From: Jordan Rife @ 2025-05-02 16:15 UTC (permalink / raw)
To: netdev, bpf
Cc: Jordan Rife, Aditi Ghag, Daniel Borkmann, Martin KaFai Lau,
Willem de Bruijn, Kuniyuki Iwashima, Alexei Starovoitov
Introduce a set of tests that exercise various bucket resume scenarios:
* remove_seen resumes iteration after removing a socket from the bucket
that we've already processed. Before, with the offset-based approach,
this test would have skipped an unseen socket after resuming
iteration. With the cookie-based approach, we now see all sockets
exactly once.
* remove_unseen exercises the condition where the next socket that we
would have seen is removed from the bucket before we resume iteration.
This tests the scenario where we need to scan past the first cookie in
our remembered cookies list to find the socket from which to resume
iteration.
* remove_all exercises the condition where all sockets we remembered
were removed from the bucket to make sure iteration terminates and
returns no more results.
* add_some exercises the condition where a few, but not enough to
trigger a realloc, sockets are added to the head of the current bucket
between reads. Before, with the offset-based approach, this test would
have repeated sockets we've already seen. With the cookie-based
approach, we now see all sockets exactly once.
* force_realloc exercises the condition that we need to realloc the
batch on a subsequent read, since more sockets than can be held in the
current batch array were added to the current bucket. This exercies
the logic inside bpf_iter_udp_realloc_batch that copies cookies into
the new batch to make sure nothing is skipped or repeated.
Signed-off-by: Jordan Rife <jordan@jrife.io>
---
.../bpf/prog_tests/sock_iter_batch.c | 414 ++++++++++++++++++
1 file changed, 414 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/sock_iter_batch.c b/tools/testing/selftests/bpf/prog_tests/sock_iter_batch.c
index 74dbe91806a0..a4517bee34d5 100644
--- a/tools/testing/selftests/bpf/prog_tests/sock_iter_batch.c
+++ b/tools/testing/selftests/bpf/prog_tests/sock_iter_batch.c
@@ -7,6 +7,7 @@
#define TEST_NS "sock_iter_batch_netns"
+static const int init_batch_size = 16;
static const int nr_soreuse = 4;
struct iter_out {
@@ -14,6 +15,418 @@ struct iter_out {
__u64 cookie;
} __packed;
+struct sock_count {
+ __u64 cookie;
+ int count;
+};
+
+static int insert(__u64 cookie, struct sock_count counts[], int counts_len)
+{
+ int insert = -1;
+ int i = 0;
+
+ for (; i < counts_len; i++) {
+ if (!counts[i].cookie) {
+ insert = i;
+ } else if (counts[i].cookie == cookie) {
+ insert = i;
+ break;
+ }
+ }
+ if (insert < 0)
+ return insert;
+
+ counts[insert].cookie = cookie;
+ counts[insert].count++;
+
+ return counts[insert].count;
+}
+
+static int read_n(int iter_fd, int n, struct sock_count counts[],
+ int counts_len)
+{
+ struct iter_out out;
+ int nread = 1;
+ int i = 0;
+
+ for (; nread > 0 && (n < 0 || i < n); i++) {
+ nread = read(iter_fd, &out, sizeof(out));
+ if (!nread || !ASSERT_EQ(nread, sizeof(out), "nread"))
+ break;
+ ASSERT_GE(insert(out.cookie, counts, counts_len), 0, "insert");
+ }
+
+ ASSERT_TRUE(n < 0 || i == n, "n < 0 || i == n");
+
+ return i;
+}
+
+static __u64 socket_cookie(int fd)
+{
+ __u64 cookie;
+ socklen_t cookie_len = sizeof(cookie);
+
+ if (!ASSERT_OK(getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie,
+ &cookie_len), "getsockopt(SO_COOKIE)"))
+ return 0;
+ return cookie;
+}
+
+static bool was_seen(int fd, struct sock_count counts[], int counts_len)
+{
+ __u64 cookie = socket_cookie(fd);
+ int i = 0;
+
+ for (; cookie && i < counts_len; i++)
+ if (cookie == counts[i].cookie)
+ return true;
+
+ return false;
+}
+
+static int get_seen_socket(int *fds, struct sock_count counts[], int n)
+{
+ int i = 0;
+
+ for (; i < n; i++)
+ if (was_seen(fds[i], counts, n))
+ return i;
+ return -1;
+}
+
+static int get_nth_socket(int *fds, int fds_len, struct bpf_link *link, int n)
+{
+ int i, nread, iter_fd;
+ int nth_sock_idx = -1;
+ struct iter_out out;
+
+ iter_fd = bpf_iter_create(bpf_link__fd(link));
+ if (!ASSERT_OK_FD(iter_fd, "bpf_iter_create"))
+ return -1;
+
+ for (; n >= 0; n--) {
+ nread = read(iter_fd, &out, sizeof(out));
+ if (!nread || !ASSERT_GE(nread, 1, "nread"))
+ goto done;
+ }
+
+ for (i = 0; i < fds_len && nth_sock_idx < 0; i++)
+ if (fds[i] >= 0 && socket_cookie(fds[i]) == out.cookie)
+ nth_sock_idx = i;
+done:
+ close(iter_fd);
+ return nth_sock_idx;
+}
+
+static int get_seen_count(int fd, struct sock_count counts[], int n)
+{
+ __u64 cookie = socket_cookie(fd);
+ int count = 0;
+ int i = 0;
+
+ for (; cookie && !count && i < n; i++)
+ if (cookie == counts[i].cookie)
+ count = counts[i].count;
+
+ return count;
+}
+
+static void check_n_were_seen_once(int *fds, int fds_len, int n,
+ struct sock_count counts[], int counts_len)
+{
+ int seen_once = 0;
+ int seen_cnt;
+ int i = 0;
+
+ for (; i < fds_len; i++) {
+ /* Skip any sockets that were closed or that weren't seen
+ * exactly once.
+ */
+ if (fds[i] < 0)
+ continue;
+ seen_cnt = get_seen_count(fds[i], counts, counts_len);
+ if (seen_cnt && ASSERT_EQ(seen_cnt, 1, "seen_cnt"))
+ seen_once++;
+ }
+
+ ASSERT_EQ(seen_once, n, "seen_once");
+}
+
+static void remove_seen(int family, int sock_type, const char *addr, __u16 port,
+ int *socks, int socks_len, struct sock_count *counts,
+ int counts_len, struct bpf_link *link, int iter_fd)
+{
+ int close_idx;
+
+ /* Iterate through the first socks_len - 1 sockets. */
+ read_n(iter_fd, socks_len - 1, counts, counts_len);
+
+ /* Make sure we saw socks_len - 1 sockets exactly once. */
+ check_n_were_seen_once(socks, socks_len, socks_len - 1, counts,
+ counts_len);
+
+ /* Close a socket we've already seen to remove it from the bucket. */
+ close_idx = get_seen_socket(socks, counts, counts_len);
+ if (!ASSERT_GE(close_idx, 0, "close_idx"))
+ return;
+ close(socks[close_idx]);
+ socks[close_idx] = -1;
+
+ /* Iterate through the rest of the sockets. */
+ read_n(iter_fd, -1, counts, counts_len);
+
+ /* Make sure the last socket wasn't skipped and that there were no
+ * repeats.
+ */
+ check_n_were_seen_once(socks, socks_len, socks_len - 1, counts,
+ counts_len);
+}
+
+static void remove_unseen(int family, int sock_type, const char *addr,
+ __u16 port, int *socks, int socks_len,
+ struct sock_count *counts, int counts_len,
+ struct bpf_link *link, int iter_fd)
+{
+ int close_idx;
+
+ /* Iterate through the first socket. */
+ read_n(iter_fd, 1, counts, counts_len);
+
+ /* Make sure we saw a socket from fds. */
+ check_n_were_seen_once(socks, socks_len, 1, counts, counts_len);
+
+ /* Close what would be the next socket in the bucket to exercise the
+ * condition where we need to skip past the first cookie we remembered.
+ */
+ close_idx = get_nth_socket(socks, socks_len, link, 1);
+ if (!ASSERT_GE(close_idx, 0, "close_idx"))
+ return;
+ close(socks[close_idx]);
+ socks[close_idx] = -1;
+
+ /* Iterate through the rest of the sockets. */
+ read_n(iter_fd, -1, counts, counts_len);
+
+ /* Make sure the remaining sockets were seen exactly once and that we
+ * didn't repeat the socket that was already seen.
+ */
+ check_n_were_seen_once(socks, socks_len, socks_len - 1, counts,
+ counts_len);
+}
+
+static void remove_all(int family, int sock_type, const char *addr,
+ __u16 port, int *socks, int socks_len,
+ struct sock_count *counts, int counts_len,
+ struct bpf_link *link, int iter_fd)
+{
+ int close_idx, i;
+
+ /* Iterate through the first socket. */
+ read_n(iter_fd, 1, counts, counts_len);
+
+ /* Make sure we saw a socket from fds. */
+ check_n_were_seen_once(socks, socks_len, 1, counts, counts_len);
+
+ /* Close all remaining sockets to exhaust the list of saved cookies and
+ * exit without putting any sockets into the batch on the next read.
+ */
+ for (i = 0; i < socks_len - 1; i++) {
+ close_idx = get_nth_socket(socks, socks_len, link, 1);
+ if (!ASSERT_GE(close_idx, 0, "close_idx"))
+ return;
+ close(socks[close_idx]);
+ socks[close_idx] = -1;
+ }
+
+ /* Make sure there are no more sockets returned */
+ ASSERT_EQ(read_n(iter_fd, -1, counts, counts_len), 0, "read_n");
+}
+
+static void add_some(int family, int sock_type, const char *addr, __u16 port,
+ int *socks, int socks_len, struct sock_count *counts,
+ int counts_len, struct bpf_link *link, int iter_fd)
+{
+ int *new_socks = NULL;
+
+ /* Iterate through the first socks_len - 1 sockets. */
+ read_n(iter_fd, socks_len - 1, counts, counts_len);
+
+ /* Make sure we saw socks_len - 1 sockets exactly once. */
+ check_n_were_seen_once(socks, socks_len, socks_len - 1, counts,
+ counts_len);
+
+ /* Double the number of sockets in the bucket. */
+ new_socks = start_reuseport_server(family, sock_type, addr, port, 0,
+ socks_len);
+ if (!ASSERT_OK_PTR(new_socks, "start_reuseport_server"))
+ goto done;
+
+ /* Iterate through the rest of the sockets. */
+ read_n(iter_fd, -1, counts, counts_len);
+
+ /* Make sure each of the original sockets was seen exactly once. */
+ check_n_were_seen_once(socks, socks_len, socks_len, counts,
+ counts_len);
+done:
+ free_fds(new_socks, socks_len);
+}
+
+static void force_realloc(int family, int sock_type, const char *addr,
+ __u16 port, int *socks, int socks_len,
+ struct sock_count *counts, int counts_len,
+ struct bpf_link *link, int iter_fd)
+{
+ int *new_socks = NULL;
+
+ /* Iterate through the first socket just to initialize the batch. */
+ read_n(iter_fd, 1, counts, counts_len);
+
+ /* Double the number of sockets in the bucket to force a realloc on the
+ * next read.
+ */
+ new_socks = start_reuseport_server(family, sock_type, addr, port, 0,
+ socks_len);
+ if (!ASSERT_OK_PTR(new_socks, "start_reuseport_server"))
+ goto done;
+
+ /* Iterate through the rest of the sockets. */
+ read_n(iter_fd, -1, counts, counts_len);
+
+ /* Make sure each socket from the first set was seen exactly once. */
+ check_n_were_seen_once(socks, socks_len, socks_len, counts,
+ counts_len);
+done:
+ free_fds(new_socks, socks_len);
+}
+
+struct test_case {
+ void (*test)(int family, int sock_type, const char *addr, __u16 port,
+ int *socks, int socks_len, struct sock_count *counts,
+ int counts_len, struct bpf_link *link, int iter_fd);
+ const char *description;
+ int init_socks;
+ int max_socks;
+ int sock_type;
+ int family;
+};
+
+static struct test_case resume_tests[] = {
+ {
+ .description = "udp: resume after removing a seen socket",
+ .init_socks = nr_soreuse,
+ .max_socks = nr_soreuse,
+ .sock_type = SOCK_DGRAM,
+ .family = AF_INET6,
+ .test = remove_seen,
+ },
+ {
+ .description = "udp: resume after removing one unseen socket",
+ .init_socks = nr_soreuse,
+ .max_socks = nr_soreuse,
+ .sock_type = SOCK_DGRAM,
+ .family = AF_INET6,
+ .test = remove_unseen,
+ },
+ {
+ .description = "udp: resume after removing all unseen sockets",
+ .init_socks = nr_soreuse,
+ .max_socks = nr_soreuse,
+ .sock_type = SOCK_DGRAM,
+ .family = AF_INET6,
+ .test = remove_all,
+ },
+ {
+ .description = "udp: resume after adding a few sockets",
+ .init_socks = nr_soreuse,
+ .max_socks = nr_soreuse,
+ .sock_type = SOCK_DGRAM,
+ /* Use AF_INET so that new sockets are added to the head of the
+ * bucket's list.
+ */
+ .family = AF_INET,
+ .test = add_some,
+ },
+ {
+ .description = "udp: force a realloc to occur",
+ .init_socks = init_batch_size,
+ .max_socks = init_batch_size * 2,
+ .sock_type = SOCK_DGRAM,
+ /* Use AF_INET6 so that new sockets are added to the tail of the
+ * bucket's list, needing to be added to the next batch to force
+ * a realloc.
+ */
+ .family = AF_INET6,
+ .test = force_realloc,
+ },
+};
+
+static void do_resume_test(struct test_case *tc)
+{
+ struct sock_iter_batch *skel = NULL;
+ static const __u16 port = 10001;
+ struct bpf_link *link = NULL;
+ struct sock_count *counts;
+ int err, iter_fd = -1;
+ const char *addr;
+ int *fds = NULL;
+ int local_port;
+
+ counts = calloc(tc->max_socks, sizeof(*counts));
+ if (!ASSERT_OK_PTR(counts, "counts"))
+ goto done;
+ skel = sock_iter_batch__open();
+ if (!ASSERT_OK_PTR(skel, "sock_iter_batch__open"))
+ goto done;
+
+ /* Prepare a bucket of sockets in the kernel hashtable */
+ addr = tc->family == AF_INET6 ? "::1" : "127.0.0.1";
+ fds = start_reuseport_server(tc->family, tc->sock_type, addr, port, 0,
+ tc->init_socks);
+ if (!ASSERT_OK_PTR(fds, "start_reuseport_server"))
+ goto done;
+ local_port = get_socket_local_port(*fds);
+ if (!ASSERT_GE(local_port, 0, "get_socket_local_port"))
+ goto done;
+ skel->rodata->ports[0] = ntohs(local_port);
+ skel->rodata->sf = tc->family;
+
+ err = sock_iter_batch__load(skel);
+ if (!ASSERT_OK(err, "sock_iter_batch__load"))
+ goto done;
+
+ link = bpf_program__attach_iter(tc->sock_type == SOCK_STREAM ?
+ skel->progs.iter_tcp_soreuse :
+ skel->progs.iter_udp_soreuse,
+ NULL);
+ if (!ASSERT_OK_PTR(link, "bpf_program__attach_iter"))
+ goto done;
+
+ iter_fd = bpf_iter_create(bpf_link__fd(link));
+ if (!ASSERT_OK_FD(iter_fd, "bpf_iter_create"))
+ goto done;
+
+ tc->test(tc->family, tc->sock_type, addr, port, fds, tc->init_socks,
+ counts, tc->max_socks, link, iter_fd);
+done:
+ free(counts);
+ free_fds(fds, tc->init_socks);
+ if (iter_fd >= 0)
+ close(iter_fd);
+ bpf_link__destroy(link);
+ sock_iter_batch__destroy(skel);
+}
+
+static void do_resume_tests(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(resume_tests); i++) {
+ if (test__start_subtest(resume_tests[i].description)) {
+ do_resume_test(&resume_tests[i]);
+ }
+ }
+}
+
static void do_test(int sock_type, bool onebyone)
{
int err, i, nread, to_read, total_read, iter_fd = -1;
@@ -135,6 +548,7 @@ void test_sock_iter_batch(void)
do_test(SOCK_DGRAM, true);
do_test(SOCK_DGRAM, false);
}
+ do_resume_tests();
close_netns(nstoken);
done:
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration
2025-05-02 16:15 [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration Jordan Rife
` (6 preceding siblings ...)
2025-05-02 16:15 ` [PATCH v7 bpf-next 7/7] selftests/bpf: Add tests for bucket resume logic in UDP socket iterators Jordan Rife
@ 2025-05-02 21:20 ` patchwork-bot+netdevbpf
7 siblings, 0 replies; 13+ messages in thread
From: patchwork-bot+netdevbpf @ 2025-05-02 21:20 UTC (permalink / raw)
To: Jordan Rife
Cc: netdev, bpf, aditi.ghag, daniel, martin.lau,
willemdebruijn.kernel, kuniyu, alexei.starovoitov
Hello:
This series was applied to bpf/bpf-next.git (net)
by Martin KaFai Lau <martin.lau@kernel.org>:
On Fri, 2 May 2025 09:15:19 -0700 you wrote:
> Both UDP and TCP socket iterators use iter->offset to track progress
> through a bucket, which is a measure of the number of matching sockets
> from the current bucket that have been seen or processed by the
> iterator. On subsequent iterations, if the current bucket has
> unprocessed items, we skip at least iter->offset matching items in the
> bucket before adding any remaining items to the next batch. However,
> iter->offset isn't always an accurate measure of "things already seen"
> when the underlying bucket changes between reads which can lead to
> repeated or skipped sockets. Instead, this series remembers the cookies
> of the sockets we haven't seen yet in the current bucket and resumes
> from the first cookie in that list that we can find on the next
> iteration. This series focuses on UDP socket iterators, but a later
> series will apply a similar approach to TCP socket iterators.
>
> [...]
Here is the summary with links:
- [v7,bpf-next,1/7] bpf: udp: Make mem flags configurable through bpf_iter_udp_realloc_batch
(no matching commit)
- [v7,bpf-next,2/7] bpf: udp: Make sure iter->batch always contains a full bucket snapshot
https://git.kernel.org/bpf/bpf-next/c/66d454e99d71
- [v7,bpf-next,3/7] bpf: udp: Get rid of st_bucket_done
https://git.kernel.org/bpf/bpf-next/c/3fae8959cda5
- [v7,bpf-next,4/7] bpf: udp: Use bpf_udp_iter_batch_item for bpf_udp_iter_state batch items
(no matching commit)
- [v7,bpf-next,5/7] bpf: udp: Avoid socket skips and repeats during iteration
(no matching commit)
- [v7,bpf-next,6/7] selftests/bpf: Return socket cookies from sock_iter_batch progs
https://git.kernel.org/bpf/bpf-next/c/4a0614e18c2d
- [v7,bpf-next,7/7] selftests/bpf: Add tests for bucket resume logic in UDP socket iterators
https://git.kernel.org/bpf/bpf-next/c/c58dcc1dbe30
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 bpf-next 4/7] bpf: udp: Use bpf_udp_iter_batch_item for bpf_udp_iter_state batch items
2025-05-02 16:15 ` [PATCH v7 bpf-next 4/7] bpf: udp: Use bpf_udp_iter_batch_item for bpf_udp_iter_state batch items Jordan Rife
@ 2025-05-02 21:22 ` Martin KaFai Lau
2025-05-02 21:31 ` Jordan Rife
0 siblings, 1 reply; 13+ messages in thread
From: Martin KaFai Lau @ 2025-05-02 21:22 UTC (permalink / raw)
To: Jordan Rife
Cc: Aditi Ghag, Daniel Borkmann, Willem de Bruijn, Kuniyuki Iwashima,
Alexei Starovoitov, bpf, netdev
On 5/2/25 9:15 AM, Jordan Rife wrote:
> @@ -3596,8 +3600,8 @@ static void bpf_iter_udp_put_batch(struct bpf_udp_iter_state *iter)
> {
> unsigned int cur_sk = iter->cur_sk;
>
> - while (cur_sk < iter->end_sk)
> - sock_put(iter->batch[cur_sk++]);
> + while (iter->cur_sk < iter->end_sk)
I fixed this to "while (cur_sk < iter->end_sk)". Not that matters since the next
patch 5 fixed itself but it is better to keep this patch clean.
> + sock_put(iter->batch[cur_sk++].sk);
> }
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 bpf-next 5/7] bpf: udp: Avoid socket skips and repeats during iteration
2025-05-02 16:15 ` [PATCH v7 bpf-next 5/7] bpf: udp: Avoid socket skips and repeats during iteration Jordan Rife
@ 2025-05-02 21:29 ` Martin KaFai Lau
0 siblings, 0 replies; 13+ messages in thread
From: Martin KaFai Lau @ 2025-05-02 21:29 UTC (permalink / raw)
To: Jordan Rife
Cc: Aditi Ghag, Daniel Borkmann, Willem de Bruijn, Kuniyuki Iwashima,
Alexei Starovoitov, netdev, bpf
On 5/2/25 9:15 AM, Jordan Rife wrote:
> static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
> {
> struct bpf_udp_iter_state *iter = seq->private;
> struct udp_iter_state *state = &iter->state;
> + unsigned int find_cookie, end_cookie = 0;
A nit. I removed the zero initialization in the "end_cookie". Like
"find_cookie", both of them will be initialized by iter->cur_sk and iter->end_sk
later.
Applied. Thanks.
> struct net *net = seq_file_net(seq);
> - int resume_bucket, resume_offset;
> struct udp_table *udptable;
> unsigned int batch_sks = 0;
> + int resume_bucket;
> int resizes = 0;
> struct sock *sk;
> int err = 0;
>
> resume_bucket = state->bucket;
> - resume_offset = iter->offset;
>
> /* The current batch is done, so advance the bucket. */
> if (iter->cur_sk == iter->end_sk)
> @@ -3434,6 +3452,8 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
> * before releasing the bucket lock. This allows BPF programs that are
> * called in seq_show to acquire the bucket lock if needed.
> */
> + find_cookie = iter->cur_sk;
> + end_cookie = iter->end_sk;
> iter->cur_sk = 0;
> iter->end_sk = 0;
> batch_sks = 0;
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 bpf-next 4/7] bpf: udp: Use bpf_udp_iter_batch_item for bpf_udp_iter_state batch items
2025-05-02 21:22 ` Martin KaFai Lau
@ 2025-05-02 21:31 ` Jordan Rife
0 siblings, 0 replies; 13+ messages in thread
From: Jordan Rife @ 2025-05-02 21:31 UTC (permalink / raw)
To: Martin KaFai Lau
Cc: Aditi Ghag, Daniel Borkmann, Willem de Bruijn, Kuniyuki Iwashima,
Alexei Starovoitov, bpf, netdev
> I fixed this to "while (cur_sk < iter->end_sk)". Not that matters since the next
patch 5 fixed itself but it is better to keep this patch clean.
Agh, missed this while shifting things around.
Thanks for patching it up!
Jordan
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 bpf-next 1/7] bpf: udp: Make mem flags configurable through bpf_iter_udp_realloc_batch
2025-05-02 16:15 ` [PATCH v7 bpf-next 1/7] bpf: udp: Make mem flags configurable through bpf_iter_udp_realloc_batch Jordan Rife
@ 2025-05-02 21:31 ` Martin KaFai Lau
0 siblings, 0 replies; 13+ messages in thread
From: Martin KaFai Lau @ 2025-05-02 21:31 UTC (permalink / raw)
To: Jordan Rife
Cc: Aditi Ghag, Daniel Borkmann, Willem de Bruijn, Kuniyuki Iwashima,
Alexei Starovoitov, netdev, bpf
On 5/2/25 9:15 AM, Jordan Rife wrote:
> Prepare for the next patch which needs to be able to choose either
> GFP_USER or GFP_NOWAIT for calls to bpf_iter_udp_realloc_batch.
>
> Signed-off-by: Jordan Rife <jordan@jrife.io>
> Reviewed-by: Kuniyuki Iwashima <kuniyu@amazon.com>
> ---
> net/ipv4/udp.c | 11 ++++++-----
> 1 file changed, 6 insertions(+), 5 deletions(-)
>
> diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
> index 2742cc7602bb..6a3c351aa06e 100644
> --- a/net/ipv4/udp.c
> +++ b/net/ipv4/udp.c
> @@ -3401,7 +3401,7 @@ struct bpf_udp_iter_state {
> };
>
> static int bpf_iter_udp_realloc_batch(struct bpf_udp_iter_state *iter,
> - unsigned int new_batch_sz);
> + unsigned int new_batch_sz, int flags);
I also made a minor change from "int flags" to "gfp_t flags".
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2025-05-02 21:31 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-05-02 16:15 [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 1/7] bpf: udp: Make mem flags configurable through bpf_iter_udp_realloc_batch Jordan Rife
2025-05-02 21:31 ` Martin KaFai Lau
2025-05-02 16:15 ` [PATCH v7 bpf-next 2/7] bpf: udp: Make sure iter->batch always contains a full bucket snapshot Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 3/7] bpf: udp: Get rid of st_bucket_done Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 4/7] bpf: udp: Use bpf_udp_iter_batch_item for bpf_udp_iter_state batch items Jordan Rife
2025-05-02 21:22 ` Martin KaFai Lau
2025-05-02 21:31 ` Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 5/7] bpf: udp: Avoid socket skips and repeats during iteration Jordan Rife
2025-05-02 21:29 ` Martin KaFai Lau
2025-05-02 16:15 ` [PATCH v7 bpf-next 6/7] selftests/bpf: Return socket cookies from sock_iter_batch progs Jordan Rife
2025-05-02 16:15 ` [PATCH v7 bpf-next 7/7] selftests/bpf: Add tests for bucket resume logic in UDP socket iterators Jordan Rife
2025-05-02 21:20 ` [PATCH v7 bpf-next 0/7] bpf: udp: Exactly-once socket iteration patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).