这些网络知识你清楚吗?一切都要从MTU 和 MSS 说起
共 11063字,需浏览 23分钟
·
2024-04-18 08:33
推荐一个原创技术号-非科班大厂码农,号主是机械专业转行进入腾讯的后端程序员!
MTU
-
为什么需要 MTU? -
为什么是我看到过的 MTU 都是 1500? -
如果传输的数据(在二层我们叫做 Frame)超过了 MTU 会发生什么? -
那什么时候发送的数据会超过 MTU?
下面就以上几个疑惑一一进行解释,我们从最简单的说起……
为什么需要 MTU,MTU大小为什么是1500
MTU 的存在很合理,Frame 不可能无限大,发送小的数据是可以的。所以就设定了一个最大值。我们在网卡上看到的 MTU 一般都是 1500bytes,要注意这个值指的是 Frame 内容的最大值,并不包括 Ethernet Frame 的 header 和 FCS。一个 Ether Frame 最大是 MTU + Header 14bytes + FCS 4 bytes = 1518 bytes.
Larger MTU is associated with reduced overhead. Smaller MTU values can reduce network delay.
更大的包意味着出错的概率更大,所以会增加重传的比例;
重传的代价也更大,一大段数据里面如果有一个 bit 出错了,这一大段就会整个重传;
-
以太网是分组交换网络,即存储,转发,在转发给下一跳之前,路由设备或者交换机要存储还没发完的数据,更大的 MTU 就对设备的性能有更高的要求,意味着更高的成本;
综上,MTU既不能设置的过大,也不能设置的过小,因此1500 其实是一个 Trade Off,这就是为什么一般 MTU 都是 1500。其实,不同的 2 层协议有不同的 MTU:
这就有了下一个问题:那如果超过了这个大小呢?
超过 MTU 的 Frame 会发生什么?
既然上文说到基本上所有的设备设置的 MTU 都是 1500,那么为什么还会出现超过 1500 的 MTU 呢?
什么时候发送的数据会超过 MTU?
VXLan 包结构
这样,假设我们原来的 Ethernet Frame 里面的数据是 1500 bytes,经过 VXLan 包装之后,就变成了:1500 + 14(原来的 Ethernet Frame header) + 8(VXLan header) + 8(UDP Header) + 20 (IP Header) = 1550 bytes, 超过了 50 bytes. (原来的 Frame 里的 FCS 不在里面,因为网络处理过了。)
它是原来的 Ether II frame 变成了 UDP 的数据,被包起来了,又封装成 IP,Ether 发出去。
超过 MTU 的包大部分网络设备都会直接丢掉,所以我们就需要保证发送的数据不超过 MTU (上图是一个反例)。
如何保证发送的数据不超过 MTU?
很显然,我们需要分成多份发送。如果我们要让 2 层网络发送(意思就是包括 IP header 在内一共) 4000 bytes 的数据,那么就要分成 3 个 Etherframe 来发送:第一次发送 1500 bytes,第二次 1500 bytes,第三次 1000 bytes.
-
网卡驱动知道 2 层的 MTU 是多少; -
3 层协议栈 IP 会问网卡驱动 MTU 是多少; -
4 层协议 TCP 会问 IP Max Datagram Data Size (MDDS) 是多少;
所以 TCP 在握手的时候,会把自己的 MSS 告知给对方。
MSS
MSS通告
在 TCP 的握手阶段, SYN 包里面的 TCP option 字段中,会带有 MSS,如果不带的话,default 是 536. 对方也会把 MSS 发送过来,这时候两端会比较 MSS 值,然后选择一个最小的值作为传输的 MSS.
实际应用场景是什么?拿上文我们提到的 VXLan 封装举例,VXLan 封装的这一端知道自己需要 50bytes 的 overhead 来封装 VXLan,那么它就可以告诉对方,自己能接受的最大的 MSS 是 1410bytes (1500bytes MTU – 20 IP headers – 20 UDP headers – 50 bytes VLan),对方发过来的 MSS 是 1460 bytes(1500 bytes – 20 bytes – 20 bytes). 然后两端都会用 1410 bytes 作为 TCP MSS 值,即保证发送的 4层 segment 都不会超过 1410 bytes.
这里就有一个疑问:为什么 MSS 两端都使用一个共同的值,而不是 A -> B 1410 bytes; B -> A 1460 bytes, 这样不是可以更高效吗?
这个问题的答案我找了好久,感觉很多地方说法不一,比如这里就说:
TCP MSS is an option in the TCP header that is used by the two ends of the connection independently to determine the maximum segment size that can be accepted by each host on this connection.
从 10.130.0.6 发送给 10.130.0.5 最大的包是 800.
从 10.130.0.5 到 10.130.0.6 也是 800.
MSS 设置的方法
#1、iptables
iptables -I OUTPUT -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 48
#2、ip route
ip route change 192.168.11.0/24 dev ens33 proto kernel scope link src 192.168.11.111 metric 100 advmss 48
#3、程序可以自己设置,本质上是自己往 TCP option 里写 MSS:
from scapy.all import *
ip = IP(dst="192.168.11.112")
tcp = TCP(dport=80, flags="S",options=[('MSS',48),('SAckOK', '')])
ifconfig eth0 mtu 800 up
. 这样 Kernel 的 TCP 栈在建立连接的时候会自动计算 MSS。
我们这里说的都是 TCP 两端的设备如果清楚自己的网络情况的话,可以进行的一些设置。还有一些情况,比如说一些 VPN 和 overlay,两端对此并不知晓,完全是中间的路由设备做的。中间设备需要预留 50bytes,有什么方法可以让两边都知道,发送的数据包要预留 50bytes 呢?
MSS Clamping
我们在一个 VPN 环境中测试一下,网络结构可以简单地理解为 [Client -> VPN] -> Server.
Client 端对 TCP SYN 抓包
Server 端对 TCP SYN 抓包
其实,每一层协议自己都会有机制,让自己发送的内容不超过下层协议能承载的最大内容(最大 PDU)。但是毕竟我们不处于一个完美的世界……
不完美的网络环境
我们从下往上讲起。
Layer 2
二层协议一般都很简单,如果收到了超过 MTU 的包,一般会简单地 drop 掉,要依靠上层协议来保证发送的数据不超过 MTU。
但是也有协议可以支持拆分(Fragment),比如二层的 MLPPP
Layer 3
IP 层的处理就比较经典了,自己收到的是上层协议发给它的内容,然后要负责通过 2 层来发送出去,上层的内容是无法控制的,但是要控制自己发送到下层的内容。
所以 IP 支持一个 feature 叫做 IP Fragmentation.
如果 IP Packet 超过了 1500bytes,IP 协议会将这个 packet 的 data 段拆分成多个,每一个都加上 IP header,以及 fragment header 标志这是拆分成的第几段。接收端等收到所有的 IP 分片之后,再组装成完整的数据。
我们可以通过 ping 来发送一个超过 MTU 1500 bytes 的数据。ping -s 2000 -I 172.16.42.21 172.16.42.22
抓包如下:
由此,可以发现 IP Fragmentation 其实是把上层的数据拆分到多个 IP 包里面,不管上层的数据是什么。说白了,第一个 frame 有 ICMP 的 header,第二个 ICMP 包没有。如果把承载 ICMP 协议换成 TCP 协议,我们就可以发现问题了:收到了 IP framented frame,是无法处理的,因为这个 IP 包的数据对于上层协议来说是不完整的,假设一个 IP 包被 fragment 成了 3 个 IP 包,我们就必须等到 3 个 IP 包全部到齐才可以处理。
所以说:IP Fragmentation is generally a BAD thing.
可能导致的问题有:
-
同上面提到的 MTU 为什么是 1500 一样的问题:假设拆分成了 3 个包,丢了一个包就相当于全丢了,丢包率直接变成(假设丢包率是 10%,那么3个包都不丢的概率就是 90%^3=72.9%)27%; -
导致 TCP 乱序:现在网络很多设备都是针对 TCP 做优化的,比如,根据 TCP 的 port number 去 hash 到同一条路由上去,减少 TCP reorder 的概率。但是如果 IP fragmentation 发生的话,后续的 IP 包在路由器看来并不是 TCP 包,因为 TCP header 只在第一个 fragment 上才有,所以会导致 hash 失效,从而更容易发生 TCP 乱序;另外,对段会等齐所有的 fragment 到达才会交给上层,这也导致了延迟增加和乱序的发生; -
产生一些比较难 debug 的问题; 不是所有系统都能处理 IP Fragmentation,比如 Google GCE;
所以,在现实的世界中,我们几乎看不到 IP Fragmentation 的,要依靠上层协议保证传给 IP 层的数据大小不需要 fragment.
Layer 4
上文已经提到了 MSS。但是我们平时写应用程序的时候,从没有自己分过 Segment,这是因为 TCP 是面向数据流的,你有一个 socket 之后,尽管向里面写就可以了,Kernel 的协议栈会负责给你将数据拆成正好能放到 IP 包里的大小发出去。注意这里是拆成多个 TCP segment 发送,在 IP 层并没有拆开,每一个 IP 包里面都有 TCP 的 header。
DF(Don’t fragment bit)
IP 协议的 header 中有一位 bit 叫做 DF,如果这个 bit 设置了,就是告诉中间的路由设备不要分片发送这个包,如果大于最大传输单元的大小,直接丢弃即可。丢弃这个包的设备会发回一个 ICMP 包,其中,type=3 Destination Unreachable, Code=4 Fragmentation required, and DF flag set. RFC 1191
tcpdump -s0 -p -ni eth0 'icmp and icmp[0] == 3 and icmp[1] == 4'
DF=1
,遇到 MTU 太大丢包发回来的是 ICMPv6(Of course!)
道理我都懂,但是我的抓的包怎么大?
正常情况下,jacson在序列化时,json串中的字段名称和类中属性的名称是一样如果你通过抓包去看一下 MSS 是否是有效的,里面每一个包的大小是否最大是 1500 bytes,你会怀疑人生。
明明协商的 MSS 1460,但是后面的数据居然有 1万多bytes的??在接收端抓包也一样。
这个叫 TSO,TCP Segment Offload.
但是对于抓包来说,我们看到的就是 Kernel 发送了大包,因为抓包过程是看不到后面网卡具体做了什么的。
如果我们关闭 TSO 功能: ethtool -K eth0 tx off
。然后再抓包,你就会发现抓到的每一个发送的包都是 1500 bytes 了。
但是即使你按照 1500bytes 发送,然后这时候去接收端抓包,会发现还是有大包。发送端发送的都是小包,为啥到接收端就成了大包呢?显然网卡可以对发送做 offload,也可以对接收做 offload,网卡会攒一些 TCP 包,然后合起来发送给 Kernel 的协议栈。
第二个是工作中遇到的问题,也是我看这些东西的起因。
我们的 SDN 网络有一种这样的路由:
前面提到 IP Fragment 有很多安全问题,这里列举了其中一些:
-
IP fragment overlapped:攻击者精心设计了很多 IP 分片,它们互相重叠,理论上这种包是无法在网络上出现的。如果服务器收到这些分片,可能无法正确处理(IP 实现的 Bug),那么可能会崩溃; -
IP fragment overrun:攻击者通过 IP 分片的方式,发送的 IP 包组装之后超过了 65535,可能造成服务器崩溃(溢出); -
IP fragmentation buffer full:攻击者一直发送 IP 分片,more-fragments 一直设置为 true,导致服务器收到 IP 包的时候,只能存储在 buffer 中试图将它们组装起来,直到内存耗尽 (DDoS); -
其他构造的无法正确组装的 IP 包。可能导致 DDoS,或者可能导致 IDS(入侵检测系统)无法正确组装并识别这些包,导致这些包绕过安全系统进入了服务器,最终构造出攻击。参考 Rose Fragmentation Attack IP fragment too many packets
IP fragment incomplete packet
IP Fragment Too Small
推荐阅读:
专注服务器后台技术栈知识总结分享
码农有道,和您聊技术,和您聊职场,和您聊互联网那些事!