彻底理解Cisco NAT内部的一些事
为了配一条NAT,发生了很多事。
一.Inside和Outside
很多在Cisco配置过NAT的人都有过一个疑问,那就是inside和outside的区别!以下是Cisco官方文档上关于NAT执行顺序的说明:
注意红色和蓝色圈住的部分,对于inside-outside而言,NAT发生在路由之后,而对于outside-inside而言,NAT发生在路由之前。这是目前为止,我们唯一需要记住的。
1.问题
迷惑的原因不在别的,就在inside,outside这个名字不好,实际上如果将inside-outside换成POST-ROUTING,将outside-inside换成PRE-ROUTING的话,就非常好理解了,最重要的是,换了名字之后,NAT看起来不再和设备的inside/outside网口域相关,而和"路由"发生了关系,虽然本质上没有任何变化。
后面会介绍,实际上,在理解Cisco的NAT的时候,根本不能将inside和outside单独拿出来理解,inside和outside仅仅是一个位置限定词,代表"某地",而具体的是"到某地去"还是"从某地来",还需要一个副词,这就是source和destination。在详述这个之前,姑且先将inside和outside单独拿出来使用。
接下来我来说明一下NAT和路由的关系是多么重要!考虑以下的数据流,我以"路由"这个动作为中心:
正向包:-->NAT point1-->路由-->NAT point2-->
返回包:<--NAT point1<--路由<--NAT point2<--
我们看一下在NAT point1和NAT point2上要做些什么动作才合理。首先我们先考虑转换正向包的源IP地址发生在NAT point2,那么对于返回包,目标地址转换就发生在NAT point2,返回包转换完目标地址后,发生路由查询,数据包正常返回,没有任何问题。现在考虑正向包的源IP地址转换发生在NAT point1,那么按照将NAT钩子操作安装在数据流同一位置的原则,返回包的目标地址转换只能发生在NAT point1,此时已经经过了路由查询,路由查询是基于目标地址转换前的目标地址来的,也就是说这个路由结果并非真正的路由结果,真正要想将返回数据包送到目的地,必须基于转换后目标地址来查询路由表才可以,然而即便这个针对转换前目标的路由查询结果实际上是个假的结果,你也要必须把它映射成一个真的结果(这就是ip nat outside source中add-route参数要做的事情,下面的例子详述)。以下给出一个实例:
为了使得返回包能到达1.1.1.2,必须将到达2.2.2.4的路由映射到E0那一侧才行,因此必须增加一条:
ip route 2.2.2.4 255.255.255.255 1.1.1.2
也就是说将到达目标地址转换前的下一跳设置成目标地址转换后的下一跳。
这就是NAT和路由的联动关系。Cisco中如果你用ip nat outside source设置NAT的时候,就会碰到上面的这个问题,你可以手工添加一条反向包的地址转换前路由映射,也可以通过命令行后面加add-route参数解决,add-route参数会自动为你添加那条路由,实际上它做的就是:
a.根据NAT映射表查询目的地址B转换后的目的地址A;
b.查找目的地址A查询到达A的下一跳C;
c.将到达B的下一条设置到C。
我们看到在outside上,即PRE-ROUTING上做源地址转换确实会有问题,但是可以通过add-route参数进行路由重新映射来解决之。正常合理的情况就是在inside上,即POST-ROUTING上做源地址转换。事实上,我们虽然在做源地址转换,但是问题总是出在反向数据包的目标地址转换上,合理的情况就是要将目标地址转换放在路由之前进行,反推正向数据,那就是将源地址转换放在路由之后进行,如果不了解Cisco Domain概念的话,一定会认为:不要在outside上作源地址转换!
目前为止,我来总结一下。Cisco完全割裂了路由和NAT的关系,不像Linux那样DNAT永远在路由之前进行,不管是配置的DNAT,还是SNAT的反向DNAT,都在路由前,这样就可以针对转换后的目标地址做路由查询,如果这么理解是对的,你会惊讶,Cisco竟然可以在PREROUTING(outside)做SNAT(ip nat outside source),导致反向包在POSTROUTING做DNAT...但是事情不是这么简单!
Cisco不会由于犯错而设计出如此不合理又容易把人搞晕的NAT架构的,它这么设计必然有自己的理由,是什么理由呢?下面2,3两小节给出了一些提示。
2.关于policy routing
我们知道,标准的IP路由是基于目标地址的,但是为了增加更多的策略,policy routing可以用源地址来影响路由查询结果。在这种意义上,源地址转换在路由之前就是必要的,然而这样就会导致反向的目标地址地址转换发生在路由之后!到底是:
a.为了policy routing将SNAT置于路由之前
b.为了不必add-route(虽然它确实不是什么问题,而且是自动的),将DNAT置于路由之前
需要一个权衡!然而Cisco不像Linux那样去对称设计那5个HOOK点,Cisco的方式就划分Domain的,即inside和outside
3.关于Domain和NAT domain
Cisco设备一般连接两种网络环境,一种是自己内部的,另一种是外部公共的,这就将接口分为了两个域,一个是内部域,即inside,另外一种为外部域,即outside!这种分法的名字叫得特别好,以至于它被用在了很多的领域,比如nat,然而一旦用在了nat方面,就让人糊涂了。因此我提议,对Cisco架构不是很理解的,请尝试用POST/PRE ROUTING来理解inside和outside。但是本小节想做的就是阐明使用inside和outside是合理的。
对于NAT来讲,转换的是IP地址,而IP地址可以分为Global地址以及Local地址,前者是公网可路由的地址,后者是私有地址。从inside域到outside域,需要将所有的Local地址转换为Global地址,一个首要原则就是,Global的地址在未经允许是不能出现在inside域和DMZ域(路由器可能没有)的!决策点就是路由!因此outside到inside方向的地址转换必然要在路由之前完成。这就要求inside到outside方向的地址转换必然要在路由之后完成。即:
ip nat inside source
必然发生在路由之后,而:
ip nat outside source
必然发生在路由之前。
如果你定义了某个接口比如FE0/0为outside,那么需要在接口上使能ip nat outside,这样的话,从该接口进入的包就会在路由之前去查询NAT表,如果找到对应的表项,就会执行NAT。同理如果定义某接口比如FE0/1为inside,那么需要在接口上使能ip nat inside,这样的话,从该接口进入的包先执行路由查找,然后去查询NAT表,如果找到对应的表项,则执行NAT操作。
3.1.转换方向以及转换点-Cisco NAT的设计
这个小节涉及到了对Domain的使用以及如何解读ip nat inside|outside source|destination命令。本小节总结了Cisco NAT设计的终极理论。为了简单,我不再引入Cisco定义的那四种地址以及它们和源/目标IP地址,方向的关联,这些概念都是额外的概念,最容易使人跑偏而最终陷进去。
Cisco没有像Linux那样使用"pre/post路由"这么技术化的术语来定义NAT的行为,而是完全根据Domain来定义,所谓的Domain,即路由器两边一边属于inside,另一边属于outside。那么所有的NAT无外乎就以下4种类型:
1>从inside到outside时转换源地址
2>从inside到outside时转换目标地址
3>从outside到inside时转换源地址
4>从outside到inside时转换目标地址
其中1和4互相隐含,2和3互相隐含。到此为止,我们发现Cisco的NAT并没有像Linux那么简单,Linux实际上就定义了两种NAT,即:
i> SNAT,源地址转换
ii>DNAT,目标地址转换
然后其它的约束都是设计的时候内置的:
路由前执行DNAT,路由后执行SNAT,包含隐含规则。
这就是Cisco和Linux的NAT设计的终极区别!它们侧重点不同,Cisco强调使用者的使用域,Linux强调技术本身的合理性(如何配置就需要发挥想象力了)。我们先看一下Linux的NAT设计基准是什么。Linux的NAT是全局生效的,没有"将NAT应用于接口"的说法,因此接口就成了一个match。因此管理员只需要写match/target就可以了。
对于Cisco而言,为了将4种NAT配置界面全部导出给工程师,需要一个前提操作,那就是定义inside接口和outside接口,即在哪个接口上应用inside nat,在哪个接口上应用outside nat。到此,所有的4种NAT都必须能和任意类型(inside/outside)的接口单独组合。这就打破了平衡点,变成了马鞍面,你无法找到一个点,在PRE ROUTING和POST ROUTING中完成一切,举例,如果接口E1使能inside nat,E0使能outside nat,说明E1是inside接口,E0是outside接口,那么我们考虑从inside到outside方向的两种转换,一种是转换源地址,另一种是转换目标地址,我们把它们放在一个位置还是放在一个"虚拟的平衡点"(不一定是routing)两边,即两个位置呢?我们看下面的两个图,实际上代表了两种约束:
这两个图展开后是个典型的马鞍面,原点就是路由,之所以要有个原点,是因为基于Domain的配置中,数据包从inside到outside或者反过来必然要经过一个点,从本文最上面的那幅Cisco网站上的图,我们看到基于Domain的NAT行为不仅仅是一个NAT,它需要和ACL匹配,加解密等操作联动,这些操作所依赖的IP地址和NAT发生了关联,因此基于Domain的NAT行为一定要位于路由行为的两边。由于Cisco是按照Domain即inside/outside来进行配置的(在接口上应用特定Domain的规则),因此必然是这种设计方式,而对于Linux,NAT是全局的,接口只是一个match而已,因此就完全按照路由的约束来设计。
现在,我们可以来总结一下ip nat inside|outside source|destination的含义了。我把这个命令公式化:
ip nat P H
其中H代表要做源转换还是要做目标转换,H还有一个更加隐蔽的含义,那就是它和P指名了数据的方向,也就是说:数据是以P为H的。举例,ip nat inside destination表示数据是以inside为目标的(来自outside),做目标地址转换;ip nat outside source表示数据是以outside为源发往inside的,做源地址转换。
4.Cisco的destination转换
但是,但是如何对目标地址进行转换,即将访问一个公共Global地址时,将其转到一个内部的Local地址,这就是目的地址转换,也叫地址映射,Cisco如何来做呢?实际上,很多Cisco设备的ISO版本不允许你像Linux那样无限制做DNAT,而仅仅允许映射特定的IP地址+TCP/UDP端口对或者全IP。这肯定是在outside上做目标地址转换了,在相反的方向就是在inside上做源地址转换,即:
ip nat inside source static tcp $local_ip $local_port $global_ip $global_port
注意,必须是static的NAT,这涉及到下一节要说的"如何安装NAT"
对于TCP负载均衡做的ip nat inside destination这种定制化的NAT则不在本文讨论范围内。
二.如何安装NAT
0.风格
Linux的NAT是基于5元组的,也就是NAT结果和一个流(conntrack)关联在一起,这种关联导致同属于一个五元组的一个流的所有数据包的NAT策略必须一致,对于这一硬性规定有点太强硬了,因此我在Linux上做了好几个补丁来弥补Linux的不足,当然也可以用RAWNAT。
对于Cisco,NAT不和一个流关联,除非是Stateful的。既然不和流关联,那么如何做呢?Cisco会在特定的时间将"一条NAT映射策略"安装到系统的inside NAT表或者outside NAT表中,对于从网口进入的数据包,会根据网口是inside还是outside去匹配inside NAT表或者outside NAT表中的NAT规则,仅此而已。
不管是inside NAT表还是outside NAT表,都各有两张,一张是SNAT表,另一张是DNAT表,NAT表的拍脑袋想出的数据结构可以是:
NAT table {type:SNAT or DNATdirection:inside or outsidenodes:local/global mapping}
对于每一个数据包,都要用源IP地址去查询SNAT表,用目标IP地址去查询DNAT表。然而对于Linux而言,需要的仅仅是查询conntrack结构,然后取出第一个包查询时记录于此的nat结果。
1.静态NAT
静态NAT就是一个一对一的NAT映射,也就是一个Local IP地址和一个Global IP地址之间的转换。在配置生效的时候,NAT转换规则就会被添加到NAT表中。
1.1.Cisco方式
当在inside方向上添加了一条NAT:
ip nat inside source static a b
系统会将a->b的源地址转换加入到inside的SNAT表中,同时将b->a的目标地址转换加入到outside的DNAT表中。
针对后面的所有数据包,不管是从内部发起的,还是从外部发起的,都会根据接口使能的是inside nat还是outside nat来查表匹配。
1.2.Linux方式
Linux基于conntrack,因此即使你使用
iptables -t nat -A POSTROUTING -s a -j SNAT --to-source b
也只针对匹配该策略的第一个数据包,Linux的NAT的转换一方a是作为一个match出现的,因此它严格匹配第一个包的源地址,故反方向的数据包不会匹配,因此Linux的NAT都是单向的。
2.动态NAT
动态NAT不在配置的时候规定转换后的地址,而在第一个有转换需求(由ACL来判断)的数据包到来的时候才确定它要转换成什么地址。因此,配置生效的时候,没有任何NAT规则会被加入到NAT表中。
2.1.Cisco方式
当在inside方向添加了一条动态NAT:
ip nat pool NAME ...
ip nat inside source list $acl pool NAME
...
系统不会添加任何NAT规则,只有当某一个包匹配到了acl,要引发NAT的时候,系统会动态(基于pool类型来计算)从pool中选一个要转换成的IP地址,将其添加入inside的SNAT表中,同时针对反方向的目标地址转换规则生成并加入outside的DNAT表中。
因此,Cisco动态的NAT是单向的,因此反向的数据包进入时不会匹配到acl,不会引发NAT规则的生成,也就不会匹配到任何NAT规则。
2.2.Linux方式
再次重申,Linux的nat中,待转换的IP地址是一个match,因此不管是一对一的转换还是一对多的转换,原理都是一样的。Linux并不区分静态转换和动态转换。在内核中,永远都不会出现所谓的NAT映射表,iptables添加的NAT规则不会生成映射,数据包进入匹配nat成功,也不会生成映射,nat结果仅仅存在于conntrack中作为tuple的一部分体现。
3.查询方式
3.1.Linux方式
Linux的nat查询对于第一个包是逐条匹配iptables nat表规则,对于后续的包,则转化为针对五元组的conntrack哈希查询。
3.2.Cisco方式
对于Cisco而言,不像Linux那样PREROUTING处仅仅发生DNAT,POSTROUTING处仅仅发生SNAT,而是不管在PREROUTING还是在POSTROUTING,均可能发生SNAT和DNAT,因此不管在哪个nat HOOK point,均要用数据包的源IP地址和目标IP地址分别来查询SNAT表和DNAT表。既然这么对称,那么就可以将这个查询过程抽象出来,使之成为一个独立的虚拟接口,是为NVI!
三.使用NVI虚拟接口
0.虚拟接口理念
这个就不说了。Open×××的tap,Cisco IPSec ×××的VTI,包括下文描述的NVI,都属于虚拟接口,并且所有的虚拟接口都可以通过路由的方式将数据包导入进去,至于在里面执行什么操作,这就是虚拟接口"虚拟"的地方,可以将数据包导出到字符设备,也可以加解密,当然也可以做NAT...
1.Linux 2.4的nat route
Linux 2.4的nat route依赖于policy routing,超级失败,还没有我自己写的好,《又一个Linux的双向stateless NAThttp://blog.csdn.net/dog250/article/details/8996666》详细介绍了实现方法。
2.Cisco的domainless NAT
Domainless就是说不再区分inside和outside,只是单纯地做NAT,这个在Cisco中实现得热别清爽,没有用所谓的平衡点,进而两个方向NAT的处理HOOK点也不再基于平衡点对称,所有的NAT操作全部在PREROUTING上做,然而Cisco并没有用增加配置,挂接HOOK的方式实现,而是用一个叫做NAT Virtual Interface的虚拟接口来实现,这样有什么好处呢?说实话,从界面上看不出来,但是从其实现角度,就可以通过路由的方式将带有ip nat enable配置的接口进来的包全部导入这个虚拟接口NVI0中。然后用数据包的源地址和目标地址分别查询SNAT表和DNAT表,根据结果进行NAT操作,随后进入真正的路由查询,整体流程如下图所示:
可见,不管方向,不管路由,只要数据包进入了一块带有ip nat enable配置的物理网卡,就会进行NAT匹配以及匹配成功后的操作,不管是SNAT和DNAT都在这里进行。这个实现虽然很豪放,但是却解决了所有问题,平衡点的问题不存在了,数据包在进入真正的路由查询前,NAT就已经完成了,在路由器看来,NAT操作被藏起来了,就好像数据包本来就是那个样子一样。
当然Domainless的NAT也不再和任何其它操作关联,ACL,×××感兴趣流匹配,policy routing等都和NAT无关。Cisco Domainless的NAT设计和Linux的NAT设备区别更加明显,虽然Linux的NAT也是Domainless的,但是在设计上却和Cisco基于Domain的NAT很像,因为二者的NAT都要和其它的Filter操作联动,对于Linux,NAT行为需要和大量的Netfilter行为联动。
四.总结和想法
通过使用Cisco的NAT,我有一些自己的想法,需要暂时记录下来。
1.和微软的关系,和自由的关系
我配置Cisco的时候,有种使用微软系统的感觉。遇到瓶颈的时候,束手无策!在微软的系统上,我想做单臂NAT,我想配置路由的时候带上source,折腾了好久都未果,配置Cisco的时候,同样的感觉!虽然有时候,高一点的IOS版本确实能解决问题(这是和微软一样的)。总的来讲,使用这些东西让你感觉不自由,能HACK的地方实在太少,HACK操作门槛太高,使用它们时,你要一直提心吊胆,你要么一气呵成,只要是碰到了拦路虎,哪怕多么小的拦路虎,你可能都过不去。
Linux正好是事情的另一面,UNIX处在中间。在Linux上操作,你的任何行为都不会受到阻碍,即使碰到了问题,系统不会阻碍你去编写一个程序或者内核模块,甚至重新订制内核。即使不这么深入,光看一下iproute2以及iptables就够了,本身自带的参数就能让你完成几乎所有的操作,在这个意义上,习惯了Linux的人可能也会苛求Cisco的扩展访问控制列表更加优秀一些,实际上,面对Netfilter的几乎无限的扩展能力,Cisco的扩展访问控制列表的"扩展"体现在哪里呢??
我买手机不是来hack的,我也不写代码安装在手机上,我仅仅是想用它来打电话,发信息,上网,所以我不买Android,由于价值观相差甚远,我不买和微软有任何关联的手机,不管它有多好,买了iOS是因为它和UNIX有关联,事实上,苹果很封闭,但是它的封闭不是技术本身,而是价值取向和设计,事实上,也正是因为它封闭的不是技术,才有了越狱之说,越狱之后,你将面对一个原汁原味的开放的UNIX。开放是金,开放就是一切。
2.功能性技术点和解决方案
如果碰到问题,当然要想办法去解决问题了,此时就有两条路,第一条路就是寻找单独解决该问题的功能性技术点,比如你想在做一个基于ACL的地址映射,如果你用的是Linux BOX,那么这个功能性技术点就是iptables,但是如果你用的是Cisco低端路由器,那么就无法做这件事,文档明确说不支持。此时就是第二条路了,那就是彻底否定最初的方案,说一句:"为何要这么做呢?XX会更好的。我们有整套的解决方案,满足你的所有需求。"。一般的像Cisco,微软,IBM都会提供解决方案,它们的系统上的功能性技术点往往都有短板,单独拿来用很鸡肋,只有和其它的配合才可以。因此,想hack的,千万别基于这些系统,你会烦死的,在这些系统上,这也不行,那也不行,这个不能变通,那个不能变通,用多了,人也就死板了,说话也就不好听了,"技术上的任何问题都是可以解决的"这句话到了使用这些封闭系统的人那里就成了"这是不可能的!!!"
3.人员冲突
一周以来,我跟人冲突好几次,完事后还是证明我的想法是正确的,所以再次让我感到大多数所谓的高级网管员的水平是多么的垃圾,水平垃圾个性还一个个都挺狂妄,这实在让人气愤和悲哀。这些垃圾人员的观点基本分为两类:
1>没有做过的事情就说不可能
因为以前从来没有这么做过,就说不可能,甚至否定我的技术方案。我是作为研发出现的,这就更让对方觉得我根本就不懂网络,没有经验等,第一他们没做过,第二我没网络方面的职称,这就更让他们狂妄无比。
2>想当然的事情就拍脑袋说可以,实际上根本就不可以
我说要把udp服务一对一双向映射出去:
ip nat inside source udp 1.1.1.1 12345 2.2.2.2 12345
在测试的时候,他们非要用ping来测试!这个明明是一个udp的映射,怎么用ping?!然而他们非要用ping,我告诉他们不能做全映射,因为会把不相干的不该转换的地址也转换了,因为他们知道NAT可以通过ACL+pool来做,所以他们就认为这个可以在outside口用ACL匹配地址来做...这帮×××!我要做目标地址转换,还得是双向的,也就是说不管数据流从那边发起都是可以的!那帮人连NAT转换项什么时候安装进系统的都不知道!
这些人还好意思说自己是网络工程师啊!看看我们的那些NA/NP/IE/NE/SE们,无一例外的就是靠背题的,好多证书都拿到手了,却连网络的基本原理都TMD不懂!再看看那些真正的CCIE,比如Petr Lapukhov, 4xCCIE/CCDE in CCIE R&S,IP Services:
Petr Lapukhov's career in IT begain in 1988 with a focus on computer programming, and progressed into networking with his first exposure to Novell NetWare in 1991. Initially involved with Kazan State University's campus network support and UNIX system administration, he went through the path of becoming a networking consultant, taking part in many network deployment projects. Petr currently has over 12 years of experience working in the Cisco networking field, and is the only person in the world to have obtained four CCIEs in under two years, passing each on his first attempt. Petr is an exceptional case in that he has been working with all of the technologies covered in his four CCIE tracks (R&S, Security, SP, and Voice) on a daily basis for many years. When not actively teaching classes, developing self-paced products, studying for the CCDE Practical & the CCIE Storage Lab Exam, and completing his PhD in Applied Mathematics.
看看人家的经历,从编程开始,逐步深入,从计算机体系结构,TCP/IP栈,到UNIX,最终,Cisco只是大师将这一切附着的一个平台而已,如果他不是在Cisco上工作,而是加入IBM的阵营,或者去开发Linux内核,你能说人家不是圈子里的人吗?记住,别把编程的人看作不懂网络的。