]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
BPF: Add support for libpcap
authorJoan Lledó <jlledom@member.fsf.org>
Fri, 15 May 2026 09:30:24 +0000 (10:30 +0100)
committerRoy Marples <roy@marples.name>
Fri, 15 May 2026 09:30:24 +0000 (10:30 +0100)
This makes it easier to support OS's where we don't have kernel
support for any BPF ourselves, such as GNU/Hurd.

BUILDING.md
configure
src/bpf-pcap.c [new file with mode: 0644]
src/bpf.h

index 58ad8e8cf136ec204b4ea05d6220d3649fca0f9f..1b3fc700552f80d61dbad52f0a0a539c81a5c2eb 100644 (file)
@@ -139,6 +139,11 @@ are disabled by default.
 For Valgrind, it needs to unlink the pipe files which it can't do anyway
 as it's dropped permissions. Otherwise it works fine.
 
+Enable libpcap support with --with-libpcap.
+This should only be done on systems that lack the needed kernel hooks
+as libpcap does not support a write filter and is vulnerable
+if the application is exploited.
+
 ## Init systems
 We try and detect how dhcpcd should interact with system services at runtime.
 If we cannot auto-detect how do to this, or it is wrong then
index efd7e3b5fc7cd0ed6c00119f7f1e7814733579f5..a1776e720b05af40e06e5d5dae6f5ab61105b0c7 100755 (executable)
--- a/configure
+++ b/configure
@@ -42,6 +42,7 @@ SMALL=
 SANITIZE=no
 STATUSARG=
 OPENSSL=
+LIBPCAP=
 
 DHCPCD_DEFS=dhcpcd-definitions.conf
 
@@ -119,6 +120,8 @@ for x do
        --with-poll) POLL="$var";;
        --with-openssl) OPENSSL=yes;;
        --without-openssl) OPENSSL=no;;
+       --with-libpcap) LIBPCAP=yes;;
+       --without-libpcap) LIBPCAP=no;;
        --sanitise|--sanitize) SANITIZEADDRESS="yes";;
        --serviceexists) SERVICEEXISTS=$var;;
        --servicecmd) SERVICECMD=$var;;
@@ -521,6 +524,9 @@ esac
 if [ -z "$BPF_OS" ]; then
        BPF_OS="bpf-bsd.c"
 fi
+if [ "$LIBPCAP" = yes ]; then
+       BPF_OS="bpf-pcap.c"
+fi
 
 if [ -n "${_DEFAULT_HOSTNAME+x}" ]; then
        DEFAULT_HOSTNAME="${_DEFAULT_HOSTNAME}"
@@ -1512,6 +1518,36 @@ EOF
        rm -f _openssl_sha.c _openssl_sha
 fi
 
+if [ "$LIBPCAP" = yes ]; then
+       printf "Testing for libpcap ... "
+       if $PKG_CONFIG --exists libpcap 2>/dev/null; then
+               LIBPCAP_CFLAGS=$($PKG_CONFIG --cflags libpcap 2>/dev/null)
+               LIBPCAP_LIBS=$($PKG_CONFIG --libs libpcap 2>/dev/null)
+               echo "yes"
+               echo "CFLAGS+=  $LIBPCAP_CFLAGS" >>$CONFIG_MK
+               echo "LDADD+=           $LIBPCAP_LIBS" >>$CONFIG_MK
+       else
+               cat <<EOF >_libpcap.c
+#include <pcap.h>
+
+int main(void) {
+       return pcap_activate(NULL);
+}
+EOF
+               if $XCC _libpcap.c -o libpcap -lpcap 2>&3; then
+                       echo "yes"
+                       echo "LDADD+=           -lpcap" >>$CONFIG_MK
+                       abort=false
+               else
+                       echo "no"
+                       abort=true
+               fi
+               rm -f _libpcap.c libpcap
+               $abort && exit 1
+       fi
+       echo "#define   USE_LIBPCAP" >>$CONFIG_H
+fi
+
 # Workaround for DragonFlyBSD import
 if [ "$OS" = dragonfly ]; then
        echo "#ifdef    USE_PRIVATECRYPTO" >>$CONFIG_H
