]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
farp: Add support for macOS and FreeBSD
authorDan James <sddj@me.com>
Sun, 20 Dec 2020 00:04:16 +0000 (19:04 -0500)
committerTobias Brunner <tobias@strongswan.org>
Fri, 22 Jan 2021 09:44:05 +0000 (10:44 +0100)
Co-authored-by: Tobias Brunner <tobias@strongswan.org>
Closes strongswan/strongswan#189.
References #3498.

src/libcharon/plugins/farp/farp_spoofer.c

index 5753f7910aa64e180b2e6fc9425447522a32f7e4..2a1b511c31e39d684c2287bf84b334a50d653864 100644 (file)
@@ -1,4 +1,7 @@
 /*
+ * Copyright (C) 2021 Tobias Brunner
+ * HSR Hochschule fuer Technik Rapperswil
+ *
  * Copyright (C) 2010 Martin Willi
  * Copyright (C) 2010 revosec 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 <unistd.h>
+#include <sys/ioctl.h>
+
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
 #include <sys/socket.h>
 #include <linux/if_arp.h>
 #include <linux/if_ether.h>
 #include <linux/filter.h>
-#include <sys/ioctl.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 <daemon.h>
 #include <threading/thread.h>
@@ -44,10 +82,17 @@ 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
+        */
+       linked_list_t *handlers;
+#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */
 };
 
 /**
@@ -65,6 +110,7 @@ typedef struct __attribute__((packed)) {
        uint8_t target_ip[4];
 } arp_t;
 
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
 /**
  * Send faked ARP response
  */
@@ -174,7 +220,8 @@ farp_spoofer_t *farp_spoofer_create(farp_listener_t *listener)
        if (setsockopt(this->skt, SOL_SOCKET, SO_ATTACH_FILTER,
                                   &arp_request_filter, sizeof(arp_request_filter)) < 0)
        {
-               DBG1(DBG_NET, "installing ARP packet filter failed: %s", strerror(errno));
+               DBG1(DBG_NET, "installing ARP packet filter failed: %s",
+                        strerror(errno));
                close(this->skt);
                free(this);
                return NULL;
@@ -185,3 +232,431 @@ farp_spoofer_t *farp_spoofer_create(farp_listener_t *listener)
 
        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.
+ */
+struct frame_t {
+       struct ether_header e;
+       arp_t a;
+};
+
+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)
+{
+       frame_t frame;
+       chunk_t mac;
+       ssize_t n;
+
+       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);
+       frame.e.ether_type = htons(ETHERTYPE_ARP);
+
+       frame.a.hardware_type = htons(1);
+       frame.a.protocol_type = htons(ETHERTYPE_IP);
+       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_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));
+       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;
+       struct ether_header *eh;
+       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;
+               eh = (struct ether_header*)(p + bh->bh_hdrlen);
+               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;
+}
+
+/**
+ * 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)
+{
+       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);
+
+       enumerator = this->handlers->create_enumerator(this->handlers);
+       while (enumerator->enumerate(enumerator, &handler))
+       {
+               if (handler->mac.ptr && handler->ipv4 &&
+                       setup_handler(this, handler))
+               {
+                       DBG1(DBG_NET, "listening for ARP requests on %s (%H, %#B)",
+                            handler->name, handler->ipv4, &handler->mac);
+               }
+               else
+               {
+                       this->handlers->remove_at(this->handlers, enumerator);
+                       handler_destroy(handler);
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       return this->handlers->get_count(this->handlers) > 0;
+}
+
+/**
+ * Cleanup the handlers used by this plugin.
+ */
+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);
+       free(this);
+}
+
+/**
+ * See header
+ */
+farp_spoofer_t *farp_spoofer_create(farp_listener_t *listener)
+{
+       private_farp_spoofer_t *this;
+
+       INIT(this,
+               .public = {
+                       .destroy = _destroy,
+               },
+               .listener = listener,
+               .handlers = linked_list_create(),
+       );
+
+       if (!setup_handlers(this))
+       {
+               destroy(this);
+               return NULL;
+       }
+       return &this->public;
+}
+
+#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */