Android PPP协议

PPP协议简介  

 在以太网通讯中,广泛使用TCP(或UDP)、IP与IEEE 802三个协议联合完成寻址和通讯控制任务。IEEE 802是一个局域网的链路层工作协议,不能在广域网中使用。在使用诸如电话网、ISDN网这样的广域网连接中,需要在链路层使用另外的一个称为PPP的协议程序。

  在点对点连接中,发送主机需要在链路层使用PPP协议程序来完成链路层的数据封装。控制数据往物理层发送移位寄存器上发送数据的工作,也由PPP协议程序来完成。在接收主机,链路层的工作也由PPP协议程序承担。

  在这里,发送主机的链路层仍然使用IEEE 802协议程序,因为主机直接连接的是以太网络。数据报到达路由器A后,路由器A将使用PPP封装数据报,继续将数据报转发到电话网或ISDN网的链路上。在接收方,路由器B也将使用PPP程序控制从移位寄存器中接收数据报。然后,路由器B将用IEEE 802程序重新封装数据帧,发送到自己的以太网中,交目标主机接收。

  PPP协议是一个链路层协议,工作在电话网、ISDN网这样的点对点通讯的连接上。PPP是Point-to-Point Protocol的缩写,称为点对点连接协议。

  PPP协议因为工作在点对点的连接中,因此具有如下两个特点。

  首先,点对点的连接不需要物理寻址。这是因为发送端发送出的数据报,经点对点连接链路,只会有一个接收端接收。在数据传输开始前,数据转发线路已经由电话信令信号沿电话网或ISDN网中的交换机建立起来了。开始传送数据后,电话网或ISDN网中的交换机不再需要根据报头中的链路层地址判断如何转发。在接收端,也不需要接收主机象以太网技术那样根据链路层地址辨别是否是发给自己的数据报。因此,PPP协议封装数据报时,不需要再在报头中封装链路层地址。

 

PPP(Point-to-Point Protocol)协议是在SLIP的基础上发展起来的,由于SLIP只支持异步传输方式,无协商过程,它逐渐被PPP协议所替代.PPP协议作为一种提供在点到点链路上封装,传输网络层数据包的数据链路层协议,处于OSI参考模型的第二层,主要被发计用来支持全双工的同异步链路上进行点到点之间的传输.

PPP由于能够提供验证,易扩充,支持同异步而获得较广泛的应用.

PPP协议的特点:

PPP协议是数据链路层协议

支持点到点的连接(不同于X.25,Frame Relay等数据链路层协议);

物理层可以是同步电路或异步电路(如Frame Relay 等数据链路层协议)

具有各种NCP协议,如IPCP,IPXCP更好的支持了网络层协议;

具有验证协议PAP/CHAP,更好的保证了网络的安全性.

ppp的组成部分

 PPP主要由两类协议组成:链路控制协议族(LCP)和网络控制协议族(NCP)

链路控制协议主要用于建立,拆除和监控PPP数据链路,网络层控制协议族主要用于协商在该数据链路上所传输的数据包的格式与类型。同时,PPP还提供了用于网络安全方面的验证协议族(PAP和CHAP)。

链路控制协议(LCP):建立,配置,测试PPP数据链路连接;

网络控制协议族(NCPS):协商在该链路上所传输的数据包的格式与类型,建立,配置不同网络层协议;PPP扩展协议族:提供对PPP功能的进一步支持。

PPP协议栈

ppp是一个分层结构。在底层,它能使用同步媒介(如ISDNH或同步DDN专线),也能使用异步媒介(如基于Modem拨号的PSTN网络)。

在数据链路层,PPP在链路层建立方面提供了丰富的服务,这些服务以LCP协商选项的形式提供。

在上层,PPP通过NCPs提供对多种网络层协议的支持。PPP对于每一种网络层协议都有一种封装格式来区别它们的报文。

 

PPP协商过程分为几个阶段:Dead阶段,Establish阶段,Authenticate阶段,Network阶段和Termintate阶段,在不同的阶段进行不同协议的协商.只有前面的协商出现结果后,才能转到下一个阶段,进行下一个协议的协商.

1)当物理层不可用时,PPP链路处于dead阶段,链路必须从这个阶段开始和结束.当物理层可用时,PPP在建立链路之前首先进行LCP协商,协商内容包括工作方式是SP还是MP,验证方式和最大传输单元等.

2)LCP协商过后就进入Establish阶段,此时LCP状态为Opened,表示链路已经建立.

3)如果配置了验证(远端验证本地或者本地验证远端)就进入Authenticate阶段,开始CHAP或PAP验证.

4)如果验证失败进入Terminate阶段,拆除链路,LCP状态转为Down;如果验证成功就进入Network协商阶段(NCP),此时LCP状态仍为Opened,而IPCP状态从Initial转到Request.

5)NCP协商支持IPCP协商,IPCP协商主要包括双方的IP地址.通过NCP协商来选择和配置一个网络层协议.当选中的网络层协议配置成功后,该网络层协议就可以通过这条链路发送报文了.

6)PPP链路将一直保持通信,直至有明确的LCP或NCP帧关闭这条链路,或发生了某些外部事件.(例如,用户的干预).

  PAP,CHAP验证

PAP验证为两次握手验证,口令为明文,PAP验证的过程如下:

被验证方发送用户名和口令到验证方;验证方根据用户配置查看是否有此用户以及口令是否正确,然后返回不同的响应(Acknowledge Or Not Acknowledge).

如正确则会给对端发送ACK报文,通告对端已被允许进入下一阶段协商;否则发送NAK报文,通告对端验证失败.此时,并不会直接将链路关闭.只有当验证不通过次数达到一定值(缺省为4)时,才会关闭链路,来防止因误传,网络干扰等造成不必要的LCP重新协商过程.

PAP的特点是在网络上一明文的方式传递用户名及口令,如在传输过程中被截获,便有可能对网络安全造成极大的威胁.因此,它适用于对网络安全要求相对教低的环境.

 

 CHAP验证为三次握手验证,口令为密文(密钥),CHAP验证过程如下:

验证方向被验证方发送一些随机产生的报文,并同时将本端的主机名附带上一起发送给被验证方;

 被验证方接到对端对本端的验证 请求(Challenge)时,便根据此报文中验证方的主机名和本端的用户表查找用户口令字,如找到用户表中与验证方主机名相同的用户,便利用接收到的随机报文、此用户的密钥用Md5算法生成应答(

Response),随后将应答和自己的主机名送回;

验证方接到此应答后,利用对端的用户名在本端的用户表中查找本方保留的口令字,用本方保留的口令字(密钥)和随机报文用Md5算法得出结果,与被验证方应答比较,根据比较结果返回相应的结果(ACK or NAK)。

它的特点是只在网络上传输用户名,而并不传输用户口令,因此它的安全性要比PAP高。

PPP协议结构

在国内广泛使用的宽带拨号协议PPPoE其基础就是PPP协议,此外和PPP相关的协议PPTP,L2TP也常应用于VPN虚拟专用网络。随着智能手机系统Android的兴起,PPP协议还被应用于GPRS拨号,3G/4G数据通路的建立,在嵌入式通信设备及智能手机中有着广泛的应用基础。

网络协议是分层实现的,上层一般只需要知道其直接下层,只有在极少数的情况下才使用间接下层的接口。如普通的socket编程,它们只需要知道TCP\IP协议,而不需要知道PPP协议的存在。这种分层的设计简化了协议的实现和应用程序的开发。

但是PPP协议并不只提供简单的数据链路层功能,还提供了一些扩展功能(如鉴权PAP/CHAP,加密解密ECP)。应用程序要求使用透明化,不关心这些扩展功能的存在,而反过来。ppp协议处理模块本身又无法处理这些策略性的东西,因为它不知道用户名/密码,不知道是否要进行压缩,不知道是否要进行加密。

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

pppd是用户空间的程序,它与内核态通过 设备文件(/dev/ppp)  进行通信。通过read系统调用,pppd可以读取PPP协议处理模块的数据包(PPP协议处理模块只会把应该由pppd处理的数据包发给pppd);通过write系统调用,pppd可以把要发送的数据包传递给PPP协议处理模块。通过ioctrl系统调用,pppd可以设置PPP协议的参数,可以建立/关闭连接。

在pppd里,每种协议实现都在独立的c文件中,它们通常要实现protent接口(该接口主要用于处理数据包)和fsm_callbacks接口(该接口主要用于状态机的状态切换)。数据包的接收是由main.c:get_input统一处理的,然后根据协议类型分法到具体的协议实现上。而数据包的发送则是协议实现者根据需要调用output函数完成的。

 

