Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/gcs/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/gcs/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -197,7 +197,7 @@ func TestBridgeNotify(t *testing.T) {
func TestBridgeNotifyFailure(t *testing.T) {
ntf := &prot.ContainerNotification{Operation: "testing"}
errMsg := "notify should have failed"
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 {
return errors.New(errMsg)
})
if err == nil || !strings.Contains(err.Error(), errMsg) {
Expand Down
4 changes: 4 additions & 0 deletions internal/gcs/guestcaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
14 changes: 14 additions & 0 deletions internal/gcs/guestconnection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion internal/gcs/guestconnection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down
62 changes: 49 additions & 13 deletions internal/gcs/prot/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
}
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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)
}
Expand All @@ -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
)
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions internal/hcs/schema1/schema1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion internal/oci/uvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions internal/oci/uvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
14 changes: 14 additions & 0 deletions internal/protocol/guestrequest/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
28 changes: 28 additions & 0 deletions internal/uvm/create_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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: "",
}
}

Expand Down Expand Up @@ -259,6 +266,14 @@ func prepareCommonConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWC
}

maps.Copy(doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable, opts.AdditionalHyperVConfig)
if opts.ForwardLogs {
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 {
Expand Down Expand Up @@ -507,6 +522,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() {
Expand Down Expand Up @@ -536,6 +553,17 @@ func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error
return nil, fmt.Errorf("error while creating the compute system: %w", err)
}

if opts.ForwardLogs {
// 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
Expand Down
75 changes: 75 additions & 0 deletions internal/uvm/log_wcow.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading