[client] Use GPO DNS Policy Config to configure DNS if present (#3319)

This commit is contained in:
Viktor Liu 2025-02-13 18:17:18 +01:00 committed by GitHub
parent a930c2aecf
commit c4a6dafd27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 155 additions and 102 deletions

View File

@ -1,35 +1,51 @@
package dns package dns
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"strings" "strings"
"syscall"
"github.com/hashicorp/go-multierror"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
nberrors "github.com/netbirdio/netbird/client/errors"
"github.com/netbirdio/netbird/client/internal/statemanager" "github.com/netbirdio/netbird/client/internal/statemanager"
) )
var (
userenv = syscall.NewLazyDLL("userenv.dll")
// https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-refreshpolicyex
refreshPolicyExFn = userenv.NewProc("RefreshPolicyEx")
)
const ( const (
dnsPolicyConfigMatchPath = `SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig\NetBird-Match` dnsPolicyConfigMatchPath = `SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig\NetBird-Match`
gpoDnsPolicyRoot = `SOFTWARE\Policies\Microsoft\Windows NT\DNSClient`
gpoDnsPolicyConfigMatchPath = gpoDnsPolicyRoot + `\DnsPolicyConfig\NetBird-Match`
dnsPolicyConfigVersionKey = "Version" dnsPolicyConfigVersionKey = "Version"
dnsPolicyConfigVersionValue = 2 dnsPolicyConfigVersionValue = 2
dnsPolicyConfigNameKey = "Name" dnsPolicyConfigNameKey = "Name"
dnsPolicyConfigGenericDNSServersKey = "GenericDNSServers" dnsPolicyConfigGenericDNSServersKey = "GenericDNSServers"
dnsPolicyConfigConfigOptionsKey = "ConfigOptions" dnsPolicyConfigConfigOptionsKey = "ConfigOptions"
dnsPolicyConfigConfigOptionsValue = 0x8 dnsPolicyConfigConfigOptionsValue = 0x8
)
const (
interfaceConfigPath = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces` interfaceConfigPath = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces`
interfaceConfigNameServerKey = "NameServer" interfaceConfigNameServerKey = "NameServer"
interfaceConfigSearchListKey = "SearchList" interfaceConfigSearchListKey = "SearchList"
// RP_FORCE: Reapply all policies even if no policy change was detected
rpForce = 0x1
) )
type registryConfigurator struct { type registryConfigurator struct {
guid string guid string
routingAll bool routingAll bool
gpo bool
} }
func newHostManager(wgInterface WGIface) (*registryConfigurator, error) { func newHostManager(wgInterface WGIface) (*registryConfigurator, error) {
@ -37,12 +53,20 @@ func newHostManager(wgInterface WGIface) (*registryConfigurator, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newHostManagerWithGuid(guid)
}
func newHostManagerWithGuid(guid string) (*registryConfigurator, error) { var useGPO bool
k, err := registry.OpenKey(registry.LOCAL_MACHINE, gpoDnsPolicyRoot, registry.QUERY_VALUE)
if err != nil {
log.Debugf("failed to open GPO DNS policy root: %v", err)
} else {
closer(k)
useGPO = true
log.Infof("detected GPO DNS policy configuration, using policy store")
}
return &registryConfigurator{ return &registryConfigurator{
guid: guid, guid: guid,
gpo: useGPO,
}, nil }, nil
} }
@ -51,30 +75,23 @@ func (r *registryConfigurator) supportCustomPort() bool {
} }
func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error { func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error {
var err error
if config.RouteAll { if config.RouteAll {
err = r.addDNSSetupForAll(config.ServerIP) if err := r.addDNSSetupForAll(config.ServerIP); err != nil {
if err != nil {
return fmt.Errorf("add dns setup: %w", err) return fmt.Errorf("add dns setup: %w", err)
} }
} else if r.routingAll { } else if r.routingAll {
err = r.deleteInterfaceRegistryKeyProperty(interfaceConfigNameServerKey) if err := r.deleteInterfaceRegistryKeyProperty(interfaceConfigNameServerKey); err != nil {
if err != nil {
return fmt.Errorf("delete interface registry key property: %w", err) return fmt.Errorf("delete interface registry key property: %w", err)
} }
r.routingAll = false r.routingAll = false
log.Infof("removed %s as main DNS forwarder for this peer", config.ServerIP) log.Infof("removed %s as main DNS forwarder for this peer", config.ServerIP)
} }
if err := stateManager.UpdateState(&ShutdownState{Guid: r.guid}); err != nil { if err := stateManager.UpdateState(&ShutdownState{Guid: r.guid, GPO: r.gpo}); err != nil {
log.Errorf("failed to update shutdown state: %s", err) log.Errorf("failed to update shutdown state: %s", err)
} }
var ( var searchDomains, matchDomains []string
searchDomains []string
matchDomains []string
)
for _, dConf := range config.Domains { for _, dConf := range config.Domains {
if dConf.Disabled { if dConf.Disabled {
continue continue
@ -86,16 +103,16 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager
} }
if len(matchDomains) != 0 { if len(matchDomains) != 0 {
err = r.addDNSMatchPolicy(matchDomains, config.ServerIP) if err := r.addDNSMatchPolicy(matchDomains, config.ServerIP); err != nil {
return fmt.Errorf("add dns match policy: %w", err)
}
} else { } else {
err = removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath) if err := r.removeDNSMatchPolicies(); err != nil {
} return fmt.Errorf("remove dns match policies: %w", err)
if err != nil { }
return fmt.Errorf("add dns match policy: %w", err)
} }
err = r.updateSearchDomains(searchDomains) if err := r.updateSearchDomains(searchDomains); err != nil {
if err != nil {
return fmt.Errorf("update search domains: %w", err) return fmt.Errorf("update search domains: %w", err)
} }
@ -103,9 +120,8 @@ func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig, stateManager
} }
func (r *registryConfigurator) addDNSSetupForAll(ip string) error { func (r *registryConfigurator) addDNSSetupForAll(ip string) error {
err := r.setInterfaceRegistryKeyStringValue(interfaceConfigNameServerKey, ip) if err := r.setInterfaceRegistryKeyStringValue(interfaceConfigNameServerKey, ip); err != nil {
if err != nil { return fmt.Errorf("adding dns setup for all failed: %w", err)
return fmt.Errorf("adding dns setup for all failed with error: %w", err)
} }
r.routingAll = true r.routingAll = true
log.Infof("configured %s:53 as main DNS forwarder for this peer", ip) log.Infof("configured %s:53 as main DNS forwarder for this peer", ip)
@ -113,64 +129,54 @@ func (r *registryConfigurator) addDNSSetupForAll(ip string) error {
} }
func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) error { func (r *registryConfigurator) addDNSMatchPolicy(domains []string, ip string) error {
_, err := registry.OpenKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath, registry.QUERY_VALUE) // if the gpo key is present, we need to put our DNS settings there, otherwise our config might be ignored
if err == nil { // see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnrpt/8cc31cb9-20cb-4140-9e85-3e08703b4745
err = registry.DeleteKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath) policyPath := dnsPolicyConfigMatchPath
if err != nil { if r.gpo {
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %w", dnsPolicyConfigMatchPath, err) policyPath = gpoDnsPolicyConfigMatchPath
}
if err := removeRegistryKeyFromDNSPolicyConfig(policyPath); err != nil {
return fmt.Errorf("remove existing dns policy: %w", err)
}
regKey, _, err := registry.CreateKey(registry.LOCAL_MACHINE, policyPath, registry.SET_VALUE)
if err != nil {
return fmt.Errorf("create registry key HKEY_LOCAL_MACHINE\\%s: %w", policyPath, err)
}
defer closer(regKey)
if err := regKey.SetDWordValue(dnsPolicyConfigVersionKey, dnsPolicyConfigVersionValue); err != nil {
return fmt.Errorf("set %s: %w", dnsPolicyConfigVersionKey, err)
}
if err := regKey.SetStringsValue(dnsPolicyConfigNameKey, domains); err != nil {
return fmt.Errorf("set %s: %w", dnsPolicyConfigNameKey, err)
}
if err := regKey.SetStringValue(dnsPolicyConfigGenericDNSServersKey, ip); err != nil {
return fmt.Errorf("set %s: %w", dnsPolicyConfigGenericDNSServersKey, err)
}
if err := regKey.SetDWordValue(dnsPolicyConfigConfigOptionsKey, dnsPolicyConfigConfigOptionsValue); err != nil {
return fmt.Errorf("set %s: %w", dnsPolicyConfigConfigOptionsKey, err)
}
if r.gpo {
if err := refreshGroupPolicy(); err != nil {
log.Warnf("failed to refresh group policy: %v", err)
} }
} }
regKey, _, err := registry.CreateKey(registry.LOCAL_MACHINE, dnsPolicyConfigMatchPath, registry.SET_VALUE) log.Infof("added %d match domains. Domain list: %s", len(domains), domains)
if err != nil {
return fmt.Errorf("unable to create registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %w", dnsPolicyConfigMatchPath, err)
}
err = regKey.SetDWordValue(dnsPolicyConfigVersionKey, dnsPolicyConfigVersionValue)
if err != nil {
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigVersionKey, err)
}
err = regKey.SetStringsValue(dnsPolicyConfigNameKey, domains)
if err != nil {
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigNameKey, err)
}
err = regKey.SetStringValue(dnsPolicyConfigGenericDNSServersKey, ip)
if err != nil {
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigGenericDNSServersKey, err)
}
err = regKey.SetDWordValue(dnsPolicyConfigConfigOptionsKey, dnsPolicyConfigConfigOptionsValue)
if err != nil {
return fmt.Errorf("unable to set registry value for %s, error: %w", dnsPolicyConfigConfigOptionsKey, err)
}
log.Infof("added %d match domains to the state. Domain list: %s", len(domains), domains)
return nil
}
func (r *registryConfigurator) restoreHostDNS() error {
if err := removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath); err != nil {
log.Errorf("remove registry key from dns policy config: %s", err)
}
if err := r.deleteInterfaceRegistryKeyProperty(interfaceConfigSearchListKey); err != nil {
return fmt.Errorf("remove interface registry key: %w", err)
}
return nil return nil
} }
func (r *registryConfigurator) updateSearchDomains(domains []string) error { func (r *registryConfigurator) updateSearchDomains(domains []string) error {
err := r.setInterfaceRegistryKeyStringValue(interfaceConfigSearchListKey, strings.Join(domains, ",")) if err := r.setInterfaceRegistryKeyStringValue(interfaceConfigSearchListKey, strings.Join(domains, ",")); err != nil {
if err != nil { return fmt.Errorf("update search domains: %w", err)
return fmt.Errorf("adding search domain failed with error: %w", err)
} }
log.Infof("updated search domains: %s", domains)
log.Infof("updated the search domains in the registry with %d domains. Domain list: %s", len(domains), domains)
return nil return nil
} }
@ -181,11 +187,9 @@ func (r *registryConfigurator) setInterfaceRegistryKeyStringValue(key, value str
} }
defer closer(regKey) defer closer(regKey)
err = regKey.SetStringValue(key, value) if err := regKey.SetStringValue(key, value); err != nil {
if err != nil { return fmt.Errorf("set key %s=%s: %w", key, value, err)
return fmt.Errorf("applying key %s with value \"%s\" for interface failed with error: %w", key, value, err)
} }
return nil return nil
} }
@ -196,43 +200,91 @@ func (r *registryConfigurator) deleteInterfaceRegistryKeyProperty(propertyKey st
} }
defer closer(regKey) defer closer(regKey)
err = regKey.DeleteValue(propertyKey) if err := regKey.DeleteValue(propertyKey); err != nil {
if err != nil { return fmt.Errorf("delete registry key %s: %w", propertyKey, err)
return fmt.Errorf("deleting registry key %s for interface failed with error: %w", propertyKey, err)
} }
return nil return nil
} }
func (r *registryConfigurator) getInterfaceRegistryKey() (registry.Key, error) { func (r *registryConfigurator) getInterfaceRegistryKey() (registry.Key, error) {
var regKey registry.Key
regKeyPath := interfaceConfigPath + "\\" + r.guid regKeyPath := interfaceConfigPath + "\\" + r.guid
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.SET_VALUE) regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.SET_VALUE)
if err != nil { if err != nil {
return regKey, fmt.Errorf("unable to open the interface registry key, key: HKEY_LOCAL_MACHINE\\%s, error: %w", regKeyPath, err) return regKey, fmt.Errorf("open HKEY_LOCAL_MACHINE\\%s: %w", regKeyPath, err)
} }
return regKey, nil return regKey, nil
} }
func (r *registryConfigurator) restoreUncleanShutdownDNS() error { func (r *registryConfigurator) restoreHostDNS() error {
if err := r.restoreHostDNS(); err != nil { if err := r.removeDNSMatchPolicies(); err != nil {
return fmt.Errorf("restoring dns via registry: %w", err) log.Errorf("remove dns match policies: %s", err)
} }
if err := r.deleteInterfaceRegistryKeyProperty(interfaceConfigSearchListKey); err != nil {
return fmt.Errorf("remove interface registry key: %w", err)
}
return nil return nil
} }
func (r *registryConfigurator) removeDNSMatchPolicies() error {
var merr *multierror.Error
if err := removeRegistryKeyFromDNSPolicyConfig(dnsPolicyConfigMatchPath); err != nil {
merr = multierror.Append(merr, fmt.Errorf("remove local registry key: %w", err))
}
if err := removeRegistryKeyFromDNSPolicyConfig(gpoDnsPolicyConfigMatchPath); err != nil {
merr = multierror.Append(merr, fmt.Errorf("remove GPO registry key: %w", err))
}
if err := refreshGroupPolicy(); err != nil {
merr = multierror.Append(merr, fmt.Errorf("refresh group policy: %w", err))
}
return nberrors.FormatErrorOrNil(merr)
}
func (r *registryConfigurator) restoreUncleanShutdownDNS() error {
return r.restoreHostDNS()
}
func removeRegistryKeyFromDNSPolicyConfig(regKeyPath string) error { func removeRegistryKeyFromDNSPolicyConfig(regKeyPath string) error {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.QUERY_VALUE) k, err := registry.OpenKey(registry.LOCAL_MACHINE, regKeyPath, registry.QUERY_VALUE)
if err == nil { if err != nil {
defer closer(k) log.Debugf("failed to open HKEY_LOCAL_MACHINE\\%s: %v", regKeyPath, err)
err = registry.DeleteKey(registry.LOCAL_MACHINE, regKeyPath) return nil
if err != nil {
return fmt.Errorf("unable to remove existing key from registry, key: HKEY_LOCAL_MACHINE\\%s, error: %w", regKeyPath, err)
}
} }
closer(k)
if err := registry.DeleteKey(registry.LOCAL_MACHINE, regKeyPath); err != nil {
return fmt.Errorf("delete HKEY_LOCAL_MACHINE\\%s: %w", regKeyPath, err)
}
return nil
}
func refreshGroupPolicy() error {
// refreshPolicyExFn.Call() panics if the func is not found
defer func() {
if r := recover(); r != nil {
log.Errorf("Recovered from panic: %v", r)
}
}()
ret, _, err := refreshPolicyExFn.Call(
// bMachine = TRUE (computer policy)
uintptr(1),
// dwOptions = RP_FORCE
uintptr(rpForce),
)
if ret == 0 {
if err != nil && !errors.Is(err, syscall.Errno(0)) {
return fmt.Errorf("RefreshPolicyEx failed: %w", err)
}
return fmt.Errorf("RefreshPolicyEx failed")
}
return nil return nil
} }

View File

@ -6,6 +6,7 @@ import (
type ShutdownState struct { type ShutdownState struct {
Guid string Guid string
GPO bool
} }
func (s *ShutdownState) Name() string { func (s *ShutdownState) Name() string {
@ -13,9 +14,9 @@ func (s *ShutdownState) Name() string {
} }
func (s *ShutdownState) Cleanup() error { func (s *ShutdownState) Cleanup() error {
manager, err := newHostManagerWithGuid(s.Guid) manager := &registryConfigurator{
if err != nil { guid: s.Guid,
return fmt.Errorf("create host manager: %w", err) gpo: s.GPO,
} }
if err := manager.restoreUncleanShutdownDNS(); err != nil { if err := manager.restoreUncleanShutdownDNS(); err != nil {