package scream
import (
"fmt"
"net"
"os"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
// PingHost Send a icmp packet and wait for reply, and return icmp reply time
func PingHost(addr string, blackout int) (*net.IPAddr, time.Duration, error) {
c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return nil, 0, err
}
defer c.Close()
dst, err := net.ResolveIPAddr("ip4", addr)
if err != nil {
return nil, 0, err
}
// Make a new hello message
m := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff, Seq: 1,
Data: []byte(""),
},
}
b, err := m.Marshal(nil)
if err != nil {
return dst, 0, err
}
// send hi to destination
start := time.Now()
n, err := c.WriteTo(b, dst)
if err != nil {
return dst, 0, err
} else if n != len(b) {
return dst, 0, fmt.Errorf("can not write all data, wrote: %v, data: %v", n, len(b))
}
// catch the reply
reply := make([]byte, 1500)
err = c.SetReadDeadline(time.Now().Add(time.Duration(blackout) * time.Second))
if err != nil {
return dst, 0, err
}
n, peer, err := c.ReadFrom(reply)
if err != nil {
return dst, 0, err
}
duration := time.Since(start)
rm, err := icmp.ParseMessage(1, reply[:n])
if err != nil {
return dst, 0, err
}
switch rm.Type {
case ipv4.ICMPTypeEchoReply:
return dst, duration, nil
default:
return dst, 0, fmt.Errorf("mismatch data, %v and %v", rm, peer)
}
}