diff --git a/crates/shadowsocks-service/src/config.rs b/crates/shadowsocks-service/src/config.rs index 46820e94531b..364be1c95eb4 100644 --- a/crates/shadowsocks-service/src/config.rs +++ b/crates/shadowsocks-service/src/config.rs @@ -393,6 +393,12 @@ struct SSServerExtConfig { #[serde(skip_serializing_if = "Option::is_none")] #[cfg(any(target_os = "linux", target_os = "android"))] outbound_fwmark: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + outbound_bind_addr: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + outbound_bind_interface: Option, } /// Server config type @@ -1178,9 +1184,11 @@ pub struct ServerInstanceConfig { pub config: ServerConfig, /// Server's private ACL, set to `None` will use the global `AccessControl` pub acl: Option, - /// Server's outbound fwmark to support split tunnel + /// Server's outbound fwmark / address / interface to support split tunnel #[cfg(any(target_os = "linux", target_os = "android"))] pub outbound_fwmark: Option, + pub outbound_bind_addr: Option, + pub outbound_bind_interface: Option, } impl ServerInstanceConfig { @@ -1191,6 +1199,8 @@ impl ServerInstanceConfig { acl: None, #[cfg(any(target_os = "linux", target_os = "android"))] outbound_fwmark: None, + outbound_bind_addr: None, + outbound_bind_interface: None, } } } @@ -1861,11 +1871,25 @@ impl Config { nsvr.set_timeout(timeout); } + let mut outbound_bind_addr: Option = None; + + if let Some(ref bind_addr) = config.outbound_bind_addr { + match bind_addr.parse::() { + Ok(b) => outbound_bind_addr = Some(b), + Err(..) => { + let err = Error::new(ErrorKind::Invalid, "invalid outbound_bind_addr", None); + return Err(err); + } + } + } + let server_instance = ServerInstanceConfig { config: nsvr, acl: None, #[cfg(any(target_os = "linux", target_os = "android"))] outbound_fwmark: config.outbound_fwmark, + outbound_bind_addr: outbound_bind_addr, + outbound_bind_interface: config.outbound_bind_interface.clone(), }; nconfig.server.push(server_instance); @@ -2029,11 +2053,25 @@ impl Config { nsvr.set_weight(weight); } + let mut outbound_bind_addr: Option = None; + + if let Some(ref bind_addr) = config.outbound_bind_addr { + match bind_addr.parse::() { + Ok(b) => outbound_bind_addr = Some(b), + Err(..) => { + let err = Error::new(ErrorKind::Invalid, "invalid outbound_bind_addr", None); + return Err(err); + } + } + } + let mut server_instance = ServerInstanceConfig { config: nsvr, acl: None, #[cfg(any(target_os = "linux", target_os = "android"))] outbound_fwmark: config.outbound_fwmark, + outbound_bind_addr: outbound_bind_addr, + outbound_bind_interface: config.outbound_bind_interface.clone(), }; if let Some(acl_path) = svr.acl { @@ -2056,6 +2094,14 @@ impl Config { server_instance.outbound_fwmark = Some(outbound_fwmark); } + if let Some(outbound_bind_addr) = svr.outbound_bind_addr { + server_instance.outbound_bind_addr = Some(outbound_bind_addr); + } + + if let Some(ref outbound_bind_interface) = svr.outbound_bind_interface { + server_instance.outbound_bind_interface = Some(outbound_bind_interface.clone()); + } + nconfig.server.push(server_instance); } } @@ -2830,6 +2876,8 @@ impl fmt::Display for Config { .and_then(|a| a.file_path().to_str().map(ToOwned::to_owned)), #[cfg(any(target_os = "linux", target_os = "android"))] outbound_fwmark: inst.outbound_fwmark.clone(), + outbound_bind_addr: inst.outbound_bind_addr.clone(), + outbound_bind_interface: inst.outbound_bind_interface.clone(), }); } diff --git a/crates/shadowsocks-service/src/local/http/utils.rs b/crates/shadowsocks-service/src/local/http/utils.rs index 32ae7b908cc4..41bd4a6c8920 100644 --- a/crates/shadowsocks-service/src/local/http/utils.rs +++ b/crates/shadowsocks-service/src/local/http/utils.rs @@ -131,7 +131,12 @@ pub async fn connect_host( } else { let server = balancer.best_tcp_server(); - match AutoProxyClientStream::connect(context, server.as_ref(), host).await { + match AutoProxyClientStream::connect_with_opts( + context, + server.as_ref(), + host, + server.connect_opts_ref() + ).await { Ok(s) => Ok((s, Some(server))), Err(err) => { error!( diff --git a/crates/shadowsocks-service/src/local/loadbalancing/server_data.rs b/crates/shadowsocks-service/src/local/loadbalancing/server_data.rs index f42fce1edd55..413b8dd8f474 100644 --- a/crates/shadowsocks-service/src/local/loadbalancing/server_data.rs +++ b/crates/shadowsocks-service/src/local/loadbalancing/server_data.rs @@ -86,6 +86,14 @@ impl ServerIdent { connect_opts.fwmark = Some(fwmark); } + if let Some(bind_local_addr) = svr_cfg.outbound_bind_addr { + connect_opts.bind_local_addr = Some(bind_local_addr); + } + + if let Some(ref bind_interface) = svr_cfg.outbound_bind_interface { + connect_opts.bind_interface = Some(bind_interface.clone()); + } + ServerIdent { tcp_score: ServerScore::new(svr_cfg.config.weight().tcp_weight(), max_server_rtt, check_window), udp_score: ServerScore::new(svr_cfg.config.weight().udp_weight(), max_server_rtt, check_window), diff --git a/crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs b/crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs index fd225cab5638..ed19aa6ba538 100644 --- a/crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs +++ b/crates/shadowsocks-service/src/local/net/tcp/auto_proxy_stream.rs @@ -10,7 +10,7 @@ use std::{ use pin_project::pin_project; use shadowsocks::{ - net::TcpStream, + net::{ConnectOpts, TcpStream}, relay::{socks5::Address, tcprelay::proxy_stream::ProxyClientStream}, }; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; @@ -37,19 +37,44 @@ impl AutoProxyClientStream { server: &ServerIdent, addr: A, ) -> io::Result + where + A: Into
, + { + AutoProxyClientStream::connect_with_opts(context.clone(), server, addr, context.connect_opts_ref()).await + } + + /// Connect to target `addr` via shadowsocks' server configured by `svr_cfg` + pub async fn connect_with_opts( + context: Arc, + server: &ServerIdent, + addr: A, + opts: &ConnectOpts, + ) -> io::Result where A: Into
, { let addr = addr.into(); if context.check_target_bypassed(&addr).await { - AutoProxyClientStream::connect_bypassed(context, addr).await + AutoProxyClientStream::connect_bypassed_with_opts(context, addr, opts).await } else { - AutoProxyClientStream::connect_proxied(context, server, addr).await + AutoProxyClientStream::connect_proxied_with_opts(context, server, addr, opts).await } } /// Connect directly to target `addr` pub async fn connect_bypassed(context: Arc, addr: A) -> io::Result + where + A: Into
, + { + AutoProxyClientStream::connect_bypassed_with_opts(context.clone(), addr, context.connect_opts_ref()).await + } + + /// Connect directly to target `addr` + pub async fn connect_bypassed_with_opts( + context: Arc, + addr: A, + connect_opts: &ConnectOpts, + ) -> io::Result where A: Into
, { @@ -61,7 +86,7 @@ impl AutoProxyClientStream { addr = mapped_addr; } let stream = - TcpStream::connect_remote_with_opts(context.context_ref(), &addr, context.connect_opts_ref()).await?; + TcpStream::connect_remote_with_opts(context.context_ref(), &addr, connect_opts).await?; Ok(AutoProxyClientStream::Bypassed(stream)) } @@ -71,6 +96,25 @@ impl AutoProxyClientStream { server: &ServerIdent, addr: A, ) -> io::Result + where + A: Into
, + { + AutoProxyClientStream::connect_proxied_with_opts( + context.clone(), + server, + addr, + context.connect_opts_ref() + ) + .await + } + + /// Connect to target `addr` via shadowsocks' server configured by `svr_cfg` + pub async fn connect_proxied_with_opts( + context: Arc, + server: &ServerIdent, + addr: A, + connect_opts: &ConnectOpts, + ) -> io::Result where A: Into
, { @@ -85,7 +129,7 @@ impl AutoProxyClientStream { context.context(), server.server_config(), addr, - context.connect_opts_ref(), + connect_opts, |stream| MonProxyStream::from_stream(stream, flow_stat), ) .await diff --git a/crates/shadowsocks-service/src/local/net/udp/association.rs b/crates/shadowsocks-service/src/local/net/udp/association.rs index 49b99671fd86..13e066097e6f 100644 --- a/crates/shadowsocks-service/src/local/net/udp/association.rs +++ b/crates/shadowsocks-service/src/local/net/udp/association.rs @@ -572,7 +572,7 @@ where let svr_cfg = server.server_config(); let socket = - ProxySocket::connect_with_opts(self.context.context(), svr_cfg, self.context.connect_opts_ref()) + ProxySocket::connect_with_opts(self.context.context(), svr_cfg, server.connect_opts_ref()) .await?; let socket = MonProxySocket::from_socket(socket, self.context.flow_stat()); diff --git a/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs b/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs index f4b928c50afc..b416319c8cb2 100644 --- a/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs +++ b/crates/shadowsocks-service/src/local/redir/tcprelay/mod.rs @@ -47,7 +47,7 @@ async fn establish_client_tcp_redir<'a>( let server = balancer.best_tcp_server(); let svr_cfg = server.server_config(); - let mut remote = AutoProxyClientStream::connect(context, &server, addr).await?; + let mut remote = AutoProxyClientStream::connect_with_opts(context, &server, addr, server.connect_opts_ref()).await?; establish_tcp_tunnel(svr_cfg, &mut stream, &mut remote, peer_addr, addr).await } diff --git a/crates/shadowsocks-service/src/local/socks/server/socks4/tcprelay.rs b/crates/shadowsocks-service/src/local/socks/server/socks4/tcprelay.rs index 7a8c973fcf0b..fa502d5466b8 100644 --- a/crates/shadowsocks-service/src/local/socks/server/socks4/tcprelay.rs +++ b/crates/shadowsocks-service/src/local/socks/server/socks4/tcprelay.rs @@ -102,7 +102,13 @@ impl Socks4TcpHandler { } else { let server = self.balancer.best_tcp_server(); - let r = AutoProxyClientStream::connect(self.context, &server, &target_addr).await; + let r = AutoProxyClientStream::connect_with_opts( + self.context, + &server, + &target_addr, + server.connect_opts_ref() + ) + .await; server_opt = Some(server); r diff --git a/crates/shadowsocks-service/src/local/socks/server/socks5/tcprelay.rs b/crates/shadowsocks-service/src/local/socks/server/socks5/tcprelay.rs index cd24b1bb6b43..eff5af3c46c0 100644 --- a/crates/shadowsocks-service/src/local/socks/server/socks5/tcprelay.rs +++ b/crates/shadowsocks-service/src/local/socks/server/socks5/tcprelay.rs @@ -254,7 +254,13 @@ impl Socks5TcpHandler { } else { let server = self.balancer.best_tcp_server(); - let r = AutoProxyClientStream::connect(self.context.clone(), &server, &target_addr).await; + let r = AutoProxyClientStream::connect_with_opts( + self.context, + &server, + &target_addr, + server.connect_opts_ref() + ) + .await; server_opt = Some(server); r diff --git a/crates/shadowsocks-service/src/local/tun/tcp.rs b/crates/shadowsocks-service/src/local/tun/tcp.rs index fa376b185a11..857271476e34 100644 --- a/crates/shadowsocks-service/src/local/tun/tcp.rs +++ b/crates/shadowsocks-service/src/local/tun/tcp.rs @@ -576,7 +576,7 @@ async fn establish_client_tcp_redir<'a>( let server = balancer.best_tcp_server(); let svr_cfg = server.server_config(); - let mut remote = AutoProxyClientStream::connect(context, &server, addr).await?; + let mut remote = AutoProxyClientStream::connect_with_opts(context, &server, addr, server.connect_opts_ref()).await?; establish_tcp_tunnel(svr_cfg, &mut stream, &mut remote, peer_addr, addr).await } diff --git a/crates/shadowsocks-service/src/local/tunnel/tcprelay.rs b/crates/shadowsocks-service/src/local/tunnel/tcprelay.rs index d4f2f91b30ae..1ccf55a3ccf0 100644 --- a/crates/shadowsocks-service/src/local/tunnel/tcprelay.rs +++ b/crates/shadowsocks-service/src/local/tunnel/tcprelay.rs @@ -138,6 +138,12 @@ async fn handle_tcp_client( svr_cfg.addr(), ); - let mut remote = AutoProxyClientStream::connect_proxied(context, &server, forward_addr).await?; + let mut remote = AutoProxyClientStream::connect_proxied_with_opts( + context, + &server, + forward_addr, + server.connect_opts_ref() + ) + .await?; establish_tcp_tunnel(svr_cfg, &mut stream, &mut remote, peer_addr, forward_addr).await } diff --git a/crates/shadowsocks-service/src/manager/server.rs b/crates/shadowsocks-service/src/manager/server.rs index acce92e9df63..f5db629dad1a 100644 --- a/crates/shadowsocks-service/src/manager/server.rs +++ b/crates/shadowsocks-service/src/manager/server.rs @@ -410,6 +410,8 @@ impl Manager { acl: None, // Set with --acl command line argument #[cfg(any(target_os = "linux", target_os = "android"))] outbound_fwmark: None, + outbound_bind_addr: None, + outbound_bind_interface: None, }; let mut config = Config::new(ConfigType::Server); diff --git a/crates/shadowsocks-service/src/server/mod.rs b/crates/shadowsocks-service/src/server/mod.rs index 34f26c718e88..bb4c6b47b4c0 100644 --- a/crates/shadowsocks-service/src/server/mod.rs +++ b/crates/shadowsocks-service/src/server/mod.rs @@ -116,6 +116,14 @@ pub async fn run(config: Config) -> io::Result<()> { connect_opts.fwmark = Some(fwmark); } + if let Some(bind_local_addr) = inst.outbound_bind_addr { + connect_opts.bind_local_addr = Some(bind_local_addr); + } + + if let Some(bind_interface) = inst.outbound_bind_interface { + connect_opts.bind_interface = Some(bind_interface); + } + server_builder.set_connect_opts(connect_opts.clone()); server_builder.set_accept_opts(accept_opts.clone());