[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;