123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657 |
- // 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"
- "fmt"
- "math"
- "math/rand"
- "net"
- "sync"
- "sync/atomic"
- "time"
- "trial/ping/probing/icmp"
- "git.wecise.com/wecise/common/logger"
- "git.wecise.com/wecise/common/matrix/cfg"
- "github.com/google/uuid"
- "golang.org/x/sync/errgroup"
- )
- var mcfg = cfg.MConfig()
- var receive_buffer_count = mcfg.GetInt("ping.recv.buf.count", 10)
- 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()))
- firstUUID := uuid.New()
- var firstSequence = map[uuid.UUID]map[int]struct{}{}
- firstSequence[firstUUID] = make(map[int]struct{})
- return &Pinger{
- Count: -1,
- Interval: time.Second,
- RecordRtts: true,
- Size: timeSliceLength + trackerLength,
- Timeout: time.Duration(math.MaxInt64),
- addr: addr,
- done: make(chan interface{}),
- id: r.Intn(math.MaxUint16),
- trackerUUIDs: []uuid.UUID{firstUUID},
- ipaddr: nil,
- ipv4: false,
- network: "ip",
- protocol: "udp",
- awaitingSequences: firstSequence,
- }
- }
- // 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 {
- // 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
- // Debug runs in debug mode
- Debug bool
- // 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)
- // Size of packet being sent
- Size int
- // Tracker: Used to uniquely identify packets - Deprecated
- Tracker uint64
- // 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
- // trackerUUIDs is the list of UUIDs being used for sending packets.
- trackerUUIDs []uuid.UUID
- ipv4 bool
- id int
- sequence_base int
- sequence int
- // awaitingSequences are in-flight sequence numbers we keep track of to help remove duplicate receipts
- awaitingSequences map[uuid.UUID]map[int]struct{}
- // 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 {
- // Rtt is the round-trip time it took to ping.
- Rtt time.Duration
- // IPAddr is the address of the host being pinged.
- IPAddr *net.IPAddr
- // Host is the string address of the host being pinged.
- Host string
- // NBytes is the number of bytes in the message.
- Nbytes int
- // Seq is the ICMP sequence number.
- Seq int
- // TTL is the Time To Live on the packet.
- TTL int
- // ID is the ICMP identifier.
- ID int
- }
- // 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)
- }
- 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"
- }
- }
- // 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) run() (time.Time, error) {
- 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()
- return err
- })
- return last_send_time, g.Wait()
- }
- func (p *Pinger) runLoop() (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 := getPingInfo(p.ipaddr)
- pinfo.host = p.addr
- pinfo.size = p.Size
- pinfo.timeout = p.Timeout
- pinfo.OnSend = func(pkt *icmp.Packet) {
- last_send_time = pkt.SendTime
- if p.PacketsSent == 0 {
- p.sequence_base = pkt.Seq
- }
- p.PacketsSent++
- if p.OnSend != nil {
- p.OnSend(&Packet{
- Rtt: pkt.Rtt,
- IPAddr: pkt.IPAddr,
- Host: p.addr,
- Nbytes: pkt.Nbytes,
- Seq: pkt.Seq - p.sequence_base,
- TTL: pkt.TTL,
- ID: pkt.ID,
- })
- }
- }
- pinfo.OnRecv = func(pkt *icmp.Packet) {
- inpkt := &Packet{
- Rtt: pkt.Rtt,
- IPAddr: pkt.IPAddr,
- Host: p.addr,
- Nbytes: pkt.Nbytes,
- Seq: pkt.Seq - p.sequence_base,
- TTL: pkt.TTL,
- ID: pkt.ID,
- }
- if p.OnRecv != nil {
- p.OnRecv(inpkt)
- }
- p.updateStatistics(inpkt)
- received <- nil
- }
- pinfo.OnRecvDup = func(pkt *icmp.Packet) {
- inpkt := &Packet{
- Rtt: pkt.Rtt,
- IPAddr: pkt.IPAddr,
- Host: p.addr,
- Nbytes: pkt.Nbytes,
- Seq: pkt.Seq - p.sequence_base,
- TTL: pkt.TTL,
- ID: pkt.ID,
- }
- if p.OnDuplicateRecv != nil {
- p.OnDuplicateRecv(inpkt)
- }
- p.updateStatistics(inpkt)
- }
- 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 bytesToTime(b []byte) time.Time {
- var nsec int64
- for i := uint8(0); i < 8; i++ {
- nsec += int64(b[i]) << ((7 - i) * 8)
- }
- return time.Unix(nsec/1000000000, nsec%1000000000)
- }
- func isIPv4(ip net.IP) bool {
- return len(ip.To4()) == net.IPv4len
- }
- func timeToBytes(t time.Time) []byte {
- nsec := t.UnixNano()
- b := make([]byte, 8)
- for i := uint8(0); i < 8; i++ {
- b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff)
- }
- return b
- }
- var seed = time.Now().UnixNano()
- // getSeed returns a goroutine-safe unique seed
- func getSeed() int64 {
- return atomic.AddInt64(&seed, 1)
- }
|