diff --git a/Cargo.lock b/Cargo.lock index c751c5a..6d13bd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,29 @@ dependencies = [ "which", ] +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.16", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -197,7 +220,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.13", + "syn 2.0.16", ] [[package]] @@ -214,7 +237,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.16", ] [[package]] @@ -445,7 +468,7 @@ dependencies = [ name = "nginx-sys" version = "0.1.0" dependencies = [ - "bindgen", + "bindgen 0.65.1", "duct", "flate2", "tar", @@ -457,7 +480,7 @@ dependencies = [ name = "ngx" version = "0.3.0-beta" dependencies = [ - "bindgen", + "bindgen 0.64.0", "duct", "flate2", "nginx-sys", @@ -529,6 +552,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "prettyplease" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617feabb81566b593beb4886fb8c1f38064169dae4dccad0e3220160c3b37203" +dependencies = [ + "proc-macro2", + "syn 2.0.16", +] + [[package]] name = "proc-macro2" version = "1.0.56" @@ -678,9 +711,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5388e8c..77c97b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,12 @@ name = "ngx" version = "0.3.0-beta" edition = "2021" autoexamples = false +categories = ["api-bindings", "network-programming"] +description = "FFI bindings to NGINX" +repository = "https://github.com/nginxinc/ngx-rust" +homepage = "https://github.com/nginxinc/ngx-rust" license = "Apache-2.0" +keywords = ["nginx", "module", "sys"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -23,3 +28,6 @@ duct = "0.13.6" ureq = { version = "2.6.2", features = ["tls"] } flate2 = "1.0.25" tar = "0.4.38" + +[badges] +maintenance = { status = "experimental" } diff --git a/README.md b/README.md index 4c6c6f8..0c72b1b 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,7 @@ In short, this SDK allows writing NGINX modules using the Rust language. ## Build -NGINX modules can be build against a particular version of NGINX. The following environment variables can be used to -specify particular version of NGINX or an NGINX dependency: +NGINX modules can be built against a particular version of NGINX. The following environment variables can be used to specify a particular version of NGINX or an NGINX dependency: * `ZLIB_VERSION` (default 1.2.13) - * `PCRE2_VERSION` (default 10.42) diff --git a/examples/README.md b/examples/README.md index a4d7a0f..aabf24c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,14 +1,65 @@ +- [Examples](#examples) + - [CURL](#curl) + - [AWSSIG](#awssig) + - [HTTPORIGDST - NGINX Destination IP recovery module for HTTP](#httporigdst----nginx-destination-ip-recovery-module-for-http) + - [Dependencies](#dependencies) + - [Example Configuration](#example-configuration) + - [HTTP](#http) + - [Embedded Variables](#embedded-variables) + - [Usage](#usage) + - [Caveats](#caveats) + + # Examples +This crate provides a couple of example using [ngx](https://crates.io/crates/ngx) crate: + +- [awssig.rs](./awssig.rs) - An example of NGINX dynamic module that can sign GET request using AWS Signature v4. +- [curl](./curl.rs) - An example of the Access Phase NGINX dynamic module that blocks HTTP requests if `user-agent` header starts with `curl`. +- [httporigdst](./httporigdst.rs) - A dynamic module recovers the original IP address and port number of the destination packet. + +To build all these examples simply run: + +``` +cargo build --package=examples --examples +``` + + +## CURL + +This module demonstrates how to create a minimal dynamic module with `http_request_handler`, that checks for User-Agent headers and returns status code 403 if UA starts with `curl`, if a module is disabled then uses `core::Status::NGX_DECLINED` to indicate the operation is rejected, for example, because it is disabled in the configuration (`curl off`). Additionally, it demonstrates how to write a defective parser. + +An example of nginx configuration file that uses that module can be found at [curl.conf](./curl.conf). -## NGINX Destination IP recovery module for HTTP +How to build and run in a [Docker](../Dockerfile) container curl example: +``` +# build all dynamic modules examples and specify NGINX version to use +docker buildx build --build-arg NGX_VERSION=1.23.3 -t ngx-rust . + +# start NGINX using curl.conf module example: +docker run --rm -d -p 8000:8000 ngx-rust nginx -c examples/curl.conf + +# test it - you should see 403 Forbidden +curl http://127.0.0.1:8000 -v -H "user-agent: curl" + + +# test it - you should see 404 Not Found +curl http://127.0.0.1:8000 -v -H "user-agent: foo" +``` -This dynamic module recovers original IP address and port number of the destination packet. It is useful, for example, with container sidecars where all outgoing traffic is redirected to a separate container with iptables before reaching the target. +## AWSSIG + +This module uses [NGX_HTTP_PRECONTENT_PHASE](https://nginx.org/en/docs/dev/development_guide.html#http_phases) and provides examples, of how to use external dependency and manipulate HTTP headers before sending client requests upstream. + +An example of nginx configuration file that uses that module can be found at [awssig.conf](./awssig.conf). + +## HTTPORIGDST - NGINX Destination IP recovery module for HTTP + +This dynamic module recovers the original IP address and port number of the destination packet. It is useful, for example, with container sidecars where all outgoing traffic is redirected to a separate container with iptables before reaching the target. This module can only be built with the "linux" feature enabled, and will only successfully build on a Linux OS. ### Dependencies - -This modules uses the Rust crate libc and Linux **getsockopt** socket API. +This module uses the Rust crate libc and Linux **getsockopt** socket API. ### Example Configuration #### HTTP diff --git a/src/core/buffer.rs b/src/core/buffer.rs index 4ea72cb..0dd1c10 100644 --- a/src/core/buffer.rs +++ b/src/core/buffer.rs @@ -2,16 +2,27 @@ use crate::ffi::*; use std::slice; +/// The `Buffer` trait provides methods for working with an nginx buffer (`ngx_buf_t`). pub trait Buffer { + /// Returns a raw pointer to the underlying `ngx_buf_t` of the buffer. fn as_ngx_buf(&self) -> *const ngx_buf_t; + /// Returns a mutable raw pointer to the underlying `ngx_buf_t` of the buffer. fn as_ngx_buf_mut(&mut self) -> *mut ngx_buf_t; + /// Returns the buffer contents as a byte slice. + /// + /// # Safety + /// This function is marked as unsafe because it involves raw pointer manipulation. fn as_bytes(&self) -> &[u8] { let buf = self.as_ngx_buf(); unsafe { slice::from_raw_parts((*buf).pos, self.len()) } } + /// Returns the length of the buffer contents. + /// + /// # Safety + /// This function is marked as unsafe because it involves raw pointer manipulation. fn len(&self) -> usize { let buf = self.as_ngx_buf(); unsafe { @@ -22,10 +33,16 @@ pub trait Buffer { } } + /// Returns `true` if the buffer is empty, i.e., it has zero length. fn is_empty(&self) -> bool { self.len() == 0 } + /// Sets the `last_buf` flag of the buffer. + /// + /// # Arguments + /// + /// * `last` - A boolean indicating whether the buffer is the last buffer in a request. fn set_last_buf(&mut self, last: bool) { let buf = self.as_ngx_buf_mut(); unsafe { @@ -33,6 +50,11 @@ pub trait Buffer { } } + /// Sets the `last_in_chain` flag of the buffer. + /// + /// # Arguments + /// + /// * `last` - A boolean indicating whether the buffer is the last buffer in a chain of buffers. fn set_last_in_chain(&mut self, last: bool) { let buf = self.as_ngx_buf_mut(); unsafe { @@ -41,16 +63,26 @@ pub trait Buffer { } } +/// The `MutableBuffer` trait extends the `Buffer` trait and provides methods for working with a mutable buffer. pub trait MutableBuffer: Buffer { + /// Returns a mutable reference to the buffer contents as a byte slice. + /// + /// # Safety + /// This function is marked as unsafe because it involves raw pointer manipulation. fn as_bytes_mut(&mut self) -> &mut [u8] { let buf = self.as_ngx_buf_mut(); unsafe { slice::from_raw_parts_mut((*buf).pos, self.len()) } } } +/// Wrapper struct for a temporary buffer, providing methods for working with an `ngx_buf_t`. pub struct TemporaryBuffer(*mut ngx_buf_t); impl TemporaryBuffer { + /// Creates a new `TemporaryBuffer` from an `ngx_buf_t` pointer. + /// + /// # Panics + /// Panics if the given buffer pointer is null. pub fn from_ngx_buf(buf: *mut ngx_buf_t) -> TemporaryBuffer { assert!(!buf.is_null()); TemporaryBuffer(buf) @@ -58,24 +90,35 @@ impl TemporaryBuffer { } impl Buffer for TemporaryBuffer { + /// Returns the underlying `ngx_buf_t` pointer as a raw pointer. fn as_ngx_buf(&self) -> *const ngx_buf_t { self.0 } + /// Returns a mutable reference to the underlying `ngx_buf_t` pointer. fn as_ngx_buf_mut(&mut self) -> *mut ngx_buf_t { self.0 } } impl MutableBuffer for TemporaryBuffer { + /// Returns a mutable reference to the buffer contents as a byte slice. + /// + /// # Safety + /// This function is marked as unsafe because it involves raw pointer manipulation. fn as_bytes_mut(&mut self) -> &mut [u8] { unsafe { slice::from_raw_parts_mut((*self.0).pos, self.len()) } } } +/// Wrapper struct for a memory buffer, providing methods for working with an `ngx_buf_t`. pub struct MemoryBuffer(*mut ngx_buf_t); impl MemoryBuffer { + /// Creates a new `MemoryBuffer` from an `ngx_buf_t` pointer. + /// + /// # Panics + /// Panics if the given buffer pointer is null. pub fn from_ngx_buf(buf: *mut ngx_buf_t) -> MemoryBuffer { assert!(!buf.is_null()); MemoryBuffer(buf) @@ -83,10 +126,12 @@ impl MemoryBuffer { } impl Buffer for MemoryBuffer { + /// Returns the underlying `ngx_buf_t` pointer as a raw pointer. fn as_ngx_buf(&self) -> *const ngx_buf_t { self.0 } + /// Returns a mutable reference to the underlying `ngx_buf_t` pointer. fn as_ngx_buf_mut(&mut self) -> *mut ngx_buf_t { self.0 } diff --git a/src/core/mod.rs b/src/core/mod.rs index 271e23d..a91a496 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -50,7 +50,7 @@ macro_rules! ngx_http_null_variable { /// /// This is typically used to terminate an array of Stream variable types. /// -/// [`ngx_stream_variable_t`]: TODO: find appropriate link +/// [`ngx_stream_variable_t`]: https://github.com/nginx/nginx/blob/1a8ef991d92d22eb8aded7f49595dd31a639e8a4/src/stream/ngx_stream_variables.h#L21 #[macro_export] macro_rules! ngx_stream_null_variable { () => { diff --git a/src/core/pool.rs b/src/core/pool.rs index 21f9aa9..ff16eef 100644 --- a/src/core/pool.rs +++ b/src/core/pool.rs @@ -4,18 +4,23 @@ use crate::ffi::*; use std::os::raw::c_void; use std::{mem, ptr}; +/// Wrapper struct for an `ngx_pool_t` pointer, providing methods for working with memory pools. pub struct Pool(*mut ngx_pool_t); impl Pool { - /// # Safety + /// Creates a new `Pool` from an `ngx_pool_t` pointer. /// - /// The caller has provided a valid `ngx_pool_t` that points to valid memory and is non-null. - /// A null argument will assert and panic. + /// # Safety + /// The caller must ensure that a valid `ngx_pool_t` pointer is provided, pointing to valid memory and non-null. + /// A null argument will cause an assertion failure and panic. pub unsafe fn from_ngx_pool(pool: *mut ngx_pool_t) -> Pool { assert!(!pool.is_null()); Pool(pool) } + /// Creates a buffer of the specified size in the memory pool. + /// + /// Returns `Some(TemporaryBuffer)` if the buffer is successfully created, or `None` if allocation fails. pub fn create_buffer(&mut self, size: usize) -> Option { let buf = unsafe { ngx_create_temp_buf(self.0, size) }; if buf.is_null() { @@ -25,6 +30,9 @@ impl Pool { Some(TemporaryBuffer::from_ngx_buf(buf)) } + /// Creates a buffer from a string in the memory pool. + /// + /// Returns `Some(TemporaryBuffer)` if the buffer is successfully created, or `None` if allocation fails. pub fn create_buffer_from_str(&mut self, str: &str) -> Option { let mut buffer = self.create_buffer(str.len())?; unsafe { @@ -35,6 +43,9 @@ impl Pool { Some(buffer) } + /// Creates a buffer from a static string in the memory pool. + /// + /// Returns `Some(MemoryBuffer)` if the buffer is successfully created, or `None` if allocation fails. pub fn create_buffer_from_static_str(&mut self, str: &'static str) -> Option { let buf = self.calloc_type::(); if buf.is_null() { @@ -56,6 +67,12 @@ impl Pool { Some(MemoryBuffer::from_ngx_buf(buf)) } + /// Adds a cleanup handler for a value in the memory pool. + /// + /// Returns `Ok(())` if the cleanup handler is successfully added, or `Err(())` if the cleanup handler cannot be added. + /// + /// # Safety + /// This function is marked as unsafe because it involves raw pointer manipulation. unsafe fn add_cleanup_for_value(&mut self, value: *mut T) -> Result<(), ()> { let cln = ngx_pool_cleanup_add(self.0, 0); if cln.is_null() { @@ -67,22 +84,40 @@ impl Pool { Ok(()) } + /// Allocates memory from the pool of the specified size. + /// + /// Returns a raw pointer to the allocated memory. pub fn alloc(&mut self, size: usize) -> *mut c_void { unsafe { ngx_palloc(self.0, size) } } + /// Allocates memory for a type from the pool. + /// + /// Returns a typed pointer to the allocated memory. pub fn alloc_type(&mut self) -> *mut T { self.alloc(mem::size_of::()) as *mut T } + /// Allocates zeroed memory from the pool of the specified size. + /// + /// Returns a raw pointer to the allocated memory. pub fn calloc(&mut self, size: usize) -> *mut c_void { unsafe { ngx_pcalloc(self.0, size) } } + /// Allocates zeroed memory for a type from the pool. + /// + /// Returns a typed pointer to the allocated memory. pub fn calloc_type(&mut self) -> *mut T { self.calloc(mem::size_of::()) as *mut T } + /// Allocates memory for a value of a specified type and adds a cleanup handler to the memory pool. + /// + /// Returns a typed pointer to the allocated memory if successful, or a null pointer if allocation or cleanup handler addition fails. + /// + /// # Safety + /// This function is marked as unsafe because it involves raw pointer manipulation. pub fn allocate(&mut self, value: T) -> *mut T { unsafe { let p = self.alloc(mem::size_of::()) as *mut T; @@ -96,6 +131,16 @@ impl Pool { } } +/// Cleanup handler for a specific type `T`. +/// +/// This function is called when cleaning up a value of type `T` in an FFI context. +/// +/// # Safety +/// This function is marked as unsafe due to the raw pointer manipulation and the assumption that `data` is a valid pointer to `T`. +/// +/// # Arguments +/// +/// * `data` - A raw pointer to the value of type `T` to be cleaned up. unsafe extern "C" fn cleanup_type(data: *mut c_void) { ptr::drop_in_place(data as *mut T); } diff --git a/src/http/request.rs b/src/http/request.rs index b07bba2..88607fd 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -39,7 +39,7 @@ macro_rules! http_subrequest_handler { /// /// The set handler allows setting the property referenced by the variable. /// The set handler expects a [`Request`], [`mut ngx_variable_valut_t`], and a [`usize`]. -/// Variables: https://nginx.org/en/docs/dev/development_guide.html#http_variables +/// Variables: #[macro_export] macro_rules! http_variable_set { ( $name: ident, $handler: expr ) => { @@ -59,7 +59,7 @@ macro_rules! http_variable_set { /// The get handler is responsible for evaluating a variable in the context of a specific request. /// Variable evaluators accept a [`Request`] input argument and two output /// arguments: [`ngx_http_variable_valut_t`] and [`usize`]. -/// Variables: https://nginx.org/en/docs/dev/development_guide.html#http_variables +/// Variables: #[macro_export] macro_rules! http_variable_get { ( $name: ident, $handler: expr ) => { @@ -75,6 +75,7 @@ macro_rules! http_variable_get { }; } +/// Wrapper struct for an `ngx_http_request_t` pointer, , providing methods for working with HTTP requests. #[repr(transparent)] pub struct Request(ngx_http_request_t); @@ -110,6 +111,9 @@ impl Request { self.0.connection } + /// Pointer to a [`ngx_log_t`]. + /// + /// [`ngx_log_t`]: https://nginx.org/en/docs/dev/development_guide.html#logging pub fn log(&self) -> *mut ngx_log_t { unsafe { (*self.connection()).log } } diff --git a/src/http/status.rs b/src/http/status.rs index 5aa3c11..cf02150 100644 --- a/src/http/status.rs +++ b/src/http/status.rs @@ -3,6 +3,7 @@ use crate::ffi::*; use std::error::Error; use std::fmt; +/// Represents an HTTP status code. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct HTTPStatus(pub ngx_uint_t); diff --git a/src/lib.rs b/src/lib.rs index 821c19d..a2d50b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,38 @@ +//! Bindings to NGINX +//! This project provides Rust SDK interfaces to the [NGINX](https://nginx.com) proxy allowing the creation of NGINX +//! dynamic modules completely in Rust. +//! +//! ## Build +//! +//! NGINX modules can be built against a particular version of NGINX. The following environment variables can be used +//! to specify a particular version of NGINX or an NGINX dependency: +//! +//! * `ZLIB_VERSION` (default 1.2.13) - +//! * `PCRE2_VERSION` (default 10.42) +//! * `OPENSSL_VERSION` (default 3.0.7) +//! * `NGX_VERSION` (default 1.23.3) - NGINX OSS version +//! * `NGX_DEBUG` (default to false)- if set to true, then will compile NGINX `--with-debug` option +//! +//! For example, this is how you would compile the [examples](https://github.com/nginxinc/ngx-rust/tree/master/examples) using a specific version of NGINX and enabling +//! debugging: `NGX_DEBUG=true NGX_VERSION=1.23.0 cargo build --package=examples --examples --release` +//! +//! To build Linux-only modules, use the "linux" feature: `cargo build --package=examples --examples --features=linux --release` +//! +//! After compilation, the modules can be found in the path `target/release/examples/` ( with the `.so` file extension for +//! Linux or `.dylib` for MacOS). +//! +//! Additionally, the folder `.cache/nginx/{NGX_VERSION}/{OS}/` will contain the compiled version of NGINX used to build +//! the SDK. You can start NGINX directly from this directory if you want to test the module or add it to `$PATH` +//! ```not_rust +//! $ export NGX_VERSION=1.23.3 +//! $ cargo build --package=examples --examples --features=linux --release +//! $ export PATH=$PATH:`pwd`/.cache/nginx/$NGX_VERSION/macos-x86_64/sbin +//! $ nginx -V +//! $ ls -la ./target/release/examples/ +//! # now you can use dynamic modules with the NGINX +//! ``` + +#![warn(missing_docs)] pub mod core; pub mod ffi; pub mod http;