Skip to content

Commit b9ef214

Browse files
authored
[client] Fix macOS state-based dns cleanup (#4701)
1 parent 709e24e commit b9ef214

File tree

4 files changed

+151
-32
lines changed

4 files changed

+151
-32
lines changed

client/internal/dns/host_darwin.go

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414

1515
log "github.com/sirupsen/logrus"
16+
"golang.org/x/exp/maps"
1617

1718
"github.com/netbirdio/netbird/client/internal/statemanager"
1819
)
@@ -50,28 +51,21 @@ func (s *systemConfigurator) supportCustomPort() bool {
5051
}
5152

5253
func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error {
53-
var err error
54-
55-
if err := stateManager.UpdateState(&ShutdownState{}); err != nil {
56-
log.Errorf("failed to update shutdown state: %s", err)
57-
}
58-
5954
var (
6055
searchDomains []string
6156
matchDomains []string
6257
)
6358

64-
err = s.recordSystemDNSSettings(true)
65-
if err != nil {
59+
if err := s.recordSystemDNSSettings(true); err != nil {
6660
log.Errorf("unable to update record of System's DNS config: %s", err.Error())
6761
}
6862

6963
if config.RouteAll {
7064
searchDomains = append(searchDomains, "\"\"")
71-
err = s.addLocalDNS()
72-
if err != nil {
73-
log.Infof("failed to enable split DNS")
65+
if err := s.addLocalDNS(); err != nil {
66+
log.Warnf("failed to add local DNS: %v", err)
7467
}
68+
s.updateState(stateManager)
7569
}
7670

7771
for _, dConf := range config.Domains {
@@ -86,6 +80,7 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *
8680
}
8781

8882
matchKey := getKeyWithInput(netbirdDNSStateKeyFormat, matchSuffix)
83+
var err error
8984
if len(matchDomains) != 0 {
9085
err = s.addMatchDomains(matchKey, strings.Join(matchDomains, " "), config.ServerIP, config.ServerPort)
9186
} else {
@@ -95,6 +90,7 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *
9590
if err != nil {
9691
return fmt.Errorf("add match domains: %w", err)
9792
}
93+
s.updateState(stateManager)
9894

9995
searchKey := getKeyWithInput(netbirdDNSStateKeyFormat, searchSuffix)
10096
if len(searchDomains) != 0 {
@@ -106,6 +102,7 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *
106102
if err != nil {
107103
return fmt.Errorf("add search domains: %w", err)
108104
}
105+
s.updateState(stateManager)
109106

110107
if err := s.flushDNSCache(); err != nil {
111108
log.Errorf("failed to flush DNS cache: %v", err)
@@ -114,6 +111,12 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *
114111
return nil
115112
}
116113

114+
func (s *systemConfigurator) updateState(stateManager *statemanager.Manager) {
115+
if err := stateManager.UpdateState(&ShutdownState{CreatedKeys: maps.Keys(s.createdKeys)}); err != nil {
116+
log.Errorf("failed to update shutdown state: %s", err)
117+
}
118+
}
119+
117120
func (s *systemConfigurator) string() string {
118121
return "scutil"
119122
}
@@ -167,18 +170,20 @@ func (s *systemConfigurator) removeKeyFromSystemConfig(key string) error {
167170
func (s *systemConfigurator) addLocalDNS() error {
168171
if !s.systemDNSSettings.ServerIP.IsValid() || len(s.systemDNSSettings.Domains) == 0 {
169172
if err := s.recordSystemDNSSettings(true); err != nil {
170-
log.Errorf("Unable to get system DNS configuration")
171173
return fmt.Errorf("recordSystemDNSSettings(): %w", err)
172174
}
173175
}
174176
localKey := getKeyWithInput(netbirdDNSStateKeyFormat, localSuffix)
175-
if s.systemDNSSettings.ServerIP.IsValid() && len(s.systemDNSSettings.Domains) != 0 {
176-
err := s.addSearchDomains(localKey, strings.Join(s.systemDNSSettings.Domains, " "), s.systemDNSSettings.ServerIP, s.systemDNSSettings.ServerPort)
177-
if err != nil {
178-
return fmt.Errorf("couldn't add local network DNS conf: %w", err)
179-
}
180-
} else {
177+
if !s.systemDNSSettings.ServerIP.IsValid() || len(s.systemDNSSettings.Domains) == 0 {
181178
log.Info("Not enabling local DNS server")
179+
return nil
180+
}
181+
182+
if err := s.addSearchDomains(
183+
localKey,
184+
strings.Join(s.systemDNSSettings.Domains, " "), s.systemDNSSettings.ServerIP, s.systemDNSSettings.ServerPort,
185+
); err != nil {
186+
return fmt.Errorf("add search domains: %w", err)
182187
}
183188

184189
return nil
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//go:build !ios
2+
3+
package dns
4+
5+
import (
6+
"context"
7+
"net/netip"
8+
"os/exec"
9+
"path/filepath"
10+
"strings"
11+
"testing"
12+
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
16+
"github.com/netbirdio/netbird/client/internal/statemanager"
17+
)
18+
19+
func TestDarwinDNSUncleanShutdownCleanup(t *testing.T) {
20+
if testing.Short() {
21+
t.Skip("skipping scutil integration test in short mode")
22+
}
23+
24+
tmpDir := t.TempDir()
25+
stateFile := filepath.Join(tmpDir, "state.json")
26+
27+
sm := statemanager.New(stateFile)
28+
sm.RegisterState(&ShutdownState{})
29+
sm.Start()
30+
defer func() {
31+
require.NoError(t, sm.Stop(context.Background()))
32+
}()
33+
34+
configurator := &systemConfigurator{
35+
createdKeys: make(map[string]struct{}),
36+
}
37+
38+
config := HostDNSConfig{
39+
ServerIP: netip.MustParseAddr("100.64.0.1"),
40+
ServerPort: 53,
41+
RouteAll: true,
42+
Domains: []DomainConfig{
43+
{Domain: "example.com", MatchOnly: true},
44+
},
45+
}
46+
47+
err := configurator.applyDNSConfig(config, sm)
48+
require.NoError(t, err)
49+
50+
require.NoError(t, sm.PersistState(context.Background()))
51+
52+
searchKey := getKeyWithInput(netbirdDNSStateKeyFormat, searchSuffix)
53+
matchKey := getKeyWithInput(netbirdDNSStateKeyFormat, matchSuffix)
54+
localKey := getKeyWithInput(netbirdDNSStateKeyFormat, localSuffix)
55+
56+
defer func() {
57+
for _, key := range []string{searchKey, matchKey, localKey} {
58+
_ = removeTestDNSKey(key)
59+
}
60+
}()
61+
62+
for _, key := range []string{searchKey, matchKey, localKey} {
63+
exists, err := checkDNSKeyExists(key)
64+
require.NoError(t, err)
65+
if exists {
66+
t.Logf("Key %s exists before cleanup", key)
67+
}
68+
}
69+
70+
sm2 := statemanager.New(stateFile)
71+
sm2.RegisterState(&ShutdownState{})
72+
err = sm2.LoadState(&ShutdownState{})
73+
require.NoError(t, err)
74+
75+
state := sm2.GetState(&ShutdownState{})
76+
if state == nil {
77+
t.Skip("State not saved, skipping cleanup test")
78+
}
79+
80+
shutdownState, ok := state.(*ShutdownState)
81+
require.True(t, ok)
82+
83+
err = shutdownState.Cleanup()
84+
require.NoError(t, err)
85+
86+
for _, key := range []string{searchKey, matchKey, localKey} {
87+
exists, err := checkDNSKeyExists(key)
88+
require.NoError(t, err)
89+
assert.False(t, exists, "Key %s should NOT exist after cleanup", key)
90+
}
91+
}
92+
93+
func checkDNSKeyExists(key string) (bool, error) {
94+
cmd := exec.Command(scutilPath)
95+
cmd.Stdin = strings.NewReader("show " + key + "\nquit\n")
96+
output, err := cmd.CombinedOutput()
97+
if err != nil {
98+
if strings.Contains(string(output), "No such key") {
99+
return false, nil
100+
}
101+
return false, err
102+
}
103+
return !strings.Contains(string(output), "No such key"), nil
104+
}
105+
106+
func removeTestDNSKey(key string) error {
107+
cmd := exec.Command(scutilPath)
108+
cmd.Stdin = strings.NewReader("remove " + key + "\nquit\n")
109+
_, err := cmd.CombinedOutput()
110+
return err
111+
}

client/internal/dns/host_windows.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,7 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager
179179
log.Infof("removed %s as main DNS forwarder for this peer", config.ServerIP)
180180
}
181181

182-
if err := stateManager.UpdateState(&ShutdownState{
183-
Guid: r.guid,
184-
GPO: r.gpo,
185-
NRPTEntryCount: r.nrptEntryCount,
186-
}); err != nil {
187-
log.Errorf("failed to update shutdown state: %s", err)
188-
}
182+
r.updateState(stateManager)
189183

190184
var searchDomains, matchDomains []string
191185
for _, dConf := range config.Domains {
@@ -212,13 +206,7 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager
212206
r.nrptEntryCount = 0
213207
}
214208

215-
if err := stateManager.UpdateState(&ShutdownState{
216-
Guid: r.guid,
217-
GPO: r.gpo,
218-
NRPTEntryCount: r.nrptEntryCount,
219-
}); err != nil {
220-
log.Errorf("failed to update shutdown state: %s", err)
221-
}
209+
r.updateState(stateManager)
222210

223211
if err := r.updateSearchDomains(searchDomains); err != nil {
224212
return fmt.Errorf("update search domains: %w", err)
@@ -229,6 +217,16 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager
229217
return nil
230218
}
231219

220+
func (r *registryConfigurator) updateState(stateManager *statemanager.Manager) {
221+
if err := stateManager.UpdateState(&ShutdownState{
222+
Guid: r.guid,
223+
GPO: r.gpo,
224+
NRPTEntryCount: r.nrptEntryCount,
225+
}); err != nil {
226+
log.Errorf("failed to update shutdown state: %s", err)
227+
}
228+
}
229+
232230
func (r *registryConfigurator) addDNSSetupForAll(ip netip.Addr) error {
233231
if err := r.setInterfaceRegistryKeyStringValue(interfaceConfigNameServerKey, ip.String()); err != nil {
234232
return fmt.Errorf("adding dns setup for all failed: %w", err)

client/internal/dns/unclean_shutdown_darwin.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
)
88

99
type ShutdownState struct {
10+
CreatedKeys []string
1011
}
1112

1213
func (s *ShutdownState) Name() string {
@@ -19,6 +20,10 @@ func (s *ShutdownState) Cleanup() error {
1920
return fmt.Errorf("create host manager: %w", err)
2021
}
2122

23+
for _, key := range s.CreatedKeys {
24+
manager.createdKeys[key] = struct{}{}
25+
}
26+
2227
if err := manager.restoreUncleanShutdownDNS(); err != nil {
2328
return fmt.Errorf("restore unclean shutdown dns: %w", err)
2429
}

0 commit comments

Comments
 (0)