tcpdump mailing list archives

Re: forcing pcap_loop() failures


From: Guy Harris <guy () alum mit edu>
Date: Thu, 26 Oct 2006 03:20:35 -0700

Eloy Paris wrote:
Guy,

On Wed, Oct 18, 2006 at 04:12:04PM -0700, Guy Harris wrote:

[...]

Note that in some systems with BPF (older versions of {Free,Net,Open,Dragonfly}BSD, current versions of Mac OS X), select() (and poll()) don't work correctly on BPF devices, and so you have to work around that. (Note also that poll() doesn't work on *ANY* character special file, including BPF devices but also including ttys/ ptys, in OS X 10.4[.x], i.e. Tiger.)

The workaround is to

        1) put the pcap_t into non-blocking mode (use pcap_setnonblock());

2) use the timeout value you used when opening as the timeout value for select();

3) call pcap_dispatch() if the timeout expires, not just if select() says the pcap FD is readable.

(And, as indicated, do *NOT* use poll() if you can avoid it, as, in OS 10.4[.x], you can't use poll() on the descriptor you get from pcap_get_selectable_fd(). Note that the GLib and thus GTK+ main loop uses poll(), not select.)

How would one notice that select() is not working correctly on a BPF
device on some of the BSDs?

By compiling and running the attached program (compile with -lpcap) in one window, with "-i" used to select an interface (if the default one isn't active, e.g. on OS X if you're using AirPort rather than Ethernet, use "-i en1") and with "icmp" as a filter argument, and pinging some other host in another window.

If you run it without the "-s" and "-t" flag, it just uses pcap_loop(); you should see it print "Saw a packet" shortly after a ping goes out or a ping reply arrives.

If you run it with "-s" but without "-t", it uses select() without a timeout, and pcap_dispatch(); you probably won't see it print "Saw a packet" until enough pings go out or ping replies arrive, where "enough" is probably more than you'll see unless you let it run a really long time.

If you run it with "-s" and "-t", it uses select() with a timeout (i.e., with the workaround), and pcap_dispatch(); you'll see it print "Saw a packet" shortly after a ping goes out or a ping reply arrives.

You mention that recent versions of OS X
are affected,

Actually, all versions are affected by the problem with select(). (I said "current versions" not to imply that older versions didn't have the problem, but to indicate that, unlike some other BSD-flavored OSes, recent versions of Darwin/OS X do *not* have the bug fixed.)

but I have an application that runs on OS X, that uses
select() on a BPF device, that doesn't use the workaround mentioned
above (and in pcap's man page), and I don't see any signs of broken
behavior.

How much traffic is your application getting? If enough shows up, the BPF kernel "store buffer" fills, which causes a select() on the BPF device to complete, so if it's seeing a lot of traffic, you won't see much of a delay before select() returns.

That's why the "icmp" filter - unless your machine is continuously sending or receiving a lot of ICMP packets, the test program won't see a lot of traffic. (It doesn't run in promiscuous mode.)

So, what are the symptoms of the broken behavior?

See above.
/*
 * Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 2000
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: (1) source code distributions
 * retain the above copyright notice and this paragraph in its entirety, (2)
 * distributions including binary code include the above copyright notice and
 * this paragraph in its entirety in the documentation or other materials
 * provided with the distribution, and (3) all advertising materials mentioning
 * features or use of this software display the following acknowledgement:
 * ``This product includes software developed by the University of California,
 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
 * the University nor the names of its contributors may be used to endorse
 * or promote products derived from this software without specific prior
 * written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Support for splitting captures into multiple files with a maximum
 * file size:
 *
 * Copyright (c) 2001
 *      Seth Webster <swebster () sst ll mit edu>
 */

#ifndef lint
static const char copyright[] =
    "@(#) Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 2000\n\
The Regents of the University of California.  All rights reserved.\n";
#endif

#include <pcap.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

char *program_name;

/* Forwards */
static void cleanup(int);
static void printme(u_char *, const struct pcap_pkthdr *, const u_char *);
static void usage(void) __attribute__((noreturn));
static void error(const char *, ...);
static void warning(const char *, ...);
static char *copy_argv(char **);

static pcap_t *pd;

extern int optind;
extern int opterr;
extern char *optarg;

int
main(int argc, char **argv)
{
        register int op, i;
        bpf_u_int32 localnet, netmask;
        register char *cp, *cmdbuf, *device;
        int doselect, dotimeout;
        struct bpf_program fcode;
        void (*oldhandler)(int);
        char ebuf[PCAP_ERRBUF_SIZE];
        int status;

        device = NULL;
        doselect = 0;
        dotimeout = 0;
        if ((cp = strrchr(argv[0], '/')) != NULL)
                program_name = cp + 1;
        else
                program_name = argv[0];

        opterr = 0;
        while ((op = getopt(argc, argv, "i:st")) != -1)
                switch (op) {

                case 'i':
                        device = optarg;
                        break;

                case 's':
                        doselect = 1;
                        break;

                case 't':
                        dotimeout = 1;
                        break;

                default:
                        usage();
                        /* NOTREACHED */
                }

        if (device == NULL) {
                device = pcap_lookupdev(ebuf);
                if (device == NULL)
                        error("%s", ebuf);
        }
        *ebuf = '\0';
        pd = pcap_open_live(device, 65535, 0, 1000, ebuf);
        if (pd == NULL)
                error("%s", ebuf);
        else if (*ebuf)
                warning("%s", ebuf);
        i = pcap_snapshot(pd);
        if (65535 < i)
                warning("snaplen raised from 65535 to %d", 65535, i);
        if (pcap_lookupnet(device, &localnet, &netmask, ebuf) < 0) {
                localnet = 0;
                netmask = 0;
                warning("%s", ebuf);
        }
        cmdbuf = copy_argv(&argv[optind]);

        if (pcap_compile(pd, &fcode, cmdbuf, 1, netmask) < 0)
                error("%s", pcap_geterr(pd));

        (void)signal(SIGPIPE, cleanup);
        (void)signal(SIGTERM, cleanup);
        (void)signal(SIGINT, cleanup);
        /* Cooperate with nohup(1) */
        if ((oldhandler = signal(SIGHUP, cleanup)) != SIG_DFL)
                (void)signal(SIGHUP, oldhandler);

        if (pcap_setfilter(pd, &fcode) < 0)
                error("%s", pcap_geterr(pd));
        if (pcap_get_selectable_fd(pd) == -1)
                fprintf(stderr, "Select ain't gonna work...\n");
        if (doselect) {
                do {
                        fd_set set1;
                        struct timeval timeout;

                        FD_ZERO(&set1);
                        FD_SET(pcap_fileno(pd), &set1);
                        if (dotimeout) {
                                timeout.tv_sec = 0;
                                timeout.tv_usec = 1000;
                                select(pcap_fileno(pd) + 1, &set1, NULL, NULL,
                                    &timeout);
                        } else
                                select(pcap_fileno(pd) + 1, &set1, NULL, NULL, NULL);
                        status = pcap_dispatch(pd, -1, printme, NULL);
                } while (status >= 0);
        } else
                status = pcap_loop(pd, -1, printme, NULL);
        if (status == -2) {
                /*
                 * We got interrupted, so perhaps we didn't
                 * manage to finish a line we were printing.
                 * Print an extra newline, just in case.
                 */
                putchar('\n');
        }
        (void)fflush(stdout);
        if (status == -1) {
                /*
                 * Error.  Report it.
                 */
                (void)fprintf(stderr, "%s: pcap_loop: %s\n",
                    program_name, pcap_geterr(pd));
        }
        pcap_close(pd);
        exit(status == -1 ? 1 : 0);
}

/* make a clean exit on interrupts */
static void
cleanup(int sign)
{
#if defined(HAVE_ALARM)
        alarm(0);
#endif

#ifdef HAVE_PCAP_BREAKLOOP
        /*
         * We have "pcap_breakloop()"; use it, so that we do as little
         * as possible in the signal handler (it's probably not safe
         * to do anything with standard I/O streams in a signal handler -
         * the ANSI C standard doesn't say it is).
         */
        pcap_breakloop(pd);
#else
        /*
         * We don't have "pcap_breakloop()"; this isn't safe, but
         * it's the best we can do.  Print the summary if we're
         * not reading from a savefile - i.e., if we're doing a
         * live capture - and exit.
         */
        if (pd != NULL && pcap_file(pd) == NULL) {
                /*
                 * We got interrupted, so perhaps we didn't
                 * manage to finish a line we were printing.
                 * Print an extra newline, just in case.
                 */
                putchar('\n');
                (void)fflush(stdout);
        }
        exit(0);
#endif
}

static void
printme(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
{
        printf("Saw a packet\n");
}

static void
usage(void)
{
        (void)fprintf(stderr, "Usage: %s [ -st ] [ -i interface ]\n",
            program_name);
        exit(1);
}

/* VARARGS */
static void
error(const char *fmt, ...)
{
        va_list ap;

        (void)fprintf(stderr, "%s: ", program_name);
        va_start(ap, fmt);
        (void)vfprintf(stderr, fmt, ap);
        va_end(ap);
        if (*fmt) {
                fmt += strlen(fmt);
                if (fmt[-1] != '\n')
                        (void)fputc('\n', stderr);
        }
        exit(1);
        /* NOTREACHED */
}

/* VARARGS */
static void
warning(const char *fmt, ...)
{
        va_list ap;

        (void)fprintf(stderr, "%s: WARNING: ", program_name);
        va_start(ap, fmt);
        (void)vfprintf(stderr, fmt, ap);
        va_end(ap);
        if (*fmt) {
                fmt += strlen(fmt);
                if (fmt[-1] != '\n')
                        (void)fputc('\n', stderr);
        }
}

/*
 * Copy arg vector into a new buffer, concatenating arguments with spaces.
 */
static char *
copy_argv(register char **argv)
{
        register char **p;
        register u_int len = 0;
        char *buf;
        char *src, *dst;

        p = argv;
        if (*p == 0)
                return 0;

        while (*p)
                len += strlen(*p++) + 1;

        buf = (char *)malloc(len);
        if (buf == NULL)
                error("copy_argv: malloc");

        p = argv;
        dst = buf;
        while ((src = *p++) != NULL) {
                while ((*dst++ = *src++) != '\0')
                        ;
                dst[-1] = ' ';
        }
        dst[-1] = '\0';

        return buf;
}

/*
 * On Windows, we need to open the file in binary mode, so that
 * we get all the bytes specified by the size we get from "fstat()".
 * On UNIX, that's not necessary.  O_BINARY is defined on Windows;
 * we define it as 0 if it's not defined, so it does nothing.
 */
#ifndef O_BINARY
#define O_BINARY        0
#endif

char *
read_infile(char *fname)
{
        register int i, fd, cc;
        register char *cp;
        struct stat buf;

        fd = open(fname, O_RDONLY|O_BINARY);
        if (fd < 0)
                error("can't open %s: %s", fname, pcap_strerror(errno));

        if (fstat(fd, &buf) < 0)
                error("can't stat %s: %s", fname, pcap_strerror(errno));

        cp = malloc((u_int)buf.st_size + 1);
        if (cp == NULL)
                error("malloc(%d) for %s: %s", (u_int)buf.st_size + 1,
                        fname, pcap_strerror(errno));
        cc = read(fd, cp, (u_int)buf.st_size);
        if (cc < 0)
                error("read %s: %s", fname, pcap_strerror(errno));
        if (cc != buf.st_size)
                error("short read %s (%d != %d)", fname, cc, (int)buf.st_size);

        close(fd);
        /* replace "# comment" with spaces */
        for (i = 0; i < cc; i++) {
                if (cp[i] == '#')
                        while (i < cc && cp[i] != '\n')
                                cp[i++] = ' ';
        }
        cp[cc] = '\0';
        return (cp);
}
-
This is the tcpdump-workers list.
Visit https://cod.sandelman.ca/ to unsubscribe.

Current thread: