Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kernel/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ linkme = "=0.3.27"
num = { version = "=0.4.0", default-features = false }
num-derive = "=0.3"
num-traits = { git = "https://git.mirrors.dragonos.org.cn/DragonOS-Community/num-traits.git", rev = "1597c1c", default-features = false }
smoltcp = { version = "=0.12.0", git = "https://github.com/DragonOS-Community/smoltcp", rev = "9805dae47858c7247303d9d6d19ca504632c1e4a", default-features = false, features = [
smoltcp = { version = "=0.12.0", git = "https://github.com/DragonOS-Community/smoltcp", rev = "afde7359455a5b9feacd2b8684994232acbf8903", default-features = false, features = [
"alloc",
"medium-ethernet",
"socket-raw",
Expand Down
72 changes: 61 additions & 11 deletions kernel/src/driver/net/deferred_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ use alloc::vec::Vec;
use system_error::SystemError;

pub(super) type NodeId = usize;
pub(super) type RouteIndexKey = [u8; 21];

#[derive(Clone, Copy, Debug)]
enum Node {
Free { next: Option<NodeId> },
Leaf { key: u64, slot: usize },
Leaf { key: RouteIndexKey, slot: usize },
Branch { bit: u8, children: [NodeId; 2] },
}

/// A fallibly allocated Patricia index for attacker-controlled 64-bit keys.
/// A fallibly allocated Patricia index for complete (family, ifindex, address) keys.
///
/// Branch bits strictly increase from root to leaf, bounding every lookup by
/// the key width without depending on secret hash entropy. Node IDs remain
Expand All @@ -29,7 +30,7 @@ impl DeferredRouteIndex {
self.leaves
}

pub(super) fn get(&self, key: u64) -> Option<(NodeId, usize)> {
pub(super) fn get(&self, key: RouteIndexKey) -> Option<(NodeId, usize)> {
let leaf = self.find_leaf(key)?;
match self.nodes[leaf] {
Node::Leaf {
Expand All @@ -48,7 +49,7 @@ impl DeferredRouteIndex {
}

/// Inserts a key after `try_reserve_insert`; this method cannot allocate.
pub(super) fn insert_prepared(&mut self, key: u64, slot: usize) -> NodeId {
pub(super) fn insert_prepared(&mut self, key: RouteIndexKey, slot: usize) -> NodeId {
debug_assert!(self.get(key).is_none());
let Some(root) = self.root else {
let leaf = self.alloc_prepared(Node::Leaf { key, slot });
Expand All @@ -64,8 +65,14 @@ impl DeferredRouteIndex {
Node::Leaf { key, .. } => key,
_ => unreachable!("Patricia traversal ends at a leaf"),
};
let differing_bit = (key ^ existing_key).leading_zeros() as u8;
debug_assert!(differing_bit < 64);
let differing_byte = key
.iter()
.zip(existing_key)
.position(|(a, b)| *a != b)
.expect("inserted Patricia keys are distinct");
let differing_bit = (differing_byte * 8
+ (key[differing_byte] ^ existing_key[differing_byte]).leading_zeros() as usize)
as u8;

let mut parent = None;
let mut current = root;
Expand Down Expand Up @@ -95,7 +102,7 @@ impl DeferredRouteIndex {
leaf
}

pub(super) fn set_slot(&mut self, leaf: NodeId, key: u64, slot: usize) {
pub(super) fn set_slot(&mut self, leaf: NodeId, key: RouteIndexKey, slot: usize) {
match &mut self.nodes[leaf] {
Node::Leaf {
key: leaf_key,
Expand All @@ -108,7 +115,7 @@ impl DeferredRouteIndex {
}
}

pub(super) fn remove(&mut self, key: u64) -> Option<usize> {
pub(super) fn remove(&mut self, key: RouteIndexKey) -> Option<usize> {
let root = self.root?;
if let Node::Leaf {
key: leaf_key,
Expand Down Expand Up @@ -153,7 +160,7 @@ impl DeferredRouteIndex {
Some(slot)
}

fn find_leaf(&self, key: u64) -> Option<NodeId> {
fn find_leaf(&self, key: RouteIndexKey) -> Option<NodeId> {
let mut current = self.root?;
loop {
match self.nodes[current] {
Expand All @@ -166,8 +173,8 @@ impl DeferredRouteIndex {
}
}

fn direction(key: u64, bit: u8) -> usize {
((key >> (63 - bit)) & 1) as usize
fn direction(key: RouteIndexKey, bit: u8) -> usize {
((key[bit as usize / 8] >> (7 - bit % 8)) & 1) as usize
}

fn branch_children(&self, node: NodeId) -> [NodeId; 2] {
Expand Down Expand Up @@ -209,3 +216,46 @@ impl DeferredRouteIndex {
self.free_count += 1;
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn every_key_bit_survives_insert_remove_and_slot_updates() {
let mut index = DeferredRouteIndex::default();
// Include the all-zero key and every differing bit, especially the
// high IPv6 bytes which the old 64-bit key could not represent.
let mut keys = alloc::vec![[0; 21]];
for bit in 0..168 {
let mut key = [0; 21];
key[bit / 8] = 1 << (7 - bit % 8);
keys.push(key);
}
for (slot, key) in keys.iter().copied().enumerate() {
index.try_reserve_insert().unwrap();
index.insert_prepared(key, slot);
}
assert_eq!(index.len(), keys.len());
for (slot, key) in keys.iter().copied().enumerate() {
let (leaf, actual) = index.get(key).unwrap();
assert_eq!(actual, slot);
index.set_slot(leaf, key, slot + 1000);
}
// Alternating removals exercise root replacement and stable leaf IDs.
for parity in 0..2 {
for slot in (parity..keys.len()).step_by(2) {
assert_eq!(index.remove(keys[slot]), Some(slot + 1000));
assert!(index.get(keys[slot]).is_none());
}
}
assert_eq!(index.len(), 0);
for (slot, key) in keys.iter().copied().enumerate().rev() {
index.try_reserve_insert().unwrap();
index.insert_prepared(key, slot);
}
for (slot, key) in keys.iter().copied().enumerate() {
assert_eq!(index.get(key).unwrap().1, slot);
}
}
}
58 changes: 52 additions & 6 deletions kernel/src/driver/net/deferred_queue.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,64 @@
use super::{
deferred_index::{DeferredRouteIndex, NodeId},
deferred_index::{DeferredRouteIndex, NodeId, RouteIndexKey},
local_queue::{LocalOutputDisposition, LocalOutputPacket},
};
use alloc::{collections::VecDeque, vec::Vec};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) struct DeferredRouteKey {
pub(super) oif: u32,
pub(super) next_hop: smoltcp::wire::Ipv4Address,
pub(super) next_hop: smoltcp::wire::IpAddress,
}

impl DeferredRouteKey {
fn packed(self) -> u64 {
((self.oif as u64) << 32) | u32::from_be_bytes(self.next_hop.octets()) as u64
fn packed(self) -> RouteIndexKey {
let mut key = [0; 21];
key[1..5].copy_from_slice(&self.oif.to_be_bytes());
match self.next_hop {
smoltcp::wire::IpAddress::Ipv4(address) => {
key[0] = 4;
key[17..].copy_from_slice(&address.octets());
}
smoltcp::wire::IpAddress::Ipv6(address) => {
key[0] = 6;
key[5..].copy_from_slice(&address.octets());
}
}
key
}
}

#[cfg(test)]
mod key_tests {
use super::*;
use smoltcp::wire::{IpAddress, Ipv4Address, Ipv6Address};

#[test]
fn family_device_and_all_address_bytes_identify_a_neighbor() {
let v4 = DeferredRouteKey {
oif: 2,
next_hop: IpAddress::Ipv4(Ipv4Address::new(192, 0, 2, 1)),
};
let mut bytes = [0; 16];
bytes[12..].copy_from_slice(&[192, 0, 2, 1]);
let v6 = DeferredRouteKey {
oif: 2,
next_hop: IpAddress::Ipv6(Ipv6Address::from(bytes)),
};
assert_ne!(v4.packed(), v6.packed());
assert_ne!(v6.packed(), DeferredRouteKey { oif: 3, ..v6 }.packed());
for byte in 0..16 {
let mut different = bytes;
different[byte] ^= 0x80;
assert_ne!(
v6.packed(),
DeferredRouteKey {
next_hop: IpAddress::Ipv6(Ipv6Address::from(different)),
..v6
}
.packed()
);
}
}
}

Expand Down Expand Up @@ -43,7 +89,7 @@ pub(super) struct DeferredRouteLimits {

/// Per-interface neighbor-resolution backlog.
///
/// The hash index gives direct access by `(oif, next_hop)`, while `heap` is an
/// The Patricia index gives bounded access by `(oif, next_hop)`, while `heap` is an
/// indexed min-heap whose root is the next schedulable neighbor. In-flight
/// buckets sort after every schedulable bucket and therefore never publish a
/// stale retry deadline. Both structures are protected by the containing
Expand Down Expand Up @@ -300,7 +346,7 @@ impl DeferredRouteQueue {
/// compaction and heap rebuilding are each performed only once.
pub(super) fn release_resolved(
&mut self,
mut is_resolved: impl FnMut(smoltcp::wire::Ipv4Address) -> bool,
mut is_resolved: impl FnMut(smoltcp::wire::IpAddress) -> bool,
ready: &mut VecDeque<LocalOutputPacket>,
) {
let mut removed_any = false;
Expand Down
Loading
Loading