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