6. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 通讯协议源码解读篇 ...

立山  金牌会员 | 2023-10-10 12:39:06 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 924|帖子 924|积分 2772

用Rust手把手编写一个wmproxy(代理,内网穿透等), 通讯协议源码解读篇

项目 ++wmproxy++

gite: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
事件模型的选取


  • OS线程, 简单的一个IO对应一个系统级别的线程,通常单进程创建的线程数是有限的,在线程与线程间同步数据会相当困难,线程间的调度争用会相当损耗效率,不适合IO密集的场景。
  • 事件驱动(Event driven), 事件驱动基本上是最早的高并发的IO密集型的编程模式了,如C++的libevent,RUST的MIO,通过监听IO的可读可写从而进行编程设计,缺点通常跟回调( Callback )一起使用,如果使用不好,回调层级过多会有回调地狱的风险。
  • 协程(Coroutines) 可能是目前比较火的并发模型,火遍全球的Go语言的协程设计就非常优秀。协程跟线程类似,无需改变编程模型,同时它也跟async类似,可以支持大量的任务并发运行。
  • actor模型 是erlang的杀手锏之一,它将所有并发计算分割成一个一个单元,这些单元被称为actor,单元之间通过消息传递的方式进行通信和数据传递,跟分布式系统的设计理念非常相像。由于actor模型跟现实很贴近,因此它相对来说更容易实现,但是一旦遇到流控制、失败重试等场景时,就会变得不太好用
  • async/await, 该模型为异步编辑模型,async模型的问题就是内部实现机制过于复杂,对于用户来说,理解和使用起来也没有线程和协程简单。主要是等待完成状态await,就比如读socket数据,等待系统将数据送达再继续触发读操作的执行,从而答到无损耗的运行。
这里我们选择的是async/await的模式
Rust中的async


  • Future 在 Rust 中是惰性的,只有在被轮询(poll)时才会运行, 因此丢弃一个 future 会阻止它未来再被运行, 你可以将Future理解为一个在未来某个时间点被调度执行的任务。在Rust中调用异步函数没有用await会被编辑器警告,因为这不符合预期。
  • Async 在 Rust 中使用开销是零, 意味着只有你能看到的代码(自己的代码)才有性能损耗,你看不到的(async 内部实现)都没有性能损耗,例如,你可以无需分配任何堆内存、也无需任何动态分发来使用 async,这对于热点路径的性能有非常大的好处,正是得益于此,Rust 的异步编程性能才会这么高。
  • Rust 异步运行时,Rust社区生态中已经提供了非常优异的运行时实现例如tokio,官方版本的async目前的生态相对tokio会差许多
  • 运行时同时支持单线程和多线程
流代码的封装

跟数据通讯相关的代码均放在streams目录下面。

  • center_client.rs中的CenterClient表示中心客户端,提供主动连接服务端的能力并可选择为加密(TLS)或者普通模式,并且将该客户端收发的消息转给服务端
  • center_server.rs中的CenterServer表示中心服务端,接受中心客户端的连接,并且将信息处理或者转发
  • trans_stream.rs中的TransStream表示转发流量端,提供与中心端绑定的读出写入功能,在代理服务器中客户端接收的连接因为无需处理任何数据,直接绑定为TransStream将数据完整的转发给服务端
  • virtual_stream.rs中的VirtualStream表示虚拟端,虚拟出一个流连接,并实现AsyncRead及AsyncRead,可以和流一样正常操作,在代理服务器中服务端接收到新连接,把他虚拟成一个VirtualStream,就可以直接和他连接的服务器上做双向绑定。
几种流式在代码中的转化

HTTP代理

下面展示的是http代理,通过加密TLS中的转化
flowchart TD    A[TcpStream请求到代理]|建立连接/明文|B[代理转化成TransStream]    B|转发到/内部|C[中心客户端]    C|建立加密连接/加密|D[TlsStream< TcpStream>绑定中心服务端]    D|收到Create/内部|E[虚拟出VirtualStream]    E|解析到host并连接/明文|F[TcpStream连接到http服务器]上述过程实现了程序中实现了http的代理转发
HTTP内网穿透

以下是http内网穿透在代理中的转化
flowchart TD    A[服务端绑定http对外端口]|接收连接/明文|B[外部的TcpStream]    B|转发到/内部|C[中心服务端并绑定TransStream]    C|通过客户的加密连接推送/加密|D[TlsStream< TcpStream>绑定中心客户端]    D|收到Create/内部|E[虚拟出VirtualStream]    E|解析对应的连接信息/明文|F[TcpStream连接到内网的http服务器]上述过程可以主动把公网的请求连接转发到内网,由内网提供完服务后再转发到公网的请求,从而实现内网穿透。
流代码的介绍

CenterClient中心客端

下面是代码类的定义
  1. /// 中心客户端
  2. /// 负责与服务端建立连接,断开后自动再重连
  3. pub struct CenterClient {
  4.     /// tls的客户端连接信息
  5.     tls_client: Option<Arc<rustls::ClientConfig>>,
  6.     /// tls的客户端连接域名
  7.     domain: Option<String>,
  8.     /// 连接中心服务器的地址
  9.     server_addr: SocketAddr,
  10.     /// 内网映射的相关消息
  11.     mappings: Vec<MappingConfig>,
  12.     /// 存在普通连接和加密连接,此处不为None则表示普通连接
  13.     stream: Option<TcpStream>,
  14.     /// 存在普通连接和加密连接,此处不为None则表示加密连接
  15.     tls_stream: Option<TlsStream<TcpStream>>,
  16.     /// 绑定的下一个sock_map映射
  17.     next_id: u32,
  18.     /// 发送Create,并将绑定的Sender发到做绑定
  19.     sender_work: Sender<(ProtCreate, Sender<ProtFrame>)>,
  20.     /// 接收的Sender绑定,开始服务时这值move到工作协程中,所以不能二次调用服务
  21.     receiver_work: Option<Receiver<(ProtCreate, Sender<ProtFrame>)>>,
  22.     /// 发送协议数据,接收到服务端的流数据,转发给相应的Stream
  23.     sender: Sender<ProtFrame>,
  24.     /// 接收协议数据,并转发到服务端。
  25.     receiver: Option<Receiver<ProtFrame>>,
  26. }
复制代码
主要的逻辑流程,循环监听数据流的到达,同时等待多个异步的到达,这里用的是tokio::select!宏
  1. loop {
  2.     let _ = tokio::select! {
  3.         // 严格的顺序流
  4.         biased;
  5.         // 新的流建立,这里接收Create并进行绑定
  6.         r = receiver_work.recv() => {
  7.             if let Some((create, sender)) = r {
  8.                 map.insert(create.sock_map(), sender);
  9.                 let _ = create.encode(&mut write_buf);
  10.             }
  11.         }
  12.         // 数据的接收,并将数据写入给远程端
  13.         r = receiver.recv() => {
  14.             if let Some(p) = r {
  15.                 let _ = p.encode(&mut write_buf);
  16.             }
  17.         }
  18.         // 数据的等待读取,一旦流可读则触发,读到0则关闭主动关闭所有连接
  19.         r = reader.read(&mut vec) => {
  20.             match r {
  21.                 Ok(0)=>{
  22.                     is_closed=true;
  23.                     break;
  24.                 }
  25.                 Ok(n) => {
  26.                     read_buf.put_slice(&vec[..n]);
  27.                 }
  28.                 Err(_err) => {
  29.                     is_closed = true;
  30.                     break;
  31.                 },
  32.             }
  33.         }
  34.         // 一旦有写数据,则尝试写入数据,写入成功后扣除相应的数据
  35.         r = writer.write(write_buf.chunk()), if write_buf.has_remaining() => {
  36.             match r {
  37.                 Ok(n) => {
  38.                     write_buf.advance(n);
  39.                     if !write_buf.has_remaining() {
  40.                         write_buf.clear();
  41.                     }
  42.                 }
  43.                 Err(e) => {
  44.                     println!("center_client errrrr = {:?}", e);
  45.                 },
  46.             }
  47.         }
  48.     };
  49.     loop {
  50.         // 将读出来的数据全部解析成ProtFrame并进行相应的处理,如果是0则是自身消息,其它进行转发
  51.         match Helper::decode_frame(&mut read_buf)? {
  52.             Some(p) => {
  53.                 match p {
  54.                     ProtFrame::Create(p) => {
  55.                     }
  56.                     ProtFrame::Close(_) | ProtFrame::Data(_) => {
  57.                     },
  58.                 }
  59.             }
  60.             None => {
  61.                 break;
  62.             }
  63.         }
  64.     }
  65. }
