hopr_network_types/
addr.rs

1use std::{net::IpAddr, sync::atomic::AtomicBool};
2
3use lazy_static::lazy_static;
4use multiaddr::Multiaddr;
5
6lazy_static! {
7    pub(crate) static ref ALLOW_PRIVATE_ADDRESSES: AtomicBool =
8        std::env::var("HOPR_INTERNAL_TRANSPORT_ACCEPT_PRIVATE_NETWORK_IP_ADDRESSES")
9            .map(|v| v.to_lowercase() == "true")
10            .unwrap_or_default()
11            .into();
12}
13
14/// Check if an IP address is a public/routable one.
15///
16/// If the `HOPR_INTERNAL_TRANSPORT_ACCEPT_PRIVATE_NETWORK_IP_ADDRESSES` env variable
17/// is set to `true`, this function always returns true to allow private addresses for
18/// local development and testing.
19pub fn is_public(ip_addr: IpAddr) -> bool {
20    match ip_addr {
21        IpAddr::V4(ip) => !ip.is_unspecified() && !ip.is_private() && !ip.is_loopback() && !ip.is_link_local(),
22        IpAddr::V6(ip) => {
23            !ip.is_unspecified() && !ip.is_loopback() && !ip.is_unicast_link_local() && !ip.is_unique_local()
24        }
25    }
26}
27
28#[inline]
29fn is_allowed_for_transport(ip_addr: IpAddr) -> bool {
30    ALLOW_PRIVATE_ADDRESSES.load(std::sync::atomic::Ordering::SeqCst) || is_public(ip_addr)
31}
32
33/// Check if a multiaddress contains a public/routable IP address.
34///
35/// If the `HOPR_INTERNAL_TRANSPORT_ACCEPT_PRIVATE_NETWORK_IP_ADDRESSES` env variable
36/// is set to `true`, this function always returns true to allow private addresses for
37/// local development and testing.
38pub fn is_public_address(addr: &Multiaddr) -> bool {
39    addr.iter().all(|protocol| match protocol {
40        multiaddr::Protocol::Ip4(ip) => is_allowed_for_transport(ip.into()),
41        multiaddr::Protocol::Ip6(ip) => is_allowed_for_transport(ip.into()),
42        _ => true,
43    })
44}
45
46#[cfg(test)]
47mod tests {
48    use std::str::FromStr;
49
50    use super::*;
51
52    #[inline]
53    fn override_allow_private_addresses(value: bool) {
54        ALLOW_PRIVATE_ADDRESSES.store(value, std::sync::atomic::Ordering::SeqCst);
55    }
56
57    #[inline]
58    fn get_allow_private_addresses() -> bool {
59        ALLOW_PRIVATE_ADDRESSES.load(std::sync::atomic::Ordering::SeqCst)
60    }
61
62    #[test]
63    #[serial_test::serial] // must be serial to avoid random race conditions on atomic bool on parallel execution
64    fn test_is_public_address_ipv4() -> anyhow::Result<()> {
65        override_allow_private_addresses(false);
66        assert!(!get_allow_private_addresses());
67
68        // IPv4 public addresses - should return true
69        assert!(is_public_address(&Multiaddr::from_str("/ip4/8.8.8.8")?));
70        assert!(is_public_address(&Multiaddr::from_str("/ip4/1.1.1.1")?));
71        assert!(is_public_address(&Multiaddr::from_str("/ip4/104.16.0.0")?));
72
73        // IPv4 private addresses - should return false
74        assert!(!is_public_address(&Multiaddr::from_str("/ip4/192.168.0.1")?));
75        assert!(!is_public_address(&Multiaddr::from_str("/ip4/192.168.1.254")?));
76        assert!(!is_public_address(&Multiaddr::from_str("/ip4/10.0.0.1")?));
77        assert!(!is_public_address(&Multiaddr::from_str("/ip4/10.1.0.1")?));
78        assert!(!is_public_address(&Multiaddr::from_str("/ip4/10.255.255.255")?));
79        assert!(!is_public_address(&Multiaddr::from_str("/ip4/172.16.0.0")?));
80        assert!(!is_public_address(&Multiaddr::from_str("/ip4/172.31.255.255")?));
81
82        // IPv4 loopback - should return false
83        assert!(!is_public_address(&Multiaddr::from_str("/ip4/127.0.0.1")?));
84        assert!(!is_public_address(&Multiaddr::from_str("/ip4/127.255.255.255")?));
85
86        // IPv4 link-local - should return false
87        assert!(!is_public_address(&Multiaddr::from_str("/ip4/169.254.1.1")?));
88        assert!(!is_public_address(&Multiaddr::from_str("/ip4/169.254.254.254")?));
89
90        // IPv4 unspecified - should return false
91        assert!(!is_public_address(&Multiaddr::from_str("/ip4/0.0.0.0")?));
92
93        Ok(())
94    }
95
96    #[test]
97    #[serial_test::serial] // must be serial to avoid random race conditions on atomic bool on parallel execution
98    fn test_is_public_address_ipv6() -> anyhow::Result<()> {
99        override_allow_private_addresses(false);
100        assert!(!get_allow_private_addresses());
101
102        // IPv6 public addresses - should return true
103        assert!(is_public_address(&Multiaddr::from_str("/ip6/2001:4860:4860::8888")?));
104        assert!(is_public_address(&Multiaddr::from_str("/ip6/2606:4700:4700::1111")?));
105        assert!(is_public_address(&Multiaddr::from_str(
106            "/ip6/2a00:1450:4001:830::200e"
107        )?));
108
109        // IPv6 loopback - should return false
110        assert!(!is_public_address(&Multiaddr::from_str("/ip6/::1")?));
111
112        // IPv6 unique-local - should return false
113        assert!(!is_public_address(&Multiaddr::from_str("/ip6/fc00::1")?));
114        assert!(!is_public_address(&Multiaddr::from_str("/ip6/fd00::1")?));
115        assert!(!is_public_address(&Multiaddr::from_str(
116            "/ip6/fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
117        )?));
118
119        // IPv6 link-local - should return false
120        assert!(!is_public_address(&Multiaddr::from_str("/ip6/fe80::1")?));
121        assert!(!is_public_address(&Multiaddr::from_str(
122            "/ip6/fe80::dead:beef:cafe:babe"
123        )?));
124        assert!(!is_public_address(&Multiaddr::from_str(
125            "/ip6/febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
126        )?));
127
128        // IPv6 unspecified - should return false
129        assert!(!is_public_address(&Multiaddr::from_str("/ip6/::")?));
130
131        Ok(())
132    }
133
134    #[test]
135    #[serial_test::serial] // must be serial to avoid random race conditions on atomic bool on parallel execution
136    fn test_is_public_address_with_protocols() -> anyhow::Result<()> {
137        override_allow_private_addresses(false);
138        assert!(!get_allow_private_addresses());
139
140        // Public addresses with additional protocols - should return true
141        assert!(is_public_address(&Multiaddr::from_str("/ip4/8.8.8.8/tcp/4001")?));
142        assert!(is_public_address(&Multiaddr::from_str("/ip4/1.1.1.1/udp/30303")?));
143        assert!(is_public_address(&Multiaddr::from_str(
144            "/ip4/8.8.8.8/tcp/4001/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp"
145        )?));
146        assert!(is_public_address(&Multiaddr::from_str(
147            "/ip6/2001:4860:4860::8888/tcp/4001"
148        )?));
149        assert!(is_public_address(&Multiaddr::from_str(
150            "/ip6/2001:4860:4860::8888/udp/30303"
151        )?));
152        assert!(is_public_address(&Multiaddr::from_str(
153            "/ip6/2606:4700:4700::1111/tcp/443/wss"
154        )?));
155
156        // Private/special addresses with additional protocols - should return false
157        assert!(!is_public_address(&Multiaddr::from_str("/ip4/192.168.0.1/tcp/4001")?));
158        assert!(!is_public_address(&Multiaddr::from_str("/ip4/127.0.0.1/tcp/8080")?));
159        assert!(!is_public_address(&Multiaddr::from_str("/ip4/10.0.0.1/tcp/8080")?));
160        assert!(!is_public_address(&Multiaddr::from_str("/ip4/10.1.0.1/tcp/8080")?));
161        assert!(!is_public_address(&Multiaddr::from_str("/ip4/169.254.1.1/udp/5060")?));
162        assert!(!is_public_address(&Multiaddr::from_str("/ip4/0.0.0.0/tcp/3000")?));
163        assert!(!is_public_address(&Multiaddr::from_str("/ip6/::1/tcp/4001")?));
164        assert!(!is_public_address(&Multiaddr::from_str("/ip6/fe80::1/udp/30303")?));
165        assert!(!is_public_address(&Multiaddr::from_str("/ip6/fc00::1/tcp/443")?));
166        assert!(!is_public_address(&Multiaddr::from_str("/ip6/::/tcp/8080")?));
167
168        Ok(())
169    }
170
171    #[test]
172    #[serial_test::serial] // must be serial to avoid random race conditions on atomic bool on parallel execution
173    fn test_is_public_address_mixed_protocols() -> anyhow::Result<()> {
174        override_allow_private_addresses(false);
175        assert!(!get_allow_private_addresses());
176
177        // Test with DNS and other protocols (no IP) - should return true (non-IP protocols default to true)
178        assert!(is_public_address(&Multiaddr::from_str("/dns/example.com")?));
179        assert!(is_public_address(&Multiaddr::from_str("/dns4/example.com/tcp/443")?));
180        assert!(is_public_address(&Multiaddr::from_str("/dns6/example.com/tcp/443")?));
181
182        Ok(())
183    }
184
185    #[test]
186    #[serial_test::serial] // must be serial to avoid random race conditions on atomic bool on parallel execution
187    fn test_local_testing_allows_all_addresses() -> anyhow::Result<()> {
188        override_allow_private_addresses(true);
189        assert!(get_allow_private_addresses());
190
191        // When local-testing feature is enabled, all addresses should be considered "public"
192        assert!(is_public_address(&Multiaddr::from_str("/ip4/127.0.0.1")?));
193        assert!(is_public_address(&Multiaddr::from_str("/ip4/192.168.1.1")?));
194        assert!(is_public_address(&Multiaddr::from_str("/ip4/10.0.0.1")?));
195        assert!(is_public_address(&Multiaddr::from_str("/ip4/172.16.0.1")?));
196        assert!(is_public_address(&Multiaddr::from_str("/ip6/::1")?));
197        assert!(is_public_address(&Multiaddr::from_str("/ip6/fe80::1")?));
198
199        Ok(())
200    }
201}