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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions