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_ACCESSPOINTS,
ATTR_ADDRESS, ATTR_ADDRESS,
ATTR_AUTH, ATTR_AUTH,
ATTR_BAND,
ATTR_CHANNEL,
ATTR_CONNECTED, ATTR_CONNECTED,
ATTR_DNS, ATTR_DNS,
ATTR_DOCKER, ATTR_DOCKER,
@ -52,7 +54,7 @@ from ..host.configuration import (
VlanConfig, VlanConfig,
WifiConfig, WifiConfig,
) )
from ..host.const import AuthMethod, InterfaceType, WifiMode from ..host.const import AuthMethod, InterfaceType, WifiBand, WifiMode
from .utils import api_process, api_validate from .utils import api_process, api_validate
_SCHEMA_IPV4_CONFIG = vol.Schema( _SCHEMA_IPV4_CONFIG = vol.Schema(
@ -79,6 +81,8 @@ _SCHEMA_WIFI_CONFIG = vol.Schema(
vol.Optional(ATTR_AUTH): vol.Coerce(AuthMethod), vol.Optional(ATTR_AUTH): vol.Coerce(AuthMethod),
vol.Optional(ATTR_SSID): str, vol.Optional(ATTR_SSID): str,
vol.Optional(ATTR_PSK): 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_AUTH: config.auth,
ATTR_SSID: config.ssid, ATTR_SSID: config.ssid,
ATTR_SIGNAL: config.signal, 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_AUTH, AuthMethod.OPEN),
config.get(ATTR_PSK, None), config.get(ATTR_PSK, None),
None, None,
config.get(ATTR_BAND, None),
config.get(ATTR_CHANNEL, None),
) )
elif key == ATTR_ENABLED: elif key == ATTR_ENABLED:
interface.enabled = config interface.enabled = config

View File

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

View File

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

View File

