一、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
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:
/* Kill the child */
processPADT(i, &packet, len);
break;
case CODE_SESS:
/* Ignore SESS -- children will handle them */
break;
case CODE_PADO:
case CODE_PADS:
/* Ignore PADO and PADS totally */
break;
default:
/* Syslog an error */
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阶段的通信。