摘要
- RFC 1977 要求发送端在压缩会扩张数据时保留原生 PPP 封装,以免压缩开销挤占 MTU;然而这次试压缩已经产生的字典变化必须保留,接收端也要在本地重演同一过程。
- 原生包不携带 LZW 的
CLEAR码,因此双方只能依靠相同的输入、计数、码宽和压缩率检查,在同一包后独立清空字典。 - 16 位序列号可在后续压缩包解码前暴露丢包或乱序;Reset-Request/Reset-Ack 则放弃旧历史、从零开始。它们能限制损害,却不是流量完整性或身份真实性证明。
最值得追踪的是没有压缩标记的包
RFC 1977 先做了一次看似多余的工作。发送端把一个合格的网络层数据报交给 BSD Compress,得到编码结果,再判断这个结果是否值得发送。若压缩带来显著扩张,原包必须以普通 PPP 形式上链;即便只多出不到三个字节,规范也通常建议发送原包,只要不违反其他条件。
这样做解决的是 MTU。若不可压缩数据还要套上更大的压缩封装,上层就必须把 PPP 链路当成一条 MTU 更小的链路。保留原生封装,数据报不会仅仅因为尝试压缩而变大。
然而,发送端只有真正运行压缩器之后,才知道它没有收益。LZW 在这次试算中已经读过协议字段和载荷,匹配过旧词条,加入过新词条,也可能跨过新的码宽阈值。规范不能一面丢弃编码结果,一面把这些状态变化也撤回;否则下一个真正发送的压缩包会引用接收端从未建立的历史。
于是,接收端看见可压缩协议范围内的原生 PPP 包时,要假定它若压缩便会扩张,并在本地“假装压缩”一次。附录中的 pf_bsd_incomp 明确承担这一职责:它不输出码流,却推进序列计数、输入计数、估算输出字节并建立同样的字典条目。
包在线上没有被压缩,并不等于它没有参加压缩协议。原生只是传输形式;字典更新才是持续状态。
文件压缩器失去了文件结尾
Unix compress 面对的是文件。文件结束时,私有字典自然结束。RFC 1977 把同一种算法搬到跨数据报延续的 PPP 链路后,边界消失了:字典变成两台机器共同维护、却不逐项传输的历史。
CCP 的配置选项只确定起点。类型 21 代表 BSD Compress;版本位必须是二进制 001;Dict 给出最大码宽,范围为 9 至 16 位,12 位被称为常见选择。附录提供的那份代码仅支持 9 至 15 位,这是实现边界,不应反过来改写协议允许的协商范围。
接收端也不能因为内存更多,就自行使用更大的字典。字典何时填满、码宽何时扩大、压缩率何时开始触发清空,都由最大码宽参与决定。两端若使用不同上限,状态会在没有任何显眼错误的情况下逐渐分叉。
输入字节本身还受一条固定规则约束:原始 PPP Protocol 小于 0x100 时,必须先压成单字节,再计算序列并压缩整个数据报,无论 PPP 是否协商了 Protocol-Field-Compression。这条规则不是节省字节的可选优化,而是为了让双方从完全相同的输入生成历史。
只有 PPP 进入 Network-Layer Protocol 阶段、CCP 进入 Opened,BSD Compress 包才可以传输。协商成功说明双方接受了版本和上限,却不说明内核、驱动或控制进程会以同一顺序完成后续每一次状态更新。
看不见的 CLEAR
经典 BSD LZW 把 256 保留为 CLEAR 码。它出现在压缩码流末端时,接收端就知道应该丢掉旧字典。问题在于:原生 PPP 包里根本没有 LZW 码流。若一串不可压缩数据恰好让已满字典的表现继续恶化,发送端没有可靠位置显式送出 CLEAR。
RFC 1977 的办法不是另加头部,而是让清空时刻可重复计算。附录在字典已满后,每累计 10,000 个输入字节检查一次压缩率。新的固定点比率若低于上一轮,或跌破一比一,pf_bsd_clear 就把码宽恢复到 9 位,重置最后词条、比率和计数器,并安排下一个检查点。
发送端根据试压缩结果计算;接收端根据本地模拟计算。双方必须看到同一批合格包、使用同样的协议字段压缩规则、分配同样的词条并统计同样的虚拟输出长度,才能在同一包后静默清空。
这是一笔精细的工程交换:不用增加显式开销,保住原生 MTU;代价是把算法细节变成互操作条件。尤其在刚清空后,字典还很贫乏,最初几个包通常没有压缩收益,外表会和启用压缩之前的普通包一样。仅看封装,无法判断它们是否正在建造一段新历史。
序列号要等下一个压缩包才开口
真正的 BSD Compress 包带有 16 位序列号,按高位字节优先传输。字典清空后从零开始,每处理一个合格包就加一,包括最终以原生形式发送的包;65535 之后回到零。规范建议在解码压缩载荷前先检查它。
但原生包没有 BSD Compress 头,也没有这个序列字段。假设其中一个原生包在途中丢失,接收端可能不会立刻得到编号缺口。发送端已把它加入字典并推进计数,接收端则什么都没做。直到后续压缩包出现,包内序列与接收端预期不符,失步才变得可见。
因此,序列号证明的是“不连续已经发生”,而不是“此前一直完整”。它不能指出丢的是哪一个包,不能区分丢失与乱序,不能补回缺少的字节,也不能认证对端。RFC 1977 把损坏包的发现交给 HDLC FCS 和正常丢弃机制;安全考虑部分只写明没有讨论安全问题。链路校验和循环计数器都不是密码学完整性凭据。
Reset 的含义是放弃
接收端第一次遇到意外序列时,应发送 CCP Reset-Request,并在 Reset-Ack 到来之前丢弃所有压缩包。发送端收到每一个请求都必须清空字典、把序列复位为零并应答,因为它无法知道之前的 Reset-Ack 是否抵达。接收端收到每一个匹配的应答也必须清空,因为发送端已经做过同样的动作。
RFC 1977 用“放弃一个文件,开始另一个文件”描述这件事。Reset 不重建缺少的词条,也不验证旧历史;它通过声明旧历史不再使用,制造一个双方都能重新确认的零点。重新发送 Configure-Request 也能迫使 CCP 暂时离开 Opened 再协商,但成本更高。
忙碌链路还给恢复加上一段 RTT。在应答回来之前,错误之后的多个压缩包都可能到达并被丢弃。接收端必须发出足够多的请求,确保至少一个抵达;但若重发频率高于往返时间,重复请求会造成不必要的重复清空。规范提到一秒,只是例子,不是固定计时器。
另一个边界位于软件内部。控制包若由守护进程处理,数据包由内核处理,发送端必须确保清空发生在启动新历史的 Configure-Ack 或 Reset-Ack 发出之前;接收端必须先清空,再处理下一包。抓包里出现正确应答,并不能证明两个执行路径没有发生竞态。
规范给出规则,运行系统留下收据
RFC 1977 的共同层很薄,却很严格:哪些包属于历史、版本是什么、码宽上限在哪里、比率怎样检查、序列怎样递增、失步后怎样重开。IANA 至今把 CCP 选项 21 登记为 BSD Compress,也保留 0x00FD 与 0x00FB 的压缩数据报用途。
按照 Lu Heng 对运行代码的强调,这些登记与协商是可验证规则的来源,不是具体链路成功执行的替代物。要证明两端字典同行,还需要完整包序、原生合格包、码宽变化、计数器与清空时刻的运行证据。
本文不展开 RFC 1962 的通用 CCP 协商史,不讨论 RFC 1963 的串行帧格式,也不借用 RFC 1990 的多链路重排模型;现有来源同样不能证明今日部署率或任何产品表现。RFC 1977 留下的历史要点更窄也更耐用:一个包是否压缩,是线上格式的决定;它是否改变字典,是协议状态的决定。二者从来不是同一个问题。
来源
- RFC Editor:RFC 1977 记录
- RFC 1977:PPP BSD Compression Protocol
- RFC 1962:The PPP Compression Control Protocol
- RFC 1661:The Point-to-Point Protocol
- IANA:PPP 协议字段分配
- RFC 1963:PPP Serial Data Transport Protocol
- RFC 1990:The PPP Multilink Protocol
- Lu Heng:Running-Code Primacy
- Lu Heng:Minimum Initial Specification, Localized Future Decision, and Voluntary Adoption
- RFC Editor:RFC 1977 勘误检索
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance
