apple / apple/containerization

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

Open
#736 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Swift
Stars
8.9k
Forks
359
Avg merge
2d 20h
Merged PRs (30d)
13

Description

## 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 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):

```swift
// 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

```swift
/// 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.