mirror of
https://git.proxmox.com/git/mirror_iproute2
synced 2025-10-12 17:04:56 +00:00

This work follows upon commit 6256f8c9e4
("tc, bpf: finalize eBPF
support for cls and act front-end") and takes up the idea proposed by
Hannes Frederic Sowa to spawn a shell (or any other command) that holds
generated eBPF map file descriptors.
File descriptors, based on their id, are being fetched from the same
unix domain socket as demonstrated in the bpf_agent, the shell spawned
via execvpe(2) and the map fds passed over the environment, and thus
are made available to applications in the fashion of std{in,out,err}
for read/write access, for example in case of iproute2's examples/bpf/:
# env | grep BPF
BPF_NUM_MAPS=3
BPF_MAP1=6 <- BPF_MAP_ID_QUEUE (id 1)
BPF_MAP0=5 <- BPF_MAP_ID_PROTO (id 0)
BPF_MAP2=7 <- BPF_MAP_ID_DROPS (id 2)
# ls -la /proc/self/fd
[...]
lrwx------. 1 root root 64 Apr 14 16:46 0 -> /dev/pts/4
lrwx------. 1 root root 64 Apr 14 16:46 1 -> /dev/pts/4
lrwx------. 1 root root 64 Apr 14 16:46 2 -> /dev/pts/4
[...]
lrwx------. 1 root root 64 Apr 14 16:46 5 -> anon_inode:bpf-map
lrwx------. 1 root root 64 Apr 14 16:46 6 -> anon_inode:bpf-map
lrwx------. 1 root root 64 Apr 14 16:46 7 -> anon_inode:bpf-map
The advantage (as opposed to the direct/native usage) is that now the
shell is map fd owner and applications can terminate and easily reattach
to descriptors w/o any kernel changes. Moreover, multiple applications
can easily read/write eBPF maps simultaneously.
To further allow users for experimenting with that, next step is to add
a small helper that can get along with simple data types, so that also
shell scripts can make use of bpf syscall, f.e to read/write into maps.
Generally, this allows for prepopulating maps, or any runtime altering
which could influence eBPF program behaviour (f.e. different run-time
classifications, skb modifications, ...), dumping of statistics, etc.
Reference: http://thread.gmane.org/gmane.linux.network/357471/focus=357860
Suggested-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Reviewed-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Acked-by: Alexei Starovoitov <ast@plumgrid.com>
148 lines
3.0 KiB
C
148 lines
3.0 KiB
C
/*
|
|
* e_bpf.c BPF exec proxy
|
|
*
|
|
* This program is free software; you can distribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
* Authors: Daniel Borkmann <daniel@iogearbox.net>
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include "utils.h"
|
|
|
|
#include "tc_util.h"
|
|
#include "tc_bpf.h"
|
|
|
|
#include "bpf_elf.h"
|
|
#include "bpf_scm.h"
|
|
|
|
#define BPF_DEFAULT_CMD "/bin/sh"
|
|
|
|
static char *argv_default[] = { BPF_DEFAULT_CMD, NULL };
|
|
|
|
static void explain(void)
|
|
{
|
|
fprintf(stderr, "Usage: ... bpf [ import UDS_FILE ] [ run CMD ]\n\n");
|
|
fprintf(stderr, "Where UDS_FILE provides the name of a unix domain socket file\n");
|
|
fprintf(stderr, "to import eBPF maps and the optional CMD denotes the command\n");
|
|
fprintf(stderr, "to be executed (default: \'%s\').\n", BPF_DEFAULT_CMD);
|
|
}
|
|
|
|
static int bpf_num_env_entries(void)
|
|
{
|
|
char **envp;
|
|
int num;
|
|
|
|
for (num = 0, envp = environ; *envp != NULL; envp++)
|
|
num++;
|
|
return num;
|
|
}
|
|
|
|
static int parse_bpf(struct exec_util *eu, int argc, char **argv)
|
|
{
|
|
char **argv_run = argv_default, **envp_run, *tmp;
|
|
int ret, i, env_old, env_num, env_map;
|
|
const char *bpf_uds_name = NULL;
|
|
int fds[BPF_SCM_MAX_FDS];
|
|
struct bpf_map_aux aux;
|
|
|
|
if (argc == 0)
|
|
return 0;
|
|
|
|
while (argc > 0) {
|
|
if (matches(*argv, "run") == 0) {
|
|
NEXT_ARG();
|
|
argv_run = argv;
|
|
break;
|
|
} else if (matches(*argv, "import") == 0 ||
|
|
matches(*argv, "imp") == 0) {
|
|
NEXT_ARG();
|
|
bpf_uds_name = *argv;
|
|
} else {
|
|
explain();
|
|
return -1;
|
|
}
|
|
|
|
argc--;
|
|
argv++;
|
|
}
|
|
|
|
if (!bpf_uds_name) {
|
|
fprintf(stderr, "bpf: No import parameter provided!\n");
|
|
explain();
|
|
return -1;
|
|
}
|
|
|
|
if (argv_run != argv_default && argc == 0) {
|
|
fprintf(stderr, "bpf: No run command provided!\n");
|
|
explain();
|
|
return -1;
|
|
}
|
|
|
|
memset(fds, 0, sizeof(fds));
|
|
memset(&aux, 0, sizeof(aux));
|
|
|
|
ret = bpf_recv_map_fds(bpf_uds_name, fds, &aux, ARRAY_SIZE(fds));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "bpf: Could not receive fds!\n");
|
|
return -1;
|
|
}
|
|
|
|
if (aux.num_ent == 0) {
|
|
envp_run = environ;
|
|
goto out;
|
|
}
|
|
|
|
env_old = bpf_num_env_entries();
|
|
env_num = env_old + aux.num_ent + 2;
|
|
env_map = env_old + 1;
|
|
|
|
envp_run = malloc(sizeof(*envp_run) * env_num);
|
|
if (!envp_run) {
|
|
fprintf(stderr, "bpf: No memory left to allocate env!\n");
|
|
goto err;
|
|
}
|
|
|
|
for (i = 0; i < env_old; i++)
|
|
envp_run[i] = environ[i];
|
|
|
|
ret = asprintf(&tmp, "BPF_NUM_MAPS=%u", aux.num_ent);
|
|
if (ret < 0)
|
|
goto err_free;
|
|
|
|
envp_run[env_old] = tmp;
|
|
|
|
for (i = env_map; i < env_num - 1; i++) {
|
|
ret = asprintf(&tmp, "BPF_MAP%u=%u",
|
|
aux.ent[i - env_map].id,
|
|
fds[i - env_map]);
|
|
if (ret < 0)
|
|
goto err_free_env;
|
|
|
|
envp_run[i] = tmp;
|
|
}
|
|
|
|
envp_run[env_num - 1] = NULL;
|
|
out:
|
|
return execvpe(argv_run[0], argv_run, envp_run);
|
|
|
|
err_free_env:
|
|
for (--i; i >= env_old; i--)
|
|
free(envp_run[i]);
|
|
err_free:
|
|
free(envp_run);
|
|
err:
|
|
for (i = 0; i < aux.num_ent; i++)
|
|
close(fds[i]);
|
|
return -1;
|
|
}
|
|
|
|
struct exec_util bpf_exec_util = {
|
|
.id = "bpf",
|
|
.parse_eopt = parse_bpf,
|
|
};
|