]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
dhcp: Port the plugin to FreeBSD/macOS
authorDan James <sddj@me.com>
Fri, 24 Nov 2023 15:54:04 +0000 (10:54 -0500)
committerTobias Brunner <tobias@strongswan.org>
Mon, 19 Feb 2024 08:17:53 +0000 (09:17 +0100)
This also refactors the BPF handling so it can be shared between the
dhcp and farp plugins.  The latter is adapted accordingly.

Closes strongswan/strongswan#2047

Co-authored-by: Tobias Brunner <tobias@strongswan.org>
configure.ac
src/libcharon/Makefile.am
src/libcharon/network/pf_handler.c [new file with mode: 0644]
src/libcharon/network/pf_handler.h [new file with mode: 0644]
src/libcharon/plugins/dhcp/dhcp_socket.c
src/libcharon/plugins/farp/farp_spoofer.c

index 32eae7889de5ecea14da60b7bcd0f974b65f3611..9ffb9be5e217d9b606e34757279ff5633334e36f 100644 (file)
@@ -1853,6 +1853,7 @@ AM_CONDITIONAL(USE_ATTR, test x$attr = xtrue)
 AM_CONDITIONAL(USE_ATTR_SQL, test x$attr_sql = xtrue)
 AM_CONDITIONAL(USE_COUNTERS, test x$counters = xtrue)
 AM_CONDITIONAL(USE_SELINUX, test x$selinux = xtrue)
+AM_CONDITIONAL(USE_PF_HANDLER, test x$dhcp = xtrue -o x$farp = xtrue)
 
 #  other options
 # ---------------
index fd88237fee40896c679b15955dd2adba369185e3..3f5a20e61ed60fba8b72c7a0d38e50d9ca7417c1 100644 (file)
@@ -147,6 +147,11 @@ if USE_SYSLOG
     bus/listeners/sys_logger.c bus/listeners/sys_logger.h
 endif
 
+if USE_PF_HANDLER
+  libcharon_la_SOURCES += \
+    network/pf_handler.c network/pf_handler.h
+endif
+
 daemon.lo :            $(top_builddir)/config.status
 
 AM_CPPFLAGS = \
diff --git a/src/libcharon/network/pf_handler.c b/src/libcharon/network/pf_handler.c
new file mode 100644 (file)
index 0000000..5dab3f0
--- /dev/null
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2021-2024 Tobias Brunner
+ * Copyright (C) 2020-2023 Dan James <sddj@me.com>
+ * Copyright (C) 2010 Martin Willi
+ *
+ * Copyright (C) secunet Security Networks AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+#include "pf_handler.h"
+
+#include <library.h>
+#include <unistd.h>
+
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/filter.h>
+#else
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
+
+#include <errno.h>
+#include <net/ethernet.h>
+#include <sys/ioctl.h>
+
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+
+/**
+ * Number of interfaces to cache in LFU cache
+ */
+#define IFACE_CACHE_SIZE       8
+
+/**
+ * Data for a cached interface on which packets were received
+ */
+struct cached_iface_t {
+
+       /**
+        * Index of the interface
+        */
+       int if_index;
+
+       /**
+        * Name of the interface
+        */
+       char if_name[IFNAMSIZ];
+
+       /**
+        * Hardware (mac) address of the interface
+        */
+       u_char hwaddr[ETHER_ADDR_LEN];
+
+       /**
+        * Number of the times this info has been used, for LFU cache
+        */
+       int used;
+};
+
+typedef struct cached_iface_t cached_iface_t;
+
+#endif
+
+typedef struct private_pf_handler_t private_pf_handler_t;
+
+struct private_pf_handler_t {
+
+       /**
+        * Public interface
+        */
+       pf_handler_t public;
+
+       /**
+        * Name for this handler
+        */
+       const char *name;
+
+       /**
+        * Registered callback
+        */
+       pf_packet_handler_t handler;
+
+       /**
+        * Context to pass to callback
+        */
+       void *ctx;
+
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+
+       /**
+        * AF_PACKET receive socket
+        */
+       int receive;
+
+       /**
+        * Cache of frequently used interface information
+        */
+       cached_iface_t ifaces[IFACE_CACHE_SIZE];
+
+       /**
+        * Number of currently cached interface information
+        */
+       int cached;
+
+#else
+
+       /**
+        * BPF sockets (one per interface), pf_socket_t
+        */
+       linked_list_t *pf_sockets;
+
+#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
+};
+
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+
+/**
+ * Find the index of the slot that was least frequently used
+ */
+static int find_least_used_cache_entry(private_pf_handler_t *this)
+{
+       int i, idx = 0, least_used = 0;
+
+       if (this->cached < IFACE_CACHE_SIZE)
+       {
+               /* not all slots used, choose the next unused slot */
+               idx = this->cached++;
+       }
+       else
+       {
+               /* all slots in use, choose the one with the lowest usage */
+               for (i = 0; i < this->cached; i++)
+               {
+                       if (this->ifaces[i].used < least_used)
+                       {
+                               idx = i;
+                               least_used = this->ifaces[i].used;
+                       }
+               }
+       }
+       return idx;
+}
+
+/**
+ * Retrieve information about the interface on which a packet was received
+ */
+static cached_iface_t *find_interface(private_pf_handler_t *this, int fd,
+                                                                         int ifindex)
+{
+       struct ifreq req = {
+               .ifr_ifindex = ifindex,
+       };
+       int idx;
+
+       for (idx = 0; idx < this->cached; idx++)
+       {
+               if (this->ifaces[idx].if_index == ifindex)
+               {
+                       this->ifaces[idx].used++;
+                       return &this->ifaces[idx];
+               }
+       }
+
+       if (ioctl(fd, SIOCGIFNAME, &req) == 0 &&
+               ioctl(fd, SIOCGIFHWADDR, &req) == 0 &&
+               req.ifr_hwaddr.sa_family == ARPHRD_ETHER)
+       {
+               idx = find_least_used_cache_entry(this);
+
+               this->ifaces[idx].if_index = ifindex;
+               memcpy(this->ifaces[idx].if_name, req.ifr_name, IFNAMSIZ);
+               memcpy(this->ifaces[idx].hwaddr, req.ifr_hwaddr.sa_data, ETHER_ADDR_LEN);
+               this->ifaces[idx].used = 1;
+               return &this->ifaces[idx];
+       }
+       return NULL;
+}
+
+CALLBACK(receive_packet, bool,
+       private_pf_handler_t *this, int fd, watcher_event_t event)
+{
+       cached_iface_t *iface;
+       struct sockaddr_ll addr;
+       socklen_t addr_len = sizeof(addr);
+       uint8_t packet[1500];
+       ssize_t len;
+
+       len = recvfrom(fd, &packet, sizeof(packet), MSG_DONTWAIT,
+                                  (struct sockaddr*)&addr, &addr_len);
+
+       if (len >= 0)
+       {
+               iface = find_interface(this, fd, addr.sll_ifindex);
+               if (iface)
+               {
+                       this->handler(this->ctx, iface->if_name, iface->if_index,
+                                                 chunk_create(iface->hwaddr, ETHER_ADDR_LEN), fd,
+                                                 chunk_create(packet, len));
+               }
+       }
+       return TRUE;
+}
+
+METHOD(pf_handler_t, destroy, void,
+       private_pf_handler_t *this)
+{
+       if (this->receive >= 0)
+       {
+               lib->watcher->remove(lib->watcher, this->receive);
+               close(this->receive);
+               free(this);
+       }
+}
+
+/**
+ * Setup capturing via AF_PACKET socket
+ */
+static bool setup_internal(private_pf_handler_t *this, char *iface,
+                                                  struct sock_fprog *packet_filter)
+{
+       int protocol = strcmp(this->name, "ARP") ? ETH_P_IP : ETH_P_ARP;
+
+       this->receive = socket(AF_PACKET, SOCK_DGRAM, htons(protocol));
+       if (this->receive == -1)
+       {
+               DBG1(DBG_NET, "opening %s packet socket failed: %s", this->name,
+                        strerror(errno));
+               return FALSE;
+       }
+       if (setsockopt(this->receive, SOL_SOCKET, SO_ATTACH_FILTER, packet_filter,
+                                  sizeof(struct sock_fprog)) < 0)
+       {
+               DBG1(DBG_NET, "installing %s packet socket filter failed: %s",
+                        this->name, strerror(errno));
+               return FALSE;
+       }
+       if (iface && !bind_to_device(this->receive, iface))
+       {
+               return FALSE;
+       }
+       lib->watcher->add(lib->watcher, this->receive, WATCHER_READ,
+                                         receive_packet, this);
+       DBG2(DBG_NET, "listening for %s (protocol=0x%04x) requests on fd=%d",
+                this->name, protocol, this->receive);
+       return TRUE;
+}
+
+/*
+ * Described in header
+ */
+bool bind_to_device(int fd, char *iface)
+{
+       int status;
+       struct ifreq ifreq = {};
+
+       if (strlen(iface) > sizeof(ifreq.ifr_name))
+       {
+               DBG1(DBG_CFG, "name for interface too long: '%s'", iface);
+               return FALSE;
+       }
+       memcpy(ifreq.ifr_name, iface, sizeof(ifreq.ifr_name));
+       status = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq));
+       if (status)
+       {
+               DBG1(DBG_CFG, "binding socket to '%s' failed: %s",
+                        iface, strerror(errno));
+               return FALSE;
+       }
+       return TRUE;
+}
+
+#else /* !defined(__APPLE__) && !defined(__FreeBSD__) */
+
+/**
+ * A BPF socket is required for each interface.
+ */
+struct pf_socket_t {
+
+       /**
+        * Reference to the private packet filter handler
+        */
+       private_pf_handler_t *this;
+
+       /**
+        * The name of the interface
+        */
+       char *if_name;
+
+       /**
+        * Index of the interface
+        */
+       int if_index;
+
+       /**
+        * The Ethernet MAC address of the interface
+        */
+       chunk_t mac;
+
+       /**
+        * The IPv4 address of the interface
+        */
+       host_t *ipv4;
+
+       /**
+        * The BPF file descriptor for this interface
+        */
+       int fd;
+
+       /**
+        * The BPF packet buffer length as read from the BPF fd
+        */
+       size_t buflen;
+
+       /**
+        * An allocated buffer for receiving packets from BPF
+        */
+       uint8_t *bufdat;
+};
+
+typedef struct pf_socket_t pf_socket_t;
+
+/**
+ * Free resources used by a socket.
+ */
+CALLBACK(destroy_pf_socket, void,
+       pf_socket_t *socket)
+{
+       if (socket->fd >= 0)
+       {
+               lib->watcher->remove(lib->watcher, socket->fd);
+               close(socket->fd);
+       }
+       DESTROY_IF(socket->ipv4);
+       chunk_free(&socket->mac);
+       free(socket->bufdat);
+       free(socket->if_name);
+       free(socket);
+}
+
+/**
+ * Find the handler for the named interface, creating one if needed.
+ */
+static pf_socket_t *get_pf_socket(private_pf_handler_t *this, char *if_name)
+{
+       pf_socket_t *socket, *found = NULL;
+       enumerator_t *enumerator;
+
+       enumerator = this->pf_sockets->create_enumerator(this->pf_sockets);
+       while (enumerator->enumerate(enumerator, &socket))
+       {
+               if (streq(socket->if_name, if_name))
+               {
+                       found = socket;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       if (!found)
+       {
+               INIT(found,
+                        .this = this,
+                        .if_name = strdup(if_name),
+                        .fd = -1,
+               );
+               this->pf_sockets->insert_last(this->pf_sockets, found);
+       }
+       return found;
+}
+
+/**
+ * Find and open an available BPF device.
+ */
+static int bpf_open()
+{
+       static int no_cloning_bpf = 0;
+       /* enough space for: /dev/bpf000\0 */
+       char device[12];
+       int n = no_cloning_bpf ? 0 : -1;
+       int fd;
+
+       do
+       {
+               if (n < 0)
+               {
+                       snprintf(device, sizeof(device), "/dev/bpf");
+               }
+               else
+               {
+                       snprintf(device, sizeof(device), "/dev/bpf%d", n);
+               }
+
+               fd = open(device, O_RDWR);
+
+               if (n++ < 0 && fd < 0 && errno == ENOENT)
+               {
+                       no_cloning_bpf = 1;
+                       errno = EBUSY;
+               }
+       }
+       while (fd < 0 && errno == EBUSY && n < 1000);
+
+       return fd;
+}
+
+CALLBACK(handler_onpkt, bool,
+       pf_socket_t *socket, int fd, watcher_event_t event)
+{
+       struct bpf_hdr *bh;
+       void *a;
+       uint8_t *p = socket->bufdat;
+       ssize_t n;
+       size_t pktlen;
+
+       n = read(socket->fd, socket->bufdat, socket->buflen);
+       if (n <= 0)
+       {
+               DBG1(DBG_NET, "reading %s request from %s failed: %s",
+                        socket->this->name, socket->if_name, strerror(errno));
+               return FALSE;
+       }
+
+       while (p < socket->bufdat + n)
+       {
+               bh = (struct bpf_hdr*) p;
+               a = (void*)(p + bh->bh_hdrlen + sizeof(struct ether_header));
+               pktlen = bh->bh_caplen - sizeof(struct ether_header);
+
+               socket->this->handler(socket->this->ctx, socket->if_name,
+                                                         socket->if_index, socket->mac,
+                                                         socket->fd, chunk_create(a, pktlen));
+
+               p += BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen);
+       }
+       return TRUE;
+}
+
+/**
+ * Create and initialize a BPF socket for the interface specified in the given
+ * struct. This entails opening a BPF device, binding it to the interface,
+ * setting the packet filter, and allocating a buffer for receiving packets.
+ */
+static bool setup_pf_socket(pf_socket_t *socket, pf_program_t *program)
+{
+       struct ifreq req;
+       uint32_t disable = 1;
+       uint32_t enable = 1;
+       uint32_t dlt = 0;
+
+       snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", socket->if_name);
+
+       if ((socket->fd = bpf_open()) < 0)
+       {
+               DBG1(DBG_NET, "bpf_open(%s): %s", socket->if_name, strerror(errno));
+               return FALSE;
+       }
+
+       if (ioctl(socket->fd, BIOCSETIF, &req) < 0)
+       {
+               DBG1(DBG_NET, "BIOCSETIF(%s): %s", socket->if_name, strerror(errno));
+               return FALSE;
+       }
+
+       if (ioctl(socket->fd, BIOCSHDRCMPLT, &enable) < 0)
+       {
+               DBG1(DBG_NET, "BIOCSHDRCMPLT(%s): %s", socket->if_name, strerror(errno));
+               return FALSE;
+       }
+
+       if (ioctl(socket->fd, BIOCSSEESENT, &disable) < 0)
+       {
+               DBG1(DBG_NET, "BIOCSSEESENT(%s): %s", socket->if_name, strerror(errno));
+               return FALSE;
+       }
+
+       if (ioctl(socket->fd, BIOCIMMEDIATE, &enable) < 0)
+       {
+               DBG1(DBG_NET, "BIOCIMMEDIATE(%s): %s", socket->if_name, strerror(errno));
+               return FALSE;
+       }
+
+       if (ioctl(socket->fd, BIOCGDLT, &dlt) < 0)
+       {
+               DBG1(DBG_NET, "BIOCGDLT(%s): %s", socket->if_name, strerror(errno));
+               return FALSE;
+       }
+       else if (dlt != DLT_EN10MB)
+       {
+               return FALSE;
+       }
+
+       if (ioctl(socket->fd, BIOCSETF, program) < 0)
+       {
+               DBG1(DBG_NET, "BIOCSETF(%s): %s", socket->if_name, strerror(errno));
+               return FALSE;
+       }
+
+       if (ioctl(socket->fd, BIOCGBLEN, &socket->buflen) < 0)
+       {
+               DBG1(DBG_NET, "BIOCGBLEN(%s): %s", socket->if_name, strerror(errno));
+               return FALSE;
+       }
+       socket->bufdat = malloc(socket->buflen);
+
+       lib->watcher->add(lib->watcher, socket->fd, WATCHER_READ,
+                                         handler_onpkt, socket);
+       return TRUE;
+}
+
+/**
+ * Create a socket for each BPF capable interface.  The interface must have an
+ * Ethernet MAC address, an IPv4 address, and use an Ethernet data link layer.
+ */
+static bool setup_internal(private_pf_handler_t *this, char *iface,
+                                                  pf_program_t *program)
+{
+       struct ifaddrs *ifas;
+       struct ifaddrs *ifa;
+       struct sockaddr_dl *dl;
+       pf_socket_t *socket;
+       enumerator_t *enumerator;
+       host_t *ipv4;
+
+       if (getifaddrs(&ifas) < 0)
+       {
+               DBG1(DBG_NET, "%s cannot find interfaces: %s", this->name, strerror(errno));
+               return FALSE;
+       }
+       this->pf_sockets = linked_list_create();
+       for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next)
+       {
+               switch (ifa->ifa_addr->sa_family)
+               {
+                       case AF_LINK:
+                               dl = (struct sockaddr_dl *)ifa->ifa_addr;
+                               if (dl->sdl_alen == ETHER_ADDR_LEN)
+                               {
+                                       socket = get_pf_socket(this, ifa->ifa_name);
+                                       socket->if_index = dl->sdl_index;
+                                       socket->mac = chunk_clone(chunk_create(LLADDR(dl),
+                                                                                                                       dl->sdl_alen));
+                               }
+                               break;
+                       case AF_INET:
+                               ipv4 = host_create_from_sockaddr(ifa->ifa_addr);
+                               if (ipv4 && !ipv4->is_anyaddr(ipv4))
+                               {
+                                       socket = get_pf_socket(this, ifa->ifa_name);
+                                       if (!socket->ipv4)
+                                       {
+                                               socket->ipv4 = ipv4->clone(ipv4);
+                                       }
+                               }
+                               DESTROY_IF(ipv4);
+                               break;
+                       default:
+                               break;
+               }
+       }
+       freeifaddrs(ifas);
+
+       enumerator = this->pf_sockets->create_enumerator(this->pf_sockets);
+       while (enumerator->enumerate(enumerator, &socket))
+       {
+               if (socket->mac.ptr && socket->ipv4 &&
+                       (!iface || streq(socket->if_name, iface)) &&
+                       setup_pf_socket(socket, program))
+               {
+                       DBG2(DBG_NET, "listening for %s requests on %s (%H, %#B)",
+                                this->name, socket->if_name, socket->ipv4, &socket->mac);
+               }
+               else
+               {
+                       this->pf_sockets->remove_at(this->pf_sockets, enumerator);
+                       destroy_pf_socket(socket);
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       return this->pf_sockets->get_count(this->pf_sockets) > 0;
+}
+
+METHOD(pf_handler_t, destroy, void,
+       private_pf_handler_t *this)
+{
+       DESTROY_FUNCTION_IF(this->pf_sockets, destroy_pf_socket);
+       free(this);
+}
+
+/*
+ * Described in header
+ */
+bool bind_to_device(int fd, char *iface)
+{
+#if defined(__FreeBSD__)
+       DBG1(DBG_CFG, "binding socket to '%s' failed: IP_SENDIF not implemented yet.", iface);
+       return FALSE;
+#else /* defined(__FreeBSD__) */
+       unsigned int idx = if_nametoindex(iface);
+       if (setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx)) == -1)
+       {
+               DBG1(DBG_CFG, "binding socket to '%s' failed: %s",
+                        iface, strerror(errno));
+               return FALSE;
+       }
+       return TRUE;
+#endif /* defined(__FreeBSD__) */
+}
+
+#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
+
+/*
+ * Described in header
+ */
+pf_handler_t *pf_handler_create(const char *name, char *iface,
+                                                               pf_packet_handler_t handler, void *ctx,
+                                                               pf_program_t *program)
+{
+       private_pf_handler_t *this;
+
+       INIT(this,
+                .public = {
+                       .destroy = _destroy,
+                },
+                .name = name,
+                .handler = handler,
+                .ctx = ctx,
+       );
+
+       if (!setup_internal(this, iface, program))
+       {
+               destroy(this);
+               return NULL;
+       }
+       return &this->public;
+}
diff --git a/src/libcharon/network/pf_handler.h b/src/libcharon/network/pf_handler.h
new file mode 100644 (file)
index 0000000..6eee3d4
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 Tobias Brunner
+ * Copyright (C) 2020-2023 Dan James <sddj@me.com>
+ *
+ * Copyright (C) secunet Security Networks AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef PF_HANDLER_H_
+#define PF_HANDLER_H_
+
+#include <sys/types.h>
+#include <utils/chunk.h>
+
+typedef struct pf_handler_t pf_handler_t;
+
+/**
+ * BPF implementation for different platforms
+ */
+struct pf_handler_t {
+
+       /**
+        * Destroy this instance.
+        */
+       void (*destroy)(pf_handler_t *this);
+};
+
+/**
+ * Callback that's called for received packets.
+ *
+ * @param ctx          context as passed in the constructor
+ * @param if_name      name of the interface on which the packet was received
+ * @param if_index     index of the interface on which the packet was received
+ * @param mac          MAC address of the interface on which the packet was received
+ * @param fd           file descriptor of the receiving socket (may be used to send
+ *                                     a response)
+ * @param packet       the received packet
+ */
+typedef void (*pf_packet_handler_t)(void *ctx, char *if_name, int if_index,
+                                                                       chunk_t mac, int fd, chunk_t packet);
+
+/**
+ * Type for BFP programs on different platforms
+ */
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+typedef struct sock_fprog pf_program_t;
+#else
+typedef struct bpf_program pf_program_t;
+#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
+
+/**
+ * Create a pf_handler_t instance.
+ *
+ * @param name         name to identify this handler ("ARP" is treated specially)
+ * @param iface                optional interface to limit capturing to
+ * @param handler      handler for received packets
+ * @param ctx          context passed to handler
+ * @param program      BPF filter program
+ */
+pf_handler_t *pf_handler_create(const char *name, char *iface,
+                                                               pf_packet_handler_t handler, void *ctx,
+                                                               pf_program_t *program);
+
+/**
+ * Bind a socket to a particular network interface
+ *
+ * @param fd           file descriptor of the socket
+ * @param iface                name of the interface
+ * @return                     whether the socket was successfully bound
+ */
+bool bind_to_device(int fd, char *iface);
+
+#endif /** PF_HANDLER_H_ */
index d58e5edbea0d3100536ec855a4996018d5eb7476..d144e27959415b068b1334d1edc5281141ac5934 100644 (file)
@@ -1,5 +1,6 @@
 /*
- * Copyright (C) 2012-2018 Tobias Brunner
+ * Copyright (C) 2023 Dan James <sddj@me.com>
+ * Copyright (C) 2012-2024 Tobias Brunner
  * Copyright (C) 2010 Martin Willi
  *
  * Copyright (C) secunet Security Networks AG
 
 #include <unistd.h>
 #include <errno.h>
-#include <string.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
 #include <netinet/udp.h>
+
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+#include <string.h>
 #include <linux/if_arp.h>
-#include <linux/if_ether.h>
 #include <linux/filter.h>
+#else
+#include <net/bpf.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
 
 #include <collections/linked_list.h>
 #include <utils/identification.h>
@@ -35,6 +43,7 @@
 
 #include <daemon.h>
 #include <processing/jobs/callback_job.h>
+#include <network/pf_handler.h>
 
 #define DHCP_SERVER_PORT 67
 #define DHCP_CLIENT_PORT 68
@@ -93,9 +102,9 @@ struct private_dhcp_socket_t {
        int send;
 
        /**
-        * DHCP receive socket
+        * BPF handler to receive DHCP messages
         */
