tractor/tests/discovery/test_tunnelled_addr.py

299 lines
7.6 KiB
Python

'''
`TunnelledAddress` delegation + peeling semantics.
A tunnel annotates an existing L4 addr rather than being its own
transport, so the contract under test is mostly *delegation*: the
runtime must not be able to tell a tunnelled addr from its
overlay, and **nothing** about the tunnel may cross the wire.
See `ai/tpt-backends/03_wg_tunnel_bindspace.md` §3.1/§3.4.
'''
from __future__ import annotations
import msgspec
import pytest
from tractor.discovery import (
BindspaceRef,
TunnelledAddress,
WGTunnelSpec,
mb_pubkey,
strip_tunnels,
tunnels_of,
wg8_pubkey,
)
from tractor.discovery._addr import (
is_wrapped_addr,
wrap_address,
)
from tractor.ipc._tcp import TCPAddress
from tractor.ipc._uds import UDSAddress
# a valid-looking std-base64 `wg(8)` pubkey (32B -> 44 chars)
_PUBKEY: str = 'g3x7z0AdV1rM6UQU22CC7IL3/ivn4DzrE7ikDhCZ/Dc='
@pytest.fixture
def overlay() -> TCPAddress:
return TCPAddress('10.0.11.1', 1616)
@pytest.fixture
def spec() -> WGTunnelSpec:
return WGTunnelSpec(
peer_pubkey=_PUBKEY,
bearer=('192.168.1.50', 51820),
)
@pytest.fixture
def tunnelled(
overlay: TCPAddress,
spec: WGTunnelSpec,
) -> TunnelledAddress:
return TunnelledAddress(overlay=overlay, tunnel=spec)
def test_wg_pubkey_codec_roundtrip():
'''
Standard `wg(8)` base64 keys can contain `/`, which cannot be
embedded unchanged in a slash-delimited maddr. Prove the helper
emits `u`-prefixed multibase base64url without `/` and decodes
it back to the exact original 32-byte key.
'''
mb_key: str = mb_pubkey(_PUBKEY)
assert mb_key.startswith('u')
assert '/' not in mb_key
assert wg8_pubkey(mb_key) == _PUBKEY
@pytest.mark.parametrize(
'key, converter',
[
pytest.param(
'dG9vIHNob3J0',
mb_pubkey,
id='wg8-base64',
),
pytest.param(
'udG9vIHNob3J0',
wg8_pubkey,
id='multibase',
),
],
)
def test_wg_pubkey_codec_rejects_wrong_size(
key: str,
converter,
):
'''
WireGuard silently-corrupt key handling would let an invalid
identity reach peer verification. Exercise both input encodings
with a short payload and prove conversion rejects it before a
tunnel spec or maddr can be constructed.
'''
with pytest.raises(
ValueError,
match='must decode to 32 bytes',
):
converter(key)
def test_proto_key_delegates(
tunnelled: TunnelledAddress,
overlay: TCPAddress,
):
'''
A tunnel has no transport of its own, so every table lookup
must see the *overlay's* proto-key.
'''
assert tunnelled.proto_key == overlay.proto_key == 'tcp'
def test_unwrap_is_identical_to_overlay(
tunnelled: TunnelledAddress,
overlay: TCPAddress,
):
'''
The whole point: nothing new crosses the wire, so a peer
never has to understand tunnels.
'''
assert tunnelled.unwrap() == overlay.unwrap()
# and it must survive msgpack as-is
enc: bytes = msgspec.msgpack.encode(tunnelled.unwrap())
assert msgspec.msgpack.decode(enc) == list(overlay.unwrap())
def test_unwrap_roundtrips_back_to_plain_overlay(
tunnelled: TunnelledAddress,
overlay: TCPAddress,
):
'''
`wrap_address()` on a tunnelled addr's unwrapped form yields
the *plain* overlay type — the tunnel is simply absent, which
is correct: it was never on the wire.
'''
rewrapped = wrap_address(tunnelled.unwrap())
assert type(rewrapped) is TCPAddress
assert rewrapped == overlay
assert not isinstance(rewrapped, TunnelledAddress)
def test_bindspace_and_validity_delegate(
tunnelled: TunnelledAddress,
overlay: TCPAddress,
):
assert tunnelled.bindspace == overlay.bindspace
assert tunnelled.is_valid == overlay.is_valid
def test_is_wrapped_addr_accepts_tunnelled(
tunnelled: TunnelledAddress,
overlay: TCPAddress,
):
'''
`TunnelledAddress` is deliberately absent from
`_address_types`, so `is_wrapped_addr()` needs its own
clause.
'''
assert is_wrapped_addr(overlay)
assert is_wrapped_addr(tunnelled)
# the unwrapped form is NOT a wrapped addr
assert not is_wrapped_addr(tunnelled.unwrap())
def test_namespace_comes_from_the_tunnel(
overlay: TCPAddress,
):
'''
Plain transport addresses explicitly select no namespace, while
a tunnel can select one for the same concrete overlay.
'''
uds_addr: UDSAddress = UDSAddress('/tmp', 'tractor-test.sock')
assert overlay.namespace is None
assert uds_addr.namespace is None
no_ns = TunnelledAddress(
overlay=overlay,
tunnel=WGTunnelSpec(peer_pubkey=_PUBKEY),
)
assert no_ns.namespace is None
in_ns = TunnelledAddress(
overlay=overlay,
tunnel=WGTunnelSpec(peer_pubkey=_PUBKEY, netns='wg-test'),
)
assert in_ns.namespace == ('netns', 'wg-test')
def test_realized_namespace_uses_stable_ref(
overlay: TCPAddress,
) -> None:
'''
Realization must retain a stable ref without mutating the maddr.
Build an unrealized named declaration, annotate it with the
matching realized key and inode, and prove the frozen original is
unchanged. The annotated copy must preserve transport delegation
and expose the stable inode through `.namespace`. Direct msgspec
encoding also proves only serializable ref metadata was retained.
'''
declared: TunnelledAddress = TunnelledAddress(
overlay=overlay,
tunnel=WGTunnelSpec(
peer_pubkey=_PUBKEY,
netns='wg-test',
),
)
ref: BindspaceRef = BindspaceRef(
kind='netns',
key='wg-test',
inode=1234,
)
realized: TunnelledAddress = declared.with_bindspace_ref(
ref,
)
assert declared.bindspace_ref is None
assert realized.bindspace_ref is ref
assert realized.namespace == ('netns', 1234)
assert realized.overlay is declared.overlay
assert realized.tunnel is declared.tunnel
assert realized.unwrap() == declared.unwrap()
assert realized.bindspace == declared.bindspace
declared_payload: dict[str, object] = msgspec.msgpack.decode(
msgspec.msgpack.encode(declared)
)
assert 'bindspace_ref' not in declared_payload
decoded: dict[str, object] = msgspec.msgpack.decode(
msgspec.msgpack.encode(realized)
)
assert decoded['bindspace_ref'] == {
'kind': 'netns',
'key': 'wg-test',
'inode': 1234,
}
mismatched: BindspaceRef = BindspaceRef(
kind='netns',
key='other-netns',
inode=5678,
)
with pytest.raises(
ValueError,
match='wg-test.*other-netns',
):
declared.with_bindspace_ref(
mismatched,
)
def test_strip_tunnels(
tunnelled: TunnelledAddress,
overlay: TCPAddress,
spec: WGTunnelSpec,
):
# idempotent on a plain addr
assert strip_tunnels(overlay) is overlay
# peels one
assert strip_tunnels(tunnelled) is overlay
# and collapses a nested stack in one call
nested = TunnelledAddress(overlay=tunnelled, tunnel=spec)
assert strip_tunnels(nested) is overlay
def test_tunnels_of_is_outermost_first(
tunnelled: TunnelledAddress,
overlay: TCPAddress,
):
assert tunnels_of(overlay) == ()
assert tunnels_of(tunnelled) == (tunnelled.tunnel,)
inner_spec = WGTunnelSpec(peer_pubkey=_PUBKEY, iface='wg1')
nested = TunnelledAddress(
overlay=tunnelled,
tunnel=inner_spec,
)
assert tunnels_of(nested) == (inner_spec, tunnelled.tunnel)
def test_frozen(
tunnelled: TunnelledAddress,
):
with pytest.raises(AttributeError):
tunnelled.overlay = TCPAddress('127.0.0.1', 1)