From 003eef5d7741a5b2368d187cb1af4f7ae891dae6 Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy Date: Tue, 5 Mar 2024 15:09:51 -0500 Subject: [PATCH] Add hvsock service config annotation Signed-off-by: Hamza El-Saawy --- internal/annotations/annotations.go | 27 ++++++- internal/oci/annotations.go | 104 ++++++++++++++++-------- internal/oci/annotations_test.go | 118 +++++++++++++++++++++++++++- internal/oci/uvm.go | 10 ++- internal/uvm/create.go | 38 +++++---- internal/uvm/create_lcow.go | 10 ++- internal/uvm/create_wcow.go | 8 +- internal/uvm/start.go | 2 +- 8 files changed, 256 insertions(+), 61 deletions(-) diff --git a/internal/annotations/annotations.go b/internal/annotations/annotations.go index 19c713f3e9..8e72fe00ae 100644 --- a/internal/annotations/annotations.go +++ b/internal/annotations/annotations.go @@ -1,5 +1,6 @@ -// This package contains annotations that are not exposed to end users and mainly for -// testing and debugging purposes. +// This package contains annotations that are not exposed to end users and are either: +// 1. intended for testing and debugging purposes; or +// 2. rely on undocumented Windows APIs that are subject to change. // // Do not rely on these annotations to customize production workload behavior. package annotations @@ -7,6 +8,28 @@ package annotations // uVM specific annotations const ( + // UVMHyperVSocketConfigPrefix is the prefix of an annotation to map a [hyper-v socket] service GUID + // to a JSON-encoded string of its [configuration]. + // + // The service GUID should be part of the annotation. + // For example: + // + // "io.microsoft.virtualmachine.hv-socket.service-table.00000000-0000-0000-0000-000000000000" = + // "{\"AllowWildcardBinds\": true, \"BindSecurityDescriptor\": \"D:P(A;;FA;;;WD)\"}" + // + // If multiple annotations with the same GUID are present, then it is undefined which configuration will + // take precedence. + // + // For LCOW, it is preferred to use [ExtraVSockPorts], as vsock ports specified there will take precedence. + // + // # Warning + // + // Setting the configuration for special services (e.g., the GCS) can cause catastrophic failures. + // + // [hyper-v socket]: https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service + // [configuration]: https://learn.microsoft.com/en-us/virtualization/api/hcs/schemareference#HvSocketServiceConfig + UVMHyperVSocketConfigPrefix = "io.microsoft.virtualmachine.hv-socket.service-table." + // AdditionalRegistryValues specifies additional registry keys and their values to set in the WCOW UVM. // The format is a JSON-encoded string of an array containing [HCS RegistryValue] objects. // diff --git a/internal/oci/annotations.go b/internal/oci/annotations.go index ab52938e69..3b306b3c43 100644 --- a/internal/oci/annotations.go +++ b/internal/oci/annotations.go @@ -4,13 +4,15 @@ import ( "context" "encoding/json" "errors" + "fmt" + "slices" "strconv" "strings" "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" - "golang.org/x/exp/slices" + "github.com/Microsoft/go-winio/pkg/guid" iannotations "github.com/Microsoft/hcsshim/internal/annotations" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" "github.com/Microsoft/hcsshim/internal/log" @@ -66,6 +68,7 @@ func ParseAnnotationsDisableGMSA(ctx context.Context, s *specs.Spec) bool { } // parseAdditionalRegistryValues extracts the additional registry values to set from annotations. +// // Like the [parseAnnotation*] functions, this logs errors but does not return them. func parseAdditionalRegistryValues(ctx context.Context, a map[string]string) []hcsschema.RegistryValue { // rather than have users deal with nil vs []hcsschema.RegistryValue as returns, always @@ -81,7 +84,7 @@ func parseAdditionalRegistryValues(ctx context.Context, a map[string]string) []h t := []hcsschema.RegistryValue{} if err := json.Unmarshal([]byte(v), &t); err != nil { - logAnnotationParseError(ctx, k, v, "JSON string", err) + logAnnotationValueParseError(ctx, k, v, fmt.Sprintf("%T", t), err) return []hcsschema.RegistryValue{} } @@ -182,20 +185,63 @@ func parseAdditionalRegistryValues(ctx context.Context, a map[string]string) []h return slices.Clip(rvs) } +// parseHVSocketServiceTable extracts any additional Hyper-V socket service configurations from annotations. +// +// Like the [parseAnnotation*] functions, this logs errors but does not return them. +func parseHVSocketServiceTable(ctx context.Context, a map[string]string) map[string]hcsschema.HvSocketServiceConfig { + sc := make(map[string]hcsschema.HvSocketServiceConfig) + // TODO(go1.23) use range over functions to implement a functional `filter | map $ a` + for k, v := range a { + sGUID, found := strings.CutPrefix(k, iannotations.UVMHyperVSocketConfigPrefix) + if !found { + continue + } + + entry := log.G(ctx).WithFields(logrus.Fields{ + logfields.OCIAnnotation: k, + logfields.Value: v, + "guid": sGUID, + }) + + g, err := guid.FromString(sGUID) + if err != nil { + entry.WithError(err).Warn("invalid GUID string for Hyper-V socket service configuration annotation") + continue + } + sGUID = g.String() // overwrite the GUID string to standardize format (capitalization) + + conf := hcsschema.HvSocketServiceConfig{} + if err := json.Unmarshal([]byte(v), &conf); err != nil { + logAnnotationValueParseError(ctx, k, v, fmt.Sprintf("%T", conf), err) + continue + } + + if _, found := sc[sGUID]; found { + entry.WithFields(logrus.Fields{ + "guid": sGUID, + }).Warn("overwritting existing Hyper-V socket service configuration") + } + + if entry.Logger.IsLevelEnabled(logrus.TraceLevel) { + entry.WithField("configuration", log.Format(ctx, conf)).Trace("found Hyper-V socket service configuration annotation") + } + sc[sGUID] = conf + } + + return sc +} + // general annotation parsing // ParseAnnotationsBool searches `a` for `key` and if found verifies that the // value is `true` or `false` in any case. If `key` is not found returns `def`. func ParseAnnotationsBool(ctx context.Context, a map[string]string, key string, def bool) bool { if v, ok := a[key]; ok { - switch strings.ToLower(v) { - case "true": - return true - case "false": - return false - default: - logAnnotationParseError(ctx, key, v, logfields.Bool, nil) + b, err := strconv.ParseBool(v) + if err == nil { + return b } + logAnnotationValueParseError(ctx, key, v, logfields.Bool, err) } return def } @@ -206,17 +252,11 @@ func ParseAnnotationsBool(ctx context.Context, a map[string]string, key string, // the value they point at. func ParseAnnotationsNullableBool(ctx context.Context, a map[string]string, key string) *bool { if v, ok := a[key]; ok { - switch strings.ToLower(v) { - case "true": - _bool := true - return &_bool - case "false": - _bool := false - return &_bool - default: - err := errors.New("boolean fields must be 'true', 'false', or not set") - logAnnotationParseError(ctx, key, v, logfields.Bool, err) + b, err := strconv.ParseBool(v) + if err == nil { + return &b } + logAnnotationValueParseError(ctx, key, v, logfields.Bool, err) } return nil } @@ -230,7 +270,7 @@ func ParseAnnotationsInt32(ctx context.Context, a map[string]string, key string, v := int32(countu) return v } - logAnnotationParseError(ctx, key, v, logfields.Int32, err) + logAnnotationValueParseError(ctx, key, v, logfields.Int32, err) } return def } @@ -244,7 +284,7 @@ func ParseAnnotationsUint32(ctx context.Context, a map[string]string, key string v := uint32(countu) return v } - logAnnotationParseError(ctx, key, v, logfields.Uint32, err) + logAnnotationValueParseError(ctx, key, v, logfields.Uint32, err) } return def } @@ -257,15 +297,15 @@ func ParseAnnotationsUint64(ctx context.Context, a map[string]string, key string if err == nil { return countu } - logAnnotationParseError(ctx, key, v, logfields.Uint64, err) + logAnnotationValueParseError(ctx, key, v, logfields.Uint64, err) } return def } -// ParseAnnotationCommaSeparated searches `annotations` for `annotation` corresponding to a -// list of comma separated strings -func ParseAnnotationCommaSeparatedUint32(ctx context.Context, annotations map[string]string, annotation string, def []uint32) []uint32 { - cs, ok := annotations[annotation] +// ParseAnnotationCommaSeparated searches `a` for `annotation` corresponding to a +// list of comma separated strings. +func ParseAnnotationCommaSeparatedUint32(_ context.Context, a map[string]string, key string, def []uint32) []uint32 { + cs, ok := a[key] if !ok || cs == "" { return def } @@ -289,10 +329,10 @@ func ParseAnnotationsString(a map[string]string, key string, def string) string return def } -// ParseAnnotationCommaSeparated searches `annotations` for `annotation` corresponding to a -// list of comma separated strings -func ParseAnnotationCommaSeparated(annotation string, annotations map[string]string) []string { - cs, ok := annotations[annotation] +// ParseAnnotationCommaSeparated searches `a` for `key` corresponding to a +// list of comma separated strings. +func ParseAnnotationCommaSeparated(key string, a map[string]string) []string { + cs, ok := a[key] if !ok || cs == "" { return nil } @@ -300,7 +340,7 @@ func ParseAnnotationCommaSeparated(annotation string, annotations map[string]str return results } -func logAnnotationParseError(ctx context.Context, k, v, et string, err error) { +func logAnnotationValueParseError(ctx context.Context, k, v, et string, err error) { entry := log.G(ctx).WithFields(logrus.Fields{ logfields.OCIAnnotation: k, logfields.Value: v, @@ -309,5 +349,5 @@ func logAnnotationParseError(ctx context.Context, k, v, et string, err error) { if err != nil { entry = entry.WithError(err) } - entry.Warning("annotation could not be parsed") + entry.Warning("annotation value could not be parsed") } diff --git a/internal/oci/annotations_test.go b/internal/oci/annotations_test.go index 2b5374cc7b..0facf2d617 100644 --- a/internal/oci/annotations_test.go +++ b/internal/oci/annotations_test.go @@ -1,9 +1,12 @@ package oci import ( + "bytes" "context" + "encoding/json" "errors" "fmt" + "maps" "strings" "testing" @@ -51,6 +54,8 @@ func TestProccessAnnotations_Expansion(t *testing.T) { } for _, tt := range tests { + tt := tt // TODO (go1.22) remove this since loop aliasing is avoided (https://go.dev/ref/spec#Go_1.22) + // test correct expansion for _, v := range []string{"true", "false"} { t.Run(tt.name+"_disable_unsafe_"+v, func(subtest *testing.T) { @@ -225,7 +230,9 @@ func TestParseAdditionalRegistryValues(t *testing.T) { t.Logf("registry values:\n%s", tt.give) v := strings.ReplaceAll(tt.give, "\n", "") rvs := parseAdditionalRegistryValues(ctx, map[string]string{ - iannotations.AdditionalRegistryValues: v, + "some-random-annotation": "random", + "not-microsoft.virtualmachine.wcow.additional-reg-keys": "this is fake", + iannotations.AdditionalRegistryValues: v, }) want := tt.want if want == nil { @@ -237,3 +244,112 @@ func TestParseAdditionalRegistryValues(t *testing.T) { }) } } + +func TestParseHVSocketServiceTable(t *testing.T) { + ctx := context.Background() + + toString := func(t *testing.T, v hcsschema.HvSocketServiceConfig) string { + t.Helper() + + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + enc.SetIndent("", "") + + if err := enc.Encode(v); err != nil { + t.Fatalf("encode %v to JSON: %v", v, err) + } + + return strings.TrimSpace(buf.String()) + } + + g1 := "0b52781f-b24d-5685-ddf6-69830ed40ec3" + g2 := "00000000-0000-0000-0000-000000000000" + + defaultConfig := hcsschema.HvSocketServiceConfig{ + AllowWildcardBinds: true, + BindSecurityDescriptor: "D:P(A;;FA;;;WD)", + } + defaultConfigStr := toString(t, defaultConfig) + + disabledConfig := hcsschema.HvSocketServiceConfig{ + Disabled: true, + } + disabledConfigStr := toString(t, disabledConfig) + + for _, tt := range []struct { + name string + give map[string]string + want map[string]hcsschema.HvSocketServiceConfig + }{ + { + name: "empty", + }, + { + name: "single", + give: map[string]string{ + iannotations.UVMHyperVSocketConfigPrefix + g1: defaultConfigStr, + }, + want: map[string]hcsschema.HvSocketServiceConfig{ + g1: defaultConfig, + }, + }, + { + name: "invalid guid", + give: map[string]string{ + iannotations.UVMHyperVSocketConfigPrefix + "not-a-guid": defaultConfigStr, + }, + }, + { + name: "invalid config", + give: map[string]string{ + iannotations.UVMHyperVSocketConfigPrefix + g1: `["not", "a", "valid", "config"]`, + }, + }, + { + name: "override", + give: map[string]string{ + iannotations.UVMHyperVSocketConfigPrefix + g1: defaultConfigStr, + iannotations.UVMHyperVSocketConfigPrefix + strings.ToUpper(g1): defaultConfigStr, + }, + want: map[string]hcsschema.HvSocketServiceConfig{ + g1: defaultConfig, + }, + }, + { + name: "multiple", + give: map[string]string{ + iannotations.UVMHyperVSocketConfigPrefix + strings.ToUpper(g1): defaultConfigStr, + iannotations.UVMHyperVSocketConfigPrefix + g2: disabledConfigStr, + + iannotations.UVMHyperVSocketConfigPrefix + g1: `["not", "a", "valid", "config"]`, + iannotations.UVMHyperVSocketConfigPrefix + "not-a-guid": defaultConfigStr, + "also.not-a-guid": disabledConfigStr, + }, + want: map[string]hcsschema.HvSocketServiceConfig{ + g1: defaultConfig, + g2: disabledConfig, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + annots := map[string]string{ + "some-random-annotation": "random", + "io.microsoft.virtualmachine.hv-socket.service-table": "should be ignored", + "not-microsoft.virtualmachine.hv-socket.service-table": "this is fake", + } + maps.Copy(annots, tt.give) + t.Logf("annotations:\n%v", annots) + + rvs := parseHVSocketServiceTable(ctx, annots) + t.Logf("got %v", rvs) + want := tt.want + if want == nil { + want = map[string]hcsschema.HvSocketServiceConfig{} + } + if diff := cmp.Diff(want, rvs); diff != "" { + t.Fatal(diff) + } + }) + } +} diff --git a/internal/oci/uvm.go b/internal/oci/uvm.go index caf698a645..ccf6506072 100644 --- a/internal/oci/uvm.go +++ b/internal/oci/uvm.go @@ -5,6 +5,7 @@ package oci import ( "context" "errors" + "maps" "strconv" runhcsopts "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options" @@ -157,7 +158,7 @@ func handleAnnotationKernelDirectBoot(ctx context.Context, a map[string]string, } // handleAnnotationPreferredRootFSType handles parsing annotations.PreferredRootFSType and setting -// implied options from the result +// implied options from the result. func handleAnnotationPreferredRootFSType(ctx context.Context, a map[string]string, lopts *uvm.OptionsLCOW) { lopts.PreferredRootFSType = parseAnnotationsPreferredRootFSType(ctx, a, annotations.PreferredRootFSType, lopts.PreferredRootFSType) switch lopts.PreferredRootFSType { @@ -189,7 +190,7 @@ func handleAnnotationFullyPhysicallyBacked(ctx context.Context, a map[string]str } // handleSecurityPolicy handles parsing SecurityPolicy and NoSecurityHardware and setting -// implied options from the results. Both LCOW only, not WCOW +// implied options from the results. Both LCOW only, not WCOW. func handleSecurityPolicy(ctx context.Context, a map[string]string, lopts *uvm.OptionsLCOW) { lopts.SecurityPolicy = ParseAnnotationsString(a, annotations.SecurityPolicy, lopts.SecurityPolicy) // allow actual isolated boot etc to be ignored if we have no hardware. Required for dev @@ -226,7 +227,7 @@ func handleSecurityPolicy(ctx context.Context, a map[string]string, lopts *uvm.O } } -// sets options common to both WCOW and LCOW from annotations +// sets options common to both WCOW and LCOW from annotations. func specToUVMCreateOptionsCommon(ctx context.Context, opts *uvm.Options, s *specs.Spec) { opts.MemorySizeInMB = ParseAnnotationsMemory(ctx, s, annotations.MemorySizeInMB, opts.MemorySizeInMB) opts.LowMMIOGapInMB = ParseAnnotationsUint64(ctx, s.Annotations, annotations.MemoryLowMMIOGapInMB, opts.LowMMIOGapInMB) @@ -244,6 +245,7 @@ func specToUVMCreateOptionsCommon(ctx context.Context, opts *uvm.Options, s *spe opts.ProcessDumpLocation = ParseAnnotationsString(s.Annotations, annotations.ContainerProcessDumpLocation, opts.ProcessDumpLocation) opts.NoWritableFileShares = ParseAnnotationsBool(ctx, s.Annotations, annotations.DisableWritableFileShares, opts.NoWritableFileShares) opts.DumpDirectoryPath = ParseAnnotationsString(s.Annotations, annotations.DumpDirectoryPath, opts.DumpDirectoryPath) + maps.Copy(opts.AdditionalHyperVConfig, parseHVSocketServiceTable(ctx, s.Annotations)) } // SpecToUVMCreateOpts parses `s` and returns either `*uvm.OptionsLCOW` or @@ -304,7 +306,7 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) ( wopts.DisableCompartmentNamespace = ParseAnnotationsBool(ctx, s.Annotations, annotations.DisableCompartmentNamespace, wopts.DisableCompartmentNamespace) wopts.NoDirectMap = ParseAnnotationsBool(ctx, s.Annotations, annotations.VSMBNoDirectMap, wopts.NoDirectMap) wopts.NoInheritHostTimezone = ParseAnnotationsBool(ctx, s.Annotations, annotations.NoInheritHostTimezone, wopts.NoInheritHostTimezone) - wopts.AdditionalRegistryKeys = parseAdditionalRegistryValues(ctx, s.Annotations) + wopts.AdditionalRegistryKeys = append(wopts.AdditionalRegistryKeys, parseAdditionalRegistryValues(ctx, s.Annotations)...) handleAnnotationFullyPhysicallyBacked(ctx, s.Annotations, wopts) return wopts, nil } diff --git a/internal/uvm/create.go b/internal/uvm/create.go index 6a16e64be5..26f16f1253 100644 --- a/internal/uvm/create.go +++ b/internal/uvm/create.go @@ -102,10 +102,13 @@ type Options struct { // DumpDirectoryPath is the path of the directory inside which all debug dumps etc are stored. DumpDirectoryPath string + + // AdditionalHyperVConfig are extra Hyper-V socket configurations to provide. + AdditionalHyperVConfig map[string]hcsschema.HvSocketServiceConfig } // Verifies that the final UVM options are correct and supported. -func verifyOptions(ctx context.Context, options interface{}) error { +func verifyOptions(_ context.Context, options interface{}) error { switch opts := options.(type) { case *OptionsLCOW: if opts.EnableDeferredCommit && !opts.AllowOvercommit { @@ -150,15 +153,16 @@ func verifyOptions(ctx context.Context, options interface{}) error { // If `owner` is empty it will be set to the calling executables name. func newDefaultOptions(id, owner string) *Options { opts := &Options{ - ID: id, - Owner: owner, - MemorySizeInMB: 1024, - AllowOvercommit: true, - EnableDeferredCommit: false, - ProcessorCount: defaultProcessorCount(), - FullyPhysicallyBacked: false, - NoWritableFileShares: false, - SCSIControllerCount: 1, + ID: id, + Owner: owner, + MemorySizeInMB: 1024, + AllowOvercommit: true, + EnableDeferredCommit: false, + ProcessorCount: defaultProcessorCount(), + FullyPhysicallyBacked: false, + NoWritableFileShares: false, + SCSIControllerCount: 1, + AdditionalHyperVConfig: make(map[string]hcsschema.HvSocketServiceConfig), } if opts.Owner == "" { @@ -302,7 +306,7 @@ func (uvm *UtilityVM) CreateProcess(ctx context.Context, settings interface{}) ( // IsOCI returns false, indicating the parameters to CreateProcess should not // include an OCI spec. -func (uvm *UtilityVM) IsOCI() bool { +func (*UtilityVM) IsOCI() bool { return false } @@ -342,9 +346,8 @@ func (uvm *UtilityVM) normalizeProcessorCount(ctx context.Context, requested int "assigned": hostCount, }).Warn("Changing user requested CPUCount to current number of processors") return hostCount - } else { - return requested } + return requested } // ProcessorCount returns the number of processors actually assigned to the UVM. @@ -353,7 +356,7 @@ func (uvm *UtilityVM) ProcessorCount() int32 { } // PhysicallyBacked returns if the UVM is backed by physical memory -// (Over commit and deferred commit both false) +// (Over commit and deferred commit both false). func (uvm *UtilityVM) PhysicallyBacked() bool { return uvm.physicallyBacked } @@ -377,12 +380,12 @@ func (uvm *UtilityVM) normalizeMemorySize(ctx context.Context, requested uint64) } // DevicesPhysicallyBacked describes if additional devices added to the UVM -// should be physically backed +// should be physically backed. func (uvm *UtilityVM) DevicesPhysicallyBacked() bool { return uvm.devicesPhysicallyBacked } -// VSMBNoDirectMap returns if VSMB devices should be mounted with `NoDirectMap` set to true +// VSMBNoDirectMap returns if VSMB devices should be mounted with `NoDirectMap` set to true. func (uvm *UtilityVM) VSMBNoDirectMap() bool { return uvm.vsmbNoDirectMap } @@ -394,11 +397,12 @@ func (uvm *UtilityVM) NoWritableFileShares() bool { // Closes the external GCS connection if it is being used and also closes the // listener for GCS connection. func (uvm *UtilityVM) CloseGCSConnection() (err error) { + // TODO: errors.Join to avoid ignoring an error if uvm.gc != nil { err = uvm.gc.Close() } if uvm.gcListener != nil { err = uvm.gcListener.Close() } - return + return err } diff --git a/internal/uvm/create_lcow.go b/internal/uvm/create_lcow.go index 6448d90934..c41f415f78 100644 --- a/internal/uvm/create_lcow.go +++ b/internal/uvm/create_lcow.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "fmt" "io" + "maps" "net" "os" "path/filepath" @@ -342,7 +343,7 @@ Example JSON document produced once the hcsschema.ComputeSytem returned by makeL // A large part of difference between the SNP case and the usual kernel+option+initrd case is to do with booting // from a VMGS file. The VMGS part may be used other than with SNP so is split out here. -// Make a hcsschema.ComputeSytem with the parts that target booting from a VMGS file +// Make a hcsschema.ComputeSytem with the parts that target booting from a VMGS file. func makeLCOWVMGSDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcsschema.ComputeSystem, err error) { // Raise an error if instructed to use a particular sort of rootfs. if opts.PreferredRootFSType != PreferredRootFSTypeNA { @@ -437,6 +438,8 @@ func makeLCOWVMGSDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ }, } + maps.Copy(doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable, opts.AdditionalHyperVConfig) + // Set permissions for the VSock ports: // entropyVsockPort - 1 is the entropy port, // linuxLogVsockPort - 109 used by vsockexec to log stdout/stderr logging, @@ -444,7 +447,7 @@ func makeLCOWVMGSDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ hvSockets := []uint32{entropyVsockPort, linuxLogVsockPort, gcs.LinuxGcsVsockPort, gcs.LinuxGcsVsockPort + 1} hvSockets = append(hvSockets, opts.ExtraVSockPorts...) for _, whichSocket := range hvSockets { - key := fmt.Sprintf("%08x-facb-11e6-bd58-64006a7986d3", whichSocket) // format of a linux hvsock GUID is port#-facb-11e6-bd58-64006a7986d3 + key := winio.VsockServiceID(whichSocket).String() doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable[key] = hcsschema.HvSocketServiceConfig{ AllowWildcardBinds: true, BindSecurityDescriptor: "D:P(A;;FA;;;WD)", @@ -638,6 +641,7 @@ func makeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcs // Allow administrators and SYSTEM to bind to vsock sockets // so that we can create a GCS log socket. DefaultBindSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)", + ServiceTable: make(map[string]hcsschema.HvSocketServiceConfig), }, }, Plan9: &hcsschema.Plan9{}, @@ -645,6 +649,8 @@ func makeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcs }, } + maps.Copy(doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable, opts.AdditionalHyperVConfig) + // Handle StorageQoS if set if opts.StorageQoSBandwidthMaximum > 0 || opts.StorageQoSIopsMaximum > 0 { doc.VirtualMachine.StorageQoS = &hcsschema.StorageQoS{ diff --git a/internal/uvm/create_wcow.go b/internal/uvm/create_wcow.go index 7c4107b1fe..62c2d2bf95 100644 --- a/internal/uvm/create_wcow.go +++ b/internal/uvm/create_wcow.go @@ -5,6 +5,7 @@ package uvm import ( "context" "fmt" + "maps" "os" "path/filepath" @@ -196,9 +197,10 @@ func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW, uv Devices: &hcsschema.Devices{ HvSocket: &hcsschema.HvSocket2{ HvSocketConfig: &hcsschema.HvSocketSystemConfig{ - // Allow administrators and SYSTEM to bind to vsock sockets - // so that we can create a GCS log socket. + // Allow administrators and SYSTEM to bind to hyper-v sockets + // so that we can communicate to the GCS. DefaultBindSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)", + ServiceTable: make(map[string]hcsschema.HvSocketServiceConfig), }, }, VirtualSmb: virtualSMB, @@ -206,6 +208,8 @@ func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW, uv }, } + maps.Copy(doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable, opts.AdditionalHyperVConfig) + // Handle StorageQoS if set if opts.StorageQoSBandwidthMaximum > 0 || opts.StorageQoSIopsMaximum > 0 { doc.VirtualMachine.StorageQoS = &hcsschema.StorageQoS{ diff --git a/internal/uvm/start.go b/internal/uvm/start.go index 3825be9e53..1a59168355 100644 --- a/internal/uvm/start.go +++ b/internal/uvm/start.go @@ -122,7 +122,7 @@ func parseLogrus(o *Options) OutputHandler { } // When using an external GCS connection it is necessary to send a ModifySettings request -// for HvSockt so that the GCS can setup some registry keys that are required for running +// for HvSocket so that the GCS can setup some registry keys that are required for running // containers inside the UVM. In non external GCS connection scenarios this is done by the // HCS immediately after the GCS connection is done. Since, we are using the external GCS // connection we should do that setup here after we connect with the GCS.