-       int receive;
+       pf_handler_t *pf_handler;
 
        /**
         * Do we use per-identity or random leases (and MAC addresses)
@@ -178,7 +187,7 @@ typedef struct __attribute__((packed)) {
        uint32_t your_address;
        uint32_t server_address;
        uint32_t gateway_address;
-       char client_hw_addr[6];
+       uint8_t client_hw_addr[6];
        char client_hw_padding[10];
        char server_hostname[64];
        char boot_filename[128];
@@ -199,14 +208,14 @@ static inline bool is_broadcast(host_t *host)
 /**
  * Prepare a DHCP message for a given transaction
  */
-static int prepare_dhcp(private_dhcp_socket_t *this,
+static size_t prepare_dhcp(private_dhcp_socket_t *this,
                                                dhcp_transaction_t *transaction,
                                                dhcp_message_type_t type, dhcp_t *dhcp)
 {
        chunk_t chunk;
        identification_t *identity;
        dhcp_option_t *option;
-       int optlen = 0, remaining;
+       size_t optlen = 0, remaining;
        uint32_t id;
 
        memset(dhcp, 0, sizeof(*dhcp));
@@ -281,7 +290,7 @@ static int prepare_dhcp(private_dhcp_socket_t *this,
  * Send a DHCP message with given options length
  */
 static bool send_dhcp(private_dhcp_socket_t *this,
-                                         dhcp_transaction_t *transaction, dhcp_t *dhcp, int optlen)
+                                         dhcp_transaction_t *transaction, dhcp_t *dhcp, size_t optlen)
 {
        host_t *dst;
        ssize_t len;
@@ -304,7 +313,7 @@ static bool discover(private_dhcp_socket_t *this,
 {
        dhcp_option_t *option;
        dhcp_t dhcp;
-       int optlen;
+       size_t optlen;
 
        optlen = prepare_dhcp(this, transaction, DHCP_DISCOVER, &dhcp);
 
@@ -340,7 +349,7 @@ static bool request(private_dhcp_socket_t *this,
        dhcp_t dhcp;
        host_t *offer, *server;
        chunk_t chunk;
-       int optlen;
+       size_t optlen;
 
        optlen = prepare_dhcp(this, transaction, DHCP_REQUEST, &dhcp);
 
@@ -483,7 +492,7 @@ METHOD(dhcp_socket_t, release, void,
        dhcp_t dhcp;
        host_t *release, *server;
        chunk_t chunk;
-       int optlen;
+       size_t optlen;
 
        optlen = prepare_dhcp(this, transaction, DHCP_RELEASE, &dhcp);
 
@@ -517,7 +526,7 @@ METHOD(dhcp_socket_t, release, void,
 /**
  * Handle a DHCP OFFER
  */
-static void handle_offer(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
+static void handle_offer(private_dhcp_socket_t *this, dhcp_t *dhcp, size_t optlen)
 {
        dhcp_transaction_t *transaction = NULL;
        enumerator_t *enumerator;
@@ -551,7 +560,7 @@ static void handle_offer(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
 
        if (transaction)
        {
-               int optsize, optpos = 0, pos;
+               size_t optsize, optpos = 0, pos;
                dhcp_option_t *option;
 
                while (optlen > sizeof(dhcp_option_t))
@@ -596,7 +605,7 @@ static void handle_offer(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
 /**
  * Handle a DHCP ACK
  */
-static void handle_ack(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
+static void handle_ack(private_dhcp_socket_t *this, dhcp_t *dhcp)
 {
        dhcp_transaction_t *transaction;
        enumerator_t *enumerator;
@@ -618,33 +627,31 @@ static void handle_ack(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
 }
 
 /**
- * Receive DHCP responses
+ * Complete DHCP packet
  */
-static bool receive_dhcp(private_dhcp_socket_t *this, int fd,
-                                                watcher_event_t event)
+struct __attribute__((packed)) dhcp_packet_t {
+       struct ip ip;
+       struct udphdr udp;
+       dhcp_t dhcp;
+};
+typedef struct dhcp_packet_t dhcp_packet_t;
+
+CALLBACK(receive_dhcp, void,
+       private_dhcp_socket_t *this, char *if_name, int if_index, chunk_t mac,
+       int fd, chunk_t pkt)
 {
-       struct sockaddr_ll addr;
-       socklen_t addr_len = sizeof(addr);
-       struct __attribute__((packed)) {
-               struct iphdr ip;
-               struct udphdr udp;
-               dhcp_t dhcp;
-       } packet;
-       int optlen, origoptlen, optsize, optpos = 0;
-       ssize_t len;
+       dhcp_packet_t *packet = (dhcp_packet_t*)pkt.ptr;
+       size_t optlen, origoptlen, optpos = 0, optsize;
        dhcp_option_t *option;
 
-       len = recvfrom(fd, &packet, sizeof(packet), MSG_DONTWAIT,
-                                       (struct sockaddr*)&addr, &addr_len);
-
-       if (len >= sizeof(struct iphdr) + sizeof(struct udphdr) +
+       if (pkt.len >= sizeof(struct ip) + sizeof(struct udphdr) +
                offsetof(dhcp_t, options))
        {
-               origoptlen = optlen = len - sizeof(struct iphdr) +
-                                        sizeof(struct udphdr) + offsetof(dhcp_t, options);
+               origoptlen = optlen = pkt.len - sizeof(struct ip) +
+                                                         sizeof(struct udphdr) + offsetof(dhcp_t, options);
                while (optlen > sizeof(dhcp_option_t))
                {
-                       option = (dhcp_option_t*)&packet.dhcp.options[optpos];
+                       option = (dhcp_option_t*)&packet->dhcp.options[optpos];
                        optsize = sizeof(dhcp_option_t) + option->len;
                        if (option->type == DHCP_OPTEND || optlen < optsize)
                        {
@@ -655,10 +662,10 @@ static bool receive_dhcp(private_dhcp_socket_t *this, int fd,
                                switch (option->data[0])
                                {
                                        case DHCP_OFFER:
-                                               handle_offer(this, &packet.dhcp, origoptlen);
+                                               handle_offer(this, &packet->dhcp, origoptlen);
                                                break;
                                        case DHCP_ACK:
-                                               handle_ack(this, &packet.dhcp, origoptlen);
+                                               handle_ack(this, &packet->dhcp);
                                        default:
                                                break;
                                }
@@ -668,7 +675,6 @@ static bool receive_dhcp(private_dhcp_socket_t *this, int fd,
                        optpos += optsize;
                }
        }
-       return TRUE;
 }
 
 METHOD(dhcp_socket_t, destroy, void,
@@ -682,11 +688,6 @@ METHOD(dhcp_socket_t, destroy, void,
        {
                close(this->send);
        }
-       if (this->receive > 0)
-       {
-               lib->watcher->remove(lib->watcher, this->receive);
-               close(this->receive);
-       }
        this->mutex->destroy(this->mutex);
        this->condvar->destroy(this->condvar);
        this->discover->destroy_offset(this->discover,
@@ -695,33 +696,12 @@ METHOD(dhcp_socket_t, destroy, void,
                                                                offsetof(dhcp_transaction_t, destroy));
        this->completed->destroy_offset(this->completed,
                                                                offsetof(dhcp_transaction_t, destroy));
+       DESTROY_IF(this->pf_handler);
        DESTROY_IF(this->rng);
        DESTROY_IF(this->dst);
        free(this);
 }
 
-/**
- * Bind a socket to a particular interface name
- */
-static bool bind_to_device(int fd, char *iface)
-{
-       struct ifreq ifreq = {};
-
-       if (strlen(iface) > sizeof(ifreq.ifr_name))
-       {
-               DBG1(DBG_CFG, "name for DHCP interface too long: '%s'", iface);
-               return FALSE;
-       }
-       memcpy(ifreq.ifr_name, iface, sizeof(ifreq.ifr_name));
-       if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)))
-       {
-               DBG1(DBG_CFG, "binding DHCP socket to '%s' failed: %s",
-                        iface, strerror(errno));
-               return FALSE;
-       }
-       return TRUE;
-}
-
 /**
  * See header
  */
@@ -738,29 +718,26 @@ dhcp_socket_t *dhcp_socket_create()
        socklen_t addr_len;
        char *iface;
        int on = 1, rcvbuf = 0;
+
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+       const size_t skip_ip4 = sizeof(struct iphdr);
+       const size_t skip_udp = skip_ip4 + sizeof(struct udphdr);
        struct sock_filter dhcp_filter_code[] = {
-               BPF_STMT(BPF_LD+BPF_B+BPF_ABS,
-                                offsetof(struct iphdr, protocol)),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, offsetof(struct iphdr, protocol)),
                BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_UDP, 0, 16),
-               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, sizeof(struct iphdr) +
-                                offsetof(struct udphdr, source)),
+               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, skip_ip4 + offsetof(struct udphdr, source)),
                BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_SERVER_PORT, 0, 14),
-               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, sizeof(struct iphdr) +
-                                offsetof(struct udphdr, dest)),
+               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, skip_ip4 + offsetof(struct udphdr, dest)),
                BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_CLIENT_PORT, 2, 0),
                BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_SERVER_PORT, 1, 0),
                BPF_JUMP(BPF_JMP+BPF_JA, 10, 0, 0),
-               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, sizeof(struct iphdr) +
-                                sizeof(struct udphdr) + offsetof(dhcp_t, opcode)),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, skip_udp + offsetof(dhcp_t, opcode)),
                BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, BOOTREPLY, 0, 8),
-               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, sizeof(struct iphdr) +
-                                sizeof(struct udphdr) + offsetof(dhcp_t, hw_type)),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, skip_udp + offsetof(dhcp_t, hw_type)),
                BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARPHRD_ETHER, 0, 6),
-               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, sizeof(struct iphdr) +
-                                sizeof(struct udphdr) + offsetof(dhcp_t, hw_addr_len)),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, skip_udp + offsetof(dhcp_t, hw_addr_len)),
                BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 6, 0, 4),
-               BPF_STMT(BPF_LD+BPF_W+BPF_ABS, sizeof(struct iphdr) +
-                                sizeof(struct udphdr) + offsetof(dhcp_t, magic_cookie)),
+               BPF_STMT(BPF_LD+BPF_W+BPF_ABS, skip_udp + offsetof(dhcp_t, magic_cookie)),
                BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x63825363, 0, 2),
                BPF_STMT(BPF_LD+BPF_W+BPF_LEN, 0),
                BPF_STMT(BPF_RET+BPF_A, 0),
