use crate::layer::{Error, Result};
use crate::managed::{List, Slice};
use crate::time::{Expiration, Instant};
use crate::wire::ip::{v4, v6, Address, Cidr, Subnet};
#[derive(Debug, Clone, Copy)]
pub struct Route {
pub net: Subnet,
pub next_hop: Address,
pub expires_at: Expiration,
}
impl Route {
pub fn unspecified() -> Self {
Route {
net: Cidr::new(Address::v4(0, 0, 0, 0), 0).subnet(),
next_hop: Address::Unspecified,
expires_at: Expiration::Never,
}
}
pub fn ipv4_invalid() -> Self {
Route {
net: Cidr::new(Address::v4(0, 0, 0, 0), 0).subnet(),
next_hop: Address::v4(0, 0, 0, 0).into(),
expires_at: Expiration::Never,
}
}
pub fn ipv6_invalid() -> Self {
Route {
net: Cidr::new(Address::v6(0, 0, 0, 0, 0, 0, 0, 0), 0).subnet(),
next_hop: Address::v6(0, 0, 0, 0, 0, 0, 0, 0).into(),
expires_at: Expiration::Never,
}
}
pub fn new_ipv4_gateway(gateway: v4::Address) -> Route {
Route {
net: Cidr::new(Address::v4(0, 0, 0, 0), 0).subnet(),
next_hop: gateway.into(),
expires_at: Expiration::Never,
}
}
pub fn new_ipv6_gateway(gateway: v6::Address) -> Route {
Route {
net: Cidr::new(Address::v6(0, 0, 0, 0, 0, 0, 0, 0), 0).subnet(),
next_hop: gateway.into(),
expires_at: Expiration::Never,
}
}
}
#[derive(Debug)]
pub struct Routes<'a> {
storage: List<'a, Route>,
}
impl<'a> Routes<'a> {
pub fn new<T>(storage: T) -> Self
where T: Into<Slice<'a, Route>>
{
Routes::import(List::new(storage.into()))
}
pub fn import(storage: List<'a, Route>) -> Self {
Routes { storage }
}
pub fn update<F: FnOnce(&mut [Route])>(&mut self, f: F) {
f(&mut self.storage);
}
pub fn add_route(&mut self, route: Route) -> Result<()> {
match self.storage.push() {
Some(place) => Ok(*place = route),
None => Err(Error::Exhausted),
}
}
pub fn lookup(&self, addr: Address, timestamp: Instant)
-> Option<Address>
{
assert!(addr.is_unicast());
let mut best_match = None;
for route in self.storage.iter() {
if Expiration::When(timestamp) > route.expires_at {
continue;
}
if !route.net.contains(addr) {
continue;
}
let best = best_match.get_or_insert(route);
if best.net.prefix_len() < route.net.prefix_len() {
*best = route;
}
}
best_match.map(|route| route.next_hop)
}
}
#[cfg(test)]
mod test {
use super::*;
mod mock {
use crate::wire::ip::v6::{Address as Ipv6Address, Cidr as Ipv6Cidr};
pub(crate) const ADDR_1A: Ipv6Address = Ipv6Address(
[0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1]);
pub(crate) const ADDR_1B: Ipv6Address = Ipv6Address(
[0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 13]);
pub(crate) const ADDR_1C: Ipv6Address = Ipv6Address(
[0xfe, 0x80, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 42]);
pub(crate) 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(crate) const ADDR_2A: Ipv6Address = Ipv6Address(
[0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 1]);
pub(crate) const ADDR_2B: Ipv6Address = Ipv6Address(
[0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 21]);
pub(crate) fn cidr_2() -> Ipv6Cidr {
Ipv6Cidr::new(Ipv6Address(
[0xfe, 0x80, 0, 0, 0, 0, 51, 100, 0, 0, 0, 0, 0, 0, 0, 0]), 64)
}
}
use self::mock::*;
#[test]
fn test_fill() {
let routes_storage = vec![Route::ipv4_invalid(); 3];
let mut routes = Routes::new(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 {
net: cidr_1().subnet().into(),
next_hop: ADDR_1A.into(),
expires_at: Expiration::Never,
};
routes.add_route(route)
.expect("Can add single route");
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 {
net: cidr_2().subnet().into(),
next_hop: ADDR_2A.into(),
expires_at: Expiration::When(Instant::from_millis(10)),
};
routes.add_route(route2)
.expect("Can add second route");
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()));
}
}