pendopen.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package wgengine
  4. import (
  5. "fmt"
  6. "runtime"
  7. "time"
  8. "tailscale.com/net/flowtrack"
  9. "tailscale.com/net/packet"
  10. "tailscale.com/net/tsaddr"
  11. "tailscale.com/net/tstun"
  12. "tailscale.com/types/ipproto"
  13. "tailscale.com/util/mak"
  14. "tailscale.com/wgengine/filter"
  15. )
  16. const tcpTimeoutBeforeDebug = 5 * time.Second
  17. type pendingOpenFlow struct {
  18. timer *time.Timer // until giving up on the flow
  19. // guarded by userspaceEngine.mu:
  20. // problem is non-zero if we got a MaybeBroken (non-terminal)
  21. // TSMP "reject" header.
  22. problem packet.TailscaleRejectReason
  23. }
  24. func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) {
  25. e.mu.Lock()
  26. defer e.mu.Unlock()
  27. of, ok := e.pendOpen[f]
  28. if !ok {
  29. // Not a tracked flow (likely already removed)
  30. return false
  31. }
  32. of.timer.Stop()
  33. delete(e.pendOpen, f)
  34. return true
  35. }
  36. func (e *userspaceEngine) noteFlowProblemFromPeer(f flowtrack.Tuple, problem packet.TailscaleRejectReason) {
  37. e.mu.Lock()
  38. defer e.mu.Unlock()
  39. of, ok := e.pendOpen[f]
  40. if !ok {
  41. // Not a tracked flow (likely already removed)
  42. return
  43. }
  44. of.problem = problem
  45. }
  46. func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.Wrapper) (res filter.Response) {
  47. res = filter.Accept // always
  48. if pp.IPProto == ipproto.TSMP {
  49. res = filter.DropSilently
  50. rh, ok := pp.AsTailscaleRejectedHeader()
  51. if !ok {
  52. return
  53. }
  54. if rh.MaybeBroken {
  55. e.noteFlowProblemFromPeer(rh.Flow(), rh.Reason)
  56. } else if f := rh.Flow(); e.removeFlow(f) {
  57. e.logf("open-conn-track: flow %v %v > %v rejected due to %v", rh.Proto, rh.Src, rh.Dst, rh.Reason)
  58. }
  59. return
  60. }
  61. if pp.IPVersion == 0 ||
  62. pp.IPProto != ipproto.TCP ||
  63. pp.TCPFlags&(packet.TCPSyn|packet.TCPRst) == 0 {
  64. return
  65. }
  66. // Either a SYN or a RST came back. Remove it in either case.
  67. f := flowtrack.Tuple{Proto: pp.IPProto, Dst: pp.Src, Src: pp.Dst} // src/dst reversed
  68. removed := e.removeFlow(f)
  69. if removed && pp.TCPFlags&packet.TCPRst != 0 {
  70. e.logf("open-conn-track: flow TCP %v got RST by peer", f)
  71. }
  72. return
  73. }
  74. func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.Wrapper) (res filter.Response) {
  75. res = filter.Accept // always
  76. if pp.IPVersion == 0 ||
  77. pp.IPProto != ipproto.TCP ||
  78. pp.TCPFlags&packet.TCPAck != 0 ||
  79. pp.TCPFlags&packet.TCPSyn == 0 {
  80. return
  81. }
  82. flow := flowtrack.Tuple{Proto: pp.IPProto, Src: pp.Src, Dst: pp.Dst}
  83. // iOS likes to probe Apple IPs on all interfaces to check for connectivity.
  84. // Don't start timers tracking those. They won't succeed anyway. Avoids log spam
  85. // like:
  86. // open-conn-track: timeout opening (100.115.73.60:52501 => 17.125.252.5:443); no associated peer node
  87. if runtime.GOOS == "ios" && flow.Dst.Port() == 443 && !tsaddr.IsTailscaleIP(flow.Dst.Addr()) {
  88. if _, ok := e.PeerForIP(flow.Dst.Addr()); !ok {
  89. return
  90. }
  91. }
  92. timer := time.AfterFunc(tcpTimeoutBeforeDebug, func() {
  93. e.onOpenTimeout(flow)
  94. })
  95. e.mu.Lock()
  96. defer e.mu.Unlock()
  97. if _, dup := e.pendOpen[flow]; dup {
  98. // Duplicates are expected when the OS retransmits. Ignore.
  99. return
  100. }
  101. mak.Set(&e.pendOpen, flow, &pendingOpenFlow{timer: timer})
  102. return filter.Accept
  103. }
  104. func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
  105. e.mu.Lock()
  106. of, ok := e.pendOpen[flow]
  107. if !ok {
  108. // Not a tracked flow, or already handled & deleted.
  109. e.mu.Unlock()
  110. return
  111. }
  112. delete(e.pendOpen, flow)
  113. problem := of.problem
  114. e.mu.Unlock()
  115. if !problem.IsZero() {
  116. e.logf("open-conn-track: timeout opening %v; peer reported problem: %v", flow, problem)
  117. }
  118. // Diagnose why it might've timed out.
  119. pip, ok := e.PeerForIP(flow.Dst.Addr())
  120. if !ok {
  121. e.logf("open-conn-track: timeout opening %v; no associated peer node", flow)
  122. return
  123. }
  124. n := pip.Node
  125. if n.DiscoKey.IsZero() {
  126. e.logf("open-conn-track: timeout opening %v; peer node %v running pre-0.100", flow, n.Key.ShortString())
  127. return
  128. }
  129. if n.DERP == "" {
  130. e.logf("open-conn-track: timeout opening %v; peer node %v not connected to any DERP relay", flow, n.Key.ShortString())
  131. return
  132. }
  133. ps, found := e.getPeerStatusLite(n.Key)
  134. if !found {
  135. onlyZeroRoute := true // whether peerForIP returned n only because its /0 route matched
  136. for _, r := range n.AllowedIPs {
  137. if r.Bits() != 0 && r.Contains(flow.Dst.Addr()) {
  138. onlyZeroRoute = false
  139. break
  140. }
  141. }
  142. if onlyZeroRoute {
  143. // This node was returned by peerForIP because
  144. // its exit node /0 route(s) matched, but this
  145. // might not be the exit node that's currently
  146. // selected. Rather than log misleading
  147. // errors, just don't log at all for now.
  148. // TODO(bradfitz): update this code to be
  149. // exit-node-aware and make peerForIP return
  150. // the node of the currently selected exit
  151. // node.
  152. return
  153. }
  154. e.logf("open-conn-track: timeout opening %v; target node %v in netmap but unknown to WireGuard", flow, n.Key.ShortString())
  155. return
  156. }
  157. // TODO(bradfitz): figure out what PeerStatus.LastHandshake
  158. // is. It appears to be the last time we sent a handshake,
  159. // which isn't what I expected. I thought it was when a
  160. // handshake completed, which is what I want.
  161. _ = ps.LastHandshake
  162. online := "?"
  163. if n.Online != nil {
  164. if *n.Online {
  165. online = "yes"
  166. } else {
  167. online = "no"
  168. }
  169. }
  170. if n.LastSeen != nil && online != "yes" {
  171. online += fmt.Sprintf(", lastseen=%v", durFmt(*n.LastSeen))
  172. }
  173. e.logf("open-conn-track: timeout opening %v to node %v; online=%v, lastRecv=%v",
  174. flow, n.Key.ShortString(),
  175. online,
  176. e.magicConn.LastRecvActivityOfNodeKey(n.Key))
  177. }
  178. func durFmt(t time.Time) string {
  179. if t.IsZero() {
  180. return "never"
  181. }
  182. d := time.Since(t).Round(time.Second)
  183. if d < 10*time.Minute {
  184. // node.LastSeen times are rounded very coarsely, and
  185. // we compare times from different clocks (server vs
  186. // local), so negative is common when close. Format as
  187. // "recent" if negative or actually recent.
  188. return "recent"
  189. }
  190. return d.String()
  191. }