From c9c59f9664f3fc178005302ab974889271274b58 Mon Sep 17 00:00:00 2001 From: zonyitoo Date: Sat, 15 Apr 2023 18:24:42 +0800 Subject: [PATCH] feat(plugin): Support SIP003u, Plugin UDP mode - ref shadowsocks/shadowsocks-org#180 - "plugin_mode" option in configuration file, and ssmanager API - "plugin_mode" is "tcp_only" by default - ref shadowsocks/shadowsocks-rust#1127 --- crates/shadowsocks-service/src/config.rs | 60 +++++++++++++++++++ .../src/local/tunnel/tcprelay.rs | 2 +- crates/shadowsocks-service/src/local/utils.rs | 2 +- .../shadowsocks-service/src/manager/server.rs | 15 ++++- crates/shadowsocks/src/config.rs | 22 ++++++- crates/shadowsocks/src/manager/protocol.rs | 2 + crates/shadowsocks/src/plugin/mod.rs | 17 +++++- .../src/relay/tcprelay/proxy_listener.rs | 2 +- .../src/relay/tcprelay/proxy_stream/client.rs | 6 +- .../src/relay/udprelay/proxy_socket.rs | 6 +- src/service/local.rs | 1 + src/service/manager.rs | 1 + src/service/server.rs | 1 + 13 files changed, 122 insertions(+), 15 deletions(-) diff --git a/crates/shadowsocks-service/src/config.rs b/crates/shadowsocks-service/src/config.rs index 27f86aa8dda8..10747ee7847b 100644 --- a/crates/shadowsocks-service/src/config.rs +++ b/crates/shadowsocks-service/src/config.rs @@ -148,6 +148,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 +328,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 +1650,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 +1791,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 +1921,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 +2507,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 +2563,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..ad4cba39eb5d 100644 --- a/crates/shadowsocks-service/src/manager/server.rs +++ b/crates/shadowsocks-service/src/manager/server.rs @@ -256,7 +256,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 +444,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 +548,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..9564653fc47f 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 UDP 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..ab96961433ee 100644 --- a/crates/shadowsocks/src/plugin/mod.rs +++ b/crates/shadowsocks/src/plugin/mod.rs @@ -22,7 +22,7 @@ use std::{ use log::{debug, error}; use tokio::{net::TcpStream, process::Child, time}; -use crate::config::ServerAddr; +use crate::config::{Mode, ServerAddr}; mod obfs_proxy; mod ss_plugin; @@ -33,6 +33,7 @@ pub struct PluginConfig { pub plugin: String, pub plugin_opts: Option, pub plugin_args: Vec, + pub plugin_mode: Mode, } /// Mode of Plugin @@ -60,6 +61,7 @@ pub enum PluginMode { pub struct Plugin { process: Child, local_addr: SocketAddr, + mode: Mode, } impl Plugin { @@ -108,7 +110,11 @@ impl Plugin { } } - Ok(Plugin { process, local_addr }) + Ok(Plugin { + process, + local_addr, + mode: c.plugin_mode, + }) } } } @@ -120,6 +126,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 +142,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..053387e238e8 100644 --- a/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs +++ b/crates/shadowsocks/src/relay/udprelay/proxy_socket.rs @@ -98,9 +98,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 +158,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..0aa2ae5a7ba5 100644 --- a/src/service/local.rs +++ b/src/service/local.rs @@ -583,6 +583,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..89e634d94e4c 100644 --- a/src/service/manager.rs +++ b/src/service/manager.rs @@ -373,6 +373,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..4072cc061023 100644 --- a/src/service/server.rs +++ b/src/service/server.rs @@ -354,6 +354,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);