]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
BPF: Simplify by splitting OS specifics into own files
authorRoy Marples <roy@marples.name>
Thu, 14 May 2026 20:36:09 +0000 (21:36 +0100)
committerRoy Marples <roy@marples.name>
Thu, 14 May 2026 20:36:09 +0000 (21:36 +0100)
configure
src/arp.c
src/bpf-bsd.c [new file with mode: 0644]
src/bpf-linux.c [new file with mode: 0644]
src/bpf.c
src/bpf.h
src/dhcp.c
src/if-linux.c
src/privsep-bpf.c

index 0cb6f2880257f2475a9940be342ee2de7fcd7d91..efd7e3b5fc7cd0ed6c00119f7f1e7814733579f5 100755 (executable)
--- a/configure
+++ b/configure
@@ -465,6 +465,7 @@ if [ "$SMALL" = yes ]; then
        echo "DHCPCD_DEFS=      $DHCPCD_DEFS" >>$CONFIG_MK
 fi
 
+BPF_OS=
 case "$OS" in
 freebsd*|kfreebsd*)
        # FreeBSD hide some newer POSIX APIs behind _GNU_SOURCE ...
@@ -497,6 +498,7 @@ linux*)
        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
@@ -516,6 +518,10 @@ solaris*|sunos*)
        ;;
 esac
 
+if [ -z "$BPF_OS" ]; then
+       BPF_OS="bpf-bsd.c"
+fi
+
 if [ -n "${_DEFAULT_HOSTNAME+x}" ]; then
        DEFAULT_HOSTNAME="${_DEFAULT_HOSTNAME}"
 else
@@ -529,7 +535,7 @@ echo "DEFAULT_HOSTNAME=             $DEFAULT_HOSTNAME" >>$CONFIG_MK
 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
index 9c9a6d3ac1f1c4203b00023069eff33934efea3d..7d4f1e78c1538055cfa5e4c818dadf6f2457f20f 100644 (file)
--- a/src/arp.c
+++ b/src/arp.c
@@ -562,7 +562,7 @@ arp_new(struct interface *ifp, const struct in_addr *addr)
        } 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);
diff --git a/src/bpf-bsd.c b/src/bpf-bsd.c
new file mode 100644 (file)
index 0000000..c5cfb87
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * 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);
+}
diff --git a/src/bpf-linux.c b/src/bpf-linux.c
new file mode 100644 (file)
index 0000000..9ec6779
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * 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);
+}
+
index a644a42c525c357498db9d637de031c598bcfe50..2cccc5f70fe0041dcd29ca97bf0b174f54c50ef3 100644 (file)
--- a/src/bpf.c
+++ b/src/bpf.c
@@ -138,181 +138,7 @@ bpf_frame_bcast(const struct interface *ifp, const void *frame)
        }
 }
 
-#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)
 {
@@ -335,17 +161,10 @@ 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)
@@ -489,6 +308,7 @@ bpf_arp_rw(const struct bpf *bpf, const struct in_addr *ia, bool recv)
        struct bpf_insn buf[BPF_ARP_LEN + 1];
        struct bpf_insn *bp;
        uint16_t arp_len;
+       unsigned int len;
 
        bp = buf;
        /* Check frame header. */
@@ -542,26 +362,22 @@ bpf_arp_rw(const struct bpf *bpf, const struct in_addr *ia, bool recv)
        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
 
@@ -617,7 +433,6 @@ static const struct bpf_insn bpf_bootp_read[] = {
 };
 #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,
@@ -627,7 +442,6 @@ static const struct bpf_insn bpf_bootp_write[] = {
        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 */
@@ -664,7 +478,6 @@ bpf_bootp_rw(const struct bpf *bpf, bool read)
        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;
@@ -673,11 +486,8 @@ bpf_bootp_rw(const struct bpf *bpf, bool read)
                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;
@@ -686,26 +496,17 @@ bpf_bootp_rw(const struct bpf *bpf, bool read)
        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
 }
index f15379b099b00cea6087b8c063c540871420e237..031b8ce9484af7e333c3f1a90f6c1578a0ee7066 100644 (file)
--- a/src/bpf.h
+++ b/src/bpf.h
@@ -63,6 +63,7 @@ struct bpf {
        size_t bpf_len;
        size_t bpf_pos;
 };
+struct iovec;
 
 extern const char *bpf_name;
 size_t bpf_frame_header_len(const struct interface *);
@@ -73,9 +74,13 @@ struct bpf *bpf_open(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
index 733e6ffd6fae4426bfd6f05c6b18c87deceff1b4..f7195bc7e6cb2ba186654f6215c283d318f6c51a 100644 (file)
@@ -3859,7 +3859,7 @@ dhcp_openbpf(struct interface *ifp)
        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);
index 0b7d18b094b8e94f9df04f5cbd707ab18d25f138..3f678d2954c74c14fba6629a3658075ad5e55c0b 100644 (file)
@@ -1807,158 +1807,6 @@ if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *kroutes, int af)
 }
 
 #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)
 {
index dab7b80261cec921cfaa3179cabde0ba4d6f23d5..940c576e2d46702f34988228885bbf1e324035d0 100644 (file)
@@ -240,13 +240,13 @@ ps_bpf_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg)
        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;
        }