diff --git a/crates/shadowsocks-service/src/config.rs b/crates/shadowsocks-service/src/config.rs index 27f86aa8dda8..849cc3972cb1 100644 --- a/crates/shadowsocks-service/src/config.rs +++ b/crates/shadowsocks-service/src/config.rs @@ -66,14 +66,7 @@ use serde::{Deserialize, Serialize}; use shadowsocks::relay::socks5::Address; use shadowsocks::{ config::{ - ManagerAddr, - Mode, - ReplayAttackPolicy, - ServerAddr, - ServerConfig, - ServerUser, - ServerUserManager, - ServerWeight, + ManagerAddr, Mode, ReplayAttackPolicy, ServerAddr, ServerConfig, ServerUser, ServerUserManager, ServerWeight, }, crypto::CipherKind, plugin::PluginConfig, @@ -148,6 +141,8 @@ struct SSConfig { plugin_opts: Option, #[serde(skip_serializing_if = "Option::is_none")] plugin_args: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + plugin_mode: Option, #[serde(skip_serializing_if = "Option::is_none")] timeout: Option, @@ -326,6 +321,8 @@ struct SSServerExtConfig { plugin_opts: Option, #[serde(skip_serializing_if = "Option::is_none")] plugin_args: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + plugin_mode: Option, #[serde(skip_serializing_if = "Option::is_none")] timeout: Option, @@ -1646,6 +1643,20 @@ impl Config { plugin: p.clone(), plugin_opts: config.plugin_opts.clone(), plugin_args: config.plugin_args.clone().unwrap_or_default(), + plugin_mode: match config.plugin_mode { + None => Mode::TcpOnly, + Some(ref mode) => match mode.parse::() { + Ok(m) => m, + Err(..) => { + let e = Error::new( + ErrorKind::Malformed, + "malformed `plugin_mode`, must be one of `tcp_only`, `udp_only` and `tcp_and_udp`", + None, + ); + return Err(e); + } + }, + }, }; nsvr.set_plugin(plugin); } @@ -1773,6 +1784,20 @@ impl Config { plugin: p, plugin_opts: svr.plugin_opts, plugin_args: svr.plugin_args.unwrap_or_default(), + plugin_mode: match svr.plugin_mode { + None => Mode::TcpOnly, + Some(ref mode) => match mode.parse::() { + Ok(m) => m, + Err(..) => { + let e = Error::new( + ErrorKind::Malformed, + "malformed `plugin_mode`, must be one of `tcp_only`, `udp_only` and `tcp_and_udp`", + None, + ); + return Err(e); + } + }, + }, }; nsvr.set_plugin(plugin); } @@ -1889,6 +1914,20 @@ impl Config { plugin: p, plugin_opts: config.plugin_opts, plugin_args: config.plugin_args.unwrap_or_default(), + plugin_mode: match config.plugin_mode { + None => Mode::TcpOnly, + Some(ref mode) => match mode.parse::() { + Ok(m) => m, + Err(..) => { + let e = Error::new( + ErrorKind::Malformed, + "malformed `plugin_mode`, must be one of `tcp_only`, `udp_only` and `tcp_and_udp`", + None, + ); + return Err(e); + } + }, + }, }); } } @@ -2461,6 +2500,13 @@ impl fmt::Display for Config { Some(p.plugin_args.clone()) } }); + jconf.plugin_mode = match svr.plugin() { + None => None, + Some(p) => match p.plugin_mode { + Mode::TcpOnly => None, + _ => Some(p.plugin_mode.to_string()), + }, + }; jconf.timeout = svr.timeout().map(|t| t.as_secs()); jconf.mode = Some(svr.mode().to_string()); @@ -2510,6 +2556,13 @@ impl fmt::Display for Config { Some(p.plugin_args.clone()) } }), + plugin_mode: match svr.plugin() { + None => None, + Some(p) => match p.plugin_mode { + Mode::TcpOnly => None, + _ => Some(p.plugin_mode.to_string()), + }, + }, timeout: svr.timeout().map(|t| t.as_secs()), remarks: svr.remarks().map(ToOwned::to_owned), id: svr.id().map(ToOwned::to_owned), diff --git a/crates/shadowsocks-service/src/local/tunnel/tcprelay.rs b/crates/shadowsocks-service/src/local/tunnel/tcprelay.rs index a0a445f7488b..ec73342bb4f0 100644 --- a/crates/shadowsocks-service/src/local/tunnel/tcprelay.rs +++ b/crates/shadowsocks-service/src/local/tunnel/tcprelay.rs @@ -74,7 +74,7 @@ async fn handle_tcp_client( "establishing tcp tunnel {} <-> {} through sever {} (outbound: {})", peer_addr, forward_addr, - svr_cfg.external_addr(), + svr_cfg.tcp_external_addr(), svr_cfg.addr(), ); diff --git a/crates/shadowsocks-service/src/local/utils.rs b/crates/shadowsocks-service/src/local/utils.rs index 0ac65fbeaab1..9d6532f5dcac 100644 --- a/crates/shadowsocks-service/src/local/utils.rs +++ b/crates/shadowsocks-service/src/local/utils.rs @@ -30,7 +30,7 @@ where "established tcp tunnel {} <-> {} through sever {} (outbound: {})", peer_addr, target_addr, - svr_cfg.external_addr(), + svr_cfg.tcp_external_addr(), svr_cfg.addr(), ); } else { diff --git a/crates/shadowsocks-service/src/manager/server.rs b/crates/shadowsocks-service/src/manager/server.rs index dcd50269eb6a..9172813e3820 100644 --- a/crates/shadowsocks-service/src/manager/server.rs +++ b/crates/shadowsocks-service/src/manager/server.rs @@ -11,22 +11,12 @@ use shadowsocks::{ crypto::CipherKind, dns_resolver::DnsResolver, manager::protocol::{ - self, - AddRequest, - AddResponse, - ErrorResponse, - ListResponse, - ManagerRequest, - PingResponse, - RemoveRequest, - RemoveResponse, - ServerUserConfig, - StatRequest, + self, AddRequest, AddResponse, ErrorResponse, ListResponse, ManagerRequest, PingResponse, RemoveRequest, + RemoveResponse, ServerUserConfig, StatRequest, }, net::{AcceptOpts, ConnectOpts}, plugin::PluginConfig, - ManagerListener, - ServerAddr, + ManagerListener, ServerAddr, }; use tokio::{sync::Mutex, task::JoinHandle}; @@ -256,7 +246,7 @@ impl Manager { info!( "closed managed server listening on {}, inbound address {}", v.svr_cfg.addr(), - v.svr_cfg.external_addr() + v.svr_cfg.tcp_external_addr() ); } @@ -444,6 +434,18 @@ impl Manager { plugin: plugin.clone(), plugin_opts: req.plugin_opts.clone(), plugin_args: Vec::new(), + plugin_mode: match req.plugin_mode { + None => Mode::TcpOnly, + Some(ref mode) => match mode.parse::() { + Ok(m) => m, + Err(..) => { + error!("unrecognized plugin_mode \"{}\", req: {:?}", mode, req); + + let err = format!("unrecognized plugin_mode \"{}\"", mode); + return Ok(AddResponse(err)); + } + }, + }, }; svr_cfg.set_plugin(p); } else if let Some(ref plugin) = self.svr_cfg.plugin { @@ -536,6 +538,7 @@ impl Manager { no_delay: None, plugin: None, plugin_opts: None, + plugin_mode: None, mode: None, users, }; diff --git a/crates/shadowsocks/src/config.rs b/crates/shadowsocks/src/config.rs index 1a6529f7a5be..1e8b198be8a9 100644 --- a/crates/shadowsocks/src/config.rs +++ b/crates/shadowsocks/src/config.rs @@ -541,9 +541,24 @@ impl ServerConfig { self.plugin_addr.as_ref() } - /// Get server's external address - pub fn external_addr(&self) -> &ServerAddr { - self.plugin_addr.as_ref().unwrap_or(&self.addr) + /// Get server's TCP external address + pub fn tcp_external_addr(&self) -> &ServerAddr { + if let Some(plugin) = self.plugin() { + if plugin.plugin_mode.enable_tcp() { + return self.plugin_addr.as_ref().unwrap_or(&self.addr); + } + } + &self.addr + } + + /// Get server's TCP external address + pub fn udp_external_addr(&self) -> &ServerAddr { + if let Some(plugin) = self.plugin() { + if plugin.plugin_mode.enable_udp() { + return self.plugin_addr.as_ref().unwrap_or(&self.addr); + } + } + &self.addr } /// Set timeout @@ -790,6 +805,7 @@ impl ServerConfig { plugin: p.to_owned(), plugin_opts: vsp.next().map(ToOwned::to_owned), plugin_args: Vec::new(), // SIP002 doesn't have arguments for plugins + plugin_mode: Mode::TcpOnly, // SIP002 doesn't support SIP003u }; svrconfig.set_plugin(plugin); } diff --git a/crates/shadowsocks/src/manager/protocol.rs b/crates/shadowsocks/src/manager/protocol.rs index ef24d12f726d..abc5affdc105 100644 --- a/crates/shadowsocks/src/manager/protocol.rs +++ b/crates/shadowsocks/src/manager/protocol.rs @@ -37,6 +37,8 @@ pub struct ServerConfig { #[serde(skip_serializing_if = "Option::is_none")] pub plugin_opts: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub plugin_mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub mode: Option, #[serde(skip_serializing_if = "Option::is_none")] pub users: Option>, diff --git a/crates/shadowsocks/src/plugin/mod.rs b/crates/shadowsocks/src/plugin/mod.rs index ea775ca1c76f..0be4ac67b4b0 100644 --- a/crates/shadowsocks/src/plugin/mod.rs +++ b/crates/shadowsocks/src/plugin/mod.rs @@ -22,6 +22,7 @@ use std::{ use log::{debug, error}; use tokio::{net::TcpStream, process::Child, time}; +use crate::config::Mode; use crate::config::ServerAddr; mod obfs_proxy; @@ -33,6 +34,7 @@ pub struct PluginConfig { pub plugin: String, pub plugin_opts: Option, pub plugin_args: Vec, + pub plugin_mode: Mode, } /// Mode of Plugin @@ -60,6 +62,7 @@ pub enum PluginMode { pub struct Plugin { process: Child, local_addr: SocketAddr, + mode: Mode, } impl Plugin { @@ -108,7 +111,11 @@ impl Plugin { } } - Ok(Plugin { process, local_addr }) + Ok(Plugin { + process, + local_addr, + mode: c.plugin_mode, + }) } } } @@ -120,6 +127,12 @@ impl Plugin { /// Check if plugin have been started pub async fn wait_started(&self, timeout: Duration) -> bool { + // Only test started with TCP connect() + // XXX: Is there an easy way to test if UDP port was listening? (no ICMP!) + if !self.mode.enable_tcp() { + return true; + } + let start_time = Instant::now(); loop { @@ -130,6 +143,7 @@ impl Plugin { } let remain_time = timeout - elapsed_time; + match time::timeout(remain_time, TcpStream::connect(self.local_addr)).await { Ok(Ok(..)) => { return true; diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs index b0b29ed8cd4f..2c1c7764c88d 100644 --- a/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_listener.rs @@ -39,7 +39,7 @@ impl ProxyListener { svr_cfg: &ServerConfig, accept_opts: AcceptOpts, ) -> io::Result { - let listener = match svr_cfg.external_addr() { + let listener = match svr_cfg.tcp_external_addr() { ServerAddr::SocketAddr(sa) => TcpListener::bind_with_opts(sa, accept_opts).await?, ServerAddr::DomainName(domain, port) => { lookup_then!(&context, domain, *port, |addr| { diff --git a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs index 0c2edd0058b0..953815ea27bb 100644 --- a/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs +++ b/crates/shadowsocks/src/relay/tcprelay/proxy_stream/client.rs @@ -115,7 +115,7 @@ where Some(d) => { match time::timeout( d, - OutboundTcpStream::connect_server_with_opts(&context, svr_cfg.external_addr(), opts), + OutboundTcpStream::connect_server_with_opts(&context, svr_cfg.tcp_external_addr(), opts), ) .await { @@ -129,13 +129,13 @@ where } } } - None => OutboundTcpStream::connect_server_with_opts(&context, svr_cfg.external_addr(), opts).await?, + None => OutboundTcpStream::connect_server_with_opts(&context, svr_cfg.tcp_external_addr(), opts).await?, }; trace!( "connected tcp remote {} (outbound: {}) with {:?}", svr_cfg.addr(), - svr_cfg.external_addr(), + svr_cfg.tcp_external_addr(), opts ); diff --git a/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs b/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs index fb6421381bc7..6951e27a7304 100644 --- a/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs +++ b/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs @@ -23,11 +23,7 @@ use crate::{ }; use super::crypto_io::{ - decrypt_client_payload, - decrypt_server_payload, - encrypt_client_payload, - encrypt_server_payload, - ProtocolError, + decrypt_client_payload, decrypt_server_payload, encrypt_client_payload, encrypt_server_payload, ProtocolError, ProtocolResult, }; @@ -98,9 +94,9 @@ impl ProxySocket { ) -> ProxySocketResult { // Note: Plugins doesn't support UDP relay - let socket = ShadowUdpSocket::connect_server_with_opts(&context, svr_cfg.addr(), opts).await?; + let socket = ShadowUdpSocket::connect_server_with_opts(&context, svr_cfg.udp_external_addr(), opts).await?; - trace!("connected udp remote {} with {:?}", svr_cfg.addr(), opts); + trace!("connected udp remote {} with {:?}", svr_cfg.udp_external_addr(), opts); Ok(ProxySocket::from_socket( UdpSocketType::Client, @@ -158,7 +154,7 @@ impl ProxySocket { opts: AcceptOpts, ) -> ProxySocketResult { // Plugins doesn't support UDP - let socket = match svr_cfg.addr() { + let socket = match svr_cfg.udp_external_addr() { ServerAddr::SocketAddr(sa) => ShadowUdpSocket::listen_with_opts(sa, opts).await?, ServerAddr::DomainName(domain, port) => { lookup_then!(&context, domain, *port, |addr| { diff --git a/src/service/local.rs b/src/service/local.rs index 6e40d00f9a14..dd507dfc0228 100644 --- a/src/service/local.rs +++ b/src/service/local.rs @@ -14,12 +14,7 @@ use shadowsocks_service::shadowsocks::relay::socks5::Address; use shadowsocks_service::{ acl::AccessControl, config::{ - read_variable_field_value, - Config, - ConfigType, - LocalConfig, - LocalInstanceConfig, - ProtocolType, + read_variable_field_value, Config, ConfigType, LocalConfig, LocalInstanceConfig, ProtocolType, ServerInstanceConfig, }, create_local, @@ -35,8 +30,7 @@ use shadowsocks_service::{ use crate::logging; use crate::{ config::{Config as ServiceConfig, RuntimeMode}, - monitor, - vparser, + monitor, vparser, }; #[cfg(feature = "local-dns")] @@ -583,6 +577,7 @@ pub fn main(matches: &ArgMatches) -> ExitCode { plugin: p, plugin_opts: matches.get_one::("PLUGIN_OPT").cloned(), plugin_args: Vec::new(), + plugin_mode: Mode::TcpOnly, }; sc.set_plugin(plugin); diff --git a/src/service/manager.rs b/src/service/manager.rs index 938c6be42a09..c88c2b5289ad 100644 --- a/src/service/manager.rs +++ b/src/service/manager.rs @@ -24,8 +24,7 @@ use shadowsocks_service::{ use crate::logging; use crate::{ config::{Config as ServiceConfig, RuntimeMode}, - monitor, - vparser, + monitor, vparser, }; /// Defines command line options @@ -373,6 +372,7 @@ pub fn main(matches: &ArgMatches) -> ExitCode { plugin: p, plugin_opts: matches.get_one::("PLUGIN_OPT").cloned(), plugin_args: Vec::new(), + plugin_mode: Mode::TcpOnly, }); } diff --git a/src/service/server.rs b/src/service/server.rs index 0e69ee18a222..1c7f6dcf002c 100644 --- a/src/service/server.rs +++ b/src/service/server.rs @@ -22,8 +22,7 @@ use shadowsocks_service::{ use crate::logging; use crate::{ config::{Config as ServiceConfig, RuntimeMode}, - monitor, - vparser, + monitor, vparser, }; /// Defines command line options @@ -354,6 +353,7 @@ pub fn main(matches: &ArgMatches) -> ExitCode { plugin: p, plugin_opts: matches.get_one::("PLUGIN_OPT").cloned(), plugin_args: Vec::new(), + plugin_mode: Mode::TcpOnly, }; sc.set_plugin(plugin);