From f850be31b3d1f67a178c64e9fd36643bab392096 Mon Sep 17 00:00:00 2001 From: Kevin Ji <1146876+kevinji@users.noreply.github.com> Date: Thu, 28 Mar 2024 15:42:32 -0700 Subject: [PATCH] integration-test: Use veth pair instead of lo --- test/integration-test/src/tests/load.rs | 35 ++++-- test/integration-test/src/tests/smoke.rs | 23 +++- test/integration-test/src/tests/xdp.rs | 44 ++++--- test/integration-test/src/utils.rs | 148 +++++++++++++++++++---- 4 files changed, 193 insertions(+), 57 deletions(-) diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index ffc8ddf74..e4b490758 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -18,11 +18,15 @@ use aya::{ use aya_obj::programs::XdpAttachType; use test_log::test; +use crate::utils::NetNsGuard; + const MAX_RETRIES: usize = 100; const RETRY_DURATION: Duration = Duration::from_millis(10); #[test] fn long_name() { + let netns = NetNsGuard::new(); + let mut bpf = Ebpf::load(crate::NAME_TEST).unwrap(); let name_prog: &mut Xdp = bpf .program_mut("ihaveaverylongname") @@ -30,7 +34,9 @@ fn long_name() { .try_into() .unwrap(); name_prog.load().unwrap(); - name_prog.attach("lo", XdpFlags::default()).unwrap(); + name_prog + .attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); // We used to be able to assert with bpftool that the program name was short. // It seem though that it now uses the name from the ELF symbol table instead. @@ -198,11 +204,15 @@ fn assert_unloaded(name: &str) { #[test] fn unload_xdp() { + let netns = NetNsGuard::new(); + let mut bpf = Ebpf::load(crate::TEST).unwrap(); let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); prog.load().unwrap(); assert_loaded("pass"); - let link = prog.attach("lo", XdpFlags::default()).unwrap(); + let link = prog + .attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); { let _link_owned = prog.take_link(link).unwrap(); prog.unload().unwrap(); @@ -213,7 +223,8 @@ fn unload_xdp() { prog.load().unwrap(); assert_loaded("pass"); - prog.attach("lo", XdpFlags::default()).unwrap(); + prog.attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); assert_loaded("pass"); prog.unload().unwrap(); @@ -351,15 +362,19 @@ fn pin_link() { return; } + let netns = NetNsGuard::new(); + let mut bpf = Ebpf::load(crate::TEST).unwrap(); let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); prog.load().unwrap(); - let link_id = prog.attach("lo", XdpFlags::default()).unwrap(); + let link_id = prog + .attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); let link = prog.take_link(link_id).unwrap(); assert_loaded("pass"); let fd_link: FdLink = link.try_into().unwrap(); - let pinned = fd_link.pin("/sys/fs/bpf/aya-xdp-test-lo").unwrap(); + let pinned = fd_link.pin("/sys/fs/bpf/aya-xdp-test-veth-pair").unwrap(); // because of the pin, the program is still attached prog.unload().unwrap(); @@ -382,6 +397,8 @@ fn pin_lifecycle() { return; } + let netns = NetNsGuard::new(); + // 1. Load Program and Pin { let mut bpf = Ebpf::load(crate::PASS).unwrap(); @@ -405,10 +422,12 @@ fn pin_lifecycle() { { let mut prog = Xdp::from_pin("/sys/fs/bpf/aya-xdp-test-prog", XdpAttachType::Interface).unwrap(); - let link_id = prog.attach("lo", XdpFlags::default()).unwrap(); + let link_id = prog + .attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); let link = prog.take_link(link_id).unwrap(); let fd_link: FdLink = link.try_into().unwrap(); - fd_link.pin("/sys/fs/bpf/aya-xdp-test-lo").unwrap(); + fd_link.pin("/sys/fs/bpf/aya-xdp-test-veth-pair").unwrap(); // Unpin the program. It will stay attached since its links were pinned. prog.unpin().unwrap(); @@ -423,7 +442,7 @@ fn pin_lifecycle() { let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); prog.load().unwrap(); - let link = PinnedLink::from_pin("/sys/fs/bpf/aya-xdp-test-lo") + let link = PinnedLink::from_pin("/sys/fs/bpf/aya-xdp-test-veth-pair") .unwrap() .unpin() .unwrap(); diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index 708b889ba..a4dd98926 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -16,12 +16,14 @@ fn xdp() { return; } - let _netns = NetNsGuard::new(); + let netns = NetNsGuard::new(); let mut bpf = Ebpf::load(crate::PASS).unwrap(); let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); dispatcher.load().unwrap(); - dispatcher.attach("lo", XdpFlags::default()).unwrap(); + dispatcher + .attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); } #[test] @@ -54,12 +56,13 @@ fn extension() { return; } - let _netns = NetNsGuard::new(); + let netns = NetNsGuard::new(); let mut bpf = Ebpf::load(crate::MAIN).unwrap(); let pass: &mut Xdp = bpf.program_mut("xdp_pass").unwrap().try_into().unwrap(); pass.load().unwrap(); - pass.attach("lo", XdpFlags::default()).unwrap(); + pass.attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); let mut bpf = EbpfLoader::new() .extension("xdp_drop") @@ -73,11 +76,15 @@ fn extension() { #[test] fn list_loaded_programs() { + let netns = NetNsGuard::new(); + // Load a program. let mut bpf = Ebpf::load(crate::PASS).unwrap(); let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); dispatcher.load().unwrap(); - dispatcher.attach("lo", XdpFlags::default()).unwrap(); + dispatcher + .attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); // Ensure the loaded_programs() api doesn't panic. let prog = loaded_programs() @@ -102,11 +109,15 @@ fn list_loaded_programs() { #[test] fn list_loaded_maps() { + let netns = NetNsGuard::new(); + // Load a program with maps. let mut bpf = Ebpf::load(crate::MAP_TEST).unwrap(); let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); dispatcher.load().unwrap(); - dispatcher.attach("lo", XdpFlags::default()).unwrap(); + dispatcher + .attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); // Ensure the loaded_maps() api doesn't panic and retrieve a map. let map = loaded_maps() diff --git a/test/integration-test/src/tests/xdp.rs b/test/integration-test/src/tests/xdp.rs index fb137d898..322a3f52b 100644 --- a/test/integration-test/src/tests/xdp.rs +++ b/test/integration-test/src/tests/xdp.rs @@ -1,4 +1,4 @@ -use std::{ffi::CStr, mem::MaybeUninit, net::UdpSocket, num::NonZeroU32, time::Duration}; +use std::{mem::MaybeUninit, net::UdpSocket, num::NonZeroU32, time::Duration}; use aya::{ maps::{Array, CpuMap, XskMap}, @@ -9,11 +9,11 @@ use object::{Object, ObjectSection, ObjectSymbol, SymbolSection}; use test_log::test; use xdpilone::{BufIdx, IfInfo, Socket, SocketConfig, Umem, UmemConfig}; -use crate::utils::NetNsGuard; +use crate::utils::{NetNsGuard, IP_ADDR_1, IP_ADDR_2}; #[test] fn af_xdp() { - let _netns = NetNsGuard::new(); + let netns = NetNsGuard::new(); let mut bpf = Ebpf::load(crate::REDIRECT).unwrap(); let mut socks: XskMap<_> = bpf.take_map("SOCKS").unwrap().try_into().unwrap(); @@ -24,7 +24,8 @@ fn af_xdp() { .try_into() .unwrap(); xdp.load().unwrap(); - xdp.attach("lo", XdpFlags::default()).unwrap(); + xdp.attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); // So this needs to be page aligned. Pages are 4k on all mainstream architectures except for // Apple Silicon which uses 16k pages. So let's align on that for tests to run natively there. @@ -41,9 +42,7 @@ fn af_xdp() { }; let mut iface = IfInfo::invalid(); - iface - .from_name(CStr::from_bytes_with_nul(b"lo\0").unwrap()) - .unwrap(); + iface.from_ifindex(netns.if_idx1).unwrap(); let sock = Socket::with_shared(&iface, &umem).unwrap(); let mut fq_cq = umem.fq_cq(&sock).unwrap(); // Fill Queue / Completion Queue @@ -66,9 +65,12 @@ fn af_xdp() { writer.insert_once(frame.offset); writer.commit(); - let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); - let port = sock.local_addr().unwrap().port(); - sock.send_to(b"hello AF_XDP", "127.0.0.1:1777").unwrap(); + let sock = UdpSocket::bind((IP_ADDR_1, 0)).unwrap(); + let src_port = sock.local_addr().unwrap().port(); + + const DST_PORT: u16 = 1777; + sock.send_to(b"hello AF_XDP", (IP_ADDR_2, DST_PORT)) + .unwrap(); assert_eq!(rx.available(), 1); let desc = rx.receive(1).read().unwrap(); @@ -81,8 +83,8 @@ fn af_xdp() { let (ip, buf) = buf.split_at(20); assert_eq!(ip[9], 17); // UDP let (udp, payload) = buf.split_at(8); - assert_eq!(&udp[0..2], port.to_be_bytes().as_slice()); // Source - assert_eq!(&udp[2..4], 1777u16.to_be_bytes().as_slice()); // Dest + assert_eq!(&udp[0..2], src_port.to_be_bytes().as_slice()); // Source + assert_eq!(&udp[2..4], DST_PORT.to_be_bytes().as_slice()); // Dest assert_eq!(payload, b"hello AF_XDP"); } @@ -134,7 +136,7 @@ fn map_load() { #[test] fn cpumap_chain() { - let _netns = NetNsGuard::new(); + let netns = NetNsGuard::new(); let mut bpf = Ebpf::load(crate::REDIRECT).unwrap(); @@ -157,20 +159,24 @@ fn cpumap_chain() { // Load the main program let xdp: &mut Xdp = bpf.program_mut("redirect_cpu").unwrap().try_into().unwrap(); xdp.load().unwrap(); - xdp.attach("lo", XdpFlags::default()).unwrap(); + xdp.attach_to_if_index(netns.if_idx2, XdpFlags::default()) + .unwrap(); const PAYLOAD: &str = "hello cpumap"; - let sock = UdpSocket::bind("127.0.0.1:0").unwrap(); - let addr = sock.local_addr().unwrap(); - sock.set_read_timeout(Some(Duration::from_secs(60))) + let sock1 = UdpSocket::bind((IP_ADDR_1, 0)).unwrap(); + let sock2 = UdpSocket::bind((IP_ADDR_2, 0)).unwrap(); + sock2 + .set_read_timeout(Some(Duration::from_secs(60))) + .unwrap(); + sock1 + .send_to(PAYLOAD.as_bytes(), sock2.local_addr().unwrap()) .unwrap(); - sock.send_to(PAYLOAD.as_bytes(), addr).unwrap(); // Read back the packet to ensure it went through the entire network stack, including our two // probes. let mut buf = [0u8; PAYLOAD.len() + 1]; - let n = sock.recv(&mut buf).unwrap(); + let n = sock2.recv(&mut buf).unwrap(); assert_eq!(&buf[..n], PAYLOAD.as_bytes()); assert_eq!(hits.get(&0, 0).unwrap(), 1); diff --git a/test/integration-test/src/utils.rs b/test/integration-test/src/utils.rs index 28b68d4aa..6766d5bcd 100644 --- a/test/integration-test/src/utils.rs +++ b/test/integration-test/src/utils.rs @@ -1,19 +1,42 @@ //! Utilities to run tests use std::{ - ffi::CString, - io, process, + ffi::CStr, + io, + net::Ipv4Addr, + process, sync::atomic::{AtomicU64, Ordering}, }; -use aya::netlink_set_link_up; +use aya::{ + netlink_add_ip_addr, netlink_add_veth_pair, netlink_delete_link, netlink_set_link_down, + netlink_set_link_up, +}; use libc::if_nametoindex; use netns_rs::{get_from_current_thread, NetNs}; +pub const IF_NAME_1: &CStr = c"aya-test-1"; +pub const IF_NAME_2: &CStr = c"aya-test-2"; +pub const IP_ADDR_1: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1); +pub const IP_ADDR_2: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 2); +pub const IP_PREFIX: u8 = 24; + +pub fn setup_test_veth_pair() { + unsafe { netlink_add_veth_pair(IF_NAME_1, IF_NAME_2) }.unwrap_or_else(|e| { + panic!( + "Failed to set up veth pair ({}, {}): {e}", + IF_NAME_1.to_string_lossy(), + IF_NAME_2.to_string_lossy() + ) + }) +} + pub struct NetNsGuard { name: String, old_ns: NetNs, ns: Option, + pub if_idx1: u32, + pub if_idx2: u32, } impl NetNsGuard { @@ -26,39 +49,116 @@ impl NetNsGuard { // Create and enter netns let ns = NetNs::new(&name).unwrap_or_else(|e| panic!("Failed to create netns {name}: {e}")); - let netns = Self { - old_ns, - ns: Some(ns), - name, - }; - let ns = netns.ns.as_ref().unwrap(); ns.enter() - .unwrap_or_else(|e| panic!("Failed to enter network namespace {}: {e}", netns.name)); - println!("Entered network namespace {}", netns.name); + .unwrap_or_else(|e| panic!("Failed to enter network namespace {}: {e}", name)); + println!("Entered network namespace {}", name); // By default, the loopback in a new netns is down. Set it up. - let lo = CString::new("lo").unwrap(); - unsafe { - let idx = if_nametoindex(lo.as_ptr()); - if idx == 0 { - panic!( - "Interface `lo` not found in netns {}: {}", - netns.name, - io::Error::last_os_error() - ); - } - netlink_set_link_up(idx as i32) - .unwrap_or_else(|e| panic!("Failed to set `lo` up in netns {}: {e}", netns.name)); + let lo_idx = unsafe { if_nametoindex(c"lo".as_ptr()) }; + if lo_idx == 0 { + panic!( + "Interface `lo` not found in netns {}: {}", + name, + io::Error::last_os_error() + ); + } + + unsafe { netlink_set_link_up(lo_idx as i32) } + .unwrap_or_else(|e| panic!("Failed to set `lo` up in netns {}: {e}", name)); + + setup_test_veth_pair(); + + let if_idx1 = unsafe { if_nametoindex(IF_NAME_1.as_ptr()) }; + if if_idx1 == 0 { + panic!( + "Interface `{}` not found in netns {}: {}", + IF_NAME_1.to_string_lossy(), + name, + io::Error::last_os_error() + ); + } + + let if_idx2 = unsafe { if_nametoindex(IF_NAME_2.as_ptr()) }; + if if_idx2 == 0 { + panic!( + "Interface `{}` not found in netns {}: {}", + IF_NAME_2.to_string_lossy(), + name, + io::Error::last_os_error() + ); } - netns + unsafe { netlink_add_ip_addr(if_idx1, IP_ADDR_1, IP_PREFIX) }.unwrap_or_else(|e| { + panic!( + "Failed to add IP `{}` to `{}` in netns {}: {e}", + IP_ADDR_1, + IF_NAME_1.to_string_lossy(), + name + ) + }); + + unsafe { netlink_add_ip_addr(if_idx2, IP_ADDR_2, IP_PREFIX) }.unwrap_or_else(|e| { + panic!( + "Failed to add IP `{}` to `{}` in netns {}: {e}", + IP_ADDR_2, + IF_NAME_2.to_string_lossy(), + name + ) + }); + + unsafe { netlink_set_link_up(if_idx1 as i32) }.unwrap_or_else(|e| { + panic!( + "Failed to set `{}` up in netns {}: {e}", + IF_NAME_1.to_string_lossy(), + name + ) + }); + + unsafe { netlink_set_link_up(if_idx2 as i32) }.unwrap_or_else(|e| { + panic!( + "Failed to set `{}` up in netns {}: {e}", + IF_NAME_2.to_string_lossy(), + name + ) + }); + + Self { + old_ns, + ns: Some(ns), + name, + if_idx1, + if_idx2, + } } } impl Drop for NetNsGuard { fn drop(&mut self) { // Avoid panic in panic + if let Err(e) = unsafe { netlink_set_link_down(self.if_idx1 as i32) } { + eprintln!( + "Failed to set `{}` down in netns {}: {e}", + IF_NAME_1.to_string_lossy(), + self.name + ) + } + if let Err(e) = unsafe { netlink_set_link_down(self.if_idx2 as i32) } { + eprintln!( + "Failed to set `{}` down in netns {}: {e}", + IF_NAME_2.to_string_lossy(), + self.name + ) + } + + if let Err(e) = unsafe { netlink_delete_link(self.if_idx1 as i32) } { + eprintln!( + "Failed to delete `{}` in netns {}: {e}", + IF_NAME_1.to_string_lossy(), + self.name + ) + } + if let Err(e) = self.old_ns.enter() { eprintln!("Failed to return to original netns: {e}"); }