/* * eBPF user space agent part * * Simple, _self-contained_ user space agent for the eBPF kernel * ebpf_prog.c program, which gets all map fds passed from tc via unix * domain socket in one transaction and can thus keep referencing * them from user space in order to read out (or possibly modify) * map data. Here, just as a minimal example to display counters. * * The agent only uses the bpf(2) syscall API to read or possibly * write to eBPF maps, it doesn't need to be aware of the low-level * bytecode parts and/or ELF parsing bits. * * ! For more details, see header comment in bpf_prog.c ! * * gcc bpf_agent.c -o bpf_agent -Wall -O2 * * For example, a more complex user space agent could run on each * host, reading and writing into eBPF maps used by tc classifier * and actions. It would thus allow for implementing a distributed * tc architecture, for example, which would push down central * policies into eBPF maps, and thus altering run-time behaviour. * * -- Happy eBPF hacking! ;) */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <stdint.h> #include <assert.h> #include <sys/un.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> /* Just some misc macros as min(), offsetof(), etc. */ #include "../../include/utils.h" /* Common code from fd passing. */ #include "../../include/bpf_scm.h" /* Common, shared definitions with ebpf_prog.c */ #include "bpf_shared.h" /* Mini syscall wrapper */ #include "bpf_sys.h" static void bpf_dump_drops(int fd) { int cpu, max; max = sysconf(_SC_NPROCESSORS_ONLN); printf(" `- number of drops:"); for (cpu = 0; cpu < max; cpu++) { long drops; assert(bpf_lookup_elem(fd, &cpu, &drops) == 0); printf("\tcpu%d: %5ld", cpu, drops); } printf("\n"); } static void bpf_dump_queue(int fd) { /* Just for the same of the example. */ int max_queue = 4, i; printf(" | nic queues:"); for (i = 0; i < max_queue; i++) { struct count_queue cq; int ret; memset(&cq, 0, sizeof(cq)); ret = bpf_lookup_elem(fd, &i, &cq); assert(ret == 0 || (ret < 0 && errno == ENOENT)); printf("\tq%d:[pkts: %ld, mis: %ld]", i, cq.total, cq.mismatch); } printf("\n"); } static void bpf_dump_proto(int fd) { uint8_t protos[] = { IPPROTO_TCP, IPPROTO_UDP, IPPROTO_ICMP }; char *names[] = { "tcp", "udp", "icmp" }; int i; printf(" ` protos:"); for (i = 0; i < ARRAY_SIZE(protos); i++) { struct count_tuple ct; int ret; memset(&ct, 0, sizeof(ct)); ret = bpf_lookup_elem(fd, &protos[i], &ct); assert(ret == 0 || (ret < 0 && errno == ENOENT)); printf("\t%s:[pkts: %ld, bytes: %ld]", names[i], ct.packets, ct.bytes); } printf("\n"); } static void bpf_dump_map_data(int *tfd) { int i; for (i = 0; i < 30; i++) { const int period = 5; printf("data, period: %dsec\n", period); bpf_dump_drops(tfd[BPF_MAP_ID_DROPS]); bpf_dump_queue(tfd[BPF_MAP_ID_QUEUE]); bpf_dump_proto(tfd[BPF_MAP_ID_PROTO]); sleep(period); } } static void bpf_info_loop(int *fds, struct bpf_map_aux *aux) { int i, tfd[BPF_MAP_ID_MAX]; printf("ver: %d\nobj: %s\ndev: %lu\nino: %lu\nmaps: %u\n", aux->uds_ver, aux->obj_name, aux->obj_st.st_dev, aux->obj_st.st_ino, aux->num_ent); for (i = 0; i < aux->num_ent; i++) { printf("map%d:\n", i); printf(" `- fd: %u\n", fds[i]); printf(" | serial: %u\n", aux->ent[i].id); printf(" | type: %u\n", aux->ent[i].type); printf(" | max elem: %u\n", aux->ent[i].max_elem); printf(" | size key: %u\n", aux->ent[i].size_key); printf(" ` size val: %u\n", aux->ent[i].size_value); tfd[aux->ent[i].id] = fds[i]; } bpf_dump_map_data(tfd); } static void bpf_map_get_from_env(int *tfd) { char key[64], *val; int i; for (i = 0; i < BPF_MAP_ID_MAX; i++) { memset(key, 0, sizeof(key)); snprintf(key, sizeof(key), "BPF_MAP%d", i); val = getenv(key); assert(val != NULL); tfd[i] = atoi(val); } } static int bpf_map_set_recv(int fd, int *fds, struct bpf_map_aux *aux, unsigned int entries) { struct bpf_map_set_msg msg; int *cmsg_buf, min_fd, i; char *amsg_buf, *mmsg_buf; cmsg_buf = bpf_map_set_init(&msg, NULL, 0); amsg_buf = (char *)msg.aux.ent; mmsg_buf = (char *)&msg.aux; for (i = 0; i < entries; i += min_fd) { struct cmsghdr *cmsg; int ret; min_fd = min(BPF_SCM_MAX_FDS * 1U, entries - i); bpf_map_set_init_single(&msg, min_fd); ret = recvmsg(fd, &msg.hdr, 0); if (ret <= 0) return ret ? : -1; cmsg = CMSG_FIRSTHDR(&msg.hdr); if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS) return -EINVAL; if (msg.hdr.msg_flags & MSG_CTRUNC) return -EIO; min_fd = (cmsg->cmsg_len - sizeof(*cmsg)) / sizeof(fd); if (min_fd > entries || min_fd <= 0) return -1; memcpy(&fds[i], cmsg_buf, sizeof(fds[0]) * min_fd); memcpy(&aux->ent[i], amsg_buf, sizeof(aux->ent[0]) * min_fd); memcpy(aux, mmsg_buf, offsetof(struct bpf_map_aux, ent)); if (i + min_fd == aux->num_ent) break; } return 0; } int main(int argc, char **argv) { int fds[BPF_SCM_MAX_FDS]; struct bpf_map_aux aux; struct sockaddr_un addr; int fd, ret, i; /* When arguments are being passed, we take it as a path * to a Unix domain socket, otherwise we grab the fds * from the environment to demonstrate both possibilities. */ if (argc == 1) { int tfd[BPF_MAP_ID_MAX]; bpf_map_get_from_env(tfd); bpf_dump_map_data(tfd); return 0; } fd = socket(AF_UNIX, SOCK_DGRAM, 0); if (fd < 0) { fprintf(stderr, "Cannot open socket: %s\n", strerror(errno)); exit(1); } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, argv[argc - 1], sizeof(addr.sun_path)); ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) { fprintf(stderr, "Cannot bind to socket: %s\n", strerror(errno)); exit(1); } memset(fds, 0, sizeof(fds)); memset(&aux, 0, sizeof(aux)); ret = bpf_map_set_recv(fd, fds, &aux, BPF_SCM_MAX_FDS); if (ret >= 0) bpf_info_loop(fds, &aux); for (i = 0; i < aux.num_ent; i++) close(fds[i]); close(fd); return 0; }