Skip to content

Commit 08fd958

Browse files
pchickeyensh63
andcommitted
add ngx::core::resolver, which provides async name resolution
Added behind its own feature flag, because it incurs two extra deps (thiserror and futures-channel) on top of the async feature set. This PR is an upstreaming of nginx-acme::net::resolver, see: https://github.com/nginx/nginx-acme/blob/main/src/net/resolver.rs Co-authored-by: Evgeny Shirykalov <e.shirykalov@f5.com>
1 parent cbf33dd commit 08fd958

File tree

4 files changed

+266
-0
lines changed

4 files changed

+266
-0
lines changed

Cargo.lock

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ async-task = { version = "4.7.1", optional = true }
4242
lock_api = "0.4.13"
4343
nginx-sys = { path = "nginx-sys", version = "0.5.0-beta"}
4444
pin-project-lite = { version = "0.2.16", optional = true }
45+
futures-channel = { version = "0.3.31", optional = true }
46+
thiserror = { version = "2", default-features = false, optional = true }
4547

4648
[features]
4749
default = ["std"]
@@ -53,6 +55,12 @@ async = [
5355
]
5456
# Provides APIs that require allocations via the `alloc` crate.
5557
alloc = ["allocator-api2/alloc"]
58+
# Provides async APIs to the NGINX resolver
59+
resolver = [
60+
"async",
61+
"dep:futures-channel",
62+
"dep:thiserror",
63+
]
5664
# Enables serialization support for some of the provided and re-exported types.
5765
serde = [
5866
"allocator-api2/serde",

src/core/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
mod buffer;
22
mod pool;
3+
#[cfg(feature = "resolver")]
4+
pub mod resolver;
35
pub mod slab;
46
mod status;
57
mod string;

src/core/resolver.rs

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright (c) F5, Inc.
2+
//
3+
// This source code is licensed under the Apache License, Version 2.0 license found in the
4+
// LICENSE file in the root directory of this source tree.
5+
6+
//! Wrapper for the nginx resolver.
7+
//!
8+
//! See <https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver>.
9+
10+
use core::ffi::c_void;
11+
use core::ptr::NonNull;
12+
use std::boxed::Box;
13+
use std::string::{String, ToString};
14+
15+
use crate::{
16+
collections::Vec,
17+
core::Pool,
18+
ffi::{
19+
ngx_addr_t, ngx_msec_t, ngx_resolve_name, ngx_resolve_start, ngx_resolver_ctx_t,
20+
ngx_resolver_t, ngx_str_t,
21+
},
22+
};
23+
use futures_channel::oneshot::{channel, Sender};
24+
use nginx_sys::{
25+
NGX_RESOLVE_FORMERR, NGX_RESOLVE_NOTIMP, NGX_RESOLVE_NXDOMAIN, NGX_RESOLVE_REFUSED,
26+
NGX_RESOLVE_SERVFAIL, NGX_RESOLVE_TIMEDOUT,
27+
};
28+
29+
/// Error type for all uses of `Resolver`.
30+
#[derive(thiserror::Error, Debug)]
31+
pub enum Error {
32+
/// No resolver configured
33+
#[error("No resolver configured")]
34+
NoResolver,
35+
/// Resolver error, with context of name being resolved
36+
#[error("{0}: resolving `{1}`")]
37+
Resolver(ResolverError, String),
38+
/// Allocation failed
39+
#[error("Allocation failed")]
40+
AllocationFailed,
41+
/// Unexpected error
42+
#[error("Unexpected error: {0}")]
43+
Unexpected(String),
44+
}
45+
46+
/// These cases directly reflect the NGX_RESOLVE_ error codes,
47+
/// plus a timeout, and a case for an unknown error where a known
48+
/// NGX_RESOLVE_ should be.
49+
#[derive(thiserror::Error, Debug)]
50+
pub enum ResolverError {
51+
/// Format error (NGX_RESOLVE_FORMERR)
52+
#[error("Format error")]
53+
Format,
54+
/// Server failure (NGX_RESOLVE_SERVFAIL)
55+
#[error("Server failure")]
56+
Server,
57+
/// Host not found (NGX_RESOLVE_NXDOMAIN)
58+
#[error("Host not found")]
59+
HostNotFound,
60+
/// Unimplemented (NGX_RESOLVE_NOTIMP)
61+
#[error("Unimplemented")]
62+
Unimplemented,
63+
/// Operatio refused (NGX_RESOLVE_REFUSED)
64+
#[error("Operation refused")]
65+
Refused,
66+
/// Timed out (NGX_RESOLVE_TIMEDOUT)
67+
#[error("Timed Out")]
68+
TimedOut,
69+
/// Unknown NGX_RESOLVE error
70+
#[error("Unknown NGX_RESOLVE error {0}")]
71+
Unknown(isize),
72+
}
73+
/// Convert from the NGX_RESOLVE_ error codes. Fails if code was success.
74+
impl TryFrom<isize> for ResolverError {
75+
type Error = ();
76+
fn try_from(code: isize) -> Result<ResolverError, Self::Error> {
77+
match code as u32 {
78+
0 => Err(()),
79+
NGX_RESOLVE_FORMERR => Ok(ResolverError::Format),
80+
NGX_RESOLVE_SERVFAIL => Ok(ResolverError::Server),
81+
NGX_RESOLVE_NXDOMAIN => Ok(ResolverError::HostNotFound),
82+
NGX_RESOLVE_NOTIMP => Ok(ResolverError::Unimplemented),
83+
NGX_RESOLVE_REFUSED => Ok(ResolverError::Refused),
84+
NGX_RESOLVE_TIMEDOUT => Ok(ResolverError::TimedOut),
85+
_ => Ok(ResolverError::Unknown(code)),
86+
}
87+
}
88+
}
89+
90+
type Res = Result<Vec<ngx_addr_t>, Error>;
91+
92+
struct ResCtx<'a> {
93+
ctx: Option<*mut ngx_resolver_ctx_t>,
94+
sender: Option<Sender<Res>>,
95+
pool: &'a mut Pool,
96+
}
97+
98+
impl Drop for ResCtx<'_> {
99+
fn drop(&mut self) {
100+
if let Some(ctx) = self.ctx.take() {
101+
unsafe {
102+
nginx_sys::ngx_resolve_name_done(ctx);
103+
}
104+
}
105+
}
106+
}
107+
108+
fn copy_resolved_addr(
109+
addr: *mut nginx_sys::ngx_resolver_addr_t,
110+
pool: &mut Pool,
111+
) -> Result<ngx_addr_t, Error> {
112+
let addr = NonNull::new(addr).ok_or(Error::Unexpected(
113+
"null ngx_resolver_addr_t in ngx_resolver_ctx_t.addrs".to_string(),
114+
))?;
115+
let addr = unsafe { addr.as_ref() };
116+
117+
let sockaddr = pool.alloc(addr.socklen as usize) as *mut nginx_sys::sockaddr;
118+
if sockaddr.is_null() {
119+
Err(Error::AllocationFailed)?;
120+
}
121+
unsafe {
122+
addr.sockaddr
123+
.cast::<u8>()
124+
.copy_to_nonoverlapping(sockaddr.cast(), addr.socklen as usize)
125+
};
126+
127+
let name = unsafe { ngx_str_t::from_bytes(pool.as_mut(), addr.name.as_bytes()) }
128+
.ok_or(Error::AllocationFailed)?;
129+
130+
Ok(ngx_addr_t {
131+
sockaddr,
132+
socklen: addr.socklen,
133+
name,
134+
})
135+
}
136+
137+
/// A wrapper for an ngx_resolver_t which provides an async Rust API
138+
pub struct Resolver {
139+
resolver: NonNull<ngx_resolver_t>,
140+
timeout: ngx_msec_t,
141+
}
142+
143+
impl Resolver {
144+
/// Create a new `Resolver` from existing pointer to `ngx_resolver_t` and
145+
/// timeout.
146+
pub fn from_resolver(resolver: NonNull<ngx_resolver_t>, timeout: ngx_msec_t) -> Self {
147+
Self { resolver, timeout }
148+
}
149+
150+
/// Resolve a name into a set of addresses.
151+
///
152+
/// The set of addresses may not be deterministic, because the
153+
/// implementation of the resolver may race multiple DNS requests.
154+
pub async fn resolve(&self, name: &ngx_str_t, pool: &mut Pool) -> Res {
155+
unsafe {
156+
let ctx: *mut ngx_resolver_ctx_t =
157+
ngx_resolve_start(self.resolver.as_ptr(), core::ptr::null_mut());
158+
if ctx.is_null() {
159+
Err(Error::AllocationFailed)?
160+
}
161+
if ctx as isize == -1 {
162+
Err(Error::NoResolver)?
163+
}
164+
165+
let (sender, receiver) = channel::<Res>();
166+
let rctx = Box::new(ResCtx {
167+
ctx: Some(ctx),
168+
sender: Some(sender),
169+
pool,
170+
});
171+
172+
(*ctx).name = *name;
173+
(*ctx).timeout = self.timeout;
174+
(*ctx).set_cancelable(1);
175+
(*ctx).handler = Some(Self::resolve_handler);
176+
(*ctx).data = Box::into_raw(rctx) as *mut c_void;
177+
178+
let ret = ngx_resolve_name(ctx);
179+
if ret != 0 {
180+
Err(Error::Resolver(
181+
ResolverError::try_from(ret).expect("nonzero, checked above"),
182+
name.to_string(),
183+
))?;
184+
}
185+
186+
receiver
187+
.await
188+
.map_err(|_| Error::Resolver(ResolverError::TimedOut, name.to_string()))?
189+
}
190+
}
191+
192+
unsafe extern "C" fn resolve_handler(ctx: *mut ngx_resolver_ctx_t) {
193+
let mut rctx = *Box::from_raw((*ctx).data as *mut ResCtx);
194+
rctx.ctx.take();
195+
if let Some(sender) = rctx.sender.take() {
196+
let _ = sender.send(Self::resolve_result(ctx, rctx.pool));
197+
}
198+
nginx_sys::ngx_resolve_name_done(ctx);
199+
}
200+
201+
fn resolve_result(ctx: *mut ngx_resolver_ctx_t, pool: &mut Pool) -> Res {
202+
let ctx = unsafe { ctx.as_ref().unwrap() };
203+
let s = ctx.state;
204+
if s != 0 {
205+
Err(Error::Resolver(
206+
ResolverError::try_from(s).expect("nonzero, checked above"),
207+
ctx.name.to_string(),
208+
))?;
209+
}
210+
if ctx.addrs.is_null() {
211+
Err(Error::AllocationFailed)?;
212+
}
213+
let mut out = Vec::new();
214+
for i in 0..ctx.naddrs {
215+
out.push(copy_resolved_addr(unsafe { ctx.addrs.add(i) }, pool)?);
216+
}
217+
Ok(out)
218+
}
219+
}

0 commit comments

Comments
 (0)