NAT

MikroTik RouterOS 中的 NAT 实现私有网络与公网之间的 IPv4 地址转换,既支持用于隐藏内网 IP 的源地址转换(masquerading),也支持把外部流量重定向到指定内网设备的目的地址转换。本文说明配置规则、NAT 类型,以及连接跟踪相关问题的排查要点。

NAT

网络地址转换(Network Address Translation,NAT)是一项互联网标准,它允许局域网内的主机使用一组 IP 地址进行内部通信,同时使用另一组 IP 地址进行外部通信。使用 NAT 的局域网被称为 natted 网络。要让 NAT 正常工作,每个 natted 网络中都必须存在一台 NAT 网关。当数据包在局域网与外部之间往返时,NAT 网关(NAT 路由器)会在途中改写 IP 地址。RouterOS 中的 NAT 仅支持 IPv4,不支持 NAT64。

NAT 只匹配连接的第一个数据包,随后由连接跟踪(connection tracking)记住该动作,并对属于同一连接的所有其他数据包执行相同处理。

危险

每当修改或新增 NAT 规则后,都应清空连接跟踪表;否则在旧连接条目过期之前,NAT 规则可能看起来没有生效。

NAT 的类型

NAT 分为两种类型:

  • 源地址转换(Source NAT,srcnat)。 这类 NAT 作用于源自 natted 网络的数据包。当 IP 数据包穿过路由器时,NAT 路由器会把数据包的私有源地址替换为新的公网 IP 地址;反方向的应答数据包则执行相反的操作。
  • 目的地址转换(Destination NAT,dstnat)。 这类 NAT 作用于以 natted 网络为目的地的数据包,最常见的用途是让互联网上的主机能够访问私有网络中的设备。执行 dstnat 的 NAT 路由器会在数据包发往私有网络的途中替换其目的 IP 地址。

从 RouterOS v7 开始,防火墙 NAT 新增了 INPUT 和 OUTPUT 两条链,分别用于处理投递给本机应用、以及由本机应用发出的数据包:

  • input —— 处理从某个接口进入路由器、且目的 IP 地址属于路由器自身地址的数据包。穿过路由器转发的数据包不会经过 input 链的规则。
  • output —— 处理由路由器自身产生、并从某个接口发出的数据包。穿过路由器转发的数据包不会经过 output 链的规则。

目的地址转换(Destination NAT)

网络地址转换通过修改数据包 IP 头部中的地址信息来实现。来看一个常见场景:网络管理员希望从互联网访问办公室的服务器。

我们想允许互联网上的连接访问本地 IP 为 10.0.0.3 的办公室服务器。此时需要在办公室网关路由器上配置一条目的地址转换规则:

/ip/firewall/nat/add chain=dstnat action=dst-nat dst-address=172.16.16.1 dst-port=22 to-addresses=10.0.0.3 protocol=tcp

上面这条规则的含义是:当有入站连接请求目的地址 172.16.16.1 的 TCP 22 端口时,执行 dst-nat 动作,把数据包重定向到本地 IP 为 10.0.0.3 的设备及其 22 端口。

提示

如果只允许家里那台 PC 访问,可以在 dst-nat 规则中加上 "src-address=192.168.88.1"(本例中为家庭 PC 的公网 IP)。这样也更安全。

源地址转换(Source NAT)

如果你想把本地设备隐藏在 ISP 分配给你的公网 IP 之后,就应该配置 MikroTik 路由器的源地址转换(masquerading)功能。 假设你想把办公室的电脑和服务器都隐藏在公网 IP 172.16.16.1 之后,规则如下:

/ip/firewall/nat/add chain=srcnat src-address=10.0.0.0/24 action=src-nat to-addresses=172.16.16.1 out-interface=WAN

这样 ISP 看到的所有请求都来自 IP 172.16.16.1,而看不到你局域网的 IP 地址。

Masquerade(地址伪装)

防火墙 NAT 的 action=masquerade 是 action=srcnat 的一个特殊变体。它专为公网 IP 可能随机变化的场景而设计,例如 DHCP 服务器重新分配了 IP,或 PPPoE 隧道断开重连后拿到了不同的 IP。简而言之——当公网 IP 是动态的时候使用它。

/ip/firewall/nat/add chain=srcnat src-address=10.0.0.0/24 action=masquerade out-interface=WAN

每当接口断开连接或其 IP 地址发生变化时,路由器都会清除与该接口相关的所有 masquerade 连接跟踪条目,从而缩短公网 IP 变化后的系统恢复时间。如果改用 srcnat*,*连接跟踪条目会保留,链路故障恢复后连接可以直接继续使用。

遗憾的是,当主链路断开、连接被改由其他链路转发时,这在不稳定链路上会带来一些问题。这种情况下可能出现:

  • 断开连接时,所有相关的连接跟踪条目都被清除。
  • 每个被清除(此前经过 masquerade)的连接的下一个数据包进入防火墙时都会被视作 new;如果主接口尚未恢复,数据包会经备用路由发出(如果你配置了备用路由),从而建立一条新的 masquerade 连接。
  • 主链路恢复后,路由重新走主链路,于是属于既有连接的数据包会不经 masquerade 直接从主接口发出,导致本地 IP 泄露到公网。

为避免这种情况,可以创建一条 blackhole 路由,作为断开时可能消失的那条路由的替代。

启用了 NAT 的路由器后面的主机不具备真正的端到端连通性,因此某些互联网协议在 NAT 环境下可能无法正常工作。需要从私有网络外部主动发起 TCP 连接的服务,或无状态协议(如 UDP),都可能受到影响。

为克服这些限制,RouterOS 内置了一系列所谓的 NAT helper(NAT 助手),为各种协议提供 NAT 穿越能力。

提示

虽然源地址转换和 masquerade 完成的是同一件基本工作——把一个地址空间映射到另一个地址空间,但细节略有不同。最明显的区别是:masquerade 会从数据包出口所绑定接口的 IP 中选取出站数据包的源 IP 地址。

CGNAT(NAT444)

为应对 IPv4 地址枯竭,RFC 6598 被提出并部署。其思路是在运营商网络内部使用共享的 100.64.0.0/10 地址空间,并在运营商边缘路由器上转换为单个公网 IP 或一段公网 IP。

由于这种架构的特性,它也被称为 NAT444,以区别于「普通」NAT 环境下的 NAT44 网络。它涉及三个不同的 IPv4 地址空间。

在 RouterOS 上配置 CGNAT 与配置任何普通源地址转换并无区别:

/ip/firewall/nat
 add chain=srcnat action=src-nat src-address=100.64.0.0/10 to-address=2.2.2.2 out-interface=<public_if>

其中:

  • 2.2.2.2 —— 公网 IP 地址。
  • public_if —— 运营商边缘路由器连接互联网的接口。

NAT444 的优点显而易见:占用的公网 IPv4 地址更少。但这项技术也带来了一些主要缺点:

  • 执行 CGNAT 的服务提供商路由器需要为所有地址转换维护状态表,这需要大量的内存和 CPU 资源。
  • 主机游戏问题。当两个使用同一外部公网 IPv4 地址的用户尝试互相连接时,部分游戏会失败。
  • 出于法律原因追踪用户需要额外的日志记录,因为多个家庭共用一个公网地址。
  • 任何需要入站连接的功能都会失效。虽然普通 NAT 下也如此,但终端用户通常还能在自己的 NAT 路由器上设置端口转发,而 CGNAT 让这变得不可能。这意味着无法在这里托管 Web 服务器,IP 电话默认也无法接收来电。
  • 一些 Web 服务器为对抗 SYN flood 等 DoS 攻击,只允许来自同一公网 IP 地址的有限连接数。使用 CGNAT 时该上限会更频繁地被触及,部分服务的质量可能变差。
  • 6to4 需要全球可达的地址,在采用拓扑范围受限地址的网络中无法工作。

