Use upstream `py-multiaddr` for `._multiaddr`
Drop the NIH (notinventedhere) custom parser (`parse_maddr()`,
`iter_prot_layers()`, `prots`/`prot_params` tables) which was never
called anywhere in the codebase.
Replace with a thin `mk_maddr()` factory that wraps the upstream
`multiaddr.Multiaddr` type, dispatching on `Address.proto_key` to build
spec-compliant paths.
Deats,
- `'tcp'` addrs detect ipv4 vs ipv6 via stdlib
`ipaddress` (resolves existing TODO)
- `'uds'` addrs map to `/unix/{path}` per the
multiformats protocol registry (code 400)
- fix UDS `.maddr` to include full sockpath
(previously only used `filedir`, dropped filename)
- standardize protocol names: `ipv4`->`ip4`,
`uds`->`unix`
- `.maddr` properties now return `Multiaddr` objs
(`__str__()` gives the canonical path form so all
existing f-string/log consumers work unchanged)
- update `MsgTransport` protocol hint accordingly
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
subint_spawner_backend
parent
d9cb38372f
commit
926e861f52
|
|
@ -15,145 +15,61 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Multiaddress parser and utils according the spec(s) defined by
|
Multiaddress support using the upstream `py-multiaddr` lib
|
||||||
`libp2p` and used in dependent project such as `ipfs`:
|
(a `libp2p` community standard) instead of our own NIH parser.
|
||||||
|
|
||||||
- https://docs.libp2p.io/concepts/fundamentals/addressing/
|
- https://github.com/multiformats/multiaddr
|
||||||
- https://github.com/libp2p/specs/blob/master/addressing/README.md
|
- https://github.com/multiformats/py-multiaddr
|
||||||
|
- https://github.com/multiformats/multiaddr/blob/master/protocols.csv
|
||||||
|
- https://github.com/multiformats/multiaddr/blob/master/protocols/unix.md
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from typing import Iterator
|
import ipaddress
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from bidict import bidict
|
from multiaddr import Multiaddr
|
||||||
|
|
||||||
# TODO: see if we can leverage libp2p ecosys projects instead of
|
if TYPE_CHECKING:
|
||||||
# rolling our own (parser) impls of the above addressing specs:
|
from tractor.discovery._addr import Address
|
||||||
# - https://github.com/libp2p/py-libp2p
|
|
||||||
# - https://docs.libp2p.io/concepts/nat/circuit-relay/#relay-addresses
|
|
||||||
# prots: bidict[int, str] = bidict({
|
|
||||||
prots: bidict[int, str] = {
|
|
||||||
'ipv4': 3,
|
|
||||||
'ipv6': 3,
|
|
||||||
'wg': 3,
|
|
||||||
|
|
||||||
'tcp': 4,
|
# map from tractor-internal `proto_key` identifiers
|
||||||
'udp': 4,
|
# to the standard multiaddr protocol name strings.
|
||||||
'uds': 4,
|
_tpt_proto_to_maddr: dict[str, str] = {
|
||||||
|
'tcp': 'tcp',
|
||||||
# TODO: support the next-gen shite Bo
|
'uds': 'unix',
|
||||||
# 'quic': 4,
|
|
||||||
# 'ssh': 7, # via rsyscall bootstrapping
|
|
||||||
}
|
|
||||||
|
|
||||||
prot_params: dict[str, tuple[str]] = {
|
|
||||||
'ipv4': ('addr',),
|
|
||||||
'ipv6': ('addr',),
|
|
||||||
'wg': ('addr', 'port', 'pubkey'),
|
|
||||||
|
|
||||||
'tcp': ('port',),
|
|
||||||
'udp': ('port',),
|
|
||||||
'uds': ('path',),
|
|
||||||
|
|
||||||
# 'quic': ('port',),
|
|
||||||
# 'ssh': ('port',),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def iter_prot_layers(
|
def mk_maddr(
|
||||||
multiaddr: str,
|
addr: 'Address',
|
||||||
) -> Iterator[
|
) -> Multiaddr:
|
||||||
tuple[
|
|
||||||
int,
|
|
||||||
list[str]
|
|
||||||
]
|
|
||||||
]:
|
|
||||||
'''
|
'''
|
||||||
Unpack a libp2p style "multiaddress" into multiple "segments"
|
Construct a `Multiaddr` from a tractor `Address` instance,
|
||||||
for each "layer" of the protocoll stack (in OSI terms).
|
dispatching on the `.proto_key` to build the correct
|
||||||
|
multiaddr-spec-compliant protocol path.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
tokens: list[str] = multiaddr.split('/')
|
match addr.proto_key:
|
||||||
root, tokens = tokens[0], tokens[1:]
|
case 'tcp':
|
||||||
assert not root # there is a root '/' on LHS
|
host, port = addr.unwrap()
|
||||||
itokens = iter(tokens)
|
ip = ipaddress.ip_address(host)
|
||||||
|
net_proto: str = (
|
||||||
|
'ip4' if ip.version == 4
|
||||||
|
else 'ip6'
|
||||||
|
)
|
||||||
|
return Multiaddr(
|
||||||
|
f'/{net_proto}/{host}/tcp/{port}'
|
||||||
|
)
|
||||||
|
|
||||||
prot: str|None = None
|
case 'uds':
|
||||||
params: list[str] = []
|
filedir, filename = addr.unwrap()
|
||||||
for token in itokens:
|
filepath = Path(filedir) / filename
|
||||||
# every prot path should start with a known
|
return Multiaddr(
|
||||||
# key-str.
|
f'/unix/{filepath}'
|
||||||
if token in prots:
|
)
|
||||||
if prot is None:
|
|
||||||
prot: str = token
|
|
||||||
else:
|
|
||||||
yield prot, params
|
|
||||||
prot = token
|
|
||||||
|
|
||||||
params = []
|
case _:
|
||||||
|
raise ValueError(
|
||||||
elif token not in prots:
|
f'Unsupported proto_key: {addr.proto_key!r}'
|
||||||
params.append(token)
|
)
|
||||||
|
|
||||||
else:
|
|
||||||
yield prot, params
|
|
||||||
|
|
||||||
|
|
||||||
def parse_maddr(
|
|
||||||
multiaddr: str,
|
|
||||||
) -> dict[
|
|
||||||
str,
|
|
||||||
str|int|dict,
|
|
||||||
]:
|
|
||||||
'''
|
|
||||||
Parse a libp2p style "multiaddress" into its distinct protocol
|
|
||||||
segments where each segment is of the form:
|
|
||||||
|
|
||||||
`../<protocol>/<param0>/<param1>/../<paramN>`
|
|
||||||
|
|
||||||
and is loaded into a (order preserving) `layers: dict[str,
|
|
||||||
dict[str, Any]` which holds each protocol-layer-segment of the
|
|
||||||
original `str` path as a separate entry according to its approx
|
|
||||||
OSI "layer number".
|
|
||||||
|
|
||||||
Any `paramN` in the path must be distinctly defined by a str-token in the
|
|
||||||
(module global) `prot_params` table.
|
|
||||||
|
|
||||||
For eg. for wireguard which requires an address, port number and publickey
|
|
||||||
the protocol params are specified as the entry:
|
|
||||||
|
|
||||||
'wg': ('addr', 'port', 'pubkey'),
|
|
||||||
|
|
||||||
and are thus parsed from a maddr in that order:
|
|
||||||
`'/wg/1.1.1.1/51820/<pubkey>'`
|
|
||||||
|
|
||||||
'''
|
|
||||||
layers: dict[str, str|int|dict] = {}
|
|
||||||
for (
|
|
||||||
prot_key,
|
|
||||||
params,
|
|
||||||
) in iter_prot_layers(multiaddr):
|
|
||||||
|
|
||||||
layer: int = prots[prot_key] # OSI layer used for sorting
|
|
||||||
ep: dict[str, int|str] = {
|
|
||||||
'layer': layer,
|
|
||||||
'proto': prot_key,
|
|
||||||
}
|
|
||||||
layers[prot_key] = ep
|
|
||||||
|
|
||||||
# TODO; validation and resolving of names:
|
|
||||||
# - each param via a validator provided as part of the
|
|
||||||
# prot_params def? (also see `"port"` case below..)
|
|
||||||
# - do a resolv step that will check addrs against
|
|
||||||
# any loaded network.resolv: dict[str, str]
|
|
||||||
rparams: list = list(reversed(params))
|
|
||||||
for key in prot_params[prot_key]:
|
|
||||||
val: str|int = rparams.pop()
|
|
||||||
|
|
||||||
# TODO: UGHH, dunno what we should do for validation
|
|
||||||
# here, put it in the params spec somehow?
|
|
||||||
if key == 'port':
|
|
||||||
val = int(val)
|
|
||||||
|
|
||||||
ep[key] = val
|
|
||||||
|
|
||||||
return layers
|
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,10 @@ from trio import (
|
||||||
open_tcp_listeners,
|
open_tcp_listeners,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from multiaddr import Multiaddr
|
||||||
from tractor.msg import MsgCodec
|
from tractor.msg import MsgCodec
|
||||||
from tractor.log import get_logger
|
from tractor.log import get_logger
|
||||||
|
from tractor.discovery._multiaddr import mk_maddr
|
||||||
from tractor.ipc._transport import (
|
from tractor.ipc._transport import (
|
||||||
MsgTransport,
|
MsgTransport,
|
||||||
MsgpackTransport,
|
MsgpackTransport,
|
||||||
|
|
@ -198,21 +200,8 @@ class MsgpackTCPStream(MsgpackTransport):
|
||||||
layer_key: int = 4
|
layer_key: int = 4
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def maddr(self) -> str:
|
def maddr(self) -> Multiaddr:
|
||||||
host, port = self.raddr.unwrap()
|
return mk_maddr(self.raddr)
|
||||||
return (
|
|
||||||
# TODO, use `ipaddress` from stdlib to handle
|
|
||||||
# first detecting which of `ipv4/6` before
|
|
||||||
# choosing the routing prefix part.
|
|
||||||
f'/ipv4/{host}'
|
|
||||||
|
|
||||||
f'/{self.address_type.proto_key}/{port}'
|
|
||||||
# f'/{self.chan.uid[0]}'
|
|
||||||
# f'/{self.cid}'
|
|
||||||
|
|
||||||
# f'/cid={cid_head}..{cid_tail}'
|
|
||||||
# TODO: ? not use this ^ right ?
|
|
||||||
)
|
|
||||||
|
|
||||||
def connected(self) -> bool:
|
def connected(self) -> bool:
|
||||||
return self.stream.socket.fileno() != -1
|
return self.stream.socket.fileno() != -1
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ from typing import (
|
||||||
ClassVar,
|
ClassVar,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from multiaddr import Multiaddr
|
||||||
from collections.abc import (
|
from collections.abc import (
|
||||||
AsyncGenerator,
|
AsyncGenerator,
|
||||||
AsyncIterator,
|
AsyncIterator,
|
||||||
|
|
@ -118,7 +120,7 @@ class MsgTransport(Protocol):
|
||||||
...
|
...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def maddr(self) -> str:
|
def maddr(self) -> Multiaddr|str:
|
||||||
...
|
...
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,10 @@ from trio._highlevel_open_unix_stream import (
|
||||||
has_unix,
|
has_unix,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from multiaddr import Multiaddr
|
||||||
from tractor.msg import MsgCodec
|
from tractor.msg import MsgCodec
|
||||||
from tractor.log import get_logger
|
from tractor.log import get_logger
|
||||||
|
from tractor.discovery._multiaddr import mk_maddr
|
||||||
from tractor.ipc._transport import (
|
from tractor.ipc._transport import (
|
||||||
MsgpackTransport,
|
MsgpackTransport,
|
||||||
)
|
)
|
||||||
|
|
@ -442,19 +444,11 @@ class MsgpackUDSStream(MsgpackTransport):
|
||||||
layer_key: int = 4
|
layer_key: int = 4
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def maddr(self) -> str:
|
def maddr(self) -> Multiaddr|str:
|
||||||
if not self.raddr:
|
if not self.raddr:
|
||||||
return '<unknown-peer>'
|
return '<unknown-peer>'
|
||||||
|
|
||||||
filepath: Path = Path(self.raddr.unwrap()[0])
|
return mk_maddr(self.raddr)
|
||||||
return (
|
|
||||||
f'/{self.address_type.proto_key}/{filepath}'
|
|
||||||
# f'/{self.chan.uid[0]}'
|
|
||||||
# f'/{self.cid}'
|
|
||||||
|
|
||||||
# f'/cid={cid_head}..{cid_tail}'
|
|
||||||
# TODO: ? not use this ^ right ?
|
|
||||||
)
|
|
||||||
|
|
||||||
def connected(self) -> bool:
|
def connected(self) -> bool:
|
||||||
return self.stream.socket.fileno() != -1
|
return self.stream.socket.fileno() != -1
|
||||||
|
|
|
||||||
|
|
@ -1379,7 +1379,7 @@ class Actor:
|
||||||
# - `Channel.maddr() -> str:` obvi!
|
# - `Channel.maddr() -> str:` obvi!
|
||||||
# - `Context.maddr() -> str:`
|
# - `Context.maddr() -> str:`
|
||||||
tasks_str += (
|
tasks_str += (
|
||||||
f' |_@ /ipv4/tcp/cid="{ctx.cid[-16:]} .."\n'
|
f' |_@ /ip4/tcp/cid="{ctx.cid[-16:]} .."\n'
|
||||||
f' |>> {ctx._nsf}() -> dict:\n'
|
f' |>> {ctx._nsf}() -> dict:\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue