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 }