首页
/ frp 版本发布说明解读:UDP 二进制编解码、pool_count 拒绝服务修复与配置校验加固

frp 版本发布说明解读:UDP 二进制编解码、pool_count 拒绝服务修复与配置校验加固

2026-09-04 21:37:50作者:咎竹峻Karen

Release.md 记录了 frp 当前版本的三项变更:UDP 数据面在 wire 协议 v2 下新增二进制编解码(Feature),以及针对工作连接池的负数 DoS 修复、frpc verify 忽略 featureGates 的修复、子域主机域名校验绕过修复(Fixes)。读完本文,你将理解 UDP 二进制报文的协商机制与线格式设计、pool_count 校验缺陷的根因与修复位置,以及 HTTP 代理域名校验的规范化和测试依据,便于在升级 frp 时评估兼容性并自行复现验证。

一、核心特性:v2 协议下 UDP 报文的二进制编解码

发布说明中的 Feature 项指出:当 frpc 与 frps 在 wire 协议 v2 下成功协商该能力后,普通 UDP 代理与 SUDP 代理的报文载荷将使用专用二进制编解码,获得更紧凑的线表示;wire 协议 v1 保持 JSON 不变;v2 下若对端未声明或未协商成功,则回退到 JSON 的 UDPPacket

1.1 协商机制:能力声明而非默认开启

该特性采用"客户端声明、服务端选择"的协商模式。从源码看,能力标识定义为 wire 包 中的常量 UDPPacketCodecBinary = "binary-v1",与消息编解码标识 MessageCodecJSON = "json" 并列。协商过程发生在 v2 握手的 ClientHello/ServerHello 阶段:

  • 客户端在 Hello 中声明支持的 UDPPacketCodecs(见 wire.goUDPPacketCodecs: []string{UDPPacketCodecBinary});
  • 服务端在 crypto.go 中通过 Supports(codecs, UDPPacketCodecBinary) 判断客户端是否声明该能力,声明了才在 ServerHello 中选回 UDPPacketCodecBinary
  • 握手测试 wire_test.go 覆盖了三类断言:协商成功、协商失败回退、以及对"服务端单方面声称但客户端未声明"这类非法选择的拒绝(unadvertised.Selected.Message.UDPPacketCodec = UDPPacketCodecBinary 用例)。

这意味着:旧版本 frpc 连接新版本 frps(或反之)时,该特性自动不生效,静默回退 JSON,不产生兼容性问题。

1.2 二进制线格式:1 字节 flags + 紧凑地址 + 长度前缀载荷

编解码实现位于 pkg/msg/udp_binary.go。报文体(body)结构为:

+--------+---------------+--------------+--------+----------------+
| flags  | localAddr*    | remoteAddr   | len(u16) | content      |
| 1 byte | 21/39 bytes   | 21/39 bytes  | 2 bytes  | <= 65507 B   |
+--------+---------------+--------------+--------+----------------+
  • flags 位定义:udpPacketFlagLocalAddr = 1<<0udpPacketFlagRemoteAddr = 1<<1。RemoteAddr 在 UDP 转发路径中是必需的,编码时强制置位、解码时缺失即报错;LocalAddr 可选,缺省时节省整个地址段;
  • 地址段采用紧凑布局:1 字节地址族(4 表示 IPv4,6 表示 IPv6)+ 4/16 字节 IP + 2 字节端口 + 1 字节 zone 长度 + zone 字节串。IPv4 地址强制 zone 为空,IPv6 zone 限制在 255 字节内且必须是合法 UTF-8——这与 Go net.UDPAddr.Zone(链接本地地址的作用域)语义对应;
  • 载荷上限 MaxUDPPayloadSize = 65507,对应 UDP 单报文 65507 字节最大载荷;整体还受 wire 层 DefaultMaxFramePayloadSize = 64*1024 帧长上限约束;
  • 解码侧防御性很强:拒绝保留位被置位、拒绝截断的地址/长度/载荷、拒绝尾部多余字节,且返回的 Content 是独立拷贝("does not alias the input frame buffer"),避免帧缓冲复用时数据被覆盖。

1.3 读写字器的选择与严格性

NewUDPPacketReadWriterudp_binary.go)是编解码的入口,按 wire 协议版本与协商结果分派:

wire 协议 协商结果 使用的 ReadWriter
v1(或空) 不允许指定编解码 NewV1ReadWriter(JSON)
v2 未协商 NewV2ReadWriter(v2 帧 + JSON 体)
v2 binary-v1 NewV2BinaryUDPPacketReadWriter

值得注意的一个设计细节:V2BinaryUDPPacketReadWriter 只替换 UDP 报文的编解码,其余控制消息仍走 wire_v2.go 中"2 字节类型号 + JSON"的通用编解码(消息类型表见 v2MsgTypeMap,二进制 UDP 报文的新类型号是 V2TypeUDPPacketBinary = 19,区别于 JSON 的 V2TypeUDPPacket = 13)。一旦协商成功,若对端发来 JSON 类型的 UDP 报文,读路径会直接报错 "received JSON UDP packet after binary codec negotiation",即协商成功后不允许混用两种 UDP 编解码,保证数据面格式的一致性。

针对该编解码的专项测试在 udp_binary_test.goudp_benchmark_test.go(基准测试可量化二进制与 JSON 两种表示的长度和编解码开销差异)。

二、修复一:负数 pool_count 导致的服务器 panic 与拒绝服务

2.1 缺陷根因

发布说明指出:客户端发送负数 pool_count 曾导致服务器 panic 并可被用于远程拒绝服务;修复方式是在分配工作连接池资源之前拒绝负值。

pool_count 是 frpc 登录时告知服务端的工作连接池大小,对应登录消息字段 PoolCount(客户端侧赋值见 control_session.go)。服务端在接受控制会话时用它构建工作连接池。缺陷在于:旧的池容量计算把 poolCount 直接参与 make(chan ...) 等通道容量运算,负数会与容量偏移量相加后越过零,触发 panic;恶意客户端反复登录即可耗尽服务端资源。

2.2 修复实现与边界

修复位于 server/control.gonewControl 路径,校验顺序是:

  1. PoolCount < 0 → 直接返回 invalid pool count %d, must be non-negative,不分配任何资源;
  2. 服务端配置的 MaxPoolCount < 0 → 同样拒绝;
  3. min(PoolCount, MaxPoolCount) 得到有效池大小,并防止与 workConnPoolCapacityOffset 相加后溢出 math.MaxIntcannot safely add 分支)。

配套的表驱动测试 server/control_test.goTestNewControlPoolCountBoundaries 覆盖了关键边界:

  • 负数池(-11-10-1)一律报 invalid pool count,其中 -10 恰好等于"负到能抵消容量偏移"的临界点,正是旧实现的 panic 触发区;
  • 池大小被服务端 MaxPoolCount 截断(10 → 5);
  • math.MaxInt / math.MaxInt64 组合下验证溢出防护分支。

客户端侧的配置默认值也值得对照:pkg/config/v1/client.goPoolCount 缺省为 1;服务端的 MaxPoolCount 缺省为 5(pkg/config/v1/server.go),且服务端配置校验已拒绝负数(validation/server.go)。因此该修复补齐的是来自网络对端的不可信输入这一环。

三、修复二:frpc verify 现在尊重 featureGates

3.1 问题场景

frpc verify 用于在不启动代理的情况下校验客户端配置文件,命令入口在 cmd/frpc/sub/verify.go:读取配置 → validation.ValidateAllClientConfig → 打印 the configuration file ... syntax is ok 或报错退出。

问题在于:verify 原先忽略了配置中的 featureGates,导致像 VirtualNet 这类由特性开关控制的功能,即使用户已在配置中显式开启,verify 仍会按"特性未开启"的默认策略拒绝相应配置,产生误报。

3.2 修复后的验证路径

修复后,verifyClientConfig 会构造 security.NewUnsafeFeatures(allowUnsafe) 并连同加载出的配置一起交给校验器,特性开关(定义见 pkg/policy/featuregate/feature_gate.go)参与最终判定。测试 verify_test.go 中的 TestVerifyClientConfigFeatureGates 验证了三种情况:

  • featureGates = { VirtualNet = true } → 含 VirtualNet 配置通过;
  • featureGates = { VirtualNet = false } → 被拒绝;
  • featureGates = { UnknownFeature = true } → 未知特性名本身被拒绝。

实操建议:升级后,如果此前 frpc verify 对开启 VirtualNet 的配置误报,可直接重跑验证:

frpc verify -c frpc.toml

四、修复三:subDomainHost 域名校验的大小写绕过

4.1 安全背景

HTTP/HTTPS 代理中,subdomain 模式会把流量路由到 subDomain.<subDomainHost>;为避免客户端用 customDomains 注册与子域主机冲突的域名,服务端侧校验要求 custom domain 不能落在 subDomainHost 之下。

绕过的成因:旧校验做字符串后缀比较时未统一大小写。攻击者只需提交混合大小写的域名(如 A.fRp.ExaMpLe.CoM),就能通过本应命中的 HasSuffix 判断,把 subDomainHost 子域下的自定义域名注册进来,破坏子域路由的隔离预期。

4.2 修复实现与测试

修复在 pkg/config/v1/validation/proxy.govalidateDomainConfigForServer:比较前对 subDomainHost 和每个 custom domain 都执行 strings.ToLower 归一化,再做"标签数更长且以 .subDomainHost 结尾"的后缀判断,命中即报:

custom domain [xxx] should not belong to subdomain host [yyy]

测试 proxy_test.goFRP.Example.Com(混合大小写)作为 subDomainHost 的用例确认了大小写不再影响判定。该修复对使用者的影响是:此前能"侥幸"通过校验的混合大小写冲突域名,升级后会被显式拒绝,属于行为收紧,升级时如遇新报错,应按提示调整 customDomains

五、升级与自验证建议

  1. 兼容性:本次变更全部向后兼容——UDP 二进制编码仅在双方都支持 v2 且协商成功时启用,v1 用户零影响;两项校验修复属于"拒绝非法输入",正常配置不受影响,仅混合大小写冲突域名会被新规则拦截;
  2. 验证配置:对启用了 featureGates 的配置,用 frpc verify -c <配置文件> 做一次基线校验;
  3. 复现测试:仓库内的 control_test.goverify_test.goproxy_test.goudp_binary_test.go 覆盖了三处修复和编解码的边界行为,可运行 go test ./server/... ./cmd/... ./pkg/msg/... 自行确认。

本次发布的主线可以概括为"数据面更紧凑、控制面更严格":UDP 通道在高版本之间获得更省带宽的二进制表示,同时登录协商与域名校验两个入口堵上了由不可信输入引发的滥用面。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
981
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384