tractor/examples/multihost/wg_lan/host_a_srv.py

62 lines
1.6 KiB
Python
Raw Normal View History

# tractor: distributed structured concurrency.
'''
Host A: the service host, reachable over a `wg` tunnel.
Binds `tractor`'s registrar + an `echo_srv` sub-actor on the
tunnel's *overlay* addr, declared as a single `wg` maddr.
'''
from __future__ import annotations
import tractor
import trio
from wg_maddr import (
parse_wg_maddr,
verify_wg_peer,
WGTunnelledAddr,
)
# bearer = host A's underlay `(ip, wg ListenPort)`
# key = host A's OWN tunnel pubkey
# overlay = the ep `tractor` binds, on the wg iface's addr
WG_MADDR: str = (
'/ip4/192.168.1.50/udp/51820'
'/wg/u<A_pub_b64url>'
'/ip4/10.0.11.1/tcp/1616'
)
async def echo(msg: str) -> str:
actor = tractor.current_actor()
return f'{actor.aid.name!r} echoes: {msg}'
async def main():
addr: WGTunnelledAddr = parse_wg_maddr(WG_MADDR)
assert verify_wg_peer(addr), (
f'wg pubkey from maddr not active on wg0 !\n'
f'maddr: {WG_MADDR}\n'
f'key: {addr.peer_pubkey}\n'
)
print(
f'wg bearer (kernel-owned): {addr.bearer}\n'
Peel `wg` maddrs w/ `py-multiaddr`'s own tunnel API `py-multiaddr` already ships the entire tunnel compose/peel surface and this module was reimplementing it — a raw `maddr.split('/')` plus index arithmetic, sitting directly under a comment congratulating itself for not hand-rolling a parser. Same NIH trap gh #429 existed to close, just one layer up. The API was linked from gh #443's own 2nd bullet the whole time. So every cut now goes through the real thing, | need | API | | --- | --- | | isolate the bearer | `.decapsulate_code(P_WG)` | | per-seg maddrs | `.split()` | | rejoin a seg tail | `Multiaddr.join()` | | read the key | `.value_for_protocol('wg')` | | recompose | `.encapsulate()` | `.decapsulate_code()` turns out to handle the infix `/wg/` seg cleanly *because* it cuts on proto-code and never tries to match an addr value — the key seg has no addr of its own, which was the exact thing I'd assumed would need bespoke handling. Deats, - rename the role fields `inner`/`inner_proto` -> `overlay`/`overlay_proto`, matching `py-multiaddr`'s encapsulation model (earlier segs wrap later ones) and #443's owner table. `inner` collided head-on w/ call-stack `inner`, where it reads as higher-up + later-called, while here the encapsulated addr is bound *first* and sits deeper. - drop `_segments()` and its degraded hand-split path entirely. W/o the codec there's now one actionable `RuntimeError` instead of a silent downgrade, superseding the swallow fix in 7d6e7955. - add `.as_multiaddr()` so callers can stay in `Multiaddr` land; `.maddr` is now just `str()` of it. - accept `str|Multiaddr` on the way in. - carry `bearer_ip`/`overlay_ip` so a v6 stack re-renders as v6 — the old `.maddr` hardcoded `/ip4/` and would silently mangle it. - both host scripts follow the rename to `.overlay`. ⚠️ `value_for_protocol('ip4')` on a *full* tunnelled maddr silently returns the **first** match, i.e. the bearer's host, so it's only ever called here on an already-peeled sub-maddr. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-17 21:25:07 +00:00
f'tractor overlay ep: {addr.overlay}\n'
)
async with tractor.open_nursery(
Peel `wg` maddrs w/ `py-multiaddr`'s own tunnel API `py-multiaddr` already ships the entire tunnel compose/peel surface and this module was reimplementing it — a raw `maddr.split('/')` plus index arithmetic, sitting directly under a comment congratulating itself for not hand-rolling a parser. Same NIH trap gh #429 existed to close, just one layer up. The API was linked from gh #443's own 2nd bullet the whole time. So every cut now goes through the real thing, | need | API | | --- | --- | | isolate the bearer | `.decapsulate_code(P_WG)` | | per-seg maddrs | `.split()` | | rejoin a seg tail | `Multiaddr.join()` | | read the key | `.value_for_protocol('wg')` | | recompose | `.encapsulate()` | `.decapsulate_code()` turns out to handle the infix `/wg/` seg cleanly *because* it cuts on proto-code and never tries to match an addr value — the key seg has no addr of its own, which was the exact thing I'd assumed would need bespoke handling. Deats, - rename the role fields `inner`/`inner_proto` -> `overlay`/`overlay_proto`, matching `py-multiaddr`'s encapsulation model (earlier segs wrap later ones) and #443's owner table. `inner` collided head-on w/ call-stack `inner`, where it reads as higher-up + later-called, while here the encapsulated addr is bound *first* and sits deeper. - drop `_segments()` and its degraded hand-split path entirely. W/o the codec there's now one actionable `RuntimeError` instead of a silent downgrade, superseding the swallow fix in 7d6e7955. - add `.as_multiaddr()` so callers can stay in `Multiaddr` land; `.maddr` is now just `str()` of it. - accept `str|Multiaddr` on the way in. - carry `bearer_ip`/`overlay_ip` so a v6 stack re-renders as v6 — the old `.maddr` hardcoded `/ip4/` and would silently mangle it. - both host scripts follow the rename to `.overlay`. ⚠️ `value_for_protocol('ip4')` on a *full* tunnelled maddr silently returns the **first** match, i.e. the bearer's host, so it's only ever called here on an already-peeled sub-maddr. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-17 21:25:07 +00:00
# XXX only `.overlay` crosses into the runtime; the bearer
# + key are iface-layer concerns `tractor` never binds.
Peel `wg` maddrs w/ `py-multiaddr`'s own tunnel API `py-multiaddr` already ships the entire tunnel compose/peel surface and this module was reimplementing it — a raw `maddr.split('/')` plus index arithmetic, sitting directly under a comment congratulating itself for not hand-rolling a parser. Same NIH trap gh #429 existed to close, just one layer up. The API was linked from gh #443's own 2nd bullet the whole time. So every cut now goes through the real thing, | need | API | | --- | --- | | isolate the bearer | `.decapsulate_code(P_WG)` | | per-seg maddrs | `.split()` | | rejoin a seg tail | `Multiaddr.join()` | | read the key | `.value_for_protocol('wg')` | | recompose | `.encapsulate()` | `.decapsulate_code()` turns out to handle the infix `/wg/` seg cleanly *because* it cuts on proto-code and never tries to match an addr value — the key seg has no addr of its own, which was the exact thing I'd assumed would need bespoke handling. Deats, - rename the role fields `inner`/`inner_proto` -> `overlay`/`overlay_proto`, matching `py-multiaddr`'s encapsulation model (earlier segs wrap later ones) and #443's owner table. `inner` collided head-on w/ call-stack `inner`, where it reads as higher-up + later-called, while here the encapsulated addr is bound *first* and sits deeper. - drop `_segments()` and its degraded hand-split path entirely. W/o the codec there's now one actionable `RuntimeError` instead of a silent downgrade, superseding the swallow fix in 7d6e7955. - add `.as_multiaddr()` so callers can stay in `Multiaddr` land; `.maddr` is now just `str()` of it. - accept `str|Multiaddr` on the way in. - carry `bearer_ip`/`overlay_ip` so a v6 stack re-renders as v6 — the old `.maddr` hardcoded `/ip4/` and would silently mangle it. - both host scripts follow the rename to `.overlay`. ⚠️ `value_for_protocol('ip4')` on a *full* tunnelled maddr silently returns the **first** match, i.e. the bearer's host, so it's only ever called here on an already-peeled sub-maddr. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
2026-08-17 21:25:07 +00:00
registry_addrs=[addr.overlay],
enable_transports=[addr.overlay_proto],
) as an:
await an.start_actor(
'echo_srv',
enable_modules=[__name__],
)
print(f'echo_srv up on\n {addr.maddr}\n')
await trio.sleep_forever()
if __name__ == '__main__':
trio.run(main)