Download raw body.
bpflogd(8): capture packets via BPF to log files
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
bpflogd(8): capture packets via BPF to log files