Harden the `tipc` two-host smoke test

Turn the physical-host sketch into an operator runbook covering
cluster identity, interface and bearer setup, link validation,
failure/rejoin testing, diagnostic capture and cleanup.

Explain the cluster-domain-socket analogy and identify a future
`pyroute2` TIPC codec as the path from manual `tipc(8)` commands to
the same netlink management stack planned for WireGuard.

Authorize `host_a_srv` by its stable import name so direct script
execution does not expose only `__main__` while host B requests the
callable's actual `NamespacePath`.

Prompt-IO: ai/prompt-io/opencode/20260819T003327Z_53516b09_prompt_io.md

(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
wkt/tipc_backend_378
Gud Boi 2026-08-19 14:56:48 -04:00
parent caf9c3d6d3
commit ff2b20b7d1
4 changed files with 151 additions and 12 deletions

View File

@ -0,0 +1,37 @@
---
model: openai/gpt-5.6-sol
service: opencode
session: a998acff-af66-4969-a65e-f77cbf5ef8dd
timestamp: 2026-08-19T00:33:27Z
git_ref: wkt/tipc_backend_378
scope: code
substantive: true
raw_file: 20260819T003327Z_53516b09_prompt_io.raw.md
---
## Prompt
Develop a practical bootstrap plan for testing TIPC across two
physical motherboards.
## Response summary
Expanded the two-host example into a reproducible smoke-test runbook
and made host A authorize the stable module name imported by host B.
Documented the cluster-domain-socket analogy and the prospective
`pyroute2` management path.
## Files changed
- `examples/multihost/tipc_cluster/README.md` - document setup,
validation, resilience testing, capture and cleanup.
- `examples/multihost/tipc_cluster/host_a_srv.py` - authorize the
`host_a_srv` RPC module under direct script execution.
## Human edits
The human selected two separate physical motherboards as the target
environment and requested that the resulting local work be packaged
as its own commit boundary. The human also proposed “Cluster Domain
Sockets” as clearer terminology and reusing the planned `pyroute2`
dependency for TIPC management.

View File

@ -0,0 +1,27 @@
---
model: openai/gpt-5.6-sol
service: opencode
timestamp: 2026-08-19T00:33:27Z
git_ref: wkt/tipc_backend_378
diff_cmd: git diff HEAD~1..HEAD
---
## Prompt
The user asked for a bootstrap plan to try TIPC with two real,
physically separate Linux motherboards.
## Response
The generated changes turn the existing sketch into an operator
runbook and correct the RPC module authorization used by direct
script execution:
> `git diff HEAD~1..HEAD -- examples/multihost/tipc_cluster/README.md examples/multihost/tipc_cluster/host_a_srv.py`
The runbook covers matching revisions, cluster identity, interface
selection, Ethernet bearer setup, link and name-table validation,
RPC, failure/rejoin testing, diagnostics, cleanup and a future
network-namespace fixture. It also records “Cluster Domain Sockets”
as explanatory terminology and identifies a future `pyroute2` TIPC
generic-netlink codec as the path away from manual `tipc(8)` calls.

View File

@ -2,6 +2,10 @@
TIPC is a linux-kernel cluster IPC protocol whose service names
live in a **cluster-wide name table maintained by the kernel**.
It is also described as **Cluster Domain Sockets**: the Unix-domain
socket model extended from one kernel to a cluster. That name is a
useful explanation for new users, while the code keeps `tipc` as its
protocol key to match Linux's `AF_TIPC`, kernel module and tooling.
For `tractor` that means:
- an actor's IPC address is a service name `(stype, instance)`,
@ -85,30 +89,55 @@ Everything above is single-node (`modprobe` is enough). To span
hosts you need a **bearer** on both, which is the one thing that
can't be CI'd.
For the first physical test, use two wired Linux hosts on the same
L2 segment. Prefer a direct cable or uncomplicated switch; avoid
Wi-Fi, guest VLANs and port isolation until the basic link works.
Use the same checkout and Python environment on both hosts:
```bash
# on BOTH hosts
git rev-parse HEAD # must match on A and B
uv sync --all-extras --dev
sudo modprobe tipc
# over ethernet (L2) — simplest when the hosts share a segment
sudo tipc bearer enable media eth device eth0
# choose the real wired iface; do not assume `eth0`
ip -br link
IFACE=enp3s0
# inspect existing cluster identity before changing anything
tipc node get address # must differ between hosts
tipc node get netid # must match between hosts
# use one private test netid on BOTH hosts, before enabling bearers
sudo tipc node set netid 37801
# ethernet is simplest when the hosts share an L2 segment
sudo tipc bearer enable media eth device "$IFACE"
# ..or over UDP when L2 isn't available — and MANDATORY over a
# `wg` mesh, see below
sudo tipc bearer enable media udp name uc localip 10.0.11.1
# verify BEFORE running anything: this must list the peer
tipc bearer list
tipc link list
tipc node list
```
Then:
If the link does not appear, first verify carrier, a common TIPC
network ID, distinct node addresses, a common VLAN and compatible
MTUs. Ethernet TIPC uses EtherType traffic rather than IP routing,
so a successful `ping` alone does not prove the bearer can work.
Run each command from `examples/multihost/tipc_cluster/`:
```bash
# host A
python host_a_srv.py
uv run python host_a_srv.py
# host B
python host_b_client.py
watch -n 0.5 tipc nametable show # optional second terminal
uv run python host_b_client.py
```
Note what's absent from both scripts: any IP, hostname or port.
@ -116,6 +145,51 @@ Both sides name the *same service*, and the kernel routes it.
Move `host_a_srv.py` to a third node and host B's dial keeps
working, unchanged.
For a first resilience pass, use a local console or separate
management link so the test does not cut off your own SSH session:
```bash
# host B: record the healthy baseline
tipc link list
tipc link statistics show
# either host: withdraw and recreate the bearer
sudo tipc bearer disable media eth device "$IFACE"
tipc link list
sudo tipc bearer enable media eth device "$IFACE"
tipc link list
# prove name withdrawal/republication and RPC recovery
tipc nametable show
uv run python host_b_client.py
```
Capture `uname -a`, both node addresses, `tipc bearer list`,
`tipc link list`, `tipc link statistics show`, the name table and
both Python transcripts. Those artifacts distinguish an actor bug
from bearer discovery, cluster identity or switch configuration.
Clean up a disposable Ethernet test on both hosts with:
```bash
sudo tipc bearer disable media eth device "$IFACE"
tipc link list
```
Restore any pre-existing network ID only after all bearers are
disabled. The first useful automation target is a two-node network
namespace fixture that asserts link-up, remote publication, RPC,
withdrawal and republication in that order; physical hardware then
remains the validation layer for real NIC and switch behaviour.
The commands above use iproute2's `tipc` frontend, which speaks the
kernel's `TIPCv2` generic-netlink family. The planned `pyroute2`
dependency already manages WireGuard, interfaces and namespaces and
provides generic-netlink primitives, but it does not currently ship a
TIPC message codec. Adding one upstream would let `tractor` replace
these manual commands with one Python netlink stack instead of
shelling out; until then, `tipc(8)` remains the canonical frontend.
### over a `wg` mesh
TIPC over WireGuard is the intended reference multihost
@ -231,12 +305,10 @@ self-skip when the module is absent.
## CI
Single-host TIPC *is* CI-able — the module ships with the
standard Ubuntu kernel package, so a `sudo modprobe tipc` step
plus a `--tpt-proto tipc` matrix entry should work. That's not
wired up yet; verify in a throwaway workflow first, and fall
back to a container job with `--cap-add NET_ADMIN` if the
runners refuse. Cross-node (bearer) testing stays manual — this
README is that smoke test.
standard Ubuntu kernel package. CI loads it with `sudo modprobe
tipc` and runs the suite with `--tpt-proto tipc` as a blocking
matrix leg. Cross-node bearer testing stays manual — this README
is that smoke test.
## normative refs

View File

@ -57,7 +57,10 @@ async def main() -> None:
) as an:
await an.start_actor(
'host_a',
enable_modules=[__name__],
# Host B imports this same module name to construct the
# RPC `NamespacePath`; direct script execution would
# otherwise expose it as `__main__` on host A.
enable_modules=['host_a_srv'],
)
print(
'host_a up — `tipc nametable show` on EITHER host\n'