前言:
PPP(Point to Point Protocol)协议是一种广泛使用的数据链路层协议,在国内广泛使用的宽带拨号协议PPPoE其基础就是PPP协议,此外和PPP相关的协议PPTP,L2TP也常应用于VPN虚拟专用网络。随着智能手机系统Android的兴起,PPP协议还被应用于GPRS拨号,3G/4G数据通路的建立,在嵌入式通信设备及智能手机中有着广泛的应用基础。本文主要分析Linux中PPP协议实现的关键代码和基本数据收发流程,对PPP协议的详细介绍请自行参考RFC和相关协议资料。
模块组成:
上图为PPP模块组成示意图,包括:
PPPD:PPP用户态应用程序。
PPP驱动:PPP在内核中的驱动部分,kernel源码在/drivers/net/下的ppp_generic.c, slhc.c。
PPP线路规程*:PPP TTY线路规程,kernel源码在/drivers/net/下的ppp_async.c, ppp_synctty.c,本文只考虑异步PPP。
TTY核心:TTY驱动,线路规程的通用框架层。
TTY驱动:串口TTY驱动,和具体硬件相关,本文不讨论。
说明:本文引用的pppd源码来自于android 2.3源码包,kernel源码版本为linux-2.6.18。
Linux中PPP实现主要分成两大部分:PPPD和PPPK。PPPD是用户态应用程序,负责PPP协议的具体配置,如MTU、拨号模式、认证方式、认证所需用户名/密码等。 PPPK指的是PPP内核部分,包括上图中的PPP驱动和PPP线路规程。PPPD通过PPP驱动提供的设备文件接口/dev/ppp来对PPPK进行管理控制,将用户需要的配置策略通过PPPK进行有效地实现,并且PPPD还会负责PPP协议从LCP到PAP/CHAP认证再到IPCP三个阶段协议建立和状态机的维护。因此,从Linux的设计思想来看,PPPD是策略而PPPK是机制;从数据收发流程看,所有控制帧(LCP,PAP/CHAP/EAP,IPCP/IPXCP等)都通过PPPD进行收发协商,而链路建立成功后的数据报文直接通过PPPK进行转发,如果把Linux当做通信平台,PPPD就是Control Plane而PPPK是DataPlane。
在Linux中PPPD和PPPK联系非常紧密,虽然理论上也可以有其他的应用层程序调用PPPK提供的接口来实现PPP协议栈,但目前使用最广泛的还是PPPD。PPPD的源码比较复杂,支持众多类UNIX平台,里面包含TTY驱动,字符驱动,以太网驱动这三类主要驱动,以及混杂了TTY,PTY,Ethernet等各类接口,导致代码量大且难于理解,下文我们就抽丝剥茧将PPPD中的主干代码剥离出来,遇到某些重要的系统调用,我会详细分析其在Linux内核中的具体实现。
源码分析:
PPPD的主函数main:
第一阶段:
pppd/main.c -> main():
……
//PPPD中的状态机,目前是初始化阶段
/*
* Initialize magic number generator now so that protocols may
* use magic numbers in initialization.
*/
magic_init();
/*
* Initialize each protocol.
*/
NULL//protocols[]是全局变量的协议数组
//初始化协议数组中所有协议
/*
* Initialize the default channel.
*/
tty_init(); //channel初始化,默认就是全局的tty_channel,里面包括很多TTY函数指针
//解析/etc/ppp/options中的参数
//解析PPPD命令行参数
/* can no longer change device name */
/*
* Work out the device name, if it hasn't already been specified,
* and parse the tty's options file.
*/
//实际上是调用tty_process_extra_options解析TTY 参数
//检测/dev/ppp设备文件是否有效
no_ppp_msg);
}
/*
* Check that the options given are valid and consistent.
*/
check_options(); //检查选项参数
//检测系统参数,比如内核是否支持Multilink等
auth_check_options(); //检查认证相关的参数
#ifdef HAVE_MULTILINK
mp_check_options();
#endif
NULL
NULL)
//检查每个控制协议的参数配置
//实际上是调用tty_check_options检测TTY参数
……
/*
* Detach ourselves from the terminal, if required,
* and identify who is running us.
*/
updetach)