以共享地址空间(Shared Address Space)为源或目的地址的数据包不得跨越服务提供商边界转发。服务提供商必须在入站链路上过滤此类数据包。在 RouterOS 中,可以很方便地在边缘路由器上用防火墙过滤器实现:

/ip/firewall/filter
 add chain=input src-address=100.64.0.0/10 action=drop in-interface=<public_if>
 add chain=output dst-address=100.64.0.0/10 action=drop out-interface=<public_if>
 add chain=forward src-address=100.64.0.0/10 action=drop in-interface=<public_if>
 add chain=forward src-address=100.64.0.0/10 action=drop out-interface=<public_if>
 add chain=forward dst-address=100.64.0.0/10 action=drop out-interface=<public_if>

在大型 CGN 部署网络中,服务提供商可能被要求记录映射后的地址,这会带来麻烦。所幸 RFC 7422 给出了一种管理 CGN 转换的方式,可在为滥用响应提供可追溯性的同时,显著减少所需的日志量。

该 RFC 指出,CGN 不必记录每一条连接,而是可以把用户私有地址(在 CGN 面向用户侧的接口上收到,即内部侧)确定性地映射到带有端口范围的公网地址。

这意味着需要添加单独的 NAT 规则来实现逐条映射,如下例所示:

内部 IP外部 IP/端口范围
100.64.0.12.2.2.2:5000-5199
100.64.0.22.2.2.2:5200-5399
100.64.0.32.2.2.2:5400-5599
100.64.0.42.2.2.2:5600-5799
100.64.0.52.2.2.2:5800-5999

与其手工编写这些规则,更推荐使用脚本。下面的示例可根据你的环境需求进行调整。

{
######## Adjustable values #########
:local StartingAddress 100.64.0.1
:local ClientCount 5
:local AddressesPerClient 2
:local PublicAddress 2.2.2.2
:local StartingPort 5000
:local PortsPerAddress 200
####################################

# All client chain jump
/ip/firewall/nat/add chain=srcnat action=jump jump-target=clients \
    src-address="$StartingAddress-$($StartingAddress + ($ClientCount * $AddressesPerClient) - 1)"

:local currentPort $StartingPort

:for c from=1 to=$ClientCount do={
    # Specific client chain jumps
    :if ($AddressesPerClient > 1) do={
      /ip/firewall/nat/add chain=clients action=jump jump-target="client-$c" \
      src-address="$($StartingAddress + ($AddressesPerClient * ($c - 1)))-$($StartingAddress + ($AddressesPerClient * $c -1))"
    } else={
      /ip/firewall/nat/add chain=clients action=jump jump-target="client-$c" \
      src-address="$($StartingAddress + ($AddressesPerClient * ($c - 1)))"
    }

    # Translation rules
    :for a from=1 to=$AddressesPerClient do={
      /ip/firewall/nat/add chain="client-$c" action=src-nat protocol=tcp \
      src-address="$($StartingAddress + (($c -1) * $AddressesPerClient) + $a - 1)" to-address=$PublicAddress to-ports="$currentPort-$($currentPort + $PortsPerAddress - 1)"
      /ip/firewall/nat/add chain="client-$c" action=src-nat protocol=udp \
      src-address="$($StartingAddress + (($c -1) * $AddressesPerClient) + $a - 1)" to-address=$PublicAddress to-ports="$currentPort-$($currentPort + $PortsPerAddress - 1)"
      :set currentPort ($currentPort + $PortsPerAddress)
    }
}
}

这六个局部变量可以调整,脚本既可以简单粘贴到终端执行,也可以保存在 system script 中,以便日后重新生成配置。

执行后,你应该会得到一组规则:

