VPN 隧道把你的包装进另一个包里。多出来的那几十个字节,会把 MTU、分片、丢包率和 TCP 的 MSS 一连串地牵动。这一篇在 Docker 的 Linux 虚拟机里搭了真的 GRE、VXLAN、WireGuard 和 IPsec ESP 隧道,在外层网卡上抓包,把每个包多多少字节、MTU 配错会怎样、丢包怎么被放大、TCP 效率差多少,逐项量出来。
内核:7.0.14-linuxkit;ta 的 eth0 MTU 1500
隧道 内层 84 内层 85 内层 100 内层 528 内层 1028 内层 1229
GRE → 108(+24) → 109(+24) → 124(+24) → 552(+24) → 1052(+24) → 1253(+24)
VXLAN → 134(+50) → 135(+50) → 150(+50) → 578(+50) → 1078(+50) → 1279(+50)
WireGuard → 156(+72) → 156(+71) → 172(+72) → 588(+60) → 1100(+72) → 1292(+63)
IPsec ESP(隧道模式,AES-GCM-128) → 140(+56) → 140(+55) → 156(+56) → 584(+56) → 1084(+56) → 1284(+55)
GRE 每个包恒定 +24(外层 IP 20 + GRE 头 4);VXLAN 恒定 +50(外层 IP 20 + UDP 8 + VXLAN 8 + 内层以太网头 14);WireGuard 额外字节随内层大小变化,范围 +60~+72(内层先补零到 16 的倍数);ESP 同样不是常数,范围 +55~+56(填充到 4 字节边界,加 ESP 尾 2 字节)
内层大小为 16 的倍数时,WireGuard 刚好 +60(外层 IP 20 + UDP 8 + 头 16 + 认证标签 16);不是 16 的倍数的内层包,补到下一个 16 的倍数。IPv6 作外层则每个包多 20 字节(外层 IPv6 头 40,不是 20)——这一点没有测,是计算GRE 恒定 +24,VXLAN 恒定 +50(含内层以太网头),WireGuard +60~+72、IPsec ESP +55~+56——后两者要把内层包补齐到 16 或 4 字节的倍数,所以随内层大小变化。内层 MTU = 链路 MTU − 额外字节。
g0 默认 MTU 1476 v0 默认 MTU 1500 w0 默认 MTU 1420 v1(VXLAN,指定了底层网卡 dev eth0)MTU 1450;v0(没指定 dev)MTU 1500——后者内核不知道底层是哪块网卡,没有自动减 50 字节,要自己调 IPsec ESP 在 Linux 里不是一个接口,没有接口 MTU;路径 MTU 由路由和 xfrm 状态推算
内核给 GRE 自动设 1476、WireGuard 1420;VXLAN 只有指定了底层网卡才会自动减 50 字节。隧道接口的 MTU 要和外层链路配套,否则大包会被分片或被丢。
GRE 接口 MTU 1476,内层 1476 字节 DF=是:收到 2/2 外层包 [(1500, 0, 'DF', '×2')]
GRE 接口 MTU 1476,内层 1477 字节 DF=是:收到 0/2(message too long, mtu=1476) 外层包 无
GRE 接口 MTU 1476,内层 1477 字节 DF=否:收到 2/2 外层包 [(1500, 0, 'DF'), (45, 0, 'DF'), (1500, 0, 'DF'), (45, 0, 'DF')]
WireGuard 接口 MTU 1420,内层 1420 字节 DF=是:收到 2/2 外层包 [(1480, 0, 'none', '×2')]
WireGuard 接口 MTU 1420,内层 1421 字节 DF=是:收到 0/2(message too long, mtu=1420) 外层包 无
VXLAN 接口 MTU 1500,内层 1500 字节 DF=是:收到 1/2(message too long, mtu=1450) 外层包 [(1500, 0, '+'), (70, 1480, 'none')]
10.2.0.2 dev v0 src 10.2.0.1 uid 0 cache expires 597sec mtu 1450 ← 发过一个超大包后,内核给这个目的地记下了更小的路径 MTU
VXLAN 接口 MTU 1450,内层 1450 字节 DF=是:收到 2/2 外层包 [(1500, 0, 'none', '×2')]
VXLAN 接口 MTU 1450,内层 1451 字节 DF=是:收到 0/2(message too long, mtu=1450) 外层包 无
VXLAN 接口 MTU 1450,内层 1451 字节 DF=否:收到 2/2 外层包 [(1494, 0, 'none'), (77, 0, 'none'), (1494, 0, 'none'), (77, 0, 'none')]
GRE 接口 MTU 1500,内层 1500 字节 DF=是:收到 0/2(Frag needed and DF set (mtu = 1476)) 外层包 无
GRE 接口 MTU 1500,内层 1500 字节 DF=否:收到 1/2 外层包 [(1500, 0, 'DF'), (68, 0, 'DF')]内层包带 DF 就在本地被拒绝(message too long);不带 DF 就先在内层分片、各片再封装。外层分片只在隧道接口 MTU 配得比链路允许的大时出现,而内核会在发出第一个超大包后学到更小的路径 MTU。
内层 IP 包大小 外层 UDP 载荷 外层 IP 包大小 在 en0 上发出的 IP 包(分片) 有无分片
1280 1312 1340 [1340] 无
1400 1432 1460 [1460] 无
1420 1452 1480 [1480] 无
1440 1472 1500 [1500] 无
1441 1473 1501 [1500, 21] → 分成 2 片(偏移 [0, 1480]) 有分片
1480 1512 1540 [1500, 60] → 分成 2 片(偏移 [0, 1480]) 有分片
1500 1532 1560 [1500, 80] → 分成 2 片(偏移 [0, 1480]) 有分片
外层 IP 包不能超过 en0 的 MTU 1500:内层 IP 包 ≤ 1500 − 20 − 8 − 32 = 1440 字节时刚好一个包;再大一个字节,外层就被分成两片——分片后任何一片丢失,整个数据报都丢自己写的 IP-in-UDP 封装:内层 ≤ 1440 字节时外层刚好 1500 字节一个包,1441 字节起外层被分成两片。
基线:内层 84 字节 发 15000 个,丢 149 个 = 0.99%;按公式 1 − 0.99^1 = 1.00%
内层 1450 字节(1 个外层包) 发 15000 个,丢 144 个 = 0.96%;按公式 1 − 0.99^1 = 1.00%
内层 1500 字节(一个请求变成 2 个外层包) 发 15000 个,丢 260 个 = 1.73%;按公式 1 − 0.99^2 = 1.99%一个内层包变成两个外层包,任何一个丢了都算丢。1% 丢包下实测丢失率约翻倍(0.99% → 1.73%,几轮在 1.7%~2.2% 之间)。
不用隧道,直连 MSS 1448→1448:出站 6916 个帧,共 10456464 字节(比 10,000,000 多 4.6%),最大帧 1514 字节,其中 IP 分片 0 个
GRE(接口 MTU 1476) MSS 1424→1424:出站 7039 个帧,共 10633518 字节(比 10,000,000 多 6.3%),最大帧 1514 字节,其中 IP 分片 0 个
WireGuard(接口 MTU 1420) MSS 1368→1368:出站 7315 个帧,共 10921806 字节(比 10,000,000 多 9.2%),最大帧 1494 字节,其中 IP 分片 0 个
VXLAN(接口 MTU 1450) MSS 1398→1398:出站 7158 个帧,共 10830336 字节(比 10,000,000 多 8.3%),最大帧 1514 字节,其中 IP 分片 0 个
VXLAN(MTU 错配:接口 MTU 仍是 1500) MSS 1448→1398:出站 7176 个帧,共 10832014 字节(比 10,000,000 多 8.3%),最大帧 1514 字节,其中 IP 分片 10 个
(MSS 栏是 “连接建立时 → 传完 10 MB 后” 的值;抓的是外层:隧道里的 TCP 段加上隧道头;“线上总字节”不含以太网帧间隙与 CRC;只算 ta→tb 的方向,不含 tb 回的 ACK)隧道让 MSS 变小、每段多出头部:10 MB 传输的额外开销从 4.6%(不用隧道)升到 GRE 6.3%、VXLAN 8.3%、WireGuard 9.2%。MTU 错配时 TCP 带着 DF,开头几个包付代价后 MSS 降下来,不会一直分片。
gif0 MTU 1280 stf0 MTU 1280 utun0 MTU 1500 utun1 MTU 1380 utun2 MTU 2000 utun3 MTU 1000 共 6 个隧道类接口,MTU 各不相同(['1000', '1280', '1380', '1500', '2000']):实际路径 MTU 取决于数据走的是哪个接口
6 个隧道类接口 MTU 各不相同,路径 MTU 取决于路由选中的接口。