@@ -770,6 +747,38 @@ dhcp_socket_t *dhcp_socket_create()
                sizeof(dhcp_filter_code) / sizeof(struct sock_filter),
                dhcp_filter_code,
        };
+#else
+       const size_t skip_eth = sizeof(struct ether_header);
+       const size_t skip_ip4 = skip_eth + sizeof(struct ip);
+       const size_t skip_udp = skip_ip4 + sizeof(struct udphdr);
+       struct bpf_insn instructions[] = {
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, skip_eth + offsetof(struct ip, ip_p)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_UDP, 0, 16),
+               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, skip_ip4 + offsetof(struct udphdr, uh_sport)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_SERVER_PORT, 0, 14),
+               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, skip_ip4 + offsetof(struct udphdr, uh_dport)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_CLIENT_PORT, 2, 0),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_SERVER_PORT, 1, 0),
+               BPF_JUMP(BPF_JMP+BPF_JA, 10, 0, 0),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, skip_udp + offsetof(dhcp_t, opcode)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, BOOTREPLY, 0, 8),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, skip_udp + offsetof(dhcp_t, hw_type)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARPHRD_ETHER, 0, 6),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, skip_udp + offsetof(dhcp_t, hw_addr_len)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 6, 0, 4),
+               BPF_STMT(BPF_LD+BPF_W+BPF_ABS, skip_udp + offsetof(dhcp_t, magic_cookie)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x63825363, 0, 2),
+               BPF_STMT(BPF_LD+BPF_W+BPF_LEN, 0),
+               BPF_STMT(BPF_RET+BPF_A, 0),
+               BPF_STMT(BPF_RET+BPF_K, 0),
+       };
+       struct bpf_program dhcp_filter = {
+               sizeof(instructions) / sizeof(struct bpf_insn),
+               &instructions[0],
+       };
+       /* 0 receive buffer is not accepted */
+       rcvbuf = 1;
+#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
 
        INIT(this,
                .public = {
@@ -864,25 +873,16 @@ dhcp_socket_t *dhcp_socket_create()
                return NULL;
        }
 
-       this->receive = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
-       if (this->receive == -1)
+       this->pf_handler = pf_handler_create("DHCP", iface, receive_dhcp, this,
+                                                                                &dhcp_filter);
+       if (!this->pf_handler)
        {
-               DBG1(DBG_NET, "opening DHCP receive socket failed: %s", strerror(errno));
-               destroy(this);
-               return NULL;
-       }
-       if (setsockopt(this->receive, SOL_SOCKET, SO_ATTACH_FILTER,
-                                  &dhcp_filter, sizeof(dhcp_filter)) < 0)
-       {
-               DBG1(DBG_CFG, "installing DHCP socket filter failed: %s",
-                        strerror(errno));
                destroy(this);
                return NULL;
        }
        if (iface)
        {
-               if (!bind_to_device(this->send, iface) ||
-                       !bind_to_device(this->receive, iface))
+               if (!bind_to_device(this->send, iface))
                {
                        destroy(this);
                        return NULL;
@@ -908,8 +908,5 @@ dhcp_socket_t *dhcp_socket_create()
                }
        }
 
-       lib->watcher->add(lib->watcher, this->receive, WATCHER_READ,
-                                         (watcher_cb_t)receive_dhcp, this);
-
        return &this->public;
 }
index 2493b2da3de8807a9f4e5c570e7db5bc62662ca8..65451263f5fd21e2d8728e349f7299a83d2574c1 100644 (file)
@@ -1,5 +1,6 @@
 /*
- * Copyright (C) 2021 Tobias Brunner
+ * Copyright (C) 2021-2024 Tobias Brunner
+ * Copyright (C) 2020-2023 Dan James <sddj@me.com>
  * Copyright (C) 2010 Martin Willi
  *
  * Copyright (C) secunet Security Networks AG
  * for more details.
  */
 
-/*
- * For the Apple BPF implementation.
- *
- * Copyright (C) 2020 Dan James <sddj@me.com>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
 #include "farp_spoofer.h"
 
 #include <errno.h>
 #include <linux/if_ether.h>
 #include <linux/filter.h>
 #else
-#include <fcntl.h>
-#include <ifaddrs.h>
 #include <net/bpf.h>
-#include <net/ethernet.h>
-#include <net/if.h>
 #include <net/if_arp.h>
 #include <net/if_dl.h>
 #endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
 
+#include <net/ethernet.h>
 #include <daemon.h>
 #include <threading/thread.h>
 #include <processing/jobs/callback_job.h>
+#include <network/pf_handler.h>
 
 typedef struct private_farp_spoofer_t private_farp_spoofer_t;
 
@@ -81,17 +56,10 @@ struct private_farp_spoofer_t {
         */
        farp_listener_t *listener;
 
-#if !defined(__APPLE__) && !defined(__FreeBSD__)
-       /**
-        * RAW socket for ARP requests
-        */
-       int skt;
-#else
        /**
-        * Linked list of interface handlers
+        * BPF handler for ARP requests
         */
-       linked_list_t *handlers;
-#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
+       pf_handler_t *pf_handler;
 };
 
 /**
@@ -103,177 +71,58 @@ typedef struct __attribute__((packed)) {
        uint8_t hardware_size;
        uint8_t protocol_size;
        uint16_t opcode;
-       uint8_t sender_mac[6];
+       uint8_t sender_mac[ETHER_ADDR_LEN];
        uint8_t sender_ip[4];
-       uint8_t target_mac[6];
+       uint8_t target_mac[ETHER_ADDR_LEN];
        uint8_t target_ip[4];
 } arp_t;
 
 #if !defined(__APPLE__) && !defined(__FreeBSD__)
+
 /**
  * Send faked ARP response
  */
