Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
258 changes: 258 additions & 0 deletions src/bpf-bsd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/*
* 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;

/* /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;

bpf->bpf_ifp = ifp;
bpf->bpf_flags = BPF_EOF;

#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:
bpf_close(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;
if (bpf->bpf_len == 0) {
bytes = read(bpf->bpf_fd, bpf->bpf_buffer, bpf->bpf_size);
#ifdef __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);
return 0;
}
#endif
if (bytes == -1 || bytes == 0)
return bytes;
bpf->bpf_len = (size_t)bytes;
bpf->bpf_pos = 0;
}

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;
}
return bytes;

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
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void
bpf_close(struct bpf *bpf)
{
if (bpf->bpf_fd != -1)
close(bpf->bpf_fd);
free(bpf->bpf_buffer);
free(bpf);
}
Loading
Loading