摘要

  • PPP 的 Configure-Ack 不能趁着表示同意而修改数值、调整顺序或补充条件;它必须与最新 Configure-Request 完全一致,让“接受”成为可以逐字节核对的证据。
  • LCP 协商不是一方批准整条双向链路。双方通常各自为接收方向提出条件,只有本端请求收到 Ack、同时本端也向对方发出 Ack,状态才进入 Opened;Nak 是反建议,Reject 则划定不再协商的边界。

看似更合理的数字,反而使应答无效

设想设备甲提出:自己最多接收某个大小的帧。设备乙理解 Maximum-Receive-Unit 选项,也愿意配合,却在 Configure-Ack 里换成了一个“更合适”的数值。人类可能把这看成体贴的折中,PPP 却必须把它当成无效应答。

RFC 1661 规定,Ack 的 Identifier 必须对应最近一次请求,选项的内容与顺序也必须毫无改动。只有所有选项都能识别、所有数值都可接受,接收端才可以发 Ack。它有权说“同意”,没有权一边同意一边修订。

这一苛刻要求解决的是分布式状态的歧义。如果 Ack 可以顺手优化参数,请求方就要猜测:对方是在确认原提案,还是发出新提案,抑或只是错误地描述了第三种配置。再叠加丢包、重传和交叉发送,双方很容易以为自己同意的是不同内容。原样复述把判断缩成一个狭窄问题:被接受的就是这些字节。

Identifier 负责把应答系在一次提案上。选项内容发生变化,或前一个请求已收到有效应答时,它必须变化;纯重传则可以沿用。这个编号不认证对方身份,只防止旧的配置对话被误当成当前对话。

反建议不能伪装成同意

不接受某个数值,并不总要终止协商。Configure-Nak 用于选项本身可以理解、也允许协商,但当前取值不可接受的情形。Nak 只列出有争议的选项,并可把它们改成发送方愿意接受的数值;若本地政策要求对方提出某项,Nak 还可以把遗漏的选项附在后面。

但 Nak 没有在途中改写原请求。它提出的是下一轮的可能方向,不会自动生效。原请求方仍要决定是否采纳建议,并用新的 Configure-Request 明确提出;内容一变,Identifier 也随之更新。新提案随后仍需一次逐字节一致的 Ack。

Configure-Reject 表达的是另一层边界:选项无法识别,或者本地管理规则根本不允许协商它。Reject 原样带回被拒选项,而不是提供替代数值。请求方下一轮应删除这些选项。

于是三种回答拥有不同权限。Ack 只确认已经提出的内容;Nak 表示“这个问题可以谈,但请换一个值”;Reject 表示“本端不谈这个问题”。没有数值可替换的布尔选项必须使用 Reject。任何一种回答都不能暗中更改另一端的本地政策。

一条线里其实有两场协商

“协商链路”听起来像一次统一表决。LCP 的状态却是双向分开的。除非具体选项另有规定,配置通常以半双工方式生效,并主要描述 Configure-Request 发送方的接收方向。甲向乙提出的,是甲准备怎样接收乙发来的帧;乙还要就自己的接收方向另发一份请求。

两份请求可以在链路上交叉。甲可能已经拿到自己请求的 Ack,却仍在判断乙的请求应当 Ack、Nak 还是 Reject。RFC 1171 很早就把这种状态称为 Ack-Received:已经收到同意,但还没有发出同意。一个方向的许可不会自动制造反方向的许可。

到了 RFC 1331,链路建立的判据已经写得非常清楚:只有 Configure-Ack 既已发出又已收到,LCP 才进入 Opened。因此,一端不能发布一份配置,就单方面把“物理线路可用”升级为“PPP 链路已打开”。双方分别控制自己提出的接收条件,也分别判断对方提出的条件。

这种对称不是形式主义。两端的接收能力可能不同,一端可能要求认证而另一端不要求,压缩能力与异步控制字符处理也可能不一致。两个独立提案允许不同设备建立共同链路,而无需假装它们具有完全相同的能力。

没写出来的选项由默认值接管

Configure-Request 不是设备能力清单,而是对标准默认值的修改。RFC 1661 甚至要求,取默认值的选项原则上不要放进请求。某项缺席,不代表状态未知,而是采用该项规定的默认值。

这减少了线上共同语言,却提高了观察要求。只保存显式选项的日志无法重建实际配置。有效状态等于被接受的请求,加上所有适用默认值,还必须按正确方向解释。

一份请求中的所有选项同时处理。Ack 一次接受完整的有序列表;Nak 过滤掉已经可接受的项目,只回报有争议者;Reject 只带回不能参与协商者。这种设计让一次交换具有原子边界,又不强迫任何一端公开全部能力,或接受自己虽然支持却不愿启用的功能。

即使双方争论压缩,控制报文仍要读得懂

协商会改变后续 PPP 帧的格式,这产生了一个自举陷阱:若甲认为压缩已经启用而开始省略字段,乙仍按默认格式读取,连用来修复分歧的控制报文都可能无法解析。

RFC 1548 与 RFC 1661 为此保留了退路。LCP 的配置、终止和 Code-Reject 报文,一律按照尚未启用任何配置选项的方式发送;地址、控制与协议字段压缩不能用于这组报文。

稳定编码并不证明双方已经同意。它只保证争议仍能用共同语言表达。数据面的效率可以改变,但控制面必须保留一条能回到默认解释的可识别路径。

沉默与无休止讨价还价都有限额

PPP 面对的链路可能丢包,因此 Configure-Request 配有 Restart 计时器和计数器。RFC 1661 要求 Max-Configure 可配置,并建议以十次发送作为默认上限。超过上限只表明本端没有取得有效应答,不自动证明物理故障或对方恶意。

另一种失败是每轮都有回答,却永远不能收敛。规范建议设置 Max-Failure,默认值为五次。当连续 Nak 仍换不来 Ack,随后本应发送的 Nak 会转成 Reject,本端也不再把自己希望新增的选项附入其中。

这是一种有意停止谈判的机制。反建议只有在可能形成新提案时才有价值;若数值来回摆动,继续协商只会让一个偏好长期占住状态机。显式缩小选项集合,比假装协商总能成功更诚实。

LCP 同意还不是 IP 可以工作

Configure-Request、Ack、Nak、Reject 四个代码已经出现在 1989 年 11 月的 RFC 1134 中,随后贯穿 RFC 1171、RFC 1331、RFC 1548,最终进入 1994 年 7 月的 RFC 1661。文档演进也逐渐把不同层次的完成条件分开。

底层先报告物理线路可用;LCP 协商与具体网络层无关的链路参数;若双方选中了认证协议,认证随后进行;最后,各个 Network Control Protocol 分别配置 IP 等网络层协议。只有相应 NCP 进入 Opened,那种网络层流量才获得发送资格。

所以,一份 Configure-Ack 的证明范围远小于常见的绿色状态灯。它不证明对端身份,不证明认证成功,不证明 IP 地址已经配置,也不证明路由或应用可达。它只证明回答者接受了某一份精确的 LCP 提案。

今天的 IANA PPP 参数登记表 仍把四个配置报文列为代码 1 至 4,并继续维护配置选项命名空间。登记让共同词汇保持稳定,却不是部署普查,也不担保每个实现都正确执行状态机。

薄而精确的同意,让不同设备能够互通

PPP 没有追求含糊的“双方谈妥了”。接受必须有逐字节形式,反建议与拒绝必须使用不同报文,两个方向各有状态,沉默与重复各有上限,后续协议还要分别取得自己的就绪证据。

这里不需要中央协调者规定全球统一的帧大小、认证方式或压缩政策,一端也不能取得对另一端的命令权。共同标准只规定少数双方都能本地验证的语句,其余要求仍由承担风险的设备与运营者负责。

四个很小的代码因此承载了一项制度性成果:互操作不是让某一方获胜,而是让每种回答的权限不超过它真正携带的证据。

来源与结论边界

RFC 1134、RFC 1171、RFC 1331、RFC 1548 与 RFC 1661 支持协议演进、报文语法、方向模型、阶段与收敛边界;IANA 登记表支持当前编号。它们不证明今天的部署比例、厂商一致性、运营商实践、性能收益或唯一正确的超时值。把精确复述解释为“薄而双边的权威”,是从机制得出的分析,而非 RFC 的原句。