main_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package nebula
  2. import (
  3. "fmt"
  4. "net/netip"
  5. "os"
  6. "path/filepath"
  7. "testing"
  8. "time"
  9. "github.com/slackhq/nebula/cert"
  10. cert_test "github.com/slackhq/nebula/cert_test"
  11. "github.com/slackhq/nebula/config"
  12. "github.com/slackhq/nebula/test"
  13. "github.com/slackhq/nebula/util"
  14. "github.com/stretchr/testify/assert"
  15. "github.com/stretchr/testify/require"
  16. "go.uber.org/goleak"
  17. )
  18. func TestParseCpuAffinity(t *testing.T) {
  19. l := test.NewLogger()
  20. // newConfig returns a config.C with tun.cpu_affinity set to v. A nil v
  21. // leaves the key unset.
  22. newConfig := func(v any) *config.C {
  23. c := config.NewC(l)
  24. if v != nil {
  25. c.Settings["tun"] = map[string]any{"cpu_affinity": v}
  26. }
  27. return c
  28. }
  29. // unset -> nil (listenIn falls back to spreading across the allowed set)
  30. assert.Nil(t, parseCpuAffinity(newConfig(nil), l, 1, nil))
  31. // Pick a CPU we're actually allowed to run on so a valid list survives
  32. // validation regardless of the host's affinity mask.
  33. allowed, _ := util.AllowedCPUs()
  34. validCPU := 0
  35. if len(allowed) > 0 {
  36. validCPU = allowed[0]
  37. }
  38. // valid list -> parsed through unchanged
  39. assert.Equal(t, []int{validCPU, validCPU}, parseCpuAffinity(newConfig([]any{validCPU, validCPU}), l, 2, nil))
  40. // a negative entry is out of range on every platform -> disables the override
  41. assert.Nil(t, parseCpuAffinity(newConfig([]any{validCPU, -1}), l, 2, nil))
  42. // a non-integer entry -> disables the override
  43. assert.Nil(t, parseCpuAffinity(newConfig([]any{validCPU, "not-a-cpu"}), l, 2, nil))
  44. // a CPU id outside the allowed set -> disables the override. Only assertable
  45. // where we can enumerate the allowed set (e.g. linux); 1<<20 is far beyond
  46. // any representable CPU id so it can never be in the mask.
  47. if len(allowed) > 0 {
  48. assert.Nil(t, parseCpuAffinity(newConfig([]any{1 << 20}), l, 1, nil))
  49. }
  50. // an entry also listed in tun.avoid_cpus -> disables the override
  51. assert.Nil(t, parseCpuAffinity(newConfig([]any{validCPU}), l, 1, []int{validCPU}))
  52. }
  53. func TestParseAvoidCPUs(t *testing.T) {
  54. l := test.NewLogger()
  55. for _, tc := range []struct {
  56. value any
  57. want []int
  58. }{
  59. {nil, nil}, {[]any{}, []int{}}, {[]any{1, 1 << 20}, []int{1, 1 << 20}},
  60. {"auto", nil}, {"1,2", nil}, {[]any{-1}, nil}, {[]any{1.5}, nil}, {[]any{1, "2"}, nil},
  61. } {
  62. c := config.NewC(l)
  63. if tc.value != nil {
  64. c.Settings["tun"] = map[string]any{"avoid_cpus": tc.value}
  65. }
  66. assert.Equal(t, tc.want, parseAvoidCPUs(c, l), "%v", tc.value)
  67. }
  68. // avoiding every allowed CPU leaves nowhere to pin; only checkable where the allowed set is known
  69. if allowed, _ := util.AllowedCPUs(); len(allowed) > 0 {
  70. all := make([]any, len(allowed))
  71. for i, cpu := range allowed {
  72. all[i] = cpu
  73. }
  74. c := config.NewC(l)
  75. c.Settings["tun"] = map[string]any{"avoid_cpus": all}
  76. assert.Nil(t, parseAvoidCPUs(c, l))
  77. }
  78. }
  79. // TestMain_ConfigTestReleasesItsGoroutines pins the rule that Main only leaves goroutines running
  80. // when it hands back a Control to stop them with.
  81. //
  82. // A config test gets no Control, so anything it started had nothing to stop it: the lighthouse
  83. // query worker, and a hostname resolver per dns named static host, ran for the life of the
  84. // process. That matters to every embedder that validates a config in process rather than by
  85. // exec'ing, dnclient and the apple clients included, because they do it on each config load and
  86. // the leak accumulates.
  87. func TestMain_ConfigTestReleasesItsGoroutines(t *testing.T) {
  88. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  89. l := test.NewLogger()
  90. dir := t.TempDir()
  91. before := time.Now().Add(-time.Hour)
  92. after := time.Now().Add(time.Hour)
  93. ca, _, caKey, caPEM := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, before, after, nil, nil, nil)
  94. networks := []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}
  95. _, _, keyPEM, certPEM := cert_test.NewTestCert(
  96. cert.Version2, cert.Curve_CURVE25519, ca, caKey, "config-test", before, after, networks, nil, nil)
  97. caPath := filepath.Join(dir, "ca.pem")
  98. certPath := filepath.Join(dir, "cert.pem")
  99. keyPath := filepath.Join(dir, "key.pem")
  100. require.NoError(t, os.WriteFile(caPath, caPEM, 0o600))
  101. require.NoError(t, os.WriteFile(certPath, certPEM, 0o600))
  102. require.NoError(t, os.WriteFile(keyPath, keyPEM, 0o600))
  103. // A static host by address, not by name: the query worker is the goroutine under test and a
  104. // hostname would drag a real dns lookup into a unit test.
  105. configBody := fmt.Sprintf(`
  106. pki:
  107. ca: %s
  108. cert: %s
  109. key: %s
  110. static_host_map:
  111. "10.0.0.2": ["192.0.2.1:4242"]
  112. lighthouse:
  113. hosts:
  114. - "10.0.0.2"
  115. listen:
  116. host: 127.0.0.1
  117. port: 0
  118. tun:
  119. disabled: true
  120. firewall:
  121. outbound:
  122. - port: any
  123. proto: any
  124. host: any
  125. inbound:
  126. - port: any
  127. proto: any
  128. host: any
  129. `, caPath, certPath, keyPath)
  130. require.NoError(t, os.WriteFile(filepath.Join(dir, "config.yml"), []byte(configBody), 0o600))
  131. c := config.NewC(l)
  132. require.NoError(t, c.Load(dir))
  133. ctrl, err := Main(c, true, "config-test", l, nil)
  134. require.NoError(t, err)
  135. require.Nil(t, ctrl, "a config test hands back nothing to stop, so it must stop itself")
  136. }