1 /* $NetBSD: npfd_log.c,v 1.13 2019/02/04 08:21:12 mrg Exp $ */ 2 3 /*- 4 * Copyright (c) 2015 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Mindaugas Rasiukevicius. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __RCSID("$NetBSD: npfd_log.c,v 1.13 2019/02/04 08:21:12 mrg Exp $"); 34 35 #include <sys/types.h> 36 #include <sys/param.h> 37 #include <sys/stat.h> 38 39 #include <net/if.h> 40 41 #include <stdio.h> 42 #include <string.h> 43 #include <err.h> 44 #include <inttypes.h> 45 #include <limits.h> 46 #include <stdlib.h> 47 #include <unistd.h> 48 #include <syslog.h> 49 #include <stdbool.h> 50 51 #include <pcap/pcap.h> 52 #include "npfd.h" 53 54 struct npfd_log { 55 char ifname[IFNAMSIZ]; 56 char path[MAXPATHLEN]; 57 char *filter; 58 int snaplen; 59 pcap_t *pcap; 60 pcap_dumper_t *dumper; 61 }; 62 63 static void 64 npfd_log_setfilter(npfd_log_t *ctx) 65 { 66 struct bpf_program bprog; 67 68 if (ctx->filter == NULL) 69 return; 70 71 if (pcap_compile(ctx->pcap, &bprog, ctx->filter, 1, 0) == -1) 72 errx(EXIT_FAILURE, "pcap_compile failed for `%s': %s", 73 ctx->filter, pcap_geterr(ctx->pcap)); 74 if (pcap_setfilter(ctx->pcap, &bprog) == -1) 75 errx(EXIT_FAILURE, "pcap_setfilter failed: %s", 76 pcap_geterr(ctx->pcap)); 77 pcap_freecode(&bprog); 78 } 79 80 static FILE * 81 npfd_log_gethdr(npfd_log_t *ctx, struct pcap_file_header*hdr) 82 { 83 FILE *fp = fopen(ctx->path, "r"); 84 85 hdr->magic = 0; 86 if (fp == NULL) 87 return NULL; 88 89 #define TCPDUMP_MAGIC 0xa1b2c3d4 90 91 switch (fread(hdr, sizeof(*hdr), 1, fp)) { 92 case 0: 93 hdr->magic = 0; 94 fclose(fp); 95 return NULL; 96 case 1: 97 if (hdr->magic != TCPDUMP_MAGIC || 98 hdr->version_major != PCAP_VERSION_MAJOR || 99 hdr->version_minor != PCAP_VERSION_MINOR || 100 hdr->sigfigs != (u_int)pcap_get_tstamp_precision(ctx->pcap)) 101 goto out; 102 break; 103 default: 104 goto out; 105 } 106 107 return fp; 108 out: 109 fclose(fp); 110 hdr->magic = (uint32_t)-1; 111 return NULL; 112 } 113 114 static int 115 npfd_log_getsnaplen(npfd_log_t *ctx) 116 { 117 struct pcap_file_header hdr; 118 FILE *fp = npfd_log_gethdr(ctx, &hdr); 119 if (fp == NULL) 120 return hdr.magic == (uint32_t)-1 ? -1 : 0; 121 fclose(fp); 122 return hdr.snaplen; 123 } 124 125 static int 126 npfd_log_validate(npfd_log_t *ctx) 127 { 128 struct pcap_file_header hdr; 129 FILE *fp = npfd_log_gethdr(ctx, &hdr); 130 size_t o, no; 131 132 if (fp == NULL) { 133 if (hdr.magic == 0) 134 return 0; 135 goto rename; 136 } 137 138 struct stat st; 139 if (fstat(fileno(fp), &st) == -1) 140 goto rename; 141 142 size_t count = 0; 143 for (o = sizeof(hdr);; count++) { 144 struct { 145 uint32_t sec; 146 uint32_t usec; 147 uint32_t caplen; 148 uint32_t len; 149 } pkt; 150 switch (fread(&pkt, sizeof(pkt), 1, fp)) { 151 case 0: 152 syslog(LOG_INFO, "%zu packets read from `%s'", count, 153 ctx->path); 154 fclose(fp); 155 return hdr.snaplen; 156 case 1: 157 no = o + sizeof(pkt) + pkt.caplen; 158 if (pkt.caplen > hdr.snaplen) 159 goto fix; 160 if (no > (size_t)st.st_size) 161 goto fix; 162 if (fseeko(fp, pkt.caplen, SEEK_CUR) != 0) 163 goto fix; 164 o = no; 165 break; 166 default: 167 goto fix; 168 } 169 } 170 171 fix: 172 fclose(fp); 173 no = st.st_size - o; 174 syslog(LOG_INFO, "%zu packets read from `%s', %zu extra bytes", 175 count, ctx->path, no); 176 if (no < 10240) { 177 syslog(LOG_WARNING, 178 "Incomplete last packet in `%s', truncating", 179 ctx->path); 180 if (truncate(ctx->path, (off_t)o) == -1) { 181 syslog(LOG_ERR, "Cannot truncate `%s': %m", ctx->path); 182 goto rename; 183 } 184 } else { 185 syslog(LOG_ERR, "Corrupt file `%s'", ctx->path); 186 goto rename; 187 } 188 fclose(fp); 189 return hdr.snaplen; 190 rename: 191 fclose(fp); 192 char tmp[MAXPATHLEN]; 193 if (snprintf(tmp, sizeof(tmp), "%s.XXXXXX", ctx->path) > MAXPATHLEN) 194 syslog(LOG_ERR, "Temp file truncated: `%s' does not fit", 195 ctx->path); 196 int fd; 197 if ((fd = mkstemp(tmp)) == -1) { 198 syslog(LOG_ERR, "Can't make temp file `%s': %m", tmp); 199 return -1; 200 } 201 close(fd); 202 if (rename(ctx->path, tmp) == -1) { 203 syslog(LOG_ERR, "Can't rename `%s' to `%s': %m", 204 ctx->path, tmp); 205 return -1; 206 } 207 syslog(LOG_ERR, "Renamed to `%s'", tmp); 208 return 0; 209 } 210 211 212 npfd_log_t * 213 npfd_log_create(const char *filename, const char *ifname, const char *filter, 214 int snaplen) 215 { 216 npfd_log_t *ctx; 217 218 if ((ctx = calloc(1, sizeof(*ctx))) == NULL) 219 err(EXIT_FAILURE, "malloc failed"); 220 221 snprintf(ctx->ifname, sizeof(ctx->ifname), "%s", ifname); 222 if (filename == NULL) 223 snprintf(ctx->path, sizeof(ctx->path), NPFD_LOG_PATH "/%s.pcap", 224 ctx->ifname); 225 else 226 snprintf(ctx->path, sizeof(ctx->path), "%s", filename); 227 228 if (filter != NULL) { 229 ctx->filter = strdup(filter); 230 if (ctx->filter == NULL) 231 err(EXIT_FAILURE, "malloc failed"); 232 } 233 ctx->snaplen = snaplen; 234 235 /* Open a live capture handle in non-blocking mode. */ 236 npfd_log_pcap_reopen(ctx); 237 238 /* Open the log file */ 239 npfd_log_file_reopen(ctx, false); 240 return ctx; 241 } 242 243 244 bool 245 npfd_log_pcap_reopen(npfd_log_t *ctx) 246 { 247 char errbuf[PCAP_ERRBUF_SIZE]; 248 int snaplen = ctx->snaplen; 249 250 if (ctx->pcap != NULL) 251 pcap_close(ctx->pcap); 252 else 253 syslog(LOG_INFO, "reopening pcap socket"); 254 255 ctx->pcap = pcap_create(ctx->ifname, errbuf); 256 if (ctx->pcap == NULL) 257 errx(EXIT_FAILURE, "pcap_create failed: %s", errbuf); 258 259 if (pcap_setnonblock(ctx->pcap, 1, errbuf) == -1) 260 errx(EXIT_FAILURE, "pcap_setnonblock failed: %s", errbuf); 261 262 int sl = npfd_log_getsnaplen(ctx); 263 if (sl == -1) 264 errx(EXIT_FAILURE, "corrupt log file `%s'", ctx->path); 265 266 if (sl != 0 && sl != snaplen) { 267 warnx("Overriding snaplen from %d to %d from `%s'", snaplen, 268 sl, ctx->path); 269 snaplen = sl; 270 } 271 272 if (pcap_set_snaplen(ctx->pcap, snaplen) == -1) 273 errx(EXIT_FAILURE, "pcap_set_snaplen failed: %s", 274 pcap_geterr(ctx->pcap)); 275 276 if (pcap_set_timeout(ctx->pcap, 1000) == -1) 277 errx(EXIT_FAILURE, "pcap_set_timeout failed: %s", 278 pcap_geterr(ctx->pcap)); 279 280 if (pcap_activate(ctx->pcap) == -1) 281 errx(EXIT_FAILURE, "pcap_activate failed: %s", 282 pcap_geterr(ctx->pcap)); 283 284 npfd_log_setfilter(ctx); 285 return true; 286 } 287 288 bool 289 npfd_log_file_reopen(npfd_log_t *ctx, bool die) 290 { 291 mode_t omask = umask(077); 292 293 if (ctx->dumper) 294 pcap_dump_close(ctx->dumper); 295 /* 296 * Open a log file to write for a given interface and dump there. 297 */ 298 switch (npfd_log_validate(ctx)) { 299 case -1: 300 syslog(LOG_ERR, "Giving up"); 301 exit(EXIT_FAILURE); 302 /*NOTREACHED*/ 303 case 0: 304 ctx->dumper = pcap_dump_open(ctx->pcap, ctx->path); 305 break; 306 default: 307 ctx->dumper = pcap_dump_open_append(ctx->pcap, ctx->path); 308 break; 309 } 310 (void)umask(omask); 311 312 if (ctx->dumper == NULL) { 313 if (die) 314 errx(EXIT_FAILURE, "pcap_dump_open failed for `%s': %s", 315 ctx->path, pcap_geterr(ctx->pcap)); 316 syslog(LOG_ERR, "pcap_dump_open failed for `%s': %s", 317 ctx->path, pcap_geterr(ctx->pcap)); 318 return false; 319 } 320 return true; 321 } 322 323 void 324 npfd_log_destroy(npfd_log_t *ctx) 325 { 326 if (ctx->dumper) 327 pcap_dump_close(ctx->dumper); 328 if (ctx->pcap) 329 pcap_close(ctx->pcap); 330 free(ctx); 331 } 332 333 int 334 npfd_log_getsock(npfd_log_t *ctx) 335 { 336 return pcap_get_selectable_fd(ctx->pcap); 337 } 338 339 void 340 npfd_log_flush(npfd_log_t *ctx) 341 { 342 if (!ctx->dumper) 343 return; 344 if (pcap_dump_flush(ctx->dumper) == -1) 345 syslog(LOG_ERR, "pcap_dump_flush failed for `%s': %m", 346 ctx->path); 347 } 348 349 350 int 351 npfd_log(npfd_log_t *ctx) 352 { 353 pcap_dumper_t *dumper = ctx->dumper; 354 355 return pcap_dispatch(ctx->pcap, PCAP_NPACKETS, pcap_dump, 356 (void *)dumper); 357 } 358 359 void 360 npfd_log_stats(npfd_log_t *ctx) 361 { 362 pcap_t *pcap = ctx->pcap; 363 struct pcap_stat ps; 364 365 if (pcap_stats(pcap, &ps) == -1) { 366 syslog(LOG_ERR, "pcap_stats failed: %s", pcap_geterr(pcap)); 367 return; 368 } 369 syslog(LOG_INFO, "packet statistics: %s: %u received, %u dropped", 370 ctx->ifname, ps.ps_recv, ps.ps_drop); 371 } 372