Browse Source

ipv6: address scope and `is_global_unicast`

The scope of an address is used when selecting the source IPv6 address
based on the destination address. As the scope is then also used for
unicast address, I made the scope only public in the crate instead of
outside the crate. Not sure if this should be public or not.

This commit also adds the `is_global_unicast` query function for IPv6
addresses.
Thibaut Vandervelden 1 year ago
parent
commit
45f9838ad9
2 changed files with 76 additions and 0 deletions
  1. 74 0
      src/wire/ipv6.rs
  2. 2 0
      src/wire/mod.rs

+ 74 - 0
src/wire/ipv6.rs

@@ -25,6 +25,41 @@ pub const ADDR_SIZE: usize = 16;
 /// [RFC 8200 § 2]: https://www.rfc-editor.org/rfc/rfc4291#section-2
 pub const IPV4_MAPPED_PREFIX_SIZE: usize = ADDR_SIZE - 4; // 4 == ipv4::ADDR_SIZE , cannot DRY here because of dependency on a IPv4 module which is behind the feature
 
+/// The [scope] of an address.
+///
+/// [scope]: https://www.rfc-editor.org/rfc/rfc4291#section-2.7
+#[repr(u8)]
+pub(crate) enum Scope {
+    /// Interface Local scope
+    InterfaceLocal = 0x1,
+    /// Link local scope
+    LinkLocal = 0x2,
+    /// Administratively configured
+    AdminLocal = 0x4,
+    /// Single site scope
+    SiteLocal = 0x5,
+    /// Organization scope
+    OrganizationLocal = 0x8,
+    /// Global scope
+    Global = 0xE,
+    /// Unknown scope
+    Unknown = 0xFF,
+}
+
+impl From<u8> for Scope {
+    fn from(value: u8) -> Self {
+        match value {
+            0x1 => Self::InterfaceLocal,
+            0x2 => Self::LinkLocal,
+            0x4 => Self::AdminLocal,
+            0x5 => Self::SiteLocal,
+            0x8 => Self::OrganizationLocal,
+            0xE => Self::Global,
+            _ => Self::Unknown,
+        }
+    }
+}
+
 /// A sixteen-octet IPv6 address.
 #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
 pub struct Address(pub [u8; ADDR_SIZE]);
@@ -143,6 +178,13 @@ impl Address {
         !(self.is_multicast() || self.is_unspecified())
     }
 
+    /// Query whether the IPv6 address is a [global unicast address].
+    ///
+    /// [global unicast address]: https://datatracker.ietf.org/doc/html/rfc3587
+    pub const fn is_global_unicast(&self) -> bool {
+        (self.0[0] >> 5) == 0b001
+    }
+
     /// Query whether the IPv6 address is a [multicast address].
     ///
     /// [multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7
@@ -228,6 +270,22 @@ impl Address {
         ])
     }
 
+    /// Return the scope of the address.
+    pub(crate) fn scope(&self) -> Scope {
+        if self.is_multicast() {
+            return Scope::from(self.as_bytes()[1] & 0b1111);
+        }
+
+        if self.is_link_local() {
+            Scope::LinkLocal
+        } else if self.is_unique_local() || self.is_global_unicast() {
+            // ULA are considered global scope
+            Scope::Global
+        } else {
+            Scope::Unknown
+        }
+    }
+
     /// Convert to an `IpAddress`.
     ///
     /// Same as `.into()`, but works in `const`.
@@ -840,6 +898,7 @@ mod test {
 
     const LINK_LOCAL_ADDR: Address = Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
     const UNIQUE_LOCAL_ADDR: Address = Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1);
+    const GLOBAL_UNICAST_ADDR: Address = Address::new(0x2001, 0xdb8, 0x3, 0, 0, 0, 0, 1);
 
     #[test]
     fn test_basic_multicast() {
@@ -848,11 +907,13 @@ mod test {
         assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_link_local());
         assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_loopback());
         assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_unique_local());
+        assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_global_unicast());
         assert!(!Address::LINK_LOCAL_ALL_NODES.is_unspecified());
         assert!(Address::LINK_LOCAL_ALL_NODES.is_multicast());
         assert!(!Address::LINK_LOCAL_ALL_NODES.is_link_local());
         assert!(!Address::LINK_LOCAL_ALL_NODES.is_loopback());
         assert!(!Address::LINK_LOCAL_ALL_NODES.is_unique_local());
+        assert!(!Address::LINK_LOCAL_ALL_NODES.is_global_unicast());
     }
 
     #[test]
@@ -862,6 +923,7 @@ mod test {
         assert!(LINK_LOCAL_ADDR.is_link_local());
         assert!(!LINK_LOCAL_ADDR.is_loopback());
         assert!(!LINK_LOCAL_ADDR.is_unique_local());
+        assert!(!LINK_LOCAL_ADDR.is_global_unicast());
     }
 
     #[test]
@@ -871,6 +933,7 @@ mod test {
         assert!(!Address::LOOPBACK.is_link_local());
         assert!(Address::LOOPBACK.is_loopback());
         assert!(!Address::LOOPBACK.is_unique_local());
+        assert!(!Address::LOOPBACK.is_global_unicast());
     }
 
     #[test]
@@ -880,6 +943,17 @@ mod test {
         assert!(!UNIQUE_LOCAL_ADDR.is_link_local());
         assert!(!UNIQUE_LOCAL_ADDR.is_loopback());
         assert!(UNIQUE_LOCAL_ADDR.is_unique_local());
+        assert!(!UNIQUE_LOCAL_ADDR.is_global_unicast());
+    }
+
+    #[test]
+    fn test_global_unicast() {
+        assert!(!GLOBAL_UNICAST_ADDR.is_unspecified());
+        assert!(!GLOBAL_UNICAST_ADDR.is_multicast());
+        assert!(!GLOBAL_UNICAST_ADDR.is_link_local());
+        assert!(!GLOBAL_UNICAST_ADDR.is_loopback());
+        assert!(!GLOBAL_UNICAST_ADDR.is_unique_local());
+        assert!(GLOBAL_UNICAST_ADDR.is_global_unicast());
     }
 
     #[test]

+ 2 - 0
src/wire/mod.rs

@@ -190,6 +190,8 @@ pub use self::ipv4::{
     Repr as Ipv4Repr, HEADER_LEN as IPV4_HEADER_LEN, MIN_MTU as IPV4_MIN_MTU,
 };
 
+#[cfg(feature = "proto-ipv6")]
+pub(crate) use self::ipv6::Scope as Ipv6AddressScope;
 #[cfg(feature = "proto-ipv6")]
 pub use self::ipv6::{
     Address as Ipv6Address, Cidr as Ipv6Cidr, Packet as Ipv6Packet, Repr as Ipv6Repr,