1 /* $NetBSD: npfd_log.c,v 1.8 2017/01/07 16:48:03 christos 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.8 2017/01/07 16:48:03 christos 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 <err.h> 43 #include <inttypes.h> 44 #include <limits.h> 45 #include <stdlib.h> 46 #include <unistd.h> 47 #include <syslog.h> 48 #include <stdbool.h> 49 50 #include <pcap/pcap.h> 51 #include "npfd.h" 52 53 struct npfd_log { 54 char ifname[IFNAMSIZ]; 55 char path[MAXPATHLEN]; 56 pcap_t *pcap; 57 pcap_dumper_t *dumper; 58 }; 59 60 static void 61 npfd_log_setfilter(npfd_log_t *ctx, const char *filter) 62 { 63 struct bpf_program bprog; 64 65 if (pcap_compile(ctx->pcap, &bprog, filter, 1, 0) == -1) 66 errx(EXIT_FAILURE, "pcap_compile failed for `%s': %s", filter, 67 pcap_geterr(ctx->pcap)); 68 if (pcap_setfilter(ctx->pcap, &bprog) == -1) 69 errx(EXIT_FAILURE, "pcap_setfilter failed: %s", 70 pcap_geterr(ctx->pcap)); 71 pcap_freecode(&bprog); 72 } 73 74 static FILE * 75 npfd_log_gethdr(npfd_log_t *ctx, struct pcap_file_header*hdr) 76 { 77 FILE *fp = fopen(ctx->path, "r"); 78 79 hdr->magic = 0; 80 if (fp == NULL) 81 return NULL; 82 83 #define TCPDUMP_MAGIC 0xa1b2c3d4 84 85 switch (fread(hdr, sizeof(*hdr), 1, fp)) { 86 case 0: 87 hdr->magic = 0; 88 fclose(fp); 89 return NULL; 90 case 1: 91 if (hdr->magic != TCPDUMP_MAGIC || 92 hdr->version_major != PCAP_VERSION_MAJOR || 93 hdr->version_minor != PCAP_VERSION_MINOR) 94 goto out; 95 break; 96 default: 97 goto out; 98 } 99 100 return fp; 101 out: 102 fclose(fp); 103 hdr->magic = (uint32_t)-1; 104 return NULL; 105 } 106 107 static int 108 npfd_log_getsnaplen(npfd_log_t *ctx) 109 { 110 struct pcap_file_header hdr; 111 FILE *fp = npfd_log_gethdr(ctx, &hdr); 112 if (fp == NULL) 113 return hdr.magic == (uint32_t)-1 ? -1 : 0; 114 fclose(fp); 115 return hdr.snaplen; 116 } 117 118 static int 119 npfd_log_validate(npfd_log_t *ctx) 120 { 121 struct pcap_file_header hdr; 122 FILE *fp = npfd_log_gethdr(ctx, &hdr); 123 size_t o, no; 124 125 if (fp == NULL) { 126 if (hdr.magic == 0) 127 return 0; 128 goto rename; 129 } 130 131 struct stat st; 132 if (fstat(fileno(fp), &st) == -1) 133 goto rename; 134 135 size_t count = 0; 136 for (o = sizeof(hdr);; count++) { 137 struct { 138 uint32_t sec; 139 uint32_t usec; 140 uint32_t caplen; 141 uint32_t len; 142 } pkt; 143 switch (fread(&pkt, sizeof(pkt), 1, fp)) { 144 case 0: 145 syslog(LOG_INFO, "%zu packets read from `%s'", count, 146 ctx->path); 147 fclose(fp); 148 return hdr.snaplen; 149 case 1: 150 no = o + sizeof(pkt) + pkt.caplen; 151 if (pkt.caplen > hdr.snaplen) 152 goto fix; 153 if (no > (size_t)st.st_size) 154 goto fix; 155 if (fseeko(fp, pkt.caplen, SEEK_CUR) != 0) 156 goto fix; 157 o = no; 158 break; 159 default: 160 goto fix; 161 } 162 } 163 164 fix: 165 fclose(fp); 166 no = st.st_size - o; 167 syslog(LOG_INFO, "%zu packets read from `%s', %zu extra bytes", 168 count, ctx->path, no); 169 if (no < 10240) { 170 syslog(LOG_WARNING, 171 "Incomplete last packet in `%s', truncating", 172 ctx->path); 173 if (truncate(ctx->path, (off_t)o) == -1) { 174 syslog(LOG_ERR, "Cannot truncate `%s': %m", ctx->path); 175 goto rename; 176 } 177 } else { 178 syslog(LOG_ERR, "Corrupt file `%s'", ctx->path); 179 goto rename; 180 } 181 fclose(fp); 182 return hdr.snaplen; 183 rename: 184 fclose(fp); 185 char tmp[MAXPATHLEN]; 186 snprintf(tmp, sizeof(tmp), "%s.XXXXXX", ctx->path); 187 int fd; 188 if ((fd = mkstemp(tmp)) == -1) { 189 syslog(LOG_ERR, "Can't make temp file `%s': %m", tmp); 190 return -1; 191 } 192 close(fd); 193 if (rename(ctx->path, tmp) == -1) { 194 syslog(LOG_ERR, "Can't rename `%s' to `%s': %m", 195 ctx->path, tmp); 196 return -1; 197 } 198 syslog(LOG_ERR, "Renamed to `%s'", tmp); 199 return 0; 200 } 201 202 203 npfd_log_t * 204 npfd_log_create(const char *filename, const char *ifname, const char *filter, 205 int snaplen) 206 { 207 npfd_log_t *ctx; 208 char errbuf[PCAP_ERRBUF_SIZE]; 209 210 if ((ctx = calloc(1, sizeof(*ctx))) == NULL) 211 err(EXIT_FAILURE, "malloc failed"); 212 213 /* 214 * Open a live capture handle in non-blocking mode. 215 */ 216 snprintf(ctx->ifname, sizeof(ctx->ifname), "%s", ifname); 217 ctx->pcap = pcap_create(ctx->ifname, errbuf); 218 if (ctx->pcap == NULL) 219 errx(EXIT_FAILURE, "pcap_create failed: %s", errbuf); 220 221 if (pcap_setnonblock(ctx->pcap, 1, errbuf) == -1) 222 errx(EXIT_FAILURE, "pcap_setnonblock failed: %s", errbuf); 223 224 if (filename == NULL) 225 snprintf(ctx->path, sizeof(ctx->path), NPFD_LOG_PATH "/%s.pcap", 226 ctx->ifname); 227 else 228 snprintf(ctx->path, sizeof(ctx->path), "%s", filename); 229 230 int sl = npfd_log_getsnaplen(ctx); 231 if (sl == -1) 232 errx(EXIT_FAILURE, "corrupt log file `%s'", ctx->path); 233 234 if (sl != 0 && sl != snaplen) { 235 warnx("Overriding snaplen from %d to %d from `%s'", snaplen, 236 sl, filename); 237 snaplen = sl; 238 } 239 240 if (pcap_set_snaplen(ctx->pcap, snaplen) == -1) 241 errx(EXIT_FAILURE, "pcap_set_snaplen failed: %s", 242 pcap_geterr(ctx->pcap)); 243 244 if (pcap_set_timeout(ctx->pcap, 1000) == -1) 245 errx(EXIT_FAILURE, "pcap_set_timeout failed: %s", 246 pcap_geterr(ctx->pcap)); 247 248 if (pcap_activate(ctx->pcap) == -1) 249 errx(EXIT_FAILURE, "pcap_activate failed: %s", 250 pcap_geterr(ctx->pcap)); 251 252 if (filter) 253 npfd_log_setfilter(ctx, filter); 254 255 256 npfd_log_reopen(ctx, false); 257 return ctx; 258 } 259 260 bool 261 npfd_log_reopen(npfd_log_t *ctx, bool die) 262 { 263 mode_t omask = umask(077); 264 265 if (ctx->dumper) 266 pcap_dump_close(ctx->dumper); 267 /* 268 * Open a log file to write for a given interface and dump there. 269 */ 270 switch (npfd_log_validate(ctx)) { 271 case -1: 272 syslog(LOG_ERR, "Giving up"); 273 exit(EXIT_FAILURE); 274 /*NOTREACHED*/ 275 case 0: 276 ctx->dumper = pcap_dump_open(ctx->pcap, ctx->path); 277 break; 278 default: 279 ctx->dumper = pcap_dump_open_append(ctx->pcap, ctx->path); 280 break; 281 } 282 (void)umask(omask); 283 284 if (ctx->dumper == NULL) { 285 if (die) 286 errx(EXIT_FAILURE, "pcap_dump_open failed for `%s': %s", 287 ctx->path, pcap_geterr(ctx->pcap)); 288 syslog(LOG_ERR, "pcap_dump_open failed for `%s': %s", 289 ctx->path, pcap_geterr(ctx->pcap)); 290 return false; 291 } 292 return true; 293 } 294 295 void 296 npfd_log_destroy(npfd_log_t *ctx) 297 { 298 if (ctx->dumper) 299 pcap_dump_close(ctx->dumper); 300 if (ctx->pcap) 301 pcap_close(ctx->pcap); 302 free(ctx); 303 } 304 305 int 306 npfd_log_getsock(npfd_log_t *ctx) 307 { 308 return pcap_get_selectable_fd(ctx->pcap); 309 } 310 311 void 312 npfd_log_flush(npfd_log_t *ctx) 313 { 314 if (!ctx->dumper) 315 return; 316 if (pcap_dump_flush(ctx->dumper) == -1) 317 syslog(LOG_ERR, "pcap_dump_flush failed for `%s': %m", 318 ctx->path); 319 } 320 321 322 void 323 npfd_log(npfd_log_t *ctx) 324 { 325 pcap_dumper_t *dumper = ctx->dumper; 326 327 pcap_dispatch(ctx->pcap, PCAP_NPACKETS, pcap_dump, (void *)dumper); 328 } 329 330 void 331 npfd_log_stats(npfd_log_t *ctx) 332 { 333 pcap_t *pcap = ctx->pcap; 334 struct pcap_stat ps; 335 336 if (pcap_stats(pcap, &ps) == -1) { 337 syslog(LOG_ERR, "pcap_stats failed: %s", pcap_geterr(pcap)); 338 return; 339 } 340 syslog(LOG_INFO, "packet statistics: %s: %u received, %u dropped", 341 ctx->ifname, ps.ps_recv, ps.ps_drop); 342 } 343