本人工作与网关产品有关,经常会接触到pppoe协议,但对ppp和pppoe是如何实现的不甚了解。在网上查找相关的文章大多是描述ppp/pppoe协议的具体内容和数据流大的流程,而未过多的涉及内核中pppoe驱动和ppp驱动代码实现的细节,正好这段时间在看毛德操和胡希明老师的《Linux内核源代码情景分析》,看了内核中的终端驱动,遂决定仔细学习一下PPP/PPPOE驱动这一块的内容并将这段时间的收获记录下来,以便以后翻阅同时也与大家分享。
本文不会涉及ppp协议和pppoe协议本身,即不会讨论如LCP,PAP,CHAP,IPCP, PADI等这些报文的格式等内容,如果您仅仅想了解的是ppp和pppoe协议本身,那这篇文章并不适合你。
如果你想了解ppp和pppoe驱动,想了解ppp程序是怎么建立起ppp网络接口的,以及ppp网络接口如何将数据通过具体的网络接口(比如eth0)发送的,具体的网络接口(比如eth0)收到数据后是怎样将数据给ppp网络接口的那这篇文章正好适合你。
阅读本文时建议将各个部分的流程图先复制下来保存在一个文档中,然后对照着流程图来看代码。
ppp代码下载链接 http://mirrors.aliyun.com/ubuntu/pool/main/p/ppp/ppp_2.4.7.orig.tar.gz
Linux-2.4.0代码下载链接https://mirrors.edge.kernel.org/pub/linux/kernel/v2.4/linux-2.4.0.tar.gz
pppd命令
ppp代码创建ppp网络接口
pppoe驱动建立channel
ppp驱动建立ppp网络接口并与pppoe建立的channel关联
ppp网络接口和ppp程序收包过程
ppp程序和ppp网络接口发包过程
pppd plugin /usr/lib/rp-pppoe.so nic-pon0.30 linkname internet nodefaultroute noipdefault noauth default-asyncmap hide-password nodetach usepeerdns mtu 1492 mru 1492 noaccomp nodeflate nopcomp novj novjccomp user aaa lcp-echo-interval 20 lcp-echo-failure 10 nopersist
[ppp-2.4.7]main.c中
[ppp-2.4.7]options.c
[ppp-2.4.7]options.c 中general_options
the_channel->connect()为PPPOEConnectDevice,这个函数比较长我们分段来看
Linux代码
上图中proto[protocol]->create即为pppoe_create,因为协议为PX_PROTO_OE,在pppoe_init中注册
回到PPPOEConnectDevice函数(续1)
在openInterface中会创建socket并绑定接口,如下两图所示
创建socket成功后回到PPPOEConnectDevice函数会调用discovery()函数进行pppoe协议的交互
接着看PPPOEConnectDevice函数(续2),会调用connect函数
connect函数会调用内核中的pppoe_connect,这个函数包含在pppoe_ops中,在pppoe_create中设置到sock->ops,即与我们conn->sessionSocket相关的参数中,这一过程可回看上面。
ppp_register_channel函数会生成struct channel *pch,把pch->chan 设成 chan,并把pch放到all_channels中
回到ppp的auth.c的函数start_link中the_channel->establish_ppp()为generic_establish_ppp
需要注意函数中的fd有改变开始时fd为PPPOEConnectDevice函数的返回值conn->sessionSocket,对它调用ioctl 会走到linux的
pppox_ioctl函数,该函数返回PPPOEConnectDevice[ppp代码中的plugin.c]函数调用connect创建的channel的index
接着来看 generic_establish_ppp中对fd为"/dev/ppp"调用PPPIOCATTCHAN,make_ppp_unit和PPPIOCCONNECT的动作
调用ioctl会调用ppp_ioctl函数
PPPIOCATTCHAN时回去找channel,很显然就是找到之前pppoe在connect创建的那个channel
ppp中make_ppp_unit会调用PPPIOCNEWUNIT
通过网络接口ppp0(假设为这个名字)发送数据时就会调用ppp_start_xmit,后面再看这个函数。
generic_establish_ppp函数中接着会调用PPPIOCCONNECT,通过ppp_connect_channel就将pppoe创建的channel和
ppp网络设备联系在一起了
回到ppp的main函数中看ppp程序是如何接收ppp协议的数据的
内核接收数据过程
通过ppp_receive_nonmp_frame函数大家可以看到根据收到的数据包的协议是ppp控制包还是普通网络包比如ipv4包,走不同的分支,如果是ppp协议控制包就将其放到ppp->file.rq中,我们的ppp程序会收到这个包,如何是普通的网络包就通过netif_rx进入网络协议栈。
这个pch->chan->ops->start_xmit(pch->chan, skb)调用的是pppoe_xmit,接下来我们看为什么是pppoe_xmit
这个channel是通过pppoe套接字然后调用connect方法创建的,创建时po->chan.ops = &pppoe_chan_ops;所以start_xmit就是pppoe_xmit