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

圖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流程如下圖:
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階段的通信。