@ -33,6 +33,8 @@ class WirelessProperties:
assigned_mac: str | None assigned_mac: str | None
mode: str | None mode: str | None
powersave: int | None powersave: int | None
band: str | None
channel: int | None
@dataclass(slots=True) @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_ASSIGNED_MAC = "assigned-mac-address"
CONF_ATTR_802_WIRELESS_SSID = "ssid" CONF_ATTR_802_WIRELESS_SSID = "ssid"
CONF_ATTR_802_WIRELESS_POWERSAVE = "powersave" 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_AUTH_ALG = "auth-alg"
CONF_ATTR_802_WIRELESS_SECURITY_KEY_MGMT = "key-mgmt" CONF_ATTR_802_WIRELESS_SECURITY_KEY_MGMT = "key-mgmt"
CONF_ATTR_802_WIRELESS_SECURITY_PSK = "psk" 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_ASSIGNED_MAC),
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_MODE), 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_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: 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_ETHERNET_ASSIGNED_MAC,
CONF_ATTR_802_WIRELESS, CONF_ATTR_802_WIRELESS,
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC, 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_MODE,
CONF_ATTR_802_WIRELESS_POWERSAVE, CONF_ATTR_802_WIRELESS_POWERSAVE,
CONF_ATTR_802_WIRELESS_SECURITY, CONF_ATTR_802_WIRELESS_SECURITY,
@ -50,6 +52,19 @@ if TYPE_CHECKING:
from ....host.configuration import Interface from ....host.configuration import Interface
def _get_address_data(ipv4setting) -> Variant:
address_data = []
for address in ipv4setting.address:
address_data.append(
{
"address": Variant("s", str(address.ip)),
"prefix": Variant("u", int(address.with_prefixlen.split("/")[-1])),
}
)
return Variant("aa{sv}", address_data)
def _get_ipv4_connection_settings(ipv4setting) -> dict: def _get_ipv4_connection_settings(ipv4setting) -> dict:
ipv4 = {} ipv4 = {}
if not ipv4setting or ipv4setting.method == InterfaceMethod.AUTO: if not ipv4setting or ipv4setting.method == InterfaceMethod.AUTO:
@ -58,19 +73,12 @@ def _get_ipv4_connection_settings(ipv4setting) -> dict:
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "disabled") ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "disabled")
elif ipv4setting.method == InterfaceMethod.STATIC: elif ipv4setting.method == InterfaceMethod.STATIC:
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "manual") ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "manual")
ipv4[CONF_ATTR_IPV4_ADDRESS_DATA] = _get_address_data(ipv4setting)
address_data = []
for address in ipv4setting.address:
address_data.append(
{
"address": Variant("s", str(address.ip)),
"prefix": Variant("u", int(address.with_prefixlen.split("/")[-1])),
}
)
ipv4[CONF_ATTR_IPV4_ADDRESS_DATA] = Variant("aa{sv}", address_data)
if ipv4setting.gateway: if ipv4setting.gateway:
ipv4[CONF_ATTR_IPV4_GATEWAY] = Variant("s", str(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: else:
raise RuntimeError("Invalid IPv4 InterfaceMethod") raise RuntimeError("Invalid IPv4 InterfaceMethod")
@ -199,13 +207,21 @@ def get_connection_from_interface(
elif interface.type == InterfaceType.WIRELESS: elif interface.type == InterfaceType.WIRELESS:
wireless = { wireless = {
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC: Variant("s", "preserve"), 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), CONF_ATTR_802_WIRELESS_POWERSAVE: Variant("i", 1),
} }
if interface.wifi and interface.wifi.ssid: if interface.wifi and interface.wifi.ssid:
wireless[CONF_ATTR_802_WIRELESS_SSID] = Variant( wireless[CONF_ATTR_802_WIRELESS_SSID] = Variant(
"ay", interface.wifi.ssid.encode("UTF-8") "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 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.connection import NetworkConnection
from ..dbus.network.interface import NetworkInterface from ..dbus.network.interface import NetworkInterface
from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiBand, WifiMode
@dataclass(slots=True) @dataclass(slots=True)
@ -55,6 +55,8 @@ class WifiConfig:
auth: AuthMethod auth: AuthMethod
psk: str | None psk: str | None
signal: int | None signal: int | None
band: WifiBand | None
channel: int | None
@dataclass(slots=True) @dataclass(slots=True)
@ -191,6 +193,7 @@ class Interface:
NMInterfaceMethod.DISABLED: InterfaceMethod.DISABLED, NMInterfaceMethod.DISABLED: InterfaceMethod.DISABLED,
NMInterfaceMethod.MANUAL: InterfaceMethod.STATIC, NMInterfaceMethod.MANUAL: InterfaceMethod.STATIC,
NMInterfaceMethod.LINK_LOCAL: InterfaceMethod.DISABLED, NMInterfaceMethod.LINK_LOCAL: InterfaceMethod.DISABLED,
NMInterfaceMethod.SHARED: InterfaceMethod.SHARED,
} }
return mapping.get(method, InterfaceMethod.DISABLED) return mapping.get(method, InterfaceMethod.DISABLED)
@ -237,6 +240,12 @@ class Interface:
if inet.settings.wireless.mode: if inet.settings.wireless.mode:
mode = WifiMode(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 # Signal
if inet.wireless and inet.wireless.active: if inet.wireless and inet.wireless.active:
signal = inet.wireless.active.strength signal = inet.wireless.active.strength
@ -249,10 +258,12 @@ class Interface:
auth, auth,
psk, psk,
signal, signal,
band,
channel,
) )
@staticmethod @staticmethod
def _map_nm_vlan(inet: NetworkInterface) -> WifiConfig | None: def _map_nm_vlan(inet: NetworkInterface) -> VlanConfig | None:
"""Create mapping to nm vlan property.""" """Create mapping to nm vlan property."""
if inet.type != DeviceType.VLAN or not inet.settings: if inet.type != DeviceType.VLAN or not inet.settings:
return None return None

View File

@ -13,6 +13,7 @@ class InterfaceMethod(StrEnum):
DISABLED = "disabled" DISABLED = "disabled"
STATIC = "static" STATIC = "static"
AUTO = "auto" AUTO = "auto"
SHARED = "shared"
class InterfaceType(StrEnum): class InterfaceType(StrEnum):
@ -31,6 +32,13 @@ class AuthMethod(StrEnum):
WPA_PSK = "wpa-psk" WPA_PSK = "wpa-psk"
class WifiBand(StrEnum):
"""Wifi band."""
A = "a"
BG = "bg"
class WifiMode(StrEnum): class WifiMode(StrEnum):
"""Wifi mode.""" """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): async def test_api_network_interface_update_remove(api_client: TestClient):
"""Test network manager api.""" """Test network manager api."""
resp = await api_client.post( resp = await api_client.post(

View File

@ -240,6 +240,15 @@ class NetworkManager(DBusServiceMock):
"org.freedesktop.NetworkManager.Device.InvalidConnection", "org.freedesktop.NetworkManager.Device.InvalidConnection",
"A 'wireless' setting with a valid SSID is required if no AP path was given.", "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 [ return [
"/org/freedesktop/NetworkManager/Settings/1", "/org/freedesktop/NetworkManager/Settings/1",