feat(local): basic support of macOS launchd activate socket (#1330)

supersede #1331
This commit is contained in:
ty
2023-10-23 00:38:43 +08:00
committed by GitHub
parent 0baa8dffe9
commit 10cfca302c
23 changed files with 845 additions and 226 deletions
+18 -4
View File
@@ -554,6 +554,9 @@ Example configuration:
"socks5_auth_config_path": "/path/to/auth.json",
// OPTIONAL. Instance specific ACL
"acl": "/path/to/acl/file.acl",
// OPTIONAL. macOS launchd activate socket
"launchd_tcp_socket_name": "TCPListener",
"launchd_udp_socket_name": "UDPListener"
},
{
// SOCKS5, SOCKS4/4a local server
@@ -567,7 +570,10 @@ Example configuration:
// - TCP is enabled, then SOCKS5's UDP Association command will return this address
// - UDP is enabled, then SOCKS5's UDP server will listen to this address.
"local_udp_address": "127.0.0.1",
"local_udp_port": 2081
"local_udp_port": 2081,
// OPTIONAL. macOS launchd activate socket
"launchd_tcp_socket_name": "TCPListener",
"launchd_udp_socket_name": "UDPListener"
},
{
// Tunnel local server (feature = "local-tunnel")
@@ -580,14 +586,19 @@ Example configuration:
"forward_address": "8.8.8.8",
"forward_port": 53,
// OPTIONAL. Customizing whether to start TCP and UDP tunnel
"mode": "tcp_only"
"mode": "tcp_only",
// OPTIONAL. macOS launchd activate socket
"launchd_tcp_socket_name": "TCPListener",
"launchd_udp_socket_name": "UDPListener"
},
{
// HTTP local server (feature = "local-http")
"protocol": "http",
// Listen address
"local_address": "127.0.0.1",
"local_port": 3128
"local_port": 3128,
// OPTIONAL. macOS launchd activate socket
"launchd_tcp_socket_name": "TCPListener"
},
{
// DNS local server (feature = "local-dns")
@@ -607,7 +618,10 @@ Example configuration:
// OPTIONAL. Remote DNS's port, 53 by default
"remote_dns_port": 53,
// OPTIONAL. dns client cache size for fetching dns queries.
"client_cache_size": 5
"client_cache_size": 5,
// OPTIONAL. macOS launchd activate socket
"launchd_tcp_socket_name": "TCPListener",
"launchd_udp_socket_name": "UDPListener"
},
{
// Tun local server (feature = "local-tun")
+60
View File
@@ -240,6 +240,14 @@ struct SSLocalExtConfig {
#[serde(skip_serializing_if = "Option::is_none")]
protocol: Option<String>,
/// macOS launch activate socket
#[cfg(target_os = "macos")]
#[serde(skip_serializing_if = "Option::is_none")]
launchd_udp_socket_name: Option<String>,
#[cfg(target_os = "macos")]
#[serde(skip_serializing_if = "Option::is_none")]
launchd_tcp_socket_name: Option<String>,
/// TCP Transparent Proxy type
#[cfg(feature = "local-redir")]
#[serde(skip_serializing_if = "Option::is_none")]
@@ -888,6 +896,43 @@ pub struct LocalConfig {
#[cfg(all(feature = "local-tun", unix))]
pub tun_device_fd_from_path: Option<PathBuf>,
/// macOS launchd socket for TCP listener
///
/// <https://developer.apple.com/documentation/xpc/1505523-launch_activate_socket>
/// <https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html>
///
/// ```plist
/// <key>Sockets</key>
/// <dict>
/// <key>{launchd_tcp_socket_name}</key>
/// <dict>
/// <key>SockType</key>
/// <string>stream</string>
/// ... other keys ...
/// </dict>
/// </dict>
/// ```
#[cfg(target_os = "macos")]
pub launchd_tcp_socket_name: Option<String>,
/// macOS launchd socket for UDP listener
///
/// <https://developer.apple.com/documentation/xpc/1505523-launch_activate_socket>
/// <https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html>
///
/// ```plist
/// <key>Sockets</key>
/// <dict>
/// <key>{launchd_udp_socket_name}</key>
/// <dict>
/// <key>SockType</key>
/// <string>dgram</string>
/// ... other keys ...
/// </dict>
/// </dict>
/// ```
#[cfg(target_os = "macos")]
pub launchd_udp_socket_name: Option<String>,
/// Set `IPV6_V6ONLY` for listener socket
pub ipv6_only: bool,
@@ -941,6 +986,11 @@ impl LocalConfig {
#[cfg(all(feature = "local-tun", unix))]
tun_device_fd_from_path: None,
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: None,
#[cfg(target_os = "macos")]
launchd_udp_socket_name: None,
ipv6_only: false,
#[cfg(feature = "local")]
@@ -1465,6 +1515,12 @@ impl Config {
local_config.udp_addr = Some(local_udp_addr);
}
#[cfg(target_os = "macos")]
{
local_config.launchd_tcp_socket_name = local.launchd_tcp_socket_name;
local_config.launchd_udp_socket_name = local.launchd_udp_socket_name;
}
match local.mode {
Some(mode) => match mode.parse::<Mode>() {
Ok(mode) => local_config.mode = mode,
@@ -2423,6 +2479,10 @@ impl fmt::Display for Config {
#[allow(unreachable_patterns)]
p => Some(p.as_str().to_owned()),
},
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: local.launchd_tcp_socket_name.clone(),
#[cfg(target_os = "macos")]
launchd_udp_socket_name: local.launchd_udp_socket_name.clone(),
#[cfg(feature = "local-redir")]
tcp_redir: if local.tcp_redir != RedirType::tcp_default() {
Some(local.tcp_redir.to_string())
@@ -32,15 +32,18 @@ use tokio::{
use shadowsocks::{
config::Mode,
lookup_then,
net::{TcpListener, UdpSocket as ShadowUdpSocket},
net::TcpListener,
relay::{udprelay::MAXIMUM_UDP_PAYLOAD_SIZE, Address},
ServerAddr,
};
use crate::{
acl::AccessControl,
local::{context::ServiceContext, loadbalancing::PingBalancer},
local::{
context::ServiceContext,
loadbalancing::PingBalancer,
net::{tcp::listener::create_standard_tcp_listener, udp::listener::create_standard_udp_listener},
},
};
use super::{client_cache::DnsClientCache, config::NameServerAddr};
@@ -54,6 +57,10 @@ pub struct DnsBuilder {
bind_addr: ServerAddr,
balancer: PingBalancer,
client_cache_size: usize,
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: Option<String>,
#[cfg(target_os = "macos")]
launchd_udp_socket_name: Option<String>,
}
impl DnsBuilder {
@@ -93,6 +100,10 @@ impl DnsBuilder {
bind_addr,
balancer,
client_cache_size,
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: None,
#[cfg(target_os = "macos")]
launchd_udp_socket_name: None,
}
}
@@ -101,6 +112,18 @@ impl DnsBuilder {
self.mode = mode;
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_tcp_socket_name(&mut self, n: String) {
self.launchd_tcp_socket_name = Some(n);
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_udp_socket_name(&mut self, n: String) {
self.launchd_udp_socket_name = Some(n);
}
/// Build DNS server
pub async fn build(self) -> io::Result<Dns> {
let client = Arc::new(DnsClient::new(
@@ -115,20 +138,35 @@ impl DnsBuilder {
let mut tcp_server = None;
if self.mode.enable_tcp() {
let server = DnsTcpServer::new(
#[allow(unused_mut)]
let mut builder = DnsTcpServerBuilder::new(
self.context.clone(),
&self.bind_addr,
self.bind_addr.clone(),
local_addr.clone(),
remote_addr.clone(),
client.clone(),
)
.await?;
);
#[cfg(target_os = "macos")]
if let Some(s) = self.launchd_tcp_socket_name {
builder.set_launchd_socket_name(s);
}
let server = builder.build().await?;
tcp_server = Some(server);
}
let mut udp_server = None;
if self.mode.enable_udp() {
let server = DnsUdpServer::new(self.context, &self.bind_addr, local_addr, remote_addr, client).await?;
#[allow(unused_mut)]
let mut builder = DnsUdpServerBuilder::new(self.context, self.bind_addr, local_addr, remote_addr, client);
#[cfg(target_os = "macos")]
if let Some(s) = self.launchd_udp_socket_name {
builder.set_launchd_socket_name(s);
}
let server = builder.build().await?;
udp_server = Some(server);
}
@@ -136,6 +174,68 @@ impl DnsBuilder {
}
}
struct DnsTcpServerBuilder {
context: Arc<ServiceContext>,
bind_addr: ServerAddr,
local_addr: Arc<NameServerAddr>,
remote_addr: Arc<Address>,
client: Arc<DnsClient>,
#[cfg(target_os = "macos")]
launchd_socket_name: Option<String>,
}
impl DnsTcpServerBuilder {
fn new(
context: Arc<ServiceContext>,
bind_addr: ServerAddr,
local_addr: Arc<NameServerAddr>,
remote_addr: Arc<Address>,
client: Arc<DnsClient>,
) -> DnsTcpServerBuilder {
DnsTcpServerBuilder {
context,
bind_addr,
local_addr,
remote_addr,
client,
#[cfg(target_os = "macos")]
launchd_socket_name: None,
}
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
fn set_launchd_socket_name(&mut self, n: String) {
self.launchd_socket_name = Some(n);
}
async fn build(self) -> io::Result<DnsTcpServer> {
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let listener = if let Some(launchd_socket_name) = self.launchd_socket_name {
use tokio::net::TcpListener as TokioTcpListener;
use crate::net::launch_activate_socket::get_launch_activate_tcp_listener;
let std_listener = get_launch_activate_tcp_listener(&launchd_socket_name)?;
let tokio_listener = TokioTcpListener::from_std(std_listener)?;
TcpListener::from_listener(tokio_listener, self.context.accept_opts())?
} else {
create_standard_tcp_listener(&self.context, &self.bind_addr).await?
};
} else {
let listener = create_standard_tcp_listener(&self.context, &self.bind_addr).await?;
}
}
Ok(DnsTcpServer {
listener,
local_addr: self.local_addr,
remote_addr: self.remote_addr,
client: self.client,
})
}
}
/// DNS TCP server instance
pub struct DnsTcpServer {
listener: TcpListener,
@@ -145,31 +245,6 @@ pub struct DnsTcpServer {
}
impl DnsTcpServer {
async fn new(
context: Arc<ServiceContext>,
bind_addr: &ServerAddr,
local_addr: Arc<NameServerAddr>,
remote_addr: Arc<Address>,
client: Arc<DnsClient>,
) -> io::Result<DnsTcpServer> {
let listener = match *bind_addr {
ServerAddr::SocketAddr(ref saddr) => TcpListener::bind_with_opts(saddr, context.accept_opts()).await?,
ServerAddr::DomainName(ref dname, port) => {
lookup_then!(context.context_ref(), dname, port, |addr| {
TcpListener::bind_with_opts(&addr, context.accept_opts()).await
})?
.1
}
};
Ok(DnsTcpServer {
listener,
local_addr,
remote_addr,
client,
})
}
/// Get server local address
pub fn local_addr(&self) -> io::Result<SocketAddr> {
self.listener.local_addr()
@@ -272,6 +347,67 @@ impl DnsTcpServer {
}
}
struct DnsUdpServerBuilder {
context: Arc<ServiceContext>,
bind_addr: ServerAddr,
local_addr: Arc<NameServerAddr>,
remote_addr: Arc<Address>,
client: Arc<DnsClient>,
#[cfg(target_os = "macos")]
launchd_socket_name: Option<String>,
}
impl DnsUdpServerBuilder {
fn new(
context: Arc<ServiceContext>,
bind_addr: ServerAddr,
local_addr: Arc<NameServerAddr>,
remote_addr: Arc<Address>,
client: Arc<DnsClient>,
) -> DnsUdpServerBuilder {
DnsUdpServerBuilder {
context,
bind_addr,
local_addr,
remote_addr,
client,
#[cfg(target_os = "macos")]
launchd_socket_name: None,
}
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
fn set_launchd_socket_name(&mut self, n: String) {
self.launchd_socket_name = Some(n);
}
async fn build(self) -> io::Result<DnsUdpServer> {
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let socket = if let Some(launchd_socket_name) = self.launchd_socket_name {
use tokio::net::UdpSocket as TokioUdpSocket;
use crate::net::launch_activate_socket::get_launch_activate_udp_socket;
let std_socket = get_launch_activate_udp_socket(&launchd_socket_name)?;
TokioUdpSocket::from_std(std_socket)?
} else {
create_standard_udp_listener(&self.context, &self.bind_addr).await?.into()
};
} else {
let socket = create_standard_udp_listener(&self.context, &self.bind_addr).await?.into();
}
}
Ok(DnsUdpServer {
listener: Arc::new(socket),
local_addr: self.local_addr,
remote_addr: self.remote_addr,
client: self.client,
})
}
}
/// DNS UDP server instance
pub struct DnsUdpServer {
listener: Arc<UdpSocket>,
@@ -281,34 +417,6 @@ pub struct DnsUdpServer {
}
impl DnsUdpServer {
async fn new(
context: Arc<ServiceContext>,
bind_addr: &ServerAddr,
local_addr: Arc<NameServerAddr>,
remote_addr: Arc<Address>,
client: Arc<DnsClient>,
) -> io::Result<DnsUdpServer> {
let socket = match *bind_addr {
ServerAddr::SocketAddr(ref saddr) => ShadowUdpSocket::listen(saddr).await?,
ServerAddr::DomainName(ref dname, port) => {
lookup_then!(context.context_ref(), dname, port, |addr| {
ShadowUdpSocket::listen(&addr).await
})?
.1
}
};
let socket: UdpSocket = socket.into();
let listener = Arc::new(socket);
Ok(DnsUdpServer {
listener,
local_addr,
remote_addr,
client,
})
}
/// Get server local address
pub fn local_addr(&self) -> io::Result<SocketAddr> {
self.listener.local_addr()
@@ -16,12 +16,13 @@ use hyper::{
Server,
};
use log::{error, info};
use shadowsocks::{config::ServerAddr, lookup_then, net::TcpListener};
use shadowsocks::{config::ServerAddr, net::TcpListener};
use crate::local::{
context::ServiceContext,
http::connector::Connector,
loadbalancing::PingBalancer,
net::tcp::listener::create_standard_tcp_listener,
LOCAL_DEFAULT_KEEPALIVE_TIMEOUT,
};
@@ -32,6 +33,8 @@ pub struct HttpBuilder {
context: Arc<ServiceContext>,
client_config: ServerAddr,
balancer: PingBalancer,
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: Option<String>,
}
impl HttpBuilder {
@@ -51,18 +54,34 @@ impl HttpBuilder {
context,
client_config,
balancer,
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: None,
}
}
#[cfg(target_os = "macos")]
pub fn set_launchd_tcp_socket_name(&mut self, n: String) {
self.launchd_tcp_socket_name = Some(n);
}
/// Build HTTP server instance
pub async fn build(self) -> io::Result<Http> {
let listener = match self.client_config {
ServerAddr::SocketAddr(sa) => TcpListener::bind_with_opts(&sa, self.context.accept_opts().clone()).await,
ServerAddr::DomainName(ref dname, port) => lookup_then!(self.context.context_ref(), dname, port, |addr| {
TcpListener::bind_with_opts(&addr, self.context.accept_opts().clone()).await
})
.map(|(_, b)| b),
}?;
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let listener = if let Some(launchd_socket_name) = self.launchd_tcp_socket_name {
use tokio::net::TcpListener as TokioTcpListener;
use crate::net::launch_activate_socket::get_launch_activate_tcp_listener;
let std_listener = get_launch_activate_tcp_listener(&launchd_socket_name)?;
let tokio_listener = TokioTcpListener::from_std(std_listener)?;
TcpListener::from_listener(tokio_listener, self.context.accept_opts())?
} else {
create_standard_tcp_listener(&self.context, &self.client_config).await?
};
} else {
let listener = create_standard_tcp_listener(&self.context, &self.client_config).await?;
}
}
let proxy_client_cache = Arc::new(ProxyClientCache::new(self.context.clone()));
+35 -1
View File
@@ -279,6 +279,15 @@ impl Server {
server_builder.set_udp_bind_addr(b.clone());
}
#[cfg(target_os = "macos")]
if let Some(n) = local_config.launchd_tcp_socket_name {
server_builder.set_launchd_tcp_socket_name(n);
}
#[cfg(target_os = "macos")]
if let Some(n) = local_config.launchd_udp_socket_name {
server_builder.set_launchd_udp_socket_name(n);
}
let server = server_builder.build().await?;
local_server.socks_servers.push(server);
}
@@ -305,6 +314,15 @@ impl Server {
server_builder.set_udp_bind_addr(udp_addr);
}
#[cfg(target_os = "macos")]
if let Some(n) = local_config.launchd_tcp_socket_name {
server_builder.set_launchd_tcp_socket_name(n);
}
#[cfg(target_os = "macos")]
if let Some(n) = local_config.launchd_udp_socket_name {
server_builder.set_launchd_udp_socket_name(n);
}
let server = server_builder.build().await?;
local_server.tunnel_servers.push(server);
}
@@ -315,7 +333,14 @@ impl Server {
None => return Err(io::Error::new(ErrorKind::Other, "http requires local address")),
};
let builder = HttpBuilder::with_context(context.clone(), client_addr, balancer);
#[allow(unused_mut)]
let mut builder = HttpBuilder::with_context(context.clone(), client_addr, balancer);
#[cfg(target_os = "macos")]
if let Some(n) = local_config.launchd_tcp_socket_name {
builder.set_launchd_tcp_socket_name(n);
}
let server = builder.build().await?;
local_server.http_servers.push(server);
}
@@ -366,6 +391,15 @@ impl Server {
};
server_builder.set_mode(local_config.mode);
#[cfg(target_os = "macos")]
if let Some(n) = local_config.launchd_tcp_socket_name {
server_builder.set_launchd_tcp_socket_name(n);
}
#[cfg(target_os = "macos")]
if let Some(n) = local_config.launchd_udp_socket_name {
server_builder.set_launchd_udp_socket_name(n);
}
let server = server_builder.build().await?;
local_server.dns_servers.push(server);
}
@@ -5,5 +5,5 @@ pub use self::{
udp::{UdpAssociationManager, UdpInboundWrite},
};
mod tcp;
pub(crate) mod tcp;
pub(crate) mod udp;
@@ -0,0 +1,21 @@
//! Local instance listener helpers
use std::io;
use shadowsocks::{config::ServerAddr, lookup_then, net::TcpListener};
use crate::local::context::ServiceContext;
/// Create a standard TCP listener listening on `client_config`
pub async fn create_standard_tcp_listener(
context: &ServiceContext,
client_config: &ServerAddr,
) -> io::Result<TcpListener> {
match client_config {
ServerAddr::SocketAddr(saddr) => TcpListener::bind_with_opts(saddr, context.accept_opts()).await,
ServerAddr::DomainName(dname, port) => lookup_then!(context.context_ref(), dname, *port, |addr| {
TcpListener::bind_with_opts(&addr, context.accept_opts()).await
})
.map(|(_, l)| l),
}
}
@@ -1,2 +1,3 @@
pub mod auto_proxy_io;
pub mod auto_proxy_stream;
pub mod listener;
@@ -0,0 +1,21 @@
//! Local instance listener helpers
use std::io;
use shadowsocks::{config::ServerAddr, lookup_then, net::UdpSocket};
use crate::local::context::ServiceContext;
/// Create a standard UDP listener listening on `client_config`
pub async fn create_standard_udp_listener(
context: &ServiceContext,
client_config: &ServerAddr,
) -> io::Result<UdpSocket> {
match client_config {
ServerAddr::SocketAddr(saddr) => UdpSocket::listen_with_opts(saddr, context.accept_opts()).await,
ServerAddr::DomainName(dname, port) => lookup_then!(context.context_ref(), dname, *port, |addr| {
UdpSocket::listen_with_opts(&addr, context.accept_opts()).await
})
.map(|(_, s)| s),
}
}
@@ -1,3 +1,4 @@
pub use self::association::{UdpAssociationManager, UdpInboundWrite};
pub mod association;
pub mod listener;
@@ -112,7 +112,7 @@ impl RedirTcpServer {
/// Start serving
pub async fn run(self) -> io::Result<()> {
let listener = ShadowTcpListener::from_listener(self.listener, self.context.accept_opts());
let listener = ShadowTcpListener::from_listener(self.listener, self.context.accept_opts())?;
let actual_local_addr = listener.local_addr().expect("determine port bound to");
@@ -7,8 +7,8 @@ use shadowsocks::{config::Mode, ServerAddr};
use crate::local::{context::ServiceContext, loadbalancing::PingBalancer};
pub use self::server::{SocksTcpServer, SocksUdpServer};
use self::socks5::Socks5UdpServer;
pub use self::server::{SocksTcpServer, SocksTcpServerBuilder, SocksUdpServer};
use self::socks5::Socks5UdpServerBuilder;
use super::config::Socks5AuthConfig;
@@ -28,6 +28,10 @@ pub struct SocksBuilder {
socks5_auth: Socks5AuthConfig,
client_config: ServerAddr,
balancer: PingBalancer,
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: Option<String>,
#[cfg(target_os = "macos")]
launchd_udp_socket_name: Option<String>,
}
impl SocksBuilder {
@@ -52,6 +56,10 @@ impl SocksBuilder {
socks5_auth: Socks5AuthConfig::default(),
client_config,
balancer,
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: None,
#[cfg(target_os = "macos")]
launchd_udp_socket_name: None,
}
}
@@ -83,33 +91,59 @@ impl SocksBuilder {
self.socks5_auth = p;
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_tcp_socket_name(&mut self, n: String) {
self.launchd_tcp_socket_name = Some(n);
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_udp_socket_name(&mut self, n: String) {
self.launchd_udp_socket_name = Some(n);
}
pub async fn build(self) -> io::Result<Socks> {
let udp_bind_addr = self.udp_bind_addr.unwrap_or_else(|| self.client_config.clone());
let mut udp_server = None;
if self.mode.enable_udp() {
let server = Socks5UdpServer::new(
#[allow(unused_mut)]
let mut builder = Socks5UdpServerBuilder::new(
self.context.clone(),
&udp_bind_addr,
udp_bind_addr.clone(),
self.udp_expiry_duration,
self.udp_capacity,
self.balancer.clone(),
)
.await?;
);
#[cfg(target_os = "macos")]
if let Some(s) = self.launchd_udp_socket_name {
builder.set_launchd_socket_name(s);
}
let server = builder.build().await?;
udp_server = Some(server);
}
let mut tcp_server = None;
if self.mode.enable_tcp() {
let server = SocksTcpServer::new(
#[allow(unused_mut)]
let mut builder = SocksTcpServerBuilder::new(
self.context.clone(),
self.client_config,
udp_bind_addr,
self.balancer.clone(),
self.mode,
self.socks5_auth,
)
.await?;
);
#[cfg(target_os = "macos")]
if let Some(s) = self.launchd_tcp_socket_name {
builder.set_launchd_socket_name(s);
}
let server = builder.build().await?;
tcp_server = Some(server);
}
@@ -1,16 +1,86 @@
use std::{io, net::SocketAddr, sync::Arc, time::Duration};
use log::{error, info};
use shadowsocks::{config::Mode, lookup_then, net::TcpListener as ShadowTcpListener, ServerAddr};
use shadowsocks::{config::Mode, net::TcpListener as ShadowTcpListener, ServerAddr};
use tokio::{net::TcpStream, time};
use crate::local::{context::ServiceContext, loadbalancing::PingBalancer};
use crate::local::{
context::ServiceContext,
loadbalancing::PingBalancer,
net::tcp::listener::create_standard_tcp_listener,
socks::config::Socks5AuthConfig,
};
#[cfg(feature = "local-socks4")]
use super::socks4::Socks4TcpHandler;
use super::socks5::{Socks5TcpHandler, Socks5UdpServer};
use crate::local::socks::config::Socks5AuthConfig;
pub struct SocksTcpServerBuilder {
context: Arc<ServiceContext>,
client_config: ServerAddr,
udp_bind_addr: ServerAddr,
balancer: PingBalancer,
mode: Mode,
socks5_auth: Arc<Socks5AuthConfig>,
#[cfg(target_os = "macos")]
launchd_socket_name: Option<String>,
}
impl SocksTcpServerBuilder {
pub(crate) fn new(
context: Arc<ServiceContext>,
client_config: ServerAddr,
udp_bind_addr: ServerAddr,
balancer: PingBalancer,
mode: Mode,
socks5_auth: Socks5AuthConfig,
) -> SocksTcpServerBuilder {
SocksTcpServerBuilder {
context,
client_config,
udp_bind_addr,
balancer,
mode,
socks5_auth: Arc::new(socks5_auth),
#[cfg(target_os = "macos")]
launchd_socket_name: None,
}
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_socket_name(&mut self, n: String) {
self.launchd_socket_name = Some(n);
}
pub async fn build(self) -> io::Result<SocksTcpServer> {
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let listener = if let Some(launchd_socket_name) = self.launchd_socket_name {
use tokio::net::TcpListener as TokioTcpListener;
use crate::net::launch_activate_socket::get_launch_activate_tcp_listener;
let std_listener = get_launch_activate_tcp_listener(&launchd_socket_name)?;
let tokio_listener = TokioTcpListener::from_std(std_listener)?;
ShadowTcpListener::from_listener(tokio_listener, self.context.accept_opts())?
} else {
create_standard_tcp_listener(&self.context, &self.client_config).await?
};
} else {
let listener = create_standard_tcp_listener(&self.context, &self.client_config).await?;
}
}
Ok(SocksTcpServer {
context: self.context,
listener,
udp_bind_addr: self.udp_bind_addr,
balancer: self.balancer,
mode: self.mode,
socks5_auth: self.socks5_auth,
})
}
}
/// SOCKS TCP server instance
pub struct SocksTcpServer {
@@ -23,35 +93,6 @@ pub struct SocksTcpServer {
}
impl SocksTcpServer {
pub(crate) async fn new(
context: Arc<ServiceContext>,
client_config: ServerAddr,
udp_bind_addr: ServerAddr,
balancer: PingBalancer,
mode: Mode,
socks5_auth: Socks5AuthConfig,
) -> io::Result<SocksTcpServer> {
let listener = match client_config {
ServerAddr::SocketAddr(ref saddr) => {
ShadowTcpListener::bind_with_opts(saddr, context.accept_opts()).await?
}
ServerAddr::DomainName(ref dname, port) => {
lookup_then!(context.context_ref(), dname, port, |addr| {
ShadowTcpListener::bind_with_opts(&addr, context.accept_opts()).await
})?
.1
}
};
Ok(SocksTcpServer {
context,
listener,
udp_bind_addr,
balancer,
mode,
socks5_auth: Arc::new(socks5_auth),
})
}
/// Get TCP server local addr
pub fn local_addr(&self) -> io::Result<SocketAddr> {
self.listener.local_addr()
@@ -1,6 +1,9 @@
//! SOCKS5 Local Server
pub use self::{tcprelay::Socks5TcpHandler, udprelay::Socks5UdpServer};
pub use self::{
tcprelay::Socks5TcpHandler,
udprelay::{Socks5UdpServer, Socks5UdpServerBuilder},
};
mod tcprelay;
mod udprelay;
@@ -12,8 +12,6 @@ use byte_string::ByteStr;
use bytes::{BufMut, BytesMut};
use log::{debug, error, info, trace};
use shadowsocks::{
lookup_then,
net::UdpSocket as ShadowUdpSocket,
relay::{
socks5::{Address, UdpAssociateHeader},
udprelay::MAXIMUM_UDP_PAYLOAD_SIZE,
@@ -26,11 +24,73 @@ use crate::{
local::{
context::ServiceContext,
loadbalancing::PingBalancer,
net::{UdpAssociationManager, UdpInboundWrite},
net::{udp::listener::create_standard_udp_listener, UdpAssociationManager, UdpInboundWrite},
},
net::utils::to_ipv4_mapped,
};
pub struct Socks5UdpServerBuilder {
context: Arc<ServiceContext>,
client_config: ServerAddr,
time_to_live: Option<Duration>,
capacity: Option<usize>,
balancer: PingBalancer,
#[cfg(target_os = "macos")]
launchd_socket_name: Option<String>,
}
impl Socks5UdpServerBuilder {
pub(crate) fn new(
context: Arc<ServiceContext>,
client_config: ServerAddr,
time_to_live: Option<Duration>,
capacity: Option<usize>,
balancer: PingBalancer,
) -> Socks5UdpServerBuilder {
Socks5UdpServerBuilder {
context,
client_config,
time_to_live,
capacity,
balancer,
#[cfg(target_os = "macos")]
launchd_socket_name: None,
}
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_socket_name(&mut self, n: String) {
self.launchd_socket_name = Some(n);
}
pub async fn build(self) -> io::Result<Socks5UdpServer> {
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let socket = if let Some(launchd_socket_name) = self.launchd_socket_name {
use tokio::net::UdpSocket as TokioUdpSocket;
use crate::net::launch_activate_socket::get_launch_activate_udp_socket;
let std_socket = get_launch_activate_udp_socket(&launchd_socket_name)?;
TokioUdpSocket::from_std(std_socket)?
} else {
create_standard_udp_listener(&self.context, &self.client_config).await?.into()
};
} else {
let socket = create_standard_udp_listener(&self.context, &self.client_config).await?.into();
}
}
Ok(Socks5UdpServer {
context: self.context,
time_to_live: self.time_to_live,
capacity: self.capacity,
listener: Arc::new(socket),
balancer: self.balancer,
})
}
}
#[derive(Clone)]
struct Socks5UdpInboundWriter {
inbound: Arc<UdpSocket>,
@@ -77,36 +137,6 @@ pub struct Socks5UdpServer {
}
impl Socks5UdpServer {
pub(crate) async fn new(
context: Arc<ServiceContext>,
client_config: &ServerAddr,
time_to_live: Option<Duration>,
capacity: Option<usize>,
balancer: PingBalancer,
) -> io::Result<Socks5UdpServer> {
let socket = match *client_config {
ServerAddr::SocketAddr(ref saddr) => {
ShadowUdpSocket::listen_with_opts(saddr, context.accept_opts()).await?
}
ServerAddr::DomainName(ref dname, port) => {
lookup_then!(context.context_ref(), dname, port, |addr| {
ShadowUdpSocket::listen_with_opts(&addr, context.accept_opts()).await
})?
.1
}
};
let socket: UdpSocket = socket.into();
let listener = Arc::new(socket);
Ok(Socks5UdpServer {
context,
time_to_live,
capacity,
listener,
balancer,
})
}
/// Server's listen address
pub fn local_addr(&self) -> io::Result<SocketAddr> {
self.listener.local_addr()
@@ -7,7 +7,10 @@ use shadowsocks::{config::Mode, relay::socks5::Address, ServerAddr};
use crate::local::{context::ServiceContext, loadbalancing::PingBalancer};
use super::{tcprelay::TunnelTcpServer, udprelay::TunnelUdpServer};
use super::{
tcprelay::{TunnelTcpServer, TunnelTcpServerBuilder},
udprelay::{TunnelUdpServer, TunnelUdpServerBuilder},
};
pub struct TunnelBuilder {
context: Arc<ServiceContext>,
@@ -18,6 +21,10 @@ pub struct TunnelBuilder {
client_addr: ServerAddr,
udp_addr: Option<ServerAddr>,
balancer: PingBalancer,
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: Option<String>,
#[cfg(target_os = "macos")]
launchd_udp_socket_name: Option<String>,
}
impl TunnelBuilder {
@@ -43,6 +50,10 @@ impl TunnelBuilder {
client_addr,
udp_addr: None,
balancer,
#[cfg(target_os = "macos")]
launchd_tcp_socket_name: None,
#[cfg(target_os = "macos")]
launchd_udp_socket_name: None,
}
}
@@ -66,32 +77,58 @@ impl TunnelBuilder {
self.udp_addr = Some(addr);
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_tcp_socket_name(&mut self, n: String) {
self.launchd_tcp_socket_name = Some(n);
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_udp_socket_name(&mut self, n: String) {
self.launchd_udp_socket_name = Some(n);
}
pub async fn build(self) -> io::Result<Tunnel> {
let mut tcp_server = None;
if self.mode.enable_tcp() {
let server = TunnelTcpServer::new(
#[allow(unused_mut)]
let mut builder = TunnelTcpServerBuilder::new(
self.context.clone(),
&self.client_addr,
self.client_addr.clone(),
self.balancer.clone(),
self.forward_addr.clone(),
)
.await?;
);
#[cfg(target_os = "macos")]
if let Some(s) = self.launchd_tcp_socket_name {
builder.set_launchd_socket_name(s);
}
let server = builder.build().await?;
tcp_server = Some(server);
}
let mut udp_server = None;
if self.mode.enable_udp() {
let udp_addr = self.udp_addr.as_ref().unwrap_or(&self.client_addr);
let udp_addr = self.udp_addr.unwrap_or(self.client_addr);
let server = TunnelUdpServer::new(
#[allow(unused_mut)]
let mut builder = TunnelUdpServerBuilder::new(
self.context.clone(),
udp_addr,
self.udp_expiry_duration,
self.udp_capacity,
self.balancer,
self.forward_addr,
)
.await?;
);
#[cfg(target_os = "macos")]
if let Some(s) = self.launchd_udp_socket_name {
builder.set_launchd_socket_name(s);
}
let server = builder.build().await?;
udp_server = Some(server);
}
@@ -3,16 +3,75 @@
use std::{io, net::SocketAddr, sync::Arc, time::Duration};
use log::{error, info, trace};
use shadowsocks::{lookup_then, net::TcpListener as ShadowTcpListener, relay::socks5::Address, ServerAddr};
use shadowsocks::{net::TcpListener as ShadowTcpListener, relay::socks5::Address, ServerAddr};
use tokio::{net::TcpStream, time};
use crate::local::{
context::ServiceContext,
loadbalancing::PingBalancer,
net::AutoProxyClientStream,
net::{tcp::listener::create_standard_tcp_listener, AutoProxyClientStream},
utils::{establish_tcp_tunnel, establish_tcp_tunnel_bypassed},
};
pub struct TunnelTcpServerBuilder {
context: Arc<ServiceContext>,
client_config: ServerAddr,
balancer: PingBalancer,
forward_addr: Address,
#[cfg(target_os = "macos")]
launchd_socket_name: Option<String>,
}
impl TunnelTcpServerBuilder {
pub(crate) fn new(
context: Arc<ServiceContext>,
client_config: ServerAddr,
balancer: PingBalancer,
forward_addr: Address,
) -> TunnelTcpServerBuilder {
TunnelTcpServerBuilder {
context,
client_config,
balancer,
forward_addr,
#[cfg(target_os = "macos")]
launchd_socket_name: None,
}
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_socket_name(&mut self, n: String) {
self.launchd_socket_name = Some(n);
}
pub async fn build(self) -> io::Result<TunnelTcpServer> {
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let listener = if let Some(launchd_socket_name) = self.launchd_socket_name {
use tokio::net::TcpListener as TokioTcpListener;
use crate::net::launch_activate_socket::get_launch_activate_tcp_listener;
let std_listener = get_launch_activate_tcp_listener(&launchd_socket_name)?;
let tokio_listener = TokioTcpListener::from_std(std_listener)?;
ShadowTcpListener::from_listener(tokio_listener, self.context.accept_opts())?
} else {
create_standard_tcp_listener(&self.context, &self.client_config).await?
};
} else {
let listener = create_standard_tcp_listener(&self.context, &self.client_config).await?;
}
}
Ok(TunnelTcpServer {
context: self.context,
listener,
balancer: self.balancer,
forward_addr: self.forward_addr,
})
}
}
/// TCP Tunnel instance
pub struct TunnelTcpServer {
context: Arc<ServiceContext>,
@@ -22,32 +81,6 @@ pub struct TunnelTcpServer {
}
impl TunnelTcpServer {
pub(crate) async fn new(
context: Arc<ServiceContext>,
client_config: &ServerAddr,
balancer: PingBalancer,
forward_addr: Address,
) -> io::Result<TunnelTcpServer> {
let listener = match *client_config {
ServerAddr::SocketAddr(ref saddr) => {
ShadowTcpListener::bind_with_opts(saddr, context.accept_opts()).await?
}
ServerAddr::DomainName(ref dname, port) => {
lookup_then!(context.context_ref(), dname, port, |addr| {
ShadowTcpListener::bind_with_opts(&addr, context.accept_opts()).await
})?
.1
}
};
Ok(TunnelTcpServer {
context,
listener,
balancer,
forward_addr,
})
}
/// Server's local address
pub fn local_addr(&self) -> io::Result<SocketAddr> {
self.listener.local_addr()
@@ -5,8 +5,6 @@ use std::{io, net::SocketAddr, sync::Arc, time::Duration};
use async_trait::async_trait;
use log::{debug, error, info};
use shadowsocks::{
lookup_then,
net::UdpSocket as ShadowUdpSocket,
relay::{socks5::Address, udprelay::MAXIMUM_UDP_PAYLOAD_SIZE},
ServerAddr,
};
@@ -15,9 +13,75 @@ use tokio::{net::UdpSocket, time};
use crate::local::{
context::ServiceContext,
loadbalancing::PingBalancer,
net::{UdpAssociationManager, UdpInboundWrite},
net::{udp::listener::create_standard_udp_listener, UdpAssociationManager, UdpInboundWrite},
};
pub struct TunnelUdpServerBuilder {
context: Arc<ServiceContext>,
client_config: ServerAddr,
time_to_live: Option<Duration>,
capacity: Option<usize>,
balancer: PingBalancer,
forward_addr: Address,
#[cfg(target_os = "macos")]
launchd_socket_name: Option<String>,
}
impl TunnelUdpServerBuilder {
pub(crate) fn new(
context: Arc<ServiceContext>,
client_config: ServerAddr,
time_to_live: Option<Duration>,
capacity: Option<usize>,
balancer: PingBalancer,
forward_addr: Address,
) -> TunnelUdpServerBuilder {
TunnelUdpServerBuilder {
context,
client_config,
time_to_live,
capacity,
balancer,
forward_addr,
#[cfg(target_os = "macos")]
launchd_socket_name: None,
}
}
/// macOS launchd activate socket
#[cfg(target_os = "macos")]
pub fn set_launchd_socket_name(&mut self, n: String) {
self.launchd_socket_name = Some(n);
}
pub async fn build(self) -> io::Result<TunnelUdpServer> {
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let socket = if let Some(launchd_socket_name) = self.launchd_socket_name {
use tokio::net::UdpSocket as TokioUdpSocket;
use crate::net::launch_activate_socket::get_launch_activate_udp_socket;
let std_socket = get_launch_activate_udp_socket(&launchd_socket_name)?;
TokioUdpSocket::from_std(std_socket)?
} else {
create_standard_udp_listener(&self.context, &self.client_config).await?.into()
};
} else {
let socket = create_standard_udp_listener(&self.context, &self.client_config).await?.into();
}
}
Ok(TunnelUdpServer {
context: self.context,
time_to_live: self.time_to_live,
capacity: self.capacity,
listener: Arc::new(socket),
balancer: self.balancer,
forward_addr: self.forward_addr,
})
}
}
#[derive(Clone)]
struct TunnelUdpInboundWriter {
inbound: Arc<UdpSocket>,
@@ -40,38 +104,6 @@ pub struct TunnelUdpServer {
}
impl TunnelUdpServer {
pub(crate) async fn new(
context: Arc<ServiceContext>,
client_config: &ServerAddr,
time_to_live: Option<Duration>,
capacity: Option<usize>,
balancer: PingBalancer,
forward_addr: Address,
) -> io::Result<TunnelUdpServer> {
let socket = match client_config {
ServerAddr::SocketAddr(ref saddr) => {
ShadowUdpSocket::listen_with_opts(saddr, context.accept_opts()).await?
}
ServerAddr::DomainName(ref dname, port) => {
lookup_then!(context.context_ref(), dname, *port, |addr| {
ShadowUdpSocket::listen_with_opts(&addr, context.accept_opts()).await
})?
.1
}
};
let socket: UdpSocket = socket.into();
let listener = Arc::new(socket);
Ok(TunnelUdpServer {
context,
time_to_live,
capacity,
listener,
balancer,
forward_addr,
})
}
/// Get server's local address
pub fn local_addr(&self) -> io::Result<SocketAddr> {
self.listener.local_addr()
@@ -0,0 +1,28 @@
//! macOS launch activate socket
//!
//! <https://developer.apple.com/documentation/xpc/1505523-launch_activate_socket>
//! <https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html>
use std::{
io,
net::{TcpListener, UdpSocket},
os::unix::io::FromRawFd,
};
use log::debug;
use crate::sys::get_launch_activate_socket;
/// Get a macOS launch active socket as a `TcpListener`
pub fn get_launch_activate_tcp_listener(name: &str) -> io::Result<TcpListener> {
let fd = get_launch_activate_socket(name)?;
debug!("created TCP listener from launch activate socket {}", fd);
Ok(unsafe { TcpListener::from_raw_fd(fd) })
}
/// Get a macOS launch activate socket as a `UdpSocket`
pub fn get_launch_activate_udp_socket(name: &str) -> io::Result<UdpSocket> {
let fd = get_launch_activate_socket(name)?;
debug!("created UDP socket from launch activate socket {}", fd);
Ok(unsafe { UdpSocket::from_raw_fd(fd) })
}
@@ -3,6 +3,8 @@
pub use self::{flow::FlowStat, mon_socket::MonProxySocket, mon_stream::MonProxyStream};
pub mod flow;
#[cfg(target_os = "macos")]
pub mod launch_activate_socket;
pub mod mon_socket;
pub mod mon_stream;
pub mod packet_window;
@@ -0,0 +1,85 @@
//! macOS specific APIs
use std::{ffi::CString, io, os::fd::RawFd, ptr};
use log::error;
extern "C" {
/// https://developer.apple.com/documentation/xpc/1505523-launch_activate_socket
fn launch_activate_socket(
name: *const libc::c_char,
fds: *mut *mut libc::c_int,
cnt: *mut libc::size_t,
) -> libc::c_int;
}
pub fn get_launch_activate_socket(name: &str) -> io::Result<RawFd> {
let mut fds: *mut libc::c_int = ptr::null_mut();
let mut cnt: libc::size_t = 0;
let cname = match CString::new(name) {
Ok(n) => n,
Err(..) => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("activate socket name \"{}\" contains NUL bytes", name),
));
}
};
unsafe {
let ret = launch_activate_socket(cname.as_ptr(), &mut fds as *mut _, &mut cnt as *mut _);
if ret != 0 {
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::ENOENT) => {
error!("activate socket name \"{}\" doesn't exist, error: {}", name, err);
}
Some(libc::ESRCH) => {
error!("current process is not managed by launchd, error: {}", err);
}
Some(libc::EALREADY) => {
error!(
"activate socket name \"{}\" has already been activated, error: {}",
name, err
);
}
_ => {}
}
return Err(err);
}
}
let result = if cnt == 0 {
Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("launch socket with name \"{}\" doesn't exist", name),
))
} else if cnt > 1 {
for idx in 0..cnt {
unsafe {
let fd = *(fds.offset(idx as isize));
let _ = libc::close(fd);
}
}
Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"launch socket with name \"{}\" should be unique, but found {}",
name, cnt
),
))
} else {
// Take fds[0] as the result
let fd = unsafe { *fds };
Ok(fd as RawFd)
};
if !fds.is_null() {
unsafe { libc::free(fds as *mut _) };
}
result
}
@@ -1,5 +1,14 @@
use std::io;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(target_os = "macos")] {
mod macos;
pub use self::macos::*;
}
}
#[allow(dead_code)]
#[cfg(not(target_os = "android"))]
pub fn set_nofile(nofile: u64) -> io::Result<()> {
+9 -3
View File
@@ -166,11 +166,17 @@ impl TcpListener {
}
/// Create a `TcpListener` from tokio's `TcpListener`
pub fn from_listener(listener: TokioTcpListener, accept_opts: AcceptOpts) -> TcpListener {
TcpListener {
pub fn from_listener(listener: TokioTcpListener, accept_opts: AcceptOpts) -> io::Result<TcpListener> {
// Enable TFO if supported
// macos requires TCP_FASTOPEN to be set after listen(), but other platform doesn't have this constraint
if accept_opts.tcp.fastopen {
set_tcp_fastopen(&listener)?;
}
Ok(TcpListener {
inner: listener,
accept_opts,
}
})
}
/// Polls to accept a new incoming connection to this listener.