Skip to content

allow@host:tcp guest connections to a live, host-confirmed-listening loopback port get 100% ECONNRESET — reproducible on a fresh VM and fresh port (v0.5.7) #1327

Description

@magnusnoeddegaard

Environment

  • msb version: 0.5.7 (host has been running this pinned version continuously)
  • Host: single Hetzner box, up ~120 days, heavy sandbox create/teardown churn (dozens of ephemeral sandboxes created and removed per day)
  • Feature: --net-rule allow@host:tcp:<port> guest access to host.microsandbox.internal:<port>

Problem

A guest microVM cannot reach a host loopback port via host.microsandbox.internal:<port>, even with the correct allow@host:tcp:<port> rule granted, even though the exact same port is reachable from the host itself at the exact same time.

  • curl 127.0.0.1:<port> from the host: succeeds (200) every time.
  • curl host.microsandbox.internal:<port> from inside the guest, same port, same moment: fails 100% of the time with a TCP-level reset (ECONNRESET / "Connection reset by peer" / curl: (56) Recv failure: Connection reset by peer).

Isolation performed

  • Reproduces on a completely fresh guest VM and a completely fresh target port set up seconds earlier — not VM age, load, or a stale relay/tunnel.
  • Reproduces identically against a directly-bound real service (tested our own long-running process's real listening port with a single allow@host:tcp:<port> rule and nothing else) — not specific to any SSH-relay/tunnel setup we layered on top.
  • Host nf_conntrack_count is far below its max (683 / 262144); msb's guest<->host forwarding does not appear in host iptables/nftables at all (only unrelated Docker NAT rules present) — consistent with the forwarding being entirely in-process (microsandbox_runtime::relay log lines observed).
  • Checked the host process tree for a shared/central msb daemon: found none. Each sandbox appears to run as its own independent msb sandbox --name <id> process with its own agent relay unix socket. If the connection tracker in crates/network/lib/conn.rs is scoped per-process, that would seem to rule out Guest FIN never tears down proxied TCP connections: CLOSE_WAIT sockets leak until the 256-slot connection table fills and guest egress goes dark #1180's leak (which needs prior connections to accumulate) as the explanation for a first-attempt failure on a brand-new sandbox — but I can't confirm the tracker's actual scope from outside the source.
  • First surfaced as write ECONNRESET/read ECONNRESET at the HTTP client level, sometimes as a plain HTTP 500 depending on timing.

Why we think this is msb-internal, not guest/network config

Per #621 (merged 2026-05-16), msb's proxy sends a TCP RST to the guest specifically when its own internal TcpStream::connect() to the host upstream fails. Since a direct host curl to the same port at the same time succeeds, this points at msb's own proxy process failing to open the connection — not the target refusing it.

Possible relation to #1180 (unreleased)

#1180 documents a CLOSE_WAIT leak in the shared connection tracker that fills the 256-slot connection table over time (long-lived process + heavy churn), causing all new guest connections to be refused. Our host matches that usage profile. However, our repro is a single first-attempt connection on a fresh VM/fresh port, which doesn't obviously require 256 prior leaked connections to trigger — combined with not finding a shared daemon process (above), this may be a distinct bug in the same code path rather than the same root cause. Note: the #1181 fix for #1180 is not yet in any tagged release (merged 2026-08-02, after v0.6.8; the v0.6.9 bump PR #1267 is still open) — we have not been able to test whether it resolves this.

Ask

  1. Is this a known/expected limitation of allow@host:tcp?
  2. Could this be the same root cause as Guest FIN never tears down proxied TCP connections: CLOSE_WAIT sockets leak until the 256-slot connection table fills and guest egress goes dark #1180 despite requiring no connection backlog to trigger, or a distinct bug?
  3. Are there internal limits (concurrent proxy connections, connect timeout, ephemeral port range) on the host-side proxy dial-out that could explain first-attempt failures under long uptime / high churn?

Happy to provide lsof -p <msb-pid> / /proc/<pid>/limits snapshots from the affected host on request, or any other diagnostics.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions