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