├── .gitignore ├── LICENSE ├── README.md ├── batch_ping.go ├── circle.yml ├── cmd └── ping │ ├── ping.go │ └── ping2.go ├── ping.go └── ping_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | /ping 2 | .idea 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cameron Sparr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-ping 2 | [![GoDoc](https://godoc.org/github.com/sparrc/go-ping?status.svg)](https://godoc.org/github.com/sparrc/go-ping) 3 | [![Circle CI](https://circleci.com/gh/sparrc/go-ping.svg?style=svg)](https://circleci.com/gh/sparrc/go-ping) 4 | 5 | ICMP Ping library for Go, inspired by 6 | [go-fastping](https://github.com/tatsushid/go-fastping) 7 | 8 | Here is a very simple example that sends & receives 3 packets: 9 | 10 | ```go 11 | pinger, err := ping.NewPinger("www.google.com") 12 | if err != nil { 13 | panic(err) 14 | } 15 | pinger.Count = 3 16 | pinger.Run() // blocks until finished 17 | stats := pinger.Statistics() // get send/receive/rtt stats 18 | ``` 19 | 20 | Here is an example that emulates the unix ping command: 21 | 22 | ```go 23 | pinger, err := ping.NewPinger("www.google.com") 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | pinger.OnRecv = func(pkt *ping.Packet) { 29 | fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n", 30 | pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt) 31 | } 32 | pinger.OnFinish = func(stats *ping.Statistics) { 33 | fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr) 34 | fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n", 35 | stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss) 36 | fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n", 37 | stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt) 38 | } 39 | 40 | fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr()) 41 | pinger.Run() 42 | ``` 43 | another example 44 | ``` 45 | ipSlice := []string{} 46 | ipSlice = append(ipSlice, "122.228.74.183") 47 | ipSlice = append(ipSlice, "wwww.baidu.com") 48 | ipSlice = append(ipSlice, "github.com") 49 | ipSlice = append(ipSlice, "121.42.9.143") 50 | 51 | bp, err := ping.NewBatchPinger(ipSlice, 4, time.Second*1, time.Second*10) 52 | 53 | if err != nil { 54 | fmt.Println(err) 55 | } 56 | 57 | bp.OnRecv = func(pkt *icmp.Echo) { 58 | // 59 | fmt.Printf("recv icmp_id=%d, icmp_seq=%d\n", 60 | pkt.ID, pkt.Seq) 61 | } 62 | 63 | bp.OnFinish = func(stSlice []*ping.Statistics) { 64 | for _, st := range stSlice{ 65 | fmt.Printf("\n--- %s ping statistics ---\n", st.Addr) 66 | fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n", 67 | st.PacketsSent, st.PacketsRecv, st.PacketLoss) 68 | fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n", 69 | st.MinRtt, st.AvgRtt, st.MaxRtt, st.StdDevRtt) 70 | } 71 | 72 | } 73 | 74 | bp.Run() 75 | ``` 76 | It sends ICMP packet(s) and waits for a response. If it receives a response, 77 | it calls the "receive" callback. When it's finished, it calls the "finish" 78 | callback. 79 | 80 | For a full ping example, see 81 | one address 82 | [cmd/ping/ping.go](https://github.com/vearne/go-ping/blob/master/cmd/ping/ping.go) 83 | multiple addresses 84 | [cmd/ping/ping2.go](https://github.com/vearne/go-ping/blob/master/cmd/ping/ping2.go) 85 | 86 | ## Installation: 87 | 88 | ``` 89 | go get github.com/vearne/go-ping 90 | ``` 91 | 92 | To install the native Go ping executable: 93 | 94 | ```bash 95 | go get github.com/vearne/go-ping/... 96 | $GOPATH/bin/ping 97 | ``` 98 | 99 | ## Note on Linux Support: 100 | 101 | This library attempts to send an 102 | "unprivileged" ping via UDP. On linux, this must be enabled by setting 103 | 104 | ``` 105 | sudo sysctl -w net.ipv4.ping_group_range="0 2147483647" 106 | ``` 107 | 108 | If you do not wish to do this, you can set `pinger.SetPrivileged(true)` and 109 | use setcap to allow your binary using go-ping to bind to raw sockets 110 | (or just run as super-user): 111 | 112 | ``` 113 | setcap cap_net_raw=+ep /bin/goping-binary 114 | ``` 115 | 116 | See [this blog](https://sturmflut.github.io/linux/ubuntu/2015/01/17/unprivileged-icmp-sockets-on-linux/) 117 | and [the Go icmp library](https://godoc.org/golang.org/x/net/icmp) for more details. 118 | -------------------------------------------------------------------------------- /batch_ping.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/net/icmp" 6 | "golang.org/x/net/ipv4" 7 | "golang.org/x/net/ipv6" 8 | "math" 9 | "net" 10 | "os" 11 | "os/signal" 12 | "strconv" 13 | "sync" 14 | "syscall" 15 | "time" 16 | ) 17 | 18 | var GlobalID = 0 19 | 20 | type BatchPinger struct { 21 | pingers []*Pinger2 22 | 23 | // 如果收到回应包,反查pinger 24 | PackPinger map[string]*Pinger2 25 | 26 | // Interval is the wait time between each packet send. Default is 1s. 27 | Interval time.Duration 28 | 29 | // Timeout specifies a timeout before ping exits, regardless of how many 30 | // packets have been received. 31 | Timeout time.Duration 32 | 33 | // Count tells pinger to stop after sending (and receiving) Count echo 34 | // packets. If this option is not specified, pinger will operate until 35 | // interrupted. 36 | Count int 37 | 38 | // record send count 39 | SendCount int 40 | 41 | // Debug runs in debug mode 42 | Debug bool 43 | 44 | // OnRecv is called when Pinger receives and processes a packet 45 | OnRecv func(*icmp.Echo) 46 | 47 | // OnFinish is called when Pinger exits 48 | OnFinish func([]*Statistics) 49 | 50 | // stop chan bool 51 | done chan bool 52 | 53 | ipv4 bool 54 | size int 55 | source string 56 | network string 57 | } 58 | 59 | type Pinger2 struct { 60 | ipaddr *net.IPAddr 61 | addr string 62 | 63 | // Number of packets sent 64 | PacketsSent int 65 | 66 | // Number of packets received 67 | PacketsRecv int 68 | 69 | // rtts is all of the Rtts 70 | rtts []time.Duration 71 | 72 | // ICMP id (2 bytes) 73 | id int 74 | // ICMP sequence (2 bytes) 75 | sequence int 76 | 77 | ipv4 bool 78 | size int 79 | network string 80 | } 81 | 82 | func NewPinger2(ipaddr *net.IPAddr, id int, ipv4 bool) (*Pinger2) { 83 | x := Pinger2{} 84 | x.ipaddr = ipaddr 85 | x.PacketsSent = 0 86 | x.PacketsRecv = 0 87 | x.rtts = []time.Duration{} 88 | x.id = id 89 | x.sequence = 0 90 | x.ipv4 = ipv4 91 | x.size = timeSliceLength 92 | x.addr = ipaddr.String() 93 | return &x 94 | } 95 | 96 | // NewPinger returns a new Pinger struct pointer 97 | // interval in secs 98 | func NewBatchPinger(ipSlice []string, count int, interval time.Duration, 99 | timeout time.Duration) (*BatchPinger, error) { 100 | pingers := []*Pinger2{} 101 | 102 | fmt.Printf("count ipSlice:%v\n", len(ipSlice)) 103 | 104 | var ipv4 bool 105 | for _, ip := range ipSlice { 106 | fmt.Printf("ip:%v\n", ip) 107 | 108 | ipaddr, err := net.ResolveIPAddr("ip", ip) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | if isIPv4(ipaddr.IP) { 114 | ipv4 = true 115 | } else if isIPv6(ipaddr.IP) { 116 | ipv4 = false 117 | } 118 | 119 | id := GenNextID(GlobalID) 120 | GlobalID = id 121 | pinger := NewPinger2(ipaddr, id, ipv4) 122 | pingers = append(pingers, pinger) 123 | 124 | } 125 | 126 | fmt.Printf("count pingers:%v\n", len(pingers)) 127 | 128 | return &BatchPinger{ 129 | Interval: interval, 130 | Timeout: timeout, 131 | Count: count, 132 | done: make(chan bool), 133 | network: "ip", 134 | ipv4: ipv4, 135 | size: timeSliceLength, 136 | pingers: pingers, 137 | PackPinger: make(map[string]*Pinger2), 138 | }, nil 139 | } 140 | 141 | func GenNextID(id int) int { 142 | if id < 0 { 143 | id = 0 144 | } 145 | 146 | id++ 147 | if id > 65535 { 148 | id = 0 149 | } 150 | return id 151 | } 152 | 153 | func (bp *BatchPinger) Run() { 154 | var conn *icmp.PacketConn 155 | if bp.ipv4 { 156 | fmt.Printf("source:%v, network:%v\n", bp.source, bp.network) 157 | if conn = bp.Listen(ipv4Proto[bp.network], bp.source); conn == nil { 158 | return 159 | } 160 | } else { 161 | if conn = bp.Listen(ipv6Proto[bp.network], bp.source); conn == nil { 162 | return 163 | } 164 | } 165 | defer conn.Close() 166 | defer bp.finish() 167 | 168 | var wg sync.WaitGroup 169 | recv := make(chan *packet, 2000) 170 | 171 | go bp.RecvICMP(conn, recv, &wg) 172 | 173 | err := bp.BatchSendICMP(conn) 174 | if err != nil { 175 | fmt.Println(err.Error()) 176 | } 177 | 178 | fmt.Printf("Count:%v\n", bp.Count) 179 | fmt.Printf("SendCount:%v\n", bp.SendCount) 180 | fmt.Printf("Timeout:%v\n", bp.Timeout) 181 | fmt.Printf("Interval:%v\n", bp.Interval) 182 | 183 | timeout := time.NewTicker(bp.Timeout) 184 | interval := time.NewTicker(bp.Interval) 185 | c := make(chan os.Signal, 1) 186 | signal.Notify(c, os.Interrupt) 187 | signal.Notify(c, syscall.SIGTERM) 188 | 189 | for { 190 | select { 191 | case sig := <-c: 192 | fmt.Printf("get signal %s, prepare exit \n", sig) 193 | close(bp.done) 194 | 195 | case <-bp.done: 196 | wg.Wait() 197 | return 198 | 199 | case <-timeout.C: 200 | fmt.Println("tick timeout") 201 | close(bp.done) 202 | wg.Wait() 203 | return 204 | 205 | case <-interval.C: 206 | fmt.Println("tick interval") 207 | err = bp.BatchSendICMP(conn) 208 | if err != nil { 209 | fmt.Println("FATAL: ", err.Error()) 210 | } 211 | case r := <-recv: 212 | err := bp.ProcessPacket(r) 213 | if err != nil { 214 | fmt.Println("FATAL: ", err.Error()) 215 | } 216 | default: 217 | time.Sleep(time.Millisecond * 100) 218 | allSent, allRecv := bp.GetAllPacketsRecv() 219 | //fmt.Printf("Count:%v, SendCount:%v, allSent:%v, allRecv:%v\n", 220 | //bp.Count, bp.SendCount, allSent, allRecv) 221 | if bp.Count > 0 && bp.SendCount == bp.Count && allRecv >= allSent { 222 | close(bp.done) 223 | wg.Wait() 224 | return 225 | } 226 | } 227 | } 228 | } 229 | 230 | func (bp *BatchPinger) GetAllPacketsRecv() (int, int) { 231 | allRecv := 0 232 | allSent := 0 233 | for _, pinger := range bp.pingers { 234 | allRecv += pinger.PacketsRecv 235 | allSent += pinger.PacketsSent 236 | } 237 | return allSent, allRecv 238 | } 239 | 240 | func (p *BatchPinger) Listen(netProto string, source string) *icmp.PacketConn { 241 | conn, err := icmp.ListenPacket(netProto, source) 242 | if err != nil { 243 | fmt.Printf("Error listening for ICMP packets: %s\n", err.Error()) 244 | close(p.done) 245 | return nil 246 | } 247 | return conn 248 | } 249 | 250 | func (p *BatchPinger) RecvICMP(conn *icmp.PacketConn, recv chan<- *packet, wg *sync.WaitGroup) { 251 | wg.Add(1) 252 | defer wg.Done() 253 | 254 | for { 255 | select { 256 | case <-p.done: 257 | return 258 | default: 259 | bytes := make([]byte, 512) 260 | conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100)) 261 | n, _, err := conn.ReadFrom(bytes) 262 | if err != nil { 263 | if neterr, ok := err.(*net.OpError); ok { 264 | if neterr.Timeout() { 265 | // Read timeout 266 | continue 267 | } else { 268 | close(p.done) 269 | return 270 | } 271 | } 272 | } 273 | 274 | recv <- &packet{bytes: bytes, nbytes: n} 275 | } 276 | } 277 | } 278 | 279 | func (bp *BatchPinger) BatchSendICMP(conn *icmp.PacketConn) error { 280 | if bp.SendCount >= bp.Count { 281 | return nil 282 | } 283 | 284 | for _, pinger := range bp.pingers { 285 | // id + ":" + seq 286 | key := strconv.Itoa(pinger.id) + ":" + strconv.Itoa(pinger.sequence) 287 | bp.PackPinger[key] = pinger 288 | 289 | fmt.Printf("add:%v, id:%v, seq:%v\n", pinger.addr, pinger.id, pinger.sequence) 290 | err := pinger.SendICMP(conn) 291 | if err != nil { 292 | fmt.Printf("err:%v\n", err) 293 | return err 294 | } 295 | 296 | } 297 | 298 | bp.SendCount++ 299 | return nil 300 | } 301 | 302 | func (p *Pinger2) SendICMP(conn *icmp.PacketConn) error { 303 | var typ icmp.Type 304 | if p.ipv4 { 305 | typ = ipv4.ICMPTypeEcho 306 | } else { 307 | typ = ipv6.ICMPTypeEchoRequest 308 | } 309 | 310 | var dst net.Addr = p.ipaddr 311 | if p.network == "udp" { 312 | dst = &net.UDPAddr{IP: p.ipaddr.IP, Zone: p.ipaddr.Zone} 313 | } 314 | 315 | t := timeToBytes(time.Now()) 316 | 317 | bytes, err := (&icmp.Message{ 318 | Type: typ, Code: 0, 319 | Body: &icmp.Echo{ 320 | ID: p.id, 321 | Seq: p.sequence, 322 | Data: t, 323 | }, 324 | }).Marshal(nil) 325 | if err != nil { 326 | fmt.Println("err", err) 327 | return err 328 | } 329 | 330 | for { 331 | if _, err := conn.WriteTo(bytes, dst); err != nil { 332 | fmt.Printf("send error, %v,%v\n", err, err.Error()) 333 | if neterr, ok := err.(*net.OpError); ok { 334 | if neterr.Err == syscall.ENOBUFS { 335 | continue 336 | } 337 | } 338 | } 339 | p.PacketsSent += 1 340 | p.sequence += 1 341 | break 342 | } 343 | return nil 344 | } 345 | 346 | func (bp *BatchPinger) ProcessPacket(recv *packet) error { 347 | var bytes []byte 348 | var proto int 349 | if bp.ipv4 { 350 | if bp.network == "ip" { 351 | bytes = ipv4Payload(recv.bytes) 352 | } else { 353 | bytes = recv.bytes 354 | } 355 | proto = protocolICMP 356 | } else { 357 | bytes = recv.bytes 358 | proto = protocolIPv6ICMP 359 | } 360 | 361 | var m *icmp.Message 362 | var err error 363 | if m, err = icmp.ParseMessage(proto, bytes[:recv.nbytes]); err != nil { 364 | return fmt.Errorf("Error parsing icmp message") 365 | } 366 | 367 | if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply { 368 | // Not an echo reply, ignore it 369 | return nil 370 | } 371 | 372 | body := m.Body.(*icmp.Echo) 373 | rtt := time.Since(bytesToTime(body.Data[:timeSliceLength])) 374 | 375 | key := strconv.Itoa(body.ID) + ":" + strconv.Itoa(body.Seq) 376 | pinger, ok := bp.PackPinger[key] 377 | if !ok { 378 | fmt.Println("got other process'ping") 379 | return nil 380 | } 381 | 382 | pinger.rtts = append(pinger.rtts, rtt) 383 | pinger.PacketsRecv += 1 384 | 385 | handler := bp.OnRecv 386 | if handler != nil { 387 | handler(body) 388 | } 389 | 390 | return nil 391 | } 392 | 393 | func (bp *BatchPinger) finish() { 394 | handler := bp.OnFinish 395 | if handler != nil { 396 | s := bp.Statistics() 397 | handler(s) 398 | } 399 | } 400 | 401 | func (bp *BatchPinger) Statistics() []*Statistics { 402 | stSlice := [](*Statistics){} 403 | for _, pinger := range bp.pingers { 404 | x := pinger.Statistics() 405 | stSlice = append(stSlice, x) 406 | } 407 | return stSlice 408 | } 409 | 410 | // Statistics returns the statistics of the pinger. This can be run while the 411 | // pinger is running or after it is finished. OnFinish calls this function to 412 | // get it's finished statistics. 413 | func (p *Pinger2) Statistics() *Statistics { 414 | loss := float64(p.PacketsSent-p.PacketsRecv) / float64(p.PacketsSent) * 100 415 | var min, max, total time.Duration 416 | fmt.Println(p.rtts) 417 | 418 | if len(p.rtts) > 0 { 419 | min = p.rtts[0] 420 | max = p.rtts[0] 421 | } 422 | for _, rtt := range p.rtts { 423 | if rtt < min { 424 | min = rtt 425 | } 426 | if rtt > max { 427 | max = rtt 428 | } 429 | total += rtt 430 | } 431 | s := Statistics{ 432 | PacketsSent: p.PacketsSent, 433 | PacketsRecv: p.PacketsRecv, 434 | PacketLoss: loss, 435 | Rtts: p.rtts, 436 | Addr: p.addr, 437 | IPAddr: p.ipaddr, 438 | MaxRtt: max, 439 | MinRtt: min, 440 | } 441 | if len(p.rtts) > 0 { 442 | s.AvgRtt = total / time.Duration(len(p.rtts)) 443 | var sumsquares time.Duration 444 | for _, rtt := range p.rtts { 445 | sumsquares += (rtt - s.AvgRtt) * (rtt - s.AvgRtt) 446 | } 447 | s.StdDevRtt = time.Duration(math.Sqrt( 448 | float64(sumsquares / time.Duration(len(p.rtts))))) 449 | } 450 | return &s 451 | } 452 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | test: 2 | post: 3 | - go build -race -o ping_linux ./cmd/ping/ping.go 4 | - sudo ./ping_linux --privileged -c 2 www.google.com 5 | - sudo ./ping_linux --privileged -c 3 -i 200ms www.google.com 6 | - sudo ./ping_linux --privileged -c 10 -i 100ms -t 1s www.google.com 7 | - GOOS=darwin go build -o ping_darwin ./cmd/ping/ping.go 8 | - mv ping_linux $CIRCLE_ARTIFACTS 9 | - mv ping_darwin $CIRCLE_ARTIFACTS 10 | -------------------------------------------------------------------------------- /cmd/ping/ping.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/sparrc/go-ping" 9 | ) 10 | 11 | var usage = ` 12 | Usage: 13 | 14 | ping [-c count] [-i interval] [-t timeout] [--privileged] host 15 | 16 | Examples: 17 | 18 | # ping google continuously 19 | ping www.google.com 20 | 21 | # ping google 5 times 22 | ping -c 5 www.google.com 23 | 24 | # ping google 5 times at 500ms intervals 25 | ping -c 5 -i 500ms www.google.com 26 | 27 | # ping google for 10 seconds 28 | ping -t 10s www.google.com 29 | 30 | # Send a privileged raw ICMP ping 31 | sudo ping --privileged www.google.com 32 | ` 33 | 34 | func main() { 35 | timeout := flag.Duration("t", time.Second*100000, "") 36 | interval := flag.Duration("i", time.Second, "") 37 | count := flag.Int("c", -1, "") 38 | privileged := flag.Bool("privileged", false, "") 39 | flag.Usage = func() { 40 | fmt.Printf(usage) 41 | } 42 | flag.Parse() 43 | 44 | if flag.NArg() == 0 { 45 | flag.Usage() 46 | return 47 | } 48 | 49 | host := flag.Arg(0) 50 | pinger, err := ping.NewPinger(host) 51 | if err != nil { 52 | fmt.Printf("ERROR: %s\n", err.Error()) 53 | return 54 | } 55 | 56 | pinger.OnRecv = func(pkt *ping.Packet) { 57 | fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n", 58 | pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt) 59 | } 60 | pinger.OnFinish = func(stats *ping.Statistics) { 61 | fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr) 62 | fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n", 63 | stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss) 64 | fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n", 65 | stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt) 66 | } 67 | 68 | pinger.Count = *count 69 | pinger.Interval = *interval 70 | pinger.Timeout = *timeout 71 | pinger.SetPrivileged(*privileged) 72 | 73 | fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr()) 74 | pinger.Run() 75 | } 76 | -------------------------------------------------------------------------------- /cmd/ping/ping2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/vearne/go-ping" 4 | import "fmt" 5 | import "time" 6 | import "golang.org/x/net/icmp" 7 | 8 | func main() { 9 | 10 | ipSlice := []string{} 11 | ipSlice = append(ipSlice, "122.228.74.183") 12 | ipSlice = append(ipSlice, "wwww.baidu.com") 13 | ipSlice = append(ipSlice, "github.com") 14 | ipSlice = append(ipSlice, "121.42.9.143") 15 | 16 | bp, err := ping.NewBatchPinger(ipSlice, 10000, time.Second*1, time.Second*10000) 17 | 18 | if err != nil { 19 | fmt.Println(err) 20 | } 21 | 22 | bp.OnRecv = func(pkt *icmp.Echo) { 23 | fmt.Printf("recv icmp_id=%d, icmp_seq=%d\n", 24 | pkt.ID, pkt.Seq) 25 | } 26 | 27 | bp.OnFinish = func(stSlice []*ping.Statistics) { 28 | for _, st := range stSlice{ 29 | fmt.Printf("\n--- %s ping statistics ---\n", st.Addr) 30 | fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n", 31 | st.PacketsSent, st.PacketsRecv, st.PacketLoss) 32 | fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n", 33 | st.MinRtt, st.AvgRtt, st.MaxRtt, st.StdDevRtt) 34 | } 35 | 36 | } 37 | 38 | bp.Run() 39 | } 40 | -------------------------------------------------------------------------------- /ping.go: -------------------------------------------------------------------------------- 1 | // Package ping is an ICMP ping library seeking to emulate the unix "ping" 2 | // command. 3 | // 4 | // Here is a very simple example that sends & receives 3 packets: 5 | // 6 | // pinger, err := ping.NewPinger("www.google.com") 7 | // if err != nil { 8 | // panic(err) 9 | // } 10 | // 11 | // pinger.Count = 3 12 | // pinger.Run() // blocks until finished 13 | // stats := pinger.Statistics() // get send/receive/rtt stats 14 | // 15 | // Here is an example that emulates the unix ping command: 16 | // 17 | // pinger, err := ping.NewPinger("www.google.com") 18 | // if err != nil { 19 | // fmt.Printf("ERROR: %s\n", err.Error()) 20 | // return 21 | // } 22 | // 23 | // pinger.OnRecv = func(pkt *ping.Packet) { 24 | // fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n", 25 | // pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt) 26 | // } 27 | // pinger.OnFinish = func(stats *ping.Statistics) { 28 | // fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr) 29 | // fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n", 30 | // stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss) 31 | // fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n", 32 | // stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt) 33 | // } 34 | // 35 | // fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr()) 36 | // pinger.Run() 37 | // 38 | // It sends ICMP packet(s) and waits for a response. If it receives a response, 39 | // it calls the "receive" callback. When it's finished, it calls the "finish" 40 | // callback. 41 | // 42 | // For a full ping example, see "cmd/ping/ping.go". 43 | // 44 | package ping 45 | 46 | import ( 47 | "fmt" 48 | "math" 49 | "math/rand" 50 | "net" 51 | "os" 52 | "os/signal" 53 | "sync" 54 | "syscall" 55 | "time" 56 | 57 | "golang.org/x/net/icmp" 58 | "golang.org/x/net/ipv4" 59 | "golang.org/x/net/ipv6" 60 | ) 61 | 62 | const ( 63 | timeSliceLength = 8 64 | protocolICMP = 1 65 | protocolIPv6ICMP = 58 66 | ) 67 | 68 | var ( 69 | ipv4Proto = map[string]string{"ip": "ip4:icmp", "udp": "udp4"} 70 | ipv6Proto = map[string]string{"ip": "ip6:ipv6-icmp", "udp": "udp6"} 71 | ) 72 | 73 | // NewPinger returns a new Pinger struct pointer 74 | func NewPinger(addr string) (*Pinger, error) { 75 | ipaddr, err := net.ResolveIPAddr("ip", addr) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | var ipv4 bool 81 | if isIPv4(ipaddr.IP) { 82 | ipv4 = true 83 | } else if isIPv6(ipaddr.IP) { 84 | ipv4 = false 85 | } 86 | 87 | return &Pinger{ 88 | ipaddr: ipaddr, 89 | addr: addr, 90 | Interval: time.Second, 91 | Timeout: time.Second * 100000, 92 | Count: -1, 93 | 94 | network: "udp", 95 | ipv4: ipv4, 96 | size: timeSliceLength, 97 | 98 | done: make(chan bool), 99 | }, nil 100 | } 101 | 102 | // Pinger represents ICMP packet sender/receiver 103 | type Pinger struct { 104 | // Interval is the wait time between each packet send. Default is 1s. 105 | Interval time.Duration 106 | 107 | // Timeout specifies a timeout before ping exits, regardless of how many 108 | // packets have been received. 109 | Timeout time.Duration 110 | 111 | // Count tells pinger to stop after sending (and receiving) Count echo 112 | // packets. If this option is not specified, pinger will operate until 113 | // interrupted. 114 | Count int 115 | 116 | // Debug runs in debug mode 117 | Debug bool 118 | 119 | // Number of packets sent 120 | PacketsSent int 121 | 122 | // Number of packets received 123 | PacketsRecv int 124 | 125 | // rtts is all of the Rtts 126 | rtts []time.Duration 127 | 128 | // OnRecv is called when Pinger receives and processes a packet 129 | OnRecv func(*Packet) 130 | 131 | // OnFinish is called when Pinger exits 132 | OnFinish func(*Statistics) 133 | 134 | // stop chan bool 135 | done chan bool 136 | 137 | ipaddr *net.IPAddr 138 | addr string 139 | 140 | ipv4 bool 141 | source string 142 | size int 143 | sequence int 144 | network string 145 | } 146 | 147 | type packet struct { 148 | bytes []byte 149 | nbytes int 150 | } 151 | 152 | // Packet represents a received and processed ICMP echo packet. 153 | type Packet struct { 154 | // Rtt is the round-trip time it took to ping. 155 | Rtt time.Duration 156 | 157 | // IPAddr is the address of the host being pinged. 158 | IPAddr *net.IPAddr 159 | 160 | // NBytes is the number of bytes in the message. 161 | Nbytes int 162 | 163 | // Seq is the ICMP sequence number. 164 | Seq int 165 | } 166 | 167 | // Statistics represent the stats of a currently running or finished 168 | // pinger operation. 169 | type Statistics struct { 170 | // PacketsRecv is the number of packets received. 171 | PacketsRecv int 172 | 173 | // PacketsSent is the number of packets sent. 174 | PacketsSent int 175 | 176 | // PacketLoss is the percentage of packets lost. 177 | PacketLoss float64 178 | 179 | // IPAddr is the address of the host being pinged. 180 | IPAddr *net.IPAddr 181 | 182 | // Addr is the string address of the host being pinged. 183 | Addr string 184 | 185 | // Rtts is all of the round-trip times sent via this pinger. 186 | Rtts []time.Duration 187 | 188 | // MinRtt is the minimum round-trip time sent via this pinger. 189 | MinRtt time.Duration 190 | 191 | // MaxRtt is the maximum round-trip time sent via this pinger. 192 | MaxRtt time.Duration 193 | 194 | // AvgRtt is the average round-trip time sent via this pinger. 195 | AvgRtt time.Duration 196 | 197 | // StdDevRtt is the standard deviation of the round-trip times sent via 198 | // this pinger. 199 | StdDevRtt time.Duration 200 | } 201 | 202 | // SetIPAddr sets the ip address of the target host. 203 | func (p *Pinger) SetIPAddr(ipaddr *net.IPAddr) { 204 | var ipv4 bool 205 | if isIPv4(ipaddr.IP) { 206 | ipv4 = true 207 | } else if isIPv6(ipaddr.IP) { 208 | ipv4 = false 209 | } 210 | 211 | p.ipaddr = ipaddr 212 | p.addr = ipaddr.String() 213 | p.ipv4 = ipv4 214 | } 215 | 216 | // IPAddr returns the ip address of the target host. 217 | func (p *Pinger) IPAddr() *net.IPAddr { 218 | return p.ipaddr 219 | } 220 | 221 | // SetAddr resolves and sets the ip address of the target host, addr can be a 222 | // DNS name like "www.google.com" or IP like "127.0.0.1". 223 | func (p *Pinger) SetAddr(addr string) error { 224 | ipaddr, err := net.ResolveIPAddr("ip", addr) 225 | if err != nil { 226 | return err 227 | } 228 | 229 | p.SetIPAddr(ipaddr) 230 | p.addr = addr 231 | return nil 232 | } 233 | 234 | // Addr returns the string ip address of the target host. 235 | func (p *Pinger) Addr() string { 236 | return p.addr 237 | } 238 | 239 | // SetPrivileged sets the type of ping pinger will send. 240 | // false means pinger will send an "unprivileged" UDP ping. 241 | // true means pinger will send a "privileged" raw ICMP ping. 242 | // NOTE: setting to true requires that it be run with super-user privileges. 243 | func (p *Pinger) SetPrivileged(privileged bool) { 244 | if privileged { 245 | p.network = "ip" 246 | } else { 247 | p.network = "udp" 248 | } 249 | } 250 | 251 | // Privileged returns whether pinger is running in privileged mode. 252 | func (p *Pinger) Privileged() bool { 253 | return p.network == "ip" 254 | } 255 | 256 | // Run runs the pinger. This is a blocking function that will exit when it's 257 | // done. If Count or Interval are not specified, it will run continuously until 258 | // it is interrupted. 259 | func (p *Pinger) Run() { 260 | p.run() 261 | } 262 | 263 | func (p *Pinger) run() { 264 | var conn *icmp.PacketConn 265 | if p.ipv4 { 266 | if conn = p.listen(ipv4Proto[p.network], p.source); conn == nil { 267 | return 268 | } 269 | } else { 270 | if conn = p.listen(ipv6Proto[p.network], p.source); conn == nil { 271 | return 272 | } 273 | } 274 | defer conn.Close() 275 | defer p.finish() 276 | 277 | var wg sync.WaitGroup 278 | recv := make(chan *packet, 5) 279 | wg.Add(1) 280 | go p.recvICMP(conn, recv, &wg) 281 | 282 | err := p.sendICMP(conn) 283 | if err != nil { 284 | fmt.Println(err.Error()) 285 | } 286 | 287 | timeout := time.NewTicker(p.Timeout) 288 | interval := time.NewTicker(p.Interval) 289 | c := make(chan os.Signal, 1) 290 | signal.Notify(c, os.Interrupt) 291 | signal.Notify(c, syscall.SIGTERM) 292 | 293 | for { 294 | select { 295 | case <-c: 296 | close(p.done) 297 | case <-p.done: 298 | wg.Wait() 299 | return 300 | case <-timeout.C: 301 | close(p.done) 302 | wg.Wait() 303 | return 304 | case <-interval.C: 305 | err = p.sendICMP(conn) 306 | if err != nil { 307 | fmt.Println("FATAL: ", err.Error()) 308 | } 309 | case r := <-recv: 310 | err := p.processPacket(r) 311 | if err != nil { 312 | fmt.Println("FATAL: ", err.Error()) 313 | } 314 | default: 315 | if p.Count > 0 && p.PacketsRecv >= p.Count { 316 | close(p.done) 317 | wg.Wait() 318 | return 319 | } 320 | } 321 | } 322 | } 323 | 324 | func (p *Pinger) finish() { 325 | handler := p.OnFinish 326 | if handler != nil { 327 | s := p.Statistics() 328 | handler(s) 329 | } 330 | } 331 | 332 | // Statistics returns the statistics of the pinger. This can be run while the 333 | // pinger is running or after it is finished. OnFinish calls this function to 334 | // get it's finished statistics. 335 | func (p *Pinger) Statistics() *Statistics { 336 | loss := float64(p.PacketsSent-p.PacketsRecv) / float64(p.PacketsSent) * 100 337 | var min, max, total time.Duration 338 | if len(p.rtts) > 0 { 339 | min = p.rtts[0] 340 | max = p.rtts[0] 341 | } 342 | for _, rtt := range p.rtts { 343 | if rtt < min { 344 | min = rtt 345 | } 346 | if rtt > max { 347 | max = rtt 348 | } 349 | total += rtt 350 | } 351 | s := Statistics{ 352 | PacketsSent: p.PacketsSent, 353 | PacketsRecv: p.PacketsRecv, 354 | PacketLoss: loss, 355 | Rtts: p.rtts, 356 | Addr: p.addr, 357 | IPAddr: p.ipaddr, 358 | MaxRtt: max, 359 | MinRtt: min, 360 | } 361 | if len(p.rtts) > 0 { 362 | s.AvgRtt = total / time.Duration(len(p.rtts)) 363 | var sumsquares time.Duration 364 | for _, rtt := range p.rtts { 365 | sumsquares += (rtt - s.AvgRtt) * (rtt - s.AvgRtt) 366 | } 367 | s.StdDevRtt = time.Duration(math.Sqrt( 368 | float64(sumsquares / time.Duration(len(p.rtts))))) 369 | } 370 | return &s 371 | } 372 | 373 | func (p *Pinger) recvICMP( 374 | conn *icmp.PacketConn, 375 | recv chan<- *packet, 376 | wg *sync.WaitGroup, 377 | ) { 378 | defer wg.Done() 379 | for { 380 | select { 381 | case <-p.done: 382 | return 383 | default: 384 | bytes := make([]byte, 512) 385 | conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100)) 386 | n, _, err := conn.ReadFrom(bytes) 387 | if err != nil { 388 | if neterr, ok := err.(*net.OpError); ok { 389 | if neterr.Timeout() { 390 | // Read timeout 391 | continue 392 | } else { 393 | close(p.done) 394 | return 395 | } 396 | } 397 | } 398 | 399 | recv <- &packet{bytes: bytes, nbytes: n} 400 | } 401 | } 402 | } 403 | 404 | func (p *Pinger) processPacket(recv *packet) error { 405 | var bytes []byte 406 | var proto int 407 | if p.ipv4 { 408 | if p.network == "ip" { 409 | bytes = ipv4Payload(recv.bytes) 410 | } else { 411 | bytes = recv.bytes 412 | } 413 | proto = protocolICMP 414 | } else { 415 | bytes = recv.bytes 416 | proto = protocolIPv6ICMP 417 | } 418 | 419 | var m *icmp.Message 420 | var err error 421 | if m, err = icmp.ParseMessage(proto, bytes[:recv.nbytes]); err != nil { 422 | return fmt.Errorf("Error parsing icmp message") 423 | } 424 | 425 | if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply { 426 | // Not an echo reply, ignore it 427 | return nil 428 | } 429 | 430 | outPkt := &Packet{ 431 | Nbytes: recv.nbytes, 432 | IPAddr: p.ipaddr, 433 | } 434 | 435 | switch pkt := m.Body.(type) { 436 | case *icmp.Echo: 437 | outPkt.Rtt = time.Since(bytesToTime(pkt.Data[:timeSliceLength])) 438 | outPkt.Seq = pkt.Seq 439 | p.PacketsRecv += 1 440 | default: 441 | // Very bad, not sure how this can happen 442 | return fmt.Errorf("Error, invalid ICMP echo reply. Body type: %T, %s", 443 | pkt, pkt) 444 | } 445 | 446 | p.rtts = append(p.rtts, outPkt.Rtt) 447 | handler := p.OnRecv 448 | if handler != nil { 449 | handler(outPkt) 450 | } 451 | 452 | return nil 453 | } 454 | 455 | func (p *Pinger) sendICMP(conn *icmp.PacketConn) error { 456 | var typ icmp.Type 457 | if p.ipv4 { 458 | typ = ipv4.ICMPTypeEcho 459 | } else { 460 | typ = ipv6.ICMPTypeEchoRequest 461 | } 462 | 463 | var dst net.Addr = p.ipaddr 464 | if p.network == "udp" { 465 | dst = &net.UDPAddr{IP: p.ipaddr.IP, Zone: p.ipaddr.Zone} 466 | } 467 | 468 | t := timeToBytes(time.Now()) 469 | if p.size-timeSliceLength != 0 { 470 | t = append(t, byteSliceOfSize(p.size-timeSliceLength)...) 471 | } 472 | bytes, err := (&icmp.Message{ 473 | Type: typ, Code: 0, 474 | Body: &icmp.Echo{ 475 | ID: rand.Intn(65535), 476 | Seq: p.sequence, 477 | Data: t, 478 | }, 479 | }).Marshal(nil) 480 | if err != nil { 481 | return err 482 | } 483 | 484 | for { 485 | if _, err := conn.WriteTo(bytes, dst); err != nil { 486 | if neterr, ok := err.(*net.OpError); ok { 487 | if neterr.Err == syscall.ENOBUFS { 488 | continue 489 | } 490 | } 491 | } 492 | p.PacketsSent += 1 493 | p.sequence += 1 494 | break 495 | } 496 | return nil 497 | } 498 | 499 | func (p *Pinger) listen(netProto string, source string) *icmp.PacketConn { 500 | conn, err := icmp.ListenPacket(netProto, source) 501 | if err != nil { 502 | fmt.Printf("Error listening for ICMP packets: %s\n", err.Error()) 503 | close(p.done) 504 | return nil 505 | } 506 | return conn 507 | } 508 | 509 | func byteSliceOfSize(n int) []byte { 510 | b := make([]byte, n) 511 | for i := 0; i < len(b); i++ { 512 | b[i] = 1 513 | } 514 | 515 | return b 516 | } 517 | 518 | func ipv4Payload(b []byte) []byte { 519 | if len(b) < ipv4.HeaderLen { 520 | return b 521 | } 522 | hdrlen := int(b[0]&0x0f) << 2 523 | return b[hdrlen:] 524 | } 525 | 526 | func bytesToTime(b []byte) time.Time { 527 | var nsec int64 528 | for i := uint8(0); i < 8; i++ { 529 | nsec += int64(b[i]) << ((7 - i) * 8) 530 | } 531 | return time.Unix(nsec/1000000000, nsec%1000000000) 532 | } 533 | 534 | func isIPv4(ip net.IP) bool { 535 | return len(ip.To4()) == net.IPv4len 536 | } 537 | 538 | func isIPv6(ip net.IP) bool { 539 | return len(ip) == net.IPv6len 540 | } 541 | 542 | func timeToBytes(t time.Time) []byte { 543 | nsec := t.UnixNano() 544 | b := make([]byte, 8) 545 | for i := uint8(0); i < 8; i++ { 546 | b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff) 547 | } 548 | return b 549 | } 550 | -------------------------------------------------------------------------------- /ping_test.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "net" 5 | "runtime/debug" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestNewPingerValid(t *testing.T) { 11 | p, err := NewPinger("www.google.com") 12 | AssertNoError(t, err) 13 | AssertEqualStrings(t, "www.google.com", p.Addr()) 14 | // DNS names should resolve into IP addresses 15 | AssertNotEqualStrings(t, "www.google.com", p.IPAddr().String()) 16 | AssertTrue(t, isIPv4(p.IPAddr().IP)) 17 | AssertFalse(t, p.Privileged()) 18 | // Test that SetPrivileged works 19 | p.SetPrivileged(true) 20 | AssertTrue(t, p.Privileged()) 21 | // Test setting to ipv4 address 22 | err = p.SetAddr("www.google.com") 23 | AssertNoError(t, err) 24 | AssertTrue(t, isIPv4(p.IPAddr().IP)) 25 | // Test setting to ipv6 address 26 | err = p.SetAddr("ipv6.google.com") 27 | AssertNoError(t, err) 28 | AssertTrue(t, isIPv6(p.IPAddr().IP)) 29 | 30 | p, err = NewPinger("localhost") 31 | AssertNoError(t, err) 32 | AssertEqualStrings(t, "localhost", p.Addr()) 33 | // DNS names should resolve into IP addresses 34 | AssertNotEqualStrings(t, "localhost", p.IPAddr().String()) 35 | AssertTrue(t, isIPv4(p.IPAddr().IP)) 36 | AssertFalse(t, p.Privileged()) 37 | // Test that SetPrivileged works 38 | p.SetPrivileged(true) 39 | AssertTrue(t, p.Privileged()) 40 | // Test setting to ipv4 address 41 | err = p.SetAddr("www.google.com") 42 | AssertNoError(t, err) 43 | AssertTrue(t, isIPv4(p.IPAddr().IP)) 44 | // Test setting to ipv6 address 45 | err = p.SetAddr("ipv6.google.com") 46 | AssertNoError(t, err) 47 | AssertTrue(t, isIPv6(p.IPAddr().IP)) 48 | 49 | p, err = NewPinger("127.0.0.1") 50 | AssertNoError(t, err) 51 | AssertEqualStrings(t, "127.0.0.1", p.Addr()) 52 | AssertTrue(t, isIPv4(p.IPAddr().IP)) 53 | AssertFalse(t, p.Privileged()) 54 | // Test that SetPrivileged works 55 | p.SetPrivileged(true) 56 | AssertTrue(t, p.Privileged()) 57 | // Test setting to ipv4 address 58 | err = p.SetAddr("www.google.com") 59 | AssertNoError(t, err) 60 | AssertTrue(t, isIPv4(p.IPAddr().IP)) 61 | // Test setting to ipv6 address 62 | err = p.SetAddr("ipv6.google.com") 63 | AssertNoError(t, err) 64 | AssertTrue(t, isIPv6(p.IPAddr().IP)) 65 | 66 | p, err = NewPinger("ipv6.google.com") 67 | AssertNoError(t, err) 68 | AssertEqualStrings(t, "ipv6.google.com", p.Addr()) 69 | // DNS names should resolve into IP addresses 70 | AssertNotEqualStrings(t, "ipv6.google.com", p.IPAddr().String()) 71 | AssertTrue(t, isIPv6(p.IPAddr().IP)) 72 | AssertFalse(t, p.Privileged()) 73 | // Test that SetPrivileged works 74 | p.SetPrivileged(true) 75 | AssertTrue(t, p.Privileged()) 76 | // Test setting to ipv4 address 77 | err = p.SetAddr("www.google.com") 78 | AssertNoError(t, err) 79 | AssertTrue(t, isIPv4(p.IPAddr().IP)) 80 | // Test setting to ipv6 address 81 | err = p.SetAddr("ipv6.google.com") 82 | AssertNoError(t, err) 83 | AssertTrue(t, isIPv6(p.IPAddr().IP)) 84 | 85 | // ipv6 localhost: 86 | p, err = NewPinger("::1") 87 | AssertNoError(t, err) 88 | AssertEqualStrings(t, "::1", p.Addr()) 89 | AssertTrue(t, isIPv6(p.IPAddr().IP)) 90 | AssertFalse(t, p.Privileged()) 91 | // Test that SetPrivileged works 92 | p.SetPrivileged(true) 93 | AssertTrue(t, p.Privileged()) 94 | // Test setting to ipv4 address 95 | err = p.SetAddr("www.google.com") 96 | AssertNoError(t, err) 97 | AssertTrue(t, isIPv4(p.IPAddr().IP)) 98 | // Test setting to ipv6 address 99 | err = p.SetAddr("ipv6.google.com") 100 | AssertNoError(t, err) 101 | AssertTrue(t, isIPv6(p.IPAddr().IP)) 102 | } 103 | 104 | func TestNewPingerInvalid(t *testing.T) { 105 | _, err := NewPinger("127.0.0.0.1") 106 | AssertError(t, err, "127.0.0.0.1") 107 | 108 | _, err = NewPinger("127..0.0.1") 109 | AssertError(t, err, "127..0.0.1") 110 | 111 | _, err = NewPinger("wtf") 112 | AssertError(t, err, "wtf") 113 | 114 | _, err = NewPinger(":::1") 115 | AssertError(t, err, ":::1") 116 | 117 | _, err = NewPinger("ipv5.google.com") 118 | AssertError(t, err, "ipv5.google.com") 119 | } 120 | 121 | func TestSetIPAddr(t *testing.T) { 122 | googleaddr, err := net.ResolveIPAddr("ip", "www.google.com") 123 | if err != nil { 124 | t.Fatal("Can't resolve www.google.com, can't run tests") 125 | } 126 | 127 | // Create a localhost ipv4 pinger 128 | p, err := NewPinger("localhost") 129 | AssertNoError(t, err) 130 | AssertEqualStrings(t, "localhost", p.Addr()) 131 | 132 | // set IPAddr to google 133 | p.SetIPAddr(googleaddr) 134 | AssertEqualStrings(t, googleaddr.String(), p.Addr()) 135 | } 136 | 137 | func TestStatisticsSunny(t *testing.T) { 138 | // Create a localhost ipv4 pinger 139 | p, err := NewPinger("localhost") 140 | AssertNoError(t, err) 141 | AssertEqualStrings(t, "localhost", p.Addr()) 142 | 143 | p.PacketsSent = 10 144 | p.PacketsRecv = 10 145 | p.rtts = []time.Duration{ 146 | time.Duration(1000), 147 | time.Duration(1000), 148 | time.Duration(1000), 149 | time.Duration(1000), 150 | time.Duration(1000), 151 | time.Duration(1000), 152 | time.Duration(1000), 153 | time.Duration(1000), 154 | time.Duration(1000), 155 | time.Duration(1000), 156 | } 157 | 158 | stats := p.Statistics() 159 | if stats.PacketsRecv != 10 { 160 | t.Errorf("Expected %v, got %v", 10, stats.PacketsRecv) 161 | } 162 | if stats.PacketsSent != 10 { 163 | t.Errorf("Expected %v, got %v", 10, stats.PacketsSent) 164 | } 165 | if stats.PacketLoss != 0 { 166 | t.Errorf("Expected %v, got %v", 0, stats.PacketLoss) 167 | } 168 | if stats.MinRtt != time.Duration(1000) { 169 | t.Errorf("Expected %v, got %v", time.Duration(1000), stats.MinRtt) 170 | } 171 | if stats.MaxRtt != time.Duration(1000) { 172 | t.Errorf("Expected %v, got %v", time.Duration(1000), stats.MaxRtt) 173 | } 174 | if stats.AvgRtt != time.Duration(1000) { 175 | t.Errorf("Expected %v, got %v", time.Duration(1000), stats.AvgRtt) 176 | } 177 | if stats.StdDevRtt != time.Duration(0) { 178 | t.Errorf("Expected %v, got %v", time.Duration(0), stats.StdDevRtt) 179 | } 180 | } 181 | 182 | func TestStatisticsLossy(t *testing.T) { 183 | // Create a localhost ipv4 pinger 184 | p, err := NewPinger("localhost") 185 | AssertNoError(t, err) 186 | AssertEqualStrings(t, "localhost", p.Addr()) 187 | 188 | p.PacketsSent = 20 189 | p.PacketsRecv = 10 190 | p.rtts = []time.Duration{ 191 | time.Duration(10), 192 | time.Duration(1000), 193 | time.Duration(1000), 194 | time.Duration(10000), 195 | time.Duration(1000), 196 | time.Duration(800), 197 | time.Duration(1000), 198 | time.Duration(40), 199 | time.Duration(100000), 200 | time.Duration(1000), 201 | } 202 | 203 | stats := p.Statistics() 204 | if stats.PacketsRecv != 10 { 205 | t.Errorf("Expected %v, got %v", 10, stats.PacketsRecv) 206 | } 207 | if stats.PacketsSent != 20 { 208 | t.Errorf("Expected %v, got %v", 20, stats.PacketsSent) 209 | } 210 | if stats.PacketLoss != 50 { 211 | t.Errorf("Expected %v, got %v", 50, stats.PacketLoss) 212 | } 213 | if stats.MinRtt != time.Duration(10) { 214 | t.Errorf("Expected %v, got %v", time.Duration(10), stats.MinRtt) 215 | } 216 | if stats.MaxRtt != time.Duration(100000) { 217 | t.Errorf("Expected %v, got %v", time.Duration(100000), stats.MaxRtt) 218 | } 219 | if stats.AvgRtt != time.Duration(11585) { 220 | t.Errorf("Expected %v, got %v", time.Duration(11585), stats.AvgRtt) 221 | } 222 | if stats.StdDevRtt != time.Duration(29603) { 223 | t.Errorf("Expected %v, got %v", time.Duration(29603), stats.StdDevRtt) 224 | } 225 | } 226 | 227 | // Test helpers 228 | func AssertNoError(t *testing.T, err error) { 229 | if err != nil { 230 | t.Errorf("Expected No Error but got %s, Stack:\n%s", 231 | err, string(debug.Stack())) 232 | } 233 | } 234 | 235 | func AssertError(t *testing.T, err error, info string) { 236 | if err == nil { 237 | t.Errorf("Expected Error but got %s, %s, Stack:\n%s", 238 | err, info, string(debug.Stack())) 239 | } 240 | } 241 | 242 | func AssertEqualStrings(t *testing.T, expected, actual string) { 243 | if expected != actual { 244 | t.Errorf("Expected %s, got %s, Stack:\n%s", 245 | expected, actual, string(debug.Stack())) 246 | } 247 | } 248 | 249 | func AssertNotEqualStrings(t *testing.T, expected, actual string) { 250 | if expected == actual { 251 | t.Errorf("Expected %s, got %s, Stack:\n%s", 252 | expected, actual, string(debug.Stack())) 253 | } 254 | } 255 | 256 | func AssertTrue(t *testing.T, b bool) { 257 | if !b { 258 | t.Errorf("Expected True, got False, Stack:\n%s", string(debug.Stack())) 259 | } 260 | } 261 | 262 | func AssertFalse(t *testing.T, b bool) { 263 | if b { 264 | t.Errorf("Expected False, got True, Stack:\n%s", string(debug.Stack())) 265 | } 266 | } 267 | --------------------------------------------------------------------------------