DNS plugin maintainance (#2179)

* DNS plugin maintainance

* cleanup old data

* move fallback to plugin

* smaller

* Allow plugin to write into config persistent

* Fix hosts

* create issue on dns loop
This commit is contained in:
Pascal Vizeli 2020-10-29 15:48:57 +01:00 committed by GitHub
parent db260dfbde
commit 50d36b857a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 71 additions and 93 deletions

View File

@ -48,7 +48,7 @@ class APICoreDNS(CoreSysAttributes):
ATTR_UPDATE_AVAILABLE: self.sys_plugins.dns.need_update,
ATTR_HOST: str(self.sys_docker.network.dns),
ATTR_SERVERS: self.sys_plugins.dns.servers,
ATTR_LOCALS: self.sys_host.network.dns_servers,
ATTR_LOCALS: self.sys_plugins.dns.locals,
}
@api_process

View File

@ -1,34 +0,0 @@
.:53 {
log
errors
loop
{% if debug %}debug{% endif %}
hosts /config/hosts {
fallthrough
}
template ANY AAAA local.hass.io hassio {
rcode NOERROR
}
mdns
forward . {{ locals | join(" ") }} dns://127.0.0.1:5553 {
except local.hass.io
policy sequential
health_check 5s
}
fallback REFUSED . dns://127.0.0.1:5553
fallback SERVFAIL . dns://127.0.0.1:5553
fallback NXDOMAIN . dns://127.0.0.1:5553
cache 10
}
.:5553 {
log
errors
{% if debug %}debug{% endif %}
forward . tls://1.1.1.1 tls://1.0.0.1 {
tls_servername cloudflare-dns.com
except local.hass.io
health_check 10s
}
cache 30
}

View File

@ -1,2 +1,3 @@
$supervisor hassio supervisor.local.hass.io hassio.local.hass.io
$homeassistant homeassistant homeassistant.local.hass.io home-assistant.local.hass.io
{% for entry in entries %}
{{- entry.ip_address }} {{ entry.names | join(" ") }}
{% endfor %}

View File

@ -46,7 +46,7 @@ class DockerDNS(DockerInterface, CoreSysAttributes):
detach=True,
environment={ENV_TIME: self.sys_config.timezone},
volumes={
str(self.sys_config.path_extern_dns): {"bind": "/config", "mode": "ro"}
str(self.sys_config.path_extern_dns): {"bind": "/config", "mode": "rw"}
},
)

View File

@ -292,11 +292,8 @@ class Tasks(CoreSysAttributes):
return
_LOGGER.warning("Watchdog found a problem with CoreDNS plugin!")
# Reset of failed
if await self.sys_plugins.dns.is_failed():
_LOGGER.error("CoreDNS plugin is in failed state, resetting configuration")
await self.sys_plugins.dns.reset()
await self.sys_plugins.dns.loop_detection()
# Detect loop
await self.sys_plugins.dns.loop_detection()
try:
await self.sys_plugins.dns.start()

View File

