#[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 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);
}
}