-static void send_arp(private_farp_spoofer_t *this,
-                                        arp_t *arp, struct sockaddr_ll *addr)
+static void send_arp(char *if_name, int if_index, chunk_t mac, int fd,
+                                        arp_t *arp, host_t *sender, host_t *target)
 {
-       struct ifreq req;
+       struct sockaddr_ll addr = {
+               .sll_family = AF_PACKET,
+               .sll_protocol = htons(ETH_P_ARP),
+               .sll_ifindex = if_index,
+               .sll_halen = ETHER_ADDR_LEN,
+       };
        char tmp[4];
-
-       req.ifr_ifindex = addr->sll_ifindex;
-       if (ioctl(this->skt, SIOCGIFNAME, &req) == 0 &&
-               ioctl(this->skt, SIOCGIFHWADDR, &req) == 0 &&
-               req.ifr_hwaddr.sa_family == ARPHRD_ETHER)
-       {
-               memcpy(arp->target_mac, arp->sender_mac, 6);
-               memcpy(arp->sender_mac, req.ifr_hwaddr.sa_data, 6);
-
-               memcpy(tmp, arp->sender_ip, 4);
-               memcpy(arp->sender_ip, arp->target_ip, 4);
-               memcpy(arp->target_ip, tmp, 4);
-
-               arp->opcode = htons(ARPOP_REPLY);
-
-               sendto(this->skt, arp, sizeof(*arp), 0,
-                          (struct sockaddr*)addr, sizeof(*addr));
-       }
-}
-
-CALLBACK(receive_arp, bool,
-       private_farp_spoofer_t *this, int fd, watcher_event_t event)
-{
-       struct sockaddr_ll addr;
-       socklen_t addr_len = sizeof(addr);
-       arp_t arp;
        ssize_t len;
-       host_t *local, *remote;
 
-       len = recvfrom(this->skt, &arp, sizeof(arp), MSG_DONTWAIT,
-                                  (struct sockaddr*)&addr, &addr_len);
-       if (len == sizeof(arp))
-       {
-               local = host_create_from_chunk(AF_INET,
-                                                                       chunk_create((char*)&arp.sender_ip, 4), 0);
-               remote = host_create_from_chunk(AF_INET,
-                                                                       chunk_create((char*)&arp.target_ip, 4), 0);
-               if (this->listener->has_tunnel(this->listener, local, remote))
-               {
-                       send_arp(this, &arp, &addr);
-               }
-               local->destroy(local);
-               remote->destroy(remote);
-       }
+#if DEBUG_LEVEL >= 2
+       chunk_t sender_mac = chunk_create((u_char*)arp->sender_mac, ETHER_ADDR_LEN);
 
-       return TRUE;
-}
+       DBG2(DBG_NET, "replying with %#B to ARP request for %H from %H (%#B) on %s",
+                &mac, target, sender, &sender_mac, if_name);
+#endif
 
-METHOD(farp_spoofer_t, destroy, void,
-       private_farp_spoofer_t *this)
-{
-       lib->watcher->remove(lib->watcher, this->skt);
-       close(this->skt);
-       free(this);
-}
+       memcpy(addr.sll_addr, arp->sender_mac, ETHER_ADDR_LEN);
 
-/**
- * See header
- */
-farp_spoofer_t *farp_spoofer_create(farp_listener_t *listener)
-{
-       private_farp_spoofer_t *this;
-       struct sock_filter arp_request_filter_code[] = {
-               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, offsetof(arp_t, protocol_type)),
-               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETH_P_IP, 0, 9),
-               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, offsetof(arp_t, hardware_size)),
-               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 6, 0, 7),
-               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, offsetof(arp_t, protocol_size)),
-               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 4, 0, 5),
-               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, offsetof(arp_t, opcode)),
-               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARPOP_REQUEST, 0, 3),
-               BPF_STMT(BPF_LD+BPF_W+BPF_LEN, 0),
-               BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, sizeof(arp_t), 0, 1),
-               BPF_STMT(BPF_RET+BPF_K, sizeof(arp_t)),
-               BPF_STMT(BPF_RET+BPF_K, 0),
-       };
-       struct sock_fprog arp_request_filter = {
-               sizeof(arp_request_filter_code) / sizeof(struct sock_filter),
-               arp_request_filter_code,
-       };
+       memcpy(arp->target_mac, arp->sender_mac, 6);
+       memcpy(arp->sender_mac, mac.ptr, 6);
 
-       INIT(this,
-               .public = {
-                       .destroy = _destroy,
-               },
-               .listener = listener,
-       );
+       memcpy(tmp, arp->sender_ip, 4);
+       memcpy(arp->sender_ip, arp->target_ip, 4);
+       memcpy(arp->target_ip, tmp, 4);
 
-       this->skt = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP));
-       if (this->skt == -1)
-       {
-               DBG1(DBG_NET, "opening ARP packet socket failed: %s", strerror(errno));
-               free(this);
-               return NULL;
-       }
+       arp->opcode = htons(ARPOP_REPLY);
 
-       if (setsockopt(this->skt, SOL_SOCKET, SO_ATTACH_FILTER,
-                                  &arp_request_filter, sizeof(arp_request_filter)) < 0)
+       len = sendto(fd, arp, sizeof(*arp), 0,
+                                (const struct sockaddr*)&addr, sizeof(addr));
+
+       if (len != sizeof(*arp))
        {
-               DBG1(DBG_NET, "installing ARP packet filter failed: %s",
-                        strerror(errno));
-               close(this->skt);
-               free(this);
-               return NULL;
+               DBG1(DBG_NET, "failed to send ARP reply: %s", strerror(errno));
        }
-
-       lib->watcher->add(lib->watcher, this->skt, WATCHER_READ, receive_arp, this);
-
-       return &this->public;
 }
 
 #else /* !defined(__APPLE__) && !defined(__FreeBSD__) */
 
-/**
- * A handler is required for each interface.
- */
-struct farp_handler_t {
-
-       /**
-        * Reference to the private farp spoofer.
-        */
-       private_farp_spoofer_t *this;
-
-       /**
-        * The name of the interface to be handled.
-        */
-       char *name;
-
-       /**
-        * The IPv4 address of this interface.
-        */
-       host_t *ipv4;
-
-       /**
-        * The Ethernet MAC address of this interface.
-        */
-       chunk_t mac;
-
-       /**
-        * The BPF file descriptor for this interface.
-        */
-       int fd;
-
-       /**
-        * The BPF packet buffer length as read from the BPF fd.
-        */
-       size_t buflen;
-
-       /**
-        * An allocated buffer for receiving packets from BPF.
-        */
-       uint8_t *bufdat;
-};
-
-typedef struct farp_handler_t farp_handler_t;
-
 /**
  * An Ethernet frame for an ARP packet.
  */
