一、概念
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拨号的具体流程在第五点的流程图里分析的很详细,后续有时间细看代码,晚安!