Fix: support dns_search and dns_options for all address family

`dns_search` and `dns_options` should not be specific to the address
family. Previously, `dns_search` and `dns_options` were only supported
for IPv4 nameservers, so we also need to support `dns_search` and
`dns_options` for IPv6 nameservers.

Signed-off-by: Wen Liang <liangwen12year@gmail.com>
This commit is contained in:
Wen Liang 2021-03-28 17:36:45 -04:00 committed by Gris Ge
parent 2444e27cce
commit 93e509b533
5 changed files with 165 additions and 17 deletions

View file

@ -364,21 +364,25 @@ The IP configuration supports the following options:
- `dns_search`
`dns_search` is only supported for IPv4 nameservers. Manual DNS configuration can
be specified via a list of domains to search given in the `dns_search` option.
Manual DNS configuration can be specified via a list of domains to search given in
the `dns_search` option.
- `dns_options`
`dns_options` is only supported for the NetworkManager provider and IPv4
nameservers. Manual DNS configuration via a list of DNS options can be given in the
`dns_options`. The list of supported DNS options for IPv4 nameservers is described
in [man 5 resolv.conf](https://man7.org/linux/man-pages/man5/resolv.conf.5.html).
`dns_options` is only supported for the NetworkManager provider. Manual DNS
configuration via a list of DNS options can be given in the `dns_options`. The list
of supported DNS options for IPv4 nameservers is described in
[man 5 resolv.conf](https://man7.org/linux/man-pages/man5/resolv.conf.5.html).
Currently, the list of supported DNS options is:
- `attempts:n`
- `debug`
- `edns0`
- `inet6`
- `ip6-bytestring`
- `ip6-dotint`
- `ndots:n`
- `no-check-names`
- `no-ip6-dotint`
- `no-reload`
- `no-tld-query`
- `rotate`

View file

@ -1023,14 +1023,14 @@ class NMUtil:
s_ip4.set_property(
NM.SETTING_IP_CONFIG_ROUTE_METRIC, ip["route_metric4"]
)
for d in ip["dns"]:
if d["family"] == socket.AF_INET:
s_ip4.add_dns(d["address"])
for s in ip["dns_search"]:
s_ip4.add_dns_search(s)
for nameserver in ip["dns"]:
if nameserver["family"] == socket.AF_INET:
s_ip4.add_dns(nameserver["address"])
for search_domain in ip["dns_search"]:
s_ip4.add_dns_search(search_domain)
s_ip4.clear_dns_options(True)
for s in ip["dns_options"]:
s_ip4.add_dns_option(s)
for option in ip["dns_options"]:
s_ip4.add_dns_option(option)
if ip["ipv6_disabled"]:
s_ip6.set_property(NM.SETTING_IP_CONFIG_METHOD, "disabled")
@ -1056,9 +1056,14 @@ class NMUtil:
s_ip6.set_property(
NM.SETTING_IP_CONFIG_ROUTE_METRIC, ip["route_metric6"]
)
for d in ip["dns"]:
if d["family"] == socket.AF_INET6:
s_ip6.add_dns(d["address"])
for nameserver in ip["dns"]:
if nameserver["family"] == socket.AF_INET6:
s_ip6.add_dns(nameserver["address"])
for search_domain in ip["dns_search"]:
s_ip6.add_dns_search(search_domain)
s_ip6.clear_dns_options(True)
for option in ip["dns_options"]:
s_ip6.add_dns_option(option)
if ip["route_append_only"] and connection_current:
for r in self.setting_ip_config_get_routes(

View file

@ -534,8 +534,12 @@ class ArgValidator_DictIP(ArgValidatorDict):
r"^attempts:([1-9]\d*|0)$",
r"^debug$",
r"^edns0$",
r"^inet6$",
r"^ip6-bytestring$",
r"^ip6-dotint$",
r"^ndots:([1-9]\d*|0)$",
r"^no-check-names$",
r"^no-ip6-dotint$",
r"^no-reload$",
r"^no-tld-query$",
r"^rotate$",
@ -1759,6 +1763,13 @@ class ArgValidator_ListConnections(ArgValidatorList):
VALIDATE_ONE_MODE_INITSCRIPTS = "initscripts"
def validate_connection_one(self, mode, connections, idx):
def _ipv4_enabled(connection):
has_addrs4 = any(
address["family"] == socket.AF_INET
for address in connection["ip"]["address"]
)
return connection["ip"]["dhcp4"] or has_addrs4
connection = connections[idx]
if "type" not in connection:
return
@ -1832,3 +1843,45 @@ class ArgValidator_ListConnections(ArgValidatorList):
idx,
"ip.ipv6_disabled is not supported by initscripts.",
)
# Setting ip.dns is not allowed when corresponding IP method for that
# nameserver is disabled
for nameserver in connection["ip"]["dns"]:
if nameserver["family"] == socket.AF_INET and not _ipv4_enabled(connection):
raise ValidationError.from_connection(
idx,
"IPv4 needs to be enabled to support IPv4 nameservers.",
)
if (
nameserver["family"] == socket.AF_INET6
and connection["ip"]["ipv6_disabled"]
):
raise ValidationError.from_connection(
idx,
"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"]:
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.",
)
# DNS options 'inet6', 'ip6-bytestring', 'ip6-dotint', 'no-ip6-dotint' are only
# supported for IPv6 configuration, so raise errors when IPv6 is disabled
if any(
option in connection["ip"]["dns_options"]
for option in [
"inet6",
"ip6-bytestring",
"ip6-dotint",
"no-ip6-dotint",
]
):
if connection["ip"]["ipv6_disabled"]:
raise ValidationError.from_connection(
idx,
"Setting DNS options 'inet6', 'ip6-bytestring', 'ip6-dotint', "
"'no-ip6-dotint' is not allowed when IPv6 is disabled.",
)

View file

@ -36,6 +36,7 @@
dns:
- 192.0.2.2
- 198.51.100.5
- 2001:db8::20
dns_search:
- example.com
- example.org
@ -64,7 +65,7 @@
route_append_only: no
rule_append_only: yes
- name: Verify nmcli connection DNS entry
- name: Verify nmcli connection DNS entry for IPv4
shell: |
set -euxo pipefail
nmcli connection show {{ interface }} | grep ipv4.dns
@ -72,11 +73,20 @@
ignore_errors: yes
changed_when: false
- name: Verify nmcli connection DNS entry for IPv6
shell: |
set -euxo pipefail
nmcli connection show {{ interface }} | grep ipv6.dns
register: ipv6_dns
ignore_errors: yes
changed_when: false
- name: "Assert that DNS addresses are configured correctly"
assert:
that:
- "'192.0.2.2' in ipv4_dns.stdout"
- "'198.51.100.5' in ipv4_dns.stdout"
- "'2001:db8::20' in ipv6_dns.stdout"
msg: "DNS addresses are configured incorrectly"
- name: "Assert that DNS search domains are configured correctly"
@ -84,6 +94,8 @@
that:
- "'example.com' in ipv4_dns.stdout"
- "'example.org' in ipv4_dns.stdout"
- "'example.com' in ipv6_dns.stdout"
- "'example.org' in ipv6_dns.stdout"
msg: "DNS search domains are configured incorrectly"
- name: "Assert that DNS options are configured correctly"
@ -91,6 +103,8 @@
that:
- "'rotate' in ipv4_dns.stdout"
- "'timeout:1' in ipv4_dns.stdout"
- "'rotate' in ipv6_dns.stdout"
- "'timeout:1' in ipv6_dns.stdout"
msg: "DNS options are configured incorrectly"
- import_playbook: down_profile.yml

View file

@ -3321,6 +3321,78 @@ class TestValidator(unittest.TestCase):
validator.validate(true_testcase_12)["dns_options"], ["use-vc"]
)
def test_ipv4_dns_without_ipv4_config(self):
"""
Test that configuring IPv4 DNS is not allowed when IPv4 is disabled.
"""
validator = network_lsr.argument_validator.ArgValidator_ListConnections()
ipv4_dns_without_ipv4_config = [
{
"name": "test_ipv4_dns",
"type": "ethernet",
"ip": {
"auto6": True,
"dhcp4": False,
"dns": ["198.51.100.5"],
},
}
]
self.assertRaises(
ValidationError,
validator.validate_connection_one,
"nm",
validator.validate(ipv4_dns_without_ipv4_config),
0,
)
def test_ipv6_dns_without_ipv6_config(self):
"""
Test that configuring IPv6 DNS is not allowed when IPv6 is disabled.
"""
validator = network_lsr.argument_validator.ArgValidator_ListConnections()
ipv6_dns_without_ipv6_config = [
{
"name": "test_ipv6_dns",
"type": "ethernet",
"ip": {
"ipv6_disabled": True,
"dhcp4": True,
"dns": ["2001:db8::20"],
},
}
]
self.assertRaises(
ValidationError,
validator.validate_connection_one,
"nm",
validator.validate(ipv6_dns_without_ipv6_config),
0,
)
def test_ipv6_dns_options_without_ipv6_config(self):
"""
Test that configuring IPv6 DNS options is not allowed when IPv6 is disabled.
"""
validator = network_lsr.argument_validator.ArgValidator_ListConnections()
ipv6_dns_options_without_ipv6_config = [
{
"name": "test_ipv6_dns",
"type": "ethernet",
"ip": {
"ipv6_disabled": True,
"dhcp4": True,
"dns_options": ["ip6-bytestring"],
},
}
]
self.assertRaises(
ValidationError,
validator.validate_connection_one,
"nm",
validator.validate(ipv6_dns_options_without_ipv6_config),
0,
)
def test_set_deprecated_master(self):
"""
When passing the deprecated "master" it is updated to "controller".