Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 43 additions & 1 deletion configure
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ SMALL=
SANITIZE=no
STATUSARG=
OPENSSL=
LIBPCAP=

DHCPCD_DEFS=dhcpcd-definitions.conf

Expand Down Expand Up @@ -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;;
Comment on lines +123 to +124
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add libpcap options to ./configure --help.

Line 123 and Line 124 add new flags, but the help text block does not list them. This makes feature discovery harder for users relying on --help.

Suggested help text addition
 Optional Features:
   --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
   --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --with-libpcap          use libpcap backend for BPF support
+  --without-libpcap       disable libpcap backend
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configure` around lines 123 - 124, The new configure flags --with-libpcap and
--without-libpcap are missing from the script's help/usage text; update the help
block printed by the configure script (the usage/help string or print_help
function) to include brief entries for "--with-libpcap    enable building with
libpcap support" and "--without-libpcap   disable libpcap support" so they
appear in ./configure --help; locate the help text area near other option
descriptions (the block that lists flags like --with-* and --without-*) and add
these two lines consistent with existing formatting.

--sanitise|--sanitize) SANITIZEADDRESS="yes";;
--serviceexists) SERVICEEXISTS=$var;;
--servicecmd) SERVICECMD=$var;;
Expand Down Expand Up @@ -465,6 +468,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 ...
Expand Down Expand Up @@ -497,6 +501,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
Expand All @@ -516,6 +521,13 @@ solaris*|sunos*)
;;
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}"
else
Expand All @@ -529,7 +541,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
Expand Down Expand Up @@ -1506,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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
echo "#define USE_LIBPCAP" >>$CONFIG_H
fi

# Workaround for DragonFlyBSD import
if [ "$OS" = dragonfly ]; then
echo "#ifdef USE_PRIVATECRYPTO" >>$CONFIG_H
Expand Down
2 changes: 1 addition & 1 deletion src/arp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
261 changes: 261 additions & 0 deletions src/bpf-bsd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
/*
* 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 = "Berkeley 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;
size_t hdr_max;
const uint8_t *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;
if (bpf->bpf_pos + sizeof(packet) > bpf->bpf_len) {
errno = EINVAL;
goto err;
}
payload = (const uint8_t *)bpf->bpf_buffer + bpf->bpf_pos;
memcpy(&packet, payload, sizeof(packet));
hdr_max = SIZE_MAX - packet.bh_caplen;
if (packet.bh_hdrlen > hdr_max) {
errno = EOVERFLOW;
goto err;
}
if (packet.bh_hdrlen + packet.bh_caplen >
bpf->bpf_len - bpf->bpf_pos) {
errno = EBADMSG;
goto err;
}
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);
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 */

err:
bpf->bpf_len = bpf->bpf_pos = 0;
bpf->bpf_flags |= BPF_EOF;
return -1;
}

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);
}
Loading
Loading