Bläddra i källkod

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
Dan Robertson 7 år sedan
förälder
incheckning
cc15985ba4
3 ändrade filer med 196 tillägg och 62 borttagningar
  1. 2 0
      src/wire/mod.rs
  2. 159 32
      src/wire/ndisc.rs
  3. 35 30
      src/wire/ndiscoption.rs

+ 2 - 0
src/wire/mod.rs

@@ -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,

+ 159 - 32
src/wire/ndisc.rs

@@ -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
         })
     }
 

+ 35 - 30
src/wire/ndiscoption.rs

@@ -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[..]);