[rust-netstack] NDP module groundworks

- Stub out basic layout for ip::ndp module.
- Definition of an NdpDevice, which EthernetDevice provides.

Change-Id: If9b61ae1b26c849cc8675fb4e81b6250a504251f
diff --git a/garnet/bin/recovery_netstack/core/src/device/ethernet.rs b/garnet/bin/recovery_netstack/core/src/device/ethernet.rs
index e8cbc35..a9c2b6d 100644
--- a/garnet/bin/recovery_netstack/core/src/device/ethernet.rs
+++ b/garnet/bin/recovery_netstack/core/src/device/ethernet.rs
@@ -13,7 +13,8 @@
 
 use crate::device::arp::{ArpDevice, ArpHardwareType, ArpState};
 use crate::device::{DeviceId, FrameDestination};
-use crate::ip::{AddrSubnet, Ip, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
+use crate::ip::ndp::NdpState;
+use crate::ip::{ndp, AddrSubnet, Ip, IpAddress, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
 use crate::wire::arp::peek_arp_types;
 use crate::wire::ethernet::{EthernetFrame, EthernetFrameBuilder};
 use crate::{Context, EventDispatcher};
@@ -97,6 +98,8 @@
     }
 }
 
+impl ndp::LinkLayerAddress for Mac {}
+
 /// An EtherType number.
 #[allow(missing_docs)]
 #[derive(Copy, Clone, Hash, Eq, PartialEq)]
@@ -163,6 +166,7 @@
     ipv4_addr_sub: Option<AddrSubnet<Ipv4Addr>>,
     ipv6_addr_sub: Option<AddrSubnet<Ipv6Addr>>,
     ipv4_arp: ArpState<Ipv4Addr, EthernetArpDevice>,
+    ndp: ndp::NdpState<EthernetNdpDevice>,
 }
 
 impl EthernetDeviceState {
@@ -182,6 +186,7 @@
             ipv4_addr_sub: None,
             ipv6_addr_sub: None,
             ipv4_arp: ArpState::default(),
+            ndp: NdpState::default(),
         }
     }
 }
@@ -233,7 +238,25 @@
     };
 
     #[ipv6addr]
-    let dst_mac = log_unimplemented!(None, "device::ethernet::send_ip_frame: IPv6 unimplemented");
+    let dst_mac = {
+        if let Some(dst_mac) =
+            crate::ip::ndp::lookup::<_, EthernetNdpDevice>(ctx, device_id, local_addr)
+        {
+            Some(dst_mac)
+        } else {
+            // TODO(brunodalbo) On cache misses, packets need to be held
+            //  and retransmitted once the link layer address is resolved.
+            //  per RFC 4861 section 7.2.2 the buffer should be limited to the N
+            //  most *recent* entries where N must be at least 1.
+            //  Also, in case the link layer address CAN'T be resolved, we MUST
+            //  send an ICMP destination unreachable for each packet queued
+            //  awaiting address resolution.
+            log_unimplemented!(
+                None,
+                "device::ethernet::send_ip_frame: unimplemented on ndp cache miss"
+            )
+        }
+    };
 
     if let Some(dst_mac) = dst_mac {
         let buffer = body
@@ -319,9 +342,9 @@
     device_id: u64,
 ) -> Ipv6Addr {
     // TODO(brunodalbo) the link local address is subject to the same collision
-    // verifications as prefix global addresses, we should keep a state machine
-    // about that check and cache the adopted address. For now, we just compose
-    // the link-local from the ethernet MAC.
+    //  verifications as prefix global addresses, we should keep a state machine
+    //  about that check and cache the adopted address. For now, we just compose
+    //  the link-local from the ethernet MAC.
     get_device_state(ctx, device_id).mac.to_ipv6_link_local(None)
 }
 
@@ -367,8 +390,8 @@
         .unwrap_or_else(|| panic!("no such Ethernet device: {}", device_id))
 }
 
-// Dummy type used to implement ArpDevice.
-pub(crate) struct EthernetArpDevice;
+/// Dummy type used to implement ArpDevice.
+pub(super) struct EthernetArpDevice;
 
 impl ArpDevice<Ipv4Addr> for EthernetArpDevice {
     type HardwareAddr = Mac;
@@ -408,6 +431,28 @@
     }
 }
 
+/// Dummy type used to implement NdpDevice
+pub(crate) struct EthernetNdpDevice;
+
+impl ndp::NdpDevice for EthernetNdpDevice {
+    type LinkAddress = Mac;
+    const BROADCAST: Mac = Mac::BROADCAST;
+
+    fn get_ndp_state<D: EventDispatcher>(
+        ctx: &mut Context<D>,
+        device_id: u64,
+    ) -> &mut ndp::NdpState<Self> {
+        &mut get_device_state(ctx, device_id).ndp
+    }
+
+    fn get_link_layer_addr<D: EventDispatcher>(
+        ctx: &mut Context<D>,
+        device_id: u64,
+    ) -> Self::LinkAddress {
+        get_device_state(ctx, device_id).mac
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use packet::{Buf, BufferSerializer};
diff --git a/garnet/bin/recovery_netstack/core/src/ip/mod.rs b/garnet/bin/recovery_netstack/core/src/ip/mod.rs
index ff64437..c21f04f 100644
--- a/garnet/bin/recovery_netstack/core/src/ip/mod.rs
+++ b/garnet/bin/recovery_netstack/core/src/ip/mod.rs
@@ -7,6 +7,7 @@
 mod forwarding;
 mod icmp;
 mod igmp;
+pub(crate) mod ndp;
 mod types;
 
 // It's ok to `pub use` rather `pub(crate) use` here because the items in
diff --git a/garnet/bin/recovery_netstack/core/src/ip/ndp.rs b/garnet/bin/recovery_netstack/core/src/ip/ndp.rs
new file mode 100644
index 0000000..bb110e1
--- /dev/null
+++ b/garnet/bin/recovery_netstack/core/src/ip/ndp.rs
@@ -0,0 +1,140 @@
+// Copyright 2019 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.
+
+//! The Neighboor Discovery Protocol (NDP).
+//!
+//! Neighboor Discovery for IPv6 as defined in [RFC 4861] defines mechanisms for
+//! solving the following problems:
+//! - Router Discovery
+//! - Prefix Discovery
+//! - Parameter Discovery
+//! - Address Autoconfiguration
+//! - Address resolution
+//! - Next-hop determination
+//! - Neighbor Unreachability Detection
+//! - Duplicate Address Detection
+//! - Redirect
+//!
+//! [RFC 4861]: https://tools.ietf.org/html/rfc4861
+
+use crate::ip::Ipv6Addr;
+use crate::{Context, EventDispatcher};
+use std::collections::HashMap;
+
+/// A link layer address that can be discovered using NDP.
+pub(crate) trait LinkLayerAddress: Copy + Clone {}
+
+/// A device layer protocol which can support NDP.
+///
+/// An `NdpDevice` is a device layer protocol which can support NDP.
+pub(crate) trait NdpDevice: Sized {
+    /// The link-layer address type used by this device.
+    type LinkAddress: LinkLayerAddress;
+    /// The broadcast value for link addresses on this device.
+    // NOTE(brunodalbo): RFC 4861 mentions the possibility of running NDP on
+    // link types that do not support broadcasts, but this implementation does
+    // not cover that for simplicity.
+    const BROADCAST: Self::LinkAddress;
+
+    /// Get a mutable reference to a device's NDP state.
+    fn get_ndp_state<D: EventDispatcher>(
+        ctx: &mut Context<D>,
+        device_id: u64,
+    ) -> &mut NdpState<Self>;
+
+    /// Get the link layer address for a device.
+    fn get_link_layer_addr<D: EventDispatcher>(
+        ctx: &mut Context<D>,
+        device_id: u64,
+    ) -> Self::LinkAddress;
+}
+
+/// The state associated with an instance of the Neighbor Discovery Protocol
+/// (NDP).
+///
+/// Each device will contain an `NdpState` object to keep track of discovery
+/// operations.
+pub(crate) struct NdpState<D: NdpDevice> {
+    neighbors: NeighborTable<D::LinkAddress>,
+}
+
+impl<D: NdpDevice> Default for NdpState<D> {
+    fn default() -> Self {
+        NdpState { neighbors: NeighborTable::default() }
+    }
+}
+
+/// Look up the link layer address
+pub(crate) fn lookup<D: EventDispatcher, ND: NdpDevice>(
+    ctx: &mut Context<D>,
+    device_id: u64,
+    lookup_addr: Ipv6Addr,
+) -> Option<ND::LinkAddress> {
+    // TODO(brunodalbo): Figure out what to do if a frame can't be sent
+    let result = ND::get_ndp_state(ctx, device_id).neighbors.lookup_link_addr(lookup_addr).cloned();
+
+    // Send an Neighbor Solicitation Request if the address is not in our cache
+    if result.is_none() {
+        log_unimplemented!((), "Neighbor Solicitation queries not implemented")
+    }
+
+    result
+}
+
+/// Insert a neighbor to the known neihbors table.
+pub(crate) fn insert_neighbor<D: EventDispatcher, ND: NdpDevice>(
+    ctx: &mut Context<D>,
+    device_id: u64,
+    net: Ipv6Addr,
+    hw: ND::LinkAddress,
+) {
+    ND::get_ndp_state(ctx, device_id).neighbors.set_link_address(net, hw)
+}
+
+/// `NeighborState` keeps all state that NDP may want to keep about neighbors,
+/// like link address resolution and reachability information, for example.
+struct NeighborState<H> {
+    link_address: LinkAddressResolutionValue<H>,
+}
+
+impl<H> NeighborState<H> {
+    fn new() -> Self {
+        Self { link_address: LinkAddressResolutionValue::Waiting }
+    }
+}
+
+#[derive(Debug, Eq, PartialEq)] // for testing
+enum LinkAddressResolutionValue<H> {
+    Known(H),
+    Waiting,
+}
+
+struct NeighborTable<H> {
+    table: HashMap<Ipv6Addr, NeighborState<H>>,
+}
+
+impl<H> NeighborTable<H> {
+    fn set_link_address(&mut self, neighbor: Ipv6Addr, link: H) {
+        self.table.entry(neighbor).or_insert_with(|| NeighborState::new()).link_address =
+            LinkAddressResolutionValue::Known(link);
+    }
+
+    fn set_waiting_link_address(&mut self, neighbor: Ipv6Addr) {
+        self.table.entry(neighbor).or_insert_with(|| NeighborState::new()).link_address =
+            LinkAddressResolutionValue::Waiting;
+    }
+
+    fn lookup_link_addr(&self, neighbor: Ipv6Addr) -> Option<&H> {
+        match self.table.get(&neighbor) {
+            Some(NeighborState { link_address: LinkAddressResolutionValue::Known(x) }) => Some(x),
+            _ => None,
+        }
+    }
+}
+
+impl<H> Default for NeighborTable<H> {
+    fn default() -> Self {
+        NeighborTable { table: HashMap::default() }
+    }
+}
diff --git a/garnet/bin/recovery_netstack/core/src/wire/icmp/mod.rs b/garnet/bin/recovery_netstack/core/src/wire/icmp/mod.rs
index 06781d9..2c413d1 100644
--- a/garnet/bin/recovery_netstack/core/src/wire/icmp/mod.rs
+++ b/garnet/bin/recovery_netstack/core/src/wire/icmp/mod.rs
@@ -9,7 +9,7 @@
 mod common;
 mod icmpv4;
 mod icmpv6;
-mod ndp;
+pub(crate) mod ndp;
 
 #[cfg(test)]
 mod testdata;