diff options
Diffstat (limited to 'pcap-haiku.c')
-rw-r--r-- | pcap-haiku.c | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/pcap-haiku.c b/pcap-haiku.c new file mode 100644 index 000000000000..6cb0e71a1f68 --- /dev/null +++ b/pcap-haiku.c @@ -0,0 +1,504 @@ +/* + * 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 <config.h> +#include "pcap-int.h" + +#include <OS.h> + +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/utsname.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/if_media.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdint.h> + + +// 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); +} |