route: add route types — blackhole, unreachable, prohibit

RouteTable Rule gains route_type: RouteType field:
- Unicast (default): normal forwarding
- Blackhole: silently drop, no ICMP error
- Unreachable: drop + send ICMP Destination Unreachable
- Prohibit: drop + send ICMP Administratively Prohibited

Route type checked in all forwarding/outbound paths:
- Forward path (IPv4): blackhole/unreachable/prohibit drop + error
- Output path (IPv4): blackhole/unreachable/prohibit drop + error
- Output path (IPv6): blackhole/unreachable/prohibit drop

Usage: Rule::with_type(cidr, via, dev, src, RouteType::Blackhole)
Existing Rule::new() creates Unicast routes (backward compatible).

Display: blackhole/unreachable/prohibit prefixed in route list output.
Mirrors Linux iproute2 'ip route add blackhole 10.0.0.0/8'.
This commit is contained in:
Red Bear OS
2026-07-08 15:02:40 +03:00
parent 107e3b6851
commit 25d6decc85
2 changed files with 98 additions and 9 deletions
+54 -9
View File
@@ -6,7 +6,7 @@ use smoltcp::storage::PacketMetadata;
use smoltcp::time::Instant;
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;
@@ -115,14 +115,34 @@ impl Router {
continue;
}
let route_info: Option<(Rc<str>, Option<IpAddress>)> = self.route_table.borrow()
.lookup_rule(&dst)
.map(|r| (r.dev.clone(), r.via));
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_info else {
let Some((dev_name, _via, route_type)) = route_info else {
local.push(packet.to_vec());
continue;
};
match route_type {
RouteType::Blackhole => continue,
RouteType::Unreachable => {
if let Some(error_pkt) = icmp_error::build_icmpv4_port_unreachable(packet) {
let _ = self.rx_buffer.enqueue(error_pkt.len(), ())
.map(|mut b| b.copy_from_slice(&error_pkt));
}
continue;
}
RouteType::Prohibit => {
if let Some(error_pkt) = icmp_error::build_icmpv4_port_unreachable(packet) {
let _ = self.rx_buffer.enqueue(error_pkt.len(), ())
.map(|mut 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);
@@ -261,16 +281,35 @@ impl Router {
let dst_addr = IpAddress::Ipv4(ipv4_pkt.dst_addr());
let src_addr = ipv4_pkt.src_addr();
let (next_hop, src_rule, dev_name) = {
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())
(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(|mut 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(|mut 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 {
@@ -311,16 +350,22 @@ impl Router {
};
let dst_addr = IpAddress::Ipv6(ipv6_pkt.dst_addr());
let (next_hop, dev_name) = {
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())
(next_hop, rule.dev.clone(), rule.route_type)
};
match route_type {
RouteType::Blackhole => continue,
RouteType::Unreachable | RouteType::Prohibit => continue,
RouteType::Unicast => {}
}
let mut devices = self.devices.borrow_mut();
let Some(dev) = devices.get_mut(&dev_name) else {
warn!("Device {} not found", dev_name);
+44
View File
@@ -3,12 +3,42 @@ 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,
}
impl Rule {
@@ -18,12 +48,26 @@ impl Rule {
via,
dev,
src,
route_type: RouteType::Unicast,
}
}
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 }
}
}
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 {