Implementing ICMP ping in Go
Asked Answered
C

3

9

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.

Costplus answered 30/5, 2010 at 0:46 Comment(0)
S
5

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

Shanney answered 30/5, 2010 at 3:1 Comment(2)
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.goJohnnyjumpup
Link by @Johnnyjumpup doesn't work anymore. New URL should be: golang.org/src/net/ipraw_test.goEmigration
Y
13

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

Yunyunfei answered 5/1, 2015 at 3:30 Comment(1)
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.ProtocolBenjamin
S
5

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

Shanney answered 30/5, 2010 at 3:1 Comment(2)
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.goJohnnyjumpup
Link by @Johnnyjumpup doesn't work anymore. New URL should be: golang.org/src/net/ipraw_test.goEmigration
W
2

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)
    }
}
Washday answered 2/3, 2022 at 15:6 Comment(2)
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.