[admin@MikroTik] > ip firewall nat print
Flags: X - disabled, I - invalid; D - dynamic
 0    chain=srcnat action=jump jump-target=clients
      src-address=100.64.0.1-100.64.0.10

 1    chain=clients action=jump jump-target=client-1
      src-address=100.64.0.1-100.64.0.2

 2    chain=client-1 action=src-nat to-addresses=2.2.2.2 to-ports=5000-5199
      protocol=tcp src-address=100.64.0.1

 3    chain=client-1 action=src-nat to-addresses=2.2.2.2 to-ports=5000-5199
      protocol=udp src-address=100.64.0.1

 4    chain=client-1 action=src-nat to-addresses=2.2.2.2 to-ports=5200-5399
      protocol=tcp src-address=100.64.0.2

 5    chain=client-1 action=src-nat to-addresses=2.2.2.2 to-ports=5200-5399
      protocol=udp src-address=100.64.0.2

 6    chain=clients action=jump jump-target=client-2
      src-address=100.64.0.3-100.64.0.4

 7    chain=client-2 action=src-nat to-addresses=2.2.2.2 to-ports=5400-5599
      protocol=tcp src-address=100.64.0.3

 8    chain=client-2 action=src-nat to-addresses=2.2.2.2 to-ports=5400-5599
      protocol=udp src-address=100.64.0.3

 9    chain=client-2 action=src-nat to-addresses=2.2.2.2 to-ports=5600-5799
      protocol=tcp src-address=100.64.0.4

10    chain=client-2 action=src-nat to-addresses=2.2.2.2 to-ports=5600-5799
      protocol=udp src-address=100.64.0.4

11    chain=clients action=jump jump-target=client-3
      src-address=100.64.0.5-100.64.0.6

12    chain=client-3 action=src-nat to-addresses=2.2.2.2 to-ports=5800-5999
      protocol=tcp src-address=100.64.0.5

13    chain=client-3 action=src-nat to-addresses=2.2.2.2 to-ports=5800-5999
      protocol=udp src-address=100.64.0.5

14    chain=client-3 action=src-nat to-addresses=2.2.2.2 to-ports=6000-6199
      protocol=tcp src-address=100.64.0.6

15    chain=client-3 action=src-nat to-addresses=2.2.2.2 to-ports=6000-6199
      protocol=udp src-address=100.64.0.6

[...]

Hairpin NAT(发夹 NAT)

发夹网络地址转换(NAT Loopback,NAT 回环)指的是局域网内的一台设备可以通过网关路由器的公网 IP 地址访问同一局域网内的另一台机器。

在上面的示例中,网关路由器有如下 dst-nat 配置规则:

/ip/firewall/nat/add chain=dstnat action=dst-nat dst-address=172.16.16.1 dst-port=443 to-addresses=10.0.0.3 to-ports=443 protocol=tcp

当家里那台 PC 上的用户与 Web 服务器建立连接时,路由器按配置执行 DST NAT:

  1. 客户端发出一个源 IP 为 192.168.88.1、目的 IP 为 172.16.16.1 的 443 端口数据包,请求某些 Web 资源。
  2. 路由器对数据包执行目的 NAT,将其转换为 10.0.0.3,并相应替换数据包中的目的 IP 地址。源 IP 地址保持不变,仍为 192.168.88.1。
  3. 服务器回复客户端的请求,应答数据包的源 IP 为 10.0.0.3,目的 IP 为 192.168.88.1。
  4. 路由器判断该数据包属于先前的连接,于是撤销目的 NAT,并把原始目的 IP 地址填入源 IP 字段。此时目的 IP 为 192.168.88.1,源 IP 为 172.16.16.1。
  5. 客户端收到了它期望的应答数据包,连接建立成功。

