1use std::{fmt::Display, str::FromStr};
6use thiserror::Error;
7
8#[cfg(not(feature = "default-is-ipv6"))]
9macro_rules! default_host {
11 (local) => {
12 String::from("127.0.0.1")
13 };
14 (unspec) => {
15 String::from("0.0.0.0")
16 };
17}
18#[cfg(feature = "default-is-ipv6")]
19macro_rules! default_host {
21 (local) => {
22 String::from("::1")
23 };
24 (unspec) => {
25 String::from("::")
26 };
27}
28
29pub(crate) use default_host;
31
32pub const SOCKS_DEFAULT_PORT: u16 = 1080;
34pub const TPROXY_DEFAULT_PORT: u16 = 1234;
36
37#[derive(Debug, Clone, Hash, Eq, PartialEq)]
39pub struct Remote {
40 pub local_addr: LocalSpec,
42 #[allow(clippy::struct_field_names)]
44 pub remote_addr: RemoteSpec,
45 pub protocol: Protocol,
47}
48
49#[derive(Debug, Clone, Hash, Eq, PartialEq)]
51pub enum LocalSpec {
52 Inet((String, u16)),
54 Stdio,
56}
57
58#[derive(Debug, Clone, Hash, Eq, PartialEq)]
60pub enum RemoteSpec {
61 Inet((String, u16)),
63 Socks,
65 Tproxy,
66}
67
68#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
70pub enum Protocol {
71 Tcp,
73 Udp,
75}
76
77#[derive(Clone, Error, Debug, PartialEq, Eq)]
79pub enum Error {
80 #[error("Invalid format")]
82 Format,
83 #[error("Invalid protocol")]
85 Protocol,
86 #[error("Invalid host")]
88 Host,
89 #[error("Invalid port")]
91 Port(#[from] std::num::ParseIntError),
92 #[error("socks remote must be TCP")]
94 UdpSocks,
95 #[error("stdio cannot work with Transparent Proxy")]
96 StdioTproxy,
97}
98
99impl Display for Protocol {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 f.write_str(match self {
102 Self::Tcp => "tcp",
103 Self::Udp => "udp",
104 })
105 }
106}
107
108impl FromStr for Protocol {
109 type Err = Error;
110
111 fn from_str(s: &str) -> Result<Self, Self::Err> {
112 match s.to_lowercase().as_str() {
113 "tcp" => Ok(Self::Tcp),
114 "udp" => Ok(Self::Udp),
115 _ => Err(Error::Protocol),
116 }
117 }
118}
119
120fn tokenize_remote(s: &str) -> Result<Vec<&str>, Error> {
123 let mut tokens = Vec::new();
124 let mut stuff = s;
125 loop {
126 if stuff.starts_with('[') {
128 let end = stuff.find(']').ok_or(Error::Host)? + 1;
129 tokens.push(&stuff[..end]);
130 if !stuff[end..].is_empty() && !stuff[end..].starts_with(':') {
132 return Err(Error::Format);
133 }
134 stuff = &stuff[end + 1..];
135 } else if let Some((token, rest)) = stuff.split_once(':') {
136 tokens.push(token);
137 stuff = rest;
138 } else {
139 tokens.push(stuff);
140 return Ok(tokens);
141 }
142 }
143}
144
145impl Display for Remote {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 match &self.local_addr {
148 LocalSpec::Inet((host, port)) => {
149 if host.contains(':') {
150 write!(f, "[{host}]:{port}")?;
151 } else {
152 write!(f, "{host}:{port}")?;
153 }
154 }
155 LocalSpec::Stdio => f.write_str("stdio")?,
156 }
157 match &self.remote_addr {
158 RemoteSpec::Inet((host, port)) => {
159 if host.contains(':') {
160 write!(f, ":[{host}]:{port}")?;
161 } else {
162 write!(f, ":{host}:{port}")?;
163 }
164 }
165 RemoteSpec::Socks => f.write_str(":socks")?,
166 RemoteSpec::Tproxy => f.write_str(":tproxy")?,
167 }
168 write!(f, "/{}", self.protocol)?;
169 Ok(())
170 }
171}
172
173impl FromStr for Remote {
174 type Err = Error;
175
176 #[allow(clippy::too_many_lines)]
178 fn from_str(s: &str) -> Result<Self, Self::Err> {
179 let (rest, proto) = match s.rsplit_once('/') {
180 Some((rest, proto)) => (rest, proto.parse()?),
181 None => (s, Protocol::Tcp),
182 };
183 let tokens = tokenize_remote(rest)?;
184 let result = match tokens[..] {
185 ["socks"] => Ok(Self {
187 local_addr: LocalSpec::Inet((default_host!(local), SOCKS_DEFAULT_PORT)),
188 remote_addr: RemoteSpec::Socks,
189 protocol: proto,
190 }),
191 ["tproxy"] => Ok(Self {
192 local_addr: LocalSpec::Inet((default_host!(local), TPROXY_DEFAULT_PORT)),
193 remote_addr: RemoteSpec::Tproxy,
194 protocol: proto,
195 }),
196 [port] => Ok(Self {
197 local_addr: LocalSpec::Inet((default_host!(unspec), port.parse()?)),
198 remote_addr: RemoteSpec::Inet((default_host!(local), port.parse()?)),
199 protocol: proto,
200 }),
201 ["stdio", "socks"] => Ok(Self {
203 local_addr: LocalSpec::Stdio,
204 remote_addr: RemoteSpec::Socks,
205 protocol: proto,
206 }),
207 ["stdio", "tproxy"] => Err(Error::StdioTproxy),
208 [port, "socks"] => Ok(Self {
209 local_addr: LocalSpec::Inet((default_host!(local), port.parse()?)),
210 remote_addr: RemoteSpec::Socks,
211 protocol: proto,
212 }),
213 [port, "tproxy"] => Ok(Self {
214 local_addr: LocalSpec::Inet((default_host!(local), port.parse()?)),
215 remote_addr: RemoteSpec::Tproxy,
216 protocol: proto,
217 }),
218 ["stdio", port] => Ok(Self {
219 local_addr: LocalSpec::Stdio,
220 remote_addr: RemoteSpec::Inet((default_host!(local), port.parse()?)),
221 protocol: proto,
222 }),
223 [host, port] => Ok(Self {
224 local_addr: LocalSpec::Inet((default_host!(unspec), port.parse()?)),
225 remote_addr: RemoteSpec::Inet((remove_brackets(host).to_string(), port.parse()?)),
226 protocol: proto,
227 }),
228 ["stdio", remote_host, remote_port] => Ok(Self {
233 local_addr: LocalSpec::Stdio,
234 remote_addr: RemoteSpec::Inet((
235 remove_brackets(remote_host).to_string(),
236 remote_port.parse()?,
237 )),
238 protocol: proto,
239 }),
240 [local_host, local_port, "socks"] => Ok(Self {
241 local_addr: LocalSpec::Inet((
242 remove_brackets(local_host).to_string(),
243 local_port.parse()?,
244 )),
245 remote_addr: RemoteSpec::Socks,
246 protocol: proto,
247 }),
248 [local_host, local_port, "tproxy"] => Ok(Self {
249 local_addr: LocalSpec::Inet((
250 remove_brackets(local_host).to_string(),
251 local_port.parse()?,
252 )),
253 remote_addr: RemoteSpec::Tproxy,
254 protocol: proto,
255 }),
256 [local_port, remote_host, remote_port] => Ok(Self {
257 local_addr: LocalSpec::Inet((default_host!(unspec), local_port.parse()?)),
258 remote_addr: RemoteSpec::Inet((
259 remove_brackets(remote_host).to_string(),
260 remote_port.parse()?,
261 )),
262 protocol: proto,
263 }),
264 [local_host, local_port, remote_host, remote_port] => Ok(Self {
265 local_addr: LocalSpec::Inet((
266 remove_brackets(local_host).to_string(),
267 local_port.parse()?,
268 )),
269 remote_addr: RemoteSpec::Inet((
270 remove_brackets(remote_host).to_string(),
271 remote_port.parse()?,
272 )),
273 protocol: proto,
274 }),
275 _ => Err(Error::Format),
276 };
277 if let Ok(Self {
280 remote_addr: RemoteSpec::Socks,
281 protocol: Protocol::Udp,
282 ..
283 }) = &result
284 {
285 Err(Error::UdpSocks)
286 } else {
287 result
288 }
289 }
290}
291
292#[must_use]
294pub fn remove_brackets(s: &str) -> &str {
295 if s.starts_with('[') && s.ends_with(']') {
296 &s[1..s.len() - 1]
297 } else {
298 s
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[cfg(not(feature = "default-is-ipv6"))]
308 #[test]
309 fn test_default_host() {
310 crate::tests::setup_logging();
311 assert_eq!(
312 std::net::Ipv4Addr::from_str(&default_host!(unspec)).unwrap(),
313 std::net::Ipv4Addr::UNSPECIFIED
314 );
315 assert_eq!(
316 std::net::Ipv4Addr::from_str(&default_host!(local)).unwrap(),
317 std::net::Ipv4Addr::LOCALHOST
318 );
319 }
320 #[cfg(feature = "default-is-ipv6")]
321 #[test]
322 fn test_default_host() {
323 crate::tests::setup_logging();
324 assert_eq!(
325 std::net::Ipv6Addr::from_str(&default_host!(unspec)).unwrap(),
326 std::net::Ipv6Addr::UNSPECIFIED
327 );
328 assert_eq!(
329 std::net::Ipv6Addr::from_str(&default_host!(local)).unwrap(),
330 std::net::Ipv6Addr::LOCALHOST
331 );
332 }
333
334 #[test]
335 #[allow(clippy::too_many_lines)]
336 fn test_parse_remote() {
337 crate::tests::setup_logging();
338 let tests: &[(&str, Remote)] = &[
339 (
341 "3000",
342 Remote {
343 local_addr: LocalSpec::Inet((default_host!(unspec), 3000)),
344 remote_addr: RemoteSpec::Inet((default_host!(local), 3000)),
345 protocol: Protocol::Tcp,
346 },
347 ),
348 (
349 "4000/udp",
350 Remote {
351 local_addr: LocalSpec::Inet((default_host!(unspec), 4000)),
352 remote_addr: RemoteSpec::Inet((default_host!(local), 4000)),
353 protocol: Protocol::Udp,
354 },
355 ),
356 (
357 "google.com:80",
358 Remote {
359 local_addr: LocalSpec::Inet((default_host!(unspec), 80)),
360 remote_addr: RemoteSpec::Inet((String::from("google.com"), 80)),
361 protocol: Protocol::Tcp,
362 },
363 ),
364 (
365 "示例網站.com:80",
366 Remote {
367 local_addr: LocalSpec::Inet((default_host!(unspec), 80)),
368 remote_addr: RemoteSpec::Inet((String::from("示例網站.com"), 80)),
369 protocol: Protocol::Tcp,
370 },
371 ),
372 (
373 "8080:example.com:80",
374 Remote {
375 local_addr: LocalSpec::Inet((default_host!(unspec), 8080)),
376 remote_addr: RemoteSpec::Inet((String::from("example.com"), 80)),
377 protocol: Protocol::Tcp,
378 },
379 ),
380 (
381 "socks",
382 Remote {
383 local_addr: LocalSpec::Inet((default_host!(local), SOCKS_DEFAULT_PORT)),
384 remote_addr: RemoteSpec::Socks,
385 protocol: Protocol::Tcp,
386 },
387 ),
388 (
389 "9050:socks",
390 Remote {
391 local_addr: LocalSpec::Inet((default_host!(local), 9050)),
392 remote_addr: RemoteSpec::Socks,
393 protocol: Protocol::Tcp,
394 },
395 ),
396 (
397 "127.0.0.1:1081:socks",
398 Remote {
399 local_addr: LocalSpec::Inet((String::from("127.0.0.1"), 1081)),
400 remote_addr: RemoteSpec::Socks,
401 protocol: Protocol::Tcp,
402 },
403 ),
404 (
405 "9050:socks",
406 Remote {
407 local_addr: LocalSpec::Inet((default_host!(local), 9050)),
408 remote_addr: RemoteSpec::Socks,
409 protocol: Protocol::Tcp,
410 },
411 ),
412 (
413 "tproxy",
414 Remote {
415 local_addr: LocalSpec::Inet((default_host!(local), TPROXY_DEFAULT_PORT)),
416 remote_addr: RemoteSpec::Tproxy,
417 protocol: Protocol::Tcp,
418 },
419 ),
420 (
421 "tproxy/udp",
422 Remote {
423 local_addr: LocalSpec::Inet((default_host!(local), TPROXY_DEFAULT_PORT)),
424 remote_addr: RemoteSpec::Tproxy,
425 protocol: Protocol::Udp,
426 },
427 ),
428 (
429 "5000:tproxy",
430 Remote {
431 local_addr: LocalSpec::Inet((default_host!(local), 5000)),
432 remote_addr: RemoteSpec::Tproxy,
433 protocol: Protocol::Tcp,
434 },
435 ),
436 (
437 "4567:tproxy/udp",
438 Remote {
439 local_addr: LocalSpec::Inet((default_host!(local), 4567)),
440 remote_addr: RemoteSpec::Tproxy,
441 protocol: Protocol::Udp,
442 },
443 ),
444 (
445 "127.0.0.1:1081:tproxy",
446 Remote {
447 local_addr: LocalSpec::Inet((String::from("127.0.0.1"), 1081)),
448 remote_addr: RemoteSpec::Tproxy,
449 protocol: Protocol::Tcp,
450 },
451 ),
452 (
453 "127.0.0.1:1081:tproxy/udp",
454 Remote {
455 local_addr: LocalSpec::Inet((String::from("127.0.0.1"), 1081)),
456 remote_addr: RemoteSpec::Tproxy,
457 protocol: Protocol::Udp,
458 },
459 ),
460 (
461 "[::1]:12345:tproxy/udp",
462 Remote {
463 local_addr: LocalSpec::Inet((String::from("::1"), 12345)),
464 remote_addr: RemoteSpec::Tproxy,
465 protocol: Protocol::Udp,
466 },
467 ),
468 (
469 "1.1.1.1:53/udp",
470 Remote {
471 local_addr: LocalSpec::Inet((default_host!(unspec), 53)),
472 remote_addr: RemoteSpec::Inet((String::from("1.1.1.1"), 53)),
473 protocol: Protocol::Udp,
474 },
475 ),
476 (
477 "localhost:5353:1.1.1.1:53/udp",
478 Remote {
479 local_addr: LocalSpec::Inet((String::from("localhost"), 5353)),
480 remote_addr: RemoteSpec::Inet((String::from("1.1.1.1"), 53)),
481 protocol: Protocol::Udp,
482 },
483 ),
484 (
485 "22:example.com:22",
486 Remote {
487 local_addr: LocalSpec::Inet((default_host!(unspec), 22)),
488 remote_addr: RemoteSpec::Inet((String::from("example.com"), 22)),
489 protocol: Protocol::Tcp,
490 },
491 ),
492 (
493 "[::1]:8080:google.com:80",
494 Remote {
495 local_addr: LocalSpec::Inet((String::from("::1"), 8080)),
496 remote_addr: RemoteSpec::Inet((String::from("google.com"), 80)),
497 protocol: Protocol::Tcp,
498 },
499 ),
500 (
501 "localhost:5354:[2001:4860:4860:0:0:0:0:8888]:53/udp",
502 Remote {
503 local_addr: LocalSpec::Inet((String::from("localhost"), 5354)),
504 remote_addr: RemoteSpec::Inet((
505 String::from("2001:4860:4860:0:0:0:0:8888"),
506 53,
507 )),
508 protocol: Protocol::Udp,
509 },
510 ),
511 (
512 "stdio:google.com:80",
513 Remote {
514 local_addr: LocalSpec::Stdio,
515 remote_addr: RemoteSpec::Inet((String::from("google.com"), 80)),
516 protocol: Protocol::Tcp,
517 },
518 ),
519 (
520 "stdio:socks",
521 Remote {
522 local_addr: LocalSpec::Stdio,
523 remote_addr: RemoteSpec::Socks,
524 protocol: Protocol::Tcp,
525 },
526 ),
527 (
528 "stdio:443",
529 Remote {
530 local_addr: LocalSpec::Stdio,
531 remote_addr: RemoteSpec::Inet((default_host!(local), 443)),
532 protocol: Protocol::Tcp,
533 },
534 ),
535 (
536 "stdio:5353/udp",
537 Remote {
538 local_addr: LocalSpec::Stdio,
539 remote_addr: RemoteSpec::Inet((default_host!(local), 5353)),
540 protocol: Protocol::Udp,
541 },
542 ),
543 ];
544 for (s, expected) in tests {
545 let actual = s.parse::<Remote>().unwrap();
547 assert_eq!(actual, *expected);
548 let reparsed = actual.to_string().parse::<Remote>().unwrap();
550 assert_eq!(reparsed, *expected);
551 }
552 "just_a_hostname".parse::<Remote>().unwrap_err();
553 "socks/udp".parse::<Remote>().unwrap_err();
554 assert!(matches!(
555 "socks/udp".parse::<Remote>().unwrap_err(),
556 Error::UdpSocks
557 ));
558 assert!(matches!(
559 "stdio:tproxy".parse::<Remote>().unwrap_err(),
560 Error::StdioTproxy
561 ));
562 }
563}