1
2
3 package uvm
4
5 import (
6 "bytes"
7 "context"
8 "crypto/rand"
9 "encoding/json"
10 "errors"
11 "fmt"
12 "io"
13 "net"
14 "time"
15
16 "github.com/sirupsen/logrus"
17 "golang.org/x/sync/errgroup"
18 "golang.org/x/sys/windows"
19
20 "github.com/Microsoft/hcsshim/internal/gcs"
21 "github.com/Microsoft/hcsshim/internal/hcs"
22 "github.com/Microsoft/hcsshim/internal/hcs/schema1"
23 hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
24 "github.com/Microsoft/hcsshim/internal/log"
25 "github.com/Microsoft/hcsshim/internal/logfields"
26 "github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
27 "github.com/Microsoft/hcsshim/internal/protocol/guestresource"
28 )
29
30
31
32
33
34
35
36
37 const entropyBytes = 512
38
39 type gcsLogEntryStandard struct {
40 Time time.Time `json:"time"`
41 Level logrus.Level `json:"level"`
42 Message string `json:"msg"`
43 }
44
45 type gcsLogEntry struct {
46 gcsLogEntryStandard
47 Fields map[string]interface{}
48 }
49
50
51
52 func (e *gcsLogEntry) UnmarshalJSON(b []byte) error {
53
54 e.Level = logrus.InfoLevel
55 if err := json.Unmarshal(b, &e.gcsLogEntryStandard); err != nil {
56 return err
57 }
58 if err := json.Unmarshal(b, &e.Fields); err != nil {
59 return err
60 }
61
62 if e.Level < logrus.ErrorLevel {
63 e.Level = logrus.ErrorLevel
64 }
65
66 delete(e.Fields, "time")
67 delete(e.Fields, "level")
68 delete(e.Fields, "msg")
69
70 for k, v := range e.Fields {
71 if d, ok := v.(float64); ok && float64(int64(d)) == d {
72 e.Fields[k] = int64(d)
73 }
74 }
75 return nil
76 }
77
78 func isDisconnectError(err error) bool {
79 return hcs.IsAny(err, windows.WSAECONNABORTED, windows.WSAECONNRESET)
80 }
81
82 func parseLogrus(vmid string) func(r io.Reader) {
83 return func(r io.Reader) {
84 j := json.NewDecoder(r)
85 e := log.L.Dup()
86 fields := e.Data
87 for {
88 for k := range fields {
89 delete(fields, k)
90 }
91 gcsEntry := gcsLogEntry{Fields: e.Data}
92 err := j.Decode(&gcsEntry)
93 if err != nil {
94
95
96 if !errors.Is(err, io.EOF) && !isDisconnectError(err) {
97 logrus.WithFields(logrus.Fields{
98 logfields.UVMID: vmid,
99 logrus.ErrorKey: err,
100 }).Error("gcs log read")
101 }
102 rest, _ := io.ReadAll(io.MultiReader(j.Buffered(), r))
103 rest = bytes.TrimSpace(rest)
104 if len(rest) != 0 {
105 logrus.WithFields(logrus.Fields{
106 logfields.UVMID: vmid,
107 "stderr": string(rest),
108 }).Error("gcs terminated")
109 }
110 break
111 }
112 fields[logfields.UVMID] = vmid
113 fields["vm.time"] = gcsEntry.Time
114 e.Log(gcsEntry.Level, gcsEntry.Message)
115 }
116 }
117 }
118
119
120
121
122
123
124
125 func (uvm *UtilityVM) configureHvSocketForGCS(ctx context.Context) (err error) {
126 if uvm.OS() != "windows" {
127 return nil
128 }
129
130 hvsocketAddress := &hcsschema.HvSocketAddress{
131 LocalAddress: uvm.runtimeID.String(),
132 ParentAddress: gcs.WindowsGcsHvHostID.String(),
133 }
134
135 conSetupReq := &hcsschema.ModifySettingRequest{
136 GuestRequest: guestrequest.ModificationRequest{
137 RequestType: guestrequest.RequestTypeUpdate,
138 ResourceType: guestresource.ResourceTypeHvSocket,
139 Settings: hvsocketAddress,
140 },
141 }
142
143 if err = uvm.modify(ctx, conSetupReq); err != nil {
144 return fmt.Errorf("failed to configure HVSOCK for external GCS: %s", err)
145 }
146
147 return nil
148 }
149
150
151 func (uvm *UtilityVM) Start(ctx context.Context) (err error) {
152
153 pCtx := ctx
154 ctx, cancel := context.WithTimeout(pCtx, 2*time.Minute)
155 g, gctx := errgroup.WithContext(ctx)
156 defer func() {
157 _ = g.Wait()
158 }()
159 defer cancel()
160
161
162
163 uvm.exitCh = make(chan struct{})
164
165
166
167
168
169 if uvm.entropyListener != nil {
170 g.Go(func() error {
171 conn, err := uvm.acceptAndClose(gctx, uvm.entropyListener)
172 uvm.entropyListener = nil
173 if err != nil {
174 return fmt.Errorf("failed to connect to entropy socket: %s", err)
175 }
176 defer conn.Close()
177 _, err = io.CopyN(conn, rand.Reader, entropyBytes)
178 if err != nil {
179 return fmt.Errorf("failed to write entropy: %s", err)
180 }
181 return nil
182 })
183 }
184
185 if uvm.outputListener != nil {
186 g.Go(func() error {
187 conn, err := uvm.acceptAndClose(gctx, uvm.outputListener)
188 uvm.outputListener = nil
189 if err != nil {
190 close(uvm.outputProcessingDone)
191 return fmt.Errorf("failed to connect to log socket: %s", err)
192 }
193 go func() {
194 uvm.outputHandler(conn)
195 close(uvm.outputProcessingDone)
196 }()
197 return nil
198 })
199 }
200
201 err = uvm.hcsSystem.Start(ctx)
202 if err != nil {
203 return err
204 }
205 defer func() {
206 if err != nil {
207
208
209 _ = uvm.hcsSystem.Terminate(pCtx)
210 _ = uvm.hcsSystem.Wait()
211 }
212 }()
213
214
215 go func() {
216 err := uvm.hcsSystem.Wait()
217 if err == nil {
218 err = uvm.hcsSystem.ExitError()
219 }
220 uvm.exitErr = err
221 close(uvm.exitCh)
222 }()
223
224
225
226 if err = g.Wait(); err != nil {
227 return err
228 }
229
230 if uvm.gcListener != nil {
231
232 conn, err := uvm.acceptAndClose(ctx, uvm.gcListener)
233 uvm.gcListener = nil
234 if err != nil {
235 return fmt.Errorf("failed to connect to GCS: %s", err)
236 }
237
238 var initGuestState *gcs.InitialGuestState
239 if uvm.OS() == "windows" {
240
241
242 if uvm.noInheritHostTimezone {
243 initGuestState = &gcs.InitialGuestState{
244 Timezone: utcTimezone,
245 }
246 } else {
247 tz, err := getTimezone()
248 if err != nil {
249 return err
250 }
251 initGuestState = &gcs.InitialGuestState{
252 Timezone: tz,
253 }
254 }
255 }
256
257 gcc := &gcs.GuestConnectionConfig{
258 Conn: conn,
259 Log: log.G(ctx).WithField(logfields.UVMID, uvm.id),
260 IoListen: gcs.HvsockIoListen(uvm.runtimeID),
261 InitGuestState: initGuestState,
262 }
263 uvm.gc, err = gcc.Connect(ctx, true)
264 if err != nil {
265 return err
266 }
267 uvm.guestCaps = *uvm.gc.Capabilities()
268 uvm.protocol = uvm.gc.Protocol()
269
270
271 if err = uvm.configureHvSocketForGCS(ctx); err != nil {
272 return fmt.Errorf("failed to do initial GCS setup: %s", err)
273 }
274 } else {
275
276 properties, err := uvm.hcsSystem.Properties(ctx, schema1.PropertyTypeGuestConnection)
277 if err != nil {
278 return err
279 }
280 uvm.guestCaps = properties.GuestConnectionInfo.GuestDefinedCapabilities
281 uvm.protocol = properties.GuestConnectionInfo.ProtocolVersion
282 }
283
284 if uvm.confidentialUVMOptions != nil && uvm.OS() == "linux" {
285 copts := []ConfidentialUVMOpt{
286 WithSecurityPolicy(uvm.confidentialUVMOptions.SecurityPolicy),
287 WithSecurityPolicyEnforcer(uvm.confidentialUVMOptions.SecurityPolicyEnforcer),
288 WithUVMReferenceInfo(defaultLCOWOSBootFilesPath(), uvm.confidentialUVMOptions.UVMReferenceInfoFile),
289 }
290 if err := uvm.SetConfidentialUVMOptions(ctx, copts...); err != nil {
291 return err
292 }
293 }
294
295 return nil
296 }
297
298
299
300
301 func (uvm *UtilityVM) acceptAndClose(ctx context.Context, l net.Listener) (net.Conn, error) {
302 var conn net.Conn
303 ch := make(chan error)
304 go func() {
305 var err error
306 conn, err = l.Accept()
307 ch <- err
308 }()
309 select {
310 case err := <-ch:
311 l.Close()
312 return conn, err
313 case <-ctx.Done():
314 case <-uvm.exitCh:
315 }
316 l.Close()
317 err := <-ch
318 if err == nil {
319 return conn, err
320 }
321
322
323 if ctx.Err() != nil {
324 return nil, ctx.Err()
325 }
326 if uvm.exitErr != nil {
327 return nil, uvm.exitErr
328 }
329 return nil, err
330 }
331
View as plain text