|
Packit |
b9ca78 |
#
|
|
Packit |
b9ca78 |
# Copyright (c) 2020 Red Hat, Inc.
|
|
Packit |
b9ca78 |
#
|
|
Packit |
b9ca78 |
# This file is part of nmstate
|
|
Packit |
b9ca78 |
#
|
|
Packit |
b9ca78 |
# This program is free software: you can redistribute it and/or modify
|
|
Packit |
b9ca78 |
# it under the terms of the GNU Lesser General Public License as published by
|
|
Packit |
b9ca78 |
# the Free Software Foundation, either version 2.1 of the License, or
|
|
Packit |
b9ca78 |
# (at your option) any later version.
|
|
Packit |
b9ca78 |
#
|
|
Packit |
b9ca78 |
# This program is distributed in the hope that it will be useful,
|
|
Packit |
b9ca78 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
b9ca78 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Packit |
b9ca78 |
# GNU Lesser General Public License for more details.
|
|
Packit |
b9ca78 |
#
|
|
Packit |
b9ca78 |
# You should have received a copy of the GNU Lesser General Public License
|
|
Packit |
b9ca78 |
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
Packit |
b9ca78 |
#
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
from copy import deepcopy
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
from libnmstate.error import NmstateValueError
|
|
Packit |
b9ca78 |
from libnmstate.error import NmstateVerificationError
|
|
Packit |
b9ca78 |
from libnmstate.error import NmstateNotImplementedError
|
|
Packit |
b9ca78 |
from libnmstate.iplib import is_ipv6_address
|
|
Packit |
b9ca78 |
from libnmstate.prettystate import format_desired_current_state_diff
|
|
Packit |
b9ca78 |
from libnmstate.schema import DNS
|
|
Packit |
b9ca78 |
from libnmstate.schema import Interface
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
class DnsState:
|
|
Packit |
b9ca78 |
PRIORITY_METADATA = "_priority"
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def __init__(self, des_dns_state, cur_dns_state):
|
|
Packit |
b9ca78 |
self._config_changed = False
|
|
Packit |
b9ca78 |
if des_dns_state is None or des_dns_state.get(DNS.CONFIG) is None:
|
|
Packit |
b9ca78 |
# Use current config if DNS.KEY not defined or DNS.CONFIG not
|
|
Packit |
b9ca78 |
# defined.
|
|
Packit |
b9ca78 |
self._dns_state = cur_dns_state or {}
|
|
Packit |
b9ca78 |
else:
|
|
Packit |
b9ca78 |
self._dns_state = des_dns_state
|
|
Packit |
b9ca78 |
self._validate()
|
|
Packit |
b9ca78 |
self._config_changed = _is_dns_config_changed(
|
|
Packit |
b9ca78 |
des_dns_state, cur_dns_state
|
|
Packit |
b9ca78 |
)
|
|
Packit |
b9ca78 |
self._cur_dns_state = deepcopy(cur_dns_state) if cur_dns_state else {}
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
@property
|
|
Packit |
b9ca78 |
def current_config(self):
|
|
Packit |
b9ca78 |
return _get_config(self._cur_dns_state)
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
@property
|
|
Packit |
b9ca78 |
def config(self):
|
|
Packit |
b9ca78 |
return _get_config(self._dns_state)
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
@property
|
|
Packit |
b9ca78 |
def _config_servers(self):
|
|
Packit |
b9ca78 |
return _get_config_servers(self._dns_state)
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
@property
|
|
Packit |
b9ca78 |
def _config_searches(self):
|
|
Packit |
b9ca78 |
return _get_config_searches(self._dns_state)
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def gen_metadata(self, ifaces, route_state):
|
|
Packit |
b9ca78 |
"""
|
|
Packit |
b9ca78 |
Return DNS configure targeting to store as metadata of interface.
|
|
Packit |
b9ca78 |
Data structure returned is:
|
|
Packit |
b9ca78 |
{
|
|
Packit |
b9ca78 |
iface_name: {
|
|
Packit |
b9ca78 |
Interface.IPV4: {
|
|
Packit |
b9ca78 |
DNS.SERVER: dns_servers,
|
|
Packit |
b9ca78 |
DNS.SEARCH: dns_searches,
|
|
Packit |
b9ca78 |
},
|
|
Packit |
b9ca78 |
Interface.IPV6: {
|
|
Packit |
b9ca78 |
DNS.SERVER: dns_servers,
|
|
Packit |
b9ca78 |
DNS.SEARCH: dns_searches,
|
|
Packit |
b9ca78 |
},
|
|
Packit |
b9ca78 |
}
|
|
Packit |
b9ca78 |
}
|
|
Packit |
b9ca78 |
"""
|
|
Packit |
b9ca78 |
iface_metadata = {}
|
|
Packit |
b9ca78 |
if not self._config_servers and not self._config_searches:
|
|
Packit |
b9ca78 |
return iface_metadata
|
|
Packit |
b9ca78 |
ipv4_iface, ipv6_iface = self._find_ifaces_for_name_servers(
|
|
Packit |
b9ca78 |
ifaces, route_state
|
|
Packit |
b9ca78 |
)
|
|
Packit |
b9ca78 |
if ipv4_iface == ipv6_iface:
|
|
Packit |
b9ca78 |
iface_metadata = {
|
|
Packit |
b9ca78 |
ipv4_iface: {
|
|
Packit |
b9ca78 |
Interface.IPV4: {DNS.SERVER: [], DNS.SEARCH: []},
|
|
Packit |
b9ca78 |
Interface.IPV6: {DNS.SERVER: [], DNS.SEARCH: []},
|
|
Packit |
b9ca78 |
},
|
|
Packit |
b9ca78 |
}
|
|
Packit |
b9ca78 |
else:
|
|
Packit |
b9ca78 |
if ipv4_iface:
|
|
Packit |
b9ca78 |
iface_metadata[ipv4_iface] = {
|
|
Packit |
b9ca78 |
Interface.IPV4: {DNS.SERVER: [], DNS.SEARCH: []},
|
|
Packit |
b9ca78 |
}
|
|
Packit |
b9ca78 |
if ipv6_iface:
|
|
Packit |
b9ca78 |
iface_metadata[ipv6_iface] = {
|
|
Packit |
b9ca78 |
Interface.IPV6: {DNS.SERVER: [], DNS.SEARCH: []},
|
|
Packit |
b9ca78 |
}
|
|
Packit |
b9ca78 |
index = 0
|
|
Packit |
b9ca78 |
searches_saved = False
|
|
Packit |
b9ca78 |
for server in self._config_servers:
|
|
Packit |
b9ca78 |
iface_name = None
|
|
Packit |
b9ca78 |
if is_ipv6_address(server):
|
|
Packit |
b9ca78 |
iface_name = ipv6_iface
|
|
Packit |
b9ca78 |
family = Interface.IPV6
|
|
Packit |
b9ca78 |
else:
|
|
Packit |
b9ca78 |
iface_name = ipv4_iface
|
|
Packit |
b9ca78 |
family = Interface.IPV4
|
|
Packit |
b9ca78 |
if not iface_name:
|
|
Packit |
b9ca78 |
raise NmstateValueError(
|
|
Packit |
b9ca78 |
"Failed to find suitable interface for saving DNS "
|
|
Packit |
b9ca78 |
"name servers: %s" % server
|
|
Packit |
b9ca78 |
)
|
|
Packit |
b9ca78 |
iface_dns_metada = iface_metadata[iface_name][family]
|
|
Packit |
b9ca78 |
iface_dns_metada[DNS.SERVER].append(server)
|
|
Packit |
b9ca78 |
iface_dns_metada.setdefault(DnsState.PRIORITY_METADATA, index)
|
|
Packit |
b9ca78 |
if not searches_saved:
|
|
Packit |
b9ca78 |
iface_dns_metada[DNS.SEARCH] = self._config_searches
|
|
Packit |
b9ca78 |
searches_saved = True
|
|
Packit |
b9ca78 |
index += 1
|
|
Packit |
b9ca78 |
return iface_metadata
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def _find_ifaces_for_name_servers(self, ifaces, route_state):
|
|
Packit |
b9ca78 |
"""
|
|
Packit |
b9ca78 |
Find interface to store the DNS configurations in the order of:
|
|
Packit |
b9ca78 |
* Any interface with static gateway
|
|
Packit |
b9ca78 |
* Any interface configured as dynamic IP with 'auto-dns:False'
|
|
Packit |
b9ca78 |
Return tuple: (ipv4_iface, ipv6_iface)
|
|
Packit |
b9ca78 |
"""
|
|
Packit |
b9ca78 |
ipv4_iface, ipv6_iface = self._find_ifaces_with_static_gateways(
|
|
Packit |
b9ca78 |
route_state
|
|
Packit |
b9ca78 |
)
|
|
Packit |
b9ca78 |
if not (ipv4_iface and ipv6_iface):
|
|
Packit |
b9ca78 |
(
|
|
Packit |
b9ca78 |
auto_ipv4_iface,
|
|
Packit |
b9ca78 |
auto_ipv6_iface,
|
|
Packit |
b9ca78 |
) = self._find_ifaces_with_auto_dns_false(ifaces)
|
|
Packit |
b9ca78 |
if not ipv4_iface and auto_ipv4_iface:
|
|
Packit |
b9ca78 |
ipv4_iface = auto_ipv4_iface
|
|
Packit |
b9ca78 |
if not ipv6_iface and auto_ipv6_iface:
|
|
Packit |
b9ca78 |
ipv6_iface = auto_ipv6_iface
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
return ipv4_iface, ipv6_iface
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def _find_ifaces_with_static_gateways(self, route_state):
|
|
Packit |
b9ca78 |
"""
|
|
Packit |
b9ca78 |
Return tuple of interfaces with IPv4 and IPv6 static gateways.
|
|
Packit |
b9ca78 |
"""
|
|
Packit |
b9ca78 |
ipv4_iface = None
|
|
Packit |
b9ca78 |
ipv6_iface = None
|
|
Packit |
b9ca78 |
for iface_name, route_set in route_state.config_iface_routes.items():
|
|
Packit |
b9ca78 |
for route in route_set:
|
|
Packit |
b9ca78 |
if ipv4_iface and ipv6_iface:
|
|
Packit |
b9ca78 |
return (ipv4_iface, ipv6_iface)
|
|
Packit |
b9ca78 |
if route.is_gateway:
|
|
Packit |
b9ca78 |
if route.is_ipv6:
|
|
Packit |
b9ca78 |
ipv6_iface = iface_name
|
|
Packit |
b9ca78 |
else:
|
|
Packit |
b9ca78 |
ipv4_iface = iface_name
|
|
Packit |
b9ca78 |
return (ipv4_iface, ipv6_iface)
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def _find_ifaces_with_auto_dns_false(self, ifaces):
|
|
Packit |
b9ca78 |
ipv4_iface = None
|
|
Packit |
b9ca78 |
ipv6_iface = None
|
|
Packit Service |
588ec5 |
for iface in ifaces.all_kernel_ifaces.values():
|
|
Packit |
b9ca78 |
if ipv4_iface and ipv6_iface:
|
|
Packit |
b9ca78 |
return (ipv4_iface, ipv6_iface)
|
|
Packit |
b9ca78 |
for family in (Interface.IPV4, Interface.IPV6):
|
|
Packit |
b9ca78 |
ip_state = iface.ip_state(family)
|
|
Packit |
b9ca78 |
if ip_state.is_dynamic and (not ip_state.auto_dns):
|
|
Packit |
b9ca78 |
if family == Interface.IPV4:
|
|
Packit |
b9ca78 |
ipv4_iface = iface.name
|
|
Packit |
b9ca78 |
else:
|
|
Packit |
b9ca78 |
ipv6_iface = iface.name
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
return (ipv4_iface, ipv6_iface)
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def verify(self, cur_dns_state):
|
|
Packit Service |
588ec5 |
cur_dns = DnsState(
|
|
Packit Service |
588ec5 |
des_dns_state=None,
|
|
Packit Service |
588ec5 |
cur_dns_state=cur_dns_state,
|
|
Packit Service |
588ec5 |
)
|
|
Packit |
b9ca78 |
if self.config.get(DNS.SERVER, []) != cur_dns.config.get(
|
|
Packit |
b9ca78 |
DNS.SERVER, []
|
|
Packit |
b9ca78 |
) or self.config.get(DNS.SEARCH, []) != cur_dns.config.get(
|
|
Packit |
b9ca78 |
DNS.SEARCH, []
|
|
Packit |
b9ca78 |
):
|
|
Packit |
b9ca78 |
raise NmstateVerificationError(
|
|
Packit |
b9ca78 |
format_desired_current_state_diff(
|
|
Packit Service |
588ec5 |
{DNS.KEY: self.config},
|
|
Packit Service |
588ec5 |
{DNS.KEY: cur_dns.config},
|
|
Packit |
b9ca78 |
)
|
|
Packit |
b9ca78 |
)
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def _validate(self):
|
|
Packit |
b9ca78 |
if (
|
|
Packit |
b9ca78 |
len(self._config_servers) > 2
|
|
Packit |
b9ca78 |
and any(is_ipv6_address(n) for n in self._config_servers)
|
|
Packit |
b9ca78 |
and any(not is_ipv6_address(n) for n in self._config_servers)
|
|
Packit Service |
293802 |
and _is_mixed_dns_servers(self._config_servers)
|
|
Packit |
b9ca78 |
):
|
|
Packit |
b9ca78 |
raise NmstateNotImplementedError(
|
|
Packit Service |
5e62f7 |
"Placing IPv4/IPv6 nameserver in the middle of IPv6/IPv4 "
|
|
Packit Service |
293802 |
"nameservers is not supported yet"
|
|
Packit |
b9ca78 |
)
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
@property
|
|
Packit |
b9ca78 |
def config_changed(self):
|
|
Packit |
b9ca78 |
return self._config_changed
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def _get_config(state):
|
|
Packit |
b9ca78 |
conf = state.get(DNS.CONFIG, {})
|
|
Packit |
b9ca78 |
if not conf:
|
|
Packit |
b9ca78 |
conf = {DNS.SERVER: [], DNS.SEARCH: []}
|
|
Packit |
b9ca78 |
return conf
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def _get_config_servers(state):
|
|
Packit |
b9ca78 |
return _get_config(state).get(DNS.SERVER, [])
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def _get_config_searches(state):
|
|
Packit |
b9ca78 |
return _get_config(state).get(DNS.SEARCH, [])
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
|
|
Packit |
b9ca78 |
def _is_dns_config_changed(des_dns_state, cur_dns_state):
|
|
Packit |
b9ca78 |
return _get_config_servers(des_dns_state) != _get_config_servers(
|
|
Packit |
b9ca78 |
cur_dns_state
|
|
Packit |
b9ca78 |
) or _get_config_searches(des_dns_state) != _get_config_searches(
|
|
Packit |
b9ca78 |
cur_dns_state
|
|
Packit |
b9ca78 |
)
|
|
Packit Service |
293802 |
|
|
Packit Service |
293802 |
|
|
Packit Service |
293802 |
def _is_mixed_dns_servers(servers):
|
|
Packit Service |
293802 |
"""
|
|
Packit Service |
293802 |
Return True when an IPv6 server is in the middle of two IPv4 namesevers or
|
|
Packit Service |
293802 |
an IPv4 server is in the middle of two IPv6 servers.
|
|
Packit Service |
293802 |
"""
|
|
Packit Service |
293802 |
pattern = ""
|
|
Packit Service |
293802 |
for server in servers:
|
|
Packit Service |
293802 |
if is_ipv6_address(server):
|
|
Packit Service |
293802 |
pattern += "6"
|
|
Packit Service |
293802 |
else:
|
|
Packit Service |
293802 |
pattern += "4"
|
|
Packit Service |
293802 |
|
|
Packit Service |
293802 |
return "464" in pattern or "646" in pattern
|