github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/core/ping/ping.go (about)

     1  // Copyright 2009 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Send icmp packets to a server to test network connectivity.
     6  //
     7  // Synopsis:
     8  //     ping [-hV] [-c COUNT] [-i INTERVAL] [-s PACKETSIZE] [-w DEADLINE] DESTINATION
     9  //
    10  // Options:
    11  //     -6: use ipv6 (ip6:ipv6-icmp)
    12  //     -s: data size (default: 64)
    13  //     -c: # iterations, 0 to run forever (default)
    14  //     -i: interval in milliseconds (default: 1000)
    15  //     -V: version
    16  //     -w: wait time in milliseconds (default: 100)
    17  //     -a: Audible rings a bell when a packet is received
    18  //     -h: help
    19  package main
    20  
    21  import (
    22  	"encoding/binary"
    23  	"flag"
    24  	"fmt"
    25  	"log"
    26  	"net"
    27  	"os"
    28  	"time"
    29  )
    30  
    31  var (
    32  	net6       = flag.Bool("6", false, "use ipv4 (means ip4:icmp) or 6 (ip6:ipv6-icmp)")
    33  	packetSize = flag.Int("s", 64, "Data size")
    34  	iter       = flag.Uint64("c", 0, "# iterations")
    35  	intv       = flag.Int("i", 1000, "interval in milliseconds")
    36  	version    = flag.Bool("V", false, "version")
    37  	wtf        = flag.Int("w", 100, "wait time in milliseconds")
    38  	audible    = flag.Bool("a", false, "Audible rings a bell when a packet is received")
    39  )
    40  
    41  const (
    42  	ICMP_TYPE_ECHO_REQUEST             = 8
    43  	ICMP_TYPE_ECHO_REPLY               = 0
    44  	ICMP_ECHO_REPLY_HEADER_IPV4_OFFSET = 20
    45  )
    46  
    47  const (
    48  	ICMP6_TYPE_ECHO_REQUEST             = 128
    49  	ICMP6_TYPE_ECHO_REPLY               = 129
    50  	ICMP6_ECHO_REPLY_HEADER_IPV6_OFFSET = 40
    51  )
    52  
    53  func usage() {
    54  	fmt.Fprintf(os.Stdout, "ping [-V] [-c count] [-i interval] [-s packetsize] [-w deadline] destination\n")
    55  	os.Exit(0)
    56  }
    57  
    58  func showversion() {
    59  	fmt.Fprintf(os.Stdout, "ping utility, Uroot version\n")
    60  	os.Exit(0)
    61  }
    62  
    63  func optwithoutparam() {
    64  	if *version {
    65  		showversion()
    66  	}
    67  	// if we reach this point, invalid or help (-h) gets the same result
    68  	usage()
    69  }
    70  
    71  func cksum(bs []byte) uint16 {
    72  	sum := uint32(0)
    73  
    74  	for k := 0; k < len(bs)/2; k++ {
    75  		sum += uint32(bs[k*2]) << 8
    76  		sum += uint32(bs[k*2+1])
    77  	}
    78  	if len(bs)%2 != 0 {
    79  		sum += uint32(bs[len(bs)-1]) << 8
    80  	}
    81  	sum = (sum >> 16) + (sum & 0xffff)
    82  	sum = (sum >> 16) + (sum & 0xffff)
    83  	if sum == 0xffff {
    84  		sum = 0
    85  	}
    86  
    87  	return ^uint16(sum)
    88  }
    89  
    90  func ping1(net6 bool, host string, i uint64, waitFor time.Duration) (string, error) {
    91  	netname := "ip4:icmp"
    92  	// todo: just figure out if it's an ip6 address and go from there.
    93  	if net6 {
    94  		netname = "ip6:ipv6-icmp"
    95  	}
    96  	c, derr := net.Dial(netname, host)
    97  	if derr != nil {
    98  		return "", fmt.Errorf("net.Dial(%v %v) failed: %v", netname, host, derr)
    99  	}
   100  	defer c.Close()
   101  
   102  	if net6 {
   103  		ipc := c.(*net.IPConn)
   104  		if err := setupICMPv6Socket(ipc); err != nil {
   105  			return "", fmt.Errorf("failed to set up the ICMPv6 connection: %w", err)
   106  		}
   107  	}
   108  
   109  	// Send ICMP Echo Request
   110  	c.SetDeadline(time.Now().Add(waitFor))
   111  	msg := make([]byte, *packetSize)
   112  	if net6 {
   113  		msg[0] = ICMP6_TYPE_ECHO_REQUEST
   114  	} else {
   115  		msg[0] = ICMP_TYPE_ECHO_REQUEST
   116  	}
   117  	msg[1] = 0
   118  	binary.BigEndian.PutUint16(msg[6:], uint16(i))
   119  	binary.BigEndian.PutUint16(msg[4:], uint16(i>>16))
   120  	binary.BigEndian.PutUint16(msg[2:], cksum(msg))
   121  	if _, err := c.Write(msg[:]); err != nil {
   122  		return "", fmt.Errorf("write failed: %v", err)
   123  	}
   124  
   125  	// Get ICMP Echo Reply
   126  	c.SetDeadline(time.Now().Add(waitFor))
   127  	rmsg := make([]byte, *packetSize+256)
   128  	before := time.Now()
   129  	amt, rerr := c.Read(rmsg[:])
   130  	if rerr != nil {
   131  		return "", fmt.Errorf("read failed: %v", rerr)
   132  	}
   133  	latency := time.Since(before)
   134  	if !net6 {
   135  		rmsg = rmsg[ICMP_ECHO_REPLY_HEADER_IPV4_OFFSET:]
   136  	}
   137  	if net6 {
   138  		if rmsg[0] != ICMP6_TYPE_ECHO_REPLY {
   139  			return "", fmt.Errorf("bad ICMPv6 echo reply type, got %d, want %d", rmsg[0], ICMP6_TYPE_ECHO_REPLY)
   140  		}
   141  	} else {
   142  		if rmsg[0] != ICMP_TYPE_ECHO_REPLY {
   143  			return "", fmt.Errorf("bad ICMP echo reply type, got %d, want %d", rmsg[0], ICMP_TYPE_ECHO_REPLY)
   144  		}
   145  	}
   146  	cks := binary.BigEndian.Uint16(rmsg[2:])
   147  	binary.BigEndian.PutUint16(rmsg[2:], 0)
   148  	// only validate the checksum for IPv4. For IPv6 this *should* be done by the
   149  	// TCP stack (and do we need to validate the checksum anyway?)
   150  	if !net6 && cks != cksum(rmsg) {
   151  		return "", fmt.Errorf("bad ICMP checksum: %v (expected %v)", cks, cksum(rmsg))
   152  	}
   153  	id := binary.BigEndian.Uint16(rmsg[4:])
   154  	seq := binary.BigEndian.Uint16(rmsg[6:])
   155  	rseq := uint64(id)<<16 + uint64(seq)
   156  	if rseq != i {
   157  		return "", fmt.Errorf("wrong sequence number %v (expected %v)", rseq, i)
   158  	}
   159  
   160  	return fmt.Sprintf("%d bytes from %v: icmp_seq=%v, time=%v", amt, host, i, latency), nil
   161  }
   162  
   163  func main() {
   164  	flag.Parse()
   165  
   166  	// options without parameters (right now just: -hV)
   167  	if flag.NArg() < 1 {
   168  		optwithoutparam()
   169  	}
   170  	if *packetSize < 8 {
   171  		log.Fatalf("packet size too small (must be >= 8): %v", *packetSize)
   172  	}
   173  
   174  	interval := time.Duration(*intv)
   175  	host := flag.Args()[0]
   176  
   177  	// ping needs to run forever, except if '*iter' is not zero
   178  	waitFor := time.Duration(*wtf) * time.Millisecond
   179  	var i uint64
   180  	for i = 1; *iter == 0 || i <= *iter; i++ {
   181  		msg, err := ping1(*net6, host, i, waitFor)
   182  		if err != nil {
   183  			log.Fatalf("ping failed: %v", err)
   184  		}
   185  		if *audible {
   186  			msg = "\a" + msg
   187  		}
   188  		log.Print(msg)
   189  		time.Sleep(time.Millisecond * interval)
   190  	}
   191  }