mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v3] tcp: fix device binding after failed IPv6 connect
@ 2026-11-11 11:34 Xia Yan
  2026-11-12 19:05 ` netdev-bot+sashiko
  0 siblings, 1 reply; 2+ messages in thread
From: Xia Yan @ 2026-11-11 11:34 UTC (permalink / raw)
  To: netdev
  Cc: edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms,
	torvalds, linux-kernel, stable, yana06070, netdev-bot+sashiko,
	jiayuan.chen

tcp_v6_connect() sets sk_bound_dev_if from sin6_scope_id when connecting
to a link-local address. If the route lookup fails, the error path
leaves that binding in place. Retrying against ::1 on the same socket
can then fail with ENETUNREACH, although a fresh socket can connect.

Save the original binding and restore it unconditionally on native
IPv6 errors returned by tcp_v6_connect(). Restore it before
tcp_set_state(TCP_CLOSE) and inet_bhash2_reset_saddr(), which uses the
binding to select the l3mdev for the bind2 bucket. Do not restore it
again after the TCP_CLOSE callback. Keep IPv4-mapped errors on their
existing cleanup path.

Found during LLM-assisted review of the tcp_v6_connect() error paths.
Reproduced in UML by connecting to fe80::1 through a down dummy
interface, then retrying against a listener on ::1 on the same socket.
With the patch, the retry succeeds. The VRF cleanup tests also pass
without the inet_csk_get_port() warning seen on the baseline. Tests
also covered existing bindings and BPF sock_ops callbacks. These are
custom userspace tests; the networking selftest suite was not run.

Deferred Fast Open failures after connect() returns success are
unchanged.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Suggested-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Link: https://lore.kernel.org/netdev/062d02ca-e9e1-4f4e-ac57-0de27272a205@linux.dev/
Link: https://lore.kernel.org/netdev/20261110100224.57cbf548@kernel.org/
Signed-off-by: Xia Yan <yanxia@cmss.chinamobile.com>
---
Changes in v3:
- Restore old_bound_dev_if without the scope_id_set and current-value
  checks, as Jakub requested.
- Keep the early restore before TCP_CLOSE and bind2 cleanup, and
  avoid restoring again after the close callback.
- Leave IPv4-mapped failures on the existing cleanup path.
- Use WRITE_ONCE() when installing the scope binding.

v2: https://lore.kernel.org/netdev/20261102143352.135769-1-yanxia@cmss.chinamobile.com/
v1: https://lore.kernel.org/netdev/20261031074654.73952-1-yanxia@cmss.chinamobile.com/

Testing on net d2809ccf3cfc:
- UML: reconnect and existing-binding checks, two VRF cleanup cases,
  and five BPF sock_ops cases. The baseline reproduces the stale
  binding and VRF warning; v3 completes without kernel warnings.
  Native CONNECT callback bindings are rolled back on failure;
  CLOSE callback bindings and IPv4-mapped behavior are preserved.
- x86_64, GCC 12.3.1, W=1: completed kernel and module builds with
  configurations derived from allmodconfig and allyesconfig.
  WERROR, KASAN and KCSAN were disabled; allyes also had
  KUNIT_ALL_TESTS disabled, with the resulting dependency changes.
  tcp_ipv6.o compiled without diagnostics in both configurations.

 net/ipv6/tcp_ipv6.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index b1dec5d86d633db67a754c70ee1c63290a9acb5a..641b74aaf1963e71bb59e5542899e30e33b82a1e 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -134,6 +134,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 			  int addr_len)
 {
 	struct sockaddr_in6 *usin = (struct sockaddr_in6 *) uaddr;
+	int old_bound_dev_if = READ_ONCE(sk->sk_bound_dev_if);
 	struct inet_connection_sock *icsk = inet_csk(sk);
 	struct inet_timewait_death_row *tcp_death_row;
 	struct ipv6_pinfo *np = tcp_inet6_sk(sk);
@@ -197,7 +198,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 			if (!sk_dev_equal_l3scope(sk, usin->sin6_scope_id))
 				return -EINVAL;
 
-			sk->sk_bound_dev_if = usin->sin6_scope_id;
+			WRITE_ONCE(sk->sk_bound_dev_if, usin->sin6_scope_id);
 		}
 
 		/* Connect to link-local address requires an interface */
@@ -251,7 +252,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 #if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
 			tp->af_specific = &tcp_sock_ipv6_specific;
 #endif
-			goto failure;
+			goto out;
 		}
 		np->saddr = sk->sk_v6_rcv_saddr;
 
@@ -353,9 +354,14 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
 	return 0;
 
 late_failure:
+	WRITE_ONCE(sk->sk_bound_dev_if, old_bound_dev_if);
 	tcp_set_state(sk, TCP_CLOSE);
 	inet_bhash2_reset_saddr(sk);
+	goto out;
+
 failure:
+	WRITE_ONCE(sk->sk_bound_dev_if, old_bound_dev_if);
+out:
 	inet->inet_dport = 0;
 	sk->sk_route_caps = 0;
 	return err;

base-commit: d2809ccf3cfc3c30581dc64ebec5f49f7ee1e228



^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-11-12 19:05 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 11:34 [PATCH net v3] tcp: fix device binding after failed IPv6 connect Xia Yan
2026-11-12 19:05 ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®