xref: /src/usr.sbin/ipfwpcap/ipfwpcap.c (revision a0b3ef1952603ebf0307ca723b03e5a71598dd5a)
1 /*
2  * copy diverted (or tee'd) packets to a file in 'tcpdump' format
3  * (ie. this uses the '-lpcap' routines).
4  *
5  * example usage:
6  *	# ipfwpcap -r 8091 divt.log &
7  *	# ipfw add 2864 divert 8091 ip from 128.432.53.82 to any
8  *	# ipfw add 2864 divert 8091 ip from any to 128.432.53.82
9  *
10  *   the resulting dump file can be read with ...
11  *	# tcpdump -nX -r divt.log
12  */
13 /*
14  * Written by P Kern { pkern [AT] cns.utoronto.ca }
15  *
16  * Copyright (c) 2004 University of Toronto. All rights reserved.
17  * Anyone may use or copy this software except that this copyright
18  * notice remain intact and that credit is given where it is due.
19  * The University of Toronto and the author make no warranty and
20  * accept no liability for this software.
21  *
22  * From: Header: /local/src/local.lib/SRC/ipfwpcap/RCS/ipfwpcap.c,v 1.4 2004/01/15 16:19:07 pkern Exp
23  */
24 
25 #include <stdio.h>
26 #include <errno.h>
27 #include <paths.h>
28 #include <fcntl.h>
29 #include <signal.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/time.h>
35 #include <sys/param.h>		/* for MAXPATHLEN */
36 #include <sys/socket.h>
37 #include <netinet/in.h>
38 
39 #include <netinet/in_systm.h>	/* for IP_MAXPACKET */
40 #include <netinet/ip.h>		/* for IP_MAXPACKET */
41 
42 #include <net/bpf.h>
43 
44 #include <pcap.h>
45 
46 #ifdef IP_MAXPACKET
47 #define BUFMAX	IP_MAXPACKET
48 #else
49 #define BUFMAX	65535
50 #endif
51 
52 #ifndef MAXPATHLEN
53 #define MAXPATHLEN	1024
54 #endif
55 
56 static int debug = 0;
57 static int reflect = 0;		/* 1 == write packet back to socket. */
58 
59 static ssize_t totbytes = 0, maxbytes = 0;
60 static ssize_t totpkts = 0, maxpkts = 0;
61 
62 static char *prog = NULL;
63 static char pidfile[MAXPATHLEN];
64 
65 /*
66  * tidy up.
67  */
68 static void
quit(int sig)69 quit(int sig)
70 {
71 	(void) unlink(pidfile);
72 	exit(sig);
73 }
74 
75 /*
76  * do the "paper work"
77  *	- save my own pid in /var/run/$0.{port#}.pid
78  */
79 static void
okay(int pn)80 okay(int pn)
81 {
82 	int fd;
83 	char *p, numbuf[80];
84 
85 	if (pidfile[0] == '\0') {
86 		p = strrchr(prog, '/');
87 		p = (p == NULL) ? prog : p + 1;
88 
89 		snprintf(pidfile, sizeof pidfile,
90 			"%s%s.%d.pid", _PATH_VARRUN, p, pn);
91 	}
92 
93 	fd = open(pidfile, O_WRONLY|O_CREAT|O_EXCL, 0644);
94 	if (fd < 0) {
95 		perror(pidfile);
96 		exit(21);
97 	}
98 
99 	siginterrupt(SIGTERM, 1);
100 	siginterrupt(SIGHUP, 1);
101 	signal(SIGTERM, quit);
102 	signal(SIGHUP, quit);
103 	signal(SIGINT, quit);
104 
105 	snprintf(numbuf, sizeof numbuf, "%d\n", getpid());
106 	if (write(fd, numbuf, strlen(numbuf)) < 0) {
107 		perror(pidfile);
108 		quit(23);
109 	}
110 	(void) close(fd);
111 }
112 
113 static void
usage(void)114 usage(void)
115 {
116 	fprintf(stderr,
117 "\n"
118 "usage:\n"
119 "    %s [-dr] [-b maxbytes] [-p maxpkts] [-P pidfile] portnum dumpfile\n"
120 "\n"
121 "where:\n"
122 "	'-d'  = enable debugging messages.\n"
123 "	'-r'  = reflect. write packets back to the divert socket.\n"
124 "		(ie. simulate the original intent of \"ipfw tee\").\n"
125 "	'-rr' = indicate that it is okay to quit if packet-count or\n"
126 "		byte-count limits are reached (see the NOTE below\n"
127 "		about what this implies).\n"
128 "	'-b bytcnt'   = stop dumping after {bytcnt} bytes.\n"
129 "	'-p pktcnt'   = stop dumping after {pktcnt} packets.\n"
130 "	'-P pidfile'  = alternate file to store the PID\n"
131 "			(default: /var/run/%s.{portnum}.pid).\n"
132 "\n"
133 "	portnum  = divert(4) socket port number.\n"
134 "	dumpfile = file to write captured packets (tcpdump format).\n"
135 "		   (specify '-' to write packets to stdout).\n"
136 "\n"
137 "The '-r' option should not be necessary, but because \"ipfw tee\" is broken\n"
138 "(see BUGS in ipfw(8) for details) this feature can be used along with\n"
139 "an \"ipfw divert\" rule to simulate the original intent of \"ipfw tee\".\n"
140 "\n"
141 "NOTE: With an \"ipfw divert\" rule, diverted packets will silently\n"
142 "      disappear if there is nothing listening to the divert socket.\n"
143 "\n", prog, prog);
144 	exit(1);
145 }
146 
147 int
main(int ac,char * av[])148 main(int ac, char *av[])
149 {
150 	int r, sd, portnum, l;
151         struct sockaddr_in sin;
152 	int errflg = 0;
153 
154 	int nfd;
155 	fd_set rds;
156 
157 	ssize_t nr;
158 
159 	char *dumpf, buf[BUFMAX];
160 
161 	pcap_t *p;
162 	pcap_dumper_t *dp;
163 	struct pcap_pkthdr phd;
164 
165 	prog = av[0];
166 
167 	while ((r = getopt(ac, av, "drb:p:P:")) != -1) {
168 		switch (r) {
169 		case 'd':
170 			debug++;
171 			break;
172 		case 'r':
173 			reflect++;
174 			break;
175 		case 'b':
176 			maxbytes = (ssize_t) atol(optarg);
177 			break;
178 		case 'p':
179 			maxpkts = (ssize_t) atoi(optarg);
180 			break;
181 		case 'P':
182 			strcpy(pidfile, optarg);
183 			break;
184 		case '?':
185 		default:
186 			errflg++;
187 			break;
188 		}
189 	}
190 
191 	if ((ac - optind) != 2 || errflg)
192 		usage();
193 
194 	portnum = atoi(av[optind++]);
195 	dumpf = av[optind];
196 
197 if (debug) fprintf(stderr, "bind to %d.\ndump to '%s'.\n", portnum, dumpf);
198 
199 	if ((r = socket(PF_DIVERT, SOCK_RAW, 0)) == -1) {
200 		perror("socket(DIVERT)");
201 		exit(2);
202 	}
203 	sd = r;
204 
205 	sin.sin_port = htons(portnum);
206 	sin.sin_family = AF_INET;
207 	sin.sin_addr.s_addr = INADDR_ANY;
208 
209 	if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
210 		perror("bind(divert)");
211 		exit(3);
212 	}
213 
214 	p = pcap_open_dead(DLT_RAW, BUFMAX);
215 	dp = pcap_dump_open(p, dumpf);
216 	if (dp == NULL) {
217 		pcap_perror(p, dumpf);
218 		exit(4);
219 	}
220 
221 	okay(portnum);
222 
223 	nfd = sd + 1;
224 	for (;;) {
225 		FD_ZERO(&rds);
226 		FD_SET(sd, &rds);
227 
228 		r = select(nfd, &rds, NULL, NULL, NULL);
229 		if (r == -1) {
230 			if (errno == EINTR) continue;
231 			perror("select");
232 			quit(11);
233 		}
234 
235 		if (!FD_ISSET(sd, &rds))
236 			/* hmm. no work. */
237 			continue;
238 
239 		/*
240 		 * use recvfrom(3 and sendto(3) as in natd(8).
241 		 * see /usr/src/sbin/natd/natd.c
242 		 * see ipfw(8) about using 'divert' and 'tee'.
243 		 */
244 
245 		/*
246 		 * read packet.
247 		 */
248 		l = sizeof(sin);
249 		nr = recvfrom(sd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &l);
250 if (debug) fprintf(stderr, "recvfrom(%d) = %zd (%d)\n", sd, nr, l);
251 		if (nr < 0 && errno != EINTR) {
252 			perror("recvfrom(sd)");
253 			quit(12);
254 		}
255 		if (nr <= 0) continue;
256 
257 		if (reflect) {
258 			/*
259 			 * write packet back so it can continue
260 			 * being processed by any further IPFW rules.
261 			 */
262 			l = sizeof(sin);
263 			r = sendto(sd, buf, nr, 0, (struct sockaddr *)&sin, l);
264 if (debug) fprintf(stderr, "  sendto(%d) = %d\n", sd, r);
265 			if (r < 0) { perror("sendto(sd)"); quit(13); }
266 		}
267 
268 		/*
269 		 * check maximums, if any.
270 		 * but don't quit if must continue reflecting packets.
271 		 */
272 		if (maxpkts) {
273 			totpkts++;
274 			if (totpkts > maxpkts) {
275 				if (reflect == 1) continue;
276 				quit(0);
277 			}
278 		}
279 		if (maxbytes) {
280 			totbytes += nr;
281 			if (totbytes > maxbytes) {
282 				if (reflect == 1) continue;
283 				quit(0);
284 			}
285 		}
286 
287 		/*
288 		 * save packet in tcpdump(1) format. see pcap(3).
289 		 * divert packets are fully assembled. see ipfw(8).
290 		 */
291 		(void) gettimeofday(&(phd.ts), NULL);
292 		phd.caplen = phd.len = nr;
293 		pcap_dump((u_char *)dp, &phd, buf);
294 		if (pcap_dump_flush(dp) == -1) { pcap_perror(p, dumpf); quit(14); }
295 	}
296 
297 	quit(0);
298 }
299