Update p9 to support flipcall.

PiperOrigin-RevId: 268845090
This commit is contained in:
Adin Scannell
2019-09-12 23:37:31 -07:00
committed by gVisor bot
parent 7c6ab6a219
commit a8834fc555
15 changed files with 907 additions and 91 deletions
+3
View File
@@ -20,11 +20,14 @@ go_library(
"pool.go",
"server.go",
"transport.go",
"transport_flipcall.go",
"version.go",
],
importpath = "gvisor.dev/gvisor/pkg/p9",
deps = [
"//pkg/fd",
"//pkg/fdchannel",
"//pkg/flipcall",
"//pkg/log",
"//pkg/unet",
"@org_golang_x_sys//unix:go_default_library",
+263 -17
View File
@@ -20,6 +20,8 @@ import (
"sync"
"syscall"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/flipcall"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/unet"
)
@@ -77,6 +79,45 @@ type Client struct {
// fidPool is the collection of available fids.
fidPool pool
// messageSize is the maximum total size of a message.
messageSize uint32
// payloadSize is the maximum payload size of a read or write.
//
// For large reads and writes this means that the read or write is
// broken up into buffer-size/payloadSize requests.
payloadSize uint32
// version is the agreed upon version X of 9P2000.L.Google.X.
// version 0 implies 9P2000.L.
version uint32
// sendRecv is the transport function.
//
// This is determined dynamically based on whether or not the server
// supports flipcall channels (preferred as it is faster and more
// efficient, and does not require tags).
sendRecv func(message, message) error
// -- below corresponds to sendRecvChannel --
// channelsMu protects channels.
channelsMu sync.Mutex
// channelsWg is a wait group for active clients.
channelsWg sync.WaitGroup
// channels are the set of initialized IPCs channels.
channels []*channel
// inuse is set when the channels are actually in use.
//
// This is a fixed-size slice, and the entries will be nil when the
// corresponding channel is available.
inuse []*channel
// -- below corresponds to sendRecvLegacy --
// pending is the set of pending messages.
pending map[Tag]*response
pendingMu sync.Mutex
@@ -89,19 +130,6 @@ type Client struct {
// Whoever writes to this channel is permitted to call recv. When
// finished calling recv, this channel should be emptied.
recvr chan bool
// messageSize is the maximum total size of a message.
messageSize uint32
// payloadSize is the maximum payload size of a read or write
// request. For large reads and writes this means that the
// read or write is broken up into buffer-size/payloadSize
// requests.
payloadSize uint32
// version is the agreed upon version X of 9P2000.L.Google.X.
// version 0 implies 9P2000.L.
version uint32
}
// NewClient creates a new client. It performs a Tversion exchange with
@@ -138,8 +166,15 @@ func NewClient(socket *unet.Socket, messageSize uint32, version string) (*Client
return nil, ErrBadVersionString
}
for {
// Always exchange the version using the legacy version of the
// protocol. If the protocol supports flipcall, then we switch
// our sendRecv function to use that functionality. Otherwise,
// we stick to sendRecvLegacy.
rversion := Rversion{}
err := c.sendRecv(&Tversion{Version: versionString(requested), MSize: messageSize}, &rversion)
err := c.sendRecvLegacy(&Tversion{
Version: versionString(requested),
MSize: messageSize,
}, &rversion)
// The server told us to try again with a lower version.
if err == syscall.EAGAIN {
@@ -165,9 +200,125 @@ func NewClient(socket *unet.Socket, messageSize uint32, version string) (*Client
c.version = version
break
}
// Can we switch to use the more advanced channels and create
// independent channels for communication? Prefer it if possible.
if versionSupportsFlipcall(c.version) {
// Attempt to initialize IPC-based communication.
for i := 0; i < channelsPerClient; i++ {
if err := c.openChannel(i); err != nil {
log.Warningf("error opening flipcall channel: %v", err)
break // Stop.
}
}
if len(c.channels) >= 1 {
// At least one channel created.
c.sendRecv = c.sendRecvChannel
// If we are using channels for communication, then we must poll
// for shutdown events on the main socket. If the socket happens
// to shutdown, then we will close the channels as well. This is
// necessary because channels can hang forever if the server dies
// while we're expecting a response.
go c.watch(socket) // S/R-SAFE: not relevant.
} else {
// Channel setup failed; fallback.
c.sendRecv = c.sendRecvLegacy
}
} else {
// No channels available: use the legacy mechanism.
c.sendRecv = c.sendRecvLegacy
}
return c, nil
}
// watch watches the given socket and calls Close on hang up events.
//
// This is intended to be called as a goroutine.
func (c *Client) watch(socket *unet.Socket) {
events := []unix.PollFd{
unix.PollFd{
Fd: int32(socket.FD()),
Events: unix.POLLHUP | unix.POLLRDHUP,
},
}
for {
// Wait for a shutdown event.
n, err := unix.Ppoll(events, nil, nil)
if n == 0 || err == syscall.EAGAIN {
continue
}
break
}
// Close everything down: this will kick all active clients off any
// pending requests. Note that Close must be safe to call concurrently,
// and multiple times (see Close below).
c.Close()
}
// openChannel attempts to open a client channel.
//
// Note that this function returns naked errors which should not be propagated
// directly to a caller. It is expected that the errors will be logged and a
// fallback path will be used instead.
func (c *Client) openChannel(id int) error {
var (
rchannel0 Rchannel
rchannel1 Rchannel
res = new(channel)
)
// Open the data channel.
if err := c.sendRecvLegacy(&Tchannel{
ID: uint32(id),
Control: 0,
}, &rchannel0); err != nil {
return fmt.Errorf("error handling Tchannel message: %v", err)
}
if rchannel0.FilePayload() == nil {
return fmt.Errorf("missing file descriptor on primary channel")
}
// We don't need to hold this.
defer rchannel0.FilePayload().Close()
// Open the channel for file descriptors.
if err := c.sendRecvLegacy(&Tchannel{
ID: uint32(id),
Control: 1,
}, &rchannel1); err != nil {
return err
}
if rchannel1.FilePayload() == nil {
return fmt.Errorf("missing file descriptor on file descriptor channel")
}
// Construct the endpoints.
res.desc = flipcall.PacketWindowDescriptor{
FD: rchannel0.FilePayload().FD(),
Offset: int64(rchannel0.Offset),
Length: int(rchannel0.Length),
}
if err := res.data.Init(flipcall.ClientSide, res.desc); err != nil {
rchannel1.FilePayload().Close()
return err
}
// The fds channel owns the control payload, and it will be closed when
// the channel object is closed.
res.fds.Init(rchannel1.FilePayload().Release())
// Save the channel.
c.channelsMu.Lock()
defer c.channelsMu.Unlock()
c.channels = append(c.channels, res)
c.inuse = append(c.inuse, nil)
return nil
}
// handleOne handles a single incoming message.
//
// This should only be called with the token from recvr. Note that the received
@@ -247,10 +398,10 @@ func (c *Client) waitAndRecv(done chan error) error {
}
}
// sendRecv performs a roundtrip message exchange.
// sendRecvLegacy performs a roundtrip message exchange.
//
// This is called by internal functions.
func (c *Client) sendRecv(t message, r message) error {
func (c *Client) sendRecvLegacy(t message, r message) error {
tag, ok := c.tagPool.Get()
if !ok {
return ErrOutOfTags
@@ -296,12 +447,107 @@ func (c *Client) sendRecv(t message, r message) error {
return nil
}
// sendRecvChannel uses channels to send a message.
func (c *Client) sendRecvChannel(t message, r message) error {
c.channelsMu.Lock()
if len(c.channels) == 0 {
// No channel available.
c.channelsMu.Unlock()
return c.sendRecvLegacy(t, r)
}
// Find the last used channel.
//
// Note that we must add one to the wait group while holding the
// channel mutex, in order for the Wait operation to be race-free
// below. The Wait operation shuts down all in use channels and
// waits for them to return, but must do so holding the mutex.
idx := len(c.channels) - 1
ch := c.channels[idx]
c.channels = c.channels[:idx]
c.inuse[idx] = ch
c.channelsWg.Add(1)
c.channelsMu.Unlock()
// Ensure that it's connected.
if !ch.connected {
ch.connected = true
if err := ch.data.Connect(); err != nil {
// The channel is unusable, so don't return it.
ch.Close()
c.channelsWg.Done()
return err
}
}
// Send the message.
err := ch.sendRecv(c, t, r)
if err != nil {
// On shutdown, we'll see ENOENT. This is a normal situation, and
// we shouldn't generate a spurious warning message in that case.
log.Debugf("error calling sendRecvChannel: %v", err)
}
c.channelsWg.Done()
// Return the channel.
//
// Note that we check the channel from the inuse slice here. This
// prevents a race where Close is called, which clears inuse, and
// means that we will not actually return the closed channel.
c.channelsMu.Lock()
if c.inuse[idx] != nil {
c.channels = append(c.channels, ch)
c.inuse[idx] = nil
}
c.channelsMu.Unlock()
return err
}
// Version returns the negotiated 9P2000.L.Google version number.
func (c *Client) Version() uint32 {
return c.version
}
// Close closes the underlying socket.
// Close closes the underlying socket and channels.
//
// Because Close may be called asynchronously from watch, it must be
// safe to call concurrently and multiple times.
func (c *Client) Close() error {
c.channelsMu.Lock()
defer c.channelsMu.Unlock()
// Close all inactive channels.
for _, ch := range c.channels {
ch.Shutdown()
ch.Close()
}
// Close all active channels.
for _, ch := range c.inuse {
if ch != nil {
log.Debugf("shutting down active channel@%p...", ch)
ch.Shutdown()
}
}
// Wait for active users.
c.channelsWg.Wait()
// Close all previously active channels.
for i, ch := range c.inuse {
if ch != nil {
ch.Close()
// Clear the inuse entry here so that it will not be returned
// to the channel slice, which is cleared below. See the
// comment at the end of sendRecvChannel.
c.inuse[i] = nil
}
}
c.channels = nil // Prevent use again.
// Close the main socket. Note that operation is safe to be called
// multiple times, unlikely the channel Close operations above, which
// we are careful to ensure aren't called twice.
return c.socket.Close()
}
+46 -4
View File
@@ -35,23 +35,23 @@ func TestVersion(t *testing.T) {
go s.Handle(serverSocket)
// NewClient does a Tversion exchange, so this is our test for success.
c, err := NewClient(clientSocket, 1024*1024 /* 1M message size */, HighestVersionString())
c, err := NewClient(clientSocket, DefaultMessageSize, HighestVersionString())
if err != nil {
t.Fatalf("got %v, expected nil", err)
}
// Check a bogus version string.
if err := c.sendRecv(&Tversion{Version: "notokay", MSize: 1024 * 1024}, &Rversion{}); err != syscall.EINVAL {
if err := c.sendRecv(&Tversion{Version: "notokay", MSize: DefaultMessageSize}, &Rversion{}); err != syscall.EINVAL {
t.Errorf("got %v expected %v", err, syscall.EINVAL)
}
// Check a bogus version number.
if err := c.sendRecv(&Tversion{Version: "9P1000.L", MSize: 1024 * 1024}, &Rversion{}); err != syscall.EINVAL {
if err := c.sendRecv(&Tversion{Version: "9P1000.L", MSize: DefaultMessageSize}, &Rversion{}); err != syscall.EINVAL {
t.Errorf("got %v expected %v", err, syscall.EINVAL)
}
// Check a too high version number.
if err := c.sendRecv(&Tversion{Version: versionString(highestSupportedVersion + 1), MSize: 1024 * 1024}, &Rversion{}); err != syscall.EAGAIN {
if err := c.sendRecv(&Tversion{Version: versionString(highestSupportedVersion + 1), MSize: DefaultMessageSize}, &Rversion{}); err != syscall.EAGAIN {
t.Errorf("got %v expected %v", err, syscall.EAGAIN)
}
@@ -60,3 +60,45 @@ func TestVersion(t *testing.T) {
t.Errorf("got %v expected %v", err, syscall.EINVAL)
}
}
func benchmarkSendRecv(b *testing.B, fn func(c *Client) func(message, message) error) {
// See above.
serverSocket, clientSocket, err := unet.SocketPair(false)
if err != nil {
b.Fatalf("socketpair got err %v expected nil", err)
}
defer clientSocket.Close()
// See above.
s := NewServer(nil)
go s.Handle(serverSocket)
// See above.
c, err := NewClient(clientSocket, DefaultMessageSize, HighestVersionString())
if err != nil {
b.Fatalf("got %v, expected nil", err)
}
// Initialize messages.
sendRecv := fn(c)
tversion := &Tversion{
Version: versionString(highestSupportedVersion),
MSize: DefaultMessageSize,
}
rversion := new(Rversion)
// Run in a loop.
for i := 0; i < b.N; i++ {
if err := sendRecv(tversion, rversion); err != nil {
b.Fatalf("got unexpected err: %v", err)
}
}
}
func BenchmarkSendRecvLegacy(b *testing.B) {
benchmarkSendRecv(b, func(c *Client) func(message, message) error { return c.sendRecvLegacy })
}
func BenchmarkSendRecvChannel(b *testing.B) {
benchmarkSendRecv(b, func(c *Client) func(message, message) error { return c.sendRecvChannel })
}
+50 -3
View File
@@ -305,7 +305,9 @@ func (t *Tlopen) handle(cs *connState) message {
ref.opened = true
ref.openFlags = t.Flags
return &Rlopen{QID: qid, IoUnit: ioUnit, File: osFile}
rlopen := &Rlopen{QID: qid, IoUnit: ioUnit}
rlopen.SetFilePayload(osFile)
return rlopen
}
func (t *Tlcreate) do(cs *connState, uid UID) (*Rlcreate, error) {
@@ -364,7 +366,9 @@ func (t *Tlcreate) do(cs *connState, uid UID) (*Rlcreate, error) {
// Replace the FID reference.
cs.InsertFID(t.FID, newRef)
return &Rlcreate{Rlopen: Rlopen{QID: qid, IoUnit: ioUnit, File: osFile}}, nil
rlcreate := &Rlcreate{Rlopen: Rlopen{QID: qid, IoUnit: ioUnit}}
rlcreate.SetFilePayload(osFile)
return rlcreate, nil
}
// handle implements handler.handle.
@@ -1287,5 +1291,48 @@ func (t *Tlconnect) handle(cs *connState) message {
return newErr(err)
}
return &Rlconnect{File: osFile}
rlconnect := &Rlconnect{}
rlconnect.SetFilePayload(osFile)
return rlconnect
}
// handle implements handler.handle.
func (t *Tchannel) handle(cs *connState) message {
// Ensure that channels are enabled.
if err := cs.initializeChannels(); err != nil {
return newErr(err)
}
// Lookup the given channel.
ch := cs.lookupChannel(t.ID)
if ch == nil {
return newErr(syscall.ENOSYS)
}
// Return the payload. Note that we need to duplicate the file
// descriptor for the channel allocator, because sending is a
// destructive operation between sendRecvLegacy (and now the newer
// channel send operations). Same goes for the client FD.
rchannel := &Rchannel{
Offset: uint64(ch.desc.Offset),
Length: uint64(ch.desc.Length),
}
switch t.Control {
case 0:
// Open the main data channel.
mfd, err := syscall.Dup(int(cs.channelAlloc.FD()))
if err != nil {
return newErr(err)
}
rchannel.SetFilePayload(fd.New(mfd))
case 1:
cfd, err := syscall.Dup(ch.client.FD())
if err != nil {
return newErr(err)
}
rchannel.SetFilePayload(fd.New(cfd))
default:
return newErr(syscall.EINVAL)
}
return rchannel
}
+81 -26
View File
@@ -64,6 +64,21 @@ type filer interface {
SetFilePayload(*fd.FD)
}
// filePayload embeds a File object.
type filePayload struct {
File *fd.FD
}
// FilePayload returns the file payload.
func (f *filePayload) FilePayload() *fd.FD {
return f.File
}
// SetFilePayload sets the received file.
func (f *filePayload) SetFilePayload(file *fd.FD) {
f.File = file
}
// Tversion is a version request.
type Tversion struct {
// MSize is the message size to use.
@@ -524,10 +539,7 @@ type Rlopen struct {
// IoUnit is the recommended I/O unit.
IoUnit uint32
// File may be attached via the socket.
//
// This is an extension specific to this package.
File *fd.FD
filePayload
}
// Decode implements encoder.Decode.
@@ -547,16 +559,6 @@ func (*Rlopen) Type() MsgType {
return MsgRlopen
}
// FilePayload returns the file payload.
func (r *Rlopen) FilePayload() *fd.FD {
return r.File
}
// SetFilePayload sets the received file.
func (r *Rlopen) SetFilePayload(file *fd.FD) {
r.File = file
}
// String implements fmt.Stringer.
func (r *Rlopen) String() string {
return fmt.Sprintf("Rlopen{QID: %s, IoUnit: %d, File: %v}", r.QID, r.IoUnit, r.File)
@@ -2171,8 +2173,7 @@ func (t *Tlconnect) String() string {
// Rlconnect is a connect response.
type Rlconnect struct {
// File is a host socket.
File *fd.FD
filePayload
}
// Decode implements encoder.Decode.
@@ -2186,21 +2187,73 @@ func (*Rlconnect) Type() MsgType {
return MsgRlconnect
}
// FilePayload returns the file payload.
func (r *Rlconnect) FilePayload() *fd.FD {
return r.File
}
// SetFilePayload sets the received file.
func (r *Rlconnect) SetFilePayload(file *fd.FD) {
r.File = file
}
// String implements fmt.Stringer.
func (r *Rlconnect) String() string {
return fmt.Sprintf("Rlconnect{File: %v}", r.File)
}
// Tchannel creates a new channel.
type Tchannel struct {
// ID is the channel ID.
ID uint32
// Control is 0 if the Rchannel response should provide the flipcall
// component of the channel, and 1 if the Rchannel response should
// provide the fdchannel component of the channel.
Control uint32
}
// Decode implements encoder.Decode.
func (t *Tchannel) Decode(b *buffer) {
t.ID = b.Read32()
t.Control = b.Read32()
}
// Encode implements encoder.Encode.
func (t *Tchannel) Encode(b *buffer) {
b.Write32(t.ID)
b.Write32(t.Control)
}
// Type implements message.Type.
func (*Tchannel) Type() MsgType {
return MsgTchannel
}
// String implements fmt.Stringer.
func (t *Tchannel) String() string {
return fmt.Sprintf("Tchannel{ID: %d, Control: %d}", t.ID, t.Control)
}
// Rchannel is the channel response.
type Rchannel struct {
Offset uint64
Length uint64
filePayload
}
// Decode implements encoder.Decode.
func (r *Rchannel) Decode(b *buffer) {
r.Offset = b.Read64()
r.Length = b.Read64()
}
// Encode implements encoder.Encode.
func (r *Rchannel) Encode(b *buffer) {
b.Write64(r.Offset)
b.Write64(r.Length)
}
// Type implements message.Type.
func (*Rchannel) Type() MsgType {
return MsgRchannel
}
// String implements fmt.Stringer.
func (r *Rchannel) String() string {
return fmt.Sprintf("Rchannel{Offset: %d, Length: %d}", r.Offset, r.Length)
}
const maxCacheSize = 3
// msgFactory is used to reduce allocations by caching messages for reuse.
@@ -2356,4 +2409,6 @@ func init() {
msgRegistry.register(MsgRlconnect, func() message { return &Rlconnect{} })
msgRegistry.register(MsgTallocate, func() message { return &Tallocate{} })
msgRegistry.register(MsgRallocate, func() message { return &Rallocate{} })
msgRegistry.register(MsgTchannel, func() message { return &Tchannel{} })
msgRegistry.register(MsgRchannel, func() message { return &Rchannel{} })
}
+2
View File
@@ -378,6 +378,8 @@ const (
MsgRlconnect = 137
MsgTallocate = 138
MsgRallocate = 139
MsgTchannel = 250
MsgRchannel = 251
)
// QIDType represents the file type for QIDs.
+1 -1
View File
@@ -315,7 +315,7 @@ func NewHarness(t *testing.T) (*Harness, *p9.Client) {
}()
// Create the client.
client, err := p9.NewClient(clientSocket, 1024, p9.HighestVersionString())
client, err := p9.NewClient(clientSocket, p9.DefaultMessageSize, p9.HighestVersionString())
if err != nil {
serverSocket.Close()
clientSocket.Close()
+144 -34
View File
@@ -21,6 +21,9 @@ import (
"sync/atomic"
"syscall"
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/fdchannel"
"gvisor.dev/gvisor/pkg/flipcall"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/unet"
)
@@ -45,7 +48,6 @@ type Server struct {
}
// NewServer returns a new server.
//
func NewServer(attacher Attacher) *Server {
return &Server{
attacher: attacher,
@@ -85,6 +87,8 @@ type connState struct {
// version 0 implies 9P2000.L.
version uint32
// -- below relates to the legacy handler --
// recvOkay indicates that a receive may start.
recvOkay chan bool
@@ -93,6 +97,20 @@ type connState struct {
// sendDone is signalled when a send is finished.
sendDone chan error
// -- below relates to the flipcall handler --
// channelMu protects below.
channelMu sync.Mutex
// channelWg represents active workers.
channelWg sync.WaitGroup
// channelAlloc allocates channel memory.
channelAlloc *flipcall.PacketWindowAllocator
// channels are the set of initialized channels.
channels []*channel
}
// fidRef wraps a node and tracks references.
@@ -386,6 +404,99 @@ func (cs *connState) WaitTag(t Tag) {
<-ch
}
// initializeChannels initializes all channels.
//
// This is a no-op if channels are already initialized.
func (cs *connState) initializeChannels() (err error) {
cs.channelMu.Lock()
defer cs.channelMu.Unlock()
// Initialize our channel allocator.
if cs.channelAlloc == nil {
alloc, err := flipcall.NewPacketWindowAllocator()
if err != nil {
return err
}
cs.channelAlloc = alloc
}
// Create all the channels.
for len(cs.channels) < channelsPerClient {
res := &channel{
done: make(chan struct{}),
}
res.desc, err = cs.channelAlloc.Allocate(channelSize)
if err != nil {
return err
}
if err := res.data.Init(flipcall.ServerSide, res.desc); err != nil {
return err
}
socks, err := fdchannel.NewConnectedSockets()
if err != nil {
res.data.Destroy() // Cleanup.
return err
}
res.fds.Init(socks[0])
res.client = fd.New(socks[1])
cs.channels = append(cs.channels, res)
// Start servicing the channel.
//
// When we call stop, we will close all the channels and these
// routines should finish. We need the wait group to ensure
// that active handlers are actually finished before cleanup.
cs.channelWg.Add(1)
go func() { // S/R-SAFE: Server side.
defer cs.channelWg.Done()
res.service(cs)
}()
}
return nil
}
// lookupChannel looks up the channel with given id.
//
// The function returns nil if no such channel is available.
func (cs *connState) lookupChannel(id uint32) *channel {
cs.channelMu.Lock()
defer cs.channelMu.Unlock()
if id >= uint32(len(cs.channels)) {
return nil
}
return cs.channels[id]
}
// handle handles a single message.
func (cs *connState) handle(m message) (r message) {
defer func() {
if r == nil {
// Don't allow a panic to propagate.
recover()
// Include a useful log message.
log.Warningf("panic in handler: %s", debug.Stack())
// Wrap in an EFAULT error; we don't really have a
// better way to describe this kind of error. It will
// usually manifest as a result of the test framework.
r = newErr(syscall.EFAULT)
}
}()
if handler, ok := m.(handler); ok {
// Call the message handler.
r = handler.handle(cs)
} else {
// Produce an ENOSYS error.
r = newErr(syscall.ENOSYS)
}
return
}
// handleRequest handles a single request.
//
// The recvDone channel is signaled when recv is done (with a error if
@@ -428,41 +539,20 @@ func (cs *connState) handleRequest() {
}
// Handle the message.
var r message // r is the response.
defer func() {
if r == nil {
// Don't allow a panic to propagate.
recover()
r := cs.handle(m)
// Include a useful log message.
log.Warningf("panic in handler: %s", debug.Stack())
// Clear the tag before sending. That's because as soon as this hits
// the wire, the client can legally send the same tag.
cs.ClearTag(tag)
// Wrap in an EFAULT error; we don't really have a
// better way to describe this kind of error. It will
// usually manifest as a result of the test framework.
r = newErr(syscall.EFAULT)
}
// Send back the result.
cs.sendMu.Lock()
err = send(cs.conn, tag, r)
cs.sendMu.Unlock()
cs.sendDone <- err
// Clear the tag before sending. That's because as soon as this
// hits the wire, the client can legally send another message
// with the same tag.
cs.ClearTag(tag)
// Send back the result.
cs.sendMu.Lock()
err = send(cs.conn, tag, r)
cs.sendMu.Unlock()
cs.sendDone <- err
}()
if handler, ok := m.(handler); ok {
// Call the message handler.
r = handler.handle(cs)
} else {
// Produce an ENOSYS error.
r = newErr(syscall.ENOSYS)
}
// Return the message to the cache.
msgRegistry.put(m)
m = nil // 'm' should not be touched after this point.
}
func (cs *connState) handleRequests() {
@@ -477,7 +567,27 @@ func (cs *connState) stop() {
close(cs.recvDone)
close(cs.sendDone)
for _, fidRef := range cs.fids {
// Free the channels.
cs.channelMu.Lock()
for _, ch := range cs.channels {
ch.Shutdown()
}
cs.channelWg.Wait()
for _, ch := range cs.channels {
ch.Close()
}
cs.channels = nil // Clear.
cs.channelMu.Unlock()
// Free the channel memory.
if cs.channelAlloc != nil {
cs.channelAlloc.Destroy()
}
// Close all remaining fids.
for fid, fidRef := range cs.fids {
delete(cs.fids, fid)
// Drop final reference in the FID table. Note this should
// always close the file, since we've ensured that there are no
// handlers running via the wait for Pending => 0 below.
@@ -510,7 +620,7 @@ func (cs *connState) service() error {
for i := 0; i < pending; i++ {
<-cs.sendDone
}
return err
return nil
}
// This handler is now pending.
+4 -1
View File
@@ -54,7 +54,10 @@ const (
headerLength uint32 = 7
// maximumLength is the largest possible message.
maximumLength uint32 = 4 * 1024 * 1024
maximumLength uint32 = 1 << 20
// DefaultMessageSize is a sensible default.
DefaultMessageSize uint32 = 64 << 10
// initialBufferLength is the initial data buffer we allocate.
initialBufferLength uint32 = 64
+254
View File
@@ -0,0 +1,254 @@
// Copyright 2019 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 p9
import (
"runtime"
"syscall"
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/fdchannel"
"gvisor.dev/gvisor/pkg/flipcall"
"gvisor.dev/gvisor/pkg/log"
)
// channelsPerClient is the number of channels to create per client.
//
// While the client and server will generally agree on this number, in reality
// it's completely up to the server. We simply define a minimum of 2, and a
// maximum of 4, and select the number of available processes as a tie-breaker.
// Note that we don't want the number of channels to be too large, because each
// will account for channelSize memory used, which can be large.
var channelsPerClient = func() int {
n := runtime.NumCPU()
if n < 2 {
return 2
}
if n > 4 {
return 4
}
return n
}()
// channelSize is the channel size to create.
//
// We simply ensure that this is larger than the largest possible message size,
// plus the flipcall packet header, plus the two bytes we write below.
const channelSize = int(2 + flipcall.PacketHeaderBytes + 2 + maximumLength)
// channel is a fast IPC channel.
//
// The same object is used by both the server and client implementations. In
// general, the client will use only the send and recv methods.
type channel struct {
desc flipcall.PacketWindowDescriptor
data flipcall.Endpoint
fds fdchannel.Endpoint
buf buffer
// -- client only --
connected bool
// -- server only --
client *fd.FD
done chan struct{}
}
// reset resets the channel buffer.
func (ch *channel) reset(sz uint32) {
ch.buf.data = ch.data.Data()[:sz]
}
// service services the channel.
func (ch *channel) service(cs *connState) error {
rsz, err := ch.data.RecvFirst()
if err != nil {
return err
}
for rsz > 0 {
m, err := ch.recv(nil, rsz)
if err != nil {
return err
}
r := cs.handle(m)
msgRegistry.put(m)
rsz, err = ch.send(r)
if err != nil {
return err
}
}
return nil // Done.
}
// Shutdown shuts down the channel.
//
// This must be called before Close.
func (ch *channel) Shutdown() {
ch.data.Shutdown()
}
// Close closes the channel.
//
// This must only be called once, and cannot return an error. Note that
// synchronization for this method is provided at a high-level, depending on
// whether it is the client or server. This cannot be called while there are
// active callers in either service or sendRecv.
//
// Precondition: the channel should be shutdown.
func (ch *channel) Close() error {
// Close all backing transports.
ch.fds.Destroy()
ch.data.Destroy()
if ch.client != nil {
ch.client.Close()
}
return nil
}
// send sends the given message.
//
// The return value is the size of the received response. Not that in the
// server case, this is the size of the next request.
func (ch *channel) send(m message) (uint32, error) {
if log.IsLogging(log.Debug) {
log.Debugf("send [channel @%p] %s", ch, m.String())
}
// Send any file payload.
sentFD := false
if filer, ok := m.(filer); ok {
if f := filer.FilePayload(); f != nil {
if err := ch.fds.SendFD(f.FD()); err != nil {
return 0, syscall.EIO // Map everything to EIO.
}
f.Close() // Per sendRecvLegacy.
sentFD = true // To mark below.
}
}
// Encode the message.
//
// Note that IPC itself encodes the length of messages, so we don't
// need to encode a standard 9P header. We write only the message type.
ch.reset(0)
ch.buf.WriteMsgType(m.Type())
if sentFD {
ch.buf.Write8(1) // Incoming FD.
} else {
ch.buf.Write8(0) // No incoming FD.
}
m.Encode(&ch.buf)
ssz := uint32(len(ch.buf.data)) // Updated below.
// Is there a payload?
if payloader, ok := m.(payloader); ok {
p := payloader.Payload()
copy(ch.data.Data()[ssz:], p)
ssz += uint32(len(p))
}
// Perform the one-shot communication.
n, err := ch.data.SendRecv(ssz)
if err != nil {
if n > 0 {
return n, nil
}
return 0, syscall.EIO // See above.
}
return n, nil
}
// recv decodes a message that exists on the channel.
//
// If the passed r is non-nil, then the type must match or an error will be
// generated. If the passed r is nil, then a new message will be created and
// returned.
func (ch *channel) recv(r message, rsz uint32) (message, error) {
// Decode the response from the inline buffer.
ch.reset(rsz)
t := ch.buf.ReadMsgType()
hasFD := ch.buf.Read8() != 0
if t == MsgRlerror {
// Change the message type. We check for this special case
// after decoding below, and transform into an error.
r = &Rlerror{}
} else if r == nil {
nr, err := msgRegistry.get(0, t)
if err != nil {
return nil, err
}
r = nr // New message.
} else if t != r.Type() {
// Not an error and not the expected response; propagate.
return nil, &ErrBadResponse{Got: t, Want: r.Type()}
}
// Is there a payload? Set to the latter portion.
if payloader, ok := r.(payloader); ok {
fs := payloader.FixedSize()
payloader.SetPayload(ch.buf.data[fs:])
ch.buf.data = ch.buf.data[:fs]
}
r.Decode(&ch.buf)
if ch.buf.isOverrun() {
// Nothing valid was available.
log.Debugf("recv [got %d bytes, needed more]", rsz)
return nil, ErrNoValidMessage
}
// Read any FD result.
if hasFD {
if rfd, err := ch.fds.RecvFDNonblock(); err == nil {
f := fd.New(rfd)
if filer, ok := r.(filer); ok {
// Set the payload.
filer.SetFilePayload(f)
} else {
// Don't want the FD.
f.Close()
}
} else {
// The header bit was set but nothing came in.
log.Warningf("expected FD, got err: %v", err)
}
}
// Log a message.
if log.IsLogging(log.Debug) {
log.Debugf("recv [channel @%p] %s", ch, r.String())
}
// Convert errors appropriately; see above.
if rlerr, ok := r.(*Rlerror); ok {
return nil, syscall.Errno(rlerr.Error)
}
return r, nil
}
// sendRecv sends the given message over the channel.
//
// This is used by the client.
func (ch *channel) sendRecv(c *Client, m, r message) error {
rsz, err := ch.send(m)
if err != nil {
return err
}
_, err = ch.recv(r, rsz)
return err
}
+3 -1
View File
@@ -124,7 +124,9 @@ func TestSendRecvWithFile(t *testing.T) {
t.Fatalf("unable to create file: %v", err)
}
if err := send(client, Tag(1), &Rlopen{File: f}); err != nil {
rlopen := &Rlopen{}
rlopen.SetFilePayload(f)
if err := send(client, Tag(1), rlopen); err != nil {
t.Fatalf("send got err %v expected nil", err)
}
+8 -1
View File
@@ -26,7 +26,7 @@ const (
//
// Clients are expected to start requesting this version number and
// to continuously decrement it until a Tversion request succeeds.
highestSupportedVersion uint32 = 7
highestSupportedVersion uint32 = 8
// lowestSupportedVersion is the lowest supported version X in a
// version string of the format 9P2000.L.Google.X.
@@ -148,3 +148,10 @@ func VersionSupportsMultiUser(v uint32) bool {
func versionSupportsTallocate(v uint32) bool {
return v >= 7
}
// versionSupportsFlipcall returns true if version v supports IPC channels from
// the flipcall package. Note that these must be negotiated, but this version
// string indicates that such a facility exists.
func versionSupportsFlipcall(v uint32) bool {
return v >= 8
}
+12 -2
View File
@@ -88,14 +88,24 @@ var allowedSyscalls = seccomp.SyscallRules{
seccomp.AllowValue(linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG),
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(0),
},
{
seccomp.AllowAny{},
seccomp.AllowValue(linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG),
seccomp.AllowAny{},
},
// Non-private variants are included for flipcall support. They are otherwise
// unncessary, as the sentry will use only private futexes internally.
{
seccomp.AllowAny{},
seccomp.AllowValue(linux.FUTEX_WAIT),
seccomp.AllowAny{},
seccomp.AllowAny{},
},
{
seccomp.AllowAny{},
seccomp.AllowValue(linux.FUTEX_WAKE),
seccomp.AllowAny{},
seccomp.AllowValue(0),
},
},
syscall.SYS_GETPID: {},
+1
View File
@@ -17,6 +17,7 @@ go_library(
],
deps = [
"//pkg/abi/linux",
"//pkg/flipcall",
"//pkg/log",
"//pkg/seccomp",
"@org_golang_x_sys//unix:go_default_library",
+35 -1
View File
@@ -83,6 +83,11 @@ var allowedSyscalls = seccomp.SyscallRules{
seccomp.AllowAny{},
seccomp.AllowValue(syscall.F_GETFD),
},
// Used by flipcall.PacketWindowAllocator.Init().
{
seccomp.AllowAny{},
seccomp.AllowValue(unix.F_ADD_SEALS),
},
},
syscall.SYS_FSTAT: {},
syscall.SYS_FSTATFS: {},
@@ -103,6 +108,19 @@ var allowedSyscalls = seccomp.SyscallRules{
seccomp.AllowAny{},
seccomp.AllowValue(0),
},
// Non-private futex used for flipcall.
seccomp.Rule{
seccomp.AllowAny{},
seccomp.AllowValue(linux.FUTEX_WAIT),
seccomp.AllowAny{},
seccomp.AllowAny{},
},
seccomp.Rule{
seccomp.AllowAny{},
seccomp.AllowValue(linux.FUTEX_WAKE),
seccomp.AllowAny{},
seccomp.AllowAny{},
},
},
syscall.SYS_GETDENTS64: {},
syscall.SYS_GETPID: {},
@@ -112,6 +130,7 @@ var allowedSyscalls = seccomp.SyscallRules{
syscall.SYS_LINKAT: {},
syscall.SYS_LSEEK: {},
syscall.SYS_MADVISE: {},
unix.SYS_MEMFD_CREATE: {}, /// Used by flipcall.PacketWindowAllocator.Init().
syscall.SYS_MKDIRAT: {},
syscall.SYS_MMAP: []seccomp.Rule{
{
@@ -160,6 +179,13 @@ var allowedSyscalls = seccomp.SyscallRules{
syscall.SYS_RT_SIGPROCMASK: {},
syscall.SYS_SCHED_YIELD: {},
syscall.SYS_SENDMSG: []seccomp.Rule{
// Used by fdchannel.Endpoint.SendFD().
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(0),
},
// Used by unet.SocketWriter.WriteVec().
{
seccomp.AllowAny{},
seccomp.AllowAny{},
@@ -170,7 +196,15 @@ var allowedSyscalls = seccomp.SyscallRules{
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_RDWR)},
},
syscall.SYS_SIGALTSTACK: {},
syscall.SYS_SYMLINKAT: {},
// Used by fdchannel.NewConnectedSockets().
syscall.SYS_SOCKETPAIR: {
{
seccomp.AllowValue(syscall.AF_UNIX),
seccomp.AllowValue(syscall.SOCK_SEQPACKET | syscall.SOCK_CLOEXEC),
seccomp.AllowValue(0),
},
},
syscall.SYS_SYMLINKAT: {},
syscall.SYS_TGKILL: []seccomp.Rule{
{
seccomp.AllowValue(uint64(os.Getpid())),