PPP協議體系的實現


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

    Pppoe協議的實現在協議棧中,且其底層有實際的物理設備(或者vlan設備)支持,關鍵就在於pppoe協議可以直通應用層(如上一篇所講),也可以半途連接ppp0設備。而ppp協議的實現主要在設備ppp0的驅動中,這是一個虛擬設備,沒有物理設備的支持,且保持對上層是透明。

1.代碼概述

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

    在內核中,對此的支持主要也有兩部分,一個是ppp模塊(注意這不是最終的ppp網絡設備,而是一個字符設備,主要對構建維護ppp連接提供支持),另一部份是pppoe協議模塊(其實還包括裸packet協議模塊),如下圖左所示:

    如上圖右所示,是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由一個特殊的結構來描述,如下圖右所示:

上述的兩個選項對應的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()函數來處理。

    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的關鍵:

該函數首先利用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設備的傳輸通道,如下圖所示。

    "/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。

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

4.補充PPPOE協議本身

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

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

5.收發代碼細節

    PPP協議的的數據包結構如第4節圖所示,其中pppoe的負載即為PPP協議頭+PPP數據,而PPP數據可以是一個完整的通用協議包,如IP包。本文開頭處的圖很好地描述了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發送流程

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

    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。

    數據包格式如下圖所示:

5.2接收流程

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

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

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

6.總結

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM