DEV Community

stevelatif
stevelatif

Posted on

Aya Rust tutorial Part Four XDP Hello World

© steve latif

Aya Rust Tutorial Part 4: XDP Hello World

Welcome to part 4. So far we have installed the prerequisite in part 2,
built eBPF code that loads into the kernel and passes the
verifier. Let's continue on by building another XDP
program that will print a message every time it receives a packet
on an interface. As in part 3 we will use the loopback interface.
This will show how to print a message from the kernel. This is analogous
to using 'bpf_printk' in the eBPF programs in C, see [here](https://github.com/libbpf/libbpf-bootstrap/blob/master/examples/c/kprobe.bpf.c_ul

This will involve only a few more lines of code and
will follow the same build and deployment process as in the previous chapter.

Generating the code

As we did in part 3
generate the code using cargo generate
At the prompt select hello-world as the project name

Using the template, generate the code in directory hello-world\, 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: hello-world
🔧   Destination: /home/steve/articles/learning_ebpf_with_rust/xdp-tutorial/basic01-hello-world/hello-world ...
🔧   project-name: hello-world ...
🔧   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 code will if unaltered behave as a hello world program. In the
first part of this note we will modify the generated code, but come back
to it later
Modify the generated code in the file hello-world/hello-world-ebpf/src/main.rs
so that it looks like:

#![no_std]
#![no_main]

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

#[xdp]
pub fn hello_world(_ctx: XdpContext) -> u32 {
    unsafe {
        bpf_printk!(b"packet  received!");
    }
xdp_action::XDP_PASS
}
Enter fullscreen mode Exit fullscreen mode

This code uses the unsafe macro bpf_printk
to print out a message every time a packet is received on the interface.
It returns XDP\_PASS\
bpf_printk is a useful tool for debugging. It is globally shared in the kernel
so other programs using it may disrupt its output.

Compile the code

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

Looking into the BPF-ELF object

As we did in the previous section, lets look at the generated eBPF byte code

$ llvm-readelf --sections target/bpfel-unknown-none/debug/hello-world
There are 7 section headers, starting at offset 0x2e0:

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 000238 0000a2 00      0   0  1
    [ 2] .text             PROGBITS        0000000000000000 000040 000098 00  AX  0   0  8
    [ 3] xdp               PROGBITS        0000000000000000 0000d8 000030 00  AX  0   0  8
    [ 4] .relxdp           REL             0000000000000000 000228 000010 10   I  6   3  8
    [ 5] .rodata           PROGBITS        0000000000000000 000108 000013 00   A  0   0  1
    [ 6] .symtab           SYMTAB          0000000000000000 000120 000108 18      1   8  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

As before we have an xdp section, lets disassemble that:

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

target/bpfel-unknown-none/debug/hello-world:    file format elf64-bpf

Disassembly of section xdp:

0000000000000000 <hello_world>:
   0:       r1 = 0 ll
   2:       r2 = 19
   3:       call 6
   4:       r0 = 2
   5:       exit
Enter fullscreen mode Exit fullscreen mode

Recall that the registers for eBPF:

  • r0: Stores a return value of a function, and exit value for an eBPF program
  • r1 - R5: Stores function arguments
  • r6 - R9: For general purpose usage
  • r10: Stores an address for stack frame

line 0 zeroes out the r1 register
line 2 sets r2 to 19 - the length of the output string
line 3 makes a system call, the mysterious 6 is the index of
bpf_helpers found in bpf.h
line 4 sets the exit value to 2 which corresponds to XDP_PASS

To run this let's use cargo
$ cargo xtask build-ebpf
$ cargo build
$ cargo xtask run -- i lo

To see output, open another terminal enable tracing:

echo 1 | sudo tee /sys/kernel/debug/tracing/tracing_on
Enter fullscreen mode Exit fullscreen mode

Then to see output

sudo cat /sys/kernel/debug/tracing/trace_pipe
Enter fullscreen mode Exit fullscreen mode

From another terminal, ping the loopback interface

ping 127.0.0.1
Enter fullscreen mode Exit fullscreen mode

You should see output being logged in the terminal where you ran the trace_pipe command

$ sudo cat /sys/kernel/debug/tracing/trace_pipe
 ping-75348   [000] ..s21 47214.233803: bpf_trace_printk: packet  received!
 ping-75348   [000] ..s21 47214.233815: bpf_trace_printk: packet  received!
 ping-75348   [007] ..s21 47215.236704: bpf_trace_printk: packet  received!
 ping-75348   [007] ..s21 47215.236737: bpf_trace_printk: packet  received!
Enter fullscreen mode Exit fullscreen mode

Let's return to the previous step where we generated the code. Run the
cargo generate leave the generated code and don't change it

#![no_std]
#![no_main]

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

#[xdp]
pub fn hello_world(ctx: XdpContext) -> u32 {
    match try_hello_world(ctx) {
        Ok(ret) => ret,
        Err(_) => xdp_action::XDP_ABORTED,
    }
}

fn try_hello_world(ctx: XdpContext) -> Result<u32, u32> {
    info!(&ctx, "received a packet");
    Ok(xdp_action::XDP_PASS)
}

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

Build and running it:

cargo xtask build-ebpf
cargo build
RUST_LOG=info cargo xtask run -- -i lo
Enter fullscreen mode Exit fullscreen mode

Then running ping in another terminal:

ping 127.0.0.1 
Enter fullscreen mode Exit fullscreen mode

You should see this output in the window where you ran cargo xtask run

[2024-06-08T04:24:17Z INFO  hello_world] Waiting for Ctrl-C...
[2024-06-08T04:24:21Z INFO  hello_world] received a packet
[2024-06-08T04:24:21Z INFO  hello_world] received a packet
[2024-06-08T04:24:22Z INFO  hello_world] received a packet
...
Enter fullscreen mode Exit fullscreen mode

This programs functions in the same way as the first one, but
there are significant differences in the code.

It looks more like idiomatic rust with only one unsafe block in
the panic handler.

However looking at a dump of the byte code:

$ llvm-objdump --section=xdp  -S target/bpfel-unknown-none/debug/hello-world
Enter fullscreen mode Exit fullscreen mode

target/bpfel-unknown-none/debug/hello-world: file format elf64-bpf

Disassembly of section xdp:

0000000000000000 <hello_world>:
   0:   r6 = r1
   1:   r7 = 0
   2:   *(u32 *)(r10 - 4) = r7
   3:   r2 = r10
   4:   r2 += -4
   5:   r1 = 0 ll
   7:   call 1
   8:   if r0 == 0 goto +166 <LBB0_2>
   9:   *(u8 *)(r0 + 2) = r7
  10:   r2 = 11
  11:   *(u8 *)(r0 + 1) = r2
  12:   r1 = 1
  13:   *(u8 *)(r0 + 0) = r1
  14:   r3 = r0
  15:   r3 += 3
  16:   r4 = 0 ll
  18:   r5 = *(u8 *)(r4 + 0)
  19:   *(u8 *)(r3 + 0) = r5
  20:   r5 = *(u8 *)(r4 + 1)
  21:   *(u8 *)(r3 + 1) = r5
  22:   r5 = *(u8 *)(r4 + 2)
  23:   *(u8 *)(r3 + 2) = r5
  24:   r5 = *(u8 *)(r4 + 3)
  25:   *(u8 *)(r3 + 3) = r5
  26:   r5 = *(u8 *)(r4 + 4)
  27:   *(u8 *)(r3 + 4) = r5
  28:   r5 = *(u8 *)(r4 + 5)
  29:   *(u8 *)(r3 + 5) = r5
  30:   r5 = *(u8 *)(r4 + 6)
  31:   *(u8 *)(r3 + 6) = r5
  32:   r5 = *(u8 *)(r4 + 7)
  33:   *(u8 *)(r3 + 7) = r5
  34:   r5 = *(u8 *)(r4 + 8)
  35:   *(u8 *)(r3 + 8) = r5
  36:   r5 = *(u8 *)(r4 + 9)
  37:   *(u8 *)(r3 + 9) = r5
  38:   r5 = *(u8 *)(r4 + 10)
  39:   *(u8 *)(r3 + 10) = r5
  40:   r3 = 3
  41:   *(u8 *)(r0 + 18) = r3
  42:   *(u8 *)(r0 + 17) = r3
  43:   r3 = 2
  44:   *(u8 *)(r0 + 14) = r3
  45:   *(u8 *)(r0 + 20) = r7
  46:   *(u8 *)(r0 + 19) = r2
  47:   *(u8 *)(r0 + 16) = r7
  48:   *(u8 *)(r0 + 15) = r1
  49:   r3 = r0
  50:   r3 += 21
  51:   r5 = *(u8 *)(r4 + 0)
  52:   *(u8 *)(r3 + 0) = r5
  53:   r5 = *(u8 *)(r4 + 1)
  54:   *(u8 *)(r3 + 1) = r5
  55:   r5 = *(u8 *)(r4 + 2)
  56:   *(u8 *)(r3 + 2) = r5
  57:   r5 = *(u8 *)(r4 + 3)
  58:   *(u8 *)(r3 + 3) = r5
  59:   r5 = *(u8 *)(r4 + 4)
  60:   *(u8 *)(r3 + 4) = r5
  61:   r5 = *(u8 *)(r4 + 5)
  62:   *(u8 *)(r3 + 5) = r5
  63:   r5 = *(u8 *)(r4 + 6)
  64:   *(u8 *)(r3 + 6) = r5
  65:   r5 = *(u8 *)(r4 + 7)
  66:   *(u8 *)(r3 + 7) = r5
  67:   r5 = *(u8 *)(r4 + 8)
  68:   *(u8 *)(r3 + 8) = r5
  69:   r5 = *(u8 *)(r4 + 9)
  70:   *(u8 *)(r3 + 9) = r5
  71:   r5 = *(u8 *)(r4 + 10)
  72:   *(u8 *)(r3 + 10) = r5
  73:   *(u8 *)(r0 + 33) = r2
  74:   *(u8 *)(r0 + 34) = r7
  75:   r2 = 4
  76:   *(u8 *)(r0 + 32) = r2
  77:   r3 = r0
  78:   r3 += 35
  79:   r4 = 11 ll
  81:   r5 = *(u8 *)(r4 + 0)
  82:   *(u8 *)(r3 + 0) = r5
  83:   r5 = *(u8 *)(r4 + 1)
  84:   *(u8 *)(r3 + 1) = r5
  85:   r5 = *(u8 *)(r4 + 2)
  86:   *(u8 *)(r3 + 2) = r5
  87:   r5 = *(u8 *)(r4 + 3)
  88:   *(u8 *)(r3 + 3) = r5
  89:   r5 = *(u8 *)(r4 + 4)
  90:   *(u8 *)(r3 + 4) = r5
  91:   r5 = *(u8 *)(r4 + 5)
  92:   *(u8 *)(r3 + 5) = r5
  93:   r5 = *(u8 *)(r4 + 6)
  94:   *(u8 *)(r3 + 6) = r5
  95:   r5 = *(u8 *)(r4 + 7)
  96:   *(u8 *)(r3 + 7) = r5
  97:   r5 = *(u8 *)(r4 + 8)
  98:   *(u8 *)(r3 + 8) = r5
  99:   r5 = *(u8 *)(r4 + 9)
 100:   *(u8 *)(r3 + 9) = r5
 101:   r5 = *(u8 *)(r4 + 10)
 102:   *(u8 *)(r3 + 10) = r5
 103:   *(u8 *)(r0 + 56) = r1
 104:   r1 = 8
 105:   *(u8 *)(r0 + 54) = r1
 106:   r1 = 16
 107:   *(u8 *)(r0 + 49) = r1
 108:   *(u8 *)(r0 + 66) = r7
 109:   *(u8 *)(r0 + 63) = r7
 110:   *(u8 *)(r0 + 62) = r7
 111:   *(u8 *)(r0 + 61) = r7
 112:   *(u8 *)(r0 + 60) = r7
 113:   *(u8 *)(r0 + 59) = r7
 114:   *(u8 *)(r0 + 58) = r7
 115:   *(u8 *)(r0 + 57) = r7
 116:   *(u8 *)(r0 + 55) = r7
 117:   *(u8 *)(r0 + 52) = r7
 118:   *(u8 *)(r0 + 51) = r7
 119:   *(u8 *)(r0 + 50) = r7
 120:   *(u8 *)(r0 + 48) = r7
 121:   *(u8 *)(r0 + 47) = r2
 122:   r1 = 17
 123:   *(u8 *)(r0 + 65) = r1
 124:   *(u8 *)(r0 + 64) = r1
 125:   r1 = 6
 126:   *(u8 *)(r0 + 53) = r1
 127:   r1 = 5
 128:   *(u8 *)(r0 + 46) = r1
 129:   r1 = r0
 130:   r1 += 67
 131:   r2 = 22 ll
 133:   r3 = *(u8 *)(r2 + 0)
 134:   *(u8 *)(r1 + 0) = r3
 135:   r3 = *(u8 *)(r2 + 1)
 136:   *(u8 *)(r1 + 1) = r3
 137:   r3 = *(u8 *)(r2 + 2)
 138:   *(u8 *)(r1 + 2) = r3
 139:   r3 = *(u8 *)(r2 + 3)
 140:   *(u8 *)(r1 + 3) = r3
 141:   r3 = *(u8 *)(r2 + 4)
 142:   *(u8 *)(r1 + 4) = r3
 143:   r3 = *(u8 *)(r2 + 5)
 144:   *(u8 *)(r1 + 5) = r3
 145:   r3 = *(u8 *)(r2 + 6)
 146:   *(u8 *)(r1 + 6) = r3
 147:   r3 = *(u8 *)(r2 + 7)
 148:   *(u8 *)(r1 + 7) = r3
 149:   r3 = *(u8 *)(r2 + 8)
 150:   *(u8 *)(r1 + 8) = r3
 151:   r3 = *(u8 *)(r2 + 9)
 152:   *(u8 *)(r1 + 9) = r3
 153:   r3 = *(u8 *)(r2 + 10)
 154:   *(u8 *)(r1 + 10) = r3
 155:   r3 = *(u8 *)(r2 + 11)
 156:   *(u8 *)(r1 + 11) = r3
 157:   r3 = *(u8 *)(r2 + 12)
 158:   *(u8 *)(r1 + 12) = r3
 159:   r3 = *(u8 *)(r2 + 13)
 160:   *(u8 *)(r1 + 13) = r3
 161:   r3 = *(u8 *)(r2 + 14)
 162:   *(u8 *)(r1 + 14) = r3
 163:   r3 = *(u8 *)(r2 + 15)
 164:   *(u8 *)(r1 + 15) = r3
 165:   r3 = *(u8 *)(r2 + 16)
 166:   *(u8 *)(r1 + 16) = r3
 167:   r1 = r6
 168:   r2 = 0 ll
 170:   r3 = 4294967295 ll
 172:   r4 = r0
 173:   r5 = 84
 174:   call 25

0000000000000578 <LBB0_2>:
 175:   r0 = 2
 176:   exit
Enter fullscreen mode Exit fullscreen mode

So there's a lot here, we will defer a full explanation till later, note that there are now
two system calls on line 7 and line 174:

   7:   call 1  
   ...
 174:   call 25
Enter fullscreen mode Exit fullscreen mode

call 1 corresponds to map_lookup_elem in bpf.h
call 25 corresponding to `perf_event_output in bpf.h

Much of the rest of the byte code is setting up the stack to pass arguments.

Summary

  • Seen how to set up and deploy a basic hello world program
  • print out a message when a packet is received
  • compared two different hello world programs

Top comments (0)