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, MIN_SURB_BUFFER_DURATION};
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_MAX_SESSIONS: u32 = 2048;
197
198const DEFAULT_SESSION_IDLE_TIMEOUT: Duration = Duration::from_secs(180);
199
200const SESSION_IDLE_MIN_TIMEOUT: Duration = Duration::from_secs(60);
201
202const DEFAULT_SESSION_ESTABLISH_RETRY_DELAY: Duration = Duration::from_secs(2);
203
204const DEFAULT_SESSION_ESTABLISH_MAX_RETRIES: u32 = 3;
205
206const DEFAULT_SESSION_BALANCER_SAMPLING: Duration = Duration::from_millis(500);
207
208const DEFAULT_SESSION_BALANCER_BUFFER_DURATION: Duration = Duration::from_millis(5000);
209
210fn default_session_max_sessions() -> u32 {
211 DEFAULT_SESSION_MAX_SESSIONS
212}
213
214fn default_session_balancer_buffer_duration() -> std::time::Duration {
215 DEFAULT_SESSION_BALANCER_BUFFER_DURATION
216}
217
218fn default_session_establish_max_retries() -> u32 {
219 DEFAULT_SESSION_ESTABLISH_MAX_RETRIES
220}
221
222fn default_session_idle_timeout() -> std::time::Duration {
223 DEFAULT_SESSION_IDLE_TIMEOUT
224}
225
226fn default_session_establish_retry_delay() -> std::time::Duration {
227 DEFAULT_SESSION_ESTABLISH_RETRY_DELAY
228}
229
230fn default_session_balancer_sampling() -> std::time::Duration {
231 DEFAULT_SESSION_BALANCER_SAMPLING
232}
233
234fn validate_session_idle_timeout(value: &std::time::Duration) -> Result<(), ValidationError> {
235 if SESSION_IDLE_MIN_TIMEOUT <= *value {
236 Ok(())
237 } else {
238 Err(ValidationError::new("session idle timeout is too low"))
239 }
240}
241
242fn validate_balancer_sampling(value: &std::time::Duration) -> Result<(), ValidationError> {
243 if MIN_BALANCER_SAMPLING_INTERVAL <= *value {
244 Ok(())
245 } else {
246 Err(ValidationError::new("balancer sampling interval is too low"))
247 }
248}
249
250fn validate_balancer_buffer_duration(value: &std::time::Duration) -> Result<(), ValidationError> {
251 if MIN_SURB_BUFFER_DURATION <= *value {
252 Ok(())
253 } else {
254 Err(ValidationError::new("minmum SURB buffer duration is too low"))
255 }
256}
257
258#[serde_as]
260#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Validate, smart_default::SmartDefault)]
261#[serde(deny_unknown_fields)]
262pub struct SessionGlobalConfig {
263 #[validate(custom(function = "validate_session_idle_timeout"))]
267 #[default(DEFAULT_SESSION_IDLE_TIMEOUT)]
268 #[serde(default = "default_session_idle_timeout")]
269 #[serde_as(as = "serde_with::DurationSeconds<u64>")]
270 pub idle_timeout: std::time::Duration,
271
272 #[default(DEFAULT_SESSION_MAX_SESSIONS)]
278 #[serde(default = "default_session_max_sessions")]
279 #[validate(range(min = 1))]
280 pub maximum_sessions: u32,
281
282 #[validate(range(min = 0, max = 20))]
287 #[default(DEFAULT_SESSION_ESTABLISH_MAX_RETRIES)]
288 #[serde(default = "default_session_establish_max_retries")]
289 pub establish_max_retries: u32,
290
291 #[default(DEFAULT_SESSION_ESTABLISH_RETRY_DELAY)]
295 #[serde(default = "default_session_establish_retry_delay")]
296 #[serde_as(as = "serde_with::DurationSeconds<u64>")]
297 pub establish_retry_timeout: std::time::Duration,
298
299 #[validate(custom(function = "validate_balancer_sampling"))]
303 #[default(DEFAULT_SESSION_BALANCER_SAMPLING)]
304 #[serde(default = "default_session_balancer_sampling")]
305 #[serde_as(as = "serde_with::DurationMilliSeconds<u64>")]
306 pub balancer_sampling_interval: std::time::Duration,
307
308 #[validate(custom(function = "validate_balancer_buffer_duration"))]
316 #[default(DEFAULT_SESSION_BALANCER_BUFFER_DURATION)]
317 #[serde(default = "default_session_balancer_buffer_duration")]
318 #[serde_as(as = "serde_with::DurationSeconds<u64>")]
319 pub balancer_minimum_surb_buffer_duration: std::time::Duration,
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn test_valid_domains_for_looks_like_a_domain() {
328 assert!(looks_like_domain("localhost"));
329 assert!(looks_like_domain("hoprnet.org"));
330 assert!(looks_like_domain("hub.hoprnet.org"));
331 }
332
333 #[test]
334 fn test_valid_domains_for_does_not_look_like_a_domain() {
335 assert!(!looks_like_domain(".org"));
336 assert!(!looks_like_domain("-hoprnet-.org"));
337 }
338
339 #[test]
340 fn test_valid_domains_should_be_reachable() {
341 assert!(!is_reachable_domain("google.com"));
342 }
343
344 #[test]
345 fn test_verify_valid_ip4_addresses() {
346 assert!(validate_ipv4_address("1.1.1.1").is_ok());
347 assert!(validate_ipv4_address("1.255.1.1").is_ok());
348 assert!(validate_ipv4_address("187.1.1.255").is_ok());
349 assert!(validate_ipv4_address("127.0.0.1").is_ok());
350 }
351
352 #[test]
353 fn test_verify_invalid_ip4_addresses() {
354 assert!(validate_ipv4_address("1.256.1.1").is_err());
355 assert!(validate_ipv4_address("-1.1.1.255").is_err());
356 assert!(validate_ipv4_address("127.0.0.256").is_err());
357 assert!(validate_ipv4_address("1").is_err());
358 assert!(validate_ipv4_address("1.1").is_err());
359 assert!(validate_ipv4_address("1.1.1").is_err());
360 assert!(validate_ipv4_address("1.1.1.1.1").is_err());
361 }
362
363 #[test]
364 fn test_verify_valid_dns_addresses() {
365 assert!(validate_dns_address("localhost").is_ok());
366 assert!(validate_dns_address("google.com").is_ok());
367 assert!(validate_dns_address("hub.hoprnet.org").is_ok());
368 }
369
370 #[test]
371 fn test_verify_invalid_dns_addresses() {
372 assert!(validate_dns_address("-hoprnet-.org").is_err());
373 }
374
375 #[test]
376 fn test_multiaddress_on_dappnode_default() {
377 temp_env::with_var("DAPPNODE", Some("true"), || {
378 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
379 });
380 }
381
382 #[cfg(feature = "transport-quic")]
383 #[test]
384 fn test_multiaddress_on_non_dappnode_default() {
385 temp_env::with_vars([("DAPPNODE", Some("false")), ("HOPRD_NAT", Some("false"))], || {
386 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
387 });
388 }
389
390 #[cfg(not(feature = "transport-quic"))]
391 #[test]
392 fn test_multiaddress_on_non_dappnode_default() {
393 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
394 }
395
396 #[test]
397 fn test_multiaddress_on_non_dappnode_uses_nat() {
398 temp_env::with_var("HOPRD_NAT", Some("true"), || {
399 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
400 });
401 }
402
403 #[cfg(feature = "transport-quic")]
404 #[test]
405 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
406 temp_env::with_var("HOPRD_NAT", Some("false"), || {
407 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
408 });
409 }
410
411 #[cfg(not(feature = "transport-quic"))]
412 #[test]
413 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
414 temp_env::with_var("HOPRD_NAT", Some("false"), || {
415 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
416 });
417 }
418
419 #[cfg(feature = "transport-quic")]
420 #[test]
421 fn test_multiaddress_on_dappnode_not_uses_nat() {
422 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
423 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
424 });
425 }
426
427 #[cfg(not(feature = "transport-quic"))]
428 #[test]
429 fn test_multiaddress_on_dappnode_not_uses_nat() {
430 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
431 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
432 });
433 }
434}