diff --git a/Cargo.toml b/Cargo.toml index 40f6e319..42aa5940 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-rust" -version = "1.7.0-alpha.19" +version = "1.7.0-alpha.20" authors = ["Y. T. CHUNG "] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/zonyitoo/shadowsocks-rust" diff --git a/src/bin/local.rs b/src/bin/local.rs index 8d95d5ae..a6063655 100644 --- a/src/bin/local.rs +++ b/src/bin/local.rs @@ -25,7 +25,7 @@ use futures::Future; use log::{LevelFilter, Record}; use tokio::runtime::Runtime; -use shadowsocks::{plugin::PluginConfig, run_local, Config, ConfigType, ServerAddr, ServerConfig}; +use shadowsocks::{plugin::PluginConfig, run_local, Config, ConfigType, Mode, ServerAddr, ServerConfig}; fn log_time(fmt: &mut Formatter, without_time: bool, record: &Record) -> io::Result<()> { if without_time { @@ -136,6 +136,12 @@ fn main() { .takes_value(true) .help("Server address in SIP002 URL"), ) + .arg( + Arg::with_name("NO_DELAY") + .long("no-delay") + .takes_value(false) + .help("Set no-delay option for socket"), + ) .get_matches(); let mut log_builder = Builder::new(); @@ -250,7 +256,17 @@ fn main() { return; } - config.enable_udp |= matches.is_present("ENABLE_UDP"); + if matches.is_present("ENABLE_UDP") { + if config.mode.enable_tcp() { + config.mode = Mode::TcpAndUdp; + } else { + config.mode = Mode::UdpOnly; + } + } + + if matches.is_present("NO_DELAY") { + config.no_delay = true; + } if let Some(p) = matches.value_of("PLUGIN") { let plugin = PluginConfig { diff --git a/src/bin/server.rs b/src/bin/server.rs index 17151ddc..e378a0b1 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -27,7 +27,7 @@ use futures::Future; use log::{LevelFilter, Record}; use tokio::runtime::Runtime; -use shadowsocks::{plugin::PluginConfig, run_server, Config, ConfigType, ServerAddr, ServerConfig}; +use shadowsocks::{plugin::PluginConfig, run_server, Config, ConfigType, Mode, ServerAddr, ServerConfig}; fn log_time(fmt: &mut Formatter, without_time: bool, record: &Record) -> io::Result<()> { if without_time { @@ -125,6 +125,12 @@ fn main() { .long("log-without-time") .help("Disable time in log"), ) + .arg( + Arg::with_name("NO_DELAY") + .long("no-delay") + .takes_value(false) + .help("Set no-delay option for socket"), + ) .get_matches(); let mut log_builder = Builder::new(); @@ -220,7 +226,17 @@ fn main() { return; } - config.enable_udp |= matches.is_present("ENABLE_UDP"); + if matches.is_present("ENABLE_UDP") { + if config.mode.enable_tcp() { + config.mode = Mode::TcpAndUdp; + } else { + config.mode = Mode::UdpOnly; + } + } + + if matches.is_present("NO_DELAY") { + config.no_delay = true; + } if let Some(p) = matches.value_of("PLUGIN") { let plugin = PluginConfig { diff --git a/src/config.rs b/src/config.rs index b9ccfada..6174879e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -86,8 +86,6 @@ struct SSConfig { #[serde(skip_serializing_if = "Option::is_none")] plugin_opts: Option, #[serde(skip_serializing_if = "Option::is_none")] - enable_udp: Option, - #[serde(skip_serializing_if = "Option::is_none")] timeout: Option, #[serde(skip_serializing_if = "Option::is_none")] udp_timeout: Option, @@ -99,6 +97,10 @@ struct SSConfig { dns: Option, #[serde(skip_serializing_if = "Option::is_none")] remote_dns: Option, + #[serde(skip_serializing_if = "Option::is_none")] + mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + no_delay: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -472,15 +474,63 @@ pub enum ConfigType { Server, } +/// Server mode +#[derive(Clone, Copy, Debug)] +pub enum Mode { + TcpOnly, + TcpAndUdp, + UdpOnly, +} + +impl Mode { + pub fn enable_udp(&self) -> bool { + match *self { + Mode::UdpOnly | Mode::TcpAndUdp => true, + _ => false, + } + } + + pub fn enable_tcp(&self) -> bool { + match *self { + Mode::TcpOnly | Mode::TcpAndUdp => true, + _ => false, + } + } +} + +impl fmt::Display for Mode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Mode::TcpOnly => f.write_str("tcp_only"), + Mode::TcpAndUdp => f.write_str("tcp_and_udp"), + Mode::UdpOnly => f.write_str("udp_only"), + } + } +} + +impl FromStr for Mode { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "tcp_only" => Ok(Mode::TcpOnly), + "tcp_and_udp" => Ok(Mode::TcpAndUdp), + "udp_only" => Ok(Mode::UdpOnly), + _ => Err(()), + } + } +} + /// Configuration #[derive(Clone, Debug)] pub struct Config { pub server: Vec, pub local: Option, - pub enable_udp: bool, pub forbidden_ip: HashSet, pub dns: Option, pub remote_dns: Option, + pub mode: Mode, + pub no_delay: bool, } impl Default for Config { @@ -544,10 +594,11 @@ impl Config { Config { server: Vec::new(), local: None, - enable_udp: false, forbidden_ip: HashSet::new(), dns: None, remote_dns: None, + mode: Mode::TcpOnly, + no_delay: false, } } @@ -730,9 +781,24 @@ impl Config { } } - // UDP switch - if let Some(enable) = config.enable_udp { - nconfig.enable_udp = enable; + // Mode + if let Some(m) = config.mode { + match m.parse::() { + Ok(xm) => nconfig.mode = xm, + Err(..) => { + let e = Error::new( + ErrorKind::Malformed, + "malformed `mode`, must be one of `tcp_only`, `udp_only` and `tcp_and_udp`", + None, + ); + return Err(e); + } + } + } + + // TCP nodelay + if let Some(b) = config.no_delay { + nconfig.no_delay = b; } Ok(nconfig) @@ -853,8 +919,10 @@ impl fmt::Display for Config { } } - if self.enable_udp { - jconf.enable_udp = Some(true); + jconf.mode = Some(self.mode.to_string()); + + if self.no_delay { + jconf.no_delay = Some(self.no_delay); } if !self.forbidden_ip.is_empty() { diff --git a/src/lib.rs b/src/lib.rs index 4f5988e2..9b0e4142 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ extern crate url; pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub use self::{ - config::{ClientConfig, Config, ConfigType, ServerAddr, ServerConfig}, + config::{ClientConfig, Config, ConfigType, Mode, ServerAddr, ServerConfig}, relay::{dns::run as run_dns, local::run as run_local, server::run as run_server, tcprelay::client::Socks5Client}, }; diff --git a/src/relay/local.rs b/src/relay/local.rs index c577782f..0b82666a 100644 --- a/src/relay/local.rs +++ b/src/relay/local.rs @@ -39,7 +39,7 @@ pub fn run(config: Config) -> impl Future + Send { let mut context = Context::new(config); - if context.config().enable_udp { + if context.config().mode.enable_udp() { // Clone config here, because the config for TCP relay will be modified // after plugins started let udp_context = SharedContext::new(context.clone()); diff --git a/src/relay/server.rs b/src/relay/server.rs index 654e070d..2a77012a 100644 --- a/src/relay/server.rs +++ b/src/relay/server.rs @@ -39,7 +39,7 @@ pub fn run(config: Config) -> impl Future + Send { let mut vf = Vec::new(); - if context.config().enable_udp { + if context.config().mode.enable_udp() { // Clone config here, because the config for TCP relay will be modified // after plugins started let udp_context = SharedContext::new(context.clone()); @@ -50,14 +50,16 @@ pub fn run(config: Config) -> impl Future + Send { vf.push(boxed_future(udp_fut)); } - // Hold it here, kill all plugins when `tokio::run` is finished - let plugins = launch_plugin(context.config_mut(), PluginMode::Server).expect("Failed to launch plugins"); - let mon = ::monitor::monitor_signal(plugins); + if context.config().mode.enable_tcp() { + // Hold it here, kill all plugins when `tokio::run` is finished + let plugins = launch_plugin(context.config_mut(), PluginMode::Server).expect("Failed to launch plugins"); + let mon = ::monitor::monitor_signal(plugins); - let tcp_fut = run_tcp(SharedContext::new(context)); + let tcp_fut = run_tcp(SharedContext::new(context)); - vf.push(boxed_future(mon)); - vf.push(boxed_future(tcp_fut)); + vf.push(boxed_future(mon)); + vf.push(boxed_future(tcp_fut)); + } futures_unordered(vf).into_future().then(|res| -> io::Result<()> { match res { diff --git a/src/relay/tcprelay/socks5_local.rs b/src/relay/tcprelay/socks5_local.rs index 2f7d0ba1..a1f371b0 100644 --- a/src/relay/tcprelay/socks5_local.rs +++ b/src/relay/tcprelay/socks5_local.rs @@ -148,13 +148,22 @@ fn handle_socks5_client( let addr = header.address; match header.command { socks5::Command::TcpConnect => { - debug!("CONNECT {}", addr); - let fut = - handle_socks5_connect(context, (r, w), cloned_client_addr, addr.clone(), conf).map_err(move |err| { - error!("CONNECT {} failed with error: {}", addr, err); - err - }); - boxed_future(fut) + let enable_tcp = context.config().mode.enable_tcp(); + if enable_tcp { + debug!("CONNECT {}", addr); + let fut = handle_socks5_connect(context, (r, w), cloned_client_addr, addr.clone(), conf) + .map_err(move |err| { + error!("CONNECT {} failed with error: {}", addr, err); + err + }); + boxed_future(fut) + } else { + warn!("CONNECT is not enabled"); + let fut = TcpResponseHeader::new(socks5::Reply::CommandNotSupported, addr) + .write_to(w) + .map(|_| ()); + boxed_future(fut) + } } socks5::Command::TcpBind => { warn!("BIND is not supported"); @@ -176,7 +185,7 @@ fn handle_socks5_client( boxed_future(fut) } else { - warn!("UDP Associate is not enabled"); + warn!("UDP ASSOCIATE is not enabled"); let fut = TcpResponseHeader::new(socks5::Reply::CommandNotSupported, addr) .write_to(w) .map(|_| ()); @@ -208,7 +217,7 @@ pub fn run(context: SharedContext) -> impl Future info!("ShadowSocks TCP Listening on {}", local_addr); let udp_conf = UdpConfig { - enable_udp: context.config().enable_udp, + enable_udp: context.config().mode.enable_udp(), client_addr: local_addr, }; diff --git a/tests/dns.rs b/tests/dns.rs index 211c7928..62edc2be 100644 --- a/tests/dns.rs +++ b/tests/dns.rs @@ -29,7 +29,7 @@ const CONFIG: &'static str = r#"{ "password": "abc", "timeout": 20, "method": "aes-256-gcm", - "enable_udp": true + "mode": "tcp_and_udp" }"#; #[test] diff --git a/tests/socks5.rs b/tests/socks5.rs index 4cc43572..addd1169 100644 --- a/tests/socks5.rs +++ b/tests/socks5.rs @@ -16,7 +16,7 @@ use tokio::runtime::current_thread::Runtime; use tokio_io::io::{flush, read_to_end, write_all}; use shadowsocks::{ - config::{Config, ServerConfig}, + config::{Config, Mode, ServerConfig}, crypto::CipherType, relay::{socks5::Address, tcprelay::client::Socks5Client}, run_local, @@ -49,7 +49,7 @@ impl Socks5TestServer { let mut cfg = Config::new(); cfg.local = Some(local_addr); cfg.server = vec![ServerConfig::basic(svr_addr, pwd.to_owned(), method)]; - cfg.enable_udp = enable_udp; + cfg.mode = if enable_udp { Mode::TcpAndUdp } else { Mode::TcpOnly }; cfg }, } diff --git a/tests/udp.rs b/tests/udp.rs index de7c6a69..432f6564 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -21,7 +21,7 @@ use tokio::runtime::current_thread::Runtime; use tokio_io::io::read_to_end; use shadowsocks::{ - config::{Config, ServerConfig}, + config::{Config, Mode, ServerConfig}, crypto::CipherType, relay::{ socks5::{Address, UdpAssociateHeader}, @@ -48,7 +48,7 @@ fn get_config() -> Config { PASSWORD.to_owned(), METHOD, )]; - cfg.enable_udp = true; + cfg.mode = Mode::UdpOnly; cfg }