systemd-resolved-rs 0.1.1

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

    #[test]
    fn servfail_retries_once_before_lowering_features() {
        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 opt = edns::inspect_opt(query)
                    .expect("query OPT")
                    .expect("EDNS query");
                let response = match exchange_index {
                    0 | 1 => {
                        assert!(opt.dnssec_ok());
                        let response = error_response(query, RCODE_SERVFAIL);
                        edns::add_test_response_opt(&response, 0, true)
                            .expect("SERVFAIL response")
                    }
                    2 => {
                        assert!(!opt.dnssec_ok());
                        let response = local_response(
                            query,
                            &[LocalRecord::A(Ipv4Addr::new(192, 0, 2, 72))],
                            30,
                        )
                        .expect("successful response");
                        edns::add_test_response_opt(&response, 0, false)
                            .expect("successful EDNS 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,
            attempts: 1,
            query_timeout: Duration::from_millis(500),
            read_etc_hosts: false,
            read_static_records: false,
            dnssec: ValidationMode::AllowDowngrade,
            ..Config::default()
        });
        let query = make_query("servfail.example", TYPE_A, 0x7400).expect("client query");
        let response = resolver
            .query(&query, QueryMode::Full)
            .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, 72))]
        );
        server.join().expect("mock DNS thread");
    }

    #[test]
    fn ede_not_ready_retries_without_feature_downgrade() {
        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..2 {
                let mut buffer = [0; 2048];
                let (length, peer) = socket.recv_from(&mut buffer).expect("mock DNS query");
                let query = &buffer[..length];
                let opt = edns::inspect_opt(query)
                    .expect("query OPT")
                    .expect("DNSSEC query");
                assert!(opt.dnssec_ok());
                let response = if exchange_index == 0 {
                    add_ede_opt(&error_response(query, RCODE_SERVFAIL), true, EDE_NOT_READY)
                } else {
                    let response = local_response(
                        query,
                        &[LocalRecord::A(Ipv4Addr::new(192, 0, 2, 73))],
                        30,
                    )
                    .expect("successful response");
                    edns::add_test_response_opt(&response, 0, true)
                        .expect("successful EDNS response")
                };
                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("not-ready.example", TYPE_A, 0x7401).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, 73))]
        );
        assert_eq!(budget.attempts(), 2);
        server.join().expect("mock DNS thread");
    }

    #[test]
    fn ede_not_ready_retries_are_rate_limited() {
        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 || {
            let started = Instant::now();
            loop {
                let mut buffer = [0; 2048];
                let (length, peer) = socket.recv_from(&mut buffer).expect("mock DNS query");
                let query = &buffer[..length];
                let response = if started.elapsed() < Duration::from_millis(25) {
                    add_ede_opt(&error_response(query, RCODE_SERVFAIL), true, EDE_NOT_READY)
                } else {
                    let response = local_response(
                        query,
                        &[LocalRecord::A(Ipv4Addr::new(192, 0, 2, 74))],
                        30,
                    )
                    .expect("successful response");
                    edns::add_test_response_opt(&response, 0, true)
                        .expect("successful EDNS response")
                };
                socket
                    .send_to(&response, peer)
                    .expect("mock DNS response");
                if started.elapsed() >= Duration::from_millis(25) {
                    break;
                }
            }
        });

        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("temporarily-not-ready.example", TYPE_A, 0x7403)
            .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 after transient error");
        let records = extract_address_records(&response, Some(2)).expect("address records");
        assert_eq!(
            records.addresses,
            vec![IpAddr::V4(Ipv4Addr::new(192, 0, 2, 74))]
        );
        assert_eq!(budget.attempts(), 2);
        server.join().expect("mock DNS thread");
    }

    #[test]
    fn detailed_servfail_propagates_ede_without_degrading_edns_features() {
        const EDE_DNSSEC_BOGUS: u16 = 6;

        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 || {
            let mut buffer = [0; 2048];
            let (length, peer) = socket.recv_from(&mut buffer).expect("mock DNS query");
            let query = &buffer[..length];
            let opt = edns::inspect_opt(query)
                .expect("query OPT")
                .expect("DNSSEC query");
            assert!(opt.dnssec_ok());
            let response = add_ede_opt(
                &error_response(query, RCODE_SERVFAIL),
                true,
                EDE_DNSSEC_BOGUS,
            );
            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("ede-servfail.example", TYPE_A, 0x7402).expect("client query");
        let error = resolver
            .query(&query, QueryMode::Full)
            .expect_err("SERVFAIL with EDE must be reported");
        assert!(matches!(
            error,
            ResolveError::DnssecValidationFailed {
                ref result,
                extended_dns_error_code: Some(EDE_DNSSEC_BOGUS),
                extended_dns_error_message: None,
                ..
            } if result == "upstream-failure"
        ));
        let key = ServerKey::new(ScopeKind::Global, server_address);
        let mut states = resolver.states();
        let state = states.get_mut(&key).expect("server state");
        assert_eq!(
            state
                .features
                .possible_level(
                    FeatureLevel::DnssecOk,
                    FeatureLevel::Udp,
                    Instant::now()
                ),
            FeatureLevel::DnssecOk
        );
        drop(states);
        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
    }

    fn add_ede_opt(packet: &[u8], dnssec_ok: bool, ede_code: u16) -> Vec<u8> {
        let mut response = packet.to_vec();
        response[10..12].copy_from_slice(&1_u16.to_be_bytes());
        response.push(0);
        response.extend_from_slice(&41_u16.to_be_bytes());
        response.extend_from_slice(&edns::DEFAULT_UDP_PAYLOAD_SIZE.to_be_bytes());
        response.extend_from_slice(&0_u16.to_be_bytes());
        response.extend_from_slice(&(if dnssec_ok { 0x8000_u16 } else { 0 }).to_be_bytes());
        response.extend_from_slice(&6_u16.to_be_bytes());
        response.extend_from_slice(&15_u16.to_be_bytes());
        response.extend_from_slice(&2_u16.to_be_bytes());
        response.extend_from_slice(&ede_code.to_be_bytes());
        response
    }
}