PPP协议深度解析:帧结构、LCP协商与认证机制 1. 从一个抓包现场说起为什么PPP至今还在被反复讨论很多人第一次接触PPP是在调试一条串口链路或者看运营商接入日志的时候。抓包工具里跳出一串7E FF 03 C0 21 ...第一反应往往是“这什么东西”。但如果你把时间拨回拨号上网的年代PPP几乎是每个家庭用户接入互联网的必经之路。它承担的任务很朴素在一条点对点的链路上把网络层的数据包可靠地送过去同时完成身份验证、地址协商、参数配置这一整套握手动作。PPP的全称是Point-to-Point Protocol中文叫点对点协议。它的设计目标非常明确——为在两个对等节点之间传输多协议数据报提供一套标准方法。注意这里的关键词点对点、多协议、标准方法。这三个词决定了PPP的全部性格。它不像以太网那样天生支持广播和多路访问也不像某些专有协议那样只伺候一种网络层协议。PPP的定位是“链路层的一个通用搬运工”上面可以跑IP、IPX甚至它自己家族里的其他协议。这篇文章适合谁看如果你是网络方向的在校学生PPP大概率出现在教材的某一章里但教材往往只给你一张状态机图就翻页了如果你是刚入行的运维或嵌入式开发可能在调试4G/5G模组的拨号流程、串口服务器、或者某些工业网关时撞见PPP需要快速搞懂它到底在干什么如果你是有一定经验的工程师想重新梳理一遍PPP的帧结构、LCP协商、认证流程和NCP分工这篇文章也可以当作一次系统性的复盘。我会尽量把“为什么这么设计”讲透而不是只罗列字段。需要先说明一点PPP本身是一个协议族不是单一协议。它至少包含三个核心组件——封装机制、链路控制协议LCP、网络控制协议NCP族。很多人把PPP等同于“拨号”这是不准确的。拨号只是PPP的一个典型应用场景PPP真正的价值在于它提供了一套可扩展的链路层协商框架。理解了这套框架你再看PPPoE、PPP over Serial、甚至某些专有链路层协议都会觉得似曾相识。2. PPP的帧结构每个字段都在解决一个具体问题2.1 从HDLC-like帧格式说起PPP的帧格式脱胎于HDLC但做了简化。一个标准的PPP帧长这样字段长度作用Flag1字节标志帧起始和结束固定值0x7EAddress1字节固定为0xFF表示广播地址Control1字节固定为0x03表示无编号帧Protocol1-2字节标识承载的上层协议Information可变载荷数据默认最大1500字节FCS2或4字节帧校验序列Flag1字节结束标志固定值0x7E先看Flag字段。0x7E作为帧边界问题是如果载荷里恰好出现0x7E怎么办PPP的解决办法是字节填充。发送端在同步链路和异步链路上处理方式不同异步链路用转义字符0x7D把0x7E变成0x7D 0x5E把0x7D变成0x7D 0x5D同步链路则用比特填充在连续5个1之后插入一个0。这个细节在调试时非常关键——如果你在抓包工具里看到0x7D开头的字节不要慌那是转义后的数据需要还原。Address和Control字段固定为0xFF和0x03看起来有点浪费。这其实是历史遗留HDLC里这两个字段有实际意义PPP为了兼容保留了它们但约定不使用。有些实现会协商地址控制字段压缩把这两个字节省掉这在低速链路上能省一点带宽。我在调试某些老式串口设备时遇到过对端不支持压缩的情况结果就是协商失败日志里会明确提示ACCM相关的选项被拒绝。2.2 Protocol字段PPP的“多协议”能力从哪来Protocol字段是PPP的灵魂。它用1到2个字节标识载荷类型而且规定了协议号必须是奇数偶数保留给未来可能的扩展。常见的协议号包括0x0021IP协议0x0057IPv6协议0xC021LCP0x8021IPCP0x8057IPv6CP0xC023PAP认证0xC223CHAP认证这个设计让PPP可以同时承载多种网络层协议而且控制协议和数据协议走同一个通道。LCP用来协商链路层参数NCP族用来协商各网络层参数认证协议用来验证身份。它们全部通过Protocol字段区分复用同一条物理链路。这就是为什么PPP能成为“多协议”的搬运工——它不关心你上面跑什么只负责把协商好的东西送过去。提示Protocol字段的值在协商阶段和数据阶段可能不同。比如LCP协商完成后链路进入认证阶段此时Protocol字段会变成0xC023或0xC223。如果你在抓包时只盯着0x0021看会错过整个协商过程。2.3 FCS与最大接收单元FCS是帧校验序列默认2字节也可以协商成4字节。它的计算范围覆盖Address、Control、Protocol、Information和FCS自身之外的填充字节。如果校验失败帧直接丢弃不产生任何响应。这一点和某些协议不同——PPP不会因为收到坏帧就发NAK它选择静默丢弃让上层超时重传。最大接收单元MRU默认是1500字节和以太网的MTU一致。但PPP允许通过LCP协商更大的MRU这在某些需要传输大帧的场景下有用。不过要注意MRU协商的是接收能力不是发送能力。你声明自己能收2000字节不代表对端就会发2000字节给你。实际发送时还要看对端的MRU和对链路质量的判断。3. LCP链路开张前的“装修谈判”3.1 LCP报文的三类动作LCP是PPP启动后第一个跑起来的协议。它的任务是在链路上建立、配置、测试、维护和终止连接。LCP报文分三类链路配置报文Configure-Request、Configure-Ack、Configure-Nak、Configure-Reject链路终止报文Terminate-Request、Terminate-Ack链路维护报文Code-Reject、Protocol-Reject、Echo-Request、Echo-Reply、Discard-Request配置协商的过程很像两个人谈合同。发起方发Configure-Request里面列出一堆选项接收方如果全部同意就回Configure-Ack如果有不同意的就回Configure-Nak并附上建议值如果有不认识的选项就回Configure-Reject。发起方收到Nak后调整参数重新发Request收到Reject后去掉不认识的选项重新发。这个过程可能来回好几轮直到双方达成一致。我在实际调试中见过一种情况两端都配置了“必须认证”但认证方式不匹配。一端只支持CHAP另一端只支持PAP。结果就是Configure-Nak里带着认证选项的建议值但双方都不肯让步最后链路一直卡在协商阶段。日志里会反复出现“LCP: timeout sending Config-Requests”这时候就要检查两端的认证配置是否对称。3.2 常见LCP选项及其实际含义LCP选项很多但常用的就那么几个。我整理了一张表把最常打交道的选项和它们的实际作用列出来选项类型作用调试注意MRU1最大接收单元协商的是接收能力不是发送能力ACCM2异步控制字符映射影响转义规则低速链路常用认证协议3指定PAP或CHAP两端必须一致否则协商失败魔术字5检测环路相同魔术字说明可能自环协议压缩7压缩Protocol字段省带宽但增加处理开销地址控制压缩8省掉0xFF 0x03低速链路常用魔术字这个选项值得单独说。它的原理很简单每一端生成一个随机数通过Configure-Request发给对端。如果收到的魔术字和自己发出去的一样说明链路可能形成了环路因为正常情况下对端不会把自己的魔术字原样送回来。这个机制在拨号时代很有用因为电话线路可能被错误地环回。现在虽然环路问题少了但魔术字仍然是LCP协商的标配选项。3.3 协商失败时的排查思路LCP协商失败是PPP调试中最常见的问题。我的排查顺序通常是这样的看物理层链路是否真的通了串口的话TX/RX是否接反波特率是否一致这些基础问题占故障的一半以上。看LCP报文抓包看Configure-Request有没有发出去对端有没有回应。如果Request发出去了但没回应可能是对端没启动PPP或者物理层有问题。看Nak和Reject如果对端回了Nak看它建议什么值如果回了Reject看它拒绝哪个选项。Reject通常意味着对端不支持某个选项需要去掉。看认证配置如果LCP卡在认证阶段检查两端的认证协议和用户名密码是否匹配。看超时和重传PPP有超时重传机制如果日志里反复出现重传说明对端响应有问题。注意LCP协商是有状态的不是一次Request就能搞定。如果对端回了Nak你必须调整参数后重新发Request而不是继续等。有些实现会在收到Nak后自动调整有些则需要手动干预。看日志时要注意区分“发送Request”和“重传Request”。4. 认证阶段PAP和CHAP的取舍逻辑4.1 PAP简单但不够安全PAP是Password Authentication Protocol的缩写工作方式非常直接发起方把用户名和密码用明文发给对端对端查表验证然后回Accept或Reject。整个过程只有一次交互实现简单调试方便。但PAP的问题也很明显密码明文传输。在串口这种物理链路可控的场景下风险相对有限但在PPPoE这种跑在以太网上的场景明文密码就很不合适了。所以现在运营商基本都用CHAPPAP更多出现在一些内部设备和老式工业设备上。我个人的经验是如果链路物理安全可控且对端只支持PAP那用PAP也不是不行但一定要确保密码不是复用的重要凭证。曾经见过一个案例某设备用PAP认证密码和设备的管理密码相同结果链路被搭线后管理密码也泄露了。这种低级错误在实际项目中并不少见。4.2 CHAP三次握手的挑战应答CHAP是Challenge Handshake Authentication Protocol工作方式比PAP复杂但安全性好得多。它的流程是认证方发送Challenge里面包含一个随机数和自己的标识。被认证方用这个随机数、自己的密码和Challenge里的标识算出一个MD5哈希值连同自己的用户名一起发回去。认证方用同样的算法算一遍比对结果然后回Success或Failure。CHAP的关键在于密码不在链路上传输传输的是哈希值。而且每次认证的随机数不同即使被截获也无法重放。CHAP还支持周期性重新认证进一步降低风险。但CHAP也有坑。最常见的问题是密码存储方式。有些实现要求密码以明文形式存储在配置里因为CHAP需要用它来计算哈希有些实现支持存储哈希值但要求两端配置一致。如果一端存明文一端存哈希认证就会失败。我在调试时就遇到过这种情况设备A的配置里密码是明文设备B的配置里密码是哈希结果CHAP一直失败日志里只显示“CHAP authentication failed”没有任何更多信息。后来把两端都改成明文存储才通过。4.3 认证失败时的日志解读认证失败的日志通常比较简略但有几个关键点可以帮你定位“PAP authentication failed”检查用户名密码是否匹配注意大小写和特殊字符。“CHAP authentication failed”检查密码存储方式是否一致检查Challenge和Response的标识是否匹配。“No authentication protocol negotiated”LCP阶段没有协商出认证协议检查两端的认证配置。“Authentication timeout”对端没有回应认证报文检查链路是否通对端是否卡在别的阶段。还有一个容易被忽略的点认证是单向还是双向。PPP允许只要求一端认证也允许两端互相认证。如果配置成双向认证但只有一端配了密码那另一端就会失败。这个在对接运营商时经常遇到——运营商要求你认证同时也要求你验证运营商的身份但很多设备默认只配了单向。5. NCP族给网络层协议“分房子”5.1 IPCP最常用的NCPLCP和认证都完成后链路进入网络层协议阶段。对于IP网络这个阶段跑的是IPCP。IPCP的主要任务是协商IP地址、DNS地址、以及是否使用IP头压缩。IPCP的协商过程和LCP类似也是Configure-Request/Ack/Nak/Reject那一套。最常见的场景是客户端发Configure-Request里面IP地址填0.0.0.0表示“我不知道我的地址请分配一个”服务端回Configure-Nak里面带上分配好的IP地址客户端用这个地址重新发Request服务端回Ack协商完成。DNS地址的协商也是类似流程。客户端请求DNS服务端通过Nak把DNS地址送回来。有些实现支持主备两个DNS有些只支持一个。如果客户端不需要DNS可以不请求服务端也不会主动给。提示IPCP协商的是网络层参数不是链路层参数。LCP协商的是“怎么传”IPCP协商的是“传什么地址”。两者是分开的不要混淆。5.2 IPv6CP和其他NCPIPv6CP是IPv6对应的NCP流程和IPCP类似但协商的是接口标识符而不是完整地址。IPv6的地址前缀通常由路由器通告IPv6CP只负责生成链路本地地址。除了IP和IPv6PPP还支持其他NCP比如IPXCP、OSINLCP等。这些在现在的网络环境中很少见了但理解它们的存在有助于你理解PPP的设计哲学链路层只管传网络层参数各自协商。这种分层设计让PPP可以适应不同的网络层协议而不需要为每种协议重新设计一套链路层。5.3 协商完成后的数据转发所有NCP协商完成后链路进入数据转发阶段。此时PPP帧的Protocol字段直接标识载荷类型比如0x0021表示IP数据报。数据转发阶段没有额外的握手就是纯粹的封装和解封装。但要注意数据转发阶段并不是“一劳永逸”。LCP的Echo-Request/Reply会周期性发送用来检测链路是否还活着。如果连续多次Echo没有回应PPP会认为链路已断然后重新进入LCP协商阶段。这个机制在拨号时代很重要因为电话线路可能被意外挂断。现在虽然链路更稳定了但Echo机制仍然保留用来检测中间设备的故障。6. PPP的状态机一张图背后的完整生命周期6.1 从Dead到Network的五个阶段PPP的状态机是理解整个协议的关键。它大致分为五个阶段Dead链路不可用物理层没起来。EstablishLCP协商阶段建立链路层连接。Authenticate认证阶段验证身份。NetworkNCP协商阶段配置网络层参数。Terminate链路终止阶段释放资源。每个阶段都有明确的进入和退出条件。比如从Dead进入Establish需要物理层检测到载波从Establish进入Authenticate需要LCP协商成功从Authenticate进入Network需要认证成功从Network回到Terminate可能是收到Terminate-Request也可能是Echo超时。这个状态机在调试时非常有用。当你看到日志里反复出现“LCP: state change”时对照状态机就能知道卡在哪个阶段。比如“LCP: state change Establish - Authenticate”说明LCP成功了进入认证“LCP: state change Authenticate - Terminate”说明认证失败链路要断。6.2 状态迁移中的常见异常状态迁移异常通常表现为“来回跳”或“卡住不动”。来回跳的典型场景是LCP协商成功进入认证认证失败回到Terminate然后重新开始LCP协商。这种循环说明认证配置有问题需要检查用户名密码或认证协议。卡住不动的典型场景是LCP协商一直不成功状态停在Establish。这时候要看Configure-Request有没有回应或者回应了但选项不匹配。还有一种情况是物理层不稳定链路时通时断状态机就会在Dead和Establish之间反复跳。我在调试时养成了一个习惯先看状态机再看具体报文。状态机告诉你“现在在哪”报文告诉你“为什么在这”。两者结合大部分问题都能定位。6.3 终止阶段的两种方式PPP的终止有两种方式主动终止和被动终止。主动终止是本地发Terminate-Request对端回Terminate-Ack然后链路关闭。被动终止是收到对端的Terminate-Request本地回Terminate-Ack然后关闭。还有一种情况是异常终止链路突然断开没有Terminate报文。这时候PPP会依赖Echo超时或物理层检测来发现然后强制关闭链路。异常终止在无线链路上很常见因为信号可能突然消失。这时候日志里会显示“LCP: timeout sending Echo-Requests”或“Physical layer down”。注意终止阶段不是瞬间完成的。Terminate-Request发出后要等对端的Terminate-Ack或者等超时。如果对端不回应PPP会重传几次然后强制关闭。这个重传次数和超时时间是可以配置的默认值在不同实现中可能不同。7. 实战调试从抓包到定位的完整链路7.1 抓包工具的选择与配置调试PPP抓包是少不了的。串口链路的抓包相对麻烦因为需要硬件工具把串口数据转成可分析的格式。常见的方式有串口监听工具在TX/RX线上并联一个监听设备把数据记录下来。设备内置抓包有些设备支持把PPP报文导出成pcap格式直接用Wireshark分析。日志分析如果抓包不方便至少要把PPP的协商日志打开日志里会记录每个报文的类型和关键字段。Wireshark对PPP的支持很好能自动解析LCP、PAP、CHAP、IPCP等协议。但要注意Wireshark解析PPP时需要知道链路类型同步还是异步以及是否启用了压缩。如果解析出来的报文是乱码先检查这两个配置。7.2 一个典型的协商失败案例我曾经遇到过一个案例两端设备通过串口连接LCP协商一直失败。抓包发现一端发Configure-Request对端回Configure-Reject拒绝的选项是“地址控制压缩”。但发起方明明没有配置这个选项。后来仔细看报文发现发起方的Configure-Request里确实带了地址控制压缩选项只是配置界面上没有显示——这个选项是默认开启的但配置界面没有提供关闭的入口。解决办法是升级固件或者通过命令行强制关闭这个选项。这个案例说明配置界面显示的不一定是全部有些选项是默认开启的需要看实际报文才能确认。7.3 认证阶段的密码存储陷阱另一个典型案例是CHAP认证失败。两端配置的用户名密码完全一致但CHAP就是不过。后来发现一端把密码以明文形式存储在配置文件里另一端把密码以哈希形式存储。CHAP计算哈希时明文和哈希算出来的结果不同所以认证失败。解决办法是统一密码存储方式。如果设备支持明文存储就都用明文如果只支持哈希就都用哈希。但要注意哈希存储时两端的哈希算法必须一致否则也会失败。7.4 数据转发阶段的性能问题协商都成功了数据也能转发但速度上不去。这时候要检查几个点MRU是否协商得太小如果MRU只有576字节而实际数据包是1500字节就会被分片增加开销。是否启用了不必要的压缩协议压缩和地址控制压缩能省带宽但增加处理开销。在高速链路上压缩的收益可能抵不过处理开销。FCS是2字节还是4字节4字节FCS更可靠但增加开销。在质量好的链路上2字节足够。Echo间隔是否太短Echo-Request/Reply会占用带宽如果间隔太短会影响数据转发。这些参数都可以通过LCP协商调整但需要两端都支持。如果一端不支持某个优化协商就会失败或降级。8. PPP在今天的实际位置不是过时而是隐身了8.1 PPPoEPPP在以太网上的延续PPPoE是PPP over Ethernet的缩写把PPP帧封装在以太网帧里。这是目前家庭宽带最常见的接入方式。PPPoE的协商流程和PPP基本一致只是多了一层以太网的封装。LCP、认证、IPCP这些阶段一个不少只是跑在以太网上而不是串口上。PPPoE的优点是可以在以太网上实现PPP的认证和地址分配缺点是增加了一层封装降低了有效载荷比例。但在实际使用中这个开销可以接受因为PPPoE的认证和会话管理能力是运营商需要的。8.2 蜂窝网络中的PPP在4G/5G模组中PPP仍然被广泛使用。模组通过串口或USB和主机通信PPP负责在两者之间建立数据通道。LCP协商链路参数CHAP或PAP认证IPCP分配IP地址。虽然蜂窝网络本身有复杂的协议栈但模组和主机之间的接口往往还是PPP。这个场景下的PPP调试和传统拨号类似但要注意模组的AT命令和PPP的配合。比如先发AT命令激活PDP上下文再启动PPP协商。如果顺序错了PPP会卡在LCP阶段。8.3 工业网关和串口服务器工业网关和串口服务器也经常用PPP。这些设备通常需要把串口数据转发到网络PPP提供了一种标准的链路层封装方式。有些设备还支持PPP over TCP把PPP帧封装在TCP连接里实现远程串口访问。这个场景下的PPP配置通常比较固定因为两端都是可控的设备。但要注意工业环境的电磁干扰可能影响链路质量导致FCS校验失败。这时候可以协商4字节FCS或者降低波特率。8.4 为什么PPP没有被完全取代PPP的设计哲学是“链路层只管传网络层参数各自协商”。这种分层设计让它在面对不同网络层协议时都能适应。虽然后来出现了很多新的链路层协议但PPP的协商框架和认证机制仍然被广泛借鉴。更重要的是PPP的实现非常成熟几乎所有的网络设备都支持。在需要点对点链路、需要认证、需要动态地址分配的场景下PPP仍然是一个可靠的选择。它可能不再出现在用户的视野里但在设备内部、在运营商网络里PPP仍然在默默工作。9. 几个容易被忽略的PPP细节9.1 魔术字与环路检测魔术字是LCP协商中的一个选项用来检测链路是否形成了环路。每一端生成一个随机数通过Configure-Request发给对端。如果收到的魔术字和自己发出去的一样说明链路可能形成了环路因为正常情况下对端不会把自己的魔术字原样送回来。这个机制在拨号时代很有用因为电话线路可能被错误地环回。现在虽然环路问题少了但魔术字仍然是LCP协商的标配选项。如果两端都配置了魔术字但协商失败可以检查一下魔术字是否被中间设备篡改。9.2 ACCM与异步链路的转义规则ACCM是Async Control Character Map的缩写用来指定哪些控制字符需要转义。在异步链路上0x7E是帧边界0x7D是转义字符这两个必须转义。其他控制字符是否转义由ACCM决定。ACCM的默认值通常是0xFFFFFFFF表示所有控制字符都转义。但在实际使用中很多实现会协商一个更小的值减少转义开销。如果两端ACCM不一致转义规则就不同可能导致帧解析错误。9.3 协议压缩与地址控制压缩的取舍协议压缩把Protocol字段从2字节压缩到1字节地址控制压缩把Address和Control字段省掉。这两个压缩都能省带宽但增加处理开销。在低速链路上省带宽的收益更大在高速链路上处理开销可能更值得关注。是否启用压缩取决于链路速率和设备处理能力。如果链路速率低于64kbps建议启用压缩如果高于2Mbps压缩的收益就不明显了。当然最终是否启用还要看两端是否都支持。9.4 Echo机制与链路保活Echo-Request/Reply是LCP的维护报文用来检测链路是否还活着。发送方定期发Echo-Request接收方回Echo-Reply。如果连续多次没有收到Reply发送方认为链路已断然后重新进入LCP协商阶段。Echo间隔和重传次数可以配置。间隔太短会增加开销间隔太长会延迟故障检测。默认值在不同实现中可能不同通常间隔是10秒重传3次。在无线链路上可以适当放宽因为无线链路本身就不稳定。10. 写在最后PPP教给我们的设计思路PPP最值得学习的地方不是它的帧格式也不是它的状态机而是它的分层协商思路。LCP负责链路层参数NCP负责网络层参数认证协议负责身份验证三者各司其职通过Protocol字段复用同一条链路。这种设计让PPP可以适应不同的网络层协议而不需要为每种协议重新设计一套链路层。另一个值得学习的是协商机制。PPP不假设两端配置完全一致而是通过Configure-Request/Nak/Reject来动态协商。这种机制在异构网络中非常有用因为两端的实现可能不同配置也可能不同。通过协商两端可以找到一个共同支持的参数集。最后PPP的状态机设计也很值得借鉴。每个阶段有明确的进入和退出条件状态迁移有清晰的触发事件。这种设计让调试变得有章可循——你只需要看当前状态和触发事件就能知道问题出在哪里。我在实际工作中调试PPP的次数不算多但每次遇到都能从它的设计中学到东西。它不是一个复杂的协议但它的设计思路很清晰很值得反复琢磨。如果你正在调试PPP希望这篇文章能帮你少走一些弯路。如果你只是路过希望它能让你对这个“老协议”多一分理解。