ArthurChiao's Blog

[译] NAT - 网络地址转换(2016)

Published at 2019-02-17 | Last Update 2021-10-08

译者序

本文翻译自 2016 年的一篇英文博客 NAT - Network Address Translation

由于译者水平有限,本文不免存在遗漏或错误之处。如有疑问,请查阅原文。

以下是译文。



1 绪论

网络地址转换通常涉及“在 IP 包经过路由器或防火墙的时候,修改其源和/或目的地址”Wikipedia: NAT)。

本文将介绍什么是 NAT、如何使用 NAT 以及在 Linux (或者更广泛地说,各种 Unix 的衍生版) 上如何配置 NAT。 但是,本文不会覆盖 NAT 的所有细节,而主要想让读者知道: 在现代计算机网络中它可以用来做什么,及其不能用来做什么。

接下来首先会查看 IP 包的结构,然后简要地介绍 (Linux)内核的一些功能,然后进入正 题,介绍一种最常见的 NAT 应用场景,即,通过路由器(本例中为一台支持 iptables 的 Linux 机器)将私有子网连接到互联网。

再后面,会介绍几种其他的可能性,例如重定向,或者如何使用代理(proxy)绕过防火 墙。但是,我不能保证这些解决方案是最智能、最强大或者设计最精巧的,读者应该 仅将它们视为展示 NAT 功能的一些示例方案(proof of concept)。

2 网络内的数据包

本节只讨论基于 TCP 和 UDP 协议的 IP 包。

IP 包的介绍见 IPv4 at Wikipedia。本文将主要 用到其中的两个字段:源 IP 和目的 IP。

TCP 和 UDP 是传输层协议,介绍分别见:

  1. TCP at Wikipedia
  2. UDP at Wikipedia

传输层会用到端口号,本文为简单起见,认为每个(网络)进程进行有独立的端口号,例如 ,HTTP 服务运行在 80 端口, SSH 服务运行在 22 端口。

一个 IP 地址加一个传输层端口号定义一个 socket。socket 是唯一的,一个客户端 socket 和一个服务端 socket 定义一个连接(connection),一端发送的数据,另一端就会接收到 。 例如,socket 123.123.123.123:65432 和 socket 112.112.112.112:80 建立的一个连 接,可能是 123.123.123.123 这台机器上的浏览器正在访问位于 112.112.112.112 上 面的 HTTP 服务。

服务端程序一般使用标准的端口号,称为“well known ports”(公认的端口号),见 well known ports at IANAwell known ports at Wikipedia。客户端程序使 用临时端口号,一般从 1023 往上增加,选择一个可用的。

3 Linux 和 Netfilter

Linux 内核中有一个数据包过滤框架(packet filter framework),叫做 netfilter( 项目地址 netfilter.org)。这个框架使得 Linux 机器可以像路由器一 样工作。接下来我们将使用一个命令行工具 iptables 来创建复杂的规则,用于修改和过滤 数据包。毫无意外,和 NAT 相关的最重要的规则,都在 nat 这个(iptables)table 里。这个表有三个预置的 chain:PREROUTING, OUTPUTPOSTROUTING

PREROUTINGPOSTROUTING 是最重要的 chain。如名字所示,PREROUTING chain 负 责处理刚刚到达网络接口的数据包,这时还没有做路由判断,因此还不知道这个包是发往本 机(local),还是网络内的其他主机。包经过 PREROUTING chain 之后,将进行路由判 断。如果目的是本机,那接下来的过程将不涉及 NAT;如果目的是网络内的其他机器,那包 将会被转发到那台机器,前提是这台机器配置了允许转发。

在转发包离开本机之前,它会经过 POSTROUTING chain。对于本机生成的包,这里还有一 点不同:它会先经过 OUTPUT chain,然后再经过 POSTROUTING chain。

要使用 NAT,我们首先需要配置机器。下面的例子中,# 开头的是注释,$> 开头的是命 令。

# IMPORTANT: Activate IP-forwarding in the kernel!

# Disabled by default!
$> echo "1" > /proc/sys/net/ipv4/ip_forward

# Load various modules. Usually they are already loaded 
# (especially for newer kernels), in that case 
# the following commands are not needed.
 
# Load iptables module:
$> modprobe ip_tables

# activate connection tracking
# (connection's status are taken into account)
$> modprobe ip_conntrack

# Special features for IRC:
$> modprobe ip_conntrack_irc

# Special features for FTP:
$> modprobe ip_conntrack_ftp

以上配置应该足够了。如果遇到错误,可能是因为你的内核在编译时没有指定路由相关的选 项,请参考这里

4 例子:将私有网络通过 NAT 连接到互联网

我们知道了 IP 包的结构,也知道了在 Linux 中如何操控数据包,因此接下来就可以应 用它了。NAT 最广泛的使用场景应该就是:一个私有子网的所有机器共享一个公网连接。 接下来我们就从这个例子开始。

4.1 类比:地主和租户的信件收发

我们用一个形象的例子做类比,以更容易理解 NAT。

考虑如下场景:

有一个地主,他的几个租户,和若干个邮递员。

地主有几个信箱可以收发信件,但租户没有,因此,所有直接寄给租户的信件将被邮递员直 接丢弃。但是,租户可以将他们的信件交给地主,地主会将这些信件送到邮局。那么问题是 :租户如何参与收发信件呢?

一种解决方案是:

地主将不同租户的信件放到他自己不同的信箱,并将信件上租户的地址(某种意义上说 是无效地址,因为这个地址的信件会被邮递员直接丢弃)换成自己的某个信箱的地址 。因此,回信也会被邮递员放到地主的相应邮箱,地主可以轻松地判断信件属于哪个 租户并转交给他们(转交之前地主会将自己的地址再修改回租户的地址,因此租户不会发现 信件地址被修改过)。这种方式从某种意义上说是最优的,因为它对租户来说完全透明,他 们完全意识不到邮递员是无法处理直接寄给租户的信件的。

4.2 从租户问题到计算机世界

NAT 的原理和以上租户问题很相似:

  • 租户对应本地子网内的 IP 地址,不同租户(在 NAT 路由器上)由不同端口号(port)区分
  • 地主对应NAT 路由器
  • 信件接收方对应互联网上的任意主机

一个 socket 是一个 IP 地址加一个 port。

租户的通信过程:

  1. 租户将信件放到地主的办公室
  2. 地主将发送地址(此时为租户地址)改成他自己的某个邮箱地址
  3. 地主收到回信时,将接收地址(此时为自己的某个邮箱地址)改成对应租户的地址

本地网络的情形和这个过程很类似:

  1. 子网内的所有主机(客户端)通过 socket 将数据包发送到一个 特定路由器(通过将路由器地址设置为所有机器的网关来实现,数据会通过以太网 或其他底层协议传输)
  2. 路由器将发送方的 socket 替换为自己的一个(还未使用的)socket
  3. 这个 socket 收到的数据,路由器将 socket 地址修改为对应的客户端 socket,并转 发给它

我们假设客户端的网关设置是正确的,那剩下的就是如何配置路由器了。

幸运的是,netfilter 框架会对设置的每条一条(出向或入向)规则,自动设置它的反向规则,因此我们只需要设 置一个方向的规则即可。选择哪个方向来设置规则?通常是选择不确定性小的一个方向。 例如,“替换所有从本地网络发出的数据包的地址”比“如果客户端发送过一些东西给服务端 ,那将服务端发送的数据进行某种方式的修改”要简单的多。

4.3 如何设置规则

我们希望实现的是:从本地网络发出的、目的是公网的包,将发送方地址修改为路由器 的地址。

接下来假设路由器的本地网络走 eth0 端口,到公网的网络走 eth1 端口。那么如 下命令就能完成我们期望的功能

# Connect a LAN to the internet
$> iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

解释:

  • iptables - 配置内核的工具
  • -t nat - 指定对名为 nat 的 iptables table 配置 NAT 规则
  • -A POSTROUTING - 追加(A: Append)规则到 iptables 的 POSTROUTING chain
  • -o eth1 - 指定只对从 eth1 发出的数据包做操作(o: output)
  • -j MASQUERADE - 规则匹配成功后的动作是 masquerade (伪装)数据包,例如将源地址修改为路由器地址

另外需要说明的是,(除了客户端过来的包,)路由器自己的包也会涉及以上处理逻辑,因 为它们也经过 POSTROUTING chain(见上面的图)。然而,因为路由器为客户端做 socket (IP+Port) 转换的时候,会从它的未使用端口中挑选,因此它自身的包所使用的 port 与做 NAT 的 port 肯定是不同的。因此,虽然它自身的包也会经过以上规则,但并不会被修改。

网络接口可以为任意类型,例如 ISDN 和 SDL 接口也是可以的(通常是 ppp0 或者 ippp0)。可以通过 ifconfig 查看所有(active)的接口:

# Display available network interfaces
$> ifconfig

4.4 NAT 的不足

现在,本地计算机可以访问公网,但反过来,我们看看公网上的机器访问本地机器会是 什么情况。

公网上的机器要和本地机器建立连接的话,它唯一能利用的信息就是用路由器的 IP 地址 加一个端口号。大部分情况下,这个端口都是没有被使用的,因此过来的包会被拒绝。即使 运气比较好,这个端口是路由器做 NAT 的一个端口,包仍然很可能会被拒绝,因为这个端 口已经和公网上的其他主机建立连接了。

因此,从公网上的机器向本地机器建立连接几乎是不可能的。

对于常规服务,可以静态地将路由器的端口映射到本地服务,例如,将路由器的 80 端口收到 的包转发到本地机器的 HTTP 服务器。

5 近距离查看 iptables

有了以上基础后,我们来详细查看 iptables 的功能。命令帮助:

# Abstract structure of an iptables instruction:
iptables [-t table] command [match pattern] [action]

对于 NAT,必须要选择 nat table。

5.1 选择 table

接下来的命令都会选择 nat table:

# Choosing the nat-table
# (further arguments abbreviated by [...])
iptables -t nat [...]

manglefilter table 和 NAT 无关,因此本文不介绍。因为 默认的 table 是 filter table,因此我们接下来每次都要指定 -t nat

5.2 几个重要命令

最重要的几个命令:

# In the following "chain" represents
# one of the chains PREROUTING, OUTPUT and POSTROUTING

# add a rule:
$> iptables -t nat -A chain [...]

# list rules:
$> iptables -t nat -L

# remove user-defined chain with index 'myindex':
$> iptables -t nat -D chain myindex

# Remove all rules in chain 'chain':
$> iptables -t nat -F chain

完整 iptables 命令可以查看 man page:

# manual pages of iptables
$> man iptables

q 退出帮助。

5.3 选择匹配模式(pattern)

要对特定包进行处理需要指定匹配模式。如下是几个重要的例子,完整模式信息请参阅 iptables man page。

# actions to be taken on matched packets
# will be abbreviated by '[...]'.
# Depending on the match pattern the appropriate chain is selected.

# TCP packets from 192.168.1.2:
$> iptables -t nat -A POSTROUTING -p tcp -s 192.168.1.2 [...]

# UDP packets to 192.168.1.2:
$> iptables -t nat -A POSTROUTING -p udp -d 192.168.1.2 [...]

# all packets from 192.168.x.x arriving at eth0:
$> iptables -t nat -A PREROUTING -s 192.168.0.0/16 -i eth0 [...]

# all packets except TCP packets and except packets from 192.168.1.2:
$> iptables -t nat -A PREROUTING -p ! tcp -s ! 192.168.1.2 [...]

# packets leaving at eth1:
$> iptables -t nat -A POSTROUTING -o eth1 [...]

# TCP packets from 192.168.1.2, port 12345 to 12356
# to 123.123.123.123, Port 22
# (a backslash indicates contination at the next line)
$> iptables -t nat -A POSTROUTING -p tcp -s 192.168.1.2 \
   --sport 12345:12356 -d 123.123.123.123 --dport 22 [...]

大部分选项都有长短两种格式,例如 --source-s,长格式写起来麻烦,但更易 阅读。

5.4 匹配后的动作

至此,我们已经可以指定匹配模式来过滤包了,接下来就是选择合适的动作。对于 nat table,有如下几种动作:SNAT, MASQUERADE, DNAT, REDIRECT,都需要通过 -j 指定。它们的格式如下(表示的意思将在下一节介绍):

# In the following the table selection, the command and the match pattern
# will be abbreviated using [...]

# Source-NAT: Change sender to 123.123.123.123
$> iptables [...] -j SNAT --to-source 123.123.123.123

# Mask: Change sender to outgoing network interface
$> iptables [...] -j MASQUERADE

# Destination-NAT: Change receipient to 123.123.123.123, port 22
$> iptables [...] -j DNAT --to-destination 123.123.123.123:22

# Redirect to local port 8080
$> iptables [...] -j REDIRECT --to-ports 8080

6 几种 iptables 动作

详细解释一下四种动作。

6.1 SNAT - 修改源 IP 为固定新 IP (静态)

前面的将本地私有网络连接到公网的例子中,我们已经使用了 Source NAT(SNAT )。如名字所暗示,发送方的地址会被静态地修改。

在例子中我们选择 MASQUERADE 的原因在于:对于 SNAT,必须显式指定转换后的 IP。 如果路由器配置的是静态 IP 地址,那 SNAT 是最合适的选择,因为它比 MASQUERADE 更 快,后者对每个包都需要检查指定的输出端口上配置的 IP 地址。

因为 SNAT 只对离开路由器的包有意义,因此它只用在 POSTROUTING chain 中。

# Options for SNAT (abstract of manual page)
--to-source <ipaddr>[-<ipaddr>][:port-port]

6.2 MASQUERADE - 修改源 IP 为动态新 IP(动态获取网络接口 IP)

SNAT 类似,但是对每个包都会动态获取指定输出接口(网卡)的 IP,因此如果接口 的 IP 地址发送了变化,MASQUERADE 规则不受影响,可以正常工作;而对于 SNAT 就必须重新调整规则。

SNAT 一样,MASQUERADE 只对 POSTROUTING chain 有意义。但和 SNAT 不同, MASQUERADE 不支持更详细的配置项了。

6.3 DNAT - 修改目的 IP

如果想修改包的目的 IP 地址,那需要使用 Destination NAT(DNAT)。

DNAT 可以用于运行在防火墙后面的服务器。

显然,接收端修改必须在做路由决策之前,因此 DNAT 适用于 PREROUTINGOUTPUT (本地生成的包)chain

# Options for DNAT (abstract of manual page)
--to-destination <ipaddr>[-<ipaddr>][:port-port] 

6.3 REDIRECT - 将包重定向到本机另一个端口

REDIRECT 是 DNAT 的一个特殊场景。包被重定向到路由器的另一个本地端口,可以实现, 例如透明代理的功能。和 DNAT 一样,REDIRECT 适用于 PREROUTINGOUTPUT chain 。

# Options for REDIRECT (abstract of manual page)
--to-ports <port>[-<port>] 

7 NAT 应用 (Applications)

本文第一个例子(将本地网络连接到公网)的晦涩(cryptic)指令可能让你有些困惑。 之后我们陆续给了一些命令参数的解释,读者应该比之前理解更清楚了一些。问题可能从“ 人们怎么写出这么晦涩的指令的” 变成了 “好的,我该如何使用这些晦涩的指令呢?”本章 将给出一些如何使用 NAT 的例子。应用范围并没有限制,但我将尽力涵盖最常见的一些场 景。

