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