main_test.go 4.5 KB

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