内核实现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

相关文章

Java StringBuilder 实现原理全攻略

《JavaStringBuilder实现原理全攻略》StringBuilder是Java提供的可变字符序列类,位于java.lang包中,专门用于高效处理字符串的拼接和修改操作,本文给大家介绍Ja... 目录一、StringBuilder 基本概述核心特性二、StringBuilder 核心实现2.1 内部

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

SpringBoot AspectJ切面配合自定义注解实现权限校验的示例详解

《SpringBootAspectJ切面配合自定义注解实现权限校验的示例详解》本文章介绍了如何通过创建自定义的权限校验注解,配合AspectJ切面拦截注解实现权限校验,本文结合实例代码给大家介绍的非... 目录1. 创建权限校验注解2. 创建ASPectJ切面拦截注解校验权限3. 用法示例A. 参考文章本文

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详