hopr_transport/
config.rs

1use std::{
2    fmt::{Display, Formatter},
3    net::ToSocketAddrs,
4    num::ParseIntError,
5    str::FromStr,
6    time::Duration,
7};
8
9pub use hopr_protocol_hopr::{HoprCodecConfig, HoprTicketProcessorConfig, SurbStoreConfig};
10use hopr_transport_identity::Multiaddr;
11pub use hopr_transport_network::config::NetworkConfig;
12pub use hopr_transport_probe::config::ProbeConfig;
13use hopr_transport_session::{MIN_BALANCER_SAMPLING_INTERVAL, MIN_SURB_BUFFER_DURATION};
14use proc_macro_regex::regex;
15use validator::{Validate, ValidationError, ValidationErrors};
16
17use crate::errors::HoprTransportError;
18
19/// Complete configuration of the HOPR protocol stack.
20#[derive(Debug, smart_default::SmartDefault, Validate, Clone, Copy, PartialEq)]
21#[cfg_attr(
22    feature = "serde",
23    derive(serde::Serialize, serde::Deserialize),
24    serde(deny_unknown_fields)
25)]
26pub struct HoprProtocolConfig {
27    /// Libp2p-related transport configuration
28    #[validate(nested)]
29    pub transport: TransportConfig,
30    /// Heartbeat configuration
31    #[validate(nested)]
32    pub network: NetworkConfig,
33    /// HOPR packet pipeline configuration
34    #[validate(nested)]
35    pub packet: HoprPacketPipelineConfig,
36    /// Probing protocol configuration
37    #[validate(nested)]
38    pub probe: ProbeConfig,
39    /// Session protocol global configuration
40    #[validate(nested)]
41    pub session: SessionGlobalConfig,
42}
43
44/// Configuration of the HOPR packet pipeline.
45#[derive(Clone, Copy, Debug, Default, PartialEq, Validate)]
46#[cfg_attr(
47    feature = "serde",
48    derive(serde::Serialize, serde::Deserialize),
49    serde(deny_unknown_fields)
50)]
51pub struct HoprPacketPipelineConfig {
52    /// HOPR packet codec configuration
53    #[validate(nested)]
54    pub codec: HoprCodecConfig,
55    /// HOPR ticket processing configuration
56    #[validate(nested)]
57    pub ticket_processing: HoprTicketProcessorConfig,
58    /// Single Use Reply Block (SURB) handling configuration
59    #[validate(nested)]
60    pub surb_store: SurbStoreConfig,
61}
62
63regex!(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]$");
64
65/// Check whether the string looks like a valid domain.
66#[inline]
67pub fn looks_like_domain(s: &str) -> bool {
68    is_dns_address_regex(s)
69}
70
71/// Check whether the string is an actual reachable domain.
72pub fn is_reachable_domain(host: &str) -> bool {
73    host.to_socket_addrs().is_ok_and(|i| i.into_iter().next().is_some())
74}
75
76/// Enumeration of possible host types.
77#[derive(Debug, Clone, PartialEq)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub enum HostType {
80    /// IPv4 based host
81    IPv4(String),
82    /// DNS based host
83    Domain(String),
84}
85
86impl validator::Validate for HostType {
87    fn validate(&self) -> Result<(), ValidationErrors> {
88        match &self {
89            HostType::IPv4(ip4) => validate_ipv4_address(ip4).map_err(|e| {
90                let mut errs = ValidationErrors::new();
91                errs.add("ipv4", e);
92                errs
93            }),
94            HostType::Domain(domain) => validate_dns_address(domain).map_err(|e| {
95                let mut errs = ValidationErrors::new();
96                errs.add("domain", e);
97                errs
98            }),
99        }
100    }
101}
102
103impl Default for HostType {
104    fn default() -> Self {
105        HostType::IPv4("127.0.0.1".to_owned())
106    }
107}
108
109/// Configuration of the listening host.
110///
111/// This is used for the P2P and REST API listeners.
112///
113/// Intentionally has no default because it depends on the use case.
114#[derive(Debug, Validate, Clone, PartialEq)]
115#[cfg_attr(
116    feature = "serde",
117    derive(serde::Serialize, serde::Deserialize),
118    serde(deny_unknown_fields)
119)]
120pub struct HostConfig {
121    /// Host on which to listen
122    #[cfg_attr(feature = "serde", serde(default))]
123    pub address: HostType,
124    /// Listening TCP or UDP port (mandatory).
125    #[validate(range(min = 1u16))]
126    #[cfg_attr(feature = "serde", serde(default))]
127    pub port: u16,
128}
129
130impl FromStr for HostConfig {
131    type Err = String;
132
133    fn from_str(s: &str) -> Result<Self, Self::Err> {
134        let (ip_or_dns, str_port) = match s.split_once(':') {
135            None => return Err("Invalid host, is not in the '<host>:<port>' format".into()),
136            Some(split) => split,
137        };
138
139        let port = str_port.parse().map_err(|e: ParseIntError| e.to_string())?;
140
141        if validator::ValidateIp::validate_ipv4(&ip_or_dns) {
142            Ok(Self {
143                address: HostType::IPv4(ip_or_dns.to_owned()),
144                port,
145            })
146        } else if looks_like_domain(ip_or_dns) {
147            Ok(Self {
148                address: HostType::Domain(ip_or_dns.to_owned()),
149                port,
150            })
151        } else {
152            Err("Not a valid IPv4 or domain host".into())
153        }
154    }
155}
156
157impl Display for HostConfig {
158    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
159        write!(f, "{:?}:{}", self.address, self.port)
160    }
161}
162
163fn default_multiaddr_transport(port: u16) -> String {
164    cfg_if::cfg_if! {
165        if #[cfg(all(feature = "p2p-announce-quic", feature = "p2p-transport-quic"))] {
166            // In case we run on a Dappnode-like device, presumably behind NAT, we fall back to TCP
167            // to circumvent issues with QUIC in such environments. To make this work reliably,
168            // we would need proper NAT traversal support.
169            let on_dappnode = std::env::var("DAPPNODE")
170                .map(|v| v.to_lowercase() == "true")
171                .unwrap_or(false);
172
173            // Using HOPRD_NAT a user can overwrite the default behaviour even on a Dappnode-like device
174            let uses_nat = std::env::var("HOPRD_NAT")
175                .map(|v| v.to_lowercase() == "true")
176                .unwrap_or(on_dappnode);
177
178            if uses_nat {
179                format!("tcp/{port}")
180            } else {
181                format!("udp/{port}/quic-v1")
182            }
183        } else {
184            format!("tcp/{port}")
185        }
186    }
187}
188
189impl TryFrom<&HostConfig> for Multiaddr {
190    type Error = HoprTransportError;
191
192    fn try_from(value: &HostConfig) -> Result<Self, Self::Error> {
193        match &value.address {
194            HostType::IPv4(ip) => Multiaddr::from_str(
195                format!("/ip4/{}/{}", ip.as_str(), default_multiaddr_transport(value.port)).as_str(),
196            )
197            .map_err(|e| HoprTransportError::Api(e.to_string())),
198            HostType::Domain(domain) => Multiaddr::from_str(
199                format!("/dns4/{}/{}", domain.as_str(), default_multiaddr_transport(value.port)).as_str(),
200            )
201            .map_err(|e| HoprTransportError::Api(e.to_string())),
202        }
203    }
204}
205
206fn validate_ipv4_address(s: &str) -> Result<(), ValidationError> {
207    if validator::ValidateIp::validate_ipv4(&s) {
208        let ipv4 = std::net::Ipv4Addr::from_str(s)
209            .map_err(|_| ValidationError::new("Failed to deserialize the string into an ipv4 address"))?;
210
211        if ipv4.is_private() || ipv4.is_multicast() || ipv4.is_unspecified() {
212            return Err(ValidationError::new(
213                "IPv4 cannot be private, multicast or unspecified (0.0.0.0)",
214            ))?;
215        }
216        Ok(())
217    } else {
218        Err(ValidationError::new("Invalid IPv4 address provided"))
219    }
220}
221
222fn validate_dns_address(s: &str) -> Result<(), ValidationError> {
223    if looks_like_domain(s) || is_reachable_domain(s) {
224        Ok(())
225    } else {
226        Err(ValidationError::new("Invalid DNS address provided"))
227    }
228}
229
230/// Configuration of the physical transport mechanism.
231#[derive(Debug, Default, Validate, Clone, Copy, PartialEq)]
232#[cfg_attr(
233    feature = "serde",
234    derive(serde::Serialize, serde::Deserialize),
235    serde(deny_unknown_fields)
236)]
237pub struct TransportConfig {
238    /// When true, assume that the node is running in an isolated network and does
239    /// not need any connection to nodes outside the subnet
240    #[cfg_attr(feature = "serde", serde(default))]
241    pub announce_local_addresses: bool,
242    /// When true, assume a testnet with multiple nodes running on the same machine
243    /// or in the same private IPv4 network
244    #[cfg_attr(feature = "serde", serde(default))]
245    pub prefer_local_addresses: bool,
246}
247
248const DEFAULT_SESSION_MAX_SESSIONS: u32 = 2048;
249
250const DEFAULT_SESSION_IDLE_TIMEOUT: Duration = Duration::from_mins(3);
251
252const SESSION_IDLE_MIN_TIMEOUT: Duration = Duration::from_secs(2);
253
254const DEFAULT_SESSION_ESTABLISH_RETRY_DELAY: Duration = Duration::from_secs(2);
255
256const DEFAULT_SESSION_ESTABLISH_MAX_RETRIES: u32 = 3;
257
258const DEFAULT_SESSION_BALANCER_SAMPLING: Duration = Duration::from_millis(500);
259
260const DEFAULT_SESSION_BALANCER_BUFFER_DURATION: Duration = Duration::from_secs(5);
261
262fn default_session_max_sessions() -> u32 {
263    DEFAULT_SESSION_MAX_SESSIONS
264}
265
266fn default_session_balancer_buffer_duration() -> Duration {
267    DEFAULT_SESSION_BALANCER_BUFFER_DURATION
268}
269
270fn default_session_establish_max_retries() -> u32 {
271    DEFAULT_SESSION_ESTABLISH_MAX_RETRIES
272}
273
274fn default_session_idle_timeout() -> Duration {
275    DEFAULT_SESSION_IDLE_TIMEOUT
276}
277
278fn default_session_establish_retry_delay() -> Duration {
279    DEFAULT_SESSION_ESTABLISH_RETRY_DELAY
280}
281
282fn default_session_balancer_sampling() -> Duration {
283    DEFAULT_SESSION_BALANCER_SAMPLING
284}
285
286fn validate_session_idle_timeout(value: &Duration) -> Result<(), ValidationError> {
287    if SESSION_IDLE_MIN_TIMEOUT <= *value {
288        Ok(())
289    } else {
290        Err(ValidationError::new("session idle timeout is too low"))
291    }
292}
293
294fn validate_balancer_sampling(value: &Duration) -> Result<(), ValidationError> {
295    if MIN_BALANCER_SAMPLING_INTERVAL <= *value {
296        Ok(())
297    } else {
298        Err(ValidationError::new("balancer sampling interval is too low"))
299    }
300}
301
302fn validate_balancer_buffer_duration(value: &Duration) -> Result<(), ValidationError> {
303    if MIN_SURB_BUFFER_DURATION <= *value {
304        Ok(())
305    } else {
306        Err(ValidationError::new("minmum SURB buffer duration is too low"))
307    }
308}
309
310/// Global configuration of Sessions and the Session manager.
311#[derive(Clone, Copy, Debug, PartialEq, Eq, Validate, smart_default::SmartDefault)]
312#[cfg_attr(
313    feature = "serde",
314    derive(serde::Serialize, serde::Deserialize),
315    serde(deny_unknown_fields)
316)]
317pub struct SessionGlobalConfig {
318    /// Maximum time before an idle Session is closed.
319    ///
320    /// Defaults to 3 minutes.
321    #[validate(custom(function = "validate_session_idle_timeout"))]
322    #[default(default_session_idle_timeout())]
323    #[cfg_attr(
324        feature = "serde",
325        serde(default = "default_session_idle_timeout", with = "humantime_serde")
326    )]
327    pub idle_timeout: Duration,
328
329    /// The maximum number of outgoing or incoming Sessions that
330    /// are allowed by the Session manager.
331    ///
332    /// Minimum is 1, the maximum is given by the Session tag range.
333    /// Default is 2048.
334    #[default(default_session_max_sessions())]
335    #[cfg_attr(feature = "serde", serde(default = "default_session_max_sessions"))]
336    #[validate(range(min = 1))]
337    pub maximum_sessions: u32,
338
339    /// Maximum retries to attempt to establish the Session
340    /// Set 0 for no retries.
341    ///
342    /// Defaults to 3, maximum is 20.
343    #[validate(range(min = 0, max = 20))]
344    #[default(default_session_establish_max_retries())]
345    #[cfg_attr(feature = "serde", serde(default = "default_session_establish_max_retries"))]
346    pub establish_max_retries: u32,
347
348    /// Delay between Session establishment retries.
349    ///
350    /// Default is 2 seconds.
351    #[default(default_session_establish_retry_delay())]
352    #[cfg_attr(
353        feature = "serde",
354        serde(default = "default_session_establish_retry_delay", with = "humantime_serde")
355    )]
356    pub establish_retry_timeout: Duration,
357
358    /// Sampling interval for SURB balancer in milliseconds.
359    ///
360    /// Default is 500 milliseconds.
361    #[validate(custom(function = "validate_balancer_sampling"))]
362    #[default(default_session_balancer_sampling())]
363    #[cfg_attr(
364        feature = "serde",
365        serde(default = "default_session_balancer_sampling", with = "humantime_serde")
366    )]
367    pub balancer_sampling_interval: Duration,
368
369    /// Minimum runway of received SURBs in seconds.
370    ///
371    /// This applies to incoming Sessions on Exit nodes only and is the main indicator of how
372    /// the egress traffic will be shaped, unless the `NoRateControl` Session
373    /// capability is specified during initiation.
374    ///
375    /// Default is 5 seconds, minimum is 1 second.
376    #[validate(custom(function = "validate_balancer_buffer_duration"))]
377    #[default(default_session_balancer_buffer_duration())]
378    #[cfg_attr(
379        feature = "serde",
380        serde(default = "default_session_balancer_buffer_duration", with = "humantime_serde")
381    )]
382    pub balancer_minimum_surb_buffer_duration: Duration,
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388
389    #[test]
390    fn test_valid_domains_for_looks_like_a_domain() {
391        assert!(looks_like_domain("localhost"));
392        assert!(looks_like_domain("hoprnet.org"));
393        assert!(looks_like_domain("hub.hoprnet.org"));
394    }
395
396    #[test]
397    fn test_valid_domains_for_does_not_look_like_a_domain() {
398        assert!(!looks_like_domain(".org"));
399        assert!(!looks_like_domain("-hoprnet-.org"));
400    }
401
402    #[test]
403    fn test_valid_domains_should_be_reachable() {
404        assert!(!is_reachable_domain("google.com"));
405    }
406
407    #[test]
408    fn test_verify_valid_ip4_addresses() {
409        assert!(validate_ipv4_address("1.1.1.1").is_ok());
410        assert!(validate_ipv4_address("1.255.1.1").is_ok());
411        assert!(validate_ipv4_address("187.1.1.255").is_ok());
412        assert!(validate_ipv4_address("127.0.0.1").is_ok());
413    }
414
415    #[test]
416    fn test_verify_invalid_ip4_addresses() {
417        assert!(validate_ipv4_address("1.256.1.1").is_err());
418        assert!(validate_ipv4_address("-1.1.1.255").is_err());
419        assert!(validate_ipv4_address("127.0.0.256").is_err());
420        assert!(validate_ipv4_address("1").is_err());
421        assert!(validate_ipv4_address("1.1").is_err());
422        assert!(validate_ipv4_address("1.1.1").is_err());
423        assert!(validate_ipv4_address("1.1.1.1.1").is_err());
424    }
425
426    #[test]
427    fn test_verify_valid_dns_addresses() {
428        assert!(validate_dns_address("localhost").is_ok());
429        assert!(validate_dns_address("google.com").is_ok());
430        assert!(validate_dns_address("hub.hoprnet.org").is_ok());
431    }
432
433    #[test]
434    fn test_verify_invalid_dns_addresses() {
435        assert!(validate_dns_address("-hoprnet-.org").is_err());
436    }
437
438    #[test]
439    fn test_multiaddress_on_dappnode_default() {
440        temp_env::with_var("DAPPNODE", Some("true"), || {
441            assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
442        });
443    }
444
445    #[cfg(feature = "p2p-announce-quic")]
446    #[test]
447    fn test_multiaddress_on_non_dappnode_default() {
448        temp_env::with_vars([("DAPPNODE", Some("false")), ("HOPRD_NAT", Some("false"))], || {
449            assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
450        });
451    }
452
453    #[cfg(not(feature = "p2p-announce-quic"))]
454    #[test]
455    fn test_multiaddress_on_non_dappnode_default() {
456        assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
457    }
458
459    #[test]
460    fn test_multiaddress_on_non_dappnode_uses_nat() {
461        temp_env::with_var("HOPRD_NAT", Some("true"), || {
462            assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
463        });
464    }
465
466    #[cfg(feature = "p2p-announce-quic")]
467    #[test]
468    fn test_multiaddress_on_non_dappnode_not_uses_nat() {
469        temp_env::with_var("HOPRD_NAT", Some("false"), || {
470            assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
471        });
472    }
473
474    #[cfg(not(feature = "p2p-announce-quic"))]
475    #[test]
476    fn test_multiaddress_on_non_dappnode_not_uses_nat() {
477        temp_env::with_var("HOPRD_NAT", Some("false"), || {
478            assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
479        });
480    }
481
482    #[cfg(feature = "p2p-announce-quic")]
483    #[test]
484    fn test_multiaddress_on_dappnode_not_uses_nat() {
485        temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
486            assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
487        });
488    }
489
490    #[cfg(not(feature = "p2p-announce-quic"))]
491    #[test]
492    fn test_multiaddress_on_dappnode_not_uses_nat() {
493        temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
494            assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
495        });
496    }
497}