两个 VPN 客户端,两个公网地址:让 SNAT 匹配
按源地址的 SNAT 可以挑选公网地址,但前提是网关仍能区分客户端。写规则之前,先确认还剩下哪个源。
两个 VPN 客户端可以经由两个不同的公网 IPv4 地址出去,而实现它的规则比大多数人想的要短。让它生效的不是 iptables 语法,而是数据包到达出口链时的状态:那里的源地址仍然必须能区分客户端。如果先前的 masquerade 已经把两者合并到一个地址,任何按客户端的规则都无法恢复这个区分。先解决这个问题,再写转换规则。
哪种身份能留到网关
规则匹配的是数据包的源地址,不是 VPN 用户名,也不是客户端应用显示的名字。路径上更早转换数据包的一切,决定了你的规则还能看到什么。按顺序运行 nat 表,找到源地址仍然按客户端区分的那一点。
iptables -t nat -S POSTROUTING
从顶部读这份清单。靠近顶部的 masquerade 规则会为每条匹配的流改写源地址,而放在它后面的按客户端条目匹配的是一个已不存在的地址。当两台 LAN 设备共用一个隧道并以同一个内部地址到达时,必须先改设计再改防火墙:让它们的源网段保持分开,或给各自独立的内部地址。
把内部地址的分配绑定到已认证的客户端。把该地址交给另一台设备,就等于一并交出出口策略,而源地址转换本身并不是身份证明。
转换无法凭空造出地址
每个公网地址都必须分配给这个出口点,或者路由到这里,回程也必须能找到这里。把流量从无人回程路由的地址发出的规则,会在远端被丢弃,或者被答复给从未请求过的主机。云厂商的实例通常在数据包离开之前就已把公网地址转换成私网接口地址,所以 SNAT 的源必须是厂商会接受的地址,而不是控制台显示的值。读懂厂商的地址模型并照做,而不是把公网值复制进每个数据包。
在实际操作中,这意味着在动防火墙之前,先在厂商控制台核对已路由的前缀;当映射是一对一时,用私网接口地址作转换源。下面示例中的地址来自文档保留段,所以首先要替换的就是地址文本本身。
插到通用 masquerade 之前
在添加任何东西之前,先确认出口接口和当前链。前者决定规则能匹配哪些数据包,后者显示它前面已经站着什么。
ip -4 route show default
iptables -t nat -L POSTROUTING -n -v --line-numbers
第一条命令指出真正通往互联网的出口接口。第二条打印带行号的链。通用 masquerade 条目就是那条需要留在上面的行。
iptables -t nat -I POSTROUTING 1 -s 10.88.4.20/32 -o ens3 -j SNAT --to-source 198.51.100.77
iptables -t nat -I POSTROUTING 1 -s 10.88.4.21/32 -o ens3 -j SNAT --to-source 198.51.100.78
上面两条规则各自匹配一个内部地址,把匹配限定在出口接口上,并显式设置源。要插入而不是追加:位置 1 让它们排在 masquerade 行之前。两条之间的顺序无所谓,因为源不同。把 ens3 换成 route 命令报告的值,并让其他防火墙管理器远离同样的钩子,因为两个工具写同一张表正是正确规则不生效的常见原因。
用新流验证源地址
有状态 NAT 在流开始时绑定转换,并在后续数据包上复用,所以改动前打开的会话在改动后仍保留旧源。只有新发起的连接才能证明规则生效。
conntrack -L -s 10.88.4.20 -p tcp --dport 443
读那条记录的应答方向:它的目的地址就是远端看到的源。与预期的公网地址比较,把带旧地址的条目当作流开始得太早的证据,而不是规则坏了。让客户端新建连接,然后重试查询。为强制测试而清空整张跟踪表,会中断网关上的其他所有会话。
转换仍然不选路由、不放行过滤,也不对外发布入站服务。路由错误和缺少放行规则,即使在完美的 SNAT 规则之后也会继续失败。还要检查 IPv6 和任何备用出口路径,然后把内部到公网的对应关系记进防火墙文档,让下一次改动从映射出发,而不是从规则文本出发。