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