接上文,上文讲了小明访问Internet,NAT所做的一些点滴工作。
和小明位于同一个叶子网络里的小美,也在访问Internet,她的五元组为:
访问步骤和小明的类似,故省略,那么NAT小姐姐也需要登记一下,如下表所示:
这个表咋看好像没有什么问题,但瞅得时间长了,会发现这里有问题。
当返程得IP包返回时,其五元组应该是这个样子的:
NAT查询NAT映射表,是将1.1.1.1替换为10.1.1.1好呢,还是替换为10.1.1.2好呢?
有同学说,那不是很显然吗?必须10.1.1.2啊,通过目的端口==58612就可以判断出啊!
但NAT映射表里没有记录什么端口号,只有IP地址。
那意味这张NAT映射表是有问题的,不仅要记录IP地址,还需要记录端口号信息,对吗?
这张NAT映射表看起来就清晰了许多,也不会造成NAT小姐姐混淆彼此的局面。
返程的IP报文如果目的端口== 1025,那么报文是发给10.1.1.1的。
返程的IP报文如果目的端口== 58612,那么报文是发给10.1.1.2的。
简单明了,非常好理解。
Timeout
这些动态产生的NAT映射表项就这么一直腻在那里吗?
那怎么可能,NAT的outside port资源,满打满算只有16位,理论上最多有65535个端口,如果NAT映射表没有超时回收机制,那么可用的端口号会越来越稀少。
最极端的情况是,NAT没有可用端口提供给用户上网使用。
所以NAT映射表需要超时机制,再来看一下添加了timeout的NAT映射表。
RFC推荐timeout = 300秒,最小值不得低于120秒。
300秒超时定时器随着时间的流逝而递减,一旦减为之后,NAT会删除之,这样就将端口释放给别的TCP连接使用。
关于NAT,第一个槽点
在没有NAT存在的情况下,客户端、服务器如同谈恋爱的男女朋友,一旦勾搭上了,自由自在地交流着。即使好长时间没有给对方发消息,双方依然是爱人关系。
突然半路杀出一个NAT,电灯泡一样的存在着,让双方有局促感。
双方如果在300秒没有任何交流,处于idle状态,NAT设备就神不知鬼不觉地删除表项。
双方并不知情,之后当任何一方再给对方发消息时,由于NAT已经不存在了,这是NAT的不同反应将会有不同的效果。
1.丢弃
丢弃对于NAT设备来说,干脆利落,易于实现。
报文被丢的一方,会一直重传,直到到达重传次数的上限而删除连接,这个过程通常为几分钟,对于用户来说,非常漫长。
通信的另外一方,即使对方的连接已经删除,己方的TCP连接却依然活的好好的。如果没有keealive检测机制,这个TCP连接会一直存在着,直到重启或关机的那一刻,这其实是一种资源的浪费。
2.丢弃的同时发Reset
可以立马通知数据发送方,连接需要重置,发送方会立马释放掉TCP连接,重新再连接服务器。
通信的另外一方,与情况1相同。
3.创建新的NAT映射表项
之所以区分方向,是因为大多数的NAT设备默认只允许inside到outside方向创建NAT映射表,相反的方向默认不允许,除非通过配置明确允许。
Inside---- > Outside
如果创建的NAT映射表,outside IP + outside Port和删除前是完全相同的,这个对于通信双方是最最happy的一种结果,因为双方压根感受不到NAT表删除、NAT创建的过程,因为删除前、删除后是一样的。这个技术就是作者将要阐述的端口保留(Port Reserved )。
如果创建的NAT映射表,outside IP + outside Port与删除前是不同的,那么即使报文到达对方,对方也会发现这个报文不属于任何session,从而触发对方发送Reset。
Reset报文到来会触发客户端新的TCP连接,新的连接用于继续传输数据。
但老的TCP连接,却依旧活在服务器芯中,继续浪费内存资源。
Outside---- > Inside
丢弃处理
通过上述三种处理的介绍,会发现只有做端口保留的NAT设备,才会使通信双方继续使用原有的五元组通信,其它的情况都会造成通信的中断。
即使做端口保留的NAT设备,也需要outside到inside方向有流量先流出,然后创建NAT映射表,outside到inside方向的流量才能进来。
NAT映射表刷新(Refresh)
为了避免NAT映射表超时删除而中断通信,对于TCP长连接需要做应用层的Keepalive机制,从而周期性地刷新NAT定时器,即使在很长时间内没有任何应用层数据需要发送。
应用层的Keepalive与TCP层的Keepalive有何区别?
TCP层的Keepalive默认为2小时,其主要目的是为了探测对方是否依然健在,如果多次连续探测不到对方的回应,会删除连接。
这是回收TCP连接资源的一种机制,避免长期处于idle状态的僵尸连接,占据着内存资源。
2个小时太漫长了,如果依赖TCP的Keepalive的刷新NAT映射表,黄花菜都要凉了!
应用层的Keepalive,其主要目的是为了刷新NAT映射表!
既然是为了刷新NAT映射表,Keepalive刷新周期要小于NAT表的存活时间。
为了避免单次的Keepalive报文被丢,通常需要在一个NAT存活时间周期内,需要发送Keepalive 2 -3次。
哪个方向的流量可以刷新NAT映射表?
RFC规定,inside ---> outside方向必须(must)可以刷新NAT映射表。
outside --- > inside方向可以(may)刷新NAT映射表。
以上主要是出于安全考虑,outside通常是一个公共的Internet区域,如果允许outside --- > inside刷新NAT映射表,攻击者可以周期性地发送Keepalive消息,让NAT设备上该端口号一直处于open状态,那么攻击方就可以利用该端口来攻击内网。
端口保留(PortReserved )
上文提到了端口保留,通俗地说,就是NAT设备不改变终端用户的源端口号,如下表所示:
InsidePort == Outside Port
同学们肯定会有一个问题,如果表中的两个session的源端口号都为1025,如何实现端口保留?
针对这个问题,无法给出一个统一的答案,同学们知道不同NAT设备厂商实现方式千奇百怪。造成这一现状的原因是,早期的NAT RFC文档并没有严格标准(Standard Track),只是一些建议(Best Practice)或信息(Information),这样厂商NAT实现差异很大也不足为奇了。
案例一
有以下两个五元组:
方式1:随机端口
Outside port和inside port没有任何关联,NAT设备会随机挑选自己可用的端口号来生成NAT映射表。
此种方式,由于内部用户的端口号对Internet呈现的端口号,是随机的、不可预测的,所以这种NAT实现对TCP打洞是最不友好的。
方式2:顺序分配,端口不冲突,则保留
NAT设备尽其可能,在没有端口冲突的情况下,使用端口保留,使得inside port == outside port。
但一旦发生端口冲突,后创建的NAT映射表挑选可用的端口号,这里为1027。
这种方式是RFC推荐的一种,即尽可能保留内网用户的端口,以便于Internet上的用户可以访问内网资源。
方式3:完全端口保留
端口重用(PortReuse),这种情况是NAT设备即使豁出老命,也要帮助用户保留他们的端口号。
同学们肯定发现这张表是有问题的,因为NAT设备无法分辨返程的IP报文1.1.1.1:1025,是给10.1.1.1的,还是10.1.1.2的。
通过增添通信另一方的IP、PORT就可以帮助NAT设备,分辨返程的IP报文1.1.1.1:1025是属于哪个的了,对吗?
这看起来很强大,内网用户可以保留自己端口号,即使有端口冲突的状况,也可以使用端口重用予以克服。
但是有一种情况是无法克服的,来看看以下两个五元组:
对于这种源端口冲突的情况,再能干的NAT设备也无法将两个session的端口都保留了,只能帮助先连接的TCP连接保留端口号,接下来的TCP连接,则必须使用别的端口号。
方式4:主机相同的InsideIP/Port,总是映射相同的OutsideIP/Port
方式3是inside port == outside port,而方式4是insideportoutside port,但只要主机每次通信都使用相同的insideport,那么每次都会被映射成相同的outside port。
这种方式同样便于peer-to-peer通信。hostA使用端口号portA访问第三方服务器S,S将hostA的IP/portA告诉hostB,然后hostA通过端口重用继续使用portA与hostB通信,幸运的是,NAT继续将portA分配给hostA使用,这时hostB可以使用目的端口== portA与hostA通信。
安全
有同学会问,端口保留会有安全隐患吗?Internet用户只要知道NAT设备的全球IP,以及内部主机的侦听端口号,那么是不是就可以从Internet上访问该主机的位于该端口号的服务?
完全端口保留,为了便于NAT设备后方的主机,和同样位于世界某个角落NAT设备后方的主机peer-to-peer通信。
假如有两台位于不同NAT设备后的hostA、hostB,通过第三方服务服务器的消息传递,分别知道对方的公网IP以及端口号,然后就尝试着用对方的公网IP、端口号去和对方联系。
这时hostA能够联系上对方吗?不能,没有hostB从里面配合,外面的消息根本进不去。
因为NAT映射表还是空的,必须里面的hostB先发一个SYN报文,让NAT-a建立映射表,有了映射表,hostB的SYN报文才会进入,也许这个过程会有少许的SYN报文会丢失,但TCP会重传这些丢失报文。
里面的hostB发的SYN报文,目的IP、目的端口号自然是hostA的,那么也意味着只有hostB愿意并准备接受通信的hostA的报文才能进入内网。
同理,对于hostA来说,只有hostA愿意并准备接受通信的hostB的报文才能进入内网。
如果没有这个前提,其它所有的主机报文都无法贸然进入。
只有做端口保留的NAT,才能将从inside发出的报文与从outside进入的报文无缝对接上,做牵线搭桥的自然是它们拥有相同的端口号。相同的端口号,使无缝对接成为可能。
如果没有做端口保留,outside port不等于inside port,那么inside与outside报文就无法对接再一起,形成一个完整连接。
端口保留,为peer-to-peer通信提供了极大的便利,与不做端口保留的NAT方式相比,安全性并没有降低。
如果喜欢文章却无法用语言表达,请赞赏表达您的喜悦。
领取专属 10元无门槛券
私享最新 技术干货