Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -903,10 +903,13 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
return errors.New("wireguard interface is not initialized")
}

// Cannot update the IP address without restarting the engine because
// the firewall, route manager, and other components cache the old address
if e.wgInterface.Address().String() != conf.Address {
log.Infof("peer IP address has changed from %s to %s", e.wgInterface.Address().String(), conf.Address)
// Check if IP address has changed and update the interface
currentAddr := e.wgInterface.Address().String()
if currentAddr != conf.Address {
if err := e.updateSelfPeerIP(currentAddr, conf.Address); err != nil {
log.Errorf("failed to update self peer IP: %v", err)
// Continue with the rest of the config update even if IP update fails
}
}

if conf.GetSshConfig() != nil {
Expand All @@ -926,6 +929,31 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
return nil
}

// updateSelfPeerIP updates the local WireGuard interface IP address when the management
// server changes the self peer's IP. This enables hot IP updates without requiring
// a full engine restart (netbird down && netbird up).
func (e *Engine) updateSelfPeerIP(oldAddr, newAddr string) error {
// Idempotency check: if addresses are the same, do nothing
if oldAddr == newAddr {
return nil
}

log.Infof("peer IP address has changed from %s to %s, updating local WireGuard interface", oldAddr, newAddr)

// Update the WireGuard interface with the new address
// The UpdateAddr method handles removing the old address and adding the new one
if err := e.wgInterface.UpdateAddr(newAddr); err != nil {
return fmt.Errorf("failed to update WireGuard interface address: %w", err)
}

// Update the engine config to reflect the new address
e.config.WgAddr = newAddr

log.Infof("successfully updated local WireGuard IP from %s to %s", oldAddr, newAddr)

return nil
}

// receiveManagementEvents connects to the Management Service event stream to receive updates from the management service
// E.g. when a new peer has been registered and we are allowed to connect to it.
func (e *Engine) receiveManagementEvents() {
Expand Down
58 changes: 58 additions & 0 deletions client/internal/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,64 @@ func TestEngine_SSHServerConsistency(t *testing.T) {
})
}

func TestEngine_UpdateSelfPeerIP(t *testing.T) {
t.Run("same address does nothing", func(t *testing.T) {
engine := &Engine{
config: &EngineConfig{
WgAddr: "100.64.0.1/24",
},
}

err := engine.updateSelfPeerIP("100.64.0.1/24", "100.64.0.1/24")
assert.NoError(t, err)
assert.Equal(t, "100.64.0.1/24", engine.config.WgAddr)
})

t.Run("updates address successfully", func(t *testing.T) {
updateAddrCalled := false
wgIface := &MockWGIface{
UpdateAddrFunc: func(newAddr string) error {
updateAddrCalled = true
assert.Equal(t, "100.64.0.2/24", newAddr)
return nil
},
}

engine := &Engine{
config: &EngineConfig{
WgAddr: "100.64.0.1/24",
},
wgInterface: wgIface,
}

err := engine.updateSelfPeerIP("100.64.0.1/24", "100.64.0.2/24")
assert.NoError(t, err)
assert.True(t, updateAddrCalled)
assert.Equal(t, "100.64.0.2/24", engine.config.WgAddr)
})

t.Run("returns error on interface update failure", func(t *testing.T) {
wgIface := &MockWGIface{
UpdateAddrFunc: func(newAddr string) error {
return fmt.Errorf("mock update error")
},
}

engine := &Engine{
config: &EngineConfig{
WgAddr: "100.64.0.1/24",
},
wgInterface: wgIface,
}

err := engine.updateSelfPeerIP("100.64.0.1/24", "100.64.0.2/24")
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock update error")
// Config should not be updated on failure
assert.Equal(t, "100.64.0.1/24", engine.config.WgAddr)
})
}

func TestEngine_UpdateNetworkMap(t *testing.T) {
// test setup
key, err := wgtypes.GeneratePrivateKey()
Expand Down
Loading