checksum_test.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package checksum
  2. import (
  3. "fmt"
  4. "math/rand/v2"
  5. "testing"
  6. gvisorchecksum "gvisor.dev/gvisor/pkg/tcpip/checksum"
  7. )
  8. // TestChecksumMatchesGvisor walks lengths from 0 to 4096, with several initial
  9. // seeds and a handful of starting alignments, asserting that our local
  10. // Checksum matches gvisor's reference bit-for-bit.
  11. func TestChecksumMatchesGvisor(t *testing.T) {
  12. rng := rand.New(rand.NewPCG(1, 2))
  13. const padFront = 16
  14. // Random pool large enough for the longest case + alignment slop.
  15. pool := make([]byte, 4096+padFront)
  16. for i := range pool {
  17. pool[i] = byte(rng.Uint32())
  18. }
  19. seeds := []uint16{0, 0x0001, 0xabcd, 0xffff, 0x1234, 0xfedc}
  20. offsets := []int{0, 1, 2, 3, 4, 5, 7, 8, 15, 16}
  21. for length := 0; length <= 4096; length++ {
  22. for _, seed := range seeds {
  23. for _, off := range offsets {
  24. if off+length > len(pool) {
  25. continue
  26. }
  27. buf := pool[off : off+length]
  28. want := gvisorchecksum.Checksum(buf, seed)
  29. got := Checksum(buf, seed)
  30. if got != want {
  31. t.Fatalf("len=%d off=%d seed=%#x: got %#04x want %#04x",
  32. length, off, seed, got, want)
  33. }
  34. }
  35. }
  36. }
  37. }
  38. // TestChecksumPatternedBuffers exercises specific byte patterns that have
  39. // historically tripped up checksum implementations: all-zero, all-0xff,
  40. // alternating, and ascending sequences.
  41. func TestChecksumPatternedBuffers(t *testing.T) {
  42. for length := 0; length <= 256; length++ {
  43. patterns := map[string][]byte{
  44. "zeros": make([]byte, length),
  45. "ones": bytes(length, 0xff),
  46. "alternating": pattern(length, []byte{0xa5, 0x5a}),
  47. "ascending": ascending(length),
  48. }
  49. for name, buf := range patterns {
  50. for _, seed := range []uint16{0, 0xffff, 0x8000} {
  51. want := gvisorchecksum.Checksum(buf, seed)
  52. got := Checksum(buf, seed)
  53. if got != want {
  54. t.Fatalf("%s len=%d seed=%#x: got %#04x want %#04x",
  55. name, length, seed, got, want)
  56. }
  57. }
  58. }
  59. }
  60. }
  61. func bytes(n int, v byte) []byte {
  62. b := make([]byte, n)
  63. for i := range b {
  64. b[i] = v
  65. }
  66. return b
  67. }
  68. func pattern(n int, p []byte) []byte {
  69. b := make([]byte, n)
  70. for i := range b {
  71. b[i] = p[i%len(p)]
  72. }
  73. return b
  74. }
  75. func ascending(n int) []byte {
  76. b := make([]byte, n)
  77. for i := range b {
  78. b[i] = byte(i)
  79. }
  80. return b
  81. }
  82. // TestChecksumTailPaths targets every combination of (SIMD body iterations,
  83. // trailing tail bytes) the asm handlers walk through. The tail handlers
  84. // peel off 8 → 4 → 2 → 1 byte chunks in turn; this test exercises each by
  85. // constructing lengths of the form 64*k + tail for tail ∈ [0, 63] and a
  86. // representative spread of k values, including k=0 (no main loop, all tail)
  87. // and k=1 (one main loop iter, then tail). It's explicit coverage for
  88. // payload sizes that are odd, not divisible by 4, by 8, or by 32.
  89. func TestChecksumTailPaths(t *testing.T) {
  90. rng := rand.New(rand.NewPCG(42, 17))
  91. const padFront = 16
  92. const maxK = 8
  93. pool := make([]byte, 64*maxK+padFront+64)
  94. for i := range pool {
  95. pool[i] = byte(rng.Uint32())
  96. }
  97. seeds := []uint16{0, 0xffff, 0xabcd}
  98. offsets := []int{0, 1, 3, 7, 15} // mix of aligned and odd starts
  99. for k := 0; k <= maxK; k++ {
  100. for tail := 0; tail < 64; tail++ {
  101. length := 64*k + tail
  102. for _, seed := range seeds {
  103. for _, off := range offsets {
  104. if off+length > len(pool) {
  105. continue
  106. }
  107. buf := pool[off : off+length]
  108. want := gvisorchecksum.Checksum(buf, seed)
  109. got := Checksum(buf, seed)
  110. if got != want {
  111. t.Fatalf("k=%d tail=%d (len=%d) off=%d seed=%#x: got %#04x want %#04x",
  112. k, tail, length, off, seed, got, want)
  113. }
  114. }
  115. }
  116. }
  117. }
  118. }
  119. // BenchmarkChecksumTailSizes covers payload sizes that aren't clean multiples
  120. // of the SIMD body's 32-byte (amd64) or 16-byte (arm64) chunks, so the tail
  121. // handler is meaningfully on the hot path. Sizes are picked to either exercise
  122. // every tail branch (tiny lengths) or sit slightly off realistic packet
  123. // boundaries (e.g. 1499 = MTU − 1).
  124. func BenchmarkChecksumTailSizes(b *testing.B) {
  125. sizes := []int{
  126. 1, 3, 7, 15, 31, // sub-SIMD; entire work is scalar tail
  127. 33, 35, 47, 63, // one loop32 + assorted tails
  128. 65, 95, 127, // one loop64 + assorted tails
  129. 1447, 1471, 1499, 1501, // around MTU
  130. 8191, 8193, // around USO
  131. 65531, 65533, // near the kernel max
  132. }
  133. for _, size := range sizes {
  134. buf := make([]byte, size)
  135. for i := range buf {
  136. buf[i] = byte(i)
  137. }
  138. b.Run(fmt.Sprintf("size=%d/local", size), func(b *testing.B) {
  139. b.SetBytes(int64(size))
  140. for i := 0; i < b.N; i++ {
  141. _ = Checksum(buf, 0)
  142. }
  143. })
  144. b.Run(fmt.Sprintf("size=%d/gvisor", size), func(b *testing.B) {
  145. b.SetBytes(int64(size))
  146. for i := 0; i < b.N; i++ {
  147. _ = gvisorchecksum.Checksum(buf, 0)
  148. }
  149. })
  150. }
  151. }
  152. // BenchmarkChecksum compares the local Checksum to gvisor's at sizes that
  153. // match real traffic: a TCP/IP header (60), a typical MSS (1448), a typical
  154. // USO size (8192), and the kernel's max GSO superpacket (65535).
  155. func BenchmarkChecksum(b *testing.B) {
  156. for _, size := range []int{60, 1448, 8192, 65535} {
  157. buf := make([]byte, size)
  158. for i := range buf {
  159. buf[i] = byte(i)
  160. }
  161. b.Run(fmt.Sprintf("size=%d/local", size), func(b *testing.B) {
  162. b.SetBytes(int64(size))
  163. for i := 0; i < b.N; i++ {
  164. _ = Checksum(buf, 0)
  165. }
  166. })
  167. b.Run(fmt.Sprintf("size=%d/gvisor", size), func(b *testing.B) {
  168. b.SetBytes(int64(size))
  169. for i := 0; i < b.N; i++ {
  170. _ = gvisorchecksum.Checksum(buf, 0)
  171. }
  172. })
  173. }
  174. }