为什么办公室局域网里的 VPN 客户端收不到任何回包
一台笔记本同时接在办公室以太网和同一网段的 VPN 隧道上,回包却从错误的网卡发出。宽松的反向路径过滤解释了这种沉默。
一台笔记本通过以太网接在办公室的 /24 网段上,同时又加入了一个位于同一 /24 网段的 OpenVPN 服务器。同事去 ping 隧道地址;请求明明到了,却什么都没有回来。隧道日志干干净净,上网也照常,所以隧道是错的怀疑对象。接收端机器上的 IPv4 源地址校验在任何人读到这个包之前就把它丢掉了。
丢弃发生在任何代码读到包之前
源地址校验会对每个入站包问一个问题:如果这台主机要回那个源,这个回包会不会从刚才进来的那块网卡出去?当答案是否定时,内核在到达那一刻就把包丢掉。既没有套接字、也没有转发路径会看到它,所以隧道上的抓包仍然会记录下一个应用永远收不到的请求。
这里的客户端在隧道上持有 10.66.0.9,而 OpenVPN 服务器在办公室前缀内以 192.168.80.10 应答。位于 192.168.80.25 的同事去 ping 隧道地址,于是请求穿过局域网,服务器把它送进隧道,它带着办公室源地址抵达客户端的隧道网卡。在那台机器的路由表里,办公室前缀属于以太网设备,而不属于隧道。校验拿到达网卡和路由比对,发现不一致就把包丢掉,于是同事看到完全丢包,而两端都报告隧道正常。
镜像方向也是同样的形状:当客户端主动发往办公室时,回包可能在办公室一侧因为完全相同的原因被丢掉。只在一个方向上验证过的修复,往往会变成第二张工单。
动任何 sysctl 之前,先证明当前生效的模式
读实际值,而不是相信发行版的默认值,因为每个网卡的设置只是决定的一半。生效的模式取网卡值与 all 值中较高的那个,所以在 all 为一时,隧道网卡上的零毫无意义。严格模式的判断是路由判断,所以要跑内核实际执行的那次查询,并把结果与包真正到达的网卡对照。
tcpdump -ni tun0 icmp and host 192.168.80.25
ip route get 192.168.80.25
sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.tun0.rp_filter
然后重复 ping,同时盯住错误计数。抓包已经显示的请求旁边计数在涨,就是这次丢弃的特征,而不是缺路由的特征。顺手打开火星包日志,让内核说出它拒掉的源和网卡。两者合起来就能把加密、防火墙和缺少 iroute 排除在解释之外。
改动之前先写下基线:两个值、查询选中的设备,以及你盯的那个计数。事后正是这条记录告诉你,改动是否做成了你以为的事。
每一种 rp_filter 模式究竟接受什么
内核为每个网卡暴露三个值,而它们不是同一条规则的三种深浅。零关闭检查。一是严格模式,要求源地址能经到达网卡反向路由出去。二是宽松模式,只要求表里存在通往该源的某条路由,不管它指向哪里。
| Value | Name | Accepts | Still rejects |
|---|---|---|---|
| 0 | off | any source at all | nothing on this check |
| 1 | strict | a source routed back out the arrival interface | a source whose route points elsewhere |
| 2 | loose | a source with any route in the table | a source with no route at all |
这里要紧的是宽松那一行,以及它仍然拒绝的东西。一个能在表里任何地方路由的伪造源可以通过,而完全没有路由的源仍会被拒绝。在笔记本上这是很小的让步;在替别人转发流量的路由器上,这就是很大的让步。模式属于主机,所以同一个数字在两处分量完全不同。
这个拓扑需要哪一行,是从表里推出来的,不是从习惯来的。包是合法的,它的源也是可路由的,所以宽松模式接受它,严格模式不接受。这件事里没有哪一处需要零,而选零会去掉一个在这里毫无代价的检查。
选能消除歧义的最小改动
宽松模式是能用的最小改动,而且是实打实地放宽,不是无害的开关。只要拓扑允许,就有更好的选择:去掉重叠,歧义自然消失。
如果办公室前缀只需要走以太网,就把它从配置文件拉取的路由里去掉,地址就只剩一条路径可达。如果两条路径确实必须共存,就用策略规则把源和设备钉住,而不是在每个网卡上放宽校验。这样机器上其他一切仍然保持严格过滤,例外也写在下一个工程师会去找的地方。
无论选哪条路,都写进 sysctl drop-in,而不是敲一条临时命令。把 all 和 default 两个键一起设置,这样重连就不会悄悄把严格模式恢复回来。
# /etc/sysctl.d/99-rp-filter.conf
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.rp_filter = 2
sysctl --system
宽松模式放弃了什么,又在哪里
宽松模式仍然拒绝一个任何路由都到不了的源,同时不再拒绝一个碰巧可路由的伪造源。在笔记本上这是很小的让步。在替别人转发流量的设备上,同样的选择就宽得多。所以同一个设置,跑在不同地方含义完全不同。
在替别人转发流量的主机上彻底关掉检查,是唯一该避免的修法。零会把宽松模式小心保留的那道校验扔掉。如果主机替别人转发,那就去争取上一节里的策略规则方案。
留一条备注,写清两块网卡、重叠的前缀,以及现在能通过的那次确切探测。对下一个工程师来说,这比一句“过滤已调整”有用得多。