Skip to content

Commit

Permalink
HvSocket support for containers
Browse files Browse the repository at this point in the history
Applications connecting from the host into the container should use
container-specific VMID. This ID will need to be the same as the
container's VMID inside the guest, which is calculated by HCS/GCS
like it's done in this PR by `HCSIDToGUID`.

To allow the container ID to work with HvSocket on the host, we
need to set up an AddressInfo mapping to tell HvSocket to redirect
the call into the UVM, which is done in this PR by default for
all WCOW containers.

Add `hvsocketaddr.exe` that clients can use to generate VM ID for
container.

Signed-off-by: Maksim An <maksiman@microsoft.com>
  • Loading branch information
kevpar authored and anmaxvl committed Feb 10, 2025
1 parent e5c83a1 commit 57059f2
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,8 @@ jobs:
name: Build device-util.exe
- run: ${{ env.GO_BUILD_CMD }} ./cmd/ncproxy
name: Build ncproxy.exe
- run: ${{ env.GO_BUILD_CMD }} ./cmd/hvsocketaddr
name: Build hvsocketaddr.exe
- run: ${{ env.GO_BUILD_CMD }} ./internal/tools/grantvmgroupaccess
name: Build grantvmgroupaccess.exe
- run: ${{ env.GO_BUILD_CMD }} ./internal/tools/networkagent
Expand Down Expand Up @@ -799,6 +801,7 @@ jobs:
wclayer.exe
device-util.exe
ncproxy.exe
hvsocketaddr.exe
grantvmgroupaccess.exe
networkagent.exe
securitypolicy.exe
Expand Down
24 changes: 24 additions & 0 deletions cmd/hvsocketaddr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Overview
Applications connecting from the host into the container should use container-specific VMID.
This VMID will need to be the same as the container's VMID inside the guest. One way to get
the VMID is to query HCS for it or use this binary, which outputs the same VMID, when
querying HCS isn't an option.

## Build
Build the binary as following
```powershell
> go build ./cmd/hvsocketaddr
```

## Run
Find container ID using (e.g.) `crictl.exe`:
```powershell
> crictl ps --no-trunc
```
Note that we need full container ID, rather than a truncated one.

Get VMID:
```powershell
> .\hvsocketaddr.exe <container-id>
```
The output VMID can be used by the services on the host.
37 changes: 37 additions & 0 deletions cmd/hvsocketaddr/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"bytes"
"encoding/binary"
"fmt"
"os"
"strings"
"unicode/utf16"

"github.com/Microsoft/go-winio/pkg/guid"
)

func HCSIDToGUID(id string) (guid.GUID, error) {
var buf bytes.Buffer
if err := binary.Write(&buf, binary.LittleEndian, utf16.Encode([]rune(strings.ToUpper(id)))); err != nil {
return guid.GUID{}, err
}
// Namespace GUID: cab70344-facb-41e4-b5e5-ab6592283e6e
g, err := guid.NewV5(guid.GUID{Data1: 0xcab70344, Data2: 0xfacb, Data3: 0x41e4, Data4: [8]byte{0xb5, 0xe5, 0xab, 0x65, 0x92, 0x28, 0x3e, 0x6e}}, buf.Bytes())
if err != nil {
return guid.GUID{}, err
}
return g, nil
}

