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

@ -18,7 +18,6 @@ import (
"encoding/binary"
"fmt"
"math"
"math/rand"
"sync"
"time"
@ -45,6 +44,31 @@ const (
unestablishedTimeout time.Duration = 120 * time.Second
)
// ConnTrackState represents the state of a connection.
type ConnTrackState int
const (
// ConnTrackStateInvalid is the invalid connection tracking state.
ConnTrackStateInvalid ConnTrackState = -1
// ConnTrackStateEstablished represents an established connection.
ConnTrackStateEstablished ConnTrackState = 0
// ConnTrackStateNew represents a new connection.
ConnTrackStateNew ConnTrackState = 2
// ConnTrackStateEstablishedReply represents an established connection
// in the reply direction.
ConnTrackStateEstablishedReply ConnTrackState = 3
)
// ConnTrackDirection represents the direction of a connection.
type ConnTrackDirection uint8
const (
// ConnTrackDirectionOriginal represents the original direction.
ConnTrackDirectionOriginal ConnTrackDirection = 0
// ConnTrackDirectionReply represents the reply direction.
ConnTrackDirectionReply ConnTrackDirection = 1
)
// tuple holds a connection's identifying and manipulating data in one
// direction. It is immutable.
//
@ -161,6 +185,10 @@ type conn struct {
//
// +checklocks:stateMu
lastUsed tcpip.MonotonicTime
// replySeen indicates whether a packet in the reply direction has been seen.
//
// +checklocks:stateMu
replySeen bool
}
// timedOut returns whether the connection timed out based on its state.
@ -177,6 +205,27 @@ func (cn *conn) timedOut(now tcpip.MonotonicTime) bool {
return now.Sub(cn.lastUsed) > unestablishedTimeout
}
// expiresIn returns the duration from now until the connection times out.
func (cn *conn) expiresIn() time.Duration {
var timeout time.Duration
var lastUsed tcpip.MonotonicTime
cn.stateMu.RLock()
state := cn.tcb.State()
lastUsed = cn.lastUsed
cn.stateMu.RUnlock()
if state == tcpconntrack.ResultAlive {
timeout = establishedTimeout
} else {
timeout = unestablishedTimeout
}
now := cn.ct.clock.NowMonotonic()
expires := timeout - now.Sub(lastUsed)
if expires < 0 {
return 0
}
return expires
}
// update the connection tracking state.
func (cn *conn) update(pkt *PacketBuffer, reply bool) {
cn.stateMu.Lock()
@ -184,6 +233,9 @@ func (cn *conn) update(pkt *PacketBuffer, reply bool) {
// Mark the connection as having been used recently so it isn't reaped.
cn.lastUsed = cn.ct.clock.NowMonotonic()
if reply {
cn.replySeen = true
}
if pkt.TransportProtocolNumber != header.TCPProtocolNumber {
return
@ -206,6 +258,10 @@ func (cn *conn) update(pkt *PacketBuffer, reply bool) {
}
}
type connTrackRNG interface {
Uint32() uint32
}
// ConnTrack tracks all connections created for NAT rules. Most users are
// expected to only call handlePacket, insertRedirectConn, and maybeInsertNoop.
//
@ -225,12 +281,26 @@ type ConnTrack struct {
// seed is a one-time random value initialized at stack startup
// and is used in the calculation of hash keys for the list of buckets.
// It is immutable.
//
// TODO(gvisor.dev/issue/4595): When Stack.tables becomes savable and
// ConnTrack flows into checkpoint state, this seed must be redrawn
// from secureRNG during restore AND the entries in buckets must be
// rehashed under the new seed. bucket_index = jenkins.Sum32(seed) %
// len(buckets) couples the seed value to bucket layout; redrawing the
// seed without rehashing leaves restored entries unreachable by
// Lookup. Persisting the pre-checkpoint seed extends the brute-force
// window across save boundaries.
seed uint32
// nftIDSeed is a one-time random value initialized at stack startup
// and is used in the calculation of tuple IDs for nftables.
// It is immutable.
nftIDSeed uint32
// clock provides timing used to determine conntrack reapings.
clock tcpip.Clock
// TODO(b/341946753): Restore when netstack is savable.
rand *rand.Rand `state:"nosave"`
rng connTrackRNG `state:"nosave"`
mu connTrackRWMutex `state:"nosave"`
// mu protects the buckets slice, but not buckets' contents. Only take
@ -271,99 +341,6 @@ func v6NetAndTransHdr(icmpPayload []byte, minTransHdrLen int) (header.Network, [
return netHdr, transHdr[:minTransHdrLen]
}
func getEmbeddedNetAndTransHeaders(pkt *PacketBuffer, netHdrLength int, getNetAndTransHdr netAndTransHeadersFunc, transProto tcpip.TransportProtocolNumber) (header.Network, header.ChecksummableTransport, bool) {
switch transProto {
case header.TCPProtocolNumber:
if netAndTransHeader, ok := pkt.Data().PullUp(netHdrLength + header.TCPMinimumSize); ok {
netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.TCPMinimumSize)
return netHeader, header.TCP(transHeaderBytes), true
}
case header.UDPProtocolNumber:
if netAndTransHeader, ok := pkt.Data().PullUp(netHdrLength + header.UDPMinimumSize); ok {
netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.UDPMinimumSize)
return netHeader, header.UDP(transHeaderBytes), true
}
}
return nil, nil, false
}
func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.Transport, isICMPError bool, ok bool) {
switch pkt.TransportProtocolNumber {
case header.TCPProtocolNumber:
if tcpHeader := header.TCP(pkt.TransportHeader().Slice()); len(tcpHeader) >= header.TCPMinimumSize {
return pkt.Network(), tcpHeader, false, true
}
return nil, nil, false, false
case header.UDPProtocolNumber:
if udpHeader := header.UDP(pkt.TransportHeader().Slice()); len(udpHeader) >= header.UDPMinimumSize {
return pkt.Network(), udpHeader, false, true
}
return nil, nil, false, false
case header.ICMPv4ProtocolNumber:
icmpHeader := header.ICMPv4(pkt.TransportHeader().Slice())
if len(icmpHeader) < header.ICMPv4MinimumSize {
return nil, nil, false, false
}
switch icmpType := icmpHeader.Type(); icmpType {
case header.ICMPv4Echo, header.ICMPv4EchoReply:
return pkt.Network(), icmpHeader, false, true
case header.ICMPv4DstUnreachable, header.ICMPv4TimeExceeded, header.ICMPv4ParamProblem:
default:
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
}
h, ok := pkt.Data().PullUp(header.IPv4MinimumSize)
if !ok {
panic(fmt.Sprintf("should have a valid IPv4 packet; only have %d bytes, want at least %d bytes", pkt.Data().Size(), header.IPv4MinimumSize))
}
if header.IPv4(h).HeaderLength() > header.IPv4MinimumSize {
// TODO(https://gvisor.dev/issue/6765): Handle IPv4 options.
panic("should have dropped packets with IPv4 options")
}
if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv4MinimumSize, v4NetAndTransHdr, pkt.tuple.tupleID.transProto); ok {
return netHdr, transHdr, true, true
}
return nil, nil, false, false
case header.ICMPv6ProtocolNumber:
icmpHeader := header.ICMPv6(pkt.TransportHeader().Slice())
if len(icmpHeader) < header.ICMPv6MinimumSize {
return nil, nil, false, false
}
switch icmpType := icmpHeader.Type(); icmpType {
case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
return pkt.Network(), icmpHeader, false, true
case header.ICMPv6DstUnreachable, header.ICMPv6PacketTooBig, header.ICMPv6TimeExceeded, header.ICMPv6ParamProblem:
default:
panic(fmt.Sprintf("unexpected ICMPv6 type = %d", icmpType))
}
h, ok := pkt.Data().PullUp(header.IPv6MinimumSize)
if !ok {
panic(fmt.Sprintf("should have a valid IPv6 packet; only have %d bytes, want at least %d bytes", pkt.Data().Size(), header.IPv6MinimumSize))
}
// 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 := pkt.tuple.tupleID.transProto
if got := header.IPv6(h).TransportProtocol(); got != transProto {
panic(fmt.Sprintf("got TransportProtocol() = %d, want = %d", got, transProto))
}
if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv6MinimumSize, v6NetAndTransHdr, transProto); ok {
return netHdr, transHdr, true, true
}
return nil, nil, false, false
default:
panic(fmt.Sprintf("unexpected transport protocol = %d", pkt.TransportProtocolNumber))
}
}
func getTupleIDForRegularPacket(netHdr header.Network, netProto tcpip.NetworkProtocolNumber, transHdr header.Transport, transProto tcpip.TransportProtocolNumber) tupleID {
return tupleID{
srcAddr: netHdr.SourceAddress(),
@ -376,7 +353,7 @@ func getTupleIDForRegularPacket(netHdr header.Network, netProto tcpip.NetworkPro
}
func getTupleIDForPacketInICMPError(pkt *PacketBuffer, getNetAndTransHdr netAndTransHeadersFunc, netProto tcpip.NetworkProtocolNumber, netLen int, transProto tcpip.TransportProtocolNumber) (tupleID, bool) {
if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, netLen, getNetAndTransHdr, transProto); ok {
if netHdr, transHdr, ok := pkt.GetEmbeddedNetAndTransHeaders(netLen, getNetAndTransHdr, transProto); ok {
return tupleID{
srcAddr: netHdr.DestinationAddress(),
srcPortOrEchoRequestIdent: transHdr.DestinationPort(),
@ -602,6 +579,11 @@ func (ct *ConnTrack) getConnAndUpdate(pkt *PacketBuffer, skipChecksumValidation
return t
}
// GetConnAndUpdatePkt gets the connection for the packet and also sets the packet's tuple.
func (ct *ConnTrack) GetConnAndUpdatePkt(pkt *PacketBuffer, skipChecksumValidation bool) {
pkt.tuple = ct.getConnAndUpdate(pkt, skipChecksumValidation)
}
func (ct *ConnTrack) connForTID(tid tupleID) *tuple {
ct.mu.RLock()
bkt := &ct.buckets[ct.bucket(tid)]
@ -610,6 +592,114 @@ func (ct *ConnTrack) connForTID(tid tupleID) *tuple {
return bkt.connForTID(tid, ct.clock.NowMonotonic())
}
// ConnTrackInfo holds connection tracking information for a packet.
type ConnTrackInfo struct {
State ConnTrackState
Direction ConnTrackDirection
SrcAddr tcpip.Address
DstAddr tcpip.Address
SrcPort uint16
DstPort uint16
NetProto tcpip.NetworkProtocolNumber
TransProto tcpip.TransportProtocolNumber
Expiration time.Duration
PseudoID uint32
Bytes uint64
Packets uint64
}
// ConnTrackInfoOpts holds options for GetConnTrackInfo.
type ConnTrackInfoOpts struct {
FillState bool
UseReplyDir bool
FillPseudoID bool
FillExpiration bool
}
// getTCPConnTrackState converts the TCB state to ConnTrackState.
func (cn *conn) getTCPConnTrackState(useReplyDir bool) ConnTrackState {
state := ConnTrackStateInvalid
cn.stateMu.RLock()
tcbState := cn.tcb.State()
cn.stateMu.RUnlock()
switch tcbState {
case tcpconntrack.ResultConnecting:
state = ConnTrackStateNew
case tcpconntrack.ResultAlive, tcpconntrack.ResultReset,
tcpconntrack.ResultClosedByOriginator, tcpconntrack.ResultClosedByResponder:
if useReplyDir {
state = ConnTrackStateEstablishedReply
} else {
state = ConnTrackStateEstablished
}
case tcpconntrack.ResultDrop:
state = ConnTrackStateInvalid
}
return state
}
// getConnTrackState returns the connection tracking state for the connection.
func (cn *conn) getConnTrackState(useReplyDir bool) ConnTrackState {
state := ConnTrackStateInvalid
// TCP connections have their own state machine in the TCB.
if cn.original.tupleID.transProto == header.TCPProtocolNumber {
return cn.getTCPConnTrackState(useReplyDir)
}
// For non-TCP connections, fill the info based on the reply.
cn.stateMu.RLock()
replySeen := cn.replySeen
cn.stateMu.RUnlock()
if useReplyDir {
state = ConnTrackStateEstablishedReply
} else if replySeen {
state = ConnTrackStateEstablished
} else {
state = ConnTrackStateNew
}
return state
}
// FillConnTrackInfo fills connection tracking information for the connection.
func (cn *conn) FillConnTrackInfo(opts ConnTrackInfoOpts, info *ConnTrackInfo) bool {
state := ConnTrackStateInvalid
if opts.FillState {
state = cn.getConnTrackState(opts.UseReplyDir)
}
dir := ConnTrackDirectionOriginal
t := &cn.original
if opts.UseReplyDir {
t = &cn.reply
dir = ConnTrackDirectionReply
}
tID := t.tupleID
pID := uint32(0)
if opts.FillPseudoID {
// Generate a pseudo-ID similar to Linux nf_ct_get_id
pID = tupleHash(cn.original.tupleID, cn.ct.nftIDSeed)
}
var expires time.Duration
if opts.FillExpiration {
expires = cn.expiresIn()
}
info.State = state
info.Direction = dir
info.SrcAddr = tID.srcAddr
info.DstAddr = tID.dstAddr
info.SrcPort = tID.srcPortOrEchoRequestIdent
info.DstPort = tID.dstPortOrEchoReplyIdent
info.NetProto = tID.netProto
info.TransProto = tID.transProto
info.Expiration = expires
info.PseudoID = pID
return true
}
func (bkt *bucket) connForTID(tid tupleID, now tcpip.MonotonicTime) *tuple {
bkt.mu.RLock()
defer bkt.mu.RUnlock()
@ -697,325 +787,14 @@ func (cn *conn) finalize() bool {
}
}
// If NAT has not been configured for this connection, either mark the
// connection as configured for "no-op NAT", in the case of DNAT, or, in the
// case of SNAT, perform source port remapping so that source ports used by
// locally-generated traffic do not conflict with ports occupied by existing NAT
// bindings.
//
// Note that in the typical case this is also a no-op, because `snatAction`
// will do nothing if the original tuple is already unique.
func (cn *conn) maybePerformNoopNAT(pkt *PacketBuffer, hook Hook, r *Route, dnat bool) {
cn.mu.Lock()
var manip *manipType
if dnat {
manip = &cn.destinationManip
} else {
manip = &cn.sourceManip
}
if *manip != manipNotPerformed {
cn.mu.Unlock()
_ = cn.handlePacket(pkt, hook, r)
return
}
if dnat {
*manip = manipPerformedNoop
cn.mu.Unlock()
_ = cn.handlePacket(pkt, hook, r)
return
}
cn.mu.Unlock()
// At this point, we know that NAT has not yet been performed on this
// connection, and the DNAT case has been handled with a no-op. For SNAT, we
// simply perform source port remapping to ensure that source ports for
// locally generated traffic do not clash with ports used by existing NAT
// bindings.
_, _ = snatAction(pkt, hook, r, 0, tcpip.Address{}, true /* changePort */, false /* changeAddress */)
}
type portOrIdentRange struct {
start uint16
size uint32
}
// performNAT setups up the connection for the specified NAT and rewrites the
// packet.
//
// If NAT has already been performed on the connection, then the packet will
// be rewritten with the NAT performed on the connection, ignoring the passed
// address and port range.
//
// Generally, only the first packet of a connection reaches this method; other
// packets will be manipulated without needing to modify the connection.
func (cn *conn) performNAT(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents portOrIdentRange, natAddress tcpip.Address, dnat, changePort, changeAddress bool) {
lastPortOrIdent := func() uint16 {
lastPortOrIdent := uint32(portsOrIdents.start) + portsOrIdents.size - 1
if lastPortOrIdent > math.MaxUint16 {
panic(fmt.Sprintf("got lastPortOrIdent = %d, want <= MaxUint16(=%d); portsOrIdents=%#v", lastPortOrIdent, math.MaxUint16, portsOrIdents))
}
return uint16(lastPortOrIdent)
}()
// Make sure the packet is re-written after performing NAT.
defer func() {
// handlePacket returns true if the packet may skip the NAT table as the
// connection is already NATed, but if we reach this point we must be in the
// NAT table, so the return value is useless for us.
_ = cn.handlePacket(pkt, hook, r)
}()
cn.mu.Lock()
defer cn.mu.Unlock()
var manip *manipType
var address *tcpip.Address
var portOrIdent *uint16
if dnat {
manip = &cn.destinationManip
address = &cn.reply.tupleID.srcAddr
portOrIdent = &cn.reply.tupleID.srcPortOrEchoRequestIdent
} else {
manip = &cn.sourceManip
address = &cn.reply.tupleID.dstAddr
portOrIdent = &cn.reply.tupleID.dstPortOrEchoReplyIdent
}
if *manip != manipNotPerformed {
return
}
*manip = manipPerformed
if changeAddress {
*address = natAddress
}
// Everything below here is port-fiddling.
if !changePort {
return
}
// Does the current port/ident fit in the range?
if portsOrIdents.start <= *portOrIdent && *portOrIdent <= lastPortOrIdent {
// Yes, is the current reply tuple unique?
//
// Or, does the reply tuple refer to the same connection as the current one that
// we are NATing? This would apply, for example, to a self-connected socket,
// where the original and reply tuples are identical.
other := cn.ct.connForTID(cn.reply.tupleID)
if other == nil || other.conn == cn {
// Yes! No need to change the port.
return
}
}
// Try our best to find a port/ident that results in a unique reply tuple.
//
// We limit the number of attempts to find a unique tuple to not waste a lot
// of time looking for a unique tuple.
//
// Matches linux behaviour introduced in
// https://github.com/torvalds/linux/commit/a504b703bb1da526a01593da0e4be2af9d9f5fa8.
const maxAttemptsForInitialRound uint32 = 128
const minAttemptsToContinue = 16
allowedInitialAttempts := maxAttemptsForInitialRound
if allowedInitialAttempts > portsOrIdents.size {
allowedInitialAttempts = portsOrIdents.size
}
for maxAttempts := allowedInitialAttempts; ; maxAttempts /= 2 {
// Start reach round with a random initial port/ident offset.
randOffset := cn.ct.rand.Uint32()
for i := uint32(0); i < maxAttempts; i++ {
newPortOrIdentU32 := uint32(portsOrIdents.start) + (randOffset+i)%portsOrIdents.size
if newPortOrIdentU32 > math.MaxUint16 {
panic(fmt.Sprintf("got newPortOrIdentU32 = %d, want <= MaxUint16(=%d); portsOrIdents=%#v, randOffset=%d", newPortOrIdentU32, math.MaxUint16, portsOrIdents, randOffset))
}
*portOrIdent = uint16(newPortOrIdentU32)
if other := cn.ct.connForTID(cn.reply.tupleID); other == nil {
// We found a unique tuple!
return
}
}
if maxAttempts == portsOrIdents.size {
// We already tried all the ports/idents in the range so no need to keep
// trying.
return
}
if maxAttempts < minAttemptsToContinue {
return
}
}
// We did not find a unique tuple, use the last used port anyways.
// TODO(https://gvisor.dev/issue/6850): Handle not finding a unique tuple
// better (e.g. remove the connection and drop the packet).
}
// handlePacket attempts to handle a packet and perform NAT if the connection
// has had NAT performed on it.
//
// Returns true if the packet can skip the NAT table.
func (cn *conn) handlePacket(pkt *PacketBuffer, hook Hook, rt *Route) bool {
netHdr, transHdr, isICMPError, ok := getHeaders(pkt)
if !ok {
return false
}
fullChecksum := false
updatePseudoHeader := false
natDone := &pkt.snatDone
dnat := false
switch hook {
case Prerouting:
// Packet came from outside the stack so it must have a checksum set
// already.
fullChecksum = true
updatePseudoHeader = true
natDone = &pkt.dnatDone
dnat = true
case Input:
case Forward:
panic("should not handle packet in the forwarding hook")
case Output:
natDone = &pkt.dnatDone
dnat = true
fallthrough
case Postrouting:
if pkt.TransportProtocolNumber == header.TCPProtocolNumber && pkt.GSOOptions.Type != GSONone && pkt.GSOOptions.NeedsCsum {
updatePseudoHeader = true
} else if rt.RequiresTXTransportChecksum() {
fullChecksum = true
updatePseudoHeader = true
}
default:
panic(fmt.Sprintf("unrecognized hook = %d", hook))
}
if *natDone {
panic(fmt.Sprintf("packet already had NAT(dnat=%t) performed at hook=%s; pkt=%#v", dnat, hook, pkt))
}
// TODO(gvisor.dev/issue/5748): TCP checksums on inbound packets should be
// validated if checksum offloading is off. It may require IP defrag if the
// packets are fragmented.
reply := pkt.tuple.reply
tid, manip := func() (tupleID, manipType) {
cn.mu.RLock()
defer cn.mu.RUnlock()
if reply {
tid := cn.original.tupleID
if dnat {
return tid, cn.sourceManip
}
return tid, cn.destinationManip
}
tid := cn.reply.tupleID
if dnat {
return tid, cn.destinationManip
}
return tid, cn.sourceManip
}()
switch manip {
case manipNotPerformed:
return false
case manipPerformedNoop:
*natDone = true
return true
case manipPerformed:
default:
panic(fmt.Sprintf("unhandled manip = %d", manip))
}
newPort := tid.dstPortOrEchoReplyIdent
newAddr := tid.dstAddr
if dnat {
newPort = tid.srcPortOrEchoRequestIdent
newAddr = tid.srcAddr
}
rewritePacket(
netHdr,
transHdr,
!dnat != isICMPError,
fullChecksum,
updatePseudoHeader,
newPort,
newAddr,
)
*natDone = true
if !isICMPError {
return true
}
// We performed NAT on (erroneous) packet that triggered an ICMP response, but
// not the ICMP packet itself.
switch pkt.TransportProtocolNumber {
case header.ICMPv4ProtocolNumber:
icmp := header.ICMPv4(pkt.TransportHeader().Slice())
// TODO(https://gvisor.dev/issue/6788): Incrementally update ICMP checksum.
icmp.SetChecksum(0)
icmp.SetChecksum(header.ICMPv4Checksum(icmp, pkt.Data().Checksum()))
network := header.IPv4(pkt.NetworkHeader().Slice())
if dnat {
network.SetDestinationAddressWithChecksumUpdate(tid.srcAddr)
} else {
network.SetSourceAddressWithChecksumUpdate(tid.dstAddr)
}
case header.ICMPv6ProtocolNumber:
network := header.IPv6(pkt.NetworkHeader().Slice())
srcAddr := network.SourceAddress()
dstAddr := network.DestinationAddress()
if dnat {
dstAddr = tid.srcAddr
} else {
srcAddr = tid.dstAddr
}
icmp := header.ICMPv6(pkt.TransportHeader().Slice())
// TODO(https://gvisor.dev/issue/6788): Incrementally update ICMP checksum.
icmp.SetChecksum(0)
payload := pkt.Data()
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmp,
Src: srcAddr,
Dst: dstAddr,
PayloadCsum: payload.Checksum(),
PayloadLen: payload.Size(),
}))
if dnat {
network.SetDestinationAddress(dstAddr)
} else {
network.SetSourceAddress(srcAddr)
}
}
return true
}
// bucket gets the conntrack bucket for a tupleID.
// +checklocksread:ct.mu
func (ct *ConnTrack) bucket(id tupleID) int {
return ct.bucketWithTableLength(id, len(ct.buckets))
}
func (ct *ConnTrack) bucketWithTableLength(id tupleID, tableLength int) int {
h := jenkins.Sum32(ct.seed)
func tupleHash(id tupleID, seed uint32) uint32 {
h := jenkins.Sum32(seed)
h.Write(id.srcAddr.AsSlice())
h.Write(id.dstAddr.AsSlice())
shortBuf := make([]byte, 2)
@ -1027,7 +806,12 @@ func (ct *ConnTrack) bucketWithTableLength(id tupleID, tableLength int) int {
h.Write([]byte(shortBuf))
binary.LittleEndian.PutUint16(shortBuf, uint16(id.netProto))
h.Write([]byte(shortBuf))
return int(h.Sum32()) % tableLength
return h.Sum32()
}
func (ct *ConnTrack) bucketWithTableLength(id tupleID, tableLength int) int {
h := tupleHash(id, ct.seed)
return int(h) % tableLength
}
// reapUnused deletes timed out entries from the conntrack map. The rules for
@ -1167,3 +951,53 @@ func (ct *ConnTrack) originalDst(epID TransportEndpointID, netProto tcpip.Networ
id := t.conn.original.tupleID
return id.dstAddr, id.dstPortOrEchoReplyIdent, nil
}
// NewConnTrack creates and initializes a new ConnTrack object.
func NewConnTrack(clock tcpip.Clock, rng connTrackRNG, seed *uint32) *ConnTrack {
if seed == nil {
r := rng.Uint32()
seed = &r
}
ct := &ConnTrack{
clock: clock,
rng: rng,
seed: *seed,
nftIDSeed: rng.Uint32(),
}
ct.init()
return ct
}
// NewConnTrackWithReaper creates and initializes a new ConnTrack and reaper.
// Reaper garbage collects unused connections.
func NewConnTrackWithReaper(clock tcpip.Clock, rng connTrackRNG, seed *uint32) (*ConnTrack, tcpip.Timer) {
ct := NewConnTrack(clock, rng, seed)
var reaper tcpip.Timer
bucket := 0
interval := 1 * time.Second
reaper = ct.clock.AfterFunc(interval, func() {
bucket, interval = ct.reapUnused(bucket, interval)
reaper.Reset(interval)
})
return ct, reaper
}
// NfConnTrackPriority returns the priority of the conntrack hook.
// Check `ipv4/ipv6_conntrack_ops` in nf_conntrack_proto.c.
func NfConnTrackPriority(hook NFHook) (int, bool) {
switch hook {
case NFPrerouting:
// NF_IP_PRI_CONNTRACK
return -200, true
case NFInput:
// NF_IP_PRI_CONNTRACK_CONFIRM
return math.MaxInt32, true
case NFPostrouting:
// NF_IP_PRI_CONNTRACK_CONFIRM
return math.MaxInt32, true
case NFOutput:
// NF_IP_PRI_CONNTRACK
return -200, true
}
return 0, false
}