复制代码
CenterServer中心服务端

下面是代码类的定义
  1. /// 中心服务端
  2. /// 接受中心客户端的连接,并且将信息处理或者转发
  3. pub struct CenterServer {
  4.     /// 代理的详情信息,如用户密码这类
  5.     option: ProxyOption,
  6.    
  7.     /// 发送协议数据,接收到服务端的流数据,转发给相应的Stream
  8.     sender: Sender<ProtFrame>,
  9.     /// 接收协议数据,并转发到服务端。
  10.     receiver: Option<Receiver<ProtFrame>>,
  11.     /// 发送Create,并将绑定的Sender发到做绑定
  12.     sender_work: Sender<(ProtCreate, Sender<ProtFrame>)>,
  13.     /// 接收的Sender绑定,开始服务时这值move到工作协程中,所以不能二次调用服务
  14.     receiver_work: Option<Receiver<(ProtCreate, Sender<ProtFrame>)>>,
  15.     /// 绑定的下一个sock_map映射,为双数
  16.     next_id: u32,
  17. }
复制代码
主要的逻辑流程,循环监听数据流的到达,同时等待多个异步的到达,这里用的是tokio::select!宏,select处理方法与Client相同,均处理相同逻辑,不同的是接收数据包后数据端是处理的proxy的请求,而Client处理的是内网穿透的逻辑
  1. loop {
  2.     // 将读出来的数据全部解析成ProtFrame并进行相应的处理,如果是0则是自身消息,其它进行转发
  3.     match Helper::decode_frame(&mut read_buf)? {
  4.         Some(p) => {
  5.             match p {
  6.                 ProtFrame::Create(p) => {
  7.                     tokio::spawn(async move {
  8.                         let _ = Proxy::deal_proxy(stream, flag, username, password, udp_bind).await;
  9.                     });
  10.                 }
  11.                 ProtFrame::Close(_) | ProtFrame::Data(_) => {
  12.                 },
  13.             }
  14.         }
  15.         None => {
  16.             break;
  17.         }
  18.     }
  19. }
复制代码
TransStream转发流量端

下面是代码类的定义
  1. /// 转发流量端
  2. /// 提供与中心端绑定的读出写入功能
  3. pub struct TransStream<T>
  4. where
  5.     T: AsyncRead + AsyncWrite + Unpin,
  6. {
  7.     // 流有相应的AsyncRead + AsyncWrite + Unpin均可
  8.     stream: T,
  9.     // sock绑定的句柄
  10.     id: u32,
  11.     // 读取的数据缓存,将转发成ProtFrame
  12.     read: BinaryMut,
  13.     // 写的数据缓存,直接写入到stream下,从ProtFrame转化而来
  14.     write: BinaryMut,
  15.     // 收到数据通过sender发送给中心端
  16.     in_sender: Sender<ProtFrame>,
  17.     // 收到中心端的写入请求,转成write
  18.     out_receiver: Receiver<ProtFrame>,
  19. }
