From: Dan James Date: Fri, 24 Nov 2023 15:54:04 +0000 (-0500) Subject: dhcp: Port the plugin to FreeBSD/macOS X-Git-Tag: android-2.5.0~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=187c72d1afdcc4d214a3bcabb1c95c93ea1be21e;p=thirdparty%2Fstrongswan.git dhcp: Port the plugin to FreeBSD/macOS 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 --- diff --git a/configure.ac b/configure.ac index 32eae7889d..9ffb9be5e2 100644 --- a/configure.ac +++ b/configure.ac @@ -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 # --------------- diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index fd88237fee..3f5a20e61e 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -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 index 0000000000..5dab3f04fa --- /dev/null +++ b/src/libcharon/network/pf_handler.c @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2021-2024 Tobias Brunner + * Copyright (C) 2020-2023 Dan James + * 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 . + * + * 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 +#include + +#if !defined(__APPLE__) && !defined(__FreeBSD__) +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */ + +#include +#include +#include + +#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 index 0000000000..6eee3d4149 --- /dev/null +++ b/src/libcharon/network/pf_handler.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * Copyright (C) 2020-2023 Dan James + * + * 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 . + * + * 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 +#include + +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_ */ diff --git a/src/libcharon/plugins/dhcp/dhcp_socket.c b/src/libcharon/plugins/dhcp/dhcp_socket.c index d58e5edbea..d144e27959 100644 --- a/src/libcharon/plugins/dhcp/dhcp_socket.c +++ b/src/libcharon/plugins/dhcp/dhcp_socket.c @@ -1,5 +1,6 @@ /* - * Copyright (C) 2012-2018 Tobias Brunner + * Copyright (C) 2023 Dan James + * Copyright (C) 2012-2024 Tobias Brunner * Copyright (C) 2010 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -19,13 +20,20 @@ #include #include -#include #include #include #include + +#if !defined(__APPLE__) && !defined(__FreeBSD__) +#include #include -#include #include +#else +#include +#include +#include +#include +#endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */ #include #include @@ -35,6 +43,7 @@ #include #include +#include #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; } diff --git a/src/libcharon/plugins/farp/farp_spoofer.c b/src/libcharon/plugins/farp/farp_spoofer.c index 2493b2da3d..65451263f5 100644 --- a/src/libcharon/plugins/farp/farp_spoofer.c +++ b/src/libcharon/plugins/farp/farp_spoofer.c @@ -1,5 +1,6 @@ /* - * Copyright (C) 2021 Tobias Brunner + * Copyright (C) 2021-2024 Tobias Brunner + * Copyright (C) 2020-2023 Dan James * Copyright (C) 2010 Martin Willi * * Copyright (C) secunet Security Networks AG @@ -15,30 +16,6 @@ * for more details. */ -/* - * For the Apple BPF implementation. - * - * Copyright (C) 2020 Dan James - * - * 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 @@ -51,18 +28,16 @@ #include #include #else -#include -#include #include -#include -#include #include #include #endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */ +#include #include #include #include +#include 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__) */