初めに
この記事は、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プロキシ機能で確認します。
[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