Skip to main content

rusty_sockslib/
config.rs

1use clap::Parser;
2
3use crate::helpers::{Helpers, Res};
4
5/// A super basic SOCKS5 proxy.
6///
7/// Every option can be supplied as a CLI flag or via its `RS_*` environment variable; flags win.
8#[derive(Parser, Debug, Clone)]
9#[command(name = "rusty_socks", version, about, long_about = None)]
10pub struct Config {
11    /// Network interface whose IP the proxy listens on (defaults to `0.0.0.0`).
12    #[arg(long, env = "RS_LISTEN_INTERFACE")]
13    pub listen_interface: Option<String>,
14
15    /// Network interface whose IP is used for outbound connections to endpoints (defaults to `0.0.0.0`).
16    #[arg(long, env = "RS_ENDPOINT_INTERFACE")]
17    pub endpoint_interface: Option<String>,
18
19    /// Port to listen on.
20    #[arg(long, env = "RS_PORT", default_value_t = 1080)]
21    pub port: u16,
22
23    /// Per-direction buffer size, in bytes.
24    #[arg(long, env = "RS_BUFFER_SIZE", default_value_t = 2048)]
25    pub buffer_size: usize,
26
27    /// Idle timeout in milliseconds: a connection with no traffic in either direction for this long
28    /// is closed. `0` disables the idle timeout entirely.
29    #[arg(long, env = "RS_READ_TIMEOUT", default_value_t = 60_000)]
30    pub read_timeout: u64,
31
32    /// CIDR of client addresses allowed to connect.
33    #[arg(long, env = "RS_ACCEPT_CIDR", default_value = "0.0.0.0/0")]
34    pub accept_cidr: String,
35}
36
37impl Config {
38    /// Resolve the listen IP from the configured interface, defaulting to `0.0.0.0`.
39    pub fn listen_ip(&self) -> Res<String> {
40        Self::interface_ip(self.listen_interface.as_deref())
41    }
42
43    /// Resolve the endpoint (outbound) IP from the configured interface, defaulting to `0.0.0.0`.
44    pub fn endpoint_ip(&self) -> Res<String> {
45        Self::interface_ip(self.endpoint_interface.as_deref())
46    }
47
48    fn interface_ip(interface: Option<&str>) -> Res<String> {
49        match interface {
50            Some(name) => Ok(Helpers::get_interface_ip(name)?.to_string()),
51            None => Ok("0.0.0.0".to_owned()),
52        }
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use clap::Parser;
60    use pretty_assertions::assert_eq;
61
62    #[test]
63    fn cli_flags_override_defaults() {
64        let config = Config::parse_from(["rusty_socks", "--port", "9050", "--read-timeout", "0", "--accept-cidr", "10.0.0.0/8"]);
65
66        assert_eq!(config.port, 9050);
67        assert_eq!(config.read_timeout, 0);
68        assert_eq!(config.accept_cidr, "10.0.0.0/8");
69    }
70
71    #[test]
72    fn unspecified_interface_resolves_to_wildcard() {
73        let config = Config::parse_from(["rusty_socks", "--port", "1"]);
74
75        assert_eq!(config.listen_ip().unwrap(), "0.0.0.0");
76        assert_eq!(config.endpoint_ip().unwrap(), "0.0.0.0");
77    }
78}