use super::super::*;
use arrayvec::ArrayVec;
use std::slice::from_raw_parts;
pub mod icmpv6 {
pub const MAX_ICMPV6_BYTE_LEN: usize = u32::MAX as usize;
pub const TYPE_DST_UNREACH: u8 = 1;
pub const TYPE_PACKET_TOO_BIG: u8 = 2;
pub const TYPE_TIME_EXCEEDED: u8 = 3;
pub const TYPE_PARAMETER_PROBLEM: u8 = 4;
pub const TYPE_ECHO_REQUEST: u8 = 128;
pub const TYPE_ECHO_REPLY: u8 = 129;
pub const TYPE_MULTICAST_LISTENER_QUERY: u8 = 130;
pub const TYPE_MULTICAST_LISTENER_REPORT: u8 = 131;
pub const TYPE_MULTICAST_LISTENER_REDUCTION: u8 = 132;
pub const TYPE_ROUTER_SOLICITATION: u8 = 133;
pub const TYPE_ROUTER_ADVERTISEMENT: u8 = 134;
pub const TYPE_NEIGHBOR_SOLICITATION: u8 = 135;
pub const TYPE_NEIGHBOR_ADVERTISEMENT: u8 = 136;
pub const TYPE_REDIRECT_MESSAGE: u8 = 137;
pub const TYPE_ROUTER_RENUMBERING: u8 = 138;
pub const TYPE_INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION: u8 = 141;
pub const TYPE_INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT: u8 = 142;
pub const TYPE_EXT_ECHO_REQUEST: u8 = 160;
pub const TYPE_EXT_ECHO_REPLY: u8 = 161;
pub const CODE_DST_UNREACH_NO_ROUTE: u8 = 0;
pub const CODE_DST_UNREACH_PROHIBITED: u8 = 1;
pub const CODE_DST_UNREACH_BEYOND_SCOPE: u8 = 2;
pub const CODE_DST_UNREACH_ADDR: u8 = 3;
pub const CODE_DST_UNREACH_PORT: u8 = 4;
pub const CODE_DST_UNREACH_SOURCE_ADDRESS_FAILED_POLICY: u8 = 5;
pub const CODE_DST_UNREACH_REJECT_ROUTE_TO_DEST: u8 = 6;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DestUnreachableCode {
NoRoute = 0,
Prohibited = 1,
BeyondScope = 2,
Address = 3,
Port = 4,
SourceAddressFailedPolicy = 5,
RejectRoute = 6,
}
impl DestUnreachableCode {
pub fn from_u8(code_u8: u8) -> Option<DestUnreachableCode> {
use DestUnreachableCode::*;
match code_u8 {
CODE_DST_UNREACH_NO_ROUTE => Some(NoRoute),
CODE_DST_UNREACH_PROHIBITED => Some(Prohibited),
CODE_DST_UNREACH_BEYOND_SCOPE => Some(BeyondScope),
CODE_DST_UNREACH_ADDR => Some(Address),
CODE_DST_UNREACH_PORT => Some(Port),
CODE_DST_UNREACH_SOURCE_ADDRESS_FAILED_POLICY => Some(SourceAddressFailedPolicy),
CODE_DST_UNREACH_REJECT_ROUTE_TO_DEST => Some(RejectRoute),
_ => None,
}
}
#[inline]
pub fn code_u8(&self) -> u8 {
*self as u8
}
}
pub const CODE_TIME_EXCEEDED_HOP_LIMIT_EXCEEDED: u8 = 0;
pub const CODE_TIME_EXCEEDED_FRAGMENT_REASSEMBLY_TIME_EXCEEDED: u8 = 1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TimeExceededCode {
HopLimitExceeded = 0,
FragmentReassemblyTimeExceeded = 1,
}
impl TimeExceededCode {
#[inline]
pub fn from_u8(code_u8: u8) -> Option<TimeExceededCode> {
use TimeExceededCode::*;
match code_u8 {
CODE_TIME_EXCEEDED_HOP_LIMIT_EXCEEDED => Some(HopLimitExceeded),
CODE_TIME_EXCEEDED_FRAGMENT_REASSEMBLY_TIME_EXCEEDED => {
Some(FragmentReassemblyTimeExceeded)
}
_ => None,
}
}
#[inline]
pub fn code_u8(&self) -> u8 {
*self as u8
}
}
pub const CODE_PARAM_PROBLEM_ERR_HEADER_FIELD: u8 = 0;
pub const CODE_PARAM_PROBLEM_UNRECOG_NEXT_HEADER: u8 = 1;
pub const CODE_PARAM_PROBLEM_UNRECOG_IPV6_OPTION: u8 = 2;
pub const CODE_PARAM_PROBLEM_IPV6_FIRST_FRAG_INCOMP_HEADER_CHAIN: u8 = 3;
pub const CODE_PARAM_PROBLEM_SR_UPPER_LAYER_HEADER_ERROR: u8 = 4;
pub const CODE_PARAM_PROBLEM_UNRECOG_NEXT_HEADER_BY_INTERMEDIATE_NODE: u8 = 5;
pub const CODE_PARAM_PROBLEM_EXT_HEADER_TOO_BIG: u8 = 6;
pub const CODE_PARAM_PROBLEM_EXT_HEADER_CHAIN_TOO_LONG: u8 = 7;
pub const CODE_PARAM_PROBLEM_TOO_MANY_EXT_HEADERS: u8 = 8;
pub const CODE_PARAM_PROBLEM_TOO_MANY_OPTIONS_EXT_HEADER: u8 = 9;
pub const CODE_PARAM_PROBLEM_OPTION_TOO_BIG: u8 = 10;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParameterProblemCode {
ErroneousHeaderField = 0,
UnrecognizedNextHeader = 1,
UnrecognizedIpv6Option = 2,
Ipv6FirstFragmentIncompleteHeaderChain = 3,
SrUpperLayerHeaderError = 4,
UnrecognizedNextHeaderByIntermediateNode = 5,
ExtensionHeaderTooBig = 6,
ExtensionHeaderChainTooLong = 7,
TooManyExtensionHeaders = 8,
TooManyOptionsInExtensionHeader = 9,
OptionTooBig = 10,
}
impl ParameterProblemCode {
pub fn from_u8(code_u8: u8) -> Option<ParameterProblemCode> {
use ParameterProblemCode::*;
match code_u8 {
CODE_PARAM_PROBLEM_ERR_HEADER_FIELD => Some(ErroneousHeaderField),
CODE_PARAM_PROBLEM_UNRECOG_NEXT_HEADER => Some(UnrecognizedNextHeader),
CODE_PARAM_PROBLEM_UNRECOG_IPV6_OPTION => Some(UnrecognizedIpv6Option),
CODE_PARAM_PROBLEM_IPV6_FIRST_FRAG_INCOMP_HEADER_CHAIN => {
Some(Ipv6FirstFragmentIncompleteHeaderChain)
}
CODE_PARAM_PROBLEM_SR_UPPER_LAYER_HEADER_ERROR => Some(SrUpperLayerHeaderError),
CODE_PARAM_PROBLEM_UNRECOG_NEXT_HEADER_BY_INTERMEDIATE_NODE => {
Some(UnrecognizedNextHeaderByIntermediateNode)
}
CODE_PARAM_PROBLEM_EXT_HEADER_TOO_BIG => Some(ExtensionHeaderTooBig),
CODE_PARAM_PROBLEM_EXT_HEADER_CHAIN_TOO_LONG => Some(ExtensionHeaderChainTooLong),
CODE_PARAM_PROBLEM_TOO_MANY_EXT_HEADERS => Some(TooManyExtensionHeaders),
CODE_PARAM_PROBLEM_TOO_MANY_OPTIONS_EXT_HEADER => Some(TooManyOptionsInExtensionHeader),
CODE_PARAM_PROBLEM_OPTION_TOO_BIG => Some(OptionTooBig),
_ => None,
}
}
#[inline]
pub fn code_u8(&self) -> u8 {
*self as u8
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ParameterProblemHeader {
pub code: ParameterProblemCode,
pub pointer: u32,
}
}
use icmpv6::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Icmpv6Type {
Unknown {
type_u8: u8,
code_u8: u8,
bytes5to8: [u8; 4],
},
DestinationUnreachable(icmpv6::DestUnreachableCode),
PacketTooBig {
mtu: u32,
},
TimeExceeded(icmpv6::TimeExceededCode),
ParameterProblem(icmpv6::ParameterProblemHeader),
EchoRequest(IcmpEchoHeader),
EchoReply(IcmpEchoHeader),
}
impl Icmpv6Type {
#[inline]
pub fn type_u8(&self) -> u8 {
use Icmpv6Type::*;
match self {
Unknown {
type_u8,
code_u8: _,
bytes5to8: _,
} => *type_u8,
DestinationUnreachable(_) => TYPE_DST_UNREACH,
PacketTooBig { mtu: _ } => TYPE_PACKET_TOO_BIG,
TimeExceeded(_) => TYPE_TIME_EXCEEDED,
ParameterProblem(_) => TYPE_PARAMETER_PROBLEM,
EchoRequest(_) => TYPE_ECHO_REQUEST,
EchoReply(_) => TYPE_ECHO_REPLY,
}
}
#[inline]
pub fn code_u8(&self) -> u8 {
use Icmpv6Type::*;
match self {
Unknown {
type_u8: _,
code_u8,
bytes5to8: _,
} => *code_u8,
DestinationUnreachable(code) => code.code_u8(),
PacketTooBig { mtu: _ } => 0,
TimeExceeded(code) => code.code_u8(),
ParameterProblem(header) => header.code.code_u8(),
EchoRequest(_) => 0,
EchoReply(_) => 0,
}
}
pub fn calc_checksum(
&self,
source_ip: [u8; 16],
destination_ip: [u8; 16],
payload: &[u8],
) -> Result<u16, ValueError> {
let max_payload_len: usize = (std::u32::MAX as usize) - self.header_len();
if max_payload_len < payload.len() {
return Err(ValueError::Ipv6PayloadLengthTooLarge(payload.len()));
}
let msg_len = payload.len() + self.header_len();
let pseudo_sum = checksum::Sum16BitWords::new()
.add_16bytes(source_ip)
.add_16bytes(destination_ip)
.add_2bytes([0, ip_number::IPV6_ICMP])
.add_4bytes((msg_len as u32).to_be_bytes());
use Icmpv6Type::*;
Ok(
match self {
Unknown {
type_u8,
code_u8,
bytes5to8,
} => {
pseudo_sum.add_2bytes([*type_u8, *code_u8])
.add_4bytes(*bytes5to8)
},
DestinationUnreachable(header) => {
pseudo_sum.add_2bytes([TYPE_DST_UNREACH, header.code_u8()])
},
PacketTooBig { mtu } => {
pseudo_sum.add_2bytes([TYPE_PACKET_TOO_BIG, 0])
.add_4bytes(mtu.to_be_bytes())
},
TimeExceeded(code) => {
pseudo_sum.add_2bytes([TYPE_TIME_EXCEEDED, code.code_u8()])
},
ParameterProblem(header) => {
pseudo_sum.add_2bytes([TYPE_PARAMETER_PROBLEM, header.code.code_u8()])
.add_4bytes(header.pointer.to_be_bytes())
}
EchoRequest(echo) => {
pseudo_sum.add_2bytes([TYPE_ECHO_REQUEST, 0])
.add_4bytes(echo.to_bytes())
}
EchoReply(echo) => {
pseudo_sum.add_2bytes([TYPE_ECHO_REPLY, 0])
.add_4bytes(echo.to_bytes())
}
}
.add_slice(payload)
.ones_complement()
.to_be()
)
}
pub fn to_header(
self,
source_ip: [u8; 16],
destination_ip: [u8; 16],
payload: &[u8],
) -> Result<Icmpv6Header, ValueError> {
Ok(Icmpv6Header {
checksum: self.calc_checksum(source_ip, destination_ip, payload)?,
icmp_type: self,
})
}
pub fn header_len(&self) -> usize {
use Icmpv6Type::*;
match self {
Unknown {
type_u8: _,
code_u8: _,
bytes5to8: _,
}
| DestinationUnreachable(_)
| PacketTooBig{ mtu: _ }
| TimeExceeded(_)
| ParameterProblem(_)
| EchoRequest(_)
| EchoReply(_) => 8,
}
}
#[inline]
pub fn fixed_payload_size(&self) -> Option<usize> {
use Icmpv6Type::*;
match self {
Unknown {
type_u8: _,
code_u8: _,
bytes5to8: _,
}
| DestinationUnreachable(_)
| PacketTooBig{ mtu: _ }
| TimeExceeded(_)
| ParameterProblem(_)
| EchoRequest(_)
| EchoReply(_) => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Icmpv6Header {
pub icmp_type: Icmpv6Type,
pub checksum: u16,
}
impl Icmpv6Header {
pub const MIN_SERIALIZED_SIZE: usize = 8;
pub const MAX_SERIALIZED_SIZE: usize = 8 + 16 + 16;
#[inline]
pub fn new(icmp_type: Icmpv6Type) -> Icmpv6Header {
Icmpv6Header {
icmp_type,
checksum: 0, }
}
pub fn with_checksum(
icmp_type: Icmpv6Type,
source_ip: [u8; 16],
destination_ip: [u8; 16],
payload: &[u8],
) -> Result<Icmpv6Header, ValueError> {
let checksum = icmp_type.calc_checksum(source_ip, destination_ip, payload)?;
Ok(Icmpv6Header {
icmp_type,
checksum,
})
}
#[inline]
pub fn from_slice(slice: &[u8]) -> Result<(Icmpv6Header, &[u8]), ReadError> {
let header = Icmpv6Slice::from_slice(slice)?.header();
let len = header.header_len();
Ok((header, &slice[len..]))
}
pub fn read<T: io::Read + Sized>(reader: &mut T) -> Result<Icmpv6Header, ReadError> {
let mut start = [0u8;8];
reader.read_exact(&mut start)?;
Ok(Icmpv6Slice{
slice: &start
}.header())
}
pub fn write<T: io::Write + Sized>(&self, writer: &mut T) -> Result<(), WriteError> {
writer.write_all(&self.to_bytes()).map_err(WriteError::from)
}
#[inline]
pub fn header_len(&self) -> usize {
self.icmp_type.header_len()
}
#[inline]
pub fn fixed_payload_size(&self) -> Option<usize> {
self.icmp_type.fixed_payload_size()
}
pub fn update_checksum(
&mut self,
source_ip: [u8; 16],
destination_ip: [u8; 16],
payload: &[u8],
) -> Result<(), ValueError> {
self.checksum = self
.icmp_type
.calc_checksum(source_ip, destination_ip, payload)?;
Ok(())
}
#[inline]
pub fn to_bytes(&self) -> ArrayVec<u8, { Icmpv6Header::MAX_SERIALIZED_SIZE }> {
let checksum_be = self.checksum.to_be_bytes();
let return_trivial = |type_u8: u8, code_u8: u8| -> ArrayVec<u8, { Icmpv6Header::MAX_SERIALIZED_SIZE }> {
#[rustfmt::skip]
let mut re = ArrayVec::from([
type_u8, code_u8, checksum_be[0], checksum_be[1],
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
]);
unsafe {
re.set_len(8);
}
re
};
let return_4u8 = |type_u8: u8, code_u8: u8, bytes5to8: [u8;4]| -> ArrayVec<u8, { Icmpv6Header::MAX_SERIALIZED_SIZE }> {
#[rustfmt::skip]
let mut re = ArrayVec::from([
type_u8, code_u8, checksum_be[0], checksum_be[1],
bytes5to8[0], bytes5to8[1], bytes5to8[2], bytes5to8[3],
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
]);
unsafe {
re.set_len(8);
}
re
};
use Icmpv6Type::*;
match self.icmp_type {
Unknown {
type_u8,
code_u8,
bytes5to8,
} => {
return_4u8(type_u8, code_u8, bytes5to8)
},
DestinationUnreachable(header) => {
return_trivial(TYPE_DST_UNREACH, header.code_u8())
},
PacketTooBig { mtu } => {
return_4u8(TYPE_PACKET_TOO_BIG, 0, mtu.to_be_bytes())
},
TimeExceeded(code) => {
return_trivial(TYPE_TIME_EXCEEDED, code.code_u8())
},
ParameterProblem(header) => {
return_4u8(TYPE_PARAMETER_PROBLEM, header.code.code_u8(), header.pointer.to_be_bytes())
}
EchoRequest(echo) => {
return_4u8(TYPE_ECHO_REQUEST, 0, echo.to_bytes())
},
EchoReply(echo) => {
return_4u8(TYPE_ECHO_REPLY, 0, echo.to_bytes())
},
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Icmpv6Slice<'a> {
slice: &'a [u8],
}
impl<'a> Icmpv6Slice<'a> {
#[inline]
pub fn from_slice(slice: &'a [u8]) -> Result<Icmpv6Slice<'a>, ReadError> {
use crate::ReadError::*;
if slice.len() < Icmpv6Header::MIN_SERIALIZED_SIZE {
return Err(UnexpectedEndOfSlice(Icmpv6Header::MIN_SERIALIZED_SIZE));
}
if slice.len() > icmpv6::MAX_ICMPV6_BYTE_LEN {
return Err(Icmpv6PacketTooBig(slice.len()));
}
Ok(Icmpv6Slice { slice })
}
#[inline]
pub fn header(&self) -> Icmpv6Header {
Icmpv6Header {
icmp_type: self.icmp_type(),
checksum: self.checksum(),
}
}
#[inline]
pub fn header_len(&self) -> usize {
8
}
pub fn icmp_type(&self) -> Icmpv6Type {
use Icmpv6Type::*;
match self.type_u8() {
TYPE_DST_UNREACH => {
if let Some(code) = DestUnreachableCode::from_u8(self.code_u8()) {
return DestinationUnreachable(code);
}
}
TYPE_PACKET_TOO_BIG => {
if 0 == self.code_u8() {
return PacketTooBig {
mtu: u32::from_be_bytes(self.bytes5to8()),
};
}
}
TYPE_TIME_EXCEEDED => {
if let Some(code) = TimeExceededCode::from_u8(self.code_u8()) {
return TimeExceeded(code);
}
}
TYPE_PARAMETER_PROBLEM => {
if let Some(code) = ParameterProblemCode::from_u8(self.code_u8()) {
return ParameterProblem(
ParameterProblemHeader{
code,
pointer: u32::from_be_bytes(self.bytes5to8())
}
);
}
}
TYPE_ECHO_REQUEST => {
if 0 == self.code_u8() {
return EchoRequest(IcmpEchoHeader::from_bytes(self.bytes5to8()));
}
}
TYPE_ECHO_REPLY => {
if 0 == self.code_u8() {
return EchoReply(IcmpEchoHeader::from_bytes(self.bytes5to8()));
}
}
_ => {}
}
Unknown {
type_u8: self.type_u8(),
code_u8: self.code_u8(),
bytes5to8: self.bytes5to8(),
}
}
#[inline]
pub fn type_u8(&self) -> u8 {
unsafe { *self.slice.get_unchecked(0) }
}
#[inline]
pub fn code_u8(&self) -> u8 {
unsafe { *self.slice.get_unchecked(1) }
}
#[inline]
pub fn checksum(&self) -> u16 {
unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) }
}
pub fn is_checksum_valid(&self, source_ip: [u8; 16], destination_ip: [u8; 16]) -> bool {
checksum::Sum16BitWords::new()
.add_16bytes(source_ip)
.add_16bytes(destination_ip)
.add_4bytes((self.slice().len() as u32).to_be_bytes())
.add_2bytes([0, ip_number::IPV6_ICMP])
.add_slice(self.slice)
.ones_complement()
== 0
}
#[inline]
pub fn bytes5to8(&self) -> [u8; 4] {
unsafe {
[
*self.slice.get_unchecked(4),
*self.slice.get_unchecked(5),
*self.slice.get_unchecked(6),
*self.slice.get_unchecked(7),
]
}
}
#[inline]
pub fn slice(&self) -> &'a [u8] {
self.slice
}
#[inline]
pub fn payload(&self) -> &'a [u8] {
unsafe { from_raw_parts(self.slice.as_ptr().add(8), self.slice.len() - 8) }
}
}