Skip to content

Add network interface settings for mDNS/LLMNR #5520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions supervisor/api/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
ATTR_INTERFACES,
ATTR_IPV4,
ATTR_IPV6,
ATTR_LLMNR,
ATTR_MAC,
ATTR_MDNS,
ATTR_METHOD,
ATTR_MODE,
ATTR_NAMESERVERS,
Expand All @@ -49,6 +51,7 @@
InterfaceMethod,
IpConfig,
IpSetting,
MulticastDnsMode,
VlanConfig,
WifiConfig,
)
Expand Down Expand Up @@ -90,6 +93,8 @@
vol.Optional(ATTR_IPV6): _SCHEMA_IPV6_CONFIG,
vol.Optional(ATTR_WIFI): _SCHEMA_WIFI_CONFIG,
vol.Optional(ATTR_ENABLED): vol.Boolean(),
vol.Optional(ATTR_MDNS): vol.Coerce(MulticastDnsMode),
vol.Optional(ATTR_LLMNR): vol.Coerce(MulticastDnsMode),
}
)

Expand Down Expand Up @@ -140,6 +145,8 @@ def interface_struct(interface: Interface) -> dict[str, Any]:
else None,
ATTR_WIFI: wifi_struct(interface.wifi) if interface.wifi else None,
ATTR_VLAN: vlan_struct(interface.vlan) if interface.vlan else None,
ATTR_MDNS: interface.mdns,
ATTR_LLMNR: interface.llmnr,
}


Expand Down Expand Up @@ -234,6 +241,10 @@ async def interface_update(self, request: web.Request) -> None:
)
elif key == ATTR_ENABLED:
interface.enabled = config
elif key == ATTR_MDNS:
interface.mdns = config
elif key == ATTR_LLMNR:
interface.llmnr = config

await asyncio.shield(self.sys_host.network.apply_changes(interface))

Expand Down
2 changes: 2 additions & 0 deletions supervisor/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@
ATTR_LABELS = "labels"
ATTR_LAST_BOOT = "last_boot"
ATTR_LEGACY = "legacy"
ATTR_LLMNR = "llmnr"
ATTR_LOCALS = "locals"
ATTR_LOCATION = "location"
ATTR_LOGGING = "logging"
Expand All @@ -239,6 +240,7 @@
ATTR_MACHINE = "machine"
ATTR_MAINTAINER = "maintainer"
ATTR_MAP = "map"
ATTR_MDNS = "mdns"
ATTR_MEMORY_LIMIT = "memory_limit"
ATTR_MEMORY_PERCENT = "memory_percent"
ATTR_MEMORY_USAGE = "memory_usage"
Expand Down
8 changes: 8 additions & 0 deletions supervisor/dbus/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,14 @@ class MulticastProtocolEnabled(StrEnum):
RESOLVE = "resolve"


class MulticastDnsValue(IntEnum):
"""Connection MulticastDNS (mdns/llmnr) values."""

OFF = 0
RESOLVE = 1
ANNOUNCE = 2


class DNSOverTLSEnabled(StrEnum):
"""DNS over TLS enabled."""

Expand Down
2 changes: 2 additions & 0 deletions supervisor/dbus/network/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class ConnectionProperties:
uuid: str | None
type: str | None
interface_name: str | None
mdns: int | None
llmnr: int | None


@dataclass(slots=True)
Expand Down
2 changes: 2 additions & 0 deletions supervisor/dbus/network/setting/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ async def reload(self):
data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_UUID),
data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_TYPE),
data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_INTERFACE_NAME),
data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_MDNS),
data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_LLMNR),
)

if CONF_ATTR_802_ETHERNET in data:
Expand Down
20 changes: 17 additions & 3 deletions supervisor/dbus/network/setting/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from dbus_fast import Variant

