Skip to content

Instantly share code, notes, and snippets.

@lmas
Last active April 18, 2024 11:45
Show Gist options
  • Save lmas/c13d1c9de3b2224f9c26435eb56e6ef3 to your computer and use it in GitHub Desktop.
Save lmas/c13d1c9de3b2224f9c26435eb56e6ef3 to your computer and use it in GitHub Desktop.
Simple utility package to send ICMP pings with Go
// Copyright © 2016 Alex
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"fmt"
"log"
"net"
"os"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
func main() {
addr := "google.com"
dst, dur, err := Ping(addr)
if err != nil {
panic(err)
}
log.Printf("Ping %s (%s): %s\n", addr, dst, dur)
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const (
// Stolen from https://godoc.org/golang.org/x/net/internal/iana,
// can't import "internal" packages
ProtocolICMP = 1
//ProtocolIPv6ICMP = 58
)
// Default to listen on all IPv4 interfaces
var ListenAddr = "0.0.0.0"
// Mostly based on https://github.com/golang/net/blob/master/icmp/ping_test.go
// All ye beware, there be dragons below...
func Ping(addr string) (*net.IPAddr, time.Duration, error) {
// Start listening for icmp replies
c, err := icmp.ListenPacket("ip4:icmp", ListenAddr)
if err != nil {
return nil, 0, err
}
defer c.Close()
// Resolve any DNS (if used) and get the real IP of the target
dst, err := net.ResolveIPAddr("ip4", addr)
if err != nil {
panic(err)
return nil, 0, err
}
// Make a new ICMP message
m := icmp.Message{
Type: ipv4.ICMPTypeEcho, Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff, Seq: 1, //<< uint(seq), // TODO
Data: []byte(""),
},
}
b, err := m.Marshal(nil)
if err != nil {
return dst, 0, err
}
// Send it
start := time.Now()
n, err := c.WriteTo(b, dst)
if err != nil {
return dst, 0, err
} else if n != len(b) {
return dst, 0, fmt.Errorf("got %v; want %v", n, len(b))
}
// Wait for a reply
reply := make([]byte, 1500)
err = c.SetReadDeadline(time.Now().Add(10 * time.Second))
if err != nil {
return dst, 0, err
}
n, peer, err := c.ReadFrom(reply)
if err != nil {
return dst, 0, err
}
duration := time.Since(start)
// Pack it up boys, we're done here
rm, err := icmp.ParseMessage(ProtocolICMP, reply[:n])
if err != nil {
return dst, 0, err
}
switch rm.Type {
case ipv4.ICMPTypeEchoReply:
return dst, duration, nil
default:
return dst, 0, fmt.Errorf("got %+v from %v; want echo reply", rm, peer)
}
}
@lmas
Copy link
Author

lmas commented Sep 20, 2020

@tlhakhan sorry for such a late reply, probably missed a notification :(

This is old code by now, so of course any thoughts I had while writing it is now lost. Though I guess that check is for ICMP fragments, caused by poor connection or too large packet or yeah, some other fault on the host. A better way to handle it would be to retry sending it, instead of simply erroring out. But I think it was more for logging purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment