Skip to content

BPF: Split OS specific code out into own files and add libpcap support#607

Open
rsmarples wants to merge 4 commits into
masterfrom
bpf
Open

BPF: Split OS specific code out into own files and add libpcap support#607
rsmarples wants to merge 4 commits into
masterfrom
bpf

Conversation

@rsmarples
Copy link
Copy Markdown
Member

This makes it easy to maintain the BPF sources.
libpcap makes it easier to support new OS such as GNU/Hurd where we have no BPF support right away.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2e9239ab-0867-4aad-b5b2-84e1516b496f

📥 Commits

Reviewing files that changed from the base of the PR and between e91fd57 and 157f7c7.

📒 Files selected for processing (11)
  • BUILDING.md
  • configure
  • src/arp.c
  • src/bpf-bsd.c
  • src/bpf-linux.c
  • src/bpf-pcap.c
  • src/bpf.c
  • src/bpf.h
  • src/dhcp.c
  • src/if-linux.c
  • src/privsep-bpf.c
💤 Files with no reviewable changes (1)
  • src/if-linux.c

Walkthrough

Refactors BPF into a unified backend API, adds BSD, Linux (PF_PACKET), and libpcap backends, renames ARP/BOOTP filter entry points, adds bpf_setfilter/bpf_setwfilter/bpf_lock/bpf_writev, and adds configure-time libpcap detection plus BUILDING.md notes.

BPF abstraction and multi-platform implementations

Layer / File(s) Summary
Build configuration and documentation
configure, BUILDING.md
Adds --with-libpcap/--without-libpcap, LIBPCAP var, BPF_OS selection (defaults to bpf-bsd.c, bpf-linux.c on Linux, bpf-pcap.c when LIBPCAP=yes), appends $BPF_OS to DHCPCD_SRCS, performs libpcap detection (pkg-config or compile probe), and documents usage/security constraints in BUILDING.md.
BPF interface contract
src/bpf.h
Adds void *bpf_handle to struct bpf; renames bpf_arpbpf_filter_arp, bpf_bootpbpf_filter_bootp; adds declarations for bpf_setfilter, bpf_setwfilter, bpf_lock, and bpf_writev.
BSD BPF implementation
src/bpf-bsd.c
New backend implementing /dev/bpf open/read/filter install/lock/write/close, including kernel version checks, BIOC ioctls, buffer allocation, and SunOS read workaround.
Linux BPF implementation and if-linux.c refactoring
src/bpf-linux.c, src/if-linux.c
New PF_PACKET backend in bpf-linux.c (socket open, recvmsg read, SO_ATTACH_FILTER install, optional PACKET_AUXDATA, writev/close); removed previous Linux implementation code from if-linux.c.
libpcap BPF implementation
src/bpf-pcap.c
New libpcap backend wrapping pcap handle lifecycle, pcap_next_ex reads, pcap_inject for writes, pcap_setfilter for install; write-filter and lock remain unsupported (ENOSYS).
Core BPF filter management
src/bpf.c
Refactored to compute and install filters via bpf_setfilter/bpf_setwfilter, renamed exported entry points to bpf_filter_arp/bpf_filter_bootp, always attempts read/write installs and calls bpf_lock, and uses bpf_writev for sends.
API consumer updates
src/arp.c, src/dhcp.c, src/privsep-bpf.c
Callers updated to pass/assign bpf_filter_arp and bpf_filter_bootp where previously bpf_arp/bpf_bootp were used.

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly summarizes the main changes: splitting OS-specific BPF code into separate files and adding libpcap support, which aligns with the comprehensive restructuring shown in the changeset.
Description check ✅ Passed The description is relevant to the changeset, explaining the maintainability benefits and libpcap's role in supporting new OSes like GNU/Hurd where native BPF is unavailable.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bpf

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with 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.

Inline comments:
In `@configure`:
- Around line 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.
- Around line 1537-1545: The test compilation using $XCC with _libpcap.c and
-lpcap succeeds but the fallback branch doesn't persist the linker flag to
config.mk, causing unresolved pcap_* symbols after you `#define` USE_LIBPCAP;
update the success branch (the block that runs after $XCC _libpcap.c -o libpcap
-lpcap 2>&3 and sets abort=false) to append the required linker flag into
config.mk (e.g., add the -lpcap token to whatever variable holds linker flags in
config.mk) so that builds without pkg-config still link libpcap; keep the
existing cleanup and error-exit behavior unchanged.

In `@src/bpf-bsd.c`:
- Around line 162-166: The code copies a full struct bpf_hdr into 'packet' from
'payload' before ensuring the buffer contains sizeof(packet) bytes and then
computes packet.bh_caplen+packet.bh_hdrlen which can overflow; update the logic
to first compute 'remaining = bpf->bpf_len - bpf->bpf_pos' (use size_t) and
ensure remaining >= sizeof(struct bpf_hdr) before calling memcpy into 'packet',
then verify that adding bh_hdrlen and bh_caplen cannot overflow (e.g., check
bh_hdrlen <= SIZE_MAX - bh_caplen) and that bh_hdrlen + bh_caplen <= remaining
before using them; apply the same pre-check pattern to the other memcpy site
that reads a header (the block referencing payload/memcpy around lines
~178-179).

In `@src/bpf-linux.c`:
- Around line 62-73: The bpf structure's file descriptor is left at 0 on early
failure paths, which causes eexit to mistakenly close stdin; after allocating
bpf (the calloc result used via variable bpf) set bpf->bpf_fd = -1 immediately
(right after bpf = calloc(...) and before any potential goto eexit) so any
cleanup that closes the fd (in the eexit/cleanup path) will skip closing an
invalid/standard fd; also ensure the same initialization applies before any
later failure paths around socket creation/assignment that rely on bpf->bpf_fd
(the code handling bpf->bpf_fd in the eexit block).

In `@src/bpf-pcap.c`:
- Around line 125-134: pcap_next_ex(bpf->bpf_handle, &pkt_header, &pkt_data) can
return 0 for a timeout and leaves pkt_header/pkt_data uninitialized; update the
code after calling pcap_next_ex to explicitly check for err == 0 and return 0
(no packet) before any use of pkt_header or pkt_data, keeping the existing err <
0 check unchanged and then proceed to read pkt_header->caplen and memcpy only
when err > 0.

In `@src/bpf.c`:
- Around line 365-368: The trailing unreachable "return -1;" should be removed
from the end of the function since both branches already return (calls to
bpf_setfilter and bpf_setwfilter return unconditionally); edit the block where
len is computed and the conditional on recv (the lines using len, recv,
bpf_setfilter and bpf_setwfilter) to delete the stray "return -1;" so there is
no dead code left behind.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a35dad7-9695-47fe-96f9-32a2e8115435

📥 Commits

Reviewing files that changed from the base of the PR and between e91fd57 and 43e9f47.

📒 Files selected for processing (11)
  • BUILDING.md
  • configure
  • src/arp.c
  • src/bpf-bsd.c
  • src/bpf-linux.c
  • src/bpf-pcap.c
  • src/bpf.c
  • src/bpf.h
  • src/dhcp.c
  • src/if-linux.c
  • src/privsep-bpf.c
💤 Files with no reviewable changes (1)
  • src/if-linux.c

Comment thread configure
Comment on lines +123 to +124
--with-libpcap) LIBPCAP=yes;;
--without-libpcap) LIBPCAP=no;;
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.

Comment thread configure
Comment thread src/bpf-bsd.c Outdated
Comment thread src/bpf-linux.c
Comment thread src/bpf-pcap.c
Comment thread src/bpf.c Outdated
This makes it easier to support OS's where we don't have kernel
support for any BPF ourselves, such as GNU/Hurd.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/bpf.c (1)

501-511: 💤 Low value

Indentation inconsistency on line 505.

Line 505 ( return -1;) appears to be indented with spaces while the surrounding code (e.g., line 507) uses a tab. Cosmetic, but it sticks out in a file that otherwise uses tabs consistently.

✏️ Proposed fix
 	if (bpf_bootp_rw(bpf, true) == -1)
-	       return -1;
+		return -1;
 	if (bpf_bootp_rw(bpf, false) == -1 && errno != ENOSYS)
 		return -1;
🤖 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 `@src/bpf.c` around lines 501 - 511, In bpf_filter_bootp, the indentation of
the early return statement in the bpf_bootp_rw(true) error branch uses spaces
instead of the file's tab-based indentation; update the indentation of the
`return -1;` inside the bpf_filter_bootp function so it uses a single leading
tab (matching surrounding lines and the style used in bpf_bootp_rw and bpf_lock
branches) to keep formatting consistent.
🤖 Prompt for all review comments with 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.

Nitpick comments:
In `@src/bpf.c`:
- Around line 501-511: In bpf_filter_bootp, the indentation of the early return
statement in the bpf_bootp_rw(true) error branch uses spaces instead of the
file's tab-based indentation; update the indentation of the `return -1;` inside
the bpf_filter_bootp function so it uses a single leading tab (matching
surrounding lines and the style used in bpf_bootp_rw and bpf_lock branches) to
keep formatting consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0041c6fe-df52-47ef-86e5-8f753138e5e9

📥 Commits

Reviewing files that changed from the base of the PR and between 43e9f47 and 4a4584c.

📒 Files selected for processing (7)
  • BUILDING.md
  • configure
  • src/bpf-bsd.c
  • src/bpf-linux.c
  • src/bpf-pcap.c
  • src/bpf.c
  • src/bpf.h
✅ Files skipped from review due to trivial changes (1)
  • BUILDING.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/bpf.h
  • configure
  • src/bpf-linux.c
  • src/bpf-pcap.c
  • src/bpf-bsd.c

@rsmarples rsmarples force-pushed the bpf branch 2 times, most recently from 8359aeb to b7e44d0 Compare May 15, 2026 13:58
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/bpf-bsd.c (2)

162-196: 💤 Low value

Redundant bytes != -1 check at end of loop.

bytes is set to -1 at line 162 only as a temporary marker; on every error path it jumps to err: via goto, and on the success path lines 181/183 unconditionally assign a non-negative value to bytes. The check at line 195 is therefore always true and the loop never actually iterates a second time through the parsing block. This obscures intent. Consider returning directly after the parse succeeds.

🤖 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 `@src/bpf-bsd.c` around lines 162 - 196, The final conditional "if (bytes !=
-1) return bytes;" is redundant because bytes is set to a non-negative value on
every successful parse path; remove that check and return bytes directly after
memcpy/data update and after advancing bpf->bpf_pos (i.e., replace the
conditional with an unconditional return of bytes), leaving the existing error
paths that goto err: intact; locate references to the local variable bytes, the
payload/copy logic, and the err: label in the bpf packet parsing block to make
the change.

127-131: 💤 Low value

Inconsistent cleanup in eexit: bpf->bpf_buffer is not freed.

If a future failure path is added after line 121 (after bpf_buffer is successfully allocated), this would leak the buffer. The sibling src/bpf-linux.c (lines 110-114) defensively frees both bpf_buffer and bpf. For consistency and resilience to future edits, mirror that pattern here.

♻️ Proposed fix
 eexit:
 	if (bpf->bpf_fd != -1)
 		close(bpf->bpf_fd);
+	free(bpf->bpf_buffer);
 	free(bpf);
 	return NULL;
🤖 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 `@src/bpf-bsd.c` around lines 127 - 131, The eexit cleanup currently closes
bpf->bpf_fd and frees bpf but forgets to free bpf->bpf_buffer; update the eexit
block to check and free bpf->bpf_buffer (e.g., if (bpf->bpf_buffer)
free(bpf->bpf_buffer);) before freeing bpf so the function mirrors the defensive
cleanup used in src/bpf-linux.c and avoids leaking the buffer if allocation
succeeded earlier.
src/bpf-linux.c (1)

142-167: 💤 Low value

Truncation of oversized frames is not signaled to the caller.

recvmsg is called without MSG_TRUNC, and msg.msg_flags is not inspected. If a frame larger than ETH_FRAME_LEN (e.g., VLAN-tagged or jumbo) arrives, the kernel silently truncates the returned length and the caller has no way to detect it. Since DHCP frames typically fit, this is unlikely to be hit in practice, but consider checking msg.msg_flags & MSG_TRUNC (or sizing the buffer to also cover the AUXDATA scenario) so that a truncated frame is treated as an error rather than parsed as if complete.

🤖 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 `@src/bpf-linux.c` around lines 142 - 167, Call recvmsg with MSG_TRUNC (e.g.,
recvmsg(bpf->bpf_fd, &msg, MSG_TRUNC)) and after the call inspect msg.msg_flags
for MSG_TRUNC; if set treat the read as an error (return -1 or propagate an
error) instead of silently truncating the frame. Update the recv path that
touches bpf->bpf_fd, msg, msg.msg_flags, bpf->bpf_buffer and the existing
truncation check (the (size_t)bytes > len branch) so oversized frames (larger
than ETH_FRAME_LEN or jumbo/VLAN frames) are detected via msg.msg_flags &
MSG_TRUNC and not incorrectly processed.
src/bpf-pcap.c (1)

141-160: 💤 Low value

bpf_writev returns pcap_inject's int and may silently drop the error value range.

