diff --git a/setup.py b/setup.py index 75549ab03..94252c621 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,8 @@ setup( "supervisor.addons", "supervisor.api", "supervisor.dbus", + "supervisor.dbus.payloads", + "supervisor.dbus.network", "supervisor.discovery", "supervisor.discovery.services", "supervisor.services", diff --git a/supervisor/api/network.py b/supervisor/api/network.py index d34a95c16..65471561a 100644 --- a/supervisor/api/network.py +++ b/supervisor/api/network.py @@ -93,6 +93,6 @@ class APINetwork(CoreSysAttributes): self.sys_host.network.interfaces[req_interface].update_settings(**args) ) - await asyncio.shield(self.sys_host.network.update()) + await asyncio.shield(self.sys_host.reload()) return await asyncio.shield(self.interface_info(request)) diff --git a/supervisor/dbus/const.py b/supervisor/dbus/const.py index 409d8facb..4d3754215 100644 --- a/supervisor/dbus/const.py +++ b/supervisor/dbus/const.py @@ -72,3 +72,10 @@ class InterfaceMethodSimple(str, Enum): DHCP = "dhcp" STATIC = "static" + + +class ConnectionType(str, Enum): + """Connection type.""" + + ETHERNET = "802-3-ethernet" + WIRELESS = "802-11-wireless" diff --git a/supervisor/dbus/network/__init__.py b/supervisor/dbus/network/__init__.py index 27d259ef0..42d05d617 100644 --- a/supervisor/dbus/network/__init__.py +++ b/supervisor/dbus/network/__init__.py @@ -2,13 +2,14 @@ import logging from typing import Dict, Optional -from ...exceptions import DBusError, DBusInterfaceError +from ...exceptions import DBusError, DBusFatalError, DBusInterfaceError from ...utils.gdbus import DBus from ..const import ( DBUS_ATTR_ACTIVE_CONNECTIONS, DBUS_ATTR_PRIMARY_CONNECTION, DBUS_NAME_NM, DBUS_OBJECT_NM, + ConnectionType, ) from ..interface import DBusInterface from ..utils import dbus_connected @@ -65,11 +66,17 @@ class NetworkManager(DBusInterface): await interface.connect(self.dbus, connection) - if not interface.connection.default: + if interface.connection.type not in [ + ConnectionType.ETHERNET, + ConnectionType.WIRELESS, + ]: continue try: await interface.connection.update_information() - except IndexError: + except (IndexError, DBusFatalError): + continue + + if not interface.connection.ip4_config: continue if interface.connection.object_path == data.get( diff --git a/supervisor/dbus/network/connection.py b/supervisor/dbus/network/connection.py index 902043875..7a67d420f 100644 --- a/supervisor/dbus/network/connection.py +++ b/supervisor/dbus/network/connection.py @@ -22,6 +22,7 @@ from ..const import ( DBUS_NAME_DEVICE, DBUS_NAME_IP4CONFIG, DBUS_NAME_NM, + DBUS_OBJECT_BASE, ) from .configuration import ( AddressData, @@ -91,6 +92,9 @@ class NetworkConnection(NetworkAttributes): async def update_information(self): """Update the information for childs .""" + if self._properties[DBUS_ATTR_IP4CONFIG] == DBUS_OBJECT_BASE: + return + settings = await DBus.connect( DBUS_NAME_NM, self._properties[DBUS_ATTR_CONNECTION] ) diff --git a/supervisor/dbus/network/interface.py b/supervisor/dbus/network/interface.py index df05214a1..01a28117f 100644 --- a/supervisor/dbus/network/interface.py +++ b/supervisor/dbus/network/interface.py @@ -1,5 +1,4 @@ """NetworkInterface object for Network Manager.""" -from ...const import ATTR_ADDRESS, ATTR_DNS, ATTR_GATEWAY, ATTR_METHOD, ATTR_PREFIX from ...utils.gdbus import DBus from ..const import ( DBUS_NAME_CONNECTION_ACTIVE, @@ -7,8 +6,8 @@ from ..const import ( DBUS_OBJECT_BASE, InterfaceMethod, ) +from ..payloads.interface_update import interface_update_payload from .connection import NetworkConnection -from .utils import ip2int class NetworkInterface: @@ -85,42 +84,9 @@ class NetworkInterface: async def update_settings(self, **kwargs) -> None: """Update IP configuration used for this interface.""" - if kwargs.get(ATTR_DNS): - kwargs[ATTR_DNS] = [ip2int(x.strip()) for x in kwargs[ATTR_DNS]] + payload = interface_update_payload(self, **kwargs) - if kwargs.get(ATTR_METHOD): - kwargs[ATTR_METHOD] = ( - InterfaceMethod.MANUAL - if kwargs[ATTR_METHOD] == "static" - else InterfaceMethod.AUTO - ) - - if kwargs.get(ATTR_ADDRESS): - if "/" in kwargs[ATTR_ADDRESS]: - kwargs[ATTR_PREFIX] = kwargs[ATTR_ADDRESS].split("/")[-1] - kwargs[ATTR_ADDRESS] = kwargs[ATTR_ADDRESS].split("/")[0] - kwargs[ATTR_METHOD] = InterfaceMethod.MANUAL - - await self.connection.settings.dbus.Settings.Connection.Update( - f"""{{ - 'connection': - {{ - 'id': <'{self.id}'>, - 'type': <'{self.type}'> - }}, - 'ipv4': - {{ - 'method': <'{kwargs.get(ATTR_METHOD, self.method)}'>, - 'dns': <[{",".join([f"uint32 {x}" for x in kwargs.get(ATTR_DNS, self.nameservers)])}]>, - 'address-data': <[ - {{ - 'address': <'{kwargs.get(ATTR_ADDRESS, self.ip_address)}'>, - 'prefix': - }}]>, - 'gateway': <'{kwargs.get(ATTR_GATEWAY, self.gateway)}'> - }} - }}""" - ) + await self.connection.settings.dbus.Settings.Connection.Update(payload) await self.nm_dbus.ActivateConnection( self.connection.settings.dbus.object_path, diff --git a/supervisor/dbus/payloads/__init__.py b/supervisor/dbus/payloads/__init__.py new file mode 100644 index 000000000..5ae0b813c --- /dev/null +++ b/supervisor/dbus/payloads/__init__.py @@ -0,0 +1 @@ +"""Init file for DBUS payloads.""" diff --git a/supervisor/dbus/payloads/interface_update.py b/supervisor/dbus/payloads/interface_update.py new file mode 100644 index 000000000..b6bdf3797 --- /dev/null +++ b/supervisor/dbus/payloads/interface_update.py @@ -0,0 +1,55 @@ +"""Payload generators for DBUS communication.""" +from ...const import ATTR_ADDRESS, ATTR_DNS, ATTR_GATEWAY, ATTR_METHOD, ATTR_PREFIX +from ..const import InterfaceMethod +from ..network.utils import ip2int + + +def interface_update_payload(interface, **kwargs) -> str: + """Generate a payload for network interface update.""" + if kwargs.get(ATTR_DNS): + kwargs[ATTR_DNS] = [ip2int(x.strip()) for x in kwargs[ATTR_DNS]] + + if kwargs.get(ATTR_METHOD): + kwargs[ATTR_METHOD] = ( + InterfaceMethod.MANUAL + if kwargs[ATTR_METHOD] == "static" + else InterfaceMethod.AUTO + ) + + if kwargs.get(ATTR_ADDRESS): + if "/" in kwargs[ATTR_ADDRESS]: + kwargs[ATTR_PREFIX] = kwargs[ATTR_ADDRESS].split("/")[-1] + kwargs[ATTR_ADDRESS] = kwargs[ATTR_ADDRESS].split("/")[0] + kwargs[ATTR_METHOD] = InterfaceMethod.MANUAL + + if kwargs.get(ATTR_METHOD) == "auto": + return f"""{{ + 'connection': + {{ + 'id': <'{interface.id}'>, + 'type': <'{interface.type}'> + }}, + 'ipv4': + {{ + 'method': <'{InterfaceMethod.AUTO}'> + }} + }}""" + + return f"""{{ + 'connection': + {{ + 'id': <'{interface.id}'>, + 'type': <'{interface.type}'> + }}, + 'ipv4': + {{ + 'method': <'{InterfaceMethod.MANUAL}'>, + 'dns': <[{",".join([f"uint32 {x}" for x in kwargs.get(ATTR_DNS, interface.nameservers)])}]>, + 'address-data': <[ + {{ + 'address': <'{kwargs.get(ATTR_ADDRESS, interface.ip_address)}'>, + 'prefix': + }}]>, + 'gateway': <'{kwargs.get(ATTR_GATEWAY, interface.gateway)}'> + }} + }}""" diff --git a/supervisor/host/__init__.py b/supervisor/host/__init__.py index 35c304d67..933ef6b63 100644 --- a/supervisor/host/__init__.py +++ b/supervisor/host/__init__.py @@ -69,7 +69,7 @@ class HostManager(CoreSysAttributes): [HostFeature.REBOOT, HostFeature.SHUTDOWN, HostFeature.SERVICES] ) - if self.sys_dbus.network.is_connected: + if self.sys_dbus.network.is_connected and self.sys_dbus.network.interfaces: features.append(HostFeature.NETWORK) if self.sys_dbus.hostname.is_connected: diff --git a/tests/conftest.py b/tests/conftest.py index 1e6490a79..8640ee1a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,7 @@ from supervisor.bootstrap import initialize_coresys from supervisor.coresys import CoreSys from supervisor.dbus.const import DBUS_NAME_NM, DBUS_OBJECT_BASE from supervisor.dbus.network import NetworkManager +from supervisor.dbus.network.interface import NetworkInterface from supervisor.docker import DockerAPI from supervisor.utils.gdbus import DBus @@ -124,3 +125,12 @@ async def api_client(aiohttp_client, coresys): api.webapp = web.Application() await api.load() yield await aiohttp_client(api.webapp) + + +@pytest.fixture +async def network_interface(dbus): + """Fixture for a network interface.""" + interface = NetworkInterface() + await interface.connect(dbus, "/org/freedesktop/NetworkManager/ActiveConnection/1") + await interface.connection.update_information() + yield interface diff --git a/tests/dbus/payloads/test_interface_update_payload.py b/tests/dbus/payloads/test_interface_update_payload.py new file mode 100644 index 000000000..ebcb7c6c4 --- /dev/null +++ b/tests/dbus/payloads/test_interface_update_payload.py @@ -0,0 +1,49 @@ +"""Test interface update payload.""" +import pytest + +from supervisor.dbus.payloads.interface_update import interface_update_payload +from supervisor.utils.gdbus import DBus + + +@pytest.mark.asyncio +async def test_interface_update_payload(network_interface): + """Test interface update payload.""" + assert ( + interface_update_payload(network_interface, **{"method": "auto"}) + == """{ + 'connection': + { + 'id': <'Wired connection 1'>, + 'type': <'802-3-ethernet'> + }, + 'ipv4': + { + 'method': <'auto'> + } + }""" + ) + + assert ( + interface_update_payload(network_interface, **{}) + == """{ + 'connection': + { + 'id': <'Wired connection 1'>, + 'type': <'802-3-ethernet'> + }, + 'ipv4': + { + 'method': <'manual'>, + 'dns': <[uint32 16951488]>, + 'address-data': <[ + { + 'address': <'192.168.2.148'>, + 'prefix': + }]>, + 'gateway': <'192.168.2.1'> + } + }""" + ) + + data = interface_update_payload(network_interface, **{"address": "1.1.1.1"}) + assert DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["address"] == "1.1.1.1"