diff --git a/src/bin/local.rs b/src/bin/local.rs index 5c5e7c60..bc5dcf0c 100644 --- a/src/bin/local.rs +++ b/src/bin/local.rs @@ -6,6 +6,7 @@ extern crate shadowsocks; extern crate log; use getopts::{optopt, optflag, getopts, usage}; + use std::os; use shadowsocks::config::Config; diff --git a/src/config.rs b/src/config.rs index a45d3ed3..1ea56730 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,10 +2,11 @@ extern crate serialize; use serialize::Encodable; use serialize::json; +use serialize::json::PrettyEncoder; use std::io::{File, Read, Open}; use std::to_string::ToString; -use std::fmt::{Show, Formatter, mod}; +use std::fmt::{Show, Formatter, WriteError, mod}; use std::option::Option; @@ -129,6 +130,10 @@ impl Config { impl Show for Config { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", json::encode(self)) + let mut encoder = PrettyEncoder::new(f); + match self.encode(&mut encoder) { + Ok(..) => Ok(()), + Err(..) => Err(WriteError), + } } } diff --git a/src/lib.rs b/src/lib.rs index 4f4ad369..40552559 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ extern crate serialize; #[phase(plugin, link)] extern crate log; +extern crate native; + pub const VERSION: &'static str = "0.0.1"; pub mod config; diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 1bbf489b..23ba8075 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -2,6 +2,9 @@ pub use self::tcprelay::local::TcpRelayLocal; pub use self::tcprelay::server::TcpRelayServer; use std::fmt::{Show, Formatter, FormatError}; +use std::io::net::ip::{SocketAddr, Port}; +use std::io::TcpStream; +use std::io::net::ip::{Ipv4Addr, Ipv6Addr}; pub mod tcprelay; @@ -11,10 +14,10 @@ pub trait Relay { pub const SOCK5_VERSION : u8 = 0x05; -pub const SOCK5_AUTH_METHOD_NONE : u8 = 0x00; -pub const SOCK5_AUTH_METHOD_GSSAPI : u8 = 0x01; -pub const SOCK5_AUTH_METHOD_PASSWORD : u8 = 0x02; -pub const SOCK5_AUTH_METHOD_NOT_ACCEPTABLE : u8 = 0xff; +pub const SOCK5_AUTH_METHOD_NONE : u8 = 0x00; +pub const SOCK5_AUTH_METHOD_GSSAPI : u8 = 0x01; +pub const SOCK5_AUTH_METHOD_PASSWORD : u8 = 0x02; +pub const SOCK5_AUTH_METHOD_NOT_ACCEPTABLE : u8 = 0xff; pub const SOCK5_CMD_TCP_CONNECT : u8 = 0x01; pub const SOCK5_CMD_TCP_BIND : u8 = 0x02; @@ -24,29 +27,100 @@ pub const SOCK5_ADDR_TYPE_IPV4 : u8 = 0x01; pub const SOCK5_ADDR_TYPE_DOMAIN_NAME : u8 = 0x03; pub const SOCK5_ADDR_TYPE_IPV6 : u8 = 0x04; -pub const SOCK5_REPLY_SUCCEEDED : u8 = 0x00; -pub const SOCK5_REPLY_GENERAL_FAILURE : u8 = 0x01; -pub const SOCK5_REPLY_CONNECTION_NOT_ALLOWED: u8 = 0x02; -pub const SOCK5_REPLY_NETWORK_UNREACHABLE : u8 = 0x03; -pub const SOCK5_REPLY_HOST_UNREACHABLE : u8 = 0x04; -pub const SOCK5_REPLY_CONNECTION_REFUSED : u8 = 0x05; -pub const SOCK5_REPLY_TTL_EXPIRED : u8 = 0x06; -pub const SOCK5_REPLY_COMMAND_NOT_SUPPORTED : u8 = 0x07; -pub const SOCK5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED : u8 = 0x08; +pub const SOCK5_REPLY_SUCCEEDED : u8 = 0x00; +pub const SOCK5_REPLY_GENERAL_FAILURE : u8 = 0x01; +pub const SOCK5_REPLY_CONNECTION_NOT_ALLOWED : u8 = 0x02; +pub const SOCK5_REPLY_NETWORK_UNREACHABLE : u8 = 0x03; +pub const SOCK5_REPLY_HOST_UNREACHABLE : u8 = 0x04; +pub const SOCK5_REPLY_CONNECTION_REFUSED : u8 = 0x05; +pub const SOCK5_REPLY_TTL_EXPIRED : u8 = 0x06; +pub const SOCK5_REPLY_COMMAND_NOT_SUPPORTED : u8 = 0x07; +pub const SOCK5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED : u8 = 0x08; -pub enum Sock5AddrType { - Sock5AddrTypeIpv4, - Sock5AddrTypeIpv6, - Sock5AddrTypeDomainName, +#[deriving(Show)] +pub enum Sock5CmdType { + Sock5CmdTcpConnect, + Sock5CmdTcpBind, + Sock5CmdUdpAssociate, } -impl Show for Sock5AddrType { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { - match *self { - Sock5AddrTypeIpv4 => formatter.write("Sock5AddrTypeIpv4".as_bytes()), - Sock5AddrTypeIpv6 => formatter.write("Sock5AddrTypeIpv6".as_bytes()), - Sock5AddrTypeDomainName => formatter.write("Sock5AddrTypeDomainName".as_bytes()) +pub struct DomainNameAddr { + pub domain_name: String, + pub port: Port, +} + +impl Show for DomainNameAddr { + fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> { + f.write(format!("{}:{}", self.domain_name, self.port).as_slice().as_bytes()) + } +} + +#[deriving(Show)] +pub enum Sock5AddrType { + Sock5SocketAddr(SocketAddr), + Sock5DomainNameAddr(DomainNameAddr), +} + +pub fn parse_request_header(stream: &mut TcpStream, buf: &[u8]) -> (uint, Sock5AddrType) { + let atyp = buf[0]; + match atyp { + SOCK5_ADDR_TYPE_IPV4 => { + if buf.len() < 7 { + fail!("Invalid header"); + } + + let raw_addr = buf.slice(1, 5); + let v4addr = Ipv4Addr(raw_addr[0], raw_addr[1], raw_addr[2], raw_addr[3]); + + let raw_port = buf.slice(5, 7); + let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16; + + (7u, Sock5SocketAddr(SocketAddr{ip: v4addr, port: port})) + }, + SOCK5_ADDR_TYPE_IPV6 => { + if buf.len() < 19 { + fail!("Invalid header"); + } + + let raw_addr = buf.slice(1, 17); + let v6addr = Ipv6Addr((raw_addr[0] as u16 << 8) | raw_addr[1] as u16, + (raw_addr[2] as u16 << 8) | raw_addr[3] as u16, + (raw_addr[4] as u16 << 8) | raw_addr[5] as u16, + (raw_addr[6] as u16 << 8) | raw_addr[7] as u16, + (raw_addr[8] as u16 << 8) | raw_addr[9] as u16, + (raw_addr[10] as u16 << 8) | raw_addr[11] as u16, + (raw_addr[12] as u16 << 8) | raw_addr[13] as u16, + (raw_addr[14] as u16 << 8) | raw_addr[15] as u16); + + let raw_port = buf.slice(17, 19); + // Big Endian + let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16; + + (19u, Sock5SocketAddr(SocketAddr{ip: v6addr, port: port})) + }, + SOCK5_ADDR_TYPE_DOMAIN_NAME => { + let addr_len = buf[1] as uint; + if buf.len() < 4 + addr_len { + fail!("Invalid header"); + } + let raw_addr = buf.slice(2, 2 + addr_len); + let raw_port = buf.slice(2 + addr_len, 4 + addr_len); + let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16; + + (4 + addr_len, Sock5DomainNameAddr(DomainNameAddr{ + domain_name: String::from_utf8(raw_addr.to_vec()).unwrap(), + port: port, + })) + }, + _ => { + // Address type not supported + send_error_reply(stream, SOCK5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED); + fail!("Unsupported address type: {}", atyp); } } } +pub fn send_error_reply(stream: &mut TcpStream, err_code: u8) { + let reply = [SOCK5_VERSION, err_code, 0x00]; + stream.write(reply).ok().expect("Error occurs while sending errors"); +} diff --git a/src/relay/tcprelay/local.rs b/src/relay/tcprelay/local.rs index e73814ef..4f3e15f1 100644 --- a/src/relay/tcprelay/local.rs +++ b/src/relay/tcprelay/local.rs @@ -1,22 +1,19 @@ #[phase(plugin, link)] extern crate log; +// extern crate native; use std::sync::Arc; use std::io::{Listener, TcpListener, Acceptor, TcpStream}; -use std::io::{EndOfFile}; -use std::io::net::ip::{Ipv4Addr, Ipv6Addr}; -use std::vec::Vec; -use std::string::String; +use std::io::{EndOfFile, TimedOut}; use config::Config; -use relay::Relay; +use relay::Relay; +use relay::{parse_request_header, send_error_reply}; use relay::{SOCK5_VERSION, SOCK5_AUTH_METHOD_NONE}; use relay::{SOCK5_CMD_TCP_CONNECT, SOCK5_CMD_TCP_BIND, SOCK5_CMD_UDP_ASSOCIATE}; -use relay::{SOCK5_REPLY_COMMAND_NOT_SUPPORTED, SOCK5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED}; +use relay::{SOCK5_REPLY_COMMAND_NOT_SUPPORTED}; use relay::SOCK5_REPLY_SUCCEEDED; -use relay::{SOCK5_ADDR_TYPE_IPV4, SOCK5_ADDR_TYPE_IPV6, SOCK5_ADDR_TYPE_DOMAIN_NAME}; -use relay::{Sock5AddrType, Sock5AddrTypeIpv4, Sock5AddrTypeIpv6, Sock5AddrTypeDomainName}; use crypto::cipher; use crypto::cipher::CipherVariant; @@ -60,66 +57,6 @@ impl TcpRelayLocal { stream.write(data_to_send).ok().expect("Error occurs while sending handshake reply"); } - fn send_error_reply(stream: &mut TcpStream, err_code: u8) { - let reply = [SOCK5_VERSION, err_code, 0x00]; - stream.write(reply).ok().expect("Error occurs while sending errors"); - } - - fn parse_request_header(stream: &mut TcpStream) -> (Vec, Sock5AddrType, String, u16) { - let mut raw_header = Vec::new(); - - let atyp = stream.read_exact(1).unwrap()[0]; - raw_header.push(atyp); - match atyp { - SOCK5_ADDR_TYPE_IPV4 => { - let raw_addr = stream.read_exact(4).unwrap(); - raw_header.push_all(raw_addr.as_slice()); - let v4addr = Ipv4Addr(raw_addr[0], raw_addr[1], raw_addr[2], raw_addr[3]); - - let raw_port = stream.read_exact(2).unwrap(); - raw_header.push_all(raw_port.as_slice()); - let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16; - - (raw_header, Sock5AddrTypeIpv4, v4addr.to_string(), port) - }, - SOCK5_ADDR_TYPE_IPV6 => { - let raw_addr = stream.read_exact(16).unwrap(); - raw_header.push_all(raw_addr.as_slice()); - let v6addr = Ipv6Addr((raw_addr[0] as u16 << 8) | raw_addr[1] as u16, - (raw_addr[2] as u16 << 8) | raw_addr[3] as u16, - (raw_addr[4] as u16 << 8) | raw_addr[5] as u16, - (raw_addr[6] as u16 << 8) | raw_addr[7] as u16, - (raw_addr[8] as u16 << 8) | raw_addr[9] as u16, - (raw_addr[10] as u16 << 8) | raw_addr[11] as u16, - (raw_addr[12] as u16 << 8) | raw_addr[13] as u16, - (raw_addr[14] as u16 << 8) | raw_addr[15] as u16); - - let raw_port = stream.read_exact(2).unwrap(); - raw_header.push_all(raw_port.as_slice()); - let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16; - - (raw_header, Sock5AddrTypeIpv6, v6addr.to_string(), port) - }, - SOCK5_ADDR_TYPE_DOMAIN_NAME => { - let addr_len = stream.read_exact(1).unwrap()[0]; - raw_header.push(addr_len); - let raw_addr = stream.read_exact(addr_len as uint).unwrap(); - raw_header.push_all(raw_addr.as_slice()); - - let raw_port = stream.read_exact(2).unwrap(); - raw_header.push_all(raw_port.as_slice()); - let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16; - - (raw_header, Sock5AddrTypeDomainName, String::from_utf8(raw_addr).unwrap(), port) - }, - _ => { - // Address type not supported - TcpRelayLocal::send_error_reply(stream, SOCK5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED); - fail!("Unsupported address type: {}", atyp); - } - } - } - fn handle_connect_local_stream(local_stream: &mut TcpStream, remote_stream: &mut TcpStream, cipher: &mut CipherVariant) { let mut buf = [0u8, .. 0xffff]; @@ -133,17 +70,25 @@ impl TcpRelayLocal { match remote_stream.write(encrypted_msg.as_slice()) { Ok(..) => {}, Err(err) => { - if err.kind != EndOfFile { - error!("Error occurs in handle_local_stream while writing to remote stream: {}", err); + match err.kind { + EndOfFile | TimedOut => {}, + _ => { + error!("Error occurs while writing to remote stream: {}", err); + } } + local_stream.close_read().unwrap(); break } } }, Err(err) => { - if err.kind != EndOfFile { - debug!("Error occurs in handle_local_stream while reading from local stream: {}", err); + match err.kind { + EndOfFile | TimedOut => {}, + _ => { + error!("Error occurs while reading from local stream: {}", err); + } } + remote_stream.close_write().unwrap(); break } } @@ -168,17 +113,25 @@ impl TcpRelayLocal { match local_stream.write(decrypted_msg.as_slice()) { Ok(..) => {}, Err(err) => { - if err.kind != EndOfFile { - debug!("Error occurs in handle_remote_stream while writing to local stream: {}", err); + match err.kind { + EndOfFile | TimedOut => {}, + _ => { + error!("Error occurs while writing to local stream: {}", err); + } } + remote_stream.close_read().unwrap(); break } } }, Err(err) => { - if err.kind != EndOfFile { - error!("Error occurs in handle_remote_stream while reading from remote stream: {}", err); + match err.kind { + EndOfFile | TimedOut => {}, + _ => { + error!("Error occurs while reading from remote stream: {}", err); + } } + local_stream.close_write().unwrap(); break } } @@ -234,11 +187,12 @@ impl Relay for TcpRelayLocal { fail!("Invalid sock version {}", sock_ver); } - let (raw_header, atyp, bind_addr, bind_port) - = TcpRelayLocal::parse_request_header(&mut stream); + let mut header_buf = [0u8, .. 512]; + stream.read_at_least(1, header_buf) + .ok().expect("Error occurs while reading header"); - debug!("SockVer {}, CMD {}, atyp {}, bind_addr {}, bind_port {}", - sock_ver, cmd, atyp, bind_addr, bind_port); + let (header_len, addr) + = parse_request_header(&mut stream, header_buf); let mut remote_stream = TcpStream::connect(server_addr.as_slice(), *server_port.deref()) @@ -250,11 +204,13 @@ impl Relay for TcpRelayLocal { match cmd { SOCK5_CMD_TCP_CONNECT => { + info!("CONNECT {}", addr); + let reply = [SOCK5_VERSION, SOCK5_REPLY_SUCCEEDED, 0x00, SOCK5_CMD_TCP_CONNECT, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10]; stream.write(reply).unwrap(); - let encrypted_header = cipher.encrypt(raw_header.as_slice()); + let encrypted_header = cipher.encrypt(header_buf.slice_to(header_len)); remote_stream.write(encrypted_header.as_slice()).unwrap(); TcpRelayLocal::async_handle_connect_remote_stream(stream.clone(), @@ -266,14 +222,14 @@ impl Relay for TcpRelayLocal { &mut cipher); }, SOCK5_CMD_TCP_BIND => { - + unimplemented!(); }, SOCK5_CMD_UDP_ASSOCIATE => { - + unimplemented!(); }, _ => { // unsupported CMD - TcpRelayLocal::send_error_reply(&mut stream, SOCK5_REPLY_COMMAND_NOT_SUPPORTED); + send_error_reply(&mut stream, SOCK5_REPLY_COMMAND_NOT_SUPPORTED); fail!("Unsupported command"); } } diff --git a/src/relay/tcprelay/mod.rs b/src/relay/tcprelay/mod.rs index b8fa1afd..0a2a4cd6 100644 --- a/src/relay/tcprelay/mod.rs +++ b/src/relay/tcprelay/mod.rs @@ -1,5 +1,7 @@ #[phase(plugin, link)] extern crate log; +extern crate native; + pub mod local; pub mod server; diff --git a/src/relay/tcprelay/server.rs b/src/relay/tcprelay/server.rs index d0570d1a..e1ca3785 100644 --- a/src/relay/tcprelay/server.rs +++ b/src/relay/tcprelay/server.rs @@ -1,6 +1,20 @@ +#[phase(plugin, link)] +extern crate log; + +use std::sync::Arc; +use std::io::{Listener, TcpListener, Acceptor, TcpStream}; +use std::io::net::ip::{Port, IpAddr}; +use std::io::{EndOfFile, TimedOut}; use config::Config; use relay::Relay; +use relay::{parse_request_header, Sock5SocketAddr, Sock5DomainNameAddr}; + +use crypto::cipher; +use crypto::cipher::Cipher; +use crypto::cipher::CipherVariant; + +use std::io::net::addrinfo::get_host_addresses; pub struct TcpRelayServer { config: Config, @@ -12,10 +26,149 @@ impl TcpRelayServer { config: c, } } + + fn connect_remote(addrs: Vec, port: Port) -> Option { + for addr in addrs.iter() { + match TcpStream::connect(addr.to_string().as_slice(), port) { + Ok(s) => { return Some(s) }, + Err(..) => {} + } + } + None + } + + fn async_handle_connect_remote_stream(mut local_stream: TcpStream, mut remote_stream: TcpStream, + mut cipher: CipherVariant) { + spawn(proc() { + let mut buf = [0u8, .. 0xffff]; + + loop { + match remote_stream.read_at_least(1, buf) { + Ok(len) => { + let real_buf = buf.slice_to(len); + + let encrypted_msg = cipher.encrypt(real_buf); + + match local_stream.write(encrypted_msg.as_slice()) { + Ok(..) => {}, + Err(err) => { + match err.kind { + EndOfFile | TimedOut => {}, + _ => { + error!("Error occurs while writing to local stream: {}", err); + } + } + remote_stream.close_read().unwrap(); + break + } + } + }, + Err(err) => { + match err.kind { + EndOfFile | TimedOut => {}, + _ => { + error!("Error occurs while reading from remote stream: {}", err); + } + } + local_stream.close_write().unwrap(); + break + } + } + } + }); + } } impl Relay for TcpRelayServer { fn run(&self) { + let server_addr = self.config.server.as_slice(); + let server_port = self.config.server_port; + let password = Arc::new(self.config.password.clone()); + let encrypt_method = Arc::new(self.config.method.clone()); + + let timeout = match self.config.timeout { + Some(timeout) => Some(timeout * 1000), + None => None + }; + + let mut acceptor = match TcpListener::bind(server_addr, server_port).listen() { + Ok(acpt) => acpt, + Err(e) => { + fail!("Error occurs while listening server address: {}", e.to_string()); + } + }; + + info!("Shadowsocks listening on {}:{}", server_addr, server_port); + + loop { + match acceptor.accept() { + Ok(mut stream) => { + stream.set_timeout(timeout); + + let password = password.clone(); + let encrypt_method = encrypt_method.clone(); + + spawn(proc() { + let mut cipher = cipher::with_name(encrypt_method.as_slice(), + password.as_slice().as_bytes()) + .expect("Unsupported cipher"); + + let mut buf = [0u8, .. 0xffff]; + let header = { + let header_len = stream.read(buf).ok() + .expect("Error occurs while reading header"); + let encrypted_header = buf.slice_to(header_len); + cipher.decrypt(encrypted_header) + }; + + let (_, addr) = parse_request_header(&mut stream, header.as_slice()); + info!("Connecting to {}", addr); + let mut remote_stream = match addr { + Sock5SocketAddr(sockaddr) => { + TcpStream::connect(sockaddr.ip.to_string().as_slice(), sockaddr.port) + .ok().expect("Unable to connect to remote") + }, + Sock5DomainNameAddr(domainaddr) => { + let addrs = match get_host_addresses(domainaddr.domain_name.as_slice()) { + Ok(ipaddrs) => ipaddrs, + Err(e) => { + fail!("Error occurs while get_host_addresses: {}", e); + } + }; + + if addrs.len() == 0 { + fail!("Cannot resolve host {}, empty host list", domainaddr.domain_name); + } + + TcpRelayServer::connect_remote(addrs, domainaddr.port) + .expect(format!("Unable to resolve {}", domainaddr.domain_name).as_slice()) + } + }; + + TcpRelayServer::async_handle_connect_remote_stream(stream.clone(), + remote_stream.clone(), + cipher.clone()); + + loop { + match stream.read(buf) { + Ok(len) => { + let real_buf = buf.slice_to(len); + let decrypted_msg = cipher.decrypt(real_buf); + remote_stream.write(decrypted_msg.as_slice()); + }, + Err(e) => { + error!("Error occurs while reading from client: {}", e); + break + } + } + } + }); + }, + Err(e) => { + fail!("Error occurs while accepting: {}", e.to_string()); + } + } + } } }