首页 > 其他分享 >5. 用Rust手把手编写一个Proxy(代理), 通讯协议建立, 为内网穿透做准备

5. 用Rust手把手编写一个Proxy(代理), 通讯协议建立, 为内网穿透做准备

时间:2023-09-28 11:58:14浏览次数:41  
标签:map 字节 -- Rust Socket 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来同步数据。

flowchart TD 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,
}

一个数据包的自白

我是一段数据,我要去找服务器获得详细的数据

首先我得和服务器先能沟通上,建立一条可以通讯的线

flowchart TD A[我]-->|请求连接建立|B[客户端代理] B-->|把链接交由|C[中心客户端] C-->|生成sock_map如1,并发送ProtCreate|D[中心服务端] D-->|根据ProtCreate创建与sock_map对应的唯一id|E[虚拟TCP连接] E-->|根据相应信息连接到服务端|F[服务端]

此时我已经和服务端构建起了一条通讯渠道,接下来我要和他发送数据了

flowchart TD 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

至此一条我与服务端已经可以说悄悄话啦。

内网穿透

内网穿秀本质上从中心服务端反向交由中心客户端构建起一条通讯渠道,如今数据协议已经建立,可由服务端推送数据到客户端进行处理,后续实现请看下篇

标签:map,字节,--,Rust,Socket,Proxy,内网,服务端,客户端
From: https://www.cnblogs.com/luojiawaf/p/17735387.html

相关文章

  • vue配置代理服务器proxy
    1.使用vue.config.js文件配置代理:在Vue项目的根目录下创建一个vue.config.js文件,并添加以下代码:module.exports={devServer:{proxy:{'/api':{target:'http://api.example.com',changeOrigin:true,pathRewrite:{'......
  • Rust初见
    基本类型数值类型整型溢出用u8举例,可以存放0-255。在debug模式下,编译器会检查整型溢出,例如存放了256,编译时会产生panic当使用--release参数时,Rust反而又不检查溢出,而是按照补码循环溢出处理。例如256会变成0,257会变成1,以此类推。但终究不是想要的值,所以感觉没有卵用可以通过......
  • 01 - Rust 猜数字游戏
    目录1.猜数字游戏的逻辑2.创建新项目3.猜数字游戏实现3.1获取用户输入并打印a.标准库引入b.println!宏c.可变与不可变变量d.string::new与io::stdin().read_line(&mutinput)3.2生成指定范围内的随机数3.3随机数与猜测数的比较a.字符串转数字b.数字比较大小c.循环......
  • 内网权限提升系统学习(windows)
    内网权限提升系统学习(windows)基础知识windows的权限分为四种User:普通用户权限Administrator:管理员权限,可以利用windows的机制将自己升级为System权限System:系统权限TrustedInstaller:最高权限提权分为两种:纵向提权横向提权系统内核溢出漏洞提权1.手动执行命令发......
  • Rust 日志记录库 tracing
    Rust日志记录库tracing​#2023-09-26#​#日志#​#tracing#一个好用的日志跟踪系统,可以帮助我们很快的定位程序中的bug。tracing不仅仅可以作为一个日志库去使用,还可以作为一个程序追踪库,帮助我们分析程序中存在的问题。tracing-Rusttracing各个模块​tracing​​:......
  • rust 代码组织结构
    使用包、Crate和模块管理不断增长的项目-Rust程序设计语言中文版rust组织结构中,包括以下几个概念Package(包),Crate(箱),Moudle(模块)Package这是Cargo的概念,对应一个Cargo.toml文件,也就是一个rust工程。用于构建、测试、共享Crate。1package=0/1libcrate+0/N......
  • Rust函数与闭包
    1.常规函数函数都拥有显示的类型签名,其本身也是一种类型。1.1函数类型自由函数//自由函数fnsum(a:i32,b:i32)->i32{a+b}fnmain(){assert_eq!(3,sum(1,2))}关联函数与方法structA(i32,i32);implA{//关联函数fnsum(a:i32,b:......
  • 可信而可靠,关于Rust 的学习
    最早接触到Rust是在几年前的一次技术大会上,黄东旭说TiKV是用Rust语言编写的,引起了我的一些兴趣,但只是保持关注而已。我一直认为每一种编程语言都有着各自的典型应用领域,也有着各自的编程范式,没有最好的编程语言(参见《PHP是最好的编程语言吗?》),但存在最适合当前的问题领域的编......
  • Rust+appium App自动化测试demo
    1.新建工程打开RustCover,新建工程如下:修改Cargo.toml文件如下:[package]name="test_demo"version="0.1.0"edition="2021"#Seemorekeysandtheirdefinitionsathttps://doc.rust-lang.org/cargo/reference/manifest.html[dependencies......
  • String vs &str in Rust
    Mostlikely,soonafteryou’vestartedyourRustjourney,youranintothisscenariowhereyoutriedtoworkwithstringtypes(orshouldIsay,youthoughtyouwere?),andthecompilerrefusedtocompileyourcodebecauseofsomethingthatlookslikeas......