summaryrefslogtreecommitdiffhomepage
path: root/pkg/flipcall
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/flipcall')
-rw-r--r--pkg/flipcall/BUILD32
-rw-r--r--pkg/flipcall/ctrl_futex.go146
-rw-r--r--pkg/flipcall/flipcall.go229
-rw-r--r--pkg/flipcall/flipcall_example_test.go112
-rw-r--r--pkg/flipcall/flipcall_test.go254
-rw-r--r--pkg/flipcall/flipcall_unsafe.go69
-rw-r--r--pkg/flipcall/futex_linux.go111
-rw-r--r--pkg/flipcall/io.go113
-rw-r--r--pkg/flipcall/packet_window_allocator.go166
9 files changed, 0 insertions, 1232 deletions
diff --git a/pkg/flipcall/BUILD b/pkg/flipcall/BUILD
deleted file mode 100644
index bd1d614b6..000000000
--- a/pkg/flipcall/BUILD
+++ /dev/null
@@ -1,32 +0,0 @@
-load("//tools/go_stateify:defs.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "flipcall",
- srcs = [
- "ctrl_futex.go",
- "flipcall.go",
- "flipcall_unsafe.go",
- "futex_linux.go",
- "io.go",
- "packet_window_allocator.go",
- ],
- importpath = "gvisor.dev/gvisor/pkg/flipcall",
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/abi/linux",
- "//pkg/log",
- "//pkg/memutil",
- ],
-)
-
-go_test(
- name = "flipcall_test",
- size = "small",
- srcs = [
- "flipcall_example_test.go",
- "flipcall_test.go",
- ],
- embed = [":flipcall"],
-)
diff --git a/pkg/flipcall/ctrl_futex.go b/pkg/flipcall/ctrl_futex.go
deleted file mode 100644
index 865b6f640..000000000
--- a/pkg/flipcall/ctrl_futex.go
+++ /dev/null
@@ -1,146 +0,0 @@
-// 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 flipcall
-
-import (
- "encoding/json"
- "fmt"
- "math"
- "sync/atomic"
-
- "gvisor.dev/gvisor/pkg/log"
-)
-
-type endpointControlImpl struct {
- state int32
-}
-
-// Bits in endpointControlImpl.state.
-const (
- epsBlocked = 1 << iota
- epsShutdown
-)
-
-func (ep *Endpoint) ctrlInit(opts ...EndpointOption) error {
- if len(opts) != 0 {
- return fmt.Errorf("unknown EndpointOption: %T", opts[0])
- }
- return nil
-}
-
-type ctrlHandshakeRequest struct{}
-
-type ctrlHandshakeResponse struct{}
-
-func (ep *Endpoint) ctrlConnect() error {
- if err := ep.enterFutexWait(); err != nil {
- return err
- }
- _, err := ep.futexConnect(&ctrlHandshakeRequest{})
- ep.exitFutexWait()
- return err
-}
-
-func (ep *Endpoint) ctrlWaitFirst() error {
- if err := ep.enterFutexWait(); err != nil {
- return err
- }
- defer ep.exitFutexWait()
-
- // Wait for the handshake request.
- if err := ep.futexSwitchFromPeer(); err != nil {
- return err
- }
-
- // Read the handshake request.
- reqLen := atomic.LoadUint32(ep.dataLen())
- if reqLen > ep.dataCap {
- return fmt.Errorf("invalid handshake request length %d (maximum %d)", reqLen, ep.dataCap)
- }
- var req ctrlHandshakeRequest
- if err := json.NewDecoder(ep.NewReader(reqLen)).Decode(&req); err != nil {
- return fmt.Errorf("error reading handshake request: %v", err)
- }
-
- // Write the handshake response.
- w := ep.NewWriter()
- if err := json.NewEncoder(w).Encode(ctrlHandshakeResponse{}); err != nil {
- return fmt.Errorf("error writing handshake response: %v", err)
- }
- *ep.dataLen() = w.Len()
-
- // Return control to the client.
- if err := ep.futexSwitchToPeer(); err != nil {
- return err
- }
-
- // Wait for the first non-handshake message.
- return ep.futexSwitchFromPeer()
-}
-
-func (ep *Endpoint) ctrlRoundTrip() error {
- if err := ep.futexSwitchToPeer(); err != nil {
- return err
- }
- if err := ep.enterFutexWait(); err != nil {
- return err
- }
- err := ep.futexSwitchFromPeer()
- ep.exitFutexWait()
- return err
-}
-
-func (ep *Endpoint) ctrlWakeLast() error {
- return ep.futexSwitchToPeer()
-}
-
-func (ep *Endpoint) enterFutexWait() error {
- switch eps := atomic.AddInt32(&ep.ctrl.state, epsBlocked); eps {
- case epsBlocked:
- return nil
- case epsBlocked | epsShutdown:
- atomic.AddInt32(&ep.ctrl.state, -epsBlocked)
- return shutdownError{}
- default:
- // Most likely due to ep.enterFutexWait() being called concurrently
- // from multiple goroutines.
- panic(fmt.Sprintf("invalid flipcall.Endpoint.ctrl.state before flipcall.Endpoint.enterFutexWait(): %v", eps-epsBlocked))
- }
-}
-
-func (ep *Endpoint) exitFutexWait() {
- atomic.AddInt32(&ep.ctrl.state, -epsBlocked)
-}
-
-func (ep *Endpoint) ctrlShutdown() {
- // Set epsShutdown to ensure that future calls to ep.enterFutexWait() fail.
- if atomic.AddInt32(&ep.ctrl.state, epsShutdown)&epsBlocked != 0 {
- // Wake the blocked thread. This must loop because it's possible that
- // FUTEX_WAKE occurs after the waiter sets epsBlocked, but before it
- // blocks in FUTEX_WAIT.
- for {
- // Wake MaxInt32 threads to prevent a broken or malicious peer from
- // swallowing our wakeup by FUTEX_WAITing from multiple threads.
- if err := ep.futexWakeConnState(math.MaxInt32); err != nil {
- log.Warningf("failed to FUTEX_WAKE Endpoints: %v", err)
- break
- }
- yieldThread()
- if atomic.LoadInt32(&ep.ctrl.state)&epsBlocked == 0 {
- break
- }
- }
- }
-}
diff --git a/pkg/flipcall/flipcall.go b/pkg/flipcall/flipcall.go
deleted file mode 100644
index 5c9212c33..000000000
--- a/pkg/flipcall/flipcall.go
+++ /dev/null
@@ -1,229 +0,0 @@
-// 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 flipcall implements a protocol providing Fast Local Interprocess
-// Procedure Calls between mutually-distrusting processes.
-package flipcall
-
-import (
- "fmt"
- "math"
- "sync/atomic"
- "syscall"
-)
-
-// An Endpoint provides the ability to synchronously transfer data and control
-// to a connected peer Endpoint, which may be in another process.
-//
-// Since the Endpoint control transfer model is synchronous, at any given time
-// one Endpoint "has control" (designated the active Endpoint), and the other
-// is "waiting for control" (designated the inactive Endpoint). Users of the
-// flipcall package designate one Endpoint as the client, which is initially
-// active, and the other as the server, which is initially inactive. See
-// flipcall_example_test.go for usage.
-type Endpoint struct {
- // packet is a pointer to the beginning of the packet window. (Since this
- // is a raw OS memory mapping and not a Go object, it does not need to be
- // represented as an unsafe.Pointer.) packet is immutable.
- packet uintptr
-
- // dataCap is the size of the datagram part of the packet window in bytes.
- // dataCap is immutable.
- dataCap uint32
-
- // shutdown is non-zero if Endpoint.Shutdown() has been called, or if the
- // Endpoint has acknowledged shutdown initiated by the peer. shutdown is
- // accessed using atomic memory operations.
- shutdown uint32
-
- // activeState is csClientActive if this is a client Endpoint and
- // csServerActive if this is a server Endpoint.
- activeState uint32
-
- // inactiveState is csServerActive if this is a client Endpoint and
- // csClientActive if this is a server Endpoint.
- inactiveState uint32
-
- ctrl endpointControlImpl
-}
-
-// Init must be called on zero-value Endpoints before first use. If it
-// succeeds, ep.Destroy() must be called once the Endpoint is no longer in use.
-//
-// pwd represents the packet window used to exchange data with the peer
-// Endpoint. FD may differ between Endpoints if they are in different
-// processes, but must represent the same file. The packet window must
-// initially be filled with zero bytes.
-func (ep *Endpoint) Init(pwd PacketWindowDescriptor, opts ...EndpointOption) error {
- if pwd.Length < pageSize {
- return fmt.Errorf("packet window size (%d) less than minimum (%d)", pwd.Length, pageSize)
- }
- if pwd.Length > math.MaxUint32 {
- return fmt.Errorf("packet window size (%d) exceeds maximum (%d)", pwd.Length, math.MaxUint32)
- }
- m, _, e := syscall.RawSyscall6(syscall.SYS_MMAP, 0, uintptr(pwd.Length), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED, uintptr(pwd.FD), uintptr(pwd.Offset))
- if e != 0 {
- return fmt.Errorf("failed to mmap packet window: %v", e)
- }
- ep.packet = m
- ep.dataCap = uint32(pwd.Length) - uint32(PacketHeaderBytes)
- // These will be overwritten by ep.Connect() for client Endpoints.
- ep.activeState = csServerActive
- ep.inactiveState = csClientActive
- if err := ep.ctrlInit(opts...); err != nil {
- ep.unmapPacket()
- return err
- }
- return nil
-}
-
-// NewEndpoint is a convenience function that returns an initialized Endpoint
-// allocated on the heap.
-func NewEndpoint(pwd PacketWindowDescriptor, opts ...EndpointOption) (*Endpoint, error) {
- var ep Endpoint
- if err := ep.Init(pwd, opts...); err != nil {
- return nil, err
- }
- return &ep, nil
-}
-
-// An EndpointOption configures an Endpoint.
-type EndpointOption interface {
- isEndpointOption()
-}
-
-// Destroy releases resources owned by ep. No other Endpoint methods may be
-// called after Destroy.
-func (ep *Endpoint) Destroy() {
- ep.unmapPacket()
-}
-
-func (ep *Endpoint) unmapPacket() {
- syscall.RawSyscall(syscall.SYS_MUNMAP, ep.packet, uintptr(ep.dataCap)+PacketHeaderBytes, 0)
- ep.packet = 0
-}
-
-// Shutdown causes concurrent and future calls to ep.Connect(), ep.SendRecv(),
-// ep.RecvFirst(), and ep.SendLast() to unblock and return errors. It does not
-// wait for concurrent calls to return. The effect of Shutdown on the peer
-// Endpoint is unspecified. Successive calls to Shutdown have no effect.
-//
-// Shutdown is the only Endpoint method that may be called concurrently with
-// other methods on the same Endpoint.
-func (ep *Endpoint) Shutdown() {
- if atomic.SwapUint32(&ep.shutdown, 1) != 0 {
- // ep.Shutdown() has previously been called.
- return
- }
- ep.ctrlShutdown()
-}
-
-// isShutdownLocally returns true if ep.Shutdown() has been called.
-func (ep *Endpoint) isShutdownLocally() bool {
- return atomic.LoadUint32(&ep.shutdown) != 0
-}
-
-type shutdownError struct{}
-
-// Error implements error.Error.
-func (shutdownError) Error() string {
- return "flipcall connection shutdown"
-}
-
-// DataCap returns the maximum datagram size supported by ep. Equivalently,
-// DataCap returns len(ep.Data()).
-func (ep *Endpoint) DataCap() uint32 {
- return ep.dataCap
-}
-
-// Connection state.
-const (
- // The client is, by definition, initially active, so this must be 0.
- csClientActive = 0
- csServerActive = 1
-)
-
-// Connect designates ep as a client Endpoint and blocks until the peer
-// Endpoint has called Endpoint.RecvFirst().
-//
-// Preconditions: ep.Connect(), ep.RecvFirst(), ep.SendRecv(), and
-// ep.SendLast() have never been called.
-func (ep *Endpoint) Connect() error {
- ep.activeState = csClientActive
- ep.inactiveState = csServerActive
- return ep.ctrlConnect()
-}
-
-// RecvFirst blocks until the peer Endpoint calls Endpoint.SendRecv(), then
-// returns the datagram length specified by that call.
-//
-// Preconditions: ep.SendRecv(), ep.RecvFirst(), and ep.SendLast() have never
-// been called.
-func (ep *Endpoint) RecvFirst() (uint32, error) {
- if err := ep.ctrlWaitFirst(); err != nil {
- return 0, err
- }
- recvDataLen := atomic.LoadUint32(ep.dataLen())
- if recvDataLen > ep.dataCap {
- return 0, fmt.Errorf("received packet with invalid datagram length %d (maximum %d)", recvDataLen, ep.dataCap)
- }
- return recvDataLen, nil
-}
-
-// SendRecv transfers control to the peer Endpoint, causing its call to
-// Endpoint.SendRecv() or Endpoint.RecvFirst() to return with the given
-// datagram length, then blocks until the peer Endpoint calls
-// Endpoint.SendRecv() or Endpoint.SendLast().
-//
-// Preconditions: dataLen <= ep.DataCap(). No previous call to ep.SendRecv() or
-// ep.RecvFirst() has returned an error. ep.SendLast() has never been called.
-// If ep is a client Endpoint, ep.Connect() has previously been called and
-// returned nil.
-func (ep *Endpoint) SendRecv(dataLen uint32) (uint32, error) {
- if dataLen > ep.dataCap {
- panic(fmt.Sprintf("attempting to send packet with datagram length %d (maximum %d)", dataLen, ep.dataCap))
- }
- // This store can safely be non-atomic: Under correct operation we should
- // be the only thread writing ep.dataLen(), and ep.ctrlRoundTrip() will
- // synchronize with the receiver. We will not read from ep.dataLen() until
- // after ep.ctrlRoundTrip(), so if the peer is mutating it concurrently then
- // they can only shoot themselves in the foot.
- *ep.dataLen() = dataLen
- if err := ep.ctrlRoundTrip(); err != nil {
- return 0, err
- }
- recvDataLen := atomic.LoadUint32(ep.dataLen())
- if recvDataLen > ep.dataCap {
- return 0, fmt.Errorf("received packet with invalid datagram length %d (maximum %d)", recvDataLen, ep.dataCap)
- }
- return recvDataLen, nil
-}
-
-// SendLast causes the peer Endpoint's call to Endpoint.SendRecv() or
-// Endpoint.RecvFirst() to return with the given datagram length.
-//
-// Preconditions: dataLen <= ep.DataCap(). No previous call to ep.SendRecv() or
-// ep.RecvFirst() has returned an error. ep.SendLast() has never been called.
-// If ep is a client Endpoint, ep.Connect() has previously been called and
-// returned nil.
-func (ep *Endpoint) SendLast(dataLen uint32) error {
- if dataLen > ep.dataCap {
- panic(fmt.Sprintf("attempting to send packet with datagram length %d (maximum %d)", dataLen, ep.dataCap))
- }
- *ep.dataLen() = dataLen
- if err := ep.ctrlWakeLast(); err != nil {
- return err
- }
- return nil
-}
diff --git a/pkg/flipcall/flipcall_example_test.go b/pkg/flipcall/flipcall_example_test.go
deleted file mode 100644
index edb6a8bef..000000000
--- a/pkg/flipcall/flipcall_example_test.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// 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 flipcall
-
-import (
- "bytes"
- "fmt"
- "sync"
-)
-
-func Example() {
- const (
- reqPrefix = "request "
- respPrefix = "response "
- count = 3
- maxMessageLen = len(respPrefix) + 1 // 1 digit
- )
-
- pwa, err := NewPacketWindowAllocator()
- if err != nil {
- panic(err)
- }
- defer pwa.Destroy()
- pwd, err := pwa.Allocate(PacketWindowLengthForDataCap(uint32(maxMessageLen)))
- if err != nil {
- panic(err)
- }
- var clientEP Endpoint
- if err := clientEP.Init(pwd); err != nil {
- panic(err)
- }
- defer clientEP.Destroy()
- var serverEP Endpoint
- if err := serverEP.Init(pwd); err != nil {
- panic(err)
- }
- defer serverEP.Destroy()
-
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- i := 0
- var buf bytes.Buffer
- // wait for first request
- n, err := serverEP.RecvFirst()
- if err != nil {
- return
- }
- for {
- // read request
- buf.Reset()
- buf.Write(serverEP.Data()[:n])
- fmt.Println(buf.String())
- // write response
- buf.Reset()
- fmt.Fprintf(&buf, "%s%d", respPrefix, i)
- copy(serverEP.Data(), buf.Bytes())
- // send response and wait for next request
- n, err = serverEP.SendRecv(uint32(buf.Len()))
- if err != nil {
- return
- }
- i++
- }
- }()
- defer func() {
- serverEP.Shutdown()
- serverRun.Wait()
- }()
-
- // establish connection as client
- if err := clientEP.Connect(); err != nil {
- panic(err)
- }
- var buf bytes.Buffer
- for i := 0; i < count; i++ {
- // write request
- buf.Reset()
- fmt.Fprintf(&buf, "%s%d", reqPrefix, i)
- copy(clientEP.Data(), buf.Bytes())
- // send request and wait for response
- n, err := clientEP.SendRecv(uint32(buf.Len()))
- if err != nil {
- panic(err)
- }
- // read response
- buf.Reset()
- buf.Write(clientEP.Data()[:n])
- fmt.Println(buf.String())
- }
-
- // Output:
- // request 0
- // response 0
- // request 1
- // response 1
- // request 2
- // response 2
-}
diff --git a/pkg/flipcall/flipcall_test.go b/pkg/flipcall/flipcall_test.go
deleted file mode 100644
index da9d736ab..000000000
--- a/pkg/flipcall/flipcall_test.go
+++ /dev/null
@@ -1,254 +0,0 @@
-// 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 flipcall
-
-import (
- "runtime"
- "sync"
- "testing"
- "time"
-)
-
-var testPacketWindowSize = pageSize
-
-type testConnection struct {
- pwa PacketWindowAllocator
- clientEP Endpoint
- serverEP Endpoint
-}
-
-func newTestConnectionWithOptions(tb testing.TB, clientOpts, serverOpts []EndpointOption) *testConnection {
- c := &testConnection{}
- if err := c.pwa.Init(); err != nil {
- tb.Fatalf("failed to create PacketWindowAllocator: %v", err)
- }
- pwd, err := c.pwa.Allocate(testPacketWindowSize)
- if err != nil {
- c.pwa.Destroy()
- tb.Fatalf("PacketWindowAllocator.Allocate() failed: %v", err)
- }
- if err := c.clientEP.Init(pwd, clientOpts...); err != nil {
- c.pwa.Destroy()
- tb.Fatalf("failed to create client Endpoint: %v", err)
- }
- if err := c.serverEP.Init(pwd, serverOpts...); err != nil {
- c.pwa.Destroy()
- c.clientEP.Destroy()
- tb.Fatalf("failed to create server Endpoint: %v", err)
- }
- return c
-}
-
-func newTestConnection(tb testing.TB) *testConnection {
- return newTestConnectionWithOptions(tb, nil, nil)
-}
-
-func (c *testConnection) destroy() {
- c.pwa.Destroy()
- c.clientEP.Destroy()
- c.serverEP.Destroy()
-}
-
-func testSendRecv(t *testing.T, c *testConnection) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- t.Logf("server Endpoint waiting for packet 1")
- if _, err := c.serverEP.RecvFirst(); err != nil {
- t.Fatalf("server Endpoint.RecvFirst() failed: %v", err)
- }
- t.Logf("server Endpoint got packet 1, sending packet 2 and waiting for packet 3")
- if _, err := c.serverEP.SendRecv(0); err != nil {
- t.Fatalf("server Endpoint.SendRecv() failed: %v", err)
- }
- t.Logf("server Endpoint got packet 3")
- }()
- defer func() {
- // Ensure that the server goroutine is cleaned up before
- // c.serverEP.Destroy(), even if the test fails.
- c.serverEP.Shutdown()
- serverRun.Wait()
- }()
-
- t.Logf("client Endpoint establishing connection")
- if err := c.clientEP.Connect(); err != nil {
- t.Fatalf("client Endpoint.Connect() failed: %v", err)
- }
- t.Logf("client Endpoint sending packet 1 and waiting for packet 2")
- if _, err := c.clientEP.SendRecv(0); err != nil {
- t.Fatalf("client Endpoint.SendRecv() failed: %v", err)
- }
- t.Logf("client Endpoint got packet 2, sending packet 3")
- if err := c.clientEP.SendLast(0); err != nil {
- t.Fatalf("client Endpoint.SendLast() failed: %v", err)
- }
- t.Logf("waiting for server goroutine to complete")
- serverRun.Wait()
-}
-
-func TestSendRecv(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testSendRecv(t, c)
-}
-
-func testShutdownConnect(t *testing.T, c *testConnection) {
- var clientRun sync.WaitGroup
- clientRun.Add(1)
- go func() {
- defer clientRun.Done()
- if err := c.clientEP.Connect(); err == nil {
- t.Errorf("client Endpoint.Connect() succeeded unexpectedly")
- }
- }()
- time.Sleep(time.Second) // to allow c.clientEP.Connect() to block
- c.clientEP.Shutdown()
- clientRun.Wait()
-}
-
-func TestShutdownConnect(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownConnect(t, c)
-}
-
-func testShutdownRecvFirstBeforeConnect(t *testing.T, c *testConnection) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- _, err := c.serverEP.RecvFirst()
- if err == nil {
- t.Errorf("server Endpoint.RecvFirst() succeeded unexpectedly")
- }
- }()
- time.Sleep(time.Second) // to allow c.serverEP.RecvFirst() to block
- c.serverEP.Shutdown()
- serverRun.Wait()
-}
-
-func TestShutdownRecvFirstBeforeConnect(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownRecvFirstBeforeConnect(t, c)
-}
-
-func testShutdownRecvFirstAfterConnect(t *testing.T, c *testConnection) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- if _, err := c.serverEP.RecvFirst(); err == nil {
- t.Fatalf("server Endpoint.RecvFirst() succeeded unexpectedly")
- }
- }()
- defer func() {
- // Ensure that the server goroutine is cleaned up before
- // c.serverEP.Destroy(), even if the test fails.
- c.serverEP.Shutdown()
- serverRun.Wait()
- }()
- if err := c.clientEP.Connect(); err != nil {
- t.Fatalf("client Endpoint.Connect() failed: %v", err)
- }
- c.serverEP.Shutdown()
- serverRun.Wait()
-}
-
-func TestShutdownRecvFirstAfterConnect(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownRecvFirstAfterConnect(t, c)
-}
-
-func testShutdownSendRecv(t *testing.T, c *testConnection) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- if _, err := c.serverEP.RecvFirst(); err != nil {
- t.Fatalf("server Endpoint.RecvFirst() failed: %v", err)
- }
- if _, err := c.serverEP.SendRecv(0); err == nil {
- t.Errorf("server Endpoint.SendRecv() succeeded unexpectedly")
- }
- }()
- defer func() {
- // Ensure that the server goroutine is cleaned up before
- // c.serverEP.Destroy(), even if the test fails.
- c.serverEP.Shutdown()
- serverRun.Wait()
- }()
- if err := c.clientEP.Connect(); err != nil {
- t.Fatalf("client Endpoint.Connect() failed: %v", err)
- }
- if _, err := c.clientEP.SendRecv(0); err != nil {
- t.Fatalf("client Endpoint.SendRecv() failed: %v", err)
- }
- time.Sleep(time.Second) // to allow serverEP.SendRecv() to block
- c.serverEP.Shutdown()
- serverRun.Wait()
-}
-
-func TestShutdownSendRecv(t *testing.T) {
- c := newTestConnection(t)
- defer c.destroy()
- testShutdownSendRecv(t, c)
-}
-
-func benchmarkSendRecv(b *testing.B, c *testConnection) {
- var serverRun sync.WaitGroup
- serverRun.Add(1)
- go func() {
- defer serverRun.Done()
- if b.N == 0 {
- return
- }
- if _, err := c.serverEP.RecvFirst(); err != nil {
- b.Fatalf("server Endpoint.RecvFirst() failed: %v", err)
- }
- for i := 1; i < b.N; i++ {
- if _, err := c.serverEP.SendRecv(0); err != nil {
- b.Fatalf("server Endpoint.SendRecv() failed: %v", err)
- }
- }
- if err := c.serverEP.SendLast(0); err != nil {
- b.Fatalf("server Endpoint.SendLast() failed: %v", err)
- }
- }()
- defer func() {
- c.serverEP.Shutdown()
- serverRun.Wait()
- }()
-
- if err := c.clientEP.Connect(); err != nil {
- b.Fatalf("client Endpoint.Connect() failed: %v", err)
- }
- runtime.GC()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if _, err := c.clientEP.SendRecv(0); err != nil {
- b.Fatalf("client Endpoint.SendRecv() failed: %v", err)
- }
- }
- b.StopTimer()
-}
-
-func BenchmarkSendRecv(b *testing.B) {
- c := newTestConnection(b)
- defer c.destroy()
- benchmarkSendRecv(b, c)
-}
diff --git a/pkg/flipcall/flipcall_unsafe.go b/pkg/flipcall/flipcall_unsafe.go
deleted file mode 100644
index 7c8977893..000000000
--- a/pkg/flipcall/flipcall_unsafe.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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 flipcall
-
-import (
- "reflect"
- "unsafe"
-)
-
-// Packets consist of an 8-byte header followed by an arbitrarily-sized
-// datagram. The header consists of:
-//
-// - A 4-byte native-endian connection state.
-//
-// - A 4-byte native-endian datagram length in bytes.
-const (
- sizeofUint32 = unsafe.Sizeof(uint32(0))
-
- // PacketHeaderBytes is the size of a flipcall packet header in bytes. The
- // maximum datagram size supported by a flipcall connection is equal to the
- // length of the packet window minus PacketHeaderBytes.
- //
- // PacketHeaderBytes is exported to support its use in constant
- // expressions. Non-constant expressions may prefer to use
- // PacketWindowLengthForDataCap().
- PacketHeaderBytes = 2 * sizeofUint32
-)
-
-func (ep *Endpoint) connState() *uint32 {
- return (*uint32)((unsafe.Pointer)(ep.packet))
-}
-
-func (ep *Endpoint) dataLen() *uint32 {
- return (*uint32)((unsafe.Pointer)(ep.packet + sizeofUint32))
-}
-
-// Data returns the datagram part of ep's packet window as a byte slice.
-//
-// Note that the packet window is shared with the potentially-untrusted peer
-// Endpoint, which may concurrently mutate the contents of the packet window.
-// Thus:
-//
-// - Readers must not assume that two reads of the same byte in Data() will
-// return the same result. In other words, readers should read any given byte
-// in Data() at most once.
-//
-// - Writers must not assume that they will read back the same data that they
-// have written. In other words, writers should avoid reading from Data() at
-// all.
-func (ep *Endpoint) Data() []byte {
- var bs []byte
- bsReflect := (*reflect.SliceHeader)((unsafe.Pointer)(&bs))
- bsReflect.Data = ep.packet + PacketHeaderBytes
- bsReflect.Len = int(ep.dataCap)
- bsReflect.Cap = int(ep.dataCap)
- return bs
-}
diff --git a/pkg/flipcall/futex_linux.go b/pkg/flipcall/futex_linux.go
deleted file mode 100644
index e7dd812b3..000000000
--- a/pkg/flipcall/futex_linux.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// 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.
-
-// +build linux
-
-package flipcall
-
-import (
- "encoding/json"
- "fmt"
- "runtime"
- "sync/atomic"
- "syscall"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
-)
-
-func (ep *Endpoint) futexConnect(req *ctrlHandshakeRequest) (ctrlHandshakeResponse, error) {
- var resp ctrlHandshakeResponse
-
- // Write the handshake request.
- w := ep.NewWriter()
- if err := json.NewEncoder(w).Encode(req); err != nil {
- return resp, fmt.Errorf("error writing handshake request: %v", err)
- }
- *ep.dataLen() = w.Len()
-
- // Exchange control with the server.
- if err := ep.futexSwitchToPeer(); err != nil {
- return resp, err
- }
- if err := ep.futexSwitchFromPeer(); err != nil {
- return resp, err
- }
-
- // Read the handshake response.
- respLen := atomic.LoadUint32(ep.dataLen())
- if respLen > ep.dataCap {
- return resp, fmt.Errorf("invalid handshake response length %d (maximum %d)", respLen, ep.dataCap)
- }
- if err := json.NewDecoder(ep.NewReader(respLen)).Decode(&resp); err != nil {
- return resp, fmt.Errorf("error reading handshake response: %v", err)
- }
-
- return resp, nil
-}
-
-func (ep *Endpoint) futexSwitchToPeer() error {
- // Update connection state to indicate that the peer should be active.
- if !atomic.CompareAndSwapUint32(ep.connState(), ep.activeState, ep.inactiveState) {
- return fmt.Errorf("unexpected connection state before FUTEX_WAKE: %v", atomic.LoadUint32(ep.connState()))
- }
-
- // Wake the peer's Endpoint.futexSwitchFromPeer().
- if err := ep.futexWakeConnState(1); err != nil {
- return fmt.Errorf("failed to FUTEX_WAKE peer Endpoint: %v", err)
- }
- return nil
-}
-
-func (ep *Endpoint) futexSwitchFromPeer() error {
- for {
- switch cs := atomic.LoadUint32(ep.connState()); cs {
- case ep.activeState:
- return nil
- case ep.inactiveState:
- // Continue to FUTEX_WAIT.
- default:
- return fmt.Errorf("unexpected connection state before FUTEX_WAIT: %v", cs)
- }
- if ep.isShutdownLocally() {
- return shutdownError{}
- }
- if err := ep.futexWaitConnState(ep.inactiveState); err != nil {
- return fmt.Errorf("failed to FUTEX_WAIT for peer Endpoint: %v", err)
- }
- }
-}
-
-func (ep *Endpoint) futexWakeConnState(numThreads int32) error {
- if _, _, e := syscall.RawSyscall(syscall.SYS_FUTEX, ep.packet, linux.FUTEX_WAKE, uintptr(numThreads)); e != 0 {
- return e
- }
- return nil
-}
-
-func (ep *Endpoint) futexWaitConnState(curState uint32) error {
- _, _, e := syscall.Syscall6(syscall.SYS_FUTEX, ep.packet, linux.FUTEX_WAIT, uintptr(curState), 0, 0, 0)
- if e != 0 && e != syscall.EAGAIN && e != syscall.EINTR {
- return e
- }
- return nil
-}
-
-func yieldThread() {
- syscall.Syscall(syscall.SYS_SCHED_YIELD, 0, 0, 0)
- // The thread we're trying to yield to may be waiting for a Go runtime P.
- // runtime.Gosched() will hand off ours if necessary.
- runtime.Gosched()
-}
diff --git a/pkg/flipcall/io.go b/pkg/flipcall/io.go
deleted file mode 100644
index 85e40b932..000000000
--- a/pkg/flipcall/io.go
+++ /dev/null
@@ -1,113 +0,0 @@
-// 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 flipcall
-
-import (
- "fmt"
- "io"
-)
-
-// DatagramReader implements io.Reader by reading a datagram from an Endpoint's
-// packet window. Its use is optional; users that can use Endpoint.Data() more
-// efficiently are advised to do so.
-type DatagramReader struct {
- ep *Endpoint
- off uint32
- end uint32
-}
-
-// Init must be called on zero-value DatagramReaders before first use.
-//
-// Preconditions: dataLen is 0, or was returned by a previous call to
-// ep.RecvFirst() or ep.SendRecv().
-func (r *DatagramReader) Init(ep *Endpoint, dataLen uint32) {
- r.ep = ep
- r.Reset(dataLen)
-}
-
-// Reset causes r to begin reading a new datagram of the given length from the
-// associated Endpoint.
-//
-// Preconditions: dataLen is 0, or was returned by a previous call to the
-// associated Endpoint's RecvFirst() or SendRecv() methods.
-func (r *DatagramReader) Reset(dataLen uint32) {
- if dataLen > r.ep.dataCap {
- panic(fmt.Sprintf("invalid dataLen (%d) > ep.dataCap (%d)", dataLen, r.ep.dataCap))
- }
- r.off = 0
- r.end = dataLen
-}
-
-// NewReader is a convenience function that returns an initialized
-// DatagramReader allocated on the heap.
-//
-// Preconditions: dataLen was returned by a previous call to ep.RecvFirst() or
-// ep.SendRecv().
-func (ep *Endpoint) NewReader(dataLen uint32) *DatagramReader {
- r := &DatagramReader{}
- r.Init(ep, dataLen)
- return r
-}
-
-// Read implements io.Reader.Read.
-func (r *DatagramReader) Read(dst []byte) (int, error) {
- n := copy(dst, r.ep.Data()[r.off:r.end])
- r.off += uint32(n)
- if r.off == r.end {
- return n, io.EOF
- }
- return n, nil
-}
-
-// DatagramWriter implements io.Writer by writing a datagram to an Endpoint's
-// packet window. Its use is optional; users that can use Endpoint.Data() more
-// efficiently are advised to do so.
-type DatagramWriter struct {
- ep *Endpoint
- off uint32
-}
-
-// Init must be called on zero-value DatagramWriters before first use.
-func (w *DatagramWriter) Init(ep *Endpoint) {
- w.ep = ep
-}
-
-// Reset causes w to begin writing a new datagram to the associated Endpoint.
-func (w *DatagramWriter) Reset() {
- w.off = 0
-}
-
-// NewWriter is a convenience function that returns an initialized
-// DatagramWriter allocated on the heap.
-func (ep *Endpoint) NewWriter() *DatagramWriter {
- w := &DatagramWriter{}
- w.Init(ep)
- return w
-}
-
-// Write implements io.Writer.Write.
-func (w *DatagramWriter) Write(src []byte) (int, error) {
- n := copy(w.ep.Data()[w.off:w.ep.dataCap], src)
- w.off += uint32(n)
- if n != len(src) {
- return n, fmt.Errorf("datagram would exceed maximum size of %d bytes", w.ep.dataCap)
- }
- return n, nil
-}
-
-// Len returns the length of the written datagram.
-func (w *DatagramWriter) Len() uint32 {
- return w.off
-}
diff --git a/pkg/flipcall/packet_window_allocator.go b/pkg/flipcall/packet_window_allocator.go
deleted file mode 100644
index ccb918fab..000000000
--- a/pkg/flipcall/packet_window_allocator.go
+++ /dev/null
@@ -1,166 +0,0 @@
-// 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 flipcall
-
-import (
- "fmt"
- "math/bits"
- "os"
- "syscall"
-
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/memutil"
-)
-
-var (
- pageSize = os.Getpagesize()
- pageMask = pageSize - 1
-)
-
-func init() {
- if bits.OnesCount(uint(pageSize)) != 1 {
- // This is depended on by roundUpToPage().
- panic(fmt.Sprintf("system page size (%d) is not a power of 2", pageSize))
- }
- if uintptr(pageSize) < PacketHeaderBytes {
- // This is required since Endpoint.Init() imposes a minimum packet
- // window size of 1 page.
- panic(fmt.Sprintf("system page size (%d) is less than packet header size (%d)", pageSize, PacketHeaderBytes))
- }
-}
-
-// PacketWindowDescriptor represents a packet window, a range of pages in a
-// shared memory file that is used to exchange packets between partner
-// Endpoints.
-type PacketWindowDescriptor struct {
- // FD is the file descriptor representing the shared memory file.
- FD int
-
- // Offset is the offset into the shared memory file at which the packet
- // window begins.
- Offset int64
-
- // Length is the size of the packet window in bytes.
- Length int
-}
-
-// PacketWindowLengthForDataCap returns the minimum packet window size required
-// to accommodate datagrams of the given size in bytes.
-func PacketWindowLengthForDataCap(dataCap uint32) int {
- return roundUpToPage(int(dataCap) + int(PacketHeaderBytes))
-}
-
-func roundUpToPage(x int) int {
- return (x + pageMask) &^ pageMask
-}
-
-// A PacketWindowAllocator owns a shared memory file, and allocates packet
-// windows from it.
-type PacketWindowAllocator struct {
- fd int
- nextAlloc int64
- fileSize int64
-}
-
-// Init must be called on zero-value PacketWindowAllocators before first use.
-// If it succeeds, Destroy() must be called once the PacketWindowAllocator is
-// no longer in use.
-func (pwa *PacketWindowAllocator) Init() error {
- fd, err := memutil.CreateMemFD("flipcall_packet_windows", linux.MFD_CLOEXEC|linux.MFD_ALLOW_SEALING)
- if err != nil {
- return fmt.Errorf("failed to create memfd: %v", err)
- }
- // Apply F_SEAL_SHRINK to prevent either party from causing SIGBUS in the
- // other by truncating the file, and F_SEAL_SEAL to prevent either party
- // from applying F_SEAL_GROW or F_SEAL_WRITE.
- if _, _, e := syscall.RawSyscall(syscall.SYS_FCNTL, uintptr(fd), linux.F_ADD_SEALS, linux.F_SEAL_SHRINK|linux.F_SEAL_SEAL); e != 0 {
- syscall.Close(fd)
- return fmt.Errorf("failed to apply memfd seals: %v", e)
- }
- pwa.fd = fd
- return nil
-}
-
-// NewPacketWindowAllocator is a convenience function that returns an
-// initialized PacketWindowAllocator allocated on the heap.
-func NewPacketWindowAllocator() (*PacketWindowAllocator, error) {
- var pwa PacketWindowAllocator
- if err := pwa.Init(); err != nil {
- return nil, err
- }
- return &pwa, nil
-}
-
-// Destroy releases resources owned by pwa. This invalidates file descriptors
-// previously returned by pwa.FD() and pwd.Allocate().
-func (pwa *PacketWindowAllocator) Destroy() {
- syscall.Close(pwa.fd)
-}
-
-// FD represents the file descriptor of the shared memory file backing pwa.
-func (pwa *PacketWindowAllocator) FD() int {
- return pwa.fd
-}
-
-// Allocate allocates a new packet window of at least the given size and
-// returns a PacketWindowDescriptor representing it.
-//
-// Preconditions: size > 0.
-func (pwa *PacketWindowAllocator) Allocate(size int) (PacketWindowDescriptor, error) {
- if size <= 0 {
- return PacketWindowDescriptor{}, fmt.Errorf("invalid size: %d", size)
- }
- // Page-align size to ensure that pwa.nextAlloc remains page-aligned.
- size = roundUpToPage(size)
- if size <= 0 {
- return PacketWindowDescriptor{}, fmt.Errorf("size %d overflows after rounding up to page size", size)
- }
- end := pwa.nextAlloc + int64(size) // overflow checked by ensureFileSize
- if err := pwa.ensureFileSize(end); err != nil {
- return PacketWindowDescriptor{}, err
- }
- start := pwa.nextAlloc
- pwa.nextAlloc = end
- return PacketWindowDescriptor{
- FD: pwa.fd,
- Offset: start,
- Length: size,
- }, nil
-}
-
-func (pwa *PacketWindowAllocator) ensureFileSize(min int64) error {
- if min <= 0 {
- return fmt.Errorf("file size would overflow")
- }
- if pwa.fileSize >= min {
- return nil
- }
- newSize := 2 * pwa.fileSize
- if newSize == 0 {
- newSize = int64(pageSize)
- }
- for newSize < min {
- newNewSize := newSize * 2
- if newNewSize <= 0 {
- return fmt.Errorf("file size would overflow")
- }
- newSize = newNewSize
- }
- if err := syscall.Ftruncate(pwa.fd, newSize); err != nil {
- return fmt.Errorf("ftruncate failed: %v", err)
- }
- pwa.fileSize = newSize
- return nil
-}