#![cfg(all(feature = "afpacket", target_os = "linux"))]
use pktkit::afpacket::{Config, Socket};
use pktkit::{EtherType, Frame, L2Device, MacAddr, build_frame};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
const TEST_ETHERTYPE: u16 = 0x88B5; const MAGIC: &[u8] = b"pktkit-afpacket-loopback";
fn open_lo(inbound_only: bool) -> Socket_ {
match Socket::open(Config {
interface: "lo".into(),
inbound_only,
..Default::default()
}) {
Ok(s) => s,
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
panic!("needs CAP_NET_RAW: run under sudo")
}
Err(e) => panic!("opening lo: {e}"),
}
}
type Socket_ = Arc<Socket>;
fn wait_for<F: Fn() -> bool>(timeout: Duration, cond: F) -> bool {
let deadline = Instant::now() + timeout;
while Instant::now() < deadline {
if cond() {
return true;
}
std::thread::sleep(Duration::from_millis(10));
}
cond()
}
#[test]
#[ignore = "needs CAP_NET_RAW"]
fn sent_frame_comes_back_on_loopback() {
let sock = open_lo(false);
let seen = Arc::new(Mutex::new(Vec::new()));
let seen2 = seen.clone();
sock.set_handler(Arc::new(move |f: &Frame| {
if f.ether_type() == EtherType::new(TEST_ETHERTYPE) && f.payload().starts_with(MAGIC) {
seen2.lock().unwrap().push(f.to_vec());
}
Ok(())
}));
let mut payload = MAGIC.to_vec();
payload.resize(46, 0); let out = build_frame(
MacAddr::zero(),
MacAddr::zero(),
EtherType::new(TEST_ETHERTYPE),
&payload,
);
sock.send(Frame::from_slice(&out)).unwrap();
assert!(
wait_for(Duration::from_secs(5), || !seen.lock().unwrap().is_empty()),
"the frame we sent on lo never came back"
);
assert_eq!(seen.lock().unwrap()[0], out);
let stats = sock.stats().unwrap().snapshot();
assert_eq!(stats.tx_packets, 1);
assert!(stats.rx_packets >= 1);
sock.close().unwrap();
}
#[test]
#[ignore = "needs CAP_NET_RAW"]
fn inbound_only_hides_our_own_transmissions() {
let sock = open_lo(true);
let count = Arc::new(AtomicUsize::new(0));
let count2 = count.clone();
sock.set_handler(Arc::new(move |f: &Frame| {
if f.ether_type() == EtherType::new(TEST_ETHERTYPE) {
count2.fetch_add(1, Ordering::SeqCst);
}
Ok(())
}));
let mut payload = MAGIC.to_vec();
payload.resize(46, 0);
let out = build_frame(
MacAddr::zero(),
MacAddr::zero(),
EtherType::new(TEST_ETHERTYPE),
&payload,
);
for _ in 0..5 {
sock.send(Frame::from_slice(&out)).unwrap();
}
assert!(wait_for(Duration::from_secs(2), || count
.load(Ordering::SeqCst)
>= 5));
std::thread::sleep(Duration::from_millis(200));
assert_eq!(
count.load(Ordering::SeqCst),
5,
"outgoing copies should have been filtered out"
);
sock.close().unwrap();
}
#[test]
#[ignore = "needs CAP_NET_RAW"]
fn close_stops_the_reader_and_rejects_sends() {
let sock = open_lo(false);
sock.close().unwrap();
assert!(sock.close().is_ok(), "close is idempotent");
let f = build_frame(MacAddr::zero(), MacAddr::zero(), EtherType::IPV4, &[0; 46]);
let err = sock.send(Frame::from_slice(&f)).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::NotConnected);
}
#[test]
#[ignore = "needs CAP_NET_RAW"]
fn reports_the_interface_mtu() {
let sock = open_lo(false);
assert_eq!(sock.interface(), "lo");
assert!(sock.mtu() >= 1500, "lo MTU looked like {}", sock.mtu());
sock.close().unwrap();
}