Skip to content

Commit a0a0dcd

Browse files
author
Manish Ranjan Mahanta
committed
Log Forward Service support added. Signed-off-by: Manish Ranjan Mahanta <mmahanta@microsoft.com>
1 parent 0fc5d6e commit a0a0dcd

File tree

14 files changed

+396
-31
lines changed

14 files changed

+396
-31
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ require (
3939
go.etcd.io/bbolt v1.4.0
4040
go.opencensus.io v0.24.0
4141
go.uber.org/mock v0.6.0
42+
golang.org/x/net v0.43.0
4243
golang.org/x/sync v0.16.0
4344
golang.org/x/sys v0.35.0
4445
google.golang.org/grpc v1.75.0
@@ -113,7 +114,6 @@ require (
113114
go.opentelemetry.io/otel/trace v1.37.0 // indirect
114115
golang.org/x/crypto v0.41.0 // indirect
115116
golang.org/x/mod v0.27.0 // indirect
116-
golang.org/x/net v0.43.0 // indirect
117117
golang.org/x/text v0.28.0 // indirect
118118
golang.org/x/tools v0.36.0 // indirect
119119
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect

internal/gcs/guestcaps.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,7 @@ func (w *WCOWGuestDefinedCapabilities) IsDumpStacksSupported() bool {
100100
func (w *WCOWGuestDefinedCapabilities) IsDeleteContainerStateSupported() bool {
101101
return w.DeleteContainerStateSupported
102102
}
103+
104+
func (w *WCOWGuestDefinedCapabilities) IsLogForwardingSupported() bool {
105+
return w.LogForwardingSupported
106+
}

internal/gcs/guestconnection.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,20 @@ func (gc *GuestConnection) Modify(ctx context.Context, settings interface{}) (er
184184
return gc.brdg.RPC(ctx, prot.RPCModifySettings, &req, &resp, false)
185185
}
186186

187+
func (gc *GuestConnection) ModifyServiceSettings(ctx context.Context, serviceType prot.ServiceModifyPropertyType, settings interface{}) (err error) {
188+
ctx, span := oc.StartSpan(ctx, "gcs::GuestConnection::ModifyServiceSettings", oc.WithClientSpanKind)
189+
defer span.End()
190+
defer func() { oc.SetSpanStatus(span, err) }()
191+
192+
req := prot.ServiceModificationRequest{
193+
RequestBase: makeRequest(ctx, nullContainerID),
194+
PropertyType: serviceType.String(),
195+
Settings: settings,
196+
}
197+
var resp prot.ResponseBase
198+
return gc.brdg.RPC(ctx, prot.RPCModifyServiceSettings, &req, &resp, false)
199+
}
200+
187201
func (gc *GuestConnection) DumpStacks(ctx context.Context) (response string, err error) {
188202
ctx, span := oc.StartSpan(ctx, "gcs::GuestConnection::DumpStacks", oc.WithClientSpanKind)
189203
defer span.End()

internal/gcs/prot/protocol.go

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ var WindowsGcsHvHostID = guid.GUID{
7171
Data4: [8]uint8{0x93, 0xfe, 0x42, 0x96, 0x9a, 0xe6, 0xd8, 0xd1},
7272
}
7373

74+
// WindowsLoggingHvsockServiceID is the hvsock service ID that the Windows log forward service
75+
// will connect to. 172dad59-976d-45f2-8b6c-6d1b13f2ac4d
76+
var WindowsLoggingHvsockServiceID = guid.GUID{
77+
Data1: 0x172dad59,
78+
Data2: 0x976d,
79+
Data3: 0x45f2,
80+
Data4: [8]uint8{0x8b, 0x6c, 0x6d, 0x1b, 0x13, 0xf2, 0xac, 0x4d},
81+
}
82+
7483
type AnyInString struct {
7584
Value interface{}
7685
}
@@ -83,10 +92,17 @@ func (a *AnyInString) UnmarshalText(b []byte) error {
8392
return json.Unmarshal(b, &a.Value)
8493
}
8594

95+
const (
96+
// Message_Category for the GCS protocol.
97+
ComputeSystem = 0x00100000
98+
ComputeService = 0x00200000
99+
)
100+
86101
type RPCProc uint32
87102

88103
const (
89-
RPCCreate RPCProc = (iota+1)<<8 | 1
104+
// Compute System RPCs
105+
RPCCreate RPCProc = ComputeSystem | (iota+1)<<8 | 1
90106
RPCStart
91107
RPCShutdownGraceful
92108
RPCShutdownForced
@@ -103,6 +119,26 @@ const (
103119
RPCLifecycleNotification
104120
)
105121

122+
const (
123+
// Compute Service RPCs
124+
RPCModifyServiceSettings RPCProc = ComputeService | (iota+1)<<8 | 1
125+
)
126+
127+
type ServiceModifyPropertyType uint32
128+
129+
const (
130+
LogForwardService ServiceModifyPropertyType = iota
131+
)
132+
133+
func (m ServiceModifyPropertyType) String() string {
134+
switch m {
135+
case LogForwardService:
136+
return "LogForwardService"
137+
default:
138+
return fmt.Sprintf("UnknownModifyServiceType(%d)", m)
139+
}
140+
}
141+
106142
func (rpc RPCProc) String() string {
107143
switch rpc {
108144
case RPCCreate:
@@ -135,6 +171,8 @@ func (rpc RPCProc) String() string {
135171
return "UpdateContainer"
136172
case RPCLifecycleNotification:
137173
return "LifecycleNotification"
174+
case RPCModifyServiceSettings:
175+
return "ModifyServiceSettings"
138176
default:
139177
return "0x" + strconv.FormatUint(uint64(rpc), 16)
140178
}
@@ -143,10 +181,10 @@ func (rpc RPCProc) String() string {
143181
type MsgType uint32
144182

145183
const (
146-
MsgTypeRequest MsgType = 0x10100000
147-
MsgTypeResponse MsgType = 0x20100000
148-
MsgTypeNotify MsgType = 0x30100000
149-
MsgTypeMask MsgType = 0xfff00000
184+
MsgTypeRequest MsgType = 0x10000000
185+
MsgTypeResponse MsgType = 0x20000000
186+
MsgTypeNotify MsgType = 0x30000000
187+
MsgTypeMask MsgType = 0xf0000000
150188

151189
NotifyContainer = 1<<8 | 1
152190
)
@@ -160,7 +198,7 @@ func (typ MsgType) String() string {
160198
s = "Response("
161199
case MsgTypeNotify:
162200
s = "Notify("
163-
switch typ - MsgTypeNotify {
201+
switch typ - (ComputeSystem | MsgTypeNotify) {
164202
case NotifyContainer:
165203
s += "Container"
166204
default:
@@ -267,6 +305,12 @@ type ContainerNotification struct {
267305
ResultInfo AnyInString `json:",omitempty"`
268306
}
269307

308+
type ServiceModificationRequest struct {
309+
RequestBase
310+
PropertyType string // ServiceModifyPropertyType
311+
Settings interface{} `json:",omitempty"`
312+
}
313+
270314
type ContainerExecuteProcess struct {
271315
RequestBase
272316
Settings ExecuteProcessSettings
@@ -345,13 +389,14 @@ type ContainerModifySettings struct {
345389
}
346390

347391
type GcsCapabilities struct {
348-
SendHostCreateMessage bool
349-
SendHostStartMessage bool
350-
HvSocketConfigOnStartup bool
351-
SendLifecycleNotifications bool
352-
SupportedSchemaVersions []hcsschema.Version
353-
RuntimeOsType string
354-
GuestDefinedCapabilities json.RawMessage
392+
SendHostCreateMessage bool
393+
SendHostStartMessage bool
394+
HvSocketConfigOnStartup bool
395+
SendLifecycleNotifications bool
396+
ModifyServiceSettingsSupported bool
397+
SupportedSchemaVersions []hcsschema.Version
398+
RuntimeOsType string
399+
GuestDefinedCapabilities json.RawMessage
355400
}
356401

357402
type ContainerCreateResponse struct {

internal/hcs/schema1/schema1.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ type GuestDefinedCapabilities struct {
221221
DumpStacksSupported bool `json:",omitempty"`
222222
DeleteContainerStateSupported bool `json:",omitempty"`
223223
UpdateContainerSupported bool `json:",omitempty"`
224+
LogForwardingSupported bool `json:",omitempty"`
224225
}
225226

226227
// GuestConnectionInfo is the structure of an iterm return by a GuestConnection call on a utility VM

internal/oci/uvm.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,12 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) (
416416
if err := handleWCOWSecurityPolicy(ctx, s.Annotations, wopts); err != nil {
417417
return nil, err
418418
}
419-
419+
// If security policy is enable, wopts.ForwardLogs default value should be false
420+
if wopts.SecurityPolicyEnabled {
421+
wopts.ForwardLogs = false
422+
}
423+
wopts.LogSources = ParseAnnotationsString(s.Annotations, annotations.LogSources, wopts.LogSources)
424+
wopts.ForwardLogs = ParseAnnotationsBool(ctx, s.Annotations, annotations.ForwardLogs, wopts.ForwardLogs)
420425
return wopts, nil
421426
}
422427
return nil, errors.New("cannot create UVM opts spec is not LCOW or WCOW")

internal/protocol/guestrequest/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,17 @@ const (
7474
STDErrHandle STDIOHandle = "StdErr"
7575
AllHandles STDIOHandle = "All"
7676
)
77+
78+
type LogForwardServiceRpcRequest struct {
79+
RpcType LogForwardServiceRpcType `json:"RpcType,omitempty"` // "LogForwardService"
80+
Settings string `json:"Settings,omitempty"`
81+
}
82+
83+
type LogForwardServiceRpcType string
84+
85+
const (
86+
// LogForwardServiceRPC is the RPC type for the log forward service.
87+
LogForwardServiceRPCModifyServiceSettings LogForwardServiceRpcType = "ModifyServiceSettings"
88+
LogForwardServiceRPCStartLogForwarding LogForwardServiceRpcType = "StartLogForwarding"
89+
LogForwardServiceRPCStopLogForwarding LogForwardServiceRpcType = "StopLogForwarding"
90+
)

internal/uvm/create_wcow.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ type OptionsWCOW struct {
5959

6060
// AdditionalRegistryKeys are Registry keys and their values to additionally add to the uVM.
6161
AdditionalRegistryKeys []hcsschema.RegistryValue
62+
63+
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
64+
LogSources string // ETW providers to be set for the logging service
65+
ForwardLogs bool // Whether to forward logs to the host or not
6266
}
6367

6468
func defaultConfidentialWCOWOSBootFilesPath() string {
@@ -89,6 +93,9 @@ func NewDefaultOptionsWCOW(id, owner string) *OptionsWCOW {
8993
Options: newDefaultOptions(id, owner),
9094
AdditionalRegistryKeys: []hcsschema.RegistryValue{},
9195
ConfidentialWCOWOptions: &ConfidentialWCOWOptions{},
96+
OutputHandlerCreator: parseLogrus,
97+
ForwardLogs: false,
98+
LogSources: "",
9299
}
93100
}
94101

@@ -259,6 +266,12 @@ func prepareCommonConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWC
259266
}
260267

261268
maps.Copy(doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable, opts.AdditionalHyperVConfig)
269+
key := prot.WindowsLoggingHvsockServiceID.String()
270+
doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable[key] = hcsschema.HvSocketServiceConfig{
271+
AllowWildcardBinds: true,
272+
BindSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)",
273+
ConnectSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)",
274+
}
262275

263276
// Handle StorageQoS if set
264277
if opts.StorageQoSBandwidthMaximum > 0 || opts.StorageQoSIopsMaximum > 0 {
@@ -507,6 +520,8 @@ func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error
507520
noWritableFileShares: opts.NoWritableFileShares,
508521
createOpts: *opts,
509522
blockCIMMounts: make(map[string]*UVMMountedBlockCIMs),
523+
logSources: opts.LogSources,
524+
forwardLogs: opts.ForwardLogs,
510525
}
511526

512527
defer func() {
@@ -536,6 +551,15 @@ func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error
536551
return nil, fmt.Errorf("error while creating the compute system: %w", err)
537552
}
538553

554+
// Create a socket that the executed program can send to. This is usually
555+
// used by Log Forward Service to send log data.
556+
uvm.outputHandler = opts.OutputHandlerCreator(opts.Options)
557+
uvm.outputProcessingDone = make(chan struct{})
558+
uvm.outputListener, err = winio.ListenHvsock(&winio.HvsockAddr{
559+
VMID: uvm.RuntimeID(),
560+
ServiceID: prot.WindowsLoggingHvsockServiceID,
561+
})
562+
539563
gcsServiceID := prot.WindowsGcsHvsockServiceID
540564
if opts.SecurityPolicyEnabled {
541565
gcsServiceID = prot.WindowsSidecarGcsHvsockServiceID

internal/uvm/log_wcow.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//go:build windows
2+
3+
package uvm
4+
5+
import (
6+
"context"
7+
8+
"github.com/Microsoft/hcsshim/internal/gcs"
9+
"github.com/Microsoft/hcsshim/internal/gcs/prot"
10+
"github.com/Microsoft/hcsshim/internal/log"
11+
"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
12+
)
13+
14+
func (uvm *UtilityVM) StartLogForwarding(ctx context.Context) error {
15+
// Implementation for stopping the log forwarder
16+
if uvm.OS() == "windows" && uvm.gc != nil {
17+
wcaps := gcs.GetWCOWCapabilities(uvm.gc.Capabilities())
18+
if wcaps != nil && wcaps.IsLogForwardingSupported() {
19+
req := guestrequest.LogForwardServiceRpcRequest{
20+
RpcType: guestrequest.LogForwardServiceRPCStartLogForwarding,
21+
Settings: "",
22+
}
23+
err := uvm.gc.ModifyServiceSettings(ctx, prot.LogForwardService, req)
24+
if err != nil {
25+
return err
26+
}
27+
} else {
28+
log.G(ctx).WithField("os", uvm.operatingSystem).Error("Log forwarding not supported for this OS")
29+
}
30+
}
31+
return nil
32+
}
33+
34+
func (uvm *UtilityVM) StopLogForwarding(ctx context.Context) error {
35+
// Implementation for stopping the log forwarder
36+
if uvm.OS() == "windows" && uvm.gc != nil {
37+
wcaps := gcs.GetWCOWCapabilities(uvm.gc.Capabilities())
38+
if wcaps != nil && wcaps.IsLogForwardingSupported() {
39+
req := guestrequest.LogForwardServiceRpcRequest{
40+
RpcType: guestrequest.LogForwardServiceRPCStopLogForwarding,
41+
Settings: "",
42+
}
43+
err := uvm.gc.ModifyServiceSettings(ctx, prot.LogForwardService, req)
44+
if err != nil {
45+
return err
46+
}
47+
}
48+
}
49+
return nil
50+
}
51+
52+
func (uvm *UtilityVM) SetLogSources(ctx context.Context) error {
53+
// Implementation for setting the log sources
54+
if uvm.OS() == "windows" && uvm.logSources != "" && uvm.gc != nil {
55+
wcaps := gcs.GetWCOWCapabilities(uvm.gc.Capabilities())
56+
if wcaps != nil && wcaps.IsLogForwardingSupported() {
57+
// Make a call to the GCS to set the ETW providers
58+
req := guestrequest.LogForwardServiceRpcRequest{
59+
RpcType: guestrequest.LogForwardServiceRPCModifyServiceSettings,
60+
Settings: uvm.logSources,
61+
}
62+
err := uvm.gc.ModifyServiceSettings(ctx, prot.LogForwardService, req)
63+
if err != nil {
64+
return err
65+
}
66+
}
67+
}
68+
return nil
69+
}

0 commit comments

Comments
 (0)