blob: a214b45957213bc44cc4f2dfcb3e3204f2afeffb [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Messages used for NDP (ICMPv6).
#![allow(unused)] // FIXME(joshlf)
use std::marker::PhantomData;
use std::ops::Range;
use byteorder::{ByteOrder, NetworkEndian};
use zerocopy::{ByteSlice, LayoutVerified, Unaligned};
use crate::error::ParseError;
use crate::ip::{Ipv6, Ipv6Addr};
use crate::wire::util;
use super::{IcmpIpExt, IcmpUnusedCode};
pub type Options<B> = util::Options<B, options::NdpOptionImpl>;
/// An NDP Router Solicitation.
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct RouterSolicitation {
_reserved: [u8; 4],
}
impl_from_bytes_as_bytes_unaligned!(RouterSolicitation);
impl_icmp_message!(
Ipv6,
RouterSolicitation,
RouterSolicitation,
IcmpUnusedCode,
Options<B>
);
/// An NDP Router Advertisment.
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct RouterAdvertisment {
current_hop_limit: u8,
configuration_mo: u8,
router_lifetime: [u8; 2],
reachable_time: [u8; 4],
retransmit_timer: [u8; 4],
}
impl_from_bytes_as_bytes_unaligned!(RouterAdvertisment);
impl_icmp_message!(
Ipv6,
RouterAdvertisment,
RouterAdvertisment,
IcmpUnusedCode,
Options<B>
);
impl RouterAdvertisment {
pub fn router_lifetime(&self) -> u16 {
NetworkEndian::read_u16(&self.router_lifetime)
}
pub fn reachable_time(&self) -> u32 {
NetworkEndian::read_u32(&self.reachable_time)
}
pub fn retransmit_timer(&self) -> u32 {
NetworkEndian::read_u32(&self.retransmit_timer)
}
}
/// An NDP Neighbor Solicitation.
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct NeighborSolicitation {
_reserved: [u8; 4],
target_address: Ipv6Addr,
}
impl_from_bytes_as_bytes_unaligned!(NeighborSolicitation);
impl_icmp_message!(
Ipv6,
NeighborSolicitation,
NeighborSolicitation,
IcmpUnusedCode,
Options<B>
);
/// An NDP Neighbor Advertisment.
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct NeighborAdvertisment {
flags_rso: u8,
_reserved: [u8; 3],
target_address: Ipv6Addr,
}
impl_from_bytes_as_bytes_unaligned!(NeighborAdvertisment);
impl_icmp_message!(
Ipv6,
NeighborAdvertisment,
NeighborAdvertisment,
IcmpUnusedCode,
Options<B>
);
/// An ICMPv6 Redirect Message.
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Redirect {
_reserved: [u8; 4],
target_address: Ipv6Addr,
destination_address: Ipv6Addr,
}
impl_from_bytes_as_bytes_unaligned!(Redirect);
impl_icmp_message!(Ipv6, Redirect, Redirect, IcmpUnusedCode, Options<B>);
pub mod options {
use byteorder::{ByteOrder, NetworkEndian};
use zerocopy::LayoutVerified;
use crate::ip::Ipv6Addr;
use crate::wire::util::{OptionImpl, OptionImplErr};
create_net_enum! {
NdpOptionType,
SourceLinkLayerAddress: SOURCE_LINK_LAYER_ADDRESS = 1,
TargetLinkLayerAddress: TARGET_LINK_LAYER_ADDRESS = 2,
PrefixInformation: PREFIX_INFORMATION = 3,
RedirectedHeader: REDIRECTED_HEADER = 4,
Mtu: MTU = 5,
}
#[derive(Debug)]
pub struct PrefixInformation<'a> {
prefix_length: u8,
flags_la: u8,
valid_lifetime: &'a [u8],
preferred_lifetime: &'a [u8],
prefix: LayoutVerified<&'a [u8], Ipv6Addr>,
}
impl<'a> PrefixInformation<'a> {
pub fn valid_lifetime(&self) -> u32 {
NetworkEndian::read_u32(&self.valid_lifetime)
}
pub fn preferred_lifetime(&self) -> u32 {
NetworkEndian::read_u32(&self.preferred_lifetime)
}
pub fn prefix(&self) -> &Ipv6Addr {
&self.prefix
}
}
#[allow(missing_docs)]
#[derive(Debug)]
pub enum NdpOption<'a> {
SourceLinkLayerAddress(&'a [u8]),
TargetLinkLayerAddress(&'a [u8]),
PrefixInformation(PrefixInformation<'a>),
RedirectedHeader { original_packet: &'a [u8] },
MTU { mtu: &'a [u8] },
}
#[derive(Debug)]
pub struct NdpOptionImpl;
impl OptionImplErr for NdpOptionImpl {
type Error = String;
}
impl<'a> OptionImpl<'a> for NdpOptionImpl {
// For NDP options the length should be multiplied by 8.
const OPTION_LEN_MULTIPLIER: usize = 8;
// NDP options don't have END_OF_OPTIONS or NOP.
const END_OF_OPTIONS: Option<u8> = None;
const NOP: Option<u8> = None;
type Output = NdpOption<'a>;
fn parse(kind: u8, data: &'a [u8]) -> Result<Option<NdpOption>, String> {
Ok(Some(match NdpOptionType::from_u8(kind) {
Some(NdpOptionType::SourceLinkLayerAddress) => {
NdpOption::SourceLinkLayerAddress(data)
}
Some(NdpOptionType::TargetLinkLayerAddress) => {
NdpOption::TargetLinkLayerAddress(data)
}
Some(NdpOptionType::PrefixInformation) => {
if data.len() < 14 {
// Data should always be 14 octets long
return Err("BadData".to_string());
}
let (prefix, _) = LayoutVerified::<_, Ipv6Addr>::new_from_prefix(&data[14..])
.ok_or_else(|| "No parse data".to_string())?;
NdpOption::PrefixInformation(PrefixInformation {
prefix_length: data[0],
flags_la: data[1],
valid_lifetime: &data[2..6],
preferred_lifetime: &data[6..10],
prefix,
})
}
Some(NdpOptionType::RedirectedHeader) => NdpOption::RedirectedHeader {
original_packet: &data[6..],
},
Some(NdpOptionType::Mtu) => NdpOption::MTU { mtu: &data[2..] },
None => return Ok(None),
}))
}
}
}
#[cfg(test)]
mod tests {
use packet::{ParsablePacket, ParseBuffer};
use super::*;
use crate::wire::icmp::{IcmpMessage, IcmpPacket, IcmpParseArgs};
use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketBuilder};
#[test]
fn parse_neighbor_solicitation() {
use crate::wire::icmp::testdata::ndp_neighbor::*;
let mut buf = &SOLICITATION_IP_PACKET_BYTES[..];
let ip = buf.parse::<Ipv6Packet<_>>().unwrap();
let (src_ip, dst_ip, hop_limit) = (ip.src_ip(), ip.dst_ip(), ip.hop_limit());
let icmp = buf
.parse_with::<_, IcmpPacket<_, _, NeighborSolicitation>>(IcmpParseArgs::new(
src_ip, dst_ip,
))
.unwrap();
assert_eq!(icmp.message().target_address.ipv6_bytes(), TARGET_ADDRESS);
for option in icmp.ndp_options().iter() {
match option {
options::NdpOption::SourceLinkLayerAddress(address) => {
assert_eq!(address, SOURCE_LINK_LAYER_ADDRESS);
}
o => panic!("Found unexpected option: {:?}", o),
}
}
}
#[test]
fn parse_neighbor_advertisment() {
use crate::wire::icmp::testdata::ndp_neighbor::*;
let mut buf = &ADVERTISMENT_IP_PACKET_BYTES[..];
let ip = buf.parse::<Ipv6Packet<_>>().unwrap();
let (src_ip, dst_ip, hop_limit) = (ip.src_ip(), ip.dst_ip(), ip.hop_limit());
let icmp = buf
.parse_with::<_, IcmpPacket<_, _, NeighborAdvertisment>>(IcmpParseArgs::new(
src_ip, dst_ip,
))
.unwrap();
assert_eq!(icmp.message().target_address.ipv6_bytes(), TARGET_ADDRESS);
assert_eq!(icmp.ndp_options().iter().count(), 0);
}
#[test]
fn parse_router_advertisment() {
use crate::wire::icmp::testdata::ndp_router::*;
let mut buf = &ADVERTISMENT_IP_PACKET_BYTES[..];
let ip = buf.parse::<Ipv6Packet<_>>().unwrap();
let (src_ip, dst_ip) = (ip.src_ip(), ip.dst_ip());
let icmp = buf
.parse_with::<_, IcmpPacket<_, _, RouterAdvertisment>>(IcmpParseArgs::new(
src_ip, dst_ip,
))
.unwrap();
assert_eq!(icmp.message().current_hop_limit, HOP_LIMIT);
assert_eq!(icmp.message().router_lifetime(), LIFETIME);
assert_eq!(icmp.message().reachable_time(), REACHABLE_TIME);
assert_eq!(icmp.message().retransmit_timer(), RETRANS_TIMER);
assert_eq!(icmp.ndp_options().iter().count(), 2);
for option in icmp.ndp_options().iter() {
match option {
options::NdpOption::SourceLinkLayerAddress(address) => {
assert_eq!(address, SOURCE_LINK_LAYER_ADDRESS);
}
options::NdpOption::PrefixInformation(info) => {
assert_eq!(info.valid_lifetime(), PREFIX_INFO_VALID_LIFETIME);
assert_eq!(info.preferred_lifetime(), PREFIX_INFO_PREFERRED_LIFETIME);
assert_eq!(info.prefix().ipv6_bytes(), PREFIX_ADDRESS);
}
o => panic!("Found unexpected option: {:?}", o),
}
}
}
}