diff --git a/supervisor/api/network.py b/supervisor/api/network.py index b3c903f4a..8889392f6 100644 --- a/supervisor/api/network.py +++ b/supervisor/api/network.py @@ -29,6 +29,7 @@ from ..const import ( ATTR_METHOD, ATTR_MODE, ATTR_NAMESERVERS, + ATTR_NAMESERVERS_AUTO, ATTR_PARENT, ATTR_PRIMARY, ATTR_PSK, @@ -90,6 +91,7 @@ def ipconfig_struct(config: IpConfig) -> dict[str, Any]: return { ATTR_METHOD: config.method, ATTR_ADDRESS: [address.with_prefixlen for address in config.address], + ATTR_NAMESERVERS_AUTO: config.nameservers_auto, ATTR_NAMESERVERS: [str(address) for address in config.nameservers], ATTR_GATEWAY: str(config.gateway) if config.gateway else None, ATTR_READY: config.ready, @@ -200,15 +202,19 @@ class APINetwork(CoreSysAttributes): if key == ATTR_IPV4: interface.ipv4 = replace( interface.ipv4 - or IpConfig(InterfaceMethod.STATIC, [], None, [], None), + or IpConfig(InterfaceMethod.STATIC, [], None, False, [], None), **config, ) + if interface.ipv4.method == InterfaceMethod.AUTO: + interface.ipv4.nameservers_auto = ATTR_NAMESERVERS not in config elif key == ATTR_IPV6: interface.ipv6 = replace( interface.ipv6 - or IpConfig(InterfaceMethod.STATIC, [], None, [], None), + or IpConfig(InterfaceMethod.STATIC, [], None, False, [], None), **config, ) + if interface.ipv6.method == InterfaceMethod.AUTO: + interface.ipv6.nameservers_auto = ATTR_NAMESERVERS not in config elif key == ATTR_WIFI: interface.wifi = replace( interface.wifi diff --git a/supervisor/const.py b/supervisor/const.py index b1181af2a..2a14a5c4c 100644 --- a/supervisor/const.py +++ b/supervisor/const.py @@ -244,6 +244,7 @@ ATTR_MODE = "mode" ATTR_MULTICAST = "multicast" ATTR_NAME = "name" ATTR_NAMESERVERS = "nameservers" +ATTR_NAMESERVERS_AUTO = "nameservers_auto" ATTR_NETWORK = "network" ATTR_NETWORK_DESCRIPTION = "network_description" ATTR_NETWORK_RX = "network_rx" diff --git a/supervisor/dbus/network/configuration.py b/supervisor/dbus/network/configuration.py index c598a726d..47cb6b4d1 100644 --- a/supervisor/dbus/network/configuration.py +++ b/supervisor/dbus/network/configuration.py @@ -64,6 +64,7 @@ class IpProperties: """IP properties object for Network Manager.""" method: str | None + dns: list[str] | None @dataclass(slots=True) diff --git a/supervisor/dbus/network/setting/__init__.py b/supervisor/dbus/network/setting/__init__.py index 80758988d..001d310af 100644 --- a/supervisor/dbus/network/setting/__init__.py +++ b/supervisor/dbus/network/setting/__init__.py @@ -6,7 +6,7 @@ from typing import Any from dbus_fast import Variant from dbus_fast.aio.message_bus import MessageBus -from ....const import ATTR_METHOD, ATTR_MODE, ATTR_PSK, ATTR_SSID +from ....const import ATTR_DNS, ATTR_METHOD, ATTR_MODE, ATTR_PSK, ATTR_SSID from ...const import DBUS_NAME_NM from ...interface import DBusInterface from ...utils import dbus_connected @@ -75,7 +75,7 @@ def _merge_settings_attribute( class NetworkSetting(DBusInterface): """Network connection setting object for Network Manager. - https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.Settings.Connection.html + https://networkmanager.dev/docs/api/1.48.0/gdbus-org.freedesktop.NetworkManager.Settings.Connection.html """ bus_name: str = DBUS_NAME_NM @@ -229,11 +229,13 @@ class NetworkSetting(DBusInterface): if CONF_ATTR_IPV4 in data: self._ipv4 = IpProperties( data[CONF_ATTR_IPV4].get(ATTR_METHOD), + data[CONF_ATTR_IPV4].get(ATTR_DNS), ) if CONF_ATTR_IPV6 in data: self._ipv6 = IpProperties( data[CONF_ATTR_IPV6].get(ATTR_METHOD), + data[CONF_ATTR_IPV6].get(ATTR_DNS), ) if CONF_ATTR_MATCH in data: diff --git a/supervisor/dbus/network/setting/generate.py b/supervisor/dbus/network/setting/generate.py index c25fea29f..125d7bf09 100644 --- a/supervisor/dbus/network/setting/generate.py +++ b/supervisor/dbus/network/setting/generate.py @@ -76,13 +76,6 @@ def get_connection_from_interface( ipv4["method"] = Variant("s", "disabled") else: ipv4["method"] = Variant("s", "manual") - ipv4["dns"] = Variant( - "au", - [ - socket.htonl(int(ip_address)) - for ip_address in interface.ipv4.nameservers - ], - ) adressdata = [] for address in interface.ipv4.address: @@ -96,6 +89,18 @@ def get_connection_from_interface( ipv4["address-data"] = Variant("aa{sv}", adressdata) ipv4["gateway"] = Variant("s", str(interface.ipv4.gateway)) + if ( + interface.ipv4.method == InterfaceMethod.AUTO + and not interface.ipv4.nameservers_auto + ) or interface.ipv4.method == InterfaceMethod.STATIC: + ipv4["dns"] = Variant( + "au", + [ + socket.htonl(int(ip_address)) + for ip_address in interface.ipv4.nameservers + ], + ) + conn[CONF_ATTR_IPV4] = ipv4 ipv6 = {} @@ -105,9 +110,6 @@ def get_connection_from_interface( ipv6["method"] = Variant("s", "link-local") else: ipv6["method"] = Variant("s", "manual") - ipv6["dns"] = Variant( - "aay", [ip_address.packed for ip_address in interface.ipv6.nameservers] - ) adressdata = [] for address in interface.ipv6.address: @@ -121,6 +123,14 @@ def get_connection_from_interface( ipv6["address-data"] = Variant("aa{sv}", adressdata) ipv6["gateway"] = Variant("s", str(interface.ipv6.gateway)) + if ( + interface.ipv6.method == InterfaceMethod.AUTO + and not interface.ipv6.nameservers_auto + ) or interface.ipv6.method == InterfaceMethod.STATIC: + ipv6["dns"] = Variant( + "aay", [ip_address.packed for ip_address in interface.ipv6.nameservers] + ) + conn[CONF_ATTR_IPV6] = ipv6 if interface.type == InterfaceType.ETHERNET: diff --git a/supervisor/host/configuration.py b/supervisor/host/configuration.py index 8abf5c2fc..ac3734ce2 100644 --- a/supervisor/host/configuration.py +++ b/supervisor/host/configuration.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from ipaddress import IPv4Address, IPv4Interface, IPv6Address, IPv6Interface +import socket from ..dbus.const import ( ConnectionStateFlags, @@ -32,6 +33,7 @@ class IpConfig: method: InterfaceMethod address: list[IPv4Interface | IPv6Interface] gateway: IPv4Address | IPv6Address | None + nameservers_auto: bool nameservers: list[IPv4Address | IPv6Address] ready: bool | None @@ -102,6 +104,30 @@ class Interface: bool(inet.connection) and ConnectionStateFlags.IP6_READY in inet.connection.state_flags ) + + # Use Nameserver from configuration if present (means manual DNS override) + if inet.settings.ipv4.dns: + ipv4_nameservers_auto = False + ipv4_nameservers = [ + IPv4Address(socket.ntohl(ip)) for ip in inet.settings.ipv4.dns + ] + elif inet.connection.ipv4.nameservers: + ipv4_nameservers_auto = True + ipv4_nameservers = inet.connection.ipv4.nameservers + else: + ipv4_nameservers_auto = True + ipv4_nameservers = [] + + if inet.settings.ipv6.dns: + ipv6_nameservers_auto = False + ipv6_nameservers = [IPv6Address(bytes(ip)) for ip in inet.settings.ipv6.dns] + elif inet.connection.ipv6.nameservers: + ipv6_nameservers_auto = True + ipv6_nameservers = inet.connection.ipv6.nameservers + else: + ipv6_nameservers_auto = True + ipv6_nameservers = [] + return Interface( inet.name, inet.hw_address, @@ -111,27 +137,29 @@ class Interface: inet.primary, Interface._map_nm_type(inet.type), IpConfig( - ipv4_method, - inet.connection.ipv4.address if inet.connection.ipv4.address else [], - inet.connection.ipv4.gateway, - inet.connection.ipv4.nameservers - if inet.connection.ipv4.nameservers + method=ipv4_method, + address=inet.connection.ipv4.address + if inet.connection.ipv4.address else [], - ipv4_ready, + gateway=inet.connection.ipv4.gateway, + nameservers_auto=ipv4_nameservers_auto, + nameservers=ipv4_nameservers, + ready=ipv4_ready, ) if inet.connection and inet.connection.ipv4 - else IpConfig(ipv4_method, [], None, [], ipv4_ready), + else IpConfig(ipv4_method, [], None, True, [], ipv4_ready), IpConfig( - ipv6_method, - inet.connection.ipv6.address if inet.connection.ipv6.address else [], - inet.connection.ipv6.gateway, - inet.connection.ipv6.nameservers - if inet.connection.ipv6.nameservers + method=ipv6_method, + address=inet.connection.ipv6.address + if inet.connection.ipv6.address else [], - ipv6_ready, + gateway=inet.connection.ipv6.gateway, + nameservers_auto=ipv6_nameservers_auto, + nameservers=ipv6_nameservers, + ready=ipv6_ready, ) if inet.connection and inet.connection.ipv6 - else IpConfig(ipv6_method, [], None, [], ipv6_ready), + else IpConfig(ipv6_method, [], None, True, [], ipv6_ready), Interface._map_nm_wifi(inet), Interface._map_nm_vlan(inet), )