dnsfallback.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:generate go run update-dns-fallbacks.go
  4. // Package dnsfallback contains a DNS fallback mechanism
  5. // for starting up Tailscale when the system DNS is broken or otherwise unavailable.
  6. package dnsfallback
  7. import (
  8. "context"
  9. _ "embed"
  10. "encoding/json"
  11. "errors"
  12. "fmt"
  13. "net"
  14. "net/http"
  15. "net/netip"
  16. "net/url"
  17. "os"
  18. "reflect"
  19. "sync/atomic"
  20. "time"
  21. "tailscale.com/atomicfile"
  22. "tailscale.com/net/netmon"
  23. "tailscale.com/net/netns"
  24. "tailscale.com/net/tlsdial"
  25. "tailscale.com/net/tshttpproxy"
  26. "tailscale.com/tailcfg"
  27. "tailscale.com/types/logger"
  28. "tailscale.com/util/slicesx"
  29. )
  30. // MakeLookupFunc creates a function that can be used to resolve hostnames
  31. // (e.g. as a LookupIPFallback from dnscache.Resolver).
  32. // The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
  33. func MakeLookupFunc(logf logger.Logf, netMon *netmon.Monitor) func(ctx context.Context, host string) ([]netip.Addr, error) {
  34. return func(ctx context.Context, host string) ([]netip.Addr, error) {
  35. return lookup(ctx, host, logf, netMon)
  36. }
  37. }
  38. func lookup(ctx context.Context, host string, logf logger.Logf, netMon *netmon.Monitor) ([]netip.Addr, error) {
  39. if ip, err := netip.ParseAddr(host); err == nil && ip.IsValid() {
  40. return []netip.Addr{ip}, nil
  41. }
  42. type nameIP struct {
  43. dnsName string
  44. ip netip.Addr
  45. }
  46. dm := getDERPMap()
  47. var cands4, cands6 []nameIP
  48. for _, dr := range dm.Regions {
  49. for _, n := range dr.Nodes {
  50. if ip, err := netip.ParseAddr(n.IPv4); err == nil {
  51. cands4 = append(cands4, nameIP{n.HostName, ip})
  52. }
  53. if ip, err := netip.ParseAddr(n.IPv6); err == nil {
  54. cands6 = append(cands6, nameIP{n.HostName, ip})
  55. }
  56. }
  57. }
  58. slicesx.Shuffle(cands4)
  59. slicesx.Shuffle(cands6)
  60. const maxCands = 6
  61. var cands []nameIP // up to maxCands alternating v4/v6 as long as we have both
  62. for (len(cands4) > 0 || len(cands6) > 0) && len(cands) < maxCands {
  63. if len(cands4) > 0 {
  64. cands = append(cands, cands4[0])
  65. cands4 = cands4[1:]
  66. }
  67. if len(cands6) > 0 {
  68. cands = append(cands, cands6[0])
  69. cands6 = cands6[1:]
  70. }
  71. }
  72. if len(cands) == 0 {
  73. return nil, fmt.Errorf("no DNS fallback options for %q", host)
  74. }
  75. for _, cand := range cands {
  76. if err := ctx.Err(); err != nil {
  77. return nil, err
  78. }
  79. logf("trying bootstrapDNS(%q, %q) for %q ...", cand.dnsName, cand.ip, host)
  80. ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
  81. defer cancel()
  82. dm, err := bootstrapDNSMap(ctx, cand.dnsName, cand.ip, host, logf, netMon)
  83. if err != nil {
  84. logf("bootstrapDNS(%q, %q) for %q error: %v", cand.dnsName, cand.ip, host, err)
  85. continue
  86. }
  87. if ips := dm[host]; len(ips) > 0 {
  88. slicesx.Shuffle(ips)
  89. logf("bootstrapDNS(%q, %q) for %q = %v", cand.dnsName, cand.ip, host, ips)
  90. return ips, nil
  91. }
  92. }
  93. if err := ctx.Err(); err != nil {
  94. return nil, err
  95. }
  96. return nil, fmt.Errorf("no DNS fallback candidates remain for %q", host)
  97. }
  98. // serverName and serverIP of are, say, "derpN.tailscale.com".
  99. // queryName is the name being sought (e.g. "controlplane.tailscale.com"), passed as hint.
  100. func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netip.Addr, queryName string, logf logger.Logf, netMon *netmon.Monitor) (dnsMap, error) {
  101. dialer := netns.NewDialer(logf, netMon)
  102. tr := http.DefaultTransport.(*http.Transport).Clone()
  103. tr.Proxy = tshttpproxy.ProxyFromEnvironment
  104. tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) {
  105. return dialer.DialContext(ctx, "tcp", net.JoinHostPort(serverIP.String(), "443"))
  106. }
  107. tr.TLSClientConfig = tlsdial.Config(serverName, tr.TLSClientConfig)
  108. c := &http.Client{Transport: tr}
  109. req, err := http.NewRequestWithContext(ctx, "GET", "https://"+serverName+"/bootstrap-dns?q="+url.QueryEscape(queryName), nil)
  110. if err != nil {
  111. return nil, err
  112. }
  113. dm := make(dnsMap)
  114. res, err := c.Do(req)
  115. if err != nil {
  116. return nil, err
  117. }
  118. defer res.Body.Close()
  119. if res.StatusCode != 200 {
  120. return nil, errors.New(res.Status)
  121. }
  122. if err := json.NewDecoder(res.Body).Decode(&dm); err != nil {
  123. return nil, err
  124. }
  125. return dm, nil
  126. }
  127. // dnsMap is the JSON type returned by the DERP /bootstrap-dns handler:
  128. // https://derp10.tailscale.com/bootstrap-dns
  129. type dnsMap map[string][]netip.Addr
  130. // getDERPMap returns some DERP map. The DERP servers also run a fallback
  131. // DNS server.
  132. func getDERPMap() *tailcfg.DERPMap {
  133. dm := getStaticDERPMap()
  134. // Merge in any DERP servers from the cached map that aren't in the
  135. // static map; this ensures that we're getting new region(s) while not
  136. // overriding the built-in fallbacks if things go horribly wrong and we
  137. // get a bad DERP map.
  138. //
  139. // TODO(andrew): should we expect OmitDefaultRegions here? We're not
  140. // forwarding traffic, just resolving DNS, so maybe we can ignore that
  141. // value anyway?
  142. cached := cachedDERPMap.Load()
  143. if cached == nil {
  144. return dm
  145. }
  146. for id, region := range cached.Regions {
  147. dr, ok := dm.Regions[id]
  148. if !ok {
  149. dm.Regions[id] = region
  150. continue
  151. }
  152. // Add any nodes that we don't already have.
  153. seen := make(map[string]bool)
  154. for _, n := range dr.Nodes {
  155. seen[n.HostName] = true
  156. }
  157. for _, n := range region.Nodes {
  158. if !seen[n.HostName] {
  159. dr.Nodes = append(dr.Nodes, n)
  160. }
  161. }
  162. }
  163. return dm
  164. }
  165. // getStaticDERPMap returns the DERP map that was compiled into this binary.
  166. func getStaticDERPMap() *tailcfg.DERPMap {
  167. dm := new(tailcfg.DERPMap)
  168. if err := json.Unmarshal(staticDERPMapJSON, dm); err != nil {
  169. panic(err)
  170. }
  171. return dm
  172. }
  173. //go:embed dns-fallback-servers.json
  174. var staticDERPMapJSON []byte
  175. // cachedDERPMap is the path to a cached DERP map that we loaded from our on-disk cache.
  176. var cachedDERPMap atomic.Pointer[tailcfg.DERPMap]
  177. // cachePath is the path to the DERP map cache file, set by SetCachePath via
  178. // ipnserver.New() if we have a state directory.
  179. var cachePath string
  180. // UpdateCache stores the DERP map cache back to disk.
  181. //
  182. // The caller must not mutate 'c' after calling this function.
  183. func UpdateCache(c *tailcfg.DERPMap, logf logger.Logf) {
  184. // Don't do anything if nothing changed.
  185. curr := cachedDERPMap.Load()
  186. if reflect.DeepEqual(curr, c) {
  187. return
  188. }
  189. d, err := json.Marshal(c)
  190. if err != nil {
  191. logf("[v1] dnsfallback: UpdateCache error marshaling: %v", err)
  192. return
  193. }
  194. // Only store after we're confident this is at least valid JSON
  195. cachedDERPMap.Store(c)
  196. // Don't try writing if we don't have a cache path set; this can happen
  197. // when we don't have a state path (e.g. /var/lib/tailscale) configured.
  198. if cachePath != "" {
  199. err = atomicfile.WriteFile(cachePath, d, 0600)
  200. if err != nil {
  201. logf("[v1] dnsfallback: UpdateCache error writing: %v", err)
  202. return
  203. }
  204. }
  205. }
  206. // SetCachePath sets the path to the on-disk DERP map cache that we store and
  207. // update. Additionally, if a file at this path exists, we load it and merge it
  208. // with the DERP map baked into the binary.
  209. //
  210. // This function should be called before any calls to UpdateCache, as it is not
  211. // concurrency-safe.
  212. func SetCachePath(path string, logf logger.Logf) {
  213. cachePath = path
  214. f, err := os.Open(path)
  215. if err != nil {
  216. logf("[v1] dnsfallback: SetCachePath error reading %q: %v", path, err)
  217. return
  218. }
  219. defer f.Close()
  220. dm := new(tailcfg.DERPMap)
  221. if err := json.NewDecoder(f).Decode(dm); err != nil {
  222. logf("[v1] dnsfallback: SetCachePath error decoding %q: %v", path, err)
  223. return
  224. }
  225. cachedDERPMap.Store(dm)
  226. logf("[v2] dnsfallback: SetCachePath loaded cached DERP map")
  227. }