From 76a3c2cc4c7ac07fa220637f89eab2f776daab96 Mon Sep 17 00:00:00 2001 From: andrey-pr Date: Fri, 21 Jul 2023 16:09:39 +0200 Subject: [PATCH 01/16] Added async vpn example (#58) Thank you! --- .../async/add-openvpn-connection-async.py | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100755 examples/async/add-openvpn-connection-async.py diff --git a/examples/async/add-openvpn-connection-async.py b/examples/async/add-openvpn-connection-async.py new file mode 100755 index 0000000..4200ebd --- /dev/null +++ b/examples/async/add-openvpn-connection-async.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to create a new VPN network connection profile. Currently supported only with tls-auth +# +# $ examples/async/add-openvpn-connection-async.py --help +# usage: add-openvpn-connection.py [-h] [-c CONN_ID] [-d DEV] [--remote REMOTE] [--remote-cert-tls] [-a] [--save] +# [--ca, CA_PATH] [--cert, CERT_PATH] [--key, KEY_path] [--ta, TA_PATH] +# +# Optional arguments have example values: +# +# optional arguments: +# -h, --help show this help message and exit +# -c CONN_ID Connection Id +# -u UUID Connection UUID +# -f OVPN .ovpn connection file +# -a autoconnect +# --save Save +# --ca Path to CA file +# --cert Path to cert file +# --key Path to key file +# --ta Path to tls-auth file +# +# $ add-vpn-connection.py +# New unsaved connection profile created, show it with: +# nmcli connection show "MyConnectionExample"|grep -v -e -- -e default +# +# Connection Profile settings are described at: +# https://networkmanager.dev/docs/api/latest/ref-settings.html +# +# Note: By default, it uses add_connection_unsaved() to add a temporary +# memory-only connection which is not saved to the system-connections folder: +# For reference, see: https://networkmanager.dev/docs/api/latest/spec.html +# -> org.freedesktop.NetworkManager.Settings (Settings Profile Manager) + +import asyncio +import functools +import logging +import sdbus +from uuid import uuid4 +from argparse import ArgumentParser +from pprint import pformat +from sdbus_async.networkmanager import ( + NetworkManagerSettings as SettingsManager, + ConnectionType, +) +from sdbus_async.networkmanager.settings import ( + ConnectionProfile, + ConnectionSettings, + Ipv4Settings, + Ipv6Settings, + VpnSettings +) + + +async def add_vpn_connection_async(conn_id: str, + dev: str, + remote: str, + remote_cert_tls: str, + uuid, + auto: bool, + save: bool, + ca: str, + cert: str, + key: str, + ta: str) -> str: + # Add a temporary (not yet saved) network connection profile + # param Namespace args: dev, remote, remote_cert_tls, ca_path, cert_path, key_path, ta_path + # return: dbus connection path of the created connection profile + + info = logging.getLogger().info + + # If we add many connections passing the same id, things get messy. Check: + if await SettingsManager().get_connections_by_id(conn_id): + print(f'Connection "{conn_id}" exists, remove it first') + print(f'Run: nmcli connection delete "{conn_id}"') + return "" + + profile = ConnectionProfile( + connection=ConnectionSettings( + connection_id=conn_id, + uuid=str(uuid), + connection_type=ConnectionType.VPN.value, + autoconnect=bool(auto), + ), + ipv4=Ipv4Settings(method="auto"), + ipv6=Ipv6Settings(method="auto"), + vpn=VpnSettings(data={ + 'ca': ca, + 'cert': cert, + 'cert-pass-flags': '0', + 'connection-type': 'tls', + 'dev': dev, + 'key': key, + 'remote': remote, + 'remote-cert-tls': remote_cert_tls, + 'ta': ta, + 'ta-dir': '1' + }, service_type='org.freedesktop.NetworkManager.openvpn') + ) + + s = SettingsManager() + save = bool(save) + addconnection = s.add_connection if save else s.add_connection_unsaved + connection_settings_dbus_path = await addconnection(profile.to_dbus()) + created = "created and saved" if save else "created" + info(f"New unsaved connection profile {created}, show it with:") + info(f'nmcli connection show "{conn_id}"|grep -v -e -- -e default') + info("Settings used:") + info(functools.partial(pformat, sort_dicts=False)(profile.to_settings_dict())) + return connection_settings_dbus_path + + +if __name__ == "__main__": + logging.basicConfig(format="%(message)s", level=logging.INFO) + p = ArgumentParser(description="Optional arguments have example values:") + conn_id = "MyConnectionExample" + p.add_argument("-c", dest="conn_id", default=conn_id, help="Connection Id") + p.add_argument("-u", dest="uuid", default=uuid4(), help="Connection UUID") + p.add_argument("--dev", dest="dev", default="tun", help="VPN Dev") + p.add_argument("--remote", dest="remote", default="example.com:443:tcp", help="VPN Remote") + p.add_argument("--remote-cert-tls", dest="remote_cert_tls", default="server", help="VPN Remote cert tls") + p.add_argument("--ca", dest="ca", required=True, help="VPN CA file path") + p.add_argument("--cert", dest="cert", required=True, help="VPN cert file path") + p.add_argument("--key", dest="key", required=True, help="VPN key file path") + p.add_argument("--ta", dest="ta", required=True, help="VPN TA file path") + p.add_argument("-a", dest="auto", action="store_true", help="autoconnect") + p.add_argument("--save", dest="save", action="store_true", help="Save") + args = p.parse_args() + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + if connection_dpath := asyncio.run(add_vpn_connection_async(**vars(args))): + print(f"Path of the new connection: {connection_dpath}") + print(f"UUID of the new connection: {args.uuid}") + else: + print("Error: No new connection created.") From 68485e6e0043cbb4ef75b99aec086d8a0cb44179 Mon Sep 17 00:00:00 2001 From: igo95862 Date: Mon, 20 Nov 2023 00:30:48 +0600 Subject: [PATCH 02/16] Update enums in accordance with upstream documentation This begins version 3.0 All enums were revisisted and updated in accordance to NetworkManager documentation. Some enums and their fields were renamed: * `AccessPointCapabilities` -> `WifiAccessPointCapabilities` * `WirelessCapabilities` -> `WifiCapabilities` * `WpaSecurityFlags` -> `WifiAccessPointSecurityFlags` * `P2P_*` -> `PAIR_*` * `BROADCAST_*` -> `GROUP_*` * `AUTH_*` -> `KEY_MGMT_*` * `ConnectionState` -> `ActiveConnectionState` * `ConnectionStateReason` -> `ActiveConnectionStateReason` * `ConnectionFlags` -> `SettingsConnectionFlags` * `ConnectionStateFlags` -> `ActivationStateFlags` * `DeviceCapabilities` -> `DeviceCapabilitiesFlags` * `BluetoothCapabilities` -> `BluetoothCapabilitiesFlags` * `ModemCapabilities` -> `ModemCapabilitiesFlags` * `SecretAgentCapabilities` -> `SecretAgentCapabilitiesFlags` * `VpnState` -> `VpnServiceState` * `VpnFailure` * `LOGIN_FAILURE` -> `LOGIN_FAILED` New enums: * `NetworkManagerCapabilities` * `WimaxNSPNetworkType` * `SecretAgentGetSecretsFlags` * `CheckpointCreateFlags` * `CheckpointRollbackResult` * `SettingsAddConnection2Flags` * `SettingsUpdate2Flags` * `DeviceReapplyFlags` * `NetworkManagerReloadFlags` * `RadioFlags` * `MptcpFlags` * `VpnConnectionState` * `VpnConnectionStateReason` --- CHANGELOG.md | 42 + docs/enums.rst | 83 +- examples/async/device-state-async.py | 6 +- examples/block/device-state.py | 6 +- sdbus_async/networkmanager/__init__.py | 78 +- sdbus_async/networkmanager/enums.py | 1718 +++++++++++------ .../networkmanager/interfaces_devices.py | 46 +- .../networkmanager/interfaces_other.py | 78 +- sdbus_block/networkmanager/__init__.py | 78 +- 9 files changed, 1418 insertions(+), 717 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d507da0..8435887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +## 3.0.0 + +### Breaking changes + +All enums were revisisted and updated in accordance to NetworkManager documentation. + +Some enums and their fields were renamed: + +* `AccessPointCapabilities` -> `WifiAccessPointCapabilities` +* `WirelessCapabilities` -> `WifiCapabilities` +* `WpaSecurityFlags` -> `WifiAccessPointSecurityFlags` + * `P2P_*` -> `PAIR_*` + * `BROADCAST_*` -> `GROUP_*` + * `AUTH_*` -> `KEY_MGMT_*` +* `ConnectionState` -> `ActiveConnectionState` +* `ConnectionStateReason` -> `ActiveConnectionStateReason` +* `ConnectionFlags` -> `SettingsConnectionFlags` +* `ConnectionStateFlags` -> `ActivationStateFlags` +* `DeviceCapabilities` -> `DeviceCapabilitiesFlags` +* `BluetoothCapabilities` -> `BluetoothCapabilitiesFlags` +* `ModemCapabilities` -> `ModemCapabilitiesFlags` +* `SecretAgentCapabilities` -> `SecretAgentCapabilitiesFlags` +* `VpnState` -> `VpnServiceState` +* `VpnFailure` + * `LOGIN_FAILURE` -> `LOGIN_FAILED` + +New enums: + +* `NetworkManagerCapabilities` +* `WimaxNSPNetworkType` +* `SecretAgentGetSecretsFlags` +* `CheckpointCreateFlags` +* `CheckpointRollbackResult` +* `SettingsAddConnection2Flags` +* `SettingsUpdate2Flags` +* `DeviceReapplyFlags` +* `NetworkManagerReloadFlags` +* `RadioFlags` +* `MptcpFlags` +* `VpnConnectionState` +* `VpnConnectionStateReason` + ## 2.0.0 ### Warning if you used pre-release version diff --git a/docs/enums.rst b/docs/enums.rst index 946f685..90a8cc4 100644 --- a/docs/enums.rst +++ b/docs/enums.rst @@ -1,74 +1,43 @@ Enums ================ -.. autoclass:: sdbus_async.networkmanager.AccessPointCapabilities - :members: +Python's Enum quick intro +------------------------- -.. autoclass:: sdbus_async.networkmanager.WpaSecurityFlags - :members: +There are two types of enums. ``IntEnum`` is used for a discrete values +and ``IntFlag`` is used for bit flags. For example, ``DeviceType`` identifies +a single device type as a device cannot be of multiple types. +``WifiCapabilities`` shows a particular Wifi device capabilities which it +can have multiple, for example, supporting both 5GHz and 2.4GHz radio bands. -.. autoclass:: sdbus_async.networkmanager.WiFiOperationMode - :members: +Usually ``IntEnum`` is implied unless the enum's name ends with ``Flag``. -.. autoclass:: sdbus_async.networkmanager.SecretAgentCapabilities - :members: +Example code using enums: -.. autoclass:: sdbus_async.networkmanager.ConnectionState - :members: +.. code-block:: python -.. autoclass:: sdbus_async.networkmanager.ConnectionStateFlags - :members: + from sdbus_async.networkmanager.enums import DeviceType, WifiCapabilities -.. autoclass:: sdbus_async.networkmanager.ConnectionStateReason - :members: + # Get particular device type from an integer + DeviceType(2) == DeviceType.WIFI + # Returns: True -.. autoclass:: sdbus_async.networkmanager.BluetoothCapabilities - :members: + # Check if a specific flag is enabled + WifiCapabilities.FREQ_2GHZ in WifiCapabilities(0x00000400 | 0x00000200) + # Returns: True -.. autoclass:: sdbus_async.networkmanager.IpTunnelMode - :members: + # Iterate over all enabled flags + list(WifiCapabilities(0x00000400 | 0x00000200)) + # Returns: [, ] -.. autoclass:: sdbus_async.networkmanager.ModemCapabilities - :members: +`See Python's standard library documentation for more detailed +tutorial and API reference. `_ -.. autoclass:: sdbus_async.networkmanager.WirelessCapabilities - :members: +NetworkManager's enums +------------------------- -.. autoclass:: sdbus_async.networkmanager.DeviceCapabilities - :members: - -.. autoclass:: sdbus_async.networkmanager.DeviceState - :members: - -.. autoclass:: sdbus_async.networkmanager.DeviceStateReason - :members: - -.. autoclass:: sdbus_async.networkmanager.DeviceType - :members: - -.. autoclass:: sdbus_async.networkmanager.DeviceMetered - :members: - -.. autoclass:: sdbus_async.networkmanager.ConnectivityState - :members: - -.. autoclass:: sdbus_async.networkmanager.DeviceInterfaceFlags - :members: - -.. autoclass:: sdbus_async.networkmanager.ConnectionFlags - :members: - -.. autoclass:: sdbus_async.networkmanager.VpnState - :members: - -.. autoclass:: sdbus_async.networkmanager.VpnFailure - :members: - -.. autoclass:: sdbus_async.networkmanager.NetworkManagerConnectivityState - :members: - -.. autoclass:: sdbus_async.networkmanager.NetworkManagerState - :members: +.. automodule:: sdbus_async.networkmanager.enums + :members: Helper classes ----------------------- diff --git a/examples/async/device-state-async.py b/examples/async/device-state-async.py index d55e36b..558ed3a 100755 --- a/examples/async/device-state-async.py +++ b/examples/async/device-state-async.py @@ -24,7 +24,7 @@ NetworkDeviceGeneric, DeviceState, DeviceType, - DeviceCapabilities as Capabilities, + DeviceCapabilitiesFlags, ActiveConnection, ConnectivityState, ) @@ -46,7 +46,9 @@ async def list_active_hardware_networkdevice_states(only_hw: bool) -> None: generic = NetworkDeviceGeneric(device_path) # Demonstrates an enum to match devices using capabilities: - if only_hw and await generic.capabilities & Capabilities.IS_SOFTWARE: + if only_hw and ( + DeviceCapabilitiesFlags.IS_SOFTWARE + in DeviceCapabilitiesFlags(await generic.capabilities)): continue # Create the strings for the columns using the names of the enums: diff --git a/examples/block/device-state.py b/examples/block/device-state.py index 2070072..138e163 100755 --- a/examples/block/device-state.py +++ b/examples/block/device-state.py @@ -23,7 +23,7 @@ NetworkDeviceGeneric, DeviceState, DeviceType, - DeviceCapabilities as Capabilities, + DeviceCapabilitiesFlags, ActiveConnection, ConnectivityState, ) @@ -45,7 +45,9 @@ def list_active_hardware_networkdevice_states(only_hw: bool) -> None: generic_dev = NetworkDeviceGeneric(device_path) # Demonstrates an enum to match devices using capabilities: - if only_hw and generic_dev.capabilities & Capabilities.IS_SOFTWARE: + if only_hw and ( + DeviceCapabilitiesFlags.IS_SOFTWARE + in DeviceCapabilitiesFlags(generic_dev.capabilities)): continue # Create the strings for the columns using the names of the enums: diff --git a/sdbus_async/networkmanager/__init__.py b/sdbus_async/networkmanager/__init__.py index 6f81a22..81a390c 100644 --- a/sdbus_async/networkmanager/__init__.py +++ b/sdbus_async/networkmanager/__init__.py @@ -20,30 +20,43 @@ from __future__ import annotations from .enums import ( - AccessPointCapabilities, - BluetoothCapabilities, - ConnectionFlags, - ConnectionState, - ConnectionStateFlags, - ConnectionStateReason, + ActivationStateFlags, + ActiveConnectionState, + ActiveConnectionStateReason, + BluetoothCapabilitiesFlags, + CheckpointCreateFlags, + CheckpointRollbackResult, + ConnectionMultiConnect, ConnectionType, ConnectivityState, - DeviceCapabilities, + DeviceCapabilitiesFlags, DeviceInterfaceFlags, DeviceMetered, + DeviceReapplyFlags, DeviceState, DeviceStateReason, DeviceType, IpTunnelMode, - ModemCapabilities, - NetworkManagerConnectivityState, + ModemCapabilitiesFlags, + MptcpFlags, + NetworkManagerCapabilitiesFlags, + NetworkManagerReloadFlags, NetworkManagerState, - SecretAgentCapabilities, + RadioFlags, + SecretAgentCapabilitiesFlags, + SecretAgentGetSecretsFlags, + SettingsAddConnection2Flags, + SettingsConnectionFlags, + SettingsUpdate2Flags, + VpnConnectionState, + VpnConnectionStateReason, VpnFailure, - VpnState, + VpnServiceState, + WifiAccessPointCapabilitiesFlags, + WifiAccessPointSecurityFlags, + WifiCapabilitiesFlags, WiFiOperationMode, - WirelessCapabilities, - WpaSecurityFlags, + WimaxNSPNetworkType, ) from .exceptions import ( NetworkManagerAlreadyAsleepOrAwakeError, @@ -218,30 +231,43 @@ __all__ = ( # .enums - 'AccessPointCapabilities', - 'BluetoothCapabilities', - 'ConnectionFlags', - 'ConnectionState', - 'ConnectionStateFlags', - 'ConnectionStateReason', + 'ActivationStateFlags', + 'ActiveConnectionState', + 'ActiveConnectionStateReason', + 'BluetoothCapabilitiesFlags', + 'CheckpointCreateFlags', + 'CheckpointRollbackResult', + 'ConnectionMultiConnect', 'ConnectionType', 'ConnectivityState', - 'DeviceCapabilities', + 'DeviceCapabilitiesFlags', 'DeviceInterfaceFlags', 'DeviceMetered', + 'DeviceReapplyFlags', 'DeviceState', 'DeviceStateReason', 'DeviceType', 'IpTunnelMode', - 'ModemCapabilities', - 'NetworkManagerConnectivityState', + 'ModemCapabilitiesFlags', + 'MptcpFlags', + 'NetworkManagerCapabilitiesFlags', + 'NetworkManagerReloadFlags', 'NetworkManagerState', - 'SecretAgentCapabilities', + 'RadioFlags', + 'SecretAgentCapabilitiesFlags', + 'SecretAgentGetSecretsFlags', + 'SettingsAddConnection2Flags', + 'SettingsConnectionFlags', + 'SettingsUpdate2Flags', + 'VpnConnectionState', + 'VpnConnectionStateReason', 'VpnFailure', - 'VpnState', + 'VpnServiceState', + 'WifiAccessPointCapabilitiesFlags', + 'WifiAccessPointSecurityFlags', + 'WifiCapabilitiesFlags', 'WiFiOperationMode', - 'WirelessCapabilities', - 'WpaSecurityFlags', + 'WimaxNSPNetworkType', # .exceptions 'NetworkManagerAlreadyAsleepOrAwakeError', 'NetworkManagerAlreadyEnabledOrDisabledError', diff --git a/sdbus_async/networkmanager/enums.py b/sdbus_async/networkmanager/enums.py index e1b09bc..beb1788 100644 --- a/sdbus_async/networkmanager/enums.py +++ b/sdbus_async/networkmanager/enums.py @@ -17,755 +17,1347 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +"""Enums used by the NetworkManager. + +`Copied from NetworkManager documentation. +`_ +""" from __future__ import annotations from enum import Enum, IntEnum, IntFlag -class AccessPointCapabilities(IntFlag): - """Wi-Fi Access point capabilities +class NetworkManagerCapabilitiesFlags(IntFlag): + """NetworkManager loaded plugins. + + Capabilities are positive numbers. They are part of stable API and + a certain capability number is guaranteed not to change. - Flags: + The range 0x7000 - 0x7FFF of capabilities is guaranteed not to be + used by upstream NetworkManager. It could thus be used for + downstream extensions. - * NONE - * PRIVACY - * WPS - * WPS_BUTTON - * WPS_PIN + Since NetworkManager 1.6. """ - NONE = 0x0 - PRIVACY = 0x1 - WPS = 0x2 - WPS_BUTTON = 0x4 - WPS_PIN = 0x8 - - -class WpaSecurityFlags(IntFlag): - """WPA (WiFi protected Access) encryption and authentication types - - Flags: - - * NONE - * P2P_WEP40 - * P2P_WEP104 - * P2P_TKIP - * P2P_CCMP - * BROADCAST_WEP40 - * BROADCAST_WEP104 - * BROADCAST_TKIP - * BROADCAST_CCMP - * AUTH_PSK - * AUTH_802_1X - * AUTH_SAE - * AUTH_OWE - * AUTH_OWE_TM - * AUTH_EAP_SUITE_B + + TEAM = 0x1 + """Teams can be managed. This means the team device plugin is loaded.""" + OVS = 0x2 + """OpenVSwitch can be managed. This means the OVS device plugin is loaded. + + Since NetworkManager 1.24. """ - NONE = 0x0 - P2P_WEP40 = 0x1 - P2P_WEP104 = 0x2 - P2P_TKIP = 0x4 - P2P_CCMP = 0x8 - BROADCAST_WEP40 = 0x10 - BROADCAST_WEP104 = 0x20 - BROADCAST_TKIP = 0x40 - BROADCAST_CCMP = 0x80 - AUTH_PSK = 0x100 - AUTH_802_1X = 0x200 - AUTH_SAE = 0x400 - AUTH_OWE = 0x800 - AUTH_OWE_TM = 0x1000 - AUTH_EAP_SUITE_B = 0x2000 -class WiFiOperationMode(IntEnum): - """Operation mode of WiFi access point +class NetworkManagerState(IntEnum): + """Indicates the current overall networking state.""" - * UNKNOWN - * ADHOC - * INFRASTRUCTURE - * AP - * MESH - """ UNKNOWN = 0 - ADHOC = 1 - INFRASTRUCTURE = 2 - AP = 3 - MESH = 4 + """Networking state is unknown. + This indicates a daemon error that makes it unable to reasonably assess + the state. In such event the applications are expected to assume Internet + connectivity might be present and not disable controls that require + network access. The graphical shells may hide the network accessibility + indicator altogether since no meaningful status indication can be provided. + """ + ASLEEP = 10 + """Networking is not enabled, the system is being suspended or resumed + from suspend.""" + DISCONNECTED = 20 + """There is no active network connection. -class SecretAgentCapabilities(IntFlag): - """Secret agent capabilities + The graphical shell should indicate no network connectivity and + the applications should not attempt to access the network. + """ + DISCONNECTING = 30 + """Network connections are being cleaned up. - Flags: + The applications should tear down their network sessions. + """ + CONNECTING = 40 + """A network connection is being started. - * NONE - * VPN_HINTS + The graphical shell should indicate the network is being connected + while the applications should still make no attempts to connect + the network. """ - NONE = 0x0 - VPN_HINTS = 0x1 + CONNECTED_LOCAL = 50 + """There is only local IPv4 and/or IPv6 connectivity, but no default route + to access the Internet. + The graphical shell should indicate no network connectivity. + """ + CONNECTED_SITE = 60 + """There is only site-wide IPv4 and/or IPv6 connectivity. -class ConnectionState(IntEnum): - """State of the connection + This means a default route is available, but the Internet + connectivity check (see "Connectivity" property) did not succeed. + The graphical shell should indicate limited network connectivity. + """ + GLOBAL = 70 + """There is global IPv4 and/or IPv6 Internet connectivity. - * UNKNOWN - * ACTIVATING - * ACTIVATED - * DEACTIVATING - * DEACTIVATED + This means the Internet connectivity check succeeded, the graphical shell + should indicate full network connectivity. """ + + +class ConnectivityState(IntEnum): + """System connectivity state.""" + UNKNOWN = 0 - ACTIVATING = 1 - ACTIVATED = 2 - DEACTIVATING = 3 - DEACTIVATED = 4 + """Network connectivity is unknown. + + This means the connectivity checks are disabled (e.g. on server + installations) or has not run yet. The graphical shell should assume + the Internet connection might be available and not present a captive + portal window.""" + NONE = 1 + """The host is not connected to any network. + + There's no active connection that contains a default route to the internet + and thus it makes no sense to even attempt a connectivity check. + The graphical shell should use this state to indicate the network + connection is unavailable.""" + PORTAL = 2 + """The Internet connection is hijacked by a captive portal gateway. + + The graphical shell may open a sandboxed web browser window (because + the captive portals typically attempt a man-in-the-middle attacks against + the https connections) for the purpose of authenticating to a gateway and + retrigger the connectivity check with CheckConnectivity() when the browser + window is dismissed.""" + LIMITED = 3 + """The host is connected to a network, does not appear to be able to reach + the full Internet, but a captive portal has not been detected.""" + FULL = 4 + """The host is connected to a network, and appears to be able to reach the + full Internet.""" + + +class DeviceType(IntEnum): + """Indicate the type of hardware represented by a device object.""" + UNKNOWN = 0 + """Unknown device.""" + ETHERNET = 1 + """A wired ethernet device.""" + WIFI = 2 + """An 802.11 Wi-Fi device.""" + UNUSED1 = 3 + """Not used.""" + UNUSED2 = 4 + """Not used.""" + BLUETOOTH = 5 + """A Bluetooth device supporting PAN or DUN access protocols.""" + OLPC_MESH = 6 + """An OLPC XO mesh networking device.""" + WIMAX = 7 + """An 802.16e Mobile WiMAX broadband device.""" + MODEM = 8 + """A modem supporting analog telephone, CDMA/EVDO, GSM/UMTS, or + LTE network access protocols.""" + INFINIBAND = 9 + """An IP-over-InfiniBand device.""" + BOND = 10 + """A bond master interface.""" + VLAN = 11 + """An 802.1Q VLAN interface.""" + ADSL = 12 + """ADSL modem.""" + BRIDGE = 13 + """A bridge master interface.""" + GENERIC = 14 + """Generic support for unrecognized device types.""" + TEAM = 15 + """A team master interface.""" + TUN = 16 + """A TUN or TAP interface.""" + IP_TUNNEL = 17 + """A IP tunnel interface.""" + MACVLAN = 18 + """A MACVLAN interface.""" + VXLAN = 19 + """A VXLAN interface.""" + VETH = 20 + """A VETH interface.""" + MACSEC = 21 + """A MACsec interface.""" + DUMMY = 22 + """A dummy interface.""" + PPP = 23 + """A PPP interface.""" + OVS_INTERFACE = 24 + """A Open vSwitch interface.""" + OVS_PORT = 25 + """A Open vSwitch port.""" + OVS_BRIDGE = 26 + """A Open vSwitch bridge.""" + WPAN = 27 + """A IEEE 802.15.4 (WPAN) MAC Layer Device.""" + SIXLOWPAN = 28 + """6LoWPAN interfac.e""" + WIREGUARD = 29 + """A WireGuard interface.""" + WIFI_P2P = 30 + """An 802.11 Wi-Fi P2P device. -class ConnectionStateFlags(IntFlag): - """State of connection flags + Since NetworkManager 1.16. + """ + VRF = 31 + """A VRF (Virtual Routing and Forwarding) interface. - Flags: + Since NetworkManager 1.24. + """ + LOOPBACK = 32 + """A loopback interface. - * NONE - * IS_MASTER - * IS_SLAVE - * LAYER2_READY - * IP4_READY - * IP6_READY - * MASTER_HAS_SLAVES - * LIFE_TIME_BOUND_TO_PROFILE_VISIBILITY - * EXTERNAL + Since NetworkManager 1.42. """ - NONE = 0x0 - IS_MASTER = 0x1 - IS_SLAVE = 0x2 - LAYER2_READY = 0x4 - IP4_READY = 0x8 - IP6_READY = 0x10 - MASTER_HAS_SLAVES = 0x20 - LIFE_TIME_BOUND_TO_PROFILE_VISIBILITY = 0x40 - EXTERNAL = 0x80 -class ConnectionStateReason(IntEnum): - """Connection state change reason - - * UNKNOWN - * NONE - * USER_DISCONNECTED - * DEVICE_DISCONNECTED - * SERVICE_STOPPED - * IP_CONFIG_INVALID - * CONNECT_TIMEOUT - * SERVICE_START_TIMEOUT - * SERVICE_START_FAILED - * NO_SECRETS - * LOGIN_FAILED - * CONNECTION_REMOVED - * DEPENDENCY_FAILED - * DEVICE_REALIZE_FAILED - * DEVICE_REMOVED +class DeviceCapabilitiesFlags(IntFlag): + """General device capability flags.""" + + NONE = 0x00000000 + """Device has no special capabilities.""" + SUPPORTED = 0x00000001 + """NetworkManager supports this device.""" + CARRIER_DETECTABLE = 0x00000002 + """This device can indicate carrier status.""" + IS_SOFTWARE = 0x00000004 + """This device is a software device.""" + CAN_SRIOV = 0x00000008 + """This device supports single-root I/O virtualization.""" + + +class WifiCapabilitiesFlags(IntFlag): + """802.11 specific device encryption and authentication flags.""" + + NONE = 0x00000000 + """Device has no encryption/authentication capabilities.""" + CIPHER_WEP40 = 0x00000001 + """Device supports 40/64-bit WEP encryption.""" + CIPHER_WEP104 = 0x00000002 + """Device supports 104/128-bit WEP encryption.""" + CIPHER_TKIP = 0x00000004 + """Device supports TKIP encryption.""" + CIPHER_CCMP = 0x00000008 + """Device supports AES/CCMP encryption.""" + WPA = 0x00000010 + """Device supports WPA1 authentication.""" + WPA2 = 0x00000020 + """Device supports WPA2/RSN authentication.""" + AP = 0x00000040 + """Device supports Access Point mode.""" + ADHOC = 0x00000080 + """Device supports Ad-Hoc mode.""" + FREQ_VALID = 0x00000100 + """Device reports frequency capabilities.""" + FREQ_2GHZ = 0x00000200 + """Device supports 2.4GHz frequencies.""" + FREQ_5GHZ = 0x00000400 + """Device supports 5GHz frequencies.""" + MESH = 0x00001000 + """Device supports acting as a mesh point. + + Since NetworkManager 1.20. + """ + IBSS_WPA2 = 0x2000 + """Device supports WPA2/RSN in an IBSS network. + + Since NetworkManager 1.22. """ - UNKNOWN = 0 - NONE = 1 - USER_DISCONNECTED = 2 - DEVICE_DISCONNECTED = 3 - SERVICE_STOPPED = 4 - IP_CONFIG_INVALID = 5 - CONNECT_TIMEOUT = 6 - SERVICE_START_TIMEOUT = 7 - SERVICE_START_FAILED = 8 - NO_SECRETS = 9 - LOGIN_FAILED = 10 - CONNECTION_REMOVED = 11 - DEPENDENCY_FAILED = 12 - DEVICE_REALIZE_FAILED = 13 - DEVICE_REMOVED = 14 -class BluetoothCapabilities(IntFlag): - """Bluetooth Capabilities +class WifiAccessPointCapabilitiesFlags(IntFlag): + """802.11 access point flags.""" - Flags: + NONE = 0x00000000 + """Access point has no special capabilities.""" + PRIVACY = 0x00000001 + """Access point requires authentication and encryption. - * NONE - * DIAL_UP - * NETWORK_ACCESS_POINT + Usually means WEP. """ - NONE = 0x0 - DIAL_UP = 0x1 - NETWORK_ACCESS_POINT = 0x2 + WPS = 0x00000002 + """Access point supports some WPS method.""" + WPS_BUTTON = 0x00000004 + """Access point supports push-button WPS.""" + WPS_PIN = 0x00000008 + """Access point supports PIN-based WPS.""" -class IpTunnelMode(IntEnum): - """Mode of IP tunnel - - * UNKNOWN - * IP_IP - * GRE - * SIT - * ISATAP - * VTI - * IP6_IP6 - * IP_IP6 - * IP6_GRE - * VTI6 - * GRE_TAP - * IP6_GRE_TAP +class WifiAccessPointSecurityFlags(IntFlag): + """802.11 access point security and authentication flags. + + These flags describe the current security requirements of an access point + as determined from the access point's beacon. """ - UNKNOWN = 0 - IP_IP = 1 - GRE = 2 - SIT = 3 - ISATAP = 4 - VTI = 5 - IP6_IP6 = 6 - IP_IP6 = 7 - IP6_GRE = 8 - VTI6 = 9 - GRE_TAP = 10 - IP6_GRE_TAP = 11 + NONE = 0x00000000 + """The access point has no special security requirements.""" + PAIR_WEP40 = 0x00000001 + """40/64-bit WEP is supported for pairwise/unicast encryption.""" + PAIR_WEP104 = 0x00000002 + """104/128-bit WEP is supported for pairwise/unicast encryption.""" + PAIR_TKIP = 0x00000004 + """TKIP is supported for pairwise/unicast encryption.""" + PAIR_CCMP = 0x00000008 + """AES/CCMP is supported for pairwise/unicast encryption.""" + GROUP_WEP40 = 0x00000010 + """40/64-bit WEP is supported for group/broadcast encryption.""" + GROUP_WEP104 = 0x00000020 + """104/128-bit WEP is supported for group/broadcast encryption.""" + GROUP_TKIP = 0x00000040 + """TKIP is supported for group/broadcast encryption.""" + GROUP_CCMP = 0x00000080 + """AES/CCMP is supported for group/broadcast encryption.""" + KEY_MGMT_PSK = 0x00000100 + """WPA/RSN Pre-Shared Key encryption is supported.""" + KEY_MGMT_802_1X = 0x00000200 + """802.1x authentication and key management is supported.""" + KEY_MGMT_SAE = 0x00000400 + """WPA/RSN Simultaneous Authentication of Equals is supported.""" + KEY_MGMT_OWE = 0x00000800 + """WPA/RSN Opportunistic Wireless Encryption is supported.""" + KEY_MGMT_OWE_TM = 0x00001000 + """WPA/RSN Opportunistic Wireless Encryption transition mode is supported. + + Since NetworkManager 1.26. + """ + KEY_MGMT_EAP_SUITE_B_192 = 0x00002000 + """WPA3 Enterprise Suite-B 192 bit mode is supported. -class ModemCapabilities(IntFlag): - """Modem capabilities flags + Since NetworkManager 1.30. + """ - Flags: - * NONE - * ANALOG_WIRE - * CDMA - * GSM - * LTE +class WiFiOperationMode(IntEnum): + """Indicates the 802.11 mode an access point or device is currently in.""" + + UNKNOWN = 0 + """The device or access point mode is unknown.""" + ADHOC = 1 + """For both devices and access point objects, indicates the object is part + of an Ad-Hoc 802.11 network without a central coordinating access point.""" + INFRA = 2 + """The device or access point is in infrastructure mode. + + For devices, this indicates the device is an 802.11 client/station. For + access point objects, this indicates the object is an access point that + provides connectivity to clients. """ - NONE = 0x0 - ANALOG_WIRE = 0x1 - CDMA = 0x2 - GSM = 0x4 - LTE = 0x8 - - -class WirelessCapabilities(IntFlag): - """Wireless device capabilities flags - - Flags: - - * NONE - * CIPHER_WEP40 - * CIPHER_WEP104 - * CIPHER_TKIP - * CIPHER_CCMP - * WPA - * WPA2 - * AP - * ADHOC - * FREQ_VALID - * FREQ_2GHZ - * FREQ_5GHZ - * MESH - * IBSS_WPA2 + AP = 3 + """The device is an access point/hotspot. + + Not valid for access point objects; used only for hotspot mode on the + local machine. + """ + MESH = 4 + """The device is a 802.11s mesh point. + + Since NetworkManager 1.20. """ - NONE = 0x0 - CIPHER_WEP40 = 0x1 - CIPHER_WEP104 = 0x2 - CIPHER_TKIP = 0x4 - CIPHER_CCMP = 0x8 - WPA = 0x10 - WPA2 = 0x20 - AP = 0x40 - ADHOC = 0x80 - FREQ_VALID = 0x100 - FREQ_2GHZ = 0x200 - FREQ_5GHZ = 0x400 - MESH = 0x800 - IBSS_WPA2 = 0x2000 -class DeviceCapabilities(IntFlag): - """Device Capabilities +class BluetoothCapabilitiesFlags(IntFlag): + """Bluetooth device capabilities.""" - Flags: + NONE = 0x00000000 + """Device has no usable capabilities.""" + DUN = 0x00000001 + """Device provides Dial-Up Networking capability.""" + NAP = 0x00000002 + """Device provides Network Access Point capability.""" - * NONE - * SUPPORTED - * CARRIER_DETECTABLE - * IS_SOFTWARE - * CAN_SRIOV + +class ModemCapabilitiesFlags(IntFlag): + """Modem device capabilities. + + Indicates the generic radio access technology families a modem device + supports. For more information on the specific access technologies + the device supports use the ModemManager D-Bus API. """ - NONE = 0x0 - SUPPORTED = 0x1 - CARRIER_DETECTABLE = 0x2 - IS_SOFTWARE = 0x4 - CAN_SRIOV = 0x8 + + NONE = 0x00000000 + """Modem has no usable capabilities.""" + POTS = 0x00000001 + """Modem uses the analog wired telephone network and is not a + wireless/cellular device.""" + CDMA_EVDO = 0x00000002 + """Modem supports at least one of CDMA 1xRTT, EVDO revision 0, EVDO + revision A, or EVDO revision B.""" + GSM_UMTS = 0x00000004 + """Modem supports at least one of GSM, GPRS, EDGE, UMTS, HSDPA, HSUPA + or HSPA+ packet switched data capability.""" + LTE = 0x00000008 + """Modem has LTE data capability.""" + SGNR = 0x00000040 + """Modem has 5GNR data capability. + + Since NetworkManager 1.36. + """ + + +class WimaxNSPNetworkType(IntEnum): + """WiMAX network type.""" + + UNKNOWN = 0 + """Unknown network type.""" + HOME = 1 + """Home network.""" + PARTNER = 2 + """Partner network.""" + ROAMING_PARTNER = 3 + """Roaming partner network.""" class DeviceState(IntEnum): - """Device State - - * UNKNOWN - * UNMANAGED - * UNAVAILABLE - * DISCONNECTED - * PREPARE - * CONFIG - * NEED_AUTH - * IP_CONFIG - * IP_CHECK - * SECONDARIES - * ACTIVATED - * DEACTIVATING - * FAILED - """ + """Device's state.""" + UNKNOWN = 0 + """The device's state is unknown.""" UNMANAGED = 10 + """The device is recognized, but not managed by NetworkManager.""" UNAVAILABLE = 20 + """The device is managed by NetworkManager, but is not available for use. + + Reasons may include the wireless switched off, missing firmware, no + ethernet carrier, missing supplicant or modem manager, etc. + """ DISCONNECTED = 30 + """The device can be activated, but is currently idle and not connected + to a network.""" PREPARE = 40 + """The device is preparing the connection to the network. + + This may include operations like changing the MAC address, setting physical + link properties, and anything else required to connect to the requested + network. + """ CONFIG = 50 + """The device is connecting to the requested network. + + This may include operations like associating with the Wi-Fi AP, dialing + the modem, connecting to the remote Bluetooth device, etc. + """ NEED_AUTH = 60 + """The device requires more information to continue connecting to + the requested network. + + This includes secrets like WiFi passphrases, login passwords, PIN + codes, etc. + """ IP_CONFIG = 70 + """The device is requesting IPv4 and/or IPv6 addresses and routing + information from the network.""" IP_CHECK = 80 + """The device is checking whether further action is required for the + requested network connection. + + This may include checking whether only local network access is available, + whether a captive portal is blocking access to the Internet, etc. + """ SECONDARIES = 90 + """The device is waiting for a secondary connection (like a VPN) which + must activated before the device can be activated""" ACTIVATED = 100 + """The device has a network connection, either local or global.""" DEACTIVATING = 110 + """A disconnection from the current network connection was requested, + and the device is cleaning up resources used for that connection. + + The network connection may still be valid. + """ FAILED = 120 + """The device failed to connect to the requested network and + is cleaning up the connection request""" class DeviceStateReason(IntEnum): - """Device State reason - - * NONE - * UNKNOWN - * NOW_MANAGED - * NOW_UNMANAGED - * CONFIG_FAILED - * IP_CONFIG_UNAVAILABLE - * IP_CONFIG_EXPIRED - * NO_SECRETS - * SUPPLICANT_DISCONNECT - * SUPPLICANT_CONFIG_FAILED - * SUPPLICANT_FAILED - * SUPPLICANT_TIMEOUT - * PPP_START_FAILED - * PPP_DISCONNECT - * PPP_FAILED - * DHCP_START_FAILED - * DHCP_ERROR - * DHCP_FAILED - * SHARED_START_FAILED - * SHARED_FAILED - * AUTOIP_START_FAILED - * AUTOIP_ERROR - * AUTOIP_FAILED - * MODEM_BUSY - * MODEM_NO_DIAL_TONE - * MODEM_NO_CARRIER - * MODEM_DIAL_TIMEOUT - * MODEM_DIAL_FAILED - * MODEM_INIT_FAILED - * GSM_APN_FAILED - * GSM_REGISTRATION_NOT_SEARCHING - * GSM_REGISTRATION_DENIED - * GSM_REGISTRATION_TIMEOUT - * GSM_REGISTRATION_FAILED - * GSM_PIN_CHECK_FAILED - * FIRMWARE_MISSING - * REMOVED - * SLEEPING - * CONNECTION_REMOVED - * USER_REQUESTED - * CARRIER - * CONNECTION_ASSUMED - * SUPPLICANT_AVAILABLE - * MODEM_NOT_FOUND - * BT_FAILED - * GSM_SIM_NOT_INSERTED - * GSM_SIM_PIN_REQUIRED - * GSM_SIM_PUK_REQUIRED - * GSM_SIM_WRONG - * INFINIBAND_MODE - * DEPENDENCY_FAILED - * BR2684_FAILED - * MODEM_MANAGER_UNAVAILABLE - * SSID_NOT_FOUND - * SECONDARY_CONNECTION_FAILED - * DCB_FCOE_FAILED - * TEAMD_CONTROL_FAILED - * MODEM_FAILED - * MODEM_AVAILABLE - * SIM_PIN_INCORRECT - * NEW_ACTIVATION - * PARENT_CHANGED - * PARENT_MANAGED_CHANGED - * OVSDB_FAILED - * IP_ADDRESS_DUPLICATE - * IP_METHOD_UNSUPPORTED - * SRIOV_CONFIGURATION_FAILED - * PEER_NOT_FOUND - """ + """Device state change reason codes.""" + + NONE = 0 + """No reason given.""" UNKNOWN = 1 + """Unknown error.""" NOW_MANAGED = 2 + """Device is now managed.""" NOW_UNMANAGED = 3 + """Device is now unmanaged.""" CONFIG_FAILED = 4 + """The device could not be readied for configuration.""" IP_CONFIG_UNAVAILABLE = 5 + """IP configuration could not be reserved. + + No available address, timeout, etc... + """ IP_CONFIG_EXPIRED = 6 + """The IP config is no longer valid.""" NO_SECRETS = 7 + """Secrets were required, but not provided.""" SUPPLICANT_DISCONNECT = 8 + """802.1x supplicant disconnected.""" SUPPLICANT_CONFIG_FAILED = 9 + """802.1x supplicant configuration failed.""" SUPPLICANT_FAILED = 10 + """802.1x supplicant failed.""" SUPPLICANT_TIMEOUT = 11 + """802.1x supplicant took too long to authenticate.""" PPP_START_FAILED = 12 + """PPP service failed to start.""" PPP_DISCONNECT = 13 + """PPP service disconnected.""" PPP_FAILED = 14 + """PPP failed.""" DHCP_START_FAILED = 15 + """DHCP client failed to start.""" DHCP_ERROR = 16 + """DHCP client error.""" DHCP_FAILED = 17 + """DHCP client failed.""" SHARED_START_FAILED = 18 + """Shared connection service failed to start.""" SHARED_FAILED = 19 + """Shared connection service failed.""" AUTOIP_START_FAILED = 20 + """AutoIP service failed to start.""" AUTOIP_ERROR = 21 + """AutoIP service error.""" AUTOIP_FAILED = 22 + """AutoIP service failed.""" MODEM_BUSY = 23 + """The line is busy.""" MODEM_NO_DIAL_TONE = 24 + """No dial tone.""" MODEM_NO_CARRIER = 25 + """No carrier could be established.""" MODEM_DIAL_TIMEOUT = 26 + """The dialing request timed out.""" MODEM_DIAL_FAILED = 27 + """The dialing attempt failed.""" MODEM_INIT_FAILED = 28 + """Modem initialization failed.""" GSM_APN_FAILED = 29 + """Failed to select the specified APN.""" GSM_REGISTRATION_NOT_SEARCHING = 30 + """Not searching for networks.""" GSM_REGISTRATION_DENIED = 31 + """Network registration denied.""" GSM_REGISTRATION_TIMEOUT = 32 + """Network registration timed out.""" GSM_REGISTRATION_FAILED = 33 + """Failed to register with the requested network.""" GSM_PIN_CHECK_FAILED = 34 + """PIN check failed.""" FIRMWARE_MISSING = 35 + """Necessary firmware for the device may be missing.""" REMOVED = 36 + """The device was removed.""" SLEEPING = 37 + """NetworkManager went to sleep.""" CONNECTION_REMOVED = 38 + """The device's active connection disappeared.""" USER_REQUESTED = 39 + """Device disconnected by user or client.""" CARRIER = 40 + """Carrier/link changed.""" CONNECTION_ASSUMED = 41 + """The device's existing connection was assumed.""" SUPPLICANT_AVAILABLE = 42 + """The supplicant is now available.""" MODEM_NOT_FOUND = 43 + """The modem could not be found.""" BT_FAILED = 44 + """The Bluetooth connection failed or timed out.""" GSM_SIM_NOT_INSERTED = 45 + """GSM Modem's SIM Card not inserted.""" GSM_SIM_PIN_REQUIRED = 46 + """GSM Modem's SIM Pin required.""" GSM_SIM_PUK_REQUIRED = 47 + """GSM Modem's SIM Puk required.""" GSM_SIM_WRONG = 48 + """GSM Modem's SIM wrong.""" INFINIBAND_MODE = 49 + """InfiniBand device does not support connected mode.""" DEPENDENCY_FAILED = 50 + """A dependency of the connection failed.""" BR2684_FAILED = 51 + """Problem with the RFC 2684 Ethernet over ADSL bridge.""" MODEM_MANAGER_UNAVAILABLE = 52 + """ModemManager not running.""" SSID_NOT_FOUND = 53 + """The Wi-Fi network could not be found.""" SECONDARY_CONNECTION_FAILED = 54 + """A secondary connection of the base connection failed.""" DCB_FCOE_FAILED = 55 + """DCB or FCoE setup failed.""" TEAMD_CONTROL_FAILED = 56 + """teamd control failed.""" MODEM_FAILED = 57 + """Modem failed or no longer available.""" MODEM_AVAILABLE = 58 + """Modem now ready and available.""" SIM_PIN_INCORRECT = 59 + """SIM PIN was incorrect.""" NEW_ACTIVATION = 60 + """New connection activation was enqueued.""" PARENT_CHANGED = 61 + """The device's parent changed.""" PARENT_MANAGED_CHANGED = 62 + """The device parent's management changed.""" OVSDB_FAILED = 63 + """Problem communicating with Open vSwitch database.""" IP_ADDRESS_DUPLICATE = 64 + """A duplicate IP address was detected.""" IP_METHOD_UNSUPPORTED = 65 + """The selected IP method is not supported.""" SRIOV_CONFIGURATION_FAILED = 66 + """Configuration of SR-IOV parameters failed.""" PEER_NOT_FOUND = 67 + """The Wi-Fi P2P peer could not be found.""" -# Connection Types, e.g. from connecion_profile.connection.type: -# -# There is no central list of all connection types in NM. -# The best bet is to look for nm_connection_is_type() checks which use -# NM_SETTING_(TYPE)_SETTING_NAME #defines (which are fined used for -# settings for this connection-type. One connection_type can have several -# of such settings groups, so we have to filter those to get the strings: -# -# Generated from NetworkManager source using: -# grep -r nm_connection_is_type src/| -# sed -n 's/.*NM_SETTING_/NM_SETTING_/;s/_SETTING_NAME.*/=/p' | -# sort -u >.connection_is_type -# grep -hr define.*_SETTING_NAME src/| -# sed 's/#define //;s/_SETTING_NAME//;s/ /=/' >.setting_defines -# grep -f .connection_is_type .setting_defines | -# sed 's/NM_SETTING_/ /;s/6L/SIXL/;/GENERIC/d;s/=/ = /' -# Manual edit: This resulted in WIRED instead of ETHERNET, but -# ETHERNET is the name used for DeviceType, so use ETHERNET instead to -# be able to lookup connection profiles for Ethernet using DeviceType. -# -# One src/core/nm-device-*.c can support more than one ConnectionType, -# thus there are more ConnectionTypes than DeviceTypes: +class DeviceMetered(IntEnum): + """Device metered state. + + The NMMetered enum has two different purposes: one is to configure + "connection.metered" setting of a ConnectionSettings, and + the other is to express the actual metered state of the Device at a given + moment. + + For the connection profile only UNKNOWN, NO and YES are allowed. + + The device's metered state at runtime is determined by the profile which + is currently active. If the profile explicitly specifies NO or YES, then + the device's metered state is as such. If the connection profile leaves it + undecided at UNKNOWN (the default), then NetworkManager tries to guess + the metered state, for example based on the device type or on DHCP options + (like Android devices exposing a "ANDROID_METERED" DHCP vendor option). + This then leads to either GUESS_NO or GUESS_YES. + + Most applications probably should treat the runtime state + GUESS_YES like YES, and all other states as not metered. + + Note that the per-device metered states are then combined to a global + metered state. This is basically the metered state of the device with the + best default route. However, that generalization of a global metered state + may not be correct if the default routes for IPv4 and IPv6 are on different + devices, or if policy routing is configured. In general, the global metered + state tries to express whether the traffic is likely metered, but since + that depends on the traffic itself, there is not one answer in all cases. + Hence, an application may want to consider the per-device's metered states. + """ -# From NetworkManager-1.35: -class ConnectionType(str, Enum): - """Connection Types + UNKNOWN = 0 + """The metered status is unknown.""" + YES = 1 + """Metered, the value was explicitly configured.""" + NO = 2 + """Not metered, the value was explicitly configured.""" + GUESS_YES = 3 + """Metered, the value was guessed.""" + GUESS_NO = 4 + """Not metered, the value was guessed.""" - * ADSL - * BLUETOOTH - * BOND - * BRIDGE - * CDMA - * DUMMY - * ETHERNET - * MODEM - * INFINIBAND - * IP_TUNNEL - * MACSEC - * MACVLAN - * OLPC_MESH - * OVS_BRIDGE - * OVS_INTERFACE - * OVS_INTERFACE - * PPPOE - * SIXLOWPAN - * TEAM - * TUN - * VETH - * VLAN - * VPN - * VRF - * VXLAN - * WIFI_P2P - * WIREGUARD - * WIFI - * WPAN - * LOOPBACK - """ - ADSL = "adsl" - BLUETOOTH = "bluetooth" - BOND = "bond" - BRIDGE = "bridge" - CDMA = "cdma" - DUMMY = "dummy" - ETHERNET = "802-3-ethernet" - MODEM = "gsm" - INFINIBAND = "infiniband" - IP_TUNNEL = "ip-tunnel" - MACSEC = "macsec" - MACVLAN = "macvlan" - OLPC_MESH = "802-11-olpc-mesh" - OVS_BRIDGE = "ovs-bridge" - OVS_INTERFACE = "ovs-interface" - OVS_PORT = "ovs-port" - PPPOE = "pppoe" - SIXLOWPAN = "6lowpan" - TEAM = "team" - TUN = "tun" - VETH = "veth" - VLAN = "vlan" - VPN = "vpn" - VRF = "vrf" - VXLAN = "vxlan" - WIFI_P2P = "wifi-p2p" - WIREGUARD = "wireguard" - WIFI = "802-11-wireless" - WPAN = "wpan" - LOOPBACK = "loopback" +class ConnectionMultiConnect(IntEnum): + """Ability of a connection to be active on multiple devices. -class DeviceType(IntEnum): - """Device Type + Since NetworkManager 1.14. + """ - * UNKNOWN - * ETHERNET - * WIFI - * UNUSED1 - * UNUSED2 - * BLUETOOTH - * OLPC_MESH - * WIMAX - * MODEM - * INFINIBAND - * BOND - * VLAN - * ADSL - * BRIDGE - * GENERIC - * TEAM - * TUN - * IP_TUNNEL - * MACVLAN - * VXLAN - * VETH - * MACSEC - * DUMMY - * PPP - * OVS_INTERFACE - * OVS_PORT - * OVS_BRIDGE - * WPAN - * SIXLOWPAN - * WIREGUARD - * WIFI_P2P - * VRF - * LOOPBACK - """ - UNKNOWN = 0 - ETHERNET = 1 - WIFI = 2 - UNUSED1 = 3 - UNUSED2 = 4 - BLUETOOTH = 5 - OLPC_MESH = 6 - WIMAX = 7 - MODEM = 8 - INFINIBAND = 9 - BOND = 10 - VLAN = 11 - ADSL = 12 - BRIDGE = 13 - GENERIC = 14 - TEAM = 15 - TUN = 16 - IP_TUNNEL = 17 - MACVLAN = 18 - VXLAN = 19 - VETH = 20 - MACSEC = 21 - DUMMY = 22 - PPP = 23 - OVS_INTERFACE = 24 - OVS_PORT = 25 - OVS_BRIDGE = 26 - WPAN = 27 - SIXLOWPAN = 28 - WIREGUARD = 29 - WIFI_P2P = 30 - VRF = 31 - LOOPBACK = 32 + DEFAULT = 0 + """Indicates that the per-connection setting is unspecified. + In this case, it will fallback to the default value of SINGLE. + """ + SINGLE = 1 + """The connection profile can only be active once at each moment. -class DeviceMetered(IntEnum): - """Device Metered state + Activating a profile that is already active, will first deactivate it. + """ + MANUAL_MULTIPLE = 2 + """The profile can be manually activated multiple times + on different devices. - * UNKNOWN - * YES - * NO - * GUESS_YES - * GUESS_NO + However, regarding autoconnect, the profile will autoconnect only if it + is currently not connected otherwise. """ + MULTIPLE = 3 + """The profile can autoactivate and be manually activated multiple + times together.""" + + +class ActiveConnectionState(IntEnum): + """Indicates the state of an active connection to a specific network.""" + UNKNOWN = 0 - YES = 1 - NO = 2 - GUESS_YES = 3 - GUESS_NO = 4 + """The state of the connection is unknown.""" + ACTIVATING = 1 + """A network connection is being prepared.""" + ACTIVATED = 2 + """There is a connection to the network.""" + DEACTIVATING = 3 + """The network connection is being torn down and cleaned up.""" + DEACTIVATED = 4 + """The network connection is disconnected and will be removed.""" -class ConnectivityState(IntEnum): - """Connectivity state +class ActiveConnectionStateReason(IntEnum): + """Active connection state reasons. - * UNKNOWN - * NONE - * PORTAL - * LIMITED - * FULL + Since NetworkManager 1.8. """ + UNKNOWN = 0 + """The reason for the active connection state change is unknown.""" NONE = 1 - PORTAL = 2 - LIMITED = 3 - FULL = 4 + """No reason was given for the active connection state change.""" + USER_DISCONNECTED = 2 + """The active connection changed state because the user disconnected it.""" + DEVICE_DISCONNECTED = 3 + """The active connection changed state because the device it was using was + disconnected.""" + SERVICE_STOPPED = 4 + """The service providing the VPN connection was stopped.""" + IP_CONFIG_INVALID = 5 + """The IP config of the active connection was invalid.""" + CONNECT_TIMEOUT = 6 + """The connection attempt to the VPN service timed out.""" + SERVICE_START_TIMEOUT = 7 + """A timeout occurred while starting the service providing the + VPN connection.""" + SERVICE_START_FAILED = 8 + """Starting the service providing the VPN connection failed.""" + NO_SECRETS = 9 + """Necessary secrets for the connection were not provided.""" + LOGIN_FAILED = 10 + """Authentication to the server failed.""" + CONNECTION_REMOVED = 11 + """The connection was deleted from settings.""" + DEPENDENCY_FAILED = 12 + """Master connection of this connection failed to activate.""" + DEVICE_REALIZE_FAILED = 13 + """Could not create the software device link.""" + DEVICE_REMOVED = 14 + """The device this connection depended on disappeared.""" -class DeviceInterfaceFlags(IntFlag): - """Device network interface flags +class SecretAgentGetSecretsFlags(IntFlag): + """Flags to modify the behavior of a get_secrets request.""" - Flags: + NONE = 0x0 + """No special behavior. + + By default no user interaction is allowed and requests for secrets + are fulfilled from persistent storage, or if no secrets are available + an error is returned. + """ + ALLOW_INTERACTION = 0x1 + """Allows the request to interact with the user. + + Possibly prompting via UI for secrets if any are required, or if none + are found in persistent storage. + """ + REQUEST_NEW = 0x2 + """Explicitly prompt for new secrets from the user. - * NONE - * UP - * LOWER_UP - * CARRIER + This flag signals that NetworkManager thinks any existing secrets + are invalid or wrong. This flag implies that interaction is allowed. """ + FLAG_USER_REQUESTED = 0x4 + """Set if the request was initiated by user-requested action via the D-Bus + interface, as opposed to automatically initiated by NetworkManager + in response to (for example) scan results or carrier changes.""" + WPS_PBC_ACTIVE = 0x8 + """Indicates that WPS enrollment is active with PBC method. + + The agent may suggest that the user pushes a button on the router + instead of supplying a PSK. + """ + + +class SecretAgentCapabilitiesFlags(IntFlag): + """Secret agent capabilities.""" + NONE = 0x0 + """The agent supports no special capabilities.""" + VPN_HINTS = 0x1 + """The agent supports passing hints to VPN plugin + authentication dialogs.""" + + +class IpTunnelMode(IntEnum): + """Mode of IP tunnel. + + Since NetworkManager 1.2. + """ + + UNKNOWN = 0 + """Unknown/unset tunnel mode.""" + IP_IP = 1 + """IP in IP tunnel.""" + GRE = 2 + """GRE tunnel.""" + SIT = 3 + """SIT tunnel.""" + ISATAP = 4 + """ISATAP tunnel.""" + VTI = 5 + """VTI tunnel.""" + IP6_IP6 = 6 + """IPv6 in IPv6 tunnel.""" + IP_IP6 = 7 + """IPv4 in IPv6 tunnel.""" + IP6_GRE = 8 + """IPv6 GRE tunnel.""" + VTI6 = 9 + """IPv6 VTI tunnel.""" + GRE_TAP = 10 + """GRETAP tunnel.""" + IP6_GRE_TAP = 11 + """IPv6 GRETAP tunnel.""" + + +class CheckpointCreateFlags(IntFlag): + """Flags for checkpoint_create call. + + Since NetworkManager 1.4. + """ + + NONE = 0 + """No flags.""" + DESTROY_ALL = 0x01 + """When creating a new checkpoint, destroy all existing ones.""" + DELETE_NEW_CONNECTIONS = 0x02 + """Upon rollback, delete any new connection added after the checkpoint. + + Since NetworkManager 1.6. + """ + NEW_DEVICES = 0x04 + """Upon rollback, disconnect any new device appeared after the checkpoint. + + Since NetworkManager 1.6. + """ + ALLOW_OVERLAPPING = 0x08 + """By default, creating a checkpoint fails if there are already existing + checkpoints that reference the same devices. With this flag, creation of + such checkpoints is allowed, however, if an older checkpoint that + references overlapping devices gets rolled back, it will automatically + destroy this checkpoint during rollback. This allows to create several + overlapping checkpoints in parallel, and rollback to them at will. With + the special case that rolling back to an older checkpoint will invalidate + all overlapping younger checkpoints. This opts-in that the checkpoint can + be automatically destroyed by the rollback of an older checkpoint. + + Since NetworkManager 1.12. + """ + NO_PRESERVE_EXTERNAL_PORTS = 0x10 + """During rollback, by default externally added ports attached to + bridge devices are preserved. With this flag, the rollback detaches all + external ports. This only has an effect for bridge ports. Before + NetworkManager 1.38, this was the default behavior. + + Since NetworkManager 1.38. + """ + + +class CheckpointRollbackResult(IntEnum): + """The result of a checkpoint rollback for a specific device.""" + + OK = 0 + """The rollback succeeded.""" + ERR_NO_DEVICE = 1 + """The device no longer exists.""" + ERR_DEVICE_UNMANAGED = 2 + """The device is now unmanaged.""" + ERR_FAILED = 3 + """Other errors during rollback.""" + + +class SettingsConnectionFlags(IntFlag): + """Settings Connection flags.""" + + NONE = 0 + """An alias for numeric zero, no flags set.""" + UNSAVED = 0x01 + """The connection is not saved to disk. + + That either means, that the connection is in-memory only + and currently is not backed by a file. Or, that the connection is + backed by a file, but has modifications in-memory that were not + persisted to disk. + """ + NM_GENERATED = 0x02 + """A connection is "nm-generated" if it was generated by NetworkManger. + + If the connection gets modified or saved by the user, the flag + gets cleared. A nm-generated is also unsaved and has no backing + file as it is in-memory only. + """ + VOLATILE = 0x04 + """The connection will be deleted when it disconnects. + + That is for in-memory connections (unsaved), which are currently active + but deleted on disconnect. Volatile connections are always unsaved, but + they are also no backing file on disk and are entirely in-memory only. + """ + EXTERNAL = 0x08 + """The profile was generated to represent + an external configuration of a networking device. + + Since NetworkManager 1.26. + """ + + +class ActivationStateFlags(IntFlag): + """Flags describing the current activation state. + + Since NetworkManager 1.10. + """ + + NONE = 0 + """An alias for numeric zero, no flags set.""" + IS_MASTER = 0x1 + """The device is a master.""" + IS_SLAVE = 0x2 + """The device is a slave.""" + LAYER2_READY = 0x4 + """Layer2 is activated and ready.""" + IP4_READY = 0x8 + """IPv4 setting is completed.""" + IP6_READY = 0x10 + """IPv6 setting is completed.""" + MASTER_HAS_SLAVES = 0x20 + """The master has any slave devices attached. + + This only makes sense if the device is a master. + """ + LIFE_TIME_BOUND_TO_PROFILE_VISIBILITY = 0x40 + """The lifetime of the activation is bound to the visibility of + the connection profile, which in turn depends on "connection.permissions" + and whether a session for the user exists. + + Since NetworkManager 1.16. + """ + EXTERNAL = 0x80 + """The active connection was generated to represent an external + configuration of a networking device. + + Since NetworkManager 1.26. + """ + + +class SettingsAddConnection2Flags(IntFlag): + """Flags for the add_connection2() method.""" + + NONE = 0 + """An alias for numeric zero, no flags set.""" + TO_DISK = 0x1 + """To persist the connection to disk.""" + IN_MEMORY = 0x2 + """To make the connection in-memory only.""" + BLOCK_AUTOCONNECT = 0x20 + """Usually, when the connection has autoconnect enabled and gets added, + it becomes eligible to autoconnect right away. Setting this flag, disables + autoconnect until the connection is manually activated.""" + + +class SettingsUpdate2Flags(IntFlag): + """Flags for the update2() method.""" + + NONE = 0 + """An alias for numeric zero, no flags set.""" + TO_DISK = 0x1 + """To persist the connection to disk.""" + IN_MEMORY = 0x2 + """Makes the profile in-memory. + + Note that such profiles are stored in keyfile format under /run. + If the file is already in-memory, the file in /run is updated in-place. + Otherwise, the previous storage for the profile is left unchanged on disk, + and the in-memory copy shadows it. Note that the original filename of the + previous persistent storage (if any) is remembered. That means, when later + persisting the profile again to disk, the file on disk will be overwritten + again. Likewise, when finally deleting the profile, both the storage from + /run and persistent storage are deleted (or if the persistent storage does + not allow deletion, and nmmeta file is written to mark the UUID + as deleted). + """ + IN_MEMORY_DETACHED = 0x4 + """This is almost the same as IN_MEMORY, with one difference: + when later deleting the profile, the original profile will not + be deleted. Instead a nmmeta file is written to /run to indicate + that the profile is gone. Note that if such a nmmeta tombstone + file exists and hides a file in persistent storage, then when + re-adding the profile with the same UUID, then the original storage + is taken over again.""" + IN_MEMORY_ONLY = 0x8 + """This is like IN_MEMORY, but if the connection has a corresponding + file on persistent storage, the file will be deleted right away. + If the profile is later again persisted to disk, a new, unused + filename will be chosen.""" + VOLATILE = 0x10 + """This can be specified with either IN_MEMORY, IN_MEMORY_DETACHED or + IN_MEMORY_ONLY. After making the connection in-memory only, the connection + is marked as volatile. That means, if the connection is currently not + active it will be deleted right away. Otherwise, it is marked to for + deletion once the connection deactivates. A volatile connection cannot + autoactivate again (because it's about to be deleted), but a manual + activation will clear the volatile flag.""" + BLOCK_AUTOCONNECT = 0x20 + """Usually, when the connection has autoconnect enabled and is modified, + it becomes eligible to autoconnect right away. Setting this flag, disables + autoconnect until the connection is manually activated.""" + NO_REAPPLY = 0x40 + """When a profile gets modified that is currently active, then + these changes don't take effect for the active device unless the + profile gets reactivated or the configuration reapplied. There are two + exceptions: by default "connection.zone" and "connection.metered" + properties take effect immediately. Specify this flag to prevent these + properties to take effect, so that the change is restricted to modify + the profile. + + Since NetworkManager 1.20. + """ + + +class DeviceReapplyFlags(IntFlag): + """Flags for the reapply() methodof a device. + + Since NetworkManager 1.42. + """ + + NONE = 0 + """No flag set.""" + PRESERVE_EXTERNAL_IP = 0x1 + """During reapply, preserve external IP addresses and routes.""" + + +class NetworkManagerReloadFlags(IntFlag): + """Flags for the NetworkManager reload() call.""" + + NONE = 0 + """An alias for numeric zero, no flags set. This reloads everything + that is supported and is identical to a SIGHUP.""" + CONF = 0x1 + """Reload the NetworkManager.conf configuration from disk. + + Note that this does not include connections, which can be reloaded + via Setting's reload_connections(). + """ + DNS_RC = 0x2 + """Update DNS configuration, which usually involves + writing /etc/resolv.conf anew.""" + DNS_FULL = 0x4 + """Means to restart the DNS plugin. + + This is for example useful when using dnsmasq plugin, which uses additional + configuration in /etc/NetworkManager/dnsmasq.d. If you edit those files, + you can restart the DNS plugin. This action shortly interrupts name + resolution. + """ + ALL = 0x7 + """All flags.""" + + +class DeviceInterfaceFlags(IntFlag): + """Flags for a network interface. + + Since NetworkManager 1.22. + """ + + NONE = 0 + """An alias for numeric zero, no flags set.""" UP = 0x1 + """The interface is enabled from the administrative point of view. + + Corresponds to kernel IFF_UP. + """ LOWER_UP = 0x2 + """The physical link is up. + + Corresponds to kernel IFF_LOWER_UP. + """ + PROMISC = 0x4 + """Receive all packets. + + Corresponds to kernel IFF_PROMISC. + + Since NetworkManager 1.32. + """ CARRIER = 0x10000 + """The interface has carrier. + + In most cases this is equal to the value of LOWER_UP. + However some devices have a non-standard carrier detection mechanism. + """ + LLDP_CLIENT_ENABLED = 0x20000 + """The flag to indicate device LLDP status. + Since NetworkManager 1.32. + """ -class ConnectionFlags(IntFlag): - """Connection flags - Flags +class RadioFlags(IntFlag): + """Flags related to radio interfaces. - * NONE - * UNSAVED - * GENERATED - * VOLATILE - * EXTERNAL + Since NetworkManager 1.38. """ - NONE = 0x0 - UNSAVED = 0x1 - GENERATED = 0x2 - VOLATILE = 0x4 - EXTERNAL = 0x8 + NONE = 0 + """An alias for numeric zero, no flags set.""" + WLAN_AVAILABLE = 0x1 + """A Wireless LAN device or rfkill switch is detected in the system.""" + WWAN_AVAILABLE = 0x2 + """A Wireless WAN device or rfkill switch is detected in the system.""" + + +class MptcpFlags(IntFlag): + """Flags related to Multi-path TCP. -class VpnState(IntEnum): - """VPN State + Since NetworkManager 1.40. + """ - * UNKNOWN - * INIT - * SHUTDOWN - * STARTING - * STARTED - * STOPPING - * STOPPED + NONE = 0 + """The default, meaning that no MPTCP flags are set.""" + DISABLED = 0x1 + """Don't configure MPTCP endpoints on the device.""" + ENABLED = 0x2 + """MPTCP is enabled and endpoints will be configured. + + This flag is implied if any of the other flags indicate that MPTCP is + enabled and therefore in most cases unnecessary. Note that + if "/proc/sys/net/mptcp/enabled" sysctl is disabled, MPTCP handling is + disabled despite this flag. This can be overruled with the + "also-without-sysctl" flag. Note that by default interfaces that don't + have a default route are excluded from having MPTCP endpoints configured. + This can be overruled with the "also-without-default-route" and + this affects endpoints per address family. + """ + ALSO_WITHOUT_SYSCTL = 0x4 + """Even if MPTCP handling is enabled via the "enabled" flag, + it is ignored unless "/proc/sys/net/mptcp/enabled" is on. + With this flag, MPTCP endpoints will be configured regardless + of the sysctl setting.""" + ALSO_WITHOUT_DEFAULT_ROUTE = 0x8 + """Even if MPTCP handling is enabled via the "enabled" flag, + it is ignored per-address family unless NetworkManager + configures a default route. With this flag, NetworkManager will + also configure MPTCP endpoints if there is no default route. + This takes effect per-address family.""" + SIGNAL = 0x10 + """Flag for the MPTCP endpoint. + + The endpoint will be announced/signaled to each peer via an MPTCP + ADD_ADDR sub-option. + """ + SUBFLOW = 0x20 + """Flag for the MPTCP endpoint. + + If additional subflow creation is allowed by the MPTCP limits, the MPTCP + path manager will try to create an additional subflow using this endpoint + as the source address after the MPTCP connection is established. + """ + BACKUP = 0x40 + """Flag for the MPTCP endpoint. + + If this is a subflow endpoint, the subflows created using this endpoint + will have the backup flag set during the connection process. This flag + instructs the peer to only send data on a given subflow when all non-backup + subflows are unavailable. This does not affect outgoing data, where subflow + priority is determined by the backup/non-backup flag received from + the peer. """ + FULLMESH = 0x80 + """Flag for the MPTCP endpoint. + + If this is a subflow endpoint and additional subflow creation is + allowed by the MPTCP limits, the MPTCP path manager will try to create + an additional subflow for each known peer address, using this endpoint as + the source address. This will occur after the MPTCP connection is + established. If the peer did not announce any additional addresses using + the MPTCP ADD_ADDR sub-option, this will behave the same as a plain + subflow endpoint. When the peer does announce addresses, each received + ADD_ADDR sub-option will trigger creation of an additional subflow + to generate a full mesh topology. + """ + + +# From VPN plugin + +class VpnServiceState(IntEnum): + """VPN daemon states.""" + UNKNOWN = 0 + """The state of the VPN plugin is unknown.""" INIT = 1 + """The VPN plugin is initialized.""" SHUTDOWN = 2 + """Not used.""" STARTING = 3 + """The plugin is attempting to connect to a VPN server.""" STARTED = 4 + """The plugin has connected to a VPN server.""" STOPPING = 5 + """The plugin is disconnecting from the VPN server.""" STOPPED = 6 + """The plugin has disconnected from the VPN server.""" -class VpnFailure(IntEnum): - """VPN Failure +class VpnConnectionState(IntEnum): + """VPN connection states.""" - * LOGIN_FAILURE - * CONNECT_FAILED - * BAD_IP_CONFIG - """ - LOGIN_FAILURE = 0 - CONNECT_FAILED = 1 - BAD_IP_CONFIG = 3 - - -class NetworkManagerConnectivityState(IntEnum): - """NetworkManager connectivity state enum + UNKNOWN = 0 + """The state of the VPN connection is unknown.""" + PREPARE = 1 + """The VPN connection is preparing to connect.""" + NEED_AUTH = 2 + """The VPN connection needs authorization credentials.""" + CONNECT = 3 + """The VPN connection is being established.""" + IP_CONFIG_GET = 4 + """The VPN connection is getting an IP address.""" + ACTIVATED = 5 + """The VPN connection is active.""" + FAILED = 6 + """The VPN connection failed.""" + DISCONNECTED = 7 + """The VPN connection is disconnected.""" + + +class VpnConnectionStateReason(IntEnum): + """VPN connection state reasons.""" - * UNKNOWN - * NONE - * PORTAL - * LIMITED - * FULL - """ UNKNOWN = 0 + """The reason for the VPN connection state change is unknown.""" NONE = 1 - PORTAL = 2 - LIMITED = 3 - FULL = 4 + """No reason was given for the VPN connection state change.""" + USER_DISCONNECTED = 2 + """The VPN connection changed state because the user disconnected it.""" + DEVICE_DISCONNECTED = 3 + """The VPN connection changed state because the device it was using + was disconnected.""" + STOPPED = 4 + """The service providing the VPN connection was stopped.""" + IP_CONFIG_INVALID = 5 + """The IP config of the VPN connection was invalid.""" + CONNECT_TIMEOUT = 6 + """The connection attempt to the VPN service timed out.""" + SERVICE_START_TIMEOUT = 7 + """A timeout occurred while starting the service providing + the VPN connection.""" + SERVICE_START_FAILED = 8 + """Starting the service starting the service providing the VPN connection + failed.""" + NO_SECRETS = 9 + """Necessary secrets for the VPN connection were not provided.""" + LOGIN_FAILED = 10 + """Authentication to the VPN server failed.""" + CONNECTION_REMOVED = 11 + """The connection was deleted from settings.""" -class NetworkManagerState(IntEnum): - """NetworkManager state enum +class VpnFailure(IntEnum): + """VPN plugin failure reasons.""" + + LOGIN_FAILED = 0 + """Login failed.""" + CONNECT_FAILED = 1 + """Connect failed.""" + BAD_IP_CONFIG = 2 + """Invalid IP configuration returned from the VPN plugin.""" + +# Connection Types, e.g. from connecion_profile.connection.type: +# +# There is no central list of all connection types in NM. +# The best bet is to look for nm_connection_is_type() checks which use +# NM_SETTING_(TYPE)_SETTING_NAME #defines (which are fined used for +# settings for this connection-type. One connection_type can have several +# of such settings groups, so we have to filter those to get the strings: +# +# Generated from NetworkManager source using: +# grep -r nm_connection_is_type src/| +# sed -n 's/.*NM_SETTING_/NM_SETTING_/;s/_SETTING_NAME.*/=/p' | +# sort -u >.connection_is_type +# grep -hr define.*_SETTING_NAME src/| +# sed 's/#define //;s/_SETTING_NAME//;s/ /=/' >.setting_defines +# grep -f .connection_is_type .setting_defines | +# sed 's/NM_SETTING_/ /;s/6L/SIXL/;/GENERIC/d;s/=/ = /' +# Manual edit: This resulted in WIRED instead of ETHERNET, but +# ETHERNET is the name used for DeviceType, so use ETHERNET instead to +# be able to lookup connection profiles for Ethernet using DeviceType. +# +# One src/core/nm-device-*.c can support more than one ConnectionType, +# thus there are more ConnectionTypes than DeviceTypes: - * UNKNOWN - * ASLEEP - * DISCONNECTED - * DISCONNECTING - * CONNECTING - * CONNECTED_LOCAL - * CONNECTED_SITE - * GLOBAL + +# From NetworkManager-1.35: +class ConnectionType(str, Enum): + """Connection Types. + + * ADSL + * BLUETOOTH + * BOND + * BRIDGE + * CDMA + * DUMMY + * ETHERNET + * MODEM + * INFINIBAND + * IP_TUNNEL + * MACSEC + * MACVLAN + * OLPC_MESH + * OVS_BRIDGE + * OVS_INTERFACE + * OVS_INTERFACE + * PPPOE + * SIXLOWPAN + * TEAM + * TUN + * VETH + * VLAN + * VPN + * VRF + * VXLAN + * WIFI_P2P + * WIREGUARD + * WIFI + * WPAN + * LOOPBACK """ - UNKNOWN = 0 - ASLEEP = 10 - DISCONNECTED = 20 - DISCONNECTING = 30 - CONNECTING = 40 - CONNECTED_LOCAL = 50 - CONNECTED_SITE = 60 - GLOBAL = 70 + + ADSL = "adsl" + BLUETOOTH = "bluetooth" + BOND = "bond" + BRIDGE = "bridge" + CDMA = "cdma" + DUMMY = "dummy" + ETHERNET = "802-3-ethernet" + MODEM = "gsm" + INFINIBAND = "infiniband" + IP_TUNNEL = "ip-tunnel" + MACSEC = "macsec" + MACVLAN = "macvlan" + OLPC_MESH = "802-11-olpc-mesh" + OVS_BRIDGE = "ovs-bridge" + OVS_INTERFACE = "ovs-interface" + OVS_PORT = "ovs-port" + PPPOE = "pppoe" + SIXLOWPAN = "6lowpan" + TEAM = "team" + TUN = "tun" + VETH = "veth" + VLAN = "vlan" + VPN = "vpn" + VRF = "vrf" + VXLAN = "vxlan" + WIFI_P2P = "wifi-p2p" + WIREGUARD = "wireguard" + WIFI = "802-11-wireless" + WPAN = "wpan" + LOOPBACK = "loopback" diff --git a/sdbus_async/networkmanager/interfaces_devices.py b/sdbus_async/networkmanager/interfaces_devices.py index a111eea..e433e42 100644 --- a/sdbus_async/networkmanager/interfaces_devices.py +++ b/sdbus_async/networkmanager/interfaces_devices.py @@ -45,7 +45,8 @@ def name(self) -> str: def bt_capabilities(self) -> int: """Bluetooth device capabilities - See :py:class:`BluetoothCapabilities` + See :py:class:`BluetoothCapabilitiesFlags + `. """ raise NotImplementedError @@ -92,7 +93,8 @@ class NetworkManagerDeviceIPTunnelInterfaceAsync( def mode(self) -> int: """Tunnel mode - See :py:class:`IpTunnelMode` + See :py:class:`IpTunnelMode + `. """ raise NotImplementedError @@ -305,7 +307,8 @@ def modem_capabilities(self) -> int: Switching the radio technology might require firmware reboot. - See :py:class:`ModemCapabilities` + See :py:class:`ModemCapabilitiesFlags + `. """ raise NotImplementedError @@ -707,7 +710,8 @@ def perm_hw_address(self) -> str: def mode(self) -> int: """Operating mode of the device - See :py:class:`WiFiOperationMode` + See :py:class:`WiFiOperationMode + `. """ raise NotImplementedError @@ -730,7 +734,8 @@ def active_access_point(self) -> str: def wireless_capabilities(self) -> int: """List of wireless device capabilities - See :py:class:`WirelessCapabilities` + See :py:class:`WifiCapabilitiesFlags + `. """ raise NotImplementedError @@ -850,7 +855,8 @@ def firmware_version(self) -> str: def capabilities(self) -> int: """Capabilities of the device - See :py:class:`DeviceCapabilities` + See :py:class:`DeviceCapabilitiesFlags + `. """ raise NotImplementedError @@ -858,7 +864,8 @@ def capabilities(self) -> int: def state(self) -> int: """Device state. - See :py:class:`DeviceState` + See :py:class:`DeviceState + `. """ raise NotImplementedError @@ -866,7 +873,10 @@ def state(self) -> int: def state_reason(self) -> Tuple[int, int]: """Current state and the reason. - See :py:class:`DeviceState` and :py:class:`DeviceStateReason` + See :py:class:`DeviceState + ` and + :py:class:`DeviceStateReason + `. """ raise NotImplementedError @@ -938,7 +948,8 @@ def nm_plugin_missing(self) -> bool: def device_type(self) -> int: """Device type - See :py:class:`DeviceType` + See :py:class:`DeviceType + `. """ raise NotImplementedError @@ -961,7 +972,8 @@ def mtu(self) -> int: def metered(self) -> int: """Whether the traffic is subject to limitations - See :py:class:`DeviceMetered` + See :py:class:`DeviceMetered + `. """ raise NotImplementedError @@ -987,7 +999,8 @@ def real(self) -> bool: def ip4_connectivity(self) -> int: """IPv4 connectivity state - See :py:class:`ConnectivityState` + See :py:class:`ConnectivityState + `. """ raise NotImplementedError @@ -995,7 +1008,8 @@ def ip4_connectivity(self) -> int: def ip6_connectivity(self) -> int: """IPv6 connectivity state - See :py:class:`ConnectivityState` + See :py:class:`ConnectivityState + `. """ raise NotImplementedError @@ -1003,7 +1017,8 @@ def ip6_connectivity(self) -> int: def interface_flags(self) -> int: """Interface flags - See :py:class:`DeviceInterfaceFlags` + See :py:class:`DeviceInterfaceFlags + `. """ raise NotImplementedError @@ -1018,7 +1033,10 @@ def state_changed(self) -> Tuple[int, int, int]: Tuple of new state, old state and reason for new state. - See :py:class:`DeviceState` and :py:class:`DeviceStateReason` + See :py:class:`DeviceState + ` and + :py:class:`DeviceStateReason + `. """ raise NotImplementedError diff --git a/sdbus_async/networkmanager/interfaces_other.py b/sdbus_async/networkmanager/interfaces_other.py index 2a4c4af..2a38d67 100644 --- a/sdbus_async/networkmanager/interfaces_other.py +++ b/sdbus_async/networkmanager/interfaces_other.py @@ -41,7 +41,8 @@ class NetworkManagerAccessPointInterfaceAsync( def flags(self) -> int: """Flags describing capabilities of the point - See :py:class:`AccessPointCapabilities` + See :py:class:`WifiAccessPointCapabilitiesFlags + `. """ raise NotImplementedError @@ -49,7 +50,8 @@ def flags(self) -> int: def wpa_flags(self) -> int: """Flags WPA authentication and encryption - See :py:class:`WpaSecurityFlags` + See :py:class:`WifiAccessPointSecurityFlags + `. """ raise NotImplementedError @@ -57,7 +59,8 @@ def wpa_flags(self) -> int: def rsn_flags(self) -> int: """Flags describing RSN (Robust Secure Network) capabilities - See :py:class:`WpaSecurityFlags` + See :py:class:`WifiAccessPointSecurityFlags + `. """ raise NotImplementedError @@ -80,7 +83,8 @@ def hw_address(self) -> str: def mode(self) -> int: """Mode of operation of access point - See :py:class:`WiFiOperationMode` + See :py:class:`WiFiOperationMode + `. """ raise NotImplementedError @@ -127,7 +131,8 @@ async def register_with_capabilities( ) -> None: """Same as register() but with agent capabilities - See :py:class:`SecretAgentCapabilities` + See :py:class:`SecretAgentCapabilitiesFlags + `. """ raise NotImplementedError @@ -202,7 +207,8 @@ def devices(self) -> List[str]: def state(self) -> int: """Connection state - See :py:class:`ConnectionState` + See :py:class:`ActiveConnectionState + `. """ raise NotImplementedError @@ -210,7 +216,8 @@ def state(self) -> int: def state_flags(self) -> int: """Connection state flags - See :py:class:`ConnectionStateFlags` + See :py:class:`ActivationStateFlags + `. """ raise NotImplementedError @@ -270,7 +277,10 @@ def master(self) -> str: def state_changed(self) -> Tuple[int, int]: """Signal of the new state and the reason - See :py:class:`ConnectionState` and :py:class:`ConnectionStateReason` + See :py:class:`ActiveConnectionState + ` + and :py:class:`ActiveConnectionStateReason + `. """ raise NotImplementedError @@ -284,7 +294,8 @@ class NetworkManagerVPNConnectionInterfaceAsync( def vpn_state(self) -> int: """VPN connection state - See :py:class:`ConnectionState` + See :py:class:`VpnConnectionState + `. """ raise NotImplementedError @@ -298,7 +309,10 @@ def vpn_state_changed(self) -> Tuple[int, int]: """Signal when VPN state changed Tuple of new state and reason. - See :py:class:`ConnectionState` and :py:class:`ConnectionStateReason` + See :py:class:`VpnConnectionState + ` + and :py:class:`VpnConnectionStateReason + `. """ raise NotImplementedError @@ -595,7 +609,8 @@ def unsaved(self) -> bool: def flags(self) -> int: """Connection flags - See :py:class:`ConnectionFlags` + See :py:class:`SettingsConnectionFlags + `. """ raise NotImplementedError @@ -909,7 +924,8 @@ async def new_secrets( def state(self) -> int: """VPN state - See :py:class:`VpnState` + See :py:class:`VpnServiceState + `. """ raise NotImplementedError @@ -917,7 +933,8 @@ def state(self) -> int: def state_changed(self) -> int: """Signal when VPN state changed with new VPN state. - See :py:class:`VpnState` + See :py:class:`VpnServiceState + `. """ raise NotImplementedError @@ -950,7 +967,8 @@ def login_banner(self) -> str: def failure(self) -> int: """Signal when VPN failure occurs - See :py:class:`VpnFailure` + See :py:class:`VpnFailure + `. """ raise NotImplementedError @@ -969,7 +987,8 @@ def name(self) -> str: def flags(self) -> int: """Flags describing capabilities of the point - See :py:class:`AccessPointCapabilities` + See :py:class:`WifiAccessPointCapabilitiesFlags + `. """ raise NotImplementedError @@ -1024,14 +1043,13 @@ async def reload( ) -> None: """Reload NetworkManager configuration - Flags control what to reload: + Flags control what to reload. - * 0x0 everything - * 0x1 NetworkManager.conf - * 0x2 DNS configuration - * 0x4 Restart DNS plugin + Use :py:class:`NetworkManagerReloadFlags + ` + to create flags. - :param flags: Reload what? + :param flags: What to reload. """ raise NotImplementedError @@ -1166,7 +1184,8 @@ async def check_connectivity( ) -> int: """Get current connectivity state - See :py:class:`NetworkManagerConnectivityState` + See :py:class:`ConnectivityState + `. """ raise NotImplementedError @@ -1179,7 +1198,8 @@ async def get_state( ) -> int: """Get current NetworkManager state - See :py:class:`NetworkManagerState` + See :py:class:`NetworkManagerState + `. """ raise NotImplementedError @@ -1290,7 +1310,8 @@ def primary_connection_type(self) -> str: def metered(self) -> int: """Primary connection metered status - See :py:class:`DeviceMetered` + See :py:class:`DeviceMetered + `. """ raise NotImplementedError @@ -1322,7 +1343,8 @@ def capabilities(self) -> List[int]: def state(self) -> int: """Overall state of NetworkManager - See :py:class:`NetworkManagerState` + See :py:class:`NetworkManagerState + `. """ raise NotImplementedError @@ -1330,7 +1352,8 @@ def state(self) -> int: def connectivity(self) -> int: """Overall state of connectivity - See :py:class:`NetworkManagerConnectivityState` + See :py:class:`ConnectivityState + `. """ raise NotImplementedError @@ -1365,7 +1388,8 @@ def check_permissions(self) -> None: def state_changed(self) -> int: """NetworkManager state changed - See :py:class:`NetworkManagerState` + See :py:class:`NetworkManagerState + `. """ raise NotImplementedError diff --git a/sdbus_block/networkmanager/__init__.py b/sdbus_block/networkmanager/__init__.py index dbd8dcf..6b06a65 100644 --- a/sdbus_block/networkmanager/__init__.py +++ b/sdbus_block/networkmanager/__init__.py @@ -20,30 +20,43 @@ from __future__ import annotations from .enums import ( - AccessPointCapabilities, - BluetoothCapabilities, - ConnectionFlags, - ConnectionState, - ConnectionStateFlags, - ConnectionStateReason, + ActivationStateFlags, + ActiveConnectionState, + ActiveConnectionStateReason, + BluetoothCapabilitiesFlags, + CheckpointCreateFlags, + CheckpointRollbackResult, + ConnectionMultiConnect, ConnectionType, ConnectivityState, - DeviceCapabilities, + DeviceCapabilitiesFlags, DeviceInterfaceFlags, DeviceMetered, + DeviceReapplyFlags, DeviceState, DeviceStateReason, DeviceType, IpTunnelMode, - ModemCapabilities, - NetworkManagerConnectivityState, + ModemCapabilitiesFlags, + MptcpFlags, + NetworkManagerCapabilitiesFlags, + NetworkManagerReloadFlags, NetworkManagerState, - SecretAgentCapabilities, + RadioFlags, + SecretAgentCapabilitiesFlags, + SecretAgentGetSecretsFlags, + SettingsAddConnection2Flags, + SettingsConnectionFlags, + SettingsUpdate2Flags, + VpnConnectionState, + VpnConnectionStateReason, VpnFailure, - VpnState, + VpnServiceState, + WifiAccessPointCapabilitiesFlags, + WifiAccessPointSecurityFlags, + WifiCapabilitiesFlags, WiFiOperationMode, - WirelessCapabilities, - WpaSecurityFlags, + WimaxNSPNetworkType, ) from .exceptions import ( NetworkManagerAlreadyAsleepOrAwakeError, @@ -218,30 +231,43 @@ __all__ = ( # .enums - 'AccessPointCapabilities', - 'BluetoothCapabilities', - 'ConnectionFlags', - 'ConnectionState', - 'ConnectionStateFlags', - 'ConnectionStateReason', + 'ActivationStateFlags', + 'ActiveConnectionState', + 'ActiveConnectionStateReason', + 'BluetoothCapabilitiesFlags', + 'CheckpointCreateFlags', + 'CheckpointRollbackResult', + 'ConnectionMultiConnect', 'ConnectionType', 'ConnectivityState', - 'DeviceCapabilities', + 'DeviceCapabilitiesFlags', 'DeviceInterfaceFlags', 'DeviceMetered', + 'DeviceReapplyFlags', 'DeviceState', 'DeviceStateReason', 'DeviceType', 'IpTunnelMode', - 'ModemCapabilities', - 'NetworkManagerConnectivityState', + 'ModemCapabilitiesFlags', + 'MptcpFlags', + 'NetworkManagerCapabilitiesFlags', + 'NetworkManagerReloadFlags', 'NetworkManagerState', - 'SecretAgentCapabilities', + 'RadioFlags', + 'SecretAgentCapabilitiesFlags', + 'SecretAgentGetSecretsFlags', + 'SettingsAddConnection2Flags', + 'SettingsConnectionFlags', + 'SettingsUpdate2Flags', + 'VpnConnectionState', + 'VpnConnectionStateReason', 'VpnFailure', - 'VpnState', + 'VpnServiceState', + 'WifiAccessPointCapabilitiesFlags', + 'WifiAccessPointSecurityFlags', + 'WifiCapabilitiesFlags', 'WiFiOperationMode', - 'WirelessCapabilities', - 'WpaSecurityFlags', + 'WimaxNSPNetworkType', # .exceptions 'NetworkManagerAlreadyAsleepOrAwakeError', 'NetworkManagerAlreadyEnabledOrDisabledError', From 1c80ffb1d80c91c68d9dc487b62ee8c9d1f8c1ce Mon Sep 17 00:00:00 2001 From: igo95862 Date: Mon, 20 Nov 2023 00:37:27 +0600 Subject: [PATCH 03/16] Update readthedocs configuration to recent changes Copy pasted from the working sdbus configuration. --- .readthedocs.yml | 17 ++++++++++++++--- docs/conf.py | 3 ++- docs/requirements.txt | 4 ++++ requirements.txt | 1 - 4 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 docs/requirements.txt delete mode 100644 requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml index f5fcf5d..327f8d4 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,6 +1,17 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# Copyright (C) 2023 igo95862 +--- + version: 2 +build: + os: "ubuntu-22.04" + tools: + python: "3.9" + +sphinx: + configuration: "docs/conf.py" + python: - version: 3.8 - install: - - requirements: requirements.txt + install: + - requirements: docs/requirements.txt diff --git a/docs/conf.py b/docs/conf.py index 62ae238..7c1a777 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -# Copyright (C) 2020, 2021 igo95862 +# Copyright (C) 2020-2023 igo95862 # This file is part of python-sdbus @@ -24,6 +24,7 @@ author = 'igo95862' source_suffix = '.rst' extensions = ['sdbus.autodoc'] +html_theme = "sphinx_rtd_theme" autoclass_content = 'both' autodoc_typehints = 'description' diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..7c490fe --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# Copyright (C) 2023 igo95862 +sphinx_rtd_theme +sdbus>=0.10.2 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c64eda8..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -sdbus>=0.10.2 From de2cf89f027f09d6352163bc75854517fe57e59c Mon Sep 17 00:00:00 2001 From: igo95862 Date: Sat, 25 Nov 2023 14:53:37 +0600 Subject: [PATCH 04/16] docs: Fix enums example using outdated WifiCapabilities name The new name is WifiCapabilitiesFlags. Also add a link to the enums themselves. --- docs/enums.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/enums.rst b/docs/enums.rst index 90a8cc4..10a5d60 100644 --- a/docs/enums.rst +++ b/docs/enums.rst @@ -5,10 +5,11 @@ Python's Enum quick intro ------------------------- There are two types of enums. ``IntEnum`` is used for a discrete values -and ``IntFlag`` is used for bit flags. For example, ``DeviceType`` identifies -a single device type as a device cannot be of multiple types. -``WifiCapabilities`` shows a particular Wifi device capabilities which it -can have multiple, for example, supporting both 5GHz and 2.4GHz radio bands. +and ``IntFlag`` is used for bit flags. For example, :py:class:`DeviceType ` +identifies a single device type as a device cannot be of multiple types. +:py:class:`WifiCapabilitiesFlags ` +shows a particular Wifi device capabilities which it can have multiple, for example, supporting +both 5GHz and 2.4GHz radio bands. Usually ``IntEnum`` is implied unless the enum's name ends with ``Flag``. @@ -16,19 +17,19 @@ Example code using enums: .. code-block:: python - from sdbus_async.networkmanager.enums import DeviceType, WifiCapabilities + from sdbus_async.networkmanager.enums import DeviceType, WifiCapabilitiesFlags # Get particular device type from an integer DeviceType(2) == DeviceType.WIFI # Returns: True # Check if a specific flag is enabled - WifiCapabilities.FREQ_2GHZ in WifiCapabilities(0x00000400 | 0x00000200) + WifiCapabilitiesFlags.FREQ_2GHZ in WifiCapabilitiesFlags(0x00000400 | 0x00000200) # Returns: True # Iterate over all enabled flags - list(WifiCapabilities(0x00000400 | 0x00000200)) - # Returns: [, ] + list(WifiCapabilitiesFlags(0x00000400 | 0x00000200)) + # Returns: [, ] `See Python's standard library documentation for more detailed tutorial and API reference. `_ From 21a95dddead60ade1d6ed574f02de74cb5f7c735 Mon Sep 17 00:00:00 2001 From: igo95862 Date: Sat, 25 Nov 2023 17:03:26 +0600 Subject: [PATCH 05/16] docs: Create a quickstart guide It explains the basic objects used in the NetworkManager. --- docs/index.rst | 1 + docs/quickstart.rst | 100 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 docs/quickstart.rst diff --git a/docs/index.rst b/docs/index.rst index e7c9bfd..7fc3c5f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,6 +13,7 @@ of `NetworkManager `_. :maxdepth: 3 :caption: Contents: + quickstart objects examples device_interfaces diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..a145050 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,100 @@ +NetworkManager D-Bus API quickstart +=================================== + +This is a tutorial to understand the structure of the NetworkManager +D-Bus API and how to use it. + +.. note:: + + NetworkManager usually uses the system D-Bus, however, sdbus uses the session + D-Bus by default. It is recommend to call ``sdbus.set_default_bus(sdbus.sd_bus_open_system())`` + to set the system bus as default bus. + +NetworkManager main object +-------------------------- + +This is a static object that contains information about +entire state of NetworkManager. The Python class +:py:class:`NetworkManager ` +has a predefined service name and object path. + +.. code-block:: python + + import sdbus + from sdbus_block.networkmanager import NetworkManager + + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + network_manager = NetworkManager() + +Devices +------- + +Every device object represents a network device. Device object has a generic +methods and properties that are universal across all device types and +a type specific methods. The :py:class:`NetworkDeviceGeneric +` implements generic methods +and, for example, :py:class:`NetworkDeviceWireless ` +adds Wi-Fi specific methods. + +The :py:attr:`device_type ` +property and enum :py:class:`DeviceType ` +can be used to determine particular type of a device. + +.. code-block:: python + + from sdbus_block.networkmanager import NetworkDeviceGeneric, NetworkDeviceWireless + from sdbus_block.networkmanager.enums import DeviceType + + all_devices = {path: NetworkDeviceGeneric(path) for path in network_manager.devices} + + wifi_devices = [ + NetworkDeviceWireless(path) + for path, device in all_devices.items() + if device.device_type == DeviceType.WIFI + ] + +Connection +---------- + +Connection represents a configuration containing an IP address, Wifi password, +proxy settings and etc... The main object to access connections is the +:py:class:`NetworkManagerSettings ` +which has predefined object path. + +Each individual connection has a separate path which can be accessed with +:py:class:`NetworkConnectionSettings ` +class. + +.. code-block:: python + + from sdbus_block.networkmanager import NetworkManagerSettings + + networwork_manager_settings = NetworkManagerSettings() + + all_connections = [NetworkConnectionSettings(x) for x in networwork_manager_settings.connections] + +The actual connection settings are represented by a complex double nested dictionary +of D-Bus variants. For convenience a `dataclass `_ +based helper is provided. + +The :py:meth:`get_profile ` +and :py:meth:`update_profile ` +are two main methods to interact with connection settings helper. + +.. code-block:: python + + connection = all_connections[0] + setting_dataclass = connection.get_profile() + print("uuid:", profile.connection.uuid) + +Active Connection +----------------- + +:py:class:`ActiveConnection ` +is a product of a Connection being applied to a Device. + +For example, :py:meth:`activate_connection ` +of the main NetworkManager object will create new Active Connection +(therefore configuring network on a device) and return its path. +The :py:meth:`deactivate_connection ` +will remove the active connection and remove the device's network configuration. From 709b8d30809c858a242d764fa44ec9c3d495bdaf Mon Sep 17 00:00:00 2001 From: igo95862 Date: Sat, 25 Nov 2023 17:11:12 +0600 Subject: [PATCH 06/16] Added quickstart link to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0522dbb..56e02f9 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ See [python-sdbus requirements](https://github.com/igo95862/python-sdbus#require # [Documentation](https://python-sdbus-networkmanager.readthedocs.io/en/latest/) +See [this quickstart guide for brief introduction to NetworkManager D-Bus API](https://python-sdbus-networkmanager.readthedocs.io/en/latest/quickstart.html). + This is the sub-project of [python-sdbus](https://github.com/igo95862/python-sdbus). See the [python-sdbus documentation](https://python-sdbus.readthedocs.io/en/latest/). From 76b9141b3e4a5dc169ec8c27c63a91d9b09029b5 Mon Sep 17 00:00:00 2001 From: igo95862 Date: Mon, 12 Feb 2024 00:25:43 +0600 Subject: [PATCH 07/16] Separate examples by versions While the master branch undergoing version 3.0.0 development people will try to run the examples on PyPI version and fail. Separating them by version will hopefully make it very apparent which version the examples should be run against. --- .../async/add-eth-connection-async.py | 0 .../async/add-openvpn-connection-async.py | 0 .../async/add-wifi-psk-connection-async.py | 0 .../async/delete-connection-by-uuid-async.py | 0 examples/2.0.0/async/device-state-async.py | 74 ++++++++++ .../async/list-connections-async.py | 0 .../{ => 2.0.0}/async/netdevinfo-async.py | 0 .../async/update-connection-async.py | 0 .../{ => 2.0.0}/block/add-eth-connection.py | 0 .../block/add-wifi-psk-connection.py | 0 .../block/delete-connection-by-uuid.py | 0 examples/2.0.0/block/device-state.py | 74 ++++++++++ .../{ => 2.0.0}/block/list-connections.py | 0 examples/{ => 2.0.0}/block/netdevinfo.py | 0 .../{ => 2.0.0}/block/update-connection.py | 0 .../dev/async/add-eth-connection-async.py | 108 ++++++++++++++ .../dev/async/add-openvpn-connection-async.py | 135 +++++++++++++++++ .../async/add-wifi-psk-connection-async.py | 137 ++++++++++++++++++ .../async/delete-connection-by-uuid-async.py | 41 ++++++ .../{ => dev}/async/device-state-async.py | 0 examples/dev/async/list-connections-async.py | 61 ++++++++ examples/dev/async/netdevinfo-async.py | 119 +++++++++++++++ examples/dev/async/update-connection-async.py | 67 +++++++++ examples/dev/block/add-eth-connection.py | 107 ++++++++++++++ examples/dev/block/add-wifi-psk-connection.py | 124 ++++++++++++++++ .../dev/block/delete-connection-by-uuid.py | 40 +++++ examples/{ => dev}/block/device-state.py | 0 examples/dev/block/list-connections.py | 59 ++++++++ examples/dev/block/netdevinfo.py | 112 ++++++++++++++ examples/dev/block/update-connection.py | 48 ++++++ 30 files changed, 1306 insertions(+) rename examples/{ => 2.0.0}/async/add-eth-connection-async.py (100%) rename examples/{ => 2.0.0}/async/add-openvpn-connection-async.py (100%) rename examples/{ => 2.0.0}/async/add-wifi-psk-connection-async.py (100%) rename examples/{ => 2.0.0}/async/delete-connection-by-uuid-async.py (100%) create mode 100755 examples/2.0.0/async/device-state-async.py rename examples/{ => 2.0.0}/async/list-connections-async.py (100%) rename examples/{ => 2.0.0}/async/netdevinfo-async.py (100%) rename examples/{ => 2.0.0}/async/update-connection-async.py (100%) rename examples/{ => 2.0.0}/block/add-eth-connection.py (100%) rename examples/{ => 2.0.0}/block/add-wifi-psk-connection.py (100%) rename examples/{ => 2.0.0}/block/delete-connection-by-uuid.py (100%) create mode 100755 examples/2.0.0/block/device-state.py rename examples/{ => 2.0.0}/block/list-connections.py (100%) rename examples/{ => 2.0.0}/block/netdevinfo.py (100%) rename examples/{ => 2.0.0}/block/update-connection.py (100%) create mode 100755 examples/dev/async/add-eth-connection-async.py create mode 100755 examples/dev/async/add-openvpn-connection-async.py create mode 100755 examples/dev/async/add-wifi-psk-connection-async.py create mode 100755 examples/dev/async/delete-connection-by-uuid-async.py rename examples/{ => dev}/async/device-state-async.py (100%) create mode 100755 examples/dev/async/list-connections-async.py create mode 100755 examples/dev/async/netdevinfo-async.py create mode 100755 examples/dev/async/update-connection-async.py create mode 100755 examples/dev/block/add-eth-connection.py create mode 100755 examples/dev/block/add-wifi-psk-connection.py create mode 100755 examples/dev/block/delete-connection-by-uuid.py rename examples/{ => dev}/block/device-state.py (100%) create mode 100755 examples/dev/block/list-connections.py create mode 100755 examples/dev/block/netdevinfo.py create mode 100644 examples/dev/block/update-connection.py diff --git a/examples/async/add-eth-connection-async.py b/examples/2.0.0/async/add-eth-connection-async.py similarity index 100% rename from examples/async/add-eth-connection-async.py rename to examples/2.0.0/async/add-eth-connection-async.py diff --git a/examples/async/add-openvpn-connection-async.py b/examples/2.0.0/async/add-openvpn-connection-async.py similarity index 100% rename from examples/async/add-openvpn-connection-async.py rename to examples/2.0.0/async/add-openvpn-connection-async.py diff --git a/examples/async/add-wifi-psk-connection-async.py b/examples/2.0.0/async/add-wifi-psk-connection-async.py similarity index 100% rename from examples/async/add-wifi-psk-connection-async.py rename to examples/2.0.0/async/add-wifi-psk-connection-async.py diff --git a/examples/async/delete-connection-by-uuid-async.py b/examples/2.0.0/async/delete-connection-by-uuid-async.py similarity index 100% rename from examples/async/delete-connection-by-uuid-async.py rename to examples/2.0.0/async/delete-connection-by-uuid-async.py diff --git a/examples/2.0.0/async/device-state-async.py b/examples/2.0.0/async/device-state-async.py new file mode 100755 index 0000000..d55e36b --- /dev/null +++ b/examples/2.0.0/async/device-state-async.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to list the network devices including type, state, internet +# connectivitycheck state and the identifier of the active connection. +# +# NetworkDeviceGeneric/org.freedesktop.NetworkManager.Device is described at +# https://networkmanager.dev/docs/api/latest/ref-dbus-devices.html +# +# The output resembles the output of the NM CLI command "nmcli device": +# +# Interface Type State Internet Connection +# lo Generic Unmanaged Unknown +# wlp0s20f3 Wifi Activated Full Wolke7 [primary connection] +# docker0 Bridge Activated None docker0 +# enx0c3796090408 Ethernet Activated Full enx0c3796090408 +# p2p-dev-wlp0s20f3 Wifi_P2P Disconnected None + +import argparse +import asyncio +import sdbus +from sdbus_async.networkmanager import ( + NetworkManager, + NetworkDeviceGeneric, + DeviceState, + DeviceType, + DeviceCapabilities as Capabilities, + ActiveConnection, + ConnectivityState, +) +from enum import Enum + + +def title(enum: Enum) -> str: + """Get the name of an enum: 1st character is uppercase, rest lowercase""" + return enum.name.title() + + +async def list_active_hardware_networkdevice_states(only_hw: bool) -> None: + """Print the list of activated network devices similar to nmcli device""" + nm = NetworkManager() + devices_paths = await nm.get_devices() + + print("Interface Type State Internet Connection") + for device_path in devices_paths: + generic = NetworkDeviceGeneric(device_path) + + # Demonstrates an enum to match devices using capabilities: + if only_hw and await generic.capabilities & Capabilities.IS_SOFTWARE: + continue + + # Create the strings for the columns using the names of the enums: + dev: str = await generic.interface + dtype = title(DeviceType(await generic.device_type)) + state = title(DeviceState(await generic.state)) + connectivity = title(ConnectivityState(await generic.ip4_connectivity)) + + name: str = "" + if await generic.active_connection != "/": # Connection is active + # ActiveConnection() gets propertites from active connection path: + active_conn = ActiveConnection(await generic.active_connection) + name = await active_conn.id + if await active_conn.default: + name += " [primary connection]" + + print(f"{dev:<17} {dtype:<8} {state:<12} {connectivity:<8} {name:<14}") + + +if __name__ == "__main__": + p = argparse.ArgumentParser() + p.add_argument("--hw", action="store_true", dest="only_hw", help="Only HW") + args = p.parse_args() + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + asyncio.run(list_active_hardware_networkdevice_states(args.only_hw)) diff --git a/examples/async/list-connections-async.py b/examples/2.0.0/async/list-connections-async.py similarity index 100% rename from examples/async/list-connections-async.py rename to examples/2.0.0/async/list-connections-async.py diff --git a/examples/async/netdevinfo-async.py b/examples/2.0.0/async/netdevinfo-async.py similarity index 100% rename from examples/async/netdevinfo-async.py rename to examples/2.0.0/async/netdevinfo-async.py diff --git a/examples/async/update-connection-async.py b/examples/2.0.0/async/update-connection-async.py similarity index 100% rename from examples/async/update-connection-async.py rename to examples/2.0.0/async/update-connection-async.py diff --git a/examples/block/add-eth-connection.py b/examples/2.0.0/block/add-eth-connection.py similarity index 100% rename from examples/block/add-eth-connection.py rename to examples/2.0.0/block/add-eth-connection.py diff --git a/examples/block/add-wifi-psk-connection.py b/examples/2.0.0/block/add-wifi-psk-connection.py similarity index 100% rename from examples/block/add-wifi-psk-connection.py rename to examples/2.0.0/block/add-wifi-psk-connection.py diff --git a/examples/block/delete-connection-by-uuid.py b/examples/2.0.0/block/delete-connection-by-uuid.py similarity index 100% rename from examples/block/delete-connection-by-uuid.py rename to examples/2.0.0/block/delete-connection-by-uuid.py diff --git a/examples/2.0.0/block/device-state.py b/examples/2.0.0/block/device-state.py new file mode 100755 index 0000000..2070072 --- /dev/null +++ b/examples/2.0.0/block/device-state.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to list the network devices including type, state, internet +# connectivitycheck state and the identifier of the active connection. +# +# NetworkDeviceGeneric/org.freedesktop.NetworkManager.Device is described at +# https://networkmanager.dev/docs/api/latest/ref-dbus-devices.html +# +# The output resembles the output of the NM CLI command "nmcli device": +# +# Interface Type State Internet Connection +# lo Generic Unmanaged Unknown +# wlp0s20f3 Wifi Activated Full Wolke7 [primary connection] +# docker0 Bridge Activated None docker0 +# enx0c3796090408 Ethernet Activated Full enx0c3796090408 +# p2p-dev-wlp0s20f3 Wifi_P2P Disconnected None + +import argparse +import sdbus +from sdbus_block.networkmanager import ( + NetworkManager, + NetworkDeviceGeneric, + DeviceState, + DeviceType, + DeviceCapabilities as Capabilities, + ActiveConnection, + ConnectivityState, +) +from enum import Enum + + +def title(enum: Enum) -> str: + """Get the name of an enum: 1st character is uppercase, rest lowercase""" + return enum.name.title() + + +def list_active_hardware_networkdevice_states(only_hw: bool) -> None: + """Print the list of activated network devices similar to nmcli device""" + nm = NetworkManager() + devices_paths = nm.get_devices() + + print("Interface Type State Internet Connection") + for device_path in devices_paths: + generic_dev = NetworkDeviceGeneric(device_path) + + # Demonstrates an enum to match devices using capabilities: + if only_hw and generic_dev.capabilities & Capabilities.IS_SOFTWARE: + continue + + # Create the strings for the columns using the names of the enums: + dev = generic_dev.interface + type = title(DeviceType(generic_dev.device_type)) + state = title(DeviceState(generic_dev.state)) + connectivity = title(ConnectivityState(generic_dev.ip4_connectivity)) + + if generic_dev.active_connection == "/": # No active connection + id = "" + else: + # ActiveConnection() gets propertites from active connection path: + active_connection = ActiveConnection(generic_dev.active_connection) + id = active_connection.id + if active_connection.default: + id += " [primary connection]" + + print(f"{dev:<17} {type:<8} {state:<12} {connectivity:<8} {id:<14}") + + +if __name__ == "__main__": + p = argparse.ArgumentParser() + p.add_argument("--hw", action="store_true", dest="only_hw", help="Only HW") + args = p.parse_args() + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + list_active_hardware_networkdevice_states(args.only_hw) diff --git a/examples/block/list-connections.py b/examples/2.0.0/block/list-connections.py similarity index 100% rename from examples/block/list-connections.py rename to examples/2.0.0/block/list-connections.py diff --git a/examples/block/netdevinfo.py b/examples/2.0.0/block/netdevinfo.py similarity index 100% rename from examples/block/netdevinfo.py rename to examples/2.0.0/block/netdevinfo.py diff --git a/examples/block/update-connection.py b/examples/2.0.0/block/update-connection.py similarity index 100% rename from examples/block/update-connection.py rename to examples/2.0.0/block/update-connection.py diff --git a/examples/dev/async/add-eth-connection-async.py b/examples/dev/async/add-eth-connection-async.py new file mode 100755 index 0000000..f7be36f --- /dev/null +++ b/examples/dev/async/add-eth-connection-async.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to create a new Ethernet network connection profile: +# +# examples/block/add-eth-connection-async.py --help +# usage: add-eth-connection-async.py [-h] [-c CONN_ID] [-u UUID] [-4 IP4] +# [-i INTERFACE_NAME] [-g GW] [-a] [--save] +# +# Optional arguments have example values: +# +# optional arguments: +# -h, --help show this help message and exit +# -c CONN_ID Connection Id +# -u UUID Connection UUID +# -i INTERFACE_NAME ethX device +# -4 IP4 IP4/prefix +# -g GW gw/metric +# -a autoconnect +# --save Save +# +# Connection Profile settings are described at: +# https://networkmanager.dev/docs/api/latest/ref-settings.html + +import asyncio +import sdbus +import functools +import logging +import pprint +import sys +from uuid import uuid4 +from argparse import ArgumentParser, Namespace +from sdbus_async.networkmanager import NetworkManagerSettings +from sdbus_async.networkmanager import NetworkManagerConnectionProperties + + +async def add_ethernet_connection_async(args: Namespace) -> str: + """Add a (by default) temporary (not yet saved) network connection profile + :param Namespace args: autoconnect, conn_id, psk, save, ssid, uuid + :return: dbus connection path of the created connection profile + """ + info = logging.getLogger().info + + # If we add many connections using the same id, things get messy. Check: + if await NetworkManagerSettings().get_connections_by_id(args.conn_id): + info(f'Connections using ID "{args.conn_id}" exist, remove them:') + info(f'Run: nmcli connection delete "{args.conn_id}"') + return "" + + ipaddr, prefix = args.ip4.split("/") + properties: NetworkManagerConnectionProperties = { + "connection": { + "id": ("s", args.conn_id), + "uuid": ("s", str(args.uuid)), + "type": ("s", "802-3-ethernet"), + "autoconnect": ("b", args.auto), + }, + "ipv4": { + "method": ("s", "manual"), + "address-data": ( + "aa{sv}", + [ + { + "address": ("s", ipaddr), + "prefix": ("u", int(prefix)), + }, + ], + ), + }, + "ipv6": {"method": ("s", "disabled")}, + } + if args.interface_name: + properties["connection"]["interface-name"] = ("s", args.interface_name) + if len(sys.argv) == 1 or args.gw != "192.0.2.1/4000": + default_gateway, route_metric = args.gw.split("/") + properties["ipv4"]["gateway"] = ("s", default_gateway) + properties["ipv4"]["route-metric"] = ("u", int(route_metric)) + + s = NetworkManagerSettings() + addconnection = s.add_connection if args.save else s.add_connection_unsaved + connection_settings_dbus_path = await addconnection(properties) + created = "created and saved" if args.save else "created" + + info(f"New unsaved connection profile {created}, show it with:") + info(f'nmcli connection show "{args.conn_id}"|grep -v -e -- -e default') + info("Settings used:") + info(functools.partial(pprint.pformat, sort_dicts=False)(properties)) + return connection_settings_dbus_path + + +if __name__ == "__main__": + logging.basicConfig(format="%(message)s", level=logging.INFO) + p = ArgumentParser(description="Optional arguments have example values:") + conn_id = "MyConnectionExample" + p.add_argument("-c", dest="conn_id", default=conn_id, help="Connection Id") + p.add_argument("-u", dest="uuid", default=uuid4(), help="Connection UUID") + p.add_argument("-i", dest="interface_name", default="", help="ethX device") + p.add_argument("-4", dest="ip4", default="192.0.2.8/24", help="IP4/prefix") + p.add_argument("-g", dest="gw", default="192.0.2.1/4000", help="gw/metric") + p.add_argument("-a", dest="auto", action="store_true", help="autoconnect") + p.add_argument("--save", dest="save", action="store_true", help="Save") + args = p.parse_args() + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + if connection_dpath := asyncio.run(add_ethernet_connection_async(args)): + print(f"Path of the new connection: {connection_dpath}") + print(f"UUID of the new connection: {args.uuid}") + else: + print("Error: No new connection created.") diff --git a/examples/dev/async/add-openvpn-connection-async.py b/examples/dev/async/add-openvpn-connection-async.py new file mode 100755 index 0000000..4200ebd --- /dev/null +++ b/examples/dev/async/add-openvpn-connection-async.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to create a new VPN network connection profile. Currently supported only with tls-auth +# +# $ examples/async/add-openvpn-connection-async.py --help +# usage: add-openvpn-connection.py [-h] [-c CONN_ID] [-d DEV] [--remote REMOTE] [--remote-cert-tls] [-a] [--save] +# [--ca, CA_PATH] [--cert, CERT_PATH] [--key, KEY_path] [--ta, TA_PATH] +# +# Optional arguments have example values: +# +# optional arguments: +# -h, --help show this help message and exit +# -c CONN_ID Connection Id +# -u UUID Connection UUID +# -f OVPN .ovpn connection file +# -a autoconnect +# --save Save +# --ca Path to CA file +# --cert Path to cert file +# --key Path to key file +# --ta Path to tls-auth file +# +# $ add-vpn-connection.py +# New unsaved connection profile created, show it with: +# nmcli connection show "MyConnectionExample"|grep -v -e -- -e default +# +# Connection Profile settings are described at: +# https://networkmanager.dev/docs/api/latest/ref-settings.html +# +# Note: By default, it uses add_connection_unsaved() to add a temporary +# memory-only connection which is not saved to the system-connections folder: +# For reference, see: https://networkmanager.dev/docs/api/latest/spec.html +# -> org.freedesktop.NetworkManager.Settings (Settings Profile Manager) + +import asyncio +import functools +import logging +import sdbus +from uuid import uuid4 +from argparse import ArgumentParser +from pprint import pformat +from sdbus_async.networkmanager import ( + NetworkManagerSettings as SettingsManager, + ConnectionType, +) +from sdbus_async.networkmanager.settings import ( + ConnectionProfile, + ConnectionSettings, + Ipv4Settings, + Ipv6Settings, + VpnSettings +) + + +async def add_vpn_connection_async(conn_id: str, + dev: str, + remote: str, + remote_cert_tls: str, + uuid, + auto: bool, + save: bool, + ca: str, + cert: str, + key: str, + ta: str) -> str: + # Add a temporary (not yet saved) network connection profile + # param Namespace args: dev, remote, remote_cert_tls, ca_path, cert_path, key_path, ta_path + # return: dbus connection path of the created connection profile + + info = logging.getLogger().info + + # If we add many connections passing the same id, things get messy. Check: + if await SettingsManager().get_connections_by_id(conn_id): + print(f'Connection "{conn_id}" exists, remove it first') + print(f'Run: nmcli connection delete "{conn_id}"') + return "" + + profile = ConnectionProfile( + connection=ConnectionSettings( + connection_id=conn_id, + uuid=str(uuid), + connection_type=ConnectionType.VPN.value, + autoconnect=bool(auto), + ), + ipv4=Ipv4Settings(method="auto"), + ipv6=Ipv6Settings(method="auto"), + vpn=VpnSettings(data={ + 'ca': ca, + 'cert': cert, + 'cert-pass-flags': '0', + 'connection-type': 'tls', + 'dev': dev, + 'key': key, + 'remote': remote, + 'remote-cert-tls': remote_cert_tls, + 'ta': ta, + 'ta-dir': '1' + }, service_type='org.freedesktop.NetworkManager.openvpn') + ) + + s = SettingsManager() + save = bool(save) + addconnection = s.add_connection if save else s.add_connection_unsaved + connection_settings_dbus_path = await addconnection(profile.to_dbus()) + created = "created and saved" if save else "created" + info(f"New unsaved connection profile {created}, show it with:") + info(f'nmcli connection show "{conn_id}"|grep -v -e -- -e default') + info("Settings used:") + info(functools.partial(pformat, sort_dicts=False)(profile.to_settings_dict())) + return connection_settings_dbus_path + + +if __name__ == "__main__": + logging.basicConfig(format="%(message)s", level=logging.INFO) + p = ArgumentParser(description="Optional arguments have example values:") + conn_id = "MyConnectionExample" + p.add_argument("-c", dest="conn_id", default=conn_id, help="Connection Id") + p.add_argument("-u", dest="uuid", default=uuid4(), help="Connection UUID") + p.add_argument("--dev", dest="dev", default="tun", help="VPN Dev") + p.add_argument("--remote", dest="remote", default="example.com:443:tcp", help="VPN Remote") + p.add_argument("--remote-cert-tls", dest="remote_cert_tls", default="server", help="VPN Remote cert tls") + p.add_argument("--ca", dest="ca", required=True, help="VPN CA file path") + p.add_argument("--cert", dest="cert", required=True, help="VPN cert file path") + p.add_argument("--key", dest="key", required=True, help="VPN key file path") + p.add_argument("--ta", dest="ta", required=True, help="VPN TA file path") + p.add_argument("-a", dest="auto", action="store_true", help="autoconnect") + p.add_argument("--save", dest="save", action="store_true", help="Save") + args = p.parse_args() + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + if connection_dpath := asyncio.run(add_vpn_connection_async(**vars(args))): + print(f"Path of the new connection: {connection_dpath}") + print(f"UUID of the new connection: {args.uuid}") + else: + print("Error: No new connection created.") diff --git a/examples/dev/async/add-wifi-psk-connection-async.py b/examples/dev/async/add-wifi-psk-connection-async.py new file mode 100755 index 0000000..fb9022a --- /dev/null +++ b/examples/dev/async/add-wifi-psk-connection-async.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to create a new WiFi-PSK network connection profile: +# +# $ examples/async/add-wifi-psk-connection-async.py --help +# usage: add-wifi-psk-connection.py [-h] [-c CONN_ID] [-s SSID] [-p PSK] +# [-i INTERFACE_NAME] [-a] [--save] +# +# Optional arguments have example values: +# +# optional arguments: +# -h, --help show this help message and exit +# -c CONN_ID Connection Id +# -u UUID Connection UUID +# -s SSID WiFi SSID +# -p PSK WiFi PSK +# -i INTERFACE_NAME WiFi device +# -a autoconnect +# --save Save +# +# $ add-wifi-psk-connection.py +# New unsaved connection profile created, show it with: +# nmcli connection show "MyConnectionExample"|grep -v -e -- -e default +# Settings used: +# {'connection': {'id': ('s', 'MyConnectionExample'), +# 'uuid': ('s', '90e3bcc8-d3a5-4725-a363-abb788fd47bf'), +# 'type': ('s', '802-11-wireless'), +# 'autoconnect': ('b', False)}, +# '802-11-wireless': {'mode': ('s', 'infrastructure'), +# 'security': ('s', '802-11-wireless-security'), +# 'ssid': ('ay', b'CafeSSID')}, +# '802-11-wireless-security': {'key-mgmt': ('s', 'wpa-psk'), +# 'auth-alg': ('s', 'open'), +# 'psk': ('s', 'Coffee!!')}, +# 'ipv4': {'method': ('s', 'auto')}, +# 'ipv6': {'method': ('s', 'auto')}} +# +# Connection Profile settings are described at: +# https://networkmanager.dev/docs/api/latest/ref-settings.html +# +# Note: By default, it uses add_connection_unsaved() to add a temporary +# memory-only connection which is not saved to the system-connections folder: +# For reference, see: https://networkmanager.dev/docs/api/latest/spec.html +# -> org.freedesktop.NetworkManager.Settings (Settings Profile Manager) + +import asyncio +import binascii +import functools +import logging +import sdbus +from uuid import uuid4 +from argparse import ArgumentParser, Namespace +from passlib.utils.pbkdf2 import pbkdf2 # type: ignore +from pprint import pformat +from sdbus_async.networkmanager import ( + NetworkManagerSettings as SettingsManager, + ConnectionType, +) +from sdbus_async.networkmanager.settings import ( + ConnectionProfile, + ConnectionSettings, + Ipv4Settings, + Ipv6Settings, + WirelessSettings, + WirelessSecuritySettings, +) + + +async def add_wifi_psk_connection_async(args: Namespace) -> str: + """Add a temporary (not yet saved) network connection profile + :param Namespace args: autoconnect, conn_id, psk, save, ssid, uuid + :return: dbus connection path of the created connection profile + """ + info = logging.getLogger().info + + # If we add many connections passing the same id, things get messy. Check: + if await SettingsManager().get_connections_by_id(args.conn_id): + print(f'Connection "{args.conn_id}" exists, remove it first') + print(f'Run: nmcli connection delete "{args.conn_id}"') + return "" + + if args.key_mgmt == "wpa-psk" and len(args.password) < 64: + # Hash the password into a psk hash to not store it in clear form: + pw = pbkdf2(args.password.encode(), args.ssid.encode(), 4096, 32) + args.password = binascii.hexlify(pw).decode("utf-8") + + profile = ConnectionProfile( + connection=ConnectionSettings( + connection_id=args.conn_id, + uuid=str(args.uuid), + connection_type=ConnectionType.WIFI.value, + autoconnect=bool(hasattr(args, "auto") and args.auto), + ), + ipv4=Ipv4Settings(method="auto"), + ipv6=Ipv6Settings(method="auto"), + wireless=WirelessSettings(ssid=args.ssid.encode("utf-8")), + wireless_security=WirelessSecuritySettings( + key_mgmt=args.key_mgmt, auth_alg="open", psk=args.password + ), + ) + + # To bind the new connection to a specific interface, use this: + if hasattr(args, "interface_name") and args.interface_name: + profile.connection.interface_name = args.interface_name + + s = SettingsManager() + save = bool(hasattr(args, "save") and args.save) + addconnection = s.add_connection if save else s.add_connection_unsaved + connection_settings_dbus_path = await addconnection(profile.to_dbus()) + created = "created and saved" if save else "created" + info(f"New unsaved connection profile {created}, show it with:") + info(f'nmcli connection show "{args.conn_id}"|grep -v -e -- -e default') + info("Settings used:") + info(functools.partial(pformat, sort_dicts=False)(profile.to_settings_dict())) + return connection_settings_dbus_path + + +if __name__ == "__main__": + logging.basicConfig(format="%(message)s", level=logging.INFO) + p = ArgumentParser(description="Optional arguments have example values:") + conn_id = "MyConnectionExample" + p.add_argument("-c", dest="conn_id", default=conn_id, help="Connection Id") + p.add_argument("-u", dest="uuid", default=uuid4(), help="Connection UUID") + p.add_argument("-s", dest="ssid", default="CafeSSID", help="WiFi SSID") + p.add_argument("-k", dest="key_mgmt", default="wpa-psk", help="key-mgmt") + p.add_argument("-p", dest="password", default="Coffee!!", help="WiFi PSK") + p.add_argument("-i", dest="interface_name", default="", help="WiFi device") + p.add_argument("-a", dest="auto", action="store_true", help="autoconnect") + p.add_argument("--save", dest="save", action="store_true", help="Save") + args = p.parse_args() + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + if connection_dpath := asyncio.run(add_wifi_psk_connection_async(args)): + print(f"Path of the new connection: {connection_dpath}") + print(f"UUID of the new connection: {args.uuid}") + else: + print("Error: No new connection created.") diff --git a/examples/dev/async/delete-connection-by-uuid-async.py b/examples/dev/async/delete-connection-by-uuid-async.py new file mode 100755 index 0000000..ac1ee3d --- /dev/null +++ b/examples/dev/async/delete-connection-by-uuid-async.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Create and delete a connection profile using the unique connection uuid +# +import asyncio +import logging +import sdbus +from uuid import uuid4 +from argparse import Namespace +from sdbus_async.networkmanager import NetworkManagerSettings +from sdbus_async.networkmanager import NmSettingsInvalidConnectionError + + +async def delete_connection_by_uuid(uuid: str) -> bool: + """Find and delete the connection identified by the given UUID""" + try: + await NetworkManagerSettings().delete_connection_by_uuid(uuid) + except NmSettingsInvalidConnectionError: + logging.getLogger().fatal(f"Connection {uuid} for deletion not found") + return False + return True + + +async def create_and_delete_wifi_psk_connection_async(args: Namespace) -> bool: + """Add a temporary (not yet saved) network connection profile + :param Namespace args: autoconnect, conn_id, psk, save, ssid, uuid + :return: dbus connection path of the created connection profile + """ + add_wifi_psk_connection = __import__("add-wifi-psk-connection-async") + if not await add_wifi_psk_connection.add_wifi_psk_connection_async(args): + return False + return await delete_connection_by_uuid(str(args.uuid)) + + +if __name__ == "__main__": + logging.basicConfig(format="%(message)s", level=logging.WARNING) + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + args = Namespace(conn_id="Example", uuid=uuid4(), ssid="S", psk="Password") + if asyncio.run(create_and_delete_wifi_psk_connection_async(args)): + print(f"Succeeded in creating and deleting connection {args.uuid}") diff --git a/examples/async/device-state-async.py b/examples/dev/async/device-state-async.py similarity index 100% rename from examples/async/device-state-async.py rename to examples/dev/async/device-state-async.py diff --git a/examples/dev/async/list-connections-async.py b/examples/dev/async/list-connections-async.py new file mode 100755 index 0000000..cd39131 --- /dev/null +++ b/examples/dev/async/list-connections-async.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example which lists the details of NetworkManager's connection profiles. +# This is the asyncio variant of this example using sdbus_async.networkmanager. +# The blocking variant of this example is examples/block/list-connections.py +# +# Configuration settings are described at +# https://networkmanager.dev/docs/api/latest/ref-settings.html +# +# Example output: +# | name: Wired connection 1 +# | uuid: b2caabdc-98bb-3f88-8d28-d10369d6ded9 +# | type: 802-3-ethernet +# | interface-name: enx001e101f0000 +# | ipv4: method: manual +# | ipaddr: 192.168.178.34/24 +# | route-metric: 200 +# | ipv6: method: disabled +import asyncio +import sdbus +import pprint +from sdbus_async.networkmanager import ( + NetworkManagerSettings, + NetworkConnectionSettings, +) +from typing import List + + +async def list_connection_profiles_async() -> None: + networkmanager_settings = NetworkManagerSettings() + connections_paths: List[str] = await networkmanager_settings.connections + for dbus_connection_path in connections_paths: + await print_connection_profile(dbus_connection_path) + + +async def print_connection_profile(connection_path: str) -> None: + connectionsettings_service = NetworkConnectionSettings(connection_path) + profile = await connectionsettings_service.get_profile() + connection = profile.connection + print("-------------------------------------------------------------") + print("name:", connection.connection_id) + print("uuid:", connection.uuid) + print("type:", connection.connection_type) + if connection.interface_name: + print(" interface-name:", connection.interface_name) + if profile.ipv4: + print("ipv4: method:", profile.ipv4.method) + if profile.ipv4.address_data: + for address in profile.ipv4.address_data: + print(f' ipaddr: {address.address}/{address.prefix}') + if profile.ipv4.route_metric: + print(f' route-metric: {profile.ipv4.route_metric}') + if profile.ipv6: + print("ipv6: method:", profile.ipv6.method) + pprint.pprint(profile.to_settings_dict(), sort_dicts=False) + + +if __name__ == "__main__": + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + asyncio.run(list_connection_profiles_async()) diff --git a/examples/dev/async/netdevinfo-async.py b/examples/dev/async/netdevinfo-async.py new file mode 100755 index 0000000..e4ef140 --- /dev/null +++ b/examples/dev/async/netdevinfo-async.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to list the active IPv4 protocol configuration of network devices +# and the current status of WiFi adapters +# +# For IPv4 and org.freedesktop.NetworkManager.Device.Wireless see: +# https://networkmanager.dev/docs/api/latest/settings-ipv4.html +# https://networkmanager.dev/docs/api/latest/ref-dbus-devices.html +import asyncio +import sdbus +from sdbus_async.networkmanager import ( + ConnectionType, + NetworkConnectionSettings, + NetworkManager, + NetworkManagerSettings, + NetworkDeviceGeneric, + IPv4Config, + DeviceType, + NetworkDeviceWireless, + WiFiOperationMode, + AccessPoint, +) +from typing import Any, Dict, List, Optional, Tuple +NetworkManagerAddressData = List[Dict[str, Tuple[str, Any]]] + + +async def get_most_recent_connection_id(ifname: str, dev_type: str) -> Optional[str]: + """Return the most-recently used connection_id for this device + + Besides getting the currently active connection, this will succeed + in getting the most recent connection when a device is not connected + at the moment this function is executed. + + It uses getattr(ConnectionType, dev_type) to get the connection_type + used for connection_profiles for this DeviceType. + + With a slight modification, this could return the most recent connections + of the given device, ordered by the time of the last use of them. + """ + settings_service = NetworkManagerSettings() + connection_paths: List[str] = await settings_service.connections + conns = {} + for connection_path in connection_paths: + connection_manager = NetworkConnectionSettings(connection_path) + connection = (await connection_manager.get_profile()).connection + # Filter connection profiles matching the connection type for the device: + if connection.connection_type != getattr(ConnectionType, dev_type): + continue + # If the interface_name of a connection profiles is set, it must match: + if connection.interface_name and connection.interface_name != ifname: + continue + # If connection.timestamp is not set, it was never active. Set it to 0: + if not connection.timestamp: + connection.timestamp = 0 + # Record the connection_ids of the matches, and timestamp is the key: + conns[connection.timestamp] = connection.connection_id + if not len(conns): + return None + # Returns the connection_id of the highest timestamp which was found: + return conns.get(max(conns.keys())) + + +async def list_networkdevice_details_async() -> None: + nm = NetworkManager() + devices_paths = await nm.get_devices() + + for device_path in devices_paths: + generic_device = NetworkDeviceGeneric(device_path) + device_ip4_conf_path: str = await generic_device.ip4_config + if device_ip4_conf_path == "/": + continue + if not await generic_device.managed: + continue + dev_type = DeviceType(await generic_device.device_type).name + if dev_type == DeviceType.BRIDGE.name: + continue + + dev_name = await generic_device.interface + ip4_conf = IPv4Config(device_ip4_conf_path) + gateway: str = await ip4_conf.gateway + + print("Type: ", dev_type.title()) + print("Name: ", dev_name) + + if gateway: + print("Gateway:", gateway) + + address_data: NetworkManagerAddressData = await ip4_conf.address_data + for inetaddr in address_data: + print(f'Address: {inetaddr["address"][1]}/{inetaddr["prefix"][1]}') + + nameservers: NetworkManagerAddressData = await ip4_conf.nameserver_data + for dns in nameservers: + print("DNS: ", dns["address"][1]) + + if dev_type == DeviceType.WIFI.name: + wifi = NetworkDeviceWireless(device_path) + print("Wifi: ", WiFiOperationMode(await wifi.mode).name.title()) + ap_path = await wifi.active_access_point + if ap_path == "/": + print("No active access point") + else: + ap = AccessPoint(ap_path) + ssid: bytes = await ap.ssid + if ssid: + print("SSID: ", ssid.decode("utf-8", "ignore")) + if await ap.strength: + print("Signal: ", await ap.strength) + connection_id = await get_most_recent_connection_id(dev_name, dev_type) + if connection_id: + print("Profile:", connection_id) + + print("") + + +if __name__ == "__main__": + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + asyncio.run(list_networkdevice_details_async()) diff --git a/examples/dev/async/update-connection-async.py b/examples/dev/async/update-connection-async.py new file mode 100755 index 0000000..d838bda --- /dev/null +++ b/examples/dev/async/update-connection-async.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Update a property of a connection profile, looked up by connection id +# +# This version uses connection_manager.get_profile().to_settings_dict() +# to retrieve the connection profile from NetworkManager as a settings dict. +# +# It then updates it dynamically using the given arguments: +# The default is to set ipv4.dns-search to ["domain1.com", "domain2.com"]. +# +# The dynamically updated dict is then used to update connection profile of NM. +# +# The IPv4 settings of connections profiles are documented here: +# https://networkmanager.dev/docs/api/latest/settings-ipv4.html +# +import asyncio +import sdbus +from functools import partial +from sdbus_async.networkmanager import NetworkManagerSettings +from sdbus_async.networkmanager import NetworkConnectionSettings +from sdbus_async.networkmanager.settings import ConnectionProfile +from pprint import pprint +from typing import Any, Dict + + +async def update_connection_async(args: Dict[str, Any]) -> None: + """Update the settings for [key][entry] of the 1st matching connection""" + + # Get the connection path of the connection(s) with the received id + fn = NetworkManagerSettings().get_connections_by_id(args["connection_id"]) + connection_paths = await fn + if not connection_paths: + print(f"No connection {id}, create with add-wifi-psk-connection-async") + return + + # Get the profile settings of the first connection with given id + connection_manager = NetworkConnectionSettings(connection_paths[0]) + existing_connection_profile = await connection_manager.get_profile() + settings = existing_connection_profile.to_settings_dict() + + # Update the given setting's property using the given value + setting, property = args["connection_setting"] + settings[setting][property] = args["value"] + + # Get a new ConnectionProfile with the change incorporated + new_connection_profile = ConnectionProfile.from_settings_dict(settings) + + # Update the new ConnectionProfile in NetworkManager's configuration + await connection_manager.update(new_connection_profile.to_dbus()) + + print(f'Updated {new_connection_profile.connection.uuid}.{setting}:') + partial(pprint, sort_dicts=False)(settings) + + # Restore the previous connection profile: + await connection_manager.update(existing_connection_profile.to_dbus()) + + +if __name__ == "__main__": + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + args = { + # Set MyConnectionExample.ipv4.dns-search to "domain1.com,domain2.com": + "connection_id": "MyConnectionExample", + "connection_setting": ("ipv4", "dns-search"), + "value": ["domain1.com", "domain2.com"], + } + asyncio.run(update_connection_async(args)) diff --git a/examples/dev/block/add-eth-connection.py b/examples/dev/block/add-eth-connection.py new file mode 100755 index 0000000..b692890 --- /dev/null +++ b/examples/dev/block/add-eth-connection.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to create a new Ethernet network connection profile: +# +# examples/block/add-eth-connection.py --help +# usage: add-eth-connection.py [-h] [-c CONN_ID] [-u UUID] [-i INTERFACE_NAME] +# [-4 IP4] [-g GW] [-a] [--save] +# +# Optional arguments have example values: +# +# optional arguments: +# -h, --help show this help message and exit +# -c CONN_ID Connection Id +# -u UUID Connection UUID +# -i INTERFACE_NAME ethX device +# -4 IP4 IP4/prefix +# -g GW gw/metric +# -a autoconnect +# --save Save +# +# Connection Profile settings are described at: +# https://networkmanager.dev/docs/api/latest/ref-settings.html + +import sdbus +import functools +import logging +import pprint +import sys +from uuid import uuid4 +from argparse import ArgumentParser, Namespace +from sdbus_block.networkmanager import NetworkManagerSettings +from sdbus_block.networkmanager import NetworkManagerConnectionProperties + + +def add_ethernet_connection(args: Namespace) -> str: + """Add a (by default) temporary (not yet saved) network connection profile + :param Namespace args: autoconnect, conn_id, psk, save, ssid, uuid + :return: dbus connection path of the created connection profile + """ + info = logging.getLogger().info + + # If we add many connections using the same id, things get messy. Check: + if NetworkManagerSettings().get_connections_by_id(args.conn_id): + info(f'Connections using ID "{args.conn_id}" exist, remove them:') + info(f'Run: nmcli connection delete "{args.conn_id}"') + return "" + + ipaddr, prefix = args.ip4.split("/") + properties: NetworkManagerConnectionProperties = { + "connection": { + "id": ("s", args.conn_id), + "uuid": ("s", str(args.uuid)), + "type": ("s", "802-3-ethernet"), + "autoconnect": ("b", args.auto), + }, + "ipv4": { + "method": ("s", "manual"), + "address-data": ( + "aa{sv}", + [ + { + "address": ("s", ipaddr), + "prefix": ("u", int(prefix)), + }, + ], + ), + }, + "ipv6": {"method": ("s", "disabled")}, + } + if args.interface_name: + properties["connection"]["interface-name"] = ("s", args.interface_name) + if len(sys.argv) == 1 or args.gw != "192.0.2.1/4000": + default_gateway, route_metric = args.gw.split("/") + properties["ipv4"]["gateway"] = ("s", default_gateway) + properties["ipv4"]["route-metric"] = ("u", int(route_metric)) + + s = NetworkManagerSettings() + addconnection = s.add_connection if args.save else s.add_connection_unsaved + connection_settings_dbus_path = addconnection(properties) + created = "created and saved" if args.save else "created" + + info(f"New unsaved connection profile {created}, show it with:") + info(f'nmcli connection show "{args.conn_id}"|grep -v -e -- -e default') + info("Settings used:") + info(functools.partial(pprint.pformat, sort_dicts=False)(properties)) + return connection_settings_dbus_path + + +if __name__ == "__main__": + logging.basicConfig(format="%(message)s", level=logging.INFO) + p = ArgumentParser(description="Optional arguments have example values:") + conn_id = "MyConnectionExample" + p.add_argument("-c", dest="conn_id", default=conn_id, help="Connection Id") + p.add_argument("-u", dest="uuid", default=uuid4(), help="Connection UUID") + p.add_argument("-i", dest="interface_name", default="", help="ethX device") + p.add_argument("-4", dest="ip4", default="192.0.2.8/24", help="IP4/prefix") + p.add_argument("-g", dest="gw", default="192.0.2.1/4000", help="gw/metric") + p.add_argument("-a", dest="auto", action="store_true", help="autoconnect") + p.add_argument("--save", dest="save", action="store_true", help="Save") + args = p.parse_args() + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + if connection_dpath := add_ethernet_connection(args): + print(f"Path of the new connection: {connection_dpath}") + print(f"UUID of the new connection: {args.uuid}") + else: + print("Error: No new connection created.") diff --git a/examples/dev/block/add-wifi-psk-connection.py b/examples/dev/block/add-wifi-psk-connection.py new file mode 100755 index 0000000..3b732be --- /dev/null +++ b/examples/dev/block/add-wifi-psk-connection.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to create a new WiFi-PSK network connection profile: +# +# $ examples/block/add-wifi-psk-connection.py --help +# usage: add-wifi-psk-connection.py [-h] [-c CONN_ID] [-s SSID] [-p PSK] +# [-i INTERFACE_NAME] [-a] [--save] +# +# Optional arguments have example values: +# +# optional arguments: +# -h, --help show this help message and exit +# -c CONN_ID Connection Id +# -u UUID Connection UUID +# -s SSID WiFi SSID +# -p PSK WiFi PSK +# -i INTERFACE_NAME WiFi device +# -a autoconnect +# --save Save +# +# $ add-wifi-psk-connection.py +# New unsaved connection profile created, show it with: +# nmcli connection show "MyConnectionExample"|grep -v -e -- -e default +# Settings used: +# {'connection': {'id': ('s', 'MyConnectionExample'), +# 'uuid': ('s', '90e3bcc8-d3a5-4725-a363-abb788fd47bf'), +# 'type': ('s', '802-11-wireless'), +# 'autoconnect': ('b', False)}, +# '802-11-wireless': {'mode': ('s', 'infrastructure'), +# 'security': ('s', '802-11-wireless-security'), +# 'ssid': ('ay', b'CafeSSID')}, +# '802-11-wireless-security': {'key-mgmt': ('s', 'wpa-psk'), +# 'auth-alg': ('s', 'open'), +# 'psk': ('s', 'Coffee!!')}, +# 'ipv4': {'method': ('s', 'auto')}, +# 'ipv6': {'method': ('s', 'auto')}} +# +# Connection Profile settings are described at: +# https://networkmanager.dev/docs/api/latest/ref-settings.html +# +# Note: By default, it uses add_connection_unsaved() to add a temporary +# memory-only connection which is not saved to the system-connections folder: +# For reference, see: https://networkmanager.dev/docs/api/latest/spec.html +# -> org.freedesktop.NetworkManager.Settings (Settings Profile Manager) + +import functools +import logging +import pprint +import sdbus +from uuid import uuid4 +from argparse import ArgumentParser, Namespace +from sdbus_block.networkmanager import NetworkManagerSettings +from sdbus_block.networkmanager import NetworkManagerConnectionProperties + + +def add_wifi_psk_connection(args: Namespace) -> str: + """Add a temporary (not yet saved) network connection profile + :param Namespace args: autoconnect, conn_id, psk, save, ssid, uuid + :return: dbus connection path of the created connection profile + """ + info = logging.getLogger().info + + # If we add many connections passing the same id, things get messy. Check: + if NetworkManagerSettings().get_connections_by_id(args.conn_id): + print(f'Connection "{args.conn_id}" exists, remove it first') + print(f'Run: nmcli connection delete "{args.conn_id}"') + return "" + + properties: NetworkManagerConnectionProperties = { + "connection": { + "id": ("s", args.conn_id), + "uuid": ("s", str(args.uuid)), + "type": ("s", "802-11-wireless"), + "autoconnect": ("b", bool(hasattr(args, "auto") and args.auto)), + }, + "802-11-wireless": { + "mode": ("s", "infrastructure"), + "security": ("s", "802-11-wireless-security"), + "ssid": ("ay", args.ssid.encode("utf-8")), + }, + "802-11-wireless-security": { + "key-mgmt": ("s", "wpa-psk"), + "auth-alg": ("s", "open"), + "psk": ("s", args.psk), + }, + "ipv4": {"method": ("s", "auto")}, + "ipv6": {"method": ("s", "auto")}, + } + + # To bind the new connection to a specific interface, use this: + if hasattr(args, "interface_name") and args.interface_name: + properties["connection"]["interface-name"] = ("s", args.interface_name) + + s = NetworkManagerSettings() + save = bool(hasattr(args, "save") and args.save) + addconnection = s.add_connection if save else s.add_connection_unsaved + connection_settings_dbus_path = addconnection(properties) + created = "created and saved" if save else "created" + info(f"New unsaved connection profile {created}, show it with:") + info(f'nmcli connection show "{args.conn_id}"|grep -v -e -- -e default') + info("Settings used:") + info(functools.partial(pprint.pformat, sort_dicts=False)(properties)) + return connection_settings_dbus_path + + +if __name__ == "__main__": + logging.basicConfig(format="%(message)s", level=logging.INFO) + p = ArgumentParser(description="Optional arguments have example values:") + conn_id = "MyConnectionExample" + p.add_argument("-c", dest="conn_id", default=conn_id, help="Connection Id") + p.add_argument("-u", dest="uuid", default=uuid4(), help="Connection UUID") + p.add_argument("-s", dest="ssid", default="CafeSSID", help="WiFi SSID") + p.add_argument("-p", dest="psk", default="Coffee!!", help="WiFi PSK") + p.add_argument("-i", dest="interface_name", default="", help="WiFi device") + p.add_argument("-a", dest="auto", action="store_true", help="autoconnect") + p.add_argument("--save", dest="save", action="store_true", help="Save") + args = p.parse_args() + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + if connection_dpath := add_wifi_psk_connection(args): + print(f"Path of the new connection: {connection_dpath}") + print(f"UUID of the new connection: {args.uuid}") + else: + print("Error: No new connection created.") diff --git a/examples/dev/block/delete-connection-by-uuid.py b/examples/dev/block/delete-connection-by-uuid.py new file mode 100755 index 0000000..2b42509 --- /dev/null +++ b/examples/dev/block/delete-connection-by-uuid.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Create and delete a connection profile using the unique connection uuid +# +import logging +import sdbus +from uuid import uuid4 +from argparse import Namespace +from sdbus_block.networkmanager import NetworkManagerSettings +from sdbus_block.networkmanager import NmSettingsInvalidConnectionError + + +def delete_connection_by_uuid(uuid: str) -> bool: + """Find and delete the connection identified by the given UUID""" + try: + NetworkManagerSettings().delete_connection_by_uuid(uuid) + except NmSettingsInvalidConnectionError: + logging.getLogger().fatal(f"Connection {uuid} for deletion not found") + return False + return True + + +def create_and_delete_wifi_psk_connection_async(args: Namespace) -> bool: + """Add a temporary (not yet saved) network connection profile + :param Namespace args: autoconnect, conn_id, psk, save, ssid, uuid + :return: dbus connection path of the created connection profile + """ + add_wifi_psk_connection = __import__("add-wifi-psk-connection") + if not add_wifi_psk_connection.add_wifi_psk_connection(args): + return False + return delete_connection_by_uuid(str(args.uuid)) + + +if __name__ == "__main__": + logging.basicConfig(format="%(message)s", level=logging.WARNING) + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + args = Namespace(conn_id="Example", uuid=uuid4(), ssid="S", psk="Password") + if create_and_delete_wifi_psk_connection_async(args): + print(f"Succeeded in creating and deleting connection {args.uuid}") diff --git a/examples/block/device-state.py b/examples/dev/block/device-state.py similarity index 100% rename from examples/block/device-state.py rename to examples/dev/block/device-state.py diff --git a/examples/dev/block/list-connections.py b/examples/dev/block/list-connections.py new file mode 100755 index 0000000..f8d262a --- /dev/null +++ b/examples/dev/block/list-connections.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example which lists the details of NetworkManager's connection profiles. +# +# Configuration settings are described at +# https://networkmanager.dev/docs/api/latest/ref-settings.html +# +# Example output: +# | name: Wired connection 1 +# | uuid: b2caabdc-98bb-3f88-8d28-d10369d6ded9 +# | type: 802-3-ethernet +# | interface-name: enx001e101f0000 +# | ipv4: method: manual +# | ipaddr: 192.168.178.34/24 +# | route-metric: 200 +# | ipv6: method: disabled +import sdbus +from sdbus_block.networkmanager import ( + ConnectionType, + NetworkManagerSettings, + NetworkConnectionSettings, +) + + +def list_connection_profiles_blocking() -> None: + """Call print_connection_profile_blocking() for all connection profiles""" + networkmanager_settings = NetworkManagerSettings() + for dbus_connection_path in networkmanager_settings.connections: + print_connection_profile_blocking(dbus_connection_path) + + +def print_connection_profile_blocking(connection_path: str) -> None: + """Show the use of NetworkConnectionSettings(path).get_profile()""" + profile = NetworkConnectionSettings(connection_path).get_profile() + print("-------------------------------------------------------------") + print("name:", profile.connection.connection_id) + print("uuid:", profile.connection.uuid) + print("type:", profile.connection.connection_type) + if profile.connection.interface_name: + print(" interface-name:", profile.connection.interface_name) + if profile.ipv4: + print("ipv4: method:", profile.ipv4.method) + if profile.ipv4.address_data: + for address in profile.ipv4.address_data: + print(f' ipaddr: {address.address}/{address.prefix}') + if profile.ipv4.route_metric: + print(f' route-metric: {profile.ipv4.route_metric}') + if profile.ipv6: + print("ipv6: method:", profile.ipv6.method) + if profile.connection.connection_type == ConnectionType.WIFI: + assert profile.wireless + assert profile.wireless.ssid + print("ssid:", profile.wireless.ssid.decode()) + + +if __name__ == "__main__": + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + list_connection_profiles_blocking() diff --git a/examples/dev/block/netdevinfo.py b/examples/dev/block/netdevinfo.py new file mode 100755 index 0000000..1dec341 --- /dev/null +++ b/examples/dev/block/netdevinfo.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Example to list the active IPv4 protocol configuration of network devices +# and the current status of WiFi adapters +# +# For IPv4 and org.freedesktop.NetworkManager.Device.Wireless see: +# https://networkmanager.dev/docs/api/latest/settings-ipv4.html +# https://networkmanager.dev/docs/api/latest/ref-dbus-devices.html +import sdbus +from sdbus_block.networkmanager import ( + ConnectionType, + NetworkConnectionSettings, + NetworkManager, + NetworkManagerSettings, + NetworkDeviceGeneric, + IPv4Config, + DeviceType, + NetworkDeviceWireless, + WiFiOperationMode, + AccessPoint, +) +from typing import Any, Dict, List, Optional, Tuple +NetworkManagerAddressData = List[Dict[str, Tuple[str, Any]]] + + +def get_most_recent_connection_id(ifname: str, dev_type: str) -> Optional[str]: + """Return the most-recently used connection_id for this device + + Besides getting the currently active connection, this will succeed + in getting the most recent connection when a device is not connected + at the moment this function is executed. + + It uses getattr(ConnectionType, dev_type) to get the connection_type + used for connection_profiles for this DeviceType. + + With a slight modification, this could return the most recent connections + of the given device, ordered by the time of the last use of them. + """ + settings_service = NetworkManagerSettings() + connection_paths: List[str] = settings_service.connections + conns = {} + for connection_path in connection_paths: + connection_manager = NetworkConnectionSettings(connection_path) + connection = connection_manager.get_profile().connection + # Filter connection profiles matching the connection type for the device: + if connection.connection_type != getattr(ConnectionType, dev_type): + continue + # If the interface_name of a connection profiles is set, it must match: + if connection.interface_name and connection.interface_name != ifname: + continue + # If connection.timestamp is not set, it was never active. Set it to 0: + if not connection.timestamp: + connection.timestamp = 0 + # Record the connection_ids of the matches, and timestamp is the key: + conns[connection.timestamp] = connection.connection_id + if not len(conns): + return None + # Returns the connection_id of the highest timestamp which was found: + return conns.get(max(conns.keys())) + + +def list_networkdevice_details_blocking() -> None: + + for device_path in NetworkManager().get_devices(): + generic_device = NetworkDeviceGeneric(device_path) + device_ip4_conf_path: str = generic_device.ip4_config + if device_ip4_conf_path == "/": + continue + if not generic_device.managed: + continue + dev_type = DeviceType(generic_device.device_type).name + if dev_type == DeviceType.BRIDGE.name: + continue + + dev_name = generic_device.interface + ip4_conf = IPv4Config(device_ip4_conf_path) + gateway: str = ip4_conf.gateway + + print("Type: ", dev_type.title()) + print("Name: ", dev_name) + + if gateway: + print("Gateway:", gateway) + + address_data: NetworkManagerAddressData = ip4_conf.address_data + for inetaddr in address_data: + print(f'Address: {inetaddr["address"][1]}/{inetaddr["prefix"][1]}') + + nameservers: NetworkManagerAddressData = ip4_conf.nameserver_data + for dns in nameservers: + print("DNS: ", dns["address"][1]) + + if dev_type == DeviceType.WIFI.name: + wifi = NetworkDeviceWireless(device_path) + print("Wifi: ", WiFiOperationMode(wifi.mode).name.title()) + ap = AccessPoint(wifi.active_access_point) + ssid: bytes = ap.ssid + if ssid: + print("SSID: ", ssid.decode("utf-8", "ignore")) + if ap.strength: + print("Signal: ", ap.strength) + connection_id = get_most_recent_connection_id(dev_name, dev_type) + if connection_id: + print("Profile:", connection_id) + + print("") + + +if __name__ == "__main__": + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + list_networkdevice_details_blocking() diff --git a/examples/dev/block/update-connection.py b/examples/dev/block/update-connection.py new file mode 100644 index 0000000..10d86f1 --- /dev/null +++ b/examples/dev/block/update-connection.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Update a property of a connection profile, looked up by connection id +# +# The IPv4 settings of connections profiles are documented here: +# https://networkmanager.dev/docs/api/latest/settings-ipv4.html +# + +import sdbus +from functools import partial +from sdbus_block.networkmanager import NetworkManagerSettings +from sdbus_block.networkmanager import NetworkConnectionSettings +from pprint import pprint +from typing import Any, Dict + + +def update_connection(args: Dict[str, Any]) -> None: + """Update the settings for [key][entry] of the 1st matching connection""" + con = NetworkManagerSettings().get_connections_by_id(args["connection_id"]) + settings_domain, setting = args["connection_setting"] + if con: + connection_settings = NetworkConnectionSettings(con[0]) + properties = connection_settings.get_settings() + # For compatibility with old tools, NM adds and prefers them, delete: + properties["ipv4"].pop("addresses") # -> Use ["ipv4"]["address-data"] + properties["ipv4"].pop("routes") # ----> Use ["ipv4"]["route-data"] + + # Update the setting's value in the given configuration group: + properties[settings_domain][setting] = args["value"] + connection_settings.update(properties) + + print(f'Updated {properties["connection"]["uuid"]}.{settings_domain}:') + partial(pprint, sort_dicts=False)(properties[settings_domain]) + else: + print(f"No connection matching {id}") + + +if __name__ == "__main__": + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + args = { + # Set MyConnectionExample.ipv4.dns-search to "domain1.com,domain2.com": + "connection_id": "MyConnectionExample", + "connection_setting": ("ipv4", "dns-search"), + # "as" is the so-called DBus signature, it means "array of strings": + "value": ("as", ["domain1.com", "domain2.com"]), + } + update_connection(args) From 5666c1845b235ddc994b01548fa47259adbeebcc Mon Sep 17 00:00:00 2001 From: igo95862 Date: Mon, 18 Mar 2024 22:10:02 +0500 Subject: [PATCH 08/16] docs: Make all quickstart code snippets a valid scripts Otherwise it might be confusing on where the variables come from. --- docs/quickstart.rst | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index a145050..17d3880 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -42,9 +42,18 @@ can be used to determine particular type of a device. .. code-block:: python - from sdbus_block.networkmanager import NetworkDeviceGeneric, NetworkDeviceWireless + import sdbus + + from sdbus_block.networkmanager import ( + NetworkDeviceGeneric, + NetworkDeviceWireless, + NetworkManager, + ) from sdbus_block.networkmanager.enums import DeviceType + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + network_manager = NetworkManager() + all_devices = {path: NetworkDeviceGeneric(path) for path in network_manager.devices} wifi_devices = [ @@ -67,11 +76,22 @@ class. .. code-block:: python - from sdbus_block.networkmanager import NetworkManagerSettings + import sdbus + + from sdbus_block.networkmanager import ( + NetworkConnectionSettings, + NetworkManager, + NetworkManagerSettings, + ) + + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + network_manager = NetworkManager() networwork_manager_settings = NetworkManagerSettings() - all_connections = [NetworkConnectionSettings(x) for x in networwork_manager_settings.connections] + all_connections = [ + NetworkConnectionSettings(x) for x in networwork_manager_settings.connections + ] The actual connection settings are represented by a complex double nested dictionary of D-Bus variants. For convenience a `dataclass `_ @@ -83,9 +103,23 @@ are two main methods to interact with connection settings helper. .. code-block:: python - connection = all_connections[0] + import sdbus + + from sdbus_block.networkmanager import ( + NetworkConnectionSettings, + NetworkManager, + NetworkManagerSettings, + ) + + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + network_manager = NetworkManager() + + networwork_manager_settings = NetworkManagerSettings() + + + connection = NetworkConnectionSettings(networwork_manager_settings.connections[0]) setting_dataclass = connection.get_profile() - print("uuid:", profile.connection.uuid) + print("uuid:", setting_dataclass.connection.uuid) Active Connection ----------------- From 9edda490835d8e3b81b96d69097e8cfe1ea4f7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20M=C3=A9lotte?= Date: Thu, 17 Apr 2025 14:20:05 +0200 Subject: [PATCH 09/16] Update URL to github repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The github URL has changed but it was not updated at least in some places, so update it now. Signed-off-by: Raphaël Mélotte --- README.md | 4 ++-- docs/index.rst | 2 +- setup.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 56e02f9..111a641 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Implements most NetworkManager dbus interfaces and objects. * `python-sdbus` version higher than 0.8rc2 -See [python-sdbus requirements](https://github.com/igo95862/python-sdbus#requirements). +See [python-sdbus requirements](https://github.com/python-sdbus/python-sdbus#requirements). ## Installation @@ -19,6 +19,6 @@ See [python-sdbus requirements](https://github.com/igo95862/python-sdbus#require See [this quickstart guide for brief introduction to NetworkManager D-Bus API](https://python-sdbus-networkmanager.readthedocs.io/en/latest/quickstart.html). -This is the sub-project of [python-sdbus](https://github.com/igo95862/python-sdbus). +This is the sub-project of [python-sdbus](https://github.com/python-sdbus/python-sdbus). See the [python-sdbus documentation](https://python-sdbus.readthedocs.io/en/latest/). diff --git a/docs/index.rst b/docs/index.rst index 7fc3c5f..3003952 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,5 +22,5 @@ of `NetworkManager `_. enums exceptions -See `python-sdbus `_ homepage if you are +See `python-sdbus `_ homepage if you are unfamiliar with python-sdbus. diff --git a/setup.py b/setup.py index 85c82fe..0a29d26 100644 --- a/setup.py +++ b/setup.py @@ -30,15 +30,15 @@ long_description=long_description, long_description_content_type='text/markdown', version='2.0.0', - url='https://github.com/igo95862/python-sdbus', + url='https://github.com/python-sdbus/python-sdbus', author='igo95862', author_email='igo95862@yandex.ru', license='LGPL-2.1-or-later', keywords='dbus networkmanager networking linux freedesktop', project_urls={ 'Documentation': 'https://python-sdbus.readthedocs.io/en/latest/', - 'Source': 'https://github.com/igo95862/python-sdbus/', - 'Tracker': 'https://github.com/igo95862/python-sdbus/issues/', + 'Source': 'https://github.com/python-sdbus/python-sdbus/', + 'Tracker': 'https://github.com/python-sdbus/python-sdbus/issues/', }, classifiers=[ 'Development Status :: 4 - Beta', From a2230d8cd08c22312534390d337645f1790cadbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20M=C3=A9lotte?= Date: Thu, 17 Apr 2025 14:23:43 +0200 Subject: [PATCH 10/16] setup.py: fix URLs that are specific to python-sdbus-networkmanager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation URL in the README.md pointed to 'https://python-sdbus-networkmanager.readthedocs.io', but the one in setup.py pointed to python-sdbus instead. Fix the documentation URL, and while at it fix some others that had the same issue. Signed-off-by: Raphaël Mélotte --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 0a29d26..02c1185 100644 --- a/setup.py +++ b/setup.py @@ -30,15 +30,15 @@ long_description=long_description, long_description_content_type='text/markdown', version='2.0.0', - url='https://github.com/python-sdbus/python-sdbus', + url='https://github.com/python-sdbus/python-sdbus-networkmanager', author='igo95862', author_email='igo95862@yandex.ru', license='LGPL-2.1-or-later', keywords='dbus networkmanager networking linux freedesktop', project_urls={ - 'Documentation': 'https://python-sdbus.readthedocs.io/en/latest/', - 'Source': 'https://github.com/python-sdbus/python-sdbus/', - 'Tracker': 'https://github.com/python-sdbus/python-sdbus/issues/', + 'Documentation': 'https://python-sdbus-networkmanager.readthedocs.io/en/latest/', + 'Source': 'https://github.com/python-sdbus/python-sdbus-networkmanager/', + 'Tracker': 'https://github.com/python-sdbus/python-sdbus-networkmanager/issues/', }, classifiers=[ 'Development Status :: 4 - Beta', From 7fb2d1869b078ff94f5d9038e0039aac6893ab38 Mon Sep 17 00:00:00 2001 From: igo95862 Date: Sun, 20 Apr 2025 18:15:26 +0100 Subject: [PATCH 11/16] examples: Add example of listening on device state changes Apparently some people struggle with signals. This example should show them how to use signals. --- .../async/listen-device-changes-async.py | 52 +++++++++++++++++++ .../dev/async/listen-device-changes-async.py | 52 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 examples/2.0.0/async/listen-device-changes-async.py create mode 100644 examples/dev/async/listen-device-changes-async.py diff --git a/examples/2.0.0/async/listen-device-changes-async.py b/examples/2.0.0/async/listen-device-changes-async.py new file mode 100644 index 0000000..725010f --- /dev/null +++ b/examples/2.0.0/async/listen-device-changes-async.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later +# Copyright (C) 2025 igo95862 +# +# Example of listening to device state change signals. +# Also shows the use of enums. +from __future__ import annotations + +import asyncio +from argparse import ArgumentParser + +import sdbus + +from sdbus_async.networkmanager import NetworkDeviceGeneric, NetworkManager +from sdbus_async.networkmanager.enums import DeviceState, DeviceStateReason + + +async def listen_device(device_path: str, device_name: str) -> None: + generic_device = NetworkDeviceGeneric(device_path) + print(f"Listening state changes for device {device_name!r}") + + async for ( + new_state, + old_state, + reason, + ) in generic_device.state_changed.catch(): + print( + f"Now {DeviceState(new_state).name}, " + f"was {DeviceState(old_state).name}, " + f"reason {DeviceStateReason(reason).name}" + ) + + +async def main() -> None: + arg_parser = ArgumentParser() + arg_parser.add_argument("device_name") + + args = arg_parser.parse_args() + + network_manager = NetworkManager() + + device_name = args.device_name + device_path = await network_manager.get_device_by_ip_iface(device_name) + + # If you use create_task() make sure to keep a reference to the + # task or it will get garbage collected. + await listen_device(device_path, device_name) + + +if __name__ == "__main__": + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + asyncio.run(main()) diff --git a/examples/dev/async/listen-device-changes-async.py b/examples/dev/async/listen-device-changes-async.py new file mode 100644 index 0000000..725010f --- /dev/null +++ b/examples/dev/async/listen-device-changes-async.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later +# Copyright (C) 2025 igo95862 +# +# Example of listening to device state change signals. +# Also shows the use of enums. +from __future__ import annotations + +import asyncio +from argparse import ArgumentParser + +import sdbus + +from sdbus_async.networkmanager import NetworkDeviceGeneric, NetworkManager +from sdbus_async.networkmanager.enums import DeviceState, DeviceStateReason + + +async def listen_device(device_path: str, device_name: str) -> None: + generic_device = NetworkDeviceGeneric(device_path) + print(f"Listening state changes for device {device_name!r}") + + async for ( + new_state, + old_state, + reason, + ) in generic_device.state_changed.catch(): + print( + f"Now {DeviceState(new_state).name}, " + f"was {DeviceState(old_state).name}, " + f"reason {DeviceStateReason(reason).name}" + ) + + +async def main() -> None: + arg_parser = ArgumentParser() + arg_parser.add_argument("device_name") + + args = arg_parser.parse_args() + + network_manager = NetworkManager() + + device_name = args.device_name + device_path = await network_manager.get_device_by_ip_iface(device_name) + + # If you use create_task() make sure to keep a reference to the + # task or it will get garbage collected. + await listen_device(device_path, device_name) + + +if __name__ == "__main__": + sdbus.set_default_bus(sdbus.sd_bus_open_system()) + asyncio.run(main()) From ff0567ff233aaef7ac25943822a06cd8593474dd Mon Sep 17 00:00:00 2001 From: igo95862 Date: Sat, 2 Aug 2025 14:58:14 +0100 Subject: [PATCH 12/16] Update enums using latest NetworkManager documentation Add new `NetworkManagerVersionInfoCapability`, `ClientPermission` and `ClientPermissionResult` enums. Update `DeviceType`, `DeviceStateReason` and `DeviceMetered` with new values. Thank you @diggit for bringins this to my attention. Closes #74 --- sdbus_async/networkmanager/__init__.py | 6 + sdbus_async/networkmanager/enums.py | 159 ++++++++++++++++++++++++- sdbus_block/networkmanager/__init__.py | 6 + 3 files changed, 170 insertions(+), 1 deletion(-) diff --git a/sdbus_async/networkmanager/__init__.py b/sdbus_async/networkmanager/__init__.py index 81a390c..637346f 100644 --- a/sdbus_async/networkmanager/__init__.py +++ b/sdbus_async/networkmanager/__init__.py @@ -26,6 +26,8 @@ BluetoothCapabilitiesFlags, CheckpointCreateFlags, CheckpointRollbackResult, + ClientPermission, + ClientPermissionResult, ConnectionMultiConnect, ConnectionType, ConnectivityState, @@ -42,6 +44,7 @@ NetworkManagerCapabilitiesFlags, NetworkManagerReloadFlags, NetworkManagerState, + NetworkManagerVersionInfoCapability, RadioFlags, SecretAgentCapabilitiesFlags, SecretAgentGetSecretsFlags, @@ -237,6 +240,8 @@ 'BluetoothCapabilitiesFlags', 'CheckpointCreateFlags', 'CheckpointRollbackResult', + 'ClientPermission', + 'ClientPermissionResult', 'ConnectionMultiConnect', 'ConnectionType', 'ConnectivityState', @@ -253,6 +258,7 @@ 'NetworkManagerCapabilitiesFlags', 'NetworkManagerReloadFlags', 'NetworkManagerState', + 'NetworkManagerVersionInfoCapability', 'RadioFlags', 'SecretAgentCapabilitiesFlags', 'SecretAgentGetSecretsFlags', diff --git a/sdbus_async/networkmanager/enums.py b/sdbus_async/networkmanager/enums.py index beb1788..77ed1c4 100644 --- a/sdbus_async/networkmanager/enums.py +++ b/sdbus_async/networkmanager/enums.py @@ -27,6 +27,31 @@ from enum import Enum, IntEnum, IntFlag +class NetworkManagerVersionInfoCapability(IntEnum): + """The numeric values represent the bit index of the capability. + + These capabilities can be queried in the ``version_info`` D-Bus property. + + Since NetworkManager 1.42. + """ + + SYNC_ROUTE_WITH_TABLE = 0 + """Contains the fix to a bug that caused that routes in table other + than main were not removed on reapply nor on connection down. + """ + IP4_FORWARDING = 1 + """Indicates that NetworkManager supports configuring per-device IPv4 + sysctl forwarding setting. + + Since NetworkManager 1.54. + """ + SRIOV_PRESERVE_ON_DOWN = 2 + """NetworkManager supports the "sriov.preserve-on-down" property. + + Since NetworkManager 1.54. + """ + + class NetworkManagerCapabilitiesFlags(IntFlag): """NetworkManager loaded plugins. @@ -197,7 +222,7 @@ class DeviceType(IntEnum): WPAN = 27 """A IEEE 802.15.4 (WPAN) MAC Layer Device.""" SIXLOWPAN = 28 - """6LoWPAN interfac.e""" + """6LoWPAN interface.""" WIREGUARD = 29 """A WireGuard interface.""" WIFI_P2P = 30 @@ -215,6 +240,16 @@ class DeviceType(IntEnum): Since NetworkManager 1.42. """ + HSR = 33 + """A HSR/PRP device. + + Since NetworkManager 1.46. + """ + IPVLAN = 34 + """A IPVLAN device. + + Since NetworkManager 1.52. + """ class DeviceCapabilitiesFlags(IntFlag): @@ -618,6 +653,58 @@ class DeviceStateReason(IntEnum): """Configuration of SR-IOV parameters failed.""" PEER_NOT_FOUND = 67 """The Wi-Fi P2P peer could not be found.""" + DEVICE_HANDLER_FAILED = 68 + """The device handler dispatcher returned an error. + + Since NetworkManager 1.46. + """ + UNMANAGED_BY_DEFAULT = 69 + """The device is unmanaged because the device type is unmanaged by default. + + Since NetworkManager 1.48. + """ + UNMANAGED_EXTERNAL_DOWN = 70 + """The device is unmanaged because it is an external device and is + unconfigured (down or without addresses). + + Since NetworkManager 1.48. + """ + UNMANAGED_LINK_NOT_INIT = 71 + """The device is unmanaged because the link is not initialized by udev. + + Since NetworkManager 1.48. + """ + UNMANAGED_QUITTING = 72 + """The device is unmanaged because NetworkManager is quitting. + + Since NetworkManager 1.48. + """ + UNMANAGED_SLEEPING = 73 + """The device is unmanaged because networking is disabled or the system + is suspended. + + Since NetworkManager 1.48. + """ + UNMANAGED_USER_CONF = 74 + """The device is unmanaged by user decision in NetworkManager.conf. + + Since NetworkManager 1.48. + """ + UNMANAGED_USER_EXPLICIT = 75 + """The device is unmanaged by explicit user decision. + + Since NetworkManager 1.48. + """ + UNMANAGED_USER_SETTINGS = 76 + """The device is unmanaged by user decision via settings plugin. + + Since NetworkManager 1.48. + """ + UNMANAGED_USER_UDEV = 77 + """The device is unmanaged via udev rule. + + Since NetworkManager 1.48. + """ class DeviceMetered(IntEnum): @@ -863,6 +950,14 @@ class CheckpointCreateFlags(IntFlag): Since NetworkManager 1.38. """ + TRACK_INTERNAL_GLOBAL_DNS = 0x20 + """during rollback, by default changes to global DNS via D-BUS interface + are preserved. With this flag, the rollback reverts the global DNS changes + made via D-Bus interface. Global DNS defined in [global-dns] section of + NetworkManager.conf is not impacted by this flag. + + Since NetworkManager 1.48. + """ class CheckpointRollbackResult(IntEnum): @@ -1102,6 +1197,68 @@ class DeviceInterfaceFlags(IntFlag): """ +class ClientPermission(IntEnum): + """Permissions that NetworkManager clients can obtain.""" + + NONE = 0 + """Unknown or no permission.""" + ENABLE_DISABLE_NETWORK = 1 + """Controls whether networking can be globally enabled or disabled.""" + DISABLE_WIFI = 2 + """Controls whether Wi-Fi can be globally enabled or disabled.""" + DISABLE_WWAN = 3 + """Controls whether WWAN (3G) can be globally enabled or disabled.""" + DISABLE_WIMAX = 4 + """Controls whether WiMAX can be globally enabled or disabled.""" + SLEEP_WAKE = 5 + """Controls whether the client can ask NetworkManager to sleep and wake.""" + NETWORK_CONTROL = 6 + """Controls whether networking connections can be started, stopped, and + changed. + """ + WIFI_SHARE_PROTECTED = 7 + """Controls whether a password protected Wi-Fi hotspot can be created.""" + WIFI_SHARE_OPEN = 8 + """Controls whether an open Wi-Fi hotspot can be created.""" + SETTINGS_MODIFY_SYSTEM = 9 + """Controls whether connections that are available to all users + can be modified. + """ + MODIFY_OWN = 10 + """Controls whether connections owned by the current user + can be modified. + """ + MODIFY_HOSTNAME = 11 + """Controls whether the persistent hostname can be changed.""" + MODIFY_GLOBAL_DNS = 12 + """Modify persistent global DNS configuration.""" + RELOAD = 13 + """Controls access to reload.""" + CHECKPOINT_ROLLBACK = 14 + """Permission to create checkpoints.""" + ENABLE_DISABLE_STATISTICS = 15 + """Controls whether device statistics can be globally enabled + or disabled. + """ + ENABLE_DISABLE_CONNECTIVITY_CHECK = 16 + """Controls whether connectivity check can be enabled or disabled.""" + WIFI_SCAN = 17 + """Controls whether wifi scans can be performed.""" + + +class ClientPermissionResult(IntEnum): + """Indicate what authorizations and permissions required for permission.""" + + UNKNOWN = 0 + """Unknown or no authorization.""" + YES = 1 + """Permission is available.""" + AUTH = 2 + """Authorization is necessary before the permission is available.""" + NO = 3 + """Permission to perform the operation is denied by system policy.""" + + class RadioFlags(IntFlag): """Flags related to radio interfaces. diff --git a/sdbus_block/networkmanager/__init__.py b/sdbus_block/networkmanager/__init__.py index 6b06a65..43393af 100644 --- a/sdbus_block/networkmanager/__init__.py +++ b/sdbus_block/networkmanager/__init__.py @@ -26,6 +26,8 @@ BluetoothCapabilitiesFlags, CheckpointCreateFlags, CheckpointRollbackResult, + ClientPermission, + ClientPermissionResult, ConnectionMultiConnect, ConnectionType, ConnectivityState, @@ -42,6 +44,7 @@ NetworkManagerCapabilitiesFlags, NetworkManagerReloadFlags, NetworkManagerState, + NetworkManagerVersionInfoCapability, RadioFlags, SecretAgentCapabilitiesFlags, SecretAgentGetSecretsFlags, @@ -237,6 +240,8 @@ 'BluetoothCapabilitiesFlags', 'CheckpointCreateFlags', 'CheckpointRollbackResult', + 'ClientPermission', + 'ClientPermissionResult', 'ConnectionMultiConnect', 'ConnectionType', 'ConnectivityState', @@ -253,6 +258,7 @@ 'NetworkManagerCapabilitiesFlags', 'NetworkManagerReloadFlags', 'NetworkManagerState', + 'NetworkManagerVersionInfoCapability', 'RadioFlags', 'SecretAgentCapabilitiesFlags', 'SecretAgentGetSecretsFlags', From 4b94e8f06efdd7e4e35d313bc0e598358602b7f4 Mon Sep 17 00:00:00 2001 From: Hamid Khateb Date: Fri, 22 Aug 2025 09:08:12 +0100 Subject: [PATCH 13/16] Fix NetworkManagerSettings.get_connections_by_id not using the bus it was initialized with The get_connections_by_id does not work properly with explicitly passed bus, and raises the following exception: settings_paths = await nm_settings.get_connections_by_id("eth0") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File ".../networkmanager/objects.py", line 177, in get_connections_by_id settings_properties = await settings.get_settings() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File ".../dbus_proxy_async_method.py", line 108, in _dbus_async_call reply_message = await bus.call_async(call_message) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ sdbus.dbus_exceptions.DbusServiceUnknownError: The name org.freedesktop.NetworkManager was not provided by any .service files This exception was discovered with the following test code: import asyncio from sdbus import sd_bus_open_system from sdbus_async.networkmanager import NetworkManagerSettings async def test_get_connections_by_id(): system_bus = sd_bus_open_system() nm_settings = NetworkManagerSettings(system_bus) settings_paths = await nm_settings.get_connections_by_id("eth0") print(settings_paths) if __name__ == "__main__": asyncio.run(test_get_connections_by_id()) After this fix, the issue is resolved and get_connections_by_id works correctly without raising an exception. --- sdbus_async/networkmanager/objects.py | 3 ++- sdbus_block/networkmanager/objects.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sdbus_async/networkmanager/objects.py b/sdbus_async/networkmanager/objects.py index e03c69a..f5ae24e 100644 --- a/sdbus_async/networkmanager/objects.py +++ b/sdbus_async/networkmanager/objects.py @@ -159,6 +159,7 @@ def __init__(self, bus: Optional[SdBus] = None) -> None: NETWORK_MANAGER_SERVICE_NAME, '/org/freedesktop/NetworkManager/Settings', bus) + self._nm_used_bus = bus async def get_connections_by_id(self, connection_id: str) -> List[str]: """Helper method to get a list of connection profile paths @@ -171,7 +172,7 @@ async def get_connections_by_id(self, connection_id: str) -> List[str]: connection_paths_with_matching_id = [] connection_paths: List[str] = await self.connections for connection_path in connection_paths: - settings = NetworkConnectionSettings(connection_path) + settings = NetworkConnectionSettings(connection_path, self._nm_used_bus) settings_properites = await settings.get_settings() # settings_properites["connection"]["id"][1] gives the id value: if settings_properites["connection"]["id"][1] == connection_id: diff --git a/sdbus_block/networkmanager/objects.py b/sdbus_block/networkmanager/objects.py index 3d4c68d..ef48e29 100644 --- a/sdbus_block/networkmanager/objects.py +++ b/sdbus_block/networkmanager/objects.py @@ -154,6 +154,7 @@ def __init__(self, bus: Optional[SdBus] = None) -> None: NETWORK_MANAGER_SERVICE_NAME, '/org/freedesktop/NetworkManager/Settings', bus) + self._nm_used_bus = bus def get_connections_by_id(self, connection_id: str) -> List[str]: """Helper method to get a list of connection profile paths @@ -165,7 +166,7 @@ def get_connections_by_id(self, connection_id: str) -> List[str]: """ connection_paths_with_matching_id = [] for connection_path in self.connections: - profile = NetworkConnectionSettings(connection_path) + profile = NetworkConnectionSettings(connection_path, self._nm_used_bus) # profile.get_settings()["connection"]["id"][1] gives the id value: if profile.get_settings()["connection"]["id"][1] == connection_id: connection_paths_with_matching_id.append(connection_path) From 34873e6290b02aff5b2d756ea243d3fd019040b2 Mon Sep 17 00:00:00 2001 From: Jan-Niklas Weghorn Date: Wed, 14 Jan 2026 10:28:17 +0100 Subject: [PATCH 14/16] add `persistent-keepalive` parameter to wireguard peers --- sdbus_async/networkmanager/settings/datatypes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdbus_async/networkmanager/settings/datatypes.py b/sdbus_async/networkmanager/settings/datatypes.py index 278012c..f5e3ff5 100644 --- a/sdbus_async/networkmanager/settings/datatypes.py +++ b/sdbus_async/networkmanager/settings/datatypes.py @@ -126,6 +126,10 @@ class WireguardPeers(NetworkManagerSettingsMixin): metadata={'dbus_name': 'allowed-ips', 'dbus_type': 'as'}, default=None, ) + persistent_keepalive: Optional[str] = field( + metadata={"dbus_name": "persistent-keepalive", "dbus_type": "u"}, + default=None, + ) @dataclass From d53c91d6c831c0ff3c7989db09e5af2391d7dddf Mon Sep 17 00:00:00 2001 From: Jan-Niklas Weghorn Date: Wed, 11 Feb 2026 11:01:16 +0100 Subject: [PATCH 15/16] fix: `WireguardPeers.endpoint` has wrong type int; should be str --- sdbus_async/networkmanager/settings/datatypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdbus_async/networkmanager/settings/datatypes.py b/sdbus_async/networkmanager/settings/datatypes.py index 278012c..24ab210 100644 --- a/sdbus_async/networkmanager/settings/datatypes.py +++ b/sdbus_async/networkmanager/settings/datatypes.py @@ -118,7 +118,7 @@ class WireguardPeers(NetworkManagerSettingsMixin): metadata={'dbus_name': 'public-key', 'dbus_type': 's'}, default=None, ) - endpoint: Optional[int] = field( + endpoint: Optional[str] = field( metadata={'dbus_name': 'endpoint', 'dbus_type': 's'}, default=None, ) From aa7eb222e4362c394b020041023bbcb44cb523bc Mon Sep 17 00:00:00 2001 From: Jan-Niklas Weghorn Date: Tue, 17 Feb 2026 08:30:23 +0100 Subject: [PATCH 16/16] `persistent_keepalive` type should be int --- sdbus_async/networkmanager/settings/datatypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdbus_async/networkmanager/settings/datatypes.py b/sdbus_async/networkmanager/settings/datatypes.py index f5e3ff5..537a5a4 100644 --- a/sdbus_async/networkmanager/settings/datatypes.py +++ b/sdbus_async/networkmanager/settings/datatypes.py @@ -126,7 +126,7 @@ class WireguardPeers(NetworkManagerSettingsMixin): metadata={'dbus_name': 'allowed-ips', 'dbus_type': 'as'}, default=None, ) - persistent_keepalive: Optional[str] = field( + persistent_keepalive: Optional[int] = field( metadata={"dbus_name": "persistent-keepalive", "dbus_type": "u"}, default=None, )