大家好
在《MTU工具解析与常见问题汇总-上篇》中,我们一起讨论了MTU的基本定义,以及数据包分片的具体细节,同时也简单列举了MTU不匹配导致的问题和对网络数据传输的危害。
既然MTU问题多多,那么尽可能避免由MTU引发的网络问题将显得尤为重要。通常而言,我们可以使用以下方法来检测和避免MTU问题:
1. 手工测试发现MTU值
2. TCP-MSS
3. PathMTU Discovery
今天我们就来聊聊以上工具的具体细节以及如何通过他们发现链路MTU的最佳值,同时奉上两个典型的由MTU引发的问题案例。
什么是最佳MTU值?
最佳MTU值,是指保证数据包不被分片的情况下,以字节为基本单位的链路单个数据包尺寸的最大临界值。如果数据包再多一个字节长度,数据包就会被分片。此值称为最佳MTU值。
MTU下篇:TCP-MSS,Path MTU Discovery详解以及常见问题分析
MTU工具测试环境介绍
为了演示常见MTU检测方法,特搭建网络测试环境如下:
上图网络拓扑中,存在两个网络站点A和B。A站点和B站点可以通过ISP运营商互联互通。
A站点出口路由器为SRX01,出口IP为1.1.1.1
B站点出口路由器为SRX02,出口IP为2.2.2.2
站点A和站点B出口路由器的MTU值如下:
A站点的出口MTU值为巨型MTU值9000字节。
B站点的出口MTU 值为2000字节。
在A,B站点各有公网服务器一台:
A站点Server 7, IP:3.3.3.3
B站点Server 8, IP:4.4.4.4
两者可以通过ISP的BGP路由互联互通。
场景介绍:
此场景存在的问题为:运营商内核心路由器之间的MTU值未知。但由于网络通信是一个端到端的整体行为,若不通过某种方法获悉运营商之间的链路MTU值。那A和B之间的通信可能存在数据包分片甚至丢包的行为。
在开始研究如何发现端到端的链路最优MTU值之前,让我们先做一个简单的Ping联通性测试以确保SRX01和SRX02能够正常通信。
lab@SRX01>ping 2.2.2.2 source 1.1.1.1 rapid count 5 PING2.2.2.2 (2.2.2.2): 56 data bytes !!!!! --- 2.2.2.2ping statistics --- 5 packets transmitted, 5 packets received, 0% packet loss round-tripmin/avg/max/stddev = 21.491/32.152/37.884/6.149 ms lab@SRX01> lab@SRX01>traceroute 2.2.2.2 source 1.1.1.1 tracerouteto 2.2.2.2 (2.2.2.2) from 1.1.1.1, 30 hops max, 40 byte packets 1 1.1.1.100 (1.1.1.100) 12.050ms 8.905 ms 14.453 ms 2 35.0.0.5 (35.0.0.5) 41.774ms 21.306 ms 22.849 ms 3 2.2.2.2 (2.2.2.2) 42.211 ms 29.103 ms 35.168 ms
Ping包成功,两端链路通信正常。通过Traceroute可以看出穿越的运营商路由器IP地址。
注:上图中,默认情况下,Juniper的Ping包负载尺寸为56字节。
服务器连通性测试:
环境确认测试完成,让我们来逐一介绍MTU的发现方式:
1.冷兵器时代:手工测试
最简单也是最直观的MTU检测方式自然是手工测试。
手工测试提供了其他工具无法比拟的优势:可视性,直观性。
手工测试示例
让我们来看看如何通过手工测试法发现链路的MTU值。
lab@SRX01> ping 2.2.2.2 source 1.1.1.1 rapid count 1 do-not-fragment size 8000 PING 2.2.2.2 (2.2.2.2): 8000 data bytes 36 bytes from 1.1.1.100: frag needed and DF set (MTU 1500) Vr HL TOS Len ID Flg off TTL Pro cks Src Dst 4 5 001f5c c999 2 0000 40 014c02 1.1.1.1 2.2.2.2 . --- 2.2.2.2 ping statistics --- 1 packets transmitted, 0 packets received, 100% packet loss
针对上面的Ping测试,有几点需要说明:
1. Do-not-fragment关键字很重要,在《MTU共计解析与常见问题汇总-上篇》中曾经提到过,发送方可以自主确定数据包是否需要被分片。而决定的方法为设置DF=1,告知下游路由器请不要对数据包进行分片。在此测试案例中,我指定了do-not-fragment。顾名思义,SRX01会根据我的要求把ICMP包的DF设置为1。
2. Size 8000是告知SRX01请产生一个8000字节的载荷并封装到ICMP中。为什么要用8000,是因为SRX01的本地链路MTU最大值为9000字节(包含IP包头+ICMP包头),所以我需要随机选择一个比9000稍小的数。
在上面测试中,当我用8k字节ping一个数据包的时候,ping测试失败。从而证明中间链路的MTU值是小于8000字节的。
同时,在Ping 2.2.2.2 的过程中,SRX01收到一个错误信息。
36 bytes from 1.1.1.100: frag neededand DF set (MTU 1500)
有一条从1.1.1.100发过来的消息,内容大致如下:
你的数据包太大。要想往前走,数据包需要分片,可是DF=1,我没法给你分。(顺便悄悄告诉你,前方MTU值为1500。)
难道ISP运营商内部还有咱的人,还给透露小道消息,在这里先不管这高人是谁,后续聊到Path MTU Discovery时,将通过抓包揭晓谜底。
我们就只需要知道有高人指点,前方ISO网络层数据包MTU大小最大为1500字节。
抱着半信半疑的态度,我们再Ping测试一次。
但是在开始测试之前,先打一个小算盘:
网络层1500字节=20字节IP头+8字节ICMP头+1472字节的载荷。
按照上面的推理,我猜如果Ping数据包载荷size=1472字节的话,就会成功。而如果稍稍多一点,例如1473字节Ping就会失败。
通过实验证明:
Ping Size=1472字节的包:
lab@SRX01>ping 2.2.2.2 source 1.1.1.1 rapid count 1 do-not-fragment size 1472 PING2.2.2.2 (2.2.2.2): 1472 data bytes ! --- 2.2.2.2ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-tripmin/avg/max/stddev = 28.471/28.471/28.471/0.000 ms
Ping Size=1473字节的包:
lab@SRX01>ping 2.2.2.2 source 1.1.1.1 rapid count 1 do-not-fragment size 1473 PING2.2.2.2 (2.2.2.2): 1473 data bytes 36 bytesfrom 1.1.1.100: frag needed and DF set (MTU 1500) Vr HLTOS Len ID Flg off TTL Pro cks Src Dst 4 5 00 05dd d60b 2 0000 40 01 590f 1.1.1.1 2.2.2.2 . --- 2.2.2.2ping statistics --- 1 packets transmitted, 0 packets received, 100% packet loss
验证成功。我们现在可以100%确定,此神秘人物透露的1500字节的MTU值是绝对可信的。同时也获悉端到端链路ISO第三层的最佳MTU值为1500字节。
以上就是手工测试法,简单明了。
但是有一个问题?
作为管理员,我们通过测试明确了端到端MTU的实际值。可是我们如何也让网络中的应用程序也知晓这1500字节的MTU值呢?
让我们继续往下走。
2.小米加×××:TCP-MSS协商
TCP-MSS,全称TCP maximum segment size。翻译过来是TCP最大报文尺寸。它的值代表TCP传输层期望对端发送给自己单个TCP报文的最大尺寸。
通俗来说,当TCP协议两端在初始协商进行TCP三次握手协议的时候,主机两端会把自己当前所在链路的MSS值告知对方。当一端主机收到另外一端的MSS值候,它会评估其MSS值并与自己的MSS值做对比,取最小的值来决定TCP发送的最大报文尺寸。
如何计算本地MSS值?
本地MSS=MTU-20字节的标准IP头-20字节的标准TCP头(换个角度看其实就是TCP负载)
那TCP-MSS如何在避免数据包分片中,发挥它的作用呢?
让我们来看一个实际示例:
还记得在测试环境中介绍的两台服务器Server 7 和Server 8吗?
现在两台服务器要搞事情了。
如上图,服务器Server 7 要往Server 8 用SCP的方式传送一个40M的文件。Server7 的MTU 为9000字节,Server8 的MTU 2000 字节。
TCP行为理论分析:
在TCP协商期间,Server 7和Server 8之间会把自己当前的MSS值告知对方,通过对比本地MSS与对方的MSS值后,取两者最小值,如下:
Server 7MSS值为9000-40=8960字节。
Server 8 MSS值为2000-40=1960字节。
很明显,Server 7和Server8 会达成协议,两者中取Server 8的MSS值,即1960字节。这个值将是他们的TCP数据传输过程中单个TCP数据包载荷的最大值。
让我们通过实验抓包来验证理论分析:
如上图,正如理论分析。在Server 7(3.3.3.3)与Server8(4.4.4.4)TCP三次握手期间,Server7发送了8960字节的MSS,而Server8发送了1960字节的MSS。
为了证明Server7 选择了较低的1960字节的MSS,让我们看看数据包传输过程中的TCP数据包大小。
因为TCP存在窗口流控机制,TCP速率不会从一开始就达到最大值,而是一个循序渐进增长的过程,当然最后还是如上图红框所示达到了TCP的单包最大值2014字节,即TCP-MSS值。
这里的2014为整个数据包大小,分拆后如下:
2014字节=14字节二层帧头+20字节IP头+20字节标准TCP头+12字节TCP可选项+1948的负载。
此处的TCP-MSS仍然为1960字节,只是被拆分成为了12字节的TCP可选项+1948的负载。
(记住MSS计算是以标准的20 字节TCP头为参考,在计算时拖挂的TCP选项被放在负载之内考虑)
通过抓包证明,Server7 的确使用了Server 8汇报的1960字节的TCP-MSS为最大报文尺寸,而不是自身的8960字节。从而证明我们的理论分析完全正确。
但是,让我们换个视角看看这些数据包在运营商网络里面是什么样:
上图为运营商路由器SRX03 的ge-0/0/0接口抓包,大家是否已经看见了每个数据包都带了一个分片包的尾巴,以及大量刺眼的TCP Ack数据包。
一个完整的2014字节数据包就这样被一分为二:1514字节数据包+534字节数据包
那为什么数据包在运营商网络会被分片?
大家是否还记得。在第一步中,我们通过人工方式知晓了运营商网络的ISO第三层网络层MTU值为1500字节。所以很明显,2014字节的数据包超过了1514(1500网络层+14字节链路层二层帧头)的大小,从而数据包被切割分片。
解决方案:拦截TCP-MSS值
由于管理员通过手工测试获悉了整条链路的TCP-MSS值。如果我们能够配置站点A或B的SRX路由器,拦截并修改Server7 和Server8之间的TCP-MSS协商为手工测试的TCP-MSS值,变相告知两台服务器正确的端到端TCP-MSS。那岂不避免了服务器上的TCP应用不至于触及到运营商网络的MTU天花板,从而避免被分片的命运?
答案是Yes!
在SRX01以及SRX02作如下配置:
set security flow tcp-mss all-tcp mss 1460
完成配置以后,在站点A的SRX01面向运营商接口的ge-0/0/1抓包如下:
如上图,当Server 7和Server8 在协商TCP-MSS时,无论是Server7 或Server8发送的TCP-MSS均为1460字节。
同时,通过查看TCP数据流,我们可以很明显的看出修改前后的区别,在上图中TCP完成窗口初始化后,直接以1514字节的整包传输,而不是先尝试2014字节。
TCP-MSS实验小结:
· 通过修改TCP-MSS的确能够有效的防止TCP流量触及天花板,进而优化TCP的传输性能。
· 但是TCP-MSS有一个弊端,那就是需要人工先提前检测出整个链路的MTU值,并在出口路由器上全局干预站点的TCP-MSS协商。
你说要是这MTU能够自动发现,完全不需要人为干预多好啊!
方法是有的,请接着看。
3.鸟枪换炮:Path MTU Discovery(链路MTU自动发现协议)
Path MTUDiscovery,MTU自动发现!
这工具一听就觉得高大上,但是作为资深网工,我们不光要知道 Path MTU Discovery(以下简称PMTU)是什么,更需要清楚PMTU是如何高大上,并自动发现MTU最优值的?
先上理论:
顾名思义,PMTU自动发现就是赋予应用程序权力让其自主发现端到端链路的MTU值,并绕开数据包分片这个坑。
目前支持PMTU的协议为TCP以及UDP。
其MTU发现机制非常巧妙和简单,用一句话形容就是:不怕犯错敢于尝试,最后总会得出正确值。
方法如下:
当主机发送TCP/UDP数据包时,主机会在每一个发送的数据包上打上DF=1的标志,当这些数据包经过下游链路到达目的地之前,由于DF1的原因数据包永远不会被中间节点分片。
自然而然,数据包的结局只可能有两种:被中间路由器丢弃或者顺利到达目的地。
同时,主机在发送数据包给下游目的地时,它并不知晓整个网络链路上的最小MTU值。介于此,主机在初期发送数据包时,数据包尺寸为其所在链路的MTU值。
把上面两个条件组合起来后,主机发送的数据包就会出现如下结局:
1. 如果主机本地链路的MTU大于端到端链路中某一点的MTU值,那么这个数据包因为有DF=1的原因,会被丢弃。
2. 如果路由器本地链路的MTU为整个端到端链路中最小值时,数据包很幸运的被送达目的地。
对于结局2,皆大欢喜。
而我们需要重点说说结局1。在结局1中,当链路中路由器丢弃此数据包时,此路由器会返回一个ICMP 的Destination Unreachable,Fragment Needed(目标不可达,需要分片)的消息给源主机。
对源主机而言,相比知晓目标不可达而言。它更需要另外一个消息:
这个ICMP包中另有玄机,它同时也包含了此链路路由器的下一跳MTU值!
回到本文章第一步手工测试MTU那一段,我曾提到了一个运营商内部人士,他悄悄告知了我们ISP运营商正确的MTU值是多少。而这个高人正是丢包路由器的ICMP 目标不可达消息。我们可以把这个ICMP数据包理解为一个信使,通过告知源主机正确MTU值后,源主机可以调节字节的发送数据包尺寸,从而避免数据过大被丢弃。
演示案例:
还是以TCP-MSS中的Server7和Server8通信为例,服务器Server 7 要往Server 8 用SCP的方式传送一个40M的文件。Server7 MTU9000字节,Server8 MTU 2000 字节。
不过与TCP-MSS情况不同,在此案例中我们在Server7和Server8上开启了PMTU。
同时关闭站点A与B出口路由器SRX01 和SRX02的TCP-MSS拦截:
lab@SRX01#delete security flow tcp-mss [edit] lab@SRX01#show | compare [editsecurity] - flow { - tcp-mss { - all-tcp { - mss 1460; - } - } - }
通过上图,Server7 和Server8恢复到了8960字节和1960字节的TCP-MSS。
重点在下图:
大家可以看见,根据TCP-MSS协商原则,Server7最终选择了较低的2014字节,但是这个值还是大于了运营商链路中的MTU=1514。
正如之前所说,IP为1.1.1.100(ISP运营商SRX03)的路由器针对每一个丢弃的数据包回复了一个ICMP不可达消息。而更有意思的是,在这个ICMP包里面携带了下一跳MTU=1500的惊天大秘密。
而作为源的Server7也不简单,当它收到这个小道消息后,马上修改了自己的数据包发送尺寸。从2014立即改为1514(底部红色字体数据包串),并使用此尺寸来发送后续的TCP重传数据包。
PMTU小结:
以上就是一个典型的PMTU行为案例。从中可以看出,PMTU不需要人为干预其具体MTU值,PMTU会通过试错的方式来发现最佳MTU值。
当然链路中可能存在各式各样的MTU值,而PMTU则以不变应万变。只要把上述过程在不同链路中重复一次或多次就一定会最终发现一个端到端的最优MTU值。
那PMTU有什么缺陷?
你或许已经猜到了,它太依赖ICMP消息。
常见MTU典型故障总结以及案例分析
1.防火墙做的好事儿,ICMP被干掉了。
在Path MTU Discovery中,我们发现PMTU工作的核心机制在于主机能够收到中间某个节点发过来的ICMP不可达数据包,并包含了下一跳MTU值。
那假设我们在上述拓扑中,把站点A的SRX01入站口放置ACL阻止ICMP从运营商进入站点A。
虽然理论上给站点A带来了额外的安全保障,但是副作用就是PMTU被干掉了。
因为缺失ICMP消息,PMTU不再生效。
解决方法:
日常工作中,大家可以考虑在边界防火墙上放行ICMP协议,或者至少放行ICMP unreachable(ICMP Type 3 Code 4)消息。
2.夹心饼干,三层设备之间夹杂二层设备。
在一般网络拓扑环境中,往往在三层路由器设备之间会通过二层交换机汇聚互联。
但是在某些情况下,因为设计原因或者人为失误。导致二层交换机的MTU值小于三层路由器MTU值,从而导致网络故障。
可能有人会问,那MTU小了,分片就可以了,也不至于产生网络故障。
但是仔细考虑发现,数据包分片只存在于ISO第三层的网络设备上,而二层节点只查看二层帧,其并没有重写三层IP头的能力,更不知晓三层IP层的具体网络细节。
当数据包过大以后,二层设备是不会也不能去分片数据包,因为在配置的时候,没有赋予它任何此二层VLAN的IP信息,导致其没有写IP数据包的能力去执行数据包分片。
让我们来看一个诡异的示例:
如上图,SRX3和SRX5之间配置了9000字节的MTU,而SRX03和SRX05则通过SRX04的二层设备互联。
在二层SRX04设备上,所有ISO二层帧MTU值为1514。
由于SRX03和SRX05是ISP运营商路由器,它们之间运行了BGP路由协议。但是令网络工程师不解的是,SRX03和SRX05的BGP邻居虽然处在UP状态。但是SRX05没有收到任何SRX03通告的路由?
SRX03 BGP邻居信息:
root@SRX3> show bgp summary Groups: 2 Peers: 2 Down peers: 0 Table TotPaths Act Paths Suppressed History Damp State Pending inet.0 10 7 0 0 0 0 Peer AS InPkt OutPkt OutQ Flaps Last Up/DwnState|#Active/Received/Accepted/Damped... 1.1.1.1 101 13 19 0 3 3:53 3/4/4/0 0/0/0/0 35.0.0.5 200 5 34 6904 7 11 4/6/6/0 0/0/0/0
#上述输出中,SRX05的35.0.0.5在SRX03的邻居列表中,并且SRX03收到了SRX05宣告的6条路由。如下:
root@SRX3> show route receive-protocol bgp 35.0.0.5 inet.0: 10009 destinations, 10013 routes (10009 active, 0holddown, 0 hidden) Prefix Nexthop MED Lclpref AS path * 2.2.2.0/24 35.0.0.5 200 I * 4.4.4.0/24 35.0.0.5 200 201 I * 10.69.109.0/24 35.0.0.5 200 I 10.221.22.0/24 35.0.0.5 200 201 I 35.0.0.0/24 35.0.0.5 200 I * 172.16.1.0/24 35.0.0.5 200 201 I root@SRX3>
但是在SRX05中,却没有收到任何SRX03的路由条目:
root@SRX5> show bgp summary Groups: 2 Peers: 2 Down peers: 0 Table TotPaths Act Paths Suppressed History Damp State Pending inet.0 4 3 0 0 0 0 Peer AS InPkt OutPkt OutQ Flaps Last Up/DwnState|#Active/Received/Accepted/Damped... 2.2.2.2 201 36075 35936 0 3 1w4d6h 3/4/4/0 0/0/0/0 35.0.0.3 100 2 5 0 8 4 0/0/0/0 0/0/0/0
更诡异的是,两者的BGP邻居总是过一段时间后就闪断一次。
让我们来通过抓包来揭示谜底:
在二层设备SRX04上抓包如下:
很明显,当SRX03(35.0.0.3)与SRX05(35.0.0.5)通过TCP建立BGP邻居时一切正常。数据包大小均不大于1500字节。
但是当BGP开始通过Update消息传输路由条目(NLRI)的时候,开始以单包8000字节大小以上数据包传输。而中间的SRX04 ISO二层MTU为1514字节(三层MTU为1500字节),这8000字节的数据包将会被无情的丢弃。而因为SRX04是二层设备,其将不会返回任何ICMP信息给SRX03。造成SRX03的TCP数据包不断超时并重传。最终在到达TCP最大重传次数后,BGP邻居被删除。
解决方法:
请务必保证路由器之间的二层设备MTU值与三层路由器设备MTU相同。
总结:
在这片文章中,我们一起分析了常见MTU最优值的发现方法,并简单列举了两个常见MTU问题,知识回顾如下:
1. 手工MTU发现:通过手工ICMPPing测试发现最优MTU值。
2. TCP-MSS:TCP协议的最大传输报文尺寸,通过在中间防火墙或者路由器干预端到端的TCP-MSS协商从而达到避免链路数据包分片的目的。
3. Path MTU Discovery:用于TCP/UDP数据包,通过设置初始数据包的DF=1不断试探最佳MTU值,最终发现端到端的最佳MTU值。
当然,在日常工作中,MTU的问题会比我们想象的更为复杂。
例如嵌套了IPsec ***和GRE隧道的端到端流量等。但是万变不离其中,只要大家掌握了MTU和相关工具的工作原理和细节。我们就可以从容分析各种场景并使用对应的工具来解决实际问题。
谢谢!