远程用户连接到中心,能访问中心自身局域网中的文件服务器,一切看起来都正常。随后他打开总部 10.20.0.50 上的共享,中心需要通过另一条站点到站点隧道才能到达那里,结果请求超时。两条隧道都在运行,两个计数器都在增长,所以没有哪条隧道坏了。穿过中心是三个设备上的三次路由决策,而 隧道只承载它建立时所覆盖的前缀。下面是我检查的顺序,以及用来判断每一步是否成立的命令。

经过中心需要三次查找,而不是一条隧道

每个设备各自做最长前缀匹配,然后把它忘掉。笔记本必须把 10.20.0.50 送进隧道,而不是交给本地路由器。中心必须把从远程访问接口进来的包交给站点到站点接口。总部必须把发往用户地址的应答沿隧道送回。这三个环节中任何一个失败,在浏览器里看起来都一样:一片沉默。

所以单独测试每条隧道能证明的东西少得可怜。ping 中心的局域网地址可以检验第一个决策,从总部 ping 中心则检验第三个。真正失败的包以用户池为源、以第二个站点为目的,所以要先把这些前缀写下来。这里用户池是 10.80.0.0/24,中心局域网是 10.10.0.0/24,总部是 10.20.0.0/24。

把远端前缀推送给远程客户端

在一台 OpenVPN 远程访问服务器上,客户端在连接时学习路由。没有 push,拆分隧道的客户端会把 10.20.0.50 发给自己的网关,包永远不会进入隧道。

push "route 10.20.0.0 255.255.255.0"

推送的路由在客户端连接时生效,所以一直保持在线的用户要等到重新连接才会看到变化。在客户端验证:Linux 上用 ip route get 10.20.0.50,Windows 上用 route print。输出应当指出隧道接口。

push 帮不了中心。它只是发给客户端的消息,既不是服务器上的路由,也不是转发许可,所以即使中心有完美的 push 行,流量仍会在下一跳被丢掉。人们通常正是因此断定 push 失败了。

给中心一条真正的出口路由

中心需要自己的下一跳来通往总部前缀。在可路由的隧道上,这就是一条经过站点到站点接口的普通路由,一条命令可以同时显示选中的路由和选中的源地址:

ip route get 10.20.0.50 from 10.80.0.25
sudo ip route add 10.20.0.0/24 via 172.30.0.2 dev s2s0

如果站点到站点这一段本身是 OpenVPN,路由可以写在该配置里,以对端隧道地址作为网关:route 10.20.0.0 255.255.255.0 172.30.0.2。网关不是可选项。省略它,下一跳就会落到实现层面的默认值,而这个默认值在单一远程客户端和多对等中心之间并不相同。

基于策略的 IPsec 不一样:中心内核把包发往它平常的局域网网关,策略在出口处截住它,所以隧道路由要么什么都不做,要么把流量引到策略之外。注意上面命令里的源地址,因为出现 10.10.0.1 而不是用户池地址,说明内核选了一个对端会拒绝的地址。iroute 也不是答案;它告诉服务器哪个客户端拥有自己隧道后面的子网,而不是把去往第三个站点的流量送到哪里。

把远端扩展到用户池

总部已经覆盖中心局域网 10.10.0.0/24。用户池是第二个前缀,也必须写进选择器,否则没有任何策略覆盖发往 10.80.0.25 的应答。每个对等方从自己一侧列出同一对前缀集合:

# strongSwan swanctl.conf, on the hub
local_ts  = 10.10.0.0/24, 10.80.0.0/24
remote_ts = 10.20.0.0/24
# on the headquarters peer
local_ts  = 10.20.0.0/24
remote_ts = 10.10.0.0/24, 10.80.0.0/24

一端的 local_ts 就是另一端的 remote_ts。把中心的配置块复制到总部,这一对就反了,子 SA 永远建立不起来。

较旧的 ipsec.conf 语法使用逗号分隔的 leftsubnet。正在运行的安全关联会保留它协商过的选择器,所以编辑之后要重新建立隧道。10:00 改过的选择器拿去和 09:40 建立的 SA 测试,仍然会失败,而配置文件在整个过程中看起来都是对的。

然后检查最后一跳。远端上的 ip route get 10.80.0.25 应当指向隧道。如果服务把局域网路由器当作默认网关,那台路由器也需要回程路由,否则应答就死在那里。在那段网络上运行 tcpdump -ni eth1 host 10.80.0.25 可以看出应答到底有没有离开远端。

在中心放行这条流量

转发必须开启,路径必须允许。把规则限定在这两个前缀之间,而不是接受接口之间的所有流量。

sysctl -w net.ipv4.ip_forward=1
iptables -I FORWARD -i tun0 -o s2s0 -s 10.80.0.0/24 -d 10.20.0.0/24 -j ACCEPT
iptables -I FORWARD -i s2s0 -o tun0 -s 10.20.0.0/24 -d 10.80.0.0/24 -j ACCEPT

反向路径过滤是它旁边那种安静的失败:两条腿在不同接口上时,rp_filter=1 可能把应答丢掉,而允许规则的计数器一动不动。在一次尝试前后比较 iptables -vnL FORWARD。一条从不增长的规则说明流量根本没走到它,所以应该往下看一层,而不是重写规则。

如果只有一个组需要穿越,就把路由放进 client-config-dir 下按客户端划分的文件,而不是扩大全局 push。中心上的源 NAT 也能省掉对端回程路由,但它会隐藏用户地址,于是远端站点的日志和基于地址的规则就再也分不清用户了。

先从客户端验证,然后按相反顺序回滚

测试一个 TCP 端口,而不只是 ICMP,因为 ping 通了服务仍可能被挡住。在客户端运行 nc -vz 10.20.0.50 445 和 traceroute -n 10.20.0.50,可以看出第二跳是不是中心的隧道地址。在中心上运行 tcpdump -ni tun0 host 10.20.0.50,并在站点到站点接口上用同样的过滤条件:两边都有请求、两边都没有应答,问题就转移到远端了。

然后再测一个必须保持被挡住的东西。如果用户池本不该到达的主机也能应答,说明你放开的范围超过了穿越,应当收窄规则。把所有流量转发给所有分支的中心会通过第一项测试,却在第二项上失败。

留一份回滚清单,因为改动了四个对象,其中三个很容易忘:推送的路由、中心的路由、选择器和防火墙规则。按相反顺序撤销,重新建立 SA,再让一个用户重新连接。只回滚一部分会留下一条指向已不匹配隧道的路由,那比原来的沉默更难读。 OpenVPN 2.6 手册 记录了适用于你所装版本的 push、route 和 iroute。

相关文章