Skip to content

Commit

Permalink
fixup! fixup! feat: Add MLDv2 Listener Query response support
Browse files Browse the repository at this point in the history
  • Loading branch information
bergzand committed Nov 13, 2024
1 parent 41be841 commit 593a664
Showing 1 changed file with 184 additions and 0 deletions.
184 changes: 184 additions & 0 deletions src/iface/interface/tests/ipv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Ipv6Packet<std::vec::Vec<u8>>> {
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::<std::vec::Vec<_>>()
}

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::<Vec<_>>();

assert_eq!(record_reprs, expected_records);
}
}

0 comments on commit 593a664

Please sign in to comment.