Enter inherited netns during child bootstrap

Consume an optional child-owned `(fd, inode)` capability before
multiprocessing or Trio child bootstrap begins runtime setup.

Deats,
- enter the netns before Trio patching and actor construction
- close the inherited FD before actor runtime startup
- preserve entry errors when descriptor cleanup also fails
- reject malformed FD values without closing unrelated descriptors
- cover multiprocessing and Trio ordering with real stand-in FDs

Prompt-IO: ai/prompt-io/opencode/20260828T012205Z_9ae7cd86_prompt_io.md

(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
wkt/wg_pyroute2_read
Gud Boi 2026-08-27 23:31:03 -04:00
parent 503a3ed766
commit 6d5b145787
5 changed files with 580 additions and 3 deletions

View File

@ -0,0 +1,38 @@
---
model: gpt-5.6-sol
service: opencode
session: wg-netns-bootstrap-entry
timestamp: 2026-08-28T01:22:05Z
git_ref: 9ae7cd86
scope: code
substantive: true
raw_file: 20260828T012205Z_9ae7cd86_prompt_io.raw.md
---
## Prompt
After completing and auditing the stacked rebase, the human approved
the next WireGuard Layer C slice: integrate the verified network
namespace entry primitive into child spawn bootstrap.
## Response summary
Wire an optional child-owned namespace FD and expected inode into both
child entrypoints. Keep namespace entry and FD closure synchronous and
ordered before actor runtime initialization, while deliberately
deferring backend FD transport and public API plumbing.
## Files changed
- `tractor/spawn/_entry.py` - enter and release an optional inherited
namespace capability before multiprocessing child runtime startup.
- `tractor/_child.py` - consume the capability at the start of the
Trio child bootloader, before patching or actor construction.
- `tests/test_netns_spawn.py` - prove entry ordering, failure isolation,
and exact FD ownership for both child entrypoints.
## Human edits
The human selected spawn-time namespace integration as the next work
slice and authorized implementation. No direct manual source edits
were observed during generation.

View File

@ -0,0 +1,36 @@
---
model: gpt-5.6-sol
service: opencode
timestamp: 2026-08-28T01:22:05Z
git_ref: 9ae7cd86
diff_cmd: git diff HEAD~1..HEAD
---
# Raw output - enter inherited netns during child bootstrap
The human asked to continue the WireGuard Layer C implementation after
the rebased branch landed the verified `spawn._netns.enter_netns()`
primitive.
## Generated code
> `git diff HEAD~1..HEAD -- tractor/_child.py tractor/spawn/_entry.py tests/test_netns_spawn.py`
Add one optional atomic `(namespace_fd, expected_inode)` bootstrap
capability to the multiprocessing entrypoint and Trio child bootloader.
Enter and verify the namespace before Trio patching, actor construction,
runtime state, logging, Trio startup, parent connection, sockets, or
worker threads, then close the child-owned inherited FD before
continuing.
Add privilege-free direct-entrypoint tests using real stand-in FDs and
a fake namespace syscall boundary. Cover helper-level no-op behavior,
successful entry ordering, exact FD closure, malformed-FD isolation,
primary-error preservation, and failure cleanup for both child
bootstrap paths.
## Scope boundary
This increment does not transfer FDs through spawn backends or expose a
public actor API. Backend-specific FD duplication, bootstrap failure
reporting, and root-process namespace entry remain follow-up work.

View File

@ -4,13 +4,37 @@ Pre-runtime Linux network-namespace entry validation.
'''
from __future__ import annotations
import errno
from functools import partial
import os
from pathlib import Path
from types import SimpleNamespace
from typing import BinaryIO
from typing import (
Any,
BinaryIO,
)
import pytest
from tractor.spawn import _netns
from tractor import _child
from tractor.devx import _proctitle
from tractor.spawn import (
_entry,
_netns,
_spawn,
)
from tractor.trionics import patches
def _assert_fd_closed(namespace_fd: int) -> None:
'''
Assert that bootstrap consumed its child-owned descriptor.
'''
with pytest.raises(OSError) as exc_info:
os.fstat(namespace_fd)
assert exc_info.value.errno == errno.EBADF
def test_enter_netns_rejects_mismatched_inherited_fd(
@ -140,3 +164,416 @@ def test_enter_netns_rejects_wrong_post_entry_namespace(
# Deliberately differ from `fake_stat()`'s inode + 1.
inode,
)
def test_empty_netns_bootstrap_is_a_noop(
monkeypatch: pytest.MonkeyPatch,
) -> None:
'''
Ordinary child startup must not attempt namespace entry.
Leave the optional capability unset and arm `enter_netns()` as a
failure sentinel. The bootstrap boundary must return without any
syscall or descriptor ownership work for existing spawn callers.
'''
def fail_enter_netns(namespace_fd: int, inode: int) -> int:
'''
Reject namespace entry without an explicit capability.
'''
raise AssertionError('empty bootstrap attempted netns entry')
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
assert _entry._consume_netns_bootstrap(None) is None
@pytest.mark.parametrize('namespace_fd', (-1, True, '1'))
def test_invalid_netns_fd_is_never_closed(
namespace_fd: object,
monkeypatch: pytest.MonkeyPatch,
) -> None:
'''
Invalid descriptor values must not reach the OS close boundary.
Feed negative, boolean, and non-integer values through the atomic
capability. Preserve the namespace primitive's validation error
without letting `bool` alias stdout or allowing cleanup to mask the
primary failure.
'''
entry_error = ValueError('invalid netns capability')
def fail_enter_netns(namespace_fd: int, inode: int) -> int:
'''
Raise the primary namespace bootstrap error.
'''
raise entry_error
def fail_close(inherited_fd: int) -> None:
'''
Reject cleanup for a value that cannot be an owned FD.
'''
raise AssertionError('invalid namespace FD reached close')
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
monkeypatch.setattr(
_entry,
'os',
SimpleNamespace(close=fail_close),
)
with pytest.raises(ValueError) as exc_info:
_entry._consume_netns_bootstrap(
(namespace_fd, 1), # type: ignore[arg-type]
)
assert exc_info.value is entry_error
def test_netns_entry_error_survives_close_failure(
monkeypatch: pytest.MonkeyPatch,
) -> None:
'''
Descriptor cleanup must not mask the primary bootstrap failure.
Raise a unique entry error for an oversized positive integer whose
cleanup also raises `OverflowError`. The entry error must escape
with cleanup context attached instead of being replaced by the
close failure.
'''
namespace_fd: int = 1 << 100
entry_error = ValueError('invalid netns capability')
def fail_enter_netns(inherited_fd: int, inode: int) -> int:
'''
Raise the primary namespace bootstrap error.
'''
assert inherited_fd == namespace_fd
raise entry_error
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
with pytest.raises(ValueError) as exc_info:
_entry._consume_netns_bootstrap((namespace_fd, 1))
assert exc_info.value is entry_error
assert entry_error.__notes__
assert 'close inherited namespace FD' in entry_error.__notes__[0]
assert 'OverflowError' in entry_error.__notes__[0]
@pytest.mark.parametrize('backend', ('mp', 'trio'))
def test_child_entry_consumes_netns_before_runtime(
backend: str,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
'''
Child bootstrap must enter its netns before runtime side effects.
Give each child entrypoint an exclusively owned stand-in FD. Fake
only namespace entry and every later bootstrap boundary, requiring
the FD to remain open during entry but be closed before actor state,
logging, multiprocessing setup, frame hiding, or `trio.run()`.
This proves verified entry and capability release are one
synchronous prefix of both child startup paths.
'''
token_path: Path = tmp_path / f'{backend}-netns'
token_path.touch()
namespace_fd: int = os.open(token_path, os.O_RDONLY)
expected_inode: int = os.fstat(namespace_fd).st_ino
events: list[str] = []
def fake_enter_netns(
inherited_fd: int,
inode: int,
) -> int:
'''
Record verified entry while the capability remains open.
'''
assert os.fstat(inherited_fd).st_ino == expected_inode
assert inherited_fd == namespace_fd
assert inode == expected_inode
events.append('enter-netns')
return inode
def record(
event: str,
*args: object,
**kwargs: object,
) -> None:
'''
Record one post-entry operation after proving FD release.
'''
_assert_fd_closed(namespace_fd)
events.append(event)
class ActorSpy:
'''
Record multiprocessing actor-state initialization.
'''
loglevel = None
uid = ('netns-child', 'test')
_infected_aio = False
def __setattr__(
self,
name: str,
value: object,
) -> None:
'''
Observe the first multiprocessing entrypoint mutation.
'''
if name == '_forkserver_info':
record('forkserver-info')
object.__setattr__(self, name, value)
class StateSpy:
'''
Record actor publication into runtime-global state.
'''
def __setattr__(
self,
name: str,
value: object,
) -> None:
'''
Observe `_state._current_actor` publication.
'''
record('runtime-state')
object.__setattr__(self, name, value)
def fake_current_process() -> str:
'''
Return one display value for multiprocessing startup logging.
'''
return 'fake-child-process'
def fake_start_method(start_method: str) -> SimpleNamespace:
'''
Record multiprocessing setup after namespace entry.
'''
record('start-method')
return SimpleNamespace(
current_process=fake_current_process,
)
def fake_actor(**kwargs: object) -> ActorSpy:
'''
Record Trio child actor construction after namespace entry.
'''
record('actor-construction')
return ActorSpy()
monkeypatch.setattr(_entry, 'enter_netns', fake_enter_netns)
monkeypatch.setattr(_entry, '_state', StateSpy())
monkeypatch.setattr(
_entry._frame_stack,
'hide_runtime_frames',
partial(record, 'hide-frames'),
)
monkeypatch.setattr(
_entry.trio,
'run',
partial(record, 'trio-run'),
)
monkeypatch.setattr(
_entry,
'log',
SimpleNamespace(
info=partial(record, 'log'),
cancel=partial(record, 'log'),
error=partial(record, 'log'),
),
)
monkeypatch.setattr(
_spawn,
'try_set_start_method',
fake_start_method,
)
monkeypatch.setattr(
patches,
'apply_all',
partial(record, 'trio-patches'),
)
monkeypatch.setattr(_child, 'Actor', fake_actor)
monkeypatch.setattr(
_proctitle,
'set_actor_proctitle',
partial(record, 'proctitle'),
)
monkeypatch.setattr(
_child,
'_trio_main',
partial(record, 'trio-main'),
)
actor: Any = ActorSpy()
bootstrap: tuple[int, int] = (
namespace_fd,
expected_inode,
)
if backend == 'mp':
_entry._mp_main(
actor,
[],
(None, None, None, None, None),
'mp_spawn',
netns_bootstrap=bootstrap,
)
first_runtime_event: str = 'forkserver-info'
terminal_event: str = 'trio-run'
else:
_child._actor_child_main(
uid=actor.uid,
loglevel=actor.loglevel,
parent_addr=None,
infect_asyncio=False,
netns_bootstrap=bootstrap,
)
first_runtime_event = 'trio-patches'
terminal_event = 'trio-main'
assert events[:2] == [
'enter-netns',
first_runtime_event,
]
assert events.count(terminal_event) == 1
_assert_fd_closed(namespace_fd)
@pytest.mark.parametrize('backend', ('mp', 'trio'))
def test_child_entry_failure_closes_netns_fd_before_runtime(
backend: str,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
'''
Failed namespace entry must close its FD and abort child startup.
Raise a unique error from the namespace boundary while a real
stand-in FD is open. Arm each entrypoint's first later operation as
a failure sentinel, then prove the original error escapes, the
descriptor is closed, and no actor, multiprocessing, frame, or
Trio runtime initialization begins.
'''
token_path: Path = tmp_path / f'{backend}-failed-netns'
token_path.touch()
namespace_fd: int = os.open(token_path, os.O_RDONLY)
expected_inode: int = os.fstat(namespace_fd).st_ino
entry_error = RuntimeError('netns entry failed')
events: list[str] = []
def fail_enter_netns(
inherited_fd: int,
inode: int,
) -> int:
'''
Fail entry while proving the child-owned FD is still open.
'''
assert os.fstat(inherited_fd).st_ino == expected_inode
assert inherited_fd == namespace_fd
assert inode == expected_inode
events.append('enter-netns')
raise entry_error
def fail_after_entry(*args: object, **kwargs: object) -> None:
'''
Reject any runtime operation after failed namespace entry.
'''
raise AssertionError('child runtime started after netns failure')
class ActorSpy:
'''
Reject multiprocessing actor-state initialization.
'''
loglevel = None
uid = ('failed-netns-child', 'test')
def __setattr__(
self,
name: str,
value: object,
) -> None:
'''
Reject the first multiprocessing entrypoint mutation.
'''
if name == '_forkserver_info':
fail_after_entry()
object.__setattr__(self, name, value)
monkeypatch.setattr(_entry, 'enter_netns', fail_enter_netns)
monkeypatch.setattr(
_entry._frame_stack,
'hide_runtime_frames',
fail_after_entry,
)
monkeypatch.setattr(
_spawn,
'try_set_start_method',
fail_after_entry,
)
monkeypatch.setattr(
patches,
'apply_all',
fail_after_entry,
)
monkeypatch.setattr(_child, 'Actor', fail_after_entry)
monkeypatch.setattr(
_proctitle,
'set_actor_proctitle',
fail_after_entry,
)
monkeypatch.setattr(
_child,
'_trio_main',
fail_after_entry,
)
actor: Any = ActorSpy()
bootstrap: tuple[int, int] = (
namespace_fd,
expected_inode,
)
with pytest.raises(RuntimeError) as exc_info:
if backend == 'mp':
_entry._mp_main(
actor,
[],
(None, None, None, None, None),
'mp_spawn',
netns_bootstrap=bootstrap,
)
else:
_child._actor_child_main(
uid=actor.uid,
loglevel=actor.loglevel,
parent_addr=None,
infect_asyncio=False,
netns_bootstrap=bootstrap,
)
assert exc_info.value is entry_error
assert events == ['enter-netns']
_assert_fd_closed(namespace_fd)

View File

@ -26,7 +26,10 @@ from ast import literal_eval
from typing import TYPE_CHECKING
from .runtime._runtime import Actor
from .spawn._entry import _trio_main
from .spawn._entry import (
_consume_netns_bootstrap,
_trio_main,
)
if TYPE_CHECKING:
from .discovery._addr import UnwrappedAddress
@ -52,6 +55,7 @@ def _actor_child_main(
parent_addr: UnwrappedAddress | None,
infect_asyncio: bool,
spawn_method: SpawnMethodKey = 'trio',
netns_bootstrap: tuple[int, int]|None = None,
) -> None:
'''
@ -62,7 +66,13 @@ def _actor_child_main(
invokes this from inside a fresh `concurrent.interpreters`
sub-interpreter via `Interpreter.call()`.
Consume `netns_bootstrap` before Trio patching, actor construction,
process-title setup, or actor-runtime entry. The spawn backend must
supply an exclusively child-owned FD duplicate.
'''
_consume_netns_bootstrap(netns_bootstrap)
# Apply defensive monkey-patches for upstream `trio`
# bugs we've encountered while running tractor — see
# `tractor.trionics.patches` for the catalog +

View File

@ -21,6 +21,7 @@ Sub-process entry points.
from __future__ import annotations
from functools import partial
import multiprocessing as mp
import os
from typing import (
Any,
TYPE_CHECKING,
@ -48,6 +49,7 @@ from ..runtime._runtime import (
async_main,
Actor,
)
from ._netns import enter_netns
if TYPE_CHECKING:
from ._spawn import SpawnMethodKey
@ -56,6 +58,54 @@ if TYPE_CHECKING:
log = get_logger()
def _consume_netns_bootstrap(
netns_bootstrap: tuple[int, int]|None,
) -> int|None:
'''
Enter and release one child-owned network namespace capability.
The FD must be an exclusively child-owned backend duplicate. It is
closed whether namespace entry succeeds or fails, before any actor
runtime setup can continue.
'''
if netns_bootstrap is None:
return None
namespace_fd: int
expected_inode: int
namespace_fd, expected_inode = netns_bootstrap
if (
type(namespace_fd) is not int
or
namespace_fd < 0
):
# Let `enter_netns()` report its precise validation error, but
# never pass bool/non-int/negative values to `os.close()`.
return enter_netns(
namespace_fd,
expected_inode,
)
try:
entered_inode: int = enter_netns(
namespace_fd,
expected_inode,
)
except BaseException as entry_error:
try:
os.close(namespace_fd)
except Exception as close_error:
entry_error.add_note(
f'Also failed to close inherited namespace FD '
f'{namespace_fd}: {close_error!r}'
)
raise
else:
os.close(namespace_fd)
return entered_inode
def _mp_main(
actor: Actor,
@ -64,12 +114,18 @@ def _mp_main(
start_method: SpawnMethodKey,
parent_addr: UnwrappedAddress | None = None,
infect_asyncio: bool = False,
netns_bootstrap: tuple[int, int]|None = None,
) -> None:
'''
The routine called *after fork* which invokes a fresh `trio.run()`
Consume `netns_bootstrap` before multiprocessing or actor-runtime
setup. The spawn backend must supply a child-owned FD duplicate.
'''
_consume_netns_bootstrap(netns_bootstrap)
actor._forkserver_info = forkserver_info
from ._spawn import try_set_start_method
spawn_ctx: mp.context.BaseContext = try_set_start_method(start_method)