snapshot: sagernet/gvisor v0.0.0-20260727.0-sing-box-mod.1 + SPEC 048 guard

Обновление снапшота с v0.0.0-20250811.0 на пин, которого требует
sing-box после мержа 235 коммитов (upstream d620bbbf2 "Update gvisor to
20260727.0"). Прежний снапшот был взят 2026-08-04 ровно с той версии,
на которой тогда стоял апстрим; разрыв возник 2026-08-05 вместе с его
бампом.

За год апстрим-gvisor изменил ~14 000 строк в 292 файлах. Значимое для
нас — сетевой стек: tcp/connect.go (PMTU-discovery + исправление
начального RTT/RTO: раньше задержка ACK внутри стека завышала стартовый
таймаут на несколько RTT), tcp/snd.go, tcp/rcv.go, stack/conntrack.go,
stack/packet_buffer.go. Всего 30 файлов в TCP и 37 в stack.

Баг SPEC 048 апстрим НЕ исправил — проверено по коду новой версии:
handleConnecting по-прежнему проверяет состояние endpoint'а, но не ep.h,
а performHandshake так же зануляет h и отпускает мьютекс до Close().
Поэтому guard перенесён (12 строк) вместе со своим тестом (45 строк).

Red/green проверен на новой базе: без guard'а тест падает с той же
nil-паникой, что в полевом крашдампе; с ним зелёный.
This commit is contained in:
Leadaxe 2026-08-05 14:53:31 +03:00
parent ffebe42860
commit 117243aa02
293 changed files with 16413 additions and 2842 deletions

View file

@ -341,6 +341,9 @@ func (sq *sndQueueInfo) CloneState(other *TCPSndBufState) {
// TODO(b/339664055): Checklocks should be used more extensively here. Coverage
// is currently sparse.
//
// +checklocksalias:snd.ep.mu=mu
// +checklocksalias:rcv.ep.mu=mu
// +checklocksalias:h.ep.mu=mu
// +stateify savable
type Endpoint struct {
TCPEndpointStateInner
@ -607,6 +610,16 @@ type Endpoint struct {
//
// +checklocks:mu
alsoBindToV4 bool
// terminateAtRestore indicates whether the endpoint must be terminated
// upon restore. This applies specifically when the snapshots are taken
// with the "save-resume" flag, ensuring that if such a snapshot is
// restored later, this endpoint is cleaned up. This flag is only set
// to true in beforeSave for external endpoints which do not have
// save-restore capability.
//
// +checklocks:mu
terminateAtRestore bool
}
// calculateAdvertisedMSS calculates the MSS to advertise.
@ -641,7 +654,6 @@ func (e *Endpoint) isOwnedByUser() bool {
// should not be holding the lock for long and spinning reduces latency as we
// avoid an expensive sleep/wakeup of the syscall goroutine).
// +checklocksacquire:e.mu
// +checklocksacquire:e.snd.ep.mu
func (e *Endpoint) LockUser() {
const iterations = 5
for i := 0; i < iterations; i++ {
@ -654,14 +666,14 @@ func (e *Endpoint) LockUser() {
if e.ownedByUser.Load() == 1 {
e.mu.Lock()
e.ownedByUser.Store(1)
return // +checklocksforce: this locks e.snd.ep.mu
return
}
// Spin but don't yield the processor since the lower half
// should yield the lock soon.
continue
}
e.ownedByUser.Store(1)
return // +checklocksforce: this locks e.snd.ep.mu
return
}
for i := 0; i < iterations; i++ {
@ -674,7 +686,7 @@ func (e *Endpoint) LockUser() {
if e.ownedByUser.Load() == 1 {
e.mu.Lock()
e.ownedByUser.Store(1)
return // +checklocksforce: this locks e.snd.ep.mu
return
}
// Spin but yield the processor since the lower half
// should yield the lock soon.
@ -682,7 +694,7 @@ func (e *Endpoint) LockUser() {
continue
}
e.ownedByUser.Store(1)
return // +checklocksforce: this locks e.snd.ep.mu
return
}
// Finally just give up and wait for the Lock.
@ -725,7 +737,6 @@ func (e *Endpoint) UnlockUser() {
// processor goroutine starts running before we release the lock here
// then it will fail to process as TryLock() will fail.
processor.queueEndpoint(e)
return
}
// StopWork halts packet processing. Only to be used in tests.
@ -760,10 +771,7 @@ func (e *Endpoint) AssertLockHeld(locked *Endpoint) {
// TODO(b/226403629): Remove this once checklocks understands TryLock.
// +checklocksacquire:e.mu
func (e *Endpoint) TryLock() bool {
if e.mu.TryLock() {
return true // +checklocksforce
}
return false // +checklocksignore
return e.mu.TryLock() // +checklocksforce: TryLock.
}
// setEndpointState updates the state of the endpoint to state atomically. This
@ -1015,7 +1023,6 @@ func (e *Endpoint) purgeReadQueue() {
}
// +checklocks:e.mu
// +checklocksalias:e.snd.ep.mu=e.mu
func (e *Endpoint) purgeWriteQueue() {
if e.snd != nil {
e.sndQueueInfo.sndQueueMu.Lock()
@ -1358,7 +1365,7 @@ func (e *Endpoint) ModerateRecvBuf(copied int) {
// Send the update after unlocking rcvQueueMu as sending a segment acquires
// the lock to calculate the window to be sent.
if e.EndpointState().connected() && sendNonZeroWindowUpdate {
e.rcv.nonZeroWindow() // +checklocksforce:e.rcv.ep.mu
e.rcv.nonZeroWindow()
}
}
@ -1465,7 +1472,7 @@ func (e *Endpoint) Read(dst io.Writer, opts tcpip.ReadOptions) (tcpip.ReadResult
e.rcvQueueMu.Unlock()
if e.EndpointState().connected() && sendNonZeroWindowUpdate {
e.rcv.nonZeroWindow() // +checklocksforce:e.rcv.ep.mu
e.rcv.nonZeroWindow()
}
}
@ -1601,7 +1608,6 @@ func (e *Endpoint) readFromPayloader(p tcpip.Payloader, opts tcpip.WriteOptions,
// queueSegment reads data from the payloader and returns a segment to be sent.
// +checklocks:e.mu
// +checklocksalias:e.snd.ep.mu=e.mu
func (e *Endpoint) queueSegment(p tcpip.Payloader, opts tcpip.WriteOptions) (*segment, int, tcpip.Error) {
e.sndQueueInfo.sndQueueMu.Lock()
defer e.sndQueueInfo.sndQueueMu.Unlock()
@ -1642,7 +1648,7 @@ func (e *Endpoint) queueSegment(p tcpip.Payloader, opts tcpip.WriteOptions) (*se
// Add data to the send queue.
size := int(buf.Size())
s := newOutgoingSegment(e.TransportEndpointInfo.ID, e.stack.Clock(), buf)
s := newOutgoingSegment(e.TransportEndpointInfo.ID, e.stack.Clock(), buf, e.ops.GetMark())
e.sndQueueInfo.SndBufUsed += size
e.snd.writeList.PushBack(s)
@ -1828,7 +1834,7 @@ func (e *Endpoint) OnSetReceiveBufferSize(rcvBufSz, oldSz int64) (newSz int64, p
e.LockUser()
defer e.UnlockUser()
if e.EndpointState().connected() && sendNonZeroWindowUpdate {
e.rcv.nonZeroWindow() // +checklocksforce:e.rcv.ep.mu
e.rcv.nonZeroWindow()
}
}
e.UnlockUser()
@ -1894,15 +1900,15 @@ func (e *Endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
e.UnlockUser()
case tcpip.MTUDiscoverOption:
// PROBE is accepted alongside DO/WANT/DONT. In Linux,
// PROBE sets DF but ignores ICMP-based PMTU updates;
// since gVisor lacks ICMP PMTU feedback, it behaves
// identically to DO.
switch v := tcpip.PMTUDStrategy(v); v {
case tcpip.PMTUDiscoveryWant, tcpip.PMTUDiscoveryDont, tcpip.PMTUDiscoveryDo:
case tcpip.PMTUDiscoveryWant, tcpip.PMTUDiscoveryDont, tcpip.PMTUDiscoveryDo, tcpip.PMTUDiscoveryProbe:
e.LockUser()
e.pmtud = v
e.UnlockUser()
case tcpip.PMTUDiscoveryProbe:
// We don't support a way to ignore MTU updates; it's
// either on or it's off.
return &tcpip.ErrNotSupported{}
default:
return &tcpip.ErrNotSupported{}
}
@ -2391,7 +2397,6 @@ func (e *Endpoint) registerEndpoint(addr tcpip.FullAddress, netProto tcpip.Netwo
// connect connects the endpoint to its peer.
// +checklocks:e.mu
// +checklocksalias:e.snd.ep.mu=e.mu
func (e *Endpoint) connect(addr tcpip.FullAddress, handshake bool) tcpip.Error {
connectingAddr := addr.Addr
@ -2540,7 +2545,6 @@ func (e *Endpoint) Shutdown(flags tcpip.ShutdownFlags) tcpip.Error {
}
// +checklocks:e.mu
// +checklocksalias:e.snd.ep.mu=e.mu
func (e *Endpoint) shutdownLocked(flags tcpip.ShutdownFlags) tcpip.Error {
e.shutdownFlags |= flags
switch {
@ -2581,9 +2585,18 @@ func (e *Endpoint) shutdownLocked(flags tcpip.ShutdownFlags) tcpip.Error {
return nil
}
// Queue fin segment.
s := newOutgoingSegment(e.TransportEndpointInfo.ID, e.stack.Clock(), buffer.Buffer{})
// Queue FIN and transition to the closing state immediately,
// matching Linux tcp_close_state(): the FIN may be queued but
// not yet transmitted when the write queue is blocked.
s := newOutgoingSegment(e.TransportEndpointInfo.ID, e.stack.Clock(), buffer.Buffer{}, e.ops.GetMark())
e.snd.writeList.PushBack(s)
e.updateConnDirectionState(connDirectionStateSndClosed)
switch e.EndpointState() {
case StateCloseWait:
e.setEndpointState(StateLastAck)
default:
e.setEndpointState(StateFinWait1)
}
// Mark endpoint as closed.
e.sndQueueInfo.SndClosed = true
e.sndQueueInfo.sndQueueMu.Unlock()
@ -2924,7 +2937,8 @@ func (e *Endpoint) onICMPError(err tcpip.Error, transErr stack.TransportError, p
if e.EndpointState().connecting() {
e.mu.Lock()
if lEP := e.h.listenEP; lEP != nil {
if e.h != nil && e.h.listenEP != nil {
lEP := e.h.listenEP
// Remove from listening endpoints pending list.
lEP.acceptMu.Lock()
delete(lEP.acceptQueue.pendingEndpoints, e)
@ -2956,7 +2970,7 @@ func (e *Endpoint) HandleError(transErr stack.TransportError, pkt *stack.PacketB
e.mu.Lock()
defer e.mu.Unlock()
if e.snd != nil {
e.snd.updateMaxPayloadSize(newMTU, 1 /* count */) // +checklocksforce:e.snd.ep.mu
e.snd.updateMaxPayloadSize(newMTU, 1 /* count */)
}
}
}
@ -2986,7 +3000,6 @@ func (e *Endpoint) HandleError(transErr stack.TransportError, pkt *stack.PacketB
// number of newly available bytes is v.
//
// +checklocks:e.mu
// +checklocksalias:e.snd.ep.mu=e.mu
func (e *Endpoint) updateSndBufferUsage(v int) {
sendBufferSize := e.getSendBufferSize()
e.sndQueueInfo.sndQueueMu.Lock()
@ -3168,7 +3181,6 @@ func (e *Endpoint) maxOptionSize() (size int) {
// used before invoking the probe.
//
// +checklocks:e.mu
// +checklocksalias:e.snd.ep.mu=e.mu
func (e *Endpoint) completeStateLocked(s *TCPEndpointState) {
s.TCPEndpointStateInner = e.TCPEndpointStateInner
s.ID = TCPEndpointID(e.TransportEndpointInfo.ID)
@ -3277,11 +3289,7 @@ func GetTCPSendBufferLimits(sh tcpip.StackHandler) tcpip.SendBufferSizeOption {
// This type assertion is safe because only the TCP stack calls this
// function.
ss := sh.(*stack.Stack).TCPSendBufferLimits()
return tcpip.SendBufferSizeOption{
Min: ss.Min,
Default: ss.Default,
Max: ss.Max,
}
return tcpip.SendBufferSizeOption(ss)
}
// allowOutOfWindowAck returns true if an out-of-window ACK can be sent now.
@ -3309,18 +3317,13 @@ func GetTCPReceiveBufferLimits(s tcpip.StackHandler) tcpip.ReceiveBufferSizeOpti
panic(fmt.Sprintf("s.TransportProtocolOption(%d, %#v) = %s", header.TCPProtocolNumber, ss, err))
}
return tcpip.ReceiveBufferSizeOption{
Min: ss.Min,
Default: ss.Default,
Max: ss.Max,
}
return tcpip.ReceiveBufferSizeOption(ss)
}
// computeTCPSendBufferSize implements auto tuning of send buffer size and
// returns the new send buffer size.
//
// +checklocks:e.mu
// +checklocksalias:e.snd.ep.mu=e.mu
func (e *Endpoint) computeTCPSendBufferSize() int64 {
curSndBufSz := int64(e.getSendBufferSize())