blob: 96950ca0121fccfbf3471e95a59ce801078c3dc3 [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Package fastping is an ICMP ping library inspired by AnyEvent::FastPing Perl
2// module to send ICMP ECHO REQUEST packets quickly. Original Perl module is
3// available at
4// http://search.cpan.org/~mlehmann/AnyEvent-FastPing-2.01/
5//
6// It hasn't been fully implemented original functions yet.
7//
8// Here is an example:
9//
10// p := fastping.NewPinger()
11// ra, err := net.ResolveIPAddr("ip4:icmp", os.Args[1])
12// if err != nil {
13// fmt.Println(err)
14// os.Exit(1)
15// }
16// p.AddIPAddr(ra)
17// p.OnRecv = func(addr *net.IPAddr, rtt time.Duration) {
18// fmt.Printf("IP Addr: %s receive, RTT: %v\n", addr.String(), rtt)
19// }
20// p.OnIdle = func() {
21// fmt.Println("finish")
22// }
23// err = p.Run()
24// if err != nil {
25// fmt.Println(err)
26// }
27//
28// It sends an ICMP packet and wait a response. If it receives a response,
29// it calls "receive" callback. After that, MaxRTT time passed, it calls
30// "idle" callback. If you need more example, please see "cmd/ping/ping.go".
31//
32// This library needs to run as a superuser for sending ICMP packets when
33// privileged raw ICMP endpoints is used so in such a case, to run go test
34// for the package, please run like a following
35//
36// sudo go test
37//
38package fastping
39
40import (
41 "errors"
42 "fmt"
43 "log"
44 "math/rand"
45 "net"
46 "sync"
47 "syscall"
48 "time"
49
50 "golang.org/x/net/icmp"
51 "golang.org/x/net/ipv4"
52 "golang.org/x/net/ipv6"
53)
54
55const (
56 TimeSliceLength = 8
57 ProtocolICMP = 1
58 ProtocolIPv6ICMP = 58
59)
60
61var (
62 ipv4Proto = map[string]string{"ip": "ip4:icmp", "udp": "udp4"}
63 ipv6Proto = map[string]string{"ip": "ip6:ipv6-icmp", "udp": "udp6"}
64)
65
66func byteSliceOfSize(n int) []byte {
67 b := make([]byte, n)
68 for i := 0; i < len(b); i++ {
69 b[i] = 1
70 }
71
72 return b
73}
74
75func timeToBytes(t time.Time) []byte {
76 nsec := t.UnixNano()
77 b := make([]byte, 8)
78 for i := uint8(0); i < 8; i++ {
79 b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff)
80 }
81 return b
82}
83
84func bytesToTime(b []byte) time.Time {
85 var nsec int64
86 for i := uint8(0); i < 8; i++ {
87 nsec += int64(b[i]) << ((7 - i) * 8)
88 }
89 return time.Unix(nsec/1000000000, nsec%1000000000)
90}
91
92func isIPv4(ip net.IP) bool {
93 return len(ip.To4()) == net.IPv4len
94}
95
96func isIPv6(ip net.IP) bool {
97 return len(ip) == net.IPv6len
98}
99
100func ipv4Payload(b []byte) []byte {
101 if len(b) < ipv4.HeaderLen {
102 return b
103 }
104 hdrlen := int(b[0]&0x0f) << 2
105 return b[hdrlen:]
106}
107
108type packet struct {
109 bytes []byte
110 addr net.Addr
111}
112
113type context struct {
114 stop chan bool
115 done chan bool
116 err error
117}
118
119func newContext() *context {
120 return &context{
121 stop: make(chan bool),
122 done: make(chan bool),
123 }
124}
125
126// Pinger represents ICMP packet sender/receiver
127type Pinger struct {
128 id int
129 seq int
130 // key string is IPAddr.String()
131 addrs map[string]*net.IPAddr
132 network string
133 source string
134 source6 string
135 hasIPv4 bool
136 hasIPv6 bool
137 ctx *context
138 mu sync.Mutex
139
140 // Size in bytes of the payload to send
141 Size int
142 // Number of (nano,milli)seconds of an idle timeout. Once it passed,
143 // the library calls an idle callback function. It is also used for an
144 // interval time of RunLoop() method
145 MaxRTT time.Duration
146 // OnRecv is called with a response packet's source address and its
147 // elapsed time when Pinger receives a response packet.
148 OnRecv func(*net.IPAddr, time.Duration)
149 // OnIdle is called when MaxRTT time passed
150 OnIdle func()
151 // If Debug is true, it prints debug messages to stdout.
152 Debug bool
153}
154
155// NewPinger returns a new Pinger struct pointer
156func NewPinger() *Pinger {
157 rand.Seed(time.Now().UnixNano())
158 return &Pinger{
159 id: rand.Intn(0xffff),
160 seq: rand.Intn(0xffff),
161 addrs: make(map[string]*net.IPAddr),
162 network: "ip",
163 source: "",
164 source6: "",
165 hasIPv4: false,
166 hasIPv6: false,
167 Size: TimeSliceLength,
168 MaxRTT: time.Second,
169 OnRecv: nil,
170 OnIdle: nil,
171 Debug: false,
172 }
173}
174
175// Network sets a network endpoints for ICMP ping and returns the previous
176// setting. network arg should be "ip" or "udp" string or if others are
177// specified, it returns an error. If this function isn't called, Pinger
178// uses "ip" as default.
179func (p *Pinger) Network(network string) (string, error) {
180 origNet := p.network
181 switch network {
182 case "ip":
183 fallthrough
184 case "udp":
185 p.network = network
186 default:
187 return origNet, errors.New(network + " can't be used as ICMP endpoint")
188 }
189 return origNet, nil
190}
191
192// Source sets ipv4/ipv6 source IP for sending ICMP packets and returns the previous
193// setting. Empty value indicates to use system default one (for both ipv4 and ipv6).
194func (p *Pinger) Source(source string) (string, error) {
195 // using ipv4 previous value for new empty one
196 origSource := p.source
197 if "" == source {
198 p.mu.Lock()
199 p.source = ""
200 p.source6 = ""
201 p.mu.Unlock()
202 return origSource, nil
203 }
204
205 addr := net.ParseIP(source)
206 if addr == nil {
207 return origSource, errors.New(source + " is not a valid textual representation of an IPv4/IPv6 address")
208 }
209
210 if isIPv4(addr) {
211 p.mu.Lock()
212 p.source = source
213 p.mu.Unlock()
214 } else if isIPv6(addr) {
215 origSource = p.source6
216 p.mu.Lock()
217 p.source6 = source
218 p.mu.Unlock()
219 } else {
220 return origSource, errors.New(source + " is not a valid textual representation of an IPv4/IPv6 address")
221 }
222
223 return origSource, nil
224}
225
226// AddIP adds an IP address to Pinger. ipaddr arg should be a string like
227// "192.0.2.1".
228func (p *Pinger) AddIP(ipaddr string) error {
229 addr := net.ParseIP(ipaddr)
230 if addr == nil {
231 return fmt.Errorf("%s is not a valid textual representation of an IP address", ipaddr)
232 }
233 p.mu.Lock()
234 p.addrs[addr.String()] = &net.IPAddr{IP: addr}
235 if isIPv4(addr) {
236 p.hasIPv4 = true
237 } else if isIPv6(addr) {
238 p.hasIPv6 = true
239 }
240 p.mu.Unlock()
241 return nil
242}
243
244// AddIPAddr adds an IP address to Pinger. ip arg should be a net.IPAddr
245// pointer.
246func (p *Pinger) AddIPAddr(ip *net.IPAddr) {
247 p.mu.Lock()
248 p.addrs[ip.String()] = ip
249 if isIPv4(ip.IP) {
250 p.hasIPv4 = true
251 } else if isIPv6(ip.IP) {
252 p.hasIPv6 = true
253 }
254 p.mu.Unlock()
255}
256
257// RemoveIP removes an IP address from Pinger. ipaddr arg should be a string
258// like "192.0.2.1".
259func (p *Pinger) RemoveIP(ipaddr string) error {
260 addr := net.ParseIP(ipaddr)
261 if addr == nil {
262 return fmt.Errorf("%s is not a valid textual representation of an IP address", ipaddr)
263 }
264 p.mu.Lock()
265 delete(p.addrs, addr.String())
266 p.mu.Unlock()
267 return nil
268}
269
270// RemoveIPAddr removes an IP address from Pinger. ip arg should be a net.IPAddr
271// pointer.
272func (p *Pinger) RemoveIPAddr(ip *net.IPAddr) {
273 p.mu.Lock()
274 delete(p.addrs, ip.String())
275 p.mu.Unlock()
276}
277
278// AddHandler adds event handler to Pinger. event arg should be "receive" or
279// "idle" string.
280//
281// **CAUTION** This function is deprecated. Please use OnRecv and OnIdle field
282// of Pinger struct to set following handlers.
283//
284// "receive" handler should be
285//
286// func(addr *net.IPAddr, rtt time.Duration)
287//
288// type function. The handler is called with a response packet's source address
289// and its elapsed time when Pinger receives a response packet.
290//
291// "idle" handler should be
292//
293// func()
294//
295// type function. The handler is called when MaxRTT time passed. For more
296// detail, please see Run() and RunLoop().
297func (p *Pinger) AddHandler(event string, handler interface{}) error {
298 switch event {
299 case "receive":
300 if hdl, ok := handler.(func(*net.IPAddr, time.Duration)); ok {
301 p.mu.Lock()
302 p.OnRecv = hdl
303 p.mu.Unlock()
304 return nil
305 }
306 return errors.New("receive event handler should be `func(*net.IPAddr, time.Duration)`")
307 case "idle":
308 if hdl, ok := handler.(func()); ok {
309 p.mu.Lock()
310 p.OnIdle = hdl
311 p.mu.Unlock()
312 return nil
313 }
314 return errors.New("idle event handler should be `func()`")
315 }
316 return errors.New("No such event: " + event)
317}
318
319// Run invokes a single send/receive procedure. It sends packets to all hosts
320// which have already been added by AddIP() etc. and wait those responses. When
321// it receives a response, it calls "receive" handler registered by AddHander().
322// After MaxRTT seconds, it calls "idle" handler and returns to caller with
323// an error value. It means it blocks until MaxRTT seconds passed. For the
324// purpose of sending/receiving packets over and over, use RunLoop().
325func (p *Pinger) Run() error {
326 p.mu.Lock()
327 p.ctx = newContext()
328 p.mu.Unlock()
329 p.run(true)
330 p.mu.Lock()
331 defer p.mu.Unlock()
332 return p.ctx.err
333}
334
335// RunLoop invokes send/receive procedure repeatedly. It sends packets to all
336// hosts which have already been added by AddIP() etc. and wait those responses.
337// When it receives a response, it calls "receive" handler registered by
338// AddHander(). After MaxRTT seconds, it calls "idle" handler, resend packets
339// and wait those response. MaxRTT works as an interval time.
340//
341// This is a non-blocking method so immediately returns. If you want to monitor
342// and stop sending packets, use Done() and Stop() methods. For example,
343//
344// p.RunLoop()
345// ticker := time.NewTicker(time.Millisecond * 250)
346// select {
347// case <-p.Done():
348// if err := p.Err(); err != nil {
349// log.Fatalf("Ping failed: %v", err)
350// }
351// case <-ticker.C:
352// break
353// }
354// ticker.Stop()
355// p.Stop()
356//
357// For more details, please see "cmd/ping/ping.go".
358func (p *Pinger) RunLoop() {
359 p.mu.Lock()
360 p.ctx = newContext()
361 p.mu.Unlock()
362 go p.run(false)
363}
364
365// Done returns a channel that is closed when RunLoop() is stopped by an error
366// or Stop(). It must be called after RunLoop() call. If not, it causes panic.
367func (p *Pinger) Done() <-chan bool {
368 return p.ctx.done
369}
370
371// Stop stops RunLoop(). It must be called after RunLoop(). If not, it causes
372// panic.
373func (p *Pinger) Stop() {
374 p.debugln("Stop(): close(p.ctx.stop)")
375 close(p.ctx.stop)
376 p.debugln("Stop(): <-p.ctx.done")
377 <-p.ctx.done
378}
379
380// Err returns an error that is set by RunLoop(). It must be called after
381// RunLoop(). If not, it causes panic.
382func (p *Pinger) Err() error {
383 p.mu.Lock()
384 defer p.mu.Unlock()
385 return p.ctx.err
386}
387
388func (p *Pinger) listen(netProto string, source string) *icmp.PacketConn {
389 conn, err := icmp.ListenPacket(netProto, source)
390 if err != nil {
391 p.mu.Lock()
392 p.ctx.err = err
393 p.mu.Unlock()
394 p.debugln("Run(): close(p.ctx.done)")
395 close(p.ctx.done)
396 return nil
397 }
398 return conn
399}
400
401func (p *Pinger) run(once bool) {
402 p.debugln("Run(): Start")
403 var conn, conn6 *icmp.PacketConn
404 if p.hasIPv4 {
405 if conn = p.listen(ipv4Proto[p.network], p.source); conn == nil {
406 return
407 }
408 defer conn.Close()
409 }
410
411 if p.hasIPv6 {
412 if conn6 = p.listen(ipv6Proto[p.network], p.source6); conn6 == nil {
413 return
414 }
415 defer conn6.Close()
416 }
417
418 recv := make(chan *packet, 1)
419 recvCtx := newContext()
420 wg := new(sync.WaitGroup)
421
422 p.debugln("Run(): call recvICMP()")
423 if conn != nil {
424 wg.Add(1)
425 go p.recvICMP(conn, recv, recvCtx, wg)
426 }
427 if conn6 != nil {
428 wg.Add(1)
429 go p.recvICMP(conn6, recv, recvCtx, wg)
430 }
431
432 p.debugln("Run(): call sendICMP()")
433 queue, err := p.sendICMP(conn, conn6)
434
435 ticker := time.NewTicker(p.MaxRTT)
436
437mainloop:
438 for {
439 select {
440 case <-p.ctx.stop:
441 p.debugln("Run(): <-p.ctx.stop")
442 break mainloop
443 case <-recvCtx.done:
444 p.debugln("Run(): <-recvCtx.done")
445 p.mu.Lock()
446 err = recvCtx.err
447 p.mu.Unlock()
448 break mainloop
449 case <-ticker.C:
450 p.mu.Lock()
451 handler := p.OnIdle
452 p.mu.Unlock()
453 if handler != nil {
454 handler()
455 }
456 if once || err != nil {
457 break mainloop
458 }
459 p.debugln("Run(): call sendICMP()")
460 queue, err = p.sendICMP(conn, conn6)
461 case r := <-recv:
462 p.debugln("Run(): <-recv")
463 p.procRecv(r, queue)
464 }
465 }
466
467 ticker.Stop()
468
469 p.debugln("Run(): close(recvCtx.stop)")
470 close(recvCtx.stop)
471 p.debugln("Run(): wait recvICMP()")
472 wg.Wait()
473
474 p.mu.Lock()
475 p.ctx.err = err
476 p.mu.Unlock()
477
478 p.debugln("Run(): close(p.ctx.done)")
479 close(p.ctx.done)
480 p.debugln("Run(): End")
481}
482
483func (p *Pinger) sendICMP(conn, conn6 *icmp.PacketConn) (map[string]*net.IPAddr, error) {
484 p.debugln("sendICMP(): Start")
485 p.mu.Lock()
486 p.id = rand.Intn(0xffff)
487 p.seq = rand.Intn(0xffff)
488 p.mu.Unlock()
489 queue := make(map[string]*net.IPAddr)
490 wg := new(sync.WaitGroup)
491 for key, addr := range p.addrs {
492 var typ icmp.Type
493 var cn *icmp.PacketConn
494 if isIPv4(addr.IP) {
495 typ = ipv4.ICMPTypeEcho
496 cn = conn
497 } else if isIPv6(addr.IP) {
498 typ = ipv6.ICMPTypeEchoRequest
499 cn = conn6
500 } else {
501 continue
502 }
503 if cn == nil {
504 continue
505 }
506
507 t := timeToBytes(time.Now())
508
509 if p.Size-TimeSliceLength != 0 {
510 t = append(t, byteSliceOfSize(p.Size-TimeSliceLength)...)
511 }
512
513 p.mu.Lock()
514 bytes, err := (&icmp.Message{
515 Type: typ, Code: 0,
516 Body: &icmp.Echo{
517 ID: p.id, Seq: p.seq,
518 Data: t,
519 },
520 }).Marshal(nil)
521 p.mu.Unlock()
522 if err != nil {
523 wg.Wait()
524 return queue, err
525 }
526
527 queue[key] = addr
528 var dst net.Addr = addr
529 if p.network == "udp" {
530 dst = &net.UDPAddr{IP: addr.IP, Zone: addr.Zone}
531 }
532
533 p.debugln("sendICMP(): Invoke goroutine")
534 wg.Add(1)
535 go func(conn *icmp.PacketConn, ra net.Addr, b []byte) {
536 for {
537 if _, err := conn.WriteTo(bytes, ra); err != nil {
538 if neterr, ok := err.(*net.OpError); ok {
539 if neterr.Err == syscall.ENOBUFS {
540 continue
541 }
542 }
543 }
544 break
545 }
546 p.debugln("sendICMP(): WriteTo End")
547 wg.Done()
548 }(cn, dst, bytes)
549 }
550 wg.Wait()
551 p.debugln("sendICMP(): End")
552 return queue, nil
553}
554
555func (p *Pinger) recvICMP(conn *icmp.PacketConn, recv chan<- *packet, ctx *context, wg *sync.WaitGroup) {
556 p.debugln("recvICMP(): Start")
557 for {
558 select {
559 case <-ctx.stop:
560 p.debugln("recvICMP(): <-ctx.stop")
561 wg.Done()
562 p.debugln("recvICMP(): wg.Done()")
563 return
564 default:
565 }
566
567 bytes := make([]byte, 512)
568 conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100))
569 p.debugln("recvICMP(): ReadFrom Start")
570 _, ra, err := conn.ReadFrom(bytes)
571 p.debugln("recvICMP(): ReadFrom End")
572 if err != nil {
573 if neterr, ok := err.(*net.OpError); ok {
574 if neterr.Timeout() {
575 p.debugln("recvICMP(): Read Timeout")
576 continue
577 } else {
578 p.debugln("recvICMP(): OpError happen", err)
579 p.mu.Lock()
580 ctx.err = err
581 p.mu.Unlock()
582 p.debugln("recvICMP(): close(ctx.done)")
583 close(ctx.done)
584 p.debugln("recvICMP(): wg.Done()")
585 wg.Done()
586 return
587 }
588 }
589 }
590 p.debugln("recvICMP(): p.recv <- packet")
591
592 select {
593 case recv <- &packet{bytes: bytes, addr: ra}:
594 case <-ctx.stop:
595 p.debugln("recvICMP(): <-ctx.stop")
596 wg.Done()
597 p.debugln("recvICMP(): wg.Done()")
598 return
599 }
600 }
601}
602
603func (p *Pinger) procRecv(recv *packet, queue map[string]*net.IPAddr) {
604 var ipaddr *net.IPAddr
605 switch adr := recv.addr.(type) {
606 case *net.IPAddr:
607 ipaddr = adr
608 case *net.UDPAddr:
609 ipaddr = &net.IPAddr{IP: adr.IP, Zone: adr.Zone}
610 default:
611 return
612 }
613
614 addr := ipaddr.String()
615 p.mu.Lock()
616 if _, ok := p.addrs[addr]; !ok {
617 p.mu.Unlock()
618 return
619 }
620 p.mu.Unlock()
621
622 var bytes []byte
623 var proto int
624 if isIPv4(ipaddr.IP) {
625 if p.network == "ip" {
626 bytes = ipv4Payload(recv.bytes)
627 } else {
628 bytes = recv.bytes
629 }
630 proto = ProtocolICMP
631 } else if isIPv6(ipaddr.IP) {
632 bytes = recv.bytes
633 proto = ProtocolIPv6ICMP
634 } else {
635 return
636 }
637
638 var m *icmp.Message
639 var err error
640 if m, err = icmp.ParseMessage(proto, bytes); err != nil {
641 return
642 }
643
644 if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply {
645 return
646 }
647
648 var rtt time.Duration
649 switch pkt := m.Body.(type) {
650 case *icmp.Echo:
651 p.mu.Lock()
652 if pkt.ID == p.id && pkt.Seq == p.seq {
653 rtt = time.Since(bytesToTime(pkt.Data[:TimeSliceLength]))
654 }
655 p.mu.Unlock()
656 default:
657 return
658 }
659
660 if _, ok := queue[addr]; ok {
661 delete(queue, addr)
662 p.mu.Lock()
663 handler := p.OnRecv
664 p.mu.Unlock()
665 if handler != nil {
666 handler(ipaddr, rtt)
667 }
668 }
669}
670
671func (p *Pinger) debugln(args ...interface{}) {
672 p.mu.Lock()
673 defer p.mu.Unlock()
674 if p.Debug {
675 log.Println(args...)
676 }
677}
678
679func (p *Pinger) debugf(format string, args ...interface{}) {
680 p.mu.Lock()
681 defer p.mu.Unlock()
682 if p.Debug {
683 log.Printf(format, args...)
684 }
685}