Skip to main content

relibc/header/netdb/
lookup.rs

1use alloc::{boxed::Box, string::ToString, vec::Vec};
2use core::{mem, ptr};
3
4use crate::{
5    out::Out,
6    platform::{
7        Pal, Sys,
8        types::{c_int, c_void},
9    },
10};
11
12use crate::header::{
13    bits_arpainet::htons,
14    errno::*,
15    netinet_in::{IPPROTO_UDP, in_addr, sockaddr_in},
16    sys_socket::{
17        self,
18        constants::{AF_INET, SOCK_DGRAM},
19        sockaddr, socklen_t,
20    },
21    time::{self, timespec},
22};
23
24use super::{
25    dns::{Dns, DnsQuery},
26    sys::get_dns_server,
27};
28
29pub type LookupHost = Vec<in_addr>;
30
31pub fn lookup_host(host: &str) -> Result<LookupHost, c_int> {
32    if let Some(host_direct_addr) = parse_ipv4_string(host) {
33        // already an ip address
34        return Ok(vec![in_addr {
35            s_addr: host_direct_addr,
36        }]);
37    }
38
39    let dns_string = get_dns_server().map_err(|e| e.0)?;
40
41    if let Some(dns_addr) = parse_ipv4_string(&dns_string) {
42        let mut timespec = timespec::default();
43        if let Ok(()) = Sys::clock_gettime(
44            time::constants::CLOCK_REALTIME,
45            Out::from_mut(&mut timespec),
46        ) {}; // TODO handle error
47        let tid = (timespec.tv_nsec >> 16) as u16;
48
49        let packet = Dns {
50            transaction_id: tid,
51            flags: 0x0100,
52            queries: vec![DnsQuery {
53                name: host.to_string(),
54                q_type: 0x0001,
55                q_class: 0x0001,
56            }],
57            answers: vec![],
58        };
59
60        let packet_data = packet.compile();
61        let packet_data_len = packet_data.len();
62
63        let packet_data_box = packet_data.into_boxed_slice();
64        let packet_data_ptr = Box::into_raw(packet_data_box).cast::<c_void>();
65
66        let dest = sockaddr_in {
67            sin_family: AF_INET as u16,
68            sin_port: htons(53),
69            sin_addr: in_addr { s_addr: dns_addr },
70            ..Default::default()
71        };
72        let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
73
74        let sock = unsafe {
75            let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
76            if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
77                return Err(EIO);
78            }
79            if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
80                drop(Box::from_raw(packet_data_ptr));
81                return Err(EIO);
82            }
83            sock
84        };
85
86        unsafe {
87            drop(Box::from_raw(packet_data_ptr));
88        }
89
90        let i = 0 as socklen_t;
91        let mut buf = vec![0u8; 65536];
92        let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
93
94        let count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
95        if count < 0 {
96            return Err(EIO);
97        }
98
99        match Dns::parse(&buf[..count as usize]) {
100            Ok(response) => {
101                let addrs: Vec<_> = response
102                    .answers
103                    .into_iter()
104                    .filter_map(|answer| {
105                        if answer.a_type == 0x0001
106                            && answer.a_class == 0x0001
107                            && answer.data.len() == 4
108                        {
109                            let addr = in_addr {
110                                s_addr: u32::from_ne_bytes([
111                                    answer.data[0],
112                                    answer.data[1],
113                                    answer.data[2],
114                                    answer.data[3],
115                                ]),
116                            };
117                            Some(addr)
118                        } else {
119                            None
120                        }
121                    })
122                    .collect();
123
124                Ok(addrs)
125            }
126            Err(_err) => Err(EINVAL),
127        }
128    } else {
129        Err(EINVAL)
130    }
131}
132
133pub fn lookup_addr(addr: in_addr) -> Result<Vec<Vec<u8>>, c_int> {
134    let dns_string = get_dns_server().map_err(|e| e.0)?;
135
136    if let Some(dns_addr) = parse_ipv4_string(&dns_string) {
137        let addr: [u8; 4] = addr.s_addr.to_ne_bytes();
138        // Address intentionally backwards for reverse lookup
139        let name = format!(
140            "{}.{}.{}.{}.in-addr.arpa",
141            addr[3], addr[2], addr[1], addr[0]
142        );
143
144        let mut timespec = timespec::default();
145        if let Ok(()) = Sys::clock_gettime(
146            time::constants::CLOCK_REALTIME,
147            Out::from_mut(&mut timespec),
148        ) {}; // TODO handle error
149        let tid = (timespec.tv_nsec >> 16) as u16;
150
151        let packet = Dns {
152            transaction_id: tid,
153            flags: 0x0100,
154            queries: vec![DnsQuery {
155                name,
156                q_type: 0x000C,
157                q_class: 0x0001,
158            }],
159            answers: vec![],
160        };
161
162        let packet_data = packet.compile();
163        let packet_data_len = packet_data.len();
164        let packet_data_box = packet_data.into_boxed_slice();
165        let packet_data_ptr = Box::into_raw(packet_data_box).cast::<c_void>();
166
167        let dest = sockaddr_in {
168            sin_family: AF_INET as u16,
169            sin_port: htons(53),
170            sin_addr: in_addr { s_addr: dns_addr },
171            ..Default::default()
172        };
173
174        let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
175
176        let sock = unsafe {
177            let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
178            if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
179                return Err(EIO);
180            }
181            sock
182        };
183
184        unsafe {
185            if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
186                return Err(EIO);
187            }
188        }
189
190        unsafe {
191            drop(Box::from_raw(packet_data_ptr));
192        }
193
194        let i = mem::size_of::<sockaddr_in>() as socklen_t;
195        let mut buf = [0u8; 65536];
196        let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
197
198        let count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
199        if count < 0 {
200            return Err(EIO);
201        }
202
203        match Dns::parse(&buf[..count as usize]) {
204            Ok(response) => {
205                let names = response
206                    .answers
207                    .into_iter()
208                    .filter_map(|answer| {
209                        if answer.a_type == 0x000C && answer.a_class == 0x0001 {
210                            // answer.data is encoded kinda weird.
211                            // Basically length-prefixed strings for each
212                            // subsection of the domain.
213                            // We need to parse this to insert periods where
214                            // they belong (ie at the end of each string)
215                            Some(parse_revdns_answer(&answer.data))
216                        } else {
217                            None
218                        }
219                    })
220                    .collect();
221                Ok(names)
222            }
223            Err(_err) => Err(EINVAL),
224        }
225    } else {
226        Err(EINVAL)
227    }
228}
229
230fn parse_revdns_answer(data: &[u8]) -> Vec<u8> {
231    if data.is_empty() || data[0] == 0 {
232        return vec![0];
233    }
234
235    let mut cursor = 0;
236    let mut index = 0;
237    // First byte is a length; discard
238    let mut output = data[1..].to_vec();
239    let length = data.len() - 1;
240    while index < length {
241        let offset = data[index] as usize;
242        // CVE-2024-21342
243        if offset > length {
244            return vec![0];
245        }
246        index = cursor + offset + 1;
247        // First byte was skipped so index is one less
248        output[index - 1] = b'.';
249        cursor = index;
250    }
251    // Response is NUL terminated so we must preserve that for C
252    match output.last_mut() {
253        Some(nul) => *nul = b'\0',
254        // XXX: Likely unreachable
255        None => {
256            debug_assert!(output.is_empty());
257            output = vec![0];
258        }
259    }
260    output
261}
262
263pub fn parse_ipv4_string(ip_string: &str) -> Option<u32> {
264    let dns_vec: Vec<u8> = ip_string
265        .trim()
266        .split('.')
267        .map(|octet| octet.parse::<u8>().unwrap_or(0))
268        .collect();
269
270    if dns_vec.len() != 4 {
271        return None;
272    }
273
274    let mut dns_arr = [0u8; 4];
275
276    for (i, octet) in dns_vec.iter().enumerate() {
277        dns_arr[i] = *octet;
278    }
279
280    Some(u32::from_ne_bytes(dns_arr))
281}
282
283#[cfg(test)]
284mod tests {
285    use alloc::str;
286    use core::ffi::CStr;
287
288    use super::parse_revdns_answer;
289
290    // Actual response from a query
291    const DNS_GOOGLE: &[u8] = &[3, 100, 110, 115, 6, 103, 111, 111, 103, 108, 101, 0];
292    const EXPECTED_DNS_GOOGLE: &str = "dns.google\0";
293    const EXPECTED_DNS_GOOGLE_RT: &str = "dns.google";
294
295    // Fake response that has numbers within the name (e.g. like CDNs)
296    const FAKE_WITH_NUMS: &[u8] = &[
297        14, 102, 97, 107, 101, 45, 49, 48, 48, 45, 50, 45, 51, 45, 52, 8, 102, 111, 111, 98, 97,
298        114, 50, 52, 4, 102, 97, 107, 101, 0,
299    ];
300    const EXPECTED_FAKE_RESPONSE: &str = "fake-100-2-3-4.foobar24.fake\0";
301    const EXPECTED_FAKE_RESPONSE_RT: &str = "fake-100-2-3-4.foobar24.fake";
302
303    const EMPTY_RESPONSE: &[u8] = &[0];
304    const EXPECTED_EMPTY_RESPONSE: &str = "\0";
305    const EXPECTED_EMPTY_RESPONSE_RT: &str = "";
306
307    #[test]
308    fn dns_response_dns_google() {
309        let response = parse_revdns_answer(DNS_GOOGLE);
310        assert_eq!(
311            0,
312            *response.last().unwrap(),
313            "Response should end with a NUL byte"
314        );
315
316        let response_str = str::from_utf8(&response)
317            .expect("Response is valid UTF-8; parsing shouldn't change that");
318        assert_eq!(EXPECTED_DNS_GOOGLE, response_str);
319
320        let response_cstr = CStr::from_bytes_with_nul(&response)
321            .expect("Parsed response should have only one NUL byte");
322        let response_cstr_str = response_cstr
323            .to_str()
324            .expect("Valid UTF-8 bytes to CStr to Rust str should be valid");
325        assert_eq!(EXPECTED_DNS_GOOGLE_RT, response_cstr_str);
326    }
327
328    #[test]
329    fn dns_response_fake_with_nums() {
330        let response = parse_revdns_answer(FAKE_WITH_NUMS);
331        assert_eq!(
332            0,
333            *response.last().unwrap(),
334            "Response should end with a NUL byte"
335        );
336
337        let response_str = str::from_utf8(&response)
338            .expect("Response is valid UTF-8; parsing shouldn't change that");
339        assert_eq!(EXPECTED_FAKE_RESPONSE, response_str);
340
341        let response_cstr = CStr::from_bytes_with_nul(&response)
342            .expect("Parsed response should have only one NUL byte");
343        let response_cstr_str = response_cstr
344            .to_str()
345            .expect("Valid UTF-8 bytes to CStr to Rust str should be valid");
346        assert_eq!(EXPECTED_FAKE_RESPONSE_RT, response_cstr_str);
347    }
348
349    #[test]
350    fn dns_response_empty() {
351        let response = parse_revdns_answer(EMPTY_RESPONSE);
352        assert_eq!(
353            0,
354            *response.last().unwrap(),
355            "Response should end with a NUL byte"
356        );
357
358        let response_str = str::from_utf8(&response)
359            .expect("Response is valid UTF-8; parsing shouldn't change that");
360        assert_eq!(EXPECTED_EMPTY_RESPONSE, response_str);
361
362        let response_cstr = CStr::from_bytes_with_nul(&response)
363            .expect("Parsed response should have only one NUL byte");
364        let response_cstr_str = response_cstr
365            .to_str()
366            .expect("Valid UTF-8 bytes to CStr to Rust str should be valid");
367        assert_eq!(EXPECTED_EMPTY_RESPONSE_RT, response_cstr_str);
368    }
369}