info.rs 12 KB

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