Is it possible to implement an ICMP ping in Go? The alternative is to fork a 'ping' process, but I'd rather write it in Go.
Implementing ICMP ping in Go
Currently, the ICMP Echo (Ping) function isn't supported in the Go net package.
There's no support for sending ICMP echo requests. You'd have to add support to package net. ping
Now (since late May 2010), Go supports raw sockets(See the net.IPConn type) - meaning you can implement ping yourself - and there's a ping example at code.google.com/p/go/source/browse/src/pkg/net/ipraw_test.go –
Johnnyjumpup
Link by @Johnnyjumpup doesn't work anymore. New URL should be: golang.org/src/net/ipraw_test.go –
Emigration
The following code shows how to perform a ping over IPv4 using a raw socket (requires root privs):
package main
import (
"log"
"net"
"os"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
const targetIP = "8.8.8.8"
func main() {
c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
log.Fatalf("listen err, %s", err)
}
defer c.Close()
wm := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff, Seq: 1,
Data: []byte("HELLO-R-U-THERE"),
},
}
wb, err := wm.Marshal(nil)
if err != nil {
log.Fatal(err)
}
if _, err := c.WriteTo(wb, &net.IPAddr{IP: net.ParseIP(targetIP)}); err != nil {
log.Fatalf("WriteTo err, %s", err)
}
rb := make([]byte, 1500)
n, peer, err := c.ReadFrom(rb)
if err != nil {
log.Fatal(err)
}
rm, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), rb[:n])
if err != nil {
log.Fatal(err)
}
switch rm.Type {
case ipv4.ICMPTypeEchoReply:
log.Printf("got reflection from %v", peer)
default:
log.Printf("got %+v; want echo reply", rm)
}
}
Code based on the example found here: https://godoc.org/golang.org/x/net/icmp#PacketConn
In order to ping from Linux as a non-privileged user, see this post
Right now the IANA number for ICMP (1) is not accessible via
iana.ProtocolICMP
, but it could be accessed via ipv4.ICMPTypeEcho.Protocol()
. The package golang.org/x/net/internal/iana
is internal, when used the go 1.8 compiler says: use of internal package not allowed
cf.: godoc.org/golang.org/x/net/ipv4#ICMPType.Protocol –
Benjamin Currently, the ICMP Echo (Ping) function isn't supported in the Go net package.
There's no support for sending ICMP echo requests. You'd have to add support to package net. ping
Now (since late May 2010), Go supports raw sockets(See the net.IPConn type) - meaning you can implement ping yourself - and there's a ping example at code.google.com/p/go/source/browse/src/pkg/net/ipraw_test.go –
Johnnyjumpup
Link by @Johnnyjumpup doesn't work anymore. New URL should be: golang.org/src/net/ipraw_test.go –
Emigration
To perform this without root
requirement you can use
package main
import (
"fmt"
"net"
"os"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
const target = "google.com"
func main() {
for {
time.Sleep(time.Second * 1)
Ping(target)
}
}
func Ping(target string) {
ip, err := net.ResolveIPAddr("ip4", target)
if err != nil {
panic(err)
}
conn, err := icmp.ListenPacket("udp4", "0.0.0.0")
if err != nil {
fmt.Printf("Error on ListenPacket")
panic(err)
}
defer conn.Close()
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff, Seq: 1,
Data: []byte(""),
},
}
msg_bytes, err := msg.Marshal(nil)
if err != nil {
fmt.Printf("Error on Marshal %v", msg_bytes)
panic(err)
}
// Write the message to the listening connection
if _, err := conn.WriteTo(msg_bytes, &net.UDPAddr{IP: net.ParseIP(ip.String())}); err != nil {
fmt.Printf("Error on WriteTo %v", err)
panic(err)
}
err = conn.SetReadDeadline(time.Now().Add(time.Second * 1))
if err != nil {
fmt.Printf("Error on SetReadDeadline %v", err)
panic(err)
}
reply := make([]byte, 1500)
n, _, err := conn.ReadFrom(reply)
if err != nil {
fmt.Printf("Error on ReadFrom %v", err)
panic(err)
}
parsed_reply, err := icmp.ParseMessage(1, reply[:n])
if err != nil {
fmt.Printf("Error on ParseMessage %v", err)
panic(err)
}
switch parsed_reply.Code {
case 0:
// Got a reply so we can save this
fmt.Printf("Got Reply from %s\n", target)
case 3:
fmt.Printf("Host %s is unreachable\n", target)
// Given that we don't expect google to be unreachable, we can assume that our network is down
case 11:
// Time Exceeded so we can assume our network is slow
fmt.Printf("Host %s is slow\n", target)
default:
// We don't know what this is so we can assume it's unreachable
fmt.Printf("Host %s is unreachable\n", target)
}
}
This does require the net.ping_group_range to be set to allow ICMP via command
sysctl -w net.ping_group_range="0 2147483647"
–
Balcony why do you set reply to 1500? IP header is max 60, icpm header is 8 and icmp body is max 576. So it should be safe/better to set it to 644, rigth? –
Discriminative
© 2022 - 2024 — McMap. All rights reserved.