Skip to content

Commit

Permalink
feature: cross-container named pipes
Browse files Browse the repository at this point in the history
Add new "uvm://" mount prefix to support cross-container
pipes for Xenon WCOW containers. For now, it's a WCOW-only
feature, while the Linux work is being prototyped.

Additionally, extend the logic of `GetContainerPipeMapping` to
also handle cross-container pipes within the UVM. The syntax
similar to sandbox mounts:

```
{
  "host_path": "uvm://\\\\.\\pipe\\uvmPipe",
  "container_path": "\\\\.\\pipe\\containerPipe"
}
```

Containers sharing the pipe need to have the same "host_path".

Signed-off-by: Maksim An <maksiman@microsoft.com>
  • Loading branch information
anmaxvl committed Mar 3, 2025
1 parent 20e8795 commit cc4d581
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 7 deletions.
5 changes: 3 additions & 2 deletions cmd/containerd-shim-runhcs-v1/task_hcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -988,10 +988,11 @@ func isMountTypeSupported(hostPath, mountType string) bool {
hcsoci.MountTypeVirtualDisk, hcsoci.MountTypeExtensibleVirtualDisk:
return false
default:
// Ensure that host path is not sandbox://, hugepages://
// Ensure that host path is not sandbox://, hugepages://, \\.\pipe, uvm://
if strings.HasPrefix(hostPath, guestpath.SandboxMountPrefix) ||
strings.HasPrefix(hostPath, guestpath.HugePagesMountPrefix) ||
strings.HasPrefix(hostPath, guestpath.PipePrefix) {
strings.HasPrefix(hostPath, guestpath.PipePrefix) ||
strings.HasPrefix(hostPath, guestpath.UVMMountPrefix) {
return false
} else {
// hcsshim treats mountType == "" as a normal directory mount
Expand Down
3 changes: 3 additions & 0 deletions internal/guestpath/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const (
// BlockDevMountPrefix is mount prefix used in container spec to mark a
// block-device mount.
BlockDevMountPrefix = "blockdev://"
// UVMMountPrefix is mount prefix used in container spec to mark a UVM mount
// into container.
UVMMountPrefix = "uvm://"
// PipePrefix is the mount prefix used in container spec to mark a named pipe
PipePrefix = `\\.\pipe`
// LCOWMountPathPrefixFmt is the path format in the LCOW UVM where
Expand Down
10 changes: 9 additions & 1 deletion internal/hcsoci/hcsdoc_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,20 @@ func createMountsConfig(ctx context.Context, coi *createOptionsInternal) (*mount
}
mdv2.HostPath = src
} else if mount.Type == MountTypeVirtualDisk || mount.Type == MountTypePhysicalDisk || mount.Type == MountTypeExtensibleVirtualDisk {
// For v2 schema containers, any disk mounts will be part of coi.additionalMounts.
// For v2 schema containers, any disk mounts will be part of coi.windowsAdditionalMounts.
// For v1 schema containers, we don't even get here, since there is no HostingSystem.
continue
} else if strings.HasPrefix(mount.Source, guestpath.SandboxMountPrefix) {
// Convert to the path in the guest that was asked for.
mdv2.HostPath = convertToWCOWSandboxMountPath(mount.Source)
} else if strings.HasPrefix(mount.Source, guestpath.UVMMountPrefix) {
if uvm.IsPipe(strings.TrimPrefix(mount.Source, guestpath.UVMMountPrefix)) {
src, dst := uvm.GetContainerPipeMapping(coi.HostingSystem, mount)
config.mpsv1 = append(config.mpsv1, schema1.MappedPipe{HostPath: src, ContainerPipeName: dst})
config.mpsv2 = append(config.mpsv2, hcsschema.MappedPipe{HostPath: src, ContainerPipeName: dst})
} else {
return nil, fmt.Errorf("unsupported UVM mount source: %s", mount.Source)
}
} else {
// vsmb mount
uvmPath, err := coi.HostingSystem.GetVSMBUvmPath(ctx, mount.Source, readOnly)
Expand Down
3 changes: 2 additions & 1 deletion internal/hcsoci/resources_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.R
} else if strings.HasPrefix(mount.Source, guestpath.SandboxMountPrefix) {
// Mounts that map to a path in the UVM are specified with a 'sandbox://' prefix.
//
// Example: sandbox:///a/dirInUvm destination:C:\\dirInContainer.
// Example:
// - sandbox:///a/dirInUvm destination:C:\\dirInContainer.
//
// so first convert to a path in the sandboxmounts path itself.
sandboxPath := convertToWCOWSandboxMountPath(mount.Source)
Expand Down
24 changes: 21 additions & 3 deletions internal/uvm/pipes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import (
"fmt"
"strings"

"github.com/Microsoft/hcsshim/internal/guestpath"
"github.com/Microsoft/hcsshim/internal/hcs/resourcepaths"
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-spec/specs-go"
)

const pipePrefix = `\\.\pipe\`
Expand Down Expand Up @@ -54,18 +55,35 @@ func (uvm *UtilityVM) RemovePipe(ctx context.Context, hostPath string) error {
return nil
}

// IsPipe returns true if the given path references a named pipe.
// UVMNamedPipe returns a named pipe in UVM, which will be shared across containers.
func (uvm *UtilityVM) UVMNamedPipe(hostPath string) string {
podID := strings.TrimSuffix(uvm.id, "@vm")
if uvm.operatingSystem == "windows" {
uvmPipeName := strings.TrimPrefix(hostPath, pipePrefix)
return fmt.Sprintf(`%s%s\%s`, pipePrefix, podID, uvmPipeName)
}
// TODO (anmaxvl): LCOW doesn't support UVM named pipes at the moment
return hostPath
}

// IsPipe returns true if the given path references a named pipe. The pipe can be:
// - host named pipe shared via VSMB
func IsPipe(hostPath string) bool {
return strings.HasPrefix(hostPath, pipePrefix)
}

// GetContainerPipeMapping returns the source and destination to use for a given
// pipe mount in a container.
// The pipe mount can be either a host pipe shared via VSMB or a UVM pipe.
func GetContainerPipeMapping(uvm *UtilityVM, mount specs.Mount) (src string, dst string) {
if uvm == nil {
src = mount.Source
} else {
src = vsmbSharePrefix + `IPC$\` + strings.TrimPrefix(mount.Source, pipePrefix)
if uvmPipe, ok := strings.CutPrefix(mount.Source, guestpath.UVMMountPrefix); ok {
src = uvm.UVMNamedPipe(uvmPipe)
} else {
src = vsmbSharePrefix + `IPC$\` + strings.TrimPrefix(mount.Source, pipePrefix)
}
}
dst = strings.TrimPrefix(mount.Destination, pipePrefix)
return src, dst
Expand Down

0 comments on commit cc4d581

Please sign in to comment.