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
|
|
@ -16,11 +16,13 @@ package ipv4
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header/parse"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
|
|
@ -343,10 +345,47 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
|
|||
switch h.Type() {
|
||||
case header.ICMPv4Echo:
|
||||
received.echoRequest.Increment()
|
||||
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
|
||||
|
||||
// DeliverTransportPacket may modify pkt so don't use it beyond
|
||||
// this point. Make a deep copy of the data before pkt gets sent as we will
|
||||
// be modifying fields. Both the ICMP header (with its type modified to
|
||||
// EchoReply) and payload are reused in the reply packet.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/4399): The copy may not be needed if there are no
|
||||
// waiting endpoints. Consider moving responsibility for doing the copy to
|
||||
// DeliverTransportPacket so that is is only done when needed.
|
||||
replyData := stack.PayloadSince(pkt.TransportHeader())
|
||||
defer replyData.Release()
|
||||
localAddressTemporary := pkt.NetworkPacketInfo.LocalAddressTemporary
|
||||
localAddressBroadcast := pkt.NetworkPacketInfo.LocalAddressBroadcast
|
||||
|
||||
// It's possible that a raw socket or per-stack default handler expects
|
||||
// to receive this packet.
|
||||
defaultHandlerHandled := false
|
||||
if dispatcher, ok := e.dispatcher.(stack.TransportDispatcherWithDefaultHandlerResult); ok {
|
||||
_, defaultHandlerHandled = dispatcher.DeliverTransportPacketWithDefaultHandlerResult(header.ICMPv4ProtocolNumber, pkt)
|
||||
} else {
|
||||
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
|
||||
}
|
||||
pkt = nil
|
||||
|
||||
// Skip the built-in ICMP echo reply if the request was consumed by a
|
||||
// per-stack default handler. Also preserve the IPv4 behavior for
|
||||
// temporary local addresses: the packet is delivered above, but the
|
||||
// stack does not synthesize an echo reply for it.
|
||||
if defaultHandlerHandled || localAddressTemporary {
|
||||
return
|
||||
}
|
||||
|
||||
e.sendICMPEchoReply(replyData, iph, newOptions, localAddressBroadcast)
|
||||
|
||||
case header.ICMPv4EchoReply:
|
||||
received.echoReply.Increment()
|
||||
|
||||
// ICMP sockets expect the ICMP header to be present, so we don't consume
|
||||
// the ICMP header.
|
||||
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
|
||||
|
||||
case header.ICMPv4DstUnreachable:
|
||||
received.dstUnreachable.Increment()
|
||||
|
||||
|
|
@ -411,6 +450,98 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
|
|||
}
|
||||
}
|
||||
|
||||
func (e *endpoint) sendICMPEchoReply(replyData *buffer.View, ipHdr header.IPv4, newOptions header.IPv4Options, localAddressBroadcast bool) {
|
||||
sent := e.stats.icmp.packetsSent
|
||||
if !e.protocol.allowICMPReply(header.ICMPv4EchoReply, header.ICMPv4UnusedCode) {
|
||||
sent.rateLimited.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
// As per RFC 1122 section 3.2.1.3, when a host sends any datagram, the IP
|
||||
// source address MUST be one of its own IP addresses (but not a broadcast
|
||||
// or multicast address).
|
||||
localAddr := ipHdr.DestinationAddress()
|
||||
if localAddressBroadcast || header.IsV4MulticastAddress(localAddr) {
|
||||
localAddr = tcpip.Address{}
|
||||
}
|
||||
|
||||
r, err := e.protocol.stack.FindRoute(e.nic.ID(), localAddr, ipHdr.SourceAddress(), ProtocolNumber, false /* multicastLoop */)
|
||||
if err != nil {
|
||||
// If we cannot find a route to the destination, silently drop the packet.
|
||||
return
|
||||
}
|
||||
defer r.Release()
|
||||
|
||||
outgoingEP, ok := e.protocol.getEndpointForNIC(r.NICID())
|
||||
if !ok {
|
||||
// The outgoing NIC went away.
|
||||
sent.dropped.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
// Because IP and ICMP are so closely intertwined, we need to handcraft our
|
||||
// IP header to be able to follow RFC 792. The wording on page 13 is as
|
||||
// follows:
|
||||
// IP Fields:
|
||||
// Addresses
|
||||
// The address of the source in an echo message will be the
|
||||
// destination of the echo reply message. To form an echo reply
|
||||
// message, the source and destination addresses are simply reversed,
|
||||
// the type code changed to 0, and the checksum recomputed.
|
||||
//
|
||||
// This was interpreted by early implementors to mean that all options must
|
||||
// be copied from the echo request IP header to the echo reply IP header
|
||||
// and this behaviour is still relied upon by some applications.
|
||||
//
|
||||
// Create a copy of the IP header we received, options and all, and change
|
||||
// The fields we need to alter.
|
||||
//
|
||||
// We need to produce the entire packet in the data segment in order to
|
||||
// use WriteHeaderIncludedPacket(). WriteHeaderIncludedPacket sets the
|
||||
// total length and the header checksum so we don't need to set those here.
|
||||
//
|
||||
// Take the base of the incoming request IP header but replace the options.
|
||||
replyHeaderLength := uint8(header.IPv4MinimumSize + len(newOptions))
|
||||
replyIPHdrView := buffer.NewView(int(replyHeaderLength))
|
||||
replyIPHdrView.Write(ipHdr[:header.IPv4MinimumSize])
|
||||
replyIPHdrView.Write(newOptions)
|
||||
replyIPHdr := header.IPv4(replyIPHdrView.AsSlice())
|
||||
replyIPHdr.SetHeaderLength(replyHeaderLength)
|
||||
replyIPHdr.SetSourceAddress(r.LocalAddress())
|
||||
replyIPHdr.SetDestinationAddress(r.RemoteAddress())
|
||||
replyIPHdr.SetTTL(r.DefaultTTL())
|
||||
replyIPHdr.SetTotalLength(uint16(len(replyIPHdr) + len(replyData.AsSlice())))
|
||||
replyIPHdr.SetChecksum(0)
|
||||
replyIPHdr.SetChecksum(^replyIPHdr.CalculateChecksum())
|
||||
|
||||
replyICMPHdr := header.ICMPv4(replyData.AsSlice())
|
||||
replyICMPHdr.SetType(header.ICMPv4EchoReply)
|
||||
replyICMPHdr.SetChecksum(0)
|
||||
replyICMPHdr.SetChecksum(^checksum.Checksum(replyData.AsSlice(), 0))
|
||||
|
||||
replyBuf := buffer.MakeWithView(replyIPHdrView)
|
||||
replyBuf.Append(replyData.Clone())
|
||||
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: int(r.MaxHeaderLength()),
|
||||
Payload: replyBuf,
|
||||
})
|
||||
defer replyPkt.DecRef()
|
||||
// Populate the network/transport headers in the packet buffer so the
|
||||
// ICMP packet goes through IPTables.
|
||||
if ok := parse.IPv4(replyPkt); !ok {
|
||||
panic("expected to parse IPv4 header we just created")
|
||||
}
|
||||
if ok := parse.ICMPv4(replyPkt); !ok {
|
||||
panic("expected to parse ICMPv4 header we just created")
|
||||
}
|
||||
|
||||
if err := outgoingEP.writePacket(r, replyPkt); err != nil {
|
||||
sent.dropped.Increment()
|
||||
return
|
||||
}
|
||||
sent.echoReply.Increment()
|
||||
}
|
||||
|
||||
// ======= ICMP Error packet generation =========
|
||||
|
||||
// icmpReason is a marker interface for IPv4 specific ICMP errors.
|
||||
|
|
@ -479,7 +610,12 @@ func (*icmpReasonNetworkUnreachable) isICMPReason() {}
|
|||
// icmpReasonFragmentationNeeded is an error where a packet requires
|
||||
// fragmentation while also having the Don't Fragment flag set, as per RFC 792
|
||||
// page 3, Destination Unreachable Message.
|
||||
type icmpReasonFragmentationNeeded struct{}
|
||||
type icmpReasonFragmentationNeeded struct {
|
||||
// mtu is the MTU of the next-hop link. Per RFC 1191 §4, this value
|
||||
// must be included in the ICMP Fragmentation Needed message so the
|
||||
// sender can update its path MTU cache.
|
||||
mtu uint32
|
||||
}
|
||||
|
||||
func (*icmpReasonFragmentationNeeded) isICMPReason() {}
|
||||
|
||||
|
|
@ -584,30 +720,36 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer, deliv
|
|||
}
|
||||
|
||||
sent := netEP.stats.icmp.packetsSent
|
||||
icmpType, icmpCode, counter, pointer := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte) {
|
||||
icmpType, icmpCode, counter, pointer, nextHopMTU := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte, uint16) {
|
||||
switch reason := reason.(type) {
|
||||
case *icmpReasonNetworkProhibited:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4NetProhibited, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4NetProhibited, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonHostProhibited:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4HostProhibited, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4HostProhibited, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonAdministrativelyProhibited:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4AdminProhibited, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4AdminProhibited, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonPortUnreachable:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonProtoUnreachable:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonNetworkUnreachable:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonHostUnreachable:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonFragmentationNeeded:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0
|
||||
// Per RFC 1191 §4, include the next-hop MTU in the ICMP message.
|
||||
// Cap at MaxUint16 since the field is 16 bits wide.
|
||||
mtu := reason.mtu
|
||||
if mtu > math.MaxUint16 {
|
||||
mtu = math.MaxUint16
|
||||
}
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0, uint16(mtu)
|
||||
case *icmpReasonTTLExceeded:
|
||||
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0
|
||||
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0, 0
|
||||
case *icmpReasonReassemblyTimeout:
|
||||
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0
|
||||
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0, 0
|
||||
case *icmpReasonParamProblem:
|
||||
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer
|
||||
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer, 0
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
|
||||
}
|
||||
|
|
@ -676,6 +818,7 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer, deliv
|
|||
icmpHdr.SetCode(icmpCode)
|
||||
icmpHdr.SetType(icmpType)
|
||||
icmpHdr.SetPointer(pointer)
|
||||
icmpHdr.SetMTU(nextHopMTU)
|
||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, icmpPkt.Data().Checksum()))
|
||||
|
||||
if err := route.WritePacket(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue