dns2socks_core/
config.rs

1use socks5_impl::protocol::UserKey;
2use std::net::{SocketAddr, ToSocketAddrs as _};
3
4/// Proxy server to routing DNS query to SOCKS5 server
5#[derive(clap::Parser, Debug, Clone, PartialEq, Eq)]
6#[command(author, version, about = "Proxy server to routing DNS query to SOCKS5 server", long_about = None)]
7pub struct Config {
8    /// Listen address
9    #[clap(short, long, value_name = "IP:port", default_value = "0.0.0.0:53")]
10    pub listen_addr: SocketAddr,
11
12    /// Remote DNS server address
13    #[clap(short, long, value_name = "IP:port", default_value = "8.8.8.8:53")]
14    pub dns_remote_server: SocketAddr,
15
16    /// SOCKS5 URL in the form socks5://[username[:password]@]host:port,
17    /// Username and password are encoded in percent encoding. For example:
18    /// socks5://myname:pass%40word@127.0.0.1:1080
19    #[arg(short, long, value_parser = |s: &str| ArgProxy::try_from(s), value_name = "URL", default_value = "socks5://127.0.0.1:1080")]
20    pub socks5_settings: ArgProxy,
21
22    /// Force to use TCP to proxy DNS query
23    #[clap(short, long)]
24    pub force_tcp: bool,
25
26    /// Cache DNS query records
27    #[clap(short, long)]
28    pub cache_records: bool,
29
30    /// Verbosity level
31    #[arg(short, long, value_name = "level", value_enum, default_value = "info")]
32    pub verbosity: ArgVerbosity,
33
34    /// Timeout for DNS query
35    #[clap(short, long, value_name = "seconds", default_value = "5")]
36    pub timeout: u64,
37}
38
39impl Default for Config {
40    fn default() -> Self {
41        Config {
42            listen_addr: "0.0.0.0:53".parse().unwrap(),
43            dns_remote_server: "8.8.8.8:53".parse().unwrap(),
44            socks5_settings: ArgProxy::default(),
45            force_tcp: false,
46            cache_records: false,
47            verbosity: ArgVerbosity::default(),
48            timeout: 5,
49        }
50    }
51}
52
53impl Config {
54    pub fn parse_args() -> Self {
55        clap::Parser::parse()
56    }
57
58    pub fn listen_addr(&mut self, listen_addr: SocketAddr) -> &mut Self {
59        self.listen_addr = listen_addr;
60        self
61    }
62
63    pub fn dns_remote_server(&mut self, dns_remote_server: SocketAddr) -> &mut Self {
64        self.dns_remote_server = dns_remote_server;
65        self
66    }
67
68    pub fn socks5_settings(&mut self, socks5_settings: ArgProxy) -> &mut Self {
69        self.socks5_settings = socks5_settings;
70        self
71    }
72
73    pub fn force_tcp(&mut self, force_tcp: bool) -> &mut Self {
74        self.force_tcp = force_tcp;
75        self
76    }
77
78    pub fn cache_records(&mut self, cache_records: bool) -> &mut Self {
79        self.cache_records = cache_records;
80        self
81    }
82
83    pub fn verbosity(&mut self, verbosity: ArgVerbosity) -> &mut Self {
84        self.verbosity = verbosity;
85        self
86    }
87
88    pub fn timeout(&mut self, timeout: u64) -> &mut Self {
89        self.timeout = timeout;
90        self
91    }
92}
93
94#[repr(C)]
95#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, Default)]
96pub enum ArgVerbosity {
97    Off = 0,
98    Error,
99    Warn,
100    #[default]
101    Info,
102    Debug,
103    Trace,
104}
105
106impl std::fmt::Display for ArgVerbosity {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        match self {
109            ArgVerbosity::Off => write!(f, "off"),
110            ArgVerbosity::Error => write!(f, "error"),
111            ArgVerbosity::Warn => write!(f, "warn"),
112            ArgVerbosity::Info => write!(f, "info"),
113            ArgVerbosity::Debug => write!(f, "debug"),
114            ArgVerbosity::Trace => write!(f, "trace"),
115        }
116    }
117}
118
119impl From<log::Level> for ArgVerbosity {
120    fn from(level: log::Level) -> Self {
121        match level {
122            log::Level::Error => ArgVerbosity::Error,
123            log::Level::Warn => ArgVerbosity::Warn,
124            log::Level::Info => ArgVerbosity::Info,
125            log::Level::Debug => ArgVerbosity::Debug,
126            log::Level::Trace => ArgVerbosity::Trace,
127        }
128    }
129}
130
131impl From<ArgVerbosity> for log::LevelFilter {
132    fn from(level: ArgVerbosity) -> Self {
133        match level {
134            ArgVerbosity::Off => log::LevelFilter::Off,
135            ArgVerbosity::Error => log::LevelFilter::Error,
136            ArgVerbosity::Warn => log::LevelFilter::Warn,
137            ArgVerbosity::Info => log::LevelFilter::Info,
138            ArgVerbosity::Debug => log::LevelFilter::Debug,
139            ArgVerbosity::Trace => log::LevelFilter::Trace,
140        }
141    }
142}
143
144impl TryFrom<i32> for ArgVerbosity {
145    type Error = std::io::Error;
146
147    fn try_from(value: i32) -> Result<Self, <ArgVerbosity as TryFrom<i32>>::Error> {
148        match value {
149            0 => Ok(ArgVerbosity::Off),
150            1 => Ok(ArgVerbosity::Error),
151            2 => Ok(ArgVerbosity::Warn),
152            3 => Ok(ArgVerbosity::Info),
153            4 => Ok(ArgVerbosity::Debug),
154            5 => Ok(ArgVerbosity::Trace),
155            _ => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid verbosity level")),
156        }
157    }
158}
159
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub struct ArgProxy {
162    pub proxy_type: ProxyType,
163    pub addr: SocketAddr,
164    pub credentials: Option<UserKey>,
165}
166
167impl Default for ArgProxy {
168    fn default() -> Self {
169        ArgProxy {
170            proxy_type: ProxyType::Socks5,
171            addr: "127.0.0.1:1080".parse().unwrap(),
172            credentials: None,
173        }
174    }
175}
176
177impl std::fmt::Display for ArgProxy {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        let auth = match &self.credentials {
180            Some(creds) => format!("{}", creds),
181            None => "".to_owned(),
182        };
183        if auth.is_empty() {
184            write!(f, "{}://{}", &self.proxy_type, &self.addr)
185        } else {
186            write!(f, "{}://{}@{}", &self.proxy_type, auth, &self.addr)
187        }
188    }
189}
190
191impl TryFrom<&str> for ArgProxy {
192    type Error = std::io::Error;
193    fn try_from(s: &str) -> Result<Self, Self::Error> {
194        use std::io::{Error, ErrorKind::InvalidData};
195        let e = format!("`{s}` is not a valid proxy URL");
196        let url = url::Url::parse(s).map_err(|_| Error::new(InvalidData, e))?;
197        let e = format!("`{s}` does not contain a host");
198        let host = url.host_str().ok_or(Error::new(InvalidData, e))?;
199
200        let e = format!("`{s}` does not contain a port");
201        let port = url.port_or_known_default().ok_or(Error::new(InvalidData, e))?;
202
203        let e2 = format!("`{host}` does not resolve to a usable IP address");
204        let addr = (host, port).to_socket_addrs()?.next().ok_or(Error::new(InvalidData, e2))?;
205
206        let credentials = if url.username() == "" && url.password().is_none() {
207            None
208        } else {
209            let username = percent_encoding::percent_decode(url.username().as_bytes())
210                .decode_utf8()
211                .map_err(|e| Error::new(InvalidData, e))?;
212            let password = percent_encoding::percent_decode(url.password().unwrap_or("").as_bytes())
213                .decode_utf8()
214                .map_err(|e| Error::new(InvalidData, e))?;
215            Some(UserKey::new(username, password))
216        };
217
218        let proxy_type = url.scheme().to_ascii_lowercase().as_str().try_into()?;
219
220        Ok(ArgProxy {
221            proxy_type,
222            addr,
223            credentials,
224        })
225    }
226}
227
228#[repr(C)]
229#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
230pub enum ProxyType {
231    // Http = 0,
232    // Socks4,
233    #[default]
234    Socks5,
235}
236
237impl TryFrom<&str> for ProxyType {
238    type Error = std::io::Error;
239    fn try_from(value: &str) -> Result<Self, Self::Error> {
240        use std::io::{Error, ErrorKind::InvalidData};
241        match value {
242            // "http" => Ok(ProxyType::Http),
243            // "socks4" => Ok(ProxyType::Socks4),
244            "socks5" => Ok(ProxyType::Socks5),
245            scheme => Err(Error::new(InvalidData, format!("`{scheme}` is an invalid proxy type"))),
246        }
247    }
248}
249
250impl std::fmt::Display for ProxyType {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        match self {
253            // ProxyType::Http => write!(f, "http"),
254            // ProxyType::Socks4 => write!(f, "socks4"),
255            ProxyType::Socks5 => write!(f, "socks5"),
256        }
257    }
258}