[RFC] add rust binding support to DPDK
Checks
Commit Message
Add a Cargo.toml file in the root folder and a number of other scripts
and rust-related files into buildtools/rust, which then enables DPDK to
be cloned and built as a rust crate - all-be-it one with only two
functions: rte_eal_init and rte_eal_cleanup.
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
This RFC is proposed as an alternative approach to enabling rust support
in DPDK. The key difference vs previous is that we are taking the whole
DPDK project here as a rust "crate", which can then be used by other
higher-level crates as a dependency. Building the crate does a
(minimal) build of DPDK and statically links it in, so there is no
"install" step or anything needed - the Rust app just adds DPDK to their
Cargo.toml file and then should have everything they need.
---
Cargo.toml | 16 +++++++
buildtools/meson.build | 1 +
buildtools/rust/build.rs | 45 ++++++++++++++++++++
buildtools/rust/gen-cargo-bindgen-info.py | 52 +++++++++++++++++++++++
buildtools/rust/lib.rs | 25 +++++++++++
buildtools/rust/wrapper.h | 5 +++
meson.build | 3 ++
7 files changed, 147 insertions(+)
create mode 100644 Cargo.toml
create mode 100644 buildtools/rust/build.rs
create mode 100644 buildtools/rust/gen-cargo-bindgen-info.py
create mode 100644 buildtools/rust/lib.rs
create mode 100644 buildtools/rust/wrapper.h
--
2.45.2
Comments
On Tue, Apr 08, 2025 at 03:58:38PM +0100, Bruce Richardson wrote:
> Add a Cargo.toml file in the root folder and a number of other scripts
> and rust-related files into buildtools/rust, which then enables DPDK to
> be cloned and built as a rust crate - all-be-it one with only two
> functions: rte_eal_init and rte_eal_cleanup.
>
> Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
> ---
>
> This RFC is proposed as an alternative approach to enabling rust support
> in DPDK. The key difference vs previous is that we are taking the whole
> DPDK project here as a rust "crate", which can then be used by other
> higher-level crates as a dependency. Building the crate does a
> (minimal) build of DPDK and statically links it in, so there is no
> "install" step or anything needed - the Rust app just adds DPDK to their
> Cargo.toml file and then should have everything they need.
>
> ---
> Cargo.toml | 16 +++++++
> buildtools/meson.build | 1 +
> buildtools/rust/build.rs | 45 ++++++++++++++++++++
> buildtools/rust/gen-cargo-bindgen-info.py | 52 +++++++++++++++++++++++
> buildtools/rust/lib.rs | 25 +++++++++++
> buildtools/rust/wrapper.h | 5 +++
> meson.build | 3 ++
> 7 files changed, 147 insertions(+)
> create mode 100644 Cargo.toml
> create mode 100644 buildtools/rust/build.rs
> create mode 100644 buildtools/rust/gen-cargo-bindgen-info.py
> create mode 100644 buildtools/rust/lib.rs
> create mode 100644 buildtools/rust/wrapper.h
>
<snip>
> diff --git a/buildtools/rust/lib.rs b/buildtools/rust/lib.rs
> new file mode 100644
> index 0000000000..f99c54ac42
> --- /dev/null
> +++ b/buildtools/rust/lib.rs
> @@ -0,0 +1,25 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Intel Corporation
> + */
> +
> +#![allow(non_upper_case_globals)]
> +#![allow(non_camel_case_types)]
> +#![allow(non_snake_case)]
> +
> +include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
> +
> +#[cfg(test)]
> +mod tests {
> + use super::*;
> +
> + #[test]
> + fn test_helloworld() {
> + let appname = std::ffi::CString::new("test-rs").unwrap();
> + let mut argv = [appname.into_raw()];
> + let ret = unsafe {
> + rte_eal_init(argv.len().try_into().unwrap(), argv.as_mut_ptr())
> + };
> + assert!(ret >= 0, "rte_eal_init failed");
> + unsafe { rte_eal_cleanup() };
> + }
> +}
This test code is included in the patch to make it easy to verify that DPDK
can be built and run with rust - at least doing EAL init and cleanup.
To test, once rust is available on your system (e.g. using rustup), apply
this patch and then just do "cargo test" in the root of your DPDK
directory. After waiting a minute for things to build you should see the
test run output of DPDK initialization.
The whole approach for now I'm deliberately keeping extremely minimal so as
a) not to invest too much effort before we know what way we want to go and
b) to make it easier to review and comment upon.
For anyone wanting to try this out in a standalone app or play with
developing a higher-level crate, I've pushed this patch to my github
account. This is the "Cargo.toml" file entry I've used to pull this down
when investigating wrapping eal_init into a "higher-level" rust-style init
API:
[dependencies]
dpdk-c = { git = "https://github.com/bruce-richardson/dpdk", branch = "cargo-build" }
Regards,
/Bruce
Hello Bruce,
> Add a Cargo.toml file in the root folder and a number of other scripts
> and rust-related files into buildtools/rust, which then enables DPDK to
> be cloned and built as a rust crate - all-be-it one with only two
> functions: rte_eal_init and rte_eal_cleanup.
>
> Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
> ---
>
> This RFC is proposed as an alternative approach to enabling rust support
> in DPDK. The key difference vs previous is that we are taking the whole
> DPDK project here as a rust "crate", which can then be used by other
> higher-level crates as a dependency. Building the crate does a
> (minimal) build of DPDK and statically links it in, so there is no
> "install" step or anything needed - the Rust app just adds DPDK to their
> Cargo.toml file and then should have everything they need.
>
Having a shared source directory for both for C and Rust DPDK infrastructure is
the correct approach.
My concern is how to properly maintain Rust crate once DPDK starts to implement
it's own API.
Rust files may need a separate directories to host libraries, applications
and PMD.
> diff --git a/Cargo.toml b/Cargo.toml
> new file mode 100644
> index 0000000000..fe0843975a
> --- /dev/null
> +++ b/Cargo.toml
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2025 Intel Corporation
> +
> +[package]
> +name = "dpdk-c"
> +version = "25.0.7"
> +build = "buildtools/rust/build.rs"
> +edition = "2021"
> +
> +[lib]
> +path = "buildtools/rust/lib.rs"
> +doctest = false
> +
> +[build-dependencies]
> +bindgen = "0.71"
> +meson-next = "1"
> diff --git a/buildtools/meson.build b/buildtools/meson.build
> index 4e2c1217a2..a13b5189c1 100644
> --- a/buildtools/meson.build
> +++ b/buildtools/meson.build
> @@ -26,6 +26,7 @@ header_gen_cmd = py3 + files('gen-header.py')
> has_hugepages_cmd = py3 + files('has-hugepages.py')
> cmdline_gen_cmd = py3 + files('dpdk-cmdline-gen.py')
> check_dts_requirements = py3 + files('check-dts-requirements.py')
> +gen_cargo_bindgen_info = py3 + files('rust/gen-cargo-bindgen-info.py')
>
> # install any build tools that end-users might want also
> install_data([
> diff --git a/buildtools/rust/build.rs b/buildtools/rust/build.rs
> new file mode 100644
> index 0000000000..1ffdf03d2f
> --- /dev/null
> +++ b/buildtools/rust/build.rs
> @@ -0,0 +1,45 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Intel Corporation
> + */
> +
> +extern crate meson_next as meson;
> +use std::collections::HashMap;
> +use std::env;
> +use std::path::PathBuf;
> +
> +fn main() {
> + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
> + let build_dir = out_path.join("build");
> +
> + let meson_cfg = meson::Config::new().options(HashMap::from([
> + ("enable_libs", "eal"),
> + ("enable_drivers", "net/*,net/intel/*"),
> + ("enable_apps", "test")
> + ]));
> + meson::build(".", build_dir.to_str().unwrap(), meson_cfg);
> +
> + /* open and print file 'cargo_rules.txt' from build_dir */
> + let cargo_ldflags_file = build_dir.join("cargo_ldflags.txt");
> + println!("cargo:rerun-if-changed={}", cargo_ldflags_file.display());
> + print!("{}", std::fs::read_to_string(cargo_ldflags_file).unwrap());
> +
> + let bindgen_include_file = build_dir.join("bindgen_cflags.txt");
> + let mut bindings = bindgen::Builder::default();
> + for line in std::fs::read_to_string(bindgen_include_file).unwrap().lines() {
> + bindings = bindings.clang_arg(line);
> + }
> +
> + let bindings = bindings.header("buildtools/rust/wrapper.h")
> + .derive_default(true)
> + .allowlist_function("rte_eal_init")
> + .allowlist_function("rte_eal_cleanup")
Calling the `allowlist_function()` method generates well-maintained target
bindings.
That approach requires to specify each DPDK symbol for Rust
library API explicitly. There are way too many symbols to bind even for
a simple network application.
> + // Tell cargo to invalidate the built crate whenever any of the
> + // included header files changed.
> + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
> + .generate()
> + .expect("Unable to generate bindings");
> +
> + bindings
> + .write_to_file(out_path.join("bindings.rs"))
> + .expect("Couldn't write bindings!");
> +}
Bindings file or directory for RTE library API is not enough.
Each PMD has it's own symbols set and will need a separate bindings library.
> diff --git a/buildtools/rust/gen-cargo-bindgen-info.py b/buildtools/rust/gen-cargo-bindgen-info.py
> new file mode 100644
> index 0000000000..bea432ead3
> --- /dev/null
> +++ b/buildtools/rust/gen-cargo-bindgen-info.py
> @@ -0,0 +1,52 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2025 Intel Corporation
> +
> +# Script to generate cargo rules for static linking in rust builds
> +# outputs one line per library, for drivers and libraries
> +
> +import os
> +import os.path
> +import sys
> +import subprocess
> +
> +if 'MESON_BUILD_ROOT' not in os.environ:
> + print('This script must be called from a meson build environment')
> + sys.exit(1)
> +
> +pkgconf = sys.argv[1]
> +os.environ['PKG_CONFIG_PATH'] = os.path.join(os.environ['MESON_BUILD_ROOT'], 'meson-uninstalled')
> +linker_flags = subprocess.check_output([pkgconf, '--libs', '--static', 'libdpdk']).decode('utf-8')
> +cflags = subprocess.check_output([pkgconf, '--cflags', 'libdpdk']).decode('utf-8')
> +
> +whole_archive = False
> +with open(os.path.join(os.environ['MESON_BUILD_ROOT'], 'cargo_ldflags.txt'), 'w') as dst:
> + for flag in linker_flags.split():
> + if flag == '-pthread':
> + continue
> + elif flag == '-Wl,--whole-archive':
> + whole_archive = True
> + elif flag == '-Wl,--no-whole-archive':
> + whole_archive = False
> + elif flag.startswith('-L'):
> + dst.write(f'cargo:rustc-link-search=native={flag[2:]}\n')
> + elif flag.startswith('-l:'):
> + libname = flag[3:]
> + if libname.startswith('lib'):
> + libname = libname[3:]
> + if libname.endswith('.a'):
> + libname = libname[:-2]
> + if whole_archive:
> + dst.write(f'cargo:rustc-link-lib=static:+whole-archive={libname}\n')
> + else:
> + dst.write(f'cargo:rustc-link-lib=static={libname}\n')
> + elif flag.startswith('-lrte_'):
> + # skip any other DPDK lib flags, we already have them above
> + continue
> + elif flag.startswith('-l'):
> + dst.write(f'cargo:rustc-link-lib={flag[2:]}\n')
> + else:
> + print(f'Warning: Unknown flag: {flag}', file=sys.stderr)
> +
> +with open(os.path.join(os.environ['MESON_BUILD_ROOT'], 'bindgen_cflags.txt'), 'w') as dst:
> + for flag in cflags.split():
> + dst.write(f'{flag}\n')
> diff --git a/buildtools/rust/lib.rs b/buildtools/rust/lib.rs
> new file mode 100644
> index 0000000000..f99c54ac42
> --- /dev/null
> +++ b/buildtools/rust/lib.rs
> @@ -0,0 +1,25 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Intel Corporation
> + */
> +
> +#![allow(non_upper_case_globals)]
> +#![allow(non_camel_case_types)]
> +#![allow(non_snake_case)]
> +
> +include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
> +
> +#[cfg(test)]
> +mod tests {
> + use super::*;
> +
> + #[test]
> + fn test_helloworld() {
> + let appname = std::ffi::CString::new("test-rs").unwrap();
> + let mut argv = [appname.into_raw()];
> + let ret = unsafe {
> + rte_eal_init(argv.len().try_into().unwrap(), argv.as_mut_ptr())
Activating rte_eal_init() proves that Rust crate has properly linked with DPDK
libraries.
That is more like infrastructure test.
An active network port with IO capabilities is a real Rust-DPDK POC.
> + };
> + assert!(ret >= 0, "rte_eal_init failed");
> + unsafe { rte_eal_cleanup() };
> + }
> +}
> diff --git a/buildtools/rust/wrapper.h b/buildtools/rust/wrapper.h
> new file mode 100644
> index 0000000000..25f5bf94e2
> --- /dev/null
> +++ b/buildtools/rust/wrapper.h
> @@ -0,0 +1,5 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2025 Intel Corporation
> + */
> +
> +#include <rte_eal.h>
> diff --git a/meson.build b/meson.build
> index 8436d1dff8..72c82178e1 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -114,6 +114,9 @@ configure_file(output: build_cfg,
> # build pkg-config files for dpdk
> subdir('buildtools/pkg-config')
>
> +# output a list of cargo parameters if DPDK is being built for rust use
> +run_command([gen_cargo_bindgen_info, pkgconf], check: true)
> +
> if meson.is_subproject()
> subdir('buildtools/subproject')
> endif
> --
> 2.45.2
>
>
Regards,
Gregory
> From: Etelson, Gregory
> Sent: Thursday, April 10, 2025 6:28 AM
> To: Richardson, Bruce
> Cc: dev@dpdk.org
> Subject: Re: [RFC PATCH] add rust binding support to DPDK
>
> Hello Bruce,
Hi Bruce & Gregory,
> > Add a Cargo.toml file in the root folder and a number of other scripts
> > and rust-related files into buildtools/rust, which then enables DPDK to
> > be cloned and built as a rust crate - all-be-it one with only two
> > functions: rte_eal_init and rte_eal_cleanup.
> >
> > Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
> > ---
> >
> > This RFC is proposed as an alternative approach to enabling rust support
> > in DPDK. The key difference vs previous is that we are taking the whole
> > DPDK project here as a rust "crate", which can then be used by other
> > higher-level crates as a dependency. Building the crate does a
> > (minimal) build of DPDK and statically links it in, so there is no
> > "install" step or anything needed - the Rust app just adds DPDK to their
> > Cargo.toml file and then should have everything they need.
> >
>
> Having a shared source directory for both for C and Rust DPDK infrastructure is
> the correct approach.
It seems so, with the various approaches so far, agreed.
I've tested Bruce's repo, and Cargo.toml. The initial build takes some time.. but it works!
The experience of "Adding DPDK to a Rust project" is very similar to adding any other Rust crate.
I like this - it makes the "strangeness" of depending on DPDK near-zero :)
> My concern is how to properly maintain Rust crate once DPDK starts to implement
> it's own API.
I'm not really sure what is meant here. I don't understand what "own" word refers to?
I see it like this:
- DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
- The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)
So DPDK changing APIs is API/ABI breaking for *all* consumers of DPDK, not just the Rust bindings.
No special treatment required, just identify API/ABI changes when they occur (as per ABI policy) and fix.
Some approaches to how CI could help, and when to identify/fix Rust bindings took place on the last
TechBoard call (minutes not yet available on https://core.dpdk.org/techboard/minutes/).
No decision made on how - we're still at prototyping phase anyway.
> Rust files may need a separate directories to host libraries, applications and PMD.
I'm not sure this is strictly required - perhaps "nice to have". Bruce's approach
has been to minimize changes in the "DPDK C repo", doing just enough to enable
building "Safe Rust" wrappers on top in a seperate "DPDK Rust" repo.
I like this approach - use what bindgen gives access to - and incrementally export/expose
more DPDK APIs *as they are consumed*. This ensures we don't accidentally over-export,
or have mistakes go unnoticed because nobody tested/used the APIs yet. More on this below
in the context of bindgen usage.
<snip>
> > diff --git a/buildtools/rust/build.rs b/buildtools/rust/build.rs
> > new file mode 100644
> > index 0000000000..1ffdf03d2f
> > --- /dev/null
> > +++ b/buildtools/rust/build.rs
<snip>
> > + let bindings = bindings.header("buildtools/rust/wrapper.h")
> > + .derive_default(true)
> > + .allowlist_function("rte_eal_init")
> > + .allowlist_function("rte_eal_cleanup")
>
> Calling the `allowlist_function()` method generates well-maintained target
> bindings.
> That approach requires to specify each DPDK symbol for Rust
> library API explicitly. There are way too many symbols to bind even for
> a simple network application.
The "allowlist" approach ensures that DPDK C functions, bindgen-converted in Rust are used, and reviewed.
If all DPDK C APIs are exported, there is additional and unknown risk of bugs, that the users
of the DPDK-rust-bindings will hit: providing bad user-experience. I do not believe it is a good
idea to export all API structs & symbols without any tests, reviews, and quality control.
If it is really desired by a end user to export all C API as "unsafe Rust" bindings, there are many
crates existing already doing this (and your patch Gregory also provides that method?). I do not feel
this adds value for DPDK community to maintain: the end-goal must be an ergonomic and safe Rust API.
My vote is to incrementally "allowlist" new APIs, and having a "Safe Rust" (or "idiomatic Rust APIs")
wrapping them to export public functions in the "end-user facing" DPDK Rust crate. This ensures that
any public function in the "DPDK Rust" crate has been built, reviewed and tested.
This will cause the initial versions of the "DPDK Rust" to be very minimal, and to gradually
export more functionality and APIs in a safe way. From my perspective, this is the only way
to give a consistent high-quality user-experience that is the norm for the Rust ecosystem.
> Bindings file or directory for RTE library API is not enough.
> Each PMD has it's own symbols set and will need a separate bindings library.
As per above, I recommend we focus on minimal functionality, exported and exposed in an ergonomic way.
That means initial focus on EAL, Lcore management, Mempools, Ethdev, and Mbufs. Or from a "use-case"
perspective, enough to get L2-macswap to work using "Safe Rust" bindings.
This means that things like "PMD specific symbols" are not going to be high on the priority list initially.
Once the above L2 forward use-case is satisfied, I'm happy to provide input on designing an API which is
"PMD specific" in a generic way (initial technical thoughts, a "dyn Trait" with downcast to a PMD specific trait).
<snip>
> > +#[cfg(test)]
> > +mod tests {
> > + use super::*;
> > +
> > + #[test]
> > + fn test_helloworld() {
> > + let appname = std::ffi::CString::new("test-rs").unwrap();
> > + let mut argv = [appname.into_raw()];
> > + let ret = unsafe {
> > + rte_eal_init(argv.len().try_into().unwrap(), argv.as_mut_ptr())
>
> Activating rte_eal_init() proves that Rust crate has properly linked with DPDK
> libraries.
> That is more like infrastructure test.
> An active network port with IO capabilities is a real Rust-DPDK POC.
Agreed - as you can see, this is a #[test]. It is intended only to show that eal_init() and cleanup() work.
Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over
them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports,
lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?
I will make some Safe-Rust API suggestions for EAL, and send a patch sometime next week to discuss.
Thanks for the inputs! Regards, -Harry
Hello Bruce & Harry,
<snip>
>
>> My concern is how to properly maintain Rust crate once DPDK starts to implement
>> it's own API.
>
> I'm not really sure what is meant here. I don't understand what "own" word refers to?
>
> I see it like this:
> - DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
> - The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)
>
Bindgen cannot provide access to all DPDK public API.
A good example here is rte_eth_rx_burst().
That function is defined as inline and bindgen does not translate it.
Also, the function definition references rte_eth_fp_ops array that is not part of the
public DPDK API. That means Rust cannot duplicate rte_eth_rx_burst() "as-is" and
the solution can require extensions to existing DPDK API.
I added a new public API that exports rte_eth_fp_ops for a given port Id.
Rust implementation of rte_eth_rx_burst() does not have to follow the original
approach.
Usage of rte_eth_fp_ops is good for C, but Rust has different methods.
For conclusion, Rust DPDK infrastructure cannot relay on bindgen only and needs
to provide native implementation for some public DPDK API.
It can be easier to maintain Rust files separately.
You can check out my Rust DPDK version here: https://github.com/getelson-at-mellanox/rdpdk
<snip>
>
> Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over
> them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports,
> lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?
I suggest to set the first goal to initiate a port and run simple Rx/Tx functions.
>
> I will make some Safe-Rust API suggestions for EAL, and send a patch sometime next week to discuss.
>
> Thanks for the inputs! Regards, -Harry
Regards,
Gregory
> From: Etelson, Gregory
> Sent: Friday, April 11, 2025 4:39 PM
> To: Van Haaren, Harry
> Cc: Richardson, Bruce; dev@dpdk.org
> Subject: Re: [RFC PATCH] add rust binding support to DPDK
>
> Hello Bruce & Harry,
>
> <snip>
>
> >
> >> My concern is how to properly maintain Rust crate once DPDK starts to implement
> >> it's own API.
> >
> > I'm not really sure what is meant here. I don't understand what "own" word refers to?
> >
> > I see it like this:
> > - DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
> > - The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)
> >
>
> Bindgen cannot provide access to all DPDK public API.
Ah - you're referring to C static inline functions, declared in header files, which bindgen doesn't wrap.
Correct - thanks - I understand your point now.
> A good example here is rte_eth_rx_burst().
> That function is defined as inline and bindgen does not translate it.
> Also, the function definition references rte_eth_fp_ops array that is not part of the
> public DPDK API. That means Rust cannot duplicate rte_eth_rx_burst() "as-is" and
> the solution can require extensions to existing DPDK API.
>
> I added a new public API that exports rte_eth_fp_ops for a given port Id.
>
> Rust implementation of rte_eth_rx_burst() does not have to follow the original
> approach.
> Usage of rte_eth_fp_ops is good for C, but Rust has different methods.
Agreed there is a certain "mismatch" sometimes, if functions aren't in the
actually "C ABI" then they can't be called via bindgen.
Agree that elegant solutions (clean, maintainable, and high performance) will have
to be found here. Many existing solutions just wrap the "static inline" function into
a "non-static" function, and export it as a public symbol. That allows calling into it
from Rust (via bindgen-generated header) however causes an actual function call..
(LTO _might_ fix/inline it, but not everybody compiles with LTO.. link times!)
As DPDK uses static-inline functions primarily for "packet-at-a-time" performance reasons,
it is unfortunate to give-up (some small amounts of..?) performance by having a C->Rust FFI call.
We have work to do to find/propose the best solution.
> For conclusion, Rust DPDK infrastructure cannot relay on bindgen only and needs
> to provide native implementation for some public DPDK API.
> It can be easier to maintain Rust files separately.
OK, now I understand your "kind-of-C-DPDK, kind of Rust-DPDK" code, or the "own" code reference above.
I'm not sure right now where that would be best implemented/maintained, I'll have to think about it and do a POC.
> You can check out my Rust DPDK version here: https://github.com/getelson-at-mellanox/rdpdk
Thanks for the link - I see you've been pushing code actively! Good to see opportunities,
and compare approaches and concepts. Have you investigated wrapping the various pointers
into structs, and providing safer APIs? For example, the [*mut rte_mbuf; 64] array for RX causes
raw pointers to be handled for all packet-processing - resulting in "unsafe{ /* work here */ }" blocks.
The code in the above repo feels like "DPDK C code written in Rust". It is a great step towards better
understanding, and having something that works is very valuable; thanks for sharing it.
A "top down" application view might help to brainstorm idiomatic/Safe Rust APIs, and then we can
discuss/understand how to map these high level APIs onto the "DPDK C in Rust" or even "DPDK C API/ABI" layers.
Does that seem like a good method to you, to achieve an ergonomic Safe Rust API as the end result?
> > Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over
> > them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports,
> > lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?
>
> I suggest to set the first goal to initiate a port and run simple Rx/Tx functions.
Agreed, we need some API to "work towards" (Top down) as well as "work from" (DPDK C APIs, and your repo above).
> > I will make some Safe-Rust API suggestions for EAL, and send a patch sometime next week to discuss.
Hopefully this Safe-Rust API proposal will be a candidate for "Top Down" API design approach.
> > Thanks for the inputs! Regards, -Harry
>
> Regards,
> Gregory
Thanks! -Harry
Hello Harry,
>>>> My concern is how to properly maintain Rust crate once DPDK starts to implement
>>>> it's own API.
>>>
>>> I'm not really sure what is meant here. I don't understand what "own" word refers to?
>>>
>>> I see it like this:
>>> - DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
>>> - The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)
>>>
>>
>> Bindgen cannot provide access to all DPDK public API.
>
> Ah - you're referring to C static inline functions, declared in header files, which bindgen doesn't wrap.
> Correct - thanks - I understand your point now.
>
>> A good example here is rte_eth_rx_burst().
>> That function is defined as inline and bindgen does not translate it.
>> Also, the function definition references rte_eth_fp_ops array that is not part of the
>> public DPDK API. That means Rust cannot duplicate rte_eth_rx_burst() "as-is" and
>> the solution can require extensions to existing DPDK API.
>>
>> I added a new public API that exports rte_eth_fp_ops for a given port Id.
>>
>> Rust implementation of rte_eth_rx_burst() does not have to follow the original
>> approach.
>> Usage of rte_eth_fp_ops is good for C, but Rust has different methods.
>
> Agreed there is a certain "mismatch" sometimes, if functions aren't in the
> actually "C ABI" then they can't be called via bindgen.
>
> Agree that elegant solutions (clean, maintainable, and high performance) will have
> to be found here. Many existing solutions just wrap the "static inline" function into
> a "non-static" function, and export it as a public symbol. That allows calling into it
> from Rust (via bindgen-generated header) however causes an actual function call..
> (LTO _might_ fix/inline it, but not everybody compiles with LTO.. link times!)
>
The core DPDK code is maintained as a self-contained pure C project.
What about extending that model and provide direct access to DPDK resources that
are needed for Rust API ?
That can be arranged as part of "native" DPDK API or as extension for
Rust-enabled DPDK only.
I'm currently experimenting with the latter in
https://github.com/getelson-at-mellanox/rdpdk/tree/main/dpdk-patches
> As DPDK uses static-inline functions primarily for "packet-at-a-time" performance reasons,
> it is unfortunate to give-up (some small amounts of..?) performance by having a C->Rust FFI call.
> We have work to do to find/propose the best solution.
What exactly is a small amount of performance degradation ?
For some existing performance oriented projects loosing performance to
eliminate the `unsafe {}` construct is not an option.
>
>> For conclusion, Rust DPDK infrastructure cannot relay on bindgen only and needs
>> to provide native implementation for some public DPDK API.
>> It can be easier to maintain Rust files separately.
>
> OK, now I understand your "kind-of-C-DPDK, kind of Rust-DPDK" code, or the "own" code reference above.
> I'm not sure right now where that would be best implemented/maintained, I'll have to think about it and do a POC.
>
>
>> You can check out my Rust DPDK version here: https://github.com/getelson-at-mellanox/rdpdk
>
> Thanks for the link - I see you've been pushing code actively! Good to see opportunities,
> and compare approaches and concepts. Have you investigated wrapping the various pointers
> into structs, and providing safer APIs? For example, the [*mut rte_mbuf; 64] array for RX causes
> raw pointers to be handled for all packet-processing - resulting in "unsafe{ /* work here */ }" blocks.
>
This construct can work:
struct PktsBuf<const SIZE: usize> {
buffer: [*mut rte_mbuf; SIZE]
}
> The code in the above repo feels like "DPDK C code written in Rust".
Agree.
At this stage there is no native Rust DPDK API.
> It is a great step towards better
> understanding, and having something that works is very valuable; thanks for sharing it.
>
> A "top down" application view might help to brainstorm idiomatic/Safe Rust APIs, and then we can
> discuss/understand how to map these high level APIs onto the "DPDK C in Rust" or even "DPDK C API/ABI" layers.
>
> Does that seem like a good method to you, to achieve an ergonomic Safe Rust API as the end result?
>
I think we need to conclude what the safe API means in terms of DPDK project.
Because it's possible to provide native Rust API for DPDK what will use FFI.
Specially, if Rust PMD is not in plans and performance is one of the main goals.
>
>>> Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over
>>> them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports,
>>> lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?
check out for Rx, L2 addr swap, Tx sequence here:
https://github.com/getelson-at-mellanox/rdpdk/blob/253fde7c16a5514bf99d3823725f825262e244c8/app/runpmd/runpmd.rs#L156
Regards,
Gregory
Hello all,
I wanted to chime in as someone who uses DPDK from Rust in quite a few projects.
No snips so the conversation can continue past me for things I don't comment on.
Hello Harry,
>>>> My concern is how to properly maintain Rust crate once DPDK starts to implement
>>>> it's own API.
>>>
>>> I'm not really sure what is meant here. I don't understand what "own" word refers to?
>>>
>>> I see it like this:
>>> - DPDK has the public C API exported (and that stays the same as today, with ABI rules, version.map files, etc)
>>> - The Rust crate consumes the public C API (via bindgen, as done in this patch. More detail about bindgen below.)
>>>
>>
>> Bindgen cannot provide access to all DPDK public API.
>
> Ah - you're referring to C static inline functions, declared in header files, which bindgen doesn't wrap.
> Correct - thanks - I understand your point now.
>
>> A good example here is rte_eth_rx_burst().
>> That function is defined as inline and bindgen does not translate it.
>> Also, the function definition references rte_eth_fp_ops array that is not part of the
>> public DPDK API. That means Rust cannot duplicate rte_eth_rx_burst() "as-is" and
>> the solution can require extensions to existing DPDK API.
>>
>> I added a new public API that exports rte_eth_fp_ops for a given port Id.
>>
>> Rust implementation of rte_eth_rx_burst() does not have to follow the original
>> approach.
>> Usage of rte_eth_fp_ops is good for C, but Rust has different methods.
>
> Agreed there is a certain "mismatch" sometimes, if functions aren't in the
> actually "C ABI" then they can't be called via bindgen.
>
> Agree that elegant solutions (clean, maintainable, and high performance) will have
> to be found here. Many existing solutions just wrap the "static inline" function into
> a "non-static" function, and export it as a public symbol. That allows calling into it
> from Rust (via bindgen-generated header) however causes an actual function call..
> (LTO _might_ fix/inline it, but not everybody compiles with LTO.. link times!)
>
The core DPDK code is maintained as a self-contained pure C project.
What about extending that model and provide direct access to DPDK resources that
are needed for Rust API ?
That can be arranged as part of "native" DPDK API or as extension for
Rust-enabled DPDK only.
I'm currently experimenting with the latter in
https://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fgetelson-at-mellanox%2Frdpdk%2Ftree%2Fmain%2Fdpdk-patches&data=05%7C02%7Cowen.hilyard%40unh.edu%7C59c29799f1544a59f44108dd7a62352a%7Cd6241893512d46dc8d2bbe47e25f5666%7C0%7C0%7C638801284429834568%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=3YrOkklWRWlwKyfg9BblBnLsNqnCEf7RdAxdUW3JGgM%3D&reserved=0<https://github.com/getelson-at-mellanox/rdpdk/tree/main/dpdk-patches>
There are some performance wins to be found along this path. It's not a lot, but you can "devirtualize" a lot of DPDK if you use Rust's generics to specialize on particular hardware combinations, at the cost of multiplying code size by the number of supported NICs. I can do this in my bindings since I tend to only need to test a few software PMDs and the hardware I have, with a fallback to "dyn dpdk::EthDev" which pulls data from info structs all of the time, instead of only pulling runtime-only information (ex: mac addr, rss key). This is paired with a top-level function which does dispatch for different hardware to "lift" the runtime information about what NIC is used to compile time. I think the main win here is from inlining driver functions, but I haven't done a detailed analysis of the "why". These are marginal improvements, around a half percent for nic_single_core_perf on my hardware, but there may be more wins for less heavily optimized paths through DPDK where the compiler can do more heavy lifting.
> As DPDK uses static-inline functions primarily for "packet-at-a-time" performance reasons,
> it is unfortunate to give-up (some small amounts of..?) performance by having a C->Rust FFI call.
> We have work to do to find/propose the best solution.
What exactly is a small amount of performance degradation ?
For some existing performance oriented projects loosing performance to
eliminate the `unsafe {}` construct is not an option.
If you are willing to compile with clang and use LTO, both rustc and clang will emit LLVM IR, which gets inlined at link time. I haven't been able to measure a performance difference vs C code even when calling "static inline" hot path function wrappers across the FFI boundary. This does increase compile times by quite a bit, more than LTO with pure C would increase them due to the amount of LLVM IR that rustc emits, but it means that the performance difference barely exists if it does at all.
>
>> For conclusion, Rust DPDK infrastructure cannot relay on bindgen only and needs
>> to provide native implementation for some public DPDK API.
>> It can be easier to maintain Rust files separately.
>
> OK, now I understand your "kind-of-C-DPDK, kind of Rust-DPDK" code, or the "own" code reference above.
> I'm not sure right now where that would be best implemented/maintained, I'll have to think about it and do a POC.
>
>
>> You can check out my Rust DPDK version here: https://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fgetelson-at-mellanox%2Frdpdk&data=05%7C02%7Cowen.hilyard%40unh.edu%7C59c29799f1544a59f44108dd7a62352a%7Cd6241893512d46dc8d2bbe47e25f5666%7C0%7C0%7C638801284429852869%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=Qxiozd%2B2%2BnlqsKeuzzVdsU1egsfukfgNEyQyZASpLr8%3D&reserved=0<https://github.com/getelson-at-mellanox/rdpdk>
>
> Thanks for the link - I see you've been pushing code actively! Good to see opportunities,
> and compare approaches and concepts. Have you investigated wrapping the various pointers
> into structs, and providing safer APIs? For example, the [*mut rte_mbuf; 64] array for RX causes
> raw pointers to be handled for all packet-processing - resulting in "unsafe{ /* work here */ }" blocks.
>
This construct can work:
struct PktsBuf<const SIZE: usize> {
buffer: [*mut rte_mbuf; SIZE]
}
I think that they mean a construct that wraps a *mut rte_mbuf and provides a safe interface. Another option, depending on how invasive the bindings are allowed to be, is to make them references with a lifetime tied to the mempool they are allocated from. For many usecases, mempools exist for the entire lifetime of the program and have static lifetimes, meaning that the references have static lifetimes which greatly simplifies a lot of the code. This also gets rid of unsafe accesses. However, it would require a wrapper around the buffer with the valid length, something like a fixed-capacity vector. Another option is to make the pointers Option<NonNull<rte_mbuf>>, which has the same ABI as a C *rte_mbuf but forces null checks. This will be annoying to program against, but for "one packet at a time to completion" processing it's equivalent to "if (buffer[i] != null) { ... }" at the top of the loop. For the pipeline or graph libraries, this latter option will incur a lot of potentially extra null checks, but the upside is that it's ABI compatible with the burst functions from the C API.
> The code in the above repo feels like "DPDK C code written in Rust".
Agree.
At this stage there is no native Rust DPDK API.
> It is a great step towards better
> understanding, and having something that works is very valuable; thanks for sharing it.
>
> A "top down" application view might help to brainstorm idiomatic/Safe Rust APIs, and then we can
> discuss/understand how to map these high level APIs onto the "DPDK C in Rust" or even "DPDK C API/ABI" layers.
>
> Does that seem like a good method to you, to achieve an ergonomic Safe Rust API as the end result?
>
I think we need to conclude what the safe API means in terms of DPDK project.
Because it's possible to provide native Rust API for DPDK what will use FFI.
Specially, if Rust PMD is not in plans and performance is one of the main goals.
I think most of the value of Rust can be gotten by making a good Rust API available to consumers of DPDK. There is value in having Rust for PMDs, but there is a lot more code written which consumes DPDK than code in new PMDs. It might be better to hold that conversation until someone wants to write a Rust PMD. I think that there is also value in a version which is "maximum safety" that takes performance hits where necessary. DPDK is far enough ahead of the performance of most other options that "DPDK at 80%" is still going to be fast enough for many purposes and the extra safety is valuable there from an ease-of-use perspective. There are, of course, applications which need everything DPDK has to offer from a performance perspective, and those should be served as well, but I think DPDK can offer a spectrum where users get steadily closer to "zero overhead abstraction over the DPDK C API" the more performance they need, possibly sacrificing some safety along the way.
>
>>> Next steps are to "allowlist" more DPDK public API functions, and start building "Safe Rust" APIs over
>>> them, in order to expose an ergonomic and misuse-resistant API. As you note, this is where a network ports,
>>> lcores, mempools, and ethdev configuration are all required. First goal something like "Safe L2 macswap"?
check out for Rx, L2 addr swap, Tx sequence here:
https://nam12.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fgetelson-at-mellanox%2Frdpdk%2Fblob%2F253fde7c16a5514bf99d3823725f825262e244c8%2Fapp%2Frunpmd%2Frunpmd.rs%23L156&data=05%7C02%7Cowen.hilyard%40unh.edu%7C59c29799f1544a59f44108dd7a62352a%7Cd6241893512d46dc8d2bbe47e25f5666%7C0%7C0%7C638801284429865170%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=DKvEoYTKjKEx75AKr16Ge7wN7vigNrv75O1p%2BwTzdH0%3D&reserved=0<https://github.com/getelson-at-mellanox/rdpdk/blob/253fde7c16a5514bf99d3823725f825262e244c8/app/runpmd/runpmd.rs#L156>
Regards,
Gregory
>From: Owen Hilyard
>Sent: Sunday, April 13, 2025 6:09 PM
>To: Etelson, Gregory; Van Haaren, Harry
>Cc: Richardson, Bruce; dev@dpdk.org
>Subject: Re: [RFC PATCH] add rust binding support to DPDK
>
>Hello all,
>
>I wanted to chime in as someone who uses DPDK from Rust in quite a few projects.
Great - thanks - more input from different perspectives is good!
<big snip>
> I think most of the value of Rust can be gotten by making a good Rust API available to consumers of DPDK.
Yes - agree here! Adoption of DPDK in Rust ecosystem must "feel normal" just like depending on any other Rust crate.
> There is value in having Rust for PMDs, but there is a lot more code written which consumes DPDK than code in new PMDs. It might be better to hold that conversation until someone wants to write a Rust PMD.
Building PMDs in Rust (while interesting) is not the primary goal here; correct. "End user" of DPDK having a safe and high-performance Rust API is the goal.
> I think that there is also value in a version which is "maximum safety" that takes performance hits where necessary. DPDK is far enough ahead of the performance of most other options that "DPDK at 80%" is still going to be fast enough for many purposes and the extra safety is valuable there from an ease-of-use perspective. There are, of course, applications which need everything DPDK has to offer from a performance perspective, and those should be served as well, but I think DPDK can offer a spectrum where users get steadily closer to "zero overhead abstraction over the DPDK C API" the more performance they need, possibly sacrificing some safety along the way.
I like this perspective - as hardware vendors its easy to get hyper focused on maximum performance. To be clear; I want to ensure DPDK+Rust can hit max
performance too, but lets focus on Safety/Ergonomics first and then optimize, not the other way around.
I see (Gregory) you posted some more "Rust like abstraction" code for DPDK: https://mails.dpdk.org/archives/dev/2025-April/317088.html
I'll reply there shortly, and give some thoughts on the details as next steps towards a safe/abstracted (but high performance!) API.
Regards, -Harry
new file mode 100644
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2025 Intel Corporation
+
+[package]
+name = "dpdk-c"
+version = "25.0.7"
+build = "buildtools/rust/build.rs"
+edition = "2021"
+
+[lib]
+path = "buildtools/rust/lib.rs"
+doctest = false
+
+[build-dependencies]
+bindgen = "0.71"
+meson-next = "1"
@@ -26,6 +26,7 @@ header_gen_cmd = py3 + files('gen-header.py')
has_hugepages_cmd = py3 + files('has-hugepages.py')
cmdline_gen_cmd = py3 + files('dpdk-cmdline-gen.py')
check_dts_requirements = py3 + files('check-dts-requirements.py')
+gen_cargo_bindgen_info = py3 + files('rust/gen-cargo-bindgen-info.py')
# install any build tools that end-users might want also
install_data([
new file mode 100644
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+extern crate meson_next as meson;
+use std::collections::HashMap;
+use std::env;
+use std::path::PathBuf;
+
+fn main() {
+ let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+ let build_dir = out_path.join("build");
+
+ let meson_cfg = meson::Config::new().options(HashMap::from([
+ ("enable_libs", "eal"),
+ ("enable_drivers", "net/*,net/intel/*"),
+ ("enable_apps", "test")
+ ]));
+ meson::build(".", build_dir.to_str().unwrap(), meson_cfg);
+
+ /* open and print file 'cargo_rules.txt' from build_dir */
+ let cargo_ldflags_file = build_dir.join("cargo_ldflags.txt");
+ println!("cargo:rerun-if-changed={}", cargo_ldflags_file.display());
+ print!("{}", std::fs::read_to_string(cargo_ldflags_file).unwrap());
+
+ let bindgen_include_file = build_dir.join("bindgen_cflags.txt");
+ let mut bindings = bindgen::Builder::default();
+ for line in std::fs::read_to_string(bindgen_include_file).unwrap().lines() {
+ bindings = bindings.clang_arg(line);
+ }
+
+ let bindings = bindings.header("buildtools/rust/wrapper.h")
+ .derive_default(true)
+ .allowlist_function("rte_eal_init")
+ .allowlist_function("rte_eal_cleanup")
+ // Tell cargo to invalidate the built crate whenever any of the
+ // included header files changed.
+ .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
+ .generate()
+ .expect("Unable to generate bindings");
+
+ bindings
+ .write_to_file(out_path.join("bindings.rs"))
+ .expect("Couldn't write bindings!");
+}
new file mode 100644
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2025 Intel Corporation
+
+# Script to generate cargo rules for static linking in rust builds
+# outputs one line per library, for drivers and libraries
+
+import os
+import os.path
+import sys
+import subprocess
+
+if 'MESON_BUILD_ROOT' not in os.environ:
+ print('This script must be called from a meson build environment')
+ sys.exit(1)
+
+pkgconf = sys.argv[1]
+os.environ['PKG_CONFIG_PATH'] = os.path.join(os.environ['MESON_BUILD_ROOT'], 'meson-uninstalled')
+linker_flags = subprocess.check_output([pkgconf, '--libs', '--static', 'libdpdk']).decode('utf-8')
+cflags = subprocess.check_output([pkgconf, '--cflags', 'libdpdk']).decode('utf-8')
+
+whole_archive = False
+with open(os.path.join(os.environ['MESON_BUILD_ROOT'], 'cargo_ldflags.txt'), 'w') as dst:
+ for flag in linker_flags.split():
+ if flag == '-pthread':
+ continue
+ elif flag == '-Wl,--whole-archive':
+ whole_archive = True
+ elif flag == '-Wl,--no-whole-archive':
+ whole_archive = False
+ elif flag.startswith('-L'):
+ dst.write(f'cargo:rustc-link-search=native={flag[2:]}\n')
+ elif flag.startswith('-l:'):
+ libname = flag[3:]
+ if libname.startswith('lib'):
+ libname = libname[3:]
+ if libname.endswith('.a'):
+ libname = libname[:-2]
+ if whole_archive:
+ dst.write(f'cargo:rustc-link-lib=static:+whole-archive={libname}\n')
+ else:
+ dst.write(f'cargo:rustc-link-lib=static={libname}\n')
+ elif flag.startswith('-lrte_'):
+ # skip any other DPDK lib flags, we already have them above
+ continue
+ elif flag.startswith('-l'):
+ dst.write(f'cargo:rustc-link-lib={flag[2:]}\n')
+ else:
+ print(f'Warning: Unknown flag: {flag}', file=sys.stderr)
+
+with open(os.path.join(os.environ['MESON_BUILD_ROOT'], 'bindgen_cflags.txt'), 'w') as dst:
+ for flag in cflags.split():
+ dst.write(f'{flag}\n')
new file mode 100644
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_helloworld() {
+ let appname = std::ffi::CString::new("test-rs").unwrap();
+ let mut argv = [appname.into_raw()];
+ let ret = unsafe {
+ rte_eal_init(argv.len().try_into().unwrap(), argv.as_mut_ptr())
+ };
+ assert!(ret >= 0, "rte_eal_init failed");
+ unsafe { rte_eal_cleanup() };
+ }
+}
new file mode 100644
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#include <rte_eal.h>
@@ -114,6 +114,9 @@ configure_file(output: build_cfg,
# build pkg-config files for dpdk
subdir('buildtools/pkg-config')
+# output a list of cargo parameters if DPDK is being built for rust use
+run_command([gen_cargo_bindgen_info, pkgconf], check: true)
+
if meson.is_subproject()
subdir('buildtools/subproject')
endif