diff --git a/.$design.drawio.bkp b/.$design.drawio.bkp
new file mode 100644
index 000000000..dbd02f992
--- /dev/null
+++ b/.$design.drawio.bkp
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.$design.svg.bkp b/.$design.svg.bkp
new file mode 100644
index 000000000..5db03b151
--- /dev/null
+++ b/.$design.svg.bkp
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.$notes.drawio.svg.bkp b/.$notes.drawio.svg.bkp
new file mode 100644
index 000000000..68ce1e936
--- /dev/null
+++ b/.$notes.drawio.svg.bkp
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index a51511ac4..efdb6197a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -765,7 +765,6 @@ dependencies = [
"clap",
"ctrlc",
"dataplane-dpdk",
- "dataplane-id",
"dataplane-mgmt",
"dataplane-nat",
"dataplane-net",
@@ -774,18 +773,10 @@ dependencies = [
"dataplane-routing",
"dataplane-stats",
"dataplane-vpcmap",
- "dyn-iter",
- "hyper",
- "hyper-util",
"metrics",
"metrics-exporter-prometheus",
"mio",
"netdev",
- "once_cell",
- "ordermap",
- "parking_lot",
- "serde",
- "serde_yml",
"tokio",
"tracing",
"tracing-subscriber",
@@ -849,6 +840,14 @@ dependencies = [
name = "dataplane-dpdk-sysroot-helper"
version = "0.0.1"
+[[package]]
+name = "dataplane-driver"
+version = "0.1.0"
+dependencies = [
+ "dataplane-net",
+ "dataplane-pipeline",
+]
+
[[package]]
name = "dataplane-errno"
version = "0.1.0"
@@ -966,6 +965,7 @@ dependencies = [
"arrayvec",
"bitflags 2.9.1",
"bolero",
+ "dataplane-id",
"derive_builder 0.20.2",
"etherparse",
"linux-raw-sys 0.10.0",
diff --git a/Cargo.toml b/Cargo.toml
index e800eb5e6..e458dbed4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ members = [
"dpdk",
"dpdk-sys",
"dpdk-sysroot-helper",
+ "driver",
"errno",
"id",
"interface-manager",
@@ -31,7 +32,7 @@ config = { path = "./config", package = "dataplane-config" }
dpdk = { path = "./dpdk", package = "dataplane-dpdk" }
dpdk-sys = { path = "./dpdk-sys", package = "dataplane-dpdk-sys" }
dpdk-sysroot-helper = { path = "./dpdk-sysroot-helper", package = "dataplane-dpdk-sysroot-helper" }
-dplane-rpc = { git = "https://github.com/githedgehog/dplane-rpc.git", version = "1.1.2"}
+dplane-rpc = { git = "https://github.com/githedgehog/dplane-rpc.git", version = "1.1.2" }
errno = { path = "./errno", package = "dataplane-errno" }
gateway_config = { git = "https://github.com/githedgehog/gateway-proto", tag = "v0.12.0", version = "0.12.0" }
id = { path = "./id", package = "dataplane-id" }
@@ -72,7 +73,7 @@ etherparse = { version = "0.18.2", default-features = false, features = [] }
fixin = { git = "https://github.com/githedgehog/fixin", branch = "main" }
futures = { version = "0.3.31", default-features = false, features = [] }
hyper = { version = "1.6.0", default-features = false, features = ["http1", "server"] }
-hyper-util = { version = "0.1.16", features = ["tokio"]}
+hyper-util = { version = "0.1.16", features = ["tokio"] }
ipnet = { version = "2.11.0", default-features = false, features = [] }
left-right = { version = "0.11.5" }
libc = { version = "1.0.0-alpha.1", default-features = false, features = [] }
diff --git a/config/src/errors.rs b/config/src/errors.rs
index a417f7e8c..067cb4509 100644
--- a/config/src/errors.rs
+++ b/config/src/errors.rs
@@ -77,6 +77,8 @@ pub enum ConfigError {
InvalidIpAddress(String),
#[error("Invalid mask length in interface address: {0}")]
InvalidMaskLength(String),
+ #[error("Invalid configuration: {0}")]
+ Invalid(String),
}
/// Result-like type for configurations
diff --git a/dataplane/.$design.drawio.bkp b/dataplane/.$design.drawio.bkp
new file mode 100644
index 000000000..cd12db673
--- /dev/null
+++ b/dataplane/.$design.drawio.bkp
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dataplane/Cargo.toml b/dataplane/Cargo.toml
index 3bc7535e2..d4f295c7c 100644
--- a/dataplane/Cargo.toml
+++ b/dataplane/Cargo.toml
@@ -13,10 +13,6 @@ axum-server = { workspace = true }
clap = { workspace = true, features = ["std", "derive", "usage"] }
ctrlc = { workspace = true, features = ["termination"] }
dpdk = { workspace = true }
-dyn-iter = { workspace = true }
-hyper = { workspace = true }
-hyper-util = { workspace = true }
-id = { workspace = true }
metrics = { workspace = true }
metrics-exporter-prometheus = { workspace = true }
mgmt = { workspace = true }
@@ -24,18 +20,13 @@ mio = { workspace = true, features = ["os-ext", "net"] }
nat = { workspace = true }
net = { workspace = true, features = ["test_buffer"] }
netdev = { workspace = true }
-once_cell = { workspace = true }
-ordermap = { workspace = true, features = ["std"] }
-parking_lot = { workspace = true }
pipeline = { workspace = true }
pkt-meta = { workspace = true }
routing = { workspace = true }
-serde = { workspace = true, features = ["derive"] }
-serde_yml = { workspace = true }
-tokio = { workspace = true }
stats = { workspace = true }
+tokio = { workspace = true }
tracing = { workspace = true }
-tracing-subscriber = { workspace = true, features = ["default"] }
+tracing-subscriber = { workspace = true, default-features = true }
vpcmap = { workspace = true }
[dev-dependencies]
diff --git a/dataplane/design.drawio b/dataplane/design.drawio
new file mode 100644
index 000000000..cd12db673
--- /dev/null
+++ b/dataplane/design.drawio
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dataplane/src/args.rs b/dataplane/src/args.rs
index f8d7407fe..6e4573f5f 100644
--- a/dataplane/src/args.rs
+++ b/dataplane/src/args.rs
@@ -96,9 +96,8 @@ impl CmdArgs {
}
}
- #[allow(clippy::unused_self)]
- pub fn kernel_params(&self) -> Vec {
- self.interface.clone()
+ pub fn kernel_params(&self) -> &Vec {
+ &self.interface
}
pub fn eal_params(&self) -> Vec {
diff --git a/dataplane/src/drivers/kernel.rs b/dataplane/src/drivers/kernel.rs
index dcd2e9f6e..4e31fb298 100644
--- a/dataplane/src/drivers/kernel.rs
+++ b/dataplane/src/drivers/kernel.rs
@@ -25,6 +25,7 @@ use std::{thread, time};
use crate::CmdArgs;
use net::buffer::test_buffer::TestBuffer;
+use net::interface::InterfaceIndex;
use net::packet::Packet;
use net::packet::{DoneReason, InterfaceId};
use netdev::Interface;
@@ -33,17 +34,17 @@ use tracing::{debug, error, warn};
/// Simple representation of a kernel interface.
pub struct Kif {
- ifindex: u32, /* ifindex of interface */
- token: Token, /* token for polling */
- name: String, /* name of interface */
- sock: RawPacketStream, /* packet socket */
- raw_fd: RawFd, /* raw desc of packet socket */
+ ifindex: InterfaceIndex, /* ifindex of interface */
+ token: Token, /* token for polling */
+ name: String, /* name of interface */
+ sock: RawPacketStream, /* packet socket */
+ raw_fd: RawFd, /* raw desc of packet socket */
}
impl Kif {
/// Create a kernel interface entry. Each interface gets a [`Token`] assigned
/// and a packet socket opened, which gets registered in a poller to detect
/// activity.
- fn new(ifindex: u32, name: &str, token: Token) -> Option {
+ fn new(ifindex: InterfaceIndex, name: &str, token: Token) -> Option {
let Ok(mut sock) = RawPacketStream::new() else {
error!("Failed to open raw sock for interface {name}");
return None;
@@ -83,7 +84,7 @@ impl KifTable {
}
/// Add a kernel interface 'representor' to this table. For each interface, a packet socket
/// is created and a poller [`Token`] assigned. Failures are simply logged.
- pub fn add(&mut self, ifindex: u32, name: &str) {
+ pub fn add(&mut self, ifindex: InterfaceIndex, name: &str) {
debug!("Adding interface '{name}'...");
let token = Token(self.next_token);
let interface = Kif::new(ifindex, name, token);
@@ -109,7 +110,7 @@ impl KifTable {
/// Get a mutable refernce to the [`Kif`] with the indicated ifindex
/// Todo: replace this linear search with a hash lookup
- pub fn get_mut_by_index(&mut self, ifindex: u32) -> Option<&mut Kif> {
+ pub fn get_mut_by_index(&mut self, ifindex: InterfaceIndex) -> Option<&mut Kif> {
self.by_token
.values_mut()
.find(|kif| kif.ifindex == ifindex)
@@ -117,17 +118,24 @@ impl KifTable {
}
/// Get the ifindex of the interface with the given name
-fn get_interface_ifindex(interfaces: &[Interface], name: &str) -> Option {
- interfaces
- .iter()
- .position(|interface| interface.name == name)
- .map(|pos| interfaces[pos].index)
+fn get_interface_ifindex(interfaces: &[Interface], name: &str) -> Option {
+ interfaces.iter().find_map(|interface| {
+ if interface.name == name {
+ InterfaceIndex::try_new(interface.index)
+ .map_err(|e| {
+ error!("{e}");
+ })
+ .ok()
+ } else {
+ None
+ }
+ })
}
/// Build a table of kernel interfaces to receive packets from (or send to).
/// Interfaces of interest are indicated by --interface INTERFACE in the command line.
/// Argument --interface ANY|any instructs the driver to capture on all interfaces.
-fn build_kif_table(args: impl IntoIterator- + Clone>) -> KifTable {
+fn build_kif_table(args: impl IntoIterator
- >) -> KifTable {
/* learn about existing kernel network interfaces. We need these to know their ifindex */
let interfaces = netdev::get_interfaces();
@@ -145,7 +153,10 @@ fn build_kif_table(args: impl IntoIterator
- + Clone>) -> K
if ifnames.len() == 1 && ifnames[0].to_uppercase() == "ANY" {
/* use all interfaces */
for interface in &interfaces {
- kiftable.add(interface.index, &interface.name);
+ kiftable.add(
+ InterfaceIndex::try_new(interface.index).unwrap_or_else(|e| unreachable!("{}", e)),
+ &interface.name,
+ );
}
} else {
/* use only the interfaces specified in args */
@@ -168,7 +179,7 @@ pub struct DriverKernel;
impl DriverKernel {
/// Starts the kernel driver
pub fn start(
- args: impl IntoIterator
- + Clone>,
+ args: impl IntoIterator
- >,
setup_pipeline: impl FnOnce() -> DynPipeline,
) {
let mut pipeline = setup_pipeline();
@@ -194,7 +205,7 @@ impl DriverKernel {
let mut meta = pkt.get_meta_mut();
if let Some(oif) = &meta.oif {
/* lookup outgoing interface and xmit packet */
- if let Some(outgoing) = kiftable.get_mut_by_index(oif.get_id()) {
+ if let Some(outgoing) = kiftable.get_mut_by_index(*oif) {
match pkt.serialize() {
Ok(out) => {
debug!(
@@ -209,7 +220,7 @@ impl DriverKernel {
}
}
} else {
- warn!("Unable to find interface with ifindex {}", oif.get_id());
+ warn!("Unable to find interface with ifindex {}", oif);
}
} else {
warn!("Outgoing interface not set for packet");
@@ -222,7 +233,7 @@ impl DriverKernel {
/// Tries to receive frames from the indicated interface and builds `Packet`s
/// out of them. Returns a vector of [`Packet`]s
- pub fn packet_recv(interface: &mut Kif) -> Vec> {
+ pub fn packet_recv(interface: &mut Kif) -> impl Iterator
- > + use<> {
let mut raw = [0u8; 2048];
let mut pkts = Vec::with_capacity(10);
while let Ok(bytes) = interface.sock.read(&mut raw) {
@@ -233,7 +244,7 @@ impl DriverKernel {
Ok(mut incoming) => {
/* set the iif id */
let mut meta = incoming.get_meta_mut();
- meta.iif = InterfaceId::new(interface.ifindex);
+ meta.iif = Some(interface.ifindex);
pkts.push(incoming);
}
Err(e) => {
@@ -243,6 +254,6 @@ impl DriverKernel {
}
}
}
- pkts
+ pkts.into_iter()
}
}
diff --git a/dataplane/src/packet_processor/egress.rs b/dataplane/src/packet_processor/egress.rs
index 9de61018e..bcc50cc7c 100644
--- a/dataplane/src/packet_processor/egress.rs
+++ b/dataplane/src/packet_processor/egress.rs
@@ -15,11 +15,12 @@ use net::{
};
use net::headers::TryEthMut;
+use net::interface::InterfaceIndex;
use net::packet::{DoneReason, Packet};
use pipeline::NetworkFunction;
use routing::interfaces::iftablerw::IfTableReader;
-use routing::interfaces::interface::{IfIndex, IfState, IfType, Interface};
+use routing::interfaces::interface::{IfState, IfType, Interface};
use routing::{atable::atablerw::AtableReader, interfaces::iftable::IfTable};
#[allow(unused)]
@@ -119,7 +120,7 @@ impl Egress {
&self,
packet: &mut Packet,
addr: IpAddr,
- ifindex: IfIndex,
+ ifindex: InterfaceIndex,
) -> Option {
let nfi = &self.name;
@@ -150,7 +151,7 @@ impl Egress {
fn resolve_next_mac(
&self,
- ifindex: IfIndex,
+ ifindex: InterfaceIndex,
packet: &mut Packet,
) -> Option {
let nfi = &self.name;
@@ -178,7 +179,6 @@ impl Egress {
};
/* resolve destination mac */
- let oif = oif.get_id();
let Some(dst_mac) = self.resolve_next_mac(oif, packet) else {
// we could not figure out the destination MAC.
// resolve_next_mac() already calls packet.done()
diff --git a/dataplane/src/packet_processor/ingress.rs b/dataplane/src/packet_processor/ingress.rs
index 57fce18a6..246d51539 100644
--- a/dataplane/src/packet_processor/ingress.rs
+++ b/dataplane/src/packet_processor/ingress.rs
@@ -9,14 +9,14 @@ use tracing::{debug, trace, warn};
use net::buffer::PacketBufferMut;
use net::eth::mac::Mac;
-use net::headers::{TryEth, TryIpv4, TryIpv6};
+use net::headers::{TryEth, TryIp};
use net::packet::{DoneReason, Packet};
use pipeline::NetworkFunction;
use routing::interfaces::iftablerw::IfTableReader;
use routing::interfaces::interface::{Attachment, IfState, IfType, Interface};
-#[allow(unused)]
+#[derive(Debug)]
pub struct Ingress {
name: String,
iftr: IfTableReader,
@@ -42,7 +42,7 @@ impl Ingress {
packet: &mut Packet,
) {
let nfi = self.name();
- if packet.try_ipv4().is_some() || packet.try_ipv6().is_some() {
+ if packet.try_ip().is_some() {
match &interface.attachment {
Some(Attachment::VRF(fibr)) => {
let Some(vrfid) = fibr.get_id().map(|x| x.as_u32()) else {
@@ -54,7 +54,7 @@ impl Ingress {
debug!("{nfi}: Packet is for VRF {vrfid}");
packet.get_meta_mut().vrf = Some(vrfid);
}
- Some(Attachment::BD) => unimplemented!(),
+ Some(Attachment::BD) => unimplemented!(), // TODO: what is this?
None => {
warn!("{nfi}: Interface {} is detached", interface.name);
packet.done(DoneReason::InterfaceDetached);
@@ -66,30 +66,39 @@ impl Ingress {
}
}
+ #[tracing::instrument(level = "trace")]
fn interface_ingress_eth_non_local(
&self,
- _interface: &Interface,
+ interface: &Interface,
dst_mac: Mac,
packet: &mut Packet,
) {
- let nfi = self.name();
/* Here we would check if the interface is part of some
bridge domain. But we don't support bridging yet. */
- trace!("{nfi}: Recvd frame for mac {dst_mac} (not for us)");
+ trace!(
+ "{nfi}: Recvd frame for mac {dst_mac} (not for us) (interface {ifname})",
+ nfi = self.name(),
+ ifname = interface.name
+ );
packet.done(DoneReason::MacNotForUs);
}
+ #[tracing::instrument(level = "trace")]
fn interface_ingress_eth_bcast(
&self,
- _interface: &Interface,
+ interface: &Interface,
packet: &mut Packet,
) {
let nfi = self.name();
packet.get_meta_mut().set_l2bcast(true);
packet.done(DoneReason::Unhandled);
- warn!("{nfi}: Processing of broadcast ethernet frames is not supported");
+ warn!(
+ "{nfi}: Processing of broadcast ethernet frames is not supported (interface {ifname})",
+ ifname = interface.name
+ );
}
+ #[tracing::instrument(level = "trace")]
fn interface_ingress_eth(
&self,
interface: &Interface,
@@ -119,6 +128,7 @@ impl Ingress {
}
}
+ #[tracing::instrument(level = "trace")]
fn interface_ingress(
&self,
interface: &Interface,
@@ -142,21 +152,26 @@ impl Ingress {
}
impl NetworkFunction for Ingress {
+ #[tracing::instrument(level = "trace", skip(input))]
fn process<'a, Input: Iterator
- > + 'a>(
&'a mut self,
input: Input,
) -> impl Iterator
- > + 'a {
- trace!("{}", self.name);
input.filter_map(move |mut packet| {
let nfi = self.name();
if !packet.is_done() {
if let Some(iftable) = self.iftr.enter() {
- let iif = packet.get_meta().iif.get_id();
- if let Some(interface) = iftable.get_interface(iif) {
- self.interface_ingress(interface, &mut packet);
- } else {
- warn!("{nfi}: unknown incoming interface {iif}");
- packet.done(DoneReason::InterfaceUnknown);
+ match packet.get_meta().iif {
+ None => {}
+ Some(iif) => match iftable.get_interface(iif) {
+ None => {
+ warn!("{nfi}: unknown incoming interface {iif}");
+ packet.done(DoneReason::InterfaceUnknown);
+ }
+ Some(interface) => {
+ self.interface_ingress(interface, &mut packet);
+ }
+ },
}
}
}
diff --git a/dataplane/src/packet_processor/ipforward.rs b/dataplane/src/packet_processor/ipforward.rs
index 10df78ce6..15d579613 100644
--- a/dataplane/src/packet_processor/ipforward.rs
+++ b/dataplane/src/packet_processor/ipforward.rs
@@ -6,13 +6,12 @@
#![allow(clippy::similar_names)]
use arrayvec::ArrayVec;
-use std::net::IpAddr;
-use tracing::{debug, error, trace, warn};
-
use net::headers::{TryHeadersMut, TryIpv4Mut, TryIpv6Mut};
-use net::packet::{DoneReason, InterfaceId, Packet};
+use net::packet::{DoneReason, Packet};
use net::{buffer::PacketBufferMut, checksum::Checksum};
use pipeline::NetworkFunction;
+use std::net::IpAddr;
+use tracing::{debug, error, trace, warn};
use routing::fib::fibobjects::{EgressObject, FibEntry, PktInstruction};
use routing::fib::fibtable::FibTable;
@@ -20,12 +19,12 @@ use routing::fib::fibtable::FibTableReader;
use routing::fib::fibtype::FibId;
use routing::evpn::Vtep;
-use routing::interfaces::interface::IfIndex;
use routing::rib::encapsulation::{Encapsulation, VxlanEncapsulation};
use routing::rib::vrf::VrfId;
use net::headers::Headers;
use net::headers::Net;
+use net::interface::InterfaceIndex;
use net::ip::NextHeader;
use net::ipv4::Ipv4;
use net::ipv4::UnicastIpv4Addr;
@@ -61,7 +60,7 @@ impl IpForwarder {
/* get destination ip address */
let Some(dst) = packet.ip_destination() else {
- error!("{nfi}: Failed to get destination ip address for packet");
+ debug!("{nfi}: Failed to get destination ip address for packet");
packet.done(DoneReason::InternalFailure);
return;
};
@@ -75,19 +74,19 @@ impl IpForwarder {
/* Read-only access to the fib table */
let Some(fibtr) = self.fibtr.enter() else {
- error!("{nfi}: Unable to lookup fib for vrf {vrfid}");
+ debug!("{nfi}: Unable to lookup fib for vrf {vrfid}");
packet.done(DoneReason::InternalFailure);
return;
};
/* Lookup the fib which needs to be consulted */
let Some(fibr) = fibtr.get_fib(&fibid) else {
- error!("{nfi}: Unable to find fib with id {fibid} for vrf {vrfid}");
+ debug!("{nfi}: Unable to find fib with id {fibid} for vrf {vrfid}");
packet.done(DoneReason::InternalFailure);
return;
};
/* Read-only access to fib */
let Some(fib) = fibr.enter() else {
- error!("{nfi}: Unable to read from fib {fibid}");
+ debug!("{nfi}: Unable to read from fib {fibid}");
packet.done(DoneReason::InternalFailure);
return;
};
@@ -101,14 +100,14 @@ impl IpForwarder {
if !fibentry.is_iplocal() {
Self::decrement_ttl(packet, dst);
if packet.is_done() {
- warn!("TTL/Hop-count limit exceeded!");
+ debug!("TTL/Hop-count limit exceeded!");
return;
}
}
/* execute instructions according to FIB */
self.packet_exec_instructions(&fibtr, packet, fibentry, fib.get_vtep());
} else {
- error!("Could not get fib group for {prefix}. Will drop packet...");
+ debug!("Could not get fib group for {prefix}. Will drop packet...");
packet.done(DoneReason::InternalFailure);
}
}
@@ -118,7 +117,7 @@ impl IpForwarder {
&self,
packet: &mut Packet,
fibtable: &FibTable,
- _ifindex: IfIndex, /* we get it from metadata */
+ _ifindex: InterfaceIndex, /* we get it from metadata */
) {
let nfi = &self.name;
@@ -132,12 +131,12 @@ impl IpForwarder {
debug!("{nfi}: Packet comes with vni {vni}");
let fibid = FibId::from_vni(vni);
let Some(fib) = fibtable.get_fib(&fibid) else {
- error!("{nfi}: Failed to find fib {fibid} associated to vni {vni}");
+ debug!("{nfi}: Failed to find fib {fibid} associated to vni {vni}");
packet.done(DoneReason::Unroutable);
return;
};
let Some(next_vrf) = fib.get_id().map(|id| id.as_u32()) else {
- error!("{nfi}: Failed to access fib {fibid} to determine vrf");
+ debug!("{nfi}: Failed to access fib {fibid} to determine vrf");
packet.done(DoneReason::InternalFailure);
return;
};
@@ -151,7 +150,7 @@ impl IpForwarder {
packet.get_meta_mut().set_nat(true);
}
Some(Err(bad)) => {
- warn!("The decapsulated packet is malformed!: {bad:?}");
+ debug!("The decapsulated packet is malformed!: {bad:#?}");
packet.done(DoneReason::Malformed);
}
None => {
@@ -259,7 +258,7 @@ impl IpForwarder {
// build vxlan headers for encapsulation
match Self::build_vxlan_headers(vxlan, vtep) {
Err(e) => {
- error!("{nfi}: Failed to build VxLAN headers: {e}");
+ warn!("{nfi}: Failed to build VxLAN headers: {e}");
packet.done(DoneReason::InternalFailure);
}
Ok(vxlan_headers) => match packet.vxlan_encap(&vxlan_headers) {
@@ -305,7 +304,7 @@ impl IpForwarder {
) {
let meta = packet.get_meta_mut();
if let Some(ifindex) = egress.ifindex() {
- meta.oif = Some(InterfaceId::new(*ifindex));
+ meta.oif = Some(*ifindex);
}
if let Some(addr) = egress.address() {
meta.nh_addr = Some(*addr);
@@ -380,6 +379,7 @@ impl IpForwarder {
}
impl NetworkFunction for IpForwarder {
+ #[tracing::instrument(level = "trace", skip(self, input))]
fn process<'a, Input: Iterator
- > + 'a>(
&'a mut self,
input: Input,
diff --git a/design.drawio b/design.drawio
new file mode 100644
index 000000000..69ed93978
--- /dev/null
+++ b/design.drawio
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design.svg b/design.svg
new file mode 100644
index 000000000..5db03b151
--- /dev/null
+++ b/design.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/driver/Cargo.toml b/driver/Cargo.toml
new file mode 100644
index 000000000..b9404fc23
--- /dev/null
+++ b/driver/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "dataplane-driver"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+# internal
+net = { workspace = true }
+pipeline = { workspace = true }
diff --git a/driver/src/facade.rs b/driver/src/facade.rs
new file mode 100644
index 000000000..c49d7a4bd
--- /dev/null
+++ b/driver/src/facade.rs
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Open Network Fabric Authors
+
+use net::buffer::PacketBufferMut;
+use net::packet::Packet;
+
+pub trait Initialize {
+ type Error;
+
+ type Args>>: TryFrom>;
+
+ fn initialize(args: Self::Args) -> Result
+ where
+ I: Iterator>,
+ Self: Sized;
+}
+
+pub trait Receive {
+ type Error;
+
+ fn receive(
+ &mut self,
+ ) -> Result>, Self::Error>;
+}
+
+pub trait Transmit {
+ type Error;
+
+ fn transmit(
+ &mut self,
+ buf: impl IntoIterator
- >,
+ ) -> Result<(), Self::Error>;
+}
+
+pub trait Run: Receive + Transmit {
+ fn run(&mut self)
+
+}
diff --git a/driver/src/lib.rs b/driver/src/lib.rs
new file mode 100644
index 000000000..3bf63072c
--- /dev/null
+++ b/driver/src/lib.rs
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Open Network Fabric Authors
+
+mod facade;
diff --git a/interface-manager/Cargo.toml b/interface-manager/Cargo.toml
index 319bc7142..5f54eaeab 100644
--- a/interface-manager/Cargo.toml
+++ b/interface-manager/Cargo.toml
@@ -7,6 +7,7 @@ publish = false
[features]
default = []
+netdevsim = []
bolero = ["dep:bolero", "net/bolero"]
[dependencies]
diff --git a/interface-manager/src/interface/mod.rs b/interface-manager/src/interface/mod.rs
index 1a1f2e619..96e7fad25 100644
--- a/interface-manager/src/interface/mod.rs
+++ b/interface-manager/src/interface/mod.rs
@@ -11,8 +11,6 @@ mod tap;
mod vrf;
mod vtep;
-use std::num::NonZero;
-
#[allow(unused_imports)] // re-export
pub use association::*;
#[allow(unused_imports)] // re-export
@@ -28,8 +26,15 @@ pub use vrf::*;
#[allow(unused_imports)] // re-export
pub use vtep::*;
+#[cfg(feature = "netdevsim")]
+mod netdevsim;
+#[cfg(feature = "netdevsim")]
+#[allow(unused_imports)]
+pub use netdevsim::*;
+
use crate::{Manager, manager_of};
use derive_builder::Builder;
+use futures::TryFutureExt;
use multi_index_map::MultiIndexMap;
use net::eth::ethtype::EthType;
use net::eth::mac::SourceMac;
@@ -45,12 +50,13 @@ use net::route::RouteTableId;
use net::vxlan::InvalidVni;
use rekon::{AsRequirement, Create, Op, Reconcile, Remove, Update};
use rtnetlink::packet_route::link::{
- InfoBridge, InfoData, InfoVrf, InfoVxlan, LinkAttribute, LinkFlags, LinkInfo, LinkMessage,
- State,
+ InfoBridge, InfoData, InfoKind, InfoVrf, InfoVxlan, LinkAttribute, LinkFlags, LinkInfo,
+ LinkMessage, State,
};
-use rtnetlink::{LinkBridge, LinkUnspec, LinkVrf, LinkVxlan};
+use rtnetlink::{LinkBridge, LinkDummy, LinkUnspec, LinkVrf, LinkVxlan};
use serde::{Deserialize, Serialize};
-use tracing::{error, trace, warn};
+use std::num::NonZero;
+use tracing::{debug, error, warn};
/// The specified / intended state for a network interface.
///
@@ -144,6 +150,7 @@ impl Create for Manager {
]))
.build()
}
+ InterfacePropertiesSpec::Dummy => LinkDummy::new(requirement.name.as_ref()).build(),
InterfacePropertiesSpec::Vtep(properties) => {
LinkVxlan::new(requirement.name.as_ref(), properties.vni.as_u32())
.set_info_data(InfoData::Vxlan(vec![
@@ -161,6 +168,14 @@ impl Create for Manager {
warn!("expected pci device missing: {requirement:#?}");
return Err(rtnetlink::Error::RequestFailed);
}
+ InterfacePropertiesSpec::Tap => {
+ return TapDevice::open(&requirement.name)
+ .map_err(|err| {
+ warn!("failed to create tap device: {err:?}");
+ rtnetlink::Error::RequestFailed
+ })
+ .await;
+ }
};
if let Some(mac) = requirement.mac {
message
@@ -606,81 +621,70 @@ pub trait TryFromLinkMessage {
Self: Sized;
}
-fn extract_vrf_data(builder: &mut VrfPropertiesBuilder, info: &LinkInfo) {
- if let LinkInfo::Data(InfoData::Vrf(datas)) = info {
- for data in datas {
- if let InfoVrf::TableId(raw) = data {
- match RouteTableId::try_from(*raw) {
- Ok(route_table) => {
- builder.route_table_id(route_table);
- }
- Err(err) => {
- error!("zero is not a legal route table id!: {err:?}");
- }
+fn extract_vrf_data(builder: &mut VrfPropertiesBuilder, datas: &[InfoVrf]) {
+ for data in datas {
+ if let InfoVrf::TableId(raw) = data {
+ match RouteTableId::try_from(*raw) {
+ Ok(route_table) => {
+ builder.route_table_id(route_table);
+ }
+ Err(err) => {
+ error!("zero is not a legal route table id!: {err:?}");
}
}
}
}
}
-fn extract_vxlan_info(builder: &mut VtepPropertiesBuilder, info: &LinkInfo) {
- if let LinkInfo::Data(InfoData::Vxlan(datas)) = info {
- for data in datas {
- match data {
- InfoVxlan::Id(vni) => {
- match (*vni).try_into() {
- Ok(vni) => {
- builder.vni(Some(vni));
- }
- Err(InvalidVni::ReservedZero) => {
- builder.vni(None); // likely an external vtep
- }
- Err(InvalidVni::TooLarge(wrong)) => {
- error!("found too large VNI: {wrong}");
- }
+fn extract_vxlan_info(builder: &mut VtepPropertiesBuilder, datas: &[InfoVxlan]) {
+ for data in datas {
+ match data {
+ InfoVxlan::Id(vni) => {
+ match (*vni).try_into() {
+ Ok(vni) => {
+ builder.vni(Some(vni));
}
- }
- InfoVxlan::Local(local) => match UnicastIpv4Addr::try_from(*local) {
- Ok(local) => {
- if local.inner().is_unspecified() {
- warn!(
- "likely OS error: unspecified local ipv4 address for vtep: {local}"
- );
- builder.local(None);
- }
- builder.local(Some(local));
+ Err(InvalidVni::ReservedZero) => {
+ builder.vni(None); // likely an external vtep
+ }
+ Err(InvalidVni::TooLarge(wrong)) => {
+ error!("found too large VNI: {wrong}");
}
- Err(err) => {
- error!("{err}");
+ }
+ }
+ InfoVxlan::Local(local) => match UnicastIpv4Addr::try_from(*local) {
+ Ok(local) => {
+ if local.inner().is_unspecified() {
+ warn!("likely OS error: unspecified local ipv4 address for vtep: {local}");
builder.local(None);
}
- },
- InfoVxlan::Ttl(ttl) => {
- builder.ttl(Some(*ttl));
+ builder.local(Some(local));
}
- _ => {}
+ Err(err) => {
+ error!("{err}");
+ builder.local(None);
+ }
+ },
+ InfoVxlan::Ttl(ttl) => {
+ builder.ttl(Some(*ttl));
}
+ _ => {}
}
}
}
-fn extract_bridge_info(builder: &mut BridgePropertiesBuilder, info: &LinkInfo) -> bool {
- let mut is_bridge = false;
- if let LinkInfo::Data(InfoData::Bridge(datas)) = info {
- is_bridge = true;
- for data in datas {
- match data {
- InfoBridge::VlanFiltering(f) => {
- builder.vlan_filtering(*f);
- }
- InfoBridge::VlanProtocol(p) => {
- builder.vlan_protocol(EthType::from(*p));
- }
- _ => {}
+fn extract_bridge_info(builder: &mut BridgePropertiesBuilder, datas: &[InfoBridge]) {
+ for data in datas {
+ match data {
+ InfoBridge::VlanFiltering(f) => {
+ builder.vlan_filtering(*f);
+ }
+ InfoBridge::VlanProtocol(p) => {
+ builder.vlan_protocol(EthType::from(*p));
}
+ _ => {}
}
}
- is_bridge
}
impl TryFromLinkMessage for Interface {
@@ -699,7 +703,7 @@ impl TryFromLinkMessage for Interface {
let mut vrf_builder = VrfPropertiesBuilder::default();
let mut bridge_builder = BridgePropertiesBuilder::default();
let mut pci_netdev_builder = PciNetdevPropertiesBuilder::default();
- let mut is_bridge = false;
+ let mut kind: Option = None;
builder.admin_state(if message.header.flags.contains(LinkFlags::Up) {
AdminState::Up
} else {
@@ -719,9 +723,29 @@ impl TryFromLinkMessage for Interface {
}
LinkAttribute::LinkInfo(infos) => {
for info in infos {
- extract_vrf_data(&mut vrf_builder, info);
- extract_vxlan_info(&mut vtep_builder, info);
- is_bridge |= extract_bridge_info(&mut bridge_builder, info);
+ match info {
+ LinkInfo::Kind(kind_) => match &kind {
+ None => {
+ kind = Some(kind_.clone());
+ }
+ Some(old_kind) => {
+ warn!("duplicate kind attribute: {old_kind:?} {kind_:?}");
+ }
+ },
+ LinkInfo::Data(data) => match data {
+ InfoData::Bridge(datas) => {
+ extract_bridge_info(&mut bridge_builder, datas);
+ }
+ InfoData::Vxlan(datas) => {
+ extract_vxlan_info(&mut vtep_builder, datas);
+ }
+ InfoData::Vrf(datas) => {
+ extract_vrf_data(&mut vrf_builder, datas);
+ }
+ _ => {}
+ },
+ _ => {}
+ }
}
}
LinkAttribute::IfName(name) => match InterfaceName::try_from(name.clone()) {
@@ -779,7 +803,7 @@ impl TryFromLinkMessage for Interface {
let dev = match PciEbdf::try_new(parent_name.clone()) {
Ok(dev) => dev,
Err(err) => {
- trace!("{err}");
+ debug!("{err}");
continue;
}
};
@@ -789,44 +813,47 @@ impl TryFromLinkMessage for Interface {
}
}
- match (
- vrf_builder.build(),
- vtep_builder.build(),
- pci_netdev_builder.build(),
- ) {
- (Ok(vrf), Err(_), Err(_)) => {
- builder.properties(InterfaceProperties::Vrf(vrf));
- }
- (Err(_), Ok(vtep), Err(_)) => {
- builder.properties(InterfaceProperties::Vtep(vtep));
- }
- (Err(_), Err(_), Ok(rep)) => {
- builder.properties(InterfaceProperties::Pci(rep));
- }
- (Err(_), Err(_), Err(_)) => {
- if is_bridge {
- match bridge_builder.build() {
- Ok(bridge) => {
- builder.properties(InterfaceProperties::Bridge(bridge));
- }
- Err(err) => {
- error!("{err:?}");
- }
- }
+ match (kind, pci_netdev_builder.build()) {
+ (Some(kind), Err(_)) => match kind {
+ InfoKind::Dummy => {
+ builder.properties(InterfaceProperties::Dummy);
}
+ InfoKind::Tun => {
+ builder.properties(InterfaceProperties::Tap);
+ }
+ InfoKind::Bridge => match bridge_builder.build() {
+ Ok(props) => {
+ builder.properties(InterfaceProperties::Bridge(props));
+ }
+ Err(e) => {
+ debug!("failed to assemble bridge properties: {e}");
+ }
+ },
+ InfoKind::Vxlan => match vtep_builder.build() {
+ Ok(props) => {
+ builder.properties(InterfaceProperties::Vtep(props));
+ }
+ Err(e) => {
+ debug!("failed to assemble vtep properties {e}");
+ }
+ },
+ InfoKind::Vrf => match vrf_builder.build() {
+ Ok(props) => {
+ builder.properties(InterfaceProperties::Vrf(props));
+ }
+ Err(e) => {
+ debug!("{e}");
+ }
+ },
+ _ => {}
+ },
+ (None, Ok(props)) => {
+ builder.properties(InterfaceProperties::Pci(props));
}
- (Ok(vrf), Ok(vtep), Ok(rep)) => {
- error!("multiple link types satisfied at once: {vrf:?}, {vtep:?}, {rep:?}");
- }
- (Ok(vrf), Ok(vtep), Err(_)) => {
- error!("multiple link types satisfied at once: {vrf:?}, {vtep:?}");
- }
- (Ok(vrf), Err(_), Ok(rep)) => {
- error!("multiple link types satisfied at once: {vrf:?}, {rep:?}");
- }
- (Err(_), Ok(vtep), Ok(rep)) => {
- error!("multiple link types satisfied at once: {vtep:?}, {rep:?}");
+ (Some(kind), Ok(pci)) => {
+ warn!("pci and info kind data are mutually exclusive: {kind:#?}, {pci:#?}");
}
+ (None, Err(_)) => {}
}
builder.build()
}
diff --git a/interface-manager/src/interface/netdevsim.rs b/interface-manager/src/interface/netdevsim.rs
new file mode 100644
index 000000000..f7cd561f7
--- /dev/null
+++ b/interface-manager/src/interface/netdevsim.rs
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Open Network Fabric Authors
+
+use derive_builder::Builder;
+use multi_index_map::MultiIndexMap;
+use net::interface::NetDevSimPort;
+use serde::{Deserialize, Serialize};
+
+#[derive(
+ Builder,
+ Clone,
+ Debug,
+ Deserialize,
+ Eq,
+ Hash,
+ MultiIndexMap,
+ Ord,
+ PartialEq,
+ PartialOrd,
+ Serialize,
+)]
+#[multi_index_derive(Debug, Clone, Default, Serialize, Deserialize)]
+#[cfg_attr(any(test, feature = "bolero"), derive(bolero::TypeGenerator))]
+pub struct NetdevSimPropertiesSpec {
+ #[multi_index(ordered_non_unique)]
+ pub port: NetDevSimPort,
+}
diff --git a/interface-manager/src/interface/pci.rs b/interface-manager/src/interface/pci.rs
index 187be259e..71717cf48 100644
--- a/interface-manager/src/interface/pci.rs
+++ b/interface-manager/src/interface/pci.rs
@@ -13,24 +13,28 @@ use serde::{Deserialize, Serialize};
Builder,
Clone,
Debug,
+ Default,
+ Deserialize,
Eq,
Hash,
MultiIndexMap,
Ord,
PartialEq,
PartialOrd,
- Deserialize,
Serialize,
)]
#[multi_index_derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "bolero"), derive(bolero::TypeGenerator))]
pub struct PciNetdevPropertiesSpec {
#[multi_index(ordered_non_unique)]
+ #[builder(default)]
pub switch_id: Option, // the embedded switch id (if any)
#[multi_index(ordered_non_unique)]
+ #[builder(default)]
pub port_name: Option, // note: NOT strictly an InterfaceName
#[multi_index(ordered_non_unique)]
- pub parent_dev: PciEbdf, // typically a pci address
+ #[builder(default)]
+ pub parent_dev: Option,
}
impl AsRequirement for PciNetdevProperties {
@@ -46,15 +50,20 @@ impl AsRequirement for PciNetdevProperties {
PciNetdevPropertiesSpec {
switch_id: self.switch_id.clone(),
port_name: self.port_name.clone(),
- parent_dev: self.parent_dev.clone(),
+ parent_dev: Some(self.parent_dev.clone()),
}
}
}
impl PartialEq for PciNetdevPropertiesSpec {
fn eq(&self, other: &PciNetdevProperties) -> bool {
- self.parent_dev == other.parent_dev
- && self.port_name == other.port_name
- && self.switch_id == other.switch_id
+ match &self.parent_dev {
+ None => false,
+ Some(parent_dev) => {
+ *parent_dev == other.parent_dev
+ && self.port_name == other.port_name
+ && self.switch_id == other.switch_id
+ }
+ }
}
}
diff --git a/interface-manager/src/interface/properties.rs b/interface-manager/src/interface/properties.rs
index cb7ff0444..49282702f 100644
--- a/interface-manager/src/interface/properties.rs
+++ b/interface-manager/src/interface/properties.rs
@@ -13,12 +13,16 @@ use serde::{Deserialize, Serialize};
pub enum InterfacePropertiesSpec {
/// The planned properties of a bridge.
Bridge(BridgePropertiesSpec),
+ /// The planned properties of a dummy interface
+ Dummy,
+ /// The planned properties of a tap device
+ Tap,
+ /// The expected properties of a pci netdev.
+ Pci(PciNetdevPropertiesSpec),
/// The planned properties of a vtep (vxlan device).
Vtep(VtepPropertiesSpec),
/// The planned properties of a vrf
Vrf(VrfPropertiesSpec),
- /// The expected properties of a pci netdev.
- Pci(PciNetdevPropertiesSpec),
}
impl AsRequirement for InterfaceProperties {
@@ -32,11 +36,13 @@ impl AsRequirement for InterfaceProperties {
InterfaceProperties::Bridge(props) => {
InterfacePropertiesSpec::Bridge(props.as_requirement())
}
+ InterfaceProperties::Dummy => InterfacePropertiesSpec::Dummy,
InterfaceProperties::Vtep(props) => {
InterfacePropertiesSpec::Vtep(props.as_requirement()?)
}
InterfaceProperties::Vrf(props) => InterfacePropertiesSpec::Vrf(props.as_requirement()),
InterfaceProperties::Pci(rep) => InterfacePropertiesSpec::Pci(rep.as_requirement()),
+ InterfaceProperties::Tap => InterfacePropertiesSpec::Tap,
InterfaceProperties::Other => return None,
})
}
diff --git a/interface-manager/src/interface/tap.rs b/interface-manager/src/interface/tap.rs
index efd66fedc..b8e492297 100644
--- a/interface-manager/src/interface/tap.rs
+++ b/interface-manager/src/interface/tap.rs
@@ -1,12 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Open Network Fabric Authors
+use derive_builder::Builder;
+use multi_index_map::MultiIndexMap;
use net::buffer::{PacketBuffer, PacketBufferMut};
use net::interface::InterfaceName;
+use serde::{Deserialize, Serialize};
use std::num::NonZero;
-use std::os::fd::AsRawFd;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
-use tracing::{debug, error, info};
+use tracing::error;
+
+/// The planned properties of a dummy interface.
+#[derive(
+ Builder,
+ Clone,
+ Debug,
+ Eq,
+ Hash,
+ MultiIndexMap,
+ Ord,
+ PartialEq,
+ PartialOrd,
+ Deserialize,
+ Serialize,
+)]
+#[multi_index_derive(Debug, Clone, Default, Serialize, Deserialize)]
+#[cfg_attr(any(test, feature = "bolero"), derive(bolero::TypeGenerator))]
+pub struct TapDevicePropertiesSpec {}
#[derive(Debug)]
#[repr(transparent)]
@@ -31,31 +51,51 @@ mod helper {
/// We are subject to a contract with the kernel.
///
#[repr(transparent)]
- #[derive(Debug, Copy, Clone)]
- pub(super) struct InterfaceRequest(libc::ifreq);
+ #[derive(Debug)]
+ struct InterfaceRequestInner(libc::ifreq);
+
+ /// This is a validated type around a value which is regrettably fragile.
+ ///
+ /// 1. Passed directly to the kernel.
+ /// 2. By a privileged thread.
+ /// 3. In an ioctl.
+ /// 4. By an implicitly null terminated pointer.
+ ///
+ /// As a result, strict checks are in place to ensure memory integrity.
+ #[derive(Debug)]
+ #[non_exhaustive]
+ pub(super) struct InterfaceRequest {
+ pub(super) name: InterfaceName,
+ request: Pin>,
+ }
+
+ #[allow(unsafe_code)]
+ unsafe impl Send for InterfaceRequest {}
use net::interface::InterfaceName;
use nix::libc;
+ use std::os::fd::AsRawFd;
+ use std::pin::Pin;
+ use tracing::{info, trace, warn};
nix::ioctl_write_ptr_bad!(
/// Create a tap device
make_tap_device,
libc::TUNSETIFF,
- InterfaceRequest
+ InterfaceRequestInner
);
nix::ioctl_write_ptr_bad!(
/// Keep the tap device after the program ends
persist_tap_device,
libc::TUNSETPERSIST,
- InterfaceRequest
+ InterfaceRequestInner
);
- impl InterfaceRequest {
- /// Create a new `InterfaceRequest`.
- #[cold]
+ impl InterfaceRequestInner {
+ /// Create a new `InterfaceRequestInner`.
#[tracing::instrument(level = "trace")]
- pub(super) fn new(name: &InterfaceName) -> Self {
+ fn new(name: &InterfaceName) -> Self {
// we cannot support any platform for which this condition does not hold
static_assertions::const_assert_eq!(libc::IF_NAMESIZE, InterfaceName::MAX_LEN + 1);
let mut ifreq = libc::ifreq {
@@ -71,16 +111,57 @@ mod helper {
ifreq.ifr_name[i] = *byte as libc::c_char;
}
}
- InterfaceRequest(ifreq)
+ InterfaceRequestInner(ifreq)
+ }
+ }
+
+ impl InterfaceRequest {
+ /// Create a new `InterfaceRequest`.
+ #[cold]
+ #[tracing::instrument(level = "trace")]
+ pub fn new(name: InterfaceName) -> Self {
+ let request = Box::pin(InterfaceRequestInner::new(&name));
+ Self { name, request }
+ }
+
+ pub async fn create(self) -> Result<(), std::io::Error> {
+ let name = self.name;
+ trace!("opening /dev/net/tun");
+ let tap_file = tokio::fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(false)
+ .truncate(false)
+ .open("/dev/net/tun")
+ .await?;
+ trace!("attempting to create tap device");
+ #[allow(unsafe_code, clippy::borrow_as_ptr)] // well-checked constraints
+ let ret = unsafe { make_tap_device(tap_file.as_raw_fd(), &*self.request)? };
+ if ret < 0 {
+ let err = std::io::Error::last_os_error();
+ warn!("failed to create tap device {name}: {err}");
+ return Err(err);
+ }
+ info!("created tap device");
+ trace!("attempting to persist tap device");
+ #[allow(unsafe_code, clippy::borrow_as_ptr)] // well-checked constraints
+ let ret = unsafe { persist_tap_device(tap_file.as_raw_fd(), &*self.request)? };
+ if ret < 0 {
+ let err = std::io::Error::last_os_error();
+ warn!("failed to persist tap device: {err}");
+ return Err(err);
+ }
+ info!("persisted tap device: {name}");
+ Ok(())
}
}
#[cfg(any(test, feature = "bolero"))]
mod contract {
- use crate::interface::tap::helper::InterfaceRequest;
+ use crate::interface::tap::helper::InterfaceRequestInner;
use bolero::{Driver, TypeGenerator};
- impl TypeGenerator for InterfaceRequest {
+ impl TypeGenerator for InterfaceRequestInner {
fn generate(driver: &mut D) -> Option {
Some(Self::new(&driver.produce()?))
}
@@ -89,7 +170,7 @@ mod helper {
#[cfg(test)]
mod test {
- use crate::interface::tap::helper::InterfaceRequest;
+ use crate::interface::tap::helper::InterfaceRequestInner;
use net::interface::InterfaceName;
use std::ffi::CStr;
@@ -99,7 +180,7 @@ mod helper {
.with_type()
.for_each(|name: &InterfaceName| {
let name_str = name.to_string();
- let ifreq = InterfaceRequest::new(name);
+ let ifreq = InterfaceRequestInner::new(name);
assert_eq!(ifreq.0.ifr_name[ifreq.0.ifr_name.len() - 1], 0);
assert_eq!(ifreq.0.ifr_name[name_str.len()], 0);
#[allow(unsafe_code)] // test code
@@ -116,7 +197,7 @@ mod helper {
assert_eq!(*name, name_parse_back);
assert_eq!(
ifreq.0.ifr_name,
- InterfaceRequest::new(&name_parse_back).0.ifr_name
+ InterfaceRequestInner::new(&name_parse_back).0.ifr_name
);
});
}
@@ -125,11 +206,14 @@ mod helper {
fn interface_request_contract() {
bolero::check!()
.with_type()
- .for_each(|req: &InterfaceRequest| {
+ .for_each(|req: &InterfaceRequestInner| {
#[allow(unsafe_code)] // test code
let as_cstr = unsafe { CStr::from_ptr(req.0.ifr_name.as_ptr()) };
let as_ifname = InterfaceName::try_from(as_cstr.to_str().unwrap()).unwrap();
- assert_eq!(req.0.ifr_name, InterfaceRequest::new(&as_ifname).0.ifr_name);
+ assert_eq!(
+ req.0.ifr_name,
+ InterfaceRequestInner::new(&as_ifname).0.ifr_name
+ );
});
}
}
@@ -143,35 +227,8 @@ impl TapDevice {
/// If the tap device cannot be opened or created, an io::Error is returned.
#[cold]
#[tracing::instrument(level = "info")]
- pub async fn open(name: &InterfaceName) -> Result {
- let ifreq = helper::InterfaceRequest::new(name);
- debug!("opening /dev/net/tun");
- let tap_file = tokio::fs::OpenOptions::new()
- .read(true)
- .write(true)
- .create(false)
- .truncate(false)
- .open("/dev/net/tun")
- .await?;
- debug!("attempting to create tap device: {name}");
- #[allow(unsafe_code, clippy::borrow_as_ptr)] // well-checked constraints
- let ret = unsafe { helper::make_tap_device(tap_file.as_raw_fd(), &ifreq)? };
- if ret < 0 {
- let err = std::io::Error::last_os_error();
- error!("failed to create tap device {name}: {err}");
- return Err(err);
- }
- info!("created tap device: {name}");
- debug!("attempting to persist tap device: {name}");
- #[allow(unsafe_code, clippy::borrow_as_ptr)] // well-checked constraints
- let ret = unsafe { helper::persist_tap_device(tap_file.as_raw_fd(), &ifreq)? };
- if ret < 0 {
- let err = std::io::Error::last_os_error();
- error!("failed to persist tap device {name}: {err}");
- return Err(err);
- }
- info!("persisted tap device: {name}");
- Ok(Self { file: tap_file })
+ pub async fn open(name: &InterfaceName) -> Result<(), std::io::Error> {
+ helper::InterfaceRequest::new(name.clone()).create().await
}
/// Read a packet from the tap, filling out the provided buffer with the contents of the packet.
diff --git a/mgmt/Cargo.toml b/mgmt/Cargo.toml
index 50d6d9921..8a5ed0fa7 100644
--- a/mgmt/Cargo.toml
+++ b/mgmt/Cargo.toml
@@ -12,6 +12,7 @@ required-features = ["bolero"]
[features]
default = []
+fake-pci-as-netdevsim = ["interface-manager/netdevsim"]
bolero = ["dep:bolero", "interface-manager/bolero", "id/bolero", "net/bolero"]
[dependencies]
diff --git a/mgmt/src/lib.rs b/mgmt/src/lib.rs
index 76b654211..55f889acd 100644
--- a/mgmt/src/lib.rs
+++ b/mgmt/src/lib.rs
@@ -12,4 +12,5 @@ pub mod processor;
/* VPC manager */
pub mod vpc_manager;
+mod link_manager;
mod tests;
diff --git a/mgmt/src/link_manager/README.md b/mgmt/src/link_manager/README.md
new file mode 100644
index 000000000..3ae26e0aa
--- /dev/null
+++ b/mgmt/src/link_manager/README.md
@@ -0,0 +1,64 @@
+# Design notes
+
+## Pipeline threads
+
+Pipeline threads are responsible for processing packets.
+
+Network function logic like routing, NAT, and firewall MUST run on pipeline threads.
+
+* Pipeline threads should typically run on isolated cores.
+* Pipeline threads SHOULD NOT run an async runtime.
+ Instead, they SHOULD use a library like [`kanal`](https://github.com/fereidani/kanal) to
+ tx/rx messages with an async thread (see [trap-thread]) to trap packets to the kernel.
+* Pipeline threads SHOULD NOT have any linux capabilities.
+* Pipeline threads MUST run in the `link` network namespace OR in a private network namespace with no active network interfaces.
+
+
+## I/O threads
+
+I/O threads are responsible for writing packets to and receiving packets from network devices.
+
+* These threads MUST run in the `link` network namespace.
+* These threads SHOULD exist in a 1:1 mapping with pipeline threads.
+* These threads SHOULD be pinned to the SMT sibling of the core the pipeline thread is
+ running on.
+* I/O threads MAY also run some processing tasks (e.g., parsing) as polling the queue as
+ quickly as possible is unlikely to be efficient.
+* These threads MUST send and receive `Vec`s of packet buffers to the pipeline threads via a
+ sync queue.
+* These threads MAY run with higher privileges than pipeline threads.
+ Specifically, they MAY need `CAP_NET_RAW` and possibly `CAP_SYS_RAWIO`.
+* These threads SHOULD run with only the privileges needed to tx/rx packets to the NIC
+ queue(s).
+
+## Trap thread
+
+The trap thread is responsible for receiving packets from and writing packets to the proxy tap devices in the `vpc` network namespace.
+
+* The trap thread MAY run in the `vpc` network namespace.
+* The trap-thread MUST be a single thread that holds the rx side of an MPSC queue.
+* The tx side(s) of the MPSC queue(s) MUST be held by the pipeline and/or I/O threads.
+* The trap-thread SHOULD run with only the privileges required to tx/rx packets to/from the
+ tap devices in the `vpc` network namespace. Specifically, the trap-thread SHOULD not run with `CAP_SYS_ADMIN`.
+
+## vpc-manager thread
+
+The primary job of the `vpc-manager` thread is to configure the `vpc` network namespace as required by the control plane services running there (e.g., FRR).
+
+* The vpc-manager thread SHOULD be a single thread.
+* The vpc-manager thread MUST run with the `CAP_NET_ADMIN` capability.
+* The vpc-manager thread SHOULD NOT run with the `CAP_SYS_ADMIN` capability.
+* The vpc-manager thread MUST run an async runtime.
+
+## Notes
+
+We need to manage network namespaces to make this design work.
+
+Note that the way we must configure a thread in DPDK requires some extra work over and above creating a system thread (mostly to be able to use and free DPDK allocated memory).
+However, we want to be able to support the `AF_PACKET` driver in addition to a DPDK-based driver.
+In the case of the `AF_PACKET` based driver we won't have DPDK at all, but we will still have the same basic threading structure.
+Thus, thread management MUST be done via trait or something that facilitates dependency injection.
+
+
+
+
diff --git a/mgmt/src/link_manager/mod.rs b/mgmt/src/link_manager/mod.rs
new file mode 100644
index 000000000..95c7c8fa9
--- /dev/null
+++ b/mgmt/src/link_manager/mod.rs
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Open Network Fabric Authors
+
+//! First up, we need to manage network namespaces.
+//!
+//! This needs to be done via trait (or something that facilitates dependency injection) because the
+//! threading model makes no sense without that.
+//!
+//! More specifically, the way you configure a thread in DPDK requires some extra stuff over and
+//! above creating a system thread.
+//! But we want to be able to support an `AF_PACKET` driver in addition to a DPDK-based driver.
+//!
+//! In the case of the `AF_PACKET` based driver we won't have DPDK at all. But we will still
+//! have the same basic threading structure.
+//!
+//! This begs the question: what are the types of threads that the drivers will need
+//!
+//! 1. pipeline threads: threads which actually process packets. These are our
+//! workhorse threads.
+//!
+//! * pipeline threads should typically run on isolated cores.
+//! * I do not currently think they should run an async runtime.
+//! Instead, they should use a library like [`kanal`](https://github.com/fereidani/kanal) to
+//! tx/rx messages with an async thread (see `trap-worker`s) to trap packets to the kernel.
+//! * Ideally, pipeline threads have almost zero privileges.
+//! * These threads MUST run in the `link` network namespace.
+//!
+//! 2. I/O threads: threads which are responsible for writing packets to and receiving packets from
+//! the network.
+//!
+//! * These threads MUST run in the `link` network namespace.
+//! * These threads SHOULD exist in a 1:1 mapping with pipeline threads.
+//! * These threads SHOULD be pinned to the SMT sibling of the core the pipeline thread is
+//! running on.
+//! * I/O threads MAY also run some processing tasks (e.g. parsing). Polling the queue as
+//! quickly as possible is unlikely to be efficient.
+//! * These threads MUST send and receive vectors of packet buffers to the pipeline threads via a
+//! sync queue.
+//! * These threads MAY run with higher privileges than pipeline threads.
+//! Specifically, they MAY need `CAP_NET_RAW` and possibly `CAP_SYS_RAWIO`.
+//! * These threads SHOULD run with only the privileges needed to tx/rx packets to the NIC
+//! queue(s).
+//!
+//! 3. Trap thread
+//!
+//! * The trap thread MUST run in the `vpc` network namespace.
+//! * There MUST be a single thread that holds the rx side of an MPSC queue.
+//! * The tx side of the queue(s) MUST be held by the pipeline and/or I/O threads.
+//! * The trap-thread SHOULD run with only the privileges required to tx/rx packets to/from the
+//! tap devices in the `vpc` network namespace.
+//!
+//! 4. vpc-manager thread
+//!
+//! * The primary job of the `vpc-manager` thread is to configure the `vpc` network namespace as
+//! required by the control plane services running there (e.g. FRR).
+//! * The `vpc-manager` SHOULD be a single thread.
+//! * The `vpc-manager` MUST run with the `CAP_NET_ADMIN` capability.
+//! * The `vpc-manager` MAY run with the `CAP_SYS_ADMIN` capability, but this SHOULD be avoided
+//! if possible.
+//! * The `vpc-manager` MUST run an async runtime.
diff --git a/mgmt/src/processor/confbuild/router.rs b/mgmt/src/processor/confbuild/router.rs
index 5e2f213e3..774d953e9 100644
--- a/mgmt/src/processor/confbuild/router.rs
+++ b/mgmt/src/processor/confbuild/router.rs
@@ -12,7 +12,7 @@ use crate::processor::confbuild::namegen::VpcInterfacesNames;
use std::collections::HashMap;
use tracing::{debug, error};
-use net::interface::{Interface, InterfaceName, Mtu};
+use net::interface::{Interface, InterfaceIndex, InterfaceName, Mtu};
use routing::interfaces::interface::{AttachConfig, IfDataEthernet, IfState, IfType};
use config::internal::interfaces::interface::InterfaceConfig;
@@ -61,7 +61,15 @@ fn build_router_interface_config(
vrfid: VrfId,
) -> Result {
let name = kiface.name.as_str();
- let mut new = RouterInterfaceConfig::new(name, kiface.index);
+ let ifindex = match InterfaceIndex::try_new(kiface.index) {
+ Ok(ifindex) => ifindex,
+ Err(err) => {
+ let msg = format!("{err}");
+ error!("{err}");
+ return Err(ConfigError::Invalid(msg));
+ }
+ };
+ let mut new = RouterInterfaceConfig::new(name, ifindex);
// set admin status -- currently from oper
let status = if kiface.is_up() {
diff --git a/mgmt/src/vpc_manager/mod.rs b/mgmt/src/vpc_manager/mod.rs
index d193a012a..02f49a857 100644
--- a/mgmt/src/vpc_manager/mod.rs
+++ b/mgmt/src/vpc_manager/mod.rs
@@ -4,6 +4,7 @@
use crate::processor::confbuild::namegen::VpcInterfacesNames;
use config::InternalConfig;
+use config::internal::interfaces::interface::InterfaceType;
use config::internal::routing::evpn::VtepConfig;
use derive_builder::Builder;
use futures::TryStreamExt;
@@ -11,14 +12,15 @@ use interface_manager::Manager;
use interface_manager::interface::{
BridgePropertiesSpec, InterfaceAssociationSpec, InterfacePropertiesSpec, InterfaceSpecBuilder,
MultiIndexInterfaceAssociationSpecMap, MultiIndexInterfaceSpecMap,
- MultiIndexPciNetdevPropertiesSpecMap, MultiIndexVrfPropertiesSpecMap,
- MultiIndexVtepPropertiesSpecMap, TryFromLinkMessage, VrfPropertiesSpec, VtepPropertiesSpec,
+ MultiIndexVrfPropertiesSpecMap, MultiIndexVtepPropertiesSpecMap, TryFromLinkMessage,
+ VrfPropertiesSpec, VtepPropertiesSpec,
};
use multi_index_map::MultiIndexMap;
use net::eth::ethtype::EthType;
+use net::eth::mac::SourceMac;
use net::interface::{
- AdminState, Interface, InterfaceProperties, MultiIndexInterfaceMap,
- MultiIndexPciNetdevPropertiesMap, MultiIndexVrfPropertiesMap, MultiIndexVtepPropertiesMap,
+ AdminState, Interface, InterfaceName, InterfaceProperties, MultiIndexInterfaceMap,
+ MultiIndexVrfPropertiesMap, MultiIndexVtepPropertiesMap,
};
use net::ip::UnicastIpAddr;
use net::route::RouteTableId;
@@ -28,7 +30,7 @@ use rtnetlink::Handle;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::sync::Arc;
-use tracing::{debug, error, trace, warn};
+use tracing::{debug, error, warn};
#[derive(Clone, Debug)]
pub struct VpcManager {
@@ -86,7 +88,6 @@ impl From for VpcDiscriminant {
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
pub struct RequiredInformationBase {
pub interfaces: MultiIndexInterfaceSpecMap,
- pub pci_netdevs: MultiIndexPciNetdevPropertiesSpecMap,
pub vrfs: MultiIndexVrfPropertiesSpecMap,
pub vteps: MultiIndexVtepPropertiesSpecMap,
pub associations: MultiIndexInterfaceAssociationSpecMap,
@@ -95,7 +96,6 @@ pub struct RequiredInformationBase {
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
pub struct ObservedInformationBase {
pub interfaces: MultiIndexInterfaceMap,
- pub pci_netdevs: MultiIndexPciNetdevPropertiesMap,
pub vrfs: MultiIndexVrfPropertiesMap,
pub vteps: MultiIndexVtepPropertiesMap,
}
@@ -128,7 +128,6 @@ impl Observe for VpcManager {
}
let mut vtep_properties = MultiIndexVtepPropertiesMap::default();
let mut vrf_properties = MultiIndexVrfPropertiesMap::default();
- let mut pci_netdev_properties = MultiIndexPciNetdevPropertiesMap::default();
let mut indexes_to_remove = vec![];
for (_, observation) in observations.iter() {
match &observation.properties {
@@ -150,18 +149,11 @@ impl Observe for VpcManager {
}
}
}
- InterfaceProperties::Pci(pci) => {
- match pci_netdev_properties.try_insert(pci.clone()) {
- Ok(_) => {}
- Err(err) => {
- error!("{err:?}");
- indexes_to_remove.push(observation.index);
- }
- }
- }
- InterfaceProperties::Other | InterfaceProperties::Bridge(_) => {
- /* nothing to index */
- }
+ InterfaceProperties::Other
+ | InterfaceProperties::Tap
+ | InterfaceProperties::Pci(_)
+ | InterfaceProperties::Bridge(_)
+ | InterfaceProperties::Dummy => { /* nothing to index */ }
}
}
for sliced in indexes_to_remove {
@@ -171,7 +163,6 @@ impl Observe for VpcManager {
.interfaces(observations)
.vteps(vtep_properties)
.vrfs(vrf_properties)
- .pci_netdevs(pci_netdev_properties)
.build()
{
Ok(ob) => Ok(ob),
@@ -319,15 +310,56 @@ impl TryFrom<&InternalConfig> for RequiredInformationBase {
let mut rb_builder = RequiredInformationBaseBuilder::default();
let mut interfaces = MultiIndexInterfaceSpecMap::default();
let mut vrfs = MultiIndexVrfPropertiesSpecMap::default();
- // TODO: empty for now:
- // will need to get expected pci devices from internal config in follow on pr
- let pci_netdevs = MultiIndexPciNetdevPropertiesSpecMap::default();
let mut vteps = MultiIndexVtepPropertiesSpecMap::default();
let mut associations = MultiIndexInterfaceAssociationSpecMap::default();
for config in internal.vrfs.iter_by_tableid() {
- if config.default {
- trace!("skipping default config: {config:?}");
- continue;
+ for iface in config.interfaces.values() {
+ match &iface.iftype {
+ InterfaceType::Ethernet(eth) => {
+ let mut tap = InterfaceSpecBuilder::default();
+ match InterfaceName::try_from(iface.name.as_str()) {
+ Ok(name) => {
+ tap.name(name);
+ }
+ Err(e) => {
+ error!("{e}");
+ continue;
+ }
+ };
+ match eth.mac.map(SourceMac::try_from) {
+ Some(Ok(mac)) => {
+ tap.mac(Some(mac));
+ }
+ None => {
+ tap.mac(None);
+ }
+ Some(Err(e)) => {
+ error!("{e}");
+ continue;
+ }
+ };
+ tap.properties(InterfacePropertiesSpec::Tap);
+ tap.mtu(iface.mtu);
+ tap.admin_state(AdminState::Up);
+ match tap.build() {
+ Ok(iface) => match interfaces.try_insert(iface) {
+ Ok(added) => {
+ debug!("added proxy tap interface to spec: {added:?}");
+ }
+ Err(e) => {
+ error!("{e}");
+ }
+ },
+ Err(e) => {
+ error!("{e}");
+ continue;
+ }
+ }
+ }
+ _ => {
+ continue;
+ }
+ }
}
let main_vtep = internal.vtep.as_ref().unwrap_or_else(|| unreachable!());
let vtep_ip = match main_vtep.address {
@@ -344,10 +376,7 @@ impl TryFrom<&InternalConfig> for RequiredInformationBase {
vrf.controller(None);
vrf.admin_state(AdminState::Up);
match config.tableid {
- None => {
- error!("no route_table_id set for config: {config:?}");
- panic!("no route_table_id set for config: {config:?}");
- }
+ None => {}
Some(route_table_id) => {
debug!("route_table set for config: {config:?}");
vrf.properties(InterfacePropertiesSpec::Vrf(VrfPropertiesSpec {
@@ -472,7 +501,6 @@ impl TryFrom<&InternalConfig> for RequiredInformationBase {
rb_builder.interfaces(interfaces);
rb_builder.vteps(vteps);
rb_builder.vrfs(vrfs);
- rb_builder.pci_netdevs(pci_netdevs);
rb_builder.associations(associations);
rb_builder.build()
}
@@ -513,7 +541,6 @@ mod contract {
}
let mut requirements = RequiredInformationBase::default();
let mut bridges = vec![];
- let mut pci_netdevs = vec![];
let mut vrfs = vec![];
let mut vteps = vec![];
@@ -559,11 +586,9 @@ mod contract {
.unwrap();
}
}
- InterfacePropertiesSpec::Pci(_) => {
- if let Ok(rep) = requirements.interfaces.try_insert(interface) {
- pci_netdevs.push(rep.clone());
- }
- }
+ InterfacePropertiesSpec::Tap
+ | InterfacePropertiesSpec::Dummy
+ | InterfacePropertiesSpec::Pci(_) => {}
}
}
if !bridges.is_empty() {
diff --git a/mgmt/tests/reconcile.rs b/mgmt/tests/reconcile.rs
index ff1da85be..8c72ac018 100644
--- a/mgmt/tests/reconcile.rs
+++ b/mgmt/tests/reconcile.rs
@@ -170,6 +170,8 @@ async fn reconcile_demo() {
InterfacePropertiesSpec::Pci(prop) => {
pci_props.try_insert(prop.clone()).unwrap();
}
+ InterfacePropertiesSpec::Dummy => {}
+ InterfacePropertiesSpec::Tap => {}
}
}
@@ -203,7 +205,6 @@ async fn reconcile_demo() {
.interfaces(required_interface_map)
.vteps(vtep_props)
.vrfs(vrf_props)
- .pci_netdevs(pci_props)
.associations(associations)
.build()
.unwrap();
@@ -258,16 +259,16 @@ async fn reconcile_demo() {
];
for interface in interfaces {
match &interface.properties {
- InterfacePropertiesSpec::Bridge(_) => {}
+ InterfacePropertiesSpec::Bridge(_)
+ | InterfacePropertiesSpec::Dummy
+ | InterfacePropertiesSpec::Pci(_)
+ | InterfacePropertiesSpec::Tap => {}
InterfacePropertiesSpec::Vtep(props) => {
req.vteps.try_insert(props.clone()).unwrap();
}
InterfacePropertiesSpec::Vrf(props) => {
req.vrfs.try_insert(props.clone()).unwrap();
}
- InterfacePropertiesSpec::Pci(props) => {
- req.pci_netdevs.try_insert(props.clone()).unwrap();
- }
}
req.interfaces.try_insert(interface).unwrap();
}
diff --git a/net/Cargo.toml b/net/Cargo.toml
index 45d0cd80b..a0872bd98 100644
--- a/net/Cargo.toml
+++ b/net/Cargo.toml
@@ -7,8 +7,9 @@ license = "Apache-2.0"
[features]
default = []
+netdevsim = []
-bolero = ["dep:bolero"]
+bolero = ["dep:bolero", "id/bolero"]
test_buffer = []
[dependencies]
@@ -24,6 +25,7 @@ ordermap = { workspace = true, features = ["std"] }
serde = { workspace = true, features = ["derive", "std"] }
thiserror = { workspace = true }
tracing = { workspace = true }
+id = { workspace = true }
[dev-dependencies]
bolero = { workspace = true, features = ["alloc", "arbitrary", "std"] }
diff --git a/net/src/interface/display.rs b/net/src/interface/display.rs
index 40df60cb2..4728ba1c7 100644
--- a/net/src/interface/display.rs
+++ b/net/src/interface/display.rs
@@ -86,13 +86,13 @@ impl Display for PciNetdevProperties {
match &self.switch_id {
None => {}
Some(switch_id) => {
- write!(f, "switch-id: {switch_id}")?;
+ write!(f, "switch-id: {switch_id} ")?;
}
}
match &self.port_name {
None => {}
Some(port_name) => {
- write!(f, "port-name: {port_name}")?;
+ write!(f, "port-name: {port_name} ")?;
}
}
write!(f, "parent-dev: {}", self.parent_dev)
@@ -106,6 +106,7 @@ impl Display for InterfaceProperties {
InterfaceProperties::Vrf(vrf) => vrf.fmt(f),
InterfaceProperties::Vtep(vtep) => vtep.fmt(f),
InterfaceProperties::Pci(rep) => rep.fmt(f),
+ InterfaceProperties::Dummy | InterfaceProperties::Tap => "".fmt(f),
InterfaceProperties::Other => write!(f, "other"),
}
}
@@ -114,9 +115,11 @@ impl Display for InterfaceProperties {
fn ifproperty_to_str(properties: &InterfaceProperties) -> &'static str {
match properties {
InterfaceProperties::Bridge(_) => "bridge",
+ InterfaceProperties::Dummy => "dummy",
InterfaceProperties::Vrf(_) => "vrf",
InterfaceProperties::Vtep(_) => "vtep",
InterfaceProperties::Pci(_) => "pci",
+ InterfaceProperties::Tap => "tap",
InterfaceProperties::Other => "other",
}
}
diff --git a/net/src/interface/mod.rs b/net/src/interface/mod.rs
index cdbb08ccf..019011054 100644
--- a/net/src/interface/mod.rs
+++ b/net/src/interface/mod.rs
@@ -20,7 +20,7 @@ use tracing::error;
mod bridge;
pub mod display;
mod mtu;
-mod pci;
+mod physical;
mod vrf;
mod vtep;
@@ -29,7 +29,7 @@ pub use bridge::*;
#[allow(unused_imports)] // re-export
pub use mtu::*;
#[allow(unused_imports)] // re-export
-pub use pci::*;
+pub use physical::*;
#[allow(unused_imports)] // re-export
pub use vrf::*;
#[allow(unused_imports)] // re-export
@@ -307,12 +307,16 @@ impl Interface {
pub enum InterfaceProperties {
/// Properties of bridges
Bridge(BridgeProperties),
+ /// Dummy interface properties
+ Dummy,
/// Properties of VTEPs (vxlan devices)
Vtep(VtepProperties),
/// Properties of VRFs
Vrf(VrfProperties),
/// Physical pci netdev properties
Pci(PciNetdevProperties),
+ /// Tap device properties
+ Tap,
/// Properties of something we don't currently support manipulating
Other,
}
diff --git a/net/src/interface/physical/bus.rs b/net/src/interface/physical/bus.rs
new file mode 100644
index 000000000..d4b4a26d7
--- /dev/null
+++ b/net/src/interface/physical/bus.rs
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Open Network Fabric Authors
+
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
+pub enum Bus {
+ Pci,
+ #[cfg(feature = "netdevsim")]
+ NetDevSim,
+}
diff --git a/net/src/interface/physical/mod.rs b/net/src/interface/physical/mod.rs
new file mode 100644
index 000000000..9bf714b90
--- /dev/null
+++ b/net/src/interface/physical/mod.rs
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Open Network Fabric Authors
+
+pub mod switch;
+
+mod bus;
+#[cfg(feature = "netdevsim")]
+mod netdevsim;
+mod pci;
+
+pub use bus::*;
+#[cfg(feature = "netdevsim")]
+pub use netdevsim::*;
+pub use pci::*;
diff --git a/net/src/interface/physical/netdevsim.rs b/net/src/interface/physical/netdevsim.rs
new file mode 100644
index 000000000..710ba947c
--- /dev/null
+++ b/net/src/interface/physical/netdevsim.rs
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Open Network Fabric Authors
+
+use derive_builder::Builder;
+use id::Id;
+use multi_index_map::MultiIndexMap;
+use serde::{Deserialize, Serialize};
+
+#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
+#[cfg_attr(any(test, feature = "bolero"), derive(bolero::TypeGenerator))]
+#[non_exhaustive]
+pub struct NetDevSimDevice {
+ pub id: Id,
+}
+
+#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
+#[cfg_attr(any(test, feature = "bolero"), derive(bolero::TypeGenerator))]
+#[non_exhaustive]
+pub struct NetDevSimPort {
+ pub device: NetDevSimDevice,
+ pub id: Id,
+}
+
+#[derive(
+ Builder,
+ Clone,
+ Debug,
+ Eq,
+ Hash,
+ MultiIndexMap,
+ Ord,
+ PartialEq,
+ PartialOrd,
+ Deserialize,
+ Serialize,
+)]
+#[multi_index_derive(Debug, Clone, Default, Serialize, Deserialize)]
+#[cfg_attr(any(test, feature = "bolero"), derive(bolero::TypeGenerator))]
+pub struct NetDevSimProperties {
+ #[multi_index(ordered_unique)]
+ pub port: NetDevSimPort,
+}
diff --git a/net/src/interface/pci/mod.rs b/net/src/interface/physical/pci.rs
similarity index 97%
rename from net/src/interface/pci/mod.rs
rename to net/src/interface/physical/pci.rs
index 7f019fc23..6531d91b6 100644
--- a/net/src/interface/pci/mod.rs
+++ b/net/src/interface/physical/pci.rs
@@ -7,8 +7,6 @@ use derive_builder::Builder;
use multi_index_map::MultiIndexMap;
use serde::{Deserialize, Serialize};
-pub mod switch;
-
#[derive(
Builder,
Clone,
diff --git a/net/src/interface/pci/switch.rs b/net/src/interface/physical/switch.rs
similarity index 87%
rename from net/src/interface/pci/switch.rs
rename to net/src/interface/physical/switch.rs
index 08c65b928..d729fe63d 100644
--- a/net/src/interface/pci/switch.rs
+++ b/net/src/interface/physical/switch.rs
@@ -5,23 +5,21 @@ use arrayvec::ArrayVec;
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display, Formatter, LowerHex};
-const SWITCH_ID_MAX_LEN: usize = 32;
-
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[repr(transparent)]
-pub struct SwitchId(ArrayVec);
+pub struct SwitchId(ArrayVec);
#[derive(thiserror::Error, Debug)]
pub enum SwitchIdError {
#[error("SwitchId is empty")]
Empty,
- #[error("Maximum length of an ESwitchId is 32 bytes, received {0} bytes")]
+ #[error("Maximum length of an ESwitchId is {MAX} bytes, received {0} bytes", MAX = SwitchId::MAX_LEN)]
InvalidLength(usize),
}
impl SwitchId {
/// The maximum length of an [`SwitchId`] in bytes
- pub const MAX_LEN: usize = SWITCH_ID_MAX_LEN;
+ pub const MAX_LEN: usize = 128;
/// Create a new [`SwitchId`] from a raw byte slice
///
@@ -71,7 +69,7 @@ impl Display for SwitchId {
#[cfg(any(test, feature = "bolero"))]
mod contract {
- use crate::interface::switch::{SWITCH_ID_MAX_LEN, SwitchId};
+ use crate::interface::switch::SwitchId;
use arrayvec::ArrayVec;
use bolero::generator::bolero_generator::bounded::BoundedValue;
use bolero::{Driver, TypeGenerator};
@@ -82,7 +80,7 @@ mod contract {
let len = usize::gen_bounded(
driver,
Bound::Included(&1),
- Bound::Excluded(&SWITCH_ID_MAX_LEN),
+ Bound::Excluded(&SwitchId::MAX_LEN),
)?;
let mut bytes = ArrayVec::new();
for _ in 0..len {
diff --git a/net/src/packet/display.rs b/net/src/packet/display.rs
index d29302622..e1baa378a 100644
--- a/net/src/packet/display.rs
+++ b/net/src/packet/display.rs
@@ -230,7 +230,12 @@ impl Display for BridgeDomain {
impl Display for PacketMeta {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, " metadata:")?;
- write!(f, " iif: {}", self.iif.get_id())?;
+ match self.iif {
+ None => {}
+ Some(iif) => {
+ write!(f, " iif: {iif}")?;
+ }
+ }
fmt_opt(f, " oif", self.oif, true)?;
writeln!(f, " bcast: {}", self.is_l2bcast())?;
diff --git a/net/src/packet/meta.rs b/net/src/packet/meta.rs
index 2a2685252..26c09d694 100644
--- a/net/src/packet/meta.rs
+++ b/net/src/packet/meta.rs
@@ -3,6 +3,7 @@
#![allow(missing_docs)] // TODO
+use crate::interface::InterfaceIndex;
use crate::vxlan::Vni;
use bitflags::bitflags;
use std::collections::HashMap;
@@ -83,10 +84,10 @@ bitflags! {
#[derive(Debug, Default, Clone)]
pub struct PacketMeta {
flags: MetaFlags,
- pub iif: InterfaceId, /* incoming interface - set early */
- pub oif: Option, /* outgoing interface - set late */
- pub nh_addr: Option, /* IP address of next-hop */
- pub vrf: Option, /* for IP packet, the VRF to use to route it */
+ pub iif: Option, /* incoming interface - set early */
+ pub oif: Option, /* outgoing interface - set late */
+ pub nh_addr: Option, /* IP address of next-hop */
+ pub vrf: Option, /* for IP packet, the VRF to use to route it */
pub bridge: Option, /* the bridge domain to forward the packet to */
pub done: Option, /* if Some, the reason why a packet was marked as done, including delivery to NF */
pub src_vni: Option, /* the vni value of a received vxlan encap packet, if destined to gateway */
diff --git a/notes.drawio.svg b/notes.drawio.svg
new file mode 100644
index 000000000..776bc1cb7
--- /dev/null
+++ b/notes.drawio.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/pipeline/Cargo.toml b/pipeline/Cargo.toml
index 0ee02b88f..46cae8628 100644
--- a/pipeline/Cargo.toml
+++ b/pipeline/Cargo.toml
@@ -5,11 +5,15 @@ edition = "2024"
publish = false
license = "Apache-2.0"
+[features]
+default = []
+test_buffer = ["net/test_buffer"]
+
[dependencies]
arc-swap = { workspace = true }
dyn-iter = { workspace = true }
id = { workspace = true }
-net = { workspace = true, features = ["test_buffer"] }
+net = { workspace = true }
ordermap = { workspace = true, features = ["std"] }
thiserror = { workspace = true }
tracing = { workspace = true }
diff --git a/pipeline/src/pipeline.rs b/pipeline/src/pipeline.rs
index 31696343d..6227ef687 100644
--- a/pipeline/src/pipeline.rs
+++ b/pipeline/src/pipeline.rs
@@ -35,7 +35,6 @@ pub enum PipelineError {
impl DynPipeline {
/// Create a [`DynPipeline`].
- #[allow(unused)]
#[must_use]
pub fn new() -> Self {
Self {
@@ -47,7 +46,6 @@ impl DynPipeline {
///
/// This method takes a [`NetworkFunction`] and adds it to the pipeline.
///
- #[allow(unused)]
#[must_use]
pub fn add_stage + 'static>(self, nf: NF) -> Self {
self.add_stage_dyn(nf_dyn(nf))
@@ -144,7 +142,6 @@ impl DynPipeline {
///
/// # See Also
///
- #[allow(unused)]
pub fn get_stage_by_id + 'static>(
&self,
id: &StageId,
@@ -159,7 +156,6 @@ impl DynPipeline {
///
/// # See Also
///
- #[allow(unused)]
#[must_use]
pub fn get_stage_dyn_by_id>(&self, id: &StageId) -> Option<&T> {
self.nfs
diff --git a/routing/src/atable/adjacency.rs b/routing/src/atable/adjacency.rs
index 48b8cfccc..b637da4b1 100644
--- a/routing/src/atable/adjacency.rs
+++ b/routing/src/atable/adjacency.rs
@@ -3,10 +3,9 @@
//! State objects to keep adjacency information
-use crate::interfaces::interface::IfIndex;
use ahash::RandomState;
-use dplane_rpc::msg::Ifindex;
use net::eth::mac::Mac;
+use net::interface::InterfaceIndex;
use std::collections::HashMap;
use std::net::IpAddr;
@@ -14,14 +13,14 @@ use std::net::IpAddr;
/// Object that represents an adjacency or ARP/ND entry
pub struct Adjacency {
address: IpAddr,
- ifindex: IfIndex,
+ ifindex: InterfaceIndex,
mac: Mac,
}
impl Adjacency {
/// Create an [`Adjacency`] object
#[must_use]
- pub fn new(address: IpAddr, ifindex: IfIndex, mac: Mac) -> Self {
+ pub fn new(address: IpAddr, ifindex: InterfaceIndex, mac: Mac) -> Self {
Self {
address,
ifindex,
@@ -30,7 +29,7 @@ impl Adjacency {
}
/// Get the Ifindex of an [`Adjacency`] object
#[must_use]
- pub fn get_ifindex(&self) -> Ifindex {
+ pub fn get_ifindex(&self) -> InterfaceIndex {
self.ifindex
}
@@ -49,7 +48,7 @@ impl Adjacency {
/// A table of [`Adjacency`]ies
#[derive(Default, Clone)]
-pub struct AdjacencyTable(HashMap<(IfIndex, IpAddr), Adjacency, RandomState>);
+pub struct AdjacencyTable(HashMap<(InterfaceIndex, IpAddr), Adjacency, RandomState>);
impl AdjacencyTable {
#[must_use]
@@ -64,7 +63,7 @@ impl AdjacencyTable {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
- pub fn iter(&self) -> impl Iterator
- {
+ pub fn iter(&self) -> impl Iterator
- {
self.0.iter()
}
pub fn values(&self) -> impl Iterator
- {
@@ -74,11 +73,11 @@ impl AdjacencyTable {
self.0
.insert((adjacency.ifindex, adjacency.address), adjacency);
}
- pub fn del_adjacency(&mut self, address: IpAddr, ifindex: IfIndex) {
+ pub fn del_adjacency(&mut self, address: IpAddr, ifindex: InterfaceIndex) {
self.0.remove(&(ifindex, address));
}
#[must_use]
- pub fn get_adjacency(&self, address: IpAddr, ifindex: IfIndex) -> Option<&Adjacency> {
+ pub fn get_adjacency(&self, address: IpAddr, ifindex: InterfaceIndex) -> Option<&Adjacency> {
self.0.get(&(ifindex, address))
}
pub fn clear(&mut self) {
@@ -89,25 +88,26 @@ impl AdjacencyTable {
#[cfg(test)]
#[rustfmt::skip]
pub mod tests {
- use super::*;
- use crate::rib::vrf::tests::mk_addr;
+use super::*;
+use crate::rib::vrf::tests::mk_addr;
+use net::interface::InterfaceIndex;
pub fn build_test_atable() -> AdjacencyTable {
let mut atable = AdjacencyTable::new();
{
let ip = mk_addr("10.0.0.1");
let mac = Mac::from([0x0, 0x0, 0x0, 0x0 ,0xaa, 0x1]);
- atable.add_adjacency(Adjacency::new(ip, 2, mac));
+ atable.add_adjacency(Adjacency::new(ip, InterfaceIndex::try_new(2).unwrap(), mac));
}
{
let ip = mk_addr("10.0.0.5");
let mac = Mac::from([0x0, 0x0, 0x0, 0x0 ,0xaa, 0x5]);
- atable.add_adjacency(Adjacency::new(ip, 3, mac));
+ atable.add_adjacency(Adjacency::new(ip, InterfaceIndex::try_new(3).unwrap(), mac));
}
{
let ip = mk_addr("10.0.0.9");
let mac = Mac::from([0x0, 0x0, 0x0, 0x0 ,0xaa, 0x9]);
- atable.add_adjacency(Adjacency::new(ip, 4, mac ));
+ atable.add_adjacency(Adjacency::new(ip, InterfaceIndex::try_new(4).unwrap(), mac ));
}
atable
}
@@ -118,11 +118,11 @@ pub mod tests {
let ip = mk_addr("10.0.0.1");
let mac = Mac::from([0x0, 0x0, 0x0, 0x0 ,0x0, 0x1]);
- let a1 = Adjacency::new(ip, 10, mac);
+ let a1 = Adjacency::new(ip, InterfaceIndex::try_new(10).unwrap(), mac);
atable.add_adjacency(a1);
- assert_eq!(atable.get_adjacency(ip, 10).unwrap().mac, mac);
+ assert_eq!(atable.get_adjacency(ip, InterfaceIndex::try_new(10).unwrap()).unwrap().mac, mac);
- atable.del_adjacency(ip, 10);
- assert!(atable.get_adjacency(ip, 10).is_none());
+ atable.del_adjacency(ip, InterfaceIndex::try_new(10).unwrap());
+ assert!(atable.get_adjacency(ip, InterfaceIndex::try_new(10).unwrap()).is_none());
}
}
diff --git a/routing/src/atable/atablerw.rs b/routing/src/atable/atablerw.rs
index 9f6e7b2aa..3790e8ace 100644
--- a/routing/src/atable/atablerw.rs
+++ b/routing/src/atable/atablerw.rs
@@ -3,15 +3,14 @@
//! Adjacency table left-right
+use crate::atable::adjacency::{Adjacency, AdjacencyTable};
use left_right::{Absorb, ReadGuard, ReadHandle, WriteHandle};
+use net::interface::InterfaceIndex;
use std::net::IpAddr;
-use crate::atable::adjacency::{Adjacency, AdjacencyTable};
-use crate::interfaces::interface::IfIndex;
-
enum AtableChange {
Add(Adjacency),
- Del((IpAddr, IfIndex)),
+ Del((IpAddr, InterfaceIndex)),
Clear,
}
@@ -50,7 +49,7 @@ impl AtableWriter {
self.0.publish();
}
}
- pub fn del_adjacency(&mut self, address: IpAddr, ifindex: IfIndex, publish: bool) {
+ pub fn del_adjacency(&mut self, address: IpAddr, ifindex: InterfaceIndex, publish: bool) {
self.0.append(AtableChange::Del((address, ifindex)));
if publish {
self.0.publish();
diff --git a/routing/src/atable/resolver.rs b/routing/src/atable/resolver.rs
index 260dca4b0..0eb11f3c8 100644
--- a/routing/src/atable/resolver.rs
+++ b/routing/src/atable/resolver.rs
@@ -14,20 +14,25 @@ use netdev::Interface;
use netdev::get_interfaces;
use procfs::net::arp;
+use super::adjacency::Adjacency;
+use super::atablerw::AtableReader;
use crate::atable::atablerw::AtableWriter;
use net::eth::mac::Mac;
+use net::interface::InterfaceIndex;
use tracing::{debug, error, warn};
-use super::adjacency::Adjacency;
-use super::atablerw::AtableReader;
-
/// Util that returns the ifindex of the interface with the given name out of the slice of
/// interfaces provided as argument.
-fn get_interface_ifindex(interfaces: &[Interface], name: &str) -> Option {
- interfaces
- .iter()
- .position(|interface| interface.name == name)
- .map(|pos| interfaces[pos].index)
+fn get_interface_ifindex(interfaces: &[Interface], name: &str) -> Option {
+ interfaces.iter().find_map(|interface| {
+ if interface.name == name {
+ InterfaceIndex::try_new(interface.index)
+ .map_err(|e| error!("{e}"))
+ .ok()
+ } else {
+ None
+ }
+ })
}
/// An object able to resolve ARP entries and update the adjacency table. The [`AtResolver`]
diff --git a/routing/src/config/interface.rs b/routing/src/config/interface.rs
index d95a3a6b6..49072e139 100644
--- a/routing/src/config/interface.rs
+++ b/routing/src/config/interface.rs
@@ -3,13 +3,12 @@
//! Router interface configuration
-#[allow(unused)]
-use tracing::debug;
-
use crate::RouterError;
use crate::config::RouterConfig;
use crate::interfaces::iftable::IfTable;
-use crate::interfaces::interface::IfIndex;
+use net::interface::InterfaceIndex;
+#[allow(unused)]
+use tracing::debug;
use crate::interfaces::iftablerw::IfTableWriter;
use crate::interfaces::interface::{AttachConfig, Attachment};
@@ -20,8 +19,8 @@ use crate::rib::VrfTable;
///////////////////////////////////////////////////////////////////////////////////////
pub(crate) struct ReconfigInterfacePlan {
#[allow(unused)]
- to_keep: Vec, /* interfaces to keep as is */
- to_delete: Vec, /* interfaces to delete */
+ to_keep: Vec, /* interfaces to keep as is */
+ to_delete: Vec, /* interfaces to delete */
to_modify: Vec, /* interfaces to change */
to_add: Vec, /* interfaces to add */
}
@@ -32,8 +31,8 @@ impl ReconfigInterfacePlan {
///////////////////////////////////////////////////////////////////////////////////
#[must_use]
pub(crate) fn generate(config: &RouterConfig, iftable: &IfTable) -> Self {
- let mut to_delete: Vec = vec![];
- let mut to_keep: Vec = vec![];
+ let mut to_delete: Vec = vec![];
+ let mut to_keep: Vec = vec![];
let mut to_modify: Vec = vec![];
let mut to_add: Vec = vec![];
diff --git a/routing/src/config/mod.rs b/routing/src/config/mod.rs
index 6c1996b89..dc7444a4c 100644
--- a/routing/src/config/mod.rs
+++ b/routing/src/config/mod.rs
@@ -9,20 +9,19 @@ mod interface;
mod vrf;
mod vtep;
-use config::GenId;
-use net::vxlan::Vni;
-use std::collections::{BTreeMap, BTreeSet};
-use std::fmt::format;
-use tracing::{debug, error};
-
use crate::RouterError;
use crate::evpn::Vtep;
use crate::interfaces::iftable::IfTable;
-use crate::interfaces::interface::IfIndex;
use crate::interfaces::interface::RouterInterfaceConfig;
use crate::rib::VrfTable;
use crate::rib::vrf::{RouterVrfConfig, VrfId};
use crate::routingdb::RoutingDb;
+use config::GenId;
+use net::interface::InterfaceIndex;
+use net::vxlan::Vni;
+use std::collections::{BTreeMap, BTreeSet};
+use std::fmt::format;
+use tracing::{debug, error};
use crate::config::interface::ReconfigInterfacePlan;
use crate::config::vrf::ReconfigVrfPlan;
@@ -37,7 +36,7 @@ pub type FrrConfig = String;
pub struct RouterConfig {
genid: GenId,
vrfs: BTreeMap,
- interfaces: BTreeMap,
+ interfaces: BTreeMap,
vtep: Option,
frr_cfg: Option,
}
@@ -126,7 +125,7 @@ impl RouterConfig {
/// Get the config for an interface with a given [`IfIndex`]
//////////////////////////////////////////////////////////////////////////////////
#[must_use]
- fn get_interface(&self, ifindex: IfIndex) -> Option<&RouterInterfaceConfig> {
+ fn get_interface(&self, ifindex: InterfaceIndex) -> Option<&RouterInterfaceConfig> {
self.interfaces.get(&ifindex)
}
@@ -135,7 +134,7 @@ impl RouterConfig {
//////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
#[must_use]
- fn get_interface_mut(&mut self, ifindex: IfIndex) -> Option<&mut RouterInterfaceConfig> {
+ fn get_interface_mut(&mut self, ifindex: InterfaceIndex) -> Option<&mut RouterInterfaceConfig> {
self.interfaces.get_mut(&ifindex)
}
@@ -237,7 +236,8 @@ mod tests {
use tracing::debug;
use net::{route::RouteTableId, vxlan::Vni};
use net::eth::mac::Mac;
- use crate::{config::RouterConfig, evpn::Vtep, interfaces::interface::{AttachConfig, RouterInterfaceConfig}, rib::vrf::RouterVrfConfig};
+use net::interface::InterfaceIndex;
+use crate::{config::RouterConfig, evpn::Vtep, interfaces::interface::{AttachConfig, RouterInterfaceConfig}, rib::vrf::RouterVrfConfig};
use crate::interfaces::interface::IfState;
use crate::interfaces::interface::IfType;
use crate::interfaces::interface::IfDataEthernet;
@@ -282,13 +282,15 @@ mod tests {
}
fn add_router_interface_configs(config: &mut RouterConfig) {
- let mut ifconfig = RouterInterfaceConfig::new("Loopback", 1);
+ let lo_idx = InterfaceIndex::try_new(1).unwrap();
+ let mut ifconfig = RouterInterfaceConfig::new("Loopback", lo_idx);
ifconfig.set_description("main loopback interface");
ifconfig.set_iftype(IfType::Loopback);
ifconfig.set_admin_state(IfState::Up);
config.add_interface(ifconfig);
- let mut ifconfig = RouterInterfaceConfig::new("Eth0", 10);
+ let eth0_idx = InterfaceIndex::try_new(10).unwrap();
+ let mut ifconfig = RouterInterfaceConfig::new("Eth0", eth0_idx);
ifconfig.set_description("Interface to Spine-1");
ifconfig.set_admin_state(IfState::Up);
ifconfig.set_iftype(IfType::Ethernet(IfDataEthernet {
@@ -297,7 +299,8 @@ mod tests {
ifconfig.set_attach_cfg(Some(AttachConfig::VRF(100)));
config.add_interface(ifconfig);
- let mut ifconfig = RouterInterfaceConfig::new("Eth1", 11);
+ let eth1_idx = InterfaceIndex::try_new(11).unwrap();
+ let mut ifconfig = RouterInterfaceConfig::new("Eth1", eth1_idx);
ifconfig.set_description("Interface to Spine-2");
ifconfig.set_admin_state(IfState::Up);
ifconfig.set_iftype(IfType::Ethernet(IfDataEthernet {
@@ -436,7 +439,8 @@ mod tests {
debug!("━━━━━━━━ Test: Change interface name, mac, admin state and attach it to another vrf");
config.genid = 6;
- let ifconfig = config.get_interface_mut(10).expect("Should find config");
+ let idx = InterfaceIndex::try_new(10).unwrap();
+ let ifconfig = config.get_interface_mut(idx).expect("Should find config");
ifconfig.set_name("CHANGED-NAME");
ifconfig.set_description("Interface with changed config");
ifconfig.set_admin_state(IfState::Down);
@@ -448,7 +452,7 @@ mod tests {
debug!("━━━━━━━━ Test: Detach interface");
config.genid = 7;
- let ifconfig = config.get_interface_mut(10).expect("Should find config");
+ let ifconfig = config.get_interface_mut(idx).expect("Should find config");
ifconfig.set_attach_cfg(None);
test_apply_config(&config, &mut db).expect("Should succeed");
}
diff --git a/routing/src/cpi.rs b/routing/src/cpi.rs
index 1b0734c60..f1b722127 100644
--- a/routing/src/cpi.rs
+++ b/routing/src/cpi.rs
@@ -20,6 +20,7 @@ use lpm::prefix::Prefix;
use std::os::unix::net::SocketAddr;
use std::process;
+use net::interface::InterfaceIndex;
#[allow(unused)]
use tracing::{debug, error, info, trace, warn};
@@ -271,13 +272,27 @@ impl RpcOperation for Rmac {
impl RpcOperation for IfAddress {
type ObjectStore = RoutingDb;
fn add(&self, db: &mut Self::ObjectStore) -> RpcResultCode {
+ let ifindex = match InterfaceIndex::try_new(self.ifindex) {
+ Ok(idx) => idx,
+ Err(e) => {
+ error!("{e}");
+ return RpcResultCode::InvalidRequest;
+ }
+ };
db.iftw
- .add_ip_address(self.ifindex, (self.address, self.mask_len));
+ .add_ip_address(ifindex, (self.address, self.mask_len));
RpcResultCode::Ok
}
fn del(&self, db: &mut Self::ObjectStore) -> RpcResultCode {
+ let ifindex = match InterfaceIndex::try_new(self.ifindex) {
+ Ok(idx) => idx,
+ Err(e) => {
+ error!("{e}");
+ return RpcResultCode::InvalidRequest;
+ }
+ };
db.iftw
- .del_ip_address(self.ifindex, (self.address, self.mask_len));
+ .del_ip_address(ifindex, (self.address, self.mask_len));
RpcResultCode::Ok
}
}
diff --git a/routing/src/errors.rs b/routing/src/errors.rs
index 0e555974a..34e3759ba 100644
--- a/routing/src/errors.rs
+++ b/routing/src/errors.rs
@@ -3,12 +3,13 @@
//! The error results used by this library.
+use net::interface::InterfaceIndex;
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum RouterError {
#[error("No interface with ifindex {0}")]
- NoSuchInterface(u32),
+ NoSuchInterface(InterfaceIndex),
#[error("No such VRF")]
NoSuchVrf,
@@ -23,7 +24,7 @@ pub enum RouterError {
VniInvalid(u32),
#[error("An interface with ifindex {0} already exists")]
- InterfaceExists(u32),
+ InterfaceExists(InterfaceIndex),
#[error("Invalid socket path '{0}'")]
InvalidPath(String),
diff --git a/routing/src/fib/fibobjects.rs b/routing/src/fib/fibobjects.rs
index d64060564..85bd8c814 100644
--- a/routing/src/fib/fibobjects.rs
+++ b/routing/src/fib/fibobjects.rs
@@ -5,8 +5,8 @@
use net::vxlan::Vni;
-use crate::interfaces::interface::IfIndex;
use crate::rib::encapsulation::Encapsulation;
+use net::interface::InterfaceIndex;
use std::net::IpAddr;
#[derive(Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq)]
@@ -14,14 +14,18 @@ use std::net::IpAddr;
/// has to be sent and, optionally, a next-hop ip address. If
/// no address is provided, ND/ARP is required.
pub struct EgressObject {
- pub(crate) ifindex: Option,
+ pub(crate) ifindex: Option,
pub(crate) address: Option,
pub(crate) ifname: Option,
}
impl EgressObject {
#[must_use]
- pub fn new(ifindex: Option, address: Option, ifname: Option) -> Self {
+ pub fn new(
+ ifindex: Option,
+ address: Option,
+ ifname: Option,
+ ) -> Self {
Self {
ifindex,
address,
@@ -29,7 +33,7 @@ impl EgressObject {
}
}
#[must_use]
- pub fn ifindex(&self) -> &Option {
+ pub fn ifindex(&self) -> &Option {
&self.ifindex
}
#[must_use]
@@ -210,8 +214,8 @@ impl FibEntry {
pub enum PktInstruction {
#[default]
Drop, /* drop the packet */
- Local(IfIndex), /* packet is destined to gw */
- Encap(Encapsulation), /* encapsulate the packet */
- Egress(EgressObject), /* send the packet over interface to some ip */
+ Local(InterfaceIndex), /* packet is destined to gw */
+ Encap(Encapsulation), /* encapsulate the packet */
+ Egress(EgressObject), /* send the packet over interface to some ip */
Nat,
}
diff --git a/routing/src/interfaces/iftable.rs b/routing/src/interfaces/iftable.rs
index 1132ab260..5d4ca7245 100644
--- a/routing/src/interfaces/iftable.rs
+++ b/routing/src/interfaces/iftable.rs
@@ -5,17 +5,18 @@
use crate::errors::RouterError;
use crate::fib::fibtype::{FibId, FibReader};
-use crate::interfaces::interface::{IfAddress, IfIndex, IfState, Interface, RouterInterfaceConfig};
+use crate::interfaces::interface::{IfAddress, IfState, Interface, RouterInterfaceConfig};
use ahash::RandomState;
use std::collections::HashMap;
+use net::interface::InterfaceIndex;
#[allow(unused)]
use tracing::{debug, error};
#[derive(Clone)]
/// A table of network interface objects, keyed by some ifindex (u32)
pub struct IfTable {
- by_index: HashMap,
+ by_index: HashMap,
}
#[allow(clippy::new_without_default)]
@@ -38,7 +39,7 @@ impl IfTable {
self.by_index.is_empty()
}
#[must_use]
- pub fn contains(&self, ifindex: IfIndex) -> bool {
+ pub fn contains(&self, ifindex: InterfaceIndex) -> bool {
self.by_index.contains_key(&ifindex)
}
pub fn values(&self) -> impl Iterator
- {
@@ -96,7 +97,7 @@ impl IfTable {
//////////////////////////////////////////////////////////////////
/// Remove an interface from the table
//////////////////////////////////////////////////////////////////
- pub fn del_interface(&mut self, ifindex: IfIndex) {
+ pub fn del_interface(&mut self, ifindex: InterfaceIndex) {
if let Some(iface) = self.by_index.remove(&ifindex) {
debug!("Deleted interface '{}'", iface.name);
}
@@ -106,7 +107,7 @@ impl IfTable {
/// Get an immutable reference to an [`Interface`]
//////////////////////////////////////////////////////////////////
#[must_use]
- pub fn get_interface(&self, ifindex: IfIndex) -> Option<&Interface> {
+ pub fn get_interface(&self, ifindex: InterfaceIndex) -> Option<&Interface> {
self.by_index.get(&ifindex)
}
@@ -114,7 +115,7 @@ impl IfTable {
/// Get a mutable reference to an [`Interface`]
//////////////////////////////////////////////////////////////////
#[must_use]
- pub fn get_interface_mut(&mut self, ifindex: IfIndex) -> Option<&mut Interface> {
+ pub fn get_interface_mut(&mut self, ifindex: InterfaceIndex) -> Option<&mut Interface> {
self.by_index.get_mut(&ifindex)
}
@@ -125,7 +126,11 @@ impl IfTable {
///
/// Fails if the interface is not found
//////////////////////////////////////////////////////////////////
- pub fn add_ifaddr(&mut self, ifindex: IfIndex, ifaddr: &IfAddress) -> Result<(), RouterError> {
+ pub fn add_ifaddr(
+ &mut self,
+ ifindex: InterfaceIndex,
+ ifaddr: &IfAddress,
+ ) -> Result<(), RouterError> {
if let Some(iface) = self.by_index.get_mut(&ifindex) {
iface.add_ifaddr(ifaddr);
Ok(())
@@ -137,7 +142,7 @@ impl IfTable {
//////////////////////////////////////////////////////////////////
/// Un-assign an Ip address from an interface.
//////////////////////////////////////////////////////////////////
- pub fn del_ifaddr(&mut self, ifindex: IfIndex, ifaddr: &IfAddress) {
+ pub fn del_ifaddr(&mut self, ifindex: InterfaceIndex, ifaddr: &IfAddress) {
if let Some(iface) = self.by_index.get_mut(&ifindex) {
iface.del_ifaddr(&(ifaddr.0, ifaddr.1));
}
@@ -157,7 +162,7 @@ impl IfTable {
//////////////////////////////////////////////////////////////////////
/// Attach [`Interface`] to the provided [`FibReader`]
//////////////////////////////////////////////////////////////////////
- pub fn attach_interface_to_vrf(&mut self, ifindex: IfIndex, fibr: FibReader) {
+ pub fn attach_interface_to_vrf(&mut self, ifindex: InterfaceIndex, fibr: FibReader) {
if let Some(iface) = self.get_interface_mut(ifindex) {
iface.attach_vrf(fibr);
} else {
@@ -168,7 +173,7 @@ impl IfTable {
//////////////////////////////////////////////////////////////////////
/// Detach [`Interface`] from wherever it is attached
//////////////////////////////////////////////////////////////////////
- pub fn detach_interface_from_vrf(&mut self, ifindex: IfIndex) {
+ pub fn detach_interface_from_vrf(&mut self, ifindex: InterfaceIndex) {
if let Some(iface) = self.get_interface_mut(ifindex) {
iface.detach();
} else {
@@ -179,7 +184,7 @@ impl IfTable {
//////////////////////////////////////////////////////////////////////
/// Set the operational state of an [`Interface`]
//////////////////////////////////////////////////////////////////////
- pub fn set_iface_oper_state(&mut self, ifindex: IfIndex, state: IfState) {
+ pub fn set_iface_oper_state(&mut self, ifindex: InterfaceIndex, state: IfState) {
if let Some(ifr) = self.get_interface_mut(ifindex) {
ifr.set_oper_state(state);
}
@@ -188,7 +193,7 @@ impl IfTable {
//////////////////////////////////////////////////////////////////////
/// Set the admin state of an [`Interface`]
//////////////////////////////////////////////////////////////////////
- pub fn set_iface_admin_state(&mut self, ifindex: IfIndex, state: IfState) {
+ pub fn set_iface_admin_state(&mut self, ifindex: InterfaceIndex, state: IfState) {
if let Some(ifr) = self.get_interface_mut(ifindex) {
ifr.set_admin_state(state);
}
diff --git a/routing/src/interfaces/iftablerw.rs b/routing/src/interfaces/iftablerw.rs
index 68db32fbf..d76d8a8cf 100644
--- a/routing/src/interfaces/iftablerw.rs
+++ b/routing/src/interfaces/iftablerw.rs
@@ -6,24 +6,24 @@
use crate::errors::RouterError;
use crate::fib::fibtype::FibId;
use crate::fib::fibtype::FibReader;
+use crate::interfaces::iftable::IfTable;
+use crate::interfaces::interface::{IfAddress, IfState, RouterInterfaceConfig};
use crate::rib::vrf::VrfId;
use crate::rib::vrftable::VrfTable;
use left_right::{Absorb, ReadGuard, ReadHandle, WriteHandle};
-
-use crate::interfaces::iftable::IfTable;
-use crate::interfaces::interface::{IfAddress, IfIndex, IfState, RouterInterfaceConfig};
+use net::interface::InterfaceIndex;
enum IfTableChange {
Add(RouterInterfaceConfig),
Mod(RouterInterfaceConfig),
- Del(IfIndex),
- Attach((IfIndex, FibReader)),
- Detach(IfIndex),
+ Del(InterfaceIndex),
+ Attach((InterfaceIndex, FibReader)),
+ Detach(InterfaceIndex),
DetachFromVrf(FibId),
- AddIpAddress((IfIndex, IfAddress)),
- DelIpAddress((IfIndex, IfAddress)),
- UpdateOpState((IfIndex, IfState)),
- UpdateAdmState((IfIndex, IfState)),
+ AddIpAddress((InterfaceIndex, IfAddress)),
+ DelIpAddress((InterfaceIndex, IfAddress)),
+ UpdateOpState((InterfaceIndex, IfState)),
+ UpdateAdmState((InterfaceIndex, IfState)),
}
impl Absorb for IfTable {
fn absorb_first(&mut self, change: &mut IfTableChange, _: &Self) {
@@ -99,26 +99,26 @@ impl IfTableWriter {
self.0.publish();
Ok(())
}
- pub fn del_interface(&mut self, ifindex: IfIndex) {
+ pub fn del_interface(&mut self, ifindex: InterfaceIndex) {
self.0.append(IfTableChange::Del(ifindex));
self.0.publish();
}
- pub fn add_ip_address(&mut self, ifindex: IfIndex, ifaddr: IfAddress) {
+ pub fn add_ip_address(&mut self, ifindex: InterfaceIndex, ifaddr: IfAddress) {
self.0
.append(IfTableChange::AddIpAddress((ifindex, ifaddr)));
self.0.publish();
}
- pub fn del_ip_address(&mut self, ifindex: IfIndex, ifaddr: IfAddress) {
+ pub fn del_ip_address(&mut self, ifindex: InterfaceIndex, ifaddr: IfAddress) {
self.0
.append(IfTableChange::DelIpAddress((ifindex, ifaddr)));
self.0.publish();
}
- pub fn set_iface_oper_state(&mut self, ifindex: IfIndex, state: IfState) {
+ pub fn set_iface_oper_state(&mut self, ifindex: InterfaceIndex, state: IfState) {
self.0
.append(IfTableChange::UpdateOpState((ifindex, state)));
self.0.publish();
}
- pub fn set_iface_admin_state(&mut self, ifindex: IfIndex, state: IfState) {
+ pub fn set_iface_admin_state(&mut self, ifindex: InterfaceIndex, state: IfState) {
self.0
.append(IfTableChange::UpdateAdmState((ifindex, state)));
self.0.publish();
@@ -134,7 +134,7 @@ impl IfTableWriter {
fn interface_attach_check(
&mut self,
- ifindex: IfIndex,
+ ifindex: InterfaceIndex,
vrfid: VrfId,
vrftable: &VrfTable,
) -> Result {
@@ -154,7 +154,7 @@ impl IfTableWriter {
/// Fails if the interface is not found
pub fn attach_interface_to_vrf(
&mut self,
- ifindex: IfIndex,
+ ifindex: InterfaceIndex,
vrfid: VrfId,
vrftable: &VrfTable,
) -> Result<(), RouterError> {
@@ -163,7 +163,7 @@ impl IfTableWriter {
self.0.publish();
Ok(())
}
- pub fn detach_interface(&mut self, ifindex: IfIndex) {
+ pub fn detach_interface(&mut self, ifindex: InterfaceIndex) {
self.0.append(IfTableChange::Detach(ifindex));
self.0.publish();
}
diff --git a/routing/src/interfaces/interface.rs b/routing/src/interfaces/interface.rs
index 71c962489..c3e6cc5df 100644
--- a/routing/src/interfaces/interface.rs
+++ b/routing/src/interfaces/interface.rs
@@ -8,7 +8,7 @@
use crate::fib::fibtype::{FibId, FibReader};
use crate::rib::vrf::VrfId;
use net::eth::mac::Mac;
-use net::interface::Mtu;
+use net::interface::{InterfaceIndex, Mtu};
use net::vlan::Vid;
use std::net::IpAddr;
@@ -17,9 +17,6 @@ use std::collections::HashSet;
#[allow(unused)]
use tracing::{debug, error, info};
-/// A type to uniquely identify a network interface
-pub type IfIndex = u32;
-
/// An Ipv4 or Ipv6 address and mask configured on an interface
pub type IfAddress = (IpAddr, u8);
@@ -70,31 +67,31 @@ pub enum IfState {
Up = 2,
}
-#[derive(Clone)]
+#[derive(Debug, Clone)]
pub enum Attachment {
VRF(FibReader),
- BD,
+ BD, // TODO: what is this?
}
#[derive(Clone, Debug, PartialEq)]
pub enum AttachConfig {
VRF(VrfId),
- BD,
+ BD, // TODO: what is this
}
/// An object representing the configuration for an [`Interface`]
#[derive(Clone, Debug, PartialEq)]
pub struct RouterInterfaceConfig {
- pub ifindex: IfIndex, /* ifindex of kernel interface (key) */
- pub name: String, /* name of interface */
- pub description: Option, /* description - informational */
- pub iftype: IfType, /* type of interface */
- pub admin_state: IfState, /* admin state */
+ pub ifindex: InterfaceIndex, /* ifindex of kernel interface (key) */
+ pub name: String, /* name of interface */
+ pub description: Option, /* description - informational */
+ pub iftype: IfType, /* type of interface */
+ pub admin_state: IfState, /* admin state */
pub attach_cfg: Option, /* attach config */
pub mtu: Option,
}
impl RouterInterfaceConfig {
- pub fn new(name: &str, ifindex: IfIndex) -> Self {
+ pub fn new(name: &str, ifindex: InterfaceIndex) -> Self {
Self {
ifindex,
name: name.to_owned(),
@@ -125,12 +122,12 @@ impl RouterInterfaceConfig {
}
}
-#[derive(Clone)]
+#[derive(Debug, Clone)]
/// An object representing a network interface and its state
pub struct Interface {
pub name: String,
pub description: Option,
- pub ifindex: IfIndex,
+ pub ifindex: InterfaceIndex,
pub iftype: IfType,
pub admin_state: IfState,
pub mtu: Option,
diff --git a/routing/src/interfaces/mod.rs b/routing/src/interfaces/mod.rs
index c3a725bbe..70208789d 100644
--- a/routing/src/interfaces/mod.rs
+++ b/routing/src/interfaces/mod.rs
@@ -18,6 +18,7 @@ pub mod tests {
};
use crate::rib::vrf::{RouterVrfConfig, Vrf};
use net::eth::mac::Mac;
+ use net::interface::InterfaceIndex;
use net::vlan::Vid;
use std::net::IpAddr;
use std::str::FromStr;
@@ -27,13 +28,15 @@ pub mod tests {
let mut iftable = IfTable::new();
/* create loopback */
- let mut lo = RouterInterfaceConfig::new("Loopback", 1);
+ let lo_idx = InterfaceIndex::try_new(1).unwrap();
+ let mut lo = RouterInterfaceConfig::new("Loopback", lo_idx);
lo.set_admin_state(IfState::Up);
lo.set_description("Main loopback interface");
lo.set_iftype(IfType::Loopback);
/* create Eth0 */
- let mut eth0 = RouterInterfaceConfig::new("eth0", 2);
+ let eth0_idx = InterfaceIndex::try_new(2).unwrap();
+ let mut eth0 = RouterInterfaceConfig::new("eth0", eth0_idx);
eth0.set_admin_state(IfState::Up);
eth0.set_description("Uplink to the Moon");
eth0.set_iftype(IfType::Ethernet(IfDataEthernet {
@@ -41,7 +44,8 @@ pub mod tests {
}));
/* create Eth1 */
- let mut eth1 = RouterInterfaceConfig::new("eth1", 3);
+ let eth1_idx = InterfaceIndex::try_new(3).unwrap();
+ let mut eth1 = RouterInterfaceConfig::new("eth1", eth1_idx);
eth1.set_admin_state(IfState::Up);
eth1.set_description("Downlink from Mars");
eth1.set_iftype(IfType::Ethernet(IfDataEthernet {
@@ -49,7 +53,8 @@ pub mod tests {
}));
/* create Eth2 */
- let mut eth2 = RouterInterfaceConfig::new("eth2", 4);
+ let eth2_idx = InterfaceIndex::try_new(4).unwrap();
+ let mut eth2 = RouterInterfaceConfig::new("eth2", eth2_idx);
eth2.set_admin_state(IfState::Up);
eth2.set_description("Downlink from Sun");
eth2.set_iftype(IfType::Ethernet(IfDataEthernet {
@@ -57,7 +62,8 @@ pub mod tests {
}));
/* create vlan.100 */
- let mut vlan100 = RouterInterfaceConfig::new("eth1.100", 5);
+ let vlan100_idx = InterfaceIndex::try_new(5).unwrap();
+ let mut vlan100 = RouterInterfaceConfig::new("eth1.100", vlan100_idx);
vlan100.set_admin_state(IfState::Up);
vlan100.set_description("External customer 1");
vlan100.set_iftype(IfType::Dot1q(IfDataDot1q {
@@ -66,7 +72,8 @@ pub mod tests {
}));
/* create vlan.200 */
- let mut vlan200 = RouterInterfaceConfig::new("eth1.200", 6);
+ let vlan200_idx = InterfaceIndex::try_new(6).unwrap();
+ let mut vlan200 = RouterInterfaceConfig::new("eth1.200", vlan200_idx);
vlan200.set_admin_state(IfState::Up);
vlan200.set_description("External customer 2");
vlan200.set_iftype(IfType::Dot1q(IfDataDot1q {
@@ -114,15 +121,17 @@ pub mod tests {
vrf.set_fibw(fibw);
/* lookup interface with non-existent index */
- let iface = iftable.get_interface(100);
+ let idx100 = InterfaceIndex::try_new(100).unwrap();
+ let iface = iftable.get_interface(idx100);
assert!(iface.is_none());
/* Lookup interface by ifindex 2 */
- let iface = iftable.get_interface_mut(2);
+ let idx2 = InterfaceIndex::try_new(2).unwrap();
+ let iface = iftable.get_interface_mut(idx2);
assert!(iface.is_some());
let eth0 = iface.unwrap();
assert_eq!(eth0.name, "eth0", "We should get eth0");
- assert_eq!(eth0.ifindex, 2, "eth0 has ifindex 2");
+ assert_eq!(eth0.ifindex, idx2, "eth0 has ifindex 2");
/* Add an ip address (the interface is in the iftable) */
let address = IpAddr::from_str("10.0.0.1").expect("Bad address");
@@ -136,7 +145,8 @@ pub mod tests {
let mut iftable = IfTable::new();
/* create Eth0 */
- let mut eth0 = RouterInterfaceConfig::new("eth0", 2);
+ let eth0_idx = InterfaceIndex::try_new(2).unwrap();
+ let mut eth0 = RouterInterfaceConfig::new("eth0", eth0_idx);
eth0.set_iftype(IfType::Ethernet(IfDataEthernet {
mac: Mac::from([0x0, 0xaa, 0x0, 0x0, 0x0, 0x1]),
}));
@@ -146,14 +156,14 @@ pub mod tests {
assert_eq!(iftable.len(), 1, "Eth0 should be there");
/* test get_mac */
- let iface = iftable.get_interface(2).expect("Should be there");
+ let iface = iftable.get_interface(eth0_idx).expect("Should be there");
assert_eq!(
Mac::from([0x0, 0xaa, 0x0, 0x0, 0x0, 0x1]),
iface.get_mac().unwrap()
);
/* Add interface again -- idempotence */
- let mut eth0 = RouterInterfaceConfig::new("eth0", 2);
+ let mut eth0 = RouterInterfaceConfig::new("eth0", eth0_idx);
eth0.set_iftype(IfType::Ethernet(IfDataEthernet {
mac: Mac::from([0x0, 0xaa, 0x0, 0x0, 0x0, 0x1]),
}));
@@ -162,7 +172,7 @@ pub mod tests {
assert_eq!(iftable.len(), 1, "Only eth0 should be there");
/* Delete eth0 by index */
- iftable.del_interface(2);
+ iftable.del_interface(eth0_idx);
assert_eq!(iftable.len(), 0, "No interface should be there");
}
}
diff --git a/routing/src/rib/nexthop.rs b/routing/src/rib/nexthop.rs
index 718a3aaba..77f879208 100644
--- a/routing/src/rib/nexthop.rs
+++ b/routing/src/rib/nexthop.rs
@@ -16,6 +16,7 @@ use std::hash::Hash;
use std::net::IpAddr;
use std::option::Option;
+use net::interface::InterfaceIndex;
use std::cell::RefCell;
use std::rc::Rc;
#[cfg(test)]
@@ -51,7 +52,7 @@ pub enum FwAction {
pub struct NhopKey {
pub origin: RouteOrigin,
pub address: Option,
- pub ifindex: Option,
+ pub ifindex: Option,
pub encap: Option,
pub fwaction: FwAction,
pub ifname: Option,
@@ -65,7 +66,7 @@ impl NhopKey {
pub fn new(
origin: RouteOrigin,
address: Option,
- ifindex: Option,
+ ifindex: Option,
encap: Option,
fwaction: FwAction,
ifname: Option,
@@ -99,7 +100,7 @@ impl NhopKey {
}
#[cfg(test)]
#[must_use]
- pub fn with_addr_ifindex(address: &IpAddr, ifindex: u32) -> Self {
+ pub fn with_addr_ifindex(address: &IpAddr, ifindex: InterfaceIndex) -> Self {
Self {
address: Some(*address),
ifindex: Some(ifindex),
@@ -116,7 +117,7 @@ impl NhopKey {
}
#[cfg(test)]
#[must_use]
- pub fn with_ifindex(ifindex: u32) -> Self {
+ pub fn with_ifindex(ifindex: InterfaceIndex) -> Self {
Self {
ifindex: Some(ifindex),
..Default::default()
@@ -505,9 +506,9 @@ mod tests {
let n2_k = NhopKey::expect_from("10.0.2.1");
let n3_k = NhopKey::expect_from("10.0.3.1");
- let i1_k = NhopKey::with_ifindex(1);
- let i2_k = NhopKey::with_ifindex(2);
- let i3_k = NhopKey::with_ifindex(3);
+ let i1_k = NhopKey::with_ifindex(InterfaceIndex::try_new(1).unwrap());
+ let i2_k = NhopKey::with_ifindex(InterfaceIndex::try_new(2).unwrap());
+ let i3_k = NhopKey::with_ifindex(InterfaceIndex::try_new(3).unwrap());
/* Add some next-hops and references */
{
@@ -550,7 +551,7 @@ mod tests {
fn test_nhop_store_shared_resolvers() {
let mut store = NhopStore::new();
- let i1_k = NhopKey::with_ifindex(1);
+ let i1_k = NhopKey::with_ifindex(InterfaceIndex::try_new(1).unwrap());
let n1_k = NhopKey::expect_from("11.0.0.1");
let n2_k = NhopKey::expect_from("11.0.0.2");
@@ -593,7 +594,7 @@ mod tests {
fn test_nhop_store_flush_resolvers() {
let mut store = NhopStore::new();
- let i1_k = NhopKey::with_ifindex(1);
+ let i1_k = NhopKey::with_ifindex(InterfaceIndex::try_new(1).unwrap());
let n1_k = NhopKey::expect_from("11.0.0.1");
let n2_k = NhopKey::expect_from("11.0.0.2");
@@ -620,9 +621,9 @@ mod tests {
let mut store = NhopStore::new();
/* add "interface" next-hops */
- let i1 = store.add_nhop(&NhopKey::with_ifindex(1));
- let i2 = store.add_nhop(&NhopKey::with_ifindex(2));
- let i3 = store.add_nhop(&NhopKey::with_ifindex(3));
+ let i1 = store.add_nhop(&NhopKey::with_ifindex(InterfaceIndex::try_new(1).unwrap()));
+ let i2 = store.add_nhop(&NhopKey::with_ifindex(InterfaceIndex::try_new(2).unwrap()));
+ let i3 = store.add_nhop(&NhopKey::with_ifindex(InterfaceIndex::try_new(3).unwrap()));
/* add "adjacent" nexthops */
let a1 = store.add_nhop(&NhopKey::expect_from("10.0.0.1"));
@@ -661,15 +662,15 @@ mod tests {
/* add "adjacent" nexthops with interface resolved */
let a1 = store.add_nhop(&NhopKey::with_addr_ifindex(
&("10.0.0.1".parse().unwrap()),
- 1,
+ InterfaceIndex::try_new(1).unwrap(),
));
let a2 = store.add_nhop(&NhopKey::with_addr_ifindex(
&("10.0.0.5".parse().unwrap()),
- 2,
+ InterfaceIndex::try_new(2).unwrap(),
));
let a3 = store.add_nhop(&NhopKey::with_addr_ifindex(
&("10.0.0.9".parse().unwrap()),
- 3,
+ InterfaceIndex::try_new(3).unwrap(),
));
// add "non-adjacent" nexthops
diff --git a/routing/src/rib/rib2fib.rs b/routing/src/rib/rib2fib.rs
index 5d7480960..a7acb232e 100644
--- a/routing/src/rib/rib2fib.rs
+++ b/routing/src/rib/rib2fib.rs
@@ -12,6 +12,7 @@ use crate::rib::vrf::RouteOrigin;
use crate::fib::fibobjects::{EgressObject, FibEntry, FibGroup, PktInstruction};
+use net::interface::InterfaceIndex;
#[cfg(test)]
use std::net::IpAddr;
@@ -24,7 +25,11 @@ impl Nhop {
fn build_pkt_instructions(&self) -> Vec {
let mut instructions = Vec::with_capacity(2);
if self.key.origin == RouteOrigin::Local {
- instructions.push(PktInstruction::Local(self.key.ifindex.unwrap_or(0)));
+ instructions.push(PktInstruction::Local(
+ self.key
+ .ifindex
+ .unwrap_or(InterfaceIndex::try_new(1).unwrap_or_else(|_| unreachable!())),
+ )); // TODO: why was 0 the default? That doesn't make sense at all
return instructions;
}
if self.key.fwaction == FwAction::Drop {
diff --git a/routing/src/rib/vrf.rs b/routing/src/rib/vrf.rs
index 2539f27b6..13192c84e 100644
--- a/routing/src/rib/vrf.rs
+++ b/routing/src/rib/vrf.rs
@@ -16,9 +16,9 @@ use super::nexthop::{FwAction, Nhop, NhopKey, NhopStore};
use crate::evpn::{RmacStore, Vtep};
use crate::fib::fibobjects::FibGroup;
use crate::fib::fibtype::{FibId, FibReader, FibWriter};
-use crate::interfaces::interface::IfIndex;
use lpm::prefix::{Ipv4Prefix, Ipv6Prefix, Prefix};
use lpm::trie::{PrefixMapTrieWithDefault, TrieMap};
+use net::interface::InterfaceIndex;
use net::route::RouteTableId;
use net::vxlan::Vni;
@@ -637,7 +637,7 @@ impl Vrf {
/////////////////////////////////////////////////////////////////////////
/// Special routes
/////////////////////////////////////////////////////////////////////////
- pub fn add_link_local_intf_multicast_route(&mut self, ifindex: IfIndex) {
+ pub fn add_link_local_intf_multicast_route(&mut self, ifindex: InterfaceIndex) {
let nhkey = NhopKey::new(
RouteOrigin::Local,
None,
@@ -660,7 +660,6 @@ impl Vrf {
pub mod tests {
use super::*;
use std::str::FromStr;
- use crate::interfaces::interface::IfIndex;
use crate::rib::vrf::VrfId;
use crate::rib::nexthop::{FwAction, NhopKey};
use crate::rib::encapsulation::{Encapsulation, VxlanEncapsulation};
@@ -727,14 +726,14 @@ pub mod tests {
pub fn build_test_nhop(
address: Option<&str>,
- ifindex: Option,
+ ifindex: Option,
vrfid: VrfId,
encap: Option,
) -> RouteNhop {
let key = NhopKey::new(
RouteOrigin::default(),
address.map(mk_addr),
- ifindex, encap,FwAction::Forward, None);
+ ifindex.map(|i| InterfaceIndex::try_new(i).unwrap()), encap,FwAction::Forward, None);
RouteNhop {
vrfid,
@@ -818,8 +817,8 @@ pub mod tests {
assert_eq!(best.metric, route.metric);
assert_eq!(best.origin, route.origin);
assert_eq!(best.s_nhops.len(), 2);
- assert!(best.s_nhops.iter().any(|s| s.rc.key.address == Some(mk_addr("10.0.0.1")) && s.rc.key.ifindex == Some(1)));
- assert!(best.s_nhops.iter().any(|s| s.rc.key.address == Some(mk_addr("10.0.0.2")) && s.rc.key.ifindex == Some(2)));
+ assert!(best.s_nhops.iter().any(|s| s.rc.key.address == Some(mk_addr("10.0.0.1")) && s.rc.key.ifindex == Some(InterfaceIndex::try_new(1).unwrap())));
+ assert!(best.s_nhops.iter().any(|s| s.rc.key.address == Some(mk_addr("10.0.0.2")) && s.rc.key.ifindex == Some(InterfaceIndex::try_new(2).unwrap())));
}
assert_eq!(vrf.len_v4(), (1 + num_routes) as usize, "There must be default + the ones added");
assert_eq!(vrf.nhstore.len(), 3usize,"There is drop + 2 nexthops shared by all routes");
diff --git a/routing/src/rib/vrftable.rs b/routing/src/rib/vrftable.rs
index 9065911f6..6a02031f1 100644
--- a/routing/src/rib/vrftable.rs
+++ b/routing/src/rib/vrftable.rs
@@ -326,6 +326,7 @@ mod tests {
use crate::rib::vrf::tests::build_test_vrf_nhops_partially_resolved;
use crate::rib::vrf::tests::{build_test_vrf, mk_addr};
use crate::testfib::TestFib;
+ use net::interface::InterfaceIndex;
use std::sync::Arc;
use tracing_test::traced_test;
@@ -411,44 +412,48 @@ mod tests {
/* Attach eth0 */
let vrfid = 2;
+ let idx2 = InterfaceIndex::try_new(2).unwrap();
debug!("━━━━━━━━ Test: Attach eth0 to vrf {vrfid}");
- iftw.attach_interface_to_vrf(2, vrfid, &vrftable)
+ iftw.attach_interface_to_vrf(idx2, vrfid, &vrftable)
.expect("Should succeed");
let ift = iftr.enter().unwrap();
- let eth0 = ift.get_interface(2).expect("Should find interface");
+ let eth0 = ift.get_interface(idx2).expect("Should find interface");
assert!(eth0.is_attached_to_fib(FibId::Id(vrfid)));
println!("{}", *ift);
drop(ift);
/* Attach eth1 */
let vrfid = 2;
+ let idx3 = InterfaceIndex::try_new(3).unwrap();
debug!("━━━━━━━━ Test: Attach eth1 to vrf {vrfid}");
- iftw.attach_interface_to_vrf(3, vrfid, &vrftable)
+ iftw.attach_interface_to_vrf(idx3, vrfid, &vrftable)
.expect("Should succeed");
let ift = iftr.enter().unwrap();
- let eth1 = ift.get_interface(3).expect("Should find interface");
+ let eth1 = ift.get_interface(idx3).expect("Should find interface");
assert!(eth1.is_attached_to_fib(FibId::Id(vrfid)));
println!("{}", *ift);
drop(ift);
/* Attach vlan100 */
let vrfid = 1;
+ let idx4 = InterfaceIndex::try_new(4).unwrap();
debug!("━━━━━━━━ Test: Attach eth2 to vrf {vrfid}");
- iftw.attach_interface_to_vrf(4, vrfid, &vrftable)
+ iftw.attach_interface_to_vrf(idx4, vrfid, &vrftable)
.expect("Should succeed");
let ift = iftr.enter().unwrap();
- let eth2 = ift.get_interface(4).expect("Should find interface");
+ let eth2 = ift.get_interface(idx4).expect("Should find interface");
assert!(eth2.is_attached_to_fib(FibId::Id(vrfid)));
println!("{}", *ift);
drop(ift);
/* Attach vlan200 */
let vrfid = 1;
+ let idx5 = InterfaceIndex::try_new(5).unwrap();
debug!("━━━━━━━━ Test: Attach eth1.100 to vrf {vrfid}");
- iftw.attach_interface_to_vrf(5, vrfid, &vrftable)
+ iftw.attach_interface_to_vrf(idx5, vrfid, &vrftable)
.expect("Should succeed");
let ift = iftr.enter().unwrap();
- let iface = ift.get_interface(5).expect("Should find interface");
+ let iface = ift.get_interface(idx5).expect("Should find interface");
assert!(iface.is_attached_to_fib(FibId::Id(vrfid)));
println!("{}", *ift);
drop(ift);
@@ -466,10 +471,10 @@ mod tests {
);
println!("{vrftable}");
let ift = iftr.enter().unwrap();
- let iface = ift.get_interface(4).expect("Should be there");
+ let iface = ift.get_interface(idx4).expect("Should be there");
assert!(!iface.is_attached_to_fib(FibId::Id(vrfid)));
assert!(iface.attachment.is_none());
- let iface = ift.get_interface(5).expect("Should be there");
+ let iface = ift.get_interface(idx5).expect("Should be there");
assert!(!iface.is_attached_to_fib(FibId::Id(vrfid)));
assert!(iface.attachment.is_none());
println!("{}", *ift);
@@ -498,10 +503,10 @@ mod tests {
.is_err_and(|e| e == RouterError::NoSuchVrf)
);
let ift = iftr.enter().unwrap();
- let eth0 = ift.get_interface(2).expect("Should be there");
+ let eth0 = ift.get_interface(idx2).expect("Should be there");
assert!(!eth0.is_attached_to_fib(FibId::Id(vrfid)));
assert!(eth0.attachment.is_none());
- let eth1 = ift.get_interface(3).expect("Should be there");
+ let eth1 = ift.get_interface(idx3).expect("Should be there");
assert!(!eth1.is_attached_to_fib(FibId::Id(vrfid)));
assert!(eth1.attachment.is_none());
println!("{}", *ift);
@@ -604,17 +609,18 @@ mod tests {
assert_eq!(vrftable.len(), 2); // default is always there
debug!("━━━━Test: Get interface from iftable");
+ let idx = InterfaceIndex::try_new(2).unwrap();
if let Some(iftable) = iftr.enter() {
- let iface = iftable.get_interface(2).expect("Should be there");
+ let iface = iftable.get_interface(idx).expect("Should be there");
assert_eq!(iface.name, "eth0");
debug!("\n{}", *iftable);
}
debug!("━━━━Test: Attach interface to vrf");
- iftw.attach_interface_to_vrf(2, vrfid, &vrftable)
+ iftw.attach_interface_to_vrf(idx, vrfid, &vrftable)
.expect("Should succeed");
if let Some(iftable) = iftr.enter() {
- let iface = iftable.get_interface(2).expect("Should be there");
+ let iface = iftable.get_interface(idx).expect("Should be there");
assert!(iface.attachment.is_some());
debug!("\n{}", *iftable);
}
@@ -637,7 +643,7 @@ mod tests {
assert_eq!(fibtable.len(), 1);
}
if let Some(iftable) = iftr.enter() {
- let iface = iftable.get_interface(2).expect("Should be there");
+ let iface = iftable.get_interface(idx).expect("Should be there");
assert!(iface.attachment.is_none(), "Should have been detached");
}
diff --git a/routing/src/rpc_adapt.rs b/routing/src/rpc_adapt.rs
index e2cf0bd50..78dd45c8b 100644
--- a/routing/src/rpc_adapt.rs
+++ b/routing/src/rpc_adapt.rs
@@ -21,6 +21,7 @@ use dplane_rpc::msg::{
};
use lpm::prefix::Prefix;
use net::eth::mac::Mac;
+use net::interface::InterfaceIndex;
use net::vxlan::Vni;
use std::net::{IpAddr, Ipv4Addr};
use tracing::{error, warn};
@@ -101,7 +102,16 @@ impl RouteNhop {
origin: RouteOrigin,
iftabler: &IfTableReader,
) -> Result {
- let mut ifindex = nh.ifindex;
+ let mut ifindex = nh
+ .ifindex
+ .map(|i| match InterfaceIndex::try_new(i) {
+ Ok(idx) => Ok(idx),
+ Err(e) => {
+ error!("{e}");
+ return Err(RouterError::Internal("0 is not a valid interface index"));
+ }
+ })
+ .transpose()?;
let encap = match &nh.encap {
Some(e) => {
let mut enc = Encapsulation::try_from(e)?;
@@ -119,7 +129,6 @@ impl RouteNhop {
// lookup interface name
let ifname = match ifindex {
None => None,
- Some(0) => None,
Some(k) => iftabler
.enter()
.map(|iftable| iftable.get_interface(k).map(|iface| iface.name.to_owned()))
diff --git a/scripts/test-runner.sh b/scripts/test-runner.sh
index 97cc25a5d..abddcaa0f 100755
--- a/scripts/test-runner.sh
+++ b/scripts/test-runner.sh
@@ -176,6 +176,7 @@ fi
--mount "type=bind,source=${project_dir},target=${project_dir},readonly=true,bind-propagation=rprivate" \
--mount "type=bind,source=${project_dir}/target,target=${project_dir}/target,readonly=false,bind-propagation=rprivate" \
--mount "type=bind,source=$(get_docker_sock),target=$(get_docker_sock),readonly=false,bind-propagation=rprivate" \
+ --mount "type=bind,source=/dev/net/tun,target=/dev/net/tun,readonly=false,bind-propagation=rprivate" \
--tmpfs "/run/netns:noexec,nosuid,uid=$(id -u),gid=$(id -g)" \
--tmpfs "/var/run/netns:noexec,nosuid,uid=$(id -u),gid=$(id -g)" \
--tmpfs "/tmp:nodev,noexec,nosuid,uid=$(id -u),gid=$(id -g)" \