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 }