Merge 9a1f7543e7c42b90a046d398ea56263604aca301 into 606db3585cb07a7e04a309fbfd540403da1a4e86

This commit is contained in:
Jack Thomasson 2025-02-18 20:59:14 +01:00 committed by GitHub
commit 4bbef0a4f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 100 additions and 15 deletions

View File

@ -12,6 +12,8 @@ from ..const import (
ATTR_ACCESSPOINTS,
ATTR_ADDRESS,
ATTR_AUTH,
ATTR_BAND,
ATTR_CHANNEL,
ATTR_CONNECTED,
ATTR_DNS,
ATTR_DOCKER,
@ -52,7 +54,7 @@ from ..host.configuration import (
VlanConfig,
WifiConfig,
)
from ..host.const import AuthMethod, InterfaceType, WifiMode
from ..host.const import AuthMethod, InterfaceType, WifiBand, WifiMode
from .utils import api_process, api_validate
_SCHEMA_IPV4_CONFIG = vol.Schema(
@ -79,6 +81,8 @@ _SCHEMA_WIFI_CONFIG = vol.Schema(
vol.Optional(ATTR_AUTH): vol.Coerce(AuthMethod),
vol.Optional(ATTR_SSID): str,
vol.Optional(ATTR_PSK): str,
vol.Optional(ATTR_BAND): vol.Coerce(WifiBand),
vol.Optional(ATTR_CHANNEL): vol.Coerce(int),
}
)
@ -112,6 +116,8 @@ def wifi_struct(config: WifiConfig) -> dict[str, Any]:
ATTR_AUTH: config.auth,
ATTR_SSID: config.ssid,
ATTR_SIGNAL: config.signal,
ATTR_BAND: config.band,
ATTR_CHANNEL: config.channel,
}
@ -227,6 +233,8 @@ class APINetwork(CoreSysAttributes):
config.get(ATTR_AUTH, AuthMethod.OPEN),
config.get(ATTR_PSK, None),
None,
config.get(ATTR_BAND, None),
config.get(ATTR_CHANNEL, None),
)
elif key == ATTR_ENABLED:
interface.enabled = config

View File

@ -119,6 +119,7 @@ ATTR_BACKUP_POST = "backup_post"
ATTR_BACKUP_PRE = "backup_pre"
ATTR_BACKUPS = "backups"
ATTR_BACKUPS_EXCLUDE_DATABASE = "backups_exclude_database"
ATTR_BAND = "band"
ATTR_BLK_READ = "blk_read"
ATTR_BLK_WRITE = "blk_write"
ATTR_BOARD = "board"

View File

@ -204,6 +204,7 @@ class InterfaceMethod(StrEnum):
MANUAL = "manual"
DISABLED = "disabled"
LINK_LOCAL = "link-local"
SHARED = "shared"
class ConnectionType(StrEnum):

View File

@ -33,6 +33,8 @@ class WirelessProperties:
assigned_mac: str | None
mode: str | None
powersave: int | None
band: str | None
channel: int | None
@dataclass(slots=True)

View File

@ -48,6 +48,8 @@ CONF_ATTR_802_WIRELESS_MODE = "mode"
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC = "assigned-mac-address"
CONF_ATTR_802_WIRELESS_SSID = "ssid"
CONF_ATTR_802_WIRELESS_POWERSAVE = "powersave"
CONF_ATTR_802_WIRELESS_BAND = "band"
CONF_ATTR_802_WIRELESS_CHANNEL = "channel"
CONF_ATTR_802_WIRELESS_SECURITY_AUTH_ALG = "auth-alg"
CONF_ATTR_802_WIRELESS_SECURITY_KEY_MGMT = "key-mgmt"
CONF_ATTR_802_WIRELESS_SECURITY_PSK = "psk"
@ -234,6 +236,8 @@ class NetworkSetting(DBusInterface):
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_ASSIGNED_MAC),
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_MODE),
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_POWERSAVE),
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_BAND),
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_CHANNEL),
)
if CONF_ATTR_802_WIRELESS_SECURITY in data:

View File

