diff --git a/src/iface/interface/tests/ipv6.rs b/src/iface/interface/tests/ipv6.rs index adc058fd3..08530b5b9 100644 --- a/src/iface/interface/tests/ipv6.rs +++ b/src/iface/interface/tests/ipv6.rs @@ -1378,3 +1378,187 @@ fn test_join_ipv6_multicast_group(#[case] medium: Medium) { assert!(!iface.has_multicast_group(group_addr)); } } + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "multicast", feature = "medium-ethernet"))] +fn test_handle_valid_multicast_query(#[case] medium: Medium) { + fn recv_icmpv6( + device: &mut crate::tests::TestingDevice, + timestamp: Instant, + ) -> std::vec::Vec>> { + let caps = device.capabilities(); + recv_all(device, timestamp) + .iter() + .filter_map(|frame| { + let ipv6_packet = match caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + let eth_frame = EthernetFrame::new_checked(frame).ok()?; + Ipv6Packet::new_checked(eth_frame.payload()).ok()? + } + #[cfg(feature = "medium-ip")] + Medium::Ip => Ipv6Packet::new_checked(&frame[..]).ok()?, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => todo!(), + }; + let buf = ipv6_packet.into_inner().to_vec(); + Some(Ipv6Packet::new_unchecked(buf)) + }) + .collect::>() + } + + let (mut iface, mut sockets, mut device) = setup(medium); + + let mut timestamp = Instant::ZERO; + + let mut eth_bytes = vec![0u8; 86]; + + let local_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 101); + 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 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(local_ip_addr.solicited_node()) + .unwrap(); + + iface.poll(timestamp, &mut device, &mut sockets); + // flush multicast reports from the join_multicast_group calls + recv_icmpv6(&mut device, timestamp); + + let queries = [ + // General query, expect both multicast addresses back + ( + Ipv6Address::UNSPECIFIED, + IPV6_LINK_LOCAL_ALL_NODES, + vec![query_ip_addr, local_ip_addr.solicited_node()], + ), + // Address specific query, expect only the queried address back + (query_ip_addr, query_ip_addr, vec![query_ip_addr]), + ]; + + for (mcast_query, address, _results) in queries.iter() { + let query = Icmpv6Repr::Mld(MldRepr::Query { + max_resp_code: 1000, + mcast_addr: *mcast_query, + s_flag: false, + qrv: 1, + qqic: 60, + num_srcs: 0, + data: &[0, 0, 0, 0], + }); + + let ip_repr = IpRepr::Ipv6(Ipv6Repr { + src_addr: remote_ip_addr, + dst_addr: *address, + next_header: IpProtocol::Icmpv6, + hop_limit: 1, + payload_len: query.buffer_len(), + }); + + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(EthernetAddress([0x33, 0x33, 0x00, 0x00, 0x00, 0x00])); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Ipv6); + ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default()); + query.emit( + &remote_ip_addr, + address, + &mut Icmpv6Packet::new_unchecked(&mut frame.payload_mut()[ip_repr.header_len()..]), + &ChecksumCapabilities::default(), + ); + + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments, + ); + + timestamp += crate::time::Duration::from_millis(1000); + iface.poll(timestamp, &mut device, &mut sockets); + } + + let reports = recv_icmpv6(&mut device, timestamp); + assert_eq!(reports.len(), queries.len()); + + let caps = device.capabilities(); + let checksum_caps = &caps.checksum; + for ((_mcast_query, _address, results), ipv6_packet) in queries.iter().zip(reports) { + let buf = ipv6_packet.into_inner(); + let ipv6_packet = Ipv6Packet::new_unchecked(buf.as_slice()); + + let ipv6_repr = Ipv6Repr::parse(&ipv6_packet).unwrap(); + let ip_payload = ipv6_packet.payload(); + assert_eq!(ipv6_repr.dst_addr, IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS); + + // The first 2 octets of this payload hold the next-header indicator and the + // Hop-by-Hop header length (in 8-octet words, minus 1). The remaining 6 octets + // hold the Hop-by-Hop PadN and Router Alert options. + let hbh_header = Ipv6HopByHopHeader::new_checked(&ip_payload[..8]).unwrap(); + let hbh_repr = Ipv6HopByHopRepr::parse(&hbh_header).unwrap(); + + assert_eq!(hbh_repr.options.len(), 3); + assert_eq!( + hbh_repr.options[0], + Ipv6OptionRepr::Unknown { + type_: Ipv6OptionType::Unknown(IpProtocol::Icmpv6.into()), + length: 0, + data: &[], + } + ); + assert_eq!( + hbh_repr.options[1], + Ipv6OptionRepr::RouterAlert(Ipv6OptionRouterAlert::MulticastListenerDiscovery) + ); + assert_eq!(hbh_repr.options[2], Ipv6OptionRepr::PadN(0)); + + let icmpv6_packet = + Icmpv6Packet::new_checked(&ip_payload[hbh_repr.buffer_len()..]).unwrap(); + let icmpv6_repr = Icmpv6Repr::parse( + &ipv6_packet.src_addr(), + &ipv6_packet.dst_addr(), + &icmpv6_packet, + checksum_caps, + ) + .unwrap(); + + let record_data = match icmpv6_repr { + Icmpv6Repr::Mld(MldRepr::Report { + nr_mcast_addr_rcrds, + data, + }) => { + assert_eq!(nr_mcast_addr_rcrds, results.len() as u16); + data + } + other => panic!("unexpected icmpv6_repr: {:?}", other), + }; + + let mut record_reprs = Vec::new(); + let mut payload = record_data; + + // FIXME: parsing multiple address records should be done by the MLD code + while !payload.is_empty() { + let record = MldAddressRecord::new_checked(payload).unwrap(); + let mut record_repr = MldAddressRecordRepr::parse(&record).unwrap(); + payload = record_repr.payload; + record_repr.payload = &[]; + record_reprs.push(record_repr); + } + + let expected_records = results + .iter() + .map(|addr| MldAddressRecordRepr { + num_srcs: 0, + mcast_addr: *addr, + record_type: MldRecordType::ModeIsExclude, + aux_data_len: 0, + payload: &[], + }) + .collect::>(); + + assert_eq!(record_reprs, expected_records); + } +}