/* * 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 #include #include #include #include #if !defined(__APPLE__) && !defined(__FreeBSD__) #include #include #include #include #else #include #include #include #include #endif /* !defined(__APPLE__) && !defined(__FreeBSD__) */ #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 || req.ifr_hwaddr.sa_family == ARPHRD_LOOPBACK)) { 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; }