把一台 LAN 设备呈现在网关自己的某个隧道地址上,只需要一行 NAT。连接仍然失败,因为应答的那台设备根本不知道客户端的地址能通过这台网关抵达。让数据包从客户端走进去,再让回程从里面走出来,缺掉的那一半就一目了然了。

从客户端的视角出发

本例中的客户端位于 VPN 地址池 10.62.0.0/24,连接 10.62.0.1 的 8080 端口。这个地址属于网关自己,所以数据包进入的是网关的内核,而不是别的对端。网关背后是 172.21.4.0/24 的 LAN。网关持有 172.21.4.10,真正提供 HTTP 的设备是 172.21.4.40。

属于另一台 VPN 对端的地址,不能拿来顶替网关的服务地址。如果客户端连的是某台对端的地址,那么根本不涉及任何网关规则,这一整段推演也就不适用。

有意把目的地址转换的范围界定清楚

窄版本只呈现一个端口,也只改写目的地址。数据包保留自己的协议和来源,这正是后面那段路有意思的原因。

iptables -t nat -A PREROUTING -i tun0 -s 10.62.0.0/24 -d 10.62.0.1 -p tcp --dport 8080 -j DNAT --to-destination 172.21.4.40:80

不带协议和端口条件的规则是宽版本:客户端选了哪个端口就保留哪个端口,只改地址。要呈现整台主机时这样做是正当的,只想呈现一项服务时就是错的。回头把规则读一遍,确认接口名。接口写错的 DNAT 规则什么也匹配不到,产生的超时和规则缺失一模一样,还会让人对着设备白看一个小时。

让转发的边界也同意

转换只改报文头,并不授予权限。在默认丢弃的策略下,被转发的流需要自己的放行规则,回程方向则需要策略里大概早就有的已建立连接规则。

iptables -A FORWARD -i tun0 -o lan0 -d 172.21.4.40 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

为每一条 DNAT 流都无条件放行,比这个设计更宽,还会顺带放行别人后来添加的映射。把目的地址和端口写清楚,其余的一律保持关闭。如果转发规则的计数器一直是零,而 NAT 的计数器在涨,说明数据包在到达你的规则之前就被丢掉了。去读链的顺序,而不是再加一份副本。

设备总得想办法回话

转换之后,设备看到的来源在 10.62.0.0/24 之内。它的回程会走向默认网关,而那是 LAN 的路由器,不是 VPN 网关,除非有人告诉它别的。两种安排能修好这件事,它们并不等价。在设备上或在 LAN 路由器上加一条路由,把 10.62.0.0/24 送到 172.21.4.10,客户端的来源得以保留。范围受限的来源转换则把客户端藏在网关的 LAN 地址后面。

ip route add 10.62.0.0/24 via 172.21.4.10 dev eth1
iptables -t nat -A POSTROUTING -s 10.62.0.0/24 -d 172.21.4.40 -o lan0 -p tcp --dport 80 -j SNAT --to-source 172.21.4.10

按服务的需要来选。要按客户端做策略、要日志有意义,就该用路由那种做法;给不了路由的设备,或者必须看到 LAN 地址的服务,则指向转换那种做法。不要为了让一个回程能用,就去抓一条全局 masquerade 规则。它把所有客户端都藏起来,不让任何目的地址看见,还会悄悄破坏那些依赖来源地址的策略。

不要从网关本身去测

在网关上向 10.62.0.1 发出的请求走的是 OUTPUT 链,而不是入站的 PREROUTING。它成不成功,对面向客户端的那条规则什么也说明不了,人们就是这样断定一条好用的规则坏了。

conntrack -L | grep 172.21.4.40
tcpdump -ni any 'port 8080 or port 80'

发一个真实的客户端请求,两样都读。conntrack 条目显示实际施加的转换,抓包则显示请求有没有过到 LAN、回程有没有往隧道回来。改动规则之后要换一条新连接,因为已有状态会保留旧的映射,继续按老样子失败。最后把整段对话核一遍:设备地址、转发放行和回程路由,说的是同一条流。

相关文章