github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/rootd/dns/server_darwin.go (about)

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"net"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/datawire/dlib/dgroup"
    12  	"github.com/datawire/dlib/dlog"
    13  	"github.com/telepresenceio/telepresence/v2/pkg/dnsproxy"
    14  	"github.com/telepresenceio/telepresence/v2/pkg/vif"
    15  )
    16  
    17  const (
    18  	maxRecursionTestRetries = 10
    19  	recursionTestTimeout    = 500 * time.Millisecond
    20  )
    21  
    22  // Worker places a file under the /etc/resolver directory so that it is picked up by the
    23  // macOS resolver. The file is configured with a single nameserver that points to the local IP
    24  // that the Telepresence DNS server listens to. The file is removed, and the DNS is flushed when
    25  // the worker terminates
    26  //
    27  // For more information about /etc/resolver files, please view the man pages available at
    28  //
    29  //	man 5 resolver
    30  //
    31  // or, if not on a Mac, follow this link: https://www.manpagez.com/man/5/resolver/
    32  func (s *Server) Worker(c context.Context, dev vif.Device, configureDNS func(net.IP, *net.UDPAddr)) error {
    33  	resolverDirName := filepath.Join("/etc", "resolver")
    34  
    35  	listener, err := newLocalUDPListener(c)
    36  	if err != nil {
    37  		return err
    38  	}
    39  	dnsAddr, err := splitToUDPAddr(listener.LocalAddr())
    40  	if err != nil {
    41  		return err
    42  	}
    43  	configureDNS(nil, dnsAddr)
    44  
    45  	err = os.MkdirAll(resolverDirName, 0o755)
    46  	if err != nil {
    47  		return err
    48  	}
    49  
    50  	// Ensure lingering all telepresence.* files are removed.
    51  	if err := s.removeResolverFiles(c, resolverDirName); err != nil {
    52  		return err
    53  	}
    54  
    55  	defer func() {
    56  		_ = s.removeResolverFiles(c, resolverDirName)
    57  		s.flushDNS()
    58  	}()
    59  
    60  	// Start local DNS server
    61  	g := dgroup.NewGroup(c, dgroup.GroupConfig{})
    62  	g.Go("Server", func(c context.Context) error {
    63  		if err := s.updateResolverFiles(c, resolverDirName, dnsAddr); err != nil {
    64  			return err
    65  		}
    66  		s.processSearchPaths(g, func(c context.Context, _ vif.Device) error {
    67  			return s.updateResolverFiles(c, resolverDirName, dnsAddr)
    68  		}, dev)
    69  		// Server will close the listener, so no need to close it here.
    70  		return s.Run(c, make(chan struct{}), []net.PacketConn{listener}, nil, s.resolveInCluster)
    71  	})
    72  	return g.Wait()
    73  }
    74  
    75  // removeResolverFiles performs rm -f /etc/resolver/telepresence.*.
    76  func (s *Server) removeResolverFiles(c context.Context, resolverDirName string) error {
    77  	files, err := os.ReadDir(resolverDirName)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	for _, file := range files {
    82  		if n := file.Name(); strings.HasPrefix(n, "telepresence.") {
    83  			fn := filepath.Join(resolverDirName, n)
    84  			dlog.Debugf(c, "Removing file %q", fn)
    85  			if err := os.Remove(fn); err != nil {
    86  				return err
    87  			}
    88  		}
    89  	}
    90  	return nil
    91  }
    92  
    93  func (s *Server) updateResolverFiles(c context.Context, resolverDirName string, dnsAddr *net.UDPAddr) error {
    94  	s.Lock()
    95  	defer s.Unlock()
    96  
    97  	nameservers := []string{dnsAddr.IP.String()}
    98  	port := dnsAddr.Port
    99  	newDomainResolveFile := func(domain string) *dnsproxy.ResolveFile {
   100  		return &dnsproxy.ResolveFile{
   101  			Port:        port,
   102  			Domain:      domain,
   103  			Nameservers: nameservers,
   104  		}
   105  	}
   106  
   107  	// All routes and include suffixes become domains
   108  	domains := make(map[string]*dnsproxy.ResolveFile, len(s.routes)+len(s.includeSuffixes))
   109  	for route := range s.routes {
   110  		domains[route] = newDomainResolveFile(route)
   111  	}
   112  	for _, sfx := range s.includeSuffixes {
   113  		sfx = strings.TrimPrefix(sfx, ".")
   114  		domains[sfx] = newDomainResolveFile(sfx)
   115  	}
   116  	clusterDomain := strings.TrimSuffix(s.clusterDomain, ".")
   117  	domains[clusterDomain] = newDomainResolveFile(clusterDomain)
   118  	domains[tel2SubDomain] = newDomainResolveFile(tel2SubDomain)
   119  
   120  nextSearch:
   121  	for _, search := range s.search {
   122  		search = strings.TrimSuffix(search, ".")
   123  		if df, ok := domains[search]; ok {
   124  			df.Search = append(df.Search, search)
   125  			continue
   126  		}
   127  		for domain, df := range domains {
   128  			if strings.HasSuffix(search, "."+domain) {
   129  				df.Search = append(df.Search, search)
   130  				continue nextSearch
   131  			}
   132  		}
   133  	}
   134  
   135  	for domain := range s.domains {
   136  		if _, ok := domains[domain]; !ok {
   137  			nsFile := domainResolverFile(resolverDirName, domain)
   138  			dlog.Infof(c, "Removing %s", nsFile)
   139  			if err := os.Remove(nsFile); err != nil {
   140  				dlog.Error(c, err)
   141  			}
   142  			delete(s.domains, domain)
   143  		}
   144  	}
   145  
   146  	for domain, rf := range domains {
   147  		nsFile := domainResolverFile(resolverDirName, domain)
   148  		if _, ok := s.domains[domain]; ok {
   149  			if oldRf, err := dnsproxy.ReadResolveFile(nsFile); err != nil && rf.Equals(oldRf) {
   150  				continue
   151  			}
   152  			dlog.Infof(c, "Regenerating %s", nsFile)
   153  		} else {
   154  			s.domains[domain] = struct{}{}
   155  			dlog.Infof(c, "Generating %s", nsFile)
   156  		}
   157  		if err := rf.Write(nsFile); err != nil {
   158  			dlog.Error(c, err)
   159  		}
   160  	}
   161  	s.flushDNS()
   162  	return nil
   163  }
   164  
   165  func domainResolverFile(resolverDirName, domain string) string {
   166  	return filepath.Join(resolverDirName, "telepresence."+domain)
   167  }