@ -15,6 +15,8 @@ from . import (
CONF_ATTR_802_ETHERNET_ASSIGNED_MAC,
CONF_ATTR_802_WIRELESS,
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC,
CONF_ATTR_802_WIRELESS_BAND,
CONF_ATTR_802_WIRELESS_CHANNEL,
CONF_ATTR_802_WIRELESS_MODE,
CONF_ATTR_802_WIRELESS_POWERSAVE,
CONF_ATTR_802_WIRELESS_SECURITY,
@ -50,15 +52,7 @@ if TYPE_CHECKING:
from ....host.configuration import Interface
def _get_ipv4_connection_settings(ipv4setting) -> dict:
ipv4 = {}
if not ipv4setting or ipv4setting.method == InterfaceMethod.AUTO:
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "auto")
elif ipv4setting.method == InterfaceMethod.DISABLED:
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "disabled")
elif ipv4setting.method == InterfaceMethod.STATIC:
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "manual")
def _get_address_data(ipv4setting) -> Variant:
address_data = []
for address in ipv4setting.address:
address_data.append(
@ -68,9 +62,23 @@ def _get_ipv4_connection_settings(ipv4setting) -> dict:
}
)
ipv4[CONF_ATTR_IPV4_ADDRESS_DATA] = Variant("aa{sv}", address_data)
return Variant("aa{sv}", address_data)
def _get_ipv4_connection_settings(ipv4setting) -> dict:
ipv4 = {}
if not ipv4setting or ipv4setting.method == InterfaceMethod.AUTO:
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "auto")
elif ipv4setting.method == InterfaceMethod.DISABLED:
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "disabled")
elif ipv4setting.method == InterfaceMethod.STATIC:
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "manual")
ipv4[CONF_ATTR_IPV4_ADDRESS_DATA] = _get_address_data(ipv4setting)
if ipv4setting.gateway:
ipv4[CONF_ATTR_IPV4_GATEWAY] = Variant("s", str(ipv4setting.gateway))
elif ipv4setting.method == InterfaceMethod.SHARED:
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "shared")
ipv4[CONF_ATTR_IPV4_ADDRESS_DATA] = _get_address_data(ipv4setting)
else:
raise RuntimeError("Invalid IPv4 InterfaceMethod")
@ -199,13 +207,21 @@ def get_connection_from_interface(
elif interface.type == InterfaceType.WIRELESS:
wireless = {
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC: Variant("s", "preserve"),
CONF_ATTR_802_WIRELESS_MODE: Variant("s", "infrastructure"),
CONF_ATTR_802_WIRELESS_MODE: Variant(
"s", (interface.wifi and interface.wifi.mode) or "infrastructure"
),
CONF_ATTR_802_WIRELESS_POWERSAVE: Variant("i", 1),
}
if interface.wifi and interface.wifi.ssid:
wireless[CONF_ATTR_802_WIRELESS_SSID] = Variant(
"ay", interface.wifi.ssid.encode("UTF-8")
)
if interface.wifi and interface.wifi.band:
wireless[CONF_ATTR_802_WIRELESS_BAND] = Variant("s", interface.wifi.band)
if interface.wifi and interface.wifi.channel:
wireless[CONF_ATTR_802_WIRELESS_CHANNEL] = Variant(
"u", interface.wifi.channel
)
conn[CONF_ATTR_802_WIRELESS] = wireless

View File

@ -12,7 +12,7 @@ from ..dbus.const import (
)
from ..dbus.network.connection import NetworkConnection
from ..dbus.network.interface import NetworkInterface
from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode
from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiBand, WifiMode
@dataclass(slots=True)
@ -55,6 +55,8 @@ class WifiConfig:
auth: AuthMethod
psk: str | None
signal: int | None
band: WifiBand | None
channel: int | None
@dataclass(slots=True)
@ -191,6 +193,7 @@ class Interface:
NMInterfaceMethod.DISABLED: InterfaceMethod.DISABLED,
NMInterfaceMethod.MANUAL: InterfaceMethod.STATIC,
NMInterfaceMethod.LINK_LOCAL: InterfaceMethod.DISABLED,
NMInterfaceMethod.SHARED: InterfaceMethod.SHARED,
}
return mapping.get(method, InterfaceMethod.DISABLED)
@ -237,6 +240,12 @@ class Interface:
if inet.settings.wireless.mode:
mode = WifiMode(inet.settings.wireless.mode)
# Band and Channel
band = channel = None
if mode == WifiMode.AP:
band = WifiBand(inet.settings.wireless.band)
channel = inet.settings.wireless.channel
# Signal
if inet.wireless and inet.wireless.active:
signal = inet.wireless.active.strength
@ -249,10 +258,12 @@ class Interface:
auth,
psk,
signal,
band,
channel,
)
@staticmethod
def _map_nm_vlan(inet: NetworkInterface) -> WifiConfig | None:
def _map_nm_vlan(inet: NetworkInterface) -> VlanConfig | None:
"""Create mapping to nm vlan property."""
if inet.type != DeviceType.VLAN or not inet.settings:
return None

View File

@ -13,6 +13,7 @@ class InterfaceMethod(StrEnum):
DISABLED = "disabled"
STATIC = "static"
AUTO = "auto"
SHARED = "shared"
class InterfaceType(StrEnum):
@ -31,6 +32,13 @@ class AuthMethod(StrEnum):
WPA_PSK = "wpa-psk"
class WifiBand(StrEnum):
"""Wifi band."""
A = "a"
BG = "bg"
class WifiMode(StrEnum):
"""Wifi mode."""

View File

@ -276,6 +276,31 @@ async def test_api_network_interface_update_wifi_error(api_client: TestClient):
)
async def test_api_network_interface_update_wifi_bad_channel(api_client: TestClient):
"""Test network interface WiFi API error handling for bad channel."""
# Simulate frontend WiFi interface edit where the user selects a bad channel.
resp = await api_client.post(
f"/network/interface/{TEST_INTERFACE_WLAN_NAME}/update",
json={
"enabled": True,
"ipv4": {
"method": "shared",
"address": ["10.42.0.1/24"],
},
"ipv6": {
"method": "auto",
},
"wifi": {"mode": "ap", "ssid": "HotSpot", "band": "bg", "channel": 17},
},
)
result = await resp.json()
assert result["result"] == "error"
assert (
result["message"]
== "Can't create config and activate wlan0: 802-11-wireless.channel: '17' is not a valid channel"
)
async def test_api_network_interface_update_remove(api_client: TestClient):
"""Test network manager api."""
resp = await api_client.post(

View File

@ -240,6 +240,15 @@ class NetworkManager(DBusServiceMock):
"org.freedesktop.NetworkManager.Device.InvalidConnection",
"A 'wireless' setting with a valid SSID is required if no AP path was given.",
)
if (
"channel" in connection["802-11-wireless"]
and connection["802-11-wireless"]["channel"].value > 14
):
raise DBusError(
"org.freedesktop.NetworkManager.Device.InvalidConnection",
# this is the actual error from NetworkManager
f"802-11-wireless.channel: '{connection['802-11-wireless']['channel'].value}' is not a valid channel",
)
return [
"/org/freedesktop/NetworkManager/Settings/1",