天天看點

PPP協定體系

​​PPP協定體系的實作​​

2013-05-02 22:05 by zmkeil, 1765 閱讀, 0 評論, ​​收藏​​, ​​編輯​​

    其實PPP不像是一種協定,而更像是一種應用,可以把它看成一個撥号上網的應用軟體,撥号成功後,本地主機就可以正常上網了,可以使用TCP/IP等協定,而完全感覺不到PPP的存在。而實際上PPP在網絡協定棧中增加了不少東西,但對上層透明。另外PPP一般需要底層工具來支援,如之前講的PPPoE。

PPP協定體系

    Pppoe協定的實作在協定棧中,且其底層有實際的實體裝置(或者vlan裝置)支援,關鍵就在于pppoe協定可以直通應用層(如上一篇所講),也可以半途連接配接ppp0裝置。而ppp協定的實作主要在裝置ppp0的驅動中,這是一個虛拟裝置,沒有實體裝置的支援,且保持對上層是透明。

1.代碼概述

    Ppp是一個應用程式,其代碼在ppp軟體包中,其中主體部分最終編譯成pppd檔案,另外還有很多支援部件(plugin),如pppoe、pppoa等,在子檔案夾plugin中,最終編譯成rp-pppoe.so、pppoa.so檔案。

    在核心中,對此的支援主要也有兩部分,一個是ppp子產品(注意這不是最終的ppp網絡裝置,而是一個字元裝置,主要對建構維護ppp連接配接提供支援),另一部份是pppoe協定子產品(其實還包括裸packet協定子產品),如下圖左所示:

PPP協定體系
PPP協定體系

    如上圖右所示,是pppd程式的main流程,首先是從argv(或者從file中)中獲得plugin資訊,并加載;然後執行一個for循環,主要是為了處理ppp連接配接意外中斷、或裝置暫時中斷的情況下,可以重新啟動連接配接;與ppp協定相關的部分是通過start_link(0)啟動一個ppp連接配接,然後執行while循環,處理各種ppp資訊;最後要說明的是,真正的資料通信是在建立的ppp連接配接中進行的,與這裡的pppd沒有關系,而ppp連接配接中的資訊卻可以發送到這裡的while循環中來,具體實作後面會描述。

    由于整個代碼比較複雜,沒有去詳細看,隻就其中一些關鍵點做一些了解。

2. plugin機制實作

    Ppp有很多種類型的plugin,如pppoe、pppoa等,這些plugin最終被編譯成多個xx.so檔案,并提供一個通用的struct channel{}接口,如下圖左所示。一般的pppd指令如下:

Pppd …… plugin rp-pppoe.so eth0 ……

和常見的指令結構一樣,由option+arg的方式構成,上述的指令中展現了兩個選項,一個是plugin,其參數為rp-pppoe.so,另一個是eth0,沒有參數。

    在pppd程式中,選項option由一個特殊的結構來描述,如下圖右所示:

PPP協定體系

上述的兩個選項對應的option_t結構分别為:

{"plugin",o_special,(void*)loadplugin,"load a plugin module into pppd",……}

{"device-name",o_wild,(void*)&PPPoEDevnameHook,"pppoe device name",……}

在option_from_args()函數中,主要通過parse_option()函數來處理args選項參數,其中首先利用find_option()函數來識别選項,并找到對應的option_t結構,一般采用name比對方法,如"plugin",有些則特殊,如"device-name"。然後調用process_option()函數來處理。

PPP協定體系

    Plugin選項對應的處理函數為loadplugin(),該函數利用标準庫函數dlopen(),打開plugin檔案rp-pppoe.so,然後再利用标準庫函數dlsym(),找到其中的plugin_init()函數。這種方法适用于這樣的情況:由多種動态連接配接庫供應用程式選擇,且每個動态庫(.so)中都定義了一個名稱相同的函數。Plugin_init()函數的處理很簡單,就是把自身庫特有的選項加入到全局選項表中去,上述的第二個選項{"device-name",……"pppoe device name"}就是此時加入的,當然不同的動态庫中的plugin_init()函數中,加入的選項各不相同。

    這樣就好了解第二個選項了,其處理函數是rp-pppoe.so中的私有函數PPPoEDevnameHook(arg,argv,1),該函數是加載pppoe-plugin的關鍵:

PPP協定體系

該函數首先利用socket的ioctl函數,判斷所給interface是否為以太網接口;然後将裝置名和pppoe_channel分别賦給pppd程式空間中的devname和the_channel;最後對PPPoE子產品進行一些簡單的初始化。

    這樣之後,pppd程式就可以利用pppoe子產品了,其它子產品也一樣。對于pppd而言,它不關心各種子產品實作的細節,而隻是調用各子產品提供的統一接口the_channel。

