Ethereal-dev: [Ethereal-dev] [Patch] Capture from pipe without blocking
Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.
From: Graeme Hewson <ghewson@xxxxxxxxxxxxxxxxxxx>
Date: Thu, 13 Jun 2002 18:41:20 +0100
The attached patch stops ethereal from blocking when reading from a pipe. It still blocks during the pipe open, though. Also in the patch are sundry tweaks and cleanups. Graeme Hewson
--- capture.c.orig Fri Jun 7 22:11:22 2002 +++ capture.c Thu Jun 13 18:08:17 2002 @@ -219,16 +219,26 @@ gint sync_packets; gboolean pcap_err; /* TRUE if error from pcap */ gboolean from_pipe; /* TRUE if we are capturing data from a pipe */ - gboolean modified; /* TRUE if data in the pipe uses modified pcap headers */ - gboolean byte_swapped; /* TRUE if data in the pipe is byte swapped */ packet_counts counts; wtap_dumper *pdh; +#ifndef _WIN32 + gboolean modified; /* TRUE if data in the pipe uses modified pcap headers */ + gboolean byte_swapped; /* TRUE if data in the pipe is byte swapped */ + unsigned int bytes_to_read, bytes_read; /* Used by pipe_dispatch */ + enum { + STATE_EXPECT_REC_HDR, STATE_READ_REC_HDR, + STATE_EXPECT_DATA, STATE_READ_DATA + } pipe_state; + + enum { PIPOK, PIPEOF, PIPERR, PIPNEXIST } pipe_err; +#endif } loop_data; #ifndef _WIN32 static void adjust_header(loop_data *, struct pcap_hdr *, struct pcaprec_hdr *); -static int pipe_open_live(char *, struct pcap_hdr *, loop_data *); -static int pipe_dispatch(int, loop_data *, struct pcap_hdr *); +static int pipe_open_live(char *, struct pcap_hdr *, loop_data *, char *, int); +static int pipe_dispatch(int, loop_data *, struct pcap_hdr *, \ + struct pcaprec_modified_hdr *, u_char *, char *, int); #endif /* Win32 needs the O_BINARY flag for open() */ @@ -603,7 +613,7 @@ "Capture child process failed: EOF reading its error message."); wait_for_child(FALSE); } else - simple_dialog(ESD_TYPE_WARN, NULL, msg); + simple_dialog(ESD_TYPE_CRIT, NULL, msg); g_free(msg); } @@ -1055,23 +1065,86 @@ * N.B. : we can't read the libpcap formats used in RedHat 6.1 or SuSE 6.3 * because we can't seek on pipes (see wiretap/libpcap.c for details) */ static int -pipe_open_live(char *pipename, struct pcap_hdr *hdr, loop_data *ld) +pipe_open_live(char *pipename, struct pcap_hdr *hdr, loop_data *ld, + char *errmsg, int errmsgl) { struct stat pipe_stat; int fd; guint32 magic; - int bytes_read, b; + int b, sel_ret; + unsigned int bytes_read; + fd_set rfds; + struct timeval timeout; - if (strcmp(pipename, "-") == 0) fd = 0; /* read from stdin */ - else if (stat(pipename, &pipe_stat) == 0 && S_ISFIFO(pipe_stat.st_mode)) { - if ((fd = open(pipename, O_RDONLY)) == -1) return -1; - } else return -1; +/* + * XXX Ethereal blocks until we return + */ + + if (strcmp(pipename, "-") == 0) + fd = 0; /* read from stdin */ + else { + if (stat(pipename, &pipe_stat) < 0) { + if (errno == ENOENT || errno == ENOTDIR) + ld->pipe_err = PIPNEXIST; + else { + snprintf(errmsg, errmsgl, + "The capture session could not be initiated " + "due to error on pipe: %s", strerror(errno)); + ld->pipe_err = PIPERR; + } + return -1; + } + if (! S_ISFIFO(pipe_stat.st_mode)) { + if (S_ISCHR(pipe_stat.st_mode)) { + /* + * Assume the user specified an interface on a system where + * interfaces are in /dev. Pretend we haven't seen it. + */ + ld->pipe_err = PIPNEXIST; + } else { + snprintf(errmsg, errmsgl, + "The capture session could not be initiated because\n" + "\"%s\" is neither an interface nor a pipe", pipename); + ld->pipe_err = PIPERR; + } + return -1; + } + fd = open(pipename, O_RDONLY | O_NONBLOCK); + if (fd == -1) { + snprintf(errmsg, errmsgl, + "The capture session could not be initiated " + "due to error on pipe open: %s", strerror(errno)); + ld->pipe_err = PIPERR; + return -1; + } + } ld->from_pipe = TRUE; + /* read the pcap header */ - if (read(fd, &magic, sizeof magic) != sizeof magic) { - close(fd); - return -1; + FD_ZERO(&rfds); + bytes_read = 0; + while (bytes_read < sizeof magic) { + FD_SET(fd, &rfds); + timeout.tv_sec = 0; + timeout.tv_usec = CAP_READ_TIMEOUT*1000; + sel_ret = select(fd+1, &rfds, NULL, NULL, &timeout); + if (sel_ret < 0) { + snprintf(errmsg, errmsgl, + "Unexpected error from select: %s", strerror(errno)); + goto error; + } else if (sel_ret > 0) { + b = read(fd, &magic+bytes_read, sizeof magic-bytes_read); + if (b <= 0) { + if (b == 0) + snprintf(errmsg, errmsgl, "End of file on pipe during open"); + else + snprintf(errmsg, errmsgl, "Error on pipe during open: %s", + strerror(errno)); + goto error; + } + bytes_read += b; + } } switch (magic) { @@ -1103,25 +1176,36 @@ break; default: /* Not a "libpcap" type we know about. */ - close(fd); - return -1; + snprintf(errmsg, errmsgl, "Unrecognized libpcap format"); + goto error; } /* Read the rest of the header */ - bytes_read = read(fd, hdr, sizeof(struct pcap_hdr)); - if (bytes_read <= 0) { - close(fd); - return -1; - } - while ((unsigned) bytes_read < sizeof(struct pcap_hdr)) - { - b = read(fd, ((char *)&hdr)+bytes_read, sizeof(struct pcap_hdr) - bytes_read); - if (b <= 0) { - close(fd); - return -1; + bytes_read = 0; + while (bytes_read < sizeof(struct pcap_hdr)) { + FD_SET(fd, &rfds); + timeout.tv_sec = 0; + timeout.tv_usec = CAP_READ_TIMEOUT*1000; + sel_ret = select(fd+1, &rfds, NULL, NULL, &timeout); + if (sel_ret < 0) { + snprintf(errmsg, errmsgl, + "Unexpected error from select: %s", strerror(errno)); + goto error; + } else if (sel_ret > 0) { + b = read(fd, ((char *)hdr)+bytes_read, + sizeof(struct pcap_hdr) - bytes_read); + if (b <= 0) { + if (b == 0) + snprintf(errmsg, errmsgl, "End of file on pipe during open"); + else + snprintf(errmsg, errmsgl, "Error on pipe during open: %s", + strerror(errno)); + goto error; + } + bytes_read += b; } - bytes_read += b; } + if (ld->byte_swapped) { /* Byte-swap the header fields about which we care. */ hdr->version_major = BSWAP16(hdr->version_major); @@ -1129,76 +1213,129 @@ hdr->snaplen = BSWAP32(hdr->snaplen); hdr->network = BSWAP32(hdr->network); } + if (hdr->version_major < 2) { - close(fd); - return -1; + snprintf(errmsg, errmsgl, "Unable to read old libpcap format"); + goto error; } + ld->pipe_state = STATE_EXPECT_REC_HDR; + ld->pipe_err = PIPOK; return fd; + +error: + ld->pipe_err = PIPERR; + close(fd); + return -1; + } /* We read one record from the pipe, take care of byte order in the record * header, write the record in the capture file, and update capture statistics. */ + static int -pipe_dispatch(int fd, loop_data *ld, struct pcap_hdr *hdr) +pipe_dispatch(int fd, loop_data *ld, struct pcap_hdr *hdr, + struct pcaprec_modified_hdr *rechdr, u_char *data, + char *errmsg, int errmsgl) { - struct pcaprec_modified_hdr rechdr; struct pcap_pkthdr phdr; - int bytes_to_read, bytes_read, b; - u_char pd[WTAP_MAX_PACKET_SIZE]; - - /* read the record header */ - bytes_to_read = ld->modified ? sizeof rechdr : sizeof rechdr.hdr; - bytes_read = read(fd, &rechdr, bytes_to_read); - if (bytes_read <= 0) { - close(fd); - ld->go = FALSE; - return 0; - } - while (bytes_read < bytes_to_read) - { - b = read(fd, ((char *)&rechdr)+bytes_read, bytes_to_read - bytes_read); + int b; + enum { PD_REC_HDR_READ, PD_DATA_READ, PD_PIPE_EOF, PD_PIPE_ERR, + PD_ERR } result; + + switch (ld->pipe_state) { + + case STATE_EXPECT_REC_HDR: + ld->bytes_to_read = ld->modified ? + sizeof(struct pcaprec_modified_hdr) : sizeof(struct pcaprec_hdr); + ld->bytes_read = 0; + ld->pipe_state = STATE_READ_REC_HDR; + /* Fall through */ + + case STATE_READ_REC_HDR: + b = read(fd, ((char *)rechdr)+ld->bytes_read, + ld->bytes_to_read - ld->bytes_read); if (b <= 0) { - close(fd); - ld->go = FALSE; - return 0; + if (b == 0) + result = PD_PIPE_EOF; + else + result = PD_PIPE_ERR; + break; } - bytes_read += b; - } - /* take care of byte order */ - adjust_header(ld, hdr, &rechdr.hdr); - if (rechdr.hdr.incl_len > WTAP_MAX_PACKET_SIZE) { - close(fd); - ld->go = FALSE; - return 0; - } - /* read the packet data */ - bytes_read = read(fd, pd, rechdr.hdr.incl_len); - if (bytes_read <= 0) { - close(fd); - ld->go = FALSE; - return 0; - } - while ((unsigned) bytes_read < rechdr.hdr.incl_len) - { - b = read(fd, pd+bytes_read, rechdr.hdr.incl_len - bytes_read); + if ((ld->bytes_read += b) < ld->bytes_to_read) + return 0; + result = PD_REC_HDR_READ; + break; + + case STATE_EXPECT_DATA: + ld->bytes_read = 0; + ld->pipe_state = STATE_READ_DATA; + /* Fall through */ + + case STATE_READ_DATA: + b = read(fd, data+ld->bytes_read, rechdr->hdr.incl_len - ld->bytes_read); if (b <= 0) { - close(fd); - ld->go = FALSE; + if (b == 0) + result = PD_PIPE_EOF; + else + result = PD_PIPE_ERR; + break; + } + if ((ld->bytes_read += b) < rechdr->hdr.incl_len) return 0; + result = PD_DATA_READ; + break; + + default: + snprintf(errmsg, errmsgl, "pipe_dispatch: invalid state"); + result = PD_ERR; + + } /* switch (ld->pipe_state) */ + + /* + * We've now read as much data as we were expecting, so process it. + */ + + switch (result) { + + case PD_REC_HDR_READ: + /* We've read the header. Take care of byte order. */ + adjust_header(ld, hdr, &rechdr->hdr); + if (rechdr->hdr.incl_len > WTAP_MAX_PACKET_SIZE) { + snprintf(errmsg, errmsgl, "Frame %u too long (%d bytes)", + ld->counts.total+1, rechdr->hdr.incl_len); + break; } - bytes_read += b; - } + ld->pipe_state = STATE_EXPECT_DATA; + return 0; + + case PD_DATA_READ: + /* Fill in a "struct pcap_pkthdr", and process the packet. */ + phdr.ts.tv_sec = rechdr->hdr.ts_sec; + phdr.ts.tv_usec = rechdr->hdr.ts_usec; + phdr.caplen = rechdr->hdr.incl_len; + phdr.len = rechdr->hdr.orig_len; + + capture_pcap_cb((u_char *)ld, &phdr, data); + + ld->pipe_state = STATE_EXPECT_REC_HDR; + return 1; - /* Fill in a "struct pcap_pkthdr", and process the packet. */ - phdr.ts.tv_sec = rechdr.hdr.ts_sec; - phdr.ts.tv_usec = rechdr.hdr.ts_usec; - phdr.caplen = rechdr.hdr.incl_len; - phdr.len = rechdr.hdr.orig_len; + case PD_PIPE_EOF: + ld->pipe_err = PIPEOF; + return -1; + + case PD_PIPE_ERR: + snprintf(errmsg, errmsgl, "Error reading from pipe: %s", + strerror(errno)); + case PD_ERR: + /* Fall out */ - capture_pcap_cb((u_char *)ld, &phdr, pd); + } - return 1; + ld->pipe_err = PIPERR; + /* Return here rather than inside the switch to prevent GCC warning */ + return -1; } #endif @@ -1230,24 +1367,8 @@ static const char capstart_msg = SP_CAPSTART; char errmsg[4096+1]; gboolean dump_ok; -#ifndef _WIN32 - static const char ppamsg[] = "can't find PPA for "; - char *libpcap_warn; - int sel_ret; -#endif fd_set set1; struct timeval timeout; -#ifdef MUST_DO_SELECT - int pcap_fd = 0; -#endif -#ifdef _WIN32 - WORD wVersionRequested; - WSADATA wsaData; -#endif -#ifndef _WIN32 - int pipe_fd = -1; - struct pcap_hdr hdr; -#endif struct { const gchar *title; gint *value_ptr; @@ -1268,6 +1389,26 @@ #define N_COUNTS (sizeof counts / sizeof counts[0]) +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; +#else + static const char ppamsg[] = "can't find PPA for "; + char *libpcap_warn; + int sel_ret; + int pipe_fd = -1; + struct pcap_hdr hdr; + struct pcaprec_modified_hdr rechdr; + u_char pcap_data[WTAP_MAX_PACKET_SIZE]; +#endif +#ifdef MUST_DO_SELECT + int pcap_fd = 0; +#endif + +/* Size of buffer to hold decimal representation of + signed/unsigned 64-bit int */ +#define DECISIZE 20 + /* Initialize Windows Socket if we are in a WIN32 OS This needs to be done before querying the interface for network/netmask */ #ifdef _WIN32 @@ -1343,24 +1484,23 @@ goto error; #else /* try to open cfile.iface as a pipe */ - pipe_fd = pipe_open_live(cfile.iface, &hdr, &ld); + pipe_fd = pipe_open_live(cfile.iface, &hdr, &ld, errmsg, sizeof errmsg); if (pipe_fd == -1) { - /* Well, we couldn't start the capture assuming the interface was - a device, and we couldn't open the pipe, either. - We choose to report the error for the device, rather than the - file, under the assumption that you're typically capturing on - a device. - - If this is a child process that does the capturing in sync - mode or fork mode, it shouldn't do any UI stuff until we pop up the - capture-progress window, and, since we couldn't start the - capture, we haven't popped it up. */ + /* If this is a child process that does the capturing in sync + * mode or fork mode, it shouldn't do any UI stuff until we pop up the + * capture-progress window, and, since we couldn't start the + * capture, we haven't popped it up. + */ if (!capture_child) { while (gtk_events_pending()) gtk_main_iteration(); } + if (ld.pipe_err == PIPNEXIST) { + + /* Pipe doesn't exist, so output message for interface */ + /* If we got a "can't find PPA for XXX" message, warn the user (who is running Ethereal on HP-UX) that they don't have a version of libpcap that properly handles HP-UX (libpcap 0.6.x and later @@ -1385,6 +1525,11 @@ "Please check to make sure you have sufficient permissions, and that\n" "you have the proper interface or pipe specified.%s", open_err_str, libpcap_warn); + } + /* + * Else pipe (or file) does exist and pipe_open_live() has + * filled in errmsg + */ goto error; } else /* pipe_open_live() succeeded; don't want @@ -1567,7 +1712,8 @@ * Catch SIGUSR1, so that we exit cleanly if the parent process * kills us with it due to the user selecting "Capture->Stop". */ - signal(SIGUSR1, stop_capture); + if (capture_child) + signal(SIGUSR1, stop_capture); #endif /* initialize capture stop conditions */ init_capture_stop_conditions(); @@ -1589,15 +1735,7 @@ timeout.tv_sec = 0; timeout.tv_usec = CAP_READ_TIMEOUT*1000; sel_ret = select(pipe_fd+1, &set1, NULL, NULL, &timeout); - if (sel_ret > 0) { - /* - * "select()" says we can read from the pipe without blocking; go for - * it. We are not sure we can read a whole record, but at least the - * begninning of one. pipe_dispatch() will block reading the whole - * record. - */ - inpkts = pipe_dispatch(pipe_fd, &ld, &hdr); - } else { + if (sel_ret <= 0) { inpkts = 0; if (sel_ret < 0 && errno != EINTR) { snprintf(errmsg, sizeof(errmsg), @@ -1605,6 +1743,15 @@ popup_errmsg(errmsg); ld.go = FALSE; } + } else { + /* + * "select()" says we can read from the pipe without blocking + */ + inpkts = pipe_dispatch(pipe_fd, &ld, &hdr, &rechdr, pcap_data, + errmsg, sizeof errmsg); + if (inpkts < 0) { + ld.go = FALSE; + } } } else @@ -1670,60 +1817,71 @@ } #endif } - if (inpkts > 0) + + if (inpkts > 0) { ld.sync_packets += inpkts; - /* check capture stop conditons */ - if (cnd_stop_timeout != NULL && cnd_eval(cnd_stop_timeout)) { - /* The specified capture time has elapsed; stop the capture. */ - ld.go = FALSE; - } else if (cnd_stop_capturesize != NULL && cnd_eval(cnd_stop_capturesize, - (guint32)wtap_get_bytes_dumped(ld.pdh))){ - /* Capture file reached its maximum size. */ - if (capture_opts.ringbuffer_on) { - /* Switch to the next ringbuffer file */ - if (ringbuf_switch_file(&cfile, &ld.pdh, &ld.err)) { - /* File switch succeeded: reset the condition */ - cnd_reset(cnd_stop_capturesize); + /* check capture stop conditons */ + if (cnd_stop_capturesize != NULL && cnd_eval(cnd_stop_capturesize, + (guint32)wtap_get_bytes_dumped(ld.pdh))){ + /* Capture file reached its maximum size. */ + if (capture_opts.ringbuffer_on) { + /* Switch to the next ringbuffer file */ + if (ringbuf_switch_file(&cfile, &ld.pdh, &ld.err)) { + /* File switch succeeded: reset the condition */ + cnd_reset(cnd_stop_capturesize); + } else { + /* File switch failed: stop here */ + ld.go = FALSE; + continue; + } } else { - /* File switch failed: stop here */ + /* no ringbuffer - just stop */ ld.go = FALSE; - continue; } - } else { - /* no ringbuffer - just stop */ - ld.go = FALSE; } } + /* Only update once a second so as not to overload slow displays */ cur_time = time(NULL); if (cur_time > upd_time) { upd_time = cur_time; - for (i = 0; i < N_COUNTS; i++) { - snprintf(label_str, sizeof(label_str), "%d", - *counts[i].value_ptr); - - gtk_label_set(GTK_LABEL(counts[i].value), label_str); - - snprintf(label_str, sizeof(label_str), "(%.1f%%)", - pct(*counts[i].value_ptr, ld.counts.total)); - - gtk_label_set(GTK_LABEL(counts[i].percent), label_str); - } - - /* do sync here, too */ - fflush(wtap_dump_file(ld.pdh)); - if (capture_child && ld.sync_packets) { - /* This is the child process for a sync mode capture, so send - our parent a message saying we've written out "ld.sync_packets" - packets to the capture file. */ - char tmp[20]; - sprintf(tmp, "%d%c", ld.sync_packets, SP_PACKET_COUNT); - write(1, tmp, strlen(tmp)); + if (ld.sync_packets) { + + for (i = 0; i < N_COUNTS; i++) { + snprintf(label_str, sizeof(label_str), "%d", + *counts[i].value_ptr); + + gtk_label_set(GTK_LABEL(counts[i].value), label_str); + + snprintf(label_str, sizeof(label_str), "(%.1f%%)", + pct(*counts[i].value_ptr, ld.counts.total)); + + gtk_label_set(GTK_LABEL(counts[i].percent), label_str); + } + + /* do sync here, too */ + fflush(wtap_dump_file(ld.pdh)); + + if (capture_child) { + /* This is the child process for a sync mode capture, so send + our parent a message saying we've written out "ld.sync_packets" + packets to the capture file. */ + char tmp[DECISIZE+1+1]; + sprintf(tmp, "%d%c", ld.sync_packets, SP_PACKET_COUNT); + write(1, tmp, strlen(tmp)); + } + ld.sync_packets = 0; + + } + + if (cnd_stop_timeout != NULL && cnd_eval(cnd_stop_timeout)) { + /* The specified capture time has elapsed; stop the capture. */ + ld.go = FALSE; } } - } + } /* while (ld.go) */ /* delete stop conditions */ if (cnd_stop_capturesize != NULL) @@ -1735,7 +1893,12 @@ snprintf(errmsg, sizeof(errmsg), "Error while capturing packets: %s", pcap_geterr(pch)); popup_errmsg(errmsg); +#ifdef _WIN32 } +#else + } else if (ld.from_pipe && ld.pipe_err == PIPERR) + popup_errmsg(errmsg); +#endif if (ld.err != 0) { get_capture_file_io_error(errmsg, sizeof(errmsg), cfile.save_file, ld.err, @@ -1763,7 +1926,16 @@ } } #ifndef _WIN32 - if (ld.from_pipe) + +/* XXX We exhibit different behaviour between normal mode and sync mode + * when the pipe is stdin and not already at EOF. If we're a child, the + * parent's stdin isn't closed, so if the user starts another capture, + * pipe_open_live() will very likely not see the expected magic bytes and + * will say "Unrecognized libpcap format". On the other hand, in normal + * mode, pipe_open_live() will say "End of file on pipe during open". + */ + + if (ld.from_pipe && pipe_fd >= 0) close(pipe_fd); else #endif @@ -1774,7 +1946,7 @@ *stats_known = TRUE; if (capture_child) { /* Let the parent process know. */ - char tmp[20]; + char tmp[DECISIZE+1+1]; sprintf(tmp, "%d%c", stats->ps_drop, SP_DROPS); write(1, tmp, strlen(tmp)); } @@ -1813,7 +1985,11 @@ } cfile.save_file = NULL; popup_errmsg(errmsg); - if (pch != NULL && !ld.from_pipe) + + if (ld.from_pipe) { + if (pipe_fd >= 0) + close(pipe_fd); + } else if (pch != NULL) pcap_close(pch); return FALSE; @@ -1895,7 +2071,7 @@ send_errmsg_to_parent(const char *errmsg) { int msglen = strlen(errmsg); - char lenbuf[10+1+1]; + char lenbuf[DECISIZE+1+1]; sprintf(lenbuf, "%u%c", msglen, SP_ERROR_MSG); write(1, lenbuf, strlen(lenbuf));
- Follow-Ups:
- Re: [Ethereal-dev] [Patch] Capture from pipe without blocking
- From: Guy Harris
- Re: [Ethereal-dev] [Patch] Capture from pipe without blocking
- Prev by Date: [Ethereal-dev] [PATCH] updated 802.11 dissector
- Next by Date: Re: [Ethereal-dev] [PATCH] updated 802.11 dissector
- Previous by thread: Re: [Ethereal-dev] [PATCH] updated 802.11 dissector
- Next by thread: Re: [Ethereal-dev] [Patch] Capture from pipe without blocking
- Index(es):