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

@ -20,6 +20,7 @@ import (
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/sync"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
"github.com/sagernet/gvisor/pkg/tcpip/header"
)
@ -56,6 +57,9 @@ type PacketBufferOptions struct {
// OnRelease is a function to be run when the packet buffer is no longer
// referenced (released back to the pool).
OnRelease func()
// Mark is the mark value of this packet.
Mark uint32
}
// A PacketBuffer contains all the data of a network packet.
@ -154,6 +158,10 @@ type PacketBuffer struct {
// NICID is the ID of the last interface the network packet was handled at.
NICID tcpip.NICID
// InputNICID is the ID of the interface that the network packet
// was received on.
InputNICID tcpip.NICID
// RXChecksumValidated indicates that checksum verification may be
// safely skipped.
RXChecksumValidated bool
@ -161,6 +169,9 @@ type PacketBuffer struct {
// NetworkPacketInfo holds an incoming packet's network-layer information.
NetworkPacketInfo NetworkPacketInfo
// Mark is the mark value of this packet.
Mark uint32
tuple *tuple
// onRelease is a function to be run when the packet buffer is no longer
@ -182,6 +193,7 @@ func NewPacketBuffer(opts PacketBufferOptions) *PacketBuffer {
}
pk.NetworkPacketInfo.IsForwardedPacket = opts.IsForwardedPacket
pk.onRelease = opts.OnRelease
pk.Mark = opts.Mark
pk.InitRefs()
return pk
}
@ -380,6 +392,7 @@ func (pk *PacketBuffer) Clone() *PacketBuffer {
newPk.headers = pk.headers
newPk.Hash = pk.Hash
newPk.Owner = pk.Owner
newPk.Mark = pk.Mark
newPk.GSOOptions = pk.GSOOptions
newPk.EgressRoute = pk.EgressRoute
newPk.NetworkProtocolNumber = pk.NetworkProtocolNumber
@ -388,6 +401,7 @@ func (pk *PacketBuffer) Clone() *PacketBuffer {
newPk.TransportProtocolNumber = pk.TransportProtocolNumber
newPk.PktType = pk.PktType
newPk.NICID = pk.NICID
newPk.InputNICID = pk.InputNICID
newPk.RXChecksumValidated = pk.RXChecksumValidated
newPk.NetworkPacketInfo = pk.NetworkPacketInfo
newPk.tuple = pk.tuple
@ -431,6 +445,7 @@ func (pk *PacketBuffer) CloneToInbound() *PacketBuffer {
newPk.InitRefs()
// Treat unfilled header portion as reserved.
newPk.reserved = pk.AvailableHeaderBytes()
newPk.Mark = pk.Mark
newPk.tuple = pk.tuple
return newPk
}
@ -466,10 +481,78 @@ func (pk *PacketBuffer) DeepCopyForForwarding(reservedHeaderBytes int) *PacketBu
}
newPk.tuple = pk.tuple
newPk.Mark = pk.Mark
newPk.InputNICID = pk.InputNICID
return newPk
}
// IsConnTrackConfigured returns whether connection tracking is configured for this packet.
func (pk *PacketBuffer) IsConnTrackConfigured() bool {
return pk.tuple != nil && pk.tuple.conn != nil
}
// FillConnTrackInfo fills connection tracking information for the packet.
func (pk *PacketBuffer) FillConnTrackInfo(opts ConnTrackInfoOpts, info *ConnTrackInfo) bool {
t := pk.tuple
if t == nil || t.conn == nil {
return false
}
return t.conn.FillConnTrackInfo(opts, info)
}
// IsReplyPacket returns whether the packet is a reply packet.
func (pk *PacketBuffer) IsReplyPacket() bool {
t := pk.tuple
if t == nil {
return false
}
return t.reply
}
// IsNATConfigured returns whether NAT is configured for this packet.
func (pk *PacketBuffer) IsNATConfigured(nt NATType) bool {
if !pk.IsConnTrackConfigured() {
return false
}
return pk.tuple.conn.IsNATConfigured(nt)
}
// ConfigureNoopNAT configures a no-op NAT for the packet.
// Called if no NAT rules are configured for this packet.
func (pk *PacketBuffer) ConfigureNoopNAT(natType NATType) bool {
if !pk.IsConnTrackConfigured() {
return false
}
return pk.tuple.conn.ConfigureNoopNAT(pk, natType)
}
// ConfigureNAT configures NAT for the packet.
// Called if NAT rules are configured for this packet.
// Returns whether NAT was configured or not.
func (pk *PacketBuffer) ConfigureNAT(portsOrIdents PortOrIdentRange, natAddress tcpip.Address, natType NATType, changePort, changeAddress bool) bool {
if !pk.IsConnTrackConfigured() {
return false
}
return pk.tuple.conn.ConfigureNAT(portsOrIdents, natAddress, natType, changePort, changeAddress)
}
// ConfigureMasquerade configures NAT masquerade for the packet.
func (pk *PacketBuffer) ConfigureMasquerade(portsOrIdents PortOrIdentRange, route *Route, stk *Stack, changePort bool) bool {
if !pk.IsConnTrackConfigured() {
return false
}
return pk.tuple.conn.configureMasquerade(pk, route, stk, portsOrIdents, changePort)
}
// FinalizeConnTrack finalizes the connection tracking state for the packet.
func (pk *PacketBuffer) FinalizeConnTrack() bool {
if pk.tuple == nil || pk.tuple.conn == nil {
return true
}
return pk.tuple.conn.finalize()
}
// headerInfo stores metadata about a header in a packet.
//
// +stateify savable
@ -768,3 +851,312 @@ func BufferSince(h PacketHeader) buffer.Buffer {
clone.TrimFront(int64(offset))
return clone
}
// ExperimentOptionValue returns the experiment option value from the packet
// and a bool indicating whether an experiment option value was found.
func (pk *PacketBuffer) ExperimentOptionValue() (uint16, bool) {
switch pk.NetworkProtocolNumber {
case header.IPv4ProtocolNumber:
h := header.IPv4(pk.NetworkHeader().Slice())
opts := h.Options()
iter := opts.MakeIterator()
for {
opt, done, err := iter.Next()
if err != nil {
return 0, false
}
if done {
return 0, false
}
if opt.Type() == header.IPv4OptionExperimentType {
return opt.(*header.IPv4OptionExperiment).Value(), true
}
}
case header.IPv6ProtocolNumber:
h := header.IPv6(pk.NetworkHeader().Slice())
v := pk.NetworkHeader().View()
if v != nil {
v.TrimFront(header.IPv6MinimumSize)
}
buf := buffer.MakeWithView(v)
buf.Append(pk.TransportHeader().View())
dataBuf := pk.Data().ToBuffer()
buf.Merge(&dataBuf)
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(h.NextHeader()), buf)
for {
hdr, done, err := it.Next()
if done || err != nil {
break
}
if h, ok := hdr.(header.IPv6ExperimentExtHdr); ok {
hdr.Release()
return h.Value, true
}
hdr.Release()
}
default:
panic(fmt.Sprintf("Unexpected network protocol number %d", pk.NetworkProtocolNumber))
}
return 0, false
}
// GetEmbeddedNetAndTransHeaders returns the network and transport headers of the
// packet.
func (pk *PacketBuffer) GetEmbeddedNetAndTransHeaders(netHdrLength int, getNetAndTransHdr netAndTransHeadersFunc, transProto tcpip.TransportProtocolNumber) (header.Network, header.ChecksummableTransport, bool) {
switch transProto {
case header.TCPProtocolNumber:
if netAndTransHeader, ok := pk.Data().PullUp(netHdrLength + header.TCPMinimumSize); ok {
netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.TCPMinimumSize)
return netHeader, header.TCP(transHeaderBytes), true
}
case header.UDPProtocolNumber:
if netAndTransHeader, ok := pk.Data().PullUp(netHdrLength + header.UDPMinimumSize); ok {
netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.UDPMinimumSize)
return netHeader, header.UDP(transHeaderBytes), true
}
}
return nil, nil, false
}
// GetHeaders returns the network and transport headers of the packet.
func (pk *PacketBuffer) GetHeaders() (netHdr header.Network, transHdr header.Transport, isICMPError bool, ok bool) {
switch pk.TransportProtocolNumber {
case header.TCPProtocolNumber:
if tcpHeader := header.TCP(pk.TransportHeader().Slice()); len(tcpHeader) >= header.TCPMinimumSize {
return pk.Network(), tcpHeader, false, true
}
return nil, nil, false, false
case header.UDPProtocolNumber:
if udpHeader := header.UDP(pk.TransportHeader().Slice()); len(udpHeader) >= header.UDPMinimumSize {
return pk.Network(), udpHeader, false, true
}
return nil, nil, false, false
case header.ICMPv4ProtocolNumber:
icmpHeader := header.ICMPv4(pk.TransportHeader().Slice())
if len(icmpHeader) < header.ICMPv4MinimumSize {
return nil, nil, false, false
}
switch icmpType := icmpHeader.Type(); icmpType {
case header.ICMPv4Echo, header.ICMPv4EchoReply:
return pk.Network(), icmpHeader, false, true
case header.ICMPv4DstUnreachable, header.ICMPv4TimeExceeded, header.ICMPv4ParamProblem:
default:
return nil, nil, false, false
}
h, ok := pk.Data().PullUp(header.IPv4MinimumSize)
if !ok {
return nil, nil, false, false
}
hdrLength := int(header.IPv4(h).HeaderLength())
// Pull up the full IPv4 header which might include options.
if hdrLength > header.IPv4MinimumSize {
// TODO(https://gvisor.dev/issue/6765): Handle IPv4
// options.
h, ok = pk.Data().PullUp(hdrLength)
if !ok {
return nil, nil, false, false
}
}
if netHdr, transHdr, ok := pk.GetEmbeddedNetAndTransHeaders(hdrLength, v4NetAndTransHdr, tcpip.TransportProtocolNumber(header.IPv4(h).Protocol())); ok {
return netHdr, transHdr, true, true
}
return nil, nil, false, false
case header.ICMPv6ProtocolNumber:
icmpHeader := header.ICMPv6(pk.TransportHeader().Slice())
if len(icmpHeader) < header.ICMPv6MinimumSize {
return nil, nil, false, false
}
switch icmpType := icmpHeader.Type(); icmpType {
case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
return pk.Network(), icmpHeader, false, true
case header.ICMPv6DstUnreachable, header.ICMPv6PacketTooBig, header.ICMPv6TimeExceeded, header.ICMPv6ParamProblem:
default:
return nil, nil, false, false
}
h, ok := pk.Data().PullUp(header.IPv6MinimumSize)
if !ok {
return nil, nil, false, false
}
// We do not support extension headers in ICMP errors so the next header
// in the IPv6 packet should be a tracked protocol if we reach this point.
//
// TODO(https://gvisor.dev/issue/6789): Support extension headers.
transProto, _ := header.IPv6(h).TryParseTransportProtocol()
if netHdr, transHdr, ok := pk.GetEmbeddedNetAndTransHeaders(header.IPv6MinimumSize, v6NetAndTransHdr, transProto); ok {
return netHdr, transHdr, true, true
}
return nil, nil, false, false
default:
return nil, nil, false, false
}
}
// UpdateHeaders updates the headers of the packet with the new port and address.
func UpdateHeaders(n header.Network, t header.Transport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPortOrIdent uint16, newAddr tcpip.Address) {
switch t := t.(type) {
case header.ChecksummableTransport:
if updateSRCFields {
if fullChecksum {
t.SetSourcePortWithChecksumUpdate(newPortOrIdent)
} else {
t.SetSourcePort(newPortOrIdent)
}
} else {
if fullChecksum {
t.SetDestinationPortWithChecksumUpdate(newPortOrIdent)
} else {
t.SetDestinationPort(newPortOrIdent)
}
}
if updatePseudoHeader {
var oldAddr tcpip.Address
if updateSRCFields {
oldAddr = n.SourceAddress()
} else {
oldAddr = n.DestinationAddress()
}
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr, fullChecksum)
}
case header.ICMPv4:
switch icmpType := t.Type(); icmpType {
case header.ICMPv4Echo:
if updateSRCFields {
t.SetIdentWithChecksumUpdate(newPortOrIdent)
}
case header.ICMPv4EchoReply:
if !updateSRCFields {
t.SetIdentWithChecksumUpdate(newPortOrIdent)
}
default:
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
}
case header.ICMPv6:
switch icmpType := t.Type(); icmpType {
case header.ICMPv6EchoRequest:
if updateSRCFields {
t.SetIdentWithChecksumUpdate(newPortOrIdent)
}
case header.ICMPv6EchoReply:
if !updateSRCFields {
t.SetIdentWithChecksumUpdate(newPortOrIdent)
}
default:
panic(fmt.Sprintf("unexpected ICMPv6 type = %d", icmpType))
}
var oldAddr tcpip.Address
if updateSRCFields {
oldAddr = n.SourceAddress()
} else {
oldAddr = n.DestinationAddress()
}
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr)
default:
panic(fmt.Sprintf("unhandled transport = %#v", t))
}
if checksummableNetHeader, ok := n.(header.ChecksummableNetwork); ok {
if updateSRCFields {
checksummableNetHeader.SetSourceAddressWithChecksumUpdate(newAddr)
} else {
checksummableNetHeader.SetDestinationAddressWithChecksumUpdate(newAddr)
}
} else if updateSRCFields {
n.SetSourceAddress(newAddr)
} else {
n.SetDestinationAddress(newAddr)
}
}
// CalculateTransportChecksum calculates the transport-layer checksum of the
// packet.
// TODO: b/521901282 - Verify with GSO.
func (pk *PacketBuffer) CalculateTransportChecksum() {
netHdr, transHdr, isICMPError, ok := pk.GetHeaders()
if isICMPError {
// Skip ICMP errors because GetHeaders() returns inner headers, but pk.Data()
// contains the outer payload (including inner IP header), which would
// corrupt the checksum calculation if used as the transport payload.
// Inner headers are already incrementally updated by NAT if needed.
// This aligns with Linux, which also relies on incremental updates for
// inner headers and does not perform full recalculation from scratch.
return
}
if !ok {
// Try to parse headers from Data if not set (e.g., forwarded packet).
if pk.NetworkProtocolNumber == 0 {
return
}
netHdr = pk.Network()
transProto := netHdr.TransportProtocol()
var headerSize int
switch transProto {
case header.TCPProtocolNumber:
// Peek at minimum TCP header to find data offset (which includes options).
b, ok := pk.Data().PullUp(header.TCPMinimumSize)
if !ok {
return
}
tcp := header.TCP(b)
headerSize = int(tcp.DataOffset())
if headerSize < header.TCPMinimumSize {
return
}
case header.UDPProtocolNumber:
headerSize = header.UDPMinimumSize
default:
return
}
// Consume the transport header.
if _, ok := pk.TransportHeader().Consume(headerSize); !ok {
return
}
pk.TransportProtocolNumber = transProto
// Refresh headers.
netHdr, transHdr, isICMPError, ok = pk.GetHeaders()
if !ok || isICMPError {
return
}
}
var xsum uint16
switch t := transHdr.(type) {
case header.TCP:
src := netHdr.SourceAddress()
dst := netHdr.DestinationAddress()
proto := netHdr.TransportProtocol()
totalLen := uint16(len(t) + pk.Data().Size())
xsum = header.PseudoHeaderChecksum(proto, src, dst, totalLen)
xsum = checksum.Combine(xsum, pk.Data().Checksum())
t.SetChecksum(0)
t.SetChecksum(^t.CalculateChecksum(xsum))
case header.UDP:
src := netHdr.SourceAddress()
dst := netHdr.DestinationAddress()
proto := netHdr.TransportProtocol()
totalLen := uint16(len(t) + pk.Data().Size())
xsum = header.PseudoHeaderChecksum(proto, src, dst, totalLen)
xsum = checksum.Combine(xsum, pk.Data().Checksum())
t.SetChecksum(0)
csum := ^t.CalculateChecksum(xsum)
// udp csum RFC 768.
if csum == 0 {
csum = 0xFFFF
}
t.SetChecksum(csum)
}
}