netmon_windows.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package netmon
  4. import (
  5. "context"
  6. "errors"
  7. "strings"
  8. "sync"
  9. "time"
  10. "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
  11. "tailscale.com/net/tsaddr"
  12. "tailscale.com/types/logger"
  13. )
  14. var (
  15. errClosed = errors.New("closed")
  16. )
  17. type eventMessage struct {
  18. eventType string
  19. }
  20. func (eventMessage) ignore() bool { return false }
  21. type winMon struct {
  22. logf logger.Logf
  23. ctx context.Context
  24. cancel context.CancelFunc
  25. isActive func() bool
  26. messagec chan eventMessage
  27. addressChangeCallback *winipcfg.UnicastAddressChangeCallback
  28. routeChangeCallback *winipcfg.RouteChangeCallback
  29. mu sync.Mutex
  30. lastLog time.Time // time we last logged about any windows change event
  31. // noDeadlockTicker exists just to have something scheduled as
  32. // far as the Go runtime is concerned. Otherwise "tailscaled
  33. // debug --monitor" thinks it's deadlocked with nothing to do,
  34. // as Go's runtime doesn't know about callbacks registered with
  35. // Windows.
  36. noDeadlockTicker *time.Ticker
  37. }
  38. func newOSMon(logf logger.Logf, pm *Monitor) (osMon, error) {
  39. m := &winMon{
  40. logf: logf,
  41. isActive: pm.isActive,
  42. messagec: make(chan eventMessage, 1),
  43. noDeadlockTicker: time.NewTicker(5000 * time.Hour), // arbitrary
  44. }
  45. m.ctx, m.cancel = context.WithCancel(context.Background())
  46. var err error
  47. m.addressChangeCallback, err = winipcfg.RegisterUnicastAddressChangeCallback(m.unicastAddressChanged)
  48. if err != nil {
  49. m.logf("winipcfg.RegisterUnicastAddressChangeCallback error: %v", err)
  50. m.cancel()
  51. return nil, err
  52. }
  53. m.routeChangeCallback, err = winipcfg.RegisterRouteChangeCallback(m.routeChanged)
  54. if err != nil {
  55. m.addressChangeCallback.Unregister()
  56. m.logf("winipcfg.RegisterRouteChangeCallback error: %v", err)
  57. m.cancel()
  58. return nil, err
  59. }
  60. return m, nil
  61. }
  62. func (m *winMon) IsInterestingInterface(iface string) bool { return true }
  63. func (m *winMon) Close() (ret error) {
  64. m.cancel()
  65. m.noDeadlockTicker.Stop()
  66. if m.addressChangeCallback != nil {
  67. if err := m.addressChangeCallback.Unregister(); err != nil {
  68. m.logf("addressChangeCallback.Unregister error: %v", err)
  69. ret = err
  70. } else {
  71. m.addressChangeCallback = nil
  72. }
  73. }
  74. if m.routeChangeCallback != nil {
  75. if err := m.routeChangeCallback.Unregister(); err != nil {
  76. m.logf("routeChangeCallback.Unregister error: %v", err)
  77. ret = err
  78. } else {
  79. m.routeChangeCallback = nil
  80. }
  81. }
  82. return
  83. }
  84. func (m *winMon) Receive() (message, error) {
  85. if m.ctx.Err() != nil {
  86. m.logf("Receive call on closed monitor")
  87. return nil, errClosed
  88. }
  89. t0 := time.Now()
  90. select {
  91. case msg := <-m.messagec:
  92. now := time.Now()
  93. m.mu.Lock()
  94. sinceLast := now.Sub(m.lastLog)
  95. m.lastLog = now
  96. m.mu.Unlock()
  97. // If it's either been awhile since we last logged
  98. // anything, or if this some route/addr that's not
  99. // about a Tailscale IP ("ts" prefix), then log. This
  100. // is mainly limited to suppress the flood about our own
  101. // route updates after connecting to a large tailnet
  102. // and all the IPv4 /32 routes.
  103. if sinceLast > 5*time.Second || !strings.HasPrefix(msg.eventType, "ts") {
  104. m.logf("got windows change event after %v: evt=%s", time.Since(t0).Round(time.Millisecond), msg.eventType)
  105. }
  106. return msg, nil
  107. case <-m.ctx.Done():
  108. return nil, errClosed
  109. }
  110. }
  111. // unicastAddressChanged is the callback we register with Windows to call when unicast address changes.
  112. func (m *winMon) unicastAddressChanged(_ winipcfg.MibNotificationType, row *winipcfg.MibUnicastIPAddressRow) {
  113. if !m.isActive() {
  114. // Avoid starting a goroutine that sends events to messagec,
  115. // or sending messages to messagec directly, if the monitor
  116. // hasn't started and Receive is not yet reading from messagec.
  117. //
  118. // Doing so can lead to goroutine leaks or deadlocks, especially
  119. // if the monitor is never started.
  120. return
  121. }
  122. what := "addr"
  123. if ip := row.Address.Addr(); ip.IsValid() && tsaddr.IsTailscaleIP(ip.Unmap()) {
  124. what = "tsaddr"
  125. }
  126. // start a goroutine to finish our work, to return to Windows out of this callback
  127. go m.somethingChanged(what)
  128. }
  129. // routeChanged is the callback we register with Windows to call when route changes.
  130. func (m *winMon) routeChanged(_ winipcfg.MibNotificationType, row *winipcfg.MibIPforwardRow2) {
  131. if !m.isActive() {
  132. // Avoid starting a goroutine that sends events to messagec,
  133. // or sending messages to messagec directly, if the monitor
  134. // hasn't started and Receive is not yet reading from messagec.
  135. //
  136. // Doing so can lead to goroutine leaks or deadlocks, especially
  137. // if the monitor is never started.
  138. return
  139. }
  140. what := "route"
  141. ip := row.DestinationPrefix.Prefix().Addr().Unmap()
  142. if ip.IsValid() && tsaddr.IsTailscaleIP(ip) {
  143. what = "tsroute"
  144. }
  145. // start a goroutine to finish our work, to return to Windows out of this callback
  146. go m.somethingChanged(what)
  147. }
  148. // somethingChanged gets called from OS callbacks whenever address or route changes.
  149. func (m *winMon) somethingChanged(evt string) {
  150. select {
  151. case <-m.ctx.Done():
  152. return
  153. case m.messagec <- eventMessage{eventType: evt}:
  154. return
  155. }
  156. }
  157. // isActive reports whether this monitor has been started and not yet closed.
  158. func (m *Monitor) isActive() bool {
  159. m.mu.Lock()
  160. defer m.mu.Unlock()
  161. return m.started && !m.closed
  162. }