Skip to content

Commit 4dcee75

Browse files
authored
Merge pull request #1012 from bergzand/pr/join_solicited_node
feat: Automatically join solicited-node multicast addresses
2 parents 33bf798 + 0385bad commit 4dcee75

File tree

4 files changed

+112
-12
lines changed

4 files changed

+112
-12
lines changed

src/iface/interface/mod.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,12 @@ impl Interface {
361361
pub fn update_ip_addrs<F: FnOnce(&mut Vec<IpCidr, IFACE_MAX_ADDR_COUNT>)>(&mut self, f: F) {
362362
f(&mut self.inner.ip_addrs);
363363
InterfaceInner::flush_neighbor_cache(&mut self.inner);
364-
InterfaceInner::check_ip_addrs(&self.inner.ip_addrs)
364+
InterfaceInner::check_ip_addrs(&self.inner.ip_addrs);
365+
366+
#[cfg(all(feature = "proto-ipv6", feature = "multicast"))]
367+
if self.inner.caps.medium == Medium::Ethernet {
368+
self.update_solicited_node_groups();
369+
}
365370
}
366371

367372
/// Check whether the interface has the given IP address assigned.

src/iface/interface/multicast.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use core::result::Result;
2-
use heapless::LinearMap;
2+
use heapless::{LinearMap, Vec};
33

44
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
55
use super::{check, IpPayload, Packet};
66
use super::{Interface, InterfaceInner};
7-
use crate::config::IFACE_MAX_MULTICAST_GROUP_COUNT;
7+
use crate::config::{IFACE_MAX_ADDR_COUNT, IFACE_MAX_MULTICAST_GROUP_COUNT};
88
use crate::phy::{Device, PacketMeta};
99
use crate::wire::*;
1010

@@ -156,6 +156,29 @@ impl Interface {
156156
self.inner.has_multicast_group(addr)
157157
}
158158

159+
#[cfg(feature = "proto-ipv6")]
160+
pub(super) fn update_solicited_node_groups(&mut self) {
161+
// Remove old solicited-node multicast addresses
162+
let removals: Vec<_, IFACE_MAX_MULTICAST_GROUP_COUNT> = self
163+
.inner
164+
.multicast
165+
.groups
166+
.keys()
167+
.cloned()
168+
.filter(|a| matches!(a, IpAddress::Ipv6(a) if a.is_solicited_node_multicast() && !self.inner.has_solicited_node(*a)))
169+
.collect();
170+
for removal in removals {
171+
let _ = self.leave_multicast_group(removal);
172+
}
173+
174+
let cidrs: Vec<IpCidr, IFACE_MAX_ADDR_COUNT> = Vec::from_slice(self.ip_addrs()).unwrap();
175+
for cidr in cidrs {
176+
if let IpCidr::Ipv6(cidr) = cidr {
177+
let _ = self.join_multicast_group(cidr.address().solicited_node());
178+
}
179+
}
180+
}
181+
159182
/// Do multicast egress.
160183
///
161184
/// - Send join/leave packets according to the multicast group state.

src/iface/interface/tests/ipv6.rs

+50-9
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,10 @@ fn test_join_ipv6_multicast_group(#[case] medium: Medium) {
12961296

12971297
let timestamp = Instant::from_millis(0);
12981298

1299+
// Drain the unsolicited node multicast report from the device
1300+
iface.poll(timestamp, &mut device, &mut sockets);
1301+
let _ = recv_icmpv6(&mut device, timestamp);
1302+
12991303
for &group in &groups {
13001304
iface.join_multicast_group(group).unwrap();
13011305
assert!(iface.has_multicast_group(group));
@@ -1372,10 +1376,12 @@ fn test_join_ipv6_multicast_group(#[case] medium: Medium) {
13721376
}
13731377
);
13741378

1375-
iface.leave_multicast_group(group_addr).unwrap();
1376-
assert!(!iface.has_multicast_group(group_addr));
1377-
iface.poll(timestamp, &mut device, &mut sockets);
1378-
assert!(!iface.has_multicast_group(group_addr));
1379+
if !group_addr.is_solicited_node_multicast() {
1380+
iface.leave_multicast_group(group_addr).unwrap();
1381+
assert!(!iface.has_multicast_group(group_addr));
1382+
iface.poll(timestamp, &mut device, &mut sockets);
1383+
assert!(!iface.has_multicast_group(group_addr));
1384+
}
13791385
}
13801386
}
13811387

