PPPOE的用户空间实现
一 终端
终端介绍
终端是一种字符型设备,他有多种类型,通常使用tty来简称各种类型的终端设备。看下面一副图展示了计算机系统和终端之间得联系。
终端驱动程式的主要功能是在程式和相关设备之间进行数据传递。在一个LINUX内核自身的内部,终端基本上包括两个主要的软件部分:设备驱动程式和行规则(见下图)。
设备驱动程式是个写到具体硬件上面的低级软件,该硬件允许计算机和终端进行交互。
行规则是执行一系列输入输出数据的逻辑处理,把一系列字符映射到其他的上面。
伪终端
伪终端(Pseudo Terminal)是成对的逻辑终端设备,在Linux系统上创建伪终端设备,使用了”pty master”方式,例如/dev/ptm3,他的对应端则会被自动地创建成/dev/pts/3。
伪终端从设备上的数据输入成为伪终端主设备的输入,反之亦然。
二 PPPOE 概述
PPPOE协议是基于PPP协议的协议,在PPPOE应用程式中并没有将PPP协议实现,PPP协议是由PPPD这个用户空间程式实现的,PPPOE程式只实现PPPOE协议部分代码,在适当的时候PPPD程式进行PPP协议。
在PPPOE用户程式和PPPOE服务器连接后,他将会建立一个PPP0设备,此设备是在PPPD程式中进行的,如果和服务器连接成功,他将会一直存在, 此设备就和正常的网卡设备相同,但他是个虚拟设备,经过此设备的数据发出去时还是从真实存在的网卡设备接口发送出去的,但这已在系统中做了转换。
三 PPPD
上文中说过PPPD是PPP协议的实现程式,PPP的实现由四部分组成
PPPD程式首先会进行一些初始化工作,读参数设置参数等,这部分工作非常重要,会在读代码时分析。
然后就开始走协议,代码流向见下:
main.c->main()
{
读参数
设置参数
从LCP协议开始,这是整个协议的开始部分,状态机开始发动了
lcp_lowerup(0);
lcp_open(0); /* Start protocol */
下面
while (phase != PHASE_DEAD) {
handle_events();
get_input();等待数据报文进入然后根据状态机处理报文
…………
…………
}
}
PPP协议和PPPOE有着重要的联系,不过PPP协议建立连接部分不是讨论重点,所以会着重讨论在pppoe连接成功后数据的报文流向,数据是怎么被处理的。
四 PPPOE
PPPOE代码分析
PPPOE主文件为PPPOE.c,入口函数为main()
在main函数中主要做了两件事情
PPPOE协议中的DISCOVER阶段,处理函数为discovery(),此函数做的动作就是建立一个RAW SOCKET,然后发送报文,接着等待回应,继续处理,最后成功后设置状态进入SESSION阶段,代码片断见下:
do {
。。。。。。。。。。。
sendPADI(conn);发送报文
conn->discoveryState = STATE_SENT_PADI;
waitForPADO(conn, timeout);等待回应
。。。。。。。。。。。
} while (conn->discoveryState == STATE_SENT_PADI);
do {
。。。。。。。。。。。。。
sendPADR(conn); 发送报文
conn->discoveryState = STATE_SENT_PADR;
waitForPADS(conn, timeout); 等待回应
。。。。。。。。。。。。。
} while (conn->discoveryState == STATE_SENT_PADR);
conn->discoveryState = STATE_SESSION;设置状态为SESSION,接下来进入SESSION阶段
PPPOE协议中的SESSION阶段,处理函数为session(),此函数做的动作是首先建立一个RAW SOCKET,然后从PPPD中读入数据,在加上PPPOE发送出去,再接受到数据发送到PPPD程式中,让PPPD进行处理,处理完后,就建立 PPPOE连接了,PPP0端口也建立了,并且从SERVER端分配到了IP。从上面的分析我们知道,PPPOE在这个阶段只是做了转发工作,在建立连接 后,他就一直做着转发的工作。
for (;;) {
。。。。。。。。。。。。。。。。。。。。。。。。。
从PPPD那边收到数据,然后转发出去
if (FD_ISSET(0, &readable)) {
if (conn->synchronous) {
syncReadFromPPP(conn, &packet);
} else {
asyncReadFromPPP(conn, &packet);
}
}
从网卡端口收到数据后通过,发送到PPPD中去
if (FD_ISSET(conn->sessionSocket, &readable)) {
do {
if (conn->synchronous) {
syncReadFromEth(conn, conn->sessionSocket, optClampMSS);
} else {
asyncReadFromEth(conn, conn->sessionSocket, optClampMSS);
}
} while (BPF_BUFFER_HAS_DATA);
}
}
PPPOE建立连接后数据报文走向
如果从eth收到PPPOE数据:
首先数据被PPPOE中RAW SOCKET数据接收,PPPOE解开到达数据包的PPPOE头,然后将数据传送到伪终端主设备,也就是传送到了伪终端的从设备,然后在内核中数据跑到了PPP0接口,这是个设备,他会通过驱动将这个包处理后往上层传送;
如果有数据要发送出去:
首先根据路由知道数据要发送的端口在PPP0设备端口,数据就到达了PPP0端口,PPP0将数据传送到伪终端从设备,也就是传送到了伪终端主设备,然后到达PPPOE,PPPOE程式打上PPPOE包头,然后通过RAW SOCKET从ETH口发送出去。
以上是PPP连接建立后数据报文的走向。
五 PPPOE实现中理解疑难
pppoe建立连接后协议栈协议栈如下
数据报文理应这样,报文下来后先经过PPP封装,然后再PPPOE封装,然后加上以太头出去,这样的处理既直接又高效,不过我们PPPOE用户空间程式上 面的分析画出来的数据流程图不是这样的,数据从内核中PPP0接口传送到从终端设备,再传送到PPPOE中打包,发出去,为什么不从PPP0中出来就到 PPPOE中去,封装PPPOE报文出去,多好的事情啊,但这里事实不是这样,在PPPOE内核空间实现中将会是上面说的最佳状态。在用户空间中,这样处 理是为了不破坏LINUX内核中原有的终端实现的接口,因为LINUX内核中是将串行设备作为终端设备来驱动的。
接下来讨论一下PPP0端口和从设备终端之间是怎么联系到一起的
1) ppp设备是指在点对点的物理链路之间使用PPP帧进行分组交换的内核网络接口设备,由于Linux内核将串行设备作为终端设备来驱动,于是引入PPP终 端规程来实现终端设备和PPP设备的接口. 根据终端设备的物理传输特性的不同,PPP规程分为异步规程(N_PPP)和同步规程(N_SYNC_PPP)两种, 对于普通串口设备使用异步PPP规程.
2) 在PPP驱动程式中, 每一tty终端设备对应于一条PPP传输通道(chanell),每一ppp网络设备对应于一个PPP接口单元(unit).从终端设备上接收到的数据流 通过PPP传输通道解码后转换成PPP帧传递到PPP网络接口单元,PPP接口单元再将PPP帧转换为PPP设备的接收帧. 反之, 当PPP设备发射数据帧时,发射帧通过PPP接口单元转换成PPP帧传递给PPP通道, PPP通道负责将PPP帧编码后写入终端设备.
从终端对应的文件描述符是ttyfd
ioctl(ttyfd, PPPIOCGCHAN, &chindex)
获取从终端的设备的CHANNEL
ppp_fd = open("/dev/ppp", O_RDWR);
ioctl(ppp_fd, PPPIOCATTCHAN, &chindex)
ppp_fd绑定到终端上去,就能读写终端上的输入了
PPP0端口和ppp_dev_fd绑定到一起了,我们通过ppp_dev_fd读写PPP0的输出了
ioctl(ppp_fd, PPPIOCCONNECT, &ifunit)是将ppp_fd和ifunit所对应的接口连接在一起,这样后终端和设备就连接到一起了,从PPP0端口出来的报文就发送到从 终端设备了,从终端设备上的数据也就传送到PPP0接口中去了