PPPD:PPP用户态应用程序。 pppd 提供 基本的LCP,验证(authentication)的支援,以及一个用来建立  并配置网际网路协议 (Internat Protocol (IP) )(叫做 IP 控制 议, IPCP)的 NCP 。 

PPP驱动:PPP在内核中的驱动部分,kernel源码在/drivers/net/下的ppp_generic.c, slhc.c。

PPP线路规程*:PPP TTY线路规程,kernel源码在/drivers/net/下的ppp_async.c, ppp_synctty.c,本文只考虑异步PPP。PPP线路规程主要实现PPP协议头的封装和解封装。

TTY核心:TTY驱动,线路规程的通用框架层。

TTY驱动:串口TTY驱动,和具体硬件相关,本文不讨论。

说明:本文引用的pppd源码来自于android 2.3源码包,kernel源码版本为linux-2.6.18。

Linux中PPP实现主要分成两大部分:PPPD和PPPK。PPPD是用户态应用程序,负责PPP协议的具体配置,如MTU、拨号模式、认证方式、认证所需用户名/密码等。 PPPK指的是PPP内核部分,包括上图中的PPP驱动和PPP线路规程。PPPD通过PPP驱动提供的设备文件接口/dev/ppp来对PPPK进行管理控制,将用户需要的配置策略通过PPPK进行有效地实现,并且PPPD还会负责PPP协议从LCP到PAP/CHAP认证再到IPCP三个阶段协议建立和状态机的维护。因此,从Linux的设计思想来看,PPPD是策略而PPPK是机制;从数据收发流程看,所有控制帧(LCP,PAP/CHAP/EAP,IPCP/IPXCP等)都通过PPPD进行收发协商,而链路建立成功后的数据报文直接通过PPPK进行转发,如果把Linux当做通信平台,PPPD就是Control
Plane而PPPK是DataPlane。

在Linux中PPPD和PPPK联系非常紧密,虽然理论上也可以有其他的应用层程序调用PPPK提供的接口来实现PPP协议栈,但目前使用最广泛的还是PPPD。PPPD的源码比较复杂,支持众多类UNIX平台,里面包含TTY驱动,字符驱动,以太网驱动这三类主要驱动,以及混杂了TTY,PTY,Ethernet等各类接口,导致代码量大且难于理解,下文我们就抽丝剥茧将PPPD中的主干代码剥离出来,遇到某些重要的系统调用。

源码分析:

PPPD的主函数main:

第一阶段:

 

pppd/main.c -> main():

……

new_phase(PHASE_INITIALIZE)//PPPD中的状态机,目前是初始化阶段

    /*

     * Initialize magic number generator now so that protocols may

     * use magic numbers in initialization.

     */

    magic_init();

 

    /*

     * Initialize each protocol.

     */

    for(i=0;(protp=protocols[i])!=
NULL;++i//protocols[]是全局变量的协议数组

        (*protp->init)(0)//初始化协议数组中所有协议

 

    /*

     * Initialize the default channel.

     */

    tty_init()//channel初始化,默认就是全局的tty_channel,里面包括很多TTY函数指针   

    if(!options_from_file(_PATH_SYSOPTIONS,!privileged,0,1)//解析/etc/ppp/options中的参数

       ||!options_from_user() 

       ||!parse_args(argc-1,argv+1)) //解析PPPD命令行参数

       exit(EXIT_OPTION_ERROR);

    devnam_fixed=1;       /*
can no longer change device name */

 

    /*

     * Work out the device name, if it hasn't already been specified,

     * and parse the tty's options file.

     */

    if(the_channel->process_extra_options)

       (*the_channel->process_extra_options)()//实际上是调用tty_process_extra_options解析TTY
参数

    if(!ppp_available())//检测/dev/ppp设备文件是否有效

       option_error("%s",no_ppp_msg);

       exit(EXIT_NO_KERNEL_SUPPORT);

    }

    /*

     * Check that the options given are valid and consistent.

     */

    check_options()//检查选项参数

    if(!sys_check_options()) //检测系统参数,比如内核是否支持Multilink等

       exit(EXIT_OPTION_ERROR);

    auth_check_options()//检查认证相关的参数

#ifdef HAVE_MULTILINK

    mp_check_options();

#endif

    for(i=0;(protp=protocols[i])!=
NULL;++i)

       if(protp->check_options!=
NULL)

           (*protp->check_options)()//检查每个控制协议的参数配置 

    if(the_channel->check_options)

       (*the_channel->check_options)()//实际上是调用tty_check_options检测TTY参数

 

