2021-01-18 13:13:51 +01:00
|
|
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
2021-03-22 12:23:27 +01:00
|
|
|
static const char *__doc__ =
|
|
|
|
"Passive Ping - monitor flow RTT based on TCP timestamps";
|
|
|
|
|
2020-12-11 19:25:35 +01:00
|
|
|
#include <bpf/bpf.h>
|
|
|
|
#include <bpf/libbpf.h>
|
2021-01-11 18:44:18 +01:00
|
|
|
#include <linux/if_link.h>
|
2021-01-07 18:14:27 +01:00
|
|
|
#include <net/if.h> // For if_nametoindex
|
|
|
|
#include <arpa/inet.h> // For inet_ntoa and ntohs
|
2020-12-11 19:25:35 +01:00
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <unistd.h>
|
2021-03-22 12:23:27 +01:00
|
|
|
#include <getopt.h>
|
2020-12-11 19:25:35 +01:00
|
|
|
#include <stdbool.h>
|
2021-03-22 12:23:27 +01:00
|
|
|
#include <limits.h>
|
2020-12-11 19:25:35 +01:00
|
|
|
#include <signal.h> // For detecting Ctrl-C
|
|
|
|
#include <sys/resource.h> // For setting rlmit
|
2021-01-26 18:34:23 +01:00
|
|
|
#include <sys/wait.h>
|
2021-02-02 14:28:42 +01:00
|
|
|
#include <sys/stat.h>
|
2020-12-11 19:25:35 +01:00
|
|
|
#include <time.h>
|
2021-01-07 18:14:27 +01:00
|
|
|
#include <pthread.h>
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2021-01-07 18:30:53 +01:00
|
|
|
#include "pping.h" //key and value structs for the ts_start map
|
2020-12-11 19:25:35 +01:00
|
|
|
|
2021-01-26 18:34:23 +01:00
|
|
|
#define NS_PER_SECOND 1000000000UL
|
|
|
|
#define NS_PER_MS 1000000UL
|
|
|
|
|
2021-01-07 18:30:53 +01:00
|
|
|
#define TCBPF_LOADER_SCRIPT "./bpf_egress_loader.sh"
|
2021-03-02 17:40:51 +01:00
|
|
|
#define PINNED_DIR "/sys/fs/bpf/pping"
|
2021-01-07 18:30:53 +01:00
|
|
|
#define PPING_XDP_OBJ "pping_kern_xdp.o"
|
|
|
|
#define PPING_TCBPF_OBJ "pping_kern_tc.o"
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2020-12-11 19:25:35 +01:00
|
|
|
#define XDP_FLAGS XDP_FLAGS_UPDATE_IF_NOEXIST
|
2021-01-26 18:34:23 +01:00
|
|
|
|
|
|
|
#define TS_MAP "ts_start"
|
2021-03-09 17:50:27 +01:00
|
|
|
#define FLOW_MAP "flow_state"
|
2021-01-27 12:16:11 +01:00
|
|
|
#define MAP_CLEANUP_INTERVAL \
|
|
|
|
(1 * NS_PER_SECOND) // Clean timestamp map once per second
|
|
|
|
#define TIMESTAMP_LIFETIME \
|
2021-03-09 17:50:27 +01:00
|
|
|
(10 * NS_PER_SECOND) // Clear out packet timestamps if they're over 10 seconds
|
|
|
|
#define FLOW_LIFETIME \
|
|
|
|
(300 * NS_PER_SECOND) // Clear out flows if they're inactive over 300 seconds
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2021-03-22 12:23:27 +01:00
|
|
|
#define DEFAULT_RATE_LIMIT \
|
|
|
|
(100 * NS_PER_MS) // Allow one timestamp entry per flow every 100 ms
|
|
|
|
|
2021-01-26 18:34:23 +01:00
|
|
|
#define PERF_BUFFER "rtt_events"
|
2021-01-07 18:14:27 +01:00
|
|
|
#define PERF_BUFFER_PAGES 64 // Related to the perf-buffer size?
|
|
|
|
#define PERF_POLL_TIMEOUT_MS 100
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2021-01-11 18:44:18 +01:00
|
|
|
#define MAX_PATH_LEN 1024
|
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
/*
|
|
|
|
* BPF implementation of pping using libbpf
|
2021-01-11 18:44:18 +01:00
|
|
|
* Uses TC-BPF for egress and XDP for ingress
|
2021-01-18 13:13:51 +01:00
|
|
|
* - On egrees, packets are parsed for TCP TSval,
|
|
|
|
* if found added to hashmap using flow+TSval as key,
|
|
|
|
* and current time as value
|
|
|
|
* - On ingress, packets are parsed for TCP TSecr,
|
|
|
|
* if found looksup hashmap using reverse-flow+TSecr as key,
|
|
|
|
* and calculates RTT as different between now map value
|
|
|
|
* - Calculated RTTs are pushed to userspace
|
|
|
|
* (together with the related flow) and printed out
|
2021-01-11 18:44:18 +01:00
|
|
|
*/
|
|
|
|
|
2021-01-26 18:34:23 +01:00
|
|
|
// Structure to contain arguments for clean_map (for passing to pthread_create)
|
2021-01-07 18:14:27 +01:00
|
|
|
struct map_cleanup_args {
|
2021-03-09 17:50:27 +01:00
|
|
|
int packet_map_fd;
|
|
|
|
int flow_map_fd;
|
2021-01-07 18:14:27 +01:00
|
|
|
};
|
|
|
|
|
2020-12-11 19:25:35 +01:00
|
|
|
static volatile int keep_running = 1;
|
|
|
|
|
2021-03-22 12:23:27 +01:00
|
|
|
static const struct option long_options[] = {
|
|
|
|
{ "help", no_argument, NULL, 'h' },
|
|
|
|
{ "interface", required_argument, NULL, 'i' },
|
|
|
|
{ "rate-limit", required_argument, NULL, 'r' },
|
|
|
|
{ 0, 0, NULL, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copied from Jesper Dangaaard Brouer's traffic-pacing-edt example
|
|
|
|
*/
|
|
|
|
static void print_usage(char *argv[])
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
printf("\nDOCUMENTATION:\n%s\n", __doc__);
|
|
|
|
printf("\n");
|
|
|
|
printf(" Usage: %s (options-see-below)\n", argv[0]);
|
|
|
|
printf(" Listing options:\n");
|
|
|
|
for (i = 0; long_options[i].name != 0; i++) {
|
|
|
|
printf(" --%-12s", long_options[i].name);
|
|
|
|
if (long_options[i].flag != NULL)
|
|
|
|
printf(" flag (internal value:%d)",
|
|
|
|
*long_options[i].flag);
|
|
|
|
else
|
|
|
|
printf(" short-option: -%c",
|
|
|
|
long_options[i].val);
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
2021-01-07 18:14:27 +01:00
|
|
|
void abort_program(int sig)
|
2020-12-11 19:25:35 +01:00
|
|
|
{
|
2021-01-18 13:13:51 +01:00
|
|
|
keep_running = 0;
|
2020-12-11 19:25:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static int set_rlimit(long int lim)
|
|
|
|
{
|
2021-01-18 13:13:51 +01:00
|
|
|
struct rlimit rlim = {
|
|
|
|
.rlim_cur = lim,
|
|
|
|
.rlim_max = lim,
|
|
|
|
};
|
2020-12-11 19:25:35 +01:00
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
return !setrlimit(RLIMIT_MEMLOCK, &rlim) ? 0 : -errno;
|
2020-12-11 19:25:35 +01:00
|
|
|
}
|
2021-01-11 18:44:18 +01:00
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
static int bpf_obj_open(struct bpf_object **obj, const char *obj_path,
|
2021-01-20 19:49:51 +01:00
|
|
|
char *map_path)
|
2020-12-11 19:25:35 +01:00
|
|
|
{
|
2021-01-21 17:52:08 +01:00
|
|
|
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
|
|
|
|
.pin_root_path = map_path);
|
2021-01-20 19:49:51 +01:00
|
|
|
*obj = bpf_object__open_file(obj_path, map_path ? &opts : NULL);
|
2021-01-18 13:13:51 +01:00
|
|
|
return libbpf_get_error(*obj);
|
2021-01-11 18:44:18 +01:00
|
|
|
}
|
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
static int xdp_detach(int ifindex, __u32 xdp_flags)
|
|
|
|
{
|
|
|
|
return bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);
|
2020-12-11 19:25:35 +01:00
|
|
|
}
|
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
static int xdp_attach(struct bpf_object *obj, const char *sec, int ifindex,
|
|
|
|
__u32 xdp_flags, bool force)
|
2021-01-11 18:44:18 +01:00
|
|
|
{
|
2021-01-18 13:13:51 +01:00
|
|
|
struct bpf_program *prog;
|
|
|
|
int prog_fd;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (sec)
|
|
|
|
prog = bpf_object__find_program_by_title(obj, sec);
|
|
|
|
else
|
|
|
|
prog = bpf_program__next(NULL, obj);
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
|
|
if (prog_fd < 0) {
|
|
|
|
fprintf(stderr, "Could not find program to attach\n");
|
|
|
|
return prog_fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (force) // detach current (if any) xdp-program first
|
|
|
|
xdp_detach(ifindex, xdp_flags);
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
err = bpf_set_link_xdp_fd(ifindex, prog_fd, xdp_flags);
|
|
|
|
if (err < 0) {
|
|
|
|
fprintf(stderr, "Failed loading xdp-program on interface %d\n",
|
|
|
|
ifindex);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
return 0;
|
2021-01-11 18:44:18 +01:00
|
|
|
}
|
|
|
|
|
2021-03-22 12:23:27 +01:00
|
|
|
static int init_rodata(struct bpf_object *obj, void *src, size_t size)
|
|
|
|
{
|
2021-03-29 14:55:41 +02:00
|
|
|
struct bpf_map *map = NULL;
|
|
|
|
bpf_object__for_each_map(map, obj) {
|
|
|
|
if (strstr(bpf_map__name(map), ".rodata"))
|
|
|
|
return bpf_map__set_initial_value(map, src, size);
|
|
|
|
}
|
2021-03-22 12:23:27 +01:00
|
|
|
|
2021-03-29 14:55:41 +02:00
|
|
|
// No .rodata map found
|
|
|
|
return -EINVAL;
|
2021-03-22 12:23:27 +01:00
|
|
|
}
|
|
|
|
|
2021-01-27 12:16:11 +01:00
|
|
|
static int run_program(const char *path, char *const argv[])
|
2021-01-26 18:34:23 +01:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
|
|
|
if (pid < 0)
|
|
|
|
return -errno;
|
|
|
|
if (pid == 0) {
|
2021-01-27 12:16:11 +01:00
|
|
|
execv(path, argv);
|
2021-01-26 18:34:23 +01:00
|
|
|
return -errno;
|
2021-01-27 12:16:11 +01:00
|
|
|
} else { //pid > 0
|
2021-01-26 18:34:23 +01:00
|
|
|
waitpid(pid, &status, 0);
|
|
|
|
if (WIFEXITED(status))
|
|
|
|
ret = WEXITSTATUS(status);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-02 17:40:51 +01:00
|
|
|
static int tc_bpf_attach(char *pin_dir, char *section, char *interface)
|
2021-01-26 18:34:23 +01:00
|
|
|
{
|
2021-03-02 17:40:51 +01:00
|
|
|
char prog_path[MAX_PATH_LEN];
|
|
|
|
char *const argv[] = { TCBPF_LOADER_SCRIPT, "--dev", interface, "--pinned", prog_path, NULL };
|
|
|
|
|
|
|
|
if(snprintf(prog_path, sizeof(prog_path), "%s/%s", pin_dir, section) < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2021-01-27 12:16:11 +01:00
|
|
|
return run_program(TCBPF_LOADER_SCRIPT, argv);
|
|
|
|
}
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2021-01-27 12:16:11 +01:00
|
|
|
static int tc_bpf_clear(char *interface)
|
|
|
|
{
|
|
|
|
char *const argv[] = { TCBPF_LOADER_SCRIPT, "--dev", interface,
|
|
|
|
"--remove", NULL };
|
|
|
|
return run_program(TCBPF_LOADER_SCRIPT, argv);
|
2021-01-26 18:34:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns time of CLOCK_MONOTONIC as nanoseconds in a single __u64.
|
|
|
|
* On failure, the value 0 is returned (and errno will be set).
|
|
|
|
*/
|
2021-01-27 12:16:11 +01:00
|
|
|
static __u64 get_time_ns(void)
|
2020-12-11 19:25:35 +01:00
|
|
|
{
|
2021-01-18 13:13:51 +01:00
|
|
|
struct timespec t;
|
2021-01-27 12:16:11 +01:00
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &t) != 0)
|
2021-01-18 13:13:51 +01:00
|
|
|
return 0;
|
2021-01-26 18:34:23 +01:00
|
|
|
|
|
|
|
return (__u64)t.tv_sec * NS_PER_SECOND + (__u64)t.tv_nsec;
|
2020-12-11 19:25:35 +01:00
|
|
|
}
|
|
|
|
|
2021-03-09 17:50:27 +01:00
|
|
|
static bool packet_ts_timeout(void *val_ptr, __u64 now)
|
|
|
|
{
|
|
|
|
__u64 ts = *(__u64 *)val_ptr;
|
|
|
|
if (now > ts && now - ts > TIMESTAMP_LIFETIME)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool flow_timeout(void *val_ptr, __u64 now)
|
|
|
|
{
|
|
|
|
__u64 ts = ((struct flow_state *)val_ptr)->last_timestamp;
|
|
|
|
if (now > ts && now - ts > FLOW_LIFETIME)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Loops through all entries in a map, running del_decision_func(value, time)
|
|
|
|
* on every entry, and deleting those for which it returns true.
|
|
|
|
* On sucess, returns the number of entries deleted, otherwise returns the
|
|
|
|
* (negative) error code.
|
|
|
|
*/
|
|
|
|
//TODO - maybe add some pointer to arguments for del_decision_func?
|
|
|
|
static int clean_map(int map_fd, size_t key_size, size_t value_size,
|
|
|
|
bool (*del_decision_func)(void *, __u64))
|
2020-12-11 19:25:35 +01:00
|
|
|
{
|
2021-01-26 18:34:23 +01:00
|
|
|
int removed = 0;
|
2021-03-09 17:50:27 +01:00
|
|
|
void *key, *prev_key, *value;
|
2021-01-18 13:13:51 +01:00
|
|
|
bool delete_prev = false;
|
2021-01-27 12:16:11 +01:00
|
|
|
__u64 now_nsec = get_time_ns();
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2021-03-09 17:50:27 +01:00
|
|
|
#ifdef DEBUG
|
|
|
|
int entries = 0;
|
|
|
|
__u64 duration;
|
|
|
|
#endif
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
if (now_nsec == 0)
|
|
|
|
return -errno;
|
|
|
|
|
2021-03-09 17:50:27 +01:00
|
|
|
key = malloc(key_size);
|
|
|
|
prev_key = malloc(key_size);
|
|
|
|
value = malloc(value_size);
|
|
|
|
if (!key || !prev_key || !value) {
|
|
|
|
removed = -ENOMEM;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
// Cannot delete current key because then loop will reset, see https://www.bouncybouncy.net/blog/bpf_map_get_next_key-pitfalls/
|
2021-03-09 17:50:27 +01:00
|
|
|
while (bpf_map_get_next_key(map_fd, prev_key, key) == 0) {
|
2021-01-18 13:13:51 +01:00
|
|
|
if (delete_prev) {
|
2021-03-09 17:50:27 +01:00
|
|
|
bpf_map_delete_elem(map_fd, prev_key);
|
2021-01-18 13:13:51 +01:00
|
|
|
removed++;
|
|
|
|
delete_prev = false;
|
|
|
|
}
|
|
|
|
|
2021-03-09 17:50:27 +01:00
|
|
|
if (bpf_map_lookup_elem(map_fd, key, value) == 0)
|
|
|
|
delete_prev = del_decision_func(value, now_nsec);
|
|
|
|
#ifdef DEBUG
|
2021-01-18 13:13:51 +01:00
|
|
|
entries++;
|
2021-03-09 17:50:27 +01:00
|
|
|
#endif
|
|
|
|
memcpy(prev_key, key, key_size);
|
2021-01-18 13:13:51 +01:00
|
|
|
}
|
|
|
|
if (delete_prev) {
|
2021-03-09 17:50:27 +01:00
|
|
|
bpf_map_delete_elem(map_fd, prev_key);
|
2021-01-18 13:13:51 +01:00
|
|
|
removed++;
|
|
|
|
}
|
2021-03-09 17:50:27 +01:00
|
|
|
#ifdef DEBUG
|
2021-01-27 12:16:11 +01:00
|
|
|
duration = get_time_ns() - now_nsec;
|
2021-03-09 17:50:27 +01:00
|
|
|
printf("%d: Gone through %d entries and removed %d of them in %llu.%09llu s\n",
|
|
|
|
map_fd, entries, removed, duration / NS_PER_SECOND,
|
2021-01-27 12:16:11 +01:00
|
|
|
duration % NS_PER_SECOND);
|
2021-03-09 17:50:27 +01:00
|
|
|
#endif
|
|
|
|
cleanup:
|
|
|
|
if (key)
|
|
|
|
free(key);
|
|
|
|
if (prev_key)
|
|
|
|
free(prev_key);
|
|
|
|
if (value)
|
|
|
|
free(value);
|
2021-01-18 13:13:51 +01:00
|
|
|
return removed;
|
2020-12-11 19:25:35 +01:00
|
|
|
}
|
|
|
|
|
2021-03-09 17:50:27 +01:00
|
|
|
/*
|
|
|
|
* Periodically cleans out entries from both the packet timestamp map and the
|
|
|
|
* flow state map. Maybe better to split up the cleaning of the maps into two
|
|
|
|
* separate threads instead, to better utilize multi-threading and allow for
|
|
|
|
* maps to be cleaned up at different intervals?
|
|
|
|
*/
|
2021-01-07 18:14:27 +01:00
|
|
|
static void *periodic_map_cleanup(void *args)
|
|
|
|
{
|
2021-01-18 13:13:51 +01:00
|
|
|
struct map_cleanup_args *argp = args;
|
|
|
|
struct timespec interval;
|
2021-01-26 18:34:23 +01:00
|
|
|
interval.tv_sec = MAP_CLEANUP_INTERVAL / NS_PER_SECOND;
|
|
|
|
interval.tv_nsec = MAP_CLEANUP_INTERVAL % NS_PER_SECOND;
|
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
while (keep_running) {
|
2021-03-09 17:50:27 +01:00
|
|
|
clean_map(argp->packet_map_fd, sizeof(struct packet_id),
|
|
|
|
sizeof(__u64), packet_ts_timeout);
|
|
|
|
clean_map(argp->flow_map_fd, sizeof(struct network_tuple),
|
|
|
|
sizeof(struct flow_state), flow_timeout);
|
2021-01-18 13:13:51 +01:00
|
|
|
nanosleep(&interval, NULL);
|
|
|
|
}
|
|
|
|
pthread_exit(NULL);
|
2021-01-07 18:14:27 +01:00
|
|
|
}
|
|
|
|
|
2021-02-08 20:28:46 +01:00
|
|
|
/*
|
|
|
|
* Wrapper around inet_ntop designed to handle the "bug" that mapped IPv4
|
|
|
|
* addresses are formated as IPv6 addresses for AF_INET6
|
|
|
|
*/
|
|
|
|
static int format_ip_address(int af, const struct in6_addr *addr, char *buf,
|
|
|
|
size_t size)
|
|
|
|
{
|
|
|
|
if (af == AF_INET)
|
2021-02-09 13:00:28 +01:00
|
|
|
return inet_ntop(af, &addr->s6_addr[12],
|
2021-02-08 20:28:46 +01:00
|
|
|
buf, size) ? -errno : 0;
|
|
|
|
else if (af == AF_INET6)
|
|
|
|
return inet_ntop(af, addr, buf, size) ? -errno : 0;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2021-01-07 18:14:27 +01:00
|
|
|
static void handle_rtt_event(void *ctx, int cpu, void *data, __u32 data_size)
|
|
|
|
{
|
2021-01-18 13:13:51 +01:00
|
|
|
const struct rtt_event *e = data;
|
2021-02-08 20:28:46 +01:00
|
|
|
char saddr[INET6_ADDRSTRLEN];
|
|
|
|
char daddr[INET6_ADDRSTRLEN];
|
|
|
|
|
2021-02-09 18:09:30 +01:00
|
|
|
format_ip_address(e->flow.ipv, &e->flow.saddr.ip, saddr, sizeof(saddr));
|
|
|
|
format_ip_address(e->flow.ipv, &e->flow.daddr.ip, daddr, sizeof(daddr));
|
2021-02-08 20:28:46 +01:00
|
|
|
|
|
|
|
printf("%llu.%06llu ms %s:%d+%s:%d\n", e->rtt / NS_PER_MS,
|
2021-02-09 18:09:30 +01:00
|
|
|
e->rtt % NS_PER_MS, saddr, ntohs(e->flow.saddr.port), daddr,
|
|
|
|
ntohs(e->flow.daddr.port));
|
2021-01-07 18:14:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_missed_rtt_event(void *ctx, int cpu, __u64 lost_cnt)
|
|
|
|
{
|
2021-01-18 13:13:51 +01:00
|
|
|
fprintf(stderr, "Lost %llu RTT events on CPU %d\n", lost_cnt, cpu);
|
2021-01-07 18:14:27 +01:00
|
|
|
}
|
|
|
|
|
2020-12-11 19:25:35 +01:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2021-01-20 19:49:51 +01:00
|
|
|
int err = 0;
|
|
|
|
int ifindex = 0;
|
2021-03-22 12:23:27 +01:00
|
|
|
int opt, longindex = 0;
|
|
|
|
char ifname[IF_NAMESIZE];
|
|
|
|
unsigned long rate_limit_ms = -1;
|
2021-01-18 13:13:51 +01:00
|
|
|
bool xdp_attached = false;
|
2021-01-21 17:52:08 +01:00
|
|
|
bool tc_attached = false;
|
2021-01-27 12:16:11 +01:00
|
|
|
|
2021-03-02 17:40:51 +01:00
|
|
|
struct bpf_object *xdp_obj = NULL;
|
|
|
|
struct bpf_object *tc_obj = NULL;
|
2021-01-27 12:16:11 +01:00
|
|
|
|
2021-03-22 12:23:27 +01:00
|
|
|
struct user_config config = { .rate_limit = DEFAULT_RATE_LIMIT };
|
|
|
|
|
2021-01-20 19:49:51 +01:00
|
|
|
pthread_t tid;
|
|
|
|
struct map_cleanup_args clean_args;
|
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
struct perf_buffer *pb = NULL;
|
2021-03-22 12:23:27 +01:00
|
|
|
struct perf_buffer_opts pb_opts = {
|
|
|
|
.sample_cb = handle_rtt_event,
|
|
|
|
.lost_cb = handle_missed_rtt_event,
|
|
|
|
};
|
2021-01-27 12:16:11 +01:00
|
|
|
|
2021-02-02 14:28:42 +01:00
|
|
|
// Detect if running as root
|
|
|
|
if (geteuid() != 0) {
|
|
|
|
printf("This program must be run as root.\n");
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
// Increase rlimit
|
2021-01-26 18:34:23 +01:00
|
|
|
err = set_rlimit(RLIM_INFINITY);
|
2021-01-18 13:13:51 +01:00
|
|
|
if (err) {
|
2021-01-26 18:34:23 +01:00
|
|
|
fprintf(stderr, "Could not set rlimit to infinity: %s\n",
|
|
|
|
strerror(-err));
|
2021-03-22 12:23:27 +01:00
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((opt = getopt_long(argc, argv, "hi:r:", long_options,
|
|
|
|
&longindex)) != -1) {
|
|
|
|
switch (opt) {
|
|
|
|
case 'i':
|
|
|
|
if (strlen(optarg) > IF_NAMESIZE) {
|
|
|
|
fprintf(stderr, "interface name too long\n");
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
strncpy(ifname, optarg, IF_NAMESIZE);
|
|
|
|
ifindex = if_nametoindex(ifname);
|
|
|
|
if (ifindex == 0) {
|
|
|
|
err = -errno;
|
|
|
|
fprintf(stderr,
|
|
|
|
"Could not get index of interface %s: %s\n",
|
|
|
|
ifname, strerror(-err));
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
case 'r':
|
|
|
|
rate_limit_ms = strtoul(optarg, NULL, 10);
|
|
|
|
if (rate_limit_ms == ULONG_MAX) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"rate-limit \"%s\" ms is invalid\n",
|
|
|
|
optarg);
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
config.rate_limit = rate_limit_ms * NS_PER_MS;
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
print_usage(argv);
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
print_usage(argv);
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
2021-01-18 13:13:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ifindex == 0) {
|
2021-03-22 12:23:27 +01:00
|
|
|
fprintf(stderr,
|
|
|
|
"An interface (-i or --interface) must be provided\n");
|
|
|
|
return EXIT_FAILURE;
|
2021-01-18 13:13:51 +01:00
|
|
|
}
|
|
|
|
|
2021-01-21 17:52:08 +01:00
|
|
|
// Load and attach the XDP program
|
2021-03-02 17:40:51 +01:00
|
|
|
err = bpf_obj_open(&xdp_obj, PPING_XDP_OBJ, PINNED_DIR);
|
2021-02-02 14:28:42 +01:00
|
|
|
if (err) {
|
2021-03-02 17:40:51 +01:00
|
|
|
fprintf(stderr, "Failed opening object file %s: %s\n",
|
|
|
|
PPING_XDP_OBJ, strerror(-err));
|
2021-02-02 14:28:42 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2021-03-02 17:40:51 +01:00
|
|
|
err = bpf_object__load(xdp_obj);
|
2021-01-18 13:13:51 +01:00
|
|
|
if (err) {
|
2021-03-02 17:40:51 +01:00
|
|
|
fprintf(stderr, "Failed loading XDP program: %s\n",
|
|
|
|
strerror(-err));
|
2021-01-18 13:13:51 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2021-03-02 17:40:51 +01:00
|
|
|
err = xdp_attach(xdp_obj, XDP_PROG_SEC, ifindex, XDP_FLAGS, false);
|
2021-01-21 17:52:08 +01:00
|
|
|
if (err) {
|
2021-03-02 17:40:51 +01:00
|
|
|
fprintf(stderr, "Failed attaching XDP program to %s: %s\n",
|
2021-03-22 12:23:27 +01:00
|
|
|
ifname, strerror(-err));
|
2021-03-02 17:40:51 +01:00
|
|
|
goto cleanup;
|
2021-01-21 17:52:08 +01:00
|
|
|
}
|
2021-03-02 17:40:51 +01:00
|
|
|
xdp_attached = true;
|
2021-01-27 12:16:11 +01:00
|
|
|
|
2021-03-02 17:40:51 +01:00
|
|
|
// Load, pin and attach tc program on egress
|
|
|
|
err = bpf_obj_open(&tc_obj, PPING_TCBPF_OBJ, PINNED_DIR);
|
2021-01-18 13:13:51 +01:00
|
|
|
if (err) {
|
2021-03-02 17:40:51 +01:00
|
|
|
fprintf(stderr, "Failed opening object file %s: %s\n",
|
|
|
|
PPING_TCBPF_OBJ, strerror(-err));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2021-03-22 12:23:27 +01:00
|
|
|
err = init_rodata(tc_obj, &config, sizeof(config));
|
|
|
|
if (err) {
|
|
|
|
fprintf(stderr, "Failed pushing user-configration to %s: %s\n",
|
|
|
|
PPING_TCBPF_OBJ, strerror(-err));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2021-03-02 17:40:51 +01:00
|
|
|
err = bpf_object__load(tc_obj);
|
|
|
|
if (err) {
|
|
|
|
fprintf(stderr, "Failed loading tc program: %s\n",
|
2021-01-18 13:13:51 +01:00
|
|
|
strerror(-err));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2021-01-20 19:49:51 +01:00
|
|
|
|
2021-03-02 17:40:51 +01:00
|
|
|
err = bpf_object__pin_programs(tc_obj, PINNED_DIR);
|
2021-01-18 13:13:51 +01:00
|
|
|
if (err) {
|
2021-03-02 17:40:51 +01:00
|
|
|
fprintf(stderr, "Failed pinning tc program to %s: %s\n",
|
|
|
|
PINNED_DIR, strerror(-err));
|
2021-01-18 13:13:51 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2021-03-22 12:23:27 +01:00
|
|
|
err = tc_bpf_attach(PINNED_DIR, TCBPF_PROG_SEC, ifname);
|
2021-01-21 17:52:08 +01:00
|
|
|
if (err) {
|
|
|
|
fprintf(stderr,
|
2021-03-02 17:40:51 +01:00
|
|
|
"Failed attaching tc program on interface %s: %s\n",
|
2021-03-22 12:23:27 +01:00
|
|
|
ifname, strerror(-err));
|
2021-01-21 17:52:08 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
tc_attached = true;
|
|
|
|
|
|
|
|
// Set up the periodical map cleaning
|
2021-03-09 17:50:27 +01:00
|
|
|
clean_args.packet_map_fd =
|
|
|
|
bpf_object__find_map_fd_by_name(xdp_obj, TS_MAP);
|
|
|
|
if (clean_args.packet_map_fd < 0) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Could not get file descriptor of map %s in object %s: %s\n",
|
|
|
|
TS_MAP, PPING_XDP_OBJ,
|
|
|
|
strerror(-clean_args.packet_map_fd));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
clean_args.flow_map_fd =
|
|
|
|
bpf_object__find_map_fd_by_name(tc_obj, FLOW_MAP);
|
|
|
|
if (clean_args.flow_map_fd < 0) {
|
2021-01-27 12:16:11 +01:00
|
|
|
fprintf(stderr,
|
|
|
|
"Could not get file descriptor of map %s in object %s: %s\n",
|
2021-03-09 17:50:27 +01:00
|
|
|
FLOW_MAP, PPING_TCBPF_OBJ,
|
|
|
|
strerror(-clean_args.flow_map_fd));
|
2021-01-20 19:49:51 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2021-01-26 18:34:23 +01:00
|
|
|
|
2021-01-20 19:49:51 +01:00
|
|
|
err = pthread_create(&tid, NULL, periodic_map_cleanup, &clean_args);
|
2021-01-18 13:13:51 +01:00
|
|
|
if (err) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Failed starting thread to perform periodic map cleanup: %s\n",
|
|
|
|
strerror(err));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up perf buffer
|
2021-03-02 17:40:51 +01:00
|
|
|
pb = perf_buffer__new(bpf_object__find_map_fd_by_name(xdp_obj,
|
|
|
|
PERF_BUFFER),
|
2021-01-18 13:13:51 +01:00
|
|
|
PERF_BUFFER_PAGES, &pb_opts);
|
|
|
|
err = libbpf_get_error(pb);
|
|
|
|
if (err) {
|
|
|
|
pb = NULL;
|
|
|
|
fprintf(stderr, "Failed to open perf buffer %s: %s\n",
|
2021-01-26 18:34:23 +01:00
|
|
|
PERF_BUFFER, strerror(err));
|
2021-01-18 13:13:51 +01:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2021-01-21 17:52:08 +01:00
|
|
|
// Allow program to perform cleanup on Ctrl-C
|
2021-01-18 13:13:51 +01:00
|
|
|
signal(SIGINT, abort_program);
|
|
|
|
|
|
|
|
// Main loop
|
|
|
|
while (keep_running) {
|
|
|
|
if ((err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS)) < 0) {
|
|
|
|
if (keep_running) // Only print polling error if it wasn't caused by program termination
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error polling perf buffer: %s\n",
|
|
|
|
strerror(-err));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
perf_buffer__free(pb);
|
2021-03-02 17:40:51 +01:00
|
|
|
|
2021-01-18 13:13:51 +01:00
|
|
|
if (xdp_attached) {
|
|
|
|
err = xdp_detach(ifindex, XDP_FLAGS);
|
2021-03-02 17:40:51 +01:00
|
|
|
if (err)
|
2021-01-18 13:13:51 +01:00
|
|
|
fprintf(stderr,
|
|
|
|
"Failed deatching program from ifindex %d: %s\n",
|
|
|
|
ifindex, strerror(-err));
|
|
|
|
}
|
2021-03-02 17:40:51 +01:00
|
|
|
|
2021-01-21 17:52:08 +01:00
|
|
|
if (tc_attached) {
|
2021-03-22 12:23:27 +01:00
|
|
|
err = tc_bpf_clear(ifname);
|
2021-03-02 17:40:51 +01:00
|
|
|
if (err)
|
2021-01-21 17:52:08 +01:00
|
|
|
fprintf(stderr,
|
|
|
|
"Failed removing tc-bpf program from interface %s: %s\n",
|
2021-01-26 18:34:23 +01:00
|
|
|
argv[1], strerror(-err));
|
2021-03-02 17:40:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (tc_obj) {
|
|
|
|
err = bpf_object__unpin_programs(tc_obj, PINNED_DIR);
|
|
|
|
if (err)
|
|
|
|
fprintf(stderr,
|
|
|
|
"Failed unpinning tc program from %s: %s\n",
|
|
|
|
PINNED_DIR, strerror(-err));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xdp_obj) {
|
2021-03-09 12:21:39 +01:00
|
|
|
err = bpf_object__unpin_maps(xdp_obj, NULL);
|
|
|
|
if (err)
|
|
|
|
fprintf(stderr, "Failed unpinning maps: %s\n",
|
|
|
|
strerror(-err));
|
2021-01-21 17:52:08 +01:00
|
|
|
}
|
2021-01-18 13:13:51 +01:00
|
|
|
|
|
|
|
return err != 0;
|
2020-12-11 19:25:35 +01:00
|
|
|
}
|