mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-27 02:56:31 +00:00
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:
parent
db260dfbde
commit
50d36b857a
@ -48,7 +48,7 @@ class APICoreDNS(CoreSysAttributes):
|
|||||||
ATTR_UPDATE_AVAILABLE: self.sys_plugins.dns.need_update,
|
ATTR_UPDATE_AVAILABLE: self.sys_plugins.dns.need_update,
|
||||||
ATTR_HOST: str(self.sys_docker.network.dns),
|
ATTR_HOST: str(self.sys_docker.network.dns),
|
||||||
ATTR_SERVERS: self.sys_plugins.dns.servers,
|
ATTR_SERVERS: self.sys_plugins.dns.servers,
|
||||||
ATTR_LOCALS: self.sys_host.network.dns_servers,
|
ATTR_LOCALS: self.sys_plugins.dns.locals,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -1,2 +1,3 @@
|
|||||||
$supervisor hassio supervisor.local.hass.io hassio.local.hass.io
|
{% for entry in entries %}
|
||||||
$homeassistant homeassistant homeassistant.local.hass.io home-assistant.local.hass.io
|
{{- entry.ip_address }} {{ entry.names | join(" ") }}
|
||||||
|
{% endfor %}
|
||||||
|
@ -46,7 +46,7 @@ class DockerDNS(DockerInterface, CoreSysAttributes):
|
|||||||
detach=True,
|
detach=True,
|
||||||
environment={ENV_TIME: self.sys_config.timezone},
|
environment={ENV_TIME: self.sys_config.timezone},
|
||||||
volumes={
|
volumes={
|
||||||
str(self.sys_config.path_extern_dns): {"bind": "/config", "mode": "ro"}
|
str(self.sys_config.path_extern_dns): {"bind": "/config", "mode": "rw"}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -292,10 +292,7 @@ class Tasks(CoreSysAttributes):
|
|||||||
return
|
return
|
||||||
_LOGGER.warning("Watchdog found a problem with CoreDNS plugin!")
|
_LOGGER.warning("Watchdog found a problem with CoreDNS plugin!")
|
||||||
|
|
||||||
# Reset of failed
|
# Detect loop
|
||||||
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()
|
await self.sys_plugins.dns.loop_detection()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -18,15 +18,16 @@ from ..const import ATTR_IMAGE, ATTR_SERVERS, ATTR_VERSION, DNS_SUFFIX, LogLevel
|
|||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..docker.dns import DockerDNS
|
from ..docker.dns import DockerDNS
|
||||||
from ..docker.stats import DockerStats
|
from ..docker.stats import DockerStats
|
||||||
from ..exceptions import CoreDNSError, CoreDNSUpdateError, DockerError
|
from ..exceptions import CoreDNSError, CoreDNSUpdateError, DockerError, JsonFileError
|
||||||
from ..utils.json import JsonConfig
|
from ..resolution.const import ContextType, IssueType, SuggestionType
|
||||||
|
from ..utils.json import JsonConfig, write_json_file
|
||||||
from ..validate import dns_url
|
from ..validate import dns_url
|
||||||
from .const import FILE_HASSIO_DNS
|
from .const import FILE_HASSIO_DNS
|
||||||
from .validate import SCHEMA_DNS_CONFIG
|
from .validate import SCHEMA_DNS_CONFIG
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_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")
|
RESOLV_TMPL: Path = Path(__file__).parents[1].joinpath("data/resolv.tmpl")
|
||||||
HOST_RESOLV: Path = Path("/etc/resolv.conf")
|
HOST_RESOLV: Path = Path("/etc/resolv.conf")
|
||||||
|
|
||||||
@ -49,22 +50,35 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
|||||||
super().__init__(FILE_HASSIO_DNS, SCHEMA_DNS_CONFIG)
|
super().__init__(FILE_HASSIO_DNS, SCHEMA_DNS_CONFIG)
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.instance: DockerDNS = DockerDNS(coresys)
|
self.instance: DockerDNS = DockerDNS(coresys)
|
||||||
self.coredns_template: Optional[jinja2.Template] = None
|
|
||||||
self.resolv_template: Optional[jinja2.Template] = None
|
self.resolv_template: Optional[jinja2.Template] = None
|
||||||
|
self.hosts_template: Optional[jinja2.Template] = None
|
||||||
|
|
||||||
self._hosts: List[HostEntry] = []
|
self._hosts: List[HostEntry] = []
|
||||||
self._loop: bool = False
|
self._loop: bool = False
|
||||||
|
|
||||||
@property
|
|
||||||
def corefile(self) -> Path:
|
|
||||||
"""Return Path to corefile."""
|
|
||||||
return Path(self.sys_config.path_dns, "corefile")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hosts(self) -> Path:
|
def hosts(self) -> Path:
|
||||||
"""Return Path to corefile."""
|
"""Return Path to corefile."""
|
||||||
return Path(self.sys_config.path_dns, "hosts")
|
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
|
@property
|
||||||
def servers(self) -> List[str]:
|
def servers(self) -> List[str]:
|
||||||
"""Return list of DNS servers."""
|
"""Return list of DNS servers."""
|
||||||
@ -140,22 +154,18 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
|||||||
self.save_data()
|
self.save_data()
|
||||||
|
|
||||||
# Initialize CoreDNS Template
|
# 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:
|
try:
|
||||||
self.resolv_template = jinja2.Template(RESOLV_TMPL.read_text())
|
self.resolv_template = jinja2.Template(RESOLV_TMPL.read_text())
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
_LOGGER.error("Can't read resolve.tmpl: %s", 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
|
# Run CoreDNS
|
||||||
# If running, restart to update config/hosts
|
|
||||||
# this get shipped with Supervisor
|
|
||||||
with suppress(CoreDNSError):
|
with suppress(CoreDNSError):
|
||||||
if await self.instance.is_running():
|
if not await self.instance.is_running():
|
||||||
await self.restart()
|
|
||||||
else:
|
|
||||||
await self.start()
|
await self.start()
|
||||||
|
|
||||||
# Update supervisor
|
# Update supervisor
|
||||||
@ -215,7 +225,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
async def restart(self) -> None:
|
async def restart(self) -> None:
|
||||||
"""Restart CoreDNS plugin."""
|
"""Restart CoreDNS plugin."""
|
||||||
self._write_corefile()
|
self._write_config()
|
||||||
_LOGGER.info("Restarting CoreDNS plugin")
|
_LOGGER.info("Restarting CoreDNS plugin")
|
||||||
try:
|
try:
|
||||||
await self.instance.restart()
|
await self.instance.restart()
|
||||||
@ -225,7 +235,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
"""Run CoreDNS."""
|
"""Run CoreDNS."""
|
||||||
self._write_corefile()
|
self._write_config()
|
||||||
|
|
||||||
# Start Instance
|
# Start Instance
|
||||||
_LOGGER.info("Starting CoreDNS plugin")
|
_LOGGER.info("Starting CoreDNS plugin")
|
||||||
@ -268,47 +278,48 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
|||||||
if b"plugin/loop: Loop" in log:
|
if b"plugin/loop: Loop" in log:
|
||||||
_LOGGER.error("Detected a DNS loop in local Network!")
|
_LOGGER.error("Detected a DNS loop in local Network!")
|
||||||
self._loop = True
|
self._loop = True
|
||||||
|
self.sys_resolution.create_issue(
|
||||||
|
IssueType.DNS_LOOP,
|
||||||
|
ContextType.PLUGIN,
|
||||||
|
reference=self.slug,
|
||||||
|
suggestions=[SuggestionType.EXECUTE_RESET],
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self._loop = False
|
self._loop = False
|
||||||
|
|
||||||
def _write_corefile(self) -> None:
|
def _write_config(self) -> None:
|
||||||
"""Write CoreDNS config."""
|
"""Write CoreDNS config."""
|
||||||
|
debug: bool = self.sys_config.logging == LogLevel.DEBUG
|
||||||
dns_servers: List[str] = []
|
dns_servers: List[str] = []
|
||||||
local_dns: List[str] = []
|
dns_locals: List[str] = []
|
||||||
servers: List[str] = []
|
|
||||||
|
|
||||||
# Prepare DNS serverlist: Prio 1 Manual, Prio 2 Local, Prio 3 Fallback
|
# Prepare DNS serverlist: Prio 1 Manual, Prio 2 Local, Prio 3 Fallback
|
||||||
if not self._loop:
|
if not self._loop:
|
||||||
local_dns = self.sys_host.network.dns_servers or ["dns://127.0.0.11"]
|
dns_servers = self.servers
|
||||||
servers = self.servers + local_dns
|
dns_locals = self.locals
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("Ignoring user DNS settings because of loop")
|
_LOGGER.warning("Ignoring user DNS settings because of loop")
|
||||||
|
|
||||||
# Print some usefully debug data
|
# Print some usefully debug data
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"config-dns = %s, local-dns = %s , backup-dns = CloudFlare DoT",
|
"config-dns = %s, local-dns = %s , backup-dns = CloudFlare DoT / debug: %s",
|
||||||
self.servers,
|
dns_servers,
|
||||||
local_dns,
|
dns_locals,
|
||||||
|
debug,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make sure, they are valid
|
# Write config to plugin
|
||||||
for server in servers:
|
|
||||||
try:
|
try:
|
||||||
dns_url(server)
|
write_json_file(
|
||||||
if server not in dns_servers:
|
self.coredns_config,
|
||||||
dns_servers.append(server)
|
{
|
||||||
except vol.Invalid:
|
"servers": dns_servers,
|
||||||
_LOGGER.warning("Ignoring invalid DNS Server: %s", server)
|
"locals": dns_locals,
|
||||||
|
"debug": debug,
|
||||||
# Generate config file
|
},
|
||||||
data = self.coredns_template.render(
|
|
||||||
locals=dns_servers, debug=self.sys_config.logging == LogLevel.DEBUG
|
|
||||||
)
|
)
|
||||||
|
except JsonFileError as err:
|
||||||
try:
|
_LOGGER.error("Can't update coredns config: %s", err)
|
||||||
self.corefile.write_text(data)
|
|
||||||
except OSError as err:
|
|
||||||
_LOGGER.error("Can't update corefile: %s", err)
|
|
||||||
raise CoreDNSError() from err
|
raise CoreDNSError() from err
|
||||||
|
|
||||||
def _init_hosts(self) -> None:
|
def _init_hosts(self) -> None:
|
||||||
@ -328,12 +339,13 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
def write_hosts(self) -> None:
|
def write_hosts(self) -> None:
|
||||||
"""Write hosts from memory to file."""
|
"""Write hosts from memory to file."""
|
||||||
|
# Generate config file
|
||||||
|
data = self.hosts_template.render(entries=self._hosts)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.hosts.open("w") as hosts:
|
self.hosts.write_text(data)
|
||||||
for entry in self._hosts:
|
|
||||||
hosts.write(f"{entry.ip_address!s} {' '.join(entry.names)}\n")
|
|
||||||
except OSError as err:
|
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
|
raise CoreDNSError() from err
|
||||||
|
|
||||||
def add_host(self, ipv4: IPv4Address, names: List[str], write: bool = True) -> None:
|
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:
|
def _write_resolv(self, resolv_conf: Path) -> None:
|
||||||
"""Update/Write resolv.conf file."""
|
"""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
|
# Read resolv config
|
||||||
data = self.resolv_template.render(servers=nameservers)
|
data = self.resolv_template.render(servers=nameservers)
|
||||||
|
@ -41,6 +41,7 @@ class IssueType(str, Enum):
|
|||||||
UPDATE_FAILED = "update_failed"
|
UPDATE_FAILED = "update_failed"
|
||||||
UPDATE_ROLLBACK = "update_rollback"
|
UPDATE_ROLLBACK = "update_rollback"
|
||||||
FATAL_ERROR = "fatal_error"
|
FATAL_ERROR = "fatal_error"
|
||||||
|
DNS_LOOP = "dns_loop"
|
||||||
|
|
||||||
|
|
||||||
class SuggestionType(str, Enum):
|
class SuggestionType(str, Enum):
|
||||||
@ -50,3 +51,4 @@ class SuggestionType(str, Enum):
|
|||||||
CREATE_FULL_SNAPSHOT = "create_full_snapshot"
|
CREATE_FULL_SNAPSHOT = "create_full_snapshot"
|
||||||
EXECUTE_UPDATE = "execute_update"
|
EXECUTE_UPDATE = "execute_update"
|
||||||
EXECUTE_REPAIR = "execute_repair"
|
EXECUTE_REPAIR = "execute_repair"
|
||||||
|
EXECUTE_RESET = "execute_reset"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user