Wireless Daemon for Linux
 help / color / mirror / Atom feed
From: James Prestwood <prestwoj@gmail.com>
To: Denis Kenzior <denkenz@gmail.com>, iwd@lists.linux.dev
Subject: Re: [PATCH 5/5] RFC: Initial iwtrace utility
Date: Tue, 2 Jan 2024 05:30:30 -0800	[thread overview]
Message-ID: <f787daac-e8c7-4cf1-9405-c3b13a8e164e@gmail.com> (raw)
In-Reply-To: <20231227060954.103572-5-denkenz@gmail.com>

Hi Denis,

On 12/26/23 10:09 PM, Denis Kenzior wrote:
> eBPF based tracing, similar to iwmon.  This is meant as an alternative
> to the nlmon based netlink tracing and can be used on kernels where
> nlmon might not be available.
All looks good to me. This seems like a nice utility if the support is 
more widespread than nlmon.
> ---
>   Makefile.am           |  46 ++++++---
>   configure.ac          |  11 +++
>   monitor/iwtrace.bpf.c | 194 +++++++++++++++++++++++++++++++++++++
>   monitor/iwtrace.c     | 218 ++++++++++++++++++++++++++++++++++++++++++
>   monitor/iwtrace.h     |  12 +++
>   5 files changed, 466 insertions(+), 15 deletions(-)
>   create mode 100644 monitor/iwtrace.bpf.c
>   create mode 100644 monitor/iwtrace.c
>   create mode 100644 monitor/iwtrace.h
>
> diff --git a/Makefile.am b/Makefile.am
> index 5ed6ab37164b..601db513d1e3 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -327,23 +327,39 @@ endif
>   endif
>   
>   if MONITOR
> +MONITOR_SOURCES = linux/nl80211.h \
> +				monitor/nlmon.h monitor/nlmon.c \
> +				monitor/pcap.h monitor/pcap.c \
> +				monitor/display.h monitor/display.c \
> +				src/ie.h src/ie.c \
> +				src/wscutil.h src/wscutil.c \
> +				src/mpdu.h src/mpdu.c \
> +				src/util.h src/util.c \
> +				src/crypto.h src/crypto.c \
> +				src/watchlist.h src/watchlist.c \
> +				src/eapolutil.h src/eapolutil.c \
> +				src/nl80211cmd.h src/nl80211cmd.c \
> +				src/p2putil.c src/p2putil.h \
> +				src/anqputil.h src/anqputil.c \
> +				src/band.h src/band.c
>   bin_PROGRAMS += monitor/iwmon
>   
> -monitor_iwmon_SOURCES = monitor/main.c linux/nl80211.h \
> -					monitor/nlmon.h monitor/nlmon.c \
> -					monitor/pcap.h monitor/pcap.c \
> -					monitor/display.h monitor/display.c \
> -					src/ie.h src/ie.c \
> -					src/wscutil.h src/wscutil.c \
> -					src/mpdu.h src/mpdu.c \
> -					src/util.h src/util.c \
> -					src/crypto.h src/crypto.c \
> -					src/watchlist.h src/watchlist.c \
> -					src/eapolutil.h src/eapolutil.c \
> -					src/nl80211cmd.h src/nl80211cmd.c \
> -					src/p2putil.c src/p2putil.h \
> -					src/anqputil.h src/anqputil.c \
> -					src/band.h src/band.c
> +if BPF
> +bin_PROGRAMS += monitor/iwtrace
> +
> +monitor/iwtrace.bpf.o: monitor/iwtrace.bpf.c
> +	clang -g -O2 -target bpf -c monitor/iwtrace.bpf.c -o monitor/iwtrace.tmp.bpf.o
> +	bpftool gen object monitor/iwtrace.bpf.o monitor/iwtrace.tmp.bpf.o
> +
> +monitor/iwtrace.skel.h: monitor/iwtrace.bpf.o
> +	bpftool gen skeleton monitor/iwtrace.bpf.o > monitor/iwtrace.skel.h
> +
> +monitor_iwtrace_SOURCES = monitor/iwtrace.c monitor/iwtrace.h \
> +				monitor/iwtrace.skel.h $(MONITOR_SOURCES)
> +monitor_iwtrace_LDADD = $(ell_ldadd) $(LIBBPF_LIBS)
> +endif
> +
> +monitor_iwmon_SOURCES = monitor/main.c $(MONITOR_SOURCES)
>   monitor_iwmon_LDADD = $(ell_ldadd)
>   
>   if MANUAL_PAGES
> diff --git a/configure.ac b/configure.ac
> index d84e035c2f5f..bb7bfedcf80f 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -200,6 +200,17 @@ AC_ARG_ENABLE([monitor], AS_HELP_STRING([--disable-monitor],
>   					[enable_monitor=${enableval}])
>   AM_CONDITIONAL(MONITOR, test "${enable_monitor}" != "no")
>   
> +AC_ARG_ENABLE([bpf], AS_HELP_STRING([--disable-bpf],
> +				[don't enable bpf based tracing]),
> +					[enable_bpf=${enableval}])
> +if (test "${enable_bpf}" != "no"); then
> +	PKG_CHECK_MODULES(LIBBPF, libbpf, dummy=yes,
> +			AC_MSG_ERROR(libbpf is required))
> +	AC_SUBST(LIBBPF_CFLAGS)
> +	AC_SUBST(LIBBPF_LIBS)
> +fi
> +AM_CONDITIONAL(BPF, test "${enable_bpf}" != "no")
> +
>   AC_ARG_ENABLE([dbus-policy], AS_HELP_STRING([--disable-dbus-policy],
>   				[don't install D-Bus system policy files]),
>   					[enable_dbus_policy=${enableval}])
> diff --git a/monitor/iwtrace.bpf.c b/monitor/iwtrace.bpf.c
> new file mode 100644
> index 000000000000..251b54fc2a41
> --- /dev/null
> +++ b/monitor/iwtrace.bpf.c
> @@ -0,0 +1,194 @@
> +/*
> + * Embedded Linux library
> + * Copyright (C) 2023  Cruise, LLC
> + *
> + * SPDX-License-Identifier: GPL-2.0-only
> + */
> +
> +#include <stdint.h>
> +#include <string.h>
> +#include <linux/types.h>
> +#include <linux/bpf.h>
> +#include <linux/netlink.h>
> +#include <bpf/bpf_helpers.h>
> +#include <bpf/bpf_tracing.h>
> +#include <bpf/bpf_core_read.h>
> +#include "iwtrace.h"
> +
> +char LICENSE[] SEC("license") = "GPL";
> +
> +struct sock;
> +struct netlink_ext_ack;
> +
> +struct {
> +	__uint(type, BPF_MAP_TYPE_RINGBUF);
> +	__uint(max_entries, 256 * 1024);
> +} rb SEC(".maps");
> +
> +struct sock {
> +	uint16_t sk_protocol;
> +};
> +
> +struct sk_buff {
> +	unsigned int len, data_len;
> +	unsigned char *data;
> +	struct net_device *dev;
> +	union {
> +		struct sock		*sk;
> +		int			ip_defrag_offset;
> +	};
> +};
> +
> +struct capture_256 {
> +	struct metadata meta;
> +	uint8_t packet[256 - sizeof(struct metadata)];
> +} __attribute__ ((packed));
> +
> +struct capture_1k {
> +	struct metadata meta;
> +	uint8_t packet[1024 - sizeof(struct metadata)];
> +} __attribute__ ((packed));
> +
> +struct capture_4k {
> +	struct metadata meta;
> +	uint8_t packet[4096 - sizeof(struct metadata)];
> +} __attribute__ ((packed));
> +
> +struct capture_8k {
> +	struct metadata meta;
> +	uint8_t packet[8192 - sizeof(struct metadata)];
> +} __attribute__ ((packed));
> +
> +struct capture_16k {
> +	struct metadata meta;
> +	uint8_t packet[16384];
> +} __attribute__ ((packed));
> +
> +static void metadata_fill(struct metadata *meta, const struct sk_buff *skb)
> +{
> +	struct sock *sk = __builtin_preserve_access_index(skb->sk);
> +
> +	meta->timestamp = bpf_ktime_get_boot_ns();
> +	meta->len = __builtin_preserve_access_index(skb->len);
> +	meta->protocol = __builtin_preserve_access_index(sk->sk_protocol);
> +}
> +
> +static int capture_common(const struct sk_buff *skb)
> +{
> +	uint16_t len = __builtin_preserve_access_index(skb->len);
> +	const void *data = __builtin_preserve_access_index(skb->data);
> +
> +	/*
> +	 * bpf_ringbuf_reserve is currently limited to a known constant
> +	 * value, and cannot handle values that are not constant (even if
> +	 * bounded).  bpf_ringbuf_output might be suitable, but no metadata
> +	 * could be prepended if that is used.  Another alternative is to use
> +	 * a perf buffer, but it is per-CPU and might result in packets being
> +	 * processed out of order.  We trick the validator by using several
> +	 * well known structure sizes (256/1k/4k/8k/16k) in order to save on
> +	 * memory space, but the resultant program is larger than it would be
> +	 * if dynamic sizing was supported.
> +	 */
> +	if (len <= 256 - sizeof(struct metadata)) {
> +		struct capture_256 *c256 = bpf_ringbuf_reserve(&rb,
> +					sizeof(struct capture_256), 0);
> +
> +		if (!c256)
> +			return 0;
> +
> +		metadata_fill(&c256->meta, skb);
> +
> +		if (bpf_probe_read_kernel(c256->packet, len, data) < 0)
> +			bpf_ringbuf_discard(c256, BPF_RB_NO_WAKEUP);
> +		else
> +			bpf_ringbuf_submit(c256, 0);
> +
> +		return 0;
> +	}
> +
> +	if (len <= sizeof(struct capture_1k) - sizeof(struct metadata)) {
> +		struct capture_1k *c1k = bpf_ringbuf_reserve(&rb,
> +						sizeof(struct capture_1k), 0);
> +
> +		if (!c1k)
> +			return 0;
> +
> +		metadata_fill(&c1k->meta, skb);
> +
> +		if (bpf_probe_read_kernel(c1k->packet, len, data) < 0)
> +			bpf_ringbuf_discard(c1k, BPF_RB_NO_WAKEUP);
> +		else
> +			bpf_ringbuf_submit(c1k, 0);
> +
> +		return 0;
> +	}
> +
> +	if (len <= sizeof(struct capture_4k) - sizeof(struct metadata)) {
> +		struct capture_4k *c4k = bpf_ringbuf_reserve(&rb,
> +						sizeof(struct capture_4k), 0);
> +
> +		if (!c4k)
> +			return 0;
> +
> +		metadata_fill(&c4k->meta, skb);
> +
> +		if (bpf_probe_read_kernel(c4k->packet, len, data) < 0)
> +			bpf_ringbuf_discard(c4k, BPF_RB_NO_WAKEUP);
> +		else
> +			bpf_ringbuf_submit(c4k, 0);
> +
> +		return 0;
> +	}
> +
> +	if (len <= sizeof(struct capture_8k) - sizeof(struct metadata)) {
> +		struct capture_8k *c8k = bpf_ringbuf_reserve(&rb,
> +						sizeof(struct capture_8k), 0);
> +
> +		if (!c8k)
> +			return 0;
> +
> +		metadata_fill(&c8k->meta, skb);
> +
> +		if (bpf_probe_read_kernel(c8k->packet, len, data) < 0)
> +			bpf_ringbuf_discard(c8k, BPF_RB_NO_WAKEUP);
> +		else
> +			bpf_ringbuf_submit(c8k, 0);
> +
> +		return 0;
> +	}
> +
> +	/* 16384 is the largest packet size for genl currently */
> +	if (len <= 16384) {
> +		struct capture_16k *c16k = bpf_ringbuf_reserve(&rb,
> +						sizeof(struct capture_16k), 0);
> +
> +		if (!c16k)
> +			return 0;
> +
> +		metadata_fill(&c16k->meta, skb);
> +
> +		if (bpf_probe_read_kernel(c16k->packet, len, data) < 0)
> +			bpf_ringbuf_discard(c16k, BPF_RB_NO_WAKEUP);
> +		else
> +			bpf_ringbuf_submit(c16k, 0);
> +
> +		return 0;
> +	}
> +
> +	return 0;
> +}
> +
> +SEC("fentry/__netlink_sendskb")
> +int BPF_PROG(trace___netlink_sendskb, struct sock *sk, struct sk_buff *skb)
> +{
> +	return capture_common(skb);
> +}
> +
> +SEC("fentry/netlink_rcv_skb")
> +int BPF_PROG(trace_netlink_rcv_skb, struct sk_buff *skb,
> +					int (*cb)(struct sk_buff *,
> +                                                   struct nlmsghdr *,
> +                                                   struct netlink_ext_ack *))
> +{
> +	return capture_common(skb);
> +}
> diff --git a/monitor/iwtrace.c b/monitor/iwtrace.c
> new file mode 100644
> index 000000000000..ceff71753463
> --- /dev/null
> +++ b/monitor/iwtrace.c
> @@ -0,0 +1,218 @@
> +/*
> + * Copyright (C) 2023  Cruise, LLC
> + *
> + * SPDX-License-Identifier: LGPL-2.1-or-later
> + */
> +
> +#ifdef HAVE_CONFIG_H
> +#include <config.h>
> +#endif
> +
> +#define _GNU_SOURCE
> +#include <argp.h>
> +#include <signal.h>
> +#include <stdio.h>
> +#include <time.h>
> +#include <inttypes.h>
> +#include <arpa/inet.h>
> +#include <sys/resource.h>
> +#include <bpf/libbpf.h>
> +#include <linux/rtnetlink.h>
> +#include "iwtrace.h"
> +#include "iwtrace.skel.h"
> +#include "monitor/nlmon.h"
> +#include "monitor/pcap.h"
> +#include "monitor/display.h"
> +
> +#include <ell/ell.h>
> +
> +static struct iwtrace_bpf *skel;
> +static struct ring_buffer *rb;
> +static struct l_io *io;
> +static struct l_genl *genl;
> +static struct nlmon_config config;
> +static const char *writer_path;
> +static struct nlmon *nlmon;
> +
> +#ifndef ARPHRD_NETLINK
> +#define ARPHRD_NETLINK	824
> +#endif
> +
> +static int libbpf_print_fn(enum libbpf_print_level level,
> +				const char *format, va_list args)
> +{
> +	return vfprintf(stderr, format, args);
> +}
> +
> +static int handle_packet(void *ctx, void *data, size_t size)
> +{
> +	struct metadata *meta = data;
> +	struct timeval tv;
> +
> +	data += sizeof(struct metadata);
> +
> +	tv.tv_sec = meta->timestamp / L_NSEC_PER_SEC;
> +	tv.tv_usec = (meta->timestamp % L_NSEC_PER_SEC) / L_NSEC_PER_USEC;
> +
> +	switch (meta->protocol) {
> +	case NETLINK_ROUTE:
> +		nlmon_print_rtnl(nlmon, &tv, data, meta->len);
> +		break;
> +	case NETLINK_GENERIC:
> +		nlmon_print_genl(nlmon, &tv, data, meta->len);
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static bool ringbuf_receive(struct l_io *io, void *user_data)
> +{
> +	ring_buffer__poll(rb, 0);
> +
> +	return true;
> +}
> +
> +static void nl80211_appeared(const struct l_genl_family_info *info,
> +					void *user_data)
> +{
> +	int err;
> +
> +	err = iwtrace_bpf__attach(skel);
> +	if (err) {
> +		fprintf(stderr, "Unable to attach eBPF program\n");
> +		goto failed;
> +	}
> +
> +	rb = ring_buffer__new(bpf_map__fd(skel->maps.rb),
> +					handle_packet, NULL, NULL);
> +	if (!rb) {
> +		fprintf(stderr, "Failed to create ringbuffer\n");
> +		goto failed;
> +	}
> +
> +	nlmon = nlmon_open(l_genl_family_info_get_id(info),
> +						writer_path, &config);
> +	if (!nlmon)
> +		goto failed;
> +
> +	io = l_io_new(bpf_map__fd(skel->maps.rb));
> +	l_io_set_close_on_destroy(io, false);
> +	l_io_set_read_handler(io, ringbuf_receive, NULL, NULL);
> +
> +	return;
> +failed:
> +	l_main_quit();
> +}
> +
> +static void signal_handler(uint32_t signo, void *user_data)
> +{
> +	switch (signo) {
> +	case SIGINT:
> +	case SIGTERM:
> +		l_main_quit();
> +		break;
> +	}
> +}
> +
> +static void usage(void)
> +{
> +	printf("iwtrace - Wireless monitor using eBPF\n"
> +		"Usage:\n");
> +	printf("\tiwtrace [options]\n");
> +	printf("Options:\n"
> +		"\t-w, --write <file>     Write netlink PCAP trace file\n"
> +		"\t-n, --nortnl           Don't show RTNL output\n"
> +		"\t-y, --nowiphy          Don't show 'New Wiphy' output\n"
> +		"\t-s, --noscan           Don't show scan result output\n"
> +		"\t-e, --noies            Don't show IEs except SSID\n"
> +		"\t-h, --help             Show help options\n");
> +}
> +
> +static const struct option main_options[] = {
> +	{ "write",     required_argument, NULL, 'w' },
> +	{ "nortnl",    no_argument,       NULL, 'n' },
> +	{ "nowiphy",   no_argument,       NULL, 'y' },
> +	{ "noscan",    no_argument,       NULL, 's' },
> +	{ "noies",     no_argument,       NULL, 'e' },
> +	{ "version",   no_argument,       NULL, 'v' },
> +	{ "help",      no_argument,       NULL, 'h' },
> +	{ }
> +};
> +
> +int main(int argc, char *argv[])
> +{
> +	int exit_status = EXIT_FAILURE;
> +
> +	for (;;) {
> +		int opt;
> +
> +		opt = getopt_long(argc, argv, "w:nvhyse",
> +						main_options, NULL);
> +		if (opt < 0)
> +			break;
> +
> +		switch (opt) {
> +		case 'w':
> +			writer_path = optarg;
> +			break;
> +		case 'n':
> +			config.nortnl = true;
> +			break;
> +		case 'y':
> +			config.nowiphy = true;
> +			break;
> +		case 's':
> +			config.noscan = true;
> +			break;
> +		case 'e':
> +			config.noies = true;
> +			break;
> +		case 'v':
> +			printf("%s\n", VERSION);
> +			return EXIT_SUCCESS;
> +		case 'h':
> +			usage();
> +			return EXIT_SUCCESS;
> +		default:
> +			return EXIT_FAILURE;
> +		}
> +	}
> +
> +	if (argc - optind > 0) {
> +		fprintf(stderr, "Invalid command line parameters\n");
> +		return EXIT_FAILURE;
> +	}
> +
> +	libbpf_set_print(libbpf_print_fn);
> +
> +	skel = iwtrace_bpf__open_and_load();
> +	if (!skel)
> +		return EXIT_FAILURE;
> +
> +	fprintf(stdout, "Wireless monitor (eBPF) ver %s\n", VERSION);
> +
> +	if (!l_main_init())
> +		goto init_failed;
> +
> +	genl = l_genl_new();
> +	if (!genl) {
> +		fprintf(stderr, "Failed to open generic netlink socket\n");
> +		goto genl_failed;
> +	}
> +
> +	l_genl_request_family(genl, "nl80211", nl80211_appeared, NULL, NULL);
> +	exit_status = l_main_run_with_signal(signal_handler, NULL);
> +
> +	l_genl_unref(genl);
> +	nlmon_close(nlmon);
> +	l_io_destroy(io);
> +	ring_buffer__free(rb);
> +
> +genl_failed:
> +	l_main_exit();
> +init_failed:
> +	iwtrace_bpf__destroy(skel);
> +
> +	return exit_status;
> +}
> diff --git a/monitor/iwtrace.h b/monitor/iwtrace.h
> new file mode 100644
> index 000000000000..0df95f2ea989
> --- /dev/null
> +++ b/monitor/iwtrace.h
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
> +/* Copyright (c) 2020 Facebook */
> +#ifndef __BOOTSTRAP_H
> +#define __BOOTSTRAP_H
> +
> +struct metadata {
> +	uint64_t timestamp;
> +	uint16_t len;
> +	uint16_t protocol;
> +} __attribute__ ((packed));
> +
> +#endif /* __BOOTSTRAP_H */

  reply	other threads:[~2024-01-02 13:30 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-12-27  6:09 [PATCH 1/5] monitor: Remove unused code path Denis Kenzior
2023-12-27  6:09 ` [PATCH 2/5] monitor: Remove unused PACKET_AUXDATA handling Denis Kenzior
2023-12-27  6:09 ` [PATCH 3/5] monitor: Use nlmon_print_* inside nlmon_receive Denis Kenzior
2023-12-27  6:09 ` [PATCH 4/5] monitor: Move iwmon reading logic into main.c Denis Kenzior
2023-12-27  6:09 ` [PATCH 5/5] RFC: Initial iwtrace utility Denis Kenzior
2024-01-02 13:30   ` James Prestwood [this message]
2024-01-02 17:01 ` [PATCH 1/5] monitor: Remove unused code path Denis Kenzior

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=f787daac-e8c7-4cf1-9405-c3b13a8e164e@gmail.com \
    --to=prestwoj@gmail.com \
    --cc=denkenz@gmail.com \
    --cc=iwd@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox