pendopen.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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.MakeTuple(pp.IPProto, pp.Dst, pp.Src) // 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.MakeTuple(pp.IPProto, pp.Src, 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.DstPort() == 443 && !tsaddr.IsTailscaleIP(flow.DstAddr()) {
  88. if _, ok := e.PeerForIP(flow.DstAddr()); !ok {
  89. return
  90. }
  91. }
  92. e.mu.Lock()
  93. defer e.mu.Unlock()
  94. if _, dup := e.pendOpen[flow]; dup {
  95. // Duplicates are expected when the OS retransmits. Ignore.
  96. return
  97. }
  98. timer := time.AfterFunc(tcpTimeoutBeforeDebug, func() {
  99. e.onOpenTimeout(flow)
  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.DstAddr())
  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.IsWireGuardOnly() {
  126. if n.DiscoKey().IsZero() {
  127. e.logf("open-conn-track: timeout opening %v; peer node %v running pre-0.100", flow, n.Key().ShortString())
  128. return
  129. }
  130. if n.DERP() == "" {
  131. e.logf("open-conn-track: timeout opening %v; peer node %v not connected to any DERP relay", flow, n.Key().ShortString())
  132. return
  133. }
  134. }
  135. ps, found := e.getPeerStatusLite(n.Key())
  136. if !found {
  137. onlyZeroRoute := true // whether peerForIP returned n only because its /0 route matched
  138. for i := range n.AllowedIPs().Len() {
  139. r := n.AllowedIPs().At(i)
  140. if r.Bits() != 0 && r.Contains(flow.DstAddr()) {
  141. onlyZeroRoute = false
  142. break
  143. }
  144. }
  145. if onlyZeroRoute {
  146. // This node was returned by peerForIP because
  147. // its exit node /0 route(s) matched, but this
  148. // might not be the exit node that's currently
  149. // selected. Rather than log misleading
  150. // errors, just don't log at all for now.
  151. // TODO(bradfitz): update this code to be
  152. // exit-node-aware and make peerForIP return
  153. // the node of the currently selected exit
  154. // node.
  155. return
  156. }
  157. e.logf("open-conn-track: timeout opening %v; target node %v in netmap but unknown to WireGuard", flow, n.Key().ShortString())
  158. return
  159. }
  160. // TODO(bradfitz): figure out what PeerStatus.LastHandshake
  161. // is. It appears to be the last time we sent a handshake,
  162. // which isn't what I expected. I thought it was when a
  163. // handshake completed, which is what I want.
  164. _ = ps.LastHandshake
  165. online := "?"
  166. if n.IsWireGuardOnly() {
  167. online = "wg"
  168. } else {
  169. if v := n.Online(); v != nil {
  170. if *v {
  171. online = "yes"
  172. } else {
  173. online = "no"
  174. }
  175. }
  176. if n.LastSeen() != nil && online != "yes" {
  177. online += fmt.Sprintf(", lastseen=%v", durFmt(*n.LastSeen()))
  178. }
  179. }
  180. e.logf("open-conn-track: timeout opening %v to node %v; online=%v, lastRecv=%v",
  181. flow, n.Key().ShortString(),
  182. online,
  183. e.magicConn.LastRecvActivityOfNodeKey(n.Key()))
  184. }
  185. func durFmt(t time.Time) string {
  186. if t.IsZero() {
  187. return "never"
  188. }
  189. d := time.Since(t).Round(time.Second)
  190. if d < 10*time.Minute {
  191. // node.LastSeen times are rounded very coarsely, and
  192. // we compare times from different clocks (server vs
  193. // local), so negative is common when close. Format as
  194. // "recent" if negative or actually recent.
  195. return "recent"
  196. }
  197. return d.String()
  198. }