1
0

checksum_test.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. package checksum
  2. import (
  3. "fmt"
  4. "math/rand/v2"
  5. "testing"
  6. gvisorchecksum "gvisor.dev/gvisor/pkg/tcpip/checksum"
  7. )
  8. // archImpl names one checksum function under test. The per-arch
  9. // export_*_test.go files enumerate the hand-written implementations so the
  10. // suite compares each one against gvisor directly, regardless of which one
  11. // the public Checksum dispatches to on the running CPU. Testing only the
  12. // dispatcher was tautological wherever it resolved to the gvisor fallback
  13. // (non-AVX2 amd64, fallback architectures) — gvisor compared with itself,
  14. // assembly untested, suite green.
  15. type archImpl struct {
  16. name string
  17. fn func([]byte, uint16) uint16
  18. available bool
  19. }
  20. // implsUnderTest is the public dispatcher plus every arch implementation.
  21. func implsUnderTest() []archImpl {
  22. return append([]archImpl{{name: "dispatch", fn: Checksum, available: true}}, archImpls...)
  23. }
  24. // requireAvailable skips loudly when the running CPU can't execute an
  25. // implementation — visible in test output, unlike the old silent tautology.
  26. func requireAvailable(t *testing.T, impl archImpl) {
  27. t.Helper()
  28. if !impl.available {
  29. t.Skipf("%s not supported on this CPU; its assembly is NOT tested in this run", impl.name)
  30. }
  31. }
  32. // TestChecksumMatchesGvisor walks lengths from 0 to 4096, with several initial
  33. // seeds and a handful of starting alignments, asserting that each local
  34. // implementation matches gvisor's reference bit-for-bit.
  35. func TestChecksumMatchesGvisor(t *testing.T) {
  36. for _, impl := range implsUnderTest() {
  37. t.Run(impl.name, func(t *testing.T) {
  38. requireAvailable(t, impl)
  39. rng := rand.New(rand.NewPCG(1, 2))
  40. const padFront = 16
  41. // Random pool large enough for the longest case + alignment slop.
  42. pool := make([]byte, 4096+padFront)
  43. for i := range pool {
  44. pool[i] = byte(rng.Uint32())
  45. }
  46. seeds := []uint16{0, 0x0001, 0xabcd, 0xffff, 0x1234, 0xfedc}
  47. offsets := []int{0, 1, 2, 3, 4, 5, 7, 8, 15, 16}
  48. for length := 0; length <= 4096; length++ {
  49. for _, seed := range seeds {
  50. for _, off := range offsets {
  51. if off+length > len(pool) {
  52. continue
  53. }
  54. buf := pool[off : off+length]
  55. want := gvisorchecksum.Checksum(buf, seed)
  56. got := impl.fn(buf, seed)
  57. if got != want {
  58. t.Fatalf("len=%d off=%d seed=%#x: got %#04x want %#04x",
  59. length, off, seed, got, want)
  60. }
  61. }
  62. }
  63. }
  64. })
  65. }
  66. }
  67. // TestChecksumPatternedBuffers exercises specific byte patterns that have
  68. // historically tripped up checksum implementations: all-zero, all-0xff,
  69. // alternating, and ascending sequences.
  70. func TestChecksumPatternedBuffers(t *testing.T) {
  71. for _, impl := range implsUnderTest() {
  72. t.Run(impl.name, func(t *testing.T) {
  73. requireAvailable(t, impl)
  74. for length := 0; length <= 256; length++ {
  75. patterns := map[string][]byte{
  76. "zeros": make([]byte, length),
  77. "ones": bytes(length, 0xff),
  78. "alternating": pattern(length, []byte{0xa5, 0x5a}),
  79. "ascending": ascending(length),
  80. }
  81. for name, buf := range patterns {
  82. for _, seed := range []uint16{0, 0xffff, 0x8000} {
  83. want := gvisorchecksum.Checksum(buf, seed)
  84. got := impl.fn(buf, seed)
  85. if got != want {
  86. t.Fatalf("%s len=%d seed=%#x: got %#04x want %#04x",
  87. name, length, seed, got, want)
  88. }
  89. }
  90. }
  91. }
  92. })
  93. }
  94. }
  95. func bytes(n int, v byte) []byte {
  96. b := make([]byte, n)
  97. for i := range b {
  98. b[i] = v
  99. }
  100. return b
  101. }
  102. func pattern(n int, p []byte) []byte {
  103. b := make([]byte, n)
  104. for i := range b {
  105. b[i] = p[i%len(p)]
  106. }
  107. return b
  108. }
  109. func ascending(n int) []byte {
  110. b := make([]byte, n)
  111. for i := range b {
  112. b[i] = byte(i)
  113. }
  114. return b
  115. }
  116. // TestChecksumTailPaths targets every combination of (SIMD body iterations,
  117. // trailing tail bytes) the asm handlers walk through. The tail handlers
  118. // peel off 8 → 4 → 2 → 1 byte chunks in turn; this test exercises each by
  119. // constructing lengths of the form 64*k + tail for tail ∈ [0, 63] and a
  120. // representative spread of k values, including k=0 (no main loop, all tail)
  121. // and k=1 (one main loop iter, then tail). It's explicit coverage for
  122. // payload sizes that are odd, not divisible by 4, by 8, or by 32.
  123. func TestChecksumTailPaths(t *testing.T) {
  124. for _, impl := range implsUnderTest() {
  125. t.Run(impl.name, func(t *testing.T) {
  126. requireAvailable(t, impl)
  127. rng := rand.New(rand.NewPCG(42, 17))
  128. const padFront = 16
  129. const maxK = 8
  130. pool := make([]byte, 64*maxK+padFront+64)
  131. for i := range pool {
  132. pool[i] = byte(rng.Uint32())
  133. }
  134. seeds := []uint16{0, 0xffff, 0xabcd}
  135. offsets := []int{0, 1, 3, 7, 15} // mix of aligned and odd starts
  136. for k := 0; k <= maxK; k++ {
  137. for tail := 0; tail < 64; tail++ {
  138. length := 64*k + tail
  139. for _, seed := range seeds {
  140. for _, off := range offsets {
  141. if off+length > len(pool) {
  142. continue
  143. }
  144. buf := pool[off : off+length]
  145. want := gvisorchecksum.Checksum(buf, seed)
  146. got := impl.fn(buf, seed)
  147. if got != want {
  148. t.Fatalf("k=%d tail=%d (len=%d) off=%d seed=%#x: got %#04x want %#04x",
  149. k, tail, length, off, seed, got, want)
  150. }
  151. }
  152. }
  153. }
  154. }
  155. })
  156. }
  157. }
  158. // BenchmarkChecksumTailSizes covers payload sizes that aren't clean multiples
  159. // of the SIMD body's 32-byte (amd64) or 16-byte (arm64) chunks, so the tail
  160. // handler is meaningfully on the hot path. Sizes are picked to either exercise
  161. // every tail branch (tiny lengths) or sit slightly off realistic packet
  162. // boundaries (e.g. 1499 = MTU − 1).
  163. func BenchmarkChecksumTailSizes(b *testing.B) {
  164. sizes := []int{
  165. 1, 3, 7, 15, 31, // sub-SIMD; entire work is scalar tail
  166. 33, 35, 47, 63, // one loop32 + assorted tails
  167. 65, 95, 127, // one loop64 + assorted tails
  168. 1447, 1471, 1499, 1501, // around MTU
  169. 8191, 8193, // around USO
  170. 65531, 65533, // near the kernel max
  171. }
  172. for _, size := range sizes {
  173. buf := make([]byte, size)
  174. for i := range buf {
  175. buf[i] = byte(i)
  176. }
  177. b.Run(fmt.Sprintf("size=%d/local", size), func(b *testing.B) {
  178. b.SetBytes(int64(size))
  179. for i := 0; i < b.N; i++ {
  180. _ = Checksum(buf, 0)
  181. }
  182. })
  183. b.Run(fmt.Sprintf("size=%d/gvisor", size), func(b *testing.B) {
  184. b.SetBytes(int64(size))
  185. for i := 0; i < b.N; i++ {
  186. _ = gvisorchecksum.Checksum(buf, 0)
  187. }
  188. })
  189. }
  190. }
  191. // BenchmarkChecksum compares the local Checksum to gvisor's at sizes that
  192. // match real traffic: a TCP/IP header (60), a typical MSS (1448), a typical
  193. // USO size (8192), and the kernel's max GSO superpacket (65535).
  194. func BenchmarkChecksum(b *testing.B) {
  195. for _, size := range []int{60, 1448, 8192, 65535} {
  196. buf := make([]byte, size)
  197. for i := range buf {
  198. buf[i] = byte(i)
  199. }
  200. b.Run(fmt.Sprintf("size=%d/local", size), func(b *testing.B) {
  201. b.SetBytes(int64(size))
  202. for i := 0; i < b.N; i++ {
  203. _ = Checksum(buf, 0)
  204. }
  205. })
  206. b.Run(fmt.Sprintf("size=%d/gvisor", size), func(b *testing.B) {
  207. b.SetBytes(int64(size))
  208. for i := 0; i < b.N; i++ {
  209. _ = gvisorchecksum.Checksum(buf, 0)
  210. }
  211. })
  212. }
  213. }