systemd-resolved-rs 0.1.1

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

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

        let server = thread::spawn(move || {
            for attempt 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("EDNS query");
                if attempt == 0 {
                    assert!(opt.dnssec_ok());
                } else {
                    assert!(!opt.dnssec_ok());
                }

                let response = unsigned_root_response(
                    query,
                    Ipv4Addr::new(192, 0, 2, 110),
                    attempt == 0,
                );
                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_secs(1),
            read_etc_hosts: false,
            read_static_records: false,
            dnssec: ValidationMode::AllowDowngrade,
            ..Config::default()
        });
        let query = make_query(".", TYPE_A, 0x7204).expect("root query");
        let response = resolver
            .query(&query, QueryMode::Full)
            .expect("downgraded response");
        let records = extract_address_records(&response, Some(2)).expect("address records");
        assert_eq!(
            records.addresses,
            vec![IpAddr::V4(Ipv4Addr::new(192, 0, 2, 110))]
        );

        let mut states = resolver.states();
        let state = states
            .get_mut(&ServerKey::new(ScopeKind::Global, server_address))
            .expect("server state");
        assert!(state.missing_root_rrsig);
        assert_eq!(
            state
                .features
                .possible_level(
                    FeatureLevel::DnssecOk,
                    FeatureLevel::Udp,
                    Instant::now()
                ),
            FeatureLevel::Edns0
        );
        drop(states);
        server.join().expect("mock DNS thread");
    }

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

        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 =
                unsigned_root_response(query, Ipv4Addr::new(192, 0, 2, 111), true);
            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_secs(1),
            read_etc_hosts: false,
            read_static_records: false,
            dnssec: ValidationMode::Yes,
            ..Config::default()
        });
        let query = make_query(".", TYPE_A, 0x7205).expect("root query");
        let error = resolver
            .query(&query, QueryMode::Full)
            .expect_err("strict DNSSEC must reject missing RRSIG");
        assert!(matches!(error, ResolveError::Protocol(_)));
        assert!(resolver
            .states()
            .get(&ServerKey::new(ScopeKind::Global, server_address))
            .expect("server state")
            .missing_root_rrsig);
        server.join().expect("mock DNS thread");
    }

    fn unsigned_root_response(
        query: &[u8],
        address: Ipv4Addr,
        dnssec_ok: bool,
    ) -> Vec<u8> {
        let response = local_response(query, &[LocalRecord::A(address)], 30)
            .expect("unsigned root response");
        edns::add_test_response_opt(&response, 0, dnssec_ok).expect("response OPT")
    }
}