mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-13 12:16:29 +00:00
Add IPv6 address generation mode & privacy extensions (#5892)
* feat: Add IPv6 address generation mode & privacy extensions Signed-off-by: David Rapan <david@rapan.cz> * Use NetworkManager fixture for settings init tests This fixes the test by since the extended implementation now can read the version of NetworkManager. * Add pytest for addr_gen_mode --------- Signed-off-by: David Rapan <david@rapan.cz> Co-authored-by: Stefan Agner <stefan@agner.ch>
This commit is contained in:
parent
6e6fe5ba39
commit
3b575eedba
@ -10,6 +10,7 @@ import voluptuous as vol
|
||||
|
||||
from ..const import (
|
||||
ATTR_ACCESSPOINTS,
|
||||
ATTR_ADDR_GEN_MODE,
|
||||
ATTR_ADDRESS,
|
||||
ATTR_AUTH,
|
||||
ATTR_CONNECTED,
|
||||
@ -22,6 +23,7 @@ from ..const import (
|
||||
ATTR_ID,
|
||||
ATTR_INTERFACE,
|
||||
ATTR_INTERFACES,
|
||||
ATTR_IP6_PRIVACY,
|
||||
ATTR_IPV4,
|
||||
ATTR_IPV6,
|
||||
ATTR_MAC,
|
||||
@ -46,7 +48,10 @@ from ..exceptions import APIError, APINotFound, HostNetworkNotFound
|
||||
from ..host.configuration import (
|
||||
AccessPoint,
|
||||
Interface,
|
||||
InterfaceAddrGenMode,
|
||||
InterfaceIp6Privacy,
|
||||
InterfaceMethod,
|
||||
Ip6Setting,
|
||||
IpConfig,
|
||||
IpSetting,
|
||||
VlanConfig,
|
||||
@ -68,6 +73,8 @@ _SCHEMA_IPV6_CONFIG = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_ADDRESS): [vol.Coerce(IPv6Interface)],
|
||||
vol.Optional(ATTR_METHOD): vol.Coerce(InterfaceMethod),
|
||||
vol.Optional(ATTR_ADDR_GEN_MODE): vol.Coerce(InterfaceAddrGenMode),
|
||||
vol.Optional(ATTR_IP6_PRIVACY): vol.Coerce(InterfaceIp6Privacy),
|
||||
vol.Optional(ATTR_GATEWAY): vol.Coerce(IPv6Address),
|
||||
vol.Optional(ATTR_NAMESERVERS): [vol.Coerce(IPv6Address)],
|
||||
}
|
||||
@ -94,8 +101,8 @@ SCHEMA_UPDATE = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
def ipconfig_struct(config: IpConfig, setting: IpSetting) -> dict[str, Any]:
|
||||
"""Return a dict with information about ip configuration."""
|
||||
def ip4config_struct(config: IpConfig, setting: IpSetting) -> dict[str, Any]:
|
||||
"""Return a dict with information about IPv4 configuration."""
|
||||
return {
|
||||
ATTR_METHOD: setting.method,
|
||||
ATTR_ADDRESS: [address.with_prefixlen for address in config.address],
|
||||
@ -105,6 +112,19 @@ def ipconfig_struct(config: IpConfig, setting: IpSetting) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def ip6config_struct(config: IpConfig, setting: Ip6Setting) -> dict[str, Any]:
|
||||
"""Return a dict with information about IPv6 configuration."""
|
||||
return {
|
||||
ATTR_METHOD: setting.method,
|
||||
ATTR_ADDR_GEN_MODE: setting.addr_gen_mode,
|
||||
ATTR_IP6_PRIVACY: setting.ip6_privacy,
|
||||
ATTR_ADDRESS: [address.with_prefixlen for address in config.address],
|
||||
ATTR_NAMESERVERS: [str(address) for address in config.nameservers],
|
||||
ATTR_GATEWAY: str(config.gateway) if config.gateway else None,
|
||||
ATTR_READY: config.ready,
|
||||
}
|
||||
|
||||
|
||||
def wifi_struct(config: WifiConfig) -> dict[str, Any]:
|
||||
"""Return a dict with information about wifi configuration."""
|
||||
return {
|
||||
@ -132,10 +152,10 @@ def interface_struct(interface: Interface) -> dict[str, Any]:
|
||||
ATTR_CONNECTED: interface.connected,
|
||||
ATTR_PRIMARY: interface.primary,
|
||||
ATTR_MAC: interface.mac,
|
||||
ATTR_IPV4: ipconfig_struct(interface.ipv4, interface.ipv4setting)
|
||||
ATTR_IPV4: ip4config_struct(interface.ipv4, interface.ipv4setting)
|
||||
if interface.ipv4 and interface.ipv4setting
|
||||
else None,
|
||||
ATTR_IPV6: ipconfig_struct(interface.ipv6, interface.ipv6setting)
|
||||
ATTR_IPV6: ip6config_struct(interface.ipv6, interface.ipv6setting)
|
||||
if interface.ipv6 and interface.ipv6setting
|
||||
else None,
|
||||
ATTR_WIFI: wifi_struct(interface.wifi) if interface.wifi else None,
|
||||
@ -212,25 +232,31 @@ class APINetwork(CoreSysAttributes):
|
||||
for key, config in body.items():
|
||||
if key == ATTR_IPV4:
|
||||
interface.ipv4setting = IpSetting(
|
||||
config.get(ATTR_METHOD, InterfaceMethod.STATIC),
|
||||
config.get(ATTR_ADDRESS, []),
|
||||
config.get(ATTR_GATEWAY),
|
||||
config.get(ATTR_NAMESERVERS, []),
|
||||
method=config.get(ATTR_METHOD, InterfaceMethod.STATIC),
|
||||
address=config.get(ATTR_ADDRESS, []),
|
||||
gateway=config.get(ATTR_GATEWAY),
|
||||
nameservers=config.get(ATTR_NAMESERVERS, []),
|
||||
)
|
||||
elif key == ATTR_IPV6:
|
||||
interface.ipv6setting = IpSetting(
|
||||
config.get(ATTR_METHOD, InterfaceMethod.STATIC),
|
||||
config.get(ATTR_ADDRESS, []),
|
||||
config.get(ATTR_GATEWAY),
|
||||
config.get(ATTR_NAMESERVERS, []),
|
||||
interface.ipv6setting = Ip6Setting(
|
||||
method=config.get(ATTR_METHOD, InterfaceMethod.STATIC),
|
||||
addr_gen_mode=config.get(
|
||||
ATTR_ADDR_GEN_MODE, InterfaceAddrGenMode.DEFAULT
|
||||
),
|
||||
ip6_privacy=config.get(
|
||||
ATTR_IP6_PRIVACY, InterfaceIp6Privacy.DEFAULT
|
||||
),
|
||||
address=config.get(ATTR_ADDRESS, []),
|
||||
gateway=config.get(ATTR_GATEWAY),
|
||||
nameservers=config.get(ATTR_NAMESERVERS, []),
|
||||
)
|
||||
elif key == ATTR_WIFI:
|
||||
interface.wifi = WifiConfig(
|
||||
config.get(ATTR_MODE, WifiMode.INFRASTRUCTURE),
|
||||
config.get(ATTR_SSID, ""),
|
||||
config.get(ATTR_AUTH, AuthMethod.OPEN),
|
||||
config.get(ATTR_PSK, None),
|
||||
None,
|
||||
mode=config.get(ATTR_MODE, WifiMode.INFRASTRUCTURE),
|
||||
ssid=config.get(ATTR_SSID, ""),
|
||||
auth=config.get(ATTR_AUTH, AuthMethod.OPEN),
|
||||
psk=config.get(ATTR_PSK, None),
|
||||
signal=None,
|
||||
)
|
||||
elif key == ATTR_ENABLED:
|
||||
interface.enabled = config
|
||||
@ -277,19 +303,25 @@ class APINetwork(CoreSysAttributes):
|
||||
ipv4_setting = None
|
||||
if ATTR_IPV4 in body:
|
||||
ipv4_setting = IpSetting(
|
||||
body[ATTR_IPV4].get(ATTR_METHOD, InterfaceMethod.AUTO),
|
||||
body[ATTR_IPV4].get(ATTR_ADDRESS, []),
|
||||
body[ATTR_IPV4].get(ATTR_GATEWAY, None),
|
||||
body[ATTR_IPV4].get(ATTR_NAMESERVERS, []),
|
||||
method=body[ATTR_IPV4].get(ATTR_METHOD, InterfaceMethod.AUTO),
|
||||
address=body[ATTR_IPV4].get(ATTR_ADDRESS, []),
|
||||
gateway=body[ATTR_IPV4].get(ATTR_GATEWAY, None),
|
||||
nameservers=body[ATTR_IPV4].get(ATTR_NAMESERVERS, []),
|
||||
)
|
||||
|
||||
ipv6_setting = None
|
||||
if ATTR_IPV6 in body:
|
||||
ipv6_setting = IpSetting(
|
||||
body[ATTR_IPV6].get(ATTR_METHOD, InterfaceMethod.AUTO),
|
||||
body[ATTR_IPV6].get(ATTR_ADDRESS, []),
|
||||
body[ATTR_IPV6].get(ATTR_GATEWAY, None),
|
||||
body[ATTR_IPV6].get(ATTR_NAMESERVERS, []),
|
||||
ipv6_setting = Ip6Setting(
|
||||
method=body[ATTR_IPV6].get(ATTR_METHOD, InterfaceMethod.AUTO),
|
||||
addr_gen_mode=body[ATTR_IPV6].get(
|
||||
ATTR_ADDR_GEN_MODE, InterfaceAddrGenMode.DEFAULT
|
||||
),
|
||||
ip6_privacy=body[ATTR_IPV6].get(
|
||||
ATTR_IP6_PRIVACY, InterfaceIp6Privacy.DEFAULT
|
||||
),
|
||||
address=body[ATTR_IPV6].get(ATTR_ADDRESS, []),
|
||||
gateway=body[ATTR_IPV6].get(ATTR_GATEWAY, None),
|
||||
nameservers=body[ATTR_IPV6].get(ATTR_NAMESERVERS, []),
|
||||
)
|
||||
|
||||
vlan_interface = Interface(
|
||||
|
@ -97,6 +97,7 @@ ATTR_ADDON = "addon"
|
||||
ATTR_ADDONS = "addons"
|
||||
ATTR_ADDONS_CUSTOM_LIST = "addons_custom_list"
|
||||
ATTR_ADDONS_REPOSITORIES = "addons_repositories"
|
||||
ATTR_ADDR_GEN_MODE = "addr_gen_mode"
|
||||
ATTR_ADDRESS = "address"
|
||||
ATTR_ADDRESS_DATA = "address-data"
|
||||
ATTR_ADMIN = "admin"
|
||||
@ -220,6 +221,7 @@ ATTR_INSTALLED = "installed"
|
||||
ATTR_INTERFACE = "interface"
|
||||
ATTR_INTERFACES = "interfaces"
|
||||
ATTR_IP_ADDRESS = "ip_address"
|
||||
ATTR_IP6_PRIVACY = "ip6_privacy"
|
||||
ATTR_IPV4 = "ipv4"
|
||||
ATTR_IPV6 = "ipv6"
|
||||
ATTR_ISSUES = "issues"
|
||||
|
@ -210,6 +210,24 @@ class InterfaceMethod(StrEnum):
|
||||
LINK_LOCAL = "link-local"
|
||||
|
||||
|
||||
class InterfaceAddrGenMode(IntEnum):
|
||||
"""Interface addr_gen_mode."""
|
||||
|
||||
EUI64 = 0
|
||||
STABLE_PRIVACY = 1
|
||||
DEFAULT_OR_EUI64 = 2
|
||||
DEFAULT = 3
|
||||
|
||||
|
||||
class InterfaceIp6Privacy(IntEnum):
|
||||
"""Interface ip6_privacy."""
|
||||
|
||||
DEFAULT = -1
|
||||
DISABLED = 0
|
||||
ENABLED_PREFER_PUBLIC = 1
|
||||
ENABLED = 2
|
||||
|
||||
|
||||
class ConnectionType(StrEnum):
|
||||
"""Connection type."""
|
||||
|
||||
|
@ -77,6 +77,14 @@ class IpProperties:
|
||||
dns: list[bytes | int] | None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Ip6Properties(IpProperties):
|
||||
"""IPv6 properties object for Network Manager."""
|
||||
|
||||
addr_gen_mode: int
|
||||
ip6_privacy: int
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class MatchProperties:
|
||||
"""Match properties object for Network Manager."""
|
||||
|
@ -12,6 +12,7 @@ from ...utils import dbus_connected
|
||||
from ..configuration import (
|
||||
ConnectionProperties,
|
||||
EthernetProperties,
|
||||
Ip6Properties,
|
||||
IpAddress,
|
||||
IpProperties,
|
||||
MatchProperties,
|
||||
@ -58,6 +59,8 @@ CONF_ATTR_IPV4_GATEWAY = "gateway"
|
||||
CONF_ATTR_IPV4_DNS = "dns"
|
||||
|
||||
CONF_ATTR_IPV6_METHOD = "method"
|
||||
CONF_ATTR_IPV6_ADDR_GEN_MODE = "addr-gen-mode"
|
||||
CONF_ATTR_IPV6_PRIVACY = "ip6-privacy"
|
||||
CONF_ATTR_IPV6_ADDRESS_DATA = "address-data"
|
||||
CONF_ATTR_IPV6_GATEWAY = "gateway"
|
||||
CONF_ATTR_IPV6_DNS = "dns"
|
||||
@ -69,6 +72,8 @@ IPV4_6_IGNORE_FIELDS = [
|
||||
"dns-data",
|
||||
"gateway",
|
||||
"method",
|
||||
"addr-gen-mode",
|
||||
"ip6-privacy",
|
||||
]
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
@ -111,7 +116,7 @@ class NetworkSetting(DBusInterface):
|
||||
self._ethernet: EthernetProperties | None = None
|
||||
self._vlan: VlanProperties | None = None
|
||||
self._ipv4: IpProperties | None = None
|
||||
self._ipv6: IpProperties | None = None
|
||||
self._ipv6: Ip6Properties | None = None
|
||||
self._match: MatchProperties | None = None
|
||||
super().__init__()
|
||||
|
||||
@ -151,7 +156,7 @@ class NetworkSetting(DBusInterface):
|
||||
return self._ipv4
|
||||
|
||||
@property
|
||||
def ipv6(self) -> IpProperties | None:
|
||||
def ipv6(self) -> Ip6Properties | None:
|
||||
"""Return ipv6 properties if any."""
|
||||
return self._ipv6
|
||||
|
||||
@ -223,44 +228,52 @@ class NetworkSetting(DBusInterface):
|
||||
# See: https://developer-old.gnome.org/NetworkManager/stable/ch01.html
|
||||
if CONF_ATTR_CONNECTION in data:
|
||||
self._connection = ConnectionProperties(
|
||||
data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_ID),
|
||||
data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_UUID),
|
||||
data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_TYPE),
|
||||
data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_INTERFACE_NAME),
|
||||
id=data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_ID),
|
||||
uuid=data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_UUID),
|
||||
type=data[CONF_ATTR_CONNECTION].get(CONF_ATTR_CONNECTION_TYPE),
|
||||
interface_name=data[CONF_ATTR_CONNECTION].get(
|
||||
CONF_ATTR_CONNECTION_INTERFACE_NAME
|
||||
),
|
||||
)
|
||||
|
||||
if CONF_ATTR_802_ETHERNET in data:
|
||||
self._ethernet = EthernetProperties(
|
||||
data[CONF_ATTR_802_ETHERNET].get(CONF_ATTR_802_ETHERNET_ASSIGNED_MAC),
|
||||
assigned_mac=data[CONF_ATTR_802_ETHERNET].get(
|
||||
CONF_ATTR_802_ETHERNET_ASSIGNED_MAC
|
||||
),
|
||||
)
|
||||
|
||||
if CONF_ATTR_802_WIRELESS in data:
|
||||
self._wireless = WirelessProperties(
|
||||
bytes(
|
||||
ssid=bytes(
|
||||
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_SSID, [])
|
||||
).decode(),
|
||||
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),
|
||||
assigned_mac=data[CONF_ATTR_802_WIRELESS].get(
|
||||
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC
|
||||
),
|
||||
mode=data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_MODE),
|
||||
powersave=data[CONF_ATTR_802_WIRELESS].get(
|
||||
CONF_ATTR_802_WIRELESS_POWERSAVE
|
||||
),
|
||||
)
|
||||
|
||||
if CONF_ATTR_802_WIRELESS_SECURITY in data:
|
||||
self._wireless_security = WirelessSecurityProperties(
|
||||
data[CONF_ATTR_802_WIRELESS_SECURITY].get(
|
||||
auth_alg=data[CONF_ATTR_802_WIRELESS_SECURITY].get(
|
||||
CONF_ATTR_802_WIRELESS_SECURITY_AUTH_ALG
|
||||
),
|
||||
data[CONF_ATTR_802_WIRELESS_SECURITY].get(
|
||||
key_mgmt=data[CONF_ATTR_802_WIRELESS_SECURITY].get(
|
||||
CONF_ATTR_802_WIRELESS_SECURITY_KEY_MGMT
|
||||
),
|
||||
data[CONF_ATTR_802_WIRELESS_SECURITY].get(
|
||||
psk=data[CONF_ATTR_802_WIRELESS_SECURITY].get(
|
||||
CONF_ATTR_802_WIRELESS_SECURITY_PSK
|
||||
),
|
||||
)
|
||||
|
||||
if CONF_ATTR_VLAN in data:
|
||||
self._vlan = VlanProperties(
|
||||
data[CONF_ATTR_VLAN].get(CONF_ATTR_VLAN_ID),
|
||||
data[CONF_ATTR_VLAN].get(CONF_ATTR_VLAN_PARENT),
|
||||
id=data[CONF_ATTR_VLAN].get(CONF_ATTR_VLAN_ID),
|
||||
parent=data[CONF_ATTR_VLAN].get(CONF_ATTR_VLAN_PARENT),
|
||||
)
|
||||
|
||||
if CONF_ATTR_IPV4 in data:
|
||||
@ -268,21 +281,23 @@ class NetworkSetting(DBusInterface):
|
||||
if ips := data[CONF_ATTR_IPV4].get(CONF_ATTR_IPV4_ADDRESS_DATA):
|
||||
address_data = [IpAddress(ip["address"], ip["prefix"]) for ip in ips]
|
||||
self._ipv4 = IpProperties(
|
||||
data[CONF_ATTR_IPV4].get(CONF_ATTR_IPV4_METHOD),
|
||||
address_data,
|
||||
data[CONF_ATTR_IPV4].get(CONF_ATTR_IPV4_GATEWAY),
|
||||
data[CONF_ATTR_IPV4].get(CONF_ATTR_IPV4_DNS),
|
||||
method=data[CONF_ATTR_IPV4].get(CONF_ATTR_IPV4_METHOD),
|
||||
address_data=address_data,
|
||||
gateway=data[CONF_ATTR_IPV4].get(CONF_ATTR_IPV4_GATEWAY),
|
||||
dns=data[CONF_ATTR_IPV4].get(CONF_ATTR_IPV4_DNS),
|
||||
)
|
||||
|
||||
if CONF_ATTR_IPV6 in data:
|
||||
address_data = None
|
||||
if ips := data[CONF_ATTR_IPV6].get(CONF_ATTR_IPV6_ADDRESS_DATA):
|
||||
address_data = [IpAddress(ip["address"], ip["prefix"]) for ip in ips]
|
||||
self._ipv6 = IpProperties(
|
||||
data[CONF_ATTR_IPV6].get(CONF_ATTR_IPV6_METHOD),
|
||||
address_data,
|
||||
data[CONF_ATTR_IPV6].get(CONF_ATTR_IPV6_GATEWAY),
|
||||
data[CONF_ATTR_IPV6].get(CONF_ATTR_IPV6_DNS),
|
||||
self._ipv6 = Ip6Properties(
|
||||
method=data[CONF_ATTR_IPV6].get(CONF_ATTR_IPV6_METHOD),
|
||||
addr_gen_mode=data[CONF_ATTR_IPV6].get(CONF_ATTR_IPV6_ADDR_GEN_MODE),
|
||||
ip6_privacy=data[CONF_ATTR_IPV6].get(CONF_ATTR_IPV6_PRIVACY),
|
||||
address_data=address_data,
|
||||
gateway=data[CONF_ATTR_IPV6].get(CONF_ATTR_IPV6_GATEWAY),
|
||||
dns=data[CONF_ATTR_IPV6].get(CONF_ATTR_IPV6_DNS),
|
||||
)
|
||||
|
||||
if CONF_ATTR_MATCH in data:
|
||||
|
@ -8,8 +8,13 @@ from uuid import uuid4
|
||||
|
||||
from dbus_fast import Variant
|
||||
|
||||
from ....host.configuration import VlanConfig
|
||||
from ....host.const import InterfaceMethod, InterfaceType
|
||||
from ....host.configuration import Ip6Setting, IpSetting, VlanConfig
|
||||
from ....host.const import (
|
||||
InterfaceAddrGenMode,
|
||||
InterfaceIp6Privacy,
|
||||
InterfaceMethod,
|
||||
InterfaceType,
|
||||
)
|
||||
from .. import NetworkManager
|
||||
from . import (
|
||||
CONF_ATTR_802_ETHERNET,
|
||||
@ -36,10 +41,12 @@ from . import (
|
||||
CONF_ATTR_IPV4_GATEWAY,
|
||||
CONF_ATTR_IPV4_METHOD,
|
||||
CONF_ATTR_IPV6,
|
||||
CONF_ATTR_IPV6_ADDR_GEN_MODE,
|
||||
CONF_ATTR_IPV6_ADDRESS_DATA,
|
||||
CONF_ATTR_IPV6_DNS,
|
||||
CONF_ATTR_IPV6_GATEWAY,
|
||||
CONF_ATTR_IPV6_METHOD,
|
||||
CONF_ATTR_IPV6_PRIVACY,
|
||||
CONF_ATTR_MATCH,
|
||||
CONF_ATTR_MATCH_PATH,
|
||||
CONF_ATTR_VLAN,
|
||||
@ -51,7 +58,7 @@ if TYPE_CHECKING:
|
||||
from ....host.configuration import Interface
|
||||
|
||||
|
||||
def _get_ipv4_connection_settings(ipv4setting) -> dict:
|
||||
def _get_ipv4_connection_settings(ipv4setting: IpSetting | None) -> dict:
|
||||
ipv4 = {}
|
||||
if not ipv4setting or ipv4setting.method == InterfaceMethod.AUTO:
|
||||
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "auto")
|
||||
@ -93,10 +100,32 @@ def _get_ipv4_connection_settings(ipv4setting) -> dict:
|
||||
return ipv4
|
||||
|
||||
|
||||
def _get_ipv6_connection_settings(ipv6setting) -> dict:
|
||||
def _get_ipv6_connection_settings(
|
||||
ipv6setting: Ip6Setting | None, support_addr_gen_mode_defaults: bool = False
|
||||
) -> dict:
|
||||
ipv6 = {}
|
||||
if not ipv6setting or ipv6setting.method == InterfaceMethod.AUTO:
|
||||
ipv6[CONF_ATTR_IPV6_METHOD] = Variant("s", "auto")
|
||||
if ipv6setting:
|
||||
if ipv6setting.addr_gen_mode == InterfaceAddrGenMode.EUI64:
|
||||
ipv6[CONF_ATTR_IPV6_ADDR_GEN_MODE] = Variant("i", 0)
|
||||
elif (
|
||||
not support_addr_gen_mode_defaults
|
||||
or ipv6setting.addr_gen_mode == InterfaceAddrGenMode.STABLE_PRIVACY
|
||||
):
|
||||
ipv6[CONF_ATTR_IPV6_ADDR_GEN_MODE] = Variant("i", 1)
|
||||
elif ipv6setting.addr_gen_mode == InterfaceAddrGenMode.DEFAULT_OR_EUI64:
|
||||
ipv6[CONF_ATTR_IPV6_ADDR_GEN_MODE] = Variant("i", 2)
|
||||
else:
|
||||
ipv6[CONF_ATTR_IPV6_ADDR_GEN_MODE] = Variant("i", 3)
|
||||
if ipv6setting.ip6_privacy == InterfaceIp6Privacy.DISABLED:
|
||||
ipv6[CONF_ATTR_IPV6_PRIVACY] = Variant("i", 0)
|
||||
elif ipv6setting.ip6_privacy == InterfaceIp6Privacy.ENABLED_PREFER_PUBLIC:
|
||||
ipv6[CONF_ATTR_IPV6_PRIVACY] = Variant("i", 1)
|
||||
elif ipv6setting.ip6_privacy == InterfaceIp6Privacy.ENABLED:
|
||||
ipv6[CONF_ATTR_IPV6_PRIVACY] = Variant("i", 2)
|
||||
else:
|
||||
ipv6[CONF_ATTR_IPV6_PRIVACY] = Variant("i", -1)
|
||||
elif ipv6setting.method == InterfaceMethod.DISABLED:
|
||||
ipv6[CONF_ATTR_IPV6_METHOD] = Variant("s", "link-local")
|
||||
elif ipv6setting.method == InterfaceMethod.STATIC:
|
||||
@ -183,7 +212,9 @@ def get_connection_from_interface(
|
||||
|
||||
conn[CONF_ATTR_IPV4] = _get_ipv4_connection_settings(interface.ipv4setting)
|
||||
|
||||
conn[CONF_ATTR_IPV6] = _get_ipv6_connection_settings(interface.ipv6setting)
|
||||
conn[CONF_ATTR_IPV6] = _get_ipv6_connection_settings(
|
||||
interface.ipv6setting, network_manager.version >= "1.40.0"
|
||||
)
|
||||
|
||||
if interface.type == InterfaceType.ETHERNET:
|
||||
conn[CONF_ATTR_802_ETHERNET] = {
|
||||
|
@ -8,11 +8,20 @@ from ..dbus.const import (
|
||||
ConnectionStateFlags,
|
||||
ConnectionStateType,
|
||||
DeviceType,
|
||||
InterfaceAddrGenMode as NMInterfaceAddrGenMode,
|
||||
InterfaceIp6Privacy as NMInterfaceIp6Privacy,
|
||||
InterfaceMethod as NMInterfaceMethod,
|
||||
)
|
||||
from ..dbus.network.connection import NetworkConnection
|
||||
from ..dbus.network.interface import NetworkInterface
|
||||
from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode
|
||||
from .const import (
|
||||
AuthMethod,
|
||||
InterfaceAddrGenMode,
|
||||
InterfaceIp6Privacy,
|
||||
InterfaceMethod,
|
||||
InterfaceType,
|
||||
WifiMode,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@ -46,6 +55,14 @@ class IpSetting:
|
||||
nameservers: list[IPv4Address | IPv6Address]
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Ip6Setting(IpSetting):
|
||||
"""Represent a user IPv6 setting."""
|
||||
|
||||
addr_gen_mode: InterfaceAddrGenMode = InterfaceAddrGenMode.DEFAULT
|
||||
ip6_privacy: InterfaceIp6Privacy = InterfaceIp6Privacy.DEFAULT
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class WifiConfig:
|
||||
"""Represent a wifi configuration."""
|
||||
@ -79,7 +96,7 @@ class Interface:
|
||||
ipv4: IpConfig | None
|
||||
ipv4setting: IpSetting | None
|
||||
ipv6: IpConfig | None
|
||||
ipv6setting: IpSetting | None
|
||||
ipv6setting: Ip6Setting | None
|
||||
wifi: WifiConfig | None
|
||||
vlan: VlanConfig | None
|
||||
|
||||
@ -118,8 +135,14 @@ class Interface:
|
||||
ipv4_setting = IpSetting(InterfaceMethod.DISABLED, [], None, [])
|
||||
|
||||
if inet.settings and inet.settings.ipv6:
|
||||
ipv6_setting = IpSetting(
|
||||
ipv6_setting = Ip6Setting(
|
||||
method=Interface._map_nm_method(inet.settings.ipv6.method),
|
||||
addr_gen_mode=Interface._map_nm_addr_gen_mode(
|
||||
inet.settings.ipv6.addr_gen_mode
|
||||
),
|
||||
ip6_privacy=Interface._map_nm_ip6_privacy(
|
||||
inet.settings.ipv6.ip6_privacy
|
||||
),
|
||||
address=[
|
||||
IPv6Interface(f"{ip.address}/{ip.prefix}")
|
||||
for ip in inet.settings.ipv6.address_data
|
||||
@ -134,7 +157,7 @@ class Interface:
|
||||
else [],
|
||||
)
|
||||
else:
|
||||
ipv6_setting = IpSetting(InterfaceMethod.DISABLED, [], None, [])
|
||||
ipv6_setting = Ip6Setting(InterfaceMethod.DISABLED, [], None, [])
|
||||
|
||||
ipv4_ready = (
|
||||
bool(inet.connection)
|
||||
@ -195,6 +218,28 @@ class Interface:
|
||||
|
||||
return mapping.get(method, InterfaceMethod.DISABLED)
|
||||
|
||||
@staticmethod
|
||||
def _map_nm_addr_gen_mode(addr_gen_mode: int) -> InterfaceAddrGenMode:
|
||||
"""Map IPv6 interface addr_gen_mode."""
|
||||
mapping = {
|
||||
NMInterfaceAddrGenMode.EUI64: InterfaceAddrGenMode.EUI64,
|
||||
NMInterfaceAddrGenMode.STABLE_PRIVACY: InterfaceAddrGenMode.STABLE_PRIVACY,
|
||||
NMInterfaceAddrGenMode.DEFAULT_OR_EUI64: InterfaceAddrGenMode.DEFAULT_OR_EUI64,
|
||||
}
|
||||
|
||||
return mapping.get(addr_gen_mode, InterfaceAddrGenMode.DEFAULT)
|
||||
|
||||
@staticmethod
|
||||
def _map_nm_ip6_privacy(ip6_privacy: int) -> InterfaceIp6Privacy:
|
||||
"""Map IPv6 interface ip6_privacy."""
|
||||
mapping = {
|
||||
NMInterfaceIp6Privacy.DISABLED: InterfaceIp6Privacy.DISABLED,
|
||||
NMInterfaceIp6Privacy.ENABLED_PREFER_PUBLIC: InterfaceIp6Privacy.ENABLED_PREFER_PUBLIC,
|
||||
NMInterfaceIp6Privacy.ENABLED: InterfaceIp6Privacy.ENABLED,
|
||||
}
|
||||
|
||||
return mapping.get(ip6_privacy, InterfaceIp6Privacy.DEFAULT)
|
||||
|
||||
@staticmethod
|
||||
def _map_nm_connected(connection: NetworkConnection | None) -> bool:
|
||||
"""Map connectivity state."""
|
||||
|
@ -15,6 +15,24 @@ class InterfaceMethod(StrEnum):
|
||||
AUTO = "auto"
|
||||
|
||||
|
||||
class InterfaceAddrGenMode(StrEnum):
|
||||
"""Configuration of an interface."""
|
||||
|
||||
EUI64 = "eui64"
|
||||
STABLE_PRIVACY = "stable-privacy"
|
||||
DEFAULT_OR_EUI64 = "default-or-eui64"
|
||||
DEFAULT = "default"
|
||||
|
||||
|
||||
class InterfaceIp6Privacy(StrEnum):
|
||||
"""Configuration of an interface."""
|
||||
|
||||
DEFAULT = "default"
|
||||
DISABLED = "disabled"
|
||||
ENABLED_PREFER_PUBLIC = "enabled-prefer-public"
|
||||
ENABLED = "enabled"
|
||||
|
||||
|
||||
class InterfaceType(StrEnum):
|
||||
"""Configuration of an interface."""
|
||||
|
||||
|
@ -51,8 +51,10 @@ async def test_api_network_info(api_client: TestClient, coresys: CoreSys):
|
||||
"ready": False,
|
||||
}
|
||||
assert interface["ipv6"] == {
|
||||
"addr_gen_mode": "default",
|
||||
"address": [],
|
||||
"gateway": None,
|
||||
"ip6_privacy": "default",
|
||||
"method": "disabled",
|
||||
"nameservers": [],
|
||||
"ready": False,
|
||||
|
@ -5,7 +5,7 @@ from unittest.mock import PropertyMock, patch
|
||||
from supervisor.dbus.network import NetworkManager
|
||||
from supervisor.dbus.network.interface import NetworkInterface
|
||||
from supervisor.dbus.network.setting.generate import get_connection_from_interface
|
||||
from supervisor.host.configuration import IpConfig, IpSetting, VlanConfig
|
||||
from supervisor.host.configuration import Ip6Setting, IpConfig, IpSetting, VlanConfig
|
||||
from supervisor.host.const import InterfaceMethod, InterfaceType
|
||||
from supervisor.host.network import Interface
|
||||
|
||||
@ -57,8 +57,8 @@ async def test_generate_from_vlan(network_manager: NetworkManager):
|
||||
type=InterfaceType.VLAN,
|
||||
ipv4=IpConfig([], None, [], None),
|
||||
ipv4setting=IpSetting(InterfaceMethod.AUTO, [], None, []),
|
||||
ipv6=None,
|
||||
ipv6setting=None,
|
||||
ipv6=IpConfig([], None, [], None),
|
||||
ipv6setting=Ip6Setting(InterfaceMethod.AUTO, [], None, []),
|
||||
wifi=None,
|
||||
vlan=VlanConfig(1, "eth0"),
|
||||
)
|
||||
@ -70,6 +70,8 @@ async def test_generate_from_vlan(network_manager: NetworkManager):
|
||||
assert "match" not in connection_payload["connection"]
|
||||
assert "interface-name" not in connection_payload["connection"]
|
||||
assert connection_payload["ipv4"]["method"].value == "auto"
|
||||
assert connection_payload["ipv6"]["addr-gen-mode"].value == 1
|
||||
assert connection_payload["ipv6"]["ip6-privacy"].value == -1
|
||||
|
||||
assert connection_payload["vlan"]["id"].value == 1
|
||||
assert (
|
||||
|
@ -1,14 +1,17 @@
|
||||
"""Test Network Manager Connection object."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
from awesomeversion import AwesomeVersion
|
||||
from dbus_fast import Variant
|
||||
from dbus_fast.aio.message_bus import MessageBus
|
||||
import pytest
|
||||
|
||||
from supervisor.dbus.network import NetworkManager
|
||||
from supervisor.dbus.network.interface import NetworkInterface
|
||||
from supervisor.dbus.network.setting import NetworkSetting
|
||||
from supervisor.dbus.network.setting.generate import get_connection_from_interface
|
||||
from supervisor.host.configuration import Ip6Setting
|
||||
from supervisor.host.const import InterfaceMethod
|
||||
from supervisor.host.network import Interface
|
||||
|
||||
@ -48,6 +51,7 @@ async def fixture_dbus_interface(
|
||||
async def test_ethernet_update(
|
||||
dbus_interface: NetworkInterface,
|
||||
connection_settings_service: ConnectionSettingsService,
|
||||
network_manager: NetworkManager,
|
||||
):
|
||||
"""Test network manager update."""
|
||||
connection_settings_service.Update.calls.clear()
|
||||
@ -55,7 +59,7 @@ async def test_ethernet_update(
|
||||
interface = Interface.from_dbus_interface(dbus_interface)
|
||||
conn = get_connection_from_interface(
|
||||
interface,
|
||||
MagicMock(),
|
||||
network_manager,
|
||||
name=dbus_interface.settings.connection.id,
|
||||
uuid=dbus_interface.settings.connection.uuid,
|
||||
)
|
||||
@ -124,14 +128,16 @@ async def test_ethernet_update(
|
||||
assert "802-11-wireless-security" not in settings
|
||||
|
||||
|
||||
async def test_ipv6_disabled_is_link_local(dbus_interface: NetworkInterface):
|
||||
async def test_ipv6_disabled_is_link_local(
|
||||
dbus_interface: NetworkInterface, network_manager: NetworkManager
|
||||
):
|
||||
"""Test disabled equals link local for ipv6."""
|
||||
interface = Interface.from_dbus_interface(dbus_interface)
|
||||
interface.ipv4setting.method = InterfaceMethod.DISABLED
|
||||
interface.ipv6setting.method = InterfaceMethod.DISABLED
|
||||
conn = get_connection_from_interface(
|
||||
interface,
|
||||
MagicMock(),
|
||||
network_manager,
|
||||
name=dbus_interface.settings.connection.id,
|
||||
uuid=dbus_interface.settings.connection.uuid,
|
||||
)
|
||||
@ -140,6 +146,33 @@ async def test_ipv6_disabled_is_link_local(dbus_interface: NetworkInterface):
|
||||
assert conn["ipv6"]["method"] == Variant("s", "link-local")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["version", "addr_gen_mode"],
|
||||
[
|
||||
("1.38.0", 1),
|
||||
("1.40.0", 3),
|
||||
],
|
||||
)
|
||||
async def test_ipv6_addr_gen_mode(
|
||||
dbus_interface: NetworkInterface, version: str, addr_gen_mode: int
|
||||
):
|
||||
"""Test addr_gen_mode with various NetworkManager versions."""
|
||||
interface = Interface.from_dbus_interface(dbus_interface)
|
||||
interface.ipv6setting = Ip6Setting(InterfaceMethod.AUTO, [], None, [])
|
||||
|
||||
network_manager = MagicMock()
|
||||
type(network_manager).version = PropertyMock(return_value=AwesomeVersion(version))
|
||||
conn = get_connection_from_interface(
|
||||
interface,
|
||||
network_manager,
|
||||
name=dbus_interface.settings.connection.id,
|
||||
uuid=dbus_interface.settings.connection.uuid,
|
||||
)
|
||||
|
||||
assert conn["ipv6"]["method"] == Variant("s", "auto")
|
||||
assert conn["ipv6"]["addr-gen-mode"] == Variant("i", addr_gen_mode)
|
||||
|
||||
|
||||
async def test_watching_updated_signal(
|
||||
connection_settings_service: ConnectionSettingsService, dbus_session_bus: MessageBus
|
||||
):
|
||||
|
Loading…
x
Reference in New Issue
Block a user