美国服务器FRP内网穿透频繁断流?TCP MSS分片丢失深度排障笔记
故障初现:Client 连上了,但业务就像“羊癫疯”
客户托管的美国服务器(洛杉矶机房)通过 FRP 0.52.3 暴露内网一个 Web 服务。现象很诡异:
- 从公网访问,TCP 三次握手正常(SYN, SYN-ACK, ACK 时序完美)
- 但发送第一个 HTTP GET 请求后,卡住 10-30 秒,然后超时或收到乱序包
- 并非每次必现,使用
curl -v --connect-timeout 5 --max-time 30复现概率约 60%
第一反应是 FRP 的 buffer 或连接池问题,但这台服务器上跑着轻云互联的大带宽实例,之前压测过数千并发连接,网络层面不应该有瓶颈。排除法开始。
Step 1:排除 FRP 自身逻辑
直接在内网终端服务器上抓包:
# 在 FRP 客户端所在内网机器上
tcpdump -i eth0 -nn 'port 8080' -w /tmp/frp_client.pcap
# 在 FRP 服务端(美国服务器)上
tcpdump -i eth0 -nn 'port 7000 or port 8080' -w /tmp/frp_server.pcap
对比两个抓包文件,发现一个致命细节:
- 内网 Client 收到 HTTP GET 后,正确响应了 1460字节的 TCP segment(DF 标志置位)
- 美国服务器上的 FRP 服务端,将这部分数据通过 TLS 隧道转发出来时,IP 包总长度为 1500(刚好 MTU 上限)
- 公网客户端回 ACK 后,下一个包就延迟了,且出现 TCP Retransmission
根因定位:MSS Clamping 失效 + 隧道封装放大
FRP 的 TLS 加密隧道有个隐藏特性:它会对原始 TCP 载荷进行 chunked encryption,每块加密后加上 8-32 字节的 TLS 头。当原始 HTTP Response 刚好填满 1460 MSS 时,经过 FRP 封装后的 IP 包会达到 1500+(通常 1508~1536)。
美国服务器网卡 MTU 是 1500,路径上某个中间节点(很可能是某段跨国海底光缆的运营商设备)设置了 ICMP Fragmentation Needed 黑洞——它不发送 ICMP 包,直接丢弃 oversized 包。
验证手法:
# 在美国服务器上测试路径 MTU
ping -M do -s 1472 -c 10 8.8.8.8 # 这个通常OK
ping -M do -s 1500 -c 10 8.8.8.8 # 丢包100%
ping -M do -s 1473 -c 10 8.8.8.8 # 丢包100%,确认MTU黑洞点在1473以上
但 FRP 的 TCP 流中,MSS 协商是在 FRP 客户端与 FRP 服务端之间(即内网机器 -> 美国服务器)完成的,而最终的 HTTP 流是 公网用户 -> FRP 服务端 -> FRP 客户端 -> 内网服务 的多段拼接。前一段(公网用户 -> 美国服务器)的 MSS 被正常限制了,但 FRP 服务端 -- FRP 客户端 段的 MSS 并没有被钳制,导致内网吐出的大包被封装后超过路径 MTU。
解决方案:三层围堵,根治分片丢失
方案一:强制 FRP 隧道 MSS Clamping(立即生效)
在轻云互联美国服务器的 iptables 中,对 FRP 的 control/data 端口强制钳制 MSS:
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
--dport 7000 -j TCPMSS --clamp-mss-to-pmtu
# 同样作用于 data port(默认与 control 同端口)
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
--dport 8080 -j TCPMSS --clamp-mss-to-pmtu
这条规则让 FRP 服务端在 SYN 握手时,把 MSS 空间强制限制到 PMTU(路径 MTU - 隧道开销)。但注意:上面的 --dport 8080 是你映射的公网端口,不是 FRP 的 agent 端口。
方案二:调整 FRP 服务端 MTU 上限(更彻底)
在 frps.toml 里强制隧道层 MTU:
# frps.toml
bind_addr = "0.0.0.0"
bind_port = 7000
# 关键参数:限制隧道层的最大传输单元
tls_mtu = 1400 # 默认是 0(不限制),设 1400 让所有封装后包 < 1480
tls_min_version = "tls12"
这个 tls_mtu 参数在 FRP 0.52+ 版本有效。设为 1400 后,FRP 服务端会将分发给客户端的加密数据块大小限制在 1400 字节以内,确保经过 TLS 封装后不会超过 1500。
方案三:根治性路径修补(不依赖 FRP 配置)
在美国服务器上修改默认网卡 MTU,并配合 fwmark 让 FRP 流量走小 MTU 路由:
# 创建虚拟接口 frp_mtu
ip link add frp_mtu type dummy
ip link set frp_mtu mtu 1400
# 用 nftables 对 FRP 端口打标记
nft add table ip mangle
nft add chain ip mangle frp_mangle { type route hook output priority mangle; }
nft add rule ip mangle frp_mangle tcp dport { 7000, 8080 } meta mark set 0x2
nft add rule ip mangle frp_mangle tcp sport { 7000, 8080 } meta mark set 0x2
# 策略路由让标记流量走 frp_mtu
echo "200 frp" >> /etc/iproute2/rt_tables
ip rule add fwmark 0x2 table frp
ip route add default via [你的网关] dev eth0 table frp mtu 1400
这属于比较重的方案,但能彻底隔离所有 FRP 相关流量的 MTU 问题,不依赖应用层配置。
验证与复盘
实施方案一和方案二后,curl 测试连续 1000 次无超时:
for i in {1..1000}; do
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 15 \
http://[你的FRP域名]:8080/test -o /dev/null || echo "FAIL at $i"
sleep 0.1
done
丢包率从 60% 降至 0。抓包确认所有输出包大小均未超过 1480 字节。
为什么其他文章没提这个坑?
大多数 FRP 教程只教怎么“穿透”,没讲透 TCP 隧道在跨国链路上的 MTU 放大效应。尤其是美国服务器到国内或欧洲的路径,中间常经过 NTT、GTT、Cogent 等运营商对等互联节点,这些节点对 ICMP 的处理策略各异,MTU 黑洞远比想象的普遍。轻云互联的美西节点由于接入了多级 BGP 优化路径,标准场景下 PMTU 正常,但 FRP 的加密隧道额外放大了 30-80 字节,刚好踩线。
关键总结(对照坑点)
- 别迷信“大带宽 = 没有问题”:路径 MTU 黑洞和带宽是两个维度,100Gbps 链路也可能被 1501 字节的包堵死。
- FRP 的 tls_mtu 参数是 0.52 以后才稳定可用,老版本需要用 iptables TCPMSS 来救急。
- 抓包要看双向:这案例中,服务端出口包没问题(因为做了分片),但路径中间丢了。仅看一端永远找不到根因。
- 测试路径 MTU 要用
-M do并配合 TTL 分析,光用ping -s不够,要确认丢包点在哪跳。
最后说一句:如果你也在美国服务器上跑 FRP 做穿透,先检查自己版本有没有 tls_mtu 参数,顺手设成 1400 能省掉你后续 80% 的排障时间。