shadowsocks_rust/service/
manager.rs

1//! Server Manager launchers
2
3use std::{future::Future, net::IpAddr, path::PathBuf, process::ExitCode, time::Duration};
4
5use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint, builder::PossibleValuesParser};
6use futures::future::{self, Either};
7use log::{info, trace};
8use tokio::{
9    self,
10    runtime::{Builder, Runtime},
11};
12
13#[cfg(unix)]
14use shadowsocks_service::config::ManagerServerMode;
15use shadowsocks_service::{
16    acl::AccessControl,
17    config::{Config, ConfigType, ManagerConfig, ManagerServerHost},
18    run_manager,
19    shadowsocks::{
20        config::{ManagerAddr, Mode},
21        crypto::{CipherKind, available_ciphers},
22        plugin::PluginConfig,
23    },
24};
25
26#[cfg(feature = "logging")]
27use crate::logging;
28use crate::{
29    config::{Config as ServiceConfig, RuntimeMode},
30    error::{ShadowsocksError, ShadowsocksResult},
31    monitor, vparser,
32};
33
34/// Defines command line options
35pub fn define_command_line_options(mut app: Command) -> Command {
36    app = app
37        .arg(
38            Arg::new("CONFIG")
39                .short('c')
40                .long("config")
41                .num_args(1)
42                .action(ArgAction::Set)
43                .value_parser(clap::value_parser!(PathBuf))
44                .value_hint(ValueHint::FilePath)
45                .help("Shadowsocks configuration file (https://shadowsocks.org/doc/configs.html), the only required fields are \"manager_address\" and \"manager_port\". Servers defined will be created when process is started."),
46        )
47        .arg(
48            Arg::new("UDP_ONLY")
49                .short('u')
50                .action(ArgAction::SetTrue)
51                .conflicts_with("TCP_AND_UDP")
52                .help("Server mode UDP_ONLY"),
53        )
54        .arg(
55            Arg::new("TCP_AND_UDP")
56                .short('U')
57                .action(ArgAction::SetTrue)
58                .help("Server mode TCP_AND_UDP"),
59        )
60        .arg(
61            Arg::new("OUTBOUND_BIND_ADDR")
62                .short('b')
63                .long("outbound-bind-addr")
64                .num_args(1)
65                .action(ArgAction::Set)
66                .alias("bind-addr")
67                .value_parser(vparser::parse_ip_addr)
68                .help("Bind address, outbound socket will bind this address"),
69        )
70        .arg(
71            Arg::new("OUTBOUND_BIND_INTERFACE")
72                .long("outbound-bind-interface")
73                .num_args(1)
74                .action(ArgAction::Set)
75                .help("Set SO_BINDTODEVICE / IP_BOUND_IF / IP_UNICAST_IF option for outbound socket"),
76        )
77        .arg(Arg::new("SERVER_HOST").short('s').long("server-host").num_args(1).action(ArgAction::Set).value_parser(vparser::parse_manager_server_host).help("Host name or IP address of your remote server"))
78        .arg(
79            Arg::new("MANAGER_ADDR")
80                .long("manager-addr")
81                .num_args(1)
82                .action(ArgAction::Set)
83                .alias("manager-address")
84                .value_parser(vparser::parse_manager_addr)
85                .help("ShadowSocks Manager (ssmgr) address, could be ip:port, domain:port or /path/to/unix.sock"),
86        )
87        .group(ArgGroup::new("SERVER_CONFIG").arg("MANAGER_ADDR"))
88        .arg(Arg::new("ENCRYPT_METHOD").short('m').long("encrypt-method").num_args(1).action(ArgAction::Set).value_parser(PossibleValuesParser::new(available_ciphers())).help("Default encryption method"))
89        .arg(Arg::new("TIMEOUT").long("timeout").num_args(1).action(ArgAction::Set).value_parser(clap::value_parser!(u64)).help("Default timeout seconds for TCP relay"))
90        .arg(
91            Arg::new("PLUGIN")
92                .long("plugin")
93                .num_args(1)
94                .action(ArgAction::Set)
95                .value_hint(ValueHint::CommandName)
96                .help("Default SIP003 (https://shadowsocks.org/doc/sip003.html) plugin"),
97        )
98        .arg(
99            Arg::new("PLUGIN_MODE")
100                .long("plugin-mode")
101                .num_args(1)
102                .action(ArgAction::Set)
103                .requires("PLUGIN")
104                .help("SIP003/SIP003u plugin mode, must be one of `tcp_only` (default), `udp_only` and `tcp_and_udp`"),
105        )
106        .arg(
107            Arg::new("PLUGIN_OPT")
108                .long("plugin-opts")
109                .num_args(1)
110                .action(ArgAction::Set)
111                .requires("PLUGIN")
112                .help("Default SIP003 plugin options"),
113        ).arg(Arg::new("ACL").long("acl").num_args(1).action(ArgAction::Set).value_hint(ValueHint::FilePath).help("Path to ACL (Access Control List)"))
114        .arg(Arg::new("DNS").long("dns").num_args(1).action(ArgAction::Set).help("DNS nameservers, formatted like [(tcp|udp)://]host[:port][,host[:port]]..., or unix:///path/to/dns, or predefined keys like \"google\", \"cloudflare\""))
115        .arg(Arg::new("DNS_CACHE_SIZE").long("dns-cache-size").num_args(1).action(ArgAction::Set).value_parser(clap::value_parser!(usize)).help("DNS cache size in number of records. Works when trust-dns DNS backend is used."))
116        .arg(Arg::new("TCP_NO_DELAY").long("tcp-no-delay").alias("no-delay").action(ArgAction::SetTrue).help("Set TCP_NODELAY option for sockets"))
117        .arg(Arg::new("TCP_FAST_OPEN").long("tcp-fast-open").alias("fast-open").action(ArgAction::SetTrue).help("Enable TCP Fast Open (TFO)"))
118        .arg(Arg::new("TCP_KEEP_ALIVE").long("tcp-keep-alive").num_args(1).action(ArgAction::Set).value_parser(clap::value_parser!(u64)).help("Set TCP keep alive timeout seconds"))
119        .arg(Arg::new("TCP_MULTIPATH").long("tcp-multipath").alias("mptcp").action(ArgAction::SetTrue).help("Enable Multipath-TCP (MPTCP)"))
120        .arg(Arg::new("UDP_TIMEOUT").long("udp-timeout").num_args(1).action(ArgAction::Set).value_parser(clap::value_parser!(u64)).help("Timeout seconds for UDP relay"))
121        .arg(Arg::new("UDP_MAX_ASSOCIATIONS").long("udp-max-associations").num_args(1).action(ArgAction::Set).value_parser(clap::value_parser!(usize)).help("Maximum associations to be kept simultaneously for UDP relay"))
122        .arg(Arg::new("INBOUND_SEND_BUFFER_SIZE").long("inbound-send-buffer-size").num_args(1).action(ArgAction::Set).value_parser(clap::value_parser!(u32)).help("Set inbound sockets' SO_SNDBUF option"))
123        .arg(Arg::new("INBOUND_RECV_BUFFER_SIZE").long("inbound-recv-buffer-size").num_args(1).action(ArgAction::Set).value_parser(clap::value_parser!(u32)).help("Set inbound sockets' SO_RCVBUF option"))
124        .arg(Arg::new("OUTBOUND_SEND_BUFFER_SIZE").long("outbound-send-buffer-size").num_args(1).action(ArgAction::Set).value_parser(clap::value_parser!(u32)).help("Set outbound sockets' SO_SNDBUF option"))
125        .arg(Arg::new("OUTBOUND_RECV_BUFFER_SIZE").long("outbound-recv-buffer-size").num_args(1).action(ArgAction::Set).value_parser(clap::value_parser!(u32)).help("Set outbound sockets' SO_RCVBUF option"))
126        .arg(
127            Arg::new("IPV6_FIRST")
128                .short('6')
129                .action(ArgAction::SetTrue)
130                .help("Resolve hostname to IPv6 address first"),
131        );
132
133    #[cfg(feature = "logging")]
134    {
135        app = app
136            .arg(
137                Arg::new("VERBOSE")
138                    .short('v')
139                    .action(ArgAction::Count)
140                    .help("Set log level"),
141            )
142            .arg(
143                Arg::new("LOG_WITHOUT_TIME")
144                    .long("log-without-time")
145                    .action(ArgAction::SetTrue)
146                    .help("Log without datetime prefix"),
147            )
148            .arg(
149                Arg::new("LOG_CONFIG")
150                    .long("log-config")
151                    // deprecated for removal
152                    .hide(true)
153                    .num_args(1)
154                    .action(ArgAction::Set)
155                    .value_parser(clap::value_parser!(PathBuf))
156                    .value_hint(ValueHint::FilePath)
157                    .help("log4rs configuration file"),
158            );
159    }
160
161    #[cfg(unix)]
162    {
163        app = app
164            .arg(
165                Arg::new("DAEMONIZE")
166                    .short('d')
167                    .long("daemonize")
168                    .action(ArgAction::SetTrue)
169                    .help("Daemonize"),
170            )
171            .arg(
172                Arg::new("DAEMONIZE_PID_PATH")
173                    .long("daemonize-pid")
174                    .num_args(1)
175                    .action(ArgAction::Set)
176                    .value_parser(clap::value_parser!(PathBuf))
177                    .value_hint(ValueHint::FilePath)
178                    .help("File path to store daemonized process's PID"),
179            )
180            .arg(
181                Arg::new("MANAGER_SERVER_MODE")
182                    .long("manager-server-mode")
183                    .num_args(1)
184                    .action(ArgAction::Set)
185                    .value_parser(vparser::parse_manager_server_mode)
186                    // .possible_values(["builtin", "standalone"])
187                    .help("Servers mode: builtin (default) or standalone"),
188            )
189            .arg(
190                Arg::new("MANAGER_SERVER_WORKING_DIRECTORY")
191                    .long("manager-server-working-directory")
192                    .num_args(1)
193                    .action(ArgAction::Set)
194                    .value_parser(clap::value_parser!(PathBuf))
195                    .value_hint(ValueHint::DirPath)
196                    .help("Folder for putting servers' configuration and pid files, default is current directory"),
197            );
198    }
199
200    #[cfg(all(unix, not(target_os = "android")))]
201    {
202        app = app.arg(
203            Arg::new("NOFILE")
204                .short('n')
205                .long("nofile")
206                .num_args(1)
207                .action(ArgAction::Set)
208                .value_parser(clap::value_parser!(u64))
209                .help("Set RLIMIT_NOFILE with both soft and hard limit"),
210        );
211    }
212
213    #[cfg(any(target_os = "linux", target_os = "android"))]
214    {
215        app = app.arg(
216            Arg::new("OUTBOUND_FWMARK")
217                .long("outbound-fwmark")
218                .num_args(1)
219                .action(ArgAction::Set)
220                .value_parser(clap::value_parser!(u32))
221                .help("Set SO_MARK option for outbound sockets"),
222        );
223    }
224
225    #[cfg(target_os = "freebsd")]
226    {
227        app = app.arg(
228            Arg::new("OUTBOUND_USER_COOKIE")
229                .long("outbound-user-cookie")
230                .num_args(1)
231                .action(ArgAction::Set)
232                .value_parser(clap::value_parser!(u32))
233                .help("Set SO_USER_COOKIE option for outbound sockets"),
234        );
235    }
236
237    #[cfg(feature = "multi-threaded")]
238    {
239        app = app
240            .arg(
241                Arg::new("SINGLE_THREADED")
242                    .long("single-threaded")
243                    .action(ArgAction::SetTrue)
244                    .help("Run the program all in one thread"),
245            )
246            .arg(
247                Arg::new("WORKER_THREADS")
248                    .long("worker-threads")
249                    .num_args(1)
250                    .action(ArgAction::Set)
251                    .value_parser(clap::value_parser!(usize))
252                    .help("Sets the number of worker threads the `Runtime` will use"),
253            );
254    }
255
256    #[cfg(unix)]
257    {
258        app = app.arg(
259            Arg::new("USER")
260                .long("user")
261                .short('a')
262                .num_args(1)
263                .action(ArgAction::Set)
264                .value_hint(ValueHint::Username)
265                .help("Run as another user"),
266        );
267    }
268
269    app
270}
271
272/// Create `Runtime` and `main` entry
273pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future<Output = ShadowsocksResult> + use<>)> {
274    let (config, runtime) = {
275        let config_path_opt = matches.get_one::<PathBuf>("CONFIG").cloned().or_else(|| {
276            if !matches.contains_id("SERVER_CONFIG") {
277                match crate::config::get_default_config_path("manager.json") {
278                    None => None,
279                    Some(p) => {
280                        println!("loading default config {p:?}");
281                        Some(p)
282                    }
283                }
284            } else {
285                None
286            }
287        });
288
289        let mut service_config = match config_path_opt {
290            Some(ref config_path) => ServiceConfig::load_from_file(config_path)
291                .map_err(|err| ShadowsocksError::LoadConfigFailure(format!("loading config {config_path:?}, {err}")))?,
292            None => ServiceConfig::default(),
293        };
294        service_config.set_options(matches);
295
296        #[cfg(feature = "logging")]
297        match service_config.log.config_path {
298            Some(ref path) => {
299                logging::init_with_file(path);
300            }
301            None => {
302                logging::init_with_config("ssmanager", &service_config.log);
303            }
304        }
305
306        trace!("{:?}", service_config);
307
308        let mut config = match config_path_opt {
309            Some(cpath) => Config::load_from_file(&cpath, ConfigType::Manager)
310                .map_err(|err| ShadowsocksError::LoadConfigFailure(format!("loading config {cpath:?}, {err}")))?,
311            None => Config::new(ConfigType::Manager),
312        };
313
314        if matches.get_flag("TCP_NO_DELAY") {
315            config.no_delay = true;
316        }
317
318        if matches.get_flag("TCP_FAST_OPEN") {
319            config.fast_open = true;
320        }
321
322        if let Some(keep_alive) = matches.get_one::<u64>("TCP_KEEP_ALIVE") {
323            config.keep_alive = Some(Duration::from_secs(*keep_alive));
324        }
325
326        if matches.get_flag("TCP_MULTIPATH") {
327            config.mptcp = true;
328        }
329
330        #[cfg(any(target_os = "linux", target_os = "android"))]
331        if let Some(mark) = matches.get_one::<u32>("OUTBOUND_FWMARK") {
332            config.outbound_fwmark = Some(*mark);
333        }
334
335        #[cfg(target_os = "freebsd")]
336        if let Some(user_cookie) = matches.get_one::<u32>("OUTBOUND_USER_COOKIE") {
337            config.outbound_user_cookie = Some(*user_cookie);
338        }
339
340        if let Some(iface) = matches.get_one::<String>("OUTBOUND_BIND_INTERFACE").cloned() {
341            config.outbound_bind_interface = Some(iface);
342        }
343
344        if let Some(addr) = matches.get_one::<ManagerAddr>("MANAGER_ADDR").cloned() {
345            match config.manager {
346                Some(ref mut manager_config) => {
347                    manager_config.addr = addr;
348                }
349                _ => {
350                    config.manager = Some(ManagerConfig::new(addr));
351                }
352            }
353        }
354
355        #[cfg(all(unix, not(target_os = "android")))]
356        match matches.get_one::<u64>("NOFILE") {
357            Some(nofile) => config.nofile = Some(*nofile),
358            None => {
359                if config.nofile.is_none() {
360                    crate::sys::adjust_nofile();
361                }
362            }
363        }
364
365        if let Some(ref mut manager_config) = config.manager {
366            if let Some(m) = matches.get_one::<String>("ENCRYPT_METHOD").cloned() {
367                manager_config.method = Some(m.parse::<CipherKind>().expect("method"));
368            }
369
370            if let Some(t) = matches.get_one::<u64>("TIMEOUT") {
371                manager_config.timeout = Some(Duration::from_secs(*t));
372            }
373
374            if let Some(sh) = matches.get_one::<ManagerServerHost>("SERVER_HOST").cloned() {
375                manager_config.server_host = sh;
376            }
377
378            if let Some(p) = matches.get_one::<String>("PLUGIN").cloned() {
379                manager_config.plugin = Some(PluginConfig {
380                    plugin: p,
381                    plugin_opts: matches.get_one::<String>("PLUGIN_OPT").cloned(),
382                    plugin_args: Vec::new(),
383                    plugin_mode: matches
384                        .get_one::<String>("PLUGIN_MODE")
385                        .map(|x| {
386                            x.parse::<Mode>()
387                                .expect("plugin-mode must be one of `tcp_only` (default), `udp_only` and `tcp_and_udp`")
388                        })
389                        .unwrap_or(Mode::TcpOnly),
390                });
391            }
392
393            #[cfg(unix)]
394            if let Some(server_mode) = matches.get_one::<ManagerServerMode>("MANAGER_SERVER_MODE").cloned() {
395                manager_config.server_mode = server_mode;
396            }
397
398            #[cfg(unix)]
399            if let Some(server_working_directory) =
400                matches.get_one::<PathBuf>("MANAGER_SERVER_WORKING_DIRECTORY").cloned()
401            {
402                manager_config.server_working_directory = server_working_directory;
403            }
404        }
405
406        // Overrides
407        if matches.get_flag("UDP_ONLY")
408            && let Some(ref mut m) = config.manager {
409                m.mode = Mode::UdpOnly;
410            }
411
412        if matches.get_flag("TCP_AND_UDP")
413            && let Some(ref mut m) = config.manager {
414                m.mode = Mode::TcpAndUdp;
415            }
416
417        if let Some(acl_file) = matches.get_one::<String>("ACL") {
418            let acl = AccessControl::load_from_file(acl_file)
419                .map_err(|err| ShadowsocksError::LoadAclFailure(format!("loading ACL \"{acl_file}\", {err}")))?;
420            config.acl = Some(acl);
421        }
422
423        if let Some(dns) = matches.get_one::<String>("DNS") {
424            config.set_dns_formatted(dns).expect("dns");
425        }
426
427        if let Some(dns_cache_size) = matches.get_one::<usize>("DNS_CACHE_SIZE") {
428            config.dns_cache_size = Some(*dns_cache_size);
429        }
430
431        if matches.get_flag("IPV6_FIRST") {
432            config.ipv6_first = true;
433        }
434
435        if let Some(udp_timeout) = matches.get_one::<u64>("UDP_TIMEOUT") {
436            config.udp_timeout = Some(Duration::from_secs(*udp_timeout));
437        }
438
439        if let Some(udp_max_assoc) = matches.get_one::<usize>("UDP_MAX_ASSOCIATIONS") {
440            config.udp_max_associations = Some(*udp_max_assoc);
441        }
442
443        if let Some(bs) = matches.get_one::<u32>("INBOUND_SEND_BUFFER_SIZE") {
444            config.inbound_send_buffer_size = Some(*bs);
445        }
446        if let Some(bs) = matches.get_one::<u32>("INBOUND_RECV_BUFFER_SIZE") {
447            config.inbound_recv_buffer_size = Some(*bs);
448        }
449        if let Some(bs) = matches.get_one::<u32>("OUTBOUND_SEND_BUFFER_SIZE") {
450            config.outbound_send_buffer_size = Some(*bs);
451        }
452        if let Some(bs) = matches.get_one::<u32>("OUTBOUND_RECV_BUFFER_SIZE") {
453            config.outbound_recv_buffer_size = Some(*bs);
454        }
455
456        if let Some(bind_addr) = matches.get_one::<IpAddr>("OUTBOUND_BIND_ADDR") {
457            config.outbound_bind_addr = Some(*bind_addr);
458        }
459
460        // DONE reading options
461
462        config.manager.as_ref().ok_or_else(|| {
463            ShadowsocksError::InsufficientParams(
464                "missing `manager_address`, consider specifying it by --manager-address command line option, \
465                    or \"manager_address\" and \"manager_port\" keys in configuration file"
466                    .to_string(),
467            )
468        })?;
469
470        config
471            .check_integrity()
472            .map_err(|err| ShadowsocksError::LoadConfigFailure(format!("config integrity check failed, {err}")))?;
473
474        #[cfg(unix)]
475        if matches.get_flag("DAEMONIZE") || matches.get_raw("DAEMONIZE_PID_PATH").is_some() {
476            use crate::daemonize;
477            daemonize::daemonize(matches.get_one::<PathBuf>("DAEMONIZE_PID_PATH"));
478        }
479
480        #[cfg(unix)]
481        if let Some(uname) = matches.get_one::<String>("USER") {
482            crate::sys::run_as_user(uname).map_err(|err| {
483                ShadowsocksError::InsufficientParams(format!("failed to change as user, error: {err}"))
484            })?;
485        }
486
487        info!("shadowsocks manager {} build {}", crate::VERSION, crate::BUILD_TIME);
488
489        let mut builder = match service_config.runtime.mode {
490            RuntimeMode::SingleThread => Builder::new_current_thread(),
491            #[cfg(feature = "multi-threaded")]
492            RuntimeMode::MultiThread => {
493                let mut builder = Builder::new_multi_thread();
494                if let Some(worker_threads) = service_config.runtime.worker_count {
495                    builder.worker_threads(worker_threads);
496                }
497
498                builder
499            }
500        };
501
502        let runtime = builder.enable_all().build().expect("create tokio Runtime");
503
504        (config, runtime)
505    };
506
507    let main_fut = async move {
508        let abort_signal = monitor::create_signal_monitor();
509        let server = run_manager(config);
510
511        tokio::pin!(abort_signal);
512        tokio::pin!(server);
513
514        match future::select(server, abort_signal).await {
515            // Server future resolved without an error. This should never happen.
516            Either::Left((Ok(..), ..)) => Err(ShadowsocksError::ServerExitUnexpectedly(
517                "server exited unexpectedly".to_owned(),
518            )),
519            // Server future resolved with error, which are listener errors in most cases
520            Either::Left((Err(err), ..)) => Err(ShadowsocksError::ServerAborted(format!("server aborted with {err}"))),
521            // The abort signal future resolved. Means we should just exit.
522            Either::Right(_) => Ok(()),
523        }
524    };
525
526    Ok((runtime, main_fut))
527}
528
529/// Program entrance `main`
530#[inline]
531pub fn main(matches: &ArgMatches) -> ExitCode {
532    match create(matches).and_then(|(runtime, main_fut)| runtime.block_on(main_fut)) {
533        Ok(()) => ExitCode::SUCCESS,
534        Err(err) => {
535            eprintln!("{err}");
536            err.exit_code().into()
537        }
538    }
539}
540
541#[cfg(test)]
542mod test {
543    use clap::Command;
544
545    #[test]
546    fn verify_manager_command() {
547        let mut app = Command::new("shadowsocks")
548            .version(crate::VERSION)
549            .about("A fast tunnel proxy that helps you bypass firewalls. (https://shadowsocks.org)");
550        app = super::define_command_line_options(app);
551        app.debug_assert();
552    }
553}