Skip to content

plugin: add runtime plugin loader for external capture backends including dpdk#1659

Open
vjardin wants to merge 3 commits into
the-tcpdump-group:masterfrom
vjardin:vj_dpdkplugins
Open

plugin: add runtime plugin loader for external capture backends including dpdk#1659
vjardin wants to merge 3 commits into
the-tcpdump-group:masterfrom
vjardin:vj_dpdkplugins

Conversation

@vjardin

@vjardin vjardin commented Mar 20, 2026

Copy link
Copy Markdown

Dataplanes like DPDK expose unstable APIs that change across releases. Compiling backends for these dataplanes directly into libpcap ties the two release cycles together: a DPDK API change breaks the libpcap build, and fixing it requires a coordinated update of both projects. The same problem applies to any fast-moving dataplane (grout, VPP, etc.) whose capture interface evolves independently from libpcap.

Add a generic plugin loader (pcap-plugin.c) that discovers external capture backends at runtime. Each backend ships as a pcap-*.so shared module in a well-known directory. The loader dlopen's every matching file, looks up a "pcap_plugin_entry" symbol, validates the ABI version, and dispatches findalldevs/create calls through it. Backends are found in $PCAP_PLUGIN_DIR (colon-separated), /usr/lib/pcap/plugins, and /usr/local/lib/pcap/plugins.

Because pcap-int.h is not installed, plugins cannot access the pcap_t struct directly. A set of pcap_plugin_* accessor functions is exported with default visibility: handle allocation, ops registration, field getters/setters, BPF helpers, and device enumeration. This gives plugins a stable ABI (versioned via PCAP_PLUGIN_ABI_VERSION) without exposing libpcap internals.

With this mechanism, a dataplane project builds its own pcap-*.so against its own headers and ships it alongside its packages. Upgrading the dataplane only requires rebuilding the plugin, not libpcap. If the plugin is absent or fails to load, libpcap keeps working — no compile time dependency, no conditional #ifdefs.

@guyharris

Copy link
Copy Markdown
Member

There's already a code-loading API, pcapint_load_code(), in pcap.c.

It currently has only a Windows implementation, as it was only used to load AirPcap support code, allowing the same code to work regardless of whether AirPcap support was installed. It's currently unused in the main branch, as we removed AirPcap support (Riverbed's AirPcap devices are no longer supported by the vendor).

You should probably add UN*X support to that, and use it.

@guyharris

Copy link
Copy Markdown
Member

Loading plugins should be done with great care, as opening capture devices requires elevated privileges on many platforms, which means you may be loading plugins into a program running with elevated privileges.

At minimum, plugin-loading code should require that the plugin be loaded from a specific trusted directory (or maybe one of a small set of trusted directories) and that neither the directory nor the plugin file be writable by unprivileged users.

There may be - in fact, there probably are - more things to worry about.

See, for example:

Those were found with a Google search for "securely allowing dlopen for privileged code"; there may be others. A similar search, using LoadLibrary rather than dlopen, might also be useful.

Supporting plugin modules for libpcap would be useful in that it would, as you note, allow third parties to maintain their modules independently; the reason why I, at least, haven't done more than think about it is that it really needs to be secure.

(That also brings up the problem that opening a capture device may require privileges that you really don't want the program using. Perhaps the opening should be done by a program that runs with privileges and hands a file descriptor to libpcap over a UNIX-domain socket. The program still has some privileges, in that it has a descriptor for a capture device, but it's not as if it has root privileges if that's what opening the capture device required. But that's another discussion.)

@fxlb

fxlb commented Mar 20, 2026

Copy link
Copy Markdown
Member

Adding plugins into a security-sensitive library like libpcap need conducting an in-depth security risk assessment.

@mcr

mcr commented Mar 20, 2026

Copy link
Copy Markdown
Member

So I think that we are all pretty wary about any kind of plugin system.
I suggest that DPDK look at rpcapd.c, and consider bringing that into DPDK core, where the dependancy/API drift can be better dealt with within the DPDK CI. The tcpdump/wireshark/libpcap interface is then rpcap protocol to localhost or Unix domain socket.
Maybe that also facilitates better JIT/adaption of pcap filters into DPDK runtime, which might also involve pushing filters down to smart NICs.

@rjarry

rjarry commented Mar 20, 2026

Copy link
Copy Markdown

Hey folks,

Thanks for the review and all the security refs, those are the right questions to ask.

