Fix bug in nebula TUN device naming, causing it to force nebula to reload too much

This commit is contained in:
Brian Picciano 2024-12-12 22:02:00 +01:00
parent d2c16573ff
commit c08b225ee2
8 changed files with 121 additions and 57 deletions

View File

@ -23,10 +23,11 @@ import (
// - dnsmasq // - dnsmasq
// - garage (0 or more, depending on configured storage allocations) // - garage (0 or more, depending on configured storage allocations)
type Children struct { type Children struct {
logger *mlog.Logger logger *mlog.Logger
binDirPath string binDirPath string
runtimeDir toolkit.Dir runtimeDir toolkit.Dir
garageAdminToken string garageAdminToken string
nebulaDeviceNamer *NebulaDeviceNamer
garageRPCSecret string garageRPCSecret string
@ -45,6 +46,7 @@ func New(
networkConfig daecommon.NetworkConfig, networkConfig daecommon.NetworkConfig,
runtimeDir toolkit.Dir, runtimeDir toolkit.Dir,
garageAdminToken string, garageAdminToken string,
nebulaDeviceNamer *NebulaDeviceNamer,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) ( ) (
*Children, error, *Children, error,
@ -56,11 +58,12 @@ func New(
} }
c := &Children{ c := &Children{
logger: logger, logger: logger,
binDirPath: binDirPath, binDirPath: binDirPath,
runtimeDir: runtimeDir, runtimeDir: runtimeDir,
garageAdminToken: garageAdminToken, garageAdminToken: garageAdminToken,
garageRPCSecret: garageRPCSecret, nebulaDeviceNamer: nebulaDeviceNamer,
garageRPCSecret: garageRPCSecret,
} }
if c.nebulaProc, err = nebulaPmuxProc( if c.nebulaProc, err = nebulaPmuxProc(
@ -68,6 +71,7 @@ func New(
c.logger, c.logger,
c.runtimeDir.Path, c.runtimeDir.Path,
c.binDirPath, c.binDirPath,
c.nebulaDeviceNamer,
networkConfig, networkConfig,
hostBootstrap, hostBootstrap,
); err != nil { ); err != nil {
@ -145,7 +149,12 @@ func (c *Children) reloadNebula(
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) error { ) error {
if _, changed, err := nebulaWriteConfig( if _, changed, err := nebulaWriteConfig(
ctx, c.logger, c.runtimeDir.Path, networkConfig, hostBootstrap, ctx,
c.logger,
c.runtimeDir.Path,
c.nebulaDeviceNamer,
networkConfig,
hostBootstrap,
); err != nil { ); err != nil {
return fmt.Errorf("writing a new nebula config: %w", err) return fmt.Errorf("writing a new nebula config: %w", err)
} else if !changed { } else if !changed {

View File

@ -6,7 +6,6 @@ import (
"io" "io"
"isle/bootstrap" "isle/bootstrap"
"isle/daemon/daecommon" "isle/daemon/daecommon"
"isle/nebula"
"isle/toolkit" "isle/toolkit"
"isle/yamlutil" "isle/yamlutil"
"net" "net"
@ -53,6 +52,7 @@ func waitForNebula(
} }
func nebulaConfig( func nebulaConfig(
deviceNamer *NebulaDeviceNamer,
networkConfig daecommon.NetworkConfig, networkConfig daecommon.NetworkConfig,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) ( ) (
@ -119,7 +119,10 @@ func nebulaConfig(
"respond": true, "respond": true,
}, },
"tun": m{ "tun": m{
"dev": nebula.GetDeviceName(hostBootstrap.NetworkCreationParams.ID), "dev": deviceNamer.getName(
hostBootstrap.NetworkCreationParams.ID,
hostBootstrap.ThisHost().IP(),
),
}, },
"firewall": firewall, "firewall": firewall,
} }
@ -169,12 +172,13 @@ func nebulaWriteConfig(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
runtimeDirPath string, runtimeDirPath string,
deviceNamer *NebulaDeviceNamer,
networkConfig daecommon.NetworkConfig, networkConfig daecommon.NetworkConfig,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) ( ) (
string, bool, error, string, bool, error,
) { ) {
config, err := nebulaConfig(networkConfig, hostBootstrap) config, err := nebulaConfig(deviceNamer, networkConfig, hostBootstrap)
if err != nil { if err != nil {
return "", false, fmt.Errorf("creating nebula config: %w", err) return "", false, fmt.Errorf("creating nebula config: %w", err)
} }
@ -199,13 +203,14 @@ func nebulaPmuxProc(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
runtimeDirPath, binDirPath string, runtimeDirPath, binDirPath string,
deviceNamer *NebulaDeviceNamer,
networkConfig daecommon.NetworkConfig, networkConfig daecommon.NetworkConfig,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) ( ) (
*pmuxlib.Process, error, *pmuxlib.Process, error,
) { ) {
nebulaYmlPath, _, err := nebulaWriteConfig( nebulaYmlPath, _, err := nebulaWriteConfig(
ctx, logger, runtimeDirPath, networkConfig, hostBootstrap, ctx, logger, runtimeDirPath, deviceNamer, networkConfig, hostBootstrap,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("writing nebula config: %w", err) return nil, fmt.Errorf("writing nebula config: %w", err)

View File

@ -0,0 +1,51 @@
package children
import (
"fmt"
"net/netip"
"sync"
)
type nebulaDeviceNamerKey struct {
networkID string
deviceIP netip.Addr
}
// NebulaDeviceNamer is used to assign unique TUN device names to different
// nebula networks running on the same host. It is intended to be shared amongst
// all running Children instances so that they do not accidentally conflict in
// TUN device names.
//
// NebulaDeviceNamer is thread-safe.
type NebulaDeviceNamer struct {
l sync.Mutex
counter uint64
assigned map[nebulaDeviceNamerKey]string
}
// NewNebulaDeviceNamer initializes and returns a new instance.
func NewNebulaDeviceNamer() *NebulaDeviceNamer {
return &NebulaDeviceNamer{
assigned: map[nebulaDeviceNamerKey]string{},
}
}
// getName returns a unique TUN device name for the given network and IP,
// generating a new one if the ID has never been given before.
func (n *NebulaDeviceNamer) getName(networkID string, ip netip.Addr) string {
key := nebulaDeviceNamerKey{networkID, ip}
n.l.Lock()
defer n.l.Unlock()
if name, ok := n.assigned[key]; ok {
return name
}
i := n.counter
n.counter++
name := fmt.Sprintf("isle%d-%s", i, networkID)
n.assigned[key] = name
return name
}

View File

@ -6,6 +6,7 @@ import (
"context" "context"
"fmt" "fmt"
"isle/bootstrap" "isle/bootstrap"
"isle/daemon/children"
"isle/daemon/daecommon" "isle/daemon/daecommon"
"isle/nebula" "isle/nebula"
"isle/toolkit" "isle/toolkit"
@ -126,6 +127,7 @@ type loader struct {
envBinDirPath string envBinDirPath string
networksStateDir toolkit.Dir networksStateDir toolkit.Dir
networksRuntimeDir toolkit.Dir networksRuntimeDir toolkit.Dir
nebulaDeviceNamer *children.NebulaDeviceNamer
} }
// NewLoader returns a new Loader which will use the given directories to load // NewLoader returns a new Loader which will use the given directories to load
@ -167,6 +169,7 @@ func NewLoader(
envBinDirPath, envBinDirPath,
networksStateDir, networksStateDir,
networksRuntimeDir, networksRuntimeDir,
children.NewNebulaDeviceNamer(),
}, nil }, nil
} }
@ -222,6 +225,7 @@ func (l *loader) Load(
ctx, ctx,
logger.WithNamespace("network"), logger.WithNamespace("network"),
l.envBinDirPath, l.envBinDirPath,
l.nebulaDeviceNamer,
networkStateDir, networkStateDir,
networkRuntimeDir, networkRuntimeDir,
opts, opts,
@ -254,6 +258,7 @@ func (l *loader) Join(
ctx, ctx,
logger.WithNamespace("network"), logger.WithNamespace("network"),
l.envBinDirPath, l.envBinDirPath,
l.nebulaDeviceNamer,
joiningBootstrap, joiningBootstrap,
networkStateDir, networkStateDir,
networkRuntimeDir, networkRuntimeDir,
@ -286,6 +291,7 @@ func (l *loader) Create(
ctx, ctx,
logger.WithNamespace("network"), logger.WithNamespace("network"),
l.envBinDirPath, l.envBinDirPath,
l.nebulaDeviceNamer,
networkStateDir, networkStateDir,
networkRuntimeDir, networkRuntimeDir,
creationParams, creationParams,

View File

@ -166,9 +166,10 @@ func (o *Opts) withDefaults() *Opts {
type network struct { type network struct {
logger *mlog.Logger logger *mlog.Logger
envBinDirPath string envBinDirPath string
stateDir toolkit.Dir nebulaDeviceNamer *children.NebulaDeviceNamer
runtimeDir toolkit.Dir stateDir toolkit.Dir
runtimeDir toolkit.Dir
opts *Opts opts *Opts
@ -191,6 +192,7 @@ func newNetwork(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
envBinDirPath string, envBinDirPath string,
nebulaDeviceNamer *children.NebulaDeviceNamer,
stateDir toolkit.Dir, stateDir toolkit.Dir,
runtimeDir toolkit.Dir, runtimeDir toolkit.Dir,
dirsMayExist bool, dirsMayExist bool,
@ -202,13 +204,14 @@ func newNetwork(
var ( var (
n = &network{ n = &network{
logger: logger, logger: logger,
envBinDirPath: envBinDirPath, envBinDirPath: envBinDirPath,
stateDir: stateDir, nebulaDeviceNamer: nebulaDeviceNamer,
runtimeDir: runtimeDir, stateDir: stateDir,
opts: opts.withDefaults(), runtimeDir: runtimeDir,
workerCtx: ctx, opts: opts.withDefaults(),
workerCancel: cancel, workerCtx: ctx,
workerCancel: cancel,
} }
err error err error
) )
@ -253,6 +256,7 @@ func load(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
envBinDirPath string, envBinDirPath string,
nebulaDeviceNamer *children.NebulaDeviceNamer,
stateDir toolkit.Dir, stateDir toolkit.Dir,
runtimeDir toolkit.Dir, runtimeDir toolkit.Dir,
opts *Opts, opts *Opts,
@ -263,6 +267,7 @@ func load(
ctx, ctx,
logger, logger,
envBinDirPath, envBinDirPath,
nebulaDeviceNamer,
stateDir, stateDir,
runtimeDir, runtimeDir,
true, true,
@ -292,6 +297,7 @@ func join(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
envBinDirPath string, envBinDirPath string,
nebulaDeviceNamer *children.NebulaDeviceNamer,
joiningBootstrap JoiningBootstrap, joiningBootstrap JoiningBootstrap,
stateDir toolkit.Dir, stateDir toolkit.Dir,
runtimeDir toolkit.Dir, runtimeDir toolkit.Dir,
@ -303,6 +309,7 @@ func join(
ctx, ctx,
logger, logger,
envBinDirPath, envBinDirPath,
nebulaDeviceNamer,
stateDir, stateDir,
runtimeDir, runtimeDir,
false, false,
@ -329,6 +336,7 @@ func create(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
envBinDirPath string, envBinDirPath string,
nebulaDeviceNamer *children.NebulaDeviceNamer,
stateDir toolkit.Dir, stateDir toolkit.Dir,
runtimeDir toolkit.Dir, runtimeDir toolkit.Dir,
creationParams bootstrap.CreationParams, creationParams bootstrap.CreationParams,
@ -349,6 +357,7 @@ func create(
ctx, ctx,
logger, logger,
envBinDirPath, envBinDirPath,
nebulaDeviceNamer,
stateDir, stateDir,
runtimeDir, runtimeDir,
false, false,
@ -461,6 +470,7 @@ func (n *network) initialize(
n.networkConfig, n.networkConfig,
n.runtimeDir, n.runtimeDir,
n.opts.GarageAdminToken, n.opts.GarageAdminToken,
n.nebulaDeviceNamer,
n.currBootstrap, n.currBootstrap,
) )
if err != nil { if err != nil {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"isle/bootstrap" "isle/bootstrap"
"isle/daemon/children"
"isle/daemon/daecommon" "isle/daemon/daecommon"
"isle/garage" "isle/garage"
"isle/nebula" "isle/nebula"
@ -60,10 +61,11 @@ func newPublicAddr() string {
} }
type integrationHarness struct { type integrationHarness struct {
ctx context.Context ctx context.Context
logger *mlog.Logger logger *mlog.Logger
rootDir toolkit.Dir rootDir toolkit.Dir
dirCounter atomic.Uint64 dirCounter atomic.Uint64
nebulaDeviceNamer *children.NebulaDeviceNamer
} }
func newIntegrationHarness(t *testing.T) *integrationHarness { func newIntegrationHarness(t *testing.T) *integrationHarness {
@ -86,9 +88,10 @@ func newIntegrationHarness(t *testing.T) *integrationHarness {
}) })
return &integrationHarness{ return &integrationHarness{
ctx: context.Background(), ctx: context.Background(),
logger: toolkit.NewTestLogger(t), logger: toolkit.NewTestLogger(t),
rootDir: toolkit.Dir{Path: rootDir}, rootDir: toolkit.Dir{Path: rootDir},
nebulaDeviceNamer: children.NewNebulaDeviceNamer(),
} }
} }
@ -170,6 +173,7 @@ type integrationHarnessNetwork struct {
logger *mlog.Logger logger *mlog.Logger
hostName nebula.HostName hostName nebula.HostName
stateDir, runtimeDir toolkit.Dir stateDir, runtimeDir toolkit.Dir
nebulaDeviceNamer *children.NebulaDeviceNamer
opts *Opts opts *Opts
} }
@ -204,6 +208,7 @@ func (h *integrationHarness) createNetwork(
h.ctx, h.ctx,
logger, logger,
getEnvBinDirPath(), getEnvBinDirPath(),
h.nebulaDeviceNamer,
stateDir, stateDir,
runtimeDir, runtimeDir,
opts.creationParams, opts.creationParams,
@ -222,6 +227,7 @@ func (h *integrationHarness) createNetwork(
hostName, hostName,
stateDir, stateDir,
runtimeDir, runtimeDir,
h.nebulaDeviceNamer,
networkOpts, networkOpts,
} }
@ -286,6 +292,7 @@ func (h *integrationHarness) joinNetwork(
h.ctx, h.ctx,
logger, logger,
getEnvBinDirPath(), getEnvBinDirPath(),
h.nebulaDeviceNamer,
joiningBootstrap, joiningBootstrap,
stateDir, stateDir,
runtimeDir, runtimeDir,
@ -302,6 +309,7 @@ func (h *integrationHarness) joinNetwork(
hostName, hostName,
stateDir, stateDir,
runtimeDir, runtimeDir,
h.nebulaDeviceNamer,
networkOpts, networkOpts,
} }
@ -327,6 +335,7 @@ func (nh *integrationHarnessNetwork) restart(t *testing.T) {
nh.ctx, nh.ctx,
nh.logger, nh.logger,
getEnvBinDirPath(), getEnvBinDirPath(),
nh.nebulaDeviceNamer,
nh.stateDir, nh.stateDir,
nh.runtimeDir, nh.runtimeDir,
nh.opts, nh.opts,

View File

@ -1,18 +0,0 @@
package nebula
import (
"fmt"
"sync/atomic"
)
var deviceCounter = new(atomic.Uint64)
// GetDeviceName returns the network device name to use for a particular
// network. Each returns name is gauranteed to be unique for the lifetime of the
// process.
func GetDeviceName(networkID string) string {
i := deviceCounter.Add(1) - 1
// the returned string will be too long for linux, but it will get
// automatically truncated.
return fmt.Sprintf("isle%d-%s", i, networkID)
}

View File

@ -1,8 +0,0 @@
---
type: tasks
---
# Address All TODOs
All TODOs which remain in the codebase must be addressed or moved into a task
file.