From 25d6decc852bdc294c20174ee33eb37c3cc02de8 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Wed, 8 Jul 2026 15:02:40 +0300 Subject: [PATCH] =?UTF-8?q?route:=20add=20route=20types=20=E2=80=94=20blac?= =?UTF-8?q?khole,=20unreachable,=20prohibit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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'. --- netstack/src/router/mod.rs | 63 +++++++++++++++++++++++++----- netstack/src/router/route_table.rs | 44 +++++++++++++++++++++ 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/netstack/src/router/mod.rs b/netstack/src/router/mod.rs index 447de11ac1..5e9dbc0b76 100644 --- a/netstack/src/router/mod.rs +++ b/netstack/src/router/mod.rs @@ -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, Option)> = 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); diff --git a/netstack/src/router/route_table.rs b/netstack/src/router/route_table.rs index 51943c932f..5476397295 100644 --- a/netstack/src/router/route_table.rs +++ b/netstack/src/router/route_table.rs @@ -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 { + 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, pub dev: Rc, 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, + dev: Rc, + 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 {