snapshot: sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
Содержимое пина, зафиксированного в go.mod sing-box-lx, одним коммитом без истории. Полная история SagerNet/gvisor — 1.45 ГБ и клонируется в каждой CI-джобе; наша дельта — одна вставка в одну функцию, история для неё не нужна. Module path github.com/sagernet/gvisor сохранён намеренно: на него опирается replace-директива суперпроекта. Патч поверх — отдельным коммитом, чтобы дельта читалась одним git show и переносилась на новый пин копированием. SPECS/TASKS/048-GVISOR_HANDSHAKE_NIL_CRASH
This commit is contained in:
commit
2c4ae3b0a4
712 changed files with 185689 additions and 0 deletions
215
pkg/flipcall/ctrl_futex.go
Normal file
215
pkg/flipcall/ctrl_futex.go
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !false
|
||||
// +build !false
|
||||
|
||||
package flipcall
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/log"
|
||||
)
|
||||
|
||||
type endpointControlImpl struct {
|
||||
state atomicbitops.Int32
|
||||
}
|
||||
|
||||
// Bits in endpointControlImpl.state.
|
||||
const (
|
||||
epsBlocked = 1 << iota
|
||||
epsShutdown
|
||||
)
|
||||
|
||||
func (ep *Endpoint) ctrlInit(opts ...EndpointOption) error {
|
||||
if len(opts) != 0 {
|
||||
return fmt.Errorf("unknown EndpointOption: %T", opts[0])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ep *Endpoint) ctrlConnect() error {
|
||||
if err := ep.enterFutexWait(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer ep.exitFutexWait()
|
||||
|
||||
// Write the connection request.
|
||||
w := ep.NewWriter()
|
||||
if err := json.NewEncoder(w).Encode(struct{}{}); err != nil {
|
||||
return fmt.Errorf("error writing connection request: %v", err)
|
||||
}
|
||||
*ep.dataLen() = atomicbitops.FromUint32(w.Len())
|
||||
|
||||
// Exchange control with the server.
|
||||
if err := ep.futexSetPeerActive(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ep.futexWakePeer(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ep.futexWaitUntilActive(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the connection response.
|
||||
var resp struct{}
|
||||
respLen := ep.dataLen().Load()
|
||||
if respLen > ep.dataCap {
|
||||
return fmt.Errorf("invalid connection response length %d (maximum %d)", respLen, ep.dataCap)
|
||||
}
|
||||
if err := json.NewDecoder(ep.NewReader(respLen)).Decode(&resp); err != nil {
|
||||
return fmt.Errorf("error reading connection response: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ep *Endpoint) ctrlWaitFirst() error {
|
||||
if err := ep.enterFutexWait(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer ep.exitFutexWait()
|
||||
|
||||
// Wait for the connection request.
|
||||
if err := ep.futexWaitUntilActive(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the connection request.
|
||||
reqLen := ep.dataLen().Load()
|
||||
if reqLen > ep.dataCap {
|
||||
return fmt.Errorf("invalid connection request length %d (maximum %d)", reqLen, ep.dataCap)
|
||||
}
|
||||
var req struct{}
|
||||
if err := json.NewDecoder(ep.NewReader(reqLen)).Decode(&req); err != nil {
|
||||
return fmt.Errorf("error reading connection request: %v", err)
|
||||
}
|
||||
|
||||
// Write the connection response.
|
||||
w := ep.NewWriter()
|
||||
if err := json.NewEncoder(w).Encode(struct{}{}); err != nil {
|
||||
return fmt.Errorf("error writing connection response: %v", err)
|
||||
}
|
||||
*ep.dataLen() = atomicbitops.FromUint32(w.Len())
|
||||
|
||||
// Return control to the client.
|
||||
raceBecomeInactive()
|
||||
if err := ep.futexSetPeerActive(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ep.futexWakePeer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the first non-connection message.
|
||||
return ep.futexWaitUntilActive()
|
||||
}
|
||||
|
||||
func (ep *Endpoint) ctrlRoundTrip(mayRetainP bool) error {
|
||||
if err := ep.enterFutexWait(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer ep.exitFutexWait()
|
||||
|
||||
if err := ep.futexSetPeerActive(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ep.futexWakePeer(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Since we don't know if the peer Endpoint is in the same process as this
|
||||
// one (in which case it may need our P to run), we allow our P to be
|
||||
// retaken regardless of mayRetainP.
|
||||
return ep.futexWaitUntilActive()
|
||||
}
|
||||
|
||||
func (ep *Endpoint) ctrlWakeLast() error {
|
||||
if err := ep.futexSetPeerActive(); err != nil {
|
||||
return err
|
||||
}
|
||||
return ep.futexWakePeer()
|
||||
}
|
||||
|
||||
func (ep *Endpoint) enterFutexWait() error {
|
||||
switch eps := ep.ctrl.state.Add(epsBlocked); eps {
|
||||
case epsBlocked:
|
||||
return nil
|
||||
case epsBlocked | epsShutdown:
|
||||
ep.ctrl.state.Add(-epsBlocked)
|
||||
return ShutdownError{}
|
||||
default:
|
||||
// Most likely due to ep.enterFutexWait() being called concurrently
|
||||
// from multiple goroutines.
|
||||
panic(fmt.Sprintf("invalid flipcall.Endpoint.ctrl.state before flipcall.Endpoint.enterFutexWait(): %v", eps-epsBlocked))
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *Endpoint) exitFutexWait() {
|
||||
switch eps := ep.ctrl.state.Add(-epsBlocked); eps {
|
||||
case 0:
|
||||
return
|
||||
case epsShutdown:
|
||||
// ep.ctrlShutdown() was called while we were blocked, so we are
|
||||
// responsible for indicating connection shutdown.
|
||||
ep.shutdownConn()
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid flipcall.Endpoint.ctrl.state after flipcall.Endpoint.exitFutexWait(): %v", eps+epsBlocked))
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *Endpoint) ctrlShutdown() {
|
||||
// Set epsShutdown to ensure that future calls to ep.enterFutexWait() fail.
|
||||
if ep.ctrl.state.Add(epsShutdown)&epsBlocked != 0 {
|
||||
// Wake the blocked thread. This must loop because it's possible that
|
||||
// FUTEX_WAKE occurs after the waiter sets epsBlocked, but before it
|
||||
// blocks in FUTEX_WAIT.
|
||||
for {
|
||||
// Wake MaxInt32 threads to prevent a broken or malicious peer from
|
||||
// swallowing our wakeup by FUTEX_WAITing from multiple threads.
|
||||
if err := ep.futexWakeConnState(math.MaxInt32); err != nil {
|
||||
log.Warningf("failed to FUTEX_WAKE Endpoints: %v", err)
|
||||
break
|
||||
}
|
||||
yieldThread()
|
||||
if ep.ctrl.state.Load()&epsBlocked == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There is no blocked thread, so we are responsible for indicating
|
||||
// connection shutdown.
|
||||
ep.shutdownConn()
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *Endpoint) shutdownConn() {
|
||||
switch cs := ep.connState().Swap(csShutdown); cs {
|
||||
case ep.activeState:
|
||||
if err := ep.futexWakeConnState(1); err != nil {
|
||||
log.Warningf("failed to FUTEX_WAKE peer Endpoint for shutdown: %v", err)
|
||||
}
|
||||
case ep.inactiveState:
|
||||
// The peer is currently active and will detect shutdown when it tries
|
||||
// to update the connection state.
|
||||
case csShutdown:
|
||||
// The peer also called Endpoint.Shutdown().
|
||||
default:
|
||||
log.Warningf("unexpected connection state before Endpoint.shutdownConn(): %v", cs)
|
||||
}
|
||||
}
|
||||
282
pkg/flipcall/flipcall.go
Normal file
282
pkg/flipcall/flipcall.go
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package flipcall implements a protocol providing Fast Local Interprocess
|
||||
// Procedure Calls between mutually-distrusting processes.
|
||||
package flipcall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/memutil"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// An Endpoint provides the ability to synchronously transfer data and control
|
||||
// to a connected peer Endpoint, which may be in another process.
|
||||
//
|
||||
// Since the Endpoint control transfer model is synchronous, at any given time
|
||||
// one Endpoint "has control" (designated the active Endpoint), and the other
|
||||
// is "waiting for control" (designated the inactive Endpoint). Users of the
|
||||
// flipcall package designate one Endpoint as the client, which is initially
|
||||
// active, and the other as the server, which is initially inactive. See
|
||||
// flipcall_example_test.go for usage.
|
||||
type Endpoint struct {
|
||||
// packet is a pointer to the beginning of the packet window. (Since this
|
||||
// is a raw OS memory mapping and not a Go object, it does not need to be
|
||||
// represented as an unsafe.Pointer.) packet is immutable.
|
||||
packet uintptr
|
||||
|
||||
// dataCap is the size of the datagram part of the packet window in bytes.
|
||||
// dataCap is immutable.
|
||||
dataCap uint32
|
||||
|
||||
// activeState is csClientActive if this is a client Endpoint and
|
||||
// csServerActive if this is a server Endpoint.
|
||||
activeState uint32
|
||||
|
||||
// inactiveState is csServerActive if this is a client Endpoint and
|
||||
// csClientActive if this is a server Endpoint.
|
||||
inactiveState uint32
|
||||
|
||||
// shutdown is non-zero if Endpoint.Shutdown() has been called, or if the
|
||||
// Endpoint has acknowledged shutdown initiated by the peer.
|
||||
shutdown atomicbitops.Uint32
|
||||
|
||||
ctrl endpointControlImpl
|
||||
}
|
||||
|
||||
// EndpointSide indicates which side of a connection an Endpoint belongs to.
|
||||
type EndpointSide int
|
||||
|
||||
const (
|
||||
// ClientSide indicates that an Endpoint is a client (initially-active;
|
||||
// first method call should be Connect).
|
||||
ClientSide EndpointSide = iota
|
||||
|
||||
// ServerSide indicates that an Endpoint is a server (initially-inactive;
|
||||
// first method call should be RecvFirst.)
|
||||
ServerSide
|
||||
)
|
||||
|
||||
// Init must be called on zero-value Endpoints before first use. If it
|
||||
// succeeds, ep.Destroy() must be called once the Endpoint is no longer in use.
|
||||
//
|
||||
// pwd represents the packet window used to exchange data with the peer
|
||||
// Endpoint. FD may differ between Endpoints if they are in different
|
||||
// processes, but must represent the same file. The packet window must
|
||||
// initially be filled with zero bytes.
|
||||
func (ep *Endpoint) Init(side EndpointSide, pwd PacketWindowDescriptor, opts ...EndpointOption) error {
|
||||
switch side {
|
||||
case ClientSide:
|
||||
ep.activeState = csClientActive
|
||||
ep.inactiveState = csServerActive
|
||||
case ServerSide:
|
||||
ep.activeState = csServerActive
|
||||
ep.inactiveState = csClientActive
|
||||
default:
|
||||
return fmt.Errorf("invalid EndpointSide: %v", side)
|
||||
}
|
||||
if pwd.Length < pageSize {
|
||||
return fmt.Errorf("packet window size (%d) less than minimum (%d)", pwd.Length, pageSize)
|
||||
}
|
||||
if pwd.Length > math.MaxUint32 {
|
||||
return fmt.Errorf("packet window size (%d) exceeds maximum (%d)", pwd.Length, math.MaxUint32)
|
||||
}
|
||||
m, err := memutil.MapFile(0, uintptr(pwd.Length), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED, uintptr(pwd.FD), uintptr(pwd.Offset))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mmap packet window: %v", err)
|
||||
}
|
||||
ep.packet = m
|
||||
ep.dataCap = uint32(pwd.Length) - uint32(PacketHeaderBytes)
|
||||
if err := ep.ctrlInit(opts...); err != nil {
|
||||
ep.unmapPacket()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewEndpoint is a convenience function that returns an initialized Endpoint
|
||||
// allocated on the heap.
|
||||
func NewEndpoint(side EndpointSide, pwd PacketWindowDescriptor, opts ...EndpointOption) (*Endpoint, error) {
|
||||
var ep Endpoint
|
||||
if err := ep.Init(side, pwd, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ep, nil
|
||||
}
|
||||
|
||||
// An EndpointOption configures an Endpoint.
|
||||
type EndpointOption interface {
|
||||
isEndpointOption()
|
||||
}
|
||||
|
||||
// Destroy releases resources owned by ep. No other Endpoint methods may be
|
||||
// called after Destroy.
|
||||
func (ep *Endpoint) Destroy() {
|
||||
ep.unmapPacket()
|
||||
}
|
||||
|
||||
func (ep *Endpoint) unmapPacket() {
|
||||
unix.RawSyscall(unix.SYS_MUNMAP, ep.packet, uintptr(ep.dataCap)+PacketHeaderBytes, 0)
|
||||
ep.packet = 0
|
||||
}
|
||||
|
||||
// Shutdown causes concurrent and future calls to ep.Connect(), ep.SendRecv(),
|
||||
// ep.RecvFirst(), and ep.SendLast(), as well as the same calls in the peer
|
||||
// Endpoint, to unblock and return ShutdownErrors. It does not wait for
|
||||
// concurrent calls to return. Successive calls to Shutdown have no effect.
|
||||
//
|
||||
// Shutdown is the only Endpoint method that may be called concurrently with
|
||||
// other methods on the same Endpoint.
|
||||
func (ep *Endpoint) Shutdown() {
|
||||
if ep.shutdown.Swap(1) != 0 {
|
||||
// ep.Shutdown() has previously been called.
|
||||
return
|
||||
}
|
||||
ep.ctrlShutdown()
|
||||
}
|
||||
|
||||
// isShutdownLocally returns true if ep.Shutdown() has been called.
|
||||
func (ep *Endpoint) isShutdownLocally() bool {
|
||||
return ep.shutdown.Load() != 0
|
||||
}
|
||||
|
||||
// ShutdownError is returned by most Endpoint methods after Endpoint.Shutdown()
|
||||
// has been called.
|
||||
type ShutdownError struct{}
|
||||
|
||||
// Error implements error.Error.
|
||||
func (ShutdownError) Error() string {
|
||||
return "flipcall connection shutdown"
|
||||
}
|
||||
|
||||
// DataCap returns the maximum datagram size supported by ep. Equivalently,
|
||||
// DataCap returns len(ep.Data()).
|
||||
func (ep *Endpoint) DataCap() uint32 {
|
||||
return ep.dataCap
|
||||
}
|
||||
|
||||
// Connection state.
|
||||
const (
|
||||
// The client is, by definition, initially active, so this must be 0.
|
||||
csClientActive = 0
|
||||
csServerActive = 1
|
||||
csShutdown = 2
|
||||
)
|
||||
|
||||
// Connect blocks until the peer Endpoint has called Endpoint.RecvFirst().
|
||||
//
|
||||
// Preconditions:
|
||||
// - ep is a client Endpoint.
|
||||
// - ep.Connect(), ep.RecvFirst(), ep.SendRecv(), and ep.SendLast() have never
|
||||
// been called.
|
||||
func (ep *Endpoint) Connect() error {
|
||||
err := ep.ctrlConnect()
|
||||
if err == nil {
|
||||
raceBecomeActive()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// RecvFirst blocks until the peer Endpoint calls Endpoint.SendRecv(), then
|
||||
// returns the datagram length specified by that call.
|
||||
//
|
||||
// Preconditions:
|
||||
// - ep is a server Endpoint.
|
||||
// - ep.SendRecv(), ep.RecvFirst(), and ep.SendLast() have never been called.
|
||||
func (ep *Endpoint) RecvFirst() (uint32, error) {
|
||||
if err := ep.ctrlWaitFirst(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
raceBecomeActive()
|
||||
recvDataLen := ep.dataLen().Load()
|
||||
if recvDataLen > ep.dataCap {
|
||||
return 0, fmt.Errorf("received packet with invalid datagram length %d (maximum %d)", recvDataLen, ep.dataCap)
|
||||
}
|
||||
return recvDataLen, nil
|
||||
}
|
||||
|
||||
// SendRecv transfers control to the peer Endpoint, causing its call to
|
||||
// Endpoint.SendRecv() or Endpoint.RecvFirst() to return with the given
|
||||
// datagram length, then blocks until the peer Endpoint calls
|
||||
// Endpoint.SendRecv() or Endpoint.SendLast().
|
||||
//
|
||||
// Preconditions:
|
||||
// - dataLen <= ep.DataCap().
|
||||
// - No previous call to ep.SendRecv() or ep.RecvFirst() has returned an error.
|
||||
// - ep.SendLast() has never been called.
|
||||
// - If ep is a client Endpoint, ep.Connect() has previously been called and
|
||||
// returned nil.
|
||||
func (ep *Endpoint) SendRecv(dataLen uint32) (uint32, error) {
|
||||
return ep.sendRecv(dataLen, false /* mayRetainP */)
|
||||
}
|
||||
|
||||
// SendRecvFast is equivalent to SendRecv, but may prevent the caller's runtime
|
||||
// P from being released, in which case the calling goroutine continues to
|
||||
// count against GOMAXPROCS while waiting for the peer Endpoint to return
|
||||
// control to the caller.
|
||||
//
|
||||
// SendRecvFast is appropriate if the peer Endpoint is expected to consistently
|
||||
// return control in a short amount of time (less than ~10ms).
|
||||
//
|
||||
// Preconditions: As for SendRecv.
|
||||
func (ep *Endpoint) SendRecvFast(dataLen uint32) (uint32, error) {
|
||||
return ep.sendRecv(dataLen, true /* mayRetainP */)
|
||||
}
|
||||
|
||||
func (ep *Endpoint) sendRecv(dataLen uint32, mayRetainP bool) (uint32, error) {
|
||||
if dataLen > ep.dataCap {
|
||||
panic(fmt.Sprintf("attempting to send packet with datagram length %d (maximum %d)", dataLen, ep.dataCap))
|
||||
}
|
||||
// This store can safely be non-atomic: Under correct operation we should
|
||||
// be the only thread writing ep.dataLen(), and ep.ctrlRoundTrip() will
|
||||
// synchronize with the receiver. We will not read from ep.dataLen() until
|
||||
// after ep.ctrlRoundTrip(), so if the peer is mutating it concurrently then
|
||||
// they can only shoot themselves in the foot.
|
||||
ep.dataLen().RacyStore(dataLen)
|
||||
raceBecomeInactive()
|
||||
if err := ep.ctrlRoundTrip(mayRetainP); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
raceBecomeActive()
|
||||
recvDataLen := ep.dataLen().Load()
|
||||
if recvDataLen > ep.dataCap {
|
||||
return 0, fmt.Errorf("received packet with invalid datagram length %d (maximum %d)", recvDataLen, ep.dataCap)
|
||||
}
|
||||
return recvDataLen, nil
|
||||
}
|
||||
|
||||
// SendLast causes the peer Endpoint's call to Endpoint.SendRecv() or
|
||||
// Endpoint.RecvFirst() to return with the given datagram length.
|
||||
//
|
||||
// Preconditions:
|
||||
// - dataLen <= ep.DataCap().
|
||||
// - No previous call to ep.SendRecv() or ep.RecvFirst() has returned an error.
|
||||
// - ep.SendLast() has never been called.
|
||||
// - If ep is a client Endpoint, ep.Connect() has previously been called and
|
||||
// returned nil.
|
||||
func (ep *Endpoint) SendLast(dataLen uint32) error {
|
||||
if dataLen > ep.dataCap {
|
||||
panic(fmt.Sprintf("attempting to send packet with datagram length %d (maximum %d)", dataLen, ep.dataCap))
|
||||
}
|
||||
ep.dataLen().RacyStore(dataLen)
|
||||
raceBecomeInactive()
|
||||
if err := ep.ctrlWakeLast(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
6
pkg/flipcall/flipcall_linux_state_autogen.go
Normal file
6
pkg/flipcall/flipcall_linux_state_autogen.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package flipcall
|
||||
6
pkg/flipcall/flipcall_state_autogen.go
Normal file
6
pkg/flipcall/flipcall_state_autogen.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
//go:build !false
|
||||
// +build !false
|
||||
|
||||
package flipcall
|
||||
87
pkg/flipcall/flipcall_unsafe.go
Normal file
87
pkg/flipcall/flipcall_unsafe.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package flipcall
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
)
|
||||
|
||||
// Packets consist of a 16-byte header followed by an arbitrarily-sized
|
||||
// datagram. The header consists of:
|
||||
//
|
||||
// - A 4-byte native-endian connection state.
|
||||
//
|
||||
// - A 4-byte native-endian datagram length in bytes.
|
||||
//
|
||||
// - 8 reserved bytes.
|
||||
const (
|
||||
// PacketHeaderBytes is the size of a flipcall packet header in bytes. The
|
||||
// maximum datagram size supported by a flipcall connection is equal to the
|
||||
// length of the packet window minus PacketHeaderBytes.
|
||||
//
|
||||
// PacketHeaderBytes is exported to support its use in constant
|
||||
// expressions. Non-constant expressions may prefer to use
|
||||
// PacketWindowLengthForDataCap().
|
||||
PacketHeaderBytes = 16
|
||||
)
|
||||
|
||||
func (ep *Endpoint) connState() *atomicbitops.Uint32 {
|
||||
return (*atomicbitops.Uint32)(unsafe.Pointer(ep.packet))
|
||||
}
|
||||
|
||||
func (ep *Endpoint) dataLen() *atomicbitops.Uint32 {
|
||||
return (*atomicbitops.Uint32)(unsafe.Pointer(ep.packet + 4))
|
||||
}
|
||||
|
||||
// Data returns the datagram part of ep's packet window as a byte slice.
|
||||
//
|
||||
// Note that the packet window is shared with the potentially-untrusted peer
|
||||
// Endpoint, which may concurrently mutate the contents of the packet window.
|
||||
// Thus:
|
||||
//
|
||||
// - Readers must not assume that two reads of the same byte in Data() will
|
||||
// return the same result. In other words, readers should read any given byte
|
||||
// in Data() at most once.
|
||||
//
|
||||
// - Writers must not assume that they will read back the same data that they
|
||||
// have written. In other words, writers should avoid reading from Data() at
|
||||
// all.
|
||||
func (ep *Endpoint) Data() (bs []byte) {
|
||||
bshdr := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
|
||||
bshdr.Data = ep.packet + PacketHeaderBytes
|
||||
bshdr.Len = int(ep.dataCap)
|
||||
bshdr.Cap = int(ep.dataCap)
|
||||
return
|
||||
}
|
||||
|
||||
// ioSync is a dummy variable used to indicate synchronization to the Go race
|
||||
// detector. Compare syscall.ioSync.
|
||||
var ioSync int64
|
||||
|
||||
func raceBecomeActive() {
|
||||
if sync.RaceEnabled {
|
||||
sync.RaceAcquire(unsafe.Pointer(&ioSync))
|
||||
}
|
||||
}
|
||||
|
||||
func raceBecomeInactive() {
|
||||
if sync.RaceEnabled {
|
||||
sync.RaceReleaseMerge(unsafe.Pointer(&ioSync))
|
||||
}
|
||||
}
|
||||
3
pkg/flipcall/flipcall_unsafe_state_autogen.go
Normal file
3
pkg/flipcall/flipcall_unsafe_state_autogen.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package flipcall
|
||||
88
pkg/flipcall/futex_linux.go
Normal file
88
pkg/flipcall/futex_linux.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package flipcall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/abi/linux"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (ep *Endpoint) futexSetPeerActive() error {
|
||||
if ep.connState().CompareAndSwap(ep.activeState, ep.inactiveState) {
|
||||
return nil
|
||||
}
|
||||
switch cs := ep.connState().Load(); cs {
|
||||
case csShutdown:
|
||||
return ShutdownError{}
|
||||
default:
|
||||
return fmt.Errorf("unexpected connection state before FUTEX_WAKE: %v", cs)
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *Endpoint) futexWakePeer() error {
|
||||
if err := ep.futexWakeConnState(1); err != nil {
|
||||
return fmt.Errorf("failed to FUTEX_WAKE peer Endpoint: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ep *Endpoint) futexWaitUntilActive() error {
|
||||
for {
|
||||
switch cs := ep.connState().Load(); cs {
|
||||
case ep.activeState:
|
||||
return nil
|
||||
case ep.inactiveState:
|
||||
if ep.isShutdownLocally() {
|
||||
return ShutdownError{}
|
||||
}
|
||||
if err := ep.futexWaitConnState(ep.inactiveState); err != nil {
|
||||
return fmt.Errorf("failed to FUTEX_WAIT for peer Endpoint: %v", err)
|
||||
}
|
||||
continue
|
||||
case csShutdown:
|
||||
return ShutdownError{}
|
||||
default:
|
||||
return fmt.Errorf("unexpected connection state before FUTEX_WAIT: %v", cs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *Endpoint) futexWakeConnState(numThreads int32) error {
|
||||
if _, _, e := unix.RawSyscall(unix.SYS_FUTEX, ep.packet, linux.FUTEX_WAKE, uintptr(numThreads)); e != 0 {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ep *Endpoint) futexWaitConnState(curState uint32) error {
|
||||
_, _, e := unix.Syscall6(unix.SYS_FUTEX, ep.packet, linux.FUTEX_WAIT, uintptr(curState), 0, 0, 0)
|
||||
if e != 0 && e != unix.EAGAIN && e != unix.EINTR {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func yieldThread() {
|
||||
unix.Syscall(unix.SYS_SCHED_YIELD, 0, 0, 0)
|
||||
// The thread we're trying to yield to may be waiting for a Go runtime P.
|
||||
// runtime.Gosched() will hand off ours if necessary.
|
||||
runtime.Gosched()
|
||||
}
|
||||
113
pkg/flipcall/io.go
Normal file
113
pkg/flipcall/io.go
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package flipcall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// DatagramReader implements io.Reader by reading a datagram from an Endpoint's
|
||||
// packet window. Its use is optional; users that can use Endpoint.Data() more
|
||||
// efficiently are advised to do so.
|
||||
type DatagramReader struct {
|
||||
ep *Endpoint
|
||||
off uint32
|
||||
end uint32
|
||||
}
|
||||
|
||||
// Init must be called on zero-value DatagramReaders before first use.
|
||||
//
|
||||
// Preconditions: dataLen is 0, or was returned by a previous call to
|
||||
// ep.RecvFirst() or ep.SendRecv().
|
||||
func (r *DatagramReader) Init(ep *Endpoint, dataLen uint32) {
|
||||
r.ep = ep
|
||||
r.Reset(dataLen)
|
||||
}
|
||||
|
||||
// Reset causes r to begin reading a new datagram of the given length from the
|
||||
// associated Endpoint.
|
||||
//
|
||||
// Preconditions: dataLen is 0, or was returned by a previous call to the
|
||||
// associated Endpoint's RecvFirst() or SendRecv() methods.
|
||||
func (r *DatagramReader) Reset(dataLen uint32) {
|
||||
if dataLen > r.ep.dataCap {
|
||||
panic(fmt.Sprintf("invalid dataLen (%d) > ep.dataCap (%d)", dataLen, r.ep.dataCap))
|
||||
}
|
||||
r.off = 0
|
||||
r.end = dataLen
|
||||
}
|
||||
|
||||
// NewReader is a convenience function that returns an initialized
|
||||
// DatagramReader allocated on the heap.
|
||||
//
|
||||
// Preconditions: dataLen was returned by a previous call to ep.RecvFirst() or
|
||||
// ep.SendRecv().
|
||||
func (ep *Endpoint) NewReader(dataLen uint32) *DatagramReader {
|
||||
r := &DatagramReader{}
|
||||
r.Init(ep, dataLen)
|
||||
return r
|
||||
}
|
||||
|
||||
// Read implements io.Reader.Read.
|
||||
func (r *DatagramReader) Read(dst []byte) (int, error) {
|
||||
n := copy(dst, r.ep.Data()[r.off:r.end])
|
||||
r.off += uint32(n)
|
||||
if r.off == r.end {
|
||||
return n, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// DatagramWriter implements io.Writer by writing a datagram to an Endpoint's
|
||||
// packet window. Its use is optional; users that can use Endpoint.Data() more
|
||||
// efficiently are advised to do so.
|
||||
type DatagramWriter struct {
|
||||
ep *Endpoint
|
||||
off uint32
|
||||
}
|
||||
|
||||
// Init must be called on zero-value DatagramWriters before first use.
|
||||
func (w *DatagramWriter) Init(ep *Endpoint) {
|
||||
w.ep = ep
|
||||
}
|
||||
|
||||
// Reset causes w to begin writing a new datagram to the associated Endpoint.
|
||||
func (w *DatagramWriter) Reset() {
|
||||
w.off = 0
|
||||
}
|
||||
|
||||
// NewWriter is a convenience function that returns an initialized
|
||||
// DatagramWriter allocated on the heap.
|
||||
func (ep *Endpoint) NewWriter() *DatagramWriter {
|
||||
w := &DatagramWriter{}
|
||||
w.Init(ep)
|
||||
return w
|
||||
}
|
||||
|
||||
// Write implements io.Writer.Write.
|
||||
func (w *DatagramWriter) Write(src []byte) (int, error) {
|
||||
n := copy(w.ep.Data()[w.off:w.ep.dataCap], src)
|
||||
w.off += uint32(n)
|
||||
if n != len(src) {
|
||||
return n, fmt.Errorf("datagram would exceed maximum size of %d bytes", w.ep.dataCap)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Len returns the length of the written datagram.
|
||||
func (w *DatagramWriter) Len() uint32 {
|
||||
return w.off
|
||||
}
|
||||
166
pkg/flipcall/packet_window.go
Normal file
166
pkg/flipcall/packet_window.go
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright 2019 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package flipcall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/abi/linux"
|
||||
"github.com/sagernet/gvisor/pkg/memutil"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
pageSize = os.Getpagesize()
|
||||
pageMask = pageSize - 1
|
||||
)
|
||||
|
||||
func init() {
|
||||
if bits.OnesCount(uint(pageSize)) != 1 {
|
||||
// This is depended on by roundUpToPage().
|
||||
panic(fmt.Sprintf("system page size (%d) is not a power of 2", pageSize))
|
||||
}
|
||||
if uintptr(pageSize) < PacketHeaderBytes {
|
||||
// This is required since Endpoint.Init() imposes a minimum packet
|
||||
// window size of 1 page.
|
||||
panic(fmt.Sprintf("system page size (%d) is less than packet header size (%d)", pageSize, PacketHeaderBytes))
|
||||
}
|
||||
}
|
||||
|
||||
// PacketWindowDescriptor represents a packet window, a range of pages in a
|
||||
// shared memory file that is used to exchange packets between partner
|
||||
// Endpoints.
|
||||
type PacketWindowDescriptor struct {
|
||||
// FD is the file descriptor representing the shared memory file.
|
||||
FD int
|
||||
|
||||
// Offset is the offset into the shared memory file at which the packet
|
||||
// window begins.
|
||||
Offset int64
|
||||
|
||||
// Length is the size of the packet window in bytes.
|
||||
Length int
|
||||
}
|
||||
|
||||
// PacketWindowLengthForDataCap returns the minimum packet window size required
|
||||
// to accommodate datagrams of the given size in bytes.
|
||||
func PacketWindowLengthForDataCap(dataCap uint32) int {
|
||||
return roundUpToPage(int(dataCap) + int(PacketHeaderBytes))
|
||||
}
|
||||
|
||||
func roundUpToPage(x int) int {
|
||||
return (x + pageMask) &^ pageMask
|
||||
}
|
||||
|
||||
// A PacketWindowAllocator owns a shared memory file, and allocates packet
|
||||
// windows from it.
|
||||
type PacketWindowAllocator struct {
|
||||
fd int
|
||||
nextAlloc int64
|
||||
fileSize int64
|
||||
}
|
||||
|
||||
// Init must be called on zero-value PacketWindowAllocators before first use.
|
||||
// If it succeeds, Destroy() must be called once the PacketWindowAllocator is
|
||||
// no longer in use.
|
||||
func (pwa *PacketWindowAllocator) Init() error {
|
||||
fd, err := memutil.CreateMemFD("flipcall_packet_windows", linux.MFD_CLOEXEC|linux.MFD_ALLOW_SEALING)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create memfd: %v", err)
|
||||
}
|
||||
// Apply F_SEAL_SHRINK to prevent either party from causing SIGBUS in the
|
||||
// other by truncating the file, and F_SEAL_SEAL to prevent either party
|
||||
// from applying F_SEAL_GROW or F_SEAL_WRITE.
|
||||
if _, _, e := unix.RawSyscall(unix.SYS_FCNTL, uintptr(fd), linux.F_ADD_SEALS, linux.F_SEAL_SHRINK|linux.F_SEAL_SEAL); e != 0 {
|
||||
unix.Close(fd)
|
||||
return fmt.Errorf("failed to apply memfd seals: %v", e)
|
||||
}
|
||||
pwa.fd = fd
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewPacketWindowAllocator is a convenience function that returns an
|
||||
// initialized PacketWindowAllocator allocated on the heap.
|
||||
func NewPacketWindowAllocator() (*PacketWindowAllocator, error) {
|
||||
var pwa PacketWindowAllocator
|
||||
if err := pwa.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pwa, nil
|
||||
}
|
||||
|
||||
// Destroy releases resources owned by pwa. This invalidates file descriptors
|
||||
// previously returned by pwa.FD() and pwd.Allocate().
|
||||
func (pwa *PacketWindowAllocator) Destroy() {
|
||||
unix.Close(pwa.fd)
|
||||
}
|
||||
|
||||
// FD represents the file descriptor of the shared memory file backing pwa.
|
||||
func (pwa *PacketWindowAllocator) FD() int {
|
||||
return pwa.fd
|
||||
}
|
||||
|
||||
// Allocate allocates a new packet window of at least the given size and
|
||||
// returns a PacketWindowDescriptor representing it.
|
||||
//
|
||||
// Preconditions: size > 0.
|
||||
func (pwa *PacketWindowAllocator) Allocate(size int) (PacketWindowDescriptor, error) {
|
||||
if size <= 0 {
|
||||
return PacketWindowDescriptor{}, fmt.Errorf("invalid size: %d", size)
|
||||
}
|
||||
// Page-align size to ensure that pwa.nextAlloc remains page-aligned.
|
||||
size = roundUpToPage(size)
|
||||
if size <= 0 {
|
||||
return PacketWindowDescriptor{}, fmt.Errorf("size %d overflows after rounding up to page size", size)
|
||||
}
|
||||
end := pwa.nextAlloc + int64(size) // overflow checked by ensureFileSize
|
||||
if err := pwa.ensureFileSize(end); err != nil {
|
||||
return PacketWindowDescriptor{}, err
|
||||
}
|
||||
start := pwa.nextAlloc
|
||||
pwa.nextAlloc = end
|
||||
return PacketWindowDescriptor{
|
||||
FD: pwa.FD(),
|
||||
Offset: start,
|
||||
Length: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (pwa *PacketWindowAllocator) ensureFileSize(min int64) error {
|
||||
if min <= 0 {
|
||||
return fmt.Errorf("file size would overflow")
|
||||
}
|
||||
if pwa.fileSize >= min {
|
||||
return nil
|
||||
}
|
||||
newSize := 2 * pwa.fileSize
|
||||
if newSize == 0 {
|
||||
newSize = int64(pageSize)
|
||||
}
|
||||
for newSize < min {
|
||||
newNewSize := newSize * 2
|
||||
if newNewSize <= 0 {
|
||||
return fmt.Errorf("file size would overflow")
|
||||
}
|
||||
newSize = newNewSize
|
||||
}
|
||||
if err := unix.Ftruncate(pwa.FD(), newSize); err != nil {
|
||||
return fmt.Errorf("ftruncate failed: %v", err)
|
||||
}
|
||||
pwa.fileSize = newSize
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue