nm.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux && !android && !ts_omit_networkmanager
  4. package dns
  5. import (
  6. "context"
  7. "encoding/binary"
  8. "errors"
  9. "fmt"
  10. "net"
  11. "net/netip"
  12. "sort"
  13. "time"
  14. "github.com/godbus/dbus/v5"
  15. "tailscale.com/net/tsaddr"
  16. "tailscale.com/util/cmpver"
  17. "tailscale.com/util/dnsname"
  18. )
  19. const (
  20. highestPriority = int32(-1 << 31)
  21. mediumPriority = int32(1) // Highest priority that doesn't hard-override
  22. lowerPriority = int32(200) // lower than all builtin auto priorities
  23. )
  24. // nmManager uses the NetworkManager DBus API.
  25. type nmManager struct {
  26. interfaceName string
  27. manager dbus.BusObject
  28. dnsManager dbus.BusObject
  29. }
  30. func init() {
  31. optNewNMManager.Set(newNMManager)
  32. optNMIsUsingResolved.Set(nmIsUsingResolved)
  33. optNMVersionBetween.Set(nmVersionBetween)
  34. }
  35. func newNMManager(interfaceName string) (OSConfigurator, error) {
  36. conn, err := dbus.SystemBus()
  37. if err != nil {
  38. return nil, err
  39. }
  40. return &nmManager{
  41. interfaceName: interfaceName,
  42. manager: conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager")),
  43. dnsManager: conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager/DnsManager")),
  44. }, nil
  45. }
  46. type nmConnectionSettings map[string]map[string]dbus.Variant
  47. func (m *nmManager) SetDNS(config OSConfig) error {
  48. ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout)
  49. defer cancel()
  50. // NetworkManager only lets you set DNS settings on "active"
  51. // connections, which requires an assigned IP address. This got
  52. // configured before the DNS manager was invoked, but it might
  53. // take a little time for the netlink notifications to propagate
  54. // up. So, keep retrying for the duration of the reconfigTimeout.
  55. var err error
  56. for ctx.Err() == nil {
  57. err = m.trySet(ctx, config)
  58. if err == nil {
  59. break
  60. }
  61. time.Sleep(10 * time.Millisecond)
  62. }
  63. return err
  64. }
  65. func (m *nmManager) trySet(ctx context.Context, config OSConfig) error {
  66. conn, err := dbus.SystemBus()
  67. if err != nil {
  68. return fmt.Errorf("connecting to system bus: %w", err)
  69. }
  70. // This is how we get at the DNS settings:
  71. //
  72. // org.freedesktop.NetworkManager
  73. // |
  74. // [GetDeviceByIpIface]
  75. // |
  76. // v
  77. // org.freedesktop.NetworkManager.Device <--------\
  78. // (describes a network interface) |
  79. // | |
  80. // [GetAppliedConnection] [Reapply]
  81. // | |
  82. // v |
  83. // org.freedesktop.NetworkManager.Connection |
  84. // (connection settings) ------/
  85. // contains {dns, dns-priority, dns-search}
  86. //
  87. // Ref: https://developer.gnome.org/NetworkManager/stable/settings-ipv4.html.
  88. nm := conn.Object(
  89. "org.freedesktop.NetworkManager",
  90. dbus.ObjectPath("/org/freedesktop/NetworkManager"),
  91. )
  92. var devicePath dbus.ObjectPath
  93. err = nm.CallWithContext(
  94. ctx, "org.freedesktop.NetworkManager.GetDeviceByIpIface", 0,
  95. m.interfaceName,
  96. ).Store(&devicePath)
  97. if err != nil {
  98. return fmt.Errorf("getDeviceByIpIface: %w", err)
  99. }
  100. device := conn.Object("org.freedesktop.NetworkManager", devicePath)
  101. var (
  102. settings nmConnectionSettings
  103. version uint64
  104. )
  105. err = device.CallWithContext(
  106. ctx, "org.freedesktop.NetworkManager.Device.GetAppliedConnection", 0,
  107. uint32(0),
  108. ).Store(&settings, &version)
  109. if err != nil {
  110. return fmt.Errorf("getAppliedConnection: %w", err)
  111. }
  112. // Frustratingly, NetworkManager represents IPv4 addresses as uint32s,
  113. // although IPv6 addresses are represented as byte arrays.
  114. // Perform the conversion here.
  115. var (
  116. dnsv4 []uint32
  117. dnsv6 [][]byte
  118. )
  119. for _, ip := range config.Nameservers {
  120. b := ip.As16()
  121. if ip.Is4() {
  122. dnsv4 = append(dnsv4, binary.NativeEndian.Uint32(b[12:]))
  123. } else {
  124. dnsv6 = append(dnsv6, b[:])
  125. }
  126. }
  127. // NetworkManager wipes out IPv6 address configuration unless we
  128. // tell it explicitly to keep it. Read out the current interface
  129. // settings and mirror them out to NetworkManager.
  130. var addrs6 []map[string]any
  131. if tsIf, err := net.InterfaceByName(m.interfaceName); err == nil {
  132. addrs, _ := tsIf.Addrs()
  133. for _, a := range addrs {
  134. if ipnet, ok := a.(*net.IPNet); ok {
  135. nip, ok := netip.AddrFromSlice(ipnet.IP)
  136. nip = nip.Unmap()
  137. if ok && tsaddr.IsTailscaleIP(nip) && nip.Is6() {
  138. addrs6 = append(addrs6, map[string]any{
  139. "address": nip.String(),
  140. "prefix": uint32(128),
  141. })
  142. }
  143. }
  144. }
  145. }
  146. seen := map[dnsname.FQDN]bool{}
  147. var search []string
  148. for _, dom := range config.SearchDomains {
  149. if seen[dom] {
  150. continue
  151. }
  152. seen[dom] = true
  153. search = append(search, dom.WithTrailingDot())
  154. }
  155. for _, dom := range config.MatchDomains {
  156. if seen[dom] {
  157. continue
  158. }
  159. seen[dom] = true
  160. search = append(search, "~"+dom.WithTrailingDot())
  161. }
  162. if len(config.MatchDomains) == 0 {
  163. // Non-split routing requested, add an all-domains match.
  164. search = append(search, "~.")
  165. }
  166. // Ideally we would like to disable LLMNR and mdns on the
  167. // interface here, but older NetworkManagers don't understand
  168. // those settings and choke on them, so we don't. Both LLMNR and
  169. // mdns will fail since tailscale0 doesn't do multicast, so it's
  170. // effectively fine. We used to try and enforce LLMNR and mdns
  171. // settings here, but that led to #1870.
  172. ipv4Map := settings["ipv4"]
  173. ipv4Map["dns"] = dbus.MakeVariant(dnsv4)
  174. ipv4Map["dns-search"] = dbus.MakeVariant(search)
  175. // We should only request priority if we have nameservers to set.
  176. if len(dnsv4) == 0 {
  177. ipv4Map["dns-priority"] = dbus.MakeVariant(lowerPriority)
  178. } else if len(config.MatchDomains) > 0 {
  179. // Set a fairly high priority, but don't override all other
  180. // configs when in split-DNS mode.
  181. ipv4Map["dns-priority"] = dbus.MakeVariant(mediumPriority)
  182. } else {
  183. // Negative priority means only the settings from the most
  184. // negative connection get used. The way this mixes with
  185. // per-domain routing is unclear, but it _seems_ that the
  186. // priority applies after routing has found possible
  187. // candidates for a resolution.
  188. ipv4Map["dns-priority"] = dbus.MakeVariant(highestPriority)
  189. }
  190. ipv6Map := settings["ipv6"]
  191. // In IPv6 settings, you're only allowed to provide additional
  192. // static DNS settings in "auto" (SLAAC) or "manual" mode. In
  193. // "manual" mode you also have to specify IP addresses, so we use
  194. // "auto".
  195. //
  196. // NM actually documents that to set just DNS servers, you should
  197. // use "auto" mode and then set ignore auto routes and DNS, which
  198. // basically means "autoconfigure but ignore any autoconfiguration
  199. // results you might get". As a safety, we also say that
  200. // NetworkManager should never try to make us the default route
  201. // (none of its business anyway, we handle our own default
  202. // routing).
  203. ipv6Map["method"] = dbus.MakeVariant("auto")
  204. if len(addrs6) > 0 {
  205. ipv6Map["address-data"] = dbus.MakeVariant(addrs6)
  206. }
  207. ipv6Map["ignore-auto-routes"] = dbus.MakeVariant(true)
  208. ipv6Map["ignore-auto-dns"] = dbus.MakeVariant(true)
  209. ipv6Map["never-default"] = dbus.MakeVariant(true)
  210. ipv6Map["dns"] = dbus.MakeVariant(dnsv6)
  211. ipv6Map["dns-search"] = dbus.MakeVariant(search)
  212. if len(dnsv6) == 0 {
  213. ipv6Map["dns-priority"] = dbus.MakeVariant(lowerPriority)
  214. } else if len(config.MatchDomains) > 0 {
  215. // Set a fairly high priority, but don't override all other
  216. // configs when in split-DNS mode.
  217. ipv6Map["dns-priority"] = dbus.MakeVariant(mediumPriority)
  218. } else {
  219. ipv6Map["dns-priority"] = dbus.MakeVariant(highestPriority)
  220. }
  221. // deprecatedProperties are the properties in interface settings
  222. // that are deprecated by NetworkManager.
  223. //
  224. // In practice, this means that they are returned for reading,
  225. // but submitting a settings object with them present fails
  226. // with hard-to-diagnose errors. They must be removed.
  227. deprecatedProperties := []string{
  228. "addresses", "routes",
  229. }
  230. for _, property := range deprecatedProperties {
  231. delete(ipv4Map, property)
  232. delete(ipv6Map, property)
  233. }
  234. if call := device.CallWithContext(ctx, "org.freedesktop.NetworkManager.Device.Reapply", 0, settings, version, uint32(0)); call.Err != nil {
  235. return fmt.Errorf("reapply: %w", call.Err)
  236. }
  237. return nil
  238. }
  239. func (m *nmManager) SupportsSplitDNS() bool {
  240. var mode string
  241. v, err := m.dnsManager.GetProperty("org.freedesktop.NetworkManager.DnsManager.Mode")
  242. if err != nil {
  243. return false
  244. }
  245. mode, ok := v.Value().(string)
  246. if !ok {
  247. return false
  248. }
  249. // Per NM's documentation, it only does split-DNS when it's
  250. // programming dnsmasq or systemd-resolved. All other modes are
  251. // primary-only.
  252. return mode == "dnsmasq" || mode == "systemd-resolved"
  253. }
  254. func (m *nmManager) GetBaseConfig() (OSConfig, error) {
  255. conn, err := dbus.SystemBus()
  256. if err != nil {
  257. return OSConfig{}, err
  258. }
  259. nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager/DnsManager"))
  260. v, err := nm.GetProperty("org.freedesktop.NetworkManager.DnsManager.Configuration")
  261. if err != nil {
  262. return OSConfig{}, err
  263. }
  264. cfgs, ok := v.Value().([]map[string]dbus.Variant)
  265. if !ok {
  266. return OSConfig{}, fmt.Errorf("unexpected NM config type %T", v.Value())
  267. }
  268. if len(cfgs) == 0 {
  269. return OSConfig{}, nil
  270. }
  271. type dnsPrio struct {
  272. resolvers []netip.Addr
  273. domains []string
  274. priority int32
  275. }
  276. order := make([]dnsPrio, 0, len(cfgs)-1)
  277. for _, cfg := range cfgs {
  278. if name, ok := cfg["interface"]; ok {
  279. if s, ok := name.Value().(string); ok && s == m.interfaceName {
  280. // Config for the tailscale interface, skip.
  281. continue
  282. }
  283. }
  284. var p dnsPrio
  285. if v, ok := cfg["nameservers"]; ok {
  286. if ips, ok := v.Value().([]string); ok {
  287. for _, s := range ips {
  288. ip, err := netip.ParseAddr(s)
  289. if err != nil {
  290. // hmm, what do? Shouldn't really happen.
  291. continue
  292. }
  293. p.resolvers = append(p.resolvers, ip)
  294. }
  295. }
  296. }
  297. if v, ok := cfg["domains"]; ok {
  298. if domains, ok := v.Value().([]string); ok {
  299. p.domains = domains
  300. }
  301. }
  302. if v, ok := cfg["priority"]; ok {
  303. if prio, ok := v.Value().(int32); ok {
  304. p.priority = prio
  305. }
  306. }
  307. order = append(order, p)
  308. }
  309. sort.Slice(order, func(i, j int) bool {
  310. return order[i].priority < order[j].priority
  311. })
  312. var (
  313. ret OSConfig
  314. seenResolvers = map[netip.Addr]bool{}
  315. seenSearch = map[string]bool{}
  316. )
  317. for _, cfg := range order {
  318. for _, resolver := range cfg.resolvers {
  319. if seenResolvers[resolver] {
  320. continue
  321. }
  322. ret.Nameservers = append(ret.Nameservers, resolver)
  323. seenResolvers[resolver] = true
  324. }
  325. for _, dom := range cfg.domains {
  326. if seenSearch[dom] {
  327. continue
  328. }
  329. fqdn, err := dnsname.ToFQDN(dom)
  330. if err != nil {
  331. continue
  332. }
  333. ret.SearchDomains = append(ret.SearchDomains, fqdn)
  334. seenSearch[dom] = true
  335. }
  336. if cfg.priority < 0 {
  337. // exclusive configurations preempt all other
  338. // configurations, so we're done.
  339. break
  340. }
  341. }
  342. return ret, nil
  343. }
  344. func (m *nmManager) Close() error {
  345. // No need to do anything on close, NetworkManager will delete our
  346. // settings when the tailscale interface goes away.
  347. return nil
  348. }
  349. func nmVersionBetween(first, last string) (bool, error) {
  350. conn, err := dbus.SystemBus()
  351. if err != nil {
  352. // DBus probably not running.
  353. return false, err
  354. }
  355. nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager"))
  356. v, err := nm.GetProperty("org.freedesktop.NetworkManager.Version")
  357. if err != nil {
  358. return false, err
  359. }
  360. version, ok := v.Value().(string)
  361. if !ok {
  362. return false, fmt.Errorf("unexpected type %T for NM version", v.Value())
  363. }
  364. outside := cmpver.Compare(version, first) < 0 || cmpver.Compare(version, last) > 0
  365. return !outside, nil
  366. }
  367. func nmIsUsingResolved() error {
  368. conn, err := dbus.SystemBus()
  369. if err != nil {
  370. // DBus probably not running.
  371. return err
  372. }
  373. nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager/DnsManager"))
  374. v, err := nm.GetProperty("org.freedesktop.NetworkManager.DnsManager.Mode")
  375. if err != nil {
  376. return fmt.Errorf("getting NM mode: %w", err)
  377. }
  378. mode, ok := v.Value().(string)
  379. if !ok {
  380. return fmt.Errorf("unexpected type %T for NM DNS mode", v.Value())
  381. }
  382. if mode != "systemd-resolved" {
  383. return errors.New("NetworkManager is not using systemd-resolved for DNS")
  384. }
  385. return nil
  386. }