……

    /*

     * Detach ourselves from the terminal, if required,

     * and identify who is running us.

     */

    if(!nodetach&&!updetach

       detach()//默认放在后台以daemon执行,也可配置/etc/ppp/option中的nodetach参数放在前台执行

……

    syslog(LOG_NOTICE,"pppd %s started by %s,
uid %d"
,VERSION,p,uid)//熟悉的log,现在准备执行了

    script_setenv("PPPLOGNAME",p,0);

 

    if(devnam[0])

       script_setenv("DEVICE",devnam,1);

    slprintf(numbuf,sizeof(numbuf),"%d",getpid());

    script_setenv("PPPD_PID",numbuf,1);

 

    setup_signals()//设置信号处理函数

 

    create_linkpidfile(getpid())//创建PID文件

 

    waiting=0;

 

    /*

     * 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)//设置IFNAME环境变量为接口名称如ppp0

       /*

        * Configure the interface and mark it up, etc.

        */

       demand_conf();

}

(第二阶段)……

PPP协议里包括各种控制协议如LCP,PAP,CHAP,IPCP等,这些控制协议都有很多共同的地方,因此PPPD将每个控制协议都用结构protent表示,并放在控制协议数组protocols[]中,一般常用的是LCP,PAP,CHAP,IPCP这四个协议。

 

/*

 * PPP Data Link Layer "protocol" table.

 * One entry per supported protocol.

 * The last entry must be NULL.

 */

struct protent*protocols[]={

    &lcp_protent//LCP协议

    &pap_protent//PAP协议

    &chap_protent//CHAP协议

#ifdef CBCP_SUPPORT

    &cbcp_protent,

#endif

    &ipcp_protent//IPCP协议,IPv4

#ifdef INET6

    &ipv6cp_protent, //IPCP协议,IPv6

#endif

    &ccp_protent,

    &ecp_protent,

#ifdef IPX_CHANGE

    &ipxcp_protent,

#endif

#ifdef AT_CHANGE

    &atcp_protent,

#endif

    &eap_protent,

    NULL

};

每个控制协议由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))
//初始化指针,在main()中被调用

    /* 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))
//当下层协议UP起来后的处理

    /* Lower layer has gone down */

    void(*lowerdown)__P((int
unit))
//当下层协议DOWN后的处理

    /* 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))//处理已收到的数据包

    boolenabled_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))//判断报文类型并激活链路 

};

在main()函数中会调用所有支持的控制协议的初始化函数init(),之后初始化TTY channel,解析配置文件或命令行参数,接着检测内核是否支持PPP驱动:

 

pppd/sys_linux.c

main() -> ppp_avaiable():

intppp_available(void)

{

……

    no_ppp_msg=

       "This system lacks kernel support for PPP. This could be because\n"

       "the PPP kernel module could not be loaded, or because PPP was not\n"

       "included in the kernel configuration. If PPP was included as a\n"

       "module, try `/sbin/modprobe -v ppp'. If that fails, check that\n"

       "ppp.o exists in /lib/modules/`uname -r`/net.\n"

       "See README.linux file in the ppp distribution for more details.\n";

 

    /* get the kernel version now, since we are called before sys_init */

    uname(&utsname);

    osmaj=osmin=ospatch=0;

    sscanf(utsname.release,"%d.%d.%d",&osmaj,&osmin,&ospatch);

kernel_version=KVERSION(osmaj,osmin,ospatch);

 

    fd=open("/dev/ppp",
O_RDWR);

    if(fd>=0){

       new_style_driver=1//支持PPPK

 

       /* XXX should get from driver */

       driver_version=2;

       driver_modification=4;

       driver_patch=0;

       close(fd);

       return1;

}

……

}

函数ppp_available会尝试打开/dev/ppp设备文件来判断PPP驱动是否已加载在内核中,如果此设备文件不能打开则通过uname判断内核版本号来区分当前内核版本是否支持PPP驱动,要是内核版本很老(2.3.x以下),则打开PTY设备文件并设置PPP线路规程。目前常用的内核版本基本上都是2.6以上,绝大多数情况下使用的内核都支持PPP驱动,因此本文不分析使用PTY的old driver部分。

接下来会检查选项的合法性,这些选项可以来自于配置文件/etc/ppp/options,也可以是命令行参数,PPPD里面对选项的处理比较多,这里不一一分析了。

