From cc2e2bde6fa08331cc38a9106f395fd154f34a9e Mon Sep 17 00:00:00 2001 From: Matthew Yacobucci Date: Wed, 14 Jun 2023 06:42:57 -0600 Subject: [PATCH 1/5] basic upstream --- examples/Cargo.toml | 7 ++- examples/upstream.rs | 103 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 examples/upstream.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 77593592..713f991d 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -22,13 +22,16 @@ name = "awssig" path = "awssig.rs" crate-type = ["cdylib"] - - [[example]] name = "httporigdst" path = "httporigdst.rs" crate-type = ["cdylib"] required-features = ["linux"] +[[example]] +name = "upstream" +path = "upstream.rs" +crate-type = ["cdylib"] + [features] linux = [] diff --git a/examples/upstream.rs b/examples/upstream.rs new file mode 100644 index 00000000..bc9ecfe1 --- /dev/null +++ b/examples/upstream.rs @@ -0,0 +1,103 @@ +use ngx::{ + core, + ffi::{ + nginx_version, ngx_command_t, ngx_conf_t, ngx_http_module_t, ngx_http_upstream_init_peer_pt, + ngx_http_upstream_init_pt, ngx_int_t, ngx_module_t, ngx_uint_t, NGX_CONF_TAKE1, NGX_HTTP_MODULE, + NGX_HTTP_SRV_CONF, NGX_HTTP_UPS_CONF, NGX_RS_HTTP_SRV_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, + }, + http::{HTTPModule, Merge, MergeConfigError}, + ngx_modules, ngx_null_command, ngx_string, +}; +use std::os::raw::{c_char, c_void}; + +#[derive(Debug, Default)] +struct SrvConfig { + max: u32, + original_init_upstream: ngx_http_upstream_init_pt, + origin_init_peer: ngx_http_upstream_init_peer_pt, +} + +impl Merge for SrvConfig { + fn merge(&mut self, _prev: &ModuleConfig) -> Result<(), MergeConfigError> { + Ok(()) + } +} + +#[no_mangle] +static ngx_http_upstream_custom_ctx: ngx_http_module_t = ngx_http_module_t { + preconfiguration: Some(Module::preconfiguration), + postconfiguration: Some(Module::postconfiguration), + create_main_conf: Some(Module::create_main_conf), + init_main_conf: Some(Module::init_main_conf), + create_srv_conf: Some(Module::create_srv_conf), + merge_srv_conf: Some(Module::merge_srv_conf), + create_loc_conf: Some(Module::create_loc_conf), + merge_loc_conf: Some(Module::merge_loc_conf), +}; + +#[no_mangle] +static mut ngx_http_upstream_custom_commands: [ngx_command_t; 2] = [ + ngx_command_t { + name: ngx_string!("custom"), + type_: (NGX_HTTP_UPS_CONF | NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1) as ngx_uint_t, + set: Some(ngx_http_upstream_commands_set_custom), + conf: NGX_RS_HTTP_SRV_CONF_OFFSET, + offset: 0, + post: std::ptr::null_mut(), + }, + ngx_null_command!(), +]; + +ngx_modules!(ngx_http_upstream_custom_module); + +#[no_mangle] +pub static mut ngx_http_upstream_custom_module: ngx_module_t = ngx_module_t { + ctx_index: ngx_uint_t::max_value(), + index: ngx_uint_t::max_value(), + name: std::ptr::null_mut(), + spare0: 0, + spare1: 0, + version: nginx_version as ngx_uint_t, + signature: NGX_RS_MODULE_SIGNATURE.as_ptr() as *const c_char, + + ctx: &ngx_http_upstream_custom_ctx as *const _ as *mut _, + commands: unsafe { &ngx_http_upstream_custom_commands[0] as *const _ as *mut _ }, + type_: NGX_HTTP_MODULE as ngx_uint_t, + + init_master: None, + init_module: None, + init_process: None, + init_thread: None, + exit_thread: None, + exit_process: None, + exit_master: None, + + spare_hook0: 0, + spare_hook1: 0, + spare_hook2: 0, + spare_hook3: 0, + spare_hook4: 0, + spare_hook5: 0, + spare_hook6: 0, + spare_hook7: 0, +}; + +#[no_mangle] +extern "C" fn ngx_http_upstream_commands_set_custom( + cf: *mut ngx_conf_t, + _cmd: *mut ngx_command_t, + conf: *mut c_void, +) -> *mut c_char { +} + +struct Module; + +impl HTTPModule for Module { + type MainConf = (); + type SrvConf = ModuleConfig; + type LocConf = (); + + unsafe extern "C" fn create_srv_conf(cf: *mut ngx_conf_t) -> *mut c_void { + let conf: SrvConfig; + } +} From e85530333c2a005497dfc38dcfb4872f0713efb7 Mon Sep 17 00:00:00 2001 From: Matthew Yacobucci Date: Thu, 15 Jun 2023 07:22:31 -0600 Subject: [PATCH 2/5] more upstream implementation --- examples/upstream.rs | 67 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/examples/upstream.rs b/examples/upstream.rs index bc9ecfe1..0a16f83e 100644 --- a/examples/upstream.rs +++ b/examples/upstream.rs @@ -1,16 +1,21 @@ use ngx::{ - core, + core::Pool, ffi::{ - nginx_version, ngx_command_t, ngx_conf_t, ngx_http_module_t, ngx_http_upstream_init_peer_pt, - ngx_http_upstream_init_pt, ngx_int_t, ngx_module_t, ngx_uint_t, NGX_CONF_TAKE1, NGX_HTTP_MODULE, - NGX_HTTP_SRV_CONF, NGX_HTTP_UPS_CONF, NGX_RS_HTTP_SRV_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, + nginx_version, ngx_atoi, ngx_command_t, ngx_conf_log_error, ngx_conf_t, ngx_http_module_t, + ngx_http_upstream_init_peer_pt, ngx_http_upstream_init_pt, ngx_module_t, ngx_str_t, ngx_uint_t, + NGX_CONF_NOARGS, NGX_CONF_TAKE1, NGX_CONF_UNSET, NGX_ERROR, NGX_HTTP_MODULE, NGX_HTTP_UPS_CONF, NGX_LOG_EMERG, + NGX_RS_HTTP_SRV_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, }, - http::{HTTPModule, Merge, MergeConfigError}, + http::{ngx_http_conf_get_module_srv_conf, HTTPModule, Merge, MergeConfigError}, ngx_modules, ngx_null_command, ngx_string, }; -use std::os::raw::{c_char, c_void}; +use std::{ + os::raw::{c_char, c_void}, + slice, +}; -#[derive(Debug, Default)] +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] struct SrvConfig { max: u32, original_init_upstream: ngx_http_upstream_init_pt, @@ -18,7 +23,7 @@ struct SrvConfig { } impl Merge for SrvConfig { - fn merge(&mut self, _prev: &ModuleConfig) -> Result<(), MergeConfigError> { + fn merge(&mut self, _prev: &SrvConfig) -> Result<(), MergeConfigError> { Ok(()) } } @@ -39,7 +44,7 @@ static ngx_http_upstream_custom_ctx: ngx_http_module_t = ngx_http_module_t { static mut ngx_http_upstream_custom_commands: [ngx_command_t; 2] = [ ngx_command_t { name: ngx_string!("custom"), - type_: (NGX_HTTP_UPS_CONF | NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1) as ngx_uint_t, + type_: (NGX_HTTP_UPS_CONF | NGX_CONF_NOARGS | NGX_CONF_TAKE1) as ngx_uint_t, set: Some(ngx_http_upstream_commands_set_custom), conf: NGX_RS_HTTP_SRV_CONF_OFFSET, offset: 0, @@ -83,21 +88,57 @@ pub static mut ngx_http_upstream_custom_module: ngx_module_t = ngx_module_t { }; #[no_mangle] -extern "C" fn ngx_http_upstream_commands_set_custom( +unsafe extern "C" fn ngx_http_upstream_commands_set_custom( cf: *mut ngx_conf_t, - _cmd: *mut ngx_command_t, + cmd: *mut ngx_command_t, conf: *mut c_void, ) -> *mut c_char { + //TODO need a log macros that accepts level and masks: + // NGX_LOG_DEBUG_HTTP, NGX_LOG_DEBUG_EVENT, etc. + + let mut ccf = &mut (*(conf as *mut SrvConfig)); + + if (*(*cf).args).nelts == 2 { + let value: &[ngx_str_t] = slice::from_raw_parts((*(*cf).args).elts as *const ngx_str_t, (*(*cf).args).nelts); + let n = ngx_atoi(value[1].data, value[1].len); + if n == (NGX_ERROR as isize) || n == 0 { + ngx_conf_log_error( + NGX_LOG_EMERG as usize, + cf, + 0, + "invalid value \"%V\" in \"%V\" directive".as_bytes().as_ptr() as *const i8, + value[1], + &(*cmd).name, + ); + return usize::MAX as *mut i8; + } + ccf.max = n as u32; + } + + let uscf = ngx_http_conf_get_module_srv_conf(cf, &ngx_http_upstream_custom_module); + + //ccf.original_init_upstream = if (*upstream_conf).peer.init_upstream.is_null() { + + // NGX_CONF_OK + std::ptr::null_mut() } struct Module; impl HTTPModule for Module { type MainConf = (); - type SrvConf = ModuleConfig; + type SrvConf = SrvConfig; type LocConf = (); unsafe extern "C" fn create_srv_conf(cf: *mut ngx_conf_t) -> *mut c_void { - let conf: SrvConfig; + let mut pool = Pool::from_ngx_pool((*cf).pool); + let conf = pool.alloc_type::(); + if conf.is_null() { + return std::ptr::null_mut(); + } + + (*conf).max = NGX_CONF_UNSET as u32; + + conf as *mut c_void } } From 7687762c3c2b6e3437baa29c66754d8f1348d066 Mon Sep 17 00:00:00 2001 From: Matthew Yacobucci Date: Mon, 26 Jun 2023 11:57:25 -0600 Subject: [PATCH 3/5] WIP: load balancer example --- examples/upstream.rs | 205 ++++++++++++++++++++++++++++++++++++++++--- src/http/request.rs | 9 ++ src/log.rs | 23 +++++ 3 files changed, 225 insertions(+), 12 deletions(-) diff --git a/examples/upstream.rs b/examples/upstream.rs index 0a16f83e..80fb3cea 100644 --- a/examples/upstream.rs +++ b/examples/upstream.rs @@ -1,25 +1,61 @@ +/* + * This example is based on: + * https://github.com/gabihodoroaga/nginx-upstream-module + * + * The NGINX authors are grateful to @gabihodoroaga for their contributions + * to the community at large. + * https://github.com/gabihodoroaga + */ use ngx::{ - core::Pool, + core::{Pool, Status}, ffi::{ - nginx_version, ngx_atoi, ngx_command_t, ngx_conf_log_error, ngx_conf_t, ngx_http_module_t, - ngx_http_upstream_init_peer_pt, ngx_http_upstream_init_pt, ngx_module_t, ngx_str_t, ngx_uint_t, - NGX_CONF_NOARGS, NGX_CONF_TAKE1, NGX_CONF_UNSET, NGX_ERROR, NGX_HTTP_MODULE, NGX_HTTP_UPS_CONF, NGX_LOG_EMERG, - NGX_RS_HTTP_SRV_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, + nginx_version, ngx_atoi, ngx_command_t, ngx_conf_log_error, ngx_conf_t, ngx_connection_t, + ngx_event_free_peer_pt, ngx_event_get_peer_pt, ngx_http_module_t, ngx_http_request_t, + ngx_http_upstream_init_peer_pt, ngx_http_upstream_init_pt, ngx_http_upstream_init_round_robin, + ngx_http_upstream_srv_conf_t, ngx_http_upstream_t, ngx_int_t, ngx_module_t, ngx_peer_connection_t, ngx_str_t, + ngx_uint_t, NGX_CONF_NOARGS, NGX_CONF_TAKE1, NGX_CONF_UNSET, NGX_ERROR, NGX_HTTP_MODULE, NGX_HTTP_UPS_CONF, + NGX_LOG_DEBUG_HTTP, NGX_LOG_EMERG, NGX_RS_HTTP_SRV_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, }, - http::{ngx_http_conf_get_module_srv_conf, HTTPModule, Merge, MergeConfigError}, - ngx_modules, ngx_null_command, ngx_string, + http::{ngx_http_conf_get_module_srv_conf, HTTPModule, Merge, MergeConfigError, Request}, + ngx_log_debug_http, ngx_log_debug_mask, ngx_modules, ngx_null_command, ngx_string, }; use std::{ + mem, os::raw::{c_char, c_void}, slice, }; -#[derive(Clone, Copy, Debug, Default)] +//FIXME move this to src/http/request.rs or an upstream.rs? +#[macro_export] +macro_rules! http_upstream_peer_init { + ( $name: ident, $handler: expr ) => { + #[no_mangle] + unsafe extern "C" fn $name(r: *mut ngx_http_request_t, us: *mut ngx_http_upstream_srv_conf_t) -> ngx_int_t { + let status: Status = $handler(unsafe { &mut Request::from_ngx_http_request(r) }, us); + status.0 + } + }; +} + +#[derive(Clone, Copy, Debug)] #[repr(C)] struct SrvConfig { max: u32, + + //FIXME: should these be traits that a server implements to make the + //functions easier to use? original_init_upstream: ngx_http_upstream_init_pt, - origin_init_peer: ngx_http_upstream_init_peer_pt, + original_init_peer: ngx_http_upstream_init_peer_pt, +} + +impl Default for SrvConfig { + fn default() -> Self { + SrvConfig { + max: u32::MAX, + original_init_upstream: None, + original_init_peer: None, + } + } } impl Merge for SrvConfig { @@ -28,6 +64,30 @@ impl Merge for SrvConfig { } } +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct UpstreamPeerData { + conf: Option<*const SrvConfig>, + upstream: *mut ngx_http_upstream_t, + data: *mut c_void, + client_connection: *mut ngx_connection_t, + original_get_peer: ngx_event_get_peer_pt, + original_free_peer: ngx_event_free_peer_pt, +} + +impl Default for UpstreamPeerData { + fn default() -> Self { + UpstreamPeerData { + conf: None, + upstream: std::ptr::null_mut(), + data: std::ptr::null_mut(), + client_connection: std::ptr::null_mut(), + original_get_peer: None, + original_free_peer: None, + } + } +} + #[no_mangle] static ngx_http_upstream_custom_ctx: ngx_http_module_t = ngx_http_module_t { preconfiguration: Some(Module::preconfiguration), @@ -87,13 +147,127 @@ pub static mut ngx_http_upstream_custom_module: ngx_module_t = ngx_module_t { spare_hook7: 0, }; +http_upstream_peer_init!( + init_custom_peer, + |request: &mut Request, us: *mut ngx_http_upstream_srv_conf_t| { + ngx_log_debug_http!(request, "custom init peer"); + + let mut hcpd = request.pool().alloc_type::(); + if hcpd.is_null() { + return Status::NGX_ERROR; + } + + // FIXME make this a convenience macro? + let hccf: *const SrvConfig = (*us) + .srv_conf + .offset(ngx_http_upstream_custom_module.ctx_index as isize) + as *const SrvConfig; + + // FIXME method or macro? + // Casting from Rust types to C function pointers might be better as macros for + // original_init_peer, free, and get. + // + // Casting to ngx_http_request_t might also benefit. Alternatively a trait which config and + // upstream data use may work as well. + let original_init_peer: unsafe extern "C" fn( + *mut ngx_http_request_t, + *mut ngx_http_upstream_srv_conf_t, + ) -> ngx_int_t = unsafe { mem::transmute((*hccf).original_init_peer) }; + + { + let r: *mut ngx_http_request_t = unsafe { mem::transmute(&request) }; + if original_init_peer(r, us) != Status::NGX_OK.into() { + return Status::NGX_ERROR; + } + } + + let upstream_ptr = request.upstream(); + + (*hcpd).conf = Some(hccf); + (*hcpd).upstream = upstream_ptr; + (*hcpd).data = (*upstream_ptr).peer.data; + (*hcpd).client_connection = request.connection(); + (*hcpd).original_get_peer = (*upstream_ptr).peer.get; + (*hcpd).original_free_peer = (*upstream_ptr).peer.free; + + (*upstream_ptr).peer.data = hcpd as *mut c_void; + (*upstream_ptr).peer.get = Some(ngx_http_upstream_get_custom_peer); + (*upstream_ptr).peer.free = Some(ngx_http_upstream_free_custom_peer); + + Status::NGX_OK + } +); + +#[no_mangle] +unsafe extern "C" fn ngx_http_upstream_get_custom_peer(pc: *mut ngx_peer_connection_t, data: *mut c_void) -> ngx_int_t { + let hcdp: *mut UpstreamPeerData = unsafe { mem::transmute(data) }; + + //FIXME log + + let original_get_peer: unsafe extern "C" fn(*mut ngx_peer_connection_t, *mut c_void) -> ngx_int_t = + unsafe { mem::transmute((*hcdp).original_get_peer) }; + let rc = original_get_peer(pc, (*hcdp).data); + + if rc != Status::NGX_OK.into() { + return rc; + } + Status::NGX_OK.into() +} + +#[no_mangle] +unsafe extern "C" fn ngx_http_upstream_free_custom_peer( + pc: *mut ngx_peer_connection_t, + data: *mut c_void, + state: ngx_uint_t, +) { + let hcdp: *mut UpstreamPeerData = unsafe { mem::transmute(data) }; + + let original_free_peer: unsafe extern "C" fn(*mut ngx_peer_connection_t, data: *mut c_void, ngx_uint_t) = + unsafe { mem::transmute((*hcdp).original_free_peer) }; + + //FIXME log + original_free_peer(pc, (*hcdp).data, state); +} + +#[no_mangle] +unsafe extern "C" fn ngx_http_upstream_init_custom( + cf: *mut ngx_conf_t, + us: *mut ngx_http_upstream_srv_conf_t, +) -> ngx_int_t { + ngx_log_debug_mask!(NGX_LOG_DEBUG_HTTP, (*cf).log, "custom init upstream"); + + // FIXME: this comes from ngx_http_conf_upstream_srv_conf macro which isn't built into bindings + // start creating a macros file? + let hccf: *mut SrvConfig = (*us) + .srv_conf + .offset(ngx_http_upstream_custom_module.ctx_index as isize) as *mut SrvConfig; + + // FIXME: ngx_conf_init_uint_value macro is unavailable + if (*hccf).max == u32::MAX { + (*hccf).max = 100; + } + + //FIXME make a trait and call this as a method on SrvConfig? + let init_upstream_ptr: unsafe extern "C" fn(*mut ngx_conf_t, *mut ngx_http_upstream_srv_conf_t) -> ngx_int_t = + unsafe { mem::transmute((*hccf).original_init_upstream) }; + if init_upstream_ptr(cf, us) != Status::NGX_OK.into() { + return isize::from(Status::NGX_ERROR); + } + + (*hccf).original_init_peer = (*us).peer.init; + //(*us).peer.init = Some(ngx_http_upstream_init_custom_peer); + (*us).peer.init = Some(init_custom_peer); + + isize::from(Status::NGX_OK) +} + #[no_mangle] unsafe extern "C" fn ngx_http_upstream_commands_set_custom( cf: *mut ngx_conf_t, cmd: *mut ngx_command_t, conf: *mut c_void, ) -> *mut c_char { - //TODO need a log macros that accepts level and masks: + //FIXME need a log macros that accepts level and masks: // NGX_LOG_DEBUG_HTTP, NGX_LOG_DEBUG_EVENT, etc. let mut ccf = &mut (*(conf as *mut SrvConfig)); @@ -115,9 +289,16 @@ unsafe extern "C" fn ngx_http_upstream_commands_set_custom( ccf.max = n as u32; } - let uscf = ngx_http_conf_get_module_srv_conf(cf, &ngx_http_upstream_custom_module); + let uscf: *mut ngx_http_upstream_srv_conf_t = + ngx_http_conf_get_module_srv_conf(cf, &ngx_http_upstream_custom_module) as *mut ngx_http_upstream_srv_conf_t; + + ccf.original_init_upstream = if (*uscf).peer.init_upstream.is_some() { + (*uscf).peer.init_upstream + } else { + Some(ngx_http_upstream_init_round_robin) + }; - //ccf.original_init_upstream = if (*upstream_conf).peer.init_upstream.is_null() { + (*uscf).peer.init_upstream = Some(ngx_http_upstream_init_custom); // NGX_CONF_OK std::ptr::null_mut() diff --git a/src/http/request.rs b/src/http/request.rs index 88607fdd..50437c69 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -111,6 +111,15 @@ impl Request { self.0.connection } + /// Pointer to a [`ngx_http_upstream_t`] upstream server object. + /// + /// [`ngx_http_upstream_t`]: is best described in + /// https://nginx.org/en/docs/dev/development_guide.html#http_request + /// https://nginx.org/en/docs/dev/development_guide.html#http_load_balancing + pub fn upstream(&self) -> *mut ngx_http_upstream_t { + self.0.upstream + } + /// Pointer to a [`ngx_log_t`]. /// /// [`ngx_log_t`]: https://nginx.org/en/docs/dev/development_guide.html#logging diff --git a/src/log.rs b/src/log.rs index 37d4ac4a..52719579 100644 --- a/src/log.rs +++ b/src/log.rs @@ -27,3 +27,26 @@ macro_rules! ngx_log_debug_http { $crate::ngx_log_debug!(log, $($arg)*); } } + +/// Log with appropriate debug mask. +/// +/// When the request logger is available `ngx_log_debug_http` can be used for `NGX_LOG_DEBUG_HTTP` masks. +/// This macro is useful when other masks are necessary or when the request logger is not +/// conveniently accessible. +/// +/// See https://nginx.org/en/docs/dev/development_guide.html#logging for details and available +/// masks. +#[macro_export] +macro_rules! ngx_log_debug_mask { + ( $mask:expr, $log:expr, $($arg:tt)* ) => { + let log_level = unsafe { (*$log).log_level }; + if log_level & $mask as usize != 0 { + let level = $mask as $crate::ffi::ngx_uint_t; + let fmt = ::std::ffi::CString::new("%s").unwrap(); + let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); + unsafe { + $crate::ffi::ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); + } + } + } +} From 045693f973b1fe31e12c702fa4f35d428c579032 Mon Sep 17 00:00:00 2001 From: Matthew Yacobucci Date: Thu, 29 Jun 2023 08:54:49 -0600 Subject: [PATCH 4/5] comments, upstream file, example, and README --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/README.md | 68 ++++++++++++++- examples/upstream.rs | 194 +++++++++++++++++++++++++------------------ src/http/conf.rs | 32 +++++++ src/http/mod.rs | 2 + src/http/request.rs | 31 +++++-- src/http/upstream.rs | 15 ++++ 8 files changed, 253 insertions(+), 93 deletions(-) create mode 100644 src/http/upstream.rs diff --git a/Cargo.lock b/Cargo.lock index 25ee3863..a69aae9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,7 +456,7 @@ dependencies = [ [[package]] name = "ngx" -version = "0.3.0-beta" +version = "0.4.0-beta" dependencies = [ "nginx-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 6eac1e04..6fed388b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ [package] name = "ngx" -version = "0.3.0-beta" +version = "0.4.0-beta" edition = "2021" autoexamples = false categories = ["api-bindings", "network-programming"] diff --git a/examples/README.md b/examples/README.md index aabf24c9..cfc5d17a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -98,7 +98,7 @@ The following embedded variables are provided: 1. Clone the git repository. ``` - https://github.com/nginxinc/ngx-rust + git clone git@github.com:nginxinc/ngx-rust.git ``` 2. Compile the module from the cloned repo. @@ -150,3 +150,69 @@ The following embedded variables are provided: ### Caveats This module only supports IPv4. + +## UPSTREAM - Example upstream / load balancing module for HTTP + +This module simply proxies requests through a custom load balancer to the previously configured balancer. This is for demonstration purposes only. As a module writer, you can start with this structure and adjust to your needs, then implement the proper algorithm for your usage. + +The module replaces the `peer` callback functions with its own, logs, and then calls through to the originally saved `peer` functions. This may look confusing at first, but rest assured, it's intentionally not implementing an algorithm of its own. + +### Attributions + +This module was converted from https://github.com/gabihodoroaga/nginx-upstream-module. + +### Example Configuration +#### HTTP + +```nginx configuration +load_module "modules/upstream.so" + +http { + upstream backend { + server localhost:8081; + + custom 32; + } + + server { + listen 8080; + server_name _; + + location / { + proxy_pass http://backend; + } + } +} +``` + +### Usage + +1. Clone the git repository. + ``` + git clone git@github.com:nginxinc/ngx-rust.git + ``` + +2. Compile the module from the cloned repo. + ``` + cd ${CLONED_DIRECTORY}/ngx-rust + cargo buile --package=examples --example=upstream + ``` + +3. Copy the shared object to the modules directory, /etc/nginx/modules. + ``` + cp ./target/debug/examples/libupstream.so /etc/nginx/modules + ``` + +4. Add the `load_module` directive to your configuration. + ``` + load_module "modules/libupstream.so"; + ``` + +5. Add the example `server` and `upstream` block from the example above. + +6. Reload NGINX. + ``` + nginx -t && nginx -s reload + ``` + +7. Test with `curl`. Traffic should pass to your listener on port 8081 (this could be another NGINX server for example). With debug logging enabled you should notice the "custom" log messages (see the source code for log examples). diff --git a/examples/upstream.rs b/examples/upstream.rs index 80fb3cea..d627cb6b 100644 --- a/examples/upstream.rs +++ b/examples/upstream.rs @@ -16,8 +16,12 @@ use ngx::{ ngx_uint_t, NGX_CONF_NOARGS, NGX_CONF_TAKE1, NGX_CONF_UNSET, NGX_ERROR, NGX_HTTP_MODULE, NGX_HTTP_UPS_CONF, NGX_LOG_DEBUG_HTTP, NGX_LOG_EMERG, NGX_RS_HTTP_SRV_CONF_OFFSET, NGX_RS_MODULE_SIGNATURE, }, - http::{ngx_http_conf_get_module_srv_conf, HTTPModule, Merge, MergeConfigError, Request}, - ngx_log_debug_http, ngx_log_debug_mask, ngx_modules, ngx_null_command, ngx_string, + http::{ + ngx_http_conf_get_module_srv_conf, ngx_http_conf_upstream_srv_conf_immutable, + ngx_http_conf_upstream_srv_conf_mutable, HTTPModule, Merge, MergeConfigError, Request, + }, + http_upstream_peer_init, ngx_log_debug, ngx_log_debug_http, ngx_log_debug_mask, ngx_modules, ngx_null_command, + ngx_string, }; use std::{ mem, @@ -25,25 +29,11 @@ use std::{ slice, }; -//FIXME move this to src/http/request.rs or an upstream.rs? -#[macro_export] -macro_rules! http_upstream_peer_init { - ( $name: ident, $handler: expr ) => { - #[no_mangle] - unsafe extern "C" fn $name(r: *mut ngx_http_request_t, us: *mut ngx_http_upstream_srv_conf_t) -> ngx_int_t { - let status: Status = $handler(unsafe { &mut Request::from_ngx_http_request(r) }, us); - status.0 - } - }; -} - #[derive(Clone, Copy, Debug)] #[repr(C)] struct SrvConfig { max: u32, - //FIXME: should these be traits that a server implements to make the - //functions easier to use? original_init_upstream: ngx_http_upstream_init_pt, original_init_peer: ngx_http_upstream_init_peer_pt, } @@ -68,22 +58,22 @@ impl Merge for SrvConfig { #[repr(C)] struct UpstreamPeerData { conf: Option<*const SrvConfig>, - upstream: *mut ngx_http_upstream_t, - data: *mut c_void, - client_connection: *mut ngx_connection_t, + upstream: Option<*mut ngx_http_upstream_t>, + client_connection: Option<*mut ngx_connection_t>, original_get_peer: ngx_event_get_peer_pt, original_free_peer: ngx_event_free_peer_pt, + data: *mut c_void, } impl Default for UpstreamPeerData { fn default() -> Self { UpstreamPeerData { conf: None, - upstream: std::ptr::null_mut(), - data: std::ptr::null_mut(), - client_connection: std::ptr::null_mut(), + upstream: None, + client_connection: None, original_get_peer: None, original_free_peer: None, + data: std::ptr::null_mut(), } } } @@ -147,46 +137,41 @@ pub static mut ngx_http_upstream_custom_module: ngx_module_t = ngx_module_t { spare_hook7: 0, }; +// http_upstream_init_custom_peer +// The module's custom peer.init callback. On HTTP request the peer upstream get and free callbacks +// are saved into peer data and replaced with this module's custom callbacks. http_upstream_peer_init!( - init_custom_peer, + http_upstream_init_custom_peer, |request: &mut Request, us: *mut ngx_http_upstream_srv_conf_t| { - ngx_log_debug_http!(request, "custom init peer"); + ngx_log_debug_http!(request, "CUSTOM UPSTREAM request peer init"); let mut hcpd = request.pool().alloc_type::(); if hcpd.is_null() { return Status::NGX_ERROR; } - // FIXME make this a convenience macro? - let hccf: *const SrvConfig = (*us) - .srv_conf - .offset(ngx_http_upstream_custom_module.ctx_index as isize) - as *const SrvConfig; - - // FIXME method or macro? - // Casting from Rust types to C function pointers might be better as macros for - // original_init_peer, free, and get. - // - // Casting to ngx_http_request_t might also benefit. Alternatively a trait which config and - // upstream data use may work as well. - let original_init_peer: unsafe extern "C" fn( - *mut ngx_http_request_t, - *mut ngx_http_upstream_srv_conf_t, - ) -> ngx_int_t = unsafe { mem::transmute((*hccf).original_init_peer) }; - - { - let r: *mut ngx_http_request_t = unsafe { mem::transmute(&request) }; - if original_init_peer(r, us) != Status::NGX_OK.into() { - return Status::NGX_ERROR; - } + let maybe_conf: Option<*const SrvConfig> = + ngx_http_conf_upstream_srv_conf_immutable(us, &ngx_http_upstream_custom_module); + if maybe_conf.is_none() { + return Status::NGX_ERROR; } - let upstream_ptr = request.upstream(); + let hccf = maybe_conf.unwrap(); + let original_init_peer = (*hccf).original_init_peer.unwrap(); + if original_init_peer(request.into(), us) != Status::NGX_OK.into() { + return Status::NGX_ERROR; + } + + let maybe_upstream = request.upstream(); + if maybe_upstream.is_none() { + return Status::NGX_ERROR; + } + let upstream_ptr = maybe_upstream.unwrap(); (*hcpd).conf = Some(hccf); - (*hcpd).upstream = upstream_ptr; + (*hcpd).upstream = maybe_upstream; (*hcpd).data = (*upstream_ptr).peer.data; - (*hcpd).client_connection = request.connection(); + (*hcpd).client_connection = Some(request.connection()); (*hcpd).original_get_peer = (*upstream_ptr).peer.get; (*hcpd).original_free_peer = (*upstream_ptr).peer.free; @@ -194,81 +179,115 @@ http_upstream_peer_init!( (*upstream_ptr).peer.get = Some(ngx_http_upstream_get_custom_peer); (*upstream_ptr).peer.free = Some(ngx_http_upstream_free_custom_peer); + ngx_log_debug_http!(request, "CUSTOM UPSTREAM end request peer init"); Status::NGX_OK } ); +// ngx_http_usptream_get_custom_peer +// For demonstration purposes, use the original get callback, but log this callback proxies through +// to the original. #[no_mangle] unsafe extern "C" fn ngx_http_upstream_get_custom_peer(pc: *mut ngx_peer_connection_t, data: *mut c_void) -> ngx_int_t { - let hcdp: *mut UpstreamPeerData = unsafe { mem::transmute(data) }; + let hcpd: *mut UpstreamPeerData = unsafe { mem::transmute(data) }; - //FIXME log + ngx_log_debug_mask!( + NGX_LOG_DEBUG_HTTP, + (*pc).log, + "CUSTOM UPSTREAM get peer, try: {}, conn: {:p}", + (*pc).tries, + (*hcpd).client_connection.unwrap(), + ); - let original_get_peer: unsafe extern "C" fn(*mut ngx_peer_connection_t, *mut c_void) -> ngx_int_t = - unsafe { mem::transmute((*hcdp).original_get_peer) }; - let rc = original_get_peer(pc, (*hcdp).data); + let original_get_peer = (*hcpd).original_get_peer.unwrap(); + let rc = original_get_peer(pc, (*hcpd).data); if rc != Status::NGX_OK.into() { return rc; } + + ngx_log_debug!((*pc).log, "CUSTOM UPSTREAM end get peer"); Status::NGX_OK.into() } +// ngx_http_upstream_free_custom_peer +// For demonstration purposes, use the original free callback, but log this callback proxies +// through to the original. #[no_mangle] unsafe extern "C" fn ngx_http_upstream_free_custom_peer( pc: *mut ngx_peer_connection_t, data: *mut c_void, state: ngx_uint_t, ) { - let hcdp: *mut UpstreamPeerData = unsafe { mem::transmute(data) }; + ngx_log_debug_mask!(NGX_LOG_DEBUG_HTTP, (*pc).log, "CUSTOM UPSTREAM free peer"); + + let hcpd: *mut UpstreamPeerData = unsafe { mem::transmute(data) }; - let original_free_peer: unsafe extern "C" fn(*mut ngx_peer_connection_t, data: *mut c_void, ngx_uint_t) = - unsafe { mem::transmute((*hcdp).original_free_peer) }; + let original_free_peer = (*hcpd).original_free_peer.unwrap(); - //FIXME log - original_free_peer(pc, (*hcdp).data, state); + original_free_peer(pc, (*hcpd).data, state); + + ngx_log_debug!((*pc).log, "CUSTOM UPSTREAM end free peer"); } +// ngx_http_upstream_init_custom +// The module's custom `peer.init_upstream` callback. +// The original callback is saved in our SrvConfig data and reset to this module's `peer.init`. #[no_mangle] unsafe extern "C" fn ngx_http_upstream_init_custom( cf: *mut ngx_conf_t, us: *mut ngx_http_upstream_srv_conf_t, ) -> ngx_int_t { - ngx_log_debug_mask!(NGX_LOG_DEBUG_HTTP, (*cf).log, "custom init upstream"); - - // FIXME: this comes from ngx_http_conf_upstream_srv_conf macro which isn't built into bindings - // start creating a macros file? - let hccf: *mut SrvConfig = (*us) - .srv_conf - .offset(ngx_http_upstream_custom_module.ctx_index as isize) as *mut SrvConfig; + ngx_log_debug_mask!(NGX_LOG_DEBUG_HTTP, (*cf).log, "CUSTOM UPSTREAM peer init_upstream"); + + let maybe_conf: Option<*mut SrvConfig> = + ngx_http_conf_upstream_srv_conf_mutable(us, &ngx_http_upstream_custom_module); + if let None = maybe_conf { + ngx_conf_log_error( + NGX_LOG_EMERG as usize, + cf, + 0, + "CUSTOM UPSTREAM no upstream srv_conf".as_bytes().as_ptr() as *const i8, + ); + return isize::from(Status::NGX_ERROR); + } else { + let hccf = maybe_conf.unwrap(); + // NOTE: ngx_conf_init_uint_value macro is unavailable + if (*hccf).max == u32::MAX { + (*hccf).max = 100; + } - // FIXME: ngx_conf_init_uint_value macro is unavailable - if (*hccf).max == u32::MAX { - (*hccf).max = 100; - } + let init_upstream_ptr = (*hccf).original_init_upstream.unwrap(); + if init_upstream_ptr(cf, us) != Status::NGX_OK.into() { + ngx_conf_log_error( + NGX_LOG_EMERG as usize, + cf, + 0, + "CUSTOM UPSTREAM failed calling init_upstream".as_bytes().as_ptr() as *const i8, + ); + return isize::from(Status::NGX_ERROR); + } - //FIXME make a trait and call this as a method on SrvConfig? - let init_upstream_ptr: unsafe extern "C" fn(*mut ngx_conf_t, *mut ngx_http_upstream_srv_conf_t) -> ngx_int_t = - unsafe { mem::transmute((*hccf).original_init_upstream) }; - if init_upstream_ptr(cf, us) != Status::NGX_OK.into() { - return isize::from(Status::NGX_ERROR); + (*hccf).original_init_peer = (*us).peer.init; + (*us).peer.init = Some(http_upstream_init_custom_peer); } - (*hccf).original_init_peer = (*us).peer.init; - //(*us).peer.init = Some(ngx_http_upstream_init_custom_peer); - (*us).peer.init = Some(init_custom_peer); - + ngx_log_debug!((*cf).log, "CUSTOM UPSTREAM end peer init_upstream"); isize::from(Status::NGX_OK) } +// ngx_http_upstream_commands_set_custom +// Entry point for the module, if this command is set our custom upstreams take effect. +// The original upstream initializer function is saved and replaced with this module's initializer. #[no_mangle] unsafe extern "C" fn ngx_http_upstream_commands_set_custom( cf: *mut ngx_conf_t, cmd: *mut ngx_command_t, conf: *mut c_void, ) -> *mut c_char { - //FIXME need a log macros that accepts level and masks: - // NGX_LOG_DEBUG_HTTP, NGX_LOG_DEBUG_EVENT, etc. + ngx_log_debug_mask!(NGX_LOG_DEBUG_HTTP, (*cf).log, "CUSTOM UPSTREAM module init"); + + ngx_log_debug!((*cf).log, "CUSTOM DEBUG !MASK LOG"); let mut ccf = &mut (*(conf as *mut SrvConfig)); @@ -300,10 +319,14 @@ unsafe extern "C" fn ngx_http_upstream_commands_set_custom( (*uscf).peer.init_upstream = Some(ngx_http_upstream_init_custom); + ngx_log_debug!((*cf).log, "CUSTOM UPSTREAM end module init"); // NGX_CONF_OK std::ptr::null_mut() } +// The upstream module. +// Only server blocks are supported to trigger the module command; therefore, the only callback +// implemented is our `create_srv_conf` method. struct Module; impl HTTPModule for Module { @@ -315,11 +338,20 @@ impl HTTPModule for Module { let mut pool = Pool::from_ngx_pool((*cf).pool); let conf = pool.alloc_type::(); if conf.is_null() { + ngx_conf_log_error( + NGX_LOG_EMERG as usize, + cf, + 0, + "CUSTOM UPSTREAM could not allocate memory for config" + .as_bytes() + .as_ptr() as *const i8, + ); return std::ptr::null_mut(); } (*conf).max = NGX_CONF_UNSET as u32; + ngx_log_debug!((*cf).log, "CUSTOM UPSTREAM end create_srv_conf"); conf as *mut c_void } } diff --git a/src/http/conf.rs b/src/http/conf.rs index 4761bb0f..f26cffb4 100644 --- a/src/http/conf.rs +++ b/src/http/conf.rs @@ -31,3 +31,35 @@ pub unsafe fn ngx_http_conf_get_module_loc_conf( let http_conf_ctx = (*cf).ctx as *mut ngx_http_conf_ctx_t; *(*http_conf_ctx).loc_conf.add(module.ctx_index) as *mut ngx_http_core_loc_conf_t } + +/// # Safety +/// +/// The caller has provided a value `ngx_http_upstream_srv_conf_t. If the `us` argument is null, a +/// None Option is returned; however, if the `us` internal fields are invalid or the module index +/// is out of bounds failures may still occur. +pub unsafe fn ngx_http_conf_upstream_srv_conf_immutable( + us: *const ngx_http_upstream_srv_conf_t, + module: &ngx_module_t, +) -> Option<*const T> { + if us.is_null() { + return None; + } + let cf: *const T = (*us).srv_conf.offset(module.ctx_index as isize) as *const T; + Some(cf) +} + +/// # Safety +/// +/// The caller has provided a value `ngx_http_upstream_srv_conf_t. If the `us` argument is null, a +/// None Option is returned; however, if the `us` internal fields are invalid or the module index +/// is out of bounds failures may still occur. +pub unsafe fn ngx_http_conf_upstream_srv_conf_mutable( + us: *const ngx_http_upstream_srv_conf_t, + module: &ngx_module_t, +) -> Option<*mut T> { + if us.is_null() { + return None; + } + let cf: *mut T = (*us).srv_conf.offset(module.ctx_index as isize) as *mut T; + Some(cf) +} diff --git a/src/http/mod.rs b/src/http/mod.rs index 230ce0b5..024ad1f6 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -2,8 +2,10 @@ mod conf; mod module; mod request; mod status; +mod upstream; pub use conf::*; pub use module::*; pub use request::*; pub use status::*; +pub use upstream::*; diff --git a/src/http/request.rs b/src/http/request.rs index 50437c69..c86f7c49 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -79,6 +79,17 @@ macro_rules! http_variable_get { #[repr(transparent)] pub struct Request(ngx_http_request_t); +impl<'a> From<&'a Request> for *const ngx_http_request_t { + fn from(request: &'a Request) -> Self { + &request.0 as *const _ + } +} +impl<'a> From<&'a mut Request> for *mut ngx_http_request_t { + fn from(request: &'a mut Request) -> Self { + &request.0 as *const _ as *mut _ + } +} + impl Request { /// Create a [`Request`] from an [`ngx_http_request_t`]. /// @@ -104,20 +115,22 @@ impl Request { unsafe { Pool::from_ngx_pool(self.0.pool) } } - /// Pointer to a [`ngx_connection_t`] client connection object. + /// Returns the result as an `Option` if it exists, otherwise `None`. /// - /// [`ngx_connection_t`]: https://nginx.org/en/docs/dev/development_guide.html#connection - pub fn connection(&self) -> *mut ngx_connection_t { - self.0.connection - } - - /// Pointer to a [`ngx_http_upstream_t`] upstream server object. + /// The option wraps a pointer to a [`ngx_http_upstream_t`] upstream server object. /// /// [`ngx_http_upstream_t`]: is best described in /// https://nginx.org/en/docs/dev/development_guide.html#http_request /// https://nginx.org/en/docs/dev/development_guide.html#http_load_balancing - pub fn upstream(&self) -> *mut ngx_http_upstream_t { - self.0.upstream + pub fn upstream(&self) -> Option<*mut ngx_http_upstream_t> { + Some(self.0.upstream) + } + + /// Pointer to a [`ngx_connection_t`] client connection object. + /// + /// [`ngx_connection_t`]: https://nginx.org/en/docs/dev/development_guide.html#connection + pub fn connection(&self) -> *mut ngx_connection_t { + self.0.connection } /// Pointer to a [`ngx_log_t`]. diff --git a/src/http/upstream.rs b/src/http/upstream.rs new file mode 100644 index 00000000..186ea735 --- /dev/null +++ b/src/http/upstream.rs @@ -0,0 +1,15 @@ +/// Define a static upstream peer initializer +/// +/// Initializes the upstream 'get', 'free', and 'session' callbacks and gives the module writer an +/// opportunity to set custom data. +/// Load Balancing: +#[macro_export] +macro_rules! http_upstream_peer_init { + ( $name: ident, $handler: expr ) => { + #[no_mangle] + unsafe extern "C" fn $name(r: *mut ngx_http_request_t, us: *mut ngx_http_upstream_srv_conf_t) -> ngx_int_t { + let status: Status = $handler(unsafe { &mut Request::from_ngx_http_request(r) }, us); + status.0 + } + }; +} From 33995029c3304efe19ca6e2b69b660669cf3b1aa Mon Sep 17 00:00:00 2001 From: Matthew Yacobucci Date: Wed, 5 Jul 2023 09:09:12 -0600 Subject: [PATCH 5/5] code review: minor README corrections --- examples/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index cfc5d17a..e8a1dfbc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,6 +8,11 @@ - [Embedded Variables](#embedded-variables) - [Usage](#usage) - [Caveats](#caveats) + - [UPSTREAM - Example upstream module for HTTP](#upstream---example-upstream-module-for-http) + - [Attributions](#attributions) + - [Example Configuration](#example-configuration) + - [HTTP](#http) + - [Usage](#usage) # Examples @@ -151,7 +156,7 @@ The following embedded variables are provided: This module only supports IPv4. -## UPSTREAM - Example upstream / load balancing module for HTTP +## UPSTREAM - Example upstream module for HTTP This module simply proxies requests through a custom load balancer to the previously configured balancer. This is for demonstration purposes only. As a module writer, you can start with this structure and adjust to your needs, then implement the proper algorithm for your usage. @@ -195,7 +200,7 @@ http { 2. Compile the module from the cloned repo. ``` cd ${CLONED_DIRECTORY}/ngx-rust - cargo buile --package=examples --example=upstream + cargo build --package=examples --example=upstream ``` 3. Copy the shared object to the modules directory, /etc/nginx/modules.