Explorar o código

Merge pull request #1012 from bergzand/pr/join_solicited_node

 feat: Automatically join solicited-node multicast addresses
Dario Nieuwenhuis hai 6 meses
pai
achega
4dcee75155

+ 6 - 1
src/iface/interface/mod.rs

@@ -361,7 +361,12 @@ impl Interface {
     pub fn update_ip_addrs<F: FnOnce(&mut Vec<IpCidr, IFACE_MAX_ADDR_COUNT>)>(&mut self, f: F) {
     pub fn update_ip_addrs<F: FnOnce(&mut Vec<IpCidr, IFACE_MAX_ADDR_COUNT>)>(&mut self, f: F) {
         f(&mut self.inner.ip_addrs);
         f(&mut self.inner.ip_addrs);
         InterfaceInner::flush_neighbor_cache(&mut self.inner);
         InterfaceInner::flush_neighbor_cache(&mut self.inner);
-        InterfaceInner::check_ip_addrs(&self.inner.ip_addrs)
+        InterfaceInner::check_ip_addrs(&self.inner.ip_addrs);
+
+        #[cfg(all(feature = "proto-ipv6", feature = "multicast"))]
+        if self.inner.caps.medium == Medium::Ethernet {
+            self.update_solicited_node_groups();
+        }
     }
     }
 
 
     /// Check whether the interface has the given IP address assigned.
     /// Check whether the interface has the given IP address assigned.

+ 25 - 2
src/iface/interface/multicast.rs

@@ -1,10 +1,10 @@
 use core::result::Result;
 use core::result::Result;
-use heapless::LinearMap;
+use heapless::{LinearMap, Vec};
 
 
 #[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
 #[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
 use super::{check, IpPayload, Packet};
 use super::{check, IpPayload, Packet};
 use super::{Interface, InterfaceInner};
 use super::{Interface, InterfaceInner};
-use crate::config::IFACE_MAX_MULTICAST_GROUP_COUNT;
+use crate::config::{IFACE_MAX_ADDR_COUNT, IFACE_MAX_MULTICAST_GROUP_COUNT};
 use crate::phy::{Device, PacketMeta};
 use crate::phy::{Device, PacketMeta};
 use crate::wire::*;
 use crate::wire::*;
 
 
@@ -156,6 +156,29 @@ impl Interface {
         self.inner.has_multicast_group(addr)
         self.inner.has_multicast_group(addr)
     }
     }
 
 
+    #[cfg(feature = "proto-ipv6")]
+    pub(super) fn update_solicited_node_groups(&mut self) {
+        // Remove old solicited-node multicast addresses
+        let removals: Vec<_, IFACE_MAX_MULTICAST_GROUP_COUNT> = self
+            .inner
+            .multicast
+            .groups
+            .keys()
+            .cloned()
+            .filter(|a| matches!(a, IpAddress::Ipv6(a) if a.is_solicited_node_multicast() && !self.inner.has_solicited_node(*a)))
+            .collect();
+        for removal in removals {
+            let _ = self.leave_multicast_group(removal);
+        }
+
+        let cidrs: Vec<IpCidr, IFACE_MAX_ADDR_COUNT> = Vec::from_slice(self.ip_addrs()).unwrap();
+        for cidr in cidrs {
+            if let IpCidr::Ipv6(cidr) = cidr {
+                let _ = self.join_multicast_group(cidr.address().solicited_node());
+            }
+        }
+    }
+
     /// Do multicast egress.
     /// Do multicast egress.
     ///
     ///
     /// - Send join/leave packets according to the multicast group state.
     /// - Send join/leave packets according to the multicast group state.

+ 50 - 9
src/iface/interface/tests/ipv6.rs

