aboutsummaryrefslogtreecommitdiff
path: root/pcap-linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'pcap-linux.c')
-rw-r--r--pcap-linux.c417
1 files changed, 351 insertions, 66 deletions
diff --git a/pcap-linux.c b/pcap-linux.c
index 0003af1a..f9fe8ac6 100644
--- a/pcap-linux.c
+++ b/pcap-linux.c
@@ -204,6 +204,7 @@ struct pcap_linux {
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 */
@@ -353,6 +354,14 @@ static void pcap_oneshot_mmap(u_char *user, const struct pcap_pkthdr *h,
#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
*/
static int iface_get_id(int fd, const char *device, char *ebuf);
@@ -1570,6 +1579,53 @@ linux_check_direction(const pcap_t *handle, const struct sockaddr_ll *sll)
}
/*
+ * 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
* error occured.
@@ -1616,13 +1672,6 @@ pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
* 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;
@@ -1674,14 +1723,40 @@ pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
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:
@@ -4488,7 +4563,8 @@ pcap_get_ring_frame_status(pcap_t *handle, int offset)
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];
@@ -4500,8 +4576,47 @@ static int pcap_wait_for_frames_mmap(pcap_t *handle)
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.
@@ -4509,80 +4624,250 @@ static int pcap_wait_for_frames_mmap(pcap_t *handle)
* 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;
}