info.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. //! Tests the Info API.
  2. use std::{fs, panic, path::Path, time::SystemTime};
  3. use aya::{
  4. maps::{loaded_maps, MapError},
  5. programs::{loaded_programs, ProgramError, ProgramType, SocketFilter, TracePoint},
  6. sys::enable_stats,
  7. util::KernelVersion,
  8. Ebpf,
  9. };
  10. use aya_obj::generated::bpf_map_type;
  11. use libc::EINVAL;
  12. use crate::utils::{kernel_assert, kernel_assert_eq};
  13. const BPF_JIT_ENABLE: &str = "/proc/sys/net/core/bpf_jit_enable";
  14. const BPF_STATS_ENABLED: &str = "/proc/sys/kernel/bpf_stats_enabled";
  15. #[test]
  16. fn test_loaded_programs() {
  17. // Load a program.
  18. // Since we are only testing the programs for their metadata, there is no need to "attach" them.
  19. let mut bpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
  20. let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
  21. prog.load().unwrap();
  22. let test_prog = prog.info().unwrap();
  23. // Ensure loaded program doesn't panic
  24. let mut programs = loaded_programs().peekable();
  25. if let Err(err) = programs.peek().unwrap() {
  26. if let ProgramError::SyscallError(err) = &err {
  27. // Skip entire test since feature not available
  28. if err
  29. .io_error
  30. .raw_os_error()
  31. .is_some_and(|errno| errno == EINVAL)
  32. {
  33. eprintln!(
  34. "ignoring test completely as `loaded_programs()` is not available on the host"
  35. );
  36. return;
  37. }
  38. }
  39. panic!("{err}");
  40. }
  41. // Loaded programs should contain our test program
  42. let mut programs = programs.filter_map(|prog| prog.ok());
  43. kernel_assert!(
  44. programs.any(|prog| prog.id() == test_prog.id()),
  45. KernelVersion::new(4, 13, 0)
  46. );
  47. }
  48. #[test]
  49. fn test_program_info() {
  50. // Kernels below v4.15 have been observed to have `bpf_jit_enable` disabled by default.
  51. let previously_enabled = is_sysctl_enabled(BPF_JIT_ENABLE);
  52. // Restore to previous state when panic occurs.
  53. let prev_panic = panic::take_hook();
  54. panic::set_hook(Box::new(move |panic_info| {
  55. if !previously_enabled {
  56. disable_sysctl_param(BPF_JIT_ENABLE);
  57. }
  58. prev_panic(panic_info);
  59. }));
  60. let jit_enabled = previously_enabled || enable_sysctl_param(BPF_JIT_ENABLE);
  61. let mut bpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
  62. let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
  63. prog.load().unwrap();
  64. let test_prog = prog.info().unwrap();
  65. // Test `bpf_prog_info` fields.
  66. kernel_assert_eq!(
  67. ProgramType::SocketFilter,
  68. test_prog.program_type().unwrap_or(ProgramType::Unspecified),
  69. KernelVersion::new(4, 13, 0),
  70. );
  71. kernel_assert!(test_prog.id().is_some(), KernelVersion::new(4, 13, 0));
  72. kernel_assert!(test_prog.tag().is_some(), KernelVersion::new(4, 13, 0));
  73. if jit_enabled {
  74. kernel_assert!(
  75. test_prog.size_jitted().is_some(),
  76. KernelVersion::new(4, 13, 0),
  77. );
  78. }
  79. kernel_assert!(
  80. test_prog.size_translated().is_some(),
  81. KernelVersion::new(4, 13, 0),
  82. );
  83. kernel_assert!(
  84. test_prog.loaded_at().is_some(),
  85. KernelVersion::new(4, 15, 0),
  86. );
  87. kernel_assert!(
  88. test_prog.created_by_uid().is_some_and(|uid| uid == 0),
  89. KernelVersion::new(4, 15, 0),
  90. );
  91. let maps = test_prog.map_ids().unwrap();
  92. kernel_assert!(
  93. maps.is_some_and(|ids| ids.is_empty()),
  94. KernelVersion::new(4, 15, 0),
  95. );
  96. kernel_assert!(
  97. test_prog
  98. .name_as_str()
  99. .is_some_and(|name| name == "simple_prog"),
  100. KernelVersion::new(4, 15, 0),
  101. );
  102. kernel_assert!(
  103. test_prog.gpl_compatible().is_some_and(|gpl| gpl),
  104. KernelVersion::new(4, 18, 0),
  105. );
  106. kernel_assert!(
  107. test_prog.verified_instruction_count().is_some(),
  108. KernelVersion::new(5, 16, 0),
  109. );
  110. // We can't reliably test these fields since `0` can be interpreted as the actual value or
  111. // unavailable.
  112. test_prog.btf_id();
  113. // Ensure rest of the fields do not panic.
  114. test_prog.memory_locked().unwrap();
  115. test_prog.fd().unwrap();
  116. // Restore to previous state
  117. if !previously_enabled {
  118. disable_sysctl_param(BPF_JIT_ENABLE);
  119. }
  120. }
  121. #[test]
  122. fn test_loaded_at() {
  123. let mut bpf: Ebpf = Ebpf::load(crate::SIMPLE_PROG).unwrap();
  124. let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
  125. // SystemTime is not monotonic, which can cause this test to flake. We don't expect the clock
  126. // timestamp to continuously jump around, so we add some retries. If the test is ever correct,
  127. // we know that the value returned by loaded_at() was reasonable relative to SystemTime::now().
  128. let mut failures = Vec::new();
  129. for _ in 0..5 {
  130. let t1 = SystemTime::now();
  131. prog.load().unwrap();
  132. let t2 = SystemTime::now();
  133. let loaded_at = match prog.info().unwrap().loaded_at() {
  134. Some(time) => time,
  135. None => {
  136. eprintln!("ignoring test completely as `load_time` field of `bpf_prog_info` is not available on the host");
  137. return;
  138. }
  139. };
  140. prog.unload().unwrap();
  141. let range = t1..t2;
  142. if range.contains(&loaded_at) {
  143. failures.clear();
  144. break;
  145. }
  146. failures.push(LoadedAtRange(loaded_at, range));
  147. }
  148. assert!(
  149. failures.is_empty(),
  150. "loaded_at was not in range: {failures:?}",
  151. );
  152. struct LoadedAtRange(SystemTime, std::ops::Range<SystemTime>);
  153. impl std::fmt::Debug for LoadedAtRange {
  154. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  155. let Self(loaded_at, range) = self;
  156. write!(f, "{range:?}.contains({loaded_at:?})")
  157. }
  158. }
  159. }
  160. #[test]
  161. fn test_prog_stats() {
  162. // Test depends on whether trace point exists.
  163. if !Path::new("/sys/kernel/debug/tracing/events/syscalls/sys_enter_bpf").exists() {
  164. eprintln!(
  165. "ignoring test completely as `syscalls/sys_enter_bpf` is not available on the host"
  166. );
  167. return;
  168. }
  169. let stats_fd = enable_stats(aya::sys::Stats::RunTime).ok();
  170. // Restore to previous state when panic occurs.
  171. let previously_enabled = is_sysctl_enabled(BPF_STATS_ENABLED);
  172. let prev_panic = panic::take_hook();
  173. panic::set_hook(Box::new(move |panic_info| {
  174. if !previously_enabled {
  175. disable_sysctl_param(BPF_STATS_ENABLED);
  176. }
  177. prev_panic(panic_info);
  178. }));
  179. let stats_enabled =
  180. stats_fd.is_some() || previously_enabled || enable_sysctl_param(BPF_STATS_ENABLED);
  181. if !stats_enabled {
  182. eprintln!("ignoring test completely as bpf stats could not be enabled on the host");
  183. return;
  184. }
  185. let mut bpf = Ebpf::load(crate::TEST).unwrap();
  186. let prog: &mut TracePoint = bpf
  187. .program_mut("test_tracepoint")
  188. .unwrap()
  189. .try_into()
  190. .unwrap();
  191. prog.load().unwrap();
  192. prog.attach("syscalls", "sys_enter_bpf").unwrap();
  193. let test_prog = prog.info().unwrap();
  194. kernel_assert!(
  195. test_prog.run_time().as_nanos() > 0,
  196. KernelVersion::new(5, 1, 0)
  197. );
  198. kernel_assert!(test_prog.run_count() > 0, KernelVersion::new(5, 1, 0));
  199. // Restore to previous state
  200. if !previously_enabled {
  201. disable_sysctl_param(BPF_STATS_ENABLED);
  202. }
  203. }
  204. #[test]
  205. fn list_loaded_maps() {
  206. // Load a program with maps.
  207. let mut bpf: Ebpf = Ebpf::load(crate::MAP_TEST).unwrap();
  208. let prog: &mut SocketFilter = bpf.program_mut("simple_prog").unwrap().try_into().unwrap();
  209. prog.load().unwrap();
  210. // Ensure the loaded_maps() api doesn't panic and retrieve loaded maps.
  211. let mut maps = loaded_maps().peekable();
  212. if let Err(err) = maps.peek().unwrap() {
  213. if let MapError::SyscallError(err) = &err {
  214. if err
  215. .io_error
  216. .raw_os_error()
  217. .is_some_and(|errno| errno == EINVAL)
  218. {
  219. eprintln!(
  220. "ignoring test completely as `loaded_maps()` is not available on the host"
  221. );
  222. return;
  223. }
  224. }
  225. panic!("{err}");
  226. }
  227. let mut maps: Vec<_> = maps.filter_map(|m| m.ok()).collect();
  228. // There's not a good way to extract our maps of interest with load order being
  229. // non-deterministic. Since we are trying to be more considerate of older kernels, we should
  230. // only rely on v4.13 feats.
  231. // Expected sort order should be: `BAR`, `aya_global` (if avail), `FOO`
  232. maps.sort_unstable_by_key(|m| (m.map_type(), m.id()));
  233. // Ensure program has the 2 maps.
  234. if let Ok(info) = prog.info() {
  235. let map_ids = info.map_ids().unwrap();
  236. kernel_assert!(map_ids.is_some(), KernelVersion::new(4, 15, 0));
  237. if let Some(map_ids) = map_ids {
  238. assert_eq!(2, map_ids.len());
  239. for id in map_ids.iter() {
  240. assert!(
  241. maps.iter().any(|m| m.id() == id.get()),
  242. "expected `loaded_maps()` to have `map_ids` from program"
  243. );
  244. }
  245. }
  246. }
  247. // Test `bpf_map_info` fields.
  248. let hash = maps.first().unwrap();
  249. kernel_assert_eq!(
  250. bpf_map_type::BPF_MAP_TYPE_HASH as u32,
  251. hash.map_type(),
  252. KernelVersion::new(4, 13, 0)
  253. );
  254. kernel_assert!(hash.id() > 0, KernelVersion::new(4, 13, 0));
  255. kernel_assert_eq!(4, hash.key_size(), KernelVersion::new(4, 13, 0));
  256. kernel_assert_eq!(1, hash.value_size(), KernelVersion::new(4, 13, 0));
  257. kernel_assert_eq!(8, hash.max_entries(), KernelVersion::new(4, 13, 0));
  258. kernel_assert_eq!(
  259. "BAR",
  260. hash.name_as_str().unwrap(),
  261. KernelVersion::new(4, 15, 0)
  262. );
  263. hash.map_flags();
  264. hash.fd().unwrap();
  265. let array = maps.last().unwrap();
  266. kernel_assert_eq!(
  267. bpf_map_type::BPF_MAP_TYPE_ARRAY as u32,
  268. array.map_type(),
  269. KernelVersion::new(4, 13, 0)
  270. );
  271. kernel_assert!(array.id() > 0, KernelVersion::new(4, 13, 0));
  272. kernel_assert_eq!(4, array.key_size(), KernelVersion::new(4, 13, 0));
  273. kernel_assert_eq!(4, array.value_size(), KernelVersion::new(4, 13, 0));
  274. kernel_assert_eq!(10, array.max_entries(), KernelVersion::new(4, 13, 0));
  275. kernel_assert_eq!(
  276. "FOO",
  277. array.name_as_str().unwrap(),
  278. KernelVersion::new(4, 15, 0)
  279. );
  280. array.map_flags();
  281. array.fd().unwrap();
  282. }
  283. /// Whether sysctl parameter is enabled in the `/proc` file.
  284. fn is_sysctl_enabled(path: &str) -> bool {
  285. match fs::read_to_string(path) {
  286. Ok(contents) => contents.chars().next().is_some_and(|c| c == '1'),
  287. Err(_) => false,
  288. }
  289. }
  290. /// Enable sysctl parameter through procfs.
  291. fn enable_sysctl_param(path: &str) -> bool {
  292. fs::write(path, b"1").is_ok()
  293. }
  294. /// Disable sysctl parameter through procfs.
  295. fn disable_sysctl_param(path: &str) -> bool {
  296. fs::write(path, b"0").is_ok()
  297. }