From db4df5f44994e5cff3ee2bd278355f3f83f5c4d2 Mon Sep 17 00:00:00 2001 From: Hengqi Chen Date: Wed, 12 May 2021 08:43:15 +0800 Subject: [PATCH] libbpf-tools: add bindsnoop Signed-off-by: Hengqi Chen --- libbpf-tools/.gitignore | 1 + libbpf-tools/Makefile | 1 + libbpf-tools/bindsnoop.bpf.c | 132 +++++++++++++++++++ libbpf-tools/bindsnoop.c | 246 +++++++++++++++++++++++++++++++++++ libbpf-tools/bindsnoop.h | 31 +++++ 5 files changed, 411 insertions(+) create mode 100644 libbpf-tools/bindsnoop.bpf.c create mode 100644 libbpf-tools/bindsnoop.c create mode 100644 libbpf-tools/bindsnoop.h diff --git a/libbpf-tools/.gitignore b/libbpf-tools/.gitignore index 33391e969928..46802514cfd4 100644 --- a/libbpf-tools/.gitignore +++ b/libbpf-tools/.gitignore @@ -1,4 +1,5 @@ /.output +/bindsnoop /biolatency /biopattern /biosnoop diff --git a/libbpf-tools/Makefile b/libbpf-tools/Makefile index 3ee0c552bb80..3fd356d8173d 100644 --- a/libbpf-tools/Makefile +++ b/libbpf-tools/Makefile @@ -16,6 +16,7 @@ $(error Architecture $(ARCH) is not supported yet. Please open an issue) endif APPS = \ + bindsnoop \ biolatency \ biopattern \ biosnoop \ diff --git a/libbpf-tools/bindsnoop.bpf.c b/libbpf-tools/bindsnoop.bpf.c new file mode 100644 index 000000000000..bcbfc5422ced --- /dev/null +++ b/libbpf-tools/bindsnoop.bpf.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* Copyright (c) 2021 Hengqi Chen */ +#include +#include +#include +#include +#include +#include "bindsnoop.h" +#include "maps.bpf.h" + +#define MAX_ENTRIES 10240 +#define MAX_PORTS 1024 + +const volatile pid_t target_pid = 0; +const volatile bool ignore_errors = true; +const volatile bool filter_by_port = false; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_ENTRIES); + __type(key, __u32); + __type(value, struct socket *); +} sockets SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_PORTS); + __type(key, __u16); + __type(value, __u16); +} ports SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} events SEC(".maps"); + +static int probe_entry(struct pt_regs *ctx, struct socket *socket) +{ + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u32 pid = pid_tgid >> 32; + __u32 tid = (__u32)pid_tgid; + + if (target_pid && target_pid != pid) + return 0; + + bpf_map_update_elem(&sockets, &tid, &socket, BPF_ANY); + return 0; +}; + +static int probe_exit(struct pt_regs *ctx, short ver) +{ + __u64 pid_tgid = bpf_get_current_pid_tgid(); + __u32 pid = pid_tgid >> 32; + __u32 tid = (__u32)pid_tgid; + struct socket **socketp, *socket; + struct inet_sock *inet_sock; + struct sock *sock; + union bind_options opts; + struct bind_event event = {}; + __u16 sport = 0, *port; + int ret; + + socketp = bpf_map_lookup_elem(&sockets, &tid); + if (!socketp) + return 0; + + ret = PT_REGS_RC(ctx); + if (ignore_errors && ret != 0) + goto cleanup; + + socket = *socketp; + sock = BPF_CORE_READ(socket, sk); + inet_sock = (struct inet_sock *)sock; + + sport = bpf_ntohs(BPF_CORE_READ(inet_sock, inet_sport)); + port = bpf_map_lookup_elem(&ports, &sport); + if (filter_by_port && !port) + goto cleanup; + + opts.fields.freebind = BPF_CORE_READ_BITFIELD_PROBED(inet_sock, freebind); + opts.fields.transparent = BPF_CORE_READ_BITFIELD_PROBED(inet_sock, transparent); + opts.fields.bind_address_no_port = BPF_CORE_READ_BITFIELD_PROBED(inet_sock, bind_address_no_port); + opts.fields.reuseaddress = BPF_CORE_READ_BITFIELD_PROBED(sock, __sk_common.skc_reuse); + opts.fields.reuseport = BPF_CORE_READ_BITFIELD_PROBED(sock, __sk_common.skc_reuseport); + event.opts = opts.data; + event.ts_us = bpf_ktime_get_ns() / 1000; + event.pid = pid; + event.port = sport; + event.bound_dev_if = BPF_CORE_READ(sock, __sk_common.skc_bound_dev_if); + event.ret = ret; + event.proto = BPF_CORE_READ_BITFIELD_PROBED(sock, sk_protocol); + bpf_get_current_comm(&event.task, sizeof(event.task)); + if (ver == 4) { + event.ver = ver; + bpf_probe_read_kernel(&event.addr, sizeof(event.addr), &inet_sock->inet_saddr); + } else { /* ver == 6 */ + event.ver = ver; + bpf_probe_read_kernel(&event.addr, sizeof(event.addr), sock->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); + } + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + +cleanup: + bpf_map_delete_elem(&sockets, &tid); + return 0; +} + +SEC("kprobe/inet_bind") +int BPF_KPROBE(ipv4_bind_entry, struct socket *socket) +{ + return probe_entry(ctx, socket); +} + +SEC("kretprobe/inet_bind") +int BPF_KRETPROBE(ipv4_bind_exit) +{ + return probe_exit(ctx, 4); +} + +SEC("kprobe/inet6_bind") +int BPF_KPROBE(ipv6_bind_entry, struct socket *socket) +{ + return probe_entry(ctx, socket); +} + +SEC("kretprobe/inet6_bind") +int BPF_KRETPROBE(ipv6_bind_exit) +{ + return probe_exit(ctx, 6); +} + +char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/libbpf-tools/bindsnoop.c b/libbpf-tools/bindsnoop.c new file mode 100644 index 000000000000..05bbd3fa37ab --- /dev/null +++ b/libbpf-tools/bindsnoop.c @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ + +/* + * Copyright (c) 2021 Hengqi Chen + * + * Based on bindsnoop(8) from BCC by Pavel Dubovitsky. + * 11-May-2021 Hengqi Chen Created this. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "bindsnoop.h" +#include "bindsnoop.skel.h" +#include "trace_helpers.h" + +#define PERF_BUFFER_PAGES 16 +#define PERF_POLL_TIMEOUT_MS 100 +#define warn(...) fprintf(stderr, __VA_ARGS__) + +static volatile sig_atomic_t exiting = 0; + +static bool emit_timestamp = false; +static pid_t target_pid = 0; +static bool ignore_errors = true; +static char *target_ports = NULL; + +const char *argp_program_version = "bindsnoop 0.1"; +const char *argp_program_bug_address = + "https://github.com/iovisor/bcc/tree/master/libbpf-tools"; +const char argp_program_doc[] = +"Trace bind syscalls.\n" +"\n" +"USAGE: bindsnoop [-h] [-t] [-x] [-p PID] [-P ports]\n" +"\n" +"EXAMPLES:\n" +" bindsnoop # trace all bind syscall\n" +" bindsnoop -t # include timestamps\n" +" bindsnoop -x # include errors on output\n" +" bindsnoop -p 1216 # only trace PID 1216\n" +" bindsnoop -P 80,81 # only trace port 80 and 81\n" +"\n" +"Socket options are reported as:\n" +" SOL_IP IP_FREEBIND F....\n" +" SOL_IP IP_TRANSPARENT .T...\n" +" SOL_IP IP_BIND_ADDRESS_NO_PORT ..N..\n" +" SOL_SOCKET SO_REUSEADDR ...R.\n" +" SOL_SOCKET SO_REUSEPORT ....r\n"; + +static const struct argp_option opts[] = { + { "timestamp", 't', NULL, 0, "Include timestamp on output" }, + { "failed", 'x', NULL, 0, "Include errors on output." }, + { "pid", 'p', "PID", 0, "Process ID to trace" }, + { "ports", 'P', "PORTS", 0, "Comma-separated list of ports to trace." }, + { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" }, + {}, +}; + +static error_t parse_arg(int key, char *arg, struct argp_state *state) +{ + long pid, port_num; + char *port; + + switch (key) { + case 'p': + errno = 0; + pid = strtol(arg, NULL, 10); + if (errno || pid <= 0) { + warn("Invalid PID: %s\n", arg); + argp_usage(state); + } + target_pid = pid; + break; + case 'P': + if (!arg) { + warn("No ports specified\n"); + argp_usage(state); + } + target_ports = strdup(arg); + port = strtok(arg, ","); + while (port) { + port_num = strtol(port, NULL, 10); + if (errno || port_num <= 0 || port_num > 65536) { + warn("Invalid ports: %s\n", arg); + argp_usage(state); + } + port = strtok(NULL, ","); + } + break; + case 'x': + ignore_errors = false; + break; + case 't': + emit_timestamp = true; + break; + case 'h': + argp_state_help(state, stderr, ARGP_HELP_STD_HELP); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static void sig_int(int signo) +{ + exiting = 1; +} + +static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) +{ + struct bind_event *e = data; + time_t t; + struct tm *tm; + char ts[32], addr[48]; + char opts[] = {'F', 'T', 'N', 'R', 'r', '\0'}; + const char *proto; + int i = 0; + + if (emit_timestamp) { + time(&t); + tm = localtime(&t); + strftime(ts, sizeof(ts), "%H:%M:%S", tm); + printf("%8s ", ts); + } + if (e->proto == IPPROTO_TCP) + proto = "TCP"; + else if (e->proto == IPPROTO_UDP) + proto = "UDP"; + else + proto = "UNK"; + while (opts[i]) { + if (!((1 << i) & e->opts)) { + opts[i] = '.'; + } + i++; + } + if (e->ver == 4) { + inet_ntop(AF_INET, &e->addr, addr, sizeof(addr)); + } else { + inet_ntop(AF_INET6, &e->addr, addr, sizeof(addr)); + } + printf("%-7d %-16s %-3d %-5s %-5s %-4d %-5d %-48s\n", + e->pid, e->task, e->ret, proto, opts, e->bound_dev_if, e->port, addr); +} + +static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) +{ + warn("lost %llu events on CPU #%d\n", lost_cnt, cpu); +} + +int main(int argc, char **argv) +{ + static const struct argp argp = { + .options = opts, + .parser = parse_arg, + .doc = argp_program_doc, + }; + struct perf_buffer_opts pb_opts; + struct perf_buffer *pb = NULL; + struct bindsnoop_bpf *obj; + int err, port_map_fd; + char *port; + short port_num; + + err = argp_parse(&argp, argc, argv, 0, NULL, NULL); + if (err) + return err; + + err = bump_memlock_rlimit(); + if (err) { + warn("failed to increase rlimit: %d\n", err); + return 1; + } + + obj = bindsnoop_bpf__open(); + if (!obj) { + warn("failed to open BPF object\n"); + return 1; + } + + obj->rodata->target_pid = target_pid; + obj->rodata->ignore_errors = ignore_errors; + obj->rodata->filter_by_port = target_ports != NULL; + + err = bindsnoop_bpf__load(obj); + if (err) { + warn("failed to load BPF object: %d\n", err); + goto cleanup; + } + + if (target_ports) { + port_map_fd = bpf_map__fd(obj->maps.ports); + port = strtok(target_ports, ","); + while (port) { + port_num = strtol(port, NULL, 10); + bpf_map_update_elem(port_map_fd, &port_num, &port_num, BPF_ANY); + port = strtok(NULL, ","); + } + } + + err = bindsnoop_bpf__attach(obj); + if (err) { + warn("failed to attach BPF programs: %d\n", err); + goto cleanup; + } + + pb_opts.sample_cb = handle_event; + pb_opts.lost_cb = handle_lost_events; + pb = perf_buffer__new(bpf_map__fd(obj->maps.events), + PERF_BUFFER_PAGES, &pb_opts); + err = libbpf_get_error(pb); + if (err) { + warn("failed to open perf buffer: %d\n", err); + goto cleanup; + } + + if (signal(SIGINT, sig_int) == SIG_ERR) { + warn("can't set signal handler: %s\n", strerror(-errno)); + goto cleanup; + } + + if (emit_timestamp) + printf("%-8s ", "TIME(s)"); + printf("%-7s %-16s %-3s %-5s %-5s %-4s %-5s %-48s\n", + "PID", "COMM", "RET", "PROTO", "OPTS", "IF", "PORT", "ADDR"); + + while (1) { + if ((err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS)) < 0) + break; + if (exiting) + goto cleanup; + } + warn("error polling perf buffer: %d\n", err); + +cleanup: + bindsnoop_bpf__destroy(obj); + + return err != 0; +} diff --git a/libbpf-tools/bindsnoop.h b/libbpf-tools/bindsnoop.h new file mode 100644 index 000000000000..1c881b03ec0d --- /dev/null +++ b/libbpf-tools/bindsnoop.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __BINDSNOOP_H +#define __BINDSNOOP_H + +#define TASK_COMM_LEN 16 + +struct bind_event { + unsigned __int128 addr; + __u64 ts_us; + __u32 pid; + __u32 bound_dev_if; + int ret; + __u16 port; + __u8 opts; + __u8 proto; + __u8 ver; + char task[TASK_COMM_LEN]; +}; + +union bind_options { + __u8 data; + struct { + __u8 freebind : 1; + __u8 transparent : 1; + __u8 bind_address_no_port : 1; + __u8 reuseaddress : 1; + __u8 reuseport : 1; + } fields; +}; + +#endif /* __BINDSNOOP_H */