Skip to main content

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