pcap_inject returns int (number of bytes injected, or PCAP_ERROR / -1 on error). The implicit conversion to ssize_t is fine for current frame sizes, but if pcap_inject ever returns a value below -1 (e.g., extended PCAP_ERROR_* codes), callers comparing only == -1 may misinterpret the result. Consider normalizing to -1 on any negative return and propagating errno (libpcap doesn't set errno; you may want to call pcap_geterr and log it).

What error values can pcap_inject() return according to the libpcap documentation?
🤖 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 `@src/bpf-pcap.c` around lines 141 - 160, bpf_writev currently returns
pcap_inject()'s raw int which can be <-1 and confuse callers; change bpf_writev
to treat any negative pcap_inject return as a generic error: call
pcap_geterr(bpf->bpf_handle) to obtain the libpcap error string (and log it or
include it in diagnostics), set errno to a suitable POSIX error (e.g., EIO) and
return -1, otherwise return the positive byte count as ssize_t; update
references to bpf_writev and document that callers should still check for -1.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/bpf-bsd.c`:
- Line 43: The exported string constant bpf_name contains a misspelling
("Berkley Packet Filter"); update the value to the correct spelling "Berkeley
Packet Filter" and make the identical fix for the same constant in the other
translation unit that defines it (the occurrence in bpf-pcap.c), ensuring both
definitions use the canonical "Berkeley" spelling to keep logs/user-facing text
consistent.

In `@src/bpf-pcap.c`:
- Line 50: The user-visible typo is in the const char *bpf_name definition
("Berkley Packet Filter (libpcap)") — update the string to "Berkeley Packet
Filter (libpcap)". Also check and fix the identical typo in the corresponding
bpf_name in src/bpf-bsd.c (the same variable/string) so both user-facing strings
read "Berkeley".
- Around line 115-139: The libpcap backend in bpf_read does not update the
BPF_BCAST flag, causing stale flag state; after a successful pcap_next_ex read
(in function bpf_read) compute cap_len as you already do, then call the existing
helper bpf_frame_bcast(pkt_data, cap_len) and, if it returns true, set
bpf->bpf_flags |= BPF_BCAST (clearing it otherwise is optional if other code
expects per-packet setting); ensure you reference pkt_data and cap_len (same
locals) and update bpf->bpf_flags before returning the captured length so
ARP/other callers see the correct BPF_BCAST state.

---

Nitpick comments:
In `@src/bpf-bsd.c`:
- Around line 162-196: The final conditional "if (bytes != -1) return bytes;" is
redundant because bytes is set to a non-negative value on every successful parse
path; remove that check and return bytes directly after memcpy/data update and
after advancing bpf->bpf_pos (i.e., replace the conditional with an
unconditional return of bytes), leaving the existing error paths that goto err:
intact; locate references to the local variable bytes, the payload/copy logic,
and the err: label in the bpf packet parsing block to make the change.
- Around line 127-131: The eexit cleanup currently closes bpf->bpf_fd and frees
bpf but forgets to free bpf->bpf_buffer; update the eexit block to check and
free bpf->bpf_buffer (e.g., if (bpf->bpf_buffer) free(bpf->bpf_buffer);) before
freeing bpf so the function mirrors the defensive cleanup used in
src/bpf-linux.c and avoids leaking the buffer if allocation succeeded earlier.

In `@src/bpf-linux.c`:
- Around line 142-167: Call recvmsg with MSG_TRUNC (e.g., recvmsg(bpf->bpf_fd,
&msg, MSG_TRUNC)) and after the call inspect msg.msg_flags for MSG_TRUNC; if set
treat the read as an error (return -1 or propagate an error) instead of silently
truncating the frame. Update the recv path that touches bpf->bpf_fd, msg,
msg.msg_flags, bpf->bpf_buffer and the existing truncation check (the
(size_t)bytes > len branch) so oversized frames (larger than ETH_FRAME_LEN or
jumbo/VLAN frames) are detected via msg.msg_flags & MSG_TRUNC and not
incorrectly processed.

In `@src/bpf-pcap.c`:
- Around line 141-160: bpf_writev currently returns pcap_inject()'s raw int
which can be <-1 and confuse callers; change bpf_writev to treat any negative
pcap_inject return as a generic error: call pcap_geterr(bpf->bpf_handle) to
obtain the libpcap error string (and log it or include it in diagnostics), set
errno to a suitable POSIX error (e.g., EIO) and return -1, otherwise return the
positive byte count as ssize_t; update references to bpf_writev and document
that callers should still check for -1.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 95d7ecba-a659-4638-bed4-3f52c7e1b072

📥 Commits

Reviewing files that changed from the base of the PR and between 4a4584c and b7e44d0.

📒 Files selected for processing (4)
  • src/bpf-bsd.c
  • src/bpf-linux.c
  • src/bpf-pcap.c
  • src/bpf.c
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/bpf.c

Comment thread src/bpf-bsd.c Outdated
Comment thread src/bpf-pcap.c Outdated
Comment thread src/bpf-pcap.c
@rsmarples
Copy link
Copy Markdown
Member Author

@coderabbitai full revie

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

@rsmarples: Sure! I'll redo the full review of PR #607.

✅ Actions performed

Full review triggered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants