Compare commits
12 Commits
a2e6c10118
...
51d7133f47
| Author | SHA1 | Date |
|---|---|---|
|
|
51d7133f47 | |
|
|
e0f66616cd | |
|
|
2204979492 | |
|
|
8c0ae140cd | |
|
|
e3089ba356 | |
|
|
cca3a70de4 | |
|
|
d4737e957f | |
|
|
f9f98eeb06 | |
|
|
22ef362d9f | |
|
|
ee17ed9f6e | |
|
|
7d6e79551e | |
|
|
cc85f17f5f |
|
|
@ -26,10 +26,11 @@ onto `trio` as the library's sans-io layer allows.
|
||||||
`/ip4|ip6/<h>/tcp/<p>` and `/unix/<p>`; a `.../wg/u<key>`
|
`/ip4|ip6/<h>/tcp/<p>` and `/unix/<p>`; a `.../wg/u<key>`
|
||||||
maddr raises `ValueError('Unsupported multiaddr protocol
|
maddr raises `ValueError('Unsupported multiaddr protocol
|
||||||
combo')`.
|
combo')`.
|
||||||
- there is no `wg` proto in the multiaddr spec; the first-draft
|
- there is no `wg` proto in the multiaddr *spec* yet, but
|
||||||
upstream PR is multiformats/py-multiaddr#108 with key form
|
multiformats/py-multiaddr#108 (key form `u<base64url>`) is
|
||||||
`u<base64url>` (commit `8be3a8b`), tracked by
|
**merged** as of 2026-07-28 (`f86519da`) — and unreleased, the
|
||||||
multiformats/py-multiaddr#107 and gh #483.
|
latest `0.2.0` predating it. Spec registration is still tracked
|
||||||
|
by multiformats/py-multiaddr#107 and gh #483.
|
||||||
- so **today's deployable story is declarative**: run `wg-quick`
|
- so **today's deployable story is declarative**: run `wg-quick`
|
||||||
out-of-band, parse the maddr, strip to the inner
|
out-of-band, parse the maddr, strip to the inner
|
||||||
`(host, port)`, verify the pubkey against the live tunnel,
|
`(host, port)`, verify the pubkey against the live tunnel,
|
||||||
|
|
@ -112,9 +113,12 @@ class WGTunnelSpec(
|
||||||
|
|
||||||
### 3.2 `parse_maddr()`/`mk_maddr()`
|
### 3.2 `parse_maddr()`/`mk_maddr()`
|
||||||
|
|
||||||
Grammar — **verified** against py-multiaddr#108
|
Grammar — **verified** against py-multiaddr#108, first on the
|
||||||
(`baudco/py-multiaddr@wg_support`, installed in a throwaway venv;
|
`baudco/py-multiaddr@wg_support` branch and re-verified after it
|
||||||
all three forms below parse *and* round-trip):
|
merged upstream (`multiformats/py-multiaddr@f86519da`); all three
|
||||||
|
forms below parse *and* round-trip. Note the codec also validates
|
||||||
|
that the key decodes to exactly 32 bytes, so a truncated key is a
|
||||||
|
`StringParseError`, not a silently-mangled parse:
|
||||||
|
|
||||||
```
|
```
|
||||||
/ip4/192.168.1.50/udp/51820/wg/u<A_pub>/ip4/10.0.11.1/tcp/1616
|
/ip4/192.168.1.50/udp/51820/wg/u<A_pub>/ip4/10.0.11.1/tcp/1616
|
||||||
|
|
@ -169,13 +173,16 @@ Observed protocol-name lists, for writing the `match`:
|
||||||
(bearer_names, tunnel_specs, inner_names)`. This is also what
|
(bearer_names, tunnel_specs, inner_names)`. This is also what
|
||||||
makes a wg-inside-wg stack fall out for free.
|
makes a wg-inside-wg stack fall out for free.
|
||||||
- `mk_maddr()` inverse for `TunnelledAddress`.
|
- `mk_maddr()` inverse for `TunnelledAddress`.
|
||||||
- **blocked on upstream**: `Multiaddr('/…/wg/u…')` only parses
|
- **pending an upstream release**: py-multiaddr#108 is merged, so
|
||||||
once py-multiaddr#108 lands. Until then: pin the branch in the
|
`Multiaddr('/…/wg/u…')` parses — but off a `[tool.uv.sources]`
|
||||||
`wg` extra / dev-group and gate the tests on
|
`rev` pin, since no release carries the codec. Gate the tests
|
||||||
`_have_wg_maddr_proto()` (a cheap try/except around
|
on `_have_wg_maddr_proto()`, implemented as
|
||||||
`Multiaddr('/wg/uAAAA')`). Do **not** hand-roll a `wg` parser
|
`protocols.protocol_with_name('wg')` under
|
||||||
in `tractor` — the whole point of #429 was dropping the NIH
|
`except ProtocolNotFoundError`. Do **not** probe by parsing a
|
||||||
parser.
|
dummy like `Multiaddr('/wg/uAAAA')` — the codec enforces a
|
||||||
|
32-byte key, so that raises even when the proto *is* known. Do
|
||||||
|
**not** hand-roll a `wg` parser in `tractor` — the whole point
|
||||||
|
of #429 was dropping the NIH parser.
|
||||||
|
|
||||||
### 3.3 verification helper (pure, composable)
|
### 3.3 verification helper (pure, composable)
|
||||||
|
|
||||||
|
|
@ -437,7 +444,7 @@ consider doing it *first* for exactly that reason.
|
||||||
| risk | mitigation |
|
| risk | mitigation |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `to_thread` worker runs in the wrong netns | §5.3; pass `netns=` to pyroute2 or pin a worker; test-first |
|
| `to_thread` worker runs in the wrong netns | §5.3; pass `netns=` to pyroute2 or pin a worker; test-first |
|
||||||
| py-multiaddr#108 not merged | branch pin + `_have_wg_maddr_proto()` gate; layer A's inner-addr path works regardless |
|
| py-multiaddr#108 merged but unreleased | `[tool.uv.sources]` `rev` pin + `_have_wg_maddr_proto()` gate; layer A's inner-addr path works regardless |
|
||||||
| `TunnelledAddress` leaks into `Endpoint` and breaks `inspect.getmodule()` | unwrap at parse/bindspace boundary; assert `not isinstance(ep.addr, TunnelledAddress)` in `Endpoint.__post_init__` |
|
| `TunnelledAddress` leaks into `Endpoint` and breaks `inspect.getmodule()` | unwrap at parse/bindspace boundary; assert `not isinstance(ep.addr, TunnelledAddress)` in `Endpoint.__post_init__` |
|
||||||
| privileged ops in a library | never `sudo`; explicit cap probe + actionable error; pre-provisioned is the default |
|
| privileged ops in a library | never `sudo`; explicit cap probe + actionable error; pre-provisioned is the default |
|
||||||
| pyroute2 0.9 asyncio core drags a loop into the actor | option (1) is a *thread*, not a loop; forbid `trio-asyncio` here (§4.1) |
|
| pyroute2 0.9 asyncio core drags a loop into the actor | option (1) is a *thread*, not a loop; forbid `trio-asyncio` here (§4.1) |
|
||||||
|
|
|
||||||
|
|
@ -39,16 +39,27 @@ this composed form parses and round-trips
|
||||||
|
|
||||||
## requirements
|
## requirements
|
||||||
|
|
||||||
The `wg` proto isn't in released `py-multiaddr` yet (`0.2.0` has
|
py-multiaddr #108 is **merged** (2026-07-28) but ships in no
|
||||||
no `wg` codec), so until #108 lands:
|
release yet — the latest `0.2.0` (2026-03-17) predates it and has
|
||||||
|
no `wg` codec. So `pyproject.toml` carries a temporary
|
||||||
|
`[tool.uv.sources]` `rev` pin at the merge commit, and a plain
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv pip install 'git+https://github.com/baudco/py-multiaddr.git@wg_support' multibase
|
uv sync
|
||||||
```
|
```
|
||||||
|
|
||||||
`wg_maddr.py` degrades to a plain segment split when the codec is
|
gets you a `wg`-aware `multiaddr`. That pin goes away once a
|
||||||
absent, so the examples still run — but you lose per-segment
|
release carries the codec. You also need `multibase`:
|
||||||
validation. It deliberately does **not** hand-roll a `wg` codec
|
|
||||||
|
```bash
|
||||||
|
uv pip install multibase
|
||||||
|
```
|
||||||
|
|
||||||
|
Without the codec `wg_maddr.py` degrades to a plain segment split
|
||||||
|
— the examples still run, but you lose per-segment validation
|
||||||
|
(incl. the 32-byte key-length check), so a malformed key reaches
|
||||||
|
the returned struct instead of raising. `_have_wg_maddr_proto()`
|
||||||
|
is the gate. It deliberately does **not** hand-roll a `wg` codec
|
||||||
(gh #429 was about *dropping* our NIH parser).
|
(gh #429 was about *dropping* our NIH parser).
|
||||||
|
|
||||||
## 0. tunnel setup (out-of-band, both hosts)
|
## 0. tunnel setup (out-of-band, both hosts)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
r'''
|
r'''
|
||||||
Parse `wg`-tunnelled multiaddrs into `tractor`-ready addrs.
|
Parse `wg`-tunnelled multiaddrs into `tractor`-ready addrs.
|
||||||
|
|
||||||
The canonical form (per py-multiaddr PR #108, verified to parse +
|
The canonical form (per py-multiaddr #108, verified to parse +
|
||||||
round-trip on that branch) nests the *overlay* endpoint **after**
|
round-trip against its upstream merge) nests the *overlay*
|
||||||
the `/wg/` segment:
|
endpoint **after** the `/wg/` segment:
|
||||||
|
|
||||||
/ip4/10.0.0.1/udp/51820/wg/u<key>/ip4/10.0.11.1/tcp/1616
|
/ip4/10.0.0.1/udp/51820/wg/u<key>/ip4/10.0.11.1/tcp/1616
|
||||||
\_______ wg bearer ______/\_ key _/\____ tractor ep _____/
|
\_______ wg bearer ______/\_ key _/\____ tractor ep _____/
|
||||||
|
|
@ -111,8 +111,10 @@ def parse_wg_maddr(
|
||||||
Split a `wg`-tunnelled maddr into its bearer/key/overlay
|
Split a `wg`-tunnelled maddr into its bearer/key/overlay
|
||||||
parts. Pure — no I/O.
|
parts. Pure — no I/O.
|
||||||
|
|
||||||
Uses `py-multiaddr` when it knows the `wg` proto (PR #108),
|
Total-or-raises: with a `wg`-aware `py-multiaddr` (#108) an
|
||||||
else falls back to a minimal segment split.
|
unparseable maddr raises instead of yielding a struct built
|
||||||
|
from garbage segments. See `_segments()` for the degraded
|
||||||
|
pre-#108 path.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
segs: list[str] = _segments(maddr)
|
segs: list[str] = _segments(maddr)
|
||||||
|
|
@ -164,22 +166,53 @@ def parse_wg_maddr(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _segments(maddr: str) -> list[str]:
|
_wg_proto_known: bool|None = None
|
||||||
|
|
||||||
|
|
||||||
|
def _have_wg_maddr_proto() -> bool:
|
||||||
'''
|
'''
|
||||||
Deliver a maddr's `/`-split segments, preferring the real
|
True iff the installed `py-multiaddr` knows the `/wg/` proto,
|
||||||
parser when it supports `wg`.
|
i.e. carries py-multiaddr#108.
|
||||||
|
|
||||||
|
Merged upstream 2026-07-28 but in no release as of `0.2.0`,
|
||||||
|
hence the `[tool.uv.sources]` `rev` pin.
|
||||||
|
|
||||||
|
Pure predicate; result cached since it can't change without a
|
||||||
|
reinstall.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from multiaddr import Multiaddr
|
global _wg_proto_known
|
||||||
try:
|
if _wg_proto_known is None:
|
||||||
# the real thing: validates every proto + value
|
from multiaddr.protocols import protocol_with_name
|
||||||
|
from multiaddr.exceptions import ProtocolNotFoundError
|
||||||
|
try:
|
||||||
|
protocol_with_name('wg')
|
||||||
|
_wg_proto_known = True
|
||||||
|
except ProtocolNotFoundError:
|
||||||
|
_wg_proto_known = False
|
||||||
|
|
||||||
|
return _wg_proto_known
|
||||||
|
|
||||||
|
|
||||||
|
def _segments(maddr: str) -> list[str]:
|
||||||
|
'''
|
||||||
|
Deliver a maddr's `/`-split segments, validating via the real
|
||||||
|
parser whenever it knows `wg`.
|
||||||
|
|
||||||
|
'''
|
||||||
|
if _have_wg_maddr_proto():
|
||||||
|
from multiaddr import Multiaddr
|
||||||
|
# the real thing: validates every proto + value, incl.
|
||||||
|
# that the `wg` key decodes to exactly 32 bytes. Let it
|
||||||
|
# raise — a maddr that doesn't parse must NOT reach
|
||||||
|
# `wg8_pubkey()`, which would happily emit a corrupt key.
|
||||||
Multiaddr(maddr)
|
Multiaddr(maddr)
|
||||||
except Exception:
|
|
||||||
# XXX STOPGAP, only until py-multiaddr#108 lands; then
|
# XXX, degraded path for a pre-#108 `py-multiaddr` ONLY: no
|
||||||
# this branch is dead and `Multiaddr` is authoritative.
|
# per-segment validation, so a malformed key survives to the
|
||||||
# We deliberately DON'T hand-roll a `wg` codec (the whole
|
# returned struct. We deliberately DON'T hand-roll a `wg`
|
||||||
# point of gh #429 was dropping the NIH parser).
|
# codec (the whole point of gh #429 was dropping the NIH
|
||||||
pass
|
# parser) — install the pinned rev to get validation back.
|
||||||
return [s for s in maddr.split('/') if s]
|
return [s for s in maddr.split('/') if s]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,17 @@ sync_pause = {requires-python = ">=3.13, <3.14"}
|
||||||
# linux kernel networking
|
# linux kernel networking
|
||||||
# 'pyroute2
|
# 'pyroute2
|
||||||
|
|
||||||
|
# XXX TEMP, the `/wg/u<key>` maddr proto is MERGED upstream (in
|
||||||
|
# py-multiaddr#108, 2026-07-28) but is in NO release yet; the
|
||||||
|
# latest `0.2.0` (2026-03-17) predates the merge by ~4 months.
|
||||||
|
# Pinned by `rev` (not `branch`) so CI stays reproducible.
|
||||||
|
#
|
||||||
|
# Drop this pin (and bump the `multiaddr` dep floor above) the
|
||||||
|
# moment a release carries the `wg` codec; the only consumer is
|
||||||
|
# `examples/multihost/wg_lan/`.
|
||||||
|
# |_https://github.com/multiformats/py-multiaddr/pull/108
|
||||||
|
multiaddr = { git = 'https://github.com/multiformats/py-multiaddr.git', rev = 'f86519daaa21699023d0037c58cdff600313dd09' }
|
||||||
|
|
||||||
# ------ tool.uv.sources ------
|
# ------ tool.uv.sources ------
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
|
|
|
||||||
8
uv.lock
8
uv.lock
|
|
@ -518,7 +518,7 @@ wheels = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "multiaddr"
|
name = "multiaddr"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { git = "https://github.com/multiformats/py-multiaddr.git?rev=f86519daaa21699023d0037c58cdff600313dd09#f86519daaa21699023d0037c58cdff600313dd09" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "base58" },
|
{ name = "base58" },
|
||||||
{ name = "dnspython" },
|
{ name = "dnspython" },
|
||||||
|
|
@ -533,10 +533,6 @@ dependencies = [
|
||||||
{ name = "trio-typing" },
|
{ name = "trio-typing" },
|
||||||
{ name = "varint" },
|
{ name = "varint" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/10/4e26a8577cfce1c0febc8d83087e1373e93c695c6e73ad010546fb67e229/multiaddr-0.2.0.tar.gz", hash = "sha256:acb6b25c332ec1b2f1f8fef8d03a8c63385d34a87d690df0f4bba43cdf6efe8d", size = 58356, upload-time = "2026-03-17T21:51:00.274Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b5/13/56e503d01218d1ca27ea9fda862045a4b400cae5e756f47315f5aaba0eee/multiaddr-0.2.0-py3-none-any.whl", hash = "sha256:bcff7bf3d7de3d6da0b865b25423bcb411de1d20d70cc6abfacf75170d17866c", size = 40424, upload-time = "2026-03-17T21:50:58.833Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
|
|
@ -1178,7 +1174,7 @@ requires-dist = [
|
||||||
{ name = "bidict", specifier = ">=0.23.1" },
|
{ name = "bidict", specifier = ">=0.23.1" },
|
||||||
{ name = "colorlog", specifier = ">=6.8.2,<7" },
|
{ name = "colorlog", specifier = ">=6.8.2,<7" },
|
||||||
{ name = "msgspec", specifier = ">=0.20.0" },
|
{ name = "msgspec", specifier = ">=0.20.0" },
|
||||||
{ name = "multiaddr", specifier = ">=0.2.0" },
|
{ name = "multiaddr", git = "https://github.com/multiformats/py-multiaddr.git?rev=f86519daaa21699023d0037c58cdff600313dd09" },
|
||||||
{ name = "pdbp", specifier = ">=1.8.2,<2" },
|
{ name = "pdbp", specifier = ">=1.8.2,<2" },
|
||||||
{ name = "platformdirs", specifier = ">=4.4.0" },
|
{ name = "platformdirs", specifier = ">=4.4.0" },
|
||||||
{ name = "setproctitle", specifier = ">=1.3,<2" },
|
{ name = "setproctitle", specifier = ">=1.3,<2" },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue