use super::super::*;
use super::ndp_option::{NdpOptions, Prf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RouterSolicitation {
pub(crate) options: NdpOptions,
}
impl RouterSolicitation {
pub fn new() -> Self {
Self {
options: NdpOptions::new(),
}
}
pub fn option(mut self, option: NdpOption) -> Self {
self.options.add(option);
self
}
pub fn options(mut self, options: NdpOptions) -> Self {
self.options = options;
self
}
pub fn options_ref(&self) -> &NdpOptions {
&self.options
}
}
impl Default for RouterSolicitation {
fn default() -> Self {
Self::new()
}
}
impl Layer for RouterSolicitation {
fn name(&self) -> &'static str {
"RouterSolicitation"
}
fn summary(&self) -> String {
format!("RouterSolicitation(options={})", self.options.len())
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![("option_count", self.options.len().to_string())];
for (index, option) in self.options.iter().enumerate() {
fields.push((option_field_name(index), option.to_string()));
}
fields
}
fn encoded_len(&self) -> usize {
self.options.encoded_len().unwrap_or(0)
}
fn compile(&self, _ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
self.options.encode_into(out)?;
Ok(())
}
impl_layer_object!(RouterSolicitation);
}
impl_layer_div!(RouterSolicitation);
fn option_field_name(index: usize) -> &'static str {
const NAMES: [&str; 8] = [
"option[0]",
"option[1]",
"option[2]",
"option[3]",
"option[4]",
"option[5]",
"option[6]",
"option[7]",
];
NAMES.get(index).copied().unwrap_or("option[*]")
}
impl Icmpv6 {
pub fn router_solicitation() -> Packet {
Self::router_solicitation_body(RouterSolicitation::new())
}
pub fn router_solicitation_with_source_link_layer(mac: crate::MacAddr) -> Packet {
Self::router_solicitation_body(
RouterSolicitation::new().option(NdpOption::source_link_layer_address(mac)),
)
}
fn router_solicitation_body(body: RouterSolicitation) -> Packet {
Self::new().icmp_type(ICMPV6_ROUTER_SOLICITATION).code(0) / body
}
}
pub(crate) fn decode_router_solicitation(bytes: &[u8]) -> Result<RouterSolicitation> {
let options = NdpOptions::decode(bytes)?;
Ok(RouterSolicitation { options })
}
const RA_TIMER_LEN: usize = 4;
const RA_BODY_FIXED_LEN: usize = RA_TIMER_LEN * 2;
pub const ICMPV6_RA_FLAG_MANAGED: u8 = 0x80;
pub const ICMPV6_RA_FLAG_OTHER: u8 = 0x40;
pub const ICMPV6_RA_FLAGS_RESERVED: u8 = 0x3f;
pub const ICMPV6_RA_DEFAULT_CUR_HOP_LIMIT: u8 = 64;
pub const ICMPV6_RA_DEFAULT_ROUTER_LIFETIME: u16 = 1800;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RouterAdvertisement {
pub(crate) reachable_time: u32,
pub(crate) retrans_timer: u32,
pub(crate) options: NdpOptions,
}
impl RouterAdvertisement {
pub fn new() -> Self {
Self {
reachable_time: 0,
retrans_timer: 0,
options: NdpOptions::new(),
}
}
pub fn reachable_time(mut self, reachable_time: u32) -> Self {
self.reachable_time = reachable_time;
self
}
pub fn retrans_timer(mut self, retrans_timer: u32) -> Self {
self.retrans_timer = retrans_timer;
self
}
pub fn option(mut self, option: NdpOption) -> Self {
self.options.add(option);
self
}
pub fn options(mut self, options: NdpOptions) -> Self {
self.options = options;
self
}
pub fn reachable_time_value(&self) -> u32 {
self.reachable_time
}
pub fn retrans_timer_value(&self) -> u32 {
self.retrans_timer
}
pub fn options_ref(&self) -> &NdpOptions {
&self.options
}
}
impl Default for RouterAdvertisement {
fn default() -> Self {
Self::new()
}
}
impl Layer for RouterAdvertisement {
fn name(&self) -> &'static str {
"RouterAdvertisement"
}
fn summary(&self) -> String {
format!(
"RouterAdvertisement(reachable={}, retrans={}, options={})",
self.reachable_time,
self.retrans_timer,
self.options.len()
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![
("reachable_time", self.reachable_time.to_string()),
("retrans_timer", self.retrans_timer.to_string()),
("option_count", self.options.len().to_string()),
];
for (index, option) in self.options.iter().enumerate() {
fields.push((option_field_name(index), option.to_string()));
}
fields
}
fn encoded_len(&self) -> usize {
RA_BODY_FIXED_LEN + self.options.encoded_len().unwrap_or(0)
}
fn compile(&self, _ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
out.extend_from_slice(&self.reachable_time.to_be_bytes());
out.extend_from_slice(&self.retrans_timer.to_be_bytes());
self.options.encode_into(out)?;
Ok(())
}
impl_layer_object!(RouterAdvertisement);
}
impl_layer_div!(RouterAdvertisement);
fn router_advertisement_rest_of_header(
cur_hop_limit: u8,
flags: u8,
router_lifetime: u16,
) -> [u8; 4] {
let lifetime = router_lifetime.to_be_bytes();
[cur_hop_limit, flags, lifetime[0], lifetime[1]]
}
impl Icmpv6 {
pub fn router_advertisement() -> Packet {
Self::router_advertisement_with(
ICMPV6_RA_DEFAULT_CUR_HOP_LIMIT,
false,
false,
ICMPV6_RA_DEFAULT_ROUTER_LIFETIME,
RouterAdvertisement::new(),
)
}
pub fn router_advertisement_with(
cur_hop_limit: u8,
managed: bool,
other: bool,
router_lifetime: u16,
body: RouterAdvertisement,
) -> Packet {
Self::router_advertisement_with_preference(
cur_hop_limit,
managed,
other,
Prf::Medium,
router_lifetime,
body,
)
}
pub fn router_advertisement_with_preference(
cur_hop_limit: u8,
managed: bool,
other: bool,
preference: Prf,
router_lifetime: u16,
body: RouterAdvertisement,
) -> Packet {
let mut flags = 0u8;
if managed {
flags |= ICMPV6_RA_FLAG_MANAGED;
}
if other {
flags |= ICMPV6_RA_FLAG_OTHER;
}
flags |= preference.to_flag_bits();
let rest = router_advertisement_rest_of_header(cur_hop_limit, flags, router_lifetime);
Self::new()
.icmp_type(ICMPV6_ROUTER_ADVERTISEMENT)
.code(0)
.rest_of_header(rest)
/ body
}
}
pub(crate) fn decode_router_advertisement(bytes: &[u8]) -> Result<RouterAdvertisement> {
if bytes.len() < RA_BODY_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"icmpv6.router_advertisement.body",
RA_BODY_FIXED_LEN,
bytes.len(),
));
}
let reachable_time = u32::from_be_bytes(copy_array_4(&bytes[0..4]));
let retrans_timer = u32::from_be_bytes(copy_array_4(&bytes[4..8]));
let options = NdpOptions::decode(&bytes[RA_BODY_FIXED_LEN..])?;
Ok(RouterAdvertisement {
reachable_time,
retrans_timer,
options,
})
}
const NS_TARGET_ADDRESS_LEN: usize = 16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NeighborSolicitation {
pub(crate) target_address: core::net::Ipv6Addr,
pub(crate) options: NdpOptions,
}
impl NeighborSolicitation {
pub fn new(target_address: core::net::Ipv6Addr) -> Self {
Self {
target_address,
options: NdpOptions::new(),
}
}
pub fn target_address(mut self, target_address: core::net::Ipv6Addr) -> Self {
self.target_address = target_address;
self
}
pub fn option(mut self, option: NdpOption) -> Self {
self.options.add(option);
self
}
pub fn options(mut self, options: NdpOptions) -> Self {
self.options = options;
self
}
pub fn target_address_value(&self) -> core::net::Ipv6Addr {
self.target_address
}
pub fn options_ref(&self) -> &NdpOptions {
&self.options
}
}
impl Layer for NeighborSolicitation {
fn name(&self) -> &'static str {
"NeighborSolicitation"
}
fn summary(&self) -> String {
format!(
"NeighborSolicitation(target={}, options={})",
self.target_address,
self.options.len()
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![
("target_address", self.target_address.to_string()),
("option_count", self.options.len().to_string()),
];
for (index, option) in self.options.iter().enumerate() {
fields.push((option_field_name(index), option.to_string()));
}
fields
}
fn encoded_len(&self) -> usize {
NS_TARGET_ADDRESS_LEN + self.options.encoded_len().unwrap_or(0)
}
fn compile(&self, _ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
out.extend_from_slice(&self.target_address.octets());
self.options.encode_into(out)?;
Ok(())
}
impl_layer_object!(NeighborSolicitation);
}
impl_layer_div!(NeighborSolicitation);
impl Icmpv6 {
pub fn neighbor_solicitation(target: core::net::Ipv6Addr) -> Packet {
Self::neighbor_solicitation_body(NeighborSolicitation::new(target))
}
pub fn neighbor_solicitation_with_source_link_layer(
target: core::net::Ipv6Addr,
mac: crate::MacAddr,
) -> Packet {
Self::neighbor_solicitation_body(
NeighborSolicitation::new(target).option(NdpOption::source_link_layer_address(mac)),
)
}
fn neighbor_solicitation_body(body: NeighborSolicitation) -> Packet {
Self::new().icmp_type(ICMPV6_NEIGHBOR_SOLICITATION).code(0) / body
}
}
pub(crate) fn decode_neighbor_solicitation(bytes: &[u8]) -> Result<NeighborSolicitation> {
if bytes.len() < NS_TARGET_ADDRESS_LEN {
return Err(CrafterError::buffer_too_short(
"icmpv6.neighbor_solicitation.target_address",
NS_TARGET_ADDRESS_LEN,
bytes.len(),
));
}
let mut octets = [0u8; NS_TARGET_ADDRESS_LEN];
octets.copy_from_slice(&bytes[..NS_TARGET_ADDRESS_LEN]);
let target_address = core::net::Ipv6Addr::from(octets);
let options = NdpOptions::decode(&bytes[NS_TARGET_ADDRESS_LEN..])?;
Ok(NeighborSolicitation {
target_address,
options,
})
}
const NA_TARGET_ADDRESS_LEN: usize = 16;
pub const ICMPV6_NA_FLAG_ROUTER: u8 = 0x80;
pub const ICMPV6_NA_FLAG_SOLICITED: u8 = 0x40;
pub const ICMPV6_NA_FLAG_OVERRIDE: u8 = 0x20;
pub const ICMPV6_NA_FLAGS_RESERVED: u32 = 0x1fff_ffff;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NeighborAdvertisement {
pub(crate) target_address: core::net::Ipv6Addr,
pub(crate) options: NdpOptions,
}
impl NeighborAdvertisement {
pub fn new(target_address: core::net::Ipv6Addr) -> Self {
Self {
target_address,
options: NdpOptions::new(),
}
}
pub fn target_address(mut self, target_address: core::net::Ipv6Addr) -> Self {
self.target_address = target_address;
self
}
pub fn option(mut self, option: NdpOption) -> Self {
self.options.add(option);
self
}
pub fn options(mut self, options: NdpOptions) -> Self {
self.options = options;
self
}
pub fn target_address_value(&self) -> core::net::Ipv6Addr {
self.target_address
}
pub fn options_ref(&self) -> &NdpOptions {
&self.options
}
}
impl Layer for NeighborAdvertisement {
fn name(&self) -> &'static str {
"NeighborAdvertisement"
}
fn summary(&self) -> String {
format!(
"NeighborAdvertisement(target={}, options={})",
self.target_address,
self.options.len()
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![
("target_address", self.target_address.to_string()),
("option_count", self.options.len().to_string()),
];
for (index, option) in self.options.iter().enumerate() {
fields.push((option_field_name(index), option.to_string()));
}
fields
}
fn encoded_len(&self) -> usize {
NA_TARGET_ADDRESS_LEN + self.options.encoded_len().unwrap_or(0)
}
fn compile(&self, _ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
out.extend_from_slice(&self.target_address.octets());
self.options.encode_into(out)?;
Ok(())
}
impl_layer_object!(NeighborAdvertisement);
}
impl_layer_div!(NeighborAdvertisement);
fn neighbor_advertisement_rest_of_header(
router: bool,
solicited: bool,
override_flag: bool,
) -> [u8; 4] {
let mut flags = 0u8;
if router {
flags |= ICMPV6_NA_FLAG_ROUTER;
}
if solicited {
flags |= ICMPV6_NA_FLAG_SOLICITED;
}
if override_flag {
flags |= ICMPV6_NA_FLAG_OVERRIDE;
}
[flags, 0, 0, 0]
}
impl Icmpv6 {
pub fn neighbor_advertisement(target: core::net::Ipv6Addr) -> Packet {
Self::neighbor_advertisement_with(false, false, false, NeighborAdvertisement::new(target))
}
pub fn neighbor_advertisement_with(
router: bool,
solicited: bool,
override_flag: bool,
body: NeighborAdvertisement,
) -> Packet {
let rest = neighbor_advertisement_rest_of_header(router, solicited, override_flag);
Self::new()
.icmp_type(ICMPV6_NEIGHBOR_ADVERTISEMENT)
.code(0)
.rest_of_header(rest)
/ body
}
pub fn neighbor_advertisement_with_target_link_layer(
target: core::net::Ipv6Addr,
mac: crate::MacAddr,
router: bool,
solicited: bool,
override_flag: bool,
) -> Packet {
Self::neighbor_advertisement_with(
router,
solicited,
override_flag,
NeighborAdvertisement::new(target).option(NdpOption::target_link_layer_address(mac)),
)
}
}
pub(crate) fn decode_neighbor_advertisement(bytes: &[u8]) -> Result<NeighborAdvertisement> {
if bytes.len() < NA_TARGET_ADDRESS_LEN {
return Err(CrafterError::buffer_too_short(
"icmpv6.neighbor_advertisement.target_address",
NA_TARGET_ADDRESS_LEN,
bytes.len(),
));
}
let mut octets = [0u8; NA_TARGET_ADDRESS_LEN];
octets.copy_from_slice(&bytes[..NA_TARGET_ADDRESS_LEN]);
let target_address = core::net::Ipv6Addr::from(octets);
let options = NdpOptions::decode(&bytes[NA_TARGET_ADDRESS_LEN..])?;
Ok(NeighborAdvertisement {
target_address,
options,
})
}
const REDIRECT_ADDRESS_LEN: usize = 16;
const REDIRECT_BODY_FIXED_LEN: usize = REDIRECT_ADDRESS_LEN * 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Redirect {
pub(crate) target_address: core::net::Ipv6Addr,
pub(crate) destination_address: core::net::Ipv6Addr,
pub(crate) options: NdpOptions,
}
impl Redirect {
pub fn new(
target_address: core::net::Ipv6Addr,
destination_address: core::net::Ipv6Addr,
) -> Self {
Self {
target_address,
destination_address,
options: NdpOptions::new(),
}
}
pub fn target_address(mut self, target_address: core::net::Ipv6Addr) -> Self {
self.target_address = target_address;
self
}
pub fn destination_address(mut self, destination_address: core::net::Ipv6Addr) -> Self {
self.destination_address = destination_address;
self
}
pub fn option(mut self, option: NdpOption) -> Self {
self.options.add(option);
self
}
pub fn options(mut self, options: NdpOptions) -> Self {
self.options = options;
self
}
pub fn target_address_value(&self) -> core::net::Ipv6Addr {
self.target_address
}
pub fn destination_address_value(&self) -> core::net::Ipv6Addr {
self.destination_address
}
pub fn options_ref(&self) -> &NdpOptions {
&self.options
}
}
impl Layer for Redirect {
fn name(&self) -> &'static str {
"Redirect"
}
fn summary(&self) -> String {
format!(
"Redirect(target={}, destination={}, options={})",
self.target_address,
self.destination_address,
self.options.len()
)
}
fn inspection_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = vec![
("target_address", self.target_address.to_string()),
("destination_address", self.destination_address.to_string()),
("option_count", self.options.len().to_string()),
];
for (index, option) in self.options.iter().enumerate() {
fields.push((option_field_name(index), option.to_string()));
}
fields
}
fn encoded_len(&self) -> usize {
REDIRECT_BODY_FIXED_LEN + self.options.encoded_len().unwrap_or(0)
}
fn compile(&self, _ctx: &LayerContext<'_>, out: &mut Vec<u8>) -> Result<()> {
out.extend_from_slice(&self.target_address.octets());
out.extend_from_slice(&self.destination_address.octets());
self.options.encode_into(out)?;
Ok(())
}
impl_layer_object!(Redirect);
}
impl_layer_div!(Redirect);
impl Icmpv6 {
pub fn redirect(target: core::net::Ipv6Addr, destination: core::net::Ipv6Addr) -> Packet {
Self::redirect_body(Redirect::new(target, destination))
}
fn redirect_body(body: Redirect) -> Packet {
Self::new().icmp_type(ICMPV6_REDIRECT).code(0) / body
}
}
pub(crate) fn decode_redirect(bytes: &[u8]) -> Result<Redirect> {
if bytes.len() < REDIRECT_BODY_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"icmpv6.redirect.addresses",
REDIRECT_BODY_FIXED_LEN,
bytes.len(),
));
}
let mut target = [0u8; REDIRECT_ADDRESS_LEN];
target.copy_from_slice(&bytes[..REDIRECT_ADDRESS_LEN]);
let mut destination = [0u8; REDIRECT_ADDRESS_LEN];
destination.copy_from_slice(&bytes[REDIRECT_ADDRESS_LEN..REDIRECT_BODY_FIXED_LEN]);
let options = NdpOptions::decode(&bytes[REDIRECT_BODY_FIXED_LEN..])?;
Ok(Redirect {
target_address: core::net::Ipv6Addr::from(target),
destination_address: core::net::Ipv6Addr::from(destination),
options,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocols::icmp::{Icmpv6Body, NDP_OPT_SOURCE_LINK_LAYER_ADDR};
use crate::{Ipv6, MacAddr, NetworkLayer, Packet};
use core::net::Ipv6Addr;
fn link_local_src() -> Ipv6Addr {
Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x0001)
}
fn all_routers() -> Ipv6Addr {
Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0x0002)
}
fn doc_mac() -> MacAddr {
MacAddr::new([0x00, 0x00, 0x5e, 0x00, 0x53, 0x2a])
}
#[test]
fn router_solicitation_with_slla_round_trips() {
let packet = Ipv6::new()
.src(link_local_src())
.dst(all_routers())
.hlim(255)
/ Icmpv6::router_solicitation_with_source_link_layer(doc_mac());
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(bytes[40], ICMPV6_ROUTER_SOLICITATION);
assert_eq!(bytes[41], 0, "code is 0");
assert_eq!(&bytes[44..48], &[0, 0, 0, 0]);
assert_eq!(&bytes[48..50], &[NDP_OPT_SOURCE_LINK_LAYER_ADDR, 1]);
assert_eq!(&bytes[50..56], &doc_mac().octets());
assert_eq!(bytes.len(), 56);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
let icmpv6 = decoded.layer::<Icmpv6>().unwrap();
assert_eq!(icmpv6.icmp_type_value(), ICMPV6_ROUTER_SOLICITATION);
assert_eq!(icmpv6.code_value(), 0);
assert_eq!(
icmpv6.body(),
Icmpv6Body::RouterSolicitation { reserved: 0 }
);
let rs = decoded.layer::<RouterSolicitation>().unwrap();
assert_eq!(rs.options_ref().len(), 1);
let slla = &rs.options_ref().options()[0];
assert_eq!(slla.option_type(), NDP_OPT_SOURCE_LINK_LAYER_ADDR);
assert_eq!(slla.link_layer_address(), Some(doc_mac()));
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn empty_router_solicitation_round_trips() {
let packet = Ipv6::new()
.src(link_local_src())
.dst(all_routers())
.hlim(255)
/ Icmpv6::router_solicitation();
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(bytes.len(), 40 + 8);
assert_eq!(bytes[40], ICMPV6_ROUTER_SOLICITATION);
assert_eq!(&bytes[44..48], &[0, 0, 0, 0]);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
let rs = decoded.layer::<RouterSolicitation>().unwrap();
assert!(rs.options_ref().is_empty());
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn explicit_reserved_is_preserved() {
let packet = Ipv6::new().src(link_local_src()).dst(all_routers())
/ (Icmpv6::new()
.icmp_type(ICMPV6_ROUTER_SOLICITATION)
.code(0)
.rest_of_header([0xde, 0xad, 0xbe, 0xef])
/ RouterSolicitation::new());
let compiled = packet.compile().unwrap();
assert_eq!(&compiled.as_bytes()[44..48], &[0xde, 0xad, 0xbe, 0xef]);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, compiled.as_bytes()).unwrap();
assert_eq!(
decoded.layer::<Icmpv6>().unwrap().body(),
Icmpv6Body::RouterSolicitation {
reserved: 0xdead_beef
}
);
}
#[test]
fn router_solicitation_summary_and_show() {
let body =
RouterSolicitation::new().option(NdpOption::source_link_layer_address(doc_mac()));
assert_eq!(body.summary(), "RouterSolicitation(options=1)");
let option_field = body
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "option[0]")
.map(|(_, value)| value)
.expect("show() exposes the first option");
assert!(option_field.starts_with("Source Link-Layer Address"));
let header = Icmpv6::new().icmp_type(ICMPV6_ROUTER_SOLICITATION).code(0);
assert_eq!(
header.summary(),
"Icmpv6(type=router-solicitation(133), code=0, id=-, seq=-)"
);
let body_field = header
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "body")
.map(|(_, value)| value)
.expect("show() exposes a body field");
assert_eq!(body_field, "router-solicitation(reserved=0x00000000)");
}
#[test]
fn router_advertisement_defaults_are_documented() {
let packet = Ipv6::new()
.src(link_local_src())
.dst(all_routers())
.hlim(255)
/ Icmpv6::router_advertisement();
let bytes = packet.compile().unwrap();
let bytes = bytes.as_bytes();
assert_eq!(bytes[40], ICMPV6_ROUTER_ADVERTISEMENT);
assert_eq!(bytes[41], 0, "code is 0");
assert_eq!(bytes[44], ICMPV6_RA_DEFAULT_CUR_HOP_LIMIT);
assert_eq!(bytes[44], 64);
assert_eq!(bytes[45], 0, "M/O/reserved flags all clear by default");
assert_eq!(
u16::from_be_bytes([bytes[46], bytes[47]]),
ICMPV6_RA_DEFAULT_ROUTER_LIFETIME
);
assert_eq!(u16::from_be_bytes([bytes[46], bytes[47]]), 1800);
assert_eq!(&bytes[48..52], &[0, 0, 0, 0], "reachable time = 0");
assert_eq!(&bytes[52..56], &[0, 0, 0, 0], "retrans timer = 0");
assert_eq!(bytes.len(), 56);
}
#[test]
fn router_advertisement_with_option_round_trips() {
let body = RouterAdvertisement::new()
.reachable_time(30_000)
.retrans_timer(1_000)
.option(NdpOption::source_link_layer_address(doc_mac()));
let packet = Ipv6::new()
.src(link_local_src())
.dst(all_routers())
.hlim(255)
/ Icmpv6::router_advertisement_with(255, true, true, 1800, body);
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(bytes[40], ICMPV6_ROUTER_ADVERTISEMENT);
assert_eq!(bytes[41], 0);
assert_eq!(bytes[44], 255);
assert_eq!(bytes[45], ICMPV6_RA_FLAG_MANAGED | ICMPV6_RA_FLAG_OTHER);
assert_eq!(bytes[45], 0xc0);
assert_eq!(u16::from_be_bytes([bytes[46], bytes[47]]), 1800);
assert_eq!(
u32::from_be_bytes([bytes[48], bytes[49], bytes[50], bytes[51]]),
30_000
);
assert_eq!(
u32::from_be_bytes([bytes[52], bytes[53], bytes[54], bytes[55]]),
1_000
);
assert_eq!(&bytes[56..58], &[NDP_OPT_SOURCE_LINK_LAYER_ADDR, 1]);
assert_eq!(&bytes[58..64], &doc_mac().octets());
assert_eq!(bytes.len(), 64);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
let icmpv6 = decoded.layer::<Icmpv6>().unwrap();
assert_eq!(icmpv6.icmp_type_value(), ICMPV6_ROUTER_ADVERTISEMENT);
assert_eq!(
icmpv6.body(),
Icmpv6Body::RouterAdvertisement {
cur_hop_limit: 255,
managed: true,
other: true,
preference: Prf::Medium,
reserved_flags: 0,
router_lifetime: 1800,
}
);
let ra = decoded.layer::<RouterAdvertisement>().unwrap();
assert_eq!(ra.reachable_time_value(), 30_000);
assert_eq!(ra.retrans_timer_value(), 1_000);
assert_eq!(ra.options_ref().len(), 1);
assert_eq!(
ra.options_ref().options()[0].link_layer_address(),
Some(doc_mac())
);
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn router_advertisement_flag_combinations_are_independent() {
for (managed, other) in [(false, false), (true, false), (false, true), (true, true)] {
let packet = Ipv6::new().src(link_local_src()).dst(all_routers())
/ Icmpv6::router_advertisement_with(
64,
managed,
other,
1800,
RouterAdvertisement::new(),
);
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
let mut expected = 0u8;
if managed {
expected |= ICMPV6_RA_FLAG_MANAGED;
}
if other {
expected |= ICMPV6_RA_FLAG_OTHER;
}
assert_eq!(bytes[45], expected, "M={managed} O={other}");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
match decoded.layer::<Icmpv6>().unwrap().body() {
Icmpv6Body::RouterAdvertisement {
managed: m,
other: o,
reserved_flags,
..
} => {
assert_eq!(m, managed, "M decoded independently");
assert_eq!(o, other, "O decoded independently");
assert_eq!(reserved_flags, 0, "no reserved bits set");
}
other => panic!("expected RouterAdvertisement body, got {other:?}"),
}
}
}
#[test]
fn router_advertisement_reserved_bits_and_overrides_survive() {
let packet = Ipv6::new().src(link_local_src()).dst(all_routers())
/ (Icmpv6::new()
.icmp_type(ICMPV6_ROUTER_ADVERTISEMENT)
.code(0)
.rest_of_header([0x01, 0xff, 0xff, 0xff])
/ RouterAdvertisement::new());
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(&bytes[44..48], &[0x01, 0xff, 0xff, 0xff]);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
assert_eq!(
decoded.layer::<Icmpv6>().unwrap().body(),
Icmpv6Body::RouterAdvertisement {
cur_hop_limit: 0x01,
managed: true,
other: true,
preference: Prf::Low,
reserved_flags: ICMPV6_RA_FLAGS_RESERVED & !NDP_PRF_MASK,
router_lifetime: 0xffff,
}
);
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn router_advertisement_default_router_preference_round_trips() {
for prf in [Prf::Medium, Prf::High, Prf::Low] {
let packet = Ipv6::new().src(link_local_src()).dst(all_routers())
/ Icmpv6::router_advertisement_with_preference(
64,
true,
false,
prf,
1800,
RouterAdvertisement::new(),
);
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
let expected_flags = ICMPV6_RA_FLAG_MANAGED | prf.to_flag_bits();
assert_eq!(bytes[45], expected_flags, "flags byte for {prf:?}");
assert_eq!(bytes[45] & ICMPV6_RA_FLAG_OTHER, 0, "O stays clear");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
assert_eq!(
decoded.layer::<Icmpv6>().unwrap().body(),
Icmpv6Body::RouterAdvertisement {
cur_hop_limit: 64,
managed: true,
other: false,
preference: prf,
reserved_flags: 0,
router_lifetime: 1800,
},
"decoded preference and untouched M/O/reserved for {prf:?}"
);
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
}
#[test]
fn router_advertisement_with_prefix_and_mtu_round_trips() {
use crate::protocols::icmp::{NDP_OPT_MTU, NDP_OPT_PREFIX_INFORMATION};
let prefix = Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0);
let body = RouterAdvertisement::new()
.reachable_time(30_000)
.retrans_timer(1_000)
.options(NdpOptions::new().push(NdpOption::mtu(1500)).push(
NdpOption::prefix_information(prefix, 64, true, true, 2_592_000, 604_800),
));
let packet = Ipv6::new()
.src(link_local_src())
.dst(all_routers())
.hlim(255)
/ Icmpv6::router_advertisement_with(64, false, false, 1800, body);
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
let ra = decoded.layer::<RouterAdvertisement>().unwrap();
assert_eq!(ra.reachable_time_value(), 30_000);
assert_eq!(ra.retrans_timer_value(), 1_000);
assert_eq!(ra.options_ref().len(), 2);
let opts = ra.options_ref().options();
assert_eq!(opts[0].option_type(), NDP_OPT_MTU);
assert_eq!(opts[0].mtu_value(), Some(1500));
assert_eq!(opts[1].option_type(), NDP_OPT_PREFIX_INFORMATION);
assert_eq!(opts[1].prefix(), Some(prefix));
assert_eq!(opts[1].prefix_length(), Some(64));
assert_eq!(opts[1].prefix_on_link(), Some(true));
assert_eq!(opts[1].prefix_autonomous(), Some(true));
assert_eq!(opts[1].prefix_valid_lifetime(), Some(2_592_000));
assert_eq!(opts[1].prefix_preferred_lifetime(), Some(604_800));
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn router_advertisement_prefix_flag_independence() {
let prefix = Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0);
for (on_link, autonomous) in [(false, false), (true, false), (false, true), (true, true)] {
let body = RouterAdvertisement::new().option(NdpOption::prefix_information(
prefix, 64, on_link, autonomous, 86_400, 14_400,
));
let packet = Ipv6::new().src(link_local_src()).dst(all_routers())
/ Icmpv6::router_advertisement_with(64, false, false, 1800, body);
let bytes = packet.compile().unwrap();
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes()).unwrap();
let pi = &decoded
.layer::<RouterAdvertisement>()
.unwrap()
.options_ref()
.options()[0];
assert_eq!(
pi.prefix_on_link(),
Some(on_link),
"L independent (L={on_link} A={autonomous})"
);
assert_eq!(
pi.prefix_autonomous(),
Some(autonomous),
"A independent (L={on_link} A={autonomous})"
);
assert_eq!(pi.prefix_valid_lifetime(), Some(86_400));
assert_eq!(pi.prefix_preferred_lifetime(), Some(14_400));
}
}
#[test]
fn router_advertisement_wrong_prefix_length_survives() {
let prefix = Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0);
let body = RouterAdvertisement::new()
.option(NdpOption::prefix_information(prefix, 64, true, false, 0, 0).length(5));
let packet = Ipv6::new().src(link_local_src()).dst(all_routers())
/ Icmpv6::router_advertisement_with(64, false, false, 1800, body);
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(
bytes[56],
crate::protocols::icmp::NDP_OPT_PREFIX_INFORMATION
);
assert_eq!(
bytes[57], 5,
"deliberately-wrong option length survives compile()"
);
}
#[test]
fn router_advertisement_summary_and_show() {
let body = RouterAdvertisement::new()
.reachable_time(5)
.retrans_timer(7)
.option(NdpOption::source_link_layer_address(doc_mac()));
assert_eq!(
body.summary(),
"RouterAdvertisement(reachable=5, retrans=7, options=1)"
);
let header = Icmpv6::new()
.icmp_type(ICMPV6_ROUTER_ADVERTISEMENT)
.code(0)
.rest_of_header(router_advertisement_rest_of_header(
64,
ICMPV6_RA_FLAG_MANAGED,
1800,
));
assert_eq!(
header.summary(),
"Icmpv6(type=router-advertisement(134), code=0, id=-, seq=-)"
);
let body_field = header
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "body")
.map(|(_, value)| value)
.expect("show() exposes a body field");
assert_eq!(
body_field,
"router-advertisement(cur_hop_limit=64, M=true, O=false, prf=Medium, \
reserved_flags=0x00, router_lifetime=1800)"
);
}
fn doc_target() -> Ipv6Addr {
Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x0042)
}
fn solicited_node_multicast() -> Ipv6Addr {
Ipv6Addr::new(0xff02, 0, 0, 0, 0, 1, 0xff42, 0x0042)
}
#[test]
fn neighbor_solicitation_with_slla_round_trips() {
let target = doc_target();
let packet = Ipv6::new()
.src(link_local_src())
.dst(solicited_node_multicast())
.hlim(255)
/ Icmpv6::neighbor_solicitation_with_source_link_layer(target, doc_mac());
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(bytes[40], ICMPV6_NEIGHBOR_SOLICITATION);
assert_eq!(bytes[41], 0, "code is 0");
assert_eq!(&bytes[44..48], &[0, 0, 0, 0]);
assert_eq!(&bytes[48..64], &target.octets());
assert_eq!(&bytes[64..66], &[NDP_OPT_SOURCE_LINK_LAYER_ADDR, 1]);
assert_eq!(&bytes[66..72], &doc_mac().octets());
assert_eq!(bytes.len(), 72);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
let icmpv6 = decoded.layer::<Icmpv6>().unwrap();
assert_eq!(icmpv6.icmp_type_value(), ICMPV6_NEIGHBOR_SOLICITATION);
assert_eq!(icmpv6.code_value(), 0);
assert_eq!(
icmpv6.body(),
Icmpv6Body::NeighborSolicitation { reserved: 0 }
);
let ns = decoded.layer::<NeighborSolicitation>().unwrap();
assert_eq!(ns.target_address_value(), target);
assert_eq!(ns.options_ref().len(), 1);
let slla = &ns.options_ref().options()[0];
assert_eq!(slla.option_type(), NDP_OPT_SOURCE_LINK_LAYER_ADDR);
assert_eq!(slla.link_layer_address(), Some(doc_mac()));
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn dad_neighbor_solicitation_round_trips() {
let tentative = doc_target();
let packet = Ipv6::new()
.src(Ipv6Addr::UNSPECIFIED)
.dst(solicited_node_multicast())
.hlim(255)
/ Icmpv6::neighbor_solicitation(tentative);
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(&bytes[8..24], &Ipv6Addr::UNSPECIFIED.octets());
assert_eq!(bytes[40], ICMPV6_NEIGHBOR_SOLICITATION);
assert_eq!(&bytes[44..48], &[0, 0, 0, 0], "reserved word is zero");
assert_eq!(&bytes[48..64], &tentative.octets());
assert_eq!(bytes.len(), 40 + 8 + 16);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
assert_eq!(
decoded.layer::<Ipv6>().unwrap().source(),
Ipv6Addr::UNSPECIFIED
);
let ns = decoded.layer::<NeighborSolicitation>().unwrap();
assert_eq!(ns.target_address_value(), tentative);
assert!(
ns.options_ref().is_empty(),
"a DAD probe carries no Source Link-Layer Address option"
);
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn neighbor_solicitation_explicit_reserved_is_preserved() {
let target = doc_target();
let packet = Ipv6::new()
.src(link_local_src())
.dst(solicited_node_multicast())
/ (Icmpv6::new()
.icmp_type(ICMPV6_NEIGHBOR_SOLICITATION)
.code(0)
.rest_of_header([0xde, 0xad, 0xbe, 0xef])
/ NeighborSolicitation::new(target));
let compiled = packet.compile().unwrap();
assert_eq!(&compiled.as_bytes()[44..48], &[0xde, 0xad, 0xbe, 0xef]);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, compiled.as_bytes()).unwrap();
assert_eq!(
decoded.layer::<Icmpv6>().unwrap().body(),
Icmpv6Body::NeighborSolicitation {
reserved: 0xdead_beef
}
);
assert_eq!(
decoded
.layer::<NeighborSolicitation>()
.unwrap()
.target_address_value(),
target
);
}
#[test]
fn neighbor_solicitation_summary_and_show() {
let target = doc_target();
let body = NeighborSolicitation::new(target)
.option(NdpOption::source_link_layer_address(doc_mac()));
assert_eq!(
body.summary(),
format!("NeighborSolicitation(target={target}, options=1)")
);
let target_field = body
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "target_address")
.map(|(_, value)| value)
.expect("show() exposes the target address");
assert_eq!(target_field, target.to_string());
let option_field = body
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "option[0]")
.map(|(_, value)| value)
.expect("show() exposes the first option");
assert!(option_field.starts_with("Source Link-Layer Address"));
let header = Icmpv6::new()
.icmp_type(ICMPV6_NEIGHBOR_SOLICITATION)
.code(0);
assert_eq!(
header.summary(),
"Icmpv6(type=neighbor-solicitation(135), code=0, id=-, seq=-)"
);
let body_field = header
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "body")
.map(|(_, value)| value)
.expect("show() exposes a body field");
assert_eq!(body_field, "neighbor-solicitation(reserved=0x00000000)");
}
#[test]
fn neighbor_solicitation_short_target_is_structured_error() {
let err = decode_neighbor_solicitation(&[0u8; 8]).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
#[test]
fn neighbor_advertisement_with_tlla_round_trips() {
use crate::protocols::icmp::NDP_OPT_TARGET_LINK_LAYER_ADDR;
let target = doc_target();
let packet = Ipv6::new()
.src(link_local_src())
.dst(link_local_src())
.hlim(255)
/ Icmpv6::neighbor_advertisement_with_target_link_layer(
target,
doc_mac(),
false,
true,
true,
);
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(bytes[40], ICMPV6_NEIGHBOR_ADVERTISEMENT);
assert_eq!(bytes[41], 0, "code is 0");
assert_eq!(
bytes[44],
ICMPV6_NA_FLAG_SOLICITED | ICMPV6_NA_FLAG_OVERRIDE
);
assert_eq!(bytes[44], 0x60);
assert_eq!(&bytes[45..48], &[0, 0, 0], "29 reserved bits are zero");
assert_eq!(&bytes[48..64], &target.octets());
assert_eq!(&bytes[64..66], &[NDP_OPT_TARGET_LINK_LAYER_ADDR, 1]);
assert_eq!(&bytes[66..72], &doc_mac().octets());
assert_eq!(bytes.len(), 72);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
let icmpv6 = decoded.layer::<Icmpv6>().unwrap();
assert_eq!(icmpv6.icmp_type_value(), ICMPV6_NEIGHBOR_ADVERTISEMENT);
assert_eq!(icmpv6.code_value(), 0);
assert_eq!(
icmpv6.body(),
Icmpv6Body::NeighborAdvertisement {
router: false,
solicited: true,
override_flag: true,
reserved: 0,
}
);
let na = decoded.layer::<NeighborAdvertisement>().unwrap();
assert_eq!(na.target_address_value(), target);
assert_eq!(na.options_ref().len(), 1);
let tlla = &na.options_ref().options()[0];
assert_eq!(tlla.option_type(), NDP_OPT_TARGET_LINK_LAYER_ADDR);
assert_eq!(tlla.link_layer_address(), Some(doc_mac()));
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn neighbor_advertisement_flag_combinations_are_independent() {
let target = doc_target();
for router in [false, true] {
for solicited in [false, true] {
for override_flag in [false, true] {
let packet = Ipv6::new().src(link_local_src()).dst(link_local_src())
/ Icmpv6::neighbor_advertisement_with(
router,
solicited,
override_flag,
NeighborAdvertisement::new(target),
);
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
let mut expected = 0u8;
if router {
expected |= ICMPV6_NA_FLAG_ROUTER;
}
if solicited {
expected |= ICMPV6_NA_FLAG_SOLICITED;
}
if override_flag {
expected |= ICMPV6_NA_FLAG_OVERRIDE;
}
assert_eq!(
bytes[44], expected,
"R={router} S={solicited} O={override_flag}"
);
assert_eq!(bytes[44] & 0x1f, 0, "low five bits of byte 0 reserved");
assert_eq!(&bytes[45..48], &[0, 0, 0]);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
match decoded.layer::<Icmpv6>().unwrap().body() {
Icmpv6Body::NeighborAdvertisement {
router: r,
solicited: s,
override_flag: o,
reserved,
} => {
assert_eq!(r, router, "R decoded independently");
assert_eq!(s, solicited, "S decoded independently");
assert_eq!(o, override_flag, "O decoded independently");
assert_eq!(reserved, 0, "no reserved bits set");
}
other => panic!("expected NeighborAdvertisement body, got {other:?}"),
}
assert_eq!(
decoded
.layer::<NeighborAdvertisement>()
.unwrap()
.target_address_value(),
target
);
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
}
}
}
#[test]
fn neighbor_advertisement_reserved_bits_survive() {
let target = doc_target();
let packet = Ipv6::new().src(link_local_src()).dst(link_local_src())
/ (Icmpv6::new()
.icmp_type(ICMPV6_NEIGHBOR_ADVERTISEMENT)
.code(0)
.rest_of_header([0xff, 0xff, 0xff, 0xff])
/ NeighborAdvertisement::new(target));
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(&bytes[44..48], &[0xff, 0xff, 0xff, 0xff]);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
assert_eq!(
decoded.layer::<Icmpv6>().unwrap().body(),
Icmpv6Body::NeighborAdvertisement {
router: true,
solicited: true,
override_flag: true,
reserved: ICMPV6_NA_FLAGS_RESERVED,
}
);
assert_eq!(
decoded
.layer::<NeighborAdvertisement>()
.unwrap()
.target_address_value(),
target
);
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn neighbor_advertisement_defaults_are_documented() {
let target = doc_target();
let packet = Ipv6::new()
.src(link_local_src())
.dst(link_local_src())
.hlim(255)
/ Icmpv6::neighbor_advertisement(target);
let bytes = packet.compile().unwrap();
let bytes = bytes.as_bytes();
assert_eq!(bytes[40], ICMPV6_NEIGHBOR_ADVERTISEMENT);
assert_eq!(bytes[41], 0, "code is 0");
assert_eq!(
&bytes[44..48],
&[0, 0, 0, 0],
"R/S/O and reserved all clear"
);
assert_eq!(&bytes[48..64], &target.octets());
assert_eq!(bytes.len(), 40 + 8 + 16);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
let na = decoded.layer::<NeighborAdvertisement>().unwrap();
assert_eq!(na.target_address_value(), target);
assert!(na.options_ref().is_empty());
}
#[test]
fn neighbor_advertisement_summary_and_show() {
let target = doc_target();
let body = NeighborAdvertisement::new(target)
.option(NdpOption::target_link_layer_address(doc_mac()));
assert_eq!(
body.summary(),
format!("NeighborAdvertisement(target={target}, options=1)")
);
let target_field = body
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "target_address")
.map(|(_, value)| value)
.expect("show() exposes the target address");
assert_eq!(target_field, target.to_string());
let option_field = body
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "option[0]")
.map(|(_, value)| value)
.expect("show() exposes the first option");
assert!(option_field.starts_with("Target Link-Layer Address"));
let header = Icmpv6::new()
.icmp_type(ICMPV6_NEIGHBOR_ADVERTISEMENT)
.code(0)
.rest_of_header(neighbor_advertisement_rest_of_header(false, true, true));
assert_eq!(
header.summary(),
"Icmpv6(type=neighbor-advertisement(136), code=0, id=-, seq=-)"
);
let body_field = header
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "body")
.map(|(_, value)| value)
.expect("show() exposes a body field");
assert_eq!(
body_field,
"neighbor-advertisement(R=false, S=true, O=true, reserved=0x00000000)"
);
}
#[test]
fn neighbor_advertisement_short_target_is_structured_error() {
let err = decode_neighbor_advertisement(&[0u8; 8]).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
fn redirect_target() -> Ipv6Addr {
Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x00ff)
}
fn redirect_destination() -> Ipv6Addr {
Ipv6Addr::new(0x2001, 0x0db8, 3, 0, 0, 0, 0, 0x0064)
}
#[test]
fn redirect_with_redirected_header_round_trips() {
use crate::protocols::icmp::{NDP_OPT_REDIRECTED_HEADER, NDP_OPT_TARGET_LINK_LAYER_ADDR};
let target = redirect_target();
let destination = redirect_destination();
let original = [
0x60, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3a, 0x40, 0xde, 0xad, 0xbe, 0xef, ];
let packet = Ipv6::new()
.src(redirect_target())
.dst(link_local_src())
.hlim(255)
/ (Icmpv6::new().icmp_type(ICMPV6_REDIRECT).code(0)
/ Redirect::new(target, destination)
.option(NdpOption::target_link_layer_address(doc_mac()))
.option(NdpOption::redirected_header(&original)));
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(bytes[40], ICMPV6_REDIRECT);
assert_eq!(bytes[41], 0, "code is 0");
assert_eq!(&bytes[44..48], &[0, 0, 0, 0]);
assert_eq!(&bytes[48..64], &target.octets());
assert_eq!(&bytes[64..80], &destination.octets());
assert_eq!(&bytes[80..82], &[NDP_OPT_TARGET_LINK_LAYER_ADDR, 1]);
assert_eq!(&bytes[82..88], &doc_mac().octets());
assert_eq!(&bytes[88..90], &[NDP_OPT_REDIRECTED_HEADER, 3]);
assert_eq!(&bytes[90..96], &[0, 0, 0, 0, 0, 0]);
assert_eq!(&bytes[96..108], &original);
assert_eq!(&bytes[108..112], &[0, 0, 0, 0]);
assert_eq!(bytes.len(), 112);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
let icmpv6 = decoded.layer::<Icmpv6>().unwrap();
assert_eq!(icmpv6.icmp_type_value(), ICMPV6_REDIRECT);
assert_eq!(icmpv6.code_value(), 0);
assert_eq!(icmpv6.body(), Icmpv6Body::Redirect { reserved: 0 });
let redirect = decoded.layer::<Redirect>().unwrap();
assert_eq!(redirect.target_address_value(), target);
assert_eq!(redirect.destination_address_value(), destination);
assert_eq!(redirect.options_ref().len(), 2);
let tlla = &redirect.options_ref().options()[0];
assert_eq!(tlla.option_type(), NDP_OPT_TARGET_LINK_LAYER_ADDR);
assert_eq!(tlla.link_layer_address(), Some(doc_mac()));
let rh = &redirect.options_ref().options()[1];
assert_eq!(rh.option_type(), NDP_OPT_REDIRECTED_HEADER);
let mut expected_embedded = original.to_vec();
expected_embedded.extend_from_slice(&[0, 0, 0, 0]);
assert_eq!(rh.redirected_header_data(), Some(&expected_embedded[..]));
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn redirect_defaults_and_target_equals_destination() {
let neighbor = redirect_destination();
let packet = Ipv6::new()
.src(redirect_target())
.dst(link_local_src())
.hlim(255)
/ Icmpv6::redirect(neighbor, neighbor);
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(bytes[40], ICMPV6_REDIRECT);
assert_eq!(bytes[41], 0, "code is 0");
assert_eq!(&bytes[44..48], &[0, 0, 0, 0], "reserved word zero");
assert_eq!(&bytes[48..64], &neighbor.octets());
assert_eq!(&bytes[64..80], &neighbor.octets());
assert_eq!(bytes.len(), 40 + 8 + 16 + 16);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
let redirect = decoded.layer::<Redirect>().unwrap();
assert_eq!(redirect.target_address_value(), neighbor);
assert_eq!(redirect.destination_address_value(), neighbor);
assert!(redirect.options_ref().is_empty());
assert_eq!(decoded.compile().unwrap().as_bytes(), bytes);
}
#[test]
fn redirect_explicit_reserved_is_preserved() {
let target = redirect_target();
let destination = redirect_destination();
let packet = Ipv6::new().src(redirect_target()).dst(link_local_src())
/ (Icmpv6::new()
.icmp_type(ICMPV6_REDIRECT)
.code(0)
.rest_of_header([0xca, 0xfe, 0xf0, 0x0d])
/ Redirect::new(target, destination));
let compiled = packet.compile().unwrap();
let bytes = compiled.as_bytes();
assert_eq!(&bytes[44..48], &[0xca, 0xfe, 0xf0, 0x0d]);
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes).unwrap();
assert_eq!(
decoded.layer::<Icmpv6>().unwrap().body(),
Icmpv6Body::Redirect {
reserved: 0xcafe_f00d
}
);
let redirect = decoded.layer::<Redirect>().unwrap();
assert_eq!(redirect.target_address_value(), target);
assert_eq!(redirect.destination_address_value(), destination);
}
#[test]
fn redirect_summary_and_show() {
let target = redirect_target();
let destination = redirect_destination();
let body = Redirect::new(target, destination)
.option(NdpOption::target_link_layer_address(doc_mac()));
assert_eq!(
body.summary(),
format!("Redirect(target={target}, destination={destination}, options=1)")
);
let target_field = body
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "target_address")
.map(|(_, value)| value)
.expect("show() exposes the target address");
assert_eq!(target_field, target.to_string());
let destination_field = body
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "destination_address")
.map(|(_, value)| value)
.expect("show() exposes the destination address");
assert_eq!(destination_field, destination.to_string());
let option_field = body
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "option[0]")
.map(|(_, value)| value)
.expect("show() exposes the first option");
assert!(option_field.starts_with("Target Link-Layer Address"));
let header = Icmpv6::new().icmp_type(ICMPV6_REDIRECT).code(0);
assert_eq!(
header.summary(),
"Icmpv6(type=redirect(137), code=0, id=-, seq=-)"
);
let body_field = header
.inspection_fields()
.into_iter()
.find(|(name, _)| *name == "body")
.map(|(_, value)| value)
.expect("show() exposes a body field");
assert_eq!(body_field, "redirect(reserved=0x00000000)");
}
#[test]
fn redirect_short_addresses_is_structured_error() {
let err = decode_redirect(&[0u8; 16]).unwrap_err();
assert!(matches!(err, CrafterError::BufferTooShort { .. }));
}
}