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

-33
View File
@@ -1,33 +0,0 @@
package bits
// IsOn returns true if *all* bits set in 'bits' are set in 'mask'.
func IsOn32(mask, bits uint32) bool {
return mask&bits == bits
}
// IsAnyOn returns true if *any* bit set in 'bits' is set in 'mask'.
func IsAnyOn32(mask, bits uint32) bool {
return mask&bits != 0
}
// Mask returns a T with all of the given bits set.
func Mask32(is ...int) uint32 {
ret := uint32(0)
for _, i := range is {
ret |= MaskOf32(i)
}
return ret
}
// MaskOf is like Mask, but sets only a single bit (more efficiently).
func MaskOf32(i int) uint32 {
return uint32(1) << uint32(i)
}
// IsPowerOfTwo returns true if v is power of 2.
func IsPowerOfTwo32(v uint32) bool {
if v == 0 {
return false
}
return v&(v-1) == 0
}
-33
View File
@@ -1,33 +0,0 @@
package bits
// IsOn returns true if *all* bits set in 'bits' are set in 'mask'.
func IsOn64(mask, bits uint64) bool {
return mask&bits == bits
}
// IsAnyOn returns true if *any* bit set in 'bits' is set in 'mask'.
func IsAnyOn64(mask, bits uint64) bool {
return mask&bits != 0
}
// Mask returns a T with all of the given bits set.
func Mask64(is ...int) uint64 {
ret := uint64(0)
for _, i := range is {
ret |= MaskOf64(i)
}
return ret
}
// MaskOf is like Mask, but sets only a single bit (more efficiently).
func MaskOf64(i int) uint64 {
return uint64(1) << uint64(i)
}
// IsPowerOfTwo returns true if v is power of 2.
func IsPowerOfTwo64(v uint64) bool {
if v == 0 {
return false
}
return v&(v-1) == 0
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2018 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 bits
import "golang.org/x/exp/constraints"
// Non-atomic bit operations on integral types.
// IsOn returns true if *all* bits set in 'bits' are set in 'mask'.
func IsOn[T constraints.Integer](mask, bits T) bool {
return mask&bits == bits
}
// IsAnyOn returns true if *any* bit set in 'bits' is set in 'mask'.
func IsAnyOn[T constraints.Integer](mask, bits T) bool {
return mask&bits != 0
}
// Mask returns a T with all of the given bits set.
func Mask[T constraints.Integer](is ...int) T {
ret := T(0)
for _, i := range is {
ret |= MaskOf[T](i)
}
return ret
}
// MaskOf is like Mask, but sets only a single bit (more efficiently).
func MaskOf[T constraints.Integer](i int) T {
return T(1) << T(i)
}
// IsPowerOfTwo returns true if v is power of 2.
func IsPowerOfTwo[T constraints.Integer](v T) bool {
if v == 0 {
return false
}
return v&(v-1) == 0
}
+1 -1
View File
@@ -32,6 +32,6 @@ func ForEachSetBit64(x uint64, f func(i int)) {
for x != 0 {
i := TrailingZeros64(x)
f(i)
x &^= MaskOf64(i)
x &^= MaskOf[uint64](i)
}
}
@@ -24,8 +24,8 @@ func (b *Buffer) beforeSave() {}
// +checklocksignore
func (b *Buffer) StateSave(stateSinkObject state.Sink) {
b.beforeSave()
var dataValue []byte
dataValue = b.saveData()
dataValue := b.saveData()
_ = ([]byte)(dataValue)
stateSinkObject.SaveValue(0, dataValue)
stateSinkObject.Save(1, &b.size)
}
+1 -3
View File
@@ -15,12 +15,10 @@
package buffer
import (
"reflect"
"unsafe"
)
// BasePtr returns a pointer to the view's chunk.
func (v *View) BasePtr() *byte {
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&v.chunk.data))
return (*byte)(unsafe.Pointer(hdr.Data))
return unsafe.SliceData(v.chunk.data)
}
+80 -27
View File
@@ -43,6 +43,9 @@ type Blocker interface {
// Interrupted notes whether this context is Interrupted.
Interrupted() bool
// Killed returns true if this context is interrupted by a fatal signal.
Killed() bool
// BlockOn blocks until one of the previously registered events occurs,
// or some external interrupt (cancellation).
//
@@ -55,25 +58,32 @@ type Blocker interface {
// is interrupted.
Block(C <-chan struct{}) error
// BlockWithTimeout blocks until an event is received from C, the timeout
// has elapsed (only if haveTimeout is true), or some external interrupt.
//
// It returns:
// - The remaining timeout, which is guaranteed to be 0 if the timeout
// expired, and is unspecified if haveTimeout is false.
// - An error which if the timeout expired or if interrupted.
BlockWithTimeout(C chan struct{}, haveTimeout bool, timeout time.Duration) (time.Duration, error)
// BlockWithTimeoutOn blocks until either the conditions of Block are
// satisfied, or the timeout is hit. Note that deadlines are not supported
// since the notion of "with respect to what clock" is not resolved.
//
// The return value is per BlockOn.
// It returns:
// - The remaining timeout, which is guaranteed to be 0 if the timeout
// expired.
// - Boolean as per BlockOn return value.
BlockWithTimeoutOn(waiter.Waitable, waiter.EventMask, time.Duration) (time.Duration, bool)
// UninterruptibleSleepStart indicates the beginning of an uninterruptible
// sleep state (equivalent to Linux's TASK_UNINTERRUPTIBLE). If deactivate
// is true and the Context represents a Task, the Task's AddressSpace is
// deactivated.
UninterruptibleSleepStart(deactivate bool)
// sleep state (equivalent to Linux's TASK_UNINTERRUPTIBLE).
UninterruptibleSleepStart()
// UninterruptibleSleepFinish indicates the end of an uninterruptible sleep
// state that was begun by a previous call to UninterruptibleSleepStart. If
// activate is true and the Context represents a Task, the Task's
// AddressSpace is activated. Normally activate is the same value as the
// deactivate parameter passed to UninterruptibleSleepStart.
UninterruptibleSleepFinish(activate bool)
// state that was begun by a previous call to UninterruptibleSleepStart.
UninterruptibleSleepFinish()
}
// NoTask is an implementation of Blocker that does not block.
@@ -91,7 +101,12 @@ func (nt *NoTask) Interrupt() {
// Interrupted implements Blocker.Interrupted.
func (nt *NoTask) Interrupted() bool {
return nt.cancel != nil && len(nt.cancel) > 0
return len(nt.cancel) > 0
}
// Killed implements Blocker.Killed.
func (nt *NoTask) Killed() bool {
return false
}
// Block implements Blocker.Block.
@@ -123,34 +138,47 @@ func (nt *NoTask) BlockOn(w waiter.Waitable, mask waiter.EventMask) bool {
}
}
// BlockWithTimeoutOn implements Blocker.BlockWithTimeoutOn.
func (nt *NoTask) BlockWithTimeoutOn(w waiter.Waitable, mask waiter.EventMask, duration time.Duration) (time.Duration, bool) {
// BlockWithTimeout implements Blocker.BlockWithTimeout.
func (nt *NoTask) BlockWithTimeout(C chan struct{}, haveTimeout bool, timeout time.Duration) (time.Duration, error) {
if !haveTimeout {
return timeout, nt.Block(C)
}
if nt.cancel == nil {
nt.cancel = make(chan struct{}, 1)
}
e, ch := waiter.NewChannelEntry(mask)
w.EventRegister(&e)
defer w.EventUnregister(&e)
start := time.Now() // In system time.
t := time.AfterFunc(duration, func() { ch <- struct{}{} })
remainingTimeout := func() time.Duration {
rt := timeout - time.Since(start)
if rt < 0 {
rt = 0
}
return rt
}
select {
case <-nt.cancel:
return time.Since(start), false // Interrupted.
case _, ok := <-ch:
if ok && t.Stop() {
// Timer never fired.
return time.Since(start), ok
}
// Timer fired, remain is zero.
return time.Duration(0), ok
return remainingTimeout(), errors.New("interrupted system call") // Interrupted.
case <-C:
return remainingTimeout(), nil
case <-time.After(timeout):
return 0, errors.New("timeout expired")
}
}
// BlockWithTimeoutOn implements Blocker.BlockWithTimeoutOn.
func (nt *NoTask) BlockWithTimeoutOn(w waiter.Waitable, mask waiter.EventMask, timeout time.Duration) (time.Duration, bool) {
e, ch := waiter.NewChannelEntry(mask)
w.EventRegister(&e)
defer w.EventUnregister(&e)
left, err := nt.BlockWithTimeout(ch, true, timeout)
return left, err == nil
}
// UninterruptibleSleepStart implmenents Blocker.UninterruptedSleepStart.
func (*NoTask) UninterruptibleSleepStart(bool) {}
func (*NoTask) UninterruptibleSleepStart() {}
// UninterruptibleSleepFinish implmenents Blocker.UninterruptibleSleepFinish.
func (*NoTask) UninterruptibleSleepFinish(bool) {}
func (*NoTask) UninterruptibleSleepFinish() {}
// Context represents a thread of execution (hereafter "goroutine" to reflect
// Go idiosyncrasy). It carries state associated with the goroutine across API
@@ -226,3 +254,28 @@ func (ctx *withValue) Value(key any) any {
}
return ctx.Context.Value(key)
}
// WithValues returns a copy of parent in which the values associated with keys
// are the corresponding values in the map.
func WithValues(parent Context, values map[any]any) Context {
if len(values) == 0 {
return parent
}
return &withValues{
Context: parent,
values: values,
}
}
type withValues struct {
Context
values map[any]any
}
// Value implements Context.Value.
func (ctx *withValues) Value(key any) any {
if val, ok := ctx.values[key]; ok {
return val
}
return ctx.Context.Value(key)
}
+4 -3
View File
@@ -233,9 +233,10 @@ func readHWCap(auxvFilepath string) (hwCap, error) {
for i := 0; i < l; i++ {
tag := binary.LittleEndian.Uint64(auxv[i*16:])
val := binary.LittleEndian.Uint64(auxv[i*16+8:])
if tag == _AT_HWCAP {
switch tag {
case _AT_HWCAP:
c.hwCap1 = val
} else if tag == _AT_HWCAP2 {
case _AT_HWCAP2:
c.hwCap2 = val
}
@@ -249,7 +250,7 @@ func readHWCap(auxvFilepath string) (hwCap, error) {
func initHWCap() {
c, err := readHWCap("/proc/self/auxv")
if err != nil {
log.Warningf("cpuid HWCap not initialized: %w", err)
log.Warningf("cpuid HWCap not initialized: %v", err)
} else {
hostFeatureSet.hwCap = c
}
+46 -13
View File
@@ -192,7 +192,24 @@ func (fs FeatureSet) VirtualAddressBits() uint32 {
//go:nosplit
func (fs FeatureSet) PhysicalAddressBits() uint32 {
ax, _, _, _ := fs.query(addressSizes)
return ax & 0xff
physBits := ax & 0xff
if !fs.AMD() {
return physBits
}
maxExtended, _, _, _ := fs.query(extendedFunctionInfo)
if maxExtended < uint32(amdMemoryEncryptionInfo) {
return physBits
}
memEncAX, memEncBX, _, _ := fs.query(amdMemoryEncryptionInfo)
if memEncAX&amdMemoryEncryptionFeatureMask == 0 {
return physBits
}
// AMD memory encryption reduces usable physical address width by the
// CPUID-reported amount. Match Linux's
// arch/x86/kernel/cpu/amd.c:early_detect_mem_encrypt().
return physBits - ((memEncBX >> amdPhysAddrReductionShift) & amdPhysAddrReductionMask)
}
// CacheType describes the type of a cache, as returned in eax[4:0] for eax=4.
@@ -306,6 +323,19 @@ func (fs FeatureSet) HasFeature(feature Feature) bool {
return feature.check(fs)
}
// x86Family returns the CPU family printed in /proc/cpuinfo from a CPUID
// leaf 1 EAX signature.
//
// This matches Linux arch/x86/lib/cpu.c:x86_family(). When the 4-bit
// family ID is 0xf, the 8-bit extended family is added.
func x86Family(sig uint32) uint32 {
family := (sig >> 8) & 0xf
if family == 0xf {
family += (sig >> 20) & 0xff
}
return family
}
// WriteCPUInfoTo is to generate a section of one cpu in /proc/cpuinfo. This is
// a minimal /proc/cpuinfo, it is missing some fields like "microcode" that are
// not always printed in Linux. Several fields are simply made up.
@@ -313,11 +343,11 @@ func (fs FeatureSet) WriteCPUInfoTo(cpu, numCPU uint, w io.Writer) {
// Avoid many redundant calls here, since this can occasionally appear
// in the hot path. Read all basic information up front, see above.
ax, _, _, _ := fs.query(featureInfo)
ef, em, _, f, m, _ := signatureSplit(ax)
_, em, _, _, m, _ := signatureSplit(ax)
vendor := fs.VendorID()
fmt.Fprintf(w, "processor\t: %d\n", cpu)
fmt.Fprintf(w, "vendor_id\t: %s\n", string(vendor[:]))
fmt.Fprintf(w, "cpu family\t: %d\n", ((ef<<4)&0xff)|f)
fmt.Fprintf(w, "cpu family\t: %d\n", x86Family(ax))
fmt.Fprintf(w, "model\t\t: %d\n", ((em<<4)&0xff)|m)
fmt.Fprintf(w, "model name\t: %s\n", "unknown") // Unknown for now.
fmt.Fprintf(w, "stepping\t: %s\n", "unknown") // Unknown for now.
@@ -382,15 +412,16 @@ var (
)
const (
// XCR0AMXMask are the bits that enable xsave to operate on AMX TILECFG
// and TILEDATA.
//
// Note: TILECFG and TILEDATA are always either both enabled or both
// disabled.
// XCR0AmxCfgMask is the bits that enable xsave to operate on
// AMX TILECFG.
//
// See Intel® 64 and IA-32 Architectures Software Developer’s Manual Vol.1
// section 13.3 for details.
XCR0AMXMask = uint64((1 << 17) | (1 << 18))
XCR0AmxCfgMask = uint64(1 << 17)
// XCR0AmxDataMask is the bits that enable xsave to operate on
// AMX TILEDATA.
XCR0AmxDataMask = uint64(1 << 18)
)
// ExtendedStateSize returns the number of bytes needed to save the "extended
@@ -415,13 +446,15 @@ func (fs FeatureSet) ExtendedStateSize() (size, align uint) {
// AMXExtendedStateSize returns the number of bytes within the "extended state"
// area that is used for AMX.
func (fs FeatureSet) AMXExtendedStateSize() uint {
total := uint(0)
if fs.UseXsave() {
xcr0 := xgetbv(0)
if (xcr0 & XCR0AMXMask) != 0 {
return uint(amxTileCfgSize + amxTileDataSize)
// TILECFG is not part of AMX extended state, only TILEDATA.
if (xcr0 & XCR0AmxDataMask) != 0 {
total += uint(amxTileDataSize)
}
}
return 0
return total
}
// ValidXCR0Mask returns the valid bits in control register XCR0.
@@ -435,7 +468,7 @@ func (fs FeatureSet) ValidXCR0Mask() uint64 {
return 0
}
ax, _, _, dx := fs.query(xSaveInfo)
return (uint64(dx)<<32 | uint64(ax)) &^ XCR0AMXMask
return (uint64(dx)<<32 | uint64(ax)) &^ (XCR0AmxCfgMask | XCR0AmxDataMask)
}
// UseXsave returns the choice of fp state saving instruction.
@@ -27,8 +27,8 @@ func (fs *FeatureSet) beforeSave() {}
// +checklocksignore
func (fs *FeatureSet) StateSave(stateSinkObject state.Sink) {
fs.beforeSave()
var FunctionValue Static
FunctionValue = fs.saveFunction()
FunctionValue := fs.saveFunction()
_ = (Static)(FunctionValue)
stateSinkObject.SaveValue(0, FunctionValue)
stateSinkObject.Save(1, &fs.hwCap)
}
+46 -18
View File
@@ -63,15 +63,25 @@ const xSaveInfoNumLeaves = 64 // Maximum number of xSaveInfo leaves.
// The "extended" functions.
const (
extendedStart cpuidFunction = 0x80000000
extendedFunctionInfo cpuidFunction = extendedStart + 0 // Returns highest available extended function in eax.
extendedFeatures = extendedStart + 1 // Returns some extended feature bits in edx and ecx.
processorBrandString2 = extendedStart + 2 // Processor Name String Identifier.
processorBrandString3 = extendedStart + 3 // Processor Name String Identifier.
processorBrandString4 = extendedStart + 4 // Processor Name String Identifier.
l1CacheAndTLBInfo = extendedStart + 5 // Returns L2 cache information.
l2CacheInfo = extendedStart + 6 // Returns L2 cache information.
addressSizes = extendedStart + 8 // Physical and virtual address sizes.
extendedStart cpuidFunction = 0x80000000
extendedFunctionInfo cpuidFunction = extendedStart + 0 // Returns highest available extended function in eax.
extendedFeatures = extendedStart + 1 // Returns some extended feature bits in edx and ecx.
processorBrandString2 = extendedStart + 2 // Processor Name String Identifier.
processorBrandString3 = extendedStart + 3 // Processor Name String Identifier.
processorBrandString4 = extendedStart + 4 // Processor Name String Identifier.
l1CacheAndTLBInfo = extendedStart + 5 // Returns L2 cache information.
l2CacheInfo = extendedStart + 6 // Returns L2 cache information.
addressSizes = extendedStart + 8 // Physical and virtual address sizes.
amdMemoryEncryptionInfo = extendedStart + 31 // AMD memory encryption information.
)
// AMD-defined memory encryption feature bits and fields.
const (
amdSecureMemoryEncryption = 1 << 0
amdSecureEncryptedVirtualization = 1 << 1
amdMemoryEncryptionFeatureMask = amdSecureMemoryEncryption | amdSecureEncryptedVirtualization
amdPhysAddrReductionShift = 6
amdPhysAddrReductionMask = 0x3f
)
var allowedBasicFunctions = [...]bool{
@@ -84,14 +94,15 @@ var allowedBasicFunctions = [...]bool{
}
var allowedExtendedFunctions = [...]bool{
extendedFunctionInfo - extendedStart: true,
extendedFeatures - extendedStart: true,
addressSizes - extendedStart: true,
processorBrandString2 - extendedStart: true,
processorBrandString3 - extendedStart: true,
processorBrandString4 - extendedStart: true,
l1CacheAndTLBInfo - extendedStart: true,
l2CacheInfo - extendedStart: true,
extendedFunctionInfo - extendedStart: true,
extendedFeatures - extendedStart: true,
addressSizes - extendedStart: true,
processorBrandString2 - extendedStart: true,
processorBrandString3 - extendedStart: true,
processorBrandString4 - extendedStart: true,
l1CacheAndTLBInfo - extendedStart: true,
l2CacheInfo - extendedStart: true,
amdMemoryEncryptionInfo - extendedStart: true,
}
// Function executes a CPUID function.
@@ -119,7 +130,7 @@ func (i *In) normalize() {
switch cpuidFunction(i.Eax) {
case vendorID, featureInfo, intelCacheDescriptors, extendedFunctionInfo, extendedFeatures:
i.Ecx = 0 // Ignore.
case processorBrandString2, processorBrandString3, processorBrandString4, l1CacheAndTLBInfo, l2CacheInfo:
case processorBrandString2, processorBrandString3, processorBrandString4, l1CacheAndTLBInfo, l2CacheInfo, amdMemoryEncryptionInfo:
i.Ecx = 0 // Ignore.
case intelDeterministicCacheParams, extendedFeatureInfo:
// Preserve i.Ecx.
@@ -163,6 +174,23 @@ func (fs FeatureSet) query(fn cpuidFunction) (uint32, uint32, uint32, uint32) {
return out.Eax, out.Ebx, out.Ecx, out.Edx
}
// Intersect returns the intersection of features between self and allowedFeatures.
func (fs FeatureSet) Intersect(allowedFeatures map[Feature]struct{}) (FeatureSet, error) {
hs := fs.ToStatic()
// only keep features inside allowedFeatures.
for f := range allFeatures {
if fs.HasFeature(f) {
if _, ok := allowedFeatures[f]; !ok {
log.Infof("Removing CPU feature %v as it is not allowed.", f)
hs.Remove(f)
}
}
}
return hs.ToFeatureSet(), nil
}
var hostFeatureSet FeatureSet
// HostFeatureSet returns a host CPUID.
+8
View File
@@ -18,6 +18,7 @@
package cpuid
import (
"fmt"
"os"
"runtime"
"strconv"
@@ -41,6 +42,13 @@ func (fs FeatureSet) Fixed() FeatureSet {
return fs
}
// Intersect returns the intersection of features between self and allowedFeatures.
//
// Just return error as there is no ARM64 equivalent to cpuid.Static.Remove().
func (fs FeatureSet) Intersect(allowedFeatures map[Feature]struct{}) (FeatureSet, error) {
return FeatureSet{}, fmt.Errorf("FeatureSet intersection is not supported on ARM64")
}
// Reads CPU information from host /proc/cpuinfo.
//
// Must run before syscall filter installation. This value is used to create
+2 -2
View File
@@ -105,8 +105,8 @@ func (s Static) normalize() {
if fs.HasFeature(X86FeatureXSAVE) {
in := In{Eax: uint32(xSaveInfo)}
out := s[in]
out.Ecx = maxXsaveSize
out.Ebx = xsaveSize
out.Ecx = max(out.Ecx, maxXsaveSize)
out.Ebx = max(out.Ebx, xsaveSize)
s[in] = out
}
}
@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build go1.23
//go:build amd64
#include "textflag.h"
#define GOID_OFFSET 160 // +checkoffset runtime g.goid
#define GOID_OFFSET 152
// func goid() int64
TEXT ·goid(SB),NOSPLIT|NOFRAME,$0-8
@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build go1.23
//go:build arm64
#include "textflag.h"
#define GOID_OFFSET 160 // +checkoffset runtime g.goid
#define GOID_OFFSET 152
// func goid() int64
TEXT ·goid(SB),NOSPLIT,$0-8
+161
View File
@@ -0,0 +1,161 @@
// 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 log
import (
"fmt"
"runtime"
"strings"
"gvisor.dev/gvisor/pkg/sync"
)
// This file contains helper functions analogous to the Linux kernel's WARN*
// macros. Should be used for non-fatal errors that should be treated as bugs
// none the less.
const (
warnFmtStr = "WARNING: BUG on %s:%d\n"
warnUnknownLineStr = "WARNING: BUG on unknown line\n"
catchAllMagic = "runtime.Caller failed"
)
//go:noinline
func reportBugErr(caller int, err error) {
reportBug(caller+1, err.Error(), nil)
}
func reportBug(caller int, msg string, vars []any) {
var b strings.Builder
if _, file, line, ok := runtime.Caller(caller); ok {
fmt.Fprintf(&b, warnFmtStr, file, line)
} else {
b.WriteString(warnUnknownLineStr)
}
b.WriteByte('\n')
if len(msg) > 0 {
if len(vars) > 0 {
fmt.Fprintf(&b, msg, vars...)
} else {
b.WriteString(msg)
}
b.WriteByte('\n')
}
TracebackAll(b.String())
}
var (
// warnedMu protects the variables below.
warnedMu sync.Mutex
// warnedSet is used to keep track of which WarnOnOnce calls have fired.
warnedSet map[string]struct{}
)
//go:noinline
func reportBugErrOnce(caller int, err error) {
reportBugOnce(caller+1, err.Error(), nil)
}
func reportBugOnce(caller int, msg string, vars []any) {
var b strings.Builder
if _, file, line, ok := runtime.Caller(caller); ok {
key := fmt.Sprintf("%s:%d", file, line)
warnedMu.Lock()
defer warnedMu.Unlock()
if _, ok = warnedSet[key]; !ok {
fmt.Fprintf(&b, warnFmtStr, file, line)
b.WriteByte('\n')
if len(msg) > 0 {
if len(vars) > 0 {
fmt.Fprintf(&b, msg, vars...)
} else {
b.WriteString(msg)
}
b.WriteByte('\n')
}
TracebackAll(b.String())
warnedSet[key] = struct{}{}
}
} else {
warnedMu.Lock()
defer warnedMu.Unlock()
// Use const string as a catch-all when runtime.Caller fails,
// so as to avoid log-spam since that's the point of WARN_ONCE.
if _, ok := warnedSet[catchAllMagic]; !ok {
b.WriteString(warnUnknownLineStr)
b.WriteByte('\n')
if len(msg) > 0 {
if len(vars) > 0 {
fmt.Fprintf(&b, msg, vars...)
} else {
b.WriteString(msg)
}
b.WriteByte('\n')
}
TracebackAll(b.String())
warnedSet[catchAllMagic] = struct{}{}
}
}
}
// BugTraceback will report a bug with a traceback of all goroutines if the
// error isn't nil. Use it for reporting abnormal bugs encountered at runtime
// that should be fixed.
//
// Do not use this for bad user input. Errors reported by this function should
// not be fatal.
func BugTraceback(err error) {
if err != nil {
reportBugErr(2, err)
}
}
// BugTracebackf will report a bug with a traceback of all goroutines.
// Use it for reporting abnormal bugs encountered at runtime that should be
// fixed.
//
// Do not use this for bad user input. Errors reported by this function should
// not be fatal.
func BugTracebackf(s string, a ...any) {
reportBug(2, s, a)
}
// BugTracebackOnce will report a bug with a traceback of all goroutines if the
// error isn't nil. Use it for reporting abnormal bugs encountered at runtime
// that should be fixed. If called multiple time from same invocation, will only
// print once.
//
// Do not use this for bad user input. Errors reported by this function should
// not be fatal.
func BugTracebackOnce(err error) {
if err != nil {
reportBugErrOnce(2, err)
}
}
// BugTracebackfOnce will report a bug with a traceback of all goroutines.
// Use it for reporting abnormal bugs encountered at runtime that should be
// fixed. If called multiple time from same invocation, will only print once.
//
// Do not use this for bad user input. Errors reported by this function should
// not be fatal.
func BugTracebackfOnce(s string, a ...any) {
reportBugOnce(2, s, a)
}
+60
View File
@@ -0,0 +1,60 @@
// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"fmt"
"os"
"path/filepath"
)
// FileOpts contains options for creating a log file.
type FileOpts interface {
// Build constructs the log file path based on the given pattern.
Build(logPattern string) string
}
// DefaultFileOpts is the default implementation of FileOpts which supports no
// variable substitution.
type DefaultFileOpts struct{}
// Build implements FileOpts.Build.
func (f *DefaultFileOpts) Build(logPattern string) string {
return logPattern
}
// OpenFile opens a log file using the specified flags. It uses `opts` to
// construct the log file path based on the given `logPattern`.
func OpenFile(logPattern string, flags int, opts FileOpts) (*os.File, error) {
if len(logPattern) == 0 {
return nil, nil
}
// Replace variables in the log pattern.
logPath := opts.Build(logPattern)
// Create parent directory if it doesn't exist.
dir := filepath.Dir(logPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("error creating dir %q: %v", dir, err)
}
// Open file with the specified flags.
f, err := os.OpenFile(logPath, flags, 0644)
if err != nil {
return nil, fmt.Errorf("error opening file %q: %v", logPath, err)
}
return f, nil
}
+3 -1
View File
@@ -327,7 +327,7 @@ func Stacks(all bool) []byte {
}
// stackRegexp matches one level within a stack trace.
var stackRegexp = regexp.MustCompile("(?m)^\\S+\\(.*\\)$\\r?\\n^\\t\\S+:\\d+.*$\\r?\\n")
var stackRegexp = regexp.MustCompile(`(?m)^\S+\(.*\)$\r?\n^\t\S+:\d+.*$\r?\n`)
// LocalStack returns the local goroutine stack, excluding the top N entries.
// LocalStack's own entry is excluded by default and does not need to be counted in excludeTopN.
@@ -396,4 +396,6 @@ func CopyStandardLogTo(l Level) error {
func init() {
// Store the initial value for the log.
log.Store(&BasicLogger{Level: Info, Emitter: GoogleEmitter{&Writer{Next: os.Stderr}}})
warnedSet = make(map[string]struct{})
}
+2 -2
View File
@@ -162,7 +162,7 @@ func doLeakCheck() {
skip = o.LeakCheckDisabled()
}
if skip {
log.Debugf(obj.LeakMessage())
log.Debugf("%s", obj.LeakMessage())
continue
}
msg += obj.LeakMessage() + "\n"
@@ -174,6 +174,6 @@ func doLeakCheck() {
if leakCheckPanicEnabled() {
panic(msg)
}
log.Warningf(msg)
log.Warningf("%s", msg)
}
}
@@ -25,8 +25,8 @@ func (s *Sleeper) beforeSave() {}
// +checklocksignore
func (s *Sleeper) StateSave(stateSinkObject state.Sink) {
s.beforeSave()
var sharedListValue *Waker
sharedListValue = s.saveSharedList()
sharedListValue := s.saveSharedList()
_ = (*Waker)(sharedListValue)
stateSinkObject.SaveValue(0, sharedListValue)
stateSinkObject.Save(1, &s.localList)
stateSinkObject.Save(2, &s.allWakers)
@@ -58,8 +58,8 @@ func (w *Waker) beforeSave() {}
// +checklocksignore
func (w *Waker) StateSave(stateSinkObject state.Sink) {
w.beforeSave()
var sValue wakerState
sValue = w.saveS()
sValue := w.saveS()
_ = (wakerState)(sValue)
stateSinkObject.SaveValue(0, sValue)
stateSinkObject.Save(1, &w.next)
stateSinkObject.Save(2, &w.allWakersNext)
+21 -8
View File
@@ -384,7 +384,7 @@ func (s *addrSet) InsertWithoutMergingUnchecked(gap addrGapIterator, r addrRange
if splitMaxGap {
gap.node.updateMaxGapLeaf()
}
return addrIterator{gap.node, gap.index}
return addrIterator(gap)
}
// InsertRange inserts the given segment into the set. If the new segment can
@@ -513,7 +513,7 @@ func (s *addrSet) Remove(seg addrIterator) addrGapIterator {
if addrtrackGaps != 0 {
seg.node.updateMaxGapLeaf()
}
return seg.node.rebalanceAfterRemove(addrGapIterator{seg.node, seg.index})
return seg.node.rebalanceAfterRemove(addrGapIterator(seg))
}
// RemoveAll removes all segments from the set. All existing iterators are
@@ -598,6 +598,19 @@ func (s *addrSet) RemoveFullRangeWith(r addrRange, f func(seg addrIterator)) add
}
}
// MoveFrom moves all segments from s2 to s, replacing all existing segments in
// s and leaving s2 empty.
func (s *addrSet) MoveFrom(s2 *addrSet) {
*s = *s2
for _, child := range s.root.children {
if child == nil {
break
}
child.parent = &s.root
}
s2.RemoveAll()
}
// Merge attempts to merge two neighboring segments. If successful, Merge
// returns an iterator to the merged segment, and all existing iterators are
// invalidated. Otherwise, Merge returns a terminal iterator.
@@ -1609,7 +1622,7 @@ func (seg addrIterator) PrevGap() addrGapIterator {
return seg.node.children[seg.index].lastSegment().NextGap()
}
return addrGapIterator{seg.node, seg.index}
return addrGapIterator(seg)
}
// NextGap returns the gap immediately after the iterated segment.
@@ -1901,26 +1914,26 @@ func (n *addrnode) String() string {
func (n *addrnode) writeDebugString(buf *bytes.Buffer, prefix string) {
if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
buf.WriteString(prefix)
buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
fmt.Fprintf(buf, "WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren)
}
for i := 0; i < n.nrSegments; i++ {
if child := n.children[i]; child != nil {
cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
if child.parent != n || child.parentIndex != i {
buf.WriteString(cprefix)
buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
fmt.Fprintf(buf, "WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i)
}
child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
}
buf.WriteString(prefix)
if n.hasChildren {
if addrtrackGaps != 0 {
buf.WriteString(fmt.Sprintf("- % 3d: %v => %v, maxGap: %d\n", i, n.keys[i], n.values[i], n.maxGap.Get()))
fmt.Fprintf(buf, "- % 3d: %v => %v, maxGap: %d\n", i, n.keys[i], n.values[i], n.maxGap.Get())
} else {
buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
fmt.Fprintf(buf, "- % 3d: %v => %v\n", i, n.keys[i], n.values[i])
}
} else {
buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
fmt.Fprintf(buf, "- % 3d: %v => %v\n", i, n.keys[i], n.values[i])
}
}
if child := n.children[n.nrSegments]; child != nil {
+56 -49
View File
@@ -32,7 +32,7 @@ type internalCallback interface {
source() *objectDecodeState
// callbackRun executes the callback.
callbackRun()
callbackRun(ds *decodeState)
}
// userCallback is an implementation of internalCallback.
@@ -44,7 +44,7 @@ func (userCallback) source() *objectDecodeState {
}
// callbackRun implements internalCallback.callbackRun.
func (uc userCallback) callbackRun() {
func (uc userCallback) callbackRun(*decodeState) {
uc()
}
@@ -84,7 +84,13 @@ type objectDecodeState struct {
// callbacks is a set of callbacks to execute on load.
callbacks []internalCallback
completeEntry
pendingEntry odsListElem
leafEntry odsListElem
}
type odsListElem struct {
ods *objectDecodeState
odsEntry
}
// addCallback adds a callback to the objectDecodeState.
@@ -122,8 +128,13 @@ func (ods *objectDecodeState) source() *objectDecodeState {
}
// callbackRun implements internalCallback.callbackRun.
func (ods *objectDecodeState) callbackRun() {
func (ods *objectDecodeState) callbackRun(ds *decodeState) {
ods.blockedBy--
if ods.blockedBy == 0 {
ds.leaves.PushBack(&ods.leafEntry)
} else if ods.blockedBy < 0 {
Failf("object %d has negative blockedBy: %d", ods.id, ods.blockedBy)
}
}
// decodeState is a graph of objects in the process of being decoded.
@@ -155,7 +166,11 @@ type decodeState struct {
deferred map[objectID]wire.Object
// pending is the set of objects that are not yet complete.
pending completeList
pending odsList
// leaves is the set of objects that have no dependencies (blockedBy == 0).
// leaves are consumed from the front and appended to the back.
leaves odsList
// stats tracks time data.
stats Stats
@@ -185,20 +200,12 @@ func (ds *decodeState) checkComplete(ods *objectDecodeState) bool {
// Fire all callbacks.
for _, ic := range ods.callbacks {
ic.callbackRun()
ic.callbackRun(ds)
}
// Mark completed.
cbs := ods.callbacks
ods.callbacks = nil
ds.pending.Remove(ods)
// Recursively check others.
for _, ic := range cbs {
if other := ic.source(); other != nil && other.blockedBy == 0 {
ds.checkComplete(other)
}
}
ds.pending.Remove(&ods.pendingEntry)
return true // All set.
}
@@ -223,6 +230,9 @@ func (ds *decodeState) wait(waiter *objectDecodeState, id objectID, callback fun
// Mark as blocked.
waiter.blockedBy++
if waiter.blockedBy == 1 {
ds.leaves.Remove(&waiter.leafEntry)
}
// No nil can be returned here.
other := ds.lookup(id)
@@ -280,6 +290,26 @@ func walkChild(path []wire.Dot, obj reflect.Value) reflect.Value {
return obj
}
func (ds *decodeState) growObjectsByID(id objectID) {
if len(ds.objectsByID) < int(id) {
ds.objectsByID = append(ds.objectsByID, make([]*objectDecodeState, int(id)-len(ds.objectsByID))...)
}
}
func (ds *decodeState) addObject(id objectID, obj reflect.Value) *objectDecodeState {
ods := &objectDecodeState{
id: id,
obj: obj,
}
ods.pendingEntry.ods = ods
ods.leafEntry.ods = ods
ds.growObjectsByID(id)
ds.objectsByID[id-1] = ods
ds.pending.PushBack(&ods.pendingEntry)
ds.leaves.PushBack(&ods.leafEntry)
return ods
}
// register registers a decode with a type.
//
// This type is only used to instantiate a new object if it has not been
@@ -288,11 +318,9 @@ func walkChild(path []wire.Dot, obj reflect.Value) reflect.Value {
func (ds *decodeState) register(r *wire.Ref, typ reflect.Type) reflect.Value {
// Grow the objectsByID slice.
id := objectID(r.Root)
if len(ds.objectsByID) < int(id) {
ds.objectsByID = append(ds.objectsByID, make([]*objectDecodeState, int(id)-len(ds.objectsByID))...)
}
// Does this object already exist?
ds.growObjectsByID(id)
ods := ds.objectsByID[id-1]
if ods != nil {
return walkChild(r.Dots, ods.obj)
@@ -303,12 +331,7 @@ func (ds *decodeState) register(r *wire.Ref, typ reflect.Type) reflect.Value {
typ = ds.findType(r.Type)
}
v := reflect.New(typ)
ods = &objectDecodeState{
id: id,
obj: v.Elem(),
}
ds.objectsByID[id-1] = ods
ds.pending.PushBack(ods)
ods = ds.addObject(id, v.Elem())
// Process any deferred objects & callbacks.
if encoded, ok := ds.deferred[id]; ok {
@@ -581,13 +604,8 @@ func (ds *decodeState) Load(obj reflect.Value) {
return ds.types.LookupName(id)
})
// Create the root object.
rootOds := &objectDecodeState{
id: 1,
obj: obj,
}
ds.objectsByID = append(ds.objectsByID, rootOds)
ds.pending.PushBack(rootOds)
// Add the root object with ID 1.
_ = ds.addObject(1, obj)
// Read the number of objects.
numObjects, object, err := ReadHeader(&ds.r)
@@ -603,7 +621,6 @@ func (ds *decodeState) Load(obj reflect.Value) {
encoded wire.Object
ods *objectDecodeState
id objectID
tid = typeID(1)
)
if err := safely(func() {
// Decode all objects in the stream.
@@ -616,7 +633,6 @@ func (ds *decodeState) Load(obj reflect.Value) {
switch we := encoded.(type) {
case *wire.Type:
ds.types.Register(we)
tid++
encoded = nil
continue
case wire.Uint:
@@ -673,22 +689,13 @@ func (ds *decodeState) Load(obj reflect.Value) {
// objects become complete (there is a dependency cycle).
//
// Note that we iterate backwards here, because there will be a strong
// tendendcy for blocking relationships to go from earlier objects to
// tendency for blocking relationships to go from earlier objects to
// later (deeper) objects in the graph. This will reduce the number of
// iterations required to finish all objects.
if err := safely(func() {
for ds.pending.Back() != nil {
thisCycle := false
for ods = ds.pending.Back(); ods != nil; {
if ds.checkComplete(ods) {
thisCycle = true
break
}
ods = ods.Prev()
}
if !thisCycle {
break
}
for elem := ds.leaves.Front(); elem != nil; elem = elem.Next() {
ods = elem.ods
ds.checkComplete(elem.ods)
}
}); err != nil {
Failf("error executing callbacks: %w\nfor object %#v", err, ods.obj.Interface())
@@ -696,9 +703,9 @@ func (ds *decodeState) Load(obj reflect.Value) {
// Check if we have any remaining dependency cycles. If there are any
// objects left in the pending list, then it must be due to a cycle.
if ods := ds.pending.Front(); ods != nil {
if elem := ds.pending.Front(); elem != nil {
// This must be the result of a dependency cycle.
cycle := ods.findCycle()
cycle := elem.ods.findCycle()
var buf bytes.Buffer
buf.WriteString("dependency cycle: {")
for i, cycleOS := range cycle {
@@ -708,7 +715,7 @@ func (ds *decodeState) Load(obj reflect.Value) {
fmt.Fprintf(&buf, "%q", cycleOS.obj.Type())
}
buf.WriteString("}")
Failf("incomplete graph: %s", string(buf.Bytes()))
Failf("incomplete graph: %s", buf.String())
}
}
+13 -5
View File
@@ -770,8 +770,11 @@ func (es *encodeState) Save(obj reflect.Value) {
es.encodeObject(oes.obj, oes.how, &oes.encoded)
}
}); err != nil {
// Include the object in the error message.
Failf("encoding error: %w\nfor object %#v", err, oes.obj.Interface())
// Include the object in the error message, if available.
if oes != nil && oes.obj.IsValid() {
Failf("encoding error: %w\nfor object %#v", err, oes.obj.Interface())
}
Failf("encoding error: %w", err)
}
// Check that we have objects to serialize.
@@ -802,14 +805,19 @@ func (es *encodeState) Save(obj reflect.Value) {
})
for _, id := range ids {
// Encode the id.
oes = nil
wire.Save(&es.w, wire.Uint(id))
// Marshal the object.
oes := es.pending[id]
oes = es.pending[id]
wire.Save(&es.w, oes.encoded)
}
}); err != nil {
// Include the object and the error.
Failf("error serializing object %#v: %w", oes.encoded, err)
if oes != nil {
// Include the object and the error.
Failf("error serializing object %#v: %w", oes.encoded, err)
} else {
Failf("error serializing type or ID: %w", err)
}
}
}
@@ -6,14 +6,14 @@ package state
// objects, if they are not the same. An ElementMapper is not typically
// required if: Linker is left as is, Element is left as is, or Linker and
// Element are the same type.
type completeElementMapper struct{}
type odsElementMapper struct{}
// linkerFor maps an Element to a Linker.
//
// This default implementation should be inlined.
//
//go:nosplit
func (completeElementMapper) linkerFor(elem *objectDecodeState) *objectDecodeState { return elem }
func (odsElementMapper) linkerFor(elem *odsListElem) *odsListElem { return elem }
// List is an intrusive list. Entries can be added to or removed from the list
// in O(1) time and with no additional memory allocations.
@@ -27,13 +27,13 @@ func (completeElementMapper) linkerFor(elem *objectDecodeState) *objectDecodeSta
// }
//
// +stateify savable
type completeList struct {
head *objectDecodeState
tail *objectDecodeState
type odsList struct {
head *odsListElem
tail *odsListElem
}
// Reset resets list l to the empty state.
func (l *completeList) Reset() {
func (l *odsList) Reset() {
l.head = nil
l.tail = nil
}
@@ -41,21 +41,21 @@ func (l *completeList) Reset() {
// Empty returns true iff the list is empty.
//
//go:nosplit
func (l *completeList) Empty() bool {
func (l *odsList) Empty() bool {
return l.head == nil
}
// Front returns the first element of list l or nil.
//
//go:nosplit
func (l *completeList) Front() *objectDecodeState {
func (l *odsList) Front() *odsListElem {
return l.head
}
// Back returns the last element of list l or nil.
//
//go:nosplit
func (l *completeList) Back() *objectDecodeState {
func (l *odsList) Back() *odsListElem {
return l.tail
}
@@ -64,8 +64,8 @@ func (l *completeList) Back() *objectDecodeState {
// NOTE: This is an O(n) operation.
//
//go:nosplit
func (l *completeList) Len() (count int) {
for e := l.Front(); e != nil; e = (completeElementMapper{}.linkerFor(e)).Next() {
func (l *odsList) Len() (count int) {
for e := l.Front(); e != nil; e = (odsElementMapper{}.linkerFor(e)).Next() {
count++
}
return count
@@ -74,12 +74,12 @@ func (l *completeList) Len() (count int) {
// PushFront inserts the element e at the front of list l.
//
//go:nosplit
func (l *completeList) PushFront(e *objectDecodeState) {
linker := completeElementMapper{}.linkerFor(e)
func (l *odsList) PushFront(e *odsListElem) {
linker := odsElementMapper{}.linkerFor(e)
linker.SetNext(l.head)
linker.SetPrev(nil)
if l.head != nil {
completeElementMapper{}.linkerFor(l.head).SetPrev(e)
odsElementMapper{}.linkerFor(l.head).SetPrev(e)
} else {
l.tail = e
}
@@ -90,13 +90,13 @@ func (l *completeList) PushFront(e *objectDecodeState) {
// PushFrontList inserts list m at the start of list l, emptying m.
//
//go:nosplit
func (l *completeList) PushFrontList(m *completeList) {
func (l *odsList) PushFrontList(m *odsList) {
if l.head == nil {
l.head = m.head
l.tail = m.tail
} else if m.head != nil {
completeElementMapper{}.linkerFor(l.head).SetPrev(m.tail)
completeElementMapper{}.linkerFor(m.tail).SetNext(l.head)
odsElementMapper{}.linkerFor(l.head).SetPrev(m.tail)
odsElementMapper{}.linkerFor(m.tail).SetNext(l.head)
l.head = m.head
}
@@ -107,12 +107,12 @@ func (l *completeList) PushFrontList(m *completeList) {
// PushBack inserts the element e at the back of list l.
//
//go:nosplit
func (l *completeList) PushBack(e *objectDecodeState) {
linker := completeElementMapper{}.linkerFor(e)
func (l *odsList) PushBack(e *odsListElem) {
linker := odsElementMapper{}.linkerFor(e)
linker.SetNext(nil)
linker.SetPrev(l.tail)
if l.tail != nil {
completeElementMapper{}.linkerFor(l.tail).SetNext(e)
odsElementMapper{}.linkerFor(l.tail).SetNext(e)
} else {
l.head = e
}
@@ -123,13 +123,13 @@ func (l *completeList) PushBack(e *objectDecodeState) {
// PushBackList inserts list m at the end of list l, emptying m.
//
//go:nosplit
func (l *completeList) PushBackList(m *completeList) {
func (l *odsList) PushBackList(m *odsList) {
if l.head == nil {
l.head = m.head
l.tail = m.tail
} else if m.head != nil {
completeElementMapper{}.linkerFor(l.tail).SetNext(m.head)
completeElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
odsElementMapper{}.linkerFor(l.tail).SetNext(m.head)
odsElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
l.tail = m.tail
}
@@ -140,9 +140,9 @@ func (l *completeList) PushBackList(m *completeList) {
// InsertAfter inserts e after b.
//
//go:nosplit
func (l *completeList) InsertAfter(b, e *objectDecodeState) {
bLinker := completeElementMapper{}.linkerFor(b)
eLinker := completeElementMapper{}.linkerFor(e)
func (l *odsList) InsertAfter(b, e *odsListElem) {
bLinker := odsElementMapper{}.linkerFor(b)
eLinker := odsElementMapper{}.linkerFor(e)
a := bLinker.Next()
@@ -151,7 +151,7 @@ func (l *completeList) InsertAfter(b, e *objectDecodeState) {
bLinker.SetNext(e)
if a != nil {
completeElementMapper{}.linkerFor(a).SetPrev(e)
odsElementMapper{}.linkerFor(a).SetPrev(e)
} else {
l.tail = e
}
@@ -160,9 +160,9 @@ func (l *completeList) InsertAfter(b, e *objectDecodeState) {
// InsertBefore inserts e before a.
//
//go:nosplit
func (l *completeList) InsertBefore(a, e *objectDecodeState) {
aLinker := completeElementMapper{}.linkerFor(a)
eLinker := completeElementMapper{}.linkerFor(e)
func (l *odsList) InsertBefore(a, e *odsListElem) {
aLinker := odsElementMapper{}.linkerFor(a)
eLinker := odsElementMapper{}.linkerFor(e)
b := aLinker.Prev()
eLinker.SetNext(a)
@@ -170,7 +170,7 @@ func (l *completeList) InsertBefore(a, e *objectDecodeState) {
aLinker.SetPrev(e)
if b != nil {
completeElementMapper{}.linkerFor(b).SetNext(e)
odsElementMapper{}.linkerFor(b).SetNext(e)
} else {
l.head = e
}
@@ -179,19 +179,19 @@ func (l *completeList) InsertBefore(a, e *objectDecodeState) {
// Remove removes e from l.
//
//go:nosplit
func (l *completeList) Remove(e *objectDecodeState) {
linker := completeElementMapper{}.linkerFor(e)
func (l *odsList) Remove(e *odsListElem) {
linker := odsElementMapper{}.linkerFor(e)
prev := linker.Prev()
next := linker.Next()
if prev != nil {
completeElementMapper{}.linkerFor(prev).SetNext(next)
odsElementMapper{}.linkerFor(prev).SetNext(next)
} else if l.head == e {
l.head = next
}
if next != nil {
completeElementMapper{}.linkerFor(next).SetPrev(prev)
odsElementMapper{}.linkerFor(next).SetPrev(prev)
} else if l.tail == e {
l.tail = prev
}
@@ -205,35 +205,35 @@ func (l *completeList) Remove(e *objectDecodeState) {
// methods needed by List.
//
// +stateify savable
type completeEntry struct {
next *objectDecodeState
prev *objectDecodeState
type odsEntry struct {
next *odsListElem
prev *odsListElem
}
// Next returns the entry that follows e in the list.
//
//go:nosplit
func (e *completeEntry) Next() *objectDecodeState {
func (e *odsEntry) Next() *odsListElem {
return e.next
}
// Prev returns the entry that precedes e in the list.
//
//go:nosplit
func (e *completeEntry) Prev() *objectDecodeState {
func (e *odsEntry) Prev() *odsListElem {
return e.prev
}
// SetNext assigns 'entry' as the entry that follows e in the list.
//
//go:nosplit
func (e *completeEntry) SetNext(elem *objectDecodeState) {
func (e *odsEntry) SetNext(elem *odsListElem) {
e.next = elem
}
// SetPrev assigns 'entry' as the entry that precedes e in the list.
//
//go:nosplit
func (e *completeEntry) SetPrev(elem *objectDecodeState) {
func (e *odsEntry) SetPrev(elem *odsListElem) {
e.prev = elem
}
+1 -1
View File
@@ -188,7 +188,7 @@ func (s Sink) Context() context.Context {
// Type is an interface that must be implemented by Struct objects. This allows
// these objects to be serialized while minimizing runtime reflection required.
//
// All these methods can be automatically generated by the go_statify tool.
// All these methods can be automatically generated by the go_stateify tool.
type Type interface {
// StateTypeName returns the type's name.
//
+13 -11
View File
@@ -19,6 +19,8 @@ import (
"fmt"
"sort"
"time"
"gvisor.dev/gvisor/pkg/gohacks"
)
type statEntry struct {
@@ -48,13 +50,13 @@ type Stats struct {
// on the stats object.
names []string
// last is the last start time.
last time.Time
// last is the monotonic time of the last sample.
last int64
}
// init initializes statistics.
func (s *Stats) init() {
s.last = time.Now()
s.last = gohacks.Nanotime()
s.stack = append(s.stack, 0)
}
@@ -72,12 +74,12 @@ func (s *Stats) fini(resolve func(id typeID) string) {
// sample adds the samples to the given object.
func (s *Stats) sample(id typeID) {
now := time.Now()
now := gohacks.Nanotime()
if len(s.byType) <= int(id) {
// Allocate all the missing entries in one fell swoop.
s.byType = append(s.byType, make([]statEntry, 1+int(id)-len(s.byType))...)
}
s.byType[id].total += now.Sub(s.last)
s.byType[id].total += time.Duration(now - s.last)
s.last = now
}
@@ -124,7 +126,7 @@ func (s *Stats) String() string {
total time.Duration
)
buf.WriteString("\n")
buf.WriteString(fmt.Sprintf("% 16s | % 8s | % 16s | %s\n", "total", "count", "per", "type"))
fmt.Fprintf(&buf, "% 16s | % 8s | % 16s | %s\n", "total", "count", "per", "type")
buf.WriteString("-----------------+----------+------------------+----------------\n")
for _, se := range ss {
if se.entry.count == 0 {
@@ -135,11 +137,11 @@ func (s *Stats) String() string {
count += se.entry.count
total += se.entry.total
per := se.entry.total / time.Duration(se.entry.count)
buf.WriteString(fmt.Sprintf("% 16s | %8d | % 16s | %s\n",
se.entry.total, se.entry.count, per, se.name))
fmt.Fprintf(&buf, "% 16s | %8d | % 16s | %s\n",
se.entry.total, se.entry.count, per, se.name)
}
buf.WriteString("-----------------+----------+------------------+----------------\n")
buf.WriteString(fmt.Sprintf("% 16s | % 8d | % 16s | [all]",
total, count, total/time.Duration(count)))
return string(buf.Bytes())
fmt.Fprintf(&buf, "% 16s | % 8d | % 16s | [all]",
total, count, total/time.Duration(count))
return buf.String()
}
+17 -17
View File
@@ -295,23 +295,23 @@ const interfaceType = "interface"
var primitiveTypeDatabase = func() map[string]reflect.Type {
r := make(map[string]reflect.Type)
for _, t := range []reflect.Type{
reflect.TypeOf(false),
reflect.TypeOf(int(0)),
reflect.TypeOf(int8(0)),
reflect.TypeOf(int16(0)),
reflect.TypeOf(int32(0)),
reflect.TypeOf(int64(0)),
reflect.TypeOf(uint(0)),
reflect.TypeOf(uintptr(0)),
reflect.TypeOf(uint8(0)),
reflect.TypeOf(uint16(0)),
reflect.TypeOf(uint32(0)),
reflect.TypeOf(uint64(0)),
reflect.TypeOf(""),
reflect.TypeOf(float32(0.0)),
reflect.TypeOf(float64(0.0)),
reflect.TypeOf(complex64(0.0)),
reflect.TypeOf(complex128(0.0)),
reflect.TypeFor[bool](),
reflect.TypeFor[int](),
reflect.TypeFor[int8](),
reflect.TypeFor[int16](),
reflect.TypeFor[int32](),
reflect.TypeFor[int64](),
reflect.TypeFor[uint](),
reflect.TypeFor[uintptr](),
reflect.TypeFor[uint8](),
reflect.TypeFor[uint16](),
reflect.TypeFor[uint32](),
reflect.TypeFor[uint64](),
reflect.TypeFor[string](),
reflect.TypeFor[float32](),
reflect.TypeFor[float64](),
reflect.TypeFor[complex64](),
reflect.TypeFor[complex128](),
} {
r[t.Name()] = t
}
+1 -1
View File
@@ -509,7 +509,7 @@ func loadMap(r *Reader) Map {
func (m *Map) save(w *Writer) {
l := Uint(len(m.Keys))
if int(l) != len(m.Values) {
panic(fmt.Sprintf("mismatched keys (%d) Aand values (%d)", len(m.Keys), len(m.Values)))
panic(fmt.Sprintf("mismatched keys (%d) and values (%d)", len(m.Keys), len(m.Values)))
}
l.save(w)
if l == 0 {
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2020 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package sync
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2020 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build !checklocks
// +build !checklocks
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2020 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build checklocks
// +build checklocks
+5 -4
View File
@@ -110,8 +110,8 @@ func (g *Gate) leaveClosed() {
if atomic.LoadUintptr(&g.closingG) == 0 {
return
}
if g := atomic.SwapUintptr(&g.closingG, 0); g > preparingG {
goready(g, 0)
if cG := atomic.SwapUintptr(&g.closingG, 0); cG > preparingG {
goready(cG, 0)
}
}
@@ -133,8 +133,8 @@ func (g *Gate) Close() {
panic("concurrent Close of sync.Gate")
}
if g := atomic.SwapUintptr(&g.closingG, preparingG); g != 0 {
panic(fmt.Sprintf("invalid sync.Gate.closingG during Close: %#x", g))
if cG := atomic.SwapUintptr(&g.closingG, preparingG); cG != 0 {
panic(fmt.Sprintf("invalid sync.Gate.closingG during Close: %#x", cG))
}
if atomic.LoadInt32(&g.userCount) == math.MinInt32 {
// The last call to Leave arrived while we were setting up closingG.
@@ -142,6 +142,7 @@ func (g *Gate) Close() {
}
// WaitReasonSemacquire/TraceBlockSync are consistent with WaitGroup.
gopark(gateCommit, gohacks.Noescape(unsafe.Pointer(&g.closingG)), WaitReasonSemacquire, TraceBlockSync, 0)
RaceAcquire(unsafe.Pointer(&g.closingG))
}
//go:norace
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2020 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build go1.13 && !go1.14
// +build go1.13,!go1.14
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2020 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build go1.14
// +build go1.14
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2019 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package sync
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2019 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build !race
// +build !race
+1 -1
View File
@@ -21,7 +21,7 @@
TEXT ·RaceUncheckedAtomicCompareAndSwapUintptr(SB),NOSPLIT,$0-25
MOVD ptr+0(FP), R0
MOVD old+8(FP), R1
MOVD new+16(FP), R1
MOVD new+16(FP), R2
again:
LDAXR (R0), R3
CMP R1, R3
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2019 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build race
// +build race
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2020 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build amd64
@@ -16,7 +16,7 @@ package sync
// Values for the reason argument to gopark, from Go's src/runtime/runtime2.go.
const (
WaitReasonSelect uint8 = 9 // +checkconst runtime waitReasonSelect
WaitReasonChanReceive uint8 = 14 // +checkconst runtime waitReasonChanReceive
WaitReasonSemacquire uint8 = 18 // +checkconst runtime waitReasonSemacquire
WaitReasonSelect uint8 = 18
WaitReasonChanReceive uint8 = 19
WaitReasonSemacquire uint8 = 13
)
@@ -16,6 +16,6 @@ package sync
// TraceBlockReason constants, from Go's src/runtime/trace2runtime.go.
const (
TraceBlockSelect TraceBlockReason = 3 // +checkconst runtime traceBlockSelect
TraceBlockSync TraceBlockReason = 5 // +checkconst runtime traceBlockSync
TraceBlockSelect TraceBlockReason = 3
TraceBlockSync TraceBlockReason = 5
)
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2023 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build go1.21 && !go1.24
+4 -2
View File
@@ -1,9 +1,11 @@
// Copyright 2024 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build go1.24
// https://go.dev/cl/691596 (1.26) renames the internal map type which nogo relies on.
//go:build go1.24 && !go1.26
package sync
@@ -0,0 +1,16 @@
// Copyright 2024 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// https://go.dev/cl/691596 (1.26) renames the internal map type which nogo relies on.
//go:build go1.26
package sync
import "unsafe"
// Use checkoffset to assert that maptype.hasher (the only field we use) has
// the correct offset.
const maptypeHasherOffset = unsafe.Offsetof(maptype{}.Hasher) // +checkoffset internal/abi MapType.Hasher
@@ -1,7 +1,8 @@
// Copyright 2023 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// runtime.maptype is moved to internal/abi.MapType in Go 1.21.
//
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2020 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
//go:build !amd64
// +build !amd64
@@ -16,7 +16,7 @@
#include "textflag.h"
#define NMSPINNING_OFFSET 92 // +checkoffset runtime schedt.nmspinning
#define NMSPINNING_OFFSET 116
TEXT ·addrOfSpinning(SB),NOSPLIT|NOFRAME,$0-8
LEAQ runtime·sched(SB), AX
@@ -1,4 +1,4 @@
// Copyright 2020 The gVisor Authors.
// Copyright 2023 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.
@@ -12,15 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !go1.23
//go:build !amd64
#include "textflag.h"
#define GOID_OFFSET 152 // +checkoffset runtime g.goid
// func goid() int64
TEXT ·goid(SB),NOSPLIT|NOFRAME,$0-8
MOVQ (TLS), R14
MOVQ GOID_OFFSET(R14), R14
MOVQ R14, ret+0(FP)
RET
// This file is intentionally left blank. arm64 doesn't use
// addrOfSpinning, but we still need an input to the nogo template rule.
+7 -1
View File
@@ -15,4 +15,10 @@
//go:build !amd64
// This file is intentionally left blank. Other arches don't use
// addrOfSpinning, but we still need an input to the nogo template rule.
// addrOfSpinning, but because this package is partially used in Netstack, we
// should support arches that aren't amd64 or arm64. Having this file here
// ensures that `go build` doesn't compile the package with the `-complete`
// flag, because the package isn't made up of just '.go' files.
// This allows Netstack to use the architecture-independent portions of this
// package, because the architecture-dependent portions are never compiled in
// the first place.
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2020 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// //go:linkname directives type-checked by checklinkname.
// Runtime type copies checked by checkoffset.
+3 -1
View File
@@ -1,7 +1,9 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2019 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// This is mostly copied from the standard library's sync/rwmutex.go.
//
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2019 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package sync
+2 -1
View File
@@ -1,7 +1,8 @@
// Copyright 2019 The gVisor Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// Package sync provides synchronization primitives.
//
+51 -23
View File
@@ -128,26 +128,34 @@ func (l *TCPListener) Addr() net.Addr {
}
type deadlineTimer struct {
// mu protects the fields below.
mu sync.Mutex
readTimer *time.Timer
readCancelCh chan struct{}
writeTimer *time.Timer
// +checklocks:mu
readTimer *time.Timer
// +checklocks:mu
readCancelCh chan struct{}
// +checklocks:mu
writeTimer *time.Timer
// +checklocks:mu
writeCancelCh chan struct{}
}
func (d *deadlineTimer) init() {
d.readCancelCh = make(chan struct{})
d.writeCancelCh = make(chan struct{})
func newDeadlineTimer() deadlineTimer {
return deadlineTimer{
readCancelCh: make(chan struct{}),
writeCancelCh: make(chan struct{}),
}
}
// +checklocksexclude:d.mu
func (d *deadlineTimer) readCancel() <-chan struct{} {
d.mu.Lock()
c := d.readCancelCh
d.mu.Unlock()
return c
}
// +checklocksexclude:d.mu
func (d *deadlineTimer) writeCancel() <-chan struct{} {
d.mu.Lock()
c := d.writeCancelCh
@@ -157,11 +165,11 @@ func (d *deadlineTimer) writeCancel() <-chan struct{} {
// setDeadline contains the shared logic for setting a deadline.
//
// cancelCh and timer must be pointers to deadlineTimer.readCancelCh and
// deadlineTimer.readTimer or deadlineTimer.writeCancelCh and
// deadlineTimer.writeTimer.
// cancelCh and timer must point to d.readCancelCh and d.readTimer, or to
// d.writeCancelCh and d.writeTimer. checklocks verifies that d.mu is held,
// but cannot prove that the arguments identify the matching fields of d.
//
// setDeadline must only be called while holding d.mu.
// +checklocks:d.mu
func (d *deadlineTimer) setDeadline(cancelCh *chan struct{}, timer **time.Timer, t time.Time) {
if *timer != nil && !(*timer).Stop() {
*cancelCh = make(chan struct{})
@@ -183,7 +191,7 @@ func (d *deadlineTimer) setDeadline(cancelCh *chan struct{}, timer **time.Timer,
return
}
timeout := t.Sub(time.Now())
timeout := time.Until(t)
if timeout <= 0 {
close(*cancelCh)
return
@@ -201,6 +209,8 @@ func (d *deadlineTimer) setDeadline(cancelCh *chan struct{}, timer **time.Timer,
// SetReadDeadline implements net.Conn.SetReadDeadline and
// net.PacketConn.SetReadDeadline.
//
// +checklocksexclude:d.mu
func (d *deadlineTimer) SetReadDeadline(t time.Time) error {
d.mu.Lock()
d.setDeadline(&d.readCancelCh, &d.readTimer, t)
@@ -210,6 +220,8 @@ func (d *deadlineTimer) SetReadDeadline(t time.Time) error {
// SetWriteDeadline implements net.Conn.SetWriteDeadline and
// net.PacketConn.SetWriteDeadline.
//
// +checklocksexclude:d.mu
func (d *deadlineTimer) SetWriteDeadline(t time.Time) error {
d.mu.Lock()
d.setDeadline(&d.writeCancelCh, &d.writeTimer, t)
@@ -218,6 +230,8 @@ func (d *deadlineTimer) SetWriteDeadline(t time.Time) error {
}
// SetDeadline implements net.Conn.SetDeadline and net.PacketConn.SetDeadline.
//
// +checklocksexclude:d.mu
func (d *deadlineTimer) SetDeadline(t time.Time) error {
d.mu.Lock()
d.setDeadline(&d.readCancelCh, &d.readTimer, t)
@@ -234,7 +248,7 @@ type TCPConn struct {
wq *waiter.Queue
ep tcpip.Endpoint
// readMu serializes reads and implicitly protects read.
// readMu serializes reads.
//
// Lock ordering:
// If both readMu and deadlineTimer.mu are to be used in a single
@@ -243,17 +257,18 @@ type TCPConn struct {
// read contains bytes that have been read from the endpoint,
// but haven't yet been returned.
//
// +checklocks:readMu
read []byte
}
// NewTCPConn creates a new TCPConn.
func NewTCPConn(wq *waiter.Queue, ep tcpip.Endpoint) *TCPConn {
c := &TCPConn{
wq: wq,
ep: ep,
return &TCPConn{
deadlineTimer: newDeadlineTimer(),
wq: wq,
ep: ep,
}
c.deadlineTimer.init()
return c
}
// Accept implements net.Conn.Accept.
@@ -343,6 +358,9 @@ func commonRead(b []byte, ep tcpip.Endpoint, wq *waiter.Queue, deadline <-chan s
}
// Read implements net.Conn.Read.
//
// +checklocksexclude:c.readMu
// +checklocksexclude:c.deadlineTimer.mu
func (c *TCPConn) Read(b []byte) (int, error) {
c.readMu.Lock()
defer c.readMu.Unlock()
@@ -357,6 +375,8 @@ func (c *TCPConn) Read(b []byte) (int, error) {
}
// Write implements net.Conn.Write.
//
// +checklocksexclude:c.deadlineTimer.mu
func (c *TCPConn) Write(b []byte) (int, error) {
deadline := c.writeCancel()
@@ -553,12 +573,11 @@ type UDPConn struct {
// NewUDPConn creates a new UDPConn.
func NewUDPConn(wq *waiter.Queue, ep tcpip.Endpoint) *UDPConn {
c := &UDPConn{
ep: ep,
wq: wq,
return &UDPConn{
deadlineTimer: newDeadlineTimer(),
ep: ep,
wq: wq,
}
c.deadlineTimer.init()
return c
}
// DialUDP creates a new UDPConn.
@@ -626,12 +645,16 @@ func (c *UDPConn) RemoteAddr() net.Addr {
}
// Read implements net.Conn.Read
//
// +checklocksexclude:c.deadlineTimer.mu
func (c *UDPConn) Read(b []byte) (int, error) {
bytesRead, _, err := c.ReadFrom(b)
return bytesRead, err
}
// ReadFrom implements net.PacketConn.ReadFrom.
//
// +checklocksexclude:c.deadlineTimer.mu
func (c *UDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
deadline := c.readCancel()
@@ -643,11 +666,16 @@ func (c *UDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
return n, fullToUDPAddr(addr), nil
}
// Write implements net.Conn.Write.
//
// +checklocksexclude:c.deadlineTimer.mu
func (c *UDPConn) Write(b []byte) (int, error) {
return c.WriteTo(b, nil)
}
// WriteTo implements net.PacketConn.WriteTo.
//
// +checklocksexclude:c.deadlineTimer.mu
func (c *UDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
deadline := c.writeCancel()
+1 -1
View File
@@ -170,7 +170,7 @@ var (
// https://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml.
IPv4AllSystems = tcpip.AddrFrom4([4]byte{0xe0, 0x00, 0x00, 0x01})
// IPv4Broadcast is the broadcast address of the IPv4 procotol.
// IPv4Broadcast is the broadcast address of the IPv4 protocol.
IPv4Broadcast = tcpip.AddrFrom4([4]byte{0xff, 0xff, 0xff, 0xff})
// IPv4Any is the non-routable IPv4 "any" meta address.
+70
View File
@@ -205,10 +205,80 @@ func (b IPv6) NextHeader() uint8 {
}
// TransportProtocol implements Network.TransportProtocol.
//
// Deprecated: Use TryParseTransportProtocol instead.
// This function does not parse extension headers and returns the next header
// field of the IPv6 header as the transport
// protocol which may not be the actual transport protocol.
// Use TryParseTransportProtocol to get the transport protocol correctly.
func (b IPv6) TransportProtocol() tcpip.TransportProtocolNumber {
return tcpip.TransportProtocolNumber(b.NextHeader())
}
// IsExtensionHeader returns true if the next header is a known extension header.
func IsExtensionHeader(nextHdr uint8) bool {
extType := IPv6ExtensionHeaderIdentifier(nextHdr)
switch extType {
case IPv6HopByHopOptionsExtHdrIdentifier, IPv6RoutingExtHdrIdentifier, IPv6FragmentExtHdrIdentifier, IPv6DestinationOptionsExtHdrIdentifier, IPv6AuthenticationExtHdrIdentifier, IPv6NoNextHeaderIdentifier:
return true
default:
return false
}
}
// TryParseTransportProtocol parses the IPv6 header and extension headers to get the
// transport protocol.
// Reference: net/ipv6/exthdrs_core.c:ipv6_skip_exthdr.
// Returns the transport protocol and a boolean indicating if the transport
// protocol parsing was successful.
func (b IPv6) TryParseTransportProtocol() (tcpip.TransportProtocolNumber, bool) {
if len(b) < IPv6MinimumSize {
return 0, false
}
data := []byte(b[IPv6MinimumSize:])
nxtHdr := b.NextHeader()
maybeProto := tcpip.TransportProtocolNumber(nxtHdr)
for IsExtensionHeader(nxtHdr) {
dataLen := len(data)
if dataLen < 2 {
return maybeProto, false
}
currHdrLen := 0
switch IPv6ExtensionHeaderIdentifier(nxtHdr) {
case IPv6FragmentExtHdrIdentifier:
// Fragment extension header is always 8 bytes long.
if dataLen < 8 {
return maybeProto, false
}
// Get the fragment offset from the fragment extension header.
fragOffset := binary.BigEndian.Uint16(data[2:4]) & ^uint16(0x7)
if fragOffset != 0 {
return tcpip.TransportProtocolNumber(data[0]), false
}
currHdrLen = 8
case IPv6HopByHopOptionsExtHdrIdentifier, IPv6RoutingExtHdrIdentifier, IPv6DestinationOptionsExtHdrIdentifier:
currHdrLen = int(data[1]+1) * 8
case IPv6AuthenticationExtHdrIdentifier:
// Authentication extension header length calculation is different from
// other extension headers.
currHdrLen = int(data[1]+2) * 4
default:
// IPv6NoNextHeaderIdentifier or any unknown extension header.
return maybeProto, false
}
if currHdrLen > len(data) {
return maybeProto, false
}
nxtHdr = data[0]
maybeProto = tcpip.TransportProtocolNumber(nxtHdr)
data = data[currHdrLen:]
}
if len(data) == 0 {
return maybeProto, false
}
return maybeProto, true
}
// Payload implements Network.Payload.
func (b IPv6) Payload() []byte {
return b[IPv6MinimumSize:][:b.PayloadLength()]
@@ -45,6 +45,11 @@ const (
// Destination Options extension header, as per RFC 8200 section 4.6.
IPv6DestinationOptionsExtHdrIdentifier IPv6ExtensionHeaderIdentifier = 60
// IPv6AuthenticationExtHdrIdentifier is the header identifier of an
// Authentication extension header, as per RFC 8200 section 4.1.
// TODO: b/512233021 - Parse Authentication extension header correctly.
IPv6AuthenticationExtHdrIdentifier IPv6ExtensionHeaderIdentifier = 51
// IPv6NoNextHeaderIdentifier is the header identifier used to signify the end
// of an IPv6 payload, as per RFC 8200 section 4.7.
IPv6NoNextHeaderIdentifier IPv6ExtensionHeaderIdentifier = 59
+12 -2
View File
@@ -158,9 +158,19 @@ traverseExtensions:
//
// Returns true if the header was successfully parsed.
func UDP(pkt *stack.PacketBuffer) bool {
_, ok := pkt.TransportHeader().Consume(header.UDPMinimumSize)
hdr, ok := pkt.TransportHeader().Consume(header.UDPMinimumSize)
if !ok {
return false
}
pkt.TransportProtocolNumber = header.UDPProtocolNumber
return ok
// Validate the UDP payload length.
length := int(header.UDP(hdr).Length()) - header.UDPMinimumSize
if length < 0 || length > pkt.Data().Size() {
return false
}
// Trim the payload to the length specified in the UDP header.
pkt.Data().CapLength(length)
return true
}
// TCP parses a TCP packet found in pkt.Data and populates pkt's transport
+2 -9
View File
@@ -17,7 +17,6 @@ package header
import (
"encoding/binary"
"github.com/google/btree"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
@@ -175,11 +174,6 @@ type SACKBlock struct {
End seqnum.Value
}
// Less returns true if r.Start < b.Start.
func (r SACKBlock) Less(b btree.Item) bool {
return r.Start.LessThan(b.(SACKBlock).Start)
}
// Contains returns true if b is completely contained in r.
func (r SACKBlock) Contains(b SACKBlock) bool {
return r.Start.LessThanEq(b.Start) && b.End.LessThanEq(r.End)
@@ -219,9 +213,8 @@ const (
// TCPTotalHeaderMaximumSize is the maximum size of headers from all layers in
// a TCP packet. It analogous to MAX_TCP_HEADER in Linux.
//
// TODO(b/319936470): Investigate why this needs to be at least 140 bytes. In
// Linux this value is at least 160, but in theory we should be able to use
// 138. In practice anything less than 140 starts to break GSO on gVNIC
// Note: In Linux this value is at least 160, but in theory we should be able
// to use 138. In practice anything less than 140 starts to break GSO on gVNIC
// hardware.
TCPTotalHeaderMaximumSize = 160
+27 -3
View File
@@ -134,20 +134,33 @@ func (b UDP) Encode(u *UDPFields) {
// SetSourcePortWithChecksumUpdate implements ChecksummableTransport.
func (b UDP) SetSourcePortWithChecksumUpdate(new uint16) {
if b.Checksum() == 0 {
b.SetSourcePort(new)
return
}
old := b.SourcePort()
b.SetSourcePort(new)
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
xsum := ^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new)
b.SetChecksum(normalizeChecksum(xsum))
}
// SetDestinationPortWithChecksumUpdate implements ChecksummableTransport.
func (b UDP) SetDestinationPortWithChecksumUpdate(new uint16) {
if b.Checksum() == 0 {
b.SetDestinationPort(new)
return
}
old := b.DestinationPort()
b.SetDestinationPort(new)
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
xsum := ^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new)
b.SetChecksum(normalizeChecksum(xsum))
}
// UpdateChecksumPseudoHeaderAddress implements ChecksummableTransport.
func (b UDP) UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address, fullChecksum bool) {
if fullChecksum && b.Checksum() == 0 {
return
}
xsum := b.Checksum()
if fullChecksum {
xsum = ^xsum
@@ -155,7 +168,7 @@ func (b UDP) UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address, fullCheck
xsum = checksumUpdate2ByteAlignedAddress(xsum, old, new)
if fullChecksum {
xsum = ^xsum
xsum = normalizeChecksum(^xsum)
}
b.SetChecksum(xsum)
@@ -193,3 +206,14 @@ func UDPValid(hdr UDP, payloadChecksum func() uint16, payloadSize uint16, netPro
return true, hdr.IsChecksumValid(srcAddr, dstAddr, payloadChecksum())
}
func normalizeChecksum(xsum uint16) uint16 {
// RFC 768:
// If the computed UDP checksum is zero, it is transmitted as all ones.
// An all zero transmitted checksum value means that
// the transmitter generated no checksum.
if xsum == 0 {
return 0xFFFF
}
return xsum
}
+4 -4
View File
@@ -87,8 +87,8 @@ func (v VirtioNetHeader) CSumOffset() uint16 {
func (v VirtioNetHeader) Encode(f *VirtioNetHeaderFields) {
v[flags] = uint8(f.Flags)
v[gsoType] = uint8(f.GSOType)
binary.BigEndian.PutUint16(v[hdrLen:], f.HdrLen)
binary.BigEndian.PutUint16(v[gsoSize:], f.GSOSize)
binary.BigEndian.PutUint16(v[csumStart:], f.CSumStart)
binary.BigEndian.PutUint16(v[csumOffset:], f.CSumOffset)
binary.LittleEndian.PutUint16(v[hdrLen:], f.HdrLen)
binary.LittleEndian.PutUint16(v[gsoSize:], f.GSOSize)
binary.LittleEndian.PutUint16(v[csumStart:], f.CSumStart)
binary.LittleEndian.PutUint16(v[csumOffset:], f.CSumOffset)
}
@@ -83,12 +83,19 @@ type FragmentID struct {
//
// +stateify savable
type Fragmentation struct {
mu sync.Mutex `state:"nosave"`
highLimit int
lowLimit int
reassemblers map[FragmentID]*reassembler
rList reassemblerList
memSize int
mu sync.Mutex `state:"nosave"`
highLimit int
lowLimit int
// +checklocks:mu
reassemblers map[FragmentID]*reassembler
// +checklocks:mu
rList reassemblerList
// +checklocks:mu
memSize int
timeout time.Duration
blockSize uint16
clock tcpip.Clock
@@ -101,6 +108,14 @@ type TimeoutHandler interface {
// OnReassemblyTimeout will be called with the first fragment (or nil, if the
// first fragment has not been received) of a packet whose reassembly has
// timed out.
//
// The packet is borrowed for the duration of the call; a handler that
// retains it must take its own reference.
//
// The call is synchronous with the owning Fragmentation's mutex held.
// The handler must not reenter that Fragmentation's Process or Release.
// checklocks cannot name that mutex because TimeoutHandler does not
// expose the owning Fragmentation.
OnReassemblyTimeout(pkt *stack.PacketBuffer)
}
@@ -116,8 +131,7 @@ type TimeoutHandler interface {
// fragments after reaching highMemoryLimit.
//
// reassemblingTimeout specifies the maximum time allowed to reassemble a packet.
// Fragments are lazily evicted only when a new a packet with an
// already existing fragmentation-id arrives after the timeout.
// Incomplete reassemblies are released once this timeout elapses.
func NewFragmentation(blockSize uint16, highMemoryLimit, lowMemoryLimit int, reassemblingTimeout time.Duration, clock tcpip.Clock, timeoutHandler TimeoutHandler) *Fragmentation {
if lowMemoryLimit >= highMemoryLimit {
lowMemoryLimit = highMemoryLimit
@@ -140,6 +154,8 @@ func NewFragmentation(blockSize uint16, highMemoryLimit, lowMemoryLimit int, rea
clock: clock,
timeoutHandler: timeoutHandler,
}
// Job locks f.mu before invoking this callback; checklocks cannot carry
// that lock through the stored function value.
f.releaseJob = tcpip.NewJob(f.clock, &f.mu, f.releaseReassemblersLocked)
return f
@@ -158,6 +174,8 @@ func NewFragmentation(blockSize uint16, highMemoryLimit, lowMemoryLimit int, rea
// proto is the protocol number marked in the fragment being processed. It has
// to be given here outside of the FragmentID struct because IPv6 should not use
// the protocol to identify a fragment.
//
// +checklocksexclude:f.mu
func (f *Fragmentation) Process(
id FragmentID, first, last uint16, more bool, proto uint8, pkt *stack.PacketBuffer) (
*stack.PacketBuffer, uint8, bool, error) {
@@ -180,6 +198,7 @@ func (f *Fragmentation) Process(
f.mu.Lock()
if f.reassemblers == nil {
f.mu.Unlock()
return nil, 0, false, fmt.Errorf("Release() called before fragmentation processing could finish")
}
@@ -228,6 +247,8 @@ func (f *Fragmentation) Process(
}
// Release releases all underlying resources.
//
// +checklocksexclude:f.mu
func (f *Fragmentation) Release() {
f.mu.Lock()
defer f.mu.Unlock()
@@ -237,40 +258,52 @@ func (f *Fragmentation) Release() {
f.reassemblers = nil
}
// release releases r if it has not already been marked done.
// r must belong to f.
//
// +checklocks:f.mu
// +checklocksexclude:r.mu
func (f *Fragmentation) release(r *reassembler, timedOut bool) {
// Before releasing a fragment we need to check if r is already marked as done.
// Otherwise, we would delete it twice.
if r.checkDoneOrMark() {
return
}
// checkDoneOrMark waited for active processing under r.mu. Since it
// returned false, this call marked r done, so subsequent process calls
// return before accessing r.memSize, r.pkt, or r.holes. This call now
// owns those fields exclusively without r.mu. checklocks cannot model
// this ownership transfer.
delete(f.reassemblers, r.id)
f.rList.Remove(r)
f.memSize -= r.memSize
f.memSize -= r.memSize // +checklocksignore
if f.memSize < 0 {
log.Warningf("memory counter < 0 (%d), this is an accounting bug that requires investigation", f.memSize)
f.memSize = 0
}
if h := f.timeoutHandler; timedOut && h != nil {
h.OnReassemblyTimeout(r.pkt)
h.OnReassemblyTimeout(r.pkt) // +checklocksignore
}
if r.pkt != nil {
r.pkt.DecRef()
r.pkt = nil
if r.pkt != nil { // +checklocksignore
r.pkt.DecRef() // +checklocksignore
r.pkt = nil // +checklocksignore
}
for _, h := range r.holes {
for _, h := range r.holes { // +checklocksignore
if h.pkt != nil {
h.pkt.DecRef()
h.pkt = nil
}
}
r.holes = nil
r.holes = nil // +checklocksignore
}
// releaseReassemblersLocked releases already-expired reassemblers, then
// schedules the job to call back itself for the remaining reassemblers if
// any. This function must be called with f.mu locked.
// any.
//
// +checklocks:f.mu
func (f *Fragmentation) releaseReassemblersLocked() {
now := f.clock.NowMonotonic()
for {
@@ -301,6 +334,7 @@ type PacketFragmenter struct {
fragmentCount int
currentFragment int
fragmentOffset int
mark uint32
}
// MakePacketFragmenter prepares the struct needed for packet fragmentation.
@@ -331,6 +365,7 @@ func MakePacketFragmenter(pkt *stack.PacketBuffer, fragmentPayloadLen uint32, re
reserve: reserve,
fragmentPayloadLen: int(fragmentPayloadLen),
fragmentCount: int(fragmentCount),
mark: pkt.Mark,
}
}
@@ -350,6 +385,7 @@ func (pf *PacketFragmenter) BuildNextFragment() (*stack.PacketBuffer, int, int,
fragPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: pf.reserve,
Mark: pf.mark,
})
// Copy data for the fragment.
@@ -15,14 +15,21 @@
package fragmentation
import (
"cmp"
"math"
"sort"
"slices"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// hole describes a missing or received range in a reassembler.
//
// Holes stored in reassembler.holes are protected by that reassembler's mu
// until release takes exclusive ownership. A hole has no reference to its
// reassembler, so checklocks cannot name the owning mutex.
//
// +stateify savable
type hole struct {
first uint16
@@ -34,18 +41,34 @@ type hole struct {
pkt *stack.PacketBuffer
}
// reassembler holds a packet's fragments until reassembly or release.
//
// Fragmentation.release claims exclusive ownership of memSize, holes, and
// pkt by marking reassembly done under mu. Those cleanup accesses need
// exceptions because checklocks cannot model the ownership transfer.
//
// +stateify savable
type reassembler struct {
// reassemblerEntry is protected by the owning Fragmentation.mu. There
// is no reference back to that Fragmentation, so checklocks cannot name
// its mutex here.
reassemblerEntry
id FragmentID
memSize int
proto uint8
mu sync.Mutex `state:"nosave"`
holes []hole
filled int
id FragmentID
// +checklocks:mu
memSize int
// +checklocks:mu
proto uint8
// mu follows the owning Fragmentation.mu in lock order.
mu sync.Mutex `state:"nosave"`
// +checklocks:mu
holes []hole
// +checklocks:mu
filled int
// +checklocks:mu
done bool
createdAt tcpip.MonotonicTime
pkt *stack.PacketBuffer
// +checklocks:mu
pkt *stack.PacketBuffer
}
func newReassembler(id FragmentID, clock tcpip.Clock) *reassembler {
@@ -62,6 +85,7 @@ func newReassembler(id FragmentID, clock tcpip.Clock) *reassembler {
return r
}
// +checklocksexclude:r.mu
func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *stack.PacketBuffer) (*stack.PacketBuffer, uint8, bool, int, error) {
r.mu.Lock()
defer r.mu.Unlock()
@@ -103,8 +127,21 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
}
holeFound = true
// IPv6: rfc8200#section-4.5
// Changed the text to require that IPv6 nodes must not create
// overlapping fragments. Also, when reassembling an IPv6
// datagram, if one or more its constituent fragments is
// determined to be an overlapping fragment, the entire datagram
// (and any constituent fragments) must be silently discarded.
// Includes a clarification that no ICMP error message should be
// sent if overlapping fragments are received.
if currentHole.filled {
// Incoming fragment is a subset of an existing fragment.
if first != currentHole.first || last != currentHole.last {
return nil, 0, false, 0, ErrFragmentOverlap
}
// Incoming fragment is a duplicate.
// Not dropping packet in case of duplicates.
continue
}
@@ -165,8 +202,8 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
return nil, 0, false, memConsumed, nil
}
sort.Slice(r.holes, func(i, j int) bool {
return r.holes[i].first < r.holes[j].first
slices.SortFunc(r.holes, func(a, b hole) int {
return cmp.Compare(a.first, b.first)
})
resPkt := r.holes[0].pkt.Clone()
@@ -176,6 +213,7 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
return resPkt, r.proto, true /* done */, memConsumed, nil
}
// +checklocksexclude:r.mu
func (r *reassembler) checkDoneOrMark() bool {
r.mu.Lock()
prev := r.done
@@ -39,7 +39,7 @@ type dadState struct {
extendRequest extendRequest
done *bool
timer tcpip.Timer
timer tcpip.Timer `state:"nosave"`
completionHandlers []stack.DADCompletionHandler
}
@@ -291,13 +291,13 @@ type GenericMulticastProtocolState struct {
robustnessVariable uint8
queryInterval time.Duration
mode protocolMode
modeTimer tcpip.Timer
modeTimer tcpip.Timer `state:"nosave"`
generalQueryV2Timer tcpip.Timer
generalQueryV2Timer tcpip.Timer `state:"nosave"`
// TODO(b/341946753): Restore when netstack is savable.
generalQueryV2TimerFiresAt time.Time `state:"nosave"`
stateChangedReportV2Timer tcpip.Timer
stateChangedReportV2Timer tcpip.Timer `state:"nosave"`
stateChangedReportV2TimerSet bool
}
@@ -17,7 +17,6 @@ func (d *dadState) StateFields() []string {
"nonce",
"extendRequest",
"done",
"timer",
"completionHandlers",
}
}
@@ -30,8 +29,7 @@ func (d *dadState) StateSave(stateSinkObject state.Sink) {
stateSinkObject.Save(0, &d.nonce)
stateSinkObject.Save(1, &d.extendRequest)
stateSinkObject.Save(2, &d.done)
stateSinkObject.Save(3, &d.timer)
stateSinkObject.Save(4, &d.completionHandlers)
stateSinkObject.Save(3, &d.completionHandlers)
}
func (d *dadState) afterLoad(context.Context) {}
@@ -41,8 +39,7 @@ func (d *dadState) StateLoad(ctx context.Context, stateSourceObject state.Source
stateSourceObject.Load(0, &d.nonce)
stateSourceObject.Load(1, &d.extendRequest)
stateSourceObject.Load(2, &d.done)
stateSourceObject.Load(3, &d.timer)
stateSourceObject.Load(4, &d.completionHandlers)
stateSourceObject.Load(3, &d.completionHandlers)
}
func (d *DADOptions) StateTypeName() string {
@@ -237,9 +234,6 @@ func (g *GenericMulticastProtocolState) StateFields() []string {
"robustnessVariable",
"queryInterval",
"mode",
"modeTimer",
"generalQueryV2Timer",
"stateChangedReportV2Timer",
"stateChangedReportV2TimerSet",
}
}
@@ -254,10 +248,7 @@ func (g *GenericMulticastProtocolState) StateSave(stateSinkObject state.Sink) {
stateSinkObject.Save(2, &g.robustnessVariable)
stateSinkObject.Save(3, &g.queryInterval)
stateSinkObject.Save(4, &g.mode)
stateSinkObject.Save(5, &g.modeTimer)
stateSinkObject.Save(6, &g.generalQueryV2Timer)
stateSinkObject.Save(7, &g.stateChangedReportV2Timer)
stateSinkObject.Save(8, &g.stateChangedReportV2TimerSet)
stateSinkObject.Save(5, &g.stateChangedReportV2TimerSet)
}
func (g *GenericMulticastProtocolState) afterLoad(context.Context) {}
@@ -269,10 +260,7 @@ func (g *GenericMulticastProtocolState) StateLoad(ctx context.Context, stateSour
stateSourceObject.Load(2, &g.robustnessVariable)
stateSourceObject.Load(3, &g.queryInterval)
stateSourceObject.Load(4, &g.mode)
stateSourceObject.Load(5, &g.modeTimer)
stateSourceObject.Load(6, &g.generalQueryV2Timer)
stateSourceObject.Load(7, &g.stateChangedReportV2Timer)
stateSourceObject.Load(8, &g.stateChangedReportV2TimerSet)
stateSourceObject.Load(5, &g.stateChangedReportV2TimerSet)
}
func (m *MultiCounterIPForwardingStats) StateTypeName() string {
@@ -0,0 +1,293 @@
// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ip
import (
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// ipv6FragmentOffset returns the fragment offset of the IPv6 packet
// if present.
func ipv6FragmentOffset(pkt *stack.PacketBuffer, ipHdr header.IPv6) (uint16, bool) {
if !header.IsExtensionHeader(ipHdr.NextHeader()) {
return 0, false
}
netHeaderSlice := pkt.NetworkHeader().Slice()
if len(netHeaderSlice) <= header.IPv6MinimumSize {
return 0, false
}
// Make an iterator to walk the extension headers.
buf := buffer.MakeWithData(netHeaderSlice[header.IPv6MinimumSize:])
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(ipHdr.NextHeader()), buf)
defer it.Release()
for {
extHdr, done, err := it.Next()
if err != nil || done {
break
}
switch extHdr := extHdr.(type) {
case header.IPv6FragmentExtHdr:
offset := extHdr.FragmentOffset()
extHdr.Release()
return offset, true
default:
extHdr.Release()
}
}
return 0, false
}
// buildResetPayloadV4 builds an IPv4 + TCP Reset packet in a buffer.
func buildResetPayloadV4(ttl uint8, src, dst tcpip.Address, tcpHdr header.TCP, seq, ack uint32, flags header.TCPFlags) *buffer.View {
totalHdrLen := header.IPv4MinimumSize + header.TCPMinimumSize
v := buffer.NewViewSize(totalHdrLen)
buf := v.AsSlice()
rstIPHdr := header.IPv4(buf[:header.IPv4MinimumSize])
rstIPHdr.Encode(&header.IPv4Fields{
TotalLength: uint16(totalHdrLen),
TTL: ttl,
Protocol: uint8(header.TCPProtocolNumber),
TOS: stack.DefaultTOS,
Flags: header.IPv4FlagDontFragment,
// Flip source and destination addresses.
SrcAddr: dst,
DstAddr: src,
})
rstTCPHdr := header.TCP(buf[header.IPv4MinimumSize:])
rstTCPHdr.Encode(&header.TCPFields{
SrcPort: tcpHdr.DestinationPort(),
DstPort: tcpHdr.SourcePort(),
SeqNum: seq,
AckNum: ack,
DataOffset: header.TCPMinimumSize,
Flags: flags,
})
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, dst, src, header.TCPMinimumSize)
rstTCPHdr.SetChecksum(0)
rstTCPHdr.SetChecksum(^rstTCPHdr.CalculateChecksum(xsum))
return v
}
// buildResetPayloadV6 builds an IPv6 + TCP Reset packet in a buffer.
func buildResetPayloadV6(hopLimit uint8, src, dst tcpip.Address, tcpHdr header.TCP, seq, ack uint32, flags header.TCPFlags) *buffer.View {
totalHdrLen := header.IPv6MinimumSize + header.TCPMinimumSize
v := buffer.NewViewSize(totalHdrLen)
buf := v.AsSlice()
rstIPHdr := header.IPv6(buf[:header.IPv6MinimumSize])
rstIPHdr.Encode(&header.IPv6Fields{
PayloadLength: uint16(header.TCPMinimumSize),
TransportProtocol: header.TCPProtocolNumber,
HopLimit: hopLimit,
// Flip source and destination addresses.
SrcAddr: dst,
DstAddr: src,
})
rstTCPHdr := header.TCP(buf[header.IPv6MinimumSize:])
rstTCPHdr.Encode(&header.TCPFields{
SrcPort: tcpHdr.DestinationPort(),
DstPort: tcpHdr.SourcePort(),
SeqNum: seq,
AckNum: ack,
DataOffset: header.TCPMinimumSize,
Flags: flags,
})
// Compute TCP checksum.
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, dst, src, header.TCPMinimumSize)
rstTCPHdr.SetChecksum(0)
rstTCPHdr.SetChecksum(^rstTCPHdr.CalculateChecksum(xsum))
return v
}
// RejectWithTCPReset sends a TCP reset in response to the packet.
//
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_send_reset[6]()
func RejectWithTCPReset(pkt *stack.PacketBuffer, netProto tcpip.NetworkProtocolNumber, stk *stack.Stack, deliveredLocally bool) tcpip.Error {
var src, dst tcpip.Address
var ttl uint8
isFragment := false
switch netProto {
case header.IPv4ProtocolNumber:
// Ref: net/ipv4/netfilter/nf_reject_ipv4.c:nf_reject_ip_tcphdr_get
ipHdr := header.IPv4(pkt.NetworkHeader().Slice())
if len(ipHdr) < header.IPv4MinimumSize {
return nil
}
if ipHdr.Protocol() != uint8(header.TCPProtocolNumber) {
return nil
}
if ipHdr.FragmentOffset() != 0 {
return nil
}
isFragment = ipHdr.More()
src = ipHdr.SourceAddress()
dst = ipHdr.DestinationAddress()
// Ref: net/ipv4/netfilter/nf_reject_ipv4.c:nf_send_reset
if header.IsV4MulticastAddress(dst) ||
header.IsV4MulticastAddress(src) ||
pkt.NetworkPacketInfo.LocalAddressBroadcast ||
pkt.PktType == tcpip.PacketBroadcast || pkt.PktType == tcpip.PacketMulticast ||
src == header.IPv4Any || dst == header.IPv4Any {
return nil
}
case header.IPv6ProtocolNumber:
// Ref: net/ipv6/netfilter/nf_reject_ipv6.c:nf_reject_ip6_tcphdr_get
ipHdr := header.IPv6(pkt.NetworkHeader().Slice())
if len(ipHdr) < header.IPv6MinimumSize {
return nil
}
fragOffset, ok := ipv6FragmentOffset(pkt, ipHdr)
if ok && fragOffset != 0 {
return nil
}
isFragment = ok
src = ipHdr.SourceAddress()
dst = ipHdr.DestinationAddress()
// Ref: net/ipv6/netfilter/nf_reject_ipv6.c:nf_send_reset6
if header.IsV6MulticastAddress(src) || header.IsV6MulticastAddress(dst) ||
header.IsV4MappedAddress(src) || header.IsV4MappedAddress(dst) ||
src == header.IPv6Any || dst == header.IPv6Any ||
pkt.PktType == tcpip.PacketBroadcast || pkt.PktType == tcpip.PacketMulticast {
return nil
}
default:
return nil
}
tcpHdr := func(pkt *stack.PacketBuffer) header.TCP {
// If 0 < len(transportHdr) < header.TCPMinimumSize, then the TCP header is invalid.
// Assuming a TCP packet,
// if the TCP header was parsed, the
// len should be >= header.TCPMinimumSize;
// else the TCP header was not parsed and the len should be 0.
transportHdr := pkt.TransportHeader().Slice()
if len(transportHdr) >= header.TCPMinimumSize {
return header.TCP(transportHdr)
}
if len(transportHdr) != 0 {
return nil
}
// In the case of fragmented TCP packets, the TCP header may not be parsed.
// Pull up the TCP header from the payload.
b, ok := pkt.Data().PullUp(header.TCPMinimumSize)
if !ok {
return nil
}
hdr := header.TCP(b)
hdrLen := int(hdr.DataOffset())
if hdrLen < header.TCPMinimumSize || pkt.Data().Size() < hdrLen {
return nil
}
tcpHdr, ok := pkt.Data().Consume(hdrLen)
if !ok {
return nil
}
pkt.TransportProtocolNumber = header.TCPProtocolNumber
return header.TCP(tcpHdr)
}(pkt)
if tcpHdr == nil {
return nil
}
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_reject_ip[6]_tcphdr_get()
// No RST for RST as this will cause a loop.
if tcpHdr.Flags().Contains(header.TCPFlagRst) {
return nil
}
// Check checksum integrity only for non-fragmented packets.
// We don't support refragmentation(nf_defrag) before REJECT,
// so we can't validate the checksum for fragmented packets.
if !isFragment {
// Check checksum integrity.
if !pkt.RXChecksumValidated && !tcpHdr.IsChecksumValid(src, dst, pkt.Data().Checksum(), uint16(pkt.Data().Size())) {
return nil
}
}
localAddr := dst
if !deliveredLocally {
// If the packet wasn't delivered locally, do not use the packet's destination
// address as the response's source address as we should not own the
// destination address.
localAddr = tcpip.Address{}
}
route, err := stk.FindRoute(0 /*nicID*/, localAddr, src, netProto, false /* multicastLoop */)
if err != nil {
return err
}
defer route.Release()
ttl = route.DefaultTTL()
var seq uint32
var ack uint32
payloadLen := uint32(pkt.Data().Size())
flags := header.TCPFlagRst
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_reject_ip[6]_tcphdr_put()
if tcpHdr.Flags()&header.TCPFlagAck != 0 {
seq = tcpHdr.AckNumber()
} else {
flags |= header.TCPFlagAck
ack = tcpHdr.SequenceNumber() + payloadLen
if tcpHdr.Flags()&header.TCPFlagSyn != 0 {
ack++
}
if tcpHdr.Flags()&header.TCPFlagFin != 0 {
ack++
}
}
var v *buffer.View
if netProto == header.IPv4ProtocolNumber {
v = buildResetPayloadV4(ttl, src, dst, tcpHdr, seq, ack, flags)
} else {
v = buildResetPayloadV6(ttl, src, dst, tcpHdr, seq, ack, flags)
}
rstPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(route.MaxHeaderLength()),
Payload: buffer.MakeWithView(v),
})
rstPkt.TransportProtocolNumber = header.TCPProtocolNumber
defer rstPkt.DecRef()
// TODO: b/521536712 - Add support for mark propagation.
if err := route.WriteHeaderIncludedPacket(rstPkt); err != nil {
return err
}
return nil
}
@@ -16,7 +16,6 @@ func (r *RouteTable) StateFields() []string {
return []string{
"installedRoutes",
"pendingRoutes",
"cleanupPendingRoutesTimer",
"isCleanupRoutineRunning",
"config",
}
@@ -29,9 +28,8 @@ func (r *RouteTable) StateSave(stateSinkObject state.Sink) {
r.beforeSave()
stateSinkObject.Save(0, &r.installedRoutes)
stateSinkObject.Save(1, &r.pendingRoutes)
stateSinkObject.Save(2, &r.cleanupPendingRoutesTimer)
stateSinkObject.Save(3, &r.isCleanupRoutineRunning)
stateSinkObject.Save(4, &r.config)
stateSinkObject.Save(2, &r.isCleanupRoutineRunning)
stateSinkObject.Save(3, &r.config)
}
func (r *RouteTable) afterLoad(context.Context) {}
@@ -40,9 +38,8 @@ func (r *RouteTable) afterLoad(context.Context) {}
func (r *RouteTable) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &r.installedRoutes)
stateSourceObject.Load(1, &r.pendingRoutes)
stateSourceObject.Load(2, &r.cleanupPendingRoutesTimer)
stateSourceObject.Load(3, &r.isCleanupRoutineRunning)
stateSourceObject.Load(4, &r.config)
stateSourceObject.Load(2, &r.isCleanupRoutineRunning)
stateSourceObject.Load(3, &r.config)
}
func (r *InstalledRoute) StateTypeName() string {
@@ -57,7 +57,7 @@ type RouteTable struct {
// cleanupPendingRoutesTimer is a timer that triggers a routine to remove
// pending routes that are expired.
// +checklocks:pendingMu
cleanupPendingRoutesTimer tcpip.Timer
cleanupPendingRoutesTimer tcpip.Timer `state:"nosave"`
// +checklocks:pendingMu
isCleanupRoutineRunning bool
+133 -102
View File
@@ -16,6 +16,7 @@ package ipv4
import (
"fmt"
"math"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
@@ -355,102 +356,28 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
// DeliverTransportPacket so that is is only done when needed.
replyData := stack.PayloadSince(pkt.TransportHeader())
defer replyData.Release()
ipHdr := header.IPv4(pkt.NetworkHeader().Slice())
localAddressTemporary := pkt.NetworkPacketInfo.LocalAddressTemporary
localAddressBroadcast := pkt.NetworkPacketInfo.LocalAddressBroadcast
// It's possible that a raw socket expects to receive this.
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
// It's possible that a raw socket or per-stack default handler expects
// to receive this packet.
defaultHandlerHandled := false
if dispatcher, ok := e.dispatcher.(stack.TransportDispatcherWithDefaultHandlerResult); ok {
_, defaultHandlerHandled = dispatcher.DeliverTransportPacketWithDefaultHandlerResult(header.ICMPv4ProtocolNumber, pkt)
} else {
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
}
pkt = nil
sent := e.stats.icmp.packetsSent
if !e.protocol.allowICMPReply(header.ICMPv4EchoReply, header.ICMPv4UnusedCode) {
sent.rateLimited.Increment()
// Skip the built-in ICMP echo reply if the request was consumed by a
// per-stack default handler. Also preserve the IPv4 behavior for
// temporary local addresses: the packet is delivered above, but the
// stack does not synthesize an echo reply for it.
if defaultHandlerHandled || localAddressTemporary {
return
}
// As per RFC 1122 section 3.2.1.3, when a host sends any datagram, the IP
// source address MUST be one of its own IP addresses (but not a broadcast
// or multicast address).
localAddr := ipHdr.DestinationAddress()
if localAddressBroadcast || header.IsV4MulticastAddress(localAddr) {
localAddr = tcpip.Address{}
}
r, err := e.protocol.stack.FindRoute(e.nic.ID(), localAddr, ipHdr.SourceAddress(), ProtocolNumber, false /* multicastLoop */)
if err != nil {
// If we cannot find a route to the destination, silently drop the packet.
return
}
defer r.Release()
outgoingEP, ok := e.protocol.getEndpointForNIC(r.NICID())
if !ok {
// The outgoing NIC went away.
sent.dropped.Increment()
return
}
// Because IP and ICMP are so closely intertwined, we need to handcraft our
// IP header to be able to follow RFC 792. The wording on page 13 is as
// follows:
// IP Fields:
// Addresses
// The address of the source in an echo message will be the
// destination of the echo reply message. To form an echo reply
// message, the source and destination addresses are simply reversed,
// the type code changed to 0, and the checksum recomputed.
//
// This was interpreted by early implementors to mean that all options must
// be copied from the echo request IP header to the echo reply IP header
// and this behaviour is still relied upon by some applications.
//
// Create a copy of the IP header we received, options and all, and change
// The fields we need to alter.
//
// We need to produce the entire packet in the data segment in order to
// use WriteHeaderIncludedPacket(). WriteHeaderIncludedPacket sets the
// total length and the header checksum so we don't need to set those here.
//
// Take the base of the incoming request IP header but replace the options.
replyHeaderLength := uint8(header.IPv4MinimumSize + len(newOptions))
replyIPHdrView := buffer.NewView(int(replyHeaderLength))
replyIPHdrView.Write(iph[:header.IPv4MinimumSize])
replyIPHdrView.Write(newOptions)
replyIPHdr := header.IPv4(replyIPHdrView.AsSlice())
replyIPHdr.SetHeaderLength(replyHeaderLength)
replyIPHdr.SetSourceAddress(r.LocalAddress())
replyIPHdr.SetDestinationAddress(r.RemoteAddress())
replyIPHdr.SetTTL(r.DefaultTTL())
replyIPHdr.SetTotalLength(uint16(len(replyIPHdr) + len(replyData.AsSlice())))
replyIPHdr.SetChecksum(0)
replyIPHdr.SetChecksum(^replyIPHdr.CalculateChecksum())
replyICMPHdr := header.ICMPv4(replyData.AsSlice())
replyICMPHdr.SetType(header.ICMPv4EchoReply)
replyICMPHdr.SetChecksum(0)
replyICMPHdr.SetChecksum(^checksum.Checksum(replyData.AsSlice(), 0))
replyBuf := buffer.MakeWithView(replyIPHdrView)
replyBuf.Append(replyData.Clone())
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()),
Payload: replyBuf,
})
defer replyPkt.DecRef()
// Populate the network/transport headers in the packet buffer so the
// ICMP packet goes through IPTables.
if ok := parse.IPv4(replyPkt); !ok {
panic("expected to parse IPv4 header we just created")
}
if ok := parse.ICMPv4(replyPkt); !ok {
panic("expected to parse ICMPv4 header we just created")
}
if err := outgoingEP.writePacket(r, replyPkt); err != nil {
sent.dropped.Increment()
return
}
sent.echoReply.Increment()
e.sendICMPEchoReply(replyData, iph, newOptions, localAddressBroadcast)
case header.ICMPv4EchoReply:
received.echoReply.Increment()
@@ -523,6 +450,98 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
}
}
func (e *endpoint) sendICMPEchoReply(replyData *buffer.View, ipHdr header.IPv4, newOptions header.IPv4Options, localAddressBroadcast bool) {
sent := e.stats.icmp.packetsSent
if !e.protocol.allowICMPReply(header.ICMPv4EchoReply, header.ICMPv4UnusedCode) {
sent.rateLimited.Increment()
return
}
// As per RFC 1122 section 3.2.1.3, when a host sends any datagram, the IP
// source address MUST be one of its own IP addresses (but not a broadcast
// or multicast address).
localAddr := ipHdr.DestinationAddress()
if localAddressBroadcast || header.IsV4MulticastAddress(localAddr) {
localAddr = tcpip.Address{}
}
r, err := e.protocol.stack.FindRoute(e.nic.ID(), localAddr, ipHdr.SourceAddress(), ProtocolNumber, false /* multicastLoop */)
if err != nil {
// If we cannot find a route to the destination, silently drop the packet.
return
}
defer r.Release()
outgoingEP, ok := e.protocol.getEndpointForNIC(r.NICID())
if !ok {
// The outgoing NIC went away.
sent.dropped.Increment()
return
}
// Because IP and ICMP are so closely intertwined, we need to handcraft our
// IP header to be able to follow RFC 792. The wording on page 13 is as
// follows:
// IP Fields:
// Addresses
// The address of the source in an echo message will be the
// destination of the echo reply message. To form an echo reply
// message, the source and destination addresses are simply reversed,
// the type code changed to 0, and the checksum recomputed.
//
// This was interpreted by early implementers to mean that all options must
// be copied from the echo request IP header to the echo reply IP header
// and this behaviour is still relied upon by some applications.
//
// Create a copy of the IP header we received, options and all, and change
// The fields we need to alter.
//
// We need to produce the entire packet in the data segment in order to
// use WriteHeaderIncludedPacket(). WriteHeaderIncludedPacket sets the
// total length and the header checksum so we don't need to set those here.
//
// Take the base of the incoming request IP header but replace the options.
replyHeaderLength := uint8(header.IPv4MinimumSize + len(newOptions))
replyIPHdrView := buffer.NewView(int(replyHeaderLength))
replyIPHdrView.Write(ipHdr[:header.IPv4MinimumSize])
replyIPHdrView.Write(newOptions)
replyIPHdr := header.IPv4(replyIPHdrView.AsSlice())
replyIPHdr.SetHeaderLength(replyHeaderLength)
replyIPHdr.SetSourceAddress(r.LocalAddress())
replyIPHdr.SetDestinationAddress(r.RemoteAddress())
replyIPHdr.SetTTL(r.DefaultTTL())
replyIPHdr.SetTotalLength(uint16(len(replyIPHdr) + len(replyData.AsSlice())))
replyIPHdr.SetChecksum(0)
replyIPHdr.SetChecksum(^replyIPHdr.CalculateChecksum())
replyICMPHdr := header.ICMPv4(replyData.AsSlice())
replyICMPHdr.SetType(header.ICMPv4EchoReply)
replyICMPHdr.SetChecksum(0)
replyICMPHdr.SetChecksum(^checksum.Checksum(replyData.AsSlice(), 0))
replyBuf := buffer.MakeWithView(replyIPHdrView)
replyBuf.Append(replyData.Clone())
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()),
Payload: replyBuf,
})
defer replyPkt.DecRef()
// Populate the network/transport headers in the packet buffer so the
// ICMP packet goes through IPTables.
if ok := parse.IPv4(replyPkt); !ok {
panic("expected to parse IPv4 header we just created")
}
if ok := parse.ICMPv4(replyPkt); !ok {
panic("expected to parse ICMPv4 header we just created")
}
if err := outgoingEP.writePacket(r, replyPkt); err != nil {
sent.dropped.Increment()
return
}
sent.echoReply.Increment()
}
// ======= ICMP Error packet generation =========
// icmpReason is a marker interface for IPv4 specific ICMP errors.
@@ -591,7 +610,12 @@ func (*icmpReasonNetworkUnreachable) isICMPReason() {}
// icmpReasonFragmentationNeeded is an error where a packet requires
// fragmentation while also having the Don't Fragment flag set, as per RFC 792
// page 3, Destination Unreachable Message.
type icmpReasonFragmentationNeeded struct{}
type icmpReasonFragmentationNeeded struct {
// mtu is the MTU of the next-hop link. Per RFC 1191 §4, this value
// must be included in the ICMP Fragmentation Needed message so the
// sender can update its path MTU cache.
mtu uint32
}
func (*icmpReasonFragmentationNeeded) isICMPReason() {}
@@ -696,30 +720,36 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer, deliv
}
sent := netEP.stats.icmp.packetsSent
icmpType, icmpCode, counter, pointer := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte) {
icmpType, icmpCode, counter, pointer, nextHopMTU := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte, uint16) {
switch reason := reason.(type) {
case *icmpReasonNetworkProhibited:
return header.ICMPv4DstUnreachable, header.ICMPv4NetProhibited, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4NetProhibited, sent.dstUnreachable, 0, 0
case *icmpReasonHostProhibited:
return header.ICMPv4DstUnreachable, header.ICMPv4HostProhibited, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4HostProhibited, sent.dstUnreachable, 0, 0
case *icmpReasonAdministrativelyProhibited:
return header.ICMPv4DstUnreachable, header.ICMPv4AdminProhibited, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4AdminProhibited, sent.dstUnreachable, 0, 0
case *icmpReasonPortUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0, 0
case *icmpReasonProtoUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0, 0
case *icmpReasonNetworkUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0, 0
case *icmpReasonHostUnreachable:
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0, 0
case *icmpReasonFragmentationNeeded:
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0
// Per RFC 1191 §4, include the next-hop MTU in the ICMP message.
// Cap at MaxUint16 since the field is 16 bits wide.
mtu := reason.mtu
if mtu > math.MaxUint16 {
mtu = math.MaxUint16
}
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0, uint16(mtu)
case *icmpReasonTTLExceeded:
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0, 0
case *icmpReasonReassemblyTimeout:
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0, 0
case *icmpReasonParamProblem:
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer, 0
default:
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
}
@@ -788,6 +818,7 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer, deliv
icmpHdr.SetCode(icmpCode)
icmpHdr.SetType(icmpType)
icmpHdr.SetPointer(pointer)
icmpHdr.SetMTU(nextHopMTU)
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, icmpPkt.Data().Checksum()))
if err := route.WritePacket(
+160 -41
View File
@@ -26,6 +26,7 @@ import (
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/header/parse"
"gvisor.dev/gvisor/pkg/tcpip/network/hash"
@@ -82,6 +83,7 @@ var _ stack.AddressableEndpoint = (*endpoint)(nil)
var _ stack.NetworkEndpoint = (*endpoint)(nil)
var _ IGMPEndpoint = (*endpoint)(nil)
// +checklocksalias:igmp.ep.mu=mu
// +stateify savable
type endpoint struct {
nic stack.NetworkInterface
@@ -126,13 +128,11 @@ func (e *endpoint) GetIGMPVersion() IGMPVersion {
}
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) setIGMPVersionLocked(v IGMPVersion) IGMPVersion {
return e.igmp.setVersion(v)
}
// +checklocksread:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) getIGMPVersionLocked() IGMPVersion {
return e.igmp.getVersion()
}
@@ -291,7 +291,6 @@ func (e *endpoint) Enable() tcpip.Error {
}
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) enableLocked() tcpip.Error {
// If the NIC is not enabled, the endpoint can't do anything meaningful so
// don't enable the endpoint.
@@ -362,7 +361,6 @@ func (e *endpoint) Disable() {
}
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) disableLocked() {
if !e.isEnabled() {
return
@@ -426,6 +424,11 @@ func (e *endpoint) MTU() uint32 {
return networkMTU
}
// EndpointHeaderSize returns the size necessary for the IPv4 header.
func (e *endpoint) EndpointHeaderSize() uint32 {
return header.IPv4MinimumSize
}
// MaxHeaderLength returns the maximum length needed by ipv4 headers (and
// underlying protocols).
func (e *endpoint) MaxHeaderLength() uint16 {
@@ -517,6 +520,54 @@ func (e *endpoint) handleFragments(_ *stack.Route, networkMTU uint32, pkt *stack
}
}
// recalculateChecksum recalculates the checksum of a TCP packet.
func recalculateChecksum(pkt *stack.PacketBuffer, r *stack.Route) tcpip.Error {
// RXChecksumValidated indicates that checksum verification may be
// safely skipped.
if pkt.RXChecksumValidated {
return nil
}
// NeedsCsum is set if the checksum offload is enabled, so no need to
// calculate the checksum.
if pkt.GSOOptions.Type != stack.GSONone && pkt.GSOOptions.NeedsCsum {
return nil
}
transportHeader := pkt.TransportHeader().Slice()
netHdr := header.IPv4(pkt.NetworkHeader().Slice())
switch pkt.TransportProtocolNumber {
case header.TCPProtocolNumber:
if len(transportHeader) < header.TCPMinimumSize {
return &tcpip.ErrMalformedHeader{}
}
tcp := header.TCP(transportHeader)
xsum := r.PseudoHeaderChecksum(header.TCPProtocolNumber, netHdr.PayloadLength())
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
tcp.SetChecksum(0)
tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
case header.UDPProtocolNumber:
if len(transportHeader) < header.UDPMinimumSize {
return &tcpip.ErrMalformedHeader{}
}
udp := header.UDP(transportHeader)
xsum := r.PseudoHeaderChecksum(header.UDPProtocolNumber, netHdr.PayloadLength())
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
udp.SetChecksum(0)
csum := ^udp.CalculateChecksum(xsum)
// RFC 768: If the computed checksum is zero, it is transmitted as all ones.
if csum == 0 {
csum = 0xFFFF
}
udp.SetChecksum(csum)
case header.ICMPv4ProtocolNumber:
if len(transportHeader) < header.ICMPv4MinimumSize {
return &tcpip.ErrMalformedHeader{}
}
icmp := header.ICMPv4(transportHeader)
icmp.SetChecksum(header.ICMPv4Checksum(icmp, pkt.Data().Checksum()))
}
return nil
}
// WritePacket writes a packet to the given destination address and protocol.
func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams, pkt *stack.PacketBuffer) tcpip.Error {
if err := e.addIPHeader(r.LocalAddress(), r.RemoteAddress(), pkt, params, nil /* options */); err != nil {
@@ -529,16 +580,23 @@ func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams,
func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer) tcpip.Error {
netHeader := header.IPv4(pkt.NetworkHeader().Slice())
dstAddr := netHeader.DestinationAddress()
stk := e.protocol.stack
// iptables filtering. All packets that reach here are locally
// generated.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckOutput(pkt, r, outNicName); !ok {
// iptables filtering. All packets that reach here are locally generated.
outNicName := stk.FindNICNameFromID(e.nic.ID())
if ok := stk.IPTables().CheckOutput(pkt, r, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesOutputDropped.Increment()
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckOutput(pkt, r, stack.IP) {
// nftables is telling us to drop the packet.
return nil
}
}
// If the packet is manipulated as per DNAT Output rules, handle packet
// based on destination address and do not send the packet to link
// layer.
@@ -553,6 +611,39 @@ func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer) tcpip.Er
ep.handleLocalPacket(pkt, true /* canSkipRXChecksum */)
return nil
}
// Similar to the `ip_route_me_harder` in the kernel,
// we need to find a new route for the packet.
// Implementation is similar to the func forwardUnicastPacket.
newRoute, err := stk.FindRoute(0 /* nic id */, netHeader.SourceAddress(), newDstAddr, header.IPv4ProtocolNumber, false /* multicastLoop */)
if err != nil {
e.stats.ip.OutgoingPacketErrors.Increment()
return err // Drop the packet
}
// Release the new route on exit.
defer newRoute.Release()
// Check if we need to recalculate the checksum.
// If the original route did not require a checksum but the new one does,
// we must calculate the full checksum; otherwise, NAT should have already
// done it.
if !r.RequiresTXTransportChecksum() && newRoute.RequiresTXTransportChecksum() {
if err := recalculateChecksum(pkt, newRoute); err != nil {
e.stats.ip.OutgoingPacketErrors.Increment()
return err // Drop the packet
}
}
// Update the route to the new route.
r = newRoute
// Use the new endpoint to write the packet.
forwardToEp, ok := e.protocol.getEndpointForNIC(r.NICID())
if !ok {
e.stats.ip.OutgoingPacketErrors.Increment()
return &tcpip.ErrUnknownNICID{}
}
return forwardToEp.writePacketPostRouting(r, pkt, true /* headerIncluded */)
}
return e.writePacketPostRouting(r, pkt, false /* headerIncluded */)
@@ -569,15 +660,23 @@ func (e *endpoint) writePacketPostRouting(r *stack.Route, pkt *stack.PacketBuffe
return nil
}
stk := e.protocol.stack
// Postrouting NAT can only change the source address, and does not alter the
// route or outgoing interface of the packet.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
outNicName := stk.FindNICNameFromID(e.nic.ID())
if ok := stk.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesPostroutingDropped.Increment()
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckPostrouting(pkt, r, stack.IP) {
// nftables is telling us to drop the packet.
return nil
}
}
stats := e.stats.ip
networkMTU, err := calculateNetworkMTU(e.nic.MTU(), uint32(len(pkt.NetworkHeader().Slice())))
@@ -688,6 +787,13 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckForward(pkt, route, stack.IP) {
// nftables is telling us to drop the packet.
return nil
}
}
// We need to do a deep copy of the IP packet because
// WriteHeaderIncludedPacket may modify the packet buffer, but we do
// not own it.
@@ -723,6 +829,10 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
newHdr.SetChecksum(0)
newHdr.SetChecksum(^newHdr.CalculateChecksum())
if route.RequiresTXTransportChecksum() {
newPkt.CalculateTransportChecksum()
}
switch err := forwardToEp.writePacketPostRouting(route, newPkt, true /* headerIncluded */); err.(type) {
case nil:
return nil
@@ -736,7 +846,9 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
// WriteHeaderIncludedPacket checks for the presence of the Don't Fragment bit
// while sending the packet and returns this error iff fragmentation is
// necessary and the bit is also set.
_ = e.protocol.returnError(&icmpReasonFragmentationNeeded{}, pkt, false /* deliveredLocally */)
_ = e.protocol.returnError(&icmpReasonFragmentationNeeded{
mtu: forwardToEp.nic.MTU(),
}, pkt, false /* deliveredLocally */)
return &ip.ErrMessageTooLong{}
case *tcpip.ErrNoBufferSpace:
return &ip.ErrOutgoingDeviceNoBufferSpace{}
@@ -788,6 +900,13 @@ func (e *endpoint) forwardUnicastPacket(pkt *stack.PacketBuffer) ip.ForwardingEr
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckForward(pkt, nil /* route */, stack.IP) {
// nftables is telling us to drop the packet.
return nil
}
}
// The packet originally arrived on e so provide its NIC as the input NIC.
ep.handleValidatedPacket(h, pkt, e.nic.Name() /* inNICName */)
return nil
@@ -796,9 +915,7 @@ func (e *endpoint) forwardUnicastPacket(pkt *stack.PacketBuffer) ip.ForwardingEr
r, err := stk.FindRoute(0, tcpip.Address{}, dstAddr, ProtocolNumber, false /* multicastLoop */)
switch err.(type) {
case nil:
// TODO(https://gvisor.dev/issues/8105): We should not observe ErrHostUnreachable from route
// lookups.
case *tcpip.ErrHostUnreachable, *tcpip.ErrNetworkUnreachable:
case *tcpip.ErrNetworkUnreachable:
// We return the original error rather than the result of returning
// the ICMP packet because the original error is more relevant to
// the caller.
@@ -857,7 +974,8 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
}
if e.protocol.stack.HandleLocal() {
stk := e.protocol.stack
if stk.HandleLocal() {
addressEndpoint := e.AcquireAssignedAddress(header.IPv4(pkt.NetworkHeader().Slice()).SourceAddress(), e.nic.Promiscuous(), stack.CanBePrimaryEndpoint, true /* readOnly */)
if addressEndpoint != nil {
// The source address is one of our own, so we never should have gotten
@@ -868,13 +986,22 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
}
nicID := e.nic.ID()
// Loopback traffic skips the prerouting chain.
inNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
inNicName := stk.FindNICNameFromID(nicID)
pkt.InputNICID = nicID
if ok := stk.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
// iptables is telling us to drop the packet.
stats.IPTablesPreroutingDropped.Increment()
return
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckPrerouting(pkt, nil /* route */, stack.IP) {
// nftables is telling us to drop the packet.
return
}
}
}
// CheckPrerouting can modify the backing storage of the packet, so refresh
// the header.
@@ -934,22 +1061,6 @@ func validateAddressesForForwarding(h header.IPv4) ip.ForwardingError {
return &ip.ErrInitializingSourceAddress{}
}
// As per RFC 3927 section 7,
//
// A router MUST NOT forward a packet with an IPv4 Link-Local source or
// destination address, irrespective of the router's default route
// configuration or routes obtained from dynamic routing protocols.
//
// A router which receives a packet with an IPv4 Link-Local source or
// destination address MUST NOT forward the packet. This prevents
// forwarding of packets back onto the network segment from which they
// originated, or to any other segment.
if header.IsV4LinkLocalUnicastAddress(srcAddr) {
return &ip.ErrLinkLocalSourceAddress{}
}
if dstAddr := h.DestinationAddress(); header.IsV4LinkLocalUnicastAddress(dstAddr) || header.IsV4LinkLocalMulticastAddress(dstAddr) {
return &ip.ErrLinkLocalDestinationAddress{}
}
return nil
}
@@ -1169,6 +1280,7 @@ func (e *endpoint) handleValidatedPacket(h header.IPv4, pkt *stack.PacketBuffer,
// If the packet is destined for this device, then it should be delivered
// locally. Otherwise, if forwarding is enabled, it should be forwarded.
if addressEndpoint := e.AcquireAssignedAddress(dstAddr, e.nic.Promiscuous(), stack.CanBePrimaryEndpoint, true /* readOnly */); addressEndpoint != nil {
pkt.NetworkPacketInfo.LocalAddressTemporary = addressEndpoint.Temporary()
subnet := addressEndpoint.AddressWithPrefix().Subnet()
pkt.NetworkPacketInfo.LocalAddressBroadcast = subnet.IsBroadcast(dstAddr) || dstAddr == header.IPv4Broadcast
e.deliverPacketLocally(h, pkt, inNICName)
@@ -1223,14 +1335,22 @@ func (e *endpoint) handleForwardingError(err ip.ForwardingError) {
func (e *endpoint) deliverPacketLocally(h header.IPv4, pkt *stack.PacketBuffer, inNICName string) {
stats := e.stats
stk := e.protocol.stack
// iptables filtering. All packets that reach here are intended for
// this machine and will not be forwarded.
if ok := e.protocol.stack.IPTables().CheckInput(pkt, inNICName); !ok {
if ok := stk.IPTables().CheckInput(pkt, inNICName); !ok {
// iptables is telling us to drop the packet.
stats.ip.IPTablesInputDropped.Increment()
return
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckInput(pkt, nil /* route */, stack.IP) {
// nftables is telling us to drop the packet.
return
}
}
if h.More() || h.FragmentOffset() != 0 {
if pkt.Data().Size()+len(pkt.TransportHeader().Slice()) == 0 {
// Drop the packet as it's marked as a fragment but has
@@ -1342,7 +1462,7 @@ func (e *endpoint) deliverPacketLocally(h header.IPv4, pkt *stack.PacketBuffer,
}
if p == header.IGMPProtocolNumber {
e.mu.Lock()
e.igmp.handleIGMP(pkt, hasRouterAlertOption) // +checklocksforce: e == e.igmp.ep.
e.igmp.handleIGMP(pkt, hasRouterAlertOption)
e.mu.Unlock()
return
}
@@ -1392,7 +1512,6 @@ func (e *endpoint) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, p
// sendQueuedReports sends queued igmp reports.
//
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) sendQueuedReports() {
e.igmp.sendQueuedReports()
}
@@ -1478,7 +1597,6 @@ func (e *endpoint) JoinGroup(addr tcpip.Address) tcpip.Error {
// joinGroupLocked is like JoinGroup but with locking requirements.
//
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) joinGroupLocked(addr tcpip.Address) tcpip.Error {
if !header.IsV4MulticastAddress(addr) {
return &tcpip.ErrBadAddress{}
@@ -1498,7 +1616,6 @@ func (e *endpoint) LeaveGroup(addr tcpip.Address) tcpip.Error {
// leaveGroupLocked is like LeaveGroup but with locking requirements.
//
// +checklocks:e.mu
// +checklocksalias:e.igmp.ep.mu=e.mu
func (e *endpoint) leaveGroupLocked(addr tcpip.Address) tcpip.Error {
return e.igmp.leaveGroup(addr)
}
@@ -1507,7 +1624,7 @@ func (e *endpoint) leaveGroupLocked(addr tcpip.Address) tcpip.Error {
func (e *endpoint) IsInGroup(addr tcpip.Address) bool {
e.mu.RLock()
defer e.mu.RUnlock()
return e.igmp.isInGroup(addr) // +checklocksforce: e.mu==e.igmp.ep.mu.
return e.igmp.isInGroup(addr)
}
// Stats implements stack.NetworkEndpoint.
@@ -1615,11 +1732,11 @@ func (p *protocol) Close() {
func (*protocol) Wait() {}
func (p *protocol) validateUnicastSourceAndMulticastDestination(addresses stack.UnicastSourceAndMulticastDestination) tcpip.Error {
if !p.isUnicastAddress(addresses.Source) || header.IsV4LinkLocalUnicastAddress(addresses.Source) {
if !p.isUnicastAddress(addresses.Source) {
return &tcpip.ErrBadAddress{}
}
if !header.IsV4MulticastAddress(addresses.Destination) || header.IsV4LinkLocalMulticastAddress(addresses.Destination) {
if !header.IsV4MulticastAddress(addresses.Destination) {
return &tcpip.ErrBadAddress{}
}
@@ -1873,6 +1990,8 @@ func (p *protocol) SendRejectionError(pkt *stack.PacketBuffer, rejectWith stack.
return p.returnError(&icmpReasonHostProhibited{}, pkt, inputHook)
case stack.RejectIPv4WithICMPAdminProhibited:
return p.returnError(&icmpReasonAdministrativelyProhibited{}, pkt, inputHook)
case stack.RejectIPv4WithTCPReset:
return ip.RejectWithTCPReset(pkt, ProtocolNumber, p.stack, inputHook)
default:
panic(fmt.Sprintf("unhandled %[1]T = %[1]d", rejectWith))
}
+72 -43
View File
@@ -654,55 +654,28 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
case header.ICMPv6EchoRequest:
received.echoRequest.Increment()
// As per RFC 4291 section 2.7, multicast addresses must not be used as
// source addresses in IPv6 packets.
localAddr := dstAddr
if header.IsV6MulticastAddress(dstAddr) {
localAddr = tcpip.Address{}
}
replyPayload := pkt.Data().ToBuffer()
replyHeader := make([]byte, header.ICMPv6EchoMinimumSize)
copy(replyHeader, h[:header.ICMPv6EchoMinimumSize])
r, err := e.protocol.stack.FindRoute(e.nic.ID(), localAddr, srcAddr, ProtocolNumber, false /* multicastLoop */)
if err != nil {
// If we cannot find a route to the destination, silently drop the packet.
return
// It's possible that a raw socket or per-stack default handler expects
// to receive this packet.
defaultHandlerHandled := false
if dispatcher, ok := e.dispatcher.(stack.TransportDispatcherWithDefaultHandlerResult); ok {
_, defaultHandlerHandled = dispatcher.DeliverTransportPacketWithDefaultHandlerResult(header.ICMPv6ProtocolNumber, pkt)
} else {
e.dispatcher.DeliverTransportPacket(header.ICMPv6ProtocolNumber, pkt)
}
defer r.Release()
pkt = nil
if !e.protocol.allowICMPReply(header.ICMPv6EchoReply) {
sent.rateLimited.Increment()
// Skip the built-in ICMP echo reply if the request was consumed by a
// per-stack default handler.
if defaultHandlerHandled {
replyPayload.Release()
return
}
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()) + header.ICMPv6EchoMinimumSize,
Payload: pkt.Data().ToBuffer(),
})
defer replyPkt.DecRef()
icmp := header.ICMPv6(replyPkt.TransportHeader().Push(header.ICMPv6EchoMinimumSize))
replyPkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
copy(icmp, h)
icmp.SetType(header.ICMPv6EchoReply)
replyData := replyPkt.Data()
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmp,
Src: r.LocalAddress(),
Dst: r.RemoteAddress(),
PayloadCsum: replyData.Checksum(),
PayloadLen: replyData.Size(),
}))
replyTClass, _ := iph.TOS()
if err := r.WritePacket(stack.NetworkHeaderParams{
Protocol: header.ICMPv6ProtocolNumber,
TTL: r.DefaultTTL(),
// Even though RFC 4443 does not mention anything about it, Linux uses the
// TrafficClass of the received echo request when replying.
// https://github.com/torvalds/linux/blob/0280e3c58f9/net/ipv6/icmp.c#L797
TOS: replyTClass,
}, replyPkt); err != nil {
sent.dropped.Increment()
return
}
sent.echoReply.Increment()
e.sendICMPEchoReply(replyPayload, replyHeader, srcAddr, dstAddr, iph)
case header.ICMPv6EchoReply:
received.echoReply.Increment()
@@ -898,6 +871,62 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
}
}
func (e *endpoint) sendICMPEchoReply(replyPayload buffer.Buffer, replyHeader []byte, srcAddr, dstAddr tcpip.Address, ipHdr header.IPv6) {
sent := e.stats.icmp.packetsSent
// As per RFC 4291 section 2.7, multicast addresses must not be used as
// source addresses in IPv6 packets.
localAddr := dstAddr
if header.IsV6MulticastAddress(dstAddr) {
localAddr = tcpip.Address{}
}
r, err := e.protocol.stack.FindRoute(e.nic.ID(), localAddr, srcAddr, ProtocolNumber, false /* multicastLoop */)
if err != nil {
// If we cannot find a route to the destination, silently drop the packet.
replyPayload.Release()
return
}
defer r.Release()
if !e.protocol.allowICMPReply(header.ICMPv6EchoReply) {
sent.rateLimited.Increment()
replyPayload.Release()
return
}
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(r.MaxHeaderLength()) + header.ICMPv6EchoMinimumSize,
Payload: replyPayload,
})
defer replyPkt.DecRef()
icmp := header.ICMPv6(replyPkt.TransportHeader().Push(header.ICMPv6EchoMinimumSize))
replyPkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
copy(icmp, replyHeader)
icmp.SetType(header.ICMPv6EchoReply)
replyData := replyPkt.Data()
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmp,
Src: r.LocalAddress(),
Dst: r.RemoteAddress(),
PayloadCsum: replyData.Checksum(),
PayloadLen: replyData.Size(),
}))
replyTClass, _ := ipHdr.TOS()
if err := r.WritePacket(stack.NetworkHeaderParams{
Protocol: header.ICMPv6ProtocolNumber,
TTL: r.DefaultTTL(),
// Even though RFC 4443 does not mention anything about it, Linux uses the
// TrafficClass of the received echo request when replying.
// https://github.com/torvalds/linux/blob/0280e3c58f9/net/ipv6/icmp.c#L797
TOS: replyTClass,
}, replyPkt); err != nil {
sent.dropped.Increment()
return
}
sent.echoReply.Increment()
}
// LinkAddressProtocol implements stack.LinkAddressResolver.
func (*endpoint) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
return header.IPv6ProtocolNumber
+205 -39
View File
@@ -26,6 +26,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"
"gvisor.dev/gvisor/pkg/tcpip/header/parse"
"gvisor.dev/gvisor/pkg/tcpip/network/internal/fragmentation"
@@ -184,15 +185,21 @@ var _ NDPEndpoint = (*endpoint)(nil)
type endpointMu struct {
sync.RWMutex `state:"nosave"`
// +checklocks:RWMutex
addressableEndpointState stack.AddressableEndpointState
ndp ndpState
mld mldState
// +checklocks:RWMutex
ndp ndpState
// +checklocks:RWMutex
mld mldState
}
// +stateify savable
type dadMu struct {
sync.Mutex `state:"nosave"`
// +checklocks:Mutex
dad ip.DAD
}
@@ -201,6 +208,11 @@ type endpointDAD struct {
mu dadMu
}
// NDP and MLD retain their containing endpoint after NewEndpoint initializes
// them, so their lock paths refer to the same mutex.
//
// +checklocksalias:mu.ndp.ep.mu.RWMutex=mu.RWMutex
// +checklocksalias:mu.mld.ep.mu.RWMutex=mu.RWMutex
// +stateify savable
type endpoint struct {
nic stack.NetworkInterface
@@ -316,7 +328,7 @@ func (e *endpoint) HandleLinkResolutionFailure(pkt *stack.PacketBuffer) {
// onAddressAssignedLocked handles an address being assigned.
//
// Precondition: e.mu must be exclusively locked.
// +checklocks:e.mu.RWMutex
func (e *endpoint) onAddressAssignedLocked(addr tcpip.Address) {
// As per RFC 2710 section 3,
//
@@ -586,7 +598,7 @@ func (e *endpoint) Enable() tcpip.Error {
addressEndpoint.SetKind(stack.PermanentTentative)
fallthrough
case stack.PermanentTentative:
err = e.mu.ndp.startDuplicateAddressDetection(addr, addressEndpoint)
err = e.mu.ndp.startDuplicateAddressDetection(addr, addressEndpoint) // +checklocksforce: ForEachEndpoint calls back synchronously with e.mu held.
return err == nil
case stack.Temporary, stack.PermanentExpired:
return true
@@ -671,8 +683,9 @@ func (e *endpoint) Disable() {
e.disableLocked()
}
// +checklocks:e.mu.RWMutex
func (e *endpoint) disableLocked() {
if !e.Enabled() {
if !e.isEnabled() {
return
}
@@ -696,7 +709,7 @@ func (e *endpoint) disableLocked() {
switch kind := addressEndpoint.GetKind(); kind {
case stack.Permanent, stack.PermanentTentative:
if header.IsV6UnicastAddress(addrWithPrefix.Address) {
e.mu.ndp.stopDuplicateAddressDetection(addrWithPrefix.Address, &stack.DADAborted{})
e.mu.ndp.stopDuplicateAddressDetection(addrWithPrefix.Address, &stack.DADAborted{}) // +checklocksforce: ForEachEndpoint calls back synchronously with e.mu held.
}
case stack.Temporary, stack.PermanentExpired:
default:
@@ -728,6 +741,11 @@ func (e *endpoint) MTU() uint32 {
return networkMTU
}
// EndpointHeaderSize returns the size necessary for the IPv6 header.
func (e *endpoint) EndpointHeaderSize() uint32 {
return header.IPv6MinimumSize
}
// MaxHeaderLength returns the maximum length needed by ipv6 headers (and
// underlying protocols).
func (e *endpoint) MaxHeaderLength() uint16 {
@@ -810,6 +828,64 @@ func (e *endpoint) handleFragments(r *stack.Route, networkMTU uint32, pkt *stack
}
}
// recalculateChecksum recalculates the checksum of a TCP or UDP packet.
func recalculateChecksum(pkt *stack.PacketBuffer, r *stack.Route) tcpip.Error {
// RXChecksumValidated indicates that checksum verification may be
// safely skipped.
if pkt.RXChecksumValidated {
return nil
}
// NeedsCsum is set if the checksum offload is enabled, so no need to
// calculate the checksum.
if pkt.GSOOptions.Type != stack.GSONone && pkt.GSOOptions.NeedsCsum {
return nil
}
transportHeader := pkt.TransportHeader().Slice()
payload := len(transportHeader) + pkt.Data().Size()
if payload > math.MaxUint16 {
return &tcpip.ErrMessageTooLong{}
}
payloadLength := uint16(payload)
switch pkt.TransportProtocolNumber {
case header.TCPProtocolNumber:
if len(transportHeader) < header.TCPMinimumSize {
return &tcpip.ErrMalformedHeader{}
}
tcp := header.TCP(transportHeader)
xsum := r.PseudoHeaderChecksum(header.TCPProtocolNumber, payloadLength)
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
tcp.SetChecksum(0)
tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
case header.UDPProtocolNumber:
if len(transportHeader) < header.UDPMinimumSize {
return &tcpip.ErrMalformedHeader{}
}
udp := header.UDP(transportHeader)
xsum := r.PseudoHeaderChecksum(header.UDPProtocolNumber, payloadLength)
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
udp.SetChecksum(0)
csum := ^udp.CalculateChecksum(xsum)
// RFC 768: If the computed checksum is zero, it is transmitted as all ones.
if csum == 0 {
csum = 0xFFFF
}
udp.SetChecksum(csum)
case header.ICMPv6ProtocolNumber:
if len(transportHeader) < header.ICMPv6MinimumSize {
return &tcpip.ErrMalformedHeader{}
}
icmp := header.ICMPv6(transportHeader)
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmp,
Src: r.LocalAddress(),
Dst: r.RemoteAddress(),
PayloadCsum: pkt.Data().Checksum(),
PayloadLen: pkt.Data().Size(),
}))
}
return nil
}
// WritePacket writes a packet to the given destination address and protocol.
func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams, pkt *stack.PacketBuffer) tcpip.Error {
dstAddr := r.RemoteAddress()
@@ -817,15 +893,22 @@ func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams,
return err
}
// iptables filtering. All packets that reach here are locally
// generated.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckOutput(pkt, r, outNicName); !ok {
stk := e.protocol.stack
// iptables filtering. All packets that reach here are locally generated.
outNicName := stk.FindNICNameFromID(e.nic.ID())
if ok := stk.IPTables().CheckOutput(pkt, r, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesOutputDropped.Increment()
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckOutput(pkt, r, stack.IP6) {
// nftables is telling us to drop the packet.
return nil
}
}
// If the packet is manipulated as per DNAT Output rules, handle packet
// based on destination address and do not send the packet to link
// layer.
@@ -834,12 +917,46 @@ func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams,
// removing this check short circuits broadcasts before they are sent out to
// other hosts.
if netHeader := header.IPv6(pkt.NetworkHeader().Slice()); dstAddr != netHeader.DestinationAddress() {
if ep := e.protocol.findEndpointWithAddress(netHeader.DestinationAddress()); ep != nil {
newDstAddr := netHeader.DestinationAddress()
if ep := e.protocol.findEndpointWithAddress(newDstAddr); ep != nil {
// Since we rewrote the packet but it is being routed back to us, we
// can safely assume the checksum is valid.
ep.handleLocalPacket(pkt, true /* canSkipRXChecksum */)
return nil
}
// Similar to the `ip_route_me_harder` in the kernel,
// we need to find a new route for the packet.
// Implementation is similar to the func forwardUnicastPacket.
newRoute, err := stk.FindRoute(0 /* nic id */, netHeader.SourceAddress(), newDstAddr, ProtocolNumber, false /* multicastLoop */)
if err != nil {
e.stats.ip.OutgoingPacketErrors.Increment()
return err // Drop the packet
}
// Release the new route on exit.
defer newRoute.Release()
// Check if we need to recalculate the checksum.
// If the original route did not require a checksum but the new one does,
// we must calculate the full checksum; otherwise, NAT should have already
// done it.
if !r.RequiresTXTransportChecksum() && newRoute.RequiresTXTransportChecksum() {
if err := recalculateChecksum(pkt, newRoute); err != nil {
e.stats.ip.OutgoingPacketErrors.Increment()
return err // Drop the packet
}
}
// Update the route to the new route.
r = newRoute
// Use the new endpoint to write the packet.
forwardToEp, ok := e.protocol.getEndpointForNIC(r.NICID())
if !ok {
e.stats.ip.OutgoingPacketErrors.Increment()
return &tcpip.ErrUnknownNICID{}
}
return forwardToEp.writePacket(r, pkt, params.Protocol, true /* headerIncluded */)
}
return e.writePacket(r, pkt, params.Protocol, false /* headerIncluded */)
@@ -856,15 +973,23 @@ func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer, protocol
return nil
}
stk := e.protocol.stack
// Postrouting NAT can only change the source address, and does not alter the
// route or outgoing interface of the packet.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
outNicName := stk.FindNICNameFromID(e.nic.ID())
if ok := stk.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesPostroutingDropped.Increment()
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckPostrouting(pkt, r, stack.IP6) {
// nftables is telling us to drop the packet.
return nil
}
}
stats := e.stats.ip
networkMTU, err := calculateNetworkMTU(e.nic.MTU(), uint32(len(pkt.NetworkHeader().Slice())))
if err != nil {
@@ -1005,6 +1130,14 @@ func (e *endpoint) forwardUnicastPacket(pkt *stack.PacketBuffer) ip.ForwardingEr
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckForward(pkt, nil, stack.IP6) {
// nftables is telling us to drop the packet.
return nil
}
}
// The packet originally arrived on e so provide its NIC as the input NIC.
ep.handleValidatedPacket(h, pkt, e.nic.Name() /* inNICName */)
return nil
@@ -1018,9 +1151,7 @@ func (e *endpoint) forwardUnicastPacket(pkt *stack.PacketBuffer) ip.ForwardingEr
r, err := stk.FindRoute(0, tcpip.Address{}, dstAddr, ProtocolNumber, false /* multicastLoop */)
switch err.(type) {
case nil:
// TODO(https://gvisor.dev/issues/8105): We should not observe ErrHostUnreachable from route
// lookups.
case *tcpip.ErrHostUnreachable, *tcpip.ErrNetworkUnreachable:
case *tcpip.ErrNetworkUnreachable:
// We return the original error rather than the result of returning the
// ICMP packet because the original error is more relevant to the caller.
_ = e.protocol.returnError(&icmpReasonNetUnreachable{}, pkt, false /* deliveredLocally */)
@@ -1048,6 +1179,13 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
return nil
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckForward(pkt, route, stack.IP6) {
// nftables is telling us to drop the packet.
return nil
}
}
hopLimit := h.HopLimit()
// We need to do a deep copy of the IP packet because
@@ -1063,6 +1201,10 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
// each node that forwards the packet.
newHdr.SetHopLimit(hopLimit - 1)
if route.RequiresTXTransportChecksum() {
newPkt.CalculateTransportChecksum()
}
forwardToEp, ok := e.protocol.getEndpointForNIC(route.NICID())
if !ok {
// The interface was removed after we obtained the route.
@@ -1123,7 +1265,8 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
}
if e.protocol.stack.HandleLocal() {
stk := e.protocol.stack
if stk.HandleLocal() {
addressEndpoint := e.AcquireAssignedAddress(header.IPv6(pkt.NetworkHeader().Slice()).SourceAddress(), e.nic.Promiscuous(), stack.CanBePrimaryEndpoint, true /* readOnly */)
if addressEndpoint != nil {
// The source address is one of our own, so we never should have gotten
@@ -1134,13 +1277,22 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
}
nicID := e.nic.ID()
// Loopback traffic skips the prerouting chain.
inNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
inNicName := stk.FindNICNameFromID(nicID)
pkt.InputNICID = nicID
if ok := stk.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
// iptables is telling us to drop the packet.
stats.IPTablesPreroutingDropped.Increment()
return
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckPrerouting(pkt, nil, stack.IP6) {
// nftables is telling us to drop the packet.
return
}
}
}
// CheckPrerouting can modify the backing storage of the packet, so refresh
@@ -1386,15 +1538,20 @@ func (e *endpoint) handleValidatedPacket(h header.IPv6, pkt *stack.PacketBuffer,
func (e *endpoint) deliverPacketLocally(h header.IPv6, pkt *stack.PacketBuffer, inNICName string) {
stats := e.stats.ip
// iptables filtering. All packets that reach here are intended for
// this machine and need not be forwarded.
if ok := e.protocol.stack.IPTables().CheckInput(pkt, inNICName); !ok {
stk := e.protocol.stack
if ok := stk.IPTables().CheckInput(pkt, inNICName); !ok {
// iptables is telling us to drop the packet.
stats.IPTablesInputDropped.Increment()
return
}
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
if !nft.CheckInput(pkt, nil, stack.IP6) {
// nftables is telling us to drop the packet.
return
}
}
// Any returned error is only useful for terminating execution early, but
// we have nothing left to do, so we can drop it.
_ = e.processExtensionHeaders(h, pkt, false /* forwarding */)
@@ -1495,12 +1652,13 @@ func (e *endpoint) processExtensionHeaders(h header.IPv6, pkt *stack.PacketBuffe
buf.Merge(&dataBuf)
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(h.NextHeader()), buf)
// Add a reference to pkt because fragment header processing can replace this
// packet with a new one that has an extra reference. Adding a reference here
// keeps the two in parity so they can both be DecRef'd the same way.
pkt.IncRef()
// Clone the pkt here because fragment header processing can replace this
// packet with a new one that has an extra reference. Cloning here
// keeps it so this method owns the packet either way so the packet can always
// be DecRef'd after processing is done.
processingPkt := pkt.Clone()
defer func() {
pkt.DecRef()
processingPkt.DecRef()
it.Release()
}()
@@ -1510,7 +1668,7 @@ func (e *endpoint) processExtensionHeaders(h header.IPv6, pkt *stack.PacketBuffe
)
for {
h := header.IPv6(pkt.NetworkHeader().Slice())
if done, err := e.processExtensionHeader(&it, &pkt, h, &routerAlert, &hasFragmentHeader, forwarding); err != nil || done {
if done, err := e.processExtensionHeader(&it, &processingPkt, h, &routerAlert, &hasFragmentHeader, forwarding); err != nil || done {
return err
}
}
@@ -1943,7 +2101,7 @@ func (e *endpoint) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, p
// addAndAcquirePermanentAddressLocked also joins the passed address's
// solicited-node multicast group and start duplicate address detection.
//
// Precondition: e.mu must be write locked.
// +checklocks:e.mu.RWMutex
func (e *endpoint) addAndAcquirePermanentAddressLocked(addr tcpip.AddressWithPrefix, properties stack.AddressProperties) (stack.AddressEndpoint, tcpip.Error) {
addressEndpoint, err := e.mu.addressableEndpointState.AddAndAcquireAddress(addr, properties, stack.PermanentTentative)
if err != nil {
@@ -1986,7 +2144,7 @@ func (e *endpoint) RemovePermanentAddress(addr tcpip.Address) tcpip.Error {
// removePermanentEndpointLocked is like removePermanentAddressLocked except
// it works with a stack.AddressEndpoint.
//
// Precondition: e.mu must be write locked.
// +checklocks:e.mu.RWMutex
func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEndpoint, allowSLAACInvalidation bool, reason stack.AddressRemovalReason, dadResult stack.DADResult) tcpip.Error {
addr := addressEndpoint.AddressWithPrefix()
// If we are removing an address generated via SLAAC, cleanup
@@ -2005,7 +2163,7 @@ func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEn
// removePermanentEndpointInnerLocked is like removePermanentEndpointLocked
// except it does not cleanup SLAAC address state.
//
// Precondition: e.mu must be write locked.
// +checklocks:e.mu.RWMutex
func (e *endpoint) removePermanentEndpointInnerLocked(addressEndpoint stack.AddressEndpoint, reason stack.AddressRemovalReason, dadResult stack.DADResult) tcpip.Error {
addr := addressEndpoint.AddressWithPrefix()
e.mu.ndp.stopDuplicateAddressDetection(addr.Address, dadResult)
@@ -2026,7 +2184,7 @@ func (e *endpoint) removePermanentEndpointInnerLocked(addressEndpoint stack.Addr
// hasPermanentAddressLocked returns true if the endpoint has a permanent
// address equal to the passed address.
//
// Precondition: e.mu must be read or write locked.
// +checklocksread:e.mu.RWMutex
func (e *endpoint) hasPermanentAddressRLocked(addr tcpip.Address) bool {
addressEndpoint := e.getAddressRLocked(addr)
if addressEndpoint == nil {
@@ -2037,7 +2195,7 @@ func (e *endpoint) hasPermanentAddressRLocked(addr tcpip.Address) bool {
// getAddressRLocked returns the endpoint for the passed address.
//
// Precondition: e.mu must be read or write locked.
// +checklocksread:e.mu.RWMutex
func (e *endpoint) getAddressRLocked(localAddr tcpip.Address) stack.AddressEndpoint {
return e.mu.addressableEndpointState.GetAddress(localAddr)
}
@@ -2073,7 +2231,7 @@ func (e *endpoint) AcquireAssignedAddress(localAddr tcpip.Address, allowTemp boo
// acquireAddressOrCreateTempLocked is like AcquireAssignedAddress but with
// locking requirements.
//
// Precondition: e.mu must be write locked.
// +checklocksread:e.mu.RWMutex
func (e *endpoint) acquireAddressOrCreateTempLocked(localAddr tcpip.Address, allowTemp bool, tempPEB stack.PrimaryEndpointBehavior, readOnly bool) stack.AddressEndpoint {
return e.mu.addressableEndpointState.AcquireAssignedAddress(localAddr, allowTemp, tempPEB, readOnly)
}
@@ -2090,7 +2248,7 @@ func (e *endpoint) AcquireOutgoingPrimaryAddress(remoteAddr, srcHint tcpip.Addre
//
// See stack.PrimaryEndpointBehavior for more details about the primary list.
//
// Precondition: e.mu must be read locked.
// +checklocksread:e.mu.RWMutex
func (e *endpoint) getLinkLocalAddressRLocked() tcpip.Address {
var linkLocalAddr tcpip.Address
e.mu.addressableEndpointState.ForEachPrimaryEndpoint(func(addressEndpoint stack.AddressEndpoint) bool {
@@ -2108,7 +2266,7 @@ func (e *endpoint) getLinkLocalAddressRLocked() tcpip.Address {
// acquireOutgoingPrimaryAddressRLocked is like AcquireOutgoingPrimaryAddress
// but with locking requirements.
//
// Precondition: e.mu must be read locked.
// +checklocksread:e.mu.RWMutex
func (e *endpoint) acquireOutgoingPrimaryAddressRLocked(remoteAddr, srcHint tcpip.Address, allowExpired bool) stack.AddressEndpoint {
// TODO(b/309216156): Support IPv6 hints.
@@ -2254,7 +2412,7 @@ func (e *endpoint) JoinGroup(addr tcpip.Address) tcpip.Error {
// joinGroupLocked is like JoinGroup but with locking requirements.
//
// Precondition: e.mu must be locked.
// +checklocks:e.mu.RWMutex
func (e *endpoint) joinGroupLocked(addr tcpip.Address) tcpip.Error {
if !header.IsV6MulticastAddress(addr) {
return &tcpip.ErrBadAddress{}
@@ -2273,7 +2431,7 @@ func (e *endpoint) LeaveGroup(addr tcpip.Address) tcpip.Error {
// leaveGroupLocked is like LeaveGroup but with locking requirements.
//
// Precondition: e.mu must be locked.
// +checklocks:e.mu.RWMutex
func (e *endpoint) leaveGroupLocked(addr tcpip.Address) tcpip.Error {
return e.mu.mld.leaveGroup(addr)
}
@@ -2301,14 +2459,20 @@ type protocolMu struct {
// eps is keyed by NICID to allow protocol methods to retrieve an endpoint
// when handling a packet, by looking at which NIC handled the packet.
//
// +checklocks:RWMutex
eps map[tcpip.NICID]*endpoint
// ICMP types for which the stack's global rate limiting must apply.
//
// +checklocks:RWMutex
icmpRateLimitedTypes map[header.ICMPv6Type]struct{}
// multicastForwardingDisp is the multicast forwarding event dispatcher that
// an integrator can provide to receive multicast forwarding events. Note
// that multicast packets will only be forwarded if this is non-nil.
//
// +checklocks:RWMutex
multicastForwardingDisp stack.MulticastForwardingEventDispatcher
}
@@ -2689,6 +2853,8 @@ func (p *protocol) SendRejectionError(pkt *stack.PacketBuffer, rejectWith stack.
return p.returnError(&icmpReasonPortUnreachable{}, pkt, inputHook)
case stack.RejectIPv6WithICMPAdminProhibited:
return p.returnError(&icmpReasonAdministrativelyProhibited{}, pkt, inputHook)
case stack.RejectIPv6WithTCPReset:
return ip.RejectWithTCPReset(pkt, ProtocolNumber, p.stack, inputHook)
default:
panic(fmt.Sprintf("unhandled %[1]T = %[1]d", rejectWith))
}
@@ -525,7 +525,6 @@ func (t *timer) StateTypeName() string {
func (t *timer) StateFields() []string {
return []string{
"done",
"timer",
}
}
@@ -535,7 +534,6 @@ func (t *timer) beforeSave() {}
func (t *timer) StateSave(stateSinkObject state.Sink) {
t.beforeSave()
stateSinkObject.Save(0, &t.done)
stateSinkObject.Save(1, &t.timer)
}
func (t *timer) afterLoad(context.Context) {}
@@ -543,7 +541,6 @@ func (t *timer) afterLoad(context.Context) {}
// +checklocksignore
func (t *timer) StateLoad(ctx context.Context, stateSourceObject state.Source) {
stateSourceObject.Load(0, &t.done)
stateSourceObject.Load(1, &t.timer)
}
func (o *offLinkRoute) StateTypeName() string {
+17 -14
View File
@@ -94,14 +94,14 @@ func (mld *mldState) Enabled() bool {
// SendReport implements ip.MulticastGroupProtocol.
//
// Precondition: mld.ep.mu must be read locked.
// +checklocksread:mld.ep.mu.RWMutex
func (mld *mldState) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error) {
return mld.writePacket(groupAddress, groupAddress, header.ICMPv6MulticastListenerReport)
}
// SendLeave implements ip.MulticastGroupProtocol.
//
// Precondition: mld.ep.mu must be read locked.
// +checklocksread:mld.ep.mu.RWMutex
func (mld *mldState) SendLeave(groupAddress tcpip.Address) tcpip.Error {
_, err := mld.writePacket(header.IPv6AllRoutersLinkLocalMulticastAddress, groupAddress, header.ICMPv6MulticastListenerDone)
return err
@@ -160,6 +160,8 @@ func (b *mldv2ReportBuilder) AddRecord(genericRecordType ip.MulticastGroupProtoc
}
// Send implements ip.MulticastGroupProtocolV2ReportBuilder.
//
// +checklocksread:b.mld.ep.mu.RWMutex
func (b *mldv2ReportBuilder) Send() (sent bool, err tcpip.Error) {
if len(b.records) == 0 {
return false, err
@@ -243,14 +245,14 @@ func (mld *mldState) init(ep *endpoint) {
// handleMulticastListenerQuery handles a query message.
//
// Precondition: mld.ep.mu must be locked.
// +checklocks:mld.ep.mu.RWMutex
func (mld *mldState) handleMulticastListenerQuery(mldHdr header.MLD) {
mld.genericMulticastProtocol.HandleQueryLocked(mldHdr.MulticastAddress(), mldHdr.MaximumResponseDelay())
}
// handleMulticastListenerQueryV2 handles a V2 query message.
//
// Precondition: mld.ep.mu must be locked.
// +checklocks:mld.ep.mu.RWMutex
func (mld *mldState) handleMulticastListenerQueryV2(mldHdr header.MLDv2Query) {
sources, ok := mldHdr.Sources()
if !ok {
@@ -268,7 +270,7 @@ func (mld *mldState) handleMulticastListenerQueryV2(mldHdr header.MLDv2Query) {
// handleMulticastListenerReport handles a report message.
//
// Precondition: mld.ep.mu must be locked.
// +checklocks:mld.ep.mu.RWMutex
func (mld *mldState) handleMulticastListenerReport(mldHdr header.MLD) {
mld.genericMulticastProtocol.HandleReportLocked(mldHdr.MulticastAddress())
}
@@ -278,14 +280,14 @@ func (mld *mldState) handleMulticastListenerReport(mldHdr header.MLD) {
//
// If the group is already joined, returns *tcpip.ErrDuplicateAddress.
//
// Precondition: mld.ep.mu must be locked.
// +checklocks:mld.ep.mu.RWMutex
func (mld *mldState) joinGroup(groupAddress tcpip.Address) {
mld.genericMulticastProtocol.JoinGroupLocked(groupAddress)
}
// isInGroup returns true if the specified group has been joined locally.
//
// Precondition: mld.ep.mu must be read locked.
// +checklocksread:mld.ep.mu.RWMutex
func (mld *mldState) isInGroup(groupAddress tcpip.Address) bool {
return mld.genericMulticastProtocol.IsLocallyJoinedRLocked(groupAddress)
}
@@ -294,7 +296,7 @@ func (mld *mldState) isInGroup(groupAddress tcpip.Address) bool {
// delay timers associated with that group, and sends the Done message, if
// required.
//
// Precondition: mld.ep.mu must be locked.
// +checklocks:mld.ep.mu.RWMutex
func (mld *mldState) leaveGroup(groupAddress tcpip.Address) tcpip.Error {
// LeaveGroup returns false only if the group was not joined.
if mld.genericMulticastProtocol.LeaveGroupLocked(groupAddress) {
@@ -307,7 +309,7 @@ func (mld *mldState) leaveGroup(groupAddress tcpip.Address) tcpip.Error {
// softLeaveAll leaves all groups from the perspective of MLD, but remains
// joined locally.
//
// Precondition: mld.ep.mu must be locked.
// +checklocks:mld.ep.mu.RWMutex
func (mld *mldState) softLeaveAll() {
mld.genericMulticastProtocol.MakeAllNonMemberLocked()
}
@@ -315,21 +317,21 @@ func (mld *mldState) softLeaveAll() {
// initializeAll attempts to initialize the MLD state for each group that has
// been joined locally.
//
// Precondition: mld.ep.mu must be locked.
// +checklocks:mld.ep.mu.RWMutex
func (mld *mldState) initializeAll() {
mld.genericMulticastProtocol.InitializeGroupsLocked()
}
// sendQueuedReports attempts to send any reports that are queued for sending.
//
// Precondition: mld.ep.mu must be locked.
// +checklocks:mld.ep.mu.RWMutex
func (mld *mldState) sendQueuedReports() {
mld.genericMulticastProtocol.SendQueuedReportsLocked()
}
// setVersion sets the MLD version.
//
// Precondition: mld.ep.mu must be locked.
// +checklocks:mld.ep.mu.RWMutex
func (mld *mldState) setVersion(v MLDVersion) MLDVersion {
var prev bool
switch v {
@@ -353,14 +355,14 @@ func toMLDVersion(v1Generic bool) MLDVersion {
// getVersion returns the MLD version.
//
// Precondition: mld.ep.mu must be read locked.
// +checklocksread:mld.ep.mu.RWMutex
func (mld *mldState) getVersion() MLDVersion {
return toMLDVersion(mld.genericMulticastProtocol.GetV1ModeLocked())
}
// writePacket assembles and sends an MLD packet.
//
// Precondition: mld.ep.mu must be read locked.
// +checklocksread:mld.ep.mu.RWMutex
func (mld *mldState) writePacket(destAddress, groupAddress tcpip.Address, mldType header.ICMPv6Type) (bool, tcpip.Error) {
sentStats := mld.ep.stats.icmp.packetsSent
var mldStat tcpip.MultiCounterStat
@@ -392,6 +394,7 @@ func (mld *mldState) writePacket(destAddress, groupAddress tcpip.Address, mldTyp
)
}
// +checklocksread:mld.ep.mu.RWMutex
func (mld *mldState) writePacketInner(buf *buffer.View, mldType header.ICMPv6Type, reportStat tcpip.MultiCounterStat, extensionHeaders header.IPv6ExtHdrSerializer, destAddress tcpip.Address) (bool, tcpip.Error) {
icmp := header.ICMPv6(buf.AsSlice())
icmp.SetType(mldType)
+38 -36
View File
@@ -471,7 +471,7 @@ type timer struct {
// done indicates to the timer that the timer was stopped.
done *bool
timer tcpip.Timer
timer tcpip.Timer `state:"nosave"`
}
// +stateify savable
@@ -640,7 +640,7 @@ type slaacPrefixState struct {
// This function must only be called by IPv6 addresses that are currently
// tentative.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressEndpoint stack.AddressEndpoint) tcpip.Error {
// addr must be a valid unicast IPv6 address.
if !header.IsV6UnicastAddress(addr) {
@@ -681,9 +681,9 @@ func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressE
if addressEndpoint.ConfigType() == stack.AddressConfigSlaac && !addressEndpoint.Temporary() {
// Reset the generation attempts counter as we are starting the
// generation of a new address for the SLAAC prefix.
ndp.regenerateTempSLAACAddr(addressEndpoint.AddressWithPrefix().Subnet(), true /* resetGenAttempts */)
ndp.regenerateTempSLAACAddr(addressEndpoint.AddressWithPrefix().Subnet(), true /* resetGenAttempts */) // +checklocksforce: DAD calls its handlers with ndp.ep.mu held.
}
ndp.ep.onAddressAssignedLocked(addr)
ndp.ep.onAddressAssignedLocked(addr) // +checklocksforce: DAD calls its handlers with ndp.ep.mu held.
}
})
@@ -713,7 +713,7 @@ func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressE
// (implying another node is attempting to use addr)). It is up to the caller
// of this function to handle such a scenario.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address, reason stack.DADResult) {
ndp.dad.StopLocked(addr, reason)
}
@@ -721,7 +721,7 @@ func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address, reason st
// handleRA handles a Router Advertisement message that arrived on the NIC
// this ndp is for. Does nothing if the NIC is configured to not handle RAs.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
// Is the IPv6 endpoint configured to handle RAs at all?
//
@@ -859,7 +859,7 @@ func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
// invalidateOffLinkRoute invalidates a discovered off-link route.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) invalidateOffLinkRoute(route offLinkRoute) {
state, ok := ndp.offLinkRoutes[route]
if !ok {
@@ -877,7 +877,7 @@ func (ndp *ndpState) invalidateOffLinkRoute(route offLinkRoute) {
// handleOffLinkRouteDiscovery handles the discovery of an off-link route.
//
// Precondition: ndp.ep.mu must be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) handleOffLinkRouteDiscovery(route offLinkRoute, lifetime time.Duration, prf header.NDPRoutePreference) {
ndpDisp := ndp.ep.protocol.options.NDPDisp
if ndpDisp == nil {
@@ -898,7 +898,7 @@ func (ndp *ndpState) handleOffLinkRouteDiscovery(route offLinkRoute, lifetime ti
state := offLinkRouteState{
prf: prf,
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
ndp.invalidateOffLinkRoute(route)
ndp.invalidateOffLinkRoute(route) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
}),
}
@@ -933,7 +933,7 @@ func (ndp *ndpState) handleOffLinkRouteDiscovery(route offLinkRoute, lifetime ti
//
// The prefix identified by prefix MUST NOT already be known.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) rememberOnLinkPrefix(prefix tcpip.Subnet, l time.Duration) {
ndpDisp := ndp.ep.protocol.options.NDPDisp
if ndpDisp == nil {
@@ -945,7 +945,7 @@ func (ndp *ndpState) rememberOnLinkPrefix(prefix tcpip.Subnet, l time.Duration)
state := onLinkPrefixState{
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
ndp.invalidateOnLinkPrefix(prefix)
ndp.invalidateOnLinkPrefix(prefix) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
}),
}
@@ -958,7 +958,7 @@ func (ndp *ndpState) rememberOnLinkPrefix(prefix tcpip.Subnet, l time.Duration)
// invalidateOnLinkPrefix invalidates a discovered on-link prefix.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) invalidateOnLinkPrefix(prefix tcpip.Subnet) {
s, ok := ndp.onLinkPrefixes[prefix]
@@ -983,7 +983,7 @@ func (ndp *ndpState) invalidateOnLinkPrefix(prefix tcpip.Subnet) {
// handleOnLinkPrefixInformation assumes that the prefix this pi is for is
// not the link-local prefix and the on-link flag is set.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) handleOnLinkPrefixInformation(pi header.NDPPrefixInformation) {
prefix := pi.Subnet()
prefixState, ok := ndp.onLinkPrefixes[prefix]
@@ -1036,7 +1036,7 @@ func (ndp *ndpState) handleOnLinkPrefixInformation(pi header.NDPPrefixInformatio
// handleAutonomousPrefixInformation assumes that the prefix this pi is for is
// not the link-local prefix and the autonomous flag is set.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) handleAutonomousPrefixInformation(pi header.NDPPrefixInformation) {
vl := pi.ValidLifetime()
pl := pi.PreferredLifetime()
@@ -1077,7 +1077,7 @@ func (ndp *ndpState) handleAutonomousPrefixInformation(pi header.NDPPrefixInform
//
// pl is the new preferred lifetime. vl is the new valid lifetime.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
// If we do not already have an address for this prefix and the valid
// lifetime is 0, no need to do anything further, as per RFC 4862
@@ -1100,7 +1100,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the deprecated SLAAC prefix %s", prefix))
}
ndp.deprecateSLAACAddress(state.stableAddr.addressEndpoint)
ndp.deprecateSLAACAddress(state.stableAddr.addressEndpoint) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
}),
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
state, ok := ndp.slaacPrefixes[prefix]
@@ -1108,7 +1108,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the invalidated SLAAC prefix %s", prefix))
}
ndp.invalidateSLAACPrefix(prefix, state)
ndp.invalidateSLAACPrefix(prefix, state) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
}),
tempAddrs: make(map[tcpip.Address]tempSLAACAddrState),
maxGenerationAttempts: ndp.configs.AutoGenAddressConflictRetries + 1,
@@ -1158,7 +1158,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
// addAndAcquireSLAACAddr adds a SLAAC address to the IPv6 endpoint.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) addAndAcquireSLAACAddr(addr tcpip.AddressWithPrefix, temporary bool, lifetimes stack.AddressLifetimes) stack.AddressEndpoint {
addressEndpoint, err := ndp.ep.addAndAcquirePermanentAddressLocked(addr, stack.AddressProperties{
PEB: stack.FirstPrimaryEndpoint,
@@ -1186,7 +1186,7 @@ func (ndp *ndpState) addAndAcquireSLAACAddr(addr tcpip.AddressWithPrefix, tempor
//
// Panics if the prefix is not a SLAAC prefix or it already has an address.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) generateSLAACAddr(prefix tcpip.Subnet, state *slaacPrefixState) bool {
if addressEndpoint := state.stableAddr.addressEndpoint; addressEndpoint != nil {
panic(fmt.Sprintf("ndp: SLAAC prefix %s already has a permanent address %s", prefix, addressEndpoint.AddressWithPrefix()))
@@ -1283,7 +1283,7 @@ func (ndp *ndpState) generateSLAACAddr(prefix tcpip.Subnet, state *slaacPrefixSt
//
// If generating a new address for the prefix fails, the prefix is invalidated.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) regenerateSLAACAddr(prefix tcpip.Subnet) {
state, ok := ndp.slaacPrefixes[prefix]
if !ok {
@@ -1306,6 +1306,8 @@ func (ndp *ndpState) regenerateSLAACAddr(prefix tcpip.Subnet) {
// If resetGenAttempts is true, the prefix's generation counter is reset.
//
// Returns true if a new address was generated.
//
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *slaacPrefixState, resetGenAttempts bool) bool {
// Are we configured to auto-generate new temporary global addresses for the
// prefix?
@@ -1403,7 +1405,7 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
panic(fmt.Sprintf("ndp: must have a tempAddr entry to deprecate temporary address %s", generatedAddr))
}
ndp.deprecateSLAACAddress(tempAddrState.addressEndpoint)
ndp.deprecateSLAACAddress(tempAddrState.addressEndpoint) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
}),
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
prefixState, ok := ndp.slaacPrefixes[prefix]
@@ -1416,7 +1418,7 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
panic(fmt.Sprintf("ndp: must have a tempAddr entry to invalidate temporary address %s", generatedAddr))
}
ndp.invalidateTempSLAACAddr(prefixState.tempAddrs, generatedAddr.Address, tempAddrState)
ndp.invalidateTempSLAACAddr(prefixState.tempAddrs, generatedAddr.Address, tempAddrState) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
}),
regenJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
prefixState, ok := ndp.slaacPrefixes[prefix]
@@ -1437,7 +1439,7 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
// Reset the generation attempts counter as we are starting the generation
// of a new address for the SLAAC prefix.
tempAddrState.regenerated = ndp.generateTempSLAACAddr(prefix, &prefixState, true /* resetGenAttempts */)
tempAddrState.regenerated = ndp.generateTempSLAACAddr(prefix, &prefixState, true /* resetGenAttempts */) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
prefixState.tempAddrs[generatedAddr.Address] = tempAddrState
ndp.slaacPrefixes[prefix] = prefixState
}),
@@ -1457,7 +1459,7 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
// regenerateTempSLAACAddr regenerates a temporary address for a SLAAC prefix.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) regenerateTempSLAACAddr(prefix tcpip.Subnet, resetGenAttempts bool) {
state, ok := ndp.slaacPrefixes[prefix]
if !ok {
@@ -1472,7 +1474,7 @@ func (ndp *ndpState) regenerateTempSLAACAddr(prefix tcpip.Subnet, resetGenAttemp
//
// pl is the new preferred lifetime. vl is the new valid lifetime.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixState *slaacPrefixState, pl, vl time.Duration) {
// If prefix was preferred for some finite lifetime before, cancel the
// deprecation job so it can be reset.
@@ -1658,7 +1660,7 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat
//
// deprecateSLAACAddress does nothing if the address is already deprecated.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) deprecateSLAACAddress(addressEndpoint stack.AddressEndpoint) {
if addressEndpoint.Deprecated() {
return
@@ -1672,7 +1674,7 @@ func (ndp *ndpState) deprecateSLAACAddress(addressEndpoint stack.AddressEndpoint
// invalidateSLAACPrefix invalidates a SLAAC prefix.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefixState) {
ndp.cleanupSLAACPrefixResources(prefix, state)
@@ -1690,7 +1692,7 @@ func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefi
// cleanupSLAACAddrResourcesAndNotify cleans up an invalidated SLAAC address's
// resources.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) cleanupSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPrefix, invalidatePrefix bool) {
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
ndpDisp.OnAutoGenAddressInvalidated(ndp.ep.nic.ID(), addr)
@@ -1718,7 +1720,7 @@ func (ndp *ndpState) cleanupSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPr
//
// Panics if the SLAAC prefix is not known.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaacPrefixState) {
// Invalidate all temporary addresses.
for tempAddr, tempAddrState := range state.tempAddrs {
@@ -1736,7 +1738,7 @@ func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaa
// invalidateTempSLAACAddr invalidates a temporary SLAAC address.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) invalidateTempSLAACAddr(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) {
ndp.cleanupTempSLAACAddrResourcesAndNotifyInner(tempAddrs, tempAddr, tempAddrState)
@@ -1749,7 +1751,7 @@ func (ndp *ndpState) invalidateTempSLAACAddr(tempAddrs map[tcpip.Address]tempSLA
// SLAAC address's resources from ndp and notifies the NDP dispatcher that the
// address was invalidated.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) cleanupTempSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPrefix) {
prefix := addr.Subnet()
state, ok := ndp.slaacPrefixes[prefix]
@@ -1769,7 +1771,7 @@ func (ndp *ndpState) cleanupTempSLAACAddrResourcesAndNotify(addr tcpip.AddressWi
// cleanupTempSLAACAddrResourcesAndNotify except it does not lookup the
// temporary address's state in ndp - it assumes the passed state is valid.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) cleanupTempSLAACAddrResourcesAndNotifyInner(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) {
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
ndpDisp.OnAutoGenAddressInvalidated(ndp.ep.nic.ID(), tempAddrState.addressEndpoint.AddressWithPrefix())
@@ -1788,7 +1790,7 @@ func (ndp *ndpState) cleanupTempSLAACAddrResourcesAndNotifyInner(tempAddrs map[t
// This function invalidates all discovered on-link prefixes, discovered
// routers, and auto-generated addresses.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) cleanupState() {
for prefix, state := range ndp.slaacPrefixes {
ndp.invalidateSLAACPrefix(prefix, state)
@@ -1820,7 +1822,7 @@ func (ndp *ndpState) cleanupState() {
// be solicited as there is no point soliciting routers if we don't handle their
// advertisements.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) startSolicitingRouters() {
if ndp.rtrSolicitTimer.timer != nil {
// We are already soliciting routers.
@@ -1937,7 +1939,7 @@ func (ndp *ndpState) startSolicitingRouters() {
// router solicitation will be stopped if NDP is not configured to handle RAs
// as a router.
//
// Precondition: ndp.ep.mu must be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) forwardingChanged(forwarding bool) {
if forwarding {
if ndp.configs.HandleRAs.enabled(forwarding) {
@@ -1960,7 +1962,7 @@ func (ndp *ndpState) forwardingChanged(forwarding bool) {
// stopSolicitingRouters stops soliciting routers. If routers are not currently
// being solicited, this function does nothing.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
// +checklocks:ndp.ep.mu.RWMutex
func (ndp *ndpState) stopSolicitingRouters() {
if ndp.rtrSolicitTimer.timer == nil {
// Nothing to do.
+43 -8
View File
@@ -219,18 +219,27 @@ func (ad addrToDevice) isAvailable(res Reservation, portSpecified bool) bool {
//
// +stateify savable
type PortManager struct {
// mu protects allocatedPorts.
// LOCK ORDERING: mu > ephemeralMu.
mu sync.RWMutex `state:"nosave"`
// allocatedPorts is a nesting of maps that ultimately map Reservations
// to FlagCounters describing whether the Reservation is valid and can
// be reused.
//
// mu also protects nested maps reached through allocatedPorts. Their
// helper receivers have no reference to this PortManager, so checklocks
// cannot express those helpers' owner-lock precondition.
//
// +checklocks:mu
allocatedPorts map[portDescriptor]addrToDevice
// ephemeralMu protects firstEphemeral and numEphemeral.
ephemeralMu sync.RWMutex `state:"nosave"`
ephemeralMu sync.RWMutex `state:"nosave"`
// +checklocks:ephemeralMu
firstEphemeral uint16
numEphemeral uint16
// +checklocks:ephemeralMu
numEphemeral uint16
}
// NewPortManager creates new PortManager.
@@ -251,6 +260,11 @@ type PortTester func(port uint16) (good bool, err tcpip.Error)
// possible ephemeral ports, allowing the caller to decide whether a given port
// is suitable for its needs, and stopping when a port is found or an error
// occurs.
//
// testPort is called synchronously without ephemeralMu held. Locks held by
// the caller remain held during the callback.
//
// +checklocksexclude:pm.ephemeralMu
func (pm *PortManager) PickEphemeralPort(rng rand.RNG, testPort PortTester) (port uint16, err tcpip.Error) {
pm.ephemeralMu.RLock()
firstEphemeral := pm.firstEphemeral
@@ -289,6 +303,12 @@ func pickEphemeralPort(offset uint32, first, count uint16, testPort PortTester)
// An optional PortTester can be passed in which if provided will be used to
// test if the picked port can be used. The function should return true if the
// port is safe to use, false otherwise.
//
// testPort is called synchronously with pm.mu held and must not call methods
// that acquire the same pm.mu.
//
// +checklocksexclude:pm.mu
// +checklocksexclude:pm.ephemeralMu
func (pm *PortManager) ReservePort(rng rand.RNG, res Reservation, testPort PortTester) (reservedPort uint16, err tcpip.Error) {
pm.mu.Lock()
defer pm.mu.Unlock()
@@ -313,20 +333,22 @@ func (pm *PortManager) ReservePort(rng rand.RNG, res Reservation, testPort PortT
return res.Port, nil
}
// A port wasn't specified, so try to find one.
// A port wasn't specified, so try to find one. PickEphemeralPort calls
// this callback synchronously with pm.mu still held, but checklocks
// analyzes passed callbacks without the caller's lock state.
return pm.PickEphemeralPort(rng, func(p uint16) (bool, tcpip.Error) {
res.Port = p
if !pm.reserveSpecificPortLocked(res, false /* portSpecified */) {
if !pm.reserveSpecificPortLocked(res, false /* portSpecified */) { // +checklocksignore
return false, nil
}
if testPort != nil {
ok, err := testPort(p)
if err != nil {
pm.releasePortLocked(res)
pm.releasePortLocked(res) // +checklocksignore
return false, err
}
if !ok {
pm.releasePortLocked(res)
pm.releasePortLocked(res) // +checklocksignore
return false, nil
}
}
@@ -336,6 +358,8 @@ func (pm *PortManager) ReservePort(rng rand.RNG, res Reservation, testPort PortT
// reserveSpecificPortLocked tries to reserve the given port on all given
// protocols.
//
// +checklocks:pm.mu
func (pm *PortManager) reserveSpecificPortLocked(res Reservation, portSpecified bool) bool {
// Make sure the port is available.
for _, network := range res.Networks {
@@ -376,6 +400,8 @@ func (pm *PortManager) reserveSpecificPortLocked(res Reservation, portSpecified
}
// ReserveTuple adds a port reservation for the tuple on all given protocol.
//
// +checklocksexclude:pm.mu
func (pm *PortManager) ReserveTuple(res Reservation) bool {
flagBits := res.Flags.Bits()
dst := res.dst()
@@ -428,6 +454,8 @@ func (pm *PortManager) ReserveTuple(res Reservation) bool {
// ReleasePort releases the reservation on a port/IP combination so that it can
// be reserved by other endpoints.
//
// +checklocksexclude:pm.mu
func (pm *PortManager) ReleasePort(res Reservation) {
pm.mu.Lock()
defer pm.mu.Unlock()
@@ -435,6 +463,9 @@ func (pm *PortManager) ReleasePort(res Reservation) {
pm.releasePortLocked(res)
}
// releasePortLocked releases a reservation without dropping the caller's lock.
//
// +checklocks:pm.mu
func (pm *PortManager) releasePortLocked(res Reservation) {
dst := res.dst()
for _, network := range res.Networks {
@@ -478,6 +509,8 @@ func (pm *PortManager) releasePortLocked(res Reservation) {
// PortRange returns the UDP and TCP inclusive range of ephemeral ports used in
// both IPv4 and IPv6.
//
// +checklocksexclude:pm.ephemeralMu
func (pm *PortManager) PortRange() (uint16, uint16) {
pm.ephemeralMu.RLock()
defer pm.ephemeralMu.RUnlock()
@@ -486,6 +519,8 @@ func (pm *PortManager) PortRange() (uint16, uint16) {
// SetPortRange sets the UDP and TCP IPv4 and IPv6 ephemeral port range
// (inclusive).
//
// +checklocksexclude:pm.ephemeralMu
func (pm *PortManager) SetPortRange(start uint16, end uint16) tcpip.Error {
if start > end {
return &tcpip.ErrInvalidPortRange{}
+59 -10
View File
@@ -142,7 +142,9 @@ type SocketOptions struct {
// StackHandler is initialized at the creation time and will not change.
stackHandler StackHandler `state:"manual"`
// These fields are accessed and modified using atomic operations.
// The boolean option fields below are accessed atomically. Their setters
// pass field addresses through storeAtomicBool; checklocks cannot verify
// that helper's atomic operation at those call sites.
// broadcastEnabled determines whether datagram sockets are allowed to
// send packets to a broadcast address.
@@ -230,11 +232,16 @@ type SocketOptions struct {
// passing is enabled for IPv6.
ipv6RecvErrEnabled atomicbitops.Uint32
// errQueue is the per-socket error queue. It is protected by errQueueMu.
errQueueMu sync.Mutex `state:"nosave"`
errQueue sockErrorList
// errQueue is the per-socket error queue.
//
// +checklocks:errQueueMu
errQueue sockErrorList
// bindToDevice determines the device to which the socket is bound.
//
// +checkatomic
bindToDevice atomicbitops.Int32
// getSendBufferLimits provides the handler to get the min, default and max
@@ -243,6 +250,8 @@ type SocketOptions struct {
getSendBufferLimits GetSendBufferLimits `state:"manual"`
// sendBufferSize determines the send buffer size for this socket.
//
// +checkatomic
sendBufferSize atomicbitops.Int64
// getReceiveBufferLimits provides the handler to get the min, default and
@@ -251,22 +260,34 @@ type SocketOptions struct {
getReceiveBufferLimits GetReceiveBufferLimits `state:"manual"`
// receiveBufferSize determines the receive buffer size for this socket.
//
// +checkatomic
receiveBufferSize atomicbitops.Int64
// mu protects the access to the below fields.
mu sync.Mutex `state:"nosave"`
// linger determines the amount of time the socket should linger before
// close. We currently implement this option for TCP socket only.
linger LingerOption
// rcvlowat specifies the minimum number of bytes which should be
// received to indicate the socket as readable.
//
// +checkatomic
rcvlowat atomicbitops.Int32
// experimentOptionValue is the value set for the IP option experiment header
// if it is not zero.
//
// +checkatomic
experimentOptionValue atomicbitops.Uint32
// mark is the mark value set for the socket.
//
// +checkatomic
mark atomicbitops.Uint32
mu sync.Mutex `state:"nosave"`
// linger determines the amount of time the socket should linger before
// close. We currently implement this option for TCP socket only.
//
// +checklocks:mu
linger LingerOption
}
// InitHandler initializes the handler. This must be called before using the
@@ -494,6 +515,8 @@ func (so *SocketOptions) GetIPv4RecvError() bool {
}
// SetIPv4RecvError sets value for IP_RECVERR option.
//
// +checklocksexclude:so.errQueueMu
func (so *SocketOptions) SetIPv4RecvError(v bool) {
storeAtomicBool(&so.ipv4RecvErrEnabled, v)
if !v {
@@ -507,6 +530,8 @@ func (so *SocketOptions) GetIPv6RecvError() bool {
}
// SetIPv6RecvError sets value for IPV6_RECVERR option.
//
// +checklocksexclude:so.errQueueMu
func (so *SocketOptions) SetIPv6RecvError(v bool) {
storeAtomicBool(&so.ipv6RecvErrEnabled, v)
if !v {
@@ -529,6 +554,8 @@ func (*SocketOptions) GetOutOfBandInline() bool {
func (*SocketOptions) SetOutOfBandInline(bool) {}
// GetLinger gets value for SO_LINGER option.
//
// +checklocksexclude:so.mu
func (so *SocketOptions) GetLinger() LingerOption {
so.mu.Lock()
linger := so.linger
@@ -537,6 +564,8 @@ func (so *SocketOptions) GetLinger() LingerOption {
}
// SetLinger sets value for SO_LINGER option.
//
// +checklocksexclude:so.mu
func (so *SocketOptions) SetLinger(linger LingerOption) {
so.mu.Lock()
so.linger = linger
@@ -640,6 +669,8 @@ type SockError struct {
}
// pruneErrQueue resets the queue.
//
// +checklocksexclude:so.errQueueMu
func (so *SocketOptions) pruneErrQueue() {
so.errQueueMu.Lock()
so.errQueue.Reset()
@@ -648,6 +679,8 @@ func (so *SocketOptions) pruneErrQueue() {
// DequeueErr dequeues a socket extended error from the error queue and returns
// it. Returns nil if queue is empty.
//
// +checklocksexclude:so.errQueueMu
func (so *SocketOptions) DequeueErr() *SockError {
so.errQueueMu.Lock()
defer so.errQueueMu.Unlock()
@@ -661,6 +694,8 @@ func (so *SocketOptions) DequeueErr() *SockError {
// PeekErr returns the error in the front of the error queue. Returns nil if
// the error queue is empty.
//
// +checklocksexclude:so.errQueueMu
func (so *SocketOptions) PeekErr() *SockError {
so.errQueueMu.Lock()
defer so.errQueueMu.Unlock()
@@ -670,6 +705,8 @@ func (so *SocketOptions) PeekErr() *SockError {
// QueueErr inserts the error at the back of the error queue.
//
// Preconditions: so.GetIPv4RecvError() or so.GetIPv6RecvError() is true.
//
// +checklocksexclude:so.errQueueMu
func (so *SocketOptions) QueueErr(err *SockError) {
so.errQueueMu.Lock()
defer so.errQueueMu.Unlock()
@@ -677,6 +714,8 @@ func (so *SocketOptions) QueueErr(err *SockError) {
}
// QueueLocalErr queues a local error onto the local queue.
//
// +checklocksexclude:so.errQueueMu
func (so *SocketOptions) QueueLocalErr(err Error, net NetworkProtocolNumber, info uint32, dst FullAddress, payload *buffer.View) {
so.QueueErr(&SockError{
Err: err,
@@ -771,3 +810,13 @@ func (so *SocketOptions) SetRcvlowat(rcvlowat int32) Error {
func (so *SocketOptions) GetAcceptConn() bool {
return so.handler.GetAcceptConn()
}
// GetMark gets value for SO_MARK option.
func (so *SocketOptions) GetMark() uint32 {
return so.mark.Load()
}
// SetMark sets value for SO_MARK option.
func (so *SocketOptions) SetMark(v uint32) {
so.mark.Store(v)
}
@@ -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)
}
Loaded 100 of 2146 files, more files were not shown because too many files have changed in this diff. Show more