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 @@ + + + +
link manager
link manager
p1
p1
p2
p2
vpc manager
vpc manager
p1-tap
p1-tap
p2-tap
p2-tap
kernel driver
kernel driver
pipeline
pipeline
frr
frr
strongswan
strongswan
lldpad
lldpad
link manager
link manager
p1
p1
p2
p2
vpc manager
vpc manager
p1-tap
p1-tap
p2-tap
p2-tap
dpdk driver
dpdk driver
pipeline
pipeline
frr
frr
strongswan
strongswan
lldpad
lldpad
r1
r1
v1
v1
Text is not SVG - cannot display
\ 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 @@ + + + +
link-manager netns
link-manager netns
p1
p1
p2
p2
vpc-manager netns
vpc-manager netns
p1-tap
p1-tap
p2-tap
p2-tap
kernel driver
kernel driver
pipeline
pipeline
frr
frr
strongswan
strongswan
lldpad
lldpad
trapline
trapline
dhcp server
dhcp server
dns server
dns server
web server
web server
whatever...
whatever...
user vm
user vm
vpc manager runtime
vpc manager ru...
link manager runtime
link manager ru...
Text is not SVG - cannot display
\ 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 @@ + + + +
link manager
link manager
p1
p1
p2
p2
vpc manager
vpc manager
p1-tap
p1-tap
p2-tap
p2-tap
kernel driver
kernel driver
pipeline
pipeline
frr
frr
strongswan
strongswan
lldpad
lldpad
link manager
link manager
p1
p1
p2
p2
vpc manager
vpc manager
p1-tap
p1-tap
p2-tap
p2-tap
dpdk driver
dpdk driver
pipeline
pipeline
frr
frr
strongswan
strongswan
lldpad
lldpad
r1
r1
v1
v1
Text is not SVG - cannot display
\ 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 @@ + + + +
  link-manager netns
  link-manager netns
p1
p1
p2
p2
  vpc-manager netns
  vpc-manager netns
p1-tap
p1-tap
p2-tap
p2-tap
kernel driver
kernel driver
pipeline
pipeline
frr
frr
strongswan
strongswan
lldpad
lldpad
trapline
trapline
dhcp server
dhcp server
dns server
dns server
web server
web server
whatever...
whatever...
user vm
user vm
vpc manager runtime
vpc manager ru...
link manager runtime
link manager ru...
Text is not SVG - cannot display
\ 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)" \