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:
Leadaxe 2026-08-04 15:50:08 +03:00
commit 2c4ae3b0a4
712 changed files with 185689 additions and 0 deletions

215
pkg/flipcall/ctrl_futex.go Normal file
View 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
View 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
}

View file

@ -0,0 +1,6 @@
// automatically generated by stateify.
//go:build linux
// +build linux
package flipcall

View file

@ -0,0 +1,6 @@
// automatically generated by stateify.
//go:build !false
// +build !false
package flipcall

View 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))
}
}

View file

@ -0,0 +1,3 @@
// automatically generated by stateify.
package flipcall

View 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
View 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
}

View 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
}