diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index 952bf49efbc6ad..7a6bce3865e0f6 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -315,6 +315,28 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr, sk_set_txhash(sk); + /* The route was resolved before fl6 was complete: the source address + * was unselected (::) and the source port unbound (0). Both are now + * final -- the earlier lookup selected the source (written back to + * fl6.saddr) and inet6_hash_connect() just bound the port. The IPv6 + * multipath hash uses the source port (policy=1) or the source + * address (policy=0), so the next hop picked from the incomplete + * tuple may differ from the connection's real one, and the flow would + * migrate on dst invalidation. Re-resolve with the bound port -- as + * tcp_v4_connect() does via ip_route_newports() -- to pin the flow to + * the correct path. Only repeat the lookup if the port changed. + */ + if (fl6.fl6_sport != inet->inet_sport) { + fl6.fl6_sport = inet->inet_sport; + security_sk_classify_flow(sk, flowi6_to_flowi_common(&fl6)); + dst = ip6_dst_lookup_flow(net, sk, &fl6, final_p); + if (IS_ERR(dst)) { + err = PTR_ERR(dst); + goto late_failure; + } + ip6_dst_store(sk, dst, NULL, NULL); + } + if (likely(!tp->repair)) { union tcp_seq_and_ts_off st;