ppp源码分析

一、概念

1.1 PPP:一种点到点的拨号协议,ppp支持TCP/IP和其他网络协议族,它比SLIP更新、更加强大;

1.2 LCP:链路控制协议,PPP用于建立、管理(协商)和终止拨号连接的协议;

1.3 NCP:网络控制协议,PPP与特定协议族交互所用的一组协议;

1.4 SLIP:串行线路接口协议,早起的基于TCP/IP的协议;

1.5 PAP:密码验证协议, 是 PPP 协议集中的一种链路控制协议,通过2次握手提供一种对等结点的建立认证的简单方法,这是建立在初始链路确定的基础上的;

1.6 CHAP:询问握手认证协议 (Challenge Handshake Authentication Protocol),通过3次握手周期性的校验对端的身份,可在初始链路建立完成时,在链路建立之后重复进行,通过递增改变的标识符和可变的询问值,可防止来自端点的重放攻击,限制暴露于单个攻击的时间;

1.7 IPCP:网际控制协议,网际协议控制协议(IPCP)是一个网络控制协议,用来在点对点协议连接上建立和配置网际协议。IPCP负责在点对点连接的两端配置、使能和去使能IP协议模块。IPCP使用与链路控制协议相同的报文交换机制。IPCP报文在PPP达到网络层协议阶段之前不会进行交互,任何在此阶段前收到的IPCP报文都应静默丢弃。

二、组件

1. ppp协议的链路控制协议LCP

      用于建立,配置和检测数据链路连接的连接控制协议(LCP);

2. ppp协议的网络控制协议NCP

     用于建立、配置不同网络层协议的网络控制协议(NCP)协议族;

3. ppp的扩展协议(Multilink Protocol)

三、联网

1. 联网步骤

      为了在点对点连接上建立通信,每个PPP端首先必须发送LCP包以配置和检测数据链接,在连接建立而且可选的选项都已经由LCP设置完成后,PPP必须发送NCP包用以选择和配置一个或多个网络层协议。在每层的协议被配置完成后,就可以在链路上进行通信了。在LCP或NCP显式地关闭连接以前连接一保持开放;

2. ppp实际联网步骤

      终端通过ppp协议从ISP(互联网服务提供商)那里获取IP地址,即流程是先LCP协商,然后NCP协商,NCP从ISP那里获取一个IP地址,即可以访问互联网了,然后终端通过socket连接与服务器(前置机)通讯,当我们通过socket的关闭接口close()时只是纯粹的关闭了终端与前置机的通信,而终端与ISP的连接还是仍然存在的,如果要关闭ISP连接,先要执行NCP释放网络层连接,ISP回收IP地址,接着LCP释放数据链路层连接

 

四、ppp涉及到的主要结构体

1. 协议 struct protent

/*
 * The following struct gives the addresses of procedures to call
 * for a particular protocol.
 */
struct protent {
    u_short protocol;		/* PPP protocol number */
    /* 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. option参数配置

typedef struct {
	char	*name;		/* name of the option */
	enum opt_type type; //addr的类型
	void	*addr; //变量或函数的地址
	char	*description; //该addr的描述
	unsigned int flags; //addr的属性标识
	void	*addr2; //当addr为函数时,addr2表示的是变量
	int	upper_limit; //上限值
	int	lower_limit; //下限值
	const char *source;
	short int priority; //优先级
	short int winner;
} option_t;

3. 状态机fsm

/*
 * 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;

4. 状态机回调函数

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));
    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;

5. 逻辑通道channel

/*
 * This struct contains pointers to a set of procedures for
 * doing operations on a "channel".  A channel provides a way
 * to send and receive PPP packets - the canonical example is
 * a serial port device in PPP line discipline (or equivalently
 * with PPP STREAMS modules pushed onto it).
 */
struct channel {
	/* set of options for this channel */
	option_t *options;
	/* find and process a per-channel options file */
	void (*process_extra_options) __P((void));
	/* check all the options that have been given */
	void (*check_options) __P((void));
	/* get the channel ready to do PPP, return a file descriptor */
	int  (*connect) __P((void));
	/* we're finished with the channel */
	void (*disconnect) __P((void));
	/* put the channel into PPP `mode' */
	int  (*establish_ppp) __P((int));
	/* take the channel out of PPP `mode', restore loopback if demand */
	void (*disestablish_ppp) __P((int));
	/* set the transmit-side PPP parameters of the channel */
	void (*send_config) __P((int, u_int32_t, int, int));
	/* set the receive-side PPP parameters of the channel */
	void (*recv_config) __P((int, u_int32_t, int, int));
	/* cleanup on error or normal exit */
	void (*cleanup) __P((void));
	/* close the device, called in children after fork */
	void (*close) __P((void));
};

五、流程图分析

本小节列出ppp主框架流程图,再逐个列出各子模块。

1. 主框架流程图

2. 协议初始化模块

3. tty初始化模块

4. pppd参数解析模块

5. pppd逻辑设备创建

6. ppp tty option配置

7. ppp_check_options 和 tty_check_options

8. start_link()

ppp_on脚本:

#!/bin/sh
#
# Script to initiate a ppp connection. This is the first part of the
# pair of scripts. This is not a secure pair of scripts as the codes
# are visible with the 'ps' command.  However, it is simple.
#
# These are the parameters. Change as needed.
TELEPHONE=*99***1\#	# The telephone number for the connection
ACCOUNT=card		# The account name for logon (as in 'George Burns')
PASSWORD=card		# The password for this account (and 'Gracie Allen')
LOCAL_IP=0.0.0.0	# Local IP address if known. Dynamic = 0.0.0.0
REMOTE_IP=0.0.0.0	# Remote IP address if desired. Normally 0.0.0.0
NETMASK=255.255.255.0	# The proper netmask if needed
PPP_TTY=/dev/ttyGSM1
#
# Export them so that they will be available at 'ppp-on-dialer' time.
PPP_ON_TIMEOUT=20
if [ "" != "$1" ];then
	ACCOUNT=$1
fi
if [ "" != "$2" ];then
	PASSWORD=$2
fi
if [ "" != "$3" ];then
	TELEPHONE=$3
fi
if [ "" != "$4" ];then
	PPP_ON_TIMEOUT=$4
fi

if [ "" != "$5" ];then
	PPP_TTY=$5
fi

export TELEPHONE ACCOUNT PASSWORD PPP_ON_TIMEOUT
# 
# This is the location of the script which dials the phone and logs
# in.  Please use the absolute file name as the $PATH variable is not
# used on the connect option.  (To do so on a 'root' account would be
# a security hole so don't ask.)
#
DIALER_SCRIPT=/clou/ppp/script/ppp-on-dialer
#
# Initiate the connection
# 
# I put most of the common options on this command. Please, don't
# forget the 'lock' option or some programs such as mgetty will not
# work. The asyncmap and escape will permit the PPP link to work with
# a telnet or rlogin connection. You are welcome to make any changes
# as desired. Don't use the 'defaultroute' option if you currently
# have a default route to an ethernet gateway.
#
exec pppd debug lock modem nocrtscts $PPP_TTY 115200 \
	 asyncmap 0 escape FF kdebug 0 $LOCAL_IP:$REMOTE_IP \
	user $ACCOUNT password $PASSWORD noipdefault defaultroute  netmask $NETMASK defaultroute connect $DIALER_SCRIPT

ppp_on_dialer脚本:

#!/bin/sh
#
# This is part 2 of the ppp-on script. It will perform the connection
# protocol for the desired connection.
#
exec chat -v						\
	TIMEOUT		3				\
	ABORT		'\nBUSY\r'			\
	ABORT		'\nNO ANSWER\r'			\
	ABORT		'\nRINGING\r\n\r\nRINGING\r'	\
	ABORT		'\n+CME ERROR: 100\r'			\
	''		AT				\
	'OK-+++\c-OK'	AT				\
	TIMEOUT		$PPP_ON_TIMEOUT		\
	OK		ATDT$TELEPHONE			\
	CONNECT		''				\

9. get_input()处理

lcp、ipcp...其实最终都是调用状态机fsm-iput()函数完成不同协议、不同代码域之间的切换,源码如下:

ipcp:

ipcp_input(unit, p, len)
    int unit;
    u_char *p;
    int len;
{
    fsm_input(&ipcp_fsm[unit], p, len); //见下面
}

lcp:

lcp_input(unit, p, len)
    int unit;
    u_char *p;
    int len;
{
    fsm *f = &lcp_fsm[unit];

    if (f->flags & DELAYED_UP) {
		f->flags &= ~DELAYED_UP;
		fsm_lowerup(f); 
    }

	fsm_input(f, p, len); //见下面
}

fms_input:

void
fsm_input(f, inpacket, l)
    fsm *f;
    u_char *inpacket;
    int l;
{
    u_char *inp;
    u_char code, id;
    int len;

    /*
     * Parse header (code, id and length).
     * If packet too short, drop it.
     */
    inp = inpacket;
    if (l < HEADERLEN) {
		FSMDEBUG(("fsm_input(%x): Rcvd short header.", f->protocol));
		return;
    }
    GETCHAR(code, inp); //获取代码
    GETCHAR(id, inp); //获取标识
    GETSHORT(len, inp); //获取长度
    if (len < HEADERLEN) {
		FSMDEBUG(("fsm_input(%x): Rcvd illegal length.", f->protocol));
		return;
    }
    if (len > l) {
		FSMDEBUG(("fsm_input(%x): Rcvd short packet.", f->protocol));
		return;
    }
    len -= HEADERLEN;		/* subtract header length */

    if( f->state == INITIAL || f->state == STARTING ){
		FSMDEBUG(("fsm_input(%x): Rcvd packet in state %d.",
			  f->protocol, f->state));
		return;
    }

    /*
     * Action depends on code.
     */
    switch (code) { //根据代码进行数据内容解析
    case CONFREQ: //配置请求
	fsm_rconfreq(f, id, inp, len);
	break;
    
    case CONFACK: //配置应答
	fsm_rconfack(f, id, inp, len);
	break;
    
    case CONFNAK: //配置否定应答
    case CONFREJ: //配置拒绝
	fsm_rconfnakrej(f, code, id, inp, len);
	break;
    
    case TERMREQ: //终止请求
	fsm_rtermreq(f, id, inp, len);
	break;
    
    case TERMACK: //终止应答
	fsm_rtermack(f);
	break;
    
    case CODEREJ: //代码域拒绝
	fsm_rcoderej(f, inp, len);
	break;
    
    default:
	if( !f->callbacks->extcode
	   || !(*f->callbacks->extcode)(f, code, id, inp, len) )
	    fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
	break;
    }
}

 

六、源码分析

       历时一个来月,零散的时间里终于把这ppp拨号的流程弄懂了个所以然,但目前因时间和精力问题(现在忙于国网面向对象采用SQLite3数据库架构设计),源码不在此贴出,ppp拨号的具体流程在第五点的流程图里分析的很详细,后续有时间细看代码,晚安!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值