On the rpcapd idea: the backends we're after (OVS-DPDK, grout, etc.) need to do packet capture into shared memory with no syscalls on the data path. The libpcap side would just mmap a file descriptor it gets from the dataplane over a unix socket. Going through rpcap's TCP/UDP sockets would negate the whole point -- you'd end up paying for copies and syscalls on every packet, which is exactly what these backends try to get rid of.

That said, the dlopen-in-a-privileged-process concern is legit and needs to be dealt with. I think there's a reasonable way to go about it though:

Before loading any plugin, libpcap could check if the process has elevated privileges (setuid/setgid, file capabilities on Linux). If so, either refuse to load plugins entirely, or drop the extra privileges first and only go ahead if the drop actually worked. That way a single tcpdump binary could still do tcpdump -i eth0 (needs raw socket privileges) and tcpdump -i $backend:port0 (just talks to a unix socket, no special privileges needed). These plugin backends won't need elevated privileges -- the dataplane process owns the capture, libpcap just reads from it.

In practice this probably means libpcap shouldn't load plugins silently on its own but instead expose something like pcap_load_plugins() that apps call explicitly. tcpdump would open its raw sockets, drop privileges, then call pcap_load_plugins(). Keeps the app in control instead of libpcap doing something unexpected behind its back.

Also $PCAP_PLUGIN_DIR should be ignored when running with elevated privileges, same way the dynamic linker drops LD_LIBRARY_PATH for setuid binaries. Only load from compiled-in system directories in that case, with ownership and permission checks on both the directory and the .so files.

Does that sound workable?

PS: if we want yet another layer on top, we could add optional signature verification before dlopen -- libpcap looks for a detached signature (e.g. pcap-foo.so.sig) next to each plugin and checks it against trusted public keys in a root-owned directory (say /etc/libpcap/trusted-keys/). Similar to how distros manage CA bundles or RPM signing keys -- the sysadmin decides what's trusted, not libpcap upstream. Though honestly, plugins installed via a package manager in root-owned system directories have already gone through the distro's own package signing, so it might be overkill. Worth discussing if the added complexity buys anything over the privilege checks and directory ownership validation alone.

Cheers

@infrastation

Copy link
Copy Markdown
Member

Thank you for attempting this development, it certainly has its pro et contra. The first committed version would need to be safe and maintainable, and it would take some more design and implementation work to get there. If there is a similar library that has already solved a very similar problem using plug-ins, it could be used as a model to relate the problem space and the solution space.

We are trying to make a few releases before long, so substantial changes and new features are currently mostly on hold. That said, why not consider this matter in the background meanwhile.

I agree that potentially there may be more sense in DPDK developing a stable tiny API just for packet capture (whether via libpcap or not), and hiding the internal churn behind that interface. That said, rpcap may be not the best interface/transport for that.

@infrastation infrastation added the DPDK Data Plane Development Kit label Mar 20, 2026
@vjardin

vjardin commented Mar 20, 2026

Copy link
Copy Markdown
Author

Thanks for all the feedback. I've scoped the plugin loader to mitigate the security concerns around loading external .so files

@vjardin vjardin force-pushed the vj_dpdkplugins branch 2 times, most recently from bf4e767 to 577f370 Compare March 21, 2026 00:05
Comment thread pcap-plugin.c Outdated
@fxlb

fxlb commented Mar 21, 2026

Copy link
Copy Markdown
Member

If there is a similar library that has already solved a very similar problem using plug-ins, it could be used as a model to relate the problem space and the solution space.

+1.

@vjardin

vjardin commented Mar 21, 2026

Copy link
Copy Markdown
Author

If there is a similar library that has already solved a very similar problem using plug-ins, it could be used as a model to relate the problem space and the solution space.

+1.

I did start checking. If you have a taxonomy I could refer too, can you share those ? Any URLs on their git repo ?

@vjardin

vjardin commented Mar 21, 2026

Copy link
Copy Markdown
Author

Enclosed this minor update for the BSD systems.

@mcr

mcr commented Mar 21, 2026

Copy link
Copy Markdown
Member

So I think that we are all pretty wary about any kind of plugin system. I suggest that DPDK look at rpcapd.c, and consider bringing that into DPDK core, where the dependancy/API drift can be better dealt with within the DPDK CI. The tcpdump/wireshark/libpcap interface is then rpcap protocol to localhost or Unix domain socket. Maybe that also facilitates better JIT/adaption of pcap filters into DPDK runtime, which might also involve pushing filters down to smart NICs.

The more I think about it, the more this makes more sense.
Rather than throw a plugin at libpcap and hope the DPDK user/developer/operator has the most recent one, put this functionality with DPDK. I suggest copying libpcap/rpcapd [yes, fork it] to DPDK, along with just enough of libpcap so that you can statically compile an "dpdk-pcapd" as part of DPDK. That allows DPDK to evolve its capture mechanism(s), optimize your BPF (or another thing), deal with any multi-threading, locking or scaling issues, and also deal with any permissions issues that might arise.

@vjardin

vjardin commented Mar 21, 2026

Copy link
Copy Markdown
Author

How to get high performances with your proposal ?

vjardin added 2 commits March 23, 2026 18:01
Dataplanes like DPDK expose unstable APIs that change across releases.
Compiling backends directly into libpcap ties the two release cycles
together. Add a generic plugin loader (pcap-plugin.c) that discovers
external capture backends at runtime, decoupling libpcap from backend
release cycles.

Each backend ships as a pcap-*.so shared module. The loader scans
plugin directories, loads each via the centralized pcapint_load_code()
API (extended from Windows-only to Unix with dlopen/dlsym), looks up
a "pcap_plugin_entry" symbol, checks the ABI version, and dispatches
findalldevs/create calls through it.

A set of pcap_plugin_* accessor functions (pcap/pcap-plugin.h) provides
plugins a stable ABI without exposing pcap-int.h internals: handle
allocation, ops registration, field getters/setters, BPF helpers, and
device enumeration.

Security model, modeled after OpenSSL provider loading:

A survey of PAM, NSS (glibc), GStreamer, OpenSSL (providers), and Mesa
shows they all converge on the same model: a hardcoded directory as the
default, with environment variable overrides ignored under elevated
privileges. None perform lstat(), ownership checks, or symlink
validation.

  - $PCAP_PLUGIN_DIR is read through secure_getenv() on glibc, with
    fallbacks for BSD (issetugid), Linux without glibc (getauxval
    AT_SECURE), and other Unix (uid/euid comparison). Under elevated
    privileges the env var is automatically ignored, matching how
    OpenSSL handles OPENSSL_MODULES and how ld-linux.so handles
    LD_LIBRARY_PATH.
  - The hardcoded plugin directory (PCAP_PLUGIN_DIR, default
    ${libdir}/pcap/plugins) is always scanned.
  - Filesystem permissions on the plugin directory are the security
    boundary, same as PAM, NSS, OpenSSL, and Mesa.
  - plugindir is exposed in libpcap.pc so external projects can query
    the install path at build time with:
      pkg-config --variable=plugindir libpcap
    This follows the GStreamer model (pluginsdir in gstreamer-1.0.pc).

Signed-off-by: Vincent Jardin <vjardin@free.fr>
Telecom-grade dataplanes (radio-based routers)
need nanosecond-accurate timestamps to measure inter-packet jitter and
verify hardware pacing. Default host timestamps (software clock at
packet delivery) give microsecond-level accuracy at best, which is
insufficient for sub-microsecond scheduling verification on 10G+ links.

Modern NICs (ConnectX-6 Dx, ConnectX-7) stamp each received packet
with a hardware clock at wire arrival time. The kernel exposes this
via AF_PACKET's PACKET_TIMESTAMP / SOF_TIMESTAMPING_RAW_HARDWARE,
and tcpdump accesses it with -j adapter. External capture plugins
need the same mechanism.

Add two accessors to the plugin API:

  pcap_plugin_get_tstamp_type() — read the type the user requested
  pcap_plugin_set_tstamp_type_list() — advertise supported types

Plugins call set_tstamp_type_list during create so that
pcap_list_tstamp_types (tcpdump -J) reports the available clock
sources. During activate, get_tstamp_type tells the plugin whether
the user asked for PCAP_TSTAMP_ADAPTER (NIC clock, synced to
real-time), PCAP_TSTAMP_ADAPTER_UNSYNCED (raw NIC clock), or
PCAP_TSTAMP_HOST (default software clock). The plugin then configures
its backend accordingly.

Signed-off-by: Vincent Jardin <vjardin@free.fr>
@vjardin

vjardin commented Mar 23, 2026

Copy link
Copy Markdown
Author

Based on a taxonomy that @maxime-leroy shared with me in DM, I did simplify and limit the security surface of the plugin. See the latest update.

Moreover, I tried to enrich all the cases a DPDK plugin could need.

The plugin API lacked accessors for several pcap_t options that
tcpdump (and other consumers) set before pcap_activate():
promiscuous mode, buffer size, immediate mode, and timestamp
precision. Without these, a plugin backend could not honor
user-requested capture settings.

