1. 简介
欢迎,亲爱的读者。
你将要深入迷人的(有时是令人厌烦的)NAT世界:网络地址转换,这篇HOWTO可以成为你的Linux2.4内核及其以后的准确指南。
在Linux2.4(内核版本),引入了一个叫“netfilter”的部分,专门用于拆分(mangling) (IP)包的。他上一层提供NAT,是完全依靠以前的内核制作的。
(译者注:mangle实在找不出什么合适的翻译,抱歉)
(C) 2000 Paul `Rusty' Russell. Licensed under the GNU GPL.
--------------------------------------------------------------------------------
2、 官方站点及列表位置
这里有三个官方站点:
o Thanks to Filewatcher http://netfilter.filewatcher.org.
o Thanks to The Samba Team and SGI http://netfilter.samba.org.
o Thanks to Harald Welte http://netfilter.gnumonks.org.
你可以通过以下站点访问全部相关站点。
http://www.netfilter.org and http://www.iptables.org
以下是netfilter官方邮件列表
http://www.netfilter.org/contact.html#list.
--------------------------------------------------------------------------------
2、1 什么是网络地址转换(Network Address Translation)?
通常,,网络中的(IP)包从他们的源(地址)出发(比如你家的电脑),到他们的目的地(比如www.gnumonks.org),会经过很多不同的连接(links):例如我所在澳大利亚就有19个。这些连接不会真去修改你的包:他们只是照原样传出去。
(译者注:这里的links应当认为是所有网络节点,包括主机、路由器等。通常,路由器并不是原样传送包,它至少会修改其中一点:TTL)
如果这些连接有一个做NAT,那么它(们)就会修改通过它们的包的源或者目标(地址)。正如你猜象的那样,这并非系统设计成那样的,而是NAT做了一些事情。通常进行NAT的连接(主机、服务器、路由器)会记住它是如何拆分包的,而当另一头响应的包通过时,它会对响应的包做相反的拆分,所以世界仍在运转。
(译者注:这一段的mangle应该想象为修改更合适)
--------------------------------------------------------------------------------
2、2 我为什么要NAT?
在完美的世界里,你不需要。同时,主要的理由是:
用调制解调器连接Internet
在你拨号上网时,大部分ISP只会给你一个IP地址,你可以发送你想发送的任何源地址包,但是只有响应这个(ISP给你的)地址的包才会返回。如果这种情况下你想有多台不同的机器上网(比如一个家庭网络),你就需要NAT。
这是现在NAT用得最多的功能,Linux世界的"masquerading"(伪装)非常出名,我称之为SNAT(SNAT即Source NAT,源地址转换),因为你改变了第一个包的源地址。
(译者:关于IP数据报的第一个包等内容,请参见各TCP/IP书籍)
多(重)服务器
有时你想改变进入网络中的包的目标地址(路由)。经常的,这是因为(就像上面的例子),你只有一个IP地址,但是你希望大家可以通过到那个“真实”的IP地址进入内部。如果你重写了进入包的目标地址,这样就没问题了。这种NAT在以前的Linux版本中被称为端口转发。
一个常见的变种是负载均衡,在一组机器上做映射。如果你要进行严格的比例限制, 可能需要参考Linux Virtual Server。http://linuxvirtualserver.org
透明代理
有时你可能想要经过你的Linux的包被送往本机的一个程序。这就需要用到透明代理了:代理是位于你的网络和外部世界之间的一个程序,帮助二者进行通信。之所以称为透明,是你的网络根本不知道他在和代理交谈,当然直到代理没有正常工作。
Squid可以配置为干这项工作,在以前Linux版本中它被称作重定向或者透明代理。
--------------------------------------------------------------------------------
3、 NAT的两种类型
我把NAT分为两种不同的类型:源NAT(SNAT)和目标NAT(DNAT)。(译者注:以下不再翻译SNAT和DNAT,直接用Source NAT和Destination NAT)
Source NAT是指修改第一个包的源地址:也就是说,改变连接的来源地。Source NAT会在包送出之前的最后一刻做好post-routing(动作),伪装是SNAT的一种特殊形式。
Destination NAT 是指修改第一个包的目标地址:也就是说,改变连接的目的地。Destination NAT 总是在包进入以后(马上)进行before routing(动作)。端口转发、负载均衡和透明代理都属于DNAT。
--------------------------------------------------------------------------------
4、 从2.0和2.2内核的快速转换
如果你还在为从2.0(ipfwadm)到2.2(ipchains)的转换手忙脚乱的话,很抱歉。不过这也算是个喜忧半参的消息。
首先,你可以轻松的使用ipchains和ipfwadm,就像从前一样。不过你需要安装最新发布的netfilter中的“ipchains.o”或者“ipfwadm.o”内核模块。它们是互斥的(你会被警告),而且不能和任何其他netfilter模块结合。
一旦这其中某个模块被载入,你可以像以前一样使用ipchains和ipfwadm,不过仍有以下区别:
用ipchains -M -S,或者用ipfwadm -M -s设置伪装超时不再有效。因为超时已经转移到新的NAT构架中,所以这不能做任何事。
在详细的伪装列表中,init_seq、delta和previous_delat字段始终为零。
归零和列表计数器的 -Z -L不再有效:计数器不能被归零。
这类向后兼容的部分可能和大部分连接都不能有效配合:不要在你的公司网关中使用
开发者们还要注意:
无论是否使用伪装,现在可以绑定61000 - 65095之间的端口。以前的伪装代码占用了这部分端口,因此不能使用。
尚未成文的“getsockname”,透明代理程序可以用来发现那些已不再工作的连接的真实目的地址。
尚未成文的“bind-to-foreign-address”同样还未启用:这个用于完整透明代理的设想。
--------------------------------------------------------------------------------
4、1 我只想伪装!救命!
这是绝大部分人想要的。如果你用PPP拨号上网来动态得到IP (如果你不知道,那应该就是的)你可能只想告诉你的机器,所有来自内部网络的包,要看上去同PPP连接服务器上的包一样。
# 装载NAT模块(这取代了其他的)
modprobe iptable_nat
# 在NAT表中(-t nat),路由后 POSTROUTING 加入一条规则(-A)
# 所有由ppp0送出的包(-o ppp0) 会被伪装( -j MASQUERADE)。
iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE
# 开启IP转发
echo 1 > /proc/sys/net/ipv4/ip_forward
注意这时你没有做任何的包过滤:如果需要,参见 the Packet Filtering HOWTO。
--------------------------------------------------------------------------------
4、2 那么ipmasqadm呢?
这个完全取决于用户,所以我不担心向后兼容的问题。你可以简单的使用“iptables -t nat”做端口转发。例如,在Linux2.2你要做:
# 在2.2内核,把指向1.2.3.4 8080端口的TCP包转到192.168.1.1的80端口
ipmasqadm portfw -a -P tcp -L 1.2.3.4 8080 -R 192.168.1.1 80
现在你可以这样:
# 2.4内核,在NAT(-t nat)表中加入一条规则,在路由之前(-A PREROUTING)指向
# 1.2.3.4(-d 1.2.3.4)8080端口(--dport 8080)的TCP包(-p tcp)目标地址(-j DNAT)
# 被重定向到 192.168.1.1的80端口(--to 192.168.1.1:80)。
iptables -A PREROUTING -t nat -p tcp -d 1.2.3.4 --dport 8080 -j DNAT --to 192.168.1.1:80
--------------------------------------------------------------------------------
5、NAT可以控制什么
你需要创建NAT规则,以告诉内核哪些连接将被改变和如何改变。要做到这一点,我们要用到一个用处很多的iptables工具,并告诉它用指定的“-t nat”选项修改NAT表。
NAT规则表包含三个称为“链”的列表:每个规则都按顺序检查包,直到有一个匹配。其中两个被称为PREROUTING(用于 Destination NAT,当包进入时检查),POSTROUTING(用于Source NAT,包离开时检查),第三个叫OUTPUT,这里可以忽略。
如果我有足够的艺术天分的话,下面的见图会准确的说明上述概念:
_____ _____
/ /
PREROUTING -->[Routing ]----------------->POSTROUTING----->
D-NAT/ [Decision] S-NAT/
| ^
| |
| |
| |
| |
| |
| |
--------> Local Process ------
上述每一点,当我们查看连接(中)的包时,如果是一个新的连接,我们查看 NAT表中相对应的链,看看需要做些什么。其结果就会作为对这个连接后面所有包 的反应。
(译者注:此处的连接是指一个HTTP会话之类的连接,而非物理上的线路、节点)
--------------------------------------------------------------------------------
5、1 用iptables做简单的选择
下面列出了iptables的一些标准选项。所有双横杠(--)的选项都是可以缩写的。只要iptables可以将它们与其他选项区分开来就够了。如果你的内核是以模块方式支持iptables的,你需要先用命令:insmod ip_tables来加载ip_tables.o
这是最重要的选项表格选择,“-t”。对所有NAT操作,你都需要使用'-t nat'以指定NAT表。其次重要的是'-A',添加一个新的规则到链的末尾(例如'-A POSTROUTING'到POSTROUTING链),或者'-I'从最开始插入一个规则(例如'-I PREROUTING')。
你可以指定你想要进行NAT的包的源(地址)('-s' 或者'--source')和目的(地址)('-d' 或 '--destination')。这两个选项可以跟单个IP地址(例如192.168.1.1),一个名字(例如www.gnumonks.org),或者一个网络地址(例如192.168.1.0/24或192.168.1.0/255.255.255.0[译者:这二者是等价的,只是表示方法不同])。
你可以指定进入或送出的匹配接口。不过能否指定取决于你想要写入规则的那个链:PREROUTING链你只能选择进入接口,POSTROUTING你只能选择送出接口。如果用错了,iptables会给出一个错误。
--------------------------------------------------------------------------------
5、2 关于应当挑选哪些包来拆分(mangle)的要点
我上面说了你可以指定源地址和目标地址。如果忽略源地址选项,那么所有源地址都会被匹配,同样,如果忽略目标地址,所有目标地址都将被匹配。
你还可以标出一个指定的协议('-p'或'--protocol'),诸如TCP或者UDP;那么只有这类协议的包会被匹配。这么做的主要原因是指定了协议,就可以增加额外的选项:指定'--source-port'源端口和'--destination-port'目的端口选项(可缩写为'- sport'和'-dport')。
这些选项让你可以只匹配那些特定源端口及目标端口的包。这些对于重定向Web请求(TCP 80或8080端口)而不影响其他包非常有用。
这些选项必须跟在'-p'选项后面(这可能会对加载该协议的连接库有一定影响)。你可以使用端口号,或者来自/etc/serverices文件的(端口)名。
所有这些你可以对一个包作出的不同选择都详细的列在那详细得可怕的使用手册中了(man iptables)。(译者注:参见iptables man page中文版)
--------------------------------------------------------------------------------
6、说说如何拆分包吧
现在我们知道如何选择我们想要拆分的包了。为完成我们的规则,我们需要准确的告诉内核我们想要它如何做。
--------------------------------------------------------------------------------
6、1 Source NAT
你想要进行Source NAT,改变连接的源地址。这在POSTROUIING链中完成,就在它将送出去的最后一刻。这是一个重要的细节,所有Linux本机上的其他任何东西(路由、包过滤)都会看见那个尚未改变的包。也意味着'-o'(送出接口)选项可用了。
用指定'-j SNAT'来进行Source NAT,'--to-source'选项指定一个或一段IP地址,(加上)一个或一段可选的端口号(只能用于UDP和TCP协议)。
# 改变源地址为1.2.3.4
# iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to 1.2.3.4
# 改变源地址为1.2.3.4、1.2.3.5或者1.2.3.6
# iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to 1.2.3.4-1.2.3.6
# 改变源地址为1.2.3.4,端口1-1023
# iptables -t nat -A POSTROUTING -p tcp -o eth0 -j SNAT --to 1.2.3.4:1-1023
--------------------------------------------------------------------------------
6、1、1 伪装
Source NAT的一个特例被称作伪装。它只能被用于动态分配IP地址的情况。例如标准拨号服务(静态IP地址请用SNAT)。
你无需为IP伪装明确指定源地址。它会使用包送出的那个接口(地址)作为源地址。不过更重要的是,如果那个线路关闭了的话,连接(无论如何都会丢失了)会被忘掉,意味着启用新的IP后返回的包就会有点问题了(指那些响应掉线前发出的包的包)。
# 伪装所有由ppp0送出的东西
# iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE
--------------------------------------------------------------------------------
6、2 Destination NAT
用于PREROUTING链,包刚刚进入的时候。意味着本机上的任何东西看见的都是“真正”的目的地(译者注:即已修改过的目的地址)。也意味着'-i'(进入接口)可用了。
用指定'-j DNAT'来进行Destination NAT,'--to-destination'选项指定定一个或一段IP地址,(加上)一个或一段可选的端口号(只能用于UDP和TCP协议)。
# 改变目标地址为5.6.7.8
# iptables -t nat -A PREROUTING -i eth0 -j DNAT --to 5.6.7.8
# 改变目标地址为5.6.7.8、5.6.7.9或5.6.7.10
# iptables -t nat -A PREROUTING -i eth0 -j DNAT --to 5.6.7.8-5.6.7.10
# 改变Web传送的目标地址为5.6.7.8,8080端口
# iptables -t nat -A PREROUTING -p tcp --dport 80 -i eth0 -j DNAT --to 5.6.7.8:8080
--------------------------------------------------------------------------------
6、2、1 重定向
Destination NAT的一个特例被称为重定向。它相当于对进入接口进行DNAT的简单方便的一种形式。
# 发送进入的80端口的Web传输到我们的Squid(透明)代理
# iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 80 -j REDIRECT --to-port 3128
注意Squid需要被配置为透明代理。
--------------------------------------------------------------------------------
6、3 深层次的映射
还有一些可能大部分人不会用到的NAT的细节。
--------------------------------------------------------------------------------
6、3、1 一个范围内多地址的选择
如果指定了IP地址的范围,那么机器会选择当前使用最少的那个IP地址。这就实现了最俭朴的负载均衡。
--------------------------------------------------------------------------------
6、3、2 建立空的NAT映射。
你可以使用 '-j ACCEPT' 目标让连接通过,不需要NAT参与。
--------------------------------------------------------------------------------
6、3、3 标准NAT行为
默认行为是根据用户给定的内在约束规则,对连接作最小的改动。也就是除非必要 不要进行端口重映射。
--------------------------------------------------------------------------------
6、3、4 内部源端口映射
如果其他的连接覆盖了一个连接,即使这个连接不需要使用NAT,源地址转换仍会发生。考虑IP伪装,这种情况就非常普遍。
1、一个从192.168.1.1 1024端口到www.netscape.com 80端口的Web连接已建立
2、它被伪装成IP伪装服务器的IP地址(1.2.3.4)
3、IP伪装服务器试图建立一个从www.netscape.com 80端口到1.2.3.4 1024端口的Web连接(它自己的外部接口IP地址)
4、NAT代码会修改第二个连接的源地址到1025,这样两个(连接)就不会冲突了。
当这种内部源地址映射发生时,端口分为三级:
512以下的端口
512至1023之间的端口
1024以上的端口
内部端口映射决不会被映射到(除此之外的)其他种类。
--------------------------------------------------------------------------------
6、3、5 如果NAT失败会怎样?
如果无法按照用户请求的那样,为连接建一个单独的映射,(包)会被删除。这也适用于那些无法被归为任何连接的包,因为它们是畸形的,或者是主机内存溢出了。
--------------------------------------------------------------------------------
6、3、6 多重映射,重叠和冲突
你的NAT规则可以把包映射到相同的范围。NAT代码聪明到可以避免它们的冲突。因此,两条规则把192.168.1.1和192.168.1.2的源地址映射都映射到1.2.3.4是没有问题的。
而且,你可以映射到真实的、已在使用的IP地址,只要那些地址也通过这个服务器。所以如果你分配到一个网络(1.2.3.0/24),但有一个内部网络使用了这些地址,另一个使用的是私有地址192.168.1.0/24,你可以简单的NAT 192.168.1.0/24的源地址到1.2.3.0网络,不必担心冲突。
# iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth1 -j SNAT --to 1.2.3.0/24
相同的逻辑也适用于NAT服务器本身的地址。这就是得以伪装工作的原因(由伪装后的包和来自本身的“真实”的包共享一个接口地址)。
甚至,你可以映射相同的包到许多不同的目标,它们会被共享。例如,如果你不希望映射任何东西到1.2.3.5,你可以这样做:
# iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth1 -j SNAT --to 1.2.3.0-1.2.3.4 --to 1.2.3.6-1.2.3.254
--------------------------------------------------------------------------------
6、3、7 修改本地生成的连接的目标地址
NAT代码允许你插入DNAT规则到OUTPUT链,不过这在2.4中尚未完全支持(可以使用,但必须用新的配置选项,某些测试中的代码。所以除非有人在疯狂的写这部分代码,我不相信它会很快实现)。
当前的限制是你只能修改目标地址到本机(例如'-j DNAT --to 127.0.0.1'), 不能到任何其他机器,否则相应可能不能够被正确转换。
--------------------------------------------------------------------------------
7、 特定的协议
有些协议不希望被NAT。这些协议,两种延伸必须指明:一个是协议的连接跟踪,一个是真实NAT。
在发布的netfilter中,有可用的FTP模块:ip_conntrack_ftp.o和ip_nat_ftp.o。如果你加载了任一种模块到你的内核(或者编译进去),那么任何关于FTP连接的NAT都是可行的。如果没有,那么只能使用被动FTP(passive ftp),而且如果做了一些Source NAT,它(指FTP)也许不能可靠的工作。
--------------------------------------------------------------------------------
8、 关于NAT的警告
如果你对连接做NAT,所有双向传送的包(进入和送出网络的)必须通过NAT服务器,否则NAT服务器的工作可能不可靠。特别是,连接跟踪代码重组了分片,也就意味着不光是连接跟踪不能可靠工作,甚至所有包都无法通过,因为分片被丢弃。
--------------------------------------------------------------------------------
9、 Source NAT 和路由
如果你要做SNAT,你必须注意所有机器被SNAT的包的回应都将发送到NAT服务器。例如,如果你映射了一些送出的包的源地址为1.2.3.4,那么外部的路由器必须知道发送回应包的地址到NAT服务器。可以这样做:
1、如果你对本机地址做SNAT(路由等所有事情都正常),你不需要做任何事。
2、如果你在本地LAN上做SNAT到未用地址(例如,你映射为1.2.3.99,你的1.2.3.0/24网络中未用的IP),你的NAT服务器需要像那个地址(99)一样正确响应ARP请求。最简单的办法是建立一个IP别名,例如:
# ip address add 1.2.3.99 dev eth0
3、如果你对完全不同的地址做SNAT,你必须保证被SNAT的包到达的机器会返回NAT服务器。如果NAT服务器是它们的默认网关,那么就已经行了,否则你需要发布一个路由(如果运行了路由协议)或者对每个机器手工添加路由。
--------------------------------------------------------------------------------
10、 同一个网络内的Destination NAT。
如果你要对同一个网络做端口转发,你需要确认所有以后的包和回应包都通过NAT服务器(这样它们才能被修改)。NAT代码现在(自2.4.0- test6),会屏蔽掉同组的被NAT的包送出的ICMP重定向,不过收到的服务器会继续尝试直接响应客户。(不会理解这个回应)
经典的情况是,内部人员试图访问你的“公用”Web服务器,而它实际上从公用地址(1.2.3.4)被DNAT到内部机器(192.168.1.1),例如:
# iptables -t nat -A PREROUTING -d 1.2.3.4 -p tcp --dport 80 -j DNAT --to 192.168.1.1
一种办法是运行一台内部DNS服务器,它知道你的公用(外部)Web服务器的真实的(内部)IP地址,并转发所有其他请求到外部DNS服务器。也就是说你的Web服务器能够记录真实的内部IP地址。
另一种办法是让NAT服务器映射那些连接的源地址到它自己,让服务器通过它发出响应。例如,我们可以这样做(假设NAT服务器内部IP地址是192.168.1.250):
# iptables -t nat -A POSTROUTING -d 192.168.1.1 -s 192.168.1.0/24 -p tcp --dport 80 -j SNAT --to 192.168.1.250
因为PREROUTING规则会首先运行,对内部Web服务器来说,包的去向早已确定。我们可以确定好源IP地址。
--------------------------------------------------------------------------------
11. Thanks
Thanks first to WatchGuard, and David Bonn, who believed in the netfilter idea enough to support me while I worked on it. And to everyone else who put up with my ranting as I learnt about the ugliness of NAT, especially those who read my diary.
首先感谢在我工作期间相信netfilter设想并支持我的WatchGuard和David Bonn。以及所有对NAT提出指正的朋友,尤其是读过了我的日记的。
Rusty.