From 17c72b9175f0b8103a50ae90cf830905ef2b99fc Mon Sep 17 00:00:00 2001 From: "Y. T. Chung" Date: Wed, 15 Oct 2014 19:01:34 +0800 Subject: [PATCH] first commit, have a runnable tcpserver --- .gitignore | 1 + Cargo.toml | 23 ++++++++++++ README.md | 19 ++++++++++ src/bin/local.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++ src/bin/server.rs | 81 ++++++++++++++++++++++++++++++++++++++++++ src/config.rs | 90 +++++++++++++++++++++++++++++++++++++++++++++++ src/crypto.rs | 4 +++ src/lib.rs | 15 ++++++++ src/relay.rs | 13 +++++++ src/tcprelay.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++ src/udprelay.rs | 0 11 files changed, 418 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/bin/local.rs create mode 100644 src/bin/server.rs create mode 100644 src/config.rs create mode 100644 src/crypto.rs create mode 100644 src/lib.rs create mode 100644 src/relay.rs create mode 100644 src/tcprelay.rs create mode 100644 src/udprelay.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..a12f9832 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] + +name = "shadowsocks-rust" +version = "0.0.1" +authors = ["Y. T. Chung "] + +[lib] + +name = "shadowsocks" + +[[bin]] + +name = "sslocal" +path = "src/bin/local.rs" + +[[bin]] + +name = "ssserver" +path = "src/bin/server.rs" + +[dependencies.rust-crypto] + +git = "https://github.com/DaGenix/rust-crypto.git" diff --git a/README.md b/README.md new file mode 100644 index 00000000..2fbc5ed2 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# shadowsocks-rust + +This is a port of [shadowsocks](https://github.com/clowwindy/shadowsocks). + +shadowsocks is a fast tunnel proxy that helps you bypass firewalls. + +*Currently developing and testing with rust-0.12-dev* + +## Usage + +Build with [Cargo](http://doc.crates.io): + +```bash +cargo build +``` + +## Notes + +Still under developing and waiting for the final release of rust-1.0. diff --git a/src/bin/local.rs b/src/bin/local.rs new file mode 100644 index 00000000..f2fbf5ab --- /dev/null +++ b/src/bin/local.rs @@ -0,0 +1,86 @@ +#![feature(phase)] + +extern crate getopts; +extern crate shadowsocks; +#[phase(plugin, link)] +extern crate log; + +use getopts::{optopt, optflag, getopts, usage}; +use std::os; + +use shadowsocks::config::Config; +use shadowsocks::tcprelay::TcpRelayLocal; +use shadowsocks::relay::Relay; + +fn main() { + let opts = [ + optflag("v", "version", "print version"), + optflag("h", "help", "print this message"), + optopt("c", "config", "specify config file", "config.json"), + optopt("s", "server-addr", "server address", ""), + optopt("b", "local-addr", "local address, listen only to this address if specified", ""), + optopt("k", "password", "password", ""), + optopt("p", "server-port", "server port", ""), + optopt("l", "local-port", "local socks5 proxy port", ""), + optopt("m", "encrypt-method", "entryption method", "aes-256-cfb"), + optflag("d", "debug", "print debug message"), + ]; + + let matches = getopts(os::args().tail(), opts).unwrap(); + + if matches.opt_present("h") { + println!("{}", usage(format!("Usage: {} [options]", os::args()[0]).as_slice(), + opts)); + return; + } + + if matches.opt_present("v") { + println!("{}", shadowsocks::VERSION); + return; + } + + let mut config: Config; + + if matches.opt_present("c") { + config = Config::load_from_file(matches.opt_str("c") + .unwrap().as_slice()) + } else { + config = Config::new() + } + + if matches.opt_present("s") { + let server_ip = matches.opt_str("s").unwrap(); + config.server = server_ip; + } + if matches.opt_present("b") { + let local_ip = matches.opt_str("b").unwrap(); + config.local = local_ip; + } + if matches.opt_present("k") { + let passwd = matches.opt_str("k").unwrap(); + config.password = passwd; + } + if matches.opt_present("p") { + let server_port = matches.opt_str("p").unwrap(); + config.server_port = from_str(server_port.as_slice()).unwrap(); + } + if matches.opt_present("l") { + let local_port = matches.opt_str("l").unwrap(); + config.local_port = from_str(local_port.as_slice()).unwrap(); + } + if matches.opt_present("m") { + let mut encrypt_meth = matches.opt_str("m").unwrap(); + if encrypt_meth.as_slice() == "" { + encrypt_meth = "aes-256-cfb".to_string(); + } + + config.method = encrypt_meth; + } + + info!("ShadowSocks {}", shadowsocks::VERSION); + + println!("{}", config) + + let mut tcp_server = TcpRelayLocal::new(&config); + tcp_server.run(); +} diff --git a/src/bin/server.rs b/src/bin/server.rs new file mode 100644 index 00000000..07413774 --- /dev/null +++ b/src/bin/server.rs @@ -0,0 +1,81 @@ +#![feature(phase)] + +extern crate getopts; +extern crate shadowsocks; +#[phase(plugin, link)] +extern crate log; + +use getopts::{optopt, optflag, getopts, usage}; +use std::os; + +use shadowsocks::config::Config; + +fn main() { + let opts = [ + optflag("v", "version", "print version"), + optflag("h", "help", "print this message"), + optopt("c", "config", "specify config file", "config.json"), + optopt("s", "server-addr", "server address", ""), + optopt("b", "local-addr", "local address, listen only to this address if specified", ""), + optopt("k", "password", "password", ""), + optopt("p", "server-port", "server port", ""), + optopt("l", "local-port", "local socks5 proxy port", ""), + optopt("m", "encrypt-method", "entryption method", "aes-256-cfb"), + optflag("d", "debug", "print debug message"), + ]; + + let matches = getopts(os::args().tail(), opts).unwrap(); + + if matches.opt_present("h") { + println!("{}", usage(format!("Usage: {} [options]", os::args()[0]).as_slice(), + opts)); + return; + } + + if matches.opt_present("v") { + println!("{}", shadowsocks::VERSION); + return; + } + + let mut config: Config; + + if matches.opt_present("c") { + config = Config::load_from_file(matches.opt_str("c") + .unwrap().as_slice()) + } else { + config = Config::new() + } + + if matches.opt_present("s") { + let server_ip = matches.opt_str("s").unwrap(); + config.server = server_ip; + } + if matches.opt_present("b") { + let local_ip = matches.opt_str("b").unwrap(); + config.local = local_ip; + } + if matches.opt_present("k") { + let passwd = matches.opt_str("k").unwrap(); + config.password = passwd; + } + if matches.opt_present("p") { + let server_port = matches.opt_str("p").unwrap(); + config.server_port = from_str(server_port.as_slice()).unwrap(); + } + if matches.opt_present("l") { + let local_port = matches.opt_str("l").unwrap(); + config.local_port = from_str(local_port.as_slice()).unwrap(); + } + if matches.opt_present("m") { + let mut encrypt_meth = matches.opt_str("m").unwrap(); + if encrypt_meth.as_slice() == "" { + encrypt_meth = "aes-256-cfb".to_string(); + } + + config.method = encrypt_meth; + } + + info!("ShadowSocks {}", shadowsocks::VERSION); + + println!("{}", config) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..6a8ae898 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,90 @@ +extern crate serialize; + +use serialize::{Decodable, Encodable}; +use serialize::json; +use std::io::{File, Read, Open}; + +use std::to_string::ToString; +use std::fmt::{Show, Formatter, mod}; + +use std::option::Option; + +#[deriving(Encodable, Clone)] +pub struct Config { + pub server: String, + pub local: String, + pub server_port: u16, + pub local_port: u16, + pub password: String, + pub method: String, + pub timeout: Option, + pub fast_open: bool, +} + +impl Config { + pub fn new() -> Config { + Config{ + server: "127.0.0.1".to_string(), + local: "127.0.0.1".to_string(), + server_port: 8000, + local_port: 8000, + password: "".to_string(), + method: "aes-256-cfb".to_string(), + timeout: None, + fast_open: false, + } + } + + fn parse_json_object(o: &json::JsonObject) -> Config { + let mut config = Config::new(); + + for (key, value) in o.iter() { + match key.as_slice() { + "server" => { + config.server = value.as_string().unwrap().to_string(); + }, + "server_port" => { + config.server_port = value.as_i64().unwrap() as u16; + }, + "local_port" => { + config.local_port = value.as_i64().unwrap() as u16; + }, + "password" => { + config.password = value.as_string().unwrap().to_string(); + }, + "method" => { + config.method = value.as_string().unwrap().to_string(); + }, + "timeout" => { + config.timeout = Some(value.as_i64().unwrap() as u64); + }, + "fast_open" => { + config.fast_open = value.as_boolean().unwrap(); + }, + _ => (), + } + } + + config + } + + pub fn load_from_str(s: &str) -> Config { + let object = json::from_str(s).unwrap(); + let json_object = object.as_object().unwrap(); + Config::parse_json_object(json_object) + } + + pub fn load_from_file(filename: &str) -> Config { + let reader = &mut File::open_mode(&Path::new(filename), Open, Read).unwrap(); + + let object = json::from_reader(reader).unwrap(); + let json_object = object.as_object().unwrap(); + Config::parse_json_object(json_object) + } +} + +impl Show for Config { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", json::encode(self)) + } +} diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 00000000..286240cb --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,4 @@ +extern crate "rust-crypto" as crypto; + +pub const AES_128_CFB: &'static str = "aes-128-cfb"; +pub const AES_256_CFB: &'static str = "aes-256-cfb"; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..d6e66cae --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,15 @@ +#![crate_type="lib"] +#![crate_name="shadowsocks"] +#![feature(phase)] + +extern crate serialize; +#[phase(plugin, link)] +extern crate log; + +pub const VERSION: &'static str = "0.0.1"; + +pub mod config; +pub mod relay; +pub mod tcprelay; +pub mod udprelay; +pub mod crypto; diff --git a/src/relay.rs b/src/relay.rs new file mode 100644 index 00000000..12c287e9 --- /dev/null +++ b/src/relay.rs @@ -0,0 +1,13 @@ + +pub trait Relay { + fn run(&mut self); +} + +pub enum Stage { + StageInit, + StageHello, + StageUdpAssoc, + StageDns, + StageReply, + StageStream, +} diff --git a/src/tcprelay.rs b/src/tcprelay.rs new file mode 100644 index 00000000..98ef9ce6 --- /dev/null +++ b/src/tcprelay.rs @@ -0,0 +1,86 @@ +extern crate log; + +// mod relay; +use relay::Relay; +use relay::Stage; +use relay::{StageInit, StageHello, StageUdpAssoc, StageDns, StageReply, StageStream}; + +// mod config; +use config::Config; + +use std::io::TcpListener; +use std::io::{Acceptor, Listener}; +use std::io::net::tcp::TcpAcceptor; +use std::io::{TimedOut, EndOfFile}; + +use std::str; + +pub struct TcpRelayLocal { + acceptor: TcpAcceptor, + stage: Stage, + timeout: Option, +} + +impl TcpRelayLocal { + pub fn new(c: &Config) -> TcpRelayLocal { + let mut acceptor = TcpListener::bind(c.local.as_slice(), c.local_port).unwrap().listen().unwrap(); + + TcpRelayLocal { + acceptor: acceptor, + stage: StageInit, + timeout: c.timeout, + } + } + + fn accept_loop(&mut self) { + loop { + match self.acceptor.accept() { + Ok(mut stream) => spawn(proc() { + info!("Client {} connected", stream.socket_name().unwrap()); + + loop { + let mut buf = [0u8, .. 10240]; + match stream.read(buf) { + Ok(len) => { + println!("Len: {}", len) + + let s = buf.slice_to(len); + println!("Received: {}", str::from_utf8(s).unwrap()); + stream.write(s).unwrap() + }, + Err(err) => { + if err.kind == EndOfFile { + break + } + error!("Err: {}", err); + break + } + } + } + + info!("Client {} disconnected", stream.socket_name().unwrap()); + + drop(stream) + }), + Err(e) => { + fail!(e) + } + } + } + } +} + +impl Relay for TcpRelayLocal { + fn run(&mut self) { + self.accept_loop() + } +} + +pub struct TcpRelayServer; + +impl TcpRelayServer { + pub fn new() -> TcpRelayServer { + TcpRelayServer + } + +} diff --git a/src/udprelay.rs b/src/udprelay.rs new file mode 100644 index 00000000..e69de29b