蛮荆

网络基础: MTU 和 MSS

2017-02-01


概述

MTU 属于链路层,计算大小时会包括 IP 数据包的头部和数据。

MSS 属于传输层,计算大小时仅包括 TCP 段的数据部分,不包括 TCP 和 IP 头部。

UDP 不考虑 MSS,但也应该注意 MTU 限制 (由应用层完成),避免数据报过大而导致 IP 层分片。

MTU 是什么?

MTU(Maximum Transmission Unit,最大传输单元)是指网络层在一次传输中能够传输的最大数据包(或帧)的大小,以字节为单位。

MTU 决定了单个网络帧的最大大小,超过这个大小的数据必须被网络层分片传输,所以正确配置 MTU 可以提高网络效率,减少分片带来的额外开销,避免过大的数据包在传输过程中被网络链路中节点丢弃。

发送数据包的大小是由 MTU 值较小的一方决定,如果数据包长度超过了接收方的 MSS, 就会被接受方直接丢弃。

MTU 标准大小

MTU 的大小根据以太网的标准来设置,以太网标准规定,一个网络帧最大为 1518 字节,去掉以太网头部的 18 字节后,剩余的就是以太网 MTU 的大小。

$$ MTU 标准大小 = 1500 字节 $$

Wi-Fi 同样为 1500 字节。

MSS 是什么?

MTU 表示一个数据帧的大小,但是一个数据帧中不可能全部是应用数据,还需要有传输层和网络层的头部,这样才能保证数据包正常并且正确地从发送方到达接收方。

MSS(Maximum Segment Size,最大报文段长度)是指在 TCP连接中,单个 TCP 报文段中数据部分的最大大小,以字节为单位。MSS 的主要作用是限制 TCP 报文段的大小,确保数据能够在网络路径上不被分片地传输,从而提高网络传输效率和性能。

图片来源: 网络是怎样连接的(户根勤)

MSS 由 TPC 连接的双方在 TCP 三次握手过程中协商得出的,由 MSS 值较小的一方决定。

MSS 不包括 TCP 头部和 IP 头部,它指实际数据的大小 (也就是具体的应用数据)。

$$ MSS = MTU - IP 头部大小 - TCP 头部大小 $$

例如典型的 IPv4 中

$$ MSS = 1500 − 20 − 20 = 1460 $$

如何确定是否分片?

当发送方发给接收方的数据包 (例如 2500 字节大小) 经过路由器时,如果超过路由器的 MTU 大小 (例如路由器 MTU 只有 1500),此时路由器面临两个选择:

  • 直接将数据包丢掉 (丢包)
  • 将数据包分片后再传输

路由器如何做出选择,取决于数据包在网络层是否设置了 DF(Don’t fragment)标志:

  • 如果设置了,直接丢弃数据包
  • 如果没设置,数据包被分片再传输
# 如果通信中出现类似如下的报错 (只丢大包)
# 说明很可能就是 MTU 过大导致的
Packetneeds to be fragmented but DF set

为什么 TCP 分段之后,IP 还要分片?

网络层的 IP 依赖下层的数据链路层传输数据,受限于数据链路层的 MTU, 所以必须对数据进行分片传输。

那传输层的 TCP 是依赖网络层传输数据的,直接把数据交给 IP 层不就可以了,为什么还要自己进行分段呢?

网络层的 IP 协议提供 “尽力而为” 的传输服务 (也就是不保证数据包一定会被送达),如果要使传输过程变得可靠,就要使用传输层的 TCP 协议来实现。

TCP 实现可靠传输的核心之一就是 重传机制,如果每个应用数据包都很小,那么 TCP 确实不需要分段,只要在重传时将数据包丢给网络层 IP 就可以了。

但是现实中不是这样的,考虑一些大的数据传输场景,例如大文件下载,如果 TCP 不进行数据分段处理,并且大文件在传输过程中部分数据丢包了,紧接着发生 TCP 重传 (因为丢失的数据分片导致接收方的 IP 层无法完成 IP 分片数据组装,所以接收方不会响应 Ack 报文给发送方,发送方迟迟收不到 Ack 报文,所以会触发超时重传),此时重传的就是整个大文件,这显然是不合理的,因为只需要重传丢包的数据部分就可以了。如果 TCP 对数据进行分段处理,那么发生丢包后,只需要重传丢包的部分就可以了 (根据发送方和接收方的 Seq 和 Ack 值就可以判断出哪些数据包丢失了)。

这其实就和应用层实现 大文件断点续传/下载 原因是一样的,虽然可以直接下载整个文件,但是出于网络可靠性和服务器负载等情况考虑,一般还是会选择使用数据分片 Accept-Ranges 的方式来下载文件。


MTU 很重要

为什么说 MTU 很重要?

因为如果 MTU 设置不正确 (一般情况下是过大),IP 层会数据分片,然后引起分片重组,引发延迟,延迟可能会引发丢包和 (TCP) 重传,丢包同样会引起 (TCP) 重传,最终进入网络质量的恶性循环。


如何适配 MSS ?

如果 MSS 设置不合理,就会引发丢包和分片:

  • 丢包意味着重传 (尤其在 TCP 中影响更恶劣),堪称网络性能第一杀手
  • 分片意味着传输效率降低 (需要数据分片、数组重组会带来额外的开销)

所以通信双方协商好 MSS 至关重要。

MSS 的大小是由通信双方 MSS 值较小的一方决定。

如图所示 (Wireshark 抓包),TCP 三次握手建立连接时,第一个和第二个包中,发送方和接收方分别声明了自己的 MSS:

  • 发送方声明了自己的 MSS = 1460 (字节)
  • 接收方声明了自己的 MSS = 1412 (字节),可以看出接收方的网络接入比较复杂 (在传输过程中会增加不同的报文头,导致 MSS 会不断变大),所以 MSS 给的较为保守

三次握手之后,发送方知道自己的 MTU 比接收方的要大,如果直接发送 1460 字节的包,很可能在网络链路中的某个节点被分片或者丢包了,所以在接下来传输数据时,发送方会将自己的 MSS 调整为 1412。

但是需要注意的是: 如果网络链路中的某个节点的 MTU 比发送方和接收方的更小,那么数据包还是会被分片。

默认的 MSS 大小

如果通信的对端,没有传递 MSS, 那么接下来的通信 MSS 以什么为准呢?

  • 对于 IPv4,默认的 MSS 值是 536 字节,因为 IPv4 最小 MTU 等于 576 字节,减去 40 字节(IP 头部 + TCP 头部),正好是 536
  • 对于 IPv6,默认的 MSS 值是 1220 字节,因为 IPv6 最小 MTU 等于 1280 字节,减去 60 字节(IPv6 头部 + 20 字节的 TCP 头部),正好是 1220

TCP Adjust-MSS

TCP Adjust-MSS 用于动态修户 TCP 报文的 MSS 值,通常配置在防火墙、路由器,解决网络链路中 MTU 自动适配问题。


如何获取 MTU 值 ?

查看本机 MTU

Linux 中获取 MTU 值大小:

$ ifconfig | grep -i mtu

# 输出如下
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536

从输出结果中可以看到,MTU 值和网卡设备是关联的,所以每个网卡设备的 MTU 值大小是不同的。

# 查看某个具体网卡的 MTU
$ ifconfig eth0 | grep -i mtu

# 输出如下
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500

查看网络链路中的 MTU

1. tracepath 命令

Linux 中有一个专用的命令: tracepath

$ tracepath dbwu.tech

# 输出主机到目标地址的每一跳
# 并显示路径中的最小 MTU 值
 1?: [LOCALHOST]                                         pmtu 1500
 1:  10.90.89.46                                           0.500ms
 1:  10.90.91.46                                           0.545ms

...

14:  172.70.212.4                                        191.432ms asymm 15
     Too many hops: pmtu 1500
     Resume: pmtu 1500

2. networksetup

Mac 中有一个专用的命令: networksetup

$ networksetup -getMTU en0

# 输出如下
Active MTU: 1500 (Current Setting: 1500)

3. ping 命令

ping 命令 (Linux 环境) 的几个参数说明:

  • M do:设置不允许分片
  • s :设置数据包的有效负载大小 (不包括 ICMP 头部的 8 字节)

ping 命令采用 ICMP 协议来完成探测,ICMP 头大小为 8 字节,IP 头大小为 20 字节,如果网络链路的 MTU 为 1500 字节,那么单个数据包最大长度等于:

$$ 1500 - 20 - 8 = 1472 $$

首先,先设置数据包大小为 1472,看看是否可以连通:

$ ping -M do -s 1472 -c 3 dbwu.tech

# 输出如下
PING dbwu.tech (104.21.71.166) 1472(1500) bytes of data.
1480 bytes from 104.21.71.166 (104.21.71.166): icmp_seq=1 ttl=50 time=200 ms
1480 bytes from 104.21.71.166 (104.21.71.166): icmp_seq=2 ttl=50 time=200 ms
1480 bytes from 104.21.71.166 (104.21.71.166): icmp_seq=3 ttl=50 time=200 ms

--- dbwu.tech ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 200.285/200.317/200.365/0.367 ms

通过输出结果可以看到,数据包大小为 1472 时,可以正常连通。

接下来,将数据包大小增加,设置为 1473,看看是否可以连通:

$ ping -M do -s 1473 -c 3 dbwu.tech

# 输出如下
PING dbwu.tech (172.67.147.110) 1473(1501) bytes of data.
ping: local error: Message too long, mtu=1500
ping: local error: Message too long, mtu=1500
ping: local error: Message too long, mtu=1500

--- dbwu.tech ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 1999ms

通过输出结果可以看到,数据包超过了 MTU,而且不允许分片,所以连通失败了。

最后,设置允许数据分片,看看是否可以连通:

$ ping -s 1473 -c 3 dbwu.tech

# 输出如下
PING dbwu.tech (172.67.147.110) 1473(1501) bytes of data.
1481 bytes from 172.67.147.110 (172.67.147.110): icmp_seq=1 ttl=51 time=207 ms
1481 bytes from 172.67.147.110 (172.67.147.110): icmp_seq=2 ttl=51 time=207 ms
1481 bytes from 172.67.147.110 (172.67.147.110): icmp_seq=3 ttl=51 time=207 ms

--- dbwu.tech ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 207.504/207.513/207.522/0.007 ms

通过输出结果可以看到,虽然数据包超过了 MTU,但是允许分片,所以连通依然可以成功。


Path MTU Discovery

PMTUD(Path MTU Discovery)是一种用于确定从源到目标地路径上的最大传输单元(MTU)的方法。

PMTUD 的基本思想是通过逐步减小数据包大小,确定路径中最小的 MTU 值,具体的工作过程:

  1. 发送初始数据包

源主机首先假设网络链路的最小 MTU 等于自己的 MTU,发送一个不允许分片(Don’t Fragment,IP 报文中的 DF 标志)的大数据包。

  1. ICMP “需要分片” 消息

如果数据包在网络链路的某个转发节点因为 MTU 过大 (且不允许分片) 而无法通过,就会直接丢弃数据包并返回一个 ICMP Fragmentation Needed 消息给源主机,并指明路径中此时的 MTU 值。

  1. 调整数据包大小

源主机接收到 ICMP 消息后,更新 MTU 值,并重发较小的、不允许分片的数据包。

  1. 重复上述步骤

源主机重复发送不允许分片的数据包,并根据 ICMP 消息不断调整数据包大小,直到数据包成功到达目的地。

最后一次发送的数据包的 MTU 值,也就是整个网络链路中的最小 MTU 值。

局限性

PMTUD 依赖 ICMP 协议进行探测,但是 ICMP 会被很多网络链路中的设备禁用,所以整体上可用性较差,改进的方法是 PLPMTUD(Packetization Layer Path MTU Discovery),不依赖 ICMP 消息,而是通过传输层协议(如 TCP)的反馈机制来发现网络链路的 MTU。

Linux 参数

$ sysctl net.ipv4.ip_no_pmtu_disc

net.ipv4.ip_no_pmtu_disc = 0

扩展阅读

转载申请

本作品采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,商业转载请联系作者获得授权。