Browse Source

Add Address Resolution for IPv6

Add Address Resolution via NDISC for IPv6.

Closes: #196
Approved by: whitequark
Dan Robertson 7 years ago
parent
commit
72ef278471
4 changed files with 197 additions and 29 deletions
  1. 1 1
      Cargo.toml
  2. 5 1
      examples/server.rs
  3. 178 27
      src/iface/ethernet.rs
  4. 13 0
      src/wire/ipv6.rs

+ 1 - 1
Cargo.toml

@@ -58,7 +58,7 @@ required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-tcp"]
 
 [[example]]
 name = "ping"
-required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-icmp"]
+required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-ipv6", "socket-icmp"]
 
 [[example]]
 name = "server"

+ 5 - 1
examples/server.rs

@@ -53,7 +53,11 @@ fn main() {
     let tcp4_socket = TcpSocket::new(tcp4_rx_buffer, tcp4_tx_buffer);
 
     let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
-    let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)];
+    let ip_addrs = [
+        IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
+        IpCidr::new(IpAddress::v6(0xfdbe, 0x0000, 0x0000, 0x0000,
+                                  0x0000, 0x0000, 0x0000, 0x002a), 64)
+    ];
     let mut iface = EthernetInterfaceBuilder::new(device)
             .ethernet_addr(ethernet_addr)
             .neighbor_cache(neighbor_cache)

+ 178 - 27
src/iface/ethernet.rs

@@ -20,6 +20,8 @@ use wire::{ArpPacket, ArpRepr, ArpOperation};
 use wire::{Icmpv4Packet, Icmpv4Repr, Icmpv4DstUnreachable};
 #[cfg(feature = "proto-ipv6")]
 use wire::{Icmpv6Packet, Icmpv6Repr, Icmpv6ParamProblem};
+#[cfg(feature = "proto-ipv6")]
+use wire::{NdiscNeighborFlags, NdiscRepr};
 #[cfg(all(feature = "proto-ipv6", feature = "socket-udp"))]
 use wire::Icmpv6DstUnreachable;
 #[cfg(feature = "socket-udp")]
@@ -267,25 +269,6 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
         self.inner.ip_addrs.as_ref()
     }
 
-    /// Determine if the given `Ipv6Address` is the solicited node
-    /// multicast address for a IPv6 addresses assigned to the interface.
-    /// See [RFC 4291 § 2.7.1] for more details.
-    ///
-    /// [RFC 4291 § 2.7.1]: https://tools.ietf.org/html/rfc4291#section-2.7.1
-    #[cfg(feature = "proto-ipv6")]
-    pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool {
-        self.inner.ip_addrs.iter().find(|cidr| {
-            match *cidr {
-                &IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOOPBACK=> {
-                    // Take the lower order 24 bits of the IPv6 address and
-                    // append those bits to FF02:0:0:0:0:1:FF00::/104.
-                    addr.as_bytes()[14..] == cidr.address().as_bytes()[14..]
-                }
-                _ => false,
-            }
-        }).is_some()
-    }
-
     /// Update the IP addresses of the interface.
     ///
     /// # Panics
@@ -508,6 +491,25 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
         }
     }
 
