1
2
3 package gcs
4
5 import (
6 "context"
7 "encoding/base64"
8 "encoding/hex"
9 "encoding/json"
10 "fmt"
11 "io"
12 "net"
13 "strings"
14 "sync"
15
16 "github.com/Microsoft/go-winio"
17 "github.com/Microsoft/go-winio/pkg/guid"
18 "github.com/Microsoft/hcsshim/internal/cow"
19 "github.com/Microsoft/hcsshim/internal/hcs/schema1"
20 hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
21 "github.com/Microsoft/hcsshim/internal/log"
22 "github.com/Microsoft/hcsshim/internal/logfields"
23 "github.com/Microsoft/hcsshim/internal/oc"
24 "github.com/pkg/errors"
25 "github.com/sirupsen/logrus"
26 "go.opencensus.io/trace"
27 )
28
29 const (
30 protocolVersion = 4
31
32 firstIoChannelVsockPort = LinuxGcsVsockPort + 1
33 nullContainerID = "00000000-0000-0000-0000-000000000000"
34 )
35
36
37
38 type IoListenFunc func(port uint32) (net.Listener, error)
39
40
41
42 func HvsockIoListen(vmID guid.GUID) IoListenFunc {
43 return func(port uint32) (net.Listener, error) {
44 return winio.ListenHvsock(&winio.HvsockAddr{
45 VMID: vmID,
46 ServiceID: winio.VsockServiceID(port),
47 })
48 }
49 }
50
51 type InitialGuestState struct {
52
53 Timezone *hcsschema.TimeZoneInformation
54 }
55
56
57 type GuestConnectionConfig struct {
58
59
60 Conn io.ReadWriteCloser
61
62 Log *logrus.Entry
63
64 IoListen IoListenFunc
65
66 InitGuestState *InitialGuestState
67 }
68
69
70 func (gcc *GuestConnectionConfig) Connect(ctx context.Context, isColdStart bool) (_ *GuestConnection, err error) {
71 ctx, span := oc.StartSpan(ctx, "gcs::GuestConnectionConfig::Connect", oc.WithClientSpanKind)
72 defer span.End()
73 defer func() { oc.SetSpanStatus(span, err) }()
74
75 gc := &GuestConnection{
76 nextPort: firstIoChannelVsockPort,
77 notifyChs: make(map[string]chan struct{}),
78 ioListenFn: gcc.IoListen,
79 }
80 gc.brdg = newBridge(gcc.Conn, gc.notify, gcc.Log)
81 gc.brdg.Start()
82 go func() {
83 _ = gc.brdg.Wait()
84 gc.clearNotifies()
85 }()
86 err = gc.connect(ctx, isColdStart, gcc.InitGuestState)
87 if err != nil {
88 gc.Close()
89 return nil, err
90 }
91 return gc, nil
92 }
93
94
95 type GuestConnection struct {
96 brdg *bridge
97 ioListenFn IoListenFunc
98 mu sync.Mutex
99 nextPort uint32
100 notifyChs map[string]chan struct{}
101 caps schema1.GuestDefinedCapabilities
102 os string
103 }
104
105 var _ cow.ProcessHost = &GuestConnection{}
106
107
108 func (gc *GuestConnection) Capabilities() *schema1.GuestDefinedCapabilities {
109 return &gc.caps
110 }
111
112
113 func (gc *GuestConnection) Protocol() uint32 {
114 return protocolVersion
115 }
116
117
118
119
120 func (gc *GuestConnection) connect(ctx context.Context, isColdStart bool, initGuestState *InitialGuestState) (err error) {
121 req := negotiateProtocolRequest{
122 MinimumVersion: protocolVersion,
123 MaximumVersion: protocolVersion,
124 }
125 var resp negotiateProtocolResponse
126 resp.Capabilities.GuestDefinedCapabilities = &gc.caps
127 err = gc.brdg.RPC(ctx, rpcNegotiateProtocol, &req, &resp, true)
128 if err != nil {
129 return err
130 }
131 if resp.Version != protocolVersion {
132 return fmt.Errorf("unexpected version %d returned", resp.Version)
133 }
134 gc.os = strings.ToLower(resp.Capabilities.RuntimeOsType)
135 if gc.os == "" {
136 gc.os = "windows"
137 }
138 if isColdStart && resp.Capabilities.SendHostCreateMessage {
139 conf := &uvmConfig{
140 SystemType: "Container",
141 }
142 if initGuestState != nil && initGuestState.Timezone != nil {
143 conf.TimeZoneInformation = initGuestState.Timezone
144 }
145 createReq := containerCreate{
146 requestBase: makeRequest(ctx, nullContainerID),
147 ContainerConfig: anyInString{conf},
148 }
149 var createResp responseBase
150 err = gc.brdg.RPC(ctx, rpcCreate, &createReq, &createResp, true)
151 if err != nil {
152 return err
153 }
154 if resp.Capabilities.SendHostStartMessage {
155 startReq := makeRequest(ctx, nullContainerID)
156 var startResp responseBase
157 err = gc.brdg.RPC(ctx, rpcStart, &startReq, &startResp, true)
158 if err != nil {
159 return err
160 }
161 }
162 }
163 return nil
164 }
165
166
167
168 func (gc *GuestConnection) Modify(ctx context.Context, settings interface{}) (err error) {
169 ctx, span := oc.StartSpan(ctx, "gcs::GuestConnection::Modify", oc.WithClientSpanKind)
170 defer span.End()
171 defer func() { oc.SetSpanStatus(span, err) }()
172
173 req := containerModifySettings{
174 requestBase: makeRequest(ctx, nullContainerID),
175 Request: settings,
176 }
177 var resp responseBase
178 return gc.brdg.RPC(ctx, rpcModifySettings, &req, &resp, false)
179 }
180
181 func (gc *GuestConnection) DumpStacks(ctx context.Context) (response string, err error) {
182 ctx, span := oc.StartSpan(ctx, "gcs::GuestConnection::DumpStacks", oc.WithClientSpanKind)
183 defer span.End()
184 defer func() { oc.SetSpanStatus(span, err) }()
185
186 req := dumpStacksRequest{
187 requestBase: makeRequest(ctx, nullContainerID),
188 }
189 var resp dumpStacksResponse
190 err = gc.brdg.RPC(ctx, rpcDumpStacks, &req, &resp, false)
191 return resp.GuestStacks, err
192 }
193
194 func (gc *GuestConnection) DeleteContainerState(ctx context.Context, cid string) (err error) {
195 ctx, span := oc.StartSpan(ctx, "gcs::GuestConnection::DeleteContainerState", oc.WithClientSpanKind)
196 defer span.End()
197 defer func() { oc.SetSpanStatus(span, err) }()
198 span.AddAttributes(trace.StringAttribute("cid", cid))
199
200 req := deleteContainerStateRequest{
201 requestBase: makeRequest(ctx, cid),
202 }
203 var resp responseBase
204 return gc.brdg.RPC(ctx, rpcDeleteContainerState, &req, &resp, false)
205 }
206
207
208
209 func (gc *GuestConnection) Close() error {
210 if gc.brdg == nil {
211 return nil
212 }
213 return gc.brdg.Close()
214 }
215
216
217 func (gc *GuestConnection) CreateProcess(ctx context.Context, settings interface{}) (_ cow.Process, err error) {
218 ctx, span := oc.StartSpan(ctx, "gcs::GuestConnection::CreateProcess", oc.WithClientSpanKind)
219 defer span.End()
220 defer func() { oc.SetSpanStatus(span, err) }()
221
222 return gc.exec(ctx, nullContainerID, settings)
223 }
224
225
226 func (gc *GuestConnection) OS() string {
227 return gc.os
228 }
229
230
231
232 func (gc *GuestConnection) IsOCI() bool {
233 return false
234 }
235
236 func (gc *GuestConnection) newIoChannel() (*ioChannel, uint32, error) {
237 gc.mu.Lock()
238 port := gc.nextPort
239 gc.nextPort++
240 gc.mu.Unlock()
241 l, err := gc.ioListenFn(port)
242 if err != nil {
243 return nil, 0, err
244 }
245 return newIoChannel(l), port, nil
246 }
247
248 func (gc *GuestConnection) requestNotify(cid string, ch chan struct{}) error {
249 gc.mu.Lock()
250 defer gc.mu.Unlock()
251 if gc.notifyChs == nil {
252 return errors.New("guest connection closed")
253 }
254 if _, ok := gc.notifyChs[cid]; ok {
255 return fmt.Errorf("container %s already exists", cid)
256 }
257 gc.notifyChs[cid] = ch
258 return nil
259 }
260
261 func (gc *GuestConnection) notify(ntf *containerNotification) error {
262 cid := ntf.ContainerID
263 gc.mu.Lock()
264 ch := gc.notifyChs[cid]
265 delete(gc.notifyChs, cid)
266 gc.mu.Unlock()
267 if ch == nil {
268 return fmt.Errorf("container %s not found", cid)
269 }
270 logrus.WithField(logfields.ContainerID, cid).Info("container terminated in guest")
271 close(ch)
272 return nil
273 }
274
275 func (gc *GuestConnection) clearNotifies() {
276 gc.mu.Lock()
277 chs := gc.notifyChs
278 gc.notifyChs = nil
279 gc.mu.Unlock()
280 for _, ch := range chs {
281 close(ch)
282 }
283 }
284
285 func makeRequest(ctx context.Context, cid string) requestBase {
286 r := requestBase{
287 ContainerID: cid,
288 }
289 span := trace.FromContext(ctx)
290 if span != nil {
291 sc := span.SpanContext()
292 r.OpenCensusSpanContext = &ocspancontext{
293 TraceID: hex.EncodeToString(sc.TraceID[:]),
294 SpanID: hex.EncodeToString(sc.SpanID[:]),
295 TraceOptions: uint32(sc.TraceOptions),
296 }
297 if sc.Tracestate != nil {
298 entries := sc.Tracestate.Entries()
299 if len(entries) > 0 {
300 if bytes, err := json.Marshal(sc.Tracestate.Entries()); err == nil {
301 r.OpenCensusSpanContext.Tracestate = base64.StdEncoding.EncodeToString(bytes)
302 } else {
303 log.G(ctx).WithError(err).Warn("failed to encode OpenCensus Tracestate")
304 }
305 }
306 }
307 }
308 return r
309 }
310
View as plain text