|
| 1 | +// Copyright 2023 Protocol Labs |
| 2 | +// |
| 3 | +// Permission is hereby granted, free of charge, to any person obtaining a |
| 4 | +// copy of this software and associated documentation files (the "Software"), |
| 5 | +// to deal in the Software without restriction, including without limitation |
| 6 | +// the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| 7 | +// and/or sell copies of the Software, and to permit persons to whom the |
| 8 | +// Software is furnished to do so, subject to the following conditions: |
| 9 | +// |
| 10 | +// The above copyright notice and this permission notice shall be included in |
| 11 | +// all copies or substantial portions of the Software. |
| 12 | +// |
| 13 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 14 | +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 15 | +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 16 | +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 17 | +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 18 | +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| 19 | +// DEALINGS IN THE SOFTWARE. |
| 20 | + |
| 21 | +use crate::{ |
| 22 | + multiaddr::{Multiaddr, Protocol}, |
| 23 | + transport::{ListenerId, TransportError, TransportEvent}, |
| 24 | +}; |
| 25 | +use log::debug; |
| 26 | +use std::{ |
| 27 | + pin::Pin, |
| 28 | + task::{Context, Poll}, |
| 29 | +}; |
| 30 | + |
| 31 | +/// Dropping all dial requests to non-global IP addresses. |
| 32 | +#[derive(Debug, Clone, Default)] |
| 33 | +pub struct Transport<T> { |
| 34 | + inner: T, |
| 35 | +} |
| 36 | + |
| 37 | +/// This module contains an implementation of the `is_global` IPv4 address space. |
| 38 | +/// |
| 39 | +/// Credit for this implementation goes to the Rust standard library team. |
| 40 | +/// |
| 41 | +/// Unstable tracking issue: [#27709](https://github.com/rust-lang/rust/issues/27709) |
| 42 | +mod ipv4_global { |
| 43 | + use std::net::Ipv4Addr; |
| 44 | + |
| 45 | + /// Returns [`true`] if this address is reserved by IANA for future use. [IETF RFC 1112] |
| 46 | + /// defines the block of reserved addresses as `240.0.0.0/4`. This range normally includes the |
| 47 | + /// broadcast address `255.255.255.255`, but this implementation explicitly excludes it, since |
| 48 | + /// it is obviously not reserved for future use. |
| 49 | + /// |
| 50 | + /// [IETF RFC 1112]: https://tools.ietf.org/html/rfc1112 |
| 51 | + /// |
| 52 | + /// # Warning |
| 53 | + /// |
| 54 | + /// As IANA assigns new addresses, this method will be |
| 55 | + /// updated. This may result in non-reserved addresses being |
| 56 | + /// treated as reserved in code that relies on an outdated version |
| 57 | + /// of this method. |
| 58 | + #[must_use] |
| 59 | + #[inline] |
| 60 | + const fn is_reserved(a: Ipv4Addr) -> bool { |
| 61 | + a.octets()[0] & 240 == 240 && !a.is_broadcast() |
| 62 | + } |
| 63 | + |
| 64 | + /// Returns [`true`] if this address part of the `198.18.0.0/15` range, which is reserved for |
| 65 | + /// network devices benchmarking. This range is defined in [IETF RFC 2544] as `192.18.0.0` |
| 66 | + /// through `198.19.255.255` but [errata 423] corrects it to `198.18.0.0/15`. |
| 67 | + /// |
| 68 | + /// [IETF RFC 2544]: https://tools.ietf.org/html/rfc2544 |
| 69 | + /// [errata 423]: https://www.rfc-editor.org/errata/eid423 |
| 70 | + #[must_use] |
| 71 | + #[inline] |
| 72 | + const fn is_benchmarking(a: Ipv4Addr) -> bool { |
| 73 | + a.octets()[0] == 198 && (a.octets()[1] & 0xfe) == 18 |
| 74 | + } |
| 75 | + |
| 76 | + /// Returns [`true`] if this address is part of the Shared Address Space defined in |
| 77 | + /// [IETF RFC 6598] (`100.64.0.0/10`). |
| 78 | + /// |
| 79 | + /// [IETF RFC 6598]: https://tools.ietf.org/html/rfc6598 |
| 80 | + #[must_use] |
| 81 | + #[inline] |
| 82 | + const fn is_shared(a: Ipv4Addr) -> bool { |
| 83 | + a.octets()[0] == 100 && (a.octets()[1] & 0b1100_0000 == 0b0100_0000) |
| 84 | + } |
| 85 | + |
| 86 | + /// Returns [`true`] if this is a private address. |
| 87 | + /// |
| 88 | + /// The private address ranges are defined in [IETF RFC 1918] and include: |
| 89 | + /// |
| 90 | + /// - `10.0.0.0/8` |
| 91 | + /// - `172.16.0.0/12` |
| 92 | + /// - `192.168.0.0/16` |
| 93 | + /// |
| 94 | + /// [IETF RFC 1918]: https://tools.ietf.org/html/rfc1918 |
| 95 | + #[must_use] |
| 96 | + #[inline] |
| 97 | + const fn is_private(a: Ipv4Addr) -> bool { |
| 98 | + match a.octets() { |
| 99 | + [10, ..] => true, |
| 100 | + [172, b, ..] if b >= 16 && b <= 31 => true, |
| 101 | + [192, 168, ..] => true, |
| 102 | + _ => false, |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + /// Returns [`true`] if the address appears to be globally reachable |
| 107 | + /// as specified by the [IANA IPv4 Special-Purpose Address Registry]. |
| 108 | + /// Whether or not an address is practically reachable will depend on your network configuration. |
| 109 | + /// |
| 110 | + /// Most IPv4 addresses are globally reachable; |
| 111 | + /// unless they are specifically defined as *not* globally reachable. |
| 112 | + /// |
| 113 | + /// Non-exhaustive list of notable addresses that are not globally reachable: |
| 114 | + /// |
| 115 | + /// - The [unspecified address] ([`is_unspecified`](Ipv4Addr::is_unspecified)) |
| 116 | + /// - Addresses reserved for private use ([`is_private`](Ipv4Addr::is_private)) |
| 117 | + /// - Addresses in the shared address space ([`is_shared`](Ipv4Addr::is_shared)) |
| 118 | + /// - Loopback addresses ([`is_loopback`](Ipv4Addr::is_loopback)) |
| 119 | + /// - Link-local addresses ([`is_link_local`](Ipv4Addr::is_link_local)) |
| 120 | + /// - Addresses reserved for documentation ([`is_documentation`](Ipv4Addr::is_documentation)) |
| 121 | + /// - Addresses reserved for benchmarking ([`is_benchmarking`](Ipv4Addr::is_benchmarking)) |
| 122 | + /// - Reserved addresses ([`is_reserved`](Ipv4Addr::is_reserved)) |
| 123 | + /// - The [broadcast address] ([`is_broadcast`](Ipv4Addr::is_broadcast)) |
| 124 | + /// |
| 125 | + /// For the complete overview of which addresses are globally reachable, see the table at the [IANA IPv4 Special-Purpose Address Registry]. |
| 126 | + /// |
| 127 | + /// [IANA IPv4 Special-Purpose Address Registry]: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml |
| 128 | + /// [unspecified address]: Ipv4Addr::UNSPECIFIED |
| 129 | + /// [broadcast address]: Ipv4Addr::BROADCAST |
| 130 | + #[must_use] |
| 131 | + #[inline] |
| 132 | + pub(crate) const fn is_global(a: Ipv4Addr) -> bool { |
| 133 | + !(a.octets()[0] == 0 // "This network" |
| 134 | + || is_private(a) |
| 135 | + || is_shared(a) |
| 136 | + || a.is_loopback() |
| 137 | + || a.is_link_local() |
| 138 | + // addresses reserved for future protocols (`192.0.0.0/24`) |
| 139 | + ||(a.octets()[0] == 192 && a.octets()[1] == 0 && a.octets()[2] == 0) |
| 140 | + || a.is_documentation() |
| 141 | + || is_benchmarking(a) |
| 142 | + || is_reserved(a) |
| 143 | + || a.is_broadcast()) |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +/// This module contains an implementation of the `is_global` IPv6 address space. |
| 148 | +/// |
| 149 | +/// Credit for this implementation goes to the Rust standard library team. |
| 150 | +/// |
| 151 | +/// Unstable tracking issue: [#27709](https://github.com/rust-lang/rust/issues/27709) |
| 152 | +mod ipv6_global { |
| 153 | + use std::net::Ipv6Addr; |
| 154 | + |
| 155 | + /// Returns `true` if the address is a unicast address with link-local scope, |
| 156 | + /// as defined in [RFC 4291]. |
| 157 | + /// |
| 158 | + /// A unicast address has link-local scope if it has the prefix `fe80::/10`, as per [RFC 4291 section 2.4]. |
| 159 | + /// Note that this encompasses more addresses than those defined in [RFC 4291 section 2.5.6], |
| 160 | + /// which describes "Link-Local IPv6 Unicast Addresses" as having the following stricter format: |
| 161 | + /// |
| 162 | + /// ```text |
| 163 | + /// | 10 bits | 54 bits | 64 bits | |
| 164 | + /// +----------+-------------------------+----------------------------+ |
| 165 | + /// |1111111010| 0 | interface ID | |
| 166 | + /// +----------+-------------------------+----------------------------+ |
| 167 | + /// ``` |
| 168 | + /// So while currently the only addresses with link-local scope an application will encounter are all in `fe80::/64`, |
| 169 | + /// this might change in the future with the publication of new standards. More addresses in `fe80::/10` could be allocated, |
| 170 | + /// and those addresses will have link-local scope. |
| 171 | + /// |
| 172 | + /// Also note that while [RFC 4291 section 2.5.3] mentions about the [loopback address] (`::1`) that "it is treated as having Link-Local scope", |
| 173 | + /// this does not mean that the loopback address actually has link-local scope and this method will return `false` on it. |
| 174 | + /// |
| 175 | + /// [RFC 4291]: https://tools.ietf.org/html/rfc4291 |
| 176 | + /// [RFC 4291 section 2.4]: https://tools.ietf.org/html/rfc4291#section-2.4 |
| 177 | + /// [RFC 4291 section 2.5.3]: https://tools.ietf.org/html/rfc4291#section-2.5.3 |
| 178 | + /// [RFC 4291 section 2.5.6]: https://tools.ietf.org/html/rfc4291#section-2.5.6 |
| 179 | + /// [loopback address]: Ipv6Addr::LOCALHOST |
| 180 | + #[must_use] |
| 181 | + #[inline] |
| 182 | + const fn is_unicast_link_local(a: Ipv6Addr) -> bool { |
| 183 | + (a.segments()[0] & 0xffc0) == 0xfe80 |
| 184 | + } |
| 185 | + |
| 186 | + /// Returns [`true`] if this is a unique local address (`fc00::/7`). |
| 187 | + /// |
| 188 | + /// This property is defined in [IETF RFC 4193]. |
| 189 | + /// |
| 190 | + /// [IETF RFC 4193]: https://tools.ietf.org/html/rfc4193 |
| 191 | + #[must_use] |
| 192 | + #[inline] |
| 193 | + const fn is_unique_local(a: Ipv6Addr) -> bool { |
| 194 | + (a.segments()[0] & 0xfe00) == 0xfc00 |
| 195 | + } |
| 196 | + |
| 197 | + /// Returns [`true`] if this is an address reserved for documentation |
| 198 | + /// (`2001:db8::/32`). |
| 199 | + /// |
| 200 | + /// This property is defined in [IETF RFC 3849]. |
| 201 | + /// |
| 202 | + /// [IETF RFC 3849]: https://tools.ietf.org/html/rfc3849 |
| 203 | + #[must_use] |
| 204 | + #[inline] |
| 205 | + const fn is_documentation(a: Ipv6Addr) -> bool { |
| 206 | + (a.segments()[0] == 0x2001) && (a.segments()[1] == 0xdb8) |
| 207 | + } |
| 208 | + |
| 209 | + /// Returns [`true`] if the address appears to be globally reachable |
| 210 | + /// as specified by the [IANA IPv6 Special-Purpose Address Registry]. |
| 211 | + /// Whether or not an address is practically reachable will depend on your network configuration. |
| 212 | + /// |
| 213 | + /// Most IPv6 addresses are globally reachable; |
| 214 | + /// unless they are specifically defined as *not* globally reachable. |
| 215 | + /// |
| 216 | + /// Non-exhaustive list of notable addresses that are not globally reachable: |
| 217 | + /// - The [unspecified address] ([`is_unspecified`](Ipv6Addr::is_unspecified)) |
| 218 | + /// - The [loopback address] ([`is_loopback`](Ipv6Addr::is_loopback)) |
| 219 | + /// - IPv4-mapped addresses |
| 220 | + /// - Addresses reserved for benchmarking |
| 221 | + /// - Addresses reserved for documentation ([`is_documentation`](Ipv6Addr::is_documentation)) |
| 222 | + /// - Unique local addresses ([`is_unique_local`](Ipv6Addr::is_unique_local)) |
| 223 | + /// - Unicast addresses with link-local scope ([`is_unicast_link_local`](Ipv6Addr::is_unicast_link_local)) |
| 224 | + /// |
| 225 | + /// For the complete overview of which addresses are globally reachable, see the table at the [IANA IPv6 Special-Purpose Address Registry]. |
| 226 | + /// |
| 227 | + /// Note that an address having global scope is not the same as being globally reachable, |
| 228 | + /// and there is no direct relation between the two concepts: There exist addresses with global scope |
| 229 | + /// that are not globally reachable (for example unique local addresses), |
| 230 | + /// and addresses that are globally reachable without having global scope |
| 231 | + /// (multicast addresses with non-global scope). |
| 232 | + /// |
| 233 | + /// [IANA IPv6 Special-Purpose Address Registry]: https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml |
| 234 | + /// [unspecified address]: Ipv6Addr::UNSPECIFIED |
| 235 | + /// [loopback address]: Ipv6Addr::LOCALHOST |
| 236 | + #[must_use] |
| 237 | + #[inline] |
| 238 | + pub(crate) const fn is_global(a: Ipv6Addr) -> bool { |
| 239 | + !(a.is_unspecified() |
| 240 | + || a.is_loopback() |
| 241 | + // IPv4-mapped Address (`::ffff:0:0/96`) |
| 242 | + || matches!(a.segments(), [0, 0, 0, 0, 0, 0xffff, _, _]) |
| 243 | + // IPv4-IPv6 Translat. (`64:ff9b:1::/48`) |
| 244 | + || matches!(a.segments(), [0x64, 0xff9b, 1, _, _, _, _, _]) |
| 245 | + // Discard-Only Address Block (`100::/64`) |
| 246 | + || matches!(a.segments(), [0x100, 0, 0, 0, _, _, _, _]) |
| 247 | + // IETF Protocol Assignments (`2001::/23`) |
| 248 | + || (matches!(a.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200) |
| 249 | + && !( |
| 250 | + // Port Control Protocol Anycast (`2001:1::1`) |
| 251 | + u128::from_be_bytes(a.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001 |
| 252 | + // Traversal Using Relays around NAT Anycast (`2001:1::2`) |
| 253 | + || u128::from_be_bytes(a.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002 |
| 254 | + // AMT (`2001:3::/32`) |
| 255 | + || matches!(a.segments(), [0x2001, 3, _, _, _, _, _, _]) |
| 256 | + // AS112-v6 (`2001:4:112::/48`) |
| 257 | + || matches!(a.segments(), [0x2001, 4, 0x112, _, _, _, _, _]) |
| 258 | + // ORCHIDv2 (`2001:20::/28`) |
| 259 | + || matches!(a.segments(), [0x2001, b, _, _, _, _, _, _] if b >= 0x20 && b <= 0x2F) |
| 260 | + )) |
| 261 | + || is_documentation(a) |
| 262 | + || is_unique_local(a) |
| 263 | + || is_unicast_link_local(a)) |
| 264 | + } |
| 265 | +} |
| 266 | + |
| 267 | +impl<T> Transport<T> { |
| 268 | + pub fn new(transport: T) -> Self { |
| 269 | + Transport { inner: transport } |
| 270 | + } |
| 271 | +} |
| 272 | + |
| 273 | +impl<T: crate::Transport + Unpin> crate::Transport for Transport<T> { |
| 274 | + type Output = <T as crate::Transport>::Output; |
| 275 | + type Error = <T as crate::Transport>::Error; |
| 276 | + type ListenerUpgrade = <T as crate::Transport>::ListenerUpgrade; |
| 277 | + type Dial = <T as crate::Transport>::Dial; |
| 278 | + |
| 279 | + fn listen_on(&mut self, addr: Multiaddr) -> Result<ListenerId, TransportError<Self::Error>> { |
| 280 | + self.inner.listen_on(addr) |
| 281 | + } |
| 282 | + |
| 283 | + fn remove_listener(&mut self, id: ListenerId) -> bool { |
| 284 | + self.inner.remove_listener(id) |
| 285 | + } |
| 286 | + |
| 287 | + fn dial(&mut self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> { |
| 288 | + match addr.iter().next() { |
| 289 | + Some(Protocol::Ip4(a)) => { |
| 290 | + if !ipv4_global::is_global(a) { |
| 291 | + debug!("Not dialing non global IP address {:?}.", a); |
| 292 | + return Err(TransportError::MultiaddrNotSupported(addr)); |
| 293 | + } |
| 294 | + self.inner.dial(addr) |
| 295 | + } |
| 296 | + Some(Protocol::Ip6(a)) => { |
| 297 | + if !ipv6_global::is_global(a) { |
| 298 | + debug!("Not dialing non global IP address {:?}.", a); |
| 299 | + return Err(TransportError::MultiaddrNotSupported(addr)); |
| 300 | + } |
| 301 | + self.inner.dial(addr) |
| 302 | + } |
| 303 | + _ => { |
| 304 | + debug!("Not dialing unsupported Multiaddress {:?}.", addr); |
| 305 | + Err(TransportError::MultiaddrNotSupported(addr)) |
| 306 | + } |
| 307 | + } |
| 308 | + } |
| 309 | + |
| 310 | + fn dial_as_listener( |
| 311 | + &mut self, |
| 312 | + addr: Multiaddr, |
| 313 | + ) -> Result<Self::Dial, TransportError<Self::Error>> { |
| 314 | + match addr.iter().next() { |
| 315 | + Some(Protocol::Ip4(a)) => { |
| 316 | + if !ipv4_global::is_global(a) { |
| 317 | + debug!("Not dialing non global IP address {:?}.", a); |
| 318 | + return Err(TransportError::MultiaddrNotSupported(addr)); |
| 319 | + } |
| 320 | + self.inner.dial_as_listener(addr) |
| 321 | + } |
| 322 | + Some(Protocol::Ip6(a)) => { |
| 323 | + if !ipv6_global::is_global(a) { |
| 324 | + debug!("Not dialing non global IP address {:?}.", a); |
| 325 | + return Err(TransportError::MultiaddrNotSupported(addr)); |
| 326 | + } |
| 327 | + self.inner.dial_as_listener(addr) |
| 328 | + } |
| 329 | + _ => { |
| 330 | + debug!("Not dialing unsupported Multiaddress {:?}.", addr); |
| 331 | + Err(TransportError::MultiaddrNotSupported(addr)) |
| 332 | + } |
| 333 | + } |
| 334 | + } |
| 335 | + |
| 336 | + fn address_translation(&self, listen: &Multiaddr, observed: &Multiaddr) -> Option<Multiaddr> { |
| 337 | + self.inner.address_translation(listen, observed) |
| 338 | + } |
| 339 | + |
| 340 | + fn poll( |
| 341 | + mut self: Pin<&mut Self>, |
| 342 | + cx: &mut Context<'_>, |
| 343 | + ) -> Poll<TransportEvent<Self::ListenerUpgrade, Self::Error>> { |
| 344 | + Pin::new(&mut self.inner).poll(cx) |
| 345 | + } |
| 346 | +} |
0 commit comments