@ -18,15 +18,16 @@ from ..const import ATTR_IMAGE, ATTR_SERVERS, ATTR_VERSION, DNS_SUFFIX, LogLevel
from ..coresys import CoreSys, CoreSysAttributes
from ..docker.dns import DockerDNS
from ..docker.stats import DockerStats
from ..exceptions import CoreDNSError, CoreDNSUpdateError, DockerError
from ..utils.json import JsonConfig
from ..exceptions import CoreDNSError, CoreDNSUpdateError, DockerError, JsonFileError
from ..resolution.const import ContextType, IssueType, SuggestionType
from ..utils.json import JsonConfig, write_json_file
from ..validate import dns_url
from .const import FILE_HASSIO_DNS
from .validate import SCHEMA_DNS_CONFIG
_LOGGER: logging.Logger = logging.getLogger(__name__)
COREDNS_TMPL: Path = Path(__file__).parents[1].joinpath("data/coredns.tmpl")
HOSTS_TMPL: Path = Path(__file__).parents[1].joinpath("data/hosts.tmpl")
RESOLV_TMPL: Path = Path(__file__).parents[1].joinpath("data/resolv.tmpl")
HOST_RESOLV: Path = Path("/etc/resolv.conf")
@ -49,22 +50,35 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
super().__init__(FILE_HASSIO_DNS, SCHEMA_DNS_CONFIG)
self.coresys: CoreSys = coresys
self.instance: DockerDNS = DockerDNS(coresys)
self.coredns_template: Optional[jinja2.Template] = None
self.resolv_template: Optional[jinja2.Template] = None
self.hosts_template: Optional[jinja2.Template] = None
self._hosts: List[HostEntry] = []
self._loop: bool = False
@property
def corefile(self) -> Path:
"""Return Path to corefile."""
return Path(self.sys_config.path_dns, "corefile")
@property
def hosts(self) -> Path:
"""Return Path to corefile."""
return Path(self.sys_config.path_dns, "hosts")
@property
def coredns_config(self) -> Path:
"""Return Path to coredns config file."""
return Path(self.sys_config.path_dns, "coredns.json")
@property
def locals(self) -> List[str]:
"""Return list of local system DNS servers."""
servers: List[str] = []
for server in self.sys_host.network.dns_servers:
if server in servers:
continue
with suppress(vol.Invalid):
dns_url(server)
servers.append(server)
return servers
@property
def servers(self) -> List[str]:
"""Return list of DNS servers."""
@ -140,22 +154,18 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
self.save_data()
# Initialize CoreDNS Template
try:
self.coredns_template = jinja2.Template(COREDNS_TMPL.read_text())
except OSError as err:
_LOGGER.error("Can't read coredns.tmpl: %s", err)
try:
self.resolv_template = jinja2.Template(RESOLV_TMPL.read_text())
except OSError as err:
_LOGGER.error("Can't read resolve.tmpl: %s", err)
try:
self.hosts_template = jinja2.Template(HOSTS_TMPL.read_text())
except OSError as err:
_LOGGER.error("Can't read hosts.tmpl: %s", err)
# Run CoreDNS
# If running, restart to update config/hosts
# this get shipped with Supervisor
with suppress(CoreDNSError):
if await self.instance.is_running():
await self.restart()
else:
if not await self.instance.is_running():
await self.start()
# Update supervisor
@ -215,7 +225,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
async def restart(self) -> None:
"""Restart CoreDNS plugin."""
self._write_corefile()
self._write_config()
_LOGGER.info("Restarting CoreDNS plugin")
try:
await self.instance.restart()
@ -225,7 +235,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
async def start(self) -> None:
"""Run CoreDNS."""
self._write_corefile()
self._write_config()
# Start Instance
_LOGGER.info("Starting CoreDNS plugin")
@ -268,47 +278,48 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
if b"plugin/loop: Loop" in log:
_LOGGER.error("Detected a DNS loop in local Network!")
self._loop = True
self.sys_resolution.create_issue(
IssueType.DNS_LOOP,
ContextType.PLUGIN,
reference=self.slug,
suggestions=[SuggestionType.EXECUTE_RESET],
)
else:
self._loop = False
def _write_corefile(self) -> None:
def _write_config(self) -> None:
"""Write CoreDNS config."""
debug: bool = self.sys_config.logging == LogLevel.DEBUG
dns_servers: List[str] = []
local_dns: List[str] = []
servers: List[str] = []
dns_locals: List[str] = []
# Prepare DNS serverlist: Prio 1 Manual, Prio 2 Local, Prio 3 Fallback
if not self._loop:
local_dns = self.sys_host.network.dns_servers or ["dns://127.0.0.11"]
servers = self.servers + local_dns
dns_servers = self.servers
dns_locals = self.locals
else:
_LOGGER.warning("Ignoring user DNS settings because of loop")
# Print some usefully debug data
_LOGGER.debug(
"config-dns = %s, local-dns = %s , backup-dns = CloudFlare DoT",
self.servers,
local_dns,
)
# Make sure, they are valid
for server in servers:
try:
dns_url(server)
if server not in dns_servers:
dns_servers.append(server)
except vol.Invalid:
_LOGGER.warning("Ignoring invalid DNS Server: %s", server)
# Generate config file
data = self.coredns_template.render(
locals=dns_servers, debug=self.sys_config.logging == LogLevel.DEBUG
"config-dns = %s, local-dns = %s , backup-dns = CloudFlare DoT / debug: %s",
dns_servers,
dns_locals,
debug,
)
# Write config to plugin
try:
self.corefile.write_text(data)
except OSError as err:
_LOGGER.error("Can't update corefile: %s", err)
write_json_file(
self.coredns_config,
{
"servers": dns_servers,
"locals": dns_locals,
"debug": debug,
},
)
except JsonFileError as err:
_LOGGER.error("Can't update coredns config: %s", err)
raise CoreDNSError() from err
def _init_hosts(self) -> None:
@ -328,12 +339,13 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
def write_hosts(self) -> None:
"""Write hosts from memory to file."""
# Generate config file
data = self.hosts_template.render(entries=self._hosts)
try:
with self.hosts.open("w") as hosts:
for entry in self._hosts:
hosts.write(f"{entry.ip_address!s} {' '.join(entry.names)}\n")
self.hosts.write_text(data)
except OSError as err:
_LOGGER.error("Can't write hosts file: %s", err)
_LOGGER.error("Can't update hosts: %s", err)
raise CoreDNSError() from err
def add_host(self, ipv4: IPv4Address, names: List[str], write: bool = True) -> None:
@ -428,7 +440,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
def _write_resolv(self, resolv_conf: Path) -> None:
"""Update/Write resolv.conf file."""
nameservers = [f"{self.sys_docker.network.dns!s}", "127.0.0.11"]
nameservers = [str(self.sys_docker.network.dns), "127.0.0.11"]
# Read resolv config
data = self.resolv_template.render(servers=nameservers)

View File

@ -41,6 +41,7 @@ class IssueType(str, Enum):
UPDATE_FAILED = "update_failed"
UPDATE_ROLLBACK = "update_rollback"
FATAL_ERROR = "fatal_error"
DNS_LOOP = "dns_loop"
class SuggestionType(str, Enum):
@ -50,3 +51,4 @@ class SuggestionType(str, Enum):
CREATE_FULL_SNAPSHOT = "create_full_snapshot"
EXECUTE_UPDATE = "execute_update"
EXECUTE_REPAIR = "execute_repair"
EXECUTE_RESET = "execute_reset"