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
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"
)
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()
}
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)
}
}
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)
}
}
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)
}
}
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)
}
}
Build & Run
After implementing, check if our ping works correctly.
go mod tidy
go build
./myping
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)
}
}
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)