一锅老鼠屎,坏了一粒粥---朕与MTU的爱恨情仇


一、MTU:网络世界的“快递限重”

MTU(Maximum Transmission Unit),中文名叫最大传输单元,说直白点就是网络里一个数据包能装的最大数据量,单位是字节。

这东西特像快递公司的“限重规则”:三通一达一般只接20斤以内的小件,顺丰能扛几百斤甚至几吨的货——这里的“20斤”“几百斤”,就是不同场景下的MTU。

常见的MTU值有这么几类:

  • 以太网(1Gbps以内)默认MTU是1500,这是咱们家用/企业内网最常见的“标准箱”;
  • PPPoE拨号上网(比如不少家庭宽带)的MTU通常是1492,因为PPPoE协议要多占8个字节的“快递面单”空间,得从1500里抠出来;
  • VPN隧道(比如WireGuard、OpenVPN)的MTU更小,一般在1380-1420之间,毕竟加密封装还要额外加“安全包装”。

MTU的核心作用就一个:决定数据包要不要“拆分”。比如你发一个1500字节的包到MTU=1420的网关,网关会把它切成两个小包装(比如1420+80字节),再发往下游。

但有人会钻牛角尖:“那我把MTU调最大,不就不用拆包了?” 这得看情况:

  • 要是你能控制全链路所有设备(比如公司内网),把MTU统一调到9000(巨帧),确实能提升效率;
  • 但要是数据包要走公网,劝你别折腾——公网路由器大多只认1500的标准,你的“大胖包”到了公网还是会被拆,而且只要有一个子包丢了,整个原包就废了,最后还得重传,反而浪费流量。

那反过来,“我把MTU调最小,总不会出问题吧?” 也不行。每个数据包都有“头部信息”(比如IP头、TCP头),MTU越小,相同数据量下要发的包就越多,头部占比就越大——相当于你寄10斤苹果,全用小盒子装,最后盒子重量比苹果还重,纯纯浪费带宽。

二、问题爆发:GitHub连不上,竟是MTU在搞鬼

故事得从我的IPv4地址说起。前段时间好不容易把一段公网IPv4“拉”到家里,本以为能解决访问GitHub的问题,结果还是掉链子——curl直接卡住,ping大数据包就丢包。

先放一段当时的群聊记录,感受下我当时的崩溃:

Jack153376:大家有木有检测自己的ip是否被github给ban了的脚本?
鸽※※※4:GitHub 是 v4 only
鸽※※※4:[表情]
Jack153376:我有v4
Jack153376:我的v4目前是上不去github的😭
Dia※※※ei:怎么个上不去
Dia※※※ei:timeout 还是 403
Jack153376:timeout
Jack153376:不清楚是线路原因还是ip的事
Dia※※※ei:线路吧
Dia※※※ei:Ip 应该是 403
Jack153376:我V4线路是天狼only
Dia※※※ei:curl -vvv 一下
Dia※※※ei:看下输出
Jack153376:[代码]
root@rrweb:~# curl -vvv https://github.com
*   Trying 20.205.243.166:443...
* Connected to github.com (20.205.243.166) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
Jack153376:然后就没消息了
Dia※※※ei:ping -M do -s 1472 20.205.243.166
Dia※※※ei:通吗
Jack153376:[代码]
root@rrweb:~# curl -vvv https://github.com
*   Trying 20.205.243.166:443...
* Connected to github.com (20.205.243.166) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):

* OpenSSL SSL_connect: Connection reset by peer in connection to github.com:443 
* Closing connection 0
* TLSv1.0 (OUT), TLS header, Unknown (21):
* TLSv1.3 (OUT), TLS alert, decode error (562):
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to github.com:443 
root@rrweb:~# 
root@rrweb:~# ping -M do -s 1472 20.205.243.166
PING 20.205.243.166 (20.205.243.166) 1472(1500) bytes of data.
From 44.32.191.17 icmp_seq=1 Frag needed and DF set (mtu = 1420)
ping: local error: message too long, mtu=1420
ping: local error: message too long, mtu=1420
^C
--- 20.205.243.166 ping statistics ---
9 packets transmitted, 0 received, +9 errors, 100% packet loss, time 8188ms
Yu※※※i:很明显是mtu问题了
Yu※※※i:把接口mtu调小到1420
Dia※※※ei:[表情]
Dia※※※ei:别问我为什么这么熟悉这个问题
Yu※※※i:一般卡这里都是mtu问题
Jack153376:客户端的锅?
Jack153376:你也经常遇见这个问题?
Dia※※※ei:[表情]
Dia※※※ei:碰到过
Yu※※※i:你接口mtu是1500
Yu※※※i:改成1420
On※※※8:通宵早点休息
On※※※8:[表情]
Jack153376:改了MTU就瞬间好使
Ch※※※iu:我记得天狼好像都有这个问题
Ch※※※iu:直接指网关可以解决mtu问题
Yu※※※i:他这应该是隧道网
Yu※※※i:IP是他自己的

确实,临时把客户端MTU改成1300能解决问题,但让用户手动改配置,是网管的无能——我必须找到一个一劳永逸的办法,让所有客户端不用动,就能正常上网。

三、排查:网关为啥不拆包?

问题的核心很明确:客户端发1500字节的包,到网关后应该被拆成≤1420字节的小包(因为网关的WireGuard接口MTU是1420),但实际没拆,导致数据包被丢。

我先登录网关(xroute),查了下各个接口的MTU:

root@xroute:~# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0@if306: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 5e:e9:ca:61:5c:d0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: eth1@if310: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 06:78:81:8f:09:06 brd ff:ff:ff:ff:ff:ff link-netnsid 0
4: eth2@if314: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether ae:d6:dd:98:52:16 brd ff:ff:ff:ff:ff:ff link-netnsid 0
5: wg_40005: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/none 

看起来没问题:物理接口(eth0/eth1/eth2)都是1500,WireGuard接口(wg_40005)是1420,符合常规配置。按道理,eth2收到1500字节的包,网关会自动拆成1420+80的小包,再通过wg_40005发出去。

那为啥没拆?难道网关的“拆包功能”坏了?

我先查了下Linux内核的PMTUd(路径MTU发现)配置——这是网关自动探测路径最小MTU的关键功能:

root@xroute:~# sysctl net.ipv4.ip_no_pmtu_disc
net.ipv4.ip_no_pmtu_disc = 0

ip_no_pmtu_disc=0 表示PMTUd是启用的,理论上网关能自动探测下游的MTU限制。那问题只能出在“下游路径”上——可能网关拆包后,小包到了某个节点还是超MTU,被丢了。

四、抓凶:找到链路中的“MTU瓶颈”

要解决问题,得先找到“哪个节点在卡脖子”。我用traceroute先理清数据包的传输路径:

root@xroute:~# traceroute -s 44.32.191.17 44.32.191.1
traceroute to 44.32.191.1 (44.32.191.1), 30 hops max, 60 byte packets
 1  44.32.191.2 (44.32.191.2)  11.498 ms  11.462 ms  11.449 ms
 2  44.32.191.4 (44.32.191.4)  42.560 ms  42.543 ms  42.515 ms
 3  44.32.191.1 (44.32.191.1)  46.068 ms  46.053 ms  46.057 ms

路径很清晰:xroute(44.32.191.17)→ 44.32.191.2 → 44.32.191.4 → 目标端(44.32.191.1)

接下来,我用“定向ping”测试每个节点的最大支持MTU——发送不同大小的“不分片”数据包,看哪个节点会拒绝:

# 测试节点44.32.191.2:1420字节包(1392数据+28头部)能通
root@xroute:~# ping -I 44.32.191.17 -M do -s 1392 44.32.191.2
PING 44.32.191.2 (44.32.191.2) from 44.32.191.17 : 1392(1420) bytes of data.
1400 bytes from 44.32.191.2: icmp_seq=1 ttl=64 time=11.2 ms

# 测试节点44.32.191.4:1420字节包被拒,返回MTU=1413
root@xroute:~# ping -I 44.32.191.17 -M do -s 1392 44.32.191.4 
PING 44.32.191.4 (44.32.191.4) from 44.32.191.17 : 1392(1420) bytes of data.
From 44.32.191.2 icmp_seq=1 Frag needed and DF set (mtu = 1413)

# 测试节点44.32.191.4:1413字节包(1385数据+28头部)能通
root@xroute:~# ping -I 44.32.191.17 -M do -s 1385 44.32.191.4     
PING 44.32.191.4 (44.32.191.4) from 44.32.191.17 : 1385(1413) bytes of data.
1393 bytes from 44.32.191.4: icmp_seq=1 ttl=63 time=42.4 ms

真相终于浮出水面:节点44.32.191.2到44.32.191.4的链路MTU只有1413——这就是“一锅老鼠屎”!网关按1420拆包后,小包到了这里还是超MTU,被直接丢弃,而且没把“需要更小MTU”的消息回传给客户端,导致客户端一直发大红包,恶性循环。

五、根治:统一全链路MTU,一劳永逸

一开始我以为只能“妥协”——把网关的WireGuard MTU改成1413,适配这个瓶颈。但突然想到:这些节点都是我自己的!我能控制所有设备!

既然如此,就不用妥协了,直接“统一全链路MTU”——把所有节点的关键接口MTU都改成1420(WireGuard的常规最优值),彻底消除瓶颈。

步骤1:统一所有节点的MTU

登录44.32.191.2、44.32.191.4、44.32.191.1等节点,执行以下命令(以Ubuntu为例):

# 1. 临时调整接口MTU(替换eth0/eth1为实际接口名)
ip link set eth0 mtu 1420  # 连接上游的接口
ip link set eth1 mtu 1420  # 连接下游的接口

# 2. 永久生效(用netplan配置,重启不丢)
cat > /etc/netplan/01-mtu-config.yaml << EOF
network:
  ethernets:
    eth0:
      mtu: 1420
    eth1:
      mtu: 1420
  version: 2
EOF

# 3. 应用配置
netplan apply

# 4. 验证
ip link show eth0 | grep mtu  # 应该显示mtu 1420

步骤2:同步网关配置

网关(xroute)的WireGuard接口本来就是1420,不用改,但要配置“TCP MSS Clamping”——强制TCP数据包大小不超过MTU,避免个别应用不遵守PMTUd规则:

# 1. 删除旧的MSS规则(如果有的话)
iptables -t mangle -D FORWARD -i eth2 -o wg_40005 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1373 2>/dev/null

# 2. 添加新规则:MSS=1420-40(TCP+IP头部)=1380
iptables -t mangle -A FORWARD -i eth2 -o wg_40005 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1380
iptables -t mangle -A FORWARD -i wg_40005 -o eth2 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1380

# 3. 验证规则
iptables -t mangle -L FORWARD -v -n | grep TCPMSS

步骤3:测试效果

回到客户端,不用改任何配置,直接ping和curl测试:

# 1. ping 1500字节包(1472数据+28头部),能通了!
root@rrweb:~# ping -M do -s 1472 44.32.191.1
PING 44.32.191.1 (44.32.191.1) 1472(1500) bytes of data.
1480 bytes from 44.32.191.1: icmp_seq=1 ttl=62 time=46.1 ms

# 2. curl GitHub,秒通!
root@rrweb:~# curl -I https://github.com
HTTP/2 200 
server: GitHub.com
date: Wed, 10 Jul 2024 10:30:00 GMT
content-type: text/html; charset=utf-8

六、总结:MTU配置的“三大原则”

折腾了这么久,我也算摸清了MTU的脾气,总结出三个核心原则,帮大家避开类似的坑:

1. 优先“统一全链路”,拒绝“被动适配”

如果能控制路径中所有节点(比如自己的内网、专用隧道),一定要把关键接口的MTU统一成“标准值-协议开销”的最优解——比如WireGuard场景用1420(1500以太网MTU减去80字节封装开销),PPPoE场景用1492(1500减去8字节PPPoE头部)。
像我之前遇到的1413瓶颈,要是被动把网关MTU改成1413,后续新增节点又可能出现新瓶颈;而统一成1420后,不仅解决了当下问题,还为后续扩展扫清了障碍。

2. 客户端“即插即用”是底线,别让用户改配置

作为网管,最忌讳的就是让用户手动改MTU——普通用户分不清“WAN口”和“LAN口”,更别说记一串数字。通过网关配置“TCP MSS Clamping”(比如设置MSS=1380适配1420 MTU),能强制TCP数据包符合路径限制,客户端哪怕保持1500默认MTU也能正常用,真正实现“插上网线就走”。

3. 排查用“定向ping+traceroute”,别瞎猜

遇到MTU问题时,别上来就改配置,先按“两步法”定位:

  • traceroute理清数据包传输路径,知道数据要经过哪些节点;
  • ping -M do -s 数据大小 目标IP测试每个节点的最大支持MTU(比如从1472字节开始减,直到能ping通),精准找到“卡脖子”的节点。
    比瞎猜“是不是防火墙的问题”“是不是运营商限速”高效10倍。

七、后记:MTU这门“小事”,藏着网络的“大逻辑”

最开始我觉得MTU是个“边角料”配置,直到这次踩坑才发现:它藏着网络通信的底层逻辑——“数据传输不是‘越大越好’,而是‘刚刚好’”

就像现实里寄快递,你不会为了省运费硬把100斤货塞成一个包(容易破),也不会把10斤货拆成100个小盒子(浪费包装)。MTU的本质,就是在“避免拆分”和“减少开销”之间找平衡。

现在我的网络里,不管是客户端、网关还是中间节点,MTU都统一成1420,GitHub秒开,大文件传输也不卡顿——之前那粒“老鼠屎”(1413瓶颈)被清理后,整个“粥”(链路)终于恢复了该有的味道。

最后提醒大家:遇到网络卡顿、timeout时,不妨试试ping -M do -s 1472 目标IP,说不定MTU就是那个藏在背后的“捣蛋鬼”。

声明:Jack≠Hijack|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 一锅老鼠屎,坏了一粒粥---朕与MTU的爱恨情仇


先干再说,弄毁了推倒重做!