LoginSignup
9
5

More than 5 years have passed since last update.

SOCKSサーバを標準ライブラリのみで実装する

Posted at

初めに

この記事は、SOCKSサーバを標準ライブラリのみで実装する記事です。使用するライブラリを標準ライブラリだけに絞ることで、SOCKSプロトコルへの理解とRustへの理解の両方を狙いました。

SOCKSとは

プロキシサーバとそれを実現するプロトコルの一種で、バージョン5に当たるSOCKS5というプロトコルがRFC 1928で定義されています。
具体的には、TCPストリームをrelayすることによって、プロキシとしての機能を実現します。

例えば、ClientAがSOCKSサーバXを通してServerBにアクセスする場合、ClientAはまずXとTCPで接続を確立します。そこで、AがXにSeverBのIPアドレスを伝えます。すると、Xがその情報を元にBと接続を確立します。そして、Xが、Aから送られてきたデータをBへ送りつつ、Bから送られてきたデータをAへ送ります。

実装

今回はプロトコルがシンプルなSOCKS4を実装します。

TCPサーバ

SOCKSサーバを実装する上でまず必要なのはTCPサーバ、つまり、ソケットプログラミングでおなじみのbind、listen、acceptのループです。

bind + listen

let socks_address = "127.0.0.1:10334";
let listener;

match TcpListener::bind(socks_address) {
    Err(e) => {
        println!("Error: {}", e);
        exit(0);
    },
    Ok(l) => {
        listener = l;
    },
};

accept + コネクションを別スレッドでハンドル

loop {
    match listener.accept() {
        Err(e) => {
            println!("Error: {}", e)
        },
        Ok((stream, addr)) => {
            println!("Received request from {}", addr);
            // handle request in spawned thread
            thread::spawn(move || {
                println!("This is in spawned thread");
                handle_request(stream);                
            });
        }
    }
}

TCPストリームからデータの読み出す

TCPストリームを流れてくるデータはただのバイト列なので、それを取り出し適切な形で解釈する関数が必要になります。1バイトでu8と解釈する関数、2バイトでu16と解釈する関数、nバイトで文字列と解釈する関数などです。

u8として解釈する関数

fn read_u8(s: &mut TcpStream) -> u8 {
    let mut buf: [u8; 1] = [0];

    s.read(&mut buf);
    return buf[0];
}

u16として解釈する関数

fn read_u16(s: &mut TcpStream) -> u16 {
    let mut buf: [u8; 2] = [0, 0];

    s.read(&mut buf);

    // two u8 to u16
    let data1 = buf[0] as u16;
    let data2 = buf[1] as u16;
    let data = data1 * 256 + data2;
    return data;
}

Stringとして解釈する関数

fn read_user(s: &TcpStream) -> String {
    let mut stream = s.try_clone().unwrap();
    let mut buf: [u8; 128] = [0; 128];
    let mut user = "".to_string();

    stream.read(&mut buf);

    for i in 1..128 {
        let ch = buf[i] as char;
        user.push(ch);
    };
    println!("user: {}", user);

    return user;
}

読み出したデータを処理する

バージョンや内容がプロトコルに沿っているかを確認し、繋ぎにいくべきアドレスなどを読み出します。

let (r, ver, cmd) = check_request(&client_stream);
    if !r {
        println!("Error: SOCKS request is not valid");
        return;
    }

let mut target_address;
if ver == 4 {
    match process_request_v4(&client_stream) {
        Ok(target) => {
            target_address = target;
        },
        Err(e) => {
            println!("Error: failed to process SOCKS requset");
            return
        },
    }
} else {
    println!("Error: SOCKS request is not valid");
    return;
}

TCPストリームをrelayする

では実際にRustでどのようにこのrelayを実装するかというと、まず両方のTcpStreamをcloneし、それぞれ別スレッドで逆方向に、readしたデータをwriteでストリームに流してshutdownします。

let mut client_stream_c = client_stream.try_clone().unwrap();
let mut target_stream_c = target_stream.try_clone().unwrap();

// SeverB to ClientA
thread::spawn(move || {
    copy(&mut target_stream_c, &mut client_stream_c);
    target_stream_c.shutdown(Shutdown::Read);
    client_stream_c.shutdown(Shutdown::Write);
});

// ClientA to ServerB
copy(&mut client_stream, &mut target_stream);
target_stream.shutdown(Shutdown::Write);
client_stream.shutdown(Shutdown::Read);

実際に使用する

proxychainsもしくはブラウザのSOCKSプロキシ機能で確認します。

proxychains.conf
[ProxyList]
socks4 127.0.0.1 10334
sudo apt-get install proxychains
proxychains firefox

結論

標準ライブラリのみでも200行以下でSOCKSサーバを実装できるRustはすごい

ソースコード

GitHub - ak1t0/rust-socks-server

参考文献

SOCKS4
GitHub - zmack/rust-socks: Socks5 server written in rust

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5