resolved.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux && !android && !ts_omit_resolved
  4. package dns
  5. import (
  6. "context"
  7. "fmt"
  8. "net"
  9. "strings"
  10. "time"
  11. "github.com/godbus/dbus/v5"
  12. "golang.org/x/sys/unix"
  13. "tailscale.com/health"
  14. "tailscale.com/types/logger"
  15. "tailscale.com/util/backoff"
  16. "tailscale.com/util/dnsname"
  17. )
  18. // DBus entities we talk to.
  19. //
  20. // DBus is an RPC bus. In particular, the bus we're talking to is the
  21. // system-wide bus (there is also a per-user session bus for
  22. // user-specific applications).
  23. //
  24. // Daemons connect to the bus, and advertise themselves under a
  25. // well-known object name. That object exposes paths, and each path
  26. // implements one or more interfaces that contain methods, properties,
  27. // and signals.
  28. //
  29. // Clients connect to the bus and walk that same hierarchy to invoke
  30. // RPCs, get/set properties, or listen for signals.
  31. const (
  32. dbusResolvedObject = "org.freedesktop.resolve1"
  33. dbusResolvedPath dbus.ObjectPath = "/org/freedesktop/resolve1"
  34. dbusResolvedInterface = "org.freedesktop.resolve1.Manager"
  35. dbusPath dbus.ObjectPath = "/org/freedesktop/DBus"
  36. dbusInterface = "org.freedesktop.DBus"
  37. dbusOwnerSignal = "NameOwnerChanged" // broadcast when a well-known name's owning process changes.
  38. )
  39. type resolvedLinkNameserver struct {
  40. Family int32
  41. Address []byte
  42. }
  43. type resolvedLinkDomain struct {
  44. Domain string
  45. RoutingOnly bool
  46. }
  47. // changeRequest tracks latest OSConfig and related error responses to update.
  48. type changeRequest struct {
  49. config OSConfig // configs OSConfigs, one per each SetDNS call
  50. res chan<- error // response channel
  51. }
  52. // resolvedManager is an OSConfigurator which uses the systemd-resolved DBus API.
  53. type resolvedManager struct {
  54. ctx context.Context
  55. cancel func() // terminate the context, for close
  56. logf logger.Logf
  57. health *health.Tracker
  58. ifidx int
  59. configCR chan changeRequest // tracks OSConfigs changes and error responses
  60. }
  61. func init() {
  62. optNewResolvedManager.Set(newResolvedManager)
  63. }
  64. func newResolvedManager(logf logger.Logf, health *health.Tracker, interfaceName string) (OSConfigurator, error) {
  65. iface, err := net.InterfaceByName(interfaceName)
  66. if err != nil {
  67. return nil, err
  68. }
  69. ctx, cancel := context.WithCancel(context.Background())
  70. logf = logger.WithPrefix(logf, "dns: ")
  71. mgr := &resolvedManager{
  72. ctx: ctx,
  73. cancel: cancel,
  74. logf: logf,
  75. health: health,
  76. ifidx: iface.Index,
  77. configCR: make(chan changeRequest),
  78. }
  79. go mgr.run(ctx)
  80. return mgr, nil
  81. }
  82. func (m *resolvedManager) SetDNS(config OSConfig) error {
  83. // NOTE: don't close this channel, since it's possible that the SetDNS
  84. // call will time out and return before the run loop answers, at which
  85. // point it will send on the now-closed channel.
  86. errc := make(chan error, 1)
  87. select {
  88. case <-m.ctx.Done():
  89. return m.ctx.Err()
  90. case m.configCR <- changeRequest{config, errc}:
  91. }
  92. select {
  93. case <-m.ctx.Done():
  94. return m.ctx.Err()
  95. case err := <-errc:
  96. if err != nil {
  97. m.logf("failed to configure resolved: %v", err)
  98. }
  99. return err
  100. }
  101. }
  102. func (m *resolvedManager) run(ctx context.Context) {
  103. var (
  104. conn *dbus.Conn
  105. signals chan *dbus.Signal
  106. rManager dbus.BusObject // rManager is the Resolved DBus connection
  107. )
  108. bo := backoff.NewBackoff("resolved-dbus", m.logf, 30*time.Second)
  109. needsReconnect := make(chan bool, 1)
  110. defer func() {
  111. if conn != nil {
  112. conn.Close()
  113. }
  114. }()
  115. // Reconnect the systemBus if disconnected.
  116. reconnect := func() error {
  117. var err error
  118. signals = make(chan *dbus.Signal, 16)
  119. conn, err = dbus.SystemBus()
  120. if err != nil {
  121. m.logf("dbus connection error: %v", err)
  122. } else {
  123. m.logf("[v1] dbus connected")
  124. }
  125. if err != nil {
  126. // Backoff increases time between reconnect attempts.
  127. go func() {
  128. bo.BackOff(ctx, err)
  129. needsReconnect <- true
  130. }()
  131. return err
  132. }
  133. rManager = conn.Object(dbusResolvedObject, dbus.ObjectPath(dbusResolvedPath))
  134. // Only receive the DBus signals we need to resync our config on
  135. // resolved restart. Failure to set filters isn't a fatal error,
  136. // we'll just receive all broadcast signals and have to ignore
  137. // them on our end.
  138. if err = conn.AddMatchSignal(dbus.WithMatchObjectPath(dbusPath), dbus.WithMatchInterface(dbusInterface), dbus.WithMatchMember(dbusOwnerSignal), dbus.WithMatchArg(0, dbusResolvedObject)); err != nil {
  139. m.logf("[v1] Setting DBus signal filter failed: %v", err)
  140. }
  141. conn.Signal(signals)
  142. // Reset backoff and set osConfigurationSetWarnable to healthy after a successful reconnect.
  143. bo.BackOff(ctx, nil)
  144. m.health.SetHealthy(osConfigurationSetWarnable)
  145. return nil
  146. }
  147. // Create initial systemBus connection.
  148. reconnect()
  149. lastConfig := OSConfig{}
  150. for {
  151. select {
  152. case <-ctx.Done():
  153. if rManager == nil {
  154. return
  155. }
  156. // RevertLink resets all per-interface settings on systemd-resolved to defaults.
  157. // When ctx goes away systemd-resolved auto reverts.
  158. // Keeping for potential use in future refactor.
  159. if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".RevertLink", 0, m.ifidx); call.Err != nil {
  160. m.logf("[v1] RevertLink: %v", call.Err)
  161. return
  162. }
  163. return
  164. case configCR := <-m.configCR:
  165. // Track and update sync with latest config change.
  166. lastConfig = configCR.config
  167. if rManager == nil {
  168. configCR.res <- fmt.Errorf("resolved DBus does not have a connection")
  169. continue
  170. }
  171. err := m.setConfigOverDBus(ctx, rManager, configCR.config)
  172. configCR.res <- err
  173. case <-needsReconnect:
  174. if err := reconnect(); err != nil {
  175. m.logf("[v1] SystemBus reconnect error %T", err)
  176. }
  177. continue
  178. case signal, ok := <-signals:
  179. // If signal ends and is nil then program tries to reconnect.
  180. if !ok {
  181. if err := reconnect(); err != nil {
  182. m.logf("[v1] SystemBus reconnect error %T", err)
  183. }
  184. continue
  185. }
  186. // In theory the signal was filtered by DBus, but if
  187. // AddMatchSignal in the constructor failed, we may be
  188. // getting other spam.
  189. if signal.Path != dbusPath || signal.Name != dbusInterface+"."+dbusOwnerSignal {
  190. continue
  191. }
  192. if lastConfig.IsZero() {
  193. continue
  194. }
  195. // signal.Body is a []any of 3 strings: bus name, previous owner, new owner.
  196. if len(signal.Body) != 3 {
  197. m.logf("[unexpected] DBus NameOwnerChanged len(Body) = %d, want 3")
  198. }
  199. if name, ok := signal.Body[0].(string); !ok || name != dbusResolvedObject {
  200. continue
  201. }
  202. newOwner, ok := signal.Body[2].(string)
  203. if !ok {
  204. m.logf("[unexpected] DBus NameOwnerChanged.new_owner is a %T, not a string", signal.Body[2])
  205. }
  206. if newOwner == "" {
  207. // systemd-resolved left the bus, no current owner,
  208. // nothing to do.
  209. continue
  210. }
  211. // The resolved bus name has a new owner, meaning resolved
  212. // restarted. Reprogram current config.
  213. m.logf("systemd-resolved restarted, syncing DNS config")
  214. err := m.setConfigOverDBus(ctx, rManager, lastConfig)
  215. // Set health while holding the lock, because this will
  216. // graciously serialize the resync's health outcome with a
  217. // concurrent SetDNS call.
  218. if err != nil {
  219. m.logf("failed to configure systemd-resolved: %v", err)
  220. m.health.SetUnhealthy(osConfigurationSetWarnable, health.Args{health.ArgError: err.Error()})
  221. } else {
  222. m.health.SetHealthy(osConfigurationSetWarnable)
  223. }
  224. }
  225. }
  226. }
  227. // setConfigOverDBus updates resolved DBus config and is only called from the run goroutine.
  228. func (m *resolvedManager) setConfigOverDBus(ctx context.Context, rManager dbus.BusObject, config OSConfig) error {
  229. ctx, cancel := context.WithTimeout(ctx, reconfigTimeout)
  230. defer cancel()
  231. var linkNameservers = make([]resolvedLinkNameserver, len(config.Nameservers))
  232. for i, server := range config.Nameservers {
  233. ip := server.As16()
  234. if server.Is4() {
  235. linkNameservers[i] = resolvedLinkNameserver{
  236. Family: unix.AF_INET,
  237. Address: ip[12:],
  238. }
  239. } else {
  240. linkNameservers[i] = resolvedLinkNameserver{
  241. Family: unix.AF_INET6,
  242. Address: ip[:],
  243. }
  244. }
  245. }
  246. err := rManager.CallWithContext(
  247. ctx, dbusResolvedInterface+".SetLinkDNS", 0,
  248. m.ifidx, linkNameservers,
  249. ).Store()
  250. if err != nil {
  251. return fmt.Errorf("setLinkDNS: %w", err)
  252. }
  253. linkDomains := make([]resolvedLinkDomain, 0, len(config.SearchDomains)+len(config.MatchDomains))
  254. seenDomains := map[dnsname.FQDN]bool{}
  255. for _, domain := range config.SearchDomains {
  256. if seenDomains[domain] {
  257. continue
  258. }
  259. seenDomains[domain] = true
  260. linkDomains = append(linkDomains, resolvedLinkDomain{
  261. Domain: domain.WithTrailingDot(),
  262. RoutingOnly: false,
  263. })
  264. }
  265. for _, domain := range config.MatchDomains {
  266. if seenDomains[domain] {
  267. // Search domains act as both search and match in
  268. // resolved, so it's correct to skip.
  269. continue
  270. }
  271. seenDomains[domain] = true
  272. linkDomains = append(linkDomains, resolvedLinkDomain{
  273. Domain: domain.WithTrailingDot(),
  274. RoutingOnly: true,
  275. })
  276. }
  277. if len(config.MatchDomains) == 0 && len(config.Nameservers) > 0 {
  278. // Caller requested full DNS interception, install a
  279. // routing-only root domain.
  280. linkDomains = append(linkDomains, resolvedLinkDomain{
  281. Domain: ".",
  282. RoutingOnly: true,
  283. })
  284. }
  285. err = rManager.CallWithContext(
  286. ctx, dbusResolvedInterface+".SetLinkDomains", 0,
  287. m.ifidx, linkDomains,
  288. ).Store()
  289. if err != nil && err.Error() == "Argument list too long" { // TODO: better error match
  290. // Issue 3188: older systemd-resolved had argument length limits.
  291. // Trim out the *.arpa. entries and try again.
  292. err = rManager.CallWithContext(
  293. ctx, dbusResolvedInterface+".SetLinkDomains", 0,
  294. m.ifidx, linkDomainsWithoutReverseDNS(linkDomains),
  295. ).Store()
  296. }
  297. if err != nil {
  298. return fmt.Errorf("setLinkDomains: %w", err)
  299. }
  300. if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDefaultRoute", 0, m.ifidx, len(config.MatchDomains) == 0); call.Err != nil {
  301. if dbusErr, ok := call.Err.(dbus.Error); ok && dbusErr.Name == dbus.ErrMsgUnknownMethod.Name {
  302. // on some older systems like Kubuntu 18.04.6 with systemd 237 method SetLinkDefaultRoute is absent,
  303. // but otherwise it's working good
  304. m.logf("[v1] failed to set SetLinkDefaultRoute: %v", call.Err)
  305. } else {
  306. return fmt.Errorf("setLinkDefaultRoute: %w", call.Err)
  307. }
  308. }
  309. // Some best-effort setting of things, but resolved should do the
  310. // right thing if these fail (e.g. a really old resolved version
  311. // or something).
  312. // Disable LLMNR, we don't do multicast.
  313. if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkLLMNR", 0, m.ifidx, "no"); call.Err != nil {
  314. m.logf("[v1] failed to disable LLMNR: %v", call.Err)
  315. }
  316. // Disable mdns.
  317. if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkMulticastDNS", 0, m.ifidx, "no"); call.Err != nil {
  318. m.logf("[v1] failed to disable mdns: %v", call.Err)
  319. }
  320. // We don't support dnssec consistently right now, force it off to
  321. // avoid partial failures when we split DNS internally.
  322. if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSSEC", 0, m.ifidx, "no"); call.Err != nil {
  323. m.logf("[v1] failed to disable DNSSEC: %v", call.Err)
  324. }
  325. if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSOverTLS", 0, m.ifidx, "no"); call.Err != nil {
  326. m.logf("[v1] failed to disable DoT: %v", call.Err)
  327. }
  328. if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".FlushCaches", 0); call.Err != nil {
  329. m.logf("failed to flush resolved DNS cache: %v", call.Err)
  330. }
  331. return nil
  332. }
  333. func (m *resolvedManager) SupportsSplitDNS() bool {
  334. return true
  335. }
  336. func (m *resolvedManager) GetBaseConfig() (OSConfig, error) {
  337. return OSConfig{}, ErrGetBaseConfigNotSupported
  338. }
  339. func (m *resolvedManager) Close() error {
  340. m.cancel() // stops the 'run' method goroutine
  341. return nil
  342. }
  343. // linkDomainsWithoutReverseDNS returns a copy of v without
  344. // *.arpa. entries.
  345. func linkDomainsWithoutReverseDNS(v []resolvedLinkDomain) (ret []resolvedLinkDomain) {
  346. for _, d := range v {
  347. if strings.HasSuffix(d.Domain, ".arpa.") {
  348. // Oh well. At least the rest will work.
  349. continue
  350. }
  351. ret = append(ret, d)
  352. }
  353. return ret
  354. }