/*- * Copyright (c) 2004 University of Toronto. All rights reserved. * Anyone may use or copy this software except that this copyright * notice remain intact and that credit is given where it is due. * The University of Toronto and the author make no warranty and * accept no liability for this software. * * $FreeBSD: /repoman/r/ncvs/src/usr.sbin/ipfwpcap/ipfwpcap.c,v 1.2 2006/09/04 19:30:44 sam Exp $ */ /* * Copy diverted (or tee'd) packets to a file in 'tcpdump' format * (ie. this uses the '-lpcap' routines). * * Example usage: * # ipfwpcap -r 8091 divt.log * # ipfw add 2864 divert 8091 ip from 128.432.53.82 to any * # ipfw add 2864 divert 8091 ip from any to 128.432.53.82 * * the resulting dump file can be read with ... * # tcpdump -nX -r divt.log * * Written by P Kern { pkern [AT] cns.utoronto.ca } * Adopted by V Pavluk (vladvic_r@mail.ru) * - changed sighup handler to reopen log file * - added sigalrm handler to flush data * - some of exit() codes changed to sysexits(3) * Major code reworking by Vadim Goncharov * - signals and daemonizing rewritten * - style(9) reformat, more sysexits(3) and other cleanups * - enabled own alarm sending, changed options and updated man page */ #include #include #include /* For MAXPATHLEN */ #include #include #include #include /* For IP_MAXPACKET */ #include /* For IP_MAXPACKET */ #include #include #include #include #include #include #include /* XXX normally defined in config.h */ #define HAVE_STRLCPY 1 #define HAVE_SNPRINTF 1 #define HAVE_VSNPRINTF 1 #include /* See pcap(3) and /usr/src/contrib/libpcap/. */ #ifdef IP_MAXPACKET #define BUFMAX IP_MAXPACKET #else #define BUFMAX 65535 #endif #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif #define MAXPIDLEN 9 /* Max decimal pid length in characters. */ #define DEFINTERVAL 60 /* How often to do flush (in seconds). */ static char *prog = NULL; static char pidfile[MAXPATHLEN] = { '\0' }; static int quit = 0; /* Is it time to exit? */ static int do_flush = 0; /* Time to flush log. */ static int do_reopen = 0; /* Time for log rotating. */ static int flush_interval = DEFINTERVAL; /* * Tidy up macro. */ #define QUIT(code) do { \ pcap_dump_flush(dp); \ (void)unlink(pidfile); \ exit(code); \ } while(0); void quit_sig(int sig) { quit = 1; } void flush_log(int sigalrm) { do_flush = 1; alarm(flush_interval); } void reopen_log(int sighup) { do_reopen = 1; } /* * Do the "paper work". * - fork and detach from terminal, if needed. * - save my own pid in /var/run/$0.{port#}.pid. */ void okay(int pn, int detach, int nochdir) { int pf; char *p, strpid[MAXPIDLEN + 1]; pid_t pid; if (pidfile[0] == '\0') { p = (char *)rindex(prog, '/'); p = (p == NULL) ? prog : p+1 ; snprintf(pidfile, sizeof(pidfile)-1, "%s%s.%d.pid", _PATH_VARRUN, p, pn); } pf = open(pidfile, O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK, 0644); /* * We couldn't create pid file */ if (pf == -1) { if (errno == EEXIST) { /* * If it is because it's already exists */ bzero(strpid, MAXPIDLEN + 1); fprintf(stderr, "PID file already exists!\n"); /* * Try to read the PID stored in the existing file */ pf = open(pidfile, O_RDONLY); if (pf == -1) { perror("Error opening PID file for reading"); exit(EX_IOERR); } if (read(pf, strpid, MAXPIDLEN) < 0) { perror("Error reading PID file"); exit(EX_IOERR); } pid = atol(strpid); close(pf); /* * We found PID, try to determine, whether process * is running */ if (kill(pid, 0) == 0) { /* * Signal is delivered, though process with * such PID exists */ fprintf(stderr, "%s already running with PID=%d, exiting...\n", prog, pid); exit(1); } else { /* * It seems, like the process is killed, so * we can proceed... */ fprintf(stderr, "Stale PID file, overwriting...\n"); pf = open(pidfile, O_WRONLY | O_TRUNC | O_EXLOCK); if (pf == -1) { perror("Error opening PID file for writing"); exit(EX_IOERR); } } } else { perror("Error creating PID file"); exit(EX_IOERR); } } if (detach) { if (daemon(nochdir, 0) != 0) { close(pf); (void)unlink(pidfile); perror("daemon"); exit(EX_OSERR); } } /* * Set signal handlers and system behaviour. This must be done * before saving PID to prevent small, but possible race condition * when another instance failed to create PID, reads it and tries * to send signal to us. */ siginterrupt(SIGTERM, 1); siginterrupt(SIGHUP, 1); /* Ignore 0th signal, or process may be killed with it by default... */ signal(0, SIG_IGN); signal(SIGINT, quit_sig); signal(SIGTERM, quit_sig); signal(SIGHUP, reopen_log); signal(SIGALRM, flush_log); /* Save our PID to pidfile. */ bzero(strpid, MAXPIDLEN + 1); snprintf(strpid, MAXPIDLEN, "%ld\n", getpid()); if (write(pf, strpid, strlen(strpid)) < 0) { perror("Error writing PID file"); exit(EX_IOERR); } close(pf); } void usage() { fprintf(stderr, "usage: %s [-dz] [-r | -rr] [-i flush_interval] [-b maxbytes] [-p maxpkts] [-P pidfile] portnum dumpfile\n", prog); exit(EX_USAGE); } main(int argc, char *argv[]) { int r, sd, portnum, l; struct sockaddr_in sin; int errflg = 0, zeroize = 0; int nfd; fd_set rds; ssize_t nr; char buf[BUFMAX]; int debug = 0; int reflect = 0; /* 1 == write packet back to socket. */ ssize_t totbytes = 0, maxbytes = 0; ssize_t totpkts = 0, maxpkts = 0; struct pcap_pkthdr phd; pcap_t *p; pcap_dumper_t *dp; /* Global, as signal handlers may want it. */ char *dumpf; prog = argv[0]; while ((r = getopt(argc, argv, "drzb:i:p:P:")) != -1) { switch (r) { case 'd': debug = 1; break; case 'r': reflect++; break; case 'i': flush_interval = atoi(optarg); if ((flush_interval < 5) || (flush_interval > 3600)) flush_interval = DEFINTERVAL; break; case 'b': maxbytes = (ssize_t)atol(optarg); break; case 'p': maxpkts = (ssize_t)atoi(optarg); break; case 'z': zeroize = 1; break; case 'P': strlcpy(pidfile, optarg, sizeof(pidfile)); break; case '?': default: errflg++; break; } } if (((argc - optind) != 2) || errflg) usage(); portnum = atoi(argv[optind++]); dumpf = argv[optind]; if (debug) fprintf(stderr, "bind to %d.\ndump to '%s'.\n", portnum, dumpf); if ((r = socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT)) == -1) { perror("socket(DIVERT)"); exit(EX_OSERR); } sd = r; sin.sin_port = htons(portnum); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) { perror("bind(divert)"); exit(EX_OSERR); } p = pcap_open_dead(DLT_RAW, BUFMAX); dp = pcap_dump_open(p, dumpf); if (dp == NULL) { pcap_perror(p, dumpf); exit(EX_OSFILE); } /* * We will not chdir() to root directory if user specified * non-absolute pathname to logfile, because in this case * logfile will be created in another directory after first * reopening on SIGHUP. */ okay(portnum, !debug, dumpf[0] == '/' ? 0 : 1); alarm(flush_interval); /* Start timer. */ nfd = sd + 1; while (!quit) { /* * Handle signal actions on next iteration after select()'s EINTR. */ if (do_flush) { if (debug) fprintf(stderr, "Flushing log.\n"); pcap_dump_flush(dp); do_flush = 0; } if (do_reopen) { if (debug) fprintf(stderr, "Reopening log.\n"); pcap_dump_close(dp); dp = pcap_dump_open(p, dumpf); if (zeroize) { totbytes = 0; totpkts = 0; } do_reopen = 0; } /* Prepare for select(). */ FD_ZERO(&rds); FD_SET(sd, &rds); r = select(nfd, &rds, NULL, NULL, NULL); if (r == -1) { if (errno == EINTR) continue; perror("select"); QUIT(EX_OSERR); } if (!FD_ISSET(sd, &rds)) continue; /* Hmm. No work. */ /* * Use recvfrom(3 and sendto(3) as in natd(8). * See /usr/src/sbin/natd/natd.c. * See ipfw(8) about using 'divert' and 'tee'. */ /* * Read packet. */ l = sizeof(sin); nr = recvfrom(sd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &l); if (debug) fprintf(stderr, "recvfrom(%d) = %d (%d)\n", sd, nr, l); if ((nr < 0) && (errno != EINTR)) { perror("recvfrom(sd)"); QUIT(EX_IOERR); } if (nr <= 0) continue; if (reflect > 0) { /* * Write packet back so it can continue being * processed by any further IPFW rules. */ l = sizeof(sin); r = sendto(sd, buf, nr, 0, (struct sockaddr *)&sin, l); if (debug) fprintf(stderr, "sendto(%d) = %d\n", sd, r); if (r < 0) { perror("sendto(sd)"); QUIT(EX_IOERR); } } /* * Check maximums, if any. But don't quit if must continue * reflecting packets. However, it's ok to exit when * reflect > 1. */ if (maxpkts) { totpkts++; if (totpkts > maxpkts) { if (reflect == 1) continue; QUIT(EX_OK); } } if (maxbytes) { totbytes += nr; if (totbytes > maxbytes) { if (reflect == 1) continue; QUIT(EX_OK); } } /* * Save packet in tcpdump(1) format. See pcap(3). Divert * packets are fully assembled, see ipfw(8). */ (void)gettimeofday(&(phd.ts), NULL); phd.caplen = phd.len = nr; pcap_dump((u_char *)dp, &phd, buf); if (ferror((FILE *)dp)) { perror(dumpf); QUIT(EX_IOERR); } } QUIT(EX_OK); }