mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-11-07 00:13:33 +00:00
* Refactored and separate library into crates
- shadowsocks: the core feature of shadowsocks
- shadowsocks-service: library for building shadowsocks services
- dns, http, redir, socks, tunnel
- load balancer
- shadowsocks-rust: release binaries
fix #347
* unified DnsResolver implementation
* unified local service common parameters into ServiceContext
- ServiceContext is common parameters shared between all local
implementations
- Completely removed https local support
* add #292 reply attack protection
* migrated redir local server
* support customizing outbound socket bind address
* manager outbound socket should accepts connect_opts
* republic local implementations
* socks5 udp server should always listen to client address
* socks4 controlled by local-socks4 feature
* socks4 also obey mode configuration
* socks server tcp cannot be disable. add support of udp-bind-addr parameter
* add udp-bind-addr for customizing udp-relay bind-addr
* local-dns infra, support customizing resolver
* fully implements DNS relay server
* support binding to specific interface on Linux-like platform
* tcp cannot be disabled in socks
* enable local-flow-stat
* fixed windows build
* fixed android specific warnings and compile errors
* allow udp_only mode in socks5
* dns relay listens to both TCP and UDP, mode controls outbound upstreams
* dns relay retries twice if request failed
* doc
* fix DnsClient typo
* fix stream EncryptWriter bug
* allow disable logging output
updated dependencies
* add readme
* refine doc
* remove depending on trust-dns-client
* socks4/4a client
* allow socks5 udp_only mode, fixes compile warning
* create standalone socks5 UDP relay server
- socks5 UDP association full cone (NAT2)
* server udp relay supports full cone (NAT2)
* acl moved to crate root
* redir udp relay support full cone (NAT2)
* standard socks5 udp test must use tcp_and_udp mode
* set server context fields with pub APIs
* udp_max_associations and udp_timeout default value set in Config
* local dns resolver retry with fixed attempts
* max_udp_association keeps unlimited by default
* fixed logging binary name
* pops first exited future result for local and server
* update reverse target index cache
* fix ProxyClientStreamWriteHalf that allows sending empty buffers
ref #232
* remove unused import when socks4 is disabled
* make balancer become a globally shared object
* print plugin exit status
* control local, server, manager services in features
242 lines
8.5 KiB
Rust
242 lines
8.5 KiB
Rust
//! This is a binary running in the server environment
|
|
//!
|
|
//! You have to provide all needed configuration attributes via command line parameters,
|
|
//! or you could specify a configuration file. The format of configuration file is defined
|
|
//! in mod `config`.
|
|
//!
|
|
//! *It should be notice that the extented configuration file is not suitable for the server
|
|
//! side.*
|
|
|
|
use std::{
|
|
net::{IpAddr, SocketAddr},
|
|
time::Duration,
|
|
};
|
|
|
|
use clap::{clap_app, Arg};
|
|
use futures::future::{self, Either};
|
|
use log::info;
|
|
use tokio::{self, runtime::Builder};
|
|
|
|
use shadowsocks_service::{
|
|
acl::AccessControl,
|
|
config::{Config, ConfigType, ManagerConfig, ManagerServerHost, Mode},
|
|
run_manager,
|
|
shadowsocks::{
|
|
config::{ManagerAddr, ServerAddr},
|
|
crypto::v1::{available_ciphers, CipherKind},
|
|
},
|
|
};
|
|
|
|
#[cfg(feature = "logging")]
|
|
use self::common::logging;
|
|
use self::common::{monitor, validator};
|
|
|
|
mod common;
|
|
|
|
/// shadowsocks version
|
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
|
|
fn main() {
|
|
#[allow(unused_mut)]
|
|
let mut app = clap_app!(shadowsocks =>
|
|
(version: VERSION)
|
|
(about: "A fast tunnel proxy that helps you bypass firewalls.")
|
|
|
|
(@arg UDP_ONLY: -u conflicts_with[TCP_AND_UDP] "Server mode UDP_ONLY")
|
|
(@arg TCP_AND_UDP: -U conflicts_with[UDP_ONLY] "Server mode TCP_AND_UDP")
|
|
|
|
(@arg CONFIG: -c --config +takes_value required_unless("MANAGER_ADDRESS") +next_line_help
|
|
"Shadowsocks configuration file (https://shadowsocks.org/en/config/quick-guide.html), \
|
|
the only required fields are \"manager_address\" and \"manager_port\". \
|
|
Servers defined will be created when process is started.")
|
|
|
|
(@arg BIND_ADDR: -b --("bind-addr") +takes_value "Bind address, outbound socket will bind this address")
|
|
(@arg SERVER_HOST: -s --("server-host") +takes_value "Host name or IP address of your remote server")
|
|
|
|
(@arg NO_DELAY: --("no-delay") !takes_value "Set TCP_NODELAY option for socket")
|
|
|
|
(@arg MANAGER_ADDRESS: --("manager-address") +takes_value {validator::validate_manager_addr} "ShadowSocks Manager (ssmgr) address, could be ip:port, domain:port or /path/to/unix.sock")
|
|
(@arg ENCRYPT_METHOD: -m --("encrypt-method") +takes_value possible_values(available_ciphers()) +next_line_help "Default encryption method")
|
|
(@arg TIMEOUT: --timeout +takes_value {validator::validate_u64} "Default timeout seconds for TCP relay")
|
|
|
|
(@arg NOFILE: -n --nofile +takes_value "Set RLIMIT_NOFILE with both soft and hard limit (only for *nix systems)")
|
|
(@arg ACL: --acl +takes_value "Path to ACL (Access Control List)")
|
|
);
|
|
|
|
#[cfg(feature = "logging")]
|
|
{
|
|
app = clap_app!(@app (app)
|
|
(@arg VERBOSE: -v ... "Set log level")
|
|
(@arg LOG_WITHOUT_TIME: --("log-without-time") "Log without datetime prefix")
|
|
(@arg LOG_CONFIG: --("log-config") +takes_value "log4rs configuration file")
|
|
);
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
{
|
|
app = clap_app!(@app (app)
|
|
(@arg DAEMONIZE: -d --("daemonize") "Daemonize")
|
|
(@arg DAEMONIZE_PID_PATH: --("daemonize-pid") +takes_value "File path to store daemonized process's PID")
|
|
);
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
app = clap_app!(@app (app)
|
|
(@arg OUTBOUND_FWMARK: --("outbound-fwmark") +takes_value {validator::validate_u32} "Set SO_MARK option for outbound socket")
|
|
(@arg OUTBOUND_BIND_INTERFACE: --("outbound-bind-interface") +takes_value "Set SO_BINDTODEVICE option for outbound socket")
|
|
);
|
|
}
|
|
|
|
let matches = app
|
|
.arg(
|
|
Arg::with_name("IPV6_FIRST")
|
|
.short("6")
|
|
.help("Resolve hostname to IPv6 address first"),
|
|
)
|
|
.get_matches();
|
|
|
|
#[cfg(feature = "logging")]
|
|
match matches.value_of("LOG_CONFIG") {
|
|
Some(path) => {
|
|
logging::init_with_file(path);
|
|
}
|
|
None => {
|
|
logging::init_with_config("ssmanager", &matches);
|
|
}
|
|
}
|
|
|
|
let mut config = match matches.value_of("CONFIG") {
|
|
Some(cpath) => match Config::load_from_file(cpath, ConfigType::Manager) {
|
|
Ok(cfg) => cfg,
|
|
Err(err) => {
|
|
panic!("loading config \"{}\", {}", cpath, err);
|
|
}
|
|
},
|
|
None => Config::new(ConfigType::Manager),
|
|
};
|
|
|
|
if let Some(bind_addr) = matches.value_of("BIND_ADDR") {
|
|
let bind_addr = match bind_addr.parse::<IpAddr>() {
|
|
Ok(ip) => ServerAddr::from(SocketAddr::new(ip, 0)),
|
|
Err(..) => ServerAddr::from((bind_addr, 0)),
|
|
};
|
|
|
|
config.local_addr = Some(bind_addr);
|
|
}
|
|
|
|
if matches.is_present("UDP_ONLY") {
|
|
if config.mode.enable_tcp() {
|
|
config.mode = Mode::TcpAndUdp;
|
|
} else {
|
|
config.mode = Mode::UdpOnly;
|
|
}
|
|
}
|
|
|
|
if matches.is_present("TCP_AND_UDP") {
|
|
config.mode = Mode::TcpAndUdp;
|
|
}
|
|
|
|
if matches.is_present("NO_DELAY") {
|
|
config.no_delay = true;
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
if let Some(mark) = matches.value_of("OUTBOUND_FWMARK") {
|
|
config.outbound_fwmark = Some(mark.parse::<u32>().expect("an unsigned integer for `outbound-fwmark`"));
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
if let Some(iface) = matches.value_of("OUTBOUND_BIND_INTERFACE") {
|
|
config.outbound_bind_interface = Some(From::from(iface.to_owned()));
|
|
}
|
|
|
|
if let Some(m) = matches.value_of("MANAGER_ADDRESS") {
|
|
if let Some(ref mut manager_config) = config.manager {
|
|
manager_config.addr = m.parse::<ManagerAddr>().expect("manager-address");
|
|
} else {
|
|
config.manager = Some(ManagerConfig::new(m.parse::<ManagerAddr>().expect("manager-address")));
|
|
}
|
|
}
|
|
|
|
if let Some(ref mut manager_config) = config.manager {
|
|
if let Some(m) = matches.value_of("ENCRYPT_METHOD") {
|
|
manager_config.method = Some(m.parse::<CipherKind>().expect("encrypt-method"));
|
|
}
|
|
|
|
if let Some(t) = matches.value_of("TIMEOUT") {
|
|
manager_config.timeout = Some(Duration::from_secs(t.parse::<u64>().expect("timeout")));
|
|
}
|
|
|
|
if let Some(sh) = matches.value_of("SERVER_HOST") {
|
|
manager_config.server_host = sh.parse::<ManagerServerHost>().unwrap();
|
|
}
|
|
}
|
|
|
|
if let Some(nofile) = matches.value_of("NOFILE") {
|
|
config.nofile = Some(nofile.parse::<u64>().expect("an unsigned integer for `nofile`"));
|
|
}
|
|
|
|
if let Some(acl_file) = matches.value_of("ACL") {
|
|
let acl = match AccessControl::load_from_file(acl_file) {
|
|
Ok(acl) => acl,
|
|
Err(err) => {
|
|
panic!("loading ACL \"{}\", {}", acl_file, err);
|
|
}
|
|
};
|
|
config.acl = Some(acl);
|
|
}
|
|
|
|
if matches.is_present("IPV6_FIRST") {
|
|
config.ipv6_first = true;
|
|
}
|
|
|
|
// DONE reading options
|
|
|
|
if config.manager.is_none() {
|
|
eprintln!(
|
|
"missing `manager_address`, consider specifying it by --manager-address command line option, \
|
|
or \"manager_address\" and \"manager_port\" keys in configuration file"
|
|
);
|
|
println!("{}", matches.usage());
|
|
return;
|
|
}
|
|
|
|
if let Err(err) = config.check_integrity() {
|
|
eprintln!("config integrity check failed, {}", err);
|
|
println!("{}", matches.usage());
|
|
return;
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
if matches.is_present("DAEMONIZE") {
|
|
use self::common::daemonize;
|
|
daemonize::daemonize(matches.value_of("DAEMONIZE_PID_PATH"));
|
|
}
|
|
|
|
info!("shadowsocks {}", VERSION);
|
|
|
|
let mut builder = if cfg!(feature = "single-threaded") {
|
|
Builder::new_current_thread()
|
|
} else {
|
|
Builder::new_multi_thread()
|
|
};
|
|
let runtime = builder.enable_all().build().expect("create tokio Runtime");
|
|
runtime.block_on(async move {
|
|
let abort_signal = monitor::create_signal_monitor();
|
|
let server = run_manager(config);
|
|
|
|
tokio::pin!(abort_signal);
|
|
tokio::pin!(server);
|
|
|
|
match future::select(server, abort_signal).await {
|
|
// Server future resolved without an error. This should never happen.
|
|
Either::Left((Ok(..), ..)) => panic!("server exited unexpectly"),
|
|
// Server future resolved with error, which are listener errors in most cases
|
|
Either::Left((Err(err), ..)) => panic!("aborted with {}", err),
|
|
// The abort signal future resolved. Means we should just exit.
|
|
Either::Right(_) => (),
|
|
}
|
|
});
|
|
}
|