From 50286dc9d9683257b3ff9b05064c445efdf32401 Mon Sep 17 00:00:00 2001 From: zonyitoo Date: Sun, 11 Apr 2021 20:03:04 +0800 Subject: [PATCH] free the memory of clap after parsing options - --nofile is now only existed in *nix build except Android --- bin/sslocal.rs | 712 +++++++++--------- bin/ssmanager.rs | 454 +++++------ bin/ssserver.rs | 456 +++++------ crates/shadowsocks-service/src/config.rs | 13 +- crates/shadowsocks-service/src/local/mod.rs | 2 +- crates/shadowsocks-service/src/manager/mod.rs | 2 +- crates/shadowsocks-service/src/server/mod.rs | 2 +- 7 files changed, 843 insertions(+), 798 deletions(-) diff --git a/bin/sslocal.rs b/bin/sslocal.rs index 1f4b40a2..4119249a 100644 --- a/bin/sslocal.rs +++ b/bin/sslocal.rs @@ -36,412 +36,424 @@ mod common; const VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { - let mut app = clap_app!(shadowsocks => - (version: VERSION) - (about: "A fast tunnel proxy that helps you bypass firewalls.") + let (config, runtime) = { + let mut app = clap_app!(shadowsocks => + (version: VERSION) + (about: "A fast tunnel proxy that helps you bypass firewalls.") - (@arg CONFIG: -c --config +takes_value required_unless_all(&["LOCAL_ADDR", "SERVER_CONFIG"]) "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") + (@arg CONFIG: -c --config +takes_value required_unless_all(&["LOCAL_ADDR", "SERVER_CONFIG"]) "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") - (@arg LOCAL_ADDR: -b --("local-addr") +takes_value {validator::validate_server_addr} "Local address, listen only to this address if specified") - (@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] requires[LOCAL_ADDR] "Server mode UDP_ONLY") - (@arg TCP_AND_UDP: -U requires[LOCAL_ADDR] "Server mode TCP_AND_UDP") - (@arg PROTOCOL: --protocol +takes_value requires[LOCAL_ADDR] possible_values(ProtocolType::available_protocols()) "Protocol for communicating with clients (SOCKS5 by default)") - (@arg UDP_BIND_ADDR: --("udp-bind-addr") +takes_value requires[LOCAL_ADDR] {validator::validate_server_addr} "UDP relay's bind address, default is the same as local-addr") + (@arg LOCAL_ADDR: -b --("local-addr") +takes_value {validator::validate_server_addr} "Local address, listen only to this address if specified") + (@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] requires[LOCAL_ADDR] "Server mode UDP_ONLY") + (@arg TCP_AND_UDP: -U requires[LOCAL_ADDR] "Server mode TCP_AND_UDP") + (@arg PROTOCOL: --protocol +takes_value requires[LOCAL_ADDR] possible_values(ProtocolType::available_protocols()) "Protocol for communicating with clients (SOCKS5 by default)") + (@arg UDP_BIND_ADDR: --("udp-bind-addr") +takes_value requires[LOCAL_ADDR] {validator::validate_server_addr} "UDP relay's bind address, default is the same as local-addr") - (@arg SERVER_ADDR: -s --("server-addr") +takes_value {validator::validate_server_addr} requires[PASSWORD ENCRYPT_METHOD] "Server address") - (@arg PASSWORD: -k --password +takes_value requires[SERVER_ADDR] "Server's password") - (@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value requires[SERVER_ADDR] possible_values(available_ciphers()) "Server's encryption method") - (@arg TIMEOUT: --timeout +takes_value {validator::validate_u64} requires[SERVER_ADDR] "Server's timeout seconds for TCP relay") + (@arg SERVER_ADDR: -s --("server-addr") +takes_value {validator::validate_server_addr} requires[PASSWORD ENCRYPT_METHOD] "Server address") + (@arg PASSWORD: -k --password +takes_value requires[SERVER_ADDR] "Server's password") + (@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value requires[SERVER_ADDR] possible_values(available_ciphers()) "Server's encryption method") + (@arg TIMEOUT: --timeout +takes_value {validator::validate_u64} requires[SERVER_ADDR] "Server's timeout seconds for TCP relay") - (@arg PLUGIN: --plugin +takes_value requires[SERVER_ADDR] "SIP003 (https://shadowsocks.org/en/spec/Plugin.html) plugin") - (@arg PLUGIN_OPT: --("plugin-opts") +takes_value requires[PLUGIN] "Set SIP003 plugin options") + (@arg PLUGIN: --plugin +takes_value requires[SERVER_ADDR] "SIP003 (https://shadowsocks.org/en/spec/Plugin.html) plugin") + (@arg PLUGIN_OPT: --("plugin-opts") +takes_value requires[PLUGIN] "Set SIP003 plugin options") - (@arg URL: --("server-url") +takes_value {validator::validate_server_url} "Server address in SIP002 (https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html) URL") + (@arg URL: --("server-url") +takes_value {validator::validate_server_url} "Server address in SIP002 (https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html) URL") - (@group SERVER_CONFIG => - (@attributes +multiple arg[SERVER_ADDR URL])) + (@group SERVER_CONFIG => + (@attributes +multiple arg[SERVER_ADDR URL])) - (@arg NOFILE: -n --nofile +takes_value "Set RLIMIT_NOFILE with both soft and hard limit (only for *nix systems)") - (@arg ACL: --acl +takes_value "Path to ACL (Access Control List)") - (@arg DNS: --dns +takes_value "DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"") + (@arg ACL: --acl +takes_value "Path to ACL (Access Control List)") + (@arg DNS: --dns +takes_value "DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"") - (@arg TCP_NO_DELAY: --("tcp-no-delay") !takes_value alias("no-delay") "Set TCP_NODELAY option for socket") + (@arg TCP_NO_DELAY: --("tcp-no-delay") !takes_value alias("no-delay") "Set TCP_NODELAY option for socket") - (@arg UDP_TIMEOUT: --("udp-timeout") +takes_value {validator::validate_u64} "Timeout seconds for UDP relay") - (@arg UDP_MAX_ASSOCIATIONS: --("udp-max-associations") +takes_value {validator::validate_u64} "Maximum associations to be kept simultaneously for UDP relay") + (@arg UDP_TIMEOUT: --("udp-timeout") +takes_value {validator::validate_u64} "Timeout seconds for UDP relay") + (@arg UDP_MAX_ASSOCIATIONS: --("udp-max-associations") +takes_value {validator::validate_u64} "Maximum associations to be kept simultaneously for UDP relay") - (@arg INBOUND_SEND_BUFFER_SIZE: --("inbound-send-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_SNDBUF option") - (@arg INBOUND_RECV_BUFFER_SIZE: --("inbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_RCVBUF option") - (@arg OUTBOUND_SEND_BUFFER_SIZE: --("outbound-send-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_SNDBUF option") - (@arg OUTBOUND_RECV_BUFFER_SIZE: --("outbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_RCVBUF option") - ); - - // FIXME: -6 is not a identifier, so we cannot build it with clap_app! - app = app.arg( - Arg::with_name("IPV6_FIRST") - .short("6") - .help("Resolve hostname to IPv6 address first"), - ); - - #[cfg(feature = "logging")] - { - app = clap_app!(@app (app) - (@arg VERBOSE: -v ... "Set log level") - (@arg LOG_WITHOUT_TIME: --("log-without-time") "Log without datetime prefix") - (@arg LOG_CONFIG: --("log-config") +takes_value "log4rs configuration file") - ); - } - - #[cfg(feature = "local-tunnel")] - { - app = clap_app!(@app (app) - (@arg FORWARD_ADDR: -f --("forward-addr") +takes_value requires[LOCAL_ADDR] {validator::validate_address} required_if("PROTOCOL", "tunnel") "Forwarding data directly to this address (for tunnel)") - ); - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - { - app = clap_app!(@app (app) - (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set SO_BINDTODEVICE option for outbound socket") - (@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket") - ); - } - - #[cfg(any(target_os = "macos", target_os = "ios"))] - { - app = clap_app!(@app (app) - (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set IP_BOUND_IF option for outbound socket") - ); - } - - #[cfg(feature = "local-redir")] - { - let available_redir_types = RedirType::available_types(); - - if RedirType::tcp_default() != RedirType::NotSupported { - app = clap_app!(@app (app) - (@arg TCP_REDIR: --("tcp-redir") +takes_value requires[LOCAL_ADDR] possible_values(&available_redir_types) "TCP redir (transparent proxy) type") - ); - } - - if RedirType::udp_default() != RedirType::NotSupported { - app = clap_app!(@app (app) - (@arg UDP_REDIR: --("udp-redir") +takes_value requires[LOCAL_ADDR] possible_values(&available_redir_types) "UDP redir (transparent proxy) type") - ); - } - } - - #[cfg(target_os = "android")] - { - app = clap_app!(@app (app) - (@arg VPN_MODE: --vpn "Enable VPN mode (only for Android)") - ); - } - - #[cfg(feature = "local-flow-stat")] - { - app = clap_app!(@app (app) - (@arg STAT_PATH: --("stat-path") +takes_value "Specify socket path (unix domain socket) for sending traffic statistic") - ); - } - - #[cfg(feature = "local-dns")] - { - app = clap_app!(@app (app) - (@arg LOCAL_DNS_ADDR: --("local-dns-addr") +takes_value required_if("PROTOCOL", "dns") requires[LOCAL_ADDR] {validator::validate_name_server_addr} "Specify the address of local DNS server, send queries directly") - (@arg REMOTE_DNS_ADDR: --("remote-dns-addr") +takes_value required_if("PROTOCOL", "dns") requires[LOCAL_ADDR] {validator::validate_address} "Specify the address of remote DNS server, send queries through shadowsocks' tunnel") - + (@arg INBOUND_SEND_BUFFER_SIZE: --("inbound-send-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_SNDBUF option") + (@arg INBOUND_RECV_BUFFER_SIZE: --("inbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_RCVBUF option") + (@arg OUTBOUND_SEND_BUFFER_SIZE: --("outbound-send-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_SNDBUF option") + (@arg OUTBOUND_RECV_BUFFER_SIZE: --("outbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_RCVBUF option") ); - #[cfg(target_os = "android")] + // FIXME: -6 is not a identifier, so we cannot build it with clap_app! + app = app.arg( + Arg::with_name("IPV6_FIRST") + .short("6") + .help("Resolve hostname to IPv6 address first"), + ); + + #[cfg(feature = "logging")] { app = clap_app!(@app (app) - (@arg DNS_LOCAL_ADDR: --("dns-addr") +takes_value requires_all(&["LOCAL_ADDR", "REMOTE_DNS_ADDR"]) {validator::validate_server_addr} "DNS address, listen to this address if specified") + (@arg VERBOSE: -v ... "Set log level") + (@arg LOG_WITHOUT_TIME: --("log-without-time") "Log without datetime prefix") + (@arg LOG_CONFIG: --("log-config") +takes_value "log4rs configuration file") ); } - } - - #[cfg(unix)] - { - app = clap_app!(@app (app) - (@arg DAEMONIZE: -d --("daemonize") "Daemonize") - (@arg DAEMONIZE_PID_PATH: --("daemonize-pid") +takes_value "File path to store daemonized process's PID") - ); - } - - #[cfg(feature = "multi-threaded")] - { - app = clap_app!(@app (app) - (@arg SINGLE_THREADED: --("single-threaded") "Run the program all in one thread") - (@arg WORKER_THREADS: --("worker-threads") +takes_value {validator::validate_usize} "Sets the number of worker threads the `Runtime` will use") - ); - } - - let matches = app.get_matches(); - - #[cfg(feature = "logging")] - match matches.value_of("LOG_CONFIG") { - Some(path) => { - logging::init_with_file(path); - } - None => { - logging::init_with_config("sslocal", &matches); - } - } - - let mut config = match matches.value_of("CONFIG") { - Some(cpath) => match Config::load_from_file(cpath, ConfigType::Local) { - Ok(cfg) => cfg, - Err(err) => { - panic!("loading config \"{}\", {}", cpath, err); - } - }, - None => Config::new(ConfigType::Local), - }; - - if let Some(svr_addr) = matches.value_of("SERVER_ADDR") { - let password = matches.value_of("PASSWORD").expect("password"); - let method = matches - .value_of("ENCRYPT_METHOD") - .expect("encrypt-method") - .parse::() - .expect("encryption method"); - let svr_addr = svr_addr.parse::().expect("server-addr"); - - let timeout = matches - .value_of("TIMEOUT") - .map(|t| t.parse::().expect("timeout")) - .map(Duration::from_secs); - - let mut sc = ServerConfig::new(svr_addr, password.to_owned(), method); - if let Some(timeout) = timeout { - sc.set_timeout(timeout); - } - - if let Some(p) = matches.value_of("PLUGIN") { - let plugin = PluginConfig { - plugin: p.to_owned(), - plugin_opts: matches.value_of("PLUGIN_OPT").map(ToOwned::to_owned), - plugin_args: Vec::new(), - }; - - sc.set_plugin(plugin); - } - - config.server.push(sc); - } - - if let Some(url) = matches.value_of("URL") { - let svr_addr = url.parse::().expect("server SIP002 url"); - config.server.push(svr_addr); - } - - #[cfg(feature = "local-flow-stat")] - { - if let Some(stat_path) = matches.value_of("STAT_PATH") { - config.stat_path = Some(From::from(stat_path)); - } - } - - #[cfg(target_os = "android")] - if matches.is_present("VPN_MODE") { - // A socket `protect_path` in CWD - // Same as shadowsocks-libev's android.c - config.outbound_vpn_protect_path = Some(From::from("protect_path")); - } - - if let Some(local_addr) = matches.value_of("LOCAL_ADDR") { - let local_addr = local_addr.parse::().expect("local bind addr"); - - let protocol = match matches.value_of("PROTOCOL") { - Some("socks") => ProtocolType::Socks, - #[cfg(feature = "local-http")] - Some("http") => ProtocolType::Http, - #[cfg(feature = "local-tunnel")] - Some("tunnel") => ProtocolType::Tunnel, - #[cfg(feature = "local-redir")] - Some("redir") => ProtocolType::Redir, - #[cfg(feature = "local-dns")] - Some("dns") => ProtocolType::Dns, - Some(p) => panic!("not supported `protocol` \"{}\"", p), - None => ProtocolType::Socks, - }; - - let mut local_config = LocalConfig::new(local_addr, protocol); - - if let Some(udp_bind_addr) = matches.value_of("UDP_BIND_ADDR") { - local_config.udp_addr = Some(udp_bind_addr.parse::().expect("udp-bind-addr")); - } #[cfg(feature = "local-tunnel")] - if let Some(faddr) = matches.value_of("FORWARD_ADDR") { - let addr = faddr.parse::
().expect("forward-addr"); - local_config.forward_addr = Some(addr); + { + app = clap_app!(@app (app) + (@arg FORWARD_ADDR: -f --("forward-addr") +takes_value requires[LOCAL_ADDR] {validator::validate_address} required_if("PROTOCOL", "tunnel") "Forwarding data directly to this address (for tunnel)") + ); + } + + #[cfg(all(unix, not(target_os = "android")))] + { + app = clap_app!(@app (app) + (@arg NOFILE: -n --nofile +takes_value "Set RLIMIT_NOFILE with both soft and hard limit") + ); + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + { + app = clap_app!(@app (app) + (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set SO_BINDTODEVICE option for outbound socket") + (@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket") + ); + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + { + app = clap_app!(@app (app) + (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set IP_BOUND_IF option for outbound socket") + ); } #[cfg(feature = "local-redir")] { - if let Some(tcp_redir) = matches.value_of("TCP_REDIR") { - local_config.tcp_redir = tcp_redir.parse::().expect("TCP redir type"); + let available_redir_types = RedirType::available_types(); + + if RedirType::tcp_default() != RedirType::NotSupported { + app = clap_app!(@app (app) + (@arg TCP_REDIR: --("tcp-redir") +takes_value requires[LOCAL_ADDR] possible_values(&available_redir_types) "TCP redir (transparent proxy) type") + ); } - if let Some(udp_redir) = matches.value_of("UDP_REDIR") { - local_config.udp_redir = udp_redir.parse::().expect("UDP redir type"); + if RedirType::udp_default() != RedirType::NotSupported { + app = clap_app!(@app (app) + (@arg UDP_REDIR: --("udp-redir") +takes_value requires[LOCAL_ADDR] possible_values(&available_redir_types) "UDP redir (transparent proxy) type") + ); } } + #[cfg(target_os = "android")] + { + app = clap_app!(@app (app) + (@arg VPN_MODE: --vpn "Enable VPN mode (only for Android)") + ); + } + + #[cfg(feature = "local-flow-stat")] + { + app = clap_app!(@app (app) + (@arg STAT_PATH: --("stat-path") +takes_value "Specify socket path (unix domain socket) for sending traffic statistic") + ); + } + #[cfg(feature = "local-dns")] { - use shadowsocks_service::local::dns::NameServerAddr; + app = clap_app!(@app (app) + (@arg LOCAL_DNS_ADDR: --("local-dns-addr") +takes_value required_if("PROTOCOL", "dns") requires[LOCAL_ADDR] {validator::validate_name_server_addr} "Specify the address of local DNS server, send queries directly") + (@arg REMOTE_DNS_ADDR: --("remote-dns-addr") +takes_value required_if("PROTOCOL", "dns") requires[LOCAL_ADDR] {validator::validate_address} "Specify the address of remote DNS server, send queries through shadowsocks' tunnel") - if let Some(local_dns_addr) = matches.value_of("LOCAL_DNS_ADDR") { - let addr = local_dns_addr.parse::().expect("local dns address"); - local_config.local_dns_addr = Some(addr); - } + ); - if let Some(remote_dns_addr) = matches.value_of("REMOTE_DNS_ADDR") { - let addr = remote_dns_addr.parse::
().expect("remote dns address"); - local_config.remote_dns_addr = Some(addr); + #[cfg(target_os = "android")] + { + app = clap_app!(@app (app) + (@arg DNS_LOCAL_ADDR: --("dns-addr") +takes_value requires_all(&["LOCAL_ADDR", "REMOTE_DNS_ADDR"]) {validator::validate_server_addr} "DNS address, listen to this address if specified") + ); } } - #[cfg(all(feature = "local-dns", target_os = "android"))] - if protocol != ProtocolType::Dns { - // Start a DNS local server binding to DNS_LOCAL_ADDR - // - // This is a special route only for shadowsocks-android - if let Some(dns_relay_addr) = matches.value_of("DNS_LOCAL_ADDR") { - let addr = dns_relay_addr.parse::().expect("dns relay address"); + #[cfg(unix)] + { + app = clap_app!(@app (app) + (@arg DAEMONIZE: -d --("daemonize") "Daemonize") + (@arg DAEMONIZE_PID_PATH: --("daemonize-pid") +takes_value "File path to store daemonized process's PID") + ); + } - let mut local_dns_config = LocalConfig::new(addr, ProtocolType::Dns); + #[cfg(feature = "multi-threaded")] + { + app = clap_app!(@app (app) + (@arg SINGLE_THREADED: --("single-threaded") "Run the program all in one thread") + (@arg WORKER_THREADS: --("worker-threads") +takes_value {validator::validate_usize} "Sets the number of worker threads the `Runtime` will use") + ); + } - // The `local_dns_addr` and `remote_dns_addr` are for this DNS server (for compatibility) - local_dns_config.local_dns_addr = local_config.local_dns_addr.take(); - local_dns_config.remote_dns_addr = local_config.remote_dns_addr.take(); + let matches = app.get_matches(); - config.local.push(local_dns_config); + #[cfg(feature = "logging")] + match matches.value_of("LOG_CONFIG") { + Some(path) => { + logging::init_with_file(path); + } + None => { + logging::init_with_config("sslocal", &matches); } } - if matches.is_present("UDP_ONLY") { - local_config.mode = Mode::UdpOnly; - } - - if matches.is_present("TCP_AND_UDP") { - local_config.mode = Mode::TcpAndUdp; - } - - config.local.push(local_config); - } - - if matches.is_present("TCP_NO_DELAY") { - config.no_delay = true; - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") { - config.outbound_fwmark = Some(mark.parse::().expect("an unsigned integer for `outbound-fwmark`")); - } - - #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))] - if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") { - config.outbound_bind_interface = Some(From::from(iface.to_owned())); - } - - if let Some(nofile) = matches.value_of("NOFILE") { - config.nofile = Some(nofile.parse::().expect("an unsigned integer for `nofile`")); - } - - if let Some(acl_file) = matches.value_of("ACL") { - let acl = match AccessControl::load_from_file(acl_file) { - Ok(acl) => acl, - Err(err) => { - panic!("loading ACL \"{}\", {}", acl_file, err); - } + let mut config = match matches.value_of("CONFIG") { + Some(cpath) => match Config::load_from_file(cpath, ConfigType::Local) { + Ok(cfg) => cfg, + Err(err) => { + panic!("loading config \"{}\", {}", cpath, err); + } + }, + None => Config::new(ConfigType::Local), }; - config.acl = Some(acl); - } - if let Some(dns) = matches.value_of("DNS") { - config.set_dns_formatted(dns).expect("dns"); - } + if let Some(svr_addr) = matches.value_of("SERVER_ADDR") { + let password = matches.value_of("PASSWORD").expect("password"); + let method = matches + .value_of("ENCRYPT_METHOD") + .expect("encrypt-method") + .parse::() + .expect("encryption method"); + let svr_addr = svr_addr.parse::().expect("server-addr"); - if matches.is_present("IPV6_FIRST") { - config.ipv6_first = true; - } + let timeout = matches + .value_of("TIMEOUT") + .map(|t| t.parse::().expect("timeout")) + .map(Duration::from_secs); - if let Some(udp_timeout) = matches.value_of("UDP_TIMEOUT") { - config.udp_timeout = Some(Duration::from_secs(udp_timeout.parse::().expect("udp-timeout"))); - } + let mut sc = ServerConfig::new(svr_addr, password.to_owned(), method); + if let Some(timeout) = timeout { + sc.set_timeout(timeout); + } - if let Some(udp_max_assoc) = matches.value_of("UDP_MAX_ASSOCIATIONS") { - config.udp_max_associations = Some(udp_max_assoc.parse::().expect("udp-max-associations")); - } + if let Some(p) = matches.value_of("PLUGIN") { + let plugin = PluginConfig { + plugin: p.to_owned(), + plugin_opts: matches.value_of("PLUGIN_OPT").map(ToOwned::to_owned), + plugin_args: Vec::new(), + }; - if let Some(bs) = matches.value_of("INBOUND_SEND_BUFFER_SIZE") { - config.inbound_send_buffer_size = Some(bs.parse::().expect("inbound-send-buffer-size")); - } - if let Some(bs) = matches.value_of("INBOUND_RECV_BUFFER_SIZE") { - config.inbound_recv_buffer_size = Some(bs.parse::().expect("inbound-recv-buffer-size")); - } - if let Some(bs) = matches.value_of("OUTBOUND_SEND_BUFFER_SIZE") { - config.outbound_send_buffer_size = Some(bs.parse::().expect("outbound-send-buffer-size")); - } - if let Some(bs) = matches.value_of("OUTBOUND_RECV_BUFFER_SIZE") { - config.outbound_recv_buffer_size = Some(bs.parse::().expect("outbound-recv-buffer-size")); - } + sc.set_plugin(plugin); + } - // DONE READING options + config.server.push(sc); + } - if config.local.is_empty() { - eprintln!( - "missing `local_address`, consider specifying it by --local-addr command line option, \ + if let Some(url) = matches.value_of("URL") { + let svr_addr = url.parse::().expect("server SIP002 url"); + config.server.push(svr_addr); + } + + #[cfg(feature = "local-flow-stat")] + { + if let Some(stat_path) = matches.value_of("STAT_PATH") { + config.stat_path = Some(From::from(stat_path)); + } + } + + #[cfg(target_os = "android")] + if matches.is_present("VPN_MODE") { + // A socket `protect_path` in CWD + // Same as shadowsocks-libev's android.c + config.outbound_vpn_protect_path = Some(From::from("protect_path")); + } + + if let Some(local_addr) = matches.value_of("LOCAL_ADDR") { + let local_addr = local_addr.parse::().expect("local bind addr"); + + let protocol = match matches.value_of("PROTOCOL") { + Some("socks") => ProtocolType::Socks, + #[cfg(feature = "local-http")] + Some("http") => ProtocolType::Http, + #[cfg(feature = "local-tunnel")] + Some("tunnel") => ProtocolType::Tunnel, + #[cfg(feature = "local-redir")] + Some("redir") => ProtocolType::Redir, + #[cfg(feature = "local-dns")] + Some("dns") => ProtocolType::Dns, + Some(p) => panic!("not supported `protocol` \"{}\"", p), + None => ProtocolType::Socks, + }; + + let mut local_config = LocalConfig::new(local_addr, protocol); + + if let Some(udp_bind_addr) = matches.value_of("UDP_BIND_ADDR") { + local_config.udp_addr = Some(udp_bind_addr.parse::().expect("udp-bind-addr")); + } + + #[cfg(feature = "local-tunnel")] + if let Some(faddr) = matches.value_of("FORWARD_ADDR") { + let addr = faddr.parse::
().expect("forward-addr"); + local_config.forward_addr = Some(addr); + } + + #[cfg(feature = "local-redir")] + { + if let Some(tcp_redir) = matches.value_of("TCP_REDIR") { + local_config.tcp_redir = tcp_redir.parse::().expect("TCP redir type"); + } + + if let Some(udp_redir) = matches.value_of("UDP_REDIR") { + local_config.udp_redir = udp_redir.parse::().expect("UDP redir type"); + } + } + + #[cfg(feature = "local-dns")] + { + use shadowsocks_service::local::dns::NameServerAddr; + + if let Some(local_dns_addr) = matches.value_of("LOCAL_DNS_ADDR") { + let addr = local_dns_addr.parse::().expect("local dns address"); + local_config.local_dns_addr = Some(addr); + } + + if let Some(remote_dns_addr) = matches.value_of("REMOTE_DNS_ADDR") { + let addr = remote_dns_addr.parse::
().expect("remote dns address"); + local_config.remote_dns_addr = Some(addr); + } + } + + #[cfg(all(feature = "local-dns", target_os = "android"))] + if protocol != ProtocolType::Dns { + // Start a DNS local server binding to DNS_LOCAL_ADDR + // + // This is a special route only for shadowsocks-android + if let Some(dns_relay_addr) = matches.value_of("DNS_LOCAL_ADDR") { + let addr = dns_relay_addr.parse::().expect("dns relay address"); + + let mut local_dns_config = LocalConfig::new(addr, ProtocolType::Dns); + + // The `local_dns_addr` and `remote_dns_addr` are for this DNS server (for compatibility) + local_dns_config.local_dns_addr = local_config.local_dns_addr.take(); + local_dns_config.remote_dns_addr = local_config.remote_dns_addr.take(); + + config.local.push(local_dns_config); + } + } + + if matches.is_present("UDP_ONLY") { + local_config.mode = Mode::UdpOnly; + } + + if matches.is_present("TCP_AND_UDP") { + local_config.mode = Mode::TcpAndUdp; + } + + config.local.push(local_config); + } + + if matches.is_present("TCP_NO_DELAY") { + config.no_delay = true; + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") { + config.outbound_fwmark = Some(mark.parse::().expect("an unsigned integer for `outbound-fwmark`")); + } + + #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))] + if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") { + config.outbound_bind_interface = Some(From::from(iface.to_owned())); + } + + #[cfg(all(unix, not(target_os = "android")))] + if let Some(nofile) = matches.value_of("NOFILE") { + config.nofile = Some(nofile.parse::().expect("an unsigned integer for `nofile`")); + } + + if let Some(acl_file) = matches.value_of("ACL") { + let acl = match AccessControl::load_from_file(acl_file) { + Ok(acl) => acl, + Err(err) => { + panic!("loading ACL \"{}\", {}", acl_file, err); + } + }; + config.acl = Some(acl); + } + + if let Some(dns) = matches.value_of("DNS") { + config.set_dns_formatted(dns).expect("dns"); + } + + if matches.is_present("IPV6_FIRST") { + config.ipv6_first = true; + } + + if let Some(udp_timeout) = matches.value_of("UDP_TIMEOUT") { + config.udp_timeout = Some(Duration::from_secs(udp_timeout.parse::().expect("udp-timeout"))); + } + + if let Some(udp_max_assoc) = matches.value_of("UDP_MAX_ASSOCIATIONS") { + config.udp_max_associations = Some(udp_max_assoc.parse::().expect("udp-max-associations")); + } + + if let Some(bs) = matches.value_of("INBOUND_SEND_BUFFER_SIZE") { + config.inbound_send_buffer_size = Some(bs.parse::().expect("inbound-send-buffer-size")); + } + if let Some(bs) = matches.value_of("INBOUND_RECV_BUFFER_SIZE") { + config.inbound_recv_buffer_size = Some(bs.parse::().expect("inbound-recv-buffer-size")); + } + if let Some(bs) = matches.value_of("OUTBOUND_SEND_BUFFER_SIZE") { + config.outbound_send_buffer_size = Some(bs.parse::().expect("outbound-send-buffer-size")); + } + if let Some(bs) = matches.value_of("OUTBOUND_RECV_BUFFER_SIZE") { + config.outbound_recv_buffer_size = Some(bs.parse::().expect("outbound-recv-buffer-size")); + } + + // DONE READING options + + if config.local.is_empty() { + eprintln!( + "missing `local_address`, consider specifying it by --local-addr command line option, \ or \"local_address\" and \"local_port\" in configuration file" - ); - println!("{}", matches.usage()); - return; - } + ); + println!("{}", matches.usage()); + return; + } - if config.server.is_empty() { - eprintln!( - "missing proxy servers, consider specifying it by \ + if config.server.is_empty() { + eprintln!( + "missing proxy servers, consider specifying it by \ --server-addr, --encrypt-method, --password command line option, \ or --server-url command line option, \ or configuration file, check more details in https://shadowsocks.org/en/config/quick-guide.html" - ); - println!("{}", matches.usage()); - return; - } - - if let Err(err) = config.check_integrity() { - eprintln!("config integrity check failed, {}", err); - println!("{}", matches.usage()); - return; - } - - #[cfg(unix)] - if matches.is_present("DAEMONIZE") { - use self::common::daemonize; - daemonize::daemonize(matches.value_of("DAEMONIZE_PID_PATH")); - } - - info!("shadowsocks {}", VERSION); - - #[cfg(feature = "multi-threaded")] - let mut builder = if matches.is_present("SINGLE_THREADED") { - Builder::new_current_thread() - } else { - let mut builder = Builder::new_multi_thread(); - if let Some(worker_threads) = matches.value_of("WORKER_THREADS") { - builder.worker_threads(worker_threads.parse::().expect("worker-threads")); + ); + println!("{}", matches.usage()); + return; } - builder - }; - #[cfg(not(feature = "multi-threaded"))] - let mut builder = Builder::new_current_thread(); - let runtime = builder.enable_all().build().expect("create tokio Runtime"); + if let Err(err) = config.check_integrity() { + eprintln!("config integrity check failed, {}", err); + println!("{}", matches.usage()); + return; + } + + #[cfg(unix)] + if matches.is_present("DAEMONIZE") { + use self::common::daemonize; + daemonize::daemonize(matches.value_of("DAEMONIZE_PID_PATH")); + } + + info!("shadowsocks {}", VERSION); + + #[cfg(feature = "multi-threaded")] + let mut builder = if matches.is_present("SINGLE_THREADED") { + Builder::new_current_thread() + } else { + let mut builder = Builder::new_multi_thread(); + if let Some(worker_threads) = matches.value_of("WORKER_THREADS") { + builder.worker_threads(worker_threads.parse::().expect("worker-threads")); + } + builder + }; + #[cfg(not(feature = "multi-threaded"))] + let mut builder = Builder::new_current_thread(); + + let runtime = builder.enable_all().build().expect("create tokio Runtime"); + + (config, runtime) + }; + runtime.block_on(async move { let abort_signal = monitor::create_signal_monitor(); let server = run_local(config); diff --git a/bin/ssmanager.rs b/bin/ssmanager.rs index 9e2edcd7..4d5405f3 100644 --- a/bin/ssmanager.rs +++ b/bin/ssmanager.rs @@ -34,246 +34,258 @@ mod common; const VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { - #[allow(unused_mut)] - let mut app = clap_app!(shadowsocks => - (version: VERSION) - (about: "A fast tunnel proxy that helps you bypass firewalls.") + let (config, runtime) = { + #[allow(unused_mut)] + let mut app = clap_app!(shadowsocks => + (version: VERSION) + (about: "A fast tunnel proxy that helps you bypass firewalls.") - (@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] "Server mode UDP_ONLY") - (@arg TCP_AND_UDP: -U conflicts_with[UDP_ONLY] "Server mode TCP_AND_UDP") + (@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] "Server mode UDP_ONLY") + (@arg TCP_AND_UDP: -U conflicts_with[UDP_ONLY] "Server mode TCP_AND_UDP") - (@arg CONFIG: -c --config +takes_value required_unless("MANAGER_ADDRESS") - "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html), \ - the only required fields are \"manager_address\" and \"manager_port\". \ - Servers defined will be created when process is started.") + (@arg CONFIG: -c --config +takes_value required_unless("MANAGER_ADDRESS") + "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html), \ + the only required fields are \"manager_address\" and \"manager_port\". \ + Servers defined will be created when process is started.") - (@arg BIND_ADDR: -b --("bind-addr") +takes_value {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") - (@arg SERVER_HOST: -s --("server-host") +takes_value "Host name or IP address of your remote server") + (@arg BIND_ADDR: -b --("bind-addr") +takes_value {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") + (@arg SERVER_HOST: -s --("server-host") +takes_value "Host name or IP address of your remote server") - (@arg MANAGER_ADDRESS: --("manager-address") +takes_value {validator::validate_manager_addr} "ShadowSocks Manager (ssmgr) address, could be ip:port, domain:port or /path/to/unix.sock") - (@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value possible_values(available_ciphers()) "Default encryption method") - (@arg TIMEOUT: --timeout +takes_value {validator::validate_u64} "Default timeout seconds for TCP relay") + (@arg MANAGER_ADDRESS: --("manager-address") +takes_value {validator::validate_manager_addr} "ShadowSocks Manager (ssmgr) address, could be ip:port, domain:port or /path/to/unix.sock") + (@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value possible_values(available_ciphers()) "Default encryption method") + (@arg TIMEOUT: --timeout +takes_value {validator::validate_u64} "Default timeout seconds for TCP relay") - (@arg NOFILE: -n --nofile +takes_value "Set RLIMIT_NOFILE with both soft and hard limit (only for *nix systems)") - (@arg ACL: --acl +takes_value "Path to ACL (Access Control List)") - (@arg DNS: --dns +takes_value "DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"") + (@arg ACL: --acl +takes_value "Path to ACL (Access Control List)") + (@arg DNS: --dns +takes_value "DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"") - (@arg TCP_NO_DELAY: --("tcp-no-delay") !takes_value alias("no-delay") "Set TCP_NODELAY option for socket") + (@arg TCP_NO_DELAY: --("tcp-no-delay") !takes_value alias("no-delay") "Set TCP_NODELAY option for socket") - (@arg UDP_TIMEOUT: --("udp-timeout") +takes_value {validator::validate_u64} "Timeout seconds for UDP relay") - (@arg UDP_MAX_ASSOCIATIONS: --("udp-max-associations") +takes_value {validator::validate_u64} "Maximum associations to be kept simultaneously for UDP relay") + (@arg UDP_TIMEOUT: --("udp-timeout") +takes_value {validator::validate_u64} "Timeout seconds for UDP relay") + (@arg UDP_MAX_ASSOCIATIONS: --("udp-max-associations") +takes_value {validator::validate_u64} "Maximum associations to be kept simultaneously for UDP relay") - (@arg INBOUND_SEND_BUFFER_SIZE: --("inbound-send-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_SNDBUF option") - (@arg INBOUND_RECV_BUFFER_SIZE: --("inbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_RCVBUF option") - (@arg OUTBOUND_SEND_BUFFER_SIZE: --("outbound-send-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_SNDBUF option") - (@arg OUTBOUND_RECV_BUFFER_SIZE: --("outbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_RCVBUF option") - ); - - #[cfg(feature = "logging")] - { - app = clap_app!(@app (app) - (@arg VERBOSE: -v ... "Set log level") - (@arg LOG_WITHOUT_TIME: --("log-without-time") "Log without datetime prefix") - (@arg LOG_CONFIG: --("log-config") +takes_value "log4rs configuration file") + (@arg INBOUND_SEND_BUFFER_SIZE: --("inbound-send-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_SNDBUF option") + (@arg INBOUND_RECV_BUFFER_SIZE: --("inbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_RCVBUF option") + (@arg OUTBOUND_SEND_BUFFER_SIZE: --("outbound-send-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_SNDBUF option") + (@arg OUTBOUND_RECV_BUFFER_SIZE: --("outbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_RCVBUF option") ); - } - #[cfg(unix)] - { - app = clap_app!(@app (app) - (@arg DAEMONIZE: -d --("daemonize") "Daemonize") - (@arg DAEMONIZE_PID_PATH: --("daemonize-pid") +takes_value "File path to store daemonized process's PID") - ); - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - { - app = clap_app!(@app (app) - (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set SO_BINDTODEVICE option for outbound socket") - (@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket") - ); - } - - #[cfg(any(target_os = "macos", target_os = "ios"))] - { - app = clap_app!(@app (app) - (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set IP_BOUND_IF option for outbound socket") - ); - } - - #[cfg(feature = "multi-threaded")] - { - app = clap_app!(@app (app) - (@arg SINGLE_THREADED: --("single-threaded") "Run the program all in one thread") - (@arg WORKER_THREADS: --("worker-threads") +takes_value {validator::validate_usize} "Sets the number of worker threads the `Runtime` will use") - ); - } - - let matches = app - .arg( - Arg::with_name("IPV6_FIRST") - .short("6") - .help("Resolve hostname to IPv6 address first"), - ) - .get_matches(); - - #[cfg(feature = "logging")] - match matches.value_of("LOG_CONFIG") { - Some(path) => { - logging::init_with_file(path); + #[cfg(feature = "logging")] + { + app = clap_app!(@app (app) + (@arg VERBOSE: -v ... "Set log level") + (@arg LOG_WITHOUT_TIME: --("log-without-time") "Log without datetime prefix") + (@arg LOG_CONFIG: --("log-config") +takes_value "log4rs configuration file") + ); } - None => { - logging::init_with_config("ssmanager", &matches); - } - } - let mut config = match matches.value_of("CONFIG") { - Some(cpath) => match Config::load_from_file(cpath, ConfigType::Manager) { - Ok(cfg) => cfg, - Err(err) => { - panic!("loading config \"{}\", {}", cpath, err); + #[cfg(unix)] + { + app = clap_app!(@app (app) + (@arg DAEMONIZE: -d --("daemonize") "Daemonize") + (@arg DAEMONIZE_PID_PATH: --("daemonize-pid") +takes_value "File path to store daemonized process's PID") + ); + } + + #[cfg(all(unix, not(target_os = "android")))] + { + app = clap_app!(@app (app) + (@arg NOFILE: -n --nofile +takes_value "Set RLIMIT_NOFILE with both soft and hard limit") + ); + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + { + app = clap_app!(@app (app) + (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set SO_BINDTODEVICE option for outbound socket") + (@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket") + ); + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + { + app = clap_app!(@app (app) + (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set IP_BOUND_IF option for outbound socket") + ); + } + + #[cfg(feature = "multi-threaded")] + { + app = clap_app!(@app (app) + (@arg SINGLE_THREADED: --("single-threaded") "Run the program all in one thread") + (@arg WORKER_THREADS: --("worker-threads") +takes_value {validator::validate_usize} "Sets the number of worker threads the `Runtime` will use") + ); + } + + let matches = app + .arg( + Arg::with_name("IPV6_FIRST") + .short("6") + .help("Resolve hostname to IPv6 address first"), + ) + .get_matches(); + + #[cfg(feature = "logging")] + match matches.value_of("LOG_CONFIG") { + Some(path) => { + logging::init_with_file(path); } - }, - None => Config::new(ConfigType::Manager), - }; - - if let Some(bind_addr) = matches.value_of("BIND_ADDR") { - let bind_addr = bind_addr.parse::().expect("bind-addr"); - config.local_addr = Some(bind_addr); - } - - if matches.is_present("TCP_NO_DELAY") { - config.no_delay = true; - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") { - config.outbound_fwmark = Some(mark.parse::().expect("an unsigned integer for `outbound-fwmark`")); - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") { - config.outbound_bind_interface = Some(From::from(iface.to_owned())); - } - - if let Some(m) = matches.value_of("MANAGER_ADDRESS") { - if let Some(ref mut manager_config) = config.manager { - manager_config.addr = m.parse::().expect("manager-address"); - } else { - config.manager = Some(ManagerConfig::new(m.parse::().expect("manager-address"))); - } - } - - if let Some(ref mut manager_config) = config.manager { - if let Some(m) = matches.value_of("ENCRYPT_METHOD") { - manager_config.method = Some(m.parse::().expect("encrypt-method")); - } - - if let Some(t) = matches.value_of("TIMEOUT") { - manager_config.timeout = Some(Duration::from_secs(t.parse::().expect("timeout"))); - } - - if let Some(sh) = matches.value_of("SERVER_HOST") { - manager_config.server_host = sh.parse::().unwrap(); - } - } - - // Overrides - if matches.is_present("UDP_ONLY") { - if let Some(ref mut m) = config.manager { - m.mode = m.mode.merge(Mode::UdpOnly); - } - } - - if matches.is_present("TCP_AND_UDP") { - if let Some(ref mut m) = config.manager { - m.mode = Mode::TcpAndUdp; - } - } - - if let Some(nofile) = matches.value_of("NOFILE") { - config.nofile = Some(nofile.parse::().expect("an unsigned integer for `nofile`")); - } - - if let Some(acl_file) = matches.value_of("ACL") { - let acl = match AccessControl::load_from_file(acl_file) { - Ok(acl) => acl, - Err(err) => { - panic!("loading ACL \"{}\", {}", acl_file, err); + None => { + logging::init_with_config("ssmanager", &matches); } + } + + let mut config = match matches.value_of("CONFIG") { + Some(cpath) => match Config::load_from_file(cpath, ConfigType::Manager) { + Ok(cfg) => cfg, + Err(err) => { + panic!("loading config \"{}\", {}", cpath, err); + } + }, + None => Config::new(ConfigType::Manager), }; - config.acl = Some(acl); - } - if let Some(dns) = matches.value_of("DNS") { - config.set_dns_formatted(dns).expect("dns"); - } - - if matches.is_present("IPV6_FIRST") { - config.ipv6_first = true; - } - - if let Some(udp_timeout) = matches.value_of("UDP_TIMEOUT") { - config.udp_timeout = Some(Duration::from_secs(udp_timeout.parse::().expect("udp-timeout"))); - } - - if let Some(udp_max_assoc) = matches.value_of("UDP_MAX_ASSOCIATIONS") { - config.udp_max_associations = Some(udp_max_assoc.parse::().expect("udp-max-associations")); - } - - if let Some(bs) = matches.value_of("INBOUND_SEND_BUFFER_SIZE") { - config.inbound_send_buffer_size = Some(bs.parse::().expect("inbound-send-buffer-size")); - } - if let Some(bs) = matches.value_of("INBOUND_RECV_BUFFER_SIZE") { - config.inbound_recv_buffer_size = Some(bs.parse::().expect("inbound-recv-buffer-size")); - } - if let Some(bs) = matches.value_of("OUTBOUND_SEND_BUFFER_SIZE") { - config.outbound_send_buffer_size = Some(bs.parse::().expect("outbound-send-buffer-size")); - } - if let Some(bs) = matches.value_of("OUTBOUND_RECV_BUFFER_SIZE") { - config.outbound_recv_buffer_size = Some(bs.parse::().expect("outbound-recv-buffer-size")); - } - - // DONE reading options - - if config.manager.is_none() { - eprintln!( - "missing `manager_address`, consider specifying it by --manager-address command line option, \ - or \"manager_address\" and \"manager_port\" keys in configuration file" - ); - println!("{}", matches.usage()); - return; - } - - if let Err(err) = config.check_integrity() { - eprintln!("config integrity check failed, {}", err); - println!("{}", matches.usage()); - return; - } - - #[cfg(unix)] - if matches.is_present("DAEMONIZE") { - use self::common::daemonize; - daemonize::daemonize(matches.value_of("DAEMONIZE_PID_PATH")); - } - - info!("shadowsocks {}", VERSION); - - #[cfg(feature = "multi-threaded")] - let mut builder = if matches.is_present("SINGLE_THREADED") { - Builder::new_current_thread() - } else { - let mut builder = Builder::new_multi_thread(); - if let Some(worker_threads) = matches.value_of("WORKER_THREADS") { - builder.worker_threads(worker_threads.parse::().expect("worker-threads")); + if let Some(bind_addr) = matches.value_of("BIND_ADDR") { + let bind_addr = bind_addr.parse::().expect("bind-addr"); + config.local_addr = Some(bind_addr); } - builder - }; - #[cfg(not(feature = "multi-threaded"))] - let mut builder = Builder::new_current_thread(); - let runtime = builder.enable_all().build().expect("create tokio Runtime"); + if matches.is_present("TCP_NO_DELAY") { + config.no_delay = true; + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") { + config.outbound_fwmark = Some(mark.parse::().expect("an unsigned integer for `outbound-fwmark`")); + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") { + config.outbound_bind_interface = Some(From::from(iface.to_owned())); + } + + if let Some(m) = matches.value_of("MANAGER_ADDRESS") { + if let Some(ref mut manager_config) = config.manager { + manager_config.addr = m.parse::().expect("manager-address"); + } else { + config.manager = Some(ManagerConfig::new(m.parse::().expect("manager-address"))); + } + } + + if let Some(ref mut manager_config) = config.manager { + if let Some(m) = matches.value_of("ENCRYPT_METHOD") { + manager_config.method = Some(m.parse::().expect("encrypt-method")); + } + + if let Some(t) = matches.value_of("TIMEOUT") { + manager_config.timeout = Some(Duration::from_secs(t.parse::().expect("timeout"))); + } + + if let Some(sh) = matches.value_of("SERVER_HOST") { + manager_config.server_host = sh.parse::().unwrap(); + } + } + + // Overrides + if matches.is_present("UDP_ONLY") { + if let Some(ref mut m) = config.manager { + m.mode = m.mode.merge(Mode::UdpOnly); + } + } + + if matches.is_present("TCP_AND_UDP") { + if let Some(ref mut m) = config.manager { + m.mode = Mode::TcpAndUdp; + } + } + + #[cfg(all(unix, not(target_os = "android")))] + if let Some(nofile) = matches.value_of("NOFILE") { + config.nofile = Some(nofile.parse::().expect("an unsigned integer for `nofile`")); + } + + if let Some(acl_file) = matches.value_of("ACL") { + let acl = match AccessControl::load_from_file(acl_file) { + Ok(acl) => acl, + Err(err) => { + panic!("loading ACL \"{}\", {}", acl_file, err); + } + }; + config.acl = Some(acl); + } + + if let Some(dns) = matches.value_of("DNS") { + config.set_dns_formatted(dns).expect("dns"); + } + + if matches.is_present("IPV6_FIRST") { + config.ipv6_first = true; + } + + if let Some(udp_timeout) = matches.value_of("UDP_TIMEOUT") { + config.udp_timeout = Some(Duration::from_secs(udp_timeout.parse::().expect("udp-timeout"))); + } + + if let Some(udp_max_assoc) = matches.value_of("UDP_MAX_ASSOCIATIONS") { + config.udp_max_associations = Some(udp_max_assoc.parse::().expect("udp-max-associations")); + } + + if let Some(bs) = matches.value_of("INBOUND_SEND_BUFFER_SIZE") { + config.inbound_send_buffer_size = Some(bs.parse::().expect("inbound-send-buffer-size")); + } + if let Some(bs) = matches.value_of("INBOUND_RECV_BUFFER_SIZE") { + config.inbound_recv_buffer_size = Some(bs.parse::().expect("inbound-recv-buffer-size")); + } + if let Some(bs) = matches.value_of("OUTBOUND_SEND_BUFFER_SIZE") { + config.outbound_send_buffer_size = Some(bs.parse::().expect("outbound-send-buffer-size")); + } + if let Some(bs) = matches.value_of("OUTBOUND_RECV_BUFFER_SIZE") { + config.outbound_recv_buffer_size = Some(bs.parse::().expect("outbound-recv-buffer-size")); + } + + // DONE reading options + + if config.manager.is_none() { + eprintln!( + "missing `manager_address`, consider specifying it by --manager-address command line option, \ + or \"manager_address\" and \"manager_port\" keys in configuration file" + ); + println!("{}", matches.usage()); + return; + } + + if let Err(err) = config.check_integrity() { + eprintln!("config integrity check failed, {}", err); + println!("{}", matches.usage()); + return; + } + + #[cfg(unix)] + if matches.is_present("DAEMONIZE") { + use self::common::daemonize; + daemonize::daemonize(matches.value_of("DAEMONIZE_PID_PATH")); + } + + info!("shadowsocks {}", VERSION); + + #[cfg(feature = "multi-threaded")] + let mut builder = if matches.is_present("SINGLE_THREADED") { + Builder::new_current_thread() + } else { + let mut builder = Builder::new_multi_thread(); + if let Some(worker_threads) = matches.value_of("WORKER_THREADS") { + builder.worker_threads(worker_threads.parse::().expect("worker-threads")); + } + builder + }; + #[cfg(not(feature = "multi-threaded"))] + let mut builder = Builder::new_current_thread(); + + let runtime = builder.enable_all().build().expect("create tokio Runtime"); + + (config, runtime) + }; + runtime.block_on(async move { let abort_signal = monitor::create_signal_monitor(); let server = run_manager(config); diff --git a/bin/ssserver.rs b/bin/ssserver.rs index ce145781..b3104b0a 100644 --- a/bin/ssserver.rs +++ b/bin/ssserver.rs @@ -35,255 +35,267 @@ mod common; const VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { - #[allow(unused_mut)] - let mut app = clap_app!(shadowsocks => - (version: VERSION) - (about: "A fast tunnel proxy that helps you bypass firewalls.") + let (config, runtime) = { + #[allow(unused_mut)] + let mut app = clap_app!(shadowsocks => + (version: VERSION) + (about: "A fast tunnel proxy that helps you bypass firewalls.") - (@arg CONFIG: -c --config +takes_value required_unless("SERVER_ADDR") "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") + (@arg CONFIG: -c --config +takes_value required_unless("SERVER_ADDR") "Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html)") - (@arg BIND_ADDR: -b --("bind-addr") +takes_value {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") + (@arg BIND_ADDR: -b --("bind-addr") +takes_value {validator::validate_ip_addr} "Bind address, outbound socket will bind this address") - (@arg SERVER_ADDR: -s --("server-addr") +takes_value {validator::validate_server_addr} requires[PASSWORD ENCRYPT_METHOD] "Server address") - (@arg PASSWORD: -k --password +takes_value requires[SERVER_ADDR] "Server's password") - (@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value requires[SERVER_ADDR] possible_values(available_ciphers()) "Server's encryption method") - (@arg TIMEOUT: --timeout +takes_value {validator::validate_u64} requires[SERVER_ADDR] "Server's timeout seconds for TCP relay") - (@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] requires[SERVER_ADDR] "Server mode UDP_ONLY") - (@arg TCP_AND_UDP: -U requires[SERVER_ADDR] "Server mode TCP_AND_UDP") + (@arg SERVER_ADDR: -s --("server-addr") +takes_value {validator::validate_server_addr} requires[PASSWORD ENCRYPT_METHOD] "Server address") + (@arg PASSWORD: -k --password +takes_value requires[SERVER_ADDR] "Server's password") + (@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value requires[SERVER_ADDR] possible_values(available_ciphers()) "Server's encryption method") + (@arg TIMEOUT: --timeout +takes_value {validator::validate_u64} requires[SERVER_ADDR] "Server's timeout seconds for TCP relay") + (@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] requires[SERVER_ADDR] "Server mode UDP_ONLY") + (@arg TCP_AND_UDP: -U requires[SERVER_ADDR] "Server mode TCP_AND_UDP") - (@arg PLUGIN: --plugin +takes_value requires[SERVER_ADDR] "SIP003 (https://shadowsocks.org/en/spec/Plugin.html) plugin") - (@arg PLUGIN_OPT: --("plugin-opts") +takes_value requires[PLUGIN] "Set SIP003 plugin options") + (@arg PLUGIN: --plugin +takes_value requires[SERVER_ADDR] "SIP003 (https://shadowsocks.org/en/spec/Plugin.html) plugin") + (@arg PLUGIN_OPT: --("plugin-opts") +takes_value requires[PLUGIN] "Set SIP003 plugin options") - (@arg MANAGER_ADDRESS: --("manager-address") +takes_value "ShadowSocks Manager (ssmgr) address, could be \"IP:Port\", \"Domain:Port\" or \"/path/to/unix.sock\"") + (@arg MANAGER_ADDRESS: --("manager-address") +takes_value "ShadowSocks Manager (ssmgr) address, could be \"IP:Port\", \"Domain:Port\" or \"/path/to/unix.sock\"") - (@arg NOFILE: -n --nofile +takes_value "Set RLIMIT_NOFILE with both soft and hard limit (only for *nix systems)") - (@arg ACL: --acl +takes_value "Path to ACL (Access Control List)") - (@arg DNS: --dns +takes_value "DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"") + (@arg ACL: --acl +takes_value "Path to ACL (Access Control List)") + (@arg DNS: --dns +takes_value "DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"") - (@arg TCP_NO_DELAY: --("tcp-no-delay") !takes_value alias("no-delay") "Set TCP_NODELAY option for socket") + (@arg TCP_NO_DELAY: --("tcp-no-delay") !takes_value alias("no-delay") "Set TCP_NODELAY option for socket") - (@arg UDP_TIMEOUT: --("udp-timeout") +takes_value {validator::validate_u64} "Timeout seconds for UDP relay") - (@arg UDP_MAX_ASSOCIATIONS: --("udp-max-associations") +takes_value {validator::validate_u64} "Maximum associations to be kept simultaneously for UDP relay") + (@arg UDP_TIMEOUT: --("udp-timeout") +takes_value {validator::validate_u64} "Timeout seconds for UDP relay") + (@arg UDP_MAX_ASSOCIATIONS: --("udp-max-associations") +takes_value {validator::validate_u64} "Maximum associations to be kept simultaneously for UDP relay") - (@arg INBOUND_SEND_BUFFER_SIZE: --("inbound-send-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_SNDBUF option") - (@arg INBOUND_RECV_BUFFER_SIZE: --("inbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_RCVBUF option") - (@arg OUTBOUND_SEND_BUFFER_SIZE: --("outbound-send-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_SNDBUF option") - (@arg OUTBOUND_RECV_BUFFER_SIZE: --("outbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_RCVBUF option") - ); - - #[cfg(feature = "logging")] - { - app = clap_app!(@app (app) - (@arg VERBOSE: -v ... "Set log level") - (@arg LOG_WITHOUT_TIME: --("log-without-time") "Log without datetime prefix") - (@arg LOG_CONFIG: --("log-config") +takes_value "log4rs configuration file") + (@arg INBOUND_SEND_BUFFER_SIZE: --("inbound-send-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_SNDBUF option") + (@arg INBOUND_RECV_BUFFER_SIZE: --("inbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set inbound sockets' SO_RCVBUF option") + (@arg OUTBOUND_SEND_BUFFER_SIZE: --("outbound-send-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_SNDBUF option") + (@arg OUTBOUND_RECV_BUFFER_SIZE: --("outbound-recv-buffer-size") +takes_value {validator::validate_u32} "Set outbound sockets' SO_RCVBUF option") ); - } - #[cfg(unix)] - { - app = clap_app!(@app (app) - (@arg DAEMONIZE: -d --("daemonize") "Daemonize") - (@arg DAEMONIZE_PID_PATH: --("daemonize-pid") +takes_value "File path to store daemonized process's PID") - ); - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - { - app = clap_app!(@app (app) - (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set SO_BINDTODEVICE option for outbound socket") - (@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket") - ); - } - - #[cfg(any(target_os = "macos", target_os = "ios"))] - { - app = clap_app!(@app (app) - (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set IP_BOUND_IF option for outbound socket") - ); - } - - #[cfg(feature = "multi-threaded")] - { - app = clap_app!(@app (app) - (@arg SINGLE_THREADED: --("single-threaded") "Run the program all in one thread") - (@arg WORKER_THREADS: --("worker-threads") +takes_value {validator::validate_usize} "Sets the number of worker threads the `Runtime` will use") - ); - } - - let matches = app - .arg( - Arg::with_name("IPV6_FIRST") - .short("6") - .help("Resolve hostname to IPv6 address first"), - ) - .get_matches(); - - #[cfg(feature = "logging")] - match matches.value_of("LOG_CONFIG") { - Some(path) => { - logging::init_with_file(path); + #[cfg(feature = "logging")] + { + app = clap_app!(@app (app) + (@arg VERBOSE: -v ... "Set log level") + (@arg LOG_WITHOUT_TIME: --("log-without-time") "Log without datetime prefix") + (@arg LOG_CONFIG: --("log-config") +takes_value "log4rs configuration file") + ); } - None => { - logging::init_with_config("ssserver", &matches); - } - } - let mut config = match matches.value_of("CONFIG") { - Some(cpath) => match Config::load_from_file(cpath, ConfigType::Server) { - Ok(cfg) => cfg, - Err(err) => { - panic!("loading config \"{}\", {}", cpath, err); + #[cfg(unix)] + { + app = clap_app!(@app (app) + (@arg DAEMONIZE: -d --("daemonize") "Daemonize") + (@arg DAEMONIZE_PID_PATH: --("daemonize-pid") +takes_value "File path to store daemonized process's PID") + ); + } + + #[cfg(all(unix, not(target_os = "android")))] + { + app = clap_app!(@app (app) + (@arg NOFILE: -n --nofile +takes_value "Set RLIMIT_NOFILE with both soft and hard limit") + ); + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + { + app = clap_app!(@app (app) + (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set SO_BINDTODEVICE option for outbound socket") + (@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket") + ); + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + { + app = clap_app!(@app (app) + (@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set IP_BOUND_IF option for outbound socket") + ); + } + + #[cfg(feature = "multi-threaded")] + { + app = clap_app!(@app (app) + (@arg SINGLE_THREADED: --("single-threaded") "Run the program all in one thread") + (@arg WORKER_THREADS: --("worker-threads") +takes_value {validator::validate_usize} "Sets the number of worker threads the `Runtime` will use") + ); + } + + let matches = app + .arg( + Arg::with_name("IPV6_FIRST") + .short("6") + .help("Resolve hostname to IPv6 address first"), + ) + .get_matches(); + + #[cfg(feature = "logging")] + match matches.value_of("LOG_CONFIG") { + Some(path) => { + logging::init_with_file(path); } - }, - None => Config::new(ConfigType::Server), - }; - - if let Some(svr_addr) = matches.value_of("SERVER_ADDR") { - let password = matches.value_of("PASSWORD").expect("password"); - let method = matches - .value_of("ENCRYPT_METHOD") - .expect("encrypt-method") - .parse::() - .expect("encryption method"); - let svr_addr = svr_addr.parse::().expect("server-addr"); - let timeout = matches - .value_of("TIMEOUT") - .map(|t| t.parse::().expect("timeout")) - .map(Duration::from_secs); - - let mut sc = ServerConfig::new(svr_addr, password.to_owned(), method); - if let Some(timeout) = timeout { - sc.set_timeout(timeout); - } - - if let Some(p) = matches.value_of("PLUGIN") { - let plugin = PluginConfig { - plugin: p.to_owned(), - plugin_opts: matches.value_of("PLUGIN_OPT").map(ToOwned::to_owned), - plugin_args: Vec::new(), - }; - - sc.set_plugin(plugin); - } - - if matches.is_present("UDP_ONLY") { - sc.set_mode(sc.mode().merge(Mode::UdpOnly)); - } - - if matches.is_present("TCP_AND_UDP") { - sc.set_mode(Mode::TcpAndUdp); - } - - config.server.push(sc); - } - - if let Some(bind_addr) = matches.value_of("BIND_ADDR") { - let bind_addr = bind_addr.parse::().expect("bind-addr"); - config.local_addr = Some(bind_addr); - } - - if matches.is_present("TCP_NO_DELAY") { - config.no_delay = true; - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") { - config.outbound_fwmark = Some(mark.parse::().expect("an unsigned integer for `outbound-fwmark`")); - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") { - config.outbound_bind_interface = Some(From::from(iface.to_owned())); - } - - if let Some(m) = matches.value_of("MANAGER_ADDRESS") { - config.manager = Some(ManagerConfig::new(m.parse::().expect("manager address"))); - } - - if let Some(nofile) = matches.value_of("NOFILE") { - config.nofile = Some(nofile.parse::().expect("an unsigned integer for `nofile`")); - } - - if let Some(acl_file) = matches.value_of("ACL") { - let acl = match AccessControl::load_from_file(acl_file) { - Ok(acl) => acl, - Err(err) => { - panic!("loading ACL \"{}\", {}", acl_file, err); + None => { + logging::init_with_config("ssserver", &matches); } + } + + let mut config = match matches.value_of("CONFIG") { + Some(cpath) => match Config::load_from_file(cpath, ConfigType::Server) { + Ok(cfg) => cfg, + Err(err) => { + panic!("loading config \"{}\", {}", cpath, err); + } + }, + None => Config::new(ConfigType::Server), }; - config.acl = Some(acl); - } - if let Some(dns) = matches.value_of("DNS") { - config.set_dns_formatted(dns).expect("dns"); - } + if let Some(svr_addr) = matches.value_of("SERVER_ADDR") { + let password = matches.value_of("PASSWORD").expect("password"); + let method = matches + .value_of("ENCRYPT_METHOD") + .expect("encrypt-method") + .parse::() + .expect("encryption method"); + let svr_addr = svr_addr.parse::().expect("server-addr"); + let timeout = matches + .value_of("TIMEOUT") + .map(|t| t.parse::().expect("timeout")) + .map(Duration::from_secs); - if matches.is_present("IPV6_FIRST") { - config.ipv6_first = true; - } + let mut sc = ServerConfig::new(svr_addr, password.to_owned(), method); + if let Some(timeout) = timeout { + sc.set_timeout(timeout); + } - if let Some(udp_timeout) = matches.value_of("UDP_TIMEOUT") { - config.udp_timeout = Some(Duration::from_secs(udp_timeout.parse::().expect("udp-timeout"))); - } + if let Some(p) = matches.value_of("PLUGIN") { + let plugin = PluginConfig { + plugin: p.to_owned(), + plugin_opts: matches.value_of("PLUGIN_OPT").map(ToOwned::to_owned), + plugin_args: Vec::new(), + }; - if let Some(udp_max_assoc) = matches.value_of("UDP_MAX_ASSOCIATIONS") { - config.udp_max_associations = Some(udp_max_assoc.parse::().expect("udp-max-associations")); - } + sc.set_plugin(plugin); + } - if let Some(bs) = matches.value_of("INBOUND_SEND_BUFFER_SIZE") { - config.inbound_send_buffer_size = Some(bs.parse::().expect("inbound-send-buffer-size")); - } - if let Some(bs) = matches.value_of("INBOUND_RECV_BUFFER_SIZE") { - config.inbound_recv_buffer_size = Some(bs.parse::().expect("inbound-recv-buffer-size")); - } - if let Some(bs) = matches.value_of("OUTBOUND_SEND_BUFFER_SIZE") { - config.outbound_send_buffer_size = Some(bs.parse::().expect("outbound-send-buffer-size")); - } - if let Some(bs) = matches.value_of("OUTBOUND_RECV_BUFFER_SIZE") { - config.outbound_recv_buffer_size = Some(bs.parse::().expect("outbound-recv-buffer-size")); - } + if matches.is_present("UDP_ONLY") { + sc.set_mode(sc.mode().merge(Mode::UdpOnly)); + } - // DONE READING options + if matches.is_present("TCP_AND_UDP") { + sc.set_mode(Mode::TcpAndUdp); + } - if config.server.is_empty() { - eprintln!( - "missing proxy servers, consider specifying it by \ + config.server.push(sc); + } + + if let Some(bind_addr) = matches.value_of("BIND_ADDR") { + let bind_addr = bind_addr.parse::().expect("bind-addr"); + config.local_addr = Some(bind_addr); + } + + if matches.is_present("TCP_NO_DELAY") { + config.no_delay = true; + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") { + config.outbound_fwmark = Some(mark.parse::().expect("an unsigned integer for `outbound-fwmark`")); + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") { + config.outbound_bind_interface = Some(From::from(iface.to_owned())); + } + + if let Some(m) = matches.value_of("MANAGER_ADDRESS") { + config.manager = Some(ManagerConfig::new(m.parse::().expect("manager address"))); + } + + #[cfg(all(unix, not(target_os = "android")))] + if let Some(nofile) = matches.value_of("NOFILE") { + config.nofile = Some(nofile.parse::().expect("an unsigned integer for `nofile`")); + } + + if let Some(acl_file) = matches.value_of("ACL") { + let acl = match AccessControl::load_from_file(acl_file) { + Ok(acl) => acl, + Err(err) => { + panic!("loading ACL \"{}\", {}", acl_file, err); + } + }; + config.acl = Some(acl); + } + + if let Some(dns) = matches.value_of("DNS") { + config.set_dns_formatted(dns).expect("dns"); + } + + if matches.is_present("IPV6_FIRST") { + config.ipv6_first = true; + } + + if let Some(udp_timeout) = matches.value_of("UDP_TIMEOUT") { + config.udp_timeout = Some(Duration::from_secs(udp_timeout.parse::().expect("udp-timeout"))); + } + + if let Some(udp_max_assoc) = matches.value_of("UDP_MAX_ASSOCIATIONS") { + config.udp_max_associations = Some(udp_max_assoc.parse::().expect("udp-max-associations")); + } + + if let Some(bs) = matches.value_of("INBOUND_SEND_BUFFER_SIZE") { + config.inbound_send_buffer_size = Some(bs.parse::().expect("inbound-send-buffer-size")); + } + if let Some(bs) = matches.value_of("INBOUND_RECV_BUFFER_SIZE") { + config.inbound_recv_buffer_size = Some(bs.parse::().expect("inbound-recv-buffer-size")); + } + if let Some(bs) = matches.value_of("OUTBOUND_SEND_BUFFER_SIZE") { + config.outbound_send_buffer_size = Some(bs.parse::().expect("outbound-send-buffer-size")); + } + if let Some(bs) = matches.value_of("OUTBOUND_RECV_BUFFER_SIZE") { + config.outbound_recv_buffer_size = Some(bs.parse::().expect("outbound-recv-buffer-size")); + } + + // DONE READING options + + if config.server.is_empty() { + eprintln!( + "missing proxy servers, consider specifying it by \ --server-addr, --encrypt-method, --password command line option, \ or configuration file, check more details in https://shadowsocks.org/en/config/quick-guide.html" - ); - println!("{}", matches.usage()); - return; - } - - if let Err(err) = config.check_integrity() { - eprintln!("config integrity check failed, {}", err); - println!("{}", matches.usage()); - return; - } - - #[cfg(unix)] - if matches.is_present("DAEMONIZE") { - use self::common::daemonize; - daemonize::daemonize(matches.value_of("DAEMONIZE_PID_PATH")); - } - - info!("shadowsocks {}", VERSION); - - #[cfg(feature = "multi-threaded")] - let mut builder = if matches.is_present("SINGLE_THREADED") { - Builder::new_current_thread() - } else { - let mut builder = Builder::new_multi_thread(); - if let Some(worker_threads) = matches.value_of("WORKER_THREADS") { - builder.worker_threads(worker_threads.parse::().expect("worker-threads")); + ); + println!("{}", matches.usage()); + return; } - builder - }; - #[cfg(not(feature = "multi-threaded"))] - let mut builder = Builder::new_current_thread(); - let runtime = builder.enable_all().build().expect("create tokio Runtime"); + if let Err(err) = config.check_integrity() { + eprintln!("config integrity check failed, {}", err); + println!("{}", matches.usage()); + return; + } + + #[cfg(unix)] + if matches.is_present("DAEMONIZE") { + use self::common::daemonize; + daemonize::daemonize(matches.value_of("DAEMONIZE_PID_PATH")); + } + + info!("shadowsocks {}", VERSION); + + #[cfg(feature = "multi-threaded")] + let mut builder = if matches.is_present("SINGLE_THREADED") { + Builder::new_current_thread() + } else { + let mut builder = Builder::new_multi_thread(); + if let Some(worker_threads) = matches.value_of("WORKER_THREADS") { + builder.worker_threads(worker_threads.parse::().expect("worker-threads")); + } + builder + }; + #[cfg(not(feature = "multi-threaded"))] + let mut builder = Builder::new_current_thread(); + + let runtime = builder.enable_all().build().expect("create tokio Runtime"); + + (config, runtime) + }; + runtime.block_on(async move { let abort_signal = monitor::create_signal_monitor(); let server = run_server(config); diff --git a/crates/shadowsocks-service/src/config.rs b/crates/shadowsocks-service/src/config.rs index f55ec906..cc978f69 100644 --- a/crates/shadowsocks-service/src/config.rs +++ b/crates/shadowsocks-service/src/config.rs @@ -123,6 +123,7 @@ struct SSConfig { mode: Option, #[serde(skip_serializing_if = "Option::is_none")] no_delay: Option, + #[cfg(all(unix, not(target_os = "android")))] #[serde(skip_serializing_if = "Option::is_none")] nofile: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -718,6 +719,7 @@ pub struct Config { /// Set `TCP_NODELAY` socket option pub no_delay: bool, /// `RLIMIT_NOFILE` option for *nix systems + #[cfg(all(unix, not(target_os = "android")))] pub nofile: Option, /// Set `SO_MARK` socket option for outbound sockets @@ -829,6 +831,7 @@ impl Config { ipv6_first: false, no_delay: false, + #[cfg(all(unix, not(target_os = "android")))] nofile: None, #[cfg(any(target_os = "linux", target_os = "android"))] @@ -1282,7 +1285,10 @@ impl Config { nconfig.udp_max_associations = config.udp_max_associations; // RLIMIT_NOFILE - nconfig.nofile = config.nofile; + #[cfg(all(unix, not(target_os = "android")))] + { + nconfig.nofile = config.nofile; + } // Uses IPv6 first if let Some(f) = config.ipv6_first { @@ -1774,7 +1780,10 @@ impl fmt::Display for Config { jconf.udp_max_associations = self.udp_max_associations; - jconf.nofile = self.nofile; + #[cfg(all(unix, not(target_os = "android")))] + { + jconf.nofile = self.nofile; + } if self.ipv6_first { jconf.ipv6_first = Some(self.ipv6_first); diff --git a/crates/shadowsocks-service/src/local/mod.rs b/crates/shadowsocks-service/src/local/mod.rs index dcb595ab..b40f2e27 100644 --- a/crates/shadowsocks-service/src/local/mod.rs +++ b/crates/shadowsocks-service/src/local/mod.rs @@ -58,7 +58,7 @@ pub async fn run(mut config: Config) -> io::Result<()> { } } - #[cfg(unix)] + #[cfg(all(unix, not(target_os = "android")))] if let Some(nofile) = config.nofile { use crate::sys::set_nofile; if let Err(err) = set_nofile(nofile) { diff --git a/crates/shadowsocks-service/src/manager/mod.rs b/crates/shadowsocks-service/src/manager/mod.rs index cc6c3b4e..29fc3e9f 100644 --- a/crates/shadowsocks-service/src/manager/mod.rs +++ b/crates/shadowsocks-service/src/manager/mod.rs @@ -22,7 +22,7 @@ pub async fn run(config: Config) -> io::Result<()> { trace!("{:?}", config); - #[cfg(unix)] + #[cfg(all(unix, not(target_os = "android")))] if let Some(nofile) = config.nofile { use crate::sys::set_nofile; if let Err(err) = set_nofile(nofile) { diff --git a/crates/shadowsocks-service/src/server/mod.rs b/crates/shadowsocks-service/src/server/mod.rs index 652ecad5..3b06d335 100644 --- a/crates/shadowsocks-service/src/server/mod.rs +++ b/crates/shadowsocks-service/src/server/mod.rs @@ -34,7 +34,7 @@ pub async fn run(config: Config) -> io::Result<()> { } } - #[cfg(unix)] + #[cfg(all(unix, not(target_os = "android")))] if let Some(nofile) = config.nofile { use crate::sys::set_nofile; if let Err(err) = set_nofile(nofile) {