但当与 Web 服务器处于同一网络的客户端去访问该服务器的公网 IP 地址时,就会出现问题:

  1. 客户端发出一个源 IP 为 10.0.0.2、目的 IP 为 172.16.16.1 的 443 端口数据包,请求某些 Web 资源。
  2. 路由器对数据包执行目的 NAT,将其转换为 10.0.0.3,并相应替换数据包中的目的 IP 地址。源 IP 地址保持不变,仍为 10.0.0.2。
  3. 服务器回复客户端请求。但请求的源 IP 与 Web 服务器处于同一子网,于是 Web 服务器不会把应答发回路由器,而是直接发回 10.0.0.2,应答的源 IP 为 10.0.0.3。
  4. 客户端收到了应答数据包,但会将其丢弃,因为它期望的是来自 172.16.16.1 的数据包,而不是来自 10.0.0.3 的。

要解决这个问题,我们按如下方式新增一条 src-nat 规则(即 hairpin NAT 规则):

/ip/firewall/nat
add action=masquerade chain=srcnat dst-address=10.0.0.3 out-interface=LAN protocol=tcp src-address=10.0.0.0/24

配置上述规则之后:

  1. 客户端发出一个源 IP 为 10.0.0.2、目的 IP 为 172.16.16.1 的 443 端口数据包,请求某些 Web 资源。
  2. 路由器对数据包执行目的 NAT,将其转换为 10.0.0.3,并相应替换目的 IP 地址。同时它还会对数据包执行源 NAT,把源 IP 替换为其 LAN 接口上的 IP 地址。此时目的 IP 为 10.0.0.3,源 IP 为 10.0.0.1。
  3. Web 服务器回复该请求,把源 IP 为 10.0.0.3 的应答发回路由器 LAN 接口的 IP 10.0.0.1。
  4. 路由器判断该数据包属于先前的连接,于是同时撤销源 NAT 和目的 NAT,把原始目的 IP 172.16.16.1 填入源 IP 字段,把原始源 IP 10.0.0.2 填入目的 IP 字段。

端点无关 NAT(Endpoint-Independent NAT)

端点无关 NAT 会在源 NAT 中建立一条映射,并对后续所有具有相同源 IP 和端口的数据包复用该映射。该映射由以下规则创建:

/ip/firewall/nat
add action=endpoint-independent-nat chain=srcnat out-interface=WAN protocol=udp

该映射允许执行与源无关的过滤,即可以把来自 WAN 任意源的数据包转发到映射后的内部 IP 和端口。下面这条规则用于启用该过滤:

/ip/firewall/nat
add action=endpoint-independent-nat chain=dstnat in-interface=WAN protocol=udp

危险

端点无关 NAT 仅适用于 UDP 协议。 此外,endpoint-independent-nat 还可以接受一些其他参数:

  • randomize-port —— 随机化连接将被映射到的公网端口。

更多信息参见 RFC 5128 第 2.2.3 节和第 2.2.5 节。

NAT 助手(NAT Helpers)

启用了 NAT 的路由器后面的主机不具备真正的端到端连通性,因此某些互联网协议在 NAT 环境下可能无法工作。为克服这些限制,RouterOS 内置了一系列 NAT 助手,为各种协议提供 NAT 穿越能力。

NAT 助手可以在 /ip/firewall/service-ports 菜单中管理。

可用的 NAT 助手列表:

助手说明
FTPFTP 服务助手
H323H323 服务助手
IRCIRC 服务助手
PPTPPPTP(GRE)隧道助手
UDPLITEUDP-Lite 服务助手
DCCPDCCP 服务助手
SCTPSCTP 服务助手
SIPSIP 助手。附加选项:sip-direct-media 允许把 RTP 媒体流重定向为从主叫直接发往被叫,默认值为 yes;sip-timeout 用于调整 SIP UDP 连接的 TTL,默认 1 小时,在某些环境中需要调小。
TFTPTFTP 服务助手
RTSPRTSP 服务助手

警告

如果未启用连接跟踪,防火墙服务端口会显示为未激活。

警告: udplite、dccp 和 sctp 是连接跟踪的内置服务。它们并非可单独加载的模块,因此无法单独禁用,只能随连接跟踪一起被禁用。