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#[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 }
56
57#[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 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
151impl 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 if first_byte == b'[' {
162 let sockaddr6 = SocketAddrV6::from_str(s)
165 .context("invalid IPv6 socket address")?;
166 return Self::try_from(sockaddr6);
167 }
168
169 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 if let Ok(ip4) = Ipv4Addr::from_str(prefix) {
178 return Ok(LxSocketAddress::TcpIpv4 { ip: ip4, port });
179 }
180
181 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 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 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}