int cooked; /* using SOCK_DGRAM rather than SOCK_RAW */
int ifindex; /* interface index of device we're bound to */
int lo_ifindex; /* interface index of the loopback device */
+ int netdown; /* we got an ENETDOWN and haven't resolved it */
bpf_u_int32 oldmode; /* mode to restore when turning monitor mode off */
char *mondevice; /* mac80211 monitor device we created */
u_char *mmapbuf; /* memory-mapped region pointer */
# define VLAN_TPID(hdr, hv) ETH_P_8021Q
#endif
+/*
+ * Required select timeout if we're polling for an "interface disappeared"
+ * indication - 1 millisecond.
+ */
+static const struct timeval netdown_timeout = {
+ 0, 1000 /* 1000 microseconds = 1 millisecond */
+};
+
/*
* Wrap some ioctl calls
*/
return 1;
}
+/*
+ * Check whether the device to which the pcap_t is bound still exists.
+ * We do so by asking what address the socket is bound to, and checking
+ * whether the ifindex in the address is -1, meaning "that device is gone",
+ * or some other value, meaning "that device still exists".
+ */
+static int
+device_still_exists(pcap_t *handle)
+{
+ struct pcap_linux *handlep = handle->priv;
+ struct sockaddr_ll addr;
+ socklen_t addr_len;
+
+ /*
+ * If handlep->ifindex is -1, the socket isn't bound, meaning
+ * we're capturing on the "any" device; that device never
+ * disappears. (It should also never be configured down, so
+ * we shouldn't even get here, but let's make sure.)
+ */
+ if (handlep->ifindex == -1)
+ return (1); /* it's still here */
+
+ /*
+ * OK, now try to get the address for the socket.
+ */
+ addr_len = sizeof (addr);
+ if (getsockname(handle->fd, (struct sockaddr *) &addr, &addr_len) == -1) {
+ /*
+ * Error - report an error and return -1.
+ */
+ pcap_fmt_errmsg_for_errno(handle->errbuf, PCAP_ERRBUF_SIZE,
+ errno, "getsockname failed");
+ return (-1);
+ }
+ if (addr.sll_ifindex == -1) {
+ /*
+ * This means the device went away.
+ */
+ return (0);
+ }
+
+ /*
+ * The device presumably just went down.
+ */
+ return (1);
+}
+
/*
* Read a packet from the socket calling the handler provided by
* the user. Returns the number of packets received or -1 if an
* loop, the signal handler should call pcap_breakloop()
* to set handle->break_loop (we ignore it on other
* platforms as well).
- * We also ignore ENETDOWN, so that we can continue to
- * capture traffic if the interface goes down and comes
- * back up again; comments in the kernel indicate that
- * we'll just block waiting for packets if we try to
- * receive from a socket that delivered ENETDOWN, and,
- * if we're using a memory-mapped buffer, we won't even
- * get notified of "network down" events.
*/
bp = (u_char *)handle->buffer + handle->offset;
case ENETDOWN:
/*
- * The device on which we're capturing went away.
+ * The device on which we're capturing went away
+ * or the interface was taken down.
+ *
+ * Check whether the device still exists.
+ */
+ if (device_still_exists(handle)) {
+ /*
+ * It does. Just ignore this; on the
+ * *BSDs and Darwin, we don't even
+ * get told whether the interface
+ * went down, and both there and on
+ * Linux, if it comes back up again,
+ * and new packes arrive or are sent,
+ * they'll be delivered.
+ *
+ * XXX - ideally, we'd return an
+ * "interface went down" warning,
+ * so the user can be told, but
+ * pcap_dispatch() etc. aren't
+ * defined to return warnings.
+ */
+ return 0;
+ }
+
+ /*
+ * It doesn't; report that.
*
- * XXX - we should really return
- * PCAP_ERROR_IFACE_NOT_UP, but pcap_dispatch()
- * etc. aren't defined to return that.
+ * XXX - we should really return an appropriate
+ * error for that, but pcap_dispatch() etc. aren't
+ * documented as having error returns other than
+ * PCAP_ERROR or PCAP_ERROR_BREAK.
*/
snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
- "The interface went down");
+ "The interface disappeared");
return PCAP_ERROR;
default:
static int pcap_wait_for_frames_mmap(pcap_t *handle)
{
struct pcap_linux *handlep = handle->priv;
- char c;
+ int timeout;
+ struct ifreq ifr;
int ret;
#ifdef HAVE_SYS_EVENTFD_H
struct pollfd pollinfo[2];
pollinfo[0].fd = handle->fd;
pollinfo[0].events = POLLIN;
- do {
- /*
+ /*
+ * Keep polling until we either get some packets to read, see
+ * that we got told to break out of the loop, get a fatal error,
+ * or discover that the device went away.
+ *
+ * In non-blocking mode, we must still do one poll() to catch
+ * any pending error indications, but the poll() has a timeout
+ * of 0, so that it doesn't block, and we quit after that one
+ * poll().
+ *
+ * If we've seen an ENETDOWN, it might be the first indication
+ * that the device went away, or it might just be that it was
+ * configured down. Unfortunately, there's no guarantee that
+ * the device has actually been removed as an interface, because:
+ *
+ * 1) if, as appears to be the case at least some of the time,
+ * the PF_PACKET socket code first gets a NETDEV_DOWN indication
+ * for the device and then gets a NETDEV_UNREGISTER indication
+ * for it, the first indication will cause a wakeup with ENETDOWN
+ * but won't set the packet socket's field for the interface index
+ * to -1, and the second indication won't cause a wakeup (because
+ * the first indication also caused the protocol hook to be
+ * unregistered) but will set the packet socket's field for the
+ * interface index to -1;
+ *
+ * 2) even if just a NETDEV_UNREGISTER indication is registered,
+ * the packet socket's field for the interface index only gets
+ * set to -1 after the wakeup, so there's a small but non-zero
+ * risk that a thread blocked waiting for the wakeup will get
+ * to the "fetch the socket name" code before the interface index
+ * gets set to -1, so it'll get the old interface index.
+ *
+ * Therefore, if we got an ENETDOWN and haven't seen a packet
+ * since then, we assume that we might be waiting for the interface
+ * to disappear, and poll with a timeout to try again in a short
+ * period of time. If we *do* see a packet, the interface has
+ * come back up again, and is *definitely* still there, so we
+ * don't need to poll.
+ */
+ for (;;) {
+ /*
* Yes, we do this even in non-blocking mode, as it's
* the only way to get error indications from a
* tpacket socket.
* The timeout is 0 in non-blocking mode, so poll()
* returns immediately.
*/
+ timeout = handlep->poll_timeout;
+ /*
+ * If we got an ENETDOWN and haven't gotten an indication
+ * that the device has gone away or that the device is up,
+ * we don't yet know for certain whether the device has
+ * gone away or not, do a poll() with a 1-millisecond timeout,
+ * as we have to poll indefinitely for "device went away"
+ * indications until we either get one or see that the
+ * device is up.
+ */
+ if (handlep->netdown) {
+ if (timeout != 0)
+ timeout = 1;
+ }
#ifdef HAVE_SYS_EVENTFD_H
- ret = poll(pollinfo, 2, handlep->poll_timeout);
+ ret = poll(pollinfo, 2, timeout);
#else
- ret = poll(pollinfo, 1, handlep->poll_timeout);
+ ret = poll(pollinfo, 1, timeout);
#endif
- if (ret < 0 && errno != EINTR) {
- pcap_fmt_errmsg_for_errno(handle->errbuf,
- PCAP_ERRBUF_SIZE, errno,
- "can't poll on packet socket");
- return PCAP_ERROR;
- } else if (ret > 0 && pollinfo[0].revents &&
- (pollinfo[0].revents & (POLLHUP|POLLRDHUP|POLLERR|POLLNVAL))) {
+ if (ret < 0) {
/*
- * There's some indication other than
- * "you can read on this descriptor" on
- * the descriptor.
+ * Error. If it's not EINTR, report it.
*/
- if (pollinfo[0].revents & (POLLHUP | POLLRDHUP)) {
- snprintf(handle->errbuf,
- PCAP_ERRBUF_SIZE,
- "Hangup on packet socket");
+ if (errno != EINTR) {
+ pcap_fmt_errmsg_for_errno(handle->errbuf,
+ PCAP_ERRBUF_SIZE, errno,
+ "can't poll on packet socket");
return PCAP_ERROR;
}
- if (pollinfo[0].revents & POLLERR) {
+
+ /*
+ * It's EINTR; if we were told to break out of
+ * the loop, do so.
+ */
+ if (handle->break_loop) {
+ handle->break_loop = 0;
+ return PCAP_ERROR_BREAK;
+ }
+ } else if (ret > 0) {
+ /*
+ * OK, some descriptor is ready.
+ * Check the socket descriptor first.
+ *
+ * As I read the Linux man page, pollinfo[0].revents
+ * will either be POLLIN, POLLERR, POLLHUP, or POLLNVAL.
+ */
+ if (pollinfo[0].revents == POLLIN) {
+ /*
+ * OK, we may have packets to
+ * read.
+ */
+ break;
+ }
+ if (pollinfo[0].revents != 0) {
+ /*
+ * There's some indication other than
+ * "you can read on this descriptor" on
+ * the descriptor.
+ */
+ if (pollinfo[0].revents & POLLNVAL) {
+ snprintf(handle->errbuf,
+ PCAP_ERRBUF_SIZE,
+ "Invalid polling request on packet socket");
+ return PCAP_ERROR;
+ }
+ if (pollinfo[0].revents & (POLLHUP | POLLRDHUP)) {
+ snprintf(handle->errbuf,
+ PCAP_ERRBUF_SIZE,
+ "Hangup on packet socket");
+ return PCAP_ERROR;
+ }
+ if (pollinfo[0].revents & POLLERR) {
+ /*
+ * Get the error.
+ */
+ int err;
+ socklen_t errlen;
+
+ errlen = sizeof(err);
+ if (getsockopt(handle->fd, SOL_SOCKET,
+ SO_ERROR, &err, &errlen) == -1) {
+ /*
+ * The call *itself* returned
+ * an error; make *that*
+ * the error.
+ */
+ err = errno;
+ }
+
+ /*
+ * OK, we have the error.
+ */
+ if (err == ENETDOWN) {
+ /*
+ * The device on which we're
+ * capturing went away or the
+ * interface was taken down.
+ *
+ * We don't know for certain
+ * which happened, and the
+ * next poll() may indicate
+ * that there are packets
+ * to be read, so just set
+ * a flag to get us to do
+ * checks later, and set
+ * the required select
+ * timeout to 1 millisecond
+ * so that event loops that
+ * check our socket descriptor
+ * also time out so that
+ * they can call us and we
+ * can do the checks.
+ */
+ handlep->netdown = 1;
+ handle->required_select_timeout = &netdown_timeout;
+ } else if (err == 0) {
+ /*
+ * This shouldn't happen, so
+ * report a special indication
+ * that it did.
+ */
+ snprintf(handle->errbuf,
+ PCAP_ERRBUF_SIZE,
+ "Error condition on packet socket: Reported error was 0");
+ return PCAP_ERROR;
+ } else {
+ pcap_fmt_errmsg_for_errno(handle->errbuf,
+ PCAP_ERRBUF_SIZE,
+ err,
+ "Error condition on packet socket");
+ return PCAP_ERROR;
+ }
+ }
+ }
+#ifdef HAVE_SYS_EVENTFD_H
+ /*
+ * Now check the event device.
+ */
+ if (pollinfo[1].revents & POLLIN) {
+ uint64_t value;
+ (void)read(handlep->poll_breakloop_fd, &value,
+ sizeof(value));
+
+ /*
+ * This event gets signaled by a
+ * pcap_breakloop() call; if we were told
+ * to break out of the loop, do so.
+ */
+ if (handle->break_loop) {
+ handle->break_loop = 0;
+ return PCAP_ERROR_BREAK;
+ }
+ }
+#endif
+ }
+
+ /*
+ * Either:
+ *
+ * 1) we got neither an error from poll() nor any
+ * readable descriptors, in which case there
+ * are no packets waiting to read
+ *
+ * or
+ *
+ * 2) We got readable descriptors but the PF_PACKET
+ * socket wasn't one of them, in which case there
+ * are no packets waiting to read
+ *
+ * so, if we got an ENETDOWN, we've drained whatever
+ * packets were available to read at the point of the
+ * ENETDOWN.
+ *
+ * So, if we got an ENETDOWN and haven't gotten an indication
+ * that the device has gone away or that the device is up,
+ * we don't yet know for certain whether the device has
+ * gone away or not, check whether the device exists and is
+ * up.
+ */
+ if (handlep->netdown) {
+ if (!device_still_exists(handle)) {
/*
- * A recv() will give us the actual error code.
+ * The device doesn't exist any more;
+ * report that.
*
- * XXX - make the socket non-blocking?
+ * XXX - we should really return an
+ * appropriate error for that, but
+ * pcap_dispatch() etc. aren't documented
+ * as having error returns other than
+ * PCAP_ERROR or PCAP_ERROR_BREAK.
*/
- if (recv(handle->fd, &c, sizeof c,
- MSG_PEEK) != -1)
- continue; /* what, no error? */
- if (errno == ENETDOWN) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "The interface disappeared");
+ return PCAP_ERROR;
+ }
+
+ /*
+ * The device still exists; try to see if it's up.
+ */
+ memset(&ifr, 0, sizeof(ifr));
+ pcap_strlcpy(ifr.ifr_name, handlep->device,
+ sizeof(ifr.ifr_name));
+ if (ioctl(handle->fd, SIOCGIFFLAGS, &ifr) == -1) {
+ if (errno == ENXIO || errno == ENODEV) {
/*
- * The device on which we're
- * capturing went away.
+ * OK, *now* it's gone.
*
- * XXX - we should really return
- * PCAP_ERROR_IFACE_NOT_UP, but
- * pcap_dispatch() etc. aren't
- * defined to return that.
+ * XXX - see above comment.
*/
snprintf(handle->errbuf,
- PCAP_ERRBUF_SIZE,
- "The interface went down");
+ PCAP_ERRBUF_SIZE,
+ "The interface disappeared");
+ return PCAP_ERROR;
} else {
pcap_fmt_errmsg_for_errno(handle->errbuf,
PCAP_ERRBUF_SIZE, errno,
- "Error condition on packet socket");
+ "%s: Can't get flags",
+ handlep->device);
+ return PCAP_ERROR;
}
- return PCAP_ERROR;
}
- if (pollinfo[0].revents & POLLNVAL) {
- snprintf(handle->errbuf,
- PCAP_ERRBUF_SIZE,
- "Invalid polling request on packet socket");
- return PCAP_ERROR;
+ if (ifr.ifr_flags & IFF_UP) {
+ /*
+ * It's up, so it definitely still exists.
+ * Cancel the ENETDOWN indication - we
+ * presumably got it due to the interface
+ * going down rather than the device going
+ * away - and revert to "no required select
+ * timeout.
+ */
+ handlep->netdown = 0;
+ handle->required_select_timeout = NULL;
}
}
-#ifdef HAVE_SYS_EVENTFD_H
- if (pollinfo[1].revents & POLLIN) {
- uint64_t value;
- (void)read(handlep->poll_breakloop_fd, &value, sizeof(value));
- }
-#endif
-
- /* check for break loop condition on interrupted syscall*/
- if (handle->break_loop) {
- handle->break_loop = 0;
- return PCAP_ERROR_BREAK;
- }
- } while (ret < 0);
+ /*
+ * If we're in non-blocking mode, just quit now, rather
+ * than spinning in a loop doing poll()s that immediately
+ * time out if there's no indication on any descriptor.
+ */
+ if (handlep->poll_timeout == 0)
+ break;
+ }
return 0;
}
.\"
.TH PCAP_GET_REQUIRED_SELECT_TIMEOUT 3PCAP "25 July 2018"
.SH NAME
-pcap_get_required_select_timeout \- get a file descriptor on which a
-select() can be done for a live capture
+pcap_get_required_select_timeout \- get a timeout to be used when doing
+select() for a live capture
.SH SYNOPSIS
.nf
.ft B
.ft
.LP
.ft B
-struct timeval *pcap_get_required_select_timeout(pcap_t *p);
+const struct timeval *pcap_get_required_select_timeout(pcap_t *p);
.ft
.fi
.SH DESCRIPTION
.BR epoll_wait (2),
and
.BR kevent (2)
-calls if
+calls, or
+.B NULL
+if there is no such timeout.
+If a
+.RB non- NULL
+value is returned, it must be used regardless of whether
.BR pcap_get_selectable_fd (3PCAP)
returns
-.BR PCAP_ERROR .
-.PP
-The timeout that should be used in those calls must be no larger than
-the smallest of all timeouts returned by
-.BR \%pcap_get_required_select_timeout ()
-for devices from which packets will be captured.
-.PP
-The device for which
-.BR pcap_get_selectable_fd ()
-returned
-.B PCAP_ERROR
-must be put in non-blocking mode with
-.BR pcap_setnonblock (3PCAP),
-and an attempt must always be made to read packets from the device
-when the
+.B \-1
+for any descriptor on which those calls are being done.
+.BR pcap_get_required_select_timeout ()
+should be called for all
+.BR pcap_t s
+before a call to
.BR select (),
.BR poll (),
.BR epoll_wait (),
or
-.BR kevent ()
-call returns.
+.BR kevent (),
+and any timeouts used for those calls should be updated as appropriate
+given the new value of the timeout.
+.PP
+For
+.BR kevent (),
+one
+.B EVFILT_TIMER
+filter per selectable descriptor can be used, rather than using the
+timeout argument to
+.BR kevent ();
+if the
+.B EVFILT_TIMER
+event for a particular selectable descriptor signals an event,
+.BR pcap_dispatch (2)
+should be called for the corresponding
+.BR pcap_t .
+.PP
+On Linux systems with
+.BR timerfd_create (2),
+one timer object created by
+.BR timerfd_create ()
+per selectable descriptor can be used, rather than using the timeout
+argument to
+.BR epoll_wait ();
+if the
+timer object for a particular selectable descriptor signals an event,
+.BR pcap_dispatch (2)
+should be called for the corresponding
+.BR pcap_t .
+.PP
+Otherwise, a timeout value no larger than
+the smallest of all timeouts returned by
+.BR \%pcap_get_required_select_timeout ()
+for devices from which packets will be captured and any other timeouts
+to be used in the call should be used as the timeout for the call, and,
+when the call returns,
+.BR pcap_dispatch (2)
+should be called for all
+.BR pcap_t s
+for which a
+.RB non- NULL
+timeout was returned, regardless of whether it's indicated as having
+anything to read from it or not.
+.PP
+All devices with a
+.RB non-NULL
+timeout must be put in non-blocking mode with
+.BR pcap_setnonblock (3PCAP).
.PP
Note that a device on which a read can be done without blocking may,
on some platforms, not have any packets to read if the packet buffer
timeout has expired. A call to
-.BR pcap_dispatch (3PCAP)
+.BR pcap_dispatch ()
or
.BR pcap_next_ex (3PCAP)
will return 0 in this case, but will not block.
cannot be used on any capture source for which
.BR pcap_get_selectable_fd ()
returns \-1.
+.PP
+In libpcap release 1.10.0 and later, the timeout value can change from
+call to call, so
+.BR pcap_get_required_select_timeout ()
+must be called before each call to
+.BR select (),
+.BR poll (),
+.BR epoll_wait (),
+or
+.BR kevent (),
+and the new value must be used to calculate timeouts for the call. Code
+that does that will also work with libpcap 1.9.x releases, so code
+using
+.BR pcap_get_required_select_timeout ()
+should be changed to call it for each call to
+.BR select (),
+.BR poll (),
+.BR epoll_wait (),
+or
+.BR kevent ()
+even if the code must also work with libpcap 1.9.x.
.SH SEE ALSO
.BR pcap (3PCAP),
.BR pcap_get_selectable_fd (3PCAP),
register int op;
bpf_u_int32 localnet, netmask;
register char *cp, *cmdbuf, *device;
- int doselect, dopoll, dotimeout, dononblock;
+ int doselect, dopoll, dotimeout, dononblock, quiet;
const char *mechanism;
struct bpf_program fcode;
char ebuf[PCAP_ERRBUF_SIZE];
pcap_if_t *devlist;
int selectable_fd;
- struct timeval *required_timeout;
+ const struct timeval *required_timeout;
int status;
int packet_count;
mechanism = NULL;
dotimeout = 0;
dononblock = 0;
+ quiet = 0;
if ((cp = strrchr(argv[0], '/')) != NULL)
program_name = cp + 1;
else
program_name = argv[0];
opterr = 0;
- while ((op = getopt(argc, argv, "i:sptn")) != -1) {
+ while ((op = getopt(argc, argv, "i:sptnq")) != -1) {
switch (op) {
case 'i':
dononblock = 1;
break;
+ case 'q':
+ quiet = 1;
+ break;
+
default:
usage();
/* NOTREACHED */
for (;;) {
fd_set setread, setexcept;
struct timeval seltimeout;
+ struct timeval *timeoutp;
FD_ZERO(&setread);
if (selectable_fd != -1) {
FD_ZERO(&setexcept);
FD_SET(selectable_fd, &setexcept);
}
+ required_timeout = pcap_get_required_select_timeout(pd);
if (dotimeout) {
seltimeout.tv_sec = 0;
if (required_timeout != NULL &&
seltimeout.tv_usec = required_timeout->tv_usec;
else
seltimeout.tv_usec = 1000;
- status = select(selectable_fd + 1, &setread,
- NULL, &setexcept, &seltimeout);
+ timeoutp = &seltimeout;
} else if (required_timeout != NULL) {
seltimeout = *required_timeout;
- status = select(selectable_fd + 1, &setread,
- NULL, &setexcept, &seltimeout);
+ timeoutp = &seltimeout;
} else {
- status = select((selectable_fd == -1) ?
- 0 : selectable_fd + 1, &setread,
- NULL, &setexcept, NULL);
+ timeoutp = NULL;
}
+ status = select((selectable_fd == -1) ?
+ 0 : selectable_fd + 1, &setread, NULL, &setexcept,
+ timeoutp);
if (status == -1) {
printf("Select returns error (%s)\n",
strerror(errno));
} else {
- if (selectable_fd == -1) {
- if (status != 0)
- printf("Select returned a descriptor\n");
- } else {
+ if (!quiet) {
if (status == 0)
printf("Select timed out: ");
- else
+ else{
printf("Select returned a descriptor: ");
- if (FD_ISSET(selectable_fd, &setread))
- printf("readable, ");
- else
- printf("not readable, ");
- if (FD_ISSET(selectable_fd, &setexcept))
- printf("exceptional condition\n");
- else
- printf("no exceptional condition\n");
+ if (FD_ISSET(selectable_fd, &setread))
+ printf("readable, ");
+ else
+ printf("not readable, ");
+ if (FD_ISSET(selectable_fd, &setexcept))
+ printf("exceptional condition\n");
+ else
+ printf("no exceptional condition\n");
+ }
}
packet_count = 0;
status = pcap_dispatch(pd, -1, countme,
fd.fd = selectable_fd;
fd.events = POLLIN;
+ required_timeout = pcap_get_required_select_timeout(pd);
if (dotimeout)
polltimeout = 1;
else if (required_timeout != NULL &&
printf("Poll returns error (%s)\n",
strerror(errno));
} else {
- if (selectable_fd == -1) {
- if (status != 0)
- printf("Poll returned a descriptor\n");
- } else {
+ if (!quiet) {
if (status == 0)
printf("Poll timed out\n");
else {
static void
usage(void)
{
- (void)fprintf(stderr, "Usage: %s [ -sptn ] [ -i interface ] [expression]\n",
+ (void)fprintf(stderr, "Usage: %s [ -sptnq ] [ -i interface ] [expression]\n",
program_name);
exit(1);
}