+/*
+ * Not all interfaces can be bound to by BPF, so try to bind to
+ * the specified interface; return 0 if we fail with
+ * PCAP_ERROR_NO_SUCH_DEVICE (which means we got an ENXIO when we tried
+ * to bind, which means this interface isn't in the list of interfaces
+ * attached to BPF) and 1 otherwise.
+ */
+static int
+check_bpf_bindable(const char *name)
+{
+ int fd;
+ char errbuf[PCAP_ERRBUF_SIZE];
+
+ /*
+ * On macOS, we don't do this check if the device name begins
+ * with "wlt"; at least some versions of macOS (actually, it
+ * was called "Mac OS X" then...) offer monitor mode capturing
+ * by having a separate "monitor mode" device for each wireless
+ * adapter, rather than by implementing the ioctls that
+ * {Free,Net,Open,DragonFly}BSD provide. Opening that device
+ * puts the adapter into monitor mode, which, at least for
+ * some adapters, causes them to deassociate from the network
+ * with which they're associated.
+ *
+ * Instead, we try to open the corresponding "en" device (so
+ * that we don't end up with, for users without sufficient
+ * privilege to open capture devices, a list of adapters that
+ * only includes the wlt devices).
+ */
+#ifdef __APPLE__
+ if (strncmp(name, "wlt", 3) == 0) {
+ char *en_name;
+ size_t en_name_len;
+
+ /*
+ * Try to allocate a buffer for the "en"
+ * device's name.
+ */
+ en_name_len = strlen(name) - 1;
+ en_name = malloc(en_name_len + 1);
+ if (en_name == NULL) {
+ pcap_fmt_errmsg_for_errno(errbuf, PCAP_ERRBUF_SIZE,
+ errno, "malloc");
+ return (-1);
+ }
+ strcpy(en_name, "en");
+ strcat(en_name, name + 3);
+ fd = bpf_open_and_bind(en_name, errbuf);
+ free(en_name);
+ } else
+#endif /* __APPLE */
+ fd = bpf_open_and_bind(name, errbuf);
+ if (fd < 0) {
+ /*
+ * Error - was it PCAP_ERROR_NO_SUCH_DEVICE?
+ */
+ if (fd == PCAP_ERROR_NO_SUCH_DEVICE) {
+ /*
+ * Yes, so we can't bind to this because it's
+ * not something supported by BPF.
+ */
+ return (0);
+ }
+ /*
+ * No, so we don't know whether it's supported or not;
+ * say it is, so that the user can at least try to
+ * open it and report the error (which is probably
+ * "you don't have permission to open BPF devices";
+ * reporting those interfaces means users will ask
+ * "why am I getting a permissions error when I try
+ * to capture" rather than "why am I not seeing any
+ * interfaces", making the underlying problem clearer).
+ */
+ return (1);
+ }
+
+ /*
+ * Success.
+ */
+ close(fd);
+ return (1);
+}
+
+#if defined(__FreeBSD__) && defined(SIOCIFCREATE2)
+static int
+get_usb_if_flags(const char *name _U_, bpf_u_int32 *flags _U_, char *errbuf _U_)
+{
+ /*
+ * XXX - if there's a way to determine whether there's something
+ * plugged into a given USB bus, use that to determine whether
+ * this device is "connected" or not.
+ */
+ return (0);
+}
+
+static int
+finddevs_usb(pcap_if_list_t *devlistp, char *errbuf)
+{
+ DIR *usbdir;
+ struct dirent *usbitem;
+ size_t name_max;
+ char *name;
+
+ /*
+ * We might have USB sniffing support, so try looking for USB
+ * interfaces.
+ *
+ * We want to report a usbusN device for each USB bus, but
+ * usbusN interfaces might, or might not, exist for them -
+ * we create one if there isn't already one.
+ *
+ * So, instead, we look in /dev/usb for all buses and create
+ * a "usbusN" device for each one.
+ */
+ usbdir = opendir("/dev/usb");
+ if (usbdir == NULL) {
+ /*
+ * Just punt.
+ */
+ return (0);
+ }
+
+ /*
+ * Leave enough room for a 32-bit (10-digit) bus number.
+ * Yes, that's overkill, but we won't be using
+ * the buffer very long.
+ */
+ name_max = USBUS_PREFIX_LEN + 10 + 1;
+ name = malloc(name_max);
+ if (name == NULL) {
+ closedir(usbdir);
+ return (0);
+ }
+ while ((usbitem = readdir(usbdir)) != NULL) {
+ char *p;
+ size_t busnumlen;
+
+ if (strcmp(usbitem->d_name, ".") == 0 ||
+ strcmp(usbitem->d_name, "..") == 0) {
+ /*
+ * Ignore these.
+ */
+ continue;
+ }
+ p = strchr(usbitem->d_name, '.');
+ if (p == NULL)
+ continue;
+ busnumlen = p - usbitem->d_name;
+ memcpy(name, usbus_prefix, USBUS_PREFIX_LEN);
+ memcpy(name + USBUS_PREFIX_LEN, usbitem->d_name, busnumlen);
+ *(name + USBUS_PREFIX_LEN + busnumlen) = '\0';
+ /*
+ * There's an entry in this directory for every USB device,
+ * not for every bus; if there's more than one device on
+ * the bus, there'll be more than one entry for that bus,
+ * so we need to avoid adding multiple capture devices
+ * for each bus.
+ */
+ if (find_or_add_dev(devlistp, name, PCAP_IF_UP,
+ get_usb_if_flags, NULL, errbuf) == NULL) {
+ free(name);
+ closedir(usbdir);
+ return (PCAP_ERROR);
+ }
+ }
+ free(name);
+ closedir(usbdir);
+ return (0);
+}
+#endif
+
+/*
+ * Get additional flags for a device, using SIOCGIFMEDIA.
+ */
+#ifdef SIOCGIFMEDIA
+static int
+get_if_flags(const char *name, bpf_u_int32 *flags, char *errbuf)
+{
+ int sock;
+ struct ifmediareq req;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock == -1) {
+ pcap_fmt_errmsg_for_errno(errbuf, PCAP_ERRBUF_SIZE, errno,
+ "Can't create socket to get media information for %s",
+ name);
+ return (-1);
+ }
+ memset(&req, 0, sizeof(req));
+ pcap_strlcpy(req.ifm_name, name, sizeof(req.ifm_name));
+ if (ioctl(sock, SIOCGIFMEDIA, &req) < 0) {
+ if (errno == EOPNOTSUPP || errno == EINVAL || errno == ENOTTY ||
+ errno == ENODEV || errno == EPERM
+#ifdef EPWROFF
+ || errno == EPWROFF
+#endif
+ ) {
+ /*
+ * Not supported, so we can't provide any
+ * additional information. Assume that
+ * this means that "connected" vs.
+ * "disconnected" doesn't apply.
+ *
+ * The ioctl routine for Apple's pktap devices,
+ * annoyingly, checks for "are you root?" before
+ * checking whether the ioctl is valid, so it
+ * returns EPERM, rather than ENOTSUP, for the
+ * invalid SIOCGIFMEDIA, unless you're root.
+ * So, just as we do for some ethtool ioctls
+ * on Linux, which makes the same mistake, we
+ * also treat EPERM as meaning "not supported".
+ *
+ * And it appears that Apple's llw0 device, which
+ * appears to be part of the Skywalk subsystem:
+ *
+ * http://newosxbook.com/bonus/vol1ch16.html
+ *
+ * can sometimes return EPWROFF ("Device power
+ * is off") for that ioctl, so we treat *that*
+ * as another indication that we can't get a
+ * connection status. (If it *isn't* "powered
+ * off", it's reported as a wireless device,
+ * complete with an active/inactive state.)
+ */
+ *flags |= PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE;
+ close(sock);
+ return (0);
+ }
+ pcap_fmt_errmsg_for_errno(errbuf, PCAP_ERRBUF_SIZE, errno,
+ "SIOCGIFMEDIA on %s failed", name);
+ close(sock);
+ return (-1);
+ }
+ close(sock);
+
+ /*
+ * OK, what type of network is this?
+ */
+ switch (IFM_TYPE(req.ifm_active)) {
+
+ case IFM_IEEE80211:
+ /*
+ * Wireless.
+ */
+ *flags |= PCAP_IF_WIRELESS;
+ break;
+ }
+
+ /*
+ * Do we know whether it's connected?
+ */
+ if (req.ifm_status & IFM_AVALID) {
+ /*
+ * Yes.
+ */
+ if (req.ifm_status & IFM_ACTIVE) {
+ /*
+ * It's connected.
+ */
+ *flags |= PCAP_IF_CONNECTION_STATUS_CONNECTED;
+ } else {
+ /*
+ * It's disconnected.
+ */
+ *flags |= PCAP_IF_CONNECTION_STATUS_DISCONNECTED;
+ }
+ }
+ return (0);
+}
+#else
+static int
+get_if_flags(const char *name _U_, bpf_u_int32 *flags, char *errbuf _U_)
+{
+ /*
+ * Nothing we can do other than mark loopback devices as "the
+ * connected/disconnected status doesn't apply".
+ *
+ * XXX - on Solaris, can we do what the dladm command does,
+ * i.e. get a connected/disconnected indication from a kstat?
+ * (Note that you can also get the link speed, and possibly
+ * other information, from a kstat as well.)
+ */
+ if (*flags & PCAP_IF_LOOPBACK) {
+ /*
+ * Loopback devices aren't wireless, and "connected"/
+ * "disconnected" doesn't apply to them.
+ */
+ *flags |= PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE;
+ return (0);
+ }
+ return (0);
+}
+#endif
+