Skip to content

LinuxPod: add dial(network:address:) to open a socket inside the pod network namespace #736

@SCKelemen

Description

@SCKelemen

Problem

The Containerization framework provides no API to open a TCP or UDP connection
from the host into the network namespace of a running LinuxPod. The host
can reach the pod only if a port was published at VM creation time via
--publish (Apple Container CLI) or equivalent configuration.

For a Kubernetes pod-shared VM, publishing individual ports at creation time is
not viable because:

  1. The set of listening ports is not known until after sidecars start.
  2. Kubernetes kubectl port-forward expects on-demand dial to an arbitrary
    port inside the pod's network namespace — it is not a static mapping.
  3. Pod-shared mode shares one VM network namespace across all containers in
    the pod; pre-publishing every container's ports defeats the purpose of
    dynamic port-forward.

Use case

kubectl port-forward <pod> 8080:80 expects the CRI runtime to open a TCP
connection to 127.0.0.1:80 inside the pod's network namespace and
relay bytes bidirectionally. On the pod-shared path, the CRI runtime
dispatches a JSON-RPC pod.portDial call to podvmd. The daemon must
open a socket in the pod's netns and return a bidirectional byte stream.

Reproducer

In tools/podvmd/Sources/podvmd/PodRegistry.swift (line ~470):

// MARK: - pod.portDial
//
// Apple Containerization framework (as of 0.12.x) does not expose a
// network-namespace handle or in-guest TCP dial primitive.

func portDial(_ params: PodPortDialParams) async throws -> PodPortDialResult {
    _ = try podEntry(params.handle)
    guard params.network == "tcp" else {
        throw PodVMError(
            .methodNotFound,
            "pod.portDial: unsupported network \"\(params.network)\"; only \"tcp\" is allowed"
        )
    }
    guard params.port > 0 && params.port <= 65535 else {
        throw PodVMError(
            .invalidParams,
            "pod.portDial: port must be between 1 and 65535, got \(params.port)"
        )
    }
    throw PodVMError(
        .methodNotFound,
        "pod.portDial not supported by Apple Containerization framework on this host"
    )
}

The Go CRI layer receives JSON-RPC error code -32601 and maps it to gRPC
codes.Unimplemented:

PortForward: pod-shared path: pod.portDial returned method_not_found
             (-32601: pod.portDial not supported by Apple Containerization
             framework on this host)

A kubectl port-forward against a pod-shared pod produces:

error: unable to upgrade connection: port forward not supported for this container

Proposed API shape

/// Open a TCP or UDP connection inside the pod's network namespace.
/// Returns a bidirectional stream (or pair of FileHandles) that the
/// caller can relay to an external client.
func dial(
    containerID: String?,       // nil = pod-level netns
    network: String,            // "tcp" or "udp"
    address: String             // "127.0.0.1:8080" or ":8080"
) async throws -> (any DuplexStream)

Alternatively, expose a vsock or virtio-net handle that lets the host
inject a connection into the guest's TCP stack.

Workarounds we've tried

  1. --publish hostPort:containerPort at VM creation — requires knowing
    all ports in advance. Does not work for dynamic port-forward. Occupies
    a host port per published mapping.
  2. exec a socat relay inside the pod — spawns socat TCP-LISTEN:$hostSide,fork TCP:127.0.0.1:$port as a sidecar exec.
    Functionally works but adds a process per forwarded port, is fragile
    (socat must be installed in the rootfs), and does not survive process
    restarts.
  3. vsock from host to guest — Apple Containerization does not expose
    vsock channel creation from the host side for an already-running pod.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions