
Rex — это безопасный и удобный фреймворк для расширения ядра, который позволяет загружать и выполнять программы расширения ядра на Rust вместо eBPF.
____ _______ __ _____ _ _
| _ \| ____\ \/ / | ____|_ _| |_ ___ _ __ ___(_) ___ _ __ ___
| |_) | _| \ / | _| \ \/ / __/ _ \ '_ \/ __| |/ _ \| '_ \/ __|
| _ <| |___ / \ | |___ > <| || __/ | | \__ \ | (_) | | | \__ \
|_| \_\_____/_/\_\ |_____/_/\_\\__\___|_| |_|___/_|\___/|_| |_|___/
Rex — это безопасный и удобный фреймворк для расширений ядра, который позволяет загружать и выполнять программы расширений ядра на Rust вместо eBPF. В отличие от фреймворков на основе eBPF, таких как Aya, программы Rex не проходят через внутриядерный верификатор. Вместо этого они реализуются в безопасном подмножестве Rust, для которого компилятор Rust выполняет необходимые проверки безопасности и генерирует нативный код напрямую. Такой подход избавляет от чрезмерно строгих требований верификации (например, ограничений на сложность программ) и связанных с ними запутанных ошибок верификатора, а также потенциально предоставляет лучшие возможности для оптимизации в бэкенде нативного компилятора (т. е. LLVM), чем подход «бэкенд eBPF + JIT в ядре».
В настоящее время Rex поддерживает следующие возможности:
kprobe, perf_event, tracepoint, xdp и tc.Следующий пример реализует программу kprobe, которая прикрепляется к выбранному системному вызову и внедряет ошибку (заданную через errno) в системный вызов процесса (заданного через pid). Полный пример, включая программу-загрузчик, можно найти в samples/error_injector.
#![no_std]
#![no_main]
use rex::kprobe::kprobe;
use rex::map::RexHashMap;
use rex::pt_regs::PtRegs;
use rex::rex_kprobe;
use rex::rex_map;
use rex::Result;
#[allow(non_upper_case_globals)]
#[rex_map]
static pid_to_errno: RexHashMap<i32, u64> = RexHashMap::new(1, 0);
#[rex_kprobe]
pub fn err_injector(obj: &kprobe, ctx: &mut PtRegs) -> Result {
obj.bpf_get_current_task()
.map(|t| t.get_pid())
.and_then(|p| obj.bpf_map_lookup_elem(&pid_to_errno, &p).cloned())
.map(|e| obj.bpf_override_return(ctx, e))
.ok_or(0)
}
Другие примеры программ можно найти в samples.
Подробное руководство можно найти здесь.
Дополнительные проектные документы можно найти в docs.
Существующий механизм расширений eBPF полагается на внутриядерный верификатор eBPF для обеспечения гарантий безопасности. К сожалению, это приводит к проблемам удобства использования, когда безопасные программы отклоняются верификатором, в том числе (но не ограничиваясь):
Rex призван решить эти проблемы, напрямую используя гарантии безопасности безопасного Rust. Разработчики могут реализовывать свои программы любым способом, который допустим в безопасном Rust, при небольшом числе ограничений, и им больше не нужно беспокоиться о сложности программы, генераторе кода или поиске (часто неочевидного) способа выразить одну и ту же логику в угоду верификатору.
Мы демонстрируем это на реализации BPF Memcached Cache (BMC) — современной программы-расширения для ускорения Memcached. Будучи сложной eBPF-программой, BMC вынуждена разделяться на несколько компонентов, связанных BPF-хвостовыми вызовами, и использовать неудобные реализации циклов/ветвлений в угоду верификатору, что совершенно не требуется в её реализации на Rex.
Например, покажем код логики инвалидации кэша BPF-BMC, который ищет команду SET в полезной нагрузке пакета:
// Searches for SET command in payload
for (unsigned int off = 0;
off < BMC_MAX_PACKET_LENGTH && payload + off + 1 <= data_end;
off++) {
if (set_found == 0 && payload[off] == 's' &&
payload + off + 3 <= data_end && payload[off + 1] == 'e' &&
payload[off + 2] == 't') {
off += 3;
set_found = 1;
}
...
}
Этот код не только вводит дополнительное ограничение в цикле (off < BMC_MAX_PACKET_LENGTH) исключительно для прохождения верификатора, но и использует повторяющийся шаблонный код для проверки границ пакета (data_end) и громоздкую логику поиска строки "set" в пакете.
Ни одно из этих обременений не требуется благодаря мощи безопасного Rust в Rex, у которого нет ограничений по сложности и который предоставляет больше свободы в реализации:
let set_iter = payload.windows(4).enumerate().filter_map(|(i, v)| {
if v == b"set " {
Some(i)
} else {
None
}
});
Полную реализацию BMC на Rex можно найти в samples/bmc.
Rex распространяется под лицензией GPLv2. Подмодули (Linux, Rust, LLVM) в этом репозитории распространяются на своих собственных условиях. Пожалуйста, обратитесь к соответствующим файлам лицензий за подробностями. Кроме того, бенчмарк memcached распространяется под лицензией MIT.