后面是把PPPD以daemon方式执行或保持在前台运行并设置一些环境变量和信号处理函数,最后进入到第一个关键部分,当demand这个变量为1时,表示PPPD以按需拨号方式运行。

什么是按需拨号呢?如果大家用过无线路由器就知道,一般PPPoE拨号配置页面都会有一个“按需拨号”的选项,若没有到外部网络的数据流,PPP链路就不会建立,当检测到有流量访问外部网络时,PPP就开始拨号和ISP的拨号服务器建立连接,拨号成功后才产生计费。反之,如果在一定时间内没有访问外网的流量,PPP就会断开连接,为用户节省流量费用。在宽带网络普及的今天,宽带费用基本上都是包月收费了,对家庭宽带用户此功能意义不大。不过对于3G/4G网络这种按流量收费的数据访问方式,按需拨号功能还是有其用武之地。

PPP的按需拨号功能如何实现的呢?首先调用open_ppp_loopback:

 

pppd/sys-linux.c

main() -> open_ppp_loopback():

int

open_ppp_loopback(void)

{

    intflags;

 

    looped=1//设置全局变量looped为1,后面会用到

    if(new_style_driver){

       /* allocate ourselves a ppp unit */

       if(make_ppp_unit()<0//创建PPP网络接口

           die(1);

       modify_flags(ppp_dev_fd,0,
SC_LOOP_TRAFFIC)//通过ioctl设置SC_LOOP_TRAFFIC

       set_kdebugflag(kdebugflag);

       ppp_fd=-1;

       returnppp_dev_fd;

    }

 

……(下面是old driver,忽略)

}

全局变量new_style_driver,这个变量已经在ppp_avaliable函数里被设置为1了。接下来调用make_ppp_unit打开/dev/ppp设备文件并请求建立一个新的unit。

 

pppd/sys-linux.c

main() -> open_ppp_loopback() -> make_ppp_unit():

staticintmake_ppp_unit()

{

       intx,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)
//打开/dev/ppp

       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//传入请求的unit
number,可通过/etc/ppp/options配置

       x=ioctl(ppp_dev_fd,
PPPIOCNEWUNIT,&ifunit)//请求建立一个新unit

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

       returnx;

}

这里的unit可以理解为一个PPP接口,在Linux中通过ifconfig看到的ppp0就是通过ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit)建立起来的,unit number是可以配置的,不过一般都不用配置,传入-1会自动分配一个未使用的unit number,默认从0开始。这个ioctl调用的是PPPK中注册的ppp_ioctl:

 

 

linux-2.6.18/drivers/net/ppp_generic.c

main() -> open_ppp_loopback() -> make_ppp_unit() -> ioctl(ppp_dev_fd,PPPIOCNEWUNIT,&ifunit) -> ppp_ioctl():

staticintppp_ioctl(struct
inode *inode,struct file*file,

                  
unsignedintcmd,unsignedlongarg)

