echo "DHCPCD_DEFS= $DHCPCD_DEFS" >>$CONFIG_MK
fi
+BPF_OS=
case "$OS" in
freebsd*|kfreebsd*)
# FreeBSD hide some newer POSIX APIs behind _GNU_SOURCE ...
echo "#include <asm/types.h> /* fix broken headers */" >>$CONFIG_H
echo "#include <sys/socket.h> /* fix broken headers */" >>$CONFIG_H
echo "#include <linux/rtnetlink.h>" >>$CONFIG_H
+ BPF_OS="bpf-linux.c"
# cksum does't support -a and netpgp is rare
echo "CKSUM= sha256sum --tag" >>$CONFIG_MK
echo "PGP= gpg2" >>$CONFIG_MK
;;
esac
+if [ -z "$BPF_OS" ]; then
+ BPF_OS="bpf-bsd.c"
+fi
+
if [ -n "${_DEFAULT_HOSTNAME+x}" ]; then
DEFAULT_HOSTNAME="${_DEFAULT_HOSTNAME}"
else
if [ -z "$INET" ] || [ "$INET" = yes ]; then
echo "Enabling INET support"
echo "CPPFLAGS+= -DINET" >>$CONFIG_MK
- echo "DHCPCD_SRCS+= dhcp.c ipv4.c bpf.c" >>$CONFIG_MK
+ echo "DHCPCD_SRCS+= dhcp.c ipv4.c bpf.c $BPF_OS" >>$CONFIG_MK
if [ -z "$ARP" ] || [ "$ARP" = yes ]; then
echo "Enabling ARP support"
echo "CPPFLAGS+= -DARP" >>$CONFIG_MK
} else
#endif
{
- astate->bpf = bpf_open(ifp, bpf_arp, addr);
+ astate->bpf = bpf_open(ifp, bpf_filter_arp, addr);
if (astate->bpf == NULL) {
logerr(__func__);
free(astate);
--- /dev/null
+/*
+ * dhcpcd: BPF BSD interface
+ * SPDX-License-Identifier: BSD-2-Clause
+ * Copyright (c) 2006-2025 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/bpf.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bpf.h"
+#include "logerr.h"
+
+const char *bpf_name = "Berkley Packet Filter";
+
+struct bpf *
+bpf_open(const struct interface *ifp,
+ int (*filter)(const struct bpf *, const struct in_addr *),
+ const struct in_addr *ia)
+{
+ struct bpf *bpf;
+ struct bpf_version pv = { .bv_major = 0, .bv_minor = 0 };
+ struct ifreq ifr = { .ifr_flags = 0 };
+ int ibuf_len = 0;
+#ifdef O_CLOEXEC
+#define BPF_OPEN_FLAGS O_RDWR | O_NONBLOCK | O_CLOEXEC
+#else
+#define BPF_OPEN_FLAGS O_RDWR | O_NONBLOCK
+#endif
+#ifdef BIOCIMMEDIATE
+ unsigned int flags;
+#endif
+#ifndef O_CLOEXEC
+ int fd_opts;
+#endif
+
+ bpf = calloc(1, sizeof(*bpf));
+ if (bpf == NULL)
+ return NULL;
+ bpf->bpf_ifp = ifp;
+ bpf->bpf_flags = BPF_EOF;
+
+ /* /dev/bpf is a cloner on modern kernels */
+ bpf->bpf_fd = open("/dev/bpf", BPF_OPEN_FLAGS);
+
+ /* Support older kernels where /dev/bpf is not a cloner */
+ if (bpf->bpf_fd == -1) {
+ char device[32];
+ int n = 0;
+
+ do {
+ snprintf(device, sizeof(device), "/dev/bpf%d", n++);
+ bpf->bpf_fd = open(device, BPF_OPEN_FLAGS);
+ } while (bpf->bpf_fd == -1 && errno == EBUSY);
+ }
+
+ if (bpf->bpf_fd == -1)
+ goto eexit;
+
+#ifndef O_CLOEXEC
+ if ((fd_opts = fcntl(bpf->bpf_fd, F_GETFD)) == -1 ||
+ fcntl(bpf->bpf_fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1)
+ goto eexit;
+#endif
+
+ if (ioctl(bpf->bpf_fd, BIOCVERSION, &pv) == -1)
+ goto eexit;
+ if (pv.bv_major != BPF_MAJOR_VERSION ||
+ pv.bv_minor < BPF_MINOR_VERSION) {
+ logerrx("BPF version mismatch - recompile");
+ goto eexit;
+ }
+
+ strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
+ if (ioctl(bpf->bpf_fd, BIOCSETIF, &ifr) == -1)
+ goto eexit;
+
+#ifdef BIOCIMMEDIATE
+ flags = 1;
+ if (ioctl(bpf->bpf_fd, BIOCIMMEDIATE, &flags) == -1)
+ goto eexit;
+#endif
+
+ if (filter(bpf, ia) != 0)
+ goto eexit;
+
+ /* Get the required BPF buffer length from the kernel. */
+ if (ioctl(bpf->bpf_fd, BIOCGBLEN, &ibuf_len) == -1)
+ goto eexit;
+
+ bpf->bpf_size = (size_t)ibuf_len;
+ bpf->bpf_buffer = malloc(bpf->bpf_size);
+ if (bpf->bpf_buffer == NULL)
+ goto eexit;
+
+ return bpf;
+
+eexit:
+ if (bpf->bpf_fd != -1)
+ close(bpf->bpf_fd);
+ free(bpf);
+ return NULL;
+}
+
+/* BPF requires that we read the entire buffer.
+ * So we pass the buffer in the API so we can loop on >1 packet. */
+ssize_t
+bpf_read(struct bpf *bpf, void *data, size_t len)
+{
+ ssize_t bytes;
+ struct bpf_hdr packet;
+ const char *payload;
+
+ bpf->bpf_flags &= ~BPF_EOF;
+ for (;;) {
+ if (bpf->bpf_len == 0) {
+ bytes = read(bpf->bpf_fd, bpf->bpf_buffer,
+ bpf->bpf_size);
+#if defined(__sun)
+ /* After 2^31 bytes, the kernel offset overflows.
+ * To work around this bug, lseek 0. */
+ if (bytes == -1 && errno == EINVAL) {
+ lseek(bpf->bpf_fd, 0, SEEK_SET);
+ continue;
+ }
+#endif
+ if (bytes == -1 || bytes == 0)
+ return bytes;
+ bpf->bpf_len = (size_t)bytes;
+ bpf->bpf_pos = 0;
+ }
+ bytes = -1;
+ payload = (const char *)bpf->bpf_buffer + bpf->bpf_pos;
+ memcpy(&packet, payload, sizeof(packet));
+ if (bpf->bpf_pos + packet.bh_caplen + packet.bh_hdrlen >
+ bpf->bpf_len)
+ goto next; /* Packet beyond buffer, drop. */
+ payload += packet.bh_hdrlen;
+ if (packet.bh_caplen > len)
+ bytes = (ssize_t)len;
+ else
+ bytes = (ssize_t)packet.bh_caplen;
+ if (bpf_frame_bcast(bpf->bpf_ifp, payload) == 0)
+ bpf->bpf_flags |= BPF_BCAST;
+ else
+ bpf->bpf_flags &= ~BPF_BCAST;
+ memcpy(data, payload, (size_t)bytes);
+ next:
+ bpf->bpf_pos += BPF_WORDALIGN(
+ packet.bh_hdrlen + packet.bh_caplen);
+ if (bpf->bpf_pos >= bpf->bpf_len) {
+ bpf->bpf_len = bpf->bpf_pos = 0;
+ bpf->bpf_flags |= BPF_EOF;
+ }
+ if (bytes != -1)
+ return bytes;
+ }
+
+ /* NOTREACHED */
+}
+
+int
+bpf_setfilter(const struct bpf *bpf, void *filter, unsigned int filter_len)
+{
+ struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };
+
+ /* Install the filter. */
+ return ioctl(bpf->bpf_fd, BIOCSETF, &pf);
+}
+
+int
+bpf_setwfilter(const struct bpf *bpf, void *filter, unsigned int filter_len)
+{
+#ifdef BIOCSETWF
+ struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };
+
+ /* Install the filter. */
+ return ioctl(bpf->bpf_fd, BIOCSETWF, &pf);
+#else
+#warning No BIOCSETWF support - a compromised BPF can be used as a raw socket
+ UNUSED(bpf);
+ UNUSED(filter);
+ UNUSED(filter_len);
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+int
+bpf_lock(const struct bpf *bpf)
+{
+#ifdef BIOCLOCK
+ return ioctl(bpf->bpf_fd, BIOCLOCK);
+#else
+ UNUSED(bpf);
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+#if !defined(__sun)
+/* SunOS is special too - sending via BPF goes nowhere. */
+ssize_t
+bpf_writev(const struct bpf *bpf, struct iovec *iov, int iovcnt)
+{
+ return writev(bpf->bpf_fd, iov, iovcnt);
+}
+#endif
+
+void
+bpf_close(struct bpf *bpf)
+{
+ close(bpf->bpf_fd);
+ free(bpf->bpf_buffer);
+ free(bpf);
+}
--- /dev/null
+/*
+ * dhcpcd: BPF Linux interface
+ * SPDX-License-Identifier: BSD-2-Clause
+ * Copyright (c) 2006-2025 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/filter.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bpf.h"
+
+const char *bpf_name = "Packet Socket";
+
+struct bpf *
+bpf_open(const struct interface *ifp,
+ int (*filter)(const struct bpf *, const struct in_addr *),
+ const struct in_addr *ia)
+{
+ struct bpf *bpf;
+ union sockunion {
+ struct sockaddr sa;
+ struct sockaddr_ll sll;
+ struct sockaddr_storage ss;
+ } su = { .sll = {
+ .sll_family = PF_PACKET,
+ .sll_protocol = htons(ETH_P_ALL),
+ .sll_ifindex = (int)ifp->index,
+ } };
+#ifdef PACKET_AUXDATA
+ int n;
+#endif
+
+ bpf = calloc(1, sizeof(*bpf));
+ if (bpf == NULL)
+ return NULL;
+ bpf->bpf_ifp = ifp;
+ bpf->bpf_flags = BPF_EOF;
+
+ /* Allocate a suitably large buffer for a single packet. */
+ bpf->bpf_size = ETH_FRAME_LEN;
+ bpf->bpf_buffer = malloc(bpf->bpf_size);
+ if (bpf->bpf_buffer == NULL)
+ goto eexit;
+
+ bpf->bpf_fd = xsocket(PF_PACKET, SOCK_RAW | SOCK_CXNB,
+ htons(ETH_P_ALL));
+ if (bpf->bpf_fd == -1)
+ goto eexit;
+
+ /* We cannot validate the correct interface,
+ * so we MUST set this first. */
+ if (bind(bpf->bpf_fd, &su.sa, sizeof(su.sll)) == -1)
+ goto eexit;
+
+ if (filter(bpf, ia) != 0)
+ goto eexit;
+
+ /* In the ideal world, this would be set before the bind and filter. */
+#ifdef PACKET_AUXDATA
+ n = 1;
+ if (setsockopt(bpf->bpf_fd, SOL_PACKET, PACKET_AUXDATA, &n,
+ sizeof(n)) != 0) {
+ if (errno != ENOPROTOOPT)
+ goto eexit;
+ }
+#endif
+
+ /*
+ * At this point we could have received packets for the wrong
+ * interface or which don't pass the filter.
+ * Linux should flush upon setting the filter like every other OS.
+ * There is no way of flushing them from userland.
+ * As such, consumers need to inspect each packet to ensure it's valid.
+ * Or to put it another way, don't trust the Linux BPF filter.
+ */
+
+ return bpf;
+
+eexit:
+ if (bpf->bpf_fd != -1)
+ close(bpf->bpf_fd);
+ free(bpf->bpf_buffer);
+ free(bpf);
+ return NULL;
+}
+
+/* BPF requires that we read the entire buffer.
+ * So we pass the buffer in the API so we can loop on >1 packet. */
+ssize_t
+bpf_read(struct bpf *bpf, void *data, size_t len)
+{
+ ssize_t bytes;
+ struct iovec iov = {
+ .iov_base = bpf->bpf_buffer,
+ .iov_len = bpf->bpf_size,
+ };
+ struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
+#ifdef PACKET_AUXDATA
+ union {
+ struct cmsghdr hdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct tpacket_auxdata))];
+ } cmsgbuf = { .buf = { 0 } };
+ struct cmsghdr *cmsg;
+ struct tpacket_auxdata *aux;
+#endif
+
+#ifdef PACKET_AUXDATA
+ msg.msg_control = cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+#endif
+
+ bytes = recvmsg(bpf->bpf_fd, &msg, 0);
+ if (bytes == -1)
+ return -1;
+ bpf->bpf_flags |= BPF_EOF; /* We only ever read one packet. */
+ bpf->bpf_flags &= ~BPF_PARTIALCSUM;
+ if (bytes) {
+ if (bpf_frame_bcast(bpf->bpf_ifp, bpf->bpf_buffer) == 0)
+ bpf->bpf_flags |= BPF_BCAST;
+ else
+ bpf->bpf_flags &= ~BPF_BCAST;
+ if ((size_t)bytes > len)
+ bytes = (ssize_t)len;
+ memcpy(data, bpf->bpf_buffer, (size_t)bytes);
+#ifdef PACKET_AUXDATA
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_PACKET &&
+ cmsg->cmsg_type == PACKET_AUXDATA) {
+ aux = (void *)CMSG_DATA(cmsg);
+ if (aux->tp_status & TP_STATUS_CSUMNOTREADY)
+ bpf->bpf_flags |= BPF_PARTIALCSUM;
+ }
+ }
+#endif
+ }
+ return bytes;
+}
+
+int
+bpf_setfilter(const struct bpf *bpf, void *filter, unsigned int filter_len)
+{
+ struct sock_fprog pf = {
+ .filter = filter,
+ .len = (unsigned short)filter_len,
+ };
+ int s = bpf->bpf_fd;
+
+ /* Install the filter. */
+ return setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf));
+}
+
+int
+bpf_setwfilter(__unused const struct bpf *bpf, __unused void *filter, __unused unsigned int filter_len)
+{
+#warning A compromised PF_PACKET socket can be used as a raw socket
+
+ errno = ENOSYS;
+ return -1;
+}
+
+int
+bpf_lock(const struct bpf *bpf)
+{
+#ifdef SO_LOCK_FILTER
+ int fd = bpf->bpf_fd, on = 1;
+
+ return setsockopt(fd, SOL_SOCKET, SO_LOCK_FILTER, &on, sizeof(on));
+#else
+ UNUSED(bpf);
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+ssize_t
+bpf_writev(const struct bpf *bpf, struct iovec *iov, int iovcnt)
+{
+ return writev(bpf->bpf_fd, iov, iovcnt);
+}
+
+void
+bpf_close(struct bpf *bpf)
+{
+ close(bpf->bpf_fd);
+ free(bpf->bpf_buffer);
+ free(bpf);
+}
+
}
}
-#ifndef __linux__
-/* Linux is a special snowflake for opening, attaching and reading BPF.
- * See if-linux.c for the Linux specific BPF functions. */
-
-const char *bpf_name = "Berkley Packet Filter";
-
-struct bpf *
-bpf_open(const struct interface *ifp,
- int (*filter)(const struct bpf *, const struct in_addr *),
- const struct in_addr *ia)
-{
- struct bpf *bpf;
- struct bpf_version pv = { .bv_major = 0, .bv_minor = 0 };
- struct ifreq ifr = { .ifr_flags = 0 };
- int ibuf_len = 0;
-#ifdef O_CLOEXEC
-#define BPF_OPEN_FLAGS O_RDWR | O_NONBLOCK | O_CLOEXEC
-#else
-#define BPF_OPEN_FLAGS O_RDWR | O_NONBLOCK
-#endif
-#ifdef BIOCIMMEDIATE
- unsigned int flags;
-#endif
-#ifndef O_CLOEXEC
- int fd_opts;
-#endif
-
- bpf = calloc(1, sizeof(*bpf));
- if (bpf == NULL)
- return NULL;
- bpf->bpf_ifp = ifp;
- bpf->bpf_flags = BPF_EOF;
-
- /* /dev/bpf is a cloner on modern kernels */
- bpf->bpf_fd = open("/dev/bpf", BPF_OPEN_FLAGS);
-
- /* Support older kernels where /dev/bpf is not a cloner */
- if (bpf->bpf_fd == -1) {
- char device[32];
- int n = 0;
-
- do {
- snprintf(device, sizeof(device), "/dev/bpf%d", n++);
- bpf->bpf_fd = open(device, BPF_OPEN_FLAGS);
- } while (bpf->bpf_fd == -1 && errno == EBUSY);
- }
-
- if (bpf->bpf_fd == -1)
- goto eexit;
-
-#ifndef O_CLOEXEC
- if ((fd_opts = fcntl(bpf->bpf_fd, F_GETFD)) == -1 ||
- fcntl(bpf->bpf_fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1)
- goto eexit;
-#endif
-
- if (ioctl(bpf->bpf_fd, BIOCVERSION, &pv) == -1)
- goto eexit;
- if (pv.bv_major != BPF_MAJOR_VERSION ||
- pv.bv_minor < BPF_MINOR_VERSION) {
- logerrx("BPF version mismatch - recompile");
- goto eexit;
- }
-
- strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
- if (ioctl(bpf->bpf_fd, BIOCSETIF, &ifr) == -1)
- goto eexit;
-
-#ifdef BIOCIMMEDIATE
- flags = 1;
- if (ioctl(bpf->bpf_fd, BIOCIMMEDIATE, &flags) == -1)
- goto eexit;
-#endif
-
- if (filter(bpf, ia) != 0)
- goto eexit;
-
- /* Get the required BPF buffer length from the kernel. */
- if (ioctl(bpf->bpf_fd, BIOCGBLEN, &ibuf_len) == -1)
- goto eexit;
-
- bpf->bpf_size = (size_t)ibuf_len;
- bpf->bpf_buffer = malloc(bpf->bpf_size);
- if (bpf->bpf_buffer == NULL)
- goto eexit;
-
- return bpf;
-
-eexit:
- if (bpf->bpf_fd != -1)
- close(bpf->bpf_fd);
- free(bpf);
- return NULL;
-}
-
-/* BPF requires that we read the entire buffer.
- * So we pass the buffer in the API so we can loop on >1 packet. */
-ssize_t
-bpf_read(struct bpf *bpf, void *data, size_t len)
-{
- ssize_t bytes;
- struct bpf_hdr packet;
- const char *payload;
-
- bpf->bpf_flags &= ~BPF_EOF;
- for (;;) {
- if (bpf->bpf_len == 0) {
- bytes = read(bpf->bpf_fd, bpf->bpf_buffer,
- bpf->bpf_size);
-#if defined(__sun)
- /* After 2^31 bytes, the kernel offset overflows.
- * To work around this bug, lseek 0. */
- if (bytes == -1 && errno == EINVAL) {
- lseek(bpf->bpf_fd, 0, SEEK_SET);
- continue;
- }
-#endif
- if (bytes == -1 || bytes == 0)
- return bytes;
- bpf->bpf_len = (size_t)bytes;
- bpf->bpf_pos = 0;
- }
- bytes = -1;
- payload = (const char *)bpf->bpf_buffer + bpf->bpf_pos;
- memcpy(&packet, payload, sizeof(packet));
- if (bpf->bpf_pos + packet.bh_caplen + packet.bh_hdrlen >
- bpf->bpf_len)
- goto next; /* Packet beyond buffer, drop. */
- payload += packet.bh_hdrlen;
- if (packet.bh_caplen > len)
- bytes = (ssize_t)len;
- else
- bytes = (ssize_t)packet.bh_caplen;
- if (bpf_frame_bcast(bpf->bpf_ifp, payload) == 0)
- bpf->bpf_flags |= BPF_BCAST;
- else
- bpf->bpf_flags &= ~BPF_BCAST;
- memcpy(data, payload, (size_t)bytes);
- next:
- bpf->bpf_pos += BPF_WORDALIGN(
- packet.bh_hdrlen + packet.bh_caplen);
- if (bpf->bpf_pos >= bpf->bpf_len) {
- bpf->bpf_len = bpf->bpf_pos = 0;
- bpf->bpf_flags |= BPF_EOF;
- }
- if (bytes != -1)
- return bytes;
- }
-
- /* NOTREACHED */
-}
-
-int
-bpf_attach(int fd, void *filter, unsigned int filter_len)
-{
- struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };
-
- /* Install the filter. */
- return ioctl(fd, BIOCSETF, &pf);
-}
-
-#ifdef BIOCSETWF
-static int
-bpf_wattach(int fd, void *filter, unsigned int filter_len)
-{
- struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };
-
- /* Install the filter. */
- return ioctl(fd, BIOCSETWF, &pf);
-}
-#endif
-#endif
-
#ifndef __sun
-/* SunOS is special too - sending via BPF goes nowhere. */
ssize_t
bpf_send(const struct bpf *bpf, uint16_t protocol, const void *data, size_t len)
{
}
iov[1].iov_base = UNCONST(data);
iov[1].iov_len = len;
- return writev(bpf->bpf_fd, iov, 2);
-}
-#endif
-void
-bpf_close(struct bpf *bpf)
-{
- close(bpf->bpf_fd);
- free(bpf->bpf_buffer);
- free(bpf);
+ return bpf_writev(bpf, iov, __arraycount(iov));
}
+#endif
#ifdef ARP
#define BPF_CMP_HWADDR_LEN ((((HWADDR_LEN / 4) + 2) * 2) + 1)
struct bpf_insn buf[BPF_ARP_LEN + 1];
struct bpf_insn *bp;
uint16_t arp_len;
+ unsigned int len;
bp = buf;
/* Check frame header. */
BPF_SET_STMT(bp, BPF_RET + BPF_K, 0);
bp++;
-#ifdef BIOCSETWF
- if (!recv)
- return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
-#endif
-
- return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
+ len = (unsigned int)(bp - buf);
+ if (recv) return bpf_setfilter(bpf, buf, len);
+ return bpf_setwfilter(bpf, buf, len);
+ return -1;
}
int
-bpf_arp(const struct bpf *bpf, const struct in_addr *ia)
+bpf_filter_arp(const struct bpf *bpf, const struct in_addr *ia)
{
-#ifdef BIOCSETWF
- if (bpf_arp_rw(bpf, ia, true) == -1 ||
- bpf_arp_rw(bpf, ia, false) == -1 ||
- ioctl(bpf->bpf_fd, BIOCLOCK) == -1)
+ if (bpf_arp_rw(bpf, ia, true) == -1)
+ return -1;
+ if (bpf_arp_rw(bpf, ia, false) == -1 && errno != ENOSYS)
+ return -1;
+ if (bpf_lock(bpf) == -1 && errno != ENOSYS)
return -1;
return 0;
-#else
- return bpf_arp_rw(bpf, ia, true);
-#endif
}
#endif
};
#define BPF_BOOTP_READ_LEN __arraycount(bpf_bootp_read)
-#ifdef BIOCSETWF
static const struct bpf_insn bpf_bootp_write[] = {
/* Make sure it's from and to the right port.
* RFC2131 makes no mention of encforcing a source port,
BPF_STMT(BPF_RET + BPF_K, 0),
};
#define BPF_BOOTP_WRITE_LEN __arraycount(bpf_bootp_write)
-#endif
#define BPF_BOOTP_CHADDR_LEN ((BOOTP_CHADDR_LEN / 4) * 3)
#define BPF_BOOTP_XID_LEN 4 /* BOUND check is 4 instructions */
memcpy(bp, bpf_bootp_base, sizeof(bpf_bootp_base));
bp += BPF_BOOTP_BASE_LEN;
-#ifdef BIOCSETWF
if (!read) {
memcpy(bp, bpf_bootp_write, sizeof(bpf_bootp_write));
bp += BPF_BOOTP_WRITE_LEN;
BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
bp++;
- return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
+ return bpf_setwfilter(bpf, buf, (unsigned int)(bp - buf));
}
-#else
- UNUSED(read);
-#endif
memcpy(bp, bpf_bootp_read, sizeof(bpf_bootp_read));
bp += BPF_BOOTP_READ_LEN;
BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
bp++;
- return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf));
+ return bpf_setfilter(bpf, buf, (unsigned int)(bp - buf));
}
int
-bpf_bootp(const struct bpf *bpf, __unused const struct in_addr *ia)
+bpf_filter_bootp(const struct bpf *bpf, __unused const struct in_addr *ia)
{
-#ifdef BIOCSETWF
- if (bpf_bootp_rw(bpf, true) == -1 || bpf_bootp_rw(bpf, false) == -1 ||
- ioctl(bpf->bpf_fd, BIOCLOCK) == -1)
+ if (bpf_bootp_rw(bpf, true) == -1)
+ return -1;
+ if (bpf_bootp_rw(bpf, false) == -1 && errno != ENOSYS)
+ return -1;
+ if (bpf_lock(bpf) == -1 && errno != ENOSYS)
return -1;
return 0;
-#else
-#ifdef PRIVSEP
-#if defined(__sun) /* Solaris cannot send via BPF. */
-#elif defined(BIOCSETF)
-#warning No BIOCSETWF support - a compromised BPF can be used as a raw socket
-#else
-#warning A compromised PF_PACKET socket can be used as a raw socket
-#endif
-#endif
- return bpf_bootp_rw(bpf, true);
-#endif
}
size_t bpf_len;
size_t bpf_pos;
};
+struct iovec;
extern const char *bpf_name;
size_t bpf_frame_header_len(const struct interface *);
int (*)(const struct bpf *, const struct in_addr *),
const struct in_addr *);
void bpf_close(struct bpf *);
-int bpf_attach(int, void *, unsigned int);
+int bpf_setfilter(const struct bpf *, void *, unsigned int);
+int bpf_setwfilter(const struct bpf *, void *, unsigned int);
+int bpf_lock(const struct bpf *);
ssize_t bpf_send(const struct bpf *, uint16_t, const void *, size_t);
+ssize_t bpf_writev(const struct bpf *, struct iovec *, int);
ssize_t bpf_read(struct bpf *, void *, size_t);
-int bpf_arp(const struct bpf *, const struct in_addr *);
-int bpf_bootp(const struct bpf *, const struct in_addr *);
+
+int bpf_filter_arp(const struct bpf *, const struct in_addr *);
+int bpf_filter_bootp(const struct bpf *, const struct in_addr *);
#endif
if (state->bpf != NULL)
return 0;
- state->bpf = bpf_open(ifp, bpf_bootp, NULL);
+ state->bpf = bpf_open(ifp, bpf_filter_bootp, NULL);
if (state->bpf == NULL) {
if (errno == ENOENT) {
logerrx("%s not found", bpf_name);
}
#ifdef INET
-/* Linux is a special snowflake when it comes to BPF. */
-const char *bpf_name = "Packet Socket";
-
-/* Linux is a special snowflake for opening BPF. */
-struct bpf *
-bpf_open(const struct interface *ifp,
- int (*filter)(const struct bpf *, const struct in_addr *),
- const struct in_addr *ia)
-{
- struct bpf *bpf;
- union sockunion {
- struct sockaddr sa;
- struct sockaddr_ll sll;
- struct sockaddr_storage ss;
- } su = { .sll = {
- .sll_family = PF_PACKET,
- .sll_protocol = htons(ETH_P_ALL),
- .sll_ifindex = (int)ifp->index,
- } };
-#ifdef PACKET_AUXDATA
- int n;
-#endif
-
- bpf = calloc(1, sizeof(*bpf));
- if (bpf == NULL)
- return NULL;
- bpf->bpf_ifp = ifp;
- bpf->bpf_flags = BPF_EOF;
-
- /* Allocate a suitably large buffer for a single packet. */
- bpf->bpf_size = ETH_FRAME_LEN;
- bpf->bpf_buffer = malloc(bpf->bpf_size);
- if (bpf->bpf_buffer == NULL)
- goto eexit;
-
- bpf->bpf_fd = xsocket(PF_PACKET, SOCK_RAW | SOCK_CXNB,
- htons(ETH_P_ALL));
- if (bpf->bpf_fd == -1)
- goto eexit;
-
- /* We cannot validate the correct interface,
- * so we MUST set this first. */
- if (bind(bpf->bpf_fd, &su.sa, sizeof(su.sll)) == -1)
- goto eexit;
-
- if (filter(bpf, ia) != 0)
- goto eexit;
-
- /* In the ideal world, this would be set before the bind and filter. */
-#ifdef PACKET_AUXDATA
- n = 1;
- if (setsockopt(bpf->bpf_fd, SOL_PACKET, PACKET_AUXDATA, &n,
- sizeof(n)) != 0) {
- if (errno != ENOPROTOOPT)
- goto eexit;
- }
-#endif
-
- /*
- * At this point we could have received packets for the wrong
- * interface or which don't pass the filter.
- * Linux should flush upon setting the filter like every other OS.
- * There is no way of flushing them from userland.
- * As such, consumers need to inspect each packet to ensure it's valid.
- * Or to put it another way, don't trust the Linux BPF filter.
- */
-
- return bpf;
-
-eexit:
- if (bpf->bpf_fd != -1)
- close(bpf->bpf_fd);
- free(bpf->bpf_buffer);
- free(bpf);
- return NULL;
-}
-
-/* BPF requires that we read the entire buffer.
- * So we pass the buffer in the API so we can loop on >1 packet. */
-ssize_t
-bpf_read(struct bpf *bpf, void *data, size_t len)
-{
- ssize_t bytes;
- struct iovec iov = {
- .iov_base = bpf->bpf_buffer,
- .iov_len = bpf->bpf_size,
- };
- struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
-#ifdef PACKET_AUXDATA
- union {
- struct cmsghdr hdr;
- uint8_t buf[CMSG_SPACE(sizeof(struct tpacket_auxdata))];
- } cmsgbuf = { .buf = { 0 } };
- struct cmsghdr *cmsg;
- struct tpacket_auxdata *aux;
-#endif
-
-#ifdef PACKET_AUXDATA
- msg.msg_control = cmsgbuf.buf;
- msg.msg_controllen = sizeof(cmsgbuf.buf);
-#endif
-
- bytes = recvmsg(bpf->bpf_fd, &msg, 0);
- if (bytes == -1)
- return -1;
- bpf->bpf_flags |= BPF_EOF; /* We only ever read one packet. */
- bpf->bpf_flags &= ~BPF_PARTIALCSUM;
- if (bytes) {
- if (bpf_frame_bcast(bpf->bpf_ifp, bpf->bpf_buffer) == 0)
- bpf->bpf_flags |= BPF_BCAST;
- else
- bpf->bpf_flags &= ~BPF_BCAST;
- if ((size_t)bytes > len)
- bytes = (ssize_t)len;
- memcpy(data, bpf->bpf_buffer, (size_t)bytes);
-#ifdef PACKET_AUXDATA
- for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
- cmsg = CMSG_NXTHDR(&msg, cmsg)) {
- if (cmsg->cmsg_level == SOL_PACKET &&
- cmsg->cmsg_type == PACKET_AUXDATA) {
- aux = (void *)CMSG_DATA(cmsg);
- if (aux->tp_status & TP_STATUS_CSUMNOTREADY)
- bpf->bpf_flags |= BPF_PARTIALCSUM;
- }
- }
-#endif
- }
- return bytes;
-}
-
-int
-bpf_attach(int s, void *filter, unsigned int filter_len)
-{
- struct sock_fprog pf = {
- .filter = filter,
- .len = (unsigned short)filter_len,
- };
-
- /* Install the filter. */
- if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) == -1)
- return -1;
-
-#ifdef SO_LOCK_FILTER
- int on = 1;
-
- if (setsockopt(s, SOL_SOCKET, SO_LOCK_FILTER, &on, sizeof(on)) == -1)
- return -1;
-#endif
-
- return 0;
-}
-
int
if_address(unsigned char cmd, const struct ipv4_addr *ia)
{
case PS_BPF_ARP:
psp->psp_proto = ETHERTYPE_ARP;
psp->psp_protostr = "ARP";
- psp->psp_filter = bpf_arp;
+ psp->psp_filter = bpf_filter_arp;
break;
#endif
case PS_BPF_BOOTP:
psp->psp_proto = ETHERTYPE_IP;
psp->psp_protostr = "BOOTP";
- psp->psp_filter = bpf_bootp;
+ psp->psp_filter = bpf_filter_bootp;
break;
}