Skip to main content

lexe_common/ln/
addr.rs

1use std::{
2    fmt,
3    net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
4    str::FromStr,
5};
6
7use anyhow::{Context, ensure, format_err};
8use lightning::{ln::msgs::SocketAddress, util::ser::Hostname};
9#[cfg(any(test, feature = "test-utils"))]
10use proptest_derive::Arbitrary;
11use serde_with::{DeserializeFromStr, SerializeDisplay};
12
13#[cfg(any(test, feature = "test-utils"))]
14use crate::test_utils::arbitrary;
15
16/// `LxSocketAddress` represents an internet address of a remote lightning
17/// network peer.
18///
19/// It's morally equivalent to [`lightning::ln::msgs::SocketAddress`], but
20/// intentionally ignores all TOR-related addresses since we don't currently
21/// support TOR. It also has a well-defined human-readable serialization format,
22/// unlike the LDK type.
23#[derive(Clone, Eq, PartialEq, Hash)]
24#[derive(SerializeDisplay, DeserializeFromStr)]
25#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
26pub enum LxSocketAddress {
27    TcpIpv4 {
28        #[cfg_attr(
29            any(test, feature = "test-utils"),
30            proptest(strategy = "arbitrary::any_ipv4_addr()")
31        )]
32        ip: Ipv4Addr,
33        port: u16,
34    },
35
36    TcpIpv6 {
37        #[cfg_attr(
38            any(test, feature = "test-utils"),
39            proptest(strategy = "arbitrary::any_ipv6_addr()")
40        )]
41        ip: Ipv6Addr,
42        port: u16,
43    },
44
45    TcpDns {
46        #[cfg_attr(
47            any(test, feature = "test-utils"),
48            proptest(strategy = "arbitrary::any_hostname()")
49        )]
50        hostname: Hostname,
51        port: u16,
52    },
53    // Intentionally left out: OnionV2, OnionV3
54    // We don't support TOR connections atm.
55}
56
57// There's no DNS resolution in SGX, so we can only impl for non SGX.
58// When building for SGX, `TcpStream::connect` takes a string,
59// and hostname resolution is done outside of the enclave.
60#[cfg(not(target_env = "sgx"))]
61impl std::net::ToSocketAddrs for LxSocketAddress {
62    type Iter = std::vec::IntoIter<SocketAddr>;
63
64    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
65        match self {
66            LxSocketAddress::TcpIpv4 { ip, port } => {
67                let addr = SocketAddr::V4(SocketAddrV4::new(*ip, *port));
68                Ok(vec![addr].into_iter())
69            }
70            LxSocketAddress::TcpIpv6 { ip, port } => {
71                let addr = SocketAddr::V6(SocketAddrV6::new(*ip, *port, 0, 0));
72                Ok(vec![addr].into_iter())
73            }
74            // This branch does hostname resolution
75            LxSocketAddress::TcpDns { hostname, port } =>
76                (hostname.as_str(), *port).to_socket_addrs(),
77        }
78    }
79}
80
81impl From<SocketAddrV4> for LxSocketAddress {
82    fn from(value: SocketAddrV4) -> Self {
83        Self::TcpIpv4 {
84            ip: *value.ip(),
85            port: value.port(),
86        }
87    }
88}
89
90impl TryFrom<SocketAddrV6> for LxSocketAddress {
91    type Error = anyhow::Error;
92    fn try_from(addr: SocketAddrV6) -> Result<Self, Self::Error> {
93        ensure!(
94            addr.scope_id() == 0 && addr.flowinfo() == 0,
95            "IPv6 address' scope_id and flowinfo must both be zero"
96        );
97        Ok(Self::TcpIpv6 {
98            ip: *addr.ip(),
99            port: addr.port(),
100        })
101    }
102}
103
104impl TryFrom<SocketAddr> for LxSocketAddress {
105    type Error = anyhow::Error;
106    fn try_from(value: SocketAddr) -> Result<Self, Self::Error> {
107        match value {
108            SocketAddr::V4(v4) => Ok(Self::from(v4)),
109            SocketAddr::V6(v6) => Self::try_from(v6),
110        }
111    }
112}
113
114impl From<LxSocketAddress> for SocketAddress {
115    fn from(value: LxSocketAddress) -> Self {
116        match value {
117            LxSocketAddress::TcpIpv4 { ip, port } => Self::TcpIpV4 {
118                addr: ip.octets(),
119                port,
120            },
121            LxSocketAddress::TcpIpv6 { ip, port } => Self::TcpIpV6 {
122                addr: ip.octets(),
123                port,
124            },
125            LxSocketAddress::TcpDns { hostname, port } =>
126                Self::Hostname { hostname, port },
127        }
128    }
129}
130
131impl TryFrom<SocketAddress> for LxSocketAddress {
132    type Error = anyhow::Error;
133    fn try_from(value: SocketAddress) -> Result<Self, Self::Error> {
134        match value {
135            SocketAddress::TcpIpV4 { addr, port } => Ok(Self::TcpIpv4 {
136                ip: Ipv4Addr::from(addr),
137                port,
138            }),
139            SocketAddress::TcpIpV6 { addr, port } => Ok(Self::TcpIpv6 {
140                ip: Ipv6Addr::from(addr),
141                port,
142            }),
143            SocketAddress::Hostname { hostname, port } =>
144                Ok(Self::TcpDns { hostname, port }),
145            SocketAddress::OnionV2(..) | SocketAddress::OnionV3 { .. } =>
146                Err(format_err!("TOR onion addresses are unsupported")),
147        }
148    }
149}
150
151// `<ip4 | ip6 | hostname>:<port>`
152impl FromStr for LxSocketAddress {
153    type Err = anyhow::Error;
154
155    fn from_str(s: &str) -> Result<Self, Self::Err> {
156        ensure!(!s.is_empty(), "empty string is invalid");
157
158        let first_byte = s.as_bytes()[0];
159
160        // IPv6 socket addr format always starts with '['.
161        if first_byte == b'[' {
162            // Reuse the SocketAddrV6 parser, but make sure we reject any inputs
163            // with extra scope_id or flowinfo.
164            let sockaddr6 = SocketAddrV6::from_str(s)
165                .context("invalid IPv6 socket address")?;
166            return Self::try_from(sockaddr6);
167        }
168
169        // Try to parse out the port.
170        let (prefix, port_str) =
171            s.rsplit_once(':').context("port is required")?;
172        let port = u16::from_str(port_str).context("invalid port")?;
173
174        ensure!(!prefix.is_empty(), "hostname can't be empty");
175
176        // Try parsing as an IPv4 address.
177        if let Ok(ip4) = Ipv4Addr::from_str(prefix) {
178            return Ok(LxSocketAddress::TcpIpv4 { ip: ip4, port });
179        }
180
181        // Try parsing as a hostname / dns name.
182        if let Ok(hostname) = Hostname::try_from(prefix.to_owned()) {
183            return Ok(Self::TcpDns { hostname, port });
184        }
185
186        Err(format_err!("not a valid hostname or IP address"))
187    }
188}
189
190impl fmt::Display for LxSocketAddress {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        match self {
193            Self::TcpIpv4 { ip, port } =>
194                fmt::Display::fmt(&SocketAddrV4::new(*ip, *port), f),
195            Self::TcpIpv6 { ip, port } =>
196                fmt::Display::fmt(&SocketAddrV6::new(*ip, *port, 0, 0), f),
197            Self::TcpDns { hostname, port } =>
198                write!(f, "{}:{port}", hostname.as_str()),
199        }
200    }
201}
202
203impl fmt::Debug for LxSocketAddress {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        write!(f, "{self}")
206    }
207}
208
209#[cfg(test)]
210mod test {
211    use super::*;
212    use crate::test_utils::roundtrip;
213
214    #[test]
215    fn test_fromstr_json_equiv() {
216        roundtrip::fromstr_json_string_equiv::<LxSocketAddress>();
217    }
218
219    #[test]
220    fn test_basic() {
221        // bad
222        LxSocketAddress::from_str("").unwrap_err();
223        LxSocketAddress::from_str("foo").unwrap_err();
224        LxSocketAddress::from_str("foo:").unwrap_err();
225        LxSocketAddress::from_str("1.2.3.4:").unwrap_err();
226        LxSocketAddress::from_str(":123").unwrap_err();
227        LxSocketAddress::from_str("1.2.3.4:65538").unwrap_err();
228        LxSocketAddress::from_str("[::1]:65538").unwrap_err();
229        LxSocketAddress::from_str("[::1%6969]:5050").unwrap_err();
230        LxSocketAddress::from_str("hello! world!:5050").unwrap_err();
231
232        // good
233        assert_eq!(
234            LxSocketAddress::from_str("1.2.3.4:5050").unwrap(),
235            LxSocketAddress::TcpIpv4 {
236                ip: [1, 2, 3, 4].into(),
237                port: 5050
238            },
239        );
240        assert_eq!(
241            LxSocketAddress::from_str("[::1]:5050").unwrap(),
242            LxSocketAddress::TcpIpv6 {
243                ip: [0_u16, 0, 0, 0, 0, 0, 0, 1].into(),
244                port: 5050
245            },
246        );
247        assert_eq!(
248            LxSocketAddress::from_str("lsp.lexe.app:9735").unwrap(),
249            LxSocketAddress::TcpDns {
250                hostname: Hostname::try_from("lsp.lexe.app".to_owned())
251                    .unwrap(),
252                port: 9735
253            },
254        );
255    }
256}