This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

@@ -92,5 +92,5 @@ func addressStateinitLockNames() {}
func init() {
addressStateinitLockNames()
addressStateprefixIndex = locking.NewMutexClass(reflect.TypeOf(addressStateRWMutex{}), addressStatelockNames)
addressStateprefixIndex = locking.NewMutexClass(reflect.TypeFor[addressStateRWMutex](), addressStatelockNames)
}
@@ -602,7 +602,7 @@ func (a *AddressableEndpointState) AcquireAssignedAddressOrMatching(localAddr tc
// Proceed to add a new temporary endpoint.
addr := localAddr.WithPrefix()
ep, err := a.addAndAcquireAddressLocked(addr, AddressProperties{PEB: tempPEB}, Temporary)
ep, err := a.addAndAcquireAddressLocked(addr, AddressProperties{PEB: tempPEB, Temporary: true}, Temporary)
if err != nil {
// addAndAcquireAddressLocked only returns an error if the address is
// already assigned but we just checked above if the address exists so we
@@ -750,9 +750,9 @@ type addressState struct {
// addressState.mu
mu addressStateRWMutex
refs addressStateRefs
// checklocks:mu
// +checklocks:mu
kind AddressKind
// checklocks:mu
// +checklocks:mu
configType AddressConfigType
// lifetimes holds this address' lifetimes.
//
@@ -760,12 +760,12 @@ type addressState struct {
// must be the zero value. Note that the converse does not need to be
// upheld!
//
// checklocks:mu
// +checklocks:mu
lifetimes AddressLifetimes
// The enclosing mutex must be write-locked before calling methods on the
// dispatcher.
//
// checklocks:mu
// +checklocks:mu
disp AddressDispatcher
}
@@ -92,5 +92,5 @@ func addressableEndpointStateinitLockNames() {}
func init() {
addressableEndpointStateinitLockNames()
addressableEndpointStateprefixIndex = locking.NewMutexClass(reflect.TypeOf(addressableEndpointStateRWMutex{}), addressableEndpointStatelockNames)
addressableEndpointStateprefixIndex = locking.NewMutexClass(reflect.TypeFor[addressableEndpointStateRWMutex](), addressableEndpointStatelockNames)
}
+1 -1
View File
@@ -92,5 +92,5 @@ func bridgeinitLockNames() {}
func init() {
bridgeinitLockNames()
bridgeprefixIndex = locking.NewMutexClass(reflect.TypeOf(bridgeRWMutex{}), bridgelockNames)
bridgeprefixIndex = locking.NewMutexClass(reflect.TypeFor[bridgeRWMutex](), bridgelockNames)
}
+1 -1
View File
@@ -94,5 +94,5 @@ func bucketinitLockNames() { bucketlockNames = []string{"otherTuple"} }
func init() {
bucketinitLockNames()
bucketprefixIndex = locking.NewMutexClass(reflect.TypeOf(bucketRWMutex{}), bucketlockNames)
bucketprefixIndex = locking.NewMutexClass(reflect.TypeFor[bucketRWMutex](), bucketlockNames)
}
@@ -60,5 +60,5 @@ func cleanupEndpointsinitLockNames() {}
func init() {
cleanupEndpointsinitLockNames()
cleanupEndpointsprefixIndex = locking.NewMutexClass(reflect.TypeOf(cleanupEndpointsMutex{}), cleanupEndpointslockNames)
cleanupEndpointsprefixIndex = locking.NewMutexClass(reflect.TypeFor[cleanupEndpointsMutex](), cleanupEndpointslockNames)
}
+1 -1
View File
@@ -92,5 +92,5 @@ func conninitLockNames() {}
func init() {
conninitLockNames()
connprefixIndex = locking.NewMutexClass(reflect.TypeOf(connRWMutex{}), connlockNames)
connprefixIndex = locking.NewMutexClass(reflect.TypeFor[connRWMutex](), connlockNames)
}
@@ -92,5 +92,5 @@ func connTrackinitLockNames() {}
func init() {
connTrackinitLockNames()
connTrackprefixIndex = locking.NewMutexClass(reflect.TypeOf(connTrackRWMutex{}), connTracklockNames)
connTrackprefixIndex = locking.NewMutexClass(reflect.TypeFor[connTrackRWMutex](), connTracklockNames)
}
+244 -410
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
}
@@ -92,5 +92,5 @@ func endpointsByNICinitLockNames() {}
func init() {
endpointsByNICinitLockNames()
endpointsByNICprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointsByNICRWMutex{}), endpointsByNIClockNames)
endpointsByNICprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointsByNICRWMutex](), endpointsByNIClockNames)
}
@@ -30,7 +30,7 @@ func _() {
const _headerType_name = "virtioNetHeaderlinkHeadernetworkHeadertransportHeadernumHeaderType"
var _headerType_index = [...]uint8{0, 10, 23, 38, 51}
var _headerType_index = [...]uint8{0, 15, 25, 38, 53, 66}
func (i headerType) String() string {
if i < 0 || i >= headerType(len(_headerType_index)-1) {
+13 -1
View File
@@ -15,6 +15,8 @@
package stack
import (
"context"
"golang.org/x/time/rate"
"gvisor.dev/gvisor/pkg/tcpip"
)
@@ -34,9 +36,15 @@ const (
//
// +stateify savable
type ICMPRateLimiter struct {
// TODO(b/341946753): Restore when netstack is savable.
limiter *rate.Limiter `state:"nosave"`
clock tcpip.Clock
limit rate.Limit
burst int
}
// afterLoad is invoked by stateify.
func (l *ICMPRateLimiter) afterLoad(context.Context) {
l.limiter = rate.NewLimiter(l.limit, l.burst)
}
// NewICMPRateLimiter returns a global rate limiter for controlling the rate
@@ -46,11 +54,14 @@ func NewICMPRateLimiter(clock tcpip.Clock) *ICMPRateLimiter {
return &ICMPRateLimiter{
clock: clock,
limiter: rate.NewLimiter(icmpLimit, icmpBurst),
limit: icmpLimit,
burst: icmpBurst,
}
}
// SetLimit sets a new Limit for the limiter.
func (l *ICMPRateLimiter) SetLimit(limit rate.Limit) {
l.limit = limit
l.limiter.SetLimitAt(l.clock.Now(), limit)
}
@@ -61,6 +72,7 @@ func (l *ICMPRateLimiter) Limit() rate.Limit {
// SetBurst sets a new burst size for the limiter.
func (l *ICMPRateLimiter) SetBurst(burst int) {
l.burst = burst
l.limiter.SetBurstAt(l.clock.Now(), burst)
}
+85 -6
View File
@@ -21,6 +21,7 @@ import (
"reflect"
"time"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
@@ -33,6 +34,7 @@ const (
NATID TableID = iota
MangleID
FilterID
RawID
NumTables
)
@@ -111,6 +113,27 @@ func DefaultTables(clock tcpip.Clock, rand *rand.Rand) *IPTables {
Postrouting: HookUnset,
},
},
RawID: {
Rules: []Rule{
{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
{Filter: EmptyFilter4(), Target: &ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
},
BuiltinChains: [NumHooks]int{
Prerouting: 0,
Input: HookUnset,
Forward: HookUnset,
Output: 1,
Postrouting: HookUnset,
},
Underflows: [NumHooks]int{
Prerouting: 0,
Input: HookUnset,
Forward: HookUnset,
Output: 1,
Postrouting: HookUnset,
},
},
},
v6Tables: [NumTables]Table{
NATID: {
@@ -176,11 +199,32 @@ func DefaultTables(clock tcpip.Clock, rand *rand.Rand) *IPTables {
Postrouting: HookUnset,
},
},
RawID: {
Rules: []Rule{
{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
{Filter: EmptyFilter6(), Target: &ErrorTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
},
BuiltinChains: [NumHooks]int{
Prerouting: 0,
Input: HookUnset,
Forward: HookUnset,
Output: 1,
Postrouting: HookUnset,
},
Underflows: [NumHooks]int{
Prerouting: 0,
Input: HookUnset,
Forward: HookUnset,
Output: 1,
Postrouting: HookUnset,
},
},
},
connections: ConnTrack{
seed: rand.Uint32(),
clock: clock,
rand: rand,
rng: rand,
},
}
}
@@ -215,6 +259,24 @@ func EmptyNATTable() Table {
}
}
// EmptyRawTable returns a Table with no rules and only the Prerouting and
// Output hooks set, matching the Linux raw table's valid hooks.
func EmptyRawTable() Table {
return Table{
Rules: []Rule{},
BuiltinChains: [NumHooks]int{
Input: HookUnset,
Forward: HookUnset,
Postrouting: HookUnset,
},
Underflows: [NumHooks]int{
Input: HookUnset,
Forward: HookUnset,
Postrouting: HookUnset,
},
}
}
// GetTable returns a table with the given id and IP version. It panics when an
// invalid id is provided.
func (it *IPTables) GetTable(id TableID, ipv6 bool) Table {
@@ -338,6 +400,10 @@ func (it *IPTables) shouldSkipOrPopulateTables(tables []checkTable, pkt *PacketB
// +checkescape
func (it *IPTables) CheckPrerouting(pkt *PacketBuffer, addressEP AddressableEndpoint, inNicName string) bool {
tables := [...]checkTable{ // escapes: on arm this causes an allocation.
{
fn: check,
tableID: RawID,
},
{
fn: check,
tableID: MangleID,
@@ -448,6 +514,10 @@ func (it *IPTables) CheckForward(pkt *PacketBuffer, inNicName, outNicName string
// +checkescape
func (it *IPTables) CheckOutput(pkt *PacketBuffer, r *Route, outNicName string) bool {
tables := [...]checkTable{ // escapes: on arm this causes an allocation.
{
fn: check,
tableID: RawID,
},
{
fn: check,
tableID: MangleID,
@@ -532,7 +602,7 @@ func checkNAT(it *IPTables, table Table, hook Hook, pkt *PacketBuffer, r *Route,
// See check.
func (it *IPTables) checkNAT(table Table, hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
t := pkt.tuple
if t != nil && t.conn.handlePacket(pkt, hook, r) {
if t != nil && IPTHandlePacket(pkt, hook, r) {
return true
}
@@ -561,7 +631,7 @@ func (it *IPTables) checkNAT(table Table, hook Hook, pkt *PacketBuffer, r *Route
//
// If the packet was already NATed, the connection must be NATed.
if !natDone {
t.conn.maybePerformNoopNAT(pkt, hook, r, dnat)
IPTMaybePerformNoopNAT(pkt, hook, r, dnat)
}
return true
@@ -578,7 +648,7 @@ func check(it *IPTables, table Table, hook Hook, pkt *PacketBuffer, r *Route, ad
// Precondition: The packet's network and transport header must be set.
func (it *IPTables) check(table Table, hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
ruleIdx := table.BuiltinChains[hook]
switch verdict := it.checkChain(hook, pkt, table, ruleIdx, r, addressEP, inNicName, outNicName); verdict {
switch verdict := it.checkChain(hook, pkt, table, ruleIdx, r, addressEP, inNicName, outNicName, 0 /* depth */); verdict {
// If the table returns Accept, move on to the next table.
case chainAccept:
return true
@@ -626,10 +696,19 @@ func (it *IPTables) startReaper(interval time.Duration) {
})
}
// maxJumps is the maximum allowed depth of recursion when jumping between
// chains in iptables. This prevents stack overflows from jump loops or deep
// recursion. Linux kernel uses XT_JUMP_STACK_MAX = 16.
const maxJumps = 16
// Preconditions:
// - pkt is a IPv4 packet of at least length header.IPv4MinimumSize.
// - pkt.NetworkHeader is not nil.
func (it *IPTables) checkChain(hook Hook, pkt *PacketBuffer, table Table, ruleIdx int, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) chainVerdict {
func (it *IPTables) checkChain(hook Hook, pkt *PacketBuffer, table Table, ruleIdx int, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string, depth int) chainVerdict {
if depth >= maxJumps {
log.Warningf("iptables: jump depth exceeded max %d", maxJumps)
return chainDrop
}
// Start from ruleIdx and walk the list of rules until a rule gives us
// a verdict.
for ruleIdx < len(table.Rules) {
@@ -650,7 +729,7 @@ func (it *IPTables) checkChain(hook Hook, pkt *PacketBuffer, table Table, ruleId
ruleIdx++
continue
}
switch verdict := it.checkChain(hook, pkt, table, jumpTo, r, addressEP, inNicName, outNicName); verdict {
switch verdict := it.checkChain(hook, pkt, table, jumpTo, r, addressEP, inNicName, outNicName, depth+1); verdict {
case chainAccept:
return chainAccept
case chainDrop:
+1 -1
View File
@@ -92,5 +92,5 @@ func ipTablesinitLockNames() {}
func init() {
ipTablesinitLockNames()
ipTablesprefixIndex = locking.NewMutexClass(reflect.TypeOf(ipTablesRWMutex{}), ipTableslockNames)
ipTablesprefixIndex = locking.NewMutexClass(reflect.TypeFor[ipTablesRWMutex](), ipTableslockNames)
}
+36 -86
View File
@@ -64,8 +64,11 @@ const (
RejectIPv4WithICMPNetUnreachable
RejectIPv4WithICMPHostUnreachable
RejectIPv4WithICMPPortUnreachable
RejectIPv4WithICMPProtUnreachable
RejectIPv4WithICMPEchoReply
RejectIPv4WithICMPNetProhibited
RejectIPv4WithICMPHostProhibited
RejectIPv4WithTCPReset
RejectIPv4WithICMPAdminProhibited
)
@@ -106,9 +109,14 @@ type RejectIPv6WithICMPType int
const (
_ RejectIPv6WithICMPType = iota
RejectIPv6WithICMPNoRoute
RejectIPv6WithICMPAdminProhibited
RejectIPv6WithICMPNotNeighbour
RejectIPv6WithICMPAddrUnreachable
RejectIPv6WithICMPPortUnreachable
RejectIPv6WithICMPAdminProhibited
RejectIPv6WithICMPEchoReply
RejectIPv6WithTCPReset
RejectIPv6WithICMPPolicyFail
RejectIPv6WithICMPRejectRoute
)
// RejectIPv6Target drops packets and sends back an error packet in response to the
@@ -163,7 +171,8 @@ type UserChainTarget struct {
// Action implements Target.Action.
func (*UserChainTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
panic("UserChainTarget should never be called.")
log.BugTracebackOnce(fmt.Errorf("UserChainTarget should never be called"))
return RuleDrop, 0
}
// ReturnTarget returns from the current chain. If the chain is a built-in, the
@@ -298,10 +307,10 @@ type SNATTarget struct {
}
func dnatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address, changePort, changeAddress bool) (RuleVerdict, int) {
return natAction(pkt, hook, r, portOrIdentRange{start: port, size: 1}, address, true /* dnat */, changePort, changeAddress)
return natAction(pkt, hook, r, PortOrIdentRange{Start: port, Size: 1}, address, true /* dnat */, changePort, changeAddress)
}
func targetPortRangeForTCPAndUDP(originalSrcPort uint16) portOrIdentRange {
func targetPortRangeForTCPAndUDP(originalSrcPort uint16) PortOrIdentRange {
// As per iptables(8),
//
// If no port range is specified, then source ports below 512 will be
@@ -310,16 +319,16 @@ func targetPortRangeForTCPAndUDP(originalSrcPort uint16) portOrIdentRange {
// 1024 or above.
switch {
case originalSrcPort < 512:
return portOrIdentRange{start: 1, size: 511}
return PortOrIdentRange{Start: 1, Size: 511}
case originalSrcPort < 1024:
return portOrIdentRange{start: 1, size: 1023}
return PortOrIdentRange{Start: 1, Size: 1023}
default:
return portOrIdentRange{start: 1024, size: math.MaxUint16 - 1023}
return PortOrIdentRange{Start: 1024, Size: math.MaxUint16 - 1023}
}
}
func snatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address, changePort, changeAddress bool) (RuleVerdict, int) {
portsOrIdents := portOrIdentRange{start: port, size: 1}
portsOrIdents := PortOrIdentRange{Start: port, Size: 1}
switch pkt.TransportProtocolNumber {
case header.UDPProtocolNumber:
@@ -335,20 +344,20 @@ func snatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcp
// behaviour.
//
// https://github.com/torvalds/linux/blob/58e1100fdc5990b0cc0d4beaf2562a92e621ac7d/net/netfilter/nf_nat_core.c#L391
portsOrIdents = portOrIdentRange{start: 0, size: math.MaxUint16 + 1}
portsOrIdents = PortOrIdentRange{Start: 0, Size: math.MaxUint16 + 1}
}
return natAction(pkt, hook, r, portsOrIdents, address, false /* dnat */, changePort, changeAddress)
}
func natAction(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents portOrIdentRange, address tcpip.Address, dnat, changePort, changeAddress bool) (RuleVerdict, int) {
func natAction(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents PortOrIdentRange, address tcpip.Address, dnat, changePort, changeAddress bool) (RuleVerdict, int) {
// Drop the packet if network and transport header are not set.
if len(pkt.NetworkHeader().Slice()) == 0 || len(pkt.TransportHeader().Slice()) == 0 {
return RuleDrop, 0
}
if t := pkt.tuple; t != nil {
t.conn.performNAT(pkt, hook, r, portsOrIdents, address, dnat, changePort, changeAddress)
IPTPerformNAT(pkt, hook, r, portsOrIdents, address, dnat, changePort, changeAddress)
return RuleAccept, 0
}
@@ -413,81 +422,22 @@ func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addre
return snatAction(pkt, hook, r, 0 /* port */, address, true /* changePort */, true /* changeAddress */)
}
func rewritePacket(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)
}
}
// CTTarget is a no-op implementation of the CT (conntrack) target used in the
// raw table. In Linux, CT --zone sets conntrack zones for connection tracking
// isolation. gVisor's conntrack does not support zones, so this target simply
// accepts the packet, allowing iptables-restore to load rulesets that reference
// CT targets (e.g. Istio with DNS capture enabled).
//
// +stateify savable
type CTTarget struct {
// NetworkProtocol is the network protocol the target is used with.
NetworkProtocol tcpip.NetworkProtocolNumber
if updatePseudoHeader {
var oldAddr tcpip.Address
if updateSRCFields {
oldAddr = n.SourceAddress()
} else {
oldAddr = n.DestinationAddress()
}
// Zone is the conntrack zone ID. Stored but not acted upon.
Zone uint16
}
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 ICMPv4 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)
}
// Action implements Target.Action. It is a no-op that accepts the packet.
func (*CTTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
return RuleAccept, 0
}
+5 -5
View File
@@ -82,7 +82,7 @@ const (
type IPTables struct {
connections ConnTrack
reaper tcpip.Timer
reaper tcpip.Timer `state:"nosave"`
mu ipTablesRWMutex `state:"nosave"`
// v4Tables and v6tables map tableIDs to tables. They hold builtin
@@ -283,7 +283,9 @@ func (fl IPHeaderFilter) match(pkt *PacketBuffer, hook Hook, inNicName, outNicNa
case header.IPv6ProtocolNumber:
hdr := header.IPv6(pkt.NetworkHeader().Slice())
transProto = hdr.TransportProtocol()
// The transport protocol may be preceded by IPv6 extension headers, so
// use the protocol from parsing (see IPv6.TransportProtocol).
transProto = pkt.TransportProtocolNumber
dstAddr = hdr.DestinationAddress()
srcAddr = hdr.SourceAddress()
@@ -305,7 +307,7 @@ func (fl IPHeaderFilter) match(pkt *PacketBuffer, hook Hook, inNicName, outNicNa
switch hook {
case Prerouting, Input:
return matchIfName(inNicName, fl.InputInterface, fl.InputInterfaceInvert)
case Output:
case Postrouting, Output:
return matchIfName(outNicName, fl.OutputInterface, fl.OutputInterfaceInvert)
case Forward:
if !matchIfName(inNicName, fl.InputInterface, fl.InputInterfaceInvert) {
@@ -316,8 +318,6 @@ func (fl IPHeaderFilter) match(pkt *PacketBuffer, hook Hook, inNicName, outNicNa
return false
}
return true
case Postrouting:
return true
default:
panic(fmt.Sprintf("unknown hook: %d", hook))
@@ -92,5 +92,5 @@ func multiPortEndpointinitLockNames() {}
func init() {
multiPortEndpointinitLockNames()
multiPortEndpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(multiPortEndpointRWMutex{}), multiPortEndpointlockNames)
multiPortEndpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[multiPortEndpointRWMutex](), multiPortEndpointlockNames)
}
+584
View File
@@ -0,0 +1,584 @@
// Copyright 2020 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 stack
import (
"fmt"
"math"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
// NATType represents the type of NAT.
type NATType int
const (
// SNAT is source NAT.
SNAT NATType = iota
// DNAT is destination NAT.
DNAT
// NATUnknown is unknown NAT type.
NATUnknown
)
// ToNATType converts a uint8 to a NATType.
func ToNATType(t uint8) NATType {
switch t {
case 0:
return SNAT
case 1:
return DNAT
}
return NATUnknown
}
func (natType NATType) String() string {
switch natType {
case SNAT:
return "SNAT"
case DNAT:
return "DNAT"
default:
return "NATUnknown"
}
}
// NfNATPriority returns the priority of the NAT hook.
// Check `ipv4/ipv6_nat_ops` in nf_nat_proto.c.
func NfNATPriority(hook NFHook) (int, bool) {
switch hook {
case NFPrerouting:
// NF_IP_PRI_NAT_DST
return -100, true
case NFPostrouting:
// NF_IP_PRI_NAT_SRC
return 100, true
case NFOutput:
// NF_IP_PRI_NAT_DST
return -100, true
case NFInput:
// NF_IP_PRI_NAT_SRC
return 100, true
}
// NAT is not supported for other hooks.
return 0, false
}
// NfHookToNATType returns the applicable NAT type
// for the given netfilter hook.
func NfHookToNATType(hook NFHook) NATType {
switch hook {
case NFPrerouting, NFOutput:
return DNAT
case NFInput, NFPostrouting:
return SNAT
}
return NATUnknown
}
// handlePacketOpts contains the options for handlePacket.
type handlePacketOpts struct {
fullChecksum bool
updatePseudoHeader bool
natType NATType
}
// 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 handlePacket(pkt *PacketBuffer, opts *handlePacketOpts) bool {
if opts == nil || opts.natType == NATUnknown {
return false
}
netHdr, transHdr, isICMPError, ok := pkt.GetHeaders()
if !ok {
return false
}
natDone := &pkt.snatDone
dnat := false
if opts.natType == DNAT {
natDone = &pkt.dnatDone
dnat = true
}
if *natDone {
panic(fmt.Sprintf("packet already had NAT: %s performed; pkt=%#v", opts.natType, 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
cn := pkt.tuple.conn
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
}
UpdateHeaders(
netHdr,
transHdr,
!dnat != isICMPError,
opts.fullChecksum,
opts.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
}
// IPTHandlePacket handles and applies NAT to the packet if required.
func IPTHandlePacket(pkt *PacketBuffer, hook Hook, r *Route) bool {
opts := handlePacketOpts{
fullChecksum: false,
updatePseudoHeader: false,
natType: SNAT,
}
requiresTXTransportChecksum := false
if r != nil {
requiresTXTransportChecksum = r.RequiresTXTransportChecksum()
}
switch hook {
case Prerouting:
opts.fullChecksum = true
opts.updatePseudoHeader = true
opts.natType = DNAT
case Input:
case Forward:
panic("should not handle packet in the forwarding hook")
case Output:
opts.natType = DNAT
fallthrough
case Postrouting:
if pkt.TransportProtocolNumber == header.TCPProtocolNumber && pkt.GSOOptions.Type != GSONone && pkt.GSOOptions.NeedsCsum {
opts.updatePseudoHeader = true
} else if requiresTXTransportChecksum {
opts.fullChecksum = true
opts.updatePseudoHeader = true
}
default:
panic(fmt.Sprintf("unrecognized hook = %d", hook))
}
return handlePacket(pkt, &opts)
}
// PortOrIdentRange represents a range of ports or idents
// range to use for NAT.
type PortOrIdentRange struct {
Start uint16
Size uint32
}
// ConfigureNAT 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.
//
// Returns whether the NAT was configured or not.
func (cn *conn) ConfigureNAT(portsOrIdents PortOrIdentRange, natAddress tcpip.Address, natType NATType, changePort, changeAddress bool) bool {
lastPortOrIdentU32 := uint32(portsOrIdents.Start) + portsOrIdents.Size - 1
if lastPortOrIdentU32 > math.MaxUint16 {
log.Warningf("got lastPortOrIdent = %d, want <= MaxUint16(=%d); portsOrIdents=%#v", lastPortOrIdentU32, math.MaxUint16, portsOrIdents)
return false
}
lastPortOrIdent := uint16(lastPortOrIdentU32)
cn.mu.Lock()
defer cn.mu.Unlock()
var manip *manipType
var address *tcpip.Address
var portOrIdent *uint16
if natType == 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 true
}
*manip = manipPerformed
if changeAddress {
*address = natAddress
}
// Everything below here is port-fiddling.
// TODO: b/486197011 - Handle reply tuple collisions when changePort
// is false. Check for tuple uniqueness or find a unique tuple.
// Ref: net/netfilter/nf_nat_core.c:nf_nat_l4proto_unique_tuple.
if !changePort {
return true
}
// 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 true
}
}
// 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.rng.Uint32()
for i := uint32(0); i < maxAttempts; i++ {
newPortOrIdentU32 := uint32(portsOrIdents.Start) + (randOffset+i)%portsOrIdents.Size
if newPortOrIdentU32 > math.MaxUint16 {
log.Warningf("got newPortOrIdentU32 = %d, want <= MaxUint16(=%d); portsOrIdents=%#v", newPortOrIdentU32, math.MaxUint16, portsOrIdents)
continue
}
*portOrIdent = uint16(newPortOrIdentU32)
if other := cn.ct.connForTID(cn.reply.tupleID); other == nil {
// We found a unique tuple!
return true
}
}
if maxAttempts == portsOrIdents.Size {
// We already tried all the ports/idents in the range so no need to keep
// trying.
return false
}
if maxAttempts < minAttemptsToContinue {
return false
}
}
// 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).
}
// IPTPerformNAT performs NAT on the packet and updates the connection.
// Used by IPTables.
func IPTPerformNAT(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents PortOrIdentRange, natAddress tcpip.Address, dnat, changePort, changeAddress bool) {
// 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.
_ = IPTHandlePacket(pkt, hook, r)
}()
cn := pkt.tuple.conn
natType := SNAT
if dnat {
natType = DNAT
}
_ = cn.ConfigureNAT(portsOrIdents, natAddress, natType, changePort, changeAddress)
}
// IPTMaybePerformNoopNAT can apply NAT or configure a no-op NAT.
// 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 IPTMaybePerformNoopNAT(pkt *PacketBuffer, hook Hook, r *Route, dnat bool) {
cn := pkt.tuple.conn
cn.mu.Lock()
var manip *manipType
if dnat {
manip = &cn.destinationManip
} else {
manip = &cn.sourceManip
}
if *manip != manipNotPerformed {
cn.mu.Unlock()
_ = IPTHandlePacket(pkt, hook, r)
return
}
if dnat {
*manip = manipPerformedNoop
cn.mu.Unlock()
_ = IPTHandlePacket(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 */)
}
// NFTApplyNAT applies NAT to the packet and updates the connection.
// Similar to IPTHandlePacket but for NFTables hooks.
func NFTApplyNAT(pkt *PacketBuffer, hook NFHook, rt *Route) bool {
requiresTXTransportChecksum := false
if rt != nil {
requiresTXTransportChecksum = rt.RequiresTXTransportChecksum()
}
opts := handlePacketOpts{
fullChecksum: false,
updatePseudoHeader: false,
natType: SNAT,
}
switch hook {
case NFPrerouting:
opts.fullChecksum = true
opts.updatePseudoHeader = true
opts.natType = DNAT
case NFInput:
case NFForward:
panic("should not handle packet in the forwarding hook")
case NFOutput:
opts.natType = DNAT
fallthrough
case NFPostrouting:
if pkt.TransportProtocolNumber == header.TCPProtocolNumber && pkt.GSOOptions.Type != GSONone && pkt.GSOOptions.NeedsCsum {
opts.updatePseudoHeader = true
} else if requiresTXTransportChecksum {
opts.fullChecksum = true
opts.updatePseudoHeader = true
}
default:
panic(fmt.Sprintf("unrecognized hook = %d", hook))
}
return handlePacket(pkt, &opts)
}
// IsNATConfigured returns whether NAT has been configured for the given NAT type.
func (cn *conn) IsNATConfigured(natType NATType) bool {
cn.mu.RLock()
defer cn.mu.RUnlock()
switch natType {
case SNAT:
return cn.sourceManip != manipNotPerformed
case DNAT:
return cn.destinationManip != manipNotPerformed
}
return false
}
// ConfigureNoopNAT configures the connection for no-op NAT.
// Similar to the func `IPTMaybePerformNoopNAT` except that this one only configures NO-OP NAT and is independent of IPTables.
func (cn *conn) ConfigureNoopNAT(pkt *PacketBuffer, natType NATType) bool {
cn.mu.Lock()
var manip *manipType
if natType == DNAT {
manip = &cn.destinationManip
} else {
manip = &cn.sourceManip
}
if *manip != manipNotPerformed {
cn.mu.Unlock()
return true
}
if natType == DNAT {
*manip = manipPerformedNoop
cn.mu.Unlock()
return true
}
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.
portsOrIdents := PortOrIdentRange{Start: 0, Size: math.MaxUint16 + 1}
// However, we need to extract the port from packet.
var port uint16
switch pkt.TransportProtocolNumber {
case header.UDPProtocolNumber:
port = header.UDP(pkt.TransportHeader().Slice()).SourcePort()
case header.TCPProtocolNumber:
port = header.TCP(pkt.TransportHeader().Slice()).SourcePort()
}
if port != 0 {
portsOrIdents = targetPortRangeForTCPAndUDP(port)
}
return cn.ConfigureNAT(portsOrIdents, tcpip.Address{}, natType, true /* changePort */, false /* changeAddress */)
}
// ConfigureMasquerade configures the connection for masquerade.
func (cn *conn) configureMasquerade(pkt *PacketBuffer, route *Route, stk *Stack, ports PortOrIdentRange, changePort bool) bool {
srcAddr := pkt.Network().SourceAddress()
if srcAddr == header.IPv4Any || srcAddr == header.IPv6Any {
return false
}
// Masquerade is only supported for postrouting.
if route == nil {
return false
}
// Get the network endpoint for the outgoing interface to find its primary address.
netEP, err := stk.GetNetworkEndpoint(route.NICID(), route.NetProto())
if err != nil {
return false
}
addressEP, ok := netEP.(AddressableEndpoint)
if !ok {
return false
}
// Ref: net/netfilter/nf_nat_masquerade.c:nf_nat_masquerade_ipv[4|6]()
// Use the next hop address as the destination address if it is set.
nh := route.NextHop()
if nh.Len() == 0 {
nh = pkt.Network().DestinationAddress()
}
// addressEP is expected to be set for the postrouting hook.
// Find the outgoing primary address for the destination address.
ep := addressEP.AcquireOutgoingPrimaryAddress(nh, tcpip.Address{} /* srcHint */, false /* allowExpired */)
if ep == nil {
// No address exists that we can use as a source address.
return false
}
address := ep.AddressWithPrefix().Address
ep.DecRef()
// Configure NAT for the packet to change the source address.
return cn.ConfigureNAT(ports, address, SNAT, changePort, true /* changeAddress */)
}
+3 -1
View File
@@ -45,7 +45,9 @@ type dynamicCacheEntry struct {
type neighborCacheMu struct {
neighborCacheRWMutex `state:"nosave"`
cache map[tcpip.Address]*neighborEntry
// +checklocks:neighborCacheRWMutex
cache map[tcpip.Address]*neighborEntry
// +checklocks:neighborCacheRWMutex
dynamic dynamicCacheEntry
}
@@ -92,5 +92,5 @@ func neighborCacheinitLockNames() {}
func init() {
neighborCacheinitLockNames()
neighborCacheprefixIndex = locking.NewMutexClass(reflect.TypeOf(neighborCacheRWMutex{}), neighborCachelockNames)
neighborCacheprefixIndex = locking.NewMutexClass(reflect.TypeFor[neighborCacheRWMutex](), neighborCachelockNames)
}
+17 -12
View File
@@ -90,17 +90,22 @@ type timer struct {
type neighborEntryMu struct {
neighborEntryRWMutex `state:"nosave"`
// +checklocks:neighborEntryRWMutex
neigh NeighborEntry
// done is closed when address resolution is complete. It is nil iff s is
// incomplete and resolution is not yet in progress.
// +checklocks:neighborEntryRWMutex
done chan struct{} `state:"nosave"`
// onResolve is called with the result of address resolution.
// +checklocks:neighborEntryRWMutex
onResolve []func(LinkResolutionResult) `state:"nosave"`
// +checklocks:neighborEntryRWMutex
isRouter bool
// +checklocks:neighborEntryRWMutex
timer timer
}
@@ -163,7 +168,7 @@ func newStaticNeighborEntry(cache *neighborCache, addr tcpip.Address, linkAddr t
// notifyCompletionLocked notifies those waiting for address resolution, with
// the link address if resolution completed successfully.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) notifyCompletionLocked(err tcpip.Error) {
res := LinkResolutionResult{LinkAddress: e.mu.neigh.LinkAddr, Err: err}
for _, callback := range e.mu.onResolve {
@@ -185,7 +190,7 @@ func (e *neighborEntry) notifyCompletionLocked(err tcpip.Error) {
// keyword but allows tests that use manual clocks to deterministically
// wait for this work to complete.
e.cache.nic.stack.clock.AfterFunc(0, func() {
e.cache.nic.linkResQueue.dequeue(ch, e.mu.neigh.LinkAddr, err)
e.cache.nic.linkResQueue.dequeue(ch, res.LinkAddress, err)
})
}
}
@@ -193,7 +198,7 @@ func (e *neighborEntry) notifyCompletionLocked(err tcpip.Error) {
// dispatchAddEventLocked signals to stack's NUD Dispatcher that the entry has
// been added.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) dispatchAddEventLocked() {
if nudDisp := e.cache.nic.stack.nudDisp; nudDisp != nil {
nudDisp.OnNeighborAdded(e.cache.nic.id, e.mu.neigh)
@@ -203,7 +208,7 @@ func (e *neighborEntry) dispatchAddEventLocked() {
// dispatchChangeEventLocked signals to stack's NUD Dispatcher that the entry
// has changed state or link-layer address.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) dispatchChangeEventLocked() {
if nudDisp := e.cache.nic.stack.nudDisp; nudDisp != nil {
nudDisp.OnNeighborChanged(e.cache.nic.id, e.mu.neigh)
@@ -213,7 +218,7 @@ func (e *neighborEntry) dispatchChangeEventLocked() {
// dispatchRemoveEventLocked signals to stack's NUD Dispatcher that the entry
// has been removed.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) dispatchRemoveEventLocked() {
if nudDisp := e.cache.nic.stack.nudDisp; nudDisp != nil {
nudDisp.OnNeighborRemoved(e.cache.nic.id, e.mu.neigh)
@@ -223,7 +228,7 @@ func (e *neighborEntry) dispatchRemoveEventLocked() {
// cancelTimerLocked cancels the currently scheduled action, if there is one.
// Entries in Unknown, Stale, or Static state do not have a scheduled action.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) cancelTimerLocked() {
if e.mu.timer.timer != nil {
e.mu.timer.timer.Stop()
@@ -235,7 +240,7 @@ func (e *neighborEntry) cancelTimerLocked() {
// removeLocked prepares the entry for removal.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) removeLocked() {
e.mu.neigh.UpdatedAt = e.cache.nic.stack.clock.NowMonotonic()
e.dispatchRemoveEventLocked()
@@ -255,7 +260,7 @@ func (e *neighborEntry) removeLocked() {
//
// Follows the logic defined in RFC 4861 section 7.3.3.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) setStateLocked(next NeighborState) {
e.cancelTimerLocked()
@@ -363,7 +368,7 @@ func (e *neighborEntry) setStateLocked(next NeighborState) {
//
// Follows the logic defined in RFC 4861 section 7.3.3.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) handlePacketQueuedLocked(localAddr tcpip.Address) {
switch e.mu.neigh.State {
case Unknown, Unreachable:
@@ -442,7 +447,7 @@ func (e *neighborEntry) handlePacketQueuedLocked(localAddr tcpip.Address) {
//
// Follows the logic defined in RFC 4861 section 7.2.3.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) handleProbeLocked(remoteLinkAddr tcpip.LinkAddress) {
// Probes MUST be silently discarded if the target address is tentative, does
// not exist, or not bound to the NIC as per RFC 4861 section 7.2.3. These
@@ -505,7 +510,7 @@ func (e *neighborEntry) handleProbeLocked(remoteLinkAddr tcpip.LinkAddress) {
// Generated Addresses (CGA), as defined in RFC 3972. This ensures that the
// claimed source of an NDP message is the owner of the claimed address.
//
// Precondition: e.mu MUST be locked.
// +checklocks:e.mu.neighborEntryRWMutex
func (e *neighborEntry) handleConfirmationLocked(linkAddr tcpip.LinkAddress, flags ReachabilityConfirmationFlags) {
switch e.mu.neigh.State {
case Incomplete:
@@ -579,7 +584,7 @@ func (e *neighborEntry) handleConfirmationLocked(linkAddr tcpip.LinkAddress, fla
// here.
ep := e.cache.nic.getNetworkEndpoint(header.IPv6ProtocolNumber)
if ep == nil {
panic(fmt.Sprintf("have a neighbor entry for an IPv6 router but no IPv6 network endpoint"))
panic("have a neighbor entry for an IPv6 router but no IPv6 network endpoint")
}
if ndpEP, ok := ep.(NDPEndpoint); ok {
@@ -92,5 +92,5 @@ func neighborEntryinitLockNames() {}
func init() {
neighborEntryinitLockNames()
neighborEntryprefixIndex = locking.NewMutexClass(reflect.TypeOf(neighborEntryRWMutex{}), neighborEntrylockNames)
neighborEntryprefixIndex = locking.NewMutexClass(reflect.TypeFor[neighborEntryRWMutex](), neighborEntrylockNames)
}
@@ -0,0 +1,149 @@
// Copyright 2025 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 stack
import (
"fmt"
)
// NFTablesInterface is an interface for evaluating chains.
type NFTablesInterface interface {
CheckPrerouting(pkt *PacketBuffer, route *Route, af AddressFamily) bool
CheckInput(pkt *PacketBuffer, route *Route, af AddressFamily) bool
CheckForward(pkt *PacketBuffer, route *Route, af AddressFamily) bool
CheckOutput(pkt *PacketBuffer, route *Route, af AddressFamily) bool
CheckPostrouting(pkt *PacketBuffer, route *Route, af AddressFamily) bool
CheckIngress(pkt *PacketBuffer, route *Route, af AddressFamily) bool
CheckEgress(pkt *PacketBuffer, route *Route, af AddressFamily) bool
}
// NFHook describes specific points in the pipeline where chains can be attached.
// Each address family has its own set of hooks (defined in supportedHooks).
// For IPv4/IPv6/Inet and Bridge, there are two possible pipelines:
// 1. Prerouting -> Input -> ~Local Process~ -> Output -> Postrouting
// 2. Prerouting -> Forward -> Postrouting
type NFHook uint16
const (
// NFPrerouting Hook is supported by IPv4/IPv6/Inet, Bridge Families.
// Prerouting is evaluated before a packet is routed to applications or forwarded.
NFPrerouting NFHook = iota
// NFInput Hook is supported by IPv4/IPv6/Inet, Bridge, ARP Families.
// Input is evaluated before a packet reaches an application.
NFInput
// NFForward Hook is supported by IPv4/IPv6/Inet, Bridge Families.
// Forward is evaluated once it's decided that a packet should be forwarded to another host.
NFForward
// NFOutput Hook is supported by IPv4/IPv6/Inet, Bridge, ARP Families.
// Output is evaluated after a packet is written by an application to be sent out.
NFOutput
// NFPostrouting Hook is supported by IPv4/IPv6/Inet, Bridge Families.
// Postrouting is evaluated just before a packet goes out on the wire.
NFPostrouting
// NFIngress Hook is supported by IPv4/IPv6/Inet, Bridge, Netdev Families.
// Ingress is the first hook evaluated, even before prerouting.
NFIngress
// NFEgress Hook is supported by Netdev Family only.
// Egress is the last hook evaluated, after the packet has been processed by the
// application and is being prepared for transmission out of the network interface.
NFEgress
// NFNumHooks is the number of hooks supported by nftables.
NFNumHooks
)
// hookStrings maps hooks to their string representation.
var hookStrings = map[NFHook]string{
NFPrerouting: "Prerouting",
NFInput: "Input",
NFForward: "Forward",
NFOutput: "Output",
NFPostrouting: "Postrouting",
NFIngress: "Ingress",
NFEgress: "Egress",
}
// String for Hook returns the name of the hook.
func (h NFHook) String() string {
if hook, ok := hookStrings[h]; ok {
return hook
}
panic(fmt.Sprintf("invalid NFHook: %d", int(h)))
}
// AddressFamily describes the 6 address families supported by nftables.
// The address family determines the type of packets processed, and each family
// contains hooks at specific stages of the packet processing pipeline.
type AddressFamily int
const (
// Unspec represents an unspecified address family.
Unspec AddressFamily = iota
// IP represents IPv4 Family.
IP
// IP6 represents IPv6 Family.
IP6
// Inet represents Internet Family for hybrid IPv4/IPv6 rules.
Inet
// Arp represents ARP Family for IPv4 ARP packets.
Arp
// Bridge represents Bridge Family for Ethernet packets across bridge devices.
Bridge
// Netdev represents Netdev Family for packets on ingress and egress.
Netdev
// NumAFs is the number of address families supported by nftables.
NumAFs
)
// AddressFamilyStrings maps address families to their string representation.
var AddressFamilyStrings = map[AddressFamily]string{
Unspec: "UNSPEC",
IP: "IPv4",
IP6: "IPv6",
Inet: "Internet (Both IPv4/IPv6)",
Arp: "ARP",
Bridge: "Bridge",
Netdev: "Netdev",
}
// ValidateAddressFamily ensures the family address is valid (within bounds).
// Unspecified address family is not valid. It is only used to reference all address families.
func ValidateAddressFamily(family AddressFamily) error {
if family < 1 || family >= NumAFs {
return fmt.Errorf("invalid address family: %d", int(family))
}
return nil
}
// String for AddressFamily returns the name of the address family.
func (f AddressFamily) String() string {
if af, ok := AddressFamilyStrings[f]; ok {
return af
}
panic(fmt.Sprintf("invalid address family: %d", int(f)))
}
+38 -18
View File
@@ -17,6 +17,7 @@ package stack
import (
"fmt"
"reflect"
"sort"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/tcpip"
@@ -43,6 +44,7 @@ type nic struct {
stack *Stack
id tcpip.NICID
name string
kind string
context NICContext
stats sharedStats
@@ -71,7 +73,7 @@ type nic struct {
// complete.
linkResQueue packetsPendingLinkResolution
// packetEPsMu protects annotated fields below.
// packetEPsMu protects packetEPs.
packetEPsMu packetEPsRWMutex `state:"nosave"`
// eps is protected by the mutex, but the values contained in it are not.
@@ -190,17 +192,14 @@ func newNIC(stack *Stack, id tcpip.NICID, ep LinkEndpoint, opts NICOptions) *nic
networkEndpoints: make(map[tcpip.NetworkProtocolNumber]NetworkEndpoint),
linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]*linkResolver),
duplicateAddressDetectors: make(map[tcpip.NetworkProtocolNumber]DuplicateAddressDetector),
packetEPs: make(map[tcpip.NetworkProtocolNumber]*packetEndpointList),
qDisc: qDisc,
deliverLinkPackets: opts.DeliverLinkPackets,
experimentIPOptionEnabled: opts.EnableExperimentIPOption,
kind: opts.Kind,
}
nic.linkResQueue.init(nic)
nic.packetEPsMu.Lock()
defer nic.packetEPsMu.Unlock()
nic.packetEPs = make(map[tcpip.NetworkProtocolNumber]*packetEndpointList)
resolutionRequired := ep.Capabilities()&CapabilityResolutionRequired != 0
for _, netProto := range stack.networkProtocols {
@@ -317,7 +316,7 @@ func (n *nic) enable() tcpip.Error {
// resources. This guarantees no packets between this NIC and the network
// stack.
//
// It returns an action that has to be excuted after releasing the Stack lock
// It returns an action that has to be executed after releasing the Stack lock
// and any error encountered.
func (n *nic) remove(closeLinkEndpoint bool) (func(), tcpip.Error) {
n.enableDisableMu.Lock()
@@ -574,12 +573,22 @@ func (n *nic) allPermanentAddresses() []tcpip.ProtocolAddress {
// primaryAddresses returns the primary addresses associated with this NIC.
func (n *nic) primaryAddresses() []tcpip.ProtocolAddress {
var addrs []tcpip.ProtocolAddress
for p, ep := range n.networkEndpoints {
addressableEndpoint, ok := ep.(AddressableEndpoint)
protocolNumbers := make([]tcpip.NetworkProtocolNumber, 0, len(n.networkEndpoints))
for p := range n.networkEndpoints {
protocolNumbers = append(protocolNumbers, p)
}
// Sort the network protocol numbers so that IPv4 address is always
// added to the list before IPv6 address.
sort.Slice(protocolNumbers, func(i, j int) bool {
return protocolNumbers[i] < protocolNumbers[j]
})
for _, p := range protocolNumbers {
addressableEndpoint, ok := n.networkEndpoints[p].(AddressableEndpoint)
if !ok {
continue
}
for _, a := range addressableEndpoint.PrimaryAddresses() {
addrs = append(addrs, tcpip.ProtocolAddress{Protocol: p, AddressWithPrefix: a})
}
@@ -834,23 +843,34 @@ func (n *nic) DeliverLinkPacket(protocol tcpip.NetworkProtocolNumber, pkt *Packe
// DeliverTransportPacket delivers the packets to the appropriate transport
// protocol endpoint.
func (n *nic) DeliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt *PacketBuffer) TransportPacketDisposition {
res, _ := n.deliverTransportPacket(protocol, pkt)
return res
}
// DeliverTransportPacketWithDefaultHandlerResult implements
// TransportDispatcherWithDefaultHandlerResult.DeliverTransportPacketWithDefaultHandlerResult.
func (n *nic) DeliverTransportPacketWithDefaultHandlerResult(protocol tcpip.TransportProtocolNumber, pkt *PacketBuffer) (TransportPacketDisposition, bool) {
return n.deliverTransportPacket(protocol, pkt)
}
func (n *nic) deliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt *PacketBuffer) (TransportPacketDisposition, bool) {
state, ok := n.stack.transportProtocols[protocol]
if !ok {
n.stats.unknownL4ProtocolRcvdPacketCounts.Increment(uint64(protocol))
return TransportPacketProtocolUnreachable
return TransportPacketProtocolUnreachable, false
}
transProto := state.proto
if len(pkt.TransportHeader().Slice()) == 0 {
n.stats.malformedL4RcvdPackets.Increment()
return TransportPacketHandled
return TransportPacketHandled, false
}
srcPort, dstPort, err := transProto.ParsePorts(pkt.TransportHeader().Slice())
if err != nil {
n.stats.malformedL4RcvdPackets.Increment()
return TransportPacketHandled
return TransportPacketHandled, false
}
netProto, ok := n.stack.networkProtocols[pkt.NetworkProtocolNumber]
@@ -866,13 +886,13 @@ func (n *nic) DeliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt
RemoteAddress: src,
}
if n.stack.demux.deliverPacket(protocol, pkt, id) {
return TransportPacketHandled
return TransportPacketHandled, false
}
// Try to deliver to per-stack default handler.
if state.defaultHandler != nil {
if state.defaultHandler(id, pkt) {
return TransportPacketHandled
return TransportPacketHandled, true
}
}
@@ -882,11 +902,11 @@ func (n *nic) DeliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt
switch res := transProto.HandleUnknownDestinationPacket(id, pkt); res {
case UnknownDestinationPacketMalformed:
n.stats.malformedL4RcvdPackets.Increment()
return TransportPacketHandled
return TransportPacketHandled, false
case UnknownDestinationPacketUnhandled:
return TransportPacketDestinationPortUnreachable
return TransportPacketDestinationPortUnreachable, false
case UnknownDestinationPacketHandled:
return TransportPacketHandled
return TransportPacketHandled, false
default:
panic(fmt.Sprintf("unrecognized result from HandleUnknownDestinationPacket = %d", res))
}
+1 -1
View File
@@ -92,5 +92,5 @@ func nicinitLockNames() {}
func init() {
nicinitLockNames()
nicprefixIndex = locking.NewMutexClass(reflect.TypeOf(nicRWMutex{}), niclockNames)
nicprefixIndex = locking.NewMutexClass(reflect.TypeFor[nicRWMutex](), niclockNames)
}
+11 -4
View File
@@ -320,17 +320,24 @@ func calcMaxRandomFactor(minRandomFactor float32) float32 {
type nudStateMu struct {
sync.RWMutex `state:"nosave"`
// +checklocks:RWMutex
config NUDConfigurations
// reachableTime is the duration to wait for a REACHABLE entry to
// transition into STALE after inactivity. This value is calculated with
// the algorithm defined in RFC 4861 section 6.3.2.
//
// +checklocks:RWMutex
reachableTime time.Duration
expiration tcpip.MonotonicTime
// +checklocks:RWMutex
expiration tcpip.MonotonicTime
// +checklocks:RWMutex
prevBaseReachableTime time.Duration
prevMinRandomFactor float32
prevMaxRandomFactor float32
// +checklocks:RWMutex
prevMinRandomFactor float32
// +checklocks:RWMutex
prevMaxRandomFactor float32
}
// NUDState stores states needed for calculating reachable time.
@@ -405,7 +412,7 @@ func (s *NUDState) ReachableTime() time.Duration {
// changes. In such cases, an implementation SHOULD ensure that a new
// random value gets re-computed at least once every few hours.
//
// s.mu MUST be locked for writing.
// +checklocks:s.mu.RWMutex
func (s *NUDState) recomputeReachableTimeLocked() {
s.mu.prevBaseReachableTime = s.mu.config.BaseReachableTime
s.mu.prevMinRandomFactor = s.mu.config.MinRandomFactor
@@ -20,6 +20,7 @@ import (
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/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)
}
}
@@ -92,5 +92,5 @@ func packetEndpointListinitLockNames() {}
func init() {
packetEndpointListinitLockNames()
packetEndpointListprefixIndex = locking.NewMutexClass(reflect.TypeOf(packetEndpointListRWMutex{}), packetEndpointListlockNames)
packetEndpointListprefixIndex = locking.NewMutexClass(reflect.TypeFor[packetEndpointListRWMutex](), packetEndpointListlockNames)
}
@@ -92,5 +92,5 @@ func packetEPsinitLockNames() {}
func init() {
packetEPsinitLockNames()
packetEPsprefixIndex = locking.NewMutexClass(reflect.TypeOf(packetEPsRWMutex{}), packetEPslockNames)
packetEPsprefixIndex = locking.NewMutexClass(reflect.TypeFor[packetEPsRWMutex](), packetEPslockNames)
}
@@ -60,5 +60,5 @@ func packetsPendingLinkResolutioninitLockNames() {}
func init() {
packetsPendingLinkResolutioninitLockNames()
packetsPendingLinkResolutionprefixIndex = locking.NewMutexClass(reflect.TypeOf(packetsPendingLinkResolutionMutex{}), packetsPendingLinkResolutionlockNames)
packetsPendingLinkResolutionprefixIndex = locking.NewMutexClass(reflect.TypeFor[packetsPendingLinkResolutionMutex](), packetsPendingLinkResolutionlockNames)
}
@@ -39,12 +39,16 @@ type packetsPendingLinkResolutionMu struct {
// The packets to send once the resolver completes.
//
// The link resolution channel is used as the key for this map.
//
// +checklocks:packetsPendingLinkResolutionMutex
packets map[<-chan struct{}][]pendingPacket
// FIFO of channels used to cancel the oldest goroutine waiting for
// link-address resolution.
//
// cancelChans holds the same channels that are used as keys to packets.
//
// +checklocks:packetsPendingLinkResolutionMutex
cancelChans []<-chan struct{}
}
@@ -183,6 +187,8 @@ func (f *packetsPendingLinkResolution) enqueue(r *Route, pkt *PacketBuffer) tcpi
// newCancelChannelLocked appends the link resolution channel to a FIFO. If the
// maximum number of pending resolutions is reached, the oldest channel will be
// removed and its associated pending packets will be returned.
//
// +checklocks:f.mu.packetsPendingLinkResolutionMutex
func (f *packetsPendingLinkResolution) newCancelChannelLocked(newCH <-chan struct{}) []pendingPacket {
f.mu.cancelChans = append(f.mu.cancelChans, newCH)
if len(f.mu.cancelChans) <= maxPendingResolutions {
+5 -1
View File
@@ -22,10 +22,13 @@ import (
// lockedRandomSource provides a threadsafe rand.Source.
type lockedRandomSource struct {
mu sync.Mutex
mu sync.Mutex
// +checklocks:mu
src rand.Source
}
// +checklocksexclude:r.mu
func (r *lockedRandomSource) Int63() (n int64) {
r.mu.Lock()
n = r.src.Int63()
@@ -33,6 +36,7 @@ func (r *lockedRandomSource) Int63() (n int64) {
return n
}
// +checklocksexclude:r.mu
func (r *lockedRandomSource) Seed(seed int64) {
r.mu.Lock()
r.src.Seed(seed)
+31 -6
View File
@@ -58,6 +58,10 @@ type NetworkPacketInfo struct {
// address.
LocalAddressBroadcast bool
// LocalAddressTemporary is true if the packet's local address is a temporary
// address.
LocalAddressTemporary bool
// IsForwardedPacket is true if the packet is being forwarded.
IsForwardedPacket bool
}
@@ -203,18 +207,18 @@ type PacketMMapOpts struct {
IsRx bool
Cooked bool
Stack *Stack
Stats *tcpip.TransportEndpointStats
Wq *waiter.Queue
NICID tcpip.NICID
NetProto tcpip.NetworkProtocolNumber
PacketEndpoint MappablePacketEndpoint
Version int
Reserve uint32
}
// PacketMMapEndpoint is the interface implemented by endpoints to handle memory
// mapped packets over the packet transport protocol (PACKET_MMAP).
type PacketMMapEndpoint interface {
// HandlePacket is called by the stack when new packets arrive that
// match the endpoint.
// match the endpoint. It returns true if the packet was handled by the
// endpoint and false otherwise.
//
// Implementers should treat packet as immutable and should copy it
// before modification.
@@ -223,13 +227,17 @@ type PacketMMapEndpoint interface {
// should construct its own ethernet header for applications.
//
// HandlePacket may modify pkt.
HandlePacket(nicID tcpip.NICID, netProto tcpip.NetworkProtocolNumber, pkt *PacketBuffer)
HandlePacket(nicID tcpip.NICID, netProto tcpip.NetworkProtocolNumber, pkt *PacketBuffer) bool
// Close releases any resources associated with the endpoint.
Close()
// Readiness returns the events that the endpoint is ready for.
Readiness(mask waiter.EventMask) waiter.EventMask
// Stats returns the statistics for the endpoint that can be used for
// getsockopt(PACKET_STATISTICS).
Stats() tcpip.TpacketStats
}
// UnknownDestinationPacketDisposition enumerates the possible return values from
@@ -357,6 +365,21 @@ type TransportDispatcher interface {
DeliverRawPacket(tcpip.TransportProtocolNumber, *PacketBuffer)
}
// TransportDispatcherWithDefaultHandlerResult extends TransportDispatcher with
// default-handler-specific delivery metadata.
type TransportDispatcherWithDefaultHandlerResult interface {
TransportDispatcher
// DeliverTransportPacketWithDefaultHandlerResult delivers packets to the
// appropriate transport protocol endpoint and reports whether the packet was
// specifically handled by the per-stack default transport protocol handler.
//
// pkt.NetworkHeader must be set before calling this method.
//
// DeliverTransportPacketWithDefaultHandlerResult may modify the packet.
DeliverTransportPacketWithDefaultHandlerResult(tcpip.TransportProtocolNumber, *PacketBuffer) (TransportPacketDisposition, bool)
}
// PacketLooping specifies where an outbound packet should be sent.
type PacketLooping byte
@@ -864,6 +887,9 @@ type NetworkEndpoint interface {
// minus the network endpoint max header length.
MTU() uint32
// EndpointHeaderSize returns the size of this endpoint header.
EndpointHeaderSize() uint32
// MaxHeaderLength returns the maximum size the network (and lower
// level layers combined) headers can have. Higher levels use this
// information to reserve space in the front of the packets they're
@@ -1127,7 +1153,6 @@ const (
CapabilityRXChecksumOffload
CapabilityResolutionRequired
CapabilitySaveRestore
CapabilityDisconnectOk
CapabilityLoopback
)
+10 -6
View File
@@ -180,7 +180,7 @@ func constructAndValidateRoute(netProto tcpip.NetworkProtocolNumber, addressEndp
// AssignableAddressEndpoint.
func makeRoute(netProto tcpip.NetworkProtocolNumber, gateway, localAddr, remoteAddr tcpip.Address, outgoingNIC, localAddressNIC *nic, localAddressEndpoint AssignableAddressEndpoint, handleLocal, multicastLoop bool, mtu uint32) *Route {
if localAddressNIC.stack != outgoingNIC.stack {
panic(fmt.Sprintf("cannot create a route with NICs from different stacks"))
panic("cannot create a route with NICs from different stacks")
}
if localAddr.BitLen() == 0 {
@@ -245,6 +245,14 @@ func makeRoute(netProto tcpip.NetworkProtocolNumber, gateway, localAddr, remoteA
}
func makeRouteInner(netProto tcpip.NetworkProtocolNumber, localAddr, remoteAddr tcpip.Address, outgoingNIC, localAddressNIC *nic, localAddressEndpoint AssignableAddressEndpoint, loop PacketLooping, mtu uint32) *Route {
if mtu != 0 {
adjusted := mtu - outgoingNIC.getNetworkEndpoint(netProto).EndpointHeaderSize()
if adjusted > mtu {
mtu = 0
} else {
mtu = adjusted
}
}
r := &Route{
routeInfo: routeInfo{
NetProto: netProto,
@@ -339,11 +347,6 @@ func (r *Route) HasSaveRestoreCapability() bool {
return r.outgoingNIC.NetworkLinkEndpoint.Capabilities()&CapabilitySaveRestore != 0
}
// HasDisconnectOkCapability returns true if the route supports disconnecting.
func (r *Route) HasDisconnectOkCapability() bool {
return r.outgoingNIC.NetworkLinkEndpoint.Capabilities()&CapabilityDisconnectOk != 0
}
// GSOMaxSize returns the maximum GSO packet size.
func (r *Route) GSOMaxSize() uint32 {
if gso, ok := r.outgoingNIC.NetworkLinkEndpoint.(GSOEndpoint); ok {
@@ -525,6 +528,7 @@ func (r *Route) DefaultTTL() uint8 {
// MTU returns the MTU of the route if present, otherwise the MTU of the underlying network endpoint.
func (r *Route) MTU() uint32 {
if r.mtu > 0 {
// r.mtu is already adjusted to account for IP headers. See makeRouteInner.
return r.mtu
}
return r.outgoingNIC.getNetworkEndpoint(r.NetProto()).MTU()
+1 -1
View File
@@ -92,5 +92,5 @@ func routeinitLockNames() {}
func init() {
routeinitLockNames()
routeprefixIndex = locking.NewMutexClass(reflect.TypeOf(routeRWMutex{}), routelockNames)
routeprefixIndex = locking.NewMutexClass(reflect.TypeFor[routeRWMutex](), routelockNames)
}
@@ -92,5 +92,5 @@ func routeStackinitLockNames() {}
func init() {
routeStackinitLockNames()
routeStackprefixIndex = locking.NewMutexClass(reflect.TypeOf(routeStackRWMutex{}), routeStacklockNames)
routeStackprefixIndex = locking.NewMutexClass(reflect.TypeFor[routeStackRWMutex](), routeStacklockNames)
}
@@ -22,6 +22,31 @@ import (
cryptorand "gvisor.dev/gvisor/pkg/rand"
)
// beforeSave is invoked by stateify.
func (s *Stack) beforeSave() {
// removeConf will be set only in case of save/restore.
s.mu.Lock()
if !s.removeConf {
s.mu.Unlock()
return
}
// Remove all the NICs and routes from the stack as they will be
// created again during restore based on the new network config.
deferActs := make([]func(), 0)
for id := range s.nics {
act, _ := s.removeNICLocked(id, true /* closeLinkEndpoint */)
if act != nil {
deferActs = append(deferActs, act)
}
}
s.mu.Unlock()
for _, act := range deferActs {
act()
}
}
// afterLoad is invoked by stateify.
func (s *Stack) afterLoad(context.Context) {
s.insecureRNG = rand.New(rand.NewSource(time.Now().UnixNano()))
+373 -103
View File
@@ -20,11 +20,11 @@
package stack
import (
"context"
"encoding/binary"
"fmt"
"io"
"math/rand"
"sync/atomic"
"time"
"golang.org/x/time/rate"
@@ -32,6 +32,7 @@ import (
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/log"
cryptorand "gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/ports"
@@ -96,6 +97,8 @@ type Stack struct {
// +checklocks:mu
nics map[tcpip.NICID]*nic `state:"nosave"`
// +checklocks:mu
loopbackNIC *nic `state:"nosave"`
// +checklocks:mu
defaultForwardingEnabled map[tcpip.NetworkProtocolNumber]struct{}
// nicIDGen is used to generate NIC IDs.
@@ -111,6 +114,10 @@ type Stack struct {
// clock is used to generate user-visible times.
clock tcpip.Clock
// clockResolution is an upper bound on the quantization interval of
// monotonic timestamps returned by clock.
clockResolution time.Duration
// handleLocal allows non-loopback interfaces to loop packets.
handleLocal bool
@@ -118,6 +125,17 @@ type Stack struct {
// TODO(gvisor.dev/issue/4595): S/R this field.
tables *IPTables `state:"nosave"`
// nftables is the nftables interface for packet filtering and manipulation rules.
// Using atomic.Pointer for RCU lock-free reads.
nftables atomic.Pointer[NFTablesInterface] `state:"nosave"`
// nftablesUpdateMu serializes concurrent netlink batch modifications to nftables.
nftablesUpdateMu sync.Mutex `state:"nosave"`
// nftablesConfigured indicates whether NFTables is configured with at
// least one rule on a chain at a network hook.
nftablesConfigured atomicbitops.Bool
// restoredEndpoints is a list of endpoints that need to be restored if the
// stack is being restored.
restoredEndpoints []RestoredEndpoint
@@ -174,8 +192,23 @@ type Stack struct {
// initialized at stack startup.
tsOffsetSecret uint32
// saveRestoreEnabled indicates whether the stack is saved and restored.
saveRestoreEnabled bool
// removeConf indicates whether to remove NICs and routes and terminate
// active connections before saving. This flag will be set to true only
// when resume is false.
removeConf bool `state:"nosave"`
// allowLiveTCPMigration allows TCP connection state to be migrated.
// If false, any connected TCP endpoints will be terminated
// during save/restore.
allowLiveTCPMigration bool `state:"nosave"`
// externalNetworkingDisabled indicates whether external networking is
// disabled. This means all non-loopback NICs are disabled.
externalNetworkingDisabled bool
// allowConnectedOnSave indicates whether connections should be
// allowed to remain connected during save.
allowConnectedOnSave bool
}
// NetworkProtocolFactory instantiates a network protocol.
@@ -203,6 +236,17 @@ type Options struct {
// If Clock is nil, tcpip.NewStdClock() will be used.
Clock tcpip.Clock
// ClockResolution is an upper bound on the quantization interval of
// timestamps returned by Clock.NowMonotonic. It should be set when the
// clock advances in discrete steps large enough to affect TCP loss detection;
// for example, some Windows monotonic clocks commonly update every 500 us.
// TCP RACK uses this value to avoid declaring loss based on timestamp
// quantization alone. It does not describe timer wake-up precision.
//
// A value of zero means that no quantization allowance is needed or known.
// Values less than zero are clamped to zero.
ClockResolution time.Duration
// Stats are optional statistic counters.
Stats tcpip.Stats
@@ -226,6 +270,11 @@ type Options struct {
// operations.
AllowPacketEndpointWrite bool
// AllowLiveTCPMigration allows TCP connection state to be migrated.
// If false, any connected TCP endpoints will be terminated
// during save/restore.
AllowLiveTCPMigration bool
// RandSource is an optional source to use to generate random
// numbers. If omitted it defaults to a Source seeded by the data
// returned by the stack secure RNG.
@@ -238,6 +287,9 @@ type Options struct {
// all traffic.
IPTables *IPTables
// NFTables is the nftables interface for packet filtering and manipulation rules.
NFTables NFTablesInterface
// DefaultIPTables is an optional iptables rules constructor that is called
// if IPTables is nil. If both fields are nil, iptables will allow all
// traffic.
@@ -378,6 +430,11 @@ func New(opts Options) *Stack {
opts.NUDConfigs.resetInvalidFields()
clockResolution := opts.ClockResolution
if clockResolution < 0 {
clockResolution = 0
}
s := &Stack{
transportProtocols: make(map[tcpip.TransportProtocolNumber]*transportProtocolState),
networkProtocols: make(map[tcpip.NetworkProtocolNumber]NetworkProtocol),
@@ -387,6 +444,7 @@ func New(opts Options) *Stack {
cleanupEndpoints: make(map[TransportEndpoint]struct{}),
PortManager: ports.NewPortManager(),
clock: clock,
clockResolution: clockResolution,
stats: opts.Stats.FillIn(),
handleLocal: opts.HandleLocal,
tables: opts.IPTables,
@@ -406,9 +464,11 @@ func New(opts Options) *Stack {
Default: DefaultBufferSize,
Max: DefaultMaxBufferSize,
},
tcpInvalidRateLimit: defaultTCPInvalidRateLimit,
tsOffsetSecret: secureRNG.Uint32(),
tcpInvalidRateLimit: defaultTCPInvalidRateLimit,
tsOffsetSecret: secureRNG.Uint32(),
allowLiveTCPMigration: opts.AllowLiveTCPMigration,
}
s.SetNFTables(opts.NFTables)
// Add specified network protocols.
for _, netProtoFactory := range opts.NetworkProtocols {
@@ -527,6 +587,16 @@ func (s *Stack) Clock() tcpip.Clock {
return s.clock
}
// ClockResolution returns an upper bound on the quantization interval of
// monotonic timestamps returned by the Stack's clock.
//
// A zero value means to trust the clock advances between reads at a precision
// close to the precision of the value it returns; consuming code should need no
// corrections for clock resolution.
func (s *Stack) ClockResolution() time.Duration {
return s.clockResolution
}
// Stats returns a mutable copy of the current stats.
//
// This is not generally exported via the public interface, but is available
@@ -721,12 +791,16 @@ func (s *Stack) NICMulticastForwarding(id tcpip.NICID, protocol tcpip.NetworkPro
// PortRange returns the UDP and TCP inclusive range of ephemeral ports used in
// both IPv4 and IPv6.
//
// +checklocksexclude:s.PortManager.ephemeralMu
func (s *Stack) PortRange() (uint16, uint16) {
return s.PortManager.PortRange()
}
// SetPortRange sets the UDP and TCP IPv4 and IPv6 ephemeral port range
// (inclusive).
//
// +checklocksexclude:s.PortManager.ephemeralMu
func (s *Stack) SetPortRange(start uint16, end uint16) tcpip.Error {
return s.PortManager.SetPortRange(start, end)
}
@@ -798,7 +872,7 @@ func (s *Stack) removeRoutesLocked(match func(tcpip.Route) bool) int {
return count
}
// ReplaceRoute replaces the route in the routing table which matchse
// ReplaceRoute replaces the route in the routing table which matches
// the lookup key for the routing table. If there is no match, the given
// route will still be added to the routing table.
// The lookup key consists of destination, ToS, scope and output interface.
@@ -882,12 +956,15 @@ type NICOptions struct {
// EnableExperimentIPOption specifies whether the NIC is responsible for
// passing the experiment IP option.
EnableExperimentIPOption bool
// Kind specifies the link kind of the NIC (e.g. "veth", "bridge").
Kind string
}
// GetNICByID return a network device associated with the specified ID.
func (s *Stack) GetNICByID(id tcpip.NICID) (*nic, tcpip.Error) {
s.mu.Lock()
defer s.mu.Unlock()
s.mu.RLock()
defer s.mu.RUnlock()
n, ok := s.nics[id]
if !ok {
@@ -929,6 +1006,9 @@ func (s *Stack) CreateNICWithOptions(id tcpip.NICID, ep LinkEndpoint, opts NICOp
}
}
s.nics[id] = n
if n.IsLoopback() {
s.loopbackNIC = n
}
ep.SetOnCloseAction(func() {
s.RemoveNIC(id)
})
@@ -1005,7 +1085,7 @@ func (s *Stack) CheckNIC(id tcpip.NICID) bool {
// RemoveNIC removes NIC and all related routes from the network stack.
func (s *Stack) RemoveNIC(id tcpip.NICID) tcpip.Error {
s.mu.Lock()
deferAct, err := s.removeNICLocked(id)
deferAct, err := s.removeNICLocked(id, true /* closeLinkEndpoint */)
s.mu.Unlock()
if deferAct != nil {
deferAct()
@@ -1016,7 +1096,7 @@ func (s *Stack) RemoveNIC(id tcpip.NICID) tcpip.Error {
// removeNICLocked removes NIC and all related routes from the network stack.
//
// +checklocks:s.mu
func (s *Stack) removeNICLocked(id tcpip.NICID) (func(), tcpip.Error) {
func (s *Stack) removeNICLocked(id tcpip.NICID, closeLinkEndpoint bool) (func(), tcpip.Error) {
nic, ok := s.nics[id]
if !ok {
return nil, &tcpip.ErrUnknownNICID{}
@@ -1041,7 +1121,22 @@ func (s *Stack) removeNICLocked(id tcpip.NICID) (func(), tcpip.Error) {
}
s.routeMu.Unlock()
return nic.remove(true /* closeLinkEndpoint */)
if s.loopbackNIC == nic {
s.loopbackNIC = nil
}
return nic.remove(closeLinkEndpoint)
}
// GetNICCoordinatorID returns the ID of the coordinator device of a NIC.
func (s *Stack) GetNICCoordinatorID(id tcpip.NICID) (tcpip.NICID, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
if nic, ok := s.nics[id]; ok {
if nic.Primary != nil {
return nic.Primary.id, true
}
}
return 0, false
}
// SetNICCoordinator sets a coordinator device.
@@ -1144,6 +1239,12 @@ type NICInfo struct {
// MulticastForwarding holds the forwarding status for each network endpoint
// that supports multicast forwarding.
MulticastForwarding map[tcpip.NetworkProtocolNumber]bool
// Primary is the index of the main controlling interface in a bonded setup.
Primary tcpip.NICID
// Kind specifies the link kind of the NIC (e.g. "veth", "bridge").
Kind string
}
// HasNIC returns true if the NICID is defined in the stack.
@@ -1154,65 +1255,88 @@ func (s *Stack) HasNIC(id tcpip.NICID) bool {
return ok
}
type forwardingFn func(tcpip.NetworkProtocolNumber) (bool, tcpip.Error)
func forwardingValue(forwardingFn forwardingFn, proto tcpip.NetworkProtocolNumber, nicID tcpip.NICID, fnName string) (forward bool, ok bool) {
switch forwarding, err := forwardingFn(proto); err.(type) {
case nil:
return forwarding, true
case *tcpip.ErrUnknownProtocol:
panic(fmt.Sprintf("expected network protocol %d to be available on NIC %d", proto, nicID))
case *tcpip.ErrNotSupported:
// Not all network protocols support forwarding.
default:
panic(fmt.Sprintf("nic(id=%d).%s(%d): %s", nicID, fnName, proto, err))
}
return false, false
}
// precondition: s.mu is held.
func (s *Stack) nicInfo(nic *nic, id tcpip.NICID) *NICInfo {
flags := NICStateFlags{
Up: true, // Netstack interfaces are always up.
Running: nic.Enabled(),
Promiscuous: nic.Promiscuous(),
Loopback: nic.IsLoopback(),
}
netStats := make(map[tcpip.NetworkProtocolNumber]NetworkEndpointStats)
for proto, netEP := range nic.networkEndpoints {
netStats[proto] = netEP.Stats()
}
info := NICInfo{
Name: nic.name,
LinkAddress: nic.NetworkLinkEndpoint.LinkAddress(),
ProtocolAddresses: nic.primaryAddresses(),
Flags: flags,
MTU: nic.NetworkLinkEndpoint.MTU(),
Stats: nic.stats.local,
NetworkStats: netStats,
Context: nic.context,
ARPHardwareType: nic.NetworkLinkEndpoint.ARPHardwareType(),
Forwarding: make(map[tcpip.NetworkProtocolNumber]bool),
MulticastForwarding: make(map[tcpip.NetworkProtocolNumber]bool),
Kind: nic.kind,
}
for proto := range s.networkProtocols {
if forwarding, ok := forwardingValue(nic.forwarding, proto, id, "forwarding"); ok {
info.Forwarding[proto] = forwarding
}
if multicastForwarding, ok := forwardingValue(nic.multicastForwarding, proto, id, "multicastForwarding"); ok {
info.MulticastForwarding[proto] = multicastForwarding
}
}
if nic.Primary != nil {
info.Primary = nic.Primary.id
}
return &info
}
// SingleNICInfo returns the NICInfo for the given NICID.
func (s *Stack) SingleNICInfo(id tcpip.NICID) (*NICInfo, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
if nic, ok := s.nics[id]; !ok {
return nil, false
} else {
return s.nicInfo(nic, id), true
}
}
// NICInfo returns a map of NICIDs to their associated information.
func (s *Stack) NICInfo() map[tcpip.NICID]NICInfo {
s.mu.RLock()
defer s.mu.RUnlock()
type forwardingFn func(tcpip.NetworkProtocolNumber) (bool, tcpip.Error)
forwardingValue := func(forwardingFn forwardingFn, proto tcpip.NetworkProtocolNumber, nicID tcpip.NICID, fnName string) (forward bool, ok bool) {
switch forwarding, err := forwardingFn(proto); err.(type) {
case nil:
return forwarding, true
case *tcpip.ErrUnknownProtocol:
panic(fmt.Sprintf("expected network protocol %d to be available on NIC %d", proto, nicID))
case *tcpip.ErrNotSupported:
// Not all network protocols support forwarding.
default:
panic(fmt.Sprintf("nic(id=%d).%s(%d): %s", nicID, fnName, proto, err))
}
return false, false
}
nics := make(map[tcpip.NICID]NICInfo)
for id, nic := range s.nics {
flags := NICStateFlags{
Up: true, // Netstack interfaces are always up.
Running: nic.Enabled(),
Promiscuous: nic.Promiscuous(),
Loopback: nic.IsLoopback(),
}
netStats := make(map[tcpip.NetworkProtocolNumber]NetworkEndpointStats)
for proto, netEP := range nic.networkEndpoints {
netStats[proto] = netEP.Stats()
}
info := NICInfo{
Name: nic.name,
LinkAddress: nic.NetworkLinkEndpoint.LinkAddress(),
ProtocolAddresses: nic.primaryAddresses(),
Flags: flags,
MTU: nic.NetworkLinkEndpoint.MTU(),
Stats: nic.stats.local,
NetworkStats: netStats,
Context: nic.context,
ARPHardwareType: nic.NetworkLinkEndpoint.ARPHardwareType(),
Forwarding: make(map[tcpip.NetworkProtocolNumber]bool),
MulticastForwarding: make(map[tcpip.NetworkProtocolNumber]bool),
}
for proto := range s.networkProtocols {
if forwarding, ok := forwardingValue(nic.forwarding, proto, id, "forwarding"); ok {
info.Forwarding[proto] = forwarding
}
if multicastForwarding, ok := forwardingValue(nic.multicastForwarding, proto, id, "multicastForwarding"); ok {
info.MulticastForwarding[proto] = multicastForwarding
}
}
nics[id] = info
nics[id] = *s.nicInfo(nic, id)
}
return nics
}
@@ -1378,6 +1502,29 @@ func (s *Stack) findLocalRouteFromNICRLocked(localAddressNIC *nic, localAddr, re
return r
}
func (s *Stack) loopbackLocalRoute(localAddressNIC *nic, localAddr, remoteAddr tcpip.Address, netProto tcpip.NetworkProtocolNumber) *Route {
localAddressEndpoint := localAddressNIC.getAddressOrCreateTempInner(netProto, localAddr, true /* createTemp */, NeverPrimaryEndpoint)
if localAddressEndpoint == nil {
return nil
}
r := makeLocalRoute(
netProto,
localAddr,
remoteAddr,
localAddressNIC,
localAddressNIC,
localAddressEndpoint,
)
if r.IsOutboundBroadcast() {
r.Release()
return nil
}
return r
}
// findLocalRouteRLocked returns a local route.
//
// A local route is a route to some remote address which the stack owns. That
@@ -1390,6 +1537,22 @@ func (s *Stack) findLocalRouteRLocked(localAddressNICID tcpip.NICID, localAddr,
}
if localAddressNICID == 0 {
if s.loopbackNIC != nil {
// Send all packets directed to local ip addresses through the loopback device.
for _, nic := range s.nics {
if !nic.hasAddress(netProto, remoteAddr) {
continue
}
if isSubnetBroadcastOnNIC(nic, netProto, remoteAddr) {
break
}
if r := s.loopbackLocalRoute(s.loopbackNIC, localAddr, remoteAddr, netProto); r != nil {
return r
}
break
}
}
for _, localAddressNIC := range s.nics {
if r := s.findLocalRouteFromNICRLocked(localAddressNIC, localAddr, remoteAddr, netProto); r != nil {
return r
@@ -1591,8 +1754,7 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n
}
}
// TODO(https://gvisor.dev/issues/8105): This should be ErrNetworkUnreachable.
return nil, &tcpip.ErrHostUnreachable{}
return nil, &tcpip.ErrNetworkUnreachable{}
}
if id == 0 {
@@ -1605,13 +1767,11 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n
}
if needRoute {
// TODO(https://gvisor.dev/issues/8105): This should be ErrNetworkUnreachable.
return nil, &tcpip.ErrHostUnreachable{}
return nil, &tcpip.ErrNetworkUnreachable{}
}
if header.IsV6LoopbackAddress(remoteAddr) {
return nil, &tcpip.ErrBadLocalAddress{}
}
// TODO(https://gvisor.dev/issues/8105): This should be ErrNetworkUnreachable.
return nil, &tcpip.ErrNetworkUnreachable{}
}
@@ -1940,7 +2100,7 @@ func (s *Stack) Wait() {
for id, n := range s.nics {
// Remove NIC to ensure that qDisc goroutines are correctly
// terminated on stack teardown.
act, _ := s.removeNICLocked(id)
act, _ := s.removeNICLocked(id, true /* closeLinkEndpoint */)
n.NetworkLinkEndpoint.Wait()
if act != nil {
deferActs = append(deferActs, act)
@@ -1974,26 +2134,43 @@ func (s *Stack) getNICs() map[tcpip.NICID]*nic {
return nics
}
// ResetConfig resets the stack's NICs and ID generator.
func (s *Stack) ResetConfig() {
nics := make(map[tcpip.NICID]*nic)
s.mu.Lock()
defer s.mu.Unlock()
s.nics = nics
s.loopbackNIC = nil
s.nicIDGen.Store(0)
}
// ReplaceConfig replaces config in the loaded stack.
func (s *Stack) ReplaceConfig(st *Stack) {
if st == nil {
panic("stack.Stack cannot be nil when netstack s/r is enabled")
panic("stack.Stack cannot be nil when replacing config")
}
// Update route table.
s.SetRouteTable(st.GetRouteTable())
// Update NICs.
nics := st.getNICs()
s.mu.Lock()
defer s.mu.Unlock()
s.nics = make(map[tcpip.NICID]*nic)
// Update iptables and nftables.
s.tables = st.IPTables()
s.SetNFTables(st.NFTables())
for id, nic := range nics {
nic.stack = s
s.nics[id] = nic
if nic.IsLoopback() {
s.loopbackNIC = nic
} else if s.externalNetworkingDisabled {
nic.disable()
}
_ = s.NextNICID()
}
s.tables = st.tables
}
// Restore restarts the stack after a restore. This must be called after the
@@ -2004,18 +2181,18 @@ func (s *Stack) Restore() {
s.mu.Lock()
eps := s.restoredEndpoints
s.restoredEndpoints = nil
saveRestoreEnabled := s.saveRestoreEnabled
s.mu.Unlock()
for _, e := range eps {
e.Restore(s)
}
// Now resume any protocol level background workers.
// Make sure all the endpoints are loaded correctly before resuming the
// protocol level background workers.
tcpip.AsyncLoading.Wait()
// Now restore any protocol level background workers.
for _, p := range s.transportProtocols {
if saveRestoreEnabled {
p.proto.Restore()
} else {
p.proto.Resume()
}
p.proto.Restore()
}
}
@@ -2091,6 +2268,12 @@ func (s *Stack) unregisterPacketEndpointLocked(nicID tcpip.NICID, netProto tcpip
// WritePacketToRemote writes a payload on the specified NIC using the provided
// network protocol and remote link address.
func (s *Stack) WritePacketToRemote(nicID tcpip.NICID, remote tcpip.LinkAddress, netProto tcpip.NetworkProtocolNumber, payload buffer.Buffer) tcpip.Error {
return s.WritePacketToRemoteWithMark(nicID, remote, netProto, payload, 0)
}
// WritePacketToRemoteWithMark writes a payload on the specified NIC using the
// provided network protocol, remote link address, and packet mark.
func (s *Stack) WritePacketToRemoteWithMark(nicID tcpip.NICID, remote tcpip.LinkAddress, netProto tcpip.NetworkProtocolNumber, payload buffer.Buffer, mark uint32) tcpip.Error {
s.mu.Lock()
nic, ok := s.nics[nicID]
s.mu.Unlock()
@@ -2100,6 +2283,7 @@ func (s *Stack) WritePacketToRemote(nicID tcpip.NICID, remote tcpip.LinkAddress,
pkt := NewPacketBuffer(PacketBufferOptions{
ReserveHeaderBytes: int(nic.MaxHeaderLength()),
Payload: payload,
Mark: mark,
})
defer pkt.DecRef()
pkt.NetworkProtocolNumber = netProto
@@ -2109,6 +2293,12 @@ func (s *Stack) WritePacketToRemote(nicID tcpip.NICID, remote tcpip.LinkAddress,
// WriteRawPacket writes data directly to the specified NIC without adding any
// headers.
func (s *Stack) WriteRawPacket(nicID tcpip.NICID, proto tcpip.NetworkProtocolNumber, payload buffer.Buffer) tcpip.Error {
return s.WriteRawPacketWithMark(nicID, proto, payload, 0)
}
// WriteRawPacketWithMark writes data directly to the specified NIC without adding any
// headers, setting the specified packet mark.
func (s *Stack) WriteRawPacketWithMark(nicID tcpip.NICID, proto tcpip.NetworkProtocolNumber, payload buffer.Buffer, mark uint32) tcpip.Error {
s.mu.RLock()
nic, ok := s.nics[nicID]
s.mu.RUnlock()
@@ -2118,6 +2308,7 @@ func (s *Stack) WriteRawPacket(nicID tcpip.NICID, proto tcpip.NetworkProtocolNum
pkt := NewPacketBuffer(PacketBufferOptions{
Payload: payload,
Mark: mark,
})
defer pkt.DecRef()
pkt.NetworkProtocolNumber = proto
@@ -2183,6 +2374,49 @@ func (s *Stack) IPTables() *IPTables {
return s.tables
}
// SetIPTables sets the stack's iptables.
func (s *Stack) SetIPTables(tables *IPTables) {
s.tables = tables
}
// NFTables returns the stack's nftables.
func (s *Stack) NFTables() NFTablesInterface {
val := s.nftables.Load()
if val == nil {
return nil
}
return *val
}
// SetNFTables sets the stack's nftables.
func (s *Stack) SetNFTables(nft NFTablesInterface) {
if nft == nil {
s.nftables.Store(nil)
} else {
s.nftables.Store(&nft)
}
}
// LockNFTablesUpdate locks the stack's nftables update mutex for netlink batch modification.
func (s *Stack) LockNFTablesUpdate() {
s.nftablesUpdateMu.Lock()
}
// UnlockNFTablesUpdate unlocks the stack's nftables update mutex.
func (s *Stack) UnlockNFTablesUpdate() {
s.nftablesUpdateMu.Unlock()
}
// IsNFTablesConfigured returns true if the stack has nftables configured.
func (s *Stack) IsNFTablesConfigured() bool {
return s.nftablesConfigured.Load()
}
// SetNFTablesConfigured sets whether the stack has nftables configured.
func (s *Stack) SetNFTablesConfigured(configured bool) {
s.nftablesConfigured.Store(configured)
}
// ICMPLimit returns the maximum number of ICMP messages that can be sent
// in one second.
func (s *Stack) ICMPLimit() rate.Limit {
@@ -2389,12 +2623,11 @@ func (s *Stack) SetNICStack(id tcpip.NICID, peer *Stack) (tcpip.NICID, tcpip.Err
s.mu.Unlock()
return id, nil
}
delete(s.nics, id)
// Remove routes in-place. n tracks the number of routes written.
s.RemoveRoutes(func(r tcpip.Route) bool { return r.NIC == id })
ne := nic.NetworkLinkEndpoint.(LinkEndpoint)
deferAct, err := nic.remove(false /* closeLinkEndpoint */)
linkEp := nic.NetworkLinkEndpoint.(LinkEndpoint)
name := nic.Name()
deferAct, err := s.removeNICLocked(id, false /* closeLinkEndpoint */)
s.mu.Unlock()
if deferAct != nil {
deferAct()
@@ -2404,34 +2637,71 @@ func (s *Stack) SetNICStack(id tcpip.NICID, peer *Stack) (tcpip.NICID, tcpip.Err
}
id = tcpip.NICID(peer.NextNICID())
return id, peer.CreateNICWithOptions(id, ne, NICOptions{Name: nic.Name()})
return id, peer.CreateNICWithOptions(id, linkEp, NICOptions{Name: name})
}
// EnableSaveRestore marks the saveRestoreEnabled to true.
func (s *Stack) EnableSaveRestore() {
// SetRemoveConf sets the removeConf in stack to the given value.
func (s *Stack) SetRemoveConf(removeConf bool) {
s.mu.Lock()
defer s.mu.Unlock()
s.saveRestoreEnabled = true
s.removeConf = removeConf
}
// IsSaveRestoreEnabled returns true if save restore is enabled for the stack.
func (s *Stack) IsSaveRestoreEnabled() bool {
// GetRemoveConf gets the removeConf from stack.
func (s *Stack) GetRemoveConf() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.removeConf
}
// SetAllowConnectedOnSave sets allowConnectedOnSave in stack with the given value.
func (s *Stack) SetAllowConnectedOnSave(allowConnectedOnSave bool) {
s.mu.Lock()
defer s.mu.Unlock()
return s.saveRestoreEnabled
s.allowConnectedOnSave = allowConnectedOnSave
}
// contextID is this package's type for context.Context.Value keys.
type contextID int
// GetAllowConnectedOnSave gets the allowConnectedOnSave from stack.
func (s *Stack) GetAllowConnectedOnSave() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.allowConnectedOnSave
}
const (
// CtxRestoreStack is a Context.Value key for the stack to be used in restore.
CtxRestoreStack contextID = iota
)
// AllowLiveTCPMigration returns if TCP connections can be migrated.
func (s *Stack) AllowLiveTCPMigration() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.allowLiveTCPMigration
}
// RestoreStackFromContext returns the stack to be used during restore.
func RestoreStackFromContext(ctx context.Context) *Stack {
return ctx.Value(CtxRestoreStack).(*Stack)
// SetAllowLiveTCPMigration sets if TCP connections can be migrated.
func (s *Stack) SetAllowLiveTCPMigration(allow bool) {
s.mu.Lock()
defer s.mu.Unlock()
s.allowLiveTCPMigration = allow
}
// DisableAllNonLoopbackNICs disables all non-loopback NICs in the stack.
func (s *Stack) DisableAllNonLoopbackNICs() {
s.mu.Lock()
defer s.mu.Unlock()
s.externalNetworkingDisabled = true
for _, nic := range s.nics {
if !nic.IsLoopback() {
nic.disable()
}
}
}
// EnableAllNonLoopbackNICs enables all non-loopback NICs in the stack.
func (s *Stack) EnableAllNonLoopbackNICs() {
s.mu.Lock()
defer s.mu.Unlock()
s.externalNetworkingDisabled = false
for _, nic := range s.nics {
if !nic.IsLoopback() {
nic.enable()
}
}
}
+1 -1
View File
@@ -92,5 +92,5 @@ func stackinitLockNames() {}
func init() {
stackinitLockNames()
stackprefixIndex = locking.NewMutexClass(reflect.TypeOf(stackRWMutex{}), stacklockNames)
stackprefixIndex = locking.NewMutexClass(reflect.TypeFor[stackRWMutex](), stacklockNames)
}
@@ -37,14 +37,6 @@ const (
defaultTCPInvalidRateLimit = 500 * time.Millisecond
)
// ReceiveBufferSizeOption is used by stack.(Stack*).Option/SetOption to
// get/set the default, min and max receive buffer sizes.
type ReceiveBufferSizeOption struct {
Min int
Default int
Max int
}
// TCPInvalidRateLimitOption is used by stack.(Stack*).Option/SetOption to get/set
// stack.tcpInvalidRateLimit.
type TCPInvalidRateLimitOption time.Duration
@@ -244,6 +244,7 @@ func (cn *conn) StateFields() []string {
"destinationManip",
"tcb",
"lastUsed",
"replySeen",
}
}
@@ -260,6 +261,7 @@ func (cn *conn) StateSave(stateSinkObject state.Sink) {
stateSinkObject.Save(5, &cn.destinationManip)
stateSinkObject.Save(6, &cn.tcb)
stateSinkObject.Save(7, &cn.lastUsed)
stateSinkObject.Save(8, &cn.replySeen)
}
func (cn *conn) afterLoad(context.Context) {}
@@ -274,6 +276,7 @@ func (cn *conn) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(5, &cn.destinationManip)
stateSourceObject.Load(6, &cn.tcb)
stateSourceObject.Load(7, &cn.lastUsed)
stateSourceObject.Load(8, &cn.replySeen)
}
func (ct *ConnTrack) StateTypeName() string {
@@ -283,6 +286,7 @@ func (ct *ConnTrack) StateTypeName() string {
func (ct *ConnTrack) StateFields() []string {
return []string{
"seed",
"nftIDSeed",
"clock",
"buckets",
}
@@ -294,8 +298,9 @@ func (ct *ConnTrack) beforeSave() {}
func (ct *ConnTrack) StateSave(stateSinkObject state.Sink) {
ct.beforeSave()
stateSinkObject.Save(0, &ct.seed)
stateSinkObject.Save(1, &ct.clock)
stateSinkObject.Save(2, &ct.buckets)
stateSinkObject.Save(1, &ct.nftIDSeed)
stateSinkObject.Save(2, &ct.clock)
stateSinkObject.Save(3, &ct.buckets)
}
func (ct *ConnTrack) afterLoad(context.Context) {}
@@ -303,8 +308,9 @@ func (ct *ConnTrack) afterLoad(context.Context) {}
// +checklocksignore
func (ct *ConnTrack) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &ct.seed)
stateSourceObject.Load(1, &ct.clock)
stateSourceObject.Load(2, &ct.buckets)
stateSourceObject.Load(1, &ct.nftIDSeed)
stateSourceObject.Load(2, &ct.clock)
stateSourceObject.Load(3, &ct.buckets)
}
func (bkt *bucket) StateTypeName() string {
@@ -339,6 +345,8 @@ func (l *ICMPRateLimiter) StateTypeName() string {
func (l *ICMPRateLimiter) StateFields() []string {
return []string{
"clock",
"limit",
"burst",
}
}
@@ -348,13 +356,16 @@ func (l *ICMPRateLimiter) beforeSave() {}
func (l *ICMPRateLimiter) StateSave(stateSinkObject state.Sink) {
l.beforeSave()
stateSinkObject.Save(0, &l.clock)
stateSinkObject.Save(1, &l.limit)
stateSinkObject.Save(2, &l.burst)
}
func (l *ICMPRateLimiter) afterLoad(context.Context) {}
// +checklocksignore
func (l *ICMPRateLimiter) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &l.clock)
stateSourceObject.Load(1, &l.limit)
stateSourceObject.Load(2, &l.burst)
stateSourceObject.AfterLoad(func() { l.afterLoad(ctx) })
}
func (a *AcceptTarget) StateTypeName() string {
@@ -668,6 +679,34 @@ func (mt *MasqueradeTarget) StateLoad(ctx context.Context, stateSourceObject sta
stateSourceObject.Load(0, &mt.NetworkProtocol)
}
func (c *CTTarget) StateTypeName() string {
return "pkg/tcpip/stack.CTTarget"
}
func (c *CTTarget) StateFields() []string {
return []string{
"NetworkProtocol",
"Zone",
}
}
func (c *CTTarget) beforeSave() {}
// +checklocksignore
func (c *CTTarget) StateSave(stateSinkObject state.Sink) {
c.beforeSave()
stateSinkObject.Save(0, &c.NetworkProtocol)
stateSinkObject.Save(1, &c.Zone)
}
func (c *CTTarget) afterLoad(context.Context) {}
// +checklocksignore
func (c *CTTarget) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &c.NetworkProtocol)
stateSourceObject.Load(1, &c.Zone)
}
func (it *IPTables) StateTypeName() string {
return "pkg/tcpip/stack.IPTables"
}
@@ -675,7 +714,6 @@ func (it *IPTables) StateTypeName() string {
func (it *IPTables) StateFields() []string {
return []string{
"connections",
"reaper",
"v4Tables",
"v6Tables",
"modified",
@@ -686,19 +724,17 @@ func (it *IPTables) StateFields() []string {
func (it *IPTables) StateSave(stateSinkObject state.Sink) {
it.beforeSave()
stateSinkObject.Save(0, &it.connections)
stateSinkObject.Save(1, &it.reaper)
stateSinkObject.Save(2, &it.v4Tables)
stateSinkObject.Save(3, &it.v6Tables)
stateSinkObject.Save(4, &it.modified)
stateSinkObject.Save(1, &it.v4Tables)
stateSinkObject.Save(2, &it.v6Tables)
stateSinkObject.Save(3, &it.modified)
}
// +checklocksignore
func (it *IPTables) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &it.connections)
stateSourceObject.Load(1, &it.reaper)
stateSourceObject.Load(2, &it.v4Tables)
stateSourceObject.Load(3, &it.v6Tables)
stateSourceObject.Load(4, &it.modified)
stateSourceObject.Load(1, &it.v4Tables)
stateSourceObject.Load(2, &it.v6Tables)
stateSourceObject.Load(3, &it.modified)
stateSourceObject.AfterLoad(func() { it.afterLoad(ctx) })
}
@@ -1136,6 +1172,7 @@ func (n *nic) StateFields() []string {
"stack",
"id",
"name",
"kind",
"context",
"stats",
"networkEndpoints",
@@ -1162,20 +1199,21 @@ func (n *nic) StateSave(stateSinkObject state.Sink) {
stateSinkObject.Save(1, &n.stack)
stateSinkObject.Save(2, &n.id)
stateSinkObject.Save(3, &n.name)
stateSinkObject.Save(4, &n.context)
stateSinkObject.Save(5, &n.stats)
stateSinkObject.Save(6, &n.networkEndpoints)
stateSinkObject.Save(7, &n.linkAddrResolvers)
stateSinkObject.Save(8, &n.duplicateAddressDetectors)
stateSinkObject.Save(9, &n.enabled)
stateSinkObject.Save(10, &n.spoofing)
stateSinkObject.Save(11, &n.promiscuous)
stateSinkObject.Save(12, &n.linkResQueue)
stateSinkObject.Save(13, &n.packetEPs)
stateSinkObject.Save(14, &n.qDisc)
stateSinkObject.Save(15, &n.deliverLinkPackets)
stateSinkObject.Save(16, &n.Primary)
stateSinkObject.Save(17, &n.experimentIPOptionEnabled)
stateSinkObject.Save(4, &n.kind)
stateSinkObject.Save(5, &n.context)
stateSinkObject.Save(6, &n.stats)
stateSinkObject.Save(7, &n.networkEndpoints)
stateSinkObject.Save(8, &n.linkAddrResolvers)
stateSinkObject.Save(9, &n.duplicateAddressDetectors)
stateSinkObject.Save(10, &n.enabled)
stateSinkObject.Save(11, &n.spoofing)
stateSinkObject.Save(12, &n.promiscuous)
stateSinkObject.Save(13, &n.linkResQueue)
stateSinkObject.Save(14, &n.packetEPs)
stateSinkObject.Save(15, &n.qDisc)
stateSinkObject.Save(16, &n.deliverLinkPackets)
stateSinkObject.Save(17, &n.Primary)
stateSinkObject.Save(18, &n.experimentIPOptionEnabled)
}
func (n *nic) afterLoad(context.Context) {}
@@ -1186,20 +1224,21 @@ func (n *nic) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(1, &n.stack)
stateSourceObject.Load(2, &n.id)
stateSourceObject.Load(3, &n.name)
stateSourceObject.Load(4, &n.context)
stateSourceObject.Load(5, &n.stats)
stateSourceObject.Load(6, &n.networkEndpoints)
stateSourceObject.Load(7, &n.linkAddrResolvers)
stateSourceObject.Load(8, &n.duplicateAddressDetectors)
stateSourceObject.Load(9, &n.enabled)
stateSourceObject.Load(10, &n.spoofing)
stateSourceObject.Load(11, &n.promiscuous)
stateSourceObject.Load(12, &n.linkResQueue)
stateSourceObject.Load(13, &n.packetEPs)
stateSourceObject.Load(14, &n.qDisc)
stateSourceObject.Load(15, &n.deliverLinkPackets)
stateSourceObject.Load(16, &n.Primary)
stateSourceObject.Load(17, &n.experimentIPOptionEnabled)
stateSourceObject.Load(4, &n.kind)
stateSourceObject.Load(5, &n.context)
stateSourceObject.Load(6, &n.stats)
stateSourceObject.Load(7, &n.networkEndpoints)
stateSourceObject.Load(8, &n.linkAddrResolvers)
stateSourceObject.Load(9, &n.duplicateAddressDetectors)
stateSourceObject.Load(10, &n.enabled)
stateSourceObject.Load(11, &n.spoofing)
stateSourceObject.Load(12, &n.promiscuous)
stateSourceObject.Load(13, &n.linkResQueue)
stateSourceObject.Load(14, &n.packetEPs)
stateSourceObject.Load(15, &n.qDisc)
stateSourceObject.Load(16, &n.deliverLinkPackets)
stateSourceObject.Load(17, &n.Primary)
stateSourceObject.Load(18, &n.experimentIPOptionEnabled)
}
func (p *packetEndpointList) StateTypeName() string {
@@ -1530,8 +1569,10 @@ func (pk *PacketBuffer) StateFields() []string {
"dnatDone",
"PktType",
"NICID",
"InputNICID",
"RXChecksumValidated",
"NetworkPacketInfo",
"Mark",
"tuple",
}
}
@@ -1557,9 +1598,11 @@ func (pk *PacketBuffer) StateSave(stateSinkObject state.Sink) {
stateSinkObject.Save(13, &pk.dnatDone)
stateSinkObject.Save(14, &pk.PktType)
stateSinkObject.Save(15, &pk.NICID)
stateSinkObject.Save(16, &pk.RXChecksumValidated)
stateSinkObject.Save(17, &pk.NetworkPacketInfo)
stateSinkObject.Save(18, &pk.tuple)
stateSinkObject.Save(16, &pk.InputNICID)
stateSinkObject.Save(17, &pk.RXChecksumValidated)
stateSinkObject.Save(18, &pk.NetworkPacketInfo)
stateSinkObject.Save(19, &pk.Mark)
stateSinkObject.Save(20, &pk.tuple)
}
func (pk *PacketBuffer) afterLoad(context.Context) {}
@@ -1582,9 +1625,11 @@ func (pk *PacketBuffer) StateLoad(ctx context.Context, stateSourceObject state.S
stateSourceObject.Load(13, &pk.dnatDone)
stateSourceObject.Load(14, &pk.PktType)
stateSourceObject.Load(15, &pk.NICID)
stateSourceObject.Load(16, &pk.RXChecksumValidated)
stateSourceObject.Load(17, &pk.NetworkPacketInfo)
stateSourceObject.Load(18, &pk.tuple)
stateSourceObject.Load(16, &pk.InputNICID)
stateSourceObject.Load(17, &pk.RXChecksumValidated)
stateSourceObject.Load(18, &pk.NetworkPacketInfo)
stateSourceObject.Load(19, &pk.Mark)
stateSourceObject.Load(20, &pk.tuple)
}
func (h *headerInfo) StateTypeName() string {
@@ -1783,6 +1828,7 @@ func (n *NetworkPacketInfo) StateTypeName() string {
func (n *NetworkPacketInfo) StateFields() []string {
return []string{
"LocalAddressBroadcast",
"LocalAddressTemporary",
"IsForwardedPacket",
}
}
@@ -1793,7 +1839,8 @@ func (n *NetworkPacketInfo) beforeSave() {}
func (n *NetworkPacketInfo) StateSave(stateSinkObject state.Sink) {
n.beforeSave()
stateSinkObject.Save(0, &n.LocalAddressBroadcast)
stateSinkObject.Save(1, &n.IsForwardedPacket)
stateSinkObject.Save(1, &n.LocalAddressTemporary)
stateSinkObject.Save(2, &n.IsForwardedPacket)
}
func (n *NetworkPacketInfo) afterLoad(context.Context) {}
@@ -1801,7 +1848,8 @@ func (n *NetworkPacketInfo) afterLoad(context.Context) {}
// +checklocksignore
func (n *NetworkPacketInfo) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &n.LocalAddressBroadcast)
stateSourceObject.Load(1, &n.IsForwardedPacket)
stateSourceObject.Load(1, &n.LocalAddressTemporary)
stateSourceObject.Load(2, &n.IsForwardedPacket)
}
func (p *PacketMMapOpts) StateTypeName() string {
@@ -1814,11 +1862,10 @@ func (p *PacketMMapOpts) StateFields() []string {
"IsRx",
"Cooked",
"Stack",
"Stats",
"Wq",
"NICID",
"NetProto",
"PacketEndpoint",
"Version",
"Reserve",
}
}
@@ -1831,11 +1878,10 @@ func (p *PacketMMapOpts) StateSave(stateSinkObject state.Sink) {
stateSinkObject.Save(1, &p.IsRx)
stateSinkObject.Save(2, &p.Cooked)
stateSinkObject.Save(3, &p.Stack)
stateSinkObject.Save(4, &p.Stats)
stateSinkObject.Save(5, &p.Wq)
stateSinkObject.Save(6, &p.NICID)
stateSinkObject.Save(7, &p.NetProto)
stateSinkObject.Save(8, &p.PacketEndpoint)
stateSinkObject.Save(4, &p.Wq)
stateSinkObject.Save(5, &p.PacketEndpoint)
stateSinkObject.Save(6, &p.Version)
stateSinkObject.Save(7, &p.Reserve)
}
func (p *PacketMMapOpts) afterLoad(context.Context) {}
@@ -1846,11 +1892,10 @@ func (p *PacketMMapOpts) StateLoad(ctx context.Context, stateSourceObject state.
stateSourceObject.Load(1, &p.IsRx)
stateSourceObject.Load(2, &p.Cooked)
stateSourceObject.Load(3, &p.Stack)
stateSourceObject.Load(4, &p.Stats)
stateSourceObject.Load(5, &p.Wq)
stateSourceObject.Load(6, &p.NICID)
stateSourceObject.Load(7, &p.NetProto)
stateSourceObject.Load(8, &p.PacketEndpoint)
stateSourceObject.Load(4, &p.Wq)
stateSourceObject.Load(5, &p.PacketEndpoint)
stateSourceObject.Load(6, &p.Version)
stateSourceObject.Load(7, &p.Reserve)
}
func (lifetimes *AddressLifetimes) StateTypeName() string {
@@ -2097,7 +2142,9 @@ func (s *Stack) StateFields() []string {
"cleanupEndpoints",
"PortManager",
"clock",
"clockResolution",
"handleLocal",
"nftablesConfigured",
"restoredEndpoints",
"resumableEndpoints",
"icmpRateLimiter",
@@ -2108,12 +2155,11 @@ func (s *Stack) StateFields() []string {
"receiveBufferSize",
"tcpInvalidRateLimit",
"tsOffsetSecret",
"saveRestoreEnabled",
"externalNetworkingDisabled",
"allowConnectedOnSave",
}
}
func (s *Stack) beforeSave() {}
// +checklocksignore
func (s *Stack) StateSave(stateSinkObject state.Sink) {
s.beforeSave()
@@ -2127,18 +2173,21 @@ func (s *Stack) StateSave(stateSinkObject state.Sink) {
stateSinkObject.Save(7, &s.cleanupEndpoints)
stateSinkObject.Save(8, &s.PortManager)
stateSinkObject.Save(9, &s.clock)
stateSinkObject.Save(10, &s.handleLocal)
stateSinkObject.Save(11, &s.restoredEndpoints)
stateSinkObject.Save(12, &s.resumableEndpoints)
stateSinkObject.Save(13, &s.icmpRateLimiter)
stateSinkObject.Save(14, &s.seed)
stateSinkObject.Save(15, &s.nudConfigs)
stateSinkObject.Save(16, &s.nudDisp)
stateSinkObject.Save(17, &s.sendBufferSize)
stateSinkObject.Save(18, &s.receiveBufferSize)
stateSinkObject.Save(19, &s.tcpInvalidRateLimit)
stateSinkObject.Save(20, &s.tsOffsetSecret)
stateSinkObject.Save(21, &s.saveRestoreEnabled)
stateSinkObject.Save(10, &s.clockResolution)
stateSinkObject.Save(11, &s.handleLocal)
stateSinkObject.Save(12, &s.nftablesConfigured)
stateSinkObject.Save(13, &s.restoredEndpoints)
stateSinkObject.Save(14, &s.resumableEndpoints)
stateSinkObject.Save(15, &s.icmpRateLimiter)
stateSinkObject.Save(16, &s.seed)
stateSinkObject.Save(17, &s.nudConfigs)
stateSinkObject.Save(18, &s.nudDisp)
stateSinkObject.Save(19, &s.sendBufferSize)
stateSinkObject.Save(20, &s.receiveBufferSize)
stateSinkObject.Save(21, &s.tcpInvalidRateLimit)
stateSinkObject.Save(22, &s.tsOffsetSecret)
stateSinkObject.Save(23, &s.externalNetworkingDisabled)
stateSinkObject.Save(24, &s.allowConnectedOnSave)
}
// +checklocksignore
@@ -2153,18 +2202,21 @@ func (s *Stack) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(7, &s.cleanupEndpoints)
stateSourceObject.Load(8, &s.PortManager)
stateSourceObject.Load(9, &s.clock)
stateSourceObject.Load(10, &s.handleLocal)
stateSourceObject.Load(11, &s.restoredEndpoints)
stateSourceObject.Load(12, &s.resumableEndpoints)
stateSourceObject.Load(13, &s.icmpRateLimiter)
stateSourceObject.Load(14, &s.seed)
stateSourceObject.Load(15, &s.nudConfigs)
stateSourceObject.Load(16, &s.nudDisp)
stateSourceObject.Load(17, &s.sendBufferSize)
stateSourceObject.Load(18, &s.receiveBufferSize)
stateSourceObject.Load(19, &s.tcpInvalidRateLimit)
stateSourceObject.Load(20, &s.tsOffsetSecret)
stateSourceObject.Load(21, &s.saveRestoreEnabled)
stateSourceObject.Load(10, &s.clockResolution)
stateSourceObject.Load(11, &s.handleLocal)
stateSourceObject.Load(12, &s.nftablesConfigured)
stateSourceObject.Load(13, &s.restoredEndpoints)
stateSourceObject.Load(14, &s.resumableEndpoints)
stateSourceObject.Load(15, &s.icmpRateLimiter)
stateSourceObject.Load(16, &s.seed)
stateSourceObject.Load(17, &s.nudConfigs)
stateSourceObject.Load(18, &s.nudDisp)
stateSourceObject.Load(19, &s.sendBufferSize)
stateSourceObject.Load(20, &s.receiveBufferSize)
stateSourceObject.Load(21, &s.tcpInvalidRateLimit)
stateSourceObject.Load(22, &s.tsOffsetSecret)
stateSourceObject.Load(23, &s.externalNetworkingDisabled)
stateSourceObject.Load(24, &s.allowConnectedOnSave)
stateSourceObject.AfterLoad(func() { s.afterLoad(ctx) })
}
@@ -2439,6 +2491,7 @@ func init() {
state.Register((*RedirectTarget)(nil))
state.Register((*SNATTarget)(nil))
state.Register((*MasqueradeTarget)(nil))
state.Register((*CTTarget)(nil))
state.Register((*IPTables)(nil))
state.Register((*Table)(nil))
state.Register((*Rule)(nil))
@@ -92,5 +92,5 @@ func stateConninitLockNames() {}
func init() {
stateConninitLockNames()
stateConnprefixIndex = locking.NewMutexClass(reflect.TypeOf(stateConnRWMutex{}), stateConnlockNames)
stateConnprefixIndex = locking.NewMutexClass(reflect.TypeFor[stateConnRWMutex](), stateConnlockNames)
}
@@ -92,5 +92,5 @@ func transportEndpointsinitLockNames() {}
func init() {
transportEndpointsinitLockNames()
transportEndpointsprefixIndex = locking.NewMutexClass(reflect.TypeOf(transportEndpointsRWMutex{}), transportEndpointslockNames)
transportEndpointsprefixIndex = locking.NewMutexClass(reflect.TypeFor[transportEndpointsRWMutex](), transportEndpointslockNames)
}