@@ -284,103 +133,24 @@ struct frame_t {
 
 typedef struct frame_t frame_t;
 
-/**
- * Find and open an available BPF device.
- */
-static int bpf_open()
-{
-       static int no_cloning_bpf = 0;
-       /* enough space for: /dev/bpf000\0 */
-       char device[12];
-       int n = no_cloning_bpf ? 0 : -1;
-       int fd;
-
-       do
-       {
-               if (n < 0)
-               {
-                       snprintf(device, sizeof(device), "/dev/bpf");
-               }
-               else
-               {
-                       snprintf(device, sizeof(device), "/dev/bpf%d", n);
-               }
-
-               fd = open(device, O_RDWR);
-
-               if (n++ < 0 && fd < 0 && errno == ENOENT)
-               {
-                       no_cloning_bpf = 1;
-                       errno = EBUSY;
-               }
-       }
-       while (fd < 0 && errno == EBUSY && n < 1000);
-
-       return fd;
-}
-
-/**
- * Free resources used by a handler.
- */
-static void handler_destroy(farp_handler_t *handler)
-{
-       if (handler->fd >= 0)
-       {
-               lib->watcher->remove(lib->watcher, handler->fd);
-               close(handler->fd);
-       }
-       DESTROY_IF(handler->ipv4);
-       chunk_free(&handler->mac);
-       free(handler->bufdat);
-       free(handler->name);
-       free(handler);
-}
-
-/**
- * Find the handler for the named interface, creating one if needed.
- */
-static farp_handler_t *get_handler(private_farp_spoofer_t* this,
-                                                                       char *interface_name)
-{
-       farp_handler_t *handler, *found = NULL;
-       enumerator_t *enumerator;
-
-       enumerator = this->handlers->create_enumerator(this->handlers);
-       while (enumerator->enumerate(enumerator, &handler))
-       {
-               if (streq(handler->name, interface_name))
-               {
-                       found = handler;
-                       break;
-               }
-       }
-       enumerator->destroy(enumerator);
-
-       if (!found)
-       {
-               INIT(found,
-                       .this = this,
-                       .name = strdup(interface_name),
-                       .fd = -1,
-               );
-               this->handlers->insert_last(this->handlers, found);
-       }
-       return found;
-}
-
 /**
  * Send an ARP response for the given ARP request.
  */
-static void handler_send(farp_handler_t *handler, arp_t *arpreq, host_t *lcl,
-                                                host_t *rmt)
+static void send_arp(char *if_name, int if_index, chunk_t mac, int fd,
+                                        const arp_t *arpreq, host_t *sender, host_t *target)
 {
        frame_t frame;
-       chunk_t mac;
        ssize_t n;
 
+#if DEBUG_LEVEL >= 2
+       chunk_t sender_mac = chunk_create((u_char*)arpreq->sender_mac, ETHER_ADDR_LEN);
+
+       DBG2(DBG_NET, "replying with %#B to ARP request for %H from %H (%#B) on %s",
+                &mac, target, sender, &sender_mac, if_name);
+#endif
+
        memcpy(frame.e.ether_dhost, arpreq->sender_mac, ETHER_ADDR_LEN);
-       mac = chunk_create(frame.e.ether_dhost, ETHER_ADDR_LEN);
-       memcpy(frame.e.ether_shost, handler->mac.ptr, ETHER_ADDR_LEN);
+       memcpy(frame.e.ether_shost, mac.ptr, ETHER_ADDR_LEN);
        frame.e.ether_type = htons(ETHERTYPE_ARP);
 
        frame.a.hardware_type = htons(1);
@@ -388,228 +158,50 @@ static void handler_send(farp_handler_t *handler, arp_t *arpreq, host_t *lcl,
        frame.a.hardware_size = arpreq->hardware_size;
        frame.a.protocol_size = arpreq->protocol_size;
        frame.a.opcode = htons(ARPOP_REPLY);
-       memcpy(frame.a.sender_mac, handler->mac.ptr, ETHER_ADDR_LEN);
+       memcpy(frame.a.sender_mac, mac.ptr, ETHER_ADDR_LEN);
        memcpy(frame.a.sender_ip, arpreq->target_ip, sizeof(arpreq->target_ip));
        memcpy(frame.a.target_mac, arpreq->sender_mac, sizeof(arpreq->sender_mac));
        memcpy(frame.a.target_ip, arpreq->sender_ip, sizeof(arpreq->sender_ip));
 
-       DBG2(DBG_NET, "replying to ARP request for %H from %H (%#B) on %s",
-                rmt, lcl, &mac, handler->name);
-
-       n = write(handler->fd, &frame, sizeof(frame));
+       n = write(fd, &frame, sizeof(frame));
        if (n != sizeof(frame))
        {
                DBG1(DBG_NET, "sending ARP reply failed: %s", strerror(errno));
        }
 }
 
-/**
- * Receive and examine the available ARP requests. If a tunnel exists, send an
- * ARP response back out the same interface.
- */
-CALLBACK(handler_onarp, bool,
-       farp_handler_t *handler, int fd, watcher_event_t event)
-{
-       struct bpf_hdr *bh;
-       arp_t *a;
-       host_t *lcl, *rmt;
-       uint8_t *p = handler->bufdat;
-       ssize_t n;
-
-       n = read(handler->fd, handler->bufdat, handler->buflen);
-       if (n <= 0)
-       {
-               DBG1(DBG_NET, "reading ARP request from %s failed: %s", handler->name,
-                        strerror(errno));
-               return FALSE;
-       }
-
-       while (p < handler->bufdat + n)
-       {
-               bh = (struct bpf_hdr*)p;
-               a = (arp_t*)(p + bh->bh_hdrlen + sizeof(struct ether_header));
-
-               lcl = host_create_from_chunk(AF_INET, chunk_create(a->sender_ip, 4), 0);
-               rmt = host_create_from_chunk(AF_INET, chunk_create(a->target_ip, 4), 0);
-               if (lcl && rmt &&
-                       handler->this->listener->has_tunnel(handler->this->listener,
-                                                                                               lcl, rmt))
-               {
-                       handler_send(handler, a, lcl, rmt);
-               }
-               DESTROY_IF(rmt);
-               DESTROY_IF(lcl);
-
-               p += BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen);
-       }
-       return TRUE;
-}
-
-/**
- * Create an initialize a BPF handler for the interface specified in the farp
- * handler. This entails opening a BPF device, binding it to the interface,
- * setting the packet filter, and allocating a buffer for receiving packets.
- */
-static bool setup_handler(private_farp_spoofer_t *this, farp_handler_t *handler)
-{
-       struct bpf_insn instructions[] = {
-               BPF_STMT(BPF_LD+BPF_W+BPF_LEN, 0),
-               BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K,
-                                sizeof(struct ether_header) + sizeof(arp_t), 0, 11),
-               BPF_STMT(BPF_LD+BPF_H+BPF_ABS,
-                                offsetof(struct  ether_header, ether_type)),
-               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_ARP, 0, 9),
-               BPF_STMT(BPF_LD+BPF_H+BPF_ABS,
-                                sizeof(struct ether_header) + offsetof(arp_t, protocol_type)),
-               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_IP, 0, 7),
-               BPF_STMT(BPF_LD+BPF_B+BPF_ABS,
-                                sizeof(struct ether_header) + offsetof(arp_t, hardware_size)),
-               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 6, 0, 5),
-               BPF_STMT(BPF_LD+BPF_B+BPF_ABS,
-                                sizeof(struct ether_header) + offsetof(arp_t, protocol_size)),
-               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 4, 0, 3),
-               BPF_STMT(BPF_LD+BPF_H+BPF_ABS,
-                                sizeof(struct ether_header) + offsetof(arp_t, opcode)),
-               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARPOP_REQUEST, 0, 1),
-               BPF_STMT(BPF_RET+BPF_K, 14 + sizeof(arp_t)),
-               BPF_STMT(BPF_RET+BPF_K, 0)
-       };
-       struct bpf_program program;
-       struct ifreq req;
-       uint32_t disable = 1;
-       uint32_t enable = 1;
-       uint32_t dlt = 0;
-
-       snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", handler->name);
-
-       if ((handler->fd = bpf_open()) < 0)
-       {
-               DBG1(DBG_NET, "bpf_open(%s): %s", handler->name, strerror(errno));
-               return FALSE;
-       }
-
-       if (ioctl(handler->fd, BIOCSETIF, &req) < 0)
-       {
-               DBG1(DBG_NET, "BIOCSETIF(%s): %s", handler->name, strerror(errno));
-               return FALSE;
-       }
-
-       if (ioctl(handler->fd, BIOCSHDRCMPLT, &enable) < 0)
-       {
-               DBG1(DBG_NET, "BIOCSHDRCMPLT(%s): %s", handler->name, strerror(errno));
-               return FALSE;
-       }
-
-       if (ioctl(handler->fd, BIOCSSEESENT, &disable) < 0)
-       {
-               DBG1(DBG_NET, "BIOCSSEESENT(%s): %s", handler->name, strerror(errno));
-               return FALSE;
-       }
-
-       if (ioctl(handler->fd, BIOCIMMEDIATE, &enable) < 0)
-       {
-               DBG1(DBG_NET, "BIOCIMMEDIATE(%s): %s", handler->name, strerror(errno));
-               return FALSE;
-       }
-
-       if (ioctl(handler->fd, BIOCGDLT, &dlt) < 0)
-       {
-               DBG1(DBG_NET, "BIOCGDLT(%s): %s", handler->name, strerror(errno));
-               return FALSE;
-       }
-       else if (dlt != DLT_EN10MB)
-       {
-               return FALSE;
-       }
-
-       program.bf_len = sizeof(instructions) / sizeof(struct bpf_insn);
-       program.bf_insns = &instructions[0];
-
-       if (ioctl(handler->fd, BIOCSETF, &program) < 0)
-       {
-               DBG1(DBG_NET, "BIOCSETF(%s): %s", handler->name, strerror(errno));
-               return FALSE;
-       }
-
-       if (ioctl(handler->fd, BIOCGBLEN, &handler->buflen) < 0)
-       {
-               DBG1(DBG_NET, "BIOCGBLEN(%s): %s", handler->name, strerror(errno));
-               return FALSE;
-       }
-       handler->bufdat = malloc(handler->buflen);
-
-       lib->watcher->add(lib->watcher, handler->fd, WATCHER_READ,
-                                         handler_onarp, handler);
-       return TRUE;
-}
+#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
 
