control_lifecycle_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. package nebula
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "net/netip"
  7. "sync"
  8. "testing"
  9. "time"
  10. "github.com/gaissmai/bart"
  11. "github.com/slackhq/nebula/config"
  12. "github.com/slackhq/nebula/overlay/batch"
  13. "github.com/slackhq/nebula/overlay/tio"
  14. "github.com/slackhq/nebula/routing"
  15. "github.com/slackhq/nebula/test"
  16. "github.com/slackhq/nebula/udp"
  17. "github.com/stretchr/testify/assert"
  18. "github.com/stretchr/testify/require"
  19. )
  20. type fakeDevice struct {
  21. closeOnce sync.Once
  22. closedCh chan struct{}
  23. closed bool
  24. }
  25. func newFakeDevice() *fakeDevice {
  26. return &fakeDevice{closedCh: make(chan struct{})}
  27. }
  28. // Read blocks until Close like a real tun with no traffic, then reports EOF
  29. // the same way a closed device does
  30. func (d *fakeDevice) Read() ([]tio.Packet, error) {
  31. <-d.closedCh
  32. return nil, io.EOF
  33. }
  34. func (d *fakeDevice) Write(p []byte) (int, error) { return len(p), nil }
  35. func (d *fakeDevice) Close() error {
  36. d.closeOnce.Do(func() {
  37. d.closed = true
  38. close(d.closedCh)
  39. })
  40. return nil
  41. }
  42. func (d *fakeDevice) Activate() error { return nil }
  43. func (d *fakeDevice) Networks() []netip.Prefix { return nil }
  44. func (d *fakeDevice) Name() string { return "fake" }
  45. func (d *fakeDevice) RoutesFor(netip.Addr) routing.Gateways { return nil }
  46. func (d *fakeDevice) SupportsMultiqueue() bool { return false }
  47. func (d *fakeDevice) NewMultiQueueReader() error {
  48. return errors.New("unsupported")
  49. }
  50. func (d *fakeDevice) Readers() []tio.Queue { return []tio.Queue{d} }
  51. // newReadyControl hand-builds the minimum Control that Main would have
  52. // produced right before Start, including the construction token NewInterface
  53. // takes so waiters block until Close releases the resources
  54. func newReadyControl(t *testing.T) (*Control, *fakeDevice, *fakeConn) {
  55. l := test.NewLogger()
  56. dev := newFakeDevice()
  57. conn := &fakeConn{}
  58. ctx, cancel := context.WithCancel(context.Background())
  59. myVpnNet := netip.MustParsePrefix("10.128.0.1/16")
  60. nt := new(bart.Lite)
  61. nt.Insert(myVpnNet)
  62. cs := &CertState{
  63. myVpnNetworks: []netip.Prefix{myVpnNet},
  64. myVpnNetworksTable: nt,
  65. }
  66. lh, err := NewLightHouseFromConfig(ctx, l, config.NewC(l), cs, nil, nil)
  67. require.NoError(t, err)
  68. f := &Interface{
  69. ctx: ctx,
  70. inside: dev,
  71. outside: conn,
  72. writers: []udp.Conn{conn},
  73. readers: make([]tio.Queue, 1),
  74. batchers: make([]batch.RxBatcher, 1),
  75. routines: 1,
  76. hostMap: newHostMap(l),
  77. lightHouse: lh,
  78. l: l,
  79. }
  80. f.wg.Add(1)
  81. return &Control{
  82. state: StateReady,
  83. f: f,
  84. l: l,
  85. ctx: ctx,
  86. cancel: cancel,
  87. }, dev, conn
  88. }
  89. func TestControl_StopBeforeStart(t *testing.T) {
  90. c, dev, conn := newReadyControl(t)
  91. // A Stop on a never started control must release everything Main acquired
  92. c.Stop()
  93. assert.Equal(t, StateStopped, c.State())
  94. assert.True(t, dev.closed, "the tun device should have been closed")
  95. assert.True(t, conn.closed, "the udp socket should have been closed")
  96. require.ErrorIs(t, c.ctx.Err(), context.Canceled, "the service context should have been cancelled")
  97. // Wait must return promptly now that the resources are released
  98. require.NoError(t, c.Wait())
  99. // A stopped control can never be started
  100. _, err := c.Start()
  101. require.ErrorIs(t, err, ErrAlreadyStopped)
  102. // A second Stop is a harmless no-op
  103. c.Stop()
  104. assert.Equal(t, StateStopped, c.State())
  105. require.NoError(t, c.Wait())
  106. }
  107. func TestControl_WaitBlocksUntilStop(t *testing.T) {
  108. c, _, _ := newReadyControl(t)
  109. done := make(chan error, 1)
  110. go func() { done <- c.Wait() }()
  111. select {
  112. case <-done:
  113. t.Fatal("Wait returned before Stop")
  114. case <-time.After(50 * time.Millisecond):
  115. }
  116. c.Stop()
  117. select {
  118. case err := <-done:
  119. require.NoError(t, err)
  120. case <-time.After(time.Second):
  121. t.Fatal("Wait did not return after Stop")
  122. }
  123. }
  124. type fakeConn struct {
  125. closed bool
  126. rebinds int
  127. }
  128. func (c *fakeConn) Rebind() error { c.rebinds++; return nil }
  129. func (c *fakeConn) LocalAddr() (netip.AddrPort, error) { return netip.AddrPort{}, nil }
  130. func (c *fakeConn) ListenOut(_ udp.EncReader, _ func()) error { return nil }
  131. func (c *fakeConn) WriteTo(_ []byte, _ netip.AddrPort) error { return nil }
  132. func (c *fakeConn) WriteBatch(_ [][]byte, _ []netip.AddrPort, _ []byte) error {
  133. return nil
  134. }
  135. func (c *fakeConn) ReloadConfig(_ *config.C) {}
  136. func (c *fakeConn) SupportsMultipleReaders() bool { return true }
  137. func (c *fakeConn) Close() error { c.closed = true; return nil }
  138. type multiqueueDevice struct {
  139. *fakeDevice
  140. }
  141. func (d *multiqueueDevice) SupportsMultiqueue() bool { return true }
  142. func TestControl_StartMultiqueueFailureReleases(t *testing.T) {
  143. dev := &multiqueueDevice{fakeDevice: newFakeDevice()}
  144. conn := &fakeConn{}
  145. ctx, cancel := context.WithCancel(context.Background())
  146. f := &Interface{
  147. ctx: ctx,
  148. inside: dev,
  149. outside: conn,
  150. writers: []udp.Conn{conn},
  151. readers: make([]tio.Queue, 2),
  152. batchers: make([]batch.RxBatcher, 2),
  153. routines: 2,
  154. l: test.NewLogger(),
  155. }
  156. f.wg.Add(1)
  157. c := &Control{
  158. state: StateReady,
  159. f: f,
  160. l: test.NewLogger(),
  161. ctx: ctx,
  162. cancel: cancel,
  163. }
  164. // The second reader fails to open, everything must be released
  165. _, err := c.Start()
  166. require.Error(t, err)
  167. assert.Equal(t, StateStopped, c.State())
  168. assert.True(t, dev.closed, "the tun device should have been closed")
  169. assert.True(t, conn.closed, "the udp socket should have been closed")
  170. require.ErrorIs(t, c.ctx.Err(), context.Canceled)
  171. // And Wait must not hang on the construction token
  172. require.NoError(t, c.Wait())
  173. }
  174. func TestInterface_CloseIsIdempotent(t *testing.T) {
  175. dev := newFakeDevice()
  176. f := &Interface{
  177. inside: dev,
  178. l: test.NewLogger(),
  179. }
  180. f.wg.Add(1)
  181. require.NoError(t, f.Close())
  182. assert.True(t, dev.closed)
  183. // A second Close must not double release the wg token or the device
  184. require.NoError(t, f.Close())
  185. require.NoError(t, f.wait())
  186. }
  187. func TestControl_FatalErrorReportsThroughWait(t *testing.T) {
  188. c, dev, conn := newReadyControl(t)
  189. // Mirror what Start wires up, without needing real packet readers
  190. c.f.triggerShutdown = c.Stop
  191. c.state = StateStarted
  192. boom := errors.New("boom")
  193. c.f.onFatal(boom)
  194. require.ErrorIs(t, c.Wait(), boom)
  195. assert.Equal(t, StateStopped, c.State())
  196. assert.True(t, dev.closed)
  197. assert.True(t, conn.closed)
  198. // A second fatal error must not fire the shutdown again or replace the first
  199. c.f.onFatal(errors.New("later"))
  200. require.ErrorIs(t, c.Wait(), boom)
  201. // Wait stays factual, a Stop after the death does not mask the error
  202. c.Stop()
  203. require.ErrorIs(t, c.Wait(), boom)
  204. }
  205. func TestControl_ConcurrentStopAndStart(t *testing.T) {
  206. c, _, _ := newReadyControl(t)
  207. var wg sync.WaitGroup
  208. for i := 0; i < 2; i++ {
  209. wg.Go(func() { c.Stop() })
  210. }
  211. wg.Go(func() { _, _ = c.Start() })
  212. wg.Go(func() {
  213. _ = c.Wait()
  214. // A returned Wait must always observe the final state, no matter how
  215. // the race resolved
  216. assert.Equal(t, StateStopped, c.State())
  217. })
  218. wg.Wait()
  219. // However the race resolves, the control must end fully stopped with no
  220. // panic and Wait must observe the final state
  221. require.NoError(t, c.Wait())
  222. assert.Equal(t, StateStopped, c.State())
  223. _, err := c.Start()
  224. require.ErrorIs(t, err, ErrAlreadyStopped)
  225. }
  226. func TestControl_StartStopLifecycle(t *testing.T) {
  227. c, dev, conn := newReadyControl(t)
  228. wait, err := c.Start()
  229. require.NoError(t, err)
  230. require.NotNil(t, wait, "a successful Start must return a block function")
  231. assert.Equal(t, StateStarted, c.State())
  232. _, err = c.Start()
  233. require.ErrorIs(t, err, ErrAlreadyStarted)
  234. // Stop must unpark the reader blocked in the device and release everything
  235. c.Stop()
  236. assert.Equal(t, StateStopped, c.State())
  237. assert.True(t, dev.closed, "the tun device should have been closed")
  238. assert.True(t, conn.closed, "the udp socket should have been closed")
  239. require.ErrorIs(t, c.ctx.Err(), context.Canceled)
  240. // The reader drained off a closed device, that is not a fatal error
  241. require.NoError(t, c.Wait())
  242. _, err = c.Start()
  243. require.ErrorIs(t, err, ErrAlreadyStopped)
  244. }
  245. func TestControl_RebindIsGatedByState(t *testing.T) {
  246. c, _, conn := newReadyControl(t)
  247. // A rebind before Start reaches nothing, the interface is not up
  248. c.RebindUDPServer()
  249. assert.Equal(t, 0, conn.rebinds, "rebind before start must be a no-op")
  250. wait, err := c.Start()
  251. require.NoError(t, err)
  252. require.NotNil(t, wait, "a successful Start must return a block function")
  253. c.RebindUDPServer()
  254. assert.Equal(t, 1, conn.rebinds, "rebind while started must reach the conn")
  255. // A rebind racing a completed stop must not touch the closed conn
  256. c.Stop()
  257. require.NoError(t, c.Wait())
  258. c.RebindUDPServer()
  259. assert.Equal(t, 1, conn.rebinds, "rebind after stop must be a no-op")
  260. }