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:
parent
ffebe42860
commit
117243aa02
293 changed files with 16413 additions and 2842 deletions
|
|
@ -23,7 +23,6 @@ import (
|
|||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/ports"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/seqnum"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
|
@ -33,7 +32,7 @@ var logDisconnectOnce sync.Once
|
|||
|
||||
func logDisconnect() {
|
||||
logDisconnectOnce.Do(func() {
|
||||
log.Infof("One or more TCP connections terminated during save")
|
||||
log.Infof("One or more TCP connections terminated during save restore")
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -49,17 +48,24 @@ func (e *Endpoint) beforeSave() {
|
|||
switch {
|
||||
case epState == StateInitial || epState == StateBound:
|
||||
case epState.connected() || epState.handshake():
|
||||
if !e.route.HasSaveRestoreCapability() {
|
||||
if !e.route.HasDisconnectOkCapability() {
|
||||
panic(&tcpip.ErrSaveRejection{
|
||||
Err: fmt.Errorf("endpoint cannot be saved in connected state: local %s:%d, remote %s:%d", e.TransportEndpointInfo.ID.LocalAddress, e.TransportEndpointInfo.ID.LocalPort, e.TransportEndpointInfo.ID.RemoteAddress, e.TransportEndpointInfo.ID.RemotePort),
|
||||
})
|
||||
// Terminate valid connections only for restore.
|
||||
if !e.stack.GetAllowConnectedOnSave() && !e.route.HasSaveRestoreCapability() {
|
||||
if e.stack.GetRemoveConf() {
|
||||
// Terminate the endpoint when resume=false.
|
||||
e.terminateAtRestore = false
|
||||
if !e.stack.AllowLiveTCPMigration() {
|
||||
logDisconnect()
|
||||
e.resetConnectionLocked(&tcpip.ErrConnectionAborted{})
|
||||
e.mu.Unlock()
|
||||
e.Close()
|
||||
e.mu.Lock()
|
||||
}
|
||||
} else {
|
||||
// This is set only when resume=true, the termination
|
||||
// of this endpoint will happen during restore of the
|
||||
// saved snapshot.
|
||||
e.terminateAtRestore = true
|
||||
}
|
||||
logDisconnect()
|
||||
e.resetConnectionLocked(&tcpip.ErrConnectionAborted{})
|
||||
e.mu.Unlock()
|
||||
e.Close()
|
||||
e.mu.Lock()
|
||||
}
|
||||
fallthrough
|
||||
case epState == StateListen:
|
||||
|
|
@ -133,10 +139,41 @@ func (e *Endpoint) afterLoad(ctx context.Context) {
|
|||
// Restore the endpoint to InitialState as it will be moved to
|
||||
// its origEndpointState during Restore.
|
||||
e.state = atomicbitops.FromUint32(uint32(StateInitial))
|
||||
if e.stack.IsSaveRestoreEnabled() {
|
||||
e.stack.RegisterRestoredEndpoint(e)
|
||||
e.stack.RegisterRestoredEndpoint(e)
|
||||
}
|
||||
|
||||
// Close the endpoint during restore if terminateAtRestore was set for the endpoint.
|
||||
func (e *Endpoint) closeEndpointAtRestore() {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
epState := EndpointState(e.origEndpointState)
|
||||
if !epState.connected() && !epState.handshake() {
|
||||
log.Debugf("endpoint was marked to terminate at restore in a wrong state, ID: %+v state: %v", e.ID, epState)
|
||||
return
|
||||
}
|
||||
|
||||
if epState.handshake() {
|
||||
connectedLoading.Wait()
|
||||
listenLoading.Wait()
|
||||
}
|
||||
|
||||
// Put the endpoint in the error state and do cleanup. Do not
|
||||
// attempt to send RST as route will be nil.
|
||||
e.purgeReadQueue()
|
||||
if epState.connected() {
|
||||
e.purgeWriteQueue()
|
||||
e.purgePendingRcvQueue()
|
||||
e.cleanupLocked()
|
||||
}
|
||||
e.state.Store(uint32(StateError))
|
||||
e.closeNoShutdownLocked()
|
||||
tcpip.DeleteDanglingEndpoint(e)
|
||||
|
||||
if epState.connected() {
|
||||
connectedLoading.Done()
|
||||
} else {
|
||||
stack.RestoreStackFromContext(ctx).RegisterRestoredEndpoint(e)
|
||||
connectingLoading.Done()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,48 +188,52 @@ func (e *Endpoint) Restore(s *stack.Stack) {
|
|||
snd.probeTimer.init(s.Clock(), timerHandler(e, e.snd.probeTimerExpired))
|
||||
snd.corkTimer.init(s.Clock(), timerHandler(e, e.snd.corkTimerExpired))
|
||||
}
|
||||
saveRestoreEnabled := e.stack.IsSaveRestoreEnabled()
|
||||
if !saveRestoreEnabled {
|
||||
e.stack = s
|
||||
e.protocol = protocolFromStack(s)
|
||||
}
|
||||
e.ops.InitHandler(e, e.stack, GetTCPSendBufferLimits, GetTCPReceiveBufferLimits)
|
||||
e.segmentQueue.thaw()
|
||||
|
||||
e.mu.Lock()
|
||||
id := e.ID
|
||||
terminateAtRestore := e.terminateAtRestore
|
||||
e.mu.Unlock()
|
||||
|
||||
bind := func() {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if !saveRestoreEnabled {
|
||||
addr, _, err := e.checkV4MappedLocked(tcpip.FullAddress{Addr: e.BindAddr, Port: e.TransportEndpointInfo.ID.LocalPort}, true /* bind */)
|
||||
if err != nil {
|
||||
panic("unable to parse BindAddr: " + err.String())
|
||||
}
|
||||
portRes := ports.Reservation{
|
||||
Networks: e.effectiveNetProtos,
|
||||
Transport: ProtocolNumber,
|
||||
Addr: addr.Addr,
|
||||
Port: addr.Port,
|
||||
Flags: e.boundPortFlags,
|
||||
BindToDevice: e.boundBindToDevice,
|
||||
Dest: e.boundDest,
|
||||
}
|
||||
if ok := e.stack.ReserveTuple(portRes); !ok {
|
||||
panic(fmt.Sprintf("unable to re-reserve tuple (%v, %q, %d, %+v, %d, %v)", e.effectiveNetProtos, addr.Addr, addr.Port, e.boundPortFlags, e.boundBindToDevice, e.boundDest))
|
||||
}
|
||||
}
|
||||
e.isPortReserved = true
|
||||
|
||||
// Mark endpoint as bound.
|
||||
e.setEndpointState(StateBound)
|
||||
}
|
||||
|
||||
if terminateAtRestore && !e.stack.AllowLiveTCPMigration() {
|
||||
e.closeEndpointAtRestore()
|
||||
return
|
||||
}
|
||||
|
||||
epState := EndpointState(e.origEndpointState)
|
||||
switch {
|
||||
case epState.connected():
|
||||
if e.stack.AllowLiveTCPMigration() {
|
||||
// Handle dual stack addresses.
|
||||
netProto := e.NetProto
|
||||
switch e.TransportEndpointInfo.ID.LocalAddress.BitLen() {
|
||||
case header.IPv4AddressSizeBits:
|
||||
netProto = header.IPv4ProtocolNumber
|
||||
case header.IPv6AddressSizeBits:
|
||||
netProto = header.IPv6ProtocolNumber
|
||||
}
|
||||
// Get the new local NIC for source IP and do a FindRoute here to
|
||||
// identify if the network config is same. Then only attempt restore,
|
||||
// else close the connection on our end.
|
||||
r, err := e.stack.FindRoute(0, e.TransportEndpointInfo.ID.LocalAddress, e.TransportEndpointInfo.ID.RemoteAddress, netProto, false /* multicastLoop */)
|
||||
if err != nil {
|
||||
e.closeEndpointAtRestore()
|
||||
log.Infof("Cannot find the route %+v", e.TransportEndpointInfo.ID)
|
||||
return
|
||||
}
|
||||
e.boundNICID = r.NICID()
|
||||
r.Release()
|
||||
}
|
||||
bind()
|
||||
if e.connectingAddress.BitLen() == 0 {
|
||||
e.connectingAddress = e.TransportEndpointInfo.ID.RemoteAddress
|
||||
|
|
@ -210,10 +251,8 @@ func (e *Endpoint) Restore(s *stack.Stack) {
|
|||
// Reset the scoreboard to reinitialize the sack information as
|
||||
// we do not restore SACK information.
|
||||
e.scoreboard.Reset()
|
||||
if saveRestoreEnabled {
|
||||
// Unregister the endpoint before registering again during Connect.
|
||||
e.stack.UnregisterTransportEndpoint(e.effectiveNetProtos, header.TCPProtocolNumber, e.TransportEndpointInfo.ID, e, e.boundPortFlags, e.boundBindToDevice)
|
||||
}
|
||||
// Unregister the endpoint before registering again during Connect.
|
||||
e.stack.UnregisterTransportEndpoint(e.effectiveNetProtos, header.TCPProtocolNumber, e.TransportEndpointInfo.ID, e, e.boundPortFlags, e.boundBindToDevice)
|
||||
e.mu.Lock()
|
||||
err := e.connect(tcpip.FullAddress{NIC: e.boundNICID, Addr: e.connectingAddress, Port: e.TransportEndpointInfo.ID.RemotePort}, false /* handshake */)
|
||||
if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
|
||||
|
|
@ -224,6 +263,7 @@ func (e *Endpoint) Restore(s *stack.Stack) {
|
|||
return
|
||||
}
|
||||
e.state.Store(e.origEndpointState)
|
||||
log.Infof("connect success: %+v", e.TransportEndpointInfo.ID)
|
||||
// For FIN-WAIT-2 and TIME-WAIT we need to start the appropriate timers so
|
||||
// that the socket is closed correctly.
|
||||
switch epState {
|
||||
|
|
@ -239,42 +279,24 @@ func (e *Endpoint) Restore(s *stack.Stack) {
|
|||
e.snd.corkTimer.enable(MinRTO)
|
||||
}
|
||||
e.mu.Unlock()
|
||||
e.requeueOnRestore()
|
||||
connectedLoading.Done()
|
||||
case epState == StateListen:
|
||||
tcpip.AsyncLoading.Add(1)
|
||||
if !saveRestoreEnabled {
|
||||
go func() {
|
||||
connectedLoading.Wait()
|
||||
bind()
|
||||
e.acceptMu.Lock()
|
||||
backlog := e.acceptQueue.capacity
|
||||
e.acceptMu.Unlock()
|
||||
if err := e.Listen(backlog); err != nil {
|
||||
panic("endpoint listening failed: " + err.String())
|
||||
}
|
||||
e.LockUser()
|
||||
if e.shutdownFlags != 0 {
|
||||
e.shutdownLocked(e.shutdownFlags)
|
||||
}
|
||||
e.UnlockUser()
|
||||
listenLoading.Done()
|
||||
tcpip.AsyncLoading.Done()
|
||||
}()
|
||||
} else {
|
||||
go func() {
|
||||
connectedLoading.Wait()
|
||||
e.LockUser()
|
||||
// All endpoints will be moved to initial state after
|
||||
// restore. Set endpoint to its originial listen state.
|
||||
e.setEndpointState(StateListen)
|
||||
// Initialize the listening context.
|
||||
rcvWnd := seqnum.Size(e.receiveBufferAvailable())
|
||||
e.listenCtx = newListenContext(e.stack, e.protocol, e, rcvWnd, e.ops.GetV6Only(), e.NetProto)
|
||||
e.UnlockUser()
|
||||
listenLoading.Done()
|
||||
tcpip.AsyncLoading.Done()
|
||||
}()
|
||||
}
|
||||
go func() {
|
||||
connectedLoading.Wait()
|
||||
e.LockUser()
|
||||
// All endpoints will be moved to initial state after
|
||||
// restore. Set endpoint to its originial listen state.
|
||||
e.setEndpointState(StateListen)
|
||||
// Initialize the listening context.
|
||||
rcvWnd := seqnum.Size(e.receiveBufferAvailable())
|
||||
e.listenCtx = newListenContext(e.stack, e.protocol, e, rcvWnd, e.ops.GetV6Only(), e.NetProto)
|
||||
e.UnlockUser()
|
||||
e.requeueOnRestore()
|
||||
listenLoading.Done()
|
||||
tcpip.AsyncLoading.Done()
|
||||
}()
|
||||
case epState == StateConnecting:
|
||||
// Initial SYN hasn't been sent yet so initiate a connect.
|
||||
tcpip.AsyncLoading.Add(1)
|
||||
|
|
@ -319,6 +341,7 @@ func (e *Endpoint) Restore(s *stack.Stack) {
|
|||
connectingLoading.Done()
|
||||
tcpip.AsyncLoading.Done()
|
||||
e.mu.Unlock()
|
||||
e.requeueOnRestore()
|
||||
}()
|
||||
case epState == StateBound:
|
||||
tcpip.AsyncLoading.Add(1)
|
||||
|
|
@ -345,3 +368,12 @@ func (e *Endpoint) Restore(s *stack.Stack) {
|
|||
func (e *Endpoint) Resume() {
|
||||
e.segmentQueue.thaw()
|
||||
}
|
||||
|
||||
// requeueOnRestore re-adds the endpoint to its processor's run-queue if it has
|
||||
// queued segments. The run-queue is not saved across checkpoint/restore.
|
||||
func (e *Endpoint) requeueOnRestore() {
|
||||
if e.segmentQueue.empty() || e.isOwnedByUser() {
|
||||
return
|
||||
}
|
||||
e.protocol.dispatcher.selectProcessor(e.TransportEndpointInfo.ID).queueEndpoint(e)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue