systemd-resolved-rs 0.1.1

A compatibility-oriented reimplementation of systemd-resolved
impl Resolver {
    fn lookup_name_exact(
        &self,
        name: &str,
        types: &[u16],
        ifindex: Option<i32>,
        request_flags: u64,
    ) -> Result<NameLookup, ResolveError> {
        let unicast_name = crate::idna_name::to_ascii(name).unwrap_or_else(|_| name.to_owned());
        let hook_types = types
            .iter()
            .copied()
            .filter(|rr_type| !self.config().refuse_record_types.contains(rr_type))
            .collect::<Vec<_>>();
        let (grouped_hook_checked, grouped_hook_response) = self
            .grouped_hook_record_response_dual(
                name,
                &unicast_name,
                &hook_types,
                ifindex,
                request_flags,
            )?;
        if let Some((response, response_flags, response_ifindex)) = grouped_hook_response {
            return self.lookup_name_from_grouped_hook(
                name,
                &hook_types,
                response,
                response_flags,
                response_ifindex,
            );
        }
        let outcomes = if types.len() > 1 {
            let cancellation = crate::query_cancel::current();
            thread::scope(|thread_scope| {
                let (sender, receiver) = mpsc::channel();
                for (index, &rr_type) in types.iter().enumerate() {
                    let sender = sender.clone();
                    let unicast_name = &unicast_name;
                    let cancellation = cancellation.clone();
                    thread_scope.spawn(move || {
                        let result = crate::query_cancel::with_optional(cancellation, || {
                            if grouped_hook_checked {
                                self.query_following_redirects_dual_after_grouped_hook(
                                    name,
                                    &unicast_name,
                                    wire::CLASS_IN,
                                    rr_type,
                                    ifindex,
                                    request_flags,
                                )
                            } else {
                                self.query_following_redirects_dual(
                                    name,
                                    &unicast_name,
                                    wire::CLASS_IN,
                                    rr_type,
                                    ifindex,
                                    request_flags,
                                )
                            }
                        });
                        let _ = sender.send((index, rr_type, result));
                    });
                }
                drop(sender);

                let mut outcomes: Vec<_> = receiver.into_iter().collect();
                outcomes.sort_by_key(|(index, _, _)| *index);
                outcomes
                    .into_iter()
                    .map(|(_, rr_type, result)| (rr_type, result))
                    .collect::<Vec<_>>()
            })
        } else {
            types
                .iter()
                .copied()
                .map(|rr_type| {
                    (
                        rr_type,
                        self.query_following_redirects_dual(
                            name,
                            &unicast_name,
                            wire::CLASS_IN,
                            rr_type,
                            ifindex,
                            request_flags,
                        ),
                    )
                })
                .collect()
        };

        let mut addresses = Vec::new();
        let mut address_ifindices = Vec::new();
        let mut canonical_name = None;
        let mut last_error = None;
        let mut flags = None;
        for (rr_type, result) in outcomes {
            match result {
                Ok((response, followed_name, response_flags, response_ifindex)) => {
                    flags = Some(merge_parallel_response_flags(flags, response_flags));
                    let response_family = match rr_type {
                        TYPE_A => Some(2),
                        TYPE_AAAA => Some(10),
                        _ => None,
                    };
                    let records = extract_address_records(&response, response_family)?;
                    if !records.addresses.is_empty() && canonical_name.is_none() {
                        canonical_name = Some(if records.canonical_name.is_empty() {
                            followed_name
                        } else {
                            records.canonical_name
                        });
                    }
                    for address in records.addresses {
                        if !addresses.contains(&address) {
                            addresses.push(address);
                            address_ifindices.push(response_ifindex);
                        }
                    }
                }
                Err(error) => {
                    last_error = Some(error)
                }
            }
        }
        if addresses.is_empty() {
            return Err(last_error.unwrap_or(ResolveError::NoSuchResourceRecord));
        }
        Ok(NameLookup {
            addresses,
            address_ifindices,
            canonical_name: canonical_name.unwrap_or_else(|| name.trim_end_matches('.').to_owned()),
            flags: flags.unwrap_or(0),
        })
    }

    fn name_has_pre_hook_source(
        &self,
        name: &str,
        types: &[u16],
        request_flags: u64,
    ) -> Result<bool, ResolveError> {
        if request_flags & crate::dbus_resolve1_abi::flags::SD_RESOLVED_NO_SYNTHESIZE != 0 {
            return Ok(false);
        }
        let config = self.config();
        let queries = types
            .iter()
            .map(|rr_type| make_query_with_class(name, *rr_type, wire::CLASS_IN, 0))
            .collect::<Result<Vec<_>, _>>()?;
        for query in &queries {
            if crate::static_records::answer(config.read_static_records, query)?.is_some() {
                return Ok(true);
            }
        }
        let hosts = self.hosts();
        for query in &queries {
            if hosts.lookup(&first_question(query)?).is_some() {
                return Ok(true);
            }
        }
        Ok(dns_name_dont_resolve(name))
    }

    pub(crate) fn grouped_hook_record_response_dual(
        &self,
        name: &str,
        unicast_name: &str,
        types: &[u16],
        ifindex: Option<i32>,
        request_flags: u64,
    ) -> Result<(bool, Option<(Vec<u8>, u64, Option<i32>)>), ResolveError> {
        if types.len() < 2 || self.name_has_pre_hook_source(name, types, request_flags)? {
            return Ok((false, None));
        }
        let id = self.transaction_id();
        let utf8_queries = types
            .iter()
            .map(|rr_type| make_query_with_class(name, *rr_type, wire::CLASS_IN, id))
            .collect::<Result<Vec<_>, _>>()?;
        let idna_queries = types
            .iter()
            .map(|rr_type| make_query_with_class(unicast_name, *rr_type, wire::CLASS_IN, id))
            .collect::<Result<Vec<_>, _>>()?;
        let utf8_refs = utf8_queries
            .iter()
            .map(Vec::as_slice)
            .collect::<Vec<_>>();
        let idna_refs = idna_queries
            .iter()
            .map(Vec::as_slice)
            .collect::<Vec<_>>();
        let response = crate::hook::resolve_grouped(
            &utf8_queries[0],
            &idna_refs,
            &utf8_refs,
            Duration::from_secs(30),
        );
        crate::query_cancel::check()?;
        let Some(mut response) = response else {
            return Ok((true, None));
        };
        wire::apply_query_validation_flags(&utf8_queries[0], &mut response)?;
        Ok((
            true,
            Some((
                response,
                hook_response_flags(request_flags, &utf8_queries[0]),
                ifindex.filter(|value| *value > 0),
            )),
        ))
    }

    fn lookup_name_from_grouped_hook(
        &self,
        name: &str,
        types: &[u16],
        response: Vec<u8>,
        response_flags: u64,
        response_ifindex: Option<i32>,
    ) -> Result<NameLookup, ResolveError> {
        let (rcode, extended_dns_error_code, extended_dns_error_message) =
            response_full_rcode(&response)?;
        if rcode != 0 {
            return Err(ResolveError::DnsError {
                rcode,
                query: name.to_owned(),
                extended_dns_error_code,
                extended_dns_error_message,
            });
        }

        let mut addresses = Vec::new();
        let mut canonical_name = None;
        for rr_type in types {
            let family = match *rr_type {
                TYPE_A => Some(2),
                TYPE_AAAA => Some(10),
                _ => None,
            };
            let records = extract_address_records(&response, family)?;
            if !records.addresses.is_empty() && canonical_name.is_none() {
                canonical_name = Some(if records.canonical_name.is_empty() {
                    name.trim_end_matches('.').to_owned()
                } else {
                    records.canonical_name
                });
            }
            for address in records.addresses {
                if !addresses.contains(&address) {
                    addresses.push(address);
                }
            }
        }
        if addresses.is_empty() {
            return Err(ResolveError::NoSuchResourceRecord);
        }
        let address_ifindices = vec![response_ifindex; addresses.len()];
        Ok(NameLookup {
            addresses,
            address_ifindices,
            canonical_name: canonical_name.unwrap_or_else(|| name.trim_end_matches('.').to_owned()),
            flags: response_flags,
        })
    }
}