+    /// Determine if the given `Ipv6Address` is the solicited node
+    /// multicast address for a IPv6 addresses assigned to the interface.
+    /// See [RFC 4291 § 2.7.1] for more details.
+    ///
+    /// [RFC 4291 § 2.7.1]: https://tools.ietf.org/html/rfc4291#section-2.7.1
+    #[cfg(feature = "proto-ipv6")]
+    pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool {
+        self.ip_addrs.iter().find(|cidr| {
+            match *cidr {
+                &IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOOPBACK=> {
+                    // Take the lower order 24 bits of the IPv6 address and
+                    // append those bits to FF02:0:0:0:0:1:FF00::/104.
+                    addr.as_bytes()[14..] == cidr.address().as_bytes()[14..]
+                }
+                _ => false,
+            }
+        }).is_some()
+    }
+
     /// Check whether the interface has the given IP address assigned.
     fn has_ip_addr<T: Into<IpAddress>>(&self, addr: T) -> bool {
         let addr = addr.into();
@@ -624,7 +626,8 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
         if eth_frame.src_addr().is_unicast() {
             // Fill the neighbor cache from IP header of unicast frames.
             let ip_addr = IpAddress::Ipv6(ipv6_repr.src_addr);
-            if self.in_same_network(&ip_addr) {
+            if self.in_same_network(&ip_addr) &&
+                    self.neighbor_cache.lookup_pure(&ip_addr, timestamp).is_none() {
                 self.neighbor_cache.fill(ip_addr, eth_frame.src_addr(), timestamp);
             }
         }
@@ -637,7 +640,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
 
         match ipv6_repr.next_header {
             IpProtocol::Icmpv6 =>
-                self.process_icmpv6(sockets, ip_repr, ip_payload),
+                self.process_icmpv6(sockets, timestamp, ip_repr, ip_payload),
 
             #[cfg(feature = "socket-udp")]
             IpProtocol::Udp =>
@@ -735,7 +738,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
     }
 
     #[cfg(feature = "proto-ipv6")]
-    fn process_icmpv6<'frame>(&mut self, _sockets: &mut SocketSet,
+    fn process_icmpv6<'frame>(&mut self, _sockets: &mut SocketSet, timestamp: Instant,
                               ip_repr: IpRepr, ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
     {
         let icmp_packet = Icmpv6Packet::new_checked(ip_payload)?;
@@ -762,11 +765,67 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
             // Ignore any echo replies.
             Icmpv6Repr::EchoReply { .. } => Ok(Packet::None),
 
+            // Forward any NDISC packets to the ndisc packet handler
+            Icmpv6Repr::Ndisc(repr) if ip_repr.hop_limit() == 0xff => match ip_repr {
+                IpRepr::Ipv6(ipv6_repr) => self.process_ndisc(timestamp, ipv6_repr, repr),
+                _ => Ok(Packet::None)
+            },
+
             // FIXME: do something correct here?
             _ => Err(Error::Unrecognized),
         }
     }
 
+    #[cfg(feature = "proto-ipv6")]
+    fn process_ndisc<'frame>(&mut self, timestamp: Instant, ip_repr: Ipv6Repr,
+                             repr: NdiscRepr<'frame>) -> Result<Packet<'frame>> {
+        let packet = match repr {
+            NdiscRepr::NeighborAdvert { lladdr, target_addr, flags } => {
+                let ip_addr = ip_repr.src_addr.into();
+                match lladdr {
+                    Some(lladdr) if lladdr.is_unicast() && target_addr.is_unicast() => {
+                        if flags.contains(NdiscNeighborFlags::OVERRIDE) {
+                            self.neighbor_cache.fill(ip_addr, lladdr, timestamp)
+                        } else {
+                            if self.neighbor_cache.lookup_pure(&ip_addr, timestamp).is_none() {
+                                    self.neighbor_cache.fill(ip_addr, lladdr, timestamp)
+                            }
+                        }
+                    },
+                    _ => (),
+                }
+                Ok(Packet::None)
+            }
+            NdiscRepr::NeighborSolicit { target_addr, lladdr, .. } => {
+                match lladdr {
+                    Some(lladdr) if lladdr.is_unicast() && target_addr.is_unicast() => {
+                        self.neighbor_cache.fill(ip_repr.src_addr.into(), lladdr, timestamp)
+                    },
+                    _ => (),
+                }
+                if self.has_solicited_node(ip_repr.dst_addr) && self.has_ip_addr(target_addr) {
+                    let advert = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+                        flags: NdiscNeighborFlags::SOLICITED,
+                        target_addr: target_addr,
+                        lladdr: Some(self.ethernet_addr)
+                    });
+                    let ip_repr = Ipv6Repr {
+                        src_addr: target_addr,
+                        dst_addr: ip_repr.src_addr,
+                        next_header: IpProtocol::Icmpv6,
+                        hop_limit: 0xff,
+                        payload_len: advert.buffer_len()
+                    };
+                    Ok(Packet::Icmpv6((ip_repr, advert)))
+                } else {
+                    Ok(Packet::None)
+                }
+            }
+            _ => Ok(Packet::None)
+        };
+        packet
+    }
+
     #[cfg(feature = "proto-ipv4")]
     fn process_icmpv4<'frame>(&self, _sockets: &mut SocketSet, ip_repr: IpRepr,
                               ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
@@ -1145,6 +1204,35 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
 
                 Err(Error::Unaddressable)
             }
+
+            #[cfg(feature = "proto-ipv6")]
+            (&IpAddress::Ipv6(src_addr), IpAddress::Ipv6(dst_addr)) => {
+                net_debug!("address {} not in neighbor cache, sending Neighbor Solicitation",
+                           dst_addr);
+
+                let checksum_caps = self.device_capabilities.checksum.clone();
+
+                let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit {
+                    target_addr: src_addr,
+                    lladdr: Some(self.ethernet_addr),
+                });
+
+                let ip_repr = IpRepr::Ipv6(Ipv6Repr {
+                    src_addr: src_addr,
+                    dst_addr: dst_addr.solicited_node(),
+                    next_header: IpProtocol::Icmpv6,
+                    payload_len: solicit.buffer_len(),
+                    hop_limit: 0xff
+                });
+
+                self.dispatch_ip(tx_token, timestamp, ip_repr, |ip_repr, payload| {
+                    solicit.emit(&ip_repr.src_addr(), &ip_repr.dst_addr(),
+                                 &mut Icmpv6Packet::new(payload), &checksum_caps);
+                })?;
+
+                Err(Error::Unaddressable)
+            }
+
             _ => Err(Error::Unaddressable)
         }
     }
@@ -1201,7 +1289,9 @@ mod test {
     #[cfg(feature = "proto-ipv6")]
     use wire::{Ipv6Address, Ipv6Repr};
     #[cfg(feature = "proto-ipv6")]
-    use wire::{Icmpv6Repr, Icmpv6ParamProblem};
+    use wire::{Icmpv6Packet, Icmpv6Repr, Icmpv6ParamProblem};
+    #[cfg(feature = "proto-ipv6")]
+    use wire::{NdiscNeighborFlags, NdiscRepr};
 
     use super::Packet;
 
@@ -1213,7 +1303,9 @@ mod test {
             #[cfg(feature = "proto-ipv4")]
             IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8),
             #[cfg(feature = "proto-ipv6")]
-            IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 1), 128)
+            IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 1), 128),
+            #[cfg(feature = "proto-ipv6")]
+            IpCidr::new(IpAddress::v6(0xfdbe, 0, 0, 0, 0, 0, 0, 1), 64),
         ];
 
         let iface = InterfaceBuilder::new(device)
@@ -1580,6 +1672,65 @@ mod test {
             Ok((remote_hw_addr, MockTxToken)));
     }
 
+    #[test]
+    #[cfg(feature = "proto-ipv6")]
+    fn test_handle_valid_ndisc_request() {
+        let (mut iface, mut socket_set) = create_loopback();
+
+        let mut eth_bytes = vec![0u8; 86];
+
+        let local_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1);
+        let remote_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2);
+        let local_hw_addr = EthernetAddress([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
+        let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
+
+        let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit {
+            target_addr: local_ip_addr,
+            lladdr: Some(remote_hw_addr),
+        });
+        let ip_repr = IpRepr::Ipv6(Ipv6Repr {
+            src_addr: remote_ip_addr,
+            dst_addr: local_ip_addr.solicited_node(),
+            next_header: IpProtocol::Icmpv6,
+            hop_limit: 0xff,
+            payload_len: solicit.buffer_len()
+        });
+
+        let mut frame = EthernetFrame::new(&mut eth_bytes);
+        frame.set_dst_addr(EthernetAddress([0x33, 0x33, 0x00, 0x00, 0x00, 0x00]));
+        frame.set_src_addr(remote_hw_addr);
+        frame.set_ethertype(EthernetProtocol::Ipv6);
+        {
+            ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default());
+            solicit.emit(&remote_ip_addr.into(), &local_ip_addr.solicited_node().into(),
+                         &mut Icmpv6Packet::new(&mut frame.payload_mut()[ip_repr.buffer_len()..]),
+                         &ChecksumCapabilities::default());
+        }
+
+        let icmpv6_expected = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
+            flags: NdiscNeighborFlags::SOLICITED,
+            target_addr: local_ip_addr,
+            lladdr: Some(local_hw_addr)
+        });
+
+        let ipv6_expected = Ipv6Repr {
+            src_addr: local_ip_addr,
+            dst_addr: remote_ip_addr,
+            next_header: IpProtocol::Icmpv6,
+            hop_limit: 0xff,
+            payload_len: icmpv6_expected.buffer_len()
+        };
+
+        // Ensure an Neighbor Solicitation triggers a Neighbor Advertisement
+        assert_eq!(iface.inner.process_ethernet(&mut socket_set, Instant::from_millis(0), frame.into_inner()),
+                   Ok(Packet::Icmpv6((ipv6_expected, icmpv6_expected))));
+
+        // Ensure the address of the requestor was entered in the cache
+        assert_eq!(iface.inner.lookup_hardware_addr(MockTxToken, Instant::from_secs(0),
+            &IpAddress::Ipv6(local_ip_addr), &IpAddress::Ipv6(remote_ip_addr)),
+            Ok((remote_hw_addr, MockTxToken)));
+    }
+
     #[test]
     #[cfg(feature = "proto-ipv4")]
     fn test_handle_other_arp_request() {
@@ -1694,9 +1845,9 @@ mod test {
             new_addrs.extend(addrs.to_vec());
             *addrs = From::from(new_addrs);
         });
-        assert!(iface.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0002)));
-        assert!(iface.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0xffff)));
-        assert!(!iface.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0001)));
+        assert!(iface.inner.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0002)));
+        assert!(iface.inner.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0xffff)));
+        assert!(!iface.inner.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0003)));
     }
 
     #[test]

+ 13 - 0
src/wire/ipv6.rs

@@ -169,6 +169,19 @@ impl Address {
         }
         bytes
     }
+
+    /// The solicited node for the given unicast address.
+    ///
+    /// # Panics
+    /// This function panics if the given address is not
+    /// unicast.
+    pub fn solicited_node(&self) -> Address {
+        assert!(self.is_unicast());
+        let mut bytes = [0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
+        bytes[14..].copy_from_slice(&self.0[14..]);
+        Address(bytes)
+    }
 }
 
 #[cfg(feature = "std")]