rustd-resolved 0.2.3

Native DNS resolver and name-service daemon for RustD
#[cfg(test)]
mod test_21_networkd_link_state {
    use super::*;
    use crate::networkd::OperationalState;
    use crate::routing::KernelLinkState;

    fn kernel_link(ifindex: i32) -> KernelLinkState {
        KernelLinkState {
            ifindex,
            ifname: format!("test{ifindex}"),
            flags: 0x0083,
            mtu: 1500,
            operstate: 0,
            has_ipv4_global: true,
            has_ipv4_link_local: false,
            has_ipv6_global: false,
            has_ipv6_link_local: false,
        }
    }

    fn networkd_link(ifindex: i32, operstate: OperationalState) -> NetworkdLinkState {
        let address = "192.0.2.53:53".parse().expect("DNS server");
        NetworkdLinkState {
            ifindex,
            managed: true,
            operstate,
            dns_servers: vec![address],
            dns_server_specs: vec![DnsServerSpec {
                address,
                interface: Some(format!("test{ifindex}")),
                server_name: Some("resolver.example".to_owned()),
            }],
            domains: vec![Domain {
                name: "corp.example".to_owned(),
                route_only: true,
            }],
            default_route: Some(false),
            llmnr: SupportMode::Resolve,
            multicast_dns: SupportMode::No,
            dns_over_tls: Some(TlsMode::Opportunistic),
            dnssec: Some(ValidationMode::No),
            dnssec_negative_trust_anchors: vec!["private.example".to_owned()],
        }
    }

    #[test]
    fn networkd_route_only_link_resolves_without_global_dns_leak() {
        use crate::wire::question_end;
        use std::net::{IpAddr, Ipv4Addr, UdpSocket};
        use std::thread;

        let global = UdpSocket::bind("127.0.0.1:0").expect("bind global DNS server");
        global
            .set_read_timeout(Some(Duration::from_millis(250)))
            .expect("set global timeout");
        let link = UdpSocket::bind("127.0.0.1:0").expect("bind networkd DNS server");
        link.set_read_timeout(Some(Duration::from_secs(2)))
            .expect("set link timeout");
        let link_address = link.local_addr().expect("networkd DNS address");
        let worker = thread::spawn(move || {
            let mut buffer = [0; 4096];
            let (length, peer) = link.recv_from(&mut buffer).expect("receive link query");
            let query = &buffer[..length];
            let end = question_end(query).expect("question end");
            let mut response = query[..end].to_vec();
            response[2..4].copy_from_slice(&0x8180u16.to_be_bytes());
            response[6..8].copy_from_slice(&1u16.to_be_bytes());
            response[8..12].fill(0);
            response.extend_from_slice(&[0xc0, 0x0c]);
            response.extend_from_slice(&TYPE_A.to_be_bytes());
            response.extend_from_slice(&wire::CLASS_IN.to_be_bytes());
            response.extend_from_slice(&60u32.to_be_bytes());
            response.extend_from_slice(&4u16.to_be_bytes());
            response.extend_from_slice(&[192, 0, 2, 121]);
            link.send_to(&response, peer).expect("send link response");
        });

        let ifindex = 7;
        let resolver = Resolver::new(Config {
            upstreams: vec![global.local_addr().expect("global DNS address")],
            fallback_upstreams: Vec::new(),
            query_timeout: Duration::from_secs(1),
            attempts: 1,
            cache: false,
            cache_from_localhost: true,
            read_etc_hosts: false,
            ..Config::default()
        });
        resolver
            .set_link_dns(ifindex, vec![link_address])
            .expect("create link transport state");
        let mut state = networkd_link(ifindex, OperationalState::Routable);
        state.dns_servers = vec![link_address];
        state.dns_server_specs = vec![DnsServerSpec {
            address: link_address,
            interface: None,
            server_name: None,
        }];
        state.dns_over_tls = Some(TlsMode::No);
        resolver
            .sync_networkd_links(vec![state])
            .expect("networkd link state");

        let lookup = resolver
            .lookup_name("host.corp.example", 2)
            .expect("networkd split DNS lookup");
        worker.join().expect("networkd DNS worker");
        assert_eq!(
            lookup.addresses,
            vec![IpAddr::V4(Ipv4Addr::new(192, 0, 2, 121))]
        );
        assert_eq!(lookup.address_ifindices, vec![Some(ifindex)]);
        let mut leaked = [0; 512];
        assert!(
            global.recv_from(&mut leaked).is_err(),
            "route-only networkd query leaked to the global DNS server"
        );
    }

    #[test]
    fn managed_networkd_state_populates_effective_link_and_blocks_mutation() {
        let resolver = Resolver::new(Config::default());
        resolver
            .sync_kernel_links(vec![kernel_link(7)])
            .expect("kernel link state");
        resolver
            .sync_networkd_links(vec![networkd_link(7, OperationalState::Routable)])
            .expect("networkd link state");

        let link = resolver.link(7).expect("link state");
        assert_eq!(link.dns_servers.len(), 1);
        assert_eq!(link.domains.len(), 1);
        assert_eq!(link.default_route, Some(false));
        assert_eq!(link.llmnr, SupportMode::Resolve);
        assert_eq!(link.multicast_dns, SupportMode::No);
        assert_eq!(link.dns_over_tls, TlsMode::Opportunistic);
        assert_eq!(link.dnssec, ValidationMode::No);
        assert_eq!(
            link.dnssec_negative_trust_anchors,
            vec!["private.example".to_owned()]
        );
        let specs = resolver.link_dns_specs(7);
        assert_eq!(specs.len(), 1);
        assert_eq!(specs[0].interface.as_deref(), Some("test7"));
        assert_eq!(specs[0].server_name.as_deref(), Some("resolver.example"));
        assert!(resolver.link_is_managed(7));
        assert_eq!(
            resolver.set_link_dns(7, vec!["198.51.100.53:53".parse().expect("DNS server")]),
            Err(LinkError::ManagedLink(7))
        );
    }

    #[test]
    fn late_kernel_link_applies_already_published_networkd_state() {
        let resolver = Resolver::new(Config::default());
        resolver
            .sync_kernel_links(Vec::new())
            .expect("initial empty kernel state");
        resolver
            .sync_networkd_links(vec![networkd_link(7, OperationalState::Routable)])
            .expect("early networkd link state");
        assert!(resolver.link(7).is_none());

        resolver
            .sync_kernel_links(vec![kernel_link(7)])
            .expect("late kernel link state");

        let link = resolver.link(7).expect("reconciled link state");
        assert_eq!(link.dns_servers, vec!["192.0.2.53:53".parse().unwrap()]);
        assert_eq!(link.domains[0].name, "corp.example");
        assert!(resolver.link_is_managed(7));
    }

    #[test]
    fn networkd_operstate_controls_link_scope_relevance() {
        let resolver = Resolver::new(Config::default());
        resolver
            .sync_kernel_links(vec![kernel_link(7)])
            .expect("kernel link state");
        resolver
            .sync_networkd_links(vec![networkd_link(7, OperationalState::Carrier)])
            .expect("carrier state");
        assert!(!resolver.networkd_link_relevant(7));

        resolver
            .sync_networkd_links(vec![networkd_link(7, OperationalState::Routable)])
            .expect("routable state");
        assert!(resolver.networkd_link_relevant(7));
    }

    #[test]
    fn managed_to_unmanaged_transition_reverts_resolver_state_only() {
        let resolver = Resolver::new(Config {
            dns_over_tls: TlsMode::Yes,
            dnssec: ValidationMode::Yes,
            ..Config::default()
        });
        resolver
            .sync_kernel_links(vec![kernel_link(7)])
            .expect("kernel link state");
        resolver
            .sync_networkd_links(vec![networkd_link(7, OperationalState::Routable)])
            .expect("managed state");

        let mut unmanaged = networkd_link(7, OperationalState::Routable);
        unmanaged.managed = false;
        unmanaged.dns_servers.clear();
        unmanaged.dns_server_specs.clear();
        unmanaged.domains.clear();
        resolver
            .sync_networkd_links(vec![unmanaged])
            .expect("unmanaged state");

        let link = resolver.link(7).expect("kernel link survives");
        assert!(!resolver.link_is_managed(7));
        assert!(link.dns_servers.is_empty());
        assert!(resolver.link_dns_specs(7).is_empty());
        assert!(link.domains.is_empty());
        assert!(link.kernel.is_some());
        assert_eq!(link.dns_over_tls, TlsMode::Yes);
        assert_eq!(link.dnssec, ValidationMode::Yes);
    }

    #[test]
    fn networkd_policy_inherits_manager_defaults_only_when_unset() {
        let resolver = Resolver::new(Config::default());
        resolver
            .sync_kernel_links(vec![kernel_link(7), kernel_link(8)])
            .expect("kernel link state");
        let explicit = networkd_link(7, OperationalState::Routable);
        let mut inherited = networkd_link(8, OperationalState::Routable);
        inherited.dns_over_tls = None;
        inherited.dnssec = None;
        resolver
            .sync_networkd_links(vec![explicit.clone(), inherited.clone()])
            .expect("networkd link state");

        let reloaded = Config {
            dns_over_tls: TlsMode::Yes,
            dnssec: ValidationMode::Yes,
            ..Config::default()
        };
        assert!(resolver.reload_config(reloaded));

        let explicit_link = resolver.link(7).expect("explicit link state");
        assert_eq!(explicit_link.dns_over_tls, TlsMode::Opportunistic);
        assert_eq!(explicit_link.dnssec, ValidationMode::No);
        let inherited_link = resolver.link(8).expect("inherited link state");
        assert_eq!(inherited_link.dns_over_tls, TlsMode::Yes);
        assert_eq!(inherited_link.dnssec, ValidationMode::Yes);
    }
}