diff options
| author | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-06-30 18:56:00 +0200 |
|---|---|---|
| committer | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-06-30 19:09:19 +0200 |
| commit | f9390ffea216f7b60690602794168a60d81ca325 (patch) | |
| tree | 77500323da689adab56ad78125bd7606aa9d4742 | |
| parent | 0653328c2334fec46baf37695e908ebb7626400b (diff) | |
| download | pyouroboros-f9390ffea216f7b60690602794168a60d81ca325.tar.gz pyouroboros-f9390ffea216f7b60690602794168a60d81ca325.zip | |
ouroboros: Update IRM wrapper and CLI helpers
Update the IRM wrapper and the CLI helpers to match the name/IPCP
management, QoS and security-path handling.
| -rw-r--r-- | ouroboros/cli.py | 225 | ||||
| -rw-r--r-- | ouroboros/irm.py | 563 |
2 files changed, 315 insertions, 473 deletions
diff --git a/ouroboros/cli.py b/ouroboros/cli.py index 7f07e56..4b9ebb7 100644 --- a/ouroboros/cli.py +++ b/ouroboros/cli.py @@ -19,14 +19,14 @@ # Foundation, Inc., http://www.fsf.org/about/contact/. # -""" -Higher-level wrappers that mirror CLI tool behaviour. +"""Higher-level wrappers that mirror the ``irm`` CLI tool behaviour. -The ``irm`` CLI tools perform extra steps that the raw C library API -does not, such as resolving program names via ``realpath``, looking up -IPCP pids, and the ``autobind`` flag for bootstrapping/enrolling. -This module exposes those same patterns as a Python API so that -callers do not need to re-implement them. +This is the Python counterpart of the C project's ``ouroboros/tools/irm`` +command-line tool. The ``irm`` CLI performs steps that the raw C +library API does not, such as resolving program names via ``realpath``, +looking up IPCP pids, and handling the ``autobind`` flag during +bootstrap/enroll. This module exposes those same patterns as a Python +API so callers do not need to re-implement them. Each wrapper corresponds to a specific ``irm`` sub-command: @@ -61,26 +61,28 @@ Usage:: from ouroboros.cli import bind_program, autoboot """ +from __future__ import annotations + import shutil -from typing import List, Optional +from typing import List, Optional, Set +from ouroboros.errors import BindError from ouroboros.irm import ( DT_COMP, MGMT_COMP, - IpcpType, + DhtConfig, + DirConfig, + DtConfig, + EthConfig, IpcpConfig, IpcpInfo, + IpcpType, + LinkStateConfig, NameInfo, - BindError, - IrmError, - UnicastConfig, - DtConfig, RoutingConfig, - LinkStateConfig, - LinkStatePolicy, - EthConfig, Udp4Config, Udp6Config, + UnicastConfig, bind_program as _irm_bind_program, bind_process as _irm_bind_process, bootstrap_ipcp as _irm_bootstrap_ipcp, @@ -100,6 +102,21 @@ from ouroboros.irm import ( ) from ouroboros.qos import QoSSpec +__all__ = [ + # Functions + "create_ipcp", "destroy_ipcp", "bootstrap_ipcp", "enroll_ipcp", + "connect_ipcp", "disconnect_ipcp", "list_ipcps", + "bind_program", "bind_process", "bind_ipcp", + "unbind_program", "unbind_process", "unbind_ipcp", + "create_name", "destroy_name", "reg_name", "unreg_name", "list_names", + "autoboot", + # Re-exported configuration types (convenience) + "DhtConfig", "DirConfig", "DtConfig", "EthConfig", + "IpcpConfig", "IpcpInfo", "IpcpType", + "LinkStateConfig", "NameInfo", "RoutingConfig", + "Udp4Config", "Udp6Config", "UnicastConfig", +] + def _pid_of(ipcp_name: str) -> int: """Look up the pid of a running IPCP by its name.""" @@ -109,9 +126,42 @@ def _pid_of(ipcp_name: str) -> int: raise ValueError(f"No IPCP named {ipcp_name!r}") +def _collect_pids(ipcp: Optional[str], + ipcps: Optional[List[str]], + layer: Optional[str], + layers: Optional[List[str]]) -> Set[int]: + """Collect the set of pids matching the given name/layer filters.""" + ipcp_names: List[str] = [] + if ipcp is not None: + ipcp_names.append(ipcp) + if ipcps is not None: + ipcp_names.extend(ipcps) + + layer_names: List[str] = [] + if layer is not None: + layer_names.append(layer) + if layers is not None: + layer_names.extend(layers) + + pids: Set[int] = set() + if not ipcp_names and not layer_names: + return pids + + all_ipcps = _irm_list_ipcps() + for ipcp_name in ipcp_names: + for i in all_ipcps: + if i.name == ipcp_name: + pids.add(i.pid) + break + for lyr in layer_names: + for i in all_ipcps: + if i.layer == lyr: + pids.add(i.pid) + return pids + + def destroy_ipcp(name: str) -> None: - """ - Destroy an IPCP by name. + """Destroy an IPCP by name. Mirrors ``irm ipcp destroy name <name>``. @@ -126,8 +176,7 @@ def destroy_ipcp(name: str) -> None: def list_ipcps(name: Optional[str] = None, layer: Optional[str] = None, ipcp_type: Optional[IpcpType] = None) -> List[IpcpInfo]: - """ - List running IPCPs, optionally filtered. + """List running IPCPs, optionally filtered. Mirrors ``irm ipcp list [name <n>] [type <t>] [layer <l>]``. @@ -151,8 +200,7 @@ def reg_name(name: str, ipcps: Optional[List[str]] = None, layer: Optional[str] = None, layers: Optional[List[str]] = None) -> None: - """ - Register a name with IPCP(s), creating it first if needed. + """Register a name with IPCP(s), creating it first if needed. Mirrors ``irm name register <name> ipcp <ipcp> [ipcp ...] layer <layer> [layer ...]``. @@ -178,59 +226,30 @@ def reg_name(name: str, if name not in existing: _irm_create_name(NameInfo(name=name)) - pids = set() - - # Collect IPCP names into a single list - ipcp_names = [] - if ipcp is not None: - ipcp_names.append(ipcp) - if ipcps is not None: - ipcp_names.extend(ipcps) - - # Collect layer names into a single list - layer_names = [] - if layer is not None: - layer_names.append(layer) - if layers is not None: - layer_names.extend(layers) - - if ipcp_names or layer_names: - all_ipcps = _irm_list_ipcps() - for ipcp_name in ipcp_names: - for i in all_ipcps: - if i.name == ipcp_name: - pids.add(i.pid) - break - for lyr in layer_names: - for i in all_ipcps: - if i.layer == lyr: - pids.add(i.pid) - - for p in pids: - _irm_reg_name(name, p) + for pid in _collect_pids(ipcp, ipcps, layer, layers): + _irm_reg_name(name, pid) def bind_program(prog: str, name: str, opts: int = 0, argv: Optional[List[str]] = None) -> None: - """ - Bind a program to a name, resolving bare names to full paths. + """Bind a program to a name, resolving bare names to full paths. Mirrors ``irm bind program <prog> name <name>``. The ``irm bind program`` CLI tool calls ``realpath()`` on *prog* - before passing it to the library. The raw C function + before passing it to the library. The raw C function ``irm_bind_program`` contains a ``check_prog_path`` helper that corrupts the ``PATH`` environment variable (writes NUL over ``:`` - separators) when given a bare program name. Only the first such + separators) when given a bare program name. Only the first such call would succeed in a long-running process. This wrapper resolves *prog* via ``shutil.which()`` before calling the library, avoiding the bug entirely. - :param prog: Program name or path. Bare names (without ``/``) - are resolved on ``PATH`` via ``shutil.which()``. + :param prog: Program name or path. Bare names (without ``/``) are + resolved on ``PATH`` via ``shutil.which()``. :param name: Name to bind to. :param opts: Bind options (e.g. ``BIND_AUTO``). :param argv: Arguments to pass when the program is auto-started. @@ -246,8 +265,7 @@ def bind_program(prog: str, def unbind_program(prog: str, name: str) -> None: - """ - Unbind a program from a name. + """Unbind a program from a name. Mirrors ``irm unbind program <prog> name <name>``. @@ -258,8 +276,7 @@ def unbind_program(prog: str, name: str) -> None: def bind_process(pid: int, name: str) -> None: - """ - Bind a running process to a name. + """Bind a running process to a name. Mirrors ``irm bind process <pid> name <name>``. @@ -270,8 +287,7 @@ def bind_process(pid: int, name: str) -> None: def unbind_process(pid: int, name: str) -> None: - """ - Unbind a process from a name. + """Unbind a process from a name. Mirrors ``irm unbind process <pid> name <name>``. @@ -282,12 +298,11 @@ def unbind_process(pid: int, name: str) -> None: def bind_ipcp(ipcp: str, name: str) -> None: - """ - Bind an IPCP to a name. + """Bind an IPCP to a name. Mirrors ``irm bind ipcp <ipcp> name <name>``. - Resolves the IPCP name to a pid, then calls ``bind_process``. + Resolves the IPCP name to a pid, then calls :func:`bind_process`. :param ipcp: IPCP instance name. :param name: Name to bind to. @@ -297,12 +312,11 @@ def bind_ipcp(ipcp: str, name: str) -> None: def unbind_ipcp(ipcp: str, name: str) -> None: - """ - Unbind an IPCP from a name. + """Unbind an IPCP from a name. Mirrors ``irm unbind ipcp <ipcp> name <name>``. - Resolves the IPCP name to a pid, then calls ``unbind_process``. + Resolves the IPCP name to a pid, then calls :func:`unbind_process`. :param ipcp: IPCP instance name. :param name: Name to unbind from. @@ -314,14 +328,13 @@ def unbind_ipcp(ipcp: str, name: str) -> None: def create_name(name: str, pol_lb: Optional[int] = None, info: Optional[NameInfo] = None) -> None: - """ - Create a registered name. + """Create a registered name. Mirrors ``irm name create <name> [lb <policy>]``. :param name: The name to create. :param pol_lb: Load-balance policy (optional). - :param info: Full :class:`NameInfo` (overrides *name*/*pol_lb* + :param info: Full :class:`NameInfo` (overrides *name* / *pol_lb* if given). """ if info is not None: @@ -334,8 +347,7 @@ def create_name(name: str, def list_names(name: Optional[str] = None) -> List[NameInfo]: - """ - List all registered names, optionally filtered. + """List all registered names, optionally filtered. Mirrors ``irm name list [<name>]``. @@ -353,8 +365,7 @@ def unreg_name(name: str, ipcps: Optional[List[str]] = None, layer: Optional[str] = None, layers: Optional[List[str]] = None) -> None: - """ - Unregister a name from IPCP(s). + """Unregister a name from IPCP(s). Mirrors ``irm name unregister <name> ipcp <ipcp> [ipcp ...] layer <layer> [layer ...]``. @@ -367,41 +378,14 @@ def unreg_name(name: str, :param layer: Single layer name to unregister from. :param layers: List of layer names to unregister from. """ - pids = set() - - ipcp_names = [] - if ipcp is not None: - ipcp_names.append(ipcp) - if ipcps is not None: - ipcp_names.extend(ipcps) - - layer_names = [] - if layer is not None: - layer_names.append(layer) - if layers is not None: - layer_names.extend(layers) - - if ipcp_names or layer_names: - all_ipcps = _irm_list_ipcps() - for ipcp_name in ipcp_names: - for i in all_ipcps: - if i.name == ipcp_name: - pids.add(i.pid) - break - for lyr in layer_names: - for i in all_ipcps: - if i.layer == lyr: - pids.add(i.pid) - - for p in pids: - _irm_unreg_name(name, p) + for pid in _collect_pids(ipcp, ipcps, layer, layers): + _irm_unreg_name(name, pid) def bootstrap_ipcp(name: str, conf: IpcpConfig, autobind: bool = False) -> None: - """ - Bootstrap an IPCP, optionally binding it to its name and layer. + """Bootstrap an IPCP, optionally binding it to its name and layer. Mirrors ``irm ipcp bootstrap name <n> layer <l> [autobind]``. @@ -412,7 +396,7 @@ def bootstrap_ipcp(name: str, bind_process(pid, layer_name) # accept flows for layer name bootstrap_ipcp(pid, conf) # bootstrap into the layer - This matches the C ``irm ipcp bootstrap`` tool exactly. If + This matches the C ``irm ipcp bootstrap`` tool exactly. If bootstrap fails after autobind, the bindings are rolled back. :param name: Name of the IPCP. @@ -421,17 +405,16 @@ def bootstrap_ipcp(name: str, """ pid = _pid_of(name) layer_name = conf.layer_name + autobind_types = (IpcpType.UNICAST, IpcpType.BROADCAST) - if autobind and conf.ipcp_type in (IpcpType.UNICAST, - IpcpType.BROADCAST): + if autobind and conf.ipcp_type in autobind_types: _irm_bind_process(pid, name) _irm_bind_process(pid, layer_name) try: _irm_bootstrap_ipcp(pid, conf) except Exception: - if autobind and conf.ipcp_type in (IpcpType.UNICAST, - IpcpType.BROADCAST): + if autobind and conf.ipcp_type in autobind_types: _irm_unbind_process(pid, name) _irm_unbind_process(pid, layer_name) raise @@ -439,8 +422,7 @@ def bootstrap_ipcp(name: str, def enroll_ipcp(name: str, dst: str, autobind: bool = False) -> None: - """ - Enroll an IPCP, optionally binding it to its name and layer. + """Enroll an IPCP, optionally binding it to its name and layer. Mirrors ``irm ipcp enroll name <n> layer <dst> [autobind]``. @@ -461,7 +443,7 @@ def enroll_ipcp(name: str, dst: str, _irm_enroll_ipcp(pid, dst) if autobind: - # Look up enrolled layer from the IPCP list + # Look up enrolled layer from the IPCP list. for info in _irm_list_ipcps(): if info.pid == pid: _irm_bind_process(pid, info.name) @@ -471,8 +453,7 @@ def enroll_ipcp(name: str, dst: str, def connect_ipcp(name: str, dst: str, comp: str = "*", qos: Optional[QoSSpec] = None) -> None: - """ - Connect IPCP components to a destination. + """Connect IPCP components to a destination. Mirrors ``irm ipcp connect name <n> dst <dst> [component <c>] [qos <qos>]``. @@ -494,8 +475,7 @@ def connect_ipcp(name: str, dst: str, comp: str = "*", def disconnect_ipcp(name: str, dst: str, comp: str = "*") -> None: - """ - Disconnect IPCP components from a destination. + """Disconnect IPCP components from a destination. Mirrors ``irm ipcp disconnect name <n> dst <dst> [component <c>]``. @@ -519,8 +499,7 @@ def autoboot(name: str, ipcp_type: IpcpType, layer: str, conf: Optional[IpcpConfig] = None) -> None: - """ - Create, autobind and bootstrap an IPCP in one step. + """Create, autobind and bootstrap an IPCP in one step. Convenience wrapper equivalent to:: @@ -531,8 +510,8 @@ def autoboot(name: str, :param name: Name for the IPCP. :param ipcp_type: Type of IPCP to create. :param layer: Layer name to bootstrap into. - :param conf: Optional IPCP configuration. If *None*, a - default ``IpcpConfig`` is created for the given + :param conf: Optional IPCP configuration. If *None*, a default + :class:`IpcpConfig` is created for the given *ipcp_type* and *layer*. """ create_ipcp(name, ipcp_type) diff --git a/ouroboros/irm.py b/ouroboros/irm.py index 5c23aaa..c8803ce 100644 --- a/ouroboros/irm.py +++ b/ouroboros/irm.py @@ -19,51 +19,34 @@ # Foundation, Inc., http://www.fsf.org/about/contact/. # +"""IRM (IPC Resource Manager) bindings for Ouroboros.""" + +from __future__ import annotations + +from dataclasses import dataclass, field from enum import IntEnum from typing import List, Optional from _ouroboros_irm_cffi import ffi, lib + +from ouroboros._qosspec import qos_to_qosspec +from ouroboros.errors import ( + BindError, + IpcpBootstrapError, + IpcpConnectError, + IpcpCreateError, + IpcpEnrollError, + IrmError, + NameExistsError, + NameNotFoundError, + check_ouroboros_version, + raise_errno, +) from ouroboros.qos import QoSSpec +check_ouroboros_version(lib.OUROBOROS_VERSION_MAJOR, + lib.OUROBOROS_VERSION_MINOR) -def _check_ouroboros_version(): - ouro_major = lib.OUROBOROS_VERSION_MAJOR - ouro_minor = lib.OUROBOROS_VERSION_MINOR - try: - from importlib.metadata import version, PackageNotFoundError - try: - pyouro_parts = version('PyOuroboros').split('.') - except PackageNotFoundError: - return # running from source, skip check - if ouro_major != int(pyouro_parts[0]) or \ - ouro_minor != int(pyouro_parts[1]): - raise RuntimeError( - f"Ouroboros version mismatch: library is " - f"{ouro_major}.{ouro_minor}, " - f"pyouroboros is " - f"{pyouro_parts[0]}.{pyouro_parts[1]}" - ) - except ImportError: - pass # Python < 3.8 - - -_check_ouroboros_version() - - -# Intentionally duplicated: irm uses a separate FFI (ouroboros-irm). -def _qos_to_qosspec(qos: QoSSpec): - if qos is None: - return ffi.NULL - else: - return ffi.new("qosspec_t *", - [qos.delay, - qos.bandwidth, - qos.availability, - qos.loss, - qos.ber, - qos.in_order, - qos.max_gap, - qos.timeout]) # --- Enumerations --- @@ -127,230 +110,135 @@ DT_COMP = "Data Transfer" MGMT_COMP = "Management" -# --- Exceptions --- - -class IrmError(Exception): - """General IRM error.""" - pass - - -class IpcpCreateError(IrmError): - pass - - -class IpcpBootstrapError(IrmError): - pass - - -class IpcpEnrollError(IrmError): - pass - - -class IpcpConnectError(IrmError): - pass - - -class NameError(IrmError): - pass - - -class BindError(IrmError): - pass - - # --- Configuration classes --- +@dataclass class LinkStateConfig: """Configuration for link state routing.""" - - def __init__(self, - pol: LinkStatePolicy = LinkStatePolicy.SIMPLE, - t_recalc: int = 4, - t_update: int = 15, - t_timeo: int = 60): - self.pol = pol - self.t_recalc = t_recalc - self.t_update = t_update - self.t_timeo = t_timeo + pol: LinkStatePolicy = LinkStatePolicy.SIMPLE + t_recalc: int = 4 + t_update: int = 15 + t_timeo: int = 60 +@dataclass class RoutingConfig: """Routing configuration.""" - - def __init__(self, - pol: RoutingPolicy = RoutingPolicy.LINK_STATE, - ls: LinkStateConfig = None): - self.pol = pol - self.ls = ls or LinkStateConfig() + pol: RoutingPolicy = RoutingPolicy.LINK_STATE + ls: LinkStateConfig = field(default_factory=LinkStateConfig) +@dataclass class DtConfig: """Data transfer configuration for unicast IPCPs.""" - - def __init__(self, - addr_size: int = 4, - eid_size: int = 8, - max_ttl: int = 60, - routing: RoutingConfig = None): - self.addr_size = addr_size - self.eid_size = eid_size - self.max_ttl = max_ttl - self.routing = routing or RoutingConfig() + addr_size: int = 4 + eid_size: int = 8 + max_ttl: int = 60 + routing: RoutingConfig = field(default_factory=RoutingConfig) +@dataclass class DhtConfig: """DHT directory configuration.""" - - def __init__(self, - alpha: int = 3, - k: int = 8, - t_expire: int = 86400, - t_refresh: int = 900, - t_replicate: int = 900): - self.alpha = alpha - self.k = k - self.t_expire = t_expire - self.t_refresh = t_refresh - self.t_replicate = t_replicate + alpha: int = 3 + k: int = 8 + t_expire: int = 86400 + t_refresh: int = 900 + t_replicate: int = 900 +@dataclass class DirConfig: """Directory configuration.""" - - def __init__(self, - pol: DirectoryPolicy = DirectoryPolicy.DHT, - dht: DhtConfig = None): - self.pol = pol - self.dht = dht or DhtConfig() + pol: DirectoryPolicy = DirectoryPolicy.DHT + dht: DhtConfig = field(default_factory=DhtConfig) +@dataclass class UnicastConfig: """Configuration for unicast IPCPs.""" - - def __init__(self, - dt: DtConfig = None, - dir: DirConfig = None, - addr_auth: AddressAuthPolicy = AddressAuthPolicy.FLAT_RANDOM, - cong_avoid: CongestionAvoidPolicy = CongestionAvoidPolicy.MB_ECN): - self.dt = dt or DtConfig() - self.dir = dir or DirConfig() - self.addr_auth = addr_auth - self.cong_avoid = cong_avoid + dt: DtConfig = field(default_factory=DtConfig) + dir: DirConfig = field(default_factory=DirConfig) + addr_auth: AddressAuthPolicy = AddressAuthPolicy.FLAT_RANDOM + cong_avoid: CongestionAvoidPolicy = CongestionAvoidPolicy.MB_ECN +@dataclass class EthConfig: """Configuration for Ethernet IPCPs (LLC or DIX).""" - - def __init__(self, - dev: str = "", - ethertype: int = 0xA000): - self.dev = dev - self.ethertype = ethertype + dev: str = "" + ethertype: int = 0xA000 +@dataclass class Udp4Config: """Configuration for UDP over IPv4 IPCPs.""" - - def __init__(self, - ip_addr: str = "0.0.0.0", - dns_addr: str = "0.0.0.0", - port: int = 3435): - self.ip_addr = ip_addr - self.dns_addr = dns_addr - self.port = port + ip_addr: str = "0.0.0.0" + dns_addr: str = "0.0.0.0" + port: int = 3435 +@dataclass class Udp6Config: """Configuration for UDP over IPv6 IPCPs.""" - - def __init__(self, - ip_addr: str = "::", - dns_addr: str = "::", - port: int = 3435): - self.ip_addr = ip_addr - self.dns_addr = dns_addr - self.port = port + ip_addr: str = "::" + dns_addr: str = "::" + port: int = 3435 +@dataclass class IpcpConfig: - """ - Configuration for bootstrapping an IPCP. + """Configuration for bootstrapping an IPCP. Depending on the IPCP type, set the appropriate sub-configuration: - - UNICAST: unicast (UnicastConfig) - - ETH_LLC: eth (EthConfig) - - ETH_DIX: eth (EthConfig) - - UDP4: udp4 (Udp4Config) - - UDP6: udp6 (Udp6Config) - - LOCAL: no extra config needed - - BROADCAST: no extra config needed - """ - def __init__(self, - ipcp_type: IpcpType, - layer_name: str = "", - dir_hash_algo: DirectoryHashAlgo = DirectoryHashAlgo.SHA3_256, - unicast: UnicastConfig = None, - eth: EthConfig = None, - udp4: Udp4Config = None, - udp6: Udp6Config = None): - self.ipcp_type = ipcp_type - self.layer_name = layer_name - self.dir_hash_algo = dir_hash_algo - self.unicast = unicast - self.eth = eth - self.udp4 = udp4 - self.udp6 = udp6 + - UNICAST: ``unicast`` (:class:`UnicastConfig`) + - ETH_LLC: ``eth`` (:class:`EthConfig`) + - ETH_DIX: ``eth`` (:class:`EthConfig`) + - UDP4: ``udp4`` (:class:`Udp4Config`) + - UDP6: ``udp6`` (:class:`Udp6Config`) + - LOCAL: no extra config needed + - BROADCAST: no extra config needed + """ + ipcp_type: IpcpType + layer_name: str = "" + dir_hash_algo: DirectoryHashAlgo = DirectoryHashAlgo.SHA3_256 + unicast: Optional[UnicastConfig] = None + eth: Optional[EthConfig] = None + udp4: Optional[Udp4Config] = None + udp6: Optional[Udp6Config] = None +@dataclass class NameSecPaths: """Security paths for a name (encryption, key, certificate).""" - - def __init__(self, - enc: str = "", - key: str = "", - crt: str = ""): - self.enc = enc - self.key = key - self.crt = crt + enc: str = "" + key: str = "" + crt: str = "" +@dataclass class NameInfo: """Information about a registered name.""" - - def __init__(self, - name: str, - pol_lb: LoadBalancePolicy = LoadBalancePolicy.ROUND_ROBIN, - server_sec: NameSecPaths = None, - client_sec: NameSecPaths = None): - self.name = name - self.pol_lb = pol_lb - self.server_sec = server_sec or NameSecPaths() - self.client_sec = client_sec or NameSecPaths() + name: str + pol_lb: LoadBalancePolicy = LoadBalancePolicy.ROUND_ROBIN + server_sec: NameSecPaths = field(default_factory=NameSecPaths) + client_sec: NameSecPaths = field(default_factory=NameSecPaths) +@dataclass class IpcpInfo: """Information about a running IPCP (from list_ipcps).""" - - def __init__(self, - pid: int, - ipcp_type: IpcpType, - name: str, - layer: str): - self.pid = pid - self.type = ipcp_type - self.name = name - self.layer = layer - - def __repr__(self): - return (f"IpcpInfo(pid={self.pid}, type={self.type.name}, " - f"name='{self.name}', layer='{self.layer}')") + pid: int + type: IpcpType + name: str + layer: str # --- Internal conversion functions --- def _ipcp_config_to_c(conf: IpcpConfig): - """Convert an IpcpConfig to a C struct ipcp_config *.""" + """Convert an :class:`IpcpConfig` to a C ``struct ipcp_config *``.""" _conf = ffi.new("struct ipcp_config *") # Layer info @@ -380,44 +268,44 @@ def _ipcp_config_to_c(conf: IpcpConfig): _conf.unicast.addr_auth_type = uc.addr_auth _conf.unicast.cong_avoid = uc.cong_avoid - elif conf.ipcp_type == IpcpType.ETH_LLC or conf.ipcp_type == IpcpType.ETH_DIX: + elif conf.ipcp_type in (IpcpType.ETH_LLC, IpcpType.ETH_DIX): ec = conf.eth or EthConfig() dev = ec.dev.encode() ffi.memmove(_conf.eth.dev, dev, min(len(dev), 255)) _conf.eth.ethertype = ec.ethertype elif conf.ipcp_type == IpcpType.UDP4: - uc = conf.udp4 or Udp4Config() - _conf.udp4.port = uc.port - if lib.ipcp_config_udp4_set_ip(_conf, uc.ip_addr.encode()) != 0: - raise ValueError(f"Invalid IPv4 address: {uc.ip_addr}") - if lib.ipcp_config_udp4_set_dns(_conf, uc.dns_addr.encode()) != 0: - raise ValueError(f"Invalid IPv4 DNS address: {uc.dns_addr}") + uc4 = conf.udp4 or Udp4Config() + _conf.udp4.port = uc4.port + if lib.ipcp_config_udp4_set_ip(_conf, uc4.ip_addr.encode()) != 0: + raise ValueError(f"Invalid IPv4 address: {uc4.ip_addr}") + if lib.ipcp_config_udp4_set_dns(_conf, uc4.dns_addr.encode()) != 0: + raise ValueError(f"Invalid IPv4 DNS address: {uc4.dns_addr}") elif conf.ipcp_type == IpcpType.UDP6: - uc = conf.udp6 or Udp6Config() - _conf.udp6.port = uc.port - if lib.ipcp_config_udp6_set_ip(_conf, uc.ip_addr.encode()) != 0: - raise ValueError(f"Invalid IPv6 address: {uc.ip_addr}") - if lib.ipcp_config_udp6_set_dns(_conf, uc.dns_addr.encode()) != 0: - raise ValueError(f"Invalid IPv6 DNS address: {uc.dns_addr}") + uc6 = conf.udp6 or Udp6Config() + _conf.udp6.port = uc6.port + if lib.ipcp_config_udp6_set_ip(_conf, uc6.ip_addr.encode()) != 0: + raise ValueError(f"Invalid IPv6 address: {uc6.ip_addr}") + if lib.ipcp_config_udp6_set_dns(_conf, uc6.dns_addr.encode()) != 0: + raise ValueError(f"Invalid IPv6 DNS address: {uc6.dns_addr}") return _conf def _name_info_to_c(info: NameInfo): - """Convert a NameInfo to a C struct name_info *.""" + """Convert a :class:`NameInfo` to a C ``struct name_info *``.""" _info = ffi.new("struct name_info *") name = info.name.encode() ffi.memmove(_info.name, name, min(len(name), 255)) _info.pol_lb = info.pol_lb - for attr, sec in [('s', info.server_sec), ('c', info.client_sec)]: + for attr, sec in (('s', info.server_sec), ('c', info.client_sec)): sec_paths = getattr(_info, attr) - for field in ('enc', 'key', 'crt'): - val = getattr(sec, field).encode() - ffi.memmove(getattr(sec_paths, field), val, + for fld in ('enc', 'key', 'crt'): + val = getattr(sec, fld).encode() + ffi.memmove(getattr(sec_paths, fld), val, min(len(val), 511)) return _info @@ -427,17 +315,14 @@ def _name_info_to_c(info: NameInfo): def create_ipcp(name: str, ipcp_type: IpcpType) -> int: - """ - Create a new IPCP. + """Create a new IPCP. - :param name: Name for the IPCP - :param ipcp_type: Type of IPCP to create - :return: PID of the created IPCP + :param name: Name for the IPCP. + :param ipcp_type: Type of IPCP to create. + :return: PID of the created IPCP. """ - ret = lib.irm_create_ipcp(name.encode(), ipcp_type) - if ret < 0: - raise IpcpCreateError(f"Failed to create IPCP '{name}' " - f"of type {ipcp_type.name}") + raise_errno(lib.irm_create_ipcp(name.encode(), ipcp_type), + default=IpcpCreateError) # The C function returns 0 on success, not the pid. # Look up the actual pid by name. @@ -449,34 +334,29 @@ def create_ipcp(name: str, def destroy_ipcp(pid: int) -> None: - """ - Destroy an IPCP. + """Destroy an IPCP. - :param pid: PID of the IPCP to destroy + :param pid: PID of the IPCP to destroy. """ - if lib.irm_destroy_ipcp(pid) != 0: - raise IrmError(f"Failed to destroy IPCP with pid {pid}") + raise_errno(lib.irm_destroy_ipcp(pid), default=IrmError) def list_ipcps() -> List[IpcpInfo]: - """ - List all running IPCPs. + """List all running IPCPs. - :return: List of IpcpInfo objects + :return: List of :class:`IpcpInfo` objects. """ _ipcps = ffi.new("struct ipcp_list_info **") - n = lib.irm_list_ipcps(_ipcps) - if n < 0: - raise IrmError("Failed to list IPCPs") + n = raise_errno(lib.irm_list_ipcps(_ipcps), default=IrmError) result = [] for i in range(n): info = _ipcps[0][i] result.append(IpcpInfo( pid=info.pid, - ipcp_type=IpcpType(info.type), + type=IpcpType(info.type), name=ffi.string(info.name).decode(), - layer=ffi.string(info.layer).decode() + layer=ffi.string(info.layer).decode(), )) if n > 0: @@ -486,76 +366,73 @@ def list_ipcps() -> List[IpcpInfo]: def enroll_ipcp(pid: int, dst: str) -> None: - """ - Enroll an IPCP in a layer. + """Enroll an IPCP in a layer. - :param pid: PID of the IPCP to enroll - :param dst: Name to use for enrollment + :param pid: PID of the IPCP to enroll. + :param dst: Name to use for enrollment. """ - if lib.irm_enroll_ipcp(pid, dst.encode()) != 0: - raise IpcpEnrollError(f"Failed to enroll IPCP {pid} to '{dst}'") + raise_errno(lib.irm_enroll_ipcp(pid, dst.encode()), + default=IpcpEnrollError) def bootstrap_ipcp(pid: int, conf: IpcpConfig) -> None: - """ - Bootstrap an IPCP. + """Bootstrap an IPCP. - :param pid: PID of the IPCP to bootstrap - :param conf: Configuration for the IPCP + :param pid: PID of the IPCP to bootstrap. + :param conf: Configuration for the IPCP. """ _conf = _ipcp_config_to_c(conf) - if lib.irm_bootstrap_ipcp(pid, _conf) != 0: - raise IpcpBootstrapError(f"Failed to bootstrap IPCP {pid}") + raise_errno(lib.irm_bootstrap_ipcp(pid, _conf), + default=IpcpBootstrapError) def connect_ipcp(pid: int, component: str, dst: str, - qos: QoSSpec = None) -> None: - """ - Connect an IPCP component to a destination. + qos: Optional[QoSSpec] = None) -> None: + """Connect an IPCP component to a destination. - :param pid: PID of the IPCP - :param component: Component to connect (DT_COMP or MGMT_COMP) - :param dst: Destination name - :param qos: QoS specification for the connection + :param pid: PID of the IPCP. + :param component: Component to connect (:data:`DT_COMP` or + :data:`MGMT_COMP`). + :param dst: Destination name. + :param qos: QoS specification for the connection. """ - _qos = _qos_to_qosspec(qos) + _qos = qos_to_qosspec(ffi, qos) if _qos == ffi.NULL: _qos = ffi.new("qosspec_t *") - if lib.irm_connect_ipcp(pid, dst.encode(), component.encode(), - _qos[0]) != 0: - raise IpcpConnectError(f"Failed to connect IPCP {pid} " - f"component '{component}' to '{dst}'") + raise_errno( + lib.irm_connect_ipcp(pid, dst.encode(), component.encode(), + _qos[0]), + default=IpcpConnectError, + ) def disconnect_ipcp(pid: int, component: str, dst: str) -> None: - """ - Disconnect an IPCP component from a destination. + """Disconnect an IPCP component from a destination. - :param pid: PID of the IPCP - :param component: Component to disconnect - :param dst: Destination name + :param pid: PID of the IPCP. + :param component: Component to disconnect. + :param dst: Destination name. """ - if lib.irm_disconnect_ipcp(pid, dst.encode(), - component.encode()) != 0: - raise IpcpConnectError(f"Failed to disconnect IPCP {pid} " - f"component '{component}' from '{dst}'") + raise_errno( + lib.irm_disconnect_ipcp(pid, dst.encode(), component.encode()), + default=IpcpConnectError, + ) def bind_program(prog: str, name: str, opts: int = 0, - argv: List[str] = None) -> None: - """ - Bind a program to a name. + argv: Optional[List[str]] = None) -> None: + """Bind a program to a name. - :param prog: Path to the program - :param name: Name to bind to - :param opts: Bind options (e.g. BIND_AUTO) - :param argv: Arguments to pass when the program is started + :param prog: Path to the program. + :param name: Name to bind to. + :param opts: Bind options (e.g. :data:`BIND_AUTO`). + :param argv: Arguments to pass when the program is started. """ if argv: argc = len(argv) @@ -565,96 +442,86 @@ def bind_program(prog: str, argc = 0 _argv = ffi.NULL - if lib.irm_bind_program(prog.encode(), name.encode(), - opts, argc, _argv) != 0: - raise BindError(f"Failed to bind program '{prog}' to name '{name}'") + raise_errno( + lib.irm_bind_program(prog.encode(), name.encode(), + opts, argc, _argv), + default=BindError, + ) def unbind_program(prog: str, name: str) -> None: - """ - Unbind a program from a name. + """Unbind a program from a name. - :param prog: Path to the program - :param name: Name to unbind from + :param prog: Path to the program. + :param name: Name to unbind from. """ - if lib.irm_unbind_program(prog.encode(), name.encode()) != 0: - raise BindError(f"Failed to unbind program '{prog}' " - f"from name '{name}'") + raise_errno(lib.irm_unbind_program(prog.encode(), name.encode()), + default=BindError) def bind_process(pid: int, name: str) -> None: - """ - Bind a running process to a name. + """Bind a running process to a name. - :param pid: PID of the process - :param name: Name to bind to + :param pid: PID of the process. + :param name: Name to bind to. """ - if lib.irm_bind_process(pid, name.encode()) != 0: - raise BindError(f"Failed to bind process {pid} to name '{name}'") + raise_errno(lib.irm_bind_process(pid, name.encode()), + default=BindError) def unbind_process(pid: int, name: str) -> None: - """ - Unbind a process from a name. + """Unbind a process from a name. - :param pid: PID of the process - :param name: Name to unbind from + :param pid: PID of the process. + :param name: Name to unbind from. """ - if lib.irm_unbind_process(pid, name.encode()) != 0: - raise BindError(f"Failed to unbind process {pid} " - f"from name '{name}'") + raise_errno(lib.irm_unbind_process(pid, name.encode()), + default=BindError) def create_name(info: NameInfo) -> None: - """ - Create a name in the IRM. + """Create a name in the IRM. - :param info: NameInfo describing the name to create + :param info: :class:`NameInfo` describing the name to create. """ _info = _name_info_to_c(info) - if lib.irm_create_name(_info) != 0: - raise NameError(f"Failed to create name '{info.name}'") + raise_errno(lib.irm_create_name(_info), default=NameExistsError) def destroy_name(name: str) -> None: - """ - Destroy a name in the IRM. + """Destroy a name in the IRM. - :param name: The name to destroy + :param name: The name to destroy. """ - if lib.irm_destroy_name(name.encode()) != 0: - raise NameError(f"Failed to destroy name '{name}'") + raise_errno(lib.irm_destroy_name(name.encode()), + default=NameNotFoundError) def list_names() -> List[NameInfo]: - """ - List all registered names. + """List all registered names. - :return: List of NameInfo objects + :return: List of :class:`NameInfo` objects. """ _names = ffi.new("struct name_info **") - n = lib.irm_list_names(_names) - if n < 0: - raise IrmError("Failed to list names") + n = raise_errno(lib.irm_list_names(_names), default=IrmError) result = [] for i in range(n): info = _names[0][i] - ni = NameInfo( + result.append(NameInfo( name=ffi.string(info.name).decode(), - pol_lb=LoadBalancePolicy(info.pol_lb) - ) - ni.server_sec = NameSecPaths( - enc=ffi.string(info.s.enc).decode(), - key=ffi.string(info.s.key).decode(), - crt=ffi.string(info.s.crt).decode() - ) - ni.client_sec = NameSecPaths( - enc=ffi.string(info.c.enc).decode(), - key=ffi.string(info.c.key).decode(), - crt=ffi.string(info.c.crt).decode() - ) - result.append(ni) + pol_lb=LoadBalancePolicy(info.pol_lb), + server_sec=NameSecPaths( + enc=ffi.string(info.s.enc).decode(), + key=ffi.string(info.s.key).decode(), + crt=ffi.string(info.s.crt).decode(), + ), + client_sec=NameSecPaths( + enc=ffi.string(info.c.enc).decode(), + key=ffi.string(info.c.key).decode(), + crt=ffi.string(info.c.crt).decode(), + ), + )) if n > 0: lib.free(_names[0]) @@ -663,24 +530,20 @@ def list_names() -> List[NameInfo]: def reg_name(name: str, pid: int) -> None: - """ - Register an IPCP to a name. + """Register an IPCP to a name. - :param name: The name to register - :param pid: PID of the IPCP to register + :param name: The name to register. + :param pid: PID of the IPCP to register. """ - if lib.irm_reg_name(name.encode(), pid) != 0: - raise NameError(f"Failed to register name '{name}' " - f"with IPCP {pid}") + raise_errno(lib.irm_reg_name(name.encode(), pid), + default=NameNotFoundError) def unreg_name(name: str, pid: int) -> None: - """ - Unregister an IPCP from a name. + """Unregister an IPCP from a name. - :param name: The name to unregister - :param pid: PID of the IPCP to unregister + :param name: The name to unregister. + :param pid: PID of the IPCP to unregister. """ - if lib.irm_unreg_name(name.encode(), pid) != 0: - raise NameError(f"Failed to unregister name '{name}' " - f"from IPCP {pid}") + raise_errno(lib.irm_unreg_name(name.encode(), pid), + default=NameNotFoundError) |
