431 lines
15 KiB
Diff
431 lines
15 KiB
Diff
From 1577e1073593d19cdaa7de2eadfc893978cab255 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= <ihuguet@riseup.net>
|
|
Date: Fri, 18 Jul 2025 14:22:41 +0200
|
|
Subject: [PATCH 1/5] Fix interface state detection when NM device is
|
|
"unavailable"
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
A device that is not ready to connect may have the NetworkManager's state
|
|
"unavailable" in some circumstances. Consider this as `state: down`, as
|
|
we do for the "disconnected" state.
|
|
|
|
Fixes https://github.com/nmstate/nmstate/issues/2798
|
|
|
|
Signed-off-by: Íñigo Huguet <ihuguet@riseup.net>
|
|
---
|
|
rust/src/lib/nm/show.rs | 4 +++-
|
|
1 file changed, 3 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/rust/src/lib/nm/show.rs b/rust/src/lib/nm/show.rs
|
|
index 78ff66a1..d4dd575f 100644
|
|
--- a/rust/src/lib/nm/show.rs
|
|
+++ b/rust/src/lib/nm/show.rs
|
|
@@ -285,7 +285,9 @@ fn nm_dev_to_nm_iface(nm_dev: &NmDevice) -> Option<Interface> {
|
|
base_iface.state = InterfaceState::Ignore;
|
|
}
|
|
}
|
|
- NmDeviceState::Disconnected => base_iface.state = InterfaceState::Down,
|
|
+ NmDeviceState::Disconnected | NmDeviceState::Unavailable => {
|
|
+ base_iface.state = InterfaceState::Down
|
|
+ }
|
|
_ => base_iface.state = InterfaceState::Up,
|
|
}
|
|
base_iface.iface_type = nm_dev_iface_type_to_nmstate(nm_dev);
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From f09fc1a5efc589b5740922cdfcc68ba29b94406e Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= <ihuguet@riseup.net>
|
|
Date: Fri, 18 Jul 2025 16:12:18 +0200
|
|
Subject: [PATCH 2/5] Fix interface state detection in kernel mode
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
Any device with administrative state UP was being considered as `state:
|
|
up` in kernel mode. This is not correct because if the operational state
|
|
is not Up, for example because of the cable being disconnected, nobody
|
|
would think that the device is **really** up.
|
|
|
|
Fix it by ignoring the administrative state and consider only the
|
|
operational state.
|
|
|
|
Fixes https://github.com/nmstate/nmstate/issues/2798
|
|
|
|
Signed-off-by: Íñigo Huguet <ihuguet@riseup.net>
|
|
---
|
|
rust/src/lib/nispor/base_iface.rs | 5 ++++-
|
|
1 file changed, 4 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/rust/src/lib/nispor/base_iface.rs b/rust/src/lib/nispor/base_iface.rs
|
|
index f7f3e507..4b04abdb 100644
|
|
--- a/rust/src/lib/nispor/base_iface.rs
|
|
+++ b/rust/src/lib/nispor/base_iface.rs
|
|
@@ -37,8 +37,11 @@ fn np_iface_type_to_nmstate(
|
|
impl From<(&nispor::IfaceState, &[nispor::IfaceFlag])> for InterfaceState {
|
|
fn from(tuple: (&nispor::IfaceState, &[nispor::IfaceFlag])) -> Self {
|
|
let (state, flags) = tuple;
|
|
+ // nispor::IfaceState::Up means operational up.
|
|
+ // Check also the Running flag with, according to [1], means operational
|
|
+ // state Up or Unknown.
|
|
+ // [1] https://www.kernel.org/doc/Documentation/networking/operstates.txt
|
|
if *state == nispor::IfaceState::Up
|
|
- || flags.contains(&nispor::IfaceFlag::Up)
|
|
|| flags.contains(&nispor::IfaceFlag::Running)
|
|
{
|
|
InterfaceState::Up
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 6f7692f9900499d75548eda250487d09f59c357c Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= <ihuguet@riseup.net>
|
|
Date: Fri, 18 Jul 2025 09:38:32 +0200
|
|
Subject: [PATCH 3/5] dns: don't add DNS search and/or options to down NM
|
|
connection
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
When configuring only search and/or options in dns-resolver we add them
|
|
to a NM connection and not to NM's global config, but nmstate may choose
|
|
a down interface, which results on errors at apply time or even on the
|
|
configurations being accepted but stay unused by NetworkManager.
|
|
|
|
Fix that by always checking that the interface is valid for DNS, and by
|
|
adding the requirement of the interface being up.
|
|
|
|
Signed-off-by: Íñigo Huguet <ihuguet@riseup.net>
|
|
---
|
|
rust/src/lib/dns.rs | 3 ++
|
|
rust/src/lib/nm/dns.rs | 120 ++++++++++++++++++++++-------------------
|
|
2 files changed, 67 insertions(+), 56 deletions(-)
|
|
|
|
diff --git a/rust/src/lib/dns.rs b/rust/src/lib/dns.rs
|
|
index 1aae91ff..e3025ed3 100644
|
|
--- a/rust/src/lib/dns.rs
|
|
+++ b/rust/src/lib/dns.rs
|
|
@@ -382,6 +382,9 @@ pub(crate) fn parse_dns_ipv6_link_local_srv(
|
|
impl MergedInterface {
|
|
// IP stack is merged with current at this point.
|
|
pub(crate) fn is_iface_valid_for_dns(&self, is_ipv6: bool) -> bool {
|
|
+ if !self.merged.is_up() {
|
|
+ return false;
|
|
+ }
|
|
if is_ipv6 {
|
|
self.merged.base_iface().ipv6.as_ref().map(|ip_conf| {
|
|
ip_conf.enabled && (ip_conf.is_static() || (ip_conf.is_auto()))
|
|
diff --git a/rust/src/lib/nm/dns.rs b/rust/src/lib/nm/dns.rs
|
|
index a180178a..3c45f78b 100644
|
|
--- a/rust/src/lib/nm/dns.rs
|
|
+++ b/rust/src/lib/nm/dns.rs
|
|
@@ -693,13 +693,14 @@ fn store_dns_search_or_options_to_auto_iface(
|
|
Some(i) => i,
|
|
None => continue,
|
|
};
|
|
- if iface
|
|
- .merged
|
|
- .base_iface()
|
|
- .ipv6
|
|
- .as_ref()
|
|
- .map(|i| i.is_auto())
|
|
- .unwrap_or_default()
|
|
+ if iface.is_iface_valid_for_dns(true)
|
|
+ && iface
|
|
+ .merged
|
|
+ .base_iface()
|
|
+ .ipv6
|
|
+ .as_ref()
|
|
+ .map(|i| i.is_auto())
|
|
+ .unwrap_or_default()
|
|
{
|
|
return set_iface_dns_search_or_option(
|
|
iface,
|
|
@@ -708,13 +709,14 @@ fn store_dns_search_or_options_to_auto_iface(
|
|
true,
|
|
);
|
|
}
|
|
- if iface
|
|
- .merged
|
|
- .base_iface()
|
|
- .ipv4
|
|
- .as_ref()
|
|
- .map(|i| i.is_auto())
|
|
- .unwrap_or_default()
|
|
+ if iface.is_iface_valid_for_dns(false)
|
|
+ && iface
|
|
+ .merged
|
|
+ .base_iface()
|
|
+ .ipv4
|
|
+ .as_ref()
|
|
+ .map(|i| i.is_auto())
|
|
+ .unwrap_or_default()
|
|
{
|
|
return set_iface_dns_search_or_option(
|
|
iface,
|
|
@@ -754,13 +756,14 @@ fn store_dns_search_or_options_to_auto_iface(
|
|
Some(i) => i,
|
|
None => continue,
|
|
};
|
|
- if iface
|
|
- .merged
|
|
- .base_iface()
|
|
- .ipv6
|
|
- .as_ref()
|
|
- .map(|i| i.is_auto())
|
|
- .unwrap_or_default()
|
|
+ if iface.is_iface_valid_for_dns(true)
|
|
+ && iface
|
|
+ .merged
|
|
+ .base_iface()
|
|
+ .ipv6
|
|
+ .as_ref()
|
|
+ .map(|i| i.is_auto())
|
|
+ .unwrap_or_default()
|
|
{
|
|
return set_iface_dns_search_or_option(
|
|
iface,
|
|
@@ -769,13 +772,14 @@ fn store_dns_search_or_options_to_auto_iface(
|
|
true,
|
|
);
|
|
}
|
|
- if iface
|
|
- .merged
|
|
- .base_iface()
|
|
- .ipv4
|
|
- .as_ref()
|
|
- .map(|i| i.is_auto())
|
|
- .unwrap_or_default()
|
|
+ if iface.is_iface_valid_for_dns(false)
|
|
+ && iface
|
|
+ .merged
|
|
+ .base_iface()
|
|
+ .ipv4
|
|
+ .as_ref()
|
|
+ .map(|i| i.is_auto())
|
|
+ .unwrap_or_default()
|
|
{
|
|
return set_iface_dns_search_or_option(
|
|
iface,
|
|
@@ -821,13 +825,14 @@ fn store_dns_search_or_options_to_ip_enabled_iface(
|
|
Some(i) => i,
|
|
None => continue,
|
|
};
|
|
- if iface
|
|
- .merged
|
|
- .base_iface()
|
|
- .ipv6
|
|
- .as_ref()
|
|
- .map(|i| i.enabled)
|
|
- .unwrap_or_default()
|
|
+ if iface.is_iface_valid_for_dns(true)
|
|
+ && iface
|
|
+ .merged
|
|
+ .base_iface()
|
|
+ .ipv6
|
|
+ .as_ref()
|
|
+ .map(|i| i.enabled)
|
|
+ .unwrap_or_default()
|
|
{
|
|
return set_iface_dns_search_or_option(
|
|
iface,
|
|
@@ -836,13 +841,14 @@ fn store_dns_search_or_options_to_ip_enabled_iface(
|
|
true,
|
|
);
|
|
}
|
|
- if iface
|
|
- .merged
|
|
- .base_iface()
|
|
- .ipv4
|
|
- .as_ref()
|
|
- .map(|i| i.enabled)
|
|
- .unwrap_or_default()
|
|
+ if iface.is_iface_valid_for_dns(false)
|
|
+ && iface
|
|
+ .merged
|
|
+ .base_iface()
|
|
+ .ipv4
|
|
+ .as_ref()
|
|
+ .map(|i| i.enabled)
|
|
+ .unwrap_or_default()
|
|
{
|
|
return set_iface_dns_search_or_option(
|
|
iface,
|
|
@@ -882,13 +888,14 @@ fn store_dns_search_or_options_to_ip_enabled_iface(
|
|
Some(i) => i,
|
|
None => continue,
|
|
};
|
|
- if iface
|
|
- .merged
|
|
- .base_iface()
|
|
- .ipv6
|
|
- .as_ref()
|
|
- .map(|i| i.enabled)
|
|
- .unwrap_or_default()
|
|
+ if iface.is_iface_valid_for_dns(true)
|
|
+ && iface
|
|
+ .merged
|
|
+ .base_iface()
|
|
+ .ipv6
|
|
+ .as_ref()
|
|
+ .map(|i| i.enabled)
|
|
+ .unwrap_or_default()
|
|
{
|
|
return set_iface_dns_search_or_option(
|
|
iface,
|
|
@@ -897,13 +904,14 @@ fn store_dns_search_or_options_to_ip_enabled_iface(
|
|
true,
|
|
);
|
|
}
|
|
- if iface
|
|
- .merged
|
|
- .base_iface()
|
|
- .ipv4
|
|
- .as_ref()
|
|
- .map(|i| i.enabled)
|
|
- .unwrap_or_default()
|
|
+ if iface.is_iface_valid_for_dns(false)
|
|
+ && iface
|
|
+ .merged
|
|
+ .base_iface()
|
|
+ .ipv4
|
|
+ .as_ref()
|
|
+ .map(|i| i.enabled)
|
|
+ .unwrap_or_default()
|
|
{
|
|
return set_iface_dns_search_or_option(
|
|
iface,
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From ea08a8dc2b5f6d39f9199e6484a289e2621b0b40 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= <ihuguet@riseup.net>
|
|
Date: Wed, 23 Jul 2025 16:18:27 +0200
|
|
Subject: [PATCH 4/5] nm dns: purge ifaces before saving new config
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
In store_dns_config_to_iface we correctly purge the current DNS
|
|
configurations from NM profiles before saving the new config, ensuring
|
|
that no old leftover configs remain afterwards.
|
|
|
|
Do the same in store_dns_search_or_option_to_iface so the DNS config is
|
|
correctly applied when only DNS searches or options are defined too.
|
|
|
|
Without this fix, the configuration fails in the following scenario:
|
|
- A deactivated NM profile/device contains a dns-search
|
|
- A desired state with only DNS searches or options is applied
|
|
- The new state is saved to an activated interface, but the dns-search
|
|
from the deactivated one is not removed as expected.
|
|
|
|
$ nmcli c show downiface | grep dns-search
|
|
ipv4.dns-search: example.com
|
|
ipv6.dns-search: --
|
|
$ nmcli c show upiface | grep dns-search
|
|
ipv4.dns-search: --
|
|
ipv6.dns-search: --
|
|
$ cat desired.yml
|
|
dns-resolver:
|
|
config:
|
|
search: ["example2.com"]
|
|
server: []
|
|
$ nmstatectl apply desired.yml
|
|
...
|
|
Retrying on: VerificationError: Failed to apply DNS config: desire searches 'example2.com', got 'example.com example2.com'
|
|
...
|
|
$ nmstate apply --no-verify desired.yml
|
|
...
|
|
$ nmcli c show downiface | grep dns-search
|
|
ipv4.dns-search: example.com <-- NOT PURGED!
|
|
ipv6.dns-search: --
|
|
$ nmcli c show upiface | grep dns-search
|
|
ipv4.dns-search: --
|
|
ipv6.dns-search: example2.com
|
|
|
|
Signed-off-by: Íñigo Huguet <ihuguet@riseup.net>
|
|
---
|
|
rust/src/lib/nm/dns.rs | 4 ++++
|
|
1 file changed, 4 insertions(+)
|
|
|
|
diff --git a/rust/src/lib/nm/dns.rs b/rust/src/lib/nm/dns.rs
|
|
index 3c45f78b..7e125579 100644
|
|
--- a/rust/src/lib/nm/dns.rs
|
|
+++ b/rust/src/lib/nm/dns.rs
|
|
@@ -579,6 +579,10 @@ pub(crate) fn store_dns_search_or_option_to_iface(
|
|
let (cur_v4_ifaces, cur_v6_ifaces) =
|
|
get_cur_dns_ifaces(&merged_state.interfaces);
|
|
|
|
+ // First purge existing configs
|
|
+ purge_dns_config(false, &cur_v4_ifaces, merged_state)?;
|
|
+ purge_dns_config(true, &cur_v6_ifaces, merged_state)?;
|
|
+
|
|
// Use current DNS interface if they are desired
|
|
for iface_name in cur_v6_ifaces {
|
|
if let Some(iface) =
|
|
--
|
|
2.49.0
|
|
|
|
|
|
From 66c6aff0d4fe4873515a898f3f58b752b054fdd2 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= <ihuguet@riseup.net>
|
|
Date: Thu, 24 Jul 2025 10:26:49 +0200
|
|
Subject: [PATCH 5/5] test: nm dns: add test for DNS searches in down iface
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
Signed-off-by: Íñigo Huguet <ihuguet@riseup.net>
|
|
---
|
|
tests/integration/nm/dns_test.py | 33 ++++++++++++++++++++++++++++++++
|
|
1 file changed, 33 insertions(+)
|
|
|
|
diff --git a/tests/integration/nm/dns_test.py b/tests/integration/nm/dns_test.py
|
|
index 57a5e1d5..2062b27f 100644
|
|
--- a/tests/integration/nm/dns_test.py
|
|
+++ b/tests/integration/nm/dns_test.py
|
|
@@ -328,3 +328,36 @@ def test_write_both_global_dns_and_iface_dns(eth1_up):
|
|
assert cmdlib.exec_cmd(
|
|
"nmcli -g ipv4.dns c show eth1".split(), check=True
|
|
)[1].strip() == ",".join(TEST_DNS_SRVS)
|
|
+
|
|
+
|
|
+# https://issues.redhat.com/browse/RHEL-102333
|
|
+def test_set_dns_search_only_in_down_iface(auto_eth1, eth2_up):
|
|
+ # Add a DNS search domain to a down interface
|
|
+ cmdlib.exec_cmd("nmcli device down eth2".split(), check=True)
|
|
+ cmdlib.exec_cmd("nmcli c modify eth2 ipv4.method auto".split(), check=True)
|
|
+ cmdlib.exec_cmd(
|
|
+ "nmcli c modify eth2 ipv4.dns-search 'example.com'".split(),
|
|
+ check=True,
|
|
+ )
|
|
+
|
|
+ # Assert that the DNS searches configuration can change correctly
|
|
+ libnmstate.apply(
|
|
+ {
|
|
+ DNS.KEY: {DNS.CONFIG: {DNS.SEARCH: ["example2.com"]}},
|
|
+ }
|
|
+ )
|
|
+
|
|
+ # Assert that the old configuration has been purged from the down interface
|
|
+ # and the new one hasn't been added to it.
|
|
+ _r, search4, _e = cmdlib.exec_cmd(
|
|
+ "nmcli -g ipv4.dns-search c show eth2".split(), check=True
|
|
+ )
|
|
+ _r, search6, _e = cmdlib.exec_cmd(
|
|
+ "nmcli -g ipv6.dns-search c show eth2".split(), check=True
|
|
+ )
|
|
+ assert "example.com" not in search4 and "example.com" not in search6
|
|
+ assert "example2.com" not in search4 and "example2.com" not in search6
|
|
+
|
|
+ # It is not possible to assert that the new configuration has been put into
|
|
+ # eth1 because, if there are more interfaces in the host, it might be in
|
|
+ # any of them
|
|
--
|
|
2.49.0
|
|
|