dropbox / goebpf

Library to work with eBPF programs from Go
Other
1.13k stars 86 forks source link

Invalid BPF instruction while bpf map lookup after having called `htons` or `bpf_htons` #50

Closed martenwallewein closed 3 years ago

martenwallewein commented 3 years ago

Hey all, at first I want to thank for the great work you've done so far with this package, I really like the simple API and had a very easy quickstart with this.

At the moment, I struggle with having the error LoadElf() failed: loadPrograms() failed: Invalid BPF instruction (at 144): &{133 0 1 0 4294967295} with the following code:

BPF_MAP_DEF(rxcnt) = {
    .map_type = BPF_MAP_TYPE_PERCPU_ARRAY,
    .key_size = sizeof(__u32),
    .value_size = sizeof(__u64),
    .max_entries = 256,
};
BPF_MAP_ADD(rxcnt);

static inline void count_tx(__u32 protocol)
{
    __u64 *rxcnt_count;
    rxcnt_count = bpf_map_lookup_elem(&rxcnt, &protocol);

    if (rxcnt_count)
        *rxcnt_count += 1;
}

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

    size_t offset = sizeof(struct ether_header) +
                    sizeof(struct iphdr);

    if(data + offset > data_end) {
        return XDP_PASS; // too short
    }
    count_tx(0);
    const struct ether_header *eh = (const struct ether_header *)data;
    // FIXME: Without htons|bpf_htons it works as expected, with one of them we got
    // LoadElf() failed: loadPrograms() failed: Invalid BPF instruction (at 144): &{133 0 1 0 4294967295}
    if(eh->ether_type != htons(ETHERTYPE_IP)) {
       return XDP_PASS; // not IP
    }

    // FIXME: THis somehow depends on this instruction
    // If we have a map lookup after this htons instructions, the error occurs
    count_tx(1);
    return XDP_PASS;
}

Since I'm new to ebpf, not sure if I'm doing something wrong, but loading the respective ELF file with another approach (https://github.com/xdp-project/xdp-tutorial) works as expected. A full setup to reproduce this can be found here: https://github.com/martenwallewein/goepf-repro.

It would be nice if you could have a look at this if I'm doing something wrong or if there is a bug in the loadPrograms function.

Best, Marten

belyalov commented 3 years ago

Hey! :)

I've seen this few times while writing eBPF programs, it is always hard to debug such issues. In general, remember that eBPF is very very small subset of C language and whatever compiler produces not always accepted by eBPF VM.

So, few tips to avoid problems:

// unwind it to __u16 ether_type_reversed = htons(ETHERTYPE_IP); if (ether_type_reversed != ETHERTYPE_IP)

// or ever better - pre-process all constants

define ETHERTYPE_IP_REVERSED 0x0008

if(eh->ether_type != ETHERTYPE_IP_REVERSED)


- Inline all functions with `static inline __attribute__((always_inline))`, however I don't know the exact difference between `inline` and `always_inline` it helped me several times.
chenhengqi commented 3 years ago

Hello, you probably should use struct ethhdr instead of struct ether_header.

martenwallewein commented 3 years ago

Hey guys, thanks a lot for the replies. I will try to implement the methods by myself and follow you ideas of unwinding the code.