/* * Copyright 2006-2010, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler, axeld@pinc-software.de * James Woodcock */ #include #include "pcap-int.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // IFT_TUN was renamed to IFT_TUNNEL in the master branch after R1/beta4 (the // integer value didn't change). Even though IFT_TUN is a no-op in versions // that define it, for the time being it is desirable to support compiling // libpcap on versions with the old macro and using it on later versions that // support tunnel interfaces. #ifndef IFT_TUNNEL #define IFT_TUNNEL IFT_TUN #endif /* * Private data for capturing on Haiku sockets. */ struct pcap_haiku { struct pcap_stat stat; int aux_socket; struct ifreq ifreq; // The original state of the promiscuous mode at the activation time, // if the capture should be run in promiscuous mode. int orig_promisc; }; static int pcap_read_haiku(pcap_t* handle, int maxPackets _U_, pcap_handler callback, u_char* userdata) { // Receive a single packet u_char* buffer = (u_char*)handle->buffer; ssize_t bytesReceived; do { if (handle->break_loop) { handle->break_loop = 0; return PCAP_ERROR_BREAK; } bytesReceived = recvfrom(handle->fd, buffer, handle->bufsize, MSG_TRUNC, NULL, NULL); } while (bytesReceived < 0 && errno == B_INTERRUPTED); // The kernel does not implement timestamping of network packets, so // doing it ASAP in userland is the best that can be done. bigtime_t ts = real_time_clock_usecs(); if (bytesReceived < 0) { if (errno == B_WOULD_BLOCK) { // there is no packet for us return 0; } pcapint_fmt_errmsg_for_errno(handle->errbuf, PCAP_ERRBUF_SIZE, errno, "recvfrom"); return PCAP_ERROR; } struct pcap_haiku* handlep = (struct pcap_haiku*)handle->priv; // BPF is 32-bit, which is more than sufficient for any realistic // packet size. if (bytesReceived > UINT32_MAX) goto drop; // At this point, if the recvfrom() call populated its struct sockaddr // and socklen_t arguments, it would be the right time to drop packets // that have .sa_family not valid for the current DLT. But in the // current master branch (hrev57588) this would erroneously drop some // valid packets: recvfrom(), at least for tap mode tunnels, sets the // address length to 0 for all incoming packets and sets .sa_len and // .sa_family to 0 for packets that are broadcast or multicast. So it // cannot be done yet, if there is a good reason to do it in the first // place. handlep->stat.ps_recv++; bpf_u_int32 wireLength = (bpf_u_int32)bytesReceived; // As long as the buffer is large enough, the captured length is equal // to the wire length, but let's get the lengths right anyway in case // packets grow bigger or the buffer grows smaller in future and the // MSG_TRUNC effect kicks in. bpf_u_int32 captureLength = wireLength <= handle->bufsize ? wireLength : handle->bufsize; // run the packet filter if (handle->fcode.bf_insns) { // NB: pcapint_filter() takes the wire length and the captured // length, not the snapshot length of the pcap_t handle. if (pcapint_filter(handle->fcode.bf_insns, buffer, wireLength, captureLength) == 0) goto drop; } // fill in pcap_header struct pcap_pkthdr header; header.caplen = captureLength <= (bpf_u_int32)handle->snapshot ? captureLength : (bpf_u_int32)handle->snapshot; header.len = wireLength; header.ts.tv_usec = ts % 1000000; header.ts.tv_sec = ts / 1000000; /* Call the user supplied callback function */ callback(userdata, &header, buffer); return 1; drop: handlep->stat.ps_drop++; return 0; } static int dgram_socket(const int af, char *errbuf) { int ret = socket(af, SOCK_DGRAM, 0); if (ret < 0) { pcapint_fmt_errmsg_for_errno(errbuf, PCAP_ERRBUF_SIZE, errno, "socket"); return PCAP_ERROR; } return ret; } static int ioctl_ifreq(const int fd, const unsigned long op, const char *name, struct ifreq *ifreq, char *errbuf) { if (ioctl(fd, op, ifreq, sizeof(struct ifreq)) < 0) { pcapint_fmt_errmsg_for_errno(errbuf, PCAP_ERRBUF_SIZE, errno, "%s", name); return PCAP_ERROR; } return 0; } static int get_promisc(pcap_t *handle) { struct pcap_haiku *handlep = (struct pcap_haiku *)handle->priv; // SIOCGIFFLAGS would work fine for AF_LINK too. if (ioctl_ifreq(handlep->aux_socket, SIOCGIFFLAGS, "SIOCGIFFLAGS", &handlep->ifreq, handle->errbuf) < 0) return PCAP_ERROR; return (handlep->ifreq.ifr_flags & IFF_PROMISC) != 0; } static int set_promisc(pcap_t *handle, const int enable) { struct pcap_haiku *handlep = (struct pcap_haiku *)handle->priv; if (enable) handlep->ifreq.ifr_flags |= IFF_PROMISC; else handlep->ifreq.ifr_flags &= ~IFF_PROMISC; // SIOCSIFFLAGS works for AF_INET, but not for AF_LINK. return ioctl_ifreq(handlep->aux_socket, SIOCSIFFLAGS, "SIOCSIFFLAGS", &handlep->ifreq, handle->errbuf); } static void pcap_cleanup_haiku(pcap_t *handle) { struct pcap_haiku *handlep = (struct pcap_haiku *)handle->priv; if (handlep->aux_socket >= 0) { // Closing the sockets has no effect on IFF_PROMISC, hence the // need to restore the original state on one hand and the // possibility of clash with other processes managing the same // interface flag. Unset promiscuous mode iff the activation // function had set it and it is still set now. if (handle->opt.promisc && ! handlep->orig_promisc && get_promisc(handle)) (void)set_promisc(handle, 0); close(handlep->aux_socket); handlep->aux_socket = -1; } pcapint_cleanup_live_common(handle); } static int pcap_inject_haiku(pcap_t *handle, const void *buffer _U_, int size _U_) { // Haiku currently (hrev57588) does not support sending raw packets. // https://dev.haiku-os.org/ticket/18810 strlcpy(handle->errbuf, "Sending packets isn't supported yet", PCAP_ERRBUF_SIZE); return PCAP_ERROR; } static int pcap_stats_haiku(pcap_t *handle, struct pcap_stat *stats) { struct pcap_haiku* handlep = (struct pcap_haiku*)handle->priv; *stats = handlep->stat; // Now ps_recv and ps_drop are accurate, but ps_ifdrop still equals to // the snapshot value from the activation time. if (ioctl_ifreq(handlep->aux_socket, SIOCGIFSTATS, "SIOCGIFSTATS", &handlep->ifreq, handle->errbuf) < 0) return PCAP_ERROR; // The result is subject to wrapping around the 32-bit integer space, // but that cannot be significantly improved as long as it has to fit // into a 32-bit member of pcap_stats. stats->ps_ifdrop = handlep->ifreq.ifr_stats.receive.dropped - stats->ps_ifdrop; return 0; } static int pcap_activate_haiku(pcap_t *handle) { struct pcap_haiku *handlep = (struct pcap_haiku *)handle->priv; int ret = PCAP_ERROR; // we need a socket to talk to the networking stack if ((handlep->aux_socket = dgram_socket(AF_INET, handle->errbuf)) < 0) goto error; // pcap_stats_haiku() will need a baseline for ps_ifdrop. // At the time of this writing SIOCGIFSTATS returns EINVAL for AF_LINK // sockets. if (ioctl_ifreq(handlep->aux_socket, SIOCGIFSTATS, "SIOCGIFSTATS", &handlep->ifreq, handle->errbuf) < 0) { // Detect a non-existent network interface at least at the // first ioctl() use. if (errno == EINVAL) ret = PCAP_ERROR_NO_SUCH_DEVICE; goto error; } handlep->stat.ps_ifdrop = handlep->ifreq.ifr_stats.receive.dropped; // get link level interface for this interface if ((handle->fd = dgram_socket(AF_LINK, handle->errbuf)) < 0) goto error; // Derive a DLT from the interface type. // At the time of this writing SIOCGIFTYPE cannot be used for this // purpose: it returns EINVAL for AF_LINK sockets and sets ifr_type to // 0 for AF_INET sockets. Use the same method as Haiku ifconfig does // (SIOCGIFADDR and AF_LINK). if (ioctl_ifreq(handle->fd, SIOCGIFADDR, "SIOCGIFADDR", &handlep->ifreq, handle->errbuf) < 0) goto error; struct sockaddr_dl *sdl = (struct sockaddr_dl *)&handlep->ifreq.ifr_addr; if (sdl->sdl_family != AF_LINK) { snprintf(handle->errbuf, PCAP_ERRBUF_SIZE, "Got AF %d instead of AF_LINK for interface \"%s\".", sdl->sdl_family, handle->opt.device); goto error; } switch (sdl->sdl_type) { case IFT_ETHER: // Ethernet on all versions, also tap (L2) mode tunnels on // versions after R1/beta4. handle->linktype = DLT_EN10MB; break; case IFT_TUNNEL: // Unused on R1/beta4 and earlier versions, tun (L3) mode // tunnels on later versions. case IFT_LOOP: // The loopback interface on all versions. // Both IFT_TUNNEL and IFT_LOOP prepended a dummy Ethernet // header until hrev57585: https://dev.haiku-os.org/ticket/18801 handle->linktype = DLT_RAW; break; default: snprintf(handle->errbuf, PCAP_ERRBUF_SIZE, "Unknown interface type 0x%0x for interface \"%s\".", sdl->sdl_type, handle->opt.device); goto error; } // start monitoring if (ioctl_ifreq(handle->fd, SIOCSPACKETCAP, "SIOCSPACKETCAP", &handlep->ifreq, handle->errbuf) < 0) goto error; handle->selectable_fd = handle->fd; handle->read_op = pcap_read_haiku; handle->setfilter_op = pcapint_install_bpf_program; /* no kernel filtering */ handle->inject_op = pcap_inject_haiku; handle->stats_op = pcap_stats_haiku; handle->cleanup_op = pcap_cleanup_haiku; // use default hooks where possible handle->getnonblock_op = pcapint_getnonblock_fd; handle->setnonblock_op = pcapint_setnonblock_fd; /* * Turn a negative snapshot value (invalid), a snapshot value of * 0 (unspecified), or a value bigger than the normal maximum * value, into the maximum allowed value. * * If some application really *needs* a bigger snapshot * length, we should just increase MAXIMUM_SNAPLEN. */ if (handle->snapshot <= 0 || handle->snapshot > MAXIMUM_SNAPLEN) handle->snapshot = MAXIMUM_SNAPLEN; // Although it would be trivial to size the buffer at the kernel end of // the capture socket using setsockopt() and SO_RCVBUF, there seems to // be no point in doing so: setting the size low silently drops some // packets in the kernel, setting it high does not result in a visible // improvement. Let's leave this buffer as it is until it is clear why // it would need resizing. Meanwhile pcap_set_buffer_size() will have // no effect on Haiku. // It would be wrong to size the buffer at the libpcap end of the // capture socket to the interface MTU, which limits only outgoing // packets and only at layer 3. For example, an Ethernet interface // with ifconfig/ioctl() MTU set to 1500 ordinarily sends layer 2 // packets as large as 1514 bytes and receives layer 2 packets as large // as the NIC and the driver happen to accept (e.g. 9018 bytes for // ipro1000). This way, valid packets larger than the MTU can occur in // a capture and will arrive truncated to pcap_read_haiku() if the // buffer is not large enough. So let's keep it large enough for most // if not all practical use cases, then pcap_read_haiku() can handle // the unlikely truncation as and if necessary. handle->bufsize = 65536; // allocate buffer for monitoring the device handle->buffer = (u_char*)malloc(handle->bufsize); if (handle->buffer == NULL) { pcapint_fmt_errmsg_for_errno(handle->errbuf, PCAP_ERRBUF_SIZE, errno, "buffer malloc"); goto error; } if (handle->opt.promisc) { // Set promiscuous mode iff required, in any case remember the // original state. if ((handlep->orig_promisc = get_promisc(handle)) < 0) goto error; if (! handlep->orig_promisc && set_promisc(handle, 1) < 0) return PCAP_WARNING_PROMISC_NOTSUP; } return 0; error: pcap_cleanup_haiku(handle); return ret; } static int validate_ifname(const char *device, char *errbuf) { if (strlen(device) >= IF_NAMESIZE) { snprintf(errbuf, PCAP_ERRBUF_SIZE, "Interface name \"%s\" is too long.", device); return PCAP_ERROR; } return 0; } // #pragma mark - pcap API static int can_be_bound(const char *name) { if (strcmp(name, "loop") != 0) return 1; // In Haiku versions before hrev57010 the loopback interface allows to // start a capture, but the capture never receives any packets. // // Since compiling libpcap on one Haiku version and using the binary on // another seems to be commonplace, comparing B_HAIKU_VERSION at the // compile time would not always work as intended. Let's at least // remove unsuitable well-known 64-bit versions (with or without // updates) from the problem space at run time. const char *badversions[] = { "hrev56578", // R1/beta4 "hrev55182", // R1/beta3 "hrev54154", // R1/beta2 "hrev52295", // R1/beta1 "hrev44702", // R1/alpha4 NULL }; struct utsname uts; (void)uname(&uts); for (const char **s = badversions; *s; s++) if (! strncmp(uts.version, *s, strlen(*s))) return 0; return 1; } pcap_t * pcapint_create_interface(const char *device, char *errorBuffer) { if (validate_ifname(device, errorBuffer) < 0) return NULL; if (! can_be_bound(device)) { snprintf(errorBuffer, PCAP_ERRBUF_SIZE, "Interface \"%s\" does not support capturing traffic.", device); return NULL; } pcap_t* handle = PCAP_CREATE_COMMON(errorBuffer, struct pcap_haiku); if (handle == NULL) return NULL; handle->activate_op = pcap_activate_haiku; struct pcap_haiku *handlep = (struct pcap_haiku *)handle->priv; handlep->aux_socket = -1; strcpy(handlep->ifreq.ifr_name, device); return handle; } static int get_if_flags(const char *name, bpf_u_int32 *flags, char *errbuf) { if (validate_ifname(name, errbuf) < 0) return PCAP_ERROR; if (*flags & PCAP_IF_LOOPBACK || ! strncmp(name, "tun", strlen("tun")) || ! strncmp(name, "tap", strlen("tap"))) { /* * Loopback devices aren't wireless, and "connected"/ * "disconnected" doesn't apply to them. * * Neither does it to tunnel interfaces. A tun mode tunnel * can be identified by the IFT_TUNNEL value, but tap mode * tunnels and Ethernet interfaces both use IFT_ETHER, so let's * use the interface name prefix until there is a better * solution. */ *flags |= PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE; return (0); } int fd = dgram_socket(AF_LINK, errbuf); if (fd < 0) return PCAP_ERROR; struct ifreq ifreq; strcpy(ifreq.ifr_name, name); if (ioctl_ifreq(fd, SIOCGIFFLAGS, "SIOCGIFFLAGS", &ifreq, errbuf) < 0) { close(fd); return PCAP_ERROR; } *flags |= (ifreq.ifr_flags & IFF_LINK) ? PCAP_IF_CONNECTION_STATUS_CONNECTED : PCAP_IF_CONNECTION_STATUS_DISCONNECTED; if (ioctl_ifreq(fd, SIOCGIFMEDIA, "SIOCGIFMEDIA", &ifreq, errbuf) < 0) { close(fd); return PCAP_ERROR; } if (IFM_TYPE(ifreq.ifr_media) == IFM_IEEE80211) *flags |= PCAP_IF_WIRELESS; close(fd); return (0); } int pcapint_platform_finddevs(pcap_if_list_t* _allDevices, char* errorBuffer) { return pcapint_findalldevs_interfaces(_allDevices, errorBuffer, can_be_bound, get_if_flags); } /* * Libpcap version string. */ const char * pcap_lib_version(void) { return (PCAP_VERSION_STRING); }