ss_rs/
plugin.rs

1//! SIP 003 plugin implementation.
2
3use std::{
4    io::{self, ErrorKind},
5    net::{SocketAddr, TcpListener},
6    process::Stdio,
7};
8
9use tokio::process::{Child, Command};
10
11/// Starts a plugin with the given options.
12///
13/// Returns listening address and the child process.
14///
15/// For ss-local: the listening address is plugin address.
16///
17/// For ss-remote: the listening address is ss-remote address (ss-remote is behind the plugin).
18pub fn start_plugin(
19    plugin: &str,
20    plugin_opts: &str,
21    raw_addr: SocketAddr,
22    is_server: bool,
23) -> io::Result<(SocketAddr, Child)> {
24    log::info!(
25        "Starting plugin ({}) with options ({})",
26        plugin,
27        plugin_opts
28    );
29
30    let free_port = match find_free_port() {
31        Some(port) => port,
32        None => {
33            return Err(io::Error::new(ErrorKind::Other, "no free port available"));
34        }
35    };
36
37    let listening_addr: SocketAddr = match is_server {
38        true => format!("{}:{}", raw_addr.ip(), free_port).parse().unwrap(),
39        false => format!("127.0.0.1:{}", free_port).parse().unwrap(),
40    };
41
42    let local_addr = listening_addr.clone();
43    let remote_addr = raw_addr;
44    let plugin = exec_plugin(plugin, plugin_opts, local_addr, remote_addr)?;
45
46    match is_server {
47        true => log::info!("Plugin listening on {}", remote_addr),
48        false => log::info!("Plugin listening on {}", local_addr),
49    }
50
51    Ok((listening_addr, plugin))
52}
53
54fn exec_plugin(
55    plugin: &str,
56    plugin_opts: &str,
57    local_addr: SocketAddr,
58    remote_addr: SocketAddr,
59) -> io::Result<Child> {
60    Command::new(plugin)
61        .env("SS_LOCAL_HOST", local_addr.ip().to_string())
62        .env("SS_LOCAL_PORT", local_addr.port().to_string())
63        .env("SS_REMOTE_HOST", remote_addr.ip().to_string())
64        .env("SS_REMOTE_PORT", remote_addr.port().to_string())
65        .env("SS_PLUGIN_OPTIONS", plugin_opts)
66        .stdin(Stdio::null())
67        .stdout(Stdio::null())
68        // .stderr(Stdio::null())
69        .spawn()
70}
71
72fn find_free_port() -> Option<u16> {
73    for port in (1025..=u16::MAX).rev() {
74        match TcpListener::bind(format!("127.0.0.1:{}", port)) {
75            Ok(_) => return Some(port),
76            Err(_) => continue,
77        }
78    }
79
80    None
81}