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 篇原創文章 · 獲贊 7 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章