1use socks5_impl::protocol::UserKey;
2use std::net::{SocketAddr, ToSocketAddrs as _};
3
4#[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 #[clap(short, long, value_name = "IP:port", default_value = "0.0.0.0:53")]
10 pub listen_addr: SocketAddr,
11
12 #[clap(short, long, value_name = "IP:port", default_value = "8.8.8.8:53")]
14 pub dns_remote_server: SocketAddr,
15
16 #[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 #[clap(short, long)]
24 pub force_tcp: bool,
25
26 #[clap(short, long)]
28 pub cache_records: bool,
29
30 #[arg(short, long, value_name = "level", value_enum, default_value = "info")]
32 pub verbosity: ArgVerbosity,
33
34 #[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 #[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 "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::Socks5 => write!(f, "socks5"),
256 }
257 }
258}