farp: Add support for macOS and FreeBSD
Co-authored-by: Tobias Brunner <tobias@strongswan.org> Closes strongswan/strongswan#189. References #3498.
This commit is contained in:
parent
8e367df6db
commit
95a0d800c9
|
@ -1,4 +1,7 @@
|
|||
/*
|
||||
* Copyright (C) 2021 Tobias Brunner
|
||||
* HSR Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* Copyright (C) 2010 Martin Willi
|
||||
* Copyright (C) 2010 revosec AG
|
||||
*
|
||||
|
@ -13,15 +16,50 @@
|
|||
* 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__) */
|
||||
|
|
Loading…
Reference in New Issue