| /* |
| * 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); |
| } |