You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
6.0 KiB
258 lines
6.0 KiB
/* |
|
* 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; |
|
}
|
|
|