1. 程式人生 > >ppp 完全理解(二)【轉】

ppp 完全理解(二)【轉】

轉自: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)