mirror of
https://github.com/netbirdio/netbird.git
synced 2025-02-23 13:41:19 +01:00
* Add DNS list argument for mobile client * Write testable code Many places are checked the wgInterface != nil condition. It is doing it just because to avoid the real wgInterface creation for tests. Instead of this involve a wgInterface interface what is moc-able. * Refactor the DNS server internal code structure With the fake resolver has been involved several if-else statement and generated some unused variables to distinguish the listener and fake resolver solutions at running time. With this commit the fake resolver and listener based solution has been moved into two separated structure. Name of this layer is the 'service'. With this modification the unit test looks simpler and open the option to add new logic for the permanent DNS service usage for mobile systems. * Remove is running check in test We can not ensure the state well so remove this check. The test will fail if the server is not running well.
267 lines
7.7 KiB
Go
267 lines
7.7 KiB
Go
package dns
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
netbirdDNSStateKeyFormat = "State:/Network/Service/NetBird-%s/DNS"
|
|
globalIPv4State = "State:/Network/Global/IPv4"
|
|
primaryServiceSetupKeyFormat = "Setup:/Network/Service/%s/DNS"
|
|
keySupplementalMatchDomains = "SupplementalMatchDomains"
|
|
keySupplementalMatchDomainsNoSearch = "SupplementalMatchDomainsNoSearch"
|
|
keyServerAddresses = "ServerAddresses"
|
|
keyServerPort = "ServerPort"
|
|
arraySymbol = "* "
|
|
digitSymbol = "# "
|
|
scutilPath = "/usr/sbin/scutil"
|
|
searchSuffix = "Search"
|
|
matchSuffix = "Match"
|
|
)
|
|
|
|
type systemConfigurator struct {
|
|
// primaryServiceID primary interface in the system. AKA the interface with the default route
|
|
primaryServiceID string
|
|
createdKeys map[string]struct{}
|
|
}
|
|
|
|
func newHostManager(_ WGIface) (hostManager, error) {
|
|
return &systemConfigurator{
|
|
createdKeys: make(map[string]struct{}),
|
|
}, nil
|
|
}
|
|
|
|
func (s *systemConfigurator) supportCustomPort() bool {
|
|
return true
|
|
}
|
|
|
|
func (s *systemConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
|
var err error
|
|
|
|
if config.routeAll {
|
|
err = s.addDNSSetupForAll(config.serverIP, config.serverPort)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if s.primaryServiceID != "" {
|
|
err = s.removeKeyFromSystemConfig(getKeyWithInput(primaryServiceSetupKeyFormat, s.primaryServiceID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.primaryServiceID = ""
|
|
log.Infof("removed %s:%d as main DNS resolver for this peer", config.serverIP, config.serverPort)
|
|
}
|
|
|
|
var (
|
|
searchDomains []string
|
|
matchDomains []string
|
|
)
|
|
|
|
for _, dConf := range config.domains {
|
|
if dConf.disabled {
|
|
continue
|
|
}
|
|
if dConf.matchOnly {
|
|
matchDomains = append(matchDomains, dConf.domain)
|
|
continue
|
|
}
|
|
searchDomains = append(searchDomains, dConf.domain)
|
|
}
|
|
|
|
matchKey := getKeyWithInput(netbirdDNSStateKeyFormat, matchSuffix)
|
|
if len(matchDomains) != 0 {
|
|
err = s.addMatchDomains(matchKey, strings.Join(matchDomains, " "), config.serverIP, config.serverPort)
|
|
} else {
|
|
log.Infof("removing match domains from the system")
|
|
err = s.removeKeyFromSystemConfig(matchKey)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
searchKey := getKeyWithInput(netbirdDNSStateKeyFormat, searchSuffix)
|
|
if len(searchDomains) != 0 {
|
|
err = s.addSearchDomains(searchKey, strings.Join(searchDomains, " "), config.serverIP, config.serverPort)
|
|
} else {
|
|
log.Infof("removing search domains from the system")
|
|
err = s.removeKeyFromSystemConfig(searchKey)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *systemConfigurator) restoreHostDNS() error {
|
|
lines := ""
|
|
for key := range s.createdKeys {
|
|
lines += buildRemoveKeyOperation(key)
|
|
keyType := "search"
|
|
if strings.Contains(key, matchSuffix) {
|
|
keyType = "match"
|
|
}
|
|
log.Infof("removing %s domains from system", keyType)
|
|
}
|
|
if s.primaryServiceID != "" {
|
|
lines += buildRemoveKeyOperation(getKeyWithInput(primaryServiceSetupKeyFormat, s.primaryServiceID))
|
|
log.Infof("restoring DNS resolver configuration for system")
|
|
}
|
|
_, err := runSystemConfigCommand(wrapCommand(lines))
|
|
if err != nil {
|
|
log.Errorf("got an error while cleaning the system configuration: %s", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *systemConfigurator) removeKeyFromSystemConfig(key string) error {
|
|
line := buildRemoveKeyOperation(key)
|
|
_, err := runSystemConfigCommand(wrapCommand(line))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
delete(s.createdKeys, key)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *systemConfigurator) addSearchDomains(key, domains string, ip string, port int) error {
|
|
err := s.addDNSState(key, domains, ip, port, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("added %d search domains to the state. Domain list: %s", len(strings.Split(domains, " ")), domains)
|
|
|
|
s.createdKeys[key] = struct{}{}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *systemConfigurator) addMatchDomains(key, domains, dnsServer string, port int) error {
|
|
err := s.addDNSState(key, domains, dnsServer, port, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("added %d match domains to the state. Domain list: %s", len(strings.Split(domains, " ")), domains)
|
|
|
|
s.createdKeys[key] = struct{}{}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *systemConfigurator) addDNSState(state, domains, dnsServer string, port int, enableSearch bool) error {
|
|
noSearch := "1"
|
|
if enableSearch {
|
|
noSearch = "0"
|
|
}
|
|
lines := buildAddCommandLine(keySupplementalMatchDomains, arraySymbol+domains)
|
|
lines += buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+noSearch)
|
|
lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer)
|
|
lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port))
|
|
|
|
addDomainCommand := buildCreateStateWithOperation(state, lines)
|
|
stdinCommands := wrapCommand(addDomainCommand)
|
|
|
|
_, err := runSystemConfigCommand(stdinCommands)
|
|
if err != nil {
|
|
return fmt.Errorf("got error while applying state for domains %s, error: %s", domains, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error {
|
|
primaryServiceKey := s.getPrimaryService()
|
|
if primaryServiceKey == "" {
|
|
return fmt.Errorf("couldn't find the primary service key")
|
|
}
|
|
|
|
err := s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Infof("configured %s:%d as main DNS resolver for this peer", dnsServer, port)
|
|
s.primaryServiceID = primaryServiceKey
|
|
return nil
|
|
}
|
|
|
|
func (s *systemConfigurator) getPrimaryService() string {
|
|
line := buildCommandLine("show", globalIPv4State, "")
|
|
stdinCommands := wrapCommand(line)
|
|
b, err := runSystemConfigCommand(stdinCommands)
|
|
if err != nil {
|
|
log.Error("got error while sending the command: ", err)
|
|
return ""
|
|
}
|
|
scanner := bufio.NewScanner(bytes.NewReader(b))
|
|
for scanner.Scan() {
|
|
text := scanner.Text()
|
|
if strings.Contains(text, "PrimaryService") {
|
|
return strings.TrimSpace(strings.Split(text, ":")[1])
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int) error {
|
|
lines := buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+strconv.Itoa(0))
|
|
lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer)
|
|
lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port))
|
|
addDomainCommand := buildCreateStateWithOperation(setupKey, lines)
|
|
stdinCommands := wrapCommand(addDomainCommand)
|
|
_, err := runSystemConfigCommand(stdinCommands)
|
|
if err != nil {
|
|
return fmt.Errorf("got error while applying dns setup, error: %s", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getKeyWithInput(format, key string) string {
|
|
return fmt.Sprintf(format, key)
|
|
}
|
|
|
|
func buildAddCommandLine(key, value string) string {
|
|
return buildCommandLine("d.add", key, value)
|
|
}
|
|
|
|
func buildCommandLine(action, key, value string) string {
|
|
return fmt.Sprintf("%s %s %s\n", action, key, value)
|
|
}
|
|
|
|
func wrapCommand(commands string) string {
|
|
return fmt.Sprintf("open\n%s\nquit\n", commands)
|
|
}
|
|
|
|
func buildRemoveKeyOperation(key string) string {
|
|
return fmt.Sprintf("remove %s\n", key)
|
|
}
|
|
|
|
func buildCreateStateWithOperation(state, commands string) string {
|
|
return buildWriteStateOperation("set", state, commands)
|
|
}
|
|
|
|
func buildWriteStateOperation(operation, state, commands string) string {
|
|
return fmt.Sprintf("d.init\n%s %s\n%s\nset %s\n", operation, state, commands, state)
|
|
}
|
|
|
|
func runSystemConfigCommand(command string) ([]byte, error) {
|
|
cmd := exec.Command(scutilPath)
|
|
cmd.Stdin = strings.NewReader(command)
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("got error while running system configuration command: \"%s\", error: %s", command, err)
|
|
}
|
|
return out, nil
|
|
}
|