Similarly, plugins had no way to advertise supported timestamp
precisions or data link types back to libpcap, so
pcap_list_tstamp_precisions() and pcap_list_datalinks() would
return empty results for plugin-backed devices. Plugins also
could not provide a selectable file descriptor for event-driven
callers.

New getters (plugin reads during activate):
  pcap_plugin_get_promisc()
  pcap_plugin_get_buffer_size()
  pcap_plugin_get_immediate()
  pcap_plugin_get_tstamp_precision()

New setters (plugin advertises capabilities):
  pcap_plugin_set_tstamp_precision_list()
  pcap_plugin_set_datalink_list()
  pcap_plugin_set_selectable_fd()

Signed-off-by: Vincent Jardin <vjardin@free.fr>
@vjardin

vjardin commented Mar 26, 2026

Copy link
Copy Markdown
Author

The ci seems to be flakky : choco times out or does not prepare properly the Windows environment ; s390x timeout on an unrelated topic.

@vjardin

vjardin commented Mar 26, 2026

Copy link
Copy Markdown
Author

@guyharris : please, can I get your review on it ?

@guyharris

Copy link
Copy Markdown
Member

The ci seems to be flakky : choco times out or does not prepare properly the Windows environment

AppVeyor isn't the best. At one point I looked at another one - I think it was Cirrus CI - but they didn't have anything with Visual Studio pre-installed, and having to install it every time would impose a significant hit to CI time. Maybe it's changed, or maybe there's a way to get it done once and reuse it.

s390x timeout on an unrelated topic.

For some reason it's currently prone to timeouts in the filter tests. The only thing noteworthy about it is that it's big-endian (which is a feature, not a bug, until the last big-endian machine running anything UN*Xy is shut down, and IBM doesn't seem to be in a hurry to get rid of either z/Architecture or Linux on Z); it's not as if those machines are exactly tiny machines with slow CPUs.

Maybe there's some issue with big-endian machines in the filter code.

@guyharris

Copy link
Copy Markdown
Member

The ci seems to be flakky : choco times out or does not prepare properly the Windows environment

AppVeyor isn't the best. At one point I looked at another one - I think it was Cirrus CI - but they didn't have anything with Visual Studio pre-installed, and having to install it every time would impose a significant hit to CI time. Maybe it's changed, or maybe there's a way to get it done once and reuse it.

I've hit "re-run incomplete" on the Appveyor build.

@guyharris

Copy link
Copy Markdown
Member

For some reason it's currently prone to timeouts in the filter tests. The only thing noteworthy about it is that it's big-endian (which is a feature, not a bug, until the last big-endian machine running anything UN*Xy is shut down, and IBM doesn't seem to be in a hurry to get rid of either z/Architecture or Linux on Z); it's not as if those machines are exactly tiny machines with slow CPUs.

Maybe there's some issue with big-endian machines in the filter code.

Or maybe not, given that it's not reliably failing on any given test; it might just be random luck.

@vjardin

vjardin commented Mar 26, 2026

Copy link
Copy Markdown
Author

Or maybe not, given that it's not reliably failing on any given test; it might just be random luck.

Can we increase the timeout ?

How to get detailed logs/traces ?

@infrastation

Copy link
Copy Markdown
Member

Complete CI logs are available in respective CI checks associated with the pull request, if the obvious needs to be stated.

linux-s390x is hosted by OSU OSL, which are in the process of moving the data centre (donations are welcome to cover the expenses), so some flakiness is expected. If this persists, it will be dealt with later as and if required.

One more time, releasing libpcap 1.11.0 is a more important and more urgent problem to solve compared to fixing of DPDK support, so these changes will have to wait until time is good for merging.

@infrastation

Copy link
Copy Markdown
Member

Apparently, the donations page is now here (and requires manual input to donate specifically to OSL).

@vjardin

vjardin commented Apr 2, 2026

Copy link
Copy Markdown
Author

@infrastation : Denis, fyi, the following has to be selected:

image

When will the release 1.11.0 be tag'd ?

Meanwhile, can someone confirm the approach of this pull request ? We could use it to clean up the unsupported DPDK case of the libpcap.

@infrastation

Copy link
Copy Markdown
Member

A few urgent problems are in the way of 1.11.0 release, this is being worked on.

rjarry added a commit to vjardin/grout that referenced this pull request Apr 14, 2026
Add libpcap as a meson wrap subproject with three patches on top of
upstream commit bac2884b. These patches implement the plugin loader
proposed in the-tcpdump-group/libpcap#1659:

 - runtime plugin loader for external capture modules
 - timestamp type accessors for adapter code
 - complete accessor API for tcpdump feature parity

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
rjarry added a commit to vjardin/grout that referenced this pull request Apr 14, 2026
Add libpcap as a meson wrap subproject with three patches on top of
upstream commit bac2884b. These patches implement the plugin loader
proposed in the-tcpdump-group/libpcap#1659:

 - runtime plugin loader for external capture modules
 - timestamp type accessors for adapter code
 - complete accessor API for tcpdump feature parity

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
rjarry added a commit to vjardin/grout that referenced this pull request Apr 14, 2026
Add libpcap as a meson wrap subproject with three patches on top of
upstream commit bac2884b. These patches implement the plugin loader
proposed in the-tcpdump-group/libpcap#1659:

 - runtime plugin loader for external capture modules
 - timestamp type accessors for adapter code
 - complete accessor API for tcpdump feature parity

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
rjarry added a commit to vjardin/grout that referenced this pull request Apr 14, 2026
Add libpcap as a meson wrap subproject with three patches on top of
upstream commit bac2884b. These patches implement the plugin loader
proposed in the-tcpdump-group/libpcap#1659:

 - runtime plugin loader for external capture modules
 - timestamp type accessors for adapter code
 - complete accessor API for tcpdump feature parity

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
@vjardin

vjardin commented Apr 15, 2026

Copy link
Copy Markdown
Author

thanks @rjarry for the early wrap integrations on DPDK/grout.

rjarry added a commit to vjardin/grout that referenced this pull request Apr 15, 2026
Add libpcap as a meson wrap subproject with three patches on top of
upstream commit bac2884b. These patches implement the plugin loader
proposed in the-tcpdump-group/libpcap#1659:

 - runtime plugin loader for external capture modules
 - timestamp type accessors for adapter code
 - complete accessor API for tcpdump feature parity

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
rjarry added a commit to vjardin/grout that referenced this pull request Apr 15, 2026
Add libpcap as a meson wrap subproject with three patches on top of
upstream commit bac2884b. These patches implement the plugin loader
proposed in the-tcpdump-group/libpcap#1659:

 - runtime plugin loader for external capture modules
 - timestamp type accessors for adapter code
 - complete accessor API for tcpdump feature parity

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
rjarry added a commit to vjardin/grout that referenced this pull request Apr 23, 2026
Add libpcap as a meson wrap subproject with three patches on top of
upstream commit bac2884b. These patches implement the plugin loader
proposed in the-tcpdump-group/libpcap#1659:

 - runtime plugin loader for external capture modules
 - timestamp type accessors for adapter code
 - complete accessor API for tcpdump feature parity

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
rjarry added a commit to vjardin/grout that referenced this pull request May 5, 2026
Add libpcap as a meson wrap subproject with three patches on top of
upstream commit bac2884b. These patches implement the plugin loader
proposed in the-tcpdump-group/libpcap#1659:

 - runtime plugin loader for external capture modules
 - timestamp type accessors for adapter code
 - complete accessor API for tcpdump feature parity

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
rjarry added a commit to vjardin/grout that referenced this pull request May 5, 2026
The packet capture feature needs libpcap with the plugin loader
API proposed in the-tcpdump-group/libpcap#1659
which is not yet merged upstream. Add libpcap as a meson wrap
subproject with three patches on top of upstream commit bac2884b
implementing the runtime plugin loader, timestamp type accessors
and a complete accessor API for tcpdump feature parity.

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
rjarry added a commit to vjardin/grout that referenced this pull request May 13, 2026
The packet capture feature needs libpcap with the plugin loader
API proposed in the-tcpdump-group/libpcap#1659
which is not yet merged upstream. Add libpcap as a meson wrap
subproject with three patches on top of upstream commit bac2884b
implementing the runtime plugin loader, timestamp type accessors
and a complete accessor API for tcpdump feature parity.

The meson.build exposes a plugindir variable via declare_dependency()
so consumers can install plugins in the right location.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
@vjardin

vjardin commented May 27, 2026

Copy link
Copy Markdown
Author

Hi,

Sorry for not being "aware" of the release trains of libpcap, but what is the landing/when for the release for a possible merge of this pull request (assuming ok one) ? Behind the hood, it is to sync with grout and DPDK communities and to avoid the risks of "local" patches.

thank you,

@infrastation

Copy link
Copy Markdown
Member

Thank you for waiting Vincent. libpcap 1.11.0 has not been released yet because it got blocked by libpcap 1.10.7, which has not been released yet because it is still blocked by important bug fixes that took longer than expected. This is going to take a bit longer.

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

Labels

DPDK Data Plane Development Kit

Development

Successfully merging this pull request may close these issues.

6 participants