package dns import ( "bytes" "fmt" "os" log "github.com/sirupsen/logrus" ) 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) supportCustomPort() bool { return false } 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 nameserver group with all domains configured") } 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 || dConf.disabled { 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. Added %d search domains. Search list: %s", defaultResolvConfPath, appendedDomains, searchDomains) 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 }