RouterOS 的Hairpin 配置
先来解释下为什么需要Hairpin NAT(环回NAT)。你在自己的路由器上配置了DNAT(映射),让内部服务可以暴露到公网访问,但是当你自己在内网环境访问的时候就会发现访问出现了问题:
假设我们的环境是这样的:
- 内网PC地址:192.168.88.10
- 内网服务:192.168.88.40:38783
- 路由器网关地址:192.168.88.1
- 外网地址:180.159.19.232:38783 ==dnat== 192.168.88.40:38783
我们先来看下外网访问会发生什么:
- 路由器收到公网地址1.2.3.4 访问180.159.19.232:38783 的请求。(src=1.2.3.4:随机端口,dst=180.159.19.232:38783)
- 路由器匹配到DNAT 策略,于是将请求forward 给服务器,服务器收到请求(src=1.2.3.4:随机端口,dst=192.168.88.40:38783)
- 服务器响应请求回复。(src=192.168.88.40:38783,dst=1.2.3.4:随机端口)
- 路由器回程NAT,会匹配之前的请求进程,通过公网180.159.19.232:38783 回复给1.2.3.4:随机端口。
但是内网PC 请求就出现了问题:
- 路由器收到内网地址192.168.88.10 访问180.159.19.232:38783 的请求。(src=192.168.88.10:随机端口,dst=180.159.19.232:38783)
- 路由器看到请求的公网地址是自己的,于是将请求直接forward 给服务器,服务器收到请求(src=192.168.80.10:随机端口,dst=192.168.88.40:38783)
- 服务器响应请求回复。(src=192.168.88.40:38783,dst=192.168.88.10:随机端口)
- 于是192.168.88.10 收到了一个直接从192.168.88.40:38783 过来的数据包,和它之前请求的180.159.19.232:38783 不匹配,于是就被丢弃了。
为了解决RouterOS 这种处理方式,我们需要识别这类请求,将请求通过masquerade 伪装来实现来回路径的一致性:
添加一个脚本来获取当前的公网IP 地址,并更新到地址列表WANs 中:
/system script add name=update-public-ip source="
/tool fetch url=\"http://ifconfig.me/ip\" dst-path=pubip.txt;
:delay 5;
:local currentIP [/file get pubip.txt contents];
:put (\"Fetched Public IP: \" . \$currentIP);
/ip firewall address-list set [/ip firewall address-list find list=WANs] address=\$currentIP;
"
添加一个定时任务,每5分钟执行上面的脚本:
/system scheduler add interval=5m name=Schedule-Public-IP \
on-event="/system script run update-public-ip"
添加Hairpin NAT Mangle规则,当内网地址访问WANs 列表里的地址(也就是公网地址)会对连接打上标记“hairpin”:
/ip firewall mangle add chain=prerouting place-before=0 \
comment="Hairpin NAT" src-address=192.168.88.0/24 \
dst-address-list=WANs action=mark-connection \
new-connection-mark=hairpin
添加Hairpin NAT源地址转换,所有打上hairpin标签的连接会先进行一次原地址转换:
/ip firewall nat add chain=srcnat place-before=0 \
comment="Hairpin NAT" connection-mark=hairpin \
action=masquerade
最终整个请求:
- 路由器收到内网地址192.168.88.10 访问180.159.19.232:38783 的请求。(src=192.168.88.10:随机端口,dst=180.159.19.232:38783)
- 路由器匹配到了Mangle 策略,然后对这个请求进行了伪装masquerade,伪装后的请求forward 给服务器,服务器收到请求(src=192.168.88.1:随机端口,dst=192.168.88.40:38783)
- 服务器响应请求回复。(src=192.168.88.40:38783,dst=192.168.88.1:随机端口)
- 路由器根据之前的masquerade 表匹配到回程转给192.168.88.10,于是192.168.88.10收到了正确的包(src= 180.159.19.232:38783,dst=192.168.88.10:随机端口)
最后说明下这个实现可能和大部份网络材料不太一样,主要是增加了脚本获取公网IP 这部分。由于贵国的家庭网络环境复杂,路由器并不一定会直接配置公网IP,所以脚本的加入可以完全覆盖所有可能的情况(比如通过光猫的DMZ 配置来实现DNAT)。