DEV Community

stevelatif
stevelatif

Posted on

Aya Rust tutorial Part Three XDP Pass

© steve latif

Aya Rust Tutorial Part 3: XDP_pass

Assumptions

Assuming you already set up the prerequisites and installed the required tools
and libraries.
The eBPF program will load into the running kernel where it will be
verified.

The eBPF program will run in a virtual machine that is built into the Linux
kernel. This virtual machine has nine general purpose registers R1-R9 and one
read only register R10 that functions as a frame pointer. Running anything
that can be loaded dynamically in the kernel with elevated privileges can
be potential security issue. The eBPF virtual machine contains
a verifier see https://docs.kernel.org/bpf/verifier.html
The verifier will check and reject if the program that contains:

  • loops
  • any type of pointer arithmetic
  • bounds or alignment violations
  • unreachable instructions

There are other restrictions, consult the documentation for more details

If you have worked with rust code with cargo before, you will have cycled
through iterations of

cargo build
cargo run
Enter fullscreen mode Exit fullscreen mode

Where the source tree of a simple application would like:

$ tree
.
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files
Enter fullscreen mode Exit fullscreen mode

and after the application had been compiled:

$ cargo build
   Compiling hello v0.1.0 (/tmp/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 1.07s
$ tree
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── target
    ├── CACHEDIR.TAG
    └── debug
        ├── build
        ├── deps
        │   ├── hello-20e1cfc616fb61ac
        │   └── hello-20e1cfc616fb61ac.d
        ├── examples
        ├── hello
        ├── hello.d
        └── incremental
            └── hello-3iozzekpyrysr
                ├── s-gvk87j1cfi-6yflog-9nq5zq3lt8rcg1slvtqhu2l92
                │   ├── 1cwggjlit3xor5e4.o
                │   ├── 3nmgwmthvx9ijli.o
                │   ├── 3qweoew2z2q7s3nh.o
                │   ├── 4j2x83e2uqqcjw4i.o
                │   ├── 4pluyvgu1vsjgq2o.o
                │   ├── 54dgri4zf1sqnocs.o
                │   ├── dep-graph.bin
                │   ├── query-cache.bin
                │   └── work-products.bin
                └── s-gvk87j1cfi-6yflog.lock

9 directories, 18 files
Enter fullscreen mode Exit fullscreen mode

The compiled binary can be found in target/debug/hello and can be run
directly from that location or by using cargo

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/hello`
Hello, world!
Enter fullscreen mode Exit fullscreen mode

The aya framework creates two programs, an eBPF program that will
be loaded into the kernel and user space program that will be load the
eBPF program, and can also pass and receive data with the eBPF program
using maps - more about these in later parts.

The code framework for the eBPF and user space will be set up using a template.
The eBPF program will be compiled and run using a cargo
workflow extension:

cargo xtask build-ebpf
Enter fullscreen mode Exit fullscreen mode

Compilation is a two stage process:

cargo xtask build-ebpf
cargo build
Enter fullscreen mode Exit fullscreen mode

You can examine the xtask part by looking at the files in xdp_pass/xtask after
you generate the code in the next step:
$ ls xdp-pass/xtask/src/
build_ebpf.rs main.rs run.rs

Generating the code

Why use a template to generate the code? Aya-rs has been rapidly evolving,
using older examples of code with the latest versions can lead to some
errors that are hard to debug. Use the sample code here as a rough guide.
Assuming that we have cargo and the generate extension have been installed, we
can generate the code, at the prompt select xdp-pass as the project name

Using the template, generate the code in directory xdp-pass\, select the xdp option.

$ cargo generate https://github.com/aya-rs/aya-template  
⚠️   Favorite `https://github.com/aya-rs/aya-template` not found in config, using it as a git repository: https://github.com/aya-rs/aya-template
🤷   Project Name: xdp-pass
🔧   Destination: /home/steve/articles/learning_ebpf_with_rust/xdp-tutorial/basic01-xdp-pass/xdp-pass ...
🔧   project-name: xdp-pass ...
🔧   Generating template ...
? 🤷   Which type of eBPF program? ›
  cgroup_skb
  cgroup_sockopt
  cgroup_sysctl
  classifier
  fentry
  fexit
  kprobe
  kretprobe
  lsm
  perf_event
  raw_tracepoint
  sk_msg
  sock_ops
  socket_filter
  tp_btf
  tracepoint
  uprobe
  uretprobe
❯ xdp
Enter fullscreen mode Exit fullscreen mode

The generated files:

$ tree xdp-pass/
xdp-pass/
├── Cargo.toml
├── README.md
├── xdp-pass
│   ├── Cargo.toml
│   └── src
│       └── main.rs
├── xdp-pass-common
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── xdp-pass-ebpf
│   ├── Cargo.toml
│   ├── rust-toolchain.toml
│   └── src
│       └── main.rs
└── xtask
    ├── Cargo.toml
    └── src
        ├── build_ebpf.rs
        ├── main.rs
        └── run.rs

8 directories, 13 files
Enter fullscreen mode Exit fullscreen mode

Look at the file: and modify so it looks like:

#![no_std]
#![no_main]

use aya_ebpf::{bindings::xdp_action, macros::xdp, programs::XdpContext};

#[xdp]
pub fn xdp_pass(_ctx: XdpContext) -> u32 {
    xdp_action::XDP_PASS
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    unsafe { core::hint::unreachable_unchecked() }
}
Enter fullscreen mode Exit fullscreen mode

The templated code will run and return XDP\_PASS\

Compile the code

cargo xtask build-ebpf
cargo build 
Enter fullscreen mode Exit fullscreen mode

Compile in this order else the cargo build\ will fail.

The xtask step will generate the eBPF object file:
https://raw.githubusercontent.com/stevelatif/articles/main/blogs/target/bpfel-unknown-none/debug/xdp-pass

Looking into the BPF-ELF object

eBPF bytecode is run in a virtual machine in the Linux kernel. There are 10 registers:

  • R0 stores function return values, and the exit value for an eBPF program
  • R1-R5 stores function arguments
  • R6-R9 are for general purpose usage
  • R10 stores adresses for the stack frame

Inspecting the sections of the eBPF file:

$ llvm-readelf --sections target/bpfel-unknown-none/debug/xdp-pass
There are 5 section headers, starting at offset 0x228:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .strtab           STRTAB          0000000000000000 0001c0 000068 00      0   0  1
  [ 2] .text             PROGBITS        0000000000000000 000040 000098 00  AX  0   0  8
  [ 3] xdp               PROGBITS        0000000000000000 0000d8 000010 00  AX  0   0  8
  [ 4] .symtab           SYMTAB          0000000000000000 0000e8 0000d8 18      1   6  8
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  R (retain), p (processor specific)
Enter fullscreen mode Exit fullscreen mode

We can see the xdp section we defined with the xdp macro in the code, so lets inspect that

$ llvm-objdump --no-show-raw-insn --section=xdp  -S target/bpfel-unknown-none/debug/xdp-pass

target/bpfel-unknown-none/debug/xdp-pass:       file format elf64-bpf

Disassembly of section xdp:

0000000000000000 <xdp_pass>:
       0:       r0 = 2
       1:       exit
Enter fullscreen mode Exit fullscreen mode

Setting the r0 register to 2 corresponds to returning XDP_PASS

Use the IOvisor documentation of the opcodes from here https://github.com/iovisor/bpf-docs/blob/master/eBPF.md

We can run the program using cargo, as its an XDP program we will have to specify a network interface.
For this introductory example we can use the the loopback
interface for convenience:

$ RUST_LOG=info cargo xtask run -- -i lo
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/xtask run -- -i lo`
    Finished `dev` profile [optimized] target(s) in 0.10s
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
[2024-04-28T06:14:44Z WARN  xdp_pass] failed to initialize eBPF logger: log event array AYA_LOGS doesn't exist
[2024-04-28T06:14:44Z INFO  xdp_pass] Waiting for Ctrl-C...
Enter fullscreen mode Exit fullscreen mode

We can also load eBPF programs using iproute2

sudo ip link set dev lo xdpgeneric obj   https://raw.githubusercontent.com/stevelatif/articles/main/blogs/target/bpfel-unknown-none/debug/xdp-pass sec xdp
Enter fullscreen mode Exit fullscreen mode

We can see the loaded program with bpftool:

sudo bpftool prog | grep -A 5 xdp
4509: xdp  name xdp_pass  tag 3b185187f1855c4c
        loaded_at 2024-04-28T09:15:17-0700  uid 0
        xlated 16B  jited 22B  memlock 4096B
Enter fullscreen mode Exit fullscreen mode

We can generate a dump of the byte code of the running eBPF byte code using bpftool. Generate a dot file using bpftool and the id number for 4509
from above.

$ sudo bpftool prog dump xlated id 4509 visual &> 4509.dot
Enter fullscreen mode Exit fullscreen mode

And then use that to generate an image file with graphviz

$ dot -Tpng /tmp/4509.dot -o ~/4509.png
Enter fullscreen mode Exit fullscreen mode

Image description

The comparable C version of the eBPF looks like:

/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("xdp")
int  xdp_pass_func(struct xdp_md *ctx)
{
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
Enter fullscreen mode Exit fullscreen mode

Which would be compiled like:

clang -O2 -target bpf -c ebpf_program.c -o ebpf_program.oh
Enter fullscreen mode Exit fullscreen mode

And loaded:

$ sudo  ip link set dev lo xdpgeneric obj   https://raw.githubusercontent.com/stevelatif/articles/main/blogs/xdp_pass.o sec xdp                                                                                                                                                                        
$ sudo ip link show dev lo                                                                                                                                                                                                              
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000                                                                                                                                                                
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00                                                                                                                                                                                                                          
    prog/xdp id 4529 tag 3b185187f1855c4c jited                         
Enter fullscreen mode Exit fullscreen mode

Dumping the byte code shows similar sections,

$ llvm-readelf --sections xdp_pass.o
There are 7 section headers, starting at offset 0x108:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .strtab           STRTAB          0000000000000000 0000ba 00004e 00      0   0  1
  [ 2] .text             PROGBITS        0000000000000000 000040 000000 00  AX  0   0  4
  [ 3] xdp               PROGBITS        0000000000000000 000040 000010 00  AX  0   0  8
  [ 4] license           PROGBITS        0000000000000000 000050 000004 00  WA  0   0  1
  [ 5] .llvm_addrsig     LLVM_ADDRSIG    0000000000000000 0000b8 000002 00   E  6   0  1
  [ 6] .symtab           SYMTAB          0000000000000000 000058 000060 18      1   2  8
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  R (retain), p (processor specific)
Enter fullscreen mode Exit fullscreen mode

Disassembly is the same as the Rust version:

steve@Peshawer:~/git/aya_discovery/xdp_pass$ llvm-objdump --no-show-raw-insn --section=xdp  -S https://raw.githubusercontent.com/stevelatif/articles/main/blogs/xdp_pass.o

https://raw.githubusercontent.com/stevelatif/articles/main/blogs/xdp_pass.o: file format elf64-bpf

Disassembly of section xdp:

0000000000000000 <xdp_pass_func_02>:
0: r0 = 2
1: exit

Enter fullscreen mode Exit fullscreen mode




Summary

  • We can use Rust with the aya crate to build eBPF programs
  • Load and unload the eBPF programs we created using
    • Cargo
    • ip net2
  • Disassemble the byte code using
    • llvm-objdump
  • Generate graphics to aid in debugging using graphviz.

Top comments (0)