mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-09 18:26:30 +00:00
Network: abstract dbus and supervisor - ipv6/wifi/vlan (#2217)
* Abstract code between dbus - supervisor * cleanup v2 * fix address vs interface * fix API calls * Fix methodnames * add vlan type * add vlan support * Fix tests * Add wifi support * more OOO * fix typing import * typing part 2 * Fix profile * fix test payload * ignore powersafe * support privancy * fix property * Fix tests * full support all API * Fix all * more robust * Update supervisor/dbus/network/connection.py Co-authored-by: Joakim Sørensen <joasoe@gmail.com> * Fix gateway * fix empty gateway * Allow no ipv6 or ipv4 kernel support * Exclude device drivers * Add wifi * Use loop on api * refactory p1 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * refactory p2 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * refactory p3 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * refactory p4 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * refactory p5 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * refactory p6 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * refactory p7 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * refactory p8 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * Fix lint * update sup p1 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * update sup p2 Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * fix tests Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * fix logging Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * improve mock handling Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * add fixtures Signed-off-by: Pascal Vizeli <pvizeli@syshack.ch> * fix tests * better testing * Add more tests * Fix API test * Add test for vlan payload * Support variation * Fix doc string * support remove & wifi scan * make sure we ignore local-link on ipv6 * remove privancy - add vlan * Fix tests * fix isort * Fixture dbus by commands * Add dnsmanager fixture * expose commands called by dbus * Add wifi tests * Update supervisor/plugins/dns.py Co-authored-by: Joakim Sørensen <joasoe@gmail.com> * Address comments & fix tests * change url to be closer on others Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
This commit is contained in:
parent
ffaeb2b96d
commit
bd786811a3
@ -117,6 +117,14 @@ class RestAPI(CoreSysAttributes):
|
||||
"/network/interface/{interface}/update",
|
||||
api_network.interface_update,
|
||||
),
|
||||
web.get(
|
||||
"/network/interface/{interface}/accesspoints",
|
||||
api_network.scan_accesspoints,
|
||||
),
|
||||
web.post(
|
||||
"/network/interface/{interface}/vlan/{vlan}",
|
||||
api_network.create_vlan,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -1,74 +1,158 @@
|
||||
"""REST API for network."""
|
||||
import asyncio
|
||||
from ipaddress import ip_address, ip_interface
|
||||
from typing import Any, Dict
|
||||
|
||||
from aiohttp import web
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
|
||||
from ..const import (
|
||||
ATTR_ACCESSPOINTS,
|
||||
ATTR_ADDRESS,
|
||||
ATTR_AUTH,
|
||||
ATTR_CONNECTED,
|
||||
ATTR_DNS,
|
||||
ATTR_DOCKER,
|
||||
ATTR_ENABLED,
|
||||
ATTR_FREQUENCY,
|
||||
ATTR_GATEWAY,
|
||||
ATTR_ID,
|
||||
ATTR_INTERFACE,
|
||||
ATTR_INTERFACES,
|
||||
ATTR_IP_ADDRESS,
|
||||
ATTR_IPV4,
|
||||
ATTR_IPV6,
|
||||
ATTR_MAC,
|
||||
ATTR_METHOD,
|
||||
ATTR_METHODS,
|
||||
ATTR_MODE,
|
||||
ATTR_NAMESERVERS,
|
||||
ATTR_PRIMARY,
|
||||
ATTR_PSK,
|
||||
ATTR_SIGNAL,
|
||||
ATTR_SSID,
|
||||
ATTR_TYPE,
|
||||
ATTR_VLAN,
|
||||
ATTR_WIFI,
|
||||
DOCKER_NETWORK,
|
||||
DOCKER_NETWORK_MASK,
|
||||
)
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..dbus.const import InterfaceMethodSimple
|
||||
from ..dbus.network.interface import NetworkInterface
|
||||
from ..dbus.network.utils import int2ip
|
||||
from ..exceptions import APIError
|
||||
from ..exceptions import APIError, HostNetworkNotFound
|
||||
from ..host.const import AuthMethod, InterfaceType, WifiMode
|
||||
from ..host.network import (
|
||||
AccessPoint,
|
||||
Interface,
|
||||
InterfaceMethod,
|
||||
IpConfig,
|
||||
VlanConfig,
|
||||
WifiConfig,
|
||||
)
|
||||
from .utils import api_process, api_validate
|
||||
|
||||
SCHEMA_UPDATE = vol.Schema(
|
||||
_SCHEMA_IP_CONFIG = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_ADDRESS): vol.Coerce(str),
|
||||
vol.Optional(ATTR_METHOD): vol.In(ATTR_METHODS),
|
||||
vol.Optional(ATTR_GATEWAY): vol.Coerce(str),
|
||||
vol.Optional(ATTR_DNS): [str],
|
||||
vol.Optional(ATTR_ADDRESS): [vol.Coerce(ip_interface)],
|
||||
vol.Optional(ATTR_METHOD): vol.Coerce(InterfaceMethod),
|
||||
vol.Optional(ATTR_GATEWAY): vol.Coerce(ip_address),
|
||||
vol.Optional(ATTR_NAMESERVERS): [vol.Coerce(ip_address)],
|
||||
}
|
||||
)
|
||||
|
||||
_SCHEMA_WIFI_CONFIG = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_MODE): vol.Coerce(WifiMode),
|
||||
vol.Optional(ATTR_AUTH): vol.Coerce(AuthMethod),
|
||||
vol.Optional(ATTR_SSID): str,
|
||||
vol.Optional(ATTR_PSK): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def interface_information(interface: NetworkInterface) -> dict:
|
||||
# pylint: disable=no-value-for-parameter
|
||||
SCHEMA_UPDATE = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_IPV4): _SCHEMA_IP_CONFIG,
|
||||
vol.Optional(ATTR_IPV6): _SCHEMA_IP_CONFIG,
|
||||
vol.Optional(ATTR_WIFI): _SCHEMA_WIFI_CONFIG,
|
||||
vol.Optional(ATTR_ENABLED): vol.Boolean(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def ipconfig_struct(config: IpConfig) -> dict:
|
||||
"""Return a dict with information about ip configuration."""
|
||||
return {
|
||||
ATTR_METHOD: config.method,
|
||||
ATTR_ADDRESS: [address.with_prefixlen for address in config.address],
|
||||
ATTR_NAMESERVERS: [str(address) for address in config.nameservers],
|
||||
ATTR_GATEWAY: str(config.gateway),
|
||||
}
|
||||
|
||||
|
||||
def wifi_struct(config: WifiConfig) -> dict:
|
||||
"""Return a dict with information about wifi configuration."""
|
||||
return {
|
||||
ATTR_MODE: config.mode,
|
||||
ATTR_AUTH: config.auth,
|
||||
ATTR_SSID: config.ssid,
|
||||
ATTR_SIGNAL: config.signal,
|
||||
}
|
||||
|
||||
|
||||
def interface_struct(interface: Interface) -> dict:
|
||||
"""Return a dict with information of a interface to be used in th API."""
|
||||
return {
|
||||
ATTR_INTERFACE: interface.name,
|
||||
ATTR_IP_ADDRESS: f"{interface.ip_address}/{interface.prefix}",
|
||||
ATTR_GATEWAY: interface.gateway,
|
||||
ATTR_ID: interface.id,
|
||||
ATTR_TYPE: interface.type,
|
||||
ATTR_NAMESERVERS: [int2ip(x) for x in interface.nameservers],
|
||||
ATTR_METHOD: InterfaceMethodSimple.DHCP
|
||||
if interface.method == "auto"
|
||||
else InterfaceMethodSimple.STATIC,
|
||||
ATTR_ENABLED: interface.enabled,
|
||||
ATTR_CONNECTED: interface.connected,
|
||||
ATTR_PRIMARY: interface.primary,
|
||||
ATTR_IPV4: ipconfig_struct(interface.ipv4) if interface.ipv4 else None,
|
||||
ATTR_IPV6: ipconfig_struct(interface.ipv6) if interface.ipv6 else None,
|
||||
ATTR_WIFI: wifi_struct(interface.wifi) if interface.wifi else None,
|
||||
ATTR_VLAN: wifi_struct(interface.vlan) if interface.vlan else None,
|
||||
}
|
||||
|
||||
|
||||
def accesspoint_struct(accesspoint: AccessPoint) -> dict:
|
||||
"""Return a dict for AccessPoint."""
|
||||
return {
|
||||
ATTR_MODE: accesspoint.mode,
|
||||
ATTR_SSID: accesspoint.ssid,
|
||||
ATTR_FREQUENCY: accesspoint.frequency,
|
||||
ATTR_SIGNAL: accesspoint.signal,
|
||||
ATTR_MAC: accesspoint.mac,
|
||||
}
|
||||
|
||||
|
||||
class APINetwork(CoreSysAttributes):
|
||||
"""Handle REST API for network."""
|
||||
|
||||
def _get_interface(self, name: str) -> Interface:
|
||||
"""Get Interface by name or default."""
|
||||
name = name.lower()
|
||||
|
||||
if name == "default":
|
||||
for interface in self.sys_host.network.interfaces:
|
||||
if not interface.primary:
|
||||
continue
|
||||
return interface
|
||||
|
||||
else:
|
||||
try:
|
||||
return self.sys_host.network.get(name)
|
||||
except HostNetworkNotFound:
|
||||
pass
|
||||
|
||||
raise APIError(f"Interface {name} does not exsist") from None
|
||||
|
||||
@api_process
|
||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||
"""Return network information."""
|
||||
interfaces = {}
|
||||
for interface in self.sys_host.network.interfaces:
|
||||
interfaces[
|
||||
self.sys_host.network.interfaces[interface].name
|
||||
] = interface_information(self.sys_host.network.interfaces[interface])
|
||||
|
||||
return {
|
||||
ATTR_INTERFACES: interfaces,
|
||||
ATTR_INTERFACES: [
|
||||
interface_struct(interface)
|
||||
for interface in self.sys_host.network.interfaces
|
||||
],
|
||||
ATTR_DOCKER: {
|
||||
ATTR_INTERFACE: DOCKER_NETWORK,
|
||||
ATTR_ADDRESS: str(DOCKER_NETWORK_MASK),
|
||||
@ -80,42 +164,88 @@ class APINetwork(CoreSysAttributes):
|
||||
@api_process
|
||||
async def interface_info(self, request: web.Request) -> Dict[str, Any]:
|
||||
"""Return network information for a interface."""
|
||||
req_interface = request.match_info.get(ATTR_INTERFACE)
|
||||
interface = self._get_interface(request.match_info.get(ATTR_INTERFACE))
|
||||
|
||||
if req_interface.lower() == "default":
|
||||
for interface in self.sys_host.network.interfaces:
|
||||
if not self.sys_host.network.interfaces[interface].primary:
|
||||
continue
|
||||
return interface_information(
|
||||
self.sys_host.network.interfaces[interface]
|
||||
)
|
||||
|
||||
else:
|
||||
for interface in self.sys_host.network.interfaces:
|
||||
if req_interface != self.sys_host.network.interfaces[interface].name:
|
||||
continue
|
||||
return interface_information(
|
||||
self.sys_host.network.interfaces[interface]
|
||||
)
|
||||
|
||||
return {}
|
||||
return interface_struct(interface)
|
||||
|
||||
@api_process
|
||||
async def interface_update(self, request: web.Request) -> Dict[str, Any]:
|
||||
async def interface_update(self, request: web.Request) -> None:
|
||||
"""Update the configuration of an interface."""
|
||||
req_interface = request.match_info.get(ATTR_INTERFACE)
|
||||
interface = self._get_interface(request.match_info.get(ATTR_INTERFACE))
|
||||
|
||||
if not self.sys_host.network.interfaces.get(req_interface):
|
||||
raise APIError(f"Interface {req_interface} does not exsist")
|
||||
|
||||
args = await api_validate(SCHEMA_UPDATE, request)
|
||||
if not args:
|
||||
# Validate data
|
||||
body = await api_validate(SCHEMA_UPDATE, request)
|
||||
if not body:
|
||||
raise APIError("You need to supply at least one option to update")
|
||||
|
||||
await asyncio.shield(
|
||||
self.sys_host.network.interfaces[req_interface].update_settings(**args)
|
||||
# Apply config
|
||||
for key, config in body.items():
|
||||
if key == ATTR_IPV4:
|
||||
interface.ipv4 = attr.evolve(interface.ipv4, **config)
|
||||
elif key == ATTR_IPV6:
|
||||
interface.ipv6 = attr.evolve(interface.ipv6, **config)
|
||||
elif key == ATTR_WIFI:
|
||||
interface.wifi = attr.evolve(interface.wifi, **config)
|
||||
elif key == ATTR_ENABLED:
|
||||
interface.enabled = config
|
||||
|
||||
await asyncio.shield(self.sys_host.network.apply_changes(interface))
|
||||
|
||||
@api_process
|
||||
async def scan_accesspoints(self, request: web.Request) -> Dict[str, Any]:
|
||||
"""Scan and return a list of available networks."""
|
||||
interface = self._get_interface(request.match_info.get(ATTR_INTERFACE))
|
||||
|
||||
# Only wlan is supported
|
||||
if interface.type != InterfaceType.WIRELESS:
|
||||
raise APIError(f"Interface {interface.name} is not a valid wireless card!")
|
||||
|
||||
ap_list = await self.sys_host.network.scan_wifi(interface)
|
||||
|
||||
return {ATTR_ACCESSPOINTS: [accesspoint_struct(ap) for ap in ap_list]}
|
||||
|
||||
@api_process
|
||||
async def create_vlan(self, request: web.Request) -> None:
|
||||
"""Create a new vlan."""
|
||||
interface = self._get_interface(request.match_info.get(ATTR_INTERFACE))
|
||||
vlan = int(request.match_info.get(ATTR_VLAN))
|
||||
|
||||
# Only ethernet is supported
|
||||
if interface.type != InterfaceType.ETHERNET:
|
||||
raise APIError(
|
||||
f"Interface {interface.name} is not a valid ethernet card for vlan!"
|
||||
)
|
||||
body = await api_validate(SCHEMA_UPDATE, request)
|
||||
|
||||
vlan_config = VlanConfig(vlan, interface.name)
|
||||
|
||||
ipv4_config = None
|
||||
if ATTR_IPV4 in body:
|
||||
ipv4_config = IpConfig(
|
||||
body[ATTR_IPV4].get(ATTR_METHOD, InterfaceMethod.DHCP),
|
||||
body[ATTR_IPV4].get(ATTR_ADDRESS, []),
|
||||
body[ATTR_IPV4].get(ATTR_GATEWAY, None),
|
||||
body[ATTR_IPV4].get(ATTR_NAMESERVERS, []),
|
||||
)
|
||||
|
||||
ipv6_config = None
|
||||
if ATTR_IPV6 in body:
|
||||
ipv6_config = IpConfig(
|
||||
body[ATTR_IPV6].get(ATTR_METHOD, InterfaceMethod.DHCP),
|
||||
body[ATTR_IPV6].get(ATTR_ADDRESS, []),
|
||||
body[ATTR_IPV6].get(ATTR_GATEWAY, None),
|
||||
body[ATTR_IPV6].get(ATTR_NAMESERVERS, []),
|
||||
)
|
||||
|
||||
vlan_interface = Interface(
|
||||
"",
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
InterfaceType.VLAN,
|
||||
ipv4_config,
|
||||
ipv6_config,
|
||||
None,
|
||||
vlan_config,
|
||||
)
|
||||
|
||||
await asyncio.shield(self.sys_host.reload())
|
||||
|
||||
return await asyncio.shield(self.interface_info(request))
|
||||
await asyncio.shield(self.sys_host.network.apply_changes(vlan_interface))
|
||||
|
@ -158,7 +158,6 @@ ATTR_HOST_PID = "host_pid"
|
||||
ATTR_HOSTNAME = "hostname"
|
||||
ATTR_ICON = "icon"
|
||||
ATTR_ISSUES = "issues"
|
||||
ATTR_ID = "id"
|
||||
ATTR_IMAGE = "image"
|
||||
ATTR_IMAGES = "images"
|
||||
ATTR_INDEX = "index"
|
||||
@ -176,6 +175,7 @@ ATTR_INTERFACE = "interface"
|
||||
ATTR_INTERFACES = "interfaces"
|
||||
ATTR_IP_ADDRESS = "ip_address"
|
||||
ATTR_IPV4 = "ipv4"
|
||||
ATTR_IPV6 = "ipv6"
|
||||
ATTR_KERNEL = "kernel"
|
||||
ATTR_KERNEL_MODULES = "kernel_modules"
|
||||
ATTR_LAST_BOOT = "last_boot"
|
||||
@ -193,7 +193,6 @@ ATTR_MEMORY_PERCENT = "memory_percent"
|
||||
ATTR_MEMORY_USAGE = "memory_usage"
|
||||
ATTR_MESSAGE = "message"
|
||||
ATTR_METHOD = "method"
|
||||
ATTR_METHODS = ["dhcp", "static"]
|
||||
ATTR_MODE = "mode"
|
||||
ATTR_MULTICAST = "multicast"
|
||||
ATTR_NAME = "name"
|
||||
@ -277,6 +276,17 @@ ATTR_WATCHDOG = "watchdog"
|
||||
ATTR_WEBUI = "webui"
|
||||
ATTR_OBSERVER = "observer"
|
||||
ATTR_UPDATE_AVAILABLE = "update_available"
|
||||
ATTR_WIFI = "wifi"
|
||||
ATTR_VLAN = "vlan"
|
||||
ATTR_SSD = "ssid"
|
||||
ATTR_AUTH = "auth"
|
||||
ATTR_PSK = "psk"
|
||||
ATTR_CONNECTED = "connected"
|
||||
ATTR_ENABLED = "enabled"
|
||||
ATTR_SIGNAL = "signal"
|
||||
ATTR_MAC = "mac"
|
||||
ATTR_FREQUENCY = "frequency"
|
||||
ATTR_ACCESSPOINTS = "accesspoints"
|
||||
|
||||
PROVIDE_SERVICE = "provide"
|
||||
NEED_SERVICE = "need"
|
||||
|
@ -3,9 +3,12 @@ from enum import Enum
|
||||
|
||||
DBUS_NAME_CONNECTION_ACTIVE = "org.freedesktop.NetworkManager.Connection.Active"
|
||||
DBUS_NAME_DEVICE = "org.freedesktop.NetworkManager.Device"
|
||||
DBUS_NAME_DEVICE_WIRELESS = "org.freedesktop.NetworkManager.Device.Wireless"
|
||||
DBUS_NAME_DNS = "org.freedesktop.NetworkManager.DnsManager"
|
||||
DBUS_NAME_ACCESSPOINT = "org.freedesktop.NetworkManager.AccessPoint"
|
||||
DBUS_NAME_HOSTNAME = "org.freedesktop.hostname1"
|
||||
DBUS_NAME_IP4CONFIG = "org.freedesktop.NetworkManager.IP4Config"
|
||||
DBUS_NAME_IP6CONFIG = "org.freedesktop.NetworkManager.IP6Config"
|
||||
DBUS_NAME_NM = "org.freedesktop.NetworkManager"
|
||||
DBUS_NAME_RAUC = "de.pengutronix.rauc"
|
||||
DBUS_NAME_RAUC_INSTALLER = "de.pengutronix.rauc.Installer"
|
||||
@ -15,13 +18,14 @@ DBUS_NAME_SYSTEMD = "org.freedesktop.systemd1"
|
||||
|
||||
DBUS_OBJECT_BASE = "/"
|
||||
DBUS_OBJECT_DNS = "/org/freedesktop/NetworkManager/DnsManager"
|
||||
DBUS_OBJECT_SETTINGS = "/org/freedesktop/NetworkManager/Settings"
|
||||
DBUS_OBJECT_HOSTNAME = "/org/freedesktop/hostname1"
|
||||
DBUS_OBJECT_NM = "/org/freedesktop/NetworkManager"
|
||||
DBUS_OBJECT_SYSTEMD = "/org/freedesktop/systemd1"
|
||||
|
||||
DBUS_ATTR_802_WIRELESS = "802-11-wireless"
|
||||
DBUS_ATTR_802_WIRELESS_SECURITY = "802-11-wireless-security"
|
||||
DBUS_ATTR_ACTIVE_CONNECTIONS = "ActiveConnections"
|
||||
DBUS_ATTR_ACTIVE_CONNECTION = "ActiveConnection"
|
||||
DBUS_ATTR_ACTIVE_ACCESSPOINT = "ActiveAccessPoint"
|
||||
DBUS_ATTR_ADDRESS_DATA = "AddressData"
|
||||
DBUS_ATTR_BOOT_SLOT = "BootSlot"
|
||||
DBUS_ATTR_CHASSIS = "Chassis"
|
||||
@ -33,25 +37,32 @@ DBUS_ATTR_DEPLOYMENT = "Deployment"
|
||||
DBUS_ATTR_DEVICE_INTERFACE = "Interface"
|
||||
DBUS_ATTR_DEVICE_TYPE = "DeviceType"
|
||||
DBUS_ATTR_DEVICES = "Devices"
|
||||
DBUS_ATTR_DRIVER = "Driver"
|
||||
DBUS_ATTR_GATEWAY = "Gateway"
|
||||
DBUS_ATTR_ID = "Id"
|
||||
DBUS_ATTR_IP4ADDRESS = "Ip4Address"
|
||||
DBUS_ATTR_SSID = "Ssid"
|
||||
DBUS_ATTR_FREQUENCY = "Frequency"
|
||||
DBUS_ATTR_HWADDRESS = "HwAddress"
|
||||
DBUS_ATTR_MODE = "Mode"
|
||||
DBUS_ATTR_STRENGTH = "Strength"
|
||||
DBUS_ATTR_IP4CONFIG = "Ip4Config"
|
||||
DBUS_ATTR_IP6CONFIG = "Ip6Config"
|
||||
DBUS_ATTR_KERNEL_RELEASE = "KernelRelease"
|
||||
DBUS_ATTR_LAST_ERROR = "LastError"
|
||||
DBUS_ATTR_MODE = "Mode"
|
||||
DBUS_ATTR_NAMESERVERS = "Nameservers"
|
||||
DBUS_ATTR_NAMESERVER_DATA = "NameserverData"
|
||||
DBUS_ATTR_OPERATING_SYSTEM_PRETTY_NAME = "OperatingSystemPrettyName"
|
||||
DBUS_ATTR_OPERATION = "Operation"
|
||||
DBUS_ATTR_PRIMARY_CONNECTION = "PrimaryConnection"
|
||||
DBUS_ATTR_RCMANAGER = "RcManager"
|
||||
DBUS_ATTR_REAL = "Real"
|
||||
DBUS_ATTR_STATE = "State"
|
||||
DBUS_ATTR_STATIC_HOSTNAME = "StaticHostname"
|
||||
DBUS_ATTR_STATIC_OPERATING_SYSTEM_CPE_NAME = "OperatingSystemCPEName"
|
||||
DBUS_ATTR_TYPE = "Type"
|
||||
DBUS_ATTR_UUID = "Uuid"
|
||||
DBUS_ATTR_VARIANT = "Variant"
|
||||
DBUS_ATTR_MANAGED = "Managed"
|
||||
|
||||
|
||||
class RaucState(str, Enum):
|
||||
@ -67,13 +78,7 @@ class InterfaceMethod(str, Enum):
|
||||
|
||||
AUTO = "auto"
|
||||
MANUAL = "manual"
|
||||
|
||||
|
||||
class InterfaceMethodSimple(str, Enum):
|
||||
"""Interface method."""
|
||||
|
||||
DHCP = "dhcp"
|
||||
STATIC = "static"
|
||||
DISABLED = "disabled"
|
||||
|
||||
|
||||
class ConnectionType(str, Enum):
|
||||
@ -81,3 +86,41 @@ class ConnectionType(str, Enum):
|
||||
|
||||
ETHERNET = "802-3-ethernet"
|
||||
WIRELESS = "802-11-wireless"
|
||||
|
||||
|
||||
class ConnectionStateType(int, Enum):
|
||||
"""Connection states.
|
||||
|
||||
https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMActiveConnectionState
|
||||
"""
|
||||
|
||||
UNKNOWN = 0
|
||||
ACTIVATING = 1
|
||||
ACTIVATED = 2
|
||||
DEACTIVATING = 3
|
||||
DEACTIVATED = 4
|
||||
|
||||
|
||||
class DeviceType(int, Enum):
|
||||
"""Device types.
|
||||
|
||||
https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMDeviceType
|
||||
"""
|
||||
|
||||
UNKNOWN = 0
|
||||
ETHERNET = 1
|
||||
WIRELESS = 2
|
||||
BLUETOOTH = 5
|
||||
VLAN = 11
|
||||
TUN = 16
|
||||
VETH = 20
|
||||
|
||||
|
||||
class WirelessMethodType(int, Enum):
|
||||
"""Device Type."""
|
||||
|
||||
UNKNOWN = 0
|
||||
ADHOC = 1
|
||||
INFRASTRUCTURE = 2
|
||||
ACCESSPOINT = 3
|
||||
MESH = 4
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""Interface class for D-Bus wrappers."""
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from ..utils.gdbus import DBus
|
||||
|
||||
@ -18,3 +18,15 @@ class DBusInterface(ABC):
|
||||
@abstractmethod
|
||||
async def connect(self):
|
||||
"""Connect to D-Bus."""
|
||||
|
||||
|
||||
class DBusInterfaceProxy(ABC):
|
||||
"""Handle D-Bus interface proxy."""
|
||||
|
||||
dbus: Optional[DBus] = None
|
||||
object_path: Optional[str] = None
|
||||
properties: Optional[Dict[str, Any]] = None
|
||||
|
||||
@abstractmethod
|
||||
async def connect(self):
|
||||
"""Connect to D-Bus."""
|
||||
|
@ -1,20 +1,24 @@
|
||||
"""Network Manager implementation for DBUS."""
|
||||
import logging
|
||||
from typing import Dict, Optional
|
||||
from typing import Any, Awaitable, Dict
|
||||
|
||||
from ...exceptions import DBusError, DBusFatalError, DBusInterfaceError
|
||||
import sentry_sdk
|
||||
|
||||
from ...exceptions import DBusError, DBusInterfaceError
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import (
|
||||
DBUS_ATTR_ACTIVE_CONNECTIONS,
|
||||
DBUS_ATTR_DEVICES,
|
||||
DBUS_ATTR_PRIMARY_CONNECTION,
|
||||
DBUS_NAME_NM,
|
||||
DBUS_OBJECT_BASE,
|
||||
DBUS_OBJECT_NM,
|
||||
ConnectionType,
|
||||
DeviceType,
|
||||
)
|
||||
from ..interface import DBusInterface
|
||||
from ..utils import dbus_connected
|
||||
from .dns import NetworkManagerDNS
|
||||
from .interface import NetworkInterface
|
||||
from .settings import NetworkManagerSettings
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@ -25,23 +29,48 @@ class NetworkManager(DBusInterface):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize Properties."""
|
||||
self._dns: NetworkManagerDNS = NetworkManagerDNS()
|
||||
self._interfaces: Optional[Dict[str, NetworkInterface]] = []
|
||||
self._settings: NetworkManagerSettings = NetworkManagerSettings()
|
||||
self._interfaces: Dict[str, NetworkInterface] = {}
|
||||
|
||||
@property
|
||||
def dns(self) -> NetworkManagerDNS:
|
||||
"""Return NetworkManager DNS interface."""
|
||||
return self._dns
|
||||
|
||||
@property
|
||||
def settings(self) -> NetworkManagerSettings:
|
||||
"""Return NetworkManager global settings."""
|
||||
return self._settings
|
||||
|
||||
@property
|
||||
def interfaces(self) -> Dict[str, NetworkInterface]:
|
||||
"""Return a dictionary of active interfaces."""
|
||||
return self._interfaces
|
||||
|
||||
@dbus_connected
|
||||
def activate_connection(
|
||||
self, connection_object: str, device_object: str
|
||||
) -> Awaitable[Any]:
|
||||
"""Activate a connction on a device."""
|
||||
return self.dbus.ActivateConnection(
|
||||
connection_object, device_object, DBUS_OBJECT_BASE
|
||||
)
|
||||
|
||||
@dbus_connected
|
||||
def add_and_activate_connection(
|
||||
self, settings: str, device_object: str
|
||||
) -> Awaitable[Any]:
|
||||
"""Activate a connction on a device."""
|
||||
return self.dbus.AddAndActivateConnection(
|
||||
settings, device_object, DBUS_OBJECT_BASE
|
||||
)
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Connect to system's D-Bus."""
|
||||
try:
|
||||
self.dbus = await DBus.connect(DBUS_NAME_NM, DBUS_OBJECT_NM)
|
||||
await self.dns.connect()
|
||||
await self.settings.connect()
|
||||
except DBusError:
|
||||
_LOGGER.warning("Can't connect to Network Manager")
|
||||
except DBusInterfaceError:
|
||||
@ -60,28 +89,33 @@ class NetworkManager(DBusInterface):
|
||||
_LOGGER.warning("Can't get properties for Network Manager")
|
||||
return
|
||||
|
||||
self._interfaces = {}
|
||||
for connection in data.get(DBUS_ATTR_ACTIVE_CONNECTIONS, []):
|
||||
interface = NetworkInterface()
|
||||
self._interfaces.clear()
|
||||
for device in data.get(DBUS_ATTR_DEVICES, []):
|
||||
interface = NetworkInterface(self.dbus, device)
|
||||
|
||||
await interface.connect(self.dbus, connection)
|
||||
|
||||
if interface.connection.type not in [
|
||||
ConnectionType.ETHERNET,
|
||||
ConnectionType.WIRELESS,
|
||||
]:
|
||||
continue
|
||||
# Connect to interface
|
||||
try:
|
||||
await interface.connection.update_information()
|
||||
except (IndexError, DBusFatalError, KeyError):
|
||||
await interface.connect()
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Error while processing interface: %s", err)
|
||||
sentry_sdk.capture_exception(err)
|
||||
continue
|
||||
|
||||
if not interface.connection.ip4_config:
|
||||
# Skeep interface
|
||||
if (
|
||||
interface.type
|
||||
not in [
|
||||
DeviceType.ETHERNET,
|
||||
DeviceType.WIRELESS,
|
||||
DeviceType.VLAN,
|
||||
]
|
||||
or not interface.managed
|
||||
):
|
||||
continue
|
||||
|
||||
if interface.connection.object_path == data.get(
|
||||
if interface.connection and interface.connection.object_path == data.get(
|
||||
DBUS_ATTR_PRIMARY_CONNECTION
|
||||
):
|
||||
interface.connection.primary = True
|
||||
interface.primary = True
|
||||
|
||||
self._interfaces[interface.name] = interface
|
||||
|
52
supervisor/dbus/network/accesspoint.py
Normal file
52
supervisor/dbus/network/accesspoint.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""Connection object for Network Manager."""
|
||||
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import (
|
||||
DBUS_ATTR_FREQUENCY,
|
||||
DBUS_ATTR_HWADDRESS,
|
||||
DBUS_ATTR_MODE,
|
||||
DBUS_ATTR_SSID,
|
||||
DBUS_ATTR_STRENGTH,
|
||||
DBUS_NAME_ACCESSPOINT,
|
||||
DBUS_NAME_NM,
|
||||
)
|
||||
from ..interface import DBusInterfaceProxy
|
||||
|
||||
|
||||
class NetworkWirelessAP(DBusInterfaceProxy):
|
||||
"""NetworkWireless AP object for Network Manager."""
|
||||
|
||||
def __init__(self, object_path: str) -> None:
|
||||
"""Initialize NetworkWireless AP object."""
|
||||
self.object_path = object_path
|
||||
self.properties = {}
|
||||
|
||||
@property
|
||||
def ssid(self) -> str:
|
||||
"""Return details about ssid."""
|
||||
return bytes(self.properties[DBUS_ATTR_SSID]).decode()
|
||||
|
||||
@property
|
||||
def frequency(self) -> int:
|
||||
"""Return details about frequency."""
|
||||
return self.properties[DBUS_ATTR_FREQUENCY]
|
||||
|
||||
@property
|
||||
def mac(self) -> str:
|
||||
"""Return details about mac address."""
|
||||
return self.properties[DBUS_ATTR_HWADDRESS]
|
||||
|
||||
@property
|
||||
def mode(self) -> int:
|
||||
"""Return details about mac address."""
|
||||
return self.properties[DBUS_ATTR_MODE]
|
||||
|
||||
@property
|
||||
def strength(self) -> int:
|
||||
"""Return details about mac address."""
|
||||
return int(self.properties[DBUS_ATTR_STRENGTH])
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Get connection information."""
|
||||
self.dbus = await DBus.connect(DBUS_NAME_NM, self.object_path)
|
||||
self.properties = await self.dbus.get_properties(DBUS_NAME_ACCESSPOINT)
|
@ -1,71 +1,75 @@
|
||||
"""NetworkConnection object4s for Network Manager."""
|
||||
from typing import List
|
||||
from ipaddress import IPv4Address, IPv4Interface, IPv6Address, IPv6Interface
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import attr
|
||||
|
||||
from ...utils.gdbus import DBus
|
||||
|
||||
|
||||
class NetworkAttributes:
|
||||
"""NetworkAttributes object for Network Manager."""
|
||||
|
||||
def __init__(self, object_path: str, properties: dict) -> None:
|
||||
"""Initialize NetworkAttributes object."""
|
||||
self._properties = properties
|
||||
self.object_path = object_path
|
||||
|
||||
|
||||
@attr.s
|
||||
class AddressData:
|
||||
"""AddressData object for Network Manager."""
|
||||
|
||||
address: str = attr.ib()
|
||||
prefix: int = attr.ib()
|
||||
|
||||
|
||||
@attr.s
|
||||
@attr.s(slots=True)
|
||||
class IpConfiguration:
|
||||
"""NetworkSettingsIPConfig object for Network Manager."""
|
||||
|
||||
gateway: str = attr.ib()
|
||||
method: str = attr.ib()
|
||||
nameservers: List[int] = attr.ib()
|
||||
address_data: AddressData = attr.ib()
|
||||
gateway: Optional[Union[IPv6Address, IPv6Address]] = attr.ib()
|
||||
nameservers: List[Union[IPv6Address, IPv6Address]] = attr.ib()
|
||||
address: List[Union[IPv4Interface, IPv6Interface]] = attr.ib()
|
||||
|
||||
|
||||
@attr.s
|
||||
@attr.s(slots=True)
|
||||
class DNSConfiguration:
|
||||
"""DNS configuration Object."""
|
||||
|
||||
nameservers: List[str] = attr.ib()
|
||||
nameservers: List[Union[IPv4Address, IPv6Address]] = attr.ib()
|
||||
domains: List[str] = attr.ib()
|
||||
interface: str = attr.ib()
|
||||
priority: int = attr.ib()
|
||||
vpn: bool = attr.ib()
|
||||
|
||||
|
||||
@attr.s
|
||||
class NetworkSettings:
|
||||
"""NetworkSettings object for Network Manager."""
|
||||
@attr.s(slots=True)
|
||||
class ConnectionProperties:
|
||||
"""Connection Properties object for Network Manager."""
|
||||
|
||||
dbus: DBus = attr.ib()
|
||||
id: Optional[str] = attr.ib()
|
||||
uuid: Optional[str] = attr.ib()
|
||||
type: Optional[str] = attr.ib()
|
||||
|
||||
|
||||
@attr.s
|
||||
class NetworkDevice:
|
||||
"""Device properties."""
|
||||
|
||||
dbus: DBus = attr.ib()
|
||||
interface: str = attr.ib()
|
||||
ip4_address: int = attr.ib()
|
||||
device_type: int = attr.ib()
|
||||
real: bool = attr.ib()
|
||||
|
||||
|
||||
@attr.s
|
||||
@attr.s(slots=True)
|
||||
class WirelessProperties:
|
||||
"""WirelessProperties object for Network Manager."""
|
||||
"""Wireless Properties object for Network Manager."""
|
||||
|
||||
properties: dict = attr.ib()
|
||||
security: dict = attr.ib()
|
||||
ssid: str = attr.ib()
|
||||
ssid: Optional[str] = attr.ib()
|
||||
assigned_mac: Optional[str] = attr.ib()
|
||||
mode: Optional[str] = attr.ib()
|
||||
powersave: Optional[int] = attr.ib()
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class WirelessSecurityProperties:
|
||||
"""Wireless Security Properties object for Network Manager."""
|
||||
|
||||
auth_alg: Optional[str] = attr.ib()
|
||||
key_mgmt: Optional[str] = attr.ib()
|
||||
psk: Optional[str] = attr.ib()
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class EthernetProperties:
|
||||
"""Ethernet properties object for Network Manager."""
|
||||
|
||||
assigned_mac: Optional[str] = attr.ib()
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class VlanProperties:
|
||||
"""Ethernet properties object for Network Manager."""
|
||||
|
||||
id: Optional[int] = attr.ib()
|
||||
parent: Optional[str] = attr.ib()
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class IpProperties:
|
||||
"""IP properties object for Network Manager."""
|
||||
|
||||
method: Optional[str] = attr.ib()
|
||||
|
@ -1,146 +1,116 @@
|
||||
"""Connection object for Network Manager."""
|
||||
from ipaddress import ip_address, ip_interface
|
||||
from typing import Optional
|
||||
|
||||
from ...const import ATTR_ADDRESS, ATTR_IPV4, ATTR_METHOD, ATTR_PREFIX, ATTR_SSID
|
||||
from ...const import ATTR_ADDRESS, ATTR_PREFIX
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import (
|
||||
DBUS_ATTR_802_WIRELESS,
|
||||
DBUS_ATTR_802_WIRELESS_SECURITY,
|
||||
DBUS_ATTR_ADDRESS_DATA,
|
||||
DBUS_ATTR_CONNECTION,
|
||||
DBUS_ATTR_DEFAULT,
|
||||
DBUS_ATTR_DEVICE_INTERFACE,
|
||||
DBUS_ATTR_DEVICE_TYPE,
|
||||
DBUS_ATTR_DEVICES,
|
||||
DBUS_ATTR_GATEWAY,
|
||||
DBUS_ATTR_ID,
|
||||
DBUS_ATTR_IP4ADDRESS,
|
||||
DBUS_ATTR_IP4CONFIG,
|
||||
DBUS_ATTR_IP6CONFIG,
|
||||
DBUS_ATTR_NAMESERVER_DATA,
|
||||
DBUS_ATTR_NAMESERVERS,
|
||||
DBUS_ATTR_REAL,
|
||||
DBUS_ATTR_STATE,
|
||||
DBUS_ATTR_TYPE,
|
||||
DBUS_ATTR_UUID,
|
||||
DBUS_NAME_DEVICE,
|
||||
DBUS_NAME_CONNECTION_ACTIVE,
|
||||
DBUS_NAME_IP4CONFIG,
|
||||
DBUS_NAME_IP6CONFIG,
|
||||
DBUS_NAME_NM,
|
||||
DBUS_OBJECT_BASE,
|
||||
ConnectionType,
|
||||
)
|
||||
from .configuration import (
|
||||
AddressData,
|
||||
IpConfiguration,
|
||||
NetworkAttributes,
|
||||
NetworkDevice,
|
||||
NetworkSettings,
|
||||
WirelessProperties,
|
||||
)
|
||||
from ..interface import DBusInterfaceProxy
|
||||
from .configuration import IpConfiguration
|
||||
|
||||
|
||||
class NetworkConnection(NetworkAttributes):
|
||||
class NetworkConnection(DBusInterfaceProxy):
|
||||
"""NetworkConnection object for Network Manager."""
|
||||
|
||||
def __init__(self, object_path: str, properties: dict) -> None:
|
||||
def __init__(self, object_path: str) -> None:
|
||||
"""Initialize NetworkConnection object."""
|
||||
super().__init__(object_path, properties)
|
||||
self._device_dbus: DBus = None
|
||||
self._settings_dbus: DBus = None
|
||||
self._settings: Optional[NetworkSettings] = None
|
||||
self._ip4_config: Optional[IpConfiguration] = None
|
||||
self._device: Optional[NetworkDevice]
|
||||
self._wireless: Optional[WirelessProperties] = None
|
||||
self.primary: bool = False
|
||||
self.object_path = object_path
|
||||
self.properties = {}
|
||||
|
||||
@property
|
||||
def settings(self) -> NetworkSettings:
|
||||
"""Return a settings object for the connection."""
|
||||
return self._settings
|
||||
|
||||
@property
|
||||
def device(self) -> NetworkDevice:
|
||||
"""Return the device used in the connection."""
|
||||
return self._device
|
||||
|
||||
@property
|
||||
def default(self) -> bool:
|
||||
"""Return a boolean connection is marked as default."""
|
||||
return self._properties[DBUS_ATTR_DEFAULT]
|
||||
self._ipv4: Optional[IpConfiguration] = None
|
||||
self._ipv6: Optional[IpConfiguration] = None
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
"""Return the id of the connection."""
|
||||
return self._properties[DBUS_ATTR_ID]
|
||||
return self.properties[DBUS_ATTR_ID]
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
"""Return the type of the connection."""
|
||||
return self._properties[DBUS_ATTR_TYPE]
|
||||
|
||||
@property
|
||||
def ip4_config(self) -> IpConfiguration:
|
||||
"""Return a ip configuration object for the connection."""
|
||||
return self._ip4_config
|
||||
return self.properties[DBUS_ATTR_TYPE]
|
||||
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
"""Return the uuid of the connection."""
|
||||
return self._properties[DBUS_ATTR_UUID]
|
||||
|
||||
@property
|
||||
def wireless(self) -> str:
|
||||
"""Return wireless properties if any."""
|
||||
if self.type != ConnectionType.WIRELESS:
|
||||
return None
|
||||
return self._wireless
|
||||
return self.properties[DBUS_ATTR_UUID]
|
||||
|
||||
@property
|
||||
def state(self) -> int:
|
||||
"""
|
||||
Return the state of the connection.
|
||||
"""Return the state of the connection."""
|
||||
return self.properties[DBUS_ATTR_STATE]
|
||||
|
||||
https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMActiveConnectionState
|
||||
"""
|
||||
return self._properties[DBUS_ATTR_STATE]
|
||||
@property
|
||||
def setting_object(self) -> int:
|
||||
"""Return the connection object path."""
|
||||
return self.properties[DBUS_ATTR_CONNECTION]
|
||||
|
||||
async def update_information(self):
|
||||
"""Update the information for childs ."""
|
||||
if self._properties[DBUS_ATTR_IP4CONFIG] == DBUS_OBJECT_BASE:
|
||||
return
|
||||
@property
|
||||
def ipv4(self) -> Optional[IpConfiguration]:
|
||||
"""Return a ip4 configuration object for the connection."""
|
||||
return self._ipv4
|
||||
|
||||
settings = await DBus.connect(
|
||||
DBUS_NAME_NM, self._properties[DBUS_ATTR_CONNECTION]
|
||||
)
|
||||
device = await DBus.connect(
|
||||
DBUS_NAME_NM, self._properties[DBUS_ATTR_DEVICES][0]
|
||||
)
|
||||
ip4 = await DBus.connect(DBUS_NAME_NM, self._properties[DBUS_ATTR_IP4CONFIG])
|
||||
@property
|
||||
def ipv6(self) -> Optional[IpConfiguration]:
|
||||
"""Return a ip6 configuration object for the connection."""
|
||||
return self._ipv6
|
||||
|
||||
data = (await settings.Settings.Connection.GetSettings())[0]
|
||||
device_data = await device.get_properties(DBUS_NAME_DEVICE)
|
||||
ip4_data = await ip4.get_properties(DBUS_NAME_IP4CONFIG)
|
||||
async def connect(self) -> None:
|
||||
"""Get connection information."""
|
||||
self.dbus = await DBus.connect(DBUS_NAME_NM, self.object_path)
|
||||
self.properties = await self.dbus.get_properties(DBUS_NAME_CONNECTION_ACTIVE)
|
||||
|
||||
self._settings = NetworkSettings(settings)
|
||||
# IPv4
|
||||
if self.properties[DBUS_ATTR_IP4CONFIG] != DBUS_OBJECT_BASE:
|
||||
ip4 = await DBus.connect(DBUS_NAME_NM, self.properties[DBUS_ATTR_IP4CONFIG])
|
||||
ip4_data = await ip4.get_properties(DBUS_NAME_IP4CONFIG)
|
||||
|
||||
self._ip4_config = IpConfiguration(
|
||||
ip4_data.get(DBUS_ATTR_GATEWAY),
|
||||
data[ATTR_IPV4].get(ATTR_METHOD),
|
||||
ip4_data.get(DBUS_ATTR_NAMESERVERS),
|
||||
AddressData(
|
||||
ip4_data.get(DBUS_ATTR_ADDRESS_DATA)[0].get(ATTR_ADDRESS),
|
||||
ip4_data.get(DBUS_ATTR_ADDRESS_DATA)[0].get(ATTR_PREFIX),
|
||||
),
|
||||
)
|
||||
self._ipv4 = IpConfiguration(
|
||||
ip_address(ip4_data[DBUS_ATTR_GATEWAY])
|
||||
if ip4_data.get(DBUS_ATTR_GATEWAY)
|
||||
else None,
|
||||
[
|
||||
ip_address(nameserver[ATTR_ADDRESS])
|
||||
for nameserver in ip4_data.get(DBUS_ATTR_NAMESERVER_DATA, [])
|
||||
],
|
||||
[
|
||||
ip_interface(f"{address[ATTR_ADDRESS]}/{address[ATTR_PREFIX]}")
|
||||
for address in ip4_data.get(DBUS_ATTR_ADDRESS_DATA, [])
|
||||
],
|
||||
)
|
||||
|
||||
self._wireless = WirelessProperties(
|
||||
data.get(DBUS_ATTR_802_WIRELESS, {}),
|
||||
data.get(DBUS_ATTR_802_WIRELESS_SECURITY, {}),
|
||||
bytes(data.get(DBUS_ATTR_802_WIRELESS, {}).get(ATTR_SSID, [])).decode(),
|
||||
)
|
||||
# IPv6
|
||||
if self.properties[DBUS_ATTR_IP6CONFIG] != DBUS_OBJECT_BASE:
|
||||
ip6 = await DBus.connect(DBUS_NAME_NM, self.properties[DBUS_ATTR_IP6CONFIG])
|
||||
ip6_data = await ip6.get_properties(DBUS_NAME_IP6CONFIG)
|
||||
|
||||
self._device = NetworkDevice(
|
||||
device,
|
||||
device_data.get(DBUS_ATTR_DEVICE_INTERFACE),
|
||||
device_data.get(DBUS_ATTR_IP4ADDRESS),
|
||||
device_data.get(DBUS_ATTR_DEVICE_TYPE),
|
||||
device_data.get(DBUS_ATTR_REAL),
|
||||
)
|
||||
self._ipv6 = IpConfiguration(
|
||||
ip_address(ip6_data[DBUS_ATTR_GATEWAY])
|
||||
if ip6_data.get(DBUS_ATTR_GATEWAY)
|
||||
else None,
|
||||
[
|
||||
ip_address(bytes(nameserver))
|
||||
for nameserver in ip6_data.get(DBUS_ATTR_NAMESERVERS)
|
||||
],
|
||||
[
|
||||
ip_interface(f"{address[ATTR_ADDRESS]}/{address[ATTR_PREFIX]}")
|
||||
for address in ip6_data.get(DBUS_ATTR_ADDRESS_DATA, [])
|
||||
],
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""D-Bus interface for hostname."""
|
||||
from ipaddress import ip_address
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
@ -75,7 +76,7 @@ class NetworkManagerDNS(DBusInterface):
|
||||
# Parse configuraton
|
||||
self._configuration = [
|
||||
DNSConfiguration(
|
||||
config.get(ATTR_NAMESERVERS),
|
||||
[ip_address(nameserver) for nameserver in config.get(ATTR_NAMESERVERS)],
|
||||
config.get(ATTR_DOMAINS),
|
||||
config.get(ATTR_INTERFACE),
|
||||
config.get(ATTR_PRIORITY),
|
||||
|
@ -1,101 +1,96 @@
|
||||
"""NetworkInterface object for Network Manager."""
|
||||
from typing import Optional
|
||||
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import (
|
||||
DBUS_NAME_CONNECTION_ACTIVE,
|
||||
DBUS_ATTR_ACTIVE_CONNECTION,
|
||||
DBUS_ATTR_DEVICE_INTERFACE,
|
||||
DBUS_ATTR_DEVICE_TYPE,
|
||||
DBUS_ATTR_DRIVER,
|
||||
DBUS_ATTR_MANAGED,
|
||||
DBUS_NAME_DEVICE,
|
||||
DBUS_NAME_NM,
|
||||
DBUS_OBJECT_BASE,
|
||||
ConnectionType,
|
||||
InterfaceMethod,
|
||||
DeviceType,
|
||||
)
|
||||
from ..payloads.generate import interface_update_payload
|
||||
from ..interface import DBusInterfaceProxy
|
||||
from .connection import NetworkConnection
|
||||
from .setting import NetworkSetting
|
||||
from .wireless import NetworkWireless
|
||||
|
||||
|
||||
class NetworkInterface:
|
||||
"""NetworkInterface object for Network Manager, this serves as a proxy to other objects."""
|
||||
class NetworkInterface(DBusInterfaceProxy):
|
||||
"""NetworkInterface object for Network Manager."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, nm_dbus: DBus, object_path: str) -> None:
|
||||
"""Initialize NetworkConnection object."""
|
||||
self._connection = None
|
||||
self._nm_dbus = None
|
||||
self.object_path = object_path
|
||||
self.properties = {}
|
||||
|
||||
self.primary = True
|
||||
|
||||
self._connection: Optional[NetworkConnection] = None
|
||||
self._settings: Optional[NetworkSetting] = None
|
||||
self._wireless: Optional[NetworkWireless] = None
|
||||
self._nm_dbus: DBus = nm_dbus
|
||||
|
||||
@property
|
||||
def nm_dbus(self) -> DBus:
|
||||
"""Return the NM DBus connection."""
|
||||
return self._nm_dbus
|
||||
def name(self) -> str:
|
||||
"""Return interface name."""
|
||||
return self.properties[DBUS_ATTR_DEVICE_INTERFACE]
|
||||
|
||||
@property
|
||||
def connection(self) -> NetworkConnection:
|
||||
def type(self) -> int:
|
||||
"""Return interface type."""
|
||||
return self.properties[DBUS_ATTR_DEVICE_TYPE]
|
||||
|
||||
@property
|
||||
def driver(self) -> str:
|
||||
"""Return interface driver."""
|
||||
return self.properties[DBUS_ATTR_DRIVER]
|
||||
|
||||
@property
|
||||
def managed(self) -> bool:
|
||||
"""Return interface driver."""
|
||||
return self.properties[DBUS_ATTR_MANAGED]
|
||||
|
||||
@property
|
||||
def connection(self) -> Optional[NetworkConnection]:
|
||||
"""Return the connection used for this interface."""
|
||||
return self._connection
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the interface name."""
|
||||
return self.connection.device.interface
|
||||
def settings(self) -> Optional[NetworkSetting]:
|
||||
"""Return the connection settings used for this interface."""
|
||||
return self._settings
|
||||
|
||||
@property
|
||||
def primary(self) -> bool:
|
||||
"""Return true if it's the primary interfac."""
|
||||
return self.connection.primary
|
||||
def wireless(self) -> Optional[NetworkWireless]:
|
||||
"""Return the wireless data for this interface."""
|
||||
return self._wireless
|
||||
|
||||
@property
|
||||
def ip_address(self) -> str:
|
||||
"""Return the ip_address."""
|
||||
return self.connection.ip4_config.address_data.address
|
||||
async def connect(self) -> None:
|
||||
"""Get device information."""
|
||||
self.dbus = await DBus.connect(DBUS_NAME_NM, self.object_path)
|
||||
self.properties = await self.dbus.get_properties(DBUS_NAME_DEVICE)
|
||||
|
||||
@property
|
||||
def prefix(self) -> str:
|
||||
"""Return the network prefix."""
|
||||
return self.connection.ip4_config.address_data.prefix
|
||||
# Abort if device is not managed
|
||||
if not self.managed:
|
||||
return
|
||||
|
||||
@property
|
||||
def type(self) -> ConnectionType:
|
||||
"""Return the interface type."""
|
||||
return self.connection.type
|
||||
# If connection exists
|
||||
if self.properties[DBUS_ATTR_ACTIVE_CONNECTION] != DBUS_OBJECT_BASE:
|
||||
self._connection = NetworkConnection(
|
||||
self.properties[DBUS_ATTR_ACTIVE_CONNECTION]
|
||||
)
|
||||
await self._connection.connect()
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
"""Return the interface id."""
|
||||
return self.connection.id
|
||||
# Attach settings
|
||||
if self.connection and self.connection.setting_object != DBUS_OBJECT_BASE:
|
||||
self._settings = NetworkSetting(self.connection.setting_object)
|
||||
await self._settings.connect()
|
||||
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
"""Return the interface uuid."""
|
||||
return self.connection.uuid
|
||||
|
||||
@property
|
||||
def method(self) -> InterfaceMethod:
|
||||
"""Return the interface method."""
|
||||
return InterfaceMethod(self.connection.ip4_config.method)
|
||||
|
||||
@property
|
||||
def gateway(self) -> str:
|
||||
"""Return the gateway."""
|
||||
return self.connection.ip4_config.gateway
|
||||
|
||||
@property
|
||||
def nameservers(self) -> str:
|
||||
"""Return the nameservers."""
|
||||
return self.connection.ip4_config.nameservers
|
||||
|
||||
async def connect(self, nm_dbus: DBus, connection_object: str) -> None:
|
||||
"""Get connection information."""
|
||||
self._nm_dbus = nm_dbus
|
||||
connection_bus = await DBus.connect(DBUS_NAME_NM, connection_object)
|
||||
connection_properties = await connection_bus.get_properties(
|
||||
DBUS_NAME_CONNECTION_ACTIVE
|
||||
)
|
||||
self._connection = NetworkConnection(connection_object, connection_properties)
|
||||
|
||||
async def update_settings(self, **kwargs) -> None:
|
||||
"""Update IP configuration used for this interface."""
|
||||
payload = interface_update_payload(self, **kwargs)
|
||||
|
||||
await self.connection.settings.dbus.Settings.Connection.Update(payload)
|
||||
|
||||
await self.nm_dbus.ActivateConnection(
|
||||
self.connection.settings.dbus.object_path,
|
||||
self.connection.device.dbus.object_path,
|
||||
DBUS_OBJECT_BASE,
|
||||
)
|
||||
# Wireless
|
||||
if self.type == DeviceType.WIRELESS:
|
||||
self._wireless = NetworkWireless(self.object_path)
|
||||
await self._wireless.connect()
|
||||
|
148
supervisor/dbus/network/setting.py
Normal file
148
supervisor/dbus/network/setting.py
Normal file
@ -0,0 +1,148 @@
|
||||
"""Connection object for Network Manager."""
|
||||
from typing import Any, Awaitable, Optional
|
||||
|
||||
from ...const import ATTR_METHOD, ATTR_MODE, ATTR_PSK, ATTR_SSID
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import DBUS_NAME_NM
|
||||
from ..interface import DBusInterfaceProxy
|
||||
from ..utils import dbus_connected
|
||||
from .configuration import (
|
||||
ConnectionProperties,
|
||||
EthernetProperties,
|
||||
IpProperties,
|
||||
VlanProperties,
|
||||
WirelessProperties,
|
||||
WirelessSecurityProperties,
|
||||
)
|
||||
|
||||
CONF_ATTR_CONNECTION = "connection"
|
||||
CONF_ATTR_802_ETHERNET = "802-3-ethernet"
|
||||
CONF_ATTR_802_WIRELESS = "802-11-wireless"
|
||||
CONF_ATTR_802_WIRELESS_SECURITY = "802-11-wireless-security"
|
||||
CONF_ATTR_VLAN = "vlan"
|
||||
CONF_ATTR_IPV4 = "ipv4"
|
||||
CONF_ATTR_IPV6 = "ipv6"
|
||||
|
||||
ATTR_ID = "id"
|
||||
ATTR_UUID = "uuid"
|
||||
ATTR_TYPE = "type"
|
||||
ATTR_PARENT = "parent"
|
||||
ATTR_ASSIGNED_MAC = "assigned-mac-address"
|
||||
ATTR_POWERSAVE = "powersave"
|
||||
ATTR_AUTH_ALGO = "auth-algo"
|
||||
ATTR_KEY_MGMT = "key-mgmt"
|
||||
|
||||
|
||||
class NetworkSetting(DBusInterfaceProxy):
|
||||
"""NetworkConnection object for Network Manager."""
|
||||
|
||||
def __init__(self, object_path: str) -> None:
|
||||
"""Initialize NetworkConnection object."""
|
||||
self.object_path = object_path
|
||||
self.properties = {}
|
||||
|
||||
self._connection: Optional[ConnectionProperties] = None
|
||||
self._wireless: Optional[WirelessProperties] = None
|
||||
self._wireless_security: Optional[WirelessSecurityProperties] = None
|
||||
self._ethernet: Optional[EthernetProperties] = None
|
||||
self._vlan: Optional[VlanProperties] = None
|
||||
self._ipv4: Optional[IpProperties] = None
|
||||
self._ipv6: Optional[IpProperties] = None
|
||||
|
||||
@property
|
||||
def connection(self) -> Optional[ConnectionProperties]:
|
||||
"""Return connection properties if any."""
|
||||
return self._connection
|
||||
|
||||
@property
|
||||
def wireless(self) -> Optional[WirelessProperties]:
|
||||
"""Return wireless properties if any."""
|
||||
return self._wireless
|
||||
|
||||
@property
|
||||
def wireless_security(self) -> Optional[WirelessSecurityProperties]:
|
||||
"""Return wireless security properties if any."""
|
||||
return self._wireless_security
|
||||
|
||||
@property
|
||||
def ethernet(self) -> Optional[EthernetProperties]:
|
||||
"""Return Ethernet properties if any."""
|
||||
return self._ethernet
|
||||
|
||||
@property
|
||||
def vlan(self) -> Optional[VlanProperties]:
|
||||
"""Return Vlan properties if any."""
|
||||
return self._vlan
|
||||
|
||||
@property
|
||||
def ipv4(self) -> Optional[IpProperties]:
|
||||
"""Return ipv4 properties if any."""
|
||||
return self._ipv4
|
||||
|
||||
@property
|
||||
def ipv6(self) -> Optional[IpProperties]:
|
||||
"""Return ipv6 properties if any."""
|
||||
return self._ipv6
|
||||
|
||||
@dbus_connected
|
||||
def get_settings(self) -> Awaitable[Any]:
|
||||
"""Return connection settings."""
|
||||
return self.dbus.Settings.Connection.GetSettings()
|
||||
|
||||
@dbus_connected
|
||||
def update(self, settings: str) -> Awaitable[None]:
|
||||
"""Update connection settings."""
|
||||
return self.dbus.Settings.Connection.Update(settings)
|
||||
|
||||
@dbus_connected
|
||||
def delete(self) -> Awaitable[None]:
|
||||
"""Delete connection settings."""
|
||||
return self.dbus.Settings.Connection.Delete()
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Get connection information."""
|
||||
self.dbus = await DBus.connect(DBUS_NAME_NM, self.object_path)
|
||||
data = (await self.get_settings())[0]
|
||||
|
||||
if CONF_ATTR_CONNECTION in data:
|
||||
self._connection = ConnectionProperties(
|
||||
data[CONF_ATTR_CONNECTION].get(ATTR_ID),
|
||||
data[CONF_ATTR_CONNECTION].get(ATTR_UUID),
|
||||
data[CONF_ATTR_CONNECTION].get(ATTR_TYPE),
|
||||
)
|
||||
|
||||
if CONF_ATTR_802_ETHERNET in data:
|
||||
self._ethernet = EthernetProperties(
|
||||
data[CONF_ATTR_802_ETHERNET].get(ATTR_ASSIGNED_MAC),
|
||||
)
|
||||
|
||||
if CONF_ATTR_802_WIRELESS in data:
|
||||
self._wireless = WirelessProperties(
|
||||
bytes(data[CONF_ATTR_802_WIRELESS].get(ATTR_SSID, [])).decode(),
|
||||
data[CONF_ATTR_802_WIRELESS].get(ATTR_ASSIGNED_MAC),
|
||||
data[CONF_ATTR_802_WIRELESS].get(ATTR_MODE),
|
||||
data[CONF_ATTR_802_WIRELESS].get(ATTR_POWERSAVE),
|
||||
)
|
||||
|
||||
if CONF_ATTR_802_WIRELESS_SECURITY in data:
|
||||
self._wireless_security = WirelessSecurityProperties(
|
||||
data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_AUTH_ALGO),
|
||||
data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_KEY_MGMT),
|
||||
data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_PSK),
|
||||
)
|
||||
|
||||
if CONF_ATTR_VLAN in data:
|
||||
self._vlan = VlanProperties(
|
||||
data[CONF_ATTR_VLAN].get(ATTR_ID),
|
||||
data[CONF_ATTR_VLAN].get(ATTR_PARENT),
|
||||
)
|
||||
|
||||
if CONF_ATTR_IPV4 in data:
|
||||
self._ipv4 = IpProperties(
|
||||
data[CONF_ATTR_IPV4].get(ATTR_METHOD),
|
||||
)
|
||||
|
||||
if CONF_ATTR_IPV6 in data:
|
||||
self._ipv6 = IpProperties(
|
||||
data[CONF_ATTR_IPV6].get(ATTR_METHOD),
|
||||
)
|
36
supervisor/dbus/network/settings.py
Normal file
36
supervisor/dbus/network/settings.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""Network Manager implementation for DBUS."""
|
||||
import logging
|
||||
from typing import Any, Awaitable
|
||||
|
||||
from ...exceptions import DBusError, DBusInterfaceError
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import DBUS_NAME_NM, DBUS_OBJECT_SETTINGS
|
||||
from ..interface import DBusInterface
|
||||
from ..utils import dbus_connected
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NetworkManagerSettings(DBusInterface):
|
||||
"""Handle D-Bus interface for Network Manager."""
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Connect to system's D-Bus."""
|
||||
try:
|
||||
self.dbus = await DBus.connect(DBUS_NAME_NM, DBUS_OBJECT_SETTINGS)
|
||||
except DBusError:
|
||||
_LOGGER.warning("Can't connect to Network Manager Settings")
|
||||
except DBusInterfaceError:
|
||||
_LOGGER.warning(
|
||||
"No Network Manager Settings support on the host. Local network functions have been disabled."
|
||||
)
|
||||
|
||||
@dbus_connected
|
||||
def add_connection(self, settings: str) -> Awaitable[Any]:
|
||||
"""Add new connection."""
|
||||
return self.dbus.Settings.AddConnection(settings)
|
||||
|
||||
@dbus_connected
|
||||
def reload_connections(self) -> Awaitable[Any]:
|
||||
"""Reload all local connection files."""
|
||||
return self.dbus.Settings.ReloadConnections()
|
@ -1,14 +0,0 @@
|
||||
"""Network utils."""
|
||||
from ipaddress import ip_address
|
||||
|
||||
# Return a 32bit representation of a IP Address
|
||||
|
||||
|
||||
def ip2int(address: str) -> int:
|
||||
"""Return a 32bit representation for a IP address."""
|
||||
return int(ip_address(".".join(address.split(".")[::-1])))
|
||||
|
||||
|
||||
def int2ip(bitaddress: int) -> int:
|
||||
"""Return a IP Address object from a 32bit representation."""
|
||||
return ".".join([str(bitaddress >> (i << 3) & 0xFF) for i in range(0, 4)])
|
51
supervisor/dbus/network/wireless.py
Normal file
51
supervisor/dbus/network/wireless.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""Connection object for Network Manager."""
|
||||
from typing import Any, Awaitable, Optional
|
||||
|
||||
from ...utils.gdbus import DBus
|
||||
from ..const import (
|
||||
DBUS_ATTR_ACTIVE_ACCESSPOINT,
|
||||
DBUS_NAME_DEVICE_WIRELESS,
|
||||
DBUS_NAME_NM,
|
||||
DBUS_OBJECT_BASE,
|
||||
)
|
||||
from ..interface import DBusInterfaceProxy
|
||||
from ..utils import dbus_connected
|
||||
from .accesspoint import NetworkWirelessAP
|
||||
|
||||
|
||||
class NetworkWireless(DBusInterfaceProxy):
|
||||
"""NetworkWireless object for Network Manager."""
|
||||
|
||||
def __init__(self, object_path: str) -> None:
|
||||
"""Initialize NetworkConnection object."""
|
||||
self.object_path = object_path
|
||||
self.properties = {}
|
||||
|
||||
self._active: Optional[NetworkWirelessAP] = None
|
||||
|
||||
@property
|
||||
def active(self) -> Optional[NetworkWirelessAP]:
|
||||
"""Return details about active connection."""
|
||||
return self._active
|
||||
|
||||
@dbus_connected
|
||||
def request_scan(self) -> Awaitable[None]:
|
||||
"""Request a new AP scan."""
|
||||
return self.dbus.Device.Wireless.RequestScan("[]")
|
||||
|
||||
@dbus_connected
|
||||
def get_all_accesspoints(self) -> Awaitable[Any]:
|
||||
"""Return a list of all access points path."""
|
||||
return self.dbus.Device.Wireless.GetAllAccessPoints()
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Get connection information."""
|
||||
self.dbus = await DBus.connect(DBUS_NAME_NM, self.object_path)
|
||||
self.properties = await self.dbus.get_properties(DBUS_NAME_DEVICE_WIRELESS)
|
||||
|
||||
# Get details from current active
|
||||
if self.properties[DBUS_ATTR_ACTIVE_ACCESSPOINT] != DBUS_OBJECT_BASE:
|
||||
self._active = NetworkWirelessAP(
|
||||
self.properties[DBUS_ATTR_ACTIVE_ACCESSPOINT]
|
||||
)
|
||||
await self._active.connect()
|
@ -1,42 +1,43 @@
|
||||
"""Payload generators for DBUS communication."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
import jinja2
|
||||
|
||||
from ...const import ATTR_ADDRESS, ATTR_DNS, ATTR_METHOD, ATTR_PREFIX, ATTR_SSID
|
||||
from ..const import ConnectionType, InterfaceMethod
|
||||
from ..network.utils import ip2int
|
||||
from ...host.const import InterfaceType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...host.network import Interface
|
||||
|
||||
|
||||
INTERFACE_UPDATE_TEMPLATE: Path = (
|
||||
Path(__file__).parents[2].joinpath("dbus/payloads/interface_update.tmpl")
|
||||
)
|
||||
|
||||
|
||||
def interface_update_payload(interface, **kwargs) -> str:
|
||||
def interface_update_payload(
|
||||
interface: Interface, name: Optional[str] = None, uuid: Optional[str] = None
|
||||
) -> str:
|
||||
"""Generate a payload for network interface update."""
|
||||
template = jinja2.Template(INTERFACE_UPDATE_TEMPLATE.read_text())
|
||||
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
|
||||
# Generate UUID
|
||||
if not uuid:
|
||||
uuid = str(uuid4())
|
||||
|
||||
# Generate ID/name
|
||||
if not name and interface.type != InterfaceType.VLAN:
|
||||
name = f"Supervisor {interface.name} - {interface.type!s}"
|
||||
elif not name:
|
||||
name = f"Supervisor {interface.name}.{interface.vlan.id}"
|
||||
|
||||
# Fix SSID
|
||||
if interface.wifi:
|
||||
interface.wifi.ssid = ", ".join(
|
||||
[f"0x{x}" for x in interface.wifi.ssid.encode().hex(",").split(",")]
|
||||
)
|
||||
|
||||
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 interface.type == ConnectionType.WIRELESS:
|
||||
kwargs[ATTR_SSID] = ", ".join(
|
||||
[
|
||||
f"0x{x}"
|
||||
for x in interface.connection.wireless.ssid.encode().hex(",").split(",")
|
||||
]
|
||||
)
|
||||
|
||||
return template.render(interface=interface, options=kwargs)
|
||||
return template.render(interface=interface, name=name, uuid=uuid)
|
||||
|
@ -1,41 +1,104 @@
|
||||
{
|
||||
'connection':
|
||||
{
|
||||
'id': <'{{interface.id}}'>,
|
||||
'type': <'{{interface.type}}'>,
|
||||
'uuid': <'{{interface.uuid}}'>
|
||||
},
|
||||
|
||||
{% if options.get("method") == "auto" %}
|
||||
'ipv4':
|
||||
{
|
||||
'method': <'auto'>
|
||||
'id': <'{{ name }}'>,
|
||||
{% if interface.type != "vlan" %}
|
||||
'interface-name': <'{{ interface.name }}'>,
|
||||
{% endif %}
|
||||
'type': <'{% if interface.type == "ethernet" %}802-3-ethernet{% elif interface.type == "wireless" %}802-11-wireless{% else %}{{ interface.type.value }}{% endif %}'>,
|
||||
'uuid': <'{{ uuid }}'>
|
||||
}
|
||||
{% else %}
|
||||
|
||||
{% if interface.ipv4 %}
|
||||
,
|
||||
'ipv4':
|
||||
{
|
||||
{% if interface.ipv4.method == "dhcp" %}
|
||||
'method': <'auto'>
|
||||
{% elif interface.ipv4.method == "disable" %}
|
||||
'method': <'disabled'>
|
||||
{% else %}
|
||||
'method': <'manual'>,
|
||||
'dns': <[uint32 {{ options.get("dns", interface.nameservers) | list | join(",") }}]>,
|
||||
'dns': <[uint32 {{ interface.ipv4.nameservers | map("int") | join(",") }}]>,
|
||||
'address-data': <[
|
||||
{% for address in interface.ipv4.address %}
|
||||
{
|
||||
'address': <'{{ options.get("address", interface.ip_address) }}'>,
|
||||
'prefix': <uint32 {{ options.get("prefix", interface.prefix) }}>
|
||||
'address': <'{{ address.ip | string }}'>,
|
||||
'prefix': <uint32 {{ address.with_prefixlen.partition("/") | last | int }}>
|
||||
}]>,
|
||||
'gateway': <'{{ options.get("gateway", interface.gateway) }}'>
|
||||
{% endfor %}
|
||||
'gateway': <'{{ interface.ipv4.gateway | string }}'>
|
||||
{% endif %}
|
||||
}
|
||||
{% endif %}
|
||||
{% if interface.type == "802-11-wireless" %}
|
||||
|
||||
{% if interface.ipv6 %}
|
||||
,
|
||||
'ipv6':
|
||||
{
|
||||
{% if interface.ipv6.method == "dhcp" %}
|
||||
'method': <'auto'>
|
||||
{% elif interface.ipv6.method == "disable" %}
|
||||
'method': <'disabled'>
|
||||
{% else %}
|
||||
'method': <'manual'>,
|
||||
'dns': <[uint32 {{ interface.ipv6.nameservers | map("int") | join(",") }}]>,
|
||||
'address-data': <[
|
||||
{% for address in interface.ipv6.address if not address.with_prefixlen.startswith("fe80::") %}
|
||||
{
|
||||
'address': <'{{ address.ip | string }}'>,
|
||||
'prefix': <uint32 {{ address.with_prefixlen.partition("/") | last | int }}>
|
||||
}]>,
|
||||
{% endfor %}
|
||||
'gateway': <'{{ interface.ipv6.gateway | string }}'>
|
||||
{% endif %}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if interface.type == "ethernet" %}
|
||||
,
|
||||
'802-3-ethernet':
|
||||
{
|
||||
'assigned-mac-address': <'stable'>
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if interface.type == "vlan" %}
|
||||
,
|
||||
'vlan':
|
||||
{
|
||||
'id': <uint32 {{ interface.vlan.id }}>,
|
||||
'parent': <'{{ interface.vlan.interface }}'>
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if interface.type == "wireless" %}
|
||||
,
|
||||
'802-11-wireless':
|
||||
{
|
||||
'security': <'802-11-wireless-security'>,
|
||||
'ssid': <[byte {{ options.ssid }}]>
|
||||
'assigned-mac-address': <'stable'>,
|
||||
'ssid': <[byte {{ interface.wifi.ssid }}]>,
|
||||
'mode': <'{{ interface.wifi.mode.value }}'>,
|
||||
'powersave': <uint32 1>
|
||||
|
||||
},
|
||||
'802-11-wireless-security':
|
||||
{
|
||||
'auth-alg': <'{{ interface.connection.wireless.security['auth-alg'] }}'>,
|
||||
'key-mgmt': <'{{ interface.connection.wireless.security['key-mgmt'] }}'>
|
||||
{% if interface.wifi.auth == "web" %}
|
||||
'auth-alg': <'none'>,
|
||||
'key-mgmt': <'none'>
|
||||
{% elif interface.wifi.auth == "wpa-psk" %}
|
||||
'auth-alg': <'shared'>,
|
||||
'key-mgmt': <'wpa-psk'>
|
||||
{% elif interface.wifi.auth == "open" %}
|
||||
'auth-alg': <'open'>,
|
||||
'key-mgmt': <'none'>
|
||||
{% endif %}
|
||||
{% if interface.wifi.psk %}
|
||||
,
|
||||
'psk': <'{{ interface.wifi.psk }}'>
|
||||
{% endif %}
|
||||
}
|
||||
{% endif %}
|
||||
}
|
@ -172,6 +172,14 @@ class HostAppArmorError(HostError):
|
||||
"""Host apparmor functions failed."""
|
||||
|
||||
|
||||
class HostNetworkError(HostError):
|
||||
"""Error with host network."""
|
||||
|
||||
|
||||
class HostNetworkNotFound(HostError):
|
||||
"""Return if host interface is not found."""
|
||||
|
||||
|
||||
# API
|
||||
|
||||
|
||||
|
35
supervisor/host/const.py
Normal file
35
supervisor/host/const.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""Const for host."""
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class InterfaceMethod(str, Enum):
|
||||
"""Configuration of an interface."""
|
||||
|
||||
DISABLED = "disabled"
|
||||
STATIC = "static"
|
||||
DHCP = "dhcp"
|
||||
|
||||
|
||||
class InterfaceType(str, Enum):
|
||||
"""Configuration of an interface."""
|
||||
|
||||
ETHERNET = "ethernet"
|
||||
WIRELESS = "wireless"
|
||||
VLAN = "vlan"
|
||||
|
||||
|
||||
class AuthMethod(str, Enum):
|
||||
"""Authentication method."""
|
||||
|
||||
OPEN = "open"
|
||||
WEB = "web"
|
||||
WPA_PSK = "wpa-psk"
|
||||
|
||||
|
||||
class WifiMode(str, Enum):
|
||||
"""Wifi mode."""
|
||||
|
||||
INFRASTRUCTURE = "infrastructure"
|
||||
MESH = "mesh"
|
||||
ADHOC = "adhoc"
|
||||
AP = "ap"
|
@ -1,11 +1,32 @@
|
||||
"""Info control for host."""
|
||||
import logging
|
||||
from typing import Dict, List
|
||||
from __future__ import annotations
|
||||
|
||||
from supervisor.dbus.network.interface import NetworkInterface
|
||||
import asyncio
|
||||
from ipaddress import IPv4Address, IPv4Interface, IPv6Address, IPv6Interface
|
||||
import logging
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import attr
|
||||
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import DBusError, DBusNotConnectedError, HostNotSupportedError
|
||||
from ..dbus.const import (
|
||||
ConnectionStateType,
|
||||
DeviceType,
|
||||
InterfaceMethod as NMInterfaceMethod,
|
||||
WirelessMethodType,
|
||||
)
|
||||
from ..dbus.network.accesspoint import NetworkWirelessAP
|
||||
from ..dbus.network.connection import NetworkConnection
|
||||
from ..dbus.network.interface import NetworkInterface
|
||||
from ..dbus.payloads.generate import interface_update_payload
|
||||
from ..exceptions import (
|
||||
DBusError,
|
||||
DBusNotConnectedError,
|
||||
HostNetworkError,
|
||||
HostNetworkNotFound,
|
||||
HostNotSupportedError,
|
||||
)
|
||||
from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@ -18,9 +39,13 @@ class NetworkManager(CoreSysAttributes):
|
||||
self.coresys: CoreSys = coresys
|
||||
|
||||
@property
|
||||
def interfaces(self) -> Dict[str, NetworkInterface]:
|
||||
def interfaces(self) -> List[Interface]:
|
||||
"""Return a dictionary of active interfaces."""
|
||||
return self.sys_dbus.network.interfaces
|
||||
interfaces: List[Interface] = []
|
||||
for inet in self.sys_dbus.network.interfaces.values():
|
||||
interfaces.append(Interface.from_dbus_interface(inet))
|
||||
|
||||
return interfaces
|
||||
|
||||
@property
|
||||
def dns_servers(self) -> List[str]:
|
||||
@ -32,7 +57,16 @@ class NetworkManager(CoreSysAttributes):
|
||||
continue
|
||||
servers.extend(config.nameservers)
|
||||
|
||||
return [f"dns://{server}" for server in list(dict.fromkeys(servers))]
|
||||
return list(dict.fromkeys(servers))
|
||||
|
||||
def get(self, inet_name: str) -> Interface:
|
||||
"""Return interface from interface name."""
|
||||
if inet_name not in self.sys_dbus.network.interfaces:
|
||||
raise HostNetworkNotFound()
|
||||
|
||||
return Interface.from_dbus_interface(
|
||||
self.sys_dbus.network.interfaces[inet_name]
|
||||
)
|
||||
|
||||
async def update(self):
|
||||
"""Update properties over dbus."""
|
||||
@ -44,3 +78,237 @@ class NetworkManager(CoreSysAttributes):
|
||||
except DBusNotConnectedError as err:
|
||||
_LOGGER.error("No hostname D-Bus connection available")
|
||||
raise HostNotSupportedError() from err
|
||||
|
||||
async def apply_changes(self, interface: Interface) -> None:
|
||||
"""Apply Interface changes to host."""
|
||||
inet = self.sys_dbus.network.interfaces.get(interface.name)
|
||||
|
||||
# Update exist configuration
|
||||
if inet and inet.settings and interface.enabled:
|
||||
settings = interface_update_payload(
|
||||
interface,
|
||||
name=inet.settings.connection.id,
|
||||
uuid=inet.settings.connection.uuid,
|
||||
)
|
||||
|
||||
try:
|
||||
await inet.settings.update(settings)
|
||||
except DBusError as err:
|
||||
_LOGGER.error("Can't update config on %s", interface.name)
|
||||
raise HostNetworkError() from err
|
||||
|
||||
# Create new configuration and activate interface
|
||||
elif inet and interface.enabled:
|
||||
settings = interface_update_payload(interface)
|
||||
|
||||
try:
|
||||
await self.sys_dbus.network.add_and_activate_connection(
|
||||
settings, inet.object_path
|
||||
)
|
||||
except DBusError as err:
|
||||
_LOGGER.error("Can't create config and activate %s", interface.name)
|
||||
raise HostNetworkError() from err
|
||||
|
||||
# Remove config from interface
|
||||
elif inet and not interface.enabled:
|
||||
try:
|
||||
await inet.settings.delete()
|
||||
except DBusError as err:
|
||||
_LOGGER.error("Can't remove %s", interface.name)
|
||||
raise HostNetworkError() from err
|
||||
|
||||
# Create new interface (like vlan)
|
||||
elif not inet:
|
||||
settings = interface_update_payload(interface)
|
||||
|
||||
try:
|
||||
await self.sys_dbus.network.settings.add_connection(settings)
|
||||
except DBusError as err:
|
||||
_LOGGER.error("Can't create new interface")
|
||||
raise HostNetworkError() from err
|
||||
|
||||
await self.update()
|
||||
|
||||
async def scan_wifi(self, interface: Interface) -> List[AccessPoint]:
|
||||
"""Scan on Interface for AccessPoint."""
|
||||
inet = self.sys_dbus.network.interfaces.get(interface.name)
|
||||
|
||||
if inet.type != DeviceType.WIRELESS:
|
||||
_LOGGER.error("Can only scan with wireless card - %s", interface.name)
|
||||
raise HostNotSupportedError()
|
||||
|
||||
await inet.wireless.request_scan()
|
||||
await asyncio.sleep(5)
|
||||
|
||||
accesspoints: List[AccessPoint] = []
|
||||
for ap_object in (await inet.wireless.get_all_accesspoints())[0]:
|
||||
accesspoint = NetworkWirelessAP(ap_object)
|
||||
|
||||
try:
|
||||
await accesspoint.connect()
|
||||
except DBusError as err:
|
||||
_LOGGER.waring("Can't process an AP: %s", err)
|
||||
continue
|
||||
else:
|
||||
accesspoints.append(
|
||||
AccessPoint(
|
||||
WifiMode[WirelessMethodType(accesspoint.mode).name],
|
||||
accesspoint.ssid,
|
||||
accesspoint.mac,
|
||||
accesspoint.frequency,
|
||||
accesspoint.strength,
|
||||
)
|
||||
)
|
||||
|
||||
return accesspoints
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class AccessPoint:
|
||||
"""Represent a wifi configuration."""
|
||||
|
||||
mode: WifiMode = attr.ib()
|
||||
ssid: str = attr.ib()
|
||||
mac: str = attr.ib()
|
||||
frequency: int = attr.ib()
|
||||
signal: int = attr.ib()
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class IpConfig:
|
||||
"""Represent a IP configuration."""
|
||||
|
||||
method: InterfaceMethod = attr.ib()
|
||||
address: List[Union[IPv4Interface, IPv6Interface]] = attr.ib()
|
||||
gateway: Optional[Union[IPv4Address, IPv6Address]] = attr.ib()
|
||||
nameservers: List[Union[IPv4Address, IPv6Address]] = attr.ib()
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class WifiConfig:
|
||||
"""Represent a wifi configuration."""
|
||||
|
||||
mode: WifiMode = attr.ib()
|
||||
ssid: str = attr.ib()
|
||||
auth: AuthMethod = attr.ib()
|
||||
psk: Optional[str] = attr.ib()
|
||||
signal: Optional[int] = attr.ib()
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class VlanConfig:
|
||||
"""Represent a vlan configuration."""
|
||||
|
||||
id: int = attr.ib()
|
||||
interface: str = attr.ib()
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class Interface:
|
||||
"""Represent a host network interface."""
|
||||
|
||||
name: str = attr.ib()
|
||||
enabled: bool = attr.ib()
|
||||
connected: bool = attr.ib()
|
||||
primary: bool = attr.ib()
|
||||
type: InterfaceType = attr.ib()
|
||||
ipv4: Optional[IpConfig] = attr.ib()
|
||||
ipv6: Optional[IpConfig] = attr.ib()
|
||||
wifi: Optional[WifiConfig] = attr.ib()
|
||||
vlan: Optional[VlanConfig] = attr.ib()
|
||||
|
||||
@staticmethod
|
||||
def from_dbus_interface(inet: NetworkInterface) -> Interface:
|
||||
"""Concert a dbus interface into normal Interface."""
|
||||
return Interface(
|
||||
inet.name,
|
||||
inet.settings is not None,
|
||||
Interface._map_nm_connected(inet.connection),
|
||||
inet.primary,
|
||||
Interface._map_nm_type(inet.type),
|
||||
IpConfig(
|
||||
Interface._map_nm_method(inet.settings.ipv4.method),
|
||||
inet.connection.ipv4.address,
|
||||
inet.connection.ipv4.gateway,
|
||||
inet.connection.ipv4.nameservers,
|
||||
)
|
||||
if inet.connection and inet.connection.ipv4
|
||||
else None,
|
||||
IpConfig(
|
||||
Interface._map_nm_method(inet.settings.ipv6.method),
|
||||
inet.connection.ipv6.address,
|
||||
inet.connection.ipv6.gateway,
|
||||
inet.connection.ipv6.nameservers,
|
||||
)
|
||||
if inet.connection and inet.connection.ipv6
|
||||
else None,
|
||||
Interface._map_nm_wifi(inet),
|
||||
Interface._map_nm_vlan(inet),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _map_nm_method(method: str) -> InterfaceMethod:
|
||||
"""Map IP interface method."""
|
||||
mapping = {
|
||||
NMInterfaceMethod.AUTO: InterfaceMethod.DHCP,
|
||||
NMInterfaceMethod.DISABLED: InterfaceMethod.DISABLED,
|
||||
NMInterfaceMethod.MANUAL: InterfaceMethod.STATIC,
|
||||
}
|
||||
|
||||
return mapping.get(method, InterfaceMethod.DISABLED)
|
||||
|
||||
@staticmethod
|
||||
def _map_nm_connected(connection: Optional[NetworkConnection]) -> bool:
|
||||
"""Map connectivity state."""
|
||||
if not connection:
|
||||
return False
|
||||
|
||||
return connection.state in (
|
||||
ConnectionStateType.ACTIVATED,
|
||||
ConnectionStateType.ACTIVATING,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _map_nm_type(device_type: int) -> InterfaceType:
|
||||
mapping = {
|
||||
DeviceType.ETHERNET: InterfaceType.ETHERNET,
|
||||
DeviceType.WIRELESS: InterfaceType.WIRELESS,
|
||||
DeviceType.VLAN: InterfaceType.VLAN,
|
||||
}
|
||||
return mapping[device_type]
|
||||
|
||||
@staticmethod
|
||||
def _map_nm_wifi(inet: NetworkInterface) -> Optional[WifiConfig]:
|
||||
"""Create mapping to nm wifi property."""
|
||||
if inet.type != DeviceType.WIRELESS or not inet.settings:
|
||||
return None
|
||||
|
||||
# Authentication
|
||||
if inet.settings.wireless_security.key_mgmt == "none":
|
||||
auth = AuthMethod.WEB
|
||||
elif inet.settings.wireless_security.key_mgmt == "wpa-psk":
|
||||
auth = AuthMethod.WPA_PSK
|
||||
else:
|
||||
auth = AuthMethod.OPEN
|
||||
|
||||
# Signal
|
||||
if inet.wireless:
|
||||
signal = inet.wireless.active.strength
|
||||
else:
|
||||
signal = None
|
||||
|
||||
return WifiConfig(
|
||||
WifiMode[WirelessMethodType(inet.settings.wireless.mode).name],
|
||||
inet.settings.wireless.ssid,
|
||||
auth,
|
||||
inet.settings.wireless_security.psk,
|
||||
signal,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _map_nm_vlan(inet: NetworkInterface) -> Optional[WifiConfig]:
|
||||
"""Create mapping to nm vlan property."""
|
||||
if inet.type != DeviceType.VLAN or not inet.settings:
|
||||
return None
|
||||
|
||||
return VlanConfig(inet.settings.vlan.id, inet.settings.vlan.parent)
|
||||
|
@ -70,12 +70,11 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
||||
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
|
||||
for server in [
|
||||
f"dns://{server!s}" for server in self.sys_host.network.dns_servers
|
||||
]:
|
||||
with suppress(vol.Invalid):
|
||||
dns_url(server)
|
||||
servers.append(server)
|
||||
servers.append(dns_url(server))
|
||||
|
||||
return servers
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
"""Test NetworkInterface API."""
|
||||
"""Test NetwrokInterface API."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.const import DOCKER_NETWORK, DOCKER_NETWORK_MASK
|
||||
|
||||
from tests.const import TEST_INTERFACE
|
||||
from tests.const import TEST_INTERFACE, TEST_INTERFACE_WLAN
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -11,7 +13,9 @@ async def test_api_network_info(api_client, coresys):
|
||||
"""Test network manager api."""
|
||||
resp = await api_client.get("/network/info")
|
||||
result = await resp.json()
|
||||
assert TEST_INTERFACE in result["data"]["interfaces"]
|
||||
assert TEST_INTERFACE in (
|
||||
inet["interface"] for inet in result["data"]["interfaces"]
|
||||
)
|
||||
|
||||
assert result["data"]["docker"]["interface"] == DOCKER_NETWORK
|
||||
assert result["data"]["docker"]["address"] == str(DOCKER_NETWORK_MASK)
|
||||
@ -24,7 +28,21 @@ async def test_api_network_interface_info(api_client):
|
||||
"""Test network manager api."""
|
||||
resp = await api_client.get(f"/network/interface/{TEST_INTERFACE}/info")
|
||||
result = await resp.json()
|
||||
assert result["data"]["ip_address"] == "192.168.2.148/24"
|
||||
assert result["data"]["ipv4"]["address"][-1] == "192.168.2.148/24"
|
||||
assert result["data"]["ipv4"]["gateway"] == "192.168.2.1"
|
||||
assert result["data"]["ipv4"]["nameservers"] == ["192.168.2.2"]
|
||||
assert (
|
||||
result["data"]["ipv6"]["address"][0] == "2a03:169:3df5:0:6be9:2588:b26a:a679/64"
|
||||
)
|
||||
assert (
|
||||
result["data"]["ipv6"]["address"][1]
|
||||
== "fd14:949b:c9cc:0:522b:8108:8ff8:cca3/64"
|
||||
)
|
||||
assert result["data"]["ipv6"]["gateway"] == "fe80::da58:d7ff:fe00:9c69"
|
||||
assert result["data"]["ipv6"]["nameservers"] == [
|
||||
"2001:1620:2777:1::10",
|
||||
"2001:1620:2777:2::20",
|
||||
]
|
||||
assert result["data"]["interface"] == TEST_INTERFACE
|
||||
|
||||
|
||||
@ -33,7 +51,21 @@ async def test_api_network_interface_info_default(api_client):
|
||||
"""Test network manager default api."""
|
||||
resp = await api_client.get("/network/interface/default/info")
|
||||
result = await resp.json()
|
||||
assert result["data"]["ip_address"] == "192.168.2.148/24"
|
||||
assert result["data"]["ipv4"]["address"][-1] == "192.168.2.148/24"
|
||||
assert result["data"]["ipv4"]["gateway"] == "192.168.2.1"
|
||||
assert result["data"]["ipv4"]["nameservers"] == ["192.168.2.2"]
|
||||
assert (
|
||||
result["data"]["ipv6"]["address"][0] == "2a03:169:3df5:0:6be9:2588:b26a:a679/64"
|
||||
)
|
||||
assert (
|
||||
result["data"]["ipv6"]["address"][1]
|
||||
== "fd14:949b:c9cc:0:522b:8108:8ff8:cca3/64"
|
||||
)
|
||||
assert result["data"]["ipv6"]["gateway"] == "fe80::da58:d7ff:fe00:9c69"
|
||||
assert result["data"]["ipv6"]["nameservers"] == [
|
||||
"2001:1620:2777:1::10",
|
||||
"2001:1620:2777:2::20",
|
||||
]
|
||||
assert result["data"]["interface"] == TEST_INTERFACE
|
||||
|
||||
|
||||
@ -42,7 +74,14 @@ async def test_api_network_interface_update(api_client):
|
||||
"""Test network manager api."""
|
||||
resp = await api_client.post(
|
||||
f"/network/interface/{TEST_INTERFACE}/update",
|
||||
json={"method": "static", "dns": ["1.1.1.1"], "address": "192.168.2.148/24"},
|
||||
json={
|
||||
"ipv4": {
|
||||
"method": "static",
|
||||
"nameservers": ["1.1.1.1"],
|
||||
"address": ["192.168.2.148/24"],
|
||||
"gateway": "192.168.1.1",
|
||||
}
|
||||
},
|
||||
)
|
||||
result = await resp.json()
|
||||
assert result["result"] == "ok"
|
||||
@ -53,7 +92,9 @@ async def test_api_network_interface_info_invalid(api_client):
|
||||
"""Test network manager api."""
|
||||
resp = await api_client.get("/network/interface/invalid/info")
|
||||
result = await resp.json()
|
||||
assert not result["data"]
|
||||
|
||||
assert result["message"]
|
||||
assert result["result"] == "error"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -68,10 +109,26 @@ async def test_api_network_interface_update_invalid(api_client):
|
||||
assert result["message"] == "You need to supply at least one option to update"
|
||||
|
||||
resp = await api_client.post(
|
||||
f"/network/interface/{TEST_INTERFACE}/update", json={"dns": "1.1.1.1"}
|
||||
f"/network/interface/{TEST_INTERFACE}/update",
|
||||
json={"ipv4": {"nameservers": "1.1.1.1"}},
|
||||
)
|
||||
result = await resp.json()
|
||||
assert (
|
||||
result["message"]
|
||||
== "expected a list for dictionary value @ data['dns']. Got '1.1.1.1'"
|
||||
== "expected a list for dictionary value @ data['ipv4']['nameservers']. Got '1.1.1.1'"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_network_wireless_scan(api_client):
|
||||
"""Test network manager api."""
|
||||
with patch("asyncio.sleep", return_value=AsyncMock()):
|
||||
resp = await api_client.get(
|
||||
f"/network/interface/{TEST_INTERFACE_WLAN}/accesspoints"
|
||||
)
|
||||
result = await resp.json()
|
||||
|
||||
assert ["UPC4814466", "VQ@35(55720"] == [
|
||||
ap["ssid"] for ap in result["data"]["accesspoints"]
|
||||
]
|
||||
assert [47, 63] == [ap["signal"] for ap in result["data"]["accesspoints"]]
|
||||
|
@ -13,3 +13,9 @@ def load_fixture(filename: str) -> str:
|
||||
"""Load a fixture."""
|
||||
path = Path(Path(__file__).parent.joinpath("fixtures"), filename)
|
||||
return path.read_text()
|
||||
|
||||
|
||||
def exists_fixture(filename: str) -> bool:
|
||||
"""Check if a fixture exists."""
|
||||
path = Path(Path(__file__).parent.joinpath("fixtures"), filename)
|
||||
return path.exists()
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Common test functions."""
|
||||
from pathlib import Path
|
||||
import re
|
||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
@ -10,13 +11,11 @@ import pytest
|
||||
from supervisor.api import RestAPI
|
||||
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
|
||||
|
||||
from tests.common import load_fixture, load_json_fixture
|
||||
from tests.common import exists_fixture, load_fixture, load_json_fixture
|
||||
|
||||
# pylint: disable=redefined-outer-name, protected-access
|
||||
|
||||
@ -53,38 +52,53 @@ def docker() -> DockerAPI:
|
||||
def dbus() -> DBus:
|
||||
"""Mock DBUS."""
|
||||
|
||||
async def mock_get_properties(_, interface):
|
||||
return load_json_fixture(f"{interface.replace('.', '_')}.json")
|
||||
dbus_commands = []
|
||||
|
||||
async def mock_get_properties(dbus_obj, interface):
|
||||
latest = dbus_obj.object_path.split("/")[-1]
|
||||
fixture = interface.replace(".", "_")
|
||||
|
||||
if latest.isnumeric():
|
||||
fixture = f"{fixture}_{latest}"
|
||||
|
||||
return load_json_fixture(f"{fixture}.json")
|
||||
|
||||
async def mock_send(_, command, silent=False):
|
||||
if silent:
|
||||
return ""
|
||||
|
||||
filetype = "xml" if "--xml" in command else "fixture"
|
||||
fixture = f"{command[6].replace('/', '_')[1:]}.{filetype}"
|
||||
return load_fixture(fixture)
|
||||
fixture = command[6].replace("/", "_")[1:]
|
||||
if command[1] == "introspect":
|
||||
filetype = "xml"
|
||||
|
||||
if not exists_fixture(f"{fixture}.{filetype}"):
|
||||
fixture = re.sub(r"_[0-9]+$", "", fixture)
|
||||
|
||||
# special case
|
||||
if exists_fixture(f"{fixture}_*.{filetype}"):
|
||||
fixture = f"{fixture}_*"
|
||||
else:
|
||||
fixture = f"{fixture}-{command[10].split('.')[-1]}"
|
||||
filetype = "fixture"
|
||||
|
||||
dbus_commands.append(fixture)
|
||||
|
||||
return load_fixture(f"{fixture}.{filetype}")
|
||||
|
||||
with patch("supervisor.utils.gdbus.DBus._send", new=mock_send), patch(
|
||||
"supervisor.dbus.interface.DBusInterface.is_connected",
|
||||
return_value=True,
|
||||
), patch("supervisor.utils.gdbus.DBus.get_properties", new=mock_get_properties):
|
||||
|
||||
dbus_obj = DBus(DBUS_NAME_NM, DBUS_OBJECT_BASE)
|
||||
|
||||
yield dbus_obj
|
||||
yield dbus_commands
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def network_manager(dbus) -> NetworkManager:
|
||||
"""Mock NetworkManager."""
|
||||
|
||||
async def dns_update():
|
||||
pass
|
||||
|
||||
with patch("supervisor.dbus.network.NetworkManager.dns", return_value=MagicMock()):
|
||||
nm_obj = NetworkManager()
|
||||
nm_obj.dns.update = dns_update
|
||||
nm_obj = NetworkManager()
|
||||
nm_obj.dbus = dbus
|
||||
|
||||
# Init
|
||||
await nm_obj.connect()
|
||||
await nm_obj.update()
|
||||
|
||||
@ -92,7 +106,7 @@ async def network_manager(dbus) -> NetworkManager:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def coresys(loop, docker, dbus, network_manager, aiohttp_client) -> CoreSys:
|
||||
async def coresys(loop, docker, network_manager, aiohttp_client) -> CoreSys:
|
||||
"""Create a CoreSys Mock."""
|
||||
with patch("supervisor.bootstrap.initialize_system_data"), patch(
|
||||
"supervisor.bootstrap.setup_diagnostics"
|
||||
@ -106,10 +120,10 @@ async def coresys(loop, docker, dbus, network_manager, aiohttp_client) -> CoreSy
|
||||
coresys_obj = await initialize_coresys()
|
||||
|
||||
# Mock save json
|
||||
coresys_obj.ingress.save_data = MagicMock()
|
||||
coresys_obj.auth.save_data = MagicMock()
|
||||
coresys_obj.updater.save_data = MagicMock()
|
||||
coresys_obj.config.save_data = MagicMock()
|
||||
coresys_obj._ingress.save_data = MagicMock()
|
||||
coresys_obj._auth.save_data = MagicMock()
|
||||
coresys_obj._updater.save_data = MagicMock()
|
||||
coresys_obj._config.save_data = MagicMock()
|
||||
|
||||
# Mock test client
|
||||
coresys_obj.arch._default_arch = "amd64"
|
||||
@ -117,8 +131,7 @@ async def coresys(loop, docker, dbus, network_manager, aiohttp_client) -> CoreSy
|
||||
coresys_obj._machine_id = uuid4()
|
||||
|
||||
# Mock host communication
|
||||
coresys_obj._dbus = dbus
|
||||
coresys_obj._dbus.network = network_manager
|
||||
coresys_obj._dbus._network = network_manager
|
||||
|
||||
# Mock docker
|
||||
coresys_obj._docker = docker
|
||||
@ -153,15 +166,6 @@ async def api_client(aiohttp_client, coresys: CoreSys):
|
||||
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
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store_manager(coresys: CoreSys):
|
||||
"""Fixture for the store manager."""
|
||||
|
@ -1,3 +1,4 @@
|
||||
"""Consts for tests."""
|
||||
|
||||
TEST_INTERFACE = "eth0"
|
||||
TEST_INTERFACE_WLAN = "wlan0"
|
||||
|
@ -1,15 +1,49 @@
|
||||
"""Test NetwrokInterface."""
|
||||
from ipaddress import IPv4Address, IPv4Interface, IPv6Address, IPv6Interface
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.dbus.const import DeviceType, InterfaceMethod
|
||||
from supervisor.dbus.network import NetworkManager
|
||||
|
||||
from tests.const import TEST_INTERFACE
|
||||
from tests.const import TEST_INTERFACE, TEST_INTERFACE_WLAN
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_network_interface(network_manager: NetworkManager):
|
||||
async def test_network_interface_ethernet(network_manager: NetworkManager):
|
||||
"""Test network interface."""
|
||||
interface = network_manager.interfaces[TEST_INTERFACE]
|
||||
assert interface.name == TEST_INTERFACE
|
||||
assert interface.type == DeviceType.ETHERNET
|
||||
assert interface.connection.state == 2
|
||||
assert interface.connection.uuid == "0c23631e-2118-355c-bbb0-8943229cb0d6"
|
||||
|
||||
assert interface.connection.ipv4.address == [IPv4Interface("192.168.2.148/24")]
|
||||
assert interface.connection.ipv6.address == [
|
||||
IPv6Interface("2a03:169:3df5:0:6be9:2588:b26a:a679/64"),
|
||||
IPv6Interface("fd14:949b:c9cc:0:522b:8108:8ff8:cca3/64"),
|
||||
IPv6Interface("2a03:169:3df5::2f1/128"),
|
||||
IPv6Interface("fd14:949b:c9cc::2f1/128"),
|
||||
IPv6Interface("fe80::ffe3:319e:c630:9f51/64"),
|
||||
]
|
||||
|
||||
assert interface.connection.ipv4.gateway == IPv4Address("192.168.2.1")
|
||||
assert interface.connection.ipv6.gateway == IPv6Address("fe80::da58:d7ff:fe00:9c69")
|
||||
|
||||
assert interface.connection.ipv4.nameservers == [IPv4Address("192.168.2.2")]
|
||||
assert interface.connection.ipv6.nameservers == [
|
||||
IPv6Address("2001:1620:2777:1::10"),
|
||||
IPv6Address("2001:1620:2777:2::20"),
|
||||
]
|
||||
|
||||
assert interface.settings.ipv4.method == InterfaceMethod.AUTO
|
||||
assert interface.settings.ipv6.method == InterfaceMethod.AUTO
|
||||
assert interface.settings.connection.id == "Wired connection 1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_network_interface_wlan(network_manager: NetworkManager):
|
||||
"""Test network interface."""
|
||||
interface = network_manager.interfaces[TEST_INTERFACE_WLAN]
|
||||
assert interface.name == TEST_INTERFACE_WLAN
|
||||
assert interface.type == DeviceType.WIRELESS
|
||||
|
@ -1,12 +0,0 @@
|
||||
"""Test network utils."""
|
||||
from supervisor.dbus.network.utils import int2ip, ip2int
|
||||
|
||||
|
||||
def test_int2ip():
|
||||
"""Test int2ip."""
|
||||
assert int2ip(16885952) == "192.168.1.1"
|
||||
|
||||
|
||||
def test_ip2int():
|
||||
"""Test ip2int."""
|
||||
assert ip2int("192.168.1.1") == 16885952
|
@ -1,39 +1,180 @@
|
||||
"""Test interface update payload."""
|
||||
from ipaddress import ip_address, ip_interface
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.dbus.const import ConnectionType
|
||||
from supervisor.dbus.payloads.generate import interface_update_payload
|
||||
from supervisor.host.const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode
|
||||
from supervisor.host.network import VlanConfig, WifiConfig
|
||||
from supervisor.utils.gdbus import DBus
|
||||
|
||||
from tests.const import TEST_INTERFACE
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interface_update_payload_ethernet(network_interface):
|
||||
async def test_interface_update_payload_ethernet(coresys):
|
||||
"""Test interface update payload."""
|
||||
data = interface_update_payload(network_interface, **{"method": "auto"})
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto"
|
||||
interface = coresys.host.network.get(TEST_INTERFACE)
|
||||
|
||||
data = interface_update_payload(interface)
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto"
|
||||
assert DBus.parse_gvariant(data)["ipv6"]["method"] == "auto"
|
||||
|
||||
data = interface_update_payload(
|
||||
network_interface, **{"address": "1.1.1.1", "dns": ["1.1.1.1", "1.0.0.1"]}
|
||||
)
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["method"] == "manual"
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["address"] == "1.1.1.1"
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["dns"] == [16843009, 16777217]
|
||||
assert (
|
||||
DBus.parse_gvariant(data)["connection"]["uuid"]
|
||||
== "0c23631e-2118-355c-bbb0-8943229cb0d6"
|
||||
DBus.parse_gvariant(data)["802-3-ethernet"]["assigned-mac-address"] == "stable"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interface_update_payload_wireless(network_interface):
|
||||
async def test_interface_update_payload_ethernet_ipv4(coresys):
|
||||
"""Test interface update payload."""
|
||||
network_interface.connection._properties["Type"] = ConnectionType.WIRELESS
|
||||
data = interface_update_payload(network_interface, **{"method": "auto"})
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto"
|
||||
interface = coresys.host.network.get(TEST_INTERFACE)
|
||||
inet = coresys.dbus.network.interfaces[TEST_INTERFACE]
|
||||
|
||||
interface.ipv4.method = InterfaceMethod.STATIC
|
||||
interface.ipv4.address = [ip_interface("192.168.1.1/24")]
|
||||
interface.ipv4.nameservers = [ip_address("1.1.1.1"), ip_address("1.0.1.1")]
|
||||
interface.ipv4.gateway = ip_address("192.168.1.1")
|
||||
|
||||
data = interface_update_payload(
|
||||
network_interface, **{"address": "1.1.1.1", "dns": ["1.1.1.1", "1.0.0.1"]}
|
||||
interface,
|
||||
name=inet.settings.connection.id,
|
||||
uuid=inet.settings.connection.uuid,
|
||||
)
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["method"] == "manual"
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["address"] == "1.1.1.1"
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [78, 69, 84, 84]
|
||||
assert (
|
||||
DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["address"] == "192.168.1.1"
|
||||
)
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["prefix"] == 24
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["dns"] == [16843009, 16777473]
|
||||
assert (
|
||||
DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid
|
||||
)
|
||||
assert DBus.parse_gvariant(data)["connection"]["id"] == inet.settings.connection.id
|
||||
assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet"
|
||||
assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["gateway"] == "192.168.1.1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interface_update_payload_ethernet_ipv6(coresys):
|
||||
"""Test interface update payload."""
|
||||
interface = coresys.host.network.get(TEST_INTERFACE)
|
||||
inet = coresys.dbus.network.interfaces[TEST_INTERFACE]
|
||||
|
||||
interface.ipv6.method = InterfaceMethod.STATIC
|
||||
interface.ipv6.address = [ip_interface("2a03:169:3df5:0:6be9:2588:b26a:a679/64")]
|
||||
interface.ipv6.nameservers = [
|
||||
ip_address("2606:4700:4700::64"),
|
||||
ip_address("2606:4700:4700::6400"),
|
||||
]
|
||||
interface.ipv6.gateway = ip_address("fe80::da58:d7ff:fe00:9c69")
|
||||
|
||||
data = interface_update_payload(
|
||||
interface,
|
||||
name=inet.settings.connection.id,
|
||||
uuid=inet.settings.connection.uuid,
|
||||
)
|
||||
assert DBus.parse_gvariant(data)["ipv6"]["method"] == "manual"
|
||||
assert (
|
||||
DBus.parse_gvariant(data)["ipv6"]["address-data"][0]["address"]
|
||||
== "2a03:169:3df5:0:6be9:2588:b26a:a679"
|
||||
)
|
||||
assert DBus.parse_gvariant(data)["ipv6"]["address-data"][0]["prefix"] == 64
|
||||
assert DBus.parse_gvariant(data)["ipv6"]["dns"] == [
|
||||
50543257694033307102031451402929176676,
|
||||
50543257694033307102031451402929202176,
|
||||
]
|
||||
assert (
|
||||
DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid
|
||||
)
|
||||
assert DBus.parse_gvariant(data)["connection"]["id"] == inet.settings.connection.id
|
||||
assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet"
|
||||
assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name
|
||||
assert DBus.parse_gvariant(data)["ipv6"]["gateway"] == "fe80::da58:d7ff:fe00:9c69"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interface_update_payload_wireless_wpa_psk(coresys):
|
||||
"""Test interface update payload."""
|
||||
interface = coresys.host.network.get(TEST_INTERFACE)
|
||||
|
||||
interface.type = InterfaceType.WIRELESS
|
||||
interface.wifi = WifiConfig(
|
||||
WifiMode.INFRASTRUCTURE, "Test", AuthMethod.WPA_PSK, "password", 0
|
||||
)
|
||||
|
||||
data = interface_update_payload(interface)
|
||||
|
||||
assert DBus.parse_gvariant(data)["connection"]["type"] == "802-11-wireless"
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [84, 101, 115, 116]
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless"]["mode"] == "infrastructure"
|
||||
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless-security"]["auth-alg"] == "shared"
|
||||
assert (
|
||||
DBus.parse_gvariant(data)["802-11-wireless-security"]["key-mgmt"] == "wpa-psk"
|
||||
)
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless-security"]["psk"] == "password"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interface_update_payload_wireless_web(coresys):
|
||||
"""Test interface update payload."""
|
||||
interface = coresys.host.network.get(TEST_INTERFACE)
|
||||
|
||||
interface.type = InterfaceType.WIRELESS
|
||||
interface.wifi = WifiConfig(
|
||||
WifiMode.INFRASTRUCTURE, "Test", AuthMethod.WEB, "password", 0
|
||||
)
|
||||
|
||||
data = interface_update_payload(interface)
|
||||
|
||||
assert DBus.parse_gvariant(data)["connection"]["type"] == "802-11-wireless"
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [84, 101, 115, 116]
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless"]["mode"] == "infrastructure"
|
||||
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless-security"]["auth-alg"] == "none"
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless-security"]["key-mgmt"] == "none"
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless-security"]["psk"] == "password"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interface_update_payload_wireless_open(coresys):
|
||||
"""Test interface update payload."""
|
||||
interface = coresys.host.network.get(TEST_INTERFACE)
|
||||
|
||||
interface.type = InterfaceType.WIRELESS
|
||||
interface.wifi = WifiConfig(
|
||||
WifiMode.INFRASTRUCTURE, "Test", AuthMethod.OPEN, None, 0
|
||||
)
|
||||
|
||||
data = interface_update_payload(interface)
|
||||
|
||||
assert DBus.parse_gvariant(data)["connection"]["type"] == "802-11-wireless"
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [84, 101, 115, 116]
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless"]["mode"] == "infrastructure"
|
||||
assert (
|
||||
DBus.parse_gvariant(data)["802-11-wireless"]["assigned-mac-address"] == "stable"
|
||||
)
|
||||
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless-security"]["auth-alg"] == "open"
|
||||
assert DBus.parse_gvariant(data)["802-11-wireless-security"]["key-mgmt"] == "none"
|
||||
assert "psk" not in DBus.parse_gvariant(data)["802-11-wireless-security"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interface_update_payload_vlan(coresys):
|
||||
"""Test interface update payload."""
|
||||
interface = coresys.host.network.get(TEST_INTERFACE)
|
||||
|
||||
interface.type = InterfaceType.VLAN
|
||||
interface.vlan = VlanConfig(10, interface.name)
|
||||
|
||||
data = interface_update_payload(interface)
|
||||
assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto"
|
||||
assert DBus.parse_gvariant(data)["ipv6"]["method"] == "auto"
|
||||
|
||||
assert DBus.parse_gvariant(data)["vlan"]["id"] == 10
|
||||
assert DBus.parse_gvariant(data)["vlan"]["parent"] == interface.name
|
||||
assert DBus.parse_gvariant(data)["connection"]["type"] == "vlan"
|
||||
assert "interface-name" not in DBus.parse_gvariant(data)["connection"]
|
||||
|
@ -1 +1 @@
|
||||
({'connection': {'id': <'Wired connection 1'>, 'permissions': <@as []>, 'timestamp': <uint64 1598125548>, 'type': <'802-3-ethernet'>, 'uuid': <'0c23631e-2118-355c-bbb0-8943229cb0d6'>}, 'ipv4': {'address-data': <[{'address': <'192.168.2.148'>, 'prefix': <uint32 24>}]>, 'addresses': <[[uint32 2483202240, 24, 16951488]]>, 'dns': <[uint32 16951488]>, 'dns-search': <@as []>, 'gateway': <'192.168.2.1'>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, 'proxy': {}, '802-3-ethernet': {'auto-negotiate': <false>, 'mac-address-blacklist': <@as []>, 's390-options': <@a{ss} {}>}, '802-11-wireless': {'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}},)
|
||||
()
|
@ -1,8 +1,12 @@
|
||||
{
|
||||
"Devices": ["/org/freedesktop/NetworkManager/Devices/1"],
|
||||
"Devices": [
|
||||
"/org/freedesktop/NetworkManager/Devices/1",
|
||||
"/org/freedesktop/NetworkManager/Devices/3"
|
||||
],
|
||||
"AllDevices": [
|
||||
"/org/freedesktop/NetworkManager/Devices/1",
|
||||
"/org/freedesktop/NetworkManager/Devices/2"
|
||||
"/org/freedesktop/NetworkManager/Devices/2",
|
||||
"/org/freedesktop/NetworkManager/Devices/3"
|
||||
],
|
||||
"Checkpoints": [],
|
||||
"NetworkingEnabled": true,
|
||||
|
52
tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_*.xml
vendored
Normal file
52
tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_*.xml
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<!-- GDBus 2.62.5 -->
|
||||
<node>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg type="s" name="interface_name" direction="in"/>
|
||||
<arg type="s" name="property_name" direction="in"/>
|
||||
<arg type="v" name="value" direction="out"/>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg type="s" name="interface_name" direction="in"/>
|
||||
<arg type="a{sv}" name="properties" direction="out"/>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg type="s" name="interface_name" direction="in"/>
|
||||
<arg type="s" name="property_name" direction="in"/>
|
||||
<arg type="v" name="value" direction="in"/>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="s" name="interface_name"/>
|
||||
<arg type="a{sv}" name="changed_properties"/>
|
||||
<arg type="as" name="invalidated_properties"/>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg type="s" name="xml_data" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
<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.NetworkManager.AccessPoint">
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="a{sv}" name="properties"/>
|
||||
</signal>
|
||||
<property type="u" name="Flags" access="read"/>
|
||||
<property type="u" name="WpaFlags" access="read"/>
|
||||
<property type="u" name="RsnFlags" access="read"/>
|
||||
<property type="ay" name="Ssid" access="read"/>
|
||||
<property type="u" name="Frequency" access="read"/>
|
||||
<property type="s" name="HwAddress" access="read"/>
|
||||
<property type="u" name="Mode" access="read"/>
|
||||
<property type="u" name="MaxBitrate" access="read"/>
|
||||
<property type="y" name="Strength" access="read"/>
|
||||
<property type="i" name="LastSeen" access="read"/>
|
||||
</interface>
|
||||
</node>
|
12
tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json
vendored
Normal file
12
tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"Flags": 3,
|
||||
"WpaFlags": 0,
|
||||
"RsnFlags": 392,
|
||||
"Ssid": [85, 80, 67, 52, 56, 49, 52, 52, 54, 54],
|
||||
"Frequency": 2462,
|
||||
"HwAddress": "E4:57:40:A9:D7:DE",
|
||||
"Mode": 2,
|
||||
"MaxBitrate": 195000,
|
||||
"Strength": 47,
|
||||
"LastSeen": 1398776
|
||||
}
|
12
tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json
vendored
Normal file
12
tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"Flags": 1,
|
||||
"WpaFlags": 0,
|
||||
"RsnFlags": 392,
|
||||
"Ssid": [86, 81, 64, 51, 53, 40, 53, 53, 55, 50, 48],
|
||||
"Frequency": 5660,
|
||||
"HwAddress": "18:4B:0D:23:A1:9C",
|
||||
"Mode": 2,
|
||||
"MaxBitrate": 540000,
|
||||
"Strength": 63,
|
||||
"LastSeen": 1398839
|
||||
}
|
30
tests/fixtures/org_freedesktop_NetworkManager_Device_3.json
vendored
Normal file
30
tests/fixtures/org_freedesktop_NetworkManager_Device_3.json
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"Udi": "/sys/devices/platform/soc/fe300000.mmcnr/mmc_host/mmc1/mmc1:0001/mmc1:0001:1/net/wlan0",
|
||||
"Interface": "wlan0",
|
||||
"IpInterface": "",
|
||||
"Driver": "brcmfmac",
|
||||
"DriverVersion": "7.45.154",
|
||||
"FirmwareVersion": "01-4fbe0b04",
|
||||
"Capabilities": 1,
|
||||
"Ip4Address": 0,
|
||||
"State": 30,
|
||||
"StateReason": [30, 42],
|
||||
"ActiveConnection": "/",
|
||||
"Ip4Config": "/",
|
||||
"Dhcp4Config": "/",
|
||||
"Ip6Config": "/",
|
||||
"Dhcp6Config": "/",
|
||||
"Managed": true,
|
||||
"Autoconnect": true,
|
||||
"FirmwareMissing": false,
|
||||
"NmPluginMissing": false,
|
||||
"DeviceType": 2,
|
||||
"AvailableConnections": [],
|
||||
"PhysicalPortId": "",
|
||||
"Mtu": 1500,
|
||||
"Metered": 0,
|
||||
"LldpNeighbors": [],
|
||||
"Real": true,
|
||||
"Ip4Connectivity": 1,
|
||||
"Ip6Connectivity": 1
|
||||
}
|
20
tests/fixtures/org_freedesktop_NetworkManager_Device_Wireless_3.json
vendored
Normal file
20
tests/fixtures/org_freedesktop_NetworkManager_Device_Wireless_3.json
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"HwAddress": "EA:3C:50:4C:B8:82",
|
||||
"PermHwAddress": "DC:A6:32:02:BA:21",
|
||||
"Mode": 2,
|
||||
"Bitrate": 0,
|
||||
"AccessPoints": [
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/41533",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/41534",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/41535",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/41536",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/41537",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/41538",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/41539",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/41540",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/41541"
|
||||
],
|
||||
"ActiveAccessPoint": "/",
|
||||
"WirelessCapabilities": 2047,
|
||||
"LastScan": 1343924585
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<!-- GDBus 2.64.3 -->
|
||||
<!-- GDBus 2.62.5 -->
|
||||
<node>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
@ -42,6 +42,34 @@
|
||||
<property type="t" name="TxBytes" access="read"/>
|
||||
<property type="t" name="RxBytes" access="read"/>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.NetworkManager.Device.Wireless">
|
||||
<method name="GetAccessPoints">
|
||||
<arg type="ao" name="access_points" direction="out"/>
|
||||
</method>
|
||||
<method name="GetAllAccessPoints">
|
||||
<arg type="ao" name="access_points" direction="out"/>
|
||||
</method>
|
||||
<method name="RequestScan">
|
||||
<arg type="a{sv}" name="options" direction="in"/>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="a{sv}" name="properties"/>
|
||||
</signal>
|
||||
<signal name="AccessPointAdded">
|
||||
<arg type="o" name="access_point"/>
|
||||
</signal>
|
||||
<signal name="AccessPointRemoved">
|
||||
<arg type="o" name="access_point"/>
|
||||
</signal>
|
||||
<property type="s" name="HwAddress" access="read"/>
|
||||
<property type="s" name="PermHwAddress" access="read"/>
|
||||
<property type="u" name="Mode" access="read"/>
|
||||
<property type="u" name="Bitrate" access="read"/>
|
||||
<property type="ao" name="AccessPoints" access="read"/>
|
||||
<property type="o" name="ActiveAccessPoint" access="read"/>
|
||||
<property type="u" name="WirelessCapabilities" access="read"/>
|
||||
<property type="x" name="LastScan" access="read"/>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.NetworkManager.Device">
|
||||
<method name="Reapply">
|
||||
<arg type="a{sa{sv}}" name="connection" direction="in"/>
|
||||
@ -88,16 +116,5 @@
|
||||
<property type="b" name="Real" access="read"/>
|
||||
<property type="u" name="Ip4Connectivity" access="read"/>
|
||||
<property type="u" name="Ip6Connectivity" access="read"/>
|
||||
<property type="u" name="InterfaceFlags" access="read"/>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.NetworkManager.Device.Wired">
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="a{sv}" name="properties"/>
|
||||
</signal>
|
||||
<property type="s" name="HwAddress" access="read"/>
|
||||
<property type="s" name="PermHwAddress" access="read"/>
|
||||
<property type="u" name="Speed" access="read"/>
|
||||
<property type="as" name="S390Subchannels" access="read"/>
|
||||
<property type="b" name="Carrier" access="read"/>
|
||||
</interface>
|
||||
</node>
|
1
tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.fixture
vendored
Normal file
1
tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.fixture
vendored
Normal file
@ -0,0 +1 @@
|
||||
([objectpath '/org/freedesktop/NetworkManager/AccessPoint/43099', '/org/freedesktop/NetworkManager/AccessPoint/43100'],)
|
1
tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.fixture
vendored
Normal file
1
tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.fixture
vendored
Normal file
@ -0,0 +1 @@
|
||||
()
|
13
tests/fixtures/org_freedesktop_NetworkManager_DnsManager.json
vendored
Normal file
13
tests/fixtures/org_freedesktop_NetworkManager_DnsManager.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"Mode": "default",
|
||||
"RcManager": "file",
|
||||
"Configuration": [
|
||||
{
|
||||
"nameservers": ["192.168.30.1"],
|
||||
"domains": ["syshack.ch"],
|
||||
"interface": "eth0",
|
||||
"priority": 100,
|
||||
"vpn": false
|
||||
}
|
||||
]
|
||||
}
|
@ -11,7 +11,7 @@
|
||||
{ "dest": "169.254.0.0", "prefix": 16, "metric": 1000 },
|
||||
{ "dest": "0.0.0.0", "prefix": 0, "next-hop": "192.168.2.1", "metric": 100 }
|
||||
],
|
||||
"NameserverData": [{ "address": "192.168.2.1" }],
|
||||
"NameserverData": [{ "address": "192.168.2.2" }],
|
||||
"Nameservers": [16951488],
|
||||
"Domains": [],
|
||||
"Searches": [],
|
52
tests/fixtures/org_freedesktop_NetworkManager_IP6Config_*.xml
vendored
Normal file
52
tests/fixtures/org_freedesktop_NetworkManager_IP6Config_*.xml
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<!-- GDBus 2.62.5 -->
|
||||
<node>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg type="s" name="interface_name" direction="in"/>
|
||||
<arg type="s" name="property_name" direction="in"/>
|
||||
<arg type="v" name="value" direction="out"/>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg type="s" name="interface_name" direction="in"/>
|
||||
<arg type="a{sv}" name="properties" direction="out"/>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg type="s" name="interface_name" direction="in"/>
|
||||
<arg type="s" name="property_name" direction="in"/>
|
||||
<arg type="v" name="value" direction="in"/>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="s" name="interface_name"/>
|
||||
<arg type="a{sv}" name="changed_properties"/>
|
||||
<arg type="as" name="invalidated_properties"/>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg type="s" name="xml_data" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
<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.NetworkManager.IP6Config">
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="a{sv}" name="properties"/>
|
||||
</signal>
|
||||
<property type="a(ayuay)" name="Addresses" access="read"/>
|
||||
<property type="aa{sv}" name="AddressData" access="read"/>
|
||||
<property type="s" name="Gateway" access="read"/>
|
||||
<property type="a(ayuayu)" name="Routes" access="read"/>
|
||||
<property type="aa{sv}" name="RouteData" access="read"/>
|
||||
<property type="aay" name="Nameservers" access="read"/>
|
||||
<property type="as" name="Domains" access="read"/>
|
||||
<property type="as" name="Searches" access="read"/>
|
||||
<property type="as" name="DnsOptions" access="read"/>
|
||||
<property type="i" name="DnsPriority" access="read"/>
|
||||
</interface>
|
||||
</node>
|
115
tests/fixtures/org_freedesktop_NetworkManager_IP6Config_1.json
vendored
Normal file
115
tests/fixtures/org_freedesktop_NetworkManager_IP6Config_1.json
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
{
|
||||
"Addresses": [
|
||||
[
|
||||
[42, 3, 1, 105, 61, 245, 0, 0, 107, 233, 37, 136, 178, 106, 166, 121],
|
||||
64,
|
||||
[254, 128, 0, 0, 0, 0, 0, 0, 218, 88, 215, 255, 254, 0, 256, 105]
|
||||
],
|
||||
[
|
||||
[253, 20, 148, 255, 201, 204, 0, 0, 82, 43, 129, 8, 143, 248, 204, 163],
|
||||
64,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
],
|
||||
[
|
||||
[42, 3, 1, 105, 61, 245, 0, 0, 0, 0, 0, 0, 0, 0, 2, 241],
|
||||
128,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
],
|
||||
[
|
||||
[253, 20, 148, 255, 201, 204, 0, 0, 0, 0, 0, 0, 0, 0, 2, 241],
|
||||
128,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
],
|
||||
[
|
||||
[254, 128, 0, 0, 0, 0, 0, 0, 255, 227, 49, 158, 198, 48, 159, 81],
|
||||
64,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
]
|
||||
],
|
||||
"AddressData": [
|
||||
{ "address": "2a03:169:3df5:0:6be9:2588:b26a:a679", "prefix": 64 },
|
||||
{ "address": "fd14:949b:c9cc:0:522b:8108:8ff8:cca3", "prefix": 64 },
|
||||
{ "address": "2a03:169:3df5::2f1", "prefix": 128 },
|
||||
{ "address": "fd14:949b:c9cc::2f1", "prefix": 128 },
|
||||
{ "address": "fe80::ffe3:319e:c630:9f51", "prefix": 64 }
|
||||
],
|
||||
"Gateway": "fe80::da58:d7ff:fe00:9c69",
|
||||
"Routes": [
|
||||
[
|
||||
[253, 20, 148, 255, 201, 204, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
48,
|
||||
[254, 128, 0, 0, 0, 0, 0, 0, 218, 88, 215, 255, 254, 0, 256, 105],
|
||||
100
|
||||
],
|
||||
[
|
||||
[42, 3, 1, 105, 61, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
48,
|
||||
[254, 128, 0, 0, 0, 0, 0, 0, 218, 88, 215, 255, 254, 0, 256, 105],
|
||||
100
|
||||
],
|
||||
[
|
||||
[253, 20, 148, 255, 201, 204, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
64,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
100
|
||||
],
|
||||
[
|
||||
[42, 3, 1, 105, 61, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
64,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
100
|
||||
],
|
||||
[
|
||||
[42, 3, 1, 105, 61, 245, 0, 0, 0, 0, 0, 0, 0, 0, 2, 241],
|
||||
128,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
100
|
||||
],
|
||||
[
|
||||
[253, 20, 148, 255, 201, 204, 0, 0, 0, 0, 0, 0, 0, 0, 2, 241],
|
||||
128,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
100
|
||||
],
|
||||
[
|
||||
[254, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
64,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
100
|
||||
]
|
||||
],
|
||||
"RouteData": [
|
||||
{
|
||||
"dest": "fd14:949b:c9cc::",
|
||||
"prefix": 48,
|
||||
"next-hop": "fe80::da58:d7ff:fe00:9c69",
|
||||
"metric": 100
|
||||
},
|
||||
{
|
||||
"dest": "2a03:169:3df5::",
|
||||
"prefix": 48,
|
||||
"next-hop": "fe80::da58:d7ff:fe00:9c69",
|
||||
"metric": 100
|
||||
},
|
||||
{ "dest": "fd14:949b:c9cc::", "prefix": 64, "metric": 100 },
|
||||
{ "dest": "2a03:169:3df5::", "prefix": 64, "metric": 100 },
|
||||
{
|
||||
"dest": "::",
|
||||
"prefix": 0,
|
||||
"next-hop": "fe80::da58:d7ff:fe00:9c69",
|
||||
"metric": 100
|
||||
},
|
||||
{ "dest": "2a03:169:3df5::2f1", "prefix": 128, "metric": 100 },
|
||||
{ "dest": "fd14:949b:c9cc::2f1", "prefix": 128, "metric": 100 },
|
||||
{ "dest": "fe80::", "prefix": 64, "metric": 100 },
|
||||
{ "dest": "ff00::", "prefix": 8, "metric": 256, "table": 255 }
|
||||
],
|
||||
"Nameservers": [
|
||||
[32, 1, 22, 32, 39, 119, 0, 1, 0, 0, 0, 0, 0, 0, 0, 16],
|
||||
[32, 1, 22, 32, 39, 119, 0, 2, 0, 0, 0, 0, 0, 0, 0, 32]
|
||||
],
|
||||
"Domains": [],
|
||||
"Searches": [],
|
||||
"DnsOptions": [],
|
||||
"DnsPriority": 100
|
||||
}
|
85
tests/fixtures/org_freedesktop_NetworkManager_Settings.xml
vendored
Normal file
85
tests/fixtures/org_freedesktop_NetworkManager_Settings.xml
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<!-- GDBus 2.62.5 -->
|
||||
<node>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg type="s" name="interface_name" direction="in"/>
|
||||
<arg type="s" name="property_name" direction="in"/>
|
||||
<arg type="v" name="value" direction="out"/>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg type="s" name="interface_name" direction="in"/>
|
||||
<arg type="a{sv}" name="properties" direction="out"/>
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg type="s" name="interface_name" direction="in"/>
|
||||
<arg type="s" name="property_name" direction="in"/>
|
||||
<arg type="v" name="value" direction="in"/>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="s" name="interface_name"/>
|
||||
<arg type="a{sv}" name="changed_properties"/>
|
||||
<arg type="as" name="invalidated_properties"/>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg type="s" name="xml_data" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
<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.NetworkManager.Settings">
|
||||
<method name="ListConnections">
|
||||
<arg type="ao" name="connections" direction="out"/>
|
||||
</method>
|
||||
<method name="GetConnectionByUuid">
|
||||
<arg type="s" name="uuid" direction="in"/>
|
||||
<arg type="o" name="connection" direction="out"/>
|
||||
</method>
|
||||
<method name="AddConnection">
|
||||
<arg type="a{sa{sv}}" name="connection" direction="in"/>
|
||||
<arg type="o" name="path" direction="out"/>
|
||||
</method>
|
||||
<method name="AddConnectionUnsaved">
|
||||
<arg type="a{sa{sv}}" name="connection" direction="in"/>
|
||||
<arg type="o" name="path" direction="out"/>
|
||||
</method>
|
||||
<method name="AddConnection2">
|
||||
<arg type="a{sa{sv}}" name="settings" direction="in"/>
|
||||
<arg type="u" name="flags" direction="in"/>
|
||||
<arg type="a{sv}" name="args" direction="in"/>
|
||||
<arg type="o" name="path" direction="out"/>
|
||||
<arg type="a{sv}" name="result" direction="out"/>
|
||||
</method>
|
||||
<method name="LoadConnections">
|
||||
<arg type="as" name="filenames" direction="in"/>
|
||||
<arg type="b" name="status" direction="out"/>
|
||||
<arg type="as" name="failures" direction="out"/>
|
||||
</method>
|
||||
<method name="ReloadConnections">
|
||||
<arg type="b" name="status" direction="out"/>
|
||||
</method>
|
||||
<method name="SaveHostname">
|
||||
<arg type="s" name="hostname" direction="in"/>
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg type="a{sv}" name="properties"/>
|
||||
</signal>
|
||||
<signal name="NewConnection">
|
||||
<arg type="o" name="connection"/>
|
||||
</signal>
|
||||
<signal name="ConnectionRemoved">
|
||||
<arg type="o" name="connection"/>
|
||||
</signal>
|
||||
<property type="ao" name="Connections" access="read"/>
|
||||
<property type="s" name="Hostname" access="read"/>
|
||||
<property type="b" name="CanModify" access="read"/>
|
||||
</interface>
|
||||
<node name="1"/>
|
||||
</node>
|
1
tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.fixture
vendored
Normal file
1
tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.fixture
vendored
Normal file
@ -0,0 +1 @@
|
||||
()
|
@ -17,24 +17,18 @@ async def test_evaluation(coresys: CoreSys):
|
||||
|
||||
assert operating_system.reason not in coresys.resolution.unsupported
|
||||
|
||||
with patch(
|
||||
"supervisor.utils.gdbus.DBusCallWrapper",
|
||||
return_value=MagicMock(hostname=MagicMock(operating_system="unsupported")),
|
||||
):
|
||||
await operating_system()
|
||||
assert operating_system.reason in coresys.resolution.unsupported
|
||||
coresys.host._info = MagicMock(operating_system="unsupported")
|
||||
await operating_system()
|
||||
assert operating_system.reason in coresys.resolution.unsupported
|
||||
|
||||
coresys.hassos._available = True
|
||||
await operating_system()
|
||||
assert operating_system.reason not in coresys.resolution.unsupported
|
||||
coresys.hassos._available = False
|
||||
|
||||
with patch(
|
||||
"supervisor.utils.gdbus.DBusCallWrapper",
|
||||
return_value=MagicMock(hostname=MagicMock(operating_system=SUPPORTED_OS[0])),
|
||||
):
|
||||
await operating_system()
|
||||
assert operating_system.reason not in coresys.resolution.unsupported
|
||||
coresys.host._info = MagicMock(operating_system=SUPPORTED_OS[0])
|
||||
await operating_system()
|
||||
assert operating_system.reason not in coresys.resolution.unsupported
|
||||
|
||||
|
||||
async def test_did_run(coresys: CoreSys):
|
||||
|
@ -2,7 +2,7 @@
|
||||
# pylint: disable=import-error,protected-access
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from supervisor.const import CoreState
|
||||
from supervisor.const import CoreState, HostFeature
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.resolution.evaluations.systemd import EvaluateSystemd
|
||||
|
||||
@ -14,28 +14,19 @@ async def test_evaluation(coresys: CoreSys):
|
||||
|
||||
assert systemd.reason not in coresys.resolution.unsupported
|
||||
|
||||
with patch(
|
||||
"supervisor.utils.gdbus.DBusCallWrapper",
|
||||
return_value=MagicMock(systemd=MagicMock(is_connected=False)),
|
||||
):
|
||||
await systemd()
|
||||
assert systemd.reason in coresys.resolution.unsupported
|
||||
coresys._host = MagicMock()
|
||||
|
||||
with patch(
|
||||
"supervisor.utils.gdbus.DBusCallWrapper",
|
||||
return_value=MagicMock(hostname=MagicMock(is_connected=False)),
|
||||
):
|
||||
await systemd()
|
||||
assert systemd.reason in coresys.resolution.unsupported
|
||||
coresys.host.supported_features = [HostFeature.HOSTNAME]
|
||||
await systemd()
|
||||
assert systemd.reason in coresys.resolution.unsupported
|
||||
|
||||
with patch(
|
||||
"supervisor.utils.gdbus.DBusCallWrapper",
|
||||
return_value=MagicMock(
|
||||
hostname=MagicMock(is_connected=True), systemd=MagicMock(is_connected=True)
|
||||
),
|
||||
):
|
||||
await systemd()
|
||||
assert systemd.reason not in coresys.resolution.unsupported
|
||||
coresys.host.supported_features = [
|
||||
HostFeature.SERVICES,
|
||||
HostFeature.SHUTDOWN,
|
||||
HostFeature.REBOOT,
|
||||
]
|
||||
await systemd()
|
||||
assert systemd.reason in coresys.resolution.unsupported
|
||||
|
||||
|
||||
async def test_did_run(coresys: CoreSys):
|
||||
|
Loading…
x
Reference in New Issue
Block a user