gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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 "gvisor.dev/gvisor/pkg/atomicbitops" 25 "gvisor.dev/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 }