Add Repr support for NDISC packets

Add Representation layer support for NDISC packets.
This commit is contained in:
Dan Robertson 2018-04-12 16:42:09 +00:00 committed by whitequark
parent 31ab2c788e
commit 13fe5734ec
3 changed files with 210 additions and 5 deletions

View File

@ -5,6 +5,7 @@ use {Error, Result};
use phy::ChecksumCapabilities;
use super::ip::checksum;
use super::{Ipv6Packet, Ipv6Repr};
use super::NdiscRepr;
enum_with_unknown! {
/// Internet protocol control message type.
@ -43,6 +44,18 @@ impl Message {
pub fn is_error(&self) -> bool {
(u8::from(*self) & 0x80) != 0x80
}
/// Return a boolean value indicating if the given message type
/// is an [NDISC] message type.
///
/// [NDISC]: https://tools.ietf.org/html/rfc4861
pub fn is_ndisc(&self) -> bool {
match *self {
Message::RouterSolicit | Message::RouterAdvert | Message::NeighborSolicit |
Message::NeighborAdvert | Message::Redirect => true,
_ => false,
}
}
}
impl fmt::Display for Message {
@ -462,6 +475,7 @@ pub enum Repr<'a> {
seq_no: u16,
data: &'a [u8]
},
Ndisc(NdiscRepr<'a>),
#[doc(hidden)]
__Nonexhaustive
}
@ -539,6 +553,9 @@ impl<'a> Repr<'a> {
data: packet.payload()
})
},
(msg_type, 0) if msg_type.is_ndisc() => {
NdiscRepr::parse(packet).map(|repr| Repr::Ndisc(repr))
},
_ => Err(Error::Unrecognized)
}
}
@ -554,6 +571,9 @@ impl<'a> Repr<'a> {
&Repr::EchoReply { data, .. } => {
field::ECHO_SEQNO.end + data.len()
},
&Repr::Ndisc(ndisc) => {
ndisc.buffer_len()
},
&Repr::__Nonexhaustive => unreachable!()
}
}
@ -618,6 +638,10 @@ impl<'a> Repr<'a> {
packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len])
},
&Repr::Ndisc(ndisc) => {
ndisc.emit(packet)
},
&Repr::__Nonexhaustive => unreachable!(),
}

View File

@ -179,6 +179,9 @@ pub use self::icmpv6::{Message as Icmpv6Message,
pub use self::icmpv6::{RouterFlags as NdiscRouterFlags,
NeighborFlags as NdiscNeighborFlags};
#[cfg(feature = "proto-ipv6")]
pub use self::ndisc::Repr as NdiscRepr;
#[cfg(feature = "proto-ipv6")]
pub use self::ndiscoption::{NdiscOption,
Repr as NdiscOptionRepr,

View File

@ -1,6 +1,7 @@
use byteorder::{ByteOrder, NetworkEndian};
use super::icmpv6::*;
use {Error, Result};
use super::icmpv6::{field, Message, NeighborFlags, Packet, RouterFlags};
use time::Duration;
use super::Ipv6Address;
@ -180,16 +181,176 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
}
}
/// A high-level representation of an Neighbor Discovery packet header.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Repr<'a> {
RouterSolicit {
options: &'a [u8]
},
RouterAdvert {
hop_limit: u8,
flags: RouterFlags,
router_lifetime: Duration,
reachable_time: Duration,
retrans_time: Duration,
options: &'a [u8]
},
NeighborSolicit {
target_addr: Ipv6Address,
options: &'a [u8]
},
NeighborAdvert {
flags: NeighborFlags,
target_addr: Ipv6Address,
options: &'a [u8]
},
Redirect {
target_addr: Ipv6Address,
dest_addr: Ipv6Address,
options: &'a [u8]
}
}
impl<'a> Repr<'a> {
/// Parse an NDISC packet and return a high-level representation of the
/// packet.
pub fn parse<T>(packet: &Packet<&'a T>)
-> Result<Repr<'a>>
where T: AsRef<[u8]> + ?Sized {
match packet.msg_type() {
Message::RouterSolicit => {
Ok(Repr::RouterSolicit {
options: packet.payload()
})
},
Message::RouterAdvert => {
Ok(Repr::RouterAdvert {
hop_limit: packet.current_hop_limit(),
flags: packet.router_flags(),
router_lifetime: packet.router_lifetime(),
reachable_time: packet.reachable_time(),
retrans_time: packet.retrans_time(),
options: packet.payload()
})
},
Message::NeighborSolicit => {
Ok(Repr::NeighborSolicit {
target_addr: packet.target_addr(),
options: packet.payload()
})
},
Message::NeighborAdvert => {
Ok(Repr::NeighborAdvert {
flags: packet.neighbor_flags(),
target_addr: packet.target_addr(),
options: packet.payload()
})
},
Message::Redirect => {
Ok(Repr::Redirect {
target_addr: packet.target_addr(),
dest_addr: packet.dest_addr(),
options: packet.payload()
})
},
_ => Err(Error::Unrecognized)
}
}
pub fn buffer_len(&self) -> usize {
match self {
&Repr::RouterSolicit { options, .. } => {
field::UNUSED.end + options.len()
},
&Repr::RouterAdvert { options, .. } => {
field::RETRANS_TM.end + options.len()
},
&Repr::NeighborSolicit { options, .. } | &Repr::NeighborAdvert { options, .. } => {
field::TARGET_ADDR.end + options.len()
},
&Repr::Redirect { options, .. } => {
field::DEST_ADDR.end + options.len()
}
}
}
pub fn emit<T>(&self, packet: &mut Packet<&mut T>)
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
match self {
&Repr::RouterSolicit { options } => {
packet.set_msg_type(Message::RouterSolicit);
packet.set_msg_code(0);
packet.clear_reserved();
packet.payload_mut().copy_from_slice(&options[..]);
},
&Repr::RouterAdvert { hop_limit, flags, router_lifetime, reachable_time, retrans_time, options } => {
packet.set_msg_type(Message::RouterAdvert);
packet.set_msg_code(0);
packet.set_current_hop_limit(hop_limit);
packet.set_router_flags(flags);
packet.set_router_lifetime(router_lifetime);
packet.set_reachable_time(reachable_time);
packet.set_retrans_time(retrans_time);
packet.payload_mut().copy_from_slice(&options[..]);
},
&Repr::NeighborSolicit { target_addr, options } => {
packet.set_msg_type(Message::NeighborSolicit);
packet.set_msg_code(0);
packet.clear_reserved();
packet.set_target_addr(target_addr);
packet.payload_mut().copy_from_slice(&options[..]);
},
&Repr::NeighborAdvert { flags, target_addr, options } => {
packet.set_msg_type(Message::NeighborAdvert);
packet.set_msg_code(0);
packet.clear_reserved();
packet.set_neighbor_flags(flags);
packet.set_target_addr(target_addr);
packet.payload_mut().copy_from_slice(&options[..]);
},
&Repr::Redirect { target_addr, dest_addr, options } => {
packet.set_msg_type(Message::Redirect);
packet.set_msg_code(0);
packet.clear_reserved();
packet.set_target_addr(target_addr);
packet.set_dest_addr(dest_addr);
packet.payload_mut().copy_from_slice(&options[..]);
},
}
}
}
#[cfg(test)]
mod test {
use phy::ChecksumCapabilities;
use super::*;
use wire::Icmpv6Repr;
static ROUTER_ADVERT_BYTES: [u8; 16] =
[0x86, 0x00, 0x2e, 0xf3,
static ROUTER_ADVERT_BYTES: [u8; 24] =
[0x86, 0x00, 0xa7, 0x35,
0x40, 0x80, 0x03, 0x84,
0x00, 0x00, 0x03, 0x84,
0x00, 0x00, 0x03, 0x84];
0x00, 0x00, 0x03, 0x84,
0x01, 0x01, 0x52, 0x54,
0x00, 0x12, 0x34, 0x56];
static SOURCE_LINK_LAYER_OPT: [u8; 8] =
[0x01, 0x01, 0x52, 0x54,
0x00, 0x12, 0x34, 0x56];
fn create_repr<'a>() -> Icmpv6Repr<'a> {
Icmpv6Repr::Ndisc(Repr::RouterAdvert {
hop_limit: 64,
flags: RouterFlags::MANAGED,
router_lifetime: Duration::from_secs(900),
reachable_time: Duration::from_millis(900),
retrans_time: Duration::from_millis(900),
options: &SOURCE_LINK_LAYER_OPT[..]
})
}
#[test]
fn test_router_advert_deconstruct() {
@ -201,11 +362,12 @@ mod test {
assert_eq!(packet.router_lifetime(), Duration::from_secs(900));
assert_eq!(packet.reachable_time(), Duration::from_millis(900));
assert_eq!(packet.retrans_time(), Duration::from_millis(900));
assert_eq!(packet.payload(), &SOURCE_LINK_LAYER_OPT[..]);
}
#[test]
fn test_router_advert_construct() {
let mut bytes = vec![0x0; 16];
let mut bytes = vec![0x0; 24];
let mut packet = Packet::new(&mut bytes);
packet.set_msg_type(Message::RouterAdvert);
packet.set_msg_code(0);
@ -214,7 +376,23 @@ mod test {
packet.set_router_lifetime(Duration::from_secs(900));
packet.set_reachable_time(Duration::from_millis(900));
packet.set_retrans_time(Duration::from_millis(900));
packet.payload_mut().copy_from_slice(&SOURCE_LINK_LAYER_OPT[..]);
packet.fill_checksum();
assert_eq!(&packet.into_inner()[..], &ROUTER_ADVERT_BYTES[..]);
}
#[test]
fn test_router_advert_repr_parse() {
let packet = Packet::new(&ROUTER_ADVERT_BYTES[..]);
assert_eq!(Icmpv6Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(),
create_repr());
}
#[test]
fn test_router_advert_repr_emit() {
let mut bytes = vec![0x2a; 24];
let mut packet = Packet::new(&mut bytes[..]);
create_repr().emit(&mut packet, &ChecksumCapabilities::default());
assert_eq!(&packet.into_inner()[..], &ROUTER_ADVERT_BYTES[..]);
}
}