5. 用Rust手把手编写一个Proxy(代理), 通讯协议建立, 为内网穿透做准备
用Rust手把手编写一个Proxy(代理), 通讯协议建立, 为内网穿透做准备
项目 ++wmproxy++
gite: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
什么是通讯协议?
在tcp的流传输过程中,可以看做是一堆的字节的集合体,是一种“流”式协议,就像河里的水,中间没有边界。或者好比不懂汉语的来看古文,因为古文里没有任何的句读,不知何时另起一行。那我们如何正确的做到拆包解包,保证数据格式的正确呢?
以下是客户端发送两个30字节的包(P1及P2),服务端读取数据可能读出来的可能
gantt
title 粘包的可能,例每个包30字节
%% This is a comment
dateFormat X
axisFormat %s
section 示例1
P2 :a1, 1, 30
P1 :after a1, 60
section 示例2
P2,P1 :1,60
section 示例3
P2部分 :a3, 1, 20
P2部分P1全部 :after a3, 60
section 示例4
P2全部P1部分 :a4, 1, 40
P1部分 :after a4, 60
若没有事先约定好格式,在服务端部分无法正确的解析出P1包和P2包,也就意味着无法理解客户端发的内容。若此时我们约定每个包的大小固定为30字节,那么2,3,4三种可能不管收到多少,都必须等待30字节填充完毕后解析出P1,剩余的数据待待60字节接收完毕后解析P2包
粘包拆包常见的解决方案
对于粘包和拆包问题,常见的解决方案有四种:
- 发送端将每个包都封装成固定的长度,比如512字节大小。如果不足512字节可通过补0或空等进行填充到指定长度;
- 发送端在每个包的末尾使用固定的分隔符,例如\r\n。如果发生拆包需等待多个包发送过来之后再找到其中的\r\n进行合并;例如,Redis协议,每一行的结尾都是CRLF,在碰到结尾的时候才进行转发;
- 将消息分为头部和消息体,头部中保存整个消息的长度,只有读取到足够长度的消息之后才算是读到了一个完整的消息,例如HTTP2协议,固定先读3个字节的长度,9个字节的长度头信息;
- 通过自定义协议进行粘包和拆包的处理。
在此的解决方案
选择了分为头部和消息体方案,头部分为8个字节,然后前3个字节表示包体的长度,单包支持长度为8-167777215也就是16m的大小,足够应对大多数情况。
网络的拓扑图
因为每个链接的处理函数均在不同的协程里,所以这里用了Sender/Receiver来同步数据。
A[中心客户端/CenterClient]<-->|tls加密连接或普通连接|B[中心服务端/CenterServer]
C[客户端链接]<-->|Sender/Receiver|A
B<-->|Sender/Receiver|D[服务端链接]
协议的分类
协议相关的类均在
prot目录下面,统一对外的为枚举ProtFrame,类的定义如下
pub enum ProtFrame {
/// 收到新的Socket连接
Create(ProtCreate),
/// 收到旧的Socket连接关闭
Close(ProtClose),
/// 收到Socket的相关数据
Data(ProtData),
}
主要涉及类的编码及解析在方法encode,parse,定义如下
/// 把字节流转化成数据对象
pub fn parse<T: Buf>(
header: ProtFrameHeader,
buf: T,
) -> ProxyResult<ProtFrame> {
}
/// 把数据对象转化成字节流
pub fn encode<B: Buf + BufMut>(
self,
buf: &mut B,
) -> ProxyResult<usize> {
}
消息的包头
任何消息优先获取包头信息,从而才能进行相应的类型解析,类为
ProtFrameHeader,定义如下,总共8个字节
pub struct ProtFrameHeader {
/// 包体的长度, 3个字节, 最大为16m
pub length: u32,
/// 包体的类型, 如Create, Data等
kind: ProtKind,
/// 包体的标识, 如是否为响应包等
flag: ProtFlag,
/// 3个字节, socket在内存中相应的句柄, 客户端发起为单数, 服务端发起为双数
sock_map: u32,
}
消息类型的定义
暂时目前定义三种类型,
Create,Close,Data,
- Socket创建,类为
ProtCreate
/// 新的Socket连接请求,
/// 接收方创建一个虚拟链接来对应该Socket的读取写入
#[derive(Debug)]
pub struct ProtCreate {
sock_map: u32,
mode: u8,
domain: Option<String>,
}
- Socket关闭,类为
ProtClose
/// 旧的Socket连接关闭, 接收到则关闭掉当前的连接
#[derive(Debug)]
pub struct ProtClose {
sock_map: u32,
}
- Socket数据包,类为
ProtData
/// Socket的数据消息包
#[derive(Debug)]
pub struct ProtData {
sock_map: u32,
data: Binary,
}
一个数据包的自白
我是一段数据,我要去找服务器获得详细的数据
首先我得和服务器先能沟通上,建立一条可以通讯的线
A[我]-->|请求连接建立|B[客户端代理]
B-->|把链接交由|C[中心客户端]
C-->|生成sock_map如1,并发送ProtCreate|D[中心服务端]
D-->|根据ProtCreate创建与sock_map对应的唯一id|E[虚拟TCP连接]
E-->|根据相应信息连接到服务端|F[服务端]
此时我已经和服务端构建起了一条通讯渠道,接下来我要和他发送数据了
A[我]-->|发送字节数据|B[客户端代理]
B-->|读出数据交由|C[中心客户端]
C<-->|加工成ProtData发送|D[中心服务端]
D-->|根据ProtData的sock_map发送给对应|E[虚拟TCP连接]
E-->|解析成数据流写入|F[服务端]
F-->|把数据流返回|E
E-->|读出数据交由|D
C-->|根据ProtData的sock_map发送给对应|B
B-->|解析成数据流写入|A
至此一条我与服务端已经可以说悄悄话啦。
内网穿透
内网穿秀本质上从中心服务端反向交由中心客户端构建起一条通讯渠道,如今数据协议已经建立,可由服务端推送数据到客户端进行处理,后续实现请看下篇
5. 用Rust手把手编写一个Proxy(代理), 通讯协议建立, 为内网穿透做准备的更多相关文章
- 分享一个内网穿透工具frp
首先简单介绍一下内网穿透: 内网穿透:通过公网,访问局域网里的IP地址与端口,这需要将局域网里的电脑端口映射到公网的端口上:这就需要用到反向代理,即在公网服务器上必须运行一个服务程序,然后在局域网中需 ...
- 【代理】内网穿透工具 frp&frps
frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发. ### frp 的作 ...
- 借助FRP反向代理实现内网穿透
一.frp 是什么? frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP.UDP.HTTP.HTTPS 等多种协议.可以将内网服务以安全.便捷的方式通过具有公网 IP 节点的中转暴露到公 ...
- frp实现基于反向代理的内网穿透
个人博客主页: xzajyjs.cn frp是什么 简单地说,frp就是一个反向代理软件,它体积轻量但功能很强大,可以使处于内网或防火墙后的设备对外界提供服务,它支持HTTP.TCP.UDP等众多协议 ...
- 【新晋开源项目】内网穿透神器[中微子代理] 加入 Dromara 开源社区
1.关于作者 dromara开源组织成员,dromara/neutrino-proxy项目作者 名称:傲世孤尘.雨韵诗泽 名言: 扎根土壤,心向太阳.积蓄能量,绽放微光. 拘浊酒邀明月,借赤日暖苍穹. ...
- MFC+WinPcap编写一个嗅探器之七(协议)
这一节是本系列教程的结尾了,内容也比较简单,主要是对网络协议进行分析,其实学过计算机网络的同学完全可以略过 在整个项目中需要有一个头文件存放各层协议的头部定义,我把它们放在了head.h中,这个头文件 ...
- ssh后门反向代理实现内网穿透
如图所示,内网主机ginger 无公网IP地址,防火墙只允许ginger连接blackbox.example.com主机 假如你是ginger的管理员root,你想要用tech主机连接ginger主机 ...
- 新的知识点来了-ES6 Proxy代理 和 去银行存款有什么关系?
ES给开发者提供了一个新特性:Proxy,就是代理的意思.也就是我们这一节要介绍的知识点. 以前,ATM还没有那么流行的时候(暴露年纪),我们去银行存款或者取款的时候,需要在柜台前排队,等柜台工作人员 ...
- Java实战_手把手编写记事本
Java运用SWT插件编写桌面记事本应用程序 可实现windows系统桌面记事本基本功能.傻瓜式教学,一步一步手把手操作.小白也可自己编写出完整的应用程序. 须要工具:Eclipse(带SWT插件) ...
- 浏览器 Proxy SwitchyOmega 插件设置代理访问内网服务器
使用Proxy SwitchyOmega 插件通过代理 直接访问到内网网站 一.使用场景 如下图所示,如果在电脑的网络设置中开启代理,每次更换代理就需要进入这里设置改变代理.且我们可能回需求到两个网页 ...
随机推荐
- 一站式数据可观测性平台 Datavines 正式开源啦
Datavines是一站式开源数据可观测性平台,提供元数据管理.数据概览报告.数据质量管理,数据分布查询.数据趋势洞察等核心能力,致力于帮助用户全面地了解和掌管数据,让您做到心中有数,目前作为 Dat ...
- 2023-06-22:一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试 给你一个二维数组 classes ,其中 classes[i] = [passi, totali] 表
2023-06-22:一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试 给你一个二维数组 classes ,其中 classes[i] = [passi, totali] 表 ...
- 前端Vue自定义简单实用中国省市区三级联动选择器
前端Vue自定义简单实用中国省市区三级联动选择器, 请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=13118 效果图如下: 使用方法 < ...
- 【Oracle】使用PL/SQL实现冒泡排序
[Oracle]使用PL/SQL实现冒泡排序 一般来说,SQL要排序的话直接使用order by即可 不一般来说,就是瞎搞,正好也可以巩固自己的数据结构基础 存储包内容如下 规范: create or ...
- 从头学Java17-Lambda表达式
Lambda表达式 这一系列教程,旨在介绍 lambda 的概念,同时逐步教授如何在实践中使用它们. 回顾表达式.语句 表达式 表达式由变量.运算符和方法调用组成,其计算结果为单个值.您已经看到了表达 ...
- Python 爬虫实战:驾驭数据洪流,揭秘网页深处
爬虫,这个经常被人提到的词,是对数据收集过程的一种形象化描述.特别是在Python语言中,由于其丰富的库资源和良好的易用性,使得其成为编写爬虫的绝佳选择.本文将从基础知识开始,深入浅出地讲解Pytho ...
- HCL实验:1.两台PC通过交换机ping通
实验整体的拓扑图 进行交换机配置 配置好PC的ip netmask gatework 接口管理为启用状态 开启SW1 连接的端口 (一般来说是默认打开的,但有时候会自动关闭,很烦,所以最好加上这步) ...
- MyBatis实现动态SQL更新
博主记得在一个周五快下班的下午,产品找到我(为什么总感觉周五快下班就来活 ),跟我说有几个业务列表查询需要加上时间条件过滤数据,这个条件可能会变,不保证以后不修改,这个改动涉及到多个列表查询,于是博主 ...
- AcWing 4489. 最长子序列题解
思路 此题较为简单,简述一下思路. 设原始数列为 \(a\). 定义 \(dp\) 数组,初始值都为 \(1\). 遍历数组,如果 \(a[i-1]*2 \leq a[i]\) ,那么 \(dp[i] ...
- 阿里云容蓓:DCDN 助力云原生时代的应用构建及最佳实践
在数字化转型速度不断提升的今天,大带宽.低时延.高并发的场景不断涌现,内容分发网络(Content Delivery Network,CDN)应用需求还在不断攀升,打造更高质量的CDN服务将成为新时代 ...