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))
         .push(IpCidr::new(IpAddress::v4(192, 168, 69, 2), 24))
         .unwrap();
         .unwrap();
     let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
     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();
     routes.add_default_ipv4_route(default_v4_gw).unwrap();
 
 
     let medium = device.capabilities().medium;
     let medium = device.capabilities().medium;

+ 1 - 3
examples/dhcp_client.rs

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

+ 1 - 2
examples/dns.rs

@@ -53,8 +53,7 @@ fn main() {
         .unwrap();
         .unwrap();
     let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
     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 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_ipv4_route(default_v4_gw).unwrap();
     routes.add_default_ipv6_route(default_v6_gw).unwrap();
     routes.add_default_ipv6_route(default_v6_gw).unwrap();
 
 

+ 1 - 2
examples/httpclient.rs

@@ -48,8 +48,7 @@ fn main() {
         .unwrap();
         .unwrap();
     let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
     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 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_ipv4_route(default_v4_gw).unwrap();
     routes.add_default_ipv6_route(default_v6_gw).unwrap();
     routes.add_default_ipv6_route(default_v6_gw).unwrap();
 
 

+ 1 - 2
examples/ping.rs

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

+ 69 - 76
src/iface/route.rs

@@ -1,6 +1,5 @@
 use crate::time::Instant;
 use crate::time::Instant;
-use core::ops::Bound;
-use managed::ManagedMap;
+use heapless::Vec;
 
 
 use crate::wire::{IpAddress, IpCidr};
 use crate::wire::{IpAddress, IpCidr};
 #[cfg(feature = "proto-ipv4")]
 #[cfg(feature = "proto-ipv4")]
@@ -9,10 +8,13 @@ use crate::wire::{Ipv4Address, Ipv4Cidr};
 use crate::wire::{Ipv6Address, Ipv6Cidr};
 use crate::wire::{Ipv6Address, Ipv6Cidr};
 use crate::{Error, Result};
 use crate::{Error, Result};
 
 
+pub const MAX_ROUTE_COUNT: usize = 4;
+
 /// A prefix of addresses that should be routed via a router
 /// A prefix of addresses that should be routed via a router
 #[derive(Debug, Clone, Copy)]
 #[derive(Debug, Clone, Copy)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 pub struct Route {
 pub struct Route {
+    pub cidr: IpCidr,
     pub via_router: IpAddress,
     pub via_router: IpAddress,
     /// `None` means "forever".
     /// `None` means "forever".
     pub preferred_until: Option<Instant>,
     pub preferred_until: Option<Instant>,
@@ -20,11 +22,18 @@ pub struct Route {
     pub expires_at: Option<Instant>,
     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 {
 impl Route {
     /// Returns a route to 0.0.0.0/0 via the `gateway`, with no expiry.
     /// Returns a route to 0.0.0.0/0 via the `gateway`, with no expiry.
     #[cfg(feature = "proto-ipv4")]
     #[cfg(feature = "proto-ipv4")]
     pub fn new_ipv4_gateway(gateway: Ipv4Address) -> Route {
     pub fn new_ipv4_gateway(gateway: Ipv4Address) -> Route {
         Route {
         Route {
+            cidr: IPV4_DEFAULT,
             via_router: gateway.into(),
             via_router: gateway.into(),
             preferred_until: None,
             preferred_until: None,
             expires_at: None,
             expires_at: None,
@@ -35,6 +44,7 @@ impl Route {
     #[cfg(feature = "proto-ipv6")]
     #[cfg(feature = "proto-ipv6")]
     pub fn new_ipv6_gateway(gateway: Ipv6Address) -> Route {
     pub fn new_ipv6_gateway(gateway: Ipv6Address) -> Route {
         Route {
         Route {
+            cidr: IPV6_DEFAULT,
             via_router: gateway.into(),
             via_router: gateway.into(),
             preferred_until: None,
             preferred_until: None,
             expires_at: None,
             expires_at: None,
@@ -43,42 +53,21 @@ impl Route {
 }
 }
 
 
 /// A routing table.
 /// 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)]
 #[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.
     /// 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);
         f(&mut self.storage);
     }
     }
 
 
@@ -87,12 +76,11 @@ impl<'a> Routes<'a> {
     /// On success, returns the previous default route, if any.
     /// On success, returns the previous default route, if any.
     #[cfg(feature = "proto-ipv4")]
     #[cfg(feature = "proto-ipv4")]
     pub fn add_default_ipv4_route(&mut self, gateway: Ipv4Address) -> Result<Option<Route>> {
     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`").
     /// 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.
     /// On success, returns the previous default route, if any.
     #[cfg(feature = "proto-ipv6")]
     #[cfg(feature = "proto-ipv6")]
     pub fn add_default_ipv6_route(&mut self, gateway: Ipv6Address) -> Result<Option<Route>> {
     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
     /// Remove the default ipv4 gateway
@@ -113,8 +100,16 @@ impl<'a> Routes<'a> {
     /// On success, returns the previous default route, if any.
     /// On success, returns the previous default route, if any.
     #[cfg(feature = "proto-ipv4")]
     #[cfg(feature = "proto-ipv4")]
     pub fn remove_default_ipv4_route(&mut self) -> Option<Route> {
     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
     /// Remove the default ipv6 gateway
@@ -122,38 +117,35 @@ impl<'a> Routes<'a> {
     /// On success, returns the previous default route, if any.
     /// On success, returns the previous default route, if any.
     #[cfg(feature = "proto-ipv6")]
     #[cfg(feature = "proto-ipv6")]
     pub fn remove_default_ipv6_route(&mut self) -> Option<Route> {
     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> {
     pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) -> Option<IpAddress> {
         assert!(addr.is_unicast());
         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]
     #[test]
     fn test_fill() {
     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!(
         assert_eq!(
             routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
             routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)),
@@ -234,12 +225,13 @@ mod test {
         );
         );
 
 
         let route = Route {
         let route = Route {
+            cidr: cidr_1().into(),
             via_router: ADDR_1A.into(),
             via_router: ADDR_1A.into(),
             preferred_until: None,
             preferred_until: None,
             expires_at: None,
             expires_at: None,
         };
         };
         routes.update(|storage| {
         routes.update(|storage| {
-            storage.insert(cidr_1().into(), route).unwrap();
+            storage.push(route).unwrap();
         });
         });
 
 
         assert_eq!(
         assert_eq!(
@@ -264,12 +256,13 @@ mod test {
         );
         );
 
 
         let route2 = Route {
         let route2 = Route {
+            cidr: cidr_2().into(),
             via_router: ADDR_2A.into(),
             via_router: ADDR_2A.into(),
             preferred_until: Some(Instant::from_millis(10)),
             preferred_until: Some(Instant::from_millis(10)),
             expires_at: Some(Instant::from_millis(10)),
             expires_at: Some(Instant::from_millis(10)),
         };
         };
         routes.update(|storage| {
         routes.update(|storage| {
-            storage.insert(cidr_2().into(), route2).unwrap();
+            storage.push(route2).unwrap();
         });
         });
 
 
         assert_eq!(
         assert_eq!(

+ 29 - 12
src/wire/ipv6.rs

@@ -68,17 +68,34 @@ impl Address {
 
 
     /// Construct an IPv6 address from parts.
     /// Construct an IPv6 address from parts.
     #[allow(clippy::too_many_arguments)]
     #[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.
     /// Construct an IPv6 address from a sequence of octets, in big-endian.
@@ -324,7 +341,7 @@ impl Cidr {
     ///
     ///
     /// # Panics
     /// # Panics
     /// This function panics if the prefix length is larger than 128.
     /// 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);
         assert!(prefix_len <= 128);
         Cidr {
         Cidr {
             address,
             address,