Add `Address.rebind_from_sockname` opt-out
Gate `Endpoint.start_listener()`s `getsockname()`-vs-`.addr` reconciliation on a new per-addr-type `ClassVar[bool]`, set `True` on both `TCPAddress` and `UDSAddress` so existing behaviour is bit-for-bit unchanged. That reconciliation exists ONLY to learn a kernel-assigned port from a `port=0` tcp bind (its own comment says so). The incoming `tipc` backend (gh #378) has no late-binding analogue AND its `getsockname()` answers a `TIPC_ADDR_ID` port-id rather than the name-seq it published — rebinding from that would swap a dialable service name for an un-dialable, un-reconstructable port id. So opting out is semantically right rather than a hack. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
parent
4817819f1c
commit
d04f480194
|
|
@ -33,6 +33,7 @@ from ..runtime._state import (
|
||||||
)
|
)
|
||||||
from ..ipc._tcp import TCPAddress
|
from ..ipc._tcp import TCPAddress
|
||||||
from ..ipc._uds import UDSAddress
|
from ..ipc._uds import UDSAddress
|
||||||
|
from ..ipc._tipc import TIPCAddress
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..runtime._runtime import Actor
|
from ..runtime._runtime import Actor
|
||||||
|
|
@ -65,9 +66,22 @@ log = get_logger()
|
||||||
#
|
#
|
||||||
UnwrappedAddress = (
|
UnwrappedAddress = (
|
||||||
# tcp/udp/uds
|
# tcp/udp/uds
|
||||||
|
# ('127.0.0.1', 1616)
|
||||||
|
# ('/run/user/1000/tractor', 'registry@1616.sock')
|
||||||
|
#
|
||||||
|
# ..and the explicitly proto-keyed (`multiaddr`-spelled)
|
||||||
|
# form, which is where ALL backends should eventually land
|
||||||
|
# per the note below,
|
||||||
|
# ('tipc', 1953628160, 1616, 2)
|
||||||
|
#
|
||||||
|
# XXX VARIADIC bc `msgspec` refuses a union of >1 array-like
|
||||||
|
# type, so the two shapes can't be spelled as a union. Keep
|
||||||
|
# in sync with `.msg.types.UnwrappedAddress` which
|
||||||
|
# re-declares this to dodge a circular import AND is what
|
||||||
|
# actually validates the `SpawnSpec` wire msg!
|
||||||
tuple[
|
tuple[
|
||||||
str, # host/domain(tcp), filesys-dir(uds)
|
str|int,
|
||||||
int|str, # port/path(uds)
|
...,
|
||||||
]
|
]
|
||||||
# ?TODO? should we also include another 2 fields from
|
# ?TODO? should we also include another 2 fields from
|
||||||
# our `Aid` msg such that we include the runtime `Actor.uid`
|
# our `Aid` msg such that we include the runtime `Actor.uid`
|
||||||
|
|
@ -83,6 +97,17 @@ class Address(Protocol):
|
||||||
proto_key: ClassVar[str]
|
proto_key: ClassVar[str]
|
||||||
unwrapped_type: ClassVar[UnwrappedAddress]
|
unwrapped_type: ClassVar[UnwrappedAddress]
|
||||||
|
|
||||||
|
# whether `.ipc._server.Endpoint.start_listener()` should
|
||||||
|
# reconcile a bound `.addr` against its listener's
|
||||||
|
# `socket.getsockname()`.
|
||||||
|
#
|
||||||
|
# XXX NOTE, that reconciliation exists ONLY to learn the
|
||||||
|
# kernel-*assigned* port from a `port=0` tcp bind; a backend
|
||||||
|
# whose `getsockname()` reports a categorically different thing
|
||||||
|
# than what was `.bind()`ed must opt out with `False`, else the
|
||||||
|
# ep's addr gets clobbered by an un-dialable one.
|
||||||
|
rebind_from_sockname: ClassVar[bool]
|
||||||
|
|
||||||
# TODO, i feel like an `.is_bound()` is a better thing to
|
# TODO, i feel like an `.is_bound()` is a better thing to
|
||||||
# support?
|
# support?
|
||||||
# Lke, what use does this have besides a noop and if it's not
|
# Lke, what use does this have besides a noop and if it's not
|
||||||
|
|
@ -172,7 +197,8 @@ class Address(Protocol):
|
||||||
|
|
||||||
_address_types: bidict[str, Type[Address]] = {
|
_address_types: bidict[str, Type[Address]] = {
|
||||||
'tcp': TCPAddress,
|
'tcp': TCPAddress,
|
||||||
'uds': UDSAddress
|
'uds': UDSAddress,
|
||||||
|
'tipc': TIPCAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -184,6 +210,9 @@ _default_lo_addrs: dict[
|
||||||
] = {
|
] = {
|
||||||
'tcp': TCPAddress.get_root().unwrap(),
|
'tcp': TCPAddress.get_root().unwrap(),
|
||||||
'uds': UDSAddress.get_root().unwrap(),
|
'uds': UDSAddress.get_root().unwrap(),
|
||||||
|
# NOTE, pure/cheap: a service-name pair, no kernel module
|
||||||
|
# nor I/O required at import time.
|
||||||
|
'tipc': TIPCAddress.get_root().unwrap(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -229,6 +258,16 @@ def wrap_address(
|
||||||
# import pdbp; pdbp.set_trace()
|
# import pdbp; pdbp.set_trace()
|
||||||
match addr:
|
match addr:
|
||||||
|
|
||||||
|
# XXX, the explicitly proto-keyed form (spelled with the
|
||||||
|
# `multiaddr` proto name) which is where ALL backends
|
||||||
|
# should eventually land per the `UnwrappedAddress`
|
||||||
|
# migration note above.
|
||||||
|
#
|
||||||
|
# NOTE, a bare seq-pattern matches `list` too, which is
|
||||||
|
# what `msgpack` decodes our tuples back to.
|
||||||
|
case ('tipc', *_):
|
||||||
|
cls = TIPCAddress
|
||||||
|
|
||||||
# classic network socket-address as tuple/list
|
# classic network socket-address as tuple/list
|
||||||
case (
|
case (
|
||||||
(str(), int())
|
(str(), int())
|
||||||
|
|
|
||||||
|
|
@ -661,7 +661,16 @@ class Endpoint(Struct):
|
||||||
|
|
||||||
# NOTE, for handling the resolved non-0 port for
|
# NOTE, for handling the resolved non-0 port for
|
||||||
# TCP/UDP network sockets.
|
# TCP/UDP network sockets.
|
||||||
|
#
|
||||||
|
# XXX, gated on the addr-type's opt-in since for some
|
||||||
|
# backends `getsockname()` does NOT answer "the addr you
|
||||||
|
# bound"; `tipc` reports a `TIPC_ADDR_ID` port-id instead
|
||||||
|
# of the published name-seq, so rebinding from it would
|
||||||
|
# replace a dialable service-name with an un-dialable
|
||||||
|
# (and un-reconstructable) port-id.
|
||||||
if (
|
if (
|
||||||
|
self.addr.rebind_from_sockname
|
||||||
|
and
|
||||||
(unwrapped := lstnr.socket.getsockname())
|
(unwrapped := lstnr.socket.getsockname())
|
||||||
!=
|
!=
|
||||||
self.addr.unwrap()
|
self.addr.unwrap()
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ class TCPAddress(
|
||||||
unwrapped_type: ClassVar[type] = tuple[str, int]
|
unwrapped_type: ClassVar[type] = tuple[str, int]
|
||||||
def_bindspace: ClassVar[str] = '127.0.0.1'
|
def_bindspace: ClassVar[str] = '127.0.0.1'
|
||||||
|
|
||||||
|
# XXX, REQUIRED here since a `port=0` bind means the kernel
|
||||||
|
# picks and `getsockname()` is the only way we learn it.
|
||||||
|
rebind_from_sockname: ClassVar[bool] = True
|
||||||
|
|
||||||
# ?TODO, actually validate ipv4/6 with stdlib's `ipaddress`
|
# ?TODO, actually validate ipv4/6 with stdlib's `ipaddress`
|
||||||
@property
|
@property
|
||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,12 @@ class UDSAddress(
|
||||||
unwrapped_type: ClassVar[type] = tuple[str, int]
|
unwrapped_type: ClassVar[type] = tuple[str, int]
|
||||||
def_bindspace: ClassVar[Path] = get_rt_dir()
|
def_bindspace: ClassVar[Path] = get_rt_dir()
|
||||||
|
|
||||||
|
# NOTE, `getsockname()` answers the sock-file path as a `str`
|
||||||
|
# which never `==` our 2-tuple `.unwrap()`, so the round-trip
|
||||||
|
# always fires; it's a no-op modulo `.maybe_pid` and is kept
|
||||||
|
# `True` to preserve pre-existing behaviour exactly.
|
||||||
|
rebind_from_sockname: ClassVar[bool] = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bindspace(self) -> Path:
|
def bindspace(self) -> Path:
|
||||||
'''
|
'''
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue