USB 3G卡热插拔那些事4——pppd
在上一节中我们知道3G卡设备驱动已经加载好了,并且和ttyUSB*已经绑定成功,意味着我们可以拨号了,和3G卡内部3G模块通信了,而我们知道3G模块通信是tty设备,通过串行设备,这里和3G卡的两种工作模式相吻合—modem模式.
首先我们这里先给出3G工作原理图:
我们万事俱备只欠东风了,就是pppd拨号了,当然pppd的源码自己可以去网上下载最新的,自己编译(这里我们只说linux环境的).安装好pppd程序后,我们运行pppd拨号,当然在拨号前我们需要配置下我们的pppd才能完美的工作,才能真正为我们对于的tty驱动和3g驱动运作起来.
主要看pppd运行参数文件/etc/ppp/options(这个参数文件名字,看开发者自己是可以修改的)该文件指定pppd运行的参数,若运行pppd时通过命令行指定参数同时出现时,则选择/etc/ppp/options中的配置.下面我们就看相关的参数:(这里贴上我的配置)
#Copyright (c) 2017 tangbin
#set debug ,send message to /var/log/messages
debug
#To keep pppd on the terminal
nodetach #若指定updetach则拨号成功后放入后台运行,若为nodetach,则在前台执行
lock #创建一个锁定文件,其他程序在发现存在这个文件后,就能得知相应的串口已经被使用
#set seriral
/dev/ttyUSB3 #指定连接使用的设备
# set baudrate
115200 #传输速率
user "tangbin"
password "tangbin"
# set flowrate
crtscts #硬件流控,无硬件流控为nocrtscts
show-password
usepeerdns #使用服务器端协商的DNS就可以设置参数usepeerdns
noauth
noipdefault #不使用默认IP就可以加入参数noipdefault
novj #选中这个选项,将关闭双方的Van Jacobson形式TCP/IP报文头压缩
novjccomp #选中这个选项,将关闭Van Jacobson形式TCP/IP报文头压缩中的连接ID压缩。Pppd将忽略来自Van Jacobson形式压缩TCP/IP报文头中的连接ID字节,也不要求对方这样做。
noccp #关闭压缩控制协议协商
# Accept the peer's idea of our local IP address
ipcp-accept-local
# ipcp-accept-remote
ipcp-accept-remote #pppd将会接受彼端对於它的IP位址的意见,即使远端的IP位址已经在某个选项中指定
connect '/usr/sbin/chat -s -v -f /etc/ppp/peers/wcdma-chat-connect'
disconnect '/usr/sbin/chat -s -v -f /etc/ppp/peers/wcdma-chat-disconnect'
这些信息我们都可以从pppd源码包的帮助文件中获取信息,pppd/tty.c中的函数connect_tty()将会执行这个脚本。主要是对客户端的拨号的应答。
connect '/usr/sbin/chat -s -v -f /etc/ppp/peers/wcdma-chat-connect'
在pppd运行开始,要初始化tty. 就是调用tty_init()
。
下面给出pppd相关代码:
/*
* Initialize each protocol.
*/
for (i = 0; (protp = protocols[i]) != NULL; ++i)
(*protp->init)(0);
/*
* Initialize the default channel.
*/
tty_init();
展开tty_init():
void tty_init()
{
add_notifier(&pidchange, maybe_relock, 0);
the_channel = &tty_channel;
xmit_accm[3] = 0x60000000;
}
我们看到里面the_channel = &tty_channel;
的操作,struct channel *the_channel;(main.c中)是一个全局变量.
/*
* 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));
};
extern struct channel *the_channel;
在ppp.h中定义,并声明.
下面看看如何初始化的
struct channel tty_channel = {
tty_options,
&tty_process_extra_options,
&tty_check_options,
&connect_tty,
&disconnect_tty,
&tty_establish_ppp,
&tty_disestablish_ppp,
&tty_do_send_config,
&tty_recv_config,
&cleanup_tty,
&tty_close_fds
};
同理这里我们只关注&connect_tty
, 这个初始化在tty.c中.
/*
* connect_tty - get the serial port ready to start doing PPP.
* That is, open the serial port, set its speed and mode, and run
* the connector and/or welcomer.
*/
int connect_tty()
{
char *connector;
int fdflags;
#ifndef __linux__
struct stat statbuf;
#endif
char numbuf[16];
/*
* Get a pty master/slave pair if the pty, notty, socket,
* or record options were specified.
*/
strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam));
pty_master = -1;
pty_slave = -1;
real_ttyfd = -1;
if (using_pty || record_file != NULL) {
if (!get_pty(&pty_master, &pty_slave, ppp_devnam, uid)) {
error("Couldn't allocate pseudo-tty");
status = EXIT_FATAL_ERROR;
return -1;
}
set_up_tty(pty_slave, 1);
}
/*
* Lock the device if we've been asked to.
*/
status = EXIT_LOCK_FAILED;
if (lockflag && !privopen) {
if (lock(devnam) < 0)
goto errret;
locked = 1;
}
/*
* Open the serial device and set it up to be the ppp interface.
* First we open it in non-blocking mode so we can set the
* various termios flags appropriately. If we aren't dialling
* out and we want to use the modem lines, we reopen it later
* in order to wait for the carrier detect signal from the modem.
*/
got_sigterm = 0;
connector = doing_callback? callback_script: connect_script;
if (devnam[0] != 0) {
for (;;) {
/* If the user specified the device name, become the
user before opening it. */
int err, prio;
...
这里我们要特别注意/**/中的注释部分. 就是在这里我们的ppp和tty联系了起来,才真正work. 当然还加载了前面的脚本文件,来获取必要的配置信息.
tty_init()完成后,就是来和ppp内核协议通信来建立必要的ppp网络接口,以供上层应用程序用
/*
* If we're doing dial-on-demand, set up the interface now.
*/
if (demand) {
/*
* Open the loopback channel and set it up to be the ppp interface.
*/
fd_loop = open_ppp_loopback();
set_ifunit(1);
/*
* Configure the interface and mark it up, etc.
*/
demand_conf();
}
在open_ppp_loopback()这个函数中调用 make_ppp_unit()建立ppp0接口(当然这个接口名我们也可以自定义)
/*
* make_ppp_unit - make a new ppp unit for ppp_dev_fd.
* Assumes new_style_driver.
*/
static int make_ppp_unit()
{
int x, flags;
if (ppp_dev_fd >= 0) {
dbglog("in make_ppp_unit, already had /dev/ppp open?");
close(ppp_dev_fd);
}
ppp_dev_fd = open("/dev/ppp", O_RDWR);
if (ppp_dev_fd < 0)
fatal("Couldn't open /dev/ppp: %m");
flags = fcntl(ppp_dev_fd, F_GETFL);
if (flags == -1
|| fcntl(ppp_dev_fd, F_SETFL, flags | O_NONBLOCK) == -1)
warn("Couldn't set /dev/ppp to nonblock: %m");
ifunit = req_unit;
x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
if (x < 0 && req_unit >= 0 && errno == EEXIST) {
warn("Couldn't allocate PPP unit %d as it is already in use", req_unit);
ifunit = -1;
x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
}
if (x < 0)
error("Couldn't create new ppp unit: %m");
return x;
}
这里我们看到open函数打开了/dev/ppp ,然后发送一个ioctl给ppp协议
在ppp协议里
static struct file_operations ppp_device_fops = {
.owner = THIS_MODULE,
.read = ppp_read,
.write = ppp_write,
.poll = ppp_poll,
.ioctl = ppp_ioctl,
.open = ppp_open,
.release = ppp_release
};
ppp_ioctl
调用ppp_unattached_ioctl()
,在这个函数里
switch (cmd) {
case PPPIOCNEWUNIT:
/* Create a new ppp unit */
if (copy_from_user(&req, p, sizeof req))
break;
ppp = ppp_create_interface(req.unit, &err, req.ifname);
ppp_create_interface
来真正创建这个接口,以供将来应用程序使用.
既然tty和ppp都初始化了,也建立了某种联系,并且能一起协同工作了,那么我们还犹豫什么?
当然是start_link了
start_link(0);
在这个函数里我们才真正调用我们上面长篇大论的connect_tty().
建立连接的过程分两个阶段:LCP,PCAP.
pppd建立连接过程中chat辅助程序起到不可磨灭的作用.
pppd运行后作为一个守护进程来运行,它必然有监控程序,而这个就是handle_events.
有人认为我这里说这么多是扯淡,我感觉不是,为什么呢,虽然我们一般人直接用pppd拨号就可以,但是很多人并不知道为什么,为什么就把pppd、ppp协议、tty、3G模块联系起来的.这里分析下我想功夫不会是白费的.如果一旦出现问题,那么我们很容易定位问题的所在,知其然还要知其所以然.以后我们应该讲讲ppp相关的数据包收发流程.
USB 3G卡热插拔那些事5——PPP协议
或许我们把pppd拨号原理讲完已经万事大吉了,但是这里我想还是需要再说说ppp协议,我们知道3G拨号上网是通过ppp协议,利用的是ppp帧格式,就说3G卡吧,我知道3G卡首先是通过usb接口连接到设备,ppp–>tty–>Gsm/cdma/wcdma,我们在看Gsm/cdma/wcdma的驱动函数的时候,我们会发现usb相关的收发函数,为什么呢?因为在是通过usb接口连接的,那么在设备收发的时候,必须是urb包的形式才能被识别,然后才是Gsm/cdma/wcdma模块驱动,它是串行接口传输.所以在ppp协议到tty部分其实我们不用太关注其他的事情,用正常的理解就可以了.但是到了Gsm/cdma/wcdma模块的驱动部分就复杂了,为什么这么说,首先我们知道它关联了串行驱动tty,还有usb驱动.从硬件上它有一个usb转串的芯片.
前面说了这么多,那么我们就来分析下ppp协议,网上和书上已经有很多相关资料,我这里就偷工减料了^^
首先可以参考《tcp/ip协议卷1》、《linux网络体系结构-linux内核中网络协议的设计与实现》还可以参考网上一些前辈的文章加速理解.
链路控制协议LCP(Link Control Protocol);
网络控制协议NCP(Network Control Protocol);
认证协议:口令验证协议PAP(Password Authentication Protocol)和挑战握手验证协议CHAP(Challenge-Handshake Authentication Protocol)。
1、LCP协商,协商内容包括除RFC1661中所定义的选项之外,还要考虑PPPOA和PPPOE协议中规定的内容。
2、LCP协商过 后就到了Establish阶段,开始PAP或CHAP认证。PAP为两次握手认证,口令为明文。PAP认证过程如下:发送用户名同口令到认证方,认证方查看是否有此用户,口令是否正确,然后发送相应的响应。CHAP为三次握手认证,口令为密文(密钥)CHAP认证由认证方发送一些随机产生的报文,交给被认证方,被认证方用自己的口令字用MD5算法进行加密,传回密文,认证方用自己保存的口令字及随机报文用MD5算法加密,比较二者的密文,根据比较结果返回响应的响应。
3、认证成功即进行Network阶段协商(NCP),在IP接入中主要是IPCP协商(如IP地址和DNS地址的协商等)。任何阶段的协商失败都将导致链路的拆除。
4、协商成功,则链路建立成功,可以开始传输网络层数据报文。
1、PPP协议组成:
a) 链路控制协议(LCP-Link Control Protocol),完成线路的启动、测试、任选参数的协商和最终线路断开功能
b) 认证协议,最常用的包括口令验证协议PAP(Password Authentication Protocol)和挑战握手验证协议CHAP(Challenge-Handshake Authentication Protocol)
c) 用户认证,主要通过LCP协商采用何种认证协议,但认证协议本身不是PPP协议的范围
d) IP控制协议IPCP(网络控制协议(NCP)),最常用的NCP协议为。它的一个重要功能就是动态分配IP地址;
2、认证方式
2.1口令验证协议(PAP)
PAP是一种简单的明文验证方式。NAS(网络接入服务器,Network Access Server)要求用户提供用户名和口令,PAP以明文方式返回用户信息。很明显,这种验证方式的安全性较差,第三方可以很容易的获取被传送的用户名和口令,并利用这些信息与NAS建立连接获取NAS提供的所有资源。所以,一旦用户密码被第三方窃取,PAP无法提供避免受到第三方攻击的保障措施。
2.2挑战-握手验证协议(CHAP)
CHAP是一种加密的验证方式,能够避免建立连接时传送用户的真实密码。NAS向远程用户发送一个挑战口令(challenge),其中包括会话ID和一个任意生成的挑战字串(arbitrary challengestring)。远程客户必须使用MD5单向哈希算法(one-way hashing algorithm)返回用户名和加密的挑战口令,会话ID以及用户口令,其中用户名以非哈希方式发送。
CHAP对PAP进行了改进,不再直接通过链路发送明文口令,而是使用挑战口令以哈希算法对口令进行加密。因为服务器端存有客户的明文口令,所以服务器可以重复客户端进行的操作,并将结果与用户返回的口令进行对照。CHAP为每一次验证任意生成一个挑战字串来防止受到再现攻击(replay attack)。在整个连接过程中,CHAP将不定时的向客户端重复发送挑战口令,从而避免第3方冒充远程客户(remote client impersonation)进行攻击。
目前大多数模拟拨号连接都采用PPP做为数据链路协议,主要有下面特点:
错误检测
自动协商网络层地址
CHAP or PAP 认证
数据压缩
符合ISO标准
PPP和串行线路Internet协议(SLIP)常常使人混淆,SLIP仅仅支持IP协议,PPP支持IP、IPX、以及AppleTalk等多协议。
PPP将数据链路层为为3个子层:
NCP 建立和协商网络层协议及相应的地址
LCP 建立链路、认证用户和检测链路质量
HDLC 在链路上封装数据包
表2-1 概述PPP及其子层
OSI层 常见协议
第3层 IP、IPX、AppleTalk
第2层 NCP、LCP、HDLC
第1层 EIA/TIA-232、X.24、V.23、V.35和ISDN等
PPP的大多数扩展功能如数据纠错及支持多种网络协议等,都是由LCP和NCP来控制的。LCP用于配置和测试数
据链路,工作方式如下:
第1步 链路建立阶段―――首先打开连接,然后确定相关通信参数(包括MTU、compress type、及链路认证类型。链路设置完后确认帧,然后是可选的链路质量确认阶段,LCP确定链路质量
第2步 可选(必要)的认证阶段―――两种认证方式:质询应答握手认证协议(CHAP)和密码认证协议(PAP)。PPP本身不需要认证,cisco路由器异步线路需要认证,建议使用CHAP认证方式。
第3步 网络层协议阶段―――LCP引导NCP激活和配置网络层协议。这一阶段结束后即可传输数据包。
第4步 链路终止阶段―――LCP指导NCP关闭layer 3。
LCP使用3种类型LCP数据帧完成上述步骤:
链路建立帧(Link establishment frames)―――建立链路
链路终止帧(Link terminateon frames)―――关闭链路
链路维护帧(Link maintenance frames)―――维护链路
PPP 主要由以下几部分组成:
封装:一种封装多协议数据报的方法。PPP 封装提供了不同网络层协议同时在同一链路传输的多路复用技
术。PPP 封装精心设计,能保持对大多数常用硬件的兼容性。克服了SLIP不足之处的一种多用途、点到点协
议,它提供的WAN数据链接封装服务类似于LAN所提供的封闭服务。所以,PPP不仅仅提供帧定界,而且提供
协议标识和位级完整性检查服务。
链路控制协议:PPP 提供的 LCP 功能全面,适用于大多数环境。LCP 用于就封装格式选项自动达成一致
,处理数据包大小限制,探测环路链路和其他普通的配置错误,以及终止链路。LCP 提供的其他可选功能有
:认证链路中对等单元的身份,决定链路功能正常或链路失败情况。
网络控制协议:一种扩展链路控制协议,用于建立、配置、测试和管理数据链路连接。
配置:使用链路控制协议的简单和自制机制。该机制也应用于其它控制协议,例如:网络控制协议(NCP
)。
为了建立点对点链路通信,PPP 链路的每一端,必须首先发送 LCP 包以便设定和测试数据链路。在链路
建立,LCP 所需的可选功能被选定之后,PPP 必须发送 NCP 包以便选择和设定一个或更多的网络层协议。
一旦每个被选择的网络层协议都被设定好了,来自每个网络层协议的数据报就能在链路上发送了。
链路将保持通信设定不变,直到有 LCP 和 NCP 数据包关闭链路,或者是发生一些外部事件的时候(如,
休止状态的定时器期满或者网络管理员干涉)。
PPP工作流程:
当用户拨号接入 ISP 时,路由器的调制解调器对拨号做出确认,并建立一条物理连接。 PC 机向路由器发送一系列的 LCP 分组(封装成多个 PPP 帧)。
这些分组及其响应选择一些 PPP 参数,和进行网络层配置,NCP 给新接入的 PC机分配一个临时的 IP 地址,使 PC 机成为因特网上的一个主机。
通信完毕时,NCP 释放网络层连接,收回原来分配出去的 IP 地址。接着,LCP 释放数据链路层连接。最后释放的是物理层的连接。
PPP和HDLC之间最主要的区别是,PPP是面向字符的,HDLC是面向位的。
与PPP相关的RFC:
RFC1144 TCP/IP数据包压缩
RFC1220 PPP在网桥上的扩充
RFC1334 PPP认证协议
RFC1378 PPP AppleTalk控制协议(ATCP)
RFC1552 PPP互联网数据包交换控制协议(IPXCP)
RFC1570 PPP LCP协议扩充
RFC1661 PPP协议(PPP)
RFC1662 PPP中的HDLC封装
RFC1990 PPP多链路协议(MP)
上面是我从网上摘的一些片段,大致就是这个,这里我们不再具体分析ppp协议数据包具体收发函数流程.
下面我说一下pppoe相关的,因为这里我有点困惑,才搞明白.
首先我们知道ppp协议是针对串行传输的,那么它也有它自己的固定的帧格式,当然是不能直接在以太网上传输的,比如3G拨号,它没有通过以太网口,而是通过3g模块,以串行方式把数据发出去了,最后转行成无线信号. 但是我们知道:
以太网接口(WAN)支持三种连接模式:
·自动模式:即配置接口作为DHCP客户端,使用DHCP方式获取IP地址。
·手动模式:即手动为接口配置IP地址和子网掩码。
·PPPoE:即配置接口作为PPPoE客户端。PPPoE是Point-to-Point Protocol over Ethernet的简称,它利用以太网将大量主机组成网络,通过一个远端接入设备连入因特网,并对接入的每个主机实现控制、计费功能,极高的性能价格比使PPPoE在包括小区组网建设等一系列应用中被广泛采用。
它是和3G是不同的,那么这个时候就用到了pppoe模块,它把ppp协议的帧经过处理封装到以太网帧中传输到网络中.
两者的区别基本有两点:
1.3G不经过pppoe再封装.直接串口传输.
2.wan呢,则是需要pppoe封装的,因为它物理接口是以太网.