Quellcode durchsuchen

Add support for arbitrarily many routes instead of only gateways.

Closes: #219
Approved by: whitequark
Valentin Lorentz vor 6 Jahren
Ursprung
Commit
06d18130cd
9 geänderte Dateien mit 306 neuen und 135 gelöschten Zeilen
  1. 1 1
      Cargo.toml
  2. 5 2
      examples/client.rs
  3. 6 3
      examples/httpclient.rs
  4. 6 3
      examples/ping.rs
  5. 38 121
      src/iface/ethernet.rs
  6. 2 0
      src/iface/mod.rs
  7. 229 0
      src/iface/route.rs
  8. 14 0
      src/wire/ip.rs
  9. 5 5
      src/wire/ndisc.rs

+ 1 - 1
Cargo.toml

@@ -15,7 +15,7 @@ license = "0BSD"
 autoexamples = false
 
 [dependencies]
-managed = { version = "0.5", default-features = false, features = ["map"] }
+managed = { version = "0.7", default-features = false, features = ["map"] }
 byteorder = { version = "1.0", default-features = false }
 log = { version = "0.3", default-features = false, optional = true }
 libc = { version = "0.2.18", optional = true }

+ 5 - 2
examples/client.rs

@@ -11,7 +11,7 @@ use std::collections::BTreeMap;
 use std::os::unix::io::AsRawFd;
 use smoltcp::phy::wait as phy_wait;
 use smoltcp::wire::{EthernetAddress, Ipv4Address, IpAddress, IpCidr};
-use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
+use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
 use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
 use smoltcp::time::Instant;
 
@@ -40,11 +40,14 @@ fn main() {
     let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
     let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 2), 24)];
     let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
+    let mut routes_storage = [None; 1];
+    let mut routes = Routes::new(&mut routes_storage[..]);
+    routes.add_default_ipv4_route(default_v4_gw).unwrap();
     let mut iface = EthernetInterfaceBuilder::new(device)
             .ethernet_addr(ethernet_addr)
             .neighbor_cache(neighbor_cache)
             .ip_addrs(ip_addrs)
-            .ipv4_gateway(default_v4_gw)
+            .routes(routes)
             .finalize();
 
     let mut sockets = SocketSet::new(vec![]);

+ 6 - 3
examples/httpclient.rs

@@ -14,7 +14,7 @@ use std::os::unix::io::AsRawFd;
 use url::Url;
 use smoltcp::phy::wait as phy_wait;
 use smoltcp::wire::{EthernetAddress, Ipv4Address, Ipv6Address, IpAddress, IpCidr};
-use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
+use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
 use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
 use smoltcp::time::Instant;
 
@@ -47,12 +47,15 @@ fn main() {
                     IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)];
     let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
     let default_v6_gw = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100);
+    let mut routes_storage = [None; 2];
+    let mut routes = Routes::new(&mut routes_storage[..]);
+    routes.add_default_ipv4_route(default_v4_gw).unwrap();
+    routes.add_default_ipv6_route(default_v6_gw).unwrap();
     let mut iface = EthernetInterfaceBuilder::new(device)
             .ethernet_addr(ethernet_addr)
             .neighbor_cache(neighbor_cache)
             .ip_addrs(ip_addrs)
-            .ipv4_gateway(default_v4_gw)
-            .ipv6_gateway(default_v6_gw)
+            .routes(routes)
             .finalize();
 
     let mut sockets = SocketSet::new(vec![]);

+ 6 - 3
examples/ping.rs

@@ -17,7 +17,7 @@ use smoltcp::phy::wait as phy_wait;
 use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr,
                     Ipv6Address, Icmpv6Repr, Icmpv6Packet,
                     Ipv4Address, Icmpv4Repr, Icmpv4Packet};
-use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
+use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
 use smoltcp::socket::{SocketSet, IcmpSocket, IcmpSocketBuffer, IcmpPacketMetadata, IcmpEndpoint};
 use std::collections::HashMap;
 use byteorder::{ByteOrder, NetworkEndian};
@@ -99,11 +99,14 @@ fn main() {
                     IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)];
     let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
     let default_v6_gw = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100);
+    let mut routes_storage = [None; 2];
+    let mut routes = Routes::new(&mut routes_storage[..]);
+    routes.add_default_ipv4_route(default_v4_gw).unwrap();
+    routes.add_default_ipv6_route(default_v6_gw).unwrap();
     let mut iface = EthernetInterfaceBuilder::new(device)
             .ethernet_addr(ethernet_addr)
             .ip_addrs(ip_addrs)
-            .ipv4_gateway(default_v4_gw)
-            .ipv6_gateway(default_v6_gw)
+            .routes(routes)
             .neighbor_cache(neighbor_cache)
             .finalize();
 

+ 38 - 121
src/iface/ethernet.rs

@@ -3,7 +3,7 @@
 // and RFCs 8200 and 4861 for any IPv6 and NDISC work.
 
 use core::cmp;
-use managed::ManagedSlice;
+use managed::{ManagedSlice, ManagedMap};
 
 use {Error, Result};
 use phy::{Device, DeviceCapabilities, RxToken, TxToken};
@@ -14,7 +14,7 @@ use wire::{IpAddress, IpProtocol, IpRepr, IpCidr};
 #[cfg(feature = "proto-ipv6")]
 use wire::{Ipv6Address, Ipv6Packet, Ipv6Repr, IPV6_MIN_MTU};
 #[cfg(feature = "proto-ipv4")]
-use wire::{Ipv4Address, Ipv4Packet, Ipv4Repr, IPV4_MIN_MTU};
+use wire::{Ipv4Packet, Ipv4Repr, IPV4_MIN_MTU};
 #[cfg(feature = "proto-ipv4")]
 use wire::{ArpPacket, ArpRepr, ArpOperation};
 #[cfg(feature = "proto-ipv4")]
@@ -46,15 +46,16 @@ use socket::UdpSocket;
 #[cfg(feature = "socket-tcp")]
 use socket::TcpSocket;
 use super::{NeighborCache, NeighborAnswer};
+use super::Routes;
 
 /// An Ethernet network interface.
 ///
 /// The network interface logically owns a number of other data structures; to avoid
 /// a dependency on heap allocation, it instead owns a `BorrowMut<[T]>`, which can be
 /// a `&mut [T]`, or `Vec<T>` if a heap is available.
-pub struct Interface<'b, 'c, DeviceT: for<'d> Device<'d>> {
+pub struct Interface<'b, 'c, 'e, DeviceT: for<'d> Device<'d>> {
     device: DeviceT,
-    inner:  InterfaceInner<'b, 'c>,
+    inner:  InterfaceInner<'b, 'c, 'e>,
 }
 
 /// The device independent part of an Ethernet network interface.
@@ -64,31 +65,25 @@ pub struct Interface<'b, 'c, DeviceT: for<'d> Device<'d>> {
 /// the `device` mutably until they're used, which makes it impossible to call other
 /// methods on the `Interface` in this time (since its `device` field is borrowed
 /// exclusively). However, it is still possible to call methods on its `inner` field.
-struct InterfaceInner<'b, 'c> {
+struct InterfaceInner<'b, 'c, 'e> {
     neighbor_cache:         NeighborCache<'b>,
     ethernet_addr:          EthernetAddress,
     ip_addrs:               ManagedSlice<'c, IpCidr>,
-    #[cfg(feature = "proto-ipv4")]
-    ipv4_gateway:           Option<Ipv4Address>,
-    #[cfg(feature = "proto-ipv6")]
-    ipv6_gateway:           Option<Ipv6Address>,
+    routes:                 Routes<'e>,
     device_capabilities:    DeviceCapabilities,
 }
 
 /// A builder structure used for creating a Ethernet network
 /// interface.
-pub struct InterfaceBuilder <'b, 'c, DeviceT: for<'d> Device<'d>> {
+pub struct InterfaceBuilder <'b, 'c, 'e, DeviceT: for<'d> Device<'d>> {
     device:              DeviceT,
     ethernet_addr:       Option<EthernetAddress>,
     neighbor_cache:      Option<NeighborCache<'b>>,
     ip_addrs:            ManagedSlice<'c, IpCidr>,
-    #[cfg(feature = "proto-ipv4")]
-    ipv4_gateway:        Option<Ipv4Address>,
-    #[cfg(feature = "proto-ipv6")]
-    ipv6_gateway:        Option<Ipv6Address>,
+    routes:              Routes<'e>,
 }
 
-impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
+impl<'b, 'c, 'e, DeviceT> InterfaceBuilder<'b, 'c, 'e, DeviceT>
         where DeviceT: for<'d> Device<'d> {
     /// Create a builder used for creating a network interface using the
     /// given device and address.
@@ -115,16 +110,13 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
     ///         .ip_addrs(ip_addrs)
     ///         .finalize();
     /// ```
-    pub fn new(device: DeviceT) -> InterfaceBuilder<'b, 'c, DeviceT> {
+    pub fn new(device: DeviceT) -> InterfaceBuilder<'b, 'c, 'e, DeviceT> {
         InterfaceBuilder {
             device:              device,
             ethernet_addr:       None,
             neighbor_cache:      None,
             ip_addrs:            ManagedSlice::Borrowed(&mut []),
-            #[cfg(feature = "proto-ipv4")]
-            ipv4_gateway:        None,
-            #[cfg(feature = "proto-ipv6")]
-            ipv6_gateway:        None,
+            routes:              Routes::new(ManagedMap::Borrowed(&mut [])),
         }
     }
 
@@ -135,7 +127,7 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
     /// This function panics if the address is not unicast.
     ///
     /// [ethernet_addr]: struct.EthernetInterface.html#method.ethernet_addr
-    pub fn ethernet_addr(mut self, addr: EthernetAddress) -> InterfaceBuilder<'b, 'c, DeviceT> {
+    pub fn ethernet_addr(mut self, addr: EthernetAddress) -> InterfaceBuilder<'b, 'c, 'e, DeviceT> {
         InterfaceInner::check_ethernet_addr(&addr);
         self.ethernet_addr = Some(addr);
         self
@@ -148,7 +140,7 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
     /// This function panics if any of the addresses are not unicast.
     ///
     /// [ip_addrs]: struct.EthernetInterface.html#method.ip_addrs
-    pub fn ip_addrs<T>(mut self, ip_addrs: T) -> InterfaceBuilder<'b, 'c, DeviceT>
+    pub fn ip_addrs<T>(mut self, ip_addrs: T) -> InterfaceBuilder<'b, 'c, 'e, DeviceT>
         where T: Into<ManagedSlice<'c, IpCidr>>
     {
         let ip_addrs = ip_addrs.into();
@@ -157,43 +149,20 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
         self
     }
 
-    /// Set the IPv4 gateway the interface will use. See also
-    /// [ipv4_gateway].
+    /// Set the IP routes the interface will use. See also
+    /// [routes].
     ///
-    /// # Panics
-    /// This function panics if the given address is not unicast.
-    ///
-    /// [ipv4_gateway]: struct.EthernetInterface.html#method.ipv4_gateway
-    #[cfg(feature = "proto-ipv4")]
-    pub fn ipv4_gateway<T>(mut self, gateway: T) -> InterfaceBuilder<'b, 'c, DeviceT>
-        where T: Into<Ipv4Address>
+    /// [routes]: struct.EthernetInterface.html#method.routes
+    pub fn routes<T>(mut self, routes: T) -> InterfaceBuilder<'b, 'c, 'e, DeviceT>
+        where T: Into<Routes<'e>>
     {
-        let addr = gateway.into();
-        InterfaceInner::check_ipv4_gateway_addr(&addr);
-        self.ipv4_gateway = Some(addr);
-        self
-    }
-
-    /// Set the IPv6 gateway the interface will use. See also
-    /// [ipv6_gateway].
-    ///
-    /// # Panics
-    /// This function panics if the given address is not unicast.
-    ///
-    /// [ipv6_gateway]: struct.EthernetInterface.html#method.ipv6_gateway
-    #[cfg(feature = "proto-ipv6")]
-    pub fn ipv6_gateway<T>(mut self, gateway: T) -> InterfaceBuilder<'b, 'c, DeviceT>
-        where T: Into<Ipv6Address>
-    {
-        let addr = gateway.into();
-        InterfaceInner::check_ipv6_gateway_addr(&addr);
-        self.ipv6_gateway = Some(addr);
+        self.routes = routes.into();
         self
     }
 
     /// Set the Neighbor Cache the interface will use.
     pub fn neighbor_cache(mut self, neighbor_cache: NeighborCache<'b>) ->
-                         InterfaceBuilder<'b, 'c, DeviceT> {
+                         InterfaceBuilder<'b, 'c, 'e, DeviceT> {
         self.neighbor_cache = Some(neighbor_cache);
         self
     }
@@ -209,7 +178,7 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
     ///
     /// [ethernet_addr]: #method.ethernet_addr
     /// [neighbor_cache]: #method.neighbor_cache
-    pub fn finalize(self) -> Interface<'b, 'c, DeviceT> {
+    pub fn finalize(self) -> Interface<'b, 'c, 'e, DeviceT> {
         match (self.ethernet_addr, self.neighbor_cache) {
             (Some(ethernet_addr), Some(neighbor_cache)) => {
                 let device_capabilities = self.device.capabilities();
@@ -218,10 +187,7 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT>
                     inner: InterfaceInner {
                         ethernet_addr, device_capabilities, neighbor_cache,
                         ip_addrs: self.ip_addrs,
-                        #[cfg(feature = "proto-ipv4")]
-                        ipv4_gateway: self.ipv4_gateway,
-                        #[cfg(feature = "proto-ipv6")]
-                        ipv6_gateway: self.ipv6_gateway,
+                        routes: self.routes,
                     }
                 }
             },
@@ -280,7 +246,7 @@ fn icmp_reply_payload_len(len: usize, mtu: usize, header_len: usize) -> usize {
     cmp::min(len, mtu - header_len * 2 - 8)
 }
 
-impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
+impl<'b, 'c, 'e, DeviceT> Interface<'b, 'c, 'e, DeviceT>
         where DeviceT: for<'d> Device<'d> {
     /// Get the Ethernet address of the interface.
     pub fn ethernet_addr(&self) -> EthernetAddress {
@@ -315,38 +281,12 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
         self.inner.has_ip_addr(addr)
     }
 
-    /// Get the IPv4 gateway of the interface.
-    #[cfg(feature = "proto-ipv4")]
-    pub fn ipv4_gateway(&self) -> Option<Ipv4Address> {
-        self.inner.ipv4_gateway
-    }
-
-    /// Set the IPv4 gateway of the interface.
-    ///
-    /// # Panics
-    /// This function panics if the given address is not unicast.
-    #[cfg(feature = "proto-ipv4")]
-    pub fn set_ipv4_gateway<GatewayAddrT>(&mut self, gateway: GatewayAddrT)
-            where GatewayAddrT: Into<Option<Ipv4Address>> {
-        self.inner.ipv4_gateway = gateway.into();
-        self.inner.ipv4_gateway.map(|addr| InterfaceInner::check_ipv4_gateway_addr(&addr));
-    }
-
-    /// Get the IPv6 gateway of the interface.
-    #[cfg(feature = "proto-ipv6")]
-    pub fn ipv6_gateway(&self) -> Option<Ipv6Address> {
-        self.inner.ipv6_gateway
+    pub fn routes(&self) -> &'e Routes {
+        &self.inner.routes
     }
 
-    /// Set the IPv6 gateway of the interface.
-    ///
-    /// # Panics
-    /// This function panics if the given address is not unicast.
-    #[cfg(feature = "proto-ipv6")]
-    pub fn set_ipv6_gateway<GatewayAddrT>(&mut self, gateway: GatewayAddrT)
-            where GatewayAddrT: Into<Option<Ipv6Address>> {
-        self.inner.ipv6_gateway = gateway.into();
-        self.inner.ipv6_gateway.map(|addr| InterfaceInner::check_ipv6_gateway_addr(&addr));
+    pub fn routes_mut(&mut self) -> &'e mut Routes {
+        &mut self.inner.routes
     }
 
     /// Transmit packets queued in the given sockets, and receive packets queued
@@ -525,7 +465,7 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
     }
 }
 
-impl<'b, 'c> InterfaceInner<'b, 'c> {
+impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
     fn check_ethernet_addr(addr: &EthernetAddress) {
         if addr.is_multicast() {
             panic!("Ethernet address {} is not unicast", addr)
@@ -540,20 +480,6 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
         }
     }
 
-    #[cfg(feature = "proto-ipv4")]
-    fn check_ipv4_gateway_addr(addr: &Ipv4Address) {
-        if !addr.is_unicast() {
-            panic!("gateway IP address {} is not unicast", addr);
-        }
-    }
-
-    #[cfg(feature = "proto-ipv6")]
-    fn check_ipv6_gateway_addr(addr: &Ipv6Address) {
-        if !addr.is_unicast() {
-            panic!("gateway IP address {} is not unicast", addr);
-        }
-    }
-
     /// 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.
@@ -1238,30 +1164,21 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
             .is_some()
     }
 
-    fn route(&self, addr: &IpAddress) -> Result<IpAddress> {
+    fn route(&self, addr: &IpAddress, timestamp: Instant) -> Result<IpAddress> {
         // Send directly.
         if self.in_same_network(addr) || addr.is_broadcast() {
             return Ok(addr.clone())
         }
 
-        // Route via a gateway.
-        match addr {
-            #[cfg(feature = "proto-ipv4")]
-            &IpAddress::Ipv4(_) => match self.ipv4_gateway {
-                Some(gateway) => Ok(gateway.into()),
-                None => Err(Error::Unaddressable),
-            }
-            #[cfg(feature = "proto-ipv6")]
-            &IpAddress::Ipv6(_) => match self.ipv6_gateway {
-                Some(gateway) => Ok(gateway.into()),
-                None => Err(Error::Unaddressable),
-            }
-            _ => Err(Error::Unaddressable)
+        // Route via a router.
+        match self.routes.lookup(addr, timestamp) {
+            Some(router_addr) => Ok(router_addr),
+            None => Err(Error::Unaddressable),
         }
     }
 
     fn has_neighbor<'a>(&self, addr: &'a IpAddress, timestamp: Instant) -> bool {
-        match self.route(addr) {
+        match self.route(addr, timestamp) {
             Ok(routed_addr) => {
                 self.neighbor_cache
                     .lookup_pure(&routed_addr, timestamp)
@@ -1309,7 +1226,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
             }
         }
 
-        let dst_addr = self.route(dst_addr)?;
+        let dst_addr = self.route(dst_addr, timestamp)?;
 
         match self.neighbor_cache.lookup(&dst_addr, timestamp) {
             NeighborAnswer::Found(hardware_addr) =>
@@ -1435,8 +1352,8 @@ mod test {
 
     use super::Packet;
 
-    fn create_loopback<'a, 'b>() -> (EthernetInterface<'static, 'b, Loopback>,
-                                     SocketSet<'static, 'a, 'b>) {
+    fn create_loopback<'a, 'b, 'c>() -> (EthernetInterface<'static, 'b, 'c, Loopback>,
+                                         SocketSet<'static, 'a, 'b>) {
         // Create a basic device
         let device = Loopback::new();
         let ip_addrs = [

+ 2 - 0
src/iface/mod.rs

@@ -5,10 +5,12 @@ provides lookup and caching of hardware addresses, and handles management packet
 */
 
 mod neighbor;
+mod route;
 mod ethernet;
 
 pub use self::neighbor::Neighbor as Neighbor;
 pub(crate) use self::neighbor::Answer as NeighborAnswer;
 pub use self::neighbor::Cache as NeighborCache;
+pub use self::route::{Route, Routes};
 pub use self::ethernet::{Interface as EthernetInterface,
                          InterfaceBuilder as EthernetInterfaceBuilder};

+ 229 - 0
src/iface/route.rs

@@ -0,0 +1,229 @@
+use managed::ManagedMap;
+use time::Instant;
+use core::ops::Bound;
+
+use {Error, Result};
+use wire::{IpCidr, IpAddress};
+#[cfg(feature = "proto-ipv4")]
+use wire::{Ipv4Address, Ipv4Cidr};
+#[cfg(feature = "proto-ipv6")]
+use wire::{Ipv6Address, Ipv6Cidr};
+
+/// A prefix of addresses that should be routed via a router
+#[derive(Debug, Clone, Copy)]
+pub struct Route {
+    pub via_router: IpAddress,
+    /// `None` means "forever".
+    pub preferred_until: Option<Instant>,
+    /// `None` means "forever".
+    pub expires_at: Option<Instant>,
+}
+
+impl Route {
+    /// Returns a route to 0.0.0.0/0 via the `gateway`, with no expiry.
+    #[cfg(feature = "proto-ipv4")]
+    pub fn new_ipv4_gateway(gateway: Ipv4Address) -> Route {
+        Route {
+            via_router: gateway.into(),
+            preferred_until: None,
+            expires_at: None,
+        }
+    }
+
+    /// Returns a route to ::/0 via the `gateway`, with no expiry.
+    #[cfg(feature = "proto-ipv6")]
+    pub fn new_ipv6_gateway(gateway: Ipv6Address) -> Route {
+        Route {
+            via_router: gateway.into(),
+            preferred_until: None,
+            expires_at: None,
+        }
+    }
+}
+
+/// A routing table.
+///
+/// # Examples
+///
+/// On systems with heap, this table can be created with:
+///
+/// ```rust
+/// use std::collections::BTreeMap;
+/// use smoltcp::iface::Routes;
+/// let mut routes = Routes::new(BTreeMap::new());
+/// ```
+///
+/// On systems without heap, use:
+///
+/// ```rust
+/// use smoltcp::iface::Routes;
+/// let mut routes_storage = [];
+/// let mut routes = Routes::new(&mut routes_storage[..]);
+/// ```
+#[derive(Debug)]
+pub struct Routes<'a> {
+    storage: ManagedMap<'a, IpCidr, Route>,
+}
+
+impl<'a> Routes<'a> {
+    /// Creates a routing tables. The backing storage is **not** cleared
+    /// upon creation.
+    pub fn new<T>(storage: T) -> Routes<'a>
+            where T: Into<ManagedMap<'a, IpCidr, Route>> {
+        let storage = storage.into();
+        Routes { storage }
+    }
+
+    /// Update the routes of this node.
+    pub fn update<F: FnOnce(&mut ManagedMap<'a, IpCidr, Route>)>(&mut self, f: F) {
+        f(&mut self.storage);
+    }
+
+    /// Add a default ipv4 gateway (ie. "ip route add 0.0.0.0/0 via `gateway`").
+    ///
+    /// On success, returns the previous default route, if any.
+    #[cfg(feature = "proto-ipv4")]
+    pub fn add_default_ipv4_route(&mut self, gateway: Ipv4Address) -> Result<Option<Route>> {
+        let cidr = IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0);
+        let route = Route::new_ipv4_gateway(gateway);
+        match self.storage.insert(cidr, route) {
+            Ok(route) => Ok(route),
+            Err((_cidr, _route)) => Err(Error::Exhausted)
+        }
+    }
+
+    /// Add a default ipv6 gateway (ie. "ip -6 route add ::/0 via `gateway`").
+    ///
+    /// On success, returns the previous default route, if any.
+    #[cfg(feature = "proto-ipv6")]
+    pub fn add_default_ipv6_route(&mut self, gateway: Ipv6Address) -> Result<Option<Route>> {
+        let cidr = IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0), 0);
+        let route = Route::new_ipv6_gateway(gateway);
+        match self.storage.insert(cidr, route) {
+            Ok(route) => Ok(route),
+            Err((_cidr, _route)) => Err(Error::Exhausted)
+        }
+    }
+
+    pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) ->
+            Option<IpAddress> {
+        assert!(addr.is_unicast());
+
+        let cidr = match addr {
+            #[cfg(feature = "proto-ipv4")]
+            IpAddress::Ipv4(addr) => IpCidr::Ipv4(Ipv4Cidr::new(*addr, 32)),
+            #[cfg(feature = "proto-ipv6")]
+            IpAddress::Ipv6(addr) => IpCidr::Ipv6(Ipv6Cidr::new(*addr, 128)),
+            _ => unimplemented!()
+        };
+
+        for (prefix, route) in self.storage.range((Bound::Unbounded, Bound::Included(cidr))).rev() {
+            // TODO: do something with route.preferred_until
+            if let Some(expires_at) = route.expires_at {
+                if timestamp > expires_at {
+                    continue;
+                }
+            }
+
+            if prefix.contains_addr(addr) {
+                return Some(route.via_router);
+            }
+        }
+
+        None
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    #[cfg(feature = "proto-ipv6")]
+    mod mock {
+        use super::super::*;
+        pub const ADDR_1A: Ipv6Address = Ipv6Address(
+                [0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1]);
+        pub const ADDR_1B: Ipv6Address = Ipv6Address(
+                [0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 13]);
+        pub const ADDR_1C: Ipv6Address = Ipv6Address(
+                [0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 42]);
+        pub fn cidr_1() -> Ipv6Cidr {
+            Ipv6Cidr::new(Ipv6Address(
+                    [0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]), 64)
+        }
+
+        pub const ADDR_2A: Ipv6Address = Ipv6Address(
+                [0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 1]);
+        pub const ADDR_2B: Ipv6Address = Ipv6Address(
+                [0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 21]);
+        pub fn cidr_2() -> Ipv6Cidr {
+            Ipv6Cidr::new(Ipv6Address(
+                    [0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 0]), 64)
+        }
+    }
+
+    #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
+    mod mock {
+        use super::super::*;
+        pub const ADDR_1A: Ipv4Address = Ipv4Address([192, 0, 2, 1]);
+        pub const ADDR_1B: Ipv4Address = Ipv4Address([192, 0, 2, 13]);
+        pub const ADDR_1C: Ipv4Address = Ipv4Address([192, 0, 2, 42]);
+        pub fn cidr_1() -> Ipv4Cidr {
+            Ipv4Cidr::new(Ipv4Address([192, 0, 2, 0]), 24)
+        }
+
+        pub const ADDR_2A: Ipv4Address = Ipv4Address([198, 51, 100, 1]);
+        pub const ADDR_2B: Ipv4Address = Ipv4Address([198, 51, 100, 21]);
+        pub fn cidr_2() -> Ipv4Cidr {
+            Ipv4Cidr::new(Ipv4Address([198, 51, 100, 0]), 24)
+        }
+    }
+
+    use self::mock::*;
+
+    #[test]
+    fn test_fill() {
+        let mut routes_storage = [None, None, None];
+        let mut routes = Routes::new(&mut routes_storage[..]);
+
+        assert_eq!(routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)), None);
+        assert_eq!(routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)), None);
+        assert_eq!(routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)), None);
+        assert_eq!(routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)), None);
+        assert_eq!(routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)), None);
+
+        let route = Route {
+            via_router: ADDR_1A.into(),
+            preferred_until: None, expires_at: None,
+        };
+        routes.update(|storage| {
+            storage.insert(cidr_1().into(), route).unwrap();
+        });
+
+        assert_eq!(routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
+        assert_eq!(routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
+        assert_eq!(routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
+        assert_eq!(routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)), None);
+        assert_eq!(routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)), None);
+
+        let route2 = Route {
+            via_router: ADDR_2A.into(),
+            preferred_until: Some(Instant::from_millis(10)),
+            expires_at: Some(Instant::from_millis(10)),
+        };
+        routes.update(|storage| {
+            storage.insert(cidr_2().into(), route2).unwrap();
+        });
+
+        assert_eq!(routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
+        assert_eq!(routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
+        assert_eq!(routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)), Some(ADDR_1A.into()));
+        assert_eq!(routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)), Some(ADDR_2A.into()));
+        assert_eq!(routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)), Some(ADDR_2A.into()));
+
+        assert_eq!(routes.lookup(&ADDR_1A.into(), Instant::from_millis(10)), Some(ADDR_1A.into()));
+        assert_eq!(routes.lookup(&ADDR_1B.into(), Instant::from_millis(10)), Some(ADDR_1A.into()));
+        assert_eq!(routes.lookup(&ADDR_1C.into(), Instant::from_millis(10)), Some(ADDR_1A.into()));
+        assert_eq!(routes.lookup(&ADDR_2A.into(), Instant::from_millis(10)), Some(ADDR_2A.into()));
+        assert_eq!(routes.lookup(&ADDR_2B.into(), Instant::from_millis(10)), Some(ADDR_2A.into()));
+    }
+}

