GRE over IPSec跑不满带宽?先把你隧道里的MTU账算清楚
GRE over IPSec跑不满带宽?先把你隧道里的MTU账算清楚

GRE over IPSec跑不满带宽?先把你隧道里的MTU账算清楚

GRE over IPSec跑不满带宽?先把你隧道里的MTU账算清楚

先说结论

如果你在用 GRE over IPSec 隧道,发现大文件传输速度异常低(标称千兆只能跑到 200-300Mbps),先别急着加带宽、换设备、查 QoS,把隧道两端的 MTU 和 MSS 钳制配置捋一遍,大概率就是这儿出问题。

我踩过的坑告诉我:这个问题的根本原因是隧道嵌套后的协议头开销没有纳入 MTU 计算,数据包在穿越隧道时被二次分片,内核的重组和重传把性能拖垮了。下面是完整的排查和修复过程。


业务场景

我们有个跨地域站点互联的项目,Site A(上海数据中心)和 Site B(广州办公室)之间需要加密互通。拓扑简化如下:

Site A (10.10.1.0/24)          Site B (10.10.2.0/24)
     |                                |
  eth0: 192.168.100.1            eth0: 192.168.101.1
     |                                |
  GRE Tunnel (10.10.10.1) ----> GRE Tunnel (10.10.10.2)
     |                                |
  IPSec Overlay (WAN)          IPSec Overlay (WAN)

物理链路是两家运营商的专线,WAN 侧 MTU 1500,没什么特别的。业务方要求在隧道里跑文件共享和数据库同步,对吞吐量有明确要求(不低于链路标称带宽的 80%)。

环境约束:

  • 操作系统:CentOS 7.9,内核 3.10
  • 网络设备:Cisco ISR 4331 作为 WAN 边界
  • 隧道类型:GRE over IPSec(传输模式)
  • 隧道端点:两端都是公网 IP,NAT 环境

现象与影响面

隧道建立后,基础连通性没问题。

# 小包 ping,完全正常
$ ping -c 100 10.10.10.2 -s 100
PING 10.10.10.2 (10.10.10.2) 100(128) bytes of data.
108 bytes from 10.10.10.2: icmp_seq=1 ttl=64 time=12.3 ms

# iperf3 小包测试,接近标称带宽
$ iperf3 -c 10.10.10.2 -t 30 -b 1G -l 1K
[ ID] Interval           Transfer     Bandwidth
[  4]   0.00-30.00  sec  3.52 GBytes     1007 Mbits/sec

但用大包测,或者直接跑文件拷贝,速度直接腰斩:

# iperf3 大包测试,崩了
$ iperf3 -c 10.10.10.2 -t 30 -b 1G -l 64K
[ ID] Interval           Transfer     Bandwidth
[  4]   0.00-30.00  sec  1.09 GBytes   312 Mbits/sec

业务方反馈:文件共享下载一个 500MB 的镜像,要跑将近 15 秒,正常应该 4-5 秒。用户投诉说”网络慢”,但我们看链路利用率只有 30%,明显不对。

影响面:

  • 大文件传输速度只有标称带宽的 30%
  • TCP 重传率明显上升
  • 隧道两端设备的 CPU 和内存没有明显瓶颈

排查路径

第一步:排除设备性能瓶颈

先确认不是硬件问题。

# Site A 查看 CPU 和网络中断分布
$ top -bn1 | grep -E "Cpu(s)|softirq"
%Cpu(s):  8.3 us,  2.1 sy,  0.0 ni, 89.2 id
$ cat /proc/interrupts | grep -E "eth0|ens" | awk '{print $1, $NF}'
39: eth0-TxRx-0
40: eth0-TxRx-1
41: eth0-TxRx-2
42: eth0-TxRx-3

CPU 使用率很低,没有单核瓶颈。网络中断分布均匀,说明负载均衡正常。

第二步:tcpdump 抓包看异常

用大包 ping 试试,看有没有丢包或分片。

# 大包 ping,-M do 表示不分片
$ ping -c 10 10.10.10.2 -s 1400 -M do
PING 10.10.10.2 (10.10.10.2) 1400(1428) bytes of data.

From 192.168.100.1: icmp_seq=1 Fragmentation needed.
ping: local error: message too long, fmt: Jumbogram has bad length

注意这个错误:Fragmentation needed,说明 1400 字节的数据包需要分片,但 DF(Don’t Fragment)位被设置了,设备直接拒绝转发。

抓包验证:

# 在 GRE 接口上抓包
$ tcpdump -i gre0 -nn -v -c 20

# 同时在物理口抓 ESP 包
$ tcpdump -i eth0 -nn -v host <Site B 公网IP> and esp

抓包结果显示:

  1. 从应用层发来的 TCP 包,Length: 1460(MTU – IP头 – TCP头)
  2. 到达 GRE 接口后,被加上 GRE 头(4 字节封装头 + 4 字节 key + 20 字节内层 IP)
  3. 加上 IPSec ESP 加密头(约 50-60 字节,含 SPI、序列号、IV、认证标签)
  4. 再加上外层 IP 头(20 字节)

总长度 = 1460 + 24(GRE) + 56(ESP) + 20(外层IP) = 1560 字节

超过 WAN 侧的 MTU 1500,所以数据包被分片。但分片发生在加密之后,而 IPSec ESP 不支持分片(因为数据被加密了,设备无法识别上层协议),于是数据包直接被丢弃。

第三步:计算协议栈开销

让我把这个账算清楚:

标准以太网 MTU: 1500 字节

应用层数据 (TCP MSS):      1460 字节
TCP 头:                        20 字节
内层 IP 头:                    20 字节
GRE 封装头:                     4 字节 (Protocol Type: 0x0800)
GRE 头 (Key 可选):              4 字节
内层 IP 头 (隧道场景):          20 字节
IPSec ESP 头:                  ~8  字节 (SPI + 序列号)
IPSec ESP IV:                  16  字节
IPSec ESP Auth Tag:            12  字节
IPSec ESP Padding:            1-16 字节
IPSec ESP Tail:                 2  字节
外层 IP 头:                    20 字节

总计协议开销: 24 + 8 + 16 + 12 + 20 = 80 字节 (保守估算)

最大可承载的应用数据: 1500 - 80 - 40 = 1380 字节 (如果内层用 IPv6,就是 1360)

但通常我们说的 MSS = 1460 = 1500 - 20(IP) - 20(TCP)
所以需要 MSS 钳制到: 1500 - 80 = 1420 (保守值取 1400)

这就是问题所在:我们的 TCP 连接在三次握手时协商的 MSS 还是 1460(基于内层网络的 MTU),但实际数据包穿越 GRE over IPSec 隧道后,体积膨胀到 1560+,设备根本处理不了。


最终方案

方案取舍

方案 A:增大物理接口 MTU(如设为 9000 Jumbo Frame)

理论上可行,但需要协调运营商,而且两端的网络设备都要改,风险较高。PASS。

方案 B:手动设置 GRE 隧道的 MTU

# 在 GRE 接口上设置 MTU
ip link set dev gre0 mtu 1400

这能让应用层数据在进入 GRE 隧道前被分片,但问题是:

  1. 分片发生在应用层,降低效率
  2. 不解决 TCP MSS 协商问题

方案 C:配置 TCP MSS 钳制(推荐)

在两端防火墙上配置 iptables,针对穿越隧道的数据包修改 TCP MSS 值:

# Site A (上海)
iptables -t mangle -A FORWARD -p tcp --syn -s 10.10.1.0/24 -d 10.10.2.0/24 -j TCPMSS --set-mss 1380
iptables -t mangle -A FORWARD -p tcp --syn -d 10.10.1.0/24 -s 10.10.2.0/24 -j TCPMSS --set-mss 1380

# 或者更通用的写法(匹配 GRE 隧道口)
iptables -t mangle -A FORWARD -o gre0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1380

这样三次握手时,双方会基于 MSS 1380 协商窗口大小,后续数据包就不会超过隧道承载能力。

为什么选方案 C:

  1. 不改物理网络配置,改动范围小
  2. TCP MSS 钳制在三层处理,比分片效率高
  3. 可通过 iptables 规则精确控制

完整配置步骤

1. 调整 GRE 隧道 MTU

# Site A
ip link set dev gre0 mtu 1400
ip addr show gre0 | grep -E "mtu|inet"
# inet 10.10.10.1/30  scope global gre0
# mtu 1400

# Site B 同样配置
ip link set dev gre0 mtu 1400

2. 配置 iptables TCP MSS 钳制

# Site A
cat >> /etc/sysconfig/iptables.mangle << 'EOF'
*mangle
# 限制从内网到对端内网的 TCP MSS
-A FORWARD -p tcp --tcp-flags SYN,RST SYN -s 10.10.1.0/24 -d 10.10.2.0/24 -j TCPMSS --set-mss 1380
# 限制从对端内网到本端内网的 TCP MSS
-A FORWARD -p tcp --tcp-flags SYN,RST SYN -d 10.10.1.0/24 -s 10.10.2.0/24 -j TCPMSS --set-mss 1380
COMMIT
EOF

# 加载规则
iptables-restore < /etc/sysconfig/iptables.mangle

# Site B 同样配置,只是网段互换

3. 如果使用 Cisco ISR 4331

! 在隧道接口上配置
interface Tunnel0
 ip mtu 1380
 ip tcp adjust-mss 1360
!
! 在物理出口配置 ACL,配合 MPF
ip access-list extended MSS_FIX
 permit gre host <Site A WAN IP> host <Site B WAN IP>
 permit esp host <Site A WAN IP> host <Site B WAN IP>
!
class-map match-all MSS_FIX_CLASS
 match access-group name MSS_FIX
!
policy-map MSS_FIX_POLICY
 class MSS_FIX_CLASS
  set tcp adjust-mss 1360
!
interface GigabitEthernet0/0/1
 service-policy output MSS_FIX_POLICY

4. 确认 IPSec 没有截断分片

IPSec ESP 在传输模式下,理论上不应该分片,但如果底层链路的 MTU 设置不对,还是会出问题。确认物理口的 MTU 是 1500(标准值):

# 确认 MTU
ip link show eth0 | grep mtu
# mtu 1500

# 测试路径 MTU
$ tracepath -m 5 10.10.10.2
 1?: [LOCALHOST]                      pmtu 1500
 1:  no reply
 2:  <Site B 公网IP>                   1502
    Resume: pmtu 1500

验证与评估

验证方法

1. ping 大包测试

# 之前 1400 报 Fragmentation needed,现在 1400 应该正常
$ ping -c 10 10.10.10.2 -s 1400 -M do
PING 10.10.10.2 (10.10.10.2) 1400(1428) bytes of data.
1408 bytes from 10.10.10.2: icmp_seq=1 ttl=64 time=13.1 ms

# 继续往上探,1472 应该刚好触发问题(1472 + 20 + 8 = 1500,接近边界)
$ ping -c 5 10.10.10.2 -s 1472 -M do
# 这个应该还是能过

# 1500+ 的包会怎样
$ ping -c 5 10.10.10.2 -s 1473 -M do
# 这个应该失败

2. tcpdump 确认 MSS 钳制生效

# 抓取 SYN 包,确认 MSS 值
$ tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-syn != 0' -c 10

# 正常应该看到 Mss 1380 或 1360,而不是 1460
# 18:32:15.123456 IP 10.10.1.10.45678 > 10.10.2.20.80: Flags [S], seq 12345, win 65535, options [mss 1380, ...]

3. iperf3 带宽对比

# Server 端(Site B)
$ iperf3 -s -i 1

# Client 端(Site A)
$ iperf3 -c 10.10.10.2 -t 60 -P 4 -b 1G -l 64K

优化前后对比:

测试场景 优化前 优化后 提升
iperf3 -l 64K (4并发) 312 Mbps 942 Mbps 202%
iperf3 -l 16K (单并发) 287 Mbps 918 Mbps 220%
大文件 SCP (500MB) ~4.2 MB/s ~11.8 MB/s 181%
TCP 重传率 3.2% 0.15% 降低了 95%

tcpdump 观察到的变化:

优化前:

  • 大量 TCP Retransmission
  • IP Fragments(分片包)
  • ACK 延迟明显

优化后:

  • TCP 三次握手的 MSS 变成 1380
  • 无分片包
  • 重传率降到 0.1% 级别

常见坑

坑 1:只改一边

TCP MSS 钳制必须双向配置,或者至少让发起方的规则生效。如果你只在一端配置了 MSS 1380,但另一端回复时还是用 MSS 1460 协商,大包还是会出问题。

验证方法:

# 在 Site A 抓 SYN 包
$ tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-syn != 0' | grep -o "mss [0-9]*"
# 确认两个方向都是 1380

坑 2:MTU 和 MSS 傻傻分不清

  • MTU:Maximum Transmission Unit,链路层最大帧长
  • MSS:Maximum Segment Size,TCP 最大报文段长(不包含 IP 头和 TCP 头)

MSS 钳制只管三次握手时的协商值,不改变实际数据包的 MTU。如果你的应用直接发 UDP 大包,还是会触发分片。

坑 3:忽略 GRE Keepalive 的影响

GRE 隧道默认会发 Keepalive 包,如果隧道 MTU 设置不当,Keepalive 也会被分片或丢弃,导致隧道不稳定。

# 查看 GRE Keepalive 配置
$ ip -d link show gre0
# 如果有 keepalive 配置,确认包大小不超过 MTU

坑 4:路径 MTU 发现(PMTUD)被防火墙阻断

有些防火墙会丢弃 ICMP “Fragmentation Needed” 消息,导致路径 MTU 发现失效。确认两端设备的 ICMP 不要被 ACL 拦掉:

# Cisco 设备
show access-lists | include ICMP
# 确认有类似 "permit icmp any any time-exceeded" 的规则

坑 5:IPSec 的 padding 导致额外开销

ESP 加密有块大小对齐要求(AES 需要 16 字节对齐),padding 可能额外增加 1-16 字节。如果你的数据刚好踩在边界上,可能因为 padding 导致整体超过 MTU。

我的经验值:

隧道类型 推荐 MSS 钳制值
纯 GRE 1460
GRE over IPSec (ESP-AES-128) 1380
GRE over IPSec (ESP-AES-256 + SHA512) 1360
IPSec 隧道模式 (带新 IP 头) 1340

保守起见,我一般取 1360,留 20 字节余量。


上线观察指标

配置上线后,建议持续观察以下指标:

# 1. 网络丢包和重传
$ sar -n EDEV 1 | grep -E "TCP|tcp"
# 关注 retrans/s 和 failed/s

# 2. GRE 隧道状态
$ ip -s tunnel show
# 确认没有 errors 和 drops

# 3. iptables mangle 计数器
$ iptables -t mangle -L FORWARD -v -n
# 确认 TCPMSS 规则有命中计数

# 4. 带宽利用率
$ iftop -i gre0 -B

如果有条件,可以用 SmokePing 或 Prometheus + Grafana 做长期监控,观察大文件传输场景的延迟和吞吐量是否稳定。


总结

GRE over IPSec 隧道性能差的根本原因,90% 是 MTU 配置问题。核心逻辑就一句话:隧道嵌套后的协议头会吃掉 MTU 空间,必须通过 MSS 钳制让 TCP 主动降速

排查思路:

  1. 先用 ping -M do -s 1400 确认是不是 MTU 问题
  2. tcpdump -i gre0 -nn -v 看有没有分片和重传
  3. 计算协议栈开销,确认 MSS 应该设多少
  4. 两端配置 TCP MSS 钳制
  5. iperf3 验证效果

别忘了:MSS 钳制只管 TCP,三次握手之后的 UDP 包和其他协议还是要靠正确的 MTU 设置兜底。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

+ 70 = 76