Skip to content

Commit 4becb06

Browse files
pchickeyensh63
andcommitted
feat: add async wrapper for resolver
Added behind the async feature, and adds the futures-channel dep to to the deps enabled by async. 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 <[email protected]>
1 parent 1f4b572 commit 4becb06

File tree

4 files changed

+257
-0
lines changed

4 files changed

+257
-0
lines changed

Cargo.lock

Lines changed: 16 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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ 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 }
4546

4647
[features]
4748
default = ["std"]
@@ -50,6 +51,7 @@ async = [
5051
"alloc",
5152
"dep:async-task",
5253
"dep:pin-project-lite",
54+
"dep:futures-channel",
5355
]
5456
# Provides APIs that require allocations via the `alloc` crate.
5557
alloc = ["allocator-api2/alloc"]

src/async_/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22
pub use self::sleep::{sleep, Sleep};
33
pub use self::spawn::{spawn, Task};
44

5+
pub mod resolver;
6+
57
mod sleep;
68
mod spawn;

src/async_/resolver.rs

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

0 commit comments

Comments
 (0)