blob: 1d819fb59c13d5d8febe92723598838c2e849bc1 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fmt::{self, Debug, Formatter};
use crate::device::DeviceId;
use crate::ip::*;
// TODO(joshlf):
// - Implement route deletion.
// - How do we detect circular routes? Do we attempt to detect at rule
// installation time? At runtime? Using what algorithm?
// NOTE on loopback addresses: Loopback addresses should be handled before
// reaching the forwarding table. For that reason, we do not prevent a rule
// whose subnet is a subset of the loopback subnet from being installed; they
// will never get triggered anyway, so implementing the logic of detecting these
// rules is a needless complexity.
/// The destination of an outbound IP packet.
///
/// Outbound IP packets are sent to a particular device (specified by the
/// `device` field). They are sent to a particular IP host on the local network
/// attached to that device, identified by `next_hop`. Note that `next_hop` is
/// not necessarily the destination IP address of the IP packet. In particular,
/// if the destination is not on the local network, the `next_hop` will be the
/// IP address of the next IP router on the way to the destination.
pub struct Destination<I: Ip> {
pub next_hop: I::Addr,
pub device: DeviceId,
}
impl<I: Ip> Debug for Destination<I> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
// This is the same format we'd get using #[derive(Debug)], but that
// would require that I: Debug, which doesn't hold for all I: Ip.
f.debug_struct("Destination")
.field("next_hop", &self.next_hop)
.field("device", &self.device)
.finish()
}
}
#[derive(Copy, Clone)]
struct Entry<I: Ip> {
subnet: Subnet<I::Addr>,
dest: EntryDest<I::Addr>,
}
#[derive(Copy, Clone)]
enum EntryDest<A> {
Local { device: DeviceId },
Remote { next_hop: A },
}
/// An IP forwarding table.
///
/// `ForwardingTable` maps destination subnets to the nearest IP hosts (on the
/// local network) able to route IP packets to those subnets.
#[derive(Default)]
pub struct ForwardingTable<I: Ip> {
entries: Vec<Entry<I>>,
}
impl<I: Ip> ForwardingTable<I> {
pub fn add_route(&mut self, subnet: Subnet<I::Addr>, next_hop: I::Addr) {
debug!("adding route: {} -> {}", subnet, next_hop);
self.entries.push(Entry {
subnet,
dest: EntryDest::Remote { next_hop },
});
}
pub fn add_device_route(&mut self, subnet: Subnet<I::Addr>, device: DeviceId) {
debug!("adding device route: {} -> {}", subnet, device);
self.entries.push(Entry {
subnet,
dest: EntryDest::Local { device },
});
}
/// Look up an address in the table.
///
/// Look up an IP address in the table, returning a next hop IP address and
/// a device to send over. If `address` is link-local, then the returned
/// next hop will be `address`. Otherwise, it will be the link-local address
/// of an IP router capable of delivering packets to `address`.
///
/// If `address` matches an entry which maps to an IP address, `lookup` will
/// look that address up in the table as well, continuing until a link-local
/// address and device are found.
///
/// If multiple entries match `address` or any intermediate IP address, the
/// entry with the longest prefix will be chosen.
///
/// # Panics
///
/// `lookup` asserts that `address` is not in the loopback interface.
/// Traffic destined for loopback addresses from local applications should
/// be properly routed without consulting the forwarding table, and traffic
/// from the network with a loopback destination address is invalid and
/// should be dropped before consulting the forwarding table.
pub fn lookup(&self, address: I::Addr) -> Option<Destination<I>> {
assert!(
!I::LOOPBACK_SUBNET.contains(address),
"loopback addresses should be handled before consulting the forwarding table"
);
let dst = self.lookup_helper(address);
trace!("lookup({}) -> {:?}", address, dst);
dst
}
fn lookup_helper(&self, address: I::Addr) -> Option<Destination<I>> {
let best_match = self
.entries
.iter()
.filter_map(|e| {
if e.subnet.contains(address) {
Some(e)
} else {
None
}
})
.max_by_key(|e| e.subnet.prefix());
match best_match {
Some(Entry {
dest: EntryDest::Local { device },
..
}) => Some(Destination {
next_hop: address,
device: *device,
}),
Some(Entry {
dest: EntryDest::Remote { next_hop },
..
}) => self.lookup_helper(*next_hop),
None => None,
}
}
}