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