// Copyright 2021 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package lisafs import ( "math" "runtime" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/fdchannel" "gvisor.dev/gvisor/pkg/flipcall" "gvisor.dev/gvisor/pkg/log" ) var ( chanHeaderLen = uint32((*channelHeader)(nil).SizeBytes()) ) // maxChannels returns the number of channels a client can create. // // The server will reject channel creation requests beyond this (per client). // Note that we don't want the number of channels to be too large, because each // accounts for a large region of shared memory. // TODO(gvisor.dev/issue/6313): Tune the number of channels. func maxChannels() int { maxChans := runtime.GOMAXPROCS(0) if maxChans < 2 { maxChans = 2 } if maxChans > 4 { maxChans = 4 } return maxChans } // channel implements Communicator and represents the communication endpoint // for the client and server and is used to perform fast IPC. Apart from // communicating data, a channel is also capable of donating file descriptors. type channel struct { fdTracker dead bool data flipcall.Endpoint fdChan fdchannel.Endpoint } var _ Communicator = (*channel)(nil) // PayloadBuf implements Communicator.PayloadBuf. func (ch *channel) PayloadBuf(size uint32) []byte { return ch.data.Data()[chanHeaderLen : chanHeaderLen+size] } // SndRcvMessage implements Communicator.SndRcvMessage. func (ch *channel) SndRcvMessage(m MID, payloadLen uint32, wantFDs uint8) (MID, uint32, error) { // Write header. Requests can not donate FDs. ch.marshalHdr(m, 0 /* numFDs */) // One-shot communication. RPCs are expected to be quick rather than block. rcvDataLen, err := ch.data.SendRecvFast(chanHeaderLen + payloadLen) if err != nil { // This channel is now unusable. ch.dead = true // Map the transport errors to EIO, but also log the real error. log.Warningf("lisafs.sndRcvMessage: flipcall.Endpoint.SendRecv: %v", err) return 0, 0, unix.EIO } return ch.rcvMsg(rcvDataLen) } func (ch *channel) shutdown() { ch.data.Shutdown() } func (ch *channel) destroy() { ch.dead = true ch.fdChan.Destroy() ch.data.Destroy() } // createChannel creates a server side channel. It returns a packet window // descriptor (for the data channel) and an open socket for the FD channel. func (c *Connection) createChannel(maxMessageSize uint32) (*channel, flipcall.PacketWindowDescriptor, int, error) { c.channelsMu.Lock() defer c.channelsMu.Unlock() // If c.channels is nil, the connection has closed. if c.channels == nil || len(c.channels) >= maxChannels() { return nil, flipcall.PacketWindowDescriptor{}, -1, unix.ENOSYS } ch := &channel{} // Set up data channel. desc, err := c.channelAlloc.Allocate(flipcall.PacketHeaderBytes + int(chanHeaderLen+maxMessageSize)) if err != nil { return nil, flipcall.PacketWindowDescriptor{}, -1, err } if err := ch.data.Init(flipcall.ServerSide, desc); err != nil { return nil, flipcall.PacketWindowDescriptor{}, -1, err } // Set up FD channel. fdSocks, err := fdchannel.NewConnectedSockets() if err != nil { ch.data.Destroy() return nil, flipcall.PacketWindowDescriptor{}, -1, err } ch.fdChan.Init(fdSocks[0]) clientFDSock := fdSocks[1] c.channels = append(c.channels, ch) return ch, desc, clientFDSock, nil } // sendFDs sends as many FDs as it can. The failure to send an FD does not // cause an error and fail the entire RPC. FDs are considered supplementary // responses that are not critical to the RPC response itself. The failure to // send the (i)th FD will cause all the following FDs to not be sent as well // because the order in which FDs are donated is important. func (ch *channel) sendFDs(fds []int) uint8 { numFDs := len(fds) if numFDs == 0 { return 0 } if numFDs > math.MaxUint8 { log.Warningf("dropping all FDs because too many FDs to donate: %v", numFDs) return 0 } for i, fd := range fds { if err := ch.fdChan.SendFD(fd); err != nil { log.Warningf("error occurred while sending (%d/%d)th FD on channel(%p): %v", i+1, numFDs, ch, err) return uint8(i) } } return uint8(numFDs) } // channelHeader is the header present in front of each message received on // flipcall endpoint when the protocol version being used is 1. // // +marshal type channelHeader struct { message MID numFDs uint8 _ uint8 // Need to make struct packed. } func (ch *channel) marshalHdr(m MID, numFDs uint8) { header := &channelHeader{ message: m, numFDs: numFDs, } header.MarshalUnsafe(ch.data.Data()) } func (ch *channel) rcvMsg(dataLen uint32) (MID, uint32, error) { if dataLen < chanHeaderLen { log.Warningf("received data has size smaller than header length: %d", dataLen) return 0, 0, unix.EIO } // Read header first. var header channelHeader header.UnmarshalUnsafe(ch.data.Data()) // Read any FDs. for i := 0; i < int(header.numFDs); i++ { fd, err := ch.fdChan.RecvFDNonblock() if err != nil { log.Warningf("expected %d FDs, received %d successfully, got err after that: %v", header.numFDs, i, err) break } ch.TrackFD(fd) } return header.message, dataLen - chanHeaderLen, nil }