common/
cli.rs

1use std::error::Error;
2use std::net::IpAddr;
3use std::path::PathBuf;
4use std::str::FromStr;
5
6use structopt::{clap::arg_enum, StructOpt};
7use trust_dns_resolver::Resolver;
8
9#[derive(StructOpt, Debug)]
10pub enum TunnelerType {
11    Tcp,
12    Dns {
13        #[structopt(long, env)]
14        read_timeout_in_milliseconds: u64,
15        #[structopt(long, env)]
16        idle_client_timeout_in_milliseconds: u64,
17        #[structopt(default_value = "", long, env)]
18        client_suffix: String,
19    },
20    Tls {
21        #[structopt(long, env)]
22        ca_cert: PathBuf,
23        #[structopt(long, env)]
24        cert: PathBuf,
25        #[structopt(long, env)]
26        key: PathBuf,
27        #[structopt(long, env)]
28        server_hostname: String,
29    },
30}
31
32arg_enum! {
33    #[derive(Debug)]
34    pub enum TunneledType {
35        Tcp,
36        Udp,
37    }
38}
39
40fn parse_or_resolve_ip(src: &str) -> Result<IpAddr, Box<dyn Error>> {
41    IpAddr::from_str(src).or_else(|_| {
42        let resolver = Resolver::from_system_conf()?;
43        let response = resolver.lookup_ip(src)?;
44        response
45            .iter()
46            .next()
47            .ok_or_else(|| format!("failed to parse or resolve {} to an IP", src).into())
48    })
49}
50
51#[derive(StructOpt, Debug)]
52pub struct Cli {
53    #[structopt(subcommand)]
54    pub tunneler_type: TunnelerType,
55
56    #[structopt(long, env, possible_values = &TunneledType::variants(), case_insensitive = true)]
57    pub tunneled_type: TunneledType,
58
59    #[structopt(long, env, parse(try_from_str = parse_or_resolve_ip))]
60    pub remote_address: IpAddr,
61
62    #[structopt(long, env)]
63    pub remote_port: u16,
64
65    #[structopt(default_value = "0.0.0.0", long, env)]
66    pub local_address: IpAddr,
67
68    #[structopt(default_value = "8888", long, env)]
69    pub local_port: u16,
70
71    #[structopt(default_value = "info", long, env)]
72    pub log_level: log::LevelFilter,
73}
74
75#[cfg(test)]
76mod tests {
77    use std::net::{Ipv4Addr, Ipv6Addr};
78
79    use super::*;
80
81    #[test]
82    fn parse_or_resolve_ip_ipv4() -> Result<(), Box<dyn Error>> {
83        let res = parse_or_resolve_ip("127.0.0.1")?;
84        assert_eq!(res, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
85        Ok(())
86    }
87
88    #[test]
89    fn parse_or_resolve_ip_ipv6() -> Result<(), Box<dyn Error>> {
90        let res = parse_or_resolve_ip("::1")?;
91        assert_eq!(res, IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
92        Ok(())
93    }
94
95    #[test]
96    fn parse_or_resolve_ip_lookup_success() -> Result<(), Box<dyn Error>> {
97        let res = parse_or_resolve_ip("localhost")?;
98        assert_eq!(res, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
99        Ok(())
100    }
101
102    #[test]
103    fn parse_or_resolve_ip_lookup_fail() -> Result<(), Box<dyn Error>> {
104        let res = parse_or_resolve_ip("blablabla");
105        assert!(res.is_err());
106        Ok(())
107    }
108}