1*3df5232cSchristos /* $NetBSD: npfd_log.c,v 1.14 2022/04/30 13:20:09 christos Exp $ */
261e84d3fSrmind
361e84d3fSrmind /*-
461e84d3fSrmind * Copyright (c) 2015 The NetBSD Foundation, Inc.
561e84d3fSrmind * All rights reserved.
661e84d3fSrmind *
761e84d3fSrmind * This code is derived from software contributed to The NetBSD Foundation
861e84d3fSrmind * by Mindaugas Rasiukevicius.
961e84d3fSrmind *
1061e84d3fSrmind * Redistribution and use in source and binary forms, with or without
1161e84d3fSrmind * modification, are permitted provided that the following conditions
1261e84d3fSrmind * are met:
1361e84d3fSrmind * 1. Redistributions of source code must retain the above copyright
1461e84d3fSrmind * notice, this list of conditions and the following disclaimer.
1561e84d3fSrmind * 2. Redistributions in binary form must reproduce the above copyright
1661e84d3fSrmind * notice, this list of conditions and the following disclaimer in the
1761e84d3fSrmind * documentation and/or other materials provided with the distribution.
1861e84d3fSrmind *
1961e84d3fSrmind * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2061e84d3fSrmind * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2161e84d3fSrmind * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2261e84d3fSrmind * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
2361e84d3fSrmind * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2461e84d3fSrmind * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2561e84d3fSrmind * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2661e84d3fSrmind * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2761e84d3fSrmind * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2861e84d3fSrmind * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2961e84d3fSrmind * POSSIBILITY OF SUCH DAMAGE.
3061e84d3fSrmind */
3161e84d3fSrmind
3261e84d3fSrmind #include <sys/cdefs.h>
33*3df5232cSchristos __RCSID("$NetBSD: npfd_log.c,v 1.14 2022/04/30 13:20:09 christos Exp $");
34204834d4Schristos
35204834d4Schristos #include <sys/types.h>
36204834d4Schristos #include <sys/param.h>
379ed73909Schristos #include <sys/stat.h>
389ed73909Schristos
39204834d4Schristos #include <net/if.h>
4061e84d3fSrmind
4161e84d3fSrmind #include <stdio.h>
42c06842d6Schristos #include <string.h>
43d8571dafSchristos #include <err.h>
4461e84d3fSrmind #include <inttypes.h>
4561e84d3fSrmind #include <limits.h>
46204834d4Schristos #include <stdlib.h>
47f069472cSchristos #include <unistd.h>
48204834d4Schristos #include <syslog.h>
49204834d4Schristos #include <stdbool.h>
5061e84d3fSrmind
5161e84d3fSrmind #include <pcap/pcap.h>
52204834d4Schristos #include "npfd.h"
5361e84d3fSrmind
5461e84d3fSrmind struct npfd_log {
55204834d4Schristos char ifname[IFNAMSIZ];
56204834d4Schristos char path[MAXPATHLEN];
57c06842d6Schristos char *filter;
58c06842d6Schristos int snaplen;
5961e84d3fSrmind pcap_t *pcap;
6061e84d3fSrmind pcap_dumper_t *dumper;
6161e84d3fSrmind };
6261e84d3fSrmind
63d8571dafSchristos static void
npfd_log_setfilter(npfd_log_t * ctx)64c06842d6Schristos npfd_log_setfilter(npfd_log_t *ctx)
65d8571dafSchristos {
66d8571dafSchristos struct bpf_program bprog;
67d8571dafSchristos
68c06842d6Schristos if (ctx->filter == NULL)
69c06842d6Schristos return;
70c06842d6Schristos
71c06842d6Schristos if (pcap_compile(ctx->pcap, &bprog, ctx->filter, 1, 0) == -1)
72c06842d6Schristos errx(EXIT_FAILURE, "pcap_compile failed for `%s': %s",
73c06842d6Schristos ctx->filter, pcap_geterr(ctx->pcap));
74d8571dafSchristos if (pcap_setfilter(ctx->pcap, &bprog) == -1)
75d8571dafSchristos errx(EXIT_FAILURE, "pcap_setfilter failed: %s",
76d8571dafSchristos pcap_geterr(ctx->pcap));
77d8571dafSchristos pcap_freecode(&bprog);
78d8571dafSchristos }
79d8571dafSchristos
809ed73909Schristos static FILE *
npfd_log_gethdr(npfd_log_t * ctx,struct pcap_file_header * hdr)819ed73909Schristos npfd_log_gethdr(npfd_log_t *ctx, struct pcap_file_header*hdr)
829ed73909Schristos {
839ed73909Schristos FILE *fp = fopen(ctx->path, "r");
849ed73909Schristos
859ed73909Schristos hdr->magic = 0;
869ed73909Schristos if (fp == NULL)
879ed73909Schristos return NULL;
889ed73909Schristos
899ed73909Schristos #define TCPDUMP_MAGIC 0xa1b2c3d4
909ed73909Schristos
919ed73909Schristos switch (fread(hdr, sizeof(*hdr), 1, fp)) {
929ed73909Schristos case 0:
939ed73909Schristos hdr->magic = 0;
949ed73909Schristos fclose(fp);
959ed73909Schristos return NULL;
969ed73909Schristos case 1:
979ed73909Schristos if (hdr->magic != TCPDUMP_MAGIC ||
989ed73909Schristos hdr->version_major != PCAP_VERSION_MAJOR ||
99043ad880Schristos hdr->version_minor != PCAP_VERSION_MINOR ||
100043ad880Schristos hdr->sigfigs != (u_int)pcap_get_tstamp_precision(ctx->pcap))
1019ed73909Schristos goto out;
1029ed73909Schristos break;
1039ed73909Schristos default:
1049ed73909Schristos goto out;
1059ed73909Schristos }
1069ed73909Schristos
1079ed73909Schristos return fp;
1089ed73909Schristos out:
1099ed73909Schristos fclose(fp);
110cec12788Schristos hdr->magic = (uint32_t)-1;
1119ed73909Schristos return NULL;
1129ed73909Schristos }
1139ed73909Schristos
1149ed73909Schristos static int
npfd_log_getsnaplen(npfd_log_t * ctx)1159ed73909Schristos npfd_log_getsnaplen(npfd_log_t *ctx)
1169ed73909Schristos {
1179ed73909Schristos struct pcap_file_header hdr;
1189ed73909Schristos FILE *fp = npfd_log_gethdr(ctx, &hdr);
1199ed73909Schristos if (fp == NULL)
1209ed73909Schristos return hdr.magic == (uint32_t)-1 ? -1 : 0;
1219ed73909Schristos fclose(fp);
1229ed73909Schristos return hdr.snaplen;
1239ed73909Schristos }
1249ed73909Schristos
1259ed73909Schristos static int
npfd_log_validate(npfd_log_t * ctx)1269ed73909Schristos npfd_log_validate(npfd_log_t *ctx)
1279ed73909Schristos {
1289ed73909Schristos struct pcap_file_header hdr;
1299ed73909Schristos FILE *fp = npfd_log_gethdr(ctx, &hdr);
1309ed73909Schristos size_t o, no;
1319ed73909Schristos
1329ed73909Schristos if (fp == NULL) {
1339ed73909Schristos if (hdr.magic == 0)
1349ed73909Schristos return 0;
1359ed73909Schristos goto rename;
1369ed73909Schristos }
1379ed73909Schristos
1389ed73909Schristos struct stat st;
1399ed73909Schristos if (fstat(fileno(fp), &st) == -1)
1409ed73909Schristos goto rename;
1419ed73909Schristos
1429ed73909Schristos size_t count = 0;
1439ed73909Schristos for (o = sizeof(hdr);; count++) {
1449ed73909Schristos struct {
1459ed73909Schristos uint32_t sec;
1469ed73909Schristos uint32_t usec;
1479ed73909Schristos uint32_t caplen;
1489ed73909Schristos uint32_t len;
1499ed73909Schristos } pkt;
1509ed73909Schristos switch (fread(&pkt, sizeof(pkt), 1, fp)) {
1519ed73909Schristos case 0:
1529ed73909Schristos syslog(LOG_INFO, "%zu packets read from `%s'", count,
1539ed73909Schristos ctx->path);
1549ed73909Schristos fclose(fp);
1559ed73909Schristos return hdr.snaplen;
1569ed73909Schristos case 1:
1579ed73909Schristos no = o + sizeof(pkt) + pkt.caplen;
1589ed73909Schristos if (pkt.caplen > hdr.snaplen)
1599ed73909Schristos goto fix;
1609ed73909Schristos if (no > (size_t)st.st_size)
1619ed73909Schristos goto fix;
1629ed73909Schristos if (fseeko(fp, pkt.caplen, SEEK_CUR) != 0)
1639ed73909Schristos goto fix;
1649ed73909Schristos o = no;
1659ed73909Schristos break;
1669ed73909Schristos default:
1679ed73909Schristos goto fix;
1689ed73909Schristos }
1699ed73909Schristos }
1709ed73909Schristos
1719ed73909Schristos fix:
1729ed73909Schristos fclose(fp);
1739ed73909Schristos no = st.st_size - o;
1749ed73909Schristos syslog(LOG_INFO, "%zu packets read from `%s', %zu extra bytes",
1759ed73909Schristos count, ctx->path, no);
1769ed73909Schristos if (no < 10240) {
1779ed73909Schristos syslog(LOG_WARNING,
1789ed73909Schristos "Incomplete last packet in `%s', truncating",
1799ed73909Schristos ctx->path);
180cec12788Schristos if (truncate(ctx->path, (off_t)o) == -1) {
1819ed73909Schristos syslog(LOG_ERR, "Cannot truncate `%s': %m", ctx->path);
1829ed73909Schristos goto rename;
1839ed73909Schristos }
1849ed73909Schristos } else {
1859ed73909Schristos syslog(LOG_ERR, "Corrupt file `%s'", ctx->path);
1869ed73909Schristos goto rename;
1879ed73909Schristos }
1889ed73909Schristos fclose(fp);
1899ed73909Schristos return hdr.snaplen;
1909ed73909Schristos rename:
1919ed73909Schristos fclose(fp);
1929ed73909Schristos char tmp[MAXPATHLEN];
19382974f22Smrg if (snprintf(tmp, sizeof(tmp), "%s.XXXXXX", ctx->path) > MAXPATHLEN)
19482974f22Smrg syslog(LOG_ERR, "Temp file truncated: `%s' does not fit",
19582974f22Smrg ctx->path);
1969ed73909Schristos int fd;
1979ed73909Schristos if ((fd = mkstemp(tmp)) == -1) {
1989ed73909Schristos syslog(LOG_ERR, "Can't make temp file `%s': %m", tmp);
1999ed73909Schristos return -1;
2009ed73909Schristos }
2019ed73909Schristos close(fd);
2029ed73909Schristos if (rename(ctx->path, tmp) == -1) {
2039ed73909Schristos syslog(LOG_ERR, "Can't rename `%s' to `%s': %m",
2049ed73909Schristos ctx->path, tmp);
2059ed73909Schristos return -1;
2069ed73909Schristos }
2079ed73909Schristos syslog(LOG_ERR, "Renamed to `%s'", tmp);
2089ed73909Schristos return 0;
2099ed73909Schristos }
2109ed73909Schristos
2119ed73909Schristos
21261e84d3fSrmind npfd_log_t *
npfd_log_create(const char * filename,const char * ifname,const char * filter,int snaplen)2139ed73909Schristos npfd_log_create(const char *filename, const char *ifname, const char *filter,
2149ed73909Schristos int snaplen)
21561e84d3fSrmind {
21661e84d3fSrmind npfd_log_t *ctx;
21761e84d3fSrmind
218d8571dafSchristos if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
219d8571dafSchristos err(EXIT_FAILURE, "malloc failed");
22061e84d3fSrmind
221d8571dafSchristos snprintf(ctx->ifname, sizeof(ctx->ifname), "%s", ifname);
222c06842d6Schristos if (filename == NULL)
223c06842d6Schristos snprintf(ctx->path, sizeof(ctx->path), NPFD_LOG_PATH "/%s.pcap",
224c06842d6Schristos ctx->ifname);
225c06842d6Schristos else
226c06842d6Schristos snprintf(ctx->path, sizeof(ctx->path), "%s", filename);
227c06842d6Schristos
228c06842d6Schristos if (filter != NULL) {
229c06842d6Schristos ctx->filter = strdup(filter);
230c06842d6Schristos if (ctx->filter == NULL)
231c06842d6Schristos err(EXIT_FAILURE, "malloc failed");
232c06842d6Schristos }
233c06842d6Schristos ctx->snaplen = snaplen;
234c06842d6Schristos
235c06842d6Schristos /* Open a live capture handle in non-blocking mode. */
236c06842d6Schristos npfd_log_pcap_reopen(ctx);
237c06842d6Schristos
238c06842d6Schristos /* Open the log file */
239c06842d6Schristos npfd_log_file_reopen(ctx, false);
240c06842d6Schristos return ctx;
241c06842d6Schristos }
242c06842d6Schristos
243c06842d6Schristos
244c06842d6Schristos bool
npfd_log_pcap_reopen(npfd_log_t * ctx)245c06842d6Schristos npfd_log_pcap_reopen(npfd_log_t *ctx)
246c06842d6Schristos {
247c06842d6Schristos char errbuf[PCAP_ERRBUF_SIZE];
248c06842d6Schristos int snaplen = ctx->snaplen;
249*3df5232cSchristos int rc;
250c06842d6Schristos
251c06842d6Schristos if (ctx->pcap != NULL)
252c06842d6Schristos pcap_close(ctx->pcap);
253531fc462Schristos else
254531fc462Schristos syslog(LOG_INFO, "reopening pcap socket");
255c06842d6Schristos
256204834d4Schristos ctx->pcap = pcap_create(ctx->ifname, errbuf);
257d8571dafSchristos if (ctx->pcap == NULL)
258d8571dafSchristos errx(EXIT_FAILURE, "pcap_create failed: %s", errbuf);
25961e84d3fSrmind
260d8571dafSchristos if (pcap_setnonblock(ctx->pcap, 1, errbuf) == -1)
261d8571dafSchristos errx(EXIT_FAILURE, "pcap_setnonblock failed: %s", errbuf);
262204834d4Schristos
2639ed73909Schristos int sl = npfd_log_getsnaplen(ctx);
2649ed73909Schristos if (sl == -1)
2659ed73909Schristos errx(EXIT_FAILURE, "corrupt log file `%s'", ctx->path);
2669ed73909Schristos
2679ed73909Schristos if (sl != 0 && sl != snaplen) {
2689ed73909Schristos warnx("Overriding snaplen from %d to %d from `%s'", snaplen,
269c06842d6Schristos sl, ctx->path);
2709ed73909Schristos snaplen = sl;
2719ed73909Schristos }
2729ed73909Schristos
273d8571dafSchristos if (pcap_set_snaplen(ctx->pcap, snaplen) == -1)
274d8571dafSchristos errx(EXIT_FAILURE, "pcap_set_snaplen failed: %s",
275f069472cSchristos pcap_geterr(ctx->pcap));
276204834d4Schristos
27783809af3Schristos if (pcap_set_timeout(ctx->pcap, 1000) == -1)
27883809af3Schristos errx(EXIT_FAILURE, "pcap_set_timeout failed: %s",
27983809af3Schristos pcap_geterr(ctx->pcap));
28083809af3Schristos
281*3df5232cSchristos if ((rc = pcap_activate(ctx->pcap)) != 0) {
282*3df5232cSchristos const char *msg = pcap_geterr(ctx->pcap);
283*3df5232cSchristos if (rc > 0) {
284*3df5232cSchristos warnx("pcap_activate warning: %s", msg);
285*3df5232cSchristos } else {
286*3df5232cSchristos errx(EXIT_FAILURE, "pcap_activate failed: %s", msg);
287*3df5232cSchristos }
288*3df5232cSchristos }
289204834d4Schristos
290c06842d6Schristos npfd_log_setfilter(ctx);
291c06842d6Schristos return true;
292204834d4Schristos }
293204834d4Schristos
294204834d4Schristos bool
npfd_log_file_reopen(npfd_log_t * ctx,bool die)295c06842d6Schristos npfd_log_file_reopen(npfd_log_t *ctx, bool die)
296204834d4Schristos {
297177e6c06Schristos mode_t omask = umask(077);
298177e6c06Schristos
299204834d4Schristos if (ctx->dumper)
300204834d4Schristos pcap_dump_close(ctx->dumper);
30161e84d3fSrmind /*
30261e84d3fSrmind * Open a log file to write for a given interface and dump there.
30361e84d3fSrmind */
3049ed73909Schristos switch (npfd_log_validate(ctx)) {
3059ed73909Schristos case -1:
3069ed73909Schristos syslog(LOG_ERR, "Giving up");
3079ed73909Schristos exit(EXIT_FAILURE);
3089ed73909Schristos /*NOTREACHED*/
3099ed73909Schristos case 0:
310f069472cSchristos ctx->dumper = pcap_dump_open(ctx->pcap, ctx->path);
3119ed73909Schristos break;
3129ed73909Schristos default:
3139ed73909Schristos ctx->dumper = pcap_dump_open_append(ctx->pcap, ctx->path);
3149ed73909Schristos break;
3159ed73909Schristos }
316177e6c06Schristos (void)umask(omask);
3179ed73909Schristos
318204834d4Schristos if (ctx->dumper == NULL) {
319d8571dafSchristos if (die)
320d8571dafSchristos errx(EXIT_FAILURE, "pcap_dump_open failed for `%s': %s",
321d8571dafSchristos ctx->path, pcap_geterr(ctx->pcap));
322f069472cSchristos syslog(LOG_ERR, "pcap_dump_open failed for `%s': %s",
323204834d4Schristos ctx->path, pcap_geterr(ctx->pcap));
324204834d4Schristos return false;
32561e84d3fSrmind }
326204834d4Schristos return true;
32761e84d3fSrmind }
32861e84d3fSrmind
32961e84d3fSrmind void
npfd_log_destroy(npfd_log_t * ctx)33061e84d3fSrmind npfd_log_destroy(npfd_log_t *ctx)
33161e84d3fSrmind {
33261e84d3fSrmind if (ctx->dumper)
33361e84d3fSrmind pcap_dump_close(ctx->dumper);
33461e84d3fSrmind if (ctx->pcap)
33561e84d3fSrmind pcap_close(ctx->pcap);
33661e84d3fSrmind free(ctx);
33761e84d3fSrmind }
33861e84d3fSrmind
33961e84d3fSrmind int
npfd_log_getsock(npfd_log_t * ctx)34061e84d3fSrmind npfd_log_getsock(npfd_log_t *ctx)
34161e84d3fSrmind {
34261e84d3fSrmind return pcap_get_selectable_fd(ctx->pcap);
34361e84d3fSrmind }
34461e84d3fSrmind
34561e84d3fSrmind void
npfd_log_flush(npfd_log_t * ctx)346d8571dafSchristos npfd_log_flush(npfd_log_t *ctx)
347d8571dafSchristos {
348d8571dafSchristos if (!ctx->dumper)
349d8571dafSchristos return;
350d8571dafSchristos if (pcap_dump_flush(ctx->dumper) == -1)
351d8571dafSchristos syslog(LOG_ERR, "pcap_dump_flush failed for `%s': %m",
352d8571dafSchristos ctx->path);
353d8571dafSchristos }
354d8571dafSchristos
355d8571dafSchristos
356c06842d6Schristos int
npfd_log(npfd_log_t * ctx)35761e84d3fSrmind npfd_log(npfd_log_t *ctx)
35861e84d3fSrmind {
35961e84d3fSrmind pcap_dumper_t *dumper = ctx->dumper;
36061e84d3fSrmind
361c06842d6Schristos return pcap_dispatch(ctx->pcap, PCAP_NPACKETS, pcap_dump,
362c06842d6Schristos (void *)dumper);
36361e84d3fSrmind }
36461e84d3fSrmind
36561e84d3fSrmind void
npfd_log_stats(npfd_log_t * ctx)36661e84d3fSrmind npfd_log_stats(npfd_log_t *ctx)
36761e84d3fSrmind {
36861e84d3fSrmind pcap_t *pcap = ctx->pcap;
36961e84d3fSrmind struct pcap_stat ps;
37061e84d3fSrmind
371204834d4Schristos if (pcap_stats(pcap, &ps) == -1) {
37261e84d3fSrmind syslog(LOG_ERR, "pcap_stats failed: %s", pcap_geterr(pcap));
37361e84d3fSrmind return;
37461e84d3fSrmind }
375d8571dafSchristos syslog(LOG_INFO, "packet statistics: %s: %u received, %u dropped",
376d8571dafSchristos ctx->ifname, ps.ps_recv, ps.ps_drop);
37761e84d3fSrmind }
378