3.建立ppp連接配接通道

    前面講了,各種plugin子產品提供給pppd的調用接口都相同,隻是實作細節不一樣,下面主要以pppoe為例,描述如何建立一個ppp連接配接通道。回到第1節圖中的main()函數流程,可以看到建立連接配接是通過調用lcp_open(0),start_link(0)來完成的。具體細節就不看了,大緻的流程是:Lcp_open()會建立一個ppp_netdev,如ppp0,而start_link()函數則會建立一個pppoe的連接配接,作為ppp0裝置的傳輸通道,如下圖所示。

PPP協定體系

    "/dev/ppp"是一個字元裝置ppp_chrdev,其對應的核心代碼在ppp_generic.c中,對它的操作都通過标準的檔案操作ppp_file_ops來調用,其中ioctl操作中,PPPIOCNEWUNIT選項對應于建立一個新的網絡裝置net_device(ppp_netdev),該裝置的私有空間為一個struct ppp結構,而其網絡操作由ppp_netdev_ops來調用,這些特有的結構和操作決定了ppp_netdev裝置的功能與特性,後面還會較長的描述。

    Start_link()函數中,首先調用the_channel->connect()函數,對于PPPOE-plugin來說,就是建立一個PF_PPPOE的socket接口,并調用該socket的标準connect操作,該操作在前一篇的第3節中已描述,除了處理協定位址外,還有一個關鍵操作就是ppp_register_net_channel(po->chan)。然後繼續調用the_channel->establish_ppp(devfd),對于PPPOE-plugin,該函數為generic_establish_ppp(fd),它的操作很簡單,但很關鍵,就是調用ioctl(fd,PPPIOCGCHAN,…),該操作在前一篇的第3節已描述,就是設定socket插口的state|=BOUND,使得它接收到的資料包都轉交給ppp_netdev。

    經過一些列建立操作後,系統模型如下圖所示:

PPP協定體系

4.補充PPPOE協定本身

    對于一個協定,其最具代表性的就是它的幀結構,pppoe的幀結構如下圖所示:

PPP協定體系

聯系connect調用,需要使用者提供pppoeaddr(其中包括dev、sessionID、remoteMAC),才能建立一個可通信的socket連接配接,我們稱為pppoe的session連接配接。而怎麼獲得pppoeaddr呢?這其實是pppoe的發現階段,其實作一般采用PF_PACKET協定,自己設定裸資料包為pppoe-discovery格式,具體流程如下圖所示:

PPP協定體系

5.收發代碼細節

    PPP協定的的資料包結構如第4節圖所示,其中pppoe的負載即為PPP協定頭+PPP資料,而PPP資料可以是一個完整的通用協定包,如IP包。本文開頭處的圖很好地描述了PPP協定所需要做的工作,以及整個協定體系的構成、分布情況。

    首先給出整個收發流程的架構圖,如下:

PPP協定體系

    ppp子產品可以通過網絡接口或者檔案接口來操作,它們最終都通過底層pppoe_channel來完成資料通信,聯系第一節中main()函數流程中的while循環,它實際上就是通過檔案接口,來監控ppp連接配接。

    pppoe在協定棧中實作,但它用一個特殊的socket插口來服務于ppp,該socket(即第3節所述的connect插口)隻對pppd程式可見,且從不直接用它來發送資料,隻是利用它注冊的chan為ppp發送資料,而它收到的資料都通過ppp_input()遞交給ppp。這些并不會影響pppoe協定棧的工作,其他應用程式可以建立其它AF_PPPOE的socket插口,進行正常的pppoe通信。

5.1發送流程

    發送流程代碼如下圖所示:

PPP協定體系

    pppd程式通過對/dev/ppp字元裝置的操作,最終由ppp_chan實作與對端的資料通信,資料格式有ppp協定自己決定,主要用于維護ppp連接配接。

    主要看一下ppp的網絡裝置,它有自己獨立的net_device結構、IP位址等,對上層而言就像一個普通的裝置,IP協定中的路由可以選擇該裝置。該裝置的驅動程式是實作ppp協定的主要地方,它對skb進行push操作,添加ppp的協定頭,最後調用pppoe_chan将資料包發送出去。注意pppoe_chan_ops和pppoe_ops沒有太大差別,都要設定pppoe的協定頭,隻是pppoe_chan_ops接收到的skb是上層協定棧的,需要一個push操作,而pppoe_ops是自己配置設定skb。

    資料包格式如下圖所示:

PPP協定體系

5.2接收流程

    接收流程代碼如下圖所示:

PPP協定體系

    發送時逐層插入協定頭,而接收時,一般不需要删除協定頭(那樣需要額外的put(skb)操作),而僅改變skb->network/transport_header即可。為了實作對上層的透明,接收流程的最後,重新設定dev為該ppp0,proto為ppp負載資料的協定,然後調用netif_rx(skb)重新啟動接收流程,這樣該skb就仿佛是從ppp0裝置接收到的。

    整個過程中,資料包幾乎不變,僅是skb的相應字段改變,如上所述。

6.總結

    僅對ppp應用的工作流程做了簡單學習,了解了其實作的基本架構,對ppp協定本身沒有做深入學習。