ss_light/plugin/
mod.rs

1//! plugin support. SIP003 [https://shadowsocks.org/en/wiki/Plugin.html](https://shadowsocks.org/en/wiki/Plugin.html)
2use 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
21/// server plugin: CLIENT -> PLUGIN -> SERVER -> REMOTE
22///
23/// plugin listen to inbound address of server
24///
25/// server listen to local_addr
26pub struct Plugin {
27    process: Child,
28    local_addr: SocketAddr,
29}
30
31impl Plugin {
32    // start plugin in subprocess
33    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    // wait plugin exits
86    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}