Skip to main content

Lipeng's Blog

AsyncIO Socks5 Server in Rust

随着 async/await 特性在Rust中稳定下来1,在Rust中写asyncio变得越来越简单了。

async-std 封装了Rust标准库(std)中大多数的组件(例如:fs,io,net等)。

借助于 async-std 来实现异步的网络服务也变得简单起来。

这篇博客记录了如何在Rust中使用 async-std 实现一个基础的 socks5 代理服务器。

Socks5 协议

socks5 协议是一个被广泛使用的代理协议,很多系统和浏览器都支持使用 socks5 协议将网络请求通过代理服务器转发出去。

下图是一个典型的使用场景:

协议

socks5 协议设计比较简单,下图列出了其client和server握手过程2

socks5协议具体的包结构3

第一阶段 握手

client -> server 请求:
+----+----------+----------+
|VER | NMETHODS | METHODS  |
+----+----------+----------+
| 1  |    1     | 1 to 255 |
+----+----------+----------+


server -> client 回复:
+----+--------+
|VER | METHOD |
+----+--------+
| 1  |   1    |
+----+--------+ 

其中:

  • ver: 版本, 固定为 0x05
  • METHODS
    • 0x00: 无验证
    • 0x01: GSSAPI
    • 0x02: USERNAME/PASSWORD
    • 0x03 to 0x7f: IANA ASSIGNED
    • 0x80 to 0xfe: RESERVED FOR PRIVATE METHODS
    • 0xFF: NO ACCEPTABLE METHODS

第二阶段 验证 (仅当第一阶段中指定了验证方式,不同验证方式包结构不同,此处跳过)

第三阶段 发送命令

client -> server 请求:
+----+-----+-------+------+----------+----------+
|VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1  |  1  | X'00' |  1   | Variable |    2     |
+----+-----+-------+------+----------+----------+
  • VER: 版本, 固定为 0x05
  • CMD 命令,取值如下:
    • CONNECT: 0x01 连接
    • BIND: 0x02 端口监听
    • UDP: ASSOCIATE 0x03 UDP
  • RSV: reserved,值是 0x00
  • ATYP: 目标地址类型,有如下取值:
    • 0x01: IPv4
    • 0x03: 域名
    • 0x04: IPv6
  • DST.ADDR: 目标地址,IPv4(4 bytes),IPv6(16 bytes),域名(第一个字节代表长度,接下来是目标地址)
  • DST.PORT: 端口号

server -> client 回复:
+----+-----+-------+------+----------+----------+
|VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1  |  1  | X'00' |  1   | Variable |    2     |
+----+-----+-------+------+----------+----------+ 
  • VER: 版本, 固定为 0x05
  • REP: 状态码,取值如下:
    • 0x00: succeeded
    • 0x01: general SOCKS server failure
    • 0x02: connection not allowed by ruleset
    • 0x03: Network unreachable
    • 0x04: Host unreachable
    • 0x05: Connection refused
    • 0x06: TTL expired
    • 0x07: Command not supported
    • 0x08: Address type not supported
    • 0x09 to 0xff: unassigned
    • RSV : reserved,取值为 0x00
    • ATYP 是目标地址类型,有如下取值:
      • 0x01: IPv4
      • 0x03: 域名
      • 0x04: IPv6
    • BND.ADDR: 目标地址,IPv4(4 bytes),IPv6(16 bytes),域名(第一个字节代表长度,接下来是目标地址)
    • BND.PORT: 端口号

第四阶段 开始双向数据流传输

服务端把从客户端接收到的数据直接转发给(第三阶段中的)目标服务器,同时把从目标服务器收到的数据转发给客户端。

Rust 实现 异步 Socks5 服务

代码在:https://github.com/WANG-lp/socks5-rs

use async_std::io;
use async_std::net::{Ipv4Addr, Ipv6Addr};
use async_std::net::{TcpListener, TcpStream};
use async_std::prelude::*;
use async_std::task;
use bytes::Buf;
use clap::{App, Arg};

async fn process(stream: TcpStream) -> io::Result<()> {
    let peer_addr = stream.peer_addr()?;
    println!("Accepted from: {}", peer_addr);

    let mut reader = stream.clone();
    let mut writer = stream;

    // read socks5 header
    let mut buffer = vec![0u8; 512];
    reader.read_exact(&mut buffer[0..2]).await?;
    if buffer[0] != 0x05 {
        return Err(std::io::Error::new(
            std::io::ErrorKind::ConnectionAborted,
            "only socks5 protocol is supported!",
        )); // stream will be closed automaticly
    }
    let methods = buffer[1] as usize;
    reader.read_exact(&mut buffer[0..methods]).await?;
    let mut has_no_auth = false;
    for i in 0..methods {
        if buffer[i] == 0x00 {
            has_no_auth = true;
        }
    }
    if !has_no_auth {
        return Err(std::io::Error::new(
            std::io::ErrorKind::ConnectionAborted,
            "only no-auth is supported!",
        )); // stream will be closed automaticly
    }

    // server send to client accepted auth method (0x00 no-auth only yet)
    writer.write(&vec![0x05u8, 0x00]).await?;
    writer.flush().await?;

    // read socks5 cmd
    reader.read_exact(&mut buffer[0..4]).await?;
    let cmd = buffer[1]; // only support 0x01(CONNECT)
    let atype = buffer[3];

    let mut addr_port = String::from("");
    let mut flag_addr_ok = true;
    match cmd {
        0x01 => match atype {
            0x01 => {
                // ipv4: 4bytes + port
                reader.read_exact(&mut buffer[0..6]).await?;
                let mut tmp_array: [u8; 4] = Default::default();
                tmp_array.copy_from_slice(&buffer[0..4]);
                let v4addr = Ipv4Addr::from(tmp_array);
                let port: u16 = buffer[4..6].as_ref().get_u16();
                addr_port = format!("{}:{}", v4addr, port);
                // println!("ipv4: {}", addr_port);
            }
            0x03 => {
                reader.read_exact(&mut buffer[0..1]).await?;
                let len = buffer[0] as usize;
                reader.read_exact(&mut buffer[0..len + 2]).await?;
                let port: u16 = buffer[len..len + 2].as_ref().get_u16();
                if let Ok(addr) = std::str::from_utf8(&buffer[0..len]) {
                    addr_port = format!("{}:{}", addr, port);
                } else {
                    flag_addr_ok = false;
                }
                // println!("domain: {}", addr_port);
            }
            0x04 => {
                // ipv6: 6bytes + port
                reader.read_exact(&mut buffer[0..18]).await?;
                let mut tmp_array: [u8; 16] = Default::default();
                tmp_array.copy_from_slice(&buffer[0..16]);
                let v6addr = Ipv6Addr::from(tmp_array);
                let port: u16 = buffer[4..6].as_ref().get_u16();
                addr_port = format!("[{}]:{}", v6addr, port);
                // println!("ipv6: {}", addr_port);
            }
            _ => {
                flag_addr_ok = false;
            }
        },
        _ => {
            writer
                .write(&vec![
                    0x05u8, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                ])
                .await?;
            return Err(std::io::Error::new(
                std::io::ErrorKind::ConnectionAborted,
                "command is not supported!",
            ));
        }
    }

    if flag_addr_ok {
        //create connection to remote server
        if let Ok(remote_stream) = TcpStream::connect(addr_port.as_str()).await {
            println!("connect to {} ok", addr_port);
            writer
                .write(&vec![
                    0x05u8, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                ])
                .await?;

            let mut remote_read = remote_stream.clone();
            let mut remote_write = remote_stream;
            task::spawn(async move {
                match io::copy(&mut reader, &mut remote_write).await {
                    Ok(_) => {}
                    Err(e) => {
                        eprintln!("broken pipe: {}", e);
                    }
                }
            });
            io::copy(&mut remote_read, &mut writer).await?;
        } else {
            writer
                .write(&vec![
                    0x05u8, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                ])
                .await?;
            return Err(std::io::Error::new(
                std::io::ErrorKind::ConnectionRefused,
                format!("cannot make connection to {}!", addr_port),
            )); // stream will be closed automaticly
        };
    }
    println!("disconnect from {}", peer_addr);
    Ok(())
}

fn main() -> io::Result<()> {
    let matches = App::new("Socks5 server in Rust")
        .version("1.0")
        .author("Lipeng ([email protected])")
        .about("A simple socks5 server")
        .arg(
            Arg::with_name("bind")
                .short("b")
                .long("bind")
                .value_name("BIND_ADDR")
                .help("bind address")
                .required(false)
                .takes_value(true),
        )
        .arg(
            Arg::with_name("port")
                .short("p")
                .long("port")
                .value_name("BIND_PORT")
                .help("bind port")
                .required(false)
                .takes_value(true),
        )
        .get_matches();

    let bind_addr = matches.value_of("bind").unwrap_or("127.0.0.1");
    let bind_port = matches.value_of("port").unwrap_or("8080");

    task::block_on(async {
        let listener = TcpListener::bind(format!("{}:{}", bind_addr, bind_port)).await?;
        println!("Listening on {}", listener.local_addr()?);

        let mut incoming = listener.incoming();

        while let Some(stream) = incoming.next().await {
            let stream = stream?;
            task::spawn(async {
                match process(stream).await {
                    Ok(()) => {}
                    Err(e) => {
                        eprintln!("broken pipe: {}", e);
                    }
                }
            });
        }
        Ok(())
    })
}

  1. https://blog.rust-lang.org/2019/11/07/Async-await-stable.html ↩︎

  2. https://sexywp.com/socks-protocol.htm ↩︎

  3. https://jiajunhuang.com/articles/2019_06_06-socks5.md.html ↩︎