PPTP隧道应用详解

2023-12-19 10:48
文章标签 应用 详解 隧道 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 ----->---|

 

完。

这篇关于PPTP隧道应用详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/511889

相关文章

MyBatis编写嵌套子查询的动态SQL实践详解

《MyBatis编写嵌套子查询的动态SQL实践详解》在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结... 目录一、Myhttp://www.chinasem.cnBATis动态SQL的核心优势1. 灵活性与可

Python使用Tkinter打造一个完整的桌面应用

《Python使用Tkinter打造一个完整的桌面应用》在Python生态中,Tkinter就像一把瑞士军刀,它没有花哨的特效,却能快速搭建出实用的图形界面,作为Python自带的标准库,无需安装即可... 目录一、界面搭建:像搭积木一样组合控件二、菜单系统:给应用装上“控制中枢”三、事件驱动:让界面“活”

Python struct.unpack() 用法及常见错误详解

《Pythonstruct.unpack()用法及常见错误详解》struct.unpack()是Python中用于将二进制数据(字节序列)解析为Python数据类型的函数,通常与struct.pa... 目录一、函数语法二、格式字符串详解三、使用示例示例 1:解析整数和浮点数示例 2:解析字符串示例 3:解

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

MySQL 表的内外连接案例详解

《MySQL表的内外连接案例详解》本文给大家介绍MySQL表的内外连接,结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录表的内外连接(重点)内连接外连接表的内外连接(重点)内连接内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,我

Windows 系统下 Nginx 的配置步骤详解

《Windows系统下Nginx的配置步骤详解》Nginx是一款功能强大的软件,在互联网领域有广泛应用,简单来说,它就像一个聪明的交通指挥员,能让网站运行得更高效、更稳定,:本文主要介绍W... 目录一、为什么要用 Nginx二、Windows 系统下 Nginx 的配置步骤1. 下载 Nginx2. 解压

RabbitMQ工作模式中的RPC通信模式详解

《RabbitMQ工作模式中的RPC通信模式详解》在RabbitMQ中,RPC模式通过消息队列实现远程调用功能,这篇文章给大家介绍RabbitMQ工作模式之RPC通信模式,感兴趣的朋友一起看看吧... 目录RPC通信模式概述工作流程代码案例引入依赖常量类编写客户端代码编写服务端代码RPC通信模式概述在R

详解如何使用Python从零开始构建文本统计模型

《详解如何使用Python从零开始构建文本统计模型》在自然语言处理领域,词汇表构建是文本预处理的关键环节,本文通过Python代码实践,演示如何从原始文本中提取多尺度特征,并通过动态调整机制构建更精确... 目录一、项目背景与核心思想二、核心代码解析1. 数据加载与预处理2. 多尺度字符统计3. 统计结果可

Linux基础命令@grep、wc、管道符的使用详解

《Linux基础命令@grep、wc、管道符的使用详解》:本文主要介绍Linux基础命令@grep、wc、管道符的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录grep概念语法作用演示一演示二演示三,带选项 -nwc概念语法作用wc,不带选项-c,统计字节数-

SpringCloud中的@FeignClient注解使用详解

《SpringCloud中的@FeignClient注解使用详解》在SpringCloud中使用Feign进行服务间的调用时,通常会使用@FeignClient注解来标记Feign客户端接口,这篇文章... 在Spring Cloud中使用Feign进行服务间的调用时,通常会使用@FeignClient注解