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

View file

@ -0,0 +1,64 @@
package fifo
import (
"reflect"
"github.com/sagernet/gvisor/pkg/sync"
"github.com/sagernet/gvisor/pkg/sync/locking"
)
// Mutex is sync.Mutex with the correctness validator.
type queueDispatcherMutex struct {
mu sync.Mutex
}
var queueDispatcherprefixIndex *locking.MutexClass
// lockNames is a list of user-friendly lock names.
// Populated in init.
var queueDispatcherlockNames []string
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
// referring to an index within lockNames.
// Values are specified using the "consts" field of go_template_instance.
type queueDispatcherlockNameIndex int
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
// LOCK_NAME_INDEX_CONSTANTS
const ()
// Lock locks m.
// +checklocksignore
func (m *queueDispatcherMutex) Lock() {
locking.AddGLock(queueDispatcherprefixIndex, -1)
m.mu.Lock()
}
// NestedLock locks m knowing that another lock of the same type is held.
// +checklocksignore
func (m *queueDispatcherMutex) NestedLock(i queueDispatcherlockNameIndex) {
locking.AddGLock(queueDispatcherprefixIndex, int(i))
m.mu.Lock()
}
// Unlock unlocks m.
// +checklocksignore
func (m *queueDispatcherMutex) Unlock() {
locking.DelGLock(queueDispatcherprefixIndex, -1)
m.mu.Unlock()
}
// NestedUnlock unlocks m knowing that another lock of the same type is held.
// +checklocksignore
func (m *queueDispatcherMutex) NestedUnlock(i queueDispatcherlockNameIndex) {
locking.DelGLock(queueDispatcherprefixIndex, int(i))
m.mu.Unlock()
}
// DO NOT REMOVE: The following function is automatically replaced.
func queueDispatcherinitLockNames() {}
func init() {
queueDispatcherinitLockNames()
queueDispatcherprefixIndex = locking.NewMutexClass(reflect.TypeOf(queueDispatcherMutex{}), queueDispatcherlockNames)
}

View file

@ -0,0 +1,158 @@
// Copyright 2020 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 fifo provides the implementation of FIFO queuing discipline that
// queues all outbound packets and asynchronously dispatches them to the
// lower link endpoint in the order that they were queued.
package fifo
import (
"github.com/sagernet/gvisor/pkg/atomicbitops"
"github.com/sagernet/gvisor/pkg/sleep"
"github.com/sagernet/gvisor/pkg/sync"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
)
var _ stack.QueueingDiscipline = (*discipline)(nil)
const (
// BatchSize is the number of packets to write in each syscall. It is 47
// because when GVisorGSO is in use then a single 65KB TCP segment can get
// split into 46 segments of 1420 bytes and a single 216 byte segment.
BatchSize = 47
qDiscClosed = 1
)
// discipline represents a QueueingDiscipline which implements a FIFO queue for
// all outgoing packets. discipline can have 1 or more underlying
// queueDispatchers. All outgoing packets are consistently hashed to a single
// underlying queue using the PacketBuffer.Hash if set, otherwise all packets
// are queued to the first queue to avoid reordering in case of missing hash.
//
// +stateify savable
type discipline struct {
wg sync.WaitGroup `state:"nosave"`
dispatchers []queueDispatcher
closed atomicbitops.Int32
}
// queueDispatcher is responsible for dispatching all outbound packets in its
// queue. It will also smartly batch packets when possible and write them
// through the lower LinkWriter.
//
// +stateify savable
type queueDispatcher struct {
lower stack.LinkWriter
mu queueDispatcherMutex `state:"nosave"`
// +checklocks:mu
queue packetBufferCircularList
newPacketWaker sleep.Waker `state:"nosave"`
closeWaker sleep.Waker `state:"nosave"`
}
// New creates a new fifo queuing discipline with the n queues with maximum
// capacity of queueLen.
//
// +checklocksignore: we don't have to hold locks during initialization.
func New(lower stack.LinkWriter, n int, queueLen int) stack.QueueingDiscipline {
d := &discipline{
dispatchers: make([]queueDispatcher, n),
}
// Create the required dispatchers
for i := range d.dispatchers {
qd := &d.dispatchers[i]
qd.lower = lower
qd.queue.init(queueLen)
d.wg.Add(1)
go func() {
defer d.wg.Done()
qd.dispatchLoop()
}()
}
return d
}
func (qd *queueDispatcher) dispatchLoop() {
s := sleep.Sleeper{}
s.AddWaker(&qd.newPacketWaker)
s.AddWaker(&qd.closeWaker)
defer s.Done()
var batch stack.PacketBufferList
for {
switch w := s.Fetch(true); w {
case &qd.newPacketWaker:
case &qd.closeWaker:
qd.mu.Lock()
for p := qd.queue.removeFront(); p != nil; p = qd.queue.removeFront() {
p.DecRef()
}
qd.queue.decRef()
qd.mu.Unlock()
return
default:
panic("unknown waker")
}
qd.mu.Lock()
for pkt := qd.queue.removeFront(); pkt != nil; pkt = qd.queue.removeFront() {
batch.PushBack(pkt)
if batch.Len() < BatchSize && !qd.queue.isEmpty() {
continue
}
qd.mu.Unlock()
_, _ = qd.lower.WritePackets(batch)
batch.Reset()
qd.mu.Lock()
}
qd.mu.Unlock()
}
}
// WritePacket implements stack.QueueingDiscipline.WritePacket.
//
// The packet must have the following fields populated:
// - pkt.EgressRoute
// - pkt.GSOOptions
// - pkt.NetworkProtocolNumber
func (d *discipline) WritePacket(pkt *stack.PacketBuffer) tcpip.Error {
if d.closed.Load() == qDiscClosed {
return &tcpip.ErrClosedForSend{}
}
qd := &d.dispatchers[int(pkt.Hash)%len(d.dispatchers)]
qd.mu.Lock()
haveSpace := qd.queue.hasSpace()
if haveSpace {
qd.queue.pushBack(pkt.IncRef())
}
qd.mu.Unlock()
if !haveSpace {
return &tcpip.ErrNoBufferSpace{}
}
qd.newPacketWaker.Assert()
return nil
}
func (d *discipline) Close() {
d.closed.Store(qDiscClosed)
for i := range d.dispatchers {
d.dispatchers[i].closeWaker.Assert()
}
d.wg.Wait()
}

