From: Mikolaj Kucharski Subject: Re: bpflogd(8): capture packets via BPF to log files To: David Gwynne Cc: tech@openbsd.org Date: Mon, 5 May 2025 16:14:35 +0000 Hi. I don't have an usecase for this software every day, but I did had in the past, when I was troubleshooting networking problem. I find this tool extremly useful when the day comes to analyze rare network issues. I would find it strong addition to a troubleshooting tools in OpenBSD. If not in base, at least in ports. I have poors man version of it, via an infinite while loop in a shell script with: tcpdump -c $count -n -i $if $opts -s 4096 -w "$dir/$pcap" On Thu, Apr 24, 2025 at 03:44:53PM +1000, David Gwynne wrote: > this is basically pflogd(8), but different. > > the reason it exists is because i needed to continously log some packets > from span ports coming from multiple switches to try and help debug a > network issue that only seems to occur every couple of months. pflogd > provides that for a single pflog interface, but i needed it on multiple > ethernet interfaces. > > im sending this out in case anyone else is interested in it. > > the main differences are: > > - it can log on any type of BPF interface and DLT, not just pflog(4) > - a single bpflogd can log packets from multiple BPF interfaces to the > one log file > - it uses libevent and non-blocking FDs instead of a blocking on > pcap_dispatch in a loop with crazy signal handling > - this also avoids restartable syscalls > - it uses unveil and drops privs instead of chroot+privsep > - this means the log file has to be writable by the user bpflogd > runs as so it can reopen it after rotation. > - it captures full packets by default, not just 160 bytes > - you can provide a pcap-filter expression in a file > - this makes the pexp handling in rc.subr a lot more robust > - the .c files are about half the number of lines > - but it uses libevent so maybe this doesnt matter > > the network issue i was dealing with was dhcp related, so i was > running it like this: > > xdlg@sundew etc$ cat rc.d/bpflog_dhcp > #!/bin/ksh > > daemon="/opt/local/sbin/bpflogd" > daemon_flags="-f /var/log/dhcp.pcap -F /etc/bpflog_dhcp.bpfilter -i mcx1 -i mcx2" > > . /etc/rc.d/rc.subr > > rc_cmd $1 > xdlg@sundew etc$ cat /etc/bpflog_dhcp.bpfilter > vlan and udp and ((ip and (port bootpc or port bootps)) or (ip6 and (port dhcpv6-client or port dhcpv6-server))) > xdlg@sundew etc$ ls -l /var/log/dhcp.pcap > -rw-r----- 1 _pflogd sysadm 41027133 Apr 24 15:01 /var/log/dhcp.pcap > xdlg@sundew etc$ grep bpflog_dhcp /etc/newsyslog.conf > /var/log/dhcp.pcap 640 7 100000 * ZB "rcctl reload bpflog_dhcp > /dev/null" > > rcctl reload sends HUP by default, which is what bpflogd (and pflogd) > use to trigger a reopen of their logfiles. > > for fun i replaced pflogd with bpflogd by changing /etc/rc.d/pflogd on a > few boxes: > > xdlg@sundew rc.d$ diff -u pflogd /etc/rc.d/pflogd > --- pflogd Fri Jan 12 05:52:12 2018 > +++ /etc/rc.d/pflogd Thu Feb 13 11:59:50 2025 > @@ -2,11 +2,10 @@ > # > # $OpenBSD: pflogd,v 1.3 2018/01/11 19:52:12 rpe Exp $ > > -daemon="/sbin/pflogd" > +daemon="/opt/local/sbin/bpflogd" > +daemon_flags="-f /var/log/pflog -i pflog0 -s 160" > > . /etc/rc.d/rc.subr > - > -pexp="pflogd: \[priv\]" > > rc_pre() { > if pfctl -si | grep -q Enabled; then > > Index: bpflogd/Makefile > =================================================================== > RCS file: bpflogd/Makefile > diff -N bpflogd/Makefile > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ bpflogd/Makefile 24 Apr 2025 03:56:54 -0000 > @@ -0,0 +1,13 @@ > + > +PROG= bpflogd > +SRCS= bpflogd.c > +SRCS+= log.c > +MAN= bpflogd.8 > + > +LDADD=-lpcap -levent > +DPADD=${LIBPCAP} ${LIBEVENT} > + > +DEBUG=-g > +WARNINGS=yes > + > +.include > Index: bpflogd/bpflogd.8 > =================================================================== > RCS file: bpflogd/bpflogd.8 > diff -N bpflogd/bpflogd.8 > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ bpflogd/bpflogd.8 24 Apr 2025 03:56:54 -0000 > @@ -0,0 +1,129 @@ > +.\" $OpenBSD: pflogd.8,v 1.51 2019/08/30 17:51:47 jmc Exp $ > +.\" > +.\" Copyright (c) 2001 Can Erkin Acar. All rights reserved. > +.\" > +.\" Redistribution and use in source and binary forms, with or without > +.\" modification, are permitted provided that the following conditions > +.\" are met: > +.\" 1. Redistributions of source code must retain the above copyright > +.\" notice, this list of conditions and the following disclaimer. > +.\" 2. Redistributions in binary form must reproduce the above copyright > +.\" notice, this list of conditions and the following disclaimer in the > +.\" documentation and/or other materials provided with the distribution. > +.\" 3. The name of the author may not be used to endorse or promote products > +.\" derived from this software without specific prior written permission. > +.\" > +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR > +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES > +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. > +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, > +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT > +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, > +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY > +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF > +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. > +.\" > +.Dd $Mdocdate: August 30 2019 $ > +.Dt BPFLOGD 8 > +.Os > +.Sh NAME > +.Nm bpflogd > +.Nd Berkely Packet Filter logging daemon > +.Sh SYNOPSIS > +.Nm bpflogd > +.Op Fl dpP > +.Op Fl F Ar filterfile > +.Op Fl s Ar snaplen > +.Op Fl w Ar waitms > +.Op Fl y Ar datalinktype > +.Fl f Ar filename > +.Fl i Ar interface > +.Op Ar expression > +.Sh DESCRIPTION > +.Nm > +is a daemon which captures packets using > +.Xr bpf 4 > +and writes the packets to a logfile > +in > +.Xr tcpdump 8 > +binary format. > +These logs can be reviewed later using the > +.Fl r > +option of > +.Xr tcpdump 8 . > +.Pp > +.Nm > +closes and then re-opens the log file when it receives > +.Dv SIGHUP , > +permitting > +.Xr newsyslog 8 > +to rotate logfiles automatically. > +.Pp > +If the log file contains data when starting or a > +.Dv SIGHUP , > +the PCAP header is checked before new logs are appended to the existing file. > +.Pp > +The options are as follows: > +.Bl -tag -width Ds > +.It Fl d > +Debugging mode. > +.Nm > +does not daemonise and logs to the terminal. > +.It Fl f Ar filename > +Log output filename. > +The file must already exist, and be readable and writable by the > +_pflogd user. > +.It Fl F Ar filterfile > +Specify a file containing a filter expression as per > +.Xr pcap-filter 5 . > +.It Fl i Ar interface > +Specifies the interface to capture packets on using > +.Xr bpf 4 . > +This can be specified multiple times to capture packets from multiple > +interfaces, but all the interfaces must support the same datalink type. > +.It Fl p > +Do not put the interfaces into promiscuous mode. > +This is the default. > +.It Fl P > +Put the interfaces into promiscuous mode. > +.It Fl s Ar snaplen > +Capture at most the first > +.Ar snaplen > +bytes of data from each packet. > +By default > +.Nm > +captures whole packets. > +.It Fl w Ar waitms > +Specify the maximum amount of time in milliseconds between when a > +packet is captured and when it will be written to the log file. > +The default > +.Ar waitms > +value is 2000 milliseconds. > +.It Fl y Ar datalinktype > +Specify the datalink type when capturing packets. > +If this is not specified then the default datalink type on the first > +interface is used. > +.It Ar expression > +Specify a filter expression for matching packets as per > +.Xr pcap-filter 5 . > +.El > +.Pp > +A filter expression may only be specified by a file with > +.Ar -F > +or as arguments on the command line, specifying both is unsupported. > +If a filter is not provided then all packets are captured. > +.Sh SEE ALSO > +.Xr pcap_open_live 3 , > +.Xr pcap-filter 5 , > +.Xr newsyslog 8 , > +.Xr tcpdump 8 > +.Sh HISTORY > +The > +.Nm > +command appeared in > +.Ox 7.8 . > +.\" .Sh AUTHORS > +.\" .Nm > +.\" was written by > +.\" .An David Gwynne Aq Mt dlg@uq.edu.au . > Index: bpflogd/bpflogd.c > =================================================================== > RCS file: bpflogd/bpflogd.c > diff -N bpflogd/bpflogd.c > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ bpflogd/bpflogd.c 24 Apr 2025 03:56:54 -0000 > @@ -0,0 +1,686 @@ > +/* */ > + > +/* > + * Copyright (c) 2025 The University of Queensland > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > + > +#include > +#include > + > +#include > +#include > + > +#include "log.h" > + > +#ifndef nitems > +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) > +#endif > + > +int rdaemon(int); > + > +#define BPFLOGD_USER "_pflogd" > + > +#define _DEV_BPF "/dev/bpf" > + > +struct bpflogd; > + > +struct bpfif { > + struct bpflogd *bif_bd; > + const char *bif_name; > + int bif_bpf; > + > + struct event bif_ev; > + > + TAILQ_ENTRY(bpfif) bif_entry; > +}; > + > +TAILQ_HEAD(bpfif_list, bpfif); > + > +struct bpflogd { > + const char *bd_user; > + const char *bd_fname; > + int bd_fd; > + unsigned int bd_snaplen; > + int bd_dlt; > + const char *bd_dlt_name; > + > + struct bpfif_list bd_bif_list; > + > + int bd_buflen; > + void *bd_buf; > + > + struct event bd_sighup; > +}; > + > +static int bpf_maxbufsize(void); > + > +static void bpflogd_hup(int, short, void *); > + > +static void bpfif_open(struct bpfif *); > +static void bpfif_read(int, short, void *); > + > +static int bpflog_open(struct bpflogd *); > + > +__dead static void > +usage(void) > +{ > + extern char *__progname; > + > + fprintf(stderr, "usage: %s [-dpP] [-F expression] [-s snaplen] " > + "[-w delay] [-y datalinktype]" "\n" > + "\t" "-f filename -i interface [expression]\n", __progname); > + > + exit(1); > +} > + > +int > +main(int argc, char *argv[]) > +{ > + struct bpflogd _bd = { > + .bd_user = BPFLOGD_USER, > + .bd_fname = NULL, > + .bd_fd = -1, > + .bd_buflen = bpf_maxbufsize(), > + .bd_snaplen = BPF_MAXBUFSIZE, > + .bd_dlt = -1, > + .bd_bif_list = TAILQ_HEAD_INITIALIZER(_bd.bd_bif_list), > + }; > + struct bpflogd *bd = &_bd; > + struct bpfif *bif, *bif0; > + struct bpf_version bv; > + char *filter = NULL; > + struct bpf_program bf; > + struct bpf_insn insns[] = { { BPF_RET, 0, 0, -1 } }; > + int devnull = -1; > + int debug = 0; > + int promisc = 0; > + int waitms = 1000; > + struct timeval waittv; > + struct passwd *pw; > + > + int ch; > + const char *errstr; > + > + while ((ch = getopt(argc, argv, "df:F:i:pPs:u:w:y:")) != -1) { > + switch (ch) { > + case 'd': > + debug = 1; > + break; > + case 'f': > + bd->bd_fname = optarg; > + break; > + case 'F': > + filter = optarg; > + break; > + case 'i': > + TAILQ_FOREACH(bif, &bd->bd_bif_list, bif_entry) { > + if (strcmp(bif->bif_name, optarg) == 0) { > + errx(1, "interface %s already exists", > + optarg); > + } > + } > + > + bif = malloc(sizeof(*bif)); > + if (bif == NULL) > + err(1, "bpf interface alloc"); > + bif->bif_bd = bd; > + bif->bif_name = optarg; > + TAILQ_INSERT_TAIL(&bd->bd_bif_list, bif, bif_entry); > + break; > + case 'p': > + promisc = 0; > + break; > + case 'P': > + promisc = 1; > + break; > + case 's': > + bd->bd_snaplen = strtonum(optarg, 60, BPF_MAXBUFSIZE, > + &errstr); > + if (errstr != NULL) > + errx(1, "snaplen: %s", errstr); > + break; > + case 'u': > + bd->bd_user = optarg; > + break; > + case 'w': > + waitms = strtonum(optarg, 10, 300000, &errstr); > + if (errstr != NULL) > + errx(1, "wait ms: %s", errstr); > + break; > + case 'y': > + bd->bd_dlt = pcap_datalink_name_to_val(optarg); > + if (bd->bd_dlt == -1) { > + errx(1, "%s: unknown datalink type name", > + optarg); > + } > + bd->bd_dlt_name = optarg; > + break; > + default: > + usage(); > + /* NOTREACHED */ > + } > + } > + > + argc -= optind; > + argv += optind; > + > + if (bd->bd_fname == NULL) { > + warnx("output file not specified"); > + usage(); > + } > + > + bif0 = TAILQ_FIRST(&bd->bd_bif_list); > + if (bif0 == NULL) { > + warnx("no interfaces specified"); > + usage(); > + } > + > + if (filter != NULL && argc > 0) { > + warnx("use either -F or extra arguments, not both"); > + usage(); > + } > + > + signal(SIGPIPE, SIG_IGN); > + > + if (geteuid()) > + errx(1, "need root privileges"); > + > + pw = getpwnam(bd->bd_user); > + if (pw == NULL) > + errx(1, "%s: unknown user", bd->bd_user); > + > + bd->bd_buf = malloc(bd->bd_buflen); > + if (bd->bd_buf == NULL) > + err(1, "bpf read buffer"); > + > + bpfif_open(bif0); /* err on failure */ > + > + if (ioctl(bif0->bif_bpf, BIOCVERSION, &bv) == -1) > + err(1, "%s: get filter language version", bif0->bif_name); > + > + if (bv.bv_major != BPF_MAJOR_VERSION) { > + errx(1, "bpf major %u, expected %u", > + bv.bv_major, BPF_MAJOR_VERSION); > + } > + if (bv.bv_minor < BPF_MINOR_VERSION) { > + errx(1, "bpf minor %u, expected >= %u", > + bv.bv_minor, BPF_MINOR_VERSION); > + } > + > + if (bd->bd_dlt != -1) { > + if (ioctl(bif0->bif_bpf, BIOCSDLT, &bd->bd_dlt) == -1) { > + err(1, "%s: unsupported datalink type %s", > + bif0->bif_name, bd->bd_dlt_name); > + } > + } else { > + if (ioctl(bif0->bif_bpf, BIOCGDLT, &bd->bd_dlt) == -1) > + err(1, "%s: get datalink type", bif0->bif_name); > + > + bd->bd_dlt_name = pcap_datalink_val_to_name(bd->bd_dlt); > + if (bd->bd_dlt_name == NULL) { > + errx(1, "%s: datalink type %d is unknown to libpcap", > + bif0->bif_name, bd->bd_dlt); > + } > + } > + > + if (filter != NULL || argc > 0) { > + pcap_t *ph; > + char *expr = NULL; > + int i; > + > + if (filter != NULL) { > + int fd; > + ssize_t rv; > + > + fd = open(filter, O_RDONLY); > + if (fd == -1) > + err(1, "%s", filter); > + > +#define BPFLOG_FILTER_MAX 8192 > + > + expr = malloc(BPFLOG_FILTER_MAX); > + if (expr == NULL) > + err(1, NULL); > + > + rv = read(fd, expr, BPFLOG_FILTER_MAX); > + if (rv == -1) > + err(1, "%s read", filter); > + if (rv == 0) > + errx(1, "%s is empty", filter); > + if (rv >= BPFLOG_FILTER_MAX - 1) > + errx(1, "%s is too long", filter); > + > + close(fd); > + } else if (argc == 1) > + expr = argv[0]; > + else { > + size_t alen = strlen(argv[0]); > + size_t len = alen; > + > + expr = malloc(len + 1); > + if (expr == NULL) > + err(1, "bpf expression buffer"); > + > + memcpy(expr, argv[0], alen); > + > + for (i = 1; i < argc; i++) { > + size_t nlen; > + > + alen = strlen(argv[i]); > + if (alen == 0) > + continue; > + > + nlen = len + 1 + alen; > + > + expr = realloc(expr, nlen + 1); > + if (expr == NULL) > + err(1, "bpf expression buffer"); > + > + expr[len] = ' '; > + memcpy(expr + len + 1, argv[i], alen); > + > + len = nlen; > + } > + expr[len] = '\0'; > + } > + > + ph = pcap_open_dead(bd->bd_dlt, bd->bd_snaplen); > + if (ph == NULL) > + err(1, "pcap_open_dead"); > + > + if (pcap_compile(ph, &bf, expr, 1, PCAP_NETMASK_UNKNOWN) == -1) > + errx(1, "%s", pcap_geterr(ph)); > + > + pcap_close(ph); > + > + if (argc != 1) > + free(expr); > + } else { > + insns[0].k = bd->bd_snaplen; > + bf.bf_insns = insns; > + bf.bf_len = nitems(insns); > + } > + > + bif = bif0; > + while ((bif = TAILQ_NEXT(bif, bif_entry)) != NULL) { > + bpfif_open(bif); /* err on failure */ > + > + if (ioctl(bif->bif_bpf, BIOCSDLT, &bd->bd_dlt) == -1) { > + err(1, "%s: unsupported datalink type %s", > + bif->bif_name, bd->bd_dlt_name); > + } > + } > + > + waittv.tv_sec = waitms / 1000; > + waittv.tv_usec = (waitms % 1000) * 1000; > + > + TAILQ_FOREACH(bif, &bd->bd_bif_list, bif_entry) { > + if (ioctl(bif->bif_bpf, BIOCSETF, &bf) == -1) > + err(1, "%s: set filter", bif0->bif_name); > + > + if (promisc) { > + if (ioctl(bif->bif_bpf, BIOCPROMISC, NULL) == -1) > + err(1, "%s: enable promisc", bif0->bif_name); > + } > + > + if (ioctl(bif->bif_bpf, BIOCSWTIMEOUT, &waittv) == -1) > + err(1, "%s: wait ms %d", bif0->bif_name, waitms); > + } > + > + if (bf.bf_insns != insns) > + pcap_freecode(&bf); > + > + if (!debug) { > + devnull = open(_PATH_DEVNULL, O_RDWR); > + if (devnull == -1) > + err(1, "%s", _PATH_DEVNULL); > + } > + > + if (setgroups(1, &pw->pw_gid) || > + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || > + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) > + errx(1, "can't drop privileges"); > + > + endpwent(); > + > + if (unveil(bd->bd_fname, "rwc") == -1) > + err(1, "unveil %s", bd->bd_fname); > + > + bd->bd_fd = bpflog_open(bd); > + if (bd->bd_fd == -1) { > + /* error has already been printed */ > + exit(1); > + } > + > + if (!debug) { > + extern char *__progname; > + > + if (rdaemon(devnull) == -1) > + err(1, "unable to daemonize"); > + > + logger_syslog(__progname); > + } > + > + event_init(); > + > + signal_set(&bd->bd_sighup, SIGHUP, bpflogd_hup, bd); > + signal_add(&bd->bd_sighup, NULL); > + > + TAILQ_FOREACH(bif, &bd->bd_bif_list, bif_entry) { > + event_set(&bif->bif_ev, bif->bif_bpf, EV_READ | EV_PERSIST, > + bpfif_read, bif); > + event_add(&bif->bif_ev, NULL); > + } > + > + event_dispatch(); > + > + return (0); > +} > + > +static int > +bpf_maxbufsize(void) > +{ > + int mib[] = { CTL_NET, PF_BPF, NET_BPF_MAXBUFSIZE }; > + int maxbuf; > + size_t maxbufsize = sizeof(maxbuf); > + > + if (sysctl(mib, nitems(mib), &maxbuf, &maxbufsize, NULL, 0) == -1) > + return (-1); > + > + if (maxbuf > 1 << 20) > + maxbuf = 1 << 20; > + > + return (maxbuf); > +} > + > +static void > +bpflogd_hup(int nil, short events, void *arg) > +{ > + struct bpflogd *bd = arg; > + struct bpfif *bif; > + int fd; > + > + fd = bpflog_open(bd); > + > + TAILQ_FOREACH(bif, &bd->bd_bif_list, bif_entry) > + bpfif_read(bif->bif_bpf, 0, bif); > + > + close(bd->bd_fd); > + > + if (fd == -1) > + lerrx(1, "exiting"); > + > + bd->bd_fd = fd; > + > + linfo("%s turned over", bd->bd_fname); > +} > + > +static int > +bpflog_open(struct bpflogd *bd) > +{ > + const struct pcap_file_header pfh = { > + .magic = 0xa1b2c3d4, > + .version_major = PCAP_VERSION_MAJOR, > + .version_minor = PCAP_VERSION_MINOR, > + .thiszone = 0, /* we work in UTC */ > + .sigfigs = 0, > + .snaplen = BPF_MAXBUFSIZE, > + .linktype = bd->bd_dlt, > + }; > + struct pcap_file_header epfh; > + struct stat st; > + ssize_t rv; > + int fd; > + > + fd = open(bd->bd_fname, O_RDWR|O_APPEND); > + if (fd == -1) { > + lwarn("%s", bd->bd_fname); > + return (-1); > + } > + > + if (fstat(fd, &st) == -1) { > + lwarn("%s stat", bd->bd_fname); > + goto close; > + } > + > + if (st.st_size == 0) { > + rv = write(fd, &pfh, sizeof(pfh)); > + if (rv == -1) { > + lwarn("%s pcap file header write", bd->bd_fname); > + goto close; > + } > + if ((size_t)rv < sizeof(pfh)) { > + lwarnx("%s pcap file header short write", > + bd->bd_fname); > + goto close; > + } > + > + return (fd); > + } > + > + rv = pread(fd, &epfh, sizeof(epfh), 0); > + if (rv == -1) { > + lwarn("%s pcap file header read", bd->bd_fname); > + goto close; > + } > + if ((size_t)rv < sizeof(epfh)) { > + lwarn("%s pcap file header is short", bd->bd_fname); > + goto close; > + } > + > + if (epfh.magic != pfh.magic) { > + lwarnx("%s pcap file header magic is wrong", > + bd->bd_fname); > + goto close; > + } > + if (epfh.version_major != pfh.version_major || > + epfh.version_minor < pfh.version_minor) { > + lwarnx("%s pcap file header version is unsupported", > + bd->bd_fname); > + goto close; > + } > + if (epfh.thiszone != pfh.thiszone) { > + lwarnx("%s pcap file timezone is different", bd->bd_fname); > + goto close; > + } > + if (epfh.snaplen < bd->bd_snaplen) { > + lwarnx("%s pcap file snaplen is too short", bd->bd_fname); > + goto close; > + } > + if (epfh.linktype != pfh.linktype) { > + lwarnx("%s pcap file linktype is different", bd->bd_fname); > + goto close; > + } > + > + return (fd); > + > +close: > + close(fd); > + return (-1); > +} > + > +static void > +bpfif_open(struct bpfif *bif) > +{ > + struct bpflogd *bd = bif->bif_bd; > + struct ifreq ifr; > + > + bif->bif_bpf = open(_DEV_BPF, O_RDWR | O_NONBLOCK); > + if (bif->bif_bpf == -1) > + err(1, "%s: open %s", bif->bif_name, _DEV_BPF); > + > + memset(&ifr, 0, sizeof(ifr)); > + if (strlcpy(ifr.ifr_name, bif->bif_name, sizeof(ifr.ifr_name)) >= > + sizeof(ifr.ifr_name)) > + errx(1, "%s: interface name is too long", bif->bif_name); > + > + if (ioctl(bif->bif_bpf, BIOCSBLEN, &bd->bd_buflen) == -1) { > + err(1, "%s: set buffer length %d", bif->bif_name, > + bd->bd_buflen); > + } > + > + if (ioctl(bif->bif_bpf, BIOCSETIF, &ifr) == -1) > + err(1, "%s: set bpf interface", bif->bif_name); > +} > + > +#define PCAP_PKTS 64 > + > +static void > +bpfif_read(int fd, short events, void *arg) > +{ > + struct bpfif *bif = arg; > + struct bpflogd *bd = bif->bif_bd; > + ssize_t rv; > + size_t len, bpflen, caplen; > + uint8_t *buf; > + > + struct pcap_pkthdr pps[PCAP_PKTS], *pp; > + struct iovec iovs[PCAP_PKTS * 2], *iov = iovs; > + size_t np = 0; > + > + rv = read(fd, bd->bd_buf, bd->bd_buflen); > + switch (rv) { > + case -1: > + switch (errno) { > + case EINTR: > + case EAGAIN: > + break; > + default: > + lerr(1, "%s bpf read", bif->bif_name); > + /* NOTREACHED */ > + } > + return; > + case 0: > + /* bpf buffer is empty */ > + return; > + default: > + break; > + } > + > + buf = bd->bd_buf; > + len = rv; > + > + for (;;) { > + const struct bpf_hdr *bh; > + > + /* the kernel lied to us. */ > + if (len < sizeof(*bh)) > + lerrx(1, "%s: short bpf header", bif->bif_name); > + > + bh = (const struct bpf_hdr *)buf; > + bpflen = bh->bh_hdrlen + bh->bh_caplen; > + > + /* > + * If the bpf header plus data doesn't fit in what's > + * left of the buffer, we've got a problem... > + */ > + if (bpflen > len) > + lerrx(1, "%s: short bpf read", bif->bif_name); > + > + if (np >= PCAP_PKTS) { > + rv = writev(bd->bd_fd, iovs, iov - iovs); > + if (rv == -1) > + lwarn("%s write", bd->bd_fname); > + iov = iovs; > + np = 0; > + } > + > + caplen = bh->bh_caplen; > + if (caplen > BPF_MAXBUFSIZE) > + caplen = BPF_MAXBUFSIZE; > + > + pp = &pps[np++]; > + > + pp->ts = bh->bh_tstamp; > + pp->caplen = caplen; > + pp->len = bh->bh_datalen; > + > + iov->iov_base = pp; > + iov->iov_len = sizeof(*pp); > + iov++; > + iov->iov_base = buf + bh->bh_hdrlen; > + iov->iov_len = caplen; > + iov++; > + > + bpflen = BPF_WORDALIGN(bpflen); > + if (len <= bpflen) { > + /* everything is consumed */ > + break; > + } > + > + /* Move the lop to the next packet */ > + buf += bpflen; > + len -= bpflen; > + } > + > + if (np > 0) { > + rv = writev(bd->bd_fd, iovs, iov - iovs); > + if (rv == -1) > + lwarn("%s write", bd->bd_fname); > + } > + > + fsync(bd->bd_fd); > +} > + > +/* daemon(3) clone, intended to be used in a "r"estricted environment */ > +int > +rdaemon(int devnull) > +{ > + if (devnull == -1) { > + errno = EBADF; > + return (-1); > + } > + if (fcntl(devnull, F_GETFL) == -1) > + return (-1); > + > + switch (fork()) { > + case -1: > + return (-1); > + case 0: > + break; > + default: > + _exit(0); > + } > + > + if (setsid() == -1) > + return (-1); > + > + (void)dup2(devnull, STDIN_FILENO); > + (void)dup2(devnull, STDOUT_FILENO); > + (void)dup2(devnull, STDERR_FILENO); > + if (devnull > 2) > + (void)close(devnull); > + > + return (0); > +} > Index: bpflogd/log.c > =================================================================== > RCS file: bpflogd/log.c > diff -N bpflogd/log.c > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ bpflogd/log.c 24 Apr 2025 03:56:54 -0000 > @@ -0,0 +1,150 @@ > + > +/* > + * Copyright (c) 2008 David Gwynne > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "log.h" > + > +static const struct __logger conslogger = { > + err, > + errx, > + warn, > + warnx, > + warnx, /* info */ > + warnx /* debug */ > +}; > + > +__dead static void syslog_err(int, const char *, ...) > + __attribute__((__format__ (printf, 2, 3))); > +__dead static void syslog_errx(int, const char *, ...) > + __attribute__((__format__ (printf, 2, 3))); > +static void syslog_warn(const char *, ...) > + __attribute__((__format__ (printf, 1, 2))); > +static void syslog_warnx(const char *, ...) > + __attribute__((__format__ (printf, 1, 2))); > +static void syslog_info(const char *, ...) > + __attribute__((__format__ (printf, 1, 2))); > +static void syslog_debug(const char *, ...) > + __attribute__((__format__ (printf, 1, 2))); > +static void syslog_vstrerror(int, int, const char *, va_list) > + __attribute__((__format__ (printf, 3, 0))); > + > +static const struct __logger syslogger = { > + syslog_err, > + syslog_errx, > + syslog_warn, > + syslog_warnx, > + syslog_info, > + syslog_debug > +}; > + > +const struct __logger *__logger = &conslogger; > + > +void > +logger_syslog(const char *progname) > +{ > + openlog(progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); > + tzset(); > + > + __logger = &syslogger; > +} > + > +static void > +syslog_vstrerror(int e, int priority, const char *fmt, va_list ap) > +{ > + char *s; > + > + if (vasprintf(&s, fmt, ap) == -1) { > + syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror"); > + exit(1); > + } > + > + syslog(priority, "%s: %s", s, strerror(e)); > + > + free(s); > +} > + > +static void > +syslog_err(int ecode, const char *fmt, ...) > +{ > + va_list ap; > + > + va_start(ap, fmt); > + syslog_vstrerror(errno, LOG_CRIT, fmt, ap); > + va_end(ap); > + > + exit(ecode); > +} > + > +static void > +syslog_errx(int ecode, const char *fmt, ...) > +{ > + va_list ap; > + > + va_start(ap, fmt); > + vsyslog(LOG_CRIT, fmt, ap); > + va_end(ap); > + > + exit(ecode); > +} > + > +static void > +syslog_warn(const char *fmt, ...) > +{ > + va_list ap; > + > + va_start(ap, fmt); > + syslog_vstrerror(errno, LOG_ERR, fmt, ap); > + va_end(ap); > +} > + > +static void > +syslog_warnx(const char *fmt, ...) > +{ > + va_list ap; > + > + va_start(ap, fmt); > + vsyslog(LOG_ERR, fmt, ap); > + va_end(ap); > +} > + > +static void > +syslog_info(const char *fmt, ...) > +{ > + va_list ap; > + > + va_start(ap, fmt); > + vsyslog(LOG_INFO, fmt, ap); > + va_end(ap); > +} > + > +static void > +syslog_debug(const char *fmt, ...) > +{ > + va_list ap; > + > + va_start(ap, fmt); > + vsyslog(LOG_DEBUG, fmt, ap); > + va_end(ap); > +} > Index: bpflogd/log.h > =================================================================== > RCS file: bpflogd/log.h > diff -N bpflogd/log.h > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ bpflogd/log.h 24 Apr 2025 03:56:54 -0000 > @@ -0,0 +1,47 @@ > + > +/* > + * Copyright (c) 2008 David Gwynne > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +#ifndef _LOG_H_ > +#define _LOG_H_ > + > +struct __logger { > + __dead void (*err)(int, const char *, ...) > + __attribute__((__format__ (printf, 2, 3))); > + __dead void (*errx)(int, const char *, ...) > + __attribute__((__format__ (printf, 2, 3))); > + void (*warn)(const char *, ...) > + __attribute__((__format__ (printf, 1, 2))); > + void (*warnx)(const char *, ...) > + __attribute__((__format__ (printf, 1, 2))); > + void (*info)(const char *, ...) > + __attribute__((__format__ (printf, 1, 2))); > + void (*debug)(const char *, ...) > + __attribute__((__format__ (printf, 1, 2))); > +}; > + > +extern const struct __logger *__logger; > + > +#define lerr(_e, _f...) __logger->err((_e), _f) > +#define lerrx(_e, _f...) __logger->errx((_e), _f) > +#define lwarn(_f...) __logger->warn(_f) > +#define lwarnx(_f...) __logger->warnx(_f) > +#define linfo(_f...) __logger->info(_f) > +#define ldebug(_f...) __logger->debug(_f) > + > +void logger_syslog(const char *); > + > +#endif /* _LOG_H_ */ > -- Regards, Mikolaj