@@ -1296,6 +1296,10 @@ fn test_join_ipv6_multicast_group(#[case] medium: Medium) {
 
 
     let timestamp = Instant::from_millis(0);
     let timestamp = Instant::from_millis(0);
 
 
+    // Drain the unsolicited node multicast report from the device
+    iface.poll(timestamp, &mut device, &mut sockets);
+    let _ = recv_icmpv6(&mut device, timestamp);
+
     for &group in &groups {
     for &group in &groups {
         iface.join_multicast_group(group).unwrap();
         iface.join_multicast_group(group).unwrap();
         assert!(iface.has_multicast_group(group));
         assert!(iface.has_multicast_group(group));
@@ -1372,10 +1376,12 @@ fn test_join_ipv6_multicast_group(#[case] medium: Medium) {
             }
             }
         );
         );
 
 
-        iface.leave_multicast_group(group_addr).unwrap();
-        assert!(!iface.has_multicast_group(group_addr));
-        iface.poll(timestamp, &mut device, &mut sockets);
-        assert!(!iface.has_multicast_group(group_addr));
+        if !group_addr.is_solicited_node_multicast() {
+            iface.leave_multicast_group(group_addr).unwrap();
+            assert!(!iface.has_multicast_group(group_addr));
+            iface.poll(timestamp, &mut device, &mut sockets);
+            assert!(!iface.has_multicast_group(group_addr));
+        }
     }
     }
 }
 }
 
 
@@ -1414,15 +1420,12 @@ fn test_handle_valid_multicast_query(#[case] medium: Medium) {
 
 
     let mut eth_bytes = vec![0u8; 86];
     let mut eth_bytes = vec![0u8; 86];
 
 
-    let local_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 101);
+    let local_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
     let remote_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 100);
     let remote_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 100);
     let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
     let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
     let query_ip_addr = Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 0x1234);
     let query_ip_addr = Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 0x1234);
 
 
     iface.join_multicast_group(query_ip_addr).unwrap();
     iface.join_multicast_group(query_ip_addr).unwrap();
-    iface
-        .join_multicast_group(local_ip_addr.solicited_node())
-        .unwrap();
 
 
     iface.poll(timestamp, &mut device, &mut sockets);
     iface.poll(timestamp, &mut device, &mut sockets);
     // flush multicast reports from the join_multicast_group calls
     // flush multicast reports from the join_multicast_group calls
@@ -1433,7 +1436,7 @@ fn test_handle_valid_multicast_query(#[case] medium: Medium) {
         (
         (
             Ipv6Address::UNSPECIFIED,
             Ipv6Address::UNSPECIFIED,
             IPV6_LINK_LOCAL_ALL_NODES,
             IPV6_LINK_LOCAL_ALL_NODES,
-            vec![query_ip_addr, local_ip_addr.solicited_node()],
+            vec![local_ip_addr.solicited_node(), query_ip_addr],
         ),
         ),
         // Address specific query, expect only the queried address back
         // Address specific query, expect only the queried address back
         (query_ip_addr, query_ip_addr, vec![query_ip_addr]),
         (query_ip_addr, query_ip_addr, vec![query_ip_addr]),
@@ -1562,3 +1565,41 @@ fn test_handle_valid_multicast_query(#[case] medium: Medium) {
         assert_eq!(record_reprs, expected_records);
         assert_eq!(record_reprs, expected_records);
     }
     }
 }
 }
+
+#[rstest]
+#[case(Medium::Ethernet)]
+#[cfg(all(feature = "multicast", feature = "medium-ethernet"))]
+fn test_solicited_node_multicast_autojoin(#[case] medium: Medium) {
+    let (mut iface, _, _) = setup(medium);
+
+    let addr1 = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
+    let addr2 = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2);
+
+    iface.update_ip_addrs(|ip_addrs| {
+        ip_addrs.clear();
+        ip_addrs.push(IpCidr::new(addr1.into(), 64)).unwrap();
+    });
+    assert!(iface.has_multicast_group(addr1.solicited_node()));
+    assert!(!iface.has_multicast_group(addr2.solicited_node()));
+
+    iface.update_ip_addrs(|ip_addrs| {
+        ip_addrs.clear();
+        ip_addrs.push(IpCidr::new(addr2.into(), 64)).unwrap();
+    });
+    assert!(!iface.has_multicast_group(addr1.solicited_node()));
+    assert!(iface.has_multicast_group(addr2.solicited_node()));
+
+    iface.update_ip_addrs(|ip_addrs| {
+        ip_addrs.clear();
+        ip_addrs.push(IpCidr::new(addr1.into(), 64)).unwrap();
+        ip_addrs.push(IpCidr::new(addr2.into(), 64)).unwrap();
+    });
+    assert!(iface.has_multicast_group(addr1.solicited_node()));
+    assert!(iface.has_multicast_group(addr2.solicited_node()));
+
+    iface.update_ip_addrs(|ip_addrs| {
+        ip_addrs.clear();
+    });
+    assert!(!iface.has_multicast_group(addr1.solicited_node()));
+    assert!(!iface.has_multicast_group(addr2.solicited_node()));
+}