View file

@ -0,0 +1,102 @@
// automatically generated by stateify.
package fifo
import (
"context"
"github.com/sagernet/gvisor/pkg/state"
)
func (d *discipline) StateTypeName() string {
return "pkg/tcpip/link/qdisc/fifo.discipline"
}
func (d *discipline) StateFields() []string {
return []string{
"dispatchers",
"closed",
}
}
func (d *discipline) beforeSave() {}
// +checklocksignore
func (d *discipline) StateSave(stateSinkObject state.Sink) {
d.beforeSave()
stateSinkObject.Save(0, &d.dispatchers)
stateSinkObject.Save(1, &d.closed)
}
func (d *discipline) afterLoad(context.Context) {}
// +checklocksignore
func (d *discipline) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &d.dispatchers)
stateSourceObject.Load(1, &d.closed)
}
func (qd *queueDispatcher) StateTypeName() string {
return "pkg/tcpip/link/qdisc/fifo.queueDispatcher"
}
func (qd *queueDispatcher) StateFields() []string {
return []string{
"lower",
"queue",
}
}
func (qd *queueDispatcher) beforeSave() {}
// +checklocksignore
func (qd *queueDispatcher) StateSave(stateSinkObject state.Sink) {
qd.beforeSave()
stateSinkObject.Save(0, &qd.lower)
stateSinkObject.Save(1, &qd.queue)
}
func (qd *queueDispatcher) afterLoad(context.Context) {}
// +checklocksignore
func (qd *queueDispatcher) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &qd.lower)
stateSourceObject.Load(1, &qd.queue)
}
func (pl *packetBufferCircularList) StateTypeName() string {
return "pkg/tcpip/link/qdisc/fifo.packetBufferCircularList"
}
func (pl *packetBufferCircularList) StateFields() []string {
return []string{
"pbs",
"head",
"size",
}
}
func (pl *packetBufferCircularList) beforeSave() {}
// +checklocksignore
func (pl *packetBufferCircularList) StateSave(stateSinkObject state.Sink) {
pl.beforeSave()
stateSinkObject.Save(0, &pl.pbs)
stateSinkObject.Save(1, &pl.head)
stateSinkObject.Save(2, &pl.size)
}
func (pl *packetBufferCircularList) afterLoad(context.Context) {}
// +checklocksignore
func (pl *packetBufferCircularList) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &pl.pbs)
stateSourceObject.Load(1, &pl.head)
stateSourceObject.Load(2, &pl.size)
}
func init() {
state.Register((*discipline)(nil))
state.Register((*queueDispatcher)(nil))
state.Register((*packetBufferCircularList)(nil))
}

View file

@ -0,0 +1,93 @@
// Copyright 2022 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 fifo
import "github.com/sagernet/gvisor/pkg/tcpip/stack"
// packetBufferCircularList is a slice-backed circular list. All operations are
// O(1) unless otherwise noted. It only allocates once, during the call to
// init().
//
// Users should call init() before using packetBufferCircularList.
//
// +stateify savable
type packetBufferCircularList struct {
pbs []*stack.PacketBuffer
head int
size int
}
// init initializes the list with the given size.
func (pl *packetBufferCircularList) init(size int) {
pl.pbs = make([]*stack.PacketBuffer, size)
}
// length returns the number of elements in the list.
//
//go:nosplit
func (pl *packetBufferCircularList) length() int {
return pl.size
}
// hasSpace returns whether there is space left in the list.
//
//go:nosplit
func (pl *packetBufferCircularList) hasSpace() bool {
return pl.size < len(pl.pbs)
}
// isEmpty returns whether the list is empty.
//
//go:nosplit
func (pl *packetBufferCircularList) isEmpty() bool {
return pl.size == 0
}
// pushBack inserts the PacketBuffer at the end of the list.
//
// Users must check beforehand that there is space via a call to hasSpace().
// Failing to do so may clobber existing entries.
//
//go:nosplit
func (pl *packetBufferCircularList) pushBack(pb *stack.PacketBuffer) {
next := (pl.head + pl.size) % len(pl.pbs)
pl.pbs[next] = pb
pl.size++
}
// removeFront returns the first element of the list or nil.
//
//go:nosplit
func (pl *packetBufferCircularList) removeFront() *stack.PacketBuffer {
if pl.isEmpty() {
return nil
}
ret := pl.pbs[pl.head]
pl.pbs[pl.head] = nil
pl.head = (pl.head + 1) % len(pl.pbs)
pl.size--
return ret
}
// decRef decreases the reference count on each stack.PacketBuffer stored in
// the list.
//
// NOTE: runs in O(n) time.
//
//go:nosplit
func (pl *packetBufferCircularList) decRef() {
for i := 0; i < pl.size; i++ {
pl.pbs[(pl.head+i)%len(pl.pbs)].DecRef()
}
}