PPTP协议握手流程分析

http://blog.csdn.net/hdxlzh/article/details/46711901

一  PPTP概述


        PPTP(Point to Point Tunneling Protocol),即点对点隧道协议。该协议是在PPP协议的基础上开发的一种新的增强型安全协议,支持多协议虚拟专用网,可以通过密码验证协议,可扩展认证协议等方法增强安全性。远程用户可以通过ISP、直接连接Internet或者其他网络安全地访问企业网;

        它能够将PPP(点到点协议)帧封装成IP数据包,以便能够在基于IP的互联网上进行传输。PPTP使用TCP是实现隧道的创建、维护与终止,并使用GRE(通用路由封装)将PPP帧封装成隧道数据。被封装后的PPP帧的有效载荷可以被加密或压缩;

        PPTP通信过程中需要建立两种连接,一种是控制连接,另一种是数据连接。控制连接用来协商通信过程中的参数和进行数据连接的维护。而真正的数据通信部分则交由PPTP数据连接完成。以下两个章节分别介绍PPTP的控制连接和数据连接


二  PPTP控制连接的建立流程分析


    PPTP控制连接建立过程可以分为以下几步:

    1、  建立TCP连接

    2、  PPTP控制连接和GRE隧道建立

    3、  PPP协议的LCP协商

    4、  PPP协议的身份验证

    5、  PPP协议的NCP协商

    6、  PPP协议的CCP协商

    以下以抓包的方式分析上述几个步骤,

    pptp client     :      192.168.163.56 

    pptp server    :      192.168.162.196


2.1  建立TCP连接


    PPTP控制层协议是建立在TCP协议的基础上,所以刚开始即使普通的TCP三次握手


图2-1  TCP三次握数据包


图2-2  TCP三次握手时序图


    1、  Client端向Server的1723端口发TCPSYN包,请求建立TCP连接。

    2、  Server接收TCP连接请求,回SYN ACK。

    3、  Client端向Server发送确认包ACK


2.2  PPTP控制连接和隧道的建立


    在此过程,完成PPTP控制层连接和Gre隧道建立的工作

图2-3 PPTP控制层连接建立过程



图2-4 PPTP控制层连接时序图


    1、  Client向Server发送Start-Control-Connection-Request,请求建立控制连接

    2、  Server向Client发送Start-Control-Connecton-Reply,应答客户端的请求

    3、  Client向Server发送Outgoing-Call-Request,请求建立PPTP隧道,该消息包含GRE报头中的Callid,该id可唯一地标识一条隧道

    4、  Server向Client发送Outgoing-Call-Reply,应答客户端的建立PPTP隧道请求

    5、  有Client或者Server任意一方发出Set-Link-info,设置PPP协商的选项

2.3 PPP协议的LCP协商


    LCP是PPP协议的链路控制协议,负责建立、拆除和监控数据链路。协商链路参数,如认证方法,压缩方法,是否回叫等。


图2-5  PPP协议的LCP协商过程



图2-6  PPP协议的LCP协商时序图


    1、  Client发送一个Configuration Request,把自己的配置参数发送给Server

    2、  Server发送一个Configuration Request,把自己的配置参数发送给Client

    3、  Server发送一个Configuration Reject,将自己不能识别的参数告知Client,让Client进行修正

    4、  Client发送一个Configuration Ack,表示所有配置参数全部认识且可以接受,应答Server

    5、  Client修改配置项后再次发送Configuration Request

    6、  Server发送一个Configuration Ack,标示所有配置参数全部认识且可以接受,应答Client


2.4  PPP协议的身份认证


    LCP协商完成后,PPP协议的Server端会对Client端进行身份验证,在LCP协商中已经协商好身份验证协议,本文以MS-CHAP-2为例说明


图2-7  PPP协议的Chap身份认证过程



图2-8  PPP协议的Chap身份认证时序图


    1、  Server向Client发送Challenge,其中包括一个Challenge string(value字段)和Server Name(pptpd)

    2、  Client向Server发送Response,其中用户名使用明文发送,密码(syberos)和Challenge字段混合hash后以密文(value字段)形式发送

    3、  Server读取密码文件,对用户身份进行验证,验证成功,向Client发送Success,表示身份验证成功


2.5  PPP协议的NCP协商


        NCP协议是PPP协议的网络控制协议,主要用来协商双方网络层接口参数,配置虚拟端口,分配IP,DNS等信息。图中的IPCP是NCP基于TCP/IP的接口协商协议。Server和Client都要把自己的Miniport信息发送给对方


图2-9  PPP协议的NCP协商过程



图2-10  PPP协议的NCP协商时序图


    1、  Server把自己的Miniport信息通过Configuration Request发送给Client

    2、  Client接收Server的接口配置,向Server发送Configuration ACK,应答上一步骤的Request

    3、  Client把自己的Miniport信息(无效数据,等待Server分配)通过Configuration Request发送给Server

    4、  Server发现Client的配置是无效的,则给Client发送一条有效的配置信息,使用Configuration Nak发送,其中主要是给Client分配ip

    5、  Client根据Server端发送过来的配置,修改自己的Miniport的接口,再次发送Configuration Request

    6、  Server接受Client的配置,发送Configuration Ack应答Request


2.6  PPP协议的CCP协商


    CCP协议协商PPP通讯中数据加密的协议


图2-11  PPP协议的NCP协商过程



图2-12  PPP协议的NCP协商时序图


    1、  Server向Client发送Configuration Request,标识服务端支持的加密协议

    2、  Client向Server发送Configuration Request,标识客户端支持的加密协议

    3、  Client向Server发送Configuration ack,标识接受服务端的加密协议

    4、  Server向Client发送Configuration ack,标识接受客户端的加密协议


三  PPTP数据连接的分析


    PPTP数据隧道化过程采用多层封装的方法,下图显示了封装后在网络上传输的数据包格式

图3-1 PPTP数据包格式


    我们以应用层使用HTTP连接为例说明PPTP数据包的封装和解析过程;


3.1  PPTP数据包封装过程



图3-2 PPTP数据包封装过程



    封装过程:

    1、  应用层数据封装成IP数据包

    2、  将IP数据包发送到VPN的虚拟接口

    3、  VPN的虚拟接口将IP数据包压缩和加密,并增加PPP头

    4、  VPN的虚拟接口将PPP帧发送给PPTP协议驱动程序

    5、  PPTP协议驱动程序在PPP帧外添加GRE报头

    6、  PPTP协议驱动程序将GRE报头提交给TCP/IP协议驱动程序

    7、  TCP/IP协议驱动程序为GRE驱动添加IP头部

8、  为IP数据包进行数据链路层封装后通过物理网卡发送出去

3.2  PPTP数据包解析过程



图3-2 PPTP数据包解析过程


    解析过程:

    1、物理thernet帧

    2、剥掉Ethernet帧后交给TCP/IP协议驱动程序

    3、TCP/IP协议解析剥掉IP头部

    4、IP协议解析剥掉GRE头部

    5、将PPP帧发送给VPN虚拟网卡

    6、VPN虚拟网卡剥掉PPP头并对PPP有效负载进行解密或者解压缩

    7、解密或者解压缩完成后将数据提交给上层应用

    8、上层应用对数据进行处理



          从应用层看,Service和Client是直接点对点连接的,他们之间的媒介就是GRE。而这个GRE的底层并不是真正的点对点连接,

而是建立在物理网络上的一个隧道,保护传输的数据;



pppd源码详解 http://www.xuebuyuan.com/1954248.html

前言:

PPP(Point to Point Protocol)协议是一种广泛使用的数据链路层协议,在国内广泛使用的宽带拨号协议PPPoE其基础就是PPP协议,此外和PPP相关的协议PPTP,L2TP也常应用于VPN虚拟专用网络。随着智能手机系统Android的兴起,PPP协议还被应用于GPRS拨号,3G/4G数据通路的建立,在嵌入式通信设备及智能手机中有着广泛的应用基础。本文主要分析Linux中PPP协议实现的关键代码和基本数据收发流程,对PPP协议的详细介绍请自行参考RFC和相关协议资料。

模块组成:


上图为PPP模块组成示意图,包括:

PPPD:PPP用户态应用程序

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

PPP线路规程*:PPP TTY线路规程,kernel源码在/drivers/net/下的ppp_async.c, ppp_synctty.c,本文只考虑异步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中的主干代码剥离出来,遇到某些重要的系统调用,我会详细分析其在Linux内核中的具体实现。

源码分析:

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,

                   
unsigned
intcmd,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 */

       void       *rc_state;      /*
its internal state 98 */

      


  • 5
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值