Ferrar
Ferrar

Reputation: 65

eBPF / XDP map not getting created

I have an implementation in BPF for XDP, wherein I specify five maps to be created as follows:

    struct bpf_map_def SEC("maps") servers = {
        .type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(struct ip_key),
        .value_size = sizeof(struct dest_info),
        .max_entries = MAX_SERVERS,
    };
    
    struct bpf_map_def SEC("maps") server_ips = {
        .type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(struct ip_key),
        .value_size = sizeof(struct server_ip_key),
        .max_entries = MAX_SERVERS,
    };
    
    struct bpf_map_def SEC("maps") client_addrs = {
        .type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(struct port_key),
        .value_size = sizeof(struct client_port_addr),
        .max_entries = MAX_CLIENTS,
    };
    
    struct bpf_map_def SEC("maps") stoc_port_maps = {
        .type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(struct port_key),
        .value_size = sizeof(struct port_map),
        .max_entries = MAX_FLOWS,
    };
    
    struct bpf_map_def SEC("maps") ctos_port_maps = {
        .type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(struct port_key),
        .value_size = sizeof(struct port_map),
        .max_entries = MAX_FLOWS,
    };

However, no matter what I do, the servers map is not getting created. When I run bpftool map show, I only get such output as the following:

root@balancer:/xdp# bpftool map list  
68: hash  name client_addrs  flags 0x0
        key 8B  value 16B  max_entries 4096  memlock 98304B
69: hash  name ctos_port_maps  flags 0x0
        key 8B  value 20B  max_entries 4096  memlock 131072B
70: hash  name server_ips  flags 0x0
        key 8B  value 8B  max_entries 512  memlock 8192B
73: hash  name stoc_port_maps  flags 0x0
        key 8B  value 20B  max_entries 4096  memlock 131072B
74: array  name xdp_lb_k.rodata  flags 0x480
        key 4B  value 50B  max_entries 1  memlock 4096B
        frozen
root@balancer:/xdp#

It is notable that each of the key or value structs have been padded to the closest multiple of eight bytes, and there are no compile or verifier errors. I am also running the program on docker containers. So far, I have tried moving the servers map definition around in my code, commenting out the other map definitions leaving only the servers definition active, changing the name to other combinations, and a few other minor changes but nothing has worked so far.

Please let me know if you would need any other portion of my code or information for a better analysis of the situation.


Appendix 1: I am compiling the object file using this Makefile rule:

xdp_lb_kern.o: xdp_lb_kern.c 
    clang -S \
        -target bpf \
        -D __BPF_TRACING__ \
        -I../../libbpf/src \
        -I../../custom-headers \
        -Wall \
        -Wno-unused-value \
        -Wno-pointer-sign \
        -Wno-compare-distinct-pointer-types \
        -O2 -emit-llvm -c -o ${@:.o=.ll} $<
    llc -march=bpf -filetype=obj -o $@ ${@:.o=.ll}

Then, in the container's environment, I load the program using this rule:

load_balancer:
    bpftool net detach xdpgeneric dev eth0
    rm -f /sys/fs/bpf/xdp_lb
    bpftool prog load xdp_lb_kern.o /sys/fs/bpf/xdp_lb
    bpftool net attach xdpgeneric pinned /sys/fs/bpf/xdp_lb dev eth0

The compilation process generates a .o and a .ll output file. The beginning lines of the .ll output file, where the map definitions are visible, are shown below:

; ModuleID = 'xdp_lb_kern.c'
source_filename = "xdp_lb_kern.c"
target datalayout = "e-m:e-p:64:64-i64:64-n32:64-S128"
target triple = "bpf"

%struct.bpf_map_def = type { i32, i32, i32, i32, i32 }
%struct.xdp_md = type { i32, i32, i32, i32, i32 }
%struct.ip_key = type { i32, i32 }
%struct.port_key = type { i16, [3 x i16] }
%struct.ethhdr = type { [6 x i8], [6 x i8], i16 }
%struct.iphdr = type { i8, i8, i16, i16, i16, i8, i8, i16, i32, i32 }

@servers = dso_local global %struct.bpf_map_def { i32 1, i32 8, i32 32, i32 512, i32 0 }, section "maps", align 4
@server_ips = dso_local global %struct.bpf_map_def { i32 1, i32 8, i32 8, i32 512, i32 0 }, section "maps", align 4
@client_addrs = dso_local global %struct.bpf_map_def { i32 1, i32 8, i32 16, i32 4096, i32 0 }, section "maps", align 4
@stoc_port_maps = dso_local global %struct.bpf_map_def { i32 1, i32 8, i32 20, i32 4096, i32 0 }, section "maps", align 4
@ctos_port_maps = dso_local global %struct.bpf_map_def { i32 1, i32 8, i32 20, i32 4096, i32 0 }, section "maps", align 4
@loadbal.____fmt = internal constant [24 x i8] c"balancer got something!\00", align 1
@_license = dso_local global [4 x i8] c"GPL\00", section "license", align 1
@process_packet.____fmt = internal constant [26 x i8] c"it's an ip packet from %x\00", align 1
@llvm.used = appending global [7 x i8*] [i8* getelementptr inbounds ([4 x i8], [4 x i8]* @_license, i32 0, i32 0), i8* bitcast (%struct.bpf_map_def* @client_addrs to i8*), i8* bitcast (%struct.bpf_map_def* @ctos_port_maps to i8*), i8* bitcast (i32 (%struct.xdp_md*)* @loadbal to i8*), i8* bitcast (%struct.bpf_map_def* @server_ips to i8*), i8* bitcast (%struct.bpf_map_def* @servers to i8*), i8* bitcast (%struct.bpf_map_def* @stoc_port_maps to i8*)], section "llvm.metadata"

; Function Attrs: nounwind
define dso_local i32 @loadbal(%struct.xdp_md* nocapture readonly %0) #0 section "xdp" {
  %2 = alloca %struct.ip_key, align 4
  %3 = alloca %struct.port_key, align 2
  %4 = alloca %struct.port_key, align 2
  %5 = getelementptr inbounds %struct.xdp_md, %struct.xdp_md* %0, i64 0, i32 1
  %6 = load i32, i32* %5, align 4, !tbaa !2
  %7 = zext i32 %6 to i64
  %8 = inttoptr i64 %7 to i8*
  %9 = getelementptr inbounds %struct.xdp_md, %struct.xdp_md* %0, i64 0, i32 0
  %10 = load i32, i32* %9, align 4, !tbaa !7

Upvotes: 0

Views: 1004

Answers (1)

Qeole
Qeole

Reputation: 9114

As per the discussion in the comments, the map is not created because it is not actually used in your eBPF code (not provided in the question).

As you realised yourself, the branch in your code that was calling the map was in fact unreachable. Based on that, it's likely that clang compiled out this portion of code, and that the map is not used in the resulting eBPF bytecode. When preparing to load your program, bpftool (libbpf) looks at what maps are necessary, and only creates the ones that are needed for your program. It may skip maps that are defined in the ELF file if no program uses them.

One hint here is that, if the program was effectively using the map, it couldn't load successfully if the map was missing: given that your program loads, the map would necessarily be present if it was needed. Note that bpftool prog show will show you the ids of the maps used by a program.

Upvotes: 2

Related Questions