天天看點

PPPOE源碼分析

一、PPPOE協定簡介

1、Discovery階段

  此階段用來建立連接配接,當一個使用者主機想開始一個PPPoE會話時,首先必須進行發現階段以識别PPPoE Server的以太網MAC位址,并建立一個PPPoE會話辨別(Session ID)。

PPPOE源碼分析

圖1-1 Discovery階段的基本工作流程

如圖1-1所示, Discovery階段由四個步驟組成,下面将介紹它的基本工作流程。

  • PADI:如果要建立一條PPPoE連接配接,首先PPPoE用戶端就要以廣播的方式發送一個PADI(PPPoE Active Discovery Initiation)資料包,PADI資料包包括用戶端請求的服務。
  • PADO:當PPPoE伺服器收到一個PADI包之後,它會判斷自己是否能夠提供服務,如果能夠提供服務的話,就會向用戶端發送PADO(PPPoE Active Discovery Offer)資料包來進行回應。PADO資料包包括PPPoE伺服器名稱和與PADI資料包中相同的服務名。如果PPPoE伺服器不能為PADI提供服務,則不允許用PADO資料包響應。
  •  PADR:由于PADI是以廣播的形式發送出去的,PPPoE用戶端可能收到不止一個PADO資料包,它将審查所有接收到的PADO資料包并根據其中的伺服器名或所提供的服務選擇一個PPPoE伺服器,并向選中的伺服器發送PADR(PPPoE Active Discovery Request)資料包。PADR資料包包括用戶端所請求的服務。
  • PADS:當PPPoE伺服器收到用戶端發送的PADR包時,它就準備開始一個PPPoE會話,它為PPPoE會話建立一個唯一的PPPoE會話ID,并向用戶端發送PADS (PPPoE Active Discovery Session- confirmation)包作為響應。

  當發現階段正常結束後,通信的兩端都獲得會話辨別(Session ID)和對方的MAC位址,它們一起唯一定義一個PPPoE會話。

2、PPP會話階段

當PPPoE進入PPP會話階段後,用戶端和伺服器将進行标準的PPP協商,PPP協商通過後,資料通過PPP封裝發送。PPP封包作為PPPoE幀的淨荷被封裝在以太網幀内,發送到PPPoE鍊路的對端。Session ID必須是Discovery階段确定的ID,且在會話過程中保持不變,MAC位址必須是對端的MAC位址。

Rp_pppoe與ppp實作pppoe協定的過程

二、rp-pppoe代碼簡釋

1、首先,我們會運作一個pppoe-server的程式,該程式主要建立pppoe鍊路,處理各種pppoe包。其中有以下代碼:

for (i = 0; i<NumInterfaces; i++) {

       interfaces[i].eh = Event_AddHandler(event_selector,

                                       interfaces[i].sock,

                                       EVENT_FLAG_READABLE,

                                       InterfaceHandler,

                                       &interfaces[i]);

#ifdef HAVE_L2TP

       interfaces[i].session_sock = -1;

#endif

       if (!interfaces[i].eh) {

           rp_fatal("Event_AddHandler failed");

       }

    }

為每個實體接口綁定事件,當有資料包到來時就執行InterfaceHandler函數,該函數調用serverProcessPacket函數,處理收到的各種pppoe相關的包。

switch(packet.code) {

    case CODE_PADI:

       processPADI(i, &packet, len);

       break;

    case CODE_PADR:

       processPADR(i, &packet, len);

       break;

    case CODE_PADT:

       processPADT(i, &packet, len);

       break;

    case CODE_SESS:

       break;

    case CODE_PADO:

    case CODE_PADS:

       break;

    default:

       break;

    }

其中對PADR處理,調用processPADR函數,向用戶端發回一個PADS後,就調用PPPD:

sendPacket(NULL, sock, &pads, (int) (plen + HDR_SIZE));

startPPPD(cliSession);

rp-pppoe包中有個pppoe.c的檔案,這個是pppoe的用戶端,這個pppoe.c分兩個階段處理:discovery階段和session階段。Discovery階段,用戶端的pppoe跟伺服器的pppoe-server打交道(發送PADI,接收PADO,發送PADR,接收PADS),讓pppoe-server調用pppd建立ppp接口;session階段,伺服器在建立了ppp接口後,pppd根據協定會調用pppoe,生成一個伺服器端的pppoe(根據判斷,直接跳過discovery階段),跟遠端的pppoe進行session階段的通信。

Pppoe的main函數最後,調用了session函數,該函數實作session階段資料的讀寫操作:

session(&conn);

session函數調用兩個讀函數來進行資料的傳輸工作:

asyncReadFromPPP:從ppp接口讀取資料,并發送到以太網

asyncReadFromEth:從以太網讀取資料,并發送到ppp

ppp——用戶端pppoe——以太網——伺服器pppoe——ppp

Pppoe好像起到轉發的作用,其實際是打包與拆包。

Pppoe流程如下圖:

PPPOE源碼分析

2、現在說下pppd的過程

首先說說pppd的相關讀寫原理

應用程式通過socket 接口發送TCP/IP資料包,pppd在make_ppp_unit函數中 調用ioctrl(PPPIOCNEWUNIT)建立一個網絡接口(如ppp0),核心中的PPP協定子產品在處理PPPIOCNEWUNIT時,調用 register_netdev向核心注冊ppp的網絡接口,該網絡接口的傳輸函數指向ppp_start_xmit。

當應用程式發送資料時,核心根據IP 位址和路由表,找到ppp網絡接口,然後調用ppp_start_xmit函數,此時控制就轉移到PPP協定處理子產品了。ppp_start_xmit調 用函數ppp_xmit_process去發送隊列中的所有資料包,ppp_xmit_process又調用ppp_send_frame去發送單個資料 包,ppp_send_frame根據設定,調用壓縮等擴充處理之後,又經ppp_push調用pch->chan->ops-> start_xmit發送資料包。

pch ->chan->ops->start_xmit是具體的傳輸方式了,比如說對于序列槽發送方式,則是 ppp_async.c: ppp_asynctty_open中注冊的ppp_async_send函數,ppp_async_send經ppp_async_push函數調用 tty->driver->write把資料發送序列槽。

Ppp在調用tty時,會根據協定,調用其他協定腳本來進行封裝,pppd就是在這裡調用pppoe,讓其産生伺服器端的pppoe,把ppp封裝成pppoe包,與用戶端的pppoe進行session階段的通信。