Skip to content

Commit c1eb36e

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

File tree

6 files changed

+245
-8
lines changed

6 files changed

+245
-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
@@ -13,6 +13,7 @@ crate-type = ["cdylib"]
1313
base64 = "0.22.1"
1414
bytes = "1.10.1"
1515
constcat = "0.6.1"
16+
futures-channel = "0.3.31"
1617
http = "1.3.1"
1718
http-body = "1.0.1"
1819
http-body-util = "0.1.3"

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
@@ -8,13 +8,14 @@ use http::uri::Scheme;
88
use http::{Request, Response};
99
use http_body::Body;
1010
use http_body_util::BodyExt;
11-
use nginx_sys::{ngx_log_t, NGX_LOG_WARN};
11+
use nginx_sys::{ngx_log_t, ngx_resolver_t, NGX_LOG_WARN};
1212
use ngx::allocator::Box;
1313
use ngx::async_::spawn;
1414
use ngx::ngx_log_error;
1515
use thiserror::Error;
1616

1717
use super::peer_conn::PeerConnection;
18+
use super::resolver::Resolver;
1819
use crate::conf::ssl::NgxSsl;
1920

2021
// The largest response we can reasonably expect is a certificate chain, which should not exceed
@@ -47,6 +48,7 @@ pub trait HttpClient {
4748

4849
pub struct NgxHttpClient<'a> {
4950
log: NonNull<ngx_log_t>,
51+
resolver: Resolver,
5052
ssl: &'a NgxSsl,
5153
ssl_verify: bool,
5254
}
@@ -57,16 +59,34 @@ pub enum HttpClientError {
5759
Body(std::boxed::Box<dyn StdError + Send + Sync>),
5860
#[error("request error: {0}")]
5961
Http(#[from] hyper::Error),
62+
#[error("name resolution error: {0}")]
63+
Resolver(super::resolver::Error),
6064
#[error("connection error: {0}")]
61-
Io(#[from] io::Error),
65+
Io(io::Error),
6266
#[error("invalid uri: {0}")]
6367
Uri(&'static str),
6468
}
6569

70+
impl From<io::Error> for HttpClientError {
71+
fn from(err: io::Error) -> Self {
72+
match err.downcast::<super::resolver::Error>() {
73+
Ok(x) => Self::Resolver(x),
74+
Err(x) => Self::Io(x),
75+
}
76+
}
77+
}
78+
6679
impl<'a> NgxHttpClient<'a> {
67-
pub fn new(log: NonNull<ngx_log_t>, ssl: &'a NgxSsl, ssl_verify: bool) -> Self {
80+
pub fn new(
81+
log: NonNull<ngx_log_t>,
82+
resolver: NonNull<ngx_resolver_t>,
83+
resolver_timeout: usize,
84+
ssl: &'a NgxSsl,
85+
ssl_verify: bool,
86+
) -> Self {
6887
Self {
6988
log,
89+
resolver: Resolver::from_resolver(resolver, resolver_timeout),
7090
ssl,
7191
ssl_verify,
7292
}
@@ -119,7 +139,9 @@ impl HttpClient for NgxHttpClient<'_> {
119139

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

122-
peer.as_mut().connect_to(authority.as_str(), ssl).await?;
142+
peer.as_mut()
143+
.connect_to(authority.as_str(), &self.resolver, ssl)
144+
.await?;
123145

124146
if self.ssl_verify {
125147
if let Err(err) = peer.verify_peer() {

src/net/peer_conn.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ 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_msec_t, ngx_peer_connection_t, ngx_pool_t, ngx_ssl_shutdown, ngx_ssl_t,
12-
ngx_str_t, 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_msec_t, ngx_peer_connection_t, ngx_pool_t,
12+
ngx_ssl_shutdown, ngx_ssl_t, ngx_str_t, ngx_url_t, NGX_DEFAULT_POOL_SIZE, NGX_LOG_ERR,
13+
NGX_LOG_WARN,
1314
};
15+
use ngx::collections::Vec;
1416
use ngx::core::Status;
1517
use ngx::{ngx_log_debug, ngx_log_error};
1618
use openssl_sys::{SSL_get_verify_result, X509_verify_cert_error_string, X509_V_OK};
1719

1820
use super::connection::{Connection, ConnectionLogError};
21+
use super::resolver::Resolver;
1922
use crate::util::OwnedPool;
2023

2124
const ACME_DEFAULT_READ_TIMEOUT: ngx_msec_t = 60000;
@@ -140,6 +143,7 @@ impl PeerConnection {
140143
pub async fn connect_to(
141144
mut self: Pin<&mut Self>,
142145
authority: &str,
146+
res: &Resolver,
143147
ssl: Option<&ngx_ssl_t>,
144148
) -> Result<(), io::Error> {
145149
let mut url: ngx_url_t = unsafe { mem::zeroed() };
@@ -154,6 +158,9 @@ impl PeerConnection {
154158
s
155159
};
156160
url.default_port = if ssl.is_some() { 443 } else { 80 };
161+
url.set_no_resolve(1);
162+
163+
let addr_vec: Vec<ngx_addr_t>;
157164

158165
if Status(unsafe { nginx_sys::ngx_parse_url(self.pool.as_mut(), &mut url) })
159166
!= Status::NGX_OK
@@ -170,7 +177,15 @@ impl PeerConnection {
170177
self.pc.sockaddr = addr.sockaddr;
171178
self.pc.socklen = addr.socklen;
172179
} else {
173-
return Err(io::ErrorKind::NotFound.into());
180+
addr_vec = res
181+
.resolve(&url.host, self.pool.as_mut())
182+
.await
183+
.map_err(io::Error::other)?;
184+
185+
self.pc.sockaddr = addr_vec[0].sockaddr;
186+
self.pc.socklen = addr_vec[0].socklen;
187+
188+
unsafe { ngx_inet_set_port(self.pc.sockaddr, url.port) };
174189
}
175190

176191
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)