...

Source file src/github.com/Microsoft/hcsshim/internal/gcs/guestconnection.go

Documentation: github.com/Microsoft/hcsshim/internal/gcs

     1  //go:build windows
     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  // IoListenFunc is a type for a function that creates a listener for a VM for
    37  // the vsock port `port`.
    38  type IoListenFunc func(port uint32) (net.Listener, error)
    39  
    40  // HvsockIoListen returns an implementation of IoListenFunc that listens
    41  // on the specified vsock port for the VM specified by `vmID`.
    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  	// Timezone is only honored for Windows guests.
    53  	Timezone *hcsschema.TimeZoneInformation
    54  }
    55  
    56  // GuestConnectionConfig contains options for creating a guest connection.
    57  type GuestConnectionConfig struct {
    58  	// Conn specifies the connection to use for the bridge. It will be closed
    59  	// when there is an error or Close is called.
    60  	Conn io.ReadWriteCloser
    61  	// Log specifies the logrus entry to use for async log messages.
    62  	Log *logrus.Entry
    63  	// IoListen is the function to use to create listeners for the stdio connections.
    64  	IoListen IoListenFunc
    65  	// InitGuestState specifies settings to apply to the guest on creation/start. This includes things such as the timezone for the VM.
    66  	InitGuestState *InitialGuestState
    67  }
    68  
    69  // Connect establishes a GCS connection. `gcc.Conn` will be closed by this function.
    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  // GuestConnection represents a connection to the GCS.
    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  // Capabilities returns the guest's declared capabilities.
   108  func (gc *GuestConnection) Capabilities() *schema1.GuestDefinedCapabilities {
   109  	return &gc.caps
   110  }
   111  
   112  // Protocol returns the protocol version that is in use.
   113  func (gc *GuestConnection) Protocol() uint32 {
   114  	return protocolVersion
   115  }
   116  
   117  // connect establishes a GCS connection. It must not be called more than once.
   118  // isColdStart should be true when the UVM is being connected to for the first time post-boot.
   119  // It should be false for subsequent connections (e.g. if reconnecting to an existing UVM).
   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  // Modify sends a modify settings request to the null container. This is
   167  // generally used to prepare virtual hardware that has been added to the guest.
   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  // Close terminates the guest connection. It is undefined to call any other
   208  // methods on the connection after this is called.
   209  func (gc *GuestConnection) Close() error {
   210  	if gc.brdg == nil {
   211  		return nil
   212  	}
   213  	return gc.brdg.Close()
   214  }
   215  
   216  // CreateProcess creates a process in the container host.
   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  // OS returns the operating system of the container's host, "windows" or "linux".
   226  func (gc *GuestConnection) OS() string {
   227  	return gc.os
   228  }
   229  
   230  // IsOCI returns false, indicating that CreateProcess should not be called with
   231  // an OCI process spec.
   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