timeout_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package nebula
  2. import (
  3. "net/netip"
  4. "testing"
  5. "time"
  6. "github.com/slackhq/nebula/firewall"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestNewTimerWheel(t *testing.T) {
  11. // Make sure we get an object we expect
  12. tw := NewTimerWheel[firewall.Packet](time.Second, time.Second*10)
  13. assert.Equal(t, 12, tw.wheelLen)
  14. assert.Equal(t, 0, tw.current)
  15. assert.Nil(t, tw.lastTick)
  16. assert.Equal(t, time.Second*1, tw.tickDuration)
  17. assert.Equal(t, time.Second*10, tw.wheelDuration)
  18. assert.Len(t, tw.wheel, 12)
  19. // Assert the math is correct
  20. tw = NewTimerWheel[firewall.Packet](time.Second*3, time.Second*10)
  21. assert.Equal(t, 5, tw.wheelLen)
  22. tw = NewTimerWheel[firewall.Packet](time.Second*120, time.Minute*10)
  23. assert.Equal(t, 7, tw.wheelLen)
  24. // Test empty purge of non nil items
  25. i, ok := tw.Purge()
  26. assert.Equal(t, firewall.Packet{}, i)
  27. assert.False(t, ok)
  28. // Test empty purges of nil items
  29. tw2 := NewTimerWheel[*int](time.Second, time.Second*10)
  30. i2, ok := tw2.Purge()
  31. assert.Nil(t, i2)
  32. assert.False(t, ok)
  33. }
  34. func TestTimerWheel_findWheel(t *testing.T) {
  35. tw := NewTimerWheel[firewall.Packet](time.Second, time.Second*10)
  36. assert.Len(t, tw.wheel, 12)
  37. // Current + tick + 1 since we don't know how far into current we are
  38. assert.Equal(t, 2, tw.findWheel(time.Second*1))
  39. // Scale up to min duration
  40. assert.Equal(t, 2, tw.findWheel(time.Millisecond*1))
  41. // Make sure we hit that last index
  42. assert.Equal(t, 11, tw.findWheel(time.Second*10))
  43. // Scale down to max duration
  44. assert.Equal(t, 11, tw.findWheel(time.Second*11))
  45. tw.current = 1
  46. // Make sure we account for the current position properly
  47. assert.Equal(t, 3, tw.findWheel(time.Second*1))
  48. assert.Equal(t, 0, tw.findWheel(time.Second*10))
  49. }
  50. func TestTimerWheel_Add(t *testing.T) {
  51. tw := NewTimerWheel[firewall.Packet](time.Second, time.Second*10)
  52. fp1 := firewall.Packet{}
  53. tw.Add(fp1, time.Second*1)
  54. // Make sure we set head and tail properly
  55. assert.NotNil(t, tw.wheel[2])
  56. assert.Equal(t, fp1, tw.wheel[2].Head.Item)
  57. assert.Nil(t, tw.wheel[2].Head.Next)
  58. assert.Equal(t, fp1, tw.wheel[2].Tail.Item)
  59. assert.Nil(t, tw.wheel[2].Tail.Next)
  60. // Make sure we only modify head
  61. fp2 := firewall.Packet{}
  62. tw.Add(fp2, time.Second*1)
  63. assert.Equal(t, fp2, tw.wheel[2].Head.Item)
  64. assert.Equal(t, fp1, tw.wheel[2].Head.Next.Item)
  65. assert.Equal(t, fp1, tw.wheel[2].Tail.Item)
  66. assert.Nil(t, tw.wheel[2].Tail.Next)
  67. // Make sure we use free'd items first
  68. tw.itemCache = &TimeoutItem[firewall.Packet]{}
  69. tw.itemsCached = 1
  70. tw.Add(fp2, time.Second*1)
  71. assert.Nil(t, tw.itemCache)
  72. assert.Equal(t, 0, tw.itemsCached)
  73. // Ensure that all configurations of a wheel does not result in calculating an overflow of the wheel
  74. for min := time.Duration(1); min < 100; min++ {
  75. for max := min; max < 100; max++ {
  76. tw = NewTimerWheel[firewall.Packet](min, max)
  77. for current := 0; current < tw.wheelLen; current++ {
  78. tw.current = current
  79. for timeout := time.Duration(0); timeout <= tw.wheelDuration; timeout++ {
  80. tick := tw.findWheel(timeout)
  81. if tick >= tw.wheelLen {
  82. t.Errorf("Min: %v; Max: %v; Wheel len: %v; Current Tick: %v; Insert timeout: %v; Calc tick: %v", min, max, tw.wheelLen, current, timeout, tick)
  83. }
  84. }
  85. }
  86. }
  87. }
  88. }
  89. func TestTimerWheel_Purge(t *testing.T) {
  90. // First advance should set the lastTick and do nothing else
  91. tw := NewTimerWheel[firewall.Packet](time.Second, time.Second*10)
  92. assert.Nil(t, tw.lastTick)
  93. tw.Advance(time.Now())
  94. assert.NotNil(t, tw.lastTick)
  95. assert.Equal(t, 0, tw.current)
  96. fps := []firewall.Packet{
  97. {LocalAddr: netip.MustParseAddr("0.0.0.1")},
  98. {LocalAddr: netip.MustParseAddr("0.0.0.2")},
  99. {LocalAddr: netip.MustParseAddr("0.0.0.3")},
  100. {LocalAddr: netip.MustParseAddr("0.0.0.4")},
  101. }
  102. tw.Add(fps[0], time.Second*1)
  103. tw.Add(fps[1], time.Second*1)
  104. tw.Add(fps[2], time.Second*2)
  105. tw.Add(fps[3], time.Second*2)
  106. ta := time.Now().Add(time.Second * 3)
  107. lastTick := *tw.lastTick
  108. tw.Advance(ta)
  109. assert.Equal(t, 3, tw.current)
  110. assert.True(t, tw.lastTick.After(lastTick))
  111. // Make sure we get all 4 packets back
  112. for i := range 4 {
  113. p, has := tw.Purge()
  114. assert.True(t, has)
  115. assert.Equal(t, fps[i], p)
  116. }
  117. // Make sure there aren't any leftover
  118. _, ok := tw.Purge()
  119. assert.False(t, ok)
  120. assert.Nil(t, tw.expired.Head)
  121. assert.Nil(t, tw.expired.Tail)
  122. // Make sure we cached the free'd items
  123. assert.Equal(t, 4, tw.itemsCached)
  124. ci := tw.itemCache
  125. for range 4 {
  126. require.NotNil(t, ci)
  127. assert.Zero(t, ci.Item)
  128. ci = ci.Next
  129. }
  130. assert.Nil(t, ci)
  131. // Let's make sure we roll over properly
  132. ta = ta.Add(time.Second * 5)
  133. tw.Advance(ta)
  134. assert.Equal(t, 8, tw.current)
  135. ta = ta.Add(time.Second * 2)
  136. tw.Advance(ta)
  137. assert.Equal(t, 10, tw.current)
  138. ta = ta.Add(time.Second * 1)
  139. tw.Advance(ta)
  140. assert.Equal(t, 11, tw.current)
  141. ta = ta.Add(time.Second * 1)
  142. tw.Advance(ta)
  143. assert.Equal(t, 0, tw.current)
  144. }