7. 从零用Rust编写正反向代理, HTTP及TCP内网穿透原理及运行篇
wmproxy
wmproxy
是由Rust
编写,已实现http/https
代理,socks5
代理, 反向代理,静态文件服务器,内网穿透,配置热更新等, 后续将实现websocket
代理等,同时会将实现过程分享出来, 感兴趣的可以一起造个轮子法
项目 ++wmproxy++
gite: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
内网、公网
内网:也叫做局域网,通常指单一的网络环境。例如你家里的路由器网络、网吧、公司网络、学校网络。网络大小不定,内网中的主机可以互联互通,但是越出这个局域网访问,就无法访问该网络中的主机。
公网:就是互联网,其实也可以看做一个扩大版的内网,比如叫城际网,省域网,国网。有单独的公网IP,任何其它地址可以访问网络的可以直接访问该IP,从而实现服务。
为什么要内网穿透
内网限制
- IP不固定,通过家庭网,手机4G/5G访问的出口地址都是动态的,每次连接都会变化
- 运营商通常会做NAT转化,从而实际上你访问的出口地址其实也是一个内网地址,如通常
https://www.baidu.com/s?wd=ip
查询地址 - 常用端口无法使用,如80/443这类标准端口被直接限制不能使用。
公网优缺点
- 服务器贵,带宽贵
- IP固定,所有端口均可开放
- 带宽稳定,基本上所有高防机房或者云厂商都能提供稳定的带宽
内网穿透的场景
场景1:开发人员本地调试接口
描述:线上项目有问题或者有某些新功能,必须进行Debug进行调试和测试。
特点:本地调试、网速要求低、需要HTTP或者HTTPS协议。
需求:必须本地,必须HTTP[S]网址。
场景2:公司或者家里的本地存储或者公司内部系统
描述:如外出进行工作,或者本地有大量的私有数据(敏感不适合上云),但是自己必须得进行访问,如git服务或者照片服务等
特点:需要远程能随时随地的访问,访问内容不确定,但是需要能提供
需求:要相对比较稳定的线路,但是带宽相对要求较低
场景3:私有服务器和小伙伴开黑
描述:把自己的电脑做服务器,有时候云上的主机配置相对较高点的一个月费用极高,所以需要本地做私有服务器,或者把自己当做一台训练机
特点:对稳定性要求不用太高的,可以提供相应的服务
TCP内网穿透的原理
内网IP无法直接被访问,所以此时需求
- 内网服务器
- 公网服务器,有公网IP
此时网络如下,如此外部用户就能访问到内网服务器的数据,此时内网穿透客户端及服务端是保持长连接以方便进行推送,本质上是长链接在转发数据而实现穿透功能
Rust实现内网穿透
wmproxy一款简单易用的内网穿透工具,简单示例如下:
客户端相关
客户端配置client.yaml
# 连接服务端地址
server: 127.0.0.1:8091
# 连接服务端是否加密
ts: true# 内网映射配置的数组
mappings:#将localhost的域名转发到本地的127.0.0.1:8080- name: webmode: httplocal_addr: 127.0.0.1:8080domain: localhost#将tcp的流量无条件转到127.0.0.1:8080- name: tcpmode: tcplocal_addr: 127.0.0.1:8080domain:
启动客户端
wmproxy -c config/client.yaml
服务端相关
服务端配置server.yaml
#绑定的ip地址
bind_addr: 127.0.0.1:8091
#代理支持的功能,1为http,2为https,4为socks5
flag: 7
#内网映射http绑定地址
map_http_bind: 127.0.0.1:8001
#内网映射tcp绑定地址
map_tcp_bind: 127.0.0.1:8002
#内网映射https绑定地址
map_https_bind: 127.0.0.1:8003
#内网映射的公钥证书,为空则是默认证书
map_cert:
#内网映射的私钥证书,为空则是默认证书
map_key:
#接收客户端是为是加密客户端
tc: true
#当前服务模式,server为服务端,client为客户端
mode: server
启动服务端
wmproxy -c config/server.yaml
测试实现
在本地的8080端口上启动了一个简单的http文件服务器
http-server .
http测试
此时,8001的端口是http内网穿透通过服务端映射到客户端,并指向到8080端口,此时若访问
http://127.0.0.1:8001
则会显示
http映射是根据域名做映射此时我们的域名是127.0.0.1,所以直接返回404无法访问
此时若访问http://localhost:8001
,结果如下
我们就可以判定我们的内网转发成功了。
tcp测试
tcp就是在该端口上的流量无条件转发到另一个端口上,此时我们可以预测tcp映射与域名无关,我们在8002上转发到了8080上,此时我们访问
http://127.0.0.1:8002
和http://localhost:8002
都可以得到一样的结果
此时tcp转发成功
源码实现
因为TLS连接与协议无关,只要把普通的TCP转成TLS,剩下的均和普通连接一样处理即可,那么,此时我们只需要处理TCP和HTTP的请求转发即可。
监听
在程序启动的时候看我们是否配置了相应的http/https/tcp的内网穿透转发,如果有我们对相应的端口做监听,此时如果我们是https转发,要配置相应的证书,将会对TcpStream
升级为TlsStream<TcpStream>
let http_listener = if let Some(ls) = &self.option.map_http_bind {Some(TcpListener::bind(ls).await?)
} else {None
};
let mut https_listener = if let Some(ls) = &self.option.map_https_bind {Some(TcpListener::bind(ls).await?)
} else {None
};let map_accept = if https_listener.is_some() {let map_accept = self.option.get_map_tls_accept().await.ok();if map_accept.is_none() {let _ = https_listener.take();}map_accept
} else {None
};
let tcp_listener = if let Some(ls) = &self.option.map_tcp_bind {Some(TcpListener::bind(ls).await?)
} else {None
};
转发相关代码,主要在两个类里,分别为trans/http.rs
和trans/tcp.rs
在
http
里面需要预处理相关的头文件消息,
X-Forwarded-For
添加IP信息,从而使内网可以知道访问的IP来源Host
,重写Host信息,让内网端如果配置负载均衡可以正确的定位到位置Server
,重写Server信息,让内网可以明确知道这个服务端的类型
http转发源码
以下为部分代码,后续将进行比较正规的HTTP服务,以适应HTTP2
pub async fn process<T>(self, mut inbound: T) -> Result<(), ProxyError<T>>
whereT: AsyncRead + AsyncWrite + Unpin,
{let mut request;let host_name;let mut buffer = BinaryMut::new();loop {// 省略读信息request = webparse::Request::new();// 通过该方法解析标头是否合法, 若是partial(部分)则继续读数据// 若解析失败, 则表示非http协议能处理, 则抛出错误// 此处clone为浅拷贝,不确定是否一定能解析成功,不能影响偏移match request.parse_buffer(&mut buffer.clone()) {Ok(_) => match request.get_host() {Some(host) => {host_name = host;break;}None => {if !request.is_partial() {Self::err_server_status(inbound, 503).await?;return Err(ProxyError::UnknownHost);}}},// 数据不完整,还未解析完,等待传输Err(WebError::Http(HttpError::Partial)) => {continue;}Err(e) => {Self::err_server_status(inbound, 503).await?;return Err(ProxyError::from(e));}}}// 取得相关的host数据,对内网的映射端做匹配,如果未匹配到返回错误,表示不支持{let mut is_find = false;let read = self.mappings.read().await;for v in &*read {if v.domain == host_name {is_find = true;}}if !is_find {Self::not_match_err_status(inbound, "no found".to_string()).await?;return Ok(());}}// 有新的内网映射消息到达,通知客户端建立对内网指向的连接进行双向绑定,后续做正规的http服务以支持拓展let create = ProtCreate::new(self.sock_map, Some(host_name));let (stream_sender, stream_receiver) = channel::<ProtFrame>(10);let _ = self.sender_work.send((create, stream_sender)).await;// 创建传输端进行绑定let mut trans = TransStream::new(inbound, self.sock_map, self.sender, stream_receiver);trans.reader_mut().put_slice(buffer.chunk());trans.copy_wait().await?;// let _ = copy_bidirectional(&mut inbound, &mut outbound).await?;Ok(())
}
tcp转发源码
tcp处理相对比较简单,因为我们无法确定协议里是哪个类型的源码,所以对我们来说,就是单纯的把接收的数据完全转发到新的端口里。以下是部分源码
pub async fn process<T>(self, inbound: T) -> Result<(), ProxyError<T>>
whereT: AsyncRead + AsyncWrite + Unpin,
{// 寻找是否有匹配的tcp转发协议,如果有,则进行转发,如果没有则丢弃数据{let mut is_find = false;let read = self.mappings.read().await;for v in &*read {if v.mode == "tcp" {is_find = true;}}if !is_find {log::warn!("not found tcp client trans");return Ok(());}}// 通知客户端数据进行连接的建立,客户端的tcp配置只能存在有且只有一个,要不然无法确定转发源let create = ProtCreate::new(self.sock_map, None);let (stream_sender, stream_receiver) = channel::<ProtFrame>(10);let _ = self.sender_work.send((create, stream_sender)).await;let trans = TransStream::new(inbound, self.sock_map, self.sender, stream_receiver);trans.copy_wait().await?;Ok(())
}
到此部分细节已基本调通,后续将优化http的处理相关,以方便支持http的头信息重写和tcp的错误信息将写入正确的日志,以方便进行定位。
相关文章:

7. 从零用Rust编写正反向代理, HTTP及TCP内网穿透原理及运行篇
wmproxy wmproxy是由Rust编写,已实现http/https代理,socks5代理, 反向代理,静态文件服务器,内网穿透,配置热更新等, 后续将实现websocket代理等,同时会将实现过程分享出来ÿ…...

UE4.27-UE5.1设置打包Android环境
打包Android配置文件 1. 配置打包Android的SDK需求文件位于下面文件中: 2. 指定了对应的SDK环境变量名字以及NDK需求等: UE4.27-UE5.1--脚本自动配置 安装前提 1. 务必关闭虚幻编辑器和Epic Games Launcher,以确保NDK组件的安装或引擎环境…...

MySQL授权密码
mysql> crate databases school charcter set utf8; Query OK, 1 row affected, 1 warning (0.00 sec) 2.在school数据库中创建Student和Score表 mysql> use school Database changed mysql> create table student-> -> (id int(10) primary key auto_incremen…...

0X05
打开题目 点击完登录和注册都没有什么反应,所以先扫一下看看 在出现admin.php后就截止了,访问看看,进入后台。。 尝试一下弱口令 admin/12345 或者是demo/demo 设计中-自定义->右上角导出主题 找到一个导出的点,下载了一个1.zip压缩包…...
Doris优化总结
1 查看QueryProfile 利用查询执行的统计结果,可以更好的帮助我们了解Doris的执行情况,并有针对性的进行相应Debug与调优工作。 FE将查询计划拆分成为Fragment下发到BE进行任务执行。BE在执行Fragment时记录了运行状态时的统计值,并将Fragment执行的统计信息输出到日志之中。…...

案例059:基于微信小程序的在线投稿系统
文末获取源码 开发语言:Java 框架:SSM JDK版本:JDK1.8 数据库:mysql 5.7 开发软件:eclipse/myeclipse/idea Maven包:Maven3.5.4 小程序框架:uniapp 小程序开发软件:HBuilder X 小程序…...

利用STM32内置Bootloader实现USB DFU固件升级
本文将介绍如何利用STM32内置的Bootloader来实现USB DFU(Device Firmware Upgrade)固件升级功能。首先,我们会介绍USB DFU的原理和工作流程。然后,我们将详细讲解如何配置STM32芯片以支持USB DFU,并提供相应的代码示例…...
Centos7如何安装MySQL
目录 一、卸载mysql 二、安装mysql 注:本文主要是看了这位大佬安装MySQL,才想着写一篇记录一下。 一、卸载mysql 安装mysql之前一定要将之前安装的mysql相关文件删除干净,防止出现错误。 (1)关闭mysql 开启了mysql就…...

VR远程带看,助力线下门店线上化转型“自救”
VR远程带看,因自身高效的沉浸式在线沟通功能,逐渐走进了大众的视野。身临其境的线上漫游体验以及实时同屏互联的新型交互模式,提升了商家同用户之间的沟通效率,进一步实现了远程线上一对一、一对多的同屏带看,用户足不…...

算法通关村第十七关-白银挑战贪心算法高频题目
大家好我是苏麟 , 今天说说贪心算法的高频题目 . 大纲 区间问题判断区间是否重叠合并区间插入区间 区间问题 判断区间是否重叠 描述 : 给定一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间intervalsl[i] [start, end] ,请你…...
【数据结构】动态规划(Dynamic Programming)
一.动态规划(DP)的定义: 求解决策过程(decision process)最优化的数学方法。 将多阶段决策过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解。 二.动态规划的基本思想: …...

Redis key过期删除机制实现分析
文章目录 前言Redis key过期淘汰机制惰性删除机制定时扫描删除机制 前言 当我们创建Redis key时,可以通过expire命令指定key的过期时间(TTL),当超过指定的TTL时间后,key将会失效。 那么当key失效后,Redis会立刻将其删除么&#…...
ElasticSearch 谈谈分词与倒排索引的原理
ElasticSearch是一个基于Lucene的搜索服务器。Lucene是Java的一个全文检索工具包,而ElasticSearch则是一个分布式搜索和分析引擎。下面,我们将详细讨论ElasticSearch中的分词和倒排索引的原理。 分词: 在ElasticSearch中,分词是…...
【Java】Java8重要特性——Lambda函数式编程以及Stream流对集合数据的操作
【Java】Java8重要特性——Lambda函数式编程以及Stream流对集合数据的操作 前言Lambda函数式编程Stream流对集合数据操作(一)创建Stream流(二)中间操作之filter(三)中间操作之map(四)…...

大话数据结构-查找-散列表查找(哈希表)
注:本文同步发布于稀土掘金。 8 散列表查找(哈希表) 8.1 定义 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给…...

持续集成交付CICD:Sonarqube自动更新项目质量配置
目录 一、实验 1.Sonarqube手动自定义质量规则并指定项目 2.Sonarqube自动更新项目质量配置 一、实验 1.Sonarqube手动自定义质量规则并指定项目 (1)自定义质量规则 ①新配置 ②更多激活规则③根据需求激活相应规则④已新增配置 ⑤ 查看 &#x…...
Linux设置Docker自动创建Nginx容器脚本
文章目录 前言一、本地新建脚本二、复制本地脚本到服务器三、执行服务器脚本总结如有启发,可点赞收藏哟~ 前言 一、本地新建脚本 在本地新建nginx-generator.sh脚本文件,并保存以下内容 主要动态定义两个变量(容器名称/服务器本地文件名、端…...

技术博客:Vue中各种混淆用法汇总
技术博客:Vue中各种混淆用法汇总 摘要 本文主要介绍了在Vue中使用的一些常见混淆用法,包括new Vue()、export default {}、createApp()、Vue.component、Vue3注册全局组件、Vue.use()等,以及如何使用混淆器对代码进行加固,保护应…...

【python】Python生成GIF动图,多张图片转动态图,pillow
pip install pillow 示例代码: from PIL import Image, ImageSequence# 图片文件名列表 image_files [car.png, detected_map.png, base64_image_out.png]# 打开图片 images [Image.open(filename) for filename in image_files]# 设置输出 GIF 文件名 output_g…...

python/matlab图像去雾/去雨综述
图像去雾和去雨是计算机视觉领域的两个重要任务,旨在提高图像质量和可视化效果。本文将综述图像去雾和去雨的算法、理论以及相关项目代码示例。 一、图像去雾算法 基于暗通道先验的方法: 这是广泛应用于图像去雾的经典算法之一。该方法基于一个观察&…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...