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 */
next prev parent 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