PPP完全理解(三)

内核ppp协议处理模块代码分析

作者:李圳均

日期:2013/11/27

点对点协议(PPP)为在点对点连接上传输多协议数据包提供了一个标准方法。ppp 位于数据链路层,是一种为同等单元之间传输数据包这样的简单链路设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据包。

PPP 最初设计是为两个对等节点之间的 IP 流量传输提供一种封装协议。在 TCP-IP 协议集中它是一种用来同步调制连接的数据链路层协议(OSI 模式中的第二层),替代了原来非标准的第二层协议,即 SLIP。除了 IP 以外 PPP 还可以携带其它协议,包括 DECnet 和 Novell 的Internet 网包交换(IPX)。。设计目的主要是用来通 过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案。

在内核中的ppp协议处理模块,其基本功能是发送和接收IP数据报、IP数据报的封装,解封、数据报的分割与重组,其数据帧格式在文档二中有详细说明,这里再次贴出ppp的数据帧格式,说明一下ppp协议处理模块对这部分的处理。从文档二可知,pppd已实现了ppp策略性的内容,包括所有鉴权、压缩/解压和加密/解密等扩展功能的控制协议,在ppp协议处理模块,会完成从IP协议层下发的IP数据的发送并把从串口收到IP数据报发送给IP协议层。Pppd的协议数据也会通过ppp封装后与网络进行协商。

(一)PPP驱动程序的基本原理

PPP 协议之下是以太网和串口等物理层,之上是IP协议等网络层。这里,对于下层,我们只讨论串口的情况,对于上层,我们只讨论TCP/IP的情况。发送时, TCP/IP数据包经过PPP打包之后经过串口发送。接收时,从串口上来的数据经PPP解包之后上报给TCP/IP协议层。

pppd是一个后台服务进程(daemon),是一个用户空间的进程,所以把策略性的内容从内核的PPP协议处理模块移到pppd中是很自然的事了。pppd实现了所有鉴权、压缩/解压和加密/解密等扩展功能的控制协议。

在移动终端向监控中心发送定位信息的过程中,移动终端上的 GPRS 通信程序通过 socket 接口发送 TCP/IP 数据包,内核根据 IP 地址和路由表,找到 PPP 网络接口,然后调用函数 ppp_start_xmit( ),此时控制权就转移到了 PPP 协议模块。函数 ppp_start_xmit( ) 调用函数 ppp_xmit_process( ) 去发送队列中的所有数据包,而函数ppp_xmit_process( ) 会进一步调用函数 ppp_send_frame( ) 去发送单个数据包。函数 ppp_send_frame( ) 根据前面 pppd 对 PPP 协议模块的设置调用压缩等扩展功能之后,又经函数 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( )(定义在低层驱动程序中)把数据发送到串口 2(GPRS 通信模块接在串口 2 上)。

  ppp_async.c 在初始化时(ppp_async_init),调用函数 tty_register_ldisc( ) 向 tty 注册了行规程 N_PPP 的处理接口,也就是一组回调函数。在移动终端接收监控中心指令的过程中,当 GPRS 通信模块收到数据时,就会回调 N_PPP 行规程中的函数 ppp_asynctty_receive( ) 来接收数据。函数ppp_asynctty_receive( ) 调用函数 ppp_async_input( ) 把数据 buffer 转换成 sk_buff,并放入接收队列 ap->rqueue 中。ppp_async 另外有一个 tasklet(ppp_async_process)专门处理接收队列 ap->rqueue 中的数据包,ppp_async_process 一直挂在接收队列 ap->rqueue 上,一旦被唤醒,它就调用函数 ppp_input( ) 让 PPP 协议模块处理该数据包。在函数 ppp_input( ) 中,数据被分成两路,一路是协议控制数据包,放入队列 pch->file.rqb 中,交给 pppd 处理。另外一路是用户数据包,经函数 ppp_do_recv( )、ppp_receive_frame( ) 进行 PPP 协议相关的处理后,再由函数 netif_rx( ) 提交给上层的 TCP/IP 协议模块进行处理,最后经 socket 接口传递给应用层的 GPRS 通信程序。

=====================
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帧编码后写入终端设备.
在配置了多链路PPP时(CONFIG_PPP_MULTILINK),多个PPP传输通道可连接到同一PPP接口单元.
PPP接口单元将PPP帧分割成若干个片段传递给不同的PPP传输通道, 反之,
PPP传输通道接收到的PPP帧片段被PPP接口单元重组成完整的PPP帧.

3) 在Linux-2.4中, 应用程序可通过字符设备/dev/ppp监控内核PPP驱动程序.
用户可以用ioctl(PPPIOCATTACH)将文件绑定到PPP接口单元上, 来读写PPP接口单元的输出帧,
也可以用ioctl(PPPIOCATTCHAN)将文件绑定到PPP传输通道上, 来读写PPP传输通道的输入帧.

4) PPP传输通道用channel结构描述, 系统中所有打开的传输通道在all_channels链表中.
PPP接口单元用ppp结构描述, 系统中所有建立的接口单元在all_ppp_units链表中.
当终端设备的物理链路连接成功后, 用户使用ioctl(TIOCSETD)将终端切换到PPP规程.
PPP规程初始化时, 将建立终端设备的传输通道和通道驱动结构. 对于异步PPP规程来说,
通道驱动结构为asyncppp, 它包含通道操作表async_ops.
传输通道和接口单元各自包含自已的设备文件(/dev/ppp)参数结构(ppp_file).

/dev/ppp

设备文件/dev/ppp。通过read系统调用,pppd可以读取PPP协议处理模块的数据包,当然,PPP协议处理模块只会把应该由pppd处理的数据包发给pppd。通过write系统调用,pppd可以把要发送的数据包传递给PPP协议处理模块。通过ioctrl系统调用,pppd可以设置PPP协议的参数,可以建立/关闭连接。在pppd里,每种协议实现都在独立的C文件中,它们通常要实现protent接口,该接口主要用于处理数据包,和fsm_callbacks接口,该接口主要用于状态机的状态切换。数据包的接收是由main.c:get_input统一处理的,然后根据协议类型分发到具体的协议实现上。而数据包的发送则是协议实现者根据需要调用output函数完成的。

staticconst struct file_operations ppp_device_fops= {

.owner          =THIS_MODULE,

.read             =ppp_read,

.write             =ppp_write,

.poll        =ppp_poll,

.unlocked_ioctl     = ppp_ioctl,

.open            =ppp_open,

.release  =ppp_release

};

()ppp_init(void)

err= register_chrdev(PPP_MAJOR,"ppp", &ppp_device_fops);

()ppp_async_init(void)

tty_register_ldisc(N_PPP,&ppp_ldisc);

(二)ppp相关数据结构

struct ppp{

structppp_file file;            /* stuff for read/write/poll 0 */

structfile *owner;            /* file that owns this unit 48 */

struct list_head channels;       /* list of attached channels 4c */

int              n_channels;     /* how many channels are attached 54 */

spinlock_t         rlock;                  /*lock for receive side 58 */

spinlock_t         wlock;                /*lock for transmit side 5c*/

int              mru;                   /* max receive unit 60 */

unsignedint     flags;                  /* control bits 64 */

unsignedint     xstate;               /* transmit state bits 68 */

unsignedint     rstate;               /* receive state bits 6c */

int              debug;               /* debug flags 70 */

structslcompress *vj;               /* state forVJ header compression */

enumNPmode          npmode[NUM_NP];          /* what to do with each net proto 78*/

structsk_buff  *xmit_pending;        /* a packet ready to go out 88 */

structcompressor *xcomp;    /* transmit packetcompressor 8c */

void           *xc_state;        /* its internal state 90 */

structcompressor *rcomp;     /* receive decompressor94 */

void           *rc_state;        /* its internal state 98 */

unsignedlong  last_xmit;         /* jiffies when last pkt sent 9c */

unsignedlong  last_recv;         /* jiffies when last pkt rcvd a0 */

struct net_device *dev;            /* network interface device a4 */

int              closing;    /* is device closing down? a8 */

#ifdef CONFIG_PPP_MULTILINK

int              nxchan;             /* next channel to send something on */

u32            nxseq;                /* next sequence number to send */

int              mrru;                 /* MP: max reconst. receive unit */

u32            nextseq;  /* MP: seq no of next packet */

u32            minseq;             /* MP: min of most recent seqnos */

structsk_buff_head mrq;        /* MP: receivereconstruction queue */

#endif /* CONFIG_PPP_MULTILINK */

#ifdef CONFIG_PPP_FILTER

structsock_filter *pass_filter;         /*filter for packets to pass */

structsock_filter *active_filter;/* filter for pkts to reset idle */

unsignedpass_len, active_len;

#endif /* CONFIG_PPP_FILTER */

struct net         *ppp_net;         /* the net we belong to */

};

