1use std::{
3 io,
4 net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener},
5 process::{ExitStatus, Stdio},
6};
7
8use derivative::Derivative;
9use serde::{Deserialize, Serialize};
10use tokio::process::{Child, Command};
11use tracing::{error, info, trace};
12
13#[derive(Derivative, Deserialize, Serialize, Clone)]
14#[derivative(Debug)]
15pub struct PluginConfig {
16 pub name: String,
17 pub opts: Option<String>,
18 pub args: Vec<String>,
19}
20
21pub struct Plugin {
27 process: Child,
28 local_addr: SocketAddr,
29}
30
31impl Plugin {
32 pub fn start(cfg: &PluginConfig, remote_host: &str, remote_port: &str) -> io::Result<Plugin> {
34 let local_addr = get_local_port(Ipv4Addr::LOCALHOST.into())?;
35
36 trace!(
37 "starting plugin {}, opts: {:?}, args: {:?} listen to {}:{}, ss will use local {}",
38 cfg.name,
39 cfg.opts,
40 cfg.args,
41 remote_host,
42 remote_port,
43 local_addr
44 );
45
46 let mut cmd = Command::new(&cfg.name);
47
48 cmd.env("SS_REMOTE_HOST", remote_host)
49 .env("SS_REMOTE_PORT", remote_port)
50 .env("SS_LOCAL_HOST", local_addr.ip().to_string())
51 .env("SS_LOCAL_PORT", local_addr.port().to_string())
52 .stdin(Stdio::null())
53 .kill_on_drop(true);
54
55 if let Some(opts) = &cfg.opts {
56 cmd.env("SS_PLUGIN_OPTIONS", opts);
57 }
58
59 if !cfg.args.is_empty() {
60 cmd.args(&cfg.args);
61 }
62
63 match cmd.spawn() {
64 Ok(process) => {
65 info!(
66 "started plugin {} on {}:{} <-> {}, pid:{}",
67 cfg.name,
68 remote_host,
69 remote_port,
70 local_addr,
71 process.id().unwrap_or(0)
72 );
73 Ok(Plugin {
74 process,
75 local_addr,
76 })
77 }
78 Err(e) => {
79 error!("failed to start plugin {} err: {}", cfg.name, e);
80 Err(e)
81 }
82 }
83 }
84
85 pub async fn join(mut self) -> io::Result<ExitStatus> {
87 self.process.wait().await
88 }
89
90 pub fn local_addr(&self) -> SocketAddr {
91 self.local_addr
92 }
93}
94
95fn get_local_port(loop_ip: IpAddr) -> io::Result<SocketAddr> {
96 let listener = TcpListener::bind(SocketAddr::new(loop_ip, 0))?;
97 listener.local_addr()
98}
99
100#[cfg(test)]
101mod test {
102 use super::*;
103
104 #[test]
105 fn test_get_local_port() {
106 let local_addr = get_local_port(Ipv4Addr::LOCALHOST.into()).unwrap();
107 println!("{:?}", local_addr);
108 }
109}