aya-rs / aya

Aya is an eBPF library for the Rust programming language, built with a focus on developer experience and operability.
https://aya-rs.dev/book/
Apache License 2.0
3.19k stars 286 forks source link

aya: Support TC-style map definitions #156

Open d0u9 opened 2 years ago

d0u9 commented 2 years ago

The Classifier BPF source contains a hashmap in maps section. It is referenced by a program in egress section as documented in tc-bpf manual.

However, when loading via Bpf::load_file(xx), I get an RelocationError saying:

RelocationError { function: "egress", error: SectionNotFound { section_index: 5, symbol_index: 4, symbol_name: Some("config_map") } }

This bpf object can be loaded via tc filter command successfully, and the map can be shown via bpftool map.

output of llvm-readelf -S:

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 000220 000073 00      0   0  1
  [ 2] .text             PROGBITS        0000000000000000 000040 000000 00  AX  0   0  4
  [ 3] classifier/egress PROGBITS        0000000000000000 000040 0000c8 00  AX  0   0  8
  [ 4] .relclassifier/egress REL         0000000000000000 000200 000010 10     10   3  8
  [ 5] maps              PROGBITS        0000000000000000 000108 000024 00  WA  0   0  4
  [ 6] license           PROGBITS        0000000000000000 00012c 000004 00  WA  0   0  1
  [ 7] .rodata.str1.16   PROGBITS        0000000000000000 000130 000010 01 AMS  0   0 16
  [ 8] .eh_frame         PROGBITS        0000000000000000 000140 000030 00   A  0   0  8
  [ 9] .rel.eh_frame     REL             0000000000000000 000210 000010 10     10   8  8
  [10] .symtab           SYMTAB          0000000000000000 000170 000090 18      1   3  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)
alessandrod commented 2 years ago

Can you show the definition of the map, and how it's referenced by the code?

d0u9 commented 2 years ago
#include "headers.h"
#include "helpers.h"
#include "defs.h"
#include "map_types/config.h"

typedef __u32   uint32_t;

struct tuple {
    long packets;
    long bytes;
};

#define BPF_MAP_ID_STATS        1 /* agent's map identifier */
#define BPF_MAX_MARK            256

struct bpf_elf_map SEC("maps") config_map = {
    .type           =       BPF_MAP_TYPE_ARRAY,
    .id             =       BPF_MAP_ID_STATS,
    .size_key       =       sizeof(uint32_t),
    .size_value     =       sizeof(struct tuple),
    .max_elem       =       BPF_MAX_MARK,
    .pinning        =       PIN_GLOBAL_NS,
};

static inline void cls_update_stats(const struct __sk_buff *skb,
                    uint32_t mark)
{
    struct tuple *tu;

    tu = bpf_map_lookup_elem(&config_map, &mark);
    if (likely(tu)) {
        __sync_fetch_and_add(&tu->packets, 1);
        __sync_fetch_and_add(&tu->bytes, skb->len);
    }
}

SEC("classifier/egress")
int cls_main(struct __sk_buff *skb)
{
    uint32_t mark = skb->mark;

    if (unlikely(mark >= BPF_MAX_MARK))
        return 0;

    cls_update_stats(skb, mark);

    return TC_H_MAKE(TC_H_ROOT, mark);
}

char __license[] SEC("license") = "GPL";

This sample comes from https://man7.org/linux/man-pages/man8/tc-bpf.8.html

alessandrod commented 2 years ago

But the error doesn't seem to be related to that program? The function/variable names don't match

d0u9 commented 2 years ago

@alessandrod I have updated the code above. My bpf code is a modified version of the sample in the tc-bpf manual in which only section names are changed.

Full error message:

RelocationError { function: "egress", error: SectionNotFound { section_index: 5, symbol_index: 6, symbol_name: Some("config_map") } }

readelf -S :

There are 10 section headers, starting at offset 0x280:

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 000208 000072 00      0   0  1
  [ 2] .text             PROGBITS        0000000000000000 000040 000000 00  AX  0   0  4
  [ 3] classifier/egress PROGBITS        0000000000000000 000040 000090 00  AX  0   0  8
  [ 4] .relclassifier/egress REL         0000000000000000 0001e8 000010 10      9   3  8
  [ 5] maps              PROGBITS        0000000000000000 0000d0 000024 00  WA  0   0  4
  [ 6] license           PROGBITS        0000000000000000 0000f4 000004 00  WA  0   0  1
  [ 7] .eh_frame         PROGBITS        0000000000000000 0000f8 000030 00   A  0   0  8
  [ 8] .rel.eh_frame     REL             0000000000000000 0001f8 000010 10      9   7  8
  [ 9] .symtab           SYMTAB          0000000000000000 000128 0000c0 18      1   5  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)
d0u9 commented 2 years ago

It works smoothly with tc command. I think the bpf side has no probelem.

dave-tucker commented 2 years ago

@d0u9 I've reproduced this and it's not (really) an aya issue. man tc-bpf refers to how iproute2 handles ebpf. the important difference here appears to be that iproute2 allows you to use bpf_elf_map for map definitions whereas libbpf and the kernel expect bpf_map_def. Any loader other than iproute2 will fail in a similar way - although we should probably error out sooner.

I can make the example load by replacing the map definition as follows:

struct bpf_map_def __section("maps") map_stats = {
        .type           =       BPF_MAP_TYPE_ARRAY,
        .key_size       =       sizeof(uint32_t),
        .value_size     =       sizeof(struct tuple),
        .max_entries    =       BPF_MAX_MARK,
        .map_flags      =       0,
};
alessandrod commented 2 years ago

I think the problem is that we don't handle "maps" as a valid map name (and we should)

alessandrod commented 2 years ago

struct bpf_elf_map SEC("maps") config_map = {

@d0u9 could you change this to be

struct bpf_elf_map SEC("maps/config_map") config_map = {

and see if that works?

d0u9 commented 2 years ago

@alessandrod Sorry for my late reply

see if that works?

I tested the case in which SEC("maps") changes toSEC("maps/config_map"). However, the map cannot be created by tc command as wished.

It seems that tc command only accepts the maps section as its default searching place for valid BPF maps.

aquarhead commented 2 years ago

Sorry for the late ping, but

It seems that tc command only accepts the maps section as its default searching place for valid BPF maps.

I believe that's the case, hence I had used https://github.com/aquarhead/protect-the-rabbit/blob/master/Makefile.toml#L12 (in redbpf era code, but still applies here) after much painful debugging and trying random things.

Not sure what would be the best solution here (I'm out of context for a really long time xD)