1. 程式人生 > >PPTP隧道應用詳解

PPTP隧道應用詳解

本文內容基於PPTPD應用層程式pptpd-1.4.0版本。初始化時在pptp_manager函式中,新建一個TCP的套介面監聽PPTP客戶端的控制連線請求,由函式createHostSocket完成,此套介面監聽在PPTP的協議埠1723(PPTP_PORT)上:

static int createHostSocket(int *hostSocket)
{        
    if ((*hostSocket = vrf_socket(vrf, AF_INET, SOCK_STREAM, 0)) <= 0)
        return -1;
    
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PPTP_PORT);
    
    if (bind(*hostSocket, (struct sockaddr *) &address, sizeof(address)) < 0)
        return -3;
}

客戶端的開始控制連線請求(Start-Control-Connection-Request)報文如下:

由於pptpd監聽在TCP的1723埠,故此資料包會由核心送到pptpd處理。pptpd服務讀取pptp_header大小的資料,用於判斷其型別是否為開始空氣連線請求START_CTRL_CONN_RQST(1),如果為真pptpd將fork一個新的程序pptpctrl來處理與客戶端的控制報文互動,pptpd繼續監聽新的客戶端連線:

pptpctrl程序的函式pptp_handle_ctrl_connection具體處理控制報文互動。函式read_pptp_packet和send_pptp_packet接收和傳送pptp報文。互動完成之後,客戶端將進行PPP的鏈路協商,由pppd程序處理。pptpctrl程序繼續處理控制鏈路的保活與拆除等工作。

PPP協商

PPTP協議使用增強的GRE通道傳輸PPP幀,IP頭部的協議欄位protocol為47表明為GRE協議,GRE頭部的協議型別為0x880b表明為PPP協議。pptpctrl程序在處理完客戶端的OUT_CALL_RQST(7)請求之後,建立一個RAW套介面(pptp_gre_init)監聽客戶端的GRE-PPP報文,同時建立pppd程序(startCall)處理後續的ppp資料報文。  

static void pptp_handle_ctrl_connection(char **pppaddrs, struct in_addr *inetaddrs)
{
    if (FD_ISSET(clientSocket, &fds)) {
        switch (read_pptp_packet(clientSocket, (unsigned char *) packet, rply_packet, &rply_size)) {
        case OUT_CALL_RQST:
                pty_fd = startCall(pppaddrs, inetaddrs);
                if (pty_fd > maxfd) maxfd = pty_fd;
                if ((gre_fd = pptp_gre_init(call_id_pair, pty_fd, inetaddrs)) > maxfd)
                        maxfd = gre_fd;
                break;
		}
	}
}

有一點需要注意的是,pptpd並沒有採用Linux核心的pptp驅動(AF_PPPOX套介面)處理資料包,而是使用通用的RAW套介面,參見pptp_gre_init程式碼。建立套介面時指定了GRE協議號PPTP_PROTO(47),並且將套介面關聯了對端的IP地址。

函式startCall建立pppd程序之前,建立了一對pty/tty主從裝置,pptpctrl程序關閉tty從裝置,保留主裝置pty。同時將tty從裝置複製為pppd程序的標準輸入和標準輸出,兩個程序即可通過這一對pty/tty裝置通訊了。

int pptp_gre_init(u_int32_t call_id_pair, int pty_fd, struct in_addr *inetaddrs)
{
    gre_fd = vrf_socket(vrf, AF_INET, SOCK_RAW, PPTP_PROTO);

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr = inetaddrs[0];
    addr.sin_port = 0;
    bind(gre_fd, (struct sockaddr *) &addr, sizeof(addr));

    addr.sin_family = AF_INET;
    addr.sin_addr = inetaddrs[1];
    addr.sin_port = 0;
    connect(gre_fd, (struct sockaddr *) &addr, sizeof(addr));
}

由RAW套介面接收到的GRE-PPP資料幀,pptpctrl程序呼叫decaps_gre函式,進行GRE相關校驗,去掉GRE頭部資料,其餘PPP協議資料進行HDLC編碼,由pty裝置送到pppd程序處理。與之相反,pppd處理完成回覆的資料,經tty從裝置發回pptpctrl程序,由decaps_hdlc函式去掉HDLC編碼,加上GRE頭部資訊,通過RAW套介面返回給客戶端。

核心RAW套介面

程序pptpctrl使用的RAW套介面,由引數協議族AF_INET、型別SOCK_RAW和協議號PPTP_PROTO(47)指定,核心inet_create函式負責建立此套介面。由以下inetsw_array中SOCK_RAW型別的定義可見,其支援的協議為IPPROTO_IP,並不是PPTP_PROTO。其實這裡的IPPROTO_IP是個通配型別,在inet_create函式中都能匹配上。

static struct inet_protosw inetsw_array[] =
{   
    {   
        .type =       SOCK_RAW,
        .protocol =   IPPROTO_IP,    /* wild card */
        .prot =       &raw_prot,
        .ops =        &inet_sockraw_ops,
        .flags =      INET_PROTOSW_REUSE,
    }
};

對於協議號PPTP_PROTO的處理,有兩個地方,其一賦值給了inet層套介面的成員inet_num;其二,由於inet_num的存在,將此套介面連結到了以inet_num的值為索引的RAW套介面HASH雜湊表中,參見函式raw_hash_sk。

static int inet_create(struct net *net, struct socket *sock, int protocol, int kern)
{
    if (SOCK_RAW == sock->type) {
        inet->inet_num = protocol;
    }
    if (inet->inet_num) {
        err = sk->sk_prot->hash(sk);
    }
}
int raw_hash_sk(struct sock *sk)
{
    struct raw_hashinfo *h = sk->sk_prot->h.raw_hash;
    head = &h->ht[inet_sk(sk)->inet_num & (RAW_HTABLE_SIZE - 1)];
    sk_add_node(sk, head);
}

核心的RAW系統在接收到客戶端的GRE-PPP資料包時,通過資料包中的協議欄位protocol,在雜湊列表中查詢本機是否有註冊的套介面監聽此協議資料包。為真將資料包送往raw_v4_input函式處理。

int raw_local_deliver(struct sk_buff *skb, int protocol)
{
    hash = protocol & (RAW_HTABLE_SIZE - 1);
    raw_sk = sk_head(&raw_v4_hashinfo.ht[hash]);

    if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))
        raw_sk = NULL;
}

raw_v4_input函式使用__raw_v4_lookup查詢具體的監聽套介面,pptpctrl建立的套介面,指定了協議號inet_num,和本機、對端的IP地址,只有符合這三個條件的資料包送往應用層pptpctrl處理。

struct sock *__raw_v4_lookup(struct net *net, struct sock *sk, unsigned short num, __be32 raddr, __be32 laddr, int dif, int sdif)
{
    sk_for_each_from(sk) {
        struct inet_sock *inet = inet_sk(sk);

        if (net_eq(sock_net(sk), net) && inet->inet_num == num  &&
            !(inet->inet_daddr && inet->inet_daddr != raddr)    &&
            !(inet->inet_rcv_saddr && inet->inet_rcv_saddr != laddr) &&
            !(sk->sk_bound_dev_if && sk->sk_bound_dev_if != dif &&
              sk->sk_bound_dev_if != sdif))
            goto found; /* gotcha */
    }
    sk = NULL;
found:
    return sk;
}

PPTP控制報文流程

              |----------------|                   
              |                |                   
      IN ---->| KERNEL network |-----> PPTPD ---> pty           |---->--->-- OUT
              |                |                   |            |
              |----------------|                   |            |
                                                   |            |
                                                   |     |------------------|
              |----------------|                   |     |  KERNEL NETWORK  |
              |    KERNEL      |                   |     |------------------|
         |-<--|   PPP subsys   |----<----------<---|            |
         |    |                |                                | 
         |    |----------------|                               PPTPD
         |                                                      |
         |------------------->---- PPPD ------->-- tty ----->---|

完。