Skip to content

Commit 6af1f0c

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

File tree

6 files changed

+251
-8
lines changed

6 files changed

+251
-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
@@ -6,3 +6,4 @@
66
pub mod connection;
77
pub mod http;
88
pub mod peer_conn;
9+
pub mod resolver;

src/net/http.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ use http::uri::Scheme;
1313
use http::{Request, Response};
1414
use http_body::Body;
1515
use http_body_util::BodyExt;
16-
use nginx_sys::{ngx_log_t, NGX_LOG_WARN};
16+
use nginx_sys::{ngx_log_t, ngx_resolver_t, NGX_LOG_WARN};
1717
use ngx::allocator::Box;
1818
use ngx::async_::spawn;
1919
use ngx::ngx_log_error;
2020
use thiserror::Error;
2121

2222
use super::peer_conn::PeerConnection;
23+
use super::resolver::Resolver;
2324
use crate::conf::ssl::NgxSsl;
2425

2526
// The largest response we can reasonably expect is a certificate chain, which should not exceed
@@ -52,6 +53,7 @@ pub trait HttpClient {
5253

5354
pub struct NgxHttpClient<'a> {
5455
log: NonNull<ngx_log_t>,
56+
resolver: Resolver,
5557
ssl: &'a NgxSsl,
5658
ssl_verify: bool,
5759
}
@@ -62,16 +64,34 @@ pub enum HttpClientError {
6264
Body(std::boxed::Box<dyn StdError + Send + Sync>),
6365
#[error("request error: {0}")]
6466
Http(#[from] hyper::Error),
67+
#[error("name resolution error: {0}")]
68+
Resolver(super::resolver::Error),
6569
#[error("connection error: {0}")]
66-
Io(#[from] io::Error),
70+
Io(io::Error),
6771
#[error("invalid uri: {0}")]
6872
Uri(&'static str),
6973
}
7074

75+
impl From<io::Error> for HttpClientError {
76+
fn from(err: io::Error) -> Self {
77+
match err.downcast::<super::resolver::Error>() {
78+
Ok(x) => Self::Resolver(x),
79+
Err(x) => Self::Io(x),
80+
}
81+
}
82+
}
83+
7184
impl<'a> NgxHttpClient<'a> {
72-
pub fn new(log: NonNull<ngx_log_t>, ssl: &'a NgxSsl, ssl_verify: bool) -> Self {
85+
pub fn new(
86+
log: NonNull<ngx_log_t>,
87+
resolver: NonNull<ngx_resolver_t>,
88+
resolver_timeout: usize,
89+
ssl: &'a NgxSsl,
90+
ssl_verify: bool,
91+
) -> Self {
7392
Self {
7493
log,
94+
resolver: Resolver::from_resolver(resolver, resolver_timeout),
7595
ssl,
7696
ssl_verify,
7797
}
@@ -124,7 +144,9 @@ impl HttpClient for NgxHttpClient<'_> {
124144

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

127-
peer.as_mut().connect_to(authority.as_str(), ssl).await?;
147+
peer.as_mut()
148+
.connect_to(authority.as_str(), &self.resolver, ssl)
149+
.await?;
128150

129151
if self.ssl_verify {
130152
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
@@ -12,15 +12,18 @@ use core::task::{self, Poll};
1212
use std::io;
1313

1414
use nginx_sys::{
15-
ngx_connection_t, ngx_destroy_pool, ngx_event_connect_peer, ngx_event_get_peer, ngx_int_t,
16-
ngx_log_t, ngx_msec_t, ngx_peer_connection_t, ngx_pool_t, ngx_ssl_shutdown, ngx_ssl_t,
17-
ngx_str_t, ngx_url_t, NGX_DEFAULT_POOL_SIZE, NGX_LOG_ERR, NGX_LOG_WARN,
15+
ngx_addr_t, ngx_connection_t, ngx_destroy_pool, ngx_event_connect_peer, ngx_event_get_peer,
16+
ngx_inet_set_port, ngx_int_t, ngx_log_t, ngx_msec_t, ngx_peer_connection_t, ngx_pool_t,
17+
ngx_ssl_shutdown, ngx_ssl_t, ngx_str_t, ngx_url_t, NGX_DEFAULT_POOL_SIZE, NGX_LOG_ERR,
18+
NGX_LOG_WARN,
1819
};
20+
use ngx::collections::Vec;
1921
use ngx::core::Status;
2022
use ngx::{ngx_log_debug, ngx_log_error};
2123
use openssl_sys::{SSL_get_verify_result, X509_verify_cert_error_string, X509_V_OK};
2224

2325
use super::connection::{Connection, ConnectionLogError};
26+
use super::resolver::Resolver;
2427
use crate::util::OwnedPool;
2528

2629
const ACME_DEFAULT_READ_TIMEOUT: ngx_msec_t = 60000;
@@ -145,6 +148,7 @@ impl PeerConnection {
145148
pub async fn connect_to(
146149
mut self: Pin<&mut Self>,
147150
authority: &str,
151+
res: &Resolver,
148152
ssl: Option<&ngx_ssl_t>,
149153
) -> Result<(), io::Error> {
150154
let mut url: ngx_url_t = unsafe { mem::zeroed() };
@@ -159,6 +163,9 @@ impl PeerConnection {
159163
s
160164
};
161165
url.default_port = if ssl.is_some() { 443 } else { 80 };
166+
url.set_no_resolve(1);
167+
168+
let addr_vec: Vec<ngx_addr_t>;
162169

163170
if Status(unsafe { nginx_sys::ngx_parse_url(self.pool.as_mut(), &mut url) })
164171
!= Status::NGX_OK
@@ -175,7 +182,15 @@ impl PeerConnection {
175182
self.pc.sockaddr = addr.sockaddr;
176183
self.pc.socklen = addr.socklen;
177184
} else {
178-
return Err(io::ErrorKind::NotFound.into());
185+
addr_vec = res
186+
.resolve(&url.host, self.pool.as_mut())
187+
.await
188+
.map_err(io::Error::other)?;
189+
190+
self.pc.sockaddr = addr_vec[0].sockaddr;
191+
self.pc.socklen = addr_vec[0].socklen;
192+
193+
unsafe { ngx_inet_set_port(self.pc.sockaddr, url.port) };
179194
}
180195

181196
if url.url.len > url.host.len {

src/net/resolver.rs

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

0 commit comments

Comments
 (0)