diff --git a/source/extensions/dynamic_modules/sdk/README.md b/source/extensions/dynamic_modules/sdk/README.md new file mode 100644 index 000000000000..64d61baa62f2 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/README.md @@ -0,0 +1,4 @@ +## Dynamic Modules SDKs + +This directory contains the SDKs for the Dynamic Modules feature. Each SDK passes the same set of tests and +is guaranteed to provide the same functionality. diff --git a/source/extensions/dynamic_modules/sdk/rust/.gitignore b/source/extensions/dynamic_modules/sdk/rust/.gitignore new file mode 100644 index 000000000000..ea8c4bf7f35f --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/source/extensions/dynamic_modules/sdk/rust/BUILD b/source/extensions/dynamic_modules/sdk/rust/BUILD new file mode 100644 index 000000000000..480a9432eb58 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/rust/BUILD @@ -0,0 +1,15 @@ +load("@rules_rust//rust:defs.bzl", "rust_library") +load( + "//bazel:envoy_build_system.bzl", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +rust_library( + name = "envoy_proxy_dynamic_modules_rust_sdk", + srcs = glob(["src/**/*.rs"]), + edition = "2021", +) diff --git a/source/extensions/dynamic_modules/sdk/rust/Cargo.lock b/source/extensions/dynamic_modules/sdk/rust/Cargo.lock new file mode 100644 index 000000000000..f6f126e02005 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "envoy-proxy-dynamic-modules-rust-sdk" +version = "0.1.0" diff --git a/source/extensions/dynamic_modules/sdk/rust/Cargo.toml b/source/extensions/dynamic_modules/sdk/rust/Cargo.toml new file mode 100644 index 000000000000..3874a8a3301b --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/rust/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "envoy-proxy-dynamic-modules-rust-sdk" +version = "0.1.0" +edition = "2021" +authors = ["Envoy Proxy Authors "] +description = "Envoy Proxy Dynamic Modules Rust SDK" +license = "Apache-2.0" +repository = "https://github.com/envoyproxy/envoy" + +[dependencies] + +[lib] diff --git a/source/extensions/dynamic_modules/sdk/rust/README.md b/source/extensions/dynamic_modules/sdk/rust/README.md new file mode 100644 index 000000000000..9470ff7b65df --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/rust/README.md @@ -0,0 +1,7 @@ +# Envoy Dynamic Modules Rust SDK + +This directory contains the Rust SDK for the Dynamic Modules feature. The SDK passes the same set of tests and is guaranteed to provide the same functionality as the other SDKs. This directory is organized in the way that it can be used as a standalone Rust crate. The SDK is basically the high-level abstraction layer for the Dynamic Modules ABI defined in the [abi.h](../../abi.h). + +Currently, the ABI binding ([src/abi.rs](./src/abi.rs)) is manually generated by [`bindgen`](https://github.com/rust-lang/rust-bindgen) for the ABI header. Ideally, we should be able to do the bindgen in the build.rs file. + +TODO(@mathetake): figure out how to properly setup the bindgen in build.rs with rules_rust. The most recommended way is to use [crate_universe](https://bazelbuild.github.io/rules_rust/crate_universe.html#setup) and use bindgen as a dev-dependency. However, it seems that crate_universe tries to use the underlying gcc system linker which we cannot assume always available. diff --git a/source/extensions/dynamic_modules/sdk/rust/src/abi.rs b/source/extensions/dynamic_modules/sdk/rust/src/abi.rs new file mode 100644 index 000000000000..d673d5b2c599 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/rust/src/abi.rs @@ -0,0 +1,17 @@ +/* automatically generated by rust-bindgen 0.70.1 */ + +pub type wchar_t = ::std::os::raw::c_int; +#[repr(C)] +#[repr(align(16))] +#[derive(Debug, Copy, Clone)] +pub struct max_align_t { + pub __clang_max_align_nonce1: ::std::os::raw::c_longlong, + pub __bindgen_padding_0: u64, + pub __clang_max_align_nonce2: u128, +} +#[doc = " envoy_dynamic_module_type_abi_version represents a null-terminated string that contains the ABI\n version of the dynamic module. This is used to ensure that the dynamic module is built against\n the compatible version of the ABI."] +pub type envoy_dynamic_module_type_abi_version = *const ::std::os::raw::c_char; +extern "C" { + #[doc = " envoy_dynamic_module_on_program_init is called by the main thread exactly when the module is\n loaded. The function returns the ABI version of the dynamic module. If null is returned, the\n module will be unloaded immediately.\n\n For Envoy, the return value will be used to check the compatibility of the dynamic module.\n\n For dynamic modules, this is useful when they need to perform some process-wide\n initialization or check if the module is compatible with the platform, such as CPU features.\n Note that initialization routines of a dynamic module can also be performed without this function\n through constructor functions in an object file. However, normal constructors cannot be used\n to check compatibility and gracefully fail the initialization because there is no way to\n report an error to Envoy.\n\n @return envoy_dynamic_module_type_abi_version is the ABI version of the dynamic module. Null\n means the error and the module will be unloaded immediately."] + pub fn envoy_dynamic_module_on_program_init() -> envoy_dynamic_module_type_abi_version; +} diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs new file mode 100644 index 000000000000..e8aa578d6c95 --- /dev/null +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs @@ -0,0 +1,40 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] + +mod abi; + +/// Declare the init function for the dynamic module. This function is called when the dynamic module is loaded. +/// The function must return true on success, and false on failure. When it returns false, +/// the dynamic module will not be loaded. +/// +/// This is useful to perform any process-wide initialization that the dynamic module needs. +/// +/// # Example +/// +/// ``` +/// use envoy_proxy_dynamic_modules_rust_sdk::declare_program_init; +/// +/// declare_program_init!(my_program_init); +/// +/// fn my_program_init() -> bool { +/// true +/// } +/// ``` +#[macro_export] +macro_rules! declare_program_init { + ($f:ident) => { + #[no_mangle] + pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { + if ($f()) { + // This magic number is sha256 of the ABI headers which must match the + // value in abi_version.h + b"749b1e6bf97309b7d171009700a80e651ac61e35f9770c24a63460d765895a51\0".as_ptr() + as *const ::std::os::raw::c_char + } else { + ::std::ptr::null() + } + } + }; +} diff --git a/test/extensions/dynamic_modules/BUILD b/test/extensions/dynamic_modules/BUILD index 487553679f41..f136dd11269e 100644 --- a/test/extensions/dynamic_modules/BUILD +++ b/test/extensions/dynamic_modules/BUILD @@ -16,6 +16,10 @@ envoy_cc_test( "//test/extensions/dynamic_modules/test_data/c:no_op", "//test/extensions/dynamic_modules/test_data/c:no_program_init", "//test/extensions/dynamic_modules/test_data/c:program_init_fail", + "//test/extensions/dynamic_modules/test_data/rust:abi_version_mismatch", + "//test/extensions/dynamic_modules/test_data/rust:no_op", + "//test/extensions/dynamic_modules/test_data/rust:no_program_init", + "//test/extensions/dynamic_modules/test_data/rust:program_init_fail", ], deps = [ "//source/extensions/dynamic_modules:dynamic_modules_lib", diff --git a/test/extensions/dynamic_modules/abi_version_test.cc b/test/extensions/dynamic_modules/abi_version_test.cc index 3f7b4eb74834..958295281dde 100644 --- a/test/extensions/dynamic_modules/abi_version_test.cc +++ b/test/extensions/dynamic_modules/abi_version_test.cc @@ -15,7 +15,7 @@ namespace Envoy { namespace Extensions { namespace DynamicModules { -// This test ensure that abi_version.h contains the correct sha256 hash of ABI header files. +// This test ensures that abi_version.h contains the correct sha256 hash of ABI header files. TEST(DynamicModules, ABIVersionCheck) { const auto abi_header_path = TestEnvironment::substitute("{{ test_rundir }}/source/extensions/dynamic_modules/abi.h"); diff --git a/test/extensions/dynamic_modules/dynamic_modules_test.cc b/test/extensions/dynamic_modules/dynamic_modules_test.cc index 1c02d4e9d4fb..7368af924aa1 100644 --- a/test/extensions/dynamic_modules/dynamic_modules_test.cc +++ b/test/extensions/dynamic_modules/dynamic_modules_test.cc @@ -37,7 +37,7 @@ class DynamicModuleTestLanguages : public ::testing::TestWithParam }; INSTANTIATE_TEST_SUITE_P(LanguageTests, DynamicModuleTestLanguages, - testing::Values("c"), // TODO: Other languages. + testing::Values("c", "rust"), // TODO: add Go. DynamicModuleTestLanguages::languageParamToTestName); TEST_P(DynamicModuleTestLanguages, DoNotClose) { @@ -48,6 +48,7 @@ TEST_P(DynamicModuleTestLanguages, DoNotClose) { EXPECT_TRUE(module.ok()); const auto getSomeVariable = module->get()->getFunctionPointer("getSomeVariable"); + EXPECT_NE(getSomeVariable, nullptr); EXPECT_EQ(getSomeVariable(), 1); EXPECT_EQ(getSomeVariable(), 2); EXPECT_EQ(getSomeVariable(), 3); diff --git a/test/extensions/dynamic_modules/test_data/rust/.gitignore b/test/extensions/dynamic_modules/test_data/rust/.gitignore new file mode 100644 index 000000000000..96ef6c0b944e --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/rust/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/test/extensions/dynamic_modules/test_data/rust/BUILD b/test/extensions/dynamic_modules/test_data/rust/BUILD new file mode 100644 index 000000000000..b9bf03ebf106 --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/rust/BUILD @@ -0,0 +1,32 @@ +load("@rules_rust//rust:defs.bzl", "rust_shared_library") + +licenses(["notice"]) # Apache 2 + +package(default_visibility = ["//test/extensions/dynamic_modules:__pkg__"]) + +rust_shared_library( + name = "no_op", + srcs = ["no_op.rs"], + edition = "2021", + deps = [ + "//source/extensions/dynamic_modules/sdk/rust:envoy_proxy_dynamic_modules_rust_sdk", + ], +) + +rust_shared_library( + name = "no_program_init", + srcs = ["no_program_init.rs"], + edition = "2021", +) + +rust_shared_library( + name = "program_init_fail", + srcs = ["program_init_fail.rs"], + edition = "2021", +) + +rust_shared_library( + name = "abi_version_mismatch", + srcs = ["abi_version_mismatch.rs"], + edition = "2021", +) diff --git a/test/extensions/dynamic_modules/test_data/rust/Cargo.toml b/test/extensions/dynamic_modules/test_data/rust/Cargo.toml new file mode 100644 index 000000000000..592f3f84b52f --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/rust/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "test-programs" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/envoyproxy/envoy" + +[dependencies] +envoy-proxy-dynamic-modules-rust-sdk = { path = "../../../../../source/extensions/dynamic_modules/sdk/rust" } + +[[example]] +name = "no_op" +path = "no_op.rs" +crate-type = ["cdylib"] + +[[example]] +name = "no_program_init" +path = "no_program_init.rs" +crate-type = ["cdylib"] + +[[example]] +name = "program_init_fail" +path = "program_init_fail.rs" +crate-type = ["cdylib"] + +[[example]] +name = "abi_version_mismatch" +path = "abi_version_mismatch.rs" +crate-type = ["cdylib"] diff --git a/test/extensions/dynamic_modules/test_data/rust/abi_version_mismatch.rs b/test/extensions/dynamic_modules/test_data/rust/abi_version_mismatch.rs new file mode 100644 index 000000000000..8afbd0d965d8 --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/rust/abi_version_mismatch.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { + b"invalid-version-hash\0".as_ptr() as *const ::std::os::raw::c_char +} diff --git a/test/extensions/dynamic_modules/test_data/rust/no_op.rs b/test/extensions/dynamic_modules/test_data/rust/no_op.rs new file mode 100644 index 000000000000..309744dd86da --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/rust/no_op.rs @@ -0,0 +1,15 @@ +use envoy_proxy_dynamic_modules_rust_sdk::declare_program_init; +use std::sync::atomic::{AtomicI32, Ordering}; + +declare_program_init!(init); + +fn init() -> bool { + true +} + +static SOME_VARIABLE: AtomicI32 = AtomicI32::new(1); + +#[no_mangle] +pub extern "C" fn getSomeVariable() -> i32 { + SOME_VARIABLE.fetch_add(1, Ordering::SeqCst) +} diff --git a/test/extensions/dynamic_modules/test_data/rust/no_program_init.rs b/test/extensions/dynamic_modules/test_data/rust/no_program_init.rs new file mode 100644 index 000000000000..db872215ab1d --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/rust/no_program_init.rs @@ -0,0 +1,3 @@ +pub extern "C" fn foo() -> i32 { + 0 +} diff --git a/test/extensions/dynamic_modules/test_data/rust/program_init_fail.rs b/test/extensions/dynamic_modules/test_data/rust/program_init_fail.rs new file mode 100644 index 000000000000..e9d7aed01306 --- /dev/null +++ b/test/extensions/dynamic_modules/test_data/rust/program_init_fail.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn envoy_dynamic_module_on_program_init() -> *const ::std::os::raw::c_char { + ::std::ptr::null() +}