站点到站点 VPN 的响应没有走隧道
绕过隧道的响应是对端的一条路由决策。检查主机路由表、流量选择器和 rp_filter,然后在路由与 NAT 之间做选择。
我把一个上午花在一条干净建立、却只朝一个方向搬流量的隧道上:分支能访问总部的文件服务器,而从总部发往分支的每条连接都死在第一个 SYN。SA 是起的,隧道计数器在涨,握手从来不是问题所在。响应被路由到了隧道覆盖不到的地方,而这个选择属于对端。站点到站点 VPN 只搬运主机交给网关的包,而每台主机都会各自地、按每个方向、根据自己的路由表做出这个选择。
两个方向各自路由
IP 不记得一次会话的方向。每个发包的主机都各自做最长前缀查找,选一个下一跳,然后把决定忘掉。请求和响应是两个不相干的包,只是碰巧同属一条连接,而隧道只是其中任一个可以用也可以不用的一跳。IKE 交换不会告诉远端服务器如何到达你的子网;协商只是约定哪些前缀可以加密。
那份约定就是第二道闸。在 strongSwan 上,流量选择器是 leftsubnet 和 rightsubnet,源或目的落在协商对之外的包不会被加密,无论路由表怎么说。一个被路由进隧道却不在选择器里的前缀,是一种安静的单向失败:路由看起来对,包仍然明文出去或被丢弃,只有 charon 日志在抱怨。
远端主机对你的隧道一无所知
在远端 LAN 内部,响应是发给你的客户端的一个普通包。目的主机看不到 IPsec 头,也没有理由优先选择 VPN 网关;它查你的前缀,找不到比默认路由更具体的东西,就照着默认路由走。如果默认路由指向一台核心防火墙,那台防火墙要么有回隧道的路由,要么没有。没有的时候,你得到的是静默丢弃、ICMP unreachable,或者一个从面向外网的接口出去然后死掉的响应。
哪怕路由存在,第二个接口也足以把这件事破坏掉。一台管理链路在另一个 VLAN 上的 NAS、一台有备份上行链路的服务器、一条把某个源段送进第二张表的路由策略规则:其中任何一个都能截住响应。在 Linux 上,ip rule show 和 ip route show table all 会显示默认列表藏起来的路由策略与路由表。
在发送响应的那台机器上读决策
改动之前先问内核,并把源地址一起给它,因为依赖源地址的规则会改变答案。
ip route get 10.10.5.10 from 10.20.30.15
ip rule show
ip route show table all
ss -tn state established "( sport = :445 )"
输出会给出出口接口和下一跳。如果这个接口不是朝向 VPN 网关的那个,缺陷就找到了,剩下的工作是决定这条路由该放在哪里。在这里,从网关 ping 证明不了多少:它用远端主机放得进路由表的地址作答,而不是你的笔记本地址。
在 Windows 上,对应的是用 Find-NetRoute -RemoteIPAddress 10.10.5.10 看决策、用 Get-NetRoute 看表。用 New-NetRoute 添加、度量值高于默认路由的路由会静默落败,所以要读度量那一列,而不是那一条。然后用包来确认:在远端 LAN 接口上跑 tcpdump -ni any "host 10.10.5.10 and tcp port 445" 能看到请求,隧道这一侧用同样的过滤器能看出响应有没有回来。看得到请求却没有响应,说明响应被路由到别处或被丢弃了;响应出现在错误的接口上,则让你在动路由之前就抓到元凶。
当服务器的网关不是 VPN 端点时
很多站点把防火墙放在服务器与集中器之间。那台防火墙要转发响应,所以它需要一条指向 VPN 网关的客户端前缀路由,以及两个方向的转发策略。有状态的设备会把事情弄得更糟:请求从它放行的 LAN 主机穿过防火墙,响应现在从另一个方向来,连接跟踪表项不再匹配,于是包被当作无效丢弃,而不是被拒绝。策略日志看起来空空如也、流量却在消失,原因就在这里。
网关还有它自己的第二道过滤。Linux 的反向路径过滤会丢弃源地址无法经其到达接口的包,而这恰好就是一条合法的非对称回程的形态。strongSwan 的转发指南 正是出于这个原因要求 net.ipv4.ip_forward 和放宽的 net.ipv4.conf.all.rp_filter。任一侧网关上取严格值 1 都会悄悄破坏一条正确的路由,而如果你知道该看哪里,nstat -az 会显示这些丢弃。
| Symptom | Where the reply is most likely lost |
|---|---|
| Gateway ping works, LAN host silent | Source address the far host cannot place in its table |
| SYN arrives, no SYN-ACK, tunnel counters flat | No route to the client prefix on the far host or its gateway |
| Reply counted on the internet-facing interface | Default route or policy rule on the far host |
| Firewall reports invalid state | Asymmetric path through a stateful device |
| Worked until a link failed over | New path is asymmetric, or rp_filter now rejects it |
要么路由要么转换,然后在两个方向上都验证
这两种修法并不等价。加路由会保持客户端地址不变,于是基于源地址的规则、审计日志和记录调用方的应用都还能用;代价是每个做决策的跳都需要这条路由,出错的地方更多。在远端网关上做地址伪装 把回程压缩成一跳,因为响应现在直接发给网关自己,同时也掩盖了是哪个客户端做了什么。
iptables -t nat -A POSTROUTING -o eth1 -s 10.20.30.0/24 -j MASQUERADE
要有意识地选它:一旦客户端以网关地址的样子出现,基于源地址的 ACL 就分不清它们了,而 conntrack 必须维持映射,响应才会被写回隧道。如果选择器仍然把客户端前缀排除在外,NAT 什么也改变不了,因为请求根本没进过隧道。
在宣布修好之前,按顺序确认四件事:发送响应的主机上的路由、每个转发它的跳上的路由、覆盖两个前缀的选择器,以及一份显示响应带着你预期的源地址进入隧道的抓包。然后再从另一个站点重做测试。一个方向通是半开的设计,而第二个方向失败的原因,通常和你刚刚修掉的那个不一样。