内核实现PPTP数据通道

2023-12-19 10:48

本文主要是介绍内核实现PPTP数据通道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

开源的PPTP程序pptp-1.4.0未使用内核的新版PPTP驱动程序,如文章所述。结果是,所有的GRE报文都要上送到应用层pptpd处理,处理完GRE报头之后,还需要通过ppp的tty驱动送入内核的PPP系统处理内层的ppp数据部分。内核新增加的PPTP模块把GRE的处理放到了内核驱动中,不再上送PPTPD应用。并且,内核PPTP驱动提供了一个新的协议族套接口AF_PPPOX,可以为上层应用使用提供接收GRE报文,另外也可以对接内核PPP系统,作为内核PPP的通道使用。


PPTP-GRE套接口的建立

根据PPTP协议在PPTP的协商完成之后,开始交互PPTP-GRE的报文。所以在此之前,pptpd服务端程序需要创建一个PPPOX的套接口,以便接收PPTP-GRE报文,同时需要绑定本地的call id和地址,用于区分不同的pptp连接。pptp_bind函数处理套接口绑定请求,套接口地址需要指定为sockaddr_pppox类型,每个call id对应一个内核中的通道channel,故绑定函数主要是添加一个channel,如果用户未指定call id,内核将自动选择一个未使用的id,最大支持65535个id值。参见函数add_chan,内核将call id与套接口的对应关系保存在全局变量callid_sock中。之后将此套接口的状态更改为已绑定PPPOX_BOUND。

static int add_chan(struct pppox_sock *sock, struct pptp_addr *sa)
{if (!sa->call_id)   {call_id = find_next_zero_bit(callid_bitmap, MAX_CALLID, call_id + 1);if (call_id == MAX_CALLID) {call_id = find_next_zero_bit(callid_bitmap, MAX_CALLID, 1);}sa->call_id = call_id;} else if (test_bit(sa->call_id, callid_bitmap)) {goto out_err;}sock->proto.pptp.src_addr = *sa;set_bit(sa->call_id, callid_bitmap);rcu_assign_pointer(callid_sock[sa->call_id], sock);
}

另外此套接口还需要绑定一个远端通信的PPPOX套接口地址,以及作为一个通道channel注册自身到内核的PPP系统中。具体由pptp_connect函数实现。用户层需要在connect函数中提供远端的地址(sockaddr_pppox),并且初始化通道参数,比如操作集ops、头部长度hdrlen等,调用ppp_register_channel函数进行注册。

static int pptp_connect(struct socket *sock, struct sockaddr *uservaddr, int sockaddr_len, int flags)
{po->chan.private = sk;po->chan.ops = &pptp_chan_ops;po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header);error = ppp_register_channel(&po->chan);opt->dst_addr = sp->sa_addr.pptp;sk->sk_state |= PPPOX_CONNECTED;
}

PPTP-GRE通道提供给内核PPP系统发送和ioctl控制的函数接口,注册完成之后ppp报文即可由此通道发送。至此,PPTP-GRE套接口的初始化工作完成。

static const struct ppp_channel_ops pptp_chan_ops = {.start_xmit = pptp_xmit,.ioctl      = pptp_ppp_ioctl,
};

GRE-PPP报文接收

内核使用gre_add_protocol注册了PPTP GRE的接收函数pptp_rcv,注意第二个参数GREPROTO_PPTP,其值为1,即为GRE协议的版本1。版本0为通用的CISCO定义的GRE标准。这样在数据包由IP层向四层协议提交时,根据协议号47(GRE)和版本1,将会进入到pptp_rcv函数处理。

static const struct gre_protocol gre_pptp_protocol = {.handler = pptp_rcv,
};
static int __init pptp_init_module(void)
{err = gre_add_protocol(&gre_pptp_protocol, GREPROTO_PPTP);
}

pptp_rcv函数查找是否有AF_PPPOX套接口在监听此数据包中指定的call_id,判断数据包的源地址是否与套接口中指定目的地址相同,此两个条件已经在套接口初始化中分别使用bind和connect系统调用完成,找到之后,将数据包送往此套接口,通过sk_receive_skb调用接收。条件判断在函数lookup_chan中实现。

static struct pppox_sock *lookup_chan(u16 call_id, __be32 s_addr)
{struct pppox_sock *sock;struct pptp_opt *opt;sock = rcu_dereference(callid_sock[call_id]);if (sock) {opt = &sock->proto.pptp;if (opt->dst_addr.sin_addr.s_addr != s_addr)sock = NULL;elsesock_hold(sk_pppox(sock));}return sock;
}

在套接口创建(pptp_create)时,内核注册了套接口接收函数(pptp_rcv_core),此函数负责处理GRE头部信息字段,之后将数据包送往内核ppp系统进行处理,即ppp_input函数。

static int pptp_rcv_core(struct sock *sk, struct sk_buff *skb)
{ppp_input(&po->chan, skb);return NET_RX_SUCCESS;
}

对于PPP协商报文,由内核PPP系统与用户层PPPD交互处理,对于PPP数据可直接在内核的PPP系统中处理完成,最终通过PPTP通道注册的start_xmit回调函数(pptp_xmit)发出,ppto_xmit对数据包封装GRE头部信息和IP头部信息,调用ip_local_out发往对端。
以上可见,PPTP-GRE数据包的处理不再需要到用户层pptpd进行处理,通过PPTP套接口驱动在内核中就可处理,以及与内核PPP系统进行直接的交互。PPTP-GRE数据流程如下:

                                      +--------------+|              ||  USER PPPD   ||              |+--------------+|  ||  |+----------------+      +--------------+IN ---->|                |----->|              ||   KERNEL PPTP  |      |  KERNEL PPP  |OUT <----|                |<-----|              |+----------------+      +--------------+

 

内核版本

Linux-4.15

 

这篇关于内核实现PPTP数据通道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核