quick_file_transfer/ssh/
remote_info.rs

1use anyhow::bail;
2
3use crate::config::ssh::{SendSshArgs, TargetComponents};
4
5use std::{
6    net::{IpAddr, ToSocketAddrs},
7    path::Path,
8};
9
10#[derive(Debug, Clone, Copy)]
11pub enum Remote<'a> {
12    Ip(&'a str),
13    DnsHostname(IpAddr),
14    #[cfg(feature = "mdns")]
15    MdnsHostname(&'a str),
16}
17
18impl<'a> Remote<'a> {
19    pub fn new(host: &'a str) -> anyhow::Result<Self> {
20        tracing::trace!("Resolving remote: '{host}'");
21        if host.parse::<std::net::IpAddr>().is_ok() {
22            return Ok(Self::Ip(host));
23        }
24        #[cfg(feature = "mdns")]
25        if crate::ssh::mdns_util::is_mdns_hostname(host) {
26            return Ok(Self::MdnsHostname(host));
27        }
28
29        let addrs_iter = match (host, 0).to_socket_addrs() {
30            Ok(addrs_iter) => addrs_iter,
31            Err(e) => {
32                #[cfg(feature = "mdns")]
33                bail!("'{host}' was not recognized as an IP or a mDNS/DNS-SD hostname, attempt at resolving as a regular DNS hostname failed: {e}");
34                #[cfg(not(feature = "mdns"))]
35                bail!("'{host}' was not recognized as an IP, (install with mdns feature to resolve mDNS), attempt at resolving as a regular DNS hostname failed: {e}");
36            }
37        };
38
39        let mut ipv6_fallback: Option<IpAddr> = None;
40        for addr in addrs_iter {
41            if addr.ip().is_ipv4() {
42                return Ok(Self::DnsHostname(addr.ip()));
43            } else if ipv6_fallback.is_none() {
44                ipv6_fallback = Some(addr.ip());
45            }
46        }
47        if let Some(ipv6) = ipv6_fallback {
48            return Ok(Self::DnsHostname(ipv6));
49        }
50        unreachable!("Should be")
51    }
52
53    pub fn to_resolved_ip(
54        self,
55        #[cfg(feature = "mdns")] timeout_ms: u64,
56    ) -> anyhow::Result<IpAddr> {
57        match self {
58            Remote::Ip(ip) => Ok(ip.parse()?),
59            Remote::DnsHostname(hn) => Ok(hn),
60            #[cfg(feature = "mdns")]
61            Remote::MdnsHostname(hn) => {
62                let ip = super::mdns_util::get_remote_ip_from_mdns_hostname(
63                    hn,
64                    timeout_ms,
65                    crate::config::misc::IpVersion::V4,
66                )?;
67                Ok(ip)
68            }
69        }
70    }
71}
72
73pub struct RemoteInfo<'a> {
74    pub user: &'a str,
75    pub ssh_port: u16,
76    pub resolved_ip: IpAddr,
77    pub destination: &'a Path,
78}
79
80impl<'a> RemoteInfo<'a> {
81    pub fn new(user: &'a str, ssh_port: u16, resolved_ip: IpAddr, destination: &'a Path) -> Self {
82        Self {
83            user,
84            ssh_port,
85            resolved_ip,
86            destination,
87        }
88    }
89
90    pub fn from_args(ssh_args: &'a SendSshArgs, components: &'a TargetComponents) -> Self {
91        let TargetComponents {
92            ref user,
93            ref host,
94            ref destination,
95        } = components;
96
97        let resolved_ip: IpAddr = Remote::new(host.as_str())
98            .unwrap()
99            .to_resolved_ip(
100                #[cfg(feature = "mdns")]
101                ssh_args.mdns_resolve_timeout_ms,
102            )
103            .unwrap();
104
105        Self::new(user, ssh_args.ssh_port, resolved_ip, destination)
106    }
107
108    pub fn ip(&self) -> IpAddr {
109        self.resolved_ip
110    }
111    pub fn user(&self) -> &str {
112        self.user
113    }
114    pub fn dest(&self) -> &Path {
115        self.destination
116    }
117    pub fn ssh_port(&self) -> u16 {
118        self.ssh_port
119    }
120}