Use pcap_setnonblock() on Linux.
[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;
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, caplen;
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 caplen it needs. */
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 caplen = getcaplen(linkhdr);
83 if (want_pppoe) {
84 caplen += PPPOE_HDR_LEN;
85 if (linktype != DLT_EN10MB)
86 errx(1, "can't do PPPoE decoding on a non-Ethernet linktype");
87 }
88 verbosef("caplen is %d", caplen);
89
90 /* Close and re-open pcap to use the new caplen. */
91 pcap_close(pcap);
92 errbuf[0] = '\0'; /* zero length string */
93 pcap = pcap_open_live(
94 tmp_device,
95 caplen,
96 promisc,
97 CAP_TIMEOUT,
98 errbuf);
99
100 if (pcap == NULL)
101 errx(1, "pcap_open_live(): %s", errbuf);
102
103 if (errbuf[0] != '\0') /* not zero length anymore -> warning */
104 warnx("pcap_open_live() warning: %s", errbuf);
105
106 free(tmp_device);
107
108 if (promisc)
109 verbosef("capturing in promiscuous mode");
110 else
111 verbosef("capturing in non-promiscuous mode");
112
113 /* Set filter expression, if any. */
114 if (filter != NULL)
115 {
116 struct bpf_program prog;
117 char *tmp_filter = xstrdup(filter);
118 if (pcap_compile(
119 pcap,
120 &prog,
121 tmp_filter,
122 1, /* optimize */
123 0) /* netmask */
124 == -1)
125 errx(1, "pcap_compile(): %s", pcap_geterr(pcap));
126
127 if (pcap_setfilter(pcap, &prog) == -1)
128 errx(1, "pcap_setfilter(): %s", pcap_geterr(pcap));
129
130 pcap_freecode(&prog);
131 free(tmp_filter);
132 }
133
134 pcap_fd = pcap_fileno(pcap);
135
136 /* set non-blocking */
137 #ifdef linux
138 if (pcap_setnonblock(pcap, 1, errbuf) == -1)
139 errx(1, "pcap_setnonblock(): %s", errbuf);
140 #else
141 { int one = 1;
142 if (ioctl(pcap_fd, FIONBIO, &one) == -1)
143 err(1, "ioctl(pcap_fd, FIONBIO)"); }
144 #endif
145
146 #ifdef BIOCSETWF
147 {
148 /* Deny all writes to the socket */
149 struct bpf_insn bpf_wfilter[] = { BPF_STMT(BPF_RET+BPF_K, 0) };
150 int wf_len = sizeof(bpf_wfilter) / sizeof(struct bpf_insn);
151 struct bpf_program pr;
152
153 pr.bf_len = wf_len;
154 pr.bf_insns = bpf_wfilter;
155
156 if (ioctl(pcap_fd, BIOCSETWF, &pr) == -1)
157 err(1, "ioctl(pcap_fd, BIOCSETFW)");
158 verbosef("filtered out BPF writes");
159 }
160 #endif
161
162 #ifdef BIOCLOCK
163 /* set "locked" flag (no reset) */
164 if (ioctl(pcap_fd, BIOCLOCK) == -1)
165 err(1, "ioctl(pcap_fd, BIOCLOCK)");
166 verbosef("locked down BPF for security");
167 #endif
168 }
169
170 /*
171 * Set pcap_fd in the given fd_set.
172 */
173 void
174 cap_fd_set(
175 #ifdef linux
176 fd_set *read_set _unused_,
177 int *max_fd _unused_,
178 struct timeval *timeout,
179 #else
180 fd_set *read_set,
181 int *max_fd,
182 struct timeval *timeout _unused_,
183 #endif
184 int *need_timeout)
185 {
186 assert(*need_timeout == 0); /* we're first to get a shot at this */
187 #ifdef linux
188 /*
189 * Linux's BPF is immediate, so don't select() as it will lead to horrible
190 * performance. Instead, use a timeout for buffering.
191 */
192 *need_timeout = 1;
193 timeout->tv_sec = 0;
194 timeout->tv_usec = CAP_TIMEOUT * 1000; /* msec->usec */
195 #else
196 /* We have a BSD-like BPF, we can select() on it. */
197 FD_SET(pcap_fd, read_set);
198 *max_fd = max(*max_fd, pcap_fd);
199 #endif
200 }
201
202 unsigned int pkts_recv = 0, pkts_drop = 0;
203
204 static void
205 cap_stats_update(void)
206 {
207 struct pcap_stat ps;
208
209 if (pcap_stats(pcap, &ps) != 0) {
210 warnx("pcap_stats(): %s", pcap_geterr(pcap));
211 return;
212 }
213
214 pkts_recv = ps.ps_recv;
215 pkts_drop = ps.ps_drop;
216 }
217
218 /*
219 * Print hexdump of received packet.
220 */
221 static void
222 hexdump(const u_char *buf, const uint32_t len)
223 {
224 uint32_t i, col;
225
226 printf("packet of %u bytes:\n", len);
227 col = 0;
228 for (i=0, col=0; i<len; i++) {
229 if (col == 0) printf(" ");
230 printf("%02x", buf[i]);
231 if (i+1 == linkhdr->hdrlen)
232 printf("[");
233 else if (i+1 == linkhdr->hdrlen + IP_HDR_LEN)
234 printf("]");
235 else printf(" ");
236 col += 3;
237 if (col >= 72) {
238 printf("\n");
239 col = 0;
240 }
241 }
242 if (col != 0) printf("\n");
243 printf("\n");
244 }
245
246 /*
247 * Callback function for pcap_dispatch() which chains to the decoder specified
248 * in linkhdr struct.
249 */
250 static void
251 callback(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
252 {
253 if (want_hexdump) hexdump(bytes, h->caplen);
254 linkhdr->handler(user, h, bytes);
255 }
256
257 /*
258 * Process any packets currently in the capture buffer.
259 */
260 void
261 cap_poll(fd_set *read_set
262 #ifdef linux
263 _unused_
264 #endif
265 )
266 {
267 int total, ret;
268
269 #ifndef linux /* We don't use select() on Linux. */
270 if (!FD_ISSET(pcap_fd, read_set)) {
271 verbosef("cap_poll premature");
272 return;
273 }
274 #endif
275
276 /*
277 * Once per capture poll, check our IP address. It's used in accounting
278 * for traffic graphs.
279 */
280 localip_update(); /* FIXME: this might even be too often */
281
282 total = 0;
283 for (;;) {
284 ret = pcap_dispatch(
285 pcap,
286 -1, /* count, -1 = entire buffer */
287 callback,
288 NULL); /* user */
289
290 if (ret < 0) {
291 warnx("pcap_dispatch(): %s", pcap_geterr(pcap));
292 return;
293 }
294
295 /* Despite count = -1, Linux will only dispatch one packet at a time. */
296 total += ret;
297
298 #ifdef linux
299 /* keep looping until we've dispatched all the outstanding packets */
300 if (ret == 0) break;
301 #else
302 /* we get them all on the first shot */
303 break;
304 #endif
305 }
306 /*FIXME*/if (want_verbose) fprintf(stderr, "%-20d\r", total);
307 cap_stats_update();
308 }
309
310 void
311 cap_stop(void)
312 {
313 pcap_close(pcap);
314 }
315
316 /* Run through entire capfile. */
317 void
318 cap_from_file(const char *capfile, const char *filter)
319 {
320 char errbuf[PCAP_ERRBUF_SIZE];
321 int linktype, ret;
322
323 /* Open packet capture descriptor. */
324 errbuf[0] = '\0'; /* zero length string */
325 pcap = pcap_open_offline(capfile, errbuf);
326
327 if (pcap == NULL)
328 errx(1, "pcap_open_offline(): %s", errbuf);
329
330 if (errbuf[0] != '\0') /* not zero length anymore -> warning */
331 warnx("pcap_open_offline() warning: %s", errbuf);
332
333 /* Work out the linktype. */
334 linktype = pcap_datalink(pcap);
335 linkhdr = getlinkhdr(linktype);
336 if (linkhdr == NULL)
337 errx(1, "unknown linktype %d", linktype);
338 if (linkhdr->handler == NULL)
339 errx(1, "no handler for linktype %d", linktype);
340 if (linktype == DLT_EN10MB) /* FIXME: impossible with capfile? */
341 show_mac_addrs = 1;
342
343 /* Set filter expression, if any. */ /* FIXME: factor! */
344 if (filter != NULL)
345 {
346 struct bpf_program prog;
347 char *tmp_filter = xstrdup(filter);
348 if (pcap_compile(
349 pcap,
350 &prog,
351 tmp_filter,
352 1, /* optimize */
353 0) /* netmask */
354 == -1)
355 errx(1, "pcap_compile(): %s", pcap_geterr(pcap));
356
357 if (pcap_setfilter(pcap, &prog) == -1)
358 errx(1, "pcap_setfilter(): %s", pcap_geterr(pcap));
359
360 pcap_freecode(&prog);
361 free(tmp_filter);
362 }
363
364 /* Process file. */
365 ret = pcap_dispatch(
366 pcap,
367 -1, /* count, -1 = entire buffer */
368 callback,
369 NULL); /* user */
370
371 if (ret < 0)
372 errx(1, "pcap_dispatch(): %s", pcap_geterr(pcap));
373 }
374
375 /* vim:set ts=3 sw=3 tw=78 expandtab: */