Create Ping in Go
Tags: go, ping, network
Published: 2022-10-17 05:52:29

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](https://github.com/hideckies/pingo) is the enhanced version of the code in here. Please check it out if you're interested in.

<br />

## 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".

```sh
mkdir myping
cd myping
go mod init example/myping
touch main.go
```

<br />

## 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.

```go
package main

import (
    "fmt"
    "log"

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

<br />

## 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"**.

```go
package main

...

func main() {

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

}
```

<br />

## 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.

```go
...

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)
    }
}
```

<br />

## 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.

```go
...

func main() {

    ...

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

<br />

## 6. Implement to Receive Message

The packet from the destination is processed in here.

```go
...

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)
    }
}
```

<br />

## 7. Implement to Display the Result

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

```go
...

func main() {

    ...

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

<br />

## Build & Run

After implementing, check if our ping works correctly.

```sh
go mod tidy
go build
./myping
```

<br />

## Conclusion

As above, we can send ICMP packets too easily if we use some useful packages.  
Below is the entire code.

```go
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)
    }
}
```

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.