NDISC: Improve the representation layer for NDISC

Each given NDISC packet type may only include one of a few options.
Instead of including the options in the NDISC repr as a &[u8] include
the packet types available options as `Option<T>`.

Closes: #194
Approved by: whitequark
This commit is contained in:
Dan Robertson 2018-04-20 16:03:56 +00:00 committed by Homu
parent 13fe5734ec
commit 867eb1b7f8
3 changed files with 196 additions and 62 deletions

View File

@ -186,6 +186,8 @@ pub use self::ndisc::Repr as NdiscRepr;
pub use self::ndiscoption::{NdiscOption,
Repr as NdiscOptionRepr,
Type as NdiscOptionType,
PrefixInformation as NdiscPrefixInformation,
RedirectedHeader as NdiscRedirectedHeader,
PrefixInfoFlags as NdiscPrefixInfoFlags};
pub use self::udp::{Packet as UdpPacket,

View File

@ -2,6 +2,8 @@ use byteorder::{ByteOrder, NetworkEndian};
use {Error, Result};
use super::icmpv6::{field, Message, NeighborFlags, Packet, RouterFlags};
use wire::{EthernetAddress, Ipv6Repr, Ipv6Packet};
use wire::{NdiscOption, NdiscOptionRepr, NdiscOptionType, NdiscPrefixInformation, NdiscRedirectedHeader};
use time::Duration;
use super::Ipv6Address;
@ -185,7 +187,7 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Repr<'a> {
RouterSolicit {
options: &'a [u8]
lladdr: Option<EthernetAddress>
},
RouterAdvert {
hop_limit: u8,
@ -193,21 +195,24 @@ pub enum Repr<'a> {
router_lifetime: Duration,
reachable_time: Duration,
retrans_time: Duration,
options: &'a [u8]
lladdr: Option<EthernetAddress>,
mtu: Option<u32>,
prefix_info: Option<NdiscPrefixInformation>
},
NeighborSolicit {
target_addr: Ipv6Address,
options: &'a [u8]
lladdr: Option<EthernetAddress>
},
NeighborAdvert {
flags: NeighborFlags,
target_addr: Ipv6Address,
options: &'a [u8]
lladdr: Option<EthernetAddress>
},
Redirect {
target_addr: Ipv6Address,
dest_addr: Ipv6Address,
options: &'a [u8]
lladdr: Option<EthernetAddress>,
redirected_hdr: Option<NdiscRedirectedHeader<'a>>
}
}
@ -219,38 +224,100 @@ impl<'a> Repr<'a> {
where T: AsRef<[u8]> + ?Sized {
match packet.msg_type() {
Message::RouterSolicit => {
Ok(Repr::RouterSolicit {
options: packet.payload()
})
let lladdr = if packet.payload().len() > 0 {
let opt = NdiscOption::new_checked(packet.payload())?;
match opt.option_type() {
NdiscOptionType::SourceLinkLayerAddr => Some(opt.link_layer_addr()),
_ => { return Err(Error::Unrecognized); }
}
} else {
None
};
Ok(Repr::RouterSolicit { lladdr })
},
Message::RouterAdvert => {
let mut offset = 0;
let (mut lladdr, mut mtu, mut prefix_info) = (None, None, None);
while packet.payload().len() - offset > 0 {
let pkt = NdiscOption::new_checked(&packet.payload()[offset..])?;
let opt = NdiscOptionRepr::parse(&pkt)?;
match opt {
NdiscOptionRepr::SourceLinkLayerAddr(addr) => lladdr = Some(addr),
NdiscOptionRepr::Mtu(val) => mtu = Some(val),
NdiscOptionRepr::PrefixInformation(info) => prefix_info = Some(info),
_ => { return Err(Error::Unrecognized); }
}
offset += opt.buffer_len();
}
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()
lladdr, mtu, prefix_info
})
},
Message::NeighborSolicit => {
let lladdr = if packet.payload().len() > 0 {
let opt = NdiscOption::new_checked(packet.payload())?;
match opt.option_type() {
NdiscOptionType::SourceLinkLayerAddr => Some(opt.link_layer_addr()),
_ => { return Err(Error::Unrecognized); }
}
} else {
None
};
Ok(Repr::NeighborSolicit {
target_addr: packet.target_addr(),
options: packet.payload()
target_addr: packet.target_addr(), lladdr
})
},
Message::NeighborAdvert => {
let lladdr = if packet.payload().len() > 0 {
let opt = NdiscOption::new_checked(packet.payload())?;
match opt.option_type() {
NdiscOptionType::TargetLinkLayerAddr => Some(opt.link_layer_addr()),
_ => { return Err(Error::Unrecognized); }
}
} else {
None
};
Ok(Repr::NeighborAdvert {
flags: packet.neighbor_flags(),
target_addr: packet.target_addr(),
options: packet.payload()
lladdr
})
},
Message::Redirect => {
let mut offset = 0;
let (mut lladdr, mut redirected_hdr) = (None, None);
while packet.payload().len() - offset > 0 {
let opt = NdiscOption::new_checked(&packet.payload()[offset..])?;
match opt.option_type() {
NdiscOptionType::SourceLinkLayerAddr => {
lladdr = Some(opt.link_layer_addr());
offset += 8;
},
NdiscOptionType::RedirectedHeader => {
if opt.data_len() < 6 {
return Err(Error::Truncated)
} else {
let ip_packet = Ipv6Packet::new(&opt.data()[offset + 8..]);
let ip_repr = Ipv6Repr::parse(&ip_packet)?;
let data = &opt.data()[offset + 8 + ip_repr.buffer_len()..];
redirected_hdr = Some(NdiscRedirectedHeader {
header: ip_repr, data
});
offset += 8 + ip_repr.buffer_len() + data.len();
}
}
_ => { return Err(Error::Unrecognized); }
}
}
Ok(Repr::Redirect {
target_addr: packet.target_addr(),
dest_addr: packet.dest_addr(),
options: packet.payload()
lladdr, redirected_hdr
})
},
_ => Err(Error::Unrecognized)
@ -259,17 +326,40 @@ impl<'a> Repr<'a> {
pub fn buffer_len(&self) -> usize {
match self {
&Repr::RouterSolicit { options, .. } => {
field::UNUSED.end + options.len()
&Repr::RouterSolicit { lladdr } => {
match lladdr {
Some(_) => field::UNUSED.end + 8,
None => field::UNUSED.end,
}
},
&Repr::RouterAdvert { options, .. } => {
field::RETRANS_TM.end + options.len()
&Repr::RouterAdvert { lladdr, mtu, prefix_info, .. } => {
let mut offset = 0;
if lladdr.is_some() {
offset += 8;
}
if mtu.is_some() {
offset += 8;
}
if prefix_info.is_some() {
offset += 32;
}
field::RETRANS_TM.end + offset
},
&Repr::NeighborSolicit { options, .. } | &Repr::NeighborAdvert { options, .. } => {
field::TARGET_ADDR.end + options.len()
&Repr::NeighborSolicit { lladdr, .. } | &Repr::NeighborAdvert { lladdr, .. } => {
match lladdr {
Some(_) => field::TARGET_ADDR.end + 8,
None => field::TARGET_ADDR.end,
}
},
&Repr::Redirect { options, .. } => {
field::DEST_ADDR.end + options.len()
&Repr::Redirect { lladdr, redirected_hdr, .. } => {
let mut offset = 0;
if lladdr.is_some() {
offset += 8;
}
if let Some(NdiscRedirectedHeader { header, data }) = redirected_hdr {
offset += 8 + header.buffer_len() + data.len();
}
field::DEST_ADDR.end + offset
}
}
}
@ -277,14 +367,18 @@ impl<'a> Repr<'a> {
pub fn emit<T>(&self, packet: &mut Packet<&mut T>)
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
match self {
&Repr::RouterSolicit { options } => {
&Repr::RouterSolicit { lladdr } => {
packet.set_msg_type(Message::RouterSolicit);
packet.set_msg_code(0);
packet.clear_reserved();
packet.payload_mut().copy_from_slice(&options[..]);
if let Some(lladdr) = lladdr {
let mut opt_pkt = NdiscOption::new(packet.payload_mut());
NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt);
}
},
&Repr::RouterAdvert { hop_limit, flags, router_lifetime, reachable_time, retrans_time, options } => {
&Repr::RouterAdvert { hop_limit, flags, router_lifetime, reachable_time,
retrans_time, lladdr, mtu, prefix_info } => {
packet.set_msg_type(Message::RouterAdvert);
packet.set_msg_code(0);
packet.set_current_hop_limit(hop_limit);
@ -292,33 +386,64 @@ impl<'a> Repr<'a> {
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[..]);
let mut offset = 0;
if let Some(lladdr) = lladdr {
let mut opt_pkt = NdiscOption::new(packet.payload_mut());
NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt);
offset += 8;
}
if let Some(mtu) = mtu {
let mut opt_pkt = NdiscOption::new(&mut packet.payload_mut()[offset..]);
NdiscOptionRepr::Mtu(mtu).emit(&mut opt_pkt);
offset += 8;
}
if let Some(prefix_info) = prefix_info {
let mut opt_pkt = NdiscOption::new(&mut packet.payload_mut()[offset..]);
NdiscOptionRepr::PrefixInformation(prefix_info).emit(&mut opt_pkt)
}
},
&Repr::NeighborSolicit { target_addr, options } => {
&Repr::NeighborSolicit { target_addr, lladdr } => {
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[..]);
if let Some(lladdr) = lladdr {
let mut opt_pkt = NdiscOption::new(packet.payload_mut());
NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt);
}
},
&Repr::NeighborAdvert { flags, target_addr, options } => {
&Repr::NeighborAdvert { flags, target_addr, lladdr } => {
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[..]);
if let Some(lladdr) = lladdr {
let mut opt_pkt = NdiscOption::new(packet.payload_mut());
NdiscOptionRepr::TargetLinkLayerAddr(lladdr).emit(&mut opt_pkt);
}
},
&Repr::Redirect { target_addr, dest_addr, options } => {
&Repr::Redirect { target_addr, dest_addr, lladdr, redirected_hdr } => {
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[..]);
let offset = match lladdr {
Some(lladdr) => {
let mut opt_pkt = NdiscOption::new(packet.payload_mut());
NdiscOptionRepr::TargetLinkLayerAddr(lladdr).emit(&mut opt_pkt);
8
},
None => 0,
};
if let Some(redirected_hdr) = redirected_hdr {
let mut opt_pkt = NdiscOption::new(&mut packet.payload_mut()[offset..]);
NdiscOptionRepr::RedirectedHeader(redirected_hdr).emit(&mut opt_pkt);
}
},
}
}
@ -348,7 +473,9 @@ mod test {
router_lifetime: Duration::from_secs(900),
reachable_time: Duration::from_millis(900),
retrans_time: Duration::from_millis(900),
options: &SOURCE_LINK_LAYER_OPT[..]
lladdr: Some(EthernetAddress([0x52, 0x54, 0x00, 0x12, 0x34, 0x56])),
mtu: None,
prefix_info: None
})
}

View File

@ -391,23 +391,28 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for NdiscOption<&'a T> {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct PrefixInformation {
pub prefix_len: u8,
pub flags: PrefixInfoFlags,
pub valid_lifetime: Duration,
pub preferred_lifetime: Duration,
pub prefix: Ipv6Address
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct RedirectedHeader<'a> {
pub header: Ipv6Repr,
pub data: &'a [u8]
}
/// A high-level representation of an NDISC Option.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Repr<'a> {
SourceLinkLayerAddr(EthernetAddress),
TargetLinkLayerAddr(EthernetAddress),
PrefixInformation {
prefix_len: u8,
flags: PrefixInfoFlags,
valid_lifetime: Duration,
preferred_lifetime: Duration,
prefix: Ipv6Address
},
RedirectedHeader {
header: Ipv6Repr,
data: &'a [u8]
},
PrefixInformation(PrefixInformation),
RedirectedHeader(RedirectedHeader<'a>),
Mtu(u32),
Unknown {
type_: u8,
@ -437,13 +442,13 @@ impl<'a> Repr<'a> {
},
Type::PrefixInformation => {
if opt.data_len() == 4 {
Ok(Repr::PrefixInformation {
Ok(Repr::PrefixInformation(PrefixInformation {
prefix_len: opt.prefix_len(),
flags: opt.prefix_flags(),
valid_lifetime: opt.valid_lifetime(),
preferred_lifetime: opt.preferred_lifetime(),
prefix: opt.prefix()
})
}))
} else {
Err(Error::Malformed)
}
@ -457,10 +462,10 @@ impl<'a> Repr<'a> {
} else {
let ip_packet = Ipv6Packet::new(&opt.data()[field::IP_DATA..]);
let ip_repr = Ipv6Repr::parse(&ip_packet)?;
Ok(Repr::RedirectedHeader {
Ok(Repr::RedirectedHeader(RedirectedHeader {
header: ip_repr,
data: &opt.data()[field::IP_DATA + ip_repr.buffer_len()..]
})
}))
}
},
Type::Mtu => {
@ -485,9 +490,9 @@ impl<'a> Repr<'a> {
match self {
&Repr::SourceLinkLayerAddr(_) | &Repr::TargetLinkLayerAddr(_) =>
field::LL_ADDR.end,
&Repr::PrefixInformation { .. } =>
&Repr::PrefixInformation(_) =>
field::PREFIX.end,
&Repr::RedirectedHeader { header, data } =>
&Repr::RedirectedHeader(RedirectedHeader { header, data }) =>
field::IP_DATA + header.buffer_len() + data.len(),
&Repr::Mtu(_) =>
field::MTU.end,
@ -510,10 +515,10 @@ impl<'a> Repr<'a> {
opt.set_data_len(1);
opt.set_link_layer_addr(addr);
},
&Repr::PrefixInformation {
&Repr::PrefixInformation(PrefixInformation {
prefix_len, flags, valid_lifetime,
preferred_lifetime, prefix
} => {
}) => {
opt.clear_prefix_reserved();
opt.set_option_type(Type::PrefixInformation);
opt.set_data_len(4);
@ -523,9 +528,9 @@ impl<'a> Repr<'a> {
opt.set_preferred_lifetime(preferred_lifetime);
opt.set_prefix(prefix);
},
&Repr::RedirectedHeader {
&Repr::RedirectedHeader(RedirectedHeader {
header, data
} => {
}) => {
let data_len = data.len() / 8;
opt.clear_redirected_reserved();
opt.set_option_type(Type::RedirectedHeader);
@ -559,16 +564,16 @@ impl<'a> fmt::Display for Repr<'a> {
&Repr::TargetLinkLayerAddr(addr) => {
write!(f, "TargetLinkLayer addr={}", addr)
},
&Repr::PrefixInformation {
&Repr::PrefixInformation(PrefixInformation {
prefix, prefix_len,
..
} => {
}) => {
write!(f, "PrefixInformation prefix={}/{}", prefix, prefix_len)
},
&Repr::RedirectedHeader {
&Repr::RedirectedHeader(RedirectedHeader {
header,
..
} => {
}) => {
write!(f, "RedirectedHeader header={}", header)
},
&Repr::Mtu(mtu) => {
@ -605,7 +610,7 @@ mod test {
use Error;
use time::Duration;
use wire::{EthernetAddress, Ipv6Address};
use super::{NdiscOption, Type, PrefixInfoFlags, Repr};
use super::{NdiscOption, Type, PrefixInfoFlags, PrefixInformation, Repr};
static PREFIX_OPT_BYTES: [u8; 32] = [
0x03, 0x04, 0x40, 0xc0,
@ -671,26 +676,26 @@ mod test {
#[test]
fn test_repr_parse_prefix_info() {
let repr = Repr::PrefixInformation {
let repr = Repr::PrefixInformation(PrefixInformation {
prefix_len: 64,
flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
valid_lifetime: Duration::from_secs(900),
preferred_lifetime: Duration::from_secs(1000),
prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)
};
});
assert_eq!(Repr::parse(&NdiscOption::new(&PREFIX_OPT_BYTES)), Ok(repr));
}
#[test]
fn test_repr_emit_prefix_info() {
let mut bytes = [0x2a; 32];
let repr = Repr::PrefixInformation {
let repr = Repr::PrefixInformation(PrefixInformation {
prefix_len: 64,
flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF,
valid_lifetime: Duration::from_secs(900),
preferred_lifetime: Duration::from_secs(1000),
prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)
};
});
let mut opt = NdiscOption::new(&mut bytes);
repr.emit(&mut opt);
assert_eq!(&opt.into_inner()[..], &PREFIX_OPT_BYTES[..]);