一.协议初始化
主要包括下面的初始化动作
Phrase初始化,日志初始化,MagicNumber初始化(主要就是根据时间选取种子),各层协议初始化,然后进行串口的物理连接,在物理连接建立以后,就启动状态机,开始ppp协商。
二.pppd的数据结构与操作
在进行下面的研究之前需要了解几个重要的数据结构
1.struct protent
系统定义了该结构的数组,其中分层定义了lcp,pap,chap,ipcp的protent。这个结构主要包含了一些函数指针,每个函数都处理指定的情况(他们并不是回调),例如在底层up后,会调用lowerup指针指向的函数进行处理等,至于如何通知本层底层已经连接有另外的函数实现。
- struct protent *protocols[] = {
- &lcp_protent, &pap_protent, &chap_protent, &ipcp_protent, NULL
- };
这些函数指针的定义并非杂乱无章,它们分别对应了协议中指定的几种事件:收到数据包的事件,收到协议拒绝的数据包的事件(把它作为特殊的事件单独处理了),底层UP事件,底层DOWN事件,OPEN事件,CLOSE事件。
在进行上面的协议初始化的过程中,我们还遇到了struct fsm和fsm_callbacks的数据结构。
2.struct fsm
如注释所述,该结构和fsm_callbacks共同组成了状态机的数据结构。该结构中主要保存的是协商过程中状态机的一些状态信息,包括了协议中规定的需要记录的3个MAX值。同时给出在协商中出现特定事件时,需要通知上层或者下层的回调函数。这些回调函数由fsm_callbacks定义。
3.fsm_callbacks
协商中可能出现的特定事件包括RCR,RCA,RCN,RXJ等,该回调集合中还包括组装发送请求的函数,当本层达到或者离开OPENED状态时的回调函数(相当于协议中的tlu,tld操作)和启动、终止底层的函数(相当于协议中的tls,tlf操作)
三.ppp协商流程
除了上述三个结构以外,还有一组状态机函数
- 1. fsm_lowerup(f)
- 2. fsm_lowerdown(f)
- 3. fsm_open(f)
- 4. fsm_close(f, reason)
- 5. fsm_timeout(arg)
- 6. fsm_input(f, inpacket, l)
- 7. fsm_rconfreq(f, id, inp, len)
- 8. fsm_rconfack(f, id, inp, len)
- 9. fsm_rconfnakrej(f, code, id, inp, len)
- 10. fsm_rtermreq(f, id, p, len)
- 11. fsm_rtermack(f)
- 12. fsm_rcoderej(f, inp, len)
- 13. fsm_protreject(f)
- 14. fsm_sconfreq(f, retransmit)
- 15. fsm_sdata(f, code, id, data, datalen)
上面7-14的函数都在fsm_input中调用,而fsm_input函数在各层的处理UP事件的函数中调用,就是在struct protent结构中记录的input函数指针中调用。同样的,fsm_open,fsm_close,fsm_lowup,fsm_lowdown也都分别在对应该结构的open函数指针,close函数指针,lowup函数指针,lowdown函数指针中调用,即fsm的操作函数在各层注册的处理各种消息的函数中调用,而它们的作用就是按照协议要求,根据当前状态机的状态采取相应的动作,迁移状态机的状态。
整个ppp的协商由lcp_lowerup函数的启动而开始,该函数是在lcp_protent变量中绑定的用来处理底层UP时的处理函数,它调用了fsm_lowerup函数,将lcp状态机的状态由init迁移到closed,接着调用lcp_open函数,它调用了fsm_open函数,该函数发送了协商请求,从而启动了ppp选项协商。这时Phrase迁移到了ESTABLISH状态,程序进入了这样一个循环处理状态。
- while ( phase != PHASE_DEAD )
- {
- handle_events();
- get_input();
- if ( kill_link )
- lcp_close ( 0, "User request" );
- if ( open_ccp_flag )
- {
- if ( phase == PHASE_NETWORK || phase == PHASE_RUNNING )
- {
- ccp_fsm[0].flags = OPT_RESTART; /* clears OPT_SILENT */
- ( *ccp_protent.open ) ( 0 );
- }
- }
- } /* end while */
根据这个流程,我们可以大致猜想到各个函数的基本功能。Handle_events用来处理事件,get_input用来处理输入数据包,其中必定进行了状态机的转换和Phrase的转换。Kill_link是全局变量,初始化为0,一旦被置为1,表示用户请求断开连接,调用lcp_close函数来中断连接。让我们一个函数一个函数的进行分析。
Handle_events
该函数中有一个重要的函数调用wait_input(timeleft(&timo)),其中timeleft函数计算从当前时间到下次超时的时间,在wait_input函数中调用的select系统调用中传入的等待时间就是这段时间,select函数的作用是在指定的时间内,等待指定读写集合中的事件,如果等到了这样的事件,函数就退出,如果没有等到这样的事件,函数超时退出。这里,select等待的读写集合是一个全局变量in_fds,而该值在上述的循环外面已经通过调用add_fd(fd_ppp) 被赋值。add_fd函数就是把fd_ppp在in_fds集合中置位。fd_ppp是串口建立连接后生成的文件描述符,猜想是用来读写ppp帧的。这样,我们就知道wait_input函数的作用就是在下次超时事件发生之前,等待一个ppp帧。
除此之外,函数中还调用了calltimeout函数,主要用来处理超时,在下面的定时器的使用中,将会详细描述。
getinput
该函数是所有的精华所在,上面叙述的大部分函数和结构都将在该函数中使用。该函数首先读取一个ppp帧,然后进行判断,如果当前LCP没有处于open状态,那么抛弃所有非LCP包。接着还需要判断,在认证阶段,只能接收LCP包和认证协议包,其他协议包都将被抛弃。这时开始循环调用protocols变量中的各层协议的input函数指针。如果当前协议与某层协议相同就调用相应的input函数处理。最后LCP处于opened状态时,对系统不支持的协议包会发送Protocol_Rej包响应。
input
让我们看看input函数是如何运转ppp协议的。上面已经说明,我们已经发送了config_req包,我们等到了接收包后,把它送给LCP的input函数,即lcp_input,该函数调用fsm_input函数,这个函数会判断收到的数据包是config_req还是ack,还是nak等,并进一步调用相应的函数,在这些处理函数中,将根据状态机的当前状态对收到的数据包进行处理,并进行状态的迁移。我们看到,一旦数据包交给了各层的input函数,就相当于交给了状态机来处理。这样在几个交互后,lcp处于opened状态,这时会调用状态机的回调函数fsm_callbacks结构中的up函数通知上层,对于lcp来说是调用了lcp_up函数,在该函数中调用了link_established函数。该函数启动了认证协商过程,并把Phrase转换到了Authenticate阶段。又经过一个交互后认证成功,将在auth_withpeer_success函数中调用network_phase函数,把Phrase转换到了Network阶段,然后调用了ipcp_open函数,把ipcp的状态机由initial迁移到starting。奇怪的是,这样一来,只有等到新的数据包来后,才开始ipcp的选项协商,而并非像lcp那样,主动发送选项协商请求,而这与ppp日志中的记录不相符合,这里还需要进一步研究。
四.选项的协商
以LCP为例,它使用了lcp_options 结构,并定义了该结构的四个变量lcp_wantoptions,lcp_gotoptions,lcp_allowoptions,lcp_hisoptions,分别用来表示我们想要请求的选项,对方ack的选项,我们允许对方请求的选项,我们ack对方的选项。lcp_options结构中每个域的含义见附录。pppd根据lcp_wantoptions生成发送的选项请求,根据lcp_allowoptions决定接受还是拒绝接收到的选项请求。
pppd在lcp_init函数中对lcp_wantoptions和lcp_allowoptions进行赋值。在状态机的发送函数中,在当前状态不为REQSENT,ACKRCVD,ACKSENT的情况下,调用状态机的reset函数把lcp_wantoptions赋值给lcp_gotoptions来组装选项。选项的组装是靠状态机的回调函数指针addci完成的,对于LCP就是lcp_addci,组装好选项后发送数据是通过调用fsm_sdata实现的。
pppd把lcp选项根据参数的不同分成:CILEN_VOID,CILEN_CHAR,CILEN_SHORT,CILEN_CHAP,CILEN_LONG等几种方便选项的组装。另外,选项中的passive,restart标志位在lcp_open函数调用的时候,将会赋值给lcp状态机的flags。
五.定时器的使用
1. 定时器使用的数据结构
- struct callout
- {
- struct timeval c_time; /* time at which to call routine */
- void *c_arg; /* argument to routine */
- void ( *c_func ) __P ( ( void * ) ); /* routine */
- struct callout *c_next;
- };
在这个结构里存储了发生超时事件的时间点和超时时需要调用的函数,同时系统还定义了一个指向这样的结构的一个全局指针,它指向定时器链表的首部。
2. 定时器处理
1) timeout函数和untimeout函数
timeout函数用来在定时器链表中插入一个超时事件,这样会形成一个超时时间从小到大排列的链表。它被用一个宏TIMEOUT包裹,其他函数通过调用这个宏来插入超时事件。同样untimeout函数取消一个超时事件,它被另外一个宏UNTIMEOUT包裹被其他函数调用。
2) fsm_timeout
该函数总是作为timeout函数的参数,即发生超时事件时需要回调的函数出现的。它的作用是在发生超时时按照协议状态机的要求,根据当时所处的状态采取动作。
3) timeleft
返回当前到第一个超时事件之间的时间长度
4) calltimeout
前面在主流程中我们提到过这个函数,该函数用来检查超时链表中哪些节点超时了,并调用对应的回调函数。
这样我们就对定时器处理的过程比较清楚了:状态机在发送config-req、terminate-req或者其他需要定时的时候,在超时链表中插入一个超时事件。所有的超时事件都在主函数中同一进行处理。
六.Echo和ProtocolReject的处理
这两种code的数据包的处理与其他code不同,它们使用状态机回调函数中的extcode函数进行处理。
1.ProtocolReject的处理
如果LCP没有达到opened状态,该类型的包将会被抛弃,否则将会从该类型包中取出被拒绝的协议号,送给相应的协议处理。具体到LCP,如果收到了一个对LCP的ProtocolReject包(也就是协议中所谓的RXJ-),pppd调用lcp_protrej函数处理,该函数又调用了fsm_protreject函数处理,该函数根据状态机当前的状态,作出相应的处理和状态转换。显然,对于协议中所述的RXJ+的处理就是-抛弃,这也符合协议中状态机所述(保持原状态不变)。
2.Echo的处理
1)接收
如果接收到ECHOREQ和ECHOREP类型的包,和ProtocolReject包一样,都是在lcp状态机的回调函数extcode函数中处理。对于ECHOREQ,如果当前lcp不处于OPENED状态,不对该类型包处理。对ECHOREP,不论状态机处于什么状态,比较收到的magicnumber和我们记录的magicnumber,如果相等,就记录收到这样的包,否则可能说明发生了某种错误,将累计没有相应echo的计数器清零。
2)发送
在LCP状态机达到OPENED状态时,启动EchoReq的定时器,开始定时发送EchoReq;在LCP状态机离开OPENED状态时,停止EchoReq的发送。pppd使用一个全局变量lcp_echos_pending表示累计没有相应echo的次数。如果该值超过一个给定值,就以为着发生了某种错误,应该挂断lcp连接。
七.pppd的状态机
经过仔细的分析,pppd的状态机完全按照协议1548中所述实现,并对协议中提出的silent和restart选项提供了灵活的控制方式。
附录
1.struct
- struct protent
- {
- /* PPP protocol number */
- u_short protocol;
- /* Initialization procedure */
- void ( *init ) __P ( ( int unit ) );
- /* Process a received packet */
- void ( *input ) __P ( ( int unit, u_char *pkt, int len ) );
- /* Process a received protocol-reject */
- void ( *protrej ) __P ( ( int unit ) );
- /* Lower layer has come up */
- void ( *lowerup ) __P ( ( int unit ) );
- /* Lower layer has gone down */
- void ( *lowerdown ) __P ( ( int unit ) );
- /* Open the protocol */
- void ( *open ) __P ( ( int unit ) );
- /* Close the protocol */
- void ( *close ) __P ( ( int unit, char *reason ) );
- /* Print a packet in readable form */
- int ( *printpkt ) __P ( ( u_char *pkt, int len,
- void ( *printer ) __P ( ( void *, char *, ... ) ),
- void *arg ) );
- /* Process a received data packet */
- void ( *datainput ) __P ( ( int unit, u_char *pkt, int len ) );
- bool enabled_flag; /* 0 iff protocol is disabled */
- char *name; /* Text name of protocol */
- char *data_name; /* Text name of corresponding data protocol */
- option_t *options; /* List of command-line options */
- /* Check requested options, assign defaults */
- void ( *check_options ) __P ( ( void ) );
- /* Configure interface for demand-dial */
- int ( *demand_conf ) __P ( ( int unit ) );
- /* Say whether to bring up link for this pkt */
- int ( *active_pkt ) __P ( ( u_char *pkt, int len ) );
- };
2.
- /*
- * Each FSM is described by an fsm structure and fsm callbacks.
- */
- typedef struct fsm
- {
- int unit; /* Interface unit number */
- int protocol; /* Data Link Layer Protocol field value */
- int state; /* State */
- int flags; /* Contains option bits */
- u_char id; /* Current id */
- u_char reqid; /* Current request id */
- u_char seen_ack; /* Have received valid Ack/Nak/Rej to Req */
- int timeouttime; /* Timeout time in milliseconds */
- int maxconfreqtransmits; /* Maximum Configure-Request transmissions */
- int retransmits; /* Number of retransmissions left */
- int maxtermtransmits; /* Maximum Terminate-Request transmissions */
- int nakloops; /* Number of nak loops since last ack */
- int rnakloops; /* Number of naks received */
- int maxnakloops; /* Maximum number of nak loops tolerated */
- struct fsm_callbacks *callbacks; /* Callback routines */
- char *term_reason; /* Reason for closing protocol */
- int term_reason_len; /* Length of term_reason */
- } fsm;
3.
- typedef struct fsm_callbacks
- {
- void ( *resetci ) /* Reset our Configuration Information */
- __P ( ( fsm * ) );
- int ( *cilen ) /* Length of our Configuration Information */
- __P ( ( fsm * ) );
- void ( *addci ) /* Add our Configuration Information */
- __P ( ( fsm *, u_char *, int * ) );
- int ( *ackci ) /* ACK our Configuration Information */
- __P ( ( fsm *, u_char *, int ) );
- int ( *nakci ) /* NAK our Configuration Information */
- __P ( ( fsm *, u_char *, int ) );
- int ( *rejci ) /* Reject our Configuration Information */
- __P ( ( fsm *, u_char *, int ) );
- int ( *reqci ) /* Request peer's Configuration Information */
- __P ( ( fsm *, u_char *, int *, int ) );
- void ( *up ) /* Called when fsm reaches OPENED state */
- __P ( ( fsm * ) );
- void ( *down ) /* Called when fsm leaves OPENED state */
- __P ( ( fsm * ) );
- void ( *starting ) /* Called when we want the lower layer */
- __P ( ( fsm * ) );
- void ( *finished ) /* Called when we don't want the lower layer */
- __P ( ( fsm * ) );
- void ( *protreject ) /* Called when Protocol-Reject received */
- __P ( ( int ) );
- void ( *retransmit ) /* Retransmission is necessary */
- __P ( ( fsm * ) );
- int ( *extcode ) /* Called when unknown code received */
- __P ( ( fsm *, int, int, u_char *, int ) );
- char *proto_name; /* String name for protocol (for messages) */
- } fsm_callbacks;
4.
- /*
- * The state of options is described by an lcp_options structure.
- */
- typedef struct lcp_options
- {
- bool passive; /* Don't die if we don't get a response */
- bool silent; /* Wait for the other end to start first */
- bool restart; /* Restart vs. exit after close */
- bool neg_mru; /* Negotiate the MRU? */
- bool neg_asyncmap; /* Negotiate the async map? */
- bool neg_upap; /* Ask for UPAP authentication? */
- bool neg_chap; /* Ask for CHAP authentication? */
- bool neg_eap; /* Ask for EAP authentication? */
- bool neg_magicnumber; /* Ask for magic number? */
- bool neg_pcompression; /* HDLC Protocol Field Compression? */
- bool neg_accompression; /* HDLC Address/Control Field Compression? */
- bool neg_lqr; /* Negotiate use of Link Quality Reports */
- bool neg_cbcp; /* Negotiate use of CBCP */
- bool neg_mrru; /* negotiate multilink MRRU */
- bool neg_ssnhf; /* negotiate short sequence numbers */
- bool neg_endpoint; /* negotiate endpoint discriminator */
- int mru; /* Value of MRU */
- int mrru; /* Value of MRRU, and multilink enable */
- u_char chap_mdtype; /* which MD types (hashing algorithm) */
- u_int32_t asyncmap; /* Value of async map */
- u_int32_t magicnumber;
- int numloops; /* Number of loops during magic number neg. */
- u_int32_t lqr_period; /* Reporting period for LQR 1/100ths second */
- struct epdisc endpoint; /* endpoint discriminator */
- } lcp_options;