Search code examples
clinuxxdp-bpf

XDP program not capturing all ingress packets


The following XDP program does not capture all ingress XDP packets. I store the source IP in a hash table as the key and value as the number of times that IP is seen.

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <arpa/inet.h>

struct addr_desc_struct {
    __u32 src_ip;
};

struct bpf_map_def SEC("maps") addr_map = {
    .type = BPF_MAP_TYPE_LRU_HASH,
    .key_size = sizeof(struct addr_desc_struct),
    .value_size = sizeof(long),
    .max_entries = 4096
};

SEC("collect_ips")
int xdp_ip_stats_prog(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    struct iphdr *iph = data + sizeof(struct ethhdr);

    if (data_end >= (void *) (eth + sizeof(struct ethhdr))) {
        if (eth->h_proto == htons(ETH_P_IP)) {
            struct addr_desc_struct addr_desc = {.src_ip = iph->saddr};
            long init_val = 1;
            long *value = bpf_map_lookup_elem(&addr_map, &addr_desc);

            if (value) {
                __sync_fetch_and_add(value, 1);
            } else {
                bpf_map_update_elem(&addr_map, &addr_desc, &init_val, BPF_ANY);
            }
        }
    }

    return XDP_PASS;
}

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

Setup:

macOS host, VirtualBox guest running Lubuntu. I have created a 'host only adapter' on the VM. Both the VM and macOS have an interface on the 192.168.56.x network, with IPs 192.168.56.102 and 192.168.56.1, respectively. This XDP program loads successfully on the VM interface using the xdp-loader program.

Test #1:

Run a HTTP server on the VM on IP 192.168.56.102. Curl this IP from macOS.

Observation: XDP program does not capture any packets.

Test #2:

Run a HTTP server on macOS on the 192.168.56.1. Curl this IP from the VM.

Observation: XDP program captures some of the packets sent from macOS to the VM. Wireshark indicates more packets should have been received by XDP.

Test #3:

SSH from macOS into 192.168.56.102 on the VM.

Observation: No packets captured by XDP

In all tests I should expect to see packets processed by the XDP program.


Solution

  • Here's the updated function, as per Andrew's comments. Main issue was with if (data_end >= (void *) (eth + sizeof(struct ethhdr))), which results in overshooting the packet. I should have been casting to char *. Using data by itself is not as per standard, but works in clang because it adds bytes to a void *, not bytes*sizeof(some pointer).

    SEC("collect_ips")
    int xdp_ip_stats_prog(struct xdp_md *ctx) {
        void *data_end = (void *)(long)ctx->data_end;
        void *data = (void *)(long)ctx->data;
        struct ethhdr *eth = data;
        struct iphdr *iph = (char *) data + sizeof(struct ethhdr);
    
        // Without adding sizeof(struct iphdr) results in xdp-loader complaining about "invalid access to packet"
        if (data_end >= (void *) ((char *) data + sizeof(struct ethhdr) + sizeof(struct iphdr))) {
            if (eth->h_proto == htons(ETH_P_IP)) {
                struct addr_desc_struct addr_desc = {.src_ip = iph->saddr};
                long init_val = 1;
                long *value = bpf_map_lookup_elem(&addr_map, &addr_desc);
    
                if (value) {
                    __sync_fetch_and_add(value, 1);
                } else {
                    bpf_map_update_elem(&addr_map, &addr_desc, &init_val, BPF_ANY);
                }
            }
        }
    
        return XDP_PASS;
    }