use byteorder::{ByteOrder, NetworkEndian};
use core::{cmp, fmt};
use super::{Error, Result};
use crate::phy::ChecksumCapabilities;
use crate::wire::ip::checksum;
use crate::wire::MldRepr;
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
use crate::wire::NdiscRepr;
#[cfg(feature = "proto-rpl")]
use crate::wire::RplRepr;
use crate::wire::{IpAddress, IpProtocol, Ipv6Packet, Ipv6Repr};
use crate::wire::{IPV6_HEADER_LEN, IPV6_MIN_MTU};
const MAX_ERROR_PACKET_LEN: usize = IPV6_MIN_MTU - IPV6_HEADER_LEN;
enum_with_unknown! {
pub enum Message(u8) {
DstUnreachable = 0x01,
PktTooBig = 0x02,
TimeExceeded = 0x03,
ParamProblem = 0x04,
EchoRequest = 0x80,
EchoReply = 0x81,
MldQuery = 0x82,
RouterSolicit = 0x85,
RouterAdvert = 0x86,
NeighborSolicit = 0x87,
NeighborAdvert = 0x88,
Redirect = 0x89,
MldReport = 0x8f,
RplControl = 0x9b,
}
}
impl Message {
pub fn is_error(&self) -> bool {
(u8::from(*self) & 0x80) != 0x80
}
pub const fn is_ndisc(&self) -> bool {
match *self {
Message::RouterSolicit
| Message::RouterAdvert
| Message::NeighborSolicit
| Message::NeighborAdvert
| Message::Redirect => true,
_ => false,
}
}
pub const fn is_mld(&self) -> bool {
match *self {
Message::MldQuery | Message::MldReport => true,
_ => false,
}
}
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Message::DstUnreachable => write!(f, "destination unreachable"),
Message::PktTooBig => write!(f, "packet too big"),
Message::TimeExceeded => write!(f, "time exceeded"),
Message::ParamProblem => write!(f, "parameter problem"),
Message::EchoReply => write!(f, "echo reply"),
Message::EchoRequest => write!(f, "echo request"),
Message::RouterSolicit => write!(f, "router solicitation"),
Message::RouterAdvert => write!(f, "router advertisement"),
Message::NeighborSolicit => write!(f, "neighbor solicitation"),
Message::NeighborAdvert => write!(f, "neighbor advert"),
Message::Redirect => write!(f, "redirect"),
Message::MldQuery => write!(f, "multicast listener query"),
Message::MldReport => write!(f, "multicast listener report"),
Message::RplControl => write!(f, "RPL control message"),
Message::Unknown(id) => write!(f, "{id}"),
}
}
}
enum_with_unknown! {
pub enum DstUnreachable(u8) {
NoRoute = 0,
AdminProhibit = 1,
BeyondScope = 2,
AddrUnreachable = 3,
PortUnreachable = 4,
FailedPolicy = 5,
RejectRoute = 6
}
}
impl fmt::Display for DstUnreachable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
DstUnreachable::NoRoute => write!(f, "no route to destination"),
DstUnreachable::AdminProhibit => write!(
f,
"communication with destination administratively prohibited"
),
DstUnreachable::BeyondScope => write!(f, "beyond scope of source address"),
DstUnreachable::AddrUnreachable => write!(f, "address unreachable"),
DstUnreachable::PortUnreachable => write!(f, "port unreachable"),
DstUnreachable::FailedPolicy => {
write!(f, "source address failed ingress/egress policy")
}
DstUnreachable::RejectRoute => write!(f, "reject route to destination"),
DstUnreachable::Unknown(id) => write!(f, "{id}"),
}
}
}
enum_with_unknown! {
pub enum ParamProblem(u8) {
ErroneousHdrField = 0,
UnrecognizedNxtHdr = 1,
UnrecognizedOption = 2
}
}
impl fmt::Display for ParamProblem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParamProblem::ErroneousHdrField => write!(f, "erroneous header field."),
ParamProblem::UnrecognizedNxtHdr => write!(f, "unrecognized next header type."),
ParamProblem::UnrecognizedOption => write!(f, "unrecognized IPv6 option."),
ParamProblem::Unknown(id) => write!(f, "{id}"),
}
}
}
enum_with_unknown! {
pub enum TimeExceeded(u8) {
HopLimitExceeded = 0,
FragReassemExceeded = 1
}
}
impl fmt::Display for TimeExceeded {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TimeExceeded::HopLimitExceeded => write!(f, "hop limit exceeded in transit"),
TimeExceeded::FragReassemExceeded => write!(f, "fragment reassembly time exceeded"),
TimeExceeded::Unknown(id) => write!(f, "{id}"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Packet<T: AsRef<[u8]>> {
pub(super) buffer: T,
}
pub(super) mod field {
use crate::wire::field::*;
pub const TYPE: usize = 0;
pub const CODE: usize = 1;
pub const CHECKSUM: Field = 2..4;
pub const UNUSED: Field = 4..8;
pub const MTU: Field = 4..8;
pub const POINTER: Field = 4..8;
pub const ECHO_IDENT: Field = 4..6;
pub const ECHO_SEQNO: Field = 6..8;
pub const HEADER_END: usize = 8;
pub const CUR_HOP_LIMIT: usize = 4;
pub const ROUTER_FLAGS: usize = 5;
pub const ROUTER_LT: Field = 6..8;
pub const REACHABLE_TM: Field = 8..12;
pub const RETRANS_TM: Field = 12..16;
pub const TARGET_ADDR: Field = 8..24;
pub const NEIGH_FLAGS: usize = 4;
pub const DEST_ADDR: Field = 24..40;
pub const MAX_RESP_CODE: Field = 4..6;
pub const QUERY_RESV: Field = 6..8;
pub const QUERY_MCAST_ADDR: Field = 8..24;
pub const SQRV: usize = 24;
pub const QQIC: usize = 25;
pub const QUERY_NUM_SRCS: Field = 26..28;
pub const RECORD_RESV: Field = 4..6;
pub const NR_MCAST_RCRDS: Field = 6..8;
pub const RECORD_TYPE: usize = 0;
pub const AUX_DATA_LEN: usize = 1;
pub const RECORD_NUM_SRCS: Field = 2..4;
pub const RECORD_MCAST_ADDR: Field = 4..20;
}
impl<T: AsRef<[u8]>> Packet<T> {
pub const fn new_unchecked(buffer: T) -> Packet<T> {
Packet { buffer }
}
pub fn new_checked(buffer: T) -> Result<Packet<T>> {
let packet = Self::new_unchecked(buffer);
packet.check_len()?;
Ok(packet)
}
pub fn check_len(&self) -> Result<()> {
let len = self.buffer.as_ref().len();
if len < 4 {
return Err(Error);
}
match self.msg_type() {
Message::DstUnreachable
| Message::PktTooBig
| Message::TimeExceeded
| Message::ParamProblem
| Message::EchoRequest
| Message::EchoReply
| Message::MldQuery
| Message::RouterSolicit
| Message::RouterAdvert
| Message::NeighborSolicit
| Message::NeighborAdvert
| Message::Redirect
| Message::MldReport => {
if len < field::HEADER_END || len < self.header_len() {
return Err(Error);
}
}
#[cfg(feature = "proto-rpl")]
Message::RplControl => match super::rpl::RplControlMessage::from(self.msg_code()) {
super::rpl::RplControlMessage::DodagInformationSolicitation => {
if len < 6 {
return Err(Error);
}
}
super::rpl::RplControlMessage::DodagInformationObject => {
if len < 28 {
return Err(Error);
}
}
super::rpl::RplControlMessage::DestinationAdvertisementObject => {
if len < 8 || (self.dao_dodag_id_present() && len < 24) {
return Err(Error);
}
}
super::rpl::RplControlMessage::DestinationAdvertisementObjectAck => {
if len < 8 || (self.dao_dodag_id_present() && len < 24) {
return Err(Error);
}
}
super::rpl::RplControlMessage::SecureDodagInformationSolicitation
| super::rpl::RplControlMessage::SecureDodagInformationObject
| super::rpl::RplControlMessage::SecureDestinationAdvertisementObject
| super::rpl::RplControlMessage::SecureDestinationAdvertisementObjectAck
| super::rpl::RplControlMessage::ConsistencyCheck => return Err(Error),
super::rpl::RplControlMessage::Unknown(_) => return Err(Error),
},
#[cfg(not(feature = "proto-rpl"))]
Message::RplControl => return Err(Error),
Message::Unknown(_) => return Err(Error),
}
Ok(())
}
pub fn into_inner(self) -> T {
self.buffer
}
#[inline]
pub fn msg_type(&self) -> Message {
let data = self.buffer.as_ref();
Message::from(data[field::TYPE])
}
#[inline]
pub fn msg_code(&self) -> u8 {
let data = self.buffer.as_ref();
data[field::CODE]
}
#[inline]
pub fn checksum(&self) -> u16 {
let data = self.buffer.as_ref();
NetworkEndian::read_u16(&data[field::CHECKSUM])
}
#[inline]
pub fn echo_ident(&self) -> u16 {
let data = self.buffer.as_ref();
NetworkEndian::read_u16(&data[field::ECHO_IDENT])
}
#[inline]
pub fn echo_seq_no(&self) -> u16 {
let data = self.buffer.as_ref();
NetworkEndian::read_u16(&data[field::ECHO_SEQNO])
}
#[inline]
pub fn pkt_too_big_mtu(&self) -> u32 {
let data = self.buffer.as_ref();
NetworkEndian::read_u32(&data[field::MTU])
}
#[inline]
pub fn param_problem_ptr(&self) -> u32 {
let data = self.buffer.as_ref();
NetworkEndian::read_u32(&data[field::POINTER])
}
pub fn header_len(&self) -> usize {
match self.msg_type() {
Message::DstUnreachable => field::UNUSED.end,
Message::PktTooBig => field::MTU.end,
Message::TimeExceeded => field::UNUSED.end,
Message::ParamProblem => field::POINTER.end,
Message::EchoRequest => field::ECHO_SEQNO.end,
Message::EchoReply => field::ECHO_SEQNO.end,
Message::RouterSolicit => field::UNUSED.end,
Message::RouterAdvert => field::RETRANS_TM.end,
Message::NeighborSolicit => field::TARGET_ADDR.end,
Message::NeighborAdvert => field::TARGET_ADDR.end,
Message::Redirect => field::DEST_ADDR.end,
Message::MldQuery => field::QUERY_NUM_SRCS.end,
Message::MldReport => field::NR_MCAST_RCRDS.end,
_ => field::CHECKSUM.end,
}
}
pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool {
if cfg!(fuzzing) {
return true;
}
let data = self.buffer.as_ref();
checksum::combine(&[
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Icmpv6, data.len() as u32),
checksum::data(data),
]) == !0
}
}
impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
#[inline]
pub fn payload(&self) -> &'a [u8] {
let data = self.buffer.as_ref();
&data[self.header_len()..]
}
}
impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
#[inline]
pub fn set_msg_type(&mut self, value: Message) {
let data = self.buffer.as_mut();
data[field::TYPE] = value.into()
}
#[inline]
pub fn set_msg_code(&mut self, value: u8) {
let data = self.buffer.as_mut();
data[field::CODE] = value
}
#[inline]
pub fn clear_reserved(&mut self) {
match self.msg_type() {
Message::RouterSolicit
| Message::NeighborSolicit
| Message::NeighborAdvert
| Message::Redirect => {
let data = self.buffer.as_mut();
NetworkEndian::write_u32(&mut data[field::UNUSED], 0);
}
Message::MldQuery => {
let data = self.buffer.as_mut();
NetworkEndian::write_u16(&mut data[field::QUERY_RESV], 0);
data[field::SQRV] &= 0xf;
}
Message::MldReport => {
let data = self.buffer.as_mut();
NetworkEndian::write_u16(&mut data[field::RECORD_RESV], 0);
}
ty => panic!("Message type `{ty}` does not have any reserved fields."),
}
}
#[inline]
pub fn set_checksum(&mut self, value: u16) {
let data = self.buffer.as_mut();
NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
}
#[inline]
pub fn set_echo_ident(&mut self, value: u16) {
let data = self.buffer.as_mut();
NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value)
}
#[inline]
pub fn set_echo_seq_no(&mut self, value: u16) {
let data = self.buffer.as_mut();
NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value)
}
#[inline]
pub fn set_pkt_too_big_mtu(&mut self, value: u32) {
let data = self.buffer.as_mut();
NetworkEndian::write_u32(&mut data[field::MTU], value)
}
#[inline]
pub fn set_param_problem_ptr(&mut self, value: u32) {
let data = self.buffer.as_mut();
NetworkEndian::write_u32(&mut data[field::POINTER], value)
}
pub fn fill_checksum(&mut self, src_addr: &IpAddress, dst_addr: &IpAddress) {
self.set_checksum(0);
let checksum = {
let data = self.buffer.as_ref();
!checksum::combine(&[
checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Icmpv6, data.len() as u32),
checksum::data(data),
])
};
self.set_checksum(checksum)
}
#[inline]
pub fn payload_mut(&mut self) -> &mut [u8] {
let range = self.header_len()..;
let data = self.buffer.as_mut();
&mut data[range]
}
}
impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
fn as_ref(&self) -> &[u8] {
self.buffer.as_ref()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Repr<'a> {
DstUnreachable {
reason: DstUnreachable,
header: Ipv6Repr,
data: &'a [u8],
},
PktTooBig {
mtu: u32,
header: Ipv6Repr,
data: &'a [u8],
},
TimeExceeded {
reason: TimeExceeded,
header: Ipv6Repr,
data: &'a [u8],
},
ParamProblem {
reason: ParamProblem,
pointer: u32,
header: Ipv6Repr,
data: &'a [u8],
},
EchoRequest {
ident: u16,
seq_no: u16,
data: &'a [u8],
},
EchoReply {
ident: u16,
seq_no: u16,
data: &'a [u8],
},
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
Ndisc(NdiscRepr<'a>),
Mld(MldRepr<'a>),
#[cfg(feature = "proto-rpl")]
Rpl(RplRepr<'a>),
}
impl<'a> Repr<'a> {
pub fn parse<T>(
src_addr: &IpAddress,
dst_addr: &IpAddress,
packet: &Packet<&'a T>,
checksum_caps: &ChecksumCapabilities,
) -> Result<Repr<'a>>
where
T: AsRef<[u8]> + ?Sized,
{
fn create_packet_from_payload<'a, T>(packet: &Packet<&'a T>) -> Result<(&'a [u8], Ipv6Repr)>
where
T: AsRef<[u8]> + ?Sized,
{
let ip_packet = if packet.payload().len() >= IPV6_HEADER_LEN {
Ipv6Packet::new_unchecked(packet.payload())
} else {
return Err(Error);
};
let payload = &packet.payload()[ip_packet.header_len()..];
let repr = Ipv6Repr {
src_addr: ip_packet.src_addr(),
dst_addr: ip_packet.dst_addr(),
next_header: ip_packet.next_header(),
payload_len: ip_packet.payload_len().into(),
hop_limit: ip_packet.hop_limit(),
};
Ok((payload, repr))
}
if checksum_caps.icmpv6.rx() && !packet.verify_checksum(src_addr, dst_addr) {
return Err(Error);
}
match (packet.msg_type(), packet.msg_code()) {
(Message::DstUnreachable, code) => {
let (payload, repr) = create_packet_from_payload(packet)?;
Ok(Repr::DstUnreachable {
reason: DstUnreachable::from(code),
header: repr,
data: payload,
})
}
(Message::PktTooBig, 0) => {
let (payload, repr) = create_packet_from_payload(packet)?;
Ok(Repr::PktTooBig {
mtu: packet.pkt_too_big_mtu(),
header: repr,
data: payload,
})
}
(Message::TimeExceeded, code) => {
let (payload, repr) = create_packet_from_payload(packet)?;
Ok(Repr::TimeExceeded {
reason: TimeExceeded::from(code),
header: repr,
data: payload,
})
}
(Message::ParamProblem, code) => {
let (payload, repr) = create_packet_from_payload(packet)?;
Ok(Repr::ParamProblem {
reason: ParamProblem::from(code),
pointer: packet.param_problem_ptr(),
header: repr,
data: payload,
})
}
(Message::EchoRequest, 0) => Ok(Repr::EchoRequest {
ident: packet.echo_ident(),
seq_no: packet.echo_seq_no(),
data: packet.payload(),
}),
(Message::EchoReply, 0) => Ok(Repr::EchoReply {
ident: packet.echo_ident(),
seq_no: packet.echo_seq_no(),
data: packet.payload(),
}),
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
(msg_type, 0) if msg_type.is_ndisc() => NdiscRepr::parse(packet).map(Repr::Ndisc),
(msg_type, 0) if msg_type.is_mld() => MldRepr::parse(packet).map(Repr::Mld),
#[cfg(feature = "proto-rpl")]
(Message::RplControl, _) => RplRepr::parse(packet).map(Repr::Rpl),
_ => Err(Error),
}
}
pub fn buffer_len(&self) -> usize {
match self {
&Repr::DstUnreachable { header, data, .. }
| &Repr::PktTooBig { header, data, .. }
| &Repr::TimeExceeded { header, data, .. }
| &Repr::ParamProblem { header, data, .. } => cmp::min(
field::UNUSED.end + header.buffer_len() + data.len(),
MAX_ERROR_PACKET_LEN,
),
&Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => {
field::ECHO_SEQNO.end + data.len()
}
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
&Repr::Ndisc(ndisc) => ndisc.buffer_len(),
&Repr::Mld(mld) => mld.buffer_len(),
#[cfg(feature = "proto-rpl")]
Repr::Rpl(rpl) => rpl.buffer_len(),
}
}
pub fn emit<T>(
&self,
src_addr: &IpAddress,
dst_addr: &IpAddress,
packet: &mut Packet<&mut T>,
checksum_caps: &ChecksumCapabilities,
) where
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
{
fn emit_contained_packet<T>(packet: &mut Packet<&mut T>, header: Ipv6Repr, data: &[u8])
where
T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
{
let icmp_header_len = packet.header_len();
let mut ip_packet = Ipv6Packet::new_unchecked(packet.payload_mut());
header.emit(&mut ip_packet);
let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
let payload_len = cmp::min(
data.len(),
MAX_ERROR_PACKET_LEN - icmp_header_len - IPV6_HEADER_LEN,
);
payload[..payload_len].copy_from_slice(&data[..payload_len]);
}
match *self {
Repr::DstUnreachable {
reason,
header,
data,
} => {
packet.set_msg_type(Message::DstUnreachable);
packet.set_msg_code(reason.into());
emit_contained_packet(packet, header, data);
}
Repr::PktTooBig { mtu, header, data } => {
packet.set_msg_type(Message::PktTooBig);
packet.set_msg_code(0);
packet.set_pkt_too_big_mtu(mtu);
emit_contained_packet(packet, header, data);
}
Repr::TimeExceeded {
reason,
header,
data,
} => {
packet.set_msg_type(Message::TimeExceeded);
packet.set_msg_code(reason.into());
emit_contained_packet(packet, header, data);
}
Repr::ParamProblem {
reason,
pointer,
header,
data,
} => {
packet.set_msg_type(Message::ParamProblem);
packet.set_msg_code(reason.into());
packet.set_param_problem_ptr(pointer);
emit_contained_packet(packet, header, data);
}
Repr::EchoRequest {
ident,
seq_no,
data,
} => {
packet.set_msg_type(Message::EchoRequest);
packet.set_msg_code(0);
packet.set_echo_ident(ident);
packet.set_echo_seq_no(seq_no);
let data_len = cmp::min(packet.payload_mut().len(), data.len());
packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len])
}
Repr::EchoReply {
ident,
seq_no,
data,
} => {
packet.set_msg_type(Message::EchoReply);
packet.set_msg_code(0);
packet.set_echo_ident(ident);
packet.set_echo_seq_no(seq_no);
let data_len = cmp::min(packet.payload_mut().len(), data.len());
packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len])
}
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
Repr::Ndisc(ndisc) => ndisc.emit(packet),
Repr::Mld(mld) => mld.emit(packet),
#[cfg(feature = "proto-rpl")]
Repr::Rpl(ref rpl) => rpl.emit(packet),
}
if checksum_caps.icmpv6.tx() {
packet.fill_checksum(src_addr, dst_addr);
} else {
packet.set_checksum(0);
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::wire::ip::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2};
use crate::wire::{IpProtocol, Ipv6Address, Ipv6Repr};
static ECHO_PACKET_BYTES: [u8; 12] = [
0x80, 0x00, 0x19, 0xb3, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff,
];
static ECHO_PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff];
static PKT_TOO_BIG_BYTES: [u8; 60] = [
0x02, 0x00, 0x0f, 0xc9, 0x00, 0x00, 0x05, 0xdc, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11,
0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
];
static PKT_TOO_BIG_IP_PAYLOAD: [u8; 52] = [
0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00,
0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
];
static PKT_TOO_BIG_UDP_PAYLOAD: [u8; 12] = [
0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff,
];
fn echo_packet_repr() -> Repr<'static> {
Repr::EchoRequest {
ident: 0x1234,
seq_no: 0xabcd,
data: &ECHO_PACKET_PAYLOAD,
}
}
fn too_big_packet_repr() -> Repr<'static> {
Repr::PktTooBig {
mtu: 1500,
header: Ipv6Repr {
src_addr: Ipv6Address([
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01,
]),
dst_addr: Ipv6Address([
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02,
]),
next_header: IpProtocol::Udp,
payload_len: 12,
hop_limit: 0x40,
},
data: &PKT_TOO_BIG_UDP_PAYLOAD,
}
}
#[test]
fn test_echo_deconstruct() {
let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
assert_eq!(packet.msg_type(), Message::EchoRequest);
assert_eq!(packet.msg_code(), 0);
assert_eq!(packet.checksum(), 0x19b3);
assert_eq!(packet.echo_ident(), 0x1234);
assert_eq!(packet.echo_seq_no(), 0xabcd);
assert_eq!(packet.payload(), &ECHO_PACKET_PAYLOAD[..]);
assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2));
assert!(!packet.msg_type().is_error());
}
#[test]
fn test_echo_construct() {
let mut bytes = vec![0xa5; 12];
let mut packet = Packet::new_unchecked(&mut bytes);
packet.set_msg_type(Message::EchoRequest);
packet.set_msg_code(0);
packet.set_echo_ident(0x1234);
packet.set_echo_seq_no(0xabcd);
packet
.payload_mut()
.copy_from_slice(&ECHO_PACKET_PAYLOAD[..]);
packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
assert_eq!(&*packet.into_inner(), &ECHO_PACKET_BYTES[..]);
}
#[test]
fn test_echo_repr_parse() {
let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]);
let repr = Repr::parse(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&packet,
&ChecksumCapabilities::default(),
)
.unwrap();
assert_eq!(repr, echo_packet_repr());
}
#[test]
fn test_echo_emit() {
let repr = echo_packet_repr();
let mut bytes = vec![0xa5; repr.buffer_len()];
let mut packet = Packet::new_unchecked(&mut bytes);
repr.emit(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&mut packet,
&ChecksumCapabilities::default(),
);
assert_eq!(&*packet.into_inner(), &ECHO_PACKET_BYTES[..]);
}
#[test]
fn test_too_big_deconstruct() {
let packet = Packet::new_unchecked(&PKT_TOO_BIG_BYTES[..]);
assert_eq!(packet.msg_type(), Message::PktTooBig);
assert_eq!(packet.msg_code(), 0);
assert_eq!(packet.checksum(), 0x0fc9);
assert_eq!(packet.pkt_too_big_mtu(), 1500);
assert_eq!(packet.payload(), &PKT_TOO_BIG_IP_PAYLOAD[..]);
assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2));
assert!(packet.msg_type().is_error());
}
#[test]
fn test_too_big_construct() {
let mut bytes = vec![0xa5; 60];
let mut packet = Packet::new_unchecked(&mut bytes);
packet.set_msg_type(Message::PktTooBig);
packet.set_msg_code(0);
packet.set_pkt_too_big_mtu(1500);
packet
.payload_mut()
.copy_from_slice(&PKT_TOO_BIG_IP_PAYLOAD[..]);
packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2);
assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]);
}
#[test]
fn test_too_big_repr_parse() {
let packet = Packet::new_unchecked(&PKT_TOO_BIG_BYTES[..]);
let repr = Repr::parse(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&packet,
&ChecksumCapabilities::default(),
)
.unwrap();
assert_eq!(repr, too_big_packet_repr());
}
#[test]
fn test_too_big_emit() {
let repr = too_big_packet_repr();
let mut bytes = vec![0xa5; repr.buffer_len()];
let mut packet = Packet::new_unchecked(&mut bytes);
repr.emit(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&mut packet,
&ChecksumCapabilities::default(),
);
assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]);
}
#[test]
fn test_buffer_length_is_truncated_to_mtu() {
let repr = Repr::PktTooBig {
mtu: 1280,
header: Ipv6Repr {
src_addr: Default::default(),
dst_addr: Default::default(),
next_header: IpProtocol::Tcp,
hop_limit: 64,
payload_len: 1280,
},
data: &vec![0; 9999],
};
assert_eq!(repr.buffer_len(), 1280 - IPV6_HEADER_LEN);
}
#[test]
fn test_mtu_truncated_payload_roundtrip() {
let ip_packet_repr = Ipv6Repr {
src_addr: Default::default(),
dst_addr: Default::default(),
next_header: IpProtocol::Tcp,
hop_limit: 64,
payload_len: IPV6_MIN_MTU - IPV6_HEADER_LEN,
};
let mut ip_packet = Ipv6Packet::new_unchecked(vec![0; IPV6_MIN_MTU]);
ip_packet_repr.emit(&mut ip_packet);
let repr1 = Repr::PktTooBig {
mtu: IPV6_MIN_MTU as u32,
header: ip_packet_repr,
data: &ip_packet.as_ref()[IPV6_HEADER_LEN..],
};
let repr1 = Repr::PktTooBig {
mtu: IPV6_MIN_MTU as u32,
header: ip_packet_repr,
data: &ip_packet.as_ref()[IPV6_HEADER_LEN..repr1.buffer_len() - field::UNUSED.end],
};
let mut data = vec![0; MAX_ERROR_PACKET_LEN];
let mut packet = Packet::new_unchecked(&mut data);
repr1.emit(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&mut packet,
&ChecksumCapabilities::default(),
);
let packet = Packet::new_unchecked(&data);
let repr2 = Repr::parse(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&packet,
&ChecksumCapabilities::default(),
)
.unwrap();
assert_eq!(repr1, repr2);
}
#[test]
fn test_truncated_payload_ipv6_header_parse_fails() {
let repr = too_big_packet_repr();
let mut bytes = vec![0xa5; repr.buffer_len()];
let mut packet = Packet::new_unchecked(&mut bytes);
repr.emit(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&mut packet,
&ChecksumCapabilities::default(),
);
let packet = Packet::new_unchecked(&bytes[..field::HEADER_END + IPV6_HEADER_LEN - 1]);
assert!(Repr::parse(
&MOCK_IP_ADDR_1,
&MOCK_IP_ADDR_2,
&packet,
&ChecksumCapabilities::ignored(),
)
.is_err());
}
}