github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/flipcall/flipcall.go (about)

     1  // Copyright 2019 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 flipcall implements a protocol providing Fast Local Interprocess
    16  // Procedure Calls between mutually-distrusting processes.
    17  package flipcall
    18  
    19  import (
    20  	"fmt"
    21  	"math"
    22  
    23  	"golang.org/x/sys/unix"
    24  	"github.com/metacubex/gvisor/pkg/atomicbitops"
    25  	"github.com/metacubex/gvisor/pkg/memutil"
    26  )
    27  
    28  // An Endpoint provides the ability to synchronously transfer data and control
    29  // to a connected peer Endpoint, which may be in another process.
    30  //
    31  // Since the Endpoint control transfer model is synchronous, at any given time
    32  // one Endpoint "has control" (designated the active Endpoint), and the other
    33  // is "waiting for control" (designated the inactive Endpoint). Users of the
    34  // flipcall package designate one Endpoint as the client, which is initially
    35  // active, and the other as the server, which is initially inactive. See
    36  // flipcall_example_test.go for usage.
    37  type Endpoint struct {
    38  	// packet is a pointer to the beginning of the packet window. (Since this
    39  	// is a raw OS memory mapping and not a Go object, it does not need to be
    40  	// represented as an unsafe.Pointer.) packet is immutable.
    41  	packet uintptr
    42  
    43  	// dataCap is the size of the datagram part of the packet window in bytes.
    44  	// dataCap is immutable.
    45  	dataCap uint32
    46  
    47  	// activeState is csClientActive if this is a client Endpoint and
    48  	// csServerActive if this is a server Endpoint.
    49  	activeState uint32
    50  
    51  	// inactiveState is csServerActive if this is a client Endpoint and
    52  	// csClientActive if this is a server Endpoint.
    53  	inactiveState uint32
    54  
    55  	// shutdown is non-zero if Endpoint.Shutdown() has been called, or if the
    56  	// Endpoint has acknowledged shutdown initiated by the peer.
    57  	shutdown atomicbitops.Uint32
    58  
    59  	ctrl endpointControlImpl
    60  }
    61  
    62  // EndpointSide indicates which side of a connection an Endpoint belongs to.
    63  type EndpointSide int
    64  
    65  const (
    66  	// ClientSide indicates that an Endpoint is a client (initially-active;
    67  	// first method call should be Connect).
    68  	ClientSide EndpointSide = iota
    69  
    70  	// ServerSide indicates that an Endpoint is a server (initially-inactive;
    71  	// first method call should be RecvFirst.)
    72  	ServerSide
    73  )
    74  
    75  // Init must be called on zero-value Endpoints before first use. If it
    76  // succeeds, ep.Destroy() must be called once the Endpoint is no longer in use.
    77  //
    78  // pwd represents the packet window used to exchange data with the peer
    79  // Endpoint. FD may differ between Endpoints if they are in different
    80  // processes, but must represent the same file. The packet window must
    81  // initially be filled with zero bytes.
    82  func (ep *Endpoint) Init(side EndpointSide, pwd PacketWindowDescriptor, opts ...EndpointOption) error {
    83  	switch side {
    84  	case ClientSide:
    85  		ep.activeState = csClientActive
    86  		ep.inactiveState = csServerActive
    87  	case ServerSide:
    88  		ep.activeState = csServerActive
    89  		ep.inactiveState = csClientActive
    90  	default:
    91  		return fmt.Errorf("invalid EndpointSide: %v", side)
    92  	}
    93  	if pwd.Length < pageSize {
    94  		return fmt.Errorf("packet window size (%d) less than minimum (%d)", pwd.Length, pageSize)
    95  	}
    96  	if pwd.Length > math.MaxUint32 {
    97  		return fmt.Errorf("packet window size (%d) exceeds maximum (%d)", pwd.Length, math.MaxUint32)
    98  	}
    99  	m, err := memutil.MapFile(0, uintptr(pwd.Length), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED, uintptr(pwd.FD), uintptr(pwd.Offset))
   100  	if err != nil {
   101  		return fmt.Errorf("failed to mmap packet window: %v", err)
   102  	}
   103  	ep.packet = m
   104  	ep.dataCap = uint32(pwd.Length) - uint32(PacketHeaderBytes)
   105  	if err := ep.ctrlInit(opts...); err != nil {
   106  		ep.unmapPacket()
   107  		return err
   108  	}
   109  	return nil
   110  }
   111  
   112  // NewEndpoint is a convenience function that returns an initialized Endpoint
   113  // allocated on the heap.
   114  func NewEndpoint(side EndpointSide, pwd PacketWindowDescriptor, opts ...EndpointOption) (*Endpoint, error) {
   115  	var ep Endpoint
   116  	if err := ep.Init(side, pwd, opts...); err != nil {
   117  		return nil, err
   118  	}
   119  	return &ep, nil
   120  }
   121  
   122  // An EndpointOption configures an Endpoint.
   123  type EndpointOption interface {
   124  	isEndpointOption()
   125  }
   126  
   127  // Destroy releases resources owned by ep. No other Endpoint methods may be
   128  // called after Destroy.
   129  func (ep *Endpoint) Destroy() {
   130  	ep.unmapPacket()
   131  }
   132  
   133  func (ep *Endpoint) unmapPacket() {
   134  	unix.RawSyscall(unix.SYS_MUNMAP, ep.packet, uintptr(ep.dataCap)+PacketHeaderBytes, 0)
   135  	ep.packet = 0
   136  }
   137  
   138  // Shutdown causes concurrent and future calls to ep.Connect(), ep.SendRecv(),
   139  // ep.RecvFirst(), and ep.SendLast(), as well as the same calls in the peer
   140  // Endpoint, to unblock and return ShutdownErrors. It does not wait for
   141  // concurrent calls to return. Successive calls to Shutdown have no effect.
   142  //
   143  // Shutdown is the only Endpoint method that may be called concurrently with
   144  // other methods on the same Endpoint.
   145  func (ep *Endpoint) Shutdown() {
   146  	if ep.shutdown.Swap(1) != 0 {
   147  		// ep.Shutdown() has previously been called.
   148  		return
   149  	}
   150  	ep.ctrlShutdown()
   151  }
   152  
   153  // isShutdownLocally returns true if ep.Shutdown() has been called.
   154  func (ep *Endpoint) isShutdownLocally() bool {
   155  	return ep.shutdown.Load() != 0
   156  }
   157  
   158  // ShutdownError is returned by most Endpoint methods after Endpoint.Shutdown()
   159  // has been called.
   160  type ShutdownError struct{}
   161  
   162  // Error implements error.Error.
   163  func (ShutdownError) Error() string {
   164  	return "flipcall connection shutdown"
   165  }
   166  
   167  // DataCap returns the maximum datagram size supported by ep. Equivalently,
   168  // DataCap returns len(ep.Data()).
   169  func (ep *Endpoint) DataCap() uint32 {
   170  	return ep.dataCap
   171  }
   172  
   173  // Connection state.
   174  const (
   175  	// The client is, by definition, initially active, so this must be 0.
   176  	csClientActive = 0
   177  	csServerActive = 1
   178  	csShutdown     = 2
   179  )
   180  
   181  // Connect blocks until the peer Endpoint has called Endpoint.RecvFirst().
   182  //
   183  // Preconditions:
   184  //   - ep is a client Endpoint.
   185  //   - ep.Connect(), ep.RecvFirst(), ep.SendRecv(), and ep.SendLast() have never
   186  //     been called.
   187  func (ep *Endpoint) Connect() error {
   188  	err := ep.ctrlConnect()
   189  	if err == nil {
   190  		raceBecomeActive()
   191  	}
   192  	return err
   193  }
   194  
   195  // RecvFirst blocks until the peer Endpoint calls Endpoint.SendRecv(), then
   196  // returns the datagram length specified by that call.
   197  //
   198  // Preconditions:
   199  //   - ep is a server Endpoint.
   200  //   - ep.SendRecv(), ep.RecvFirst(), and ep.SendLast() have never been called.
   201  func (ep *Endpoint) RecvFirst() (uint32, error) {
   202  	if err := ep.ctrlWaitFirst(); err != nil {
   203  		return 0, err
   204  	}
   205  	raceBecomeActive()
   206  	recvDataLen := ep.dataLen().Load()
   207  	if recvDataLen > ep.dataCap {
   208  		return 0, fmt.Errorf("received packet with invalid datagram length %d (maximum %d)", recvDataLen, ep.dataCap)
   209  	}
   210  	return recvDataLen, nil
   211  }
   212  
   213  // SendRecv transfers control to the peer Endpoint, causing its call to
   214  // Endpoint.SendRecv() or Endpoint.RecvFirst() to return with the given
   215  // datagram length, then blocks until the peer Endpoint calls
   216  // Endpoint.SendRecv() or Endpoint.SendLast().
   217  //
   218  // Preconditions:
   219  //   - dataLen <= ep.DataCap().
   220  //   - No previous call to ep.SendRecv() or ep.RecvFirst() has returned an error.
   221  //   - ep.SendLast() has never been called.
   222  //   - If ep is a client Endpoint, ep.Connect() has previously been called and
   223  //     returned nil.
   224  func (ep *Endpoint) SendRecv(dataLen uint32) (uint32, error) {
   225  	return ep.sendRecv(dataLen, false /* mayRetainP */)
   226  }
   227  
   228  // SendRecvFast is equivalent to SendRecv, but may prevent the caller's runtime
   229  // P from being released, in which case the calling goroutine continues to
   230  // count against GOMAXPROCS while waiting for the peer Endpoint to return
   231  // control to the caller.
   232  //
   233  // SendRecvFast is appropriate if the peer Endpoint is expected to consistently
   234  // return control in a short amount of time (less than ~10ms).
   235  //
   236  // Preconditions: As for SendRecv.
   237  func (ep *Endpoint) SendRecvFast(dataLen uint32) (uint32, error) {
   238  	return ep.sendRecv(dataLen, true /* mayRetainP */)
   239  }
   240  
   241  func (ep *Endpoint) sendRecv(dataLen uint32, mayRetainP bool) (uint32, error) {
   242  	if dataLen > ep.dataCap {
   243  		panic(fmt.Sprintf("attempting to send packet with datagram length %d (maximum %d)", dataLen, ep.dataCap))
   244  	}
   245  	// This store can safely be non-atomic: Under correct operation we should
   246  	// be the only thread writing ep.dataLen(), and ep.ctrlRoundTrip() will
   247  	// synchronize with the receiver. We will not read from ep.dataLen() until
   248  	// after ep.ctrlRoundTrip(), so if the peer is mutating it concurrently then
   249  	// they can only shoot themselves in the foot.
   250  	ep.dataLen().RacyStore(dataLen)
   251  	raceBecomeInactive()
   252  	if err := ep.ctrlRoundTrip(mayRetainP); err != nil {
   253  		return 0, err
   254  	}
   255  	raceBecomeActive()
   256  	recvDataLen := ep.dataLen().Load()
   257  	if recvDataLen > ep.dataCap {
   258  		return 0, fmt.Errorf("received packet with invalid datagram length %d (maximum %d)", recvDataLen, ep.dataCap)
   259  	}
   260  	return recvDataLen, nil
   261  }
   262  
   263  // SendLast causes the peer Endpoint's call to Endpoint.SendRecv() or
   264  // Endpoint.RecvFirst() to return with the given datagram length.
   265  //
   266  // Preconditions:
   267  //   - dataLen <= ep.DataCap().
   268  //   - No previous call to ep.SendRecv() or ep.RecvFirst() has returned an error.
   269  //   - ep.SendLast() has never been called.
   270  //   - If ep is a client Endpoint, ep.Connect() has previously been called and
   271  //     returned nil.
   272  func (ep *Endpoint) SendLast(dataLen uint32) error {
   273  	if dataLen > ep.dataCap {
   274  		panic(fmt.Sprintf("attempting to send packet with datagram length %d (maximum %d)", dataLen, ep.dataCap))
   275  	}
   276  	ep.dataLen().RacyStore(dataLen)
   277  	raceBecomeInactive()
   278  	if err := ep.ctrlWakeLast(); err != nil {
   279  		return err
   280  	}
   281  	return nil
   282  }