xref: /netbsd-src/usr.sbin/npf/npfd/npfd_log.c (revision 3df5232c9f9824a852c9551377e2f209088e77a9)
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