tcping/ping/ping.go
2020-05-11 22:18:16 +08:00

121 lines
2.4 KiB
Go

package ping
import (
"fmt"
"net"
"os"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
type IPType struct {
Type string
ListenAddr string
Network string
ICMPNetwork string
ProtocolNumber int
RequestMessageType icmp.Type
ReplyMessageType icmp.Type
}
var (
IPType4 = IPType{
Type: "4",
ListenAddr: "0.0.0.0",
Network: "ip4",
ICMPNetwork: "ip4:icmp",
ProtocolNumber: 1,
RequestMessageType: ipv4.ICMPTypeEcho,
ReplyMessageType: ipv4.ICMPTypeEchoReply,
}
IPType6 = IPType{
Type: "6",
ListenAddr: "::",
Network: "ip6",
ICMPNetwork: "ip6:ipv6-icmp",
ProtocolNumber: 58,
RequestMessageType: ipv6.ICMPTypeEchoRequest,
ReplyMessageType: ipv6.ICMPTypeEchoReply,
}
)
func New(address string) (*net.IPAddr, time.Duration, error) {
// Check ip type
// Resolve address
var err error
var dst *net.IPAddr
var ipType IPType
dst, err = net.ResolveIPAddr("ip4", address)
if err != nil {
dst, err = net.ResolveIPAddr("ip6", address)
if err != nil {
return nil, 0, err
} else {
ipType = IPType6
}
} else {
ipType = IPType4
}
// Start listening for icmp replies
c, err := icmp.ListenPacket(ipType.ICMPNetwork, ipType.ListenAddr)
if err != nil {
return nil, 0, err
}
defer c.Close()
// Make a new ICMP message
m := icmp.Message{
Type: ipType.RequestMessageType,
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 it
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("got %v; want %v", n, len(b))
}
// Wait for a reply
reply := make([]byte, 1500)
err = c.SetReadDeadline(time.Now().Add(3 * 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)
// Pack it up boys, we're done here
rm, err := icmp.ParseMessage(ipType.ProtocolNumber, reply[:n])
if err != nil {
return dst, 0, err
}
//return dst, duration, nil
switch rm.Type {
case ipType.ReplyMessageType:
return dst, duration, nil
default:
return dst, 0, fmt.Errorf("got %+v from %v; want echo reply", rm, peer)
}
return dst, 0, err
}