详解Linux下iptables中的DNAT与SNAT设置
dnat(destination network address translation,目的地址转换) 通常被叫做目的映谢。而snat(source network address translation,源地址转换)通常被叫做源映谢。
这是我们在设置linux网关或者防火墙时经常要用来的两种方式。以前对这两个都解释得不太清楚,现在我在这里解释一下。
首先,我们要了解一下ip包的结构,如下图所示:
在任何一个ip数据包中,都会有source ip address与destination ip address这两个字段,数据包所经过的路由器也是根据这两个字段是判定数据包是由什么地方发过来的,它要将数据包发到什么地方去。而iptables的dnat与snat就是根据这个原理,对source ip address与destination ip address进行修改。
然后,我们再看看数据包在iptables中要经过的链(chain):
图中正菱形的区域是对数据包进行判定转发的地方。在这里,系统会根据ip数据包中的destination ip address中的ip地址对数据包进行分发。如果destination ip adress是本机地址,数据将会被转交给input链。如果不是本机地址,则交给forward链检测。
这也就是说,我们要做的dnat要在进入这个菱形转发区域之前,也就是在prerouting链中做,比如我们要把访问202.103.96.112的访问转发到192.168.0.112上:
iptables -t nat -a prerouting -d 202.103.96.112 -j dnat --to-destination 192.168.0.112
这个转换过程当中,其实就是将已经达到这台linux网关(防火墙)上的数据包上的destination ip address从202.103.96.112修改为192.168.0.112然后交给系统路由进行转发。
而snat自然是要在数据包流出这台机器之前的最后一个链也就是postrouting链来进行操作
iptables -t nat -a postrouting -s 192.168.0.0/24 -j snat --to-source 58.20.51.66
这个语句就是告诉系统把即将要流出本机的数据的source ip address修改成为58.20.51.66。这样,数据包在达到目的机器以后,目的机器会将包返回到58.20.51.66也就是本机。如果不做这个操作,那么你的数据包在传递的过程中,reply的包肯定会丢失。
注意,dnat target只能用在nat表的preouting 和 output 链中,或者是被这两条链调用的链里。但还要注意的是,包含dnat target的连不能被除此之外的其他链调用,如postrouting。
table 6-16. dnat target
option --to-destination
example iptables -t nat -a prerouting -p tcp -d 15.45.23.67 --dport 80 -j dnat --to-destination 192.168.1.1-192.168.1.10
explanation指定要写入ip头的地址,这也是包要被转发到的地方。上面的例子就是把所有发往地址15.45.23.67的包都转发到一段lan使用的私有地址中,即192.168.1.1到192.168.1.10。如前所述,在这种情况下,每个流都会被随机分配一个要转发到的地址,但同一个流总是使用同一个地址。我们也可以只指定一个ip地址作为参数,这样所有包都被转发到同一台机子。我们还可以在地址后指定一个或一个范围的端口。比如:--to-destination 192.168.1.1:80或192.168.1.1:80-100。snat的语法和这个target的一样,只是目的不同罢了。要注意,只有先用--protocol指定了tcp或udp协议,才能使用端口。
因为dnat要做很多工作,所以我要再啰嗦一点。我们通过一个例子来大致理解一下它是如何工作的。比如,我想通过internet连接发布我们的网站,但是http server在我们的内网里,而且我们对外只有一个合法的ip,就是防火墙那个对外的ip——$inet_ip。防火墙还有一个内网的ip——$lan_ip,http server的ip是%http_ip(当然这是内网的了)。为了完成我们的设想,要做的第一件事就是把下面这个简单的规则加入到nat表的prerouting链中:
iptables -t nat -a prerouting --dst$inet_ip -p tcp --dport 80 -j dnat / --to-destination $http_ip
现在,所有从internet来的、到防火墙的80端口去的包都会被转发(或称作被dnat)到在内网的http服务器上。如果你在internet上试验一下,一切正常吧。再从内网里试验一下,完全不能用吧。这其实是路由的问题。下面我们来好好分析这个问题。为了容易阅读,我们把在外网*问我们服务器的那台机子的ip地址记为$ext_box。
包从地址为$ext_box的机子出发,去往地址为$inet_ip的机子。
包到达防火墙。
防火墙dnat(也就是转发)这个包,而且包会经过很多其他的链检验及处理。
包离开防火墙向$http_ip前进。
包到达http服务器,服务器就会通过防火墙给以回应,当然,这要求把防火墙作为http到达$ext_box的网关。一般情况下,防火墙就是http服务器的缺省网关。
防火墙再对返回包做un-dnat(就是照着dnat的步骤反过来做一遍),这样就好像是防火墙自己回复了那个来自外网的请求包。
返回包好像没经过这么复杂的处理、没事一样回到$ext_box。
现在,我们来考虑和http服务器在同一个内网(这里是指所有机子不需要经过路由器而可以直接互相访问的网络,不是那种把服务器和客户机又分在不同子网的情况)的客户访问它时会发生什么。我们假设客户机的ip为$lan_box,其他设置同上。
包离开$lan_box,去往$inet_ip。
包到达防火墙。
包被dnat,而且还会经过其他的处理。但是包没有经过snat的处理,所以包还是使用它自己的源地址,就是$lan_box(译者注:这就是ip传输包的特点,只根据目的地的不同而改变目的地址,但不因传输过程要经过很多路由器而随着路由器改变其源地址,除非你单独进行源地址的改变。其实这一步的处理和对外来包的处理是一样的,只不过内网包的问题就在于此,所以这里交代一下原因)。
包离开防火墙,到达http服务器。
http服务器试图回复这个包。它在路由数据库中看到包是来自同一个网络的一台机子,因此它会把回复包直接发送到请求包的源地址(现在是回复包的目的地址),也就是$lan_box。
回复包到达客户机,但它会很困惑,因为这个包不是来自它访问的那台机子。这样,它就会把这个包扔掉而去等待“真正”的回复包。
针对这个问题有个简单的解决办法,因为这些包都要进入防火墙,而且它们都去往需要做dnat才能到达的那个地址,所以我们只要对这些包做snat操作即可。比如,我们来考虑上面的例子,如果对那些进入防火墙而且是去往地址为$http_ip、端口为80的包做snat操作,那么这些包就好像是从$lan_ip来的了。这样,http服务器就会把回复包发给防火墙,而防火墙会再对包做un-dnat操作,并把包发送到客户机。解决问题的规则如下:
iptables -t -nat -a postrouting -p tcp --dst$http_ip --dport 80 -j snat / --to-source $lan_ip
要记住,按运行的顺序postrouting链是所有链中最后一个,因此包到达这条链时,已经被做过dnat操作了,所以我们在规则里要基于内网的地址$http_ip(包的目的地)来匹配包。
警告:我们刚才写的这条规则会对日志产生很大影响,这种影响应该说是很不好的,因为来自internet包在防火墙内先后经过了dnat和snat处理,才能到达http服务器(上面的例子),所以http服务器就认为包是防火墙发来的,而不知道真正的源头是其他的ip。这样, 当它记录服务情况时,所有访问记录的源地址都是防火墙的ip而不是真正的访问源。我们如果想根据这些记录来了解访问情况就不可能了。因此上面提供的“简单办法”并不是一个明智的选择,但它确实可以解决“能够访问”的问题,只是没有考虑到日志而已。
其他的服务也有类似的问题。比如,你在lan内建立了smtp服务器,那你就要设置防火墙以便能转发smtp的数据流。这样你就创建了一个开放smtp的中继服务器,随之而来的就是日志的问题了。
一定要注意,这里所说的问题只是针对没有建立dmz或类似结构的网络,并且内网的用户访问的是服务器的外网地址而言的。(译者注:因为如果建立的dmz,或者服务器和客户机又被分在不同的子网里,那就不需要这么麻烦了。因为所有访问的源头都不在服务器所在的网里,所以就没有必要做snat去改变包的源地址了,从而记录也就不是问题了。如果内网客户是直接访问服务器的内网地址那就更没事了)
比较好的解决办法是为你的lan在内网建立一台单独的dns服务器(译者注:这样,内网客户使用网站名访问http服务器时,dns就可以把他解析成内网地址。客户机就可以直接去访问http服务器的内网地址了,从而避免了通过防火墙的操作,而且包的源地址也可以被http服务器的日志使用,也就没有上面说的日志问题了。),或者干脆建立dmz得了(这是最好的办法,但你要有钱哦,因为用的设备多啊)。
对上面的例子应该考虑再全面些,现在还有一个问题没解决,就是防火墙自己要访问http服务器时会发生什么,能正常访问吗?你觉得呢:)很可惜,现在的配置还是不行,仔细想想就明白了。我们这里讨论的基础都是假设机子访问的是http服务器的外网地址,那客户机就会看到页面内容,不过这不是它想看到的(它想要的在dnat上了),如果没有http服务,客户就只能收到错误信息了。前面给出的规则之所以不起作用是因为从防火墙发出的请求包不会经过那两条链。还记得防火墙自己发出的包经过哪些链吧:) 我们要在nat表的output链中添加下面的规则:
iptables -t nat -a output --dst$inet_ip -p tcp --dport 80 -j dnat / --to-destination $http_ip
有了最后这条规则,一切都正常了。和http服务器不在同一个网的机子能正常访问服务了,和它在一个网内的机子也可以正常访问服务了,防火墙本身也可以正常访问服务了,没有什么问题了。 我想大家应该能明白这些规则只是说明了数据包是如何恰当的被dnat和snat的。除此之外,在filter表中还需要其他的规则(在forward链里),以允许特定的包也能经过前面写的(在postrouting链和output链里的)规则。千万不要忘了,那些包在到达forward链之前已经在prerouting链里被dnat过了,也就是说它们的目的地址已经被改写,在写规则时要注意这一点。
上一篇: [ Java面试题 ]基础篇之一