Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of XDP support #215

Merged
merged 4 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions examples/xdp.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include "maps.bpf.h"

#define ETH_P_IPV6 0x86DD
#define ETH_P_IP 0x0800

struct packet_key_t {
u16 eth_type;
u16 proto;
u16 port;
};

struct hdr_cursor {
void *pos;
};

struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 1024);
__type(key, struct packet_key_t);
__type(value, u64);
} xdp_incoming_packets_total SEC(".maps");

// Primitive header extraction macros. See xdp-tutorial repo for more robust parsers:
// * https://github.com/xdp-project/xdp-tutorial/blob/master/common/parsing_helpers.h
#define parse_args struct hdr_cursor *cursor, void *data_end, struct
#define parse_header(type) static bool parse_##type(parse_args type **hdr) { \
size_t offset = sizeof(**hdr); \
\
if (cursor->pos + offset > data_end) { \
return false; \
} \
\
*hdr = cursor->pos; \
cursor->pos += offset; \
\
return true; \
}

parse_header(ethhdr)
parse_header(iphdr)
parse_header(ipv6hdr)
parse_header(tcphdr)
parse_header(udphdr)

static int xdp_trace(struct xdp_md *ctx) {
void *data_end = (void *) (long) ctx->data_end;
void *data = (void *) (long) ctx->data;
struct packet_key_t key = {};
struct hdr_cursor cursor = { .pos = data };
struct ethhdr *eth_hdr;
struct iphdr *ip_hdr;
struct ipv6hdr *ipv6_hdr;
struct udphdr *udp_hdr;
struct tcphdr *tcp_hdr;

if (!parse_ethhdr(&cursor, data_end, &eth_hdr)) {
return XDP_PASS;
}

key.eth_type = bpf_ntohs(eth_hdr->h_proto);

switch (eth_hdr->h_proto) {
case bpf_htons(ETH_P_IP):
if (!parse_iphdr(&cursor, data_end, &ip_hdr)) {
return XDP_PASS;
}

key.proto = ip_hdr->protocol;
break;
case bpf_htons(ETH_P_IPV6):
if (!parse_ipv6hdr(&cursor, data_end, &ipv6_hdr)) {
return XDP_PASS;
}

key.proto = ipv6_hdr->nexthdr;
break;
}

switch (key.proto) {
case IPPROTO_TCP:
if (!parse_tcphdr(&cursor, data_end, &tcp_hdr)) {
return XDP_PASS;
}

key.port = bpf_ntohs(tcp_hdr->dest);
break;
case IPPROTO_UDP:
if (!parse_udphdr(&cursor, data_end, &udp_hdr)) {
return XDP_PASS;
}

key.port = bpf_ntohs(udp_hdr->dest);
break;
}

// Skip ephemeral port range to keep metrics tidy
if (key.port >= 32768) {
return XDP_PASS;
}

increment_map(&xdp_incoming_packets_total, &key, 1);

return XDP_PASS;
}

SEC("xdp/lo")
int trace_lo(struct xdp_md *ctx) {
return xdp_trace(ctx);
}

char LICENSE[] SEC("license") = "GPL";
26 changes: 26 additions & 0 deletions examples/xdp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
metrics:
counters:
- name: xdp_incoming_packets_total
help: Incoming packets going through xdp by protocol and port
labels:
- name: ip
size: 2
decoders:
- name: uint
- name: static_map
static_map:
2048: ipv4
34525: ipv6
- name: protocol
size: 2
decoders:
- name: uint
- name: static_map
static_map:
1: icmp
6: tcp
17: udp
- name: dst_port
size: 2
decoders:
- name: uint
4 changes: 4 additions & 0 deletions exporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ func (e *Exporter) Attach() error {
if err != nil {
return fmt.Errorf("error registering libbpf handlers: %v", err)
}
err = registerXDPHandler()
if err != nil {
return fmt.Errorf("error registering xdp handlers: %v", err)
}

for _, cfg := range e.configs {
if _, ok := e.modules[cfg.Name]; ok {
Expand Down
6 changes: 3 additions & 3 deletions exporter/perf_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import (
"golang.org/x/sys/unix"
)

var libbpf_prog_handlers []int
var libbpfPerfHandlers []int

func registerHandlers() error {
if libbpf_prog_handlers != nil {
if libbpfPerfHandlers != nil {
return nil
}

Expand All @@ -42,7 +42,7 @@ func registerHandlers() error {
return fmt.Errorf("error registering prog handler: %s", unix.ErrnoName(syscall.Errno(handler)))
}

libbpf_prog_handlers = append(libbpf_prog_handlers, int(handler))
libbpfPerfHandlers = append(libbpfPerfHandlers, int(handler))

return nil
}
Expand Down
64 changes: 64 additions & 0 deletions exporter/xdp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package exporter

/*
#include <stdlib.h>
#include <bpf/libbpf.h>

extern int attachXDPCallback(const struct bpf_program *prog,
long cookie,
struct bpf_link **link);
*/
import "C"

import (
"fmt"
"net"
"strings"
"syscall"
"unsafe"

"github.com/aquasecurity/libbpfgo"
"golang.org/x/sys/unix"
)

var libbpfXDPHandlers []int

func registerXDPHandler() error {
if libbpfXDPHandlers != nil {
return nil
}

name := C.CString("xdp/")
defer C.free(unsafe.Pointer(name))

opts := C.struct_libbpf_prog_handler_opts{}
opts.sz = C.sizeof_struct_libbpf_prog_handler_opts
opts.prog_attach_fn = C.libbpf_prog_attach_fn_t(C.attachXDPCallback)

handler := C.libbpf_register_prog_handler(name, uint32(libbpfgo.BPFProgTypeXdp), uint32(libbpfgo.BPFAttachTypeXDP), &opts)
if handler < 0 {
return fmt.Errorf("error registering prog handler: %s", unix.ErrnoName(syscall.Errno(handler)))
}

libbpfXDPHandlers = append(libbpfXDPHandlers, int(handler))

return nil
}

func attachXDP(prog *C.struct_bpf_program) ([]*C.struct_bpf_link, error) {
name := C.GoString(C.bpf_program__name(prog))
section := C.GoString(C.bpf_program__section_name(prog))
device := strings.TrimPrefix(section, "xdp/")

iface, err := net.InterfaceByName(device)
if err != nil {
return nil, fmt.Errorf("failed to find device %q for program %q: %v", device, name, err)
}

link, err := C.bpf_program__attach_xdp(prog, C.int(iface.Index))
if link == nil {
return nil, fmt.Errorf("failed to attach xdp on device %q for program %s: %v\n", device, name, err)
}

return []*C.struct_bpf_link{link}, nil
}
27 changes: 27 additions & 0 deletions exporter/xdp_cb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package exporter

import "C"

import (
"log"
"syscall"
"unsafe"
)

// These callbacks need to be in a separate file to avoid multiple definitions error

//export attachXDPCallback
func attachXDPCallback(prog unsafe.Pointer, cookie C.long, link *unsafe.Pointer) C.int {
program := (*C.struct_bpf_program)(prog)

links, err := attachXDP(program)
if err != nil {
log.Printf("Error attaching XDP: %v", err)
return C.int(syscall.EINVAL)
}

// Use the first link as we need to return something
*link = unsafe.Pointer(&links[0])

return C.int(0)
}