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

  • 3
    いいね
  • 0
    コメント

初めに

この記事は、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

この投稿は Rust Advent Calendar 201615日目の記事です。