struct channel{

struct ppp_file file;            /*stuff for read/write/poll */

structlist_head list;                  /* linkin all/new_channels list */

structppp_channel *chan;      /* public channeldata structure */

structrw_semaphore chan_sem;   /* protects`chan' during chan ioctl */

spinlock_t         downl;               /*protects `chan', file.xq dequeue */

struct ppp         *ppp;                 /* ppp unit we're connected to */

structnet         *chan_net;       /* the net channel belongs to */

structlist_head clist;                /* link inlist of channels per unit */

rwlock_t  upl;            /*protects `ppp' */

#ifdef CONFIG_PPP_MULTILINK

u8              avail;                  /* flag used in multilink stuff */

u8              had_frag;          /* >= 1 fragments have been sent */

u32            lastseq;    /* MP: last sequence # received */

int     speed;                 /*speed of the corresponding ppp channel*/

#endif /* CONFIG_PPP_MULTILINK */

};

struct ppp_file {

enum{

INTERFACE=1,CHANNEL

}                 kind;

struct sk_buff_head xq;            /*pppd transmit queue */      /*传输队列*/

struct sk_buff_head rq;            /*receive queue for pppd */    /*发送队列*/

wait_queue_head_trwait;      /* for poll on reading/dev/ppp */

atomic_t  refcnt;               /*# refs (incl /dev/ppp attached) */

int              hdrlen;               /* space to leave for headers */

int              index;                 /* interface unit / channel number */

int              dead;                 /* unit/channel has been shut down */

};

struct ppp_channel {

void           *private;  /* channel private data */

struct ppp_channel_ops *ops;         /* operations for this channel */

int              mtu;                   /* max transmit packet size */

int              hdrlen;               /* amount of headroom channel needs */

void           *ppp;                 /*opaque to channel */

int              speed;               /* transfer rate (bytes/second) */

/*the following is not used at present */

int              latency;    /* overhead time in milliseconds */

};

struct ppp_channel_ops {

/*Send a packet (or multilink fragment) on this channel.

Returns 1 if it was accepted, 0 if not. */

int    (*start_xmit)(struct ppp_channel *, struct sk_buff *);

/*Handle an ioctl call that has come in via /dev/ppp. */

int    (*ioctl)(struct ppp_channel *, unsigned int, unsigned long);

};

static struct ppp_channel_ops async_ops = {

ppp_async_send,

ppp_async_ioctl

};

(三)ppp内核发送数据过程

应用程序通过socket 接口发送TCP/IP数据包,这些TCP/IP数据包如何流经PPP协议处理模块,然后通过串口发送出去呢?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_start_xmit(struct sk_buff *skb, structnet_device *dev)

ppp_xmit_process(struct ppp *ppp)

ppp_push(struct ppp *ppp)

pch->chan->ops->start_xmit(pch->chan,skb)

ppp_async_send(struct ppp_channel *chan,struct sk_buff *skb)

ppp_async_push(struct asyncppp *ap)

tty->ops->write(tty, ap->optr,avail)

(四)ppp内核接受数据过程

接收数据的情形又是如何的?ppp_async.c在初始化(ppp_async_init),调用tty_register_ldisc向tty注册了行规程处理接口,也就是一组回调函数,当串口tty收到数据时,它就会回调ppp_ldisc的

ppp_asynctty_receive函数接收数据。ppp_asynctty_receive调用ppp_async_input把数据buffer转换成sk_buff,并放入接收队列ap->rqueue中。ppp_async另外有一个tasklet(ppp_async_process)专门处理接收队列ap->rqueue中的数据包,ppp_async_process一直挂在接收队列ap->rqueue上,一旦被唤醒,它就调用ppp_input函数让PPP协议处理模块处理该数据包。

在ppp_input函数中,数据被分成两路,一路是控制协议数据包,放入pch->file.rqb队列,交给pppd处理。另外一路是用户数据包,经ppp_do_recv/ppp_receive_frame进行PPP处理之后,再由netif_rx提交给上层协议处理,最后经 socket传递到应用程序。

ppp_asynctty_receive(struct tty_struct*tty, const unsigned char *buf,

char *cflags, int count)

ppp_async_input(ap, buf, cflags, count);

ppp_async_process(unsigned long arg)

ppp_input(struct ppp_channel *chan, structsk_buff *skb)

{

if (!pch->ppp || proto >= 0xc000 ||proto == PPP_CCPFRAG) {

/*put it on the channel queue */

skb_queue_tail(&pch->file.rq, skb);   //是控制协议数据包,放入pch->file.rqb队列,交给pppd处理。

/*drop old frames if queue too long */

while(pch->file.rq.qlen > PPP_MAX_RQLEN

&& (skb =skb_dequeue(&pch->file.rq)))

kfree_skb(skb);

wake_up_interruptible(&pch->file.rwait);

}

else {

ppp_do_recv(pch->ppp, skb, pch);  //进行PPP处理之后,再由netif_rx提交给上层协议处理

}

}

PPP完全理解(三)相关推荐

  1. 网络知识入门,用户如何连接互联网,ADSL调制解调器的妙用,PPP上网的三种方式(十二)

    互联网与家庭网络的区别      互联网是一个遍布世界的巨大而复杂的系统,但其基本工作方式却出奇地简单.和家庭.公司网络一样,互联网也是通过路由器来转发包的, 而且路由器的基本结构和工作方式也并没有什 ...

  2. ppp 完全理解(一)

    ppp 完全理解(一) ppp 协议简介.功能及组成分析 作者:李圳均 日期:2013/11/27 简介: 正式介绍前,分析两个名词:ppp.pppd,在调试中,这两个名字经常出现,ppp(Point ...

  3. ElasticSearch 深入理解 三:集群部署设计

    ElasticSearch 深入理解 三:集群部署设计 ElasticSearch从名字中也可以知道,它的Elastic跟Search是同等重要的,甚至以Elastic为主要导向. Elastic即可 ...

  4. 一分钟理解三次握手和四次挥手

    注:三次握手和四次挥手本身并不复杂,但却可以从很多角度理解这两个过程,本文仅取一个比较有趣的视点解释 为什么要三次握手? 答:三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收 ...

  5. 通俗理解 三次握手四次挥手(老友依恋式)

    首先我们来看一张图 该图描述的意思: A:你好老哥,我要从你和你建立连接,向B服务端发送请求连接的请求,然后等待对面老哥的回答(其中包含自己要与他确认连接的同步序列字段:可以理解为,咱们两人的手机号码 ...

  6. 自己理解三叉树TernarySearchTrie

    花了差不多一天半的时间终于把一颗三叉树看完了,不过对于里面还有点疑惑,下面在代码里注释上了自己的理解,里面还存在一些疑问,欢迎理解的朋友们指出其中的错误,以及解答里面的疑问. package org. ...

  7. 3.12 CMMI3级——深入理解三个O(OPF OPD OT)

    3级出现了3个以O开头的PA: OPF. OPD.OT O就是Organizational(组织的)的第一个字母,在2级的时候还没有见到一个O开头的PA,3级就出现了3个,4级还会有1个,5级也会有1 ...

  8. 从TCP报文理解三次握手

    目录 TCP报文 Source Port(源端口)/Destination Port(目标端口) Sequence Number(拆包序列号) Acknowledge Number(回应序号) Dat ...

  9. 理解三次握手和四次挥手

    2019独角兽企业重金招聘Python工程师标准>>> TCP(Transmission Control Protocol)网络传输控制协议,是一种面向连接的.可靠的.基于字节流的传 ...

最新文章

  1. 分治策略解决幂乘问题
  2. 2019RPA财务行业峰会:财务数字化转型
  3. Maya mayapy.exe 安装 Cython,编译 pyd
  4. P2053 SCOI2007 修车,费用流好题
  5. 逻辑回归模型_联邦学习体系下——逻辑回归模型
  6. SQL中binary 和 varbinary的区别
  7. python3.7知识点汇总
  8. CISA 发布关于 Treck TCP/IP 栈中新漏洞的 ICS 安全公告
  9. 函数对象、对象、原型
  10. Spark学习内容介绍
  11. 5S管理卫生考评办法
  12. 教资缴费显示内部服务器错误,中小学教师资格考试网上支付常见问题
  13. python if嵌套/while嵌套/竞技叠杯
  14. 计算机原理学习(2)-- 存储器和I/O设备和总线
  15. 凌晨三点半,见证中国奥运体育代表团的第30和31块金牌!
  16. 综合素质计算机的知识考点,综合素质常识考题
  17. 如何解决DOSBox 0.74无法运行edit指令
  18. 【金猿案例展】中国中车——基于大数据的车辆运维预警监控系统项目
  19. C++的count函数
  20. 成功的经验 失败的教训

热门文章

  1. be careful
  2. 《面试补习》-熔断降级我学会了!
  3. vue class动态类名
  4. 搜狐畅游2019校招笔试题-游戏开发工程师(java)
  5. flutter练习项目 漫画客户端
  6. “网抑云”年度听歌报告:分享被隐藏的自己
  7. 家用激光投影将成未来发展趋势,当贝投影占据其50%销量份额
  8. 别人的灵敏度永远压不住枪?手把手教你怎么调节一套适合自己的刺激战场灵敏度
  9. 到底什么是“通感一体化”?
  10. SAP-跨模块知识-权限与角色(02)-角色制作的方式