feat: add --publish-socket for exposing a guest TCP port as a host unix socket#2
Open
luke wants to merge 1 commit into
Open
feat: add --publish-socket for exposing a guest TCP port as a host unix socket#2luke wants to merge 1 commit into
luke wants to merge 1 commit into
Conversation
…ix socket Completes the socket-bridge triple from smol-machines#656: expose (guest unix socket -> host), mount (host unix socket -> guest), and now publish (guest TCP port -> host unix socket) — -p without the host TCP listener, so a guest service like an engine API is reachable on the host only through a socket file the caller owns (the colima DOCKER_HOST shape). Publish rides the smol-machines#656 machinery end to end: a new SocketDirection::Publish is host-side identical to Expose (dynamic vsock port, libkrun listens on the host socket, stale file replaced at start), and the guest agent's serve_publish mirrors serve_expose except each host connection dials the guest port on loopback over TCP, with a short connect timeout so a dial the guest firewall silently drops cannot pin the forwarder thread. The shared relay already carries independent half-close generically, and a FIN maps to a TCP shutdown, so streaming protocols keep their tail bytes in both directions; a connection to a port with no listener is dropped exactly like an expose target whose socket is missing — no start-order coupling. There are no functional launcher changes (doc comments only), and no BootConfig fields are added, so the cfg(not(unix)) initializers stay untouched. CLI grammar is --publish-socket HOST_PATH:GUEST_PORT (host first, matching -p and --mount-socket); the port is validated at parse time and split on the last colon so host paths containing ':' keep working. Unit tests cover the protocol round-trip and malformed publish targets, the unix<->TCP relay with half-close, and the CLI parser; test_ports.sh gains an HTTP round-trip through a published socket (including a port-based no-host-TCP-listener check) and a no-listener/recovery case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
--publish-socket HOST_PATH:GUEST_PORTonmachine createmakes a TCP port theguest listens on reachable on the host as a Unix socket file —
-pwithout thehost TCP listener. It completes the socket-bridge triple from smol-machines#656: expose
(guest unix socket → host), mount (host unix socket → guest), publish (guest
TCP port → host unix socket).
Why
Tools that embed smolvm as an engine want the only host-side reachability of
a guest service to be a socket file the caller owns (the colima
DOCKER_HOST=unix://shape): no port collisions, no loopback exposure to otherlocal processes, file-permission access control for free. Guest workloads
overwhelmingly listen on TCP, so the unix↔unix bridges from smol-machines#656 don't reach
them without a shim inside the guest.
How
Publish rides the smol-machines#656 machinery end to end. A new
SocketDirection::Publishis host-side identical to
Expose— dynamic vsock port, libkrun listens on thehost socket, stale file replaced at start, same 64-entry cap — and the guest
agent's
serve_publishmirrorsserve_expose, except each host connectiondials the guest port on loopback over TCP with a short connect timeout, so a
dial the guest firewall silently drops cannot pin the forwarder thread (a
refused connection still fails immediately). The shared relay is generic over the
stream type, so independent half-close carries across the TCP leg unchanged (a
FIN maps to
shutdown(SHUT_WR)); a port with no listener behaves like anexpose target whose socket is missing — the connection is dropped and the host
client sees a reset, no start-order coupling. The launcher pipeline and
BootConfigcarry no functional changes — doc comments updated only, no newfields — so the
cfg(not(unix))initializers from the smol-machines#656 follow-up areunaffected. The wire encoding extends naturally to
port|publish|8080withparse-time and decode-time validation.
CLI grammar is host-first, matching
-p HOST:GUESTand--mount-socket; thespec splits on the last colon so host paths containing
:keep working.The AGENTS.md section added here also documents the two existing smol-machines#656 flags
(
--expose-socket/--mount-socket), which were previously undocumented —included so all three bridges are described in one place.
Tests
(path, port 0, >65535) are skipped like other malformed entries
client half-close (plus the existing three relay tests)
:tests/test_ports.sh): HTTP round-trip through the publishedsocket with an
sscheck that no process holds a host TCP listener on thepublished port; stale socket file replaced at start; no-listener fails fast
and recovers once a listener appears
real guest HTTP server, 20 concurrent requests, half-close probes in both
directions with byte-count asserts,
ssproof that no process listens onthe published ports, no-listener fail-fast plus recovery, and stop/start
lifecycle — 13/13 passing
ssplatform adaptations noted in the transcript)Format/clippy/unit gates green on Linux; Windows cross-compile checked.
Possible follow-up
Three pieces of shared smol-machines#656 semantics are deliberately left out of this PR
and would make a natural follow-up hardening PR, since each touches the
existing expose/mount behavior too: an explicit
,mode=option (or tighterdefault) for the host socket file, which today is created with the invoking
user's umask and not chmod'd, leaving access control to the host path's
directory permissions; surfacing a host-socket bind failure
(
krun_add_vsock_port2returning an error) as a hard launch error instead ofa warn-and-disable; and rejecting duplicate host paths across bridge
definitions, where today the last bind quietly wins.