Probes
Source Code
Full code for the example in this chapter is available here.
What are the probes in eBPF?
The probe BPF programs attach to kernel (kprobes) or user-side (uprobes)
functions and are able to access the function parameters of those functions.
You can find more information about probes in the
kernel documentation, including the difference between kprobes
and kretprobes.
Example project
To illustrate kprobes with Aya, let's write a program which
attaches a eBPF handler to the tcp_connect
function and allows
printing the source and destination IP addresses from the socket parameter.
Design
For this demo program, we are going to rely on aya-log to print IP addresses
from the BPF program and not going to have any custom BPF maps (besides those
created by aya-log).
eBPF code
- From the
tcp_connect
signature, we see that struct sock *sk
is the only
function parameter. We will access it from the ProbeContext
ctx handle.
- We call
bpf_probe_read_kernel
helper to copy the
struct sock_common __sk_common
portion of the socket structure. (For uprobe
programs, we would need to call bpf_probe_read_user
instead.)
- We match the
skc_family
field, and for AF_INET
(IPv4) and AF_INET6
(IPv6) values, extract and print the src and destination addresses using
aya-log info!
macro.
Here's how the eBPF code looks like:
kprobetcp-ebpf/src/main.rs |
---|
| #![no_std]
#![no_main]
#[allow(clippy::all)]
#[allow(non_upper_case_globals)]
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
#[allow(dead_code)]
#[rustfmt::skip]
mod binding;
use crate::binding::{sock, sock_common};
use aya_ebpf::{
helpers::bpf_probe_read_kernel, macros::kprobe, programs::ProbeContext,
};
use aya_log_ebpf::info;
const AF_INET: u16 = 2;
const AF_INET6: u16 = 10;
#[kprobe]
pub fn kprobetcp(ctx: ProbeContext) -> u32 {
match try_kprobetcp(ctx) {
Ok(ret) => ret,
Err(ret) => ret.try_into().unwrap_or(1),
}
}
fn try_kprobetcp(ctx: ProbeContext) -> Result<u32, i64> {
let sock: *mut sock = ctx.arg(0).ok_or(1i64)?;
let sk_common = unsafe {
bpf_probe_read_kernel(&(*sock).__sk_common as *const sock_common)
}?;
match sk_common.skc_family {
AF_INET => {
let src_addr = u32::from_be(unsafe {
sk_common.__bindgen_anon_1.__bindgen_anon_1.skc_rcv_saddr
});
let dest_addr: u32 = u32::from_be(unsafe {
sk_common.__bindgen_anon_1.__bindgen_anon_1.skc_daddr
});
info!(
&ctx,
"AF_INET src address: {:i}, dest address: {:i}",
src_addr,
dest_addr,
);
Ok(0)
}
AF_INET6 => {
let src_addr = sk_common.skc_v6_rcv_saddr;
let dest_addr = sk_common.skc_v6_daddr;
info!(
&ctx,
"AF_INET6 src addr: {:i}, dest addr: {:i}",
unsafe { src_addr.in6_u.u6_addr8 },
unsafe { dest_addr.in6_u.u6_addr8 }
);
Ok(0)
}
_ => Ok(0),
}
}
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
|
Userspace code
The purpose of the userspace code is to load the eBPF program and attach it to the
tcp_connect
function.
Here's how the code looks like:
kprobetcp/src/main.rs |
---|
| use aya::programs::KProbe;
use aya_log::EbpfLogger;
use clap::Parser;
use log::{info, warn};
use tokio::signal;
#[derive(Debug, Parser)]
struct Opt {}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let _opt = Opt::parse();
env_logger::init();
// This will include your eBPF object file as raw bytes at compile-time and load it at
// runtime. This approach is recommended for most real-world use cases. If you would
// like to specify the eBPF program at runtime rather than at compile-time, you can
// reach for `Ebpf::load_file` instead.
let mut bpf = aya::Ebpf::load(aya::include_bytes_aligned!(concat!(
env!("OUT_DIR"),
"/kprobetcp"
)))?;
if let Err(e) = EbpfLogger::init(&mut bpf) {
// This can happen if you remove all log statements from your eBPF program.
warn!("failed to initialize eBPF logger: {e}");
}
let program: &mut KProbe =
bpf.program_mut("kprobetcp").unwrap().try_into()?;
program.load()?;
program.attach("tcp_connect", 0)?;
info!("Waiting for Ctrl-C...");
signal::ctrl_c().await?;
info!("Exiting...");
Ok(())
}
|
Running the program
$ RUST_LOG=info cargo run --config 'target."cfg(all())".runner="sudo -E"'
[2022-12-28T20:50:00Z INFO kprobetcp] Waiting for Ctrl-C...
[2022-12-28T20:50:05Z INFO kprobetcp] AF_INET6 src addr: 2001:4998:efeb:282::249, dest addr: 2606:2800:220:1:248:1893:25c8:1946
[2022-12-28T20:50:11Z INFO kprobetcp] AF_INET src address: 10.53.149.148, dest address: 10.87.116.72
[2022-12-28T20:50:30Z INFO kprobetcp] AF_INET src address: 10.53.149.148, dest address: 98.138.219.201