use super::*;
pub(crate) mod constants;
mod body;
pub use self::body::{Icmpv6Body, Icmpv6ErrorBody};
mod decode;
pub(crate) use self::decode::append_icmpv6_packet;
mod message;
pub use self::message::extended_echo::{
ICMPV6_CODE_EXTENDED_ECHO_REPLY_MALFORMED_QUERY,
ICMPV6_CODE_EXTENDED_ECHO_REPLY_MULTIPLE_INTERFACES, ICMPV6_CODE_EXTENDED_ECHO_REPLY_NO_ERROR,
ICMPV6_CODE_EXTENDED_ECHO_REPLY_NO_SUCH_INTERFACE,
ICMPV6_CODE_EXTENDED_ECHO_REPLY_NO_SUCH_TABLE_ENTRY, ICMPV6_EXTENDED_ECHO_REPLY_ACTIVE,
ICMPV6_EXTENDED_ECHO_REPLY_IPV4, ICMPV6_EXTENDED_ECHO_REPLY_IPV6,
ICMPV6_EXTENDED_ECHO_REPLY_STATE_DELAY, ICMPV6_EXTENDED_ECHO_REPLY_STATE_FAILED,
ICMPV6_EXTENDED_ECHO_REPLY_STATE_INCOMPLETE, ICMPV6_EXTENDED_ECHO_REPLY_STATE_PROBE,
ICMPV6_EXTENDED_ECHO_REPLY_STATE_REACHABLE, ICMPV6_EXTENDED_ECHO_REPLY_STATE_RESERVED,
ICMPV6_EXTENDED_ECHO_REPLY_STATE_STALE, ICMPV6_EXTENDED_ECHO_REQUEST_L_BIT,
};
pub(crate) use self::message::mld::{
decode_mldv2_query, decode_mldv2_report, decode_multicast_listener_message,
};
pub use self::message::mld::{
Mldv2Query, Mldv2Report, MulticastAddressRecord, MulticastListenerMessage, MulticastRecordType,
MLDV2_QUERY_MIN_BODY_LEN, MLDV2_QUERY_QRV_MASK, MLDV2_QUERY_RESV_MASK, MLDV2_QUERY_S_FLAG,
};
pub(crate) use self::message::ndp::{
decode_neighbor_advertisement, decode_neighbor_solicitation, decode_redirect,
decode_router_advertisement, decode_router_solicitation,
};
pub use self::message::ndp::{
NeighborAdvertisement, NeighborSolicitation, Redirect, RouterAdvertisement, RouterSolicitation,
ICMPV6_NA_FLAGS_RESERVED, ICMPV6_NA_FLAG_OVERRIDE, ICMPV6_NA_FLAG_ROUTER,
ICMPV6_NA_FLAG_SOLICITED, ICMPV6_RA_DEFAULT_CUR_HOP_LIMIT, ICMPV6_RA_DEFAULT_ROUTER_LIFETIME,
ICMPV6_RA_FLAGS_RESERVED, ICMPV6_RA_FLAG_MANAGED, ICMPV6_RA_FLAG_OTHER,
};
pub use self::message::ndp_option::{
ndp_option_type_is_known, ndp_option_type_name, ndp_rdnss_length_units, NdpOption, NdpOptions,
Pref64Plc, Prf, NDP_DNS_LIFETIME_INFINITY, NDP_DNS_RESERVED_LEN,
NDP_LINK_LAYER_ADDR_ETHERNET_LEN, NDP_MTU_OPTION_LEN, NDP_MTU_OPTION_UNITS, NDP_NONCE_MIN_LEN,
NDP_OPTION_HEADER_LEN, NDP_OPTION_LENGTH_UNIT, NDP_OPT_CAPTIVE_PORTAL, NDP_OPT_DNSSL,
NDP_OPT_MTU, NDP_OPT_NONCE, NDP_OPT_PREF64, NDP_OPT_PREFIX_INFORMATION,
NDP_OPT_RA_FLAGS_EXTENSION, NDP_OPT_RDNSS, NDP_OPT_REDIRECTED_HEADER,
NDP_OPT_ROUTE_INFORMATION, NDP_OPT_SOURCE_LINK_LAYER_ADDR, NDP_OPT_TARGET_LINK_LAYER_ADDR,
NDP_PREF64_LEN, NDP_PREF64_PLC_MASK, NDP_PREF64_PREFIX_LEN, NDP_PREF64_SCALED_LIFETIME_MAX,
NDP_PREF64_SCALED_LIFETIME_SHIFT, NDP_PREF64_UNITS, NDP_PREFIX_FLAGS_RESERVED,
NDP_PREFIX_FLAG_AUTONOMOUS, NDP_PREFIX_FLAG_ON_LINK, NDP_PREFIX_INFORMATION_LEN,
NDP_PREFIX_INFORMATION_UNITS, NDP_PREFIX_LIFETIME_INFINITY, NDP_PRF_MASK, NDP_PRF_SHIFT,
NDP_RA_FLAGS_EXTENSION_BITS_LEN, NDP_RA_FLAGS_EXTENSION_LEN, NDP_RA_FLAGS_EXTENSION_UNITS,
NDP_RDNSS_ADDRESS_LEN, NDP_REDIRECTED_HEADER_RESERVED_LEN,
NDP_ROUTE_INFORMATION_LEN_FULL_PREFIX, NDP_ROUTE_INFORMATION_LEN_HALF_PREFIX,
NDP_ROUTE_INFORMATION_LEN_NO_PREFIX, NDP_ROUTE_LIFETIME_INFINITY,
};
pub(crate) use self::message::node_info::decode_node_information;
pub use self::message::node_info::{
NodeInformation, NI_NONCE_LEN, NI_QTYPE_IPV4_ADDRESSES, NI_QTYPE_NODE_ADDRESSES,
NI_QTYPE_NODE_NAME, NI_QTYPE_NOOP, NI_QUERY_CODE_SUBJECT_IPV4, NI_QUERY_CODE_SUBJECT_IPV6,
NI_QUERY_CODE_SUBJECT_NAME, NI_RESPONSE_CODE_REFUSED, NI_RESPONSE_CODE_SUCCESS,
NI_RESPONSE_CODE_UNKNOWN_QTYPE,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Icmpv6 {
icmp_type: Field<u8>,
code: Field<u8>,
checksum: Field<u16>,
rest_of_header: Field<[u8; 4]>,
identifier: Field<u16>,
sequence_number: Field<u16>,
length: Field<u8>,
mtu: Field<u32>,
pointer: Field<u32>,
extended_flags: Field<u8>,
}
impl Icmpv6 {
pub fn new() -> Self {
Self {
icmp_type: Field::defaulted(ICMPV6_ECHO_REQUEST),
code: Field::defaulted(0),
checksum: Field::unset(),
rest_of_header: Field::defaulted([0; 4]),
identifier: Field::defaulted(0),
sequence_number: Field::defaulted(0),
length: Field::unset(),
mtu: Field::unset(),
pointer: Field::unset(),
extended_flags: Field::unset(),
}
}
pub fn echo_request() -> Self {
Self::new().kind(IcmpKind::EchoRequest)
}
pub fn echo_reply() -> Self {
Self::new().kind(IcmpKind::EchoReply)
}
pub fn time_exceeded() -> Self {
Self::new().kind(IcmpKind::TimeExceeded)
}
pub fn destination_unreachable() -> Self {
Self::new().kind(IcmpKind::DestinationUnreachable)
}
pub fn packet_too_big() -> Self {
Self::new().icmp_type(ICMPV6_PACKET_TOO_BIG)
}
pub fn kind(mut self, kind: IcmpKind) -> Self {
self.icmp_type.set_user(kind.ipv6_type());
self
}
pub fn icmp_type(mut self, icmp_type: u8) -> Self {
self.icmp_type.set_user(icmp_type);
self
}
pub fn type_(self, icmp_type: u8) -> Self {
self.icmp_type(icmp_type)
}
pub fn code(mut self, code: u8) -> Self {
self.code.set_user(code);
self
}
pub fn checksum(mut self, checksum: u16) -> Self {
self.checksum.set_user(checksum);
self
}
pub fn chksum(self, checksum: u16) -> Self {
self.checksum(checksum)
}
pub fn rest_of_header(mut self, rest_of_header: [u8; 4]) -> Self {
self.rest_of_header.set_user(rest_of_header);
self
}
pub fn identifier(mut self, identifier: u16) -> Self {
self.identifier.set_user(identifier);
self
}
pub fn id(self, identifier: u16) -> Self {
self.identifier(identifier)
}
pub fn sequence_number(mut self, sequence_number: u16) -> Self {
self.sequence_number.set_user(sequence_number);
self
}
pub fn seq(self, sequence_number: u16) -> Self {
self.sequence_number(sequence_number)
}
pub fn length(mut self, length: u8) -> Self {
self.length.set_user(length);
self
}
pub fn len(self, length: u8) -> Self {
self.length(length)
}
pub fn mtu(mut self, mtu: u32) -> Self {
self.mtu.set_user(mtu);
self
}
pub fn pointer(mut self, pointer: u32) -> Self {
self.pointer.set_user(pointer);
self
}
pub fn extended_flags(mut self, extended_flags: u8) -> Self {
self.extended_flags.set_user(extended_flags);
self
}
pub fn extended_l_bit(mut self, set: bool) -> Self {
let base = self.extended_flags.value().copied().unwrap_or(0);
let value = if set {
base | ICMPV6_EXTENDED_ECHO_REQUEST_L_BIT
} else {
base & !ICMPV6_EXTENDED_ECHO_REQUEST_L_BIT
};
self.extended_flags.set_user(value);
self
}
pub fn extended_state(mut self, state: u8) -> Self {
let base = self.extended_flags.value().copied().unwrap_or(0);
let value = (base & 0x1f) | ((state & 0x07) << 5);
self.extended_flags.set_user(value);
self
}
pub fn extended_active(mut self, set: bool) -> Self {
let base = self.extended_flags.value().copied().unwrap_or(0);
let value = if set {
base | ICMPV6_EXTENDED_ECHO_REPLY_ACTIVE
} else {
base & !ICMPV6_EXTENDED_ECHO_REPLY_ACTIVE
};
self.extended_flags.set_user(value);
self
}
pub fn extended_ipv4(mut self, set: bool) -> Self {
let base = self.extended_flags.value().copied().unwrap_or(0);
let value = if set {
base | ICMPV6_EXTENDED_ECHO_REPLY_IPV4
} else {
base & !ICMPV6_EXTENDED_ECHO_REPLY_IPV4
};
self.extended_flags.set_user(value);
self
}
pub fn extended_ipv6(mut self, set: bool) -> Self {
let base = self.extended_flags.value().copied().unwrap_or(0);
let value = if set {
base | ICMPV6_EXTENDED_ECHO_REPLY_IPV6
} else {
base & !ICMPV6_EXTENDED_ECHO_REPLY_IPV6
};
self.extended_flags.set_user(value);
self
}
pub fn icmp_type_value(&self) -> u8 {
value_or_copy(&self.icmp_type, ICMPV6_ECHO_REQUEST)
}
pub fn code_value(&self) -> u8 {
value_or_copy(&self.code, 0)
}
pub fn checksum_value(&self) -> Option<u16> {
self.checksum.value().copied()
}
pub fn identifier_value(&self) -> Option<u16> {
let icmp_type = self.icmp_type_value();
if is_echo_v6(icmp_type) || is_extended_echo_v6(icmp_type) {
Some(value_or_u16_from_rest(
&self.identifier,
&self.rest_of_header,
0,
))
} else {
None
}
}
pub fn sequence_number_value(&self) -> Option<u16> {
let icmp_type = self.icmp_type_value();
if is_extended_echo_v6(icmp_type) {
Some(u16::from(value_or_u8_from_rest(
&self.extended_sequence_number_byte(),
&self.rest_of_header,
2,
)))
} else if is_echo_v6(icmp_type) {
Some(value_or_u16_from_rest(
&self.sequence_number,
&self.rest_of_header,
2,
))
} else {
None
}
}
pub fn rest_of_header_value(&self) -> [u8; 4] {
self.effective_rest_of_header(None, 8).unwrap_or([0; 4])
}
pub fn length_value(&self) -> Option<u8> {
self.length.value().copied()
}
pub fn extended_flags_value(&self) -> Option<u8> {
if is_extended_echo_v6(self.icmp_type_value()) {
Some(value_or_u8_from_rest(
&self.extended_flags,
&self.rest_of_header,
3,
))
} else {
None
}
}
pub fn extended_l_bit_value(&self) -> Option<bool> {
if self.icmp_type_value() == ICMPV6_EXTENDED_ECHO_REQUEST {
self.extended_flags_value()
.map(|flags| flags & ICMPV6_EXTENDED_ECHO_REQUEST_L_BIT != 0)
} else {
None
}
}
pub fn extended_state_value(&self) -> Option<u8> {
if self.icmp_type_value() == ICMPV6_EXTENDED_ECHO_REPLY {
self.extended_flags_value().map(|flags| (flags >> 5) & 0x07)
} else {
None
}
}
pub fn extended_active_value(&self) -> Option<bool> {
if self.icmp_type_value() == ICMPV6_EXTENDED_ECHO_REPLY {
self.extended_flags_value()
.map(|flags| flags & ICMPV6_EXTENDED_ECHO_REPLY_ACTIVE != 0)
} else {
None
}
}
pub fn extended_ipv4_value(&self) -> Option<bool> {
if self.icmp_type_value() == ICMPV6_EXTENDED_ECHO_REPLY {
self.extended_flags_value()
.map(|flags| flags & ICMPV6_EXTENDED_ECHO_REPLY_IPV4 != 0)
} else {
None
}
}
pub fn extended_ipv6_value(&self) -> Option<bool> {
if self.icmp_type_value() == ICMPV6_EXTENDED_ECHO_REPLY {
self.extended_flags_value()
.map(|flags| flags & ICMPV6_EXTENDED_ECHO_REPLY_IPV6 != 0)
} else {
None
}
}
pub fn kind_value(&self) -> Option<IcmpKind> {
match self.icmp_type_value() {
ICMPV6_DESTINATION_UNREACHABLE => Some(IcmpKind::DestinationUnreachable),
ICMPV6_TIME_EXCEEDED => Some(IcmpKind::TimeExceeded),
ICMPV6_PARAMETER_PROBLEM => Some(IcmpKind::ParameterProblem),
ICMPV6_ECHO_REQUEST => Some(IcmpKind::EchoRequest),
ICMPV6_ECHO_REPLY => Some(IcmpKind::EchoReply),
_ => None,
}
}
pub fn body(&self) -> Icmpv6Body {
Icmpv6Body::from_header(self)
}
fn effective_rest_of_header(
&self,
ctx: Option<LayerContext<'_>>,
extension_unit: usize,
) -> Result<[u8; 4]> {
let raw_is_user = self.rest_of_header.is_user_set();
let mut rest = value_or_copy(&self.rest_of_header, [0; 4]);
match self.icmp_type_value() {
ICMPV6_ECHO_REQUEST | ICMPV6_ECHO_REPLY => {
if self.identifier.is_user_set() || !raw_is_user {
let identifier =
value_or_u16_from_rest(&self.identifier, &self.rest_of_header, 0);
rest[..2].copy_from_slice(&identifier.to_be_bytes());
}
if self.sequence_number.is_user_set() || !raw_is_user {
let sequence =
value_or_u16_from_rest(&self.sequence_number, &self.rest_of_header, 2);
rest[2..4].copy_from_slice(&sequence.to_be_bytes());
}
}
ICMPV6_DESTINATION_UNREACHABLE | ICMPV6_TIME_EXCEEDED => {
if let Some(length) =
self.overriding_extension_length(ctx, extension_unit, raw_is_user)?
{
rest[0] = length;
}
}
ICMPV6_PACKET_TOO_BIG => {
if self.mtu.is_user_set() || !raw_is_user {
if let Some(mtu) = self.mtu.value().copied() {
rest.copy_from_slice(&mtu.to_be_bytes());
}
}
}
ICMPV6_PARAMETER_PROBLEM => {
if self.pointer.is_user_set() || !raw_is_user {
if let Some(pointer) = self.pointer.value().copied() {
rest.copy_from_slice(&pointer.to_be_bytes());
}
}
}
ICMPV6_EXTENDED_ECHO_REQUEST | ICMPV6_EXTENDED_ECHO_REPLY => {
if self.identifier.is_user_set() || !raw_is_user {
let identifier =
value_or_u16_from_rest(&self.identifier, &self.rest_of_header, 0);
rest[..2].copy_from_slice(&identifier.to_be_bytes());
}
if self.sequence_number.is_user_set() || !raw_is_user {
let sequence = value_or_u8_from_rest(
&self.extended_sequence_number_byte(),
&self.rest_of_header,
2,
);
rest[2] = sequence;
}
if self.extended_flags.is_user_set() || !raw_is_user {
if let Some(flags) = self.extended_flags.value().copied() {
rest[3] = flags;
}
}
}
_ => {}
}
Ok(rest)
}
fn extended_sequence_number_byte(&self) -> Field<u8> {
match self.sequence_number.value().copied() {
Some(value) => Field::user(value as u8),
None => Field::unset(),
}
}
fn overriding_extension_length(
&self,
ctx: Option<LayerContext<'_>>,
extension_unit: usize,
raw_is_user: bool,
) -> Result<Option<u8>> {
if self.length.is_user_set() {
return Ok(self.length.value().copied());
}
if raw_is_user {
return Ok(None);
}
self.effective_extension_length(ctx, extension_unit)
}
fn effective_extension_length(
&self,
ctx: Option<LayerContext<'_>>,
unit: usize,
) -> Result<Option<u8>> {
if let Some(length) = self.length.value().copied() {
return Ok(Some(length));
}
let Some(ctx) = ctx else {
return Ok(None);
};
if !icmpv6_type_allows_extensions(self.icmp_type_value()) {
return Ok(None);
}
let len = encoded_len_until_extension(ctx);
u8::try_from(len / unit).map(Some).map_err(|_| {
CrafterError::invalid_field_value(
"icmpv6.length",
"ICMPv6 original datagram length does not fit in one byte",
)
})
}
}
impl Default for Icmpv6 {
fn default() -> Self {
Self::new()
}
}
impl IcmpLayer for Icmpv6 {
fn icmp_type_value(&self) -> u8 {
Icmpv6::icmp_type_value(self)
}
fn code_value(&self) -> u8 {
Icmpv6::code_value(self)
}
fn checksum_value(&self) -> Option<u16> {
Icmpv6::checksum_value(self)
}
fn identifier_value(&self) -> Option<u16> {
Icmpv6::identifier_value(self)
}
fn sequence_number_value(&self) -> Option<u16> {
Icmpv6::sequence_number_value(self)
}
fn kind(&self) -> Option<IcmpKind> {
self.kind_value()
}
}
impl Layer for Icmpv6 {
fn name(&self) -> &'static str {
"Icmpv6"
}
fn summary(&self) -> String {
format!(
"Icmpv6(type={}, code={}, id={}, seq={})",
icmpv6_type_summary(self.icmp_type_value()),
self.code_value(),
self.identifier_value()
.map(|value| value.to_string())
.unwrap_or_else(|| "-".to_string()),
self.sequence_number_value()
.map(|value| value.to_string())
.unwrap_or_else(|| "-".to_string())
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let body = self.body();
vec![
("type", icmpv6_type_summary(self.icmp_type_value())),
("code", self.code_value().to_string()),
("body", body.detail()),
(
"checksum",
self.checksum_value()
.map(|value| format!("0x{value:04x}"))
.unwrap_or_else(|| "auto".to_string()),
),
("rest_of_header", hex_bytes(&self.rest_of_header_value())),
(
"identifier",
self.identifier_value()
.map(|value| format!("0x{value:04x}"))
.unwrap_or_else(|| "-".to_string()),
),
(
"sequence_number",
self.sequence_number_value()
.map(|value| value.to_string())
.unwrap_or_else(|| "-".to_string()),
),
(
"length",
self.length_value()
.map(|value| value.to_string())
.unwrap_or_else(|| "auto".to_string()),
),
]
}
fn encoded_len(&self) -> usize {
ICMP_HEADER_LEN
}
fn encoded_len_with_context(&self, ctx: &LayerContext<'_>) -> usize {
ICMP_HEADER_LEN + payload_len_after(*ctx)
}
fn compile(&self, ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
let rest_of_header = self.effective_rest_of_header(Some(*ctx), 8)?;
let start = out.len();
out.push(self.icmp_type_value());
out.push(self.code_value());
out.extend_from_slice(&0u16.to_be_bytes());
out.extend_from_slice(&rest_of_header);
if let Err(err) = compile_payload_after_into(*ctx, out) {
out.truncate(start);
return Err(err);
}
let checksum = self.checksum.value().copied().unwrap_or_else(|| {
checksum_context(*ctx, IPPROTO_ICMPV6)
.map(|pseudo_header| pseudo_header.checksum(&out[start..]))
.unwrap_or(0)
});
out[start + 2..start + 4].copy_from_slice(&checksum.to_be_bytes());
Ok(())
}
fn consumes_following(&self) -> bool {
true
}
impl_layer_object!(Icmpv6);
}
impl_layer_div!(Icmpv6);
#[cfg(test)]
mod icmpv6 {
use super::{IcmpKind, Icmpv6, Icmpv6Body, Icmpv6ErrorBody};
use crate::packet::Layer;
use crate::protocols::icmp::{
ICMPV6_ECHO_REQUEST, ICMPV6_PACKET_TOO_BIG, ICMPV6_PARAMETER_PROBLEM,
};
const UNMODELED_ICMPV6_TYPE: u8 = 200;
use crate::{Ipv6, NetworkLayer, Packet, Raw};
use core::net::Ipv6Addr;
const IPV6_ICMP_FIXTURE: &[u8] = fixture_bytes!("bytes/ipv6-icmp-echo-request.bin");
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)
}
#[test]
fn icmpv6_echo_request_matches_golden_bytes() {
let packet = Ipv6::new().src(src()).dst(dst()).fl(0x12345).hlim(64)
/ Icmpv6::echo_request().id(0x4242).seq(2)
/ Raw::from("libcrafter-ipv6");
let bytes = packet.compile().unwrap();
assert_eq!(bytes.as_bytes(), IPV6_ICMP_FIXTURE);
assert_eq!(&bytes.as_bytes()[40..42], &[ICMPV6_ECHO_REQUEST, 0]);
assert_eq!(&bytes.as_bytes()[42..44], &0x00d0u16.to_be_bytes());
}
#[test]
fn icmpv6_decode_from_ipv6_exposes_echo_fields_and_payload() {
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, IPV6_ICMP_FIXTURE).unwrap();
let ipv6 = decoded.layer::<Ipv6>().unwrap();
let icmpv6 = decoded.layer::<Icmpv6>().unwrap();
let raw = decoded.layer::<Raw>().unwrap();
assert_eq!(ipv6.source(), src());
assert_eq!(ipv6.destination(), dst());
assert_eq!(ipv6.flow_label_value(), 0x12345);
assert_eq!(ipv6.payload_length_value(), Some(23));
assert_eq!(icmpv6.kind_value(), Some(IcmpKind::EchoRequest));
assert_eq!(icmpv6.checksum_value(), Some(0x00d0));
assert_eq!(icmpv6.identifier_value(), Some(0x4242));
assert_eq!(icmpv6.sequence_number_value(), Some(2));
assert_eq!(raw.as_bytes(), b"libcrafter-ipv6");
assert_eq!(decoded.compile().unwrap().as_bytes(), IPV6_ICMP_FIXTURE);
}
#[test]
fn icmpv6_explicit_checksum_is_preserved() {
let bytes = (Ipv6::new().src(src()).dst(dst())
/ Icmpv6::echo_request().id(7).seq(8).checksum(0x2222)
/ Raw::from("abc"))
.compile()
.unwrap();
assert_eq!(&bytes.as_bytes()[42..44], &[0x22, 0x22]);
}
#[test]
fn icmpv6_decode_rejects_short_inputs() {
let short = (Ipv6::new().nh(crate::IPPROTO_ICMPV6) / Raw::from_bytes([0u8; 7]))
.compile()
.unwrap();
assert!(Packet::decode_from_l3(NetworkLayer::Ipv6, short.as_bytes()).is_err());
}
#[test]
fn icmpv6_body_classifies_echo() {
let body = Icmpv6::echo_request().id(0x4242).seq(2).body();
assert_eq!(
body,
Icmpv6Body::Echo {
identifier: 0x4242,
sequence_number: 2,
}
);
assert_eq!(body.label(), "echo");
}
#[test]
fn icmpv6_body_classifies_errors() {
let du = Icmpv6::destination_unreachable().code(4).body();
assert_eq!(
du,
Icmpv6Body::Error(Icmpv6ErrorBody::DestinationUnreachable)
);
assert_eq!(du.label(), "error");
assert_eq!(
Icmpv6::packet_too_big().mtu(1280).body(),
Icmpv6Body::Error(Icmpv6ErrorBody::PacketTooBig { mtu: 1280 })
);
assert_eq!(
Icmpv6::time_exceeded().body(),
Icmpv6Body::Error(Icmpv6ErrorBody::TimeExceeded)
);
assert_eq!(
Icmpv6::new()
.icmp_type(ICMPV6_PARAMETER_PROBLEM)
.pointer(6)
.body(),
Icmpv6Body::Error(Icmpv6ErrorBody::ParameterProblem { pointer: 6 })
);
}
#[test]
fn icmpv6_body_classifies_extended_echo() {
let request = Icmpv6::extended_echo_request()
.id(0x1234)
.seq(7)
.extended_l_bit(true)
.body();
assert_eq!(
request,
Icmpv6Body::ExtendedEchoRequest {
identifier: 0x1234,
sequence_number: 7,
local: true,
reserved_flags: 0,
}
);
assert_eq!(request.label(), "extended-echo-request");
let reply = Icmpv6::extended_echo_reply()
.id(0xabcd)
.seq(5)
.extended_state(2)
.extended_active(true)
.extended_ipv6(true)
.body();
assert_eq!(
reply,
Icmpv6Body::ExtendedEchoReply {
identifier: 0xabcd,
sequence_number: 5,
state: 2,
active: true,
ipv4: false,
ipv6: true,
}
);
assert_eq!(reply.label(), "extended-echo-reply");
}
#[test]
fn icmpv6_body_preserves_unknown_type() {
let icmpv6 = Icmpv6::new()
.icmp_type(UNMODELED_ICMPV6_TYPE)
.rest_of_header([0xde, 0xad, 0xbe, 0xef]);
let body = icmpv6.body();
assert_eq!(
body,
Icmpv6Body::Unknown {
icmp_type: UNMODELED_ICMPV6_TYPE,
rest_of_header: [0xde, 0xad, 0xbe, 0xef],
}
);
assert_eq!(body.label(), "unknown");
}
#[test]
fn icmpv6_summary_is_stable_and_show_reports_body() {
let echo = Icmpv6::echo_request().id(0x4242).seq(2);
assert_eq!(
echo.summary(),
"Icmpv6(type=echo-request(128), code=0, id=16962, seq=2)"
);
let echo_body = echo
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "body")
.map(|(_, value)| value)
.expect("show() exposes a body field");
assert_eq!(echo_body, "echo(id=0x4242, seq=2)");
let ptb = Icmpv6::packet_too_big().mtu(1280);
assert_eq!(
ptb.summary(),
"Icmpv6(type=packet-too-big(2), code=0, id=-, seq=-)"
);
let ptb_body = ptb
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "body")
.map(|(_, value)| value)
.expect("show() exposes a body field");
assert_eq!(ptb_body, "error(packet-too-big, mtu=1280)");
let unknown = Icmpv6::new().icmp_type(ICMPV6_PACKET_TOO_BIG ^ 0xff);
let unknown_body = unknown
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "body")
.map(|(_, value)| value)
.expect("show() exposes a body field");
assert!(unknown_body.starts_with("unknown(type="));
}
}