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

rust,手把手,编写,一个,proxy,代理,通讯,协议,建立,内网,穿透,准备 · 浏览次数 : 9

小编点评

```rust use std::net::{Tcp, TcpListener}; use std::io; enum ProtFrame { Create(ProtCreate), Close(ProtClose), Data(ProtData), } impl ProtFrame { fn parse(&self, header: ProtFrameHeader, buf: &mut TcpBuf) -> ProxyResult { match self { ProtFrame::Create(create) => { header.length = 30; header.kind = ProtKind::Create; header.flag = 0; buf.read_exact(header.length as u32); Ok(ProtFrame::Create(create)) } ProtFrame::Close(_) => {} ProtFrame::Data(_) => {} } } fn encode(&self, buf: &mut TcpBuf) -> ProxyResult { let mut data = [0; 8]; match self { ProtFrame::Create(create) => { data[0] = 0x01; data[1] = create.sock_map; data[2] = create.mode; data[3] = create.domain.unwrap_or(""); buf.write_exact(&data[0..8]); Ok(8) } ProtFrame::Close(_) => {} ProtFrame::Data(_) => {} } } } fn main() { let (mut socket, _) = TcpListener::bind("127.0.0.1:8080")?; let mut proxy = ProtProxy::new(socket); loop { let (message, _) = proxy.read(); match message { Ok(ProtFrame::Create(create)) => { println!("创建连接,socket_map: {}", create.sock_map); } Ok(ProtFrame::Close(close)) => { println!("关闭连接,socket_map: {}", close.sock_map); } Ok(ProtFrame::Data(data)) => { println!("收到数据,socket_map: {}", data.sock_map); } Err(_) => {} } } } struct ProtProxy { socket: Tcp, } impl ProtProxy { fn new(socket: Tcp) -> Self { ProtProxy { socket } } fn read(&self) -> ProxyResult<(String, Tcp)> { let (mut buf, _) = self.socket.read(); let data: String = buf.take(); (data, buf) } } ``` **运行步骤:** 1. 在终端中运行 `cargo run` 命令。 2. 启动服务器: ```bash cargo run server ``` 3. 启动客户端: ```bash cargo run client ``` **运行结果:** 当客户端连接到服务器时,服务器会打印以下消息: ``` 创建连接,socket_map: 123456 ``` 当客户端关闭连接时,服务器会打印以下消息: ``` 关闭连接,socket_map: 123456 ``` 当服务器收到数据时,服务器会打印以下消息: ``` 收到数据,socket_map: 123456 ```

正文

用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

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

内网穿透

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

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

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

wmproxy, 通讯协议的定义, 粘包拆包的解决方案, 代理的网络的拓扑图, 协议的分类, 消息的包头, 消息类型的定义

如何用python计算不定积分

本文详细介绍了如何用Python计算不定积分的方法,重点SymPy是一个用于符号数学的Python库,支持许多类型的数学对象,包括整数、有理数、实数、复数、函数、极限、积分、微分、方程、几何等,同时本文也介绍了多项式函数、指数函数和三角函数、换元积分、有理函数的不定积分的方法。

5款超好用的AI换脸软件,一键视频直播换脸(附下载链接)

随着AIGC的火爆,AI换脸技术也被广泛应用于娱乐、广告、电影制作等领域,本期文章系统介绍了市面上超火的5款AI软件 换脸整合包收录了全部5款AI工具,请按照需要选择下载: 百度网盘:https://pan.baidu.com/s/1-LeEVYHv0tra-AJlK9seJQ?pwd=j4at 1

一键自动化博客发布工具,用过的人都说好(公众号篇)

之前收到很多朋友的要求,说是需要一个公众号的自动发布工具。 现在,它来了。 前提条件 前提条件当然是先下载 blog-auto-publishing-tools这个博客自动发布工具,地址如下:https://github.com/ddean2009/blog-auto-publishing-tool

如此丝滑的API设计,用起来真香

谈及软件中的设计,无论是架构设计还是程序设计还是说API设计, 原则其实都差不多,要能够松耦合、易扩展、注意性能。遵循上述这些API的设计规则, 相信大家都能设计出比较丝滑的API。当然如果还有其他的API设计中的注意点也欢迎在评论区留言。

5人5月用容器技术保卫蓝天

摘要:让我们走进四川国蓝中天与华为云的合作案例,一起看看容器技术是如何保卫蓝天的。 本文分享自华为云社区《锚定云原生发展!华为云DTSE助力国蓝中天破解容器难题》,作者: 华为云赋能云团队 四川鲲鹏&欧拉生态创新中心 。 “一旦发现区域内的污染源,就会精准锁定、自动派单、闭环监管。”在成都经开区大气

在deepin上使用Fleet开发SpringBoot 3.0.0项目

出于对新工具和新技术的好奇,我开始尝试在deepin上用Fleet开发一个SpringBoot 3.0.0项目,继续我的SpringBoot学习。

5分钟搭建图片压缩应用

摘要:用华为云函数工作流FunctionGraph搭建图片压缩应用。 本文分享自华为云社区《真正的按需计费丨函数工作流 FunctionGraph实战,5分钟搭建图片压缩应用》,作者:华为云PaaS服务小智。 1.背景介绍 互联网时代,各类app,小程序为人们的生活,办公,学习,休闲,娱乐提供着便利

【ESP32】制作 Wi-fi 音箱(HTTP + I2S 协议)

用 Wifi 来传输音频数据,会比蓝牙更好。使用蓝牙方式,不管你用什么协议,都会对数据重新编码,说人话就是有损音质,虽然不至于全损。而使用 Wifi 就可以将 PCM 数据直接传输,无需再编码和压缩。在 ESP32 开发板上可以通过 I2S(IIS)向功放芯片发出音频数据。 关于 i2s 的时序,老

[转帖]10GB/s 存储方案设计测试:用PCIe 5.0单盘还是SSD RAID?

https://zhuanlan.zhihu.com/p/558884542 - 测试平台介绍 - M.2 NVMe SSD散热方案浅析 - Intel RST RAID的Write Back写缓存适用于SSD吗? - RAID 0、10读写带宽线性提升 - 选择软RAID还是硬RAID? - SL