1use std::{
4 io::{self, ErrorKind},
5 net::{SocketAddr, TcpListener},
6 process::Stdio,
7};
8
9use tokio::process::{Child, Command};
10
11pub 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 .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}