DEV Community

hideckies
hideckies

Posted on • Originally published at blog.hdks.org

Create Ping in Go

The other day, I create Ping in Go programming langueage for learning purposes.

To be honest, I wanted to create it without relying on external packages because I wanted to know deeply about how Ping works.

But fortunately (and unfortunately), there are useful packages (like "net/icmp", "net/ipv4" or "net/ipv6") for sending ICMP packets in Go. So I decided to use them.

The code of this repository is the enhanced version of the code in here. Please check it out if you're interested in.

1. Create a New Go Project

First off, make a new folder and create a new project using "go mod init" command.

Then create a new file e.g. "main.go".

mkdir myping
cd myping
go mod init example/myping
touch main.go
Enter fullscreen mode Exit fullscreen mode

2. Import External Packages

Open your favorite code editor, and start writing code in the "main.go".

Import some packages which used for receiving/sending ICMP packets and common ones for showing results.

package main

import (
    "fmt"
    "log"

    "golang.org/x/net/icmp"
    "golang.org/x/net/ipv4"
    "golang.org/x/net/ipv6"
)
Enter fullscreen mode Exit fullscreen mode

3. Create an ICMP Packet Receiver

To check if packets are coming back from the destination, you need to make a listener and wait for getting the packets.

Here I use IPv4 for the network structure so the network value of the argument of "icmp.ListenPacket" is "ip4:icmp" or "ip4:1". If you use IPv6, this value must be "ip6:ipv6-icmp" or "ip6:58".

package main

...

func main() {

    packetconn, err := icmp.ListenPacket("ip4:1", "0.0.0.0")
    if err != nil {
        log.Fatal(err)
    }
    defer packetconn.Close()

}
Enter fullscreen mode Exit fullscreen mode

4. Prepare ICMP Message

Next, create an ICMP message which contains Code, Identifier, Data, and so on.

As I said above, I use Ipv4 so set the Type field into ipv4.ICMPType.

Set 0 (echo reply) into the Code field for ping.

...

func main() {

    ...

    msg := &icmp.Message{
        Type: ipv4.ICMPType,
        Code: 0,
        Body: &icmp.Echo{
            ID: os.Getpid() & 0xffff,
            Seq: 0,
            Data: []byte("hello"),
        }
    }

    wb, err := msg.Marshal(nil)
    if err != nil {
        log.Fatal(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Implement to Send Packets

WriteTo function writes the ICMP message to the destination.

As an example this time, set Google DNS (8.8.8.8) as the destination.

...

func main() {

    ...

    if _, err := packetconn.WriteTo(wb, "8.8.8.8"); err != nil {
        log.Fatal(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Implement to Receive Message

The packet from the destination is processed in here.

...

func main() {

    ...

    rb := make([]byte, 1500)
    n, peer, err := packetconn.ReadFrom(rb)
    if err != nil {
        log.Fatal(err)
    }

    rm, err != icmp.ParseMessage(1, rb[:n])
    if err !=nil {
        log.Fatal(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

7. Implement to Display the Result

At the end, display the information about the received packet or failed.

...

func main() {

    ...

    switch rm.Type {
        case ipv4.ICMPTypeEchoReply:
            fmt.Printf("received from %v", peer)
        default:
            fmt.Printf("Failed: %+v\n", rm)
    }
}
Enter fullscreen mode Exit fullscreen mode

Build & Run

After implementing, check if our ping works correctly.

go mod tidy
go build
./myping
Enter fullscreen mode Exit fullscreen mode

Conclusion

As above, we can send ICMP packets too easily if we use some useful packages.

Below is the entire code.

package main

import (
    "fmt"
    "log"

    "golang.org/x/net/icmp"
    "golang.org/x/net/ipv4"
    "golang.org/x/net/ipv6"
)

func main() {
    packetconn, err := icmp.ListenPacket("ip4:1", "0.0.0.0")
    if err != nil {
        log.Fatal(err)
    }
    defer packetconn.Close()

    msg := &icmp.Message{
        Type: ipv4.ICMPType,
        Code: 0,
        Body: &icmp.Echo{
            ID: os.Getpid() & 0xffff,
            Seq: 0,
            Data: []byte("hello"),
        }
    }

    wb, err := msg.Marshal(nil)
    if err != nil {
        log.Fatal(err)
    }

    if _, err := packetconn.WriteTo(wb, "8.8.8.8"); err != nil {
        log.Fatal(err)
    }

    rb := make([]byte, 1500)
    n, peer, err := packetconn.ReadFrom(rb)
    if err != nil {
        log.Fatal(err)
    }

    rm, err != icmp.ParseMessage(1, rb[:n])
    if err !=nil {
        log.Fatal(err)
    }

    switch rm.Type {
        case ipv4.ICMPTypeEchoReply:
            fmt.Printf("received from %v", peer)
        default:
            fmt.Printf("Failed: %+v\n", rm)
    }
}
Enter fullscreen mode Exit fullscreen mode

Actually I wanted to make it while examining the internal parts more, but I couldn't pursue it deeply because the packages are so useful. I would like to try this again later if I feel like it.

Top comments (0)