├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── ping.go └── ping_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mehrdad Arshad Rad 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 | # ping 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/mehrdadrad/ping)](https://goreportcard.com/report/github.com/mehrdadrad/ping) 3 | [![GoDoc](https://godoc.org/github.com/mehrdadrad/ping?status.svg)](https://godoc.org/github.com/mehrdadrad/ping) 4 | 5 | Golang native ICMP-based ping IPv4 and IPv6 library 6 | 7 | ## Features 8 | - IPv4 and IPv6 9 | - non-privileged datagram-oriented ICMP 10 | - privileged raw ICMP 11 | - type of server 12 | - time to live 13 | - source ip address 14 | - incoming interface 15 | 16 | ## Supported platform 17 | - Linux 18 | - macOS 19 | 20 | ## Usage & Example 21 | 22 | For usage and examples see the [Godoc](http://godoc.org/github.com/mehrdadrad/ping). 23 | 24 | ```go 25 | package main 26 | 27 | import ( 28 | "fmt" 29 | "log" 30 | 31 | "github.com/mehrdadrad/ping" 32 | ) 33 | 34 | func main() { 35 | p, err := ping.New("google.com") 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | p.SetCount(4) 41 | 42 | r, err := p.Run() 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | for pr := range r { 48 | fmt.Printf("%#v\n", pr) 49 | } 50 | ``` 51 | ``` 52 | #go run main.go 53 | ping.Response{RTT:4.938, Size:64, TTL:56, Seq:0, Addr:"172.217.5.206", If:"eth0", Err:error(nil)} 54 | ping.Response{RTT:5.202, Size:64, TTL:56, Seq:1, Addr:"172.217.5.206", If:"eth0", Err:error(nil)} 55 | ping.Response{RTT:6.576, Size:64, TTL:56, Seq:2, Addr:"172.217.5.206", If:"eth0", Err:error(nil)} 56 | ping.Response{RTT:4.126, Size:64, TTL:56, Seq:3, Addr:"172.217.5.206", If:"eth0", Err:error(nil)} 57 | ping.Response{RTT:4.983, Size:64, TTL:56, Seq:4, Addr:"172.217.5.206", If:"eth0", Err:error(nil)} 58 | 59 | ``` 60 | 61 | ## License 62 | This project is licensed under MIT license. Please read the LICENSE file. 63 | 64 | ## Contribute 65 | Welcomes any kind of contribution, please follow the next steps: 66 | 67 | - Fork the project on github.com. 68 | - Create a new branch. 69 | - Commit changes to the new branch. 70 | - Send a pull request. 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mehrdadrad/ping 2 | 3 | go 1.14 4 | 5 | require golang.org/x/net v0.0.0-20200506145744-7e3656a0809f 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 2 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= 3 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 4 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 5 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= 6 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 7 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 8 | -------------------------------------------------------------------------------- /ping.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "math/rand" 8 | "net" 9 | "strings" 10 | "syscall" 11 | "time" 12 | 13 | "golang.org/x/net/icmp" 14 | "golang.org/x/net/ipv4" 15 | "golang.org/x/net/ipv6" 16 | ) 17 | 18 | const ( 19 | // ProtocolIPv4ICMP is IANA ICMP IPv4 20 | ProtocolIPv4ICMP = 1 21 | // ProtocolIPv6ICMP is IANA ICMP IPv6 22 | ProtocolIPv6ICMP = 58 23 | // icmpHeaderSize is ICMP header size 24 | icmpHeaderSize = 8 25 | ) 26 | 27 | // packet represents ping packet 28 | type packet struct { 29 | bytes []byte 30 | addr net.Addr 31 | ttl int 32 | err error 33 | } 34 | 35 | // Response represent ping response 36 | type Response struct { 37 | RTT float64 38 | Size int 39 | TTL int 40 | Seq int 41 | Addr string 42 | If string 43 | Err error 44 | } 45 | 46 | // Ping represents ping 47 | type Ping struct { 48 | m icmp.Message 49 | id int 50 | seq int 51 | pSize int 52 | ttl int 53 | tos int 54 | count int 55 | addr net.Addr 56 | addrs []net.IP 57 | ifs map[int]string 58 | host string 59 | isV4Avail bool 60 | forceV4 bool 61 | forceV6 bool 62 | privileged bool 63 | network string 64 | source string 65 | timeout time.Duration 66 | interval time.Duration 67 | } 68 | 69 | // New constructs ping object 70 | func New(host string) (*Ping, error) { 71 | var err error 72 | 73 | rand.Seed(time.Now().UnixNano()) 74 | 75 | p := Ping{ 76 | id: rand.Intn(0xffff), 77 | seq: -1, 78 | pSize: 64, 79 | ttl: 64, 80 | tos: 0, 81 | host: host, 82 | count: 1, 83 | privileged: true, 84 | } 85 | 86 | // resolve host 87 | ips, err := net.LookupIP(host) 88 | if err != nil { 89 | return nil, err 90 | } 91 | p.addrs = ips 92 | 93 | ifs, err := net.Interfaces() 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | p.ifs = make(map[int]string) 99 | 100 | p.ifs[0] = "na" 101 | for i := range ifs { 102 | p.ifs[ifs[i].Index] = ifs[i].Name 103 | } 104 | 105 | p.timeout, _ = time.ParseDuration("2s") 106 | p.interval, _ = time.ParseDuration("1s") 107 | 108 | return &p, nil 109 | } 110 | 111 | // SetSrcIPAddr sets the source ip address 112 | func (p *Ping) SetSrcIPAddr(addr string) { 113 | p.source = addr 114 | } 115 | 116 | // SetCount sets the count packets 117 | func (p *Ping) SetCount(c int) { 118 | p.count = c 119 | } 120 | 121 | // SetTTL sets the IPv4 packet TTL or IPv6 hop-limit for ICMP request packets 122 | func (p *Ping) SetTTL(t int) { 123 | p.ttl = t 124 | } 125 | 126 | // SetPacketSize sets the ICMP packet size 127 | func (p *Ping) SetPacketSize(s int) { 128 | p.pSize = s 129 | } 130 | 131 | // SetForceV4 sets force v4 132 | func (p *Ping) SetForceV4() { 133 | p.forceV4 = true 134 | p.forceV6 = false 135 | } 136 | 137 | // SetForceV6 sets force v6 138 | func (p *Ping) SetForceV6() { 139 | p.forceV4 = false 140 | p.forceV6 = true 141 | } 142 | 143 | // SetPrivilegedICMP sets privileged raw ICMP or non-privileged datagram-oriented ICMP 144 | func (p *Ping) SetPrivilegedICMP(i bool) { 145 | p.privileged = i 146 | } 147 | 148 | // SetInterval sets wait interval between sending each packet 149 | func (p *Ping) SetInterval(i string) error { 150 | interval, err := time.ParseDuration(i) 151 | if err != nil { 152 | return err 153 | } 154 | 155 | p.interval = interval 156 | 157 | return nil 158 | } 159 | 160 | // SetTimeout sets wait time for a reply for each packet sent 161 | func (p *Ping) SetTimeout(i string) error { 162 | timeout, err := time.ParseDuration(i) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | p.timeout = timeout 168 | 169 | return nil 170 | } 171 | 172 | // SetTOS sets type of service for each echo request packet 173 | func (p *Ping) SetTOS(t int) error { 174 | if t > 255 && t < 0 { 175 | return fmt.Errorf("invalid tos") 176 | } 177 | 178 | p.tos = t 179 | 180 | return nil 181 | } 182 | 183 | // setIP set ip address 184 | func (p *Ping) setIP(ips []net.IP) error { 185 | for _, ip := range ips { 186 | if !isIPv6(ip.String()) && !p.forceV6 { 187 | if p.privileged { 188 | p.addr = &net.IPAddr{IP: ip} 189 | p.network = "ip4:icmp" 190 | } else { 191 | p.addr = &net.UDPAddr{IP: ip, Port: 0} 192 | p.network = "udp4" 193 | } 194 | 195 | p.isV4Avail = true 196 | 197 | return nil 198 | } else if isIPv6(ip.String()) && !p.forceV4 { 199 | if p.privileged { 200 | p.addr = &net.IPAddr{IP: ip} 201 | p.network = "ip6:ipv6-icmp" 202 | } else { 203 | p.addr = &net.UDPAddr{IP: ip, Port: 0} 204 | p.network = "udp6" 205 | } 206 | 207 | p.isV4Avail = false 208 | 209 | return nil 210 | } 211 | } 212 | 213 | return fmt.Errorf("there is not A or AAAA record") 214 | } 215 | 216 | // isIPv6 returns true if ip version is v6 217 | func isIPv6(ip string) bool { 218 | return strings.Count(ip, ":") >= 2 219 | } 220 | 221 | // Run sends the ICMP message to destination / target 222 | func (p *Ping) Run() (chan Response, error) { 223 | var ( 224 | r = make(chan Response, 1) 225 | conn *icmp.PacketConn 226 | err error 227 | ) 228 | 229 | if err := p.setIP(p.addrs); err != nil { 230 | return nil, err 231 | } 232 | 233 | if p.isV4Avail { 234 | if conn, err = p.listen(); err != nil { 235 | return nil, err 236 | } 237 | } else { 238 | if conn, err = p.listen(); err != nil { 239 | return nil, err 240 | } 241 | } 242 | 243 | go func() { 244 | for n := 0; n < p.count; n++ { 245 | p.ping(conn, r) 246 | if n != p.count-1 { 247 | time.Sleep(p.interval) 248 | } 249 | } 250 | close(r) 251 | }() 252 | return r, nil 253 | } 254 | 255 | // RunWithContext sends the ICMP message to destination / target with context 256 | func (p *Ping) RunWithContext(ctx context.Context) (chan Response, error) { 257 | var ( 258 | r = make(chan Response, 1) 259 | conn *icmp.PacketConn 260 | err error 261 | ) 262 | 263 | if err := p.setIP(p.addrs); err != nil { 264 | return nil, err 265 | } 266 | 267 | if p.isV4Avail { 268 | if conn, err = p.listen(); err != nil { 269 | return nil, err 270 | } 271 | } else { 272 | if conn, err = p.listen(); err != nil { 273 | return nil, err 274 | } 275 | } 276 | 277 | go func() { 278 | for n := 0; n < p.count; n++ { 279 | select { 280 | case <-ctx.Done(): 281 | conn.Close() 282 | default: 283 | p.ping(conn, r) 284 | if n != p.count-1 { 285 | time.Sleep(p.interval) 286 | } 287 | } 288 | } 289 | close(r) 290 | }() 291 | 292 | return r, nil 293 | } 294 | 295 | // listen starts to listen incoming icmp 296 | func (p *Ping) listen() (*icmp.PacketConn, error) { 297 | c, err := icmp.ListenPacket(p.network, p.source) 298 | if err != nil { 299 | return c, err 300 | } 301 | return c, nil 302 | } 303 | 304 | // recv4 reads icmp message for IPv4 305 | func (p *Ping) recv4(conn *icmp.PacketConn, rcvdChan chan<- Response) { 306 | var ( 307 | err error 308 | src net.Addr 309 | ts = time.Now() 310 | ifName string 311 | n, ttl, icmpType int 312 | ) 313 | 314 | bytes := make([]byte, 1500) 315 | conn.SetReadDeadline(time.Now().Add(p.timeout)) 316 | 317 | for { 318 | var cm *ipv4.ControlMessage 319 | n, cm, src, err = conn.IPv4PacketConn().ReadFrom(bytes) 320 | if cm != nil { 321 | ttl = cm.TTL 322 | ifName = p.ifs[cm.IfIndex] 323 | } else { 324 | ifName = "na" 325 | } 326 | 327 | if err != nil { 328 | if neterr, ok := err.(*net.OpError); ok { 329 | if neterr.Timeout() { 330 | err = errors.New("Request timeout") 331 | } 332 | } 333 | } 334 | 335 | bytes = bytes[:n] 336 | 337 | if n > 0 { 338 | icmpType = int(bytes[0]) 339 | } 340 | 341 | switch icmpType { 342 | case int(ipv4.ICMPTypeTimeExceeded): 343 | if n >= 28 && p.isMyReply(bytes) { 344 | err = errors.New("Time exceeded") 345 | rcvdChan <- Response{Addr: p.getIPAddr(src), TTL: ttl, Seq: p.seq, Size: p.pSize, If: ifName, Err: err} 346 | return 347 | } 348 | case int(ipv4.ICMPTypeEchoReply): 349 | if n >= 8 && p.isMyEchoReply(bytes) { 350 | rtt := float64(time.Now().UnixNano()-getTimeStamp(bytes[8:])) / 1000000 351 | rcvdChan <- Response{Addr: p.getIPAddr(src), TTL: ttl, Seq: p.seq, Size: p.pSize, RTT: rtt, If: ifName, Err: err} 352 | return 353 | } 354 | case int(ipv4.ICMPTypeDestinationUnreachable): 355 | if n >= 28 && p.isMyReply(bytes) { 356 | err = errors.New(unreachableMessage(bytes)) 357 | rcvdChan <- Response{Addr: p.getIPAddr(src), TTL: ttl, Seq: p.seq, Size: p.pSize, If: ifName, Err: err} 358 | return 359 | } 360 | case int(ipv4.ICMPTypeRedirect): 361 | if n >= 28 && p.isMyReply(bytes) { 362 | err = errors.New(redirectMessage(bytes)) 363 | rcvdChan <- Response{Addr: p.getIPAddr(src), TTL: ttl, Seq: p.seq, Size: p.pSize, If: ifName, Err: err} 364 | return 365 | } 366 | default: 367 | // TODO 368 | } 369 | 370 | if time.Since(ts) < p.timeout { 371 | continue 372 | } 373 | 374 | err = errors.New("Request timeout") 375 | rcvdChan <- Response{Addr: p.getIPAddr(src), Seq: p.seq, Err: err} 376 | break 377 | } 378 | } 379 | 380 | // recv6 reads icmp message for IPv6 381 | func (p *Ping) recv6(conn *icmp.PacketConn, rcvdChan chan<- Response) { 382 | var ( 383 | err error 384 | src net.Addr 385 | ts = time.Now() 386 | ifName string 387 | n, ttl, icmpType int 388 | ) 389 | 390 | bytes := make([]byte, 1500) 391 | conn.SetReadDeadline(time.Now().Add(p.timeout)) 392 | 393 | for { 394 | var cm *ipv6.ControlMessage 395 | n, cm, src, err = conn.IPv6PacketConn().ReadFrom(bytes) 396 | if cm != nil { 397 | ttl = cm.HopLimit 398 | ifName = p.ifs[cm.IfIndex] 399 | } else { 400 | ifName = "na" 401 | } 402 | 403 | if err != nil { 404 | if neterr, ok := err.(*net.OpError); ok { 405 | if neterr.Timeout() { 406 | err = errors.New("Request timeout") 407 | } 408 | } 409 | } 410 | 411 | bytes = bytes[:n] 412 | 413 | if n > 0 { 414 | icmpType = int(bytes[0]) 415 | } 416 | 417 | switch icmpType { 418 | case int(ipv6.ICMPTypeTimeExceeded): 419 | if n >= 48 && p.isMyReply(bytes) { 420 | err = errors.New("Time exceeded") 421 | rcvdChan <- Response{Addr: p.getIPAddr(src), TTL: ttl, Seq: p.seq, Size: p.pSize, If: ifName, Err: err} 422 | return 423 | } 424 | case int(ipv6.ICMPTypeEchoReply): 425 | if n >= 8 && p.isMyEchoReply(bytes) { 426 | rtt := float64(time.Now().UnixNano()-getTimeStamp(bytes[8:])) / 1000000 427 | rcvdChan <- Response{Addr: p.getIPAddr(src), TTL: ttl, Seq: p.seq, Size: p.pSize, RTT: rtt, If: ifName, Err: err} 428 | return 429 | } 430 | case int(ipv6.ICMPTypeDestinationUnreachable): 431 | if n >= 48 && p.isMyReply(bytes) { 432 | err = errors.New(unreachableMessage(bytes)) 433 | rcvdChan <- Response{Addr: p.getIPAddr(src), TTL: ttl, Seq: p.seq, Size: p.pSize, If: ifName, Err: err} 434 | return 435 | } 436 | case int(ipv6.ICMPTypeRedirect): 437 | if n >= 48 && p.isMyReply(bytes) { 438 | err = errors.New(redirectMessage(bytes)) 439 | rcvdChan <- Response{Addr: p.getIPAddr(src), TTL: ttl, Seq: p.seq, Size: p.pSize, If: ifName, Err: err} 440 | return 441 | } 442 | default: 443 | // TODO 444 | } 445 | 446 | if time.Since(ts) < p.timeout { 447 | continue 448 | } 449 | 450 | err = errors.New("Request timeout") 451 | rcvdChan <- Response{Addr: p.getIPAddr(src), Seq: p.seq, Err: err} 452 | break 453 | } 454 | } 455 | 456 | func (p *Ping) send(conn *icmp.PacketConn) error { 457 | var ( 458 | icmpType icmp.Type 459 | err error 460 | ) 461 | 462 | if isIPv6(p.addr.String()) { 463 | icmpType = ipv6.ICMPTypeEchoRequest 464 | conn.IPv6PacketConn().SetHopLimit(p.ttl) 465 | conn.IPv6PacketConn().SetControlMessage(ipv6.FlagHopLimit, true) 466 | conn.IPv6PacketConn().SetControlMessage(ipv6.FlagInterface, true) 467 | 468 | } else { 469 | icmpType = ipv4.ICMPTypeEcho 470 | conn.IPv4PacketConn().SetTTL(p.ttl) 471 | conn.IPv4PacketConn().SetControlMessage(ipv4.FlagTTL, true) 472 | conn.IPv4PacketConn().SetTOS(p.tos) 473 | conn.IPv4PacketConn().SetControlMessage(ipv4.FlagInterface, true) 474 | } 475 | 476 | p.seq++ 477 | bytes, err := (&icmp.Message{ 478 | Type: icmpType, Code: 0, 479 | Body: &icmp.Echo{ 480 | ID: p.id, 481 | Seq: p.seq, 482 | Data: p.payload(time.Now().UnixNano()), 483 | }, 484 | }).Marshal(nil) 485 | if err != nil { 486 | return err 487 | } 488 | 489 | for range []int{0, 1} { 490 | if _, err = conn.WriteTo(bytes, p.addr); err != nil { 491 | if neterr, ok := err.(*net.OpError); ok { 492 | if neterr.Err == syscall.ENOBUFS { 493 | continue 494 | } 495 | } 496 | } 497 | break 498 | } 499 | 500 | return err 501 | } 502 | 503 | func (p *Ping) payload(ts int64) []byte { 504 | data := make([]byte, 8) 505 | n := icmpHeaderSize + 8 506 | 507 | // add timestamp 508 | for i := uint8(0); i < 8; i++ { 509 | data[i] = byte((ts >> (i * 8)) & 0xff) 510 | } 511 | 512 | // add id if privileged icmp 513 | if !p.privileged { 514 | id := make([]byte, 2) 515 | for i := uint8(0); i < 2; i++ { 516 | id[i] = byte((int16(p.id) >> (i * 8)) & 0xff) 517 | } 518 | data = append(data, id...) 519 | n += 2 520 | } 521 | 522 | payload := make([]byte, p.pSize-n) 523 | return append(data, payload...) 524 | } 525 | 526 | // ping sends and receives an ICMP packet 527 | func (p *Ping) ping(conn *icmp.PacketConn, resp chan Response) { 528 | if err := p.send(conn); err != nil { 529 | resp <- Response{Err: err, Addr: p.getIPAddr(p.addr), Seq: p.seq, Size: p.pSize} 530 | } else { 531 | if p.isV4Avail { 532 | p.recv4(conn, resp) 533 | } else { 534 | p.recv6(conn, resp) 535 | } 536 | } 537 | } 538 | 539 | func (p *Ping) isMyReply(bytes []byte) bool { 540 | var n = 28 541 | 542 | if !p.isV4Avail { 543 | n = 48 544 | } 545 | 546 | respID := int(bytes[n+4])<<8 | int(bytes[n+5]) 547 | respSq := int(bytes[n+6])<<8 | int(bytes[n+7]) 548 | 549 | if p.id == respID && p.seq == respSq { 550 | return true 551 | } 552 | 553 | return false 554 | } 555 | 556 | func (p *Ping) isMyEchoReply(bytes []byte) bool { 557 | var respID int 558 | 559 | if p.privileged { 560 | respID = int(bytes[4])<<8 | int(bytes[5]) 561 | } else { 562 | respID = int(bytes[17])<<8 | int(bytes[16]) 563 | } 564 | 565 | respSq := int(bytes[6])<<8 | int(bytes[7]) 566 | if respID == p.id && respSq == p.seq { 567 | return true 568 | } 569 | 570 | return false 571 | } 572 | 573 | func (p *Ping) getIPAddr(a net.Addr) string { 574 | switch a.(type) { 575 | case *net.UDPAddr: 576 | h, _, err := net.SplitHostPort(a.String()) 577 | if err != nil { 578 | return "na" 579 | } 580 | return h 581 | case *net.IPAddr: 582 | return a.String() 583 | } 584 | 585 | h, _, err := net.SplitHostPort(p.addr.String()) 586 | if err != nil { 587 | return "na" 588 | } 589 | return h 590 | } 591 | 592 | func getTimeStamp(m []byte) int64 { 593 | var ts int64 594 | for i := uint(0); i < 8; i++ { 595 | ts += int64(m[i]) << (i * 8) 596 | } 597 | return ts 598 | } 599 | 600 | func unreachableMessage(bytes []byte) string { 601 | code := int(bytes[1]) 602 | mtu := int(bytes[6])<<8 | int(bytes[7]) 603 | var errors = []string{ 604 | "Network unreachable", 605 | "Host unreachable", 606 | "Protocol unreachable", 607 | "Port unreachable", 608 | fmt.Sprintf("The datagram is too big - next-hop MTU: %d", mtu), 609 | "Source route failed", 610 | "Destination network unknown", 611 | "Destination host unknown", 612 | "Source host isolated", 613 | "The destination network is administratively prohibited", 614 | "The destination host is administratively prohibited", 615 | "The network is unreachable for Type Of Service", 616 | "The host is unreachable for Type Of Service", 617 | "Communication administratively prohibited", 618 | "Host precedence violation", 619 | "Precedence cutoff in effect", 620 | } 621 | 622 | return errors[code] 623 | } 624 | 625 | func redirectMessage(bytes []byte) string { 626 | code := int(bytes[1]) 627 | var errors = []string{ 628 | "Redirect for Network", 629 | "Redirect for Host", 630 | "Redirect for Type of Service and Network", 631 | "Redirect for Type of Service and Host", 632 | } 633 | 634 | return errors[code] 635 | } 636 | -------------------------------------------------------------------------------- /ping_test.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "math/rand" 5 | "net" 6 | "os" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | var ( 12 | HostV4 = getEnvHostV4() 13 | HostV6 = getEnvHostV6() 14 | 15 | p = Ping{ 16 | id: rand.Intn(0xffff), 17 | seq: -1, 18 | pSize: 64, 19 | ttl: 64, 20 | tos: 0, 21 | host: HostV4, 22 | source: "0.0.0.0", 23 | isV4Avail: true, 24 | count: 1, 25 | network: "udp4", 26 | } 27 | ) 28 | 29 | func TestNew(t *testing.T) { 30 | _, err := New(HostV4) 31 | if err != nil { 32 | t.Error("New failed:", err) 33 | } 34 | } 35 | 36 | func TestGetTimeStamp(t *testing.T) { 37 | ts1 := time.Now().UnixNano() 38 | pl := p.payload(ts1) 39 | ts2 := getTimeStamp(pl) 40 | if ts1 != ts2 { 41 | t.Error("timestamp failed") 42 | } 43 | } 44 | 45 | func TestGetIPAddr(t *testing.T) { 46 | i := net.UDPAddr{ 47 | IP: net.ParseIP("192.168.10.1"), 48 | Port: 1000, 49 | } 50 | 51 | o := p.getIPAddr(&i) 52 | 53 | if o != "192.168.10.1" { 54 | t.Error("getIPAddr UDPAdd failed") 55 | } 56 | 57 | ii := net.IPAddr{ 58 | IP: net.ParseIP("192.168.10.1"), 59 | } 60 | 61 | o = p.getIPAddr(&ii) 62 | 63 | if o != "192.168.10.1" { 64 | t.Error("getIPAddr UDPAdd failed") 65 | } 66 | } 67 | 68 | func TestListen(t *testing.T) { 69 | conn, err := p.listen() 70 | if err != nil { 71 | t.Error(err) 72 | } 73 | 74 | a := conn.IPv4PacketConn().LocalAddr() 75 | if a.String() != "0.0.0.0:0" { 76 | t.Error("expect to have 0.0.0.0 but, ", a.String()) 77 | } 78 | } 79 | 80 | func TestSendRecv4(t *testing.T) { 81 | 82 | p, err := New(HostV4) 83 | if err != nil { 84 | t.Error(err) 85 | } 86 | 87 | p.privileged = false 88 | p.network = "udp4" 89 | 90 | conn, err := p.listen() 91 | if err != nil { 92 | t.Error(err) 93 | } 94 | 95 | p.addr = &net.UDPAddr{ 96 | IP: net.ParseIP(HostV4), 97 | Port: 0, 98 | } 99 | 100 | err = p.send(conn) 101 | if err != nil { 102 | t.Error(err) 103 | } 104 | 105 | rc := make(chan Response, 1) 106 | p.recv4(conn, rc) 107 | r := <-rc 108 | 109 | if r.Err != nil { 110 | t.Error(r.Err) 111 | } 112 | } 113 | 114 | func TestSendRecv6(t *testing.T) { 115 | 116 | p, err := New("::1") 117 | if err != nil { 118 | t.Error(err) 119 | } 120 | 121 | p.privileged = false 122 | p.network = "udp6" 123 | 124 | conn, err := p.listen() 125 | if err != nil { 126 | t.Error(err) 127 | } 128 | 129 | p.addr = &net.UDPAddr{ 130 | IP: net.ParseIP("::1"), 131 | Port: 0, 132 | } 133 | 134 | err = p.send(conn) 135 | if err != nil { 136 | t.Error(err) 137 | } 138 | 139 | rc := make(chan Response, 1) 140 | p.recv6(conn, rc) 141 | r := <-rc 142 | 143 | if r.Err != nil { 144 | t.Error(r.Err) 145 | } 146 | } 147 | 148 | func TestSetIP(t *testing.T) { 149 | p, err := New(HostV4) 150 | if err != nil { 151 | t.Error(err) 152 | } 153 | 154 | ips := []net.IP{net.ParseIP(HostV4)} 155 | 156 | p.privileged = true 157 | p.setIP(ips) 158 | if p.network != "ip4:icmp" { 159 | t.Error("expected ip4:icmp but got", p.network) 160 | } 161 | 162 | p.privileged = false 163 | p.setIP(ips) 164 | if p.network != "udp4" { 165 | t.Error("expected udp4 but got", p.network) 166 | } 167 | } 168 | 169 | func TestSetIP6(t *testing.T) { 170 | p, err := New("::1") 171 | if err != nil { 172 | t.Error(err) 173 | } 174 | 175 | ips := []net.IP{net.ParseIP("::1")} 176 | 177 | p.privileged = true 178 | p.setIP(ips) 179 | if p.network != "ip6:ipv6-icmp" { 180 | t.Error("expected ip6:icmp but got", p.network) 181 | } 182 | 183 | p.privileged = false 184 | p.setIP(ips) 185 | if p.network != "udp6" { 186 | t.Error("expected udp6 but got", p.network) 187 | } 188 | } 189 | 190 | func TestSetSrcIPAddr(t *testing.T) { 191 | p.SetSrcIPAddr(HostV4) 192 | if p.source != HostV4 { 193 | t.Error("expected source 127.0.0.1 but got,", p.source) 194 | } 195 | } 196 | 197 | func TestSetInterval(t *testing.T) { 198 | err := p.SetInterval("2s") 199 | if err != nil { 200 | t.Error("unexpected error", err) 201 | } 202 | if p.interval != time.Second*2 { 203 | t.Error("expected 2s interval but got", p.interval.String()) 204 | } 205 | 206 | err = p.SetInterval("2") 207 | if err == nil { 208 | t.Error("expected to have error but nothing") 209 | } 210 | } 211 | 212 | func TestUnreachableMessage(t *testing.T) { 213 | 214 | msg := unreachableMessage([]byte{0, 3, 0, 0, 0, 0, 0, 0}) 215 | if msg != "Port unreachable" { 216 | t.Error("expected to get Port unreachable but got,", msg) 217 | } 218 | } 219 | 220 | func TestIsMyEchoReply(t *testing.T) { 221 | p.seq = 0 222 | p.id = 8247 223 | data := []byte{0x0, 0x0, 0xe9, 0xd, 0x20, 0x37, 0x0, 0x0, 0xb8, 224 | 0x20, 0xfb, 0xa1, 0xf5, 0xc1, 0x16, 0x16, 0x37, 0x20} 225 | if ok := p.isMyEchoReply(data); !ok { 226 | t.Error("expected to get true but got false") 227 | } 228 | 229 | p.privileged = true 230 | if ok := p.isMyEchoReply(data); !ok { 231 | t.Error("expected to get true but got false") 232 | } 233 | 234 | } 235 | 236 | func TestRun(t *testing.T) { 237 | p1, err := New(HostV4) 238 | if err != nil { 239 | t.Fatal(err) 240 | } 241 | 242 | p1.SetPrivilegedICMP(false) 243 | p1.SetCount(1) 244 | 245 | r, err := p1.Run() 246 | if err != nil { 247 | t.Fatal(err) 248 | } 249 | 250 | result := <-r 251 | if result.Err != nil { 252 | t.Fatal(result.Err) 253 | } 254 | 255 | if result.Addr != HostV4 { 256 | t.Errorf("expect addr : %s but got %s", HostV4, result.Addr) 257 | } 258 | } 259 | 260 | func getEnvHostV4() string { 261 | h := os.Getenv("PING_HOST_V4") 262 | if len(h) > 0 { 263 | return h 264 | } 265 | 266 | return "127.0.0.1" 267 | } 268 | 269 | func getEnvHostV6() string { 270 | h := os.Getenv("PING_HOST_V6") 271 | if len(h) > 0 { 272 | return h 273 | } 274 | 275 | return "::1" 276 | } 277 | --------------------------------------------------------------------------------