概述
以 tcp_v4_connect() 为例,一次 TCP 连接建立会触发 3 次路由查询,每次的目的和 flowi4 参数不同。这三次查询与 ECMP 多路径选路机制密切相关,参见 ECMP选路: fib_select_multipath Hash 机制深度解析。
三次查询对比
| 查询 | 触发函数 | saddr | sport | hash 特征 | 主要目的 |
|---|---|---|---|---|---|
| 第 1 次 | __ip_route_output_key() |
0 或已绑定 | 0(wildcard) | 随机 sport → 随机 hash | 获取源/目的地址 |
| 第 2 次 | ip_route_output_flow() |
已确定 | 0(wildcard) | 随机 sport → 随机 hash | 正式路由 + xfrm 策略 |
| 第 3 次 | ip_route_newports() |
已确定 | 真实端口 | 真实五元组 → 确定性 hash | 端口变更后重查 xfrm |
第 1 次查询:ip_route_connect() → __ip_route_output_key()
1 | /* include/net/route.h — ip_route_connect() */ |
此时 fl4 的关键状态:
saddr= 0(未绑定源地址时)或已绑定的源地址sport=orig_sport(通常为 0,即 wildcard)flow_flags=FLOWI_FLAG_ANY_SPORT(因为sport=0,由ip_route_connect_init()设置)
目的:获取源地址(
saddr)和目的地址(daddr),为后续端口分配提供地址信息。此次查询中fib_multipath_hash()因FLOWI_FLAG_ANY_SPORT会用随机源端口做 hash(L4 策略下),使新连接随机分散到不同 nexthop。查询完成后路由对象被释放,只保留地址。
第 2 次查询:ip_route_connect() → ip_route_output_flow()
1 | /* include/net/route.h — ip_route_connect() 末尾 */ |
此时 fl4 的关键状态(与第 1 次的区别):
saddr= 第 1 次查询得到的源地址(非零)sport=orig_sport(仍为 0)flow_flags= 仍含FLOWI_FLAG_ANY_SPORTflowi4_oif= 可能已被flowi4_update_output()回写
目的:用回填的地址做正式路由查找,并经过 xfrm(IPsec)策略 检查。如果配置了 IPsec,可能返回一条加密隧道路由。此次 hash 仍然带随机端口(sport 还是 0),可能选中与第 1 次不同的 nexthop。
第 3 次查询:ip_route_newports()
1 | /* tcp_v4_connect() 中,inet_hash_connect() 分配端口之后 */ |
此时 fl4 的关键状态(与前两次的区别):
saddr= 已确定的源地址sport=inet_hash_connect()分配的真实源端口(非零)dport= 目的端口flow_flags= 不再含FLOWI_FLAG_ANY_SPORT(因为 sport 非零)
目的:端口分配后用完整五元组重查路由,确保 IPsec 等基于端口的策略能正确匹配。此次 hash 用的是真实端口号,产生的 hash 值与前两次大概率不同。
问题:源地址不一致
三次查询的 hash 可能各不相同,导致选中不同的 nexthop。第 1 次选了 NH0(saddr=10.0.0.1),第 3 次可能选了 NH1(saddr=10.0.1.1),但 fl4->saddr 已经锁定为 10.0.0.1。数据包从 NH1 出去但带着 NH0 的源 IP,在双 ISP 场景下会被运营商丢包。
这就是 v6.16 引入 源地址优选 机制要解决的问题,详见 ECMP选路: fib_select_multipath Hash 机制深度解析 第 4.4 节。