国外流传的一道CCIE挑战题,这道题难度不低,出题的人是一位罗马尼亚裔的美籍CCIE。为了防止作弊,所有参与者的回答都被出题人屏蔽,自己和出题者交流了一会,在得到所有自己所需要的信息后,花了几天的时间做模拟实验,找相关资料研究,最终给出了正确的答案。出题者肯定了我的解法,但是提醒我解法不止一种,并向我解释了其他的解法,自己受益颇多,对ACL和Fragmentation(数据包分片)也有了更深层的理解。

 
先来看看这道题的拓扑,背景信息以及问题。
 
图1
 
从一道CCIE挑战题深入理解ACL和数据包分片
 
图2
 
从一道CCIE挑战题深入理解ACL和数据包分片
 
背景信息:
 
1. 某公司有三个site,分别为R1,R2,R3。
 
2. R3和R2之间跑GRE隧道,R2和R3的interface tunnel的MTU被人为的从1476改为1440.
 
3. R2和R1之间有两条connection,一条是它们两个互相直连的Backdoor(192.168.12.0/24),另外一条是通过ISP走Internet,R2-ISP-R1,走Internet的流量做了NAT(严格来说是PAT),走Backdoor的流量没有NAT。
 
4. 用户(172.16.1.10)直连R3,服务器(1.1.1.10)直连R1,服务器上运行的程序需要用到TCP port 1001和1002.
 
5. 需求: 如图2所示,在R2上用PBR分流,实现TCP 1001的流量走R3-R2-R1, TCP 1002的流量走R3-R2-ISP-R1。
 
问题:
 
图1给出了R2的配置,测试结果是走R3-R2-R1这条route的TCP1001的流量正常(图3),走R3-R2-ISP-R1的TCP 1002的流量则有问题:TCP三次握手已经完成,但是session随后卡住,不久之后session timeout(图4),需要找出原因并给出解法。
 
图3(TCP1001流量正常)
 
TCP1001流量正常
 
图4(TCP1002流量:TCP三次握手成功,但是随即卡住并Timeout)
 
TCP1002流量:TCP三次握手成功,但是随即卡住并Timeout)
 
解题思路:
 
1. 
首先通看一遍出题者给的R2的配置,没发现什么大问题。因为既然用户端(172.16.1.10)和服务器(1.1.1.10)之间的TCP 1001和1002两种流量的TCP三次握手都成功完成了,那就说明了无论是路由,PBR,GRE隧道,还是PAT等等的配置都没有任何问题。
 
2. 重新读一遍题目的背景信息,其中第二条引起了我的注意:“R2和R3的interface tunnel的MTU被人为地从1476改为1440”。众所周知,GRE隧道的header是24 Byte,所以通常来说GRE隧道端口的MTU是1500-24=1476 Byte (前提是配置GRE之前,物理端口使用的MTU是默认的1500),那么再被人为地改为1440之后有什么影响呢?这里有很重要的一点要考虑,那就是用户端(172.16.1.10)是否开启了PMTUD(Path MTU Discovery),也就是“路径MTU发现”的功能,因为这直接影响到用户端的IP包在经过R3时会不会因为前后MTU的不匹配而导致被R3进行Fragmentation (IP数据包分片),因为通常来说,Fragementation有一个很大的缺点,那就是它大大降低了网络的传输性能,会引起很多诸如丢包,延时,线路不稳定,甚至连接失效的问题,对于这个问题,因为出题者在背景信息里没有明确说明,我特此询问了他,得到的回答是,在他的实验环境里,用户端(172.16.1.10)实际上是一台跑FreeBSD的服务器,他在用户端通过PMTUD探测到了GRE的MTU(1476)之后,故意关闭了PMTUD,并给我解释他之所以没在背景信息里给出这个条件,目的就是为了提高这道题的难度,并说我能考虑到这点,说明我的思路是对的,叫我继续做下去。
 