func main() {
if len(os.Args) != 2 || os.Args[1] == "--help" || os.Args[1] == "-h" {
fmt.Printf("usage: %s <CONTAINER ID>\n", os.Args[0])
os.Exit(1)
}
g, err := HCSIDToGUID(os.Args[1])
if err != nil {
fmt.Printf("error: %s\n", err)
}
fmt.Printf("%s\n", g)
}
2 changes: 2 additions & 0 deletions internal/hcs/schema2/properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type Properties struct {

RuntimeId string `json:"RuntimeId,omitempty"`

SystemGUID string `json:"SystemGUID,omitempty"`

RuntimeTemplateId string `json:"RuntimeTemplateId,omitempty"`

State string `json:"State,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions internal/hcs/schema2/property_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ const (
PTICHeartbeatStatus PropertyType = "ICHeartbeatStatus"
PTProcessorTopology PropertyType = "ProcessorTopology"
PTCPUGroup PropertyType = "CpuGroup"
PTSystemGUID PropertyType = "SystemGUID"
)
24 changes: 22 additions & 2 deletions internal/hcsoci/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ import (
"strconv"

"github.com/Microsoft/go-winio/pkg/guid"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"

"github.com/Microsoft/hcsshim/internal/cow"
"github.com/Microsoft/hcsshim/internal/guestpath"
"github.com/Microsoft/hcsshim/internal/hcs"
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
"github.com/Microsoft/hcsshim/internal/hvsocket"
"github.com/Microsoft/hcsshim/internal/layers"
"github.com/Microsoft/hcsshim/internal/log"
"github.com/Microsoft/hcsshim/internal/oci"
"github.com/Microsoft/hcsshim/internal/resources"
"github.com/Microsoft/hcsshim/internal/schemaversion"
"github.com/Microsoft/hcsshim/internal/uvm"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)

var (
Expand Down Expand Up @@ -278,6 +280,24 @@ func CreateContainer(ctx context.Context, createOptions *CreateOptions) (_ cow.C
if err != nil {
return nil, r, err
}

if coi.HostingSystem.OS() == "windows" {
log.G(ctx).Debug("redirecting container HvSocket for WCOW")
props, err := c.PropertiesV2(ctx, hcsschema.PTSystemGUID)
if err != nil {
return nil, r, fmt.Errorf("query created container properties failed: %w", err)
}
containerSystemGUID, err := guid.FromString(props.SystemGUID)
if err != nil {
return nil, r, fmt.Errorf("convert to system GUID failed: %w", err)
}
addressInfoCloser, err := hvsocket.CreateAddressInfo(containerSystemGUID, coi.HostingSystem.RuntimeID(), true)
if err != nil {
return nil, r, fmt.Errorf("redirect container HvSocket failed: %w", err)
}
r.Add(addressInfoCloser)
}

return c, r, nil
}

Expand Down
81 changes: 81 additions & 0 deletions internal/hvsocket/hvsocket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//go:build windows
// +build windows

package hvsocket

import (
"context"
"fmt"
"unsafe"

"github.com/Microsoft/go-winio/pkg/guid"
"golang.org/x/sys/windows"

"github.com/Microsoft/hcsshim/internal/resources"
)

const (
addressFlagPassthru = 0x00000001
ioCtlHVSocketUpdateAddressInfo = 0x21c004
)

type addressInfo struct {
systemID guid.GUID
virtualMachineID guid.GUID
siloID guid.GUID
flags uint32
}

type addressInfoCloser struct {
handle windows.Handle
}

var _ resources.ResourceCloser = addressInfoCloser{}

func (aic addressInfoCloser) Release(_ context.Context) error {
return windows.CloseHandle(aic.handle)
}

func CreateAddressInfo(cid, vmid guid.GUID, passthru bool) (resources.ResourceCloser, error) {
path := fmt.Sprintf(`\\.\HvSocketSystem\AddressInfo\{%s}`, cid)
u16, err := windows.UTF16PtrFromString(path)
if err != nil {
return nil, err
}
h, err := windows.CreateFile(
u16,
windows.GENERIC_READ|windows.GENERIC_WRITE,
0,
nil,
windows.CREATE_NEW,
0,
0,
)
if err != nil {
return nil, err
}

addrInfo := addressInfo{
systemID: cid,
virtualMachineID: vmid,
}
if passthru {
addrInfo.flags |= addressFlagPassthru
}

var ret uint32
if err := windows.DeviceIoControl(
h,
ioCtlHVSocketUpdateAddressInfo,
(*byte)(unsafe.Pointer(&addrInfo)),
uint32(unsafe.Sizeof(addrInfo)),
nil,
0,
&ret,
nil,
); err != nil {
return nil, err
}

return &addressInfoCloser{h}, nil
}

0 comments on commit 57059f2

Please sign in to comment.