use std::{
convert::{From, Infallible},
default::Default,
error,
fmt::{self, Debug, Display, Formatter},
fs::OpenOptions,
io::{self, Read},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
option::Option,
path::{Path, PathBuf},
str::FromStr,
string::ToString,
time::Duration,
};
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
use bytes::Bytes;
use cfg_if::cfg_if;
use log::error;
use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[cfg(feature = "trust-dns")]
use trust_dns_resolver::config::{NameServerConfigGroup, ResolverConfig};
use url::{self, Url};
use crate::{
acl::AccessControl,
context::Context,
crypto::cipher::CipherType,
plugin::PluginConfig,
relay::{dns_resolver::resolve_bind_addr, socks5::Address},
};
#[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")]
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>>,
#[serde(skip_serializing_if = "Option::is_none")]
dns: Option<String>,
#[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 {
address: String,
port: u16,
password: String,
method: 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")]
timeout: Option<u64>,
}
#[derive(Clone, Debug)]
pub enum ServerAddr {
SocketAddr(SocketAddr),
DomainName(String, u16),
}
impl ServerAddr {
pub fn host(&self) -> String {
match *self {
ServerAddr::SocketAddr(ref s) => s.ip().to_string(),
ServerAddr::DomainName(ref dm, _) => dm.clone(),
}
}
pub fn port(&self) -> u16 {
match *self {
ServerAddr::SocketAddr(ref s) => s.port(),
ServerAddr::DomainName(_, p) => p,
}
}
pub async fn bind_addr(&self, context: &Context) -> io::Result<SocketAddr> {
match resolve_bind_addr(context, self).await {
Ok(s) => Ok(s),
Err(err) => {
error!("Failed to resolve {} for bind(), error: {}", self, err);
Err(err)
}
}
}
}
#[derive(Debug)]
pub struct ServerAddrError;
impl FromStr for ServerAddr {
type Err = ServerAddrError;
fn from_str(s: &str) -> Result<ServerAddr, ServerAddrError> {
match s.parse::<SocketAddr>() {
Ok(addr) => Ok(ServerAddr::SocketAddr(addr)),
Err(..) => {
let mut sp = s.split(':');
match (sp.next(), sp.next()) {
(Some(dn), Some(port)) => match port.parse::<u16>() {
Ok(port) => Ok(ServerAddr::DomainName(dn.to_owned(), port)),
Err(..) => Err(ServerAddrError),
},
_ => Err(ServerAddrError),
}
}
}
}
}
impl Display for ServerAddr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
ServerAddr::SocketAddr(ref a) => write!(f, "{}", a),
ServerAddr::DomainName(ref d, port) => write!(f, "{}:{}", d, port),
}
}
}
impl From<SocketAddr> for ServerAddr {
fn from(addr: SocketAddr) -> ServerAddr {
ServerAddr::SocketAddr(addr)
}
}
impl<I: Into<String>> From<(I, u16)> for ServerAddr {
fn from((dname, port): (I, u16)) -> ServerAddr {
ServerAddr::DomainName(dname.into(), port)
}
}
#[derive(Clone, Debug)]
pub struct ServerConfig {
addr: ServerAddr,
password: String,
method: CipherType,
timeout: Option<Duration>,
enc_key: Bytes,
plugin: Option<PluginConfig>,
plugin_addr: Option<ServerAddr>,
}
impl ServerConfig {
pub fn new(
addr: ServerAddr,
pwd: String,
method: CipherType,
timeout: Option<Duration>,
plugin: Option<PluginConfig>,
) -> ServerConfig {
let enc_key = method.bytes_to_key(pwd.as_bytes());
ServerConfig {
addr,
password: pwd,
method,
timeout,
enc_key,
plugin,
plugin_addr: None,
}
}
pub fn basic(addr: SocketAddr, password: String, method: CipherType) -> ServerConfig {
ServerConfig::new(ServerAddr::SocketAddr(addr), password, method, None, None)
}
pub fn set_method(&mut self, t: CipherType, pwd: String) {
self.password = pwd;
self.method = t;
self.enc_key = t.bytes_to_key(self.password.as_bytes());
}
pub fn set_plugin(&mut self, p: PluginConfig) {
self.plugin = Some(p);
}
pub fn set_addr(&mut self, a: ServerAddr) {
self.addr = a;
}
pub fn addr(&self) -> &ServerAddr {
&self.addr
}
pub fn key(&self) -> &[u8] {
&self.enc_key[..]
}
pub fn clone_key(&self) -> Bytes {
self.enc_key.clone()
}
pub fn password(&self) -> &str {
&self.password[..]
}
pub fn method(&self) -> CipherType {
self.method
}
pub fn timeout(&self) -> Option<Duration> {
self.timeout
}
pub fn plugin(&self) -> Option<&PluginConfig> {
self.plugin.as_ref()
}
pub fn set_plugin_addr(&mut self, a: ServerAddr) {
self.plugin_addr = Some(a);
}
pub fn plugin_addr(&self) -> Option<&ServerAddr> {
self.plugin_addr.as_ref()
}
pub fn external_addr(&self) -> &ServerAddr {
self.plugin_addr.as_ref().unwrap_or(&self.addr)
}
pub fn to_qrcode_url(&self) -> String {
let param = format!("{}:{}@{}", self.method(), self.password(), self.addr());
format!("ss://{}", encode_config(¶m, URL_SAFE_NO_PAD))
}
pub fn to_url(&self) -> String {
let user_info = format!("{}:{}", self.method(), self.password());
let encoded_user_info = encode_config(&user_info, URL_SAFE_NO_PAD);
let mut url = format!("ss://{}@{}", encoded_user_info, self.addr());
if let Some(c) = self.plugin() {
let mut plugin = c.plugin.clone();
if let Some(ref opt) = c.plugin_opt {
plugin += ";";
plugin += opt;
}
let plugin_param = [("plugin", &plugin)];
url += "/?";
url += &serde_urlencoded::to_string(&plugin_param).unwrap();
}
url
}
pub fn from_url(encoded: &str) -> Result<ServerConfig, UrlParseError> {
let parsed = Url::parse(encoded).map_err(UrlParseError::from)?;
if parsed.scheme() != "ss" {
return Err(UrlParseError::InvalidScheme);
}
let user_info = parsed.username();
let account = match decode_config(user_info, URL_SAFE_NO_PAD) {
Ok(account) => match String::from_utf8(account) {
Ok(ac) => ac,
Err(..) => {
return Err(UrlParseError::InvalidAuthInfo);
}
},
Err(err) => {
error!("Failed to parse UserInfo with Base64, err: {}", err);
return Err(UrlParseError::InvalidUserInfo);
}
};
let host = match parsed.host_str() {
Some(host) => host,
None => return Err(UrlParseError::MissingHost),
};
let port = parsed.port().unwrap_or(8388);
let addr = format!("{}:{}", host, port);
let mut sp2 = account.splitn(2, ':');
let (method, pwd) = match (sp2.next(), sp2.next()) {
(Some(m), Some(p)) => (m, p),
_ => panic!("Malformed input"),
};
let addr = match addr.parse::<ServerAddr>() {
Ok(a) => a,
Err(err) => {
error!("Failed to parse \"{}\" to ServerAddr, err: {:?}", addr, err);
return Err(UrlParseError::InvalidServerAddr);
}
};
let mut plugin = None;
if let Some(q) = parsed.query() {
let query = match serde_urlencoded::from_bytes::<Vec<(String, String)>>(q.as_bytes()) {
Ok(q) => q,
Err(err) => {
error!("Failed to parse QueryString, err: {}", err);
return Err(UrlParseError::InvalidQueryString);
}
};
for (key, value) in query {
if key != "plugin" {
continue;
}
let mut vsp = value.splitn(2, ';');
match vsp.next() {
None => {}
Some(p) => {
plugin = Some(PluginConfig {
plugin: p.to_owned(),
plugin_opt: vsp.next().map(ToOwned::to_owned),
})
}
}
}
}
let svrconfig = ServerConfig::new(addr, pwd.to_owned(), method.parse().unwrap(), None, plugin);
Ok(svrconfig)
}
}
impl FromStr for ServerConfig {
type Err = UrlParseError;
fn from_str(s: &str) -> Result<ServerConfig, Self::Err> {
ServerConfig::from_url(s)
}
}
#[derive(Debug, Clone)]
pub enum ManagerAddr {
SocketAddr(SocketAddr),
DomainName(String, u16),
#[cfg(unix)]
UnixSocketAddr(PathBuf),
}
#[derive(Debug)]
pub struct ManagerAddrError;
impl FromStr for ManagerAddr {
type Err = ManagerAddrError;
fn from_str(s: &str) -> Result<ManagerAddr, ManagerAddrError> {
match s.find(':') {
Some(pos) => {
match s.parse::<SocketAddr>() {
Ok(saddr) => Ok(ManagerAddr::SocketAddr(saddr)),
Err(..) => {
let (sdomain, sport) = s.split_at(pos);
let (sdomain, sport) = (sdomain.trim(), sport[1..].trim());
match sport.parse::<u16>() {
Ok(port) => Ok(ManagerAddr::DomainName(sdomain.to_owned(), port)),
Err(..) => Err(ManagerAddrError),
}
}
}
}
#[cfg(unix)]
None => {
Ok(ManagerAddr::UnixSocketAddr(PathBuf::from(s)))
}
#[cfg(not(unix))]
None => Err(ManagerAddrError),
}
}
}
impl Display for ManagerAddr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
ManagerAddr::SocketAddr(ref saddr) => fmt::Display::fmt(saddr, f),
ManagerAddr::DomainName(ref dname, port) => write!(f, "{}:{}", dname, port),
#[cfg(unix)]
ManagerAddr::UnixSocketAddr(ref path) => fmt::Display::fmt(&path.display(), f),
}
}
}
impl From<SocketAddr> for ManagerAddr {
fn from(addr: SocketAddr) -> ManagerAddr {
ManagerAddr::SocketAddr(addr)
}
}
impl<'a> From<(&'a str, u16)> for ManagerAddr {
fn from((dname, port): (&'a str, u16)) -> ManagerAddr {
ManagerAddr::DomainName(dname.to_owned(), port)
}
}
impl From<(String, u16)> for ManagerAddr {
fn from((dname, port): (String, u16)) -> ManagerAddr {
ManagerAddr::DomainName(dname, port)
}
}
#[cfg(unix)]
impl From<PathBuf> for ManagerAddr {
fn from(p: PathBuf) -> ManagerAddr {
ManagerAddr::UnixSocketAddr(p)
}
}
#[derive(Debug, Clone)]
pub enum UrlParseError {
ParseError(url::ParseError),
InvalidScheme,
InvalidUserInfo,
MissingHost,
InvalidAuthInfo,
InvalidServerAddr,
InvalidQueryString,
}
impl From<url::ParseError> for UrlParseError {
fn from(err: url::ParseError) -> UrlParseError {
UrlParseError::ParseError(err)
}
}
impl fmt::Display for UrlParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
UrlParseError::ParseError(ref err) => fmt::Display::fmt(err, f),
UrlParseError::InvalidScheme => write!(f, "URL must have \"ss://\" scheme"),
UrlParseError::InvalidUserInfo => write!(f, "invalid user info"),
UrlParseError::MissingHost => write!(f, "missing host"),
UrlParseError::InvalidAuthInfo => write!(f, "invalid authentication info"),
UrlParseError::InvalidServerAddr => write!(f, "invalid server address"),
UrlParseError::InvalidQueryString => write!(f, "invalid query string"),
}
}
}
impl error::Error for UrlParseError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
UrlParseError::ParseError(ref err) => Some(err as &dyn error::Error),
UrlParseError::InvalidScheme => None,
UrlParseError::InvalidUserInfo => None,
UrlParseError::MissingHost => None,
UrlParseError::InvalidAuthInfo => None,
UrlParseError::InvalidServerAddr => None,
UrlParseError::InvalidQueryString => None,
}
}
}
pub type ClientConfig = ServerAddr;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConfigType {
Socks5Local,
#[cfg(feature = "local-socks4")]
Socks4Local,
#[cfg(feature = "local-http")]
HttpLocal,
#[cfg(all(
feature = "local-http",
any(feature = "local-http-native-tls", feature = "local-http-rustls")
))]
HttpsLocal,
#[cfg(feature = "local-tunnel")]
TunnelLocal,
#[cfg(feature = "local-redir")]
RedirLocal,
#[cfg(feature = "local-dns-relay")]
DnsLocal,
Server,
Manager,
}
impl ConfigType {
pub fn is_local(self) -> bool {
match self {
ConfigType::Socks5Local => true,
#[cfg(feature = "local-socks4")]
ConfigType::Socks4Local => true,
#[cfg(feature = "local-dns-relay")]
ConfigType::DnsLocal => true,
#[cfg(feature = "local-tunnel")]
ConfigType::TunnelLocal => true,
#[cfg(feature = "local-http")]
ConfigType::HttpLocal => true,
#[cfg(all(
feature = "local-http",
any(feature = "local-http-native-tls", feature = "local-http-rustls")
))]
ConfigType::HttpsLocal => true,
#[cfg(feature = "local-redir")]
ConfigType::RedirLocal => true,
ConfigType::Server | ConfigType::Manager => false,
}
}
pub fn is_server(self) -> bool {
match self {
ConfigType::Socks5Local => false,
#[cfg(feature = "local-socks4")]
ConfigType::Socks4Local => false,
#[cfg(feature = "local-dns-relay")]
ConfigType::DnsLocal => false,
#[cfg(feature = "local-tunnel")]
ConfigType::TunnelLocal => false,
#[cfg(feature = "local-http")]
ConfigType::HttpLocal => false,
#[cfg(all(
feature = "local-http",
any(feature = "local-http-native-tls", feature = "local-http-rustls")
))]
ConfigType::HttpsLocal => false,
#[cfg(feature = "local-redir")]
ConfigType::RedirLocal => false,
ConfigType::Manager => false,
ConfigType::Server => true,
}
}
pub fn is_manager(self) -> bool {
matches!(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(()),
}
}
}
#[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(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))
}
}
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())),
}
}
}
impl ManagerServerHost {
pub async fn bind_addr(&self, ctx: &Context, port: u16) -> io::Result<SocketAddr> {
match *self {
ManagerServerHost::Domain(ref domain) => {
let vaddrs = ctx.dns_resolve(domain, port).await?;
Ok(vaddrs[0])
}
ManagerServerHost::Ip(ip) => Ok(SocketAddr::new(ip, port)),
}
}
}
#[derive(Clone, Debug)]
pub struct ManagerConfig {
pub addr: ManagerAddr,
pub method: Option<CipherType>,
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(),
}
}
pub async fn bind_addr(&self, ctx: &Context, port: u16) -> io::Result<SocketAddr> {
self.server_host.bind_addr(ctx, port).await
}
}
#[derive(Clone, Debug)]
pub struct Config {
pub server: Vec<ServerConfig>,
pub local_addr: Option<ClientConfig>,
pub forward: Option<Address>,
pub dns: Option<String>,
pub mode: Mode,
pub no_delay: bool,
pub manager: Option<ManagerConfig>,
pub config_type: ConfigType,
pub udp_timeout: Option<Duration>,
pub udp_max_associations: Option<usize>,
pub nofile: Option<u64>,
pub acl: Option<AccessControl>,
pub tcp_redir: RedirType,
pub udp_redir: RedirType,
#[cfg(feature = "local-flow-stat")]
pub stat_path: Option<PathBuf>,
pub protect_path: Option<PathBuf>,
pub local_dns_path: Option<PathBuf>,
#[cfg(feature = "local-dns-relay")]
pub dns_local_addr: Option<ClientConfig>,
pub local_dns_addr: Option<SocketAddr>,
pub remote_dns_addr: Option<Address>,
pub ipv6_first: bool,
#[cfg(feature = "local-http-native-tls")]
pub tls_identity_path: Option<PathBuf>,
#[cfg(feature = "local-http-native-tls")]
pub tls_identity_password: Option<String>,
#[cfg(feature = "local-http-rustls")]
pub tls_identity_certificate_path: Option<PathBuf>,
#[cfg(feature = "local-http-rustls")]
pub tls_identity_private_key_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,
forward: None,
dns: None,
mode: Mode::TcpOnly,
no_delay: false,
manager: None,
config_type,
udp_timeout: None,
udp_max_associations: None,
nofile: None,
acl: None,
tcp_redir: RedirType::tcp_default(),
udp_redir: RedirType::udp_default(),
#[cfg(feature = "local-flow-stat")]
stat_path: None,
protect_path: None,
local_dns_path: None,
#[cfg(feature = "local-dns-relay")]
dns_local_addr: None,
local_dns_addr: None,
remote_dns_addr: None,
ipv6_first: false,
#[cfg(feature = "local-http-native-tls")]
tls_identity_path: None,
#[cfg(feature = "local-http-native-tls")]
tls_identity_password: None,
#[cfg(feature = "local-http-rustls")]
tls_identity_certificate_path: None,
#[cfg(feature = "local-http-rustls")]
tls_identity_private_key_path: None,
}
}
fn load_from_ssconfig(config: SSConfig, config_type: ConfigType) -> Result<Config, Error> {
let mut nconfig = Config::new(config_type);
if let Some(la) = config.local_address {
let port = match config.local_port {
None => {
let err = Error::new(ErrorKind::MissingField, "missing `local_port`", None);
return Err(err);
}
Some(p) => {
if config_type.is_server() {
0
} else {
p
}
}
};
let local = match la.parse::<IpAddr>() {
Ok(ip) => ServerAddr::from(SocketAddr::new(ip, port)),
Err(..) => {
ServerAddr::from((la, port))
}
};
nconfig.local_addr = Some(local);
}
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::<CipherType>() {
Ok(m) => m,
Err(..) => {
let err = Error::new(
ErrorKind::Invalid,
"unsupported method",
Some(format!("`{}` is not a supported method", m)),
);
return Err(err);
}
};
let plugin = match config.plugin {
None => None,
Some(plugin) => Some(PluginConfig {
plugin,
plugin_opt: config.plugin_opts,
}),
};
let timeout = config.timeout.map(Duration::from_secs);
let nsvr = ServerConfig::new(addr, pwd, method, timeout, plugin);
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 {
let addr = match svr.address.parse::<Ipv4Addr>() {
Ok(v4) => ServerAddr::SocketAddr(SocketAddr::V4(SocketAddrV4::new(v4, svr.port))),
Err(..) => match svr.address.parse::<Ipv6Addr>() {
Ok(v6) => ServerAddr::SocketAddr(SocketAddr::V6(SocketAddrV6::new(v6, svr.port, 0, 0))),
Err(..) => ServerAddr::DomainName(svr.address, svr.port),
},
};
let method = match svr.method.parse::<CipherType>() {
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 plugin = match svr.plugin {
None => None,
Some(p) => Some(PluginConfig {
plugin: p,
plugin_opt: svr.plugin_opts,
}),
};
let timeout = svr.timeout.or(config.timeout).map(Duration::from_secs);
let nsvr = ServerConfig::new(addr, svr.password, method, timeout, plugin);
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.timeout = Some(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);
}
nconfig.dns = config.dns;
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)
}
#[doc(hidden)]
#[cfg(feature = "trust-dns")]
pub fn get_dns_config(&self) -> Option<ResolverConfig> {
self.dns.as_ref().and_then(|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()),
_ => {
match ds.parse::<IpAddr>() {
Ok(ip) => Some(ResolverConfig::from_parts(
None,
vec![],
NameServerConfigGroup::from_ips_clear(&[ip], 53),
)),
Err(..) => {
error!(
"Failed to parse DNS \"{}\" in config to IpAddr, fallback to system config",
ds
);
None
}
}
}
}
})
}
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() {
if self.local_addr.is_some() {
return Ok(());
}
let err = Error::new(
ErrorKind::MissingField,
"missing `local_address` and `local_port` for client configuration",
None,
);
return Err(err);
}
if self.config_type.is_server() {
if !self.server.is_empty() {
return Ok(());
}
let err = Error::new(
ErrorKind::MissingField,
"missing any valid servers in configuration",
None,
);
return Err(err);
}
if self.config_type.is_manager() {
if self.manager.is_some() {
return Ok(());
}
let err = Error::new(
ErrorKind::MissingField,
"missing `manager_addr` and `manager_port` 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 => {
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_opt.clone());
jconf.timeout = svr.timeout().map(|t| t.as_secs());
}
_ => {
let mut vsvr = Vec::new();
for svr in &self.server {
vsvr.push(SSServerExtConfig {
address: match *svr.addr() {
ServerAddr::SocketAddr(ref sa) => sa.ip().to_string(),
ServerAddr::DomainName(ref dm, ..) => dm.to_string(),
},
port: match *svr.addr() {
ServerAddr::SocketAddr(ref sa) => sa.port(),
ServerAddr::DomainName(.., port) => port,
},
password: svr.password().to_string(),
method: svr.method().to_string(),
plugin: svr.plugin().map(|p| p.plugin.to_string()),
plugin_opts: svr.plugin().and_then(|p| p.plugin_opt.clone()),
timeout: svr.timeout().map(|t| t.as_secs()),
});
}
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);
}
if let Some(ref dns) = self.dns {
jconf.dns = Some(dns.to_string());
}
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())
}
}