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