-
-
Notifications
You must be signed in to change notification settings - Fork 552
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/dns client configuration (#563)
Added host configurators for Linux, Windows, and macOS. The host configurator will update the peer system configuration directing DNS queries according to its capabilities. Some Linux distributions don't support split (match) DNS or custom ports, and that will be reported to our management system in another PR
- Loading branch information
Showing
23 changed files
with
1,552 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package dns | ||
|
||
import ( | ||
"context" | ||
"github.com/godbus/dbus/v5" | ||
log "github.com/sirupsen/logrus" | ||
"time" | ||
) | ||
|
||
const dbusDefaultFlag = 0 | ||
|
||
func isDbusListenerRunning(dest string, path dbus.ObjectPath) bool { | ||
obj, closeConn, err := getDbusObject(dest, path) | ||
if err != nil { | ||
return false | ||
} | ||
defer closeConn() | ||
|
||
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) | ||
defer cancel() | ||
|
||
err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0).Store() | ||
return err == nil | ||
} | ||
|
||
func getDbusObject(dest string, path dbus.ObjectPath) (dbus.BusObject, func(), error) { | ||
conn, err := dbus.SystemBus() | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
obj := conn.Object(dest, path) | ||
|
||
closeFunc := func() { | ||
closeErr := conn.Close() | ||
if closeErr != nil { | ||
log.Warnf("got an error closing dbus connection, err: %s", closeErr) | ||
} | ||
} | ||
|
||
return obj, closeFunc, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package dns | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
log "github.com/sirupsen/logrus" | ||
"os" | ||
) | ||
|
||
const ( | ||
fileGeneratedResolvConfContentHeader = "# Generated by NetBird" | ||
fileGeneratedResolvConfSearchBeginContent = "search " | ||
fileGeneratedResolvConfContentFormat = fileGeneratedResolvConfContentHeader + | ||
"\n# If needed you can restore the original file by copying back %s\n\nnameserver %s\n" + | ||
fileGeneratedResolvConfSearchBeginContent + "%s\n" | ||
) | ||
const ( | ||
fileDefaultResolvConfBackupLocation = defaultResolvConfPath + ".original.netbird" | ||
fileMaxLineCharsLimit = 256 | ||
fileMaxNumberOfSearchDomains = 6 | ||
) | ||
|
||
var fileSearchLineBeginCharCount = len(fileGeneratedResolvConfSearchBeginContent) | ||
|
||
type fileConfigurator struct { | ||
originalPerms os.FileMode | ||
} | ||
|
||
func newFileConfigurator() (hostManager, error) { | ||
return &fileConfigurator{}, nil | ||
} | ||
|
||
func (f *fileConfigurator) applyDNSConfig(config hostDNSConfig) error { | ||
backupFileExist := false | ||
_, err := os.Stat(fileDefaultResolvConfBackupLocation) | ||
if err == nil { | ||
backupFileExist = true | ||
} | ||
|
||
if !config.routeAll { | ||
if backupFileExist { | ||
err = f.restore() | ||
if err != nil { | ||
return fmt.Errorf("unable to configure DNS for this peer using file manager without a Primary nameserver group. Restoring the original file return err: %s", err) | ||
} | ||
} | ||
return fmt.Errorf("unable to configure DNS for this peer using file manager without a Primary nameserver group") | ||
} | ||
managerType, err := getOSDNSManagerType() | ||
if err != nil { | ||
return err | ||
} | ||
switch managerType { | ||
case fileManager, netbirdManager: | ||
if !backupFileExist { | ||
err = f.backup() | ||
if err != nil { | ||
return fmt.Errorf("unable to backup the resolv.conf file") | ||
} | ||
} | ||
default: | ||
// todo improve this and maybe restart DNS manager from scratch | ||
return fmt.Errorf("something happened and file manager is not your prefered host dns configurator, restart the agent") | ||
} | ||
|
||
var searchDomains string | ||
appendedDomains := 0 | ||
for _, dConf := range config.domains { | ||
if dConf.matchOnly { | ||
continue | ||
} | ||
if appendedDomains >= fileMaxNumberOfSearchDomains { | ||
// lets log all skipped domains | ||
log.Infof("already appended %d domains to search list. Skipping append of %s domain", fileMaxNumberOfSearchDomains, dConf.domain) | ||
continue | ||
} | ||
if fileSearchLineBeginCharCount+len(searchDomains) > fileMaxLineCharsLimit { | ||
// lets log all skipped domains | ||
log.Infof("search list line is larger than %d characters. Skipping append of %s domain", fileMaxLineCharsLimit, dConf.domain) | ||
continue | ||
} | ||
|
||
searchDomains += " " + dConf.domain | ||
appendedDomains++ | ||
} | ||
content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains) | ||
err = writeDNSConfig(content, defaultResolvConfPath, f.originalPerms) | ||
if err != nil { | ||
err = f.restore() | ||
if err != nil { | ||
log.Errorf("attempt to restore default file failed with error: %s", err) | ||
} | ||
return err | ||
} | ||
log.Infof("created a NetBird managed %s file with your DNS settings", defaultResolvConfPath) | ||
return nil | ||
} | ||
|
||
func (f *fileConfigurator) restoreHostDNS() error { | ||
return f.restore() | ||
} | ||
|
||
func (f *fileConfigurator) backup() error { | ||
stats, err := os.Stat(defaultResolvConfPath) | ||
if err != nil { | ||
return fmt.Errorf("got an error while checking stats for %s file. Error: %s", defaultResolvConfPath, err) | ||
} | ||
|
||
f.originalPerms = stats.Mode() | ||
|
||
err = copyFile(defaultResolvConfPath, fileDefaultResolvConfBackupLocation) | ||
if err != nil { | ||
return fmt.Errorf("got error while backing up the %s file. Error: %s", defaultResolvConfPath, err) | ||
} | ||
return nil | ||
} | ||
|
||
func (f *fileConfigurator) restore() error { | ||
err := copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath) | ||
if err != nil { | ||
return fmt.Errorf("got error while restoring the %s file from %s. Error: %s", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err) | ||
} | ||
|
||
return os.RemoveAll(fileDefaultResolvConfBackupLocation) | ||
} | ||
|
||
func writeDNSConfig(content, fileName string, permissions os.FileMode) error { | ||
log.Debugf("creating managed file %s", fileName) | ||
var buf bytes.Buffer | ||
buf.WriteString(content) | ||
err := os.WriteFile(fileName, buf.Bytes(), permissions) | ||
if err != nil { | ||
return fmt.Errorf("got an creating resolver file %s. Error: %s", fileName, err) | ||
} | ||
return nil | ||
} | ||
|
||
func copyFile(src, dest string) error { | ||
stats, err := os.Stat(src) | ||
if err != nil { | ||
return fmt.Errorf("got an error while checking stats for %s file when copying it. Error: %s", src, err) | ||
} | ||
|
||
bytesRead, err := os.ReadFile(src) | ||
if err != nil { | ||
return fmt.Errorf("got an error while reading the file %s file for copy. Error: %s", src, err) | ||
} | ||
|
||
err = os.WriteFile(dest, bytesRead, stats.Mode()) | ||
if err != nil { | ||
return fmt.Errorf("got an writing the destination file %s for copy. Error: %s", dest, err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package dns | ||
|
||
import ( | ||
"fmt" | ||
nbdns "github.com/netbirdio/netbird/dns" | ||
"strings" | ||
) | ||
|
||
type hostManager interface { | ||
applyDNSConfig(config hostDNSConfig) error | ||
restoreHostDNS() error | ||
} | ||
|
||
type hostDNSConfig struct { | ||
domains []domainConfig | ||
routeAll bool | ||
serverIP string | ||
serverPort int | ||
} | ||
|
||
type domainConfig struct { | ||
domain string | ||
matchOnly bool | ||
} | ||
|
||
type mockHostConfigurator struct { | ||
applyDNSConfigFunc func(config hostDNSConfig) error | ||
restoreHostDNSFunc func() error | ||
} | ||
|
||
func (m *mockHostConfigurator) applyDNSConfig(config hostDNSConfig) error { | ||
if m.applyDNSConfigFunc != nil { | ||
return m.applyDNSConfigFunc(config) | ||
} | ||
return fmt.Errorf("method applyDNSSettings is not implemented") | ||
} | ||
|
||
func (m *mockHostConfigurator) restoreHostDNS() error { | ||
if m.restoreHostDNSFunc != nil { | ||
return m.restoreHostDNSFunc() | ||
} | ||
return fmt.Errorf("method restoreHostDNS is not implemented") | ||
} | ||
|
||
func newNoopHostMocker() hostManager { | ||
return &mockHostConfigurator{ | ||
applyDNSConfigFunc: func(config hostDNSConfig) error { return nil }, | ||
restoreHostDNSFunc: func() error { return nil }, | ||
} | ||
} | ||
|
||
func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip string, port int) hostDNSConfig { | ||
config := hostDNSConfig{ | ||
routeAll: false, | ||
serverIP: ip, | ||
serverPort: port, | ||
} | ||
for _, nsConfig := range dnsConfig.NameServerGroups { | ||
if nsConfig.Primary { | ||
config.routeAll = true | ||
} | ||
|
||
for _, domain := range nsConfig.Domains { | ||
config.domains = append(config.domains, domainConfig{ | ||
domain: strings.TrimSuffix(domain, "."), | ||
matchOnly: true, | ||
}) | ||
} | ||
} | ||
|
||
for _, customZone := range dnsConfig.CustomZones { | ||
config.domains = append(config.domains, domainConfig{ | ||
domain: strings.TrimSuffix(customZone.Domain, "."), | ||
matchOnly: false, | ||
}) | ||
} | ||
|
||
return config | ||
} |
Oops, something went wrong.