关键字:iproute2,iptables,NAT,ip address label
这篇的内容其实很基本,但是标题其实想了很久才写上去,因为要为这些内容写一个合适的标题实在是太困难了。这一方面是这件事情的折腾有太多不得已的理由,也有太多没法写在Blog里面的关键字。而另一方面是在因为这件事情过于基本了,以至于如果认真的相信计算机网络技术基础这门课程的基本内容(话说当年考试只扣了一分)就什么问题都不会有的分分钟搞定的事情。而自己纯粹没有把事情从头用一种严谨客观的方式去思考,所以足足折腾了10天才明白这是怎么回事。
除了罗嗦一下现代Linux系统里面进行网络参数设定的一些基本工具外,这篇Blog涉及的内容有这样几个:(1)Linux系统,准确的说是内核版本2.6以上的Ubuntu系统里面,IP数据包的路由过程是怎样的。(2)iptables究竟在转发这件事情上起了什么作用。(3)对于同一个NIC(Network Interface Card,就是网卡端口)来说,如果绑定了多于一个的IP地址,那么如何在数据包收发的时候进行IP地址的区分。
先从最基本的说起,在现代Linux系统里面,主要是指内核版本2.6以后的Linux系统,要进行网络参数的管理,例如ip地址设定,路由设定等等,所需要用到的命令已经不是经典系统里面的ifconfig命令和route命令了,这些功能都被集成到了一个新的工具里面,命令ip。这个命令来自于软件包iproute2。我们在这篇Blog里面用到的内容主要包括ip route,ip addr,ip rule这三个。
然后我们说路由设定的事情。一个基本的路由设定差不多是下面这个样子的:
ip route add xxx.xxx.xxx.xxx/dd via yyy.yyy.yyy.yyy dev ethN
这其中xxx.xxx.xxx.xxx数据包要被送达的最终地址,yyy.yyy.yyy.yyy是下一跳的地址,或者叫在当前机器看起来的网关地址,跟Windows系统莱斯,这个地址可以写网关的地址,也可以直接写本机出口网卡的地址,但是,如果你打算在一个网卡上绑多于一个的ip地址,最好还是写网关地址。ethN是本机出口网卡的设备名。
这里必须明确的一点是,标准的,或者说RFC里面定义的路由表是完全根据目标地址,也就是xxx.xxx.xxx.xxx以及TOS(Type of Service)来决定的,而在实际中,不论是Windows还是Linux,貌似都没有直接实现TOS路由这件事情,因此,实际上只是由目标地址来决定这个路由的走向的。
以及,更需要注意的是,路由只是将数据包进行转发,并不会对数据包的内容进行改动,包括源地址和目的地址,都不会改动。
这里还需要说明的是,Linux是具有源地址路由这个事情的,这是由ip rule来做的。具体来说就是Linux的255张路由表可以按照不同优先级根据源地址不同来进行查找。例如如下的
ip rule add from kkk.kkk.kkk.kkk pref M table Z
就是对于kkk.kkk.kkk.kkk发来的数据包,以优先级M,查阅路由表Z,决定其路由。
然后我们接着说,如果你在一个NIC上放了两个IP地址会发生什么?答案是什么都不会发生,这根本就是一个允许的事情,RFC文档里面就是这么定的,要保证IP层连接,NIC上至少需要一个IP地址,至于多几个,这没什么关系。不论是Windows还是Linux,这都是允许的。要给一个NIC放几个ip地址,这就要用下面的方法了。
ip addr add xxx.xxx.xxx.xxx dev ethN label ethN:M
这里xxx.xxx.xxx.xxx就是要放到网卡ethN上面去的ip地址,后面的那个ethN:M叫做地址标签,一般形如eth0:0,eth0:1的。千万要注意,这个只是个标签,并不是任何虚拟设备,不论eth0:0还是eth0:1对应的设备都是eth0。在路由表里指定eth0:0或者eth0:1都是会变成eth0且不加区分的。根据Linux官方文档的说法,这个功能的存在仅仅是为了兼容以前的设备别名(alias)而存在的。
那么,在这种情况下,由本机发出的一个数据包,他的源地址究竟是哪个ip地址呢?先说一个最简单粗暴的结论,就是如果你的两个ip地址来自同一个CIDR子网,这根本不重要,因为你的机器总能收到回传数据的。然而,如果你非要纠结这件事情,那就要使用ip route命令中的src参数了。具体来说,就是
ip route add xxx.xxx.xxx.xxx/dd via yyy.yyy.yyy.yyy dev ethN src zzz.zzz.zzz.zzz
src参数会指定一个已经被放置到本机的某一个网卡上的ip地址,这时候,你发出的数据包的源地址一般来说,就会被填上zzz.zzz.zzz.zzz这个地址。但是,这并不是一定的。因为,还有两个个必须考虑到的因素,一个是,数据包并不来自本机,另一个就是iptables可能会做NAT。
先说数据包不来自本机是怎么回事。如果你的内核里面的ipv4的forward功能已经用sysctl命令打开了,那么对于本机网卡上接受到的目的地址不属于本机但路由经过本机的数据包,是会经由本机路由判断后传递到对应的网卡上发出去的。一定注意,是经由本机路由判断后才发出去的。这种情况下,数据包的源地址是发送数据的那个机器的地址,而并不是本机地址,本机路由中的src参数对于这个数据包的源地址是没有任何影响的。本机只会按照dev参数的要求将其送达对应的网络接口,以及对应的下一跳路由器。
接着说iptables干了啥,iptables在这件事情上最主要的是会通过NAT影响发出数据包的源地址。如果你要用Ubuntu做一个网关,那么很多时候会用到下面这个命令:
iptables -t nat -A POSTROUTING -j MASQUERADE
必须说明的是,这个命令实际是做了源地址NAT,是下面命令的一个自动化版本
iptables -t nat -A POSTROUTING -j SNAT –to-source nnn.nnn.nnn.nnn
其中nnn.nnn.nnn.nnn就是网关的WAN地址,这是会被直接填到外发数据包的源地址里面的,请注意,这已经是在完成本机路由之后了。如果你在iptables里面不做-o判断,那么这是会在所有网卡上生效的。如果你有两个WAN地址,在同一个网卡上,那么你用SNAT的时候,是可以手工指定NAT的源地址的,这相当于在NAT层面上实现路由表里面的src参数。
然而,如果是MASQUERADE的话,是会自动选择IP地址的,特别是,如果在一个NIC上有几个ip的话,MASQUERADE是会选择其中的primary ip的。至于primary ip是怎么决定的,这里面有一些规则,具体规则可以在网络上找到。大体来说,首先设定上去的,就是primary ip。
总结一下,Ubuntu系统发出的ip数据包的源地址是这样决定的。是不是本机发出的数据包,如果是的话,根据路由表src参数决定后,再由iptables的NAT机制改写。如果不是本机发出的,不受路由表src参数影响,但受iptables的NAT机制影响。至于这么简单的事情为什么要搞这么复杂,看了我后面的Blog也许就明白了。