{

       struct ppp_file*pf=file->private_data;

……

       if(pf==0)

              returnppp_unattached_ioctl(pf,file,cmd,arg);

 

TIPS:这里还要解释一下PPPK中channel和unit的关系,一个channel相当于一个物理链路,而unit相当于一个接口。在Multilink PPP中,一个unit可以由多个channel组合而成,也就是说一个PPP接口下面可以有多个物理链路,这里的物理链路不一定是物理接口,也可以是一个物理接口上的多个频段(channel)比如HDLC channel。

PPPK中channel用结构channel表示,unit用结构ppp表示。

 

linux-2.6.18/drivers/net/ppp_generic.c

/*

 * Data structure describing one ppp unit.

 * A ppp unit corresponds to a ppp network interface device

 * and represents a multilink bundle.

 * It can have 0 or more ppp channels connected to it.

 */

struct ppp{

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

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

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

       struct slcompress*vj;        /*
state for VJ header compression */

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

       struct sk_buff      *xmit_pending;     /*
a packet ready to go out 88 */

       struct compressor*xcomp;/*
transmit packet compressor 8c */

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

       struct compressor*rcomp; /*
receive decompressor 94 */

 

 

Android 3G 调试记录

转载的文章http://blog.csdn.net/pottichu/article/details/5910191

1.  JAVA 部分
android/development/data/etc/apns-conf_sdk.xml   --->  system/etc/apns-conf.xml
注意 apns 的版本,
<apns version="6">
<apn carrier="中国联通 3g 网络 (China Unicom)"
      apn="3gnet"
      mcc="460"
      mnc="01"
      proxy=""
      port=""
      user=""
      server=""
      password=""
      type="default,supl"
 />
</apns>

android/packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java
文件中:
public void onCreate(SQLiteDatabase db) 
会根据  system/etc/apns-conf.xml 创建
/data/data/com.android.providers.telephony/databases/telephony.db

而pppd 拨号时候需要依赖 telephony 所提供的 apn 等。


2. drvier 部分。
我的 3G 模块是 moto 的 HTM1000, USB 接口。3G 驱动简单点说就是一个 usb 转串口的驱动。
模块提供 2 个 tty 设备, 一个用来发送 AT 指令, 一个用于 pppd 拨号。


3. C 部分。
cd android/hardware/ril/

3.1, 先说说 rild/radiooptions.c
这是一个测试程序,
    perror("Usage: radiooptions [option] [extra_socket_args]/n/
           0 - RADIO_RESET, /n/
           1 - RADIO_OFF, /n/
           2 - UNSOL_NETWORK_STATE_CHANGE, /n/
           3 - QXDM_ENABLE, /n/
           4 - QXDM_DISABLE, /n/
           5 - RADIO_ON, /n/
           6 apn- SETUP_PDP apn, /n/
           7 - DEACTIVE_PDP, /n/
           8 number - DIAL_CALL number, /n/
           9 - ANSWER_CALL, /n/
           10 - END_CALL /n");
}
可以用来测试接打电话等。

选项 6 可以用来测试 pppd 拨号。


3.2  rild/rild.c
如果有权限问题, 可以直接修改 init.rc 直接给 rild root 权限。

main 函数中会读取 /proc/cmdline 中的  android.ril= 来决定 tty设备的名称。
所以不用那么麻烦了, 直接写成自己的设备名称就行了。
            arg_overrides[1] = "-d";
            arg_overrides[2] = "/dev/ttyMOT1";

3.3 reference-ril/reference-ril.c
拨号流程清参考另一篇文章。
            asprintf(&cmd, "AT+CGACT=0,%d", s_defultPDPCid);
            err = at_send_command(cmd, NULL);
            free(cmd);

            asprintf(&cmd, "AT+CGDCONT=%d,/"IP/",/"%s/",,0,0", s_defultPDPCid, apn);
            err = at_send_command(cmd, NULL);
            free(cmd);
也 就相当于发送:   'AT+CGDCONT=1 “ip", "3gnet", 0,0'
这点很重要,
接下来本来是需要发送 "ATD*99***1#" 可是我的情况很怪异,
用 /dev/ttyMOT0 可以 成功,并且 pppd  也能成功, 但用 /dev/ttyMOT1 就不能成功, 无耐之下。。
只好让 /dev/ttyMOT1 作为 rild 使用的接口。
/dev/ttyMOT0 作为 pppd 的接口,并且 "ATD*99***1#"由 /dev/ttyMOT0 去发送, 无奈之下只好再移植一个 chat .
接下来:
    property_set("net.ppp0.status", "0");
    asprintf(&cmd, "/system/bin/pppd connect '/system/bin/chat -esvf /system/etc/Talk.chat' user %s password %s /dev/ttyMOT0 115200  crtscts modem novj noipdefault nobsdcomp usepeerdns defaultroute  noauth debug nodetach dump &",(username == NULL) ? "any" : username, (password == NULL) ? "any" : password);
               err = system(cmd);
               LOGD("Launch: %s and ret is: %d!", cmd, err);
               free(cmd);

                /* wait for system update property */
for(iretry=0; iretry<ppp_retry; iretry++)
 {
        sleep(2);
        property_get("net.ppp0.status", pppstatus, NULL);
        LOGD("net.ppp0.status = %s/n", pppstatus);
        if(atoi(pppstatus) > 0)
               break;
 }

由于 pppd 不会立即返回,所以只好 修改 pppd如果连接成功则
    property_set("net.ppp0.status", "1");


#cat /system/etc/Talk.chat
ABORT 'BUSY'
ABORT 'NO ANSWER'
ABORT 'ERROR'
TIMEOUT 20
'' 'ATZ'
OK ATDT*99***1#
CONNECT  ""


pppd 连接成功以后会 设置网关和 dns 等。

 

另外, android 默认是打开拨号上网功能的。 但也有一些手机做了个开关。

 

 

pppd拨号脚本配置

和PPP连接关系密切的有两个脚本是chat和options两个脚本。其中,chat脚本是用来进行AT呼叫和控制的脚本,而options脚本就影响PPP连接的。


1.  CHAT脚本
   一个简单的chat脚本有下面的结构:
""              AT
OK             ATDT  dialnumber
CONNECT      ""
一个chat脚本都是由字符串对来组成的。前面的字符串都是期望获取的串,紧跟的是发送的字符串。这个脚本每一项的具体含义是这样的:
1)    MODEM期望空字符串。这句话的意思直接理解就是MODEM不管收到什么字符串,先发出字符串AT;
2)    期望收到“OK”字符串,然后发送字符串“ATDT dialnumber”
3)    如果收到“CONNECT”,就不再发送,认为数据链路已建立连接。
  这样的chat脚本是最简单的,如果需要进行超时控制,就可以加入如下字段:
TIMEOUT 10
如果要增加对特殊情况的处理,就加入下面字段:
        ABORT           BUSY          
        ABORT           NO ANSWER
        ABORT           RINGING
这三行语句的意思是:如果收到字符串“BUSY”、“NO ANSWER”、“RINGING”就退出执行。
所以在考虑到各种特殊情况下,配置一个PPP连接的CHAT脚本就可以像下面这样:
TIMEOUT         30
ABORT           BUSY          
    ABORT           NO ANSWER
    ABORT           RINGING
 ""                 AT
OK                ATDT dialnumber
CONNECT         ""


2.OPTIONS脚本
OPTIONS脚本的内容,为PPP连接指定了连接使用的设备、使用的控制字符传输速率、指定了硬件加速、溢出控制等。
例如下面的options脚本:
ttyS0 ---- 指定连接使用的设备,例如:ttyS0、ttyS1等
57600 ---- 设置连接使用的控制字符传输速率,可以设置为57600、115200等
debug ---- 如果需要加入调试信息,就加入参数debug
logfile /var/ ppplog ---- 将连接过程中的信息输入到某个文件中
mtu 1500
-detach
noipdefault ---- 不使用默认IP就可以加入参数noipdefault
defaultroute
usepeerdns ---- 使用服务器端协商的DNS就可以设置参数usepeerdns
lcp-echo-failure 4 ---- 当连续4次没有收到发出的LCP回声请求时,就认为服务器端已不再响应,就退出执行。这里的失败次数可以灵活来决定。
-ccp ---- 不使用压缩控制协议
-vj ---- 关掉式IP头压缩
-chap ---- 不使用chap鉴权
-mschap-v2 ---- 不使用mschap鉴权
user
hide-password
connect "/usr/bin/chat -v -t6 -f /var/ chat" ---- 制定了要使用的chat脚本的位置。加上参数-v 告诉 chat命令将其所有的输出/入拷贝到系统记录里(通常是 /var/log/messages)。-t 6 指定了执行chat该命令的时间为6s。chat脚本的位置可以位于/etc/目录下,也可以位于/var下,这个可以更加需要灵活设置。

persist --- 永久链接(自动重拨)

crtscts --- 告诉ppp使用modem的硬件流量控制

modem --- 使ppp使用DCD信号来判断连接是否正常,有无掉线现象

deflate --- 使pppd使用defalte压缩方式

idle --- 设置了一个时间限制,当在300秒的时间内没有数据传送,就断开连接

lock --- 则创建一个锁定文件,其他程序在发现存在这个文件后,就能得知相应的串口已经被使用。

demond --- 参数告诉pppd停留在后台,监视网络数据,一旦有要求就立即进行连网,超时后就断开连接,但pppd仍然停留在后台等待下次数据传送

其他的参数具体含义可以参照参考PPPD2.4.4 中options.c中注释:


int debug = 0;  
int kdebugflag = 0;  
int default_device = 1; 
char devnam[MAXPATHLEN]; 
bool nodetach = 0;  
bool updetach = 0;  
int maxconnect = 0;  
char user[MAXNAMELEN]; 
char passwd[MAXSECRETLEN]; 
bool persist = 0;  
char our_name[MAXNAMELEN]; 
bool demand = 0;  
char *ipparam = NULL; 
int idle_time_limit = 0; 
int holdoff = 30;  
bool holdoff_specified; 
int log_to_fd = 1;  
bool log_default = 1; 
int maxfail = 10;  
char linkname[MAXPATHLEN]; 
bool tune_kernel;  
int connect_delay = 1000; 
int req_unit = -1;  
bool multilink = 0;  
char *bundle_name = NULL; 
bool dump_options;  
bool dryrun;   
char *domain;  
int child_wait = 5;  

 

3. 鉴权脚本
    一般情况下,PPP连接是需要进行身份认证的。签权方式有两种,一种是PAP鉴权,另一种是CHAP鉴权。鉴权需要的用户名和密码是存放在PAP-secrets和chap-secrets脚本中,以如下方式存放:
Username * password
    需要进行鉴权时,通过在options脚本中指定鉴权方式为PAP或CHAP,PPP模块就会从PAP-secrets和chap-secrets脚本中读出用户名和密码,附加到PPP的鉴权包中,发送到服务器端进行身份认证。


4. PPP拨号过程与脚本之间的关系
  脚本设置成功以后,怎么和pppd2.4.4一起工作呢?这个需要从pppd程序的主函数入手。
这个过程是这样的:
  Pppd程序启动以后,就会按照pathname.h中所指定的文家位置去寻找options脚本文件。这个可以按照需要指定文件的位置,确保pppd能够准确找到该文件。
  Pppd找到options文件后,按顺序读入参数行。在这里,就可以获取系统要使用的是哪个设备来进行联网、设备的速率是多少。
  Pppd 将配置的参数解析为程序当中的全局变量标识符,进行pppd连接控制。


实践情况
下面是FT282 上的PPP连接使用的OPTIONS、 CHAT和PAP-SECRETS脚本。
OPTIONS:
connect "/usr/bin/chat -v -t6 -f /var/ppp/td-scdma_chat"
ttyUSB0
115200
debug
logfile /var/log/ppplog
mtu 1500
-detach
noauth
noipdefault
defaultroute
usepeerdns
crtscts
lock
lcp-echo-failure 4
-ccp
-vj
-chap
-mschap-v2
user
hide-password
CHAT:
    "" AT
OK ATDT*98*1#
CONNECT ""
PAP-SECRETS:
   wap *wap
经过在FT282上进行测试,发现能够顺利进行PPP连接。

 

pppd(pap,chap,eap控制协议) 使用dev/ppp。pppd实现了所有鉴权、压缩/解压和加密/解密等扩展功能的控制协议。

 

android 3G modem 移植参考文章:

http://blog.csdn.net/bi511304183/article/details/16878717

http://www.docin.com/p-529954249.html

http://www.2cto.com/kf/201108/99933.html

http://blog.chinaunix.net/uid-20786208-id-3178315.html

http://www.docin.com/p-529954249.html

http://blog.chinaunix.net/uid-9688646-id-3138573.html

http://www.cnblogs.com/Harvey-Technology/articles/3754824.html

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PPP(Point-to-Point Protocol)协议是一种数据链路层协议,用于在两个网络节点之间建立点对点的连接。在思科模拟器中,可以使用PPP协议来封装传输加密信息,以保证数据的安全性。下面是使用思科模拟器配置路由器接口使用PPP协议封装并协商路由器之间通信的共享秘钥(身份验证)的步骤: 1. 首先,在思科模拟器中创建两个路由器,并将它们连接起来。 2. 在两个路由器上分别配置接口IP地址,并启用接口。 3. 在两个路由器上分别配置PPP协议,并启用PAP或CHAP身份验证。 4. 在路由器1上,输入以下命令来配置PPP协议: ``` Router1#configure terminal Router1(config)#interface serial 0/0/0 Router1(config-if)#encapsulation ppp Router1(config-if)#ppp authentication pap Router1(config-if)#exit ``` 5. 在路由器2上,输入以下命令来配置PPP协议: ``` Router2#configure terminal Router2(config)#interface serial 0/0/0 Router2(config-if)#encapsulation ppp Router2(config-if)#ppp authentication pap Router2(config-if)#exit ``` 6. 在路由器1上,输入以下命令来配置PAP身份验证: ``` Router1(config)#username Router2 password cisco ``` 7. 在路由器2上,输入以下命令来配置PAP身份验证: ``` Router2(config)#username Router1 password cisco ``` 8. 现在,两个路由器之间的PPP协议已经配置完成。可以使用`show interfaces`命令来查看接口状态和PPP协议的配置情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值