from ....host.configuration import VlanConfig
from ....host.const import InterfaceMethod, InterfaceType
from ....host.const import InterfaceMethod, InterfaceType, MulticastDnsMode
from ...const import MulticastDnsValue
from .. import NetworkManager
from . import (
CONF_ATTR_802_ETHERNET,
Expand Down Expand Up @@ -134,6 +135,16 @@ def _get_ipv6_connection_settings(ipv6setting) -> dict:
return ipv6


def _map_mdns_setting(mode: MulticastDnsMode | None) -> int:
mapping = {
MulticastDnsMode.OFF: MulticastDnsValue.OFF,
MulticastDnsMode.RESOLVE: MulticastDnsValue.RESOLVE,
MulticastDnsMode.ANNOUNCE: MulticastDnsValue.ANNOUNCE,
}

return int(mapping[mode] if mode else MulticastDnsValue.ANNOUNCE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is actually also the mode -1 (default). Since we created the connections with the hardcoded value 2 (announce) so far, it probably hardly ever happens.

I wonder if we actually should remove the hard coded announce (2) today and use default (-1) (the default is announce (2) too, at least on HAOS since home-assistant/operating-system#986, but it feels cleaner). Anyhow, this should be a separate PR, possible only once we start to officially deprecate old operating system versions.

But ideally we introduce the default option here already, since Supervisor might also encounter such a connection setting today. I guess this should be straight forward, essentially add Default with a value of -1 to MulticastDnsMode and MulticastDnsValue.



def get_connection_from_interface(
interface: Interface,
network_manager: NetworkManager,
Expand Down Expand Up @@ -162,13 +173,16 @@ def get_connection_from_interface(
if not uuid:
uuid = str(uuid4())

llmnr = _map_mdns_setting(interface.llmnr)
mdns = _map_mdns_setting(interface.mdns)

conn: dict[str, dict[str, Variant]] = {
CONF_ATTR_CONNECTION: {
CONF_ATTR_CONNECTION_ID: Variant("s", name),
CONF_ATTR_CONNECTION_UUID: Variant("s", uuid),
CONF_ATTR_CONNECTION_TYPE: Variant("s", iftype),
CONF_ATTR_CONNECTION_LLMNR: Variant("i", 2),
CONF_ATTR_CONNECTION_MDNS: Variant("i", 2),
CONF_ATTR_CONNECTION_LLMNR: Variant("i", llmnr),
CONF_ATTR_CONNECTION_MDNS: Variant("i", mdns),
CONF_ATTR_CONNECTION_AUTOCONNECT: Variant("b", True),
},
}
Expand Down
30 changes: 29 additions & 1 deletion supervisor/host/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@
ConnectionStateType,
DeviceType,
InterfaceMethod as NMInterfaceMethod,
MulticastDnsValue,
)
from ..dbus.network.connection import NetworkConnection
from ..dbus.network.interface import NetworkInterface
from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode
from .const import (
AuthMethod,
InterfaceMethod,
InterfaceType,
MulticastDnsMode,
WifiMode,
)


@dataclass(slots=True)
Expand Down Expand Up @@ -82,6 +89,8 @@ class Interface:
ipv6setting: IpSetting | None
wifi: WifiConfig | None
vlan: VlanConfig | None
mdns: MulticastDnsMode | None
llmnr: MulticastDnsMode | None

def equals_dbus_interface(self, inet: NetworkInterface) -> bool:
"""Return true if this represents the dbus interface."""
Expand Down Expand Up @@ -145,6 +154,13 @@ def from_dbus_interface(inet: NetworkInterface) -> "Interface":
and ConnectionStateFlags.IP6_READY in inet.connection.state_flags
)

if inet.settings and inet.settings.connection:
mdns = inet.settings.connection.mdns
llmnr = inet.settings.connection.llmnr
else:
mdns = None
llmnr = None

return Interface(
inet.name,
inet.hw_address,
Expand Down Expand Up @@ -181,6 +197,8 @@ def from_dbus_interface(inet: NetworkInterface) -> "Interface":
ipv6_setting,
Interface._map_nm_wifi(inet),
Interface._map_nm_vlan(inet),
Interface._map_nm_multicast_dns(mdns),
Interface._map_nm_multicast_dns(llmnr),
)

@staticmethod
Expand Down Expand Up @@ -258,3 +276,13 @@ def _map_nm_vlan(inet: NetworkInterface) -> WifiConfig | None:
return None

return VlanConfig(inet.settings.vlan.id, inet.settings.vlan.parent)

@staticmethod
def _map_nm_multicast_dns(mode: int | None) -> MulticastDnsMode | None:
mapping = {
MulticastDnsValue.OFF: MulticastDnsMode.OFF,
MulticastDnsValue.RESOLVE: MulticastDnsMode.RESOLVE,
MulticastDnsValue.ANNOUNCE: MulticastDnsMode.ANNOUNCE,
}

return mapping[mode]
8 changes: 8 additions & 0 deletions supervisor/host/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,11 @@ class LogFormatter(StrEnum):

PLAIN = "plain"
VERBOSE = "verbose"


class MulticastDnsMode(StrEnum):
"""Multicast DNS (MDNS/LLMNR) mode."""

OFF = "off"
RESOLVE = "resolve"
ANNOUNCE = "announce"
Loading