~ubuntu-branches/ubuntu/natty/darkstat/natty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
/* darkstat 3
 * copyright (c) 2001-2010 Emil Mikulic.
 *
 * cap.c: interface to libpcap.
 *
 * You may use, modify and redistribute this file under the terms of the
 * GNU General Public License version 2. (see COPYING.GPL)
 */

#include "darkstat.h"
#include "cap.h"
#include "conv.h"
#include "decode.h"
#include "hosts_db.h"
#include "localip.h"

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h> /* Solaris' FIONBIO hides here */
#endif
#include <assert.h>
#include "err.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

extern int want_pppoe, want_macs, want_hexdump, want_snaplen, wait_secs;

/* The cap process life-cycle:
 *
 * Init           - cap_init()
 * Fill fd_set    - cap_fd_set()
 * Poll           - cap_poll()
 * Stop           - cap_stop()
 */

/* Globals - only useful within this module. */
static pcap_t *pcap = NULL;
static int pcap_fd = -1;
static const linkhdr_t *linkhdr = NULL;

#define CAP_TIMEOUT 500 /* granularity of capture buffer, in milliseconds */

/* ---------------------------------------------------------------------------
 * Init pcap.  Exits on failure.
 */
void
cap_init(const char *device, const char *filter, int promisc)
{
   char errbuf[PCAP_ERRBUF_SIZE], *tmp_device;
   int linktype, snaplen, waited;

   /* pcap doesn't like device being const */
   tmp_device = xstrdup(device);

   /* Open packet capture descriptor. */
   waited = 0;
   for (;;) {
      errbuf[0] = '\0'; /* zero length string */
      pcap = pcap_open_live(
         tmp_device,
         1,          /* snaplen, irrelevant at this point */
         0,          /* promisc, also irrelevant */
         CAP_TIMEOUT,
         errbuf);
      if (pcap != NULL) break; /* success! */

      if ((wait_secs != -1) && strstr(errbuf, "device is not up")) {
         if ((wait_secs > 0) && (waited >= wait_secs))
            errx(1, "waited %d secs, giving up: pcap_open_live(): %s",
               waited, errbuf);

         verbosef("waited %d secs, interface is not up", waited);
         sleep(1);
         waited++;
      }
      else errx(1, "pcap_open_live(): %s", errbuf);
   }

   /* Work out the linktype and what snaplen we need. */
   linktype = pcap_datalink(pcap);
   verbosef("linktype is %d", linktype);
   if ((linktype == DLT_EN10MB) && want_macs)
      show_mac_addrs = 1;
   linkhdr = getlinkhdr(linktype);
   if (linkhdr == NULL)
      errx(1, "unknown linktype %d", linktype);
   if (linkhdr->handler == NULL)
      errx(1, "no handler for linktype %d", linktype);
   snaplen = getsnaplen(linkhdr);
   if (want_pppoe) {
      snaplen += PPPOE_HDR_LEN;
      if (linktype != DLT_EN10MB)
         errx(1, "can't do PPPoE decoding on a non-Ethernet linktype");
   }
   verbosef("calculated snaplen minimum %d", snaplen);
#ifdef linux
   /* Ubuntu 9.04 has a problem where requesting snaplen <= 60 will
    * give us 42 bytes, and we need at least 54 for TCP headers.
    *
    * Hack to set minimum snaplen to tcpdump's default:
    */
   snaplen = max(snaplen, 68);
#endif
   if (want_snaplen > -1)
      snaplen = want_snaplen;
   verbosef("using snaplen %d", snaplen);

   /* Close and re-open pcap to use the new snaplen. */
   pcap_close(pcap);
   errbuf[0] = '\0'; /* zero length string */
   pcap = pcap_open_live(
      tmp_device,
      snaplen,
      promisc,
      CAP_TIMEOUT,
      errbuf);

   if (pcap == NULL)
      errx(1, "pcap_open_live(): %s", errbuf);

   if (errbuf[0] != '\0') /* not zero length anymore -> warning */
      warnx("pcap_open_live() warning: %s", errbuf);

   free(tmp_device);

   if (promisc)
      verbosef("capturing in promiscuous mode");
   else
      verbosef("capturing in non-promiscuous mode");

   /* Set filter expression, if any. */
   if (filter != NULL)
   {
      struct bpf_program prog;
      char *tmp_filter = xstrdup(filter);
      if (pcap_compile(
            pcap,
            &prog,
            tmp_filter,
            1,          /* optimize */
            0)          /* netmask */
            == -1)
         errx(1, "pcap_compile(): %s", pcap_geterr(pcap));

      if (pcap_setfilter(pcap, &prog) == -1)
         errx(1, "pcap_setfilter(): %s", pcap_geterr(pcap));

      pcap_freecode(&prog);
      free(tmp_filter);
   }

   pcap_fd = pcap_fileno(pcap);

   /* set non-blocking */
#ifdef linux
   if (pcap_setnonblock(pcap, 1, errbuf) == -1)
      errx(1, "pcap_setnonblock(): %s", errbuf);
#else
{ int one = 1;
   if (ioctl(pcap_fd, FIONBIO, &one) == -1)
      err(1, "ioctl(pcap_fd, FIONBIO)"); }
#endif

#ifdef BIOCSETWF
{
   /* Deny all writes to the socket */
   struct bpf_insn bpf_wfilter[] = { BPF_STMT(BPF_RET+BPF_K, 0) };
   int wf_len = sizeof(bpf_wfilter) / sizeof(struct bpf_insn);
   struct bpf_program pr;

   pr.bf_len = wf_len;
   pr.bf_insns = bpf_wfilter;

   if (ioctl(pcap_fd, BIOCSETWF, &pr) == -1)
      err(1, "ioctl(pcap_fd, BIOCSETFW)");
   verbosef("filtered out BPF writes");
}
#endif

#ifdef BIOCLOCK
   /* set "locked" flag (no reset) */
   if (ioctl(pcap_fd, BIOCLOCK) == -1)
      err(1, "ioctl(pcap_fd, BIOCLOCK)");
   verbosef("locked down BPF for security");
#endif
}

/*
 * Set pcap_fd in the given fd_set.
 */
void
cap_fd_set(
#ifdef linux
   fd_set *read_set _unused_,
   int *max_fd _unused_,
   struct timeval *timeout,
#else
   fd_set *read_set,
   int *max_fd,
   struct timeval *timeout _unused_,
#endif
   int *need_timeout)
{
   assert(*need_timeout == 0); /* we're first to get a shot at this */
#ifdef linux
   /*
    * Linux's BPF is immediate, so don't select() as it will lead to horrible
    * performance.  Instead, use a timeout for buffering.
    */
   *need_timeout = 1;
   timeout->tv_sec = 0;
   timeout->tv_usec = CAP_TIMEOUT * 1000; /* msec->usec */
#else
   /* We have a BSD-like BPF, we can select() on it. */
   FD_SET(pcap_fd, read_set);
   *max_fd = max(*max_fd, pcap_fd);
#endif
}

unsigned int pkts_recv = 0, pkts_drop = 0;

static void
cap_stats_update(void)
{
   struct pcap_stat ps;

   if (pcap_stats(pcap, &ps) != 0) {
      warnx("pcap_stats(): %s", pcap_geterr(pcap));
      return;
   }

   pkts_recv = ps.ps_recv;
   pkts_drop = ps.ps_drop;
}

/*
 * Print hexdump of received packet.
 */
static void
hexdump(const u_char *buf, const uint32_t len)
{
   uint32_t i, col;

   printf("packet of %u bytes:\n", len);
   for (i=0, col=0; i<len; i++) {
      if (col == 0) printf(" ");
      printf("%02x", buf[i]);
      if (i+1 == linkhdr->hdrlen)
         printf("[");
      else if (i+1 == linkhdr->hdrlen + IP_HDR_LEN)
         printf("]");
      else printf(" ");
      col += 3;
      if (col >= 72) {
         printf("\n");
         col = 0;
      }
   }
   if (col != 0) printf("\n");
   printf("\n");
}

/*
 * Callback function for pcap_dispatch() which chains to the decoder specified
 * in linkhdr struct.
 */
static void
callback(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
{
   if (want_hexdump) hexdump(bytes, h->caplen);
   linkhdr->handler(user, h, bytes);
}

/*
 * Process any packets currently in the capture buffer.
 */
void
cap_poll(fd_set *read_set
#ifdef linux
   _unused_
#endif
)
{
   int total, ret;

#ifndef linux /* We don't use select() on Linux. */
   if (!FD_ISSET(pcap_fd, read_set)) {
      verbosef("cap_poll premature");
      return;
   }
#endif

   /*
    * Once per capture poll, check our IP address.  It's used in accounting
    * for traffic graphs.
    */
   localip_update(); /* FIXME: this might even be too often */

   total = 0;
   for (;;) {
      ret = pcap_dispatch(
            pcap,
            -1,               /* count, -1 = entire buffer */
            callback,
            NULL);            /* user */

      if (ret < 0) {
         warnx("pcap_dispatch(): %s", pcap_geterr(pcap));
         return;
      }

      /* Despite count = -1, Linux will only dispatch one packet at a time. */
      total += ret;

#ifdef linux
      /* keep looping until we've dispatched all the outstanding packets */
      if (ret == 0) break;
#else
      /* we get them all on the first shot */
      break;
#endif
   }
   /*FIXME*/if (want_verbose) fprintf(stderr, "%-20d\r", total);
   cap_stats_update();
}

void
cap_stop(void)
{
   pcap_close(pcap);
}

/* Run through entire capfile. */
void
cap_from_file(const char *capfile, const char *filter)
{
   char errbuf[PCAP_ERRBUF_SIZE];
   int linktype, ret;

   /* Open packet capture descriptor. */
   errbuf[0] = '\0'; /* zero length string */
   pcap = pcap_open_offline(capfile, errbuf);

   if (pcap == NULL)
      errx(1, "pcap_open_offline(): %s", errbuf);

   if (errbuf[0] != '\0') /* not zero length anymore -> warning */
      warnx("pcap_open_offline() warning: %s", errbuf);

   /* Work out the linktype. */
   linktype = pcap_datalink(pcap);
   linkhdr = getlinkhdr(linktype);
   if (linkhdr == NULL)
      errx(1, "unknown linktype %d", linktype);
   if (linkhdr->handler == NULL)
      errx(1, "no handler for linktype %d", linktype);
   if (linktype == DLT_EN10MB) /* FIXME: impossible with capfile? */
      show_mac_addrs = 1;

   /* Set filter expression, if any. */ /* FIXME: factor! */
   if (filter != NULL)
   {
      struct bpf_program prog;
      char *tmp_filter = xstrdup(filter);
      if (pcap_compile(
            pcap,
            &prog,
            tmp_filter,
            1,          /* optimize */
            0)          /* netmask */
            == -1)
         errx(1, "pcap_compile(): %s", pcap_geterr(pcap));

      if (pcap_setfilter(pcap, &prog) == -1)
         errx(1, "pcap_setfilter(): %s", pcap_geterr(pcap));

      pcap_freecode(&prog);
      free(tmp_filter);
   }

   /* Process file. */
   ret = pcap_dispatch(
         pcap,
         -1,               /* count, -1 = entire buffer */
         callback,
         NULL);            /* user */

   if (ret < 0)
      errx(1, "pcap_dispatch(): %s", pcap_geterr(pcap));
}

/* vim:set ts=3 sw=3 tw=78 expandtab: */