HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 代理技术全栈手册

    • 代理技术全栈手册 - HiHuo
    • 原理篇

      • 第01章 代理是什么:正向 / 反向 / 透明 / 隧道的统一模型
      • 第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量
      • 第03章 一个请求穿过代理的一生:连接生命周期全景
    • 协议篇

      • 第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池
      • 第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS
      • 第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
      • 第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战
      • 第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY
    • 层级与转发篇

      • 第09章 L4 代理:TCP/UDP 转发与连接级负载均衡
      • 第10章 L7 代理:协议感知与基于内容的路由
      • 第11章 透明代理:iptables REDIRECT/DNAT、TPROXY 与 eBPF 劫持
      • 第12章 数据搬运的艺术:splice / sendfile / 零拷贝 / io_uring
    • 组件横评篇

      • 第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程
      • 第14章 HAProxy:L4/L7、ACL、健康检查与 stick table
      • 第15章 Envoy:xDS 动态配置与 filter chain,为何是云原生数据面
      • 第16章 Traefik / Caddy:自动服务发现与自动 HTTPS
      • 第17章 Squid 与正向/缓存代理:企业出网、缓存与审计
      • 第18章 mitmproxy:抓包、改包、脚本化调试
      • 第19章 内网穿透与隧道:frp / gost / SSH 隧道 / ngrok
      • 第20章 科学上网生态的技术原理(技术中立)
    • 多语言手写篇

      • 第21章 Go:100 行手写 HTTP/CONNECT + SOCKS5 代理
      • 第22章 Rust:基于 tokio 的高性能 TCP 代理
      • 第23章 Python:asyncio 实现,适合调试与脚本
      • 第24章 C:epoll 裸写与零拷贝,及语言选型对比
    • 容器与K8s篇

      • 第25章 Docker 里的代理:HTTP_PROXY、build/pull 与 daemon 配置
      • 第26章 Sidecar 与流量劫持:Istio init-container 的 iptables 原理
      • 第27章 Ingress 与南北流量:Ingress-nginx 与 Gateway API
      • 第28章 Egress 与出网治理:出口网关、registry mirror、审计
      • 第29章 Service Mesh 数据面:Envoy Sidecar 全链路
    • 进阶篇

      • 第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
      • 第31章 性能调优:并发模型、连接池、超时与重试、压测
      • 第32章 排错决策树:502 / 504 / 握手失败 / 环路 / 泄漏
      • 第33章 代理安全:开放代理、SSRF、凭证泄漏与攻击面
    • 底层机制篇

      • 第34章 代理的背压与流控:一个代理最难的部分
      • 第35章 socket 与 TCP 状态机:半关闭、超时、连接生命周期
      • 第36章 HTTP/2 帧、流控与 HPACK:h2 代理的内部机制
      • 第37章 负载均衡算法推导与韧性状态机
      • 第38章 Capstone:把玩具代理改造成生产级骨架
    • 综合实战篇

      • 第39章 企业多跳转发链:拓扑、协议矩阵与贯穿性难题
      • 第40章 端到端实战:把 6 类流量全代理通
      • 第41章 更刁钻的流量:gRPC、长轮询、WebRTC、大文件、双向流
      • 第42章 可落地完整参考实现:一套能跑的多协议转发栈
    • 附录

      • 附录 A:代理协议报文速查(HTTP / SOCKS / PAC / PROXY protocol)
      • 附录 B:组件选型决策树
      • 附录 C:抓包与命令速查

第22章 Rust:基于 tokio 的高性能 TCP 代理

学习目标

  • 用 tokio 写出 L4 TCP 代理和 SOCKS5 代理,对照 第21章 的 Go 实现
  • 理解 Rust async/await 与 Go goroutine 的模型差异(无栈状态机 vs 有栈协程)
  • 体会 copy_bidirectional、所有权/move 在网络编程中的作用
  • 明白为什么 Cloudflare/字节等用 Rust 重写代理(pingora)

前置知识

  • 第21章 Go 手写代理、第06章 SOCKS 协议、第09章 L4
  • 一点 Rust 基础(所有权、async/await)

原理

为什么用 Rust 写代理

代理是性能敏感、长期运行、处理不可信输入的程序——这正是 Rust 的甜点:

  • 无 GC:没有 GC 停顿,尾延迟稳定(代理最怕 p99 抖动)
  • 零成本抽象:高级写法编译成和手写 C 一样快的代码
  • 内存安全:所有权 + 借用检查在编译期消灭"野指针/数据竞争"——处理不可信网络输入时这是巨大优势
  • tokio:成熟的多线程 work-stealing 异步运行时,性能顶级

代表作:Cloudflare 的 pingora 用 Rust 重写了原本基于 Nginx 的代理,扛着每天万亿级请求,省 CPU 又降尾延迟。

Rust async vs Go goroutine

Go goroutineRust async/await
协程模型有栈协程,运行时隐式调度无栈协程,编译成状态机
让出 CPU隐式(运行时在阻塞点切换)显式 .await
运行时内置 runtime选 runtime(tokio/async-std)
心智负担低(像写同步代码)较高(生命周期、Send、Pin)
极致性能/控制好更好(无 GC、可控内存)

一句话:Go 用"简单"换开发速度,Rust 用"显式"换极致性能与安全。 第21章 的 go handle(c) 在 Rust 里是 tokio::spawn(async move { ... })。


代码一:L4 TCP 透传代理

最能体现 tokio 简洁的是 L4 代理——copy_bidirectional 就是 Go io.Copy 双向版的对应物:

// Cargo.toml: tokio = { version = "1", features = ["full"] }
use tokio::io::copy_bidirectional;
use tokio::net::{TcpListener, TcpStream};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("0.0.0.0:8088").await?;
    println!("L4 代理监听 :8088 → 转发 example.com:80");

    loop {
        let (mut inbound, peer) = listener.accept().await?;
        // 每个连接一个异步任务(对应 Go 的 go handle(c))
        tokio::spawn(async move {
            match TcpStream::connect("93.184.216.34:80").await {
                Ok(mut outbound) => {
                    // 双向拷贝,任一端关闭即返回(对应 Go 的两个 io.Copy)
                    if let Err(e) = copy_bidirectional(&mut inbound, &mut outbound).await {
                        eprintln!("{peer} 转发结束: {e}");
                    }
                }
                Err(e) => eprintln!("连后端失败: {e}"),
            }
        });
    }
}
cargo run
curl -s -H "Host: example.com" http://127.0.0.1:8088/ -o /dev/null -w "%{http_code}\n"   # 200

move 把 inbound/peer 的所有权转移进 task——这是 Rust 强制你想清楚"谁拥有这个连接",从根上杜绝了悬垂引用。


代码二:SOCKS5 代理(异步手撕协议)

和 第21章 的 Go SOCKS5 逐字节对应,但用 tokio 的异步读写:

use tokio::io::{copy_bidirectional, AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("0.0.0.0:1080").await?;
    println!("SOCKS5 代理监听 :1080");
    loop {
        let (client, _) = listener.accept().await?;
        tokio::spawn(async move {
            if let Err(e) = handle(client).await {
                eprintln!("会话错误: {e}");
            }
        });
    }
}

async fn handle(mut client: TcpStream) -> std::io::Result<()> {
    // 阶段1:方法协商  VER NMETHODS METHODS...
    let mut head = [0u8; 2];
    client.read_exact(&mut head).await?;
    if head[0] != 0x05 {
        return Ok(());
    }
    let mut methods = vec![0u8; head[1] as usize];
    client.read_exact(&mut methods).await?;
    client.write_all(&[0x05, 0x00]).await?; // 选 0x00 无认证

    // 阶段2:请求  VER CMD RSV ATYP ...
    let mut req = [0u8; 4];
    client.read_exact(&mut req).await?;
    if req[1] != 0x01 {
        // 只支持 CONNECT
        client.write_all(&[0x05, 0x07, 0, 0x01, 0, 0, 0, 0, 0, 0]).await?;
        return Ok(());
    }
    // 按 ATYP 解析目标地址(对应第06章报文)
    let host = match req[3] {
        0x01 => {
            let mut a = [0u8; 4];
            client.read_exact(&mut a).await?;
            format!("{}.{}.{}.{}", a[0], a[1], a[2], a[3])
        }
        0x03 => {
            let mut l = [0u8; 1];
            client.read_exact(&mut l).await?;
            let mut d = vec![0u8; l[0] as usize];
            client.read_exact(&mut d).await?;
            String::from_utf8_lossy(&d).into_owned()
        }
        0x04 => {
            let mut a = [0u8; 16];
            client.read_exact(&mut a).await?;
            std::net::Ipv6Addr::from(a).to_string()
        }
        _ => {
            client.write_all(&[0x05, 0x08, 0, 0x01, 0, 0, 0, 0, 0, 0]).await?;
            return Ok(());
        }
    };
    let mut port = [0u8; 2];
    client.read_exact(&mut port).await?;
    let port = u16::from_be_bytes(port);

    // 阶段3:拨号 + 应答 + 转发
    let mut server = match TcpStream::connect((host.as_str(), port)).await {
        Ok(s) => s,
        Err(_) => {
            client.write_all(&[0x05, 0x05, 0, 0x01, 0, 0, 0, 0, 0, 0]).await?;
            return Ok(());
        }
    };
    client.write_all(&[0x05, 0x00, 0, 0x01, 0, 0, 0, 0, 0, 0]).await?; // 成功
    copy_bidirectional(&mut client, &mut server).await?;
    Ok(())
}
curl --socks5-hostname 127.0.0.1:1080 https://example.com/ -s -o /dev/null -w "%{http_code}\n"  # 200

把这段和 第21章 的 Go 版并排:协议字节完全一样(都是 第06章 的 RFC 1928),区别只在语言——Go 用 io.ReadFull、Rust 用 read_exact().await,错误处理 Rust 用 ? 更显式。


灵活性与性能

维度Rust + tokio 的表现
吞吐/延迟顶级,无 GC 停顿,尾延迟稳定
内存安全编译期保证,处理不可信输入零担忧
并发tokio 多线程 work-stealing,百万连接
零拷贝可用 splice(需手动或 tokio-splice 类库;copy_bidirectional 默认走用户态缓冲,见 第12章)
开发速度慢于 Go(借用检查、Pin/Send 心智负担)
生态tokio/hyper/tower/pingora,云原生代理新势力

何时选 Rust:要榨干性能、要内存安全、长期运行的核心数据面——Cloudflare pingora、字节的部分网关都是例证。代价是开发更慢、门槛更高。


排错

现象根因解决
编译报 value moved所有权被移动后又用clone() 或调整借用
task 不执行忘了 .await 或没 spawn异步函数必须被 await/spawn 驱动
future is not Send跨 await 持有非 Send 类型换 Send 类型或缩小持有范围
连接不关闭、fd 泄漏错误路径没 drop stream用 ?/作用域确保 drop
性能没达预期阻塞调用堵了 runtime阻塞操作放 spawn_blocking

本章小结

  • tokio 写代理:tokio::spawn 对应 Go 的 go,copy_bidirectional 对应双向 io.Copy。
  • SOCKS5 的协议字节与 Go 版完全一致,差异只在语言表达(read_exact().await + ?)。
  • Rust 优势:无 GC、零成本、内存安全、尾延迟稳;代价是开发慢、门槛高。
  • 选 Rust 写核心数据面(pingora 即代表)。

下一章 第23章 Python + asyncio,走向另一极端:用最易读的语言快速写出代理与调试工具——mitmproxy 就是 Python 写的。

Prev
第21章 Go:100 行手写 HTTP/CONNECT + SOCKS5 代理
Next
第23章 Python:asyncio 实现,适合调试与脚本