Index | Thread | Search

From:
Mikolaj Kucharski <mikolaj@kucharski.name>
Subject:
Re: bpflogd(8): capture packets via BPF to log files
To:
David Gwynne <david@gwynne.id.au>
Cc:
tech@openbsd.org
Date:
Mon, 5 May 2025 16:14:35 +0000

Download raw body.

Thread
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 <bsd.prog.mk>
> 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 <sys/types.h>
> +#include <sys/sysctl.h>
> +#include <sys/ioctl.h>
> +#include <sys/stat.h>
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <stdint.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <paths.h>
> +#include <signal.h>
> +#include <pwd.h>
> +#include <errno.h>
> +#include <err.h>
> +
> +#include <sys/queue.h>
> +
> +#include <net/if.h>
> +#include <net/bpf.h>
> +
> +#include <event.h>
> +#include <pcap.h>
> +
> +#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 <loki@animata.net>
> + *
> + * 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 <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <err.h>
> +#include <time.h>
> +#include <syslog.h>
> +#include <stdarg.h>
> +
> +#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 <loki@animata.net>
> + *
> + * 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