88//! See <https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver>.
99
1010use alloc:: string:: { String , ToString } ;
11- use core:: ffi:: c_void;
1211use core:: fmt;
1312use core:: num:: NonZero ;
1413use core:: pin:: Pin ;
1514use core:: ptr:: NonNull ;
1615use core:: task:: { Context , Poll , Waker } ;
1716
1817use crate :: {
19- allocator:: Box ,
2018 collections:: Vec ,
2119 core:: Pool ,
2220 ffi:: {
@@ -101,7 +99,7 @@ impl From<NonZero<isize>> for ResolverError {
10199 }
102100}
103101
104- type Res = Result < Vec < ngx_addr_t > , Error > ;
102+ type Res = Result < Vec < ngx_addr_t , Pool > , Error > ;
105103
106104/// A wrapper for an ngx_resolver_t which provides an async Rust API
107105pub struct Resolver {
@@ -117,12 +115,22 @@ impl Resolver {
117115 }
118116
119117 /// Resolve a name into a set of addresses.
120- ///
121- /// The set of addresses may not be deterministic, because the
122- /// implementation of the resolver may race multiple DNS requests.
123- pub async fn resolve ( & self , name : & ngx_str_t , pool : & Pool ) -> Res {
124- let mut resolver = Resolution :: new ( name, pool, self . resolver , self . timeout ) ?;
125- resolver. as_mut ( ) . await
118+ pub async fn resolve_name ( & self , name : & ngx_str_t , pool : & Pool ) -> Res {
119+ let mut ctx = ResolverCtx :: new ( self . resolver ) ?;
120+ ctx. name = * name;
121+ ctx. timeout = self . timeout ;
122+
123+ Resolution :: new ( ctx, pool) . await
124+ }
125+
126+ /// Resolve a service into a set of addresses.
127+ pub async fn resolve_service ( & self , name : & ngx_str_t , service : & ngx_str_t , pool : & Pool ) -> Res {
128+ let mut ctx = ResolverCtx :: new ( self . resolver ) ?;
129+ ctx. name = * name;
130+ ctx. service = * service;
131+ ctx. timeout = self . timeout ;
132+
133+ Resolution :: new ( ctx, pool) . await
126134 }
127135}
128136
@@ -136,68 +144,22 @@ struct Resolution<'a> {
136144 // Pool used for allocating `Vec<ngx_addr_t>` contents in `Res`. Read by
137145 // the callback handler.
138146 pool : & ' a Pool ,
139- // Pointer to the ngx_resolver_ctx_t. Resolution constructs this with
140- // ngx_resolver_name_start in the constructor, and is responsible for
141- // freeing it, with ngx_resolver_name_done, once it is no longer needed -
142- // this happens in either the callback handler, or the drop impl. Calling
143- // ngx_resolver_name_done before the callback fires ensure nginx does not
144- // ever call the callback.
145- ctx : Option < NonNull < ngx_resolver_ctx_t > > ,
147+ // Pointer to the ngx_resolver_ctx_t.
148+ ctx : Option < ResolverCtx > ,
146149}
147150
148151impl < ' a > Resolution < ' a > {
149- fn new (
150- name : & ngx_str_t ,
151- pool : & ' a Pool ,
152- resolver : NonNull < ngx_resolver_t > ,
153- timeout : ngx_msec_t ,
154- ) -> Result < Pin < Box < Self > > , Error > {
155- let mut ctx = unsafe {
156- // Start a new resolver context. This implementation currently
157- // passes a null for the second argument `temp`. A non-null `temp`
158- // provides a fast, non-callback-based path for immediately
159- // returning an addr iff `temp` contains a name which is textual
160- // form of an addr.
161- let ctx = ngx_resolve_start ( resolver. as_ptr ( ) , core:: ptr:: null_mut ( ) ) ;
162- NonNull :: new ( ctx) . ok_or ( Error :: AllocationFailed ) ?
163- } ;
164-
165- // Create a pinned Resolution on the heap, so that we can make
166- // a stable pointer to the Resolution struct.
167- let mut this = Pin :: new ( Box :: new ( Resolution {
152+ pub fn new ( mut ctx : ResolverCtx , pool : & ' a Pool ) -> Self {
153+ ctx. handler = Some ( Self :: handler) ;
154+
155+ ctx. set_cancelable ( 1 ) ;
156+
157+ Self {
168158 complete : None ,
169159 waker : None ,
170160 pool,
171161 ctx : Some ( ctx) ,
172- } ) ) ;
173-
174- {
175- // Set up the ctx with everything the resolver needs to resolve a
176- // name, and the handler callback which is called on completion.
177- let ctx: & mut ngx_resolver_ctx_t = unsafe { ctx. as_mut ( ) } ;
178- ctx. name = * name;
179- ctx. timeout = timeout;
180- ctx. set_cancelable ( 1 ) ;
181- ctx. handler = Some ( Self :: handler) ;
182- // Safety: Self::handler, Future::poll, and Drop::drop will have
183- // access to &mut Resolution. Nginx is single-threaded and we are
184- // assured only one of those is on the stack at a time, except if
185- // Self::handler wakes a task which polls or drops the Future,
186- // which it only does after use of &mut Resolution is complete.
187- let ptr: & mut Resolution = unsafe { Pin :: into_inner_unchecked ( this. as_mut ( ) ) } ;
188- ctx. data = ptr as * mut Resolution as * mut c_void ;
189- }
190-
191- // Start name resolution using the ctx. If the name is in the dns
192- // cache, the handler may get called from this stack. Otherwise, it
193- // will be called later by nginx when it gets a dns response or a
194- // timeout.
195- let ret = unsafe { ngx_resolve_name ( ctx. as_ptr ( ) ) } ;
196- if let Some ( e) = NonZero :: new ( ret) {
197- return Err ( Error :: Resolver ( ResolverError :: from ( e) , name. to_string ( ) ) ) ;
198162 }
199-
200- Ok ( this)
201163 }
202164
203165 // Nginx will call this handler when name resolution completes. If the
@@ -206,74 +168,38 @@ impl<'a> Resolution<'a> {
206168 unsafe extern "C" fn handler ( ctx : * mut ngx_resolver_ctx_t ) {
207169 let mut data = unsafe { NonNull :: new_unchecked ( ( * ctx) . data as * mut Resolution ) } ;
208170 let this: & mut Resolution = unsafe { data. as_mut ( ) } ;
209- this. complete = Some ( Self :: resolve_result ( ctx, this. pool ) ) ;
210171
211- let mut ctx = this. ctx . take ( ) . expect ( "ctx must be present" ) ;
212- unsafe { nginx_sys:: ngx_resolve_name_done ( ctx. as_mut ( ) ) } ;
172+ if let Some ( ctx) = this. ctx . take ( ) {
173+ this. complete = Some ( ctx. into_result ( this. pool ) ) ;
174+ }
213175
214176 // Wake last, after all use of &mut Resolution, because wake may
215177 // poll Resolution future on current stack.
216178 if let Some ( waker) = this. waker . take ( ) {
217179 waker. wake ( ) ;
218180 }
219181 }
220-
221- /// Take the results in a ctx and make an owned copy as a
222- /// Result<Vec<ngx_addr_t>, Error>, where the internals of the ngx_addr_t
223- /// are allocated on the given Pool
224- fn resolve_result ( ctx : * mut ngx_resolver_ctx_t , pool : & Pool ) -> Res {
225- let ctx = unsafe { ctx. as_ref ( ) . unwrap ( ) } ;
226- if let Some ( e) = NonZero :: new ( ctx. state ) {
227- return Err ( Error :: Resolver (
228- ResolverError :: from ( e) ,
229- ctx. name . to_string ( ) ,
230- ) ) ;
231- }
232- if ctx. addrs . is_null ( ) {
233- Err ( Error :: AllocationFailed ) ?;
234- }
235-
236- if ctx. naddrs > 0 {
237- unsafe { core:: slice:: from_raw_parts ( ctx. addrs , ctx. naddrs ) }
238- . iter ( )
239- . map ( |addr| Self :: copy_resolved_addr ( addr, pool) )
240- . collect :: < Result < Vec < _ > , _ > > ( )
241- } else {
242- Ok ( Vec :: new ( ) )
243- }
244- }
245-
246- /// Take the contents of an ngx_resolver_addr_t and make an owned copy as
247- /// an ngx_addr_t, using the Pool for allocation of the internals.
248- fn copy_resolved_addr (
249- addr : & nginx_sys:: ngx_resolver_addr_t ,
250- pool : & Pool ,
251- ) -> Result < ngx_addr_t , Error > {
252- let sockaddr = pool. alloc ( addr. socklen as usize ) as * mut nginx_sys:: sockaddr ;
253- if sockaddr. is_null ( ) {
254- Err ( Error :: AllocationFailed ) ?;
255- }
256- unsafe {
257- addr. sockaddr
258- . cast :: < u8 > ( )
259- . copy_to_nonoverlapping ( sockaddr. cast ( ) , addr. socklen as usize )
260- } ;
261-
262- let name = unsafe { ngx_str_t:: from_bytes ( pool. as_ptr ( ) , addr. name . as_bytes ( ) ) }
263- . ok_or ( Error :: AllocationFailed ) ?;
264-
265- Ok ( ngx_addr_t {
266- sockaddr,
267- socklen : addr. socklen ,
268- name,
269- } )
270- }
271182}
272183
273184impl < ' a > core:: future:: Future for Resolution < ' a > {
274- type Output = Result < Vec < ngx_addr_t > , Error > ;
185+ type Output = Result < Vec < ngx_addr_t , Pool > , Error > ;
186+
275187 fn poll ( mut self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Self :: Output > {
276188 let mut this = self . as_mut ( ) ;
189+
190+ if this. waker . is_none ( ) && this. complete . is_none ( ) {
191+ let addr = core:: ptr:: from_mut ( unsafe { Pin :: into_inner_unchecked ( this. as_mut ( ) ) } ) ;
192+
193+ if let Some ( ctx) = & mut this. ctx {
194+ // Start name resolution using the ctx. If the name is in the dns
195+ // cache, the handler may get called from this stack. Otherwise, it
196+ // will be called later by nginx when it gets a dns response or a
197+ // timeout.
198+ ctx. data = addr. cast ( ) ;
199+ ctx. resolve ( ) ?;
200+ }
201+ }
202+
277203 // The handler populates this.complete, and we consume it here:
278204 match this. complete . take ( ) {
279205 Some ( res) => Poll :: Ready ( res) ,
@@ -292,15 +218,106 @@ impl<'a> core::future::Future for Resolution<'a> {
292218 }
293219}
294220
295- impl < ' a > Drop for Resolution < ' a > {
221+ struct ResolverCtx ( NonNull < ngx_resolver_ctx_t > ) ;
222+
223+ impl core:: ops:: Deref for ResolverCtx {
224+ type Target = ngx_resolver_ctx_t ;
225+
226+ fn deref ( & self ) -> & Self :: Target {
227+ // SAFETY: this wrapper is always constructed with a valid non-empty resolve context
228+ unsafe { self . 0 . as_ref ( ) }
229+ }
230+ }
231+
232+ impl core:: ops:: DerefMut for ResolverCtx {
233+ fn deref_mut ( & mut self ) -> & mut Self :: Target {
234+ // SAFETY: this wrapper is always constructed with a valid non-empty resolve context
235+ unsafe { self . 0 . as_mut ( ) }
236+ }
237+ }
238+
239+ impl Drop for ResolverCtx {
296240 fn drop ( & mut self ) {
297- // ctx is taken and freed if the Resolution reaches the handler
298- // callback, but if dropped before that callback, this will cancel any
299- // ongoing work as well as free the ctx memory.
300- if let Some ( mut ctx) = self . ctx . take ( ) {
301- unsafe {
302- nginx_sys:: ngx_resolve_name_done ( ctx. as_mut ( ) ) ;
241+ unsafe {
242+ nginx_sys:: ngx_resolve_name_done ( self . 0 . as_mut ( ) ) ;
243+ }
244+ }
245+ }
246+
247+ impl ResolverCtx {
248+ /// Creates a new resolver context.
249+ ///
250+ /// This implementation currently passes a null for the second argument `temp`. A non-null
251+ /// `temp` provides a fast, non-callback-based path for immediately returning an addr if
252+ /// `temp` contains a name which is textual form of an addr.
253+ pub fn new ( resolver : NonNull < ngx_resolver_t > ) -> Result < Self , Error > {
254+ let ctx = unsafe { ngx_resolve_start ( resolver. as_ptr ( ) , core:: ptr:: null_mut ( ) ) } ;
255+ NonNull :: new ( ctx) . map ( Self ) . ok_or ( Error :: AllocationFailed )
256+ }
257+
258+ /// Starts name resolution.
259+ pub fn resolve ( & mut self ) -> Result < ( ) , Error > {
260+ let ret = unsafe { ngx_resolve_name ( self . 0 . as_ptr ( ) ) } ;
261+
262+ if let Some ( e) = NonZero :: new ( ret) {
263+ let name = self . name . to_string ( ) ;
264+ Err ( Error :: Resolver ( ResolverError :: from ( e) , name) )
265+ } else {
266+ Ok ( ( ) )
267+ }
268+ }
269+
270+ /// Take the results in a ctx and make an owned copy as a
271+ /// Result<Vec<ngx_addr_t>, Error>, where the internals of the ngx_addr_t
272+ /// are allocated on the given Pool
273+ pub fn into_result ( self , pool : & Pool ) -> Result < Vec < ngx_addr_t , Pool > , Error > {
274+ if let Some ( e) = NonZero :: new ( self . state ) {
275+ return Err ( Error :: Resolver (
276+ ResolverError :: from ( e) ,
277+ self . name . to_string ( ) ,
278+ ) ) ;
279+ }
280+ if self . addrs . is_null ( ) {
281+ Err ( Error :: AllocationFailed ) ?;
282+ }
283+
284+ let mut out = Vec :: new_in ( pool. clone ( ) ) ;
285+
286+ if self . naddrs > 0 {
287+ out. try_reserve_exact ( self . naddrs )
288+ . map_err ( |_| Error :: AllocationFailed ) ?;
289+
290+ for addr in unsafe { core:: slice:: from_raw_parts ( self . addrs , self . naddrs ) } {
291+ out. push ( copy_resolved_addr ( addr, pool) ?) ;
303292 }
304293 }
294+
295+ Ok ( out)
305296 }
306297}
298+
299+ /// Take the contents of an ngx_resolver_addr_t and make an owned copy as
300+ /// an ngx_addr_t, using the Pool for allocation of the internals.
301+ fn copy_resolved_addr (
302+ addr : & nginx_sys:: ngx_resolver_addr_t ,
303+ pool : & Pool ,
304+ ) -> Result < ngx_addr_t , Error > {
305+ let sockaddr = pool. alloc ( addr. socklen as usize ) as * mut nginx_sys:: sockaddr ;
306+ if sockaddr. is_null ( ) {
307+ Err ( Error :: AllocationFailed ) ?;
308+ }
309+ unsafe {
310+ addr. sockaddr
311+ . cast :: < u8 > ( )
312+ . copy_to_nonoverlapping ( sockaddr. cast ( ) , addr. socklen as usize )
313+ } ;
314+
315+ let name = unsafe { ngx_str_t:: from_bytes ( pool. as_ptr ( ) , addr. name . as_bytes ( ) ) }
316+ . ok_or ( Error :: AllocationFailed ) ?;
317+
318+ Ok ( ngx_addr_t {
319+ sockaddr,
320+ socklen : addr. socklen ,
321+ name,
322+ } )
323+ }
0 commit comments