#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
use std::ffi::OsString;
use std::{
convert::{From, Infallible},
default::Default,
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;
use serde::{Deserialize, Serialize};
#[cfg(any(feature = "local-tunnel", feature = "local-dns"))]
use shadowsocks::relay::socks5::Address;
use shadowsocks::{
config::{ManagerAddr, ServerAddr, ServerConfig},
crypto::v1::CipherKind,
plugin::PluginConfig,
};
#[cfg(feature = "trust-dns")]
use trust_dns_resolver::config::{NameServerConfig, Protocol, ResolverConfig};
use crate::acl::AccessControl;
#[cfg(feature = "local-dns")]
use crate::local::dns::NameServerAddr;
#[cfg(feature = "trust-dns")]
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum SSDnsConfig {
Simple(String),
TrustDns(ResolverConfig),
}
#[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>,
#[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")]
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")]
servers: Option<Vec<SSServerExtConfig>>,
#[cfg(feature = "trust-dns")]
#[serde(skip_serializing_if = "Option::is_none")]
dns: Option<SSDnsConfig>,
#[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")]
nofile: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
ipv6_first: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug)]
struct SSServerExtConfig {
#[serde(alias = "address")]
server: String,
#[serde(alias = "port")]
server_port: u16,
password: String,
method: String,
#[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")]
timeout: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
remarks: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
}
pub type ClientConfig = ServerAddr;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConfigType {
Local,
Server,
Manager,
}
impl ConfigType {
pub fn is_local(self) -> bool {
self == ConfigType::Local
}
pub fn is_server(self) -> bool {
self == ConfigType::Server
}
pub fn is_manager(self) -> bool {
self == ConfigType::Manager
}
}
#[derive(Clone, Copy, Debug)]
pub enum Mode {
TcpOnly,
TcpAndUdp,
UdpOnly,
}
impl Mode {
pub fn enable_udp(self) -> bool {
matches!(self, Mode::UdpOnly | Mode::TcpAndUdp)
}
pub fn enable_tcp(self) -> bool {
matches!(self, Mode::TcpOnly | Mode::TcpAndUdp)
}
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Mode::TcpOnly => f.write_str("tcp_only"),
Mode::TcpAndUdp => f.write_str("tcp_and_udp"),
Mode::UdpOnly => f.write_str("udp_only"),
}
}
}
impl FromStr for Mode {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"tcp_only" => Ok(Mode::TcpOnly),
"tcp_and_udp" => Ok(Mode::TcpAndUdp),
"udp_only" => Ok(Mode::UdpOnly),
_ => Err(()),
}
}
}
cfg_if! {
if #[cfg(feature = "local-redir")] {
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter)]
pub enum RedirType {
NotSupported,
#[cfg(any(target_os = "linux", target_os = "android"))]
Redirect,
#[cfg(any(target_os = "linux", target_os = "android"))]
TProxy,
#[cfg(any(
target_os = "openbsd",
target_os = "freebsd",
target_os = "netbsd",
target_os = "solaris",
target_os = "macos",
target_os = "ios"
))]
PacketFilter,
#[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"))] {
pub fn tcp_default() -> RedirType {
RedirType::Redirect
}
pub fn udp_default() -> RedirType {
RedirType::TProxy
}
} else if #[cfg(any(target_os = "openbsd", target_os = "freebsd"))] {
pub fn tcp_default() -> RedirType {
RedirType::PacketFilter
}
pub fn udp_default() -> RedirType {
RedirType::PacketFilter
}
} else if #[cfg(any(target_os = "netbsd", target_os = "solaris", target_os = "macos", target_os = "ios"))] {
pub fn tcp_default() -> RedirType {
RedirType::PacketFilter
}
pub fn udp_default() -> RedirType {
RedirType::NotSupported
}
} else {
pub fn tcp_default() -> RedirType {
RedirType::NotSupported
}
pub fn udp_default() -> RedirType {
RedirType::NotSupported
}
}
}
pub fn is_supported(self) -> bool {
self != RedirType::NotSupported
}
pub fn name(self) -> &'static str {
match self {
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 = "openbsd",
target_os = "freebsd",
target_os = "netbsd",
target_os = "solaris",
target_os = "macos",
target_os = "ios"
))]
RedirType::PacketFilter => "pf",
#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "ios"))]
RedirType::IpFirewall => "ipfw",
}
}
pub fn available_types() -> Vec<&'static str> {
let mut v = Vec::new();
for e in Self::iter() {
match e {
RedirType::NotSupported => continue,
#[allow(unreachable_patterns)]
_ => v.push(e.name()),
}
}
v
}
}
impl Display for RedirType {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug)]
pub struct InvalidRedirType;
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 = "openbsd",
target_os = "freebsd",
target_os = "netbsd",
target_os = "solaris",
target_os = "macos",
target_os = "ios",
))]
"pf" => Ok(RedirType::PacketFilter),
#[cfg(any(
target_os = "freebsd",
target_os = "macos",
target_os = "ios",
target_os = "dragonfly"
))]
"ipfw" => Ok(RedirType::IpFirewall),
_ => Err(InvalidRedirType),
}
}
}
}
}
#[derive(Clone, Debug)]
pub enum ManagerServerHost {
Domain(String),
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())),
}
}
}
#[derive(Clone, Debug)]
pub struct ManagerConfig {
pub addr: ManagerAddr,
pub method: Option<CipherKind>,
pub timeout: Option<Duration>,
pub server_host: ManagerServerHost,
}
impl ManagerConfig {
pub fn new(addr: ManagerAddr) -> ManagerConfig {
ManagerConfig {
addr,
method: None,
timeout: None,
server_host: ManagerServerHost::default(),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ProtocolType {
Socks,
#[cfg(feature = "local-http")]
Http,
#[cfg(feature = "local-tunnel")]
Tunnel,
#[cfg(feature = "local-redir")]
Redir,
#[cfg(feature = "local-dns")]
Dns,
}
impl Default for ProtocolType {
fn default() -> ProtocolType {
ProtocolType::Socks
}
}
impl ProtocolType {
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",
}
}
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",
]
}
}
#[derive(Debug)]
pub struct ProtocolTypeError;
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),
_ => Err(ProtocolTypeError),
}
}
}
#[derive(Clone, Debug)]
pub struct Config {
pub server: Vec<ServerConfig>,
pub local_addr: Option<ClientConfig>,
#[cfg(feature = "local-tunnel")]
pub forward: Option<Address>,
#[cfg(feature = "trust-dns")]
pub dns: Option<ResolverConfig>,
pub ipv6_first: bool,
#[cfg(feature = "local-dns")]
pub dns_bind_addr: Option<ClientConfig>,
#[cfg(feature = "local-dns")]
pub local_dns_addr: Option<NameServerAddr>,
#[cfg(feature = "local-dns")]
pub remote_dns_addr: Option<Address>,
pub no_delay: bool,
pub nofile: Option<u64>,
#[cfg(any(target_os = "linux", target_os = "android"))]
pub outbound_fwmark: Option<u32>,
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
pub outbound_bind_interface: Option<OsString>,
#[cfg(target_os = "android")]
pub outbound_vpn_protect_path: Option<PathBuf>,
pub inbound_send_buffer_size: Option<u32>,
pub inbound_recv_buffer_size: Option<u32>,
pub outbound_send_buffer_size: Option<u32>,
pub outbound_recv_buffer_size: Option<u32>,
pub manager: Option<ManagerConfig>,
pub mode: Mode,
pub config_type: ConfigType,
pub local_protocol: ProtocolType,
pub udp_timeout: Option<Duration>,
pub udp_max_associations: Option<usize>,
pub udp_bind_addr: Option<ClientConfig>,
pub acl: Option<AccessControl>,
#[cfg(feature = "local-redir")]
pub tcp_redir: RedirType,
#[cfg(feature = "local-redir")]
pub udp_redir: RedirType,
#[cfg(feature = "local-flow-stat")]
pub stat_path: Option<PathBuf>,
}
#[derive(Copy, Clone, Debug)]
pub enum ErrorKind {
MissingField,
Malformed,
Invalid,
JsonParsingError,
IoError,
}
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 }
}
}
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 {
pub fn new(config_type: ConfigType) -> Config {
Config {
server: Vec::new(),
local_addr: None,
#[cfg(feature = "local-tunnel")]
forward: None,
#[cfg(feature = "trust-dns")]
dns: None,
ipv6_first: false,
#[cfg(feature = "local-dns")]
dns_bind_addr: None,
#[cfg(feature = "local-dns")]
local_dns_addr: None,
#[cfg(feature = "local-dns")]
remote_dns_addr: None,
no_delay: false,
nofile: None,
#[cfg(any(target_os = "linux", target_os = "android"))]
outbound_fwmark: None,
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios"))]
outbound_bind_interface: None,
#[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,
mode: Mode::TcpOnly,
config_type,
local_protocol: ProtocolType::default(),
udp_timeout: None,
udp_max_associations: None,
udp_bind_addr: None,
acl: None,
#[cfg(feature = "local-redir")]
tcp_redir: RedirType::tcp_default(),
#[cfg(feature = "local-redir")]
udp_redir: RedirType::udp_default(),
#[cfg(feature = "local-flow-stat")]
stat_path: None,
}
}
fn load_from_ssconfig(config: SSConfig, config_type: ConfigType) -> Result<Config, Error> {
let mut nconfig = Config::new(config_type);
match config.local_address {
Some(la) => {
let local_port = if config_type.is_local() {
let local_port = config.local_port.unwrap_or(0);
if local_port == 0 {
let err = Error::new(ErrorKind::MissingField, "missing `local_port`", None);
return Err(err);
}
local_port
} else if config_type.is_server() || config_type.is_manager() {
0
} else {
config.local_port.unwrap_or(0)
};
let local_addr = match la.parse::<IpAddr>() {
Ok(ip) => ServerAddr::from(SocketAddr::new(ip, local_port)),
Err(..) => {
ServerAddr::from((la, local_port))
}
};
nconfig.local_addr = Some(local_addr);
}
None => {
if config_type.is_local() && config.local_port.is_some() {
let ip = if config.ipv6_first.unwrap_or(false) {
Ipv6Addr::LOCALHOST.into()
} else {
Ipv4Addr::LOCALHOST.into()
};
let local_port = config.local_port.unwrap_or(0);
if local_port == 0 {
let err = Error::new(ErrorKind::MissingField, "`local_port` shouldn't be 0", None);
return Err(err);
}
let local_addr = ServerAddr::from(SocketAddr::new(ip, local_port));
nconfig.local_addr = Some(local_addr);
}
}
};
match (config.server, config.server_port, config.password, config.method) {
(Some(address), Some(port), Some(pwd), 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!("`{}` is not a supported method", m)),
);
return Err(err);
}
};
let mut nsvr = ServerConfig::new(addr, pwd, method);
if let Some(p) = config.plugin {
if !p.is_empty() {
let plugin = PluginConfig {
plugin: p,
plugin_opts: config.plugin_opts,
plugin_args: config.plugin_args.unwrap_or_default(),
};
nsvr.set_plugin(plugin);
}
}
if let Some(timeout) = config.timeout.map(Duration::from_secs) {
nsvr.set_timeout(timeout);
}
nconfig.server.push(nsvr);
}
(None, None, None, None) => (),
_ => {
let err = Error::new(
ErrorKind::Malformed,
"`server`, `server_port`, `method`, `password` must be provided together",
None,
);
return Err(err);
}
}
if let Some(servers) = config.servers {
for svr in servers {
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);
}
};
let mut nsvr = ServerConfig::new(addr, svr.password, method);
if let Some(p) = svr.plugin {
if !p.is_empty() {
let plugin = PluginConfig {
plugin: p,
plugin_opts: svr.plugin_opts,
plugin_args: svr.plugin_args.unwrap_or_default(),
};
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);
}
nconfig.server.push(nsvr);
}
}
if let Some(timeout) = config.timeout {
let timeout = Duration::from_secs(timeout);
for svr in &mut nconfig.server {
if svr.timeout().is_none() {
svr.set_timeout(timeout);
}
}
}
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(..) => {
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 manager_config = ManagerConfig::new(manager);
nconfig.manager = Some(manager_config);
}
#[cfg(feature = "trust-dns")]
{
nconfig.dns = match config.dns {
Some(SSDnsConfig::Simple(ds)) => {
match &ds[..] {
"google" => Some(ResolverConfig::google()),
"cloudflare" => Some(ResolverConfig::cloudflare()),
#[cfg(feature = "dns-over-tls")]
"cloudflare_tls" => Some(ResolverConfig::cloudflare_tls()),
#[cfg(feature = "dns-over-https")]
"cloudflare_https" => Some(ResolverConfig::cloudflare_https()),
"quad9" => Some(ResolverConfig::quad9()),
#[cfg(feature = "dns-over-tls")]
"quad9_tls" => Some(ResolverConfig::quad9_tls()),
nameservers => {
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 host[:port][,host[:port]]...",
None,
);
return Err(e);
};
c.add_name_server(NameServerConfig {
socket_addr,
protocol: Protocol::Udp,
tls_dns_name: None,
trust_nx_responses: false,
#[cfg(any(feature = "dns-over-tls", feature = "dns-over-https"))]
tls_config: None,
});
c.add_name_server(NameServerConfig {
socket_addr,
protocol: Protocol::Tcp,
tls_dns_name: None,
trust_nx_responses: false,
#[cfg(any(feature = "dns-over-tls", feature = "dns-over-https"))]
tls_config: None,
});
}
if c.name_servers().is_empty() {
None
} else {
Some(c)
}
}
}
}
Some(SSDnsConfig::TrustDns(c)) => Some(c),
None => None,
};
}
if let Some(m) = config.mode {
match m.parse::<Mode>() {
Ok(xm) => nconfig.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);
}
}
}
if let Some(b) = config.no_delay {
nconfig.no_delay = b;
}
nconfig.udp_timeout = config.udp_timeout.map(Duration::from_secs);
nconfig.udp_max_associations = config.udp_max_associations;
nconfig.nofile = config.nofile;
if let Some(f) = config.ipv6_first {
nconfig.ipv6_first = f;
}
Ok(nconfig)
}
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)
}
pub fn load_from_file(filename: &str, config_type: ConfigType) -> Result<Config, Error> {
let mut reader = OpenOptions::new().read(true).open(&Path::new(filename))?;
let mut content = String::new();
reader.read_to_string(&mut content)?;
Config::load_from_str(&content[..], config_type)
}
pub fn has_server_plugins(&self) -> bool {
for server in &self.server {
if server.plugin().is_some() {
return true;
}
}
false
}
pub fn check_integrity(&self) -> Result<(), Error> {
if self.config_type.is_local() {
match self.local_addr {
None => {
let err = Error::new(
ErrorKind::MissingField,
"missing `local_address` and `local_port` for client configuration",
None,
);
return Err(err);
}
Some(ref addr) => {
if addr.port() == 0 {
let err = Error::new(
ErrorKind::Malformed,
"`local_port` couldn't be 0 for client configuration",
None,
);
return Err(err);
}
}
}
if self.server.is_empty() {
let err = Error::new(
ErrorKind::MissingField,
"missing `servers` for client configuration",
None,
);
return Err(err);
}
}
if self.config_type.is_server() {
if self.server.is_empty() {
let err = Error::new(
ErrorKind::MissingField,
"missing any valid servers in configuration",
None,
);
return Err(err);
}
if let Some(ref addr) = self.local_addr {
if addr.port() != 0 {
let err = Error::new(
ErrorKind::Malformed,
"`local_port` must be 0 for server configuration",
None,
);
return Err(err);
}
}
}
if self.config_type.is_manager() {
if self.manager.is_none() {
let err = Error::new(
ErrorKind::MissingField,
"missing `manager_addr` and `manager_port` in configuration",
None,
);
return Err(err);
}
if let Some(ref addr) = self.local_addr {
if addr.port() != 0 {
let err = Error::new(
ErrorKind::Malformed,
"`local_port` must be 0 for server configuration",
None,
);
return Err(err);
}
}
}
for server in &self.server {
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);
}
}
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() {
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);
}
}
}
}
#[cfg(feature = "local-dns")]
if self.local_protocol == ProtocolType::Dns || self.dns_bind_addr.is_some() {
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")]
if self.local_protocol == ProtocolType::Tunnel {
if self.forward.is_none() {
let err = Error::new(ErrorKind::MissingField, "missing `forward` in configuration", None);
return Err(err);
}
}
Ok(())
}
}
impl fmt::Display for Config {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut jconf = SSConfig::default();
if let Some(ref client) = self.local_addr {
match *client {
ServerAddr::SocketAddr(ref sa) => {
jconf.local_address = Some(sa.ip().to_string());
jconf.local_port = Some(sa.port());
}
ServerAddr::DomainName(ref dname, port) => {
jconf.local_address = Some(dname.to_owned());
jconf.local_port = Some(port);
}
}
}
match self.server.len() {
0 => {}
1 if self.server[0].id().is_none() && self.server[0].remarks().is_none() => {
let svr = &self.server[0];
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 = 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.timeout = svr.timeout().map(|t| t.as_secs());
}
_ => {
let mut vsvr = Vec::new();
for svr in &self.server {
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: svr.password().to_string(),
method: svr.method().to_string(),
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())
}
}),
timeout: svr.timeout().map(|t| t.as_secs()),
remarks: svr.remarks().map(ToOwned::to_owned),
id: svr.id().map(ToOwned::to_owned),
});
}
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,
};
}
jconf.mode = Some(self.mode.to_string());
if self.no_delay {
jconf.no_delay = Some(self.no_delay);
}
#[cfg(feature = "trust-dns")]
if let Some(ref dns) = self.dns {
jconf.dns = Some(SSDnsConfig::TrustDns(dns.clone()));
}
jconf.udp_timeout = self.udp_timeout.map(|t| t.as_secs());
jconf.udp_max_associations = self.udp_max_associations;
jconf.nofile = self.nofile;
if self.ipv6_first {
jconf.ipv6_first = Some(self.ipv6_first);
}
write!(f, "{}", json5::to_string(&jconf).unwrap())
}
}