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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue