From aa1e053ce638e4f2c91184a43703f4c99998b5bb Mon Sep 17 00:00:00 2001 From: Guy Harris Date: Sun, 21 May 2006 21:32:04 +0000 Subject: If we have pcap_breakloop(), at least on UN*X we can stop the capture with a pcap_breakloop() call - we don't need to call select() before calling pcap_dispatch(). Even if we do need to call select(), we don't need to supply it with a timeout - it's OK if we block indefinitely, as the signal will interrupt select(). That also means we can pass -1 as the count to pcap_dispatch(), as pcap_breakloop() will terminate the loop in pcap_dispatch(). Use sigaction() to catch SIGUSR1, so we can make sure that the signal handler doesn't get reset when the signal is delivered, and that system calls don't restart when we return from the signal handler. svn path=/trunk/; revision=18201 --- Makefile.nmake | 1 + acinclude.m4 | 2 +- capture-wpcap.c | 23 +++++ capture_loop.c | 258 +++++++++++++++++++++++++++++--------------------------- capture_loop.h | 49 ++++++++--- config.h.win32 | 1 + config.nmake | 7 ++ 7 files changed, 204 insertions(+), 137 deletions(-) diff --git a/Makefile.nmake b/Makefile.nmake index 5ab4818480..ef6bf13272 100644 --- a/Makefile.nmake +++ b/Makefile.nmake @@ -229,6 +229,7 @@ config.h : config.h.win32 config.nmake -e "s/@HAVE_PCAP_FINDALLDEVS@/$(PCAP_FINDALLDEVS_CONFIG)/" \ -e "s/@HAVE_PCAP_DATALINK_NAME_TO_VAL@/$(PCAP_DATALINK_NAME_TO_VAL_CONFIG)/" \ -e "s/@HAVE_PCAP_DATALINK_VAL_TO_NAME@/$(PCAP_DATALINK_VAL_TO_NAME_CONFIG)/" \ + -e "s/@HAVE_PCAP_BREAKLOOP@/$(PCAP_BREAKLOOP_CONFIG)/" \ -e "s/@HAVE_LIBETHEREALDLL@/$(LIBETHEREAL_CONFIG)/" \ -e "s/@WPCAP_CONSTIFIED@/$(WPCAP_CONSTIFIED_CONFIG)/" \ -e "s/@HAVE_GNUTLS@/$(GNUTLS_CONFIG)/" \ diff --git a/acinclude.m4 b/acinclude.m4 index e831c7372b..df4e712543 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -437,7 +437,7 @@ and did you also install that package?]])) else AC_MSG_RESULT(no) fi - AC_CHECK_FUNCS(pcap_open_dead pcap_freecode) + AC_CHECK_FUNCS(pcap_open_dead pcap_freecode pcap_breakloop) # # Later versions of Mac OS X 10.3[.x] ship a pcap.h that # doesn't define pcap_if_t but ship an 0.8[.x] libpcap, diff --git a/capture-wpcap.c b/capture-wpcap.c index ebff52e05e..bb06067352 100644 --- a/capture-wpcap.c +++ b/capture-wpcap.c @@ -49,6 +49,11 @@ gboolean has_wpcap = FALSE; #ifdef HAVE_LIBPCAP +/* + * XXX - should we require at least WinPcap 3.1 both for building an + * for using Wireshark? + */ + static char* (*p_pcap_lookupdev) (char *); static void (*p_pcap_close) (pcap_t *); static int (*p_pcap_stats) (pcap_t *, struct pcap_stat *); @@ -80,6 +85,9 @@ static int (*p_pcap_datalink_name_to_val) (const char *); #ifdef HAVE_PCAP_DATALINK_VAL_TO_NAME static const char *(*p_pcap_datalink_val_to_name) (int); #endif +#ifdef HAVE_PCAP_BREAKLOOP +static void (*p_pcap_breakloop) (pcap_t *); +#endif static const char *(*p_pcap_lib_version) (void); static int (*p_pcap_setbuff) (pcap_t *, int dim); static int (*p_pcap_next_ex) (pcap_t *, struct pcap_pkthdr **pkt_header, const u_char **pkt_data); @@ -120,6 +128,14 @@ load_wpcap(void) #endif #ifdef HAVE_PCAP_DATALINK_VAL_TO_NAME SYM(pcap_datalink_val_to_name, TRUE), +#endif +#ifdef HAVE_PCAP_BREAKLOOP + /* + * We don't try to work around the lack of this at + * run time; it's present in WinPcap 3.1, which is + * the version we build with and ship with. + */ + SYM(pcap_breakloop, FALSE), #endif SYM(pcap_lib_version, TRUE), SYM(pcap_setbuff, TRUE), @@ -422,6 +438,13 @@ pcap_datalink_val_to_name(int dlt) } #endif +#ifdef HAVE_PCAP_BREAKLOOP +void pcap_breakloop(pcap_t *a) +{ + p_pcap_breakloop(a); +} +#endif + /* setbuff is win32 specific! */ int pcap_setbuff(pcap_t *a, int b) { diff --git a/capture_loop.c b/capture_loop.c index 16d32bb350..02508fc46d 100644 --- a/capture_loop.c +++ b/capture_loop.c @@ -811,154 +811,154 @@ capture_loop_dispatch(capture_options *capture_opts _U_, loop_data *ld, #endif #ifndef _WIN32 - if (ld->from_cap_pipe) { - /* dispatch from capture pipe */ + if (ld->from_cap_pipe) { + /* dispatch from capture pipe */ #ifdef LOG_CAPTURE_VERBOSE - g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: from capture pipe"); + g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: from capture pipe"); #endif + FD_ZERO(&set1); + FD_SET(ld->cap_pipe_fd, &set1); + timeout.tv_sec = 0; + timeout.tv_usec = CAP_READ_TIMEOUT*1000; + sel_ret = select(ld->cap_pipe_fd+1, &set1, NULL, NULL, &timeout); + if (sel_ret <= 0) { + inpkts = 0; + if (sel_ret < 0 && errno != EINTR) { + g_snprintf(errmsg, errmsg_len, + "Unexpected error from select: %s", strerror(errno)); + report_capture_error(errmsg, please_report); + ld->go = FALSE; + } + } else { + /* + * "select()" says we can read from the pipe without blocking + */ + inpkts = cap_pipe_dispatch(ld, pcap_data, errmsg, errmsg_len); + if (inpkts < 0) { + ld->go = FALSE; + } + } + } + else +#endif /* _WIN32 */ + { + /* dispatch from pcap */ +#ifdef MUST_DO_SELECT + /* + * If we have "pcap_get_selectable_fd()", we use it to get the + * descriptor on which to select; if that's -1, it means there + * is no descriptor on which you can do a "select()" (perhaps + * because you're capturing on a special device, and that device's + * driver unfortunately doesn't support "select()", in which case + * we don't do the select - which means it might not be possible + * to stop a capture until a packet arrives. If that's unacceptable, + * plead with whoever supplies the software for that device to add + * "select()" support, or upgrade to libpcap 0.8.1 or later, and + * rebuild Ethereal or get a version built with libpcap 0.8.1 or + * later, so it can use pcap_breakloop(). + */ +#ifdef LOG_CAPTURE_VERBOSE + g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: from pcap_dispatch with select"); +#endif + if (ld->pcap_fd != -1) { FD_ZERO(&set1); - FD_SET(ld->cap_pipe_fd, &set1); - timeout.tv_sec = 0; - timeout.tv_usec = CAP_READ_TIMEOUT*1000; - sel_ret = select(ld->cap_pipe_fd+1, &set1, NULL, NULL, &timeout); - if (sel_ret <= 0) { - inpkts = 0; + FD_SET(ld->pcap_fd, &set1); + sel_ret = select(ld->pcap_fd+1, &set1, NULL, NULL, NULL); + if (sel_ret > 0) { + /* + * "select()" says we can read from it without blocking; go for + * it. + * + * We don't have pcap_breakloop(), so we only process one packet + * per pcap_dispatch() call, to allow a signal to stop the + * processing immediately, rather than processing all packets + * in a batch before quitting. + */ + inpkts = pcap_dispatch(ld->pcap_h, 1, ld->packet_cb, (u_char *)ld); + if (inpkts < 0) { + ld->pcap_err = TRUE; + ld->go = FALSE; /* error or pcap_breakloop() - stop capturing */ + } + } else { + inpkts = 0; if (sel_ret < 0 && errno != EINTR) { g_snprintf(errmsg, errmsg_len, "Unexpected error from select: %s", strerror(errno)); report_capture_error(errmsg, please_report); ld->go = FALSE; } - } else { - /* - * "select()" says we can read from the pipe without blocking - */ - inpkts = cap_pipe_dispatch(ld, pcap_data, errmsg, errmsg_len); - if (inpkts < 0) { - ld->go = FALSE; - } } } else -#endif /* _WIN32 */ - { - /* dispatch from pcap */ -#ifdef MUST_DO_SELECT - /* - * Sigh. The semantics of the read timeout argument to - * "pcap_open_live()" aren't particularly well specified by - * the "pcap" man page - at least with the BSD BPF code, the - * intent appears to be, at least in part, a way of cutting - * down the number of reads done on a capture, by blocking - * until the buffer fills or a timer expires - and the Linux - * libpcap doesn't actually support it, so we can't use it - * to break out of the "pcap_dispatch()" every 1/4 of a second - * or so. Linux's libpcap is not the only libpcap that doesn't - * support the read timeout. - * - * Furthermore, at least on Solaris, the bufmod STREAMS module's - * read timeout won't go off if no data has arrived, i.e. it cannot - * be used to guarantee that a read from a DLPI stream will return - * within a specified amount of time regardless of whether any - * data arrives or not. - * - * Thus, on all platforms other than BSD, we do a "select()" on the - * file descriptor for the capture, with a timeout of CAP_READ_TIMEOUT - * milliseconds, or CAP_READ_TIMEOUT*1000 microseconds. - * - * "select()", on BPF devices, doesn't work as you might expect; - * at least on some versions of some flavors of BSD, the timer - * doesn't start until a read is done, so it won't expire if - * only a "select()" or "poll()" is posted. - * - * If we have "pcap_get_selectable_fd()", we use it to get the - * descriptor on which to select; if that's -1, it means there - * is no descriptor on which you can do a "select()" (perhaps - * because you're capturing on a special device, and that device's - * driver unfortunately doesn't support "select()", in which case - * we don't do the select - which means Ethereal might block, - * unable to accept user input, until a packet arrives. If - * that's unacceptable, plead with whoever supplies the software - * for that device to add "select()" support. - */ -#ifdef LOG_CAPTURE_VERBOSE - g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: from pcap_dispatch with select"); -#endif - if (ld->pcap_fd != -1) { - FD_ZERO(&set1); - FD_SET(ld->pcap_fd, &set1); - timeout.tv_sec = 0; - timeout.tv_usec = CAP_READ_TIMEOUT*1000; - sel_ret = select(ld->pcap_fd+1, &set1, NULL, NULL, &timeout); - if (sel_ret > 0) { - /* - * "select()" says we can read from it without blocking; go for - * it. - */ - inpkts = pcap_dispatch(ld->pcap_h, 1, ld->packet_cb, (u_char *)ld); - if (inpkts < 0) { - ld->pcap_err = TRUE; - ld->go = FALSE; - } - } else { - inpkts = 0; - if (sel_ret < 0 && errno != EINTR) { - g_snprintf(errmsg, errmsg_len, - "Unexpected error from select: %s", strerror(errno)); - report_capture_error(errmsg, please_report); - ld->go = FALSE; - } - } - } - else #endif /* MUST_DO_SELECT */ - { - /* dispatch from pcap without select */ + { + /* dispatch from pcap without select */ #if 1 #ifdef LOG_CAPTURE_VERBOSE - g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: from pcap_dispatch"); + g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: from pcap_dispatch"); #endif - inpkts = pcap_dispatch(ld->pcap_h, 1, ld->packet_cb, (u_char *) ld); - if (inpkts < 0) { +#ifdef _WIN32 + /* + * On Windows, we don't support asynchronously telling a process to + * stop capturing; instead, we check for an indication on a pipe + * after processing packets. We therefore process only one packet + * at a time, so that we can check the pipe after every packet. + */ + inpkts = pcap_dispatch(ld->pcap_h, 1, ld->packet_cb, (u_char *) ld); +#else + inpkts = pcap_dispatch(ld->pcap_h, -1, ld->packet_cb, (u_char *) ld); +#endif + if (inpkts < 0) { + if (inpkts == -1) { + /* Error, rather than pcap_breakloop(). */ ld->pcap_err = TRUE; - ld->go = FALSE; } -#else - { + ld->go = FALSE; /* error or pcap_breakloop() - stop capturing */ + } +#else /* pcap_next_ex */ #ifdef LOG_CAPTURE_VERBOSE - g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: from pcap_next_ex"); + g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: from pcap_next_ex"); #endif - /* XXX - this is currently unused, as there is some confusion with pcap_next_ex() vs. pcap_dispatch() */ - - /* WinPcap's remote capturing feature doesn't work, see http://wiki.ethereal.com/CaptureSetup_2fWinPcapRemote */ - /* for reference, an example remote interface: rpcap://[1.2.3.4]/\Device\NPF_{39993D68-7C9B-4439-A329-F2D888DA7C5C} */ + /* XXX - this is currently unused, as there is some confusion with pcap_next_ex() vs. pcap_dispatch() */ - /* emulate dispatch from pcap */ - int in; - struct pcap_pkthdr *pkt_header; - u_char *pkt_data; + /* + * WinPcap's remote capturing feature doesn't work with pcap_dispatch(), + * see http://wiki.ethereal.com/CaptureSetup_2fWinPcapRemote + * This should be fixed in the WinPcap 4.0 alpha release. + * + * For reference, an example remote interface: + * rpcap://[1.2.3.4]/\Device\NPF_{39993D68-7C9B-4439-A329-F2D888DA7C5C} + */ - inpkts = 0; - while( (in = pcap_next_ex(ld->pcap_h, &pkt_header, &pkt_data)) == 1) { - ld->packet_cb( (u_char *) ld, pkt_header, pkt_data); - inpkts++; - } + /* emulate dispatch from pcap */ + { + int in; + struct pcap_pkthdr *pkt_header; + u_char *pkt_data; + + inpkts = 0; + in = 0; + while(ld->go && + (in = pcap_next_ex(ld->pcap_h, &pkt_header, &pkt_data)) == 1) { + ld->packet_cb( (u_char *) ld, pkt_header, pkt_data); + inpkts++; + } - if(in < 0) { - ld->pcap_err = TRUE; - ld->go = FALSE; - inpkts = in; - } + if(in < 0) { + ld->pcap_err = TRUE; + ld->go = FALSE; + inpkts = in; } -#endif } +#endif /* pcap_next_ex */ } + } #ifdef LOG_CAPTURE_VERBOSE - g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: %d new packet%s", inpkts, plurality(inpkts, "", "s")); + g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, "capture_loop_dispatch: %d new packet%s", inpkts, plurality(inpkts, "", "s")); #endif - return inpkts; + return inpkts; } @@ -1069,6 +1069,9 @@ capture_loop_stop_signal_handler(int signo _U_) int capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct pcap_stat *stats) { +#ifndef _WIN32 + struct sigaction act; +#endif time_t upd_time, cur_time; time_t start_time; int err_close; @@ -1116,8 +1119,16 @@ capture_loop_start(capture_options *capture_opts, gboolean *stats_known, struct * Catch SIGUSR1, so that we exit cleanly if the parent process * kills us with it due to the user selecting "Capture->Stop". */ - signal(SIGUSR1, capture_loop_stop_signal_handler); -#endif + act.sa_handler = capture_loop_stop_signal_handler; + /* + * Arrange that system calls not get restarted, because when + * our signal handler returns we don't want to restart + * a call that was waiting for packets to arrive. + */ + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaction(SIGUSR1, &act, NULL); +#endif /* _WIN32 */ g_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_INFO, "Capture loop starting ..."); capture_opts_log(LOG_DOMAIN_CAPTURE_CHILD, G_LOG_LEVEL_DEBUG, capture_opts); @@ -1452,7 +1463,11 @@ error: void capture_loop_stop(void) { - ld.go = FALSE; +#ifdef HAVE_PCAP_BREAKLOOP + pcap_breakloop(ld.pcap_h); +#else + ld.go = FALSE; +#endif } @@ -1542,4 +1557,3 @@ capture_loop_packet_cb(u_char *user, const struct pcap_pkthdr *phdr, } #endif /* HAVE_LIBPCAP */ - diff --git a/capture_loop.h b/capture_loop.h index c0a91e2e24..839ba528b9 100644 --- a/capture_loop.h +++ b/capture_loop.h @@ -51,28 +51,49 @@ extern void capture_loop_stop(void); /*** the following is internal only (should be moved to capture_loop_int.h) ***/ +#ifndef HAVE_PCAP_BREAKLOOP /* - * We don't want to do a "select()" on the pcap_t's file descriptor on - * BSD (because "select()" doesn't work correctly on BPF devices on at - * least some releases of some flavors of BSD), and we don't want to do - * it on Windows (because "select()" is something for sockets, not for - * arbitrary handles). (Note that "Windows" here includes Cygwin; - * even in its pretend-it's-UNIX environment, we're using WinPcap, not - * a UNIX libpcap.) + * We don't have pcap_breakloop(), which is the only way to ensure that + * pcap_dispatch(), pcap_loop(), or even pcap_next() or pcap_next_ex() + * won't, if the call to read the next packet or batch of packets is + * is interrupted by a signal on UN*X, just go back and try again to + * read again. * - * We *do* want to do it on other platforms, as, on other platforms (with - * the possible exception of Ultrix and Digital UNIX), the read timeout - * doesn't expire if no packets have arrived, so a "pcap_dispatch()" call - * will block until packets arrive, causing the UI to hang. + * On UN*X, we catch SIGUSR1 as a "stop capturing" signal, and, in + * the signal handler, set a flag to stop capturing; however, without + * a guarantee of that sort, we can't guarantee that we'll stop capturing + * if the read will be retried and won't time out if no packets arrive. + * + * Therefore, on at least some platforms, we work around the lack of + * pcap_breakloop() by doing a select() on the pcap_t's file descriptor + * to wait for packets to arrive, so that we're probably going to be + * blocked in the select() when the signal arrives, and can just bail + * out of the loop at that point. + * + * However, we don't want to that on BSD (because "select()" doesn't work + * correctly on BPF devices on at least some releases of some flavors of + * BSD), and we don't want to do it on Windows (because "select()" is + * something for sockets, not for arbitrary handles). (Note that "Windows" + * here includes Cygwin; even in its pretend-it's-UNIX environment, we're + * using WinPcap, not a UNIX libpcap.) + * + * Fortunately, we don't need to do it on BSD, because the libpcap timeout + * on BSD times out even if no packets have arrived, so we'll eventually + * exit pcap_dispatch() with an indication that no packets have arrived, + * and will break out of the capture loop at that point. + * + * On Windows, we can't send a SIGUSR1 to stop capturing, so none of this + * applies in any case. * * XXX - the various BSDs appear to define BSD in ; we don't * want to include it if it's not present on this platform, however. */ -#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && \ +# if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && \ !defined(__bsdi__) && !defined(__APPLE__) && !defined(_WIN32) && \ !defined(__CYGWIN__) -# define MUST_DO_SELECT -#endif +# define MUST_DO_SELECT +# endif /* avoid select */ +#endif /* HAVE_PCAP_BREAKLOOP */ typedef void (*capture_packet_cb_fct)(u_char *, const struct pcap_pkthdr *, const u_char *); diff --git a/config.h.win32 b/config.h.win32 index a78f6254bc..46f0ccdbaf 100644 --- a/config.h.win32 +++ b/config.h.win32 @@ -56,6 +56,7 @@ #define NEED_MKSTEMP 1 @HAVE_LIBPCAP@ +@HAVE_PCAP_BREAKLOOP@ @HAVE_PCAP_FINDALLDEVS@ @HAVE_PCAP_DATALINK_NAME_TO_VAL@ @HAVE_PCAP_DATALINK_VAL_TO_NAME@ diff --git a/config.nmake b/config.nmake index b02347ec71..c78fd8b933 100644 --- a/config.nmake +++ b/config.nmake @@ -354,10 +354,16 @@ WINPCAP_CONFIG=^#define HAVE_LIBPCAP 1 PCAP_FINDALLDEVS_CONFIG=^#define HAVE_PCAP_FINDALLDEVS 1 PCAP_DATALINK_NAME_TO_VAL_CONFIG=^#define HAVE_PCAP_DATALINK_NAME_TO_VAL 1 PCAP_DATALINK_VAL_TO_NAME_CONFIG=^#define HAVE_PCAP_DATALINK_VAL_TO_NAME 1 +!IF "$(WINPCAP_VERSION)" == "3.1" +PCAP_BREAKLOOP_CONFIG=^#define HAVE_PCAP_BREAKLOOP 1 +!ELSE +PCAP_BREAKLOOP_CONFIG= +!ENDIF WPCAP_CONSTIFIED_CONFIG=^#define WPCAP_CONSTIFIED 1 !ELSE PCAP_FINDALLDEVS_CONFIG= PCAP_DATALINK_VAL_TO_NAME_CONFIG= +PCAP_BREAKLOOP_CONFIG= WPCAP_CONSTIFIED= !ENDIF !ELSE @@ -365,6 +371,7 @@ WINPCAP_CONFIG= PCAP_FINDALLDEVS_CONFIG= PCAP_DATALINK_NAME_TO_VAL_CONFIG= PCAP_DATALINK_VAL_TO_NAME_CONFIG= +PCAP_BREAKLOOP_CONFIG= WPCAP_CONSTIFIED= !ENDIF -- cgit v1.2.3