clinuxebpfxdp-bpflibbpf

How to use BPF_MAP_TYPE_CPUMAP?


I'm working on a BPF program where I need to update a BPF map with a new element. However, I'm encountering an error that says Cannot allocate memory. Here is the relevant part of my code:

I want to build a simple demo of BPF_MAP_TYPE_CPUMAP and to redirect every packet to cpu0 and print log to pipe.

For kernel:

// cpu_map_kernel.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
struct {
    __uint(type, BPF_MAP_TYPE_CPUMAP);
    __uint(key_size, sizeof(__u32));
    __uint(value_size, sizeof(struct bpf_cpumap_val));
    __uint(pinning, LIBBPF_PIN_BY_NAME);
} cpu_map SEC(".maps");


SEC("xdp")
int xdp_main(struct xdp_md *ctx)
{
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;

    struct ethhdr *eth = data;
    if (eth + 1 > data_end)
        return XDP_PASS;

    struct iphdr *ip = data + sizeof(*eth);
    if (ip + 1 > data_end)
        return XDP_PASS;
    if (ip->protocol != IPPROTO_UDP)
        return XDP_PASS;

    __u32 cpu_dest = 0;
    return bpf_redirect_map(&cpu_map, cpu_dest, 0);
}

SEC("xdp")
int just_pass(struct xdp_md *ctx)
{
    bpf_printk("Just pass by cpu0\n");
    return XDP_PASS;
}

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

For user space:

// cpu_map_user.c
#include <stdio.h>
#include <sys/resource.h>
#include <linux/limits.h>
#include <signal.h>
#include <unistd.h>
#include <linux/if_link.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>

#include "cpu_map.skel.h"

static volatile int quit = 0;

void sigint_handler(int signum)
{
    quit = 1;
}

int main()
{
    struct cpu_map_bpf *skel;
    __u32 xdp_flags = 0;
    int ret;
    int n_cpus = libbpf_num_possible_cpus();
    printf("Current libbpf_num_possible_cpus: %d\n", n_cpus);

    // By default, 8MiB of memory can be locked to RAM
    struct rlimit r = { RLIM_INFINITY, RLIM_INFINITY };
    if (setrlimit(RLIMIT_MEMLOCK, &r)) {
        perror("setrlimit failed");
        return 1;
    }

    struct rlimit cur = {};
    getrlimit(RLIMIT_MEMLOCK, &cur);
    printf("Current Mem limit: cur %lld, max %lld\n", cur.rlim_cur, cur.rlim_max);

    skel = cpu_map_bpf__open();
    if (!skel) {
        fprintf(stderr, "Failed to open BPF skeleton\n");
        goto end_destroy;
    }

    if (bpf_map__set_max_entries(skel->maps.cpu_map, n_cpus) < 0) {
        fprintf(stderr, "Failed to set max entries for cpu_map map: %s", strerror(errno));
        goto end_destroy;
    }

    ret = cpu_map_bpf__load(skel);
    if (ret < 0) {
        fprintf(stderr, "Failed to cpu_map_bpf__load: %s\n", strerror(errno));
        goto end_destroy;
    }

    int cpu_map_fd = bpf_map__fd(skel->maps.cpu_map);
    int just_pass_fd = bpf_program__fd(skel->progs.just_pass);
    struct bpf_cpumap_val val = {
        .qsize = 1024,
        .bpf_prog.fd = just_pass_fd,
    };
    __u32 cpu = 0;
    ret = bpf_map_update_elem(cpu_map_fd, &cpu, &val, BPF_EXIST);
    if (ret < 0) {
        fprintf(stderr, "Failed to update cpu_map: %s\n", strerror(errno));
        goto end_destroy;
    } else {
        fprintf(stderr, "Successfully updated cpu_map\n");
        return 0;
    }

    int LO_IFINDEX = 1;
    int cpu_prog_fd = bpf_program__fd(skel->progs.xdp_main);
    if (bpf_xdp_attach(LO_IFINDEX, cpu_prog_fd, xdp_flags, NULL) < 0) {
        fprintf(stderr, "Error: bpf_set_link_xdp_fd failed for interface %d\n", LO_IFINDEX);
        goto end_destroy;
    } else {
        printf("Main BPF program attached to XDP on interface %d\n", LO_IFINDEX);
    }

    printf("Successfully started!\n");
    signal(SIGINT, sigint_handler);
    signal(SIGINT, sigint_handler);
    signal(SIGTERM, sigint_handler);
    while (!quit)
        usleep(100000);
    printf("Exiting...\n");
    goto end_destroy;

end_destroy:
    cpu_map_bpf__destroy(skel);
    printf("End of the program\n");
    return 0;
}

It fails with error:

Current libbpf_num_possible_cpus: 24
Current Mem limit: cur -1, max -1
Failed to update cpu_map: Cannot allocate memory
End of the program

What is the meaning of Cannot allocate memory (with id ENOMEM)? How can I fix it?

I have tried to set RLIMIT_MEMLOCK to RLIM_INFINITY, but it still panicked on bpf_map_update_elem(map_progs_xdp_fd, &XDP_PROG_SET_CACHE_ENTRY, &set_cache_entry_fd, 0);


Solution

  • Can't be 100% certain, but what I suspect is happening is that you are getting the -ENOMEM from this location.

    if (cpumap_value.qsize == 0) {
      rcpu = NULL; /* Same as deleting */
    } else {
      /* Updating qsize cause re-allocation of bpf_cpu_map_entry */
      rcpu = __cpu_map_entry_alloc(map, &cpumap_value, key_cpu);
      if (!rcpu)
          return -ENOMEM;
    }
    

    It seems to be a case of wrong error code by the kernel so a bit misleading. The code assumes that if __cpu_map_entry_alloc fails that its an allocation error (since that is what the code originally did, but it has been updated).

    However, we also return NULL from this function in this case:

    if (fd > 0 && __cpu_map_load_bpf_program(rcpu, map, fd))
      goto free_ptr_ring;
    

    Which we do enter since you are specifying a secondary XDP program.

    This is __cpu_map_load_bpf_program:

    static int __cpu_map_load_bpf_program(struct bpf_cpu_map_entry *rcpu,
                        struct bpf_map *map, int fd)
    {
      struct bpf_prog *prog;
    
      prog = bpf_prog_get_type(fd, BPF_PROG_TYPE_XDP);
      if (IS_ERR(prog))
          return PTR_ERR(prog);
    
      if (prog->expected_attach_type != BPF_XDP_CPUMAP ||
          !bpf_prog_map_compatible(map, prog)) {
          bpf_prog_put(prog);
          return -EINVAL;
      }
    
      rcpu->value.bpf_prog.id = prog->aux->id;
      rcpu->prog = prog;
    
      return 0;
    }
    

    As you can see it checks that the specified program is XDP, is loaded with the BPF_XDP_CPUMAP attach type and is compatible.

    Your just_pass program seems to be loaded without this special attach type. According to the libbpf ELF section naming convention. It should be in the xdp/cpumap section for it to be loaded with that attach type.

    __cpu_map_load_bpf_program even tries to return -EINVAL but it gets discarded and replaced with -ENOMEM instead, hence the odd error code.