7.1 透明代理

假设我们有一个本地网络,通过 NAT 连接到公网。我们需要在路由器的 8080 端口运行 一个 HTTP 代理,处理本地网络 HTTP 流量。

首先想到的解决方案是:让每个用户设置他们的浏览器使用代理服务器,然后禁止所有 80 端口的出流量。对于很小的网络,这种方式可能让人满意,但无法扩展到很大的网络,因为 这种方式需要对每个客户端进行配置。(也失去了“透明代理”的意义)。

使用 NAT,我们有另一种可能:所有从 80 端口进来的流量,重定向到 8080 端口。相应 的命令:

# Transparent proxying:
# (local net at eth0, proxy server at port 8080)
$> iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 \
   -j REDIRECT --to-ports 8080 

当然,8080 端口要有 HTTP 代理运行。另外,你的服务器可能做一些特殊配置(甚至特殊 编译)才能支持透明代理。

透明代理的缺点是 CPU load 会升高(尤其是大型网络),对太新或太旧的浏览器可能有兼 容性问题。

7.2 绕过防火墙

在继续本文之前,我首先要发出警告:

每位读者在使用以下提供的技术之前,都需要确认这些步骤是否会破坏你们的现有环境! 使用如下命令带来的风险是读者自己的,对不当使用以下命令和技术带来的损坏,我不负 任何责任!

你可能意想不到,NAT 在这种场景下可以派上用场。

我们假设路由器只有少量端口可以被本地网络访问。

首先要找到这些已经被打开的(open)端口。一个的常用工具是 nmap(请只扫描你自己的机器,扫描未知的机器会被认为 是入侵行为的前奏)。

# Scan a machine:
# (Replace www.example.com by an appropriate machine)
$> nmap www.example.com

以上命令会扫描出很多端口,其中大部分处于 “closed” 状态(该端口上没有服务),另 外有一部分处于 “filtered” 状态(该端口上没有连接),可能有一些处于 “open” 状态( 服务正在该端口上运行)。我们假设 5000 以下的端口除了 80 都处于 closed 状态,但是 5000 以上的端口有 可达的。

为了实现可以连接任意外网机器的 5000 以下的任意端口,需要一个位于防火墙之前的机器 (称为跳板机,位置没有关系,只要网络可达,并且它自己没有被防火墙限制),它要能被 访问,并且支持 NAT (iptables)。假设这台机器 IP 为 111.111.111.111

7.2.1 配置内网机器可以 SSH 到跳板机

首先需要 SSH 登录到 111.111.111.111(由于防火墙限制 5000 以下端口,SSH 端口默 认是 22,因此需要在防火墙外面找机器才能登录)。然后在 111.111.111.111 上执行:

# Redirect SSH from port 5000 to port 22:
$> iptables -t nat -A PREROUTING -p tcp --dport 5000 -j REDIRECT --to-ports 22

这个命令做的事情就是,将 5000 端口 进来的流量重定向到本机的 22 端口(SSH)。

然后,我们在防火墙内的机器上,就可以通过 5000 端口用 SSH 访问工作站了,工作站会 将请求转向 111.111.111.11122 端口。

或者,你也可以配置 111.111.111.111 的 SSH 进程直接运行在 5000 端口,这样就不需 要以上 iptables 规则了。

7.2.2 通过跳板机从内网连接到公网举例:邮件服务器 POP3

现在,你可以在跳板机 111.111.111.111 上配置连接其他机器或服务(运行在其他端口 )的 NAT 规则来。

例如,通过跳板机的 5001 端口连接机器 123.123.123.123 的 110 (POP3)端口,执行:

# redirect port 5001 to port 110 (POP3) at 123.123.123.123:
$> iptables -t nat -A PREROUTING -p tcp --dport 5001 \
   -j DNAT --to-destination 123.123.123.123:110
   
