| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- package nebula
- import (
- "fmt"
- "net/netip"
- "os"
- "path/filepath"
- "testing"
- "time"
- "github.com/slackhq/nebula/cert"
- cert_test "github.com/slackhq/nebula/cert_test"
- "github.com/slackhq/nebula/config"
- "github.com/slackhq/nebula/test"
- "github.com/slackhq/nebula/util"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "go.uber.org/goleak"
- )
- func TestParseCpuAffinity(t *testing.T) {
- l := test.NewLogger()
- // newConfig returns a config.C with tun.cpu_affinity set to v. A nil v
- // leaves the key unset.
- newConfig := func(v any) *config.C {
- c := config.NewC(l)
- if v != nil {
- c.Settings["tun"] = map[string]any{"cpu_affinity": v}
- }
- return c
- }
- // unset -> nil (listenIn falls back to spreading across the allowed set)
- assert.Nil(t, parseCpuAffinity(newConfig(nil), l, 1, nil))
- // Pick a CPU we're actually allowed to run on so a valid list survives
- // validation regardless of the host's affinity mask.
- allowed, _ := util.AllowedCPUs()
- validCPU := 0
- if len(allowed) > 0 {
- validCPU = allowed[0]
- }
- // valid list -> parsed through unchanged
- assert.Equal(t, []int{validCPU, validCPU}, parseCpuAffinity(newConfig([]any{validCPU, validCPU}), l, 2, nil))
- // a negative entry is out of range on every platform -> disables the override
- assert.Nil(t, parseCpuAffinity(newConfig([]any{validCPU, -1}), l, 2, nil))
- // a non-integer entry -> disables the override
- assert.Nil(t, parseCpuAffinity(newConfig([]any{validCPU, "not-a-cpu"}), l, 2, nil))
- // a CPU id outside the allowed set -> disables the override. Only assertable
- // where we can enumerate the allowed set (e.g. linux); 1<<20 is far beyond
- // any representable CPU id so it can never be in the mask.
- if len(allowed) > 0 {
- assert.Nil(t, parseCpuAffinity(newConfig([]any{1 << 20}), l, 1, nil))
- }
- // an entry also listed in tun.avoid_cpus -> disables the override
- assert.Nil(t, parseCpuAffinity(newConfig([]any{validCPU}), l, 1, []int{validCPU}))
- }
- func TestParseAvoidCPUs(t *testing.T) {
- l := test.NewLogger()
- for _, tc := range []struct {
- value any
- want []int
- }{
- {nil, nil}, {[]any{}, []int{}}, {[]any{1, 1 << 20}, []int{1, 1 << 20}},
- {"auto", nil}, {"1,2", nil}, {[]any{-1}, nil}, {[]any{1.5}, nil}, {[]any{1, "2"}, nil},
- } {
- c := config.NewC(l)
- if tc.value != nil {
- c.Settings["tun"] = map[string]any{"avoid_cpus": tc.value}
- }
- assert.Equal(t, tc.want, parseAvoidCPUs(c, l), "%v", tc.value)
- }
- // avoiding every allowed CPU leaves nowhere to pin; only checkable where the allowed set is known
- if allowed, _ := util.AllowedCPUs(); len(allowed) > 0 {
- all := make([]any, len(allowed))
- for i, cpu := range allowed {
- all[i] = cpu
- }
- c := config.NewC(l)
- c.Settings["tun"] = map[string]any{"avoid_cpus": all}
- assert.Nil(t, parseAvoidCPUs(c, l))
- }
- }
- // TestMain_ConfigTestReleasesItsGoroutines pins the rule that Main only leaves goroutines running
- // when it hands back a Control to stop them with.
- //
- // A config test gets no Control, so anything it started had nothing to stop it: the lighthouse
- // query worker, and a hostname resolver per dns named static host, ran for the life of the
- // process. That matters to every embedder that validates a config in process rather than by
- // exec'ing, dnclient and the apple clients included, because they do it on each config load and
- // the leak accumulates.
- func TestMain_ConfigTestReleasesItsGoroutines(t *testing.T) {
- defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
- l := test.NewLogger()
- dir := t.TempDir()
- before := time.Now().Add(-time.Hour)
- after := time.Now().Add(time.Hour)
- ca, _, caKey, caPEM := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, before, after, nil, nil, nil)
- networks := []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}
- _, _, keyPEM, certPEM := cert_test.NewTestCert(
- cert.Version2, cert.Curve_CURVE25519, ca, caKey, "config-test", before, after, networks, nil, nil)
- caPath := filepath.Join(dir, "ca.pem")
- certPath := filepath.Join(dir, "cert.pem")
- keyPath := filepath.Join(dir, "key.pem")
- require.NoError(t, os.WriteFile(caPath, caPEM, 0o600))
- require.NoError(t, os.WriteFile(certPath, certPEM, 0o600))
- require.NoError(t, os.WriteFile(keyPath, keyPEM, 0o600))
- // A static host by address, not by name: the query worker is the goroutine under test and a
- // hostname would drag a real dns lookup into a unit test.
- configBody := fmt.Sprintf(`
- pki:
- ca: %s
- cert: %s
- key: %s
- static_host_map:
- "10.0.0.2": ["192.0.2.1:4242"]
- lighthouse:
- hosts:
- - "10.0.0.2"
- listen:
- host: 127.0.0.1
- port: 0
- tun:
- disabled: true
- firewall:
- outbound:
- - port: any
- proto: any
- host: any
- inbound:
- - port: any
- proto: any
- host: any
- `, caPath, certPath, keyPath)
- require.NoError(t, os.WriteFile(filepath.Join(dir, "config.yml"), []byte(configBody), 0o600))
- c := config.NewC(l)
- require.NoError(t, c.Load(dir))
- ctrl, err := Main(c, true, "config-test", l, nil)
- require.NoError(t, err)
- require.Nil(t, ctrl, "a config test hands back nothing to stop, so it must stop itself")
- }
|