Skip to content

Commit db76540

Browse files
ensh63pchickey
authored andcommitted
Net: async name resolution via ngx_resolver_t.
Co-authored-by: Pat Hickey <[email protected]>
1 parent c5f6e26 commit db76540

File tree

6 files changed

+244
-8
lines changed

6 files changed

+244
-8
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ crate-type = ["cdylib"]
1212
[dependencies]
1313
base64 = "0.22.1"
1414
constcat = "0.6.1"
15+
futures-channel = "0.3.31"
1516
http = "1.3.1"
1617
http-body = "1.0.1"
1718
hyper = { version = "1.6.0", features = ["client", "http1"] }

src/net.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod connection;
22
pub mod http;
33
pub mod peer_conn;
4+
pub mod resolver;

src/net/http.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ use http::uri::Scheme;
77
use http::{Request, Response};
88
use http_body::Body;
99
use hyper::body;
10-
use nginx_sys::{ngx_log_t, NGX_LOG_WARN};
10+
use nginx_sys::{ngx_log_t, ngx_resolver_t, NGX_LOG_WARN};
1111
use ngx::allocator::Box;
1212
use ngx::async_::spawn;
1313
use ngx::ngx_log_error;
1414
use thiserror::Error;
1515

1616
use super::peer_conn::PeerConnection;
17+
use super::resolver::Resolver;
1718
use crate::conf::ssl::NgxSsl;
1819

1920
const NGINX_VER: &str = match nginx_sys::NGINX_VER.to_str() {
@@ -43,6 +44,7 @@ pub trait HttpClient {
4344

4445
pub struct NgxHttpClient<'a> {
4546
log: NonNull<ngx_log_t>,
47+
resolver: Resolver,
4648
ssl: &'a NgxSsl,
4749
ssl_verify: bool,
4850
}
@@ -51,16 +53,34 @@ pub struct NgxHttpClient<'a> {
5153
pub enum HttpClientError {
5254
#[error("request error: {0}")]
5355
Http(#[from] hyper::Error),
56+
#[error("name resolution error: {0}")]
57+
Resolver(super::resolver::Error),
5458
#[error("connection error: {0}")]
55-
Io(#[from] io::Error),
59+
Io(io::Error),
5660
#[error("invalid uri: {0}")]
5761
Uri(&'static str),
5862
}
5963

64+
impl From<io::Error> for HttpClientError {
65+
fn from(err: io::Error) -> Self {
66+
match err.downcast::<super::resolver::Error>() {
67+
Ok(x) => Self::Resolver(x),
68+
Err(x) => Self::Io(x),
69+
}
70+
}
71+
}
72+
6073
impl<'a> NgxHttpClient<'a> {
61-
pub fn new(log: NonNull<ngx_log_t>, ssl: &'a NgxSsl, ssl_verify: bool) -> Self {
74+
pub fn new(
75+
log: NonNull<ngx_log_t>,
76+
resolver: NonNull<ngx_resolver_t>,
77+
resolver_timeout: usize,
78+
ssl: &'a NgxSsl,
79+
ssl_verify: bool,
80+
) -> Self {
6281
Self {
6382
log,
83+
resolver: Resolver::from_resolver(resolver, resolver_timeout),
6484
ssl,
6585
ssl_verify,
6686
}
@@ -114,7 +134,9 @@ impl HttpClient for NgxHttpClient<'_> {
114134

115135
let mut peer = Box::pin(PeerConnection::new(self.log)?);
116136

117-
peer.as_mut().connect_to(authority.as_str(), ssl).await?;
137+
peer.as_mut()
138+
.connect_to(authority.as_str(), &self.resolver, ssl)
139+
.await?;
118140

119141
if self.ssl_verify {
120142
if let Err(err) = peer.verify_peer() {

src/net/peer_conn.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ use core::task::{self, Poll};
77
use std::io;
88

99
use nginx_sys::{
10-
ngx_connection_t, ngx_destroy_pool, ngx_event_connect_peer, ngx_event_get_peer, ngx_int_t,
11-
ngx_log_t, ngx_peer_connection_t, ngx_pool_t, ngx_ssl_shutdown, ngx_ssl_t, ngx_str_t,
12-
ngx_url_t, NGX_DEFAULT_POOL_SIZE, NGX_LOG_ERR, NGX_LOG_WARN,
10+
ngx_addr_t, ngx_connection_t, ngx_destroy_pool, ngx_event_connect_peer, ngx_event_get_peer,
11+
ngx_inet_set_port, ngx_int_t, ngx_log_t, ngx_peer_connection_t, ngx_pool_t, ngx_ssl_shutdown,
12+
ngx_ssl_t, ngx_str_t, ngx_url_t, NGX_DEFAULT_POOL_SIZE, NGX_LOG_ERR, NGX_LOG_WARN,
1313
};
14+
use ngx::collections::Vec;
1415
use ngx::core::Status;
1516
use ngx::{ngx_log_debug, ngx_log_error};
1617
use openssl_sys::{SSL_get_verify_result, X509_verify_cert_error_string, X509_V_OK};
1718

1819
use super::connection::{Connection, ConnectionLogError};
20+
use super::resolver::Resolver;
1921
use crate::util::OwnedPool;
2022

2123
/// Async wrapper over an [ngx_peer_connection_t].
@@ -137,6 +139,7 @@ impl PeerConnection {
137139
pub async fn connect_to(
138140
mut self: Pin<&mut Self>,
139141
authority: &str,
142+
res: &Resolver,
140143
ssl: Option<&ngx_ssl_t>,
141144
) -> Result<(), io::Error> {
142145
let mut url: ngx_url_t = unsafe { mem::zeroed() };
@@ -151,6 +154,9 @@ impl PeerConnection {
151154
s
152155
};
153156
url.default_port = if ssl.is_some() { 443 } else { 80 };
157+
url.set_no_resolve(1);
158+
159+
let addr_vec: Vec<ngx_addr_t>;
154160

155161
if Status(unsafe { nginx_sys::ngx_parse_url(self.pool.as_mut(), &mut url) })
156162
!= Status::NGX_OK
@@ -167,7 +173,15 @@ impl PeerConnection {
167173
self.pc.sockaddr = addr.sockaddr;
168174
self.pc.socklen = addr.socklen;
169175
} else {
170-
return Err(io::ErrorKind::NotFound.into());
176+
addr_vec = res
177+
.resolve(&url.host, self.pool.as_mut())
178+
.await
179+
.map_err(io::Error::other)?;
180+
181+
self.pc.sockaddr = addr_vec[0].sockaddr;
182+
self.pc.socklen = addr_vec[0].socklen;
183+
184+
unsafe { ngx_inet_set_port(self.pc.sockaddr, url.port) };
171185
}
172186

173187
if url.url.len > url.host.len {

src/net/resolver.rs

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

0 commit comments

Comments
 (0)