Skip to content

Commit c4a6daf

Browse files
authored
[client] Use GPO DNS Policy Config to configure DNS if present (#3319)
1 parent a930c2a commit c4a6daf

File tree

2 files changed

+140
-87
lines changed

2 files changed

+140
-87
lines changed

client/internal/dns/host_windows.go

+136-84
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,72 @@
11
package dns
22

33
import (
4+
"errors"
45
"fmt"
56
"io"
67
"strings"
8+
"syscall"
79

10+
"github.com/hashicorp/go-multierror"
811
log "github.com/sirupsen/logrus"
912
"golang.org/x/sys/windows/registry"
1013

14+
nberrors "github.com/netbirdio/netbird/client/errors"
1115
"github.com/netbirdio/netbird/client/internal/statemanager"
1216
)
1317

18+
var (
19+
userenv = syscall.NewLazyDLL("userenv.dll")
20+
21+
// https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-refreshpolicyex
22+
refreshPolicyExFn = userenv.NewProc("RefreshPolicyEx")
23+
)
24+
1425
const (
15-
dnsPolicyConfigMatchPath = `SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig\NetBird-Match`
26+
dnsPolicyConfigMatchPath = `SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig\NetBird-Match`
27+
gpoDnsPolicyRoot = `SOFTWARE\Policies\Microsoft\Windows NT\DNSClient`
28+
gpoDnsPolicyConfigMatchPath = gpoDnsPolicyRoot + `\DnsPolicyConfig\NetBird-Match`
29+
1630
dnsPolicyConfigVersionKey = "Version"
1731
dnsPolicyConfigVersionValue = 2
1832
dnsPolicyConfigNameKey = "Name"
1933
dnsPolicyConfigGenericDNSServersKey = "GenericDNSServers"
2034
dnsPolicyConfigConfigOptionsKey = "ConfigOptions"
2135
dnsPolicyConfigConfigOptionsValue = 0x8
22-
)
2336

24-
const (
2537
interfaceConfigPath = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces`
2638
interfaceConfigNameServerKey = "NameServer"
2739
interfaceConfigSearchListKey = "SearchList"
40+
41+
// RP_FORCE: Reapply all policies even if no policy change was detected
42+
rpForce = 0x1
2843
)
2944

3045
type registryConfigurator struct {
3146
guid string
3247
routingAll bool
48+
gpo bool
3349
}
3450

3551
func newHostManager(wgInterface WGIface) (*registryConfigurator, error) {
3652
guid, err := wgInterface.GetInterfaceGUIDString()
3753
if err != nil {
3854
return nil, err
3955
}
40-
return newHostManagerWithGuid(guid)
41-
}
4256

43-
func newHostManagerWithGuid(guid string) (*registryConfigurator, error) {
57+
var useGPO bool
58+
k, err := registry.OpenKey(registry.LOCAL_MACHINE, gpoDnsPolicyRoot, registry.QUERY_VALUE)
59+
if err != nil {
60+
log.Debugf("failed to open GPO DNS policy root: %v", err)
61+
} else {
62+
closer(k)
63+
useGPO = true
64+
log.Infof("detected GPO DNS policy configuration, using policy store")
65+
}
66+
4467
return &registryConfigurator{
4568
guid: guid,
69+
gpo: useGPO,
4670
}, nil
4771
}
4872

@@ -51,30 +75,23 @@ func (r *registryConfigurator) supportCustomPort() bool {
5175
}
5276

5377
func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error {
54-
var err error
5578
if config.RouteAll {
56-
err = r.addDNSSetupForAll(config.ServerIP)
57-
if err != nil {
79+
if err := r.addDNSSetupForAll(config.ServerIP); err != nil {
5880
return fmt.Errorf("add dns setup: %w", err)
5981
}
6082
} else if r.routingAll {
61-
err = r.deleteInterfaceRegistryKeyProperty(interfaceConfigNameServerKey)
62-
if err != nil {
83+
if err := r.deleteInterfaceRegistryKeyProperty(interfaceConfigNameServerKey); err != nil {
6384
return fmt.Errorf("delete interface registry key property: %w", err)
6485
}
6586
r.routingAll = false
6687
log.Infof("removed %s as main DNS forwarder for this peer", config.ServerIP)
6788
}
6889

69-
if err := stateManager.UpdateState(&ShutdownState{Guid: r.guid}); err != nil {
90+
if err := stateManager.UpdateState(&ShutdownState{Guid: r.guid, GPO: r.gpo}); err != nil {
7091
log.Errorf("failed to update shutdown state: %s", err)
7192
}
7293

73-
var (
74-
searchDomains []string
75-
matchDomains []string
76-
)
77-
94+
var searchDomains, matchDomains []string
7895
for _, dConf := range config.Domains {
7996
if dConf.Disabled {
8097
continue
@@ -86,91 +103,80 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager
86103
}
87104

88105
if len(matchDomains) != 0 {
89-
err = r.addDNSMatchPolicy(matchDomains, config.ServerIP)
106+
if err := r.addDNSMatchPolicy(matchDomains, config.ServerIP); err != nil {
107+
return fmt.Errorf("add dns match policy: %w", err)
108+
}
90109
} else {
91-
err = removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath)
92-
}
93-
if err != nil {
94-
return fmt.Errorf("add dns match policy: %w", err)
110+
if err := r.removeDNSMatchPolicies(); err != nil {
111+
return fmt.Errorf("remove dns match policies: %w", err)
112+
}
95113
}
96114

97-
err = r.updateSearchDomains(searchDomains)
98-
if err != nil {
115+
if err := r.updateSearchDomains(searchDomains); err != nil {
99116
return fmt.Errorf("update search domains: %w", err)
100117
}
101118

102119
return nil
103120
}
104121

105122
func (r *registryConfigurator) addDNSSetupForAll(ip string) error {
106-
err := r.setInterfaceRegistryKeyStringValue(interfaceConfigNameServerKey, ip)
107-
if err != nil {
108-
return fmt.Errorf("adding dns setup for all failed with error: %w", err)
123+
if err := r.setInterfaceRegistryKeyStringValue(interfaceConfigNameServerKey, ip); err != nil {
124+
return fmt.Errorf("adding dns setup for all failed: %w", err)
109125
}
110126
r.routingAll = true
111127
log.Infof("configured %s:53 as main DNS forwarder for this peer", ip)
112128
return nil
113129
}
114130

115131
func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) error {
116-
_, err := registry.OpenKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath, registry.QUERY_VALUE)
117-
if err == nil {
118-
err = registry.DeleteKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath)
119-
if err != nil {
120-
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %w", dnsPolicyConfigMatchPath, err)
121-
}
132+
// if the gpo key is present, we need to put our DNS settings there, otherwise our config might be ignored
133+
// see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnrpt/8cc31cb9-20cb-4140-9e85-3e08703b4745
134+
policyPath := dnsPolicyConfigMatchPath
135+
if r.gpo {
136+
policyPath = gpoDnsPolicyConfigMatchPath
122137
}
123138

124-
regKey, _, err := registry.CreateKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath, registry.SET_VALUE)
125-
if err != nil {
126-
return fmt.Errorf("unable to create registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %w", dnsPolicyConfigMatchPath, err)
139+
if err := removeRegistryKeyFromDNSPolicyConfig(policyPath); err != nil {
140+
return fmt.Errorf("remove existing dns policy: %w", err)
127141
}
128142

129-
err = regKey.SetDWordValue(dnsPolicyConfigVersionKey, dnsPolicyConfigVersionValue)
143+
regKey, _, err := registry.CreateKey(registry.LOCAL_MACHINE, policyPath, registry.SET_VALUE)
130144
if err != nil {
131-
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigVersionKey, err)
145+
return fmt.Errorf("create registry key HKEY_LOCAL_MACHINE\\%s: %w", policyPath, err)
132146
}
147+
defer closer(regKey)
133148

134-
err = regKey.SetStringsValue(dnsPolicyConfigNameKey, domains)
135-
if err != nil {
136-
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigNameKey, err)
149+
if err := regKey.SetDWordValue(dnsPolicyConfigVersionKey, dnsPolicyConfigVersionValue); err != nil {
150+
return fmt.Errorf("set %s: %w", dnsPolicyConfigVersionKey, err)
137151
}
138152

139-
err = regKey.SetStringValue(dnsPolicyConfigGenericDNSServersKey, ip)
140-
if err != nil {
141-
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigGenericDNSServersKey, err)
153+
if err := regKey.SetStringsValue(dnsPolicyConfigNameKey, domains); err != nil {
154+
return fmt.Errorf("set %s: %w", dnsPolicyConfigNameKey, err)
142155
}
143156

144-
err = regKey.SetDWordValue(dnsPolicyConfigConfigOptionsKey, dnsPolicyConfigConfigOptionsValue)
145-
if err != nil {
146-
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigConfigOptionsKey, err)
157+
if err := regKey.SetStringValue(dnsPolicyConfigGenericDNSServersKey, ip); err != nil {
158+
return fmt.Errorf("set %s: %w", dnsPolicyConfigGenericDNSServersKey, err)
147159
}
148160

149-
log.Infof("added %d match domains to the state. Domain list: %s", len(domains), domains)
150-
151-
return nil
152-
}
153-
154-
func (r *registryConfigurator) restoreHostDNS() error {
155-
if err := removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath); err != nil {
156-
log.Errorf("remove registry key from dns policy config: %s", err)
161+
if err := regKey.SetDWordValue(dnsPolicyConfigConfigOptionsKey, dnsPolicyConfigConfigOptionsValue); err != nil {
162+
return fmt.Errorf("set %s: %w", dnsPolicyConfigConfigOptionsKey, err)
157163
}
158164

159-
if err := r.deleteInterfaceRegistryKeyProperty(interfaceConfigSearchListKey); err != nil {
160-
return fmt.Errorf("remove interface registry key: %w", err)
165+
if r.gpo {
166+
if err := refreshGroupPolicy(); err != nil {
167+
log.Warnf("failed to refresh group policy: %v", err)
168+
}
161169
}
162170

171+
log.Infof("added %d match domains. Domain list: %s", len(domains), domains)
163172
return nil
164173
}
165174

166175
func (r *registryConfigurator) updateSearchDomains(domains []string) error {
167-
err := r.setInterfaceRegistryKeyStringValue(interfaceConfigSearchListKey, strings.Join(domains, ","))
168-
if err != nil {
169-
return fmt.Errorf("adding search domain failed with error: %w", err)
176+
if err := r.setInterfaceRegistryKeyStringValue(interfaceConfigSearchListKey, strings.Join(domains, ",")); err != nil {
177+
return fmt.Errorf("update search domains: %w", err)
170178
}
171-
172-
log.Infof("updated the search domains in the registry with %d domains. Domain list: %s", len(domains), domains)
173-
179+
log.Infof("updated search domains: %s", domains)
174180
return nil
175181
}
176182

@@ -181,11 +187,9 @@ func (r *registryConfigurator) setInterfaceRegistryKeyStringValue(key, value str
181187
}
182188
defer closer(regKey)
183189

184-
err = regKey.SetStringValue(key, value)
185-
if err != nil {
186-
return fmt.Errorf("applying key %s with value \"%s\" for interface failed with error: %w", key, value, err)
190+
if err := regKey.SetStringValue(key, value); err != nil {
191+
return fmt.Errorf("set key %s=%s: %w", key, value, err)
187192
}
188-
189193
return nil
190194
}
191195

@@ -196,43 +200,91 @@ func (r *registryConfigurator) deleteInterfaceRegistryKeyProperty(propertyKey st
196200
}
197201
defer closer(regKey)
198202

199-
err = regKey.DeleteValue(propertyKey)
200-
if err != nil {
201-
return fmt.Errorf("deleting registry key %s for interface failed with error: %w", propertyKey, err)
203+
if err := regKey.DeleteValue(propertyKey); err != nil {
204+
return fmt.Errorf("delete registry key %s: %w", propertyKey, err)
202205
}
203-
204206
return nil
205207
}
206208

207209
func (r *registryConfigurator) getInterfaceRegistryKey() (registry.Key, error) {
208-
var regKey registry.Key
209-
210210
regKeyPath := interfaceConfigPath + "\\" + r.guid
211-
212211
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.SET_VALUE)
213212
if err != nil {
214-
return regKey, fmt.Errorf("unable to open the interface registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %w", regKeyPath, err)
213+
return regKey, fmt.Errorf("open HKEY_LOCAL_MACHINE\\%s: %w", regKeyPath, err)
215214
}
216-
217215
return regKey, nil
218216
}
219217

220-
func (r *registryConfigurator) restoreUncleanShutdownDNS() error {
221-
if err := r.restoreHostDNS(); err != nil {
222-
return fmt.Errorf("restoring dns via registry: %w", err)
218+
func (r *registryConfigurator) restoreHostDNS() error {
219+
if err := r.removeDNSMatchPolicies(); err != nil {
220+
log.Errorf("remove dns match policies: %s", err)
221+
}
222+
223+
if err := r.deleteInterfaceRegistryKeyProperty(interfaceConfigSearchListKey); err != nil {
224+
return fmt.Errorf("remove interface registry key: %w", err)
223225
}
226+
224227
return nil
225228
}
226229

230+
func (r *registryConfigurator) removeDNSMatchPolicies() error {
231+
var merr *multierror.Error
232+
if err := removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath); err != nil {
233+
merr = multierror.Append(merr, fmt.Errorf("remove local registry key: %w", err))
234+
}
235+
236+
if err := removeRegistryKeyFromDNSPolicyConfig(gpoDnsPolicyConfigMatchPath); err != nil {
237+
merr = multierror.Append(merr, fmt.Errorf("remove GPO registry key: %w", err))
238+
}
239+
240+
if err := refreshGroupPolicy(); err != nil {
241+
merr = multierror.Append(merr, fmt.Errorf("refresh group policy: %w", err))
242+
}
243+
244+
return nberrors.FormatErrorOrNil(merr)
245+
}
246+
247+
func (r *registryConfigurator) restoreUncleanShutdownDNS() error {
248+
return r.restoreHostDNS()
249+
}
250+
227251
func removeRegistryKeyFromDNSPolicyConfig(regKeyPath string) error {
228252
k, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.QUERY_VALUE)
229-
if err == nil {
230-
defer closer(k)
231-
err = registry.DeleteKey(registry.LOCAL_MACHINE, regKeyPath)
232-
if err != nil {
233-
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %w", regKeyPath, err)
253+
if err != nil {
254+
log.Debugf("failed to open HKEY_LOCAL_MACHINE\\%s: %v", regKeyPath, err)
255+
return nil
256+
}
257+
258+
closer(k)
259+
if err := registry.DeleteKey(registry.LOCAL_MACHINE, regKeyPath); err != nil {
260+
return fmt.Errorf("delete HKEY_LOCAL_MACHINE\\%s: %w", regKeyPath, err)
261+
}
262+
263+
return nil
264+
}
265+
266+
func refreshGroupPolicy() error {
267+
// refreshPolicyExFn.Call() panics if the func is not found
268+
defer func() {
269+
if r := recover(); r != nil {
270+
log.Errorf("Recovered from panic: %v", r)
234271
}
272+
}()
273+
274+
ret, _, err := refreshPolicyExFn.Call(
275+
// bMachine = TRUE (computer policy)
276+
uintptr(1),
277+
// dwOptions = RP_FORCE
278+
uintptr(rpForce),
279+
)
280+
281+
if ret == 0 {
282+
if err != nil && !errors.Is(err, syscall.Errno(0)) {
283+
return fmt.Errorf("RefreshPolicyEx failed: %w", err)
284+
}
285+
return fmt.Errorf("RefreshPolicyEx failed")
235286
}
287+
236288
return nil
237289
}
238290

client/internal/dns/unclean_shutdown_windows.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@ import (
66

77
type ShutdownState struct {
88
Guid string
9+
GPO bool
910
}
1011

1112
func (s *ShutdownState) Name() string {
1213
return "dns_state"
1314
}
1415

1516
func (s *ShutdownState) Cleanup() error {
16-
manager, err := newHostManagerWithGuid(s.Guid)
17-
if err != nil {
18-
return fmt.Errorf("create host manager: %w", err)
17+
manager := &registryConfigurator{
18+
guid: s.Guid,
19+
gpo: s.GPO,
1920
}
2021

2122
if err := manager.restoreUncleanShutdownDNS(); err != nil {

0 commit comments

Comments
 (0)