diff --git a/src/node/tests/ethernet.rs b/src/node/tests/ethernet.rs deleted file mode 100644 index 227ef3a..0000000 --- a/src/node/tests/ethernet.rs +++ /dev/null @@ -1,214 +0,0 @@ -//! Ethernet transport integration tests. -//! -//! Tests that the Ethernet transport works end-to-end using veth pairs. -//! All tests require root or CAP_NET_RAW and are marked `#[ignore]`. - -use super::*; -use crate::config::EthernetConfig; -use crate::transport::ethernet::EthernetTransport; -use crate::transport::{TransportAddr, TransportHandle, TransportId, packet_channel}; -use spanning_tree::{TestNode, cleanup_nodes, drain_all_packets, initiate_handshake}; - -use std::process::Command; -use std::sync::atomic::{AtomicU32, Ordering}; - -/// Atomic counter for unique veth names across tests. -static VETH_COUNTER: AtomicU32 = AtomicU32::new(0); - -/// RAII wrapper for a veth pair. -/// -/// Creates a pair of connected virtual Ethernet interfaces. Destroying -/// one end automatically destroys the other. -struct VethPair { - name_a: String, - name_b: String, -} - -impl VethPair { - /// Create a new veth pair with unique interface names. - /// - /// Names are kept under 15 chars (IFNAMSIZ limit). Format: `ftXXa`/`ftXXb` - /// where XX is an atomic counter combined with PID for cross-process uniqueness. - fn create() -> Self { - let id = VETH_COUNTER.fetch_add(1, Ordering::Relaxed); - let pid = std::process::id() % 10000; - let name_a = format!("ft{}{}a", pid, id); - let name_b = format!("ft{}{}b", pid, id); - - assert!(name_a.len() <= 15, "veth name too long: {}", name_a); - assert!(name_b.len() <= 15, "veth name too long: {}", name_b); - - // Create veth pair - let status = Command::new("ip") - .args([ - "link", "add", &name_a, "type", "veth", "peer", "name", &name_b, - ]) - .status() - .expect("failed to run 'ip link add'"); - assert!(status.success(), "failed to create veth pair"); - - // Bring both ends up - let status = Command::new("ip") - .args(["link", "set", &name_a, "up"]) - .status() - .expect("failed to run 'ip link set up'"); - assert!(status.success(), "failed to bring up {}", name_a); - - let status = Command::new("ip") - .args(["link", "set", &name_b, "up"]) - .status() - .expect("failed to run 'ip link set up'"); - assert!(status.success(), "failed to bring up {}", name_b); - - VethPair { name_a, name_b } - } -} - -impl Drop for VethPair { - fn drop(&mut self) { - // Deleting one end destroys both - let _ = Command::new("ip") - .args(["link", "delete", &self.name_a]) - .status(); - } -} - -/// Create a test node with a live Ethernet transport on the given interface. -/// -/// Parallel to `make_test_node()` in spanning_tree.rs but uses -/// EthernetTransport instead of UDP. -async fn make_test_node_ethernet(interface: &str) -> TestNode { - let mut node = make_node(); - let transport_id = TransportId::new(1); - - let config = EthernetConfig { - interface: interface.to_string(), - discovery: Some(false), - announce: Some(false), - accept_connections: Some(true), - ..Default::default() - }; - - let (packet_tx, packet_rx) = packet_channel(256); - let mut transport = EthernetTransport::new(transport_id, None, config, packet_tx); - transport.start_async().await.unwrap(); - - let mac = transport - .local_mac() - .expect("transport should have MAC after start"); - let addr = TransportAddr::from_bytes(&mac); - - node.transports - .insert(transport_id, TransportHandle::Ethernet(transport)); - - TestNode { - node, - transport_id, - packet_rx: spanning_tree::bridge_to_unbounded(packet_rx), - addr, - } -} - -/// Two nodes on a veth pair complete a Noise handshake and establish peering. -#[tokio::test] -#[ignore] // Requires root or CAP_NET_RAW -async fn test_ethernet_two_node_handshake() { - let veth = VethPair::create(); - - let mut nodes = vec![ - make_test_node_ethernet(&veth.name_a).await, - make_test_node_ethernet(&veth.name_b).await, - ]; - - // Initiate handshake from node 0 to node 1 - initiate_handshake(&mut nodes, 0, 1).await; - - // Drain all packets (handshake + tree announce) - let total = drain_all_packets(&mut nodes, false).await; - assert!(total > 0, "should have processed packets"); - - // Verify bidirectional peering - let addr_0 = *nodes[0].node.node_addr(); - let addr_1 = *nodes[1].node.node_addr(); - assert!( - nodes[0].node.get_peer(&addr_1).is_some(), - "node 0 should have node 1 as peer" - ); - assert!( - nodes[1].node.get_peer(&addr_0).is_some(), - "node 1 should have node 0 as peer" - ); - - cleanup_nodes(&mut nodes).await; -} - -/// Two Ethernet nodes converge to a correct spanning tree (2-node tree). -#[tokio::test] -#[ignore] // Requires root or CAP_NET_RAW -async fn test_ethernet_data_exchange() { - use spanning_tree::verify_tree_convergence; - - let veth = VethPair::create(); - - let mut nodes = vec![ - make_test_node_ethernet(&veth.name_a).await, - make_test_node_ethernet(&veth.name_b).await, - ]; - - initiate_handshake(&mut nodes, 0, 1).await; - let total = drain_all_packets(&mut nodes, false).await; - assert!(total > 0); - - // Verify spanning tree convergence - verify_tree_convergence(&nodes); - - // The root should be the node with the smallest NodeAddr - let expected_root = std::cmp::min(*nodes[0].node.node_addr(), *nodes[1].node.node_addr()); - assert_eq!(*nodes[0].node.tree_state().root(), expected_root); - assert_eq!(*nodes[1].node.tree_state().root(), expected_root); - - cleanup_nodes(&mut nodes).await; -} - -/// Mixed transport: 2 Ethernet nodes + 2 UDP nodes coexist. -/// -/// Each transport forms its own connected component. Validates that -/// `process_available_packets()` handles heterogeneous transport types. -#[tokio::test] -#[ignore] // Requires root or CAP_NET_RAW -async fn test_mixed_transport_coexistence() { - use spanning_tree::{make_test_node, verify_tree_convergence_components}; - - let veth = VethPair::create(); - - // Create 2 Ethernet nodes and 2 UDP nodes - let eth_0 = make_test_node_ethernet(&veth.name_a).await; - let eth_1 = make_test_node_ethernet(&veth.name_b).await; - let udp_0 = make_test_node().await; - let udp_1 = make_test_node().await; - - let mut nodes = vec![eth_0, eth_1, udp_0, udp_1]; - - // Handshake within each component - initiate_handshake(&mut nodes, 0, 1).await; // Ethernet pair - initiate_handshake(&mut nodes, 2, 3).await; // UDP pair - - // Drain all packets across both transports - let total = drain_all_packets(&mut nodes, false).await; - assert!(total > 0); - - // Verify each component converges independently - verify_tree_convergence_components(&nodes, &[vec![0, 1], vec![2, 3]]); - - // Ethernet component has its own root - let eth_root = std::cmp::min(*nodes[0].node.node_addr(), *nodes[1].node.node_addr()); - assert_eq!(*nodes[0].node.tree_state().root(), eth_root); - assert_eq!(*nodes[1].node.tree_state().root(), eth_root); - - // UDP component has its own root - let udp_root = std::cmp::min(*nodes[2].node.node_addr(), *nodes[3].node.node_addr()); - assert_eq!(*nodes[2].node.tree_state().root(), udp_root); - assert_eq!(*nodes[3].node.tree_state().root(), udp_root); - - cleanup_nodes(&mut nodes).await; -} diff --git a/src/node/tests/mod.rs b/src/node/tests/mod.rs index c6484ec..8c00841 100644 --- a/src/node/tests/mod.rs +++ b/src/node/tests/mod.rs @@ -13,8 +13,6 @@ mod bootstrap; mod decrypt_failure; mod disconnect; mod discovery; -#[cfg(target_os = "linux")] -mod ethernet; mod forwarding; mod handshake; mod heartbeat; diff --git a/testing/chaos/scenarios/ethernet-only.yaml b/testing/chaos/scenarios/ethernet-only.yaml index 39a823b..1b11f2f 100644 --- a/testing/chaos/scenarios/ethernet-only.yaml +++ b/testing/chaos/scenarios/ethernet-only.yaml @@ -50,10 +50,11 @@ traffic: # A 4-node mesh forms a spanning tree: one root and three nodes # with a parent. All six provably-completed archived runs show exactly # that, so these are the shape of a converged mesh rather than a -# tolerance fitted to observations. This is currently the only -# assertion covering Ethernet transport anywhere in CI: the three -# tests in src/node/tests/ethernet.rs are #[ignore]d for requiring -# CAP_NET_RAW and neither runner passes --ignored. +# tolerance fitted to observations. This is the only assertion covering +# Ethernet transport anywhere in CI, and it covers the control plane +# only: traffic is disabled above, so no datagram crosses an Ethernet +# link in any test. Framing, the length field that trims NIC minimum- +# frame padding, and AEAD over Ethernet are all unexercised as a result. assertions: baseline: min_nodes_reporting: 4