When generating page-aligned random bytes, random data started at the beginning of the buffer that will be chopped off. When the page size differs, the start of the returned slice is different than expected for the expected checksums, causing the tests to fail.
619 lines
13 KiB
Go
619 lines
13 KiB
Go
package tun
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"net/netip"
|
|
"sort"
|
|
"syscall"
|
|
"testing"
|
|
"unsafe"
|
|
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
gvisorChecksum "gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
)
|
|
|
|
type archChecksumDetails struct {
|
|
name string
|
|
available bool
|
|
f func([]byte, uint16) uint16
|
|
}
|
|
|
|
func fillRandomBuffer(seed int64, buf []byte) {
|
|
rng := rand.New(rand.NewSource(seed))
|
|
n, err := rng.Read(buf)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if n != len(buf) {
|
|
panic("incomplete random buffer")
|
|
}
|
|
}
|
|
|
|
func deterministicRandomBytes(seed int64, length int) []byte {
|
|
buf := make([]byte, length)
|
|
fillRandomBuffer(seed, buf)
|
|
return buf
|
|
}
|
|
|
|
func getPageAlignedRandomBytes(seed int64, length int) []byte {
|
|
alignment := syscall.Getpagesize()
|
|
buf := make([]byte, length+(alignment-1))
|
|
bufPtr := uintptr(unsafe.Pointer(&buf[0]))
|
|
alignedBufPtr := (bufPtr + uintptr(alignment-1)) & ^uintptr(alignment-1)
|
|
alignedStart := int(alignedBufPtr - bufPtr)
|
|
|
|
buf = buf[alignedStart : alignedStart+length]
|
|
fillRandomBuffer(seed, buf)
|
|
return buf
|
|
}
|
|
|
|
func TestChecksum(t *testing.T) {
|
|
alignedBuf := getPageAlignedRandomBytes(10, 8192)
|
|
allOnes := make([]byte, 65535)
|
|
for i := range allOnes {
|
|
allOnes[i] = 0xff
|
|
}
|
|
allFE := make([]byte, 65535)
|
|
for i := range allFE {
|
|
allFE[i] = 0xfe
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
data []byte
|
|
initial uint16
|
|
want uint16
|
|
}{
|
|
{
|
|
name: "empty",
|
|
data: []byte{},
|
|
initial: 0,
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "max initial",
|
|
data: []byte{},
|
|
initial: math.MaxUint16,
|
|
want: 0xffff,
|
|
},
|
|
{
|
|
name: "odd length",
|
|
data: []byte{0x01, 0x02, 0x01},
|
|
initial: 0,
|
|
want: 0x0202,
|
|
},
|
|
{
|
|
name: "tiny",
|
|
data: []byte{0x01, 0x02, 0x01, 0x02, 0x01, 0x02},
|
|
initial: 0,
|
|
want: 0x0306,
|
|
},
|
|
{
|
|
name: "initial",
|
|
data: []byte{0x01, 0x02, 0x01, 0x02, 0x01, 0x02},
|
|
initial: 0x1000,
|
|
want: 0x1306,
|
|
},
|
|
// cleanup0 through cleanup15 is 1024 (handled by large SIMD loops) +
|
|
// 32 (handled by small SIMD loops) + n, where n ranges from 0 to 15
|
|
// to cover all of the leftover byte sizes that are possible after small
|
|
// SIMD loops that handle 16 bytes.
|
|
{
|
|
name: "cleanup0",
|
|
data: deterministicRandomBytes(1, 1056),
|
|
initial: 0,
|
|
want: 0x11ec,
|
|
},
|
|
{
|
|
name: "cleanup1",
|
|
data: deterministicRandomBytes(1, 1057),
|
|
initial: 0,
|
|
want: 0xc5ec,
|
|
},
|
|
{
|
|
name: "cleanup2",
|
|
data: deterministicRandomBytes(1, 1058),
|
|
initial: 0,
|
|
want: 0xc6ad,
|
|
},
|
|
{
|
|
name: "cleanup3",
|
|
data: deterministicRandomBytes(1, 1059),
|
|
initial: 0,
|
|
want: 0x86ae,
|
|
},
|
|
{
|
|
name: "cleanup4",
|
|
data: deterministicRandomBytes(1, 1060),
|
|
initial: 0,
|
|
want: 0x878e,
|
|
},
|
|
{
|
|
name: "cleanup5",
|
|
data: deterministicRandomBytes(1, 1061),
|
|
initial: 0,
|
|
want: 0xdb8e,
|
|
},
|
|
{
|
|
name: "cleanup6",
|
|
data: deterministicRandomBytes(1, 1062),
|
|
initial: 0,
|
|
want: 0xdbd5,
|
|
},
|
|
{
|
|
name: "cleanup7",
|
|
data: deterministicRandomBytes(1, 1063),
|
|
initial: 0,
|
|
want: 0xcfd6,
|
|
},
|
|
{
|
|
name: "cleanup8",
|
|
data: deterministicRandomBytes(1, 1064),
|
|
initial: 0,
|
|
want: 0xd090,
|
|
},
|
|
{
|
|
name: "cleanup9",
|
|
data: deterministicRandomBytes(1, 1065),
|
|
initial: 0,
|
|
want: 0x0791,
|
|
},
|
|
{
|
|
name: "cleanup10",
|
|
data: deterministicRandomBytes(1, 1066),
|
|
initial: 0,
|
|
want: 0x079f,
|
|
},
|
|
{
|
|
name: "cleanup11",
|
|
data: deterministicRandomBytes(1, 1067),
|
|
initial: 0,
|
|
want: 0xba9f,
|
|
},
|
|
{
|
|
name: "cleanup12",
|
|
data: deterministicRandomBytes(1, 1068),
|
|
initial: 0,
|
|
want: 0xbb0c,
|
|
},
|
|
{
|
|
name: "cleanup13",
|
|
data: deterministicRandomBytes(1, 1069),
|
|
initial: 0,
|
|
want: 0x770d,
|
|
},
|
|
{
|
|
name: "cleanup14",
|
|
data: deterministicRandomBytes(1, 1070),
|
|
initial: 0,
|
|
want: 0x780a,
|
|
},
|
|
{
|
|
name: "cleanup15",
|
|
data: deterministicRandomBytes(1, 1071),
|
|
initial: 0,
|
|
want: 0x640b,
|
|
},
|
|
// small1 through small15 covers small sizes that are not large enough
|
|
// to do overlapped reads.
|
|
{
|
|
name: "small1",
|
|
data: deterministicRandomBytes(2, 1),
|
|
initial: 0x1122,
|
|
want: 0x4022,
|
|
},
|
|
{
|
|
name: "small2",
|
|
data: deterministicRandomBytes(2, 2),
|
|
initial: 0x1122,
|
|
want: 0x40a4,
|
|
},
|
|
{
|
|
name: "small3",
|
|
data: deterministicRandomBytes(2, 3),
|
|
initial: 0x1122,
|
|
want: 0xc2a4,
|
|
},
|
|
{
|
|
name: "small4",
|
|
data: deterministicRandomBytes(2, 4),
|
|
initial: 0x1122,
|
|
want: 0xc36f,
|
|
},
|
|
{
|
|
name: "small5",
|
|
data: deterministicRandomBytes(2, 5),
|
|
initial: 0x1122,
|
|
want: 0xa570,
|
|
},
|
|
{
|
|
name: "small6",
|
|
data: deterministicRandomBytes(2, 6),
|
|
initial: 0x1122,
|
|
want: 0xa669,
|
|
},
|
|
{
|
|
name: "small7",
|
|
data: deterministicRandomBytes(2, 7),
|
|
initial: 0x1122,
|
|
want: 0x0f6a,
|
|
},
|
|
{
|
|
name: "small8",
|
|
data: deterministicRandomBytes(2, 8),
|
|
initial: 0x1122,
|
|
want: 0x0fd9,
|
|
},
|
|
{
|
|
name: "small9",
|
|
data: deterministicRandomBytes(2, 9),
|
|
initial: 0x1122,
|
|
want: 0x40d9,
|
|
},
|
|
{
|
|
name: "small10",
|
|
data: deterministicRandomBytes(2, 10),
|
|
initial: 0x1122,
|
|
want: 0x411d,
|
|
},
|
|
{
|
|
name: "small11",
|
|
data: deterministicRandomBytes(2, 11),
|
|
initial: 0x1122,
|
|
want: 0x011e,
|
|
},
|
|
{
|
|
name: "small12",
|
|
data: deterministicRandomBytes(2, 12),
|
|
initial: 0x1122,
|
|
want: 0x01c8,
|
|
},
|
|
{
|
|
name: "small13",
|
|
data: deterministicRandomBytes(2, 13),
|
|
initial: 0x1122,
|
|
want: 0x4dc8,
|
|
},
|
|
{
|
|
name: "small14",
|
|
data: deterministicRandomBytes(2, 14),
|
|
initial: 0x1122,
|
|
want: 0x4eb5,
|
|
},
|
|
{
|
|
name: "small15",
|
|
data: deterministicRandomBytes(2, 15),
|
|
initial: 0x1122,
|
|
want: 0xa4b5,
|
|
},
|
|
// other small-ish sizes
|
|
{
|
|
name: "small16",
|
|
data: deterministicRandomBytes(1, 16),
|
|
initial: 0,
|
|
want: 0x02fa,
|
|
},
|
|
{
|
|
name: "small32",
|
|
data: deterministicRandomBytes(1, 32),
|
|
initial: 0,
|
|
want: 0x03ee,
|
|
},
|
|
{
|
|
name: "small64",
|
|
data: deterministicRandomBytes(1, 64),
|
|
initial: 0,
|
|
want: 0x3f85,
|
|
},
|
|
{
|
|
name: "medium",
|
|
data: deterministicRandomBytes(1, 1400),
|
|
initial: 0,
|
|
want: 0xbea5,
|
|
},
|
|
{
|
|
name: "big",
|
|
data: deterministicRandomBytes(2, 65000),
|
|
initial: 0,
|
|
want: 0x3ba7,
|
|
},
|
|
{
|
|
name: "big-initial",
|
|
data: deterministicRandomBytes(2, 65000),
|
|
initial: 0x1234,
|
|
want: 0x4ddb,
|
|
},
|
|
{
|
|
// big-small-loop is intended to exercise a few iterations of a big
|
|
// initial loop of 128 bytes or larger + a smaller loop of 16 bytes
|
|
// + some leftover
|
|
name: "big-small-loop",
|
|
data: deterministicRandomBytes(3, 1094),
|
|
initial: 0x9999,
|
|
want: 0xe65b,
|
|
},
|
|
{
|
|
name: "page-aligned",
|
|
data: alignedBuf[:4096],
|
|
initial: 0,
|
|
want: 0x963b,
|
|
},
|
|
{
|
|
name: "32-aligned",
|
|
data: alignedBuf[32:4128],
|
|
initial: 0,
|
|
want: 0x30c4,
|
|
},
|
|
{
|
|
name: "16-aligned",
|
|
data: alignedBuf[16:4112],
|
|
initial: 0,
|
|
want: 0xaeff,
|
|
},
|
|
{
|
|
name: "8-aligned",
|
|
data: alignedBuf[8:4104],
|
|
initial: 0,
|
|
want: 0x6c3b,
|
|
},
|
|
{
|
|
name: "4-aligned",
|
|
data: alignedBuf[4:4100],
|
|
initial: 0,
|
|
want: 0x2e4a,
|
|
},
|
|
{
|
|
name: "2-aligned",
|
|
data: alignedBuf[2:4098],
|
|
initial: 0,
|
|
want: 0xc702,
|
|
},
|
|
{
|
|
name: "unaligned",
|
|
data: alignedBuf[1:4097],
|
|
initial: 0,
|
|
want: 0x3bc7,
|
|
},
|
|
{
|
|
name: "unalignedAndOdd",
|
|
data: alignedBuf[1:4096],
|
|
initial: 0,
|
|
want: 0x3b13,
|
|
},
|
|
{
|
|
name: "fe1282",
|
|
data: allFE[:1282],
|
|
initial: 0,
|
|
want: 0x7c7c,
|
|
},
|
|
{
|
|
name: "fe",
|
|
data: allFE,
|
|
initial: 0,
|
|
want: 0x7e81,
|
|
},
|
|
{
|
|
name: "maximum",
|
|
data: allOnes,
|
|
initial: 0,
|
|
want: 0xff00,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
for _, fd := range archChecksumFuncs {
|
|
t.Run(fd.name, func(t *testing.T) {
|
|
if !fd.available {
|
|
t.Skip("can not run on this system")
|
|
}
|
|
if got := fd.f(tt.data, tt.initial); got != tt.want {
|
|
t.Errorf("%s checksum = %04x, want %04x", fd.name, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
t.Run("reference", func(t *testing.T) {
|
|
if got := gvisorChecksum.Checksum(tt.data, tt.initial); got != tt.want {
|
|
t.Errorf("reference checksum = %04x, want %04x", got, tt.want)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPseudoHeaderChecksumNoFold(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
protocol uint8
|
|
srcAddr []byte
|
|
dstAddr []byte
|
|
totalLen uint16
|
|
want uint16
|
|
}{
|
|
{
|
|
name: "ipv4",
|
|
protocol: syscall.IPPROTO_TCP,
|
|
srcAddr: netip.MustParseAddr("192.168.1.1").AsSlice(),
|
|
dstAddr: netip.MustParseAddr("192.168.1.2").AsSlice(),
|
|
totalLen: 1492,
|
|
want: 0x892e,
|
|
},
|
|
{
|
|
name: "ipv6",
|
|
protocol: syscall.IPPROTO_TCP,
|
|
srcAddr: netip.MustParseAddr("2001:db8:3333:4444:5555:6666:7777:8888").AsSlice(),
|
|
dstAddr: netip.MustParseAddr("2001:db8:aaaa:bbbb:cccc:dddd:eeee:ffff").AsSlice(),
|
|
totalLen: 1492,
|
|
want: 0x947f,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Run("pseudoHeaderChecksum32", func(t *testing.T) {
|
|
got := pseudoHeaderChecksum32(tt.protocol, tt.srcAddr, tt.dstAddr, tt.totalLen)
|
|
if got != tt.want {
|
|
t.Errorf("got %04x, want %04x", got, tt.want)
|
|
}
|
|
})
|
|
t.Run("pseudoHeaderChecksum64", func(t *testing.T) {
|
|
got := pseudoHeaderChecksum64(tt.protocol, tt.srcAddr, tt.dstAddr, tt.totalLen)
|
|
if got != tt.want {
|
|
t.Errorf("got %04x, want %04x", got, tt.want)
|
|
}
|
|
})
|
|
t.Run("reference", func(t *testing.T) {
|
|
got := header.PseudoHeaderChecksum(
|
|
tcpip.TransportProtocolNumber(tt.protocol),
|
|
tcpip.AddrFromSlice(tt.srcAddr),
|
|
tcpip.AddrFromSlice(tt.dstAddr),
|
|
tt.totalLen)
|
|
if got != tt.want {
|
|
t.Errorf("got %04x, want %04x", got, tt.want)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func FuzzChecksum(f *testing.F) {
|
|
buf := getPageAlignedRandomBytes(1234, 65536)
|
|
|
|
f.Add([]byte{}, uint16(0))
|
|
f.Add([]byte{}, uint16(0x1234))
|
|
f.Add([]byte{}, uint16(0))
|
|
f.Add(buf[:15], uint16(0x1234))
|
|
f.Add(buf[:256], uint16(0x1234))
|
|
f.Add(buf[:1280], uint16(0x1234))
|
|
f.Add(buf[:1288], uint16(0x1234))
|
|
f.Add(buf[1:1050], uint16(0x1234))
|
|
|
|
f.Fuzz(func(t *testing.T, data []byte, initial uint16) {
|
|
want := gvisorChecksum.Checksum(data, initial)
|
|
|
|
for _, fd := range archChecksumFuncs {
|
|
t.Run(fd.name, func(t *testing.T) {
|
|
if !fd.available {
|
|
t.Skip("can not run on this system")
|
|
}
|
|
if got := fd.f(data, initial); got != want {
|
|
t.Errorf("%s checksum = %04x, want %04x", fd.name, got, want)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
var result uint16
|
|
|
|
func BenchmarkChecksum(b *testing.B) {
|
|
offsets := []int{ // offsets from page alignment
|
|
0,
|
|
1,
|
|
2,
|
|
4,
|
|
8,
|
|
16,
|
|
}
|
|
lengths := []int{
|
|
0,
|
|
7,
|
|
15,
|
|
16,
|
|
31,
|
|
64,
|
|
90,
|
|
95,
|
|
128,
|
|
256,
|
|
512,
|
|
1024,
|
|
1240,
|
|
1500,
|
|
2048,
|
|
4096,
|
|
8192,
|
|
9000,
|
|
9001,
|
|
16384,
|
|
65536,
|
|
}
|
|
if !sort.IntsAreSorted(offsets) {
|
|
b.Fatal("offsets are not sorted")
|
|
}
|
|
largestLength := lengths[len(lengths)-1]
|
|
if !sort.IntsAreSorted(lengths) {
|
|
b.Fatal("lengths are not sorted")
|
|
}
|
|
largestOffset := lengths[len(offsets)-1]
|
|
alignedBuf := getPageAlignedRandomBytes(1, largestOffset+largestLength)
|
|
var r uint16
|
|
for _, offset := range offsets {
|
|
name := fmt.Sprintf("%vAligned", offset)
|
|
if offset == 0 {
|
|
name = "pageAligned"
|
|
}
|
|
offsetBuf := alignedBuf[offset:]
|
|
b.Run(name, func(b *testing.B) {
|
|
for _, length := range lengths {
|
|
b.Run(fmt.Sprintf("%d", length), func(b *testing.B) {
|
|
for _, fd := range archChecksumFuncs {
|
|
b.Run(fd.name, func(b *testing.B) {
|
|
if !fd.available {
|
|
b.Skip("can not run on this system")
|
|
}
|
|
b.SetBytes(int64(length))
|
|
for i := 0; i < b.N; i++ {
|
|
r += fd.f(offsetBuf[:length], 0)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
result = r
|
|
}
|
|
|
|
func BenchmarkPseudoHeaderChecksum(b *testing.B) {
|
|
tests := []struct {
|
|
name string
|
|
protocol uint8
|
|
srcAddr []byte
|
|
dstAddr []byte
|
|
totalLen uint16
|
|
want uint16
|
|
}{
|
|
{
|
|
name: "ipv4",
|
|
protocol: syscall.IPPROTO_TCP,
|
|
srcAddr: []byte{192, 168, 1, 1},
|
|
dstAddr: []byte{192, 168, 1, 2},
|
|
totalLen: 1492,
|
|
want: 0x892e,
|
|
},
|
|
{
|
|
name: "ipv6",
|
|
protocol: syscall.IPPROTO_TCP,
|
|
srcAddr: netip.MustParseAddr("2001:db8:3333:4444:5555:6666:7777:8888").AsSlice(),
|
|
dstAddr: netip.MustParseAddr("2001:db8:aaaa:bbbb:cccc:dddd:eeee:ffff").AsSlice(),
|
|
totalLen: 1492,
|
|
want: 0x892e,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
b.Run(tt.name, func(b *testing.B) {
|
|
b.Run("pseudoHeaderChecksum32", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
result += pseudoHeaderChecksum32(tt.protocol, tt.srcAddr, tt.dstAddr, tt.totalLen)
|
|
}
|
|
})
|
|
b.Run("pseudoHeaderChecksum64", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
result += pseudoHeaderChecksum64(tt.protocol, tt.srcAddr, tt.dstAddr, tt.totalLen)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|