github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/flipcall/ctrl_futex.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  //go:build go1.1
    16  // +build go1.1
    17  
    18  package flipcall
    19  
    20  import (
    21  	"encoding/json"
    22  	"fmt"
    23  	"math"
    24  
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/atomicbitops"
    26  	"github.com/nicocha30/gvisor-ligolo/pkg/log"
    27  )
    28  
    29  type endpointControlImpl struct {
    30  	state atomicbitops.Int32
    31  }
    32  
    33  // Bits in endpointControlImpl.state.
    34  const (
    35  	epsBlocked = 1 << iota
    36  	epsShutdown
    37  )
    38  
    39  func (ep *Endpoint) ctrlInit(opts ...EndpointOption) error {
    40  	if len(opts) != 0 {
    41  		return fmt.Errorf("unknown EndpointOption: %T", opts[0])
    42  	}
    43  	return nil
    44  }
    45  
    46  func (ep *Endpoint) ctrlConnect() error {
    47  	if err := ep.enterFutexWait(); err != nil {
    48  		return err
    49  	}
    50  	defer ep.exitFutexWait()
    51  
    52  	// Write the connection request.
    53  	w := ep.NewWriter()
    54  	if err := json.NewEncoder(w).Encode(struct{}{}); err != nil {
    55  		return fmt.Errorf("error writing connection request: %v", err)
    56  	}
    57  	*ep.dataLen() = atomicbitops.FromUint32(w.Len())
    58  
    59  	// Exchange control with the server.
    60  	if err := ep.futexSetPeerActive(); err != nil {
    61  		return err
    62  	}
    63  	if err := ep.futexWakePeer(); err != nil {
    64  		return err
    65  	}
    66  	if err := ep.futexWaitUntilActive(); err != nil {
    67  		return err
    68  	}
    69  
    70  	// Read the connection response.
    71  	var resp struct{}
    72  	respLen := ep.dataLen().Load()
    73  	if respLen > ep.dataCap {
    74  		return fmt.Errorf("invalid connection response length %d (maximum %d)", respLen, ep.dataCap)
    75  	}
    76  	if err := json.NewDecoder(ep.NewReader(respLen)).Decode(&resp); err != nil {
    77  		return fmt.Errorf("error reading connection response: %v", err)
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  func (ep *Endpoint) ctrlWaitFirst() error {
    84  	if err := ep.enterFutexWait(); err != nil {
    85  		return err
    86  	}
    87  	defer ep.exitFutexWait()
    88  
    89  	// Wait for the connection request.
    90  	if err := ep.futexWaitUntilActive(); err != nil {
    91  		return err
    92  	}
    93  
    94  	// Read the connection request.
    95  	reqLen := ep.dataLen().Load()
    96  	if reqLen > ep.dataCap {
    97  		return fmt.Errorf("invalid connection request length %d (maximum %d)", reqLen, ep.dataCap)
    98  	}
    99  	var req struct{}
   100  	if err := json.NewDecoder(ep.NewReader(reqLen)).Decode(&req); err != nil {
   101  		return fmt.Errorf("error reading connection request: %v", err)
   102  	}
   103  
   104  	// Write the connection response.
   105  	w := ep.NewWriter()
   106  	if err := json.NewEncoder(w).Encode(struct{}{}); err != nil {
   107  		return fmt.Errorf("error writing connection response: %v", err)
   108  	}
   109  	*ep.dataLen() = atomicbitops.FromUint32(w.Len())
   110  
   111  	// Return control to the client.
   112  	raceBecomeInactive()
   113  	if err := ep.futexSetPeerActive(); err != nil {
   114  		return err
   115  	}
   116  	if err := ep.futexWakePeer(); err != nil {
   117  		return err
   118  	}
   119  
   120  	// Wait for the first non-connection message.
   121  	return ep.futexWaitUntilActive()
   122  }
   123  
   124  func (ep *Endpoint) ctrlRoundTrip(mayRetainP bool) error {
   125  	if err := ep.enterFutexWait(); err != nil {
   126  		return err
   127  	}
   128  	defer ep.exitFutexWait()
   129  
   130  	if err := ep.futexSetPeerActive(); err != nil {
   131  		return err
   132  	}
   133  	if err := ep.futexWakePeer(); err != nil {
   134  		return err
   135  	}
   136  	// Since we don't know if the peer Endpoint is in the same process as this
   137  	// one (in which case it may need our P to run), we allow our P to be
   138  	// retaken regardless of mayRetainP.
   139  	return ep.futexWaitUntilActive()
   140  }
   141  
   142  func (ep *Endpoint) ctrlWakeLast() error {
   143  	if err := ep.futexSetPeerActive(); err != nil {
   144  		return err
   145  	}
   146  	return ep.futexWakePeer()
   147  }
   148  
   149  func (ep *Endpoint) enterFutexWait() error {
   150  	switch eps := ep.ctrl.state.Add(epsBlocked); eps {
   151  	case epsBlocked:
   152  		return nil
   153  	case epsBlocked | epsShutdown:
   154  		ep.ctrl.state.Add(-epsBlocked)
   155  		return ShutdownError{}
   156  	default:
   157  		// Most likely due to ep.enterFutexWait() being called concurrently
   158  		// from multiple goroutines.
   159  		panic(fmt.Sprintf("invalid flipcall.Endpoint.ctrl.state before flipcall.Endpoint.enterFutexWait(): %v", eps-epsBlocked))
   160  	}
   161  }
   162  
   163  func (ep *Endpoint) exitFutexWait() {
   164  	switch eps := ep.ctrl.state.Add(-epsBlocked); eps {
   165  	case 0:
   166  		return
   167  	case epsShutdown:
   168  		// ep.ctrlShutdown() was called while we were blocked, so we are
   169  		// responsible for indicating connection shutdown.
   170  		ep.shutdownConn()
   171  	default:
   172  		panic(fmt.Sprintf("invalid flipcall.Endpoint.ctrl.state after flipcall.Endpoint.exitFutexWait(): %v", eps+epsBlocked))
   173  	}
   174  }
   175  
   176  func (ep *Endpoint) ctrlShutdown() {
   177  	// Set epsShutdown to ensure that future calls to ep.enterFutexWait() fail.
   178  	if ep.ctrl.state.Add(epsShutdown)&epsBlocked != 0 {
   179  		// Wake the blocked thread. This must loop because it's possible that
   180  		// FUTEX_WAKE occurs after the waiter sets epsBlocked, but before it
   181  		// blocks in FUTEX_WAIT.
   182  		for {
   183  			// Wake MaxInt32 threads to prevent a broken or malicious peer from
   184  			// swallowing our wakeup by FUTEX_WAITing from multiple threads.
   185  			if err := ep.futexWakeConnState(math.MaxInt32); err != nil {
   186  				log.Warningf("failed to FUTEX_WAKE Endpoints: %v", err)
   187  				break
   188  			}
   189  			yieldThread()
   190  			if ep.ctrl.state.Load()&epsBlocked == 0 {
   191  				break
   192  			}
   193  		}
   194  	} else {
   195  		// There is no blocked thread, so we are responsible for indicating
   196  		// connection shutdown.
   197  		ep.shutdownConn()
   198  	}
   199  }
   200  
   201  func (ep *Endpoint) shutdownConn() {
   202  	switch cs := ep.connState().Swap(csShutdown); cs {
   203  	case ep.activeState:
   204  		if err := ep.futexWakeConnState(1); err != nil {
   205  			log.Warningf("failed to FUTEX_WAKE peer Endpoint for shutdown: %v", err)
   206  		}
   207  	case ep.inactiveState:
   208  		// The peer is currently active and will detect shutdown when it tries
   209  		// to update the connection state.
   210  	case csShutdown:
   211  		// The peer also called Endpoint.Shutdown().
   212  	default:
   213  		log.Warningf("unexpected connection state before Endpoint.shutdownConn(): %v", cs)
   214  	}
   215  }