systemd-resolved-rs 0.1.1

A compatibility-oriented reimplementation of systemd-resolved
#[cfg(test)]
mod test_17_dns_attempt_budget {
    use super::*;
    use crate::wire::LocalRecord;

    #[test]
    fn upstream_attempt_budget_stops_after_twenty_four_emissions() {
        let mut budget = DnsAttemptBudget::new();
        for expected in 1..=DNS_TRANSACTION_ATTEMPTS_MAX {
            assert!(budget.begin_attempt().is_ok());
            assert_eq!(budget.attempts(), expected);
        }
        let error = budget
            .begin_attempt()
            .expect_err("twenty-fifth DNS emission must be rejected");
        assert_eq!(error.varlink_id(), "io.systemd.Resolve.MaxAttemptsReached");
    }

    #[test]
    fn feature_downgrades_consume_the_shared_attempt_budget() {
        let socket = UdpSocket::bind("127.0.0.1:0").expect("mock DNS bind");
        let server_address = socket.local_addr().expect("mock DNS address");
        socket
            .set_read_timeout(Some(Duration::from_secs(2)))
            .expect("mock DNS timeout");

        let server = thread::spawn(move || {
            for exchange_index in 0..3 {
                let mut buffer = [0; 2048];
                let (length, peer) = socket.recv_from(&mut buffer).expect("mock DNS query");
                let query = &buffer[..length];
                let response = match exchange_index {
                    0 => {
                        let opt = edns::inspect_opt(query)
                            .expect("query OPT")
                            .expect("DNSSEC OPT");
                        assert!(opt.dnssec_ok());
                        let response = error_response(query, RCODE_FORMERR);
                        edns::add_test_response_opt(&response, 0, true)
                            .expect("DNSSEC FORMERR response")
                    }
                    1 => {
                        let opt = edns::inspect_opt(query)
                            .expect("query OPT")
                            .expect("EDNS0 OPT");
                        assert!(!opt.dnssec_ok());
                        let response = error_response(query, RCODE_FORMERR);
                        edns::add_test_response_opt(&response, 0, false)
                            .expect("EDNS0 FORMERR response")
                    }
                    2 => {
                        assert!(edns::inspect_opt(query).expect("plain query").is_none());
                        local_response(
                            query,
                            &[LocalRecord::A(Ipv4Addr::new(192, 0, 2, 70))],
                            30,
                        )
                        .expect("plain DNS response")
                    }
                    _ => unreachable!(),
                };
                socket
                    .send_to(&response, peer)
                    .expect("mock DNS response");
            }
        });

        let resolver = Resolver::new(Config {
            upstreams: vec![server_address],
            fallback_upstreams: Vec::new(),
            cache: false,
            query_timeout: Duration::from_millis(500),
            read_etc_hosts: false,
            read_static_records: false,
            dnssec: ValidationMode::AllowDowngrade,
            ..Config::default()
        });
        let query = make_query("budget.example", TYPE_A, 0x7300).expect("client query");
        let mut budget = DnsAttemptBudget::new();
        let response = resolver
            .exchange_with_features(
                ServerKey::new(ScopeKind::Global, server_address),
                &query,
                &mut budget,
            )
            .expect("resolver response");
        let records = extract_address_records(&response, Some(2)).expect("address records");
        assert_eq!(
            records.addresses,
            vec![IpAddr::V4(Ipv4Addr::new(192, 0, 2, 70))]
        );
        assert_eq!(budget.attempts(), 3);
        server.join().expect("mock DNS thread");
    }

    fn error_response(query: &[u8], rcode: u16) -> Vec<u8> {
        let end = wire::question_end(query).expect("question end");
        let mut response = query[..end].to_vec();
        let query_flags = u16::from_be_bytes([query[2], query[3]]);
        let flags = (query_flags & 0x0100) | 0x8000 | 0x0080 | rcode;
        response[2..4].copy_from_slice(&flags.to_be_bytes());
        response[6..12].fill(0);
        response
    }
}