diff --git a/go.mod b/go.mod index a6836010b5..fc6ee74124 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( go.etcd.io/bbolt v1.4.0 go.opencensus.io v0.24.0 go.uber.org/mock v0.6.0 + golang.org/x/net v0.43.0 golang.org/x/sync v0.16.0 golang.org/x/sys v0.35.0 google.golang.org/grpc v1.75.0 @@ -113,7 +114,6 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect golang.org/x/crypto v0.41.0 // indirect golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect golang.org/x/text v0.28.0 // indirect golang.org/x/tools v0.36.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect diff --git a/internal/gcs/bridge.go b/internal/gcs/bridge.go index 3f09b3d1ff..b03b555662 100644 --- a/internal/gcs/bridge.go +++ b/internal/gcs/bridge.go @@ -332,7 +332,7 @@ func (brdg *bridge) recvLoop() error { } case prot.MsgTypeNotify: - if typ != prot.NotifyContainer|prot.MsgTypeNotify { + if typ != prot.NotifyContainer|prot.ComputeSystem|prot.MsgTypeNotify { return fmt.Errorf("bridge received unknown unknown notification message %s", typ) } var ntf prot.ContainerNotification diff --git a/internal/gcs/bridge_test.go b/internal/gcs/bridge_test.go index 3da35e9d9d..73b73820f8 100644 --- a/internal/gcs/bridge_test.go +++ b/internal/gcs/bridge_test.go @@ -179,7 +179,7 @@ func notifyThroughBridge(t *testing.T, typ prot.MsgType, msg interface{}, fn not func TestBridgeNotify(t *testing.T) { ntf := &prot.ContainerNotification{Operation: "testing"} recvd := false - err := notifyThroughBridge(t, prot.MsgTypeNotify|prot.NotifyContainer, ntf, func(nntf *prot.ContainerNotification) error { + err := notifyThroughBridge(t, prot.MsgTypeNotify|prot.ComputeSystem|prot.NotifyContainer, ntf, func(nntf *prot.ContainerNotification) error { if !reflect.DeepEqual(ntf, nntf) { t.Errorf("%+v != %+v", ntf, nntf) } diff --git a/internal/gcs/guestcaps.go b/internal/gcs/guestcaps.go index 22ca548d3b..58b1f8ebb7 100644 --- a/internal/gcs/guestcaps.go +++ b/internal/gcs/guestcaps.go @@ -100,3 +100,7 @@ func (w *WCOWGuestDefinedCapabilities) IsDumpStacksSupported() bool { func (w *WCOWGuestDefinedCapabilities) IsDeleteContainerStateSupported() bool { return w.DeleteContainerStateSupported } + +func (w *WCOWGuestDefinedCapabilities) IsLogForwardingSupported() bool { + return w.LogForwardingSupported +} diff --git a/internal/gcs/guestconnection.go b/internal/gcs/guestconnection.go index 9107dd4d3b..35e6709d15 100644 --- a/internal/gcs/guestconnection.go +++ b/internal/gcs/guestconnection.go @@ -184,6 +184,20 @@ func (gc *GuestConnection) Modify(ctx context.Context, settings interface{}) (er return gc.brdg.RPC(ctx, prot.RPCModifySettings, &req, &resp, false) } +func (gc *GuestConnection) ModifyServiceSettings(ctx context.Context, serviceType prot.ServiceModifyPropertyType, settings interface{}) (err error) { + ctx, span := oc.StartSpan(ctx, "gcs::GuestConnection::ModifyServiceSettings", oc.WithClientSpanKind) + defer span.End() + defer func() { oc.SetSpanStatus(span, err) }() + + req := prot.ServiceModificationRequest{ + RequestBase: makeRequest(ctx, nullContainerID), + PropertyType: string(serviceType), + Settings: settings, + } + var resp prot.ResponseBase + return gc.brdg.RPC(ctx, prot.RPCModifyServiceSettings, &req, &resp, false) +} + func (gc *GuestConnection) DumpStacks(ctx context.Context) (response string, err error) { ctx, span := oc.StartSpan(ctx, "gcs::GuestConnection::DumpStacks", oc.WithClientSpanKind) defer span.End() diff --git a/internal/gcs/guestconnection_test.go b/internal/gcs/guestconnection_test.go index 47d8be5dd7..d8bc04aeea 100644 --- a/internal/gcs/guestconnection_test.go +++ b/internal/gcs/guestconnection_test.go @@ -131,7 +131,7 @@ func simpleGcsLoop(t *testing.T, rw io.ReadWriter) error { return err } time.Sleep(50 * time.Millisecond) - err = sendJSON(t, rw, prot.MsgType(prot.MsgTypeNotify|prot.NotifyContainer), 0, &prot.ContainerNotification{ + err = sendJSON(t, rw, prot.MsgType(prot.MsgTypeNotify|prot.ComputeSystem|prot.NotifyContainer), 0, &prot.ContainerNotification{ RequestBase: prot.RequestBase{ ContainerID: req.ContainerID, }, diff --git a/internal/gcs/prot/protocol.go b/internal/gcs/prot/protocol.go index 6be8f95482..e976bb3e20 100644 --- a/internal/gcs/prot/protocol.go +++ b/internal/gcs/prot/protocol.go @@ -71,6 +71,15 @@ var WindowsGcsHvHostID = guid.GUID{ Data4: [8]uint8{0x93, 0xfe, 0x42, 0x96, 0x9a, 0xe6, 0xd8, 0xd1}, } +// WindowsLoggingHvsockServiceID is the hvsock service ID that the Windows log forward service +// will connect to. 172dad59-976d-45f2-8b6c-6d1b13f2ac4d +var WindowsLoggingHvsockServiceID = guid.GUID{ + Data1: 0x172dad59, + Data2: 0x976d, + Data3: 0x45f2, + Data4: [8]uint8{0x8b, 0x6c, 0x6d, 0x1b, 0x13, 0xf2, 0xac, 0x4d}, +} + type AnyInString struct { Value interface{} } @@ -83,10 +92,17 @@ func (a *AnyInString) UnmarshalText(b []byte) error { return json.Unmarshal(b, &a.Value) } +const ( + // Message_Category for the GCS protocol. + ComputeSystem = 0x00100000 + ComputeService = 0x00200000 +) + type RPCProc uint32 const ( - RPCCreate RPCProc = (iota+1)<<8 | 1 + // Compute System RPCs + RPCCreate RPCProc = ComputeSystem | (iota+1)<<8 | 1 RPCStart RPCShutdownGraceful RPCShutdownForced @@ -103,6 +119,17 @@ const ( RPCLifecycleNotification ) +const ( + // Compute Service RPCs + RPCModifyServiceSettings RPCProc = ComputeService | (iota+1)<<8 | 1 +) + +type ServiceModifyPropertyType string + +const ( + LogForwardService = ServiceModifyPropertyType("LogForwardService") +) + func (rpc RPCProc) String() string { switch rpc { case RPCCreate: @@ -135,6 +162,8 @@ func (rpc RPCProc) String() string { return "UpdateContainer" case RPCLifecycleNotification: return "LifecycleNotification" + case RPCModifyServiceSettings: + return "ModifyServiceSettings" default: return "0x" + strconv.FormatUint(uint64(rpc), 16) } @@ -143,10 +172,10 @@ func (rpc RPCProc) String() string { type MsgType uint32 const ( - MsgTypeRequest MsgType = 0x10100000 - MsgTypeResponse MsgType = 0x20100000 - MsgTypeNotify MsgType = 0x30100000 - MsgTypeMask MsgType = 0xfff00000 + MsgTypeRequest MsgType = 0x10000000 + MsgTypeResponse MsgType = 0x20000000 + MsgTypeNotify MsgType = 0x30000000 + MsgTypeMask MsgType = 0xf0000000 NotifyContainer = 1<<8 | 1 ) @@ -160,7 +189,7 @@ func (typ MsgType) String() string { s = "Response(" case MsgTypeNotify: s = "Notify(" - switch typ - MsgTypeNotify { + switch typ - (ComputeSystem | MsgTypeNotify) { case NotifyContainer: s += "Container" default: @@ -267,6 +296,12 @@ type ContainerNotification struct { ResultInfo AnyInString `json:",omitempty"` } +type ServiceModificationRequest struct { + RequestBase + PropertyType string // ServiceModifyPropertyType + Settings interface{} `json:",omitempty"` +} + type ContainerExecuteProcess struct { RequestBase Settings ExecuteProcessSettings @@ -345,13 +380,14 @@ type ContainerModifySettings struct { } type GcsCapabilities struct { - SendHostCreateMessage bool - SendHostStartMessage bool - HvSocketConfigOnStartup bool - SendLifecycleNotifications bool - SupportedSchemaVersions []hcsschema.Version - RuntimeOsType string - GuestDefinedCapabilities json.RawMessage + SendHostCreateMessage bool + SendHostStartMessage bool + HvSocketConfigOnStartup bool + SendLifecycleNotifications bool + ModifyServiceSettingsSupported bool + SupportedSchemaVersions []hcsschema.Version + RuntimeOsType string + GuestDefinedCapabilities json.RawMessage } type ContainerCreateResponse struct { diff --git a/internal/hcs/schema1/schema1.go b/internal/hcs/schema1/schema1.go index d1f219cfad..e0a3010f8d 100644 --- a/internal/hcs/schema1/schema1.go +++ b/internal/hcs/schema1/schema1.go @@ -221,6 +221,7 @@ type GuestDefinedCapabilities struct { DumpStacksSupported bool `json:",omitempty"` DeleteContainerStateSupported bool `json:",omitempty"` UpdateContainerSupported bool `json:",omitempty"` + LogForwardingSupported bool `json:",omitempty"` } // GuestConnectionInfo is the structure of an iterm return by a GuestConnection call on a utility VM diff --git a/internal/oci/uvm.go b/internal/oci/uvm.go index 889e31f09e..a2a701b724 100644 --- a/internal/oci/uvm.go +++ b/internal/oci/uvm.go @@ -416,7 +416,12 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) ( if err := handleWCOWSecurityPolicy(ctx, s.Annotations, wopts); err != nil { return nil, err } - + // If security policy is enable, wopts.ForwardLogs default value should be false + if wopts.SecurityPolicyEnabled { + wopts.ForwardLogs = false + } + wopts.LogSources = ParseAnnotationsString(s.Annotations, annotations.LogSources, wopts.LogSources) + wopts.ForwardLogs = ParseAnnotationsBool(ctx, s.Annotations, annotations.ForwardLogs, wopts.ForwardLogs) return wopts, nil } return nil, errors.New("cannot create UVM opts spec is not LCOW or WCOW") diff --git a/internal/oci/uvm_test.go b/internal/oci/uvm_test.go index f8405c64c6..609cab5225 100644 --- a/internal/oci/uvm_test.go +++ b/internal/oci/uvm_test.go @@ -117,6 +117,10 @@ func Test_SpecToUVMCreateOptions_Default_WCOW(t *testing.T) { wopts := (opts).(*uvm.OptionsWCOW) dopts := uvm.NewDefaultOptionsWCOW(t.Name(), "") + // output handler equality is always false, so set to nil + wopts.OutputHandlerCreator = nil + dopts.OutputHandlerCreator = nil + if !cmp.Equal(*wopts, *dopts) { t.Fatalf("should not have updated create options from default when no annotation are provided:\n%s", cmp.Diff(wopts, dopts)) } diff --git a/internal/protocol/guestrequest/types.go b/internal/protocol/guestrequest/types.go index aa27e5badc..dc35ceaf8a 100644 --- a/internal/protocol/guestrequest/types.go +++ b/internal/protocol/guestrequest/types.go @@ -74,3 +74,17 @@ const ( STDErrHandle STDIOHandle = "StdErr" AllHandles STDIOHandle = "All" ) + +type LogForwardServiceRPCRequest struct { + RPCType RPCType `json:"RPCType,omitempty"` // "LogForwardService" + Settings string `json:"Settings,omitempty"` +} + +type RPCType string + +const ( + // LogForwardServiceRPC is the RPC type for the log forward service. + RPCModifyServiceSettings RPCType = "ModifyServiceSettings" + RPCStartLogForwarding RPCType = "StartLogForwarding" + RPCStopLogForwarding RPCType = "StopLogForwarding" +) diff --git a/internal/uvm/create_wcow.go b/internal/uvm/create_wcow.go index 645df6df9b..cd15722284 100644 --- a/internal/uvm/create_wcow.go +++ b/internal/uvm/create_wcow.go @@ -59,6 +59,10 @@ type OptionsWCOW struct { // AdditionalRegistryKeys are Registry keys and their values to additionally add to the uVM. AdditionalRegistryKeys []hcsschema.RegistryValue + + OutputHandlerCreator OutputHandlerCreator // Creates an [OutputHandler] that controls how output received over HVSocket from the UVM is handled. Defaults to parsing output as ETW Log events + LogSources string // ETW providers to be set for the logging service + ForwardLogs bool // Whether to forward logs to the host or not } func defaultConfidentialWCOWOSBootFilesPath() string { @@ -89,6 +93,9 @@ func NewDefaultOptionsWCOW(id, owner string) *OptionsWCOW { Options: newDefaultOptions(id, owner), AdditionalRegistryKeys: []hcsschema.RegistryValue{}, ConfidentialWCOWOptions: &ConfidentialWCOWOptions{}, + OutputHandlerCreator: parseLogrus, + ForwardLogs: true, // Default to true for WCOW, and set to false for CWCOW in internal/oci/uvm.go SpecToUVMCreateOpts + LogSources: "", } } @@ -259,6 +266,12 @@ func prepareCommonConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWC } maps.Copy(doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable, opts.AdditionalHyperVConfig) + key := prot.WindowsLoggingHvsockServiceID.String() + doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable[key] = hcsschema.HvSocketServiceConfig{ + AllowWildcardBinds: true, + BindSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)", + ConnectSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)", + } // Handle StorageQoS if set if opts.StorageQoSBandwidthMaximum > 0 || opts.StorageQoSIopsMaximum > 0 { @@ -507,6 +520,8 @@ func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error noWritableFileShares: opts.NoWritableFileShares, createOpts: *opts, blockCIMMounts: make(map[string]*UVMMountedBlockCIMs), + logSources: opts.LogSources, + forwardLogs: opts.ForwardLogs, } defer func() { @@ -536,6 +551,15 @@ func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error return nil, fmt.Errorf("error while creating the compute system: %w", err) } + // Create a socket that the executed program can send to. This is usually + // used by Log Forward Service to send log data. + uvm.outputHandler = opts.OutputHandlerCreator(opts.Options) + uvm.outputProcessingDone = make(chan struct{}) + uvm.outputListener, err = winio.ListenHvsock(&winio.HvsockAddr{ + VMID: uvm.RuntimeID(), + ServiceID: prot.WindowsLoggingHvsockServiceID, + }) + gcsServiceID := prot.WindowsGcsHvsockServiceID if opts.SecurityPolicyEnabled { gcsServiceID = prot.WindowsSidecarGcsHvsockServiceID diff --git a/internal/uvm/log_wcow.go b/internal/uvm/log_wcow.go new file mode 100644 index 0000000000..5a74bf34c9 --- /dev/null +++ b/internal/uvm/log_wcow.go @@ -0,0 +1,75 @@ +//go:build windows + +package uvm + +import ( + "context" + + "github.com/Microsoft/hcsshim/internal/gcs" + "github.com/Microsoft/hcsshim/internal/gcs/prot" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" +) + +func (uvm *UtilityVM) StartLogForwarding(ctx context.Context) error { + // Implementation for starting the log forwarding service + if uvm.OS() != "windows" || uvm.gc == nil { + return errNotSupported + } + + wcaps := gcs.GetWCOWCapabilities(uvm.gc.Capabilities()) + if wcaps != nil && wcaps.IsLogForwardingSupported() { + req := guestrequest.LogForwardServiceRPCRequest{ + RPCType: guestrequest.RPCStartLogForwarding, + Settings: "", + } + err := uvm.gc.ModifyServiceSettings(ctx, prot.LogForwardService, req) + if err != nil { + return err + } + } else { + log.G(ctx).WithField("os", uvm.operatingSystem).Error("Log forwarding not supported for this OS") + } + return nil +} + +func (uvm *UtilityVM) StopLogForwarding(ctx context.Context) error { + // Implementation for stopping the log forwarding service + if uvm.OS() != "windows" || uvm.gc == nil { + return errNotSupported + } + + wcaps := gcs.GetWCOWCapabilities(uvm.gc.Capabilities()) + if wcaps != nil && wcaps.IsLogForwardingSupported() { + req := guestrequest.LogForwardServiceRPCRequest{ + RPCType: guestrequest.RPCStopLogForwarding, + Settings: "", + } + err := uvm.gc.ModifyServiceSettings(ctx, prot.LogForwardService, req) + if err != nil { + return err + } + } + return nil +} + +func (uvm *UtilityVM) SetLogSources(ctx context.Context) error { + // Implementation for setting the log sources + if uvm.OS() != "windows" || uvm.gc == nil { + return errNotSupported + } + + wcaps := gcs.GetWCOWCapabilities(uvm.gc.Capabilities()) + if wcaps != nil && wcaps.IsLogForwardingSupported() { + // Make a call to the GCS to set the ETW providers + req := guestrequest.LogForwardServiceRPCRequest{ + RPCType: guestrequest.RPCModifyServiceSettings, + Settings: uvm.logSources, + } + err := uvm.gc.ModifyServiceSettings(ctx, prot.LogForwardService, req) + if err != nil { + return err + } + } + return nil +} diff --git a/internal/uvm/start.go b/internal/uvm/start.go index de8046f94d..6b047b2b70 100644 --- a/internal/uvm/start.go +++ b/internal/uvm/start.go @@ -11,9 +11,11 @@ import ( "fmt" "io" "net" + "sync" "time" "github.com/sirupsen/logrus" + "golang.org/x/net/netutil" "golang.org/x/sync/errgroup" "golang.org/x/sys/windows" @@ -65,6 +67,17 @@ func (e *gcsLogEntry) UnmarshalJSON(b []byte) error { if e.Level < logrus.ErrorLevel { e.Level = logrus.ErrorLevel } + if e.Fields["Source"] == "ETW" { + // Windows ETW log entry + // Original ETW Event Data may have "message" or "Message" field instead of "msg" + if msg, ok := e.Fields["message"].(string); ok { + e.Message = msg + delete(e.Fields, "message") + } else if msg, ok := e.Fields["Message"].(string); ok { + e.Message = msg + delete(e.Fields, "Message") + } + } // Clear special fields. delete(e.Fields, "time") delete(e.Fields, "level") @@ -182,7 +195,7 @@ func (uvm *UtilityVM) Start(ctx context.Context) (err error) { // until the host accepts and closes the entropy connection. if uvm.entropyListener != nil { g.Go(func() error { - conn, err := uvm.acceptAndClose(gctx, uvm.entropyListener) + conn, err := uvm.accept(gctx, uvm.entropyListener, true) uvm.entropyListener = nil if err != nil { e.WithError(err).Error("failed to connect to entropy socket") @@ -199,22 +212,58 @@ func (uvm *UtilityVM) Start(ctx context.Context) (err error) { } if uvm.outputListener != nil { - g.Go(func() error { - conn, err := uvm.acceptAndClose(gctx, uvm.outputListener) - uvm.outputListener = nil - if err != nil { - e.WithError(err).Error("failed to connect to log socket") - close(uvm.outputProcessingDone) - return fmt.Errorf("failed to connect to log socket: %w", err) - } - go func() { - e.Trace("uvm output handler starting") - uvm.outputHandler(conn) - close(uvm.outputProcessingDone) - e.Debug("uvm output handler finished") - }() - return nil - }) + switch uvm.operatingSystem { + case "windows": + // Windows specific handling + // For windows, the Listener can recieve a connection later, so we + // start the output handler in a goroutine with a non-timeout context. + // This allows the output handler to run independently of the UVM Create's + // lifecycle. This approach potentially allows to wait for reconnections, + // while limiting the number of concurrent connections to 1. + // This is useful for the case when logging service is restarted. + g.Go(func() error { + go func() { + var wg sync.WaitGroup + uvm.outputListener = netutil.LimitListener(uvm.outputListener, 1) + for { + conn, err := uvm.accept(context.Background(), uvm.outputListener, false) + if err != nil { + e.WithError(err).Error("failed to connect to log socket") + close(uvm.outputProcessingDone) + break + } + wg.Add(1) + go func() { + defer wg.Done() + e.Info("uvm output handler starting") + uvm.outputHandler(conn) + }() + e.Info("uvm output handler finished") + } + wg.Wait() + close(uvm.outputProcessingDone) + }() + return nil + }) + default: + // Default handling + g.Go(func() error { + conn, err := uvm.accept(gctx, uvm.outputListener, true) + uvm.outputListener = nil + if err != nil { + e.WithError(err).Error("failed to connect to log socket") + close(uvm.outputProcessingDone) + return fmt.Errorf("failed to connect to log socket: %w", err) + } + go func() { + e.Trace("uvm output handler starting") + uvm.outputHandler(conn) + close(uvm.outputProcessingDone) + e.Debug("uvm output handler finished") + }() + return nil + }) + } } err = uvm.hcsSystem.Start(ctx) @@ -251,7 +300,7 @@ func (uvm *UtilityVM) Start(ctx context.Context) (err error) { if uvm.gcListener != nil { // Accept the GCS connection. - conn, err := uvm.acceptAndClose(ctx, uvm.gcListener) + conn, err := uvm.accept(ctx, uvm.gcListener, true) uvm.gcListener = nil if err != nil { return fmt.Errorf("failed to connect to GCS: %w", err) @@ -347,13 +396,23 @@ func (uvm *UtilityVM) Start(ctx context.Context) (err error) { } } + if uvm.OS() == "windows" && uvm.forwardLogs { + // If the UVM is Windows and log forwarding is enabled, set the log sources + // and start the log forwarding service. + if err := uvm.SetLogSources(ctx); err != nil { + e.WithError(err).Error("failed to set log sources") + } + if err := uvm.StartLogForwarding(ctx); err != nil { + e.WithError(err).Error("failed to start log forwarding") + } + } return nil } -// acceptAndClose accepts a connection and then closes a listener. If the +// accept accepts a connection and then closes a listener. If the // context becomes done or the utility VM terminates, the operation will be // cancelled (but the listener will still be closed). -func (uvm *UtilityVM) acceptAndClose(ctx context.Context, l net.Listener) (net.Conn, error) { +func (uvm *UtilityVM) accept(ctx context.Context, l net.Listener, closeListener bool) (net.Conn, error) { var conn net.Conn ch := make(chan error) go func() { @@ -363,7 +422,9 @@ func (uvm *UtilityVM) acceptAndClose(ctx context.Context, l net.Listener) (net.C }() select { case err := <-ch: - l.Close() + if closeListener { + l.Close() + } return conn, err case <-ctx.Done(): case <-uvm.exitCh: diff --git a/internal/uvm/types.go b/internal/uvm/types.go index 7686e608f6..a4b38720cb 100644 --- a/internal/uvm/types.go +++ b/internal/uvm/types.go @@ -146,6 +146,9 @@ type UtilityVM struct { // ref counting for block CIMs blockCIMMounts map[string]*UVMMountedBlockCIMs blockCIMMountLock sync.Mutex + + forwardLogs bool // Indicates whether to forward logs from the UVM to the host + logSources string // ETW providers to enable for log forwarding } func (uvm *UtilityVM) ScratchEncryptionEnabled() bool { diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 6e76e59423..222f4b9448 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -400,6 +400,36 @@ const ( // VSMBNoDirectMap specifies that no direct mapping should be used for any VSMBs added to the UVM. VSMBNoDirectMap = "io.microsoft.virtualmachine.wcow.virtualSMB.nodirectmap" + + // LogSources specifies the ETW providers to be set for the logging service as a base64-encoded JSON string. + // + // For example: + // + // { + // "logConfig": { + // "sources": [ + // { + // "type": "ETW", + // "providers": [ + // { + // "providerGuid": "80CE50DE-D264-4581-950D-ABADEEE0D340", + // "providerName": "Microsoft.Windows.HyperV.Compute", + // "level": "Information" + // } + // ] + // } + // ] + // } + // } + // + // Would be encoded as: + // + // "io.microsoft.virtualmachine.wcow.logsources" = + // "eyJsb2dDb25maWciOnsic291cmNlcyI6W3sidHlwZSI6IkVUVyIsInByb3ZpZGVycyI6W3sicHJvdmlkZXJHdWlkIjoiODBDRTUwREUtRDI2NC00NTgxLTk1MEQtQUJBREVFRTBEMzQwIiwicHJvdmlkZXJOYW1lIjoiTWljcm9zb2Z0LldpbmRvd3MuSHlwZXJWLkNvbXB1dGUiLCJsZXZlbCI6IkluZm9ybWF0aW9uIn1dfV19fQ==" + LogSources = "io.microsoft.virtualmachine.wcow.logsources" + + // ForwardLogs specifies whether to forward logs to the host or not. + ForwardLogs = "io.microsoft.virtualmachine.wcow.forwardlogs" ) // LCOW uVM annotations. diff --git a/vendor/golang.org/x/net/netutil/listen.go b/vendor/golang.org/x/net/netutil/listen.go new file mode 100644 index 0000000000..f8b779ea27 --- /dev/null +++ b/vendor/golang.org/x/net/netutil/listen.go @@ -0,0 +1,87 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package netutil provides network utility functions, complementing the more +// common ones in the net package. +package netutil // import "golang.org/x/net/netutil" + +import ( + "net" + "sync" +) + +// LimitListener returns a Listener that accepts at most n simultaneous +// connections from the provided Listener. +func LimitListener(l net.Listener, n int) net.Listener { + return &limitListener{ + Listener: l, + sem: make(chan struct{}, n), + done: make(chan struct{}), + } +} + +type limitListener struct { + net.Listener + sem chan struct{} + closeOnce sync.Once // ensures the done chan is only closed once + done chan struct{} // no values sent; closed when Close is called +} + +// acquire acquires the limiting semaphore. Returns true if successfully +// acquired, false if the listener is closed and the semaphore is not +// acquired. +func (l *limitListener) acquire() bool { + select { + case <-l.done: + return false + case l.sem <- struct{}{}: + return true + } +} +func (l *limitListener) release() { <-l.sem } + +func (l *limitListener) Accept() (net.Conn, error) { + if !l.acquire() { + // If the semaphore isn't acquired because the listener was closed, expect + // that this call to accept won't block, but immediately return an error. + // If it instead returns a spurious connection (due to a bug in the + // Listener, such as https://golang.org/issue/50216), we immediately close + // it and try again. Some buggy Listener implementations (like the one in + // the aforementioned issue) seem to assume that Accept will be called to + // completion, and may otherwise fail to clean up the client end of pending + // connections. + for { + c, err := l.Listener.Accept() + if err != nil { + return nil, err + } + c.Close() + } + } + + c, err := l.Listener.Accept() + if err != nil { + l.release() + return nil, err + } + return &limitListenerConn{Conn: c, release: l.release}, nil +} + +func (l *limitListener) Close() error { + err := l.Listener.Close() + l.closeOnce.Do(func() { close(l.done) }) + return err +} + +type limitListenerConn struct { + net.Conn + releaseOnce sync.Once + release func() +} + +func (l *limitListenerConn) Close() error { + err := l.Conn.Close() + l.releaseOnce.Do(l.release) + return err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index f0747c7edc..acc1f25fa6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -560,6 +560,7 @@ golang.org/x/net/http2/hpack golang.org/x/net/idna golang.org/x/net/internal/httpcommon golang.org/x/net/internal/timeseries +golang.org/x/net/netutil golang.org/x/net/trace # golang.org/x/sync v0.16.0 ## explicit; go 1.23.0