Files
Archive/shadowsocks-rust/crates/shadowsocks-service/src/config.rs
2025-03-13 19:35:42 +01:00

3224 lines
121 KiB
Rust

//! This is a mod for storing and parsing configuration
//!
//! According to shadowsocks' official documentation, the standard configuration
//! file should be in JSON format:
//!
//! ```ignore
//! {
//! "server": "127.0.0.1",
//! "server_port": 1080,
//! "local_port": 8388,
//! "password": "the-password",
//! "timeout": 300,
//! "method": "aes-256-cfb",
//! "local_address": "127.0.0.1"
//! }
//! ```
//!
//! But this configuration is not for using multiple shadowsocks server, so we
//! introduce an extended configuration file format:
//!
//! ```ignore
//! {
//! "servers": [
//! {
//! "server": "127.0.0.1",
//! "server_port": 1080,
//! "password": "hellofuck",
//! "method": "bf-cfb"
//! },
//! {
//! "server": "127.0.0.1",
//! "server_port": 1081,
//! "password": "hellofuck",
//! "method": "aes-128-cfb"
//! }
//! ],
//! "local_port": 8388,
//! "local_address": "127.0.0.1"
//! }
//! ```
//!
//! These defined server will be used with a load balancing algorithm.
use std::{
borrow::Cow,
convert::{From, Infallible},
default::Default,
env,
fmt::{self, Debug, Display, Formatter},
fs::OpenOptions,
io::Read,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
option::Option,
path::{Path, PathBuf},
str::FromStr,
string::ToString,
time::Duration,
};
use cfg_if::cfg_if;
#[cfg(feature = "hickory-dns")]
use hickory_resolver::config::{NameServerConfig, ResolverConfig};
#[cfg(feature = "local-tun")]
use ipnet::IpNet;
#[cfg(feature = "local-fake-dns")]
use ipnet::{Ipv4Net, Ipv6Net};
use log::warn;
use serde::{Deserialize, Serialize};
#[cfg(any(feature = "local-tunnel", feature = "local-dns"))]
use shadowsocks::relay::socks5::Address;
use shadowsocks::{
config::{
ManagerAddr, Mode, ReplayAttackPolicy, ServerAddr, ServerConfig, ServerSource, ServerUser, ServerUserManager,
ServerWeight,
},
crypto::CipherKind,
plugin::PluginConfig,
};
use crate::acl::AccessControl;
#[cfg(feature = "local-dns")]
use crate::local::dns::NameServerAddr;
#[cfg(feature = "local")]
use crate::local::socks::config::Socks5AuthConfig;
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum SSDnsConfig {
Simple(String),
#[cfg(feature = "hickory-dns")]
HickoryDns(ResolverConfig),
}
#[derive(Serialize, Deserialize, Debug, Default)]
struct SSSecurityConfig {
#[serde(skip_serializing_if = "Option::is_none")]
replay_attack: Option<SSSecurityReplayAttackConfig>,
}
#[derive(Serialize, Deserialize, Debug, Default)]
struct SSSecurityReplayAttackConfig {
#[serde(skip_serializing_if = "Option::is_none")]
policy: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Default)]
struct SSBalancerConfig {
#[serde(skip_serializing_if = "Option::is_none")]
max_server_rtt: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
check_interval: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
check_best_interval: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug, Default)]
struct SSConfig {
#[serde(skip_serializing_if = "Option::is_none")]
server: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
server_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
local_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
local_port: Option<u16>,
/// macOS launch activate socket for local_port
#[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>,
#[serde(skip_serializing_if = "Option::is_none")]
protocol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
manager_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
manager_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
plugin: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
plugin_opts: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
plugin_args: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
plugin_mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
timeout: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
udp_timeout: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
udp_max_associations: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
udp_mtu: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none", alias = "shadowsocks")]
servers: Option<Vec<SSServerExtConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
locals: Option<Vec<SSLocalExtConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
dns: Option<SSDnsConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
dns_cache_size: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
no_delay: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
keep_alive: Option<u64>,
#[cfg(all(unix, not(target_os = "android")))]
#[serde(skip_serializing_if = "Option::is_none")]
nofile: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
ipv6_first: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
ipv6_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
fast_open: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
mptcp: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg(any(target_os = "linux", target_os = "android"))]
outbound_fwmark: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg(target_os = "freebsd")]
outbound_user_cookie: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
outbound_bind_addr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
outbound_bind_interface: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
outbound_udp_allow_fragmentation: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
security: Option<SSSecurityConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
balancer: Option<SSBalancerConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
acl: Option<String>,
#[cfg(feature = "local-online-config")]
#[serde(skip_serializing_if = "Option::is_none")]
version: Option<u32>,
#[cfg(feature = "local-online-config")]
#[serde(skip_serializing_if = "Option::is_none")]
online_config: Option<SSOnlineConfig>,
}
#[derive(Serialize, Deserialize, Debug, Default)]
struct SSLocalExtConfig {
#[serde(skip_serializing_if = "Option::is_none")]
local_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
local_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
disabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
local_udp_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
local_udp_port: Option<u16>,
#[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")]
tcp_redir: Option<String>,
/// UDP Transparent Proxy type
#[cfg(feature = "local-redir")]
#[serde(skip_serializing_if = "Option::is_none")]
udp_redir: Option<String>,
/// Local DNS's address
///
/// Sending DNS query directly to this address
#[cfg(feature = "local-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
local_dns_address: Option<String>,
#[cfg(feature = "local-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
local_dns_port: Option<u16>,
/// Remote DNS's address
///
/// Sending DNS query through proxy to this address
#[cfg(feature = "local-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
remote_dns_address: Option<String>,
#[cfg(feature = "local-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
remote_dns_port: Option<u16>,
#[cfg(feature = "local-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
client_cache_size: Option<usize>,
/// Tunnel
#[cfg(feature = "local-tunnel")]
#[serde(skip_serializing_if = "Option::is_none")]
forward_address: Option<String>,
#[cfg(feature = "local-tunnel")]
#[serde(skip_serializing_if = "Option::is_none")]
forward_port: Option<u16>,
/// Tun
#[cfg(feature = "local-tun")]
#[serde(skip_serializing_if = "Option::is_none")]
tun_interface_name: Option<String>,
#[cfg(feature = "local-tun")]
#[serde(skip_serializing_if = "Option::is_none")]
tun_interface_address: Option<String>,
#[cfg(feature = "local-tun")]
#[serde(skip_serializing_if = "Option::is_none")]
tun_interface_destination: Option<String>,
#[cfg(all(feature = "local-tun", unix))]
#[serde(skip_serializing_if = "Option::is_none")]
tun_device_fd_from_path: Option<String>,
/// SOCKS5
#[cfg(feature = "local")]
#[serde(skip_serializing_if = "Option::is_none")]
socks5_auth_config_path: Option<String>,
/// Fake DNS
#[cfg(feature = "local-fake-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fake_dns_record_expire_duration: Option<u64>,
#[cfg(feature = "local-fake-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fake_dns_ipv4_network: Option<String>,
#[cfg(feature = "local-fake-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fake_dns_ipv6_network: Option<String>,
#[cfg(feature = "local-fake-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fake_dns_database_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
acl: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
struct SSServerUserConfig {
name: String,
password: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct SSServerExtConfig {
// SIP008 https://github.com/shadowsocks/shadowsocks-org/issues/89
//
// `address` and `port` are non-standard field name only for shadowsocks-rust
#[serde(alias = "address")]
server: String,
#[serde(alias = "port")]
server_port: u16,
#[serde(skip_serializing_if = "Option::is_none")]
password: Option<String>,
method: String,
#[serde(skip_serializing_if = "Option::is_none")]
users: Option<Vec<SSServerUserConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
disabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
plugin: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
plugin_opts: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
plugin_args: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
plugin_mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
timeout: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", alias = "name")]
remarks: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
tcp_weight: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
udp_weight: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
acl: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg(any(target_os = "linux", target_os = "android"))]
outbound_fwmark: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
outbound_bind_addr: Option<IpAddr>,
#[serde(skip_serializing_if = "Option::is_none")]
outbound_bind_interface: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
outbound_udp_allow_fragmentation: Option<bool>,
}
#[cfg(feature = "local-online-config")]
#[derive(Serialize, Deserialize, Debug, Default)]
struct SSOnlineConfig {
config_url: String,
update_interval: Option<u64>,
}
/// Server config type
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConfigType {
/// Config for local
Local,
/// Config for server
Server,
/// Config for Manager server
Manager,
/// Config for online config (SIP008)
/// https://shadowsocks.org/doc/sip008.html
#[cfg(feature = "local-online-config")]
OnlineConfig,
}
impl ConfigType {
/// Check if it is local server type
pub fn is_local(self) -> bool {
self == ConfigType::Local
}
/// Check if it is remote server type
pub fn is_server(self) -> bool {
self == ConfigType::Server
}
/// Check if it is manager server type
pub fn is_manager(self) -> bool {
self == ConfigType::Manager
}
/// Check if it is online config type (SIP008)
#[cfg(feature = "local-online-config")]
pub fn is_online_config(self) -> bool {
self == ConfigType::OnlineConfig
}
}
cfg_if! {
if #[cfg(feature = "local-redir")] {
/// Transparent Proxy type
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RedirType {
/// For not supported platforms
NotSupported,
/// For Linux-like systems' Netfilter `REDIRECT`. Only for TCP connections.
///
/// This is supported from Linux 2.4 Kernel. Document: <https://www.netfilter.org/documentation/index.html#documentation-howto>
///
/// NOTE: Filter rule `REDIRECT` can only be applied to TCP connections.
#[cfg(any(target_os = "linux", target_os = "android"))]
Redirect,
/// For Linux-like systems' Netfilter TPROXY rule.
///
/// NOTE: Filter rule `TPROXY` can be applied to TCP and UDP connections.
#[cfg(any(target_os = "linux", target_os = "android"))]
TProxy,
/// Packet Filter (pf)
///
/// Supported by OpenBSD 3.0+, FreeBSD 5.3+, NetBSD 3.0+, Solaris 11.3+, macOS 10.7+, iOS, QNX
///
/// Document: <https://www.freebsd.org/doc/handbook/firewalls-pf.html>
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "macos",
target_os = "ios"
))]
PacketFilter,
/// IPFW
///
/// Supported by FreeBSD, macOS 10.6- (Have been removed completely on macOS 10.10)
///
/// Document: https://www.freebsd.org/doc/handbook/firewalls-ipfw.html
#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "ios"))]
IpFirewall,
}
impl RedirType {
cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "android"))] {
/// Default TCP transparent proxy solution on this platform
pub const fn tcp_default() -> RedirType {
RedirType::Redirect
}
/// Available TCP transparent proxy types
#[doc(hidden)]
pub fn tcp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[RedirType::Redirect.name(), RedirType::TProxy.name()];
AVAILABLE_TYPES
}
/// Default UDP transparent proxy solution on this platform
pub const fn udp_default() -> RedirType {
RedirType::TProxy
}
/// Available UDP transparent proxy types
#[doc(hidden)]
pub fn udp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[RedirType::TProxy.name()];
AVAILABLE_TYPES
}
} else if #[cfg(any(target_os = "freebsd"))] {
/// Default TCP transparent proxy solution on this platform
pub fn tcp_default() -> RedirType {
RedirType::PacketFilter
}
/// Available TCP transparent proxy types
#[doc(hidden)]
pub fn tcp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[RedirType::PacketFilter.name(), RedirType::IpFirewall.name()];
AVAILABLE_TYPES
}
/// Default UDP transparent proxy solution on this platform
pub fn udp_default() -> RedirType {
RedirType::PacketFilter
}
/// Available UDP transparent proxy types
#[doc(hidden)]
pub const fn udp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[RedirType::PacketFilter.name(), RedirType::IpFirewall.name()];
AVAILABLE_TYPES
}
} else if #[cfg(target_os = "openbsd")] {
/// Default TCP transparent proxy solution on this platform
pub fn tcp_default() -> RedirType {
RedirType::PacketFilter
}
/// Available TCP transparent proxy types
#[doc(hidden)]
pub fn tcp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[RedirType::PacketFilter.name()];
AVAILABLE_TYPES
}
/// Default UDP transparent proxy solution on this platform
pub fn udp_default() -> RedirType {
RedirType::PacketFilter
}
/// Available UDP transparent proxy types
#[doc(hidden)]
pub const fn udp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[RedirType::PacketFilter.name()];
AVAILABLE_TYPES
}
} else if #[cfg(any(target_os = "macos", target_os = "ios"))] {
/// Default TCP transparent proxy solution on this platform
pub fn tcp_default() -> RedirType {
RedirType::PacketFilter
}
/// Available TCP transparent proxy types
#[doc(hidden)]
pub const fn tcp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[RedirType::PacketFilter.name(), RedirType::IpFirewall.name()];
AVAILABLE_TYPES
}
/// Default UDP transparent proxy solution on this platform
pub fn udp_default() -> RedirType {
RedirType::PacketFilter
}
/// Available UDP transparent proxy types
#[doc(hidden)]
pub const fn udp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[RedirType::PacketFilter.name()];
AVAILABLE_TYPES
}
} else {
/// Default TCP transparent proxy solution on this platform
pub fn tcp_default() -> RedirType {
RedirType::NotSupported
}
/// Available TCP transparent proxy types
#[doc(hidden)]
pub const fn tcp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[];
AVAILABLE_TYPES
}
/// Default UDP transparent proxy solution on this platform
pub fn udp_default() -> RedirType {
RedirType::NotSupported
}
/// Available UDP transparent proxy types
#[doc(hidden)]
pub const fn udp_available_types() -> &'static [&'static str] {
const AVAILABLE_TYPES: &[&str] = &[];
AVAILABLE_TYPES
}
}
}
/// Check if transparent proxy is supported on this platform
pub fn is_supported(self) -> bool {
self != RedirType::NotSupported
}
/// Name of redirect type (transparent proxy type)
pub const fn name(self) -> &'static str {
match self {
// Dummy, shouldn't be used in any useful situations
RedirType::NotSupported => "not_supported",
#[cfg(any(target_os = "linux", target_os = "android"))]
RedirType::Redirect => "redirect",
#[cfg(any(target_os = "linux", target_os = "android"))]
RedirType::TProxy => "tproxy",
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "macos",
target_os = "ios"
))]
RedirType::PacketFilter => "pf",
#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "ios"))]
RedirType::IpFirewall => "ipfw",
}
}
}
impl Display for RedirType {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(self.name())
}
}
/// Error type for `RedirType`'s `FromStr::Err`
#[derive(Debug)]
pub struct InvalidRedirType;
impl Display for InvalidRedirType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid RedirType")
}
}
impl FromStr for RedirType {
type Err = InvalidRedirType;
fn from_str(s: &str) -> Result<RedirType, InvalidRedirType> {
match s {
#[cfg(any(target_os = "linux", target_os = "android"))]
"redirect" => Ok(RedirType::Redirect),
#[cfg(any(target_os = "linux", target_os = "android"))]
"tproxy" => Ok(RedirType::TProxy),
#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "macos",
target_os = "ios",
))]
"pf" => Ok(RedirType::PacketFilter),
#[cfg(any(
target_os = "freebsd",
target_os = "macos",
target_os = "ios",
))]
"ipfw" => Ok(RedirType::IpFirewall),
_ => Err(InvalidRedirType),
}
}
}
}
}
/// Host for servers to bind
///
/// Servers will bind to a port of this host
#[derive(Clone, Debug)]
pub enum ManagerServerHost {
/// Domain name
Domain(String),
/// IP address
Ip(IpAddr),
}
impl Default for ManagerServerHost {
fn default() -> ManagerServerHost {
ManagerServerHost::Ip(Ipv4Addr::UNSPECIFIED.into())
}
}
impl FromStr for ManagerServerHost {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<IpAddr>() {
Ok(s) => Ok(ManagerServerHost::Ip(s)),
Err(..) => Ok(ManagerServerHost::Domain(s.to_owned())),
}
}
}
/// Mode of Manager's server
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub enum ManagerServerMode {
/// Run shadowsocks server in the same process of manager
#[default]
Builtin,
/// Run shadowsocks server in standalone (process) mode
#[cfg(unix)]
Standalone,
}
/// Parsing ManagerServerMode error
#[derive(Debug, Clone, Copy)]
pub struct ManagerServerModeError;
impl Display for ManagerServerModeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("invalid ManagerServerMode")
}
}
impl FromStr for ManagerServerMode {
type Err = ManagerServerModeError;
fn from_str(s: &str) -> Result<ManagerServerMode, Self::Err> {
match s {
"builtin" => Ok(ManagerServerMode::Builtin),
#[cfg(unix)]
"standalone" => Ok(ManagerServerMode::Standalone),
_ => Err(ManagerServerModeError),
}
}
}
impl Display for ManagerServerMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ManagerServerMode::Builtin => f.write_str("builtin"),
#[cfg(unix)]
ManagerServerMode::Standalone => f.write_str("standalone"),
}
}
}
/// Configuration for Manager
#[derive(Clone, Debug)]
pub struct ManagerConfig {
/// Address of `ss-manager`. Send servers' statistic data to the manager server
pub addr: ManagerAddr,
/// Manager's default method
pub method: Option<CipherKind>,
/// Manager's default plugin
pub plugin: Option<PluginConfig>,
/// Timeout for TCP connections, setting to manager's created servers
pub timeout: Option<Duration>,
/// IP/Host for servers to bind (inbound)
///
/// Note: Outbound address is defined in Config.local_addr
pub server_host: ManagerServerHost,
/// Server's mode
pub mode: Mode,
/// Server's running mode
pub server_mode: ManagerServerMode,
/// Server's command if running in Standalone mode
#[cfg(unix)]
pub server_program: String,
/// Server's working directory if running in Standalone mode
#[cfg(unix)]
pub server_working_directory: PathBuf,
}
impl ManagerConfig {
/// Create a ManagerConfig with default options
pub fn new(addr: ManagerAddr) -> ManagerConfig {
ManagerConfig {
addr,
method: None,
plugin: None,
timeout: None,
server_host: ManagerServerHost::default(),
mode: Mode::TcpOnly,
server_mode: ManagerServerMode::Builtin,
#[cfg(unix)]
server_program: "ssserver".to_owned(),
#[cfg(unix)]
server_working_directory: match std::env::current_dir() {
Ok(d) => d,
Err(..) => "/tmp/shadowsocks-manager".into(),
},
}
}
}
/// Protocol of local server
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub enum ProtocolType {
#[default]
Socks,
#[cfg(feature = "local-http")]
Http,
#[cfg(feature = "local-tunnel")]
Tunnel,
#[cfg(feature = "local-redir")]
Redir,
#[cfg(feature = "local-dns")]
Dns,
#[cfg(feature = "local-tun")]
Tun,
#[cfg(feature = "local-fake-dns")]
FakeDns,
}
impl ProtocolType {
/// As string representation
pub fn as_str(&self) -> &'static str {
match *self {
ProtocolType::Socks => "socks",
#[cfg(feature = "local-http")]
ProtocolType::Http => "http",
#[cfg(feature = "local-tunnel")]
ProtocolType::Tunnel => "tunnel",
#[cfg(feature = "local-redir")]
ProtocolType::Redir => "redir",
#[cfg(feature = "local-dns")]
ProtocolType::Dns => "dns",
#[cfg(feature = "local-tun")]
ProtocolType::Tun => "tun",
#[cfg(feature = "local-fake-dns")]
ProtocolType::FakeDns => "fake-dns",
}
}
/// Get all available protocols
pub fn available_protocols() -> &'static [&'static str] {
&[
"socks",
#[cfg(feature = "local-http")]
"http",
#[cfg(feature = "local-tunnel")]
"tunnel",
#[cfg(feature = "local-redir")]
"redir",
#[cfg(feature = "local-dns")]
"dns",
#[cfg(feature = "local-tun")]
"tun",
#[cfg(feature = "local-fake-dns")]
"fake-dns",
]
}
}
/// Error while parsing `ProtocolType` from string
#[derive(Debug)]
pub struct ProtocolTypeError;
impl Display for ProtocolTypeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("invalid ProtocolType")
}
}
impl FromStr for ProtocolType {
type Err = ProtocolTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"socks" => Ok(ProtocolType::Socks),
#[cfg(feature = "local-http")]
"http" => Ok(ProtocolType::Http),
#[cfg(feature = "local-tunnel")]
"tunnel" => Ok(ProtocolType::Tunnel),
#[cfg(feature = "local-redir")]
"redir" => Ok(ProtocolType::Redir),
#[cfg(feature = "local-dns")]
"dns" => Ok(ProtocolType::Dns),
#[cfg(feature = "local-tun")]
"tun" => Ok(ProtocolType::Tun),
#[cfg(feature = "local-fake-dns")]
"fake-dns" => Ok(ProtocolType::FakeDns),
_ => Err(ProtocolTypeError),
}
}
}
/// Local server configuration
#[derive(Clone, Debug)]
pub struct LocalConfig {
/// Listen address for local servers
pub addr: Option<ServerAddr>,
/// Local Protocol
pub protocol: ProtocolType,
/// Mode
/// Uses global `mode` if not specified
pub mode: Mode,
/// UDP server bind address. Uses `addr` if not specified
///
/// Resolving Android's issue: [shadowsocks/shadowsocks-android#2571](https://github.com/shadowsocks/shadowsocks-android/issues/2571)
pub udp_addr: Option<ServerAddr>,
/// UDP Associate address. Uses `udp_addr` if not specified
pub udp_associate_addr: Option<ServerAddr>,
/// Destination address for tunnel
#[cfg(feature = "local-tunnel")]
pub forward_addr: Option<Address>,
/// TCP Transparent Proxy type
#[cfg(feature = "local-redir")]
pub tcp_redir: RedirType,
/// UDP Transparent Proxy type
#[cfg(feature = "local-redir")]
pub udp_redir: RedirType,
/// Local DNS's address
///
/// Sending DNS query directly to this address
#[cfg(feature = "local-dns")]
pub local_dns_addr: Option<NameServerAddr>,
/// Remote DNS's address
///
/// Sending DNS query through proxy to this address
#[cfg(feature = "local-dns")]
pub remote_dns_addr: Option<Address>,
// client cache size
// if a lot of `create connection` observed in log,
// increase the size
#[cfg(feature = "local-dns")]
pub client_cache_size: Option<usize>,
/// Tun interface's name
///
/// Linux: eth0, eth1, ...
/// macOS: utun0, utun1, ...
#[cfg(feature = "local-tun")]
pub tun_interface_name: Option<String>,
/// Tun interface's address and netmask
#[cfg(feature = "local-tun")]
pub tun_interface_address: Option<IpNet>,
/// Tun interface's destination address and netmask
#[cfg(feature = "local-tun")]
pub tun_interface_destination: Option<IpNet>,
/// Tun interface's file descriptor
#[cfg(all(feature = "local-tun", unix))]
pub tun_device_fd: Option<std::os::unix::io::RawFd>,
/// Tun interface's file descriptor read from this Unix Domain Socket
#[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,
/// SOCKS5 Authentication configuration
#[cfg(feature = "local")]
pub socks5_auth: Socks5AuthConfig,
/// Fake DNS record expire seconds
#[cfg(feature = "local-fake-dns")]
pub fake_dns_record_expire_duration: Option<Duration>,
/// Fake DNS IPv4 allocation space
#[cfg(feature = "local-fake-dns")]
pub fake_dns_ipv4_network: Option<Ipv4Net>,
/// Fake DNS IPv6 allocation space
#[cfg(feature = "local-fake-dns")]
pub fake_dns_ipv6_network: Option<Ipv6Net>,
/// Fake DNS storage database path
#[cfg(feature = "local-fake-dns")]
pub fake_dns_database_path: Option<PathBuf>,
}
impl LocalConfig {
/// Create a new `LocalConfig`
pub fn new(protocol: ProtocolType) -> LocalConfig {
// DNS server runs in `TcpAndUdp` mode by default to maintain backwards compatibility
// see https://github.com/shadowsocks/shadowsocks-rust/issues/1281
let mode = match protocol {
#[cfg(feature = "local-dns")]
ProtocolType::Dns => Mode::TcpAndUdp,
_ => Mode::TcpOnly,
};
LocalConfig {
addr: None,
protocol,
mode,
udp_addr: None,
udp_associate_addr: None,
#[cfg(feature = "local-tunnel")]
forward_addr: None,
#[cfg(feature = "local-redir")]
tcp_redir: RedirType::tcp_default(),
#[cfg(feature = "local-redir")]
udp_redir: RedirType::udp_default(),
#[cfg(feature = "local-dns")]
local_dns_addr: None,
#[cfg(feature = "local-dns")]
remote_dns_addr: None,
#[cfg(feature = "local-dns")]
client_cache_size: None,
#[cfg(feature = "local-tun")]
tun_interface_name: None,
#[cfg(feature = "local-tun")]
tun_interface_address: None,
#[cfg(feature = "local-tun")]
tun_interface_destination: None,
#[cfg(all(feature = "local-tun", unix))]
tun_device_fd: None,
#[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")]
socks5_auth: Socks5AuthConfig::default(),
#[cfg(feature = "local-fake-dns")]
fake_dns_record_expire_duration: None,
#[cfg(feature = "local-fake-dns")]
fake_dns_ipv4_network: None,
#[cfg(feature = "local-fake-dns")]
fake_dns_ipv6_network: None,
#[cfg(feature = "local-fake-dns")]
fake_dns_database_path: None,
}
}
/// Create a new `LocalConfig` with listen address
pub fn new_with_addr(addr: ServerAddr, protocol: ProtocolType) -> LocalConfig {
let mut config = LocalConfig::new(protocol);
config.addr = Some(addr);
config
}
fn check_integrity(&self) -> Result<(), Error> {
match self.protocol {
#[cfg(feature = "local-tun")]
ProtocolType::Tun => {}
_ => {
if self.addr.is_none() {
let err = Error::new(ErrorKind::MissingField, "missing `addr` in configuration", None);
return Err(err);
}
}
}
match self.protocol {
#[cfg(feature = "local-dns")]
ProtocolType::Dns => {
if self.local_dns_addr.is_none() || self.remote_dns_addr.is_none() {
let err = Error::new(
ErrorKind::MissingField,
"missing `local_dns_addr` or `remote_dns_addr` in configuration",
None,
);
return Err(err);
}
}
#[cfg(feature = "local-tunnel")]
ProtocolType::Tunnel => {
if self.forward_addr.is_none() {
let err = Error::new(ErrorKind::MissingField, "missing `forward_addr` in configuration", None);
return Err(err);
}
}
#[cfg(feature = "local-http")]
ProtocolType::Http => {
if !self.mode.enable_tcp() {
let err = Error::new(ErrorKind::Invalid, "TCP mode have to be enabled for http", None);
return Err(err);
}
}
_ => {}
}
Ok(())
}
// Check if it is a basic format of local
pub fn is_basic(&self) -> bool {
if self.protocol != ProtocolType::Socks || self.udp_addr.is_some() {
return false;
}
#[cfg(feature = "local-tunnel")]
if self.forward_addr.is_some() {
return false;
}
#[cfg(feature = "local-redir")]
if self.tcp_redir != RedirType::tcp_default() || self.udp_redir != RedirType::udp_default() {
return false;
}
#[cfg(feature = "local-dns")]
if self.local_dns_addr.is_some() || self.remote_dns_addr.is_some() {
return false;
}
true
}
}
#[derive(Clone, Debug, Default)]
pub enum DnsConfig {
#[default]
System,
#[cfg(feature = "hickory-dns")]
HickoryDns(ResolverConfig),
#[cfg(feature = "local-dns")]
LocalDns(NameServerAddr),
}
/// Security Config
#[derive(Clone, Debug, Default)]
pub struct SecurityConfig {
pub replay_attack: SecurityReplayAttackConfig,
}
#[derive(Clone, Debug, Default)]
pub struct SecurityReplayAttackConfig {
pub policy: ReplayAttackPolicy,
}
/// Balancer Config
#[derive(Clone, Debug, Default)]
pub struct BalancerConfig {
/// MAX rtt of servers, which is the timeout duration of each check requests
pub max_server_rtt: Option<Duration>,
/// Interval between each checking
pub check_interval: Option<Duration>,
/// Interval for checking the best server
pub check_best_interval: Option<Duration>,
}
/// Address for local to report flow statistic data
#[cfg(feature = "local-flow-stat")]
#[derive(Debug, Clone)]
pub enum LocalFlowStatAddress {
/// UNIX Domain Socket address
#[cfg(unix)]
UnixStreamPath(PathBuf),
/// TCP Stream Address
TcpStreamAddr(SocketAddr),
}
/// Server instance config
#[derive(Debug, Clone)]
pub struct ServerInstanceConfig {
/// Server's config
pub config: ServerConfig,
/// Server's private ACL, set to `None` will use the global `AccessControl`
pub acl: Option<AccessControl>,
/// Server's outbound fwmark / address / interface to support split tunnel
#[cfg(any(target_os = "linux", target_os = "android"))]
pub outbound_fwmark: Option<u32>,
pub outbound_bind_addr: Option<IpAddr>,
pub outbound_bind_interface: Option<String>,
pub outbound_udp_allow_fragmentation: Option<bool>,
}
impl ServerInstanceConfig {
/// Create with `ServerConfig`
pub fn with_server_config(config: ServerConfig) -> ServerInstanceConfig {
ServerInstanceConfig {
config,
acl: None,
#[cfg(any(target_os = "linux", target_os = "android"))]
outbound_fwmark: None,
outbound_bind_addr: None,
outbound_bind_interface: None,
outbound_udp_allow_fragmentation: None,
}
}
}
/// Local instance config
#[derive(Debug, Clone)]
pub struct LocalInstanceConfig {
/// Local server's config
pub config: LocalConfig,
/// Server's private ACL, set to `None` will use the global `AccessControl`
pub acl: Option<AccessControl>,
}
impl LocalInstanceConfig {
/// Create with `LocalConfig`
pub fn with_local_config(config: LocalConfig) -> LocalInstanceConfig {
LocalInstanceConfig { config, acl: None }
}
}
/// OnlineConfiguration (SIP008)
/// https://shadowsocks.org/doc/sip008.html
#[cfg(feature = "local-online-config")]
#[derive(Debug, Clone)]
pub struct OnlineConfig {
/// SIP008 URL
pub config_url: String,
/// Update interval, 3600s by default
pub update_interval: Option<Duration>,
}
/// Configuration
#[derive(Clone, Debug)]
pub struct Config {
/// Remote ShadowSocks server configurations
pub server: Vec<ServerInstanceConfig>,
/// Local server configuration
pub local: Vec<LocalInstanceConfig>,
/// DNS configuration, uses system-wide DNS configuration by default
///
/// Value could be a `IpAddr`, uses UDP DNS protocol with port `53`. For example: `8.8.8.8`
///
/// Also Value could be some pre-defined DNS server names:
///
/// - `google`
/// - `cloudflare`, `cloudflare_tls`, `cloudflare_https`
/// - `quad9`, `quad9_tls`
pub dns: DnsConfig,
pub dns_cache_size: Option<usize>,
/// Uses IPv6 addresses first
///
/// Set to `true` if you want to query IPv6 addresses before IPv4
pub ipv6_first: bool,
/// Set `IPV6_V6ONLY` for listener sockets
pub ipv6_only: bool,
/// Set `TCP_NODELAY` socket option
pub no_delay: bool,
/// Set `TCP_FASTOPEN` socket option
pub fast_open: bool,
/// Set TCP Keep-Alive duration, will set both `TCP_KEEPIDLE` and `TCP_KEEPINTVL`
///
/// <https://github.com/shadowsocks/shadowsocks-rust/issues/546>
///
/// If this is not set, sockets will be set with a default timeout
pub keep_alive: Option<Duration>,
/// Multipath-TCP
pub mptcp: bool,
/// `RLIMIT_NOFILE` option for *nix systems
#[cfg(all(unix, not(target_os = "android")))]
pub nofile: Option<u64>,
/// Set `SO_MARK` socket option for outbound sockets
#[cfg(any(target_os = "linux", target_os = "android"))]
pub outbound_fwmark: Option<u32>,
/// Set `SO_USER_COOKIE` socket option for outbound sockets
#[cfg(target_os = "freebsd")]
pub outbound_user_cookie: Option<u32>,
/// Set `SO_BINDTODEVICE` (Linux), `IP_BOUND_IF` (BSD), `IP_UNICAST_IF` (Windows) socket option for outbound sockets
pub outbound_bind_interface: Option<String>,
/// Outbound sockets will `bind` to this address
pub outbound_bind_addr: Option<IpAddr>,
/// Outbound UDP sockets allow IP fragmentation
pub outbound_udp_allow_fragmentation: bool,
/// Path to protect callback unix address, only for Android
#[cfg(target_os = "android")]
pub outbound_vpn_protect_path: Option<PathBuf>,
/// Set `SO_SNDBUF` for inbound sockets
pub inbound_send_buffer_size: Option<u32>,
/// Set `SO_RCVBUF` for inbound sockets
pub inbound_recv_buffer_size: Option<u32>,
/// Set `SO_SNDBUF` for outbound sockets
pub outbound_send_buffer_size: Option<u32>,
/// Set `SO_RCVBUF` for outbound sockets
pub outbound_recv_buffer_size: Option<u32>,
/// Manager's configuration
pub manager: Option<ManagerConfig>,
/// Config is for Client or Server
pub config_type: ConfigType,
/// Timeout for UDP Associations, default is 5 minutes
pub udp_timeout: Option<Duration>,
/// Maximum number of UDP Associations, default is unconfigured
pub udp_max_associations: Option<usize>,
/// Maximum Transmission Unit (MTU) size for UDP packets
/// 65535 by default. Suggestion: 1500
/// NOTE: mtu includes IP header, UDP header, UDP payload
pub udp_mtu: Option<usize>,
/// ACL configuration (Global)
///
/// Could be overwritten by servers/locals' private `acl`
pub acl: Option<AccessControl>,
/// Flow statistic report Unix socket path (only for Android)
#[cfg(feature = "local-flow-stat")]
pub local_stat_addr: Option<LocalFlowStatAddress>,
/// Replay attack policy
pub security: SecurityConfig,
/// Balancer config of local server
pub balancer: BalancerConfig,
/// Configuration file path, the actual path of the configuration.
/// This is normally for auto-reloading if implementation supports.
pub config_path: Option<PathBuf>,
/// OnlineConfiguration (SIP008)
/// https://shadowsocks.org/doc/sip008.html
#[cfg(feature = "local-online-config")]
pub online_config: Option<OnlineConfig>,
}
/// Configuration parsing error kind
#[derive(Copy, Clone, Debug)]
pub enum ErrorKind {
/// Missing required fields in JSON configuration
MissingField,
/// Missing some keys that must be provided together
Malformed,
/// Invalid value of some configuration keys
Invalid,
/// Invalid JSON
JsonParsingError,
/// `std::io::Error`
IoError,
}
/// Configuration parsing error
pub struct Error {
pub kind: ErrorKind,
pub desc: &'static str,
pub detail: Option<String>,
}
impl Error {
pub fn new(kind: ErrorKind, desc: &'static str, detail: Option<String>) -> Error {
Error { kind, desc, detail }
}
}
impl std::error::Error for Error {
fn description(&self) -> &str {
self.desc
}
}
macro_rules! impl_from {
($error:ty, $kind:expr, $desc:expr) => {
impl From<$error> for Error {
fn from(err: $error) -> Self {
Error::new($kind, $desc, Some(format!("{:?}", err)))
}
}
};
}
impl_from!(::std::io::Error, ErrorKind::IoError, "error while reading file");
impl_from!(json5::Error, ErrorKind::JsonParsingError, "json parse error");
impl Debug for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.detail {
None => write!(f, "{}", self.desc),
Some(ref det) => write!(f, "{} {}", self.desc, det),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.detail {
None => f.write_str(self.desc),
Some(ref d) => write!(f, "{}, {}", self.desc, d),
}
}
}
impl Config {
/// Creates an empty configuration
pub fn new(config_type: ConfigType) -> Config {
Config {
server: Vec::new(),
local: Vec::new(),
dns: DnsConfig::default(),
dns_cache_size: None,
ipv6_first: false,
ipv6_only: false,
no_delay: false,
fast_open: false,
keep_alive: None,
mptcp: false,
#[cfg(all(unix, not(target_os = "android")))]
nofile: None,
#[cfg(any(target_os = "linux", target_os = "android"))]
outbound_fwmark: None,
#[cfg(target_os = "freebsd")]
outbound_user_cookie: None,
outbound_bind_interface: None,
outbound_bind_addr: None,
outbound_udp_allow_fragmentation: false,
#[cfg(target_os = "android")]
outbound_vpn_protect_path: None,
inbound_send_buffer_size: None,
inbound_recv_buffer_size: None,
outbound_send_buffer_size: None,
outbound_recv_buffer_size: None,
manager: None,
config_type,
udp_timeout: None,
udp_max_associations: None,
udp_mtu: None,
acl: None,
#[cfg(feature = "local-flow-stat")]
local_stat_addr: None,
security: SecurityConfig::default(),
balancer: BalancerConfig::default(),
config_path: None,
#[cfg(feature = "local-online-config")]
online_config: None,
}
}
fn load_from_ssconfig(config: SSConfig, config_type: ConfigType) -> Result<Config, Error> {
let mut nconfig = Config::new(config_type);
// Client
//
// local_address is allowed to be NULL, which means to bind to ::1 or 127.0.0.1
//
// https://shadowsocks.org/en/config/quick-guide.html
#[inline]
fn get_local_address(local_address: Option<String>, local_port: u16, ipv6_first: bool) -> ServerAddr {
match local_address {
Some(addr) => {
match addr.parse::<IpAddr>() {
Ok(ip) => ServerAddr::from(SocketAddr::new(ip, local_port)),
Err(..) => {
// treated as domain
ServerAddr::from((addr, local_port))
}
}
}
None => {
// Implementation note: This is not implemented like libev which will choose IPv6 or IPv6 LoopBack address
// by checking all its remote servers if all of them supports IPv6.
let ip = if ipv6_first {
Ipv6Addr::LOCALHOST.into()
} else {
Ipv4Addr::LOCALHOST.into()
};
ServerAddr::from(SocketAddr::new(ip, local_port))
}
}
}
// Mode
let mut global_mode = Mode::TcpOnly;
if let Some(m) = config.mode {
match m.parse::<Mode>() {
Ok(xm) => global_mode = xm,
Err(..) => {
let e = Error::new(
ErrorKind::Malformed,
"malformed `mode`, must be one of `tcp_only`, `udp_only` and `tcp_and_udp`",
None,
);
return Err(e);
}
}
}
match config_type {
ConfigType::Local => {
// Standard config
if config.local_address.is_some() && config.local_port.unwrap_or(0) == 0 {
let err = Error::new(ErrorKind::MissingField, "missing `local_port`", None);
return Err(err);
}
if let Some(local_port) = config.local_port {
// local_port won't be 0, it was checked above
assert_ne!(local_port, 0);
let local_addr =
get_local_address(config.local_address, local_port, config.ipv6_first.unwrap_or(false));
// shadowsocks uses SOCKS5 by default
let mut local_config = LocalConfig::new(ProtocolType::Socks);
local_config.addr = Some(local_addr);
local_config.mode = global_mode;
local_config.protocol = match config.protocol {
None => ProtocolType::Socks,
Some(p) => match p.parse::<ProtocolType>() {
Ok(p) => p,
Err(..) => {
let err = Error::new(
ErrorKind::Malformed,
"`protocol` invalid",
Some(format!("unrecognized protocol {p}")),
);
return Err(err);
}
},
};
#[cfg(target_os = "macos")]
{
local_config
.launchd_tcp_socket_name
.clone_from(&config.launchd_tcp_socket_name);
local_config
.launchd_udp_socket_name
.clone_from(&config.launchd_udp_socket_name);
}
let local_instance = LocalInstanceConfig {
config: local_config,
acl: None,
};
nconfig.local.push(local_instance);
}
// Ext locals
// `locals` are only effective in local server
if let Some(locals) = config.locals {
for local in locals {
if local.disabled.unwrap_or(false) {
continue;
}
let protocol = match local.protocol {
None => ProtocolType::Socks,
Some(p) => match p.parse::<ProtocolType>() {
Ok(p) => p,
Err(..) => {
let err = Error::new(
ErrorKind::Malformed,
"`protocol` invalid",
Some(format!("unrecognized protocol {p}")),
);
return Err(err);
}
},
};
let mut local_config = LocalConfig::new(protocol);
if let Some(local_port) = local.local_port {
if local_port == 0 {
let err = Error::new(ErrorKind::Malformed, "`local_port` cannot be 0", None);
return Err(err);
}
let local_addr =
get_local_address(local.local_address, local_port, config.ipv6_first.unwrap_or(false));
local_config.addr = Some(local_addr);
} else if local.local_address.is_some() {
let err = Error::new(ErrorKind::Malformed, "missing `local_port`", None);
return Err(err);
}
if let Some(local_udp_port) = local.local_udp_port {
if local_udp_port == 0 {
let err = Error::new(ErrorKind::Malformed, "`local_udp_port` cannot be 0", None);
return Err(err);
}
let local_udp_addr = get_local_address(
local.local_udp_address,
local_udp_port,
config.ipv6_first.unwrap_or(false),
);
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,
Err(..) => {
let err = Error::new(ErrorKind::Malformed, "invalid `mode`", None);
return Err(err);
}
},
None => {
// DNS server runs in `TcpAndUdp` mode by default to maintain backwards compatibility
// see https://github.com/shadowsocks/shadowsocks-rust/issues/1281
let mode = match protocol {
#[cfg(feature = "local-dns")]
ProtocolType::Dns => Mode::TcpAndUdp,
_ => global_mode,
};
local_config.mode = mode;
}
}
#[cfg(feature = "local-tunnel")]
if let Some(forward_address) = local.forward_address {
let forward_port = match local.forward_port {
None | Some(0) => {
let err =
Error::new(ErrorKind::Malformed, "`forward_port` cannot be missing or 0", None);
return Err(err);
}
Some(p) => p,
};
local_config.forward_addr = Some(match forward_address.parse::<IpAddr>() {
Ok(ip) => Address::from(SocketAddr::new(ip, forward_port)),
Err(..) => Address::from((forward_address, forward_port)),
});
}
#[cfg(feature = "local-redir")]
if let Some(tcp_redir) = local.tcp_redir {
match tcp_redir.parse::<RedirType>() {
Ok(r) => local_config.tcp_redir = r,
Err(..) => {
let err = Error::new(ErrorKind::Malformed, "`tcp_redir` invalid", None);
return Err(err);
}
}
}
#[cfg(feature = "local-redir")]
if let Some(udp_redir) = local.udp_redir {
match udp_redir.parse::<RedirType>() {
Ok(r) => local_config.udp_redir = r,
Err(..) => {
let err = Error::new(ErrorKind::Malformed, "`udp_redir` invalid", None);
return Err(err);
}
}
}
#[cfg(feature = "local-dns")]
if let Some(local_dns_address) = local.local_dns_address {
match local_dns_address.parse::<IpAddr>() {
Ok(ip) => {
local_config.local_dns_addr = Some(NameServerAddr::SocketAddr(SocketAddr::new(
ip,
local.local_dns_port.unwrap_or(53),
)));
}
#[cfg(unix)]
Err(..) => {
local_config.local_dns_addr =
Some(NameServerAddr::UnixSocketAddr(PathBuf::from(local_dns_address)));
}
#[cfg(not(unix))]
Err(..) => {
let err = Error::new(ErrorKind::Malformed, "`local_dns_address` invalid", None);
return Err(err);
}
}
}
#[cfg(feature = "local-dns")]
if let Some(client_cache_size) = local.client_cache_size {
local_config.client_cache_size = Some(client_cache_size);
}
#[cfg(feature = "local-dns")]
if let Some(remote_dns_address) = local.remote_dns_address {
let remote_dns_port = local.remote_dns_port.unwrap_or(53);
local_config.remote_dns_addr = Some(match remote_dns_address.parse::<IpAddr>() {
Ok(ip) => Address::from(SocketAddr::new(ip, remote_dns_port)),
Err(..) => Address::from((remote_dns_address, remote_dns_port)),
});
}
#[cfg(feature = "local-tun")]
if let Some(tun_interface_address) = local.tun_interface_address {
match tun_interface_address.parse::<IpNet>() {
Ok(addr) => local_config.tun_interface_address = Some(addr),
Err(..) => {
let err = Error::new(ErrorKind::Malformed, "`tun_interface_address` invalid", None);
return Err(err);
}
}
}
#[cfg(feature = "local-tun")]
if let Some(tun_interface_destination) = local.tun_interface_destination {
match tun_interface_destination.parse::<IpNet>() {
Ok(addr) => local_config.tun_interface_destination = Some(addr),
Err(..) => {
let err =
Error::new(ErrorKind::Malformed, "`tun_interface_destination` invalid", None);
return Err(err);
}
}
}
#[cfg(feature = "local-tun")]
if let Some(tun_interface_name) = local.tun_interface_name {
local_config.tun_interface_name = Some(tun_interface_name);
}
#[cfg(all(feature = "local-tun", unix))]
if let Some(tun_device_fd_from_path) = local.tun_device_fd_from_path {
local_config.tun_device_fd_from_path = Some(From::from(tun_device_fd_from_path));
}
#[cfg(feature = "local")]
if let Some(socks5_auth_config_path) = local.socks5_auth_config_path {
local_config.socks5_auth = Socks5AuthConfig::load_from_file(&socks5_auth_config_path)?;
}
#[cfg(feature = "local-fake-dns")]
{
if let Some(d) = local.fake_dns_record_expire_duration {
local_config.fake_dns_record_expire_duration = Some(Duration::from_secs(d));
}
if let Some(n) = local.fake_dns_ipv4_network {
match n.parse::<Ipv4Net>() {
Ok(n) => local_config.fake_dns_ipv4_network = Some(n),
Err(..) => {
let err =
Error::new(ErrorKind::Malformed, "invalid `fake_dns_ipv4_network`", None);
return Err(err);
}
}
}
if let Some(n) = local.fake_dns_ipv6_network {
match n.parse::<Ipv6Net>() {
Ok(n) => local_config.fake_dns_ipv6_network = Some(n),
Err(..) => {
let err =
Error::new(ErrorKind::Malformed, "invalid `fake_dns_ipv6_network`", None);
return Err(err);
}
}
}
if let Some(p) = local.fake_dns_database_path {
local_config.fake_dns_database_path = Some(p.into());
}
}
let mut local_instance = LocalInstanceConfig {
config: local_config,
acl: None,
};
if let Some(acl_path) = local.acl {
let acl = match AccessControl::load_from_file(&acl_path) {
Ok(acl) => acl,
Err(err) => {
let err = Error::new(
ErrorKind::Invalid,
"acl loading failed",
Some(format!("file {acl_path}, error: {err}")),
);
return Err(err);
}
};
local_instance.acl = Some(acl);
}
nconfig.local.push(local_instance);
}
}
}
ConfigType::Server | ConfigType::Manager => {
// NOTE: IGNORED.
// servers only uses `local_address` for binding outbound interfaces
//
// This behavior causes lots of confusion. use outbound_bind_addr instead
}
#[cfg(feature = "local-online-config")]
ConfigType::OnlineConfig => {
// SIP008. https://shadowsocks.org/doc/sip008.html
// "version" should be set to "1"
match config.version {
Some(1) => {}
Some(v) => {
let err = Error::new(
ErrorKind::Invalid,
"invalid online config version",
Some(format!("version: {v}")),
);
return Err(err);
}
None => {
warn!("OnlineConfig \"version\" is missing in the configuration, assuming it is a compatible version for this project");
}
}
}
}
let server_source = match config_type {
ConfigType::Local | ConfigType::Server | ConfigType::Manager => ServerSource::Configuration,
#[cfg(feature = "local-online-config")]
ConfigType::OnlineConfig => ServerSource::OnlineConfig,
};
// Standard config
// Server
match (config.server, config.server_port, config.password, &config.method) {
(Some(address), Some(port), pwd_opt, Some(m)) => {
let addr = match address.parse::<Ipv4Addr>() {
Ok(v4) => ServerAddr::SocketAddr(SocketAddr::V4(SocketAddrV4::new(v4, port))),
Err(..) => match address.parse::<Ipv6Addr>() {
Ok(v6) => ServerAddr::SocketAddr(SocketAddr::V6(SocketAddrV6::new(v6, port, 0, 0))),
Err(..) => ServerAddr::DomainName(address, port),
},
};
let method = match m.parse::<CipherKind>() {
Ok(m) => m,
Err(..) => {
let err = Error::new(
ErrorKind::Invalid,
"unsupported method",
Some(format!("`{m}` is not a supported method")),
);
return Err(err);
}
};
// Only "password" support getting from environment variable.
let password = match pwd_opt {
Some(ref pwd) => read_variable_field_value(pwd),
None => {
if method.is_none() {
String::new().into()
} else {
let err = Error::new(
ErrorKind::MissingField,
"`password` is required",
Some(format!("`password` is required for method {method}")),
);
return Err(err);
}
}
};
let mut nsvr = match ServerConfig::new(addr, password, method) {
Ok(svr) => svr,
Err(serr) => {
let err = Error::new(
ErrorKind::Malformed,
"server config create failed",
Some(format!("{}", serr)),
);
return Err(err);
}
};
nsvr.set_source(server_source);
nsvr.set_mode(global_mode);
if let Some(ref p) = config.plugin {
// SIP008 allows "plugin" to be an empty string
// Empty string implies "no plugin"
if !p.is_empty() {
let plugin = PluginConfig {
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::<Mode>() {
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);
}
}
if let Some(timeout) = config.timeout.map(Duration::from_secs) {
nsvr.set_timeout(timeout);
}
let mut outbound_bind_addr: Option<IpAddr> = None;
if let Some(ref bind_addr) = config.outbound_bind_addr {
match bind_addr.parse::<IpAddr>() {
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_interface: config.outbound_bind_interface.clone(),
outbound_udp_allow_fragmentation: config.outbound_udp_allow_fragmentation,
};
nconfig.server.push(server_instance);
}
(None, None, None, Some(_)) if config_type.is_manager() => {
// Set the default method for manager
}
(None, None, None, None) => (),
_ => {
let err = Error::new(
ErrorKind::Malformed,
"`server`, `server_port`, `method`, `password` must be provided together",
None,
);
return Err(err);
}
}
// Ext servers
if let Some(servers) = config.servers {
for svr in servers {
// Skip if server is disabled
if svr.disabled.unwrap_or(false) {
continue;
}
let address = svr.server;
let port = svr.server_port;
let addr = match address.parse::<Ipv4Addr>() {
Ok(v4) => ServerAddr::SocketAddr(SocketAddr::V4(SocketAddrV4::new(v4, port))),
Err(..) => match address.parse::<Ipv6Addr>() {
Ok(v6) => ServerAddr::SocketAddr(SocketAddr::V6(SocketAddrV6::new(v6, port, 0, 0))),
Err(..) => ServerAddr::DomainName(address, port),
},
};
let method = match svr.method.parse::<CipherKind>() {
Ok(m) => m,
Err(..) => {
let err = Error::new(
ErrorKind::Invalid,
"unsupported method",
Some(format!("`{}` is not a supported method", svr.method)),
);
return Err(err);
}
};
// Only "password" support getting from environment variable.
let password = match svr.password {
Some(ref pwd) => read_variable_field_value(pwd),
None => {
if method.is_none() {
String::new().into()
} else {
let err = Error::new(
ErrorKind::MissingField,
"`password` is required",
Some(format!("`password` is required for method {method}")),
);
return Err(err);
}
}
};
let mut nsvr = match ServerConfig::new(addr, password, method) {
Ok(svr) => svr,
Err(serr) => {
let err = Error::new(
ErrorKind::Malformed,
"server config create failed",
Some(format!("{}", serr)),
);
return Err(err);
}
};
nsvr.set_source(server_source);
// Extensible Identity Header, Users
if let Some(users) = svr.users {
let mut user_manager = ServerUserManager::new();
for user in users {
let user = match ServerUser::with_encoded_key(user.name, &user.password) {
Ok(u) => u,
Err(..) => {
let err = Error::new(
ErrorKind::Malformed,
"`users[].password` should be base64 encoded",
None,
);
return Err(err);
}
};
user_manager.add_user(user);
}
nsvr.set_user_manager(user_manager);
}
match svr.mode {
Some(mode) => match mode.parse::<Mode>() {
Ok(mode) => nsvr.set_mode(mode),
Err(..) => {
let err = Error::new(ErrorKind::Invalid, "invalid `mode`", None);
return Err(err);
}
},
None => {
// Server will derive mode from the global scope
if matches!(config_type, ConfigType::Server | ConfigType::Manager) {
nsvr.set_mode(global_mode);
}
}
}
if let Some(p) = svr.plugin {
// SIP008 allows "plugin" to be an empty string
// Empty string implies "no plugin"
if !p.is_empty() {
let plugin = PluginConfig {
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::<Mode>() {
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);
}
}
if let Some(timeout) = config.timeout.map(Duration::from_secs) {
nsvr.set_timeout(timeout);
}
if let Some(remarks) = svr.remarks {
nsvr.set_remarks(remarks);
}
if let Some(id) = svr.id {
nsvr.set_id(id);
}
if svr.tcp_weight.is_some() || svr.udp_weight.is_some() {
let tcp_weight = svr.tcp_weight.unwrap_or(1.0);
if !(0.0..=1.0).contains(&tcp_weight) {
let err = Error::new(ErrorKind::Invalid, "invalid `tcp_weight`, must be in [0, 1]", None);
return Err(err);
}
let udp_weight = svr.udp_weight.unwrap_or(1.0);
if !(0.0..=1.0).contains(&udp_weight) {
let err = Error::new(ErrorKind::Invalid, "invalid `udp_weight`, must be in [0, 1]", None);
return Err(err);
}
let mut weight = ServerWeight::new();
weight.set_tcp_weight(tcp_weight);
weight.set_udp_weight(udp_weight);
nsvr.set_weight(weight);
}
let mut outbound_bind_addr: Option<IpAddr> = None;
if let Some(ref bind_addr) = config.outbound_bind_addr {
match bind_addr.parse::<IpAddr>() {
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_interface: config.outbound_bind_interface.clone(),
outbound_udp_allow_fragmentation: config.outbound_udp_allow_fragmentation,
};
if let Some(acl_path) = svr.acl {
let acl = match AccessControl::load_from_file(&acl_path) {
Ok(acl) => acl,
Err(err) => {
let err = Error::new(
ErrorKind::Invalid,
"acl loading failed",
Some(format!("file {acl_path}, error: {err}")),
);
return Err(err);
}
};
server_instance.acl = Some(acl);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
if let Some(outbound_fwmark) = svr.outbound_fwmark {
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());
}
if let Some(outbound_udp_allow_fragmentation) = svr.outbound_udp_allow_fragmentation {
server_instance.outbound_udp_allow_fragmentation = Some(outbound_udp_allow_fragmentation);
}
nconfig.server.push(server_instance);
}
}
// Set timeout globally
if let Some(timeout) = config.timeout {
let timeout = Duration::from_secs(timeout);
// Set as a default timeout
for inst in &mut nconfig.server {
let svr = &mut inst.config;
if svr.timeout().is_none() {
svr.set_timeout(timeout);
}
}
}
// Manager Address
if let Some(ma) = config.manager_address {
let manager = match config.manager_port {
Some(port) => {
match ma.parse::<IpAddr>() {
Ok(ip) => ManagerAddr::from(SocketAddr::new(ip, port)),
Err(..) => {
// treated as domain
ManagerAddr::from((ma, port))
}
}
}
#[cfg(unix)]
None => ManagerAddr::from(PathBuf::from(ma)),
#[cfg(not(unix))]
None => {
let e = Error::new(ErrorKind::MissingField, "missing `manager_port`", None);
return Err(e);
}
};
let mut manager_config = ManagerConfig::new(manager);
manager_config.mode = global_mode;
if let Some(ref m) = config.method {
match m.parse::<CipherKind>() {
Ok(method) => manager_config.method = Some(method),
Err(..) => {
let err = Error::new(
ErrorKind::Invalid,
"unsupported method",
Some(format!("`{m}` is not a supported method")),
);
return Err(err);
}
}
}
if let Some(p) = config.plugin {
// SIP008 allows "plugin" to be an empty string
// Empty string implies "no plugin"
if !p.is_empty() {
manager_config.plugin = Some(PluginConfig {
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::<Mode>() {
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);
}
},
},
});
}
}
nconfig.manager = Some(manager_config);
}
// DNS
{
match config.dns {
Some(SSDnsConfig::Simple(ds)) => nconfig.set_dns_formatted(&ds)?,
#[cfg(feature = "hickory-dns")]
Some(SSDnsConfig::HickoryDns(c)) => nconfig.dns = DnsConfig::HickoryDns(c),
None => nconfig.dns = DnsConfig::System,
}
nconfig.dns_cache_size = config.dns_cache_size;
}
// TCP nodelay
if let Some(b) = config.no_delay {
nconfig.no_delay = b;
}
// TCP fast open
if let Some(b) = config.fast_open {
nconfig.fast_open = b;
}
// TCP Keep-Alive
if let Some(d) = config.keep_alive {
nconfig.keep_alive = Some(Duration::from_secs(d));
}
// Multipath-TCP
if let Some(b) = config.mptcp {
nconfig.mptcp = b;
}
// UDP
nconfig.udp_timeout = config.udp_timeout.map(Duration::from_secs);
// Maximum associations to be kept simultaneously
nconfig.udp_max_associations = config.udp_max_associations;
// MTU for UDP
nconfig.udp_mtu = config.udp_mtu;
// RLIMIT_NOFILE
#[cfg(all(unix, not(target_os = "android")))]
{
nconfig.nofile = config.nofile;
}
// Uses IPv6 first
if let Some(f) = config.ipv6_first {
nconfig.ipv6_first = f;
}
// IPV6_V6ONLY
if let Some(o) = config.ipv6_only {
nconfig.ipv6_only = o;
}
// SO_MARK
#[cfg(any(target_os = "linux", target_os = "android"))]
if let Some(fwmark) = config.outbound_fwmark {
nconfig.outbound_fwmark = Some(fwmark);
}
// SO_USER_COOKIE
#[cfg(target_os = "freebsd")]
if let Some(user_cookie) = config.outbound_user_cookie {
nconfig.outbound_user_cookie = Some(user_cookie);
}
// Outbound bind() address
if let Some(bind_addr) = config.outbound_bind_addr {
match bind_addr.parse::<IpAddr>() {
Ok(b) => nconfig.outbound_bind_addr = Some(b),
Err(..) => {
let err = Error::new(ErrorKind::Invalid, "invalid outbound_bind_addr", None);
return Err(err);
}
}
}
// Bind device / interface
nconfig.outbound_bind_interface = config.outbound_bind_interface;
if let Some(b) = config.outbound_udp_allow_fragmentation {
nconfig.outbound_udp_allow_fragmentation = b;
}
// Security
if let Some(sec) = config.security {
if let Some(replay_attack) = sec.replay_attack {
if let Some(policy) = replay_attack.policy {
match policy.parse::<ReplayAttackPolicy>() {
Ok(p) => nconfig.security.replay_attack.policy = p,
Err(..) => {
let err = Error::new(ErrorKind::Invalid, "invalid replay attack policy", None);
return Err(err);
}
}
}
}
}
if let Some(balancer) = config.balancer {
nconfig.balancer = BalancerConfig {
max_server_rtt: balancer.max_server_rtt.map(Duration::from_secs),
check_interval: balancer.check_interval.map(Duration::from_secs),
check_best_interval: balancer.check_best_interval.map(Duration::from_secs),
};
}
if let Some(acl_path) = config.acl {
let acl = match AccessControl::load_from_file(&acl_path) {
Ok(acl) => acl,
Err(err) => {
let err = Error::new(
ErrorKind::Invalid,
"acl loading failed",
Some(format!("file {acl_path}, error: {err}")),
);
return Err(err);
}
};
nconfig.acl = Some(acl);
}
#[cfg(feature = "local-online-config")]
if let Some(online_config) = config.online_config {
nconfig.online_config = Some(OnlineConfig {
config_url: online_config.config_url,
update_interval: online_config.update_interval.map(Duration::from_secs),
});
}
Ok(nconfig)
}
/// Set DNS configuration in string format
///
/// 1. `[(unix|tcp|udp)://]host[:port][,host[:port]]...`
/// 2. Pre-defined. Like `google`, `cloudflare`
pub fn set_dns_formatted(&mut self, dns: &str) -> Result<(), Error> {
self.dns = match dns {
"system" => DnsConfig::System,
#[cfg(feature = "hickory-dns")]
"google" => DnsConfig::HickoryDns(ResolverConfig::google()),
#[cfg(all(feature = "hickory-dns", feature = "dns-over-tls"))]
"google_tls" => DnsConfig::HickoryDns(ResolverConfig::google_tls()),
#[cfg(all(feature = "hickory-dns", feature = "dns-over-https"))]
"google_https" => DnsConfig::HickoryDns(ResolverConfig::google_https()),
#[cfg(all(feature = "hickory-dns", feature = "dns-over-h3"))]
"google_h3" => DnsConfig::HickoryDns(ResolverConfig::google_h3()),
#[cfg(feature = "hickory-dns")]
"cloudflare" => DnsConfig::HickoryDns(ResolverConfig::cloudflare()),
#[cfg(all(feature = "hickory-dns", feature = "dns-over-tls"))]
"cloudflare_tls" => DnsConfig::HickoryDns(ResolverConfig::cloudflare_tls()),
#[cfg(all(feature = "hickory-dns", feature = "dns-over-https"))]
"cloudflare_https" => DnsConfig::HickoryDns(ResolverConfig::cloudflare_https()),
#[cfg(feature = "hickory-dns")]
"quad9" => DnsConfig::HickoryDns(ResolverConfig::quad9()),
#[cfg(all(feature = "hickory-dns", feature = "dns-over-tls"))]
"quad9_tls" => DnsConfig::HickoryDns(ResolverConfig::quad9_tls()),
#[cfg(all(feature = "hickory-dns", feature = "dns-over-https"))]
"quad9_https" => DnsConfig::HickoryDns(ResolverConfig::quad9_https()),
nameservers => self.parse_dns_nameservers(nameservers)?,
};
Ok(())
}
#[cfg(any(feature = "hickory-dns", feature = "local-dns"))]
fn parse_dns_nameservers(&mut self, nameservers: &str) -> Result<DnsConfig, Error> {
use hickory_resolver::proto::xfer::Protocol;
#[cfg(all(unix, feature = "local-dns"))]
if let Some(nameservers) = nameservers.strip_prefix("unix://") {
// A special DNS server only for shadowsocks-android
// It serves like a TCP DNS server but using unix domain sockets
return Ok(DnsConfig::LocalDns(NameServerAddr::UnixSocketAddr(PathBuf::from(
nameservers,
))));
}
enum DnsProtocol {
Tcp,
Udp,
Both,
}
impl DnsProtocol {
fn enable_tcp(&self) -> bool {
matches!(*self, DnsProtocol::Tcp | DnsProtocol::Both)
}
fn enable_udp(&self) -> bool {
matches!(*self, DnsProtocol::Udp | DnsProtocol::Both)
}
}
let mut protocol = DnsProtocol::Both;
let mut nameservers = nameservers;
if nameservers.starts_with("tcp://") {
protocol = DnsProtocol::Tcp;
nameservers = &nameservers[6..];
} else if nameservers.starts_with("udp://") {
protocol = DnsProtocol::Udp;
nameservers = &nameservers[6..];
}
// If enables Trust-DNS, then it supports multiple nameservers
//
// Set ips directly
// Similar to shadowsocks-libev's `ares_set_servers_ports_csv`
//
// ```
// host[:port][,host[:port]]...
// ```
//
// For example:
// `192.168.1.100,192.168.1.101,3.4.5.6`
let mut c = ResolverConfig::new();
for part in nameservers.split(',') {
let socket_addr = if let Ok(socket_addr) = part.parse::<SocketAddr>() {
socket_addr
} else if let Ok(ipaddr) = part.parse::<IpAddr>() {
SocketAddr::new(ipaddr, 53)
} else {
let e = Error::new(
ErrorKind::Invalid,
"invalid `dns` value, can only be [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\"",
None,
);
return Err(e);
};
if protocol.enable_udp() {
let ns_config = NameServerConfig::new(socket_addr, Protocol::Udp);
c.add_name_server(ns_config);
}
if protocol.enable_tcp() {
let ns_config = NameServerConfig::new(socket_addr, Protocol::Tcp);
c.add_name_server(ns_config);
}
}
Ok(if c.name_servers().is_empty() {
DnsConfig::System
} else {
DnsConfig::HickoryDns(c)
})
}
#[cfg(not(any(feature = "hickory-dns", feature = "local-dns")))]
fn parse_dns_nameservers(&mut self, _nameservers: &str) -> Result<DnsConfig, Error> {
Ok(DnsConfig::System)
}
/// Load Config from a `str`
pub fn load_from_str(s: &str, config_type: ConfigType) -> Result<Config, Error> {
let c = json5::from_str::<SSConfig>(s)?;
Config::load_from_ssconfig(c, config_type)
}
/// Load Config from a File
pub fn load_from_file<P: AsRef<Path>>(filename: P, config_type: ConfigType) -> Result<Config, Error> {
let filename = filename.as_ref();
let mut reader = OpenOptions::new().read(true).open(filename)?;
let mut content = String::new();
reader.read_to_string(&mut content)?;
let mut config = Config::load_from_str(&content[..], config_type)?;
// Record the path of the configuration for auto-reloading
config.config_path = Some(filename.to_owned());
Ok(config)
}
/// Check if there are any plugin are enabled with servers
pub fn has_server_plugins(&self) -> bool {
for inst in &self.server {
let server = &inst.config;
if server.plugin().is_some() {
return true;
}
}
false
}
/// Check if all required fields are already set
pub fn check_integrity(&self) -> Result<(), Error> {
if self.config_type.is_local() {
if self.local.is_empty() {
let err = Error::new(
ErrorKind::MissingField,
"missing `locals` for client configuration",
None,
);
return Err(err);
}
for local_config in &self.local {
local_config.config.check_integrity()?;
}
// Balancer related checks
if let Some(rtt) = self.balancer.max_server_rtt {
if rtt.as_secs() == 0 {
let err = Error::new(ErrorKind::Invalid, "balancer.max_server_rtt must be > 0", None);
return Err(err);
}
}
if let Some(intv) = self.balancer.check_interval {
if intv.as_secs() == 0 {
let err = Error::new(ErrorKind::Invalid, "balancer.check_interval must be > 0", None);
return Err(err);
}
}
}
if self.config_type.is_server() && self.server.is_empty() {
let err = Error::new(
ErrorKind::MissingField,
"missing any valid servers in configuration",
None,
);
return Err(err);
}
#[cfg(feature = "local-online-config")]
if self.config_type.is_online_config() && self.server.is_empty() {
let err = Error::new(
ErrorKind::MissingField,
"missing any valid servers in configuration",
None,
);
return Err(err);
}
if self.config_type.is_manager() && self.manager.is_none() {
let err = Error::new(
ErrorKind::MissingField,
"missing `manager_addr` and `manager_port` in configuration",
None,
);
return Err(err);
}
for inst in &self.server {
let server = &inst.config;
// Plugin shouldn't be an empty string
if let Some(plugin) = server.plugin() {
if plugin.plugin.trim().is_empty() {
let err = Error::new(ErrorKind::Malformed, "`plugin` shouldn't be an empty string", None);
return Err(err);
}
}
// Server's domain name shouldn't be an empty string
match server.addr() {
ServerAddr::SocketAddr(sa) => {
if sa.port() == 0 {
let err = Error::new(ErrorKind::Malformed, "`server_port` shouldn't be 0", None);
return Err(err);
}
if self.config_type.is_local() {
// Only server could bind to INADDR_ANY
let ip = sa.ip();
if ip.is_unspecified() {
let err = Error::new(
ErrorKind::Malformed,
"`server` shouldn't be an unspecified address (INADDR_ANY)",
None,
);
return Err(err);
}
}
#[cfg(feature = "local-online-config")]
if self.config_type.is_online_config() {
// Only server could bind to INADDR_ANY
let ip = sa.ip();
if ip.is_unspecified() {
let err = Error::new(
ErrorKind::Malformed,
"`server` shouldn't be an unspecified address (INADDR_ANY)",
None,
);
return Err(err);
}
}
}
ServerAddr::DomainName(dn, port) => {
if dn.is_empty() || *port == 0 {
let err = Error::new(
ErrorKind::Malformed,
"`server` shouldn't be an empty string, `server_port` shouldn't be 0",
None,
);
return Err(err);
}
}
}
// Users' key must match key length
if let Some(user_manager) = server.user_manager() {
#[cfg(feature = "aead-cipher-2022")]
if server.method().is_aead_2022() {
use shadowsocks::config::method_support_eih;
if user_manager.user_count() > 0 && !method_support_eih(server.method()) {
let err = Error::new(
ErrorKind::Invalid,
"server method doesn't support Extended Identity Header (EIH), remove `users`",
Some(format!("method {}", server.method())),
);
return Err(err);
}
}
let key_len = server.method().key_len();
for user in user_manager.users_iter() {
if user.key().len() != key_len {
let err = Error::new(
ErrorKind::Malformed,
"`users[].password` length must be exactly the same as method's key length",
None,
);
return Err(err);
}
}
}
}
Ok(())
}
}
impl fmt::Display for Config {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Convert to json
let mut jconf = SSConfig::default();
// Locals
if !self.local.is_empty() {
if self.local.len() == 1 && self.local[0].config.is_basic() {
let local_instance = &self.local[0];
let local = &local_instance.config;
if let Some(ref a) = local.addr {
jconf.local_address = Some(match a {
ServerAddr::SocketAddr(sa) => sa.ip().to_string(),
ServerAddr::DomainName(dm, ..) => dm.to_string(),
});
jconf.local_port = Some(match a {
ServerAddr::SocketAddr(sa) => sa.port(),
ServerAddr::DomainName(.., port) => *port,
});
}
#[cfg(target_os = "macos")]
{
jconf.launchd_tcp_socket_name.clone_from(&local.launchd_tcp_socket_name);
jconf.launchd_udp_socket_name.clone_from(&local.launchd_udp_socket_name);
}
if local.protocol != ProtocolType::Socks {
jconf.protocol = Some(local.protocol.as_str().to_owned());
}
// ACL
if let Some(ref acl) = local_instance.acl {
jconf.acl = Some(acl.file_path().to_str().unwrap().to_owned());
}
} else {
let mut jlocals = Vec::with_capacity(self.local.len());
for local_instance in &self.local {
let local = &local_instance.config;
let jlocal = SSLocalExtConfig {
local_address: local.addr.as_ref().map(|a| match a {
ServerAddr::SocketAddr(sa) => sa.ip().to_string(),
ServerAddr::DomainName(dm, ..) => dm.to_string(),
}),
local_port: local.addr.as_ref().map(|a| match a {
ServerAddr::SocketAddr(sa) => sa.port(),
ServerAddr::DomainName(.., port) => *port,
}),
disabled: None,
local_udp_address: local.udp_addr.as_ref().map(|udp_addr| match udp_addr {
ServerAddr::SocketAddr(sa) => sa.ip().to_string(),
ServerAddr::DomainName(dm, ..) => dm.to_string(),
}),
local_udp_port: local.udp_addr.as_ref().map(|udp_addr| match udp_addr {
ServerAddr::SocketAddr(sa) => sa.port(),
ServerAddr::DomainName(.., port) => *port,
}),
mode: Some(local.mode.to_string()),
protocol: match local.protocol {
ProtocolType::Socks => None,
#[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())
} else {
None
},
#[cfg(feature = "local-redir")]
udp_redir: if local.udp_redir != RedirType::udp_default() {
Some(local.udp_redir.to_string())
} else {
None
},
#[cfg(feature = "local-tunnel")]
forward_address: match local.forward_addr {
None => None,
Some(ref forward_addr) => match forward_addr {
Address::SocketAddress(sa) => Some(sa.ip().to_string()),
Address::DomainNameAddress(dm, ..) => Some(dm.to_string()),
},
},
#[cfg(feature = "local-tunnel")]
forward_port: match local.forward_addr {
None => None,
Some(ref forward_addr) => match forward_addr {
Address::SocketAddress(sa) => Some(sa.port()),
Address::DomainNameAddress(.., port) => Some(*port),
},
},
#[cfg(feature = "local-dns")]
local_dns_address: match local.local_dns_addr {
None => None,
Some(ref local_dns_addr) => match local_dns_addr {
NameServerAddr::SocketAddr(sa) => Some(sa.ip().to_string()),
#[cfg(unix)]
NameServerAddr::UnixSocketAddr(path) => {
Some(path.to_str().expect("path is not utf-8").to_owned())
}
},
},
#[cfg(feature = "local-dns")]
local_dns_port: match local.local_dns_addr {
None => None,
Some(ref local_dns_addr) => match local_dns_addr {
NameServerAddr::SocketAddr(sa) => Some(sa.port()),
#[cfg(unix)]
NameServerAddr::UnixSocketAddr(..) => None,
},
},
#[cfg(feature = "local-dns")]
remote_dns_address: match local.remote_dns_addr {
None => None,
Some(ref remote_dns_addr) => match remote_dns_addr {
Address::SocketAddress(sa) => Some(sa.ip().to_string()),
Address::DomainNameAddress(dm, ..) => Some(dm.to_string()),
},
},
#[cfg(feature = "local-dns")]
remote_dns_port: match local.remote_dns_addr {
None => None,
Some(ref remote_dns_addr) => match remote_dns_addr {
Address::SocketAddress(sa) => Some(sa.port()),
Address::DomainNameAddress(.., port) => Some(*port),
},
},
#[cfg(feature = "local-dns")]
client_cache_size: local.client_cache_size,
#[cfg(feature = "local-tun")]
tun_interface_name: local.tun_interface_name.clone(),
#[cfg(feature = "local-tun")]
tun_interface_address: local.tun_interface_address.as_ref().map(ToString::to_string),
#[cfg(feature = "local-tun")]
tun_interface_destination: local.tun_interface_destination.as_ref().map(ToString::to_string),
#[cfg(all(feature = "local-tun", unix))]
tun_device_fd_from_path: local
.tun_device_fd_from_path
.as_ref()
.map(|p| p.to_str().expect("tun_device_fd_from_path is not utf-8").to_owned()),
#[cfg(feature = "local")]
socks5_auth_config_path: None,
#[cfg(feature = "local-fake-dns")]
fake_dns_record_expire_duration: local.fake_dns_record_expire_duration.map(|d| d.as_secs()),
#[cfg(feature = "local-fake-dns")]
fake_dns_ipv4_network: local.fake_dns_ipv4_network.map(|n| n.to_string()),
#[cfg(feature = "local-fake-dns")]
fake_dns_ipv6_network: local.fake_dns_ipv6_network.map(|n| n.to_string()),
#[cfg(feature = "local-fake-dns")]
fake_dns_database_path: local
.fake_dns_database_path
.as_ref()
.and_then(|n| n.to_str().map(ToOwned::to_owned)),
acl: local_instance
.acl
.as_ref()
.and_then(|a| a.file_path().to_str().map(ToOwned::to_owned)),
};
jlocals.push(jlocal);
}
jconf.locals = Some(jlocals);
}
}
// Servers
match self.server.len() {
0 => {}
// For 1 server, uses standard configure format
1 if self.server[0].config.is_basic() => {
let inst = &self.server[0];
let svr = &inst.config;
jconf.server = Some(match *svr.addr() {
ServerAddr::SocketAddr(ref sa) => sa.ip().to_string(),
ServerAddr::DomainName(ref dm, ..) => dm.to_string(),
});
jconf.server_port = Some(match *svr.addr() {
ServerAddr::SocketAddr(ref sa) => sa.port(),
ServerAddr::DomainName(.., port) => port,
});
jconf.method = Some(svr.method().to_string());
jconf.password = if svr.method().is_none() {
None
} else {
Some(svr.password().to_string())
};
jconf.plugin = svr.plugin().map(|p| p.plugin.to_string());
jconf.plugin_opts = svr.plugin().and_then(|p| p.plugin_opts.clone());
jconf.plugin_args = svr.plugin().and_then(|p| {
if p.plugin_args.is_empty() {
None
} else {
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());
if let Some(ref acl) = inst.acl {
jconf.acl = Some(acl.file_path().to_str().unwrap().to_owned());
}
}
// For >1 servers, uses extended multiple server format
_ => {
let mut vsvr = Vec::new();
for inst in &self.server {
let svr = &inst.config;
vsvr.push(SSServerExtConfig {
server: match *svr.addr() {
ServerAddr::SocketAddr(ref sa) => sa.ip().to_string(),
ServerAddr::DomainName(ref dm, ..) => dm.to_string(),
},
server_port: match *svr.addr() {
ServerAddr::SocketAddr(ref sa) => sa.port(),
ServerAddr::DomainName(.., port) => port,
},
password: if svr.method().is_none() {
None
} else {
Some(svr.password().to_string())
},
method: svr.method().to_string(),
users: svr.user_manager().map(|m| {
let mut vu = Vec::new();
for u in m.users_iter() {
vu.push(SSServerUserConfig {
name: u.name().to_owned(),
password: u.encoded_key(),
});
}
vu
}),
disabled: None,
plugin: svr.plugin().map(|p| p.plugin.to_string()),
plugin_opts: svr.plugin().and_then(|p| p.plugin_opts.clone()),
plugin_args: svr.plugin().and_then(|p| {
if p.plugin_args.is_empty() {
None
} else {
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),
mode: Some(svr.mode().to_string()),
tcp_weight: if (svr.weight().tcp_weight() - 1.0).abs() > f32::EPSILON {
Some(svr.weight().tcp_weight())
} else {
None
},
udp_weight: if (svr.weight().udp_weight() - 1.0).abs() > f32::EPSILON {
Some(svr.weight().udp_weight())
} else {
None
},
acl: inst
.acl
.as_ref()
.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,
outbound_bind_addr: inst.outbound_bind_addr,
outbound_bind_interface: inst.outbound_bind_interface.clone(),
outbound_udp_allow_fragmentation: inst.outbound_udp_allow_fragmentation,
});
}
jconf.servers = Some(vsvr);
}
}
if let Some(ref m) = self.manager {
jconf.manager_address = Some(match m.addr {
ManagerAddr::SocketAddr(ref saddr) => saddr.ip().to_string(),
ManagerAddr::DomainName(ref dname, ..) => dname.clone(),
#[cfg(unix)]
ManagerAddr::UnixSocketAddr(ref path) => path.display().to_string(),
});
jconf.manager_port = match m.addr {
ManagerAddr::SocketAddr(ref saddr) => Some(saddr.port()),
ManagerAddr::DomainName(.., port) => Some(port),
#[cfg(unix)]
ManagerAddr::UnixSocketAddr(..) => None,
};
if jconf.mode.is_none() {
jconf.mode = Some(m.mode.to_string());
}
if jconf.method.is_none() {
if let Some(ref m) = m.method {
jconf.method = Some(m.to_string());
}
}
if jconf.plugin.is_none() {
if let Some(ref p) = m.plugin {
jconf.plugin = Some(p.plugin.clone());
if let Some(ref o) = p.plugin_opts {
jconf.plugin_opts = Some(o.clone());
}
if !p.plugin_args.is_empty() {
jconf.plugin_args = Some(p.plugin_args.clone());
}
}
}
}
if self.no_delay {
jconf.no_delay = Some(self.no_delay);
}
if self.fast_open {
jconf.fast_open = Some(self.fast_open);
}
if let Some(keepalive) = self.keep_alive {
jconf.keep_alive = Some(keepalive.as_secs());
}
if self.mptcp {
jconf.mptcp = Some(self.mptcp);
}
match self.dns {
DnsConfig::System => {}
#[cfg(feature = "hickory-dns")]
DnsConfig::HickoryDns(ref dns) => {
jconf.dns = Some(SSDnsConfig::HickoryDns(dns.clone()));
}
#[cfg(feature = "local-dns")]
DnsConfig::LocalDns(ref ns) => {
jconf.dns = Some(SSDnsConfig::Simple(ns.to_string()));
}
}
jconf.udp_timeout = self.udp_timeout.map(|t| t.as_secs());
jconf.udp_max_associations = self.udp_max_associations;
jconf.udp_mtu = self.udp_mtu;
#[cfg(all(unix, not(target_os = "android")))]
{
jconf.nofile = self.nofile;
}
if self.ipv6_first {
jconf.ipv6_first = Some(self.ipv6_first);
}
if self.ipv6_only {
jconf.ipv6_only = Some(self.ipv6_only);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
{
jconf.outbound_fwmark = self.outbound_fwmark;
}
#[cfg(target_os = "freebsd")]
{
jconf.outbound_user_cookie = self.outbound_user_cookie;
}
jconf.outbound_bind_addr = self.outbound_bind_addr.map(|i| i.to_string());
jconf.outbound_bind_interface.clone_from(&self.outbound_bind_interface);
jconf.outbound_udp_allow_fragmentation = Some(self.outbound_udp_allow_fragmentation);
// Security
if self.security.replay_attack.policy != ReplayAttackPolicy::default() {
jconf.security = Some(SSSecurityConfig {
replay_attack: Some(SSSecurityReplayAttackConfig {
policy: Some(self.security.replay_attack.policy.to_string()),
}),
});
}
// Balancer
if self.balancer.max_server_rtt.is_some() || self.balancer.check_interval.is_some() {
jconf.balancer = Some(SSBalancerConfig {
max_server_rtt: self.balancer.max_server_rtt.as_ref().map(Duration::as_secs),
check_interval: self.balancer.check_interval.as_ref().map(Duration::as_secs),
check_best_interval: self.balancer.check_best_interval.as_ref().map(Duration::as_secs),
});
}
// ACL
if let Some(ref acl) = self.acl {
jconf.acl = Some(acl.file_path().to_str().unwrap().to_owned());
}
// OnlineConfig
#[cfg(feature = "local-online-config")]
if let Some(ref online_config) = self.online_config {
jconf.online_config = Some(SSOnlineConfig {
config_url: online_config.config_url.clone(),
update_interval: online_config.update_interval.as_ref().map(Duration::as_secs),
});
}
write!(f, "{}", json5::to_string(&jconf).unwrap())
}
}
/// Parse variable value if it is an environment variable
///
/// If value is in format `${VAR_NAME}` then it will try to read from `VAR_NAME` environment variable.
/// It will return the original value if fails to read `${VAR_NAME}`.
pub fn read_variable_field_value(value: &str) -> Cow<'_, str> {
if let Some(left_over) = value.strip_prefix("${") {
if let Some(var_name) = left_over.strip_suffix('}') {
match env::var(var_name) {
Ok(value) => return value.into(),
Err(err) => {
warn!(
"couldn't read password from environment variable {}, error: {}",
var_name, err
);
}
}
}
}
value.into()
}