3.顺着这个思路继续往下看,现在用户端的MTU是1476(PMTUD被关闭之前学到的),PMTUD被关闭后,用户端无法重新探测到R2和R3的GRE隧道的新MTU(1440),所以用户端还是照旧把1476的包丢给R3,又因为1476大于1440,导致这时R3不得不对用户端的包做IP分片了。这就导致了R2从R3接收到的用户端的包其实是一大堆fragments(IP碎片)。考虑到这里后,虽然自己清楚IP分片会导致各种各样的链路问题,但针对这道题来说,究竟是什么导致了TCP1002流量的问题,自己曾一时毫无头绪。
 
4. 既然用户端的包有被R3做IP分片的事实存在,那么为什么走R3-R2-R1的TCP 1001的流量没有问题,唯独走R3-R2-ISP-R1的TCP 1002却有问题呢?带着这个疑问,回头再看R2的配置,下面这个被PBR用来分流的ACL引起了我的注意。
 
ip access-list extended ACL_USE_BACKDOOR
permit tcp host 172.16.1.10 host 1.1.1.10 eq 1001
exit
 
这个ACL表达的意思一目了然:匹配所有从用户端172.16.1.10去往服务器1.1.1.10,TCP端口为1001的数据。乍一看没有任何问题,实则这里面隐含了一个巨大的玄机,那就是当4层ACL遇到IP碎片时所产生的一些列“化学反应",要搞清楚这个,必须先弄明白IP碎片到底是怎么一回事,为此我特意画了下面两个图来说明。图5为正常情况下,没有被路由器fragmented的一个完整的IP包(MTU为1500),图6则是这道题里,已经被R3(MTU 1440)做了IP分片后的用户端的包(MTU 1476),分别为“第一个碎片”和“第二个碎片”
 
 
图5
 
从一道CCIE挑战题深入理解ACL和数据包分片
 
图6
 
从一道CCIE挑战题深入理解ACL和数据包分片
 
5.图5很好理解,通常一个完整IP包的MTU是1500 Byte,扣去3层的IP header (20 Byte,蓝色部分)和4层的TCP header (20 Byte,橙色部分),剩下的data payload为1460 Byte, 这个1460 Byte在TCP里又叫做MSS (Maximum Segment Size,注意只有TCP有MSS,UDP没有这个东西),MSS是后话,暂且不表。
 
6. 图6是解出这道题的关键所在。第一个碎片(1440 Byte)和第二个碎片(56 Byte)的区别一目了然,第一个碎片有4层header(橙色),第二个碎片没有,这种情况对1001和1002的包有什么影响呢?详解如下:
 
首先再回顾一次这个ACL,看它对TCP 1001和TCP 1002两种IP碎片包产生了什么影响:
 
ip access-list extended ACL_USE_BACKDOOR
permit tcp host 172.16.1.10 host 1.1.1.10 eq 1001
exit
 
TCP 1001的第一个碎片,Src IP = 172.16.1.10, Dst IP = 1.1.1.10, Src tcp port = any, Dst tcp port = 1001,符合ACL的条件,R2对这个碎片做PBR,即将它丢给R1。
 
TCP 1001的第二个碎片,Src IP = 172.16.1.10, Dst IP = 1.1.1.10, 依然符合ACL的条件,R2对这个碎片做PBR,即将它丢给R1。
 
TCP 1002的第一个碎片,Src IP = 172.16.1.10, Dst IP = 1.1.1.10, Src tcp port = any, Dst tcp port = 1002,不符合ACL的条件,R2对这个碎片做PAT,将它丢给ISP。
 
TCP 1002的第二个碎片,Src IP = 172.16.1.10, Dst IP = 1.1.1.10, 符合ACL的条件,R2对这个碎片做PBR,即将它丢给R1。
 
结论:
 