-/**
- * Create a handler for each BPF capable interface. The interface must have an
- * Ethernet MAC address, an IPv4 address, and use an Ethernet data link layer.
- */
-static bool setup_handlers(private_farp_spoofer_t *this)
+CALLBACK(handle_arp_pkt, void,
+       private_farp_spoofer_t *this, char *if_name, int if_index, chunk_t mac,
+       int fd, chunk_t packet)
 {
-       struct ifaddrs *ifas;
-       struct ifaddrs *ifa;
-       struct sockaddr_dl *dl;
-       farp_handler_t* handler;
-       enumerator_t *enumerator;
-       host_t *ipv4;
-
-       if (getifaddrs(&ifas) < 0)
-       {
-               DBG1(DBG_NET, "farp cannot find interfaces: %s", strerror(errno));
-               return FALSE;
-       }
-       for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next)
-       {
-               switch (ifa->ifa_addr->sa_family)
-               {
-                       case AF_LINK:
-                               dl = (struct sockaddr_dl*)ifa->ifa_addr;
-                               if (dl->sdl_alen == ETHER_ADDR_LEN)
-                               {
-                                       handler = get_handler(this, ifa->ifa_name);
-                                       handler->mac = chunk_clone(chunk_create(LLADDR(dl),
-                                                                                                                       dl->sdl_alen));
-                               }
-                               break;
-                       case AF_INET:
-                               ipv4 = host_create_from_sockaddr(ifa->ifa_addr);
-                               if (ipv4 && !ipv4->is_anyaddr(ipv4))
-                               {
-                                       handler = get_handler(this, ifa->ifa_name);
-                                       if (!handler->ipv4)
-                                       {
-                                               handler->ipv4 = ipv4->clone(ipv4);
-                                       }
-                               }
-                               DESTROY_IF(ipv4);
-                               break;
-                       default:
-                               break;
-               }
-       }
-       freeifaddrs(ifas);
+       arp_t *a = (arp_t*)packet.ptr;
+       host_t *sender, *target;
 
-       enumerator = this->handlers->create_enumerator(this->handlers);
-       while (enumerator->enumerate(enumerator, &handler))
+       if (packet.len == sizeof(arp_t))
        {
-               if (handler->mac.ptr && handler->ipv4 &&
-                       setup_handler(this, handler))
+               sender = host_create_from_chunk(AF_INET,
+                                                                               chunk_create((char*)a->sender_ip, 4), 0);
+               target = host_create_from_chunk(AF_INET,
+                                                                               chunk_create((char*)a->target_ip, 4), 0);
+               if (this->listener->has_tunnel(this->listener, sender, target))
                {
-                       DBG1(DBG_NET, "listening for ARP requests on %s (%H, %#B)",
-                            handler->name, handler->ipv4, &handler->mac);
+                       send_arp(if_name, if_index, mac, fd, a, sender, target);
                }
                else
                {
-                       this->handlers->remove_at(this->handlers, enumerator);
-                       handler_destroy(handler);
+                       DBG2(DBG_NET, "not sending ARP reply, no tunnel between %H -> %H",
+                                sender, target);
                }
+               target->destroy(target);
+               sender->destroy(sender);
+       }
+       else
+       {
+               DBG1(DBG_NET, "ARP request with invalid size %d received (expected: %d)",
+                        packet.len, sizeof(arp_t));
        }
-       enumerator->destroy(enumerator);
-
-       return this->handlers->get_count(this->handlers) > 0;
 }
 
 /**
@@ -617,16 +209,7 @@ static bool setup_handlers(private_farp_spoofer_t *this)
  */
 METHOD(farp_spoofer_t, destroy, void, private_farp_spoofer_t *this)
 {
-       enumerator_t *enumerator;
-       farp_handler_t *handler;
-
-       enumerator = this->handlers->create_enumerator(this->handlers);
-       while (enumerator->enumerate(enumerator, &handler))
-       {
-               handler_destroy(handler);
-       }
-       enumerator->destroy(enumerator);
-       this->handlers->destroy(this->handlers);
+       this->pf_handler->destroy(this->pf_handler);
        free(this);
 }
 
@@ -635,6 +218,48 @@ METHOD(farp_spoofer_t, destroy, void, private_farp_spoofer_t *this)
  */
 farp_spoofer_t *farp_spoofer_create(farp_listener_t *listener)
 {
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+       struct sock_filter arp_request_filter_code[] = {
+               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, offsetof(arp_t, protocol_type)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETH_P_IP, 0, 9),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, offsetof(arp_t, hardware_size)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 6, 0, 7),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, offsetof(arp_t, protocol_size)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 4, 0, 5),
+               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, offsetof(arp_t, opcode)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARPOP_REQUEST, 0, 3),
+               BPF_STMT(BPF_LD+BPF_W+BPF_LEN, 0),
+               BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, sizeof(arp_t), 0, 1),
+               BPF_STMT(BPF_RET+BPF_K, sizeof(arp_t)),
+               BPF_STMT(BPF_RET+BPF_K, 0),
+       };
+       struct sock_fprog arp_request_filter = {
+               sizeof(arp_request_filter_code) / sizeof(struct sock_filter),
+               arp_request_filter_code,
+       };
+#else
+       const size_t skip_eth = sizeof(struct ether_header);
+       struct bpf_insn instructions[] = {
+               BPF_STMT(BPF_LD+BPF_W+BPF_LEN, 0),
+               BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, skip_eth + sizeof(arp_t), 0, 11),
+               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, offsetof(struct ether_header, ether_type)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_ARP, 0, 9),
+               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, skip_eth + offsetof(arp_t, protocol_type)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_IP, 0, 7),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, skip_eth + offsetof(arp_t, hardware_size)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 6, 0, 5),
+               BPF_STMT(BPF_LD+BPF_B+BPF_ABS, skip_eth + offsetof(arp_t, protocol_size)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 4, 0, 3),
+               BPF_STMT(BPF_LD+BPF_H+BPF_ABS, skip_eth + offsetof(arp_t, opcode)),
+               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARPOP_REQUEST, 0, 1),
+               BPF_STMT(BPF_RET+BPF_K, 14 + sizeof(arp_t)),
+               BPF_STMT(BPF_RET+BPF_K, 0)
+       };
+       struct bpf_program arp_request_filter = {
+               sizeof(instructions) / sizeof(struct bpf_insn),
+               &instructions[0]
+       };
+#endif
        private_farp_spoofer_t *this;
 
        INIT(this,
@@ -642,15 +267,14 @@ farp_spoofer_t *farp_spoofer_create(farp_listener_t *listener)
                        .destroy = _destroy,
                },
                .listener = listener,
-               .handlers = linked_list_create(),
        );
 
-       if (!setup_handlers(this))
+       this->pf_handler = pf_handler_create("ARP", NULL, handle_arp_pkt, this,
+                                                                                &arp_request_filter);
+       if (!this->pf_handler)
        {
                destroy(this);
                return NULL;
        }
        return &this->public;
 }
-
-#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */