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

+31 -1
View File
@@ -221,4 +221,34 @@ Some files carry the following license, noted at the top of each file:
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.
------------------
Some files carry the "BSD" license, noted at the top of each file:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Google Inc. nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-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 {
Loaded 100 of 2146 files, more files were not shown because too many files have changed in this diff. Show more