加密之后还要管内存:比特币 v2 传输的 4095 字节垃圾与 256 KiB 预读线
BIP324 给节点通信加密这件事已经被讲了很多遍,但加密层跑起来之后,节点怎么防止对端用字节流把自己内存撑爆,细节很少有人拆。对照 Bitcoin Core v31.0 的 src/net.h 与 src/net.cpp,v2 传输(V2Transport)身上绑着三条互不重叠的内存纪律。
第一条:握手垃圾最多 4095 字节
v2 握手阶段允许在密文里垫一段随机”垃圾”字节,用来打乱流量特征。net.h 里 V2Transport 类定义 MAX_GARBAGE_LEN = 4095:发送方挑一段不超过 4095 字节的随机填充(net.cpp 的发送路径用随机数发生器在 0 到这个上限之间取长度),接收方则按”垃圾段加上终止符”的总预算收字节,收满还等不到终止符就判定握手异常。这条线的意义是双向的:给隐私留出抖动空间的同时,把握手期的缓冲区占用钉死在个位数千字节量级——握手还没完成,谁都不该让对端指挥自己分配大内存。
第二条:单包正文的长度天花板
加密包解出的”正文长度”有校验:net.cpp 在接收状态机里计算允许的最大正文尺寸 MAX_CONTENTS_LEN,等于 1 字节的消息类型编码位、加上 12 字节消息类型名、再加上线协议允许的最大载荷——取 MAX_SIZE 与 MAX_PROTOCOL_MESSAGE_LENGTH(net.h 里定义为 400 万字节)中的较小者。超过这个长度声明的包,不配被继续拼接收纳。
第三条:缓冲区最多比已收多留 256 KiB
最容易被忽视的是预读线。net.cpp 的接收函数里定义了 MAX_RESERVE_AHEAD = 256 KiB:处理对端涌来的字节时,接收缓冲区相对”已经真正收到的字节数”最多预留这么多额外空间,同时也不允许超过最大允许消息尺寸再加一截。注释解释得很直白:这是为了防止对端用缓慢滴流、长度声明虚高等手法,诱导本节点为一个迟迟不完整的消息预先圈占巨额内存。
为什么值得普通运维关心
三条线连起来看,是同一套设计哲学:加密与认证之外,协议实现还必须假设对端充满恶意,任何”由对端数字决定的内存分配”都要有硬顶。v2 之前的实现同样有护栏(例如消息头里的长度字段),v2 把垃圾填充与新长度编码引入后,又补了垃圾预算与预读预算两块板。家用节点排查”内存曲线异常爬升”时,这三行常量就是理论天花板——如果占用远超它们允许的尺度,问题多半不在协议缓冲,而在索引、缓存或内存池那几本大账上。
顺带一句隐私账:垃圾长度是发送方随机挑的,观察者无法从握手包长直接反推内容长度;但 4095 的上限也意味着抖动幅度有限,别把流量混淆想象成无痕隐身。
把三条线连成一张防守图
v2 传输的接收路径可以画成三道串行关卡:垃圾段先受 4095 字节的握手预算约束,等不到终止符就整段作废;进入正式收包后,长度声明先对照 MAX_CONTENTS_LEN 的天花板,虚报者白给一个断连理由;最后才轮到 256 KiB 预读线兜底——就算前两道都放行,缓冲区也绝不为”还没到手的字节”圈占无上限的内存。三道线分别对应握手期、解析期、拼包期三个不同的攻击窗口,缺任何一条,另一条都可能被绕过:比如只限正文不设预读线,对端可以谎称超长消息慢慢滴流;只设预读线不管垃圾段,握手阶段就能浪费带宽与缓冲区。
这类常量还有一个工程含义值得记:它们全都不是配置项。比特币核心的哲学是把协议级护栏写死在代码里,不给运营者调错的机会——你可以通过白名单豁免对端的限速与封禁,但改不了缓冲区预算。想调性能参数去动 maxreceivebuffer、maxsendbuffer 那族连接级缓冲配置,它们管的是消息队列吞吐,和 v2 传输的防滥用护栏是两套体系,混调不会互相顶牛,也别指望互相救场。
风险提示:本文基于 v31.0 源码描述协议实现细节,不同版本常量可能调整;不涉及任何投资建议。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。