aya-0.13.1/.cargo_vcs_info.json0000644000000001410000000000100116640ustar { "git": { "sha1": "2791badd947e3abb459e5339a23a66d0a56c42d0" }, "path_in_vcs": "aya" }aya-0.13.1/BREAKING-CHANGES.md000064400000000000000000000131071046102023000132340ustar 00000000000000# Breaking Changes This document contains a list of breaking changes in each version and some notes to help migrate between versions. It is compiled manually from the commit history and changelog. We also tag PRs on github with a [breaking change] label. [breaking change]: (https://github.com/aya-rs/aya/issues?q=label%3A%22breaking+change%22) ## Summary - [v0.12.0](#v0120) - In `aya::Bpf::programs`, `name` uses the function name from the ELF file. - Maps API has been reworked. - `aya::ProgramFd` trait has been removed. - `aya::BpfLoader::set_global` signature has changed. - MSRV has been bumped to 1.66.0. - BTF types have moved to the `aya-obj` crate. - `aya::PerfEvent::attach` and `detach` signatures have changed. ## v0.12.0 ### In `aya::Bpf::programs`, `name` uses the function name from the ELF file In previous versions, the `name` parameter of `aya::Bpf::programs` was derived from the ELF section name. If you were using `aya-bpf`, this was sensible since our macros took care of appending the function name to the section name, resulting in a section name like `kprobe/my_function`. However, loading this program using libbpf > 1.0 would fail due the section name not following the newly enforced [naming convention]. Likewise, loading eBPF programs written in C using Aya was also problematic. Given the following C program: ```c SEC("kprobe") int my_function(struct pt_regs *ctx) { return 0; } ``` Loading this program using Aya would require the following: ```rust let bpf = Bpf::load(&program)?; let my_function = bpf.program("kprobe")?; ``` This was not intuitive and was a frequent source of confusion. To solve this, Aya was changed to resolve function names from the ELF files symbol table. Therefore, if your function is defined as: ```rust #[kprobe] fn my_function(_ctx: KprobeContext) -> i32 { 0 } ``` Or in C: ```c SEC("kprobe") int my_function(struct pt_regs *ctx) { return 0; } ``` Then you should load it using: ```rust let bpf = Bpf::load(&program)?; let my_function = bpf.program("my_function")?; ``` Migration is straightforward. Simply replace the `name` parameter in `Bpf::programs` with the function name from the ELF file. If you are using `aya-bpf`, you should update to the latest (git) version and recompile your programs. The name argument inside our macros has been deprecated and should therefore be removed. Given the following: ```rust #[kprobe(name="another_name")] fn some_name(_ctx: KprobeContext) -> i32 { 0 } ``` You would update it to: ```rust #[kprobe] fn another_name(_ctx: KprobeContext) -> i32 { 0 } ``` Note here that changing the exported function name is not supported anymore. [naming convention]: https://docs.kernel.org/bpf/libbpf/program_types.html ### Maps API has been reworked In v0.11.0 and earlier `aya::Bpf::map_mut` and `aya::Bpf::map` were used to access maps. These functions returned a `Result` that contained a `MapRef` or `MapRefMut` respectively. Since the `OwnedFd` and `BorrowedFd` types were added to the Rust standard library, we've been able to better reason around File descriptor ownership and therefore the `MapRef` and `MapRefMut` types have been removed. These APIs now return `Option<&Map>` or `Option<&mut Map>` respectively. Additionally a new `aya::Bpf::take_map` method has been added to take ownership of a map - a common requirement when working with maps using async. ### `aya::ProgramFd` trait has been removed The `ProgramFd` trait has been removed. This trait was used to provide a means of retrieving the file descriptor of a program. As with the maps API, the `OwnedFd` and `BorrowedFd` types have made this trait redundant. To migrate, you can remove any imports of the `ProgramFd` trait from your code since `aya::Bpf::program::fd()` is available to retrieve the file descriptor. The `fd()` method returns an `Result`, which differs from the previous API that returned an `Option` so you will now have the ability to handle errors more effectively. ### `aya::BpfLoader::set_global` signature has changed The `value` in `set_global` was previously required to implement `aya::Pod`. The constraint has now been changed to be `Into` which includes both `aya::Pod` types and slices of `aya::Pod` types. Additionally, a new argument `must_exist` has been added. If the `must_exist` argument is true, BpfLoader::load will fail with `aya::ParseError::SymbolNotFound` if the loaded object code does not contain the variable. ### MSRV has been bumped to 1.66.0 The minimum supported Rust version has been bumped to 1.66.0. This is due to the use of the `std::os::fd` module which was stabilized in this version. To migrate you will need to ensure that you are using rustc 1.66.0 or later. ### BTF types have moved to the `aya-obj` crate The BTF types have been moved to the `aya-obj` crate. This is to allow the BTF and ELF parsing code to be used independently of `aya`. To migrate, if you were using `aya::Btf::from_sys_fs` or `aya::Btf::parse_file`, you should use `aya_obj::Btf::from_sys_fs` or `aya_obj::Btf::parse_file`. ### `aya::PerfEvent::attach` and `detach` signatures have changed The `attach` method has been changed to take an additional boolean argument called `inherit`. Where the `scope` argument determines which processes are sampled, if `inherit` is true, any new processes spawned by those processes will also automatically get sampled. Also of note is that both `attach` and `detach` deal with a new type called `PerfEventLinkId` where previously they used `PerfEventId`. This change was required to support the more modern attachment method for perf event programs. aya-0.13.1/CHANGELOG.md000064400000000000000000007244301046102023000123030ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 0.13.1 (2024-11-01) ### Chore - Add comments in `*_wrong_map` tests - Rename bpf -> ebpf - Fix unused_qualifications lints This was failing the docs build. ### Documentation - fix typo - Use `Ebpf` instead of `Bpf` ### New Features - Implement TCX This commit adds the initial support for TCX bpf links. This is a new, multi-program, attachment type allows for the caller to specify where they would like to be attached relative to other programs at the attachment point using the LinkOrder type. - Provide a deprecated `BpfError` alias - Rename Bpf to Ebpf And BpfLoader to EbpfLoader. This also adds type aliases to preserve the use of the old names, making updating to a new Aya release less of a burden. These aliases are marked as deprecated since we'll likely remove them in a later release. ### Bug Fixes - Fill bss maps with zeros The loader should fill bss maps with zeros according to the size of the ELF section. Failure to do so yields weird verifier messages as follows: ``` cannot access ptr member ops with moff 0 in struct bpf_map with off 0 size 4 ``` Reference to this in the cilium/ebpf code is here [1]. I could not find a reference in libbpf. - Fix PerfEventArray resize logic There was a logic bug in the previously merged patch where we set the correctly calculated max_entries size with the original. To fix this and prevent regressions a unit test was added. This highlighted that the original map definition needs to be mutated in order for the max_entries change to be properly applied. As such, this resize logic moved out of aya::sys into aya::maps - Set PerfEventArray max_entries to nCPUs Both libbpf and cilium/ebpf have will set the max_entries of a BPF_MAP_TYPE_PERF_EVENT_ARRAY to the number of online CPUs if it was omitted at map definition time. This adds that same logic to Aya. - fix panic when creating map on custom ubuntu kernel - fix rustdocs-args ordering in taplo to -D warnings This fixes the current rustdoc build error by correcting the ordering of `rustdoc-args` to `-D warnings`. Additionally, this also removes the `recorder_arrays` field (defaults to false) so that the order is not modified, which is what caused the error in the first place. ### Other - use FdLink in SockOps programs - remove unwrap and NonZero* in info Addresses the feedback from #1007: - remove panic from `unwrap` and `expect` - Option => Option with `0` mapping to `None` - revamp MapInfo be more friendly with older kernels Adds detection for whether a field is available in `MapInfo`: - For `map_type()`, we treturn new enum `MapType` instead of the integer representation. - For fields that can't be zero, we return `Option` type. - For `name_as_str()`, it now uses the feature probe `bpf_name()` to detect if field is available. Although the feature probe checks for program name, it can also be used for map name since they were both introduced in the same commit. - revamp ProgramInfo be more friendly with older kernels Purpose of this commit is to add detections for whether a field is available in `ProgramInfo`. - For `program_type()`, we return the new enum `ProgramType` instead of the integer representation. - For fields that we know cannot be zero, we return `Option` type. - For `name_as_str()`, it now also uses the feature probe `bpf_name()` to detect if field is available or not. - Two additional feature probes are added for the fields: - `prog_info_map_ids()` probe -> `map_ids()` field - `prog_info_gpl_compatible()` probe -> `gpl_compatible()` field With the `prog_info_map_ids()` probe, the previous implementation that I had for `bpf_prog_get_info_by_fd()` is shortened to use the probe instead of having to make 2 potential syscalls. The `test_loaded_at()` test is also moved into info tests since it is better related to the info tests. - add conversion u32 to enum type for prog, link, & attach type Add conversion from u32 to program type, link type, and attach type. Additionally, remove duplicate match statement for u32 conversion to `BPF_MAP_TYPE_BLOOM_FILTER` & `BPF_MAP_TYPE_CGRP_STORAGE`. New error `InvalidTypeBinding` is created to represent when a parsed/received value binding to a type is invalid. This is used in the new conversions added here, and also replaces `InvalidMapTypeError` in `TryFrom` for `bpf_map_type`. - improve integration tests for info API Improves the existing integraiton tests for `loaded_programs()` and `loaded_maps()` in consideration for older kernels: - Opt for `SocketFilter` program in tests since XDP requires v4.8 and fragments requires v5.18. - For assertion tests, first perform the assertion, if the assertion fails, then it checks the host kernel version to see if it is above the minimum version requirement. If not, then continue with test, otherwise fail. For assertions that are skipped, they're logged in stderr which can be observed with `-- --nocapture`. This also fixes the `bpf_prog_get_info_by_fd()` call for kernels below v4.15. If calling syscall on kernels below v4.15, it can produce an `E2BIG` error because `check_uarg_tail_zero()` expects the entire struct to all-zero bytes (which is caused from the map info). Instead, we first attempt the syscall with the map info filled, if it returns `E2BIG`, then perform syscall again with empty closure. Also adds doc for which version a kernel feature was introduced for better awareness. The tests have been verified kernel versions: - 4.13.0 - 4.15.0 - 6.1.0 - adjust bpf programs for big endian In aya/src/sys/bpf.rs, there are several simple bpf programs written as byte arrays. These need to be adjusted to account for big endian. - expose run_time_ns and run_cnt fields in ProgramInfo Added functions to expose `run_time_ns` & `run_cnt` statistics from ProgramInfo/bpf_prog_info. - add BPF_ENABLE_STATS syscall function Add bpf syscall function for BPF_ENABLE_STATS to enable stats tracking for benchmarking purposes. Additionally, move `#[cfg(test)]` annotation around the `Drop` trait instead. Having separate functions causes some complications when needing ownership/moving of the inner value `OwnedFd` when `Drop` is manually implemented. - :programs::uprobe: fix bad variable name The variable fn_name was very much *not* the fn_name, but rather the object file path. - adjust symbol lookup tests for object crate alignment requirements The object::File::parse API requires parameter to be aligned with 8 bytes. Adjusted the Vec in the tests with miri to meet this requirement. - add symbol lookup in associated debug files This change enhances the logic for symbol lookup in uprobe or uretprobe. If the symbol is not found in the original binary, the search continues in the debug file associated through the debuglink section. Before searching the symbol table, it compares the build IDs of the two files. The symbol lookup will only be terminated if both build IDs exist and do not match. This modification does not affect the existing symbol lookup logic. - Generate new bindings - include license in crate workspace This PR includes the licenses files in the crate workspace subdirectory. Without this, they won't be showing on crates.io and would be giving out errors on tooling such as rust2rpm. - appease new nightly clippy lints ``` error: unnecessary qualification --> aya/src/maps/ring_buf.rs:434:22 | 434 | ptr: ptr::NonNull::new(ptr).ok_or( | ^^^^^^^^^^^^^^^^^ | note: the lint level is defined here --> aya/src/lib.rs:72:5 | 72 | unused_qualifications, | ^^^^^^^^^^^^^^^^^^^^^ help: remove the unnecessary path segments | 434 - ptr: ptr::NonNull::new(ptr).ok_or( 434 + ptr: NonNull::new(ptr).ok_or( | error: unnecessary qualification --> aya/src/maps/mod.rs:225:21 | 225 | let mut limit = std::mem::MaybeUninit::::uninit(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the unnecessary path segments | 225 - let mut limit = std::mem::MaybeUninit::::uninit(); 225 + let mut limit = mem::MaybeUninit::::uninit(); | error: unnecessary qualification --> aya/src/programs/mod.rs:614:9 | 614 | crate::obj::Program { | ^^^^^^^^^^^^^^^^^^^ | help: remove the unnecessary path segments | 614 - crate::obj::Program { 614 + obj::Program { | error: unnecessary qualification --> aya/src/util.rs:373:14 | 373 | unsafe { std::slice::from_raw_parts(bpf_name.as_ptr() as *const _, length) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the unnecessary path segments | 373 - unsafe { std::slice::from_raw_parts(bpf_name.as_ptr() as *const _, length) } 373 + unsafe { slice::from_raw_parts(bpf_name.as_ptr() as *const _, length) } | error: unnecessary qualification --> aya/src/maps/mod.rs:1130:47 | 1130 | .copy_from_slice(unsafe { std::mem::transmute(TEST_NAME) }); | ^^^^^^^^^^^^^^^^^^^ | note: the lint level is defined here --> aya/src/lib.rs:72:5 | 72 | unused_qualifications, | ^^^^^^^^^^^^^^^^^^^^^ help: remove the unnecessary path segments | 1130 - .copy_from_slice(unsafe { std::mem::transmute(TEST_NAME) }); 1130 + .copy_from_slice(unsafe { mem::transmute(TEST_NAME) }); | ``` ### Performance - cache `nr_cpus` in a thread_local ### Test - adjust test byte arrays for big endian Adding support for s390x (big endian architecture) and found that some of the unit tests have structures and files implemented as byte arrays. They are all coded as little endian and need a bug endian version to work properly. ### New Features (BREAKING) - Rename BpfRelocationError -> EbpfRelocationError - Rename BpfSectionKind to EbpfSectionKind ### Commit Statistics - 69 commits contributed to the release over the course of 241 calendar days. - 247 days passed between releases. - 32 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release aya-obj v0.2.1 ([`c6a34ca`](https://github.com/aya-rs/aya/commit/c6a34cade195d682e1eece5b71e3ab48e48f3cda)) - Merge pull request #1073 from dave-tucker/reloc-bug ([`b2ac9fe`](https://github.com/aya-rs/aya/commit/b2ac9fe85db6c25d0b8155a75a2df96a80a19811)) - Fill bss maps with zeros ([`ca0c32d`](https://github.com/aya-rs/aya/commit/ca0c32d1076af81349a52235a4b6fb3937a697b3)) - Release aya-obj v0.2.0, aya v0.13.0, safety bump aya v0.13.0 ([`c169b72`](https://github.com/aya-rs/aya/commit/c169b727e6b8f8c2dda57f54b8c77f8b551025c6)) - Implement TCX ([`5478cac`](https://github.com/aya-rs/aya/commit/5478cac008471bdb80aa30733e4456b70ec1a5bd)) - Cache `nr_cpus` in a thread_local ([`d05110f`](https://github.com/aya-rs/aya/commit/d05110fd86f9b317d47ffb7cf5c00e588635d4cd)) - Clarify `Arc` usage ([`afd777b`](https://github.com/aya-rs/aya/commit/afd777b705312b7bafec2a116041a2318d3aa70f)) - Replace `Arc` with `&'static` ([`e992c28`](https://github.com/aya-rs/aya/commit/e992c280cbae7af7e484767a0b79314b14a4de84)) - Avoid intermediate allocations in parse_cpu_ranges ([`0e86757`](https://github.com/aya-rs/aya/commit/0e867572ff8e009bbcd1a63037b4ab5b80e35549)) - Reduce duplication in `{nr,possible}_cpus` ([`f3b2744`](https://github.com/aya-rs/aya/commit/f3b27440725a0eb2f1615c92cb0047e3b1548d66)) - Replace `lazy_static` with `std::sync::LazyLock` ([`2b299d4`](https://github.com/aya-rs/aya/commit/2b299d4fba1ddda70c2e8af324f999cb23683559)) - Appease clippy ([`0f16363`](https://github.com/aya-rs/aya/commit/0f163633e3d73c59f857880c967c27e9f52e8610)) - Merge pull request #1023 from l2dy/fdlink/sockops ([`2cd3576`](https://github.com/aya-rs/aya/commit/2cd35769dce05b46a4dd07381c990c6acd4cfe0d)) - Use FdLink in SockOps programs ([`c44f8b0`](https://github.com/aya-rs/aya/commit/c44f8b0f5bddd820a4a98cff293126c0146b827a)) - Remove unwrap and NonZero* in info ([`02d1db5`](https://github.com/aya-rs/aya/commit/02d1db5fc043fb7af90c14d13de6419ec5b9bcb5)) - Merge pull request #985 from reyzell/main ([`40f3032`](https://github.com/aya-rs/aya/commit/40f303205f7a800877fe3f9a4fb1893141741e13)) - Add the option to support multiple and overrideable programs per cgroup ([`f790685`](https://github.com/aya-rs/aya/commit/f790685d759cbd97cb09ad48d87cdece28fbe579)) - Merge pull request #1007 from tyrone-wu/aya/info-api ([`15eb935`](https://github.com/aya-rs/aya/commit/15eb935bce6d41fb67189c48ce582b074544e0ed)) - Revamp MapInfo be more friendly with older kernels ([`fbb0930`](https://github.com/aya-rs/aya/commit/fbb09304a2de0d8baf7ea20c9727fcd2e4fb7f41)) - Revamp ProgramInfo be more friendly with older kernels ([`88f5ac3`](https://github.com/aya-rs/aya/commit/88f5ac31142f1657b41b1ee0f217dcd9125b210a)) - Add conversion u32 to enum type for prog, link, & attach type ([`1634fa7`](https://github.com/aya-rs/aya/commit/1634fa7188e40ed75da53517f1fdb7396c348c34)) - Improve integration tests for info API ([`cb8e478`](https://github.com/aya-rs/aya/commit/cb8e47880082ccfcd75b02209b686e15426e9b6a)) - Merge pull request #959 from tyrone-wu/aya/program_info_stats ([`ab000ad`](https://github.com/aya-rs/aya/commit/ab000ad7c3b0715c3cdd9798bd08fc834b114f1a)) - Merge pull request #974 from Billy99/billy99-arch-ppc64-s390x ([`ab5e688`](https://github.com/aya-rs/aya/commit/ab5e688fd49fcfb402ad47d51cb445437fbd8cb7)) - Adjust bpf programs for big endian ([`cd1db86`](https://github.com/aya-rs/aya/commit/cd1db86fd490b3c0f03229bd8999a2e67ccecfc4)) - Adjust test byte arrays for big endian ([`eef7346`](https://github.com/aya-rs/aya/commit/eef7346fb2231f8741410381198015cceeebfac9)) - Simplify doctest ([`4362020`](https://github.com/aya-rs/aya/commit/43620206918facbf003d8b878ae28c5b07955167)) - Appease nightly clippy ([`bce3c4f`](https://github.com/aya-rs/aya/commit/bce3c4fb1d0cd6e8f9f64420c59e02a42c96b2c8)) - Expose run_time_ns and run_cnt fields in ProgramInfo ([`a25f501`](https://github.com/aya-rs/aya/commit/a25f501ecebaceaacdd1212fac34f528b51ad0fd)) - Add BPF_ENABLE_STATS syscall function ([`fa6af6a`](https://github.com/aya-rs/aya/commit/fa6af6a20439cccd8ab961f83dce545fb5884dd4)) - Fix PerfEventArray resize logic ([`3d57d35`](https://github.com/aya-rs/aya/commit/3d57d358e40591acf23dfde740697fbfff026410)) - Add comments in `*_wrong_map` tests ([`e575712`](https://github.com/aya-rs/aya/commit/e575712c596d03b93f75d160e3d95241eb895d39)) - Set PerfEventArray max_entries to nCPUs ([`25d986a`](https://github.com/aya-rs/aya/commit/25d986a26d9c88cd499a8b795054d583f01476b2)) - Use MockableFd everywhere ([`e12fcf4`](https://github.com/aya-rs/aya/commit/e12fcf46cb1e0856a8105ed43fda184fa4648713)) - Merge pull request #991 from l2dy/typo-1 ([`2cd9858`](https://github.com/aya-rs/aya/commit/2cd9858ea9381232acaffcb5a08bc74e90a8863e)) - Fix typo ([`f1773d5`](https://github.com/aya-rs/aya/commit/f1773d5af43f5f29b100572e65a60d58f2ce7fac)) - Merge pull request #983 from ajwerner/fix-variable-name ([`d5414bf`](https://github.com/aya-rs/aya/commit/d5414bf10c80ae8cef757f0cdf06bfdd38746daa)) - :programs::uprobe: fix bad variable name ([`d413e2f`](https://github.com/aya-rs/aya/commit/d413e2f285643cbeb665fd3c517e2c9d93d45825)) - Fix panic when creating map on custom ubuntu kernel ([`38d8e32`](https://github.com/aya-rs/aya/commit/38d8e32baa5a4538de9daa6fae634aea6372573c)) - Appease clippy ([`78acd74`](https://github.com/aya-rs/aya/commit/78acd74badb6aa2463f89fbdf713325dad75dc9e)) - Don't deny unused_qualifications ([`781914f`](https://github.com/aya-rs/aya/commit/781914f058ef805bd0780ff72a2a66c63255bc07)) - Fix rustdocs-args ordering in taplo to -D warnings ([`5e13283`](https://github.com/aya-rs/aya/commit/5e13283f59b0c3b4cb47de1e31d8d0960e80b4cc)) - Remove deny(pointer_structural_match) ([`4e843a3`](https://github.com/aya-rs/aya/commit/4e843a35237c2de49d17621dccb4a2a35bb4030c)) - Merge pull request #938 from swananan/enhance_urpobe_symbol_lookup ([`bde4b5f`](https://github.com/aya-rs/aya/commit/bde4b5f86b12a3e4ac2f99898edb1b564fe9dd7e)) - Fix clippy ([`c7898c5`](https://github.com/aya-rs/aya/commit/c7898c596f2f74f29570101d0f71f35b0ab4104b)) - Adjust symbol lookup tests for object crate alignment requirements ([`462514e`](https://github.com/aya-rs/aya/commit/462514ed4c4c06e9618d029a57708c7fa14ab748)) - Add symbol lookup in associated debug files ([`e6e1bfe`](https://github.com/aya-rs/aya/commit/e6e1bfeb58ac392637061640365b057182ee1b39)) - Merge pull request #928 from seanyoung/io-error ([`d0e9b95`](https://github.com/aya-rs/aya/commit/d0e9b95aa5edc6c056687caeb950e1ce44b18d66)) - S/MiriSafeFd/MockableFd/ ([`a11b61e`](https://github.com/aya-rs/aya/commit/a11b61ebfde8713c35b6f2a760e470d3586803a7)) - Remove miri ignores ([`cb6d3bd`](https://github.com/aya-rs/aya/commit/cb6d3bd75d162e4928fdf4daa7f515e1ad85ae85)) - Document miri skip reasons ([`35962a4`](https://github.com/aya-rs/aya/commit/35962a4794484aa3b37dadc98a70a659fd107b75)) - Avoid crashing under Miri ([`7a7d168`](https://github.com/aya-rs/aya/commit/7a7d16885a89af8c10a52e5aba0927784d42f551)) - Deduplicate test helpers ([`7e1666f`](https://github.com/aya-rs/aya/commit/7e1666fb83e5c2b270cb24becb84adebbe29be1a)) - Reduce duplication ([`58e154e`](https://github.com/aya-rs/aya/commit/58e154e1bc4846a6a2afcb8397aa599cfb7ea6fd)) - Expose io_error in SyscallError ([`a6c45f6`](https://github.com/aya-rs/aya/commit/a6c45f61c77c4bbec4409debb8447cd606f0db5d)) - Appease clippy ([`09442c2`](https://github.com/aya-rs/aya/commit/09442c2cbe9513365dfc1df8d4f7cf6f808a67ed)) - Generate new bindings ([`b06ff40`](https://github.com/aya-rs/aya/commit/b06ff402780b80862933791831c578e4c339fc96)) - Appease clippy ([`0a32dac`](https://github.com/aya-rs/aya/commit/0a32dacd2fd2f225f4a3709ac4ea2838a9937378)) - Merge pull request #528 from dave-tucker/rename-all-the-things ([`63d8d4d`](https://github.com/aya-rs/aya/commit/63d8d4d34bdbbee149047dc0a5e9c2b191f3b32d)) - Include license in crate workspace ([`a4e68eb`](https://github.com/aya-rs/aya/commit/a4e68ebdbf0e0b591509f36316d12d9689d23f89)) - Use `Ebpf` instead of `Bpf` ([`57a69fe`](https://github.com/aya-rs/aya/commit/57a69fe9d28e858562a429bacd9a0a7700b96726)) - Provide a deprecated `BpfError` alias ([`110a76c`](https://github.com/aya-rs/aya/commit/110a76cb9a1b2ab5c5ad3b6c0828a4ae670e67a0)) - Rename Bpf to Ebpf ([`8c79b71`](https://github.com/aya-rs/aya/commit/8c79b71bd5699a686f33360520aa95c1a2895fa5)) - Rename BpfRelocationError -> EbpfRelocationError ([`fd48c55`](https://github.com/aya-rs/aya/commit/fd48c55466a23953ce7a4912306e1acf059b498b)) - Rename BpfSectionKind to EbpfSectionKind ([`cf3e2ca`](https://github.com/aya-rs/aya/commit/cf3e2ca677c81224368fb2838ebc5b10ee98419a)) - Rename bpf -> ebpf ([`70ac91d`](https://github.com/aya-rs/aya/commit/70ac91dc1e6f209a701cd868db215763d65efa73)) - Fix unused_qualifications lints ([`481b73b`](https://github.com/aya-rs/aya/commit/481b73b6d8dd9a796d891bba137400c2a43a0afe)) - Add `CgroupDevice::query` ([`542306d`](https://github.com/aya-rs/aya/commit/542306d295e51ac1ec117ce453544f201875af3d)) - Appease new nightly clippy lints ([`e38eac6`](https://github.com/aya-rs/aya/commit/e38eac6352ccb5c2b44d621161a27898744ea397))
## 0.13.0 (2024-10-09) ### Chore - Add comments in `*_wrong_map` tests - Rename bpf -> ebpf - Fix unused_qualifications lints This was failing the docs build. ### Documentation - fix typo - Use `Ebpf` instead of `Bpf` ### New Features - Implement TCX This commit adds the initial support for TCX bpf links. This is a new, multi-program, attachment type allows for the caller to specify where they would like to be attached relative to other programs at the attachment point using the LinkOrder type. - Provide a deprecated `BpfError` alias - Rename Bpf to Ebpf And BpfLoader to EbpfLoader. This also adds type aliases to preserve the use of the old names, making updating to a new Aya release less of a burden. These aliases are marked as deprecated since we'll likely remove them in a later release. ### Bug Fixes - Fix PerfEventArray resize logic There was a logic bug in the previously merged patch where we set the correctly calculated max_entries size with the original. To fix this and prevent regressions a unit test was added. This highlighted that the original map definition needs to be mutated in order for the max_entries change to be properly applied. As such, this resize logic moved out of aya::sys into aya::maps - Set PerfEventArray max_entries to nCPUs Both libbpf and cilium/ebpf have will set the max_entries of a BPF_MAP_TYPE_PERF_EVENT_ARRAY to the number of online CPUs if it was omitted at map definition time. This adds that same logic to Aya. - fix panic when creating map on custom ubuntu kernel - fix rustdocs-args ordering in taplo to -D warnings This fixes the current rustdoc build error by correcting the ordering of `rustdoc-args` to `-D warnings`. Additionally, this also removes the `recorder_arrays` field (defaults to false) so that the order is not modified, which is what caused the error in the first place. ### Other - use FdLink in SockOps programs - remove unwrap and NonZero* in info Addresses the feedback from #1007: - remove panic from `unwrap` and `expect` - Option => Option with `0` mapping to `None` - revamp MapInfo be more friendly with older kernels Adds detection for whether a field is available in `MapInfo`: - For `map_type()`, we treturn new enum `MapType` instead of the integer representation. - For fields that can't be zero, we return `Option` type. - For `name_as_str()`, it now uses the feature probe `bpf_name()` to detect if field is available. Although the feature probe checks for program name, it can also be used for map name since they were both introduced in the same commit. - revamp ProgramInfo be more friendly with older kernels Purpose of this commit is to add detections for whether a field is available in `ProgramInfo`. - For `program_type()`, we return the new enum `ProgramType` instead of the integer representation. - For fields that we know cannot be zero, we return `Option` type. - For `name_as_str()`, it now also uses the feature probe `bpf_name()` to detect if field is available or not. - Two additional feature probes are added for the fields: - `prog_info_map_ids()` probe -> `map_ids()` field - `prog_info_gpl_compatible()` probe -> `gpl_compatible()` field With the `prog_info_map_ids()` probe, the previous implementation that I had for `bpf_prog_get_info_by_fd()` is shortened to use the probe instead of having to make 2 potential syscalls. The `test_loaded_at()` test is also moved into info tests since it is better related to the info tests. - add conversion u32 to enum type for prog, link, & attach type Add conversion from u32 to program type, link type, and attach type. Additionally, remove duplicate match statement for u32 conversion to `BPF_MAP_TYPE_BLOOM_FILTER` & `BPF_MAP_TYPE_CGRP_STORAGE`. New error `InvalidTypeBinding` is created to represent when a parsed/received value binding to a type is invalid. This is used in the new conversions added here, and also replaces `InvalidMapTypeError` in `TryFrom` for `bpf_map_type`. - improve integration tests for info API Improves the existing integraiton tests for `loaded_programs()` and `loaded_maps()` in consideration for older kernels: - Opt for `SocketFilter` program in tests since XDP requires v4.8 and fragments requires v5.18. - For assertion tests, first perform the assertion, if the assertion fails, then it checks the host kernel version to see if it is above the minimum version requirement. If not, then continue with test, otherwise fail. For assertions that are skipped, they're logged in stderr which can be observed with `-- --nocapture`. This also fixes the `bpf_prog_get_info_by_fd()` call for kernels below v4.15. If calling syscall on kernels below v4.15, it can produce an `E2BIG` error because `check_uarg_tail_zero()` expects the entire struct to all-zero bytes (which is caused from the map info). Instead, we first attempt the syscall with the map info filled, if it returns `E2BIG`, then perform syscall again with empty closure. Also adds doc for which version a kernel feature was introduced for better awareness. The tests have been verified kernel versions: - 4.13.0 - 4.15.0 - 6.1.0 - adjust bpf programs for big endian In aya/src/sys/bpf.rs, there are several simple bpf programs written as byte arrays. These need to be adjusted to account for big endian. - expose run_time_ns and run_cnt fields in ProgramInfo Added functions to expose `run_time_ns` & `run_cnt` statistics from ProgramInfo/bpf_prog_info. - add BPF_ENABLE_STATS syscall function Add bpf syscall function for BPF_ENABLE_STATS to enable stats tracking for benchmarking purposes. Additionally, move `#[cfg(test)]` annotation around the `Drop` trait instead. Having separate functions causes some complications when needing ownership/moving of the inner value `OwnedFd` when `Drop` is manually implemented. - :programs::uprobe: fix bad variable name The variable fn_name was very much *not* the fn_name, but rather the object file path. - adjust symbol lookup tests for object crate alignment requirements The object::File::parse API requires parameter to be aligned with 8 bytes. Adjusted the Vec in the tests with miri to meet this requirement. - add symbol lookup in associated debug files This change enhances the logic for symbol lookup in uprobe or uretprobe. If the symbol is not found in the original binary, the search continues in the debug file associated through the debuglink section. Before searching the symbol table, it compares the build IDs of the two files. The symbol lookup will only be terminated if both build IDs exist and do not match. This modification does not affect the existing symbol lookup logic. - Generate new bindings - include license in crate workspace This PR includes the licenses files in the crate workspace subdirectory. Without this, they won't be showing on crates.io and would be giving out errors on tooling such as rust2rpm. - appease new nightly clippy lints ``` error: unnecessary qualification --> aya/src/maps/ring_buf.rs:434:22 | 434 | ptr: ptr::NonNull::new(ptr).ok_or( | ^^^^^^^^^^^^^^^^^ | note: the lint level is defined here --> aya/src/lib.rs:72:5 | 72 | unused_qualifications, | ^^^^^^^^^^^^^^^^^^^^^ help: remove the unnecessary path segments | 434 - ptr: ptr::NonNull::new(ptr).ok_or( 434 + ptr: NonNull::new(ptr).ok_or( | error: unnecessary qualification --> aya/src/maps/mod.rs:225:21 | 225 | let mut limit = std::mem::MaybeUninit::::uninit(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the unnecessary path segments | 225 - let mut limit = std::mem::MaybeUninit::::uninit(); 225 + let mut limit = mem::MaybeUninit::::uninit(); | error: unnecessary qualification --> aya/src/programs/mod.rs:614:9 | 614 | crate::obj::Program { | ^^^^^^^^^^^^^^^^^^^ | help: remove the unnecessary path segments | 614 - crate::obj::Program { 614 + obj::Program { | error: unnecessary qualification --> aya/src/util.rs:373:14 | 373 | unsafe { std::slice::from_raw_parts(bpf_name.as_ptr() as *const _, length) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: remove the unnecessary path segments | 373 - unsafe { std::slice::from_raw_parts(bpf_name.as_ptr() as *const _, length) } 373 + unsafe { slice::from_raw_parts(bpf_name.as_ptr() as *const _, length) } | error: unnecessary qualification --> aya/src/maps/mod.rs:1130:47 | 1130 | .copy_from_slice(unsafe { std::mem::transmute(TEST_NAME) }); | ^^^^^^^^^^^^^^^^^^^ | note: the lint level is defined here --> aya/src/lib.rs:72:5 | 72 | unused_qualifications, | ^^^^^^^^^^^^^^^^^^^^^ help: remove the unnecessary path segments | 1130 - .copy_from_slice(unsafe { std::mem::transmute(TEST_NAME) }); 1130 + .copy_from_slice(unsafe { mem::transmute(TEST_NAME) }); | ``` ### Performance - cache `nr_cpus` in a thread_local ### Test - adjust test byte arrays for big endian Adding support for s390x (big endian architecture) and found that some of the unit tests have structures and files implemented as byte arrays. They are all coded as little endian and need a bug endian version to work properly. ### New Features (BREAKING) - Rename BpfRelocationError -> EbpfRelocationError - Rename BpfSectionKind to EbpfSectionKind ## 0.12.0 (2024-02-28) ### Chore - Use the cargo workspace package table This allows for inheritance of common fields from the workspace root. The following fields have been made common: - authors - license - repository - homepage - edition - Appease clippy unused imports - tracefs review fixes ### Chore - Don't use path deps in workspace This moves the path dependencies back into the per-crate Cargo.toml. It is required such that the release tooling can correctly calculate which version constraints require changing when we perform a release. ### Documentation - Document breaking changes This provides a `BREAKING-CHANGES.md` that we can populate per-crate. Doing so will allow us to pull this content into our changelog and websites to make things easier for users. - Add labels for optional features Following the lead of crates like tokio and nix, we now annotate APIs that require optional features. This helps in cases where a user wants to have an `AsyncPerfEventArray` which is documented on crates.io, but it's not obvious that you have to enable the `async` feature. - Add crabby logo - Document more breaking changes - Add CHANGELOG ### New Features - get_tracefs function ### Bug Fixes - invalid transmute when calling fd Corrent an invalid transmutation for sock_map. fd is already a ref of MapFd, so transmuting &fd to &SockMapFd is equivalent to transmuting &&SockMapFd into &SockMapFd which is buggy. - Relax unnecessarily strict atomic ordering on probe event_alias - remove useless `any` `all` in cfg. ### Other - reformat to please rustfmt - export some missing modules Previously we were only re-exporting the program types from these, so links and other pub types were not exported. - perf_event: add inherit argument to attach() - add StackTraceMap::remove() - appease new nightly clippy lints ``` error: this call to `as_ref.map(...)` does nothing --> aya/src/bpf.rs:536:30 | 536 | let btf_fd = btf_fd.as_ref().map(Arc::clone); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `btf_fd.clone()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref note: the lint level is defined here --> aya/src/lib.rs:41:5 | 41 | clippy::all, | ^^^^^^^^^^^ = note: `#[deny(clippy::useless_asref)]` implied by `#[deny(clippy::all)]` error: could not compile `aya` (lib) due to 1 previous error warning: build failed, waiting for other jobs to finish... error: initializer for `thread_local` value can be made `const` --> aya/src/sys/fake.rs:14:61 | 14 | pub(crate) static TEST_MMAP_RET: RefCell<*mut c_void> = RefCell::new(ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(ptr::null_mut()) }` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#thread_local_initializer_can_be_made_const = note: `#[deny(clippy::thread_local_initializer_can_be_made_const)]` implied by `#[deny(clippy::all)]` ``` - appease nightly lint ``` error: lint `unused_tuple_struct_fields` has been renamed to `dead_code` --> aya/src/lib.rs:74:5 | 74 | unused_tuple_struct_fields, | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `dead_code` | = note: `-D renamed-and-removed-lints` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` ``` See https://github.com/rust-lang/rust/commit/9fcf9c141068984ffcbb4cb00c. - add SchedClassifier::attach_to_link Similar to Xdp::attach_to_link, can be used to replace/upgrade the program attached to a link. - add SchedClassifierLink::attach_type() getter The link already exposes priority() and handle(). Expose attach_type() too. - Fix ringbuf docs doctests are not running in CI and therefore the didn't catch the ringbuf docs failures. This commit fixes the issues in the examples. - pin for (async)perf_event_array Implement pinning for perf_event_array and async_perf_event_array. Additionally make the core MapData.pin method operate on a reference rather than a mutable reference. - make RingBuf: Send + Sync There was no reason for them not to be -- the APIs all require mutable references and hold onto mutable references, so there cannot be internal concurrency. The !Send + !Sync came from the MMap, but not for any good reason. - extracting program and map names with the same function - add MapInfo struct following the same pattern as ProgramInfo This makes the APIs for loading maps and programs more similar. - support loading a map by fd This adds support to loading maps by fd similarly to the way programs can be loaded by fd. - make KernelVersion::code public - Add markdownlint This adds a linter to catch common markdown formatting errors. The linter used is markdownlint-cli2 which is available on all platforms and has an associated Github Action to automate these checks in CI. Configuration is checked in at .markdownlint-cli2.yaml. You may run the check locally using `markdownlint-cli2`. Or you may install the extension for VSCode: DavidAnson.vscode-markdownlint - update async-io requirement from 1.3 to 2.0 Updates the requirements on [async-io](https://github.com/smol-rs/async-io) to permit the latest version. - [Release notes](https://github.com/smol-rs/async-io/releases) - [Changelog](https://github.com/smol-rs/async-io/blob/master/CHANGELOG.md) - [Commits](https://github.com/smol-rs/async-io/compare/v1.3.0...v1.13.0) --- updated-dependencies: - dependency-name: async-io dependency-type: direct:production ... - fix unused async-io dependency linter error Not using the `dep:` syntax created a Cargo feature flag for async-io, though this feature alone does nothing without the `async_std` or `async_tokio` features. - Implement RingBuf This implements the userspace binding for RingBuf. Instead of streaming the samples as heap buffers, the process_ring function takes a callback to which we pass the event's byte region, roughly following [libbpf]'s API design. This avoids a copy and allows marking the consumer pointer in a timely manner. - move mmap from perf_buffer.rs to sys/mod.rs mmap() is needed for the ring buffer implementation, so move it to a common module - impl From for MapTypeError - sort variants Missed in 5e637071c130fece2b26f6a7246bdef5f782fced due to merge skew with 7b71c7e1cd8d6948764d02afb0279151c6eae437. - import types from std::ffi rather than libc - sort variants - remove redundant keys `default-features = false` is already in the root Cargo.toml. - add pin() api - Adds new `maps_mut()` API to the BpfManager to allow us to iterate though and pin all of maps at the same time. - Adds new pin(Path)/unpin(Path) api to Maps so they can be generically pinned AFTER load. - Adds macro for pinning explicit map types in aya. Convert all explicit map types "inner" field to be pub crate in order to facilitate this. - fix libbpf_pin_by_name Aligns with libbpf for the special LIBBPF_PIN_BY_NAME map flag. Specifically if the flag is provided without a pin path default to "/sys/fs/bpf". - Deprecate `syscall_prefix` Using the prefix only for the host architecture is often not enough, kernels usually provide symbols for more architectures, which are used by multilib applications. Handling them might or might not be necessary depending on the use case. Due to that complexity, we decided to let the callers to handle prefixes the way they prefer. - group_imports = "StdExternalCrate" High time we stop debating this; let the robots do the work. - Fix program loading on kernels with a patch > 255 - fix load time and add test Time since boot is defined as the UNIX_EPOCH plus the duration since boot. which is realtime - boottime NOT boottime - realtime. Add a integration test to ensure this doesn't happen again. - make maps work on kernels not supporting ProgIds On startup, the kernel is probed for support of chained program ids for CpuMap, DevMap and DevMapHash, and will patch maps at load time to have the proper size. Then, at runtime, the support is checked and will error out if a program id is passed when the kernel does not support it. - use ProgramFd instead of impl AsRawFd Not having a generic here allows to pass `None` without specifying the actual type you don't care about. - add documentation for XDP maps - fix docstring missing trailing period - add support for chained xdp programs in {cpu,dev}map set/insert functions can now take an optional bpf program fd to run once the packet has been redirected from the main probe - add support for map-bound XDP programs Such programs are to be bound to cpumap or devmap instead of the usual network interfaces. - Update XDP maps implementations Map impls changed since this was first written. - Implement XDP Map Types This commit adds implementations for: - xskmap - devmap - devmap_hash - cpumap Which can all be used to redirect XDP packets to various different locations - Make MapData::pin pub This is to solve a use-case where a user (in this case bpfd) may want to: - MapData::from_pin to open a pinned map from bpffs - MapData::pin to pin that object into another bpffs Both operations should be easily accomplished without needing to cast a MapData into a concrete Map type - e.g aya::maps::HashMap. - Remove MapData::pinned BPF objects can be pinned multiple times, to multiple different places. Tracking whether or not a map is pinned in a bool is therefore not sufficient. We could track this in a HashSet, but there is really no reason to track it at all. - fix typos, avoid fallible conversions - MapData::{obj, fd} are private - `MapFd` and `SockMapFd` are owned - add program_info() api to program Add a new api to the outer level `Program` structure which allows users to get the program's kernel info before casting it to an explicit program variant. - support TryFrom for LRU hash maps The macro to implement TryFrom for MapData didn't have the ability to specify that more than one variant of MapData can be valid for a single map implementation. Support for new syntax was added to the macro so that the implementation can succeed for both valid variants in the HashMap and PerCpuHashMap impl. - rework TryFrom macros The old macros were repetitive and inflexible. This unifies the various macros used to generate TryFrom implementations for map implementations from the relevant map enum variants. Cleanup in anticipation of fixing #636. The API changes are just about renaming the return to Self and Self::Error; they are not real changes. - access inner through async Avoid holding onto raw file descriptors. Remove some implied bounds (BorrowMut implies Borrow). - ProgAttachLink and LircLink hold owned FDs - use OwnedFd - Use AsFd when attaching fds to programs This is a breaking change but adds another level of safety to ensure the file descriptor we receive is valid. Additionally, this allows aya to internally easily duplicate this file descriptor using std library methods instead of manually calling `dup` which doesn't duplicate with the CLOSE_ON_EXEC flag that is standard pratice to avoid leaking the file descriptor when exec'ing. - Use BorrowedFd when using the program fd in sys/bpf.rs This commit reveals but does not address a file descriptor leak in LircLink2::query. This function returns a list of `LircLink`s where each of them have a program file descriptor that is not going to be closed. This commit does not add this leak; it merely makes it louder in the code. - support non-UTF8 probing - avoid path UTF-8 assumptions - deny various allow-by-default lints - fix docs build Appease the new lint rustdoc::redundant_explicit_links that was added in https://github.com/rust-lang/rust/pull/113167. - BloomFilter::insert takes &mut self This is consistent with all the other maps. - MapData::fd is non-optional The primary driver of change here is that `MapData::create` is now a factory function that returns `Result` rather than mutating `&mut self`. The remaining changes are consequences of that change, the most notable of which is the removal of several errors which are no longer possible. - use RAII to close FDs - `ProgramData::attach_prog_fd` is owned This prevents a file descriptor leak when extensions are used. This is an API breaking change. - `ProgramFd` is owned - add helper methods for ProgramInfo - Add helper methods to get useful information from the ProgramInfo object which is returned by the `loaded_programs()` API. Specifically this code mirrors the `bpftool prog` command in terms of useful fields. - Add a new API macro to each aya `Program` type to allow us to fetch its accompanying `ProgramInfo` metadata after its been loaded. - Add a new ProgramInfo constructor that builds a new instance using a raw fd. - Add a smoke test for the loaded_programs() API as well as all the relevant methods on the ProgramInfo type. - Plug attach_btf_obj_fd leak - Don't store bpf_fd in MapData This is only used in create and therefore can be passed as a parameter. - refactor btf_obj_get_info_by_fd to share code - add map_ids to bpf_prog_get_info_by_fd Allows the caller to pass a slice which the kernel will populate with map ids used by the program. - avoid vector allocation when parsing ksyms - Use OwnedFd in FdLink. - Remove name from ProgramSection The name here is never used as we get the program name from the symbol table instead. - refactor target resolution This attempts to do fewer lossy conversions and to avoid some allocations. - extract library path resolving The function is extracted so that a test could be written. This test is valid on linux-gnu targets, and it doesn't need any special privileges. This is in anticipation of removing the code that uses this functionality (seemingly incidentally) from integration tests. - Set BPF_F_SLEEPABLE for sleepable programs - preallocate the vector This code badly needs tests :( - plug file descriptor leak This leaked a file descriptor if bpf_prog_get_info_by_fd failed. - push error construction up - make `loaded_programs` opaque - extract common SyscallError We currently have 4 copies of this. - `sys_bpf` takes mut ref Some syscalls mutate the argument, we can't be passing an immutable reference here. - avoid repeating BPF_BTF_LOAD dance - Return `OwnedFd` for `perf_event_open`. This fixes a file descriptor leak when creating a link of BPF_PERF_EVENT attach type. - better panic messages Always include operands in failing assertions. Use assert_matches over manual match + panic. - Use Arc when loading BTF fd This fixes an existing file descriptor leak when there is BTF data in the loaded object. To avoid lifetime issues while having minimal impact to UX the `OwnedFd` returned from the BPF_BTF_LOAD syscall will be wrapped in an `Arc` and shared accross the programs and maps of the loaded BPF file. - Make SysResult generic on Ok variant - bpf_prog_get_fd_by_id returns OwnedFd - Define dependencies on the workspace level This way we will avoid version mismatches and make differences in features across our crates clearer. - Ignore embedded BTF error if not truely required This allows fallback to BTF manual relocation when BTF loading fail when not truely required. - compile C probes using build.rs - Add libbpf as a submodule. This prevents having to plumb its location around (which can't be passed to Cargo build scripts) and also controls the version against which codegen has run. - Move bpf written in C to the integration-test crate and define constants for each probe. - Remove magic; each C source file must be directly enumerated in the build script and in lib.rs. - don't allocate static strings - Make Features part of the public API This commit adds a new probe for bpf_attach_cookie, which would be used to implement USDT probes. Since USDT probes aren't currently supported, we this triggers a dead_code warning in clippy. There are cases where exposing FEATURES - our lazy static - is actually helpful to users of the library. For example, they may wish to choose to load a different version of their bytecode based on current features. Or, in the case of an orchestrator like bpfd, we might want to allow users to describe which features their program needs and return nice error message is one or more nodes in their cluster doesn't support the necessary feature set. To do this without breaking the API, we make all the internal members of the `Features` and `BtfFeatures` structs private, and add accessors for them. We then add a `features()` API to avoid leaking the lazy_static. - Remove iter_key from LPM Trie API Based on the discussion in Discord we've decided to drop the iter_key() API for LPM Trie. According to the kernel self-tests and experimentation done in Aya, providing a key into bpf_map_get_next_id will either: - If key is an EXACT match, proceed iterating through all keys in the trie from this point - If key is NOT an EXACT match, proceed iterating through all keys in the trie starting at the leftmost entry. An API in Aya could be crafted that gets the LPM match + less specific matches for a prefix using these semantics BUT it would only apply to userspace. Therefore we've opted out of fixing this. - replace os::unix::prelude with os::fd - allow global value to be optional This allow to not error out when a global symbol is missing from the object. - add syscall_prefix and syscall_fnname_add_prefix These two functions are needed because kernel symbols representing syscalls have architecture-specific prefixes. These are the equivalent of bcc's get_syscall_fnname and get_syscall_prefix. - Fix uprobe support on 4.16 and lower Fix uprobe support on Ubuntu 18.04. - Add support for old ld.so.cache format This fix uprobe support on Debian 10. (and possibly others) This implement support to parse the original libc5 format. - Make probe event_alias unique This fixes issues when trying to attach the same kernel function multiple times on 4.17 and lower (possibly upper too?) - Do not create data maps on kernel without global data support Fix map creation failure when a BPF have a data section on older kernel. (< 5.2) If the BPF uses that section, relocation will fail accordingly and report an error. - Move program's functions to the same map - update bitflags requirement from 1.2.1 to 2.2.1 Updates the requirements on [bitflags](https://github.com/bitflags/bitflags) to permit the latest version. - [Release notes](https://github.com/bitflags/bitflags/releases) - [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md) - [Commits](https://github.com/bitflags/bitflags/compare/1.2.1...2.2.1) - update object requirement from 0.30 to 0.31 Updates the requirements on [object](https://github.com/gimli-rs/object) to permit the latest version. - [Release notes](https://github.com/gimli-rs/object/releases) - [Changelog](https://github.com/gimli-rs/object/blob/master/CHANGELOG.md) - [Commits](https://github.com/gimli-rs/object/compare/0.30.0...0.31.0) --- updated-dependencies: - dependency-name: object dependency-type: direct:production ... - flip feature "no_std" to feature "std" This fixes `cargo build --all-features` by sidestepping the feature unification problem described in The Cargo Book[0]. Add `cargo hack --feature-powerset` to CI to enforce that this doesn't regress (and that all combinations of features work). Since error_in_core is nightly-only, use core-error and a fake std module to allow aya-obj to build without std on stable. [0] https://doc.rust-lang.org/cargo/reference/features.html#feature-unification - Correctly set the kernel code version for Debian kernel Fix BPF syscall failure related to the kernel code version. - Correctly set the kernel code version for Ubuntu kernel Fix BPF syscall failure related to the kernel code version. - Add sanitize code for kernels without bpf_probe_read_kernel Required for kernel before 5.5. Also move Features to aya-obj. - Do not use unwrap with btf_fd in bpf_create_map Fixes a crash when trying to create a map of type BPF_MAP_TYPE_PERCPU_ARRAY when btf_fd is None. Tested on Ubuntu 18.04 (4.15.0-202-generic) - support relocations across multiple text sections + fixes Fix R_BPF_64_64 text relocations in sections other than .text (for instance .text.unlikely). Also fix misc bugs triggered by integration tests. - make it possible to use set_global() with slices of Pod(s) - make it possible to use set_global() with slices of Pod(s) - Allow to attach XDP probe by interface index - Fix MapData Clone implementation The Clone implementation of MapData was previously not storing the result of the dup operation. - Add loaded_programs() API to list all loaded programs This uses a Programs iterator to yield all loaded bpf programs using bpf_prog_get_next_id. - MapData should be Borrow, not AsRef We don't ever do ref-to-ref conversion for MapData so Borrow should suffice. - Fix is_perf_link_supported This was mistakenly comparing the exit code of the syscall, which is always -1 and not the corresponding error-code. Added unit tests to ensure we don't regress. - More discrete feature logging Just use the Debug formatter vs. printing a message for each probe. - Enable bpf_link for perf_attach programs This adds support for bpf_link to PerfEvent, Tracepoint, Kprobe and Uprobe programs. - Add probe for bpf_link_create for perf programs - Make features a lazy_static - Add support for multibuffer programs This adds support for loading XDP programs that are multi-buffer capable, which is signalled using the xdp.frags section name. When this is set, we should set the BPF_F_XDP_HAS_FRAGS flag when loading the program into the kernel. - Add from_pin for Programs This commit adds from_pin() which allows the creation of a Program from a path on bpffs. This is useful to be able to call `attach` or other APIs for programs that are already loaded to the kernel. This differs from #444 since it implements this on the concrete program type, not the Program enum, allowing the user to pass in any additional context that isn't available from bpf_prog_info. - fix Lru and LruPerCpu hash maps They were broken by https://github.com/aya-rs/aya/pull/397 - update documentation and versioning info - Set the version number of `aya-obj` to `0.1.0`. - Update the description of the `aya-obj` crate. - Add a section in README and rustdoc warning about the unstable API. - add basic documentation to public members Types relevant to maps are moved into aya_obj::maps. Some members are marked `pub(crate)` again. - migrate aya::obj into a separate crate To split the crate into two, several changes were made: 1. Most `pub(crate)` are now `pub` to allow access from Aya; 2. Parts of BpfError are merged into, for example, RelocationError; 3. BTF part of Features is moved into the new crate; 4. `#![deny(missing_docs)]` is removed temporarily; 5. Some other code gets moved into the new crate, mainly: - aya::{bpf_map_def, BtfMapDef, PinningType}, - aya::programs::{CgroupSock*AttachType}, The new crate is currenly allowing missing_docs. Member visibility will be adjusted later to minimize exposure of implementation details. - migrate bindgen destination - make btf::RelocationError private - fix regression computing pointer sizes Computing pointer sizes was broken in #285 - fix detaching links on drop - add missing TryFrom for HashMap, PerCpuHashMap and LpmTrie - update object requirement from 0.29 to 0.30 Updates the requirements on [object](https://github.com/gimli-rs/object) to permit the latest version. - [Release notes](https://github.com/gimli-rs/object/releases) - [Changelog](https://github.com/gimli-rs/object/blob/master/CHANGELOG.md) - [Commits](https://github.com/gimli-rs/object/compare/0.29.0...0.30.0) --- updated-dependencies: - dependency-name: object dependency-type: direct:production ... - Fix the error message in `MapData::pin()` The syscall name is `BPF_OBJ_PIN`, not `BPF_OBJ_GET`. - Remove unused dependencies - Disable miri warnings about integer-to-pointer conversions `override_syscall` performs integer-to-pointer conversion. This is considered harmful on the newest Rust nightly which provides `ptr::from_exposed_addr`, but there is no other way on Rust stable than doing `as *const T`, which is what miri is unhappy about. - add BpfLoader::set_max_entries Add BpfLoader::set_max_entries, which sets the max_entries for the specified map, as the load-time option. The max_entries set at map initialization in the ebpf component can be overwritten by this method called on the userspace component. If you want to set max_entries for multiple maps in an ebpf component, you can do so by calling set_max_entries in the form of a method chain. - Rename from_pinned and from_path to from_pin - Fix review comments from #387 - Add integration test for pinning lifecycle - Replace From for XdpLink with TryFrom - Rename bpf_obj_get_info_by_id - fix miss doc period - change variant names - More pinning fixes This commit fixes a bug and adds some missing lifecycle APIs. 1. Adds PinnedLink::from_path to create a pinned link from bpffs 2. Adds From for FdLink to allow for ^ to be converted 3. Adds From for XdpLink - Fix segfault in define_link_wrapper The From<$wrapper> for $base implemention is refers to itself, eventually causing a segfault. - Improved BTF Type API This commit removes reliance on generated BtfType structs, as well as adding a dedicated struct for each BTF type. As such, we can now add nice accessors like `bits()` and `encoding()` for Int vs. inlined shift/mask operations. - update `VerifierLogLevel` to use bitflags - Fix Link Pinning 1. Removes OwnedLink 2. Allows Links to be converted into FdLink 3. Introduces a PinnedLink type to handle wrap FdLink when pinned and support un-pinning - update `VerifierLogLevel` level variants - use enum to set verifier log level - expose BPF verifier log level configuration - Remove MapError::InvalidPinPath - Use PinError for all pinning errors - Implement FdLink::pin() This allows for FdLinks to also be pinned to BpfFs. In order for it to be called, the user would first call `take_link` to get the underlying link. This can then be destructured to an FdLink where FdLink::pin() may be called. - Allow pin to be used on all programs This allows for `pin` to be called as `Xdp::pin()` or Program::pin() - the same way that unload() can be used. This simplifies the use of this API. - Fix rlimit warning on for 32bit systems - Raise the RLIMIT_MEMLOCK warning only if failed to create a map Also, mention that setting the RLIMIT_MEMLOCK to a higher value is an option. - Raise the warning when RMILIT_MEMLOCK is not RLIM_INFINITY Kernels before 5.11 don't use cgroup accounting, so they might reach the RLIMIT_MEMLOCK when creating maps. After this change, we raise a warning recommending to raise the RLIMIT_MEMLOCK. - Fix latest nightly lints - update object requirement from 0.28 to 0.29 Updates the requirements on [object](https://github.com/gimli-rs/object) to permit the latest version. - [Release notes](https://github.com/gimli-rs/object/releases) - [Changelog](https://github.com/gimli-rs/object/blob/master/CHANGELOG.md) - [Commits](https://github.com/gimli-rs/object/compare/0.28.0...0.29.0) --- updated-dependencies: - dependency-name: object dependency-type: direct:production ... - Improve Extension Docs - Add Extension::attach_to_program() This allows for Extension programs already loaded to the kernel to be attached to another program that is BTF-compatible with the one provided at `load()` time - Replace ProgramFd trait with struct This removes the ProgramFd trait with a struct that wraps a RawFd. Program::fd() has been implemented as well as fd() for each Program Type. This allows for a better API than requiring the use of the ProgramFd trait. - Implement attach_to_link for XDP - Add support for bpf_link_update - Add Map::fd() function to return a MapFd - Add crabby, sync with aya/README.md - Implement BPF_PROG_TYPE_CGROUP_SOCK - Unload programs on drop ### Test - avoid lossy string conversions We can be strict in tests. - s/assert!(.*) ==/assert_eq!\1,/ One case manually adjusted to `assert_matches!`. - add the possibility to run a test inside a network namespace For tests that do networking operations, this allows to have a clean-state network namespace and interfaces for each test. Mainly, this avoids "device or resource busy" errors when reusing the loopback interface across tests. ### Commit Statistics - 434 commits contributed to the release. - 631 days passed between releases. - 182 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release aya-obj v0.1.0, aya v0.12.0, safety bump aya-log v0.2.0 ([`0e99fa0`](https://github.com/aya-rs/aya/commit/0e99fa0f340b2fb2e0da3b330aa6555322a77eec)) - Don't use path deps in workspace ([`13b1fc6`](https://github.com/aya-rs/aya/commit/13b1fc63ef2ae083ba03ce9de24cb4f31f989d21)) - Merge pull request #892 from dave-tucker/breaking-changes-v2 ([`daa5a47`](https://github.com/aya-rs/aya/commit/daa5a473105e0c99f5f171ba519d076a7157af6e)) - Merge pull request #891 from dave-tucker/changelog ([`431ce23`](https://github.com/aya-rs/aya/commit/431ce23f27ef5c36a6b38c73b38f23b1cf007900)) - Document more breaking changes ([`2d9d7a1`](https://github.com/aya-rs/aya/commit/2d9d7a1a0b8fb944a9843642e85480b16c11bd11)) - Add CHANGELOG ([`12280a8`](https://github.com/aya-rs/aya/commit/12280a83f967ba9a90dcd066b3470f4bcc4ea77c)) - Merge pull request #889 from dave-tucker/breaking-changes ([`5c9c044`](https://github.com/aya-rs/aya/commit/5c9c044719f84dcb76edfa496e3999194253b5c4)) - Document breaking changes ([`281ac1a`](https://github.com/aya-rs/aya/commit/281ac1ac02cf0da7be1161b25c2ef023b922bc0c)) - Merge pull request #882 from dave-tucker/metadata ([`0fadd69`](https://github.com/aya-rs/aya/commit/0fadd695377b8a3f0d9a3af3bc8140f0f1bed8d2)) - Use the cargo workspace package table ([`b3e7ef7`](https://github.com/aya-rs/aya/commit/b3e7ef741c5b8d09fc7dc8302576f8174be75ff4)) - Merge pull request #885 from dave-tucker/nightly-up ([`2d72197`](https://github.com/aya-rs/aya/commit/2d721971cfae39e168f0dc4dac1f219490c16fbf)) - Appease clippy unused imports ([`770a95e`](https://github.com/aya-rs/aya/commit/770a95e0779a6a943c2f5439334fa208ac2ca7e6)) - Appease rustc dead_code lint ([`963dd13`](https://github.com/aya-rs/aya/commit/963dd1321925c95f80c8a2bf656b88a39497ca01)) - Invalid transmute when calling fd ([`c31cce4`](https://github.com/aya-rs/aya/commit/c31cce4a368ac56b42196604ef110139d28a2f8e)) - Merge pull request #878 from alessandrod/missing-exports ([`46b4805`](https://github.com/aya-rs/aya/commit/46b48053df07d813ee519598382f74cead5fd602)) - Reformat to please rustfmt ([`2be705b`](https://github.com/aya-rs/aya/commit/2be705bfa04a80b1c4b58a69750e485aa0f2639a)) - Reorder imports a bit ([`9b4f876`](https://github.com/aya-rs/aya/commit/9b4f87646d2e0973be6ff30cb1acf26e1f416b3f)) - Export some missing modules ([`d570450`](https://github.com/aya-rs/aya/commit/d570450a0c4622a5a8e7e62b321847d3155af1ea)) - Perf_event: add inherit argument to attach() ([`0f6a734`](https://github.com/aya-rs/aya/commit/0f6a7343926b23190483bed49855fdc9bb10988d)) - Add StackTraceMap::remove() ([`92b1947`](https://github.com/aya-rs/aya/commit/92b194788527b1318e262a3b9bb4558339aee05b)) - Merge pull request #865 from tamird/appease-lint ([`09851a2`](https://github.com/aya-rs/aya/commit/09851a2090287b531652e8547a14c70532633d6c)) - Appease new nightly clippy lints ([`7022528`](https://github.com/aya-rs/aya/commit/7022528f04e08ef1a79ef0fee78323f29b6cc81c)) - Merge pull request #861 from tamird/appease-lint ([`604742a`](https://github.com/aya-rs/aya/commit/604742a2f218012ea967d220ecf7be9b74cda8b8)) - Appease nightly lint ([`7c1bfef`](https://github.com/aya-rs/aya/commit/7c1bfeffe8988bb60020d6b61ee0f10aa5f1e1e7)) - Add SchedClassifier::attach_to_link ([`2257cbe`](https://github.com/aya-rs/aya/commit/2257cbeccb18a3f486c9d64b52b33a331c89531e)) - Add SchedClassifierLink::attach_type() getter ([`b13645b`](https://github.com/aya-rs/aya/commit/b13645b13da5b843728959e0416617ea19096613)) - Merge pull request #858 from dave-tucker/ringbuf-doctests ([`13f21dc`](https://github.com/aya-rs/aya/commit/13f21dce1b875b225e6561b2b734411cde2a225a)) - Fix ringbuf docs ([`e9e2f48`](https://github.com/aya-rs/aya/commit/e9e2f48d4fa8825fec9d343e76999d58b170cdd8)) - Pin for (async)perf_event_array ([`b176967`](https://github.com/aya-rs/aya/commit/b1769678f48f7abf6c987a1d686bbaffd5d1e664)) - Merge pull request #843 from ajwerner/ringbuf-send-sync ([`931cd55`](https://github.com/aya-rs/aya/commit/931cd55905d74192a80dbbf22a758a53135f8716)) - Make RingBuf: Send + Sync ([`c06fcc3`](https://github.com/aya-rs/aya/commit/c06fcc3edafe8efefc90d2eff1b4b4a5489fb9eb)) - Extracting program and map names with the same function ([`15faca8`](https://github.com/aya-rs/aya/commit/15faca8b2eddfad22594824cc634bfa1e540eeaa)) - Add MapInfo struct following the same pattern as ProgramInfo ([`4d24d1c`](https://github.com/aya-rs/aya/commit/4d24d1cfe8108365403d834e40efa3fa72983f6d)) - Support loading a map by fd ([`36420d9`](https://github.com/aya-rs/aya/commit/36420d929734beb7486cc5d14944fc7cf8e9b62a)) - Make KernelVersion::code public ([`68ba020`](https://github.com/aya-rs/aya/commit/68ba02002fbd3bcf157c72b8212a697551cae8e6)) - Merge pull request #746 from dave-tucker/markdownlint ([`958931e`](https://github.com/aya-rs/aya/commit/958931efcbfd86cab2220b36b7ebb2b34c18a842)) - Add markdownlint ([`8780a50`](https://github.com/aya-rs/aya/commit/8780a50be194f7d7c41f6886f1c5de8eee4e59d0)) - Merge pull request #825 from aya-rs/dependabot/cargo/async-io-2.0 ([`67fe16e`](https://github.com/aya-rs/aya/commit/67fe16e723c13b30fa4a2a73d58eda812d7ef517)) - Update async-io requirement from 1.3 to 2.0 ([`c89b2d1`](https://github.com/aya-rs/aya/commit/c89b2d156dbddd495f885edecbf71910cc61bba8)) - Merge pull request #821 from Tuetuopay/fix-udeps ([`f037a94`](https://github.com/aya-rs/aya/commit/f037a94c9f0f3ea4ccdfec631446384e188b64c5)) - Fix unused async-io dependency linter error ([`984c08c`](https://github.com/aya-rs/aya/commit/984c08cbad73c51a501b528c53e72f6130976639)) - Merge pull request #629 from ajwerner/ringbuf ([`6284994`](https://github.com/aya-rs/aya/commit/62849944f2d807e2214984f40ca0ee8193585f18)) - Implement RingBuf ([`e2cf734`](https://github.com/aya-rs/aya/commit/e2cf734490bc188bcedb1eac92d23d81123e42cd)) - Move mmap from perf_buffer.rs to sys/mod.rs ([`4af9d1b`](https://github.com/aya-rs/aya/commit/4af9d1bd3ea8dd638bddeb2ae2a8ccea6d11b249)) - Impl From for MapTypeError ([`b73c0a4`](https://github.com/aya-rs/aya/commit/b73c0a46f572a77d6d05d96d65f638848ac9b132)) - Merge pull request #814 from tamird/sort-variants-again ([`cb455fe`](https://github.com/aya-rs/aya/commit/cb455febbbb350ae1ad15eeb465b5684077b5168)) - Sort variants ([`8462b69`](https://github.com/aya-rs/aya/commit/8462b69716d5918a599933bb9688fa7f57b8ee1d)) - Merge pull request #812 from tamird/redundant-cargo ([`715d490`](https://github.com/aya-rs/aya/commit/715d49022eefb152ef8817c730d9eac2b3e6d66f)) - Merge pull request #813 from tamird/sort-variants ([`ae612a0`](https://github.com/aya-rs/aya/commit/ae612a0a1061ea65237b62a7d9fa1f2f583dff36)) - Merge pull request #811 from tamird/libc ([`b7ceee4`](https://github.com/aya-rs/aya/commit/b7ceee4f51da23b124f4f500c7685b65d89d20d0)) - Import types from std::ffi rather than libc ([`5cdd1ba`](https://github.com/aya-rs/aya/commit/5cdd1baf291f7d98128257a6a73cf8df2c144908)) - Sort variants ([`5e63707`](https://github.com/aya-rs/aya/commit/5e637071c130fece2b26f6a7246bdef5f782fced)) - Remove redundant keys ([`cc48523`](https://github.com/aya-rs/aya/commit/cc48523347c2be5520779ef8eeadc6d3a68649d0)) - Merge pull request #783 from astoycos/map_pin2 ([`ef27bce`](https://github.com/aya-rs/aya/commit/ef27bce619981ef8be8b368ac39054f4ce0c5505)) - Add pin() api ([`7b71c7e`](https://github.com/aya-rs/aya/commit/7b71c7e1cd8d6948764d02afb0279151c6eae437)) - Fix libbpf_pin_by_name ([`0bf97eb`](https://github.com/aya-rs/aya/commit/0bf97eba64b44835300d8291cd4f78c220c3ad48)) - Merge pull request #806 from vadorovsky/deprecate-syscall-prefix ([`66bd85a`](https://github.com/aya-rs/aya/commit/66bd85a8de011acd207dd806ccafb6ec56a73500)) - Deprecate `syscall_prefix` ([`bd6ba3a`](https://github.com/aya-rs/aya/commit/bd6ba3ad8bae0537eee9eb78d20620592daa3c76)) - Merge pull request #797 from aya-rs/rustfmt-group-imports ([`373fb7b`](https://github.com/aya-rs/aya/commit/373fb7bf06ba80ee4c120d8c112f5e810204c472)) - Group_imports = "StdExternalCrate" ([`d16e607`](https://github.com/aya-rs/aya/commit/d16e607fd4b6258b516913071fdacafeb2bbbff9)) - Merge pull request #791 from nrxus/fix-kernel-code-on-submode-gt-255 ([`6786383`](https://github.com/aya-rs/aya/commit/67863833ca87ddeb51e3dd7fd5ec87a22f7be63a)) - Fix program loading on kernels with a patch > 255 ([`0a6a267`](https://github.com/aya-rs/aya/commit/0a6a2674fa6cbfda986b20d76f64802f0f65c2f0)) - Merge pull request #527 from Tuetuopay/xdpmaps ([`7f9ce06`](https://github.com/aya-rs/aya/commit/7f9ce062f4b8b5cefbe07d8ea47363266f7eacd1)) - Aya, bpf: misc fixes following review comments ([`579e3ce`](https://github.com/aya-rs/aya/commit/579e3cee22ae8e932efb0894ca7fd9ceb91ca7fa)) - Merge pull request #769 from astoycos/fix-loaded-at ([`c130500`](https://github.com/aya-rs/aya/commit/c130500f18943b380e66ab6286c051f9548c47d0)) - Fix load time and add test ([`dffff1c`](https://github.com/aya-rs/aya/commit/dffff1ce6b6c4500b970dec53b57b7eb9c3ec717)) - Make maps work on kernels not supporting ProgIds ([`00dc7a5`](https://github.com/aya-rs/aya/commit/00dc7a5bd4468b7d86d7f167a49e78d89016e2ac)) - Use ProgramFd instead of impl AsRawFd ([`c6754c6`](https://github.com/aya-rs/aya/commit/c6754c614ed3aca142bb27fae4e8d488aff72019)) - Add documentation for XDP maps ([`9ed1d3d`](https://github.com/aya-rs/aya/commit/9ed1d3d2811db89dc7314914d92922c54032382c)) - Fix docstring missing trailing period ([`f7fbbcd`](https://github.com/aya-rs/aya/commit/f7fbbcd0e5cad297ddc5407e201580f878b4c5ee)) - Add support for chained xdp programs in {cpu,dev}map ([`0647927`](https://github.com/aya-rs/aya/commit/0647927e32333de662c6a065d5f5b9761c429e68)) - Add support for map-bound XDP programs ([`139f382`](https://github.com/aya-rs/aya/commit/139f3826383daba9a10dc7aacc079f31d28980fc)) - Update XDP maps implementations ([`ede3e91`](https://github.com/aya-rs/aya/commit/ede3e91014075de01af02da624cad99861da2dad)) - Implement XDP Map Types ([`ec8293a`](https://github.com/aya-rs/aya/commit/ec8293ab8644cbf8f1c4e7b1c44b286bc0ae969a)) - Merge pull request #790 from dave-tucker/no-map-pinned ([`42fd82e`](https://github.com/aya-rs/aya/commit/42fd82e32b85715df78508ef77f79eb8c2edd890)) - Make MapData::pin pub ([`938f979`](https://github.com/aya-rs/aya/commit/938f979fe7a82f6d31c3b7e926682864c507e381)) - Remove MapData::pinned ([`0f4021e`](https://github.com/aya-rs/aya/commit/0f4021ec89ef2dc5c28355ecfde4b2c53b4b6429)) - Merge pull request #782 from astoycos/prog-info ([`0b6ea31`](https://github.com/aya-rs/aya/commit/0b6ea313ded3240715d1b30d3b247e2bc983659e)) - Merge pull request #770 from aya-rs/mapfd-is-owned ([`41d01f6`](https://github.com/aya-rs/aya/commit/41d01f638bc81306749dd0f6aa7d2a677f4de27b)) - Fix typos, avoid fallible conversions ([`0dacb34`](https://github.com/aya-rs/aya/commit/0dacb34d449f71b1998b0a23cd58c0023277c2ef)) - MapData::{obj, fd} are private ([`b4d5a1e`](https://github.com/aya-rs/aya/commit/b4d5a1e8dbb82fc6fca543ad3b6e2f8175ae83b6)) - `MapFd` and `SockMapFd` are owned ([`f415926`](https://github.com/aya-rs/aya/commit/f41592663cda156082255b93db145cfdd19378e5)) - Add program_info() api to program ([`6ab7475`](https://github.com/aya-rs/aya/commit/6ab7475fa66d1b8155487dfc548645e2b8ee20c6)) - Merge pull request #775 from aya-rs/perf-as-raw-fd ([`92d3056`](https://github.com/aya-rs/aya/commit/92d3056db35df729efdbdf757ec389485ce7d8fd)) - Merge pull request #774 from ajwerner/try_from_LruHash ([`8d3fc49`](https://github.com/aya-rs/aya/commit/8d3fc49d68b5f75010451d8820a0239e538e41a7)) - Support TryFrom for LRU hash maps ([`172859c`](https://github.com/aya-rs/aya/commit/172859c66b25fbfa0d6d2af38ba7dd3f8e99d999)) - Merge pull request #777 from ajwerner/ajwerner/TryFrom-macros ([`792f467`](https://github.com/aya-rs/aya/commit/792f467d40dc4c6c543c62dda7a608863fc364dc)) - Rework TryFrom macros ([`2a1bf60`](https://github.com/aya-rs/aya/commit/2a1bf609b2b1239c9a789f1a1c814dfa888dfd1d)) - Access inner through async ([`8b0c7f1`](https://github.com/aya-rs/aya/commit/8b0c7f12046c2ebadcee5e7ab813d5a34ddc08c6)) - Merge pull request #772 from aya-rs/link-owned ([`8668436`](https://github.com/aya-rs/aya/commit/8668436787d3c6b0079a8841e69da3d1f654a650)) - Merge pull request #771 from aya-rs/xdp-raw ([`c4d1d10`](https://github.com/aya-rs/aya/commit/c4d1d1086a9fbcf907c260e5a6893bc71f712cfd)) - ProgAttachLink and LircLink hold owned FDs ([`204d020`](https://github.com/aya-rs/aya/commit/204d02022a94dab441029855e5d39d5143444204)) - Use OwnedFd ([`cee0265`](https://github.com/aya-rs/aya/commit/cee0265b5291acb747cf3a9532cfbf61c455f398)) - Merge pull request #723 from nrxus/map-program-owned-fd ([`c4643b3`](https://github.com/aya-rs/aya/commit/c4643b395f06d65876462e95f2988f773b725742)) - Use AsFd when attaching fds to programs ([`6895b1e`](https://github.com/aya-rs/aya/commit/6895b1e2ede8d571e7f7069a109932e917fd3ede)) - Use BorrowedFd when using the program fd in sys/bpf.rs ([`d2e74e5`](https://github.com/aya-rs/aya/commit/d2e74e562dfa601397b3570ece1a51f5013b9928)) - Merge pull request #765 from aya-rs/more-utf8-fixes ([`461c275`](https://github.com/aya-rs/aya/commit/461c2759c58f31af7a2ea396a477f8a8c0f8875f)) - Support non-UTF8 probing ([`1ccfdbc`](https://github.com/aya-rs/aya/commit/1ccfdbc17577a5f132ba0af2eb9b754e6e19ddca)) - Merge pull request #742 from aya-rs/avoid-utf-assumption ([`8ffd9bb`](https://github.com/aya-rs/aya/commit/8ffd9bb236a4dfc7694bbdac2b6ea1236b238582)) - Avoid path UTF-8 assumptions ([`0bba9b1`](https://github.com/aya-rs/aya/commit/0bba9b14b02a01ca33dbb1fa4a910b77a73a4d65)) - Avoid lossy string conversions ([`572d047`](https://github.com/aya-rs/aya/commit/572d047e37111b732be49ef3ad6fb16f70aa4063)) - Merge pull request #763 from aya-rs/lints ([`ff8c124`](https://github.com/aya-rs/aya/commit/ff8c124770104d04de78057ea33a35442f86671d)) - Deny various allow-by-default lints ([`abda239`](https://github.com/aya-rs/aya/commit/abda239d635e70c34898a425d119040d1bac39a5)) - Merge pull request #764 from aya-rs/fix-docs ([`1fa1241`](https://github.com/aya-rs/aya/commit/1fa1241ccb218c0809595bad3e6c65643d89aa43)) - Fix docs build ([`9ff1bf3`](https://github.com/aya-rs/aya/commit/9ff1bf3d3bb8f3d51ecaf625dbf3f8d2dbb51abc)) - Merge pull request #758 from aya-rs/map-fd-not-option ([`1d5f764`](https://github.com/aya-rs/aya/commit/1d5f764d07c06fa25167d1d4cf341913d4f0cd01)) - BloomFilter::insert takes &mut self ([`a31544b`](https://github.com/aya-rs/aya/commit/a31544b6e77d6868d950820ad31fc1fe8ed3666b)) - MapData::fd is non-optional ([`89bc255`](https://github.com/aya-rs/aya/commit/89bc255f1d14d72a61064b9b40b641b58f8970e0)) - Merge pull request #757 from aya-rs/attach-fd-owned ([`c7b5cd5`](https://github.com/aya-rs/aya/commit/c7b5cd5eb5ca238781c30794fbe72e1794b89a23)) - Use RAII to close FDs ([`3d68fa3`](https://github.com/aya-rs/aya/commit/3d68fa32cba3dfadc6a611cf285c7f6733abd39a)) - `ProgramData::attach_prog_fd` is owned ([`ae6526e`](https://github.com/aya-rs/aya/commit/ae6526e59b2875807524d466667e2d89c4cd4b8e)) - Merge pull request #744 from aya-rs/programfd-borrowed ([`e813a05`](https://github.com/aya-rs/aya/commit/e813a054adfe8e62c13da6dc1ab95a53576d18f2)) - `ProgramFd` is owned ([`504fd1d`](https://github.com/aya-rs/aya/commit/504fd1df0a29a02f5a19185e302c3e305a1045c7)) - Merge pull request #637 from astoycos/helpers ([`bcc9743`](https://github.com/aya-rs/aya/commit/bcc97432548b19d14ea974b1a83969dc159c6af4)) - Add helper methods for ProgramInfo ([`e1a5568`](https://github.com/aya-rs/aya/commit/e1a556894c412daeb44c09c6aa2f9f4489952f34)) - Merge pull request #702 from dave-tucker/mapdata-btffd ([`03c5012`](https://github.com/aya-rs/aya/commit/03c5012db20fb0f9445c4464370ca339dc743c33)) - Merge pull request #748 from aya-rs/btf_obj_fd-owned ([`7f98e41`](https://github.com/aya-rs/aya/commit/7f98e419e623440b350d6e888b6794d1fb35ce01)) - Plug attach_btf_obj_fd leak ([`d88ca62`](https://github.com/aya-rs/aya/commit/d88ca62aaaff690335c18ac725164c82fd173be2)) - Don't store bpf_fd in MapData ([`db975e9`](https://github.com/aya-rs/aya/commit/db975e977813ed6961963f7052ae53bc6df69309)) - Merge pull request #747 from aya-rs/helpers ([`5bc922a`](https://github.com/aya-rs/aya/commit/5bc922af23f9b4fb08910f287622c0c486f162d9)) - Refactor btf_obj_get_info_by_fd to share code ([`5ac1862`](https://github.com/aya-rs/aya/commit/5ac186299b468e54f93b16393bae44b3d896c544)) - Add map_ids to bpf_prog_get_info_by_fd ([`c7a19bc`](https://github.com/aya-rs/aya/commit/c7a19bcefba25455279d9e718f6430dee7a84b74)) - Merge pull request #743 from aya-rs/avoid-vec-ksyms ([`90cf131`](https://github.com/aya-rs/aya/commit/90cf13163b73aa87f99be285f7a9e4bc98557c7b)) - Avoid vector allocation when parsing ksyms ([`5138c73`](https://github.com/aya-rs/aya/commit/5138c731a92a8e5107e41829573617fc624ea9c7)) - Merge pull request #740 from addisoncrump/main ([`0c0cf70`](https://github.com/aya-rs/aya/commit/0c0cf70deba630c7fedb12f4820a47e3fa76a135)) - Nuclear option: no symbol resolution in the crate ([`ed77727`](https://github.com/aya-rs/aya/commit/ed777273b187cc30afa573101a1fade14a4fb465)) - Merge pull request #725 from dave-tucker/enum64 ([`2a55fc7`](https://github.com/aya-rs/aya/commit/2a55fc7bd3a15340b5b644d668f3a387bbdb09d3)) - Aya, aya-obj: Implement ENUM64 fixups ([`e38e256`](https://github.com/aya-rs/aya/commit/e38e2566e3393034b37c299e50c6a4b70d51ad1d)) - Merge pull request #709 from nrxus/fd-link-owned-fd ([`bd5442a`](https://github.com/aya-rs/aya/commit/bd5442a1de94f8d1359829d6f172cdd9f3452a09)) - Use OwnedFd in FdLink. ([`8ebf0ac`](https://github.com/aya-rs/aya/commit/8ebf0ac3279db08a6b71ae6fed42a135d627e576)) - Merge pull request #720 from dave-tucker/programsection-noname ([`e915379`](https://github.com/aya-rs/aya/commit/e9153792f1c18caa5899edc7c05487eb291415a4)) - Extract trait SymbolResolver ([`d8709de`](https://github.com/aya-rs/aya/commit/d8709de9f232483943132e6ffdf54ae8fdb0839d)) - Merge pull request #718 from ajwerner/better-code ([`ef6308b`](https://github.com/aya-rs/aya/commit/ef6308b640609743d7eb51453b605fe90a6aaf38)) - Remove name from ProgramSection ([`cca9b8f`](https://github.com/aya-rs/aya/commit/cca9b8f1a7e345a39d852bd18a43974871d3ed4b)) - Refactor target resolution ([`81fb4e5`](https://github.com/aya-rs/aya/commit/81fb4e5568b2521a61db2db839126a4b7df240df)) - Merge pull request #717 from ajwerner/no-libc-in-integration-tests ([`de8604d`](https://github.com/aya-rs/aya/commit/de8604d0119930491d602cb18d700191ac3e2e95)) - Merge pull request #711 from dave-tucker/sleepable ([`77e9603`](https://github.com/aya-rs/aya/commit/77e9603976b58491427df049a163e1945bc0bf27)) - Extract library path resolving ([`dcc6b84`](https://github.com/aya-rs/aya/commit/dcc6b84a8803cfee37ab4e138c89616f1fc1b002)) - Merge pull request #712 from aya-rs/loaded-links ([`368ddf1`](https://github.com/aya-rs/aya/commit/368ddf10c470a8a3c0420eb0f09cd254801c333b)) - Add links iterator ([`30faa5f`](https://github.com/aya-rs/aya/commit/30faa5f68f362b385b9ca96ff300dffcfd774033)) - Merge pull request #716 from aya-rs/prealloc-vec ([`b1bf61c`](https://github.com/aya-rs/aya/commit/b1bf61ca61285bc2390b3b3d10ee6a91eabfef34)) - Set BPF_F_SLEEPABLE for sleepable programs ([`71737f5`](https://github.com/aya-rs/aya/commit/71737f55764f56a764a5b21de0e59b8ecc49477c)) - Preallocate the vector ([`89ef97e`](https://github.com/aya-rs/aya/commit/89ef97e8482d1d0c1bb243441d911f688e183315)) - Plug file descriptor leak ([`7bb9b7f`](https://github.com/aya-rs/aya/commit/7bb9b7f5a5f03e815a5274457a93d0b20677059f)) - Push error construction up ([`b1404e9`](https://github.com/aya-rs/aya/commit/b1404e9a73aee4cdf93e96b44d35057ae1f6f079)) - Make `loaded_programs` opaque ([`a0af7e0`](https://github.com/aya-rs/aya/commit/a0af7e0b2fddaf297887c3e4c7480ef625c88ada)) - Extract common SyscallError ([`de8519a`](https://github.com/aya-rs/aya/commit/de8519a38083e96f9a0c34f0577657b8050db8a8)) - `sys_bpf` takes mut ref ([`4cb3ea6`](https://github.com/aya-rs/aya/commit/4cb3ea6e8fa990b88c5e8a67f1c852355bc7d99a)) - Merge pull request #714 from aya-rs/dry-btf-load ([`f095c59`](https://github.com/aya-rs/aya/commit/f095c591af9010dcbf9e7e8e4c6f9e2741c8592b)) - Avoid repeating BPF_BTF_LOAD dance ([`7ee6f52`](https://github.com/aya-rs/aya/commit/7ee6f52a7442e97d81ef41bc75673c8560bec5b0)) - Merge pull request #706 from aya-rs/reloc-tests ([`3692e53`](https://github.com/aya-rs/aya/commit/3692e53ff0e5cb87983a9b2dd54624baad22d582)) - S/assert!(.*) ==/assert_eq!\1,/ ([`6f3cce7`](https://github.com/aya-rs/aya/commit/6f3cce75cf11af27a9267dd88a688fc24e6b17b5)) - Merge pull request #707 from aya-rs/one-option-not-two ([`4c3219f`](https://github.com/aya-rs/aya/commit/4c3219f754c79e00ae4e56e13b646123cde31c61)) - Reduce state cardinality from 4 to 2 ([`0ec9afd`](https://github.com/aya-rs/aya/commit/0ec9afdb07f42bc40e60c9a6af1908b23f5bf263)) - Merge pull request #701 from nrxus/perf-event-owned-fd ([`445cb8b`](https://github.com/aya-rs/aya/commit/445cb8b46318a13a94e10e11000232d4bd5b23af)) - Return `OwnedFd` for `perf_event_open`. ([`dbfba18`](https://github.com/aya-rs/aya/commit/dbfba18dac87cbd837820316d53ad09b27d0c469)) - Merge pull request #704 from aya-rs/better-panic ([`868a9b0`](https://github.com/aya-rs/aya/commit/868a9b00b3701a4e035dc1d70cac934ef836655b)) - Better panic messages ([`17f25a6`](https://github.com/aya-rs/aya/commit/17f25a67934ad10443a4fbb62a563b5f6edcaa5f)) - Merge pull request #696 from Tuetuopay/tests-netns ([`f705eab`](https://github.com/aya-rs/aya/commit/f705eabe667d4abdfd0b895de586cc4145319cf0)) - Add the possibility to run a test inside a network namespace ([`c74813f`](https://github.com/aya-rs/aya/commit/c74813f8c545fca288094f47b20096e58eb5f46a)) - Merge pull request #699 from aya-rs/cache-again-god-damn-it ([`e95f76a`](https://github.com/aya-rs/aya/commit/e95f76a5b348070dd6833d37ea16db04f6afa612)) - Do not escape newlines on Err(LoadError).unwrap() ([`8961be9`](https://github.com/aya-rs/aya/commit/8961be95268d2a4464ef75b0898cf07f9ba44470)) - Merge pull request #662 from nrxus/use-owned-fd-for-btf ([`13e83b2`](https://github.com/aya-rs/aya/commit/13e83b24ee572009176b82840fb5cf1845fcf3dd)) - Use Arc when loading BTF fd ([`ea96c29`](https://github.com/aya-rs/aya/commit/ea96c29ccbae6c59a6a5bfc90f402ad307e22665)) - Make SysResult generic on Ok variant ([`683a1cf`](https://github.com/aya-rs/aya/commit/683a1cf2e4cdfba05ba35d708fecc4f43b0e83b3)) - Replace std::os::unix::io for std::os::fd ([`c63d990`](https://github.com/aya-rs/aya/commit/c63d9904f7e64349bd23c029a3bf31aaf1d97291)) - Merge pull request #688 from aya-rs/get-fd-owned ([`53d36a3`](https://github.com/aya-rs/aya/commit/53d36a3fe04b6828568603bfcfd9a588418abb1b)) - Bpf_prog_get_fd_by_id returns OwnedFd ([`76c78e3`](https://github.com/aya-rs/aya/commit/76c78e3bf82eb77c947dd125ed6624dfa6f4cc1c)) - Merge pull request #667 from vadorovsky/workspace-dependencies ([`f554d42`](https://github.com/aya-rs/aya/commit/f554d421053bc34266afbf8e00b28705ab4b41d2)) - Define dependencies on the workspace level ([`96fa08b`](https://github.com/aya-rs/aya/commit/96fa08bd82233268154edf30b106876f5a4f0e30)) - Merge pull request #671 from dave-tucker/misc-fixes ([`7ac808c`](https://github.com/aya-rs/aya/commit/7ac808cf551154b73f0e471af8a1a2ab88258409)) - Clippy fixes for latest nightly ([`764eb30`](https://github.com/aya-rs/aya/commit/764eb309b082a2e54b0d98782bb9cecd1243ff42)) - Merge pull request #656 from aya-rs/kernel-version-fml ([`232cd45`](https://github.com/aya-rs/aya/commit/232cd45e41031060238d37fc7f08eb3d63fa2eeb)) - Handle WSL kernel version strings ([`35ed85a`](https://github.com/aya-rs/aya/commit/35ed85a87ff467c7091c8749e48b8475cd1af592)) - Replace matches with assert_matches ([`961f45d`](https://github.com/aya-rs/aya/commit/961f45da37616b912d2d4ed594036369f3f8285b)) - Merge pull request #650 from aya-rs/test-cleanup ([`61608e6`](https://github.com/aya-rs/aya/commit/61608e64583f9dc599eef9b8db098f38a765b285)) - Merge pull request #584 from marysaka/fix/btf-kern-optional ([`0766e70`](https://github.com/aya-rs/aya/commit/0766e705486df167b0d5cf736c1edc14880bce17)) - Don't use env::tempdir ([`5407d4a`](https://github.com/aya-rs/aya/commit/5407d4a9a1885806a0f74abfc8cfe17baf13e124)) - Remove "async" feature ([`fa91fb4`](https://github.com/aya-rs/aya/commit/fa91fb4f59be3505664f8088b6e3e8da2c372253)) - Ignore embedded BTF error if not truely required ([`74b5468`](https://github.com/aya-rs/aya/commit/74b546827cdde13872e141e9e5b6cc9ac39efe1e)) - Fix build ([`242d8c3`](https://github.com/aya-rs/aya/commit/242d8c33c4ff71f766f32f184f826d3216929faa)) - Merge pull request #520 from astoycos/unsupported-map ([`eb60d65`](https://github.com/aya-rs/aya/commit/eb60d6561362e57955b2963b9a6b0a818281aabf)) - Merge pull request #560 from astoycos/fix-perf-link-pin ([`edb7baf`](https://github.com/aya-rs/aya/commit/edb7baf9a37187027e4459a7c716b22c2204ca1f)) - Add FdLink documentation and example ([`80b371f`](https://github.com/aya-rs/aya/commit/80b371f6d134aeba0f31716bf091d03cc9ca13fe)) - Merge pull request #644 from aya-rs/build-script ([`7def6d7`](https://github.com/aya-rs/aya/commit/7def6d72183d786427b8232945e139bb8c84d2d2)) - Implement FdLink conversions ([`58895db`](https://github.com/aya-rs/aya/commit/58895db9b46f810d8dd1550951d490ff56dcb9b4)) - Compile C probes using build.rs ([`8c61fc9`](https://github.com/aya-rs/aya/commit/8c61fc9ea6d1d52b38a238541fb229bc850c82ac)) - Merge pull request #648 from aya-rs/clippy-more ([`a840a17`](https://github.com/aya-rs/aya/commit/a840a17308c1c27867e67baa62942738c5bd2caf)) - Clippy over tests and integration-ebpf ([`e621a09`](https://github.com/aya-rs/aya/commit/e621a09181d0a5ddb6289d8b13d4b89a71de63f1)) - Merge pull request #643 from aya-rs/procfs ([`6e9aba5`](https://github.com/aya-rs/aya/commit/6e9aba55fe8d23aa337b29a1cab890bb54816068)) - Type-erase KernelVersion::current error ([`a1e0130`](https://github.com/aya-rs/aya/commit/a1e0130387390c148fc035ed1ae6b5665be4a96f)) - Invert comparison ([`6bceb1c`](https://github.com/aya-rs/aya/commit/6bceb1c3da2d0d71842073a2503810a666ef3caf)) - Rewrite kernel version logic ([`6e570f0`](https://github.com/aya-rs/aya/commit/6e570f0f14e615ccd0eeb80dfb68f3674f6ee74a)) - Remove procfs dependency ([`cc2bc0a`](https://github.com/aya-rs/aya/commit/cc2bc0acc183f178292d630789031aed0634f878)) - Remove verifier log special case ([`b5ebcb7`](https://github.com/aya-rs/aya/commit/b5ebcb7cc5fd0f719567b97f682a0ea0f8e0dc13)) - Merge pull request #641 from aya-rs/logger-messages-plz ([`4c0983b`](https://github.com/aya-rs/aya/commit/4c0983bca962e0e9b2711805ae7fbc6b53457c34)) - Get verifier logs when loading programs ([`b45a5bb`](https://github.com/aya-rs/aya/commit/b45a5bb71b485dbf05e21ddc4392bfda78f2e6f5)) - Hide details of VerifierLog ([`6b94b20`](https://github.com/aya-rs/aya/commit/6b94b2080dc4c122954beea814b2a1a4569e9aa3)) - Use procfs crate for kernel version parsing ([`b611038`](https://github.com/aya-rs/aya/commit/b611038d5b41a45ca70553550dbdef9aa1fd117c)) - Merge pull request #642 from aya-rs/less-strings ([`32be47a`](https://github.com/aya-rs/aya/commit/32be47a23b94902caadcc7bb1612adbd18318eca)) - Don't allocate static strings ([`27120b3`](https://github.com/aya-rs/aya/commit/27120b328aac5f992eed98b03216a9880a381749)) - Merge pull request #639 from aya-rs/test-no-bpftool ([`e93e3c4`](https://github.com/aya-rs/aya/commit/e93e3c4a55c5c740f4b1d4ce52be709f218dae1b)) - Remove dependency on bpftool in integration tests ([`ff86f13`](https://github.com/aya-rs/aya/commit/ff86f1385c1c45ed8dadcf160f2d4ae67b39ec13)) - Merge pull request #531 from dave-tucker/probe-cookie ([`bc0d021`](https://github.com/aya-rs/aya/commit/bc0d02143f5bc6103cca27d5f0c7a40beacd0668)) - Make Features part of the public API ([`47f764c`](https://github.com/aya-rs/aya/commit/47f764c19185a69a00f3925239797caa98cd5afe)) - Merge pull request #526 from dave-tucker/trie ([`76d35d1`](https://github.com/aya-rs/aya/commit/76d35d10ce2ca86daee8646e701d42ceedfd9d06)) - Remove iter_key from LPM Trie API ([`00c480d`](https://github.com/aya-rs/aya/commit/00c480d2f95d4c47fc281173307c179220cc4452)) - Merge pull request #633 from ajwerner/change-fd-import ([`5c6bd55`](https://github.com/aya-rs/aya/commit/5c6bd5526096a5a4c10e95643365d16d0090cf8f)) - Replace os::unix::prelude with os::fd ([`65d10f9`](https://github.com/aya-rs/aya/commit/65d10f9ffcad065bd87c15aacc85cc4a0c2a03ee)) - Merge pull request #632 from marysaka/feat/global-data-optional ([`b2737d5`](https://github.com/aya-rs/aya/commit/b2737d5b0d18ce09202ca9eb2ce772b1144ea6b8)) - Update aya/src/bpf.rs ([`77cce84`](https://github.com/aya-rs/aya/commit/77cce840f7957f12e29f29e1f05762173bb2b92b)) - Allow global value to be optional ([`93435fc`](https://github.com/aya-rs/aya/commit/93435fc85400aa036f3890c43c78c9c9eb4baa96)) - Fixups in response to alessandrod review ([`17930a8`](https://github.com/aya-rs/aya/commit/17930a88c5ea08d0ae68ac8fecee59f425fd06a3)) - Add Unsupported Map type ([`b5719c5`](https://github.com/aya-rs/aya/commit/b5719c5b3fcb7e48896212bdc7ee82d40f838bc2)) - Merge pull request #625 from FedericoPonzi/issue-534 ([`9cdae81`](https://github.com/aya-rs/aya/commit/9cdae8126573e598284f7dc3f6fff2f97a48cc02)) - Add syscall_prefix and syscall_fnname_add_prefix ([`987e848`](https://github.com/aya-rs/aya/commit/987e8489d05c50b777272124a7ec6ef6f3db6145)) - Merge pull request #622 from marysaka/fix/uprobe-416-lower ([`e5bac02`](https://github.com/aya-rs/aya/commit/e5bac0295306a6627658e818939927c387442295)) - Fix uprobe support on 4.16 and lower ([`49c6f5d`](https://github.com/aya-rs/aya/commit/49c6f5d12253cccf6354f818bf6d3b190428dc29)) - Merge pull request #621 from marysaka/fix/uprobe-debian-10 ([`41fe944`](https://github.com/aya-rs/aya/commit/41fe944a1a60279705f5ed156b25675ea302e861)) - Add support for old ld.so.cache format ([`8e9f395`](https://github.com/aya-rs/aya/commit/8e9f395eab70b23b84b14e17d9b1518918b35ee6)) - Merge pull request #619 from poliorcetics/relax-ordering-probe-alias ([`37b7c1e`](https://github.com/aya-rs/aya/commit/37b7c1e6141c53fce195b01deafedb18a955e9e1)) - Relax unnecessarily strict atomic ordering on probe event_alias ([`243986c`](https://github.com/aya-rs/aya/commit/243986c1da440c763393a4a37d5b3922b6baa3cc)) - Merge pull request #618 from marysaka/fix/aya-probe-event-alias-uniq ([`d56ed8f`](https://github.com/aya-rs/aya/commit/d56ed8fd687453f248fa79b6459598d53e11f40a)) - Make probe event_alias unique ([`e9be3d9`](https://github.com/aya-rs/aya/commit/e9be3d9023478b0132779267dcd88222f69feef5)) - Merge pull request #602 from marysaka/fix/btf-reloc-all-functions ([`3a9a54f`](https://github.com/aya-rs/aya/commit/3a9a54fd9b2f69e2427accbe0451761ecc537197)) - Merge pull request #616 from nak3/fix-bump ([`3211d2c`](https://github.com/aya-rs/aya/commit/3211d2c92801d8208c76856cb271f2b7772a0313)) - Add a few tweak a code to fix libbpf's API change. ([`afb4aa1`](https://github.com/aya-rs/aya/commit/afb4aa1c66b17d9e2b9a445f345c206764a9d391)) - Fixed a typo in the per_cpu_hashmap documentation ([`3d1013d`](https://github.com/aya-rs/aya/commit/3d1013d72981e673cbc3d24401d5855ab12f6a02)) - Merge pull request #607 from Hanaasagi/fix-warning ([`d4bfd72`](https://github.com/aya-rs/aya/commit/d4bfd72f578d24fb3a9bc5fb8177f08f98ef56d0)) - Remove useless `any` `all` in cfg. ([`0e4aec4`](https://github.com/aya-rs/aya/commit/0e4aec475ff2e9448196bce0b4598a983419974e)) - Merge pull request #605 from marysaka/fix/global-data-reloc-ancient-kernels ([`9c437aa`](https://github.com/aya-rs/aya/commit/9c437aafd96bebc5c90fdc7f370b5415174b1019)) - Do not create data maps on kernel without global data support ([`591e212`](https://github.com/aya-rs/aya/commit/591e21267a9bc9adca9818095de5a695cee7ee9b)) - Move program's functions to the same map ([`9e1109b`](https://github.com/aya-rs/aya/commit/9e1109b3ce70a3668771bd11a7fda101eec3ab93)) - Merge pull request #592 from probulate/update-bitflags ([`67f480e`](https://github.com/aya-rs/aya/commit/67f480eb8e1798ddb6c025560bd77bb21f46650b)) - Update bitflags requirement from 1.2.1 to 2.2.1 ([`ae8a95b`](https://github.com/aya-rs/aya/commit/ae8a95b0ee513b220b0b5ff1332ca24059ed3e7e)) - Merge pull request #577 from aya-rs/dependabot/cargo/object-0.31 ([`deb054a`](https://github.com/aya-rs/aya/commit/deb054afa45cfb9ffb7b213f34fc549c9503c0dd)) - Merge pull request #545 from epompeii/lsm_sleepable ([`120b59d`](https://github.com/aya-rs/aya/commit/120b59dd2e42805cf5880ada8f1bd0ba5faf4a44)) - Update object requirement from 0.30 to 0.31 ([`4c78f7f`](https://github.com/aya-rs/aya/commit/4c78f7f1a014cf54d54c805233a0f29eb1ca5eeb)) - Merge pull request #586 from probulate/no-std-inversion ([`45efa63`](https://github.com/aya-rs/aya/commit/45efa6384ffbcff82ca55e151c446d930147abf0)) - Flip feature "no_std" to feature "std" ([`33a0a2b`](https://github.com/aya-rs/aya/commit/33a0a2b604e77b63b771b9d0e167c894793492b5)) - Merge branch 'aya-rs:main' into lsm_sleepable ([`1f2006b`](https://github.com/aya-rs/aya/commit/1f2006bfde865cc4308643b21d51cf4a8e69d6d4)) - Merge pull request #525 from dave-tucker/borrow ([`ed14751`](https://github.com/aya-rs/aya/commit/ed14751c791a0db4d01ec759a6dc21e2f6cc3312)) - Merge pull request #579 from marysaka/fix/ubuntu-debian-kernel-version-code ([`1066c6c`](https://github.com/aya-rs/aya/commit/1066c6c2e5607d4484591cbfb77efd9464d802b2)) - Correctly set the kernel code version for Debian kernel ([`3aeeb81`](https://github.com/aya-rs/aya/commit/3aeeb8167baa2edb511f39b3d396d9112443aa73)) - Correctly set the kernel code version for Ubuntu kernel ([`f1d8918`](https://github.com/aya-rs/aya/commit/f1d891836e73d503c1841f5e7aee199d2b223afa)) - Merge pull request #582 from marysaka/feature/no-kern-read-sanitizer ([`b5c2928`](https://github.com/aya-rs/aya/commit/b5c2928b0e0d20c48157a5862f0d2c3dd5dbb784)) - Add sanitize code for kernels without bpf_probe_read_kernel ([`1132b6e`](https://github.com/aya-rs/aya/commit/1132b6e01b86856aa1fddf179fcc7e3825e79406)) - Merge pull request #580 from marysaka/fix/bpf_create_map_panic ([`edd9928`](https://github.com/aya-rs/aya/commit/edd9928314baf6e53f091f06b166aad797fa7337)) - Do not use unwrap with btf_fd in bpf_create_map ([`7c25fe9`](https://github.com/aya-rs/aya/commit/7c25fe90a9611545aba047cd347ca431616130b6)) - Merge pull request #572 from alessandrod/reloc-fixes ([`542ada3`](https://github.com/aya-rs/aya/commit/542ada3fe7f9d4d06542253361acc5fadce3f24b)) - Support relocations across multiple text sections + fixes ([`93ac3e9`](https://github.com/aya-rs/aya/commit/93ac3e94bcb47864670c124dfe00e16ed2ab6f5e)) - Aya, aya-obj: refactor map relocations ([`401ea5e`](https://github.com/aya-rs/aya/commit/401ea5e8482ece34b6c88de85ec474bdfc577fd4)) - Review ([`85714d5`](https://github.com/aya-rs/aya/commit/85714d5cf3622da49d1442c34caa63451d9efe48)) - Program_section ([`17f497c`](https://github.com/aya-rs/aya/commit/17f497ce4207c5c26023914d956c7c69411b25c1)) - Merge pull request #557 from drewvis/main ([`b13070a`](https://github.com/aya-rs/aya/commit/b13070a3429033700f8d13b4f01f81d4ede07fe1)) - Make it possible to use set_global() with slices of Pod(s) ([`bcb2972`](https://github.com/aya-rs/aya/commit/bcb2972a969f85e8c6c77e1213d89cc8198e8fe7)) - Added code check comment ([`8f64cf8`](https://github.com/aya-rs/aya/commit/8f64cf8cd5bf5d03445a6a79216775fda83179be)) - Add check for empty tracefs mounts ([`3a2c0cd`](https://github.com/aya-rs/aya/commit/3a2c0cd1dd7472feb77019dec3a4a8bc466167b7)) - Revert "aya: make it possible to use set_global() with slices of Pod(s)" ([`8ef00c4`](https://github.com/aya-rs/aya/commit/8ef00c4c637bb3f86f6cabb86f44d5e9a40d6506)) - Make it possible to use set_global() with slices of Pod(s) ([`b614ffd`](https://github.com/aya-rs/aya/commit/b614ffd603f4a276fd060659e14e5794bb26381f)) - Merge pull request #548 from kriomant/feature-xdp-attach-by-index ([`d6319f9`](https://github.com/aya-rs/aya/commit/d6319f95c91212218f4282aa025e3295166c9b7f)) - Don't leak libc types ([`ce60854`](https://github.com/aya-rs/aya/commit/ce60854934312c9ebb75178f44faf9369febf6ad)) - Fix formatting ([`896e3ab`](https://github.com/aya-rs/aya/commit/896e3ab3130c4de44e8c0f4f974163c13aa50ff0)) - Rename method and fix comment ([`676b5cd`](https://github.com/aya-rs/aya/commit/676b5cdc0df380471090153ab4ff2e641e4e1d03)) - Allow to attach XDP probe by interface index ([`2e3c177`](https://github.com/aya-rs/aya/commit/2e3c1779be03898dd6a01644012ef21b2475ad63)) - Merge pull request #539 from marysaka/fix/map_data_clone ([`113e3ef`](https://github.com/aya-rs/aya/commit/113e3ef0183acc69202fa6587643449f793cfff8)) - Fix MapData Clone implementation ([`94049ec`](https://github.com/aya-rs/aya/commit/94049ec661ed715e65fb4fb29c92d10d803699cc)) - Merge pull request #524 from dave-tucker/prog_list ([`d9878a6`](https://github.com/aya-rs/aya/commit/d9878a67917069e89e4ea974d116c8fdab3da4e5)) - Add loaded_programs() API to list all loaded programs ([`de4905a`](https://github.com/aya-rs/aya/commit/de4905a24bc0f665c40af964b56471c04434a8b4)) - MapData should be Borrow, not AsRef ([`b1a70fc`](https://github.com/aya-rs/aya/commit/b1a70fc6e40f7ad398bce9994f3bb2642267ca8b)) - Merge pull request #523 from dave-tucker/fix_perf_link ([`56c1438`](https://github.com/aya-rs/aya/commit/56c143831e8f056ac77bb5282a340c1a545eb0f2)) - Fix is_perf_link_supported ([`ce79de7`](https://github.com/aya-rs/aya/commit/ce79de7ff6b965efa25840b35b0d051c3087db0a)) - Merge pull request #522 from dave-tucker/perf_link ([`d7d6442`](https://github.com/aya-rs/aya/commit/d7d6442671a38098613b1a0accea0c08272d9fc0)) - More discrete feature logging ([`7479c1d`](https://github.com/aya-rs/aya/commit/7479c1dd6c1356bddb0401dbeea65618674524c9)) - Enable bpf_link for perf_attach programs ([`d0b3d3b`](https://github.com/aya-rs/aya/commit/d0b3d3b2fac955ed0e1e3d885fcd3ba67941dc8c)) - Add probe for bpf_link_create for perf programs ([`763b92a`](https://github.com/aya-rs/aya/commit/763b92a2e007a17cc2b6a17929dcb6a5c26c9f88)) - Make features a lazy_static ([`ce22ca6`](https://github.com/aya-rs/aya/commit/ce22ca668f3e7c0f9832d28370457204537d2e50)) - Merge pull request #519 from dave-tucker/frags ([`bc83f20`](https://github.com/aya-rs/aya/commit/bc83f208b11542607e02751126a68b1ca568873b)) - Add support for multibuffer programs ([`a18693b`](https://github.com/aya-rs/aya/commit/a18693b42dc986bde06b07540e261ecac59eef24)) - Merge pull request #496 from dave-tucker/program-from-pinned3 ([`811ab29`](https://github.com/aya-rs/aya/commit/811ab299deee19017fa8158b7b9e8eea88107c6a)) - Add from_pin for Programs ([`7a720ab`](https://github.com/aya-rs/aya/commit/7a720ab0c1061b7a6f4e8e7bf862d86550bbdc7b)) - Merge pull request #515 from alessandrod/fix-lru-hash ([`cfa693b`](https://github.com/aya-rs/aya/commit/cfa693bc3b4442a8c97cfcd24551ea6439f25e50)) - Fix Lru and LruPerCpu hash maps ([`c22014c`](https://github.com/aya-rs/aya/commit/c22014c757c88c40091e44a48e14920f6e6e0334)) - Merge pull request #512 from astoycos/crucial-btf-fixes ([`27017ca`](https://github.com/aya-rs/aya/commit/27017ca8a32e692d6226e11857d68e5c0acc249e)) - Support BTF key/value specification for all maps ([`52e6250`](https://github.com/aya-rs/aya/commit/52e625060e463c5b7b0ec0fe9f683b82d7227ad0)) - Merge pull request #445 from anfredette/tc-link-recon ([`22d7931`](https://github.com/aya-rs/aya/commit/22d79312f7f5d8afd97dfaa42d3cd063206772e3)) - Address review comments from @alessandrod ([`7c24296`](https://github.com/aya-rs/aya/commit/7c24296b5df73a5d9d07872a3832cf4e9aa9c76f)) - Merge pull request #471 from banditopazzo/tracefs_mount_select ([`7e5637b`](https://github.com/aya-rs/aya/commit/7e5637bb9c9a0c35424e17139eaf58c825aad08c)) - Tracefs review fixes ([`48fdf5a`](https://github.com/aya-rs/aya/commit/48fdf5a250ce74516a02c0f34b0f359f7f9a4d63)) - Get_tracefs function ([`c6c4ac7`](https://github.com/aya-rs/aya/commit/c6c4ac7eeaf7e6cfa31ab0b949aa93b136eda91b)) - Updates after rebase due to changes in define_link_wrapper ([`d43879d`](https://github.com/aya-rs/aya/commit/d43879d99177c33c5d33827d8a3c7572841dd9df)) - Remove SchedClassifierLink description ([`6766532`](https://github.com/aya-rs/aya/commit/6766532341803ab70501e0c522afc0656385e002)) - Address review comments ([`2972d46`](https://github.com/aya-rs/aya/commit/2972d462a505aaba8d9e40ddf2c6110e497283db)) - Address review comments ([`65f5b76`](https://github.com/aya-rs/aya/commit/65f5b76593f35b8ca09f5d868a4195086ddca831)) - Rename SchedClassifierLink:new() to new_tc_link() ([`849796c`](https://github.com/aya-rs/aya/commit/849796c4208b520cd12a7ac5de28857dc885c026)) - Additional edits to SchedClassifierLink documentation. ([`67efc33`](https://github.com/aya-rs/aya/commit/67efc33414df049853247e344dfaa37eeeafe602)) - Combine updates to SchedClassifierLink example made by Dave Tucker ([`6563e6c`](https://github.com/aya-rs/aya/commit/6563e6cc065d01270aad50d9c3449f9deb9f04d6)) - Add example for SchedClassifierLink::new() ([`c3a8400`](https://github.com/aya-rs/aya/commit/c3a8400e4d219eb72167ccaeb500d36ad924873d)) - Support reconstruction of `SchedClassifierLink` ([`f46fd17`](https://github.com/aya-rs/aya/commit/f46fd17cc31f7f900f01d8e97baebe6445c468d4)) - Expose inner errors ([`1899d5f`](https://github.com/aya-rs/aya/commit/1899d5f4fd3ec5d91e40a94d671a7756125a4487)) - Merge pull request #484 from vadorovsky/update-tokio ([`bea0e83`](https://github.com/aya-rs/aya/commit/bea0e83512cc6d45b3e4fb5c3f62432c434139b7)) - Update Tokio and inventory ([`dad75f4`](https://github.com/aya-rs/aya/commit/dad75f45ac357e86eebc92c4f95f6dd4e43d8496)) - Merge pull request #475 from yesh0/aya-obj ([`897957a`](https://github.com/aya-rs/aya/commit/897957ac84370cd1ee463bdf2ff4859333b41012)) - Update documentation and versioning info ([`9c451a3`](https://github.com/aya-rs/aya/commit/9c451a3357317405dd8e2e4df7d006cee943adcc)) - Add basic documentation to public members ([`e52497c`](https://github.com/aya-rs/aya/commit/e52497cb9c02123ae450ca36fb6f898d24b25c4b)) - Migrate aya::obj into a separate crate ([`ac49827`](https://github.com/aya-rs/aya/commit/ac49827e204801079be2b87160a795ef412bd6cb)) - Migrate bindgen destination ([`81bc307`](https://github.com/aya-rs/aya/commit/81bc307dce452f0aacbfbe8c304089d11ddd8c5e)) - Btf relocs: don't panic on failed relocation ([`c6f93b1`](https://github.com/aya-rs/aya/commit/c6f93b177511d3dfb0ab6cce4fa298d13707dedc)) - Make btf::RelocationError private ([`aba99ea`](https://github.com/aya-rs/aya/commit/aba99ea4b1f5694e115ae49e9dbe058d3e761fd8)) - Fix regression computing pointer sizes ([`12e422b`](https://github.com/aya-rs/aya/commit/12e422b21134e3f4fb1949b248ecfd2afd768e53)) - Resolve symbol address for PIE executables ([`1a22792`](https://github.com/aya-rs/aya/commit/1a22792ee75642a3998634cc16abda7d813b45bb)) - Fix detaching links on drop ([`b3ae778`](https://github.com/aya-rs/aya/commit/b3ae7786d335fd0294a6ddecf3f31ef28d56af9d)) - Merge pull request #461 from FallingSnow/main ([`9f5d157`](https://github.com/aya-rs/aya/commit/9f5d157628e1636390cc73fd5d21db62b1894a13)) - Fix LpnTrieKeys -> LpmTrieKeys typo ([`10ac595`](https://github.com/aya-rs/aya/commit/10ac5957c1a01dd5a41307c45e2824cf20021dff)) - Merge pull request #466 from bpfdeploy-io/ml/cgroup-device ([`d1919a8`](https://github.com/aya-rs/aya/commit/d1919a83ed21ec8156732b9bb34194c9c8a50bc1)) - Fix doctest issue ([`925504f`](https://github.com/aya-rs/aya/commit/925504f230db683f2728f72577c3c7f2504b6f16)) - Fix CI, clippy and feedback ([`4b6d97e`](https://github.com/aya-rs/aya/commit/4b6d97e4db4261921a43958366b66fa3e0da237b)) - Add support for BPF_PROG_TYPE_CGROUP_DEVICE ([`8f1163a`](https://github.com/aya-rs/aya/commit/8f1163a400b13010acf5353ddc43e9b81ca61d7a)) - Fix formatting ([`a44f054`](https://github.com/aya-rs/aya/commit/a44f054bec60c64c5254a9799ae0c92683dd8889)) - Merge pull request #460 from Tuetuopay/owned-per-cpu-hash-map ([`66d435f`](https://github.com/aya-rs/aya/commit/66d435fc7c6cf55a64263de3263f9864762f4f92)) - Remove old test ([`1368eb9`](https://github.com/aya-rs/aya/commit/1368eb94e7609e477a503da5b9fe94ed0f3cea93)) - Add ability to iterate over lpmtrie key matches ([`9a3682e`](https://github.com/aya-rs/aya/commit/9a3682e793e02ce16c83e5ba8dcfa89f44d6b434)) - Fix lpmtrie iter returning nothing ([`8fe64ae`](https://github.com/aya-rs/aya/commit/8fe64aef1fa13cc2796ab62889526745203cd579)) - Add missing TryFrom for HashMap, PerCpuHashMap and LpmTrie ([`51bb50e`](https://github.com/aya-rs/aya/commit/51bb50ed8e9726723b395515374053e59cd4c402)) - Iterate lpmtrie ([`e4182a9`](https://github.com/aya-rs/aya/commit/e4182a9eabe157e7b506111b0dda2d0dc15f0d8d)) - Merge pull request #456 from dmitris/uninlined_format_args ([`16b029e`](https://github.com/aya-rs/aya/commit/16b029ed3708470afd2a6d67615b30c8d30b5059)) - Fix uninlined_format_args clippy issues ([`055d94f`](https://github.com/aya-rs/aya/commit/055d94f58be4f80ada416b99278a22f600c71285)) - Merge pull request #450 from aya-rs/dependabot/cargo/object-0.30 ([`1ded0e6`](https://github.com/aya-rs/aya/commit/1ded0e61cd9d49007408584210c51521be2aad5f)) - Update object requirement from 0.29 to 0.30 ([`1fe7bba`](https://github.com/aya-rs/aya/commit/1fe7bba070cc74008cc8165030b01336cb9acbe1)) - Merge pull request #452 from vadorovsky/fix-lint ([`9382de7`](https://github.com/aya-rs/aya/commit/9382de75cc4cbf6e115a96fb33a40137fd70476a)) - Fix clippy error ([`176d61a`](https://github.com/aya-rs/aya/commit/176d61ae23c3977a8f2b6c9a41286604ebe9280e)) - Merge pull request #418 from anfredette/tc-handle ([`7fef833`](https://github.com/aya-rs/aya/commit/7fef833e3a94aef9eba7c9fa0f83e3fc9ba0e1ea)) - Make doc fixes ([`abb75ba`](https://github.com/aya-rs/aya/commit/abb75ba0293ca7b68abeff3d670dadc5997eb959)) - Merge pull request #431 from 0b01/refs ([`88d7777`](https://github.com/aya-rs/aya/commit/88d77775530341ec32ff4f764b729e53a48c0de0)) - Fix formatting ([`76e417a`](https://github.com/aya-rs/aya/commit/76e417a474ca6f89956dd3834e387928c328b44c)) - Support both attach() and attach_with_options() for SchedClassifier ([`a3e3e80`](https://github.com/aya-rs/aya/commit/a3e3e806986b6c3761b1072626825d7f58376c50)) - Merge pull request #435 from vadorovsky/pin-fix-error-msg ([`3e089a6`](https://github.com/aya-rs/aya/commit/3e089a61d19955440bdbd77d2ab62a2f68ccf99f)) - Fix the error message in `MapData::pin()` ([`e0a9895`](https://github.com/aya-rs/aya/commit/e0a98952601bf8244a1f046a106b6419313537b6)) - Make sure everything is marked correctly ([`6ce60ad`](https://github.com/aya-rs/aya/commit/6ce60ad21da48d98a47442d234b94e60b1449008)) - Fix array ([`9525b1a`](https://github.com/aya-rs/aya/commit/9525b1a370a0f3123bf97b31040ca6d07d315ef3)) - Fix wrong bounds ([`575fea4`](https://github.com/aya-rs/aya/commit/575fea4cb9a19540de82283855fb84fad8113dfd)) - Cargo fmt ([`fbfbedb`](https://github.com/aya-rs/aya/commit/fbfbedb6a89825bec107b2f0b8fc6fed8042321d)) - Use & ([`9991ffb`](https://github.com/aya-rs/aya/commit/9991ffb093bff3a10678c48f8c4c7610558ab809)) - Add test case ([`e9ec257`](https://github.com/aya-rs/aya/commit/e9ec257328299551a750883075095f8a60f1cce3)) - Use Borrow instead ([`1247ffc`](https://github.com/aya-rs/aya/commit/1247ffc19b8d68d37c032a7b916e3f2b3531972f)) - Use a struct for setting priority and handle in SchedClassfier attach ([`af3de84`](https://github.com/aya-rs/aya/commit/af3de84b081941bd3139c7089618a53dfa37ac83)) - Support using handle in tc programs ([`ac07608`](https://github.com/aya-rs/aya/commit/ac07608b7922a545cd1de1996b66dcbdeb7fbbe1)) - Merge pull request #397 from astoycos/refactor-map-api2 ([`d6cb1a1`](https://github.com/aya-rs/aya/commit/d6cb1a16ad0f8df483e2234fb01ab55bdbeaa8b8)) - Fix doc links, update rustdoc args ([`82edd68`](https://github.com/aya-rs/aya/commit/82edd681c398f73de026a695837dd37643ed124a)) - Make map APIs return an option ([`f3262e8`](https://github.com/aya-rs/aya/commit/f3262e87bd6ff895537df47fcf5d17c598e564cc)) - Fixups4 ([`4ddf260`](https://github.com/aya-rs/aya/commit/4ddf2600b4084d224d66810f8372f42354dc40e0)) - Fixups 3 ([`440097d`](https://github.com/aya-rs/aya/commit/440097d7bc671c78fa33d13f890c3aa456530306)) - Fixups 2 ([`939d16c`](https://github.com/aya-rs/aya/commit/939d16cce5dcfeaebc5d571d21127105fc886186)) - Fixups ([`8009361`](https://github.com/aya-rs/aya/commit/8009361694a7f8967a31734d109f79a6b26516dc)) - Implement Copy for MapData ([`893f9f4`](https://github.com/aya-rs/aya/commit/893f9f44a22d4c78d86107cdac1204c4da65938a)) - Use SockMapFd ([`898a14d`](https://github.com/aya-rs/aya/commit/898a14d42559c7567f45e43fccdaa741a3ac9f27)) - Core refactor of Map API ([`1aefa2e`](https://github.com/aya-rs/aya/commit/1aefa2e5e6d22a600cc7339d289d64ab06f842e3)) - Merge branch 'aya-rs:main' into integration-tests-cli-options ([`4183c7a`](https://github.com/aya-rs/aya/commit/4183c7a7d21c655b71c6a62998d71d7ffe653f53)) - Merge pull request #411 from abhijeetbhagat/fix-warnings ([`94bc93e`](https://github.com/aya-rs/aya/commit/94bc93ea07664c682198389d42a14bfe58367967)) - Fix all clippy warnings ([`6c813b8`](https://github.com/aya-rs/aya/commit/6c813b8c38a172e8372d7009b50029d1cb8513e4)) - Merge pull request #406 from dave-tucker/unused-deps ([`57ab0d7`](https://github.com/aya-rs/aya/commit/57ab0d7978e211b16fece64be25edef28ab9441a)) - Remove unused dependencies ([`ec2bd69`](https://github.com/aya-rs/aya/commit/ec2bd690532cc21b22e07cfa1539a195bf5e149c)) - Merge pull request #404 from dave-tucker/async-docs ([`14ba644`](https://github.com/aya-rs/aya/commit/14ba644aa5ac4263062d6249420fb07b36ca5080)) - Add labels for optional features ([`95e8c78`](https://github.com/aya-rs/aya/commit/95e8c78db8fef4fcc12a9cbf0a52753049070e4b)) - Merge pull request #398 from vadorovsky/fix-miri ([`3f2f3a8`](https://github.com/aya-rs/aya/commit/3f2f3a8be038edd8944823b11ff772e924d3d962)) - Disable miri warnings about integer-to-pointer conversions ([`43aff57`](https://github.com/aya-rs/aya/commit/43aff5779390881d785a4d1c0d6c7bd681381dfe)) - Avoid integer to pointer casts ([`2432677`](https://github.com/aya-rs/aya/commit/2432677b2bafcfa4028f9315f6754b14c070b4dd)) - Merge pull request #393 from aztecher/impl-set_max_entries ([`a93a975`](https://github.com/aya-rs/aya/commit/a93a975cc63e4e47542289b8c9d8fb83fa135ef9)) - Add BpfLoader::set_max_entries ([`2eccf1d`](https://github.com/aya-rs/aya/commit/2eccf1d57da4c9bfa1ea4c0802bc34905c9b1f72)) - Merge pull request #394 from vadorovsky/clippy ([`6eca4f5`](https://github.com/aya-rs/aya/commit/6eca4f570990e0334281560be969505380445688)) - Fix clippy warnings ([`5a4b5ff`](https://github.com/aya-rs/aya/commit/5a4b5ff8d8bf8d3f36cb784d9e3af5284e388433)) - Merge pull request #391 from dave-tucker/fix-387 ([`e696389`](https://github.com/aya-rs/aya/commit/e696389837c1625de9f58ad9f77c56ecfb268b0c)) - Rename from_pinned and from_path to from_pin ([`5693fb9`](https://github.com/aya-rs/aya/commit/5693fb994123b88eb856af83c5b8f79afd1d789f)) - Fix review comments from #387 ([`de6fa98`](https://github.com/aya-rs/aya/commit/de6fa98963b7c5a311aafec6afe956ff716d68c5)) - Merge pull request #387 from astoycos/map-from-prog ([`eb26a6b`](https://github.com/aya-rs/aya/commit/eb26a6b116885fa19294c944080b42408f49bd30)) - Add `from_pinned` and `from_fd` methods ([`8a9cbf1`](https://github.com/aya-rs/aya/commit/8a9cbf179f62b68def7d30612cdffb576d062baa)) - Merge pull request #378 from dave-tucker/pin-fixes-again ([`98e25ca`](https://github.com/aya-rs/aya/commit/98e25ca5e6550526d35f783d74e6276f7aaf8a71)) - Add integration test for pinning lifecycle ([`7c244e1`](https://github.com/aya-rs/aya/commit/7c244e1f65fdb80f65c6a317773f3ff069255cd8)) - Replace From for XdpLink with TryFrom ([`f961cbb`](https://github.com/aya-rs/aya/commit/f961cbb3d43693e21a9c633d8b581c8a24fa7055)) - Rename bpf_obj_get_info_by_id ([`6af2053`](https://github.com/aya-rs/aya/commit/6af2053cf3fd36522642169710d2804feb1e20a5)) - Merge pull request #376 from conectado/verifier-log-level ([`fe22b02`](https://github.com/aya-rs/aya/commit/fe22b0210894e7f383566110f094acf6a738125e)) - Fix miss doc period ([`3bed2c2`](https://github.com/aya-rs/aya/commit/3bed2c2b94a47503ba32e9879c7a29fe9f8e9227)) - Change variant names ([`c30ae6e`](https://github.com/aya-rs/aya/commit/c30ae6e0010adda3d3e3de792cf2919f3c1dcf32)) - More pinning fixes ([`4b5b9ab`](https://github.com/aya-rs/aya/commit/4b5b9ab3d92befe967709ad6cc55264fc0541b73)) - Merge pull request #384 from aya-rs/codegen ([`7b99a57`](https://github.com/aya-rs/aya/commit/7b99a5733084d4638e74327901bb442ce8b4333b)) - [codegen] Update libbpf to efd33720cdf4a0049323403df5daad0e9e894b3dUpdate libbpf to efd33720cdf4a0049323403df5daad0e9e894b3d ([`ed849ff`](https://github.com/aya-rs/aya/commit/ed849ffd18fd360fef9b5bfa636054a5f18f0170)) - Merge pull request #381 from aya-rs/codegen ([`49c5a94`](https://github.com/aya-rs/aya/commit/49c5a94aa0198897a9aef9a1cad4523bb63c56ee)) - [codegen] Update libbpf to efd33720cdf4a0049323403df5daad0e9e894b3dUpdate libbpf to efd33720cdf4a0049323403df5daad0e9e894b3d ([`8e96011`](https://github.com/aya-rs/aya/commit/8e96011c2da245090f8b8603bf6d57cf7c5902a4)) - Merge pull request #379 from dave-tucker/fix-link-segfault ([`9451699`](https://github.com/aya-rs/aya/commit/945169996c435815f0b9ef1a591c17a6fbec5a0d)) - Fix segfault in define_link_wrapper ([`18584e2`](https://github.com/aya-rs/aya/commit/18584e2259382bbb4e56007eacbe81dba25db05a)) - Merge pull request #285 from dave-tucker/btf-redux ([`66b4f79`](https://github.com/aya-rs/aya/commit/66b4f79ecafe9832fcc1e44373598774b9954514)) - Improved BTF Type API ([`f34ebeb`](https://github.com/aya-rs/aya/commit/f34ebeba99e409bb369a74687e1664a50c430c1e)) - Update `VerifierLogLevel` to use bitflags ([`7b14319`](https://github.com/aya-rs/aya/commit/7b143199fb61edd168f3efc860a8e8c1d4cd9136)) - Merge pull request #366 from dave-tucker/pin-redux-2 ([`4826bf7`](https://github.com/aya-rs/aya/commit/4826bf7f748722724536c7f9bbc3234262e35128)) - Fix Link Pinning ([`4c1d645`](https://github.com/aya-rs/aya/commit/4c1d645aa6e8150b50007ff42eb17e270a5b80af)) - Merge pull request #371 from conectado/verifier-log-level ([`b95adc3`](https://github.com/aya-rs/aya/commit/b95adc3135f9b9cc74d16052250b5d8611caf9dc)) - Update `VerifierLogLevel` level variants ([`a602525`](https://github.com/aya-rs/aya/commit/a6025255f56a941c2614d8bbf395e07b47588b75)) - Use enum to set verifier log level ([`edd8039`](https://github.com/aya-rs/aya/commit/edd80397dce46f6e2a4cc96bd951562987721e55)) - Expose BPF verifier log level configuration ([`3211646`](https://github.com/aya-rs/aya/commit/3211646aef48c7d388941a4a9e932e66bec87fd6)) - Change from Rust edition 2018 to 2021 ([`944d6b8`](https://github.com/aya-rs/aya/commit/944d6b8a1647df36c17cd060b15c37ac9615f4a7)) - Add support for setting priority for classifier programs ([`207c689`](https://github.com/aya-rs/aya/commit/207c689f560de2963210d245ae718b1f09d9eaae)) - Merge pull request #355 from dave-tucker/rm-map-pin-path ([`55a7e3c`](https://github.com/aya-rs/aya/commit/55a7e3c4d0b92bc19ccb9705358b95a1f4bf448c)) - Remove MapError::InvalidPinPath ([`03a15b9`](https://github.com/aya-rs/aya/commit/03a15b98643a520269197e5db98cc48715a61577)) - Merge pull request #343 from dave-tucker/pinning-redux ([`8e6c9ad`](https://github.com/aya-rs/aya/commit/8e6c9ad0d279fd127f2453b948b6306600eb566d)) - Use PinError for all pinning errors ([`34ba2bc`](https://github.com/aya-rs/aya/commit/34ba2bc0482f9a16bc9c7ad138e9288c66e4bac4)) - Implement FdLink::pin() ([`64f8a43`](https://github.com/aya-rs/aya/commit/64f8a434d2a337578bde86c1983f46a3282e7f53)) - Allow pin to be used on all programs ([`5726b6d`](https://github.com/aya-rs/aya/commit/5726b6d044011b462b04e533f881e0dd26d60d0f)) - Merge pull request #350 from dave-tucker/monorepo ([`f37a514`](https://github.com/aya-rs/aya/commit/f37a51433ff5283205ba5d1e74cdc75fbdeea160)) - Fix rlimit warning on for 32bit systems ([`c9e70a8`](https://github.com/aya-rs/aya/commit/c9e70a8758ef10cfe1970e5f7a1e830e0ba5ec8e)) - Merge pull request #140 from dave-tucker/btf-maps ([`73ee3cf`](https://github.com/aya-rs/aya/commit/73ee3cff70db51d5bb2d4934c2767a1ab2f13eda)) - Support BTF Maps ([`f976229`](https://github.com/aya-rs/aya/commit/f97622947706a8efd06546c45860cc60cfe41a13)) - Merge pull request #344 from vadorovsky/rlimit-v2 ([`fa4347a`](https://github.com/aya-rs/aya/commit/fa4347aae4251f30f583cfa198584392b3853087)) - Raise the RLIMIT_MEMLOCK warning only if failed to create a map ([`3d592d0`](https://github.com/aya-rs/aya/commit/3d592d0f295b0a2c385e200bb0224c57c144f5ea)) - Merge pull request #342 from vadorovsky/rlimit ([`a7fa938`](https://github.com/aya-rs/aya/commit/a7fa938f1e96c81e941eaf543e8acef03bbcfc52)) - Raise the warning when RMILIT_MEMLOCK is not RLIM_INFINITY ([`bebe98e`](https://github.com/aya-rs/aya/commit/bebe98e6706ec4c149508f8aabdd44707d1c6d73)) - Merge pull request #336 from dave-tucker/clippy ([`6188c9d`](https://github.com/aya-rs/aya/commit/6188c9dee34ef603ca04cc7cf5b113a9e96c37d2)) - Fix latest nightly lints ([`336faf5`](https://github.com/aya-rs/aya/commit/336faf553e1ef8d21298a4f6e9835a22e29904ad)) - Merge pull request #330 from aya-rs/dependabot/cargo/object-0.29 ([`f2fb211`](https://github.com/aya-rs/aya/commit/f2fb2116344e3fed10cff1dd1a1474971204e799)) - Update object requirement from 0.28 to 0.29 ([`661a215`](https://github.com/aya-rs/aya/commit/661a21570f1154f4ae32c81a8a142913f7deec86)) - Merge pull request #328 from drewkett/map-update-no-key ([`a301a56`](https://github.com/aya-rs/aya/commit/a301a563167d27498c2eda1a9a87d07ba6475024)) - Merge pull request #282 from dave-tucker/bpfd ([`e5f455f`](https://github.com/aya-rs/aya/commit/e5f455f238e930dff476087085ba847bb82eca87)) - Improve Extension Docs ([`004f3dd`](https://github.com/aya-rs/aya/commit/004f3dd6644b0c0a2ff1e877093a5ee0610eb830)) - Add Extension::attach_to_program() ([`9e85b92`](https://github.com/aya-rs/aya/commit/9e85b923230bd1db18fb87a3a6bc4a5c60a6b405)) - Replace ProgramFd trait with struct ([`b441332`](https://github.com/aya-rs/aya/commit/b4413322e3730b183546fcfdfc4b12f0ffce4a9c)) - Implement attach_to_link for XDP ([`fd52bfe`](https://github.com/aya-rs/aya/commit/fd52bfeadc70020e4111bb4dda0ca4e361c3be43)) - Add support for bpf_link_update ([`ccb1897`](https://github.com/aya-rs/aya/commit/ccb189784f87d58bc397b22c04e976cabcbd8e00)) - Have bpf_map_update_elem take Option<&K> for key ([`36edf09`](https://github.com/aya-rs/aya/commit/36edf092541574633ff03f7deb8b95003b2bcdd2)) - Add Map::fd() function to return a MapFd ([`623579a`](https://github.com/aya-rs/aya/commit/623579a47f1fd169ba9503bd71550c3fcce76b21)) - Merge pull request #320 from dave-tucker/moar-crabby-docs ([`ed3b690`](https://github.com/aya-rs/aya/commit/ed3b690a6d0638eaa563704c9be45559205cffeb)) - Add crabby, sync with aya/README.md ([`2b98259`](https://github.com/aya-rs/aya/commit/2b98259be73865cf6b213de1b73d0b7b0086a22f)) - Add crabby logo ([`713cd4e`](https://github.com/aya-rs/aya/commit/713cd4e858d9474318104b2a1e4dee0a25e8c67a)) - Merge pull request #315 from dave-tucker/sock ([`7549eb9`](https://github.com/aya-rs/aya/commit/7549eb979c39555215edfc58fbf94cdf735dc949)) - Implement BPF_PROG_TYPE_CGROUP_SOCK ([`7b21a2d`](https://github.com/aya-rs/aya/commit/7b21a2d17eac57696352b2519bd76a4c7e9b1a2b)) - Unload programs on drop ([`0cd1e51`](https://github.com/aya-rs/aya/commit/0cd1e514763fd99dc287128317e9a36312ff6883))
## v0.11.0 (2022-06-06) ### Other - Rename forget_link to take_link - Add support for BPF_PROG_TYPE_SK_LOOKUP - Export program modules This allows access to XdpLink, XdpLinkId etc... which is currently unavailable since these modules are private - fix new lints on nightly - Add all crates to sidebar - Add BPF_PROG_TYPE_CGROUP_SOCK_ADDR - Implement forget_link - Fix lint against latest nightly - Relocate maps using symbol_index Since we support multiple maps in the same section, the section_index is no longer a unique way to identify maps. This commit uses the symbol index as the identifier, but falls back to section_index for rodata and bss maps since we don't retrieve the symbol_index during parsing. - revert version to 0.10.7 The world isn't ready to have pre-releases in git - rework links Remove LinkRef and remove the Rc> that was used to store type-erased link values in ProgramData. Among other things, this allows `Bpf` to be `Send`, which makes it easier to use it with async runtimes. Change the link API to: let link_id = prog.attach(...)?; ... prog.detach(link_id)?; Link ids are strongly typed, so it's impossible to eg: let link_id = uprobe.attach(...)?; xdp.detach(link_id); As it would result in a compile time error. Links are still stored inside ProgramData, and unless detached explicitly, they are automatically detached when the parent program gets dropped. - Support multiple maps in map sections This commit uses the symbol table to discover all maps inside an ELF section. Instead of doing what libbpf does - divide the section data in to equal sized chunks - we read in to section data using the symbol address and offset, thus allowing us to support definitions of varying lengths. - perf_buffer: call BytesMut::reserve() internally This changes PerfBuffer::read_events() to call BytesMut::reserve() internally, and deprecates PerfBufferError::MoreSpaceNeeded. This makes for a more ergonomic API, and allows for a more idiomatic usage of BytesMut. For example consider: let mut buffers = vec![BytesMut::with_capacity(N), ...]; loop { let events = oob_cpu_buf.read_events(&mut buffers).unwrap(); for buf in &mut buffers[..events.read] { let sub: Bytes = buf.split_off(n).into(); process_sub_buf(sub); } ... } This is a common way to process perf bufs, where a sub buffer is split off from the original buffer and then processed. In the next iteration of the loop when it's time to read again, two things can happen: - if processing of the sub buffer is complete and `sub` has been dropped, read_events() will call buf.reserve(sample_size) and hit a fast path in BytesMut that will just restore the original capacity of the buffer (assuming sample_size <= N). - if processing of the sub buffer hasn't ended (eg the buffer has been stored or is being processed in another thread), buf.reserve(sample_size) will actually allocate the new memory required to read the sample. In other words, calling buf.reserve(sample_size) inside read_events() simplifies doing zero-copy processing of buffers in many cases. ### Commit Statistics - 45 commits contributed to the release over the course of 57 calendar days. - 79 days passed between releases. - 13 commits were understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#256](https://github.com/aya-rs/aya/issues/256), [#264](https://github.com/aya-rs/aya/issues/264), [#268](https://github.com/aya-rs/aya/issues/268) ### Commit Details
view details * **[#256](https://github.com/aya-rs/aya/issues/256)** - Add support for BPF_PROG_TYPE_CGROUP_SYSCTL ([`f721021`](https://github.com/aya-rs/aya/commit/f721021a0af2a31d9f7c8d63100fbaa6b23a4b1e)) * **[#264](https://github.com/aya-rs/aya/issues/264)** - Program unload API ([`e2685c9`](https://github.com/aya-rs/aya/commit/e2685c98d8ff976610efea019d23d2f584f577c2)) * **[#268](https://github.com/aya-rs/aya/issues/268)** - Add support for BPF_PROG_TYPE_CGROUP_SOCKOPT ([`e68d734`](https://github.com/aya-rs/aya/commit/e68d734c68c9adb5269c0174cd06d416d5e0f5fe)) * **Uncategorized** - (cargo-release) version 0.11.0 ([`d85b36f`](https://github.com/aya-rs/aya/commit/d85b36f6d80236f142395f1ab173acbed74af99b)) - Merge pull request #306 from dave-tucker/take_link ([`4ae5bc4`](https://github.com/aya-rs/aya/commit/4ae5bc4b9b4b41c04b15b74b3293df217f55c6f1)) - Rename forget_link to take_link ([`b2a6f00`](https://github.com/aya-rs/aya/commit/b2a6f00212997a997799c88ba9022a69d9a0b582)) - Merge pull request #296 from aya-rs/codegen ([`de8ab7f`](https://github.com/aya-rs/aya/commit/de8ab7f4153041a34f3c5d876b1d9b6fdf062110)) - [codegen] Update libbpf to 4eb6485c08867edaa5a0a81c64ddb23580420340Update libbpf to 4eb6485c08867edaa5a0a81c64ddb23580420340 ([`bbb34b3`](https://github.com/aya-rs/aya/commit/bbb34b328587faf41a0aba42ff7eb9a785149028)) - Merge pull request #286 from nak3/add-BPF_MAP_TYPE_BLOOM_FILTER ([`1633700`](https://github.com/aya-rs/aya/commit/16337001e4f7140ab33d84368027cf5cdea658a0)) - Fix typo, take & to query the value ([`c192817`](https://github.com/aya-rs/aya/commit/c192817a59fe33f52819ec235cefbf6cda353086)) - Merge pull request #265 from dave-tucker/sklookup ([`a047354`](https://github.com/aya-rs/aya/commit/a0473548ca045be8b0f00b9b430b00a7350c6128)) - Add support for BPF_PROG_TYPE_SK_LOOKUP ([`2226b89`](https://github.com/aya-rs/aya/commit/2226b89ceb94ea29beb71376c43f371d2830ef61)) - Add support for BPF_MAP_TYPE_BLOOM_FILTER ([`c4262f7`](https://github.com/aya-rs/aya/commit/c4262f793dbce8558c5823a94db257ac227a5a0b)) - Merge pull request #281 from dave-tucker/export ([`7d8365c`](https://github.com/aya-rs/aya/commit/7d8365c3513532300b21fb80987835cb24e3402c)) - Export program modules ([`824baf9`](https://github.com/aya-rs/aya/commit/824baf9d642424b891ae8380cc3741fffe795123)) - Merge pull request #279 from aya-rs/codegen ([`de1559a`](https://github.com/aya-rs/aya/commit/de1559ab7759bd941d7f4a3a3ba373d99a3ac77a)) - [codegen] Update libbpf to 47595c2f08aece55baaf21ed0b72f5c5abf2cb5eUpdate libbpf to 47595c2f08aece55baaf21ed0b72f5c5abf2cb5e ([`4767664`](https://github.com/aya-rs/aya/commit/4767664d5d78c854b143669d489e3a90d2cbaf74)) - Merge pull request #278 from dave-tucker/riscv ([`b71fe64`](https://github.com/aya-rs/aya/commit/b71fe64a105589c806609e1f755171e77e673085)) - Riscv scaffolding for codegen ([`edaa70b`](https://github.com/aya-rs/aya/commit/edaa70b5ba2427ef9496732ff46f5526eab02a4d)) - Merge pull request #276 from dave-tucker/clippy ([`0d7fb44`](https://github.com/aya-rs/aya/commit/0d7fb4472d72f468b4c438c0e6967b99d3ac81f2)) - Fix new lints on nightly ([`4a32e7d`](https://github.com/aya-rs/aya/commit/4a32e7d985de5b55f263cf9244791debb34cc00f)) - Merge pull request #273 from dave-tucker/fix_sidebar ([`9904237`](https://github.com/aya-rs/aya/commit/9904237ac1f7dba7ef5d39d6127cdf8c591235cd)) - Add all crates to sidebar ([`ba312c4`](https://github.com/aya-rs/aya/commit/ba312c48d561b5a414cdd1301c322266e38118a4)) - Merge pull request #263 from nak3/cgroup-skb-attach-type ([`63b6286`](https://github.com/aya-rs/aya/commit/63b6286bd92382da90b28fd23f6fe35ed932ea28)) - Merge pull request #267 from aya-rs/codegen ([`aacf6ec`](https://github.com/aya-rs/aya/commit/aacf6ec110b1b9a59a5d3e00f022a040088e32fb)) - [codegen] Update libbpf to 86eb09863c1c0177e99c2c703092042d3cdba910Update libbpf to 86eb09863c1c0177e99c2c703092042d3cdba910 ([`7f7c78a`](https://github.com/aya-rs/aya/commit/7f7c78ad6b4bb619c209e9e724aeed8d9ea9a00c)) - Use map() ([`5d22869`](https://github.com/aya-rs/aya/commit/5d228695a46ebcde0e2e351f7b8f691daa3634ea)) - Merge pull request #261 from dave-tucker/cgroup_sock ([`8fd8816`](https://github.com/aya-rs/aya/commit/8fd8816dfdf2951017e6ee79aa6d0f2dc39c6edb)) - Add BPF_PROG_TYPE_CGROUP_SOCK_ADDR ([`af54b6c`](https://github.com/aya-rs/aya/commit/af54b6c818c4f08d599df82beeb3661b8e26ca48)) - Set attach type during load for BPF_PROG_TYPE_CGROUP_SKB ([`29c10fa`](https://github.com/aya-rs/aya/commit/29c10fafb70148c32b978185622214318ac5ea66)) - Merge pull request #253 from dave-tucker/forget ([`2fca4ae`](https://github.com/aya-rs/aya/commit/2fca4aee4e98b78c928be0c7aeb4c2166d93548a)) - Implement forget_link ([`8069ad1`](https://github.com/aya-rs/aya/commit/8069ad14d0baad310f52b9f1f5a651b77566310f)) - Merge pull request #254 from dave-tucker/clippy ([`e71e07f`](https://github.com/aya-rs/aya/commit/e71e07f88e634157dbb9b8f863d52603447a557d)) - Fix lint against latest nightly ([`cdaa3af`](https://github.com/aya-rs/aya/commit/cdaa3af5ae12161e12db438282912f7b027ea277)) - Merge pull request #252 from dave-tucker/multimap-relo ([`4afc5ea`](https://github.com/aya-rs/aya/commit/4afc5ea7117c99d5fc16f5646344c984537f15d6)) - Relocate maps using symbol_index ([`d1f2215`](https://github.com/aya-rs/aya/commit/d1f22151935edebed13e0baaa04f25a96ddb30f0)) - Revert version to 0.10.7 ([`4e57d1f`](https://github.com/aya-rs/aya/commit/4e57d1fe32763f3016a454941b8295ece4b36f9e)) - Merge pull request #251 from aya-rs/codegen ([`e1f448e`](https://github.com/aya-rs/aya/commit/e1f448e6b715505fbf83baea257addf49a23e413)) - [codegen] Update libbpf to 3a4e26307d0f9b227e3ebd28b443a1a715e4e17dUpdate libbpf to 3a4e26307d0f9b227e3ebd28b443a1a715e4e17d ([`d6ca3e1`](https://github.com/aya-rs/aya/commit/d6ca3e1ae71c4081a98c3e6d4564cc68cfaa5817)) - Merge pull request #249 from alessandrod/new-links ([`b039ac5`](https://github.com/aya-rs/aya/commit/b039ac524e2ba732f48f1117f14eb9c69299e6a5)) - Rework links ([`cb57d10`](https://github.com/aya-rs/aya/commit/cb57d10d25611a35b2cc34523d95b9f331470958)) - Merge pull request #181 from dave-tucker/multimap ([`5472ac0`](https://github.com/aya-rs/aya/commit/5472ac035463e2d60e11f05a21f606f5242d2357)) - Support multiple maps in map sections ([`f357be7`](https://github.com/aya-rs/aya/commit/f357be7db45b7201be6864e83fb7eb7e78cd984a)) - Merge pull request #243 from alessandrod/perf-reserve ([`a1d4499`](https://github.com/aya-rs/aya/commit/a1d4499967d8949aad9cd4b4e07f7478d0c3ee9b)) - Perf_buffer: call BytesMut::reserve() internally ([`ad1636d`](https://github.com/aya-rs/aya/commit/ad1636d2e795212ed6e326bd7df0fc60794be115))
## v0.10.7 (2022-03-19) ### Chore - formatting ### Bug Fixes - make maps compatible with kernel <= 4.14 In kernel 4.15 and additional parameter was added to allow maps to have names but using this breaks on older kernels. This change makes it so the name is only added on kernels 4.15 and newer. ### Other - fix lint errors - Improve documentation of set_global method Use `static` instead of `const` and mention the necessity of using `core::ptr::read_volatile`. - Fix Loading from cgroup/skb sections fa037a88e2f0820d2a64bbaae12464bf5dce083d allowed for cgroup skb programs that did not specify an attach direction to use the cgroup/skb section name per the convention established in libbpf. It did not add the necessary code to load programs from those sections which is added in this commit - implement Pod for arrays of Pod types If a type is POD (meaning it can be converted to a byte array), then an array of such type is POD. - update parking_lot requirement from 0.11.1 to 0.12.0 Updates the requirements on [parking_lot](https://github.com/Amanieu/parking_lot) to permit the latest version. - [Release notes](https://github.com/Amanieu/parking_lot/releases) - [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md) - [Commits](https://github.com/Amanieu/parking_lot/compare/0.11.1...0.12.0) --- updated-dependencies: - dependency-name: parking_lot dependency-type: direct:production ... - fix func_info/line_info offsets Given the new start instruction offset, rebase func_infos and line_infos. - relocate .text references Handle relocations against .text symbols in all instructions not just calls. Makes it so that let x = &some_function triggers linking of some_function in the current program and handles the resulting relocation accordingly. Among other things, enables the use of bpf_for_each_map_elem. - Replace / in DATASEC before load to kernel This replaces the / character with a . which is allowed in the kernel names. Not allowing a forward slash is perhaps a kernel bug, but lets fix it up here as it's commonly used for Aya - fix match arms Don't match on kind and use if let... Match on the BtfType - add a test for each BTF fix - fix borrow check errors - Merge Fixup and Sanitzation to single step Aya will now perform sanitzation and fixups in a single phase, requiring only one pass over the BTF. This modifies the parsed BTF in place. - Fix BTF verifier output Currently errors can occur if the verifier output is > buffer as we get ENOMEM. We should only provide a log_buf if initial load failed, then retry up to 10 times to get full verifier output. To DRY this logic it has been moved to a function so its shared with program loading - fix sanitization if BTF_FUNC_GLOBAL is not supported The lower 16 bits were not actually being cleared. - fixup func protos If an argument has a type, it must also have a name, see btf_func_check in the kernel. - run fixup in place - Fix name truncation - Truncate long program names - Add support for BTF_TYPE_KIND_{TAG,DECL_TAG} Adds support for two new BTF kinds including feature probes and BTF sanitization - Fix BTF type resolution for Arrays and Ints The union of `size` and `type` is unused in BTF_KIND_ARRAY. Type information of elements is in the btf_array struct that follows in the type_ field while the index type is in the index_type field. For BTF_KIND_INT, only the offset should be compared and size and signedness should be ignored. - maps: rename from_pinned() to open_pinned() - Retrieve program from pinned path - allocate func/line_info buffers outside if the pointer isn't valid in the current code! - document the public api - Add fixup for PTR types from Rust - Add Btf::to_bytes This allows for parsed BTF to be re-encoded such that it could be loaded in to the kernel. It moves bytes_of to the utils package. We could use Object::bytes_of, but this requires the impl of the Pod trait on generated code. - Fix for rename of BPF_ -> BPF_CORE_ - update object requirement from 0.27 to 0.28 Updates the requirements on [object](https://github.com/gimli-rs/object) to permit the latest version. - [Release notes](https://github.com/gimli-rs/object/releases) - [Changelog](https://github.com/gimli-rs/object/blob/master/CHANGELOG.md) - [Commits](https://github.com/gimli-rs/object/compare/0.27.0...0.28.1) --- updated-dependencies: - dependency-name: object dependency-type: direct:production ... - Remove unnecessary unsafe markers on map iteration. Map iteration can yield stale keys and values by virtue of sharing a data structure with BPF programs which can modify it. However, all accesses remain perfectly safe and will not cause memory corruption or data races. - eliminate name duplication in maps and programs. Map and ProgramData objects had unnecessarily cloned strings for their names, despite them being just as easily available to external users via bpf.maps() and bpf.programs(). - use correct program name when relocating - Improve section detection This commit improves section detection. Previously, a section named "xdp_metadata" would be interpretted as a program section, which is incorrect. This commit first attempts to identify a BPF section by name, then by section.kind() == SectionKind::Text (executable code). The computed section kind is stored in the Section so variants can be easily matched on later. - remove unnecessary usage of &dyn trait in favor of impl trait. This should improve performance in most situations by eliminating unnecessary fat pointer indirection. - programs_mut iterator to complement programs. - close file descriptors on Map drop. - expand include_bytes_aligned to accept expressions. This allows one to this macro with literal expressions involving macros such as concat! and env!. - fix test warnings ### Commit Statistics - 105 commits contributed to the release. - 125 days passed between releases. - 39 commits were understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#111](https://github.com/aya-rs/aya/issues/111) ### Commit Details
view details * **[#111](https://github.com/aya-rs/aya/issues/111)** - Fix test warnings ([`761cb79`](https://github.com/aya-rs/aya/commit/761cb79fe3b9006a4091dcc7d68604b671387194)) * **Uncategorized** - (cargo-release) version 0.10.7 ([`f01497e`](https://github.com/aya-rs/aya/commit/f01497e021aa6d2ab98141c53713a4457e6b2d68)) - Fix lint errors ([`9a642d3`](https://github.com/aya-rs/aya/commit/9a642d373f3bd3b96b1e0a031388a8161cae5143)) - Merge pull request #228 from nak3/fix-socket_filter ([`d690710`](https://github.com/aya-rs/aya/commit/d6907103376512249e28457e924d2ccdb5415237)) - Merge pull request #229 from dave-tucker/fix_cgroup_skb_attach_v2 ([`3dc9308`](https://github.com/aya-rs/aya/commit/3dc9308c8e7c4a263ab7d1a064655516b5016071)) - Merge pull request #224 from Tuetuopay/pod-arrays ([`02c376c`](https://github.com/aya-rs/aya/commit/02c376ceb7ff2a1704b3804fd7ef76d107bb0a6f)) - Merge pull request #238 from vadorovsky/fix-doc-set-global ([`5269ab5`](https://github.com/aya-rs/aya/commit/5269ab5b1cb2fc023b8cc38dddda778787fb858f)) - Improve documentation of set_global method ([`7dd2e3d`](https://github.com/aya-rs/aya/commit/7dd2e3d1f87559636ba33a7bbb76e57f20d43e8e)) - Merge pull request #237 from hi120ki/fix-typo-fentry ([`7fdf37a`](https://github.com/aya-rs/aya/commit/7fdf37ad51e993acf8b4ca4f936a0cedbadfa84a)) - Fix typo in aya/src/programs/fentry.rs ([`ab46253`](https://github.com/aya-rs/aya/commit/ab462533c7da6509bdc67d396517bc8113e37e6c)) - Fix unit test ([`5725a97`](https://github.com/aya-rs/aya/commit/5725a97648ada61813d4aec7b16e111ea628f730)) - Fix socket_filter section match ([`9e41317`](https://github.com/aya-rs/aya/commit/9e41317ca6f2834d9050632552f913eda9725848)) - Merge pull request #234 from xonatius/patch-1 ([`e0d818f`](https://github.com/aya-rs/aya/commit/e0d818ff2da62b7311dd99269cd47131b564093f)) - Fix typo in aya/README.md ([`49e998d`](https://github.com/aya-rs/aya/commit/49e998dc7eb248b5fe31a03bc1ca4db7e736bac7)) - Fix Loading from cgroup/skb sections ([`5ee1321`](https://github.com/aya-rs/aya/commit/5ee13217652216a3a01c82dd7d8a79ea8502ca12)) - Merge pull request #222 from aya-rs/dependabot/cargo/parking_lot-0.12.0 ([`00e34ec`](https://github.com/aya-rs/aya/commit/00e34ec29c120e44ea0bf6879f5630c664116b5c)) - Implement Pod for arrays of Pod types ([`08211f6`](https://github.com/aya-rs/aya/commit/08211f6132fd93493267e853139f5f5724e093b0)) - Update parking_lot requirement from 0.11.1 to 0.12.0 ([`ab7eed2`](https://github.com/aya-rs/aya/commit/ab7eed2759d062fbe267e7e96f84c7a3f477ef11)) - Merge pull request #161 from nimrodshn/add_lpm_trie ([`2a18239`](https://github.com/aya-rs/aya/commit/2a1823934671ced3c910a2e6f287ba569bea9c60)) - Fix #128: Add support for BPF_MAP_TYPE_LPM_TRIE map ([`c6e66d8`](https://github.com/aya-rs/aya/commit/c6e66d8080c8ca50917ced2d3d86ba1fb2af6758)) - Merge pull request #179 from dave-tucker/btf_datasec_name ([`6316748`](https://github.com/aya-rs/aya/commit/6316748ec1a6c0f5673d12f2d7e48ff607ad6af8)) - Merge pull request #177 from alessandrod/ptr-relocs ([`b2182c6`](https://github.com/aya-rs/aya/commit/b2182c6c4ed343d9d1e8d3180adf93199806846a)) - Fix func_info/line_info offsets ([`f169a3f`](https://github.com/aya-rs/aya/commit/f169a3fc6bb203d2a41de449472c1115b49ffe15)) - Relocate .text references ([`8202105`](https://github.com/aya-rs/aya/commit/8202105b7dd415fc028050daada44a75d2ed7202)) - Replace / in DATASEC before load to kernel ([`825bb3a`](https://github.com/aya-rs/aya/commit/825bb3ad2044e186f873acbbb0a53de8d2b6e6cc)) - Merge pull request #175 from dave-tucker/merge_fixup_sanitize ([`1904aea`](https://github.com/aya-rs/aya/commit/1904aeaef9aa1a450b7d319fc9df7f5e52f06793)) - Fix match arms ([`99fa85e`](https://github.com/aya-rs/aya/commit/99fa85eab899c807c76274663240a19b4df41371)) - Add a test for each BTF fix ([`326825a`](https://github.com/aya-rs/aya/commit/326825aab0b54898d9eb2e5338d70c8c663ed0e3)) - Fix borrow check errors ([`4efc206`](https://github.com/aya-rs/aya/commit/4efc2061a8aa0c25cb648a86cdc39ca44784de94)) - Merge Fixup and Sanitzation to single step ([`a1b46ec`](https://github.com/aya-rs/aya/commit/a1b46ece05e73896250f86815c4ad6df6095797d)) - Merge pull request #164 from dave-tucker/btf_verifier ([`06f8938`](https://github.com/aya-rs/aya/commit/06f89388082274155519e620cc046feeba7aab00)) - Fix BTF verifier output ([`5d8b279`](https://github.com/aya-rs/aya/commit/5d8b279265bd2715b83cbed871697bbc763a00a9)) - Merge pull request #173 from alessandrod/func-proto-fixup ([`d9496df`](https://github.com/aya-rs/aya/commit/d9496df3a7d6762732a5dad943e9c8cdf82de25a)) - Merge pull request #174 from alessandrod/func-global-fix ([`f70ab2c`](https://github.com/aya-rs/aya/commit/f70ab2caa746fd1f07213d9d02eb649d3fb8f948)) - Fix sanitization if BTF_FUNC_GLOBAL is not supported ([`7ad0524`](https://github.com/aya-rs/aya/commit/7ad0524283006fce221910df4c1817af503b5b61)) - Fixup func protos ([`9ba2e14`](https://github.com/aya-rs/aya/commit/9ba2e147a1a82e97849cc8b7ca524550803ec3a9)) - Run fixup in place ([`89b5dd3`](https://github.com/aya-rs/aya/commit/89b5dd32ede08d3aeb5a07cf980f8af8ff326445)) - Merge pull request #168 from dave-tucker/decl_tag ([`b45a160`](https://github.com/aya-rs/aya/commit/b45a160bb0018529a006cfe6f58a73d671116a4c)) - Merge pull request #172 from dave-tucker/name_trunc ([`b93188f`](https://github.com/aya-rs/aya/commit/b93188fefebcdae19b7856d6009918386bf55e10)) - Fix name truncation ([`8f9a32f`](https://github.com/aya-rs/aya/commit/8f9a32ff10a13d414ff95edc2f5645a7a5162732)) - Merge pull request #171 from dave-tucker/nametoolong ([`dccdc45`](https://github.com/aya-rs/aya/commit/dccdc45ccd91452f511f18147cb84da8b9e6dd2b)) - Truncate long program names ([`437432c`](https://github.com/aya-rs/aya/commit/437432cdd60bbe11e7021f52297e459fd14ff069)) - Add support for BTF_TYPE_KIND_{TAG,DECL_TAG} ([`5d9ff70`](https://github.com/aya-rs/aya/commit/5d9ff70498785ea1becbc347c6798f76be11036f)) - Merge pull request #169 from dave-tucker/fix_array_relo ([`1492d85`](https://github.com/aya-rs/aya/commit/1492d85a7bad7749fc06fea32ceafdcd12d34107)) - Merge pull request #157 from dave-tucker/doc-aya ([`6a91fdf`](https://github.com/aya-rs/aya/commit/6a91fdf5a7f0bbce94e679a41d2f9b7f5fbaa41c)) - Fix BTF type resolution for Arrays and Ints ([`686ce45`](https://github.com/aya-rs/aya/commit/686ce45f930cef68f6fdfb73dc5ebc2d259d5954)) - Merge pull request #167 from aya-rs/codegen ([`0118773`](https://github.com/aya-rs/aya/commit/01187735f0c5ecdb3c632a5ed3b8508309ca0ee4)) - Update libbpf to be89b28f96be426e30a2b0c5312d13b30ee518c7 ([`324c679`](https://github.com/aya-rs/aya/commit/324c679a41ba7e5448092a0e5b1ca7e06adb78e2)) - Maps: rename from_pinned() to open_pinned() ([`4e9bc32`](https://github.com/aya-rs/aya/commit/4e9bc32a3dee903f8bfe71430c03b1c664607c5d)) - Merge pull request #165 from dave-tucker/prog_pinned ([`f12054a`](https://github.com/aya-rs/aya/commit/f12054a00dbd48e0aa66d818a818690769c4a10b)) - Retrieve program from pinned path ([`abc8d27`](https://github.com/aya-rs/aya/commit/abc8d27440de76cf5ed2ca4aa56883bc07d3afc4)) - Merge pull request #163 from aya-rs/codegen ([`353b5f9`](https://github.com/aya-rs/aya/commit/353b5f9cb170394b8a8742a3e6c65d4c086648e2)) - Update libbpf to 22411acc4b2c846868fd570b2d9f3b016d2af2cb ([`0619f80`](https://github.com/aya-rs/aya/commit/0619f8009085090c2afd0614701e74dd6fc669f5)) - Merge pull request #158 from dave-tucker/btf-fix ([`001348a`](https://github.com/aya-rs/aya/commit/001348a301372a08e62d274fb2ead5d110d1d79e)) - Allocate func/line_info buffers outside if ([`83cfe56`](https://github.com/aya-rs/aya/commit/83cfe56fe7690e752ebb646509bd282db227af2b)) - Document the public api ([`bca0158`](https://github.com/aya-rs/aya/commit/bca01580e722a20e5c6026a744c92c5423f6437b)) - Merge pull request #127 from dave-tucker/ext ([`c5a10f8`](https://github.com/aya-rs/aya/commit/c5a10f8fbe1c2b27651f9b11d077399612b8318f)) - Add fixup for PTR types from Rust ([`877c760`](https://github.com/aya-rs/aya/commit/877c76043a6d313391159523dc40046426800c43)) - Add BPF_PROG_TYPE_EXT ([`5c6131a`](https://github.com/aya-rs/aya/commit/5c6131afba02e22531fa82d8f40444311aeec5c9)) - Add Btf::to_bytes ([`379bb31`](https://github.com/aya-rs/aya/commit/379bb313b13dd259a26fe3513ae6784bb85291ef)) - Merge pull request #146 from dave-tucker/ro-maps ([`faa3676`](https://github.com/aya-rs/aya/commit/faa36763f78d3190492508ce9ed40d98eca81750)) - Mark .rodata maps as readonly and freeze on load ([`65a0b83`](https://github.com/aya-rs/aya/commit/65a0b832057a007f8a64eb5c2e3de712e502d634)) - Merge pull request #145 from aya-rs/codegen ([`3a4c84f`](https://github.com/aya-rs/aya/commit/3a4c84fe17f0f308c618788da9a88269ab10560f)) - Fix for rename of BPF_ -> BPF_CORE_ ([`2b7dda7`](https://github.com/aya-rs/aya/commit/2b7dda766f3b002fc96915366d560c0a279106e3)) - Update libbpf to 19656636a9b9a2de1f71fa3135709295c16701cc ([`05d4bc3`](https://github.com/aya-rs/aya/commit/05d4bc39ea4dd6897aa6685cec37e57e0f039577)) - Support for fentry and fexit programs ([`7e2fcd1`](https://github.com/aya-rs/aya/commit/7e2fcd1d6d86af4c818f2140e23061154430f33f)) - Update object requirement from 0.27 to 0.28 ([`54b0c67`](https://github.com/aya-rs/aya/commit/54b0c677958b07fb2a8ece28fd55251b74dcebc8)) - Merge pull request #136 from nimrodshn/add_impl_pod_for_u128 ([`6313ddf`](https://github.com/aya-rs/aya/commit/6313ddfe0cae7d7581a473a3f942856e3e2e4fc9)) - Implement Pod for u128 ([`24a292f`](https://github.com/aya-rs/aya/commit/24a292f605220c4df11e54c93c76819b3dd42909)) - Merge pull request #134 from aya-rs/codegen ([`f34b76c`](https://github.com/aya-rs/aya/commit/f34b76c8d3bc55c3671252bb54e3d4d64ba21ddd)) - Update libbpf to 93e89b34740c509406e948c78a404dd2fba67b8b ([`17d43cd`](https://github.com/aya-rs/aya/commit/17d43cd6f8b7894ef3741bbb2b51f726a879e2b2)) - Merge pull request #125 from dave-tucker/btf ([`26d188c`](https://github.com/aya-rs/aya/commit/26d188c659e905d4121bc97a574f961172593889)) - Merge pull request #131 from eero-thia/thia/safe_iter ([`441a660`](https://github.com/aya-rs/aya/commit/441a660b3e6b540de82377c46f4d6f2709b7462c)) - Remove unnecessary unsafe markers on map iteration. ([`1897036`](https://github.com/aya-rs/aya/commit/18970369e27228c45117b125724a22d8999ec1fc)) - Merge pull request #120 from eero-thia/thia/dedup ([`07a6016`](https://github.com/aya-rs/aya/commit/07a6016ebb370bc3d37c2865ed65bd0028f1eeb2)) - Eliminate name duplication in maps and programs. ([`f56dd0a`](https://github.com/aya-rs/aya/commit/f56dd0a70b36a97036eb9447efa20f0e1c93c8d7)) - Merge pull request #130 from wg/main ([`a340c2a`](https://github.com/aya-rs/aya/commit/a340c2a9fa1a01a237d1c0bb2f53c463c8719470)) - Use correct program name when relocating ([`bb8a813`](https://github.com/aya-rs/aya/commit/bb8a813eefd86fbdb218174ccb7bfd2578ab9692)) - Improve section detection ([`e4d9774`](https://github.com/aya-rs/aya/commit/e4d9774bf780eee5c3740d153df0682265089307)) - Merge pull request #115 from eero-thia/thia/impl_trait ([`a03426f`](https://github.com/aya-rs/aya/commit/a03426f1947ce82227431392d20a905a1347bcd8)) - Remove unnecessary usage of &dyn trait in favor of impl trait. ([`daa7ea6`](https://github.com/aya-rs/aya/commit/daa7ea6d0dad1895768d3a1cdc62911b15c72a94)) - Merge pull request #116 from eero-thia/thia/close ([`98b36b2`](https://github.com/aya-rs/aya/commit/98b36b23bc7b52fae4dce98b905be52adf12167f)) - Merge pull request #121 from eero-thia/thia/programs_mut ([`2955ca1`](https://github.com/aya-rs/aya/commit/2955ca1d1f350a0fc266410c464dbefcb6a42e2f)) - Programs_mut iterator to complement programs. ([`c7f8db9`](https://github.com/aya-rs/aya/commit/c7f8db9a0b4e632233561fdd075cf201ae7cccb5)) - Merge pull request #122 from eero-thia/thia/include_bytes_aligned ([`a6bf554`](https://github.com/aya-rs/aya/commit/a6bf554a74bf7a82bae9d97050505a03b241ef61)) - Close file descriptors on Map drop. ([`1584bc4`](https://github.com/aya-rs/aya/commit/1584bc47bd027455b07e456683c0fb97920a5314)) - Expand include_bytes_aligned to accept expressions. ([`f8f17a0`](https://github.com/aya-rs/aya/commit/f8f17a09fbf1b87d14735e015c55d387a6ed048b)) - Merge pull request #108 from deverton/kprobe-debugfs ([`6db30fa`](https://github.com/aya-rs/aya/commit/6db30fad9ca8151abe51d1ccfafc3e90f9fd4adc)) - Refactoring after feedback. ([`0e84610`](https://github.com/aya-rs/aya/commit/0e84610976c3148e1912f337e1589104373f0a96)) - Support pid filtering in debugfs ([`606c326`](https://github.com/aya-rs/aya/commit/606c3267c42a1a3b7e20dba193bb6fbcbc114105)) - Handle probe entry offsets ([`1dc7554`](https://github.com/aya-rs/aya/commit/1dc75542b4f500d43c158f1bc4dc4db142c612f2)) - Merge branch 'main' into kprobe-debugfs ([`4e6aeb2`](https://github.com/aya-rs/aya/commit/4e6aeb2e6959a4872f55283e1968053dfa5e02e8)) - Merge pull request #109 from deverton/dynamic-kver ([`b82d7f0`](https://github.com/aya-rs/aya/commit/b82d7f0515a8d7ffab1f61edd5d843b7f6b2dccc)) - Updates based on feedback ([`3dff6e8`](https://github.com/aya-rs/aya/commit/3dff6e855521a3bdbf11f6d4da98d1ea5d7536a3)) - Use current kernel version as default if not specified ([`4277205`](https://github.com/aya-rs/aya/commit/4277205e9d4f6a28a1f38aa8a990bdd97e683af1)) - Functional detach of debugfs probes. ([`42c9737`](https://github.com/aya-rs/aya/commit/42c9737d47f35f4b6f9d7e65be590d53a7c69e35)) - Fix event_alias comparison when looking in event list ([`a4faabc`](https://github.com/aya-rs/aya/commit/a4faabcf93400ae0ee85c49b92ed6343dae3aee8)) - Don't duplicate perf_attach code and formatting ([`84fa219`](https://github.com/aya-rs/aya/commit/84fa2197ec42305fb5366995b28fb720bc819041)) - Attempt auto detach of probe for debugfs ([`d0321bd`](https://github.com/aya-rs/aya/commit/d0321bd1ee92ce7157ce948eebe54845b447c378)) - Support k/uprobes on older kernels. ([`34aa790`](https://github.com/aya-rs/aya/commit/34aa790a917512783fa50c60527b2e694fb93ce3)) - Merge pull request #107 from deverton/skip-map-name ([`5b0e518`](https://github.com/aya-rs/aya/commit/5b0e5186414749c6e135aa6c1ceb7d67259fc4a1)) - Formatting ([`07e3824`](https://github.com/aya-rs/aya/commit/07e3824aa4972fee73bbb0c9e3b96a417615aafb)) - Stub `kernel_version` for tests ([`49f6a8e`](https://github.com/aya-rs/aya/commit/49f6a8e81949e127559d7f5698abe19fca4be853)) - Fix lint issues ([`d966881`](https://github.com/aya-rs/aya/commit/d966881e46ce191bb29d624fed9c740f3078567e)) - Make maps compatible with kernel <= 4.14 ([`fc08611`](https://github.com/aya-rs/aya/commit/fc0861105a632deb15b17855160e0b375d7c5305))
## v0.10.6 (2021-11-13) ### Other - fix name parsing for sk_skb sections This commit fixes name parsing of sk_skb sections such that both named and unnamed variants will work correctly. - netlink: use NETLINK_EXT_ACK from libc crate NETLINK_EXT_ACK is available since libc crate version 0.2.105, see https://github.com/rust-lang/libc/releases/tag/0.2.105 - fix incorrect section size for .bss - improve map errors to be more descriptive - pass Btf by reference instead of loading new Btf in Lsm::load - implement btf tracepoint programs - fix include_bytes_aligned! macro to work in some corner cases I found a corner case in my own development workflow that caused the existing macro to not work properly. The following changes appear to fix things. Ideally, we could add some test cases to CI to prevent regressions. This would require creating a dedicated directory to hold test cases so that we can "include" them at compile time. - introduce include_bytes_aligned!() macro This is a helper macro that can be used to include bytes at compile-time that can then be used in Bpf::load(). Unlike std's include_bytes!(), this macro also ensures that the resulting byte array is correctly aligned so that it can be parsed as an ELF binary. - update object requirement from 0.26 to 0.27 Updates the requirements on [object](https://github.com/gimli-rs/object) to permit the latest version. - [Release notes](https://github.com/gimli-rs/object/releases) - [Changelog](https://github.com/gimli-rs/object/blob/master/CHANGELOG.md) - [Commits](https://github.com/gimli-rs/object/compare/0.26.0...0.27.0) --- updated-dependencies: - dependency-name: object dependency-type: direct:production ... ### Commit Statistics - 22 commits contributed to the release. - 28 days passed between releases. - 9 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - (cargo-release) version 0.10.6 ([`182182d`](https://github.com/aya-rs/aya/commit/182182d8404fefaf9b34f432c4b06c14ef4f78f8)) - Merge pull request #104 from dave-tucker/fix_skskb_load ([`daf8630`](https://github.com/aya-rs/aya/commit/daf863013360c834428bef8ecbe82b9f71dd023e)) - Fix name parsing for sk_skb sections ([`352e54b`](https://github.com/aya-rs/aya/commit/352e54b72405b5e9f21a947ff0146f3ba162b78a)) - Merge pull request #98 from aya-rs/codegen ([`f632f81`](https://github.com/aya-rs/aya/commit/f632f81db152fd1185047550ec819926a83b0eae)) - Update libbpf to 16dfb4ffe4aed03fafc00e0557b1ce1310a09731 ([`4a7f47d`](https://github.com/aya-rs/aya/commit/4a7f47d93a4e392072df06c8c2f3bbb9aaee6df7)) - Merge pull request #94 from tklauser/netlink-ext-ack-libc ([`563d4ba`](https://github.com/aya-rs/aya/commit/563d4ba1c38a420e408ae17df9c73acd5d244eb3)) - Netlink: use NETLINK_EXT_ACK from libc crate ([`2136f05`](https://github.com/aya-rs/aya/commit/2136f0546161adb55947c1a3ad002b236106b737)) - Merge pull request #90 from willfindlay/fix-bss ([`dd7e1de`](https://github.com/aya-rs/aya/commit/dd7e1de348fc14e6e1e40e6498ccc5488f8b1456)) - Fix incorrect section size for .bss ([`1e6b1af`](https://github.com/aya-rs/aya/commit/1e6b1afbe42b191f18bef28e9dc3adff9c739eae)) - Merge pull request #89 from willfindlay/errors ([`3a8e4fe`](https://github.com/aya-rs/aya/commit/3a8e4fe9b91538a0fafd8c91ae96185c1a017651)) - Improve map errors to be more descriptive ([`27d803b`](https://github.com/aya-rs/aya/commit/27d803b634d3f540fa36163c6f6eb146ffdb7e27)) - Merge pull request #85 from willfindlay/tp_btf ([`17b730c`](https://github.com/aya-rs/aya/commit/17b730c717b3696196b3a7c1562991f880d921a0)) - Pass Btf by reference instead of loading new Btf in Lsm::load ([`6b6d4af`](https://github.com/aya-rs/aya/commit/6b6d4af932a31632e8b1ee0a23be4ec6636194fb)) - Implement btf tracepoint programs ([`6539cbb`](https://github.com/aya-rs/aya/commit/6539cbb555fc7e597c814f56b3ef8bacd2bcd895)) - Merge pull request #68 from vadorovsky/lsm ([`140005d`](https://github.com/aya-rs/aya/commit/140005d9e30818e86bc27ff79767075d0cca62ff)) - Add support for raw tracepoint and LSM programs ([`169478c`](https://github.com/aya-rs/aya/commit/169478c863adea838ef9a73a8b3323b4815b4ee2)) - Merge pull request #78 from willfindlay/main ([`56fd09c`](https://github.com/aya-rs/aya/commit/56fd09c443e1d1e00ffba18497d786b71a6b5292)) - Fix include_bytes_aligned! macro to work in some corner cases ([`99f6f9e`](https://github.com/aya-rs/aya/commit/99f6f9e14d4cdcdd53b3df3cf107d041e662ea06)) - Merge pull request #76 from willfindlay/load_include_bytes ([`a947747`](https://github.com/aya-rs/aya/commit/a94774755f3198d972d5ddd5225beef614b2fc5d)) - Introduce include_bytes_aligned!() macro ([`4df4e9c`](https://github.com/aya-rs/aya/commit/4df4e9c14eb1019a8c2299c48b15420ee7f20855)) - Bump libbpf to 92c1e61a605410b16d6330fdd4a7a4e03add86d4 ([`03e9935`](https://github.com/aya-rs/aya/commit/03e993535827aa4b654ca489726da3cc6b275408)) - Update object requirement from 0.26 to 0.27 ([`c99dcfb`](https://github.com/aya-rs/aya/commit/c99dcfb9d33ba762ed005ac6d53a2290901a83d7))
## v0.10.5 (2021-10-15) ### Other - fix call relocation bug Take the section offset into account when looking up relocation entries - Disable Stacked Borrows and skip some tests The perf_buffer code fails due to stacked borrows, skip this for now. munmap isn't supported by miri. - fix clippy - improve docs a bit and make BpfLoader default to loading BTF if available - loader: take BTF info as reference Allows sharing the same BTF info across many loaders ### Commit Statistics - 7 commits contributed to the release. - 24 days passed between releases. - 5 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - (cargo-release) version 0.10.5 ([`4152e8b`](https://github.com/aya-rs/aya/commit/4152e8b1a43f24d005870cb68a0ef3dbd76169c7)) - Fix call relocation bug ([`59a1854`](https://github.com/aya-rs/aya/commit/59a1854a6bd74845e3c45227ade757399a376897)) - Disable Stacked Borrows and skip some tests ([`dc4b928`](https://github.com/aya-rs/aya/commit/dc4b928ec5a1a40fa19af5a4f8f5141fa7f91425)) - Fix clippy ([`52c5189`](https://github.com/aya-rs/aya/commit/52c51895ba1fdcd2ee627e7b7d3d8bb4622c2a1d)) - Improve docs a bit and make BpfLoader default to loading BTF if available ([`64e3fb4`](https://github.com/aya-rs/aya/commit/64e3fb4cc82eb944327a4decc46c66d85305a564)) - Loader: take BTF info as reference ([`5f8f18e`](https://github.com/aya-rs/aya/commit/5f8f18e3a1b13b2452294d6c8a33dde961fa511c)) - Implement Pinning For Programs and Maps ([`9426f36`](https://github.com/aya-rs/aya/commit/9426f36f79101e296ec3ffc4bbef8913a1130eff))
## v0.10.4 (2021-09-21) ### Other - minor PerfEvent API tweaks - run xtask codegen aya - only consider Text symbols as relocatable functions - fix bug with nested call relocations Use the correct offset when looking up relocation entries while doing nested call relocations. - update authors and repository link - Fix size of Unknown variant The size of Unknown should be ty_size, otherwise when it is encountered, we never advance the cursor and it creates an infinite loop. - Add some tests for reading btf data - Add bindings for BTF_KIND_FLOAT ### Commit Statistics - 13 commits contributed to the release. - 52 days passed between releases. - 8 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - (cargo-release) version 0.10.4 ([`a7f5b37`](https://github.com/aya-rs/aya/commit/a7f5b3775d27c89e226c49c4a2f21d0e90b08591)) - Bump obj to 0.26 ([`a10a7b3`](https://github.com/aya-rs/aya/commit/a10a7b3bf2a7719e2c08c7474b7b89b1f5b4a35a)) - Minor PerfEvent API tweaks ([`98361a4`](https://github.com/aya-rs/aya/commit/98361a4c931cbfe5190da64d3efc70547219a877)) - Run xtask codegen aya ([`b0a05e7`](https://github.com/aya-rs/aya/commit/b0a05e759e49a164eead2990d93c793ca494f7c6)) - Add support for PerfEvent programs. ([`c39dff6`](https://github.com/aya-rs/aya/commit/c39dff602530b757367f93099a867a4307a7d519)) - Only consider Text symbols as relocatable functions ([`c56a6b1`](https://github.com/aya-rs/aya/commit/c56a6b16aa3fdafeb531c280c7a5d8dae7a4612a)) - Fix bug with nested call relocations ([`d9fc0f4`](https://github.com/aya-rs/aya/commit/d9fc0f484ffbe17a5a8b8e7b697ee9de4d46ff65)) - Make Clippy Happy ([`e9bad0b`](https://github.com/aya-rs/aya/commit/e9bad0b61d95afb3ffc119be7a6fd9a109758a7b)) - Update authors and repository link ([`9c27910`](https://github.com/aya-rs/aya/commit/9c27910f76d7152091973dfe99c37ae448c25541)) - Remove docs. Update URLs to aya-rs ([`8acb92d`](https://github.com/aya-rs/aya/commit/8acb92d61cd21ebaaf7a529b977947ef7e10abfc)) - Fix size of Unknown variant ([`4e1ce25`](https://github.com/aya-rs/aya/commit/4e1ce2534c23a51a67cd3e56fe389e207bdcf3b3)) - Add some tests for reading btf data ([`569b8ca`](https://github.com/aya-rs/aya/commit/569b8ca39ed0aac5b814f86d6f54ed44dc6295c4)) - Add bindings for BTF_KIND_FLOAT ([`753a683`](https://github.com/aya-rs/aya/commit/753a683704f685582da37b0290a66cf37e1092d7))
## v0.10.3 (2021-07-31) ### Bug Fixes - pass BTF object by reference in order to allow multiple eBPF programs to share it and save memory (closes #30). ### Other - programs: tweak LircMode2::query doc. - netlink: fix clippy lint - fix clippy warnings - tc: add qdisc_detach_program qdisc_detach_program can be used to detach all the programs that have the given name. It's useful when you want to detach programs that were attached by some other process (eg. iproute2), or when you want to detach programs that were previously left attached because the program that attached them was killed. - netlink: fix alignment when writing attributes - netlink: fix handling of multipart messages - tc: clean up netlink code a bit - fix formatting - fix clippy warnings - obj: improve parse_map_def tests Add a test that checks that we handle ELF section padding correctly and simplify the other tests. - don't error out parsing padded map sections - tc: make qdisc_add_clsact return io::Error - kprobe: remove pid argument Kprobes can only be attached globally. Per-pid logic needs to be implemented on the BPF side with bpf_get_current_pid_tgid. - add minimum kernel version for each map and program type - add missing load() in kprobe example - support both bpf_map_def layout variants Libbpf and iproute2 use two slightly different `bpf_map_def` layouts. This change implements support for loading both. - netlink: tc: use ptr::read_unaligned instead of deferencing a potentially unaligned ptr - netlink: port TC code to using new nlattr utils - netlink: refactor nlattr writing code - netlink: introduce NestedAttrs builder and switch XDP to it NestedAttrs is a safe interface for writing nlattrs. This is the first step towards making the netlink code safer and easier to maintain. - refactor program section parsing This renames aya::obj::ProgramKind to aya::obj::ProgramSection and moves all the program section parsing to ProgramSection::from_str. - fix tracepoint prefix in a couple more places - fix trace point section name Trace points have prefix "tracepoint" not "trace_point". ### Commit Statistics - 29 commits contributed to the release. - 43 days passed between releases. - 24 commits were understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#18](https://github.com/aya-rs/aya/issues/18), [#31](https://github.com/aya-rs/aya/issues/31), [#32](https://github.com/aya-rs/aya/issues/32) ### Commit Details
view details * **[#18](https://github.com/aya-rs/aya/issues/18)** - Add minimum kernel version for each map and program type ([`35f15f7`](https://github.com/aya-rs/aya/commit/35f15f70e0d83f5e19153c9d2917add10c154d1e)) * **[#31](https://github.com/aya-rs/aya/issues/31)** - Pass BTF object by reference in order to allow multiple eBPF programs to share it and save memory (closes #30). ([`b4b019e`](https://github.com/aya-rs/aya/commit/b4b019e447c9829a0405b0fd40f1f2f66652db8f)) * **[#32](https://github.com/aya-rs/aya/issues/32)** - Implement query for lirc programs ([`81e07e9`](https://github.com/aya-rs/aya/commit/81e07e96611652b1b1ec4bb9121732862692cf2d)) * **Uncategorized** - (cargo-release) version 0.10.3 ([`f30abca`](https://github.com/aya-rs/aya/commit/f30abca15e77ea841643f31a4bab0c30113dcda4)) - Programs: tweak LircMode2::query doc. ([`66a12ff`](https://github.com/aya-rs/aya/commit/66a12ffcf70b2702a9eee7f89cabe64aa9c46126)) - Netlink: fix clippy lint ([`8c03ba0`](https://github.com/aya-rs/aya/commit/8c03ba052a168d390c9d997f4bbf32d610864042)) - Fix clippy warnings ([`fa2cbe2`](https://github.com/aya-rs/aya/commit/fa2cbe2f825cc0c257e983793af58aa63aea6287)) - Tc: add qdisc_detach_program ([`c2a90c2`](https://github.com/aya-rs/aya/commit/c2a90c2c010e69508c5704542f1133d82f793aa0)) - Netlink: fix alignment when writing attributes ([`0a9d021`](https://github.com/aya-rs/aya/commit/0a9d02140acdaa35c5f5c7e17ea08a6823922e20)) - Netlink: fix handling of multipart messages ([`abb199e`](https://github.com/aya-rs/aya/commit/abb199e6f436b67485ed77211daca3e990ca6c0d)) - Tc: clean up netlink code a bit ([`9185f32`](https://github.com/aya-rs/aya/commit/9185f32f6f7bb33c160ded526e7122462fef77dd)) - Fix formatting ([`d996a88`](https://github.com/aya-rs/aya/commit/d996a88de47d60053504695036a14157ce6b3aa6)) - Fix clippy warnings ([`0878c45`](https://github.com/aya-rs/aya/commit/0878c4505a95931c745b6b0bdbcb5413579acd85)) - Obj: improve parse_map_def tests ([`21e01df`](https://github.com/aya-rs/aya/commit/21e01df242376d4a9d4f67664277263f0dc8d173)) - Don't error out parsing padded map sections ([`b657930`](https://github.com/aya-rs/aya/commit/b657930a3ee61f88ada0630afdac6b1c77459244)) - Added support for armv7-unknown-linux-gnueabi and armv7-unknown-linux-gnueabihf ([`8311abf`](https://github.com/aya-rs/aya/commit/8311abfdcbbe70da6abdd67b78b831d53998aad5)) - Tc: make qdisc_add_clsact return io::Error ([`9c8e78b`](https://github.com/aya-rs/aya/commit/9c8e78b7d4192b376ec2e532d9ddcf81c3c5182e)) - Aya, aya-bpf-bindings: regenerate bindings ([`122a530`](https://github.com/aya-rs/aya/commit/122a5306e72c7560629bcef160e7f676b84eabd7)) - Kprobe: remove pid argument ([`08c71df`](https://github.com/aya-rs/aya/commit/08c71dfeb19b2b4358d75baf5b95f8d4e6521935)) - Add missing load() in kprobe example ([`bb15e82`](https://github.com/aya-rs/aya/commit/bb15e82c1d8373700dda52f69d6c4bf6f5489a03)) - Support both bpf_map_def layout variants ([`d8d3117`](https://github.com/aya-rs/aya/commit/d8d311738c974f3b6fad22006ab2b827d0925ce8)) - Netlink: tc: use ptr::read_unaligned instead of deferencing a potentially unaligned ptr ([`5f0ff16`](https://github.com/aya-rs/aya/commit/5f0ff1698a12141ffe50e160de252f664773c140)) - Netlink: port TC code to using new nlattr utils ([`7f2ceaf`](https://github.com/aya-rs/aya/commit/7f2ceaf12e3aeadd81a55a75c268f254192cf866)) - Netlink: refactor nlattr writing code ([`d9b5ab5`](https://github.com/aya-rs/aya/commit/d9b5ab575f6e2cbca793881094e1846a39332fa1)) - Netlink: introduce NestedAttrs builder and switch XDP to it ([`c240a2c`](https://github.com/aya-rs/aya/commit/c240a2c73381a6864f343c79069abfd5f9e9b729)) - Refactor program section parsing ([`bb595c4`](https://github.com/aya-rs/aya/commit/bb595c4e69ff0c72c8327e7f64d43ca7a4bc16a3)) - Fix tracepoint prefix in a couple more places ([`0188622`](https://github.com/aya-rs/aya/commit/018862258064a39f5613ecc81c1e257bea2c4e74)) - Fix trace point section name ([`a0151dd`](https://github.com/aya-rs/aya/commit/a0151dd48520ac801042da3c26bf4739b549d1b1)) - Merge pull request #4 from seanyoung/doctest ([`521ef09`](https://github.com/aya-rs/aya/commit/521ef09463278588004bcec8dcd22d4f8caeb1ab))
## v0.10.2 (2021-06-17) ### Other - tc: fix QdiscRequest layout ### Commit Statistics - 5 commits contributed to the release. - 1 day passed between releases. - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - (cargo-release) version 0.10.2 ([`d70e291`](https://github.com/aya-rs/aya/commit/d70e29158037a083d5886d30d2029bd054974af5)) - Tc: fix QdiscRequest layout ([`fee71b4`](https://github.com/aya-rs/aya/commit/fee71b42f16e4d1f683e94f64b038f6e8b2f4f0a)) - Fix doctest and run them during CI ([`1196ba1`](https://github.com/aya-rs/aya/commit/1196ba1dccebfb0953d0e4d5244f81612600fdb0)) - Merge pull request #3 from seanyoung/lirc ([`59cfbc5`](https://github.com/aya-rs/aya/commit/59cfbc51c824d576ae9c0ea0815ad44d40107ac4)) - Add support for lirc programs ([`b49ba69`](https://github.com/aya-rs/aya/commit/b49ba69d09576a4dd34fbc703a938acd50cb6e7a))
## v0.10.1 (2021-06-16) ### Commit Statistics - 3 commits contributed to the release. - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - (cargo-release) version 0.10.1 ([`304abfb`](https://github.com/aya-rs/aya/commit/304abfbfeb05058be40a8832d5f7682f46a81fa0)) - Merge pull request #1 from aquarhead/fix-load-file ([`cdc7374`](https://github.com/aya-rs/aya/commit/cdc737490df927b131de5b5868eeb43c6ecaa58a)) - Fix Bpf::load_file when BTF doesn't exist ([`f1fc304`](https://github.com/aya-rs/aya/commit/f1fc30411d7d0b65b52a7e9312c8fd20340bd2d2))
## v0.10.0 (2021-06-15) ### Other - add more fields to Cargo.toml - bump version to 0.10 - add doc aliases for maps and programs - refactor tc code a bit and add docs - improve async perf map docs - tweak PerfEventArray docs - ProgramArray: more doc fixes - ProgramArray: tweak docs - implement ProgramFd for CgroupSkb - fix CgroupSkb docs - programs: add support for BPF_PROG_TYPE_CGROUP_SKB programs - programs: fix detaching programs attached with bpf_prog_attach - programs: fix syscall name in errors - handle reordered functions LLVM will split .text into .text.hot .text.unlikely etc and move the content around in order to improve locality. We need to parse all the text sections or relocations can potentially fail. - improve call relocation error messages - BpfError: set the #[source] attribute for RelocationErrors - add support for attaching and detaching TC programs This change adds support for attaching TC programs directly from aya, without having to use iproute2/tc. - add support for Stack and Queue maps - add id and pinning fields to bpf_map_def - netlink: improve error messages - add support for BPF_PROG_TYPE_SCHED_CLS programs - perf_map: fix bug when max_entries=0 When a perf map has max_entries=0, max_entries is dynamically set at load time to the number of possible cpus as reported by /sys/devices/system/cpu/possible. This change fixes a bug where instead of setting max_entries to the number of possible cpus, we were setting it to the cpu index of the last possible cpu. - update generated bindings Update generated bindings with kernel headers from libbpf 4ccc1f0 - xdp: fix detaching on kernels older than 5.7 XDP_FLAGS_REPLACE was added in 5.7. Now for kernels >= 5.7 whenever we detach an XDP program we pass along the program fd we expect to be detaching. For older kernels, we just detach whatever is attached, which is not great but it's the way the API worked pre XDP_FLAGS_REPLACE. - xdp: set flags when attaching with netlink - fix BpfError display strings - fix warnings - programs: rework load_program() retry code a bit - programs: add support for SkMsg programs - maps: add SockHash - add support for SockOps programs - add support BPF_PROG_TYPE_SK_SKB programs and SockMaps - fix program array key size - small doc fixes - more docs - consolidate errors into ProgramError::SyscallError - split aya::programs::probe into ::kprobe and ::uprobe & add docs - add maps::StackTraceMap Map type for BPF_MAP_TYPE_STACK_TRACE. - add util::kernel_symbols() kernel_symbols() can be used to load /proc/kallsyms in a BTreeMap. Useful for looking up symbols from stack addresses. - add bpf_map_lookup_elem_ptr - tweak docs - rename ProgramArray::unset to ProgramArray::clear_index - rename ProgramArray::keys to ProgramArray::indices - maps: add PerCpuArray - rework IterableMap and ProgramArray Make MapKeys not use IterableMap. Leave only ProgramArray::get, ProgramArray::set and ProgramArray::unset exposed as the other syscalls don't work consistently for program arrays. - PerCpuKernelMem doesn't need to be public - add aya::maps::Array - add aya::maps::array and move ProgramArray under it - hash_map: add doc aliases for HASH and LRU_HASH - per_cpu_hash_map: add support for BPF_MAP_TYPE_LRU_PERCPU_HASH - maps: introduce MapError::KeyNotFound Change get() from -> Result, MapError> to -> Result where MapError::KeyNotFound is returned instead of Ok(None) to signify that the key is not present. - rename MapError::NotFound to MapError::MapNotFound - add PerCpuHashMap - move hash_map.rs to hash_map/hash_map.rs - hash_map: factor out common hash code This is in preparation of adding new hash map types - fix warnings - don't export VerifierLog - HashMap: add support for LRU maps - more docs - tweak docs - rename perf map and add docs Rename the perf_map module to just perf, and rename PerfMap to PerfEventArray. - maps: add docs and make the hash_map and program_array modules public - add HashMap docs - make HashMap::new private - add ProgramArray docs - make ProgramArray::new private - remove pop() lookup_and_delete_elem is only supported for QUEUE and STACK maps at the moment. - add some docs for the crate and `Bpf` - maps: group syscall errors into MapError::SyscallError - fix bindings for PERF_EVENT_IOC_{ENABLE|DISABLE|SET_BPF} - remove TryInto magic from program()/program_mut() too For programs it's actually useful being able to get the underlying Program enum, for example when iterating/loading all the programs - remove TryInto cleverness from map() and map_mut() Require callers to call try_into() explicitly. It's more characters, but it's easier to understand/document. Also introduce MapError::NotFound instead of returning Result>. - fix some badly completed match arms - fix verifier log handling - add support for function calls - section: collecting relocations can't fail anymore - obj: rename symbol_table to symbols_by_index - add Program::name() and make ::prog_type() public - bpf: Add Bpf::programs() - bpf: remove lifetime param from previous signature - maps: add Map::name() and Map::map_type() - add Bpf::maps() to get all the maps - switch to rustified enums - generate code with xtask - xdp BPF_LINK_CREATE was added in 5.9 - obj: implement sane defaults for license and kernel version Default to license=GPL and kernel_version=any - implement missing bit of retprobes - sys: fix warning - rename gen-bindings to gen-bindings.sh - tweak error display - support max_entries=0 When a PerfMap has max_entries=0, set max_entries to the number of available CPUs. - add possible_cpus() - enable only the std feature for the futures crate - add explicit BTF argument to the load API Add a `target_btf: Option` argument to Bpf::load. None can be passed to indicate to skip BTF relocation, for example for kernels that don't support it. Some(btf) can be used to pass BTF parsed with Btf::from_sys_fs() or Btf::parse/parse_file. Finally, add a simpler Bpf::load_file(path) that uses from_sys_fs() internally to simplify the common case. - add support for attaching with custom xdp flags - rework ProgramError a bit Move type specific errors to XdpError SocketFilterError etc. Annotate all source errors with #[source] - add internal API to create links - fail new() for high level wrappers if the underlying map hasn't been created - remove unused methods - add AsyncPerfMap When the async_tokio or async_std features are enabled, AsyncPerfMap provides an async version of PerfMap which returns a future from read_events() - split in sub modules - implement AsRawFd ### Commit Statistics - 121 commits contributed to the release over the course of 110 calendar days. - 102 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Copy readme into aya/ ([`94b5e2e`](https://github.com/aya-rs/aya/commit/94b5e2e4e6a535ca74113c5f62c4bd1a7f265469)) - Add more fields to Cargo.toml ([`7694bac`](https://github.com/aya-rs/aya/commit/7694bacf04f5ba3cf58b4e432ff746ce2987d67d)) - Doc fixes ([`be0b7bb`](https://github.com/aya-rs/aya/commit/be0b7bbd832a0321018c78b0c008a4280bd1da6e)) - Bump version to 0.10 ([`9f7b017`](https://github.com/aya-rs/aya/commit/9f7b017d5d4a4eb63c98c258e3b382628e711253)) - Add doc aliases for maps and programs ([`768640d`](https://github.com/aya-rs/aya/commit/768640dd4696eaf8c76d7a8b15ab195b3979b281)) - More docs ([`293e66a`](https://github.com/aya-rs/aya/commit/293e66af65566942b424bbc0c9e5a2cd1be69599)) - Refactor tc code a bit and add docs ([`ad58e17`](https://github.com/aya-rs/aya/commit/ad58e171ff1645d02998b399db6535a226b5a5ec)) - More docs ([`11e21e8`](https://github.com/aya-rs/aya/commit/11e21e83bedc8de2b7290d5546b36f47a981266a)) - More doc fixes ([`6c7df27`](https://github.com/aya-rs/aya/commit/6c7df27bd03e658ebe7855d865377c2cc6e57e52)) - Improve async perf map docs ([`28158e6`](https://github.com/aya-rs/aya/commit/28158e6028b12efad61d159c3b505d58a03bfd8a)) - Tweak PerfEventArray docs ([`6ecf7da`](https://github.com/aya-rs/aya/commit/6ecf7dabf35f22869a95a8cd176b6f217ee57b05)) - ProgramArray: more doc fixes ([`6772595`](https://github.com/aya-rs/aya/commit/6772595f3ea2d0178e02efc97a2fe2a789a04b24)) - ProgramArray: tweak docs ([`4bde0c5`](https://github.com/aya-rs/aya/commit/4bde0c54bdcf6f749f6b6034874cf1bfecb0b08f)) - Implement ProgramFd for CgroupSkb ([`2cda5db`](https://github.com/aya-rs/aya/commit/2cda5dbbe74e5d41ce7e2c895678b853d2867001)) - Fix CgroupSkb docs ([`2d7b9b2`](https://github.com/aya-rs/aya/commit/2d7b9b2e904e2435527d6640e631195ffbd7e050)) - Programs: add support for BPF_PROG_TYPE_CGROUP_SKB programs ([`08a68fa`](https://github.com/aya-rs/aya/commit/08a68faf8a8baa344dd6fee64529ad2dcc0a0846)) - Programs: fix detaching programs attached with bpf_prog_attach ([`fb3e2f7`](https://github.com/aya-rs/aya/commit/fb3e2f7f9d06b22c7fe44eccefc2ce94ad322ed0)) - Programs: fix syscall name in errors ([`6658025`](https://github.com/aya-rs/aya/commit/665802594c2181ae890b3655867944a3fef11508)) - Handle reordered functions ([`81a0b61`](https://github.com/aya-rs/aya/commit/81a0b61164079ca276d23e0f31f2853087650198)) - Improve call relocation error messages ([`b92b1e1`](https://github.com/aya-rs/aya/commit/b92b1e18a97135684907d238578146f7aabacc84)) - BpfError: set the #[source] attribute for RelocationErrors ([`20b2d4c`](https://github.com/aya-rs/aya/commit/20b2d4c77dbc1597f68e6ae16a55d129e42b5c5d)) - Add support for attaching and detaching TC programs ([`6974d34`](https://github.com/aya-rs/aya/commit/6974d349e8f86c98f450122788efeedecbf61970)) - Add support for Stack and Queue maps ([`31f8d71`](https://github.com/aya-rs/aya/commit/31f8d71604f4eddd7981cd72e1deb01d2076f7f4)) - Add id and pinning fields to bpf_map_def ([`40b7da6`](https://github.com/aya-rs/aya/commit/40b7da6655aca6aed5dfe72a0db80bf0e67c2ce1)) - Netlink: improve error messages ([`dc4e020`](https://github.com/aya-rs/aya/commit/dc4e020f29acc544bb49a74bbad4c553f370d0f3)) - Add support for BPF_PROG_TYPE_SCHED_CLS programs ([`5effc97`](https://github.com/aya-rs/aya/commit/5effc972ac04960d0346e1f5504b595a206fc019)) - Perf_map: fix bug when max_entries=0 ([`4222b14`](https://github.com/aya-rs/aya/commit/4222b140ec594e99e77cda8539b16f21820ae155)) - Update generated bindings ([`3b7ffd0`](https://github.com/aya-rs/aya/commit/3b7ffd0048a5250ca160f8a7b584bf0ea73eb249)) - Xdp: fix detaching on kernels older than 5.7 ([`30d2b25`](https://github.com/aya-rs/aya/commit/30d2b25f1173904b2542a25792d7dff5b97b837f)) - Xdp: set flags when attaching with netlink ([`607cf68`](https://github.com/aya-rs/aya/commit/607cf68a692be60eb9f44d26352c513ea6295456)) - Fix BpfError display strings ([`bb7728a`](https://github.com/aya-rs/aya/commit/bb7728a2c5905923534e51bd53d8d5720f7319e1)) - Fix warnings ([`9e12c93`](https://github.com/aya-rs/aya/commit/9e12c9324c623bf751053ff874bc47055343753e)) - Programs: rework load_program() retry code a bit ([`9a24f20`](https://github.com/aya-rs/aya/commit/9a24f20e6f85d9ff2e438dddb3530b88e9583851)) - Programs: add support for SkMsg programs ([`1441754`](https://github.com/aya-rs/aya/commit/144175434f5b07ac7379be6e30a4b613c225f21b)) - Maps: add SockHash ([`dad300c`](https://github.com/aya-rs/aya/commit/dad300c88bdfade10f8318fcbbe9a8b5e4de89ce)) - Add support for SockOps programs ([`ca4b3bf`](https://github.com/aya-rs/aya/commit/ca4b3bfc0462b445f2aae56f9d4c2a80581eda39)) - Add support BPF_PROG_TYPE_SK_SKB programs and SockMaps ([`b57cace`](https://github.com/aya-rs/aya/commit/b57cace941333910891462127ce0199ae01c3c7c)) - Fix program array key size ([`b6cd813`](https://github.com/aya-rs/aya/commit/b6cd813af5fbd290a1f2e80b08c170ab868dfb3f)) - Small doc fixes ([`0b3e532`](https://github.com/aya-rs/aya/commit/0b3e532d7a4a8fe575b41f64d94cfedab10c36ea)) - More docs ([`79f1b38`](https://github.com/aya-rs/aya/commit/79f1b385a5366cc6f9a6a84172ab131850028a87)) - Consolidate errors into ProgramError::SyscallError ([`683a58e`](https://github.com/aya-rs/aya/commit/683a58ea6dfaeb65d00ea3cf63b215cd82fd3d5a)) - Split aya::programs::probe into ::kprobe and ::uprobe & add docs ([`ae863bc`](https://github.com/aya-rs/aya/commit/ae863bc663bde69eb71d5c1ec265b0d4c205f7ff)) - Add maps::StackTraceMap ([`d9634ae`](https://github.com/aya-rs/aya/commit/d9634ae945ba09bedfc10c748e4e35a6ca3bfde8)) - Add util::kernel_symbols() ([`67c9cc0`](https://github.com/aya-rs/aya/commit/67c9cc03597e6f00bb6917a493cd9bb405a05b4d)) - Add bpf_map_lookup_elem_ptr ([`2cdb10e`](https://github.com/aya-rs/aya/commit/2cdb10e7f2734d85bd8ab115a081f4d1a8b2e2ed)) - Tweak docs ([`ad6d059`](https://github.com/aya-rs/aya/commit/ad6d0596ab076aac84c12425ab493e6b6f24477d)) - Rename ProgramArray::unset to ProgramArray::clear_index ([`f464279`](https://github.com/aya-rs/aya/commit/f4642797408d31c8375562ead9f4480e8579f59d)) - Rename ProgramArray::keys to ProgramArray::indices ([`9ad2a5e`](https://github.com/aya-rs/aya/commit/9ad2a5e72d269724953f0f311e803a88637af2ee)) - Maps: add PerCpuArray ([`b0364f7`](https://github.com/aya-rs/aya/commit/b0364f76aba500464470628f719b5ca7aab4b36a)) - Rework IterableMap and ProgramArray ([`74d5f17`](https://github.com/aya-rs/aya/commit/74d5f17559036c2eb42d4679ff98fe2ab7e76d4f)) - PerCpuKernelMem doesn't need to be public ([`aa3a30d`](https://github.com/aya-rs/aya/commit/aa3a30d1965d96f3b6fc345be763561e6270f2ae)) - Add aya::maps::Array ([`1746bbf`](https://github.com/aya-rs/aya/commit/1746bbf5b83a5b392f39eefe02fc3731db77d893)) - Add aya::maps::array and move ProgramArray under it ([`c3b9021`](https://github.com/aya-rs/aya/commit/c3b902137becc48b72091ba59a9a4e297ba56d33)) - Hash_map: add doc aliases for HASH and LRU_HASH ([`6cec8be`](https://github.com/aya-rs/aya/commit/6cec8be564a590717e9af7eb007f3f5d10ffff0e)) - Per_cpu_hash_map: add support for BPF_MAP_TYPE_LRU_PERCPU_HASH ([`7a989b4`](https://github.com/aya-rs/aya/commit/7a989b43b9ee5b4807f53001c2d7d8824a162a34)) - Maps: introduce MapError::KeyNotFound ([`635dcd4`](https://github.com/aya-rs/aya/commit/635dcd44b9135dd75d958909f76da28676e6efe7)) - Rename MapError::NotFound to MapError::MapNotFound ([`fd142e4`](https://github.com/aya-rs/aya/commit/fd142e467c32b6aa4b0d2e8d62816a63c1fa4220)) - Add PerCpuHashMap ([`3a5b289`](https://github.com/aya-rs/aya/commit/3a5b28916385b35824bc2a05606808e5e8c1968a)) - Move hash_map.rs to hash_map/hash_map.rs ([`d5098c9`](https://github.com/aya-rs/aya/commit/d5098c9e575b7d5447506648ebeae146192eeda1)) - Hash_map: factor out common hash code ([`6a12a48`](https://github.com/aya-rs/aya/commit/6a12a48f0360456452c34737c5d52cd289ce23e8)) - Fix warnings ([`ac83273`](https://github.com/aya-rs/aya/commit/ac83273da86c5d10100f58563a9d764a79183367)) - Don't export VerifierLog ([`46e0a2e`](https://github.com/aya-rs/aya/commit/46e0a2ede4e423e620acd55f3ed4f755aa8c8b38)) - HashMap: add support for LRU maps ([`7c6ae76`](https://github.com/aya-rs/aya/commit/7c6ae769756a9605f7823e36243b69a3a88a1370)) - More docs ([`04fde46`](https://github.com/aya-rs/aya/commit/04fde468556ace883009b4abe63840aa7b89f29f)) - Tweak docs ([`eea27f5`](https://github.com/aya-rs/aya/commit/eea27f52f3d6455d97838e833b36f6c846c99cb7)) - Rename perf map and add docs ([`5aa9cb1`](https://github.com/aya-rs/aya/commit/5aa9cb12ade0014d64582864e66bbeae593f6edc)) - Maps: add docs and make the hash_map and program_array modules public ([`d94bfde`](https://github.com/aya-rs/aya/commit/d94bfde29521fa6fc1d661d1976d4800dd10b1d8)) - Add HashMap docs ([`ce3f83a`](https://github.com/aya-rs/aya/commit/ce3f83acb11388f2c0b07a8b1de95c7df22b97f9)) - Make HashMap::new private ([`e28da88`](https://github.com/aya-rs/aya/commit/e28da8812ed5d9e76e764fbc28312e8588aff2af)) - Add ProgramArray docs ([`24f7c37`](https://github.com/aya-rs/aya/commit/24f7c37158ede0217354d8c6305904737980f292)) - Make ProgramArray::new private ([`3fddc81`](https://github.com/aya-rs/aya/commit/3fddc8165c6e2c28c4fda19e72156b640da4a0c7)) - Remove pop() ([`6682a5f`](https://github.com/aya-rs/aya/commit/6682a5ff395d8fbea20393ff81140adcfc2a0c09)) - Add some docs for the crate and `Bpf` ([`1bbbf61`](https://github.com/aya-rs/aya/commit/1bbbf616b6bedef4a6d42cd630ecd7e3b9366dc5)) - Maps: group syscall errors into MapError::SyscallError ([`563ce46`](https://github.com/aya-rs/aya/commit/563ce46118258805892bdff97f0e57b2838e0de8)) - Fix bindings for PERF_EVENT_IOC_{ENABLE|DISABLE|SET_BPF} ([`f9554d6`](https://github.com/aya-rs/aya/commit/f9554d6db5c1fe3c906c798bc9b2a9c28fb0db7b)) - Remove TryInto magic from program()/program_mut() too ([`a92bfeb`](https://github.com/aya-rs/aya/commit/a92bfebf50df2c56bca242c6a9c3dedd04135675)) - Remove TryInto cleverness from map() and map_mut() ([`42e0a65`](https://github.com/aya-rs/aya/commit/42e0a659b2f82cca537d70f906e0a475f0ab6b03)) - Fix some badly completed match arms ([`d3482c0`](https://github.com/aya-rs/aya/commit/d3482c063ca888a6465f3e8866d5d3de93cbbd99)) - Fix verifier log handling ([`ee05f9d`](https://github.com/aya-rs/aya/commit/ee05f9d9497ea83ee9cfffbaa0d1b87d9d57c26e)) - Add support for function calls ([`92b4ed2`](https://github.com/aya-rs/aya/commit/92b4ed2664264b4af36b29c7b08d13505eae9b08)) - Section: collecting relocations can't fail anymore ([`8b0eee3`](https://github.com/aya-rs/aya/commit/8b0eee317d71f0139ec030b1f0583edf8670c296)) - Obj: rename symbol_table to symbols_by_index ([`318c16c`](https://github.com/aya-rs/aya/commit/318c16cea32731613339d22fad11a29be8d79976)) - Add Program::name() and make ::prog_type() public ([`286e117`](https://github.com/aya-rs/aya/commit/286e117fe0bab8542f2e1d5fd309562689d88c00)) - Bpf: Add Bpf::programs() ([`0199e4b`](https://github.com/aya-rs/aya/commit/0199e4b29704df4cebf65d3d5f09ab1af6982cbd)) - Bpf: remove lifetime param from previous signature ([`dcb5121`](https://github.com/aya-rs/aya/commit/dcb5121985113e1b90a5e50a43d71b4f00826ebe)) - Maps: add Map::name() and Map::map_type() ([`ed53f74`](https://github.com/aya-rs/aya/commit/ed53f7470b386f3a870e34399bbb52c6ea72d07d)) - Add Bpf::maps() to get all the maps ([`0a493ba`](https://github.com/aya-rs/aya/commit/0a493baed6b4d020ed7d5d87191d912662eb2159)) - Switch to rustified enums ([`29f2d9b`](https://github.com/aya-rs/aya/commit/29f2d9b2d9e4265d0d0d2f13c314ef27d5c4ebcf)) - Generate code with xtask ([`59ed237`](https://github.com/aya-rs/aya/commit/59ed237343c16ba0b96f917991a7ec2f971ecd5d)) - Xdp BPF_LINK_CREATE was added in 5.9 ([`8327ffb`](https://github.com/aya-rs/aya/commit/8327ffbb8d77e39046f851ca0f38ed153e140715)) - Obj: implement sane defaults for license and kernel version ([`1e779c5`](https://github.com/aya-rs/aya/commit/1e779c520a90daa642a67cf3b986536aa50ad5ef)) - Implement missing bit of retprobes ([`f11df77`](https://github.com/aya-rs/aya/commit/f11df77f859feee2a88a69b96da4a1a22839c45a)) - Sys: fix warning ([`b7369d2`](https://github.com/aya-rs/aya/commit/b7369d2763fe8c7061071986c41d1bcb0682f5a7)) - Rename gen-bindings to gen-bindings.sh ([`82bcef3`](https://github.com/aya-rs/aya/commit/82bcef37906472e7a32fa602cf26a97387590057)) - Tweak error display ([`245cd46`](https://github.com/aya-rs/aya/commit/245cd46baba5e1b532c7bf8b3eb732ab398bb529)) - Fix build with musl ([`3e8a279`](https://github.com/aya-rs/aya/commit/3e8a279a5910badd4720c06f2a046e47e6a6f657)) - Support max_entries=0 ([`68a633f`](https://github.com/aya-rs/aya/commit/68a633fe51299ab6feaea370fd7b86740d284731)) - Add possible_cpus() ([`f56c32b`](https://github.com/aya-rs/aya/commit/f56c32b46bdd4c634b1ce6136ecab3c88d202040)) - Format fixes ([`a3ab2ef`](https://github.com/aya-rs/aya/commit/a3ab2eff57c55faf323d9663e9198851abdc3e2f)) - Enable only the std feature for the futures crate ([`0cf5d17`](https://github.com/aya-rs/aya/commit/0cf5d17e383d240a27d463c89bec5d3a19854e4f)) - Fix RawFd import paths ([`3abe9bb`](https://github.com/aya-rs/aya/commit/3abe9bb859320063484c5d222f8322bf8392fec2)) - Add explicit BTF argument to the load API ([`2cec04c`](https://github.com/aya-rs/aya/commit/2cec04c5781bc7b03c601dbb0cb1c23f3df22385)) - Add support for attaching with custom xdp flags ([`55d8bcf`](https://github.com/aya-rs/aya/commit/55d8bcf3860d2cf5db7f59b8b5caaa32de6e668d)) - Rework ProgramError a bit ([`d326038`](https://github.com/aya-rs/aya/commit/d326038cf4b0a6fe7966de038054966bf3016380)) - Add internal API to create links ([`f88ca1f`](https://github.com/aya-rs/aya/commit/f88ca1f1f1449db1a4a323a67edf0ac5a4878ee1)) - Fail new() for high level wrappers if the underlying map hasn't been created ([`ba992a2`](https://github.com/aya-rs/aya/commit/ba992a2414430387737e76db2e10b04c98f56847)) - Trim deps a bit more ([`873691d`](https://github.com/aya-rs/aya/commit/873691d050e5156c9f4fb271d6b13a25d0952564)) - The futures crate is only needed when async is enabled ([`f1da541`](https://github.com/aya-rs/aya/commit/f1da5412342e6839e80555912f08a13a9e3a976c)) - Remove unused methods ([`14c9845`](https://github.com/aya-rs/aya/commit/14c98455a940d6cead424b7e30a62845c256ae26)) - Fix warnings ([`a5e19fc`](https://github.com/aya-rs/aya/commit/a5e19fc4ac3cd53ca8f1d1d43fbc0dfa08aa8d55)) - Add AsyncPerfMap ([`fdc4dad`](https://github.com/aya-rs/aya/commit/fdc4dad5ff88a419d982f67568f5271c69e73f0a)) - Split in sub modules ([`4be0c45`](https://github.com/aya-rs/aya/commit/4be0c45305f0b0c639bfb6b645848fd4e1e0774f)) - Implement AsRawFd ([`95a24c6`](https://github.com/aya-rs/aya/commit/95a24c6f8b2483f05afabb0b3afaacfea4ebe061)) - Add IOError variants to PerfMapError and PerfBufferError ([`5d6fe8b`](https://github.com/aya-rs/aya/commit/5d6fe8bdf4f31bbb694b83c83127cf9f598f3716)) - Make aya::maps::perf_map public ([`b9be2f1`](https://github.com/aya-rs/aya/commit/b9be2f1a9b61631df0189b40881b778e9e278e43)) - Change the suffix of errors from *Failed to *Error ([`160e0be`](https://github.com/aya-rs/aya/commit/160e0be6d6aec27a31c6108810fc4853f25f6a53)) - Bpf, perf_map: make maps usable from multiple threads ([`d4e2825`](https://github.com/aya-rs/aya/commit/d4e282535b9a780006786758a4b91856b401ac77)) - Make online_cpus() util public ([`d7c91ef`](https://github.com/aya-rs/aya/commit/d7c91efb2deb77f1a76489375888d01b27f0e710)) - Generate arch specific bindings ([`2215e20`](https://github.com/aya-rs/aya/commit/2215e202f431fc25449541fcbbdb65ff096f3132)) - Add src/generated/netlink_bindings.rs to repo ([`1de3929`](https://github.com/aya-rs/aya/commit/1de392964b8f447615579d231a36e3bb9260b027)) - Turn the project into a workspace, move code under aya/ ([`af8f769`](https://github.com/aya-rs/aya/commit/af8f769b509e4a002c3bd3138fe745ae962de2db))
aya-0.13.1/Cargo.toml0000644000000041330000000000100076670ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.80.0" name = "aya" version = "0.13.1" authors = ["Aya Contributors"] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "An eBPF library with a focus on developer experience and operability." homepage = "https://aya-rs.dev" documentation = "https://docs.rs/aya" readme = "README.md" keywords = [ "bpf", "ebpf", "kernel", "linux", ] license = "MIT OR Apache-2.0" repository = "https://github.com/aya-rs/aya" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", "-D", "warnings", ] [lib] name = "aya" path = "src/lib.rs" [dependencies.assert_matches] version = "1.5.0" default-features = false [dependencies.async-io] version = "2.0" optional = true default-features = false [dependencies.aya-obj] version = "^0.2.1" features = ["std"] [dependencies.bitflags] version = "2.2.1" default-features = false [dependencies.bytes] version = "1" default-features = false [dependencies.libc] version = "0.2.105" default-features = false [dependencies.log] version = "0.4" default-features = false [dependencies.object] version = "0.36" features = [ "elf", "read_core", "std", "write", ] default-features = false [dependencies.once_cell] version = "1.20.1" default-features = false [dependencies.thiserror] version = "1" default-features = false [dependencies.tokio] version = "1.24.0" features = ["rt"] optional = true default-features = false [dev-dependencies.tempfile] version = "3" default-features = false [features] async_std = ["dep:async-io"] async_tokio = ["tokio/net"] default = [] aya-0.13.1/Cargo.toml.orig000064400000000000000000000021441046102023000133500ustar 00000000000000[package] name = "aya" version = "0.13.1" description = "An eBPF library with a focus on developer experience and operability." keywords = ["bpf", "ebpf", "kernel", "linux"] readme = "README.md" documentation = "https://docs.rs/aya" rust-version = "1.80.0" authors.workspace = true license.workspace = true repository.workspace = true homepage.workspace = true edition.workspace = true [dependencies] assert_matches = { workspace = true } async-io = { workspace = true, optional = true } aya-obj = { path = "../aya-obj", version = "^0.2.1", features = ["std"] } bitflags = { workspace = true } bytes = { workspace = true } libc = { workspace = true } log = { workspace = true } object = { workspace = true, features = ["elf", "read_core", "std", "write"] } once_cell = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["rt"], optional = true } [dev-dependencies] tempfile = { workspace = true } [features] default = [] async_tokio = ["tokio/net"] async_std = ["dep:async-io"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs", "-D", "warnings"] aya-0.13.1/LICENSE-APACHE000064400000000000000000000251441046102023000124120ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2021 Alessandro Decina Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. aya-0.13.1/LICENSE-MIT000064400000000000000000000020461046102023000121160ustar 00000000000000Copyright (c) 2021 Alessandro Decina Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. aya-0.13.1/README.md000064400000000000000000000106541046102023000117450ustar 00000000000000# [![Aya](../assets/logo.svg)](https://aya-rs.dev) [![Crates.io][crates-badge]][crates-url] ![License][license-badge] ![Build status][build-badge] [![Book][book-badge]][book-url] [crates-badge]: https://img.shields.io/crates/v/aya.svg?style=for-the-badge&logo=rust [crates-url]: https://crates.io/crates/aya [license-badge]: https://img.shields.io/badge/license-MIT%2FApache--2.0-blue?style=for-the-badge [build-badge]: https://img.shields.io/github/actions/workflow/status/aya-rs/aya/build-aya.yml?branch=main&style=for-the-badge [book-badge]: https://img.shields.io/badge/read%20the-book-9cf.svg?style=for-the-badge&logo=mdbook [book-url]: https://aya-rs.dev/book ## API Documentation [![Unreleased Documentation][git-docs-badge]][git-api-docs] [![Documentaiton][api-docs-badge]][api-docs] [git-docs-badge]: https://img.shields.io/badge/docs-unreleased-red.svg?style=for-the-badge&logo=docsdotrs [git-api-docs]: https://docs.aya-rs.dev [api-docs-badge]: https://img.shields.io/badge/docs-released-blue.svg?style=for-the-badge&logo=docsdotrs [api-docs]: https://docs.rs/aya ## Community [![Discord][discord-badge]][chat-url] [![Awesome][awesome-badge]][awesome-aya] Join [the conversation on Discord][chat-url] to discuss anything related to Aya or discover and contribute to a list of [Awesome Aya][awesome-aya] projects. [discord-badge]: https://img.shields.io/badge/Discord-chat-5865F2?style=for-the-badge&logo=discord [chat-url]: https://discord.gg/xHW2cb2N6G [awesome-aya]: https://github.com/aya-rs/awesome-aya [awesome-badge]: https://img.shields.io/badge/Awesome-Aya-FC60A8?style=for-the-badge&logo=awesomelists ## Overview eBPF is a technology that allows running user-supplied programs inside the Linux kernel. For more info see [What is eBBF](https://ebpf.io/what-is-ebpf). Aya is an eBPF library built with a focus on operability and developer experience. It does not rely on [libbpf] nor [bcc] - it's built from the ground up purely in Rust, using only the [libc] crate to execute syscalls. With BTF support and when linked with musl, it offers a true [compile once, run everywhere solution][co-re], where a single self-contained binary can be deployed on many linux distributions and kernel versions. Some of the major features provided include: * Support for the **BPF Type Format** (BTF), which is transparently enabled when supported by the target kernel. This allows eBPF programs compiled against one kernel version to run on different kernel versions without the need to recompile. * Support for function call relocation and global data maps, which allows eBPF programs to make **function calls** and use **global variables and initializers**. * **Async support** with both [tokio] and [async-std]. * Easy to deploy and fast to build: aya doesn't require a kernel build or compiled headers, and not even a C toolchain; a release build completes in a matter of seconds. [libbpf]: https://github.com/libbpf/libbpf [bcc]: https://github.com/iovisor/bcc [libc]: https://docs.rs/libc [co-re]: https://facebookmicrosites.github.io/bpf/blog/2020/02/19/bpf-portability-and-co-re.html [tokio]: https://docs.rs/tokio [async-std]: https://docs.rs/async-std ### Example Aya supports a large chunk of the eBPF API. The following example shows how to use a `BPF_PROG_TYPE_CGROUP_SKB` program with aya: ```rust use std::fs::File; use aya::Ebpf; use aya::programs::{CgroupSkb, CgroupSkbAttachType}; // load the BPF code let mut ebpf = Ebpf::load_file("bpf.o")?; // get the `ingress_filter` program compiled into `bpf.o`. let ingress: &mut CgroupSkb = ebpf.program_mut("ingress_filter")?.try_into()?; // load the program into the kernel ingress.load()?; // attach the program to the root cgroup. `ingress_filter` will be called for all // incoming packets. let cgroup = File::open("/sys/fs/cgroup/unified")?; ingress.attach(cgroup, CgroupSkbAttachType::Ingress)?; ``` ## Contributing Please see the [contributing guide](https://github.com/aya-rs/aya/blob/main/CONTRIBUTING.md). ## License Aya is distributed under the terms of either the [MIT license] or the [Apache License] (version 2.0), at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [MIT license]: https://github.com/aya-rs/aya/blob/main/LICENSE-MIT [Apache license]: https://github.com/aya-rs/aya/blob/main/LICENSE-APACHE aya-0.13.1/src/bpf.rs000064400000000000000000001242711046102023000123730ustar 00000000000000use std::{ borrow::Cow, collections::{HashMap, HashSet}, fs, io, os::{ fd::{AsFd as _, AsRawFd as _}, raw::c_int, }, path::{Path, PathBuf}, sync::{Arc, LazyLock}, }; use aya_obj::{ btf::{BtfFeatures, BtfRelocationError}, generated::{BPF_F_SLEEPABLE, BPF_F_XDP_HAS_FRAGS}, relocation::EbpfRelocationError, EbpfSectionKind, Features, }; use log::{debug, warn}; use thiserror::Error; use crate::{ generated::{ bpf_map_type::{self, *}, AYA_PERF_EVENT_IOC_DISABLE, AYA_PERF_EVENT_IOC_ENABLE, AYA_PERF_EVENT_IOC_SET_BPF, }, maps::{Map, MapData, MapError}, obj::{ btf::{Btf, BtfError}, Object, ParseError, ProgramSection, }, programs::{ BtfTracePoint, CgroupDevice, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr, CgroupSockopt, CgroupSysctl, Extension, FEntry, FExit, KProbe, LircMode2, Lsm, PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, }, sys::{ bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, is_btf_datasec_supported, is_btf_decl_tag_supported, is_btf_enum64_supported, is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported, is_btf_supported, is_btf_type_tag_supported, is_info_gpl_compatible_supported, is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs, }, util::{bytes_of, bytes_of_slice, nr_cpus, page_size}, }; pub(crate) const BPF_OBJ_NAME_LEN: usize = 16; pub(crate) const PERF_EVENT_IOC_ENABLE: c_int = AYA_PERF_EVENT_IOC_ENABLE; pub(crate) const PERF_EVENT_IOC_DISABLE: c_int = AYA_PERF_EVENT_IOC_DISABLE; pub(crate) const PERF_EVENT_IOC_SET_BPF: c_int = AYA_PERF_EVENT_IOC_SET_BPF; /// Marker trait for types that can safely be converted to and from byte slices. pub unsafe trait Pod: Copy + 'static {} macro_rules! unsafe_impl_pod { ($($struct_name:ident),+ $(,)?) => { $( unsafe impl Pod for $struct_name { } )+ } } unsafe_impl_pod!(i8, u8, i16, u16, i32, u32, i64, u64, u128, i128); // It only makes sense that an array of POD types is itself POD unsafe impl Pod for [T; N] {} pub use aya_obj::maps::{bpf_map_def, PinningType}; pub(crate) static FEATURES: LazyLock = LazyLock::new(detect_features); fn detect_features() -> Features { let btf = if is_btf_supported() { Some(BtfFeatures::new( is_btf_func_supported(), is_btf_func_global_supported(), is_btf_datasec_supported(), is_btf_float_supported(), is_btf_decl_tag_supported(), is_btf_type_tag_supported(), is_btf_enum64_supported(), )) } else { None }; let f = Features::new( is_prog_name_supported(), is_probe_read_kernel_supported(), is_perf_link_supported(), is_bpf_global_data_supported(), is_bpf_cookie_supported(), is_prog_id_supported(BPF_MAP_TYPE_CPUMAP), is_prog_id_supported(BPF_MAP_TYPE_DEVMAP), is_info_map_ids_supported(), is_info_gpl_compatible_supported(), btf, ); debug!("BPF Feature Detection: {:#?}", f); f } /// Returns a reference to the detected BPF features. pub fn features() -> &'static Features { &FEATURES } /// Builder style API for advanced loading of eBPF programs. /// /// Loading eBPF code involves a few steps, including loading maps and applying /// relocations. You can use `EbpfLoader` to customize some of the loading /// options. /// /// # Examples /// /// ```no_run /// use aya::{EbpfLoader, Btf}; /// use std::fs; /// /// let bpf = EbpfLoader::new() /// // load the BTF data from /sys/kernel/btf/vmlinux /// .btf(Btf::from_sys_fs().ok().as_ref()) /// // load pinned maps from /sys/fs/bpf/my-program /// .map_pin_path("/sys/fs/bpf/my-program") /// // finally load the code /// .load_file("file.o")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` #[derive(Debug)] pub struct EbpfLoader<'a> { btf: Option>, map_pin_path: Option, globals: HashMap<&'a str, (&'a [u8], bool)>, max_entries: HashMap<&'a str, u32>, extensions: HashSet<&'a str>, verifier_log_level: VerifierLogLevel, allow_unsupported_maps: bool, } /// Builder style API for advanced loading of eBPF programs. #[deprecated(since = "0.13.0", note = "use `EbpfLoader` instead")] pub type BpfLoader<'a> = EbpfLoader<'a>; bitflags::bitflags! { /// Used to set the verifier log level flags in [EbpfLoader](EbpfLoader::verifier_log_level()). #[derive(Clone, Copy, Debug)] pub struct VerifierLogLevel: u32 { /// Sets no verifier logging. const DISABLE = 0; /// Enables debug verifier logging. const DEBUG = 1; /// Enables verbose verifier logging. const VERBOSE = 2 | Self::DEBUG.bits(); /// Enables verifier stats. const STATS = 4; } } impl Default for VerifierLogLevel { fn default() -> Self { Self::DEBUG | Self::STATS } } impl<'a> EbpfLoader<'a> { /// Creates a new loader instance. pub fn new() -> Self { Self { btf: Btf::from_sys_fs().ok().map(Cow::Owned), map_pin_path: None, globals: HashMap::new(), max_entries: HashMap::new(), extensions: HashSet::new(), verifier_log_level: VerifierLogLevel::default(), allow_unsupported_maps: false, } } /// Sets the target [BTF](Btf) info. /// /// The loader defaults to loading `BTF` info using [Btf::from_sys_fs]. /// Use this method if you want to load `BTF` from a custom location or /// pass `None` to disable `BTF` relocations entirely. /// # Example /// /// ```no_run /// use aya::{EbpfLoader, Btf, Endianness}; /// /// let bpf = EbpfLoader::new() /// // load the BTF data from a custom location /// .btf(Btf::parse_file("/custom_btf_file", Endianness::default()).ok().as_ref()) /// .load_file("file.o")?; /// /// # Ok::<(), aya::EbpfError>(()) /// ``` pub fn btf(&mut self, btf: Option<&'a Btf>) -> &mut Self { self.btf = btf.map(Cow::Borrowed); self } /// Allows programs containing unsupported maps to be loaded. /// /// By default programs containing unsupported maps will fail to load. This /// method can be used to configure the loader so that unsupported maps will /// be loaded, but won't be accessible from userspace. Can be useful when /// using unsupported maps that are only accessed from eBPF code and don't /// require any userspace interaction. /// /// # Example /// /// ```no_run /// use aya::EbpfLoader; /// /// let bpf = EbpfLoader::new() /// .allow_unsupported_maps() /// .load_file("file.o")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` /// pub fn allow_unsupported_maps(&mut self) -> &mut Self { self.allow_unsupported_maps = true; self } /// Sets the base directory path for pinned maps. /// /// Pinned maps will be loaded from `path/MAP_NAME`. /// The caller is responsible for ensuring the directory exists. /// /// # Example /// /// ```no_run /// use aya::EbpfLoader; /// /// let bpf = EbpfLoader::new() /// .map_pin_path("/sys/fs/bpf/my-program") /// .load_file("file.o")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` /// pub fn map_pin_path>(&mut self, path: P) -> &mut Self { self.map_pin_path = Some(path.as_ref().to_owned()); self } /// Sets the value of a global variable. /// /// If the `must_exist` argument is `true`, [`EbpfLoader::load`] will fail with [`ParseError::SymbolNotFound`] if the loaded object code does not contain the variable. /// /// From Rust eBPF, a global variable can be defined as follows: /// /// ```no_run /// #[no_mangle] /// static VERSION: i32 = 0; /// ``` /// /// Then it can be accessed using `core::ptr::read_volatile`: /// /// ```no_run /// # #[no_mangle] /// # static VERSION: i32 = 0; /// # unsafe fn try_test() { /// let version = core::ptr::read_volatile(&VERSION); /// # } /// ``` /// /// The type of a global variable must be `Pod` (plain old data), for instance `u8`, `u32` and /// all other primitive types. You may use custom types as well, but you must ensure that those /// types are `#[repr(C)]` and only contain other `Pod` types. /// /// From C eBPF, you would annotate a global variable as `volatile const`. /// /// # Example /// /// ```no_run /// use aya::EbpfLoader; /// /// let bpf = EbpfLoader::new() /// .set_global("VERSION", &2, true) /// .set_global("PIDS", &[1234u16, 5678], true) /// .load_file("file.o")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` /// pub fn set_global>>( &mut self, name: &'a str, value: T, must_exist: bool, ) -> &mut Self { self.globals.insert(name, (value.into().bytes, must_exist)); self } /// Set the max_entries for specified map. /// /// Overwrite the value of max_entries of the map that matches /// the provided name before the map is created. /// /// # Example /// /// ```no_run /// use aya::EbpfLoader; /// /// let bpf = EbpfLoader::new() /// .set_max_entries("map", 64) /// .load_file("file.o")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` /// pub fn set_max_entries(&mut self, name: &'a str, size: u32) -> &mut Self { self.max_entries.insert(name, size); self } /// Treat the provided program as an [`Extension`] /// /// When attempting to load the program with the provided `name` /// the program type is forced to be ] [`Extension`] and is not /// inferred from the ELF section name. /// /// # Example /// /// ```no_run /// use aya::EbpfLoader; /// /// let bpf = EbpfLoader::new() /// .extension("myfunc") /// .load_file("file.o")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` /// pub fn extension(&mut self, name: &'a str) -> &mut Self { self.extensions.insert(name); self } /// Sets BPF verifier log level. /// /// # Example /// /// ```no_run /// use aya::{EbpfLoader, VerifierLogLevel}; /// /// let bpf = EbpfLoader::new() /// .verifier_log_level(VerifierLogLevel::VERBOSE | VerifierLogLevel::STATS) /// .load_file("file.o")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` /// pub fn verifier_log_level(&mut self, level: VerifierLogLevel) -> &mut Self { self.verifier_log_level = level; self } /// Loads eBPF bytecode from a file. /// /// # Examples /// /// ```no_run /// use aya::EbpfLoader; /// /// let bpf = EbpfLoader::new().load_file("file.o")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` pub fn load_file>(&mut self, path: P) -> Result { let path = path.as_ref(); self.load(&fs::read(path).map_err(|error| EbpfError::FileError { path: path.to_owned(), error, })?) } /// Loads eBPF bytecode from a buffer. /// /// # Examples /// /// ```no_run /// use aya::EbpfLoader; /// use std::fs; /// /// let data = fs::read("file.o").unwrap(); /// let bpf = EbpfLoader::new().load(&data)?; /// # Ok::<(), aya::EbpfError>(()) /// ``` pub fn load(&mut self, data: &[u8]) -> Result { let Self { btf, map_pin_path, globals, max_entries, extensions, verifier_log_level, allow_unsupported_maps, } = self; let mut obj = Object::parse(data)?; obj.patch_map_data(globals.clone())?; let btf_fd = if let Some(features) = &FEATURES.btf() { if let Some(btf) = obj.fixup_and_sanitize_btf(features)? { match load_btf(btf.to_bytes(), *verifier_log_level) { Ok(btf_fd) => Some(Arc::new(btf_fd)), // Only report an error here if the BTF is truly needed, otherwise proceed without. Err(err) => { for program in obj.programs.values() { match program.section { ProgramSection::Extension | ProgramSection::FEntry { sleepable: _ } | ProgramSection::FExit { sleepable: _ } | ProgramSection::Lsm { sleepable: _ } | ProgramSection::BtfTracePoint => { return Err(EbpfError::BtfError(err)) } ProgramSection::KRetProbe | ProgramSection::KProbe | ProgramSection::UProbe { sleepable: _ } | ProgramSection::URetProbe { sleepable: _ } | ProgramSection::TracePoint | ProgramSection::SocketFilter | ProgramSection::Xdp { frags: _, attach_type: _, } | ProgramSection::SkMsg | ProgramSection::SkSkbStreamParser | ProgramSection::SkSkbStreamVerdict | ProgramSection::SockOps | ProgramSection::SchedClassifier | ProgramSection::CgroupSkb | ProgramSection::CgroupSkbIngress | ProgramSection::CgroupSkbEgress | ProgramSection::CgroupSockAddr { attach_type: _ } | ProgramSection::CgroupSysctl | ProgramSection::CgroupSockopt { attach_type: _ } | ProgramSection::LircMode2 | ProgramSection::PerfEvent | ProgramSection::RawTracePoint | ProgramSection::SkLookup | ProgramSection::CgroupSock { attach_type: _ } | ProgramSection::CgroupDevice => {} } } warn!("Object BTF couldn't be loaded in the kernel: {err}"); None } } } else { None } } else { None }; if let Some(btf) = &btf { obj.relocate_btf(btf)?; } let mut maps = HashMap::new(); for (name, mut obj) in obj.maps.drain() { if let (false, EbpfSectionKind::Bss | EbpfSectionKind::Data | EbpfSectionKind::Rodata) = (FEATURES.bpf_global_data(), obj.section_kind()) { continue; } let num_cpus = || { Ok(nr_cpus().map_err(|(path, error)| EbpfError::FileError { path: PathBuf::from(path), error, })? as u32) }; let map_type: bpf_map_type = obj.map_type().try_into().map_err(MapError::from)?; if let Some(max_entries) = max_entries_override( map_type, max_entries.get(name.as_str()).copied(), || obj.max_entries(), num_cpus, || page_size() as u32, )? { obj.set_max_entries(max_entries) } match obj.map_type().try_into() { Ok(BPF_MAP_TYPE_CPUMAP) => { obj.set_value_size(if FEATURES.cpumap_prog_id() { 8 } else { 4 }) } Ok(BPF_MAP_TYPE_DEVMAP | BPF_MAP_TYPE_DEVMAP_HASH) => { obj.set_value_size(if FEATURES.devmap_prog_id() { 8 } else { 4 }) } _ => (), } let btf_fd = btf_fd.as_deref().map(|fd| fd.as_fd()); let mut map = match obj.pinning() { PinningType::None => MapData::create(obj, &name, btf_fd)?, PinningType::ByName => { // pin maps in /sys/fs/bpf by default to align with libbpf // behavior https://github.com/libbpf/libbpf/blob/v1.2.2/src/libbpf.c#L2161. let path = map_pin_path .as_deref() .unwrap_or_else(|| Path::new("/sys/fs/bpf")); MapData::create_pinned_by_name(path, obj, &name, btf_fd)? } }; map.finalize()?; maps.insert(name, map); } let text_sections = obj .functions .keys() .map(|(section_index, _)| *section_index) .collect(); obj.relocate_maps( maps.iter() .map(|(s, data)| (s.as_str(), data.fd().as_fd().as_raw_fd(), data.obj())), &text_sections, )?; obj.relocate_calls(&text_sections)?; obj.sanitize_functions(&FEATURES); let programs = obj .programs .drain() .map(|(name, prog_obj)| { let function_obj = obj.functions.get(&prog_obj.function_key()).unwrap().clone(); let prog_name = if FEATURES.bpf_name() { Some(name.clone()) } else { None }; let section = prog_obj.section.clone(); let obj = (prog_obj, function_obj); let btf_fd = btf_fd.as_ref().map(Arc::clone); let program = if extensions.contains(name.as_str()) { Program::Extension(Extension { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }) } else { match §ion { ProgramSection::KProbe => Program::KProbe(KProbe { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), kind: ProbeKind::KProbe, }), ProgramSection::KRetProbe => Program::KProbe(KProbe { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), kind: ProbeKind::KRetProbe, }), ProgramSection::UProbe { sleepable } => { let mut data = ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); if *sleepable { data.flags = BPF_F_SLEEPABLE; } Program::UProbe(UProbe { data, kind: ProbeKind::UProbe, }) } ProgramSection::URetProbe { sleepable } => { let mut data = ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); if *sleepable { data.flags = BPF_F_SLEEPABLE; } Program::UProbe(UProbe { data, kind: ProbeKind::URetProbe, }) } ProgramSection::TracePoint => Program::TracePoint(TracePoint { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::SocketFilter => Program::SocketFilter(SocketFilter { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::Xdp { frags, attach_type, .. } => { let mut data = ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); if *frags { data.flags = BPF_F_XDP_HAS_FRAGS; } Program::Xdp(Xdp { data, attach_type: *attach_type, }) } ProgramSection::SkMsg => Program::SkMsg(SkMsg { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::CgroupSysctl => Program::CgroupSysctl(CgroupSysctl { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::CgroupSockopt { attach_type, .. } => { Program::CgroupSockopt(CgroupSockopt { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), attach_type: *attach_type, }) } ProgramSection::SkSkbStreamParser => Program::SkSkb(SkSkb { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), kind: SkSkbKind::StreamParser, }), ProgramSection::SkSkbStreamVerdict => Program::SkSkb(SkSkb { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), kind: SkSkbKind::StreamVerdict, }), ProgramSection::SockOps => Program::SockOps(SockOps { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::SchedClassifier => { Program::SchedClassifier(SchedClassifier { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }) } ProgramSection::CgroupSkb => Program::CgroupSkb(CgroupSkb { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), expected_attach_type: None, }), ProgramSection::CgroupSkbIngress => Program::CgroupSkb(CgroupSkb { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), expected_attach_type: Some(CgroupSkbAttachType::Ingress), }), ProgramSection::CgroupSkbEgress => Program::CgroupSkb(CgroupSkb { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), expected_attach_type: Some(CgroupSkbAttachType::Egress), }), ProgramSection::CgroupSockAddr { attach_type, .. } => { Program::CgroupSockAddr(CgroupSockAddr { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), attach_type: *attach_type, }) } ProgramSection::LircMode2 => Program::LircMode2(LircMode2 { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::PerfEvent => Program::PerfEvent(PerfEvent { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::RawTracePoint => Program::RawTracePoint(RawTracePoint { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::Lsm { sleepable } => { let mut data = ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); if *sleepable { data.flags = BPF_F_SLEEPABLE; } Program::Lsm(Lsm { data }) } ProgramSection::BtfTracePoint => Program::BtfTracePoint(BtfTracePoint { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::FEntry { sleepable } => { let mut data = ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); if *sleepable { data.flags = BPF_F_SLEEPABLE; } Program::FEntry(FEntry { data }) } ProgramSection::FExit { sleepable } => { let mut data = ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); if *sleepable { data.flags = BPF_F_SLEEPABLE; } Program::FExit(FExit { data }) } ProgramSection::Extension => Program::Extension(Extension { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::SkLookup => Program::SkLookup(SkLookup { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), ProgramSection::CgroupSock { attach_type, .. } => { Program::CgroupSock(CgroupSock { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), attach_type: *attach_type, }) } ProgramSection::CgroupDevice => Program::CgroupDevice(CgroupDevice { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), }), } }; (name, program) }) .collect(); let maps = maps .drain() .map(parse_map) .collect::, EbpfError>>()?; if !*allow_unsupported_maps { maps.iter().try_for_each(|(_, x)| match x { Map::Unsupported(map) => Err(EbpfError::MapError(MapError::Unsupported { map_type: map.obj().map_type(), })), _ => Ok(()), })?; }; Ok(Ebpf { maps, programs }) } } fn parse_map(data: (String, MapData)) -> Result<(String, Map), EbpfError> { let (name, map) = data; let map_type = bpf_map_type::try_from(map.obj().map_type()).map_err(MapError::from)?; let map = match map_type { BPF_MAP_TYPE_ARRAY => Map::Array(map), BPF_MAP_TYPE_PERCPU_ARRAY => Map::PerCpuArray(map), BPF_MAP_TYPE_PROG_ARRAY => Map::ProgramArray(map), BPF_MAP_TYPE_HASH => Map::HashMap(map), BPF_MAP_TYPE_LRU_HASH => Map::LruHashMap(map), BPF_MAP_TYPE_PERCPU_HASH => Map::PerCpuHashMap(map), BPF_MAP_TYPE_LRU_PERCPU_HASH => Map::PerCpuLruHashMap(map), BPF_MAP_TYPE_PERF_EVENT_ARRAY => Map::PerfEventArray(map), BPF_MAP_TYPE_RINGBUF => Map::RingBuf(map), BPF_MAP_TYPE_SOCKHASH => Map::SockHash(map), BPF_MAP_TYPE_SOCKMAP => Map::SockMap(map), BPF_MAP_TYPE_BLOOM_FILTER => Map::BloomFilter(map), BPF_MAP_TYPE_LPM_TRIE => Map::LpmTrie(map), BPF_MAP_TYPE_STACK => Map::Stack(map), BPF_MAP_TYPE_STACK_TRACE => Map::StackTraceMap(map), BPF_MAP_TYPE_QUEUE => Map::Queue(map), BPF_MAP_TYPE_CPUMAP => Map::CpuMap(map), BPF_MAP_TYPE_DEVMAP => Map::DevMap(map), BPF_MAP_TYPE_DEVMAP_HASH => Map::DevMapHash(map), BPF_MAP_TYPE_XSKMAP => Map::XskMap(map), m => { warn!("The map {name} is of type {:#?} which is currently unsupported in Aya, use `allow_unsupported_maps()` to load it anyways", m); Map::Unsupported(map) } }; Ok((name, map)) } /// Computes the value which should be used to override the max_entries value of the map /// based on the user-provided override and the rules for that map type. fn max_entries_override( map_type: bpf_map_type, user_override: Option, current_value: impl Fn() -> u32, num_cpus: impl Fn() -> Result, page_size: impl Fn() -> u32, ) -> Result, EbpfError> { let max_entries = || user_override.unwrap_or_else(¤t_value); Ok(match map_type { BPF_MAP_TYPE_PERF_EVENT_ARRAY if max_entries() == 0 => Some(num_cpus()?), BPF_MAP_TYPE_RINGBUF => Some(adjust_to_page_size(max_entries(), page_size())) .filter(|adjusted| *adjusted != max_entries()) .or(user_override), _ => user_override, }) } // Adjusts the byte size of a RingBuf map to match a power-of-two multiple of the page size. // // This mirrors the logic used by libbpf. // See https://github.com/libbpf/libbpf/blob/ec6f716eda43/src/libbpf.c#L2461-L2463 fn adjust_to_page_size(byte_size: u32, page_size: u32) -> u32 { // If the byte_size is zero, return zero and let the verifier reject the map // when it is loaded. This is the behavior of libbpf. if byte_size == 0 { return 0; } // TODO: Replace with primitive method when int_roundings (https://github.com/rust-lang/rust/issues/88581) // is stabilized. fn div_ceil(n: u32, rhs: u32) -> u32 { let d = n / rhs; let r = n % rhs; if r > 0 && rhs > 0 { d + 1 } else { d } } let pages_needed = div_ceil(byte_size, page_size); page_size * pages_needed.next_power_of_two() } #[cfg(test)] mod tests { use crate::generated::bpf_map_type::*; const PAGE_SIZE: u32 = 4096; const NUM_CPUS: u32 = 4; #[test] fn test_adjust_to_page_size() { use super::adjust_to_page_size; [ (0, 0), (4096, 1), (4096, 4095), (4096, 4096), (8192, 4097), (8192, 8192), (16384, 8193), ] .into_iter() .for_each(|(exp, input)| assert_eq!(exp, adjust_to_page_size(input, PAGE_SIZE))) } #[test] fn test_max_entries_override() { use super::max_entries_override; [ (BPF_MAP_TYPE_RINGBUF, Some(1), 1, Some(PAGE_SIZE)), (BPF_MAP_TYPE_RINGBUF, None, 1, Some(PAGE_SIZE)), (BPF_MAP_TYPE_RINGBUF, None, PAGE_SIZE, None), (BPF_MAP_TYPE_PERF_EVENT_ARRAY, None, 1, None), (BPF_MAP_TYPE_PERF_EVENT_ARRAY, Some(42), 1, Some(42)), (BPF_MAP_TYPE_PERF_EVENT_ARRAY, Some(0), 1, Some(NUM_CPUS)), (BPF_MAP_TYPE_PERF_EVENT_ARRAY, None, 0, Some(NUM_CPUS)), (BPF_MAP_TYPE_PERF_EVENT_ARRAY, None, 42, None), (BPF_MAP_TYPE_ARRAY, None, 1, None), (BPF_MAP_TYPE_ARRAY, Some(2), 1, Some(2)), ] .into_iter() .for_each(|(map_type, user_override, current_value, exp)| { assert_eq!( exp, max_entries_override( map_type, user_override, || { current_value }, || Ok(NUM_CPUS), || PAGE_SIZE ) .unwrap() ) }) } } impl Default for EbpfLoader<'_> { fn default() -> Self { EbpfLoader::new() } } /// The main entry point into the library, used to work with eBPF programs and maps. #[derive(Debug)] pub struct Ebpf { maps: HashMap, programs: HashMap, } /// The main entry point into the library, used to work with eBPF programs and maps. #[deprecated(since = "0.13.0", note = "use `Ebpf` instead")] pub type Bpf = Ebpf; impl Ebpf { /// Loads eBPF bytecode from a file. /// /// Parses the given object code file and initializes the [maps](crate::maps) defined in it. If /// the kernel supports [BTF](Btf) debug info, it is automatically loaded from /// `/sys/kernel/btf/vmlinux`. /// /// For more loading options, see [EbpfLoader]. /// /// # Examples /// /// ```no_run /// use aya::Ebpf; /// /// let bpf = Ebpf::load_file("file.o")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` pub fn load_file>(path: P) -> Result { EbpfLoader::new() .btf(Btf::from_sys_fs().ok().as_ref()) .load_file(path) } /// Loads eBPF bytecode from a buffer. /// /// Parses the object code contained in `data` and initializes the /// [maps](crate::maps) defined in it. If the kernel supports [BTF](Btf) /// debug info, it is automatically loaded from `/sys/kernel/btf/vmlinux`. /// /// For more loading options, see [EbpfLoader]. /// /// # Examples /// /// ```no_run /// use aya::{Ebpf, Btf}; /// use std::fs; /// /// let data = fs::read("file.o").unwrap(); /// // load the BTF data from /sys/kernel/btf/vmlinux /// let bpf = Ebpf::load(&data)?; /// # Ok::<(), aya::EbpfError>(()) /// ``` pub fn load(data: &[u8]) -> Result { EbpfLoader::new() .btf(Btf::from_sys_fs().ok().as_ref()) .load(data) } /// Returns a reference to the map with the given name. /// /// The returned type is mostly opaque. In order to do anything useful with it you need to /// convert it to a [typed map](crate::maps). /// /// For more details and examples on maps and their usage, see the [maps module /// documentation][crate::maps]. pub fn map(&self, name: &str) -> Option<&Map> { self.maps.get(name) } /// Returns a mutable reference to the map with the given name. /// /// The returned type is mostly opaque. In order to do anything useful with it you need to /// convert it to a [typed map](crate::maps). /// /// For more details and examples on maps and their usage, see the [maps module /// documentation][crate::maps]. pub fn map_mut(&mut self, name: &str) -> Option<&mut Map> { self.maps.get_mut(name) } /// Takes ownership of a map with the given name. /// /// Use this when borrowing with [`map`](crate::Ebpf::map) or [`map_mut`](crate::Ebpf::map_mut) /// is not possible (eg when using the map from an async task). The returned /// map will be closed on `Drop`, therefore the caller is responsible for /// managing its lifetime. /// /// The returned type is mostly opaque. In order to do anything useful with it you need to /// convert it to a [typed map](crate::maps). /// /// For more details and examples on maps and their usage, see the [maps module /// documentation][crate::maps]. pub fn take_map(&mut self, name: &str) -> Option { self.maps.remove(name) } /// An iterator over all the maps. /// /// # Examples /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// for (name, map) in bpf.maps() { /// println!( /// "found map `{}`", /// name, /// ); /// } /// # Ok::<(), aya::EbpfError>(()) /// ``` pub fn maps(&self) -> impl Iterator { self.maps.iter().map(|(name, map)| (name.as_str(), map)) } /// A mutable iterator over all the maps. /// /// # Examples /// ```no_run /// # use std::path::Path; /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError), /// # #[error(transparent)] /// # Pin(#[from] aya::pin::PinError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// # let pin_path = Path::new("/tmp/pin_path"); /// for (_, map) in bpf.maps_mut() { /// map.pin(pin_path)?; /// } /// # Ok::<(), Error>(()) /// ``` pub fn maps_mut(&mut self) -> impl Iterator { self.maps.iter_mut().map(|(name, map)| (name.as_str(), map)) } /// Returns a reference to the program with the given name. /// /// You can use this to inspect a program and its properties. To load and attach a program, use /// [program_mut](Self::program_mut) instead. /// /// For more details on programs and their usage, see the [programs module /// documentation](crate::programs). /// /// # Examples /// /// ```no_run /// # let bpf = aya::Ebpf::load(&[])?; /// let program = bpf.program("SSL_read").unwrap(); /// println!("program SSL_read is of type {:?}", program.prog_type()); /// # Ok::<(), aya::EbpfError>(()) /// ``` pub fn program(&self, name: &str) -> Option<&Program> { self.programs.get(name) } /// Returns a mutable reference to the program with the given name. /// /// Used to get a program before loading and attaching it. For more details on programs and /// their usage, see the [programs module documentation](crate::programs). /// /// # Examples /// /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::programs::UProbe; /// /// let program: &mut UProbe = bpf.program_mut("SSL_read").unwrap().try_into()?; /// program.load()?; /// program.attach(Some("SSL_read"), 0, "libssl", None)?; /// # Ok::<(), aya::EbpfError>(()) /// ``` pub fn program_mut(&mut self, name: &str) -> Option<&mut Program> { self.programs.get_mut(name) } /// An iterator over all the programs. /// /// # Examples /// ```no_run /// # let bpf = aya::Ebpf::load(&[])?; /// for (name, program) in bpf.programs() { /// println!( /// "found program `{}` of type `{:?}`", /// name, /// program.prog_type() /// ); /// } /// # Ok::<(), aya::EbpfError>(()) /// ``` pub fn programs(&self) -> impl Iterator { self.programs.iter().map(|(s, p)| (s.as_str(), p)) } /// An iterator mutably referencing all of the programs. /// /// # Examples /// ```no_run /// # use std::path::Path; /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError), /// # #[error(transparent)] /// # Pin(#[from] aya::pin::PinError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// # let pin_path = Path::new("/tmp/pin_path"); /// for (_, program) in bpf.programs_mut() { /// program.pin(pin_path)?; /// } /// # Ok::<(), Error>(()) /// ``` pub fn programs_mut(&mut self) -> impl Iterator { self.programs.iter_mut().map(|(s, p)| (s.as_str(), p)) } } /// The error type returned by [`Ebpf::load_file`] and [`Ebpf::load`]. #[derive(Debug, Error)] pub enum EbpfError { /// Error loading file #[error("error loading {path}")] FileError { /// The file path path: PathBuf, #[source] /// The original io::Error error: io::Error, }, /// Unexpected pinning type #[error("unexpected pinning type {name}")] UnexpectedPinningType { /// The value encountered name: u32, }, /// Error parsing BPF object #[error("error parsing BPF object: {0}")] ParseError(#[from] ParseError), /// Error parsing BTF object #[error("BTF error: {0}")] BtfError(#[from] BtfError), /// Error performing relocations #[error("error relocating function")] RelocationError(#[from] EbpfRelocationError), /// Error performing relocations #[error("error relocating section")] BtfRelocationError(#[from] BtfRelocationError), /// No BTF parsed for object #[error("no BTF parsed for object")] NoBTF, #[error("map error: {0}")] /// A map error MapError(#[from] MapError), #[error("program error: {0}")] /// A program error ProgramError(#[from] ProgramError), } /// The error type returned by [`Bpf::load_file`] and [`Bpf::load`]. #[deprecated(since = "0.13.0", note = "use `EbpfError` instead")] pub type BpfError = EbpfError; fn load_btf( raw_btf: Vec, verifier_log_level: VerifierLogLevel, ) -> Result { let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| { bpf_load_btf(raw_btf.as_slice(), logger, verifier_log_level) }); ret.map_err(|(_, io_error)| BtfError::LoadError { io_error, verifier_log, }) } /// Global data that can be exported to eBPF programs before they are loaded. /// /// Valid global data includes `Pod` types and slices of `Pod` types. See also /// [EbpfLoader::set_global]. pub struct GlobalData<'a> { bytes: &'a [u8], } impl<'a, T: Pod> From<&'a [T]> for GlobalData<'a> { fn from(s: &'a [T]) -> Self { GlobalData { bytes: bytes_of_slice(s), } } } impl<'a, T: Pod> From<&'a T> for GlobalData<'a> { fn from(v: &'a T) -> Self { GlobalData { // Safety: v is Pod bytes: unsafe { bytes_of(v) }, } } } aya-0.13.1/src/lib.rs000064400000000000000000000127221046102023000123670ustar 00000000000000//! [![](https://aya-rs.dev/assets/images/aya_logo_docs.svg)](https://aya-rs.dev) //! //! A library to work with eBPF programs. //! //! eBPF is a technology that allows running user-supplied programs inside the //! Linux kernel. For more info see //! [https://ebpf.io/what-is-ebpf](https://ebpf.io/what-is-ebpf). //! //! Aya is an eBPF library built with a focus on operability and developer experience. It does not //! rely on [libbpf](https://github.com/libbpf/libbpf) nor [bcc](https://github.com/iovisor/bcc) - //! it's built from the ground up purely in Rust, using only the [libc](https://crates.io/libc) //! crate to execute syscalls. With BTF support and when linked with musl, it offers a true //! [compile once, run everywhere //! solution](https://facebookmicrosites.github.io/bpf/blog/2020/02/19/bpf-portability-and-co-re.html), //! where a single self-contained binary can be deployed on many linux distributions //! and kernel versions. //! //! Some of the major features provided include: //! //! * Support for the **BPF Type Format** (BTF), which is transparently enabled when //! supported by the target kernel. This allows eBPF programs compiled against //! one kernel version to run on different kernel versions without the need to //! recompile. //! * Support for function call relocation and global data maps, which //! allows eBPF programs to make **function calls** and use **global variables //! and initializers**. //! * **Async support** with both [tokio] and [async-std]. //! * Easy to deploy and fast to build: aya doesn't require a kernel build or //! compiled headers, and not even a C toolchain; a release build completes in a matter //! of seconds. //! //! [tokio]: https://docs.rs/tokio //! [async-std]: https://docs.rs/async-std #![doc( html_logo_url = "https://aya-rs.dev/assets/images/crabby.svg", html_favicon_url = "https://aya-rs.dev/assets/images/crabby.svg" )] #![cfg_attr(docsrs, feature(doc_cfg))] #![deny( clippy::all, clippy::use_self, absolute_paths_not_starting_with_crate, deprecated_in_future, elided_lifetimes_in_paths, explicit_outlives_requirements, ffi_unwind_calls, keyword_idents, //let_underscore_drop, macro_use_extern_crate, meta_variable_misuse, missing_abi, //missing_copy_implementations, missing_docs, non_ascii_idents, noop_method_call, rust_2021_incompatible_closure_captures, rust_2021_incompatible_or_patterns, rust_2021_prefixes_incompatible_syntax, rust_2021_prelude_collisions, single_use_lifetimes, trivial_numeric_casts, unreachable_pub, //unsafe_op_in_unsafe_fn, unstable_features, unused_crate_dependencies, unused_extern_crates, unused_import_braces, unused_lifetimes, unused_macro_rules, //unused_qualifications, https://github.com/rust-lang/rust/commit/9ccc7b7 added size_of to the prelude, but we need to continue to qualify it so that we build on older compilers. //unused_results, )] #![allow(clippy::missing_safety_doc, clippy::len_without_is_empty)] #![cfg_attr( all(feature = "async_tokio", feature = "async_std"), allow(unused_crate_dependencies) )] mod bpf; pub mod maps; pub mod pin; pub mod programs; pub mod sys; pub mod util; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; use aya_obj as obj; use aya_obj::generated; pub use bpf::*; pub use obj::btf::{Btf, BtfError}; pub use object::Endianness; #[doc(hidden)] pub use sys::netlink_set_link_up; // See https://github.com/rust-lang/rust/pull/124210; this structure exists to avoid crashing the // process when we try to close a fake file descriptor. #[derive(Debug)] struct MockableFd { #[cfg(not(test))] fd: OwnedFd, #[cfg(test)] fd: Option, } impl MockableFd { #[cfg(test)] const fn mock_signed_fd() -> i32 { 1337 } #[cfg(test)] const fn mock_unsigned_fd() -> u32 { 1337 } #[cfg(not(test))] fn from_fd(fd: OwnedFd) -> Self { Self { fd } } #[cfg(test)] fn from_fd(fd: OwnedFd) -> Self { let fd = Some(fd); Self { fd } } #[cfg(not(test))] fn inner(&self) -> &OwnedFd { let Self { fd } = self; fd } #[cfg(test)] fn inner(&self) -> &OwnedFd { let Self { fd } = self; fd.as_ref().unwrap() } #[cfg(not(test))] fn into_inner(self) -> OwnedFd { self.fd } #[cfg(test)] fn into_inner(mut self) -> OwnedFd { self.fd.take().unwrap() } fn try_clone(&self) -> std::io::Result { let fd = self.inner(); let fd = fd.try_clone()?; Ok(Self::from_fd(fd)) } } impl From for MockableFd where OwnedFd: From, { fn from(value: T) -> Self { let fd = OwnedFd::from(value); Self::from_fd(fd) } } impl AsFd for MockableFd { fn as_fd(&self) -> BorrowedFd<'_> { self.inner().as_fd() } } impl AsRawFd for MockableFd { fn as_raw_fd(&self) -> RawFd { self.inner().as_raw_fd() } } impl FromRawFd for MockableFd { unsafe fn from_raw_fd(fd: RawFd) -> Self { let fd = OwnedFd::from_raw_fd(fd); Self::from_fd(fd) } } #[cfg(test)] impl Drop for MockableFd { fn drop(&mut self) { use std::os::fd::AsRawFd as _; let Self { fd } = self; let fd = fd.take().unwrap(); if fd.as_raw_fd() < Self::mock_signed_fd() { std::mem::drop(fd) } else { std::mem::forget(fd) } } } aya-0.13.1/src/maps/array/array.rs000064400000000000000000000064041046102023000150150ustar 00000000000000use std::{ borrow::{Borrow, BorrowMut}, marker::PhantomData, os::fd::AsFd as _, }; use crate::{ maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError}, sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError}, Pod, }; /// A fixed-size array. /// /// The size of the array is defined on the eBPF side using the `bpf_map_def::max_entries` field. /// All the entries are zero-initialized when the map is created. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 3.19. /// /// # Examples /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::Array; /// /// let mut array = Array::try_from(bpf.map_mut("ARRAY").unwrap())?; /// array.set(1, 42, 0)?; /// assert_eq!(array.get(&1, 0)?, 42); /// # Ok::<(), aya::EbpfError>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_ARRAY")] pub struct Array { pub(crate) inner: T, _v: PhantomData, } impl, V: Pod> Array { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::(data)?; Ok(Self { inner: map, _v: PhantomData, }) } /// Returns the number of elements in the array. /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn len(&self) -> u32 { self.inner.borrow().obj.max_entries() } /// Returns the value stored at the given index. /// /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] /// if `bpf_map_lookup_elem` fails. pub fn get(&self, index: &u32, flags: u64) -> Result { let data = self.inner.borrow(); check_bounds(data, *index)?; let fd = data.fd().as_fd(); let value = bpf_map_lookup_elem(fd, index, flags).map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })?; value.ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the array. The iterator item type is `Result`. pub fn iter(&self) -> impl Iterator> + '_ { (0..self.len()).map(move |i| self.get(&i, 0)) } } impl, V: Pod> Array { /// Sets the value of the element at the given index. /// /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] /// if `bpf_map_update_elem` fails. pub fn set(&mut self, index: u32, value: impl Borrow, flags: u64) -> Result<(), MapError> { let data = self.inner.borrow_mut(); check_bounds(data, index)?; let fd = data.fd().as_fd(); bpf_map_update_elem(fd, Some(&index), value.borrow(), flags).map_err(|(_, io_error)| { SyscallError { call: "bpf_map_update_elem", io_error, } })?; Ok(()) } } impl, V: Pod> IterableMap for Array { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, index: &u32) -> Result { self.get(index, 0) } } aya-0.13.1/src/maps/array/mod.rs000064400000000000000000000003011046102023000144440ustar 00000000000000//! Array types. #[allow(clippy::module_inception)] mod array; mod per_cpu_array; mod program_array; pub use array::*; pub use per_cpu_array::PerCpuArray; pub use program_array::ProgramArray; aya-0.13.1/src/maps/array/per_cpu_array.rs000064400000000000000000000101101046102023000165170ustar 00000000000000use std::{ borrow::{Borrow, BorrowMut}, marker::PhantomData, os::fd::AsFd as _, }; use crate::{ maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError, PerCpuValues}, sys::{bpf_map_lookup_elem_per_cpu, bpf_map_update_elem_per_cpu, SyscallError}, Pod, }; /// A per-CPU fixed-size array. /// /// The size of the array is defined on the eBPF side using the `bpf_map_def::max_entries` field. /// All the entries are zero-initialized when the map is created. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.6. /// /// # Examples /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::{PerCpuArray, PerCpuValues}; /// use aya::util::nr_cpus; /// /// let mut array = PerCpuArray::try_from(bpf.map_mut("ARRAY").unwrap())?; /// /// // set array[1] = 42 for all cpus /// let nr_cpus = nr_cpus().map_err(|(_, error)| error)?; /// array.set(1, PerCpuValues::try_from(vec![42u32; nr_cpus])?, 0)?; /// /// // retrieve the values at index 1 for all cpus /// let values = array.get(&1, 0)?; /// assert_eq!(values.len(), nr_cpus); /// for cpu_val in values.iter() { /// assert_eq!(*cpu_val, 42u32); /// } /// # Ok::<(), Error>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_PERCPU_ARRAY")] pub struct PerCpuArray { pub(crate) inner: T, _v: PhantomData, } impl, V: Pod> PerCpuArray { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::(data)?; Ok(Self { inner: map, _v: PhantomData, }) } /// Returns the number of elements in the array. /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn len(&self) -> u32 { self.inner.borrow().obj.max_entries() } /// Returns a slice of values - one for each CPU - stored at the given index. /// /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] /// if `bpf_map_lookup_elem` fails. pub fn get(&self, index: &u32, flags: u64) -> Result, MapError> { let data = self.inner.borrow(); check_bounds(data, *index)?; let fd = data.fd().as_fd(); let value = bpf_map_lookup_elem_per_cpu(fd, index, flags).map_err(|(_, io_error)| { SyscallError { call: "bpf_map_lookup_elem", io_error, } })?; value.ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the array. The iterator item type is /// `Result, MapError>`. pub fn iter(&self) -> impl Iterator, MapError>> + '_ { (0..self.len()).map(move |i| self.get(&i, 0)) } } impl, V: Pod> PerCpuArray { /// Sets the values - one for each CPU - at the given index. /// /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] /// if `bpf_map_update_elem` fails. pub fn set(&mut self, index: u32, values: PerCpuValues, flags: u64) -> Result<(), MapError> { let data = self.inner.borrow_mut(); check_bounds(data, index)?; let fd = data.fd().as_fd(); bpf_map_update_elem_per_cpu(fd, &index, &values, flags).map_err(|(_, io_error)| { SyscallError { call: "bpf_map_update_elem", io_error, } })?; Ok(()) } } impl, V: Pod> IterableMap> for PerCpuArray { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, index: &u32) -> Result, MapError> { self.get(index, 0) } } aya-0.13.1/src/maps/array/program_array.rs000064400000000000000000000071501046102023000165430ustar 00000000000000//! An array of eBPF program file descriptors used as a jump table. use std::{ borrow::{Borrow, BorrowMut}, os::fd::{AsFd as _, AsRawFd as _, RawFd}, }; use crate::{ maps::{check_bounds, check_kv_size, MapData, MapError, MapKeys}, programs::ProgramFd, sys::{bpf_map_delete_elem, bpf_map_update_elem, SyscallError}, }; /// An array of eBPF program file descriptors used as a jump table. /// /// eBPF programs can jump to other programs calling `bpf_tail_call(ctx, /// prog_array, index)`. You can use [`ProgramArray`] to configure which /// programs correspond to which jump indexes. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.2. /// /// # Examples /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::ProgramArray; /// use aya::programs::CgroupSkb; /// /// let mut prog_array = ProgramArray::try_from(bpf.take_map("JUMP_TABLE").unwrap())?; /// let prog_0: &CgroupSkb = bpf.program("example_prog_0").unwrap().try_into()?; /// let prog_0_fd = prog_0.fd().unwrap(); /// let prog_1: &CgroupSkb = bpf.program("example_prog_1").unwrap().try_into()?; /// let prog_1_fd = prog_1.fd().unwrap(); /// let prog_2: &CgroupSkb = bpf.program("example_prog_2").unwrap().try_into()?; /// let prog_2_fd = prog_2.fd().unwrap(); /// let flags = 0; /// /// // bpf_tail_call(ctx, JUMP_TABLE, 0) will jump to prog_0 /// prog_array.set(0, &prog_0_fd, flags); /// /// // bpf_tail_call(ctx, JUMP_TABLE, 1) will jump to prog_1 /// prog_array.set(1, &prog_1_fd, flags); /// /// // bpf_tail_call(ctx, JUMP_TABLE, 2) will jump to prog_2 /// prog_array.set(2, &prog_2_fd, flags); /// # Ok::<(), aya::EbpfError>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_PROG_ARRAY")] pub struct ProgramArray { pub(crate) inner: T, } impl> ProgramArray { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::(data)?; Ok(Self { inner: map }) } /// An iterator over the indices of the array that point to a program. The iterator item type /// is `Result`. pub fn indices(&self) -> MapKeys<'_, u32> { MapKeys::new(self.inner.borrow()) } } impl> ProgramArray { /// Sets the target program file descriptor for the given index in the jump table. /// /// When an eBPF program calls `bpf_tail_call(ctx, prog_array, index)`, control /// flow will jump to `program`. pub fn set(&mut self, index: u32, program: &ProgramFd, flags: u64) -> Result<(), MapError> { let data = self.inner.borrow_mut(); check_bounds(data, index)?; let fd = data.fd().as_fd(); let prog_fd = program.as_fd(); let prog_fd = prog_fd.as_raw_fd(); bpf_map_update_elem(fd, Some(&index), &prog_fd, flags).map_err(|(_, io_error)| { SyscallError { call: "bpf_map_update_elem", io_error, } })?; Ok(()) } /// Clears the value at index in the jump table. /// /// Calling `bpf_tail_call(ctx, prog_array, index)` on an index that has been cleared returns an /// error. pub fn clear_index(&mut self, index: &u32) -> Result<(), MapError> { let data = self.inner.borrow_mut(); check_bounds(data, *index)?; let fd = data.fd().as_fd(); bpf_map_delete_elem(fd, index) .map(|_| ()) .map_err(|(_, io_error)| { SyscallError { call: "bpf_map_delete_elem", io_error, } .into() }) } } aya-0.13.1/src/maps/bloom_filter.rs000064400000000000000000000127161046102023000152410ustar 00000000000000//! A Bloom Filter. use std::{ borrow::{Borrow, BorrowMut}, marker::PhantomData, os::fd::AsFd as _, }; use crate::{ maps::{check_v_size, MapData, MapError}, sys::{bpf_map_lookup_elem_ptr, bpf_map_push_elem, SyscallError}, Pod, }; /// A Bloom Filter. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.16. /// /// # Examples /// /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::bloom_filter::BloomFilter; /// /// let mut bloom_filter = BloomFilter::try_from(bpf.map_mut("BLOOM_FILTER").unwrap())?; /// /// bloom_filter.insert(1, 0)?; /// /// assert!(bloom_filter.contains(&1, 0).is_ok()); /// assert!(bloom_filter.contains(&2, 0).is_err()); /// /// # Ok::<(), aya::EbpfError>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_BLOOM_FILTER")] #[derive(Debug)] pub struct BloomFilter { pub(crate) inner: T, _v: PhantomData, } impl, V: Pod> BloomFilter { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_v_size::(data)?; Ok(Self { inner: map, _v: PhantomData, }) } /// Query the existence of the element. pub fn contains(&self, mut value: &V, flags: u64) -> Result<(), MapError> { let fd = self.inner.borrow().fd().as_fd(); bpf_map_lookup_elem_ptr::(fd, None, &mut value, flags) .map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })? .ok_or(MapError::ElementNotFound)?; Ok(()) } } impl, V: Pod> BloomFilter { /// Inserts a value into the map. pub fn insert(&mut self, value: impl Borrow, flags: u64) -> Result<(), MapError> { let fd = self.inner.borrow_mut().fd().as_fd(); bpf_map_push_elem(fd, value.borrow(), flags).map_err(|(_, io_error)| SyscallError { call: "bpf_map_push_elem", io_error, })?; Ok(()) } } #[cfg(test)] mod tests { use std::io; use assert_matches::assert_matches; use libc::{EFAULT, ENOENT}; use super::*; use crate::{ generated::{ bpf_cmd, bpf_map_type::{BPF_MAP_TYPE_ARRAY, BPF_MAP_TYPE_BLOOM_FILTER}, }, maps::{ test_utils::{self, new_map}, Map, }, obj, sys::{override_syscall, SysResult, Syscall}, }; fn new_obj_map() -> obj::Map { test_utils::new_obj_map::(BPF_MAP_TYPE_BLOOM_FILTER) } fn sys_error(value: i32) -> SysResult { Err((-1, io::Error::from_raw_os_error(value))) } #[test] fn test_wrong_value_size() { let map = new_map(new_obj_map()); assert_matches!( BloomFilter::<_, u16>::new(&map), Err(MapError::InvalidValueSize { size: 2, expected: 4 }) ); } #[test] fn test_try_from_wrong_map() { // Use any map type here other than BPF_MAP_TYPE_PERF_EVENT_ARRAY as it will trip miri let map = new_map(test_utils::new_obj_map::(BPF_MAP_TYPE_ARRAY)); let map = Map::Array(map); assert_matches!( BloomFilter::<_, u32>::try_from(&map), Err(MapError::InvalidMapType { .. }) ); } #[test] fn test_new_ok() { let map = new_map(new_obj_map()); assert!(BloomFilter::<_, u32>::new(&map).is_ok()); } #[test] fn test_try_from_ok() { let map = new_map(new_obj_map()); let map = Map::BloomFilter(map); assert!(BloomFilter::<_, u32>::try_from(&map).is_ok()) } #[test] fn test_insert_syscall_error() { let mut map = new_map(new_obj_map()); let mut bloom_filter = BloomFilter::<_, u32>::new(&mut map).unwrap(); override_syscall(|_| sys_error(EFAULT)); assert_matches!( bloom_filter.insert(1, 0), Err(MapError::SyscallError(SyscallError { call: "bpf_map_push_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT) ); } #[test] fn test_insert_ok() { let mut map = new_map(new_obj_map()); let mut bloom_filter = BloomFilter::<_, u32>::new(&mut map).unwrap(); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM, .. } => Ok(1), _ => sys_error(EFAULT), }); assert!(bloom_filter.insert(0, 42).is_ok()); } #[test] fn test_contains_syscall_error() { let map = new_map(new_obj_map()); let bloom_filter = BloomFilter::<_, u32>::new(&map).unwrap(); override_syscall(|_| sys_error(EFAULT)); assert_matches!( bloom_filter.contains(&1, 0), Err(MapError::SyscallError(SyscallError { call: "bpf_map_lookup_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT) ); } #[test] fn test_contains_not_found() { let map = new_map(new_obj_map()); let bloom_filter = BloomFilter::<_, u32>::new(&map).unwrap(); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM, .. } => sys_error(ENOENT), _ => sys_error(EFAULT), }); assert_matches!(bloom_filter.contains(&1, 0), Err(MapError::ElementNotFound)); } } aya-0.13.1/src/maps/hash_map/hash_map.rs000064400000000000000000000371461046102023000161300ustar 00000000000000use std::{ borrow::{Borrow, BorrowMut}, marker::PhantomData, os::fd::AsFd as _, }; use crate::{ maps::{check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys}, sys::{bpf_map_lookup_elem, SyscallError}, Pod, }; /// A hash map that can be shared between eBPF programs and user space. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 3.19. /// /// # Examples /// /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::HashMap; /// /// let mut redirect_ports = HashMap::try_from(bpf.map_mut("REDIRECT_PORTS").unwrap())?; /// /// // redirect port 80 to 8080 /// redirect_ports.insert(80, 8080, 0); /// // redirect port 443 to 8443 /// redirect_ports.insert(443, 8443, 0); /// # Ok::<(), aya::EbpfError>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_HASH")] #[doc(alias = "BPF_MAP_TYPE_LRU_HASH")] #[derive(Debug)] pub struct HashMap { pub(crate) inner: T, _k: PhantomData, _v: PhantomData, } impl, K: Pod, V: Pod> HashMap { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::(data)?; Ok(Self { inner: map, _k: PhantomData, _v: PhantomData, }) } /// Returns a copy of the value associated with the key. pub fn get(&self, key: &K, flags: u64) -> Result { let fd = self.inner.borrow().fd().as_fd(); let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })?; value.ok_or(MapError::KeyNotFound) } /// An iterator visiting all key-value pairs in arbitrary order. The /// iterator item type is `Result<(K, V), MapError>`. pub fn iter(&self) -> MapIter<'_, K, V, Self> { MapIter::new(self) } /// An iterator visiting all keys in arbitrary order. The iterator element /// type is `Result`. pub fn keys(&self) -> MapKeys<'_, K> { MapKeys::new(self.inner.borrow()) } } impl, K: Pod, V: Pod> HashMap { /// Inserts a key-value pair into the map. pub fn insert( &mut self, key: impl Borrow, value: impl Borrow, flags: u64, ) -> Result<(), MapError> { hash_map::insert(self.inner.borrow_mut(), key.borrow(), value.borrow(), flags) } /// Removes a key from the map. pub fn remove(&mut self, key: &K) -> Result<(), MapError> { hash_map::remove(self.inner.borrow_mut(), key) } } impl, K: Pod, V: Pod> IterableMap for HashMap { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, key: &K) -> Result { Self::get(self, key, 0) } } #[cfg(test)] mod tests { use std::io; use assert_matches::assert_matches; use libc::{EFAULT, ENOENT}; use super::*; use crate::{ generated::{ bpf_attr, bpf_cmd, bpf_map_type::{BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH}, }, maps::{ test_utils::{self, new_map}, Map, }, obj, sys::{override_syscall, SysResult, Syscall}, }; fn new_obj_map() -> obj::Map { test_utils::new_obj_map::(BPF_MAP_TYPE_HASH) } fn sys_error(value: i32) -> SysResult { Err((-1, io::Error::from_raw_os_error(value))) } #[test] fn test_wrong_key_size() { let map = new_map(new_obj_map()); assert_matches!( HashMap::<_, u8, u32>::new(&map), Err(MapError::InvalidKeySize { size: 1, expected: 4 }) ); } #[test] fn test_wrong_value_size() { let map = new_map(new_obj_map()); assert_matches!( HashMap::<_, u32, u16>::new(&map), Err(MapError::InvalidValueSize { size: 2, expected: 4 }) ); } #[test] fn test_try_from_wrong_map() { let map = new_map(new_obj_map()); let map = Map::Array(map); assert_matches!( HashMap::<_, u8, u32>::try_from(&map), Err(MapError::InvalidMapType { .. }) ); } #[test] fn test_try_from_wrong_map_values() { let map = new_map(new_obj_map()); let map = Map::HashMap(map); assert_matches!( HashMap::<_, u32, u16>::try_from(&map), Err(MapError::InvalidValueSize { size: 2, expected: 4 }) ); } #[test] fn test_new_ok() { let map = new_map(new_obj_map()); assert!(HashMap::<_, u32, u32>::new(&map).is_ok()); } #[test] fn test_try_from_ok() { let map = new_map(new_obj_map()); let map = Map::HashMap(map); assert!(HashMap::<_, u32, u32>::try_from(&map).is_ok()) } #[test] fn test_try_from_ok_lru() { let map_data = || new_map(test_utils::new_obj_map::(BPF_MAP_TYPE_LRU_HASH)); let map = Map::HashMap(map_data()); assert!(HashMap::<_, u32, u32>::try_from(&map).is_ok()); let map = Map::LruHashMap(map_data()); assert!(HashMap::<_, u32, u32>::try_from(&map).is_ok()) } #[test] fn test_insert_syscall_error() { let mut map = new_map(new_obj_map()); let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap(); override_syscall(|_| sys_error(EFAULT)); assert_matches!( hm.insert(1, 42, 0), Err(MapError::SyscallError(SyscallError { call: "bpf_map_update_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT) ); } #[test] fn test_insert_ok() { let mut map = new_map(new_obj_map()); let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap(); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM, .. } => Ok(1), _ => sys_error(EFAULT), }); assert!(hm.insert(1, 42, 0).is_ok()); } #[test] fn test_insert_boxed_ok() { let mut map = new_map(new_obj_map()); let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap(); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM, .. } => Ok(1), _ => sys_error(EFAULT), }); assert!(hm.insert(Box::new(1), Box::new(42), 0).is_ok()); } #[test] fn test_remove_syscall_error() { let mut map = new_map(new_obj_map()); let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap(); override_syscall(|_| sys_error(EFAULT)); assert_matches!( hm.remove(&1), Err(MapError::SyscallError(SyscallError { call: "bpf_map_delete_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT) ); } #[test] fn test_remove_ok() { let mut map = new_map(new_obj_map()); let mut hm = HashMap::<_, u32, u32>::new(&mut map).unwrap(); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_DELETE_ELEM, .. } => Ok(1), _ => sys_error(EFAULT), }); assert!(hm.remove(&1).is_ok()); } #[test] fn test_get_syscall_error() { let map = new_map(new_obj_map()); override_syscall(|_| sys_error(EFAULT)); let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); assert_matches!( hm.get(&1, 0), Err(MapError::SyscallError(SyscallError { call: "bpf_map_lookup_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT) ); } #[test] fn test_get_not_found() { let map = new_map(new_obj_map()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM, .. } => sys_error(ENOENT), _ => sys_error(EFAULT), }); let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); assert_matches!(hm.get(&1, 0), Err(MapError::KeyNotFound)); } fn bpf_key(attr: &bpf_attr) -> Option { match unsafe { attr.__bindgen_anon_2.key } as *const T { p if p.is_null() => None, p => Some(unsafe { *p }), } } fn set_next_key(attr: &bpf_attr, next: T) { let key = unsafe { attr.__bindgen_anon_2.__bindgen_anon_1.next_key } as *const T as *mut T; unsafe { *key = next }; } fn set_ret(attr: &bpf_attr, ret: T) { let value = unsafe { attr.__bindgen_anon_2.__bindgen_anon_1.value } as *const T as *mut T; unsafe { *value = ret }; } #[test] fn test_keys_empty() { let map = new_map(new_obj_map()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY, .. } => sys_error(ENOENT), _ => sys_error(EFAULT), }); let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); let keys = hm.keys().collect::, _>>(); assert_matches!(keys, Ok(ks) if ks.is_empty()) } fn get_next_key(attr: &bpf_attr) -> SysResult { match bpf_key(attr) { None => set_next_key(attr, 10), Some(10) => set_next_key(attr, 20), Some(20) => set_next_key(attr, 30), Some(30) => return sys_error(ENOENT), Some(_) => return sys_error(EFAULT), }; Ok(1) } fn lookup_elem(attr: &bpf_attr) -> SysResult { match bpf_key(attr) { Some(10) => set_ret(attr, 100), Some(20) => set_ret(attr, 200), Some(30) => set_ret(attr, 300), Some(_) => return sys_error(ENOENT), None => return sys_error(EFAULT), }; Ok(1) } #[test] fn test_keys() { let map = new_map(new_obj_map()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY, attr, } => get_next_key(attr), _ => sys_error(EFAULT), }); let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); let keys = hm.keys().collect::, _>>().unwrap(); assert_eq!(&keys, &[10, 20, 30]) } #[test] fn test_keys_error() { let map = new_map(new_obj_map()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY, attr, } => { match bpf_key(attr) { None => set_next_key(attr, 10), Some(10) => set_next_key(attr, 20), Some(_) => return sys_error(EFAULT), }; Ok(1) } _ => sys_error(EFAULT), }); let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); let mut keys = hm.keys(); assert_matches!(keys.next(), Some(Ok(10))); assert_matches!(keys.next(), Some(Ok(20))); assert_matches!( keys.next(), Some(Err(MapError::SyscallError(SyscallError { call: "bpf_map_get_next_key", io_error: _ }))) ); assert_matches!(keys.next(), None); } #[test] fn test_iter() { let map = new_map(new_obj_map()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY, attr, } => get_next_key(attr), Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM, attr, } => lookup_elem(attr), _ => sys_error(EFAULT), }); let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); let items = hm.iter().collect::, _>>().unwrap(); assert_eq!(&items, &[(10, 100), (20, 200), (30, 300)]) } #[test] fn test_iter_key_deleted() { let map = new_map(new_obj_map()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY, attr, } => get_next_key(attr), Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM, attr, } => { match bpf_key(attr) { Some(10) => set_ret(attr, 100), Some(20) => return sys_error(ENOENT), Some(30) => set_ret(attr, 300), Some(_) => return sys_error(ENOENT), None => return sys_error(EFAULT), }; Ok(1) } _ => sys_error(EFAULT), }); let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); let items = hm.iter().collect::, _>>().unwrap(); assert_eq!(&items, &[(10, 100), (30, 300)]) } #[test] fn test_iter_key_error() { let map = new_map(new_obj_map()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY, attr, } => { match bpf_key(attr) { None => set_next_key(attr, 10), Some(10) => set_next_key(attr, 20), Some(20) => return sys_error(EFAULT), Some(30) => return sys_error(ENOENT), Some(i) => panic!("invalid key {}", i), }; Ok(1) } Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM, attr, } => lookup_elem(attr), _ => sys_error(EFAULT), }); let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); let mut iter = hm.iter(); assert_matches!(iter.next(), Some(Ok((10, 100)))); assert_matches!(iter.next(), Some(Ok((20, 200)))); assert_matches!( iter.next(), Some(Err(MapError::SyscallError(SyscallError { call: "bpf_map_get_next_key", io_error: _ }))) ); assert_matches!(iter.next(), None); } #[test] fn test_iter_value_error() { let map = new_map(new_obj_map()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_NEXT_KEY, attr, } => get_next_key(attr), Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM, attr, } => { match bpf_key(attr) { Some(10) => set_ret(attr, 100), Some(20) => return sys_error(EFAULT), Some(30) => set_ret(attr, 300), Some(_) => return sys_error(ENOENT), None => return sys_error(EFAULT), }; Ok(1) } _ => sys_error(EFAULT), }); let hm = HashMap::<_, u32, u32>::new(&map).unwrap(); let mut iter = hm.iter(); assert_matches!(iter.next(), Some(Ok((10, 100)))); assert_matches!( iter.next(), Some(Err(MapError::SyscallError(SyscallError { call: "bpf_map_lookup_elem", io_error: _ }))) ); assert_matches!(iter.next(), Some(Ok((30, 300)))); assert_matches!(iter.next(), None); } } aya-0.13.1/src/maps/hash_map/mod.rs000064400000000000000000000017251046102023000151210ustar 00000000000000//! Hash map types. use std::os::fd::AsFd as _; use crate::{ maps::MapError, sys::{bpf_map_delete_elem, bpf_map_update_elem, SyscallError}, Pod, }; #[allow(clippy::module_inception)] mod hash_map; mod per_cpu_hash_map; pub use hash_map::*; pub use per_cpu_hash_map::*; use super::MapData; pub(crate) fn insert( map: &MapData, key: &K, value: &V, flags: u64, ) -> Result<(), MapError> { let fd = map.fd().as_fd(); bpf_map_update_elem(fd, Some(key), value, flags).map_err(|(_, io_error)| SyscallError { call: "bpf_map_update_elem", io_error, })?; Ok(()) } pub(crate) fn remove(map: &MapData, key: &K) -> Result<(), MapError> { let fd = map.fd().as_fd(); bpf_map_delete_elem(fd, key) .map(|_| ()) .map_err(|(_, io_error)| { SyscallError { call: "bpf_map_delete_elem", io_error, } .into() }) } aya-0.13.1/src/maps/hash_map/per_cpu_hash_map.rs000064400000000000000000000126261046102023000176410ustar 00000000000000//! Per-CPU hash map. use std::{ borrow::{Borrow, BorrowMut}, marker::PhantomData, os::fd::AsFd as _, }; use crate::{ maps::{ check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys, PerCpuValues, }, sys::{bpf_map_lookup_elem_per_cpu, bpf_map_update_elem_per_cpu, SyscallError}, Pod, }; /// Similar to [`HashMap`](crate::maps::HashMap) but each CPU holds a separate value for a given key. Typically used to /// minimize lock contention in eBPF programs. /// /// This type can be used with eBPF maps of type `BPF_MAP_TYPE_PERCPU_HASH` and /// `BPF_MAP_TYPE_LRU_PERCPU_HASH`. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.6. /// /// # Examples /// /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::PerCpuHashMap; /// /// const CPU_IDS: u8 = 1; /// const WAKEUPS: u8 = 2; /// /// let mut hm = PerCpuHashMap::<_, u8, u32>::try_from(bpf.map_mut("PER_CPU_STORAGE").unwrap())?; /// let cpu_ids = unsafe { hm.get(&CPU_IDS, 0)? }; /// let wakeups = unsafe { hm.get(&WAKEUPS, 0)? }; /// for (cpu_id, wakeups) in cpu_ids.iter().zip(wakeups.iter()) { /// println!("cpu {} woke up {} times", cpu_id, wakeups); /// } /// # Ok::<(), aya::EbpfError>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_LRU_PERCPU_HASH")] #[doc(alias = "BPF_MAP_TYPE_PERCPU_HASH")] pub struct PerCpuHashMap { pub(crate) inner: T, _k: PhantomData, _v: PhantomData, } impl, K: Pod, V: Pod> PerCpuHashMap { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::(data)?; Ok(Self { inner: map, _k: PhantomData, _v: PhantomData, }) } /// Returns a slice of values - one for each CPU - associated with the key. pub fn get(&self, key: &K, flags: u64) -> Result, MapError> { let fd = self.inner.borrow().fd().as_fd(); let values = bpf_map_lookup_elem_per_cpu(fd, key, flags).map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })?; values.ok_or(MapError::KeyNotFound) } /// An iterator visiting all key-value pairs in arbitrary order. The /// iterator item type is `Result<(K, PerCpuValues), MapError>`. pub fn iter(&self) -> MapIter<'_, K, PerCpuValues, Self> { MapIter::new(self) } /// An iterator visiting all keys in arbitrary order. The iterator element /// type is `Result`. pub fn keys(&self) -> MapKeys<'_, K> { MapKeys::new(self.inner.borrow()) } } impl, K: Pod, V: Pod> PerCpuHashMap { /// Inserts a slice of values - one for each CPU - for the given key. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::{PerCpuHashMap, PerCpuValues}; /// use aya::util::nr_cpus; /// /// const RETRIES: u8 = 1; /// /// let nr_cpus = nr_cpus().map_err(|(_, error)| error)?; /// let mut hm = PerCpuHashMap::<_, u8, u32>::try_from(bpf.map_mut("PER_CPU_STORAGE").unwrap())?; /// hm.insert( /// RETRIES, /// PerCpuValues::try_from(vec![3u32; nr_cpus])?, /// 0, /// )?; /// # Ok::<(), Error>(()) /// ``` pub fn insert( &mut self, key: impl Borrow, values: PerCpuValues, flags: u64, ) -> Result<(), MapError> { let fd = self.inner.borrow_mut().fd().as_fd(); bpf_map_update_elem_per_cpu(fd, key.borrow(), &values, flags).map_err( |(_, io_error)| SyscallError { call: "bpf_map_update_elem", io_error, }, )?; Ok(()) } /// Removes a key from the map. pub fn remove(&mut self, key: &K) -> Result<(), MapError> { hash_map::remove(self.inner.borrow_mut(), key) } } impl, K: Pod, V: Pod> IterableMap> for PerCpuHashMap { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, key: &K) -> Result, MapError> { Self::get(self, key, 0) } } #[cfg(test)] mod tests { use super::*; use crate::{ generated::bpf_map_type::{BPF_MAP_TYPE_LRU_PERCPU_HASH, BPF_MAP_TYPE_PERCPU_HASH}, maps::{test_utils, Map}, }; #[test] fn test_try_from_ok() { let map = Map::PerCpuHashMap(test_utils::new_map(test_utils::new_obj_map::( BPF_MAP_TYPE_PERCPU_HASH, ))); assert!(PerCpuHashMap::<_, u32, u32>::try_from(&map).is_ok()) } #[test] fn test_try_from_ok_lru() { let map_data = || test_utils::new_map(test_utils::new_obj_map::(BPF_MAP_TYPE_LRU_PERCPU_HASH)); let map = Map::PerCpuHashMap(map_data()); assert!(PerCpuHashMap::<_, u32, u32>::try_from(&map).is_ok()); let map = Map::PerCpuLruHashMap(map_data()); assert!(PerCpuHashMap::<_, u32, u32>::try_from(&map).is_ok()) } } aya-0.13.1/src/maps/info.rs000064400000000000000000000356251046102023000135230ustar 00000000000000//! Metadata information about an eBPF map. use std::{ ffi::CString, os::fd::{AsFd as _, BorrowedFd}, path::Path, }; use aya_obj::generated::{bpf_map_info, bpf_map_type}; use super::{MapError, MapFd}; use crate::{ sys::{ bpf_get_object, bpf_map_get_fd_by_id, bpf_map_get_info_by_fd, iter_map_ids, SyscallError, }, util::bytes_of_bpf_name, FEATURES, }; /// Provides Provides metadata information about a loaded eBPF map. /// /// Introduced in kernel v4.13. #[doc(alias = "bpf_map_info")] #[derive(Debug)] pub struct MapInfo(pub(crate) bpf_map_info); impl MapInfo { pub(crate) fn new_from_fd(fd: BorrowedFd<'_>) -> Result { let info = bpf_map_get_info_by_fd(fd.as_fd())?; Ok(Self(info)) } /// Loads map info from a map ID. /// /// Uses kernel v4.13 features. pub fn from_id(id: u32) -> Result { bpf_map_get_fd_by_id(id) .map_err(MapError::from) .and_then(|fd| Self::new_from_fd(fd.as_fd())) } /// The type of map. /// /// Introduced in kernel v4.13. pub fn map_type(&self) -> Result { bpf_map_type::try_from(self.0.type_) .unwrap_or(bpf_map_type::__MAX_BPF_MAP_TYPE) .try_into() } /// The unique ID for this map. /// /// Introduced in kernel v4.13. pub fn id(&self) -> u32 { self.0.id } /// The key size for this map in bytes. /// /// Introduced in kernel v4.13. pub fn key_size(&self) -> u32 { self.0.key_size } /// The value size for this map in bytes. /// /// Introduced in kernel v4.13. pub fn value_size(&self) -> u32 { self.0.value_size } /// The maximum number of entries in this map. /// /// Introduced in kernel v4.13. pub fn max_entries(&self) -> u32 { self.0.max_entries } /// The flags used in loading this map. /// /// Introduced in kernel v4.13. pub fn map_flags(&self) -> u32 { self.0.map_flags } /// The name of the map, limited to 16 bytes. /// /// Introduced in kernel v4.15. pub fn name(&self) -> &[u8] { bytes_of_bpf_name(&self.0.name) } /// The name of the map as a &str. /// /// `None` is returned if the name was not valid unicode or if field is not available. /// /// Introduced in kernel v4.15. pub fn name_as_str(&self) -> Option<&str> { let name = std::str::from_utf8(self.name()).ok()?; // Char in program name was introduced in the same commit as map name (FEATURES.bpf_name() || !name.is_empty()).then_some(name) } /// Returns a file descriptor referencing the map. /// /// The returned file descriptor can be closed at any time and doing so does /// not influence the life cycle of the map. /// /// Uses kernel v4.13 features. pub fn fd(&self) -> Result { let Self(info) = self; let fd = bpf_map_get_fd_by_id(info.id)?; Ok(MapFd::from_fd(fd)) } /// Loads a map from a pinned path in bpffs. /// /// Uses kernel v4.4 and v4.13 features. pub fn from_pin>(path: P) -> Result { use std::os::unix::ffi::OsStrExt as _; // TODO: avoid this unwrap by adding a new error variant. let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { call: "BPF_OBJ_GET", io_error, })?; Self::new_from_fd(fd.as_fd()) } } /// Returns an iterator of [`MapInfo`] over all eBPF maps on the host. /// /// Unlike [`Ebpf::maps`](crate::Ebpf::maps), this includes all maps on the host system, not /// just those tied to a specific [`crate::Ebpf`] instance. /// /// Uses kernel v4.13 features. /// /// # Example /// ``` /// # use aya::maps::loaded_maps; /// # /// for m in loaded_maps() { /// match m { /// Ok(map) => println!("{:?}", map.name_as_str()), /// Err(e) => println!("Error iterating maps: {:?}", e), /// } /// } /// ``` /// /// # Errors /// /// Returns [`MapError::SyscallError`] if any of the syscalls required to either get /// next map id, get the map fd, or the [`MapInfo`] fail. /// /// In cases where iteration can't be performed, for example the caller does not have the necessary /// privileges, a single item will be yielded containing the error that occurred. pub fn loaded_maps() -> impl Iterator> { iter_map_ids().map(|id| { let id = id?; MapInfo::from_id(id) }) } /// The type of eBPF map. #[non_exhaustive] #[doc(alias = "bpf_map_type")] #[derive(Copy, Clone, Debug, PartialEq)] pub enum MapType { /// An unspecified program type. Unspecified = bpf_map_type::BPF_MAP_TYPE_UNSPEC as isize, /// A Hash map type. See [`HashMap`](super::hash_map::HashMap) for the map implementation. /// /// Introduced in kernel v3.19. #[doc(alias = "BPF_MAP_TYPE_HASH")] Hash = bpf_map_type::BPF_MAP_TYPE_HASH as isize, /// An Array map type. See [`Array`](super::array::Array) for the map implementation. /// /// Introduced in kernel v3.19. #[doc(alias = "BPF_MAP_TYPE_ARRAY")] Array = bpf_map_type::BPF_MAP_TYPE_ARRAY as isize, /// A Program Array map type. See [`ProgramArray`](super::array::ProgramArray) for the map /// implementation. /// /// Introduced in kernel v4.2. #[doc(alias = "BPF_MAP_TYPE_PROG_ARRAY")] ProgramArray = bpf_map_type::BPF_MAP_TYPE_PROG_ARRAY as isize, /// A Perf Event Array map type. See [`PerfEventArray`](super::perf::PerfEventArray) and /// [`AsyncPerfEventArray`](super::perf::AsyncPerfEventArray) for the map implementations. /// /// Introduced in kernel v4.3. #[doc(alias = "BPF_MAP_TYPE_PERF_EVENT_ARRAY")] PerfEventArray = bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY as isize, /// A per-CPU Hash map type. See [`PerCpuHashMap`](super::hash_map::PerCpuHashMap) for the map /// implementation. /// /// Introduced in kernel v4.6. #[doc(alias = "BPF_MAP_TYPE_PERCPU_HASH")] PerCpuHash = bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH as isize, /// A per-CPU Array map type. See [`PerCpuArray`](super::array::PerCpuArray) for the map /// implementation. /// /// Introduced in kernel v4.6. #[doc(alias = "BPF_MAP_TYPE_PERCPU_ARRAY")] PerCpuArray = bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY as isize, /// A Stack Trace map type. See [`StackTraceMap`](super::stack_trace::StackTraceMap) for the map /// implementation. /// /// Introduced in kernel v4.6. #[doc(alias = "BPF_MAP_TYPE_STACK_TRACE")] StackTrace = bpf_map_type::BPF_MAP_TYPE_STACK_TRACE as isize, /// A cGroup Array map type. /// /// Introduced in kernel v4.8. #[doc(alias = "BPF_MAP_TYPE_CGROUP_ARRAY")] CgroupArray = bpf_map_type::BPF_MAP_TYPE_CGROUP_ARRAY as isize, /// A Least Recently Used (LRU) Hash map type. See [`HashMap`](super::hash_map::HashMap) for /// the map implementation. /// /// Introduced in kernel v4.10. #[doc(alias = "BPF_MAP_TYPE_LRU_HASH")] LruHash = bpf_map_type::BPF_MAP_TYPE_LRU_HASH as isize, /// A Least Recently Used (LRU) per-CPU Hash map type. See /// [`PerCpuHashMap`](super::hash_map::PerCpuHashMap) for the map implementation. /// /// Introduced in kernel v4.10. #[doc(alias = "BPF_MAP_TYPE_LRU_PERCPU_HASH")] LruPerCpuHash = bpf_map_type::BPF_MAP_TYPE_LRU_PERCPU_HASH as isize, /// A Longest Prefix Match (LPM) Trie map type. See [`LpmTrie`](super::lpm_trie::LpmTrie) for /// the map implementation. /// /// Introduced in kernel v4.11. #[doc(alias = "BPF_MAP_TYPE_LPM_TRIE")] LpmTrie = bpf_map_type::BPF_MAP_TYPE_LPM_TRIE as isize, /// An Array of Maps map type. /// /// Introduced in kernel v4.12. #[doc(alias = "BPF_MAP_TYPE_ARRAY_OF_MAPS")] ArrayOfMaps = bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS as isize, /// A Hash of Maps map type. /// /// Introduced in kernel v4.12. #[doc(alias = "BPF_MAP_TYPE_HASH_OF_MAPS")] HashOfMaps = bpf_map_type::BPF_MAP_TYPE_HASH_OF_MAPS as isize, /// A Device Map type. See [`DevMap`](super::xdp::DevMap) for the map implementation. /// /// Introduced in kernel v4.14. #[doc(alias = "BPF_MAP_TYPE_DEVMAP")] DevMap = bpf_map_type::BPF_MAP_TYPE_DEVMAP as isize, /// A Socket Map type. See [`SockMap`](super::sock::SockMap) for the map implementation. /// /// Introduced in kernel v4.14. #[doc(alias = "BPF_MAP_TYPE_SOCKMAP")] SockMap = bpf_map_type::BPF_MAP_TYPE_SOCKMAP as isize, /// A CPU Map type. See [`CpuMap`](super::xdp::CpuMap) for the map implementation. /// /// Introduced in kernel v4.15. #[doc(alias = "BPF_MAP_TYPE_CPUMAP")] CpuMap = bpf_map_type::BPF_MAP_TYPE_CPUMAP as isize, /// An XDP Socket Map type. See [`XskMap`](super::xdp::XskMap) for the map implementation. /// /// Introduced in kernel v4.18. #[doc(alias = "BPF_MAP_TYPE_XSKMAP")] XskMap = bpf_map_type::BPF_MAP_TYPE_XSKMAP as isize, /// A Socket Hash map type. See [`SockHash`](super::sock::SockHash) for the map implementation. /// /// Introduced in kernel v4.18. #[doc(alias = "BPF_MAP_TYPE_SOCKHASH")] SockHash = bpf_map_type::BPF_MAP_TYPE_SOCKHASH as isize, /// A cGroup Storage map type. /// /// Introduced in kernel v4.19. // #[deprecated] #[doc(alias = "BPF_MAP_TYPE_CGROUP_STORAGE")] #[doc(alias = "BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED")] CgroupStorage = bpf_map_type::BPF_MAP_TYPE_CGROUP_STORAGE as isize, /// A Reuseport Socket Array map type. /// /// Introduced in kernel v4.19. #[doc(alias = "BPF_MAP_TYPE_REUSEPORT_SOCKARRAY")] ReuseportSockArray = bpf_map_type::BPF_MAP_TYPE_REUSEPORT_SOCKARRAY as isize, /// A per-CPU cGroup Storage map type. /// /// Introduced in kernel v4.20. #[doc(alias = "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE")] #[doc(alias = "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED")] PerCpuCgroupStorage = bpf_map_type::BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE as isize, /// A Queue map type. See [`Queue`](super::queue::Queue) for the map implementation. /// /// Introduced in kernel v4.20. #[doc(alias = "BPF_MAP_TYPE_QUEUE")] Queue = bpf_map_type::BPF_MAP_TYPE_QUEUE as isize, /// A Stack map type. See [`Stack`](super::stack::Stack) for the map implementation. /// /// Introduced in kernel v4.20. #[doc(alias = "BPF_MAP_TYPE_STACK")] Stack = bpf_map_type::BPF_MAP_TYPE_STACK as isize, /// A Socket-local Storage map type. /// /// Introduced in kernel v5.2. #[doc(alias = "BPF_MAP_TYPE_SK_STORAGE")] SkStorage = bpf_map_type::BPF_MAP_TYPE_SK_STORAGE as isize, /// A Device Hash Map type. See [`DevMapHash`](super::xdp::DevMapHash) for the map /// implementation. /// /// Introduced in kernel v5.4. #[doc(alias = "BPF_MAP_TYPE_DEVMAP_HASH")] DevMapHash = bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH as isize, /// A Struct Ops map type. /// /// Introduced in kernel v5.6. #[doc(alias = "BPF_MAP_TYPE_STRUCT_OPS")] StructOps = bpf_map_type::BPF_MAP_TYPE_STRUCT_OPS as isize, /// A Ring Buffer map type. See [`RingBuf`](super::ring_buf::RingBuf) for the map /// implementation. /// /// Introduced in kernel v5.8. #[doc(alias = "BPF_MAP_TYPE_RINGBUF")] RingBuf = bpf_map_type::BPF_MAP_TYPE_RINGBUF as isize, /// An Inode Storage map type. /// /// Introduced in kernel v5.10. #[doc(alias = "BPF_MAP_TYPE_INODE_STORAGE")] InodeStorage = bpf_map_type::BPF_MAP_TYPE_INODE_STORAGE as isize, /// A Task Storage map type. /// /// Introduced in kernel v5.11. #[doc(alias = "BPF_MAP_TYPE_TASK_STORAGE")] TaskStorage = bpf_map_type::BPF_MAP_TYPE_TASK_STORAGE as isize, /// A Bloom Filter map type. See [`BloomFilter`](super::bloom_filter::BloomFilter) for the map /// implementation. /// /// Introduced in kernel v5.16. #[doc(alias = "BPF_MAP_TYPE_BLOOM_FILTER")] BloomFilter = bpf_map_type::BPF_MAP_TYPE_BLOOM_FILTER as isize, /// A User Ring Buffer map type. /// /// Introduced in kernel v6.1. #[doc(alias = "BPF_MAP_TYPE_USER_RINGBUF")] UserRingBuf = bpf_map_type::BPF_MAP_TYPE_USER_RINGBUF as isize, /// A cGroup Storage map type. /// /// Introduced in kernel v6.2. #[doc(alias = "BPF_MAP_TYPE_CGRP_STORAGE")] CgrpStorage = bpf_map_type::BPF_MAP_TYPE_CGRP_STORAGE as isize, /// An Arena map type. /// /// Introduced in kernel v6.9. #[doc(alias = "BPF_MAP_TYPE_ARENA")] Arena = bpf_map_type::BPF_MAP_TYPE_ARENA as isize, } impl TryFrom for MapType { type Error = MapError; fn try_from(map_type: bpf_map_type) -> Result { use bpf_map_type::*; Ok(match map_type { BPF_MAP_TYPE_UNSPEC => Self::Unspecified, BPF_MAP_TYPE_HASH => Self::Hash, BPF_MAP_TYPE_ARRAY => Self::Array, BPF_MAP_TYPE_PROG_ARRAY => Self::ProgramArray, BPF_MAP_TYPE_PERF_EVENT_ARRAY => Self::PerfEventArray, BPF_MAP_TYPE_PERCPU_HASH => Self::PerCpuHash, BPF_MAP_TYPE_PERCPU_ARRAY => Self::PerCpuArray, BPF_MAP_TYPE_STACK_TRACE => Self::StackTrace, BPF_MAP_TYPE_CGROUP_ARRAY => Self::CgroupArray, BPF_MAP_TYPE_LRU_HASH => Self::LruHash, BPF_MAP_TYPE_LRU_PERCPU_HASH => Self::LruPerCpuHash, BPF_MAP_TYPE_LPM_TRIE => Self::LpmTrie, BPF_MAP_TYPE_ARRAY_OF_MAPS => Self::ArrayOfMaps, BPF_MAP_TYPE_HASH_OF_MAPS => Self::HashOfMaps, BPF_MAP_TYPE_DEVMAP => Self::DevMap, BPF_MAP_TYPE_SOCKMAP => Self::SockMap, BPF_MAP_TYPE_CPUMAP => Self::CpuMap, BPF_MAP_TYPE_XSKMAP => Self::XskMap, BPF_MAP_TYPE_SOCKHASH => Self::SockHash, BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED => Self::CgroupStorage, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY => Self::ReuseportSockArray, BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE_DEPRECATED => Self::PerCpuCgroupStorage, BPF_MAP_TYPE_QUEUE => Self::Queue, BPF_MAP_TYPE_STACK => Self::Stack, BPF_MAP_TYPE_SK_STORAGE => Self::SkStorage, BPF_MAP_TYPE_DEVMAP_HASH => Self::DevMapHash, BPF_MAP_TYPE_STRUCT_OPS => Self::StructOps, BPF_MAP_TYPE_RINGBUF => Self::RingBuf, BPF_MAP_TYPE_INODE_STORAGE => Self::InodeStorage, BPF_MAP_TYPE_TASK_STORAGE => Self::TaskStorage, BPF_MAP_TYPE_BLOOM_FILTER => Self::BloomFilter, BPF_MAP_TYPE_USER_RINGBUF => Self::UserRingBuf, BPF_MAP_TYPE_CGRP_STORAGE => Self::CgrpStorage, BPF_MAP_TYPE_ARENA => Self::Arena, __MAX_BPF_MAP_TYPE => { return Err(MapError::InvalidMapType { map_type: map_type as u32, }) } }) } } aya-0.13.1/src/maps/lpm_trie.rs000064400000000000000000000252601046102023000143750ustar 00000000000000//! A LPM Trie. use std::{ borrow::{Borrow, BorrowMut}, marker::PhantomData, os::fd::AsFd as _, }; use crate::{ maps::{check_kv_size, IterableMap, MapData, MapError, MapIter, MapKeys}, sys::{bpf_map_delete_elem, bpf_map_lookup_elem, bpf_map_update_elem, SyscallError}, Pod, }; /// A Longest Prefix Match Trie. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.20. /// /// # Examples /// /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::lpm_trie::{LpmTrie, Key}; /// use std::net::Ipv4Addr; /// /// let mut trie = LpmTrie::try_from(bpf.map_mut("LPM_TRIE").unwrap())?; /// let ipaddr = Ipv4Addr::new(8, 8, 8, 8); /// // The following represents a key for the "8.8.8.8/16" subnet. /// // The first argument - the prefix length - represents how many bits should be matched against. The second argument is the actual data to be matched. /// let key = Key::new(16, u32::from(ipaddr).to_be()); /// trie.insert(&key, 1, 0)?; /// /// // LpmTrie matches against the longest (most accurate) key. /// let lookup = Key::new(32, u32::from(ipaddr).to_be()); /// let value = trie.get(&lookup, 0)?; /// assert_eq!(value, 1); /// /// // If we were to insert a key with longer 'prefix_len' /// // our trie should match against it. /// let longer_key = Key::new(24, u32::from(ipaddr).to_be()); /// trie.insert(&longer_key, 2, 0)?; /// let value = trie.get(&lookup, 0)?; /// assert_eq!(value, 2); /// # Ok::<(), aya::EbpfError>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_LPM_TRIE")] #[derive(Debug)] pub struct LpmTrie { pub(crate) inner: T, _k: PhantomData, _v: PhantomData, } /// A Key for an LpmTrie map. /// /// # Examples /// /// ```no_run /// use aya::maps::lpm_trie::{LpmTrie, Key}; /// use std::net::Ipv4Addr; /// /// let ipaddr = Ipv4Addr::new(8,8,8,8); /// let key = Key::new(16, u32::from(ipaddr).to_be()); /// ``` #[repr(packed)] pub struct Key { prefix_len: u32, data: K, } impl Key { /// Creates a new key. /// /// `prefix_len` is the number of bits in the data to match against. /// `data` is the data in the key which is typically an IPv4 or IPv6 address. /// If using a key to perform a longest prefix match on you would use a `prefix_len` /// of 32 for IPv4 and 128 for IPv6. /// /// # Examples /// /// ```no_run /// use aya::maps::lpm_trie::{LpmTrie, Key}; /// use std::net::Ipv4Addr; /// /// let ipaddr = Ipv4Addr::new(8, 8, 8, 8); /// let key = Key::new(16, u32::from(ipaddr).to_be()); /// ``` pub fn new(prefix_len: u32, data: K) -> Self { Self { prefix_len, data } } /// Returns the number of bits in the data to be matched. pub fn prefix_len(&self) -> u32 { self.prefix_len } /// Returns the data stored in the Key. pub fn data(&self) -> K { self.data } } impl Copy for Key {} impl Clone for Key { fn clone(&self) -> Self { *self } } // A Pod impl is required as Key struct is a key for a map. unsafe impl Pod for Key {} impl, K: Pod, V: Pod> LpmTrie { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::, V>(data)?; Ok(Self { inner: map, _k: PhantomData, _v: PhantomData, }) } /// Returns a copy of the value associated with the longest prefix matching key in the LpmTrie. pub fn get(&self, key: &Key, flags: u64) -> Result { let fd = self.inner.borrow().fd().as_fd(); let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })?; value.ok_or(MapError::KeyNotFound) } /// An iterator visiting all key-value pairs. The /// iterator item type is `Result<(K, V), MapError>`. pub fn iter(&self) -> MapIter<'_, Key, V, Self> { MapIter::new(self) } /// An iterator visiting all keys. The iterator element /// type is `Result, MapError>`. pub fn keys(&self) -> MapKeys<'_, Key> { MapKeys::new(self.inner.borrow()) } } impl, K: Pod, V: Pod> LpmTrie { /// Inserts a key value pair into the map. pub fn insert( &mut self, key: &Key, value: impl Borrow, flags: u64, ) -> Result<(), MapError> { let fd = self.inner.borrow().fd().as_fd(); bpf_map_update_elem(fd, Some(key), value.borrow(), flags).map_err(|(_, io_error)| { SyscallError { call: "bpf_map_update_elem", io_error, } })?; Ok(()) } /// Removes an element from the map. /// /// Both the prefix and data must match exactly - this method does not do a longest prefix match. pub fn remove(&mut self, key: &Key) -> Result<(), MapError> { let fd = self.inner.borrow().fd().as_fd(); bpf_map_delete_elem(fd, key) .map(|_| ()) .map_err(|(_, io_error)| { SyscallError { call: "bpf_map_delete_elem", io_error, } .into() }) } } impl, K: Pod, V: Pod> IterableMap, V> for LpmTrie { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, key: &Key) -> Result { self.get(key, 0) } } #[cfg(test)] mod tests { use std::{io, net::Ipv4Addr}; use assert_matches::assert_matches; use libc::{EFAULT, ENOENT}; use super::*; use crate::{ generated::{ bpf_cmd, bpf_map_type::{BPF_MAP_TYPE_ARRAY, BPF_MAP_TYPE_LPM_TRIE}, }, maps::{ test_utils::{self, new_map}, Map, }, obj, sys::{override_syscall, SysResult, Syscall}, }; fn new_obj_map() -> obj::Map { test_utils::new_obj_map::>(BPF_MAP_TYPE_LPM_TRIE) } fn sys_error(value: i32) -> SysResult { Err((-1, io::Error::from_raw_os_error(value))) } #[test] fn test_wrong_key_size() { let map = new_map(new_obj_map()); assert_matches!( LpmTrie::<_, u16, u32>::new(&map), Err(MapError::InvalidKeySize { size: 6, expected: 8 // four bytes for prefixlen and four bytes for data. }) ); } #[test] fn test_wrong_value_size() { let map = new_map(new_obj_map()); assert_matches!( LpmTrie::<_, u32, u16>::new(&map), Err(MapError::InvalidValueSize { size: 2, expected: 4 }) ); } #[test] fn test_try_from_wrong_map() { // Use any map type here other than BPF_MAP_TYPE_PERF_EVENT_ARRAY as it will trip miri let map = new_map(test_utils::new_obj_map::(BPF_MAP_TYPE_ARRAY)); let map = Map::Array(map); assert_matches!( LpmTrie::<_, u32, u32>::try_from(&map), Err(MapError::InvalidMapType { .. }) ); } #[test] fn test_new_ok() { let map = new_map(new_obj_map()); assert!(LpmTrie::<_, u32, u32>::new(&map).is_ok()); } #[test] fn test_try_from_ok() { let map = new_map(new_obj_map()); let map = Map::LpmTrie(map); assert!(LpmTrie::<_, u32, u32>::try_from(&map).is_ok()) } #[test] fn test_insert_syscall_error() { let mut map = new_map(new_obj_map()); let mut trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); let key = Key::new(16, u32::from(ipaddr).to_be()); override_syscall(|_| sys_error(EFAULT)); assert_matches!( trie.insert(&key, 1, 0), Err(MapError::SyscallError(SyscallError { call: "bpf_map_update_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT) ); } #[test] fn test_insert_ok() { let mut map = new_map(new_obj_map()); let mut trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); let key = Key::new(16, u32::from(ipaddr).to_be()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_UPDATE_ELEM, .. } => Ok(1), _ => sys_error(EFAULT), }); assert!(trie.insert(&key, 1, 0).is_ok()); } #[test] fn test_remove_syscall_error() { let mut map = new_map(new_obj_map()); let mut trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); let key = Key::new(16, u32::from(ipaddr).to_be()); override_syscall(|_| sys_error(EFAULT)); assert_matches!( trie.remove(&key), Err(MapError::SyscallError(SyscallError { call: "bpf_map_delete_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT) ); } #[test] fn test_remove_ok() { let mut map = new_map(new_obj_map()); let mut trie = LpmTrie::<_, u32, u32>::new(&mut map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); let key = Key::new(16, u32::from(ipaddr).to_be()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_DELETE_ELEM, .. } => Ok(1), _ => sys_error(EFAULT), }); assert!(trie.remove(&key).is_ok()); } #[test] fn test_get_syscall_error() { let map = new_map(new_obj_map()); let trie = LpmTrie::<_, u32, u32>::new(&map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); let key = Key::new(16, u32::from(ipaddr).to_be()); override_syscall(|_| sys_error(EFAULT)); assert_matches!( trie.get(&key, 0), Err(MapError::SyscallError(SyscallError { call: "bpf_map_lookup_elem", io_error })) if io_error.raw_os_error() == Some(EFAULT) ); } #[test] fn test_get_not_found() { let map = new_map(new_obj_map()); let trie = LpmTrie::<_, u32, u32>::new(&map).unwrap(); let ipaddr = Ipv4Addr::new(8, 8, 8, 8); let key = Key::new(16, u32::from(ipaddr).to_be()); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_LOOKUP_ELEM, .. } => sys_error(ENOENT), _ => sys_error(EFAULT), }); assert_matches!(trie.get(&key, 0), Err(MapError::KeyNotFound)); } } aya-0.13.1/src/maps/mod.rs000064400000000000000000001150551046102023000133430ustar 00000000000000//! Data structures used to setup and share data with eBPF programs. //! //! The eBPF platform provides data structures - maps in eBPF speak - that are //! used to setup and share data with eBPF programs. When you call //! [`Ebpf::load_file`](crate::Ebpf::load_file) or //! [`Ebpf::load`](crate::Ebpf::load), all the maps defined in the eBPF code get //! initialized and can then be accessed using [`Ebpf::map`](crate::Ebpf::map), //! [`Ebpf::map_mut`](crate::Ebpf::map_mut), or //! [`Ebpf::take_map`](crate::Ebpf::take_map). //! //! # Typed maps //! //! The eBPF API includes many map types each supporting different operations. //! [`Ebpf::map`](crate::Ebpf::map), [`Ebpf::map_mut`](crate::Ebpf::map_mut), and //! [`Ebpf::take_map`](crate::Ebpf::take_map) always return the opaque //! [`&Map`](crate::maps::Map), [`&mut Map`](crate::maps::Map), and [`Map`] //! types respectively. Those three types can be converted to *typed maps* using //! the [`TryFrom`] or [`TryInto`] trait. For example: //! //! ```no_run //! # #[derive(Debug, thiserror::Error)] //! # enum Error { //! # #[error(transparent)] //! # IO(#[from] std::io::Error), //! # #[error(transparent)] //! # Map(#[from] aya::maps::MapError), //! # #[error(transparent)] //! # Program(#[from] aya::programs::ProgramError), //! # #[error(transparent)] //! # Ebpf(#[from] aya::EbpfError) //! # } //! # let mut bpf = aya::Ebpf::load(&[])?; //! use aya::maps::SockMap; //! use aya::programs::SkMsg; //! //! let intercept_egress = SockMap::try_from(bpf.map_mut("INTERCEPT_EGRESS").unwrap())?; //! let map_fd = intercept_egress.fd().try_clone()?; //! let prog: &mut SkMsg = bpf.program_mut("intercept_egress_packet").unwrap().try_into()?; //! prog.load()?; //! prog.attach(&map_fd)?; //! //! # Ok::<(), Error>(()) //! ``` //! //! # Maps and `Pod` values //! //! Many map operations copy data from kernel space to user space and vice //! versa. Because of that, all map values must be plain old data and therefore //! implement the [Pod] trait. use std::{ borrow::Borrow, ffi::CString, fmt, io, marker::PhantomData, mem, ops::Deref, os::fd::{AsFd, BorrowedFd, OwnedFd}, path::Path, ptr, }; use aya_obj::{generated::bpf_map_type, InvalidTypeBinding}; use libc::{getrlimit, rlim_t, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY}; use log::warn; use thiserror::Error; use crate::{ obj::{self, parse_map_info, EbpfSectionKind}, pin::PinError, sys::{ bpf_create_map, bpf_get_object, bpf_map_freeze, bpf_map_get_fd_by_id, bpf_map_get_next_key, bpf_map_update_elem_ptr, bpf_pin_object, SyscallError, }, util::{nr_cpus, KernelVersion}, PinningType, Pod, }; pub mod array; pub mod bloom_filter; pub mod hash_map; mod info; pub mod lpm_trie; pub mod perf; pub mod queue; pub mod ring_buf; pub mod sock; pub mod stack; pub mod stack_trace; pub mod xdp; pub use array::{Array, PerCpuArray, ProgramArray}; pub use bloom_filter::BloomFilter; pub use hash_map::{HashMap, PerCpuHashMap}; pub use info::{loaded_maps, MapInfo, MapType}; pub use lpm_trie::LpmTrie; #[cfg(any(feature = "async_tokio", feature = "async_std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "async_tokio", feature = "async_std"))))] pub use perf::AsyncPerfEventArray; pub use perf::PerfEventArray; pub use queue::Queue; pub use ring_buf::RingBuf; pub use sock::{SockHash, SockMap}; pub use stack::Stack; pub use stack_trace::StackTraceMap; pub use xdp::{CpuMap, DevMap, DevMapHash, XskMap}; #[derive(Error, Debug)] /// Errors occuring from working with Maps pub enum MapError { /// Invalid map type encontered #[error("invalid map type {map_type}")] InvalidMapType { /// The map type map_type: u32, }, /// Invalid map name encountered #[error("invalid map name `{name}`")] InvalidName { /// The map name name: String, }, /// Failed to create map #[error("failed to create map `{name}` with code {code}")] CreateError { /// Map name name: String, /// Error code code: i64, #[source] /// Original io::Error io_error: io::Error, }, /// Invalid key size #[error("invalid key size {size}, expected {expected}")] InvalidKeySize { /// Size encountered size: usize, /// Size expected expected: usize, }, /// Invalid value size #[error("invalid value size {size}, expected {expected}")] InvalidValueSize { /// Size encountered size: usize, /// Size expected expected: usize, }, /// Index is out of bounds #[error("the index is {index} but `max_entries` is {max_entries}")] OutOfBounds { /// Index accessed index: u32, /// Map size max_entries: u32, }, /// Key not found #[error("key not found")] KeyNotFound, /// Element not found #[error("element not found")] ElementNotFound, /// Progam Not Loaded #[error("the program is not loaded")] ProgramNotLoaded, /// An IO error occurred #[error(transparent)] IoError(#[from] io::Error), /// Syscall failed #[error(transparent)] SyscallError(#[from] SyscallError), /// Could not pin map #[error("map `{name:?}` requested pinning. pinning failed")] PinError { /// The map name name: Option, /// The reason for the failure #[source] error: PinError, }, /// Program IDs are not supported #[error("program ids are not supported by the current kernel")] ProgIdNotSupported, /// Unsupported Map type #[error("Unsupported map type found {map_type}")] Unsupported { /// The map type map_type: u32, }, } impl From> for MapError { fn from(e: InvalidTypeBinding) -> Self { let InvalidTypeBinding { value } = e; Self::InvalidMapType { map_type: value } } } /// A map file descriptor. #[derive(Debug)] pub struct MapFd { fd: crate::MockableFd, } impl MapFd { fn from_fd(fd: crate::MockableFd) -> Self { Self { fd } } fn try_clone(&self) -> io::Result { let Self { fd } = self; let fd = fd.try_clone()?; Ok(Self { fd }) } } impl AsFd for MapFd { fn as_fd(&self) -> BorrowedFd<'_> { let Self { fd } = self; fd.as_fd() } } /// Raises a warning about rlimit. Should be used only if creating a map was not /// successful. fn maybe_warn_rlimit() { let mut limit = mem::MaybeUninit::::uninit(); let ret = unsafe { getrlimit(RLIMIT_MEMLOCK, limit.as_mut_ptr()) }; if ret == 0 { let limit = unsafe { limit.assume_init() }; if limit.rlim_cur == RLIM_INFINITY { return; } struct HumanSize(rlim_t); impl fmt::Display for HumanSize { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let &Self(size) = self; if size < 1024 { write!(f, "{} bytes", size) } else if size < 1024 * 1024 { write!(f, "{} KiB", size / 1024) } else { write!(f, "{} MiB", size / 1024 / 1024) } } } warn!( "RLIMIT_MEMLOCK value is {}, not RLIM_INFINITY; if experiencing problems with creating \ maps, try raising RLIMIT_MEMLOCK either to RLIM_INFINITY or to a higher value sufficient \ for the size of your maps", HumanSize(limit.rlim_cur) ); } } /// eBPF map types. #[derive(Debug)] pub enum Map { /// An [`Array`] map. Array(MapData), /// A [`BloomFilter`] map. BloomFilter(MapData), /// A [`CpuMap`] map. CpuMap(MapData), /// A [`DevMap`] map. DevMap(MapData), /// A [`DevMapHash`] map. DevMapHash(MapData), /// A [`HashMap`] map. HashMap(MapData), /// A [`LpmTrie`] map. LpmTrie(MapData), /// A [`HashMap`] map that uses a LRU eviction policy. LruHashMap(MapData), /// A [`PerCpuArray`] map. PerCpuArray(MapData), /// A [`PerCpuHashMap`] map. PerCpuHashMap(MapData), /// A [`PerCpuHashMap`] map that uses a LRU eviction policy. PerCpuLruHashMap(MapData), /// A [`PerfEventArray`] map. PerfEventArray(MapData), /// A [`ProgramArray`] map. ProgramArray(MapData), /// A [`Queue`] map. Queue(MapData), /// A [`RingBuf`] map. RingBuf(MapData), /// A [`SockHash`] map SockHash(MapData), /// A [`SockMap`] map. SockMap(MapData), /// A [`Stack`] map. Stack(MapData), /// A [`StackTraceMap`] map. StackTraceMap(MapData), /// An unsupported map type. Unsupported(MapData), /// A [`XskMap`] map. XskMap(MapData), } impl Map { /// Returns the low level map type. fn map_type(&self) -> u32 { match self { Self::Array(map) => map.obj.map_type(), Self::BloomFilter(map) => map.obj.map_type(), Self::CpuMap(map) => map.obj.map_type(), Self::DevMap(map) => map.obj.map_type(), Self::DevMapHash(map) => map.obj.map_type(), Self::HashMap(map) => map.obj.map_type(), Self::LpmTrie(map) => map.obj.map_type(), Self::LruHashMap(map) => map.obj.map_type(), Self::PerCpuArray(map) => map.obj.map_type(), Self::PerCpuHashMap(map) => map.obj.map_type(), Self::PerCpuLruHashMap(map) => map.obj.map_type(), Self::PerfEventArray(map) => map.obj.map_type(), Self::ProgramArray(map) => map.obj.map_type(), Self::Queue(map) => map.obj.map_type(), Self::RingBuf(map) => map.obj.map_type(), Self::SockHash(map) => map.obj.map_type(), Self::SockMap(map) => map.obj.map_type(), Self::Stack(map) => map.obj.map_type(), Self::StackTraceMap(map) => map.obj.map_type(), Self::Unsupported(map) => map.obj.map_type(), Self::XskMap(map) => map.obj.map_type(), } } /// Pins the map to a BPF filesystem. /// /// When a map is pinned it will remain loaded until the corresponding file /// is deleted. All parent directories in the given `path` must already exist. pub fn pin>(&self, path: P) -> Result<(), PinError> { match self { Self::Array(map) => map.pin(path), Self::BloomFilter(map) => map.pin(path), Self::CpuMap(map) => map.pin(path), Self::DevMap(map) => map.pin(path), Self::DevMapHash(map) => map.pin(path), Self::HashMap(map) => map.pin(path), Self::LpmTrie(map) => map.pin(path), Self::LruHashMap(map) => map.pin(path), Self::PerCpuArray(map) => map.pin(path), Self::PerCpuHashMap(map) => map.pin(path), Self::PerCpuLruHashMap(map) => map.pin(path), Self::PerfEventArray(map) => map.pin(path), Self::ProgramArray(map) => map.pin(path), Self::Queue(map) => map.pin(path), Self::RingBuf(map) => map.pin(path), Self::SockHash(map) => map.pin(path), Self::SockMap(map) => map.pin(path), Self::Stack(map) => map.pin(path), Self::StackTraceMap(map) => map.pin(path), Self::Unsupported(map) => map.pin(path), Self::XskMap(map) => map.pin(path), } } } // Implements map pinning for different map implementations macro_rules! impl_map_pin { ($ty_param:tt { $($ty:ident),+ $(,)? }) => { $(impl_map_pin!(<$ty_param> $ty);)+ }; ( <($($ty_param:ident),*)> $ty:ident ) => { impl, $($ty_param: Pod),*> $ty { /// Pins the map to a BPF filesystem. /// /// When a map is pinned it will remain loaded until the corresponding file /// is deleted. All parent directories in the given `path` must already exist. pub fn pin>(self, path: P) -> Result<(), PinError> { let data = self.inner.borrow(); data.pin(path) } } }; } impl_map_pin!(() { ProgramArray, SockMap, StackTraceMap, CpuMap, DevMap, DevMapHash, XskMap, }); impl_map_pin!((V) { Array, PerCpuArray, SockHash, BloomFilter, Queue, Stack, }); impl_map_pin!((K, V) { HashMap, PerCpuHashMap, LpmTrie, }); // Implements TryFrom for different map implementations. Different map implementations can be // constructed from different variants of the map enum. Also, the implementation may have type // parameters (which we assume all have the bound `Pod` and nothing else). macro_rules! impl_try_from_map { // At the root the type parameters are marked as a single token tree which will be pasted into // the invocation for each type. Note that the later patterns require that the token tree be // zero or more comma separated idents wrapped in parens. Note that the tt metavar is used here // rather than the repeated idents used later because the macro language does not allow one // repetition to be pasted inside another. ($ty_param:tt { $($ty:ident $(from $($variant:ident)|+)?),+ $(,)? }) => { $(impl_try_from_map!(<$ty_param> $ty $(from $($variant)|+)?);)+ }; // Add the "from $variant" using $ty as the default if it is missing. (<$ty_param:tt> $ty:ident) => { impl_try_from_map!(<$ty_param> $ty from $ty); }; // Dispatch for each of the lifetimes. ( <($($ty_param:ident),*)> $ty:ident from $($variant:ident)|+ ) => { impl_try_from_map!(<'a> ($($ty_param),*) $ty from $($variant)|+); impl_try_from_map!(<'a mut> ($($ty_param),*) $ty from $($variant)|+); impl_try_from_map!(<> ($($ty_param),*) $ty from $($variant)|+); }; // An individual impl. ( <$($l:lifetime $($m:ident)?)?> ($($ty_param:ident),*) $ty:ident from $($variant:ident)|+ ) => { impl<$($l,)? $($ty_param: Pod),*> TryFrom<$(&$l $($m)?)? Map> for $ty<$(&$l $($m)?)? MapData, $($ty_param),*> { type Error = MapError; fn try_from(map: $(&$l $($m)?)? Map) -> Result { match map { $(Map::$variant(map_data) => Self::new(map_data),)+ map => Err(MapError::InvalidMapType { map_type: map.map_type() }), } } } }; } impl_try_from_map!(() { CpuMap, DevMap, DevMapHash, PerfEventArray, ProgramArray, RingBuf, SockMap, StackTraceMap, XskMap, }); #[cfg(any(feature = "async_tokio", feature = "async_std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "async_tokio", feature = "async_std"))))] impl_try_from_map!(() { AsyncPerfEventArray from PerfEventArray, }); impl_try_from_map!((V) { Array, BloomFilter, PerCpuArray, Queue, SockHash, Stack, }); impl_try_from_map!((K, V) { HashMap from HashMap|LruHashMap, LpmTrie, PerCpuHashMap from PerCpuHashMap|PerCpuLruHashMap, }); pub(crate) fn check_bounds(map: &MapData, index: u32) -> Result<(), MapError> { let max_entries = map.obj.max_entries(); if index >= max_entries { Err(MapError::OutOfBounds { index, max_entries }) } else { Ok(()) } } pub(crate) fn check_kv_size(map: &MapData) -> Result<(), MapError> { let size = mem::size_of::(); let expected = map.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let size = mem::size_of::(); let expected = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); }; Ok(()) } pub(crate) fn check_v_size(map: &MapData) -> Result<(), MapError> { let size = mem::size_of::(); let expected = map.obj.value_size() as usize; if size != expected { return Err(MapError::InvalidValueSize { size, expected }); }; Ok(()) } /// A generic handle to a BPF map. /// /// You should never need to use this unless you're implementing a new map type. #[derive(Debug)] pub struct MapData { obj: obj::Map, fd: MapFd, } impl MapData { /// Creates a new map with the provided `name` pub fn create( mut obj: obj::Map, name: &str, btf_fd: Option>, ) -> Result { let c_name = CString::new(name).map_err(|_| MapError::InvalidName { name: name.into() })?; // BPF_MAP_TYPE_PERF_EVENT_ARRAY's max_entries should not exceed the number of // CPUs. // // By default, the newest versions of Aya, libbpf and cilium/ebpf define `max_entries` of // `PerfEventArray` as `0`, with an intention to get it replaced with a correct value // by the loader. // // We allow custom values (potentially coming either from older versions of aya-ebpf or // programs written in C) as long as they don't exceed the number of CPUs. // // Otherwise, when the value is `0` or too large, we set it to the number of CPUs. if obj.map_type() == bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32 { let nr_cpus = nr_cpus().map_err(|(_, error)| MapError::IoError(error))? as u32; if obj.max_entries() == 0 || obj.max_entries() > nr_cpus { obj.set_max_entries(nr_cpus); } }; #[cfg(not(test))] let kernel_version = KernelVersion::current().unwrap(); #[cfg(test)] let kernel_version = KernelVersion::new(0xff, 0xff, 0xff); let fd = bpf_create_map(&c_name, &obj, btf_fd, kernel_version).map_err(|(code, io_error)| { if kernel_version < KernelVersion::new(5, 11, 0) { maybe_warn_rlimit(); } MapError::CreateError { name: name.into(), code, io_error, } })?; Ok(Self { obj, fd: MapFd::from_fd(fd), }) } pub(crate) fn create_pinned_by_name>( path: P, obj: obj::Map, name: &str, btf_fd: Option>, ) -> Result { use std::os::unix::ffi::OsStrExt as _; // try to open map in case it's already pinned let path = path.as_ref().join(name); let path_string = match CString::new(path.as_os_str().as_bytes()) { Ok(path) => path, Err(error) => { return Err(MapError::PinError { name: Some(name.into()), error: PinError::InvalidPinPath { path, error }, }); } }; match bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { call: "BPF_OBJ_GET", io_error, }) { Ok(fd) => Ok(Self { obj, fd: MapFd::from_fd(fd), }), Err(_) => { let map = Self::create(obj, name, btf_fd)?; map.pin(&path).map_err(|error| MapError::PinError { name: Some(name.into()), error, })?; Ok(map) } } } pub(crate) fn finalize(&mut self) -> Result<(), MapError> { let Self { obj, fd } = self; if !obj.data().is_empty() { bpf_map_update_elem_ptr(fd.as_fd(), &0 as *const _, obj.data_mut().as_mut_ptr(), 0) .map_err(|(_, io_error)| SyscallError { call: "bpf_map_update_elem", io_error, }) .map_err(MapError::from)?; } if obj.section_kind() == EbpfSectionKind::Rodata { bpf_map_freeze(fd.as_fd()) .map_err(|(_, io_error)| SyscallError { call: "bpf_map_freeze", io_error, }) .map_err(MapError::from)?; } Ok(()) } /// Loads a map from a pinned path in bpffs. pub fn from_pin>(path: P) -> Result { use std::os::unix::ffi::OsStrExt as _; let path = path.as_ref(); let path_string = CString::new(path.as_os_str().as_bytes()).map_err(|error| MapError::PinError { name: None, error: PinError::InvalidPinPath { path: path.into(), error, }, })?; let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { call: "BPF_OBJ_GET", io_error, })?; Self::from_fd_inner(fd) } /// Loads a map from a map id. pub fn from_id(id: u32) -> Result { let fd = bpf_map_get_fd_by_id(id)?; Self::from_fd_inner(fd) } fn from_fd_inner(fd: crate::MockableFd) -> Result { let MapInfo(info) = MapInfo::new_from_fd(fd.as_fd())?; Ok(Self { obj: parse_map_info(info, PinningType::None), fd: MapFd::from_fd(fd), }) } /// Loads a map from a file descriptor. /// /// If loading from a BPF Filesystem (bpffs) you should use [`Map::from_pin`](crate::maps::MapData::from_pin). /// This API is intended for cases where you have received a valid BPF FD from some other means. /// For example, you received an FD over Unix Domain Socket. pub fn from_fd(fd: OwnedFd) -> Result { let fd = crate::MockableFd::from_fd(fd); Self::from_fd_inner(fd) } /// Allows the map to be pinned to the provided path. /// /// Any directories in the the path provided should have been created by the caller. /// The path must be on a BPF filesystem. /// /// # Errors /// /// Returns a [`PinError::SyscallError`] if the underlying syscall fails. /// This may also happen if the path already exists, in which case the wrapped /// [`std::io::Error`] kind will be [`std::io::ErrorKind::AlreadyExists`]. /// Returns a [`PinError::InvalidPinPath`] if the path provided cannot be /// converted to a [`CString`]. /// /// # Example /// /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// # use aya::maps::MapData; /// /// let mut map = MapData::from_pin("/sys/fs/bpf/my_map")?; /// map.pin("/sys/fs/bpf/my_map2")?; /// /// # Ok::<(), Box>(()) /// ``` pub fn pin>(&self, path: P) -> Result<(), PinError> { use std::os::unix::ffi::OsStrExt as _; let Self { fd, obj: _ } = self; let path = path.as_ref(); let path_string = CString::new(path.as_os_str().as_bytes()).map_err(|error| { PinError::InvalidPinPath { path: path.to_path_buf(), error, } })?; bpf_pin_object(fd.as_fd(), &path_string).map_err(|(_, io_error)| SyscallError { call: "BPF_OBJ_PIN", io_error, })?; Ok(()) } /// Returns the file descriptor of the map. pub fn fd(&self) -> &MapFd { let Self { obj: _, fd } = self; fd } pub(crate) fn obj(&self) -> &obj::Map { let Self { obj, fd: _ } = self; obj } /// Returns the kernel's information about the loaded map. pub fn info(&self) -> Result { MapInfo::new_from_fd(self.fd.as_fd()) } } /// An iterable map pub trait IterableMap { /// Get a generic map handle fn map(&self) -> &MapData; /// Get the value for the provided `key` fn get(&self, key: &K) -> Result; } /// Iterator returned by `map.keys()`. pub struct MapKeys<'coll, K: Pod> { map: &'coll MapData, err: bool, key: Option, } impl<'coll, K: Pod> MapKeys<'coll, K> { fn new(map: &'coll MapData) -> Self { Self { map, err: false, key: None, } } } impl Iterator for MapKeys<'_, K> { type Item = Result; fn next(&mut self) -> Option> { if self.err { return None; } let fd = self.map.fd().as_fd(); let key = bpf_map_get_next_key(fd, self.key.as_ref()).map_err(|(_, io_error)| SyscallError { call: "bpf_map_get_next_key", io_error, }); match key { Err(err) => { self.err = true; Some(Err(err.into())) } Ok(key) => { self.key = key; key.map(Ok) } } } } /// Iterator returned by `map.iter()`. pub struct MapIter<'coll, K: Pod, V, I: IterableMap> { keys: MapKeys<'coll, K>, map: &'coll I, _v: PhantomData, } impl<'coll, K: Pod, V, I: IterableMap> MapIter<'coll, K, V, I> { fn new(map: &'coll I) -> Self { Self { keys: MapKeys::new(map.map()), map, _v: PhantomData, } } } impl> Iterator for MapIter<'_, K, V, I> { type Item = Result<(K, V), MapError>; fn next(&mut self) -> Option { loop { match self.keys.next() { Some(Ok(key)) => match self.map.get(&key) { Ok(value) => return Some(Ok((key, value))), Err(MapError::KeyNotFound) => continue, Err(e) => return Some(Err(e)), }, Some(Err(e)) => return Some(Err(e)), None => return None, } } } } pub(crate) struct PerCpuKernelMem { bytes: Vec, } impl PerCpuKernelMem { pub(crate) fn as_mut_ptr(&mut self) -> *mut u8 { self.bytes.as_mut_ptr() } } /// A slice of per-CPU values. /// /// Used by maps that implement per-CPU storage like [`PerCpuHashMap`]. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let bpf = aya::Ebpf::load(&[])?; /// use aya::maps::PerCpuValues; /// use aya::util::nr_cpus; /// /// let nr_cpus = nr_cpus().map_err(|(_, error)| error)?; /// let values = PerCpuValues::try_from(vec![42u32; nr_cpus])?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] pub struct PerCpuValues { values: Box<[T]>, } impl TryFrom> for PerCpuValues { type Error = io::Error; fn try_from(values: Vec) -> Result { let nr_cpus = nr_cpus().map_err(|(_, error)| error)?; if values.len() != nr_cpus { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("not enough values ({}), nr_cpus: {}", values.len(), nr_cpus), )); } Ok(Self { values: values.into_boxed_slice(), }) } } impl PerCpuValues { pub(crate) fn alloc_kernel_mem() -> Result { let value_size = (mem::size_of::() + 7) & !7; let nr_cpus = nr_cpus().map_err(|(_, error)| error)?; Ok(PerCpuKernelMem { bytes: vec![0u8; nr_cpus * value_size], }) } pub(crate) unsafe fn from_kernel_mem(mem: PerCpuKernelMem) -> Self { let mem_ptr = mem.bytes.as_ptr() as usize; let value_size = (mem::size_of::() + 7) & !7; let mut values = Vec::new(); let mut offset = 0; while offset < mem.bytes.len() { values.push(ptr::read_unaligned((mem_ptr + offset) as *const _)); offset += value_size; } Self { values: values.into_boxed_slice(), } } pub(crate) fn build_kernel_mem(&self) -> Result { let mut mem = Self::alloc_kernel_mem()?; let mem_ptr = mem.as_mut_ptr() as usize; let value_size = (mem::size_of::() + 7) & !7; for i in 0..self.values.len() { unsafe { ptr::write_unaligned((mem_ptr + i * value_size) as *mut _, self.values[i]) }; } Ok(mem) } } impl Deref for PerCpuValues { type Target = Box<[T]>; fn deref(&self) -> &Self::Target { &self.values } } #[cfg(test)] mod test_utils { use crate::{ bpf_map_def, generated::{bpf_cmd, bpf_map_type}, maps::MapData, obj::{self, maps::LegacyMap, EbpfSectionKind}, sys::{override_syscall, Syscall}, }; pub(super) fn new_map(obj: obj::Map) -> MapData { override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_CREATE, .. } => Ok(crate::MockableFd::mock_signed_fd().into()), call => panic!("unexpected syscall {:?}", call), }); MapData::create(obj, "foo", None).unwrap() } pub(super) fn new_obj_map(map_type: bpf_map_type) -> obj::Map { obj::Map::Legacy(LegacyMap { def: bpf_map_def { map_type: map_type as u32, key_size: std::mem::size_of::() as u32, value_size: 4, max_entries: 1024, ..Default::default() }, section_index: 0, section_kind: EbpfSectionKind::Maps, data: Vec::new(), symbol_index: None, }) } pub(super) fn new_obj_map_with_max_entries( map_type: bpf_map_type, max_entries: u32, ) -> obj::Map { obj::Map::Legacy(LegacyMap { def: bpf_map_def { map_type: map_type as u32, key_size: std::mem::size_of::() as u32, value_size: 4, max_entries, ..Default::default() }, section_index: 0, section_kind: EbpfSectionKind::Maps, data: Vec::new(), symbol_index: None, }) } } #[cfg(test)] mod tests { use std::os::fd::AsRawFd as _; use assert_matches::assert_matches; use libc::{c_char, EFAULT}; fn new_obj_map() -> obj::Map { test_utils::new_obj_map::(crate::generated::bpf_map_type::BPF_MAP_TYPE_HASH) } use super::*; use crate::{ generated::bpf_cmd, sys::{override_syscall, Syscall}, }; #[test] fn test_from_map_id() { override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_FD_BY_ID, attr, } => { assert_eq!( unsafe { attr.__bindgen_anon_6.__bindgen_anon_1.map_id }, 1234 ); Ok(crate::MockableFd::mock_signed_fd().into()) } Syscall::Ebpf { cmd: bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, attr, } => { assert_eq!( unsafe { attr.info.bpf_fd }, crate::MockableFd::mock_unsigned_fd(), ); Ok(0) } _ => Err((-1, io::Error::from_raw_os_error(EFAULT))), }); assert_matches!( MapData::from_id(1234), Ok(MapData { obj: _, fd, }) => assert_eq!(fd.as_fd().as_raw_fd(), crate::MockableFd::mock_signed_fd()) ); } #[test] fn test_create() { override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_CREATE, .. } => Ok(crate::MockableFd::mock_signed_fd().into()), _ => Err((-1, io::Error::from_raw_os_error(EFAULT))), }); assert_matches!( MapData::create(new_obj_map(), "foo", None), Ok(MapData { obj: _, fd, }) => assert_eq!(fd.as_fd().as_raw_fd(), crate::MockableFd::mock_signed_fd()) ); } #[test] #[cfg_attr(miri, ignore = "nr_cpus() opens a file on procfs that upsets miri")] fn test_create_perf_event_array() { override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_CREATE, .. } => Ok(crate::MockableFd::mock_signed_fd().into()), _ => Err((-1, io::Error::from_raw_os_error(EFAULT))), }); let nr_cpus = nr_cpus().unwrap(); // Create with max_entries > nr_cpus is clamped to nr_cpus assert_matches!( MapData::create(test_utils::new_obj_map_with_max_entries::( crate::generated::bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY, 65535, ), "foo", None), Ok(MapData { obj, fd, }) => { assert_eq!(fd.as_fd().as_raw_fd(), crate::MockableFd::mock_signed_fd()); assert_eq!(obj.max_entries(), nr_cpus as u32) } ); // Create with max_entries = 0 is set to nr_cpus assert_matches!( MapData::create(test_utils::new_obj_map_with_max_entries::( crate::generated::bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY, 0, ), "foo", None), Ok(MapData { obj, fd, }) => { assert_eq!(fd.as_fd().as_raw_fd(), crate::MockableFd::mock_signed_fd()); assert_eq!(obj.max_entries(), nr_cpus as u32) } ); // Create with max_entries < nr_cpus is unchanged assert_matches!( MapData::create(test_utils::new_obj_map_with_max_entries::( crate::generated::bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY, 1, ), "foo", None), Ok(MapData { obj, fd, }) => { assert_eq!(fd.as_fd().as_raw_fd(), crate::MockableFd::mock_signed_fd()); assert_eq!(obj.max_entries(), 1) } ); } #[test] #[cfg_attr( miri, ignore = "`let map_info = unsafe { &mut *(attr.info.info as *mut bpf_map_info) }` is trying to retag from for Unique permission, but no exposed tags have suitable permission in the borrow stack for this location" )] fn test_name() { use crate::generated::bpf_map_info; const TEST_NAME: &str = "foo"; override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_CREATE, .. } => Ok(crate::MockableFd::mock_signed_fd().into()), Syscall::Ebpf { cmd: bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, attr, } => { assert_eq!( unsafe { attr.info.info_len }, mem::size_of::() as u32 ); let map_info = unsafe { &mut *(attr.info.info as *mut bpf_map_info) }; map_info.name[..TEST_NAME.len()].copy_from_slice(unsafe { mem::transmute::<&[u8], &[c_char]>(TEST_NAME.as_bytes()) }); Ok(0) } _ => Err((-1, io::Error::from_raw_os_error(EFAULT))), }); let map_data = MapData::create(new_obj_map(), TEST_NAME, None).unwrap(); assert_eq!(TEST_NAME, map_data.info().unwrap().name_as_str().unwrap()); } #[test] #[cfg_attr( miri, ignore = "`let map_info = unsafe { &mut *(attr.info.info as *mut bpf_map_info) }` is trying to retag from for Unique permission, but no exposed tags have suitable permission in the borrow stack for this location" )] fn test_loaded_maps() { use crate::generated::bpf_map_info; override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_NEXT_ID, attr, } => unsafe { let id = attr.__bindgen_anon_6.__bindgen_anon_1.start_id; if id < 5 { attr.__bindgen_anon_6.next_id = id + 1; Ok(0) } else { Err((-1, io::Error::from_raw_os_error(libc::ENOENT))) } }, Syscall::Ebpf { cmd: bpf_cmd::BPF_MAP_GET_FD_BY_ID, attr, } => Ok((unsafe { attr.__bindgen_anon_6.__bindgen_anon_1.map_id } + crate::MockableFd::mock_unsigned_fd()) .into()), Syscall::Ebpf { cmd: bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, attr, } => { let map_info = unsafe { &mut *(attr.info.info as *mut bpf_map_info) }; map_info.id = unsafe { attr.info.bpf_fd } - crate::MockableFd::mock_unsigned_fd(); map_info.key_size = 32; map_info.value_size = 64; map_info.map_flags = 1234; map_info.max_entries = 99; Ok(0) } _ => Err((-1, io::Error::from_raw_os_error(EFAULT))), }); assert_eq!( loaded_maps() .map(|map_info| { let map_info = map_info.unwrap(); ( map_info.id(), map_info.key_size(), map_info.value_size(), map_info.map_flags(), map_info.max_entries(), map_info.fd().unwrap().as_fd().as_raw_fd(), ) }) .collect::>(), (1..6) .map(|i: u8| ( i.into(), 32, 64, 1234, 99, crate::MockableFd::mock_signed_fd() + i32::from(i) )) .collect::>(), ); } #[test] fn test_create_failed() { override_syscall(|_| Err((-42, io::Error::from_raw_os_error(EFAULT)))); assert_matches!( MapData::create(new_obj_map(), "foo", None), Err(MapError::CreateError { name, code, io_error }) => { assert_eq!(name, "foo"); assert_eq!(code, -42); assert_eq!(io_error.raw_os_error(), Some(EFAULT)); } ); } } aya-0.13.1/src/maps/perf/async_perf_event_array.rs000064400000000000000000000146241046102023000202500ustar 00000000000000use std::{ borrow::{Borrow, BorrowMut}, path::Path, }; // See https://doc.rust-lang.org/cargo/reference/features.html#mutually-exclusive-features. // // We should eventually split async functionality out into separate crates "aya-async-tokio" and // "async-async-std". Presently we arbitrarily choose tokio over async-std when both are requested. #[cfg(all(not(feature = "async_tokio"), feature = "async_std"))] use async_io::Async; use bytes::BytesMut; #[cfg(feature = "async_tokio")] use tokio::io::unix::AsyncFd; use crate::maps::{ perf::{Events, PerfBufferError, PerfEventArray, PerfEventArrayBuffer}, MapData, MapError, PinError, }; /// A `Future` based map that can be used to receive events from eBPF programs using the linux /// [`perf`](https://perf.wiki.kernel.org/index.php/Main_Page) API. /// /// This is the async version of [`PerfEventArray`], which provides integration /// with [tokio](https://docs.rs/tokio) and [async-std](https:/docs.rs/async-std) and a nice `Future` based API. /// /// To receive events you need to: /// * call [`AsyncPerfEventArray::open`] /// * call [`AsyncPerfEventArrayBuffer::read_events`] to read the events /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.3. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError), /// # #[error(transparent)] /// # PerfBuf(#[from] aya::maps::perf::PerfBufferError), /// # } /// # #[cfg(feature = "async_tokio")] /// # async fn try_main() -> Result<(), Error> { /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::perf::{AsyncPerfEventArray, PerfBufferError}; /// use aya::util::online_cpus; /// use bytes::BytesMut; /// use tokio::task; // or async_std::task /// /// // try to convert the PERF_ARRAY map to an AsyncPerfEventArray /// let mut perf_array = AsyncPerfEventArray::try_from(bpf.take_map("PERF_ARRAY").unwrap())?; /// /// for cpu_id in online_cpus().map_err(|(_, error)| error)? { /// // open a separate perf buffer for each cpu /// let mut buf = perf_array.open(cpu_id, None)?; /// /// // process each perf buffer in a separate task /// task::spawn(async move { /// let mut buffers = (0..10) /// .map(|_| BytesMut::with_capacity(1024)) /// .collect::>(); /// /// loop { /// // wait for events /// let events = buf.read_events(&mut buffers).await?; /// /// // events.read contains the number of events that have been read, /// // and is always <= buffers.len() /// for i in 0..events.read { /// let buf = &mut buffers[i]; /// // process buf /// } /// } /// /// Ok::<_, PerfBufferError>(()) /// }); /// } /// /// # Ok(()) /// # } /// ``` #[doc(alias = "BPF_MAP_TYPE_PERF_EVENT_ARRAY")] pub struct AsyncPerfEventArray { perf_map: PerfEventArray, } impl> AsyncPerfEventArray { /// Opens the perf buffer at the given index. /// /// The returned buffer will receive all the events eBPF programs send at the given index. pub fn open( &mut self, index: u32, page_count: Option, ) -> Result, PerfBufferError> { let Self { perf_map } = self; let buf = perf_map.open(index, page_count)?; #[cfg(feature = "async_tokio")] let buf = AsyncFd::new(buf)?; #[cfg(all(not(feature = "async_tokio"), feature = "async_std"))] let buf = Async::new(buf)?; Ok(AsyncPerfEventArrayBuffer { buf }) } /// Pins the map to a BPF filesystem. /// /// When a map is pinned it will remain loaded until the corresponding file /// is deleted. All parent directories in the given `path` must already exist. pub fn pin>(&self, path: P) -> Result<(), PinError> { self.perf_map.pin(path) } } impl> AsyncPerfEventArray { pub(crate) fn new(map: T) -> Result { Ok(Self { perf_map: PerfEventArray::new(map)?, }) } } /// A `Future` based ring buffer that can receive events from eBPF programs. /// /// [`AsyncPerfEventArrayBuffer`] is a ring buffer that can receive events from eBPF programs that /// use `bpf_perf_event_output()`. It's returned by [`AsyncPerfEventArray::open`]. /// /// See the [`AsyncPerfEventArray` documentation](AsyncPerfEventArray) for an overview of how to /// use perf buffers. pub struct AsyncPerfEventArrayBuffer> { #[cfg(not(any(feature = "async_tokio", feature = "async_std")))] buf: PerfEventArrayBuffer, #[cfg(feature = "async_tokio")] buf: AsyncFd>, #[cfg(all(not(feature = "async_tokio"), feature = "async_std"))] buf: Async>, } impl> AsyncPerfEventArrayBuffer { /// Reads events from the buffer. /// /// This method reads events into the provided slice of buffers, filling /// each buffer in order stopping when there are no more events to read or /// all the buffers have been filled. /// /// Returns the number of events read and the number of events lost. Events /// are lost when user space doesn't read events fast enough and the ring /// buffer fills up. pub async fn read_events( &mut self, buffers: &mut [BytesMut], ) -> Result { let Self { buf } = self; loop { #[cfg(feature = "async_tokio")] let mut guard = buf.readable_mut().await?; #[cfg(feature = "async_tokio")] let buf = guard.get_inner_mut(); #[cfg(all(not(feature = "async_tokio"), feature = "async_std"))] let buf = { if !buf.get_ref().readable() { buf.readable().await?; } unsafe { buf.get_mut() } }; let events = buf.read_events(buffers)?; const EMPTY: Events = Events { read: 0, lost: 0 }; if events != EMPTY { break Ok(events); } #[cfg(feature = "async_tokio")] guard.clear_ready(); } } } aya-0.13.1/src/maps/perf/mod.rs000064400000000000000000000011261046102023000142700ustar 00000000000000//! Ring buffer types used to receive events from eBPF programs using the linux //! `perf` API. //! //! See [`PerfEventArray`] and [`AsyncPerfEventArray`]. #[cfg(any(feature = "async_tokio", feature = "async_std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "async_tokio", feature = "async_std"))))] mod async_perf_event_array; mod perf_buffer; mod perf_event_array; #[cfg(any(feature = "async_tokio", feature = "async_std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "async_tokio", feature = "async_std"))))] pub use async_perf_event_array::*; pub use perf_buffer::*; pub use perf_event_array::*; aya-0.13.1/src/maps/perf/perf_buffer.rs000064400000000000000000000465571046102023000160170ustar 00000000000000use std::{ ffi::c_void, io, mem, os::fd::{AsFd, BorrowedFd}, ptr, slice, sync::atomic::{self, AtomicPtr, Ordering}, }; use bytes::BytesMut; use libc::{munmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE}; use thiserror::Error; use crate::{ generated::{ perf_event_header, perf_event_mmap_page, perf_event_type::{PERF_RECORD_LOST, PERF_RECORD_SAMPLE}, }, sys::{mmap, perf_event_ioctl, perf_event_open_bpf, SysResult}, PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, }; /// Perf buffer error. #[derive(Error, Debug)] pub enum PerfBufferError { /// the page count value passed to [`PerfEventArray::open`](crate::maps::PerfEventArray::open) is invalid. #[error("invalid page count {page_count}, the value must be a power of two")] InvalidPageCount { /// the page count page_count: usize, }, /// `perf_event_open` failed. #[error("perf_event_open failed: {io_error}")] OpenError { /// the source of this error #[source] io_error: io::Error, }, /// `mmap`-ping the buffer failed. #[error("mmap failed: {io_error}")] MMapError { /// the source of this error #[source] io_error: io::Error, }, /// The `PERF_EVENT_IOC_ENABLE` ioctl failed #[error("PERF_EVENT_IOC_ENABLE failed: {io_error}")] PerfEventEnableError { #[source] /// the source of this error io_error: io::Error, }, /// `read_events()` was called with no output buffers. #[error("read_events() was called with no output buffers")] NoBuffers, /// `read_events()` was called with a buffer that is not large enough to /// contain the next event in the perf buffer. #[deprecated( since = "0.10.8", note = "read_events() now calls BytesMut::reserve() internally, so this error is never returned" )] #[error("the buffer needs to be of at least {size} bytes")] MoreSpaceNeeded { /// expected size size: usize, }, /// An IO error occurred. #[error(transparent)] IOError(#[from] io::Error), } /// Return type of `read_events()`. #[derive(Debug, PartialEq, Eq)] pub struct Events { /// The number of events read. pub read: usize, /// The number of events lost. pub lost: usize, } #[derive(Debug)] pub(crate) struct PerfBuffer { buf: AtomicPtr, size: usize, page_size: usize, fd: crate::MockableFd, } impl PerfBuffer { pub(crate) fn open( cpu_id: u32, page_size: usize, page_count: usize, ) -> Result { if !page_count.is_power_of_two() { return Err(PerfBufferError::InvalidPageCount { page_count }); } let fd = perf_event_open_bpf(cpu_id as i32) .map_err(|(_, io_error)| PerfBufferError::OpenError { io_error })?; let size = page_size * page_count; let buf = unsafe { mmap( ptr::null_mut(), size + page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd.as_fd(), 0, ) }; if buf == MAP_FAILED { return Err(PerfBufferError::MMapError { io_error: io::Error::last_os_error(), }); } let perf_buf = Self { buf: AtomicPtr::new(buf as *mut perf_event_mmap_page), size, page_size, fd, }; perf_event_ioctl(perf_buf.fd.as_fd(), PERF_EVENT_IOC_ENABLE, 0) .map_err(|(_, io_error)| PerfBufferError::PerfEventEnableError { io_error })?; Ok(perf_buf) } pub(crate) fn readable(&self) -> bool { let header = self.buf.load(Ordering::SeqCst); let head = unsafe { (*header).data_head } as usize; let tail = unsafe { (*header).data_tail } as usize; head != tail } pub(crate) fn read_events( &mut self, buffers: &mut [BytesMut], ) -> Result { if buffers.is_empty() { return Err(PerfBufferError::NoBuffers); } let header = self.buf.load(Ordering::SeqCst); let base = header as usize + self.page_size; let mut events = Events { read: 0, lost: 0 }; let mut buf_n = 0; let fill_buf = |start_off, base, mmap_size, out_buf: &mut [u8]| { let len = out_buf.len(); let end = (start_off + len) % mmap_size; let start = start_off % mmap_size; if start < end { out_buf.copy_from_slice(unsafe { slice::from_raw_parts((base + start) as *const u8, len) }); } else { let size = mmap_size - start; unsafe { out_buf[..size] .copy_from_slice(slice::from_raw_parts((base + start) as *const u8, size)); out_buf[size..] .copy_from_slice(slice::from_raw_parts(base as *const u8, len - size)); } } }; let read_event = |event_start, event_type, base, buf: &mut BytesMut| { let sample_size = match event_type { x if x == PERF_RECORD_SAMPLE as u32 || x == PERF_RECORD_LOST as u32 => { let mut size = [0u8; mem::size_of::()]; fill_buf( event_start + mem::size_of::(), base, self.size, &mut size, ); u32::from_ne_bytes(size) } _ => return Ok(None), } as usize; let sample_start = (event_start + mem::size_of::() + mem::size_of::()) % self.size; match event_type { x if x == PERF_RECORD_SAMPLE as u32 => { buf.clear(); buf.reserve(sample_size); unsafe { buf.set_len(sample_size) }; fill_buf(sample_start, base, self.size, buf); Ok(Some((1, 0))) } x if x == PERF_RECORD_LOST as u32 => { let mut count = [0u8; mem::size_of::()]; fill_buf( event_start + mem::size_of::() + mem::size_of::(), base, self.size, &mut count, ); Ok(Some((0, u64::from_ne_bytes(count) as usize))) } _ => Ok(None), } }; let head = unsafe { (*header).data_head } as usize; let mut tail = unsafe { (*header).data_tail } as usize; let result = loop { if head == tail { break Ok(()); } if buf_n == buffers.len() { break Ok(()); } let buf = &mut buffers[buf_n]; let event_start = tail % self.size; let event = unsafe { ptr::read_unaligned((base + event_start) as *const perf_event_header) }; let event_size = event.size as usize; match read_event(event_start, event.type_, base, buf) { Ok(Some((read, lost))) => { if read > 0 { buf_n += 1; events.read += read; } events.lost += lost; } Ok(None) => { /* skip unknown event type */ } Err(e) => { // we got an error and we didn't process any events, propagate the error // and give the caller a chance to increase buffers break Err(e); } } tail += event_size; }; atomic::fence(Ordering::SeqCst); unsafe { (*header).data_tail = tail as u64 }; result.map(|()| events) } } impl AsFd for PerfBuffer { fn as_fd(&self) -> BorrowedFd<'_> { self.fd.as_fd() } } impl Drop for PerfBuffer { fn drop(&mut self) { unsafe { let _: SysResult<_> = perf_event_ioctl(self.fd.as_fd(), PERF_EVENT_IOC_DISABLE, 0); munmap( self.buf.load(Ordering::SeqCst) as *mut c_void, self.size + self.page_size, ); } } } #[cfg(test)] mod tests { use std::fmt::Debug; use assert_matches::assert_matches; use super::*; use crate::sys::{override_syscall, Syscall, TEST_MMAP_RET}; #[repr(C)] #[derive(Debug)] struct Sample { header: perf_event_header, size: u32, } const PAGE_SIZE: usize = 4096; union MMappedBuf { mmap_page: perf_event_mmap_page, data: [u8; PAGE_SIZE * 2], } fn fake_mmap(buf: &MMappedBuf) { override_syscall(|call| match call { Syscall::PerfEventOpen { .. } | Syscall::PerfEventIoctl { .. } => { Ok(crate::MockableFd::mock_signed_fd().into()) } call => panic!("unexpected syscall: {:?}", call), }); TEST_MMAP_RET.with(|ret| *ret.borrow_mut() = buf as *const _ as *mut _); } #[test] fn test_invalid_page_count() { assert_matches!( PerfBuffer::open(1, PAGE_SIZE, 0), Err(PerfBufferError::InvalidPageCount { .. }) ); assert_matches!( PerfBuffer::open(1, PAGE_SIZE, 3), Err(PerfBufferError::InvalidPageCount { .. }) ); assert_matches!( PerfBuffer::open(1, PAGE_SIZE, 5), Err(PerfBufferError::InvalidPageCount { .. }) ); } #[test] fn test_no_out_bufs() { let mmapped_buf = MMappedBuf { data: [0; PAGE_SIZE * 2], }; fake_mmap(&mmapped_buf); let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap(); assert_matches!(buf.read_events(&mut []), Err(PerfBufferError::NoBuffers)) } #[test] #[cfg_attr( miri, ignore = "`unsafe { (*header).data_tail = tail as u64 };` is attempting a write access using using a tag that only grants SharedReadOnly permission" )] fn test_no_events() { let mmapped_buf = MMappedBuf { data: [0; PAGE_SIZE * 2], }; fake_mmap(&mmapped_buf); let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap(); let out_buf = BytesMut::with_capacity(4); assert_eq!( buf.read_events(&mut [out_buf]).unwrap(), Events { read: 0, lost: 0 } ); } #[test] #[cfg_attr( miri, ignore = "`ptr::write_unaligned(dst, value)` is attempting a write access but no exposed tags have suitable permission in the borrow stack for this location" )] fn test_read_first_lost() { let mut mmapped_buf = MMappedBuf { data: [0; PAGE_SIZE * 2], }; fake_mmap(&mmapped_buf); #[repr(C)] #[derive(Debug)] struct LostSamples { header: perf_event_header, id: u64, count: u64, } let evt = LostSamples { header: perf_event_header { type_: PERF_RECORD_LOST as u32, misc: 0, size: mem::size_of::() as u16, }, id: 1, count: 0xCAFEBABE, }; write(&mut mmapped_buf, 0, evt); let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap(); let out_buf = BytesMut::with_capacity(0); let events = buf.read_events(&mut [out_buf]).unwrap(); assert_eq!(events.lost, 0xCAFEBABE); } #[repr(C)] #[derive(Debug)] struct PerfSample { s_hdr: Sample, value: T, } fn write(mmapped_buf: &mut MMappedBuf, offset: usize, value: T) -> usize { let dst = (mmapped_buf as *const _ as usize + PAGE_SIZE + offset) as *const PerfSample as *mut T; unsafe { ptr::write_unaligned(dst, value); mmapped_buf.mmap_page.data_head = (offset + mem::size_of::()) as u64; mmapped_buf.mmap_page.data_head as usize } } fn write_sample(mmapped_buf: &mut MMappedBuf, offset: usize, value: T) -> usize { let sample = PerfSample { s_hdr: Sample { header: perf_event_header { type_: PERF_RECORD_SAMPLE as u32, misc: 0, size: mem::size_of::>() as u16, }, size: mem::size_of::() as u32, }, value, }; write(mmapped_buf, offset, sample) } fn u32_from_buf(buf: &[u8]) -> u32 { u32::from_ne_bytes(buf[..4].try_into().unwrap()) } fn u64_from_buf(buf: &[u8]) -> u64 { u64::from_ne_bytes(buf[..8].try_into().unwrap()) } #[test] #[cfg_attr( miri, ignore = "`ptr::write_unaligned(dst, value)` is attempting a write access but no exposed tags have suitable permission in the borrow stack for this location" )] fn test_read_first_sample() { let mut mmapped_buf = MMappedBuf { data: [0; PAGE_SIZE * 2], }; fake_mmap(&mmapped_buf); let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap(); write_sample(&mut mmapped_buf, 0, 0xCAFEBABEu32); let mut out_bufs = [BytesMut::with_capacity(4)]; let events = buf.read_events(&mut out_bufs).unwrap(); assert_eq!(events, Events { lost: 0, read: 1 }); assert_eq!(u32_from_buf(&out_bufs[0]), 0xCAFEBABE); } #[test] #[cfg_attr( miri, ignore = "`ptr::write_unaligned(dst, value)` is attempting a write access but no exposed tags have suitable permission in the borrow stack for this location" )] fn test_read_many_with_many_reads() { let mut mmapped_buf = MMappedBuf { data: [0; PAGE_SIZE * 2], }; fake_mmap(&mmapped_buf); let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap(); let next = write_sample(&mut mmapped_buf, 0, 0xCAFEBABEu32); write_sample(&mut mmapped_buf, next, 0xBADCAFEu32); let mut out_bufs = [BytesMut::with_capacity(4)]; let events = buf.read_events(&mut out_bufs).unwrap(); assert_eq!(events, Events { lost: 0, read: 1 }); assert_eq!(u32_from_buf(&out_bufs[0]), 0xCAFEBABE); let events = buf.read_events(&mut out_bufs).unwrap(); assert_eq!(events, Events { lost: 0, read: 1 }); assert_eq!(u32_from_buf(&out_bufs[0]), 0xBADCAFE); } #[test] #[cfg_attr( miri, ignore = "`ptr::write_unaligned(dst, value)` is attempting a write access but no exposed tags have suitable permission in the borrow stack for this location" )] fn test_read_many_with_one_read() { let mut mmapped_buf = MMappedBuf { data: [0; PAGE_SIZE * 2], }; fake_mmap(&mmapped_buf); let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap(); let next = write_sample(&mut mmapped_buf, 0, 0xCAFEBABEu32); write_sample(&mut mmapped_buf, next, 0xBADCAFEu32); let mut out_bufs = (0..3) .map(|_| BytesMut::with_capacity(4)) .collect::>(); let events = buf.read_events(&mut out_bufs).unwrap(); assert_eq!(events, Events { lost: 0, read: 2 }); assert_eq!(u32_from_buf(&out_bufs[0]), 0xCAFEBABE); assert_eq!(u32_from_buf(&out_bufs[1]), 0xBADCAFE); } #[test] #[cfg_attr( miri, ignore = "`ptr::write_unaligned(dst, value)` is attempting a write access but no exposed tags have suitable permission in the borrow stack for this location" )] fn test_read_last_sample() { let mut mmapped_buf = MMappedBuf { data: [0; PAGE_SIZE * 2], }; fake_mmap(&mmapped_buf); let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap(); let offset = PAGE_SIZE - mem::size_of::>(); mmapped_buf.mmap_page.data_tail = offset as u64; write_sample(&mut mmapped_buf, offset, 0xCAFEBABEu32); let mut out_bufs = [BytesMut::with_capacity(4)]; let events = buf.read_events(&mut out_bufs).unwrap(); assert_eq!(events, Events { lost: 0, read: 1 }); assert_eq!(u32_from_buf(&out_bufs[0]), 0xCAFEBABE); } #[test] #[cfg_attr( miri, ignore = "`ptr::write_unaligned(dst, value)` is attempting a write access but no exposed tags have suitable permission in the borrow stack for this location" )] fn test_read_wrapping_sample_size() { let mut mmapped_buf = MMappedBuf { data: [0; PAGE_SIZE * 2], }; fake_mmap(&mmapped_buf); let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap(); let header = perf_event_header { type_: PERF_RECORD_SAMPLE as u32, misc: 0, size: mem::size_of::>() as u16, }; let offset = PAGE_SIZE - mem::size_of::() - 2; mmapped_buf.mmap_page.data_tail = offset as u64; write(&mut mmapped_buf, offset, header); #[cfg(target_endian = "little")] { write(&mut mmapped_buf, PAGE_SIZE - 2, 0x0004u16); write(&mut mmapped_buf, 0, 0x0000u16); } #[cfg(target_endian = "big")] { write(&mut mmapped_buf, PAGE_SIZE - 2, 0x0000u16); write(&mut mmapped_buf, 0, 0x0004u16); } write(&mut mmapped_buf, 2, 0xBAADCAFEu32); let mut out_bufs = [BytesMut::with_capacity(8)]; let events = buf.read_events(&mut out_bufs).unwrap(); assert_eq!(events, Events { lost: 0, read: 1 }); assert_eq!(u32_from_buf(&out_bufs[0]), 0xBAADCAFE); } #[test] #[cfg_attr( miri, ignore = "`ptr::write_unaligned(dst, value)` is attempting a write access but no exposed tags have suitable permission in the borrow stack for this location" )] fn test_read_wrapping_value() { let mut mmapped_buf = MMappedBuf { data: [0; PAGE_SIZE * 2], }; fake_mmap(&mmapped_buf); let mut buf = PerfBuffer::open(1, PAGE_SIZE, 1).unwrap(); let sample = PerfSample { s_hdr: Sample { header: perf_event_header { type_: PERF_RECORD_SAMPLE as u32, misc: 0, size: mem::size_of::>() as u16, }, size: mem::size_of::() as u32, }, #[cfg(target_endian = "little")] value: 0xCAFEBABEu32, #[cfg(target_endian = "big")] value: 0xBAADCAFEu32, }; let offset = PAGE_SIZE - mem::size_of::>(); mmapped_buf.mmap_page.data_tail = offset as u64; write(&mut mmapped_buf, offset, sample); #[cfg(target_endian = "little")] write(&mut mmapped_buf, 0, 0xBAADCAFEu32); #[cfg(target_endian = "big")] write(&mut mmapped_buf, 0, 0xCAFEBABEu32); let mut out_bufs = [BytesMut::with_capacity(8)]; let events = buf.read_events(&mut out_bufs).unwrap(); assert_eq!(events, Events { lost: 0, read: 1 }); assert_eq!(u64_from_buf(&out_bufs[0]), 0xBAADCAFECAFEBABE); } } aya-0.13.1/src/maps/perf/perf_event_array.rs000064400000000000000000000160031046102023000170440ustar 00000000000000//! A map that can be used to receive events from eBPF programs using the linux [`perf`] API //! //! [`perf`]: https://perf.wiki.kernel.org/index.php/Main_Page. use std::{ borrow::{Borrow, BorrowMut}, ops::Deref, os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd}, path::Path, sync::Arc, }; use bytes::BytesMut; use crate::{ maps::{ perf::{Events, PerfBuffer, PerfBufferError}, MapData, MapError, PinError, }, sys::bpf_map_update_elem, util::page_size, }; /// A ring buffer that can receive events from eBPF programs. /// /// [`PerfEventArrayBuffer`] is a ring buffer that can receive events from eBPF /// programs that use `bpf_perf_event_output()`. It's returned by [`PerfEventArray::open`]. /// /// See the [`PerfEventArray` documentation](PerfEventArray) for an overview of how to use /// perf buffers. pub struct PerfEventArrayBuffer { _map: Arc, buf: PerfBuffer, } impl> PerfEventArrayBuffer { /// Returns true if the buffer contains events that haven't been read. pub fn readable(&self) -> bool { self.buf.readable() } /// Reads events from the buffer. /// /// This method reads events into the provided slice of buffers, filling /// each buffer in order stopping when there are no more events to read or /// all the buffers have been filled. /// /// Returns the number of events read and the number of events lost. Events /// are lost when user space doesn't read events fast enough and the ring /// buffer fills up. /// /// # Errors /// /// [`PerfBufferError::NoBuffers`] is returned when `out_bufs` is empty. pub fn read_events(&mut self, out_bufs: &mut [BytesMut]) -> Result { self.buf.read_events(out_bufs) } } impl> AsFd for PerfEventArrayBuffer { fn as_fd(&self) -> BorrowedFd<'_> { self.buf.as_fd() } } impl> AsRawFd for PerfEventArrayBuffer { fn as_raw_fd(&self) -> RawFd { self.buf.as_fd().as_raw_fd() } } /// A map that can be used to receive events from eBPF programs using the linux [`perf`] API. /// /// Each element of a [`PerfEventArray`] is a separate [`PerfEventArrayBuffer`] which can be used /// to receive events sent by eBPF programs that use `bpf_perf_event_output()`. /// /// To receive events you need to: /// * call [`PerfEventArray::open`] /// * poll the returned [`PerfEventArrayBuffer`] to be notified when events are /// inserted in the buffer /// * call [`PerfEventArrayBuffer::read_events`] to read the events /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.3. /// /// # Examples /// /// A common way to use a perf array is to have one perf buffer for each /// available CPU: /// /// ```no_run /// # use aya::maps::perf::PerfEventArrayBuffer; /// # use aya::maps::MapData; /// # use std::borrow::BorrowMut; /// # struct Poll { _t: std::marker::PhantomData }; /// # impl> Poll { /// # fn poll_readable(&self) -> &mut [PerfEventArrayBuffer] { /// # &mut [] /// # } /// # } /// # fn poll_buffers>(bufs: Vec>) -> Poll { /// # Poll { _t: std::marker::PhantomData } /// # } /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError), /// # #[error(transparent)] /// # PerfBuf(#[from] aya::maps::perf::PerfBufferError), /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::PerfEventArray; /// use aya::util::online_cpus; /// use bytes::BytesMut; /// /// let mut perf_array = PerfEventArray::try_from(bpf.map_mut("EVENTS").unwrap())?; /// /// // eBPF programs are going to write to the EVENTS perf array, using the id of the CPU they're /// // running on as the array index. /// let mut perf_buffers = Vec::new(); /// for cpu_id in online_cpus().map_err(|(_, error)| error)? { /// // this perf buffer will receive events generated on the CPU with id cpu_id /// perf_buffers.push(perf_array.open(cpu_id, None)?); /// } /// /// let mut out_bufs = [BytesMut::with_capacity(1024)]; /// /// // poll the buffers to know when they have queued events /// let poll = poll_buffers(perf_buffers); /// loop { /// for read_buf in poll.poll_readable() { /// read_buf.read_events(&mut out_bufs)?; /// // process out_bufs /// } /// } /// /// # Ok::<(), Error>(()) /// ``` /// /// # Polling and avoiding lost events /// /// In the example above the implementation of `poll_buffers()` and `poll.poll_readable()` is not /// given. [`PerfEventArrayBuffer`] implements the [`AsRawFd`] trait, so you can implement polling /// using any crate that can poll file descriptors, like [epoll], [mio] etc. /// /// Perf buffers are internally implemented as ring buffers. If your eBPF programs produce large /// amounts of data, in order not to lose events you might want to process each /// [`PerfEventArrayBuffer`] on a different thread. /// /// # Async /// /// If you are using [tokio] or [async-std], you should use `AsyncPerfEventArray` which /// efficiently integrates with those and provides a nicer `Future` based API. /// /// [`perf`]: https://perf.wiki.kernel.org/index.php/Main_Page /// [epoll]: https://docs.rs/epoll /// [mio]: https://docs.rs/mio /// [tokio]: https://docs.rs/tokio /// [async-std]: https://docs.rs/async-std #[doc(alias = "BPF_MAP_TYPE_PERF_EVENT_ARRAY")] pub struct PerfEventArray { map: Arc, page_size: usize, } impl> PerfEventArray { pub(crate) fn new(map: T) -> Result { Ok(Self { map: Arc::new(map), page_size: page_size(), }) } /// Pins the map to a BPF filesystem. /// /// When a map is pinned it will remain loaded until the corresponding file /// is deleted. All parent directories in the given `path` must already exist. pub fn pin>(&self, path: P) -> Result<(), PinError> { let data: &MapData = self.map.deref().borrow(); data.pin(path) } } impl> PerfEventArray { /// Opens the perf buffer at the given index. /// /// The returned buffer will receive all the events eBPF programs send at the given index. pub fn open( &mut self, index: u32, page_count: Option, ) -> Result, PerfBufferError> { // FIXME: keep track of open buffers let map_data: &MapData = self.map.deref().borrow(); let map_fd = map_data.fd().as_fd(); let buf = PerfBuffer::open(index, self.page_size, page_count.unwrap_or(2))?; bpf_map_update_elem(map_fd, Some(&index), &buf.as_fd().as_raw_fd(), 0) .map_err(|(_, io_error)| io_error)?; Ok(PerfEventArrayBuffer { buf, _map: self.map.clone(), }) } } aya-0.13.1/src/maps/queue.rs000064400000000000000000000046651046102023000137140ustar 00000000000000//! A FIFO queue. use std::{ borrow::{Borrow, BorrowMut}, marker::PhantomData, os::fd::AsFd as _, }; use crate::{ maps::{check_kv_size, MapData, MapError}, sys::{bpf_map_lookup_and_delete_elem, bpf_map_push_elem, SyscallError}, Pod, }; /// A FIFO queue. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.20. /// /// # Examples /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::Queue; /// /// let mut queue = Queue::try_from(bpf.map_mut("ARRAY").unwrap())?; /// queue.push(42, 0)?; /// queue.push(43, 0)?; /// assert_eq!(queue.pop(0)?, 42); /// # Ok::<(), aya::EbpfError>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_QUEUE")] pub struct Queue { pub(crate) inner: T, _v: PhantomData, } impl, V: Pod> Queue { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::<(), V>(data)?; Ok(Self { inner: map, _v: PhantomData, }) } /// Returns the number of elements the queue can hold. /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn capacity(&self) -> u32 { self.inner.borrow().obj.max_entries() } } impl, V: Pod> Queue { /// Removes the first element and returns it. /// /// # Errors /// /// Returns [`MapError::ElementNotFound`] if the queue is empty, [`MapError::SyscallError`] /// if `bpf_map_lookup_and_delete_elem` fails. pub fn pop(&mut self, flags: u64) -> Result { let fd = self.inner.borrow().fd().as_fd(); let value = bpf_map_lookup_and_delete_elem::(fd, None, flags).map_err( |(_, io_error)| SyscallError { call: "bpf_map_lookup_and_delete_elem", io_error, }, )?; value.ok_or(MapError::ElementNotFound) } /// Appends an element at the end of the queue. /// /// # Errors /// /// [`MapError::SyscallError`] if `bpf_map_update_elem` fails. pub fn push(&mut self, value: impl Borrow, flags: u64) -> Result<(), MapError> { let fd = self.inner.borrow().fd().as_fd(); bpf_map_push_elem(fd, value.borrow(), flags).map_err(|(_, io_error)| SyscallError { call: "bpf_map_push_elem", io_error, })?; Ok(()) } } aya-0.13.1/src/maps/ring_buf.rs000064400000000000000000000416621046102023000143610ustar 00000000000000//! A [ring buffer map][ringbuf] that may be used to receive events from eBPF programs. //! As of Linux 5.8, this is the preferred way to transfer per-event data from eBPF //! programs to userspace. //! //! [ringbuf]: https://www.kernel.org/doc/html/latest/bpf/ringbuf.html use std::{ borrow::Borrow, ffi::{c_int, c_void}, fmt::{self, Debug, Formatter}, io, mem, ops::Deref, os::fd::{AsFd as _, AsRawFd, BorrowedFd, RawFd}, ptr, ptr::NonNull, slice, sync::atomic::{AtomicU32, AtomicUsize, Ordering}, }; use libc::{munmap, off_t, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE}; use crate::{ generated::{BPF_RINGBUF_BUSY_BIT, BPF_RINGBUF_DISCARD_BIT, BPF_RINGBUF_HDR_SZ}, maps::{MapData, MapError}, sys::{mmap, SyscallError}, util::page_size, }; /// A map that can be used to receive events from eBPF programs. /// /// This is similar to [`crate::maps::PerfEventArray`], but different in a few ways: /// * It's shared across all CPUs, which allows a strong ordering between events. /// * Data notifications are delivered precisely instead of being sampled for every N events; the /// eBPF program can also control notification delivery if sampling is desired for performance /// reasons. By default, a notification will be sent if the consumer is caught up at the time of /// committing. The eBPF program can use the `BPF_RB_NO_WAKEUP` or `BPF_RB_FORCE_WAKEUP` flags to /// control this behavior. /// * On the eBPF side, it supports the reserve-commit pattern where the event can be directly /// written into the ring without copying from a temporary location. /// * Dropped sample notifications go to the eBPF program as the return value of `reserve`/`output`, /// and not the userspace reader. This might require extra code to handle, but allows for more /// flexible schemes to handle dropped samples. /// /// To receive events you need to: /// * Construct [`RingBuf`] using [`RingBuf::try_from`]. /// * Call [`RingBuf::next`] to poll events from the [`RingBuf`]. /// /// To receive async notifications of data availability, you may construct an /// [`tokio::io::unix::AsyncFd`] from the [`RingBuf`]'s file descriptor and poll it for readiness. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.8. /// /// # Examples /// /// ```no_run /// # struct PollFd(T); /// # fn poll_fd(t: T) -> PollFd { PollFd(t) } /// # impl PollFd { /// # fn readable(&mut self) -> Guard<'_, T> { Guard(self) } /// # } /// # struct Guard<'a, T>(&'a mut PollFd); /// # impl Guard<'_, T> { /// # fn inner_mut(&mut self) -> &mut T { /// # let Guard(PollFd(t)) = self; /// # t /// # } /// # fn clear_ready(&mut self) {} /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::RingBuf; /// use std::convert::TryFrom; /// /// let ring_buf = RingBuf::try_from(bpf.map_mut("ARRAY").unwrap()).unwrap(); /// let mut poll = poll_fd(ring_buf); /// loop { /// let mut guard = poll.readable(); /// let ring_buf = guard.inner_mut(); /// while let Some(item) = ring_buf.next() { /// println!("Received: {:?}", item); /// } /// guard.clear_ready(); /// } /// # Ok::<(), aya::EbpfError>(()) /// ``` /// /// # Polling /// /// In the example above the implementations of poll(), poll.readable(), guard.inner_mut(), and /// guard.clear_ready() are not given. RingBuf implements the AsRawFd trait, so you can implement /// polling using any crate that can poll file descriptors, like epoll, mio etc. The above example /// API is motivated by that of [`tokio::io::unix::AsyncFd`]. #[doc(alias = "BPF_MAP_TYPE_RINGBUF")] pub struct RingBuf { map: T, consumer: ConsumerPos, producer: ProducerData, } impl> RingBuf { pub(crate) fn new(map: T) -> Result { let data: &MapData = map.borrow(); let page_size = page_size(); let map_fd = data.fd().as_fd(); let byte_size = data.obj.max_entries(); let consumer_metadata = ConsumerMetadata::new(map_fd, 0, page_size)?; let consumer = ConsumerPos::new(consumer_metadata); let producer = ProducerData::new(map_fd, page_size, page_size, byte_size)?; Ok(Self { map, consumer, producer, }) } } impl RingBuf { /// Try to take a new entry from the ringbuf. /// /// Returns `Some(item)` if the ringbuf is not empty. Returns `None` if the ringbuf is empty, in /// which case the caller may register for availability notifications through `epoll` or other /// APIs. Only one RingBufItem may be outstanding at a time. // // This is not an implementation of `Iterator` because we need to be able to refer to the // lifetime of the iterator in the returned `RingBufItem`. If the Iterator::Item leveraged GATs, // one could imagine an implementation of `Iterator` that would work. GATs are stabilized in // Rust 1.65, but there's not yet a trait that the community seems to have standardized around. #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Option> { let Self { consumer, producer, .. } = self; producer.next(consumer) } } /// Access to the RawFd can be used to construct an AsyncFd for use with epoll. impl> AsRawFd for RingBuf { fn as_raw_fd(&self) -> RawFd { let Self { map, consumer: _, producer: _, } = self; map.borrow().fd().as_fd().as_raw_fd() } } /// The current outstanding item read from the ringbuf. pub struct RingBufItem<'a> { data: &'a [u8], consumer: &'a mut ConsumerPos, } impl Deref for RingBufItem<'_> { type Target = [u8]; fn deref(&self) -> &Self::Target { let Self { data, .. } = self; data } } impl Drop for RingBufItem<'_> { fn drop(&mut self) { let Self { consumer, data } = self; consumer.consume(data.len()) } } impl Debug for RingBufItem<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let Self { data, consumer: ConsumerPos { pos, metadata: ConsumerMetadata { mmap: _ }, }, } = self; // In general Relaxed here is sufficient, for debugging, it certainly is. f.debug_struct("RingBufItem") .field("pos", pos) .field("len", &data.len()) .finish() } } struct ConsumerMetadata { mmap: MMap, } impl ConsumerMetadata { fn new(fd: BorrowedFd<'_>, offset: usize, page_size: usize) -> Result { let mmap = MMap::new( fd, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, offset.try_into().unwrap(), )?; Ok(Self { mmap }) } } impl AsRef for ConsumerMetadata { fn as_ref(&self) -> &AtomicUsize { let Self { mmap: MMap { ptr, .. }, } = self; unsafe { ptr.cast::().as_ref() } } } struct ConsumerPos { pos: usize, metadata: ConsumerMetadata, } impl ConsumerPos { fn new(metadata: ConsumerMetadata) -> Self { // Load the initial value of the consumer position. SeqCst is used to be safe given we don't // have any claims about memory synchronization performed by some previous writer. let pos = metadata.as_ref().load(Ordering::SeqCst); Self { pos, metadata } } fn consume(&mut self, len: usize) { let Self { pos, metadata } = self; // TODO: Use primitive method when https://github.com/rust-lang/rust/issues/88581 is stabilized. fn next_multiple_of(n: usize, multiple: usize) -> usize { match n % multiple { 0 => n, rem => n + (multiple - rem), } } *pos += next_multiple_of(usize::try_from(BPF_RINGBUF_HDR_SZ).unwrap() + len, 8); // Write operation needs to be properly ordered with respect to the producer committing new // data to the ringbuf. The producer uses xchg (SeqCst) to commit new data [1]. The producer // reads the consumer offset after clearing the busy bit on a new entry [2]. By using SeqCst // here we ensure that either a subsequent read by the consumer to consume messages will see // an available message, or the producer in the kernel will see the updated consumer offset // that is caught up. // // [1]: https://github.com/torvalds/linux/blob/2772d7df/kernel/bpf/ringbuf.c#L487-L488 // [2]: https://github.com/torvalds/linux/blob/2772d7df/kernel/bpf/ringbuf.c#L494 metadata.as_ref().store(*pos, Ordering::SeqCst); } } struct ProducerData { mmap: MMap, // Offset in the mmap where the data starts. data_offset: usize, // A cache of the value of the producer position. It is used to avoid re-reading the producer // position when we know there is more data to consume. pos_cache: usize, // A bitmask which truncates u32 values to the domain of valid offsets in the ringbuf. mask: u32, } impl ProducerData { fn new( fd: BorrowedFd<'_>, offset: usize, page_size: usize, byte_size: u32, ) -> Result { // The producer pages have one page of metadata and then the data pages, all mapped // read-only. Note that the length of the mapping includes the data pages twice as the // kernel will map them two time consecutively to avoid special handling of entries that // cross over the end of the ring buffer. // // The kernel diagram below shows the layout of the ring buffer. It references "meta pages", // but we only map exactly one producer meta page read-only. The consumer meta page is mapped // read-write elsewhere, and is taken into consideration via the offset parameter. // // From kernel/bpf/ringbuf.c[0]: // // Each data page is mapped twice to allow "virtual" // continuous read of samples wrapping around the end of ring // buffer area: // ------------------------------------------------------ // | meta pages | real data pages | same data pages | // ------------------------------------------------------ // | | 1 2 3 4 5 6 7 8 9 | 1 2 3 4 5 6 7 8 9 | // ------------------------------------------------------ // | | TA DA | TA DA | // ------------------------------------------------------ // ^^^^^^^ // | // Here, no need to worry about special handling of wrapped-around // data due to double-mapped data pages. This works both in kernel and // when mmap()'ed in user-space, simplifying both kernel and // user-space implementations significantly. // // [0]: https://github.com/torvalds/linux/blob/3f01e9fe/kernel/bpf/ringbuf.c#L108-L124 let len = page_size + 2 * usize::try_from(byte_size).unwrap(); let mmap = MMap::new(fd, len, PROT_READ, MAP_SHARED, offset.try_into().unwrap())?; // byte_size is required to be a power of two multiple of page_size (which implicitly is a // power of 2), so subtracting one will create a bitmask for values less than byte_size. debug_assert!(byte_size.is_power_of_two()); let mask = byte_size - 1; Ok(Self { mmap, data_offset: page_size, pos_cache: 0, mask, }) } fn next<'a>(&'a mut self, consumer: &'a mut ConsumerPos) -> Option> { let Self { ref mmap, data_offset, pos_cache, mask, } = self; let pos = unsafe { mmap.ptr.cast().as_ref() }; let mmap_data = mmap.as_ref(); let data_pages = mmap_data.get(*data_offset..).unwrap_or_else(|| { panic!( "offset {} out of bounds, data len {}", data_offset, mmap_data.len() ) }); while data_available(pos, pos_cache, consumer) { match read_item(data_pages, *mask, consumer) { Item::Busy => return None, Item::Discard { len } => consumer.consume(len), Item::Data(data) => return Some(RingBufItem { data, consumer }), } } return None; enum Item<'a> { Busy, Discard { len: usize }, Data(&'a [u8]), } fn data_available( producer: &AtomicUsize, cache: &mut usize, consumer: &ConsumerPos, ) -> bool { let ConsumerPos { pos: consumer, .. } = consumer; if consumer == cache { // This value is written using Release by the kernel [1], and should be read with // Acquire to ensure that the prior writes to the entry header are visible. // // [1]: https://github.com/torvalds/linux/blob/eb26cbb1/kernel/bpf/ringbuf.c#L447-L448 *cache = producer.load(Ordering::Acquire); } // Note that we don't compare the order of the values because the producer position may // overflow u32 and wrap around to 0. Instead we just compare equality and assume that // the consumer position is always logically less than the producer position. // // Note also that the kernel, at the time of writing [1], doesn't seem to handle this // overflow correctly at all, and it's not clear that one can produce events after the // producer position has wrapped around. // // [1]: https://github.com/torvalds/linux/blob/4b810bf0/kernel/bpf/ringbuf.c#L434-L440 consumer != cache } fn read_item<'data>(data: &'data [u8], mask: u32, pos: &ConsumerPos) -> Item<'data> { let ConsumerPos { pos, .. } = pos; let offset = pos & usize::try_from(mask).unwrap(); let must_get_data = |offset, len| { data.get(offset..offset + len).unwrap_or_else(|| { panic!("{:?} not in {:?}", offset..offset + len, 0..data.len()) }) }; let header_ptr = must_get_data(offset, mem::size_of::()).as_ptr() as *const AtomicU32; // Pair the kernel's SeqCst write (implies Release) [1] with an Acquire load. This // ensures data written by the producer will be visible. // // [1]: https://github.com/torvalds/linux/blob/eb26cbb1/kernel/bpf/ringbuf.c#L488 let header = unsafe { &*header_ptr }.load(Ordering::Acquire); if header & BPF_RINGBUF_BUSY_BIT != 0 { Item::Busy } else { let len = usize::try_from(header & mask).unwrap(); if header & BPF_RINGBUF_DISCARD_BIT != 0 { Item::Discard { len } } else { let data_offset = offset + usize::try_from(BPF_RINGBUF_HDR_SZ).unwrap(); let data = must_get_data(data_offset, len); Item::Data(data) } } } } } // MMap corresponds to a memory-mapped region. // // The data is unmapped in Drop. struct MMap { ptr: NonNull, len: usize, } // Needed because NonNull is !Send and !Sync out of caution that the data // might be aliased unsafely. unsafe impl Send for MMap {} unsafe impl Sync for MMap {} impl MMap { fn new( fd: BorrowedFd<'_>, len: usize, prot: c_int, flags: c_int, offset: off_t, ) -> Result { match unsafe { mmap(ptr::null_mut(), len, prot, flags, fd, offset) } { MAP_FAILED => Err(MapError::SyscallError(SyscallError { call: "mmap", io_error: io::Error::last_os_error(), })), ptr => Ok(Self { ptr: NonNull::new(ptr).ok_or( // This should never happen, but to be paranoid, and so we never need to talk // about a null pointer, we check it anyway. MapError::SyscallError(SyscallError { call: "mmap", io_error: io::Error::new( io::ErrorKind::Other, "mmap returned null pointer", ), }), )?, len, }), } } } impl AsRef<[u8]> for MMap { fn as_ref(&self) -> &[u8] { let Self { ptr, len } = self; unsafe { slice::from_raw_parts(ptr.as_ptr().cast(), *len) } } } impl Drop for MMap { fn drop(&mut self) { let Self { ptr, len } = *self; unsafe { munmap(ptr.as_ptr(), len) }; } } aya-0.13.1/src/maps/sock/mod.rs000064400000000000000000000011761046102023000143000ustar 00000000000000//! Socket maps. mod sock_hash; mod sock_map; use std::{ io, os::fd::{AsFd, BorrowedFd}, }; pub use sock_hash::SockHash; pub use sock_map::SockMap; /// A socket map file descriptor. #[repr(transparent)] pub struct SockMapFd(super::MapFd); impl SockMapFd { /// Creates a new instance that shares the same underlying file description as [`self`]. pub fn try_clone(&self) -> io::Result { let Self(inner) = self; let inner = inner.try_clone()?; Ok(Self(inner)) } } impl AsFd for SockMapFd { fn as_fd(&self) -> BorrowedFd<'_> { let Self(fd) = self; fd.as_fd() } } aya-0.13.1/src/maps/sock/sock_hash.rs000064400000000000000000000107411046102023000154610ustar 00000000000000use std::{ borrow::{Borrow, BorrowMut}, marker::PhantomData, os::fd::{AsFd as _, AsRawFd, RawFd}, }; use crate::{ maps::{ check_kv_size, hash_map, sock::SockMapFd, IterableMap, MapData, MapError, MapFd, MapIter, MapKeys, }, sys::{bpf_map_lookup_elem, SyscallError}, Pod, }; /// A hash map of TCP or UDP sockets. /// /// A `SockHash` is used to store TCP or UDP sockets. eBPF programs can then be /// attached to the map to inspect, filter or redirect network buffers on those /// sockets. /// /// A `SockHash` can also be used to redirect packets to sockets contained by the /// map using `bpf_redirect_map()`, `bpf_sk_redirect_hash()` etc. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.18. /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::io::Write; /// use std::net::TcpStream; /// use std::os::fd::AsRawFd; /// use aya::maps::SockHash; /// use aya::programs::SkMsg; /// /// let mut intercept_egress = SockHash::<_, u32>::try_from(bpf.map("INTERCEPT_EGRESS").unwrap())?; /// let map_fd = intercept_egress.fd().try_clone()?; /// /// let prog: &mut SkMsg = bpf.program_mut("intercept_egress_packet").unwrap().try_into()?; /// prog.load()?; /// prog.attach(&map_fd)?; /// /// let mut client = TcpStream::connect("127.0.0.1:1234")?; /// let mut intercept_egress = SockHash::try_from(bpf.map_mut("INTERCEPT_EGRESS").unwrap())?; /// /// intercept_egress.insert(1234, client.as_raw_fd(), 0)?; /// /// // the write will be intercepted /// client.write_all(b"foo")?; /// # Ok::<(), Error>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_SOCKHASH")] pub struct SockHash { pub(crate) inner: T, _k: PhantomData, } impl, K: Pod> SockHash { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::(data)?; Ok(Self { inner: map, _k: PhantomData, }) } /// Returns the fd of the socket stored at the given key. pub fn get(&self, key: &K, flags: u64) -> Result { let fd = self.inner.borrow().fd().as_fd(); let value = bpf_map_lookup_elem(fd, key, flags).map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })?; value.ok_or(MapError::KeyNotFound) } /// An iterator visiting all key-value pairs in arbitrary order. The /// iterator item type is `Result<(K, V), MapError>`. pub fn iter(&self) -> MapIter<'_, K, RawFd, Self> { MapIter::new(self) } /// An iterator visiting all keys in arbitrary order. The iterator element /// type is `Result`. pub fn keys(&self) -> MapKeys<'_, K> { MapKeys::new(self.inner.borrow()) } /// Returns the map's file descriptor. /// /// The returned file descriptor can be used to attach programs that work with /// socket maps, like [`SkMsg`](crate::programs::SkMsg) and [`SkSkb`](crate::programs::SkSkb). pub fn fd(&self) -> &SockMapFd { let fd: &MapFd = self.inner.borrow().fd(); // TODO(https://github.com/rust-lang/rfcs/issues/3066): avoid this unsafe. // SAFETY: `SockMapFd` is #[repr(transparent)] over `MapFd`. unsafe { std::mem::transmute(fd) } } } impl, K: Pod> SockHash { /// Inserts a socket under the given key. pub fn insert( &mut self, key: impl Borrow, value: I, flags: u64, ) -> Result<(), MapError> { hash_map::insert( self.inner.borrow_mut(), key.borrow(), &value.as_raw_fd(), flags, ) } /// Removes a socket from the map. pub fn remove(&mut self, key: &K) -> Result<(), MapError> { hash_map::remove(self.inner.borrow_mut(), key) } } impl, K: Pod> IterableMap for SockHash { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, key: &K) -> Result { Self::get(self, key, 0) } } aya-0.13.1/src/maps/sock/sock_map.rs000064400000000000000000000072731046102023000153210ustar 00000000000000//! An array of eBPF program file descriptors used as a jump table. use std::{ borrow::{Borrow, BorrowMut}, os::fd::{AsFd as _, AsRawFd, RawFd}, }; use crate::{ maps::{check_bounds, check_kv_size, sock::SockMapFd, MapData, MapError, MapFd, MapKeys}, sys::{bpf_map_delete_elem, bpf_map_update_elem, SyscallError}, }; /// An array of TCP or UDP sockets. /// /// A `SockMap` is used to store TCP or UDP sockets. eBPF programs can then be /// attached to the map to inspect, filter or redirect network buffers on those /// sockets. /// /// A `SockMap` can also be used to redirect packets to sockets contained by the /// map using `bpf_redirect_map()`, `bpf_sk_redirect_map()` etc. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.14. /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::SockMap; /// use aya::programs::SkSkb; /// /// let intercept_ingress = SockMap::try_from(bpf.map("INTERCEPT_INGRESS").unwrap())?; /// let map_fd = intercept_ingress.fd().try_clone()?; /// /// let prog: &mut SkSkb = bpf.program_mut("intercept_ingress_packet").unwrap().try_into()?; /// prog.load()?; /// prog.attach(&map_fd)?; /// /// # Ok::<(), Error>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_SOCKMAP")] pub struct SockMap { pub(crate) inner: T, } impl> SockMap { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::(data)?; Ok(Self { inner: map }) } /// An iterator over the indices of the array that point to a program. The iterator item type /// is `Result`. pub fn indices(&self) -> MapKeys<'_, u32> { MapKeys::new(self.inner.borrow()) } /// Returns the map's file descriptor. /// /// The returned file descriptor can be used to attach programs that work with /// socket maps, like [`SkMsg`](crate::programs::SkMsg) and [`SkSkb`](crate::programs::SkSkb). pub fn fd(&self) -> &SockMapFd { let fd: &MapFd = self.inner.borrow().fd(); // TODO(https://github.com/rust-lang/rfcs/issues/3066): avoid this unsafe. // SAFETY: `SockMapFd` is #[repr(transparent)] over `MapFd`. unsafe { std::mem::transmute(fd) } } } impl> SockMap { /// Stores a socket into the map. pub fn set(&mut self, index: u32, socket: &I, flags: u64) -> Result<(), MapError> { let data = self.inner.borrow_mut(); let fd = data.fd().as_fd(); check_bounds(data, index)?; bpf_map_update_elem(fd, Some(&index), &socket.as_raw_fd(), flags).map_err( |(_, io_error)| SyscallError { call: "bpf_map_update_elem", io_error, }, )?; Ok(()) } /// Removes the socket stored at `index` from the map. pub fn clear_index(&mut self, index: &u32) -> Result<(), MapError> { let data = self.inner.borrow_mut(); let fd = data.fd().as_fd(); check_bounds(data, *index)?; bpf_map_delete_elem(fd, index) .map(|_| ()) .map_err(|(_, io_error)| { SyscallError { call: "bpf_map_delete_elem", io_error, } .into() }) } } aya-0.13.1/src/maps/stack.rs000064400000000000000000000047401046102023000136670ustar 00000000000000//! A LIFO stack. use std::{ borrow::{Borrow, BorrowMut}, marker::PhantomData, os::fd::AsFd as _, }; use crate::{ maps::{check_kv_size, MapData, MapError}, sys::{bpf_map_lookup_and_delete_elem, bpf_map_update_elem, SyscallError}, Pod, }; /// A LIFO stack. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.20. /// /// # Examples /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::Stack; /// /// let mut stack = Stack::try_from(bpf.map_mut("STACK").unwrap())?; /// stack.push(42, 0)?; /// stack.push(43, 0)?; /// assert_eq!(stack.pop(0)?, 43); /// # Ok::<(), aya::EbpfError>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_STACK")] pub struct Stack { pub(crate) inner: T, _v: PhantomData, } impl, V: Pod> Stack { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::<(), V>(data)?; Ok(Self { inner: map, _v: PhantomData, }) } /// Returns the number of elements the stack can hold. /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn capacity(&self) -> u32 { self.inner.borrow().obj.max_entries() } } impl, V: Pod> Stack { /// Removes the last element and returns it. /// /// # Errors /// /// Returns [`MapError::ElementNotFound`] if the stack is empty, [`MapError::SyscallError`] /// if `bpf_map_lookup_and_delete_elem` fails. pub fn pop(&mut self, flags: u64) -> Result { let fd = self.inner.borrow().fd().as_fd(); let value = bpf_map_lookup_and_delete_elem::(fd, None, flags).map_err( |(_, io_error)| SyscallError { call: "bpf_map_lookup_and_delete_elem", io_error, }, )?; value.ok_or(MapError::ElementNotFound) } /// Pushes an element on the stack. /// /// # Errors /// /// [`MapError::SyscallError`] if `bpf_map_update_elem` fails. pub fn push(&mut self, value: impl Borrow, flags: u64) -> Result<(), MapError> { let fd = self.inner.borrow().fd().as_fd(); bpf_map_update_elem(fd, None::<&u32>, value.borrow(), flags).map_err(|(_, io_error)| { SyscallError { call: "bpf_map_update_elem", io_error, } })?; Ok(()) } } aya-0.13.1/src/maps/stack_trace.rs000064400000000000000000000144521046102023000150460ustar 00000000000000//! A hash map of kernel or user space stack traces. //! //! See [`StackTraceMap`] for documentation and examples. use std::{ borrow::{Borrow, BorrowMut}, fs, io, mem, os::fd::AsFd as _, path::Path, str::FromStr, }; use crate::{ maps::{IterableMap, MapData, MapError, MapIter, MapKeys}, sys::{bpf_map_delete_elem, bpf_map_lookup_elem_ptr, SyscallError}, }; /// A hash map of kernel or user space stack traces. /// /// Stack trace maps can be used to store stack traces captured by eBPF programs, which can be /// useful for profiling, to associate a trace to an event, etc. You can capture traces calling /// `stack_id = bpf_get_stackid(ctx, map, flags)` from eBPF, and then you can retrieve the traces /// from their stack ids. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.6. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let bpf = aya::Ebpf::load(&[])?; /// use aya::maps::StackTraceMap; /// use aya::util::kernel_symbols; /// /// let mut stack_traces = StackTraceMap::try_from(bpf.map("STACK_TRACES").unwrap())?; /// // load kernel symbols from /proc/kallsyms /// let ksyms = kernel_symbols()?; /// /// // NOTE: you typically send stack_ids from eBPF to user space using other maps /// let stack_id = 1234; /// let mut stack_trace = stack_traces.get(&stack_id, 0)?; /// /// // here we resolve symbol names using kernel symbols. If this was a user space stack (for /// // example captured from a uprobe), you'd have to load the symbols using some other mechanism /// // (eg loading the target binary debuginfo) /// for frame in stack_trace.frames() { /// if let Some(sym) = ksyms.range(..=frame.ip).next_back().map(|(_, s)| s) { /// println!( /// "{:#x} {}", /// frame.ip, /// sym /// ); /// } else { /// println!( /// "{:#x}", /// frame.ip /// ); /// } /// } /// /// # Ok::<(), Error>(()) /// ``` /// #[derive(Debug)] #[doc(alias = "BPF_MAP_TYPE_STACK_TRACE")] pub struct StackTraceMap { pub(crate) inner: T, max_stack_depth: usize, } impl> StackTraceMap { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); let expected = mem::size_of::(); let size = data.obj.key_size() as usize; if size != expected { return Err(MapError::InvalidKeySize { size, expected }); } let max_stack_depth = sysctl::("kernel/perf_event_max_stack").map_err(|io_error| SyscallError { call: "sysctl", io_error, })?; let size = data.obj.value_size() as usize; if size > max_stack_depth * mem::size_of::() { return Err(MapError::InvalidValueSize { size, expected }); } Ok(Self { inner: map, max_stack_depth, }) } /// Returns the stack trace with the given stack_id. /// /// # Errors /// /// Returns [`MapError::KeyNotFound`] if there is no stack trace with the /// given `stack_id`, or [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails. pub fn get(&self, stack_id: &u32, flags: u64) -> Result { let fd = self.inner.borrow().fd().as_fd(); let mut frames = vec![0; self.max_stack_depth]; bpf_map_lookup_elem_ptr(fd, Some(stack_id), frames.as_mut_ptr(), flags) .map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })? .ok_or(MapError::KeyNotFound)?; let frames = frames .into_iter() .take_while(|ip| *ip != 0) .map(|ip| StackFrame { ip }) .collect::>(); Ok(StackTrace { id: *stack_id, frames, }) } /// An iterator visiting all (`stack_id`, `stack_trace`) pairs in arbitrary order. The /// iterator item type is `Result<(u32, StackTrace), MapError>`. pub fn iter(&self) -> MapIter<'_, u32, StackTrace, Self> { MapIter::new(self) } /// An iterator visiting all the stack_ids in arbitrary order. The iterator element /// type is `Result`. pub fn stack_ids(&self) -> MapKeys<'_, u32> { MapKeys::new(self.inner.borrow()) } } impl> IterableMap for StackTraceMap { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, index: &u32) -> Result { self.get(index, 0) } } impl<'a, T: Borrow> IntoIterator for &'a StackTraceMap { type Item = Result<(u32, StackTrace), MapError>; type IntoIter = MapIter<'a, u32, StackTrace, StackTraceMap>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl> StackTraceMap { /// Removes the stack trace with the given stack_id. pub fn remove(&mut self, stack_id: &u32) -> Result<(), MapError> { let fd = self.inner.borrow().fd().as_fd(); bpf_map_delete_elem(fd, stack_id) .map(|_| ()) .map_err(|(_, io_error)| { SyscallError { call: "bpf_map_delete_elem", io_error, } .into() }) } } /// A kernel or user space stack trace. /// /// See the [`StackTraceMap`] documentation for examples. pub struct StackTrace { /// The stack trace id as returned by `bpf_get_stackid()`. pub id: u32, frames: Vec, } impl StackTrace { /// Returns the frames in this stack trace. pub fn frames(&self) -> &[StackFrame] { &self.frames } } /// A stack frame. pub struct StackFrame { /// The instruction pointer of this frame. pub ip: u64, } fn sysctl(key: &str) -> Result { let val = fs::read_to_string(Path::new("/proc/sys").join(key))?; val.trim() .parse::() .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, val)) } aya-0.13.1/src/maps/xdp/cpu_map.rs000064400000000000000000000146051046102023000150020ustar 00000000000000//! An array of available CPUs. use std::{ borrow::{Borrow, BorrowMut}, num::NonZeroU32, os::fd::{AsFd, AsRawFd}, }; use aya_obj::generated::bpf_cpumap_val; use super::XdpMapError; use crate::{ maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError}, programs::ProgramFd, sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError}, Pod, FEATURES, }; /// An array of available CPUs. /// /// XDP programs can use this map to redirect packets to a target /// CPU for processing. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.15. /// /// # Examples /// ```no_run /// # let elf_bytes = &[]; /// use aya::maps::xdp::CpuMap; /// use aya::util::nr_cpus; /// /// let nr_cpus = nr_cpus().unwrap() as u32; /// let mut bpf = aya::EbpfLoader::new() /// .set_max_entries("CPUS", nr_cpus) /// .load(elf_bytes) /// .unwrap(); /// let mut cpumap = CpuMap::try_from(bpf.map_mut("CPUS").unwrap())?; /// let flags = 0; /// let queue_size = 2048; /// for i in 0..nr_cpus { /// cpumap.set(i, queue_size, None, flags); /// } /// /// # Ok::<(), aya::EbpfError>(()) /// ``` /// /// # See also /// /// Kernel documentation: #[doc(alias = "BPF_MAP_TYPE_CPUMAP")] pub struct CpuMap { pub(crate) inner: T, } impl> CpuMap { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); if FEATURES.cpumap_prog_id() { check_kv_size::(data)?; } else { check_kv_size::(data)?; } Ok(Self { inner: map }) } /// Returns the number of elements in the array. /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn len(&self) -> u32 { self.inner.borrow().obj.max_entries() } /// Returns the queue size and optional program for a given CPU index. /// /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `cpu_index` is out of bounds, /// [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails. pub fn get(&self, cpu_index: u32, flags: u64) -> Result { let data = self.inner.borrow(); check_bounds(data, cpu_index)?; let fd = data.fd().as_fd(); let value = if FEATURES.cpumap_prog_id() { bpf_map_lookup_elem::<_, bpf_cpumap_val>(fd, &cpu_index, flags).map(|value| { value.map(|value| CpuMapValue { queue_size: value.qsize, // SAFETY: map writes use fd, map reads use id. // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6241 prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }), }) }) } else { bpf_map_lookup_elem::<_, u32>(fd, &cpu_index, flags).map(|value| { value.map(|qsize| CpuMapValue { queue_size: qsize, prog_id: None, }) }) }; value .map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })? .ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the map. pub fn iter(&self) -> impl Iterator> + '_ { (0..self.len()).map(move |i| self.get(i, 0)) } } impl> CpuMap { /// Sets the queue size at the given CPU index, and optionally a chained program. /// /// When sending the packet to the CPU at the given index, the kernel will queue up to /// `queue_size` packets before dropping them. /// /// Starting from Linux kernel 5.9, another XDP program can be passed in that will be run on the /// target CPU, instead of the CPU that receives the packets. This allows to perform minimal /// computations on CPUs that directly handle packets from a NIC's RX queues, and perform /// possibly heavier ones in other, less busy CPUs. /// /// The chained program must be loaded with the `BPF_XDP_CPUMAP` attach type. When using /// `aya-ebpf`, that means XDP programs that specify the `map = "cpumap"` argument. See the /// kernel-space `aya_ebpf::xdp` for more information. /// /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] /// if `bpf_map_update_elem` fails, [`XdpMapError::ChainedProgramNotSupported`] if the kernel /// does not support chained programs and one is provided. pub fn set( &mut self, cpu_index: u32, queue_size: u32, program: Option<&ProgramFd>, flags: u64, ) -> Result<(), XdpMapError> { let data = self.inner.borrow_mut(); check_bounds(data, cpu_index)?; let fd = data.fd().as_fd(); let res = if FEATURES.cpumap_prog_id() { let mut value = unsafe { std::mem::zeroed::() }; value.qsize = queue_size; // Default is valid as the kernel will only consider fd > 0: // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466 value.bpf_prog.fd = program .map(|prog| prog.as_fd().as_raw_fd()) .unwrap_or_default(); bpf_map_update_elem(fd, Some(&cpu_index), &value, flags) } else { if program.is_some() { return Err(XdpMapError::ChainedProgramNotSupported); } bpf_map_update_elem(fd, Some(&cpu_index), &queue_size, flags) }; res.map_err(|(_, io_error)| { MapError::from(SyscallError { call: "bpf_map_update_elem", io_error, }) })?; Ok(()) } } impl> IterableMap for CpuMap { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, key: &u32) -> Result { self.get(*key, 0) } } unsafe impl Pod for bpf_cpumap_val {} #[derive(Clone, Copy, Debug)] /// The value of a CPU map. pub struct CpuMapValue { /// Size of the for the CPU. pub queue_size: u32, /// Chained XDP program ID. pub prog_id: Option, } aya-0.13.1/src/maps/xdp/dev_map.rs000064400000000000000000000143241046102023000147670ustar 00000000000000//! An array of network devices. use std::{ borrow::{Borrow, BorrowMut}, num::NonZeroU32, os::fd::{AsFd, AsRawFd}, }; use aya_obj::generated::bpf_devmap_val; use super::XdpMapError; use crate::{ maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError}, programs::ProgramFd, sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError}, Pod, FEATURES, }; /// An array of network devices. /// /// XDP programs can use this map to redirect to other network /// devices. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.14. /// /// # Examples /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::xdp::DevMap; /// /// let mut devmap = DevMap::try_from(bpf.map_mut("IFACES").unwrap())?; /// // Lookups at index 2 will redirect packets to interface with index 3 (e.g. eth1) /// devmap.set(2, 3, None, 0); /// /// # Ok::<(), aya::EbpfError>(()) /// ``` /// /// # See also /// /// Kernel documentation: #[doc(alias = "BPF_MAP_TYPE_DEVMAP")] pub struct DevMap { pub(crate) inner: T, } impl> DevMap { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); if FEATURES.devmap_prog_id() { check_kv_size::(data)?; } else { check_kv_size::(data)?; } Ok(Self { inner: map }) } /// Returns the number of elements in the array. /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn len(&self) -> u32 { self.inner.borrow().obj.max_entries() } /// Returns the target interface index and optional program at a given index. /// /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] /// if `bpf_map_lookup_elem` fails. pub fn get(&self, index: u32, flags: u64) -> Result { let data = self.inner.borrow(); check_bounds(data, index)?; let fd = data.fd().as_fd(); let value = if FEATURES.devmap_prog_id() { bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &index, flags).map(|value| { value.map(|value| DevMapValue { if_index: value.ifindex, // SAFETY: map writes use fd, map reads use id. // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228 prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }), }) }) } else { bpf_map_lookup_elem::<_, u32>(fd, &index, flags).map(|value| { value.map(|ifindex| DevMapValue { if_index: ifindex, prog_id: None, }) }) }; value .map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })? .ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the array. pub fn iter(&self) -> impl Iterator> + '_ { (0..self.len()).map(move |i| self.get(i, 0)) } } impl> DevMap { /// Sets the target interface index at index, and optionally a chained program. /// /// When redirecting using `index`, packets will be transmitted by the interface with /// `target_if_index`. /// /// Starting from Linux kernel 5.8, another XDP program can be passed in that will be run before /// actual transmission. It can be used to modify the packet before transmission with NIC /// specific data (MAC address update, checksum computations, etc) or other purposes. /// /// The chained program must be loaded with the `BPF_XDP_DEVMAP` attach type. When using /// `aya-ebpf`, that means XDP programs that specify the `map = "devmap"` argument. See the /// kernel-space `aya_ebpf::xdp` for more information. /// /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] /// if `bpf_map_update_elem` fails, [`MapError::ProgIdNotSupported`] if the kernel does not /// support chained programs and one is provided. pub fn set( &mut self, index: u32, target_if_index: u32, program: Option<&ProgramFd>, flags: u64, ) -> Result<(), XdpMapError> { let data = self.inner.borrow_mut(); check_bounds(data, index)?; let fd = data.fd().as_fd(); let res = if FEATURES.devmap_prog_id() { let mut value = unsafe { std::mem::zeroed::() }; value.ifindex = target_if_index; // Default is valid as the kernel will only consider fd > 0: // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866 // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918 value.bpf_prog.fd = program .map(|prog| prog.as_fd().as_raw_fd()) .unwrap_or_default(); bpf_map_update_elem(fd, Some(&index), &value, flags) } else { if program.is_some() { return Err(XdpMapError::ChainedProgramNotSupported); } bpf_map_update_elem(fd, Some(&index), &target_if_index, flags) }; res.map_err(|(_, io_error)| { MapError::from(SyscallError { call: "bpf_map_update_elem", io_error, }) })?; Ok(()) } } impl> IterableMap for DevMap { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, key: &u32) -> Result { self.get(*key, 0) } } unsafe impl Pod for bpf_devmap_val {} #[derive(Clone, Copy, Debug)] /// The value of a device map. pub struct DevMapValue { /// Target interface index to redirect to. pub if_index: u32, /// Chained XDP program ID. pub prog_id: Option, } aya-0.13.1/src/maps/xdp/dev_map_hash.rs000064400000000000000000000132551046102023000157740ustar 00000000000000//! An hashmap of network devices. use std::{ borrow::{Borrow, BorrowMut}, num::NonZeroU32, os::fd::{AsFd, AsRawFd}, }; use aya_obj::generated::bpf_devmap_val; use super::{dev_map::DevMapValue, XdpMapError}; use crate::{ maps::{check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys}, programs::ProgramFd, sys::{bpf_map_lookup_elem, SyscallError}, FEATURES, }; /// An hashmap of network devices. /// /// XDP programs can use this map to redirect to other network /// devices. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.4. /// /// # Examples /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::xdp::DevMapHash; /// /// let mut devmap = DevMapHash::try_from(bpf.map_mut("IFACES").unwrap())?; /// // Lookups with key 2 will redirect packets to interface with index 3 (e.g. eth1) /// devmap.insert(2, 3, None, 0); /// /// # Ok::<(), aya::EbpfError>(()) /// ``` /// /// # See also /// /// Kernel documentation: #[doc(alias = "BPF_MAP_TYPE_DEVMAP_HASH")] pub struct DevMapHash { pub(crate) inner: T, } impl> DevMapHash { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); if FEATURES.devmap_prog_id() { check_kv_size::(data)?; } else { check_kv_size::(data)?; } Ok(Self { inner: map }) } /// Returns the target interface index and optional program for a given key. /// /// # Errors /// /// Returns [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails. pub fn get(&self, key: u32, flags: u64) -> Result { let fd = self.inner.borrow().fd().as_fd(); let value = if FEATURES.devmap_prog_id() { bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &key, flags).map(|value| { value.map(|value| DevMapValue { if_index: value.ifindex, // SAFETY: map writes use fd, map reads use id. // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228 prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }), }) }) } else { bpf_map_lookup_elem::<_, u32>(fd, &key, flags).map(|value| { value.map(|ifindex| DevMapValue { if_index: ifindex, prog_id: None, }) }) }; value .map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, })? .ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the devmap in arbitrary order. pub fn iter(&self) -> MapIter<'_, u32, DevMapValue, Self> { MapIter::new(self) } /// An iterator visiting all keys in arbitrary order. pub fn keys(&self) -> MapKeys<'_, u32> { MapKeys::new(self.inner.borrow()) } } impl> DevMapHash { /// Inserts an ifindex and optionally a chained program in the map. /// /// When redirecting using `key`, packets will be transmitted by the interface with `ifindex`. /// /// Starting from Linux kernel 5.8, another XDP program can be passed in that will be run before /// actual transmission. It can be used to modify the packet before transmission with NIC /// specific data (MAC address update, checksum computations, etc) or other purposes. /// /// The chained program must be loaded with the `BPF_XDP_DEVMAP` attach type. When using /// `aya-ebpf`, that means XDP programs that specify the `map = "devmap"` argument. See the /// kernel-space `aya_ebpf::xdp` for more information. /// /// # Errors /// /// Returns [`MapError::SyscallError`] if `bpf_map_update_elem` fails, /// [`MapError::ProgIdNotSupported`] if the kernel does not support chained programs and one is /// provided. pub fn insert( &mut self, key: u32, target_if_index: u32, program: Option<&ProgramFd>, flags: u64, ) -> Result<(), XdpMapError> { if FEATURES.devmap_prog_id() { let mut value = unsafe { std::mem::zeroed::() }; value.ifindex = target_if_index; // Default is valid as the kernel will only consider fd > 0: // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866 // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918 value.bpf_prog.fd = program .map(|prog| prog.as_fd().as_raw_fd()) .unwrap_or_default(); hash_map::insert(self.inner.borrow_mut(), &key, &value, flags)?; } else { if program.is_some() { return Err(XdpMapError::ChainedProgramNotSupported); } hash_map::insert(self.inner.borrow_mut(), &key, &target_if_index, flags)?; } Ok(()) } /// Removes a value from the map. /// /// # Errors /// /// Returns [`MapError::SyscallError`] if `bpf_map_delete_elem` fails. pub fn remove(&mut self, key: u32) -> Result<(), MapError> { hash_map::remove(self.inner.borrow_mut(), &key) } } impl> IterableMap for DevMapHash { fn map(&self) -> &MapData { self.inner.borrow() } fn get(&self, key: &u32) -> Result { self.get(*key, 0) } } aya-0.13.1/src/maps/xdp/mod.rs000064400000000000000000000010611046102023000141250ustar 00000000000000//! XDP maps. mod cpu_map; mod dev_map; mod dev_map_hash; mod xsk_map; pub use cpu_map::CpuMap; pub use dev_map::DevMap; pub use dev_map_hash::DevMapHash; use thiserror::Error; pub use xsk_map::XskMap; use super::MapError; #[derive(Error, Debug)] /// Errors occuring from working with XDP maps. pub enum XdpMapError { /// Chained programs are not supported. #[error("chained programs are not supported by the current kernel")] ChainedProgramNotSupported, /// Map operation failed. #[error(transparent)] MapError(#[from] MapError), } aya-0.13.1/src/maps/xdp/xsk_map.rs000064400000000000000000000045461046102023000150230ustar 00000000000000//! An array of AF_XDP sockets. use std::{ borrow::{Borrow, BorrowMut}, os::fd::{AsFd, AsRawFd, RawFd}, }; use crate::{ maps::{check_bounds, check_kv_size, MapData, MapError}, sys::{bpf_map_update_elem, SyscallError}, }; /// An array of AF_XDP sockets. /// /// XDP programs can use this map to redirect packets to a target /// AF_XDP socket using the `XDP_REDIRECT` action. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.18. /// /// # Examples /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// # let socket_fd = 1; /// use aya::maps::XskMap; /// /// let mut xskmap = XskMap::try_from(bpf.map_mut("SOCKETS").unwrap())?; /// // socket_fd is the RawFd of an AF_XDP socket /// xskmap.set(0, socket_fd, 0); /// # Ok::<(), aya::EbpfError>(()) /// ``` /// /// # See also /// /// Kernel documentation: #[doc(alias = "BPF_MAP_TYPE_XSKMAP")] pub struct XskMap { pub(crate) inner: T, } impl> XskMap { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); check_kv_size::(data)?; Ok(Self { inner: map }) } /// Returns the number of elements in the array. /// /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. pub fn len(&self) -> u32 { self.inner.borrow().obj.max_entries() } } impl> XskMap { /// Sets the `AF_XDP` socket at a given index. /// /// When redirecting a packet, the `AF_XDP` socket at `index` will recieve the packet. Note /// that it will do so only if the socket is bound to the same queue the packet was recieved /// on. /// /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] /// if `bpf_map_update_elem` fails. pub fn set(&mut self, index: u32, socket_fd: impl AsRawFd, flags: u64) -> Result<(), MapError> { let data = self.inner.borrow_mut(); check_bounds(data, index)?; let fd = data.fd().as_fd(); bpf_map_update_elem(fd, Some(&index), &socket_fd.as_raw_fd(), flags).map_err( |(_, io_error)| SyscallError { call: "bpf_map_update_elem", io_error, }, )?; Ok(()) } } aya-0.13.1/src/pin.rs000064400000000000000000000013561046102023000124100ustar 00000000000000//! Pinning BPF objects to the BPF filesystem. use thiserror::Error; use crate::sys::SyscallError; /// An error ocurred working with a pinned BPF object. #[derive(Error, Debug)] pub enum PinError { /// The object FD is not known by Aya. #[error("the BPF object `{name}`'s FD is not known")] NoFd { /// Object name. name: String, }, /// The path for the BPF object is not valid. #[error("invalid pin path `{}`", path.display())] InvalidPinPath { /// The path. path: std::path::PathBuf, #[source] /// The source error. error: std::ffi::NulError, }, /// An error ocurred making a syscall. #[error(transparent)] SyscallError(#[from] SyscallError), } aya-0.13.1/src/programs/cgroup_device.rs000064400000000000000000000134421046102023000162710ustar 00000000000000//! Cgroup device programs. use std::os::fd::AsFd; use crate::{ generated::{bpf_attach_type::BPF_CGROUP_DEVICE, bpf_prog_type::BPF_PROG_TYPE_CGROUP_DEVICE}, programs::{ bpf_prog_get_fd_by_id, define_link_wrapper, load_program, query, CgroupAttachMode, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, ProgramFd, }, sys::{bpf_link_create, LinkTarget, ProgQueryTarget, SyscallError}, util::KernelVersion, }; /// A program used to watch or prevent device interaction from a cgroup. /// /// [`CgroupDevice`] programs can be attached to a cgroup and will be called every /// time a process inside that cgroup tries to access (e.g. read, write, mknod) /// a device (identified through its major and minor number). See /// [mknod](https://man7.org/linux/man-pages/man2/mknod.2.html) as a starting point. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is [4.15](https://github.com/torvalds/linux/commit/ebc614f687369f9df99828572b1d85a7c2de3d92). /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::programs::{CgroupAttachMode, CgroupDevice}; /// /// let cgroup = std::fs::File::open("/sys/fs/cgroup/unified")?; /// let program: &mut CgroupDevice = bpf.program_mut("cgroup_dev").unwrap().try_into()?; /// program.load()?; /// program.attach(cgroup, CgroupAttachMode::Single)?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_CGROUP_DEVICE")] pub struct CgroupDevice { pub(crate) data: ProgramData, } impl CgroupDevice { /// Loads the program inside the kernel pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_CGROUP_DEVICE, &mut self.data) } /// Attaches the program to the given cgroup. /// /// The returned value can be used to detach, see [CgroupDevice::detach] pub fn attach( &mut self, cgroup: T, mode: CgroupAttachMode, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let cgroup_fd = cgroup.as_fd(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(cgroup_fd), BPF_CGROUP_DEVICE, None, mode.into(), None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(CgroupDeviceLink::new(CgroupDeviceLinkInner::Fd( FdLink::new(link_fd), ))) } else { let link = ProgAttachLink::attach(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, mode)?; self.data .links .insert(CgroupDeviceLink::new(CgroupDeviceLinkInner::ProgAttach( link, ))) } } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link( &mut self, link_id: CgroupDeviceLinkId, ) -> Result { self.data.take_link(link_id) } /// Detaches the program /// /// See [CgroupDevice::attach]. pub fn detach(&mut self, link_id: CgroupDeviceLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Queries the cgroup for attached programs. pub fn query(target_fd: T) -> Result, ProgramError> { let target_fd = target_fd.as_fd(); let (_, prog_ids) = query( ProgQueryTarget::Fd(target_fd), BPF_CGROUP_DEVICE, 0, &mut None, )?; prog_ids .into_iter() .map(|prog_id| { let prog_fd = bpf_prog_get_fd_by_id(prog_id)?; let target_fd = target_fd.try_clone_to_owned()?; let target_fd = crate::MockableFd::from_fd(target_fd); let prog_fd = ProgramFd(prog_fd); Ok(CgroupDeviceLink::new(CgroupDeviceLinkInner::ProgAttach( ProgAttachLink::new(prog_fd, target_fd, BPF_CGROUP_DEVICE), ))) }) .collect() } } #[derive(Debug, Hash, Eq, PartialEq)] enum CgroupDeviceLinkIdInner { Fd(::Id), ProgAttach(::Id), } #[derive(Debug)] enum CgroupDeviceLinkInner { Fd(FdLink), ProgAttach(ProgAttachLink), } impl Link for CgroupDeviceLinkInner { type Id = CgroupDeviceLinkIdInner; fn id(&self) -> Self::Id { match self { Self::Fd(fd) => CgroupDeviceLinkIdInner::Fd(fd.id()), Self::ProgAttach(p) => CgroupDeviceLinkIdInner::ProgAttach(p.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::Fd(fd) => fd.detach(), Self::ProgAttach(p) => p.detach(), } } } define_link_wrapper!( /// The link used by [CgroupDevice] programs. CgroupDeviceLink, /// The type returned by [CgroupDevice::attach]. Can be passed to [CgroupDevice::detach]. CgroupDeviceLinkId, CgroupDeviceLinkInner, CgroupDeviceLinkIdInner ); aya-0.13.1/src/programs/cgroup_skb.rs000064400000000000000000000152311046102023000156070ustar 00000000000000//! Cgroup skb programs. use std::{hash::Hash, os::fd::AsFd, path::Path}; use crate::{ generated::{ bpf_attach_type::{BPF_CGROUP_INET_EGRESS, BPF_CGROUP_INET_INGRESS}, bpf_prog_type::BPF_PROG_TYPE_CGROUP_SKB, }, programs::{ define_link_wrapper, load_program, CgroupAttachMode, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, }, sys::{bpf_link_create, LinkTarget, SyscallError}, util::KernelVersion, VerifierLogLevel, }; /// A program used to inspect or filter network activity for a given cgroup. /// /// [`CgroupSkb`] programs can be used to inspect or filter network activity /// generated on all the sockets belonging to a given [cgroup]. They can be /// attached to both _ingress_ and _egress_. /// /// [cgroup]: https://man7.org/linux/man-pages/man7/cgroups.7.html /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.10. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::fs::File; /// use aya::programs::{CgroupAttachMode, CgroupSkb, CgroupSkbAttachType}; /// /// let file = File::open("/sys/fs/cgroup/unified")?; /// let egress: &mut CgroupSkb = bpf.program_mut("egress_filter").unwrap().try_into()?; /// egress.load()?; /// egress.attach(file, CgroupSkbAttachType::Egress, CgroupAttachMode::Single)?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_CGROUP_SKB")] pub struct CgroupSkb { pub(crate) data: ProgramData, pub(crate) expected_attach_type: Option, } impl CgroupSkb { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { self.data.expected_attach_type = self.expected_attach_type .map(|attach_type| match attach_type { CgroupSkbAttachType::Ingress => BPF_CGROUP_INET_INGRESS, CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS, }); load_program(BPF_PROG_TYPE_CGROUP_SKB, &mut self.data) } /// Returns the expected attach type of the program. /// /// [`CgroupSkb`] programs can specify the expected attach type in their ELF /// section name, eg `cgroup_skb/ingress` or `cgroup_skb/egress`. This /// method returns `None` for programs defined with the generic section /// `cgroup/skb`. pub fn expected_attach_type(&self) -> &Option { &self.expected_attach_type } /// Attaches the program to the given cgroup. /// /// The returned value can be used to detach, see [CgroupSkb::detach]. pub fn attach( &mut self, cgroup: T, attach_type: CgroupSkbAttachType, mode: CgroupAttachMode, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let cgroup_fd = cgroup.as_fd(); let attach_type = match attach_type { CgroupSkbAttachType::Ingress => BPF_CGROUP_INET_INGRESS, CgroupSkbAttachType::Egress => BPF_CGROUP_INET_EGRESS, }; if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(cgroup_fd), attach_type, None, mode.into(), None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(CgroupSkbLink::new(CgroupSkbLinkInner::Fd(FdLink::new( link_fd, )))) } else { let link = ProgAttachLink::attach(prog_fd, cgroup_fd, attach_type, mode)?; self.data .links .insert(CgroupSkbLink::new(CgroupSkbLinkInner::ProgAttach(link))) } } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: CgroupSkbLinkId) -> Result { self.data.take_link(link_id) } /// Detaches the program. /// /// See [CgroupSkb::attach]. pub fn detach(&mut self, link_id: CgroupSkbLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>( path: P, expected_attach_type: CgroupSkbAttachType, ) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, expected_attach_type: Some(expected_attach_type), }) } } #[derive(Debug, Hash, Eq, PartialEq)] enum CgroupSkbLinkIdInner { Fd(::Id), ProgAttach(::Id), } #[derive(Debug)] enum CgroupSkbLinkInner { Fd(FdLink), ProgAttach(ProgAttachLink), } impl Link for CgroupSkbLinkInner { type Id = CgroupSkbLinkIdInner; fn id(&self) -> Self::Id { match self { Self::Fd(fd) => CgroupSkbLinkIdInner::Fd(fd.id()), Self::ProgAttach(p) => CgroupSkbLinkIdInner::ProgAttach(p.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::Fd(fd) => fd.detach(), Self::ProgAttach(p) => p.detach(), } } } define_link_wrapper!( /// The link used by [CgroupSkb] programs. CgroupSkbLink, /// The type returned by [CgroupSkb::attach]. Can be passed to [CgroupSkb::detach]. CgroupSkbLinkId, CgroupSkbLinkInner, CgroupSkbLinkIdInner ); /// Defines where to attach a [`CgroupSkb`] program. #[derive(Copy, Clone, Debug)] pub enum CgroupSkbAttachType { /// Attach to ingress. Ingress, /// Attach to egress. Egress, } aya-0.13.1/src/programs/cgroup_sock.rs000064400000000000000000000127121046102023000157700ustar 00000000000000//! Cgroup socket programs. use std::{hash::Hash, os::fd::AsFd, path::Path}; pub use aya_obj::programs::CgroupSockAttachType; use crate::{ generated::bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK, programs::{ define_link_wrapper, load_program, CgroupAttachMode, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, }, sys::{bpf_link_create, LinkTarget, SyscallError}, util::KernelVersion, VerifierLogLevel, }; /// A program that is called on socket creation, bind and release. /// /// [`CgroupSock`] programs can be used to allow or deny socket creation from /// within a [cgroup], or they can be used to monitor and gather statistics. /// /// [cgroup]: https://man7.org/linux/man-pages/man7/cgroups.7.html /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.10. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::fs::File; /// use aya::programs::{CgroupAttachMode, CgroupSock, CgroupSockAttachType}; /// /// let file = File::open("/sys/fs/cgroup/unified")?; /// let bind: &mut CgroupSock = bpf.program_mut("bind").unwrap().try_into()?; /// bind.load()?; /// bind.attach(file, CgroupAttachMode::Single)?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK")] pub struct CgroupSock { pub(crate) data: ProgramData, pub(crate) attach_type: CgroupSockAttachType, } impl CgroupSock { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(self.attach_type.into()); load_program(BPF_PROG_TYPE_CGROUP_SOCK, &mut self.data) } /// Attaches the program to the given cgroup. /// /// The returned value can be used to detach, see [CgroupSock::detach]. pub fn attach( &mut self, cgroup: T, mode: CgroupAttachMode, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let cgroup_fd = cgroup.as_fd(); let attach_type = self.data.expected_attach_type.unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(cgroup_fd), attach_type, None, mode.into(), None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(CgroupSockLink::new(CgroupSockLinkInner::Fd(FdLink::new( link_fd, )))) } else { let link = ProgAttachLink::attach(prog_fd, cgroup_fd, attach_type, mode)?; self.data .links .insert(CgroupSockLink::new(CgroupSockLinkInner::ProgAttach(link))) } } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: CgroupSockLinkId) -> Result { self.data.take_link(link_id) } /// Detaches the program. /// /// See [CgroupSock::attach]. pub fn detach(&mut self, link_id: CgroupSockLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>( path: P, attach_type: CgroupSockAttachType, ) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, attach_type }) } } #[derive(Debug, Hash, Eq, PartialEq)] enum CgroupSockLinkIdInner { Fd(::Id), ProgAttach(::Id), } #[derive(Debug)] enum CgroupSockLinkInner { Fd(FdLink), ProgAttach(ProgAttachLink), } impl Link for CgroupSockLinkInner { type Id = CgroupSockLinkIdInner; fn id(&self) -> Self::Id { match self { Self::Fd(fd) => CgroupSockLinkIdInner::Fd(fd.id()), Self::ProgAttach(p) => CgroupSockLinkIdInner::ProgAttach(p.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::Fd(fd) => fd.detach(), Self::ProgAttach(p) => p.detach(), } } } define_link_wrapper!( /// The link used by [CgroupSock] programs. CgroupSockLink, /// The type returned by [CgroupSock::attach]. Can be passed to [CgroupSock::detach]. CgroupSockLinkId, CgroupSockLinkInner, CgroupSockLinkIdInner ); aya-0.13.1/src/programs/cgroup_sock_addr.rs000064400000000000000000000133461046102023000167660ustar 00000000000000//! Cgroup socket address programs. use std::{hash::Hash, os::fd::AsFd, path::Path}; pub use aya_obj::programs::CgroupSockAddrAttachType; use crate::{ generated::bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK_ADDR, programs::{ define_link_wrapper, load_program, CgroupAttachMode, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, }, sys::{bpf_link_create, LinkTarget, SyscallError}, util::KernelVersion, VerifierLogLevel, }; /// A program that can be used to inspect or modify socket addresses (`struct sockaddr`). /// /// [`CgroupSockAddr`] programs can be used to inspect or modify socket addresses passed to /// various syscalls within a [cgroup]. They can be attached to a number of different /// places as described in [`CgroupSockAddrAttachType`]. /// /// [cgroup]: https://man7.org/linux/man-pages/man7/cgroups.7.html /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.17. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::fs::File; /// use aya::programs::{CgroupAttachMode, CgroupSockAddr, CgroupSockAddrAttachType}; /// /// let file = File::open("/sys/fs/cgroup/unified")?; /// let egress: &mut CgroupSockAddr = bpf.program_mut("connect4").unwrap().try_into()?; /// egress.load()?; /// egress.attach(file, CgroupAttachMode::Single)?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK_ADDR")] pub struct CgroupSockAddr { pub(crate) data: ProgramData, pub(crate) attach_type: CgroupSockAddrAttachType, } impl CgroupSockAddr { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(self.attach_type.into()); load_program(BPF_PROG_TYPE_CGROUP_SOCK_ADDR, &mut self.data) } /// Attaches the program to the given cgroup. /// /// The returned value can be used to detach, see [CgroupSockAddr::detach]. pub fn attach( &mut self, cgroup: T, mode: CgroupAttachMode, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let cgroup_fd = cgroup.as_fd(); let attach_type = self.data.expected_attach_type.unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(cgroup_fd), attach_type, None, mode.into(), None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(CgroupSockAddrLink::new(CgroupSockAddrLinkInner::Fd( FdLink::new(link_fd), ))) } else { let link = ProgAttachLink::attach(prog_fd, cgroup_fd, attach_type, mode)?; self.data.links.insert(CgroupSockAddrLink::new( CgroupSockAddrLinkInner::ProgAttach(link), )) } } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link( &mut self, link_id: CgroupSockAddrLinkId, ) -> Result { self.data.take_link(link_id) } /// Detaches the program. /// /// See [CgroupSockAddr::attach]. pub fn detach(&mut self, link_id: CgroupSockAddrLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>( path: P, attach_type: CgroupSockAddrAttachType, ) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, attach_type }) } } #[derive(Debug, Hash, Eq, PartialEq)] enum CgroupSockAddrLinkIdInner { Fd(::Id), ProgAttach(::Id), } #[derive(Debug)] enum CgroupSockAddrLinkInner { Fd(FdLink), ProgAttach(ProgAttachLink), } impl Link for CgroupSockAddrLinkInner { type Id = CgroupSockAddrLinkIdInner; fn id(&self) -> Self::Id { match self { Self::Fd(fd) => CgroupSockAddrLinkIdInner::Fd(fd.id()), Self::ProgAttach(p) => CgroupSockAddrLinkIdInner::ProgAttach(p.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::Fd(fd) => fd.detach(), Self::ProgAttach(p) => p.detach(), } } } define_link_wrapper!( /// The link used by [CgroupSockAddr] programs. CgroupSockAddrLink, /// The type returned by [CgroupSockAddr::attach]. Can be passed to [CgroupSockAddr::detach]. CgroupSockAddrLinkId, CgroupSockAddrLinkInner, CgroupSockAddrLinkIdInner ); aya-0.13.1/src/programs/cgroup_sockopt.rs000064400000000000000000000130431046102023000165110ustar 00000000000000//! Cgroup socket option programs. use std::{hash::Hash, os::fd::AsFd, path::Path}; pub use aya_obj::programs::CgroupSockoptAttachType; use crate::{ generated::bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCKOPT, programs::{ define_link_wrapper, load_program, CgroupAttachMode, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, }, sys::{bpf_link_create, LinkTarget, SyscallError}, util::KernelVersion, VerifierLogLevel, }; /// A program that can be used to get or set options on sockets. /// /// [`CgroupSockopt`] programs can be attached to a cgroup and will be called every /// time a process executes getsockopt or setsockopt system call. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.3. /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::fs::File; /// use aya::programs::{CgroupAttachMode, CgroupSockopt}; /// /// let file = File::open("/sys/fs/cgroup/unified")?; /// let program: &mut CgroupSockopt = bpf.program_mut("cgroup_sockopt").unwrap().try_into()?; /// program.load()?; /// program.attach(file, CgroupAttachMode::Single)?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCKOPT")] pub struct CgroupSockopt { pub(crate) data: ProgramData, pub(crate) attach_type: CgroupSockoptAttachType, } impl CgroupSockopt { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(self.attach_type.into()); load_program(BPF_PROG_TYPE_CGROUP_SOCKOPT, &mut self.data) } /// Attaches the program to the given cgroup. /// /// The returned value can be used to detach, see [CgroupSockopt::detach]. pub fn attach( &mut self, cgroup: T, mode: CgroupAttachMode, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let cgroup_fd = cgroup.as_fd(); let attach_type = self.data.expected_attach_type.unwrap(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(cgroup_fd), attach_type, None, mode.into(), None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(CgroupSockoptLink::new(CgroupSockoptLinkInner::Fd( FdLink::new(link_fd), ))) } else { let link = ProgAttachLink::attach(prog_fd, cgroup_fd, attach_type, mode)?; self.data .links .insert(CgroupSockoptLink::new(CgroupSockoptLinkInner::ProgAttach( link, ))) } } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link( &mut self, link_id: CgroupSockoptLinkId, ) -> Result { self.data.take_link(link_id) } /// Detaches the program. /// /// See [CgroupSockopt::attach]. pub fn detach(&mut self, link_id: CgroupSockoptLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>( path: P, attach_type: CgroupSockoptAttachType, ) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, attach_type }) } } #[derive(Debug, Hash, Eq, PartialEq)] enum CgroupSockoptLinkIdInner { Fd(::Id), ProgAttach(::Id), } #[derive(Debug)] enum CgroupSockoptLinkInner { Fd(FdLink), ProgAttach(ProgAttachLink), } impl Link for CgroupSockoptLinkInner { type Id = CgroupSockoptLinkIdInner; fn id(&self) -> Self::Id { match self { Self::Fd(fd) => CgroupSockoptLinkIdInner::Fd(fd.id()), Self::ProgAttach(p) => CgroupSockoptLinkIdInner::ProgAttach(p.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::Fd(fd) => fd.detach(), Self::ProgAttach(p) => p.detach(), } } } define_link_wrapper!( /// The link used by [CgroupSockopt] programs. CgroupSockoptLink, /// The type returned by [CgroupSockopt::attach]. Can be passed to [CgroupSockopt::detach]. CgroupSockoptLinkId, CgroupSockoptLinkInner, CgroupSockoptLinkIdInner ); aya-0.13.1/src/programs/cgroup_sysctl.rs000064400000000000000000000112301046102023000163440ustar 00000000000000//! Cgroup sysctl programs. use std::{hash::Hash, os::fd::AsFd}; use crate::{ generated::{bpf_attach_type::BPF_CGROUP_SYSCTL, bpf_prog_type::BPF_PROG_TYPE_CGROUP_SYSCTL}, programs::{ define_link_wrapper, load_program, CgroupAttachMode, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, }, sys::{bpf_link_create, LinkTarget, SyscallError}, util::KernelVersion, }; /// A program used to watch for sysctl changes. /// /// [`CgroupSysctl`] programs can be attached to a cgroup and will be called every /// time a process inside that cgroup tries to read from or write to a sysctl knob in proc. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.2. /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::fs::File; /// use aya::programs::{CgroupAttachMode, CgroupSysctl}; /// /// let file = File::open("/sys/fs/cgroup/unified")?; /// let program: &mut CgroupSysctl = bpf.program_mut("cgroup_sysctl").unwrap().try_into()?; /// program.load()?; /// program.attach(file, CgroupAttachMode::Single)?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_CGROUP_SYSCTL")] pub struct CgroupSysctl { pub(crate) data: ProgramData, } impl CgroupSysctl { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_CGROUP_SYSCTL, &mut self.data) } /// Attaches the program to the given cgroup. /// /// The returned value can be used to detach, see [CgroupSysctl::detach]. pub fn attach( &mut self, cgroup: T, mode: CgroupAttachMode, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let cgroup_fd = cgroup.as_fd(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(cgroup_fd), BPF_CGROUP_SYSCTL, None, mode.into(), None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(CgroupSysctlLink::new(CgroupSysctlLinkInner::Fd( FdLink::new(link_fd), ))) } else { let link = ProgAttachLink::attach(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL, mode)?; self.data .links .insert(CgroupSysctlLink::new(CgroupSysctlLinkInner::ProgAttach( link, ))) } } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link( &mut self, link_id: CgroupSysctlLinkId, ) -> Result { self.data.take_link(link_id) } /// Detaches the program. /// /// See [CgroupSysctl::attach]. pub fn detach(&mut self, link_id: CgroupSysctlLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } } #[derive(Debug, Hash, Eq, PartialEq)] enum CgroupSysctlLinkIdInner { Fd(::Id), ProgAttach(::Id), } #[derive(Debug)] enum CgroupSysctlLinkInner { Fd(FdLink), ProgAttach(ProgAttachLink), } impl Link for CgroupSysctlLinkInner { type Id = CgroupSysctlLinkIdInner; fn id(&self) -> Self::Id { match self { Self::Fd(fd) => CgroupSysctlLinkIdInner::Fd(fd.id()), Self::ProgAttach(p) => CgroupSysctlLinkIdInner::ProgAttach(p.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::Fd(fd) => fd.detach(), Self::ProgAttach(p) => p.detach(), } } } define_link_wrapper!( /// The link used by [CgroupSysctl] programs. CgroupSysctlLink, /// The type returned by [CgroupSysctl::attach]. Can be passed to [CgroupSysctl::detach]. CgroupSysctlLinkId, CgroupSysctlLinkInner, CgroupSysctlLinkIdInner ); aya-0.13.1/src/programs/extension.rs000064400000000000000000000174201046102023000154670ustar 00000000000000//! Extension programs. use std::os::fd::{AsFd as _, BorrowedFd}; use object::Endianness; use thiserror::Error; use crate::{ generated::{bpf_attach_type::BPF_CGROUP_INET_INGRESS, bpf_prog_type::BPF_PROG_TYPE_EXT}, obj::btf::BtfKind, programs::{ define_link_wrapper, load_program, FdLink, FdLinkId, ProgramData, ProgramError, ProgramFd, }, sys::{self, bpf_link_create, LinkTarget, SyscallError}, Btf, }; /// The type returned when loading or attaching an [`Extension`] fails. #[derive(Debug, Error)] pub enum ExtensionError { /// Target BPF program does not have BTF loaded to the kernel. #[error("target BPF program does not have BTF loaded to the kernel")] NoBTF, } /// A program used to extend existing BPF programs. /// /// [`Extension`] programs can be loaded to replace a global /// function in a program that has already been loaded. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.9 /// /// # Examples /// /// ```no_run /// use aya::{EbpfLoader, programs::{Xdp, XdpFlags, Extension}}; /// /// let mut bpf = EbpfLoader::new().extension("extension").load_file("app.o")?; /// let prog: &mut Xdp = bpf.program_mut("main").unwrap().try_into()?; /// prog.load()?; /// prog.attach("eth0", XdpFlags::default())?; /// /// let prog_fd = prog.fd().unwrap(); /// let prog_fd = prog_fd.try_clone().unwrap(); /// let ext: &mut Extension = bpf.program_mut("extension").unwrap().try_into()?; /// ext.load(prog_fd, "function_to_replace")?; /// ext.attach()?; /// Ok::<(), aya::EbpfError>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_EXT")] pub struct Extension { pub(crate) data: ProgramData, } impl Extension { /// Loads the extension inside the kernel. /// /// Prepares the code included in the extension to replace the code of the function /// `func_name` within the eBPF program represented by the `program` file descriptor. /// This requires that both the [`Extension`] and `program` have had their BTF /// loaded into the kernel. /// /// The BPF verifier requires that we specify the target program and function name /// at load time, so it can identify that the program and target are BTF compatible /// and to enforce this constraint when programs are attached. /// /// The extension code will be loaded but inactive until it's attached. /// There are no restrictions on what functions may be replaced, so you could replace /// the main entry point of your program with an extension. pub fn load(&mut self, program: ProgramFd, func_name: &str) -> Result<(), ProgramError> { let (btf_fd, btf_id) = get_btf_info(program.as_fd(), func_name)?; self.data.attach_btf_obj_fd = Some(btf_fd); self.data.attach_prog_fd = Some(program); self.data.attach_btf_id = Some(btf_id); load_program(BPF_PROG_TYPE_EXT, &mut self.data) } /// Attaches the extension. /// /// Attaches the extension to the program and function name specified at load time, /// effectively replacing the original target function. /// /// The returned value can be used to detach the extension and restore the /// original function, see [Extension::detach]. pub fn attach(&mut self) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let target_fd = self .data .attach_prog_fd .as_ref() .ok_or(ProgramError::NotLoaded)?; let target_fd = target_fd.as_fd(); let btf_id = self.data.attach_btf_id.ok_or(ProgramError::NotLoaded)?; // the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(target_fd), BPF_CGROUP_INET_INGRESS, Some(btf_id), 0, None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(ExtensionLink::new(FdLink::new(link_fd))) } /// Attaches the extension to another program. /// /// Attaches the extension to a program and/or function other than the one provided /// at load time. You may only attach to another program/function if the BTF /// type signature is identical to that which was verified on load. Attempting to /// attach to an invalid program/function will result in an error. /// /// Once attached, the extension effectively replaces the original target function. /// /// The returned value can be used to detach the extension and restore the /// original function, see [Extension::detach]. pub fn attach_to_program( &mut self, program: &ProgramFd, func_name: &str, ) -> Result { let target_fd = program.as_fd(); let (_, btf_id) = get_btf_info(target_fd, func_name)?; let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); // the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(target_fd), BPF_CGROUP_INET_INGRESS, Some(btf_id), 0, None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(ExtensionLink::new(FdLink::new(link_fd))) } /// Detaches the extension. /// /// Detaching restores the original code overridden by the extension program. /// See [Extension::attach]. pub fn detach(&mut self, link_id: ExtensionLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: ExtensionLinkId) -> Result { self.data.take_link(link_id) } } /// Retrieves the FD of the BTF object for the provided `prog_fd` and the BTF ID of the function /// with the name `func_name` within that BTF object. fn get_btf_info( prog_fd: BorrowedFd<'_>, func_name: &str, ) -> Result<(crate::MockableFd, u32), ProgramError> { // retrieve program information let info = sys::bpf_prog_get_info_by_fd(prog_fd, &mut [])?; // btf_id refers to the ID of the program btf that was loaded with bpf(BPF_BTF_LOAD) if info.btf_id == 0 { return Err(ProgramError::ExtensionError(ExtensionError::NoBTF)); } // the bpf fd of the BTF object let btf_fd = sys::bpf_btf_get_fd_by_id(info.btf_id)?; // we need to read the btf bytes into a buffer but we don't know the size ahead of time. // assume 4kb. if this is too small we can resize based on the size obtained in the response. let mut buf = vec![0u8; 4096]; loop { let info = sys::btf_obj_get_info_by_fd(btf_fd.as_fd(), &mut buf)?; let btf_size = info.btf_size as usize; if btf_size > buf.len() { buf.resize(btf_size, 0u8); continue; } buf.truncate(btf_size); break; } let btf = Btf::parse(&buf, Endianness::default()).map_err(ProgramError::Btf)?; let btf_id = btf .id_by_type_name_kind(func_name, BtfKind::Func) .map_err(ProgramError::Btf)?; Ok((btf_fd, btf_id)) } define_link_wrapper!( /// The link used by [Extension] programs. ExtensionLink, /// The type returned by [Extension::attach]. Can be passed to [Extension::detach]. ExtensionLinkId, FdLink, FdLinkId ); aya-0.13.1/src/programs/fentry.rs000064400000000000000000000060551046102023000147640ustar 00000000000000//! Fentry programs. use crate::{ generated::{bpf_attach_type::BPF_TRACE_FENTRY, bpf_prog_type::BPF_PROG_TYPE_TRACING}, obj::btf::{Btf, BtfKind}, programs::{ define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId, ProgramData, ProgramError, }, }; /// A program that can be attached to the entry point of (almost) any kernel /// function. /// /// [`FEntry`] programs are similar to [kprobes](crate::programs::KProbe), but /// the difference is that fentry has practically zero overhead to call before /// kernel function. Fentry programs can be also attached to other eBPF /// programs. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.5. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # BtfError(#[from] aya::BtfError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError), /// # } /// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?; /// use aya::{Ebpf, programs::FEntry, BtfError, Btf}; /// /// let btf = Btf::from_sys_fs()?; /// let program: &mut FEntry = bpf.program_mut("filename_lookup").unwrap().try_into()?; /// program.load("filename_lookup", &btf)?; /// program.attach()?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_TRACE_FENTRY")] #[doc(alias = "BPF_PROG_TYPE_TRACING")] pub struct FEntry { pub(crate) data: ProgramData, } impl FEntry { /// Loads the program inside the kernel. /// /// Loads the program so it's executed when the kernel function `fn_name` /// is entered. The `btf` argument must contain the BTF info for the /// running kernel. pub fn load(&mut self, fn_name: &str, btf: &Btf) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(BPF_TRACE_FENTRY); self.data.attach_btf_id = Some(btf.id_by_type_name_kind(fn_name, BtfKind::Func)?); load_program(BPF_PROG_TYPE_TRACING, &mut self.data) } /// Attaches the program. /// /// The returned value can be used to detach, see [FEntry::detach]. pub fn attach(&mut self) -> Result { attach_raw_tracepoint(&mut self.data, None) } /// Detaches the program. /// /// See [FEntry::attach]. pub fn detach(&mut self, link_id: FEntryLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: FEntryLinkId) -> Result { self.data.take_link(link_id) } } define_link_wrapper!( /// The link used by [FEntry] programs. FEntryLink, /// The type returned by [FEntry::attach]. Can be passed to [FEntry::detach]. FEntryLinkId, FdLink, FdLinkId ); aya-0.13.1/src/programs/fexit.rs000064400000000000000000000060301046102023000145650ustar 00000000000000//! Fexit programs. use crate::{ generated::{bpf_attach_type::BPF_TRACE_FEXIT, bpf_prog_type::BPF_PROG_TYPE_TRACING}, obj::btf::{Btf, BtfKind}, programs::{ define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId, ProgramData, ProgramError, }, }; /// A program that can be attached to the exit point of (almost) anny kernel /// function. /// /// [`FExit`] programs are similar to [kretprobes](crate::programs::KProbe), /// but the difference is that fexit has practically zero overhead to call /// before kernel function. Fexit programs can be also attached to other eBPF /// programs. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.5. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # BtfError(#[from] aya::BtfError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError), /// # } /// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?; /// use aya::{Ebpf, programs::FExit, BtfError, Btf}; /// /// let btf = Btf::from_sys_fs()?; /// let program: &mut FExit = bpf.program_mut("filename_lookup").unwrap().try_into()?; /// program.load("filename_lookup", &btf)?; /// program.attach()?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_TRACE_FEXIT")] #[doc(alias = "BPF_PROG_TYPE_TRACING")] pub struct FExit { pub(crate) data: ProgramData, } impl FExit { /// Loads the program inside the kernel. /// /// Loads the program so it's executed when the kernel function `fn_name` /// is exited. The `btf` argument must contain the BTF info for the running /// kernel. pub fn load(&mut self, fn_name: &str, btf: &Btf) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(BPF_TRACE_FEXIT); self.data.attach_btf_id = Some(btf.id_by_type_name_kind(fn_name, BtfKind::Func)?); load_program(BPF_PROG_TYPE_TRACING, &mut self.data) } /// Attaches the program. /// /// The returned value can be used to detach, see [FExit::detach]. pub fn attach(&mut self) -> Result { attach_raw_tracepoint(&mut self.data, None) } /// Detaches the program. /// /// See [FExit::attach]. pub fn detach(&mut self, link_id: FExitLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: FExitLinkId) -> Result { self.data.take_link(link_id) } } define_link_wrapper!( /// The link used by [FExit] programs. FExitLink, /// The type returned by [FExit::attach]. Can be passed to [FExit::detach]. FExitLinkId, FdLink, FdLinkId ); aya-0.13.1/src/programs/info.rs000064400000000000000000000474071046102023000144160ustar 00000000000000//! Metadata information about an eBPF program. use std::{ ffi::CString, os::fd::{AsFd as _, BorrowedFd}, path::Path, time::{Duration, SystemTime}, }; use aya_obj::generated::{bpf_prog_info, bpf_prog_type}; use super::{ utils::{boot_time, get_fdinfo}, ProgramError, ProgramFd, }; use crate::{ sys::{ bpf_get_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, iter_prog_ids, SyscallError, }, util::bytes_of_bpf_name, FEATURES, }; /// Provides metadata information about a loaded eBPF program. /// /// Introduced in kernel v4.13. #[doc(alias = "bpf_prog_info")] #[derive(Debug)] pub struct ProgramInfo(pub(crate) bpf_prog_info); impl ProgramInfo { pub(crate) fn new_from_fd(fd: BorrowedFd<'_>) -> Result { let info = bpf_prog_get_info_by_fd(fd, &mut [])?; Ok(Self(info)) } /// The type of program. /// /// Introduced in kernel v4.13. pub fn program_type(&self) -> Result { bpf_prog_type::try_from(self.0.type_) .unwrap_or(bpf_prog_type::__MAX_BPF_PROG_TYPE) .try_into() } /// The unique ID for this program. /// /// Introduced in kernel v4.13. pub fn id(&self) -> u32 { self.0.id } /// The program tag. /// /// The tag is a SHA sum of the program's instructions which be used as an alternative to /// [`Self::id()`]. A program's ID can vary every time it's loaded or unloaded, but the tag /// will remain the same. /// /// Introduced in kernel v4.13. pub fn tag(&self) -> u64 { u64::from_be_bytes(self.0.tag) } /// The size in bytes of the program's JIT-compiled machine code. /// /// Note that this field is only updated when BPF JIT compiler is enabled. Kernels v4.15 and /// above may already have it enabled by default. /// /// Introduced in kernel v4.13. pub fn size_jitted(&self) -> u32 { self.0.jited_prog_len } /// The size in bytes of the program's translated eBPF bytecode. /// /// The translated bytecode is after it has been passed though the verifier where it was /// possibly modified by the kernel. /// /// `None` is returned if the field is not available. /// /// Introduced in kernel v4.15. pub fn size_translated(&self) -> Option { (self.0.xlated_prog_len > 0).then_some(self.0.xlated_prog_len) } /// The time when the program was loaded. /// /// `None` is returned if the field is not available. /// /// Introduced in kernel v4.15. pub fn loaded_at(&self) -> Option { (self.0.load_time > 0).then_some(boot_time() + Duration::from_nanos(self.0.load_time)) } /// The user ID of the process who loaded the program. /// /// `None` is returned if the field is not available. /// /// Introduced in kernel v4.15. pub fn created_by_uid(&self) -> Option { // This field was introduced in the same commit as `load_time`. (self.0.load_time > 0).then_some(self.0.created_by_uid) } /// The IDs of the maps used by the program. /// /// `None` is returned if the field is not available. /// /// Introduced in kernel v4.15. pub fn map_ids(&self) -> Result>, ProgramError> { if FEATURES.prog_info_map_ids() { let mut map_ids = vec![0u32; self.0.nr_map_ids as usize]; bpf_prog_get_info_by_fd(self.fd()?.as_fd(), &mut map_ids)?; Ok(Some(map_ids)) } else { Ok(None) } } /// The name of the program as was provided when it was load. This is limited to 16 bytes. /// /// Introduced in kernel v4.15. pub fn name(&self) -> &[u8] { bytes_of_bpf_name(&self.0.name) } /// The name of the program as a &str. /// /// `None` is returned if the name was not valid unicode or if field is not available. /// /// Introduced in kernel v4.15. pub fn name_as_str(&self) -> Option<&str> { let name = std::str::from_utf8(self.name()).ok()?; (FEATURES.bpf_name() || !name.is_empty()).then_some(name) } /// Returns true if the program is defined with a GPL-compatible license. /// /// `None` is returned if the field is not available. /// /// Introduced in kernel v4.18. pub fn gpl_compatible(&self) -> Option { FEATURES .prog_info_gpl_compatible() .then_some(self.0.gpl_compatible() != 0) } /// The BTF ID for the program. /// /// Introduced in kernel v5.0. pub fn btf_id(&self) -> Option { (self.0.btf_id > 0).then_some(self.0.btf_id) } /// The accumulated time that the program has been actively running. /// /// This is not to be confused with the duration since the program was /// first loaded on the host. /// /// Note this field is only updated for as long as /// [`enable_stats`](crate::sys::enable_stats) is enabled /// with [`Stats::RunTime`](crate::sys::Stats::RunTime). /// /// Introduced in kernel v5.1. pub fn run_time(&self) -> Duration { Duration::from_nanos(self.0.run_time_ns) } /// The accumulated execution count of the program. /// /// Note this field is only updated for as long as /// [`enable_stats`](crate::sys::enable_stats) is enabled /// with [`Stats::RunTime`](crate::sys::Stats::RunTime). /// /// Introduced in kernel v5.1. pub fn run_count(&self) -> u64 { self.0.run_cnt } /// The number of verified instructions in the program. /// /// This may be less than the total number of instructions in the compiled program due to dead /// code elimination in the verifier. /// /// `None` is returned if the field is not available. /// /// Introduced in kernel v5.16. pub fn verified_instruction_count(&self) -> Option { (self.0.verified_insns > 0).then_some(self.0.verified_insns) } /// How much memory in bytes has been allocated and locked for the program. pub fn memory_locked(&self) -> Result { get_fdinfo(self.fd()?.as_fd(), "memlock") } /// Returns a file descriptor referencing the program. /// /// The returned file descriptor can be closed at any time and doing so does /// not influence the life cycle of the program. /// /// Uses kernel v4.13 features. pub fn fd(&self) -> Result { let Self(info) = self; let fd = bpf_prog_get_fd_by_id(info.id)?; Ok(ProgramFd(fd)) } /// Loads a program from a pinned path in bpffs. /// /// Uses kernel v4.4 and v4.13 features. pub fn from_pin>(path: P) -> Result { use std::os::unix::ffi::OsStrExt as _; // TODO: avoid this unwrap by adding a new error variant. let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { call: "BPF_OBJ_GET", io_error, })?; Self::new_from_fd(fd.as_fd()) } } /// Returns information about a loaded program with the [`ProgramInfo`] structure. /// /// This information is populated at load time by the kernel and can be used /// to correlate a given [`crate::programs::Program`] to it's corresponding [`ProgramInfo`] /// metadata. macro_rules! impl_info { ($($struct_name:ident),+ $(,)?) => { $( impl $struct_name { /// Returns metadata information of this program. /// /// Uses kernel v4.13 features. pub fn info(&self) -> Result { let ProgramFd(fd) = self.fd()?; ProgramInfo::new_from_fd(fd.as_fd()) } } )+ } } pub(crate) use impl_info; /// Returns an iterator of [`ProgramInfo`] over all eBPF programs loaded on the host. /// /// Unlike [`Ebpf::programs`](crate::Ebpf::programs), this includes **all** programs on the host /// system, not just those tied to a specific [`crate::Ebpf`] instance. /// /// Uses kernel v4.13 features. /// /// # Example /// ``` /// # use aya::programs::loaded_programs; /// # /// for p in loaded_programs() { /// match p { /// Ok(program) => println!("{}", String::from_utf8_lossy(program.name())), /// Err(e) => println!("Error iterating programs: {:?}", e), /// } /// } /// ``` /// /// # Errors /// /// Returns [`ProgramError::SyscallError`] if any of the syscalls required to either get /// next program id, get the program fd, or the [`ProgramInfo`] fail. /// /// In cases where iteration can't be performed, for example the caller does not have the necessary /// privileges, a single item will be yielded containing the error that occurred. pub fn loaded_programs() -> impl Iterator> { iter_prog_ids() .map(|id| { let id = id?; bpf_prog_get_fd_by_id(id) }) .map(|fd| { let fd = fd?; bpf_prog_get_info_by_fd(fd.as_fd(), &mut []) }) .map(|result| result.map(ProgramInfo).map_err(Into::into)) } /// The type of eBPF program. #[non_exhaustive] #[doc(alias = "bpf_prog_type")] #[derive(Copy, Clone, Debug, PartialEq)] pub enum ProgramType { /// An unspecified program type. Unspecified = bpf_prog_type::BPF_PROG_TYPE_UNSPEC as isize, /// A Socket Filter program type. See [`SocketFilter`](super::socket_filter::SocketFilter) /// for the program implementation. /// /// Introduced in kernel v3.19. #[doc(alias = "BPF_PROG_TYPE_SOCKET_FILTER")] SocketFilter = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as isize, /// A Kernel Probe program type. See [`KProbe`](super::kprobe::KProbe) and /// [`UProbe`](super::uprobe::UProbe) for the program implementations. /// /// Introduced in kernel v4.1. #[doc(alias = "BPF_PROG_TYPE_KPROBE")] KProbe = bpf_prog_type::BPF_PROG_TYPE_KPROBE as isize, /// A Traffic Control (TC) Classifier program type. See /// [`SchedClassifier`](super::tc::SchedClassifier) for the program implementation. /// /// Introduced in kernel v4.1. #[doc(alias = "BPF_PROG_TYPE_SCHED_CLS")] SchedClassifier = bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS as isize, /// A Traffic Control (TC) Action program type. /// /// Introduced in kernel v4.1. #[doc(alias = "BPF_PROG_TYPE_SCHED_ACT")] SchedAction = bpf_prog_type::BPF_PROG_TYPE_SCHED_ACT as isize, /// A Tracepoint program type. See [`TracePoint`](super::trace_point::TracePoint) for the /// program implementation. /// /// Introduced in kernel v4.7. #[doc(alias = "BPF_PROG_TYPE_TRACEPOINT")] TracePoint = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as isize, /// An Express Data Path (XDP) program type. See [`Xdp`](super::xdp::Xdp) for the program /// implementation. /// /// Introduced in kernel v4.8. #[doc(alias = "BPF_PROG_TYPE_XDP")] Xdp = bpf_prog_type::BPF_PROG_TYPE_XDP as isize, /// A Perf Event program type. See [`PerfEvent`](super::perf_event::PerfEvent) for the program /// implementation. /// /// Introduced in kernel v4.9. #[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")] PerfEvent = bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT as isize, /// A cGroup Socket Buffer program type. See [`CgroupSkb`](super::cgroup_skb::CgroupSkb) for /// the program implementation. /// /// Introduced in kernel v4.10. #[doc(alias = "BPF_PROG_TYPE_CGROUP_SKB")] CgroupSkb = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SKB as isize, /// A cGroup Socket program type. See [`CgroupSock`](super::cgroup_sock::CgroupSock) for the /// program implementation. /// /// Introduced in kernel v4.10. #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK")] CgroupSock = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK as isize, /// A Lightweight Tunnel (LWT) Input program type. /// /// Introduced in kernel v4.10. #[doc(alias = "BPF_PROG_TYPE_LWT_IN")] LwtInput = bpf_prog_type::BPF_PROG_TYPE_LWT_IN as isize, /// A Lightweight Tunnel (LWT) Output program type. /// /// Introduced in kernel v4.10. #[doc(alias = "BPF_PROG_TYPE_LWT_OUT")] LwtOutput = bpf_prog_type::BPF_PROG_TYPE_LWT_OUT as isize, /// A Lightweight Tunnel (LWT) Transmit program type. /// /// Introduced in kernel v4.10. #[doc(alias = "BPF_PROG_TYPE_LWT_XMIT")] LwtXmit = bpf_prog_type::BPF_PROG_TYPE_LWT_XMIT as isize, /// A Socket Operation program type. See [`SockOps`](super::sock_ops::SockOps) for the program /// implementation. /// /// Introduced in kernel v4.13. #[doc(alias = "BPF_PROG_TYPE_SOCK_OPS")] SockOps = bpf_prog_type::BPF_PROG_TYPE_SOCK_OPS as isize, /// A Socket-to-Socket Buffer program type. See [`SkSkb`](super::sk_skb::SkSkb) for the program /// implementation. /// /// Introduced in kernel v4.14. #[doc(alias = "BPF_PROG_TYPE_SK_SKB")] SkSkb = bpf_prog_type::BPF_PROG_TYPE_SK_SKB as isize, /// A cGroup Device program type. See [`CgroupDevice`](super::cgroup_device::CgroupDevice) /// for the program implementation. /// /// Introduced in kernel v4.15. #[doc(alias = "BPF_PROG_TYPE_CGROUP_DEVICE")] CgroupDevice = bpf_prog_type::BPF_PROG_TYPE_CGROUP_DEVICE as isize, /// A Socket Message program type. See [`SkMsg`](super::sk_msg::SkMsg) for the program /// implementation. /// /// Introduced in kernel v4.17. #[doc(alias = "BPF_PROG_TYPE_SK_MSG")] SkMsg = bpf_prog_type::BPF_PROG_TYPE_SK_MSG as isize, /// A Raw Tracepoint program type. See [`RawTracePoint`](super::raw_trace_point::RawTracePoint) /// for the program implementation. /// /// Introduced in kernel v4.17. #[doc(alias = "BPF_PROG_TYPE_RAW_TRACEPOINT")] RawTracePoint = bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT as isize, /// A cGroup Socket Address program type. See /// [`CgroupSockAddr`](super::cgroup_sock_addr::CgroupSockAddr) for the program implementation. /// /// Introduced in kernel v4.17. #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK_ADDR")] CgroupSockAddr = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK_ADDR as isize, /// A Lightweight Tunnel (LWT) Seg6local program type. /// /// Introduced in kernel v4.18. #[doc(alias = "BPF_PROG_TYPE_LWT_SEG6LOCAL")] LwtSeg6local = bpf_prog_type::BPF_PROG_TYPE_LWT_SEG6LOCAL as isize, /// A Linux Infrared Remote Control (LIRC) Mode2 program type. See /// [`LircMode2`](super::lirc_mode2::LircMode2) for the program implementation. /// /// Introduced in kernel v4.18. #[doc(alias = "BPF_PROG_TYPE_LIRC_MODE2")] LircMode2 = bpf_prog_type::BPF_PROG_TYPE_LIRC_MODE2 as isize, /// A Socket Reuseport program type. /// /// Introduced in kernel v4.19. #[doc(alias = "BPF_PROG_TYPE_SK_REUSEPORT")] SkReuseport = bpf_prog_type::BPF_PROG_TYPE_SK_REUSEPORT as isize, /// A Flow Dissector program type. /// /// Introduced in kernel v4.20. #[doc(alias = "BPF_PROG_TYPE_FLOW_DISSECTOR")] FlowDissector = bpf_prog_type::BPF_PROG_TYPE_FLOW_DISSECTOR as isize, /// A cGroup Sysctl program type. See [`CgroupSysctl`](super::cgroup_sysctl::CgroupSysctl) for /// the program implementation. /// /// Introduced in kernel v5.2. #[doc(alias = "BPF_PROG_TYPE_CGROUP_SYSCTL")] CgroupSysctl = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SYSCTL as isize, /// A Writable Raw Tracepoint program type. /// /// Introduced in kernel v5.2. #[doc(alias = "BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE")] RawTracePointWritable = bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE as isize, /// A cGroup Socket Option program type. See [`CgroupSockopt`](super::cgroup_sockopt::CgroupSockopt) /// for the program implementation. /// /// Introduced in kernel v5.3. #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCKOPT")] CgroupSockopt = bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCKOPT as isize, /// A Tracing program type. See [`FEntry`](super::fentry::FEntry), [`FExit`](super::fexit::FExit), /// and [`BtfTracePoint`](super::tp_btf::BtfTracePoint) for the program implementations. /// /// Introduced in kernel v5.5. #[doc(alias = "BPF_PROG_TYPE_TRACING")] Tracing = bpf_prog_type::BPF_PROG_TYPE_TRACING as isize, /// A Struct Ops program type. /// /// Introduced in kernel v5.6. #[doc(alias = "BPF_PROG_TYPE_STRUCT_OPS")] StructOps = bpf_prog_type::BPF_PROG_TYPE_STRUCT_OPS as isize, /// A Extension program type. See [`Extension`](super::extension::Extension) for the program /// implementation. /// /// Introduced in kernel v5.6. #[doc(alias = "BPF_PROG_TYPE_EXT")] Extension = bpf_prog_type::BPF_PROG_TYPE_EXT as isize, /// A Linux Security Module (LSM) program type. See [`Lsm`](super::lsm::Lsm) for the program /// implementation. /// /// Introduced in kernel v5.7. #[doc(alias = "BPF_PROG_TYPE_LSM")] Lsm = bpf_prog_type::BPF_PROG_TYPE_LSM as isize, /// A Socket Lookup program type. See [`SkLookup`](super::sk_lookup::SkLookup) for the program /// implementation. /// /// Introduced in kernel v5.9. #[doc(alias = "BPF_PROG_TYPE_SK_LOOKUP")] SkLookup = bpf_prog_type::BPF_PROG_TYPE_SK_LOOKUP as isize, /// A Syscall program type. /// /// Introduced in kernel v5.14. #[doc(alias = "BPF_PROG_TYPE_SYSCALL")] Syscall = bpf_prog_type::BPF_PROG_TYPE_SYSCALL as isize, /// A Netfilter program type. /// /// Introduced in kernel v6.4. #[doc(alias = "BPF_PROG_TYPE_NETFILTER")] Netfilter = bpf_prog_type::BPF_PROG_TYPE_NETFILTER as isize, } impl TryFrom for ProgramType { type Error = ProgramError; fn try_from(prog_type: bpf_prog_type) -> Result { use bpf_prog_type::*; Ok(match prog_type { BPF_PROG_TYPE_UNSPEC => Self::Unspecified, BPF_PROG_TYPE_SOCKET_FILTER => Self::SocketFilter, BPF_PROG_TYPE_KPROBE => Self::KProbe, BPF_PROG_TYPE_SCHED_CLS => Self::SchedClassifier, BPF_PROG_TYPE_SCHED_ACT => Self::SchedAction, BPF_PROG_TYPE_TRACEPOINT => Self::TracePoint, BPF_PROG_TYPE_XDP => Self::Xdp, BPF_PROG_TYPE_PERF_EVENT => Self::PerfEvent, BPF_PROG_TYPE_CGROUP_SKB => Self::CgroupSkb, BPF_PROG_TYPE_CGROUP_SOCK => Self::CgroupSock, BPF_PROG_TYPE_LWT_IN => Self::LwtInput, BPF_PROG_TYPE_LWT_OUT => Self::LwtOutput, BPF_PROG_TYPE_LWT_XMIT => Self::LwtXmit, BPF_PROG_TYPE_SOCK_OPS => Self::SockOps, BPF_PROG_TYPE_SK_SKB => Self::SkSkb, BPF_PROG_TYPE_CGROUP_DEVICE => Self::CgroupDevice, BPF_PROG_TYPE_SK_MSG => Self::SkMsg, BPF_PROG_TYPE_RAW_TRACEPOINT => Self::RawTracePoint, BPF_PROG_TYPE_CGROUP_SOCK_ADDR => Self::CgroupSockAddr, BPF_PROG_TYPE_LWT_SEG6LOCAL => Self::LwtSeg6local, BPF_PROG_TYPE_LIRC_MODE2 => Self::LircMode2, BPF_PROG_TYPE_SK_REUSEPORT => Self::SkReuseport, BPF_PROG_TYPE_FLOW_DISSECTOR => Self::FlowDissector, BPF_PROG_TYPE_CGROUP_SYSCTL => Self::CgroupSysctl, BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE => Self::RawTracePointWritable, BPF_PROG_TYPE_CGROUP_SOCKOPT => Self::CgroupSockopt, BPF_PROG_TYPE_TRACING => Self::Tracing, BPF_PROG_TYPE_STRUCT_OPS => Self::StructOps, BPF_PROG_TYPE_EXT => Self::Extension, BPF_PROG_TYPE_LSM => Self::Lsm, BPF_PROG_TYPE_SK_LOOKUP => Self::SkLookup, BPF_PROG_TYPE_SYSCALL => Self::Syscall, BPF_PROG_TYPE_NETFILTER => Self::Netfilter, __MAX_BPF_PROG_TYPE => return Err(ProgramError::UnexpectedProgramType), }) } } aya-0.13.1/src/programs/kprobe.rs000064400000000000000000000114451046102023000147360ustar 00000000000000//! Kernel space probes. use std::{ ffi::OsStr, io, os::fd::AsFd as _, path::{Path, PathBuf}, }; use thiserror::Error; use crate::{ generated::{bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_KPROBE}, programs::{ define_link_wrapper, load_program, perf_attach::{PerfLinkIdInner, PerfLinkInner}, probe::{attach, ProbeKind}, FdLink, LinkError, ProgramData, ProgramError, }, sys::bpf_link_get_info_by_fd, VerifierLogLevel, }; /// A kernel probe. /// /// Kernel probes are eBPF programs that can be attached to almost any function inside /// the kernel. They can be of two kinds: /// /// - `kprobe`: get attached to the *start* of the target functions /// - `kretprobe`: get attached to the *return address* of the target functions /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.1. /// /// # Examples /// /// ```no_run /// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?; /// use aya::{Ebpf, programs::KProbe}; /// /// let program: &mut KProbe = bpf.program_mut("intercept_wakeups").unwrap().try_into()?; /// program.load()?; /// program.attach("try_to_wake_up", 0)?; /// # Ok::<(), aya::EbpfError>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_KPROBE")] pub struct KProbe { pub(crate) data: ProgramData, pub(crate) kind: ProbeKind, } impl KProbe { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_KPROBE, &mut self.data) } /// Returns `KProbe` if the program is a `kprobe`, or `KRetProbe` if the /// program is a `kretprobe`. pub fn kind(&self) -> ProbeKind { self.kind } /// Attaches the program. /// /// Attaches the probe to the given function name inside the kernel. If /// `offset` is non-zero, it is added to the address of the target /// function. /// /// If the program is a `kprobe`, it is attached to the *start* address of the target function. /// Conversely if the program is a `kretprobe`, it is attached to the return address of the /// target function. /// /// The returned value can be used to detach from the given function, see [KProbe::detach]. pub fn attach>( &mut self, fn_name: T, offset: u64, ) -> Result { attach(&mut self.data, self.kind, fn_name.as_ref(), offset, None) } /// Detaches the program. /// /// See [KProbe::attach]. pub fn detach(&mut self, link_id: KProbeLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: KProbeLinkId) -> Result { self.data.take_link(link_id) } /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P, kind: ProbeKind) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, kind }) } } define_link_wrapper!( /// The link used by [KProbe] programs. KProbeLink, /// The type returned by [KProbe::attach]. Can be passed to [KProbe::detach]. KProbeLinkId, PerfLinkInner, PerfLinkIdInner ); /// The type returned when attaching a [`KProbe`] fails. #[derive(Debug, Error)] pub enum KProbeError { /// Error detaching from debugfs #[error("`{filename}`")] FileError { /// The file name filename: PathBuf, /// The [`io::Error`] returned from the file operation #[source] io_error: io::Error, }, } impl TryFrom for FdLink { type Error = LinkError; fn try_from(value: KProbeLink) -> Result { if let PerfLinkInner::FdLink(fd) = value.into_inner() { Ok(fd) } else { Err(LinkError::InvalidLink) } } } impl TryFrom for KProbeLink { type Error = LinkError; fn try_from(fd_link: FdLink) -> Result { let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?; if info.type_ == (bpf_link_type::BPF_LINK_TYPE_KPROBE_MULTI as u32) { return Ok(Self::new(PerfLinkInner::FdLink(fd_link))); } Err(LinkError::InvalidLink) } } aya-0.13.1/src/programs/links.rs000064400000000000000000000474471046102023000146070ustar 00000000000000//! Program links. use std::{ collections::{hash_map::Entry, HashMap}, ffi::CString, io, os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, RawFd}, path::{Path, PathBuf}, }; use thiserror::Error; use crate::{ generated::{ bpf_attach_type, BPF_F_AFTER, BPF_F_ALLOW_MULTI, BPF_F_ALLOW_OVERRIDE, BPF_F_BEFORE, BPF_F_ID, BPF_F_LINK, BPF_F_REPLACE, }, pin::PinError, programs::{MultiProgLink, MultiProgram, ProgramError, ProgramFd, ProgramId}, sys::{bpf_get_object, bpf_pin_object, bpf_prog_attach, bpf_prog_detach, SyscallError}, }; /// A Link. pub trait Link: std::fmt::Debug + 'static { /// Unique Id type Id: std::fmt::Debug + std::hash::Hash + Eq + PartialEq; /// Returns the link id fn id(&self) -> Self::Id; /// Detaches the LinkOwnedLink is gone... but this doesn't work :( fn detach(self) -> Result<(), ProgramError>; } /// Program attachment mode. #[derive(Clone, Copy, Debug, Default)] pub enum CgroupAttachMode { /// Allows only one BPF program in the cgroup subtree. #[default] Single, /// Allows the program to be overridden by one in a sub-cgroup. AllowOverride, /// Allows multiple programs to be run in the cgroup subtree. AllowMultiple, } impl From for u32 { fn from(mode: CgroupAttachMode) -> Self { match mode { CgroupAttachMode::Single => 0, CgroupAttachMode::AllowOverride => BPF_F_ALLOW_OVERRIDE, CgroupAttachMode::AllowMultiple => BPF_F_ALLOW_MULTI, } } } #[derive(Debug)] pub(crate) struct LinkMap { links: HashMap, } impl LinkMap { pub(crate) fn new() -> Self { Self { links: HashMap::new(), } } pub(crate) fn insert(&mut self, link: T) -> Result { let id = link.id(); match self.links.entry(link.id()) { Entry::Occupied(_) => return Err(ProgramError::AlreadyAttached), Entry::Vacant(e) => e.insert(link), }; Ok(id) } pub(crate) fn remove(&mut self, link_id: T::Id) -> Result<(), ProgramError> { self.links .remove(&link_id) .ok_or(ProgramError::NotAttached)? .detach() } pub(crate) fn remove_all(&mut self) -> Result<(), ProgramError> { for (_, link) in self.links.drain() { link.detach()?; } Ok(()) } pub(crate) fn forget(&mut self, link_id: T::Id) -> Result { self.links.remove(&link_id).ok_or(ProgramError::NotAttached) } } impl Drop for LinkMap { fn drop(&mut self) { let _ = self.remove_all(); } } /// The identifier of an `FdLink`. #[derive(Debug, Hash, Eq, PartialEq)] pub struct FdLinkId(pub(crate) RawFd); /// A file descriptor link. /// /// Fd links are returned directly when attaching some program types (for /// instance [`crate::programs::cgroup_skb::CgroupSkb`]), or can be obtained by /// converting other link types (see the `TryFrom` implementations). /// /// An important property of fd links is that they can be pinned. Pinning /// can be used keep a link attached "in background" even after the program /// that has created the link terminates. /// /// # Example /// /// ```no_run /// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?; /// use aya::{Ebpf, programs::{links::FdLink, KProbe}}; /// /// let program: &mut KProbe = bpf.program_mut("intercept_wakeups").unwrap().try_into()?; /// program.load()?; /// let link_id = program.attach("try_to_wake_up", 0)?; /// let link = program.take_link(link_id).unwrap(); /// let fd_link: FdLink = link.try_into().unwrap(); /// fd_link.pin("/sys/fs/bpf/intercept_wakeups_link").unwrap(); /// /// # Ok::<(), aya::EbpfError>(()) /// ``` #[derive(Debug)] pub struct FdLink { pub(crate) fd: crate::MockableFd, } impl FdLink { pub(crate) fn new(fd: crate::MockableFd) -> Self { Self { fd } } /// Pins the link to a BPF file system. /// /// When a link is pinned it will remain attached even after the link instance is dropped, /// and will only be detached once the pinned file is removed. To unpin, see [`PinnedLink::unpin()`]. /// /// The parent directories in the provided path must already exist before calling this method, /// and must be on a BPF file system (bpffs). /// /// # Example /// ```no_run /// # use aya::programs::{links::FdLink, Extension}; /// # use std::convert::TryInto; /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError), /// # #[error(transparent)] /// # Pin(#[from] aya::pin::PinError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// # let prog: &mut Extension = bpf.program_mut("example").unwrap().try_into()?; /// let link_id = prog.attach()?; /// let owned_link = prog.take_link(link_id)?; /// let fd_link: FdLink = owned_link.into(); /// let pinned_link = fd_link.pin("/sys/fs/bpf/example")?; /// # Ok::<(), Error>(()) /// ``` pub fn pin>(self, path: P) -> Result { use std::os::unix::ffi::OsStrExt as _; let path = path.as_ref(); let path_string = CString::new(path.as_os_str().as_bytes()).map_err(|error| { PinError::InvalidPinPath { path: path.into(), error, } })?; bpf_pin_object(self.fd.as_fd(), &path_string).map_err(|(_, io_error)| SyscallError { call: "BPF_OBJ_PIN", io_error, })?; Ok(PinnedLink::new(path.into(), self)) } } impl Link for FdLink { type Id = FdLinkId; fn id(&self) -> Self::Id { FdLinkId(self.fd.as_raw_fd()) } fn detach(self) -> Result<(), ProgramError> { // detach is a noop since it consumes self. once self is consumed, drop will be triggered // and the link will be detached. // // Other links don't need to do this since they use define_link_wrapper!, but FdLink is a // bit special in that it defines a custom ::new() so it can't use the macro. Ok(()) } } impl From for FdLink { fn from(p: PinnedLink) -> Self { p.inner } } /// A pinned file descriptor link. /// /// This link has been pinned to the BPF filesystem. On drop, the file descriptor that backs /// this link will be closed. Whether or not the program remains attached is dependent /// on the presence of the file in BPFFS. #[derive(Debug)] pub struct PinnedLink { inner: FdLink, path: PathBuf, } impl PinnedLink { fn new(path: PathBuf, link: FdLink) -> Self { Self { inner: link, path } } /// Creates a [`crate::programs::links::PinnedLink`] from a valid path on bpffs. pub fn from_pin>(path: P) -> Result { use std::os::unix::ffi::OsStrExt as _; // TODO: avoid this unwrap by adding a new error variant. let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| { LinkError::SyscallError(SyscallError { call: "BPF_OBJ_GET", io_error, }) })?; Ok(Self::new(path.as_ref().to_path_buf(), FdLink::new(fd))) } /// Removes the pinned link from the filesystem and returns an [`FdLink`]. pub fn unpin(self) -> Result { std::fs::remove_file(self.path)?; Ok(self.inner) } } /// The identifier of a `ProgAttachLink`. #[derive(Debug, Hash, Eq, PartialEq)] pub struct ProgAttachLinkId(RawFd, RawFd, bpf_attach_type); /// The Link type used by programs that are attached with `bpf_prog_attach`. #[derive(Debug)] pub struct ProgAttachLink { prog_fd: ProgramFd, target_fd: crate::MockableFd, attach_type: bpf_attach_type, } impl ProgAttachLink { pub(crate) fn new( prog_fd: ProgramFd, target_fd: crate::MockableFd, attach_type: bpf_attach_type, ) -> Self { Self { prog_fd, target_fd, attach_type, } } pub(crate) fn attach( prog_fd: BorrowedFd<'_>, target_fd: BorrowedFd<'_>, attach_type: bpf_attach_type, mode: CgroupAttachMode, ) -> Result { // The link is going to own this new file descriptor so we are // going to need a duplicate whose lifetime we manage. Let's // duplicate it prior to attaching it so the new file // descriptor is closed at drop in case it fails to attach. let prog_fd = prog_fd.try_clone_to_owned()?; let prog_fd = crate::MockableFd::from_fd(prog_fd); let target_fd = target_fd.try_clone_to_owned()?; let target_fd = crate::MockableFd::from_fd(target_fd); bpf_prog_attach(prog_fd.as_fd(), target_fd.as_fd(), attach_type, mode.into())?; let prog_fd = ProgramFd(prog_fd); Ok(Self { prog_fd, target_fd, attach_type, }) } } impl Link for ProgAttachLink { type Id = ProgAttachLinkId; fn id(&self) -> Self::Id { ProgAttachLinkId( self.prog_fd.as_fd().as_raw_fd(), self.target_fd.as_raw_fd(), self.attach_type, ) } fn detach(self) -> Result<(), ProgramError> { bpf_prog_detach( self.prog_fd.as_fd(), self.target_fd.as_fd(), self.attach_type, ) .map_err(Into::into) } } macro_rules! define_link_wrapper { (#[$doc1:meta] $wrapper:ident, #[$doc2:meta] $wrapper_id:ident, $base:ident, $base_id:ident) => { #[$doc2] #[derive(Debug, Hash, Eq, PartialEq)] pub struct $wrapper_id($base_id); #[$doc1] #[derive(Debug)] pub struct $wrapper(Option<$base>); #[allow(dead_code)] // allow dead code since currently XDP/TC are the only consumers of inner and // into_inner impl $wrapper { fn new(base: $base) -> $wrapper { $wrapper(Some(base)) } fn inner(&self) -> &$base { self.0.as_ref().unwrap() } fn into_inner(mut self) -> $base { self.0.take().unwrap() } } impl Drop for $wrapper { fn drop(&mut self) { use crate::programs::links::Link; if let Some(base) = self.0.take() { let _ = base.detach(); } } } impl $crate::programs::Link for $wrapper { type Id = $wrapper_id; fn id(&self) -> Self::Id { $wrapper_id(self.0.as_ref().unwrap().id()) } fn detach(mut self) -> Result<(), ProgramError> { self.0.take().unwrap().detach() } } impl From<$base> for $wrapper { fn from(b: $base) -> $wrapper { $wrapper(Some(b)) } } impl From<$wrapper> for $base { fn from(mut w: $wrapper) -> $base { w.0.take().unwrap() } } }; } pub(crate) use define_link_wrapper; #[derive(Error, Debug)] /// Errors from operations on links. pub enum LinkError { /// Invalid link. #[error("Invalid link")] InvalidLink, /// Syscall failed. #[error(transparent)] SyscallError(#[from] SyscallError), } #[derive(Debug)] pub(crate) enum LinkRef { Id(u32), Fd(RawFd), } bitflags::bitflags! { /// Flags which are use to build a set of MprogOptions. #[derive(Clone, Copy, Debug, Default)] pub(crate) struct MprogFlags: u32 { const REPLACE = BPF_F_REPLACE; const BEFORE = BPF_F_BEFORE; const AFTER = BPF_F_AFTER; const ID = BPF_F_ID; const LINK = BPF_F_LINK; } } /// Arguments required for interacting with the kernel's multi-prog API. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 6.6.0. /// /// # Example /// /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::programs::{tc, SchedClassifier, TcAttachType, tc::TcAttachOptions, LinkOrder}; /// /// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?; /// prog.load()?; /// let options = TcAttachOptions::TcxOrder(LinkOrder::first()); /// prog.attach_with_options("eth0", TcAttachType::Ingress, options)?; /// /// # Ok::<(), aya::EbpfError>(()) /// ``` #[derive(Debug)] pub struct LinkOrder { pub(crate) link_ref: LinkRef, pub(crate) flags: MprogFlags, } /// Ensure that default link ordering is to be attached last. impl Default for LinkOrder { fn default() -> Self { Self { link_ref: LinkRef::Fd(0), flags: MprogFlags::AFTER, } } } impl LinkOrder { /// Attach before all other links. pub fn first() -> Self { Self { link_ref: LinkRef::Id(0), flags: MprogFlags::BEFORE, } } /// Attach after all other links. pub fn last() -> Self { Self { link_ref: LinkRef::Id(0), flags: MprogFlags::AFTER, } } /// Attach before the given link. pub fn before_link(link: &L) -> Result { Ok(Self { link_ref: LinkRef::Fd(link.fd()?.as_raw_fd()), flags: MprogFlags::BEFORE | MprogFlags::LINK, }) } /// Attach after the given link. pub fn after_link(link: &L) -> Result { Ok(Self { link_ref: LinkRef::Fd(link.fd()?.as_raw_fd()), flags: MprogFlags::AFTER | MprogFlags::LINK, }) } /// Attach before the given program. pub fn before_program(program: &P) -> Result { Ok(Self { link_ref: LinkRef::Fd(program.fd()?.as_raw_fd()), flags: MprogFlags::BEFORE, }) } /// Attach after the given program. pub fn after_program(program: &P) -> Result { Ok(Self { link_ref: LinkRef::Fd(program.fd()?.as_raw_fd()), flags: MprogFlags::AFTER, }) } /// Attach before the program with the given id. pub fn before_program_id(id: ProgramId) -> Self { Self { link_ref: LinkRef::Id(id.0), flags: MprogFlags::BEFORE | MprogFlags::ID, } } /// Attach after the program with the given id. pub fn after_program_id(id: ProgramId) -> Self { Self { link_ref: LinkRef::Id(id.0), flags: MprogFlags::AFTER | MprogFlags::ID, } } } #[cfg(test)] mod tests { use std::{cell::RefCell, fs::File, rc::Rc}; use assert_matches::assert_matches; use tempfile::tempdir; use super::{FdLink, Link, LinkMap}; use crate::{ generated::{BPF_F_ALLOW_MULTI, BPF_F_ALLOW_OVERRIDE}, programs::{CgroupAttachMode, ProgramError}, sys::override_syscall, }; #[derive(Debug, Hash, Eq, PartialEq)] struct TestLinkId(u8, u8); #[derive(Debug)] struct TestLink { id: (u8, u8), detached: Rc>, } impl TestLink { fn new(a: u8, b: u8) -> Self { Self { id: (a, b), detached: Rc::new(RefCell::new(0)), } } } impl Link for TestLink { type Id = TestLinkId; fn id(&self) -> Self::Id { TestLinkId(self.id.0, self.id.1) } fn detach(self) -> Result<(), ProgramError> { *self.detached.borrow_mut() += 1; Ok(()) } } #[test] fn test_link_map() { let mut links = LinkMap::new(); let l1 = TestLink::new(1, 2); let l1_detached = Rc::clone(&l1.detached); let l2 = TestLink::new(1, 3); let l2_detached = Rc::clone(&l2.detached); let id1 = links.insert(l1).unwrap(); let id2 = links.insert(l2).unwrap(); assert_eq!(*l1_detached.borrow(), 0); assert_eq!(*l2_detached.borrow(), 0); assert!(links.remove(id1).is_ok()); assert_eq!(*l1_detached.borrow(), 1); assert_eq!(*l2_detached.borrow(), 0); assert!(links.remove(id2).is_ok()); assert_eq!(*l1_detached.borrow(), 1); assert_eq!(*l2_detached.borrow(), 1); } #[test] fn test_already_attached() { let mut links = LinkMap::new(); links.insert(TestLink::new(1, 2)).unwrap(); assert_matches!( links.insert(TestLink::new(1, 2)), Err(ProgramError::AlreadyAttached) ); } #[test] fn test_not_attached() { let mut links = LinkMap::new(); let l1 = TestLink::new(1, 2); let l1_id1 = l1.id(); let l1_id2 = l1.id(); links.insert(TestLink::new(1, 2)).unwrap(); links.remove(l1_id1).unwrap(); assert_matches!(links.remove(l1_id2), Err(ProgramError::NotAttached)); } #[test] fn test_drop_detach() { let l1 = TestLink::new(1, 2); let l1_detached = Rc::clone(&l1.detached); let l2 = TestLink::new(1, 3); let l2_detached = Rc::clone(&l2.detached); { let mut links = LinkMap::new(); let id1 = links.insert(l1).unwrap(); links.insert(l2).unwrap(); // manually remove one link assert!(links.remove(id1).is_ok()); assert_eq!(*l1_detached.borrow(), 1); assert_eq!(*l2_detached.borrow(), 0); } // remove the other on drop assert_eq!(*l1_detached.borrow(), 1); assert_eq!(*l2_detached.borrow(), 1); } #[test] fn test_owned_detach() { let l1 = TestLink::new(1, 2); let l1_detached = Rc::clone(&l1.detached); let l2 = TestLink::new(1, 3); let l2_detached = Rc::clone(&l2.detached); let owned_l1 = { let mut links = LinkMap::new(); let id1 = links.insert(l1).unwrap(); links.insert(l2).unwrap(); // manually forget one link let owned_l1 = links.forget(id1); assert_eq!(*l1_detached.borrow(), 0); assert_eq!(*l2_detached.borrow(), 0); owned_l1.unwrap() }; // l2 is detached on `Drop`, but l1 is still alive assert_eq!(*l1_detached.borrow(), 0); assert_eq!(*l2_detached.borrow(), 1); // manually detach l1 assert!(owned_l1.detach().is_ok()); assert_eq!(*l1_detached.borrow(), 1); assert_eq!(*l2_detached.borrow(), 1); } #[test] #[cfg_attr(miri, ignore = "`mkdir` not available when isolation is enabled")] fn test_pin() { let dir = tempdir().unwrap(); let f1 = File::create(dir.path().join("f1")).expect("unable to create file in tmpdir"); let fd_link = FdLink::new(f1.into()); // override syscall to allow for pin to happen in our tmpdir override_syscall(|_| Ok(0)); // create the file that would have happened as a side-effect of a real pin operation let pin = dir.path().join("f1-pin"); File::create(&pin).expect("unable to create file in tmpdir"); assert!(pin.exists()); let pinned_link = fd_link.pin(&pin).expect("pin failed"); pinned_link.unpin().expect("unpin failed"); assert!(!pin.exists()); } #[test] fn test_cgroup_attach_flag() { assert_eq!(u32::from(CgroupAttachMode::Single), 0); assert_eq!( u32::from(CgroupAttachMode::AllowOverride), BPF_F_ALLOW_OVERRIDE ); assert_eq!( u32::from(CgroupAttachMode::AllowMultiple), BPF_F_ALLOW_MULTI ); } } aya-0.13.1/src/programs/lirc_mode2.rs000064400000000000000000000117301046102023000154700ustar 00000000000000//! Lirc programs. use std::os::fd::{AsFd, AsRawFd as _, RawFd}; use crate::{ generated::{bpf_attach_type::BPF_LIRC_MODE2, bpf_prog_type::BPF_PROG_TYPE_LIRC_MODE2}, programs::{ load_program, query, CgroupAttachMode, Link, ProgramData, ProgramError, ProgramFd, ProgramInfo, }, sys::{bpf_prog_attach, bpf_prog_detach, bpf_prog_get_fd_by_id, ProgQueryTarget}, }; /// A program used to decode IR into key events for a lirc device. /// /// [`LircMode2`] programs can be used to inspect infrared pulses, spaces, /// and timeouts received by a lirc IR receiver. /// /// [lirc]: https://www.kernel.org/doc/html/latest/userspace-api/media/rc/lirc-dev.html /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.18. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::fs::File; /// use aya::programs::LircMode2; /// /// let file = File::open("/dev/lirc0")?; /// let mut bpf = aya::Ebpf::load_file("imon_rsc.o")?; /// let decoder: &mut LircMode2 = bpf.program_mut("imon_rsc").unwrap().try_into().unwrap(); /// decoder.load()?; /// decoder.attach(file)?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_LIRC_MODE2")] pub struct LircMode2 { pub(crate) data: ProgramData, } impl LircMode2 { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_LIRC_MODE2, &mut self.data) } /// Attaches the program to the given lirc device. /// /// The returned value can be used to detach, see [LircMode2::detach]. pub fn attach(&mut self, lircdev: T) -> Result { let prog_fd = self.fd()?; // The link is going to own this new file descriptor so we are // going to need a duplicate whose lifetime we manage. Let's // duplicate it prior to attaching it so the new file // descriptor is closed at drop in case it fails to attach. let prog_fd = prog_fd.try_clone()?; let lircdev_fd = lircdev.as_fd().try_clone_to_owned()?; let lircdev_fd = crate::MockableFd::from_fd(lircdev_fd); bpf_prog_attach( prog_fd.as_fd(), lircdev_fd.as_fd(), BPF_LIRC_MODE2, CgroupAttachMode::Single.into(), )?; self.data.links.insert(LircLink::new(prog_fd, lircdev_fd)) } /// Detaches the program. /// /// See [LircMode2::attach]. pub fn detach(&mut self, link_id: LircLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: LircLinkId) -> Result { self.data.take_link(link_id) } /// Queries the lirc device for attached programs. pub fn query(target_fd: T) -> Result, ProgramError> { let target_fd = target_fd.as_fd(); let (_, prog_ids) = query(ProgQueryTarget::Fd(target_fd), BPF_LIRC_MODE2, 0, &mut None)?; prog_ids .into_iter() .map(|prog_id| { let prog_fd = bpf_prog_get_fd_by_id(prog_id)?; let target_fd = target_fd.try_clone_to_owned()?; let target_fd = crate::MockableFd::from_fd(target_fd); let prog_fd = ProgramFd(prog_fd); Ok(LircLink::new(prog_fd, target_fd)) }) .collect() } } /// The type returned by [LircMode2::attach]. Can be passed to [LircMode2::detach]. #[derive(Debug, Hash, Eq, PartialEq)] pub struct LircLinkId(RawFd, RawFd); #[derive(Debug)] /// An LircMode2 Link pub struct LircLink { prog_fd: ProgramFd, target_fd: crate::MockableFd, } impl LircLink { pub(crate) fn new(prog_fd: ProgramFd, target_fd: crate::MockableFd) -> Self { Self { prog_fd, target_fd } } /// Get ProgramInfo from this link pub fn info(&self) -> Result { let Self { prog_fd, target_fd: _, } = self; ProgramInfo::new_from_fd(prog_fd.as_fd()) } } impl Link for LircLink { type Id = LircLinkId; fn id(&self) -> Self::Id { LircLinkId(self.prog_fd.as_fd().as_raw_fd(), self.target_fd.as_raw_fd()) } fn detach(self) -> Result<(), ProgramError> { bpf_prog_detach(self.prog_fd.as_fd(), self.target_fd.as_fd(), BPF_LIRC_MODE2) .map_err(Into::into) } } aya-0.13.1/src/programs/lsm.rs000064400000000000000000000063471046102023000142540ustar 00000000000000//! LSM probes. use crate::{ generated::{bpf_attach_type::BPF_LSM_MAC, bpf_prog_type::BPF_PROG_TYPE_LSM}, obj::btf::{Btf, BtfKind}, programs::{ define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId, ProgramData, ProgramError, }, }; /// A program that attaches to Linux LSM hooks. Used to implement security policy and /// audit logging. /// /// LSM probes can be attached to the kernel's [security hooks][1] to implement mandatory /// access control policy and security auditing. /// /// LSM probes require a kernel compiled with `CONFIG_BPF_LSM=y` and `CONFIG_DEBUG_INFO_BTF=y`. /// In order for the probes to fire, you also need the BPF LSM to be enabled through your /// kernel's boot paramters (like `lsm=lockdown,yama,bpf`). /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.7. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum LsmError { /// # #[error(transparent)] /// # BtfError(#[from] aya::BtfError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError), /// # } /// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?; /// use aya::{Ebpf, programs::Lsm, BtfError, Btf}; /// /// let btf = Btf::from_sys_fs()?; /// let program: &mut Lsm = bpf.program_mut("lsm_prog").unwrap().try_into()?; /// program.load("security_bprm_exec", &btf)?; /// program.attach()?; /// # Ok::<(), LsmError>(()) /// ``` /// /// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_LSM")] pub struct Lsm { pub(crate) data: ProgramData, } impl Lsm { /// Loads the program inside the kernel. /// /// # Arguments /// /// * `lsm_hook_name` - full name of the LSM hook that the program should /// be attached to pub fn load(&mut self, lsm_hook_name: &str, btf: &Btf) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(BPF_LSM_MAC); let type_name = format!("bpf_lsm_{lsm_hook_name}"); self.data.attach_btf_id = Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Func)?); load_program(BPF_PROG_TYPE_LSM, &mut self.data) } /// Attaches the program. /// /// The returned value can be used to detach, see [Lsm::detach]. pub fn attach(&mut self) -> Result { attach_raw_tracepoint(&mut self.data, None) } /// Detaches the program. /// /// See [Lsm::attach]. pub fn detach(&mut self, link_id: LsmLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: LsmLinkId) -> Result { self.data.take_link(link_id) } } define_link_wrapper!( /// The link used by [Lsm] programs. LsmLink, /// The type returned by [Lsm::attach]. Can be passed to [Lsm::detach]. LsmLinkId, FdLink, FdLinkId ); aya-0.13.1/src/programs/mod.rs000064400000000000000000000754501046102023000142410ustar 00000000000000//! eBPF program types. //! //! eBPF programs are loaded inside the kernel and attached to one or more hook //! points. Whenever the hook points are reached, the programs are executed. //! //! # Loading and attaching programs //! //! When you call [`Ebpf::load_file`] or [`Ebpf::load`], all the programs included //! in the object code are parsed and relocated. Programs are not loaded //! automatically though, since often you will need to do some application //! specific setup before you can actually load them. //! //! In order to load and attach a program, you need to retrieve it using [`Ebpf::program_mut`], //! then call the `load()` and `attach()` methods, for example: //! //! ```no_run //! use aya::{Ebpf, programs::KProbe}; //! //! let mut bpf = Ebpf::load_file("ebpf_programs.o")?; //! // intercept_wakeups is the name of the program we want to load //! let program: &mut KProbe = bpf.program_mut("intercept_wakeups").unwrap().try_into()?; //! program.load()?; //! // intercept_wakeups will be called every time try_to_wake_up() is called //! // inside the kernel //! program.attach("try_to_wake_up", 0)?; //! # Ok::<(), aya::EbpfError>(()) //! ``` //! //! The signature of the `attach()` method varies depending on what kind of //! program you're trying to attach. //! //! [`Ebpf::load_file`]: crate::Ebpf::load_file //! [`Ebpf::load`]: crate::Ebpf::load //! [`Ebpf::programs`]: crate::Ebpf::programs //! [`Ebpf::program`]: crate::Ebpf::program //! [`Ebpf::program_mut`]: crate::Ebpf::program_mut //! [`maps`]: crate::maps // modules we don't export mod info; mod probe; mod utils; // modules we explicitly export so their pub items (Links etc) get exported too pub mod cgroup_device; pub mod cgroup_skb; pub mod cgroup_sock; pub mod cgroup_sock_addr; pub mod cgroup_sockopt; pub mod cgroup_sysctl; pub mod extension; pub mod fentry; pub mod fexit; pub mod kprobe; pub mod links; pub mod lirc_mode2; pub mod lsm; pub mod perf_attach; pub mod perf_event; pub mod raw_trace_point; pub mod sk_lookup; pub mod sk_msg; pub mod sk_skb; pub mod sock_ops; pub mod socket_filter; pub mod tc; pub mod tp_btf; pub mod trace_point; pub mod uprobe; pub mod xdp; use std::{ ffi::CString, io, os::fd::{AsFd, BorrowedFd}, path::{Path, PathBuf}, sync::Arc, }; use info::impl_info; pub use info::{loaded_programs, ProgramInfo, ProgramType}; use libc::ENOSPC; use tc::SchedClassifierLink; use thiserror::Error; // re-export the main items needed to load and attach pub use crate::programs::{ cgroup_device::CgroupDevice, cgroup_skb::{CgroupSkb, CgroupSkbAttachType}, cgroup_sock::{CgroupSock, CgroupSockAttachType}, cgroup_sock_addr::{CgroupSockAddr, CgroupSockAddrAttachType}, cgroup_sockopt::{CgroupSockopt, CgroupSockoptAttachType}, cgroup_sysctl::CgroupSysctl, extension::{Extension, ExtensionError}, fentry::FEntry, fexit::FExit, kprobe::{KProbe, KProbeError}, links::{CgroupAttachMode, Link, LinkOrder}, lirc_mode2::LircMode2, lsm::Lsm, perf_event::{PerfEvent, PerfEventScope, PerfTypeId, SamplePolicy}, probe::ProbeKind, raw_trace_point::RawTracePoint, sk_lookup::SkLookup, sk_msg::SkMsg, sk_skb::{SkSkb, SkSkbKind}, sock_ops::SockOps, socket_filter::{SocketFilter, SocketFilterError}, tc::{SchedClassifier, TcAttachType, TcError}, tp_btf::BtfTracePoint, trace_point::{TracePoint, TracePointError}, uprobe::{UProbe, UProbeError}, xdp::{Xdp, XdpError, XdpFlags}, }; use crate::{ generated::{bpf_attach_type, bpf_link_info, bpf_prog_info, bpf_prog_type}, maps::MapError, obj::{self, btf::BtfError, VerifierLog}, pin::PinError, programs::{links::*, perf_attach::*}, sys::{ bpf_btf_get_fd_by_id, bpf_get_object, bpf_link_get_fd_by_id, bpf_link_get_info_by_fd, bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id, bpf_prog_query, iter_link_ids, retry_with_verifier_logs, EbpfLoadProgramAttrs, ProgQueryTarget, SyscallError, }, util::KernelVersion, VerifierLogLevel, }; /// Error type returned when working with programs. #[derive(Debug, Error)] pub enum ProgramError { /// The program is already loaded. #[error("the program is already loaded")] AlreadyLoaded, /// The program is not loaded. #[error("the program is not loaded")] NotLoaded, /// The program is already attached. #[error("the program was already attached")] AlreadyAttached, /// The program is not attached. #[error("the program is not attached")] NotAttached, /// Loading the program failed. #[error("the BPF_PROG_LOAD syscall failed. Verifier output: {verifier_log}")] LoadError { /// The [`io::Error`] returned by the `BPF_PROG_LOAD` syscall. #[source] io_error: io::Error, /// The error log produced by the kernel verifier. verifier_log: VerifierLog, }, /// A syscall failed. #[error(transparent)] SyscallError(#[from] SyscallError), /// The network interface does not exist. #[error("unknown network interface {name}")] UnknownInterface { /// interface name name: String, }, /// The program is not of the expected type. #[error("unexpected program type")] UnexpectedProgramType, /// A map error occurred while loading or attaching a program. #[error(transparent)] MapError(#[from] MapError), /// An error occurred while working with a [`KProbe`]. #[error(transparent)] KProbeError(#[from] KProbeError), /// An error occurred while working with an [`UProbe`]. #[error(transparent)] UProbeError(#[from] UProbeError), /// An error occurred while working with a [`TracePoint`]. #[error(transparent)] TracePointError(#[from] TracePointError), /// An error occurred while working with a [`SocketFilter`]. #[error(transparent)] SocketFilterError(#[from] SocketFilterError), /// An error occurred while working with an [`Xdp`] program. #[error(transparent)] XdpError(#[from] XdpError), /// An error occurred while working with a TC program. #[error(transparent)] TcError(#[from] TcError), /// An error occurred while working with an [`Extension`] program. #[error(transparent)] ExtensionError(#[from] ExtensionError), /// An error occurred while working with BTF. #[error(transparent)] Btf(#[from] BtfError), /// The program is not attached. #[error("the program name `{name}` is invalid")] InvalidName { /// program name name: String, }, /// An error occurred while working with IO. #[error(transparent)] IOError(#[from] io::Error), } /// A [`Program`] file descriptor. #[derive(Debug)] pub struct ProgramFd(crate::MockableFd); impl ProgramFd { /// Creates a new instance that shares the same underlying file description as [`self`]. pub fn try_clone(&self) -> io::Result { let Self(inner) = self; let inner = inner.try_clone()?; Ok(Self(inner)) } } impl AsFd for ProgramFd { fn as_fd(&self) -> BorrowedFd<'_> { let Self(fd) = self; fd.as_fd() } } /// A [`Program`] identifier. pub struct ProgramId(u32); impl ProgramId { /// Create a new program id. /// /// This method is unsafe since it doesn't check that the given `id` is a /// valid program id. pub unsafe fn new(id: u32) -> Self { Self(id) } } /// eBPF program type. #[derive(Debug)] pub enum Program { /// A [`KProbe`] program KProbe(KProbe), /// A [`UProbe`] program UProbe(UProbe), /// A [`TracePoint`] program TracePoint(TracePoint), /// A [`SocketFilter`] program SocketFilter(SocketFilter), /// A [`Xdp`] program Xdp(Xdp), /// A [`SkMsg`] program SkMsg(SkMsg), /// A [`SkSkb`] program SkSkb(SkSkb), /// A [`CgroupSockAddr`] program CgroupSockAddr(CgroupSockAddr), /// A [`SockOps`] program SockOps(SockOps), /// A [`SchedClassifier`] program SchedClassifier(SchedClassifier), /// A [`CgroupSkb`] program CgroupSkb(CgroupSkb), /// A [`CgroupSysctl`] program CgroupSysctl(CgroupSysctl), /// A [`CgroupSockopt`] program CgroupSockopt(CgroupSockopt), /// A [`LircMode2`] program LircMode2(LircMode2), /// A [`PerfEvent`] program PerfEvent(PerfEvent), /// A [`RawTracePoint`] program RawTracePoint(RawTracePoint), /// A [`Lsm`] program Lsm(Lsm), /// A [`BtfTracePoint`] program BtfTracePoint(BtfTracePoint), /// A [`FEntry`] program FEntry(FEntry), /// A [`FExit`] program FExit(FExit), /// A [`Extension`] program Extension(Extension), /// A [`SkLookup`] program SkLookup(SkLookup), /// A [`CgroupSock`] program CgroupSock(CgroupSock), /// A [`CgroupDevice`] program CgroupDevice(CgroupDevice), } impl Program { /// Returns the program type. pub fn prog_type(&self) -> ProgramType { match self { Self::KProbe(_) | Self::UProbe(_) => ProgramType::KProbe, Self::TracePoint(_) => ProgramType::TracePoint, Self::SocketFilter(_) => ProgramType::SocketFilter, Self::Xdp(_) => ProgramType::Xdp, Self::SkMsg(_) => ProgramType::SkMsg, Self::SkSkb(_) => ProgramType::SkSkb, Self::SockOps(_) => ProgramType::SockOps, Self::SchedClassifier(_) => ProgramType::SchedClassifier, Self::CgroupSkb(_) => ProgramType::CgroupSkb, Self::CgroupSysctl(_) => ProgramType::CgroupSysctl, Self::CgroupSockopt(_) => ProgramType::CgroupSockopt, Self::LircMode2(_) => ProgramType::LircMode2, Self::PerfEvent(_) => ProgramType::PerfEvent, Self::RawTracePoint(_) => ProgramType::RawTracePoint, Self::Lsm(_) => ProgramType::Lsm, Self::BtfTracePoint(_) | Self::FEntry(_) | Self::FExit(_) => ProgramType::Tracing, Self::Extension(_) => ProgramType::Extension, Self::CgroupSockAddr(_) => ProgramType::CgroupSockAddr, Self::SkLookup(_) => ProgramType::SkLookup, Self::CgroupSock(_) => ProgramType::CgroupSock, Self::CgroupDevice(_) => ProgramType::CgroupDevice, } } /// Pin the program to the provided path pub fn pin>(&mut self, path: P) -> Result<(), PinError> { match self { Self::KProbe(p) => p.pin(path), Self::UProbe(p) => p.pin(path), Self::TracePoint(p) => p.pin(path), Self::SocketFilter(p) => p.pin(path), Self::Xdp(p) => p.pin(path), Self::SkMsg(p) => p.pin(path), Self::SkSkb(p) => p.pin(path), Self::SockOps(p) => p.pin(path), Self::SchedClassifier(p) => p.pin(path), Self::CgroupSkb(p) => p.pin(path), Self::CgroupSysctl(p) => p.pin(path), Self::CgroupSockopt(p) => p.pin(path), Self::LircMode2(p) => p.pin(path), Self::PerfEvent(p) => p.pin(path), Self::RawTracePoint(p) => p.pin(path), Self::Lsm(p) => p.pin(path), Self::BtfTracePoint(p) => p.pin(path), Self::FEntry(p) => p.pin(path), Self::FExit(p) => p.pin(path), Self::Extension(p) => p.pin(path), Self::CgroupSockAddr(p) => p.pin(path), Self::SkLookup(p) => p.pin(path), Self::CgroupSock(p) => p.pin(path), Self::CgroupDevice(p) => p.pin(path), } } /// Unloads the program from the kernel. pub fn unload(self) -> Result<(), ProgramError> { match self { Self::KProbe(mut p) => p.unload(), Self::UProbe(mut p) => p.unload(), Self::TracePoint(mut p) => p.unload(), Self::SocketFilter(mut p) => p.unload(), Self::Xdp(mut p) => p.unload(), Self::SkMsg(mut p) => p.unload(), Self::SkSkb(mut p) => p.unload(), Self::SockOps(mut p) => p.unload(), Self::SchedClassifier(mut p) => p.unload(), Self::CgroupSkb(mut p) => p.unload(), Self::CgroupSysctl(mut p) => p.unload(), Self::CgroupSockopt(mut p) => p.unload(), Self::LircMode2(mut p) => p.unload(), Self::PerfEvent(mut p) => p.unload(), Self::RawTracePoint(mut p) => p.unload(), Self::Lsm(mut p) => p.unload(), Self::BtfTracePoint(mut p) => p.unload(), Self::FEntry(mut p) => p.unload(), Self::FExit(mut p) => p.unload(), Self::Extension(mut p) => p.unload(), Self::CgroupSockAddr(mut p) => p.unload(), Self::SkLookup(mut p) => p.unload(), Self::CgroupSock(mut p) => p.unload(), Self::CgroupDevice(mut p) => p.unload(), } } /// Returns the file descriptor of a program. /// /// Can be used to add a program to a [`crate::maps::ProgramArray`] or attach an [`Extension`] program. pub fn fd(&self) -> Result<&ProgramFd, ProgramError> { match self { Self::KProbe(p) => p.fd(), Self::UProbe(p) => p.fd(), Self::TracePoint(p) => p.fd(), Self::SocketFilter(p) => p.fd(), Self::Xdp(p) => p.fd(), Self::SkMsg(p) => p.fd(), Self::SkSkb(p) => p.fd(), Self::SockOps(p) => p.fd(), Self::SchedClassifier(p) => p.fd(), Self::CgroupSkb(p) => p.fd(), Self::CgroupSysctl(p) => p.fd(), Self::CgroupSockopt(p) => p.fd(), Self::LircMode2(p) => p.fd(), Self::PerfEvent(p) => p.fd(), Self::RawTracePoint(p) => p.fd(), Self::Lsm(p) => p.fd(), Self::BtfTracePoint(p) => p.fd(), Self::FEntry(p) => p.fd(), Self::FExit(p) => p.fd(), Self::Extension(p) => p.fd(), Self::CgroupSockAddr(p) => p.fd(), Self::SkLookup(p) => p.fd(), Self::CgroupSock(p) => p.fd(), Self::CgroupDevice(p) => p.fd(), } } /// Returns information about a loaded program with the [`ProgramInfo`] structure. /// /// This information is populated at load time by the kernel and can be used /// to get kernel details for a given [`Program`]. pub fn info(&self) -> Result { match self { Self::KProbe(p) => p.info(), Self::UProbe(p) => p.info(), Self::TracePoint(p) => p.info(), Self::SocketFilter(p) => p.info(), Self::Xdp(p) => p.info(), Self::SkMsg(p) => p.info(), Self::SkSkb(p) => p.info(), Self::SockOps(p) => p.info(), Self::SchedClassifier(p) => p.info(), Self::CgroupSkb(p) => p.info(), Self::CgroupSysctl(p) => p.info(), Self::CgroupSockopt(p) => p.info(), Self::LircMode2(p) => p.info(), Self::PerfEvent(p) => p.info(), Self::RawTracePoint(p) => p.info(), Self::Lsm(p) => p.info(), Self::BtfTracePoint(p) => p.info(), Self::FEntry(p) => p.info(), Self::FExit(p) => p.info(), Self::Extension(p) => p.info(), Self::CgroupSockAddr(p) => p.info(), Self::SkLookup(p) => p.info(), Self::CgroupSock(p) => p.info(), Self::CgroupDevice(p) => p.info(), } } } #[derive(Debug)] pub(crate) struct ProgramData { pub(crate) name: Option, pub(crate) obj: Option<(obj::Program, obj::Function)>, pub(crate) fd: Option, pub(crate) links: LinkMap, pub(crate) expected_attach_type: Option, pub(crate) attach_btf_obj_fd: Option, pub(crate) attach_btf_id: Option, pub(crate) attach_prog_fd: Option, pub(crate) btf_fd: Option>, pub(crate) verifier_log_level: VerifierLogLevel, pub(crate) path: Option, pub(crate) flags: u32, } impl ProgramData { pub(crate) fn new( name: Option, obj: (obj::Program, obj::Function), btf_fd: Option>, verifier_log_level: VerifierLogLevel, ) -> Self { Self { name, obj: Some(obj), fd: None, links: LinkMap::new(), expected_attach_type: None, attach_btf_obj_fd: None, attach_btf_id: None, attach_prog_fd: None, btf_fd, verifier_log_level, path: None, flags: 0, } } pub(crate) fn from_bpf_prog_info( name: Option, fd: crate::MockableFd, path: &Path, info: bpf_prog_info, verifier_log_level: VerifierLogLevel, ) -> Result { let attach_btf_id = if info.attach_btf_id > 0 { Some(info.attach_btf_id) } else { None }; let attach_btf_obj_fd = (info.attach_btf_obj_id != 0) .then(|| bpf_btf_get_fd_by_id(info.attach_btf_obj_id)) .transpose()?; Ok(Self { name, obj: None, fd: Some(ProgramFd(fd)), links: LinkMap::new(), expected_attach_type: None, attach_btf_obj_fd, attach_btf_id, attach_prog_fd: None, btf_fd: None, verifier_log_level, path: Some(path.to_path_buf()), flags: 0, }) } pub(crate) fn from_pinned_path>( path: P, verifier_log_level: VerifierLogLevel, ) -> Result { use std::os::unix::ffi::OsStrExt as _; // TODO: avoid this unwrap by adding a new error variant. let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); let fd = bpf_get_object(&path_string).map_err(|(_, io_error)| SyscallError { call: "bpf_obj_get", io_error, })?; let info = ProgramInfo::new_from_fd(fd.as_fd())?; let name = info.name_as_str().map(|s| s.to_string()); Self::from_bpf_prog_info(name, fd, path.as_ref(), info.0, verifier_log_level) } } impl ProgramData { fn fd(&self) -> Result<&ProgramFd, ProgramError> { self.fd.as_ref().ok_or(ProgramError::NotLoaded) } pub(crate) fn take_link(&mut self, link_id: T::Id) -> Result { self.links.forget(link_id) } } fn unload_program(data: &mut ProgramData) -> Result<(), ProgramError> { data.links.remove_all()?; data.fd .take() .ok_or(ProgramError::NotLoaded) .map(|ProgramFd { .. }| ()) } fn pin_program>(data: &ProgramData, path: P) -> Result<(), PinError> { use std::os::unix::ffi::OsStrExt as _; let fd = data.fd.as_ref().ok_or(PinError::NoFd { name: data .name .as_deref() .unwrap_or("") .to_string(), })?; let path = path.as_ref(); let path_string = CString::new(path.as_os_str().as_bytes()).map_err(|error| PinError::InvalidPinPath { path: path.into(), error, })?; bpf_pin_object(fd.as_fd(), &path_string).map_err(|(_, io_error)| SyscallError { call: "BPF_OBJ_PIN", io_error, })?; Ok(()) } fn load_program( prog_type: bpf_prog_type, data: &mut ProgramData, ) -> Result<(), ProgramError> { let ProgramData { name, obj, fd, links: _, expected_attach_type, attach_btf_obj_fd, attach_btf_id, attach_prog_fd, btf_fd, verifier_log_level, path: _, flags, } = data; if fd.is_some() { return Err(ProgramError::AlreadyLoaded); } if obj.is_none() { // This program was loaded from a pin in bpffs return Err(ProgramError::AlreadyLoaded); } let obj = obj.as_ref().unwrap(); let ( obj::Program { license, kernel_version, .. }, obj::Function { instructions, func_info, line_info, func_info_rec_size, line_info_rec_size, .. }, ) = obj; let target_kernel_version = kernel_version.unwrap_or_else(|| KernelVersion::current().unwrap().code()); let prog_name = if let Some(name) = name { let mut name = name.clone(); if name.len() > 15 { name.truncate(15); } let prog_name = CString::new(name.clone()) .map_err(|_| ProgramError::InvalidName { name: name.clone() })?; Some(prog_name) } else { None }; let attr = EbpfLoadProgramAttrs { name: prog_name, ty: prog_type, insns: instructions, license, kernel_version: target_kernel_version, expected_attach_type: *expected_attach_type, prog_btf_fd: btf_fd.as_ref().map(|f| f.as_fd()), attach_btf_obj_fd: attach_btf_obj_fd.as_ref().map(|fd| fd.as_fd()), attach_btf_id: *attach_btf_id, attach_prog_fd: attach_prog_fd.as_ref().map(|fd| fd.as_fd()), func_info_rec_size: *func_info_rec_size, func_info: func_info.clone(), line_info_rec_size: *line_info_rec_size, line_info: line_info.clone(), flags: *flags, }; let (ret, verifier_log) = retry_with_verifier_logs(10, |logger| { bpf_load_program(&attr, logger, *verifier_log_level) }); match ret { Ok(prog_fd) => { *fd = Some(ProgramFd(prog_fd)); Ok(()) } Err((_, io_error)) => Err(ProgramError::LoadError { io_error, verifier_log, }), } } pub(crate) fn query( target: ProgQueryTarget<'_>, attach_type: bpf_attach_type, query_flags: u32, attach_flags: &mut Option, ) -> Result<(u64, Vec), ProgramError> { let mut prog_ids = vec![0u32; 64]; let mut prog_cnt = prog_ids.len() as u32; let mut revision = 0; let mut retries = 0; loop { match bpf_prog_query( &target, attach_type, query_flags, attach_flags.as_mut(), &mut prog_ids, &mut prog_cnt, &mut revision, ) { Ok(_) => { prog_ids.resize(prog_cnt as usize, 0); return Ok((revision, prog_ids)); } Err((_, io_error)) => { if retries == 0 && io_error.raw_os_error() == Some(ENOSPC) { prog_ids.resize(prog_cnt as usize, 0); retries += 1; } else { return Err(SyscallError { call: "bpf_prog_query", io_error, } .into()); } } } } } macro_rules! impl_program_unload { ($($struct_name:ident),+ $(,)?) => { $( impl $struct_name { /// Unloads the program from the kernel. /// /// Links will be detached before unloading the program. Note /// that owned links obtained using `take_link()` will not be /// detached. pub fn unload(&mut self) -> Result<(), ProgramError> { unload_program(&mut self.data) } } impl Drop for $struct_name { fn drop(&mut self) { let _ = self.unload(); } } )+ } } impl_program_unload!( KProbe, UProbe, TracePoint, SocketFilter, Xdp, SkMsg, SkSkb, SchedClassifier, CgroupSkb, CgroupSysctl, CgroupSockopt, LircMode2, PerfEvent, Lsm, RawTracePoint, BtfTracePoint, FEntry, FExit, Extension, CgroupSockAddr, SkLookup, SockOps, CgroupSock, CgroupDevice, ); macro_rules! impl_fd { ($($struct_name:ident),+ $(,)?) => { $( impl $struct_name { /// Returns the file descriptor of this Program. pub fn fd(&self) -> Result<&ProgramFd, ProgramError> { self.data.fd() } } )+ } } impl_fd!( KProbe, UProbe, TracePoint, SocketFilter, Xdp, SkMsg, SkSkb, SchedClassifier, CgroupSkb, CgroupSysctl, CgroupSockopt, LircMode2, PerfEvent, Lsm, RawTracePoint, BtfTracePoint, FEntry, FExit, Extension, CgroupSockAddr, SkLookup, SockOps, CgroupSock, CgroupDevice, ); /// Trait implemented by the [`Program`] types which support the kernel's /// [generic multi-prog API](https://github.com/torvalds/linux/commit/053c8e1f235dc3f69d13375b32f4209228e1cb96). /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 6.6.0. pub trait MultiProgram { /// Borrows the file descriptor. fn fd(&self) -> Result, ProgramError>; } macro_rules! impl_multiprog_fd { ($($struct_name:ident),+ $(,)?) => { $( impl MultiProgram for $struct_name { fn fd(&self) -> Result, ProgramError> { Ok(self.fd()?.as_fd()) } } )+ } } impl_multiprog_fd!(SchedClassifier); /// Trait implemented by the [`Link`] types which support the kernel's /// [generic multi-prog API](https://github.com/torvalds/linux/commit/053c8e1f235dc3f69d13375b32f4209228e1cb96). /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 6.6.0. pub trait MultiProgLink { /// Borrows the file descriptor. fn fd(&self) -> Result, LinkError>; } macro_rules! impl_multiproglink_fd { ($($struct_name:ident),+ $(,)?) => { $( impl MultiProgLink for $struct_name { fn fd(&self) -> Result, LinkError> { let link: &FdLink = self.try_into()?; Ok(link.fd.as_fd()) } } )+ } } impl_multiproglink_fd!(SchedClassifierLink); macro_rules! impl_program_pin{ ($($struct_name:ident),+ $(,)?) => { $( impl $struct_name { /// Pins the program to a BPF filesystem. /// /// When a BPF object is pinned to a BPF filesystem it will remain loaded after /// Aya has unloaded the program. /// To remove the program, the file on the BPF filesystem must be removed. /// Any directories in the the path provided should have been created by the caller. pub fn pin>(&mut self, path: P) -> Result<(), PinError> { self.data.path = Some(path.as_ref().to_path_buf()); pin_program(&self.data, path) } /// Removes the pinned link from the filesystem. pub fn unpin(mut self) -> Result<(), io::Error> { if let Some(path) = self.data.path.take() { std::fs::remove_file(path)?; } Ok(()) } } )+ } } impl_program_pin!( KProbe, UProbe, TracePoint, SocketFilter, Xdp, SkMsg, SkSkb, SchedClassifier, CgroupSkb, CgroupSysctl, CgroupSockopt, LircMode2, PerfEvent, Lsm, RawTracePoint, BtfTracePoint, FEntry, FExit, Extension, CgroupSockAddr, SkLookup, SockOps, CgroupSock, CgroupDevice, ); macro_rules! impl_from_pin { ($($struct_name:ident),+ $(,)?) => { $( impl $struct_name { /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data }) } } )+ } } // Use impl_from_pin if the program doesn't require additional data impl_from_pin!( TracePoint, SocketFilter, SkMsg, CgroupSysctl, LircMode2, PerfEvent, Lsm, RawTracePoint, BtfTracePoint, FEntry, FExit, Extension, SkLookup, SockOps, CgroupDevice, ); macro_rules! impl_try_from_program { ($($ty:ident),+ $(,)?) => { $( impl<'a> TryFrom<&'a Program> for &'a $ty { type Error = ProgramError; fn try_from(program: &'a Program) -> Result<&'a $ty, ProgramError> { match program { Program::$ty(p) => Ok(p), _ => Err(ProgramError::UnexpectedProgramType), } } } impl<'a> TryFrom<&'a mut Program> for &'a mut $ty { type Error = ProgramError; fn try_from(program: &'a mut Program) -> Result<&'a mut $ty, ProgramError> { match program { Program::$ty(p) => Ok(p), _ => Err(ProgramError::UnexpectedProgramType), } } } )+ } } impl_try_from_program!( KProbe, UProbe, TracePoint, SocketFilter, Xdp, SkMsg, SkSkb, SockOps, SchedClassifier, CgroupSkb, CgroupSysctl, CgroupSockopt, LircMode2, PerfEvent, Lsm, RawTracePoint, BtfTracePoint, FEntry, FExit, Extension, CgroupSockAddr, SkLookup, CgroupSock, CgroupDevice, ); impl_info!( KProbe, UProbe, TracePoint, SocketFilter, Xdp, SkMsg, SkSkb, SchedClassifier, CgroupSkb, CgroupSysctl, CgroupSockopt, LircMode2, PerfEvent, Lsm, RawTracePoint, BtfTracePoint, FEntry, FExit, Extension, CgroupSockAddr, SkLookup, SockOps, CgroupSock, CgroupDevice, ); // TODO(https://github.com/aya-rs/aya/issues/645): this API is currently used in tests. Stabilize // and remove doc(hidden). #[doc(hidden)] pub fn loaded_links() -> impl Iterator> { iter_link_ids() .map(|id| { let id = id?; bpf_link_get_fd_by_id(id) }) .map(|fd| { let fd = fd?; bpf_link_get_info_by_fd(fd.as_fd()) }) .map(|result| result.map_err(Into::into)) } aya-0.13.1/src/programs/perf_attach.rs000064400000000000000000000063101046102023000157270ustar 00000000000000//! Perf attach links. use std::os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, RawFd}; use crate::{ generated::bpf_attach_type::BPF_PERF_EVENT, programs::{ probe::{detach_debug_fs, ProbeEvent}, FdLink, Link, ProgramError, }, sys::{bpf_link_create, perf_event_ioctl, LinkTarget, SysResult, SyscallError}, FEATURES, PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_SET_BPF, }; #[derive(Debug, Hash, Eq, PartialEq)] pub(crate) enum PerfLinkIdInner { FdLinkId(::Id), PerfLinkId(::Id), } #[derive(Debug)] pub(crate) enum PerfLinkInner { FdLink(FdLink), PerfLink(PerfLink), } impl Link for PerfLinkInner { type Id = PerfLinkIdInner; fn id(&self) -> Self::Id { match self { Self::FdLink(link) => PerfLinkIdInner::FdLinkId(link.id()), Self::PerfLink(link) => PerfLinkIdInner::PerfLinkId(link.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::FdLink(link) => link.detach(), Self::PerfLink(link) => link.detach(), } } } /// The identifer of a PerfLink. #[derive(Debug, Hash, Eq, PartialEq)] pub struct PerfLinkId(RawFd); /// The attachment type of PerfEvent programs. #[derive(Debug)] pub struct PerfLink { perf_fd: crate::MockableFd, event: Option, } impl Link for PerfLink { type Id = PerfLinkId; fn id(&self) -> Self::Id { PerfLinkId(self.perf_fd.as_raw_fd()) } fn detach(self) -> Result<(), ProgramError> { let Self { perf_fd, event } = self; let _: SysResult<_> = perf_event_ioctl(perf_fd.as_fd(), PERF_EVENT_IOC_DISABLE, 0); if let Some(event) = event { let _: Result<_, _> = detach_debug_fs(event); } Ok(()) } } pub(crate) fn perf_attach( prog_fd: BorrowedFd<'_>, fd: crate::MockableFd, ) -> Result { if FEATURES.bpf_perf_link() { let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(fd.as_fd()), BPF_PERF_EVENT, None, 0, None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; Ok(PerfLinkInner::FdLink(FdLink::new(link_fd))) } else { perf_attach_either(prog_fd, fd, None) } } pub(crate) fn perf_attach_debugfs( prog_fd: BorrowedFd<'_>, fd: crate::MockableFd, event: ProbeEvent, ) -> Result { perf_attach_either(prog_fd, fd, Some(event)) } fn perf_attach_either( prog_fd: BorrowedFd<'_>, fd: crate::MockableFd, event: Option, ) -> Result { perf_event_ioctl(fd.as_fd(), PERF_EVENT_IOC_SET_BPF, prog_fd.as_raw_fd()).map_err( |(_, io_error)| SyscallError { call: "PERF_EVENT_IOC_SET_BPF", io_error, }, )?; perf_event_ioctl(fd.as_fd(), PERF_EVENT_IOC_ENABLE, 0).map_err(|(_, io_error)| { SyscallError { call: "PERF_EVENT_IOC_ENABLE", io_error, } })?; Ok(PerfLinkInner::PerfLink(PerfLink { perf_fd: fd, event })) } aya-0.13.1/src/programs/perf_event.rs000064400000000000000000000155201046102023000156070ustar 00000000000000//! Perf event programs. use std::os::fd::AsFd as _; pub use crate::generated::{ perf_hw_cache_id, perf_hw_cache_op_id, perf_hw_cache_op_result_id, perf_hw_id, perf_sw_ids, }; use crate::{ generated::{ bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT, perf_type_id::{ PERF_TYPE_BREAKPOINT, PERF_TYPE_HARDWARE, PERF_TYPE_HW_CACHE, PERF_TYPE_RAW, PERF_TYPE_SOFTWARE, PERF_TYPE_TRACEPOINT, }, }, programs::{ links::define_link_wrapper, load_program, perf_attach, perf_attach::{PerfLinkIdInner, PerfLinkInner}, FdLink, LinkError, ProgramData, ProgramError, }, sys::{bpf_link_get_info_by_fd, perf_event_open, SyscallError}, }; /// The type of perf event #[repr(u32)] #[derive(Debug, Clone)] pub enum PerfTypeId { /// PERF_TYPE_HARDWARE Hardware = PERF_TYPE_HARDWARE as u32, /// PERF_TYPE_SOFTWARE Software = PERF_TYPE_SOFTWARE as u32, /// PERF_TYPE_TRACEPOINT TracePoint = PERF_TYPE_TRACEPOINT as u32, /// PERF_TYPE_HW_CACHE HwCache = PERF_TYPE_HW_CACHE as u32, /// PERF_TYPE_RAW Raw = PERF_TYPE_RAW as u32, /// PERF_TYPE_BREAKPOINT Breakpoint = PERF_TYPE_BREAKPOINT as u32, } /// Sample Policy #[derive(Debug, Clone)] pub enum SamplePolicy { /// Period Period(u64), /// Frequency Frequency(u64), } /// The scope of a PerfEvent #[derive(Debug, Clone)] #[allow(clippy::enum_variant_names)] pub enum PerfEventScope { /// Calling process, any cpu CallingProcessAnyCpu, /// calling process, one cpu CallingProcessOneCpu { /// cpu id cpu: u32, }, /// one process, any cpu OneProcessAnyCpu { /// process id pid: u32, }, /// one process, one cpu OneProcessOneCpu { /// cpu id cpu: u32, /// process id pid: u32, }, /// all processes, one cpu AllProcessesOneCpu { /// cpu id cpu: u32, }, } /// A program that can be attached at a perf event. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.9. /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::util::online_cpus; /// use aya::programs::perf_event::{ /// perf_sw_ids::PERF_COUNT_SW_CPU_CLOCK, PerfEvent, PerfEventScope, PerfTypeId, SamplePolicy, /// }; /// /// let prog: &mut PerfEvent = bpf.program_mut("observe_cpu_clock").unwrap().try_into()?; /// prog.load()?; /// /// for cpu in online_cpus().map_err(|(_, error)| error)? { /// prog.attach( /// PerfTypeId::Software, /// PERF_COUNT_SW_CPU_CLOCK as u64, /// PerfEventScope::AllProcessesOneCpu { cpu }, /// SamplePolicy::Period(1000000), /// true, /// )?; /// } /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")] pub struct PerfEvent { pub(crate) data: ProgramData, } impl PerfEvent { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_PERF_EVENT, &mut self.data) } /// Attaches to the given perf event. /// /// The possible values and encoding of the `config` argument depends on the /// `perf_type`. See `perf_sw_ids`, `perf_hw_id`, `perf_hw_cache_id`, /// `perf_hw_cache_op_id` and `perf_hw_cache_op_result_id`. /// /// The `scope` argument determines which processes are sampled. If `inherit` /// is true, any new processes spawned by those processes will also /// automatically get sampled. /// /// The returned value can be used to detach, see [PerfEvent::detach]. pub fn attach( &mut self, perf_type: PerfTypeId, config: u64, scope: PerfEventScope, sample_policy: SamplePolicy, inherit: bool, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let (sample_period, sample_frequency) = match sample_policy { SamplePolicy::Period(period) => (period, None), SamplePolicy::Frequency(frequency) => (0, Some(frequency)), }; let (pid, cpu) = match scope { PerfEventScope::CallingProcessAnyCpu => (0, -1), PerfEventScope::CallingProcessOneCpu { cpu } => (0, cpu as i32), PerfEventScope::OneProcessAnyCpu { pid } => (pid as i32, -1), PerfEventScope::OneProcessOneCpu { cpu, pid } => (pid as i32, cpu as i32), PerfEventScope::AllProcessesOneCpu { cpu } => (-1, cpu as i32), }; let fd = perf_event_open( perf_type as u32, config, pid, cpu, sample_period, sample_frequency, false, inherit, 0, ) .map_err(|(_code, io_error)| SyscallError { call: "perf_event_open", io_error, })?; let link = perf_attach(prog_fd, fd)?; self.data.links.insert(PerfEventLink::new(link)) } /// Detaches the program. /// /// See [PerfEvent::attach]. pub fn detach(&mut self, link_id: PerfEventLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: PerfEventLinkId) -> Result { self.data.take_link(link_id) } } impl TryFrom for FdLink { type Error = LinkError; fn try_from(value: PerfEventLink) -> Result { if let PerfLinkInner::FdLink(fd) = value.into_inner() { Ok(fd) } else { Err(LinkError::InvalidLink) } } } impl TryFrom for PerfEventLink { type Error = LinkError; fn try_from(fd_link: FdLink) -> Result { let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?; if info.type_ == (bpf_link_type::BPF_LINK_TYPE_PERF_EVENT as u32) { return Ok(Self::new(PerfLinkInner::FdLink(fd_link))); } Err(LinkError::InvalidLink) } } define_link_wrapper!( /// The link used by [PerfEvent] programs. PerfEventLink, /// The type returned by [PerfEvent::attach]. Can be passed to [PerfEvent::detach]. PerfEventLinkId, PerfLinkInner, PerfLinkIdInner ); aya-0.13.1/src/programs/probe.rs000064400000000000000000000250711046102023000145630ustar 00000000000000use std::{ ffi::{OsStr, OsString}, fmt::Write as _, fs::{self, OpenOptions}, io::{self, Write}, os::fd::AsFd as _, path::{Path, PathBuf}, process, sync::atomic::{AtomicUsize, Ordering}, }; use libc::pid_t; use crate::{ programs::{ kprobe::KProbeError, perf_attach, perf_attach::PerfLinkInner, perf_attach_debugfs, trace_point::read_sys_fs_trace_point_id, uprobe::UProbeError, utils::find_tracefs_path, Link, ProgramData, ProgramError, }, sys::{perf_event_open_probe, perf_event_open_trace_point, SyscallError}, util::KernelVersion, }; static PROBE_NAME_INDEX: AtomicUsize = AtomicUsize::new(0); /// Kind of probe program #[derive(Debug, Copy, Clone)] pub enum ProbeKind { /// Kernel probe KProbe, /// Kernel return probe KRetProbe, /// User space probe UProbe, /// User space return probe URetProbe, } impl ProbeKind { fn pmu(&self) -> &'static str { match *self { Self::KProbe | Self::KRetProbe => "kprobe", Self::UProbe | Self::URetProbe => "uprobe", } } } pub(crate) fn lines(bytes: &[u8]) -> impl Iterator { use std::os::unix::ffi::OsStrExt as _; bytes.as_ref().split(|b| b == &b'\n').map(|mut line| { while let [stripped @ .., c] = line { if c.is_ascii_whitespace() { line = stripped; continue; } break; } OsStr::from_bytes(line) }) } pub(crate) trait OsStringExt { fn starts_with(&self, needle: &OsStr) -> bool; #[allow(dead_code)] // Would be odd to have the others without this one. fn ends_with(&self, needle: &OsStr) -> bool; fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr>; fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr>; } impl OsStringExt for OsStr { fn starts_with(&self, needle: &OsStr) -> bool { use std::os::unix::ffi::OsStrExt as _; self.as_bytes().starts_with(needle.as_bytes()) } fn ends_with(&self, needle: &OsStr) -> bool { use std::os::unix::ffi::OsStrExt as _; self.as_bytes().ends_with(needle.as_bytes()) } fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr> { use std::os::unix::ffi::OsStrExt as _; self.as_bytes() .strip_prefix(prefix.as_bytes()) .map(Self::from_bytes) } fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr> { use std::os::unix::ffi::OsStrExt as _; self.as_bytes() .strip_suffix(suffix.as_bytes()) .map(Self::from_bytes) } } #[derive(Debug)] pub(crate) struct ProbeEvent { kind: ProbeKind, event_alias: OsString, } pub(crate) fn attach>( program_data: &mut ProgramData, kind: ProbeKind, // NB: the meaning of this argument is different for kprobe/kretprobe and uprobe/uretprobe; in // the kprobe case it is the name of the function to attach to, in the uprobe case it is a path // to the binary or library. // // TODO: consider encoding the type and the argument in the [`ProbeKind`] enum instead of a // separate argument. fn_name: &OsStr, offset: u64, pid: Option, ) -> Result { // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155 // Use debugfs to create probe let prog_fd = program_data.fd()?; let prog_fd = prog_fd.as_fd(); let link = if KernelVersion::current().unwrap() < KernelVersion::new(4, 17, 0) { let (fd, event_alias) = create_as_trace_point(kind, fn_name, offset, pid)?; perf_attach_debugfs(prog_fd, fd, ProbeEvent { kind, event_alias }) } else { let fd = create_as_probe(kind, fn_name, offset, pid)?; perf_attach(prog_fd, fd) }?; program_data.links.insert(T::from(link)) } pub(crate) fn detach_debug_fs(event: ProbeEvent) -> Result<(), ProgramError> { use ProbeKind::*; let tracefs = find_tracefs_path()?; let ProbeEvent { kind, event_alias: _, } = &event; let kind = *kind; let result = delete_probe_event(tracefs, event); result.map_err(|(filename, io_error)| match kind { KProbe | KRetProbe => KProbeError::FileError { filename, io_error }.into(), UProbe | URetProbe => UProbeError::FileError { filename, io_error }.into(), }) } fn create_as_probe( kind: ProbeKind, fn_name: &OsStr, offset: u64, pid: Option, ) -> Result { use ProbeKind::*; let perf_ty = match kind { KProbe | KRetProbe => read_sys_fs_perf_type(kind.pmu()) .map_err(|(filename, io_error)| KProbeError::FileError { filename, io_error })?, UProbe | URetProbe => read_sys_fs_perf_type(kind.pmu()) .map_err(|(filename, io_error)| UProbeError::FileError { filename, io_error })?, }; let ret_bit = match kind { KRetProbe => Some( read_sys_fs_perf_ret_probe(kind.pmu()) .map_err(|(filename, io_error)| KProbeError::FileError { filename, io_error })?, ), URetProbe => Some( read_sys_fs_perf_ret_probe(kind.pmu()) .map_err(|(filename, io_error)| UProbeError::FileError { filename, io_error })?, ), _ => None, }; perf_event_open_probe(perf_ty, ret_bit, fn_name, offset, pid).map_err(|(_code, io_error)| { SyscallError { call: "perf_event_open", io_error, } .into() }) } fn create_as_trace_point( kind: ProbeKind, name: &OsStr, offset: u64, pid: Option, ) -> Result<(crate::MockableFd, OsString), ProgramError> { use ProbeKind::*; let tracefs = find_tracefs_path()?; let event_alias = match kind { KProbe | KRetProbe => create_probe_event(tracefs, kind, name, offset) .map_err(|(filename, io_error)| KProbeError::FileError { filename, io_error })?, UProbe | URetProbe => create_probe_event(tracefs, kind, name, offset) .map_err(|(filename, io_error)| UProbeError::FileError { filename, io_error })?, }; let category = format!("{}s", kind.pmu()); let tpid = read_sys_fs_trace_point_id(tracefs, &category, event_alias.as_ref())?; let fd = perf_event_open_trace_point(tpid, pid).map_err(|(_code, io_error)| SyscallError { call: "perf_event_open", io_error, })?; Ok((fd, event_alias)) } fn create_probe_event( tracefs: &Path, kind: ProbeKind, fn_name: &OsStr, offset: u64, ) -> Result { use std::os::unix::ffi::OsStrExt as _; use ProbeKind::*; let events_file_name = tracefs.join(format!("{}_events", kind.pmu())); let probe_type_prefix = match kind { KProbe | UProbe => 'p', KRetProbe | URetProbe => 'r', }; let mut event_alias = OsString::new(); write!( &mut event_alias, "aya_{}_{}_", process::id(), probe_type_prefix, ) .unwrap(); for b in fn_name.as_bytes() { let b = match *b { b'.' | b'/' | b'-' => b'_', b => b, }; event_alias.push(OsStr::from_bytes(&[b])); } write!( &mut event_alias, "_{:#x}_{}", offset, PROBE_NAME_INDEX.fetch_add(1, Ordering::AcqRel) ) .unwrap(); let mut probe = OsString::new(); write!(&mut probe, "{}:{}s/", probe_type_prefix, kind.pmu(),).unwrap(); probe.push(&event_alias); probe.push(" "); probe.push(fn_name); match kind { KProbe => write!(&mut probe, "+{offset}").unwrap(), UProbe | URetProbe => write!(&mut probe, ":{offset:#x}").unwrap(), _ => {} }; probe.push("\n"); OpenOptions::new() .append(true) .open(&events_file_name) .and_then(|mut events_file| events_file.write_all(probe.as_bytes())) .map_err(|e| (events_file_name, e))?; Ok(event_alias) } fn delete_probe_event(tracefs: &Path, event: ProbeEvent) -> Result<(), (PathBuf, io::Error)> { use std::os::unix::ffi::OsStrExt as _; let ProbeEvent { kind, event_alias } = event; let events_file_name = tracefs.join(format!("{}_events", kind.pmu())); fs::read(&events_file_name) .and_then(|events| { let found = lines(&events).any(|line| { let mut line = line.as_bytes(); // See [`create_probe_event`] and the documentation: // // https://docs.kernel.org/trace/kprobetrace.html // // https://docs.kernel.org/trace/uprobetracer.html loop { match line.split_first() { None => break false, Some((b, rest)) => { line = rest; if *b == b'/' { break line.starts_with(event_alias.as_bytes()); } } } } }); if found { OpenOptions::new() .append(true) .open(&events_file_name) .and_then(|mut events_file| { let mut rm = OsString::new(); rm.push("-:"); rm.push(event_alias); rm.push("\n"); events_file.write_all(rm.as_bytes()) }) } else { Ok(()) } }) .map_err(|e| (events_file_name, e)) } fn read_sys_fs_perf_type(pmu: &str) -> Result { let file = Path::new("/sys/bus/event_source/devices") .join(pmu) .join("type"); fs::read_to_string(&file) .and_then(|perf_ty| { perf_ty .trim() .parse::() .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) }) .map_err(|e| (file, e)) } fn read_sys_fs_perf_ret_probe(pmu: &str) -> Result { let file = Path::new("/sys/bus/event_source/devices") .join(pmu) .join("format/retprobe"); fs::read_to_string(&file) .and_then(|data| { let mut parts = data.trim().splitn(2, ':').skip(1); let config = parts .next() .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid format"))?; config .parse::() .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) }) .map_err(|e| (file, e)) } aya-0.13.1/src/programs/raw_trace_point.rs000064400000000000000000000050611046102023000166310ustar 00000000000000//! Raw tracepoints. use std::ffi::CString; use crate::{ generated::bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT, programs::{ define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId, ProgramData, ProgramError, }, }; /// A program that can be attached at a pre-defined kernel trace point. /// /// Unlike [`TracePoint`](super::TracePoint), the kernel does not pre-process /// the arguments before calling the program. /// /// The kernel provides a set of pre-defined trace points that eBPF programs can /// be attached to. See`/sys/kernel/debug/tracing/events` for a list of which /// events can be traced. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.17. /// /// # Examples /// /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::programs::RawTracePoint; /// /// let program: &mut RawTracePoint = bpf.program_mut("sys_enter").unwrap().try_into()?; /// program.load()?; /// program.attach("sys_enter")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_RAW_TRACEPOINT")] pub struct RawTracePoint { pub(crate) data: ProgramData, } impl RawTracePoint { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_RAW_TRACEPOINT, &mut self.data) } /// Attaches the program to the given tracepoint. /// /// The returned value can be used to detach, see [RawTracePoint::detach]. pub fn attach(&mut self, tp_name: &str) -> Result { let tp_name_c = CString::new(tp_name).unwrap(); attach_raw_tracepoint(&mut self.data, Some(&tp_name_c)) } /// Detaches from a tracepoint. /// /// See [RawTracePoint::attach]. pub fn detach(&mut self, link_id: RawTracePointLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link( &mut self, link_id: RawTracePointLinkId, ) -> Result { self.data.take_link(link_id) } } define_link_wrapper!( /// The link used by [RawTracePoint] programs. RawTracePointLink, /// The type returned by [RawTracePoint::attach]. Can be passed to [RawTracePoint::detach]. RawTracePointLinkId, FdLink, FdLinkId ); aya-0.13.1/src/programs/sk_lookup.rs000064400000000000000000000065221046102023000154620ustar 00000000000000//! Programmable socket lookup. use std::os::fd::AsFd; use super::links::FdLink; use crate::{ generated::{bpf_attach_type::BPF_SK_LOOKUP, bpf_prog_type::BPF_PROG_TYPE_SK_LOOKUP}, programs::{define_link_wrapper, load_program, FdLinkId, ProgramData, ProgramError}, sys::{bpf_link_create, LinkTarget, SyscallError}, }; /// A program used to redirect incoming packets to a local socket. /// /// [`SkLookup`] programs are attached to network namespaces to provide programmable /// socket lookup for TCP/UDP when a packet is to be delievered locally. /// /// You may attach multiple programs to the same namespace and they are executed /// in the order they were attached. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.9. /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::fs::File; /// use aya::programs::SkLookup; /// /// let file = File::open("/var/run/netns/test")?; /// let program: &mut SkLookup = bpf.program_mut("sk_lookup").unwrap().try_into()?; /// program.load()?; /// program.attach(file)?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_SK_LOOKUP")] pub struct SkLookup { pub(crate) data: ProgramData, } impl SkLookup { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(BPF_SK_LOOKUP); load_program(BPF_PROG_TYPE_SK_LOOKUP, &mut self.data) } /// Attaches the program to the given network namespace. /// /// The returned value can be used to detach, see [SkLookup::detach]. pub fn attach(&mut self, netns: T) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let netns_fd = netns.as_fd(); let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(netns_fd), BPF_SK_LOOKUP, None, 0, None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(SkLookupLink::new(FdLink::new(link_fd))) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: SkLookupLinkId) -> Result { self.data.take_link(link_id) } /// Detaches the program. /// /// See [SkLookup::attach]. pub fn detach(&mut self, link_id: SkLookupLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } } define_link_wrapper!( /// The link used by [SkLookup] programs. SkLookupLink, /// The type returned by [SkLookup::attach]. Can be passed to [SkLookup::detach]. SkLookupLinkId, FdLink, FdLinkId ); aya-0.13.1/src/programs/sk_msg.rs000064400000000000000000000070511046102023000147350ustar 00000000000000//! Skmsg programs. use std::os::fd::AsFd as _; use crate::{ generated::{bpf_attach_type::BPF_SK_MSG_VERDICT, bpf_prog_type::BPF_PROG_TYPE_SK_MSG}, maps::sock::SockMapFd, programs::{ define_link_wrapper, load_program, CgroupAttachMode, ProgAttachLink, ProgAttachLinkId, ProgramData, ProgramError, }, }; /// A program used to intercept messages sent with `sendmsg()`/`sendfile()`. /// /// [`SkMsg`] programs are attached to [socket maps], and can be used inspect, /// filter and redirect messages sent on sockets. See also [`SockMap`] and /// [`SockHash`]. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.17. /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::io::Write; /// use std::net::TcpStream; /// use std::os::fd::AsRawFd; /// use aya::maps::SockHash; /// use aya::programs::SkMsg; /// /// let intercept_egress: SockHash<_, u32> = bpf.map("INTERCEPT_EGRESS").unwrap().try_into()?; /// let map_fd = intercept_egress.fd().try_clone()?; /// /// let prog: &mut SkMsg = bpf.program_mut("intercept_egress_packet").unwrap().try_into()?; /// prog.load()?; /// prog.attach(&map_fd)?; /// /// let mut client = TcpStream::connect("127.0.0.1:1234")?; /// let mut intercept_egress: SockHash<_, u32> = bpf.map_mut("INTERCEPT_EGRESS").unwrap().try_into()?; /// /// intercept_egress.insert(1234, client.as_raw_fd(), 0)?; /// /// // the write will be intercepted /// client.write_all(b"foo")?; /// # Ok::<(), Error>(()) /// ``` /// /// [socket maps]: crate::maps::sock /// [`SockMap`]: crate::maps::SockMap /// [`SockHash`]: crate::maps::SockHash #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_SK_MSG")] pub struct SkMsg { pub(crate) data: ProgramData, } impl SkMsg { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_SK_MSG, &mut self.data) } /// Attaches the program to the given sockmap. /// /// The returned value can be used to detach, see [SkMsg::detach]. pub fn attach(&mut self, map: &SockMapFd) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let link = ProgAttachLink::attach( prog_fd, map.as_fd(), BPF_SK_MSG_VERDICT, CgroupAttachMode::Single, )?; self.data.links.insert(SkMsgLink::new(link)) } /// Detaches the program from a sockmap. /// /// See [SkMsg::attach]. pub fn detach(&mut self, link_id: SkMsgLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: SkMsgLinkId) -> Result { self.data.take_link(link_id) } } define_link_wrapper!( /// The link used by [SkMsg] programs. SkMsgLink, /// The type returned by [SkMsg::attach]. Can be passed to [SkMsg::detach]. SkMsgLinkId, ProgAttachLink, ProgAttachLinkId ); aya-0.13.1/src/programs/sk_skb.rs000064400000000000000000000102121046102023000147170ustar 00000000000000//! Skskb programs. use std::{os::fd::AsFd as _, path::Path}; use crate::{ generated::{ bpf_attach_type::{BPF_SK_SKB_STREAM_PARSER, BPF_SK_SKB_STREAM_VERDICT}, bpf_prog_type::BPF_PROG_TYPE_SK_SKB, }, maps::sock::SockMapFd, programs::{ define_link_wrapper, load_program, CgroupAttachMode, ProgAttachLink, ProgAttachLinkId, ProgramData, ProgramError, }, VerifierLogLevel, }; /// The kind of [`SkSkb`] program. #[derive(Copy, Clone, Debug)] pub enum SkSkbKind { /// A Stream Parser StreamParser, /// A Stream Verdict StreamVerdict, } /// A program used to intercept ingress socket buffers. /// /// [`SkSkb`] programs are attached to [socket maps], and can be used to /// inspect, redirect or filter incoming packet. See also [`SockMap`] and /// [`SockHash`]. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.14. /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::maps::SockMap; /// use aya::programs::SkSkb; /// /// let intercept_ingress: SockMap<_> = bpf.map("INTERCEPT_INGRESS").unwrap().try_into()?; /// let map_fd = intercept_ingress.fd().try_clone()?; /// /// let prog: &mut SkSkb = bpf.program_mut("intercept_ingress_packet").unwrap().try_into()?; /// prog.load()?; /// prog.attach(&map_fd)?; /// /// # Ok::<(), Error>(()) /// ``` /// /// [socket maps]: crate::maps::sock /// [`SockMap`]: crate::maps::SockMap /// [`SockHash`]: crate::maps::SockHash #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_SK_SKB")] pub struct SkSkb { pub(crate) data: ProgramData, pub(crate) kind: SkSkbKind, } impl SkSkb { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_SK_SKB, &mut self.data) } /// Attaches the program to the given socket map. /// /// The returned value can be used to detach, see [SkSkb::detach]. pub fn attach(&mut self, map: &SockMapFd) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let attach_type = match self.kind { SkSkbKind::StreamParser => BPF_SK_SKB_STREAM_PARSER, SkSkbKind::StreamVerdict => BPF_SK_SKB_STREAM_VERDICT, }; let link = ProgAttachLink::attach(prog_fd, map.as_fd(), attach_type, CgroupAttachMode::Single)?; self.data.links.insert(SkSkbLink::new(link)) } /// Detaches the program. /// /// See [SkSkb::attach]. pub fn detach(&mut self, link_id: SkSkbLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: SkSkbLinkId) -> Result { self.data.take_link(link_id) } /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P, kind: SkSkbKind) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, kind }) } } define_link_wrapper!( /// The link used by [SkSkb] programs. SkSkbLink, /// The type returned by [SkSkb::attach]. Can be passed to [SkSkb::detach]. SkSkbLinkId, ProgAttachLink, ProgAttachLinkId ); aya-0.13.1/src/programs/sock_ops.rs000064400000000000000000000106231046102023000152710ustar 00000000000000//! Socket option programs. use std::os::fd::AsFd; use crate::{ generated::{bpf_attach_type::BPF_CGROUP_SOCK_OPS, bpf_prog_type::BPF_PROG_TYPE_SOCK_OPS}, programs::{ define_link_wrapper, load_program, CgroupAttachMode, FdLink, Link, ProgAttachLink, ProgramData, ProgramError, }, sys::{bpf_link_create, LinkTarget, SyscallError}, util::KernelVersion, }; /// A program used to work with sockets. /// /// [`SockOps`] programs can access or set socket options, connection /// parameters, watch connection state changes and more. They are attached to /// cgroups. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.13. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::fs::File; /// use aya::programs::{CgroupAttachMode, SockOps}; /// /// let file = File::open("/sys/fs/cgroup/unified")?; /// let prog: &mut SockOps = bpf.program_mut("intercept_active_sockets").unwrap().try_into()?; /// prog.load()?; /// prog.attach(file, CgroupAttachMode::Single)?; /// # Ok::<(), Error>(()) #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_SOCK_OPS")] pub struct SockOps { pub(crate) data: ProgramData, } impl SockOps { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_SOCK_OPS, &mut self.data) } /// Attaches the program to the given cgroup. /// /// The returned value can be used to detach, see [SockOps::detach]. pub fn attach( &mut self, cgroup: T, mode: CgroupAttachMode, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let cgroup_fd = cgroup.as_fd(); let attach_type = BPF_CGROUP_SOCK_OPS; if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { let link_fd = bpf_link_create( prog_fd, LinkTarget::Fd(cgroup_fd), attach_type, None, mode.into(), None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(SockOpsLink::new(SockOpsLinkInner::Fd(FdLink::new(link_fd)))) } else { let link = ProgAttachLink::attach(prog_fd, cgroup_fd, attach_type, mode)?; self.data .links .insert(SockOpsLink::new(SockOpsLinkInner::ProgAttach(link))) } } /// Detaches the program. /// /// See [SockOps::attach]. pub fn detach(&mut self, link_id: SockOpsLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: SockOpsLinkId) -> Result { self.data.take_link(link_id) } } #[derive(Debug, Hash, Eq, PartialEq)] enum SockOpsLinkIdInner { Fd(::Id), ProgAttach(::Id), } #[derive(Debug)] enum SockOpsLinkInner { Fd(FdLink), ProgAttach(ProgAttachLink), } impl Link for SockOpsLinkInner { type Id = SockOpsLinkIdInner; fn id(&self) -> Self::Id { match self { Self::Fd(fd) => SockOpsLinkIdInner::Fd(fd.id()), Self::ProgAttach(p) => SockOpsLinkIdInner::ProgAttach(p.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::Fd(fd) => fd.detach(), Self::ProgAttach(p) => p.detach(), } } } define_link_wrapper!( /// The link used by [SockOps] programs. SockOpsLink, /// The type returned by [SockOps::attach]. Can be passed to [SockOps::detach]. SockOpsLinkId, SockOpsLinkInner, SockOpsLinkIdInner ); aya-0.13.1/src/programs/socket_filter.rs000064400000000000000000000104221046102023000163030ustar 00000000000000//! Socket filter programs. use std::{ io, mem, os::fd::{AsFd, AsRawFd, RawFd}, }; use libc::{setsockopt, SOL_SOCKET}; use thiserror::Error; use crate::{ generated::{bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER, SO_ATTACH_BPF, SO_DETACH_BPF}, programs::{load_program, Link, ProgramData, ProgramError}, }; /// The type returned when attaching a [`SocketFilter`] fails. #[derive(Debug, Error)] pub enum SocketFilterError { /// Setting the `SO_ATTACH_BPF` socket option failed. #[error("setsockopt SO_ATTACH_BPF failed")] SoAttachEbpfError { /// original [`io::Error`] #[source] io_error: io::Error, }, } /// A program used to inspect and filter incoming packets on a socket. /// /// [`SocketFilter`] programs are attached on sockets and can be used to inspect /// and filter incoming packets. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.0. /// /// # Examples /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use std::net::TcpStream; /// use aya::programs::SocketFilter; /// /// let mut client = TcpStream::connect("127.0.0.1:1234")?; /// let prog: &mut SocketFilter = bpf.program_mut("filter_packets").unwrap().try_into()?; /// prog.load()?; /// prog.attach(&client)?; /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_SOCKET_FILTER")] pub struct SocketFilter { pub(crate) data: ProgramData, } impl SocketFilter { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_SOCKET_FILTER, &mut self.data) } /// Attaches the filter on the given socket. /// /// The returned value can be used to detach from the socket, see [SocketFilter::detach]. pub fn attach(&mut self, socket: T) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let prog_fd = prog_fd.as_raw_fd(); let socket = socket.as_fd(); let socket = socket.as_raw_fd(); let ret = unsafe { setsockopt( socket, SOL_SOCKET, SO_ATTACH_BPF as i32, &prog_fd as *const _ as *const _, mem::size_of::() as u32, ) }; if ret < 0 { return Err(SocketFilterError::SoAttachEbpfError { io_error: io::Error::last_os_error(), } .into()); } self.data.links.insert(SocketFilterLink { socket, prog_fd }) } /// Detaches the program. /// /// See [SocketFilter::attach]. pub fn detach(&mut self, link_id: SocketFilterLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link( &mut self, link_id: SocketFilterLinkId, ) -> Result { self.data.take_link(link_id) } } /// The type returned by [SocketFilter::attach]. Can be passed to [SocketFilter::detach]. #[derive(Debug, Hash, Eq, PartialEq)] pub struct SocketFilterLinkId(RawFd, RawFd); /// A SocketFilter Link #[derive(Debug)] pub struct SocketFilterLink { socket: RawFd, prog_fd: RawFd, } impl Link for SocketFilterLink { type Id = SocketFilterLinkId; fn id(&self) -> Self::Id { SocketFilterLinkId(self.socket, self.prog_fd) } fn detach(self) -> Result<(), ProgramError> { unsafe { setsockopt( self.socket, SOL_SOCKET, SO_DETACH_BPF as i32, &self.prog_fd as *const _ as *const _, mem::size_of::() as u32, ); } Ok(()) } } aya-0.13.1/src/programs/tc.rs000064400000000000000000000504131046102023000140600ustar 00000000000000//! Network traffic control programs. use std::{ ffi::{CStr, CString}, io, os::fd::AsFd as _, path::Path, }; use thiserror::Error; use super::{FdLink, ProgramInfo}; use crate::{ generated::{ bpf_attach_type::{self, BPF_TCX_EGRESS, BPF_TCX_INGRESS}, bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS, TC_H_CLSACT, TC_H_MIN_EGRESS, TC_H_MIN_INGRESS, }, programs::{ define_link_wrapper, load_program, query, Link, LinkError, LinkOrder, ProgramData, ProgramError, }, sys::{ bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, bpf_prog_get_fd_by_id, netlink_find_filter_with_name, netlink_qdisc_add_clsact, netlink_qdisc_attach, netlink_qdisc_detach, LinkTarget, ProgQueryTarget, SyscallError, }, util::{ifindex_from_ifname, tc_handler_make, KernelVersion}, VerifierLogLevel, }; /// Traffic control attach type. #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum TcAttachType { /// Attach to ingress. Ingress, /// Attach to egress. Egress, /// Attach to custom parent. Custom(u32), } /// A network traffic control classifier. /// /// [`SchedClassifier`] programs can be used to inspect, filter or redirect /// network packets in both ingress and egress. They are executed as part of the /// linux network traffic control system. See /// [https://man7.org/linux/man-pages/man8/tc-bpf.8.html](https://man7.org/linux/man-pages/man8/tc-bpf.8.html). /// /// # Examples /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.1. /// /// ```no_run /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # #[error(transparent)] /// # Map(#[from] aya::maps::MapError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError) /// # } /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::programs::{tc, SchedClassifier, TcAttachType}; /// /// // the clsact qdisc needs to be added before SchedClassifier programs can be /// // attached /// tc::qdisc_add_clsact("eth0")?; /// /// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?; /// prog.load()?; /// prog.attach("eth0", TcAttachType::Ingress)?; /// /// # Ok::<(), Error>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_SCHED_CLS")] pub struct SchedClassifier { pub(crate) data: ProgramData, } /// Errors from TC programs #[derive(Debug, Error)] pub enum TcError { /// netlink error while attaching ebpf program #[error("netlink error while attaching ebpf program to tc")] NetlinkError { /// the [`io::Error`] from the netlink call #[source] io_error: io::Error, }, /// the clsact qdisc is already attached #[error("the clsact qdisc is already attached")] AlreadyAttached, /// tcx links can only be attached to ingress or egress, custom attachment is not supported #[error("tcx links can only be attached to ingress or egress, custom attachment: {0} is not supported")] InvalidTcxAttach(u32), /// operation not supported for programs loaded via tcx #[error("operation not supported for programs loaded via tcx")] InvalidLinkOperation, } impl TcAttachType { pub(crate) fn tc_parent(&self) -> u32 { match self { Self::Custom(parent) => *parent, Self::Ingress => tc_handler_make(TC_H_CLSACT, TC_H_MIN_INGRESS), Self::Egress => tc_handler_make(TC_H_CLSACT, TC_H_MIN_EGRESS), } } pub(crate) fn tcx_attach_type(&self) -> Result { match self { Self::Ingress => Ok(BPF_TCX_INGRESS), Self::Egress => Ok(BPF_TCX_EGRESS), Self::Custom(tcx_attach_type) => Err(TcError::InvalidTcxAttach(*tcx_attach_type)), } } } /// Options for a SchedClassifier attach operation. /// /// The options vary based on what is supported by the current kernel. Kernels /// older than 6.6.0 must utilize netlink for attachments, while newer kernels /// can utilize the modern TCX eBPF link type which supports the kernel's /// multi-prog API. #[derive(Debug)] pub enum TcAttachOptions { /// Netlink attach options. Netlink(NlOptions), /// Tcx attach options. TcxOrder(LinkOrder), } /// Options for SchedClassifier attach via netlink. #[derive(Debug, Default, Hash, Eq, PartialEq)] pub struct NlOptions { /// Priority assigned to tc program with lower number = higher priority. /// If set to default (0), the system chooses the next highest priority or 49152 if no filters exist yet pub priority: u16, /// Handle used to uniquely identify a program at a given priority level. /// If set to default (0), the system chooses a handle. pub handle: u32, } impl SchedClassifier { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_SCHED_CLS, &mut self.data) } /// Attaches the program to the given `interface`. /// /// On kernels >= 6.6.0, it will attempt to use the TCX interface and attach as /// the last TCX program. On older kernels, it will fallback to using the /// legacy netlink interface. /// /// For finer grained control over link ordering use [`SchedClassifier::attach_with_options`]. /// /// The returned value can be used to detach, see [SchedClassifier::detach]. /// /// # Errors /// /// When attaching fails, [`ProgramError::SyscallError`] is returned for /// kernels `>= 6.6.0`, and [`TcError::NetlinkError`] is returned for /// older kernels. A common cause of netlink attachment failure is not having added /// the `clsact` qdisc to the given interface, see [`qdisc_add_clsact`] /// pub fn attach( &mut self, interface: &str, attach_type: TcAttachType, ) -> Result { if !matches!(attach_type, TcAttachType::Custom(_)) && KernelVersion::current().unwrap() >= KernelVersion::new(6, 6, 0) { self.attach_with_options( interface, attach_type, TcAttachOptions::TcxOrder(LinkOrder::default()), ) } else { self.attach_with_options( interface, attach_type, TcAttachOptions::Netlink(NlOptions::default()), ) } } /// Attaches the program to the given `interface` with options defined in [`TcAttachOptions`]. /// /// The returned value can be used to detach, see [SchedClassifier::detach]. /// /// # Errors /// /// [`TcError::NetlinkError`] is returned if attaching fails. A common cause /// of failure is not having added the `clsact` qdisc to the given /// interface, see [`qdisc_add_clsact`] /// pub fn attach_with_options( &mut self, interface: &str, attach_type: TcAttachType, options: TcAttachOptions, ) -> Result { let if_index = ifindex_from_ifname(interface) .map_err(|io_error| TcError::NetlinkError { io_error })?; self.do_attach(if_index, attach_type, options, true) } /// Atomically replaces the program referenced by the provided link. /// /// Ownership of the link will transfer to this program. pub fn attach_to_link( &mut self, link: SchedClassifierLink, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); match link.into_inner() { TcLinkInner::FdLink(link) => { let fd = link.fd; let link_fd = fd.as_fd(); bpf_link_update(link_fd.as_fd(), prog_fd, None, 0).map_err(|(_, io_error)| { SyscallError { call: "bpf_link_update", io_error, } })?; self.data .links .insert(SchedClassifierLink::new(TcLinkInner::FdLink(FdLink::new( fd, )))) } TcLinkInner::NlLink(NlLink { if_index, attach_type, priority, handle, }) => self.do_attach( if_index, attach_type, TcAttachOptions::Netlink(NlOptions { priority, handle }), false, ), } } fn do_attach( &mut self, if_index: u32, attach_type: TcAttachType, options: TcAttachOptions, create: bool, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); match options { TcAttachOptions::Netlink(options) => { let name = self.data.name.as_deref().unwrap_or_default(); // TODO: avoid this unwrap by adding a new error variant. let name = CString::new(name).unwrap(); let (priority, handle) = unsafe { netlink_qdisc_attach( if_index as i32, &attach_type, prog_fd, &name, options.priority, options.handle, create, ) } .map_err(|io_error| TcError::NetlinkError { io_error })?; self.data .links .insert(SchedClassifierLink::new(TcLinkInner::NlLink(NlLink { if_index, attach_type, priority, handle, }))) } TcAttachOptions::TcxOrder(options) => { let link_fd = bpf_link_create( prog_fd, LinkTarget::IfIndex(if_index), attach_type.tcx_attach_type()?, None, options.flags.bits(), Some(&options.link_ref), ) .map_err(|(_, io_error)| SyscallError { call: "bpf_mprog_attach", io_error, })?; self.data .links .insert(SchedClassifierLink::new(TcLinkInner::FdLink(FdLink::new( link_fd, )))) } } } /// Detaches the program. /// /// See [SchedClassifier::attach]. pub fn detach(&mut self, link_id: SchedClassifierLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link( &mut self, link_id: SchedClassifierLinkId, ) -> Result { self.data.take_link(link_id) } /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data }) } /// Queries a given interface for attached TCX programs. /// /// # Example /// /// ```no_run /// # use aya::programs::tc::{TcAttachType, SchedClassifier}; /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # } /// let (revision, programs) = SchedClassifier::query_tcx("eth0", TcAttachType::Ingress)?; /// # Ok::<(), Error>(()) /// ``` pub fn query_tcx( interface: &str, attach_type: TcAttachType, ) -> Result<(u64, Vec), ProgramError> { let if_index = ifindex_from_ifname(interface) .map_err(|io_error| TcError::NetlinkError { io_error })?; let (revision, prog_ids) = query( ProgQueryTarget::IfIndex(if_index), attach_type.tcx_attach_type()?, 0, &mut None, )?; let prog_infos = prog_ids .into_iter() .map(|prog_id| { let prog_fd = bpf_prog_get_fd_by_id(prog_id)?; let prog_info = ProgramInfo::new_from_fd(prog_fd.as_fd())?; Ok::(prog_info) }) .collect::>()?; Ok((revision, prog_infos)) } } #[derive(Debug, Hash, Eq, PartialEq)] pub(crate) struct NlLinkId(u32, TcAttachType, u16, u32); #[derive(Debug)] pub(crate) struct NlLink { if_index: u32, attach_type: TcAttachType, priority: u16, handle: u32, } impl Link for NlLink { type Id = NlLinkId; fn id(&self) -> Self::Id { NlLinkId(self.if_index, self.attach_type, self.priority, self.handle) } fn detach(self) -> Result<(), ProgramError> { unsafe { netlink_qdisc_detach( self.if_index as i32, &self.attach_type, self.priority, self.handle, ) } .map_err(|io_error| TcError::NetlinkError { io_error })?; Ok(()) } } #[derive(Debug, Hash, Eq, PartialEq)] pub(crate) enum TcLinkIdInner { FdLinkId(::Id), NlLinkId(::Id), } #[derive(Debug)] pub(crate) enum TcLinkInner { FdLink(FdLink), NlLink(NlLink), } impl Link for TcLinkInner { type Id = TcLinkIdInner; fn id(&self) -> Self::Id { match self { Self::FdLink(link) => TcLinkIdInner::FdLinkId(link.id()), Self::NlLink(link) => TcLinkIdInner::NlLinkId(link.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::FdLink(link) => link.detach(), Self::NlLink(link) => link.detach(), } } } impl<'a> TryFrom<&'a SchedClassifierLink> for &'a FdLink { type Error = LinkError; fn try_from(value: &'a SchedClassifierLink) -> Result { if let TcLinkInner::FdLink(fd) = value.inner() { Ok(fd) } else { Err(LinkError::InvalidLink) } } } impl TryFrom for FdLink { type Error = LinkError; fn try_from(value: SchedClassifierLink) -> Result { if let TcLinkInner::FdLink(fd) = value.into_inner() { Ok(fd) } else { Err(LinkError::InvalidLink) } } } impl TryFrom for SchedClassifierLink { type Error = LinkError; fn try_from(fd_link: FdLink) -> Result { let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?; if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TCX as u32) { return Ok(Self::new(TcLinkInner::FdLink(fd_link))); } Err(LinkError::InvalidLink) } } define_link_wrapper!( /// The link used by [SchedClassifier] programs. SchedClassifierLink, /// The type returned by [SchedClassifier::attach]. Can be passed to [SchedClassifier::detach]. SchedClassifierLinkId, TcLinkInner, TcLinkIdInner ); impl SchedClassifierLink { /// Constructs a [`SchedClassifierLink`] where the `if_name`, `attach_type`, /// `priority` and `handle` are already known. This may have been found from a link created by /// [SchedClassifier::attach], the output of the `tc filter` command or from the output of /// another BPF loader. /// /// Note: If you create a link for a program that you do not own, detaching it may have /// unintended consequences. /// /// # Errors /// Returns [`io::Error`] if `if_name` is invalid. If the other parameters are invalid this call /// will succeed, but calling [`SchedClassifierLink::detach`] will return [`TcError::NetlinkError`]. /// /// # Examples /// ```no_run /// # use aya::programs::tc::SchedClassifierLink; /// # use aya::programs::TcAttachType; /// # #[derive(Debug, thiserror::Error)] /// # enum Error { /// # #[error(transparent)] /// # IO(#[from] std::io::Error), /// # } /// # fn read_persisted_link_details() -> (&'static str, TcAttachType, u16, u32) { /// # ("eth0", TcAttachType::Ingress, 50, 1) /// # } /// // Get the link parameters from some external source. Where and how the parameters are /// // persisted is up to your application. /// let (if_name, attach_type, priority, handle) = read_persisted_link_details(); /// let new_tc_link = SchedClassifierLink::attached(if_name, attach_type, priority, handle)?; /// /// # Ok::<(), Error>(()) /// ``` pub fn attached( if_name: &str, attach_type: TcAttachType, priority: u16, handle: u32, ) -> Result { let if_index = ifindex_from_ifname(if_name)?; Ok(Self(Some(TcLinkInner::NlLink(NlLink { if_index, attach_type, priority, handle, })))) } /// Returns the attach type. pub fn attach_type(&self) -> Result { if let TcLinkInner::NlLink(n) = self.inner() { Ok(n.attach_type) } else { Err(TcError::InvalidLinkOperation.into()) } } /// Returns the allocated priority. If none was provided at attach time, this was allocated for you. pub fn priority(&self) -> Result { if let TcLinkInner::NlLink(n) = self.inner() { Ok(n.priority) } else { Err(TcError::InvalidLinkOperation.into()) } } /// Returns the assigned handle. If none was provided at attach time, this was allocated for you. pub fn handle(&self) -> Result { if let TcLinkInner::NlLink(n) = self.inner() { Ok(n.handle) } else { Err(TcError::InvalidLinkOperation.into()) } } } /// Add the `clasct` qdisc to the given interface. /// /// The `clsact` qdisc must be added to an interface before [`SchedClassifier`] /// programs can be attached. pub fn qdisc_add_clsact(if_name: &str) -> Result<(), io::Error> { let if_index = ifindex_from_ifname(if_name)?; unsafe { netlink_qdisc_add_clsact(if_index as i32) } } /// Detaches the programs with the given name. /// /// # Errors /// /// Returns [`io::ErrorKind::NotFound`] to indicate that no programs with the /// given name were found, so nothing was detached. Other error kinds indicate /// an actual failure while detaching a program. pub fn qdisc_detach_program( if_name: &str, attach_type: TcAttachType, name: &str, ) -> Result<(), io::Error> { let cstr = CString::new(name)?; qdisc_detach_program_fast(if_name, attach_type, &cstr) } /// Detaches the programs with the given name as a C string. /// Unlike qdisc_detach_program, this function does not allocate an additional /// CString to. /// /// # Errors /// /// Returns [`io::ErrorKind::NotFound`] to indicate that no programs with the /// given name were found, so nothing was detached. Other error kinds indicate /// an actual failure while detaching a program. fn qdisc_detach_program_fast( if_name: &str, attach_type: TcAttachType, name: &CStr, ) -> Result<(), io::Error> { let if_index = ifindex_from_ifname(if_name)? as i32; let filter_info = unsafe { netlink_find_filter_with_name(if_index, attach_type, name)? }; if filter_info.is_empty() { return Err(io::Error::new( io::ErrorKind::NotFound, name.to_string_lossy(), )); } for (prio, handle) in filter_info { unsafe { netlink_qdisc_detach(if_index, &attach_type, prio, handle)? }; } Ok(()) } aya-0.13.1/src/programs/tp_btf.rs000064400000000000000000000065401046102023000147320ustar 00000000000000//! BTF-enabled raw tracepoints. use crate::{ generated::{bpf_attach_type::BPF_TRACE_RAW_TP, bpf_prog_type::BPF_PROG_TYPE_TRACING}, obj::btf::{Btf, BtfKind}, programs::{ define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId, ProgramData, ProgramError, }, }; /// Marks a function as a [BTF-enabled raw tracepoint][1] eBPF program that can be attached at /// a pre-defined kernel trace point. /// /// The kernel provides a set of pre-defined trace points that eBPF programs can /// be attached to. See `/sys/kernel/debug/tracing/events` for a list of which /// events can be traced. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 5.5. /// /// # Examples /// /// ```no_run /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] /// # BtfError(#[from] aya::BtfError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), /// # #[error(transparent)] /// # Ebpf(#[from] aya::EbpfError), /// # } /// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?; /// use aya::{Ebpf, programs::BtfTracePoint, BtfError, Btf}; /// /// let btf = Btf::from_sys_fs()?; /// let program: &mut BtfTracePoint = bpf.program_mut("sched_process_fork").unwrap().try_into()?; /// program.load("sched_process_fork", &btf)?; /// program.attach()?; /// # Ok::<(), Error>(()) /// ``` /// /// [1]: https://github.com/torvalds/linux/commit/9e15db66136a14cde3f35691f1d839d950118826 #[derive(Debug)] #[doc(alias = "BPF_TRACE_RAW_TP")] #[doc(alias = "BPF_PROG_TYPE_TRACING")] pub struct BtfTracePoint { pub(crate) data: ProgramData, } impl BtfTracePoint { /// Loads the program inside the kernel. /// /// # Arguments /// /// * `tracepoint` - full name of the tracepoint that we should attach to /// * `btf` - btf information for the target system pub fn load(&mut self, tracepoint: &str, btf: &Btf) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(BPF_TRACE_RAW_TP); let type_name = format!("btf_trace_{tracepoint}"); self.data.attach_btf_id = Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Typedef)?); load_program(BPF_PROG_TYPE_TRACING, &mut self.data) } /// Attaches the program. /// /// The returned value can be used to detach, see [BtfTracePoint::detach]. pub fn attach(&mut self) -> Result { attach_raw_tracepoint(&mut self.data, None) } /// Detaches the program. /// /// See [BtfTracePoint::attach]. pub fn detach(&mut self, link_id: BtfTracePointLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link( &mut self, link_id: BtfTracePointLinkId, ) -> Result { self.data.take_link(link_id) } } define_link_wrapper!( /// The link used by [BtfTracePoint] programs. BtfTracePointLink, /// The type returned by [BtfTracePoint::attach]. Can be passed to [BtfTracePoint::detach]. BtfTracePointLinkId, FdLink, FdLinkId ); aya-0.13.1/src/programs/trace_point.rs000064400000000000000000000113551046102023000157630ustar 00000000000000//! Tracepoint programs. use std::{fs, io, os::fd::AsFd as _, path::Path}; use thiserror::Error; use crate::{ generated::{bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT}, programs::{ define_link_wrapper, load_program, perf_attach::{perf_attach, PerfLinkIdInner, PerfLinkInner}, utils::find_tracefs_path, FdLink, LinkError, ProgramData, ProgramError, }, sys::{bpf_link_get_info_by_fd, perf_event_open_trace_point, SyscallError}, }; /// The type returned when attaching a [`TracePoint`] fails. #[derive(Debug, Error)] pub enum TracePointError { /// Error detaching from debugfs #[error("`{filename}`")] FileError { /// The file name filename: String, /// The [`io::Error`] returned from the file operation #[source] io_error: io::Error, }, } /// A program that can be attached at a pre-defined kernel trace point. /// /// The kernel provides a set of pre-defined trace points that eBPF programs can /// be attached to. See `/sys/kernel/debug/tracing/events` for a list of which /// events can be traced. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.7. /// /// # Examples /// /// ```no_run /// # let mut bpf = aya::Ebpf::load(&[])?; /// use aya::programs::TracePoint; /// /// let prog: &mut TracePoint = bpf.program_mut("trace_context_switch").unwrap().try_into()?; /// prog.load()?; /// prog.attach("sched", "sched_switch")?; /// # Ok::<(), aya::EbpfError>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_TRACEPOINT")] pub struct TracePoint { pub(crate) data: ProgramData, } impl TracePoint { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_TRACEPOINT, &mut self.data) } /// Attaches to a given trace point. /// /// For a list of the available event categories and names, see /// `/sys/kernel/debug/tracing/events`. /// /// The returned value can be used to detach, see [TracePoint::detach]. pub fn attach(&mut self, category: &str, name: &str) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); let tracefs = find_tracefs_path()?; let id = read_sys_fs_trace_point_id(tracefs, category, name.as_ref())?; let fd = perf_event_open_trace_point(id, None).map_err(|(_code, io_error)| SyscallError { call: "perf_event_open_trace_point", io_error, })?; let link = perf_attach(prog_fd, fd)?; self.data.links.insert(TracePointLink::new(link)) } /// Detaches from a trace point. /// /// See [TracePoint::attach]. pub fn detach(&mut self, link_id: TracePointLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: TracePointLinkId) -> Result { self.data.take_link(link_id) } } define_link_wrapper!( /// The link used by [TracePoint] programs. TracePointLink, /// The type returned by [TracePoint::attach]. Can be passed to [TracePoint::detach]. TracePointLinkId, PerfLinkInner, PerfLinkIdInner ); impl TryFrom for FdLink { type Error = LinkError; fn try_from(value: TracePointLink) -> Result { if let PerfLinkInner::FdLink(fd) = value.into_inner() { Ok(fd) } else { Err(LinkError::InvalidLink) } } } impl TryFrom for TracePointLink { type Error = LinkError; fn try_from(fd_link: FdLink) -> Result { let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?; if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) { return Ok(Self::new(PerfLinkInner::FdLink(fd_link))); } Err(LinkError::InvalidLink) } } pub(crate) fn read_sys_fs_trace_point_id( tracefs: &Path, category: &str, name: &Path, ) -> Result { let file = tracefs.join("events").join(category).join(name).join("id"); let id = fs::read_to_string(&file).map_err(|io_error| TracePointError::FileError { filename: file.display().to_string(), io_error, })?; let id = id .trim() .parse::() .map_err(|error| TracePointError::FileError { filename: file.display().to_string(), io_error: io::Error::new(io::ErrorKind::Other, error), })?; Ok(id) } aya-0.13.1/src/programs/uprobe.rs000064400000000000000000000564701046102023000147570ustar 00000000000000//! User space probes. use std::{ borrow::Cow, error::Error, ffi::{CStr, OsStr, OsString}, fs, io::{self, BufRead, Cursor, Read}, mem, os::{fd::AsFd as _, raw::c_char, unix::ffi::OsStrExt}, path::{Path, PathBuf}, sync::LazyLock, }; use libc::pid_t; use object::{Object, ObjectSection, ObjectSymbol, Symbol}; use thiserror::Error; use crate::{ generated::{bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_KPROBE}, programs::{ define_link_wrapper, load_program, perf_attach::{PerfLinkIdInner, PerfLinkInner}, probe::{attach, OsStringExt as _, ProbeKind}, FdLink, LinkError, ProgramData, ProgramError, }, sys::bpf_link_get_info_by_fd, VerifierLogLevel, }; const LD_SO_CACHE_FILE: &str = "/etc/ld.so.cache"; static LD_SO_CACHE: LazyLock> = LazyLock::new(|| LdSoCache::load(LD_SO_CACHE_FILE)); const LD_SO_CACHE_HEADER_OLD: &str = "ld.so-1.7.0\0"; const LD_SO_CACHE_HEADER_NEW: &str = "glibc-ld.so.cache1.1"; /// An user space probe. /// /// User probes are eBPF programs that can be attached to any userspace /// function. They can be of two kinds: /// /// - `uprobe`: get attached to the *start* of the target functions /// - `uretprobe`: get attached to the *return address* of the target functions #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_KPROBE")] pub struct UProbe { pub(crate) data: ProgramData, pub(crate) kind: ProbeKind, } impl UProbe { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_KPROBE, &mut self.data) } /// Returns `UProbe` if the program is a `uprobe`, or `URetProbe` if the /// program is a `uretprobe`. pub fn kind(&self) -> ProbeKind { self.kind } /// Attaches the program. /// /// Attaches the uprobe to the function `fn_name` defined in the `target`. /// If `offset` is non-zero, it is added to the address of the target /// function. If `pid` is not `None`, the program executes only when the target /// function is executed by the given `pid`. /// /// The `target` argument can be an absolute path to a binary or library, or /// a library name (eg: `"libc"`). /// /// If the program is an `uprobe`, it is attached to the *start* address of the target /// function. Instead if the program is a `uretprobe`, it is attached to the return address of /// the target function. /// /// The returned value can be used to detach, see [UProbe::detach]. pub fn attach>( &mut self, fn_name: Option<&str>, offset: u64, target: T, pid: Option, ) -> Result { let path = resolve_attach_path(target.as_ref(), pid)?; let sym_offset = if let Some(fn_name) = fn_name { resolve_symbol(&path, fn_name).map_err(|error| UProbeError::SymbolError { symbol: fn_name.to_string(), error: Box::new(error), })? } else { 0 }; let path = path.as_os_str(); attach(&mut self.data, self.kind, path, sym_offset + offset, pid) } /// Detaches the program. /// /// See [UProbe::attach]. pub fn detach(&mut self, link_id: UProbeLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: UProbeLinkId) -> Result { self.data.take_link(link_id) } /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P, kind: ProbeKind) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; Ok(Self { data, kind }) } } fn resolve_attach_path(target: &Path, pid: Option) -> Result, UProbeError> { // Look up the path for the target. If it there is a pid, and the target is a library name // that is in the process's memory map, use the path of that library. Otherwise, use the target as-is. pid.and_then(|pid| { find_lib_in_proc_maps(pid, target) .map_err(|io_error| UProbeError::FileError { filename: Path::new("/proc").join(pid.to_string()).join("maps"), io_error, }) .map(|v| v.map(Cow::Owned)) .transpose() }) .or_else(|| target.is_absolute().then(|| Ok(Cow::Borrowed(target)))) .or_else(|| { LD_SO_CACHE .as_ref() .map_err(|io_error| UProbeError::InvalidLdSoCache { io_error }) .map(|cache| cache.resolve(target).map(Cow::Borrowed)) .transpose() }) .unwrap_or_else(|| { Err(UProbeError::InvalidTarget { path: target.to_owned(), }) }) } // Only run this test on linux with glibc because only in that configuration do we know that we'll // be dynamically linked to libc and can exercise resolving the path to libc via the current // process's memory map. #[test] #[cfg_attr( any(miri, not(all(target_os = "linux", target_env = "gnu"))), ignore = "requires glibc, doesn't work in miri" )] fn test_resolve_attach_path() { // Look up the current process's pid. let pid = std::process::id().try_into().unwrap(); // Now let's resolve the path to libc. It should exist in the current process's memory map and // then in the ld.so.cache. let libc_path = resolve_attach_path("libc".as_ref(), Some(pid)).unwrap(); let libc_path = libc_path.to_str().unwrap(); // Make sure we got a path that contains libc. assert!(libc_path.contains("libc"), "libc_path: {}", libc_path); } define_link_wrapper!( /// The link used by [UProbe] programs. UProbeLink, /// The type returned by [UProbe::attach]. Can be passed to [UProbe::detach]. UProbeLinkId, PerfLinkInner, PerfLinkIdInner ); impl TryFrom for FdLink { type Error = LinkError; fn try_from(value: UProbeLink) -> Result { if let PerfLinkInner::FdLink(fd) = value.into_inner() { Ok(fd) } else { Err(LinkError::InvalidLink) } } } impl TryFrom for UProbeLink { type Error = LinkError; fn try_from(fd_link: FdLink) -> Result { let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?; if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) { return Ok(Self::new(PerfLinkInner::FdLink(fd_link))); } Err(LinkError::InvalidLink) } } /// The type returned when attaching an [`UProbe`] fails. #[derive(Debug, Error)] pub enum UProbeError { /// There was an error parsing `/etc/ld.so.cache`. #[error("error reading `{}` file", LD_SO_CACHE_FILE)] InvalidLdSoCache { /// the original [`io::Error`] #[source] io_error: &'static io::Error, }, /// The target program could not be found. #[error("could not resolve uprobe target `{path}`")] InvalidTarget { /// path to target path: PathBuf, }, /// There was an error resolving the target symbol. #[error("error resolving symbol")] SymbolError { /// symbol name symbol: String, /// the original error #[source] error: Box, }, /// There was an error accessing `filename`. #[error("`{filename}`")] FileError { /// The file name filename: PathBuf, /// The [`io::Error`] returned from the file operation #[source] io_error: io::Error, }, } fn proc_maps_libs(pid: pid_t) -> Result, io::Error> { use std::os::unix::ffi::OsStrExt as _; let maps_file = format!("/proc/{pid}/maps"); let data = fs::read(maps_file)?; let libs = data .split(|b| b == &b'\n') .filter_map(|mut line| { while let [stripped @ .., c] = line { if c.is_ascii_whitespace() { line = stripped; continue; } break; } let path = line.split(|b| b.is_ascii_whitespace()).last()?; let path = Path::new(OsStr::from_bytes(path)); path.is_absolute() .then(|| { path.file_name() .map(|file_name| (file_name.to_owned(), path.to_owned())) }) .flatten() }) .collect(); Ok(libs) } fn find_lib_in_proc_maps(pid: pid_t, lib: &Path) -> Result, io::Error> { let libs = proc_maps_libs(pid)?; let lib = lib.as_os_str(); let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(lib); Ok(libs.into_iter().find_map(|(file_name, path)| { file_name.strip_prefix(lib).and_then(|suffix| { (suffix.starts_with(OsStr::new(".so")) || suffix.starts_with(OsStr::new("-"))) .then_some(path) }) })) } #[derive(Debug)] pub(crate) struct CacheEntry { key: OsString, value: OsString, _flags: i32, } #[derive(Debug)] pub(crate) struct LdSoCache { entries: Vec, } impl LdSoCache { fn load>(path: T) -> Result { let data = fs::read(path)?; Self::parse(&data) } fn parse(data: &[u8]) -> Result { let mut cursor = Cursor::new(data); let read_u32 = |cursor: &mut Cursor<_>| -> Result { let mut buf = [0u8; mem::size_of::()]; cursor.read_exact(&mut buf)?; Ok(u32::from_ne_bytes(buf)) }; let read_i32 = |cursor: &mut Cursor<_>| -> Result { let mut buf = [0u8; mem::size_of::()]; cursor.read_exact(&mut buf)?; Ok(i32::from_ne_bytes(buf)) }; // Check for new format let mut buf = [0u8; LD_SO_CACHE_HEADER_NEW.len()]; cursor.read_exact(&mut buf)?; let header = std::str::from_utf8(&buf).map_err(|_| { io::Error::new(io::ErrorKind::InvalidData, "invalid ld.so.cache header") })?; let new_format = header == LD_SO_CACHE_HEADER_NEW; // Check for old format if !new_format { cursor.set_position(0); let mut buf = [0u8; LD_SO_CACHE_HEADER_OLD.len()]; cursor.read_exact(&mut buf)?; let header = std::str::from_utf8(&buf).map_err(|_| { io::Error::new(io::ErrorKind::InvalidData, "invalid ld.so.cache header") })?; if header != LD_SO_CACHE_HEADER_OLD { return Err(io::Error::new( io::ErrorKind::InvalidData, "invalid ld.so.cache header", )); } } let num_entries = read_u32(&mut cursor)?; if new_format { cursor.consume(6 * mem::size_of::()); } let offset = if !new_format { cursor.position() as usize + num_entries as usize * 12 } else { 0 }; let entries = (0..num_entries) .map(|_: u32| { let flags = read_i32(&mut cursor)?; let k_pos = read_u32(&mut cursor)? as usize; let v_pos = read_u32(&mut cursor)? as usize; if new_format { cursor.consume(12); } let read_str = |pos| { use std::os::unix::ffi::OsStrExt as _; OsStr::from_bytes( unsafe { CStr::from_ptr( cursor.get_ref()[offset + pos..].as_ptr() as *const c_char ) } .to_bytes(), ) .to_owned() }; let key = read_str(k_pos); let value = read_str(v_pos); Ok::<_, io::Error>(CacheEntry { key, value, _flags: flags, }) }) .collect::>()?; Ok(Self { entries }) } fn resolve(&self, lib: &Path) -> Option<&Path> { let lib = lib.as_os_str(); let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(lib); self.entries .iter() .find_map(|CacheEntry { key, value, _flags }| { key.strip_prefix(lib).and_then(|suffix| { suffix .starts_with(OsStr::new(".so")) .then_some(Path::new(value.as_os_str())) }) }) } } #[derive(Error, Debug)] enum ResolveSymbolError { #[error(transparent)] Io(#[from] io::Error), #[error("error parsing ELF")] Object(#[from] object::Error), #[error("unknown symbol `{0}`")] Unknown(String), #[error("symbol `{0}` does not appear in section")] NotInSection(String), #[error("symbol `{0}` in section `{1:?}` which has no offset")] SectionFileRangeNone(String, Result), #[error("failed to access debuglink file `{0}`: `{1}`")] DebuglinkAccessError(String, io::Error), #[error("symbol `{0}` not found, mismatched build IDs in main and debug files")] BuildIdMismatch(String), } fn construct_debuglink_path( filename: &[u8], main_path: &Path, ) -> Result { let filename_str = OsStr::from_bytes(filename); let debuglink_path = Path::new(filename_str); let resolved_path = if debuglink_path.is_relative() { // If the debug path is relative, resolve it against the parent of the main path main_path.parent().map_or_else( || PathBuf::from(debuglink_path), // Use original if no parent |parent| parent.join(debuglink_path), ) } else { // If the path is not relative, just use original PathBuf::from(debuglink_path) }; Ok(resolved_path) } fn verify_build_ids<'a>( main_obj: &'a object::File<'a>, debug_obj: &'a object::File<'a>, symbol_name: &str, ) -> Result<(), ResolveSymbolError> { let main_build_id = main_obj.build_id().ok().flatten(); let debug_build_id = debug_obj.build_id().ok().flatten(); match (debug_build_id, main_build_id) { (Some(debug_build_id), Some(main_build_id)) => { // Only perform a comparison if both build IDs are present if debug_build_id != main_build_id { return Err(ResolveSymbolError::BuildIdMismatch(symbol_name.to_owned())); } Ok(()) } _ => Ok(()), } } fn find_debug_path_in_object<'a>( obj: &'a object::File<'a>, main_path: &Path, symbol: &str, ) -> Result { match obj.gnu_debuglink() { Ok(Some((filename, _))) => construct_debuglink_path(filename, main_path), Ok(None) => Err(ResolveSymbolError::Unknown(symbol.to_string())), Err(err) => Err(ResolveSymbolError::Object(err)), } } fn find_symbol_in_object<'a>(obj: &'a object::File<'a>, symbol: &str) -> Option> { obj.dynamic_symbols() .chain(obj.symbols()) .find(|sym| sym.name().map(|name| name == symbol).unwrap_or(false)) } fn resolve_symbol(path: &Path, symbol: &str) -> Result { let data = fs::read(path)?; let obj = object::read::File::parse(&*data)?; let mut debug_data = Vec::default(); let mut debug_obj_keeper = None; let sym = find_symbol_in_object(&obj, symbol).map_or_else( || { // Only search in the debug object if the symbol was not found in the main object let debug_path = find_debug_path_in_object(&obj, path, symbol)?; debug_data = fs::read(&debug_path).map_err(|e| { ResolveSymbolError::DebuglinkAccessError( debug_path .to_str() .unwrap_or("Debuglink path missing") .to_string(), e, ) })?; let debug_obj = object::read::File::parse(&*debug_data)?; verify_build_ids(&obj, &debug_obj, symbol)?; debug_obj_keeper = Some(debug_obj); find_symbol_in_object(debug_obj_keeper.as_ref().unwrap(), symbol) .ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string())) }, Ok, )?; let needs_addr_translation = matches!( obj.kind(), object::ObjectKind::Dynamic | object::ObjectKind::Executable ); if !needs_addr_translation { Ok(sym.address()) } else { let index = sym .section_index() .ok_or_else(|| ResolveSymbolError::NotInSection(symbol.to_string()))?; let section = obj.section_by_index(index)?; let (offset, _size) = section.file_range().ok_or_else(|| { ResolveSymbolError::SectionFileRangeNone( symbol.to_string(), section.name().map(str::to_owned), ) })?; Ok(sym.address() - section.address() + offset) } } #[cfg(test)] mod tests { use object::{write::SectionKind, Architecture, BinaryFormat, Endianness}; use super::*; #[test] fn test_relative_path_with_parent() { let filename = b"debug_info"; let main_path = Path::new("/usr/lib/main_binary"); let expected = Path::new("/usr/lib/debug_info"); let result = construct_debuglink_path(filename, main_path).unwrap(); assert_eq!( result, expected, "The debug path should resolve relative to the main path's parent" ); } #[test] fn test_relative_path_without_parent() { let filename = b"debug_info"; let main_path = Path::new("main_binary"); let expected = Path::new("debug_info"); let result = construct_debuglink_path(filename, main_path).unwrap(); assert_eq!( result, expected, "The debug path should be the original path as there is no parent" ); } #[test] fn test_absolute_path() { let filename = b"/absolute/path/to/debug_info"; let main_path = Path::new("/usr/lib/main_binary"); let expected = Path::new("/absolute/path/to/debug_info"); let result = construct_debuglink_path(filename, main_path).unwrap(); assert_eq!( result, expected, "The debug path should be the same as the input absolute path" ); } fn create_elf_with_debuglink( debug_filename: &[u8], crc: u32, ) -> Result, object::write::Error> { let mut obj = object::write::Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little); let section_name = b".gnu_debuglink"; let section_id = obj.add_section(vec![], section_name.to_vec(), SectionKind::Note); let mut debuglink_data = Vec::new(); debuglink_data.extend_from_slice(debug_filename); debuglink_data.push(0); // Null terminator while debuglink_data.len() % 4 != 0 { debuglink_data.push(0); } debuglink_data.extend(&crc.to_le_bytes()); obj.append_section_data(section_id, &debuglink_data, 4 /* align */); obj.write() } fn create_elf_with_build_id(build_id: &[u8]) -> Result, object::write::Error> { let mut obj = object::write::Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little); let section_name = b".note.gnu.build-id"; let section_id = obj.add_section(vec![], section_name.to_vec(), SectionKind::Note); let mut note_data = Vec::new(); let build_id_name = b"GNU"; note_data.extend(&(build_id_name.len() as u32 + 1).to_le_bytes()); note_data.extend(&(build_id.len() as u32).to_le_bytes()); note_data.extend(&3u32.to_le_bytes()); note_data.extend_from_slice(build_id_name); note_data.push(0); // Null terminator note_data.extend_from_slice(build_id); obj.append_section_data(section_id, ¬e_data, 4 /* align */); obj.write() } fn aligned_slice(vec: &mut Vec) -> &mut [u8] { let alignment = 8; let original_size = vec.len(); let total_size = original_size + alignment - 1; if vec.capacity() < total_size { vec.reserve(total_size - vec.capacity()); } if vec.len() < total_size { vec.resize(total_size, 0); } let ptr = vec.as_ptr() as usize; let aligned_ptr = (ptr + alignment - 1) & !(alignment - 1); let offset = aligned_ptr - ptr; if offset > 0 { let tmp = vec.len(); vec.copy_within(0..tmp - offset, offset); } &mut vec[offset..offset + original_size] } #[test] fn test_find_debug_path_success() { let debug_filepath = b"main.debug"; let mut main_bytes = create_elf_with_debuglink(debug_filepath, 0x123 /* fake CRC */) .expect("got main_bytes"); let align_bytes = aligned_slice(&mut main_bytes); let main_obj = object::File::parse(&*align_bytes).expect("got main obj"); let main_path = Path::new("/path/to/main"); let result = find_debug_path_in_object(&main_obj, main_path, "symbol"); assert_eq!(result.unwrap(), Path::new("/path/to/main.debug")); } #[test] fn test_verify_build_ids_same() { let build_id = b"test_build_id"; let mut main_bytes = create_elf_with_build_id(build_id).expect("got main_bytes"); let align_bytes = aligned_slice(&mut main_bytes); let main_obj = object::File::parse(&*align_bytes).expect("got main obj"); let debug_build_id = b"test_build_id"; let mut debug_bytes = create_elf_with_build_id(debug_build_id).expect("got debug bytes"); let align_bytes = aligned_slice(&mut debug_bytes); let debug_obj = object::File::parse(&*align_bytes).expect("got debug obj"); assert!(verify_build_ids(&main_obj, &debug_obj, "symbol_name").is_ok()); } #[test] fn test_verify_build_ids_different() { let build_id = b"main_build_id"; let mut main_bytes = create_elf_with_build_id(build_id).expect("got main_bytes"); let align_bytes = aligned_slice(&mut main_bytes); let main_obj = object::File::parse(&*align_bytes).expect("got main obj"); let debug_build_id = b"debug_build_id"; let mut debug_bytes = create_elf_with_build_id(debug_build_id).expect("got debug bytes"); let align_bytes = aligned_slice(&mut debug_bytes); let debug_obj = object::File::parse(&*align_bytes).expect("got debug obj"); assert!(matches!( verify_build_ids(&main_obj, &debug_obj, "symbol_name"), Err(ResolveSymbolError::BuildIdMismatch(_)) )); } } aya-0.13.1/src/programs/utils.rs000064400000000000000000000062751046102023000146210ustar 00000000000000//! Common functions shared between multiple eBPF program types. use std::{ ffi::CStr, fs::File, io::{self, BufRead, BufReader}, os::fd::{AsFd as _, AsRawFd as _, BorrowedFd}, path::Path, sync::LazyLock, time::{Duration, SystemTime, UNIX_EPOCH}, }; use crate::{ programs::{FdLink, Link, ProgramData, ProgramError}, sys::{bpf_raw_tracepoint_open, SyscallError}, }; /// Attaches the program to a raw tracepoint. pub(crate) fn attach_raw_tracepoint>( program_data: &mut ProgramData, tp_name: Option<&CStr>, ) -> Result { let prog_fd = program_data.fd()?; let prog_fd = prog_fd.as_fd(); let pfd = bpf_raw_tracepoint_open(tp_name, prog_fd).map_err(|(_code, io_error)| SyscallError { call: "bpf_raw_tracepoint_open", io_error, })?; program_data.links.insert(FdLink::new(pfd).into()) } /// Find tracefs filesystem path. pub(crate) fn find_tracefs_path() -> Result<&'static Path, ProgramError> { static TRACE_FS: LazyLock> = LazyLock::new(|| { [ Path::new("/sys/kernel/tracing"), Path::new("/sys/kernel/debug/tracing"), ] .into_iter() .find(|&mount| { // Check that the mount point exists and is not empty // Documented here: (https://www.kernel.org/doc/Documentation/trace/ftrace.txt) // In some cases, tracefs will only mount at /sys/kernel/debug/tracing // but, the kernel will still create the directory /sys/kernel/tracing. // The user may be expected to manually mount the directory in order for it to // exist in /sys/kernel/tracing according to the documentation. mount.exists() && match mount.read_dir() { Ok(mut entries) => entries.next().is_some(), Err(io::Error { .. }) => false, } }) }); TRACE_FS .as_deref() .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "tracefs not found").into()) } /// The time at which the system is booted. pub(crate) fn boot_time() -> SystemTime { let get_time = |clock_id| { let mut time = unsafe { std::mem::zeroed::() }; assert_eq!( unsafe { libc::clock_gettime(clock_id, &mut time) }, 0, "clock_gettime({}, _)", clock_id ); let libc::timespec { tv_sec, tv_nsec } = time; Duration::new(tv_sec as u64, tv_nsec as u32) }; let since_boot = get_time(libc::CLOCK_BOOTTIME); let since_epoch = get_time(libc::CLOCK_REALTIME); UNIX_EPOCH + since_epoch - since_boot } /// Get the specified information from a file descriptor's fdinfo. pub(crate) fn get_fdinfo(fd: BorrowedFd<'_>, key: &str) -> Result { let info = File::open(format!("/proc/self/fdinfo/{}", fd.as_raw_fd()))?; let reader = BufReader::new(info); for line in reader.lines() { let line = line?; if !line.contains(key) { continue; } let (_key, val) = line.rsplit_once('\t').unwrap(); return Ok(val.parse().unwrap()); } Ok(0) } aya-0.13.1/src/programs/xdp.rs000064400000000000000000000270301046102023000142440ustar 00000000000000//! eXpress Data Path (XDP) programs. use std::{ ffi::CString, hash::Hash, io, os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, RawFd}, path::Path, }; use libc::if_nametoindex; use thiserror::Error; use crate::{ generated::{ bpf_link_type, bpf_prog_type, XDP_FLAGS_DRV_MODE, XDP_FLAGS_HW_MODE, XDP_FLAGS_REPLACE, XDP_FLAGS_SKB_MODE, XDP_FLAGS_UPDATE_IF_NOEXIST, }, obj::programs::XdpAttachType, programs::{ define_link_wrapper, load_program, FdLink, Link, LinkError, ProgramData, ProgramError, }, sys::{ bpf_link_create, bpf_link_get_info_by_fd, bpf_link_update, netlink_set_xdp_fd, LinkTarget, SyscallError, }, util::KernelVersion, VerifierLogLevel, }; /// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`. #[derive(Debug, Error)] pub enum XdpError { /// netlink error while attaching XDP program #[error("netlink error while attaching XDP program")] NetlinkError { /// the [`io::Error`] from the netlink call #[source] io_error: io::Error, }, } bitflags::bitflags! { /// Flags passed to [`Xdp::attach()`]. #[derive(Clone, Copy, Debug, Default)] pub struct XdpFlags: u32 { /// Skb mode. const SKB_MODE = XDP_FLAGS_SKB_MODE; /// Driver mode. const DRV_MODE = XDP_FLAGS_DRV_MODE; /// Hardware mode. const HW_MODE = XDP_FLAGS_HW_MODE; /// Replace a previously attached XDP program. const REPLACE = XDP_FLAGS_REPLACE; /// Only attach if there isn't another XDP program already attached. const UPDATE_IF_NOEXIST = XDP_FLAGS_UPDATE_IF_NOEXIST; } } /// An XDP program. /// /// eXpress Data Path (XDP) programs can be attached to the very early stages of network /// processing, where they can apply custom packet processing logic. When supported by the /// underlying network driver, XDP programs can execute directly on network cards, greatly /// reducing CPU load. /// /// # Minimum kernel version /// /// The minimum kernel version required to use this feature is 4.8. /// /// # Examples /// /// ```no_run /// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?; /// use aya::{Ebpf, programs::{Xdp, XdpFlags}}; /// /// let program: &mut Xdp = bpf.program_mut("intercept_packets").unwrap().try_into()?; /// program.attach("eth0", XdpFlags::default())?; /// # Ok::<(), aya::EbpfError>(()) /// ``` #[derive(Debug)] #[doc(alias = "BPF_PROG_TYPE_XDP")] pub struct Xdp { pub(crate) data: ProgramData, pub(crate) attach_type: XdpAttachType, } impl Xdp { /// Loads the program inside the kernel. pub fn load(&mut self) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(self.attach_type.into()); load_program(bpf_prog_type::BPF_PROG_TYPE_XDP, &mut self.data) } /// Attaches the program to the given `interface`. /// /// The returned value can be used to detach, see [Xdp::detach]. /// /// # Errors /// /// If the given `interface` does not exist /// [`ProgramError::UnknownInterface`] is returned. /// /// When attaching fails, [`ProgramError::SyscallError`] is returned for /// kernels `>= 5.9.0`, and instead /// [`XdpError::NetlinkError`] is returned for older /// kernels. pub fn attach(&mut self, interface: &str, flags: XdpFlags) -> Result { // TODO: avoid this unwrap by adding a new error variant. let c_interface = CString::new(interface).unwrap(); let if_index = unsafe { if_nametoindex(c_interface.as_ptr()) }; if if_index == 0 { return Err(ProgramError::UnknownInterface { name: interface.to_string(), }); } self.attach_to_if_index(if_index, flags) } /// Attaches the program to the given interface index. /// /// The returned value can be used to detach, see [Xdp::detach]. /// /// # Errors /// /// When attaching fails, [`ProgramError::SyscallError`] is returned for /// kernels `>= 5.9.0`, and instead /// [`XdpError::NetlinkError`] is returned for older /// kernels. pub fn attach_to_if_index( &mut self, if_index: u32, flags: XdpFlags, ) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); if KernelVersion::current().unwrap() >= KernelVersion::new(5, 9, 0) { // Unwrap safety: the function starts with `self.fd()?` that will succeed if and only // if the program has been loaded, i.e. there is an fd. We get one by: // - Using `Xdp::from_pin` that sets `expected_attach_type` // - Calling `Xdp::attach` that sets `expected_attach_type`, as geting an `Xdp` // instance through `Xdp:try_from(Program)` does not set any fd. // So, in all cases where we have an fd, we have an expected_attach_type. Thus, if we // reach this point, expected_attach_type is guaranteed to be Some(_). let attach_type = self.data.expected_attach_type.unwrap(); let link_fd = bpf_link_create( prog_fd, LinkTarget::IfIndex(if_index), attach_type, None, flags.bits(), None, ) .map_err(|(_, io_error)| SyscallError { call: "bpf_link_create", io_error, })?; self.data .links .insert(XdpLink::new(XdpLinkInner::FdLink(FdLink::new(link_fd)))) } else { let if_index = if_index as i32; unsafe { netlink_set_xdp_fd(if_index, Some(prog_fd), None, flags.bits()) } .map_err(|io_error| XdpError::NetlinkError { io_error })?; let prog_fd = prog_fd.as_raw_fd(); self.data .links .insert(XdpLink::new(XdpLinkInner::NlLink(NlLink { if_index, prog_fd, flags, }))) } } /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. /// /// On drop, any managed links are detached and the program is unloaded. This will not result in /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>( path: P, attach_type: XdpAttachType, ) -> Result { let mut data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; data.expected_attach_type = Some(attach_type.into()); Ok(Self { data, attach_type }) } /// Detaches the program. /// /// See [Xdp::attach]. pub fn detach(&mut self, link_id: XdpLinkId) -> Result<(), ProgramError> { self.data.links.remove(link_id) } /// Takes ownership of the link referenced by the provided link_id. /// /// The link will be detached on `Drop` and the caller is now responsible /// for managing its lifetime. pub fn take_link(&mut self, link_id: XdpLinkId) -> Result { self.data.take_link(link_id) } /// Atomically replaces the program referenced by the provided link. /// /// Ownership of the link will transfer to this program. pub fn attach_to_link(&mut self, link: XdpLink) -> Result { let prog_fd = self.fd()?; let prog_fd = prog_fd.as_fd(); match link.into_inner() { XdpLinkInner::FdLink(fd_link) => { let link_fd = fd_link.fd; bpf_link_update(link_fd.as_fd(), prog_fd, None, 0).map_err(|(_, io_error)| { SyscallError { call: "bpf_link_update", io_error, } })?; self.data .links .insert(XdpLink::new(XdpLinkInner::FdLink(FdLink::new(link_fd)))) } XdpLinkInner::NlLink(nl_link) => { let if_index = nl_link.if_index; let old_prog_fd = nl_link.prog_fd; // SAFETY: TODO(https://github.com/aya-rs/aya/issues/612): make this safe by not holding `RawFd`s. let old_prog_fd = unsafe { BorrowedFd::borrow_raw(old_prog_fd) }; let flags = nl_link.flags; let replace_flags = flags | XdpFlags::REPLACE; unsafe { netlink_set_xdp_fd( if_index, Some(prog_fd), Some(old_prog_fd), replace_flags.bits(), ) .map_err(|io_error| XdpError::NetlinkError { io_error })?; } let prog_fd = prog_fd.as_raw_fd(); self.data .links .insert(XdpLink::new(XdpLinkInner::NlLink(NlLink { if_index, prog_fd, flags, }))) } } } } #[derive(Debug)] pub(crate) struct NlLink { if_index: i32, prog_fd: RawFd, flags: XdpFlags, } impl Link for NlLink { type Id = (i32, RawFd); fn id(&self) -> Self::Id { (self.if_index, self.prog_fd) } fn detach(self) -> Result<(), ProgramError> { let flags = if KernelVersion::current().unwrap() >= KernelVersion::new(5, 7, 0) { self.flags.bits() | XDP_FLAGS_REPLACE } else { self.flags.bits() }; // SAFETY: TODO(https://github.com/aya-rs/aya/issues/612): make this safe by not holding `RawFd`s. let prog_fd = unsafe { BorrowedFd::borrow_raw(self.prog_fd) }; let _ = unsafe { netlink_set_xdp_fd(self.if_index, None, Some(prog_fd), flags) }; Ok(()) } } #[derive(Debug, Hash, Eq, PartialEq)] pub(crate) enum XdpLinkIdInner { FdLinkId(::Id), NlLinkId(::Id), } #[derive(Debug)] pub(crate) enum XdpLinkInner { FdLink(FdLink), NlLink(NlLink), } impl Link for XdpLinkInner { type Id = XdpLinkIdInner; fn id(&self) -> Self::Id { match self { Self::FdLink(link) => XdpLinkIdInner::FdLinkId(link.id()), Self::NlLink(link) => XdpLinkIdInner::NlLinkId(link.id()), } } fn detach(self) -> Result<(), ProgramError> { match self { Self::FdLink(link) => link.detach(), Self::NlLink(link) => link.detach(), } } } impl TryFrom for FdLink { type Error = LinkError; fn try_from(value: XdpLink) -> Result { if let XdpLinkInner::FdLink(fd) = value.into_inner() { Ok(fd) } else { Err(LinkError::InvalidLink) } } } impl TryFrom for XdpLink { type Error = LinkError; fn try_from(fd_link: FdLink) -> Result { // unwrap of fd_link.fd will not panic since it's only None when being dropped. let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?; if info.type_ == (bpf_link_type::BPF_LINK_TYPE_XDP as u32) { return Ok(Self::new(XdpLinkInner::FdLink(fd_link))); } Err(LinkError::InvalidLink) } } define_link_wrapper!( /// The link used by [Xdp] programs. XdpLink, /// The type returned by [Xdp::attach]. Can be passed to [Xdp::detach]. XdpLinkId, XdpLinkInner, XdpLinkIdInner ); aya-0.13.1/src/sys/bpf.rs000064400000000000000000001320411046102023000132030ustar 00000000000000use std::{ cmp, ffi::{c_char, CStr, CString}, io, iter, mem::{self, MaybeUninit}, os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, FromRawFd as _, RawFd}, slice, }; use assert_matches::assert_matches; use libc::{ENOENT, ENOSPC}; use obj::{ btf::{BtfEnum64, Enum64}, generated::bpf_stats_type, maps::{bpf_map_def, LegacyMap}, EbpfSectionKind, VerifierLog, }; use crate::{ generated::{ bpf_attach_type, bpf_attr, bpf_btf_info, bpf_cmd, bpf_insn, bpf_link_info, bpf_map_info, bpf_map_type, bpf_prog_info, bpf_prog_type, BPF_F_REPLACE, }, maps::{MapData, PerCpuValues}, obj::{ self, btf::{ BtfParam, BtfType, DataSec, DataSecEntry, DeclTag, Float, Func, FuncLinkage, FuncProto, FuncSecInfo, Int, IntEncoding, LineSecInfo, Ptr, TypeTag, Var, VarLinkage, }, copy_instructions, }, programs::links::LinkRef, sys::{syscall, SysResult, Syscall, SyscallError}, util::KernelVersion, Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, FEATURES, }; pub(crate) fn bpf_create_map( name: &CStr, def: &obj::Map, btf_fd: Option>, kernel_version: KernelVersion, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_1 }; u.map_type = def.map_type(); u.key_size = def.key_size(); u.value_size = def.value_size(); u.max_entries = def.max_entries(); u.map_flags = def.map_flags(); if let obj::Map::Btf(m) = def { use bpf_map_type::*; // Mimic https://github.com/libbpf/libbpf/issues/355 // Currently a bunch of (usually pretty specialized) BPF maps do not support // specifying BTF types for the key and value. match u.map_type.try_into() { Ok(BPF_MAP_TYPE_PERF_EVENT_ARRAY) | Ok(BPF_MAP_TYPE_CGROUP_ARRAY) | Ok(BPF_MAP_TYPE_STACK_TRACE) | Ok(BPF_MAP_TYPE_ARRAY_OF_MAPS) | Ok(BPF_MAP_TYPE_HASH_OF_MAPS) | Ok(BPF_MAP_TYPE_DEVMAP) | Ok(BPF_MAP_TYPE_DEVMAP_HASH) | Ok(BPF_MAP_TYPE_CPUMAP) | Ok(BPF_MAP_TYPE_XSKMAP) | Ok(BPF_MAP_TYPE_SOCKMAP) | Ok(BPF_MAP_TYPE_SOCKHASH) | Ok(BPF_MAP_TYPE_QUEUE) | Ok(BPF_MAP_TYPE_STACK) | Ok(BPF_MAP_TYPE_RINGBUF) => { u.btf_key_type_id = 0; u.btf_value_type_id = 0; u.btf_fd = 0; } _ => { u.btf_key_type_id = m.def.btf_key_type_id; u.btf_value_type_id = m.def.btf_value_type_id; u.btf_fd = btf_fd.map(|fd| fd.as_raw_fd()).unwrap_or_default() as u32; } } } // https://github.com/torvalds/linux/commit/ad5b177bd73f5107d97c36f56395c4281fb6f089 // The map name was added as a parameter in kernel 4.15+ so we skip adding it on // older kernels for compatibility if kernel_version >= KernelVersion::new(4, 15, 0) { // u.map_name is 16 bytes max and must be NULL terminated let name_len = cmp::min(name.to_bytes().len(), BPF_OBJ_NAME_LEN - 1); u.map_name[..name_len] .copy_from_slice(unsafe { slice::from_raw_parts(name.as_ptr(), name_len) }); } // SAFETY: BPF_MAP_CREATE returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_MAP_CREATE, &mut attr) } } pub(crate) fn bpf_pin_object(fd: BorrowedFd<'_>, path: &CStr) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_4 }; u.bpf_fd = fd.as_raw_fd() as u32; u.pathname = path.as_ptr() as u64; sys_bpf(bpf_cmd::BPF_OBJ_PIN, &mut attr) } /// Introduced in kernel v4.4. pub(crate) fn bpf_get_object(path: &CStr) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_4 }; u.pathname = path.as_ptr() as u64; // SAFETY: BPF_OBJ_GET returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_OBJ_GET, &mut attr) } } pub(crate) struct EbpfLoadProgramAttrs<'a> { pub(crate) name: Option, pub(crate) ty: bpf_prog_type, pub(crate) insns: &'a [bpf_insn], pub(crate) license: &'a CStr, pub(crate) kernel_version: u32, pub(crate) expected_attach_type: Option, pub(crate) prog_btf_fd: Option>, pub(crate) attach_btf_obj_fd: Option>, pub(crate) attach_btf_id: Option, pub(crate) attach_prog_fd: Option>, pub(crate) func_info_rec_size: usize, pub(crate) func_info: FuncSecInfo, pub(crate) line_info_rec_size: usize, pub(crate) line_info: LineSecInfo, pub(crate) flags: u32, } pub(crate) fn bpf_load_program( aya_attr: &EbpfLoadProgramAttrs<'_>, log_buf: &mut [u8], verifier_log_level: VerifierLogLevel, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; if let Some(prog_name) = &aya_attr.name { let mut name: [c_char; 16] = [0; 16]; let name_bytes = prog_name.to_bytes(); let len = cmp::min(name.len(), name_bytes.len()); name[..len].copy_from_slice(unsafe { slice::from_raw_parts(name_bytes.as_ptr() as *const c_char, len) }); u.prog_name = name; } u.prog_flags = aya_attr.flags; u.prog_type = aya_attr.ty as u32; if let Some(v) = aya_attr.expected_attach_type { u.expected_attach_type = v as u32; } u.insns = aya_attr.insns.as_ptr() as u64; u.insn_cnt = aya_attr.insns.len() as u32; u.license = aya_attr.license.as_ptr() as u64; u.kern_version = aya_attr.kernel_version; // these must be allocated here to ensure the slice outlives the pointer // so .as_ptr below won't point to garbage let line_info_buf = aya_attr.line_info.line_info_bytes(); let func_info_buf = aya_attr.func_info.func_info_bytes(); if let Some(btf_fd) = aya_attr.prog_btf_fd { u.prog_btf_fd = btf_fd.as_raw_fd() as u32; if aya_attr.line_info_rec_size > 0 { u.line_info = line_info_buf.as_ptr() as *const _ as u64; u.line_info_cnt = aya_attr.line_info.len() as u32; u.line_info_rec_size = aya_attr.line_info_rec_size as u32; } if aya_attr.func_info_rec_size > 0 { u.func_info = func_info_buf.as_ptr() as *const _ as u64; u.func_info_cnt = aya_attr.func_info.len() as u32; u.func_info_rec_size = aya_attr.func_info_rec_size as u32; } } if !log_buf.is_empty() { u.log_level = verifier_log_level.bits(); u.log_buf = log_buf.as_mut_ptr() as u64; u.log_size = log_buf.len() as u32; } if let Some(v) = aya_attr.attach_btf_obj_fd { u.__bindgen_anon_1.attach_btf_obj_fd = v.as_raw_fd() as _; } if let Some(v) = aya_attr.attach_prog_fd { u.__bindgen_anon_1.attach_prog_fd = v.as_raw_fd() as u32; } if let Some(v) = aya_attr.attach_btf_id { u.attach_btf_id = v; } bpf_prog_load(&mut attr) } fn lookup( fd: BorrowedFd<'_>, key: Option<&K>, flags: u64, cmd: bpf_cmd, ) -> SysResult> { let mut attr = unsafe { mem::zeroed::() }; let mut value = MaybeUninit::zeroed(); let u = unsafe { &mut attr.__bindgen_anon_2 }; u.map_fd = fd.as_raw_fd() as u32; if let Some(key) = key { u.key = key as *const _ as u64; } u.__bindgen_anon_1.value = &mut value as *mut _ as u64; u.flags = flags; match sys_bpf(cmd, &mut attr) { Ok(_) => Ok(Some(unsafe { value.assume_init() })), Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None), Err(e) => Err(e), } } pub(crate) fn bpf_map_lookup_elem( fd: BorrowedFd<'_>, key: &K, flags: u64, ) -> SysResult> { lookup(fd, Some(key), flags, bpf_cmd::BPF_MAP_LOOKUP_ELEM) } pub(crate) fn bpf_map_lookup_and_delete_elem( fd: BorrowedFd<'_>, key: Option<&K>, flags: u64, ) -> SysResult> { lookup(fd, key, flags, bpf_cmd::BPF_MAP_LOOKUP_AND_DELETE_ELEM) } pub(crate) fn bpf_map_lookup_elem_per_cpu( fd: BorrowedFd<'_>, key: &K, flags: u64, ) -> SysResult>> { let mut mem = PerCpuValues::::alloc_kernel_mem().map_err(|io_error| (-1, io_error))?; match bpf_map_lookup_elem_ptr(fd, Some(key), mem.as_mut_ptr(), flags) { Ok(_) => Ok(Some(unsafe { PerCpuValues::from_kernel_mem(mem) })), Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None), Err(e) => Err(e), } } pub(crate) fn bpf_map_lookup_elem_ptr( fd: BorrowedFd<'_>, key: Option<&K>, value: *mut V, flags: u64, ) -> SysResult> { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_2 }; u.map_fd = fd.as_raw_fd() as u32; if let Some(key) = key { u.key = key as *const _ as u64; } u.__bindgen_anon_1.value = value as u64; u.flags = flags; match sys_bpf(bpf_cmd::BPF_MAP_LOOKUP_ELEM, &mut attr) { Ok(_) => Ok(Some(())), Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None), Err(e) => Err(e), } } pub(crate) fn bpf_map_update_elem( fd: BorrowedFd<'_>, key: Option<&K>, value: &V, flags: u64, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_2 }; u.map_fd = fd.as_raw_fd() as u32; if let Some(key) = key { u.key = key as *const _ as u64; } u.__bindgen_anon_1.value = value as *const _ as u64; u.flags = flags; sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &mut attr) } pub(crate) fn bpf_map_push_elem( fd: BorrowedFd<'_>, value: &V, flags: u64, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_2 }; u.map_fd = fd.as_raw_fd() as u32; u.__bindgen_anon_1.value = value as *const _ as u64; u.flags = flags; sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &mut attr) } pub(crate) fn bpf_map_update_elem_ptr( fd: BorrowedFd<'_>, key: *const K, value: *mut V, flags: u64, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_2 }; u.map_fd = fd.as_raw_fd() as u32; u.key = key as u64; u.__bindgen_anon_1.value = value as u64; u.flags = flags; sys_bpf(bpf_cmd::BPF_MAP_UPDATE_ELEM, &mut attr) } pub(crate) fn bpf_map_update_elem_per_cpu( fd: BorrowedFd<'_>, key: &K, values: &PerCpuValues, flags: u64, ) -> SysResult { let mut mem = values.build_kernel_mem().map_err(|e| (-1, e))?; bpf_map_update_elem_ptr(fd, key, mem.as_mut_ptr(), flags) } pub(crate) fn bpf_map_delete_elem(fd: BorrowedFd<'_>, key: &K) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_2 }; u.map_fd = fd.as_raw_fd() as u32; u.key = key as *const _ as u64; sys_bpf(bpf_cmd::BPF_MAP_DELETE_ELEM, &mut attr) } pub(crate) fn bpf_map_get_next_key( fd: BorrowedFd<'_>, key: Option<&K>, ) -> SysResult> { let mut attr = unsafe { mem::zeroed::() }; let mut next_key = MaybeUninit::uninit(); let u = unsafe { &mut attr.__bindgen_anon_2 }; u.map_fd = fd.as_raw_fd() as u32; if let Some(key) = key { u.key = key as *const _ as u64; } u.__bindgen_anon_1.next_key = &mut next_key as *mut _ as u64; match sys_bpf(bpf_cmd::BPF_MAP_GET_NEXT_KEY, &mut attr) { Ok(_) => Ok(Some(unsafe { next_key.assume_init() })), Err((_, io_error)) if io_error.raw_os_error() == Some(ENOENT) => Ok(None), Err(e) => Err(e), } } // since kernel 5.2 pub(crate) fn bpf_map_freeze(fd: BorrowedFd<'_>) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_2 }; u.map_fd = fd.as_raw_fd() as u32; sys_bpf(bpf_cmd::BPF_MAP_FREEZE, &mut attr) } pub(crate) enum LinkTarget<'f> { Fd(BorrowedFd<'f>), IfIndex(u32), } // since kernel 5.7 pub(crate) fn bpf_link_create( prog_fd: BorrowedFd<'_>, target: LinkTarget<'_>, attach_type: bpf_attach_type, btf_id: Option, flags: u32, link_ref: Option<&LinkRef>, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; attr.link_create.__bindgen_anon_1.prog_fd = prog_fd.as_raw_fd() as u32; match target { LinkTarget::Fd(fd) => { attr.link_create.__bindgen_anon_2.target_fd = fd.as_raw_fd() as u32; } LinkTarget::IfIndex(ifindex) => { attr.link_create.__bindgen_anon_2.target_ifindex = ifindex; } }; attr.link_create.attach_type = attach_type as u32; if let Some(btf_id) = btf_id { attr.link_create.__bindgen_anon_3.target_btf_id = btf_id; } attr.link_create.flags = flags; // since kernel 6.6 match link_ref { Some(LinkRef::Fd(fd)) => { attr.link_create .__bindgen_anon_3 .tcx .__bindgen_anon_1 .relative_fd = fd.to_owned() as u32; } Some(LinkRef::Id(id)) => { attr.link_create .__bindgen_anon_3 .tcx .__bindgen_anon_1 .relative_id = id.to_owned(); } None => {} }; // SAFETY: BPF_LINK_CREATE returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_LINK_CREATE, &mut attr) } } // since kernel 5.7 pub(crate) fn bpf_link_update( link_fd: BorrowedFd<'_>, new_prog_fd: BorrowedFd<'_>, old_prog_fd: Option, flags: u32, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; attr.link_update.link_fd = link_fd.as_raw_fd() as u32; attr.link_update.__bindgen_anon_1.new_prog_fd = new_prog_fd.as_raw_fd() as u32; if let Some(fd) = old_prog_fd { attr.link_update.__bindgen_anon_2.old_prog_fd = fd as u32; attr.link_update.flags = flags | BPF_F_REPLACE; } else { attr.link_update.flags = flags; } sys_bpf(bpf_cmd::BPF_LINK_UPDATE, &mut attr) } pub(crate) fn bpf_prog_attach( prog_fd: BorrowedFd<'_>, target_fd: BorrowedFd<'_>, attach_type: bpf_attach_type, flags: u32, ) -> Result<(), SyscallError> { let mut attr = unsafe { mem::zeroed::() }; attr.__bindgen_anon_5.attach_bpf_fd = prog_fd.as_raw_fd() as u32; attr.__bindgen_anon_5.__bindgen_anon_1.target_fd = target_fd.as_raw_fd() as u32; attr.__bindgen_anon_5.attach_type = attach_type as u32; attr.__bindgen_anon_5.attach_flags = flags; let ret = sys_bpf(bpf_cmd::BPF_PROG_ATTACH, &mut attr).map_err(|(code, io_error)| { assert_eq!(code, -1); SyscallError { call: "bpf_prog_attach", io_error, } })?; assert_eq!(ret, 0); Ok(()) } pub(crate) fn bpf_prog_detach( prog_fd: BorrowedFd<'_>, target_fd: BorrowedFd<'_>, attach_type: bpf_attach_type, ) -> Result<(), SyscallError> { let mut attr = unsafe { mem::zeroed::() }; attr.__bindgen_anon_5.attach_bpf_fd = prog_fd.as_raw_fd() as u32; attr.__bindgen_anon_5.__bindgen_anon_1.target_fd = target_fd.as_raw_fd() as u32; attr.__bindgen_anon_5.attach_type = attach_type as u32; let ret = sys_bpf(bpf_cmd::BPF_PROG_DETACH, &mut attr).map_err(|(code, io_error)| { assert_eq!(code, -1); SyscallError { call: "bpf_prog_detach", io_error, } })?; assert_eq!(ret, 0); Ok(()) } #[derive(Debug)] pub(crate) enum ProgQueryTarget<'a> { Fd(BorrowedFd<'a>), IfIndex(u32), } pub(crate) fn bpf_prog_query( target: &ProgQueryTarget<'_>, attach_type: bpf_attach_type, query_flags: u32, attach_flags: Option<&mut u32>, prog_ids: &mut [u32], prog_cnt: &mut u32, revision: &mut u64, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; match target { ProgQueryTarget::Fd(fd) => { attr.query.__bindgen_anon_1.target_fd = fd.as_raw_fd() as u32; } ProgQueryTarget::IfIndex(ifindex) => { attr.query.__bindgen_anon_1.target_ifindex = *ifindex; } } attr.query.attach_type = attach_type as u32; attr.query.query_flags = query_flags; attr.query.__bindgen_anon_2.prog_cnt = prog_ids.len() as u32; attr.query.prog_ids = prog_ids.as_mut_ptr() as u64; let ret = sys_bpf(bpf_cmd::BPF_PROG_QUERY, &mut attr); *prog_cnt = unsafe { attr.query.__bindgen_anon_2.prog_cnt }; *revision = unsafe { attr.query.revision }; if let Some(attach_flags) = attach_flags { *attach_flags = unsafe { attr.query.attach_flags }; } ret } /// Introduced in kernel v4.13. pub(crate) fn bpf_prog_get_fd_by_id(prog_id: u32) -> Result { let mut attr = unsafe { mem::zeroed::() }; attr.__bindgen_anon_6.__bindgen_anon_1.prog_id = prog_id; // SAFETY: BPF_PROG_GET_FD_BY_ID returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_GET_FD_BY_ID, &mut attr) }.map_err(|(code, io_error)| { assert_eq!(code, -1); SyscallError { call: "bpf_prog_get_fd_by_id", io_error, } }) } /// Introduced in kernel v4.13. fn bpf_obj_get_info_by_fd( fd: BorrowedFd<'_>, init: F, ) -> Result { let mut attr = unsafe { mem::zeroed::() }; let mut info = unsafe { mem::zeroed() }; init(&mut info); attr.info.bpf_fd = fd.as_raw_fd() as u32; attr.info.info = &info as *const _ as u64; attr.info.info_len = mem::size_of_val(&info) as u32; match sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &mut attr) { Ok(code) => { assert_eq!(code, 0); Ok(info) } Err((code, io_error)) => { assert_eq!(code, -1); Err(SyscallError { call: "bpf_obj_get_info_by_fd", io_error, }) } } } /// Introduced in kernel v4.13. pub(crate) fn bpf_prog_get_info_by_fd( fd: BorrowedFd<'_>, map_ids: &mut [u32], ) -> Result { // An `E2BIG` error can occur on kernels below v4.15 when handing over a large struct where the // extra space is not all-zero bytes. bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| { if FEATURES.prog_info_map_ids() { info.nr_map_ids = map_ids.len() as _; info.map_ids = map_ids.as_mut_ptr() as _; } }) } /// Introduced in kernel v4.13. pub(crate) fn bpf_map_get_fd_by_id(map_id: u32) -> Result { let mut attr = unsafe { mem::zeroed::() }; attr.__bindgen_anon_6.__bindgen_anon_1.map_id = map_id; // SAFETY: BPF_MAP_GET_FD_BY_ID returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_MAP_GET_FD_BY_ID, &mut attr) }.map_err(|(code, io_error)| { assert_eq!(code, -1); SyscallError { call: "bpf_map_get_fd_by_id", io_error, } }) } pub(crate) fn bpf_map_get_info_by_fd(fd: BorrowedFd<'_>) -> Result { bpf_obj_get_info_by_fd(fd, |_| {}) } pub(crate) fn bpf_link_get_fd_by_id(link_id: u32) -> Result { let mut attr = unsafe { mem::zeroed::() }; attr.__bindgen_anon_6.__bindgen_anon_1.link_id = link_id; // SAFETY: BPF_LINK_GET_FD_BY_ID returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_LINK_GET_FD_BY_ID, &mut attr) }.map_err(|(code, io_error)| { assert_eq!(code, -1); SyscallError { call: "bpf_link_get_fd_by_id", io_error, } }) } pub(crate) fn bpf_link_get_info_by_fd(fd: BorrowedFd<'_>) -> Result { bpf_obj_get_info_by_fd(fd, |_| {}) } pub(crate) fn btf_obj_get_info_by_fd( fd: BorrowedFd<'_>, buf: &mut [u8], ) -> Result { bpf_obj_get_info_by_fd(fd, |info: &mut bpf_btf_info| { info.btf = buf.as_mut_ptr() as _; info.btf_size = buf.len() as _; }) } pub(crate) fn bpf_raw_tracepoint_open( name: Option<&CStr>, prog_fd: BorrowedFd<'_>, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; attr.raw_tracepoint.name = match name { Some(n) => n.as_ptr() as u64, None => 0, }; attr.raw_tracepoint.prog_fd = prog_fd.as_raw_fd() as u32; // SAFETY: BPF_RAW_TRACEPOINT_OPEN returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_RAW_TRACEPOINT_OPEN, &mut attr) } } pub(crate) fn bpf_load_btf( raw_btf: &[u8], log_buf: &mut [u8], verifier_log_level: VerifierLogLevel, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_7 }; u.btf = raw_btf.as_ptr() as *const _ as u64; u.btf_size = mem::size_of_val(raw_btf) as u32; if !log_buf.is_empty() { u.btf_log_level = verifier_log_level.bits(); u.btf_log_buf = log_buf.as_mut_ptr() as u64; u.btf_log_size = log_buf.len() as u32; } // SAFETY: `BPF_BTF_LOAD` returns a newly created fd. unsafe { fd_sys_bpf(bpf_cmd::BPF_BTF_LOAD, &mut attr) } } // SAFETY: only use for bpf_cmd that return a new file descriptor on success. unsafe fn fd_sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult { let fd = sys_bpf(cmd, attr)?; let fd = fd.try_into().map_err(|_| { ( fd, io::Error::new( io::ErrorKind::InvalidData, format!("{cmd:?}: invalid fd returned: {fd}"), ), ) })?; Ok(crate::MockableFd::from_raw_fd(fd)) } pub(crate) fn bpf_btf_get_fd_by_id(id: u32) -> Result { let mut attr = unsafe { mem::zeroed::() }; attr.__bindgen_anon_6.__bindgen_anon_1.btf_id = id; // SAFETY: BPF_BTF_GET_FD_BY_ID returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_BTF_GET_FD_BY_ID, &mut attr) }.map_err(|(code, io_error)| { assert_eq!(code, -1); SyscallError { call: "bpf_btf_get_fd_by_id", io_error, } }) } pub(crate) fn is_prog_name_supported() -> bool { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; let mut name: [c_char; 16] = [0; 16]; let cstring = CString::new("aya_name_check").unwrap(); let name_bytes = cstring.to_bytes(); let len = cmp::min(name.len(), name_bytes.len()); name[..len].copy_from_slice(unsafe { slice::from_raw_parts(name_bytes.as_ptr() as *const c_char, len) }); u.prog_name = name; // The fields conforming an encoded basic instruction are stored in the following order: // opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF. // opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF. // Multi-byte fields ('imm' and 'offset') are stored using endian order. // https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding let prog: &[u8] = &[ 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; let gpl = b"GPL\0"; u.license = gpl.as_ptr() as u64; let insns = copy_instructions(prog).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32; bpf_prog_load(&mut attr).is_ok() } /// Tests whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` is available. pub(crate) fn is_info_map_ids_supported() -> bool { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32; let prog: &[u8] = &[ 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; let insns = copy_instructions(prog).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; let gpl = b"GPL\0"; u.license = gpl.as_ptr() as u64; let prog_fd = match bpf_prog_load(&mut attr) { Ok(fd) => fd, Err(_) => return false, }; bpf_obj_get_info_by_fd(prog_fd.as_fd(), |info: &mut bpf_prog_info| { info.nr_map_ids = 1 }) .is_ok() } /// Tests whether `gpl_compatible` field in `bpf_prog_info` is available. pub(crate) fn is_info_gpl_compatible_supported() -> bool { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32; let prog: &[u8] = &[ 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; let insns = copy_instructions(prog).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; let gpl = b"GPL\0"; u.license = gpl.as_ptr() as u64; let prog_fd = match bpf_prog_load(&mut attr) { Ok(fd) => fd, Err(_) => return false, }; if let Ok::(info) = bpf_obj_get_info_by_fd(prog_fd.as_fd(), |_| {}) { return info.gpl_compatible() != 0; } false } pub(crate) fn is_probe_read_kernel_supported() -> bool { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; // The fields conforming an encoded basic instruction are stored in the following order: // opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF. // opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF. // Multi-byte fields ('imm' and 'offset') are stored using endian order. // https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding #[cfg(target_endian = "little")] let prog: &[u8] = &[ 0xbf, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r1 = r10 0x07, 0x01, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, // r1 -= 8 0xb7, 0x02, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // r2 = 8 0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r3 = 0 0x85, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, // call 113 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; #[cfg(target_endian = "big")] let prog: &[u8] = &[ 0xbf, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r1 = r10 0x07, 0x10, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf8, // r1 -= 8 0xb7, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // r2 = 8 0xb7, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // r3 = 0 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, // call 113 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; let gpl = b"GPL\0"; u.license = gpl.as_ptr() as u64; let insns = copy_instructions(prog).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32; bpf_prog_load(&mut attr).is_ok() } pub(crate) fn is_perf_link_supported() -> bool { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; // The fields conforming an encoded basic instruction are stored in the following order: // opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF. // opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF. // Multi-byte fields ('imm' and 'offset') are stored using endian order. // https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding let prog: &[u8] = &[ 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; let gpl = b"GPL\0"; u.license = gpl.as_ptr() as u64; let insns = copy_instructions(prog).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32; if let Ok(fd) = bpf_prog_load(&mut attr) { let fd = fd.as_fd(); matches!( // Uses an invalid target FD so we get EBADF if supported. bpf_link_create(fd, LinkTarget::IfIndex(u32::MAX), bpf_attach_type::BPF_PERF_EVENT, None, 0, None), // Returns EINVAL if unsupported. EBADF if supported. Err((_, e)) if e.raw_os_error() == Some(libc::EBADF), ) } else { false } } pub(crate) fn is_bpf_global_data_supported() -> bool { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; // The fields conforming an encoded basic instruction are stored in the following order: // opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF. // opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF. // Multi-byte fields ('imm' and 'offset') are stored using endian order. // https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding #[cfg(target_endian = "little")] let prog: &[u8] = &[ 0x18, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ld_pseudo r1, 0x2, 0x0 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // 0x7a, 0x01, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, // stdw [r1 + 0x0], 0x2a 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; #[cfg(target_endian = "big")] let prog: &[u8] = &[ 0x18, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // ld_pseudo r1, 0x2, 0x0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x7a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, // stdw [r1 + 0x0], 0x2a 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; let mut insns = copy_instructions(prog).unwrap(); let map = MapData::create( obj::Map::Legacy(LegacyMap { def: bpf_map_def { map_type: bpf_map_type::BPF_MAP_TYPE_ARRAY as u32, key_size: 4, value_size: 32, max_entries: 1, ..Default::default() }, section_index: 0, section_kind: EbpfSectionKind::Maps, symbol_index: None, data: Vec::new(), }), "aya_global", None, ); if let Ok(map) = map { insns[0].imm = map.fd().as_fd().as_raw_fd(); let gpl = b"GPL\0"; u.license = gpl.as_ptr() as u64; u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32; bpf_prog_load(&mut attr).is_ok() } else { false } } pub(crate) fn is_bpf_cookie_supported() -> bool { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; // The fields conforming an encoded basic instruction are stored in the following order: // opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF. // opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF. // Multi-byte fields ('imm' and 'offset') are stored using endian order. // https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding #[cfg(target_endian = "little")] let prog: &[u8] = &[ 0x85, 0x00, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, // call bpf_get_attach_cookie 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; #[cfg(target_endian = "big")] let prog: &[u8] = &[ 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, // call bpf_get_attach_cookie 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; let gpl = b"GPL\0"; u.license = gpl.as_ptr() as u64; let insns = copy_instructions(prog).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; u.prog_type = bpf_prog_type::BPF_PROG_TYPE_KPROBE as u32; bpf_prog_load(&mut attr).is_ok() } /// Tests whether CpuMap, DevMap and DevMapHash support program ids pub(crate) fn is_prog_id_supported(map_type: bpf_map_type) -> bool { assert_matches!( map_type, bpf_map_type::BPF_MAP_TYPE_CPUMAP | bpf_map_type::BPF_MAP_TYPE_DEVMAP | bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH ); let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_1 }; u.map_type = map_type as u32; u.key_size = 4; u.value_size = 8; // 4 for CPU ID, 8 for CPU ID + prog ID u.max_entries = 1; u.map_flags = 0; // SAFETY: BPF_MAP_CREATE returns a new file descriptor. let fd = unsafe { fd_sys_bpf(bpf_cmd::BPF_MAP_CREATE, &mut attr) }; fd.is_ok() } pub(crate) fn is_btf_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("int"); let int_type = BtfType::Int(Int::new(name_offset, 4, IntEncoding::Signed, 0)); btf.add_type(int_type); let btf_bytes = btf.to_bytes(); bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok() } pub(crate) fn is_btf_func_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("int"); let int_type = BtfType::Int(Int::new(name_offset, 4, IntEncoding::Signed, 0)); let int_type_id = btf.add_type(int_type); let a_name = btf.add_string("a"); let b_name = btf.add_string("b"); let params = vec![ BtfParam { name_offset: a_name, btf_type: int_type_id, }, BtfParam { name_offset: b_name, btf_type: int_type_id, }, ]; let func_proto = BtfType::FuncProto(FuncProto::new(params, int_type_id)); let func_proto_type_id = btf.add_type(func_proto); let add = btf.add_string("inc"); let func = BtfType::Func(Func::new(add, func_proto_type_id, FuncLinkage::Static)); btf.add_type(func); let btf_bytes = btf.to_bytes(); bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok() } pub(crate) fn is_btf_func_global_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("int"); let int_type = BtfType::Int(Int::new(name_offset, 4, IntEncoding::Signed, 0)); let int_type_id = btf.add_type(int_type); let a_name = btf.add_string("a"); let b_name = btf.add_string("b"); let params = vec![ BtfParam { name_offset: a_name, btf_type: int_type_id, }, BtfParam { name_offset: b_name, btf_type: int_type_id, }, ]; let func_proto = BtfType::FuncProto(FuncProto::new(params, int_type_id)); let func_proto_type_id = btf.add_type(func_proto); let add = btf.add_string("inc"); let func = BtfType::Func(Func::new(add, func_proto_type_id, FuncLinkage::Global)); btf.add_type(func); let btf_bytes = btf.to_bytes(); bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok() } pub(crate) fn is_btf_datasec_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("int"); let int_type = BtfType::Int(Int::new(name_offset, 4, IntEncoding::Signed, 0)); let int_type_id = btf.add_type(int_type); let name_offset = btf.add_string("foo"); let var_type = BtfType::Var(Var::new(name_offset, int_type_id, VarLinkage::Static)); let var_type_id = btf.add_type(var_type); let name_offset = btf.add_string(".data"); let variables = vec![DataSecEntry { btf_type: var_type_id, offset: 0, size: 4, }]; let datasec_type = BtfType::DataSec(DataSec::new(name_offset, variables, 4)); btf.add_type(datasec_type); let btf_bytes = btf.to_bytes(); bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok() } pub(crate) fn is_btf_enum64_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("enum64"); let enum_64_type = BtfType::Enum64(Enum64::new( name_offset, true, vec![BtfEnum64::new(btf.add_string("a"), 1)], )); btf.add_type(enum_64_type); let btf_bytes = btf.to_bytes(); bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok() } pub(crate) fn is_btf_float_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("float"); let float_type = BtfType::Float(Float::new(name_offset, 16)); btf.add_type(float_type); let btf_bytes = btf.to_bytes(); bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok() } pub(crate) fn is_btf_decl_tag_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("int"); let int_type = BtfType::Int(Int::new(name_offset, 4, IntEncoding::Signed, 0)); let int_type_id = btf.add_type(int_type); let name_offset = btf.add_string("foo"); let var_type = BtfType::Var(Var::new(name_offset, int_type_id, VarLinkage::Static)); let var_type_id = btf.add_type(var_type); let name_offset = btf.add_string("decl_tag"); let decl_tag = BtfType::DeclTag(DeclTag::new(name_offset, var_type_id, -1)); btf.add_type(decl_tag); let btf_bytes = btf.to_bytes(); bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok() } pub(crate) fn is_btf_type_tag_supported() -> bool { let mut btf = Btf::new(); let int_type = BtfType::Int(Int::new(0, 4, IntEncoding::Signed, 0)); let int_type_id = btf.add_type(int_type); let name_offset = btf.add_string("int"); let type_tag = BtfType::TypeTag(TypeTag::new(name_offset, int_type_id)); let type_tag_type = btf.add_type(type_tag); btf.add_type(BtfType::Ptr(Ptr::new(0, type_tag_type))); let btf_bytes = btf.to_bytes(); bpf_load_btf(btf_bytes.as_slice(), &mut [], Default::default()).is_ok() } fn bpf_prog_load(attr: &mut bpf_attr) -> SysResult { // SAFETY: BPF_PROG_LOAD returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_LOAD, attr) } } fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult { syscall(Syscall::Ebpf { cmd, attr }) } fn bpf_obj_get_next_id( id: u32, cmd: bpf_cmd, name: &'static str, ) -> Result, SyscallError> { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_6 }; u.__bindgen_anon_1.start_id = id; match sys_bpf(cmd, &mut attr) { Ok(code) => { assert_eq!(code, 0); Ok(Some(unsafe { attr.__bindgen_anon_6.next_id })) } Err((code, io_error)) => { assert_eq!(code, -1); if io_error.raw_os_error() == Some(ENOENT) { Ok(None) } else { Err(SyscallError { call: name, io_error, }) } } } } fn iter_obj_ids( cmd: bpf_cmd, name: &'static str, ) -> impl Iterator> { let mut current_id = Some(0); iter::from_fn(move || { let next_id = { let current_id = current_id?; bpf_obj_get_next_id(current_id, cmd, name).transpose() }; current_id = next_id.as_ref().and_then(|next_id| match next_id { Ok(next_id) => Some(*next_id), Err(SyscallError { .. }) => None, }); next_id }) } /// Introduced in kernel v4.13. pub(crate) fn iter_prog_ids() -> impl Iterator> { iter_obj_ids(bpf_cmd::BPF_PROG_GET_NEXT_ID, "bpf_prog_get_next_id") } pub(crate) fn iter_link_ids() -> impl Iterator> { iter_obj_ids(bpf_cmd::BPF_LINK_GET_NEXT_ID, "bpf_link_get_next_id") } /// Introduced in kernel v4.13. pub(crate) fn iter_map_ids() -> impl Iterator> { iter_obj_ids(bpf_cmd::BPF_MAP_GET_NEXT_ID, "bpf_map_get_next_id") } /// Introduced in kernel v5.8. pub(crate) fn bpf_enable_stats( stats_type: bpf_stats_type, ) -> Result { let mut attr = unsafe { mem::zeroed::() }; attr.enable_stats.type_ = stats_type as u32; // SAFETY: BPF_ENABLE_STATS returns a new file descriptor. unsafe { fd_sys_bpf(bpf_cmd::BPF_ENABLE_STATS, &mut attr) }.map_err(|(_, io_error)| { SyscallError { call: "bpf_enable_stats", io_error, } }) } pub(crate) fn retry_with_verifier_logs( max_retries: usize, f: impl Fn(&mut [u8]) -> SysResult, ) -> (SysResult, VerifierLog) { const MIN_LOG_BUF_SIZE: usize = 1024 * 10; const MAX_LOG_BUF_SIZE: usize = (u32::MAX >> 8) as usize; let mut log_buf = Vec::new(); let mut retries = 0; loop { let ret = f(log_buf.as_mut_slice()); if retries != max_retries { if let Err((_, io_error)) = &ret { if retries == 0 || io_error.raw_os_error() == Some(ENOSPC) { let len = (log_buf.capacity() * 10).clamp(MIN_LOG_BUF_SIZE, MAX_LOG_BUF_SIZE); log_buf.resize(len, 0); if let Some(first) = log_buf.first_mut() { *first = 0; } retries += 1; continue; } } } if let Some(pos) = log_buf.iter().position(|b| *b == 0) { log_buf.truncate(pos); } let log_buf = String::from_utf8(log_buf).unwrap(); break (ret, VerifierLog::new(log_buf)); } } #[cfg(test)] mod tests { use libc::{EBADF, EINVAL}; use super::*; use crate::sys::override_syscall; #[test] fn test_attach_with_attributes() { const FAKE_FLAGS: u32 = 1234; const FAKE_FD: i32 = 4321; // Test attach flags propagated to system call. override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_PROG_ATTACH, attr, } => { assert_eq!(unsafe { attr.__bindgen_anon_5.attach_flags }, FAKE_FLAGS); Ok(0) } _ => Err((-1, io::Error::from_raw_os_error(EINVAL))), }); let prog_fd = unsafe { BorrowedFd::borrow_raw(FAKE_FD) }; let tgt_fd = unsafe { BorrowedFd::borrow_raw(FAKE_FD) }; let mut result = bpf_prog_attach( prog_fd, tgt_fd, bpf_attach_type::BPF_CGROUP_SOCK_OPS, FAKE_FLAGS, ); result.unwrap(); // Test with no flags. override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_PROG_ATTACH, attr, } => { assert_eq!(unsafe { attr.__bindgen_anon_5.attach_flags }, 0); Ok(0) } _ => Err((-1, io::Error::from_raw_os_error(EINVAL))), }); result = bpf_prog_attach(prog_fd, tgt_fd, bpf_attach_type::BPF_CGROUP_SOCK_OPS, 0); result.unwrap(); } #[test] fn test_perf_link_supported() { override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_LINK_CREATE, .. } => Err((-1, io::Error::from_raw_os_error(EBADF))), _ => Ok(crate::MockableFd::mock_signed_fd().into()), }); let supported = is_perf_link_supported(); assert!(supported); override_syscall(|call| match call { Syscall::Ebpf { cmd: bpf_cmd::BPF_LINK_CREATE, .. } => Err((-1, io::Error::from_raw_os_error(EINVAL))), _ => Ok(crate::MockableFd::mock_signed_fd().into()), }); let supported = is_perf_link_supported(); assert!(!supported); } #[test] fn test_prog_id_supported() { override_syscall(|_call| Ok(crate::MockableFd::mock_signed_fd().into())); // Ensure that the three map types we can check are accepted let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_CPUMAP); assert!(supported); let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_DEVMAP); assert!(supported); let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH); assert!(supported); override_syscall(|_call| Err((-1, io::Error::from_raw_os_error(EINVAL)))); let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_CPUMAP); assert!(!supported); } #[test] #[should_panic = "assertion failed: `BPF_MAP_TYPE_HASH` does not match `bpf_map_type::BPF_MAP_TYPE_CPUMAP | bpf_map_type::BPF_MAP_TYPE_DEVMAP | bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH`"] fn test_prog_id_supported_reject_types() { is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_HASH); } } aya-0.13.1/src/sys/fake.rs000064400000000000000000000012251046102023000133410ustar 00000000000000use std::{cell::RefCell, ffi::c_void, io, ptr}; use super::{SysResult, Syscall}; type SyscallFn = unsafe fn(Syscall<'_>) -> SysResult; #[cfg(test)] thread_local! { pub(crate) static TEST_SYSCALL: RefCell = RefCell::new(test_syscall); pub(crate) static TEST_MMAP_RET: RefCell<*mut c_void> = const { RefCell::new(ptr::null_mut()) }; } #[cfg(test)] unsafe fn test_syscall(_call: Syscall<'_>) -> SysResult { Err((-1, io::Error::from_raw_os_error(libc::EINVAL))) } #[cfg(test)] pub(crate) fn override_syscall(call: unsafe fn(Syscall<'_>) -> SysResult) { TEST_SYSCALL.with(|test_impl| *test_impl.borrow_mut() = call); } aya-0.13.1/src/sys/mod.rs000064400000000000000000000126021046102023000132130ustar 00000000000000//! A collection of system calls for performing eBPF related operations. mod bpf; mod netlink; mod perf_event; #[cfg(test)] mod fake; use std::{ ffi::{c_int, c_void}, io, mem, os::fd::{AsRawFd as _, BorrowedFd, OwnedFd}, }; pub(crate) use bpf::*; #[cfg(test)] pub(crate) use fake::*; use libc::{pid_t, SYS_bpf, SYS_perf_event_open}; #[doc(hidden)] pub use netlink::netlink_set_link_up; pub(crate) use netlink::*; pub(crate) use perf_event::*; use thiserror::Error; use crate::generated::{bpf_attr, bpf_cmd, perf_event_attr}; pub(crate) type SysResult = Result; pub(crate) enum Syscall<'a> { Ebpf { cmd: bpf_cmd, attr: &'a mut bpf_attr, }, PerfEventOpen { attr: perf_event_attr, pid: pid_t, cpu: i32, group: i32, flags: u32, }, PerfEventIoctl { fd: BorrowedFd<'a>, request: c_int, arg: c_int, }, } /// A system call error. #[derive(Debug, Error)] #[error("`{call}` failed")] pub struct SyscallError { /// The name of the syscall which failed. pub call: &'static str, /// The [`io::Error`] returned by the syscall. #[source] pub io_error: io::Error, } impl std::fmt::Debug for Syscall<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Ebpf { cmd, attr: _ } => f .debug_struct("Syscall::Ebpf") .field("cmd", cmd) .field("attr", &format_args!("_")) .finish(), Self::PerfEventOpen { attr: _, pid, cpu, group, flags, } => f .debug_struct("Syscall::PerfEventOpen") .field("attr", &format_args!("_")) .field("pid", pid) .field("cpu", cpu) .field("group", group) .field("flags", flags) .finish(), Self::PerfEventIoctl { fd, request, arg } => f .debug_struct("Syscall::PerfEventIoctl") .field("fd", fd) .field("request", request) .field("arg", arg) .finish(), } } } fn syscall(call: Syscall<'_>) -> SysResult { #[cfg(test)] return TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) }); #[cfg_attr(test, allow(unreachable_code))] { let ret = unsafe { match call { Syscall::Ebpf { cmd, attr } => { libc::syscall(SYS_bpf, cmd, attr, mem::size_of::()) } Syscall::PerfEventOpen { attr, pid, cpu, group, flags, } => libc::syscall(SYS_perf_event_open, &attr, pid, cpu, group, flags), Syscall::PerfEventIoctl { fd, request, arg } => { let ret = libc::ioctl(fd.as_raw_fd(), request.try_into().unwrap(), arg); // `libc::ioctl` returns i32 on x86_64 while `libc::syscall` returns i64. #[allow(clippy::useless_conversion)] ret.into() } } }; // `libc::syscall` returns i32 on armv7. #[allow(clippy::useless_conversion)] match ret.into() { ret @ 0.. => Ok(ret), ret => Err((ret, io::Error::last_os_error())), } } } #[cfg_attr(test, allow(unused_variables))] pub(crate) unsafe fn mmap( addr: *mut c_void, len: usize, prot: c_int, flags: c_int, fd: BorrowedFd<'_>, offset: libc::off_t, ) -> *mut c_void { #[cfg(not(test))] return libc::mmap(addr, len, prot, flags, fd.as_raw_fd(), offset); #[cfg(test)] TEST_MMAP_RET.with(|ret| *ret.borrow()) } /// The type of eBPF statistic to enable. #[non_exhaustive] #[doc(alias = "bpf_stats_type")] #[derive(Copy, Clone, Debug)] pub enum Stats { /// Tracks [`run_time`](crate::programs::ProgramInfo::run_time) and /// [`run_count`](crate::programs::ProgramInfo::run_count) fields. #[doc(alias = "BPF_STATS_RUN_TIME")] RunTime, } impl From for crate::generated::bpf_stats_type { fn from(value: Stats) -> Self { use crate::generated::bpf_stats_type::*; match value { Stats::RunTime => BPF_STATS_RUN_TIME, } } } /// Enable global statistics tracking for eBPF programs and returns a /// [file descriptor](`OwnedFd`) handler. /// /// Statistics tracking is disabled when the [file descriptor](`OwnedFd`) is /// dropped (either automatically when the variable goes out of scope or /// manually through [`Drop`]). /// /// Usage: /// 1. Obtain fd from [`enable_stats`] and bind it to a variable. /// 2. Record the statistic of interest. /// 3. Wait for a recorded period of time. /// 4. Record the statistic of interest again, and calculate the difference. /// 5. Close/release fd automatically or manually. /// /// Introduced in kernel v5.8. /// /// # Examples /// /// ```no_run /// # use aya::sys::{SyscallError}; /// use aya::sys::{enable_stats, Stats}; /// /// let _fd = enable_stats(Stats::RunTime)?; /// # Ok::<(), SyscallError>(()) /// ``` #[doc(alias = "BPF_ENABLE_STATS")] pub fn enable_stats(stats_type: Stats) -> Result { bpf_enable_stats(stats_type.into()).map(|fd| fd.into_inner()) } aya-0.13.1/src/sys/netlink.rs000064400000000000000000000606321046102023000141060ustar 00000000000000use std::{ collections::HashMap, ffi::CStr, io, mem, os::fd::{AsRawFd as _, BorrowedFd, FromRawFd as _}, ptr, slice, }; use libc::{ getsockname, nlattr, nlmsgerr, nlmsghdr, recv, send, setsockopt, sockaddr_nl, socket, AF_NETLINK, AF_UNSPEC, ETH_P_ALL, IFF_UP, IFLA_XDP, NETLINK_EXT_ACK, NETLINK_ROUTE, NLA_ALIGNTO, NLA_F_NESTED, NLA_TYPE_MASK, NLMSG_DONE, NLMSG_ERROR, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_ECHO, NLM_F_EXCL, NLM_F_MULTI, NLM_F_REQUEST, RTM_DELTFILTER, RTM_GETTFILTER, RTM_NEWQDISC, RTM_NEWTFILTER, RTM_SETLINK, SOCK_RAW, SOL_NETLINK, }; use thiserror::Error; use crate::{ generated::{ ifinfomsg, tcmsg, IFLA_XDP_EXPECTED_FD, IFLA_XDP_FD, IFLA_XDP_FLAGS, NLMSG_ALIGNTO, TCA_BPF_FD, TCA_BPF_FLAGS, TCA_BPF_FLAG_ACT_DIRECT, TCA_BPF_NAME, TCA_KIND, TCA_OPTIONS, TC_H_CLSACT, TC_H_INGRESS, TC_H_MAJ_MASK, TC_H_UNSPEC, XDP_FLAGS_REPLACE, }, programs::TcAttachType, util::tc_handler_make, }; const NLA_HDR_LEN: usize = align_to(mem::size_of::(), NLA_ALIGNTO as usize); // Safety: marking this as unsafe overall because of all the pointer math required to comply with // netlink alignments pub(crate) unsafe fn netlink_set_xdp_fd( if_index: i32, fd: Option>, old_fd: Option>, flags: u32, ) -> Result<(), io::Error> { let sock = NetlinkSocket::open()?; // Safety: Request is POD so this is safe let mut req = mem::zeroed::(); let nlmsg_len = mem::size_of::() + mem::size_of::(); req.header = nlmsghdr { nlmsg_len: nlmsg_len as u32, nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) as u16, nlmsg_type: RTM_SETLINK, nlmsg_pid: 0, nlmsg_seq: 1, }; req.if_info.ifi_family = AF_UNSPEC as u8; req.if_info.ifi_index = if_index; // write the attrs let attrs_buf = request_attributes(&mut req, nlmsg_len); let mut attrs = NestedAttrs::new(attrs_buf, IFLA_XDP); attrs.write_attr( IFLA_XDP_FD as u16, fd.map(|fd| fd.as_raw_fd()).unwrap_or(-1), )?; if flags > 0 { attrs.write_attr(IFLA_XDP_FLAGS as u16, flags)?; } if flags & XDP_FLAGS_REPLACE != 0 { attrs.write_attr( IFLA_XDP_EXPECTED_FD as u16, old_fd.map(|fd| fd.as_raw_fd()).unwrap(), )?; } let nla_len = attrs.finish()?; req.header.nlmsg_len += align_to(nla_len, NLA_ALIGNTO as usize) as u32; sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; sock.recv()?; Ok(()) } pub(crate) unsafe fn netlink_qdisc_add_clsact(if_index: i32) -> Result<(), io::Error> { let sock = NetlinkSocket::open()?; let mut req = mem::zeroed::(); let nlmsg_len = mem::size_of::() + mem::size_of::(); req.header = nlmsghdr { nlmsg_len: nlmsg_len as u32, nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE) as u16, nlmsg_type: RTM_NEWQDISC, nlmsg_pid: 0, nlmsg_seq: 1, }; req.tc_info.tcm_family = AF_UNSPEC as u8; req.tc_info.tcm_ifindex = if_index; req.tc_info.tcm_handle = tc_handler_make(TC_H_CLSACT, TC_H_UNSPEC); req.tc_info.tcm_parent = tc_handler_make(TC_H_CLSACT, TC_H_INGRESS); req.tc_info.tcm_info = 0; // add the TCA_KIND attribute let attrs_buf = request_attributes(&mut req, nlmsg_len); let attr_len = write_attr_bytes(attrs_buf, 0, TCA_KIND as u16, b"clsact\0")?; req.header.nlmsg_len += align_to(attr_len, NLA_ALIGNTO as usize) as u32; sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; sock.recv()?; Ok(()) } pub(crate) unsafe fn netlink_qdisc_attach( if_index: i32, attach_type: &TcAttachType, prog_fd: BorrowedFd<'_>, prog_name: &CStr, priority: u16, handle: u32, create: bool, ) -> Result<(u16, u32), io::Error> { let sock = NetlinkSocket::open()?; let mut req = mem::zeroed::(); let nlmsg_len = mem::size_of::() + mem::size_of::(); // When create=true, we're creating a new attachment so we must set NLM_F_CREATE. Then we also // set NLM_F_EXCL so that attaching fails if there's already a program attached to the given // handle. // // When create=false we're replacing an existing attachment so we must not set either flags. // // See https://github.com/torvalds/linux/blob/3a87498/net/sched/cls_api.c#L2304 let request_flags = if create { NLM_F_CREATE | NLM_F_EXCL } else { // NLM_F_REPLACE exists, but seems unused by cls_bpf 0 }; req.header = nlmsghdr { nlmsg_len: nlmsg_len as u32, nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK | NLM_F_ECHO | request_flags) as u16, nlmsg_type: RTM_NEWTFILTER, nlmsg_pid: 0, nlmsg_seq: 1, }; req.tc_info.tcm_family = AF_UNSPEC as u8; req.tc_info.tcm_handle = handle; // auto-assigned, if zero req.tc_info.tcm_ifindex = if_index; req.tc_info.tcm_parent = attach_type.tc_parent(); req.tc_info.tcm_info = tc_handler_make((priority as u32) << 16, htons(ETH_P_ALL as u16) as u32); let attrs_buf = request_attributes(&mut req, nlmsg_len); // add TCA_KIND let kind_len = write_attr_bytes(attrs_buf, 0, TCA_KIND as u16, b"bpf\0")?; // add TCA_OPTIONS which includes TCA_BPF_FD, TCA_BPF_NAME and TCA_BPF_FLAGS let mut options = NestedAttrs::new(&mut attrs_buf[kind_len..], TCA_OPTIONS as u16); options.write_attr(TCA_BPF_FD as u16, prog_fd)?; options.write_attr_bytes(TCA_BPF_NAME as u16, prog_name.to_bytes_with_nul())?; let flags: u32 = TCA_BPF_FLAG_ACT_DIRECT; options.write_attr(TCA_BPF_FLAGS as u16, flags)?; let options_len = options.finish()?; req.header.nlmsg_len += align_to(kind_len + options_len, NLA_ALIGNTO as usize) as u32; sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; // find the RTM_NEWTFILTER reply and read the tcm_info and tcm_handle fields // which we'll need to detach let tc_msg = match sock .recv()? .iter() .find(|reply| reply.header.nlmsg_type == RTM_NEWTFILTER) { Some(reply) => ptr::read_unaligned(reply.data.as_ptr() as *const tcmsg), None => { // if sock.recv() succeeds we should never get here unless there's a // bug in the kernel return Err(io::Error::new( io::ErrorKind::Other, "no RTM_NEWTFILTER reply received, this is a bug.", )); } }; let priority = ((tc_msg.tcm_info & TC_H_MAJ_MASK) >> 16) as u16; Ok((priority, tc_msg.tcm_handle)) } pub(crate) unsafe fn netlink_qdisc_detach( if_index: i32, attach_type: &TcAttachType, priority: u16, handle: u32, ) -> Result<(), io::Error> { let sock = NetlinkSocket::open()?; let mut req = mem::zeroed::(); req.header = nlmsghdr { nlmsg_len: (mem::size_of::() + mem::size_of::()) as u32, nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) as u16, nlmsg_type: RTM_DELTFILTER, nlmsg_pid: 0, nlmsg_seq: 1, }; req.tc_info.tcm_family = AF_UNSPEC as u8; req.tc_info.tcm_handle = handle; // auto-assigned, if zero req.tc_info.tcm_info = tc_handler_make((priority as u32) << 16, htons(ETH_P_ALL as u16) as u32); req.tc_info.tcm_parent = attach_type.tc_parent(); req.tc_info.tcm_ifindex = if_index; sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; sock.recv()?; Ok(()) } // Returns a vector of tuple (priority, handle) for filters matching the provided parameters pub(crate) unsafe fn netlink_find_filter_with_name( if_index: i32, attach_type: TcAttachType, name: &CStr, ) -> Result, io::Error> { let mut req = mem::zeroed::(); let nlmsg_len = mem::size_of::() + mem::size_of::(); req.header = nlmsghdr { nlmsg_len: nlmsg_len as u32, nlmsg_type: RTM_GETTFILTER, nlmsg_flags: (NLM_F_REQUEST | NLM_F_DUMP) as u16, nlmsg_pid: 0, nlmsg_seq: 1, }; req.tc_info.tcm_family = AF_UNSPEC as u8; req.tc_info.tcm_handle = 0; // auto-assigned, if zero req.tc_info.tcm_ifindex = if_index; req.tc_info.tcm_parent = attach_type.tc_parent(); let sock = NetlinkSocket::open()?; sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; let mut filter_info = Vec::new(); for msg in sock.recv()? { if msg.header.nlmsg_type != RTM_NEWTFILTER { continue; } let tc_msg = ptr::read_unaligned(msg.data.as_ptr() as *const tcmsg); let priority = (tc_msg.tcm_info >> 16) as u16; let attrs = parse_attrs(&msg.data[mem::size_of::()..])?; if let Some(opts) = attrs.get(&(TCA_OPTIONS as u16)) { let opts = parse_attrs(opts.data)?; if let Some(f_name) = opts.get(&(TCA_BPF_NAME as u16)) { if let Ok(f_name) = CStr::from_bytes_with_nul(f_name.data) { if name == f_name { filter_info.push((priority, tc_msg.tcm_handle)); } } } } } Ok(filter_info) } #[doc(hidden)] pub unsafe fn netlink_set_link_up(if_index: i32) -> Result<(), io::Error> { let sock = NetlinkSocket::open()?; // Safety: Request is POD so this is safe let mut req = mem::zeroed::(); let nlmsg_len = mem::size_of::() + mem::size_of::(); req.header = nlmsghdr { nlmsg_len: nlmsg_len as u32, nlmsg_flags: (NLM_F_REQUEST | NLM_F_ACK) as u16, nlmsg_type: RTM_SETLINK, nlmsg_pid: 0, nlmsg_seq: 1, }; req.if_info.ifi_family = AF_UNSPEC as u8; req.if_info.ifi_index = if_index; req.if_info.ifi_flags = IFF_UP as u32; req.if_info.ifi_change = IFF_UP as u32; sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?; sock.recv()?; Ok(()) } #[repr(C)] struct Request { header: nlmsghdr, if_info: ifinfomsg, attrs: [u8; 64], } #[repr(C)] struct TcRequest { header: nlmsghdr, tc_info: tcmsg, attrs: [u8; 64], } struct NetlinkSocket { sock: crate::MockableFd, _nl_pid: u32, } impl NetlinkSocket { fn open() -> Result { // Safety: libc wrapper let sock = unsafe { socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) }; if sock < 0 { return Err(io::Error::last_os_error()); } // SAFETY: `socket` returns a file descriptor. let sock = unsafe { crate::MockableFd::from_raw_fd(sock) }; let enable = 1i32; // Safety: libc wrapper unsafe { setsockopt( sock.as_raw_fd(), SOL_NETLINK, NETLINK_EXT_ACK, &enable as *const _ as *const _, mem::size_of::() as u32, ) }; // Safety: sockaddr_nl is POD so this is safe let mut addr = unsafe { mem::zeroed::() }; addr.nl_family = AF_NETLINK as u16; let mut addr_len = mem::size_of::() as u32; // Safety: libc wrapper if unsafe { getsockname( sock.as_raw_fd(), &mut addr as *mut _ as *mut _, &mut addr_len as *mut _, ) } < 0 { return Err(io::Error::last_os_error()); } Ok(Self { sock, _nl_pid: addr.nl_pid, }) } fn send(&self, msg: &[u8]) -> Result<(), io::Error> { if unsafe { send( self.sock.as_raw_fd(), msg.as_ptr() as *const _, msg.len(), 0, ) } < 0 { return Err(io::Error::last_os_error()); } Ok(()) } fn recv(&self) -> Result, io::Error> { let mut buf = [0u8; 4096]; let mut messages = Vec::new(); let mut multipart = true; 'out: while multipart { multipart = false; // Safety: libc wrapper let len = unsafe { recv( self.sock.as_raw_fd(), buf.as_mut_ptr() as *mut _, buf.len(), 0, ) }; if len < 0 { return Err(io::Error::last_os_error()); } if len == 0 { break; } let len = len as usize; let mut offset = 0; while offset < len { let message = NetlinkMessage::read(&buf[offset..])?; offset += align_to(message.header.nlmsg_len as usize, NLMSG_ALIGNTO as usize); multipart = message.header.nlmsg_flags & NLM_F_MULTI as u16 != 0; match message.header.nlmsg_type as i32 { NLMSG_ERROR => { let err = message.error.unwrap(); if err.error == 0 { // this is an ACK continue; } return Err(io::Error::from_raw_os_error(-err.error)); } NLMSG_DONE => break 'out, _ => messages.push(message), } } } Ok(messages) } } struct NetlinkMessage { header: nlmsghdr, data: Vec, error: Option, } impl NetlinkMessage { fn read(buf: &[u8]) -> Result { if mem::size_of::() > buf.len() { return Err(io::Error::new( io::ErrorKind::Other, "buffer smaller than nlmsghdr", )); } // Safety: nlmsghdr is POD so read is safe let header = unsafe { ptr::read_unaligned(buf.as_ptr() as *const nlmsghdr) }; let msg_len = header.nlmsg_len as usize; if msg_len < mem::size_of::() || msg_len > buf.len() { return Err(io::Error::new(io::ErrorKind::Other, "invalid nlmsg_len")); } let data_offset = align_to(mem::size_of::(), NLMSG_ALIGNTO as usize); if data_offset >= buf.len() { return Err(io::Error::new(io::ErrorKind::Other, "need more data")); } let (data, error) = if header.nlmsg_type == NLMSG_ERROR as u16 { if data_offset + mem::size_of::() > buf.len() { return Err(io::Error::new( io::ErrorKind::Other, "NLMSG_ERROR but not enough space for nlmsgerr", )); } ( Vec::new(), // Safety: nlmsgerr is POD so read is safe Some(unsafe { ptr::read_unaligned(buf[data_offset..].as_ptr() as *const nlmsgerr) }), ) } else { (buf[data_offset..msg_len].to_vec(), None) }; Ok(Self { header, data, error, }) } } const fn align_to(v: usize, align: usize) -> usize { (v + (align - 1)) & !(align - 1) } fn htons(u: u16) -> u16 { u.to_be() } struct NestedAttrs<'a> { buf: &'a mut [u8], top_attr_type: u16, offset: usize, } impl<'a> NestedAttrs<'a> { fn new(buf: &'a mut [u8], top_attr_type: u16) -> Self { Self { buf, top_attr_type, offset: NLA_HDR_LEN, } } fn write_attr(&mut self, attr_type: u16, value: T) -> Result { let size = write_attr(self.buf, self.offset, attr_type, value)?; self.offset += size; Ok(size) } fn write_attr_bytes(&mut self, attr_type: u16, value: &[u8]) -> Result { let size = write_attr_bytes(self.buf, self.offset, attr_type, value)?; self.offset += size; Ok(size) } fn finish(self) -> Result { let nla_len = self.offset; let attr = nlattr { nla_type: NLA_F_NESTED as u16 | self.top_attr_type, nla_len: nla_len as u16, }; write_attr_header(self.buf, 0, attr)?; Ok(nla_len) } } fn write_attr( buf: &mut [u8], offset: usize, attr_type: u16, value: T, ) -> Result { let value = unsafe { slice::from_raw_parts(&value as *const _ as *const _, mem::size_of::()) }; write_attr_bytes(buf, offset, attr_type, value) } fn write_attr_bytes( buf: &mut [u8], offset: usize, attr_type: u16, value: &[u8], ) -> Result { let attr = nlattr { nla_type: attr_type, nla_len: ((NLA_HDR_LEN + value.len()) as u16), }; write_attr_header(buf, offset, attr)?; let value_len = write_bytes(buf, offset + NLA_HDR_LEN, value)?; Ok(NLA_HDR_LEN + value_len) } fn write_attr_header(buf: &mut [u8], offset: usize, attr: nlattr) -> Result { let attr = unsafe { slice::from_raw_parts(&attr as *const _ as *const _, mem::size_of::()) }; write_bytes(buf, offset, attr)?; Ok(NLA_HDR_LEN) } fn write_bytes(buf: &mut [u8], offset: usize, value: &[u8]) -> Result { let align_len = align_to(value.len(), NLA_ALIGNTO as usize); if offset + align_len > buf.len() { return Err(io::Error::new(io::ErrorKind::Other, "no space left")); } buf[offset..offset + value.len()].copy_from_slice(value); Ok(align_len) } struct NlAttrsIterator<'a> { attrs: &'a [u8], offset: usize, } impl<'a> NlAttrsIterator<'a> { fn new(attrs: &'a [u8]) -> Self { Self { attrs, offset: 0 } } } impl<'a> Iterator for NlAttrsIterator<'a> { type Item = Result, NlAttrError>; fn next(&mut self) -> Option { let buf = &self.attrs[self.offset..]; if buf.is_empty() { return None; } if NLA_HDR_LEN > buf.len() { self.offset = buf.len(); return Some(Err(NlAttrError::InvalidBufferLength { size: buf.len(), expected: NLA_HDR_LEN, })); } let attr = unsafe { ptr::read_unaligned(buf.as_ptr() as *const nlattr) }; let len = attr.nla_len as usize; let align_len = align_to(len, NLA_ALIGNTO as usize); if len < NLA_HDR_LEN { return Some(Err(NlAttrError::InvalidHeaderLength(len))); } if align_len > buf.len() { return Some(Err(NlAttrError::InvalidBufferLength { size: buf.len(), expected: align_len, })); } let data = &buf[NLA_HDR_LEN..len]; self.offset += align_len; Some(Ok(NlAttr { header: attr, data })) } } fn parse_attrs(buf: &[u8]) -> Result>, NlAttrError> { let mut attrs = HashMap::new(); for attr in NlAttrsIterator::new(buf) { let attr = attr?; attrs.insert(attr.header.nla_type & NLA_TYPE_MASK as u16, attr); } Ok(attrs) } #[derive(Clone)] struct NlAttr<'a> { header: nlattr, data: &'a [u8], } #[derive(Debug, Error, PartialEq, Eq)] enum NlAttrError { #[error("invalid buffer size `{size}`, expected `{expected}`")] InvalidBufferLength { size: usize, expected: usize }, #[error("invalid nlattr header length `{0}`")] InvalidHeaderLength(usize), } impl From for io::Error { fn from(e: NlAttrError) -> Self { Self::new(io::ErrorKind::Other, e) } } unsafe fn request_attributes(req: &mut T, msg_len: usize) -> &mut [u8] { let attrs_addr = align_to(req as *mut _ as usize + msg_len, NLMSG_ALIGNTO as usize); let attrs_end = req as *mut _ as usize + mem::size_of::(); slice::from_raw_parts_mut(attrs_addr as *mut u8, attrs_end - attrs_addr) } fn bytes_of(val: &T) -> &[u8] { let size = mem::size_of::(); unsafe { slice::from_raw_parts(slice::from_ref(val).as_ptr().cast(), size) } } #[cfg(test)] mod tests { use std::ffi::CString; use super::*; #[test] fn test_nested_attrs() { let mut buf = [0; 64]; // write IFLA_XDP with 2 nested attrs let mut attrs = NestedAttrs::new(&mut buf, IFLA_XDP); attrs.write_attr(IFLA_XDP_FD as u16, 42u32).unwrap(); attrs .write_attr(IFLA_XDP_EXPECTED_FD as u16, 24u32) .unwrap(); let len = attrs.finish().unwrap() as u16; // 3 nlattr headers (IFLA_XDP, IFLA_XDP_FD and IFLA_XDP_EXPECTED_FD) + the fd let nla_len = (NLA_HDR_LEN * 3 + mem::size_of::() * 2) as u16; assert_eq!(len, nla_len); // read IFLA_XDP let attr = unsafe { ptr::read_unaligned(buf.as_ptr() as *const nlattr) }; assert_eq!(attr.nla_type, NLA_F_NESTED as u16 | IFLA_XDP); assert_eq!(attr.nla_len, nla_len); // read IFLA_XDP_FD + fd let attr = unsafe { ptr::read_unaligned(buf[NLA_HDR_LEN..].as_ptr() as *const nlattr) }; assert_eq!(attr.nla_type, IFLA_XDP_FD as u16); assert_eq!(attr.nla_len, (NLA_HDR_LEN + mem::size_of::()) as u16); let fd = unsafe { ptr::read_unaligned(buf[NLA_HDR_LEN * 2..].as_ptr() as *const u32) }; assert_eq!(fd, 42); // read IFLA_XDP_EXPECTED_FD + fd let attr = unsafe { ptr::read_unaligned( buf[NLA_HDR_LEN * 2 + mem::size_of::()..].as_ptr() as *const nlattr ) }; assert_eq!(attr.nla_type, IFLA_XDP_EXPECTED_FD as u16); assert_eq!(attr.nla_len, (NLA_HDR_LEN + mem::size_of::()) as u16); let fd = unsafe { ptr::read_unaligned( buf[NLA_HDR_LEN * 3 + mem::size_of::()..].as_ptr() as *const u32 ) }; assert_eq!(fd, 24); } #[test] fn test_nlattr_iterator_empty() { let mut iter = NlAttrsIterator::new(&[]); assert!(iter.next().is_none()); } #[test] fn test_nlattr_iterator_one() { let mut buf = [0; NLA_HDR_LEN + mem::size_of::()]; write_attr(&mut buf, 0, IFLA_XDP_FD as u16, 42u32).unwrap(); let mut iter = NlAttrsIterator::new(&buf); let attr = iter.next().unwrap().unwrap(); assert_eq!(attr.header.nla_type, IFLA_XDP_FD as u16); assert_eq!(attr.data.len(), mem::size_of::()); assert_eq!(u32::from_ne_bytes(attr.data.try_into().unwrap()), 42); assert!(iter.next().is_none()); } #[test] fn test_nlattr_iterator_many() { let mut buf = [0; (NLA_HDR_LEN + mem::size_of::()) * 2]; write_attr(&mut buf, 0, IFLA_XDP_FD as u16, 42u32).unwrap(); write_attr( &mut buf, NLA_HDR_LEN + mem::size_of::(), IFLA_XDP_EXPECTED_FD as u16, 12u32, ) .unwrap(); let mut iter = NlAttrsIterator::new(&buf); let attr = iter.next().unwrap().unwrap(); assert_eq!(attr.header.nla_type, IFLA_XDP_FD as u16); assert_eq!(attr.data.len(), mem::size_of::()); assert_eq!(u32::from_ne_bytes(attr.data.try_into().unwrap()), 42); let attr = iter.next().unwrap().unwrap(); assert_eq!(attr.header.nla_type, IFLA_XDP_EXPECTED_FD as u16); assert_eq!(attr.data.len(), mem::size_of::()); assert_eq!(u32::from_ne_bytes(attr.data.try_into().unwrap()), 12); assert!(iter.next().is_none()); } #[test] fn test_nlattr_iterator_nested() { let mut buf = [0; 1024]; let mut options = NestedAttrs::new(&mut buf, TCA_OPTIONS as u16); options.write_attr(TCA_BPF_FD as u16, 42).unwrap(); let name = CString::new("foo").unwrap(); options .write_attr_bytes(TCA_BPF_NAME as u16, name.to_bytes_with_nul()) .unwrap(); options.finish().unwrap(); let mut iter = NlAttrsIterator::new(&buf); let outer = iter.next().unwrap().unwrap(); assert_eq!( outer.header.nla_type & NLA_TYPE_MASK as u16, TCA_OPTIONS as u16 ); let mut iter = NlAttrsIterator::new(outer.data); let inner = iter.next().unwrap().unwrap(); assert_eq!( inner.header.nla_type & NLA_TYPE_MASK as u16, TCA_BPF_FD as u16 ); let inner = iter.next().unwrap().unwrap(); assert_eq!( inner.header.nla_type & NLA_TYPE_MASK as u16, TCA_BPF_NAME as u16 ); let name = CStr::from_bytes_with_nul(inner.data).unwrap(); assert_eq!(name.to_str().unwrap(), "foo"); } } aya-0.13.1/src/sys/perf_event.rs000064400000000000000000000105461046102023000145760ustar 00000000000000use std::{ ffi::{c_int, CString, OsStr}, io, mem, os::fd::{BorrowedFd, FromRawFd as _}, }; use libc::pid_t; use super::{syscall, SysResult, Syscall}; use crate::generated::{ perf_event_attr, perf_event_sample_format::PERF_SAMPLE_RAW, perf_sw_ids::PERF_COUNT_SW_BPF_OUTPUT, perf_type_id::{PERF_TYPE_SOFTWARE, PERF_TYPE_TRACEPOINT}, PERF_FLAG_FD_CLOEXEC, }; #[allow(clippy::too_many_arguments)] pub(crate) fn perf_event_open( perf_type: u32, config: u64, pid: pid_t, cpu: c_int, sample_period: u64, sample_frequency: Option, wakeup: bool, inherit: bool, flags: u32, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; attr.config = config; attr.size = mem::size_of::() as u32; attr.type_ = perf_type; attr.sample_type = PERF_SAMPLE_RAW as u64; attr.set_inherit(if inherit { 1 } else { 0 }); attr.__bindgen_anon_2.wakeup_events = u32::from(wakeup); if let Some(frequency) = sample_frequency { attr.set_freq(1); attr.__bindgen_anon_1.sample_freq = frequency; } else { attr.__bindgen_anon_1.sample_period = sample_period; } perf_event_sys(attr, pid, cpu, flags) } pub(crate) fn perf_event_open_bpf(cpu: c_int) -> SysResult { perf_event_open( PERF_TYPE_SOFTWARE as u32, PERF_COUNT_SW_BPF_OUTPUT as u64, -1, cpu, 1, None, true, false, PERF_FLAG_FD_CLOEXEC, ) } pub(crate) fn perf_event_open_probe( ty: u32, ret_bit: Option, name: &OsStr, offset: u64, pid: Option, ) -> SysResult { use std::os::unix::ffi::OsStrExt as _; let mut attr = unsafe { mem::zeroed::() }; if let Some(ret_bit) = ret_bit { attr.config = 1 << ret_bit; } let c_name = CString::new(name.as_bytes()).unwrap(); attr.size = mem::size_of::() as u32; attr.type_ = ty; attr.__bindgen_anon_3.config1 = c_name.as_ptr() as u64; attr.__bindgen_anon_4.config2 = offset; let cpu = if pid.is_some() { -1 } else { 0 }; let pid = pid.unwrap_or(-1); perf_event_sys(attr, pid, cpu, PERF_FLAG_FD_CLOEXEC) } pub(crate) fn perf_event_open_trace_point( id: u32, pid: Option, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; attr.size = mem::size_of::() as u32; attr.type_ = PERF_TYPE_TRACEPOINT as u32; attr.config = id as u64; let cpu = if pid.is_some() { -1 } else { 0 }; let pid = pid.unwrap_or(-1); perf_event_sys(attr, pid, cpu, PERF_FLAG_FD_CLOEXEC) } pub(crate) fn perf_event_ioctl(fd: BorrowedFd<'_>, request: c_int, arg: c_int) -> SysResult { let call = Syscall::PerfEventIoctl { fd, request, arg }; #[cfg(not(test))] return syscall(call); #[cfg(test)] return crate::sys::TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) }); } fn perf_event_sys( attr: perf_event_attr, pid: pid_t, cpu: i32, flags: u32, ) -> SysResult { let fd = syscall(Syscall::PerfEventOpen { attr, pid, cpu, group: -1, flags, })?; let fd = fd.try_into().map_err(|_| { ( fd, io::Error::new( io::ErrorKind::InvalidData, format!("perf_event_open: invalid fd returned: {fd}"), ), ) })?; // SAFETY: perf_event_open returns a new file descriptor on success. unsafe { Ok(crate::MockableFd::from_raw_fd(fd)) } } /* impl TryFrom for perf_event_type { PERF_RECORD_MMAP = 1, PERF_RECORD_LOST = 2, PERF_RECORD_COMM = 3, PERF_RECORD_EXIT = 4, PERF_RECORD_THROTTLE = 5, PERF_RECORD_UNTHROTTLE = 6, PERF_RECORD_FORK = 7, PERF_RECORD_READ = 8, PERF_RECORD_SAMPLE = 9, PERF_RECORD_MMAP2 = 10, PERF_RECORD_AUX = 11, PERF_RECORD_ITRACE_START = 12, PERF_RECORD_LOST_SAMPLES = 13, PERF_RECORD_SWITCH = 14, PERF_RECORD_SWITCH_CPU_WIDE = 15, PERF_RECORD_NAMESPACES = 16, PERF_RECORD_KSYMBOL = 17, PERF_RECORD_BPF_EVENT = 18, PERF_RECORD_CGROUP = 19, PERF_RECORD_MAX type Error = (); fn try_from(value: u32) -> Result { todo!() } } */ aya-0.13.1/src/util.rs000064400000000000000000000364541046102023000126060ustar 00000000000000//! Utility functions. use std::{ collections::BTreeMap, error::Error, ffi::{CStr, CString}, fmt::Display, fs::{self, File}, io::{self, BufRead, BufReader}, mem, num::ParseIntError, slice, str::{FromStr, Utf8Error}, }; use libc::{if_nametoindex, sysconf, uname, utsname, _SC_PAGESIZE}; use crate::{ generated::{TC_H_MAJ_MASK, TC_H_MIN_MASK}, Pod, }; /// Represents a kernel version, in major.minor.release version. // Adapted from https://docs.rs/procfs/latest/procfs/sys/kernel/struct.Version.html. #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd)] pub struct KernelVersion { pub(crate) major: u8, pub(crate) minor: u8, pub(crate) patch: u16, } #[derive(thiserror::Error, Debug)] enum CurrentKernelVersionError { #[error("failed to read kernel version")] IO(#[from] io::Error), #[error("failed to parse kernel version")] ParseError(String), #[error("kernel version string is not valid UTF-8")] Utf8(#[from] Utf8Error), } impl KernelVersion { /// Constructor. pub fn new(major: u8, minor: u8, patch: u16) -> Self { Self { major, minor, patch, } } /// Returns the kernel version of the currently running kernel. pub fn current() -> Result { Self::get_kernel_version() } /// The equivalent of LINUX_VERSION_CODE. pub fn code(self) -> u32 { let Self { major, minor, mut patch, } = self; // Certain LTS kernels went above the "max" 255 patch so // backports were done to cap the patch version let max_patch = match (major, minor) { // On 4.4 + 4.9, any patch 257 or above was hardcoded to 255. // See: https://github.com/torvalds/linux/commit/a15813a + // https://github.com/torvalds/linux/commit/42efb098 (4, 4 | 9) => 257, // On 4.14, any patch 252 or above was hardcoded to 255. // See: https://github.com/torvalds/linux/commit/e131e0e (4, 14) => 252, // On 4.19, any patch 222 or above was hardcoded to 255. // See: https://github.com/torvalds/linux/commit/a256aac (4, 19) => 222, // For other kernels (i.e., newer LTS kernels as other // ones won't reach 255+ patches) clamp it to 255. See: // https://github.com/torvalds/linux/commit/9b82f13e _ => 255, }; // anything greater or equal to `max_patch` is hardcoded to // 255. if patch >= max_patch { patch = 255; } (u32::from(major) << 16) + (u32::from(minor) << 8) + u32::from(patch) } // These (get_ubuntu_kernel_version, parse_ubuntu_kernel_version, read_ubuntu_kernel_version_file) // are ported from https://github.com/torvalds/linux/blob/3f01e9f/tools/lib/bpf/libbpf_probes.c#L21-L101. fn get_ubuntu_kernel_version() -> Result, CurrentKernelVersionError> { Self::read_ubuntu_kernel_version_file().and_then(|content| { content .and_then(|content| Self::parse_ubuntu_kernel_version(&content).transpose()) .transpose() }) } fn read_ubuntu_kernel_version_file() -> Result, CurrentKernelVersionError> { const UBUNTU_KVER_FILE: &str = "/proc/version_signature"; match fs::read_to_string(UBUNTU_KVER_FILE) { Ok(s) => Ok(Some(s)), Err(e) => { if e.kind() != io::ErrorKind::NotFound { Err(e.into()) } else { Ok(None) } } } } fn parse_ubuntu_kernel_version(s: &str) -> Result, CurrentKernelVersionError> { let mut parts = s.split_terminator(char::is_whitespace); let mut next = || { parts .next() .ok_or_else(|| CurrentKernelVersionError::ParseError(s.to_string())) }; let _ubuntu: &str = next()?; let _ubuntu_version: &str = next()?; let kernel_version_string = next()?; Self::parse_kernel_version_string(kernel_version_string).map(Some) } fn get_debian_kernel_version( info: &utsname, ) -> Result, CurrentKernelVersionError> { // Safety: man 2 uname: // // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are // terminated by a null byte ('\0'). let s = unsafe { CStr::from_ptr(info.version.as_ptr()) }; let s = s.to_str()?; let kernel_version_string = match s.split_once("Debian ") { Some((_prefix, suffix)) => suffix, None => return Ok(None), }; Self::parse_kernel_version_string(kernel_version_string).map(Some) } fn get_kernel_version() -> Result { if let Ok(Some(v)) = Self::get_ubuntu_kernel_version() { return Ok(v); } let mut info = unsafe { mem::zeroed::() }; if unsafe { uname(&mut info) } != 0 { return Err(io::Error::last_os_error().into()); } if let Some(v) = Self::get_debian_kernel_version(&info)? { return Ok(v); } // Safety: man 2 uname: // // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are // terminated by a null byte ('\0'). let s = unsafe { CStr::from_ptr(info.release.as_ptr()) }; let s = s.to_str()?; Self::parse_kernel_version_string(s) } fn parse_kernel_version_string(s: &str) -> Result { fn parse>(s: Option<&str>) -> Option { s.map(str::parse).transpose().unwrap_or_default() } let error = || CurrentKernelVersionError::ParseError(s.to_string()); let mut parts = s.split(|c: char| c == '.' || !c.is_ascii_digit()); let major = parse(parts.next()).ok_or_else(error)?; let minor = parse(parts.next()).ok_or_else(error)?; let patch = parse(parts.next()).ok_or_else(error)?; Ok(Self::new(major, minor, patch)) } } impl Display for KernelVersion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}.{}.{}", self.major, self.minor, self.patch) } } const ONLINE_CPUS: &str = "/sys/devices/system/cpu/online"; const POSSIBLE_CPUS: &str = "/sys/devices/system/cpu/possible"; /// Returns the numeric IDs of the CPUs currently online. pub fn online_cpus() -> Result, (&'static str, io::Error)> { read_cpu_ranges(ONLINE_CPUS) } /// Get the number of possible cpus. /// /// See `/sys/devices/system/cpu/possible`. pub fn nr_cpus() -> Result { thread_local! { // TODO(https://github.com/rust-lang/rust/issues/109737): Use // `std::cell::OnceCell` when `get_or_try_init` is stabilized. static CACHE: once_cell::unsync::OnceCell = const { once_cell::unsync::OnceCell::new() }; } CACHE.with(|cell| { cell.get_or_try_init(|| read_cpu_ranges(POSSIBLE_CPUS).map(|cpus| cpus.len())) .copied() }) } fn read_cpu_ranges(path: &'static str) -> Result, (&'static str, io::Error)> { (|| { let data = fs::read_to_string(path)?; parse_cpu_ranges(data.trim()) })() .map_err(|error| (path, error)) } fn parse_cpu_ranges(data: &str) -> Result, io::Error> { data.split(',') .map(|range| { let mut iter = range .split('-') .map(|s| s.parse::().map_err(|ParseIntError { .. }| range)); let start = iter.next().unwrap()?; // str::split always returns at least one element. let end = match iter.next() { None => start, Some(end) => { if iter.next().is_some() { return Err(range); } end? } }; Ok(start..=end) }) .try_fold(Vec::new(), |mut cpus, range| { let range = range.map_err(|range| io::Error::new(io::ErrorKind::InvalidData, range))?; cpus.extend(range); Ok(cpus) }) } /// Loads kernel symbols from `/proc/kallsyms`. /// /// See [`crate::maps::StackTraceMap`] for an example on how to use this to resolve kernel addresses to symbols. pub fn kernel_symbols() -> Result, io::Error> { let mut reader = BufReader::new(File::open("/proc/kallsyms")?); parse_kernel_symbols(&mut reader) } fn parse_kernel_symbols(reader: impl BufRead) -> Result, io::Error> { reader .lines() .map(|line| { let line = line?; (|| { let mut parts = line.splitn(4, ' '); let addr = parts.next()?; let _kind = parts.next()?; let name = parts.next()?; let addr = match u64::from_str_radix(addr, 16) { Ok(addr) => Some(addr), Err(ParseIntError { .. }) => None, }?; Some((addr, name.to_owned())) })() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, line.clone())) }) .collect() } /// Returns the prefix used by syscalls. /// /// # Example /// /// ```no_run /// use aya::util::syscall_prefix; /// let prefix = syscall_prefix().unwrap(); /// let syscall_fname = format!("{prefix}exec"); /// ``` /// /// # Errors /// /// Returns [`std::io::ErrorKind::NotFound`] if the prefix can't be guessed. Returns other [`std::io::Error`] kinds if `/proc/kallsyms` can't be opened or is somehow invalid. #[deprecated( since = "0.12.0", note = "On some systems - commonly on 64 bit kernels that support running \ 32 bit applications - the syscall prefix depends on what architecture an \ application is compiled for, therefore attaching to only one prefix is \ incorrect and can lead to security issues." )] pub fn syscall_prefix() -> Result<&'static str, io::Error> { const PREFIXES: [&str; 7] = [ "sys_", "__x64_sys_", "__x32_compat_sys_", "__ia32_compat_sys_", "__arm64_sys_", "__s390x_sys_", "__s390_sys_", ]; let ksym = kernel_symbols()?; for p in PREFIXES { let prefixed_syscall = format!("{}bpf", p); if ksym.values().any(|el| *el == prefixed_syscall) { return Ok(p); } } Err(io::ErrorKind::NotFound.into()) } pub(crate) fn ifindex_from_ifname(if_name: &str) -> Result { let c_str_if_name = CString::new(if_name)?; let c_if_name = c_str_if_name.as_ptr(); // Safety: libc wrapper let if_index = unsafe { if_nametoindex(c_if_name) }; if if_index == 0 { return Err(io::Error::last_os_error()); } Ok(if_index) } pub(crate) fn tc_handler_make(major: u32, minor: u32) -> u32 { (major & TC_H_MAJ_MASK) | (minor & TC_H_MIN_MASK) } /// Include bytes from a file for use in a subsequent [`crate::Ebpf::load`]. /// /// This macro differs from the standard `include_bytes!` macro since it also ensures that /// the bytes are correctly aligned to be parsed as an ELF binary. This avoid some nasty /// compilation errors when the resulting byte array is not the correct alignment. /// /// # Examples /// ```ignore /// use aya::{Ebpf, include_bytes_aligned}; /// /// let mut bpf = Ebpf::load(include_bytes_aligned!( /// "/path/to/bpf.o" /// ))?; /// /// # Ok::<(), aya::EbpfError>(()) /// ``` #[macro_export] macro_rules! include_bytes_aligned { ($path:expr) => {{ #[repr(align(32))] pub struct Aligned32; #[repr(C)] pub struct Aligned { pub _align: [Aligned32; 0], pub bytes: Bytes, } const ALIGNED: &Aligned<[u8]> = &Aligned { _align: [], bytes: *include_bytes!($path), }; &ALIGNED.bytes }}; } pub(crate) fn page_size() -> usize { // Safety: libc (unsafe { sysconf(_SC_PAGESIZE) }) as usize } // bytes_of converts a to a byte slice pub(crate) unsafe fn bytes_of(val: &T) -> &[u8] { let size = mem::size_of::(); slice::from_raw_parts(slice::from_ref(val).as_ptr().cast(), size) } pub(crate) fn bytes_of_slice(val: &[T]) -> &[u8] { let size = val.len().wrapping_mul(mem::size_of::()); // Safety: // Any alignment is allowed. // The size is determined in this function. // The Pod trait ensures the type is valid to cast to bytes. unsafe { slice::from_raw_parts(val.as_ptr().cast(), size) } } pub(crate) fn bytes_of_bpf_name(bpf_name: &[core::ffi::c_char; 16]) -> &[u8] { let length = bpf_name .iter() .rposition(|ch| *ch != 0) .map(|pos| pos + 1) .unwrap_or(0); unsafe { slice::from_raw_parts(bpf_name.as_ptr() as *const _, length) } } #[cfg(test)] mod tests { use assert_matches::assert_matches; use super::*; #[test] fn test_parse_kernel_version_string() { // cat /proc/version_signature on Proxmox VE 8.1.4. assert_matches!(KernelVersion::parse_ubuntu_kernel_version(""), Err(CurrentKernelVersionError::ParseError(s)) if s.is_empty()); // cat /proc/version_signature on Ubuntu 22.04. assert_matches!(KernelVersion::parse_ubuntu_kernel_version( "Ubuntu 5.15.0-82.91-generic 5.15.111"), Ok(Some(kernel_version)) => { assert_eq!(kernel_version, KernelVersion::new(5, 15, 111)) }); // WSL. assert_matches!(KernelVersion::parse_kernel_version_string("5.15.90.1-microsoft-standard-WSL2"), Ok(kernel_version) => { assert_eq!(kernel_version, KernelVersion::new(5, 15, 90)) }); // uname -r on Fedora. assert_matches!(KernelVersion::parse_kernel_version_string("6.3.11-200.fc38.x86_64"), Ok(kernel_version) => { assert_eq!(kernel_version, KernelVersion::new(6, 3, 11)) }); } #[test] fn test_parse_online_cpus() { assert_eq!(parse_cpu_ranges("0").unwrap(), vec![0]); assert_eq!(parse_cpu_ranges("0,1").unwrap(), vec![0, 1]); assert_eq!(parse_cpu_ranges("0,1,2").unwrap(), vec![0, 1, 2]); assert_eq!( parse_cpu_ranges("0-7").unwrap(), (0..=7).collect::>() ); assert_eq!( parse_cpu_ranges("0-3,4-7").unwrap(), (0..=7).collect::>() ); assert_eq!( parse_cpu_ranges("0-5,6,7").unwrap(), (0..=7).collect::>() ); assert!(parse_cpu_ranges("").is_err()); assert!(parse_cpu_ranges("0-1,2-").is_err()); assert!(parse_cpu_ranges("foo").is_err()); } #[test] fn test_parse_kernel_symbols() { let data = "0000000000002000 A irq_stack_backing_store\n\ 0000000000006000 A cpu_tss_rw [foo bar]\n" .as_bytes(); let syms = parse_kernel_symbols(&mut BufReader::new(data)).unwrap(); assert_eq!(syms.keys().collect::>(), vec![&0x2000, &0x6000]); assert_eq!( syms.get(&0x2000u64).unwrap().as_str(), "irq_stack_backing_store" ); assert_eq!(syms.get(&0x6000u64).unwrap().as_str(), "cpu_tss_rw"); } }