1use std::{
2 fmt::{Display, Formatter},
3 net::ToSocketAddrs,
4 num::ParseIntError,
5 str::FromStr,
6 time::Duration,
7};
8
9use hopr_transport_identity::Multiaddr;
10pub use hopr_transport_network::config::NetworkConfig;
11pub use hopr_transport_probe::config::ProbeConfig;
12pub use hopr_transport_protocol::config::ProtocolConfig;
13use hopr_transport_session::MIN_BALANCER_SAMPLING_INTERVAL;
14use proc_macro_regex::regex;
15use serde::{Deserialize, Serialize};
16use serde_with::serde_as;
17use validator::{Validate, ValidationError};
18
19use crate::errors::HoprTransportError;
20
21pub struct HoprTransportConfig {
22 pub transport: TransportConfig,
23 pub network: NetworkConfig,
24 pub protocol: ProtocolConfig,
25 pub probe: ProbeConfig,
26 pub session: SessionGlobalConfig,
27}
28
29regex!(is_dns_address_regex "^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)*[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$");
30
31#[inline]
33pub fn looks_like_domain(s: &str) -> bool {
34 is_dns_address_regex(s)
35}
36
37pub fn is_reachable_domain(host: &str) -> bool {
39 host.to_socket_addrs().is_ok_and(|i| i.into_iter().next().is_some())
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub enum HostType {
45 IPv4(String),
47 Domain(String),
49}
50
51impl Default for HostType {
52 fn default() -> Self {
53 HostType::IPv4("127.0.0.1".to_owned())
54 }
55}
56
57#[derive(Debug, Serialize, Deserialize, Validate, Clone, PartialEq)]
63#[serde(deny_unknown_fields)]
64pub struct HostConfig {
65 #[serde(default)] pub address: HostType,
68 #[validate(range(min = 1u16))]
70 #[serde(default)] pub port: u16,
72}
73
74impl FromStr for HostConfig {
75 type Err = String;
76
77 fn from_str(s: &str) -> Result<Self, Self::Err> {
78 let (ip_or_dns, str_port) = match s.split_once(':') {
79 None => return Err("Invalid host, is not in the '<host>:<port>' format".into()),
80 Some(split) => split,
81 };
82
83 let port = str_port.parse().map_err(|e: ParseIntError| e.to_string())?;
84
85 if validator::ValidateIp::validate_ipv4(&ip_or_dns) {
86 Ok(Self {
87 address: HostType::IPv4(ip_or_dns.to_owned()),
88 port,
89 })
90 } else if looks_like_domain(ip_or_dns) {
91 Ok(Self {
92 address: HostType::Domain(ip_or_dns.to_owned()),
93 port,
94 })
95 } else {
96 Err("Not a valid IPv4 or domain host".into())
97 }
98 }
99}
100
101impl Display for HostConfig {
102 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
103 write!(f, "{:?}:{}", self.address, self.port)
104 }
105}
106
107fn default_multiaddr_transport(port: u16) -> String {
108 cfg_if::cfg_if! {
109 if #[cfg(feature = "transport-quic")] {
110 let on_dappnode = std::env::var("DAPPNODE")
114 .map(|v| v.to_lowercase() == "true")
115 .unwrap_or(false);
116
117 let uses_nat = std::env::var("HOPRD_NAT")
119 .map(|v| v.to_lowercase() == "true")
120 .unwrap_or(on_dappnode);
121
122 if uses_nat {
123 format!("tcp/{port}")
124 } else {
125 format!("udp/{port}/quic-v1")
126 }
127 } else {
128 format!("tcp/{port}")
129 }
130 }
131}
132
133impl TryFrom<&HostConfig> for Multiaddr {
134 type Error = HoprTransportError;
135
136 fn try_from(value: &HostConfig) -> Result<Self, Self::Error> {
137 match &value.address {
138 HostType::IPv4(ip) => Multiaddr::from_str(
139 format!("/ip4/{}/{}", ip.as_str(), default_multiaddr_transport(value.port)).as_str(),
140 )
141 .map_err(|e| HoprTransportError::Api(e.to_string())),
142 HostType::Domain(domain) => Multiaddr::from_str(
143 format!("/dns4/{}/{}", domain.as_str(), default_multiaddr_transport(value.port)).as_str(),
144 )
145 .map_err(|e| HoprTransportError::Api(e.to_string())),
146 }
147 }
148}
149
150fn validate_ipv4_address(s: &str) -> Result<(), ValidationError> {
151 if validator::ValidateIp::validate_ipv4(&s) {
152 let ipv4 = std::net::Ipv4Addr::from_str(s)
153 .map_err(|_| ValidationError::new("Failed to deserialize the string into an ipv4 address"))?;
154
155 if ipv4.is_private() || ipv4.is_multicast() || ipv4.is_unspecified() {
156 return Err(ValidationError::new(
157 "IPv4 cannot be private, multicast or unspecified (0.0.0.0)",
158 ))?;
159 }
160 Ok(())
161 } else {
162 Err(ValidationError::new("Invalid IPv4 address provided"))
163 }
164}
165
166fn validate_dns_address(s: &str) -> Result<(), ValidationError> {
167 if looks_like_domain(s) || is_reachable_domain(s) {
168 Ok(())
169 } else {
170 Err(ValidationError::new("Invalid DNS address provided"))
171 }
172}
173
174pub fn validate_external_host(host: &HostConfig) -> Result<(), ValidationError> {
176 match &host.address {
177 HostType::IPv4(ip4) => validate_ipv4_address(ip4),
178 HostType::Domain(domain) => validate_dns_address(domain),
179 }
180}
181
182#[derive(Debug, Default, Serialize, Deserialize, Validate, Clone, PartialEq)]
184#[serde(deny_unknown_fields)]
185pub struct TransportConfig {
186 #[serde(default)]
189 pub announce_local_addresses: bool,
190 #[serde(default)]
193 pub prefer_local_addresses: bool,
194}
195
196const DEFAULT_SESSION_IDLE_TIMEOUT: Duration = Duration::from_secs(180);
197
198const SESSION_IDLE_MIN_TIMEOUT: Duration = Duration::from_secs(60);
199
200const DEFAULT_SESSION_ESTABLISH_RETRY_DELAY: Duration = Duration::from_secs(2);
201
202const DEFAULT_SESSION_ESTABLISH_MAX_RETRIES: u32 = 3;
203
204const DEFAULT_SESSION_BALANCER_SAMPLING: Duration = Duration::from_secs(1);
205
206fn default_session_establish_max_retries() -> u32 {
207 DEFAULT_SESSION_ESTABLISH_MAX_RETRIES
208}
209
210fn default_session_idle_timeout() -> std::time::Duration {
211 DEFAULT_SESSION_IDLE_TIMEOUT
212}
213
214fn default_session_establish_retry_delay() -> std::time::Duration {
215 DEFAULT_SESSION_ESTABLISH_RETRY_DELAY
216}
217
218fn default_session_balancer_sampling() -> std::time::Duration {
219 DEFAULT_SESSION_BALANCER_SAMPLING
220}
221
222fn validate_session_idle_timeout(value: &std::time::Duration) -> Result<(), ValidationError> {
223 if SESSION_IDLE_MIN_TIMEOUT <= *value {
224 Ok(())
225 } else {
226 Err(ValidationError::new("session idle timeout is too low"))
227 }
228}
229
230fn validate_balancer_sampling(value: &std::time::Duration) -> Result<(), ValidationError> {
231 if MIN_BALANCER_SAMPLING_INTERVAL <= *value {
232 Ok(())
233 } else {
234 Err(ValidationError::new("balancer sampling interval is too low"))
235 }
236}
237
238#[serde_as]
240#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Validate, smart_default::SmartDefault)]
241#[serde(deny_unknown_fields)]
242pub struct SessionGlobalConfig {
243 #[validate(custom(function = "validate_session_idle_timeout"))]
247 #[default(DEFAULT_SESSION_IDLE_TIMEOUT)]
248 #[serde(default = "default_session_idle_timeout")]
249 #[serde_as(as = "serde_with::DurationSeconds<u64>")]
250 pub idle_timeout: std::time::Duration,
251
252 #[validate(range(min = 0, max = 20))]
257 #[default(DEFAULT_SESSION_ESTABLISH_MAX_RETRIES)]
258 #[serde(default = "default_session_establish_max_retries")]
259 pub establish_max_retries: u32,
260
261 #[default(DEFAULT_SESSION_ESTABLISH_RETRY_DELAY)]
265 #[serde(default = "default_session_establish_retry_delay")]
266 #[serde_as(as = "serde_with::DurationSeconds<u64>")]
267 pub establish_retry_timeout: std::time::Duration,
268
269 #[validate(custom(function = "validate_balancer_sampling"))]
273 #[default(DEFAULT_SESSION_BALANCER_SAMPLING)]
274 #[serde(default = "default_session_balancer_sampling")]
275 #[serde_as(as = "serde_with::DurationMilliSeconds<u64>")]
276 pub balancer_sampling_interval: std::time::Duration,
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_valid_domains_for_looks_like_a_domain() {
285 assert!(looks_like_domain("localhost"));
286 assert!(looks_like_domain("hoprnet.org"));
287 assert!(looks_like_domain("hub.hoprnet.org"));
288 }
289
290 #[test]
291 fn test_valid_domains_for_does_not_look_like_a_domain() {
292 assert!(!looks_like_domain(".org"));
293 assert!(!looks_like_domain("-hoprnet-.org"));
294 }
295
296 #[test]
297 fn test_valid_domains_should_be_reachable() {
298 assert!(!is_reachable_domain("google.com"));
299 }
300
301 #[test]
302 fn test_verify_valid_ip4_addresses() {
303 assert!(validate_ipv4_address("1.1.1.1").is_ok());
304 assert!(validate_ipv4_address("1.255.1.1").is_ok());
305 assert!(validate_ipv4_address("187.1.1.255").is_ok());
306 assert!(validate_ipv4_address("127.0.0.1").is_ok());
307 }
308
309 #[test]
310 fn test_verify_invalid_ip4_addresses() {
311 assert!(validate_ipv4_address("1.256.1.1").is_err());
312 assert!(validate_ipv4_address("-1.1.1.255").is_err());
313 assert!(validate_ipv4_address("127.0.0.256").is_err());
314 assert!(validate_ipv4_address("1").is_err());
315 assert!(validate_ipv4_address("1.1").is_err());
316 assert!(validate_ipv4_address("1.1.1").is_err());
317 assert!(validate_ipv4_address("1.1.1.1.1").is_err());
318 }
319
320 #[test]
321 fn test_verify_valid_dns_addresses() {
322 assert!(validate_dns_address("localhost").is_ok());
323 assert!(validate_dns_address("google.com").is_ok());
324 assert!(validate_dns_address("hub.hoprnet.org").is_ok());
325 }
326
327 #[test]
328 fn test_verify_invalid_dns_addresses() {
329 assert!(validate_dns_address("-hoprnet-.org").is_err());
330 }
331
332 #[test]
333 fn test_multiaddress_on_dappnode_default() {
334 temp_env::with_var("DAPPNODE", Some("true"), || {
335 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
336 });
337 }
338
339 #[cfg(feature = "transport-quic")]
340 #[test]
341 fn test_multiaddress_on_non_dappnode_default() {
342 temp_env::with_vars([("DAPPNODE", Some("false")), ("HOPRD_NAT", Some("false"))], || {
343 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
344 });
345 }
346
347 #[cfg(not(feature = "transport-quic"))]
348 #[test]
349 fn test_multiaddress_on_non_dappnode_default() {
350 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
351 }
352
353 #[test]
354 fn test_multiaddress_on_non_dappnode_uses_nat() {
355 temp_env::with_var("HOPRD_NAT", Some("true"), || {
356 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
357 });
358 }
359
360 #[cfg(feature = "transport-quic")]
361 #[test]
362 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
363 temp_env::with_var("HOPRD_NAT", Some("false"), || {
364 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
365 });
366 }
367
368 #[cfg(not(feature = "transport-quic"))]
369 #[test]
370 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
371 temp_env::with_var("HOPRD_NAT", Some("false"), || {
372 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
373 });
374 }
375
376 #[cfg(feature = "transport-quic")]
377 #[test]
378 fn test_multiaddress_on_dappnode_not_uses_nat() {
379 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
380 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
381 });
382 }
383
384 #[cfg(not(feature = "transport-quic"))]
385 #[test]
386 fn test_multiaddress_on_dappnode_not_uses_nat() {
387 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
388 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
389 });
390 }
391}