* [PATCH net-next v1 0/2] Preserve UDP socket addresses on abort
@ 2026-02-24 19:04 Jordan Rife
2026-02-24 19:04 ` [PATCH net-next v1 1/2] udp: " Jordan Rife
2026-02-24 19:04 ` [PATCH net-next v1 2/2] selftests/bpf: Ensure dst addr/port are preserved after socket abort Jordan Rife
0 siblings, 2 replies; 5+ messages in thread
From: Jordan Rife @ 2026-02-24 19:04 UTC (permalink / raw)
To: netdev
Cc: Jordan Rife, bpf, Willem de Bruijn, Eric Dumazet, Daniel Borkmann,
Martin KaFai Lau, Stanislav Fomichev, Andrii Nakryiko,
Yusuke Suzuki
BPF cgroup/sock_release hooks can be useful for performing cleanup,
map maintenance, or other bookkeeping when sockets are released. Cilium
uses cgroup/sock_release hooks to do just that, cleaning up maps keyed
by socket destination addresses. This works fine for TCP and connected
UDP sockets when they're closed gracefully, but Yusuke reported that
this fails for connected UDP when sockets are aborted prior to closing
the socket. Cilium in particular actively destroys/aborts connected UDP
sockets, so this scenario can happen easily.
From Yusuke's testing results in [1]:
1. socket connects to "127.0.0.1:8000"
2. socket aborted - diag_destroy (tcp_abort/udp_abort)
3. close(sock_fd)
4. BPF_CGROUP_RUN_PROG_INET_SOCK_RELEASE(sk) runs my_sock_release()
__section("cgroup/sock_release")
int my_sock_release(struct bpf_sock *ctx)
{
...
/* For TCP, dst_ip4 == "127.0.0.1" but for connected UDP
* dst_ip4 == 0.
*/
...
}
Case Protocol IP Family Type Result
1 TCP IPv4 Regular close Cleaned up
2 TCP IPv4 Abort Cleaned up
3 TCP IPv6 Regular close Cleaned up
4 TCP IPv6 Abort Cleaned up
5 UDP (Connected) IPv4 Regular close Cleaned up
6 UDP (Connected) IPv4 Abort Not cleaned up
7 UDP (Connected) IPv6 Regular close Cleaned up
8 UDP (Connected) IPv6 Abort Not cleaned up
This patch aims to make the behavior consistent between TCP and
connected UDP by preserving the socket destination address and port
through to the sock_release hook.
Regarding my approach:
Currently, udp_abort() calls __udp_disconnect(), but this seems like
overkill in the case of a socket abort. __udp_disconnect() handles two
special cases:
1. When a socket binds to 0.0.0.0, connects, then disconnects using
AF_UNSPEC it needs to be rehashed as described in commit 303d0403b8c2
("udp: rehash on disconnect").
2. Avoids unhashing the socket in cases where it was bound to a specific
port.
This makes sense in the case of a graceful disconnect (AF_UNSPEC) where
the socket remains intact and may be used in the future, but it seemed
sufficient in the case of a socket abort to simply set sk_err so that
future operations on that socket fail and to unconditionally unhash the
socket if it is currently hashed, thus avoiding any of the field
manipulation that __udp_disconnect() would otherwise do.
[1]: https://github.com/cilium/cilium/issues/42649
Jordan Rife (2):
udp: Preserve UDP socket addresses on abort
selftests/bpf: Ensure dst addr/port are preserved after socket abort
net/ipv4/udp.c | 4 +-
.../bpf/prog_tests/sock_destroy_release.c | 135 ++++++++++++++++++
.../bpf/progs/sock_destroy_release.c | 59 ++++++++
3 files changed, 197 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/sock_destroy_release.c
create mode 100644 tools/testing/selftests/bpf/progs/sock_destroy_release.c
--
2.53.0.414.gf7e9f6c205-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net-next v1 1/2] udp: Preserve UDP socket addresses on abort
2026-02-24 19:04 [PATCH net-next v1 0/2] Preserve UDP socket addresses on abort Jordan Rife
@ 2026-02-24 19:04 ` Jordan Rife
2026-02-24 19:04 ` [PATCH net-next v1 2/2] selftests/bpf: Ensure dst addr/port are preserved after socket abort Jordan Rife
1 sibling, 0 replies; 5+ messages in thread
From: Jordan Rife @ 2026-02-24 19:04 UTC (permalink / raw)
To: netdev
Cc: Jordan Rife, bpf, Willem de Bruijn, Eric Dumazet, Daniel Borkmann,
Martin KaFai Lau, Stanislav Fomichev, Andrii Nakryiko,
Yusuke Suzuki
Currently, udp_abort() invokes __udp_disconnect() which clears out
socket fields like inet_daddr and inet_dport. This makes fields like
dst_ip4, dst_ip6, and dst_port useless inside cgroup/sock_release BPF
hooks following an abort on a connected UDP socket, since they'll always
equal zero. This differs from the behavior for TCP sockets where a
cgroup/sock_release hook will see the address and port that the socket
was connected to at the time it was aborted. This causes issues in
Cilium where a sock_release hook is used to perform map maintenance when
sockets are released, since Cilium actively destroys connected UDP
sockets in some cases.
Make the behavior consistent between TCP and UDP sockets by not clearing
out these fields in udp_abort(). Instead, just unhash the socket, mark
its state as TCP_CLOSE, and preserve the state of the other fields.
Fixes: f5836749c9c0 ("bpf: Add BPF_CGROUP_INET_SOCK_RELEASE hook")
Reported-by: Yusuke Suzuki <yusuke.suzuki@isovalent.com>
Signed-off-by: Jordan Rife <jrife@google.com>
---
net/ipv4/udp.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index b96e47f1c8a2..01a0a0fbcd56 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -3247,7 +3247,9 @@ int udp_abort(struct sock *sk, int err)
sk->sk_err = err;
sk_error_report(sk);
- __udp_disconnect(sk, 0);
+ sk->sk_state = TCP_CLOSE;
+ sk->sk_prot->unhash(sk);
+ sk_dst_reset(sk);
out:
if (!has_current_bpf_ctx())
--
2.53.0.414.gf7e9f6c205-goog
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH net-next v1 2/2] selftests/bpf: Ensure dst addr/port are preserved after socket abort
2026-02-24 19:04 [PATCH net-next v1 0/2] Preserve UDP socket addresses on abort Jordan Rife
2026-02-24 19:04 ` [PATCH net-next v1 1/2] udp: " Jordan Rife
@ 2026-02-24 19:04 ` Jordan Rife
2026-02-27 3:36 ` [net-next,v1,2/2] " Jakub Kicinski
1 sibling, 1 reply; 5+ messages in thread
From: Jordan Rife @ 2026-02-24 19:04 UTC (permalink / raw)
To: netdev
Cc: Jordan Rife, bpf, Willem de Bruijn, Eric Dumazet, Daniel Borkmann,
Martin KaFai Lau, Stanislav Fomichev, Andrii Nakryiko,
Yusuke Suzuki
Ensure that sock_release hooks see the original dst_ip4, dst_ip6, and
dst_port values for connected UDP and TCP sockets following a socket
abort.
Signed-off-by: Jordan Rife <jrife@google.com>
---
.../bpf/prog_tests/sock_destroy_release.c | 135 ++++++++++++++++++
.../bpf/progs/sock_destroy_release.c | 59 ++++++++
2 files changed, 194 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/sock_destroy_release.c
create mode 100644 tools/testing/selftests/bpf/progs/sock_destroy_release.c
diff --git a/tools/testing/selftests/bpf/prog_tests/sock_destroy_release.c b/tools/testing/selftests/bpf/prog_tests/sock_destroy_release.c
new file mode 100644
index 000000000000..9bd2be4d45a2
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/sock_destroy_release.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "network_helpers.h"
+#include "sock_destroy_release.skel.h"
+
+#define TEST_NS "sock_destroy_release_netns"
+
+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 void destroy(struct sock_destroy_release *skel, int fd, int sock_type)
+{
+ __u64 cookie = socket_cookie(fd);
+ struct bpf_link *link = NULL;
+ int iter_fd = -1;
+ int nread;
+ __u64 out;
+
+ skel->bss->abort_cookie = cookie;
+
+ link = bpf_program__attach_iter(sock_type == SOCK_STREAM ?
+ skel->progs.abort_tcp :
+ skel->progs.abort_udp, 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;
+
+ /* Delete matching socket. */
+ nread = read(iter_fd, &out, sizeof(out));
+ ASSERT_GE(nread, 0, "nread");
+ if (nread)
+ ASSERT_EQ(out, cookie, "cookie matches");
+done:
+ if (iter_fd >= 0)
+ close(iter_fd);
+ bpf_link__destroy(link);
+ close(fd);
+}
+
+static void do_test(struct sock_destroy_release *skel, int sock_type,
+ int family)
+{
+ const char *addr = family == AF_INET ? "127.0.0.1" : "::1";
+ int listen_fd = -1, connect_fd = -1;
+ static const int port = 10001;
+
+ listen_fd = start_server(family, sock_type, addr, port, 0);
+ if (!ASSERT_OK_FD(listen_fd, "start_server"))
+ goto cleanup;
+
+ connect_fd = connect_to_fd(listen_fd, 0);
+ if (!ASSERT_OK_FD(connect_fd, "connect_to_fd"))
+ goto cleanup;
+
+ memset(&skel->bss->sk, 0, sizeof(skel->bss->sk));
+ destroy(skel, connect_fd, sock_type);
+ if (!ASSERT_EQ(ntohs(skel->bss->sk.dst_port), port, "dst_port"))
+ goto cleanup;
+ if (family == AF_INET) {
+ if (!ASSERT_EQ(ntohl(skel->bss->sk.dst_ip4), 0x7f000001,
+ "dst_ip4"))
+ goto cleanup;
+ } else {
+ if (!ASSERT_EQ(skel->bss->sk.dst_ip6[0], 0, "dst_ip6[0]"))
+ goto cleanup;
+ if (!ASSERT_EQ(skel->bss->sk.dst_ip6[1], 0, "dst_ip6[1]"))
+ goto cleanup;
+ if (!ASSERT_EQ(skel->bss->sk.dst_ip6[2], 0, "dst_ip6[2]"))
+ goto cleanup;
+ if (!ASSERT_EQ(ntohl(skel->bss->sk.dst_ip6[3]), 0x1,
+ "dst_ip6[3]"))
+ goto cleanup;
+ }
+cleanup:
+ if (connect_fd >= 0)
+ close(connect_fd);
+ if (listen_fd >= 0)
+ close(listen_fd);
+}
+
+void test_sock_destroy_release(void)
+{
+ struct sock_destroy_release *skel = NULL;
+ struct nstoken *nstoken = NULL;
+ int cgroup_fd = -1;
+
+ skel = sock_destroy_release__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ goto done;
+
+ cgroup_fd = test__join_cgroup("/sock_destroy_release");
+ if (!ASSERT_OK_FD(cgroup_fd, "join_cgroup"))
+ goto done;
+
+ skel->links.sock_release = bpf_program__attach_cgroup(
+ skel->progs.sock_release, cgroup_fd);
+ if (!ASSERT_OK_PTR(skel->links.sock_release, "attach_cgroup"))
+ goto done;
+
+ SYS_NOFAIL("ip netns del " TEST_NS);
+ SYS(done, "ip netns add %s", TEST_NS);
+ SYS(done, "ip -net %s link set dev lo up", TEST_NS);
+
+ nstoken = open_netns(TEST_NS);
+ if (!ASSERT_OK_PTR(nstoken, "open_netns"))
+ goto done;
+
+ if (test__start_subtest("tcp")) {
+ do_test(skel, SOCK_STREAM, AF_INET);
+ do_test(skel, SOCK_STREAM, AF_INET6);
+ }
+ if (test__start_subtest("udp")) {
+ do_test(skel, SOCK_DGRAM, AF_INET);
+ do_test(skel, SOCK_DGRAM, AF_INET6);
+ }
+done:
+ if (nstoken)
+ close_netns(nstoken);
+ if (cgroup_fd >= 0)
+ close(cgroup_fd);
+ SYS_NOFAIL("ip netns del " TEST_NS);
+ sock_destroy_release__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/sock_destroy_release.c b/tools/testing/selftests/bpf/progs/sock_destroy_release.c
new file mode 100644
index 000000000000..add322ab1303
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/sock_destroy_release.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_endian.h>
+#include "bpf_kfuncs.h"
+
+volatile __u64 abort_cookie;
+
+void maybe_abort(struct sock_common *sk, struct seq_file *seq)
+{
+ __u64 sock_cookie;
+
+ if (!sk)
+ return;
+
+ sock_cookie = bpf_get_socket_cookie(sk);
+ if (sock_cookie != abort_cookie)
+ return;
+
+ bpf_sock_destroy(sk);
+ bpf_seq_write(seq, &sock_cookie, sizeof(sock_cookie));
+}
+
+SEC("iter/udp")
+int abort_udp(struct bpf_iter__udp *ctx)
+{
+ maybe_abort((struct sock_common *)ctx->udp_sk,
+ ctx->meta->seq);
+
+ return 0;
+}
+
+SEC("iter/tcp")
+int abort_tcp(struct bpf_iter__tcp *ctx)
+{
+ maybe_abort((struct sock_common *)ctx->sk_common,
+ ctx->meta->seq);
+
+ return 0;
+}
+
+struct bpf_sock sk = {};
+
+SEC("cgroup/sock_release")
+int sock_release(struct bpf_sock *ctx)
+{
+ sk.dst_ip4 = ctx->dst_ip4;
+ sk.dst_ip6[0] = ctx->dst_ip6[0];
+ sk.dst_ip6[1] = ctx->dst_ip6[1];
+ sk.dst_ip6[2] = ctx->dst_ip6[2];
+ sk.dst_ip6[3] = ctx->dst_ip6[3];
+ sk.dst_port = ctx->dst_port;
+
+ return 1;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.53.0.414.gf7e9f6c205-goog
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [net-next,v1,2/2] selftests/bpf: Ensure dst addr/port are preserved after socket abort
2026-02-24 19:04 ` [PATCH net-next v1 2/2] selftests/bpf: Ensure dst addr/port are preserved after socket abort Jordan Rife
@ 2026-02-27 3:36 ` Jakub Kicinski
2026-02-27 15:09 ` Jordan Rife
0 siblings, 1 reply; 5+ messages in thread
From: Jakub Kicinski @ 2026-02-27 3:36 UTC (permalink / raw)
To: jrife
Cc: Jakub Kicinski, edumazet, daniel, sdf, netdev, andrii, bpf,
yusuke.suzuki, willemdebruijn.kernel, martin.lau
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
selftests/bpf: Ensure dst addr/port are preserved after socket abort
Ensure that sock_release hooks see the original dst_ip4, dst_ip6, and
dst_port values for connected UDP and TCP sockets following a socket
abort.
> diff --git a/tools/testing/selftests/bpf/prog_tests/sock_destroy_release.c b/tools/testing/selftests/bpf/prog_tests/sock_destroy_release.c
> new file mode 100644
> index 000000000000..9bd2be4d45a2
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/sock_destroy_release.c
> @@ -0,0 +1,135 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <test_progs.h>
> +#include "network_helpers.h"
> +#include "sock_destroy_release.skel.h"
> +
> +#define TEST_NS "sock_destroy_release_netns"
> +
> +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 void destroy(struct sock_destroy_release *skel, int fd, int sock_type)
> +{
> + __u64 cookie = socket_cookie(fd);
> + struct bpf_link *link = NULL;
> + int iter_fd = -1;
> + int nread;
> + __u64 out;
> +
> + skel->bss->abort_cookie = cookie;
> +
> + link = bpf_program__attach_iter(sock_type == SOCK_STREAM ?
> + skel->progs.abort_tcp :
> + skel->progs.abort_udp, 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;
> +
> + /* Delete matching socket. */
> + nread = read(iter_fd, &out, sizeof(out));
> + ASSERT_GE(nread, 0, "nread");
> + if (nread)
> + ASSERT_EQ(out, cookie, "cookie matches");
> +done:
> + if (iter_fd >= 0)
> + close(iter_fd);
> + bpf_link__destroy(link);
> + close(fd);
> +}
> +
> +static void do_test(struct sock_destroy_release *skel, int sock_type,
> + int family)
> +{
> + const char *addr = family == AF_INET ? "127.0.0.1" : "::1";
> + int listen_fd = -1, connect_fd = -1;
> + static const int port = 10001;
> +
> + listen_fd = start_server(family, sock_type, addr, port, 0);
> + if (!ASSERT_OK_FD(listen_fd, "start_server"))
> + goto cleanup;
> +
> + connect_fd = connect_to_fd(listen_fd, 0);
> + if (!ASSERT_OK_FD(connect_fd, "connect_to_fd"))
> + goto cleanup;
> +
> + memset(&skel->bss->sk, 0, sizeof(skel->bss->sk));
> + destroy(skel, connect_fd, sock_type);
Does this cause a double close of connect_fd?
The destroy() function closes the fd parameter at line 49:
close(fd);
But do_test() doesn't update its local connect_fd variable after calling
destroy().
Later in the cleanup code:
> +cleanup:
> + if (connect_fd >= 0)
> + close(connect_fd);
connect_fd still holds the closed value and gets closed again.
While this usually results in a benign EBADF error, there's a small window
where another operation could create a new fd with the same number, and the
second close would incorrectly close the reused fd.
Should connect_fd be set to -1 after calling destroy() to prevent the double
close?
[ ... ]
> diff --git a/tools/testing/selftests/bpf/progs/sock_destroy_release.c b/tools/testing/selftests/bpf/progs/sock_destroy_release.c
> new file mode 100644
> index 000000000000..add322ab1303
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/sock_destroy_release.c
> @@ -0,0 +1,59 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include "vmlinux.h"
> +#include <bpf/bpf_helpers.h>
> +#include <bpf/bpf_core_read.h>
> +#include <bpf/bpf_endian.h>
> +#include "bpf_kfuncs.h"
> +
> +volatile __u64 abort_cookie;
> +
> +void maybe_abort(struct sock_common *sk, struct seq_file *seq)
> +{
> + __u64 sock_cookie;
> +
> + if (!sk)
> + return;
> +
> + sock_cookie = bpf_get_socket_cookie(sk);
> + if (sock_cookie != abort_cookie)
> + return;
> +
> + bpf_sock_destroy(sk);
> + bpf_seq_write(seq, &sock_cookie, sizeof(sock_cookie));
> +}
> +
> +SEC("iter/udp")
> +int abort_udp(struct bpf_iter__udp *ctx)
> +{
> + maybe_abort((struct sock_common *)ctx->udp_sk,
> + ctx->meta->seq);
> +
> + return 0;
> +}
> +
> +SEC("iter/tcp")
> +int abort_tcp(struct bpf_iter__tcp *ctx)
> +{
> + maybe_abort((struct sock_common *)ctx->sk_common,
> + ctx->meta->seq);
> +
> + return 0;
> +}
> +
> +struct bpf_sock sk = {};
> +
> +SEC("cgroup/sock_release")
> +int sock_release(struct bpf_sock *ctx)
> +{
> + sk.dst_ip4 = ctx->dst_ip4;
> + sk.dst_ip6[0] = ctx->dst_ip6[0];
> + sk.dst_ip6[1] = ctx->dst_ip6[1];
> + sk.dst_ip6[2] = ctx->dst_ip6[2];
> + sk.dst_ip6[3] = ctx->dst_ip6[3];
> + sk.dst_port = ctx->dst_port;
> +
> + return 1;
> +}
> +
> +char _license[] SEC("license") = "GPL";
--
pw-bot: cr
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [net-next,v1,2/2] selftests/bpf: Ensure dst addr/port are preserved after socket abort
2026-02-27 3:36 ` [net-next,v1,2/2] " Jakub Kicinski
@ 2026-02-27 15:09 ` Jordan Rife
0 siblings, 0 replies; 5+ messages in thread
From: Jordan Rife @ 2026-02-27 15:09 UTC (permalink / raw)
To: Jakub Kicinski
Cc: edumazet, daniel, sdf, netdev, andrii, bpf, yusuke.suzuki,
willemdebruijn.kernel, martin.lau
> Should connect_fd be set to -1 after calling destroy() to prevent the double
> close?
Yep, I'll update this in v2.
Thanks!
Jordan
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-02-27 15:09 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-24 19:04 [PATCH net-next v1 0/2] Preserve UDP socket addresses on abort Jordan Rife
2026-02-24 19:04 ` [PATCH net-next v1 1/2] udp: " Jordan Rife
2026-02-24 19:04 ` [PATCH net-next v1 2/2] selftests/bpf: Ensure dst addr/port are preserved after socket abort Jordan Rife
2026-02-27 3:36 ` [net-next,v1,2/2] " Jakub Kicinski
2026-02-27 15:09 ` Jordan Rife
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox