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
|
|
@ -302,6 +302,7 @@ type PacketFragmenter struct {
|
|||
fragmentCount int
|
||||
currentFragment int
|
||||
fragmentOffset int
|
||||
mark uint32
|
||||
}
|
||||
|
||||
// MakePacketFragmenter prepares the struct needed for packet fragmentation.
|
||||
|
|
@ -332,6 +333,7 @@ func MakePacketFragmenter(pkt *stack.PacketBuffer, fragmentPayloadLen uint32, re
|
|||
reserve: reserve,
|
||||
fragmentPayloadLen: int(fragmentPayloadLen),
|
||||
fragmentCount: int(fragmentCount),
|
||||
mark: pkt.Mark,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -351,6 +353,7 @@ func (pf *PacketFragmenter) BuildNextFragment() (*stack.PacketBuffer, int, int,
|
|||
|
||||
fragPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: pf.reserve,
|
||||
Mark: pf.mark,
|
||||
})
|
||||
|
||||
// Copy data for the fragment.
|
||||
|
|
|
|||
|
|
@ -103,8 +103,21 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
|
|||
}
|
||||
|
||||
holeFound = true
|
||||
// IPv6: rfc8200#section-4.5
|
||||
// Changed the text to require that IPv6 nodes must not create
|
||||
// overlapping fragments. Also, when reassembling an IPv6
|
||||
// datagram, if one or more its constituent fragments is
|
||||
// determined to be an overlapping fragment, the entire datagram
|
||||
// (and any constituent fragments) must be silently discarded.
|
||||
// Includes a clarification that no ICMP error message should be
|
||||
// sent if overlapping fragments are received.
|
||||
if currentHole.filled {
|
||||
// Incoming fragment is a subset of an existing fragment.
|
||||
if first != currentHole.first || last != currentHole.last {
|
||||
return nil, 0, false, 0, ErrFragmentOverlap
|
||||
}
|
||||
// Incoming fragment is a duplicate.
|
||||
// Not dropping packet incase of duplicates.
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ type dadState struct {
|
|||
extendRequest extendRequest
|
||||
|
||||
done *bool
|
||||
timer tcpip.Timer
|
||||
timer tcpip.Timer `state:"nosave"`
|
||||
|
||||
completionHandlers []stack.DADCompletionHandler
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,13 +291,13 @@ type GenericMulticastProtocolState struct {
|
|||
robustnessVariable uint8
|
||||
queryInterval time.Duration
|
||||
mode protocolMode
|
||||
modeTimer tcpip.Timer
|
||||
modeTimer tcpip.Timer `state:"nosave"`
|
||||
|
||||
generalQueryV2Timer tcpip.Timer
|
||||
generalQueryV2Timer tcpip.Timer `state:"nosave"`
|
||||
// TODO(b/341946753): Restore when netstack is savable.
|
||||
generalQueryV2TimerFiresAt time.Time `state:"nosave"`
|
||||
|
||||
stateChangedReportV2Timer tcpip.Timer
|
||||
stateChangedReportV2Timer tcpip.Timer `state:"nosave"`
|
||||
stateChangedReportV2TimerSet bool
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ func (d *dadState) StateFields() []string {
|
|||
"nonce",
|
||||
"extendRequest",
|
||||
"done",
|
||||
"timer",
|
||||
"completionHandlers",
|
||||
}
|
||||
}
|
||||
|
|
@ -30,8 +29,7 @@ func (d *dadState) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(0, &d.nonce)
|
||||
stateSinkObject.Save(1, &d.extendRequest)
|
||||
stateSinkObject.Save(2, &d.done)
|
||||
stateSinkObject.Save(3, &d.timer)
|
||||
stateSinkObject.Save(4, &d.completionHandlers)
|
||||
stateSinkObject.Save(3, &d.completionHandlers)
|
||||
}
|
||||
|
||||
func (d *dadState) afterLoad(context.Context) {}
|
||||
|
|
@ -41,8 +39,7 @@ func (d *dadState) StateLoad(ctx context.Context, stateSourceObject state.Source
|
|||
stateSourceObject.Load(0, &d.nonce)
|
||||
stateSourceObject.Load(1, &d.extendRequest)
|
||||
stateSourceObject.Load(2, &d.done)
|
||||
stateSourceObject.Load(3, &d.timer)
|
||||
stateSourceObject.Load(4, &d.completionHandlers)
|
||||
stateSourceObject.Load(3, &d.completionHandlers)
|
||||
}
|
||||
|
||||
func (d *DADOptions) StateTypeName() string {
|
||||
|
|
@ -237,9 +234,6 @@ func (g *GenericMulticastProtocolState) StateFields() []string {
|
|||
"robustnessVariable",
|
||||
"queryInterval",
|
||||
"mode",
|
||||
"modeTimer",
|
||||
"generalQueryV2Timer",
|
||||
"stateChangedReportV2Timer",
|
||||
"stateChangedReportV2TimerSet",
|
||||
}
|
||||
}
|
||||
|
|
@ -254,10 +248,7 @@ func (g *GenericMulticastProtocolState) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(2, &g.robustnessVariable)
|
||||
stateSinkObject.Save(3, &g.queryInterval)
|
||||
stateSinkObject.Save(4, &g.mode)
|
||||
stateSinkObject.Save(5, &g.modeTimer)
|
||||
stateSinkObject.Save(6, &g.generalQueryV2Timer)
|
||||
stateSinkObject.Save(7, &g.stateChangedReportV2Timer)
|
||||
stateSinkObject.Save(8, &g.stateChangedReportV2TimerSet)
|
||||
stateSinkObject.Save(5, &g.stateChangedReportV2TimerSet)
|
||||
}
|
||||
|
||||
func (g *GenericMulticastProtocolState) afterLoad(context.Context) {}
|
||||
|
|
@ -269,10 +260,7 @@ func (g *GenericMulticastProtocolState) StateLoad(ctx context.Context, stateSour
|
|||
stateSourceObject.Load(2, &g.robustnessVariable)
|
||||
stateSourceObject.Load(3, &g.queryInterval)
|
||||
stateSourceObject.Load(4, &g.mode)
|
||||
stateSourceObject.Load(5, &g.modeTimer)
|
||||
stateSourceObject.Load(6, &g.generalQueryV2Timer)
|
||||
stateSourceObject.Load(7, &g.stateChangedReportV2Timer)
|
||||
stateSourceObject.Load(8, &g.stateChangedReportV2TimerSet)
|
||||
stateSourceObject.Load(5, &g.stateChangedReportV2TimerSet)
|
||||
}
|
||||
|
||||
func (m *MultiCounterIPForwardingStats) StateTypeName() string {
|
||||
|
|
|
|||
293
pkg/tcpip/network/internal/ip/reject_with_reset.go
Normal file
293
pkg/tcpip/network/internal/ip/reject_with_reset.go
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
// Copyright 2026 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ip
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// ipv6FragmentOffset returns the fragment offset of the IPv6 packet
|
||||
// if present.
|
||||
func ipv6FragmentOffset(pkt *stack.PacketBuffer, ipHdr header.IPv6) (uint16, bool) {
|
||||
if !header.IsExtensionHeader(ipHdr.NextHeader()) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
netHeaderSlice := pkt.NetworkHeader().Slice()
|
||||
if len(netHeaderSlice) <= header.IPv6MinimumSize {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Make an iterator to walk the extension headers.
|
||||
buf := buffer.MakeWithData(netHeaderSlice[header.IPv6MinimumSize:])
|
||||
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(ipHdr.NextHeader()), buf)
|
||||
defer it.Release()
|
||||
|
||||
for {
|
||||
extHdr, done, err := it.Next()
|
||||
if err != nil || done {
|
||||
break
|
||||
}
|
||||
switch extHdr := extHdr.(type) {
|
||||
case header.IPv6FragmentExtHdr:
|
||||
offset := extHdr.FragmentOffset()
|
||||
extHdr.Release()
|
||||
return offset, true
|
||||
default:
|
||||
extHdr.Release()
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// buildResetPayloadV4 builds an IPv4 + TCP Reset packet in a buffer.
|
||||
func buildResetPayloadV4(ttl uint8, src, dst tcpip.Address, tcpHdr header.TCP, seq, ack uint32, flags header.TCPFlags) *buffer.View {
|
||||
totalHdrLen := header.IPv4MinimumSize + header.TCPMinimumSize
|
||||
v := buffer.NewViewSize(totalHdrLen)
|
||||
buf := v.AsSlice()
|
||||
|
||||
rstIPHdr := header.IPv4(buf[:header.IPv4MinimumSize])
|
||||
rstIPHdr.Encode(&header.IPv4Fields{
|
||||
TotalLength: uint16(totalHdrLen),
|
||||
TTL: ttl,
|
||||
Protocol: uint8(header.TCPProtocolNumber),
|
||||
TOS: stack.DefaultTOS,
|
||||
Flags: header.IPv4FlagDontFragment,
|
||||
// Flip source and destination addresses.
|
||||
SrcAddr: dst,
|
||||
DstAddr: src,
|
||||
})
|
||||
|
||||
rstTCPHdr := header.TCP(buf[header.IPv4MinimumSize:])
|
||||
rstTCPHdr.Encode(&header.TCPFields{
|
||||
SrcPort: tcpHdr.DestinationPort(),
|
||||
DstPort: tcpHdr.SourcePort(),
|
||||
SeqNum: seq,
|
||||
AckNum: ack,
|
||||
DataOffset: header.TCPMinimumSize,
|
||||
Flags: flags,
|
||||
})
|
||||
|
||||
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, dst, src, header.TCPMinimumSize)
|
||||
rstTCPHdr.SetChecksum(0)
|
||||
rstTCPHdr.SetChecksum(^rstTCPHdr.CalculateChecksum(xsum))
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// buildResetPayloadV6 builds an IPv6 + TCP Reset packet in a buffer.
|
||||
func buildResetPayloadV6(hopLimit uint8, src, dst tcpip.Address, tcpHdr header.TCP, seq, ack uint32, flags header.TCPFlags) *buffer.View {
|
||||
totalHdrLen := header.IPv6MinimumSize + header.TCPMinimumSize
|
||||
v := buffer.NewViewSize(totalHdrLen)
|
||||
buf := v.AsSlice()
|
||||
|
||||
rstIPHdr := header.IPv6(buf[:header.IPv6MinimumSize])
|
||||
rstIPHdr.Encode(&header.IPv6Fields{
|
||||
PayloadLength: uint16(header.TCPMinimumSize),
|
||||
TransportProtocol: header.TCPProtocolNumber,
|
||||
HopLimit: hopLimit,
|
||||
// Flip source and destination addresses.
|
||||
SrcAddr: dst,
|
||||
DstAddr: src,
|
||||
})
|
||||
|
||||
rstTCPHdr := header.TCP(buf[header.IPv6MinimumSize:])
|
||||
rstTCPHdr.Encode(&header.TCPFields{
|
||||
SrcPort: tcpHdr.DestinationPort(),
|
||||
DstPort: tcpHdr.SourcePort(),
|
||||
SeqNum: seq,
|
||||
AckNum: ack,
|
||||
DataOffset: header.TCPMinimumSize,
|
||||
Flags: flags,
|
||||
})
|
||||
|
||||
// Compute TCP checksum.
|
||||
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, dst, src, header.TCPMinimumSize)
|
||||
rstTCPHdr.SetChecksum(0)
|
||||
rstTCPHdr.SetChecksum(^rstTCPHdr.CalculateChecksum(xsum))
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// RejectWithTCPReset sends a TCP reset in response to the packet.
|
||||
//
|
||||
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_send_reset[6]()
|
||||
func RejectWithTCPReset(pkt *stack.PacketBuffer, netProto tcpip.NetworkProtocolNumber, stk *stack.Stack, deliveredLocally bool) tcpip.Error {
|
||||
var src, dst tcpip.Address
|
||||
var ttl uint8
|
||||
isFragment := false
|
||||
|
||||
switch netProto {
|
||||
case header.IPv4ProtocolNumber:
|
||||
// Ref: net/ipv4/netfilter/nf_reject_ipv4.c:nf_reject_ip_tcphdr_get
|
||||
ipHdr := header.IPv4(pkt.NetworkHeader().Slice())
|
||||
if len(ipHdr) < header.IPv4MinimumSize {
|
||||
return nil
|
||||
}
|
||||
if ipHdr.Protocol() != uint8(header.TCPProtocolNumber) {
|
||||
return nil
|
||||
}
|
||||
if ipHdr.FragmentOffset() != 0 {
|
||||
return nil
|
||||
}
|
||||
isFragment = ipHdr.More()
|
||||
src = ipHdr.SourceAddress()
|
||||
dst = ipHdr.DestinationAddress()
|
||||
|
||||
// Ref: net/ipv4/netfilter/nf_reject_ipv4.c:nf_send_reset
|
||||
if header.IsV4MulticastAddress(dst) ||
|
||||
header.IsV4MulticastAddress(src) ||
|
||||
pkt.NetworkPacketInfo.LocalAddressBroadcast ||
|
||||
pkt.PktType == tcpip.PacketBroadcast || pkt.PktType == tcpip.PacketMulticast ||
|
||||
src == header.IPv4Any || dst == header.IPv4Any {
|
||||
return nil
|
||||
}
|
||||
|
||||
case header.IPv6ProtocolNumber:
|
||||
// Ref: net/ipv6/netfilter/nf_reject_ipv6.c:nf_reject_ip6_tcphdr_get
|
||||
ipHdr := header.IPv6(pkt.NetworkHeader().Slice())
|
||||
if len(ipHdr) < header.IPv6MinimumSize {
|
||||
return nil
|
||||
}
|
||||
fragOffset, ok := ipv6FragmentOffset(pkt, ipHdr)
|
||||
if ok && fragOffset != 0 {
|
||||
return nil
|
||||
}
|
||||
isFragment = ok
|
||||
src = ipHdr.SourceAddress()
|
||||
dst = ipHdr.DestinationAddress()
|
||||
|
||||
// Ref: net/ipv6/netfilter/nf_reject_ipv6.c:nf_send_reset6
|
||||
if header.IsV6MulticastAddress(src) || header.IsV6MulticastAddress(dst) ||
|
||||
header.IsV4MappedAddress(src) || header.IsV4MappedAddress(dst) ||
|
||||
src == header.IPv6Any || dst == header.IPv6Any ||
|
||||
pkt.PktType == tcpip.PacketBroadcast || pkt.PktType == tcpip.PacketMulticast {
|
||||
return nil
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
tcpHdr := func(pkt *stack.PacketBuffer) header.TCP {
|
||||
// If 0 < len(transportHdr) < header.TCPMinimumSize, then the TCP header is invalid.
|
||||
// Assuming a TCP packet,
|
||||
// if the TCP header was parsed, the
|
||||
// len should be >= header.TCPMinimumSize;
|
||||
// else the TCP header was not parsed and the len should be 0.
|
||||
transportHdr := pkt.TransportHeader().Slice()
|
||||
if len(transportHdr) >= header.TCPMinimumSize {
|
||||
return header.TCP(transportHdr)
|
||||
}
|
||||
if len(transportHdr) != 0 {
|
||||
return nil
|
||||
}
|
||||
// In the case of fragmented TCP packets, the TCP header may not be parsed.
|
||||
// Pull up the TCP header from the payload.
|
||||
b, ok := pkt.Data().PullUp(header.TCPMinimumSize)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
hdr := header.TCP(b)
|
||||
hdrLen := int(hdr.DataOffset())
|
||||
if hdrLen < header.TCPMinimumSize || pkt.Data().Size() < hdrLen {
|
||||
return nil
|
||||
}
|
||||
tcpHdr, ok := pkt.Data().Consume(hdrLen)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
pkt.TransportProtocolNumber = header.TCPProtocolNumber
|
||||
return header.TCP(tcpHdr)
|
||||
}(pkt)
|
||||
if tcpHdr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_reject_ip[6]_tcphdr_get()
|
||||
// No RST for RST as this will cause a loop.
|
||||
if tcpHdr.Flags().Contains(header.TCPFlagRst) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check checksum integrity only for non-fragmented packets.
|
||||
// We don't support refragmentation(nf_defrag) before REJECT,
|
||||
// so we can't validate the checksum for fragmented packets.
|
||||
if !isFragment {
|
||||
// Check checksum integrity.
|
||||
if !pkt.RXChecksumValidated && !tcpHdr.IsChecksumValid(src, dst, pkt.Data().Checksum(), uint16(pkt.Data().Size())) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
localAddr := dst
|
||||
if !deliveredLocally {
|
||||
// If the packet wasn't delivered locally, do not use the packet's destination
|
||||
// address as the response's source address as we should not own the
|
||||
// destination address.
|
||||
localAddr = tcpip.Address{}
|
||||
}
|
||||
|
||||
route, err := stk.FindRoute(0 /*nicID*/, localAddr, src, netProto, false /* multicastLoop */)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer route.Release()
|
||||
ttl = route.DefaultTTL()
|
||||
|
||||
var seq uint32
|
||||
var ack uint32
|
||||
payloadLen := uint32(pkt.Data().Size())
|
||||
flags := header.TCPFlagRst
|
||||
|
||||
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_reject_ip[6]_tcphdr_put()
|
||||
if tcpHdr.Flags()&header.TCPFlagAck != 0 {
|
||||
seq = tcpHdr.AckNumber()
|
||||
} else {
|
||||
flags |= header.TCPFlagAck
|
||||
ack = tcpHdr.SequenceNumber() + payloadLen
|
||||
if tcpHdr.Flags()&header.TCPFlagSyn != 0 {
|
||||
ack++
|
||||
}
|
||||
if tcpHdr.Flags()&header.TCPFlagFin != 0 {
|
||||
ack++
|
||||
}
|
||||
}
|
||||
|
||||
var v *buffer.View
|
||||
if netProto == header.IPv4ProtocolNumber {
|
||||
v = buildResetPayloadV4(ttl, src, dst, tcpHdr, seq, ack, flags)
|
||||
} else {
|
||||
v = buildResetPayloadV6(ttl, src, dst, tcpHdr, seq, ack, flags)
|
||||
}
|
||||
|
||||
rstPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: int(route.MaxHeaderLength()),
|
||||
Payload: buffer.MakeWithView(v),
|
||||
})
|
||||
rstPkt.TransportProtocolNumber = header.TCPProtocolNumber
|
||||
defer rstPkt.DecRef()
|
||||
|
||||
// TODO: b/521536712 - Add support for mark propagation.
|
||||
if err := route.WriteHeaderIncludedPacket(rstPkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ func (r *RouteTable) StateFields() []string {
|
|||
return []string{
|
||||
"installedRoutes",
|
||||
"pendingRoutes",
|
||||
"cleanupPendingRoutesTimer",
|
||||
"isCleanupRoutineRunning",
|
||||
"config",
|
||||
}
|
||||
|
|
@ -29,9 +28,8 @@ func (r *RouteTable) StateSave(stateSinkObject state.Sink) {
|
|||
r.beforeSave()
|
||||
stateSinkObject.Save(0, &r.installedRoutes)
|
||||
stateSinkObject.Save(1, &r.pendingRoutes)
|
||||
stateSinkObject.Save(2, &r.cleanupPendingRoutesTimer)
|
||||
stateSinkObject.Save(3, &r.isCleanupRoutineRunning)
|
||||
stateSinkObject.Save(4, &r.config)
|
||||
stateSinkObject.Save(2, &r.isCleanupRoutineRunning)
|
||||
stateSinkObject.Save(3, &r.config)
|
||||
}
|
||||
|
||||
func (r *RouteTable) afterLoad(context.Context) {}
|
||||
|
|
@ -40,9 +38,8 @@ func (r *RouteTable) afterLoad(context.Context) {}
|
|||
func (r *RouteTable) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &r.installedRoutes)
|
||||
stateSourceObject.Load(1, &r.pendingRoutes)
|
||||
stateSourceObject.Load(2, &r.cleanupPendingRoutesTimer)
|
||||
stateSourceObject.Load(3, &r.isCleanupRoutineRunning)
|
||||
stateSourceObject.Load(4, &r.config)
|
||||
stateSourceObject.Load(2, &r.isCleanupRoutineRunning)
|
||||
stateSourceObject.Load(3, &r.config)
|
||||
}
|
||||
|
||||
func (r *InstalledRoute) StateTypeName() string {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ type RouteTable struct {
|
|||
// cleanupPendingRoutesTimer is a timer that triggers a routine to remove
|
||||
// pending routes that are expired.
|
||||
// +checklocks:pendingMu
|
||||
cleanupPendingRoutesTimer tcpip.Timer
|
||||
cleanupPendingRoutesTimer tcpip.Timer `state:"nosave"`
|
||||
// +checklocks:pendingMu
|
||||
isCleanupRoutineRunning bool
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue