转自:https://blog.csdn.net/tianruxishui/article/details/44057717
ppp 完全理解(二)
pppd 协议及代码分析
作者:李圳均
日期:2013/11/27
通过前文所述,我们可以知道,pppd是一个后台服务进程(daemon),是一个用户空间的进程,其实现了ppp策略性的内容,包括所有鉴权、压缩/解压和加密/解密等扩展功能的控制协议。
PPP提供了一种通过串行点对点连接传输数据包的方法。它主要包括四个部分:
· 在串行链路上封装IP数据报的方法
· 链路控制协议 LCP(Link Control Protocol);
· 网络控制协议 NCP(Network Control Protocol);
· 认证协议:口 令验证协议PAP(Password Authentication Protocol)和挑战握手验证协议CHAP(Challenge-Handshake Authentication Protocol)。
注:在一般的PPP描述中会把PPP会为三个部分,有些是上面分类的前三个,其依据是认证协议在一定程度上可分到LCP中;而另一些则是后面三个,其依据是PPP在串行链路上封装IP数据报的方法是ppp协议处理模块的内容,其分类以ppp控制协议为标准。本文档将主要介绍LCP,NCP及认证协议相关内容,有关在串行链路上封装IP数据报的方法,将在下一个文档中详细介绍。
一:ppp协议
1:ppp数据帧格式
PPP 协议是在高级数据链路控制协议(HDLC)的基础上设计的,因此,其基本格式与 HDLC 相同,只是地址域和控制域已经被固定,PPP 数据帧格式如下表:
• 标志域
标志域为一个字节,表示数据帧的开始或结束。标志序列为 16 进制 7E。它是数据帧间隔,两个数据帧之间只要一个标志即可,两个连续的标志表示一个空数据帧。
• 地址域
地址域为一个内容为 16 进制 FF 的字节,该地址为广播地址,所有的终端都必须接受并使用该地址,使用其它地址的数据帧应该被忽略。控制域控制域为一个字节,内容为 16 进制 0x03,对于包含其它内容的数据帧应该忽略。
• 协议域
协议域为两个字节,它表示 PPP 封装的信息域内的信息使用的协议。下面给出常用的几种协议编号,详细信息可以参考文献[26]:(协议编号为 16 进制形式)
0021: IP 8021:IP 控制协议
C021: 连接控制协议 C023:密码鉴权协议 PAP
C223: 握手挑战鉴权协议
完全内容:
0001 Padding Protocol
0003 to 001f reserved (transparency inefficient)
0021 Internet Protocol
0023 OSI Network Layer
0025 Xerox NS IDP
0027 DECnet Phase IV
0029 Appletalk
002b Novell IPX
002d Van Jacobson Compressed TCP/IP 002f Van Jacobson Uncompressed TCP/IP
0031 Bridging PDU
0033 Stream Protocol (ST-II)
0035 Banyan Vines
0037 unused
0039 AppleTalk EDDP
003b AppleTalk SmartBuffered
003d Multi-Link
005d reserved (compression inefficient) 00cf reserved (PPP NLPID)
00fd 1st choice compression
00ff reserved (compression inefficient) 0201 802.1d Hello Packets
0203 IBM Source Routing BPDU
0231 Luxcom
0233 Sigma Network Systems
8021 Internet Protocol Control Protocol 8023 OSI Network Layer Control Protocol
8025 Xerox NS IDP Control Protocol 8027 DECnet Phase IV Control Protocol
8029 Appletalk Control Protocol
802b Novell IPX Control Protocol
802d Reserved
802f Reserved
8031 Bridging NCP
8033 Stream Protocol Control Protocol 8035 Banyan Vines Control Protocol
8037 unused
8039 Reserved
803b Reserved
803d Multi-Link Control Protocol
80fd Compression Control Protocol
80ff Reserved
c021 Link Control Protocol
c023 Password Authentication Protocol
c025 Link Quality Report
c223 Challenge Handshake Authentication Protocol
• 信息域
信息域的长度是可变的,其中的内容是按协议域指定的协议封装的用户数据,信息域的最大长度默认是 1500 字节,在经过双方协商后,可以更改为其它值。
• 校验域
校验域(FrameCheck Sequence, FCS)为 2 个字节,它计算的是在没有插入任何转义符号前的地址域、控制域、协议域、信息域内的数据,不包括标志域和校验域。在发送数据时,依次计算上述内容,然后将计算后的结果放入校验域;在接收时,首先去除转义字符,然后再计算校验。在接收中计算校验时可以将校验域也计算在内,计算的结果应该是固定值F0B8(16 进制),有关该算法的具体实现,见文献[26]。
• 空闲
在链路空闲时间中,异步链路可以发送全 1 表示链路空闲,同步链路应该发送标志序列表示空闲。由于 PPP 采用起始结束标志来表示数据帧的起始和结束,而在 PPP 的信息字段和 FCS 字段都可能包含 7E,这样可能会被误认为是开始/结束标志,所以 PPP 中引入了一个转义序列。转义序列包括一个转义字符 7D,后面是原来的值与 0x20 异或的结果,即7E 转义为7D 5E。而发送 7D 时则转义为 7D 5D。同样,这种转义方式还保护控制字符,比如 XOFF 是底层驱动用来中断串行传输的,为了避免引起混淆,将 0x14 用转义序列7D 34发送。默认的,0x00 到 0x1F 之间的所有值都要转义,不过,经过协商后可以去除部分需要转义的值。
2:拨号建立连接的过程
拨号建立连接的过程就是 PPP 操作的过程。为了在一个点对点链路上建立通信,通信双方必须发送链路控制协议数据包来配置和测试链路。当链路建立后,通信的一方可能需要进行鉴权,然后使用 NCP 数据包来选择和配置网络层使用的协议。当这些过程完成后,通信链路建立完毕,通信双方就可以开始发送数据了。这个链路将一直存在直到通信的一方发送 LCP、NCP 数据包关闭链路或发生其它意外事故。在本节我们将介绍 PPP 操作过程中的各个阶段,其中涉及到的协议将在下文继续说明。在建立、维持和终止PPP 连接的过程中,经历了若干个阶段,如下面两图所示
1. 链路死亡阶段(物理层不存在)
连接通常开始和结束于这个阶段。在链路死亡阶段,通信链路不存在。当有外部操作要启动连接时,PPP 开始进入建立阶段并向 LCP 协议的状态机发送 UP 事件。
2.链路建立阶段
链路建立阶段使用 LCP协议协商通信过程中所需要的配置信息。这些信息包括:通信过程中的数据包的最大长度、异步通信中的控制字符映射、协议和地址控制域压缩、鉴权协议以及链路检测协议等。这些选项是链路建立阶段后必须使用的。如果某些选项没有进行协商则认为使用默认值。值得注意的是,此时协商的内容都是与网络层协议无关的选项,与网络有关的选项要在网络层协议阶段使用特定的协议进行协商。当通信的双方都收到对方的确认信息后,通信链路建立完毕。
3. 鉴权阶段
鉴权阶段不是必须的,但是在很多系统都需要进行鉴权,以验证客户端的身份。需要鉴权的主机在链路建立阶段发送配置鉴权协议的选项。目前,PPP 中支持的鉴权协议包括密码鉴权协议(PAP)和挑战握手协议(CHAP)。如果鉴权通过则进入网络层协商阶段,否则直接进入终止阶段。
4. 网络层协议阶段
当进入网络协议阶段后,首先必须使用特定的网络配置协议进行网络层协议配置,比如如果网络层使用 IP 协议,则使用 IP 控制协议(IPControl Protocol, IPCP)进行网络配置,以获得相关的网络协议信息。当网络协议配置通过后,就可以使用网络协议进行数据传输了,此后 PPP 中携带的数据将是网络协议数据。
5. 终止阶段
PPP可以在任何时候终止连接。终止连接采用 LCP 协议,当链路关闭时,PPP 通知相应的网络层协议采取相应的行动。当通信双方交换终止数据包后,整个终止过程就完成了, PPP 进入链路死亡阶段
3:LCP(链路控制协议)
如上所述,链路控制协议(LCP)用于 PPP 链路的建立、维护和拆除。LCP 数据包是在 PPP 数据包的信息域发送的。LCP 数据帧分为如下三个部分:
• 连接配置包:用于建立和配置一个 PPP 链路连接
• 连接终止包:用于终止链路连接。
• 连接维持包:用于管理和测试链路连接。
整个LCP数据包的内容都在ppp数据包的信息域中,ppp协议字段通过C021这个值标注当前ppp数据报为LCP协议数据。
1:LCP数据包格式
其中代码域用来决定数据包的类型;标志域用来确定发送和请求数据包是否匹配;长度域表示数据包的长度,包括代码域、标志域、长度域和数据域。
2.LCP 数据包类型
根据代码域的不同,LCP 数据包可以分为以下数据帧:
• 配置请求数据帧(Configure-Request):代码域:1
为了打开一个 LCP 连接,必须发送一个配置请求数据包,欲设置的数据在 LCP 的数据域中设置,接收到该数据包后必须应答。配置选项的内容在下文论述。
• 配置确认数据帧(Configure-ACK):代码域:2
如果接收到的配置数据包中的所有配置选项都可以接受,则用配置确认数据帧应答。应答时将配置请求数据包的代码域、标志域和数据域复制到配置确认数据帧中。
• 配置否认数据帧(Configure-NAK):代码域:3
如果在接收到的配置数据帧中有参数无法接受,则用该数据帧应答。将无法接受的选项的内容修改为可以接受的值后按顺序添加到数据域中,如果还有其它选项需要协商,也可以增加到数据域中。
• 配置拒绝数据帧(Configure-Reject):代码域:4
如果接收到的配置请求数据帧中有部分选项无法识别或不允许使用,则用配置拒绝数据帧应答。此时,复制标志域并将请求数据帧中的要拒绝的选项按原来的顺序复制到数据域中。
• 终止请求数据帧/终止确认数据帧(Terminate-Request/Terminate-ACK)
终止请求数据帧:代码域为 5;终止确认数据帧:代码域为6如果通信一方要终止链路连接,则应该发送终止请求数据帧,代码域设为 5,数据域为任何附加信息;接收到终止请求的一方发送终止确认数据帧,此时代码域为 6,标志域和
数据域从接收到的请求数据帧中拷贝。
• 代码拒绝数据帧(Code-Reject):代码域:7
如果接收到的数据帧的代码域为无效代码,则用代码拒绝帧应答,表示该错误无法恢复。接收到代码拒绝数据帧的主机应该报告错误。
• 协议拒绝数据帧(Protocol-Reject):代码域:8
如果在 PPP 封装中接收到一个未知的通信协议,表示对方想要使用一个本机不支持的协议。此时,如果 LCP 已经处于打开状态,则必须发送协议拒绝数据帧来通知对方,信息域中包括拒绝的协议和信息;但如果在其它状态,则直接丢弃数据帧。
• 回应请求数据帧/回应应答数据帧(Echo-Request/Echo-Reply)
Echo-Request:代码域:9 Echo-Reply:代码域:10 LCP 包含 Echo-Request 和 Echo-Reply 代码用于训练双方通信的数据链路层上的循环通信机制。通信一方发送一个 Echo-Request 包,其中代码域为 9,在信息域中插入本地魔数(Magic-Number,关于魔数,见下文)和任何用于测试的数据。接收到Echo-Request 的一方则用 Echo-Reply 来回应,其中代码域为 10,标志域从请求数据帧中复制,然后在信息域中插入本地魔数,并将请求数据包的内容拷贝到应答数据包中。数据帧格式如下表:
• 丢弃请求数据帧(Discard-Request):代码域:11
该数据帧提供了一种在数据链路层上的测试机制,一方发送该数据帧,另一方接收后直接丢弃。
3.LCP 配置选项
LCP 配置选项允许在一个点对点链路上通过协商修订标准特性值,这些选项包括:最大接收单元,异步控制字符映射、链路鉴权协议等。如果一个配置选项没有在配置请求数据包(Configure-Request)中出现,那么该配置选项将使用默认值。配置选项列表的结束由LCP 数据包的长度标识。在协商过程中,除非特别声明,这些配置选项应用在半双工方式,经过协商后的值仅在接收配置请求数据包的方向上有效。配置选项是 LCP 配置请求等数据帧的数据域内的值。配置选项格式如下:
选项类型:1 字节,指示配置选项类别。
选项长度:1 字节,表示该选项的长度,包括类型、长度和数据。
数据:指示该选项的配置内容,它的格式和长度由选项类型决定。
选项类型分别如下:
• 最大接收单元(Maximum-Receive-Unit, MRU)
该选项用来通知对方该实现可以接收的最大数据包长度,如果要将数据包长度设置为较小值,必须保证在链路同步丢失后仍然能够接收 1500 个字节的数据包。
• 异步控制字符映射(Asynchronous-Control-Character-Map, ACCM)
这个配置选项提供了一个在异步链路上协商控制字符映射表的方法。默认的,PPP 将所有的控制字符映射到相应的两字符序列。然而,很少有必要将所有控制字符都进行转义映射。因此,应用程序可以通过该选项去通知对方哪些控制字符需要进行转义。控制字符映射表通过 4 个字节来表示,其中的每一位表示相应的值是否映射,0 表示不进行映射,1 表示进行映射。在传输过程中最先传输的是第 31 位,最后传输的是 0 位。其中,第 0 位对应的是ASCII 码 NUL。
• 鉴权协议(Authentication-Protocol)
一般在网络层交换数据前要求进行鉴权,这个配置选项提供了一种协商鉴权协议的方法。默认不进行鉴权。在请求鉴权的过时,每次只能使用一个鉴权协议选项,只有当该协议被拒绝以后,才能再请求使用别的协议进行。
• 质量协议(Quality-Protocol)
在一些连接中,可能需要决定什么时候、多久进行数据发送,这一过程称为质量监控。这个配置选项提供了一种协商使用的质量监控协议的方法。默认不使用质量监控协议。
• 魔数(Magic-Number)
该选项提供了一种探测短路连接和其它数据链路层异常的方法,它可能在其它配置选项中用到。使用魔数检测链路的基本思想是:当一方接收到带有魔数选项的配置请求数据帧后,将接收到的魔数与上次发送的魔数进行比较,如果不相同就认为没有发生短路。如果两个魔数相同,则需要发送一个携带不同魔数的配置否认帧,然后将接收到的魔数与发送的魔数进行比较。
• 协议域压缩(Protocol-Field-Compression)
该选项提供了一种压缩数据链路层协议域的方法。在标准的PPP 中,协议编号为两个字节,经过协商后,可以把编号小于 256 的协议压缩为一个字节传输,比如传输 IP 信息时,协议编号可以由 0021 压缩为 21,但是编号大于 256 的协议无法压缩。默认不使用协议压缩。
选项类型:7 选项长度:2
• 地址和控制域压缩(Address-and-Control-Field-Compression)
该选项提供了一种压缩数据链路层地址和控制域的方法。标准 PPP 协议中必须发送地址和控制域,但由于这些是固定值,因此很容易压缩。在接收过程中,如果没有接收到 FF则认为进行了地址和控制域压缩。
选项类型:8 选项长度:2
下面为一组LCP协商过程的LOG:
我对第一条LCP报文做一个分析,后面可以由读自己练习一下。
Ff 03是ppp协议的地址域和控制域,C0 21表示这个报文是LCP数据包,01是LCP代码,表示配置请求数据帧(Configure-Request),01是LCP标志位,标志两条发送和接收命令的对就关系,00 14是LCP命令的长度,02 06表示异步控制字符映射(Asynchronous-Control-Character-Map, ACCM),0000 00 00 是ACCM的值,表示全部不映射,05 06,表示魔术字,a5 d0 6c aa是魔术字的值,07 02表示协议域压缩(Protocol-Field-Compression),08 02表示地址和控制域压缩(Address-and-Control-Field-Compression)
sent [ LCP ConfReqid=0x1 <asyncmap 0x0> <magic0xa5d06caa> <pcomp> <accomp>]
ff 03 c0 21 01 01 0014 02 06 00 00 00 00 05 06a5 d0 6c aa 07 02 0802
rcvd [LCP ConfReq id=0xa7 <asyncmap 0x0> <authchap MD5> <magic 0x2d0c577> <pcomp> <accomp>]
rcvd ff 03 c0 21 01 a7 00 19 02 06 00 00 00 00 03 05 c223 05 05 06 02 d0 c5 77 07 02 08 02
sent [ LCP ConfNakid=0xa7 <auth pap>]
sent ff 03 c0 21 03 a7 0008 03 04 c0 23
rcvd [LCP ConfAck id=0x1 <asyncmap 0x0> <magic0xa5d06caa> <pcomp> <accomp>]
rcvd ff 03 c0 21 02 01 00 14 02 06 00 00 00 00 05 06 a5d0 6c aa 07 02 08 02
rcvd [LCP ConfReq id=0xa8 <asyncmap 0x0> <authpap> <magic 0x2d0c577> <pcomp> <accomp>]
rcvd ff 03 c0 21 01 a8 00 18 02 06 00 00 00 00 03 04 c023 05 06 02 d0 c5 77 07 02 08 02
sent [LCP ConfAck id=0xa8 <asyncmap 0x0> <authpap> <magic 0x2d0c577> <pcomp> <accomp>]
sent ff 03 c0 21 02 a8 00 18 02 06 00 00 00 00 03 04 c023 05 06 02 d0 c5 77 07 02 08 02
rcvd [LCP DiscReq id=0xa9 magic=0x2d0c577]
rcvd ff 03 c0 21 0b a9 00 08 02 d0 c5 77
4. LCP 协商过程
下图所示为一个典型的 LCP 协商过程。图中终端 1 和终端 2 分别代表通信的双方,有向线段表示数据帧的流向。图中给出了终端 1 方向的协商完成过程。表格中给出的是每次发送的数据帧的具体内容。终端 1 第一次发送的请求数据帧中请求使用的鉴权协议为PAP,终端 2 不使用 PAP 协议鉴权,所以终端 2 用 NAK 数据帧应答,终端 1 检测到对方不使用 PAP 后,重新以 CHAP 协议发送请求数据帧,终端 2 此时可以接受所有的配置请求,所以终端 2 发送协商 ACK 数据帧。这样就完成了一个方向的协商,同理,终端 2 发送的协商请求过程与此类似。图中标注的值都是没有经过转义的字符,实际发送过程中要转义这些字符。
4:鉴权协议
在 PPP 连接过程中, LCP 协议定义了一种使用鉴权协议进行鉴权的方法。这种机制可以使用不同的协议进行鉴权,目前支持的鉴权协议包括 PAP(Password Authentication Protocol)和CHAP(Challenge Handshake Authentication Protocol)。
1.密码鉴权协议(PAP)PAP 提供了一种通过双向握手进行身份确认的简单方法。在LCP 链路建立后,被鉴权者将身份和密码发送给鉴权者,然后等待对方的确认信息。因为用户的身份和密码是通过链路以明码的方式发送的,所以 PAP 不是一种绝对安全的鉴权方法。
(1)PAP 数据帧格式
(2)数据帧类型
• 鉴权请求(Authenticate-Request)
鉴权请求用于启动密码认证协议,将本地的身份标识和密码发送给对方,并等待对方应答。该过程可以多次重复直到接收到对方的应答信息。
• 鉴权确认/鉴权否认(Authenticate-ACK/Authenticate-NAK)
如果接收到的鉴权请求数据帧中的用户标识和密码都合法,那么通信终端将使用鉴权确认数据帧进行确认,以通知对方已经通过了身份验证;如果接收到的信息不合法,则使用鉴权否认数据帧通知对方。在鉴权确认数据帧的数据域中可以包含一些用于显示的信息。
2.挑战握手鉴权协议(CHAP)
CHAP 协议使用三方握手来周期性的确定对方的身份,它可以在链路建立后的任何时候进行。当链路建立后,鉴权者向对方发送一个“挑战”信息,对方使用单向链表(one-way hash)函数计算后发送结果,鉴权者将接收到的信息与自己计算出来的结果进行比较,如果两者相同,则鉴权成功;否则,鉴权失败,连接被终止。同 PAP 相比,CHAP 更具有安全性。首先,鉴权过程中使用不断变化的挑战信息和身份标识,这使得攻击者很难有机会进行破解;其次,鉴权由鉴权者控制,它可以随时对对方进行身份确认。使用 CHAP 时,必须配合一种链表算法,目前与 CHAP 配合使用的算法是 MD5 算法。在 PPP 中使用CHAP必须在 LCP 协商时配置相应的鉴权算法为 CHAP,配置选项格式如下:
(2)CHAP 数据帧类型
• 挑战和应答数据帧
挑战数据帧用来启动 CHAP。鉴权者在 LCP 协商后主动发送挑战信息来验证用户身份。对方在接收到挑战信息后用单向链表算法进行计算,然后将计算后的结果用应答数据帧进行应答。数据帧格式如下:
代码:挑战数据帧为 1;应答数据帧为 2
标识:一个字节。每次发送挑战数据帧时必须使用不同的标识码;应答数据帧必须将挑战数据帧的标识码复制后发送。
挑战值长度:一个字节,指示挑战值的长度。
挑战值:一个以上的字节,首先发送高位字节。挑战值是一个可变的字节流,每次挑战要使用不同的数值;应答数据帧中该域存放经过计算后的信息流,信息流的长度取决于使用的链表算法,比如 MD5 算法计算的结果是 16 字节。
名称:标识传输数据包的系统的名称,但是该域的值并没有限制,可以采用不同数值进行发送。
• 成功和失败数据帧如果接收到的应答信息是正确的,那么主机使用成功数据
包进行应答;反之,主机发送失败数据包并终止连接。
代码:成功数据帧为 3;失败数据帧为 4
标识:必须从应答数据帧中复制该值。
信息:信息域是可选的而且其内容是由具体的应用来决定的,一般来说,信息域存放的是可以显示的 ASCII 码。
下面是一个PAP鉴权过程的命令
c0 23:PAP;AuthReq :01;id :01;长度:00 14;用户名长度:0d;用户名:77 65 72 74 33 34 35 36 24 25 35 3637;密码长度:01;密码71
用户名为:wert3456$%567;密码为:q;
sent [ PAP AuthReq id=0x1 user="wert3456$%567" password=<hidden>]
ff 03 c0 23 01 01 00 14 0d77 65 72 74 33 34 35 36 24 25 35 36 37 01 71
rcvd [ PAP AuthAck id=0x1 "" ]
ff 03 c0 23 02 01 0005 00
下面是CHAP鉴权的过程,没有二进制的命令,读者可试着自行解析为二进制。
rcvd [CHAP Challenge id=0x1<cefafe87223004753244f465e862a987>, name = "UMTS_CHAP_SRVR"]
sent [CHAP Response id=0x1 <b811aff6568ff284cd4609d3f08cf5a8>, name= "test"]
rcvd [CHAP Success id=0x1""]
CHAP authentication succeeded
注:以上两组LOG都是在不需要鉴权的状态下抓取的,所以在AuthAck 时是个空值,在有鉴权的情况下,AuthAck 时的值与LOG可能不同,请读者注意区分
5:IPCP协议
在 LCP 协商和鉴权阶段后, PPP 操作进入网络协议配置阶段。在这个阶段通信双方发送 NCP(Network Control Protocol)数据包来选择和配置一个或多个网络层协议。比如,如果网络层要使用 IP 协议,那么此时必须使用一个网络配置协议来配置双方的 IP 地址、域名服务器 IP 地址等,只有双方获得了这些必要的信息,才能进行网络协议层的数据传输。配置 IP 协议的 PPP 配置协议是 IPCP。IPCP 用来配置、使能、禁用通信双方的 IP 协议模块。IPCP使用与 LCP 相同的协商机制,与 LCP 不同的地方有:
• PPP 中的数据链路层协议域
因为 IPCP 是在 PPP 的数据域中发送的,因此,在 PPP 的协议域中必须设置为 IPCP的协议编号。IPCP 的编号是 8021。
• 使用的代码域 IPCP 使用的数据帧格式与 LCP 相同,只是它的代码只有 1到7 的这 7 种取值,即它只有 7 种数据帧类型,主要用来发送配置请求、接收应答以及拒绝应答等,见上文有关 LCP 数据帧的部分。
• 配置选项
IPCP 使用与 LCP 完全不同的配置选项,见下文详细论述。
1.IPCP 数据帧格式
2.IPCP 配置选项
• IP-Addresses:代码域:1
由于该选项在具体应用中存在问题,所以目前不再使用该选项,它由 IP-Address 选项取代。只有在对方发送该协商选项时才使用,否则不要主动使用该选项[29]。
• IP-Compression-Protocol 用来提供协商使用指定的压缩协议,默认不使用压缩选项。选项的格式如下:
IP 压缩协议域指明要使用的压缩协议,协议编号与 PPP 协议域中的协议编号相同。目前支持的协议有 Van JacobsonCompressed TCP/IP[29],编号为 002D(16 进制)。
• IP-Address 用来协商本地使用的IP 地址。该选项允许请求发送者提供自己的IP 地址或请求对方给自己分配 IP 地址,在后一种情况下,请求者发送一个全为 0 的IP 地址,对方在一个 NAK 数据帧中给出请求者的 IP 地址。选项的格式如下:
• Primary DNS Server Address/Secondary DNSServer Address
该选项用来协商远端的主、次 DNS(Domain Name System,域名服务器)服务器地址。将选项的数据域设置为 0 表示要求对方提供 DNS 地址,对方使用 NAK 数据帧来应答 DNS地址。选项格式如下:
• Primary NBNS Server Address/Secondary NBNSServer Address
该选项用来协商远端的主、次 NBNS(NetBIOS Name Server, NetBIOS 名称服务器)节点地址。该选项的数据为 0 表示要求对方提供 NBNS 地址,对方使用 NAK 数据帧来应答NBNS 地址。类型代码分别为 130 和 132。
3.IPCP 协商过程
客户端首先发送 IP 地址和 DNS 地址都为零的请求数据帧,服务器接收到后用 NAK 数据帧回复动态 IP 地址和DNS 地址,然后客户端再次用接收到地址发送请求,接收到 ACK数据帧即完成了 IPCP 配置。服务器直接将自己的 IP 地址发送给客户端,接收到 ACK 数据帧后 IPCP 配置成功。这个过程如图 3-8 所示。
sent [ IPCP ConfReq id=0x1 <addr 0.0.0.0> <ms-dns1 0.0.0.0> <ms-dns3 0.0.0.0>]
ff 03 80 21 01 01 00 16 0306 00 00 00 00 81 06 00 00 00 00 83 06 00 00 00 00
rcvd [LCP ProtRej id=0xaa 80fd 01 01 00 0f 1a 04 78 00 18 04 78 00 15 03 2f]
rcvd ff 03 c0 21 08 aa 00 15 80 fd 01 01 00 0f 1a04 78 00 18 04 78 00 15 03 2f
rcvd [IPCP ConfNak id=0x1<ms-dns1 10.11.12.13> <ms-dns3 10.11.12.14> <ms-wins10.11.12.13> <ms-wins 10.11.12.14>]
rcvd ff 03 80 21 03 01 00 1c 81 06 0a 0b 0c 0d 8306 0a 0b 0c 0e 82 06 0a 0b 0c 0d 84 06 0a 0b 0c 0e
sent [IPCP ConfReq id=0x2<addr 0.0.0.0> <ms-dns1 10.11.12.13> <ms-dns3 10.11.12.14>]
sent ff 03 80 21 01 02 00 16 03 06 00 00 00 00 8106 0a 0b 0c 0d 83 06 0a 0b 0c 0e
rcvd [IPCP ConfReq id=0x2a]
rcvd ff 03 80 21 01 2a 00 04
sent [IPCP ConfNak id=0x2a<addr 0.0.0.0>]
sent ff 03 80 21 03 2a 00 0a 03 06 00 00 00 00
rcvd [IPCP ConfNak id=0x2<addr 10.28.15.174> <ms-dns1 58.22.96.66> <ms-dns3218.104.128.106>]
rcvd ff 03 80 21 03 02 00 16 03 06 0a 1c 0f ae 8106 3a 16 60 42 83 06 da 68 80 6a
sent [IPCP ConfReq id=0x3<addr 10.28.15.174> <ms-dns1 58.22.96.66> <ms-dns3218.104.128.106>]
sent ff 03 80 21 01 03 00 16 03 06 0a 1c 0f ae 8106 3a 16 60 42 83 06 da 68 80 6a
rcvd [IPCP ConfReq id=0x2b]
rcvd ff 03 80 21 01 2b 00 04
sent [IPCP ConfAck id=0x2b]
sent ff 03 80 21 02 2b 00 04
rcvd [IPCP ConfAck id=0x3<addr 10.28.15.174> <ms-dns1 58.22.96.66> <ms-dns3218.104.128.106>]
rcvd ff 03 80 21 02 03 00 16 03 06 0a 1c 0f ae 8106 3a 16 60 42 83 06 da 68 80 6a
二:pppd代码分析
pppd的代码从main.c开始,入口函数为main(),下面从main函数的几个关键点讲一下pppd的代码。
for (i = 0; (protp = protocols[i]) != NULL; ++i)
(*protp->init)(0);
这个循环从结构数组protocols中读出初化函数并执行,数组定义如下,目前ifdef中的部分没有支持。
struct protent *protocols[] = {
&lcp_protent,
&pap_protent,
&chap_protent,
#ifdef CBCP_SUPPORT
&cbcp_protent,
#endif
&ipcp_protent,
#ifdef INET6
&ipv6cp_protent,
#endif
&ccp_protent,
&ecp_protent,
#ifdef IPX_CHANGE
&ipxcp_protent,
#endif
#ifdef AT_CHANGE
&atcp_protent,
#endif
&eap_protent,
NULL
};
数组protocols中的结构体定义如下,其定义了pppd支持的几种协议的基本操作、协议数据、操作选项列表原型
struct protent {
u_short protocol; /*PPP protocol number */
/* Initialization procedure */
void (*init) __P((int unit));
/* Process a received packet */
void (*input) __P((int unit, u_char *pkt, int len));
/* Process a received protocol-reject */
void (*protrej) __P((int unit));
/* Lower layer has come up */
void (*lowerup) __P((int unit));
/* Lower layer has gone down */
void (*lowerdown) __P((int unit));
/* Open the protocol */
void (*open) __P((int unit));
/* Close the protocol */
void (*close) __P((int unit, char *reason));
/* Print a packet in readable form */
int (*printpkt) __P((u_char *pkt,int len,
void (*printer) __P((void *, char *, ...)),
void *arg));
/* Process a received data packet */
void (*datainput) __P((int unit, u_char *pkt, int len));
bool enabled_flag; /* 0iff protocol is disabled */
char *name; /*Text name of protocol */
char *data_name; /* Textname of corresponding data protocol */
option_t *options; /*List of command-line options */
/* Check requested options, assign defaults */
void (*check_options) __P((void));
/* Configure interface for demand-dial */
int (*demand_conf) __P((intunit));
/* Say whether to bring up link for this pkt */
int (*active_pkt) __P((u_char*pkt, int len));
};
以PAP鉴权协议为例,下面是PAP的protent结构体。结构体中定义了几个操作函数,协议名,以及pap_option_list(PAP协议操作选项)
struct protent pap_protent = {
PPP_PAP,
upap_init,
upap_input,
upap_protrej,
upap_lowerup,
upap_lowerdown,
NULL,
NULL,
upap_printpkt,
NULL,
1,
"PAP",
NULL,
pap_option_list,
NULL,
NULL,
NULL
};
option_t 记录了pppd的参数,启动pppd时传入相应该的参数既可完成对应的功能,option_t的原型如下:
typedef struct {
char *name; /*name of the option */
enumopt_type type;
void *addr;
char *description;
unsignedint flags;
void *addr2;
int upper_limit;
int lower_limit;
constchar *source;
shortint priority;
shortint winner;
} option_t;
下面便是pap的协议操作选项,有5个选项,通过设置启动参数来使用。
static option_t pap_option_list[] = {
{"hide-password", o_bool, &hide_password,
"Don't output passwords to log", OPT_PRIO | 1 },
{"show-password", o_bool, &hide_password,
"Show password string in debug log messages", OPT_PRIOSUB | 0},
{"pap-restart", o_int, &upap[0].us_timeouttime,
"Set retransmit timeout for PAP", OPT_PRIO },
{"pap-max-authreq", o_int, &upap[0].us_maxtransmits,
"Set max number of transmissions for auth-reqs", OPT_PRIO },
{"pap-timeout", o_int, &upap[0].us_reqtimeout,
"Set time limit for peer PAP authentication", OPT_PRIO },
{NULL }
};
Ppp 是通过串口来通信的,所以设置通信通道是ppp很重要的一部分,这部分的代码在tty.c中实现,主要通过下面这个结构中的几个函数来实现。
/*
*This struct contains pointers to a set of procedures for
*doing operations on a "channel". A channel provides a way
* tosend and receive PPP packets - the canonical example is
* aserial port device in PPP line discipline (or equivalently
*with PPP STREAMS modules pushed onto it).
*/
struct channel {
/*set of options for this channel */
option_t*options;
/*find and process a per-channel options file */
void(*process_extra_options) __P((void));
/*check all the options that have been given */
void(*check_options) __P((void));
/*get the channel ready to do PPP, return a file descriptor */
int (*connect) __P((void));
/*we're finished with the channel */
void(*disconnect) __P((void));
/*put the channel into PPP `mode' */
int (*establish_ppp) __P((int));
/*take the channel out of PPP `mode', restore loopback if demand */
void(*disestablish_ppp) __P((int));
/*set the transmit-side PPP parameters of the channel */
void(*send_config) __P((int, u_int32_t, int, int));
/*set the receive-side PPP parameters of the channel */
void(*recv_config) __P((int, u_int32_t, int, int));
/*cleanup on error or normal exit */
void(*cleanup) __P((void));
/*close the device, called in children after fork */
void(*close) __P((void));
};
状态机的回调函数,具体会在每个协议处理模块内实现,状态机在处理ppp连接建立过程时通过状态机调用具体的协议处理模块,完成协议处理过程。
typedef struct fsm_callbacks {
void (*resetci) /* Resetour Configuration Information */
__P((fsm*));
int (*cilen) /* Length of our ConfigurationInformation */
__P((fsm*));
void (*addci) /* Add ourConfiguration Information */
__P((fsm*, u_char *, int *));
int (*ackci) /* ACK our ConfigurationInformation */
__P((fsm*, u_char *, int));
int (*nakci) /* NAK our ConfigurationInformation */
__P((fsm*, u_char *, int, int));
int (*rejci) /* Reject our ConfigurationInformation */
__P((fsm*, u_char *, int));
int (*reqci) /* Request peer's ConfigurationInformation */
__P((fsm*, u_char *, int *, int));
void (*up) /*Called when fsm reaches OPENED state */
__P((fsm*));
void (*down) /* Calledwhen fsm leaves OPENED state */
__P((fsm*));
void (*starting) /*Called when we want the lower layer */
__P((fsm*));
void (*finished) /*Called when we don't want the lower layer */
__P((fsm*));
void (*protreject) /*Called when Protocol-Reject received */
__P((int));
void (*retransmit) /*Retransmission is necessary */
__P((fsm*));
int (*extcode) /*Called when unknown code received */
__P((fsm*, int, int, u_char *, int));
char *proto_name; /*String name for protocol (for messages) */
} fsm_callbacks;
整个程序的主体实现是从主函数的LCP_OPEN()开始的,在这个函数里,调用了有限状态机FSM_OPEN(),而在FSM_OPEN()中,callback指针指向了starting,于是就到了LCP_STARTING()函数来实现一个OPEN事件从而使得PPP状态准备从DEAD到ESTABLISHED的转变。接下来,回到主函数,下面一步是调用START_LINK(),在此函数中会把一个串口设备作为PPP的接口,并把状态转变为ESTABLISHED,然后调用lcp_lowerup()来告诉上层底层已经UP,lcp_lowerup()中调用FSM_LOWERUP()来发送一个configure-request请求,再把当前状态设置为REQSENT状态,至此,第一个LCP协商的报文已经发送出去。
接下来的流程实现主要就是在这个while循环中实现了。之前说过了我们已经发送了第一个配置协商报文,所以handle_events()主要就是做等待接收数据包的时间处理了,在handle_events()里主要调用了两个函数一个是wait_input(),他的任务是等待并判断是否超时。还有一个是calltimeout()他主要是做超时的处理。当等待并未超时而且有数据包过来,则调用整个PPPD中最重要的函数get_input()函数。他主要接收过来的数据包并做出相应的动作。接下来就get_input()函数进行详细的说明,首先对包进行判断,丢弃所有不在LCP阶段和没有OPENED状态的包,然后protop指针指向当前协议的input函数。于是就进入了LCP_INPUT(),同理LCP_INPUT()调用了FSM_INPUT()对收到的包进行代码域的判断,判断收到的是什么包。假设比较顺利,我们收到的是CONFACK的包,于是调用fsm_rconack()函数,在此函数中根据当前自身的状态来决定下一步的状态如何改变,这里我们假设也很顺利,已经发送完了configure-ack,因此我们把FSM当前状态变成了OPENED状态,并把callback指针指向UP.所以我们马上就调用LCP_UP()在那里我们又调用了link_established()函数来进入认证的协商,或者如果没有认证则直接进入网络层协议。当然这里我们还是要认证的所有在LINK_ESTABLISHED()里我们选择是利用何种认证方式是PAP,还是EAP,还是CHAP.假设我们这里采用CHAP而且是选择CHAP WITH PEER,意思是等待对端先发送CHALLENGE挑战报文。于是我们又调用了chap_auth_peer()函数,并等待接收挑战报文。于是从新又来到handle_events()等待接收。再利用get_input()来接收包,在get_input()里这次调用chap_input(),再调用FSM_INPUT(),在那里我们再对包的代码域进行判断,这次判断出是CHAP_CHALLENGE包,则我们要调用chap_respond()函数来回应对端,继续等待对方的报文,再次利用CHAP_INPUT(),FSM_INPUT()来判断,如果是SUCCESS,则调用chap_handle_status(),在这个函数里调用auth_withpeer_success函数,从而进入网络层阶段,调用network_phase()函数。网络层的互动是从start_networks()开始的,如果在网络层阶段同时有CCP协议(压缩控制协议)则进行压缩控制协议的协商,然后再进入正式的IPCP的协商,而IPCP的协商主要也是通过protop指针指向IPCP_OPEN()开始的。而IPCP_OPEN()则是调用了FSM_OPEN(),在这里,首先发送一个configure-request包,然后和之前一样等待接收。经过几个交互后最后调用NP_UP()完成网络层的协商,至此PPP链路可以承载网络层的数据包了。
(六)pppd程序接受数据过程
Example: get_input()
read_packet (unsigned char *buf) //get a PPP packet from the serial device
read(ppp_fd, buf, len);
(*protp->input)(0, p, len);-------》
lcp_input(unit, p, len)
fsm_input(f, inpacket, l)
(七)pppd程序发送数据过程
Example: start_link(unit)
lcp_lowerup(0);
fsm_lowerup(f)
fsm_sdata(f, code, id, data, datalen)
output (int unit, unsigned char *p, int len)
write(fd, p, len)
get_input()
lcp_input(unit, p, len)
fsm_input(f, inpacket, l)
fsm_rconfreq(f, id, inp, len);
fsm_sdata(f, code, id, data, datalen)
output (int unit, unsigned char *p, int len)
write(fd, p, len)