From patchwork Wed Dec 27 06:09:50 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Denis Kenzior X-Patchwork-Id: 13505154 Received: from mail-oa1-f51.google.com (mail-oa1-f51.google.com [209.85.160.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 95B1B4C9F for ; Wed, 27 Dec 2023 06:10:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="AYQLRzf5" Received: by mail-oa1-f51.google.com with SMTP id 586e51a60fabf-204e1203a22so74918fac.1 for ; Tue, 26 Dec 2023 22:10:44 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1703657443; x=1704262243; darn=lists.linux.dev; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=R5Se5BGCQUSsT2Yvzo8uqyBZrNXRRIs2rsNO8xOD9no=; b=AYQLRzf54S0iBAt0iLfp8s1WSaUcscfOYIfuLZMMD3vvmJ3J+y+BocNUSbw/cg8b5t f3UX9mNFA1HkYEL65gZ8rXra3GrA70LtyNn87vmjkTMVMK5hJN2w488AGkY9Cy611y+E 5sqyDrrmGXQlqAUxyZ/Xu9W4XU4feYyriTKnxD62DZC6LLAnMcyHR8YMuGSCxXcoo1He Xx44ad0LceUIpHLgzpXaom3IRbTp2trq2hNR5tkWY94Hu4oKtKYwg9/EiUai9A2iIg99 ffvA6sZp5Kz5dRJcY2MAMi0Sib2f5iaXk2QWW/FlS1TET4XqPhGbfaWkAF8aH7wPoWzU RC0A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1703657443; x=1704262243; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=R5Se5BGCQUSsT2Yvzo8uqyBZrNXRRIs2rsNO8xOD9no=; b=JSKEfCbIHEPo6X3CUahRYkEZfymVSxTz/WfOAcFlne7Un5vtS8yvCpk4WildS6fSyN Xr1yJngF/lJw1rcR8XgKLKTCzwfHE6+MxS4bzKjIfplsjOO79WqmR0tzhRnKJ/vvxXNX 8CFa6vqwF0R8ARAl+165fL3ydjdH+no5MtY5qyWki8iiVNrQFgMcSmx8fWDHBKWYGEnj 5yHYJqCSjSZD+/MGvOegTyZSufzz+6zQRPT0GODHCdpEYzZXmSuGgDwSMli7jMO0nFIf az/rET1/i27poLSICXURXDzQ9sOEQqR95wnHYFzHdlEYIgeglj+4eYYbYWO4mUZvl2jv LO7A== X-Gm-Message-State: AOJu0YzBbm3es44XdQ431e02NnX5LyvZG/33QH74drMTrJoEw/wTWRTc Cte1VDTfpNt5910AIvCufLj9nDyXt3Q= X-Google-Smtp-Source: AGHT+IFA1/yTqpSG776Fhs5KJSY1nDRYJsbZhiSKEguZ04sUGt4W5a2NO8qDs44YZ2SwHVCqVXvw7Q== X-Received: by 2002:a05:6870:2192:b0:203:dfdc:a80a with SMTP id l18-20020a056870219200b00203dfdca80amr7084754oae.112.1703657443337; Tue, 26 Dec 2023 22:10:43 -0800 (PST) Received: from localhost.localdomain (216.106.68.145.reverse.socket.net. [216.106.68.145]) by smtp.gmail.com with ESMTPSA id vs7-20020a056871a10700b002032bb7895fsm3048841oab.55.2023.12.26.22.10.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 26 Dec 2023 22:10:43 -0800 (PST) From: Denis Kenzior To: iwd@lists.linux.dev Cc: Denis Kenzior Subject: [PATCH 5/5] RFC: Initial iwtrace utility Date: Wed, 27 Dec 2023 00:09:50 -0600 Message-ID: <20231227060954.103572-5-denkenz@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20231227060954.103572-1-denkenz@gmail.com> References: <20231227060954.103572-1-denkenz@gmail.com> Precedence: bulk X-Mailing-List: iwd@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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. --- 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 +#include +#include +#include +#include +#include +#include +#include +#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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iwtrace.h" +#include "iwtrace.skel.h" +#include "monitor/nlmon.h" +#include "monitor/pcap.h" +#include "monitor/display.h" + +#include + +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 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 */