mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-07 17:26:32 +00:00
Add unsupported message if no systemd-resolved (#3487)
* Require resolved for supported systems * Added properties for dbus resolved
This commit is contained in:
parent
b451e555d3
commit
dd3a4a1f47
@ -1,11 +1,13 @@
|
||||
"""Constants for DBUS."""
|
||||
from enum import Enum
|
||||
from enum import Enum, IntEnum
|
||||
from socket import AF_INET, AF_INET6
|
||||
|
||||
DBUS_NAME_HAOS = "io.hass.os"
|
||||
DBUS_NAME_HOSTNAME = "org.freedesktop.hostname1"
|
||||
DBUS_NAME_LOGIND = "org.freedesktop.login1"
|
||||
DBUS_NAME_NM = "org.freedesktop.NetworkManager"
|
||||
DBUS_NAME_RAUC = "de.pengutronix.rauc"
|
||||
DBUS_NAME_RESOLVED = "org.freedesktop.resolve1"
|
||||
DBUS_NAME_SYSTEMD = "org.freedesktop.systemd1"
|
||||
DBUS_NAME_TIMEDATE = "org.freedesktop.timedate1"
|
||||
|
||||
@ -24,6 +26,7 @@ DBUS_IFACE_IP4CONFIG = "org.freedesktop.NetworkManager.IP4Config"
|
||||
DBUS_IFACE_IP6CONFIG = "org.freedesktop.NetworkManager.IP6Config"
|
||||
DBUS_IFACE_NM = "org.freedesktop.NetworkManager"
|
||||
DBUS_IFACE_RAUC_INSTALLER = "de.pengutronix.rauc.Installer"
|
||||
DBUS_IFACE_RESOLVED_MANAGER = "org.freedesktop.resolve1.Manager"
|
||||
DBUS_IFACE_SETTINGS_CONNECTION = "org.freedesktop.NetworkManager.Settings.Connection"
|
||||
DBUS_IFACE_SYSTEMD_MANAGER = "org.freedesktop.systemd1.Manager"
|
||||
DBUS_IFACE_TIMEDATE = "org.freedesktop.timedate1"
|
||||
@ -43,6 +46,7 @@ DBUS_OBJECT_HAOS_SYSTEM = "/io/hass/os/System"
|
||||
DBUS_OBJECT_HOSTNAME = "/org/freedesktop/hostname1"
|
||||
DBUS_OBJECT_LOGIND = "/org/freedesktop/login1"
|
||||
DBUS_OBJECT_NM = "/org/freedesktop/NetworkManager"
|
||||
DBUS_OBJECT_RESOLVED = "/org/freedesktop/resolve1"
|
||||
DBUS_OBJECT_SETTINGS = "/org/freedesktop/NetworkManager/Settings"
|
||||
DBUS_OBJECT_SYSTEMD = "/org/freedesktop/systemd1"
|
||||
DBUS_OBJECT_TIMEDATE = "/org/freedesktop/timedate1"
|
||||
@ -52,19 +56,33 @@ DBUS_ATTR_ACTIVE_CONNECTION = "ActiveConnection"
|
||||
DBUS_ATTR_ACTIVE_CONNECTIONS = "ActiveConnections"
|
||||
DBUS_ATTR_ADDRESS_DATA = "AddressData"
|
||||
DBUS_ATTR_BOOT_SLOT = "BootSlot"
|
||||
DBUS_ATTR_CACHE_STATISTICS = "CacheStatistics"
|
||||
DBUS_ATTR_CHASSIS = "Chassis"
|
||||
DBUS_ATTR_COMPATIBLE = "Compatible"
|
||||
DBUS_ATTR_CONFIGURATION = "Configuration"
|
||||
DBUS_ATTR_CONNECTION = "Connection"
|
||||
DBUS_ATTR_CONNECTION_ENABLED = "ConnectivityCheckEnabled"
|
||||
DBUS_ATTR_CURRENT_DEVICE = "CurrentDevice"
|
||||
DBUS_ATTR_CURRENT_DNS_SERVER = "CurrentDNSServer"
|
||||
DBUS_ATTR_CURRENT_DNS_SERVER_EX = "CurrentDNSServerEx"
|
||||
DBUS_ATTR_DEFAULT = "Default"
|
||||
DBUS_ATTR_DEPLOYMENT = "Deployment"
|
||||
DBUS_ATTR_DEVICE_INTERFACE = "Interface"
|
||||
DBUS_ATTR_DEVICE_TYPE = "DeviceType"
|
||||
DBUS_ATTR_DEVICES = "Devices"
|
||||
DBUS_ATTR_DIAGNOSTICS = "Diagnostics"
|
||||
DBUS_ATTR_DNS = "DNS"
|
||||
DBUS_ATTR_DNS_EX = "DNSEx"
|
||||
DBUS_ATTR_DNS_OVER_TLS = "DNSOverTLS"
|
||||
DBUS_ATTR_DNS_STUB_LISTENER = "DNSStubListener"
|
||||
DBUS_ATTR_DNSSEC = "DNSSEC"
|
||||
DBUS_ATTR_DNSSEC_NEGATIVE_TRUST_ANCHORS = "DNSSECNegativeTrustAnchors"
|
||||
DBUS_ATTR_DNSSEC_STATISTICS = "DNSSECStatistics"
|
||||
DBUS_ATTR_DNSSEC_SUPPORTED = "DNSSECSupported"
|
||||
DBUS_ATTR_DOMAINS = "Domains"
|
||||
DBUS_ATTR_DRIVER = "Driver"
|
||||
DBUS_ATTR_FALLBACK_DNS = "FallbackDNS"
|
||||
DBUS_ATTR_FALLBACK_DNS_EX = "FallbackDNSEx"
|
||||
DBUS_ATTR_FINISH_TIMESTAMP = "FinishTimestamp"
|
||||
DBUS_ATTR_FIRMWARE_TIMESTAMP_MONOTONIC = "FirmwareTimestampMonotonic"
|
||||
DBUS_ATTR_FREQUENCY = "Frequency"
|
||||
@ -76,11 +94,13 @@ DBUS_ATTR_IP6CONFIG = "Ip6Config"
|
||||
DBUS_ATTR_KERNEL_RELEASE = "KernelRelease"
|
||||
DBUS_ATTR_KERNEL_TIMESTAMP_MONOTONIC = "KernelTimestampMonotonic"
|
||||
DBUS_ATTR_LAST_ERROR = "LastError"
|
||||
DBUS_ATTR_LLMNR = "LLMNR"
|
||||
DBUS_ATTR_LLMNR_HOSTNAME = "LLMNRHostname"
|
||||
DBUS_ATTR_LOADER_TIMESTAMP_MONOTONIC = "LoaderTimestampMonotonic"
|
||||
DBUS_ATTR_LOCALRTC = "LocalRTC"
|
||||
DBUS_ATTR_MANAGED = "Managed"
|
||||
DBUS_ATTR_MODE = "Mode"
|
||||
DBUS_ATTR_MODE = "Mode"
|
||||
DBUS_ATTR_MULTICAST_DNS = "MulticastDNS"
|
||||
DBUS_ATTR_NAMESERVER_DATA = "NameserverData"
|
||||
DBUS_ATTR_NAMESERVERS = "Nameservers"
|
||||
DBUS_ATTR_NTP = "NTP"
|
||||
@ -89,6 +109,7 @@ DBUS_ATTR_OPERATING_SYSTEM_PRETTY_NAME = "OperatingSystemPrettyName"
|
||||
DBUS_ATTR_OPERATION = "Operation"
|
||||
DBUS_ATTR_PARSER_VERSION = "ParserVersion"
|
||||
DBUS_ATTR_PRIMARY_CONNECTION = "PrimaryConnection"
|
||||
DBUS_ATTR_RESOLV_CONF_MODE = "ResolvConfMode"
|
||||
DBUS_ATTR_RCMANAGER = "RcManager"
|
||||
DBUS_ATTR_SSID = "Ssid"
|
||||
DBUS_ATTR_STATE = "State"
|
||||
@ -97,6 +118,7 @@ DBUS_ATTR_STATIC_OPERATING_SYSTEM_CPE_NAME = "OperatingSystemCPEName"
|
||||
DBUS_ATTR_STRENGTH = "Strength"
|
||||
DBUS_ATTR_TIMEUSEC = "TimeUSec"
|
||||
DBUS_ATTR_TIMEZONE = "Timezone"
|
||||
DBUS_ATTR_TRANSACTION_STATISTICS = "TransactionStatistics"
|
||||
DBUS_ATTR_TYPE = "Type"
|
||||
DBUS_ATTR_USERSPACE_TIMESTAMP_MONOTONIC = "UserspaceTimestampMonotonic"
|
||||
DBUS_ATTR_UUID = "Uuid"
|
||||
@ -176,3 +198,53 @@ class WirelessMethodType(int, Enum):
|
||||
INFRASTRUCTURE = 2
|
||||
ACCESSPOINT = 3
|
||||
MESH = 4
|
||||
|
||||
|
||||
class DNSAddressFamily(IntEnum):
|
||||
"""Address family for DNS server."""
|
||||
|
||||
INET = AF_INET
|
||||
INET6 = AF_INET6
|
||||
|
||||
|
||||
class MulticastProtocolEnabled(str, Enum):
|
||||
"""Multicast protocol enabled or resolve."""
|
||||
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
RESOLVE = "resolve"
|
||||
|
||||
|
||||
class DNSOverTLSEnabled(str, Enum):
|
||||
"""DNS over TLS enabled."""
|
||||
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
OPPORTUNISTIC = "opportunistic"
|
||||
|
||||
|
||||
class DNSSECValidation(str, Enum):
|
||||
"""DNSSEC validation enforced."""
|
||||
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
ALLOW_DOWNGRADE = "allow-downgrade"
|
||||
|
||||
|
||||
class DNSStubListenerEnabled(str, Enum):
|
||||
"""DNS stub listener enabled."""
|
||||
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
TCP_ONLY = "tcp"
|
||||
UDP_ONLY = "udp"
|
||||
|
||||
|
||||
class ResolvConfMode(str, Enum):
|
||||
"""Resolv.conf management mode."""
|
||||
|
||||
FOREIGN = "foreign"
|
||||
MISSING = "missing"
|
||||
STATIC = "static"
|
||||
STUB = "stub"
|
||||
UPLINK = "uplink"
|
||||
|
@ -9,6 +9,7 @@ from .interface import DBusInterface
|
||||
from .logind import Logind
|
||||
from .network import NetworkManager
|
||||
from .rauc import Rauc
|
||||
from .resolved import Resolved
|
||||
from .systemd import Systemd
|
||||
from .timedate import TimeDate
|
||||
|
||||
@ -29,6 +30,7 @@ class DBusManager(CoreSysAttributes):
|
||||
self._network: NetworkManager = NetworkManager()
|
||||
self._agent: OSAgent = OSAgent()
|
||||
self._timedate: TimeDate = TimeDate()
|
||||
self._resolved: Resolved = Resolved()
|
||||
|
||||
@property
|
||||
def systemd(self) -> Systemd:
|
||||
@ -65,6 +67,11 @@ class DBusManager(CoreSysAttributes):
|
||||
"""Return the timedate interface."""
|
||||
return self._timedate
|
||||
|
||||
@property
|
||||
def resolved(self) -> Resolved:
|
||||
"""Return the resolved interface."""
|
||||
return self._resolved
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Connect interfaces to D-Bus."""
|
||||
if not SOCKET_DBUS.exists():
|
||||
@ -81,6 +88,7 @@ class DBusManager(CoreSysAttributes):
|
||||
self.timedate,
|
||||
self.network,
|
||||
self.rauc,
|
||||
self.resolved,
|
||||
]
|
||||
for dbus in dbus_loads:
|
||||
_LOGGER.info("Load dbus interface %s", dbus.name)
|
||||
|
188
supervisor/dbus/resolved.py
Normal file
188
supervisor/dbus/resolved.py
Normal file
@ -0,0 +1,188 @@
|
||||
"""D-Bus interface for systemd-resolved."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from ..exceptions import DBusError, DBusInterfaceError
|
||||
from ..utils.dbus import DBus
|
||||
from .const import (
|
||||
DBUS_ATTR_CACHE_STATISTICS,
|
||||
DBUS_ATTR_CURRENT_DNS_SERVER,
|
||||
DBUS_ATTR_CURRENT_DNS_SERVER_EX,
|
||||
DBUS_ATTR_DNS,
|
||||
DBUS_ATTR_DNS_EX,
|
||||
DBUS_ATTR_DNS_OVER_TLS,
|
||||
DBUS_ATTR_DNS_STUB_LISTENER,
|
||||
DBUS_ATTR_DNSSEC,
|
||||
DBUS_ATTR_DNSSEC_NEGATIVE_TRUST_ANCHORS,
|
||||
DBUS_ATTR_DNSSEC_STATISTICS,
|
||||
DBUS_ATTR_DNSSEC_SUPPORTED,
|
||||
DBUS_ATTR_DOMAINS,
|
||||
DBUS_ATTR_FALLBACK_DNS,
|
||||
DBUS_ATTR_FALLBACK_DNS_EX,
|
||||
DBUS_ATTR_LLMNR,
|
||||
DBUS_ATTR_LLMNR_HOSTNAME,
|
||||
DBUS_ATTR_MULTICAST_DNS,
|
||||
DBUS_ATTR_RESOLV_CONF_MODE,
|
||||
DBUS_ATTR_TRANSACTION_STATISTICS,
|
||||
DBUS_IFACE_RESOLVED_MANAGER,
|
||||
DBUS_NAME_RESOLVED,
|
||||
DBUS_OBJECT_RESOLVED,
|
||||
DNSAddressFamily,
|
||||
DNSOverTLSEnabled,
|
||||
DNSSECValidation,
|
||||
DNSStubListenerEnabled,
|
||||
MulticastProtocolEnabled,
|
||||
ResolvConfMode,
|
||||
)
|
||||
from .interface import DBusInterface, dbus_property
|
||||
from .utils import dbus_connected
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Resolved(DBusInterface):
|
||||
"""Handle D-Bus interface for systemd-resolved."""
|
||||
|
||||
name = DBUS_NAME_RESOLVED
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize Properties."""
|
||||
self.properties: dict[str, Any] = {}
|
||||
|
||||
async def connect(self):
|
||||
"""Connect to D-Bus."""
|
||||
try:
|
||||
self.dbus = await DBus.connect(DBUS_NAME_RESOLVED, DBUS_OBJECT_RESOLVED)
|
||||
except DBusError:
|
||||
_LOGGER.warning("Can't connect to systemd-resolved.")
|
||||
except DBusInterfaceError:
|
||||
_LOGGER.warning(
|
||||
"Host has no systemd-resolved support. DNS will not work correctly."
|
||||
)
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def cache_statistics(self) -> tuple[int, int, int] | None:
|
||||
"""Return current cache entries and hits and misses since last reset."""
|
||||
return self.properties[DBUS_ATTR_CACHE_STATISTICS]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def current_dns_server(
|
||||
self,
|
||||
) -> list[tuple[int, DNSAddressFamily, bytes]] | None:
|
||||
"""Return current DNS server."""
|
||||
return self.properties[DBUS_ATTR_CURRENT_DNS_SERVER]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def current_dns_server_ex(
|
||||
self,
|
||||
) -> list[tuple[int, DNSAddressFamily, bytes, int, str]] | None:
|
||||
"""Return current DNS server including port and server name."""
|
||||
return self.properties[DBUS_ATTR_CURRENT_DNS_SERVER_EX]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def dns(self) -> list[tuple[int, DNSAddressFamily, bytes]] | None:
|
||||
"""Return DNS servers in use."""
|
||||
return self.properties[DBUS_ATTR_DNS]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def dns_ex(self) -> list[tuple[int, DNSAddressFamily, bytes, int, str]] | None:
|
||||
"""Return DNS servers in use including port and server name."""
|
||||
return self.properties[DBUS_ATTR_DNS_EX]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def dns_over_tls(self) -> DNSOverTLSEnabled | None:
|
||||
"""Return DNS over TLS enabled."""
|
||||
return self.properties[DBUS_ATTR_DNS_OVER_TLS]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def dns_stub_listener(self) -> DNSStubListenerEnabled | None:
|
||||
"""Return DNS stub listener enabled on port 53."""
|
||||
return self.properties[DBUS_ATTR_DNS_STUB_LISTENER]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def dnssec(self) -> DNSSECValidation | None:
|
||||
"""Return DNSSEC validation enforced."""
|
||||
return self.properties[DBUS_ATTR_DNSSEC]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def dnssec_negative_trust_anchors(self) -> list[str] | None:
|
||||
"""Return DNSSEC negative trust anchors."""
|
||||
return self.properties[DBUS_ATTR_DNSSEC_NEGATIVE_TRUST_ANCHORS]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def dnssec_statistics(self) -> tuple[int, int, int, int] | None:
|
||||
"""Return Secure, insecure, bogus, and indeterminate DNSSEC validations since last reset."""
|
||||
return self.properties[DBUS_ATTR_DNSSEC_STATISTICS]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def dnssec_supported(self) -> bool | None:
|
||||
"""Return DNSSEC enabled and selected DNS servers support it."""
|
||||
return self.properties[DBUS_ATTR_DNSSEC_SUPPORTED]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def domains(self) -> list[tuple[int, str, bool]] | None:
|
||||
"""Return search and routing domains in use."""
|
||||
return self.properties[DBUS_ATTR_DOMAINS]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def fallback_dns(self) -> list[tuple[int, DNSAddressFamily, bytes]] | None:
|
||||
"""Return fallback DNS servers."""
|
||||
return self.properties[DBUS_ATTR_FALLBACK_DNS]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def fallback_dns_ex(
|
||||
self,
|
||||
) -> list[tuple[int, DNSAddressFamily, bytes, int, str]] | None:
|
||||
"""Return fallback DNS servers including port and server name."""
|
||||
return self.properties[DBUS_ATTR_FALLBACK_DNS_EX]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def llmnr(self) -> MulticastProtocolEnabled | None:
|
||||
"""Return LLMNR enabled."""
|
||||
return self.properties[DBUS_ATTR_LLMNR]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def llmnr_hostname(self) -> str | None:
|
||||
"""Return LLMNR hostname on network."""
|
||||
return self.properties[DBUS_ATTR_LLMNR_HOSTNAME]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def multicast_dns(self) -> MulticastProtocolEnabled | None:
|
||||
"""Return MDNS enabled."""
|
||||
return self.properties[DBUS_ATTR_MULTICAST_DNS]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def resolv_conf_mode(self) -> ResolvConfMode | None:
|
||||
"""Return how /etc/resolv.conf managed on host."""
|
||||
return self.properties[DBUS_ATTR_RESOLV_CONF_MODE]
|
||||
|
||||
@property
|
||||
@dbus_property
|
||||
def transaction_statistics(self) -> tuple[int, int] | None:
|
||||
"""Return transactions processing and processed since last reset."""
|
||||
return self.properties[DBUS_ATTR_TRANSACTION_STATISTICS]
|
||||
|
||||
@dbus_connected
|
||||
async def update(self):
|
||||
"""Update Properties."""
|
||||
self.properties = await self.dbus.get_properties(DBUS_IFACE_RESOLVED_MANAGER)
|
@ -41,6 +41,7 @@ class UnsupportedReason(str, Enum):
|
||||
SOFTWARE = "software"
|
||||
SOURCE_MODS = "source_mods"
|
||||
SYSTEMD = "systemd"
|
||||
SYSTEMD_RESOLVED = "systemd_resolved"
|
||||
|
||||
|
||||
class UnhealthyReason(str, Enum):
|
||||
|
34
supervisor/resolution/evaluations/resolved.py
Normal file
34
supervisor/resolution/evaluations/resolved.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""Evaluation class for systemd-resolved."""
|
||||
|
||||
from ...const import CoreState
|
||||
from ...coresys import CoreSys
|
||||
from ..const import UnsupportedReason
|
||||
from .base import EvaluateBase
|
||||
|
||||
|
||||
def setup(coresys: CoreSys) -> EvaluateBase:
|
||||
"""Initialize evaluation-setup function."""
|
||||
return EvaluateResolved(coresys)
|
||||
|
||||
|
||||
class EvaluateResolved(EvaluateBase):
|
||||
"""Evaluate systemd-resolved."""
|
||||
|
||||
@property
|
||||
def reason(self) -> UnsupportedReason:
|
||||
"""Return a UnsupportedReason enum."""
|
||||
return UnsupportedReason.SYSTEMD_RESOLVED
|
||||
|
||||
@property
|
||||
def on_failure(self) -> str:
|
||||
"""Return a string that is printed when self.evaluate is False."""
|
||||
return "Systemd-Resolved is required for DNS in Home Assistant."
|
||||
|
||||
@property
|
||||
def states(self) -> list[CoreState]:
|
||||
"""Return a list of valid states when this evaluation can run."""
|
||||
return [CoreState.SETUP]
|
||||
|
||||
async def evaluate(self) -> bool:
|
||||
"""Run evaluation."""
|
||||
return not self.sys_dbus.resolved.is_connected
|
115
tests/dbus/test_resolved.py
Normal file
115
tests/dbus/test_resolved.py
Normal file
@ -0,0 +1,115 @@
|
||||
"""Test systemd-resolved dbus interface."""
|
||||
|
||||
from socket import AF_INET6, inet_aton, inet_pton
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.dbus.const import (
|
||||
DNSOverTLSEnabled,
|
||||
DNSSECValidation,
|
||||
DNSStubListenerEnabled,
|
||||
MulticastProtocolEnabled,
|
||||
ResolvConfMode,
|
||||
)
|
||||
|
||||
DNS_IP_FIELDS = [
|
||||
"DNS",
|
||||
"DNSEx",
|
||||
"FallbackDNS",
|
||||
"FallbackDNSEx",
|
||||
"CurrentDNSServer",
|
||||
"CurrentDNSServerEx",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(name="coresys_ip_bytes")
|
||||
async def fixture_coresys_ip_bytes(coresys: CoreSys) -> CoreSys:
|
||||
"""Coresys with ip addresses correctly mocked as bytes."""
|
||||
get_properties = coresys.dbus.network.dbus.get_properties
|
||||
|
||||
async def mock_get_properties(dbus_obj, interface):
|
||||
reply = await get_properties(interface)
|
||||
|
||||
for field in DNS_IP_FIELDS:
|
||||
if field in reply and len(reply[field]) > 0:
|
||||
if isinstance(reply[field][0], list):
|
||||
for entry in reply[field]:
|
||||
entry[2] = bytes(entry[2])
|
||||
else:
|
||||
reply[field][2] = bytes(reply[field][2])
|
||||
|
||||
return reply
|
||||
|
||||
with patch("supervisor.utils.dbus.DBus.get_properties", new=mock_get_properties):
|
||||
yield coresys
|
||||
|
||||
|
||||
async def test_dbus_resolved_info(coresys_ip_bytes: CoreSys):
|
||||
"""Test systemd-resolved dbus connection."""
|
||||
coresys = coresys_ip_bytes
|
||||
|
||||
assert coresys.dbus.resolved.dns is None
|
||||
|
||||
await coresys.dbus.resolved.connect()
|
||||
await coresys.dbus.resolved.update()
|
||||
|
||||
assert coresys.dbus.resolved.llmnr_hostname == "homeassistant"
|
||||
assert coresys.dbus.resolved.llmnr == MulticastProtocolEnabled.YES
|
||||
assert coresys.dbus.resolved.multicast_dns == MulticastProtocolEnabled.RESOLVE
|
||||
assert coresys.dbus.resolved.dns_over_tls == DNSOverTLSEnabled.NO
|
||||
|
||||
assert len(coresys.dbus.resolved.dns) == 2
|
||||
assert coresys.dbus.resolved.dns[0] == [0, 2, inet_aton("127.0.0.1")]
|
||||
assert coresys.dbus.resolved.dns[1] == [0, 10, inet_pton(AF_INET6, "::1")]
|
||||
assert len(coresys.dbus.resolved.dns_ex) == 2
|
||||
assert coresys.dbus.resolved.dns_ex[0] == [0, 2, inet_aton("127.0.0.1"), 0, ""]
|
||||
assert coresys.dbus.resolved.dns_ex[1] == [0, 10, inet_pton(AF_INET6, "::1"), 0, ""]
|
||||
|
||||
assert len(coresys.dbus.resolved.fallback_dns) == 2
|
||||
assert coresys.dbus.resolved.fallback_dns[0] == [0, 2, inet_aton("1.1.1.1")]
|
||||
assert coresys.dbus.resolved.fallback_dns[1] == [
|
||||
0,
|
||||
10,
|
||||
inet_pton(AF_INET6, "2606:4700:4700::1111"),
|
||||
]
|
||||
assert len(coresys.dbus.resolved.fallback_dns_ex) == 2
|
||||
assert coresys.dbus.resolved.fallback_dns_ex[0] == [
|
||||
0,
|
||||
2,
|
||||
inet_aton("1.1.1.1"),
|
||||
0,
|
||||
"cloudflare-dns.com",
|
||||
]
|
||||
assert coresys.dbus.resolved.fallback_dns_ex[1] == [
|
||||
0,
|
||||
10,
|
||||
inet_pton(AF_INET6, "2606:4700:4700::1111"),
|
||||
0,
|
||||
"cloudflare-dns.com",
|
||||
]
|
||||
|
||||
assert coresys.dbus.resolved.current_dns_server == [0, 2, inet_aton("127.0.0.1")]
|
||||
assert coresys.dbus.resolved.current_dns_server_ex == [
|
||||
0,
|
||||
2,
|
||||
inet_aton("127.0.0.1"),
|
||||
0,
|
||||
"",
|
||||
]
|
||||
|
||||
assert len(coresys.dbus.resolved.domains) == 1
|
||||
assert coresys.dbus.resolved.domains[0] == [0, "local.hass.io", False]
|
||||
|
||||
assert coresys.dbus.resolved.transaction_statistics == [0, 100000]
|
||||
assert coresys.dbus.resolved.cache_statistics == [10, 50000, 10000]
|
||||
assert coresys.dbus.resolved.dnssec == DNSSECValidation.NO
|
||||
assert coresys.dbus.resolved.dnssec_statistics == [0, 0, 0, 0]
|
||||
assert coresys.dbus.resolved.dnssec_supported is False
|
||||
assert coresys.dbus.resolved.dnssec_negative_trust_anchors == [
|
||||
"168.192.in-addr.arpa",
|
||||
"local",
|
||||
]
|
||||
assert coresys.dbus.resolved.dns_stub_listener == DNSStubListenerEnabled.NO
|
||||
assert coresys.dbus.resolved.resolv_conf_mode == ResolvConfMode.FOREIGN
|
194
tests/fixtures/org_freedesktop_resolve1.xml
vendored
Normal file
194
tests/fixtures/org_freedesktop_resolve1.xml
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.freedesktop.DBus.Peer">
|
||||
<method name="Ping"/>
|
||||
<method name="GetMachineId">
|
||||
<arg type="s" name="machine_uuid" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg name="data" type="s" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg name="interface" direction="in" type="s"/>
|
||||
<arg name="property" direction="in" type="s"/>
|
||||
<arg name="value" direction="out" type="v"/>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg name="interface" direction="in" type="s"/>
|
||||
<arg name="properties" direction="out" type="a{sv}"/>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg name="interface" direction="in" type="s"/>
|
||||
<arg name="property" direction="in" type="s"/>
|
||||
<arg name="value" direction="in" type="v"/>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="s" name="interface"/>
|
||||
<arg type="a{sv}" name="changed_properties"/>
|
||||
<arg type="as" name="invalidated_properties"/>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.resolve1.Manager">
|
||||
<property name="LLMNRHostname" type="s" access="read">
|
||||
</property>
|
||||
<property name="LLMNR" type="s" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="MulticastDNS" type="s" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="DNSOverTLS" type="s" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="DNS" type="a(iiay)" access="read">
|
||||
</property>
|
||||
<property name="DNSEx" type="a(iiayqs)" access="read">
|
||||
</property>
|
||||
<property name="FallbackDNS" type="a(iiay)" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
|
||||
</property>
|
||||
<property name="FallbackDNSEx" type="a(iiayqs)" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
|
||||
</property>
|
||||
<property name="CurrentDNSServer" type="(iiay)" access="read">
|
||||
</property>
|
||||
<property name="CurrentDNSServerEx" type="(iiayqs)" access="read">
|
||||
</property>
|
||||
<property name="Domains" type="a(isb)" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="TransactionStatistics" type="(tt)" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="CacheStatistics" type="(ttt)" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="DNSSEC" type="s" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="DNSSECStatistics" type="(tttt)" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="DNSSECSupported" type="b" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="DNSSECNegativeTrustAnchors" type="as" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="DNSStubListener" type="s" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<property name="ResolvConfMode" type="s" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
|
||||
</property>
|
||||
<method name="ResolveHostname">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="s" name="name" direction="in"/>
|
||||
<arg type="i" name="family" direction="in"/>
|
||||
<arg type="t" name="flags" direction="in"/>
|
||||
<arg type="a(iiay)" name="addresses" direction="out"/>
|
||||
<arg type="s" name="canonical" direction="out"/>
|
||||
<arg type="t" name="flags" direction="out"/>
|
||||
</method>
|
||||
<method name="ResolveAddress">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="i" name="family" direction="in"/>
|
||||
<arg type="ay" name="address" direction="in"/>
|
||||
<arg type="t" name="flags" direction="in"/>
|
||||
<arg type="a(is)" name="names" direction="out"/>
|
||||
<arg type="t" name="flags" direction="out"/>
|
||||
</method>
|
||||
<method name="ResolveRecord">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="s" name="name" direction="in"/>
|
||||
<arg type="q" name="class" direction="in"/>
|
||||
<arg type="q" name="type" direction="in"/>
|
||||
<arg type="t" name="flags" direction="in"/>
|
||||
<arg type="a(iqqay)" name="records" direction="out"/>
|
||||
<arg type="t" name="flags" direction="out"/>
|
||||
</method>
|
||||
<method name="ResolveService">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="s" name="name" direction="in"/>
|
||||
<arg type="s" name="type" direction="in"/>
|
||||
<arg type="s" name="domain" direction="in"/>
|
||||
<arg type="i" name="family" direction="in"/>
|
||||
<arg type="t" name="flags" direction="in"/>
|
||||
<arg type="a(qqqsa(iiay)s)" name="srv_data" direction="out"/>
|
||||
<arg type="aay" name="txt_data" direction="out"/>
|
||||
<arg type="s" name="canonical_name" direction="out"/>
|
||||
<arg type="s" name="canonical_type" direction="out"/>
|
||||
<arg type="s" name="canonical_domain" direction="out"/>
|
||||
<arg type="t" name="flags" direction="out"/>
|
||||
</method>
|
||||
<method name="GetLink">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="o" name="path" direction="out"/>
|
||||
</method>
|
||||
<method name="SetLinkDNS">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="a(iay)" name="addresses" direction="in"/>
|
||||
</method>
|
||||
<method name="SetLinkDNSEx">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="a(iayqs)" name="addresses" direction="in"/>
|
||||
</method>
|
||||
<method name="SetLinkDomains">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="a(sb)" name="domains" direction="in"/>
|
||||
</method>
|
||||
<method name="SetLinkDefaultRoute">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="b" name="enable" direction="in"/>
|
||||
</method>
|
||||
<method name="SetLinkLLMNR">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="s" name="mode" direction="in"/>
|
||||
</method>
|
||||
<method name="SetLinkMulticastDNS">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="s" name="mode" direction="in"/>
|
||||
</method>
|
||||
<method name="SetLinkDNSOverTLS">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="s" name="mode" direction="in"/>
|
||||
</method>
|
||||
<method name="SetLinkDNSSEC">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="s" name="mode" direction="in"/>
|
||||
</method>
|
||||
<method name="SetLinkDNSSECNegativeTrustAnchors">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
<arg type="as" name="names" direction="in"/>
|
||||
</method>
|
||||
<method name="RevertLink">
|
||||
<arg type="i" name="ifindex" direction="in"/>
|
||||
</method>
|
||||
<method name="RegisterService">
|
||||
<arg type="s" name="name" direction="in"/>
|
||||
<arg type="s" name="name_template" direction="in"/>
|
||||
<arg type="s" name="type" direction="in"/>
|
||||
<arg type="q" name="service_port" direction="in"/>
|
||||
<arg type="q" name="service_priority" direction="in"/>
|
||||
<arg type="q" name="service_weight" direction="in"/>
|
||||
<arg type="aa{say}" name="txt_datas" direction="in"/>
|
||||
<arg type="o" name="service_path" direction="out"/>
|
||||
</method>
|
||||
<method name="UnregisterService">
|
||||
<arg type="o" name="service_path" direction="in"/>
|
||||
</method>
|
||||
<method name="ResetStatistics">
|
||||
</method>
|
||||
<method name="FlushCaches">
|
||||
</method>
|
||||
<method name="ResetServerFeatures">
|
||||
</method>
|
||||
</interface>
|
||||
<node name="link"/>
|
||||
<node name="dnssd"/>
|
||||
</node>
|
39
tests/fixtures/org_freedesktop_resolve1_Manager.json
vendored
Normal file
39
tests/fixtures/org_freedesktop_resolve1_Manager.json
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"LLMNRHostname": "homeassistant",
|
||||
"LLMNR": "yes",
|
||||
"MulticastDNS": "resolve",
|
||||
"DNSOverTLS": "no",
|
||||
"DNS": [
|
||||
[0, 2, [127, 0, 0, 1]],
|
||||
[0, 10, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]]
|
||||
],
|
||||
"DNSEx": [
|
||||
[0, 2, [127, 0, 0, 1], 0, ""],
|
||||
[0, 10, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 0, ""]
|
||||
],
|
||||
"FallbackDNS": [
|
||||
[0, 2, [1, 1, 1, 1]],
|
||||
[0, 10, [38, 6, 71, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17]]
|
||||
],
|
||||
"FallbackDNSEx": [
|
||||
[0, 2, [1, 1, 1, 1], 0, "cloudflare-dns.com"],
|
||||
[
|
||||
0,
|
||||
10,
|
||||
[38, 6, 71, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17],
|
||||
0,
|
||||
"cloudflare-dns.com"
|
||||
]
|
||||
],
|
||||
"CurrentDNSServer": [0, 2, [127, 0, 0, 1]],
|
||||
"CurrentDNSServerEx": [0, 2, [127, 0, 0, 1], 0, ""],
|
||||
"Domains": [[0, "local.hass.io", false]],
|
||||
"TransactionStatistics": [0, 100000],
|
||||
"CacheStatistics": [10, 50000, 10000],
|
||||
"DNSSEC": "no",
|
||||
"DNSSECStatistics": [0, 0, 0, 0],
|
||||
"DNSSECSupported": false,
|
||||
"DNSSECNegativeTrustAnchors": ["168.192.in-addr.arpa", "local"],
|
||||
"DNSStubListener": "no",
|
||||
"ResolvConfMode": "foreign"
|
||||
}
|
49
tests/resolution/evaluation/test_evaluate_resolved.py
Normal file
49
tests/resolution/evaluation/test_evaluate_resolved.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""Test evaluate systemd-resolved."""
|
||||
|
||||
from unittest.mock import PropertyMock, patch
|
||||
|
||||
from supervisor.const import CoreState
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.resolution.evaluations.resolved import EvaluateResolved
|
||||
|
||||
|
||||
async def test_evaluation(coresys: CoreSys):
|
||||
"""Test evaluation."""
|
||||
resolved = EvaluateResolved(coresys)
|
||||
coresys.core.state = CoreState.SETUP
|
||||
|
||||
assert resolved.reason not in coresys.resolution.unsupported
|
||||
|
||||
with patch.object(
|
||||
type(coresys.dbus.resolved), "is_connected", PropertyMock(return_value=False)
|
||||
):
|
||||
await resolved()
|
||||
assert resolved.reason in coresys.resolution.unsupported
|
||||
|
||||
await resolved()
|
||||
assert resolved.reason not in coresys.resolution.unsupported
|
||||
|
||||
|
||||
async def test_did_run(coresys: CoreSys):
|
||||
"""Test that the evaluation ran as expected."""
|
||||
resolved = EvaluateResolved(coresys)
|
||||
should_run = resolved.states
|
||||
should_not_run = [state for state in CoreState if state not in should_run]
|
||||
assert len(should_run) != 0
|
||||
assert len(should_not_run) != 0
|
||||
|
||||
with patch(
|
||||
"supervisor.resolution.evaluations.resolved.EvaluateResolved.evaluate",
|
||||
return_value=None,
|
||||
) as evaluate:
|
||||
for state in should_run:
|
||||
coresys.core.state = state
|
||||
await resolved()
|
||||
evaluate.assert_called_once()
|
||||
evaluate.reset_mock()
|
||||
|
||||
for state in should_not_run:
|
||||
coresys.core.state = state
|
||||
await resolved()
|
||||
evaluate.assert_not_called()
|
||||
evaluate.reset_mock()
|
Loading…
x
Reference in New Issue
Block a user