Commit 473c5daa authored by Daniel Borkmann's avatar Daniel Borkmann
Browse files

Merge branch 'bpf-xdp-sample-libbpf'



Maciej Fijalkowski says:

====================
This patchset tries to address the situation where:
* user loads a particular xdp sample application that does stats polling
* user loads another sample application on the same interface
* then, user sends SIGINT/SIGTERM to the app that was attached as a first one
* second application ends up with an unloaded xdp program

1st patch contains a helper libbpf function for getting the map fd by a
given map name.
In patch 2 Jesper removes the read_trace_pipe usage from xdp_redirect_cpu which
was a blocker for converting this sample to libbpf usage.
3rd patch updates a bunch of xdp samples to make the use of libbpf.
Patch 4 adjusts RLIMIT_MEMLOCK for two samples touched in this patchset.
In patch 5 extack messages are added for cases where dev_change_xdp_fd returns
with an error so user has an idea what was the reason for not attaching the
xdp program onto interface.
Patch 6 makes the samples behavior similar to what iproute2 does when loading
xdp prog - the "force" flag is introduced.
Patch 7 introduces the libbpf function that will query the driver from
userspace about the currently attached xdp prog id.

Use it in samples that do polling by checking the prog id in signal handler
and comparing it with previously stored one which is the scope of patch 8.

Thanks!

v1->v2:
* add a libbpf helper for getting a prog via relative index
* include xdp_redirect_cpu into conversion

v2->v3: mostly addressing Daniel's/Jesper's comments
* get rid of the helper from v1->v2
* feed the xdp_redirect_cpu with program name instead of number

v3->v4:
* fix help message in xdp_sample_pkts

v4->v5:
* in get_link_xdp_fd, assign prog_id only when libbpf_nl_get_link returned
  with 0
* add extack messages in dev_change_xdp_fd
* check the return value of bpf_get_link_xdp_id when exiting from sample progs

v5->v6:
* rebase
====================

Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
parents 6f20c71d 3b7a8ec2
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -7983,8 +7983,10 @@ int dev_change_xdp_fd(struct net_device *dev, struct netlink_ext_ack *extack,
	query = flags & XDP_FLAGS_HW_MODE ? XDP_QUERY_PROG_HW : XDP_QUERY_PROG;

	bpf_op = bpf_chk = ops->ndo_bpf;
	if (!bpf_op && (flags & (XDP_FLAGS_DRV_MODE | XDP_FLAGS_HW_MODE)))
	if (!bpf_op && (flags & (XDP_FLAGS_DRV_MODE | XDP_FLAGS_HW_MODE))) {
		NL_SET_ERR_MSG(extack, "underlying driver does not support XDP in native mode");
		return -EOPNOTSUPP;
	}
	if (!bpf_op || (flags & XDP_FLAGS_SKB_MODE))
		bpf_op = generic_xdp_install;
	if (bpf_op == bpf_chk)
@@ -7992,11 +7994,15 @@ int dev_change_xdp_fd(struct net_device *dev, struct netlink_ext_ack *extack,

	if (fd >= 0) {
		if (__dev_xdp_query(dev, bpf_chk, XDP_QUERY_PROG) ||
		    __dev_xdp_query(dev, bpf_chk, XDP_QUERY_PROG_HW))
		    __dev_xdp_query(dev, bpf_chk, XDP_QUERY_PROG_HW)) {
			NL_SET_ERR_MSG(extack, "native and generic XDP can't be active at the same time");
			return -EEXIST;
		}
		if ((flags & XDP_FLAGS_UPDATE_IF_NOEXIST) &&
		    __dev_xdp_query(dev, bpf_op, query))
		    __dev_xdp_query(dev, bpf_op, query)) {
			NL_SET_ERR_MSG(extack, "XDP program already attached");
			return -EBUSY;
		}

		prog = bpf_prog_get_type_dev(fd, BPF_PROG_TYPE_XDP,
					     bpf_op == ops->ndo_bpf);
+4 −4
Original line number Diff line number Diff line
@@ -87,18 +87,18 @@ test_cgrp2_sock2-objs := bpf_load.o test_cgrp2_sock2.o
xdp1-objs := xdp1_user.o
# reuse xdp1 source intentionally
xdp2-objs := xdp1_user.o
xdp_router_ipv4-objs := bpf_load.o xdp_router_ipv4_user.o
xdp_router_ipv4-objs := xdp_router_ipv4_user.o
test_current_task_under_cgroup-objs := bpf_load.o $(CGROUP_HELPERS) \
				       test_current_task_under_cgroup_user.o
trace_event-objs := bpf_load.o trace_event_user.o $(TRACE_HELPERS)
sampleip-objs := bpf_load.o sampleip_user.o $(TRACE_HELPERS)
tc_l2_redirect-objs := bpf_load.o tc_l2_redirect_user.o
lwt_len_hist-objs := bpf_load.o lwt_len_hist_user.o
xdp_tx_iptunnel-objs := bpf_load.o xdp_tx_iptunnel_user.o
xdp_tx_iptunnel-objs := xdp_tx_iptunnel_user.o
test_map_in_map-objs := bpf_load.o test_map_in_map_user.o
per_socket_stats_example-objs := cookie_uid_helper_example.o
xdp_redirect-objs := bpf_load.o xdp_redirect_user.o
xdp_redirect_map-objs := bpf_load.o xdp_redirect_map_user.o
xdp_redirect-objs := xdp_redirect_user.o
xdp_redirect_map-objs := xdp_redirect_map_user.o
xdp_redirect_cpu-objs := bpf_load.o xdp_redirect_cpu_user.o
xdp_monitor-objs := bpf_load.o xdp_monitor_user.o
xdp_rxq_info-objs := xdp_rxq_info_user.o
+30 −4
Original line number Diff line number Diff line
@@ -22,11 +22,23 @@
#include "bpf/libbpf.h"

static int ifindex;
static __u32 xdp_flags;
static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
static __u32 prog_id;

static void int_exit(int sig)
{
	__u32 curr_prog_id = 0;

	if (bpf_get_link_xdp_id(ifindex, &curr_prog_id, xdp_flags)) {
		printf("bpf_get_link_xdp_id failed\n");
		exit(1);
	}
	if (prog_id == curr_prog_id)
		bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);
	else if (!curr_prog_id)
		printf("couldn't find a prog id on a given interface\n");
	else
		printf("program on interface changed, not removing\n");
	exit(0);
}

