// Package probing is a simple but powerful ICMP echo (ping) library. // // Here is a very simple example that sends and receives three packets: // // pinger, err := probing.NewPinger("www.google.com") // if err != nil { // panic(err) // } // pinger.Count = 3 // err = pinger.Run() // blocks until finished // if err != nil { // panic(err) // } // stats := pinger.Statistics() // get send/receive/rtt stats // // Here is an example that emulates the traditional UNIX ping command: // // pinger, err := probing.NewPinger("www.google.com") // if err != nil { // panic(err) // } // // Listen for Ctrl-C. // c := make(chan os.Signal, 1) // signal.Notify(c, os.Interrupt) // go func() { // for _ = range c { // pinger.Stop() // } // }() // pinger.OnRecv = func(pkt *probing.Packet) { // fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n", // pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt) // } // pinger.OnFinish = func(stats *probing.Statistics) { // fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr) // fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n", // stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss) // fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n", // stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt) // } // fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr()) // err = pinger.Run() // if err != nil { // panic(err) // } // // It sends ICMP Echo Request packet(s) and waits for an Echo Reply in response. // If it receives a response, it calls the OnRecv callback. When it's finished, // it calls the OnFinish callback. // // For a full ping example, see "cmd/ping/ping.go". package probing import ( "errors" "math" "math/rand" "net" "sync" "sync/atomic" "time" "trial/ping/probing/icmp" "golang.org/x/sync/errgroup" ) var receive_buffer_count = mcfg.GetInt("ping.recv.buf.count", 500) var ping_ttl = mcfg.GetInt("ping.ttl", 64) // var ping_interval = mcfg.GetDuration("ping.interval", 1000*time.Millisecond) // var concurlimit_ping = mcfg.GetInt("concurlimit.ping", 100) // var concurchan_ping = make(chan struct{}, concurlimit_ping) // var lastpingtimemutex sync.Mutex // var lastpingtime = map[string]time.Time{} // var ETIMEDOUT error = fmt.Errorf("timeout") // const ( // timeSliceLength = 8 // trackerLength = len(uuid.UUID{}) // ) // New returns a new Pinger struct pointer. func New(addr string) *Pinger { r := rand.New(rand.NewSource(getSeed())) return &Pinger{ Count: -1, Interval: time.Second, RecordRtts: true, Size: 64, // timeSliceLength + trackerLength, Timeout: time.Duration(math.MaxInt64), addr: addr, done: make(chan interface{}), id: r.Intn(math.MaxUint16), ipaddr: nil, ipv4: false, network: "ip", protocol: "udp", } } // NewPinger returns a new Pinger and resolves the address. func NewPinger(addr string) (*Pinger, error) { p := New(addr) return p, p.Resolve() } // Pinger represents a packet sender/receiver. type Pinger struct { // Size of packet being sent Size int // Interval is the wait time between each packet send. Default is 1s. Interval time.Duration // Timeout specifies a timeout before ping exits, regardless of how many // packets have been received. Timeout time.Duration // Count tells pinger to stop after sending (and receiving) Count echo // packets. If this option is not specified, pinger will operate until // interrupted. Count int // Number of packets sent PacketsSent int // Number of packets received PacketsRecv int // Number of duplicate packets received PacketsRecvDuplicates int // Round trip time statistics minRtt time.Duration maxRtt time.Duration avgRtt time.Duration stdDevRtt time.Duration stddevm2 time.Duration statsMu sync.RWMutex // If true, keep a record of rtts of all received packets. // Set to false to avoid memory bloat for long running pings. RecordRtts bool // rtts is all of the Rtts rtts []time.Duration // OnSetup is called when Pinger has finished setting up the listening socket OnSetup func() // OnSend is called when Pinger sends a packet OnSend func(*Packet) // OnRecv is called when Pinger receives and processes a packet OnRecv func(*Packet) // OnRecv is called when Pinger receives and processes a packet OnTimeout func(*Packet) // OnFinish is called when Pinger exits OnFinish func(*Statistics) // OnDuplicateRecv is called when a packet is received that has already been received. OnDuplicateRecv func(*Packet) // Source is the source IP address Source string // Channel and mutex used to communicate when the Pinger should stop between goroutines. done chan interface{} lock sync.Mutex ipaddr *net.IPAddr addr string ipv4 bool id int sequence_base int // network is one of "ip", "ip4", or "ip6". network string // protocol is "icmp" or "udp". protocol string } // Packet represents a received and processed ICMP echo packet. type Packet struct { // Host is the string address of the host being pinged. Host string // IPAddr is the address of the host being pinged. IPAddr *net.IPAddr // ID is the ICMP identifier. ID int // Seq is the ICMP sequence number. Seq int // NBytes is the number of bytes in the message. Nbytes int // TTL is the Time To Live on the packet. TTL int // Rtt is the round-trip time it took to ping. Rtt time.Duration } // Statistics represent the stats of a currently running or finished // pinger operation. type Statistics struct { // PacketsRecv is the number of packets received. PacketsRecv int // PacketsSent is the number of packets sent. PacketsSent int // PacketsRecvDuplicates is the number of duplicate responses there were to a sent packet. PacketsRecvDuplicates int // PacketLoss is the percentage of packets lost. PacketLoss float64 // IPAddr is the address of the host being pinged. IPAddr *net.IPAddr // Addr is the string address of the host being pinged. Addr string // Rtts is all of the round-trip times sent via this pinger. Rtts []time.Duration // MinRtt is the minimum round-trip time sent via this pinger. MinRtt time.Duration // MaxRtt is the maximum round-trip time sent via this pinger. MaxRtt time.Duration // AvgRtt is the average round-trip time sent via this pinger. AvgRtt time.Duration // StdDevRtt is the standard deviation of the round-trip times sent via // this pinger. StdDevRtt time.Duration } func (p *Pinger) updateStatistics(pkt *Packet) { p.statsMu.Lock() defer p.statsMu.Unlock() p.PacketsRecv++ if p.RecordRtts { // p.rtts = append(p.rtts, pkt.Rtt) p.rtts[pkt.Seq] = pkt.Rtt } if p.PacketsRecv == 1 || pkt.Rtt < p.minRtt { p.minRtt = pkt.Rtt } if pkt.Rtt > p.maxRtt { p.maxRtt = pkt.Rtt } pktCount := time.Duration(p.PacketsRecv) // welford's online method for stddev // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm delta := pkt.Rtt - p.avgRtt p.avgRtt += delta / pktCount delta2 := pkt.Rtt - p.avgRtt p.stddevm2 += delta * delta2 p.stdDevRtt = time.Duration(math.Sqrt(float64(p.stddevm2 / pktCount))) } // SetIPAddr sets the ip address of the target host. func (p *Pinger) SetIPAddr(ipaddr *net.IPAddr) { p.ipv4 = isIPv4(ipaddr.IP) p.ipaddr = ipaddr p.addr = ipaddr.String() } // IPAddr returns the ip address of the target host. func (p *Pinger) IPAddr() *net.IPAddr { return p.ipaddr } // Resolve does the DNS lookup for the Pinger address and sets IP protocol. func (p *Pinger) Resolve() error { if len(p.addr) == 0 { return errors.New("addr cannot be empty") } ipaddr, err := net.ResolveIPAddr(p.network, p.addr) if err != nil { return err } p.ipv4 = isIPv4(ipaddr.IP) p.ipaddr = ipaddr return nil } // SetAddr resolves and sets the ip address of the target host, addr can be a // DNS name like "www.google.com" or IP like "127.0.0.1". func (p *Pinger) SetAddr(addr string) error { oldAddr := p.addr p.addr = addr err := p.Resolve() if err != nil { p.addr = oldAddr return err } return nil } // Addr returns the string ip address of the target host. func (p *Pinger) Addr() string { return p.addr } // SetNetwork allows configuration of DNS resolution. // * "ip" will automatically select IPv4 or IPv6. // * "ip4" will select IPv4. // * "ip6" will select IPv6. func (p *Pinger) SetNetwork(n string) { switch n { case "ip4": p.network = "ip4" case "ip6": p.network = "ip6" default: p.network = "ip" } } /* https://stackoverflow.com/questions/41423637/go-ping-library-for-unprivileged-icmp-ping-in-golang/41425527#41425527 This library attempts to send an "unprivileged" ping via UDP. On linux, this must be enabled by setting sudo sysctl -w net.ipv4.ping_group_range="0 2147483647" If you do not wish to do this, you can set pinger.SetPrivileged(true) and use setcap to allow your binary using go-ping to bind to raw sockets (or just run as super-user): setcap cap_net_raw=+ep /bin/goping-binary getcap /bin/goping-binary to validate */ // SetPrivileged sets the type of ping pinger will send. // false means pinger will send an "unprivileged" UDP ping. // true means pinger will send a "privileged" raw ICMP ping. // NOTE: setting to true requires that it be run with super-user privileges. func (p *Pinger) SetPrivileged(privileged bool) { if privileged { p.protocol = "icmp" } else { p.protocol = "udp" } } // Privileged returns whether pinger is running in privileged mode. func (p *Pinger) Privileged() bool { return p.protocol == "icmp" } // SetID sets the ICMP identifier. func (p *Pinger) SetID(id int) { p.id = id } // ID returns the ICMP identifier. func (p *Pinger) ID() int { return p.id } var pingipmtx sync.Mutex var pingips = map[string]chan interface{}{} var pingads = map[string]chan interface{}{} // Run runs the pinger. This is a blocking function that will exit when it's // done. If Count or Interval are not specified, it will run continuously until // it is interrupted. func (p *Pinger) Run() (err error) { // 同一地址,只能有一个实例运行,排队等待 pingipmtx.Lock() pingipchan := pingips[p.ipaddr.String()] if pingipchan == nil { pingipchan = make(chan interface{}, 1) pingips[p.ipaddr.String()] = pingipchan } pingadchan := pingads[p.addr] if pingadchan == nil { pingadchan = make(chan interface{}, 1) pingads[p.addr] = pingadchan } pingipmtx.Unlock() pingipchan <- 1 pingadchan <- 1 var last_send_time time.Time defer func() { d := p.Interval - time.Since(last_send_time) if d > 0 { time.Sleep(d) // 两次运行之间至少间隔 } <-pingipchan <-pingadchan }() // sleeptime := time.Duration(0) // for sleeptime >= 0 { // time.Sleep(sleeptime) // lastpingtimemutex.Lock() // alastpingtime := lastpingtime[p.ipaddr.String()] // sleeptime = ping_interval_one_host - time.Since(alastpingtime) // if sleeptime <= 0 { // lastpingtime[p.ipaddr.String()] = time.Now() // } else { // // logger.Error(fmt.Sprint("ping", p.addr, "[", p.ipaddr.String(), "]", "同一地址至少间隔一秒")) // } // lastpingtimemutex.Unlock() // } // defer func() { // lastpingtimemutex.Lock() // lastpingtime[p.ipaddr.String()] = time.Now() // lastpingtimemutex.Unlock() // }() // concurchan_ping <- struct{}{} // defer func() { // <-concurchan_ping // }() last_send_time, err = p.run() return } func (p *Pinger) init() *mpinfo { pinfo := getPingInfo(p.ipaddr) pinfo.host = p.addr pinfo.size = p.Size pinfo.timeout = p.Timeout p.sequence_base = pinfo.lastseq if p.RecordRtts { p.rtts = make([]time.Duration, p.Count) } return pinfo } func (p *Pinger) run() (time.Time, error) { pinfo := p.init() defer p.finish() err := MPConn(p.ipv4, p.protocol).Listen() if err != nil { return time.Time{}, err } if handler := p.OnSetup; handler != nil { handler() } var g errgroup.Group var last_send_time time.Time g.Go(func() (err error) { defer p.Stop() last_send_time, err = p.runLoop(pinfo) return err }) return last_send_time, g.Wait() } func (p *Pinger) runLoop(pinfo *mpinfo) (time.Time, error) { timeout := time.NewTimer(p.Timeout) interval := time.NewTimer(0) timeout.Stop() defer func() { interval.Stop() timeout.Stop() }() received := make(chan interface{}, 1) last_send_time := time.Now() pinfo.OnSend = func(pkt *icmp.Packet) { last_send_time = pkt.SendTime seq := pkt.Seq - p.sequence_base if seq < 0 || seq >= p.Count { return } p.PacketsSent++ if p.OnSend != nil { p.OnSend(&Packet{ Host: p.addr, IPAddr: pkt.IPAddr, ID: p.id, Seq: seq, Nbytes: pkt.Nbytes, TTL: pkt.TTL, Rtt: pkt.Rtt, }) } } pinfo.OnRecv = func(pkt *icmp.Packet) { seq := pkt.Seq - p.sequence_base if seq < 0 || seq >= p.Count { return } inpkt := &Packet{ Host: p.addr, IPAddr: pkt.IPAddr, ID: p.id, Seq: seq, Nbytes: pkt.Nbytes, TTL: pkt.TTL, Rtt: pkt.Rtt, } if p.OnRecv != nil { p.OnRecv(inpkt) } p.updateStatistics(inpkt) received <- nil } pinfo.OnRecvDup = func(pkt *icmp.Packet) { seq := pkt.Seq - p.sequence_base if seq < 0 || seq >= p.Count { return } inpkt := &Packet{ Host: p.addr, IPAddr: pkt.IPAddr, ID: p.id, Seq: seq, Nbytes: pkt.Nbytes, TTL: pkt.TTL, Rtt: pkt.Rtt, } if p.OnDuplicateRecv != nil { p.OnDuplicateRecv(inpkt) } } pinfo.OnRecvTimeout = func(pkt *icmp.Packet) { seq := pkt.Seq - p.sequence_base if seq < 0 || seq >= p.Count { return } outpkt := &Packet{ Host: p.addr, IPAddr: pkt.IPAddr, ID: p.id, Seq: seq, Nbytes: pkt.Nbytes, TTL: pkt.TTL, Rtt: pkt.Rtt, } if p.OnTimeout != nil { p.OnTimeout(outpkt) } } for { select { case <-p.done: return last_send_time, nil case <-timeout.C: return last_send_time, nil case <-interval.C: if p.Count > 0 && p.PacketsSent >= p.Count { timeout.Reset(p.Timeout) } else { if err := MPConn(p.ipv4, p.protocol).Ping(pinfo); err != nil { logger.Errorf("sending packet: %s", err) } interval.Reset(p.Interval) } case <-received: if p.Count > 0 && p.PacketsRecv >= p.Count { return last_send_time, nil } } } } // func (p *Pinger) ping(pinfo *mpinfo, received chan interface{}) error { // sleeptime := time.Duration(0) // for sleeptime >= 0 { // time.Sleep(sleeptime) // lastpingtimemutex.Lock() // alastpingtime := lastpingtime[p.ipaddr.String()] // sleeptime = ping_interval - time.Since(alastpingtime) // if sleeptime <= 0 { // lastpingtime[p.ipaddr.String()] = time.Now() // } else { // // logger.Error(fmt.Sprint("ping", p.addr, "[", p.ipaddr.String(), "]", "同一地址至少间隔一秒")) // } // lastpingtimemutex.Unlock() // } // defer func() { // lastpingtimemutex.Lock() // lastpingtime[p.ipaddr.String()] = time.Now() // lastpingtimemutex.Unlock() // }() // return MPConn(p.ipv4, p.protocol).Ping(pinfo) // } func (p *Pinger) Stop() { p.lock.Lock() defer p.lock.Unlock() open := true select { case _, open = <-p.done: default: } if open { close(p.done) } } func (p *Pinger) finish() { handler := p.OnFinish if handler != nil { s := p.Statistics() handler(s) } } // Statistics returns the statistics of the pinger. This can be run while the // pinger is running or after it is finished. OnFinish calls this function to // get it's finished statistics. func (p *Pinger) Statistics() *Statistics { p.statsMu.RLock() defer p.statsMu.RUnlock() sent := p.PacketsSent loss := float64(sent-p.PacketsRecv) / float64(sent) * 100 s := Statistics{ PacketsSent: sent, PacketsRecv: p.PacketsRecv, PacketsRecvDuplicates: p.PacketsRecvDuplicates, PacketLoss: loss, Rtts: p.rtts, Addr: p.addr, IPAddr: p.ipaddr, MaxRtt: p.maxRtt, MinRtt: p.minRtt, AvgRtt: p.avgRtt, StdDevRtt: p.stdDevRtt, } return &s } func isIPv4(ip net.IP) bool { return len(ip.To4()) == net.IPv4len } var seed = time.Now().UnixNano() // getSeed returns a goroutine-safe unique seed func getSeed() int64 { return atomic.AddInt64(&seed, 1) }