diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index b55d036c7f4dac..559cd0aa851a60 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -325,6 +325,30 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr, if (err) goto late_failure; + /* The route above was resolved before the ephemeral source port was + * bound: fl6->fl6_sport was 0 and FLOWI_FLAG_ANY_SPORT had + * rt6_multipath_hash() substitute a random source port, so under a + * port-based hash policy the next hop was picked for a port this + * connection does not use. Every later route rebuild goes through + * inet6_csk_route_socket(), which hashes the real source port, so the + * flow would move to a different next hop the first time the cached + * dst is invalidated. Re-resolve now that the port is final, as + * tcp_v4_connect() does via ip_route_newports(), so the next hop + * stored here is the one every later lookup selects. The source + * address is already final: the first lookup wrote it into fl6->saddr. + */ + if (fl6->fl6_sport != inet->inet_sport) { + fl6->fl6_sport = inet->inet_sport; + fl6->flowi6_flags &= ~FLOWI_FLAG_ANY_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, false, false); + } + if (likely(!tp->repair)) { union tcp_seq_and_ts_off st;