vrl/stdlib/
reverse_dns.rs

1use crate::compiler::prelude::*;
2
3#[cfg(not(target_arch = "wasm32"))]
4mod non_wasm {
5    use crate::compiler::prelude::*;
6    use crate::value::Value;
7    use dns_lookup::lookup_addr;
8    use std::net::IpAddr;
9
10    fn reverse_dns(value: &Value) -> Resolved {
11        let ip: IpAddr = value
12            .try_bytes_utf8_lossy()?
13            .parse()
14            .map_err(|err| format!("unable to parse IP address: {err}"))?;
15        let host = lookup_addr(&ip).map_err(|err| format!("unable to perform a lookup : {err}"))?;
16
17        Ok(host.into())
18    }
19
20    #[derive(Debug, Clone)]
21    pub(super) struct ReverseDnsFn {
22        pub(super) value: Box<dyn Expression>,
23    }
24
25    impl FunctionExpression for ReverseDnsFn {
26        fn resolve(&self, ctx: &mut Context) -> Resolved {
27            let value = self.value.resolve(ctx)?;
28            reverse_dns(&value)
29        }
30
31        fn type_def(&self, _: &state::TypeState) -> TypeDef {
32            TypeDef::bytes().fallible()
33        }
34    }
35}
36
37#[allow(clippy::wildcard_imports)]
38#[cfg(not(target_arch = "wasm32"))]
39use non_wasm::*;
40
41#[derive(Clone, Copy, Debug)]
42pub struct ReverseDns;
43
44impl Function for ReverseDns {
45    fn identifier(&self) -> &'static str {
46        "reverse_dns"
47    }
48
49    fn usage(&self) -> &'static str {
50        "Performs a reverse DNS lookup on the provided IP address to retrieve the associated hostname."
51    }
52
53    fn category(&self) -> &'static str {
54        Category::System.as_ref()
55    }
56
57    fn return_kind(&self) -> u16 {
58        kind::BYTES
59    }
60
61    fn parameters(&self) -> &'static [Parameter] {
62        const PARAMETERS: &[Parameter] = &[Parameter::required(
63            "value",
64            kind::BYTES,
65            "The IP address (IPv4 or IPv6) to perform the reverse DNS lookup on.",
66        )];
67        PARAMETERS
68    }
69
70    fn examples(&self) -> &'static [Example] {
71        &[example! {
72            title: "Example",
73            source: r#"reverse_dns!("127.0.0.1")"#,
74            result: Ok("localhost"),
75        }]
76    }
77
78    #[cfg(not(target_arch = "wasm32"))]
79    fn compile(
80        &self,
81        _state: &state::TypeState,
82        _ctx: &mut FunctionCompileContext,
83        arguments: ArgumentList,
84    ) -> Compiled {
85        let value = arguments.required("value");
86
87        Ok(ReverseDnsFn { value }.as_expr())
88    }
89
90    #[cfg(target_arch = "wasm32")]
91    fn compile(
92        &self,
93        _state: &state::TypeState,
94        ctx: &mut FunctionCompileContext,
95        _arguments: ArgumentList,
96    ) -> Compiled {
97        Ok(super::WasmUnsupportedFunction::new(ctx.span(), TypeDef::bytes().fallible()).as_expr())
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::value;
105
106    test_function![
107        reverse_dns => ReverseDns;
108
109        invalid_ip {
110            args: func_args![value: value!("999.999.999.999")],
111            want: Err("unable to parse IP address: invalid IP address syntax"),
112            tdef: TypeDef::bytes().fallible(),
113        }
114
115        google_ipv4 {
116            args: func_args![value: value!("8.8.8.8")],
117            want: Ok(value!("dns.google")),
118            tdef: TypeDef::bytes().fallible(),
119        }
120
121        google_ipv6 {
122            args: func_args![value: value!("2001:4860:4860::8844")],
123            want: Ok(value!("dns.google")),
124            tdef: TypeDef::bytes().fallible(),
125        }
126
127        invalid_type {
128            args: func_args![value: value!(1)],
129            want: Err("expected string, got integer"),
130            tdef: TypeDef::bytes().fallible(),
131        }
132    ];
133}