use super::*;
use crate::endian::read_u16_be;
fn decode_icmpv6_parts(bytes: &[u8]) -> Result<(Icmpv6, &[u8])> {
if bytes.len() < ICMP_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"icmpv6 header",
ICMP_HEADER_LEN,
bytes.len(),
));
}
let rest = copy_array_4(&bytes[4..8]);
let icmp_type = bytes[0];
let is_extended_echo = is_extended_echo_v6(icmp_type);
let icmpv6 = Icmpv6 {
icmp_type: Field::user(icmp_type),
code: Field::user(bytes[1]),
checksum: Field::user(read_u16_be(&bytes[2..4])?),
rest_of_header: Field::user(rest),
identifier: if is_echo_v6(icmp_type) || is_extended_echo {
Field::user(u16::from_be_bytes([rest[0], rest[1]]))
} else {
Field::unset()
},
sequence_number: if is_extended_echo {
Field::user(u16::from(rest[2]))
} else {
field_from_echo(icmp_type, &rest, 2, is_echo_v6)
},
length: if icmpv6_type_allows_extensions(icmp_type) {
Field::user(rest[0])
} else {
Field::unset()
},
mtu: if icmp_type == ICMPV6_PACKET_TOO_BIG {
Field::user(u32::from_be_bytes(rest))
} else {
Field::unset()
},
pointer: if icmp_type == ICMPV6_PARAMETER_PROBLEM {
Field::user(u32::from_be_bytes(rest))
} else {
Field::unset()
},
extended_flags: if is_extended_echo {
Field::user(rest[3])
} else {
Field::unset()
},
};
Ok((icmpv6, &bytes[ICMP_HEADER_LEN..]))
}
pub(crate) fn append_icmpv6_packet(mut packet: Packet, bytes: &[u8]) -> Result<Packet> {
let (icmpv6, payload) = decode_icmpv6_parts(bytes)?;
let icmp_type = icmpv6.icmp_type_value();
packet = packet.push_icmpv6(icmpv6);
if icmp_type == ICMPV6_MULTICAST_LISTENER_QUERY && payload.len() >= MLDV2_QUERY_MIN_BODY_LEN {
if let Ok(query) = decode_mldv2_query(payload) {
return Ok(packet.push(query));
}
}
if icmp_type == ICMPV6_MULTICAST_LISTENER_QUERY
|| icmp_type == ICMPV6_MULTICAST_LISTENER_REPORT
|| icmp_type == ICMPV6_MULTICAST_LISTENER_DONE
{
if let Ok(mld) = decode_multicast_listener_message(payload) {
return Ok(packet.push(mld));
}
}
if icmp_type == ICMPV6_MLDV2_REPORT {
if let Ok(report) = decode_mldv2_report(payload) {
return Ok(packet.push(report));
}
}
if icmp_type == ICMPV6_ROUTER_SOLICITATION {
if let Ok(rs) = decode_router_solicitation(payload) {
return Ok(packet.push(rs));
}
}
if icmp_type == ICMPV6_ROUTER_ADVERTISEMENT {
if let Ok(ra) = decode_router_advertisement(payload) {
return Ok(packet.push(ra));
}
}
if icmp_type == ICMPV6_NEIGHBOR_SOLICITATION {
if let Ok(ns) = decode_neighbor_solicitation(payload) {
return Ok(packet.push_neighbor_solicitation(ns));
}
}
if icmp_type == ICMPV6_NEIGHBOR_ADVERTISEMENT {
if let Ok(na) = decode_neighbor_advertisement(payload) {
return Ok(packet.push(na));
}
}
if icmp_type == ICMPV6_REDIRECT {
if let Ok(redirect) = decode_redirect(payload) {
return Ok(packet.push(redirect));
}
}
if icmp_type == ICMPV6_NODE_INFORMATION_QUERY || icmp_type == ICMPV6_NODE_INFORMATION_RESPONSE {
if let Ok(node_info) = decode_node_information(payload) {
return Ok(packet.push(node_info));
}
}
if icmp_type == ICMPV6_EXTENDED_ECHO_REQUEST {
if let Some(layers) = decode_extended_echo_extension(payload) {
for layer in layers {
packet = packet.push_box(layer);
}
return Ok(packet);
}
}
if !payload.is_empty() {
packet = packet.push_raw(Raw::from_bytes(payload));
}
Ok(packet)
}
#[cfg(test)]
mod tests {
use super::append_icmpv6_packet;
use crate::packet::{Layer, Packet, Raw};
use crate::protocols::icmp::{
Icmpv6, Mldv2Report, MulticastListenerMessage, NeighborAdvertisement, NeighborSolicitation,
NodeInformation, Redirect, RouterAdvertisement, RouterSolicitation,
};
use crate::{Ipv6, NetworkLayer};
use core::net::Ipv6Addr;
const UNMODELED_ICMPV6_TYPE: u8 = 200;
fn src() -> Ipv6Addr {
Ipv6Addr::new(0x2001, 0x0db8, 1, 0, 0, 0, 0, 0x0010)
}
fn dst() -> Ipv6Addr {
Ipv6Addr::new(0x2001, 0x0db8, 2, 0, 0, 0, 0, 0x0020)
}
fn target() -> Ipv6Addr {
Ipv6Addr::new(0x2001, 0x0db8, 1, 0, 0, 0, 0, 0x00ff)
}
fn group() -> Ipv6Addr {
Ipv6Addr::new(0xff05, 0, 0, 0, 0, 0, 0, 0x00fb)
}
fn round_trip(packet: Packet) -> Packet {
let bytes = packet.compile().expect("compile");
Packet::decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes()).expect("decode_from_l3")
}
#[test]
fn ndp_types_decode_to_typed_bodies() {
let rs = round_trip(Ipv6::new().src(src()).dst(dst()) / Icmpv6::router_solicitation());
assert!(rs.layer::<RouterSolicitation>().is_some());
let ra = round_trip(Ipv6::new().src(src()).dst(dst()) / Icmpv6::router_advertisement());
assert!(ra.layer::<RouterAdvertisement>().is_some());
let ns =
round_trip(Ipv6::new().src(src()).dst(dst()) / Icmpv6::neighbor_solicitation(target()));
assert!(ns.layer::<NeighborSolicitation>().is_some());
let na = round_trip(
Ipv6::new().src(src()).dst(dst()) / Icmpv6::neighbor_advertisement(target()),
);
assert!(na.layer::<NeighborAdvertisement>().is_some());
let redirect =
round_trip(Ipv6::new().src(src()).dst(dst()) / Icmpv6::redirect(target(), dst()));
assert!(redirect.layer::<Redirect>().is_some());
}
#[test]
fn mld_and_node_info_types_decode_to_typed_bodies() {
let query =
round_trip(Ipv6::new().src(src()).dst(dst()) / Icmpv6::mld_query(group(), 1000));
assert!(query.layer::<MulticastListenerMessage>().is_some());
let report =
round_trip(Ipv6::new().src(src()).dst(dst()) / Icmpv6::mldv2_report(Vec::new()));
assert!(report.layer::<Mldv2Report>().is_some());
let ni = round_trip(
Ipv6::new().src(src()).dst(dst())
/ Icmpv6::node_information_query(0, 0, [0u8; 8], Vec::new()),
);
assert!(ni.layer::<NodeInformation>().is_some());
}
#[test]
fn unknown_type_decodes_to_header_plus_raw_and_round_trips() {
let packet = Ipv6::new().src(src()).dst(dst())
/ Icmpv6::new()
.icmp_type(UNMODELED_ICMPV6_TYPE)
.rest_of_header([0xde, 0xad, 0xbe, 0xef])
/ Raw::from_bytes([0x01, 0x02, 0x03, 0x04]);
let bytes = packet.compile().expect("compile");
let decoded =
Packet::decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes()).expect("decode_from_l3");
let icmpv6 = decoded.layer::<Icmpv6>().expect("Icmpv6 header decodes");
assert_eq!(icmpv6.icmp_type_value(), UNMODELED_ICMPV6_TYPE);
let raw = decoded
.layer::<Raw>()
.expect("unknown body preserved as Raw");
assert_eq!(raw.as_bytes(), &[0x01, 0x02, 0x03, 0x04]);
assert!(decoded.layer::<RouterSolicitation>().is_none());
assert_eq!(
decoded.compile().expect("recompile").as_bytes(),
bytes.as_bytes()
);
}
#[test]
fn deferred_families_decode_to_header_plus_raw() {
for icmp_type in [138u8, 141, 142] {
let packet = Ipv6::new().src(src()).dst(dst())
/ Icmpv6::new().icmp_type(icmp_type)
/ Raw::from_bytes([0xaa, 0xbb, 0xcc, 0xdd]);
let decoded = round_trip(packet);
let icmpv6 = decoded.layer::<Icmpv6>().expect("header decodes");
assert_eq!(icmpv6.icmp_type_value(), icmp_type);
assert_eq!(
decoded
.layer::<Raw>()
.expect("payload preserved as Raw")
.as_bytes(),
&[0xaa, 0xbb, 0xcc, 0xdd]
);
}
}
#[test]
fn malformed_known_body_falls_back_to_raw() {
let mut icmpv6_bytes = vec![
crate::protocols::icmp::ICMPV6_NEIGHBOR_SOLICITATION,
0, 0,
0, 0,
0,
0,
0, ];
icmpv6_bytes.extend_from_slice(&[0xde, 0xad]);
let base = Packet::new();
let decoded = append_icmpv6_packet(base, &icmpv6_bytes).expect("no panic");
assert!(decoded.layer::<NeighborSolicitation>().is_none());
let raw = decoded
.layer::<Raw>()
.expect("malformed body preserved as Raw");
assert_eq!(raw.as_bytes(), &[0xde, 0xad]);
}
#[test]
fn truncated_header_is_structured_error() {
let base = Packet::new();
let err = append_icmpv6_packet(base, &[0x80, 0x00, 0x00]).unwrap_err();
let message = err.to_string();
assert!(
message.contains("icmpv6 header"),
"unexpected error: {message}"
);
}
#[test]
fn checksum_auto_fills_for_new_families_and_user_value_survives() {
let cases: Vec<Packet> = vec![
Ipv6::new().src(src()).dst(dst()) / Icmpv6::router_solicitation(),
Ipv6::new().src(src()).dst(dst()) / Icmpv6::router_advertisement(),
Ipv6::new().src(src()).dst(dst()) / Icmpv6::neighbor_solicitation(target()),
Ipv6::new().src(src()).dst(dst()) / Icmpv6::neighbor_advertisement(target()),
Ipv6::new().src(src()).dst(dst()) / Icmpv6::redirect(target(), dst()),
Ipv6::new().src(src()).dst(dst()) / Icmpv6::mld_query(group(), 1000),
Ipv6::new().src(src()).dst(dst()) / Icmpv6::mldv2_report(Vec::new()),
Ipv6::new().src(src()).dst(dst()) / Icmpv6::extended_echo_request().id(1).seq(2),
];
for packet in cases {
let decoded = round_trip(packet);
let icmpv6 = decoded.layer::<Icmpv6>().expect("Icmpv6 header");
assert_ne!(
icmpv6.checksum_value(),
Some(0),
"checksum should be auto-filled for {}",
icmpv6.summary()
);
}
let forced = Ipv6::new().src(src()).dst(dst())
/ Icmpv6::new()
.icmp_type(crate::protocols::icmp::ICMPV6_NEIGHBOR_SOLICITATION)
.code(0)
.checksum(0xbeef)
/ NeighborSolicitation::new(target());
let bytes = forced.compile().expect("compile");
assert_eq!(&bytes.as_bytes()[42..44], &[0xbe, 0xef]);
}
}