Browse Source

iface: use heapless Vec for routes.

Lookup is O(n) now. However, it previously did 32 (or 128 for ipv6!)
map lookups. Since the route table typically doesn't have that many
routes, the new code is likely faster even if it's O(n).
Dario Nieuwenhuis 2 years ago
parent
commit
9beb57a992
8 changed files with 110 additions and 106 deletions
  1. 1 2
      examples/client.rs
  2. 1 3
      examples/dhcp_client.rs
  3. 1 2
      examples/dns.rs
  4. 1 2
      examples/httpclient.rs
  5. 1 2
      examples/ping.rs
  6. 7 7
      src/iface/interface/mod.rs
  7. 69 76
      src/iface/route.rs
  8. 29 12
      src/wire/ipv6.rs

+ 1 - 2
examples/client.rs

@@ -47,8 +47,7 @@ fn main() {
         .push(IpCidr::new(IpAddress::v4(192, 168, 69, 2), 24))
         .unwrap();
     let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
-    let mut routes_storage = [None; 1];
-    let mut routes = Routes::new(&mut routes_storage[..]);
+    let mut routes = Routes::new();
     routes.add_default_ipv4_route(default_v4_gw).unwrap();
 
     let medium = device.capabilities().medium;

+ 1 - 3
examples/dhcp_client.rs

@@ -34,11 +34,9 @@ fn main() {
     ip_addrs
         .push(IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0))
         .unwrap();
-    let mut routes_storage = [None; 1];
-    let routes = Routes::new(&mut routes_storage[..]);
 
     let medium = device.capabilities().medium;
-    let mut builder = InterfaceBuilder::new().ip_addrs(ip_addrs).routes(routes);
+    let mut builder = InterfaceBuilder::new().ip_addrs(ip_addrs);
     if medium == Medium::Ethernet {
         builder = builder
             .hardware_addr(ethernet_addr.into())

+ 1 - 2
examples/dns.rs

@@ -53,8 +53,7 @@ fn main() {
         .unwrap();
     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[..]);
+    let mut routes = Routes::new();
     routes.add_default_ipv4_route(default_v4_gw).unwrap();
     routes.add_default_ipv6_route(default_v6_gw).unwrap();
 

+ 1 - 2
examples/httpclient.rs

@@ -48,8 +48,7 @@ fn main() {
         .unwrap();
     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[..]);
+    let mut routes = Routes::new();
     routes.add_default_ipv4_route(default_v4_gw).unwrap();
     routes.add_default_ipv6_route(default_v6_gw).unwrap();
 

+ 1 - 2
examples/ping.rs

@@ -126,8 +126,7 @@ fn main() {
         .unwrap();
     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[..]);
+    let mut routes = Routes::new();
     routes.add_default_ipv4_route(default_v4_gw).unwrap();
     routes.add_default_ipv6_route(default_v6_gw).unwrap();
 

+ 7 - 7
src/iface/interface/mod.rs

@@ -275,7 +275,7 @@ pub struct InterfaceInner<'a> {
     ip_addrs: Vec<IpCidr, MAX_IP_ADDRS_NUM>,
     #[cfg(feature = "proto-ipv4")]
     any_ip: bool,
-    routes: Routes<'a>,
+    routes: Routes,
     #[cfg(feature = "proto-igmp")]
     ipv4_multicast_groups: ManagedMap<'a, Ipv4Address, ()>,
     /// When to report for (all or) the next multicast group membership via IGMP
@@ -294,7 +294,7 @@ pub struct InterfaceBuilder<'a> {
     ip_addrs: Vec<IpCidr, MAX_IP_ADDRS_NUM>,
     #[cfg(feature = "proto-ipv4")]
     any_ip: bool,
-    routes: Routes<'a>,
+    routes: Routes,
     /// Does not share storage with `ipv6_multicast_groups` to avoid IPv6 size overhead.
     #[cfg(feature = "proto-igmp")]
     ipv4_multicast_groups: ManagedMap<'a, Ipv4Address, ()>,
@@ -371,7 +371,7 @@ let iface = builder.finalize(&mut device);
             ip_addrs: Vec::new(),
             #[cfg(feature = "proto-ipv4")]
             any_ip: false,
-            routes: Routes::new(ManagedMap::Borrowed(&mut [])),
+            routes: Routes::new(),
             #[cfg(feature = "proto-igmp")]
             ipv4_multicast_groups: ManagedMap::Borrowed(&mut []),
             random_seed: 0,
@@ -469,7 +469,7 @@ let iface = builder.finalize(&mut device);
     /// [routes]: struct.Interface.html#method.routes
     pub fn routes<T>(mut self, routes: T) -> InterfaceBuilder<'a>
     where
-        T: Into<Routes<'a>>,
+        T: Into<Routes>,
     {
         self.routes = routes.into();
         self
@@ -1023,11 +1023,11 @@ impl<'a> Interface<'a> {
         self.inner.ipv4_address()
     }
 
-    pub fn routes(&self) -> &Routes<'a> {
+    pub fn routes(&self) -> &Routes {
         &self.inner.routes
     }
 
-    pub fn routes_mut(&mut self) -> &mut Routes<'a> {
+    pub fn routes_mut(&mut self) -> &mut Routes {
         &mut self.inner.routes
     }
 
@@ -1570,7 +1570,7 @@ impl<'a> InterfaceInner<'a> {
             ])
             .unwrap(),
             rand: Rand::new(1234),
-            routes: Routes::new(&mut [][..]),
+            routes: Routes::new(),
 
             #[cfg(feature = "proto-ipv4")]
             any_ip: false,

+ 69 - 76
src/iface/route.rs

@@ -1,6 +1,5 @@
 use crate::time::Instant;
-use core::ops::Bound;
-use managed::ManagedMap;
+use heapless::Vec;
 
 use crate::wire::{IpAddress, IpCidr};
 #[cfg(feature = "proto-ipv4")]
@@ -9,10 +8,13 @@ use crate::wire::{Ipv4Address, Ipv4Cidr};
 use crate::wire::{Ipv6Address, Ipv6Cidr};
 use crate::{Error, Result};
 
+pub const MAX_ROUTE_COUNT: usize = 4;
+
 /// A prefix of addresses that should be routed via a router
 #[derive(Debug, Clone, Copy)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub struct Route {
+    pub cidr: IpCidr,
     pub via_router: IpAddress,
     /// `None` means "forever".
     pub preferred_until: Option<Instant>,
@@ -20,11 +22,18 @@ pub struct Route {
     pub expires_at: Option<Instant>,
 }
 
+#[cfg(feature = "proto-ipv4")]
+const IPV4_DEFAULT: IpCidr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::new(0, 0, 0, 0), 0));
+#[cfg(feature = "proto-ipv6")]
+const IPV6_DEFAULT: IpCidr =
+    IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 0), 0));
+
 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 {
+            cidr: IPV4_DEFAULT,
             via_router: gateway.into(),
             preferred_until: None,
             expires_at: None,
@@ -35,6 +44,7 @@ impl Route {
     #[cfg(feature = "proto-ipv6")]
     pub fn new_ipv6_gateway(gateway: Ipv6Address) -> Route {
         Route {
+            cidr: IPV6_DEFAULT,
             via_router: gateway.into(),
             preferred_until: None,
             expires_at: None,
@@ -43,42 +53,21 @@ impl Route {
 }
 
 /// 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>,
+pub struct Routes {
+    storage: Vec<Route, MAX_ROUTE_COUNT>,
 }
 
-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 }
+impl Routes {
+    /// Creates a new empty routing table.
+    pub fn new() -> Self {
+        Self {
+            storage: Vec::new(),
+        }
     }
 
     /// Update the routes of this node.
-    pub fn update<F: FnOnce(&mut ManagedMap<'a, IpCidr, Route>)>(&mut self, f: F) {
+    pub fn update<F: FnOnce(&mut Vec<Route, MAX_ROUTE_COUNT>)>(&mut self, f: F) {
         f(&mut self.storage);
     }
 
@@ -87,12 +76,11 @@ impl<'a> Routes<'a> {
     /// 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),
-        }
+        let old = self.remove_default_ipv4_route();
+        self.storage
+            .push(Route::new_ipv4_gateway(gateway))
+            .map_err(|_| Error::Exhausted)?;
+        Ok(old)
     }
 
     /// Add a default ipv6 gateway (ie. "ip -6 route add ::/0 via `gateway`").
@@ -100,12 +88,11 @@ impl<'a> Routes<'a> {
     /// 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),
-        }
+        let old = self.remove_default_ipv6_route();
+        self.storage
+            .push(Route::new_ipv6_gateway(gateway))
+            .map_err(|_| Error::Exhausted)?;
+        Ok(old)
     }
 
     /// Remove the default ipv4 gateway
@@ -113,8 +100,16 @@ impl<'a> Routes<'a> {
     /// On success, returns the previous default route, if any.
     #[cfg(feature = "proto-ipv4")]
     pub fn remove_default_ipv4_route(&mut self) -> Option<Route> {
-        let cidr = IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0);
-        self.storage.remove(&cidr)
+        if let Some((i, _)) = self
+            .storage
+            .iter()
+            .enumerate()
+            .find(|(_, r)| r.cidr == IPV4_DEFAULT)
+        {
+            Some(self.storage.remove(i))
+        } else {
+            None
+        }
     }
 
     /// Remove the default ipv6 gateway
@@ -122,38 +117,35 @@ impl<'a> Routes<'a> {
     /// On success, returns the previous default route, if any.
     #[cfg(feature = "proto-ipv6")]
     pub fn remove_default_ipv6_route(&mut self) -> Option<Route> {
-        let cidr = IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0), 0);
-        self.storage.remove(&cidr)
+        if let Some((i, _)) = self
+            .storage
+            .iter()
+            .enumerate()
+            .find(|(_, r)| r.cidr == IPV6_DEFAULT)
+        {
+            Some(self.storage.remove(i))
+        } else {
+            None
+        }
     }
 
     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)),
-        };
-
-        for (prefix, route) in self
-            .storage
-            .range((Bound::Unbounded::<IpCidr>, Bound::Included(cidr)))
-            .rev()
-        {
-            // TODO: do something with route.preferred_until
-            if let Some(expires_at) = route.expires_at {
-                if timestamp > expires_at {
-                    continue;
+        self.storage
+            .iter()
+            // Keep only matching routes
+            .filter(|route| {
+                if let Some(expires_at) = route.expires_at {
+                    if timestamp > expires_at {
+                        return false;
+                    }
                 }
-            }
-
-            if prefix.contains_addr(addr) {
-                return Some(route.via_router);
-            }
-        }
-
-        None
+                route.cidr.contains_addr(addr)
+            })
+            // pick the most specific one (highest prefix_len)
+            .max_by_key(|route| route.cidr.prefix_len())
+            .map(|route| route.via_router)
     }
 }
 
@@ -209,8 +201,7 @@ mod test {
 
     #[test]
     fn test_fill() {
-        let mut routes_storage = [None, None, None];
-        let mut routes = Routes::new(&mut routes_storage[..]);
+        let mut routes = Routes::new();
 
         assert_eq!(
             routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
@@ -234,12 +225,13 @@ mod test {
         );
 
         let route = Route {
+            cidr: cidr_1().into(),
             via_router: ADDR_1A.into(),
             preferred_until: None,
             expires_at: None,
         };
         routes.update(|storage| {
-            storage.insert(cidr_1().into(), route).unwrap();
+            storage.push(route).unwrap();
         });
 
         assert_eq!(
@@ -264,12 +256,13 @@ mod test {
         );
 
         let route2 = Route {
+            cidr: cidr_2().into(),
             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();
+            storage.push(route2).unwrap();
         });
 
         assert_eq!(

+ 29 - 12
src/wire/ipv6.rs

@@ -68,17 +68,34 @@ impl Address {
 
     /// Construct an IPv6 address from parts.
     #[allow(clippy::too_many_arguments)]
-    pub fn new(a0: u16, a1: u16, a2: u16, a3: u16, a4: u16, a5: u16, a6: u16, a7: u16) -> Address {
-        let mut addr = [0u8; ADDR_SIZE];
-        NetworkEndian::write_u16(&mut addr[..2], a0);
-        NetworkEndian::write_u16(&mut addr[2..4], a1);
-        NetworkEndian::write_u16(&mut addr[4..6], a2);
-        NetworkEndian::write_u16(&mut addr[6..8], a3);
-        NetworkEndian::write_u16(&mut addr[8..10], a4);
-        NetworkEndian::write_u16(&mut addr[10..12], a5);
-        NetworkEndian::write_u16(&mut addr[12..14], a6);
-        NetworkEndian::write_u16(&mut addr[14..], a7);
-        Address(addr)
+    pub const fn new(
+        a0: u16,
+        a1: u16,
+        a2: u16,
+        a3: u16,
+        a4: u16,
+        a5: u16,
+        a6: u16,
+        a7: u16,
+    ) -> Address {
+        Address([
+            (a0 >> 8) as u8,
+            a0 as u8,
+            (a1 >> 8) as u8,
+            a1 as u8,
+            (a2 >> 8) as u8,
+            a2 as u8,
+            (a3 >> 8) as u8,
+            a3 as u8,
+            (a4 >> 8) as u8,
+            a4 as u8,
+            (a5 >> 8) as u8,
+            a5 as u8,
+            (a6 >> 8) as u8,
+            a6 as u8,
+            (a7 >> 8) as u8,
+            a7 as u8,
+        ])
     }
 
     /// Construct an IPv6 address from a sequence of octets, in big-endian.
@@ -324,7 +341,7 @@ impl Cidr {
     ///
     /// # Panics
     /// This function panics if the prefix length is larger than 128.
-    pub fn new(address: Address, prefix_len: u8) -> Cidr {
+    pub const fn new(address: Address, prefix_len: u8) -> Cidr {
         assert!(prefix_len <= 128);
         Cidr {
             address,