@@ -63,7 +75,8 @@ static void usage(const char *prog)
		"usage: %s [OPTS] IFACE\n\n"
		"OPTS:\n"
		"    -S    use skb-mode\n"
		"    -N    enforce native mode\n",
		"    -N    enforce native mode\n"
		"    -F    force loading prog\n",
		prog);
}

@@ -73,11 +86,14 @@ int main(int argc, char **argv)
	struct bpf_prog_load_attr prog_load_attr = {
		.prog_type	= BPF_PROG_TYPE_XDP,
	};
	const char *optstr = "SN";
	struct bpf_prog_info info = {};
	__u32 info_len = sizeof(info);
	const char *optstr = "FSN";
	int prog_fd, map_fd, opt;
	struct bpf_object *obj;
	struct bpf_map *map;
	char filename[256];
	int err;

	while ((opt = getopt(argc, argv, optstr)) != -1) {
		switch (opt) {
@@ -87,6 +103,9 @@ int main(int argc, char **argv)
		case 'N':
			xdp_flags |= XDP_FLAGS_DRV_MODE;
			break;
		case 'F':
			xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST;
			break;
		default:
			usage(basename(argv[0]));
			return 1;
@@ -135,6 +154,13 @@ int main(int argc, char **argv)
		return 1;
	}

	err = bpf_obj_get_info_by_fd(prog_fd, &info, &info_len);
	if (err) {
		printf("can't get prog info - %s\n", strerror(errno));
		return err;
	}
	prog_id = info.id;

	poll_stats(map_fd, 2);

	return 0;
+32 −6
Original line number Diff line number Diff line
@@ -24,12 +24,25 @@
#define STATS_INTERVAL_S 2U

static int ifindex = -1;
static __u32 xdp_flags;
static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
static __u32 prog_id;

static void int_exit(int sig)
{
	if (ifindex > -1)
	__u32 curr_prog_id = 0;

	if (ifindex > -1) {
		if (bpf_get_link_xdp_id(ifindex, &curr_prog_id, xdp_flags)) {
			printf("bpf_get_link_xdp_id failed\n");
			exit(1);
		}
		if (prog_id == curr_prog_id)
			bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);
		else if (!curr_prog_id)
			printf("couldn't find a prog id on a given iface\n");
		else
			printf("program on interface changed, not removing\n");
	}
	exit(0);
}

@@ -60,6 +73,7 @@ static void usage(const char *cmd)
	printf("    -T <stop-after-X-seconds> Default: 0 (forever)\n");
	printf("    -S use skb-mode\n");
	printf("    -N enforce native mode\n");
	printf("    -F force loading prog\n");
	printf("    -h Display this help\n");
}

@@ -70,12 +84,15 @@ int main(int argc, char **argv)
		.prog_type	= BPF_PROG_TYPE_XDP,
	};
	unsigned char opt_flags[256] = {};
	const char *optstr = "i:T:SNFh";
	struct bpf_prog_info info = {};
	__u32 info_len = sizeof(info);
	unsigned int kill_after_s = 0;
	const char *optstr = "i:T:SNh";
	int i, prog_fd, map_fd, opt;
	struct bpf_object *obj;
	struct bpf_map *map;
	char filename[256];
	int err;

	for (i = 0; i < strlen(optstr); i++)
		if (optstr[i] != 'h' && 'a' <= optstr[i] && optstr[i] <= 'z')
@@ -96,6 +113,9 @@ int main(int argc, char **argv)
		case 'N':
			xdp_flags |= XDP_FLAGS_DRV_MODE;
			break;
		case 'F':
			xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST;
			break;
		default:
			usage(argv[0]);
			return 1;
@@ -142,9 +162,15 @@ int main(int argc, char **argv)
		return 1;
	}

	poll_stats(map_fd, kill_after_s);
	err = bpf_obj_get_info_by_fd(prog_fd, &info, &info_len);
	if (err) {
		printf("can't get prog info - %s\n", strerror(errno));
		return 1;
	}
	prog_id = info.id;

	bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);
	poll_stats(map_fd, kill_after_s);
	int_exit(0);

	return 0;
}
+136 −60
Original line number Diff line number Diff line
@@ -24,20 +24,26 @@ static const char *__doc__ =
/* How many xdp_progs are defined in _kern.c */
#define MAX_PROG 6

/* Wanted to get rid of bpf_load.h and fake-"libbpf.h" (and instead
 * use bpf/libbpf.h), but cannot as (currently) needed for XDP
 * attaching to a device via bpf_set_link_xdp_fd()
 */
#include <bpf/bpf.h>
#include "bpf_load.h"
#include "bpf/libbpf.h"

#include "bpf_util.h"

static int ifindex = -1;
static char ifname_buf[IF_NAMESIZE];
static char *ifname;

static __u32 xdp_flags;
static __u32 prog_id;

static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
static int cpu_map_fd;
static int rx_cnt_map_fd;
static int redirect_err_cnt_map_fd;
static int cpumap_enqueue_cnt_map_fd;
static int cpumap_kthread_cnt_map_fd;
static int cpus_available_map_fd;
static int cpus_count_map_fd;
static int cpus_iterator_map_fd;
static int exception_cnt_map_fd;

/* Exit return codes */
#define EXIT_OK		0
@@ -51,27 +57,50 @@ static const struct option long_options[] = {
	{"help",	no_argument,		NULL, 'h' },
	{"dev",		required_argument,	NULL, 'd' },
	{"skb-mode",	no_argument,		NULL, 'S' },
	{"debug",	no_argument,		NULL, 'D' },
	{"sec",		required_argument,	NULL, 's' },
	{"prognum",	required_argument,	NULL, 'p' },
	{"progname",	required_argument,	NULL, 'p' },
	{"qsize",	required_argument,	NULL, 'q' },
	{"cpu",		required_argument,	NULL, 'c' },
	{"stress-mode", no_argument,		NULL, 'x' },
	{"no-separators", no_argument,		NULL, 'z' },
	{"force",	no_argument,		NULL, 'F' },
	{0, 0, NULL,  0 }
};

static void int_exit(int sig)
{
	__u32 curr_prog_id = 0;

	if (ifindex > -1) {
		if (bpf_get_link_xdp_id(ifindex, &curr_prog_id, xdp_flags)) {
			printf("bpf_get_link_xdp_id failed\n");
			exit(EXIT_FAIL);
		}
		if (prog_id == curr_prog_id) {
			fprintf(stderr,
				"Interrupted: Removing XDP program on ifindex:%d device:%s\n",
				ifindex, ifname);
	if (ifindex > -1)
			bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);
		} else if (!curr_prog_id) {
			printf("couldn't find a prog id on a given iface\n");
		} else {
			printf("program on interface changed, not removing\n");
		}
	}
	exit(EXIT_OK);
}

static void usage(char *argv[])
static void print_avail_progs(struct bpf_object *obj)
{
	struct bpf_program *pos;

	bpf_object__for_each_program(pos, obj) {
		if (bpf_program__is_xdp(pos))
			printf(" %s\n", bpf_program__title(pos, false));
	}
}

static void usage(char *argv[], struct bpf_object *obj)
{
	int i;

@@ -89,6 +118,8 @@ static void usage(char *argv[])
				long_options[i].val);
		printf("\n");
	}
	printf("\n Programs to be used for --progname:\n");
	print_avail_progs(obj);
	printf("\n");
}

@@ -263,7 +294,7 @@ static __u64 calc_errs_pps(struct datarec *r,

static void stats_print(struct stats_record *stats_rec,
			struct stats_record *stats_prev,
			int prog_num)
			char *prog_name)
{
	unsigned int nr_cpus = bpf_num_possible_cpus();
	double pps = 0, drop = 0, err = 0;
@@ -273,7 +304,7 @@ static void stats_print(struct stats_record *stats_rec,
	int i;

	/* Header */
	printf("Running XDP/eBPF prog_num:%d\n", prog_num);
	printf("Running XDP/eBPF prog_name:%s\n", prog_name);
	printf("%-15s %-7s %-14s %-11s %-9s\n",
	       "XDP-cpumap", "CPU:to", "pps", "drop-pps", "extra-info");

@@ -424,20 +455,20 @@ static void stats_collect(struct stats_record *rec)
{
	int fd, i;

	fd = map_fd[1]; /* map: rx_cnt */
	fd = rx_cnt_map_fd;
	map_collect_percpu(fd, 0, &rec->rx_cnt);

	fd = map_fd[2]; /* map: redirect_err_cnt */
	fd = redirect_err_cnt_map_fd;
	map_collect_percpu(fd, 1, &rec->redir_err);

	fd = map_fd[3]; /* map: cpumap_enqueue_cnt */
	fd = cpumap_enqueue_cnt_map_fd;
	for (i = 0; i < MAX_CPUS; i++)
		map_collect_percpu(fd, i, &rec->enq[i]);

	fd = map_fd[4]; /* map: cpumap_kthread_cnt */
	fd = cpumap_kthread_cnt_map_fd;
	map_collect_percpu(fd, 0, &rec->kthread);

	fd = map_fd[8]; /* map: exception_cnt */
	fd = exception_cnt_map_fd;
	map_collect_percpu(fd, 0, &rec->exception);
}

@@ -462,7 +493,7 @@ static int create_cpu_entry(__u32 cpu, __u32 queue_size,
	/* Add a CPU entry to cpumap, as this allocate a cpu entry in
	 * the kernel for the cpu.
	 */
	ret = bpf_map_update_elem(map_fd[0], &cpu, &queue_size, 0);
	ret = bpf_map_update_elem(cpu_map_fd, &cpu, &queue_size, 0);
	if (ret) {
		fprintf(stderr, "Create CPU entry failed (err:%d)\n", ret);
		exit(EXIT_FAIL_BPF);
@@ -471,23 +502,22 @@ static int create_cpu_entry(__u32 cpu, __u32 queue_size,
	/* Inform bpf_prog's that a new CPU is available to select
	 * from via some control maps.
	 */
	/* map_fd[5] = cpus_available */
	ret = bpf_map_update_elem(map_fd[5], &avail_idx, &cpu, 0);
	ret = bpf_map_update_elem(cpus_available_map_fd, &avail_idx, &cpu, 0);
	if (ret) {
		fprintf(stderr, "Add to avail CPUs failed\n");
		exit(EXIT_FAIL_BPF);
	}

	/* When not replacing/updating existing entry, bump the count */
	/* map_fd[6] = cpus_count */
	ret = bpf_map_lookup_elem(map_fd[6], &key, &curr_cpus_count);
	ret = bpf_map_lookup_elem(cpus_count_map_fd, &key, &curr_cpus_count);
	if (ret) {
		fprintf(stderr, "Failed reading curr cpus_count\n");
		exit(EXIT_FAIL_BPF);
	}
	if (new) {
		curr_cpus_count++;
		ret = bpf_map_update_elem(map_fd[6], &key, &curr_cpus_count, 0);
		ret = bpf_map_update_elem(cpus_count_map_fd, &key,
					  &curr_cpus_count, 0);
		if (ret) {
			fprintf(stderr, "Failed write curr cpus_count\n");
			exit(EXIT_FAIL_BPF);
@@ -510,8 +540,8 @@ static void mark_cpus_unavailable(void)
	int ret, i;

	for (i = 0; i < MAX_CPUS; i++) {
		/* map_fd[5] = cpus_available */
		ret = bpf_map_update_elem(map_fd[5], &i, &invalid_cpu, 0);
		ret = bpf_map_update_elem(cpus_available_map_fd, &i,
					  &invalid_cpu, 0);
		if (ret) {
			fprintf(stderr, "Failed marking CPU unavailable\n");
			exit(EXIT_FAIL_BPF);
@@ -531,7 +561,7 @@ static void stress_cpumap(void)
	create_cpu_entry(1, 16000, 0, false);
}

static void stats_poll(int interval, bool use_separators, int prog_num,
static void stats_poll(int interval, bool use_separators, char *prog_name,
		       bool stress_mode)
{
	struct stats_record *record, *prev;
@@ -547,7 +577,7 @@ static void stats_poll(int interval, bool use_separators, int prog_num,
	while (1) {
		swap(&prev, &record);
		stats_collect(record);
		stats_print(record, prev, prog_num);
		stats_print(record, prev, prog_name);
		sleep(interval);
		if (stress_mode)
			stress_cpumap();
@@ -557,20 +587,55 @@ static void stats_poll(int interval, bool use_separators, int prog_num,
	free_stats_record(prev);
}

static int init_map_fds(struct bpf_object *obj)
{
	cpu_map_fd = bpf_object__find_map_fd_by_name(obj, "cpu_map");
	rx_cnt_map_fd = bpf_object__find_map_fd_by_name(obj, "rx_cnt");
	redirect_err_cnt_map_fd =
		bpf_object__find_map_fd_by_name(obj, "redirect_err_cnt");
	cpumap_enqueue_cnt_map_fd =
		bpf_object__find_map_fd_by_name(obj, "cpumap_enqueue_cnt");
	cpumap_kthread_cnt_map_fd =
		bpf_object__find_map_fd_by_name(obj, "cpumap_kthread_cnt");
	cpus_available_map_fd =
		bpf_object__find_map_fd_by_name(obj, "cpus_available");
	cpus_count_map_fd = bpf_object__find_map_fd_by_name(obj, "cpus_count");
	cpus_iterator_map_fd =
		bpf_object__find_map_fd_by_name(obj, "cpus_iterator");
	exception_cnt_map_fd =
		bpf_object__find_map_fd_by_name(obj, "exception_cnt");

	if (cpu_map_fd < 0 || rx_cnt_map_fd < 0 ||
	    redirect_err_cnt_map_fd < 0 || cpumap_enqueue_cnt_map_fd < 0 ||
	    cpumap_kthread_cnt_map_fd < 0 || cpus_available_map_fd < 0 ||
	    cpus_count_map_fd < 0 || cpus_iterator_map_fd < 0 ||
	    exception_cnt_map_fd < 0)
		return -ENOENT;

	return 0;
}

int main(int argc, char **argv)
{
	struct rlimit r = {10 * 1024 * 1024, RLIM_INFINITY};
	char *prog_name = "xdp_cpu_map5_lb_hash_ip_pairs";
	struct bpf_prog_load_attr prog_load_attr = {
		.prog_type	= BPF_PROG_TYPE_UNSPEC,
	};
	struct bpf_prog_info info = {};
	__u32 info_len = sizeof(info);
	bool use_separators = true;
	bool stress_mode = false;
	struct bpf_program *prog;
	struct bpf_object *obj;
	char filename[256];
	bool debug = false;
	int added_cpus = 0;
	int longindex = 0;
	int interval = 2;
	int prog_num = 5;
	int add_cpu = -1;
	int opt, err;
	int prog_fd;
	__u32 qsize;
	int opt;

	/* Notice: choosing he queue size is very important with the
	 * ixgbe driver, because it's driver page recycling trick is
@@ -581,26 +646,29 @@ int main(int argc, char **argv)
	qsize = 128+64;

	snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
	prog_load_attr.file = filename;

	if (setrlimit(RLIMIT_MEMLOCK, &r)) {
		perror("setrlimit(RLIMIT_MEMLOCK)");
		return 1;
	}

	if (load_bpf_file(filename)) {
		fprintf(stderr, "ERR in load_bpf_file(): %s", bpf_log_buf);
	if (bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd))
		return EXIT_FAIL;
	}

	if (!prog_fd[0]) {
		fprintf(stderr, "ERR: load_bpf_file: %s\n", strerror(errno));
	if (prog_fd < 0) {
		fprintf(stderr, "ERR: bpf_prog_load_xattr: %s\n",
			strerror(errno));
		return EXIT_FAIL;
	}
	if (init_map_fds(obj) < 0) {
		fprintf(stderr, "bpf_object__find_map_fd_by_name failed\n");
		return EXIT_FAIL;
	}

	mark_cpus_unavailable();

	/* Parse commands line args */
	while ((opt = getopt_long(argc, argv, "hSd:",
	while ((opt = getopt_long(argc, argv, "hSd:s:p:q:c:xzF",
				  long_options, &longindex)) != -1) {
		switch (opt) {
		case 'd':
@@ -624,9 +692,6 @@ int main(int argc, char **argv)
		case 'S':
			xdp_flags |= XDP_FLAGS_SKB_MODE;
			break;
		case 'D':
			debug = true;
			break;
		case 'x':
			stress_mode = true;
			break;
@@ -635,13 +700,7 @@ int main(int argc, char **argv)
			break;
		case 'p':
			/* Selecting eBPF prog to load */
			prog_num = atoi(optarg);
			if (prog_num < 0 || prog_num >= MAX_PROG) {
				fprintf(stderr,
					"--prognum too large err(%d):%s\n",
					errno, strerror(errno));
				goto error;
			}
			prog_name = optarg;
			break;
		case 'c':
			/* Add multiple CPUs */
@@ -658,24 +717,27 @@ int main(int argc, char **argv)
		case 'q':
			qsize = atoi(optarg);
			break;
		case 'F':
			xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST;
			break;
		case 'h':
		error:
		default:
			usage(argv);
			usage(argv, obj);
			return EXIT_FAIL_OPTION;
		}
	}
	/* Required option */
	if (ifindex == -1) {
		fprintf(stderr, "ERR: required option --dev missing\n");
		usage(argv);
		usage(argv, obj);
		return EXIT_FAIL_OPTION;
	}
	/* Required option */
	if (add_cpu == -1) {
		fprintf(stderr, "ERR: required option --cpu missing\n");
		fprintf(stderr, " Specify multiple --cpu option to add more\n");
		usage(argv);
		usage(argv, obj);
		return EXIT_FAIL_OPTION;
	}

@@ -683,16 +745,30 @@ int main(int argc, char **argv)
	signal(SIGINT, int_exit);
	signal(SIGTERM, int_exit);

	if (bpf_set_link_xdp_fd(ifindex, prog_fd[prog_num], xdp_flags) < 0) {
	prog = bpf_object__find_program_by_title(obj, prog_name);
	if (!prog) {
		fprintf(stderr, "bpf_object__find_program_by_title failed\n");
		return EXIT_FAIL;
	}

	prog_fd = bpf_program__fd(prog);
	if (prog_fd < 0) {
		fprintf(stderr, "bpf_program__fd failed\n");
		return EXIT_FAIL;
	}

	if (bpf_set_link_xdp_fd(ifindex, prog_fd, xdp_flags) < 0) {
		fprintf(stderr, "link set xdp fd failed\n");
		return EXIT_FAIL_XDP;
	}

	if (debug) {
		printf("Debug-mode reading trace pipe (fix #define DEBUG)\n");
		read_trace_pipe();
	err = bpf_obj_get_info_by_fd(prog_fd, &info, &info_len);
	if (err) {
		printf("can't get prog info - %s\n", strerror(errno));
		return err;
	}
	prog_id = info.id;

	stats_poll(interval, use_separators, prog_num, stress_mode);
	stats_poll(interval, use_separators, prog_name, stress_mode);
	return EXIT_OK;
}
Loading