+ 31 - 0
src/wire/ipv6.rs

@@ -125,6 +125,11 @@ pub(crate) trait AddressExt {
     /// `x_` prefix is to avoid a collision with the still-unstable method in `core::ip`.
     /// `x_` prefix is to avoid a collision with the still-unstable method in `core::ip`.
     fn x_multicast_scope(&self) -> MulticastScope;
     fn x_multicast_scope(&self) -> MulticastScope;
 
 
+    /// Query whether the IPv6 address is a [solicited-node multicast address].
+    ///
+    /// [Solicited-node multicast address]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.7.1
+    fn is_solicited_node_multicast(&self) -> bool;
+
     /// If `self` is a CIDR-compatible subnet mask, return `Some(prefix_len)`,
     /// If `self` is a CIDR-compatible subnet mask, return `Some(prefix_len)`,
     /// where `prefix_len` is the number of leading zeroes. Return `None` otherwise.
     /// where `prefix_len` is the number of leading zeroes. Return `None` otherwise.
     fn prefix_len(&self) -> Option<u8>;
     fn prefix_len(&self) -> Option<u8>;
@@ -193,6 +198,13 @@ impl AddressExt for Address {
         }
         }
     }
     }
 
 
+    fn is_solicited_node_multicast(&self) -> bool {
+        self.octets()[0..13]
+            == [
+                0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
+            ]
+    }
+
     fn prefix_len(&self) -> Option<u8> {
     fn prefix_len(&self) -> Option<u8> {
         let mut ones = true;
         let mut ones = true;
         let mut prefix_len = 0;
         let mut prefix_len = 0;
@@ -680,6 +692,8 @@ pub(crate) mod test {
     const UNIQUE_LOCAL_ADDR: Address = Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 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);
     const GLOBAL_UNICAST_ADDR: Address = Address::new(0x2001, 0xdb8, 0x3, 0, 0, 0, 0, 1);
 
 
+    const TEST_SOL_NODE_MCAST_ADDR: Address = Address::new(0xff02, 0, 0, 0, 0, 1, 0xff01, 101);
+
     #[test]
     #[test]
     fn test_basic_multicast() {
     fn test_basic_multicast() {
         assert!(!LINK_LOCAL_ALL_ROUTERS.is_unspecified());
         assert!(!LINK_LOCAL_ALL_ROUTERS.is_unspecified());
@@ -688,12 +702,14 @@ pub(crate) mod test {
         assert!(!LINK_LOCAL_ALL_ROUTERS.is_loopback());
         assert!(!LINK_LOCAL_ALL_ROUTERS.is_loopback());
         assert!(!LINK_LOCAL_ALL_ROUTERS.x_is_unique_local());
         assert!(!LINK_LOCAL_ALL_ROUTERS.x_is_unique_local());
         assert!(!LINK_LOCAL_ALL_ROUTERS.is_global_unicast());
         assert!(!LINK_LOCAL_ALL_ROUTERS.is_global_unicast());
+        assert!(!LINK_LOCAL_ALL_ROUTERS.is_solicited_node_multicast());
         assert!(!LINK_LOCAL_ALL_NODES.is_unspecified());
         assert!(!LINK_LOCAL_ALL_NODES.is_unspecified());
         assert!(LINK_LOCAL_ALL_NODES.is_multicast());
         assert!(LINK_LOCAL_ALL_NODES.is_multicast());
         assert!(!LINK_LOCAL_ALL_NODES.is_link_local());
         assert!(!LINK_LOCAL_ALL_NODES.is_link_local());
         assert!(!LINK_LOCAL_ALL_NODES.is_loopback());
         assert!(!LINK_LOCAL_ALL_NODES.is_loopback());
         assert!(!LINK_LOCAL_ALL_NODES.x_is_unique_local());
         assert!(!LINK_LOCAL_ALL_NODES.x_is_unique_local());
         assert!(!LINK_LOCAL_ALL_NODES.is_global_unicast());
         assert!(!LINK_LOCAL_ALL_NODES.is_global_unicast());
+        assert!(!LINK_LOCAL_ALL_NODES.is_solicited_node_multicast());
     }
     }
 
 
     #[test]
     #[test]
@@ -704,6 +720,7 @@ pub(crate) mod test {
         assert!(!LINK_LOCAL_ADDR.is_loopback());
         assert!(!LINK_LOCAL_ADDR.is_loopback());
         assert!(!LINK_LOCAL_ADDR.x_is_unique_local());
         assert!(!LINK_LOCAL_ADDR.x_is_unique_local());
         assert!(!LINK_LOCAL_ADDR.is_global_unicast());
         assert!(!LINK_LOCAL_ADDR.is_global_unicast());
+        assert!(!LINK_LOCAL_ADDR.is_solicited_node_multicast());
     }
     }
 
 
     #[test]
     #[test]
@@ -714,6 +731,7 @@ pub(crate) mod test {
         assert!(Address::LOCALHOST.is_loopback());
         assert!(Address::LOCALHOST.is_loopback());
         assert!(!Address::LOCALHOST.x_is_unique_local());
         assert!(!Address::LOCALHOST.x_is_unique_local());
         assert!(!Address::LOCALHOST.is_global_unicast());
         assert!(!Address::LOCALHOST.is_global_unicast());
+        assert!(!Address::LOCALHOST.is_solicited_node_multicast());
     }
     }
 
 
     #[test]
     #[test]
@@ -724,6 +742,7 @@ pub(crate) mod test {
         assert!(!UNIQUE_LOCAL_ADDR.is_loopback());
         assert!(!UNIQUE_LOCAL_ADDR.is_loopback());
         assert!(UNIQUE_LOCAL_ADDR.x_is_unique_local());
         assert!(UNIQUE_LOCAL_ADDR.x_is_unique_local());
         assert!(!UNIQUE_LOCAL_ADDR.is_global_unicast());
         assert!(!UNIQUE_LOCAL_ADDR.is_global_unicast());
+        assert!(!UNIQUE_LOCAL_ADDR.is_solicited_node_multicast());
     }
     }
 
 
     #[test]
     #[test]
