Warn if the event loop or pcap_dispatch are too slow.
[darkstat] / cap.c
1 /* darkstat 3
2 * copyright (c) 2001-2011 Emil Mikulic.
3 *
4 * cap.c: interface to libpcap.
5 *
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
8 */
9
10 #include "acct.h"
11 #include "cdefs.h"
12 #include "cap.h"
13 #include "config.h"
14 #include "conv.h"
15 #include "decode.h"
16 #include "err.h"
17 #include "hosts_db.h"
18 #include "localip.h"
19 #include "now.h"
20 #include "opt.h"
21
22 #include <sys/ioctl.h>
23 #include <sys/types.h>
24 #include <sys/socket.h>
25 #include <sys/wait.h>
26 #ifdef HAVE_SYS_FILIO_H
27 # include <sys/filio.h> /* Solaris' FIONBIO hides here */
28 #endif
29 #include <assert.h>
30 #include <pcap.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 /* The cap process life-cycle:
37 *
38 * Init - cap_init()
39 * Fill fd_set - cap_fd_set()
40 * Poll - cap_poll()
41 * Stop - cap_stop()
42 */
43
44 /* Globals - only useful within this module. */
45 static pcap_t *pcap = NULL;
46 static int pcap_fd = -1;
47 static const struct linkhdr *linkhdr = NULL;
48
49 #define CAP_TIMEOUT 500 /* granularity of capture buffer, in milliseconds */
50
51 /* ---------------------------------------------------------------------------
52 * Init pcap. Exits on failure.
53 */
54 void
55 cap_init(const char *device, const char *filter, int promisc)
56 {
57 char errbuf[PCAP_ERRBUF_SIZE], *tmp_device;
58 int linktype, snaplen, waited;
59
60 /* pcap doesn't like device being const */
61 tmp_device = xstrdup(device);
62
63 /* Open packet capture descriptor. */
64 waited = 0;
65 for (;;) {
66 errbuf[0] = '\0'; /* zero length string */
67 pcap = pcap_open_live(
68 tmp_device,
69 1, /* snaplen, irrelevant at this point */
70 0, /* promisc, also irrelevant */
71 CAP_TIMEOUT,
72 errbuf);
73 if (pcap != NULL) break; /* success! */
74
75 if ((opt_wait_secs != -1) && strstr(errbuf, "device is not up")) {
76 if ((opt_wait_secs > 0) && (waited >= opt_wait_secs))
77 errx(1, "waited %d secs, giving up: pcap_open_live(): %s",
78 waited, errbuf);
79
80 verbosef("waited %d secs, interface is not up", waited);
81 sleep(1);
82 waited++;
83 }
84 else errx(1, "pcap_open_live(): %s", errbuf);
85 }
86
87 /* Work out the linktype and what snaplen we need. */
88 linktype = pcap_datalink(pcap);
89 verbosef("linktype is %d", linktype);
90 if ((linktype == DLT_EN10MB) && opt_want_macs)
91 hosts_db_show_macs = 1;
92 linkhdr = getlinkhdr(linktype);
93 if (linkhdr == NULL)
94 errx(1, "unknown linktype %d", linktype);
95 if (linkhdr->decoder == NULL)
96 errx(1, "no decoder for linktype %d", linktype);
97 snaplen = getsnaplen(linkhdr);
98 if (opt_want_pppoe) {
99 snaplen += PPPOE_HDR_LEN;
100 if (linktype != DLT_EN10MB)
101 errx(1, "can't do PPPoE decoding on a non-Ethernet linktype");
102 }
103 verbosef("calculated snaplen minimum %d", snaplen);
104 #ifdef linux
105 /* Ubuntu 9.04 has a problem where requesting snaplen <= 60 will
106 * give us 42 bytes, and we need at least 54 for TCP headers.
107 *
108 * Hack to set minimum snaplen to tcpdump's default:
109 */
110 snaplen = MAX(snaplen, 96);
111 #endif
112 if (opt_want_snaplen > -1)
113 snaplen = opt_want_snaplen;
114 verbosef("using snaplen %d", snaplen);
115
116 /* Close and re-open pcap to use the new snaplen. */
117 pcap_close(pcap);
118 errbuf[0] = '\0'; /* zero length string */
119 pcap = pcap_open_live(
120 tmp_device,
121 snaplen,
122 promisc,
123 CAP_TIMEOUT,
124 errbuf);
125
126 if (pcap == NULL)
127 errx(1, "pcap_open_live(): %s", errbuf);
128
129 if (errbuf[0] != '\0') /* not zero length anymore -> warning */
130 warnx("pcap_open_live() warning: %s", errbuf);
131
132 free(tmp_device);
133
134 if (promisc)
135 verbosef("capturing in promiscuous mode");
136 else
137 verbosef("capturing in non-promiscuous mode");
138
139 /* Set filter expression, if any. */
140 if (filter != NULL)
141 {
142 struct bpf_program prog;
143 char *tmp_filter = xstrdup(filter);
144 if (pcap_compile(
145 pcap,
146 &prog,
147 tmp_filter,
148 1, /* optimize */
149 0) /* netmask */
150 == -1)
151 errx(1, "pcap_compile(): %s", pcap_geterr(pcap));
152
153 if (pcap_setfilter(pcap, &prog) == -1)
154 errx(1, "pcap_setfilter(): %s", pcap_geterr(pcap));
155
156 pcap_freecode(&prog);
157 free(tmp_filter);
158 }
159
160 pcap_fd = pcap_fileno(pcap);
161
162 /* set non-blocking */
163 #ifdef linux
164 if (pcap_setnonblock(pcap, 1, errbuf) == -1)
165 errx(1, "pcap_setnonblock(): %s", errbuf);
166 #else
167 { int one = 1;
168 if (ioctl(pcap_fd, FIONBIO, &one) == -1)
169 err(1, "ioctl(pcap_fd, FIONBIO)"); }
170 #endif
171
172 #ifdef BIOCSETWF
173 {
174 /* Deny all writes to the socket */
175 struct bpf_insn bpf_wfilter[] = { BPF_STMT(BPF_RET+BPF_K, 0) };
176 int wf_len = sizeof(bpf_wfilter) / sizeof(struct bpf_insn);
177 struct bpf_program pr;
178
179 pr.bf_len = wf_len;
180 pr.bf_insns = bpf_wfilter;
181
182 if (ioctl(pcap_fd, BIOCSETWF, &pr) == -1)
183 err(1, "ioctl(pcap_fd, BIOCSETFW)");
184 verbosef("filtered out BPF writes");
185 }
186 #endif
187
188 #ifdef BIOCLOCK
189 /* set "locked" flag (no reset) */
190 if (ioctl(pcap_fd, BIOCLOCK) == -1)
191 err(1, "ioctl(pcap_fd, BIOCLOCK)");
192 verbosef("locked down BPF for security");
193 #endif
194 }
195
196 /*
197 * Set pcap_fd in the given fd_set.
198 */
199 void
200 cap_fd_set(
201 #ifdef linux
202 fd_set *read_set _unused_,
203 int *max_fd _unused_,
204 struct timeval *timeout,
205 #else
206 fd_set *read_set,
207 int *max_fd,
208 struct timeval *timeout _unused_,
209 #endif
210 int *need_timeout)
211 {
212 assert(*need_timeout == 0); /* we're first to get a shot at this */
213 #ifdef linux
214 /*
215 * Linux's BPF is immediate, so don't select() as it will lead to horrible
216 * performance. Instead, use a timeout for buffering.
217 */
218 *need_timeout = 1;
219 timeout->tv_sec = 0;
220 timeout->tv_usec = CAP_TIMEOUT * 1000; /* msec->usec */
221 #else
222 /* We have a BSD-like BPF, we can select() on it. */
223 FD_SET(pcap_fd, read_set);
224 *max_fd = MAX(*max_fd, pcap_fd);
225 #endif
226 }
227
228 unsigned int cap_pkts_recv = 0, cap_pkts_drop = 0;
229
230 static void
231 cap_stats_update(void)
232 {
233 struct pcap_stat ps;
234
235 if (pcap_stats(pcap, &ps) != 0) {
236 warnx("pcap_stats(): %s", pcap_geterr(pcap));
237 return;
238 }
239
240 cap_pkts_recv = ps.ps_recv;
241 cap_pkts_drop = ps.ps_drop;
242 }
243
244 /*
245 * Print hexdump of received packet.
246 */
247 static void
248 hexdump(const u_char *buf, const uint32_t len)
249 {
250 uint32_t i, col;
251
252 printf("packet of %u bytes:\n", len);
253 for (i=0, col=0; i<len; i++) {
254 if (col == 0) printf(" ");
255 printf("%02x", buf[i]);
256 if (i+1 == linkhdr->hdrlen)
257 printf("|"); /* marks end of link headers (e.g. ethernet) */
258 else
259 printf(" ");
260 col += 3;
261 if (col >= 72) {
262 printf("\n");
263 col = 0;
264 }
265 }
266 if (col != 0) printf("\n");
267 printf("\n");
268 }
269
270 /* Callback function for pcap_dispatch() which chains to the decoder specified
271 * in linkhdr struct.
272 */
273 static void callback(u_char *user _unused_,
274 const struct pcap_pkthdr *pheader,
275 const u_char *pdata) {
276 struct pktsummary sm;
277
278 if (opt_want_hexdump)
279 hexdump(pdata, pheader->caplen);
280 memset(&sm, 0, sizeof(sm));
281 if (linkhdr->decoder(pheader, pdata, &sm))
282 acct_for(&sm);
283 }
284
285 /*
286 * Process any packets currently in the capture buffer.
287 */
288 void
289 cap_poll(fd_set *read_set
290 #ifdef linux
291 _unused_
292 #endif
293 )
294 {
295 int total, ret;
296
297 #ifndef linux /* We don't use select() on Linux. */
298 if (!FD_ISSET(pcap_fd, read_set)) {
299 verbosef("cap_poll premature");
300 return;
301 }
302 #endif
303
304 /* Once per capture poll, check our IP address. It's used in accounting
305 * for traffic graphs.
306 */
307 localip_update(opt_interface, local_ips);
308
309 total = 0;
310 for (;;) {
311 struct timespec t;
312
313 timer_start(&t);
314 ret = pcap_dispatch(
315 pcap,
316 -1, /* count, -1 = entire buffer */
317 callback,
318 NULL); /* user */
319 if (ret < 0) {
320 warnx("pcap_dispatch(): %s", pcap_geterr(pcap));
321 return;
322 }
323 timer_stop(&t, 2*CAP_TIMEOUT*1000000, "pcap_dispatch took too long");
324
325 /* Despite count = -1, Linux will only dispatch one packet at a time. */
326 total += ret;
327
328 #ifdef linux
329 /* keep looping until we've dispatched all the outstanding packets */
330 if (ret == 0) break;
331 #else
332 /* we get them all on the first shot */
333 break;
334 #endif
335 }
336 cap_stats_update();
337 }
338
339 void
340 cap_stop(void)
341 {
342 pcap_close(pcap);
343 }
344
345 /* Run through entire capfile. */
346 void
347 cap_from_file(const char *capfile, const char *filter)
348 {
349 char errbuf[PCAP_ERRBUF_SIZE];
350 int linktype, ret;
351
352 /* Open packet capture descriptor. */
353 errbuf[0] = '\0'; /* zero length string */
354 pcap = pcap_open_offline(capfile, errbuf);
355
356 if (pcap == NULL)
357 errx(1, "pcap_open_offline(): %s", errbuf);
358
359 if (errbuf[0] != '\0') /* not zero length anymore -> warning */
360 warnx("pcap_open_offline() warning: %s", errbuf);
361
362 /* Work out the linktype. */
363 linktype = pcap_datalink(pcap);
364 linkhdr = getlinkhdr(linktype);
365 if (linkhdr == NULL)
366 errx(1, "unknown linktype %d", linktype);
367 if (linkhdr->decoder == NULL)
368 errx(1, "no decoder for linktype %d", linktype);
369 if (linktype == DLT_EN10MB) /* FIXME: impossible with capfile? */
370 hosts_db_show_macs = 1;
371
372 /* Set filter expression, if any. */ /* FIXME: factor! */
373 if (filter != NULL)
374 {
375 struct bpf_program prog;
376 char *tmp_filter = xstrdup(filter);
377 if (pcap_compile(
378 pcap,
379 &prog,
380 tmp_filter,
381 1, /* optimize */
382 0) /* netmask */
383 == -1)
384 errx(1, "pcap_compile(): %s", pcap_geterr(pcap));
385
386 if (pcap_setfilter(pcap, &prog) == -1)
387 errx(1, "pcap_setfilter(): %s", pcap_geterr(pcap));
388
389 pcap_freecode(&prog);
390 free(tmp_filter);
391 }
392
393 /* Process file. */
394 ret = pcap_dispatch(
395 pcap,
396 -1, /* count, -1 = entire buffer */
397 callback,
398 NULL); /* user */
399
400 if (ret < 0)
401 errx(1, "pcap_dispatch(): %s", pcap_geterr(pcap));
402 }
403
404 /* vim:set ts=3 sw=3 tw=78 expandtab: */