A.TCP 1001的第一个和第二个碎片都满足ACL,都被R2的PBR丢给了R1,R1再将两个碎片包传给服务器1.1.1.10,所以TCP 1001的流量正常。
 
B.TCP 1002的第一个碎片(1440 Byte)没有满足ACL,被R2的PBR丢给了ISP,第二个碎片(56 Byte)满足了ACL,被R2的PBR丢给了R1,现在两个碎片被活生生的拆散开了,两个碎片包“分道扬镳”的结果造成了TCP 3次握手成功,但是随即timeout的问题。
 
解法:
 
前面已经很系统地分析出了问题所在:因为用户端关闭了PMTUD,再加上R3和R2的MTU从1476被改小到1440,导致MTU不匹配,从而引发了R3对用户端的包进行Fragmentation(IP 分片)的行为,而又因为R2上配置的4层ACL导致了TCP 1002的第一个和第二个碎片包被R2分别丢去了ISP和R1,导致碎片无法复原,最终造成TCP 1002数据的连接问题。
 
弄清楚了问题出在哪里后,接下来就是对症下药了,这道题解法有多种,一一解释如下。
 
解法1
 
用户端172.16.1.10开回PMTUD,让用户端学习到新的MTU(1440),从而避免fragmentation。这是最直接也是最简单的解法,这也是为什么出题者在背景信息里没有给出“关闭了PMTUD"这个条件,不然的话,每个人都回答“开回PMTUD",那这道挑战CCIE的题还有什么挑战性可言?
 
解法2
 
在R3端口上修改MSS,命令为ip tcp adjust-mss,避免fragmentation。
 
前面简单提到过MSS这个东西,关于MSS,一些总结如下:
 
1. MSS出现在TCP的SYN包里, 它和UDP无关。
 
2.MSS和MTU的区别在于,MSS是TCP数据包每次能够传输的最大数据分段,它永远比MTU小40 Byte,这40 Byte是什么?前面的图5已经解释过了,即3层包头(20 Byte)和4层包头(20 Byte)。以这道题为例,用户端包的MTU 为1476,那么它TCP SYN包里的MSS就为1436。
 
3. 顾名思义,思科的ip tcp adjust-mss这个命令的作用就是用来修改MSS大小的,以这道题为例,在R3上配置ip tcp adjust-mss 1300,当用户端的包(MSS 1436)到了R3上后,因为1436>1300,这时R3就会把用户端TCP包的MSS修改为1300,然后丢给R2, R2看到1300<1400,也就不会再对这个包做IP分片了,从而避免了fragmentation。
 
4. ip tcp adjust-mss这个命令是在端口下配置,至于配置在哪个端口,没有硬性规定,就这道题来说,把它配置在直连用户端的端口或者连接R2的端口上都可以,效果一样。
 
解法3
 
修改PBR的ACL,让TCP 1002的碎片也走R3-R2-R1。
 
Ip access-list extended ACL_USE_BACKDOOR
permit tcp host 172.16.1.10 host 1.1.1.10 eq 1001
Permit tcp host 172.16.1.10 host 1.1.1.10 eq 1002
 
这个解法不完美,主要有两个原因:
 
1. 违反了题目的规定,即TCP 1002的流量要走R3-R2-ISP-R1。
 
2. 这个解法治标不治本,因为将来如果遇到其他TCP端口的类似需求怎么办,一直违背分流的初衷?
 
解法4
 
修改PBR的ACL,逼迫TCP 1002的碎片被PAT,然后走R3-R2-ISP-R1。
 
ip access-list extended ACL_USE_BACKDOOR
deny tcp host 172.16.1.10 host 1.1.1.10 eq 1002
permit tcp host 172.16.1.10 host 1.1.1.10 eq 1001
 
这个解法同样不完美,原因很简单,TCP 1001的第一个碎片没问题,但是第二个碎片也被丢给了ISP,同样的问题又发生了,只是这次出问题的是TCP 1001。