ping.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package ping allows sending ICMP echo requests to a host in order to
  4. // determine network latency.
  5. package ping
  6. import (
  7. "bytes"
  8. "context"
  9. "crypto/rand"
  10. "encoding/binary"
  11. "errors"
  12. "fmt"
  13. "io"
  14. "log"
  15. "net"
  16. "net/netip"
  17. "sync"
  18. "sync/atomic"
  19. "time"
  20. "golang.org/x/net/icmp"
  21. "golang.org/x/net/ipv4"
  22. "golang.org/x/net/ipv6"
  23. "tailscale.com/syncs"
  24. "tailscale.com/types/logger"
  25. "tailscale.com/util/mak"
  26. )
  27. const (
  28. v4Type = "ip4:icmp"
  29. v6Type = "ip6:icmp"
  30. )
  31. type response struct {
  32. t time.Time
  33. err error
  34. }
  35. type outstanding struct {
  36. ch chan response
  37. data []byte
  38. }
  39. // PacketListener defines the interface required to listen to packages
  40. // on an address.
  41. type ListenPacketer interface {
  42. ListenPacket(ctx context.Context, typ string, addr string) (net.PacketConn, error)
  43. }
  44. // Pinger represents a set of ICMP echo requests to be sent at a single time.
  45. //
  46. // A new instance should be created for each concurrent set of ping requests;
  47. // this type should not be reused.
  48. type Pinger struct {
  49. lp ListenPacketer
  50. // closed guards against send incrementing the waitgroup concurrently with close.
  51. closed atomic.Bool
  52. Logf logger.Logf
  53. Verbose bool
  54. timeNow func() time.Time
  55. id uint16 // uint16 per RFC 792
  56. wg sync.WaitGroup
  57. // Following fields protected by mu
  58. mu syncs.Mutex
  59. // conns is a map of "type" to net.PacketConn, type is either
  60. // "ip4:icmp" or "ip6:icmp"
  61. conns map[string]net.PacketConn
  62. seq uint16 // uint16 per RFC 792
  63. pings map[uint16]outstanding
  64. }
  65. // New creates a new Pinger. The Context provided will be used to create
  66. // network listeners, and to set an absolute deadline (if any) on the net.Conn
  67. func New(ctx context.Context, logf logger.Logf, lp ListenPacketer) *Pinger {
  68. var id [2]byte
  69. if _, err := io.ReadFull(rand.Reader, id[:]); err != nil {
  70. panic("net/ping: New:" + err.Error())
  71. }
  72. return &Pinger{
  73. lp: lp,
  74. Logf: logf,
  75. timeNow: time.Now,
  76. id: binary.LittleEndian.Uint16(id[:]),
  77. pings: make(map[uint16]outstanding),
  78. }
  79. }
  80. func (p *Pinger) mkconn(ctx context.Context, typ, addr string) (net.PacketConn, error) {
  81. if p.closed.Load() {
  82. return nil, net.ErrClosed
  83. }
  84. c, err := p.lp.ListenPacket(ctx, typ, addr)
  85. if err != nil {
  86. return nil, err
  87. }
  88. // Start by setting the deadline from the context; note that this
  89. // applies to all future I/O, so we only need to do it once.
  90. deadline, ok := ctx.Deadline()
  91. if ok {
  92. if err := c.SetReadDeadline(deadline); err != nil {
  93. return nil, err
  94. }
  95. }
  96. p.wg.Add(1)
  97. go p.run(ctx, c, typ)
  98. return c, err
  99. }
  100. // getConn creates or returns a conn matching typ which is ip4:icmp
  101. // or ip6:icmp.
  102. func (p *Pinger) getConn(ctx context.Context, typ string) (net.PacketConn, error) {
  103. p.mu.Lock()
  104. defer p.mu.Unlock()
  105. if c, ok := p.conns[typ]; ok {
  106. return c, nil
  107. }
  108. var addr = "0.0.0.0"
  109. if typ == v6Type {
  110. addr = "::"
  111. }
  112. c, err := p.mkconn(ctx, typ, addr)
  113. if err != nil {
  114. return nil, err
  115. }
  116. mak.Set(&p.conns, typ, c)
  117. return c, nil
  118. }
  119. func (p *Pinger) logf(format string, a ...any) {
  120. if p.Logf != nil {
  121. p.Logf(format, a...)
  122. } else {
  123. log.Printf(format, a...)
  124. }
  125. }
  126. func (p *Pinger) vlogf(format string, a ...any) {
  127. if p.Verbose {
  128. p.logf(format, a...)
  129. }
  130. }
  131. func (p *Pinger) Close() error {
  132. p.closed.Store(true)
  133. p.mu.Lock()
  134. conns := p.conns
  135. p.conns = nil
  136. p.mu.Unlock()
  137. var errs []error
  138. for _, c := range conns {
  139. if err := c.Close(); err != nil {
  140. errs = append(errs, err)
  141. }
  142. }
  143. p.wg.Wait()
  144. p.cleanupOutstanding()
  145. return errors.Join(errs...)
  146. }
  147. func (p *Pinger) run(ctx context.Context, conn net.PacketConn, typ string) {
  148. defer p.wg.Done()
  149. defer func() {
  150. conn.Close()
  151. p.mu.Lock()
  152. delete(p.conns, typ)
  153. p.mu.Unlock()
  154. }()
  155. buf := make([]byte, 1500)
  156. loop:
  157. for {
  158. select {
  159. case <-ctx.Done():
  160. break loop
  161. default:
  162. }
  163. n, _, err := conn.ReadFrom(buf)
  164. if err != nil {
  165. // Ignore temporary errors; everything else is fatal
  166. if netErr, ok := err.(net.Error); !ok || !netErr.Temporary() {
  167. break
  168. }
  169. continue
  170. }
  171. p.handleResponse(buf[:n], p.timeNow(), typ)
  172. }
  173. }
  174. func (p *Pinger) cleanupOutstanding() {
  175. // Complete outstanding requests
  176. p.mu.Lock()
  177. defer p.mu.Unlock()
  178. for _, o := range p.pings {
  179. o.ch <- response{err: net.ErrClosed}
  180. }
  181. }
  182. func (p *Pinger) handleResponse(buf []byte, now time.Time, typ string) {
  183. // We need to handle responding to both IPv4
  184. // and IPv6.
  185. var icmpType icmp.Type
  186. switch typ {
  187. case v4Type:
  188. icmpType = ipv4.ICMPTypeEchoReply
  189. case v6Type:
  190. icmpType = ipv6.ICMPTypeEchoReply
  191. default:
  192. p.vlogf("handleResponse: unknown icmp.Type")
  193. return
  194. }
  195. m, err := icmp.ParseMessage(icmpType.Protocol(), buf)
  196. if err != nil {
  197. p.vlogf("handleResponse: invalid packet: %v", err)
  198. return
  199. }
  200. if m.Type != icmpType {
  201. p.vlogf("handleResponse: wanted m.Type=%d; got %d", icmpType, m.Type)
  202. return
  203. }
  204. resp, ok := m.Body.(*icmp.Echo)
  205. if !ok || resp == nil {
  206. p.vlogf("handleResponse: wanted body=*icmp.Echo; got %v", m.Body)
  207. return
  208. }
  209. // We assume we sent this if the ID in the response is ours.
  210. if uint16(resp.ID) != p.id {
  211. p.vlogf("handleResponse: wanted ID=%d; got %d", p.id, resp.ID)
  212. return
  213. }
  214. // Search for existing running echo request
  215. var o outstanding
  216. p.mu.Lock()
  217. if o, ok = p.pings[uint16(resp.Seq)]; ok {
  218. // Ensure that the data matches before we delete from our map,
  219. // so a future correct packet will be handled correctly.
  220. if bytes.Equal(resp.Data, o.data) {
  221. delete(p.pings, uint16(resp.Seq))
  222. } else {
  223. p.vlogf("handleResponse: got response for Seq %d with mismatched data", resp.Seq)
  224. ok = false
  225. }
  226. } else {
  227. p.vlogf("handleResponse: got response for unknown Seq %d", resp.Seq)
  228. }
  229. p.mu.Unlock()
  230. if ok {
  231. o.ch <- response{t: now}
  232. }
  233. }
  234. // Send sends an ICMP Echo Request packet to the destination, waits for a
  235. // response, and returns the duration between when the request was sent and
  236. // when the reply was received.
  237. //
  238. // If provided, "data" is sent with the packet and is compared upon receiving a
  239. // reply.
  240. func (p *Pinger) Send(ctx context.Context, dest net.Addr, data []byte) (time.Duration, error) {
  241. // Use sequential sequence numbers on the assumption that we will not
  242. // wrap around when using a single Pinger instance
  243. p.mu.Lock()
  244. p.seq++
  245. seq := p.seq
  246. p.mu.Unlock()
  247. // Check whether the address is IPv4 or IPv6 to
  248. // determine the icmp.Type and conn to use.
  249. var conn net.PacketConn
  250. var icmpType icmp.Type = ipv4.ICMPTypeEcho
  251. ap, err := netip.ParseAddr(dest.String())
  252. if err != nil {
  253. return 0, err
  254. }
  255. if ap.Is6() {
  256. icmpType = ipv6.ICMPTypeEchoRequest
  257. conn, err = p.getConn(ctx, v6Type)
  258. } else {
  259. conn, err = p.getConn(ctx, v4Type)
  260. }
  261. if err != nil {
  262. return 0, err
  263. }
  264. m := icmp.Message{
  265. Type: icmpType,
  266. Code: 0,
  267. Body: &icmp.Echo{
  268. ID: int(p.id),
  269. Seq: int(seq),
  270. Data: data,
  271. },
  272. }
  273. b, err := m.Marshal(nil)
  274. if err != nil {
  275. return 0, err
  276. }
  277. // Register our response before sending since we could otherwise race a
  278. // quick reply.
  279. ch := make(chan response, 1)
  280. p.mu.Lock()
  281. p.pings[seq] = outstanding{ch: ch, data: data}
  282. p.mu.Unlock()
  283. start := p.timeNow()
  284. n, err := conn.WriteTo(b, dest)
  285. if err != nil {
  286. return 0, err
  287. } else if n != len(b) {
  288. return 0, fmt.Errorf("conn.WriteTo: got %v; want %v", n, len(b))
  289. }
  290. select {
  291. case resp := <-ch:
  292. if resp.err != nil {
  293. return 0, resp.err
  294. }
  295. return resp.t.Sub(start), nil
  296. case <-ctx.Done():
  297. return 0, ctx.Err()
  298. }
  299. }