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
|
|
@ -66,13 +66,16 @@ type Endpoint struct {
|
|||
// TODO(https://gvisor.dev/issue/6389): Use different fields for IPv4/IPv6.
|
||||
// +checklocks:mu
|
||||
multicastAddr tcpip.Address
|
||||
// TODO(https://gvisor.dev/issue/6389): Use different fields for IPv4/IPv6.
|
||||
// +checklocks:mu
|
||||
multicastNICID tcpip.NICID
|
||||
// +checklocks:mu
|
||||
ipv6MulticastNICID tcpip.NICID
|
||||
// +checklocks:mu
|
||||
ipv4TOS uint8
|
||||
// +checklocks:mu
|
||||
ipv6TClass uint8
|
||||
// +checklocks:mu
|
||||
pmtud tcpip.PMTUDStrategy
|
||||
|
||||
// Lock ordering: mu > infoMu.
|
||||
infoMu sync.RWMutex `state:"nosave"`
|
||||
|
|
@ -181,7 +184,11 @@ func (e *Endpoint) Close() {
|
|||
}
|
||||
|
||||
for mem := range e.multicastMemberships {
|
||||
e.stack.LeaveGroup(e.netProto, mem.nicID, mem.multicastAddr)
|
||||
proto, err := e.multicastNetProto(mem.multicastAddr)
|
||||
if err != nil {
|
||||
panic("non multicast address in an existing membership")
|
||||
}
|
||||
e.stack.LeaveGroup(proto, mem.nicID, mem.multicastAddr)
|
||||
}
|
||||
e.multicastMemberships = nil
|
||||
|
||||
|
|
@ -229,6 +236,7 @@ type WriteContext struct {
|
|||
route *stack.Route
|
||||
ttl uint8
|
||||
tos uint8
|
||||
df bool
|
||||
}
|
||||
|
||||
func (c *WriteContext) MTU() uint32 {
|
||||
|
|
@ -274,33 +282,33 @@ func (c *WriteContext) TryNewPacketBuffer(reserveHdrBytes int, data buffer.Buffe
|
|||
if !e.hasSendSpaceRLocked() {
|
||||
return nil
|
||||
}
|
||||
return c.newPacketBufferLocked(reserveHdrBytes, data)
|
||||
|
||||
mark := e.ops.GetMark()
|
||||
return c.newPacketBufferLocked(reserveHdrBytes, data, mark)
|
||||
}
|
||||
|
||||
// TryNewPacketBufferFromPayloader returns a new packet buffer iff the endpoint's send buffer
|
||||
// TryNewPacketBufferFromPayloader returns a new packet buffer if the endpoint's send buffer
|
||||
// is not full. Otherwise, data from `payloader` isn't read.
|
||||
//
|
||||
// If this method returns nil, the caller should wait for the endpoint to become
|
||||
// writable.
|
||||
func (c *WriteContext) TryNewPacketBufferFromPayloader(reserveHdrBytes int, payloader tcpip.Payloader) *stack.PacketBuffer {
|
||||
func (c *WriteContext) TryNewPacketBufferFromPayloader(reserveHdrBytes int, payloader tcpip.Payloader) (*stack.PacketBuffer, tcpip.Error) {
|
||||
e := c.e
|
||||
|
||||
e.sendBufferSizeInUseMu.Lock()
|
||||
defer e.sendBufferSizeInUseMu.Unlock()
|
||||
|
||||
if !e.hasSendSpaceRLocked() {
|
||||
return nil
|
||||
return nil, &tcpip.ErrWouldBlock{}
|
||||
}
|
||||
var data buffer.Buffer
|
||||
if _, err := data.WriteFromReader(payloader, int64(payloader.Len())); err != nil {
|
||||
data.Release()
|
||||
return nil
|
||||
return nil, &tcpip.ErrBadBuffer{}
|
||||
}
|
||||
return c.newPacketBufferLocked(reserveHdrBytes, data)
|
||||
mark := e.ops.GetMark()
|
||||
return c.newPacketBufferLocked(reserveHdrBytes, data, mark), nil
|
||||
}
|
||||
|
||||
// +checklocks:c.e.sendBufferSizeInUseMu
|
||||
func (c *WriteContext) newPacketBufferLocked(reserveHdrBytes int, data buffer.Buffer) *stack.PacketBuffer {
|
||||
func (c *WriteContext) newPacketBufferLocked(reserveHdrBytes int, data buffer.Buffer, mark uint32) *stack.PacketBuffer {
|
||||
e := c.e
|
||||
// Note that we allow oversubscription - if there is any space at all in the
|
||||
// send buffer, we accept the full packet which may be larger than the space
|
||||
|
|
@ -323,6 +331,7 @@ func (c *WriteContext) newPacketBufferLocked(reserveHdrBytes int, data buffer.Bu
|
|||
return stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: reserveHdrBytes,
|
||||
Payload: data,
|
||||
Mark: mark,
|
||||
OnRelease: func() {
|
||||
e.sendBufferSizeInUseMu.Lock()
|
||||
if got := e.sendBufferSizeInUse; got < pktSize {
|
||||
|
|
@ -360,6 +369,7 @@ func (c *WriteContext) WritePacket(pkt *stack.PacketBuffer, headerIncluded bool)
|
|||
Protocol: c.e.transProto,
|
||||
TTL: c.ttl,
|
||||
TOS: c.tos,
|
||||
DF: c.df,
|
||||
ExperimentOptionValue: expOptVal,
|
||||
}, pkt)
|
||||
|
||||
|
|
@ -433,8 +443,8 @@ func (e *Endpoint) AcquireContextForWrite(opts tcpip.WriteOptions) (WriteContext
|
|||
route := e.connectedRoute
|
||||
to := opts.To
|
||||
info := e.Info()
|
||||
switch {
|
||||
case to == nil:
|
||||
switch to {
|
||||
case nil:
|
||||
// If the user doesn't specify a destination, they should have
|
||||
// connected to another address.
|
||||
if e.State() != transport.DatagramEndpointStateConnected {
|
||||
|
|
@ -566,11 +576,28 @@ func (e *Endpoint) AcquireContextForWrite(opts tcpip.WriteOptions) (WriteContext
|
|||
panic(fmt.Sprintf("invalid protocol number = %d", netProto))
|
||||
}
|
||||
|
||||
// Set the DF (Don't Fragment) bit based on the PMTUD strategy,
|
||||
// matching TCP behavior in connect.go.
|
||||
// Note: In gVisor, WANT and DO are treated identically (both set DF).
|
||||
// Linux kernel differentiates them (WANT allows local fragmentation,
|
||||
// DO returns EMSGSIZE), but gVisor's IPv4 layer always allows local
|
||||
// fragmentation for locally-generated packets regardless of DF
|
||||
// (see gvisor.dev/issue/5919).
|
||||
//
|
||||
// PROBE also sets DF, matching Linux ip_dont_fragment(). In Linux,
|
||||
// PROBE differs from DO only in that it ignores incoming ICMP
|
||||
// "Fragmentation Needed" messages (i.e. does not update the cached
|
||||
// route PMTU). Since gVisor does not implement ICMP-based PMTU
|
||||
// feedback for transport sockets, PROBE and DO are functionally
|
||||
// equivalent here.
|
||||
df := e.pmtud == tcpip.PMTUDiscoveryWant || e.pmtud == tcpip.PMTUDiscoveryDo || e.pmtud == tcpip.PMTUDiscoveryProbe
|
||||
|
||||
return WriteContext{
|
||||
e: e,
|
||||
route: route,
|
||||
ttl: ttl,
|
||||
tos: tos,
|
||||
df: df,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -613,7 +640,7 @@ func (e *Endpoint) connectRouteRLocked(nicID tcpip.NICID, localAddr tcpip.Addres
|
|||
localAddr = tcpip.Address{}
|
||||
}
|
||||
|
||||
if header.IsV4MulticastAddress(addr.Addr) || header.IsV6MulticastAddress(addr.Addr) {
|
||||
if header.IsV4MulticastAddress(addr.Addr) {
|
||||
if nicID == 0 {
|
||||
nicID = e.multicastNICID
|
||||
}
|
||||
|
|
@ -621,6 +648,9 @@ func (e *Endpoint) connectRouteRLocked(nicID tcpip.NICID, localAddr tcpip.Addres
|
|||
localAddr = e.multicastAddr
|
||||
}
|
||||
}
|
||||
if header.IsV6MulticastAddress(addr.Addr) && nicID == 0 {
|
||||
nicID = e.ipv6MulticastNICID
|
||||
}
|
||||
}
|
||||
|
||||
// Find a route to the desired destination.
|
||||
|
|
@ -835,9 +865,18 @@ func (e *Endpoint) GetRemoteAddress() (tcpip.FullAddress, bool) {
|
|||
func (e *Endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
|
||||
switch opt {
|
||||
case tcpip.MTUDiscoverOption:
|
||||
// Return not supported if the value is not disabling path
|
||||
// MTU discovery.
|
||||
if tcpip.PMTUDStrategy(v) != tcpip.PMTUDiscoveryDont {
|
||||
// Store PMTU discovery settings. The DF bit on outgoing
|
||||
// packets is set accordingly in AcquireContextForWrite.
|
||||
// PROBE is accepted alongside DO/WANT/DONT. In Linux,
|
||||
// PROBE sets DF but ignores ICMP-based PMTU updates;
|
||||
// since gVisor lacks ICMP PMTU feedback, it behaves
|
||||
// identically to DO.
|
||||
switch tcpip.PMTUDStrategy(v) {
|
||||
case tcpip.PMTUDiscoveryWant, tcpip.PMTUDiscoveryDont, tcpip.PMTUDiscoveryDo, tcpip.PMTUDiscoveryProbe:
|
||||
e.mu.Lock()
|
||||
e.pmtud = tcpip.PMTUDStrategy(v)
|
||||
e.mu.Unlock()
|
||||
default:
|
||||
return &tcpip.ErrNotSupported{}
|
||||
}
|
||||
|
||||
|
|
@ -865,6 +904,18 @@ func (e *Endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
|
|||
e.mu.Lock()
|
||||
e.ipv6TClass = uint8(v)
|
||||
e.mu.Unlock()
|
||||
|
||||
case tcpip.IPv6MulticastInterfaceOption:
|
||||
if v != 0 && !e.stack.CheckNIC(tcpip.NICID(v)) {
|
||||
return &tcpip.ErrUnknownNICID{}
|
||||
}
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
nic := tcpip.NICID(v)
|
||||
if info := e.Info(); info.BindNICID != 0 && info.BindNICID != nic {
|
||||
return &tcpip.ErrInvalidEndpointState{}
|
||||
}
|
||||
e.ipv6MulticastNICID = nic
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -874,8 +925,10 @@ func (e *Endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
|
|||
func (e *Endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
|
||||
switch opt {
|
||||
case tcpip.MTUDiscoverOption:
|
||||
// The only supported setting is path MTU discovery disabled.
|
||||
return int(tcpip.PMTUDiscoveryDont), nil
|
||||
e.mu.Lock()
|
||||
v := int(e.pmtud)
|
||||
e.mu.Unlock()
|
||||
return v, nil
|
||||
|
||||
case tcpip.MulticastTTLOption:
|
||||
e.mu.Lock()
|
||||
|
|
@ -907,11 +960,30 @@ func (e *Endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
|
|||
e.mu.RUnlock()
|
||||
return v, nil
|
||||
|
||||
case tcpip.IPv6MulticastInterfaceOption:
|
||||
e.mu.RLock()
|
||||
v := int(e.ipv6MulticastNICID)
|
||||
e.mu.RUnlock()
|
||||
return v, nil
|
||||
|
||||
default:
|
||||
return -1, &tcpip.ErrUnknownProtocolOption{}
|
||||
}
|
||||
}
|
||||
|
||||
// multicastNetProto returns the network protocol of a given multicast address.
|
||||
// Returns an error if the address is not a multicast address.
|
||||
func (e *Endpoint) multicastNetProto(addr tcpip.Address) (tcpip.NetworkProtocolNumber, tcpip.Error) {
|
||||
switch {
|
||||
case header.IsV4MulticastAddress(addr):
|
||||
return header.IPv4ProtocolNumber, nil
|
||||
case header.IsV6MulticastAddress(addr):
|
||||
return header.IPv6ProtocolNumber, nil
|
||||
default:
|
||||
return 0, &tcpip.ErrInvalidOptionValue{}
|
||||
}
|
||||
}
|
||||
|
||||
// SetSockOpt sets the socket option.
|
||||
func (e *Endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error {
|
||||
switch v := opt.(type) {
|
||||
|
|
@ -952,21 +1024,23 @@ func (e *Endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error {
|
|||
e.multicastAddr = addr
|
||||
|
||||
case *tcpip.AddMembershipOption:
|
||||
if !(header.IsV4MulticastAddress(v.MulticastAddr) && e.netProto == header.IPv4ProtocolNumber) && !(header.IsV6MulticastAddress(v.MulticastAddr) && e.netProto == header.IPv6ProtocolNumber) {
|
||||
return &tcpip.ErrInvalidOptionValue{}
|
||||
// Allowing IP_ADD_MEMBERSHIP on an ipv6 socket matches Linux behavior:
|
||||
// https://github.com/torvalds/linux/blob/cec1e6e5d1a/net/ipv6/ipv6_sockglue.c#L964
|
||||
proto, err := e.multicastNetProto(v.MulticastAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nicID := v.NIC
|
||||
|
||||
if v.InterfaceAddr.Unspecified() {
|
||||
if nicID == 0 {
|
||||
if r, err := e.stack.FindRoute(0, tcpip.Address{}, v.MulticastAddr, e.netProto, false /* multicastLoop */); err == nil {
|
||||
if r, err := e.stack.FindRoute(0, tcpip.Address{}, v.MulticastAddr, proto, false /* multicastLoop */); err == nil {
|
||||
nicID = r.NICID()
|
||||
r.Release()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nicID = e.stack.CheckLocalAddress(nicID, e.netProto, v.InterfaceAddr)
|
||||
nicID = e.stack.CheckLocalAddress(nicID, proto, v.InterfaceAddr)
|
||||
}
|
||||
if nicID == 0 {
|
||||
return &tcpip.ErrUnknownDevice{}
|
||||
|
|
@ -981,27 +1055,28 @@ func (e *Endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error {
|
|||
return &tcpip.ErrPortInUse{}
|
||||
}
|
||||
|
||||
if err := e.stack.JoinGroup(e.netProto, nicID, v.MulticastAddr); err != nil {
|
||||
if err := e.stack.JoinGroup(proto, nicID, v.MulticastAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.multicastMemberships[memToInsert] = struct{}{}
|
||||
|
||||
case *tcpip.RemoveMembershipOption:
|
||||
if !(header.IsV4MulticastAddress(v.MulticastAddr) && e.netProto == header.IPv4ProtocolNumber) && !(header.IsV6MulticastAddress(v.MulticastAddr) && e.netProto == header.IPv6ProtocolNumber) {
|
||||
return &tcpip.ErrInvalidOptionValue{}
|
||||
proto, err := e.multicastNetProto(v.MulticastAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nicID := v.NIC
|
||||
if v.InterfaceAddr.Unspecified() {
|
||||
if nicID == 0 {
|
||||
if r, err := e.stack.FindRoute(0, tcpip.Address{}, v.MulticastAddr, e.netProto, false /* multicastLoop */); err == nil {
|
||||
if r, err := e.stack.FindRoute(0, tcpip.Address{}, v.MulticastAddr, proto, false /* multicastLoop */); err == nil {
|
||||
nicID = r.NICID()
|
||||
r.Release()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nicID = e.stack.CheckLocalAddress(nicID, e.netProto, v.InterfaceAddr)
|
||||
nicID = e.stack.CheckLocalAddress(nicID, proto, v.InterfaceAddr)
|
||||
}
|
||||
if nicID == 0 {
|
||||
return &tcpip.ErrUnknownDevice{}
|
||||
|
|
@ -1016,7 +1091,7 @@ func (e *Endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error {
|
|||
return &tcpip.ErrBadLocalAddress{}
|
||||
}
|
||||
|
||||
if err := e.stack.LeaveGroup(e.netProto, nicID, v.MulticastAddr); err != nil {
|
||||
if err := e.stack.LeaveGroup(proto, nicID, v.MulticastAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue