Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dns: Support IPv6 link local address as DNS nameserver #2105

Merged
merged 1 commit into from Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
185 changes: 155 additions & 30 deletions rust/src/lib/dns.rs
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: Apache-2.0

use std::str::FromStr;

use serde::{Deserialize, Serialize};

use crate::{
Expand Down Expand Up @@ -228,42 +230,50 @@ pub(crate) fn reselect_dns_ifaces(
desired: &NetworkState,
current: &NetworkState,
) -> (String, String) {
(
find_ifaces_in_desire(false, &desired.interfaces)
.or_else(|| {
find_valid_ifaces_for_dns(
false,
&desired.interfaces,
&current.interfaces,
)
})
.unwrap_or_default(),
find_ifaces_in_desire(true, &desired.interfaces)
.or_else(|| {
find_valid_ifaces_for_dns(
true,
&desired.interfaces,
&current.interfaces,
)
})
.unwrap_or_default(),
let ipv4_iface = find_ifaces_in_desire(false, &desired.interfaces)
.or_else(|| {
find_valid_ifaces_for_dns(
false,
&desired.interfaces,
&current.interfaces,
)
})
.unwrap_or_default();

let ipv6_iface = extra_ipv6_link_local_iface_from_dns_srv(
desired
.dns
.config
.as_ref()
.and_then(|c| c.server.as_deref()),
)
.or_else(|| find_ifaces_in_desire(true, &desired.interfaces))
.or_else(|| {
find_valid_ifaces_for_dns(
true,
&desired.interfaces,
&current.interfaces,
)
})
.unwrap_or_default();

(ipv4_iface, ipv6_iface)
}

// Return None if specified interface has IP configuration as None.
fn is_iface_valid_for_dns(is_ipv6: bool, iface: &Interface) -> Option<bool> {
// IP stack is merged with current at this point.
fn is_iface_valid_for_dns(is_ipv6: bool, iface: &Interface) -> bool {
if is_ipv6 {
iface.base_iface().ipv6.as_ref().map(|ip_conf| {
ip_conf.enabled
&& (ip_conf.is_static()
|| (ip_conf.is_auto() && ip_conf.auto_dns == Some(false)))
})
}) == Some(true)
} else {
iface.base_iface().ipv4.as_ref().map(|ip_conf| {
ip_conf.enabled
&& (ip_conf.is_static()
|| (ip_conf.is_auto() && ip_conf.auto_dns == Some(false)))
})
}) == Some(true)
}
}

Expand All @@ -278,7 +288,7 @@ fn current_dns_ifaces_are_still_valid(
if let Some(des_iface) =
desired.interfaces.kernel_ifaces.get(iface_name)
{
if is_iface_valid_for_dns(false, des_iface) == Some(false) {
if !is_iface_valid_for_dns(false, des_iface) {
return false;
}
}
Expand All @@ -289,7 +299,7 @@ fn current_dns_ifaces_are_still_valid(
if let Some(des_iface) =
desired.interfaces.kernel_ifaces.get(iface_name)
{
if is_iface_valid_for_dns(true, des_iface) == Some(false) {
if !is_iface_valid_for_dns(true, des_iface) {
return false;
}
}
Expand All @@ -305,7 +315,7 @@ fn find_ifaces_in_desire(
desired: &Interfaces,
) -> Option<String> {
for (iface_name, iface) in desired.kernel_ifaces.iter() {
if is_iface_valid_for_dns(is_ipv6, iface) == Some(true) {
if is_iface_valid_for_dns(is_ipv6, iface) {
return Some(iface_name.to_string());
}
}
Expand All @@ -322,10 +332,10 @@ fn find_valid_ifaces_for_dns(
.iter()
.chain(current.kernel_ifaces.iter())
{
if is_iface_valid_for_dns(is_ipv6, iface) == Some(true) {
if is_iface_valid_for_dns(is_ipv6, iface) {
let des_iface = desired.kernel_ifaces.get(iface_name);
if let Some(des_iface) = des_iface {
if is_iface_valid_for_dns(is_ipv6, des_iface) != Some(false) {
if is_iface_valid_for_dns(is_ipv6, des_iface) {
return Some(iface_name.to_string());
}
} else {
Expand Down Expand Up @@ -445,7 +455,9 @@ pub(crate) fn purge_dns_config(
}
}

// Only preferred: true will save the searches
// Argument `preferred`: true will save the searches
// Assuming all IPv6 link local address is pointing to specified argument
// `iface_name` iface.
fn _save_dns_to_iface(
is_ipv6: bool,
iface_name: &str,
Expand All @@ -455,7 +467,13 @@ fn _save_dns_to_iface(
current: &NetworkState,
preferred: bool,
) -> Result<(), NmstateError> {
let (servers, searches) = dns_conf;
let (mut servers, searches) = dns_conf;
for srv in servers.as_mut_slice() {
if let Some((ip, _)) = parse_dns_ipv6_link_local_srv(srv)? {
srv.replace_range(.., ip.to_string().as_str());
}
}

if iface_name.is_empty() {
let e = NmstateError::new(
ErrorKind::InvalidArgument,
Expand Down Expand Up @@ -538,3 +556,110 @@ fn _save_dns_to_iface(

Ok(())
}

// * Specified interface is valid for hold IPv6 DNS config.
// * Cannot have more than one IPv6 link-local DNS interface.
pub(crate) fn validate_ipv6_link_local_address_dns_srv(
desired: &NetworkState,
current: &NetworkState,
) -> Result<(), NmstateError> {
let mut iface_names = Vec::new();
if let Some(srvs) =
desired.dns.config.as_ref().and_then(|c| c.server.as_ref())
{
for srv in srvs {
if let Some((_, iface_name)) = parse_dns_ipv6_link_local_srv(srv)? {
let iface = if let Some(iface) =
desired.interfaces.kernel_ifaces.get(iface_name).or_else(
|| current.interfaces.kernel_ifaces.get(iface_name),
) {
iface
} else {
return Err(NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"Desired IPv6 link local DNS server {} is \
pointing to interface {} which does not exist.",
srv, iface_name
),
));
};
if is_iface_valid_for_dns(true, iface) {
iface_names.push(iface.name());
} else {
return Err(NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"Interface {} has IPv6 disabled, \
hence cannot hold desired IPv6 link local \
DNS server {}",
iface_name, srv
),
));
}
}
}
}
if iface_names.len() >= 2 {
return Err(NmstateError::new(
ErrorKind::NotImplementedError,
format!(
"Only support IPv6 link local DNS name server(s) \
pointing to a single interface, but got '{}'",
iface_names.join(" ")
),
));
}

Ok(())
}

fn parse_dns_ipv6_link_local_srv(
srv: &str,
) -> Result<Option<(std::net::Ipv6Addr, &str)>, NmstateError> {
if srv.contains('%') {
let splits: Vec<&str> = srv.split('%').collect();
if splits.len() == 2 {
match std::net::Ipv6Addr::from_str(splits[0]) {
Ok(ip) => return Ok(Some((ip, splits[1]))),
Err(_) => {
return Err(NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"Invalid IPv6 address in {}, only IPv6 link local \
address is allowed to have '%' character in DNS \
name server, the correct format should be \
'fe80::deef:1%eth1'",
srv
),
));
}
}
} else {
return Err(NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"Invalid DNS server {}, the IPv6 \
link local DNS server should be in the format like \
'fe80::deef:1%eth1'",
srv
),
));
}
}
Ok(None)
}

fn extra_ipv6_link_local_iface_from_dns_srv(
srvs: Option<&[String]>,
) -> Option<String> {
if let Some(srvs) = srvs {
for srv in srvs {
let splits: Vec<&str> = srv.split('%').collect();
if splits.len() == 2 && !splits[1].is_empty() {
return Some(splits[1].to_string());
}
}
}
None
}
4 changes: 3 additions & 1 deletion rust/src/lib/net_state.rs
Expand Up @@ -8,7 +8,7 @@ use serde::{Deserialize, Deserializer, Serialize};
use crate::{
dns::{
get_cur_dns_ifaces, is_dns_changed, purge_dns_config,
reselect_dns_ifaces,
reselect_dns_ifaces, validate_ipv6_link_local_address_dns_srv,
},
DnsState, ErrorKind, HostNameState, Interface, InterfaceType, Interfaces,
NmstateError, OvsDbGlobalConfig, RouteRules, Routes,
Expand Down Expand Up @@ -531,6 +531,8 @@ impl NetworkState {
let mut self_clone = self.clone();
self_clone.dns.merge_current(&current.dns);

validate_ipv6_link_local_address_dns_srv(&self_clone, current)?;

if is_dns_changed(&self_clone, current) {
let (v4_iface_name, v6_iface_name) =
reselect_dns_ifaces(&self_clone, current);
Expand Down
56 changes: 51 additions & 5 deletions rust/src/lib/nm/query/dns.rs
@@ -1,15 +1,42 @@
// SPDX-License-Identifier: Apache-2.0

use std::str::FromStr;

use super::super::{
error::nm_error_to_nmstate,
nm_dbus::{NmApi, NmSettingIp},
nm_dbus::{NmApi, NmDnsEntry, NmSettingIp},
};

use crate::{
ip::is_ipv6_unicast_link_local, DnsClientState, DnsState, Interfaces,
NmstateError,
};

use crate::{DnsClientState, DnsState, Interfaces, NmstateError};
pub(crate) fn nm_dns_to_nmstate(
iface_name: &str,
nm_ip_setting: &NmSettingIp,
) -> DnsClientState {
let mut servers = Vec::new();
if let Some(srvs) = nm_ip_setting.dns.as_ref() {
for srv in srvs {
if let Ok(ip) = std::net::Ipv6Addr::from_str(srv.as_str()) {
if is_ipv6_unicast_link_local(&ip) {
servers.push(format!("{}%{}", srv, iface_name));
} else {
servers.push(srv.to_string());
}
} else {
servers.push(srv.to_string());
}
}
}

pub(crate) fn nm_dns_to_nmstate(nm_ip_setting: &NmSettingIp) -> DnsClientState {
DnsClientState {
server: nm_ip_setting.dns.clone(),
server: if nm_ip_setting.dns.is_none() {
None
} else {
Some(servers)
},
search: nm_ip_setting.dns_search.clone(),
priority: nm_ip_setting.dns_priority,
}
Expand All @@ -26,7 +53,7 @@ pub(crate) fn retrieve_dns_info(
let mut running_srvs: Vec<String> = Vec::new();
let mut running_schs: Vec<String> = Vec::new();
for nm_dns_entry in nm_dns_entires {
running_srvs.extend_from_slice(nm_dns_entry.name_servers.as_slice());
running_srvs.extend(nm_dns_srvs_to_nmstate(&nm_dns_entry));
running_schs.extend_from_slice(nm_dns_entry.domains.as_slice());
}

Expand Down Expand Up @@ -76,3 +103,22 @@ pub(crate) fn retrieve_dns_info(
}),
})
}

fn nm_dns_srvs_to_nmstate(nm_dns_entry: &NmDnsEntry) -> Vec<String> {
let mut srvs = Vec::new();
for srv in nm_dns_entry.name_servers.as_slice() {
if let Ok(ip) = std::net::Ipv6Addr::from_str(srv.as_str()) {
if is_ipv6_unicast_link_local(&ip)
&& !nm_dns_entry.interface.is_empty()
{
srvs.push(format!("{}%{}", srv, nm_dns_entry.interface));
continue;
} else {
srvs.push(srv.to_string());
}
} else {
srvs.push(srv.to_string());
}
}
srvs
}
5 changes: 3 additions & 2 deletions rust/src/lib/nm/query/ip.rs
Expand Up @@ -51,7 +51,7 @@ pub(crate) fn nm_ip_setting_to_nmstate4(
"auto_table_id",
"auto_route_metric",
],
dns: Some(nm_dns_to_nmstate(nm_ip_setting)),
dns: Some(nm_dns_to_nmstate("", nm_ip_setting)),
dhcp_client_id: nm_dhcp_client_id_to_nmstate(nm_ip_setting),
auto_route_metric: nm_ip_setting.route_metric.map(|i| i as u32),
..Default::default()
Expand All @@ -62,6 +62,7 @@ pub(crate) fn nm_ip_setting_to_nmstate4(
}

pub(crate) fn nm_ip_setting_to_nmstate6(
iface_name: &str,
nm_ip_setting: &NmSettingIp,
) -> InterfaceIpv6 {
if let Some(nm_ip_method) = &nm_ip_setting.method {
Expand Down Expand Up @@ -97,7 +98,7 @@ pub(crate) fn nm_ip_setting_to_nmstate6(
"addr_gen_mode",
"auto_route_metric",
],
dns: Some(nm_dns_to_nmstate(nm_ip_setting)),
dns: Some(nm_dns_to_nmstate(iface_name, nm_ip_setting)),
dhcp_duid: nm_dhcp_duid_to_nmstate(nm_ip_setting),
addr_gen_mode: {
if enabled {
Expand Down
5 changes: 4 additions & 1 deletion rust/src/lib/nm/show.rs
Expand Up @@ -218,7 +218,10 @@ fn nm_conn_to_base_iface(
) -> Option<BaseInterface> {
if let Some(iface_name) = nm_conn.iface_name() {
let ipv4 = nm_conn.ipv4.as_ref().map(nm_ip_setting_to_nmstate4);
let ipv6 = nm_conn.ipv6.as_ref().map(nm_ip_setting_to_nmstate6);
let ipv6 = nm_conn
.ipv6
.as_ref()
.map(|nm_ip_set| nm_ip_setting_to_nmstate6(iface_name, nm_ip_set));

let mut base_iface = BaseInterface::new();
base_iface.name = iface_name.to_string();
Expand Down