Repository navigation
bug: Podman driver picks the wrong host IP for the supervisor callback on multi-homed Linux hosts (regression from #2942) #3412
Description
User Story
As a user running the local Podman driver on a multi-homed Linux laptop (wired dock + Wi-Fi + Tailscale), I want openshell sandbox create to work without hand-editing the gateway config or bringing interfaces down.
Problem Statement
On a host with multiple network interfaces, the Podman driver and Podman disagree on which host IP host.containers.internal points to, so the in-container supervisor can't reach the gateway and provisioning fails.
- The gateway binds its callback listener to the default-route interface plus loopback:
192.168.1.27:17670and127.0.0.1:17670. - The driver tells the supervisor to dial
host.containers.internal:17670and injects--add-host host.containers.internal:host-gateway. - Podman resolves
host-gatewayto a different interface — here the Tailscale address100.64.0.2(wt0, CGNAT100.64.0.0/10), where nothing listens. Bringing Wi-Fi down didn't help; Podman then used the Tailscale IP instead of the wired one. - The supervisor's policy fetch fails, it exits 1, the sandbox container follows, and the sandbox enters
Error.
The reported error is also misleading: ContainerExited: Container exited with code 0 refers to the sandbox container reacting to its supervisor dying. The real failure is only in the openshell-supervisor-<id> container logs.
Impact / Why This Matters
-
Consequence: sandbox creation fails outright on multi-homed hosts (dock + Wi-Fi + VPN/mesh laptops are common), and the surfaced error points at the wrong container, so it's hard to diagnose.
-
Workaround: set
host_gateway_ip = "127.0.0.1"under[openshell.drivers.podman]in~/.config/openshell/gateway.toml, thensystemctl --user restart openshell-gateway. This works because the supervisor uses--network host, so loopback reaches the gateway's127.0.0.1:17670listener, and bothhost.containers.internaland127.0.0.1are cert SANs. The sandbox then reachesReady.[openshell.drivers.podman] host_gateway_ip = "127.0.0.1"
-
Why insufficient: it relies on an undocumented field and non-obvious reasoning about host-network loopback; it isn't discoverable from the error. The default should just work on multi-homed hosts.
Confirmed Regression (#2942)
Confirmed by version bisection: this is a regression introduced by PR #2942 (RFC 0012 sandbox architecture).
- 0.0.116 (does not contain feat(isolation): implement the RFC 0012 sandbox architecture #2942; tagged 2026-08-28):
openshell sandbox createreaches a working sandbox shell with default config (nohost_gateway_ip) on this multi-homed host. ✅ - 0.0.117-dev / current main (contains feat(isolation): implement the RFC 0012 sandbox architecture #2942, merged 2026-09-16 as
c1f2e7189, first released inv0.1.0-pre.2): the same default config fails withError, and only thehost_gateway_ip = "127.0.0.1"workaround makes it succeed. ❌ - The gateway binds the same two listeners (
192.168.1.27+127.0.0.1) on both versions, so theDefaultRouteInterfacelistener is not the trigger — the change is the callback path.
Mechanism:
-
feat(isolation): implement the RFC 0012 sandbox architecture #2942 split the sandbox into two containers and moved the gateway callback onto a host-networked supervisor (
crates/openshell-driver-podman/src/container.rs:supervisor.netns.nsmode = "host"). Previously a single container made the callback. -
The callback machinery predates feat(isolation): implement the RFC 0012 sandbox architecture #2942 and assumes bridge/pasta semantics: the
DefaultRouteInterfacelistener negotiation is from feat(compute): negotiate gateway callback listeners #2492, and thehost-gateway--add-hostinjection from fix(podman): avoid host-gateway on macOS machines #1637. -
Under the same
--add-host host.containers.internal:host-gateway, the network mode changes what the alias resolves to:Network mode host.containers.internalresolves toBridge ( --network openshell) — pre-feat(isolation): implement the RFC 0012 sandbox architecture #2942 style169.254.1.2(link-local host-gateway)Host netns ( --network host) — feat(isolation): implement the RFC 0012 sandbox architecture #2942 supervisornon-link-local host IP (here, the Tailscale interface) -
The supervisor even warns about this:
host.openshell.internal maps to a non-link-local IP; trusted-gateway SSRF exemption disabled. The callback path was designed around a link-local host-gateway (bridge/pasta), which host networking does not provide. On a single-homed host the host-netns resolution still lands on a reachable IP; on a multi-homed host it selects the wrong interface, where nothing listens. -
Note: the
host_gateway_ip = "127.0.0.1"workaround only works because the supervisor is now host-networked (an artifact of the feat(isolation): implement the RFC 0012 sandbox architecture #2942 change) and therefore shares the host loopback.
Acceptance Criteria
- A multi-homed Linux host reaches
Readywith the Podman driver using default config (no manualhost_gateway_ip). - The supervisor reaches the gateway regardless of which interface Podman picks for
host-gateway. - When the supervisor can't reach the gateway, the phase error reports its failure reason, not
ContainerExited: code 0. - Covered by a test, or the multi-homed
host_gateway_ipguidance is documented.
Reproduction Steps
- On a Linux host whose interfaces differ from Podman's
host-gatewayresolution (e.g. wired dock + Wi-Fi + Tailscale), configure the Podman driver. - Run
openshell sandbox create --provider <any>. - The sandbox enters
Errorduring provisioning.
Confirm the mismatch:
ss -ltn | grep 17670
# LISTEN ... 192.168.1.27:17670 (default-route iface)
# LISTEN ... 127.0.0.1:17670
podman run --rm --network host --add-host host.containers.internal:host-gateway \
registry.fedoraproject.org/fedora-minimal:latest getent hosts host.containers.internal
# 100.64.0.2 host.containers.internal <-- Tailscale wt0, nothing listening thereEnvironment
- OpenShell:
0.0.117-dev.167+g7e7a8d561(development build; regression absent in0.0.116), installed with:curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | OPENSHELL_VERSION=dev sh - OS / kernel: Fedora Linux 44 (Workstation) /
7.2.5-200.fc44.x86_64 - Podman:
5.8.4(netavark) - Driver:
podman(rootless); gateway runs as theopenshell-gatewaysystemd user service (native install, not containerized) - Network: multi-homed — wired dock
192.168.1.27, Wi-Fi192.168.1.75(same subnet), Tailscalewt0in100.64.0.0/10
Related
Related: #2540, #1952, and the macOS cases #1519 / #1634. Distinct from #1909 (containerized gateway on Fedora 44).
Logs
Supervisor container (openshell-supervisor-<id>) — the actual failure:
WARN openshell_supervisor: Policy fetch failed, retrying
Error: × Policy fetch failed after 5 attempts: failed to connect to OpenShell server
Activity
jgarciao commentedon Sep 17, 2026
CC: @elezar
elezar commentedon Sep 17, 2026
elezar commentedon Sep 17, 2026
Implementation Plan
Issue type:
fixComplexity: Medium
Confidence: High — the callback topology after PR #2942 is clear from the current code
Summary
Update the existing PR #2874 implementation so callback routing follows the callback producer's network namespace rather than Podman's rootful/rootless mode. Native Linux supervisors use the host network namespace, so automatic callbacks will use the existing gateway aliases mapped to host loopback and request a loopback listener; Podman Machine and explicit remote endpoints keep their platform-specific routes.
Scope
crates/openshell-driver-podman/src/driver.rs: select loopback for automatic Linux callbacks, remove bridge-gateway discovery and the rootless network-helper gate from this path, preserve explicit remote endpoints and explicit host-gateway overrides.crates/openshell-driver-podman/src/container.rs: map the supervisor's driver-owned aliases to127.0.0.1for the automatic Linux host-network route.crates/openshell-driver-podman/src/config.rs: document the updated empty/defaulthost_gateway_ipbehavior.proto/compute_driver.proto,crates/openshell-server/, and Docker driver compatibility changes: remove PR fix(gateway)!: handle delayed Podman bridge listeners #2874's delayed-bind protocol/server machinery because the managed bridge is no longer the callback origin after PR feat(isolation): implement the RFC 0012 sandbox architecture #2942.architecture/gateway.md,crates/openshell-driver-podman/NETWORKING.md,docs/reference/gateway-config.mdx,docs/reference/sandbox-compute-drivers.mdx, andskills/debug-openshell-cluster/SKILL.md: replace delayed-bind guidance with the callback-origin invariant and deployment boundaries.Implementation Steps
Test Plan
host_gateway_ip, explicit remote endpoints, loopback host entries, and absence of managed-network gateway inspection.Risks & Open Questions
grpc_endpointand appropriate gateway exposure.ContainerExited: code 0diagnostic requires an explicit supervisor-derived terminal condition and possibly structured failure propagation. Keep that lifecycle/diagnostics work separate from PR fix(gateway)!: handle delayed Podman bridge listeners #2874; the updated PR fixes the callback regression but does not claim that diagnostic criterion.Documentation Impact
docs/reference/gateway-config.mdxbecause the effective default semantics of emptyhost_gateway_ipchange on Linux.Revision 1 — initial plan for updating PR #2874
politerealism commentedon Sep 17, 2026
I'm picking this up.
jgarciao commentedon Sep 18, 2026
Verified fixed by #3365 ✅
Built the gateway + runtime images from #3365 and re-ran
sandbox createwith default config (nohost_gateway_ip) on a genuinely multi-homed host — wired dock (enp0s13f0u2, default route metric 100), Wi-Fi (wlp9s0f0, metric 600), and Tailscale (wt0) all up. The sandbox reachesReady.Why #3365 fixes it
The fix changes how the Podman driver picks the supervisor's gateway endpoint. Since RFC 0012 the supervisor runs host-networked (
--network host), so it shares the host's loopback. #3365 addsselect_grpc_endpoint(), which on a Linux host returnshttp://127.0.0.1:<gateway-port>instead of thehost.containers.internalalias:Because the endpoint is now loopback, Podman's
host-gatewayresolution is off the callback path entirely — it no longer matters which interface Podman would have mappedhost-gatewayto (the wired IP, or the Tailscale IP as in the original report). That's the root cause removed, not worked around.The old callback machinery this issue described —
GetGatewayListenerRequirementsnegotiation, theDefaultRouteInterfacelistener, and nonlocal bind support — is deleted in the same PR.Verification evidence
For contrast, a
mainbuild on the same host hands the supervisorOPENSHELL_ENDPOINT=http://host.containers.internal:18080; that only works when Podman'shost-gatewayhappens to resolve to the interface the gateway binds, which is exactly the fragile behavior this issue is about.Acceptance criteria
Readywith default config (no manualhost_gateway_ip).host-gatewayresolution (loopback removes that dependency).grpc_endpoint_uses_loopback_on_linuxasserts the Linux endpoint selection.politerealism commentedon Sep 21, 2026
Looks like we can close this issue then @elezar