diff --git a/src/bpf-pcap.c b/src/bpf-pcap.c
new file mode 100644 (file)
index 0000000..93d294a
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * BPF libpcap interface
+ * SPDX-License-Identifier: BSD-2-Clause
+ * Copyright (c) 2025 Joan Lledó <jlledom@member.fsf.org>
+ * 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 <errno.h>
+#include <pcap.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bpf.h"
+#include "logerr.h"
+
+#define PCAP_CHECK(call, name)                                         \
+       do {                                                           \
+               int status = (call);                                   \
+               if (status < 0)                                        \
+                       logerrx("%s: %s failed: %s", __func__, name,   \
+                           pcap_statustostr(status));                 \
+               else if (status > 0)                                   \
+                       logwarnx("%s: %s warning: %s", __func__, name, \
+                           pcap_statustostr(status));                 \
+       } while (0)
+
+#define ETH_MTU 1500
+
+const char *bpf_name = "Berkley Packet Filter (libpcap)";
+
+struct bpf *
+bpf_open(const struct interface *ifp,
+    int (*filter)(const struct bpf *, const struct in_addr *),
+    __unused const struct in_addr *ia)
+{
+       int err;
+       struct bpf *bpf;
+       pcap_t *handle;
+       char errbuf[PCAP_ERRBUF_SIZE];
+       int snaplen;
+
+       bpf = calloc(1, sizeof(*bpf));
+       if (bpf == NULL)
+               return NULL;
+
+       snaplen = ifp->mtu ? ifp->mtu : ETH_MTU;
+       bpf->bpf_ifp = ifp;
+       bpf->bpf_size = bpf_frame_header_len(ifp) + (size_t)snaplen;
+       bpf->bpf_buffer = malloc(bpf->bpf_size);
+       if (bpf->bpf_buffer == NULL)
+               goto eexit;
+       bpf->bpf_len = 0;
+       bpf->bpf_pos = 0;
+       bpf->bpf_flags = BPF_EOF;
+
+       bpf->bpf_handle = handle = pcap_create(ifp->name, errbuf);
+       if (handle == NULL) {
+               logerrx("%s: pcap_create: %s", __func__, errbuf);
+               goto eexit;
+       }
+
+       PCAP_CHECK(pcap_set_snaplen(handle, snaplen), "pcap_set_snaplen");
+       PCAP_CHECK(pcap_set_promisc(handle, 0), "pcap_set_promisc");
+       PCAP_CHECK(pcap_set_immediate_mode(handle, 1),
+           "pcap_set_immediate_mode");
+
+       err = pcap_activate(handle);
+       if (err != 0) {
+               if (err < 0) {
+                       logerrx("%s: pcap_activate failed: %s", __func__,
+                           pcap_statustostr(err));
+                       goto eexit;
+               }
+               logwarnx("%s: pcap_activate warning: %s", __func__,
+                   pcap_statustostr(err));
+       }
+
+       bpf->bpf_fd = pcap_get_selectable_fd(handle);
+       if (bpf->bpf_fd < 0) {
+               logerrx("%s: pcap_get_selectable_fd failed", __func__);
+               goto eexit;
+       }
+
+       if (filter(bpf, ia) != 0)
+               goto eexit;
+
+       return bpf;
+
+eexit:
+       bpf_close(bpf);
+       return NULL;
+}
+
+ssize_t
+bpf_read(struct bpf *bpf, void *data, size_t len)
+{
+       struct pcap_pkthdr *pkt_header;
+       const u_char *pkt_data;
+       size_t cap_len;
+       int err;
+
+       bpf->bpf_flags |= BPF_EOF; /* We only read one packet per call */
+
+       err = pcap_next_ex(bpf->bpf_handle, &pkt_header, &pkt_data);
+
+       if (err < 0)
+               return -1;
+
+       /* Packet read successfully */
+       cap_len = pkt_header->caplen;
+       if (cap_len > len)
+               cap_len = len;
+       memcpy(data, pkt_data, cap_len);
+
+       return (ssize_t)cap_len;
+}
+
+ssize_t
+bpf_writev(const struct bpf *bpf, struct iovec *iov, int iovcnt)
+{
+       int i;
+       size_t len = 0;
+       uint8_t *bp = bpf->bpf_buffer;
+
+       for (i = 0; i < iovcnt; i++) {
+               len += iov[i].iov_len;
+               /* This should be impossible. */
+               if (bpf->bpf_size < len) {
+                       errno = ENOBUFS;
+                       return -1;
+               }
+               memcpy(bp, iov[i].iov_base, iov[i].iov_len);
+               bp += iov[i].iov_len;
+       }
+
+       return pcap_inject(bpf->bpf_handle, bpf->bpf_buffer, len);
+}
+
+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 pcap_setfilter(bpf->bpf_handle, &pf);
+}
+
+int
+bpf_setwfilter(__unused const struct bpf *bpf, __unused void *filter,
+    __unused unsigned int filter_len)
+{
+#warning A compromised libpcap socket can be used as a raw socket
+
+       errno = ENOSYS;
+       return -1;
+}
+
+int
+bpf_lock(__unused const struct bpf *bpf)
+{
+       errno = ENOSYS;
+       return -1;
+}
+
+void
+bpf_close(struct bpf *bpf)
+{
+       if (bpf->bpf_handle != NULL)
+               pcap_close(bpf->bpf_handle);
+       free(bpf->bpf_buffer);
+       free(bpf);
+}
index 031b8ce9484af7e333c3f1a90f6c1578a0ee7066..97248ab91ad042de9611de5453993c9d99fb929d 100644 (file)
--- a/src/bpf.h
+++ b/src/bpf.h
@@ -56,6 +56,7 @@
 
 struct bpf {
        const struct interface *bpf_ifp;
+       void *bpf_handle;
        int bpf_fd;
        unsigned int bpf_flags;
        void *bpf_buffer;