From 41c0ab22efa1bc48bb57c8b4935d487cbae07ecd Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Mon, 26 Jan 2026 21:24:35 +0000 Subject: [PATCH 01/11] userspace changes --- asic/src/tofino_stub/mod.rs | 1 + dpd-client/Cargo.toml | 1 + dpd-client/tests/integration_tests/common.rs | 3 + dpd-client/tests/integration_tests/mod.rs | 1 + .../tests/integration_tests/table_tests.rs | 11 +- dpd/Cargo.toml | 1 + dpd/src/api_server.rs | 281 +++++++++++------- dpd/src/link.rs | 111 ++++--- dpd/src/macaddrs.rs | 8 +- dpd/src/main.rs | 3 + dpd/src/table/mod.rs | 55 ++++ 11 files changed, 331 insertions(+), 145 deletions(-) diff --git a/asic/src/tofino_stub/mod.rs b/asic/src/tofino_stub/mod.rs index fed8ccdc..761a5a42 100644 --- a/asic/src/tofino_stub/mod.rs +++ b/asic/src/tofino_stub/mod.rs @@ -22,6 +22,7 @@ pub use crate::faux_fsm::FsmState; pub use crate::faux_fsm::FsmType; pub use crate::faux_fsm::PortFsmState; +#[cfg(feature = "multicast")] pub mod mcast; pub mod ports; pub mod table; diff --git a/dpd-client/Cargo.toml b/dpd-client/Cargo.toml index 9add026b..f7246eb5 100644 --- a/dpd-client/Cargo.toml +++ b/dpd-client/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" description = "Client library for the Dendrite data plane daemon" [features] +multicast = [] chaos = ["asic/chaos"] tofino_asic = ["asic/tofino_asic"] diff --git a/dpd-client/tests/integration_tests/common.rs b/dpd-client/tests/integration_tests/common.rs index 8d2c36e3..b7848a6d 100644 --- a/dpd-client/tests/integration_tests/common.rs +++ b/dpd-client/tests/integration_tests/common.rs @@ -566,6 +566,7 @@ impl Switch { /// Return the port label for the given physical port, useful for /// counter information. + #[cfg(feature = "multicast")] pub fn port_label(&self, phys_port: PhysPort) -> Option { let idx: usize = phys_port.into(); if phys_port == NO_PORT { @@ -920,11 +921,13 @@ pub fn gen_udp_routed_pair( #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum OxideGeneveOption { External, + #[allow(unused)] Multicast(u8), Mss(u32), } /// Build a Geneve packet with a possible multicast tag. +#[cfg(feature = "multicast")] pub fn gen_geneve_packet_with_mcast_tag( src: Endpoint, dst: Endpoint, diff --git a/dpd-client/tests/integration_tests/mod.rs b/dpd-client/tests/integration_tests/mod.rs index 7f1522a8..29c5f2e6 100644 --- a/dpd-client/tests/integration_tests/mod.rs +++ b/dpd-client/tests/integration_tests/mod.rs @@ -10,6 +10,7 @@ mod counters; mod geneve; mod icmp_ipv4; mod loopback; +#[cfg(feature = "multicast")] mod mcast; mod nat; mod port_api; diff --git a/dpd-client/tests/integration_tests/table_tests.rs b/dpd-client/tests/integration_tests/table_tests.rs index be4f7219..8c574505 100644 --- a/dpd-client/tests/integration_tests/table_tests.rs +++ b/dpd-client/tests/integration_tests/table_tests.rs @@ -4,7 +4,6 @@ // // Copyright 2026 Oxide Computer Company -use std::net::IpAddr; use std::net::Ipv4Addr; use std::net::Ipv6Addr; @@ -50,7 +49,9 @@ const IPV4_ARP_SIZE: usize = 512; // arp cache const IPV6_NEIGHBOR_SIZE: usize = 512; // ipv6 neighbor cache /// The size of the multicast table related to replication on /// admin-scoped (internal) multicast groups. +#[cfg(feature = "multicast")] const MULTICAST_TABLE_SIZE: usize = 1024; +#[cfg(feature = "multicast")] const MCAST_TAG: &str = "mcast_table_test"; // multicast group tag // The result of a table insert or delete API operation. @@ -75,6 +76,7 @@ fn gen_ipv6_cidr(idx: usize) -> Ipv6Net { } // Generates valid IPv6 multicast addresses that are admin-scoped. +#[cfg(feature = "multicast")] fn gen_ipv6_multicast_addr(idx: usize) -> Ipv6Addr { // Use admin-scoped multicast addresses (ff04::/16, ff05::/16, ff08::/16) // This ensures they will be created as internal groups @@ -451,9 +453,9 @@ impl TableTest for RouteV6 { async fn test_routev6_full() -> TestResult { test_table_capacity::(IPV6_LPM_SIZE).await } - +#[cfg(feature = "multicast")] struct MulticastReplicationTableTest {} - +#[cfg(feature = "multicast")] impl TableTest for MulticastReplicationTableTest { @@ -488,7 +490,7 @@ impl TableTest } async fn delete_entry(switch: &Switch, idx: usize) -> OpResult<()> { - let ip = IpAddr::V6(gen_ipv6_multicast_addr(idx)); + let ip = std::net::IpAddr::V6(gen_ipv6_multicast_addr(idx)); switch.client.multicast_group_delete(&ip).await } @@ -504,6 +506,7 @@ impl TableTest } } +#[cfg(feature = "multicast")] #[tokio::test] #[ignore] async fn test_multicast_replication_table_full() -> TestResult { diff --git a/dpd/Cargo.toml b/dpd/Cargo.toml index eb9ee426..74c5430e 100644 --- a/dpd/Cargo.toml +++ b/dpd/Cargo.toml @@ -5,6 +5,7 @@ authors = ["nils "] edition = "2024" [features] +multicast = [] tofino_asic = ["asic/tofino_asic"] tofino_stub = ["asic/tofino_stub"] softnpu = ["asic/softnpu"] diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index 4917f4d1..d31d7b2b 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -17,13 +17,7 @@ use dpd_types::fault::Fault; use dpd_types::link::LinkFsmCounters; use dpd_types::link::LinkId; use dpd_types::link::LinkUpCounter; -use dpd_types::mcast::MulticastGroupCreateExternalEntry; -use dpd_types::mcast::MulticastGroupCreateUnderlayEntry; -use dpd_types::mcast::MulticastGroupExternalResponse; -use dpd_types::mcast::MulticastGroupResponse; -use dpd_types::mcast::MulticastGroupUnderlayResponse; -use dpd_types::mcast::MulticastGroupUpdateExternalEntry; -use dpd_types::mcast::MulticastGroupUpdateUnderlayEntry; +use dpd_types::mcast::*; use dpd_types::oxstats::OximeterMetadata; use dpd_types::port_map::BackplaneLink; use dpd_types::route::Ipv6Route; @@ -67,6 +61,7 @@ use common::ports::TxEqSwHw; use crate::attached_subnet; use crate::counters; +#[cfg(feature = "multicast")] use crate::mcast; use crate::oxstats; use crate::rpw::Task; @@ -1699,6 +1694,7 @@ impl DpdApi for DpdApiImpl { error!(switch.log, "failed to reset ipv6 nat table: {:?}", e); err = Some(e); } + #[cfg(feature = "multicast")] if let Err(e) = mcast::reset(switch) { error!(switch.log, "failed to reset multicast state: {:?}", e); err = Some(e); @@ -1956,107 +1952,160 @@ impl DpdApi for DpdApiImpl { .map_err(HttpError::from) } + #[allow(unused_variables)] async fn multicast_group_create_external( rqctx: RequestContext>, group: TypedBody, ) -> Result, HttpError> { - let switch: &Switch = rqctx.context(); - let entry = group.into_inner(); + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + let entry = group.into_inner(); - mcast::add_group_external(switch, entry) - .map(HttpResponseCreated) - .map_err(HttpError::from) + mcast::add_group_external(switch, entry) + .map(HttpResponseCreated) + .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_group_create_underlay( rqctx: RequestContext>, group: TypedBody, ) -> Result, HttpError> { - let switch: &Switch = rqctx.context(); - let entry = group.into_inner(); + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + let entry = group.into_inner(); - mcast::add_group_internal(switch, entry) - .map(HttpResponseCreated) - .map_err(HttpError::from) + mcast::add_group_internal(switch, entry) + .map(HttpResponseCreated) + .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_group_delete( rqctx: RequestContext>, path: Path, ) -> Result { - let switch: &Switch = rqctx.context(); - let ip = path.into_inner().group_ip; + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + let ip = path.into_inner().group_ip; - mcast::del_group(switch, ip) - .map(|_| HttpResponseDeleted()) - .map_err(HttpError::from) + mcast::del_group(switch, ip) + .map(|_| HttpResponseDeleted()) + .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_reset( rqctx: RequestContext>, ) -> Result { - let switch: &Switch = rqctx.context(); + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); - mcast::reset(switch) - .map(|_| HttpResponseDeleted()) - .map_err(HttpError::from) + mcast::reset(switch) + .map(|_| HttpResponseDeleted()) + .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_group_get( rqctx: RequestContext>, path: Path, ) -> Result, HttpError> { - let switch: &Switch = rqctx.context(); - let ip = path.into_inner().group_ip; + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + let ip = path.into_inner().group_ip; - // Get the multicast group - mcast::get_group(switch, ip) - .map(HttpResponseOk) - .map_err(HttpError::from) + // Get the multicast group + mcast::get_group(switch, ip) + .map(HttpResponseOk) + .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_group_update_underlay( rqctx: RequestContext>, path: Path, group: TypedBody, ) -> Result, HttpError> { - let switch: &Switch = rqctx.context(); - let admin_scoped = path.into_inner().group_ip; + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + let admin_scoped = path.into_inner().group_ip; - mcast::modify_group_internal(switch, admin_scoped, group.into_inner()) + mcast::modify_group_internal( + switch, + admin_scoped, + group.into_inner(), + ) .map(HttpResponseOk) .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_group_get_underlay( rqctx: RequestContext>, path: Path, ) -> Result, HttpError> { - let switch: &Switch = rqctx.context(); - let admin_scoped = path.into_inner().group_ip; + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + let admin_scoped = path.into_inner().group_ip; - mcast::get_group_internal(switch, admin_scoped) - .map(HttpResponseOk) - .map_err(HttpError::from) + mcast::get_group_internal(switch, admin_scoped) + .map(HttpResponseOk) + .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_group_update_external( rqctx: RequestContext>, path: Path, group: TypedBody, ) -> Result, HttpError> { - let switch: &Switch = rqctx.context(); - let entry = group.into_inner(); - let ip = path.into_inner().group_ip; + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + let entry = group.into_inner(); + let ip = path.into_inner().group_ip; - mcast::modify_group_external(switch, ip, entry) - .map(HttpResponseCreated) - .map_err(HttpError::from) + mcast::modify_group_external(switch, ip, entry) + .map(HttpResponseCreated) + .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_groups_list( rqctx: RequestContext>, query_params: Query< @@ -2064,34 +2113,42 @@ impl DpdApi for DpdApiImpl { >, ) -> Result>, HttpError> { - let switch: &Switch = rqctx.context(); - - let pag_params = query_params.into_inner(); - let Ok(limit) = usize::try_from(rqctx.page_limit(&pag_params)?.get()) - else { - return Err( - DpdError::Invalid("Invalid page limit".to_string()).into() - ); - }; + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + + let pag_params = query_params.into_inner(); + let Ok(limit) = + usize::try_from(rqctx.page_limit(&pag_params)?.get()) + else { + return Err(DpdError::Invalid( + "Invalid page limit".to_string(), + ) + .into()); + }; - let last_addr = match &pag_params.page { - WhichPage::First(..) => None, - WhichPage::Next(MulticastGroupIpParam { group_ip }) => { - Some(*group_ip) - } - }; + let last_addr = match &pag_params.page { + WhichPage::First(..) => None, + WhichPage::Next(MulticastGroupIpParam { group_ip }) => { + Some(*group_ip) + } + }; - let entries = mcast::get_range(switch, last_addr, limit, None); + let entries = mcast::get_range(switch, last_addr, limit, None); - Ok(HttpResponseOk(ResultsPage::new( - entries, - &EmptyScanParams {}, - |e: &MulticastGroupResponse, _| MulticastGroupIpParam { - group_ip: e.ip(), - }, - )?)) + Ok(HttpResponseOk(ResultsPage::new( + entries, + &EmptyScanParams {}, + |e: &MulticastGroupResponse, _| MulticastGroupIpParam { + group_ip: e.ip(), + }, + )?)) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_groups_list_by_tag( rqctx: RequestContext>, path: Path, @@ -2100,54 +2157,74 @@ impl DpdApi for DpdApiImpl { >, ) -> Result>, HttpError> { - let switch: &Switch = rqctx.context(); - let tag = path.into_inner().tag; - - let pag_params = query_params.into_inner(); - let Ok(limit) = usize::try_from(rqctx.page_limit(&pag_params)?.get()) - else { - return Err( - DpdError::Invalid("Invalid page limit".to_string()).into() - ); - }; + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + let tag = path.into_inner().tag; + + let pag_params = query_params.into_inner(); + let Ok(limit) = + usize::try_from(rqctx.page_limit(&pag_params)?.get()) + else { + return Err(DpdError::Invalid( + "Invalid page limit".to_string(), + ) + .into()); + }; - let last_addr = match &pag_params.page { - WhichPage::First(..) => None, - WhichPage::Next(MulticastGroupIpParam { group_ip }) => { - Some(*group_ip) - } - }; + let last_addr = match &pag_params.page { + WhichPage::First(..) => None, + WhichPage::Next(MulticastGroupIpParam { group_ip }) => { + Some(*group_ip) + } + }; - let entries = mcast::get_range(switch, last_addr, limit, Some(&tag)); - Ok(HttpResponseOk(ResultsPage::new( - entries, - &EmptyScanParams {}, - |e: &MulticastGroupResponse, _| MulticastGroupIpParam { - group_ip: e.ip(), - }, - )?)) + let entries = + mcast::get_range(switch, last_addr, limit, Some(&tag)); + Ok(HttpResponseOk(ResultsPage::new( + entries, + &EmptyScanParams {}, + |e: &MulticastGroupResponse, _| MulticastGroupIpParam { + group_ip: e.ip(), + }, + )?)) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_reset_by_tag( rqctx: RequestContext>, path: Path, ) -> Result { - let switch: &Switch = rqctx.context(); - let tag = path.into_inner().tag; + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); + let tag = path.into_inner().tag; - mcast::reset_tag(switch, &tag) - .map(|_| HttpResponseDeleted()) - .map_err(HttpError::from) + mcast::reset_tag(switch, &tag) + .map(|_| HttpResponseDeleted()) + .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } + #[allow(unused_variables)] async fn multicast_reset_untagged( rqctx: RequestContext>, ) -> Result { - let switch: &Switch = rqctx.context(); + #[cfg(feature = "multicast")] + { + let switch: &Switch = rqctx.context(); - mcast::reset_untagged(switch) - .map(|_| HttpResponseDeleted()) - .map_err(HttpError::from) + mcast::reset_untagged(switch) + .map(|_| HttpResponseDeleted()) + .map_err(HttpError::from) + } + #[cfg(not(feature = "multicast"))] + Err(client_error("multicast feature disabled")) } #[cfg(feature = "tofino_asic")] diff --git a/dpd/src/link.rs b/dpd/src/link.rs index ac98c8e3..2ffe0213 100644 --- a/dpd/src/link.rs +++ b/dpd/src/link.rs @@ -14,6 +14,7 @@ use crate::fault::LinkUpTracker; use crate::ports::AdminEvent; use crate::ports::Event; use crate::table::MacOps; +#[cfg(feature = "multicast")] use crate::table::mcast; use crate::table::port_ip; use crate::table::port_mac; @@ -1576,28 +1577,38 @@ fn unplumb_link( } if link.plumbed.mac.is_some() { + let mut err = None; if let Err(e) = MacOps::::mac_clear( switch, link.asic_port_id, - ) - .and_then(|_| { - MacOps::::mac_clear( - switch, - link.asic_port_id, - ) - }) - .and_then(|_| { - // We tie this in here as ports and macs are 1:1 - mcast::mcast_egress::del_port_mapping_entry( - switch, - link.asic_port_id, - ) - }) { + ) { + err = Some(e); + } + #[cfg(feature = "multicast")] + { + if let Err(e) = + MacOps::::mac_clear( + switch, + link.asic_port_id, + ) + { + err = Some(e); + } else { + // We tie this in here as ports and macs are 1:1 + if let Err(e) = mcast::mcast_egress::del_port_mapping_entry( + switch, + link.asic_port_id, + ) { + err = Some(e); + } + } + } + + if let Some(e) = err { error!(log, "Failed to clear mac address and port mapping: {e:?}"); return Err(e); - } else { - link.plumbed.mac = None; } + link.plumbed.mac = None; } if link.plumbed.link_created { @@ -1859,18 +1870,31 @@ async fn reconcile_link( link.config.mac, link.plumbed.mac.unwrap() ); + let mut err = None; if let Err(e) = MacOps::::mac_clear(switch, asic_id) - .and_then(|_| { - MacOps::::mac_clear( - switch, asic_id, - ) - }) - .and_then(|_| { - // We tie this in here as ports and macs are 1:1 - mcast::mcast_egress::del_port_mapping_entry(switch, asic_id) - }) { + err = Some(e); + } + #[cfg(feature = "multicast")] + { + if let Err(e) = + MacOps::::mac_clear( + switch, asic_id, + ) + { + err = Some(e); + } else { + // We tie this in here as ports and macs are 1:1 + if let Err(e) = + mcast::mcast_egress::del_port_mapping_entry(switch, asic_id) + { + err = Some(e); + } + } + } + + if let Some(e) = err { record_plumb_failure( switch, &mut link, @@ -1886,22 +1910,35 @@ async fn reconcile_link( if link.plumbed.mac.is_none() { debug!(log, "Programming mac {}", link.config.mac); + let mut err = None; if let Err(e) = MacOps::::mac_set( switch, asic_id, link.config.mac, - ) - .and_then(|_| { - MacOps::::mac_set( - switch, - asic_id, - link.config.mac, - ) - }) - .and_then(|_| { - // We tie this in here as ports and macs are 1:1 - mcast::mcast_egress::add_port_mapping_entry(switch, asic_id) - }) { + ) { + err = Some(e); + } + + #[cfg(feature = "multicast")] + { + if let Err(e) = + MacOps::::mac_set( + switch, + asic_id, + link.config.mac, + ) + { + err = Some(e); + } else { + // We tie this in here as ports and macs are 1:1 + if let Err(e) = + mcast::mcast_egress::add_port_mapping_entry(switch, asic_id) + { + err = Some(e); + } + } + } + if let Some(e) = err { record_plumb_failure( switch, &mut link, diff --git a/dpd/src/macaddrs.rs b/dpd/src/macaddrs.rs index 951f19b6..0be2207f 100644 --- a/dpd/src/macaddrs.rs +++ b/dpd/src/macaddrs.rs @@ -22,6 +22,7 @@ use common::ports::PortId; cfg_if::cfg_if! { if #[cfg(feature = "tofino_asic")] { use std::convert::TryFrom; + #[cfg(feature = "multicast")] use crate::table::mcast; use crate::table::port_mac; use crate::table::MacOps; @@ -428,8 +429,11 @@ impl Switch { // Reset ingress and egress MAC tables and Port ID table(s). MacOps::::reset(self)?; - MacOps::::reset(self)?; - mcast::mcast_egress::reset_bitmap_table(self)?; + #[cfg(feature = "multicast")] + { + MacOps::::reset(self)?; + mcast::mcast_egress::reset_bitmap_table(self)?; + } // Create the link on the CPU port. let link_id = self.create_link(port_id, ¶ms)?; diff --git a/dpd/src/main.rs b/dpd/src/main.rs index 635d8e42..b8a440d0 100644 --- a/dpd/src/main.rs +++ b/dpd/src/main.rs @@ -62,6 +62,7 @@ mod freemap; mod link; mod loopback; mod macaddrs; +#[cfg(feature = "multicast")] mod mcast; mod nat; mod oxstats; @@ -194,6 +195,7 @@ pub struct Switch { pub oximeter_producer: Mutex>, pub oximeter_meta: Mutex>, pub reconciler: link::LinkReconciler, + #[cfg(feature = "multicast")] pub mcast: Mutex, mac_mgmt: Mutex, @@ -303,6 +305,7 @@ impl Switch { oximeter_producer: Mutex::new(None), oximeter_meta: Mutex::new(None), reconciler: link::LinkReconciler::default(), + #[cfg(feature = "multicast")] mcast: Mutex::new(mcast::MulticastGroupData::new()), mac_mgmt, port_history: Mutex::new(BTreeMap::new()), diff --git a/dpd/src/table/mod.rs b/dpd/src/table/mod.rs index 09869ebd..91a47b4d 100644 --- a/dpd/src/table/mod.rs +++ b/dpd/src/table/mod.rs @@ -20,6 +20,7 @@ use dpd_types::views; pub mod arp_ipv4; pub mod attached_subnet_v4; pub mod attached_subnet_v6; +#[cfg(feature = "multicast")] pub mod mcast; pub mod nat; pub mod neighbor_ipv6; @@ -29,6 +30,7 @@ pub mod port_nat; pub mod route_ipv4; pub mod route_ipv6; +#[cfg(feature = "multicast")] const NAME_TO_TYPE: [(&str, TableType); 24] = [ (route_ipv4::INDEX_TABLE_NAME, TableType::RouteIdxIpv4), (route_ipv4::FORWARD_TABLE_NAME, TableType::RouteFwdIpv4), @@ -67,6 +69,29 @@ const NAME_TO_TYPE: [(&str, TableType); 24] = [ TableType::McastEgressPortMapping, ), ]; +#[cfg(not(feature = "multicast"))] +const NAME_TO_TYPE: [(&str, TableType); 14] = [ + (route_ipv4::INDEX_TABLE_NAME, TableType::RouteIdxIpv4), + (route_ipv4::FORWARD_TABLE_NAME, TableType::RouteFwdIpv4), + (route_ipv6::INDEX_TABLE_NAME, TableType::RouteIdxIpv6), + (route_ipv6::FORWARD_TABLE_NAME, TableType::RouteFwdIpv6), + (arp_ipv4::TABLE_NAME, TableType::ArpIpv4), + (neighbor_ipv6::TABLE_NAME, TableType::NeighborIpv6), + (port_mac::TABLE_NAME, TableType::PortMac), + (port_ip::IPV4_TABLE_NAME, TableType::PortIpv4), + (port_ip::IPV6_TABLE_NAME, TableType::PortIpv6), + (nat::IPV4_TABLE_NAME, TableType::NatIngressIpv4), + (nat::IPV6_TABLE_NAME, TableType::NatIngressIpv6), + (port_nat::TABLE_NAME, TableType::NatOnly), + ( + attached_subnet_v4::EXT_SUBNET_IPV4_TABLE_NAME, + TableType::AttachedSubnetIpv4, + ), + ( + attached_subnet_v6::EXT_SUBNET_IPV6_TABLE_NAME, + TableType::AttachedSubnetIpv6, + ), +]; /// Basic statistics about p4 table usage #[derive(Clone, Debug, Default)] @@ -266,33 +291,43 @@ pub fn get_entries(switch: &Switch, name: String) -> DpdResult { MacOps::::table_dump(switch) } TableType::NatOnly => port_nat::table_dump(switch), + #[cfg(feature = "multicast")] TableType::McastIpv6 => { mcast::mcast_replication::ipv6_table_dump(switch) } + #[cfg(feature = "multicast")] TableType::McastIpv4SrcFilter => { mcast::mcast_src_filter::ipv4_table_dump(switch) } + #[cfg(feature = "multicast")] TableType::McastIpv6SrcFilter => { mcast::mcast_src_filter::ipv6_table_dump(switch) } + #[cfg(feature = "multicast")] TableType::NatIngressIpv4Mcast => { mcast::mcast_nat::ipv4_table_dump(switch) } + #[cfg(feature = "multicast")] TableType::NatIngressIpv6Mcast => { mcast::mcast_nat::ipv6_table_dump(switch) } + #[cfg(feature = "multicast")] TableType::RouteIpv4Mcast => { mcast::mcast_route::ipv4_table_dump(switch) } + #[cfg(feature = "multicast")] TableType::RouteIpv6Mcast => { mcast::mcast_route::ipv6_table_dump(switch) } + #[cfg(feature = "multicast")] TableType::PortMacMcast => { MacOps::::table_dump(switch) } + #[cfg(feature = "multicast")] TableType::McastEgressDecapPorts => { mcast::mcast_egress::bitmap_table_dump(switch) } + #[cfg(feature = "multicast")] TableType::McastEgressPortMapping => { mcast::mcast_egress::port_mapping_table_dump(switch) } @@ -341,33 +376,43 @@ pub fn get_counters( TableType::AttachedSubnetIpv6 => { attached_subnet_v6::counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::McastIpv6 => { mcast::mcast_replication::ipv6_counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::McastIpv4SrcFilter => { mcast::mcast_src_filter::ipv4_counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::McastIpv6SrcFilter => { mcast::mcast_src_filter::ipv6_counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::NatIngressIpv4Mcast => { mcast::mcast_nat::ipv4_counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::NatIngressIpv6Mcast => { mcast::mcast_nat::ipv6_counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::RouteIpv4Mcast => { mcast::mcast_route::ipv4_counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::RouteIpv6Mcast => { mcast::mcast_route::ipv6_counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::McastEgressDecapPorts => { mcast::mcast_egress::bitmap_counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::McastEgressPortMapping => { mcast::mcast_egress::port_mapping_counter_fetch(switch, force_sync) } + #[cfg(feature = "multicast")] TableType::PortMacMcast => { MacOps::::counter_fetch( switch, force_sync, @@ -382,7 +427,9 @@ pub enum TableType { RouteFwdIpv4, RouteIdxIpv6, RouteFwdIpv6, + #[cfg(feature = "multicast")] RouteIpv4Mcast, + #[cfg(feature = "multicast")] RouteIpv6Mcast, ArpIpv4, NeighborIpv6, @@ -394,13 +441,21 @@ pub enum TableType { NatOnly, AttachedSubnetIpv4, AttachedSubnetIpv6, + #[cfg(feature = "multicast")] McastIpv6, + #[cfg(feature = "multicast")] McastIpv4SrcFilter, + #[cfg(feature = "multicast")] McastIpv6SrcFilter, + #[cfg(feature = "multicast")] NatIngressIpv4Mcast, + #[cfg(feature = "multicast")] NatIngressIpv6Mcast, + #[cfg(feature = "multicast")] PortMacMcast, + #[cfg(feature = "multicast")] McastEgressDecapPorts, + #[cfg(feature = "multicast")] McastEgressPortMapping, } From ee8f110c458b04dead5a4a36cab11d812f34fe91 Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Mon, 26 Jan 2026 22:55:36 +0000 Subject: [PATCH 02/11] p4 --- .../tests/integration_tests/table_tests.rs | 8 +++ dpd/p4/sidecar.p4 | 49 ++++++++++++++++++- dpd/src/counters.rs | 38 +++++++++++--- xtask/src/codegen.rs | 5 ++ xtask/src/main.rs | 8 ++- 5 files changed, 97 insertions(+), 11 deletions(-) diff --git a/dpd-client/tests/integration_tests/table_tests.rs b/dpd-client/tests/integration_tests/table_tests.rs index 8c574505..2cb2cef6 100644 --- a/dpd-client/tests/integration_tests/table_tests.rs +++ b/dpd-client/tests/integration_tests/table_tests.rs @@ -39,8 +39,16 @@ use dpd_client::types; // investigating. If it only changes by an entry or two, it's fine to just // adjust the constant below to match the observed result. // +#[cfg(feature = "multicast")] const IPV4_LPM_SIZE: usize = 8124; // ipv4 forwarding table +#[cfg(not(feature = "multicast"))] +const IPV4_LPM_SIZE: usize = 8187; // ipv4 forwarding table + +#[cfg(feature = "multicast")] const IPV6_LPM_SIZE: usize = 1023; // ipv6 forwarding table +#[cfg(not(feature = "multicast"))] +const IPV6_LPM_SIZE: usize = 1023; // ipv6 forwarding table + const SWITCH_IPV4_ADDRS_SIZE: usize = 511; // ipv4 addrs assigned to our ports const SWITCH_IPV6_ADDRS_SIZE: usize = 511; // ipv6 addrs assigned to our ports const IPV4_NAT_TABLE_SIZE: usize = 1024; // nat routing table diff --git a/dpd/p4/sidecar.p4 b/dpd/p4/sidecar.p4 index 4981c738..ceab1690 100644 --- a/dpd/p4/sidecar.p4 +++ b/dpd/p4/sidecar.p4 @@ -105,8 +105,10 @@ control Filter( ) { DirectCounter>(CounterType_t.PACKETS_AND_BYTES) ipv4_ctr; DirectCounter>(CounterType_t.PACKETS_AND_BYTES) ipv6_ctr; +#ifdef MULTICAST Counter, PortId_t>(512, CounterType_t.PACKETS) drop_mcast_ctr; bit<16> mcast_scope; +#endif /* MULITCAST */ action dropv4() { meta.drop_reason = DROP_IPV4_SWITCH_ADDR_MISS; @@ -164,6 +166,7 @@ control Filter( if (hdr.arp.isValid()) { switch_ipv4_addr.apply(); } else if (hdr.ipv4.isValid()) { +#ifdef MULTICAST if (meta.is_mcast) { // IPv4 Multicast Address Validation (RFC 1112, RFC 7042) // @@ -194,7 +197,11 @@ control Filter( } else { switch_ipv4_addr.apply(); } +#else /* MULTICAST */ + switch_ipv4_addr.apply(); +#endif /* MULTICAST */ } else if (hdr.ipv6.isValid()) { +#ifdef MULTICAST if (meta.is_mcast) { // Validate the IPv6 multicast MAC address format (RFC 2464, // RFC 7042). @@ -227,6 +234,7 @@ control Filter( return; } } +#endif /* MULTICAST */ if (!meta.is_mcast || meta.is_link_local_mcastv6 && !meta.encap_needed) { switch_ipv6_addr.apply(); @@ -474,8 +482,10 @@ control NatIngress ( DirectCounter>(CounterType_t.PACKETS_AND_BYTES) ipv4_ingress_ctr; DirectCounter>(CounterType_t.PACKETS_AND_BYTES) ipv6_ingress_ctr; DirectCounter>(CounterType_t.PACKETS_AND_BYTES) nat_only_ctr; +#ifdef MULTICAST DirectCounter>(CounterType_t.PACKETS_AND_BYTES) mcast_ipv4_ingress_ctr; DirectCounter>(CounterType_t.PACKETS_AND_BYTES) mcast_ipv6_ingress_ctr; +#endif /* MULTICAST */ action add_encap_headers(bit<16> udp_len) { // 8 bytes with a 4 byte option @@ -602,6 +612,7 @@ control NatIngress ( counters = nat_only_ctr; } +#ifdef MULTICAST action mcast_forward_ipv4_to(ipv6_addr_t target, mac_addr_t inner_mac, geneve_vni_t vni) { meta.nat_ingress_hit = true; meta.nat_ingress_tgt = target; @@ -636,6 +647,7 @@ control NatIngress ( const size = IPV6_MULTICAST_TABLE_SIZE; counters = mcast_ipv6_ingress_ctr; } +#endif /* MULTICAST */ action set_icmp_dst_port() { meta.l4_dst_port = hdr.icmp.data[31:16]; @@ -707,6 +719,7 @@ control NatIngress ( // Note: This whole conditional could be simpler as a set of */ // `const entries`, but apply (on tables) cannot be called from actions +#ifdef MULTICAST if (hdr.ipv4.isValid()) { if (meta.is_mcast) { ingress_ipv4_mcast.apply(); @@ -722,6 +735,15 @@ control NatIngress ( ingress_ipv6.apply(); } } +#else /* MULTICAST */ + if (hdr.ipv4.isValid()) { + if (!meta.encap_needed) { + ingress_ipv4.apply(); + } + } else if (hdr.ipv6.isValid()) { + ingress_ipv6.apply(); + } +#endif /* MULTICAST */ if (ingress_hit.apply().hit) { if (hdr.ipv4.isValid()) { @@ -1259,6 +1281,7 @@ control Router4 ( } } +#ifdef MULTICAST control MulticastRouter4( inout sidecar_headers_t hdr, inout sidecar_ingress_meta_t meta, @@ -1336,6 +1359,7 @@ control MulticastRouter4( } } } +#endif /* MULTICAST */ control Router6 ( inout sidecar_headers_t hdr, @@ -1398,6 +1422,7 @@ control Router6 ( } } +#ifdef MULTICAST control MulticastRouter6 ( inout sidecar_headers_t hdr, inout sidecar_ingress_meta_t meta, @@ -1472,6 +1497,7 @@ control MulticastRouter6 ( } } } +#endif /* MULTICAST */ control L3Router( inout sidecar_headers_t hdr, @@ -1480,6 +1506,7 @@ control L3Router( inout ingress_intrinsic_metadata_for_tm_t ig_tm_md ) { apply { +#ifdef MULTICAST if (hdr.ipv4.isValid()) { if (meta.is_mcast && !meta.is_link_local_mcastv6) { MulticastRouter4.apply(hdr, meta, ig_intr_md, ig_tm_md); @@ -1493,6 +1520,13 @@ control L3Router( Router6.apply(hdr, meta, ig_intr_md, ig_tm_md); } } +#else /* MULTICAST */ + if (hdr.ipv4.isValid()) { + Router4.apply(hdr, meta, ig_intr_md, ig_tm_md); + } else if (hdr.ipv6.isValid()) { + Router6.apply(hdr, meta, ig_intr_md, ig_tm_md); + } +#endif /* MULTICAST */ if (meta.resolve_nexthop) { if (meta.nexthop_ipv4 != 0) { Arp.apply(hdr, meta, ig_intr_md, ig_tm_md); @@ -1527,6 +1561,7 @@ control MacRewrite( } } +#ifdef MULTICAST /* This control is used to rewrite the source and destination MAC addresses * for multicast packets. The destination MAC address is derived from the * destination IP address, and the source MAC address is set based on the @@ -2019,6 +2054,7 @@ control MulticastEgress ( } } } +#endif /* MULTICAST */ control Ingress( inout sidecar_headers_t hdr, @@ -2034,7 +2070,9 @@ control Ingress( NatIngress() nat_ingress; NatEgress() nat_egress; L3Router() l3_router; +#ifdef MULTICAST MulticastIngress() mcast_ingress; +#endif /* MULTICAST */ MacRewrite() mac_rewrite; Counter, PortId_t>(512, CounterType_t.PACKETS_AND_BYTES) ingress_ctr; @@ -2071,8 +2109,10 @@ control Ingress( if (!meta.dropped) { if (!meta.is_mcast || meta.is_link_local_mcastv6) { services.apply(hdr, meta, ig_intr_md, ig_tm_md); +#ifdef MULTICAST } else if (meta.is_mcast && !meta.is_link_local_mcastv6) { mcast_ingress.apply(hdr, meta, ig_intr_md, ig_tm_md); +#endif /* MULTICAST */ } } @@ -2226,6 +2266,7 @@ control Egress( inout egress_intrinsic_metadata_for_deparser_t eg_dprsr_md, inout egress_intrinsic_metadata_for_output_port_t eg_oport_md ) { +#ifdef MULTICAST MulticastMacRewrite() mac_rewrite; MulticastEgress() mcast_egress; @@ -2287,6 +2328,9 @@ control Egress( unicast_ctr.count(eg_intr_md.egress_port); } } +#else /* MULTICAST */ + apply { } +#endif /* MULTICAST */ } control EgressDeparser( @@ -2295,6 +2339,7 @@ control EgressDeparser( in sidecar_egress_meta_t meta, in egress_intrinsic_metadata_for_deparser_t eg_dprsr_md ) { +#ifdef MULTICAST Checksum() ipv4_checksum; apply { @@ -2314,9 +2359,11 @@ control EgressDeparser( hdr.inner_ipv4.dst_addr }); } - pkt.emit(hdr); } +#else + apply { pkt.emit(hdr); } +#endif } Pipeline( diff --git a/dpd/src/counters.rs b/dpd/src/counters.rs index dfad527f..dca9791f 100644 --- a/dpd/src/counters.rs +++ b/dpd/src/counters.rs @@ -55,13 +55,21 @@ enum CounterId { Packet, DropPort, DropReason, + #[cfg(feature = "multicast")] EgressDropPort, + #[cfg(feature = "multicast")] EgressDropReason, + #[cfg(feature = "multicast")] Unicast, + #[cfg(feature = "multicast")] Multicast, + #[cfg(feature = "multicast")] MulticastExt, + #[cfg(feature = "multicast")] MulticastLL, + #[cfg(feature = "multicast")] MulticastUL, + #[cfg(feature = "multicast")] MulticastDrop, } @@ -85,7 +93,12 @@ struct CounterDescription { p4_name: &'static str, } -const COUNTERS: [CounterDescription; 14] = [ +#[cfg(feature = "multicast")] +const COUNTERS_COUNT: usize = 14; +#[cfg(not(feature = "multicast"))] +const COUNTERS_COUNT: usize = 6; + +const COUNTERS: [CounterDescription; COUNTERS_COUNT] = [ CounterDescription { id: CounterId::Service, client_name: "Service", @@ -116,41 +129,49 @@ const COUNTERS: [CounterDescription; 14] = [ client_name: "Ingress_Drop_Reason", p4_name: "pipe.Ingress.drop_reason_ctr", }, + #[cfg(feature = "multicast")] CounterDescription { id: CounterId::EgressDropPort, client_name: "Egress_Drop_Port", p4_name: "pipe.Egress.drop_port_ctr", }, + #[cfg(feature = "multicast")] CounterDescription { id: CounterId::EgressDropReason, client_name: "Egress_Drop_Reason", p4_name: "pipe.Egress.drop_reason_ctr", }, + #[cfg(feature = "multicast")] CounterDescription { id: CounterId::Unicast, client_name: "Unicast", p4_name: "pipe.Egress.unicast_ctr", }, + #[cfg(feature = "multicast")] CounterDescription { id: CounterId::Multicast, client_name: "Multicast", p4_name: "pipe.Egress.mcast_ctr", }, + #[cfg(feature = "multicast")] CounterDescription { id: CounterId::MulticastExt, client_name: "Multicast_External", p4_name: "pipe.Egress.external_mcast_ctr", }, + #[cfg(feature = "multicast")] CounterDescription { id: CounterId::MulticastLL, client_name: "Multicast_Link_Local", p4_name: "pipe.Egress.link_local_mcast_ctr", }, + #[cfg(feature = "multicast")] CounterDescription { id: CounterId::MulticastUL, client_name: "Multicast_Underlay", p4_name: "pipe.Egress.underlay_mcast_ctr", }, + #[cfg(feature = "multicast")] CounterDescription { id: CounterId::MulticastDrop, client_name: "Multicast_Drop", @@ -414,19 +435,20 @@ pub async fn get_values( let key = match counter_id { CounterId::Packet => packet_label(idx.idx), CounterId::Service => service_label(idx.idx as u8), - CounterId::Ingress - | CounterId::Egress - | CounterId::EgressDropPort - | CounterId::DropPort + CounterId::Ingress | CounterId::Egress | CounterId::DropPort => { + port_label(switch, idx.idx).await + } + CounterId::DropReason => reason_label(idx.idx as u8)?, + #[cfg(feature = "multicast")] + CounterId::EgressDropPort | CounterId::Unicast | CounterId::Multicast | CounterId::MulticastExt | CounterId::MulticastLL | CounterId::MulticastUL | CounterId::MulticastDrop => port_label(switch, idx.idx).await, - CounterId::DropReason | CounterId::EgressDropReason => { - reason_label(idx.idx as u8)? - } + #[cfg(feature = "multicast")] + CounterId::EgressDropReason => reason_label(idx.idx as u8)?, }; if let Some(key) = key { diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index a2f8f3ca..797fb42b 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs @@ -126,6 +126,7 @@ pub fn build( app_name: String, sde_location: String, stages: Option, + multicast: bool, ) -> Result<()> { let root = super::project_root()?; let src_dir = match app_name.as_str() { @@ -168,6 +169,10 @@ pub fn build( if let Some(s) = stages { args.push(format!("--num-stages-override={s}")); } + if multicast { + args.push("-D".to_string()); + args.push("MULTICAST".to_string()); + } args.push(app_path); println!("op: {args:?}"); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index ed36dec4..f7cdda5c 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -79,6 +79,10 @@ enum XtaskCommands { /// pipeline stages to build for #[clap(long)] stages: Option, + + /// Include support for multicast + #[clap(long)] + multicast: bool, }, /// build an installable dataplane controller package Dist { @@ -247,8 +251,8 @@ async fn main() { if let Err(e) = match task.subcommand { XtaskCommands::Openapi(external) => external .exec_bin("dendrite-dropshot-apis", "dendrite-dropshot-apis"), - XtaskCommands::Codegen { name, sde, stages } => { - codegen::build(name, sde, stages) + XtaskCommands::Codegen { name, sde, stages, multicast } => { + codegen::build(name, sde, stages, multicast) } XtaskCommands::Dist { features, names, release, format } => { plat::dist(features, names, release, format).await From 262b326148dc50862414773110480ea8670508a4 Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Mon, 26 Jan 2026 20:52:25 -0500 Subject: [PATCH 03/11] cleanup --- aal/Cargo.toml | 3 ++- aal/src/lib.rs | 9 +++++++++ asic/Cargo.toml | 1 + asic/src/chaos/mod.rs | 29 +++++++++++++++++++++++++++++ asic/src/softnpu/mod.rs | 8 ++++++++ asic/src/tofino_asic/mod.rs | 24 +++++++++++++++++++----- asic/src/tofino_stub/mod.rs | 11 +++++++++++ dpd/src/counters.rs | 2 +- dpd/src/macaddrs.rs | 2 +- 9 files changed, 81 insertions(+), 8 deletions(-) diff --git a/aal/Cargo.toml b/aal/Cargo.toml index 4237c82f..c2efa3d9 100644 --- a/aal/Cargo.toml +++ b/aal/Cargo.toml @@ -3,7 +3,8 @@ name = "aal" version = "0.1.0" edition = "2024" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +multicast = [] [dependencies] common.workspace = true diff --git a/aal/src/lib.rs b/aal/src/lib.rs index f61a9b2b..fe3288f9 100644 --- a/aal/src/lib.rs +++ b/aal/src/lib.rs @@ -197,13 +197,16 @@ pub trait AsicOps { ) -> AsicResult>; /// Return a vector containing all of the defined multicast groups. + #[cfg(feature = "multicast")] fn mc_domains(&self) -> Vec; /// For a given multicast group, return the number of ports assigned to it. + #[cfg(feature = "multicast")] fn mc_port_count(&self, group_id: u16) -> AsicResult; /// Add a port to a multicast group. The port is identified using its ASIC /// identifier. + #[cfg(feature = "multicast")] fn mc_port_add( &self, group_id: u16, @@ -214,23 +217,29 @@ pub trait AsicOps { /// Remove a port from a multicast group. The port is identified using its ASIC /// identifier. + #[cfg(feature = "multicast")] fn mc_port_remove(&self, group_id: u16, port: AsicId) -> AsicResult<()>; /// Create a new, unpopulated multicast group. + #[cfg(feature = "multicast")] fn mc_group_create(&self, group_id: u16) -> AsicResult<()>; /// Destroy a multicast group. + #[cfg(feature = "multicast")] fn mc_group_destroy(&self, group_id: u16) -> AsicResult<()>; /// Check if a multicast group exists. + #[cfg(feature = "multicast")] fn mc_group_exists(&self, group_id: u16) -> bool { self.mc_domains().contains(&group_id) } /// Get the total number of multicast groups. + #[cfg(feature = "multicast")] fn mc_groups_count(&self) -> AsicResult; /// Set the maximum number of multicast nodes. + #[cfg(feature = "multicast")] fn mc_set_max_nodes( &self, max_nodes: u32, diff --git a/asic/Cargo.toml b/asic/Cargo.toml index 736da0d2..8f0ec66d 100644 --- a/asic/Cargo.toml +++ b/asic/Cargo.toml @@ -13,6 +13,7 @@ tofino_asic = [ tofino_stub = [] softnpu = ["softnpu-lib", "dep:propolis"] chaos = [] +multicast = [] [lib] # The genpd.rs code generated by bindgen causes the doctest to fail diff --git a/asic/src/chaos/mod.rs b/asic/src/chaos/mod.rs index 5d98945d..f3bf123d 100644 --- a/asic/src/chaos/mod.rs +++ b/asic/src/chaos/mod.rs @@ -147,12 +147,19 @@ pub struct AsicConfig { pub port_delete: Chaos, pub register_port_update_handler: Chaos, pub connector_avail_channels: Chaos, + #[cfg(feature = "multicast")] pub mc_port_count: Chaos, + #[cfg(feature = "multicast")] pub mc_port_add: Chaos, + #[cfg(feature = "multicast")] pub mc_port_remove: Chaos, + #[cfg(feature = "multicast")] pub mc_group_create: Chaos, + #[cfg(feature = "multicast")] pub mc_group_destroy: Chaos, + #[cfg(feature = "multicast")] pub mc_groups_count: Chaos, + #[cfg(feature = "multicast")] pub mc_set_max_nodes: Chaos, pub get_sidecar_identifiers: Chaos, pub table_new: TableChaos, @@ -185,12 +192,19 @@ impl AsicConfig { port_delete: Chaos::new(v), register_port_update_handler: Chaos::new(v), connector_avail_channels: Chaos::new(v), + #[cfg(feature = "multicast")] mc_port_count: Chaos::new(v), + #[cfg(feature = "multicast")] mc_port_add: Chaos::new(v), + #[cfg(feature = "multicast")] mc_port_remove: Chaos::new(v), + #[cfg(feature = "multicast")] mc_group_create: Chaos::new(v), + #[cfg(feature = "multicast")] mc_group_destroy: Chaos::new(v), + #[cfg(feature = "multicast")] mc_groups_count: Chaos::new(v), + #[cfg(feature = "multicast")] mc_set_max_nodes: Chaos::new(v), get_sidecar_identifiers: Chaos::new(v), table_new: TableChaos::uniform(v), @@ -217,7 +231,9 @@ impl AsicConfig { port_autoneg_get: Chaos::new(v), port_enable_get: Chaos::new(v), connector_avail_channels: Chaos::new(v), + #[cfg(feature = "multicast")] mc_port_count: Chaos::new(v), + #[cfg(feature = "multicast")] mc_groups_count: Chaos::new(v), get_sidecar_identifiers: Chaos::new(v), ..Default::default() @@ -236,10 +252,15 @@ impl AsicConfig { port_prbs_set: Chaos::new(v), port_add: Chaos::new(v), port_delete: Chaos::new(v), + #[cfg(feature = "multicast")] mc_port_add: Chaos::new(v), + #[cfg(feature = "multicast")] mc_port_remove: Chaos::new(v), + #[cfg(feature = "multicast")] mc_group_create: Chaos::new(v), + #[cfg(feature = "multicast")] mc_group_destroy: Chaos::new(v), + #[cfg(feature = "multicast")] mc_set_max_nodes: Chaos::new(v), // TODO this can cause dpd to fail to start //table_clear: TableChaos::uniform(v), @@ -477,16 +498,19 @@ impl AsicOps for Handle { Ok(vec![0]) } + #[cfg(feature = "multicast")] fn mc_domains(&self) -> Vec { let len = self.ports.lock().unwrap().len() as u16; (0..len).collect() } + #[cfg(feature = "multicast")] fn mc_port_count(&self, _group_id: u16) -> AsicResult { unfurl!(self, mc_port_count); Ok(self.ports.lock().unwrap().len()) } + #[cfg(feature = "multicast")] fn mc_port_add( &self, _group_id: u16, @@ -498,26 +522,31 @@ impl AsicOps for Handle { Err(AsicError::OperationUnsupported) } + #[cfg(feature = "multicast")] fn mc_port_remove(&self, _group_id: u16, _port: u16) -> AsicResult<()> { unfurl!(self, mc_port_remove); Ok(()) } + #[cfg(feature = "multicast")] fn mc_group_create(&self, _group_id: u16) -> AsicResult<()> { unfurl!(self, mc_group_create); Err(AsicError::OperationUnsupported) } + #[cfg(feature = "multicast")] fn mc_group_destroy(&self, _group_id: u16) -> AsicResult<()> { unfurl!(self, mc_group_destroy); Ok(()) } + #[cfg(feature = "multicast")] fn mc_groups_count(&self) -> AsicResult { unfurl!(self, mc_groups_count); Ok(self.ports.lock().unwrap().len()) } + #[cfg(feature = "multicast")] fn mc_set_max_nodes( &self, _max_nodes: u32, diff --git a/asic/src/softnpu/mod.rs b/asic/src/softnpu/mod.rs index 2cbc2830..2b0654d7 100644 --- a/asic/src/softnpu/mod.rs +++ b/asic/src/softnpu/mod.rs @@ -326,15 +326,18 @@ impl AsicOps for Handle { Ok(vec![0]) } + #[cfg(feature = "multicast")] fn mc_domains(&self) -> Vec { let len = self.ports.lock().unwrap().len() as u16; (0..len).collect() } + #[cfg(feature = "multicast")] fn mc_port_count(&self, _group_id: u16) -> AsicResult { Ok(self.ports.lock().unwrap().len()) } + #[cfg(feature = "multicast")] fn mc_port_add( &self, _group_id: u16, @@ -345,22 +348,27 @@ impl AsicOps for Handle { Err(AsicError::OperationUnsupported) } + #[cfg(feature = "multicast")] fn mc_port_remove(&self, _group_id: u16, _port: u16) -> AsicResult<()> { Ok(()) } + #[cfg(feature = "multicast")] fn mc_group_create(&self, _group_id: u16) -> AsicResult<()> { Err(AsicError::OperationUnsupported) } + #[cfg(feature = "multicast")] fn mc_group_destroy(&self, _group_id: u16) -> AsicResult<()> { Ok(()) } + #[cfg(feature = "multicast")] fn mc_groups_count(&self) -> AsicResult { Ok(self.ports.lock().unwrap().len()) } + #[cfg(feature = "multicast")] fn mc_set_max_nodes( &self, _max_nodes: u32, diff --git a/asic/src/tofino_asic/mod.rs b/asic/src/tofino_asic/mod.rs index c85d5c51..c69087af 100644 --- a/asic/src/tofino_asic/mod.rs +++ b/asic/src/tofino_asic/mod.rs @@ -4,7 +4,6 @@ // // Copyright 2026 Oxide Computer Company -use std::collections::HashMap; use std::sync::{Mutex, MutexGuard}; use slog::{error, info, o}; @@ -19,6 +18,7 @@ mod bf_wrapper; mod genpd; mod link_fsm; +#[cfg(feature = "multicast")] pub mod mcast; pub mod ports; pub mod qsfp; @@ -149,14 +149,17 @@ impl AsicOps for Handle { ports::get_avail_channels(self, connector) } + #[cfg(feature = "multicast")] fn mc_domains(&self) -> Vec { mcast::domains(self) } + #[cfg(feature = "multicast")] fn mc_port_count(&self, group_id: u16) -> AsicResult { mcast::domain_port_count(self, group_id) } + #[cfg(feature = "multicast")] fn mc_port_add( &self, group_id: u16, @@ -167,26 +170,32 @@ impl AsicOps for Handle { mcast::domain_add_port(self, group_id, port, rid, level_1_excl_id) } + #[cfg(feature = "multicast")] fn mc_port_remove(&self, group_id: u16, port: u16) -> AsicResult<()> { mcast::domain_remove_port(self, group_id, port) } + #[cfg(feature = "multicast")] fn mc_group_create(&self, group_id: u16) -> AsicResult<()> { mcast::domain_create(self, group_id) } + #[cfg(feature = "multicast")] fn mc_group_destroy(&self, group_id: u16) -> AsicResult<()> { mcast::domain_destroy(self, group_id) } + #[cfg(feature = "multicast")] fn mc_group_exists(&self, group_id: u16) -> bool { mcast::domain_exists(self, group_id) } + #[cfg(feature = "multicast")] fn mc_groups_count(&self) -> AsicResult { mcast::domains_count(self) } + #[cfg(feature = "multicast")] fn mc_set_max_nodes( &self, max_nodes: u32, @@ -260,7 +269,8 @@ pub struct Handle { rt: tofino_common::BfRt, log: slog::Logger, phys_ports: Mutex, - domains: Mutex>, + #[cfg(feature = "multicast")] + domains: Mutex>, eth_connector_id: Option, } @@ -323,6 +333,7 @@ impl Handle { let dev_id = 0; let p4_dir = tofino_common::get_p4_dir()?; + #[allow(unused_mut)] let mut bf = bf_wrapper::bf_init( &log, &config.devpath, @@ -330,13 +341,15 @@ impl Handle { &config.board_rev, )?; let rt = tofino_common::BfRt::init(&p4_dir)?; - let domains = Mutex::new(HashMap::new()); let phys_ports = ports::init(dev_id)?; let eth_connector_id = phys_ports.eth_connector_id; // Note: we assume that bf_mc_init() has been called as part of the // bf_switch_init() operation. - bf.mcast_hdl = mcast::create_session()?; + #[cfg(feature = "multicast")] + { + bf.mcast_hdl = mcast::create_session()?; + } Ok(Handle { dev_id, @@ -346,7 +359,8 @@ impl Handle { rt, log: log.new(o!("unit" => "tofino_asic")), phys_ports: Mutex::new(phys_ports), - domains, + #[cfg(feature = "multicast")] + domains: Mutex::new(std::collections::HashMap::new()), eth_connector_id, }) } diff --git a/asic/src/tofino_stub/mod.rs b/asic/src/tofino_stub/mod.rs index 761a5a42..6571ba3c 100644 --- a/asic/src/tofino_stub/mod.rs +++ b/asic/src/tofino_stub/mod.rs @@ -161,14 +161,17 @@ impl AsicOps for StubHandle { } } + #[cfg(feature = "multicast")] fn mc_domains(&self) -> Vec { let mc_data = self.mc_data.lock().unwrap(); mc_data.domains() } + #[cfg(feature = "multicast")] fn mc_port_count(&self, group_id: u16) -> AsicResult { let mc_data = self.mc_data.lock().unwrap(); mc_data.domain_port_count(group_id) } + #[cfg(feature = "multicast")] fn mc_port_add( &self, group_id: u16, @@ -180,6 +183,7 @@ impl AsicOps for StubHandle { let mut mc_data = self.mc_data.lock().unwrap(); mc_data.domain_port_add(group_id, port, rid, level1_excl_id) } + #[cfg(feature = "multicast")] fn mc_port_remove(&self, group_id: u16, port: u16) -> AsicResult<()> { info!( self.log, @@ -188,24 +192,28 @@ impl AsicOps for StubHandle { let mut mc_data = self.mc_data.lock().unwrap(); mc_data.domain_port_remove(group_id, port) } + #[cfg(feature = "multicast")] fn mc_group_create(&self, group_id: u16) -> AsicResult<()> { info!(self.log, "creating multicast group {}", group_id); let mut mc_data = self.mc_data.lock().unwrap(); mc_data.domain_create(group_id) } + #[cfg(feature = "multicast")] fn mc_group_destroy(&self, group_id: u16) -> AsicResult<()> { info!(self.log, "destroying multicast group {}", group_id); let mut mc_data = self.mc_data.lock().unwrap(); mc_data.domain_destroy(group_id) } + #[cfg(feature = "multicast")] fn mc_groups_count(&self) -> AsicResult { info!(self.log, "number of multicast groups"); let mc_data = self.mc_data.lock().unwrap(); Ok(mc_data.domains().len()) } + #[cfg(feature = "multicast")] fn mc_set_max_nodes( &self, max_nodes: u32, @@ -247,6 +255,7 @@ pub struct StubHandle { log: slog::Logger, phys_ports: Mutex, port_state: Mutex>, + #[cfg(feature = "multicast")] mc_data: Mutex, update_tx: Mutex>>, } @@ -257,6 +266,7 @@ impl StubHandle { let rt = BfRt::init(&p4_dir)?; let phys_ports = Mutex::new(ports::init()?); let port_state = Mutex::new(BTreeMap::new()); + #[cfg(feature = "multicast")] let mc_data = Mutex::new(mcast::init()); let log = log.new(o!()); @@ -265,6 +275,7 @@ impl StubHandle { log, phys_ports, port_state, + #[cfg(feature = "multicast")] mc_data, update_tx: Mutex::new(None), }) diff --git a/dpd/src/counters.rs b/dpd/src/counters.rs index dca9791f..3c342245 100644 --- a/dpd/src/counters.rs +++ b/dpd/src/counters.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company /// This module contains the support for reading the indirect counters defined /// by the p4 program. While direct counters are attached to an existing table, diff --git a/dpd/src/macaddrs.rs b/dpd/src/macaddrs.rs index 0be2207f..ecf48d94 100644 --- a/dpd/src/macaddrs.rs +++ b/dpd/src/macaddrs.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::collections::BTreeSet; From 5c9500af4c6c4d101ef9326ad4163809503a9f50 Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Mon, 26 Jan 2026 21:06:15 -0500 Subject: [PATCH 04/11] test cleanup --- swadm/tests/counters.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/swadm/tests/counters.rs b/swadm/tests/counters.rs index b462335d..93eea9d9 100644 --- a/swadm/tests/counters.rs +++ b/swadm/tests/counters.rs @@ -45,13 +45,21 @@ fn test_p4_counter_list() { "Egress", "Ingress_Drop_Port", "Ingress_Drop_Reason", + #[cfg(feature = "multicast")] "Egress_Drop_Port", + #[cfg(feature = "multicast")] "Egress_Drop_Reason", + #[cfg(feature = "multicast")] "Unicast", + #[cfg(feature = "multicast")] "Multicast", + #[cfg(feature = "multicast")] "Multicast_External", + #[cfg(feature = "multicast")] "Multicast_Link_Local", + #[cfg(feature = "multicast")] "Multicast_Underlay", + #[cfg(feature = "multicast")] "Multicast_Drop", ]; From 9928927ad662e3fc7984225213ad525655abbc87 Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Tue, 27 Jan 2026 10:05:23 -0500 Subject: [PATCH 05/11] add CI test for multicast-enabled dendrite --- .github/buildomat/jobs/multicast-test.sh | 133 +++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100755 .github/buildomat/jobs/multicast-test.sh diff --git a/.github/buildomat/jobs/multicast-test.sh b/.github/buildomat/jobs/multicast-test.sh new file mode 100755 index 00000000..4d852c5c --- /dev/null +++ b/.github/buildomat/jobs/multicast-test.sh @@ -0,0 +1,133 @@ +#!/bin/bash +#: +#: name = "multicast-test" +#: variety = "basic" +#: target = "ubuntu-22.04" +#: rust_toolchain = true +#: +#: output_rules = [ +#: "/work/simulator.log", +#: "/work/dpd.log", +#: ] +#: + +#### >>>>>>>>>>>>>>>>>>>>>>>>>>>> Local Usage >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +#### +#### The following environment variables are useful. +#### +#### - JUST_TEST=1 Just runs the tests, skipping system prep. +#### - TESTNAME='$name' Will just run the specified test. +#### - STARTUP_TIMEOUT=n Seconds to wait for tofino-model/dpd to start. +#### Defaults to 15. +#### - NOBUILD=1 Don't build sidecar.p4 (in case you've already +#### built it) +#### +#### <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + +export RUST_BACKTRACE=1 + +set -o errexit +set -o pipefail +set -o xtrace + +source .github/buildomat/common.sh +source .github/buildomat/linux.sh + +wd=`pwd` +export WS=$wd +STARTUP_TIMEOUT=${STARTUP_TIMEOUT:=15} + +function cleanup { + set +o errexit + set +o pipefail + cd $wd + sudo -E pkill -9 dpd + sudo -E pkill -9 tofino-model + sudo -E ./tools/veth_teardown.sh + stty sane + # wait for daemons to die, if log file sizes change this can fail CI + sleep 10 +} +trap cleanup EXIT + +if [[ $JUST_TEST -ne 1 ]]; then + # See what hugepages was before starting + sysctl vm.nr_hugepages + # Make sure huge pages is enabled. This is required for running the SDE on + # linux. + sudo -E sysctl -w vm.nr_hugepages=128 + # Under some circumstances the sysctl may not completely work, so flush + # the vm caches and retry. + sudo -E sh -c 'echo 3 > /proc/sys/vm/drop_caches' + sudo -E sysctl -w vm.nr_hugepages=128 + # See what hugepages is now. If this is zero and things go sideways later, + # you'll know why. + sysctl vm.nr_hugepages + + banner "Packages" + sudo apt update -y + sudo apt install -y \ + libpcap-dev \ + libclang-dev \ + libssl-dev \ + pkg-config \ + libcli-dev \ + sysvbanner +fi + +export SDE=/opt/oxide/tofino_sde + +banner "Build" +if [[ $NOBUILD -ne 1 ]]; then + cargo build --features=tofino_asic,multicast --bin dpd --bin swadm + cargo xtask codegen --stages $TOFINO_STAGES +fi + +banner "Test" +sudo -E ./tools/veth_setup.sh +id=`id -un` +gr=`id -gn` +sudo -E mkdir -p /work +sudo -E chown $id:$gr /work +sudo -E ./tools/run_tofino_model.sh &> /work/simulator.log & +sleep $STARTUP_TIMEOUT +sudo -E ./tools/run_dpd.sh -m 127.0.0.1 &> /work/dpd.log & +sleep $STARTUP_TIMEOUT + +banner "Links" + +./target/debug/swadm -h '[::1]' link ls || echo "failed to list links" + +banner "swadm Checks" + +pushd swadm + +DENDRITE_TEST_HOST='[::1]' \ + DENDRITE_TEST_VERBOSITY=3 \ + cargo test \ + --no-fail-fast \ + --test \ + counters \ + -- \ + --ignored + +popd + +banner "Packet Tests" + +set +o errexit +set +o pipefail +stty sane +set -o errexit +set -o pipefail + +pushd dpd-client + +DENDRITE_TEST_HOST='[::1]' \ + DENDRITE_TEST_VERBOSITY=3 \ + cargo test \ + --features tofino_asic,multicast \ + --no-fail-fast \ + $TESTNAME \ + -- \ + --ignored From a61d49b846067165c774c0d8dfa681b088e779d7 Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Tue, 27 Jan 2026 12:33:07 -0500 Subject: [PATCH 06/11] swadm fix --- swadm/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/swadm/Cargo.toml b/swadm/Cargo.toml index beeaa607..cab5bfde 100644 --- a/swadm/Cargo.toml +++ b/swadm/Cargo.toml @@ -5,6 +5,9 @@ version = "0.1.0" authors = ["nils "] edition = "2024" +[features] +multicast = [] + [dependencies] anyhow.workspace = true chrono.workspace = true From 98fe547b9ff01290c319feb81b2458b2eba41fc9 Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Tue, 27 Jan 2026 18:09:48 +0000 Subject: [PATCH 07/11] test fix --- .github/buildomat/jobs/multicast-test.sh | 4 ++-- .github/buildomat/jobs/packet-test.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/buildomat/jobs/multicast-test.sh b/.github/buildomat/jobs/multicast-test.sh index 4d852c5c..354cf0e5 100755 --- a/.github/buildomat/jobs/multicast-test.sh +++ b/.github/buildomat/jobs/multicast-test.sh @@ -80,7 +80,7 @@ export SDE=/opt/oxide/tofino_sde banner "Build" if [[ $NOBUILD -ne 1 ]]; then cargo build --features=tofino_asic,multicast --bin dpd --bin swadm - cargo xtask codegen --stages $TOFINO_STAGES + cargo xtask codegen --stages $TOFINO_STAGES --multicast fi banner "Test" @@ -96,7 +96,7 @@ sleep $STARTUP_TIMEOUT banner "Links" -./target/debug/swadm -h '[::1]' link ls || echo "failed to list links" +./target/debug/swadm '[::1]' link ls || echo "failed to list links" banner "swadm Checks" diff --git a/.github/buildomat/jobs/packet-test.sh b/.github/buildomat/jobs/packet-test.sh index 24d94bc7..0d1ff394 100755 --- a/.github/buildomat/jobs/packet-test.sh +++ b/.github/buildomat/jobs/packet-test.sh @@ -96,7 +96,7 @@ sleep $STARTUP_TIMEOUT banner "Links" -./target/debug/swadm -h '[::1]' link ls || echo "failed to list links" +./target/debug/swadm '[::1]' link ls || echo "failed to list links" banner "swadm Checks" From 5fe5d8639ad5d44092bab0f93dfb7245fef9e5c0 Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Wed, 28 Jan 2026 19:16:34 +0000 Subject: [PATCH 08/11] code review changes --- .github/buildomat/jobs/multicast-test.sh | 103 +-------- .github/buildomat/jobs/packet-test.sh | 102 +-------- .github/buildomat/packet-test-common.sh | 132 ++++++++++++ aal/src/lib.rs | 96 ++++----- asic/src/chaos/mod.rs | 208 ++++++++++--------- asic/src/softnpu/mod.rs | 100 +++++---- asic/src/tofino_asic/mod.rs | 117 ++++++----- asic/src/tofino_stub/mod.rs | 141 +++++++------ dpd-client/Cargo.toml | 2 +- dpd-client/tests/integration_tests/common.rs | 4 +- dpd/Cargo.toml | 2 +- dpd/p4/sidecar.p4 | 2 +- dpd/src/api_server.rs | 111 +++++----- dpd/src/counters.rs | 30 ++- dpd/src/link.rs | 115 +++------- dpd/src/mcast/mod.rs | 2 +- dpd/src/mcast/rollback.rs | 2 +- dpd/src/table/mod.rs | 46 ++-- swadm/tests/counters.rs | 19 +- 19 files changed, 601 insertions(+), 733 deletions(-) create mode 100755 .github/buildomat/packet-test-common.sh diff --git a/.github/buildomat/jobs/multicast-test.sh b/.github/buildomat/jobs/multicast-test.sh index 354cf0e5..a6e1669f 100755 --- a/.github/buildomat/jobs/multicast-test.sh +++ b/.github/buildomat/jobs/multicast-test.sh @@ -30,104 +30,5 @@ set -o errexit set -o pipefail set -o xtrace -source .github/buildomat/common.sh -source .github/buildomat/linux.sh - -wd=`pwd` -export WS=$wd -STARTUP_TIMEOUT=${STARTUP_TIMEOUT:=15} - -function cleanup { - set +o errexit - set +o pipefail - cd $wd - sudo -E pkill -9 dpd - sudo -E pkill -9 tofino-model - sudo -E ./tools/veth_teardown.sh - stty sane - # wait for daemons to die, if log file sizes change this can fail CI - sleep 10 -} -trap cleanup EXIT - -if [[ $JUST_TEST -ne 1 ]]; then - # See what hugepages was before starting - sysctl vm.nr_hugepages - # Make sure huge pages is enabled. This is required for running the SDE on - # linux. - sudo -E sysctl -w vm.nr_hugepages=128 - # Under some circumstances the sysctl may not completely work, so flush - # the vm caches and retry. - sudo -E sh -c 'echo 3 > /proc/sys/vm/drop_caches' - sudo -E sysctl -w vm.nr_hugepages=128 - # See what hugepages is now. If this is zero and things go sideways later, - # you'll know why. - sysctl vm.nr_hugepages - - banner "Packages" - sudo apt update -y - sudo apt install -y \ - libpcap-dev \ - libclang-dev \ - libssl-dev \ - pkg-config \ - libcli-dev \ - sysvbanner -fi - -export SDE=/opt/oxide/tofino_sde - -banner "Build" -if [[ $NOBUILD -ne 1 ]]; then - cargo build --features=tofino_asic,multicast --bin dpd --bin swadm - cargo xtask codegen --stages $TOFINO_STAGES --multicast -fi - -banner "Test" -sudo -E ./tools/veth_setup.sh -id=`id -un` -gr=`id -gn` -sudo -E mkdir -p /work -sudo -E chown $id:$gr /work -sudo -E ./tools/run_tofino_model.sh &> /work/simulator.log & -sleep $STARTUP_TIMEOUT -sudo -E ./tools/run_dpd.sh -m 127.0.0.1 &> /work/dpd.log & -sleep $STARTUP_TIMEOUT - -banner "Links" - -./target/debug/swadm '[::1]' link ls || echo "failed to list links" - -banner "swadm Checks" - -pushd swadm - -DENDRITE_TEST_HOST='[::1]' \ - DENDRITE_TEST_VERBOSITY=3 \ - cargo test \ - --no-fail-fast \ - --test \ - counters \ - -- \ - --ignored - -popd - -banner "Packet Tests" - -set +o errexit -set +o pipefail -stty sane -set -o errexit -set -o pipefail - -pushd dpd-client - -DENDRITE_TEST_HOST='[::1]' \ - DENDRITE_TEST_VERBOSITY=3 \ - cargo test \ - --features tofino_asic,multicast \ - --no-fail-fast \ - $TESTNAME \ - -- \ - --ignored +export MULTICAST=1 +source .github/buildomat/packet-test-common.sh diff --git a/.github/buildomat/jobs/packet-test.sh b/.github/buildomat/jobs/packet-test.sh index 0d1ff394..85a0ded8 100755 --- a/.github/buildomat/jobs/packet-test.sh +++ b/.github/buildomat/jobs/packet-test.sh @@ -30,104 +30,4 @@ set -o errexit set -o pipefail set -o xtrace -source .github/buildomat/common.sh -source .github/buildomat/linux.sh - -wd=`pwd` -export WS=$wd -STARTUP_TIMEOUT=${STARTUP_TIMEOUT:=15} - -function cleanup { - set +o errexit - set +o pipefail - cd $wd - sudo -E pkill -9 dpd - sudo -E pkill -9 tofino-model - sudo -E ./tools/veth_teardown.sh - stty sane - # wait for daemons to die, if log file sizes change this can fail CI - sleep 10 -} -trap cleanup EXIT - -if [[ $JUST_TEST -ne 1 ]]; then - # See what hugepages was before starting - sysctl vm.nr_hugepages - # Make sure huge pages is enabled. This is required for running the SDE on - # linux. - sudo -E sysctl -w vm.nr_hugepages=128 - # Under some circumstances the sysctl may not completely work, so flush - # the vm caches and retry. - sudo -E sh -c 'echo 3 > /proc/sys/vm/drop_caches' - sudo -E sysctl -w vm.nr_hugepages=128 - # See what hugepages is now. If this is zero and things go sideways later, - # you'll know why. - sysctl vm.nr_hugepages - - banner "Packages" - sudo apt update -y - sudo apt install -y \ - libpcap-dev \ - libclang-dev \ - libssl-dev \ - pkg-config \ - libcli-dev \ - sysvbanner -fi - -export SDE=/opt/oxide/tofino_sde - -banner "Build" -if [[ $NOBUILD -ne 1 ]]; then - cargo build --features=tofino_asic --bin dpd --bin swadm - cargo xtask codegen --stages $TOFINO_STAGES -fi - -banner "Test" -sudo -E ./tools/veth_setup.sh -id=`id -un` -gr=`id -gn` -sudo -E mkdir -p /work -sudo -E chown $id:$gr /work -sudo -E ./tools/run_tofino_model.sh &> /work/simulator.log & -sleep $STARTUP_TIMEOUT -sudo -E ./tools/run_dpd.sh -m 127.0.0.1 &> /work/dpd.log & -sleep $STARTUP_TIMEOUT - -banner "Links" - -./target/debug/swadm '[::1]' link ls || echo "failed to list links" - -banner "swadm Checks" - -pushd swadm - -DENDRITE_TEST_HOST='[::1]' \ - DENDRITE_TEST_VERBOSITY=3 \ - cargo test \ - --no-fail-fast \ - --test \ - counters \ - -- \ - --ignored - -popd - -banner "Packet Tests" - -set +o errexit -set +o pipefail -stty sane -set -o errexit -set -o pipefail - -pushd dpd-client - -DENDRITE_TEST_HOST='[::1]' \ - DENDRITE_TEST_VERBOSITY=3 \ - cargo test \ - --features tofino_asic \ - --no-fail-fast \ - $TESTNAME \ - -- \ - --ignored +source .github/buildomat/packet-test-common.sh diff --git a/.github/buildomat/packet-test-common.sh b/.github/buildomat/packet-test-common.sh new file mode 100755 index 00000000..c1dce612 --- /dev/null +++ b/.github/buildomat/packet-test-common.sh @@ -0,0 +1,132 @@ +export RUST_BACKTRACE=1 + +source .github/buildomat/common.sh +source .github/buildomat/linux.sh + +wd=`pwd` +export WS=$wd +MODEL_STARTUP_TIMEOUT=${MODEL_STARTUP_TIMEOUT:=5} +STARTUP_TIMEOUT=${STARTUP_TIMEOUT:=120} + +if [ x$MULTICAST == x ]; then + BUILD_FEATURES=tofino_asic + CODEGEN_FEATURES= + SWADM_FEATURES= + else + BUILD_FEATURES=tofino_asic,multicast + CODEGEN_FEATURES=--multicast + SWADM_FEATURES=multicast +fi + +function cleanup { + set +o errexit + set +o pipefail + cd $wd + sudo -E pkill -9 dpd + sudo -E pkill -9 tofino-model + sudo -E ./tools/veth_teardown.sh + stty sane + # wait for daemons to die, if log file sizes change this can fail CI + sleep 10 +} +trap cleanup EXIT + +if [[ $JUST_TEST -ne 1 ]]; then + # See what hugepages was before starting + sysctl vm.nr_hugepages + # Make sure huge pages is enabled. This is required for running the SDE on + # linux. + sudo -E sysctl -w vm.nr_hugepages=128 + # Under some circumstances the sysctl may not completely work, so flush + # the vm caches and retry. + sudo -E sh -c 'echo 3 > /proc/sys/vm/drop_caches' + sudo -E sysctl -w vm.nr_hugepages=128 + # See what hugepages is now. If this is zero and things go sideways later, + # you'll know why. + sysctl vm.nr_hugepages + + banner "Packages" + sudo apt update -y + sudo apt install -y \ + libpcap-dev \ + libclang-dev \ + libssl-dev \ + pkg-config \ + libcli-dev \ + sysvbanner +fi + +export SDE=/opt/oxide/tofino_sde + +banner "Build" +if [[ $NOBUILD -ne 1 ]]; then + cargo build --features=$BUILD_FEATURES --bin dpd --bin swadm + cargo xtask codegen --stages $TOFINO_STAGES $CODEGEN_FEATURES +fi + +banner "Test" +sudo -E ./tools/veth_setup.sh +id=`id -un` +gr=`id -gn` +sudo -E mkdir -p /work +sudo -E chown $id:$gr /work +sudo -E ./tools/run_tofino_model.sh &> /work/simulator.log & +sleep $MODEL_STARTUP_TIMEOUT +sudo -E ./tools/run_dpd.sh -m 127.0.0.1 &> /work/dpd.log & +echo "waiting for dpd to come online" +set +o errexit + +SLEEP_TIME=5 +iters=$(( $STARTUP_TIMEOUT / $SLEEP_TIME )) +while [ 1 ] ; do + ./target/debug/swadm build-info 2> /dev/null + if [ $? == 0 ]; then + break + fi + iters=$(($iters - 1)) + if [ $iters = 0 ]; then + echo "dpd failed to come online in $STARTUP_TIMEOUT seconds" + exit 1 + fi + sleep $SLEEP_TIME +done +set -o errexit + +banner "Links" + +./target/debug/swadm --host '[::1]' link ls || echo "failed to list links" + +banner "swadm Checks" + +pushd swadm + +DENDRITE_TEST_HOST='[::1]' \ + DENDRITE_TEST_VERBOSITY=3 \ + cargo test \ + --no-fail-fast \ + --features=$SWADM_FEATURES \ + --test \ + counters \ + -- \ + --ignored + +popd + +banner "Packet Tests" + +set +o errexit +set +o pipefail +stty sane +set -o errexit +set -o pipefail + +pushd dpd-client + +DENDRITE_TEST_HOST='[::1]' \ + DENDRITE_TEST_VERBOSITY=3 \ + cargo test \ + --features $BUILD_FEATURES \ + --no-fail-fast \ + $TESTNAME \ + -- \ + --ignored diff --git a/aal/src/lib.rs b/aal/src/lib.rs index fe3288f9..d871e768 100644 --- a/aal/src/lib.rs +++ b/aal/src/lib.rs @@ -112,8 +112,52 @@ pub enum AsicError { Missing(String), } -/// The `AsicOps` trait contains all of the non-Table related ASIC operations +/// The `AsicMulticastOps` trait contains the multicast-related ASIC operations /// that the dataplane daemon requires. +#[cfg(feature = "multicast")] +pub trait AsicMulticastOps { + /// Return a vector containing all of the defined multicast groups. + fn mc_domains(&self) -> Vec; + + /// For a given multicast group, return the number of ports assigned to it. + fn mc_port_count(&self, group_id: u16) -> AsicResult; + + /// Add a port to a multicast group. The port is identified using its ASIC + /// identifier. + fn mc_port_add( + &self, + group_id: u16, + port: AsicId, + rid: u16, + level_1_excl_id: u16, + ) -> AsicResult<()>; + + /// Remove a port from a multicast group. The port is identified using its ASIC + /// identifier. + fn mc_port_remove(&self, group_id: u16, port: AsicId) -> AsicResult<()>; + + /// Create a new, unpopulated multicast group. + fn mc_group_create(&self, group_id: u16) -> AsicResult<()>; + + /// Destroy a multicast group. + fn mc_group_destroy(&self, group_id: u16) -> AsicResult<()>; + + /// Check if a multicast group exists. + fn mc_group_exists(&self, group_id: u16) -> bool { + self.mc_domains().contains(&group_id) + } + + /// Get the total number of multicast groups. + fn mc_groups_count(&self) -> AsicResult; + + /// Set the maximum number of multicast nodes. + fn mc_set_max_nodes( + &self, + max_nodes: u32, + max_link_aggregated_nodes: u32, + ) -> AsicResult<()>; +} + pub trait AsicOps { /// Reports the kind of media plugged into the port // TODO-correctness: This should probably take a `PortId` or `Connector`. @@ -196,56 +240,6 @@ pub trait AsicOps { connector: Connector, ) -> AsicResult>; - /// Return a vector containing all of the defined multicast groups. - #[cfg(feature = "multicast")] - fn mc_domains(&self) -> Vec; - - /// For a given multicast group, return the number of ports assigned to it. - #[cfg(feature = "multicast")] - fn mc_port_count(&self, group_id: u16) -> AsicResult; - - /// Add a port to a multicast group. The port is identified using its ASIC - /// identifier. - #[cfg(feature = "multicast")] - fn mc_port_add( - &self, - group_id: u16, - port: AsicId, - rid: u16, - level_1_excl_id: u16, - ) -> AsicResult<()>; - - /// Remove a port from a multicast group. The port is identified using its ASIC - /// identifier. - #[cfg(feature = "multicast")] - fn mc_port_remove(&self, group_id: u16, port: AsicId) -> AsicResult<()>; - - /// Create a new, unpopulated multicast group. - #[cfg(feature = "multicast")] - fn mc_group_create(&self, group_id: u16) -> AsicResult<()>; - - /// Destroy a multicast group. - #[cfg(feature = "multicast")] - fn mc_group_destroy(&self, group_id: u16) -> AsicResult<()>; - - /// Check if a multicast group exists. - #[cfg(feature = "multicast")] - fn mc_group_exists(&self, group_id: u16) -> bool { - self.mc_domains().contains(&group_id) - } - - /// Get the total number of multicast groups. - #[cfg(feature = "multicast")] - fn mc_groups_count(&self) -> AsicResult; - - /// Set the maximum number of multicast nodes. - #[cfg(feature = "multicast")] - fn mc_set_max_nodes( - &self, - max_nodes: u32, - max_link_aggregated_nodes: u32, - ) -> AsicResult<()>; - /// Get sidecar identifiers of the device being managed. fn get_sidecar_identifiers(&self) -> AsicResult; diff --git a/asic/src/chaos/mod.rs b/asic/src/chaos/mod.rs index f3bf123d..95408066 100644 --- a/asic/src/chaos/mod.rs +++ b/asic/src/chaos/mod.rs @@ -11,6 +11,8 @@ use std::collections::HashMap; use std::sync::Mutex; use tokio::sync::mpsc; +#[cfg(feature = "multicast")] +use aal::AsicMulticastOps; use aal::{ AsicError, AsicId, AsicOps, AsicResult, Connector, PortHdl, PortUpdate, SidecarIdentifiers, @@ -127,6 +129,18 @@ impl TableChaos { } } +#[cfg(feature = "multicast")] +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct AsicMulticastConfig { + pub mc_port_count: Chaos, + pub mc_port_add: Chaos, + pub mc_port_remove: Chaos, + pub mc_group_create: Chaos, + pub mc_group_destroy: Chaos, + pub mc_groups_count: Chaos, + pub mc_set_max_nodes: Chaos, +} + /// The chaos ASIC config contains chaos values for each ASIC operation. #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct AsicConfig { @@ -147,20 +161,6 @@ pub struct AsicConfig { pub port_delete: Chaos, pub register_port_update_handler: Chaos, pub connector_avail_channels: Chaos, - #[cfg(feature = "multicast")] - pub mc_port_count: Chaos, - #[cfg(feature = "multicast")] - pub mc_port_add: Chaos, - #[cfg(feature = "multicast")] - pub mc_port_remove: Chaos, - #[cfg(feature = "multicast")] - pub mc_group_create: Chaos, - #[cfg(feature = "multicast")] - pub mc_group_destroy: Chaos, - #[cfg(feature = "multicast")] - pub mc_groups_count: Chaos, - #[cfg(feature = "multicast")] - pub mc_set_max_nodes: Chaos, pub get_sidecar_identifiers: Chaos, pub table_new: TableChaos, pub table_clear: TableChaos, @@ -168,6 +168,8 @@ pub struct AsicConfig { pub table_entry_add: TableChaos, pub table_entry_update: TableChaos, pub table_entry_del: TableChaos, + #[cfg(feature = "multicast")] + pub mc_config: AsicMulticastConfig, } impl AsicConfig { @@ -192,20 +194,6 @@ impl AsicConfig { port_delete: Chaos::new(v), register_port_update_handler: Chaos::new(v), connector_avail_channels: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_port_count: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_port_add: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_port_remove: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_group_create: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_group_destroy: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_groups_count: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_set_max_nodes: Chaos::new(v), get_sidecar_identifiers: Chaos::new(v), table_new: TableChaos::uniform(v), table_clear: TableChaos::uniform(v), @@ -213,6 +201,16 @@ impl AsicConfig { table_entry_add: TableChaos::uniform(v), table_entry_update: TableChaos::uniform(v), table_entry_del: TableChaos::uniform(v), + #[cfg(feature = "multicast")] + mc_config: AsicMulticastConfig { + mc_port_count: Chaos::new(v), + mc_port_add: Chaos::new(v), + mc_port_remove: Chaos::new(v), + mc_group_create: Chaos::new(v), + mc_group_destroy: Chaos::new(v), + mc_groups_count: Chaos::new(v), + mc_set_max_nodes: Chaos::new(v), + }, } } @@ -231,11 +229,13 @@ impl AsicConfig { port_autoneg_get: Chaos::new(v), port_enable_get: Chaos::new(v), connector_avail_channels: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_port_count: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_groups_count: Chaos::new(v), get_sidecar_identifiers: Chaos::new(v), + #[cfg(feature = "multicast")] + mc_config: AsicMulticastConfig { + mc_port_count: Chaos::new(v), + mc_groups_count: Chaos::new(v), + ..Default::default() + }, ..Default::default() } } @@ -252,22 +252,21 @@ impl AsicConfig { port_prbs_set: Chaos::new(v), port_add: Chaos::new(v), port_delete: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_port_add: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_port_remove: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_group_create: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_group_destroy: Chaos::new(v), - #[cfg(feature = "multicast")] - mc_set_max_nodes: Chaos::new(v), // TODO this can cause dpd to fail to start //table_clear: TableChaos::uniform(v), table_default_set: TableChaos::uniform(v), table_entry_add: TableChaos::uniform(v), table_entry_update: TableChaos::uniform(v), table_entry_del: TableChaos::uniform(v), + #[cfg(feature = "multicast")] + mc_config: AsicMulticastConfig { + mc_port_add: Chaos::new(v), + mc_port_remove: Chaos::new(v), + mc_group_create: Chaos::new(v), + mc_group_destroy: Chaos::new(v), + mc_set_max_nodes: Chaos::new(v), + ..Default::default() + }, ..Default::default() } } @@ -346,6 +345,20 @@ macro_rules! unfurl { } pub(crate) use unfurl; +/// A convenience macro for unfurling multicast chaos. The $name should be a +/// regular `Chaos` member of [`AsicMulticastConfigConfig`]. The `handle` is +/// a [`Handle`] object. +#[cfg(feature = "multicast")] +macro_rules! unfurl_mc { + ($handle:ident, $name:ident) => { + $handle + .config + .mc_config + .$name + .unfurled(&$handle.log, stringify!($name))? + }; +} + /// A convenience macro for unfurling tabular chaos. The $name should be a /// `TableChaos` member of [`AsicConfig`]. The `handle` is a [`Handle`] object. macro_rules! table_unfurl { @@ -355,6 +368,59 @@ macro_rules! table_unfurl { } pub(crate) use table_unfurl; +#[cfg(feature = "multicast")] +impl AsicMulticastOps for Handle { + fn mc_domains(&self) -> Vec { + let len = self.ports.lock().unwrap().len() as u16; + (0..len).collect() + } + + fn mc_port_count(&self, _group_id: u16) -> AsicResult { + unfurl_mc!(self, mc_port_count); + Ok(self.ports.lock().unwrap().len()) + } + + fn mc_port_add( + &self, + _group_id: u16, + _port: u16, + _rid: u16, + _level1_excl_id: u16, + ) -> AsicResult<()> { + unfurl_mc!(self, mc_port_add); + Err(AsicError::OperationUnsupported) + } + + fn mc_port_remove(&self, _group_id: u16, _port: u16) -> AsicResult<()> { + unfurl_mc!(self, mc_port_remove); + Ok(()) + } + + fn mc_group_create(&self, _group_id: u16) -> AsicResult<()> { + unfurl_mc!(self, mc_group_create); + Err(AsicError::OperationUnsupported) + } + + fn mc_group_destroy(&self, _group_id: u16) -> AsicResult<()> { + unfurl_mc!(self, mc_group_destroy); + Ok(()) + } + + fn mc_groups_count(&self) -> AsicResult { + unfurl_mc!(self, mc_groups_count); + Ok(self.ports.lock().unwrap().len()) + } + + fn mc_set_max_nodes( + &self, + _max_nodes: u32, + _max_link_aggregated_nodes: u32, + ) -> AsicResult<()> { + unfurl_mc!(self, mc_set_max_nodes); + Ok(()) + } +} + impl AsicOps for Handle { fn port_get_media(&self, port_hdl: PortHdl) -> AsicResult { unfurl!(self, port_get_media); @@ -498,64 +564,6 @@ impl AsicOps for Handle { Ok(vec![0]) } - #[cfg(feature = "multicast")] - fn mc_domains(&self) -> Vec { - let len = self.ports.lock().unwrap().len() as u16; - (0..len).collect() - } - - #[cfg(feature = "multicast")] - fn mc_port_count(&self, _group_id: u16) -> AsicResult { - unfurl!(self, mc_port_count); - Ok(self.ports.lock().unwrap().len()) - } - - #[cfg(feature = "multicast")] - fn mc_port_add( - &self, - _group_id: u16, - _port: u16, - _rid: u16, - _level1_excl_id: u16, - ) -> AsicResult<()> { - unfurl!(self, mc_port_add); - Err(AsicError::OperationUnsupported) - } - - #[cfg(feature = "multicast")] - fn mc_port_remove(&self, _group_id: u16, _port: u16) -> AsicResult<()> { - unfurl!(self, mc_port_remove); - Ok(()) - } - - #[cfg(feature = "multicast")] - fn mc_group_create(&self, _group_id: u16) -> AsicResult<()> { - unfurl!(self, mc_group_create); - Err(AsicError::OperationUnsupported) - } - - #[cfg(feature = "multicast")] - fn mc_group_destroy(&self, _group_id: u16) -> AsicResult<()> { - unfurl!(self, mc_group_destroy); - Ok(()) - } - - #[cfg(feature = "multicast")] - fn mc_groups_count(&self) -> AsicResult { - unfurl!(self, mc_groups_count); - Ok(self.ports.lock().unwrap().len()) - } - - #[cfg(feature = "multicast")] - fn mc_set_max_nodes( - &self, - _max_nodes: u32, - _max_link_aggregated_nodes: u32, - ) -> AsicResult<()> { - unfurl!(self, mc_set_max_nodes); - Ok(()) - } - fn get_sidecar_identifiers(&self) -> AsicResult { unfurl!(self, get_sidecar_identifiers); Ok(Identifiers::default()) diff --git a/asic/src/softnpu/mod.rs b/asic/src/softnpu/mod.rs index 2b0654d7..bc36e428 100644 --- a/asic/src/softnpu/mod.rs +++ b/asic/src/softnpu/mod.rs @@ -16,6 +16,8 @@ pub use crate::faux_fsm::FsmState; pub use crate::faux_fsm::FsmType; pub use crate::faux_fsm::PortFsmState; +#[cfg(feature = "multicast")] +use aal::AsicMulticastOps; use aal::{ AsicError, AsicId, AsicOps, AsicResult, Connector, PortHdl, PortUpdate, SidecarIdentifiers, @@ -143,6 +145,52 @@ impl Handle { pub fn fini(&self) {} } +#[cfg(feature = "multicast")] +impl AsicMulticastOps for Handle { + fn mc_domains(&self) -> Vec { + let len = self.ports.lock().unwrap().len() as u16; + (0..len).collect() + } + + fn mc_port_count(&self, _group_id: u16) -> AsicResult { + Ok(self.ports.lock().unwrap().len()) + } + + fn mc_port_add( + &self, + _group_id: u16, + _port: u16, + _rid: u16, + _level1_excl_id: u16, + ) -> AsicResult<()> { + Err(AsicError::OperationUnsupported) + } + + fn mc_port_remove(&self, _group_id: u16, _port: u16) -> AsicResult<()> { + Ok(()) + } + + fn mc_group_create(&self, _group_id: u16) -> AsicResult<()> { + Err(AsicError::OperationUnsupported) + } + + fn mc_group_destroy(&self, _group_id: u16) -> AsicResult<()> { + Ok(()) + } + + fn mc_groups_count(&self) -> AsicResult { + Ok(self.ports.lock().unwrap().len()) + } + + fn mc_set_max_nodes( + &self, + _max_nodes: u32, + _max_link_aggregated_nodes: u32, + ) -> AsicResult<()> { + Ok(()) + } +} + impl AsicOps for Handle { fn port_get_media(&self, port_hdl: PortHdl) -> AsicResult { Ok(match port_hdl.connector { @@ -325,58 +373,6 @@ impl AsicOps for Handle { ) -> AsicResult> { Ok(vec![0]) } - - #[cfg(feature = "multicast")] - fn mc_domains(&self) -> Vec { - let len = self.ports.lock().unwrap().len() as u16; - (0..len).collect() - } - - #[cfg(feature = "multicast")] - fn mc_port_count(&self, _group_id: u16) -> AsicResult { - Ok(self.ports.lock().unwrap().len()) - } - - #[cfg(feature = "multicast")] - fn mc_port_add( - &self, - _group_id: u16, - _port: u16, - _rid: u16, - _level1_excl_id: u16, - ) -> AsicResult<()> { - Err(AsicError::OperationUnsupported) - } - - #[cfg(feature = "multicast")] - fn mc_port_remove(&self, _group_id: u16, _port: u16) -> AsicResult<()> { - Ok(()) - } - - #[cfg(feature = "multicast")] - fn mc_group_create(&self, _group_id: u16) -> AsicResult<()> { - Err(AsicError::OperationUnsupported) - } - - #[cfg(feature = "multicast")] - fn mc_group_destroy(&self, _group_id: u16) -> AsicResult<()> { - Ok(()) - } - - #[cfg(feature = "multicast")] - fn mc_groups_count(&self) -> AsicResult { - Ok(self.ports.lock().unwrap().len()) - } - - #[cfg(feature = "multicast")] - fn mc_set_max_nodes( - &self, - _max_nodes: u32, - _max_link_aggregated_nodes: u32, - ) -> AsicResult<()> { - Ok(()) - } - fn get_sidecar_identifiers(&self) -> AsicResult { Ok(Identifiers { id: Uuid::new_v4(), diff --git a/asic/src/tofino_asic/mod.rs b/asic/src/tofino_asic/mod.rs index c69087af..3453d566 100644 --- a/asic/src/tofino_asic/mod.rs +++ b/asic/src/tofino_asic/mod.rs @@ -27,6 +27,8 @@ pub mod serdes; pub mod stats; pub mod table; +#[cfg(feature = "multicast")] +use aal::AsicMulticastOps; use aal::{ AsicError, AsicOps, AsicResult, Connector, PortHdl, SidecarIdentifiers, }; @@ -67,6 +69,55 @@ impl Default for AsicConfig { } } +#[cfg(feature = "multicast")] +impl AsicMulticastOps for Handle { + fn mc_domains(&self) -> Vec { + mcast::domains(self) + } + + fn mc_port_count(&self, group_id: u16) -> AsicResult { + mcast::domain_port_count(self, group_id) + } + + fn mc_port_add( + &self, + group_id: u16, + port: u16, + rid: u16, + level_1_excl_id: u16, + ) -> AsicResult<()> { + mcast::domain_add_port(self, group_id, port, rid, level_1_excl_id) + } + + fn mc_port_remove(&self, group_id: u16, port: u16) -> AsicResult<()> { + mcast::domain_remove_port(self, group_id, port) + } + + fn mc_group_create(&self, group_id: u16) -> AsicResult<()> { + mcast::domain_create(self, group_id) + } + + fn mc_group_destroy(&self, group_id: u16) -> AsicResult<()> { + mcast::domain_destroy(self, group_id) + } + + fn mc_group_exists(&self, group_id: u16) -> bool { + mcast::domain_exists(self, group_id) + } + + fn mc_groups_count(&self) -> AsicResult { + mcast::domains_count(self) + } + + fn mc_set_max_nodes( + &self, + max_nodes: u32, + max_link_aggregated_nodes: u32, + ) -> AsicResult<()> { + mcast::set_max_nodes(self, max_nodes, max_link_aggregated_nodes) + } +} + impl AsicOps for Handle { fn port_get_media(&self, port_hdl: PortHdl) -> AsicResult { ports::get_media(self, port_hdl) @@ -149,61 +200,6 @@ impl AsicOps for Handle { ports::get_avail_channels(self, connector) } - #[cfg(feature = "multicast")] - fn mc_domains(&self) -> Vec { - mcast::domains(self) - } - - #[cfg(feature = "multicast")] - fn mc_port_count(&self, group_id: u16) -> AsicResult { - mcast::domain_port_count(self, group_id) - } - - #[cfg(feature = "multicast")] - fn mc_port_add( - &self, - group_id: u16, - port: u16, - rid: u16, - level_1_excl_id: u16, - ) -> AsicResult<()> { - mcast::domain_add_port(self, group_id, port, rid, level_1_excl_id) - } - - #[cfg(feature = "multicast")] - fn mc_port_remove(&self, group_id: u16, port: u16) -> AsicResult<()> { - mcast::domain_remove_port(self, group_id, port) - } - - #[cfg(feature = "multicast")] - fn mc_group_create(&self, group_id: u16) -> AsicResult<()> { - mcast::domain_create(self, group_id) - } - - #[cfg(feature = "multicast")] - fn mc_group_destroy(&self, group_id: u16) -> AsicResult<()> { - mcast::domain_destroy(self, group_id) - } - - #[cfg(feature = "multicast")] - fn mc_group_exists(&self, group_id: u16) -> bool { - mcast::domain_exists(self, group_id) - } - - #[cfg(feature = "multicast")] - fn mc_groups_count(&self) -> AsicResult { - mcast::domains_count(self) - } - - #[cfg(feature = "multicast")] - fn mc_set_max_nodes( - &self, - max_nodes: u32, - max_link_aggregated_nodes: u32, - ) -> AsicResult<()> { - mcast::set_max_nodes(self, max_nodes, max_link_aggregated_nodes) - } - // Ideally we would get some sort of sidecar-level ID from the FRUID. // Until/unless that is possible, we will use the chip_id from the tofino // fuse on the sidecar. Embedded within this ID is the fab, lot, wafer id, @@ -333,13 +329,22 @@ impl Handle { let dev_id = 0; let p4_dir = tofino_common::get_p4_dir()?; - #[allow(unused_mut)] + + #[cfg(feature = "multicast")] let mut bf = bf_wrapper::bf_init( &log, &config.devpath, &p4_dir, &config.board_rev, )?; + #[cfg(not(feature = "multicast"))] + let bf = bf_wrapper::bf_init( + &log, + &config.devpath, + &p4_dir, + &config.board_rev, + )?; + let rt = tofino_common::BfRt::init(&p4_dir)?; let phys_ports = ports::init(dev_id)?; let eth_connector_id = phys_ports.eth_connector_id; diff --git a/asic/src/tofino_stub/mod.rs b/asic/src/tofino_stub/mod.rs index 6571ba3c..956c6879 100644 --- a/asic/src/tofino_stub/mod.rs +++ b/asic/src/tofino_stub/mod.rs @@ -12,6 +12,8 @@ use tokio::sync::mpsc; use crate::Identifiers; use crate::tofino_common::*; +#[cfg(feature = "multicast")] +use aal::AsicMulticastOps; use aal::{ AsicError, AsicOps, AsicResult, Connector, PortHdl, PortUpdate, SidecarIdentifiers, @@ -66,6 +68,77 @@ impl AsicLinkStats { } } +#[cfg(feature = "multicast")] +impl AsicMulticastOps for StubHandle { + #[cfg(feature = "multicast")] + fn mc_domains(&self) -> Vec { + let mc_data = self.mc_data.lock().unwrap(); + mc_data.domains() + } + #[cfg(feature = "multicast")] + fn mc_port_count(&self, group_id: u16) -> AsicResult { + let mc_data = self.mc_data.lock().unwrap(); + mc_data.domain_port_count(group_id) + } + #[cfg(feature = "multicast")] + fn mc_port_add( + &self, + group_id: u16, + port: u16, + rid: u16, + level1_excl_id: u16, + ) -> AsicResult<()> { + info!(self.log, "adding port {} to multicast group {}", port, group_id); + let mut mc_data = self.mc_data.lock().unwrap(); + mc_data.domain_port_add(group_id, port, rid, level1_excl_id) + } + #[cfg(feature = "multicast")] + fn mc_port_remove(&self, group_id: u16, port: u16) -> AsicResult<()> { + info!( + self.log, + "remvoing port {} from multicast group {}", port, group_id + ); + let mut mc_data = self.mc_data.lock().unwrap(); + mc_data.domain_port_remove(group_id, port) + } + #[cfg(feature = "multicast")] + fn mc_group_create(&self, group_id: u16) -> AsicResult<()> { + info!(self.log, "creating multicast group {}", group_id); + let mut mc_data = self.mc_data.lock().unwrap(); + mc_data.domain_create(group_id) + } + + #[cfg(feature = "multicast")] + fn mc_group_destroy(&self, group_id: u16) -> AsicResult<()> { + info!(self.log, "destroying multicast group {}", group_id); + let mut mc_data = self.mc_data.lock().unwrap(); + mc_data.domain_destroy(group_id) + } + + #[cfg(feature = "multicast")] + fn mc_groups_count(&self) -> AsicResult { + info!(self.log, "number of multicast groups"); + let mc_data = self.mc_data.lock().unwrap(); + Ok(mc_data.domains().len()) + } + + #[cfg(feature = "multicast")] + fn mc_set_max_nodes( + &self, + max_nodes: u32, + max_link_aggregated_nodes: u32, + ) -> AsicResult<()> { + info!( + self.log, + "setting max nodes to {} and max link aggregated nodes to {}", + max_nodes, + max_link_aggregated_nodes + ); + let mut mc_data = self.mc_data.lock().unwrap(); + mc_data.set_max_nodes(max_nodes, max_link_aggregated_nodes) + } +} + impl AsicOps for StubHandle { fn port_get_media(&self, port_hdl: PortHdl) -> AsicResult { ports::get_media(self, port_hdl) @@ -161,74 +234,6 @@ impl AsicOps for StubHandle { } } - #[cfg(feature = "multicast")] - fn mc_domains(&self) -> Vec { - let mc_data = self.mc_data.lock().unwrap(); - mc_data.domains() - } - #[cfg(feature = "multicast")] - fn mc_port_count(&self, group_id: u16) -> AsicResult { - let mc_data = self.mc_data.lock().unwrap(); - mc_data.domain_port_count(group_id) - } - #[cfg(feature = "multicast")] - fn mc_port_add( - &self, - group_id: u16, - port: u16, - rid: u16, - level1_excl_id: u16, - ) -> AsicResult<()> { - info!(self.log, "adding port {} to multicast group {}", port, group_id); - let mut mc_data = self.mc_data.lock().unwrap(); - mc_data.domain_port_add(group_id, port, rid, level1_excl_id) - } - #[cfg(feature = "multicast")] - fn mc_port_remove(&self, group_id: u16, port: u16) -> AsicResult<()> { - info!( - self.log, - "remvoing port {} from multicast group {}", port, group_id - ); - let mut mc_data = self.mc_data.lock().unwrap(); - mc_data.domain_port_remove(group_id, port) - } - #[cfg(feature = "multicast")] - fn mc_group_create(&self, group_id: u16) -> AsicResult<()> { - info!(self.log, "creating multicast group {}", group_id); - let mut mc_data = self.mc_data.lock().unwrap(); - mc_data.domain_create(group_id) - } - - #[cfg(feature = "multicast")] - fn mc_group_destroy(&self, group_id: u16) -> AsicResult<()> { - info!(self.log, "destroying multicast group {}", group_id); - let mut mc_data = self.mc_data.lock().unwrap(); - mc_data.domain_destroy(group_id) - } - - #[cfg(feature = "multicast")] - fn mc_groups_count(&self) -> AsicResult { - info!(self.log, "number of multicast groups"); - let mc_data = self.mc_data.lock().unwrap(); - Ok(mc_data.domains().len()) - } - - #[cfg(feature = "multicast")] - fn mc_set_max_nodes( - &self, - max_nodes: u32, - max_link_aggregated_nodes: u32, - ) -> AsicResult<()> { - info!( - self.log, - "setting max nodes to {} and max link aggregated nodes to {}", - max_nodes, - max_link_aggregated_nodes - ); - let mut mc_data = self.mc_data.lock().unwrap(); - mc_data.set_max_nodes(max_nodes, max_link_aggregated_nodes) - } - fn get_sidecar_identifiers(&self) -> AsicResult { Ok(Identifiers { id: uuid::Uuid::parse_str(SIDECAR_UUID).unwrap(), diff --git a/dpd-client/Cargo.toml b/dpd-client/Cargo.toml index f7246eb5..aadd5a80 100644 --- a/dpd-client/Cargo.toml +++ b/dpd-client/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" description = "Client library for the Dendrite data plane daemon" [features] -multicast = [] +multicast = ["asic/multicast"] chaos = ["asic/chaos"] tofino_asic = ["asic/tofino_asic"] diff --git a/dpd-client/tests/integration_tests/common.rs b/dpd-client/tests/integration_tests/common.rs index b7848a6d..3591651f 100644 --- a/dpd-client/tests/integration_tests/common.rs +++ b/dpd-client/tests/integration_tests/common.rs @@ -921,7 +921,7 @@ pub fn gen_udp_routed_pair( #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum OxideGeneveOption { External, - #[allow(unused)] + #[cfg(feature = "multicast")] Multicast(u8), Mss(u32), } @@ -1002,6 +1002,7 @@ pub fn gen_geneve_packet( 0x00, ]); } + #[cfg(feature = "multicast")] OxideGeneveOption::Multicast(tag) if *tag < 3 => { #[rustfmt::skip] geneve.options.extend_from_slice(&[ @@ -1036,6 +1037,7 @@ pub fn gen_geneve_packet( ]); geneve.options.extend_from_slice(&mss.to_be_bytes()[..]); } + #[cfg(feature = "multicast")] _ => { panic!("illegal specification for option: {opt:?}") } diff --git a/dpd/Cargo.toml b/dpd/Cargo.toml index 74c5430e..31e8a9cb 100644 --- a/dpd/Cargo.toml +++ b/dpd/Cargo.toml @@ -5,7 +5,7 @@ authors = ["nils "] edition = "2024" [features] -multicast = [] +multicast = ["aal/multicast", "asic/multicast"] tofino_asic = ["asic/tofino_asic"] tofino_stub = ["asic/tofino_stub"] softnpu = ["asic/softnpu"] diff --git a/dpd/p4/sidecar.p4 b/dpd/p4/sidecar.p4 index ceab1690..0ac7ab45 100644 --- a/dpd/p4/sidecar.p4 +++ b/dpd/p4/sidecar.p4 @@ -108,7 +108,7 @@ control Filter( #ifdef MULTICAST Counter, PortId_t>(512, CounterType_t.PACKETS) drop_mcast_ctr; bit<16> mcast_scope; -#endif /* MULITCAST */ +#endif /* MULTICAST */ action dropv4() { meta.drop_reason = DROP_IPV4_SWITCH_ADDR_MISS; diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index d31d7b2b..d5cea0cb 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -90,6 +90,33 @@ fn client_error(message: impl ToString) -> HttpError { ) } +// Generate a 501 client error with the provided message. +#[cfg(not(feature = "multicast"))] +fn not_implemented(message: impl ToString) -> HttpError { + HttpError { + status_code: dropshot::ErrorStatusCode::NOT_IMPLEMENTED.into(), + error_code: None, + external_message: message.to_string(), + internal_message: message.to_string(), + headers: None, + } +} + +// When the multicast feature is enabled, run the provided code. If +// multicast is no enabled, return 501 Not Implemented to the caller. +macro_rules! require_multicast { + ($body:expr) => {{ + #[cfg(feature = "multicast")] + { + $body + } + #[cfg(not(feature = "multicast"))] + { + Err(not_implemented("multicast feature disabled")) + } + }}; +} + pub enum DpdApiImpl {} impl DpdApi for DpdApiImpl { @@ -1958,17 +1985,14 @@ impl DpdApi for DpdApiImpl { group: TypedBody, ) -> Result, HttpError> { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let entry = group.into_inner(); mcast::add_group_external(switch, entry) .map(HttpResponseCreated) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] @@ -1977,17 +2001,14 @@ impl DpdApi for DpdApiImpl { group: TypedBody, ) -> Result, HttpError> { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let entry = group.into_inner(); mcast::add_group_internal(switch, entry) .map(HttpResponseCreated) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] @@ -1995,33 +2016,27 @@ impl DpdApi for DpdApiImpl { rqctx: RequestContext>, path: Path, ) -> Result { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let ip = path.into_inner().group_ip; mcast::del_group(switch, ip) .map(|_| HttpResponseDeleted()) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] async fn multicast_reset( rqctx: RequestContext>, ) -> Result { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); mcast::reset(switch) .map(|_| HttpResponseDeleted()) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] @@ -2029,8 +2044,7 @@ impl DpdApi for DpdApiImpl { rqctx: RequestContext>, path: Path, ) -> Result, HttpError> { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let ip = path.into_inner().group_ip; @@ -2038,9 +2052,7 @@ impl DpdApi for DpdApiImpl { mcast::get_group(switch, ip) .map(HttpResponseOk) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] @@ -2049,8 +2061,7 @@ impl DpdApi for DpdApiImpl { path: Path, group: TypedBody, ) -> Result, HttpError> { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let admin_scoped = path.into_inner().group_ip; @@ -2061,9 +2072,7 @@ impl DpdApi for DpdApiImpl { ) .map(HttpResponseOk) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] @@ -2071,17 +2080,14 @@ impl DpdApi for DpdApiImpl { rqctx: RequestContext>, path: Path, ) -> Result, HttpError> { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let admin_scoped = path.into_inner().group_ip; mcast::get_group_internal(switch, admin_scoped) .map(HttpResponseOk) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] @@ -2091,8 +2097,7 @@ impl DpdApi for DpdApiImpl { group: TypedBody, ) -> Result, HttpError> { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let entry = group.into_inner(); let ip = path.into_inner().group_ip; @@ -2100,9 +2105,7 @@ impl DpdApi for DpdApiImpl { mcast::modify_group_external(switch, ip, entry) .map(HttpResponseCreated) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] @@ -2113,8 +2116,7 @@ impl DpdApi for DpdApiImpl { >, ) -> Result>, HttpError> { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let pag_params = query_params.into_inner(); @@ -2143,9 +2145,7 @@ impl DpdApi for DpdApiImpl { group_ip: e.ip(), }, )?)) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] @@ -2157,8 +2157,7 @@ impl DpdApi for DpdApiImpl { >, ) -> Result>, HttpError> { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let tag = path.into_inner().tag; @@ -2188,9 +2187,7 @@ impl DpdApi for DpdApiImpl { group_ip: e.ip(), }, )?)) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] @@ -2198,33 +2195,27 @@ impl DpdApi for DpdApiImpl { rqctx: RequestContext>, path: Path, ) -> Result { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); let tag = path.into_inner().tag; mcast::reset_tag(switch, &tag) .map(|_| HttpResponseDeleted()) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[allow(unused_variables)] async fn multicast_reset_untagged( rqctx: RequestContext>, ) -> Result { - #[cfg(feature = "multicast")] - { + require_multicast!({ let switch: &Switch = rqctx.context(); mcast::reset_untagged(switch) .map(|_| HttpResponseDeleted()) .map_err(HttpError::from) - } - #[cfg(not(feature = "multicast"))] - Err(client_error("multicast feature disabled")) + }) } #[cfg(feature = "tofino_asic")] diff --git a/dpd/src/counters.rs b/dpd/src/counters.rs index 3c342245..4a0006eb 100644 --- a/dpd/src/counters.rs +++ b/dpd/src/counters.rs @@ -93,12 +93,7 @@ struct CounterDescription { p4_name: &'static str, } -#[cfg(feature = "multicast")] -const COUNTERS_COUNT: usize = 14; -#[cfg(not(feature = "multicast"))] -const COUNTERS_COUNT: usize = 6; - -const COUNTERS: [CounterDescription; COUNTERS_COUNT] = [ +const BASE_COUNTERS: [CounterDescription; 6] = [ CounterDescription { id: CounterId::Service, client_name: "Service", @@ -129,49 +124,47 @@ const COUNTERS: [CounterDescription; COUNTERS_COUNT] = [ client_name: "Ingress_Drop_Reason", p4_name: "pipe.Ingress.drop_reason_ctr", }, - #[cfg(feature = "multicast")] +]; + +#[cfg(not(feature = "multicast"))] +const MULTICAST_COUNTERS: [CounterDescription; 0] = []; +#[cfg(feature = "multicast")] +const MULTICAST_COUNTERS: [CounterDescription; 8] = [ CounterDescription { id: CounterId::EgressDropPort, client_name: "Egress_Drop_Port", p4_name: "pipe.Egress.drop_port_ctr", }, - #[cfg(feature = "multicast")] CounterDescription { id: CounterId::EgressDropReason, client_name: "Egress_Drop_Reason", p4_name: "pipe.Egress.drop_reason_ctr", }, - #[cfg(feature = "multicast")] CounterDescription { id: CounterId::Unicast, client_name: "Unicast", p4_name: "pipe.Egress.unicast_ctr", }, - #[cfg(feature = "multicast")] CounterDescription { id: CounterId::Multicast, client_name: "Multicast", p4_name: "pipe.Egress.mcast_ctr", }, - #[cfg(feature = "multicast")] CounterDescription { id: CounterId::MulticastExt, client_name: "Multicast_External", p4_name: "pipe.Egress.external_mcast_ctr", }, - #[cfg(feature = "multicast")] CounterDescription { id: CounterId::MulticastLL, client_name: "Multicast_Link_Local", p4_name: "pipe.Egress.link_local_mcast_ctr", }, - #[cfg(feature = "multicast")] CounterDescription { id: CounterId::MulticastUL, client_name: "Multicast_Underlay", p4_name: "pipe.Egress.underlay_mcast_ctr", }, - #[cfg(feature = "multicast")] CounterDescription { id: CounterId::MulticastDrop, client_name: "Multicast_Drop", @@ -181,7 +174,11 @@ const COUNTERS: [CounterDescription; COUNTERS_COUNT] = [ /// Get the list of names by which end users can refer to a counter. pub fn get_counter_names() -> DpdResult> { - Ok(COUNTERS.iter().map(|c| c.client_name.to_string()).collect()) + Ok(BASE_COUNTERS + .iter() + .chain(MULTICAST_COUNTERS.iter()) + .map(|c| c.client_name.to_string()) + .collect()) } /// Fetch a counter by name from the switch's list of counters. This call @@ -480,7 +477,8 @@ pub fn reset(switch: &Switch, counter_name: String) -> DpdResult<()> { /// Create internal structures for managing the counters built into sidecar.p4 pub fn init(hdl: &Handle) -> anyhow::Result>> { let mut counters = BTreeMap::new(); - for c in COUNTERS { + + for c in BASE_COUNTERS.iter().chain(MULTICAST_COUNTERS.iter()) { counters.insert( c.client_name.to_string().to_lowercase(), Mutex::new(Counter { diff --git a/dpd/src/link.rs b/dpd/src/link.rs index 2ffe0213..3fe92b78 100644 --- a/dpd/src/link.rs +++ b/dpd/src/link.rs @@ -1559,6 +1559,36 @@ pub async fn init_update_handler(switch: &Arc) -> AsicResult<()> { Ok(()) } +fn set_mac_config( + switch: &Switch, + asic_id: AsicId, + mac: MacAddr, +) -> DpdResult<()> { + MacOps::::mac_set(switch, asic_id, mac)?; + + #[cfg(feature = "multicast")] + { + MacOps::::mac_set( + switch, asic_id, mac, + )?; + mcast::mcast_egress::add_port_mapping_entry(switch, asic_id)?; + } + Ok(()) +} + +fn clear_mac_config(switch: &Switch, asic_id: AsicId) -> DpdResult<()> { + MacOps::::mac_clear(switch, asic_id)?; + + #[cfg(feature = "multicast")] + { + MacOps::::mac_clear( + switch, asic_id, + )?; + mcast::mcast_egress::del_port_mapping_entry(switch, asic_id)?; + } + Ok(()) +} + fn unplumb_link( switch: &Switch, log: &slog::Logger, @@ -1577,34 +1607,7 @@ fn unplumb_link( } if link.plumbed.mac.is_some() { - let mut err = None; - if let Err(e) = MacOps::::mac_clear( - switch, - link.asic_port_id, - ) { - err = Some(e); - } - #[cfg(feature = "multicast")] - { - if let Err(e) = - MacOps::::mac_clear( - switch, - link.asic_port_id, - ) - { - err = Some(e); - } else { - // We tie this in here as ports and macs are 1:1 - if let Err(e) = mcast::mcast_egress::del_port_mapping_entry( - switch, - link.asic_port_id, - ) { - err = Some(e); - } - } - } - - if let Some(e) = err { + if let Err(e) = clear_mac_config(switch, link.asic_port_id) { error!(log, "Failed to clear mac address and port mapping: {e:?}"); return Err(e); } @@ -1870,31 +1873,7 @@ async fn reconcile_link( link.config.mac, link.plumbed.mac.unwrap() ); - let mut err = None; - if let Err(e) = - MacOps::::mac_clear(switch, asic_id) - { - err = Some(e); - } - #[cfg(feature = "multicast")] - { - if let Err(e) = - MacOps::::mac_clear( - switch, asic_id, - ) - { - err = Some(e); - } else { - // We tie this in here as ports and macs are 1:1 - if let Err(e) = - mcast::mcast_egress::del_port_mapping_entry(switch, asic_id) - { - err = Some(e); - } - } - } - - if let Some(e) = err { + if let Err(e) = clear_mac_config(switch, asic_id) { record_plumb_failure( switch, &mut link, @@ -1910,35 +1889,7 @@ async fn reconcile_link( if link.plumbed.mac.is_none() { debug!(log, "Programming mac {}", link.config.mac); - let mut err = None; - if let Err(e) = MacOps::::mac_set( - switch, - asic_id, - link.config.mac, - ) { - err = Some(e); - } - - #[cfg(feature = "multicast")] - { - if let Err(e) = - MacOps::::mac_set( - switch, - asic_id, - link.config.mac, - ) - { - err = Some(e); - } else { - // We tie this in here as ports and macs are 1:1 - if let Err(e) = - mcast::mcast_egress::add_port_mapping_entry(switch, asic_id) - { - err = Some(e); - } - } - } - if let Some(e) = err { + if let Err(e) = set_mac_config(switch, asic_id, link.config.mac) { record_plumb_failure( switch, &mut link, diff --git a/dpd/src/mcast/mod.rs b/dpd/src/mcast/mod.rs index 9e8a5184..903b7133 100644 --- a/dpd/src/mcast/mod.rs +++ b/dpd/src/mcast/mod.rs @@ -59,7 +59,7 @@ use std::{ sync::{Arc, Mutex, Weak}, }; -use aal::{AsicError, AsicOps}; +use aal::{AsicError, AsicMulticastOps}; use common::{network::NatTarget, ports::PortId}; use dpd_types::{ link::LinkId, diff --git a/dpd/src/mcast/rollback.rs b/dpd/src/mcast/rollback.rs index 54c5330f..9b139fbe 100644 --- a/dpd/src/mcast/rollback.rs +++ b/dpd/src/mcast/rollback.rs @@ -13,7 +13,7 @@ use std::{fmt, net::IpAddr}; -use aal::AsicOps; +use aal::AsicMulticastOps; use oxnet::Ipv4Net; use slog::{debug, error}; diff --git a/dpd/src/table/mod.rs b/dpd/src/table/mod.rs index 91a47b4d..92912fb6 100644 --- a/dpd/src/table/mod.rs +++ b/dpd/src/table/mod.rs @@ -30,8 +30,7 @@ pub mod port_nat; pub mod route_ipv4; pub mod route_ipv6; -#[cfg(feature = "multicast")] -const NAME_TO_TYPE: [(&str, TableType); 24] = [ +const BASE_TABLES: [(&str, TableType); 14] = [ (route_ipv4::INDEX_TABLE_NAME, TableType::RouteIdxIpv4), (route_ipv4::FORWARD_TABLE_NAME, TableType::RouteFwdIpv4), (route_ipv6::INDEX_TABLE_NAME, TableType::RouteIdxIpv6), @@ -52,6 +51,10 @@ const NAME_TO_TYPE: [(&str, TableType); 24] = [ attached_subnet_v6::EXT_SUBNET_IPV6_TABLE_NAME, TableType::AttachedSubnetIpv6, ), +]; + +#[cfg(feature = "multicast")] +const MCAST_TABLES: [(&str, TableType); 10] = [ (mcast::mcast_replication::IPV6_TABLE_NAME, TableType::McastIpv6), (mcast::mcast_src_filter::IPV4_TABLE_NAME, TableType::McastIpv4SrcFilter), (mcast::mcast_src_filter::IPV6_TABLE_NAME, TableType::McastIpv6SrcFilter), @@ -69,29 +72,9 @@ const NAME_TO_TYPE: [(&str, TableType); 24] = [ TableType::McastEgressPortMapping, ), ]; + #[cfg(not(feature = "multicast"))] -const NAME_TO_TYPE: [(&str, TableType); 14] = [ - (route_ipv4::INDEX_TABLE_NAME, TableType::RouteIdxIpv4), - (route_ipv4::FORWARD_TABLE_NAME, TableType::RouteFwdIpv4), - (route_ipv6::INDEX_TABLE_NAME, TableType::RouteIdxIpv6), - (route_ipv6::FORWARD_TABLE_NAME, TableType::RouteFwdIpv6), - (arp_ipv4::TABLE_NAME, TableType::ArpIpv4), - (neighbor_ipv6::TABLE_NAME, TableType::NeighborIpv6), - (port_mac::TABLE_NAME, TableType::PortMac), - (port_ip::IPV4_TABLE_NAME, TableType::PortIpv4), - (port_ip::IPV6_TABLE_NAME, TableType::PortIpv6), - (nat::IPV4_TABLE_NAME, TableType::NatIngressIpv4), - (nat::IPV6_TABLE_NAME, TableType::NatIngressIpv6), - (port_nat::TABLE_NAME, TableType::NatOnly), - ( - attached_subnet_v4::EXT_SUBNET_IPV4_TABLE_NAME, - TableType::AttachedSubnetIpv4, - ), - ( - attached_subnet_v6::EXT_SUBNET_IPV6_TABLE_NAME, - TableType::AttachedSubnetIpv6, - ), -]; +const MCAST_TABLES: [(&str, TableType); 0] = []; /// Basic statistics about p4 table usage #[derive(Clone, Debug, Default)] @@ -459,26 +442,29 @@ pub enum TableType { McastEgressPortMapping, } +fn name_to_type() -> impl Iterator { + BASE_TABLES.iter().chain(MCAST_TABLES.iter()) +} + impl TryFrom<&str> for TableType { type Error = DpdError; fn try_from(name: &str) -> Result { let name = name.to_lowercase(); - for (table_name, table_type) in NAME_TO_TYPE { + for (table_name, table_type) in name_to_type() { if table_name.to_lowercase() == name { - return Ok(table_type); + return Ok(*table_type); } } - - Err(DpdError::NoSuchTable(name.to_string())) + Err(DpdError::NoSuchTable(name)) } } pub fn init(switch: &mut Switch) -> anyhow::Result<()> { debug!(switch.log, "initializing tables"); - for (name, table_type) in NAME_TO_TYPE { - switch.table_add(name, table_type)?; + for (name, table_type) in name_to_type() { + switch.table_add(name, *table_type)?; } Ok(()) diff --git a/swadm/tests/counters.rs b/swadm/tests/counters.rs index 93eea9d9..d72ad170 100644 --- a/swadm/tests/counters.rs +++ b/swadm/tests/counters.rs @@ -38,33 +38,32 @@ fn test_p4_counter_list() { assert!(!stdout.is_empty(), "Counter list output should not be empty"); // Expected P4 counters from dpd/src/counters.rs COUNTERS array - let expected_counters = [ + let base_counters = vec![ "Service", "Ingress", "Packet", "Egress", "Ingress_Drop_Port", "Ingress_Drop_Reason", - #[cfg(feature = "multicast")] + ]; + + #[cfg(not(feature = "multicast"))] + let multicast_counters = Vec::new(); + + #[cfg(feature = "multicast")] + let multicast_counters = vec![ "Egress_Drop_Port", - #[cfg(feature = "multicast")] "Egress_Drop_Reason", - #[cfg(feature = "multicast")] "Unicast", - #[cfg(feature = "multicast")] "Multicast", - #[cfg(feature = "multicast")] "Multicast_External", - #[cfg(feature = "multicast")] "Multicast_Link_Local", - #[cfg(feature = "multicast")] "Multicast_Underlay", - #[cfg(feature = "multicast")] "Multicast_Drop", ]; // Verify all expected counters are present in the output - for counter in &expected_counters { + for counter in base_counters.iter().chain(multicast_counters.iter()) { assert!( stdout.contains(counter), "Counter list should contain '{counter}' counter. Output: {stdout}" From d087c3f35e8da7d73058516eac59f460b930588b Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Wed, 28 Jan 2026 19:48:55 +0000 Subject: [PATCH 09/11] clippy --- dpd/src/api_server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index d5cea0cb..34fa6fa3 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -94,7 +94,7 @@ fn client_error(message: impl ToString) -> HttpError { #[cfg(not(feature = "multicast"))] fn not_implemented(message: impl ToString) -> HttpError { HttpError { - status_code: dropshot::ErrorStatusCode::NOT_IMPLEMENTED.into(), + status_code: dropshot::ErrorStatusCode::NOT_IMPLEMENTED, error_code: None, external_message: message.to_string(), internal_message: message.to_string(), From 4fa9d64d74c6442b1271cc3d7f4903fc187105e4 Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Wed, 28 Jan 2026 20:16:32 +0000 Subject: [PATCH 10/11] feature prop --- .github/buildomat/packet-test-common.sh | 2 +- asic/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/buildomat/packet-test-common.sh b/.github/buildomat/packet-test-common.sh index c1dce612..9ca7b310 100755 --- a/.github/buildomat/packet-test-common.sh +++ b/.github/buildomat/packet-test-common.sh @@ -79,7 +79,7 @@ set +o errexit SLEEP_TIME=5 iters=$(( $STARTUP_TIMEOUT / $SLEEP_TIME )) while [ 1 ] ; do - ./target/debug/swadm build-info 2> /dev/null + ./target/debug/swadm --host '[::1]' build-info 2> /dev/null if [ $? == 0 ]; then break fi diff --git a/asic/Cargo.toml b/asic/Cargo.toml index 8f0ec66d..39dc3cbe 100644 --- a/asic/Cargo.toml +++ b/asic/Cargo.toml @@ -13,7 +13,7 @@ tofino_asic = [ tofino_stub = [] softnpu = ["softnpu-lib", "dep:propolis"] chaos = [] -multicast = [] +multicast = ["aal/multicast"] [lib] # The genpd.rs code generated by bindgen causes the doctest to fail From 7d2e9610f09d5be1a6c7acb455742568556f3894 Mon Sep 17 00:00:00 2001 From: Nils Nieuwejaar Date: Thu, 29 Jan 2026 10:18:35 -0500 Subject: [PATCH 11/11] swadm features --- .github/buildomat/packet-test-common.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/buildomat/packet-test-common.sh b/.github/buildomat/packet-test-common.sh index 9ca7b310..bf6b2b47 100755 --- a/.github/buildomat/packet-test-common.sh +++ b/.github/buildomat/packet-test-common.sh @@ -15,7 +15,7 @@ if [ x$MULTICAST == x ]; then else BUILD_FEATURES=tofino_asic,multicast CODEGEN_FEATURES=--multicast - SWADM_FEATURES=multicast + SWADM_FEATURES=--features=multicast fi function cleanup { @@ -104,7 +104,7 @@ DENDRITE_TEST_HOST='[::1]' \ DENDRITE_TEST_VERBOSITY=3 \ cargo test \ --no-fail-fast \ - --features=$SWADM_FEATURES \ + $SWADM_FEATURES \ --test \ counters \ -- \