github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/lisafs/channel.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package lisafs
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  	"runtime"
    21  
    22  	"golang.org/x/sys/unix"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/fdchannel"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/flipcall"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/log"
    26  )
    27  
    28  var (
    29  	chanHeaderLen = uint32((*channelHeader)(nil).SizeBytes())
    30  )
    31  
    32  // maxChannels returns the number of channels a client can create.
    33  //
    34  // The server will reject channel creation requests beyond this (per client).
    35  // Note that we don't want the number of channels to be too large, because each
    36  // accounts for a large region of shared memory.
    37  // TODO(gvisor.dev/issue/6313): Tune the number of channels.
    38  func maxChannels() int {
    39  	maxChans := runtime.GOMAXPROCS(0)
    40  	if maxChans < 2 {
    41  		maxChans = 2
    42  	}
    43  	if maxChans > 4 {
    44  		maxChans = 4
    45  	}
    46  	return maxChans
    47  }
    48  
    49  // channel implements Communicator and represents the communication endpoint
    50  // for the client and server and is used to perform fast IPC. Apart from
    51  // communicating data, a channel is also capable of donating file descriptors.
    52  type channel struct {
    53  	fdTracker
    54  	dead   bool
    55  	data   flipcall.Endpoint
    56  	fdChan fdchannel.Endpoint
    57  }
    58  
    59  var _ Communicator = (*channel)(nil)
    60  
    61  // PayloadBuf implements Communicator.PayloadBuf.
    62  func (ch *channel) PayloadBuf(size uint32) []byte {
    63  	return ch.data.Data()[chanHeaderLen : chanHeaderLen+size]
    64  }
    65  
    66  // SndRcvMessage implements Communicator.SndRcvMessage.
    67  func (ch *channel) SndRcvMessage(m MID, payloadLen uint32, wantFDs uint8) (MID, uint32, error) {
    68  	// Write header. Requests can not donate FDs.
    69  	ch.marshalHdr(m, 0 /* numFDs */)
    70  
    71  	// One-shot communication. RPCs are expected to be quick rather than block.
    72  	rcvDataLen, err := ch.data.SendRecvFast(chanHeaderLen + payloadLen)
    73  	if err != nil {
    74  		// This channel is now unusable.
    75  		ch.dead = true
    76  		// Map the transport errors to EIO, but also log the real error.
    77  		log.Warningf("channel.SndRcvMessage: flipcall.Endpoint.SendRecv failed: %v", err)
    78  		return 0, 0, unix.EIO
    79  	}
    80  
    81  	return ch.rcvMsg(rcvDataLen)
    82  }
    83  
    84  // String implements fmt.Stringer.String.
    85  func (ch *channel) String() string {
    86  	return fmt.Sprintf("channel %p", ch)
    87  }
    88  
    89  func (ch *channel) shutdown() {
    90  	ch.data.Shutdown()
    91  }
    92  
    93  func (ch *channel) destroy() {
    94  	ch.dead = true
    95  	ch.fdChan.Destroy()
    96  	ch.data.Destroy()
    97  }
    98  
    99  // createChannel creates a server side channel. It returns a packet window
   100  // descriptor (for the data channel) and an open socket for the FD channel.
   101  func (c *Connection) createChannel(maxMessageSize uint32) (*channel, flipcall.PacketWindowDescriptor, int, error) {
   102  	c.channelsMu.Lock()
   103  	defer c.channelsMu.Unlock()
   104  	// If c.channels is nil, the connection has closed.
   105  	if c.channels == nil {
   106  		return nil, flipcall.PacketWindowDescriptor{}, -1, unix.ENOSYS
   107  	}
   108  	// Return ENOMEM to indicate that the server has hit its max channels limit.
   109  	if len(c.channels) >= maxChannels() {
   110  		return nil, flipcall.PacketWindowDescriptor{}, -1, unix.ENOMEM
   111  	}
   112  	ch := &channel{}
   113  
   114  	// Set up data channel.
   115  	desc, err := c.channelAlloc.Allocate(flipcall.PacketHeaderBytes + int(chanHeaderLen+maxMessageSize))
   116  	if err != nil {
   117  		return nil, flipcall.PacketWindowDescriptor{}, -1, err
   118  	}
   119  	if err := ch.data.Init(flipcall.ServerSide, desc); err != nil {
   120  		return nil, flipcall.PacketWindowDescriptor{}, -1, err
   121  	}
   122  
   123  	// Set up FD channel.
   124  	fdSocks, err := fdchannel.NewConnectedSockets()
   125  	if err != nil {
   126  		ch.data.Destroy()
   127  		return nil, flipcall.PacketWindowDescriptor{}, -1, err
   128  	}
   129  	ch.fdChan.Init(fdSocks[0])
   130  	clientFDSock := fdSocks[1]
   131  
   132  	c.channels = append(c.channels, ch)
   133  	return ch, desc, clientFDSock, nil
   134  }
   135  
   136  // sendFDs sends as many FDs as it can. The failure to send an FD does not
   137  // cause an error and fail the entire RPC. FDs are considered supplementary
   138  // responses that are not critical to the RPC response itself. The failure to
   139  // send the (i)th FD will cause all the following FDs to not be sent as well
   140  // because the order in which FDs are donated is important.
   141  func (ch *channel) sendFDs(fds []int) uint8 {
   142  	numFDs := len(fds)
   143  	if numFDs == 0 {
   144  		return 0
   145  	}
   146  
   147  	if numFDs > math.MaxUint8 {
   148  		log.Warningf("dropping all FDs because too many FDs to donate: %v", numFDs)
   149  		return 0
   150  	}
   151  
   152  	for i, fd := range fds {
   153  		if err := ch.fdChan.SendFD(fd); err != nil {
   154  			log.Warningf("error occurred while sending (%d/%d)th FD on channel(%p): %v", i+1, numFDs, ch, err)
   155  			return uint8(i)
   156  		}
   157  	}
   158  	return uint8(numFDs)
   159  }
   160  
   161  // channelHeader is the header present in front of each message received on
   162  // flipcall endpoint when the protocol version being used is 1.
   163  //
   164  // +marshal
   165  type channelHeader struct {
   166  	message MID
   167  	numFDs  uint8
   168  	_       uint8 // Need to make struct packed.
   169  }
   170  
   171  func (ch *channel) marshalHdr(m MID, numFDs uint8) {
   172  	header := &channelHeader{
   173  		message: m,
   174  		numFDs:  numFDs,
   175  	}
   176  	header.MarshalUnsafe(ch.data.Data())
   177  }
   178  
   179  func (ch *channel) rcvMsg(dataLen uint32) (MID, uint32, error) {
   180  	if dataLen < chanHeaderLen {
   181  		log.Warningf("received data has size smaller than header length: %d", dataLen)
   182  		return 0, 0, unix.EIO
   183  	}
   184  
   185  	// Read header first.
   186  	var header channelHeader
   187  	header.UnmarshalUnsafe(ch.data.Data())
   188  
   189  	// Read any FDs.
   190  	for i := 0; i < int(header.numFDs); i++ {
   191  		fd, err := ch.fdChan.RecvFDNonblock()
   192  		if err != nil {
   193  			log.Warningf("expected %d FDs, received %d successfully, got err after that: %v", header.numFDs, i, err)
   194  			break
   195  		}
   196  		ch.TrackFD(fd)
   197  	}
   198  
   199  	return header.message, dataLen - chanHeaderLen, nil
   200  }