diff --git a/README.md b/README.md index 90a5b57..3ed52f0 100644 --- a/README.md +++ b/README.md @@ -514,6 +514,15 @@ The IP configuration supports the following options: When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then "edns0" and "trust-ad" are automatically added. +- `dns_priority` + + DNS servers priority. The relative priority for DNS servers specified by this + setting. The default value is 0, a lower numerical value has higher priority. + The valid value of `dns_priority` ranges from -2147483648 to 2147483647. Negative + values have the special effect of excluding other configurations with a greater + numerical priority value; so in presence of at least one negative priority, only + DNS servers from connections with the lowest priority value will be used. + - `gateway4` and `gateway6` The default gateway for IPv4 (`gateway4`) or IPv6 (`gateway6`) packets. diff --git a/examples/eth_dns_support.yml b/examples/eth_dns_support.yml index 43c3c2e..16724ab 100644 --- a/examples/eth_dns_support.yml +++ b/examples/eth_dns_support.yml @@ -9,6 +9,7 @@ route_metric4: 100 dhcp4: no gateway4: 192.0.2.1 + dns_priority: 9999 dns: - 192.0.2.2 - 198.51.100.5 diff --git a/library/network_connections.py b/library/network_connections.py index 074d79b..31e6e90 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -1092,6 +1092,10 @@ class NMUtil: s_ip4.clear_dns_options(False) for option in ip["dns_options"]: s_ip4.add_dns_option(option) + if ip["dns_priority"] is not None: + s_ip4.set_property( + NM.SETTING_IP_CONFIG_DNS_PRIORITY, ip["dns_priority"] + ) is_ipv6_configured = False if ip["ipv6_disabled"]: @@ -1144,6 +1148,10 @@ class NMUtil: s_ip6.clear_dns_options(False) for option in ip["dns_options"]: s_ip6.add_dns_option(option) + if ip["dns_priority"] is not None: + s_ip6.set_property( + NM.SETTING_IP_CONFIG_DNS_PRIORITY, ip["dns_priority"] + ) if ip["route_append_only"] and connection_current: for r in self.setting_ip_config_get_routes( diff --git a/module_utils/network_lsr/argument_validator.py b/module_utils/network_lsr/argument_validator.py index 98261d1..df84cac 100644 --- a/module_utils/network_lsr/argument_validator.py +++ b/module_utils/network_lsr/argument_validator.py @@ -887,6 +887,12 @@ class ArgValidator_DictIP(ArgValidatorDict): ), default_value=list, ), + ArgValidatorNum( + "dns_priority", + val_min=-2147483648, + val_max=2147483647, + default_value=0, + ), ArgValidatorList( "routing_rule", nested=ArgValidatorIPRoutingRule("routing_rule[?]"), @@ -911,6 +917,7 @@ class ArgValidator_DictIP(ArgValidatorDict): "dns": [], "dns_search": [], "dns_options": [], + "dns_priority": 0, }, ) @@ -2470,19 +2477,23 @@ class ArgValidator_ListConnections(ArgValidatorList): "IPv6 needs to be enabled to support IPv6 nameservers.", ) # when IPv4 and IPv6 are disabled, setting ip.dns_options or - # ip.dns_search is not allowed - if connection["ip"]["dns_search"] or connection["ip"]["dns_options"]: + # ip.dns_search or ip.dns_priority is not allowed + if ( + connection["ip"]["dns_search"] + or connection["ip"]["dns_options"] + or connection["ip"]["dns_priority"] + ): if not _ipv4_enabled(connection) and connection["ip"]["ipv6_disabled"]: raise ValidationError.from_connection( idx, - "Setting 'dns_search' or 'dns_options' is not allowed when " - "both IPv4 and IPv6 are disabled.", + "Setting 'dns_search', 'dns_options' and 'dns_priority' are not " + "allowed when both IPv4 and IPv6 are disabled.", ) elif not _ipv4_enabled(connection) and _ipv6_is_not_configured(connection): raise ValidationError.from_connection( idx, - "Setting 'dns_search' or 'dns_options' is not allowed when " - "IPv4 is disabled and IPv6 is not configured.", + "Setting 'dns_search', 'dns_options' and 'dns_priority' are not " + "allowed when IPv4 is disabled and IPv6 is not configured.", ) # DNS options 'inet6', 'ip6-bytestring', 'ip6-dotint', 'no-ip6-dotint' are only # supported for IPv6 configuration, so raise errors when IPv6 is disabled diff --git a/tests/playbooks/tests_eth_dns_support.yml b/tests/playbooks/tests_eth_dns_support.yml index 7ab72be..8cd3978 100644 --- a/tests/playbooks/tests_eth_dns_support.yml +++ b/tests/playbooks/tests_eth_dns_support.yml @@ -34,6 +34,7 @@ route_metric4: 100 dhcp4: no gateway4: 192.0.2.1 + dns_priority: 9999 dns: - 192.0.2.2 - 198.51.100.5 @@ -108,6 +109,13 @@ - "'timeout:1' in ipv6_dns.stdout" msg: "DNS options are configured incorrectly" + - name: "Assert that DNS priority is configured correctly" + assert: + that: + - "'9999' in ipv4_dns.stdout" + - "'9999' in ipv6_dns.stdout" + msg: "DNS priority is configured incorrectly" + - import_playbook: down_profile+delete_interface.yml vars: profile: "{{ interface }}" diff --git a/tests/playbooks/tests_ipv6_dns_search.yml b/tests/playbooks/tests_ipv6_dns_search.yml index 1fa64fc..ac11641 100644 --- a/tests/playbooks/tests_ipv6_dns_search.yml +++ b/tests/playbooks/tests_ipv6_dns_search.yml @@ -138,11 +138,11 @@ assert: that: - __network_connections_result.stderr is search("Setting - 'dns_search' or 'dns_options' is not allowed when both IPv4 and - IPv6 are disabled.") + 'dns_search', 'dns_options' and 'dns_priority' are not allowed + when both IPv4 and IPv6 are disabled.") msg: Reconfiguring network connection is not failed with the error - "Setting 'dns_search' or 'dns_options' is not allowed when both - IPv4 and IPv6 are disabled." + "Setting 'dns_search', 'dns_options', and 'dns_priority' are not + allowed when both IPv4 and IPv6 are disabled." when: ansible_distribution_major_version | int > 7 always: diff --git a/tests/unit/test_network_connections.py b/tests/unit/test_network_connections.py index 196a636..bef7914 100644 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -198,6 +198,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4_send_hostname": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], }, "mac": None, @@ -587,6 +588,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4_send_hostname": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], }, "mac": None, @@ -641,6 +643,7 @@ class TestValidator(Python26CompatTestCase): "route": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "route_metric6": None, "routing_rule": [], @@ -692,6 +695,7 @@ class TestValidator(Python26CompatTestCase): "route": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "route_metric6": None, "routing_rule": [], @@ -772,6 +776,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "route_metric4": None, "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -839,6 +844,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "route_metric4": None, "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -930,6 +936,7 @@ class TestValidator(Python26CompatTestCase): "route": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "route_metric6": None, "routing_rule": [], @@ -1028,6 +1035,7 @@ class TestValidator(Python26CompatTestCase): "route_metric4": None, "routing_rule": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1063,6 +1071,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "route_metric4": None, "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1160,6 +1169,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "route_metric4": None, "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1237,6 +1247,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "route_metric4": None, "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1351,6 +1362,7 @@ class TestValidator(Python26CompatTestCase): "route_metric4": None, "routing_rule": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1387,6 +1399,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "route_metric4": None, "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1499,6 +1512,7 @@ class TestValidator(Python26CompatTestCase): "routing_rule": [], "dns_options": [], "ipv6_disabled": False, + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1534,6 +1548,7 @@ class TestValidator(Python26CompatTestCase): "route_metric4": None, "dns_options": [], "ipv6_disabled": False, + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1591,6 +1606,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "route_metric4": None, "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1701,6 +1717,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4_send_hostname": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "gateway4": None, "gateway6": None, @@ -1744,6 +1761,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4_send_hostname": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "gateway4": None, "gateway6": None, @@ -1835,6 +1853,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "route_metric4": None, "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1914,6 +1933,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "route_metric4": None, "dns_options": [], + "dns_priority": 0, "dns_search": [], "dhcp4_send_hostname": None, "gateway6": None, @@ -1988,6 +2008,7 @@ class TestValidator(Python26CompatTestCase): "route_metric6": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], }, "mac": "aa:bb:cc:dd:ee:ff", @@ -2035,6 +2056,7 @@ class TestValidator(Python26CompatTestCase): "route": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "route_metric6": None, "routing_rule": [], @@ -2108,6 +2130,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4_send_hostname": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "gateway4": None, "gateway6": None, @@ -2150,6 +2173,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4": True, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "gateway4": None, "gateway6": None, @@ -2209,6 +2233,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4_send_hostname": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "gateway4": None, "gateway6": None, @@ -2285,6 +2310,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4_send_hostname": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "gateway4": None, "gateway6": None, @@ -2367,6 +2393,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4_send_hostname": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "gateway4": None, "gateway6": None, @@ -2409,6 +2436,7 @@ class TestValidator(Python26CompatTestCase): "dhcp4_send_hostname": None, "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "gateway4": None, "gateway6": None, @@ -2540,6 +2568,7 @@ class TestValidator(Python26CompatTestCase): "routing_rule": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": ["aa", "bb"], "route_metric6": None, "dhcp4_send_hostname": None, @@ -2648,6 +2677,7 @@ class TestValidator(Python26CompatTestCase): "routing_rule": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": ["aa", "bb"], "route_metric6": None, "dhcp4_send_hostname": None, @@ -2765,6 +2795,7 @@ class TestValidator(Python26CompatTestCase): "route": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "route_metric6": None, "routing_rule": [], @@ -2845,6 +2876,7 @@ class TestValidator(Python26CompatTestCase): "route": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "route_metric6": None, "routing_rule": [], @@ -2925,6 +2957,7 @@ class TestValidator(Python26CompatTestCase): "route": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "route_metric6": None, "routing_rule": [], @@ -3003,6 +3036,7 @@ class TestValidator(Python26CompatTestCase): "route": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "route_metric6": None, "routing_rule": [], @@ -3071,6 +3105,7 @@ class TestValidator(Python26CompatTestCase): "route": [], "dns": [], "dns_options": [], + "dns_priority": 0, "dns_search": [], "route_metric6": None, "routing_rule": [], @@ -4059,8 +4094,8 @@ class TestValidator(Python26CompatTestCase): ] self.assertRaisesRegex( ValidationError, - "Setting 'dns_search' or 'dns_options' is not allowed when IPv4 is " - "disabled and IPv6 is not configured", + "Setting 'dns_search', 'dns_options' and 'dns_priority' are not allowed " + "when IPv4 is disabled and IPv6 is not configured", validator.validate_connection_one, "nm", validator.validate(dns_search_without_ipv4_and_ipv6_configuration),