复制代码
主要的逻辑流程,循环监听数据流的到达,同时等待多个异步的到达,这里用的是tokio::select!宏,监听的对象有stream可读,可写,sender的写发送及receiver的可接收
  1. loop {
  2.     // 有剩余数据,优先转化成Prot,因为数据可能从外部直接带入
  3.     if self.read.has_remaining() {
  4.         link.push_back(ProtFrame::new_data(self.id, self.read.copy_to_binary()));
  5.         self.read.clear();
  6.     }
  7.     tokio::select! {
  8.         n = reader.read(&mut buf) => {
  9.             let n = n?;
  10.             if n == 0 {
  11.                 return Ok(())
  12.             } else {
  13.                 self.read.put_slice(&buf[..n]);
  14.             }
  15.         },
  16.         r = writer.write(self.write.chunk()), if self.write.has_remaining() => {
  17.             match r {
  18.                 Ok(n) => {
  19.                     self.write.advance(n);
  20.                     if !self.write.has_remaining() {
  21.                         self.write.clear();
  22.                     }
  23.                 }
  24.                 Err(_) => todo!(),
  25.             }
  26.         }
  27.         r = self.out_receiver.recv() => {
  28.             if let Some(v) = r {
  29.                 if v.is_close() || v.is_create() {
  30.                     return Ok(())
  31.                 } else if v.is_data() {
  32.                     match v {
  33.                         ProtFrame::Data(d) => {
  34.                             self.write.put_slice(&d.data().chunk());
  35.                         }
  36.                         _ => unreachable!(),
  37.                     }
  38.                 }
  39.             } else {
  40.                 return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid frame"))
  41.             }
  42.         }
  43.         p = self.in_sender.reserve(), if link.len() > 0 => {
  44.             match p {
  45.                 Err(_)=>{
  46.                     return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid frame"))
  47.                 }
  48.                 Ok(p) => {
  49.                     p.send(link.pop_front().unwrap())
  50.                 },
  51.             }
  52.         }
  53.     }
复制代码
VirtualStream虚拟端

下面是代码类的定义,我们并未有真实的socket,通过虚拟出的端方便后续的操作
  1. /// 虚拟端
  2. /// 虚拟出一个流连接,并实现AsyncRead及AsyncRead,可以和流一样正常操作
  3. pub struct VirtualStream
  4. {
  5.     // sock绑定的句柄
  6.     id: u32,
  7.     // 收到数据通过sender发送给中心端
  8.     sender: PollSender<ProtFrame>,
  9.     // 收到中心端的写入请求,转成write
  10.     receiver: Receiver<ProtFrame>,
  11.     // 读取的数据缓存,将转发成ProtFrame
  12.     read: BinaryMut,
  13.     // 写的数据缓存,直接写入到stream下,从ProtFrame转化而来
  14.     write: BinaryMut,
  15. }
复制代码
虚拟的流主要通过实现AsyncRead及AsyncWrite
[code]impl AsyncRead for VirtualStream{    // 有读取出数据,则返回数据,返回数据0的Ready状态则表示已关闭    fn poll_read(        mut self: std::pin:in,        cx: &mut [std](https://note.youdao.com/)[link](https://note.youdao.com/)::task::Context,    ) -> std::task:oll {        loop {            match self.receiver.poll_recv(cx) {                Poll::Ready(value) => {                    if let Some(v) = value {                        if v.is_close() || v.is_create() {                            return Poll::Ready(Ok(()))                        } else if v.is_data() {                            match v {                                ProtFrame:ata(d) => {                                    self.read.put_slice(&d.data().chunk());                                }                                _ => unreachable!(),                            }                        }                    } else {                        return Poll::Ready(Ok(()))                    }                },                Poll:ending => {                    if !self.read.has_remaining() {                        return Poll:ending;                    }                },            }            if self.read.has_remaining() {                let copy = std::cmp::min(self.read.remaining(), buf.remaining());                buf.put_slice(&self.read.chunk()[..copy]);                self.read.advance(copy);                return Poll::Ready(Ok(()));            }        }            }}impl AsyncWrite for VirtualStream{    fn poll_write(        mut self: Pin,        cx: &mut std::task::Context
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

立山

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表