# Change sender to redirecting machine:
$> iptables -t nat -A POSTROUTING -p tcp --dport 110 \
   -j MASQUERADE

这里用的是 MASQUERADE,但用 SNAT 也是可以的(-j SNAT --to-source 111.111.111.111)。

用同样的方式,我们可以(从内网)连接到(外网机器的)其他端口,只要跳板机上的可用 端口足够多。即使是安全连接(例如 IMAPS)也可以这样操作,但需要忽略安全证书的警告 (只要我们知道自己在做什么)。对于上面的 POP3 例子,你还需要配置你的邮件客户端, 将服务器地址设置为 111.111.111.111,端口为 5001

7.2.3 通过跳板机绕过 HTTP 监控

假设网络供应商为 80 端口的所有连接设置了代理(例如,透明代理),然后监控所有的网络 流量(内容),我们使用 NAT 可以绕过它。

和上面类似,在跳板机上寻找一个可用端口,例如 5002,做透明代理。

然后,在你自己的(内网)机器上,执行:

# redirect http-Traffic going to Port 80 to 111.111.111.111:5002:
$> iptables -t nat -A OUTPUT -p tcp --dport 80 \
   -j DNAT --to-destination 111.111.111.111:5002

这样就成功绕过了供应商的代理(我们使用透明代理绕过了供应商的透明代理!)。

或者,不需要 iptables 规则,你可以设置浏览器的默认端口是 5002,但是这可能有一些 潜在的问题。

总结本节的透明代理方案步骤:

  1. 首先找到找到一个可用端口,可以让内网机器和跳板机建立 SSH 连接
  2. 静态地将可用端口重定向到期望的地址(大部分情况下都是 HTTP 服务)

如果想实现动态重定向,那两个 TCP 端口(其中一个用于 SSH)和一个 UDP 端口足够了, 据此可以实现从内网机器连接到任意外网机器的任意端口,唯一的不足是每次只能有一个连 接(每种协议)。

7.3 通过 NAT 从外网访问内网服务

运行在 NAT 路由器后面的内网服务器,默认是不能被外部网络直接访问的。例如,假设我 们有一个 HTTP 服务运行在内网机器 192.168.1.2,NAT 路由器的地址是 192.168.1.1 ,并通过另一张有公网 IP 123.123.123.123 的网卡连接到了外部网络。

要使得外网机器可以访问 192.168.1.2 的服务,需要执行:

# redirect http traffic to 192.168.1.2:
$> iptables -t nat -A PREROUTING -p tcp -i eth1 --dport 80 -j DNAT --to 192.168.1.2

然后,就可以通过公网 IP 123.123.123.123 的 80 端口访问 192.168.1.2 的 HTTP 服务了。

8 相关文章

类似的文章:

  1. http://iptables-tutorial.frozentux.net/iptables-tutorial.html, 非常详细的关于 iptables 的文章
  2. http://www.faqs.org/docs/Linux-mini/TransparentProxy.html, 关于透明代理更深入的介绍
  3. http://www.barryodonovan.com/publications/lg/108/, netfilter extension 来实现更多的功能

9 结束语,致谢及其他

netfilter 框架非常强大,而且一旦熟悉其语法后,配置还是比较简单的。基于 iptables/netfilter 可以完成很多功能,虽然一些命令看起来可能比较晦涩,但拿几个例子稍加训练之后,你就 会发现它们都符合特定的模式,可以用于快速简单的管理。

感谢每一位能读到这里的耐心读者 :-) 另外我还要感谢我的运行 SUSE 10.1 的笔记本,没 有在写作本文的时候挂掉。

我的母语不是英语,因此如果读者能指出本文措辞语句的一些可改进之处,我将非常高兴。 这里 (见原文,这里不贴是防止给原作者带去潜在的垃圾邮件,事实上原作者贴的是图片而不是 文本,也应该有此考虑)是我的联系邮箱。