base: apply Red Bear patches on latest upstream/main
251 files: init, acpid, ipcd, netcfg, ihdgd, virtio-gpud, scheme-utils, inputd, block driver, ptyd, ramfs, randd, initfs bootstrap, path deps, version +rb0.3.1, author attribution
This commit is contained in:
+425
-26
@@ -1,13 +1,16 @@
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
use smoltcp::phy::{Device, DeviceCapabilities, Medium};
|
||||
use smoltcp::storage::PacketMetadata;
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::IpAddress;
|
||||
use smoltcp::wire::{IpAddress, Ipv4Address, Ipv4Packet, Ipv6Packet};
|
||||
|
||||
use self::route_table::RouteTable;
|
||||
use self::route_table::{RouteTable, RouteType};
|
||||
use crate::filter::{FilterTable, Hook, PacketContext, Verdict};
|
||||
use crate::icmp_error;
|
||||
use crate::link::DeviceList;
|
||||
use crate::observer::ObserverRef;
|
||||
use crate::scheme::Smolnetd;
|
||||
|
||||
pub mod route_table;
|
||||
@@ -19,10 +22,12 @@ pub struct Router {
|
||||
tx_buffer: PacketBuffer,
|
||||
devices: Rc<RefCell<DeviceList>>,
|
||||
route_table: Rc<RefCell<RouteTable>>,
|
||||
pub ip_forward: Rc<Cell<bool>>,
|
||||
observer: ObserverRef,
|
||||
}
|
||||
|
||||
impl Router {
|
||||
pub fn new(devices: Rc<RefCell<DeviceList>>, route_table: Rc<RefCell<RouteTable>>) -> Self {
|
||||
pub fn new(devices: Rc<RefCell<DeviceList>>, route_table: Rc<RefCell<RouteTable>>, observer: ObserverRef) -> Self {
|
||||
let rx_buffer = PacketBuffer::new(
|
||||
vec![PacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE],
|
||||
vec![0u8; Router::MTU * Smolnetd::SOCKET_BUFFER_SIZE],
|
||||
@@ -36,6 +41,8 @@ impl Router {
|
||||
tx_buffer,
|
||||
devices,
|
||||
route_table,
|
||||
ip_forward: Rc::new(Cell::new(true)),
|
||||
observer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +56,204 @@ impl Router {
|
||||
can_recv
|
||||
}
|
||||
|
||||
pub fn filter_input(&mut self, filter_table: &Rc<RefCell<FilterTable>>, now: Instant) {
|
||||
let mut filtered: Vec<Vec<u8>> = Vec::new();
|
||||
while let Ok(((), packet)) = self.rx_buffer.dequeue() {
|
||||
if packet.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut table = filter_table.borrow_mut();
|
||||
let context = infer_context(Hook::InputLocal, None, packet);
|
||||
match table.evaluate(&context, now) {
|
||||
Verdict::Accept => {
|
||||
drop(table);
|
||||
filtered.push(packet.to_vec());
|
||||
}
|
||||
Verdict::Reject => {
|
||||
drop(table);
|
||||
let err_fn = if packet[0] >> 4 == 6 {
|
||||
icmp_error::build_icmpv6_port_unreachable
|
||||
} else {
|
||||
icmp_error::build_icmpv4_port_unreachable
|
||||
};
|
||||
if let Some(err_pkt) = err_fn(packet) {
|
||||
if let Ok(buf) = self.tx_buffer.enqueue(err_pkt.len(), ()) {
|
||||
buf.copy_from_slice(&err_pkt);
|
||||
}
|
||||
}
|
||||
debug!("filter: rejected INPUT packet {} → {}", context.src_addr, context.dst_addr);
|
||||
}
|
||||
Verdict::Drop | Verdict::Log => {
|
||||
drop(table);
|
||||
debug!("filter: dropped INPUT packet {} → {}", context.src_addr, context.dst_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
for packet in filtered {
|
||||
let Ok(buf) = self.rx_buffer.enqueue(packet.len(), ()) else {
|
||||
break;
|
||||
};
|
||||
buf.copy_from_slice(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forward_packets(&mut self, filter_table: &Rc<RefCell<FilterTable>>, now: Instant) {
|
||||
if !self.ip_forward.get() {
|
||||
return;
|
||||
}
|
||||
let mut forwarded: Vec<Vec<u8>> = Vec::new();
|
||||
let mut local: Vec<Vec<u8>> = Vec::new();
|
||||
|
||||
while let Ok(((), packet)) = self.rx_buffer.dequeue() {
|
||||
let packet_data: &[u8] = packet;
|
||||
if packet_data.is_empty() || packet_data[0] >> 4 != 4 {
|
||||
local.push(packet_data.to_vec());
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(ipv4) = Ipv4Packet::new_checked(packet_data) else {
|
||||
local.push(packet_data.to_vec());
|
||||
continue;
|
||||
};
|
||||
|
||||
let dst = IpAddress::Ipv4(ipv4.dst_addr());
|
||||
let is_broadcast = ipv4.dst_addr().is_broadcast() || dst.is_multicast();
|
||||
if is_broadcast {
|
||||
local.push(packet.to_vec());
|
||||
continue;
|
||||
}
|
||||
|
||||
let route_info = {
|
||||
let table = self.route_table.borrow();
|
||||
table.lookup_rule(&dst).map(|r| (r.dev.clone(), r.via, r.route_type))
|
||||
};
|
||||
|
||||
let Some((dev_name, _via, route_type)) = route_info else {
|
||||
local.push(packet.to_vec());
|
||||
continue;
|
||||
};
|
||||
|
||||
match route_type {
|
||||
RouteType::Blackhole => continue,
|
||||
RouteType::Unreachable | RouteType::Prohibit => {
|
||||
// Build the ICMPv4 error and queue it for transmit
|
||||
// back to the original sender. Using rx_buffer here
|
||||
// would re-route the error back into the input path
|
||||
// (infinite loop) and never reach the sender.
|
||||
if let Some(error_pkt) = icmp_error::build_icmpv4_port_unreachable(packet) {
|
||||
let _ = self.tx_buffer.enqueue(error_pkt.len(), ())
|
||||
.map(|b| b.copy_from_slice(&error_pkt));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
RouteType::Unicast => {}
|
||||
}
|
||||
let mut buf = packet.to_vec();
|
||||
let context = infer_context(Hook::Forward, Some(dev_name.clone()), &buf);
|
||||
|
||||
{
|
||||
let mut table = filter_table.borrow_mut();
|
||||
if table.evaluate(&context, now) == Verdict::Drop {
|
||||
debug!("filter: dropped FORWARD packet");
|
||||
continue;
|
||||
}
|
||||
if let Some((trans_addr, trans_port)) = table.nat_table.lookup_snat(
|
||||
Hook::Forward,
|
||||
IpAddress::Ipv4(ipv4.src_addr()),
|
||||
dst,
|
||||
) {
|
||||
let IpAddress::Ipv4(new_src) = trans_addr else { continue; };
|
||||
let _ = crate::filter::rewrite_src_ipv4(&mut buf, new_src);
|
||||
if let Some(port) = trans_port {
|
||||
table.nat_table.record_snat(
|
||||
IpAddress::Ipv4(ipv4.src_addr()),
|
||||
IpAddress::Ipv4(new_src),
|
||||
0, port,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ttl = ipv4.hop_limit();
|
||||
if ttl <= 1 {
|
||||
debug!("forward: TTL expired");
|
||||
if let Some(error_pkt) = icmp_error::build_icmpv4_time_exceeded(&buf) {
|
||||
if let Ok(buf) = self.tx_buffer.enqueue(error_pkt.len(), ()) {
|
||||
buf.copy_from_slice(&error_pkt);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
buf[8] = ttl - 1;
|
||||
if let Ok(mut pkt) = Ipv4Packet::new_checked(&mut buf) {
|
||||
pkt.fill_checksum();
|
||||
}
|
||||
|
||||
forwarded.push(buf);
|
||||
}
|
||||
|
||||
for packet in &forwarded {
|
||||
self.observer.capture(packet);
|
||||
}
|
||||
for packet in &local {
|
||||
self.observer.capture(packet);
|
||||
}
|
||||
|
||||
for packet in local {
|
||||
let Ok(buf) = self.rx_buffer.enqueue(packet.len(), ()) else {
|
||||
break;
|
||||
};
|
||||
buf.copy_from_slice(&packet);
|
||||
}
|
||||
for packet in forwarded {
|
||||
let Ok(buf) = self.tx_buffer.enqueue(packet.len(), ()) else {
|
||||
break;
|
||||
};
|
||||
buf.copy_from_slice(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filter_output(&self, packet: &[u8], filter_table: &Rc<RefCell<FilterTable>>, now: Instant) -> Verdict {
|
||||
if packet.is_empty() {
|
||||
return Verdict::Accept;
|
||||
}
|
||||
let context = infer_context(Hook::OutputLocal, None, packet);
|
||||
filter_table.borrow_mut().evaluate(&context, now)
|
||||
}
|
||||
|
||||
fn apply_snat(
|
||||
&self,
|
||||
packet: &mut [u8],
|
||||
filter_table: &Rc<RefCell<FilterTable>>,
|
||||
) {
|
||||
if packet.is_empty() {
|
||||
return;
|
||||
}
|
||||
let version = packet[0] >> 4;
|
||||
if version != 4 {
|
||||
return;
|
||||
}
|
||||
let Ok(ipv4) = Ipv4Packet::new_checked(&*packet) else {
|
||||
return;
|
||||
};
|
||||
let src = IpAddress::Ipv4(ipv4.src_addr());
|
||||
let dst = IpAddress::Ipv4(ipv4.dst_addr());
|
||||
let table = filter_table.borrow();
|
||||
let snat = table.nat_table.lookup_snat(
|
||||
crate::filter::Hook::OutputLocal,
|
||||
src,
|
||||
dst,
|
||||
);
|
||||
drop(table);
|
||||
|
||||
if let Some((trans_addr, _trans_port)) = snat {
|
||||
let IpAddress::Ipv4(new_src) = trans_addr else {
|
||||
return;
|
||||
};
|
||||
crate::filter::rewrite_src_ipv4(packet, new_src);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll(&mut self, now: Instant) {
|
||||
for dev in self.devices.borrow_mut().iter_mut() {
|
||||
if self.rx_buffer.is_full() {
|
||||
@@ -72,41 +277,130 @@ impl Router {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch(&mut self, now: Instant) {
|
||||
pub fn dispatch(&mut self, now: Instant, filter_table: &Rc<RefCell<FilterTable>>) {
|
||||
let mut packets: Vec<Vec<u8>> = Vec::new();
|
||||
while let Ok(((), packet)) = self.tx_buffer.dequeue() {
|
||||
if let Ok(mut packet) = smoltcp::wire::Ipv4Packet::new_checked(packet) {
|
||||
let dst_addr = IpAddress::Ipv4(packet.dst_addr());
|
||||
if packet.dst_addr().is_broadcast() {
|
||||
let buf = packet.into_inner();
|
||||
for dev in self.devices.borrow_mut().iter_mut() {
|
||||
dev.send(dst_addr, buf, now)
|
||||
}
|
||||
} else {
|
||||
let route_table = self.route_table.borrow();
|
||||
let Some(rule) = route_table.lookup_rule(&dst_addr) else {
|
||||
warn!("No route found for destination: {}", dst_addr);
|
||||
if !packet.is_empty() {
|
||||
self.observer.capture(packet);
|
||||
packets.push(packet.to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
for packet in packets {
|
||||
if packet.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match packet[0] >> 4 {
|
||||
4 => {
|
||||
let mut packet_buf = packet;
|
||||
let Ok(mut ipv4_pkt) = smoltcp::wire::Ipv4Packet::new_checked(&mut packet_buf)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let dst_addr = IpAddress::Ipv4(ipv4_pkt.dst_addr());
|
||||
let src_addr = ipv4_pkt.src_addr();
|
||||
|
||||
let next_hop = match rule.via {
|
||||
Some(via) => via,
|
||||
None => dst_addr,
|
||||
let (next_hop, src_rule, dev_name, route_type) = {
|
||||
let route_table = self.route_table.borrow();
|
||||
let Some(rule) = route_table.lookup_rule(&dst_addr) else {
|
||||
warn!("No route found for destination: {}", dst_addr);
|
||||
continue;
|
||||
};
|
||||
let next_hop = rule.via.unwrap_or(dst_addr);
|
||||
(next_hop, rule.src, rule.dev.clone(), rule.route_type)
|
||||
};
|
||||
|
||||
match route_type {
|
||||
RouteType::Blackhole => continue,
|
||||
RouteType::Unreachable => {
|
||||
if let Some(error_pkt) = icmp_error::build_icmpv4_port_unreachable(&packet_buf) {
|
||||
let _ = self.tx_buffer.enqueue(error_pkt.len(), ())
|
||||
.map(|b| b.copy_from_slice(&error_pkt));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
RouteType::Prohibit => {
|
||||
if let Some(error_pkt) = icmp_error::build_icmpv4_port_unreachable(&packet_buf) {
|
||||
let _ = self.tx_buffer.enqueue(error_pkt.len(), ())
|
||||
.map(|b| b.copy_from_slice(&error_pkt));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
RouteType::Unicast => {}
|
||||
}
|
||||
|
||||
if ipv4_pkt.dst_addr().is_broadcast() {
|
||||
let buf = ipv4_pkt.into_inner();
|
||||
if self.filter_output(buf, filter_table, now) == Verdict::Drop {
|
||||
debug!("filter: dropped OUTPUT broadcast IPv4 packet");
|
||||
continue;
|
||||
}
|
||||
self.apply_snat(buf, filter_table);
|
||||
for dev in self.devices.borrow_mut().iter_mut() {
|
||||
dev.send(dst_addr, buf, now);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if let IpAddress::Ipv4(src) = src_rule {
|
||||
if src != src_addr {
|
||||
ipv4_pkt.set_src_addr(src);
|
||||
ipv4_pkt.fill_checksum();
|
||||
}
|
||||
}
|
||||
|
||||
let buf = ipv4_pkt.into_inner();
|
||||
let mut devices = self.devices.borrow_mut();
|
||||
let Some(dev) = devices.get_mut(&rule.dev) else {
|
||||
warn!("Device {} not found", rule.dev);
|
||||
let Some(dev) = devices.get_mut(&dev_name) else {
|
||||
warn!("Device {} not found", dev_name);
|
||||
// TODO: Remove route if device doesn't exist anymore ?
|
||||
continue;
|
||||
};
|
||||
if self.filter_output(buf, filter_table, now) == Verdict::Drop {
|
||||
debug!("filter: dropped OUTPUT IPv4 packet");
|
||||
continue;
|
||||
}
|
||||
self.apply_snat(buf, filter_table);
|
||||
dev.send(next_hop, buf, now);
|
||||
}
|
||||
6 => {
|
||||
let Ok(ipv6_pkt) = smoltcp::wire::Ipv6Packet::new_checked(&packet) else {
|
||||
continue;
|
||||
};
|
||||
let dst_addr = IpAddress::Ipv6(ipv6_pkt.dst_addr());
|
||||
|
||||
let IpAddress::Ipv4(src) = rule.src;
|
||||
if src != packet.src_addr() {
|
||||
packet.set_src_addr(src);
|
||||
packet.fill_checksum()
|
||||
let (next_hop, dev_name, route_type) = {
|
||||
let route_table = self.route_table.borrow();
|
||||
let Some(rule) = route_table.lookup_rule(&dst_addr) else {
|
||||
warn!("No route found for destination: {}", dst_addr);
|
||||
continue;
|
||||
};
|
||||
let next_hop = rule.via.unwrap_or(dst_addr);
|
||||
(next_hop, rule.dev.clone(), rule.route_type)
|
||||
};
|
||||
|
||||
match route_type {
|
||||
RouteType::Blackhole => continue,
|
||||
RouteType::Unreachable | RouteType::Prohibit => continue,
|
||||
RouteType::Unicast => {}
|
||||
}
|
||||
|
||||
dev.send(next_hop, packet.into_inner(), now);
|
||||
let mut devices = self.devices.borrow_mut();
|
||||
let Some(dev) = devices.get_mut(&dev_name) else {
|
||||
warn!("Device {} not found", dev_name);
|
||||
continue;
|
||||
};
|
||||
|
||||
if self.filter_output(&packet, filter_table, now) == Verdict::Drop {
|
||||
debug!("filter: dropped OUTPUT IPv6 packet");
|
||||
continue;
|
||||
}
|
||||
|
||||
dev.send(next_hop, &packet, now);
|
||||
}
|
||||
version => {
|
||||
debug!("Dropped packet with unknown IP version: {}", version);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,3 +482,108 @@ impl<'a> smoltcp::phy::RxToken for RxToken<'a> {
|
||||
f(buf)
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_context(
|
||||
hook: Hook,
|
||||
_dev_name: Option<Rc<str>>,
|
||||
packet: &[u8],
|
||||
) -> PacketContext<'_> {
|
||||
let version = if packet.is_empty() { 0 } else { packet[0] >> 4 };
|
||||
match version {
|
||||
4 => {
|
||||
if let Ok(ipv4) = Ipv4Packet::new_checked(packet) {
|
||||
let (src_port, dst_port) = parse_ports(&ipv4.next_header(), ipv4.payload());
|
||||
PacketContext {
|
||||
hook,
|
||||
in_dev: None,
|
||||
out_dev: None,
|
||||
src_addr: IpAddress::Ipv4(ipv4.src_addr()),
|
||||
dst_addr: IpAddress::Ipv4(ipv4.dst_addr()),
|
||||
protocol: ipv4.next_header().into(),
|
||||
src_port,
|
||||
dst_port,
|
||||
packet,
|
||||
}
|
||||
} else {
|
||||
PacketContext {
|
||||
hook,
|
||||
in_dev: None,
|
||||
out_dev: None,
|
||||
src_addr: IpAddress::Ipv4(smoltcp::wire::Ipv4Address::UNSPECIFIED),
|
||||
dst_addr: IpAddress::Ipv4(smoltcp::wire::Ipv4Address::UNSPECIFIED),
|
||||
protocol: 0,
|
||||
src_port: None,
|
||||
dst_port: None,
|
||||
packet,
|
||||
}
|
||||
}
|
||||
}
|
||||
6 => {
|
||||
if let Ok(ipv6) = Ipv6Packet::new_checked(packet) {
|
||||
let nh = ipv6.next_header();
|
||||
let payload_start = 40; // fixed IPv6 header
|
||||
// Extension headers (Hop-by-Hop, Routing, Fragment, etc.)
|
||||
// would shift the transport header to a higher offset.
|
||||
// smoltcp's next_header() chases extension headers to return
|
||||
// the final protocol, but we don't compute the actual
|
||||
// transport offset. For packets with extension headers the
|
||||
// port extraction below will read the wrong bytes and return
|
||||
// None/None, which is safe: the filter matches on IP+protocol
|
||||
// but silently skips port matching.
|
||||
let payload = if packet.len() > payload_start {
|
||||
&packet[payload_start..]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
let (src_port, dst_port) = parse_ports(&nh, payload);
|
||||
PacketContext {
|
||||
hook,
|
||||
in_dev: None,
|
||||
out_dev: None,
|
||||
src_addr: IpAddress::Ipv6(ipv6.src_addr()),
|
||||
dst_addr: IpAddress::Ipv6(ipv6.dst_addr()),
|
||||
protocol: nh.into(),
|
||||
src_port,
|
||||
dst_port,
|
||||
packet,
|
||||
}
|
||||
} else {
|
||||
PacketContext {
|
||||
hook,
|
||||
in_dev: None,
|
||||
out_dev: None,
|
||||
src_addr: IpAddress::Ipv6(smoltcp::wire::Ipv6Address::UNSPECIFIED),
|
||||
dst_addr: IpAddress::Ipv6(smoltcp::wire::Ipv6Address::UNSPECIFIED),
|
||||
protocol: 0,
|
||||
src_port: None,
|
||||
dst_port: None,
|
||||
packet,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => PacketContext {
|
||||
hook,
|
||||
in_dev: None,
|
||||
out_dev: None,
|
||||
src_addr: IpAddress::Ipv4(smoltcp::wire::Ipv4Address::UNSPECIFIED),
|
||||
dst_addr: IpAddress::Ipv4(smoltcp::wire::Ipv4Address::UNSPECIFIED),
|
||||
protocol: 0,
|
||||
src_port: None,
|
||||
dst_port: None,
|
||||
packet,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ports(protocol: &smoltcp::wire::IpProtocol, payload: &[u8]) -> (Option<u16>, Option<u16>) {
|
||||
let proto_byte: u8 = (*protocol).into();
|
||||
if proto_byte != 6 && proto_byte != 17 && proto_byte != 58 {
|
||||
return (None, None);
|
||||
}
|
||||
if payload.len() < 4 {
|
||||
return (None, None);
|
||||
}
|
||||
let src = u16::from_be_bytes([payload[0], payload[1]]);
|
||||
let dst = u16::from_be_bytes([payload[2], payload[3]]);
|
||||
(Some(src), Some(dst))
|
||||
}
|
||||
|
||||
@@ -3,12 +3,43 @@ use std::rc::Rc;
|
||||
|
||||
use smoltcp::wire::{IpAddress, IpCidr};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RouteType {
|
||||
Unicast,
|
||||
Blackhole,
|
||||
Unreachable,
|
||||
Prohibit,
|
||||
}
|
||||
|
||||
impl RouteType {
|
||||
pub const fn name(self) -> &'static str {
|
||||
match self {
|
||||
RouteType::Unicast => "unicast",
|
||||
RouteType::Blackhole => "blackhole",
|
||||
RouteType::Unreachable => "unreachable",
|
||||
RouteType::Prohibit => "prohibit",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"unicast" => Some(RouteType::Unicast),
|
||||
"blackhole" => Some(RouteType::Blackhole),
|
||||
"unreachable" => Some(RouteType::Unreachable),
|
||||
"prohibit" => Some(RouteType::Prohibit),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Rule {
|
||||
pub filter: IpCidr,
|
||||
pub via: Option<IpAddress>,
|
||||
pub dev: Rc<str>,
|
||||
pub src: IpAddress,
|
||||
pub route_type: RouteType,
|
||||
pub metric: u32,
|
||||
}
|
||||
|
||||
impl Rule {
|
||||
@@ -18,12 +49,32 @@ impl Rule {
|
||||
via,
|
||||
dev,
|
||||
src,
|
||||
route_type: RouteType::Unicast,
|
||||
metric: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_type(
|
||||
filter: IpCidr,
|
||||
via: Option<IpAddress>,
|
||||
dev: Rc<str>,
|
||||
src: IpAddress,
|
||||
route_type: RouteType,
|
||||
) -> Self {
|
||||
Self { filter, via, dev, src, route_type, metric: 0 }
|
||||
}
|
||||
|
||||
pub fn with_metric(mut self, metric: u32) -> Self {
|
||||
self.metric = metric;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rule {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.route_type != RouteType::Unicast {
|
||||
write!(f, "{} ", self.route_type.name())?;
|
||||
}
|
||||
if self.filter.prefix_len() == 0 {
|
||||
write!(f, "default")?;
|
||||
} else {
|
||||
@@ -37,6 +88,10 @@ impl Display for Rule {
|
||||
write!(f, " dev {}", self.dev)?;
|
||||
write!(f, " src {}", self.src)?;
|
||||
|
||||
if self.metric != 0 {
|
||||
write!(f, " metric {}", self.metric)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -48,10 +103,15 @@ pub struct RouteTable {
|
||||
|
||||
impl RouteTable {
|
||||
pub fn lookup_rule(&self, dst: &IpAddress) -> Option<&Rule> {
|
||||
// Find the longest-prefix match. Among rules with the same
|
||||
// prefix length, prefer the one with the lowest metric.
|
||||
// Rules are sorted by (prefix_len, metric) — highest prefix
|
||||
// length first, lowest metric first within each prefix.
|
||||
self.rules
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|rule| rule.filter.contains_addr(dst))
|
||||
.filter(|rule| rule.filter.contains_addr(dst))
|
||||
.min_by_key(|rule| rule.metric)
|
||||
}
|
||||
|
||||
pub fn lookup_src_addr(&self, dst: &IpAddress) -> Option<IpAddress> {
|
||||
@@ -86,6 +146,10 @@ impl RouteTable {
|
||||
rule.src = new_src;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.rules.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RouteTable {
|
||||
|
||||
Reference in New Issue
Block a user