@@ -1414,15 +1420,12 @@ fn test_handle_valid_multicast_query(#[case] medium: Medium) {
14141420

14151421
let mut eth_bytes = vec![0u8; 86];
14161422

1417-
let local_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 101);
1423+
let local_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
14181424
let remote_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 100);
14191425
let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
14201426
let query_ip_addr = Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 0x1234);
14211427

14221428
iface.join_multicast_group(query_ip_addr).unwrap();
1423-
iface
1424-
.join_multicast_group(local_ip_addr.solicited_node())
1425-
.unwrap();
14261429

14271430
iface.poll(timestamp, &mut device, &mut sockets);
14281431
// flush multicast reports from the join_multicast_group calls
@@ -1433,7 +1436,7 @@ fn test_handle_valid_multicast_query(#[case] medium: Medium) {
14331436
(
14341437
Ipv6Address::UNSPECIFIED,
14351438
IPV6_LINK_LOCAL_ALL_NODES,
1436-
vec![query_ip_addr, local_ip_addr.solicited_node()],
1439+
vec![local_ip_addr.solicited_node(), query_ip_addr],
14371440
),
14381441
// Address specific query, expect only the queried address back
14391442
(query_ip_addr, query_ip_addr, vec![query_ip_addr]),
@@ -1562,3 +1565,41 @@ fn test_handle_valid_multicast_query(#[case] medium: Medium) {
15621565
assert_eq!(record_reprs, expected_records);
15631566
}
15641567
}
1568+
1569+
#[rstest]
1570+
#[case(Medium::Ethernet)]
1571+
#[cfg(all(feature = "multicast", feature = "medium-ethernet"))]
1572+
fn test_solicited_node_multicast_autojoin(#[case] medium: Medium) {
1573+
let (mut iface, _, _) = setup(medium);
1574+
1575+
let addr1 = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
1576+
let addr2 = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2);
1577+
1578+
iface.update_ip_addrs(|ip_addrs| {
1579+
ip_addrs.clear();
1580+
ip_addrs.push(IpCidr::new(addr1.into(), 64)).unwrap();
1581+
});
1582+
assert!(iface.has_multicast_group(addr1.solicited_node()));
1583+
assert!(!iface.has_multicast_group(addr2.solicited_node()));
1584+
1585+
iface.update_ip_addrs(|ip_addrs| {
1586+
ip_addrs.clear();
1587+
ip_addrs.push(IpCidr::new(addr2.into(), 64)).unwrap();
1588+
});
1589+
assert!(!iface.has_multicast_group(addr1.solicited_node()));
1590+
assert!(iface.has_multicast_group(addr2.solicited_node()));
1591+
1592+
iface.update_ip_addrs(|ip_addrs| {
1593+
ip_addrs.clear();
1594+
ip_addrs.push(IpCidr::new(addr1.into(), 64)).unwrap();
1595+
ip_addrs.push(IpCidr::new(addr2.into(), 64)).unwrap();
1596+
});
1597+
assert!(iface.has_multicast_group(addr1.solicited_node()));
1598+
assert!(iface.has_multicast_group(addr2.solicited_node()));
1599+
1600+
iface.update_ip_addrs(|ip_addrs| {
1601+
ip_addrs.clear();
1602+
});
1603+
assert!(!iface.has_multicast_group(addr1.solicited_node()));
1604+
assert!(!iface.has_multicast_group(addr2.solicited_node()));
1605+
}

src/wire/ipv6.rs

+31
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ pub(crate) trait AddressExt {
125125
/// `x_` prefix is to avoid a collision with the still-unstable method in `core::ip`.
126126
fn x_multicast_scope(&self) -> MulticastScope;
127127

128+
/// Query whether the IPv6 address is a [solicited-node multicast address].
129+
///
130+
/// [Solicited-node multicast address]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.7.1
131+
fn is_solicited_node_multicast(&self) -> bool;
132+
128133
/// If `self` is a CIDR-compatible subnet mask, return `Some(prefix_len)`,
129134
/// where `prefix_len` is the number of leading zeroes. Return `None` otherwise.
130135
fn prefix_len(&self) -> Option<u8>;
@@ -193,6 +198,13 @@ impl AddressExt for Address {
193198
}
194199
}
195200

201+
fn is_solicited_node_multicast(&self) -> bool {
202+
self.octets()[0..13]
203+
== [
204+
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF,
205+
]
206+
}
207+
196208
fn prefix_len(&self) -> Option<u8> {
197209
let mut ones = true;
198210
let mut prefix_len = 0;
@@ -680,6 +692,8 @@ pub(crate) mod test {
680692
const UNIQUE_LOCAL_ADDR: Address = Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1);
681693
const GLOBAL_UNICAST_ADDR: Address = Address::new(0x2001, 0xdb8, 0x3, 0, 0, 0, 0, 1);
682694

695+
const TEST_SOL_NODE_MCAST_ADDR: Address = Address::new(0xff02, 0, 0, 0, 0, 1, 0xff01, 101);
696+
683697
#[test]
684698
fn test_basic_multicast() {
685699
assert!(!LINK_LOCAL_ALL_ROUTERS.is_unspecified());
@@ -688,12 +702,14 @@ pub(crate) mod test {
688702
assert!(!LINK_LOCAL_ALL_ROUTERS.is_loopback());
689703
assert!(!LINK_LOCAL_ALL_ROUTERS.x_is_unique_local());
690704
assert!(!LINK_LOCAL_ALL_ROUTERS.is_global_unicast());
705+
assert!(!LINK_LOCAL_ALL_ROUTERS.is_solicited_node_multicast());
691706
assert!(!LINK_LOCAL_ALL_NODES.is_unspecified());
692707
assert!(LINK_LOCAL_ALL_NODES.is_multicast());
693708
assert!(!LINK_LOCAL_ALL_NODES.is_link_local());
694709
assert!(!LINK_LOCAL_ALL_NODES.is_loopback());
695710
assert!(!LINK_LOCAL_ALL_NODES.x_is_unique_local());
696711
assert!(!LINK_LOCAL_ALL_NODES.is_global_unicast());
712+
assert!(!LINK_LOCAL_ALL_NODES.is_solicited_node_multicast());
697713
}
698714

699715
#[test]
@@ -704,6 +720,7 @@ pub(crate) mod test {
704720
assert!(!LINK_LOCAL_ADDR.is_loopback());
705721
assert!(!LINK_LOCAL_ADDR.x_is_unique_local());
706722
assert!(!LINK_LOCAL_ADDR.is_global_unicast());
723+
assert!(!LINK_LOCAL_ADDR.is_solicited_node_multicast());
707724
}
708725

709726
#[test]
@@ -714,6 +731,7 @@ pub(crate) mod test {
714731
assert!(Address::LOCALHOST.is_loopback());
715732
assert!(!Address::LOCALHOST.x_is_unique_local());
716733
assert!(!Address::LOCALHOST.is_global_unicast());
734+
assert!(!Address::LOCALHOST.is_solicited_node_multicast());
717735
}
718736

719737
#[test]
@@ -724,6 +742,7 @@ pub(crate) mod test {
724742
assert!(!UNIQUE_LOCAL_ADDR.is_loopback());
725743
assert!(UNIQUE_LOCAL_ADDR.x_is_unique_local());
726744
assert!(!UNIQUE_LOCAL_ADDR.is_global_unicast());
745+
assert!(!UNIQUE_LOCAL_ADDR.is_solicited_node_multicast());
727746
}
728747

729748
#[test]
@@ -734,6 +753,18 @@ pub(crate) mod test {
734753
assert!(!GLOBAL_UNICAST_ADDR.is_loopback());
735754
assert!(!GLOBAL_UNICAST_ADDR.x_is_unique_local());
736755
assert!(GLOBAL_UNICAST_ADDR.is_global_unicast());
756+
assert!(!GLOBAL_UNICAST_ADDR.is_solicited_node_multicast());
757+
}
758+
759+
#[test]
760+
fn test_sollicited_node_multicast() {
761+
assert!(!TEST_SOL_NODE_MCAST_ADDR.is_unspecified());
762+
assert!(TEST_SOL_NODE_MCAST_ADDR.is_multicast());
763+
assert!(!TEST_SOL_NODE_MCAST_ADDR.is_link_local());
764+
assert!(!TEST_SOL_NODE_MCAST_ADDR.is_loopback());
765+
assert!(!TEST_SOL_NODE_MCAST_ADDR.x_is_unique_local());
766+
assert!(!TEST_SOL_NODE_MCAST_ADDR.is_global_unicast());
767+
assert!(TEST_SOL_NODE_MCAST_ADDR.is_solicited_node_multicast());
737768
}
738769

739770
#[test]

0 commit comments

Comments
 (0)