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
|
|
@ -371,6 +371,7 @@ func (h *handshake) synSentState(s *segment) tcpip.Error {
|
|||
seq: h.iss,
|
||||
ack: h.ackNum,
|
||||
rcvWnd: h.rcvWnd,
|
||||
df: h.ep.pmtud == tcpip.PMTUDiscoveryWant || h.ep.pmtud == tcpip.PMTUDiscoveryDo || h.ep.pmtud == tcpip.PMTUDiscoveryProbe,
|
||||
expOptVal: h.ep.getExperimentOptionValue(h.ep.route),
|
||||
}, synOpts)
|
||||
return nil
|
||||
|
|
@ -458,6 +459,7 @@ func (h *handshake) synRcvdState(s *segment) tcpip.Error {
|
|||
seq: h.iss,
|
||||
ack: h.ackNum,
|
||||
rcvWnd: h.rcvWnd,
|
||||
df: h.ep.pmtud == tcpip.PMTUDiscoveryWant || h.ep.pmtud == tcpip.PMTUDiscoveryDo || h.ep.pmtud == tcpip.PMTUDiscoveryProbe,
|
||||
expOptVal: h.ep.getExperimentOptionValue(h.ep.route),
|
||||
}, synOpts)
|
||||
return nil
|
||||
|
|
@ -555,6 +557,7 @@ func (h *handshake) processSegments() tcpip.Error {
|
|||
|
||||
// start sends the first SYN/SYN-ACK. It does not block, even if link address
|
||||
// resolution is required.
|
||||
// +checklocks:h.ep.mu
|
||||
func (h *handshake) start() {
|
||||
h.startTime = h.ep.stack.Clock().NowMonotonic()
|
||||
h.ep.amss = calculateAdvertisedMSS(h.ep.userMSS, h.ep.route)
|
||||
|
|
@ -595,6 +598,7 @@ func (h *handshake) start() {
|
|||
seq: h.iss,
|
||||
ack: h.ackNum,
|
||||
rcvWnd: h.rcvWnd,
|
||||
df: h.ep.pmtud == tcpip.PMTUDiscoveryWant || h.ep.pmtud == tcpip.PMTUDiscoveryDo || h.ep.pmtud == tcpip.PMTUDiscoveryProbe,
|
||||
expOptVal: h.ep.getExperimentOptionValue(h.ep.route),
|
||||
}, synOpts)
|
||||
}
|
||||
|
|
@ -632,6 +636,7 @@ func (h *handshake) retransmitHandlerLocked() tcpip.Error {
|
|||
seq: h.iss,
|
||||
ack: h.ackNum,
|
||||
rcvWnd: h.rcvWnd,
|
||||
df: h.ep.pmtud == tcpip.PMTUDiscoveryWant || h.ep.pmtud == tcpip.PMTUDiscoveryDo || h.ep.pmtud == tcpip.PMTUDiscoveryProbe,
|
||||
expOptVal: e.getExperimentOptionValue(e.route),
|
||||
}, h.sendSYNOpts)
|
||||
// If we have ever retransmitted the SYN-ACK or
|
||||
|
|
@ -642,11 +647,10 @@ func (h *handshake) retransmitHandlerLocked() tcpip.Error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// transitionToStateEstablisedLocked transitions the endpoint of the handshake
|
||||
// transitionToStateEstablishedLocked transitions the endpoint of the handshake
|
||||
// to an established state given the last segment received from peer. It also
|
||||
// initializes sender/receiver.
|
||||
// +checklocks:h.ep.mu
|
||||
// +checklocksalias:h.ep.snd.ep.mu=h.ep.mu
|
||||
func (h *handshake) transitionToStateEstablishedLocked(s *segment) {
|
||||
// Stop the SYN retransmissions now that handshake is complete.
|
||||
if h.retransmitTimer != nil {
|
||||
|
|
@ -656,16 +660,20 @@ func (h *handshake) transitionToStateEstablishedLocked(s *segment) {
|
|||
// Transfer handshake state to TCP connection. We disable
|
||||
// receive window scaling if the peer doesn't support it
|
||||
// (indicated by a negative send window scale).
|
||||
h.ep.snd = newSender(h.ep, h.iss, h.ackNum-1, h.sndWnd, h.mss, h.sndWndScale)
|
||||
initSender(h.ep, h.iss, h.ackNum-1, h.sndWnd, h.mss, h.sndWndScale)
|
||||
|
||||
now := h.ep.stack.Clock().NowMonotonic()
|
||||
// Use the final handshake ACK's ingress time (s.rcvdTime) rather than the
|
||||
// current clock to seed the initial RTT/RTO. If the ACK was delayed inside
|
||||
// the stack before processing, the processing-time clock would inflate the
|
||||
// initial RTO, which then persists for several RTTs.
|
||||
rcvd := s.rcvdTime
|
||||
|
||||
var rtt time.Duration
|
||||
if h.ep.SendTSOk && s.parsedOptions.TSEcr != 0 {
|
||||
rtt = h.ep.elapsed(now, s.parsedOptions.TSEcr)
|
||||
rtt = h.ep.elapsed(rcvd, s.parsedOptions.TSEcr)
|
||||
}
|
||||
if !h.sampleRTTWithTSOnly && rtt == 0 {
|
||||
rtt = now.Sub(h.startTime)
|
||||
rtt = rcvd.Sub(h.startTime)
|
||||
}
|
||||
|
||||
if rtt > 0 {
|
||||
|
|
@ -824,7 +832,10 @@ func (e *Endpoint) sendSynTCP(r *stack.Route, tf tcpFields, opts header.TCPSynOp
|
|||
if r.NetProto() == header.IPv6ProtocolNumber && tf.expOptVal != 0 {
|
||||
hdrSize += header.IPv6ExperimentHdrLength
|
||||
}
|
||||
p := stack.NewPacketBuffer(stack.PacketBufferOptions{ReserveHeaderBytes: hdrSize})
|
||||
p := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: hdrSize,
|
||||
Mark: e.ops.GetMark(),
|
||||
})
|
||||
defer p.DecRef()
|
||||
if err := e.sendTCP(r, tf, p, stack.GSO{}); err != nil {
|
||||
e.stats.SendErrors.SynSendToNetworkFailed.Increment()
|
||||
|
|
@ -900,7 +911,10 @@ func sendTCPBatch(r *stack.Route, tf tcpFields, pkt *stack.PacketBuffer, gso sta
|
|||
// Reserve extra bytes for the experiment option.
|
||||
hdrSize += header.IPv6ExperimentHdrLength
|
||||
}
|
||||
splitPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ReserveHeaderBytes: hdrSize})
|
||||
splitPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: hdrSize,
|
||||
Mark: pkt.Mark,
|
||||
})
|
||||
splitPkt.Data().ReadFromPacketData(pkt.Data(), packetSize)
|
||||
pkt = splitPkt
|
||||
}
|
||||
|
|
@ -1007,9 +1021,10 @@ func (e *Endpoint) makeOptions(sackBlocks []header.SACKBlock) []byte {
|
|||
// sendEmptyRaw sends a TCP segment with no payload to the endpoint's peer.
|
||||
//
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.snd.ep.mu=e.mu
|
||||
func (e *Endpoint) sendEmptyRaw(flags header.TCPFlags, seq, ack seqnum.Value, rcvWnd seqnum.Size) tcpip.Error {
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{})
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Mark: e.ops.GetMark(),
|
||||
})
|
||||
defer pkt.DecRef()
|
||||
return e.sendRaw(pkt, flags, seq, ack, rcvWnd)
|
||||
}
|
||||
|
|
@ -1018,7 +1033,6 @@ func (e *Endpoint) sendEmptyRaw(flags header.TCPFlags, seq, ack seqnum.Value, rc
|
|||
// ownership of pkt. pkt must not have any headers set.
|
||||
//
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.snd.ep.mu=e.mu
|
||||
func (e *Endpoint) sendRaw(pkt *stack.PacketBuffer, flags header.TCPFlags, seq, ack seqnum.Value, rcvWnd seqnum.Size) tcpip.Error {
|
||||
var sackBlocks []header.SACKBlock
|
||||
if e.EndpointState() == StateEstablished && e.rcv.pendingRcvdSegments.Len() > 0 && (flags&header.TCPFlagAck != 0) {
|
||||
|
|
@ -1033,21 +1047,21 @@ func (e *Endpoint) sendRaw(pkt *stack.PacketBuffer, flags header.TCPFlags, seq,
|
|||
}
|
||||
pkt.ReserveHeaderBytes(hdrSize)
|
||||
return e.sendTCP(e.route, tcpFields{
|
||||
id: e.TransportEndpointInfo.ID,
|
||||
ttl: calculateTTL(e.route, e.ipv4TTL, e.ipv6HopLimit),
|
||||
tos: e.sendTOS,
|
||||
flags: flags,
|
||||
seq: seq,
|
||||
ack: ack,
|
||||
rcvWnd: rcvWnd,
|
||||
opts: options,
|
||||
df: e.pmtud == tcpip.PMTUDiscoveryWant || e.pmtud == tcpip.PMTUDiscoveryDo,
|
||||
id: e.TransportEndpointInfo.ID,
|
||||
ttl: calculateTTL(e.route, e.ipv4TTL, e.ipv6HopLimit),
|
||||
tos: e.sendTOS,
|
||||
flags: flags,
|
||||
seq: seq,
|
||||
ack: ack,
|
||||
rcvWnd: rcvWnd,
|
||||
opts: options,
|
||||
// PROBE sets DF like DO; see network/endpoint.go for details.
|
||||
df: e.pmtud == tcpip.PMTUDiscoveryWant || e.pmtud == tcpip.PMTUDiscoveryDo || e.pmtud == tcpip.PMTUDiscoveryProbe,
|
||||
expOptVal: expOptVal,
|
||||
}, pkt, e.gso)
|
||||
}
|
||||
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.snd.ep.mu=e.mu
|
||||
func (e *Endpoint) sendData(next *segment) {
|
||||
// Initialize the next segment to write if it's currently nil.
|
||||
if e.snd.writeNext == nil {
|
||||
|
|
@ -1065,7 +1079,6 @@ func (e *Endpoint) sendData(next *segment) {
|
|||
// error code and sends a RST if and only if the error is not ErrConnectionReset
|
||||
// indicating that the connection is being reset due to receiving a RST.
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.snd.ep.mu=e.mu
|
||||
func (e *Endpoint) resetConnectionLocked(err tcpip.Error) {
|
||||
// Only send a reset if the connection is being aborted for a reason
|
||||
// other than receiving a reset.
|
||||
|
|
@ -1080,12 +1093,26 @@ func (e *Endpoint) resetConnectionLocked(err tcpip.Error) {
|
|||
//
|
||||
// See: https://www.snellman.net/blog/archive/2016-02-01-tcp-rst/ for more
|
||||
// information.
|
||||
sndWndEnd := e.snd.SndUna.Add(e.snd.SndWnd)
|
||||
resetSeqNum := sndWndEnd
|
||||
if !sndWndEnd.LessThan(e.snd.SndNxt) || e.snd.SndNxt.Size(sndWndEnd) < (1<<e.snd.SndWndScale) {
|
||||
resetSeqNum = e.snd.SndNxt
|
||||
//
|
||||
// e.snd and e.rcv may be nil if the endpoint is in a handshake
|
||||
// state (e.g. SynSent) where the sender and receiver have not
|
||||
// yet been initialized. Per Linux behavior, use a sequence
|
||||
// number of zero when no ACK has been received (snd is nil),
|
||||
// and a receive window of zero when rcv is nil since the
|
||||
// connection will be immediately terminated.
|
||||
var resetSeqNum seqnum.Value
|
||||
var ackNum seqnum.Value
|
||||
if e.snd != nil {
|
||||
sndWndEnd := e.snd.SndUna.Add(e.snd.SndWnd)
|
||||
resetSeqNum = sndWndEnd
|
||||
if !sndWndEnd.LessThan(e.snd.SndNxt) || e.snd.SndNxt.Size(sndWndEnd) < (1<<e.snd.SndWndScale) {
|
||||
resetSeqNum = e.snd.SndNxt
|
||||
}
|
||||
}
|
||||
e.sendEmptyRaw(header.TCPFlagAck|header.TCPFlagRst, resetSeqNum, e.rcv.RcvNxt, 0)
|
||||
if e.rcv != nil {
|
||||
ackNum = e.rcv.RcvNxt
|
||||
}
|
||||
e.sendEmptyRaw(header.TCPFlagAck|header.TCPFlagRst, resetSeqNum, ackNum, 0)
|
||||
}
|
||||
// Don't purge read queues here. If there's buffered data, it's still allowed
|
||||
// to be read.
|
||||
|
|
@ -1162,52 +1189,66 @@ func (e *Endpoint) drainClosingSegmentQueue() {
|
|||
}
|
||||
}
|
||||
|
||||
// handleReset processes an inbound segment carrying the RST flag.
|
||||
//
|
||||
// Acceptance follows RFC 5961 section 3.2:
|
||||
// - If the segment sequence number is out of window, the segment is
|
||||
// silently dropped.
|
||||
// - If the segment sequence number is in window but not exactly equal
|
||||
// to RCV.NXT, the implementation sends a challenge ACK and drops
|
||||
// the segment.
|
||||
// - Only an exact match against RCV.NXT causes the connection to be
|
||||
// reset.
|
||||
//
|
||||
// This is stricter than RFC 793 page 37, which accepted any in-window RST.
|
||||
// The strict-match rule defends against off-path blind RST injection.
|
||||
// Linux has implemented it since version 3.6 (2012); see
|
||||
// net/ipv4/tcp_input.c tcp_validate_incoming().
|
||||
//
|
||||
// +checklocks:e.mu
|
||||
func (e *Endpoint) handleReset(s *segment) (ok bool, err tcpip.Error) {
|
||||
if e.rcv.acceptable(s.sequenceNumber, 0) {
|
||||
// RFC 793, page 37 states that "in all states
|
||||
// except SYN-SENT, all reset (RST) segments are
|
||||
// validated by checking their SEQ-fields." So
|
||||
// we only process it if it's acceptable.
|
||||
switch e.EndpointState() {
|
||||
// In case of a RST in CLOSE-WAIT linux moves
|
||||
// the socket to closed state with an error set
|
||||
// to indicate EPIPE.
|
||||
//
|
||||
// Technically this seems to be at odds w/ RFC.
|
||||
// As per https://tools.ietf.org/html/rfc793#section-2.7
|
||||
// page 69 the behavior for a segment arriving
|
||||
// w/ RST bit set in CLOSE-WAIT is inlined below.
|
||||
//
|
||||
// ESTABLISHED
|
||||
// FIN-WAIT-1
|
||||
// FIN-WAIT-2
|
||||
// CLOSE-WAIT
|
||||
|
||||
// If the RST bit is set then, any outstanding RECEIVEs and
|
||||
// SEND should receive "reset" responses. All segment queues
|
||||
// should be flushed. Users should also receive an unsolicited
|
||||
// general "connection reset" signal. Enter the CLOSED state,
|
||||
// delete the TCB, and return.
|
||||
case StateCloseWait:
|
||||
e.transitionToStateCloseLocked()
|
||||
e.hardError = &tcpip.ErrAborted{}
|
||||
return false, nil
|
||||
default:
|
||||
// RFC 793, page 37 states that "in all states
|
||||
// except SYN-SENT, all reset (RST) segments are
|
||||
// validated by checking their SEQ-fields." So
|
||||
// we only process it if it's acceptable.
|
||||
return false, &tcpip.ErrConnectionReset{}
|
||||
}
|
||||
if !e.rcv.acceptable(s.sequenceNumber, 0) {
|
||||
// Out of window. Silent drop.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if s.sequenceNumber != e.rcv.RcvNxt {
|
||||
// In window but not an exact match. Send a challenge ACK and drop the
|
||||
// segment per RFC 5961 section 3.2. The challenge ACK helper rate-limits
|
||||
// challenge transmission per RFC 5961 section 7.
|
||||
e.snd.maybeSendOutOfWindowAck(s)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
switch e.EndpointState() {
|
||||
// In case of a RST in CLOSE-WAIT linux moves the socket to closed state
|
||||
// with an error set to indicate EPIPE.
|
||||
//
|
||||
// As per https://tools.ietf.org/html/rfc793#section-2.7 page 69 the
|
||||
// behavior for a segment arriving w/ RST bit set in CLOSE-WAIT is
|
||||
// inlined below.
|
||||
//
|
||||
// ESTABLISHED
|
||||
// FIN-WAIT-1
|
||||
// FIN-WAIT-2
|
||||
// CLOSE-WAIT
|
||||
//
|
||||
// If the RST bit is set then, any outstanding RECEIVEs and SEND should
|
||||
// receive "reset" responses. All segment queues should be flushed.
|
||||
// Users should also receive an unsolicited general "connection reset"
|
||||
// signal. Enter the CLOSED state, delete the TCB, and return.
|
||||
case StateCloseWait:
|
||||
e.transitionToStateCloseLocked()
|
||||
e.hardError = &tcpip.ErrAborted{}
|
||||
return false, nil
|
||||
default:
|
||||
return false, &tcpip.ErrConnectionReset{}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// handleSegments processes all inbound segments.
|
||||
//
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.snd.ep.mu=e.mu
|
||||
func (e *Endpoint) handleSegmentsLocked() tcpip.Error {
|
||||
sndUna := e.snd.SndUna
|
||||
for i := 0; i < maxSegmentsPerWake; i++ {
|
||||
|
|
@ -1262,8 +1303,6 @@ func (e *Endpoint) probeSegmentLocked() {
|
|||
// if the connection should be terminated.
|
||||
//
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.rcv.ep.mu=e.mu
|
||||
// +checklocksalias:e.snd.ep.mu=e.mu
|
||||
func (e *Endpoint) handleSegmentLocked(s *segment) (cont bool, err tcpip.Error) {
|
||||
// Invoke the tcp probe if installed. The tcp probe function will update
|
||||
// the TCPEndpointState after the segment is processed.
|
||||
|
|
@ -1337,7 +1376,6 @@ func (e *Endpoint) handleSegmentLocked(s *segment) (cont bool, err tcpip.Error)
|
|||
// keepalive packets periodically when the connection is idle. If we don't hear
|
||||
// from the other side after a number of tries, we terminate the connection.
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.snd.ep.mu=e.mu
|
||||
func (e *Endpoint) keepaliveTimerExpired() tcpip.Error {
|
||||
userTimeout := e.userTimeout
|
||||
|
||||
|
|
@ -1379,7 +1417,6 @@ func (e *Endpoint) keepaliveTimerExpired() tcpip.Error {
|
|||
// whether it is enabled for this endpoint.
|
||||
//
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.snd.ep.mu=e.mu
|
||||
func (e *Endpoint) resetKeepaliveTimer(receivedData bool) {
|
||||
e.keepalive.Lock()
|
||||
defer e.keepalive.Unlock()
|
||||
|
|
@ -1442,7 +1479,6 @@ func (e *Endpoint) handshakeFailed(err tcpip.Error) {
|
|||
// handleTimeWaitSegments processes segments received during TIME_WAIT
|
||||
// state.
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.rcv.ep.mu=e.mu
|
||||
func (e *Endpoint) handleTimeWaitSegments() (extendTimeWait bool, reuseTW func()) {
|
||||
for i := 0; i < maxSegmentsPerWake; i++ {
|
||||
s := e.segmentQueue.dequeue()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue