github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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  	"sync/atomic"
    23  
    24  	"golang.org/x/sys/unix"
    25  	"github.com/SagerNet/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. shutdown is
    57  	// accessed using atomic memory operations.
    58  	shutdown uint32
    59  
    60  	ctrl endpointControlImpl
    61  }
    62  
    63  // EndpointSide indicates which side of a connection an Endpoint belongs to.
    64  type EndpointSide int
    65  
    66  const (
    67  	// ClientSide indicates that an Endpoint is a client (initially-active;
    68  	// first method call should be Connect).
    69  	ClientSide EndpointSide = iota
    70  
    71  	// ServerSide indicates that an Endpoint is a server (initially-inactive;
    72  	// first method call should be RecvFirst.)
    73  	ServerSide
    74  )
    75  
    76  // Init must be called on zero-value Endpoints before first use. If it
    77  // succeeds, ep.Destroy() must be called once the Endpoint is no longer in use.
    78  //
    79  // pwd represents the packet window used to exchange data with the peer
    80  // Endpoint. FD may differ between Endpoints if they are in different
    81  // processes, but must represent the same file. The packet window must
    82  // initially be filled with zero bytes.
    83  func (ep *Endpoint) Init(side EndpointSide, pwd PacketWindowDescriptor, opts ...EndpointOption) error {
    84  	switch side {
    85  	case ClientSide:
    86  		ep.activeState = csClientActive
    87  		ep.inactiveState = csServerActive
    88  	case ServerSide:
    89  		ep.activeState = csServerActive
    90  		ep.inactiveState = csClientActive
    91  	default:
    92  		return fmt.Errorf("invalid EndpointSide: %v", side)
    93  	}
    94  	if pwd.Length < pageSize {
    95  		return fmt.Errorf("packet window size (%d) less than minimum (%d)", pwd.Length, pageSize)
    96  	}
    97  	if pwd.Length > math.MaxUint32 {
    98  		return fmt.Errorf("packet window size (%d) exceeds maximum (%d)", pwd.Length, math.MaxUint32)
    99  	}
   100  	m, err := memutil.MapFile(0, uintptr(pwd.Length), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED, uintptr(pwd.FD), uintptr(pwd.Offset))
   101  	if err != nil {
   102  		return fmt.Errorf("failed to mmap packet window: %v", err)
   103  	}
   104  	ep.packet = m
   105  	ep.dataCap = uint32(pwd.Length) - uint32(PacketHeaderBytes)
   106  	if err := ep.ctrlInit(opts...); err != nil {
   107  		ep.unmapPacket()
   108  		return err
   109  	}
   110  	return nil
   111  }
   112  
   113  // NewEndpoint is a convenience function that returns an initialized Endpoint
   114  // allocated on the heap.
   115  func NewEndpoint(side EndpointSide, pwd PacketWindowDescriptor, opts ...EndpointOption) (*Endpoint, error) {
   116  	var ep Endpoint
   117  	if err := ep.Init(side, pwd, opts...); err != nil {
   118  		return nil, err
   119  	}
   120  	return &ep, nil
   121  }
   122  
   123  // An EndpointOption configures an Endpoint.
   124  type EndpointOption interface {
   125  	isEndpointOption()
   126  }
   127  
   128  // Destroy releases resources owned by ep. No other Endpoint methods may be
   129  // called after Destroy.
   130  func (ep *Endpoint) Destroy() {
   131  	ep.unmapPacket()
   132  }
   133  
   134  func (ep *Endpoint) unmapPacket() {
   135  	unix.RawSyscall(unix.SYS_MUNMAP, ep.packet, uintptr(ep.dataCap)+PacketHeaderBytes, 0)
   136  	ep.packet = 0
   137  }
   138  
   139  // Shutdown causes concurrent and future calls to ep.Connect(), ep.SendRecv(),
   140  // ep.RecvFirst(), and ep.SendLast(), as well as the same calls in the peer
   141  // Endpoint, to unblock and return ShutdownErrors. It does not wait for
   142  // concurrent calls to return. Successive calls to Shutdown have no effect.
   143  //
   144  // Shutdown is the only Endpoint method that may be called concurrently with
   145  // other methods on the same Endpoint.
   146  func (ep *Endpoint) Shutdown() {
   147  	if atomic.SwapUint32(&ep.shutdown, 1) != 0 {
   148  		// ep.Shutdown() has previously been called.
   149  		return
   150  	}
   151  	ep.ctrlShutdown()
   152  }
   153  
   154  // isShutdownLocally returns true if ep.Shutdown() has been called.
   155  func (ep *Endpoint) isShutdownLocally() bool {
   156  	return atomic.LoadUint32(&ep.shutdown) != 0
   157  }
   158  
   159  // ShutdownError is returned by most Endpoint methods after Endpoint.Shutdown()
   160  // has been called.
   161  type ShutdownError struct{}
   162  
   163  // Error implements error.Error.
   164  func (ShutdownError) Error() string {
   165  	return "flipcall connection shutdown"
   166  }
   167  
   168  // DataCap returns the maximum datagram size supported by ep. Equivalently,
   169  // DataCap returns len(ep.Data()).
   170  func (ep *Endpoint) DataCap() uint32 {
   171  	return ep.dataCap
   172  }
   173  
   174  // Connection state.
   175  const (
   176  	// The client is, by definition, initially active, so this must be 0.
   177  	csClientActive = 0
   178  	csServerActive = 1
   179  	csShutdown     = 2
   180  )
   181  
   182  // Connect blocks until the peer Endpoint has called Endpoint.RecvFirst().
   183  //
   184  // Preconditions:
   185  // * ep is a client Endpoint.
   186  // * ep.Connect(), ep.RecvFirst(), ep.SendRecv(), and ep.SendLast() have never
   187  //   been called.
   188  func (ep *Endpoint) Connect() error {
   189  	err := ep.ctrlConnect()
   190  	if err == nil {
   191  		raceBecomeActive()
   192  	}
   193  	return err
   194  }
   195  
   196  // RecvFirst blocks until the peer Endpoint calls Endpoint.SendRecv(), then
   197  // returns the datagram length specified by that call.
   198  //
   199  // Preconditions:
   200  // * ep is a server Endpoint.
   201  // * ep.SendRecv(), ep.RecvFirst(), and ep.SendLast() have never been called.
   202  func (ep *Endpoint) RecvFirst() (uint32, error) {
   203  	if err := ep.ctrlWaitFirst(); err != nil {
   204  		return 0, err
   205  	}
   206  	raceBecomeActive()
   207  	recvDataLen := atomic.LoadUint32(ep.dataLen())
   208  	if recvDataLen > ep.dataCap {
   209  		return 0, fmt.Errorf("received packet with invalid datagram length %d (maximum %d)", recvDataLen, ep.dataCap)
   210  	}
   211  	return recvDataLen, nil
   212  }
   213  
   214  // SendRecv transfers control to the peer Endpoint, causing its call to
   215  // Endpoint.SendRecv() or Endpoint.RecvFirst() to return with the given
   216  // datagram length, then blocks until the peer Endpoint calls
   217  // Endpoint.SendRecv() or Endpoint.SendLast().
   218  //
   219  // Preconditions:
   220  // * dataLen <= ep.DataCap().
   221  // * No previous call to ep.SendRecv() or ep.RecvFirst() has returned an error.
   222  // * ep.SendLast() has never been called.
   223  // * If ep is a client Endpoint, ep.Connect() has previously been called and
   224  //   returned nil.
   225  func (ep *Endpoint) SendRecv(dataLen uint32) (uint32, error) {
   226  	if dataLen > ep.dataCap {
   227  		panic(fmt.Sprintf("attempting to send packet with datagram length %d (maximum %d)", dataLen, ep.dataCap))
   228  	}
   229  	// This store can safely be non-atomic: Under correct operation we should
   230  	// be the only thread writing ep.dataLen(), and ep.ctrlRoundTrip() will
   231  	// synchronize with the receiver. We will not read from ep.dataLen() until
   232  	// after ep.ctrlRoundTrip(), so if the peer is mutating it concurrently then
   233  	// they can only shoot themselves in the foot.
   234  	*ep.dataLen() = dataLen
   235  	raceBecomeInactive()
   236  	if err := ep.ctrlRoundTrip(); err != nil {
   237  		return 0, err
   238  	}
   239  	raceBecomeActive()
   240  	recvDataLen := atomic.LoadUint32(ep.dataLen())
   241  	if recvDataLen > ep.dataCap {
   242  		return 0, fmt.Errorf("received packet with invalid datagram length %d (maximum %d)", recvDataLen, ep.dataCap)
   243  	}
   244  	return recvDataLen, nil
   245  }
   246  
   247  // SendLast causes the peer Endpoint's call to Endpoint.SendRecv() or
   248  // Endpoint.RecvFirst() to return with the given datagram length.
   249  //
   250  // Preconditions:
   251  // * dataLen <= ep.DataCap().
   252  // * No previous call to ep.SendRecv() or ep.RecvFirst() has returned an error.
   253  // * ep.SendLast() has never been called.
   254  // * If ep is a client Endpoint, ep.Connect() has previously been called and
   255  //   returned nil.
   256  func (ep *Endpoint) SendLast(dataLen uint32) error {
   257  	if dataLen > ep.dataCap {
   258  		panic(fmt.Sprintf("attempting to send packet with datagram length %d (maximum %d)", dataLen, ep.dataCap))
   259  	}
   260  	*ep.dataLen() = dataLen
   261  	raceBecomeInactive()
   262  	if err := ep.ctrlWakeLast(); err != nil {
   263  		return err
   264  	}
   265  	return nil
   266  }