+ 14 - 0
src/wire/ip.rs

@@ -325,6 +325,20 @@ impl Cidr {
     }
 }
 
+#[cfg(feature = "proto-ipv4")]
+impl From<Ipv4Cidr> for Cidr {
+    fn from(addr: Ipv4Cidr) -> Self {
+        Cidr::Ipv4(addr)
+    }
+}
+
+#[cfg(feature = "proto-ipv6")]
+impl From<Ipv6Cidr> for Cidr {
+    fn from(addr: Ipv6Cidr) -> Self {
+        Cidr::Ipv6(addr)
+    }
+}
+
 impl fmt::Display for Cidr {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {

+ 5 - 5
src/wire/ndisc.rs

@@ -243,7 +243,7 @@ impl<'a> Repr<'a> {
                     let opt = NdiscOption::new_checked(packet.payload())?;
                     match opt.option_type() {
                         NdiscOptionType::SourceLinkLayerAddr => Some(opt.link_layer_addr()),
-                        _ => None,
+                        _ => { return Err(Error::Unrecognized); }
                     }
                 } else {
                     None
@@ -260,7 +260,7 @@ impl<'a> Repr<'a> {
                         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();
                 }
@@ -278,7 +278,7 @@ impl<'a> Repr<'a> {
                     let opt = NdiscOption::new_checked(packet.payload())?;
                     match opt.option_type() {
                         NdiscOptionType::SourceLinkLayerAddr => Some(opt.link_layer_addr()),
-                        _ => None,
+                        _ => { return Err(Error::Unrecognized); }
                     }
                 } else {
                     None
@@ -292,7 +292,7 @@ impl<'a> Repr<'a> {
                     let opt = NdiscOption::new_checked(packet.payload())?;
                     match opt.option_type() {
                         NdiscOptionType::TargetLinkLayerAddr => Some(opt.link_layer_addr()),
-                        _ => None,
+                        _ => { return Err(Error::Unrecognized); }
                     }
                 } else {
                     None
@@ -326,7 +326,7 @@ impl<'a> Repr<'a> {
                                 offset += 8 + ip_repr.buffer_len() + data.len();
                             }
                         }
-                        _ => ()
+                        _ => { return Err(Error::Unrecognized); }
                     }
                 }
                 Ok(Repr::Redirect {