@@ -734,6 +753,18 @@ pub(crate) mod test {
         assert!(!GLOBAL_UNICAST_ADDR.is_loopback());
         assert!(!GLOBAL_UNICAST_ADDR.is_loopback());
         assert!(!GLOBAL_UNICAST_ADDR.x_is_unique_local());
         assert!(!GLOBAL_UNICAST_ADDR.x_is_unique_local());
         assert!(GLOBAL_UNICAST_ADDR.is_global_unicast());
         assert!(GLOBAL_UNICAST_ADDR.is_global_unicast());
+        assert!(!GLOBAL_UNICAST_ADDR.is_solicited_node_multicast());
+    }
+
+    #[test]
+    fn test_sollicited_node_multicast() {
+        assert!(!TEST_SOL_NODE_MCAST_ADDR.is_unspecified());
+        assert!(TEST_SOL_NODE_MCAST_ADDR.is_multicast());
+        assert!(!TEST_SOL_NODE_MCAST_ADDR.is_link_local());
+        assert!(!TEST_SOL_NODE_MCAST_ADDR.is_loopback());
+        assert!(!TEST_SOL_NODE_MCAST_ADDR.x_is_unique_local());
+        assert!(!TEST_SOL_NODE_MCAST_ADDR.is_global_unicast());
+        assert!(TEST_SOL_NODE_MCAST_ADDR.is_solicited_node_multicast());
     }
     }
 
 
     #[test]
     #[test]