@ -1,6 +1,8 @@
use std ::{
mem ,
collections ::VecDeque ,
fs , mem ,
os ::fd ::AsRawFd as _ ,
path ::Path ,
sync ::{
Arc ,
atomic ::{ AtomicBool , Ordering } ,
@ -13,7 +15,7 @@ use anyhow::Context as _;
use assert_matches ::assert_matches ;
use aya ::{
Ebpf , EbpfLoader ,
maps ::{ Map Data, array ::PerCpuArray , ring_buf ::RingBuf } ,
maps ::{ Map , Map Data, array ::PerCpuArray , ring_buf ::RingBuf } ,
programs ::UProbe ,
} ;
use aya_obj ::generated ::BPF_RINGBUF_HDR_SZ ;
@ -412,3 +414,114 @@ impl WriterThread {
thread . join ( ) . unwrap ( ) ;
}
}
// This test checks for a bug that the consumer index always started at position
// 0 of a newly-loaded ring-buffer map. This assumption is not true for a map
// that is pinned to the bpffs filesystem since the map "remembers" the last
// consumer index position even if all processes unloaded it. The structure of
// the test is as follows:
//
// Create the pinned ring buffer, write some items to it, and read some of them.
// Leave some in there, so that we can assert that upon re-opening the map, we
// can still read them. Then, re-open the map, write some more items and read
// both the old and new items.
#[ test ]
fn pinned_ring_buf ( ) {
let mut rng = rand ::rng ( ) ;
let pin_path = Path ::new ( "/sys/fs/bpf/" ) . join ( format! ( "{:x}" , rng . random ::< u64 > ( ) ) ) ;
fs ::create_dir_all ( & pin_path ) . unwrap ( ) ;
let n = RING_BUF_MAX_ENTRIES - 1 ; // avoid thinking about the capacity
let data = std ::iter ::repeat_with ( | | rng . random ( ) )
. take ( n )
. collect ::< Vec < _ > > ( ) ;
let PinnedRingBufTest { mut ring_buf , _bpf } = PinnedRingBufTest ::new ( & pin_path ) ;
let to_write_before_reopen = data . len ( ) . min ( 8 ) ;
let mut expected = VecDeque ::new ( ) ;
for & v in data . iter ( ) . take ( to_write_before_reopen ) {
ring_buf_trigger_ebpf_program ( v ) ;
if v % 2 = = 0 {
expected . push_back ( v ) ;
}
}
let to_read_before_reopen = expected . len ( ) / 2 ;
for _ in 0 .. to_read_before_reopen {
let read = ring_buf . next ( ) . unwrap ( ) ;
let read : [ u8 ; 8 ] = ( * read )
. try_into ( )
. with_context ( | | format! ( "data: {:?}" , read . len ( ) ) )
. unwrap ( ) ;
let arg = u64 ::from_ne_bytes ( read ) ;
let exp = expected . pop_front ( ) . unwrap ( ) ;
assert_eq! ( exp , arg ) ;
}
// Close the old pinned map and re-open it.
drop ( ( _bpf , ring_buf ) ) ;
let PinnedRingBufTest { mut ring_buf , _bpf } = PinnedRingBufTest ::new ( & pin_path ) ;
// Write some more items to the ring buffer.
for & v in data . iter ( ) . skip ( to_write_before_reopen ) {
ring_buf_trigger_ebpf_program ( v ) ;
if v % 2 = = 0 {
expected . push_back ( v ) ;
}
}
for _ in 0 .. expected . len ( ) {
let read = ring_buf . next ( ) . unwrap ( ) ;
let read : [ u8 ; 8 ] = ( * read )
. try_into ( )
. with_context ( | | format! ( "data: {:?}" , read . len ( ) ) )
. unwrap ( ) ;
let arg = u64 ::from_ne_bytes ( read ) ;
let exp = expected . pop_front ( ) . unwrap ( ) ;
assert_eq! ( exp , arg ) ;
}
// Make sure that there is nothing else in the ring_buf.
assert_matches ! ( ring_buf . next ( ) , None ) ;
// Clean up the pinned map from the filesystem.
fs ::remove_dir_all ( pin_path ) . unwrap ( ) ;
}
struct PinnedRingBufTest {
_bpf : Ebpf ,
ring_buf : RingBuf < MapData > ,
}
impl PinnedRingBufTest {
fn new ( pin_path : & Path ) -> Self {
const RING_BUF_BYTE_SIZE : u32 =
( RING_BUF_MAX_ENTRIES * ( mem ::size_of ::< u64 > ( ) + BPF_RINGBUF_HDR_SZ as usize ) ) as u32 ;
let mut bpf = EbpfLoader ::new ( )
. map_pin_path ( pin_path )
. set_max_entries ( "RING_BUF" , RING_BUF_BYTE_SIZE )
. load ( crate ::RING_BUF_PINNED )
. unwrap ( ) ;
let ring_buf_pin_path = pin_path . join ( "RING_BUF" ) ;
let ring_buf = MapData ::from_pin ( ring_buf_pin_path ) . unwrap ( ) ;
let ring_buf = Map ::RingBuf ( ring_buf ) ;
let ring_buf = RingBuf ::try_from ( ring_buf ) . unwrap ( ) ;
let prog : & mut UProbe = bpf
. program_mut ( "ring_buf_test" )
. unwrap ( )
. try_into ( )
. unwrap ( ) ;
prog . load ( ) . unwrap ( ) ;
prog . attach (
"ring_buf_trigger_ebpf_program" ,
"/proc/self/exe" ,
None ,
None ,
)
. unwrap ( ) ;
Self {
_bpf : bpf ,
ring_buf ,
}
}
}