endpoint.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package localapi
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "log/slog"
  8. "net"
  9. "sync"
  10. )
  11. // endpoint is the lifecycle the two local APIs share: one socket at a path, bound and
  12. // given up under a mutex, moved by binding the new path before the old one is released,
  13. // and stopped when the context that brought it up ends. What is served on the socket,
  14. // and by what, is the embedding server's: it hands rebindLocked a serve function and
  15. // gets back the thing to close.
  16. type endpoint struct {
  17. l *slog.Logger
  18. // name is what the endpoint is called in its own log lines and errors, so the two
  19. // read apart.
  20. name string
  21. mu sync.Mutex
  22. // closer stops the serving when closed: the listener itself, or the server that owns
  23. // it. It is nil while the endpoint is stopped, which is what stopped means here.
  24. closer io.Closer
  25. path string
  26. access Access
  27. unwatch func() bool
  28. // id identifies the node listen created at path, so a reload can tell whether the
  29. // socket is still there and Stop whether the node it would unlink is still ours.
  30. id socketID
  31. }
  32. // socketID identifies a socket's node on the filesystem, by device and inode on unix.
  33. // The zero value is no identity: a platform with no node to identify, or one that could
  34. // not be read, and removeSocket then unlinks without looking.
  35. type socketID struct {
  36. dev, ino uint64
  37. }
  38. // Path returns the socket path currently being served, or "" when stopped.
  39. func (e *endpoint) Path() string {
  40. e.mu.Lock()
  41. defer e.mu.Unlock()
  42. return e.path
  43. }
  44. // rebindLocked brings the endpoint up at path with acc, moves it there, or, when it is
  45. // already there, applies a changed access. It is the one place a socket is installed and
  46. // the one way an endpoint comes up, so Wire's first attempt and its reload callback are
  47. // the same call, and an endpoint already at path is left as it is: Wire registers the
  48. // callback before the first attempt, so a HUP that lands between the two has the
  49. // callback do the first bind, and the first attempt then finds the socket it was about
  50. // to create already up.
  51. //
  52. // The new socket is bound before the old one is given up: a bad path in a reloaded
  53. // config leaves the operator with the endpoint they already had rather than none. serve
  54. // is handed the new listener once it is certain to be kept and returns what to close to
  55. // stop serving it; what becomes of the old socket's connections is that closer's.
  56. //
  57. // The caller holds e.mu for the whole operation, bind included. Releasing it around
  58. // listen would let a Stop land in between, and this would then install a fresh socket on
  59. // an endpoint that had just shut down, leaving a live socket on disk until the process
  60. // exits. listen does not take e.mu, so holding it costs the other methods one bind's
  61. // wait, at most the stale socket probe, once per reload.
  62. func (e *endpoint) rebindLocked(ctx context.Context, path string, acc Access, serve func(net.Listener) io.Closer) error {
  63. reopening := false
  64. if e.closer != nil && e.path == path {
  65. if cur, ok := identifySocket(path); ok && cur == e.id {
  66. if e.access.equal(acc) {
  67. return nil
  68. }
  69. err := applyAccess(path, acc)
  70. if err == nil {
  71. e.access = acc
  72. e.l.Info("Changed the "+e.name+"'s socket access", "path", path, "access", acc.String())
  73. return nil
  74. }
  75. if !errors.Is(err, errReopenForAccess) {
  76. return fmt.Errorf("could not change the access of %s: %w", path, err)
  77. }
  78. // The platform fixes access when the endpoint is created, so it is created
  79. // again. The name cannot be held twice, so the old one goes first, and a bind
  80. // that then fails leaves the endpoint down rather than on the old access the
  81. // operator asked to change; that is reported below.
  82. reopening = true
  83. old := e.closer
  84. e.closer, e.path, e.access, e.id = nil, "", Access{}, socketID{}
  85. _ = old.Close()
  86. } else {
  87. // The node at the path is not the one this endpoint bound: something outside
  88. // nebula removed it, logind clearing /run/user/<uid> on logout or a unit
  89. // cleaning a RuntimeDirectory= it shares, or replaced it. The listener is still
  90. // open and nothing can reach it, so it is bound afresh below; listen's stale
  91. // check decides whether whatever is there now may be cleared.
  92. e.l.Warn("The "+e.name+"'s socket is no longer at its path, binding it again", "path", path)
  93. }
  94. }
  95. ln, err := listen(path, acc)
  96. if err != nil {
  97. if reopening {
  98. return fmt.Errorf("the %s was closed to change its access and could not be created again: %w", e.name, err)
  99. }
  100. return err
  101. }
  102. id, _ := identifySocket(path)
  103. // A reload that arrives during shutdown must not bring the endpoint back. Stop is
  104. // not terminal (a Rebind after Stop is allowed, and is how an endpoint whose first
  105. // bind failed comes up), so whether it is running is no guide; the context is what
  106. // shutdown cancels, and it is checked here, while nothing else can change the state.
  107. if err := ctx.Err(); err != nil {
  108. release(ln, path, id)
  109. return fmt.Errorf("not starting the %s, nebula is shutting down: %w", e.name, err)
  110. }
  111. old, oldPath, oldID := e.closer, e.path, e.id
  112. e.closer = serve(ln)
  113. e.path = path
  114. e.access = acc
  115. e.id = id
  116. e.watchLocked(ctx)
  117. // Stop serving the old socket. When it was on this same path its node is already
  118. // gone, and the node there now is the new socket's, so only the closer goes.
  119. if old != nil {
  120. if oldPath == path {
  121. _ = old.Close()
  122. } else {
  123. release(old, oldPath, oldID)
  124. }
  125. }
  126. return nil
  127. }
  128. // watchLocked stops the endpoint when ctx is cancelled. One watcher covers a run of the
  129. // endpoint, from the first bind that succeeds until Stop: a daemon whose initial listen
  130. // failed and which later Rebinds onto a good path from a reloaded config still needs its
  131. // socket removed on shutdown, and a Rebind on a running endpoint must not stack a second
  132. // watcher on the same ctx. Stop detaches the watcher, so an endpoint started again
  133. // afterwards is watched on the ctx that started it, and the ctx of an earlier run no
  134. // longer reaches it whenever that one ends. Callers hold e.mu.
  135. func (e *endpoint) watchLocked(ctx context.Context) {
  136. if e.unwatch != nil {
  137. return
  138. }
  139. e.unwatch = context.AfterFunc(ctx, e.Stop)
  140. }
  141. // Stop closes the endpoint and removes its socket. Work in flight is the closer's
  142. // business: a listener leaves the connections it already accepted to finish, a server
  143. // cuts them off.
  144. //
  145. // The whole of it runs under e.mu, closer and socket included, so a Rebind cannot land
  146. // between the state change and the close and find a path that is still bound by the
  147. // socket it is replacing.
  148. func (e *endpoint) Stop() {
  149. e.mu.Lock()
  150. defer e.mu.Unlock()
  151. if e.closer == nil {
  152. return
  153. }
  154. release(e.closer, e.path, e.id)
  155. e.closer = nil
  156. e.path = ""
  157. e.access = Access{}
  158. e.id = socketID{}
  159. // Set whenever closer is. When this Stop is the watcher's own, stopping a watcher
  160. // that has already fired is a no-op.
  161. e.unwatch()
  162. e.unwatch = nil
  163. }
  164. // release gives up a socket's closer and the node at its path. The node goes first,
  165. // while the socket is still open: for as long as it is, the node is this endpoint's,
  166. // since a starting daemon's stale check refuses to remove a socket that answers. The
  167. // other order opens a window between the close and the unlink in which a new daemon can
  168. // find the node stale, clear it and bind its own at the same path, which the unlink then
  169. // takes from under it, leaving that daemon serving a socket nothing can reach. listen
  170. // turns off the listener's own unlink on close so this is the only unlink. id is the
  171. // node listen created, and the only node removeSocket will remove: one that replaced it,
  172. // another daemon's socket bound after ours was taken from under us, is left alone.
  173. func release(c io.Closer, path string, id socketID) {
  174. removeSocket(path, id)
  175. _ = c.Close()
  176. }