renet/src/iface/route.rs

230 lines
8.4 KiB
Rust

use managed::ManagedMap;
use crate::time::Instant;
use core::ops::Bound;
use crate::{Error, Result};
use crate::wire::{IpCidr, IpAddress};
#[cfg(feature = "proto-ipv4")]
use crate::wire::{Ipv4Address, Ipv4Cidr};
#[cfg(feature = "proto-ipv6")]
use crate::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::<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;
}
}
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()));
}
}