aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitri Staessens <dimitri@ouroboros.rocks>2026-06-30 18:56:00 +0200
committerDimitri Staessens <dimitri@ouroboros.rocks>2026-06-30 19:09:19 +0200
commitf9390ffea216f7b60690602794168a60d81ca325 (patch)
tree77500323da689adab56ad78125bd7606aa9d4742
parent0653328c2334fec46baf37695e908ebb7626400b (diff)
downloadpyouroboros-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.py225
-rw-r--r--ouroboros/irm.py563
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)