Golang实现ping

ICMP部分的结构

报头

ICMP报头从IP报头的第160位开始,即第20个字节开始(除非使用了IP报头的可选部分)。

Bits

160-167

168-175

176-183

184-191

160

Type

Code

校验码(checksum)

192

ID

序号(sequence)

  • Type - ICMP的类型,标识生成的错误报文;
  • Code - 进一步划分ICMP的类型,该字段用来查找产生错误的原因.;例如,ICMP的目标不可达类型可以把这个位设为1至15等来表示不同的意思。
  • Checksum - 校验码部分,这个字段包含有从ICMP报头和数据部分计算得来的,用于检查错误的数据,其中此校验码字段的值视为0。
  • ID - 这个字段包含了ID值,在Echo Reply类型的消息中要返回这个字段。
  • Sequence - 这个字段包含一个序号,同样要在Echo Reply类型的消息中要返回这个字段。

可能的消息列表(不是全部)

类型

代码

描述

0 - Echo Reply

0

echo响应 (被程序ping使用)

1 and 2

保留

3 - 目的地不可到达

0

目标网络不可达

1

目标主机不可达

2

目标协议不可达

3

目标端口不可达

4

要求分段并设置DF flag标志

5

源路由失败

6

未知的目标网络

7

未知的目标主机

8

源主机隔离

9

禁止访问的网络

10

禁止访问的主机

11

对特定的TOS 网络不可达

12

对特定的TOS 主机不可达

13

网络流量被禁止

4 - ICMP 拥塞控制

0

拥塞控制

5 - 路径控制

0

重定向网络

1

重定向主机

2

基于TOS 的网络重定向

3

基于TOS 的主机重定向

6

Alternate Host Address

7

保留

8 - Echo Request

0

Echo请求

9 - 路由器通告

0

路由建议

10 - 路由器请求

0

路由器的发现/选择/请求

11 - ICMP 超时

0

TTL 超时

1

分片重组超时

12 - 参数错误

0

IP 报首部参数错误

1

丢失选项

2

不支持的长度

13 - 时间戳请求

0

时间戳请求

14 - 时间戳应答

0

时间戳应答

15 - 信息请求

0

信息请求(已弃用)

16 - 信息应答

0

信息应答(已弃用)

17 - 地址掩码请求

0

地址掩码请求

18 - 地址掩码应答

0

地址掩码应答

19

因安全原因保留

20 至 29

Reserved for robustness experiment

30 - Traceroute

0

信息请求

31

数据报转换出错

32

手机网络重定向

33

Where-Are-You(originally meant for IPv6)

34

Here-I-Am(originally meant for IPv6)

35

Mobile Registration Request

36

Mobile Registration Reply

37

Domain Name Request

38

Domain Name Reply

39

SKIP Algorithm Discovery Protocol, Simple Key-Management for Internet Protocol

40

Photuris, Security failures

41

ICMP for experimental mobility protocols such as Seamoby [RFC4065]

42 到 255

保留

在使用Go语言的net.Dial函数时,发送echo request报文时,不用考虑i前20个字节的ip头;但是在接收到echo response消息时,前20字节是ip头。后面的内容才是icmp的内容,应该与echo request的内容一致。

package main

import (
	"flag"
	"fmt"
	"net"
	"os"
	"strconv"
	"time"
)

func main() {
	var count int
	var timeout int64
	var size int
	var neverstop bool

	flag.Int64Var(&timeout, "w", 1000, "等待每次回复的超时时间(毫秒)。")
	flag.IntVar(&count, "n", 4, "要发送的回显请求数。")
	flag.IntVar(&size, "l", 32, "要发送缓冲区大小。")
	flag.BoolVar(&neverstop, "t", false, "Ping 指定的主机,直到停止。")

	flag.Parse()
	args := flag.Args()

	if len(args) < 1 {
		fmt.Println("Usage: ", os.Args[0], "host")
		flag.PrintDefaults()
		flag.Usage()
		os.Exit(1)
	}

	ch := make(chan int)
	argsmap := map[string]interface{}{}

	argsmap["w"] = timeout
	argsmap["n"] = count
	argsmap["l"] = size
	argsmap["t"] = neverstop

	for _, host := range args {
		go ping(host, ch, argsmap)
	}

	for i := 0; i < len(args); i++ {
		<-ch
	}

	os.Exit(0)
}

func ping(host string, c chan int, args map[string]interface{}) {
	var count int
	var size int
	var timeout int64
	var neverstop bool
	count = args["n"].(int)
	size = args["l"].(int)
	timeout = args["w"].(int64)
	neverstop = args["t"].(bool)

	cname, _ := net.LookupCNAME(host)
	starttime := time.Now()
	conn, err := net.DialTimeout("ip4:icmp", host, time.Duration(timeout*1000*1000))
	ip := conn.RemoteAddr()
	fmt.Println("正在 Ping " + cname + " [" + ip.String() + "] 具有 32 字节的数据:")

	var seq int16 = 1
	id0, id1 := genidentifier(host)
	const ECHO_REQUEST_HEAD_LEN = 8

	sendN := 0
	recvN := 0
	lostN := 0
	shortT := -1
	longT := -1
	sumT := 0

	for count > 0 || neverstop {
		sendN++
		var msg []byte = make([]byte, size+ECHO_REQUEST_HEAD_LEN)
		msg[0] = 8                        // echo
		msg[1] = 0                        // code 0
		msg[2] = 0                        // checksum
		msg[3] = 0                        // checksum
		msg[4], msg[5] = id0, id1         //identifier[0] identifier[1]
		msg[6], msg[7] = gensequence(seq) //sequence[0], sequence[1]

		length := size + ECHO_REQUEST_HEAD_LEN

		check := checkSum(msg[0:length])
		msg[2] = byte(check >> 8)
		msg[3] = byte(check & 255)

		conn, err = net.DialTimeout("ip:icmp", host, time.Duration(timeout*1000*1000))

		checkError(err)

		starttime = time.Now()
		conn.SetDeadline(starttime.Add(time.Duration(timeout * 1000 * 1000)))
		_, err = conn.Write(msg[0:length])

		const ECHO_REPLY_HEAD_LEN = 20

		var receive []byte = make([]byte, ECHO_REPLY_HEAD_LEN+length)
		n, err := conn.Read(receive)
		_ = n

		var endduration int = int(int64(time.Since(starttime)) / (1000 * 1000))

		sumT += endduration

		time.Sleep(1000 * 1000 * 1000)

		if err != nil || receive[ECHO_REPLY_HEAD_LEN+4] != msg[4] || receive[ECHO_REPLY_HEAD_LEN+5] != msg[5] || receive[ECHO_REPLY_HEAD_LEN+6] != msg[6] || receive[ECHO_REPLY_HEAD_LEN+7] != msg[7] || endduration >= int(timeout) || receive[ECHO_REPLY_HEAD_LEN] == 11 {
			lostN++
			fmt.Println("对 " + cname + "[" + ip.String() + "]" + " 的请求超时。")
		} else {
			if shortT == -1 {
				shortT = endduration
			} else if shortT > endduration {
				shortT = endduration
			}
			if longT == -1 {
				longT = endduration
			} else if longT < endduration {
				longT = endduration
			}
			recvN++
			ttl := int(receive[8])
			//			fmt.Println(ttl)
			fmt.Println("来自 " + cname + "[" + ip.String() + "]" + " 的回复: 字节=32 时间=" + strconv.Itoa(endduration) + "ms TTL=" + strconv.Itoa(ttl))
		}

		seq++
		count--
	}
	stat(ip.String(), sendN, lostN, recvN, shortT, longT, sumT)
	c <- 1
}

func checkSum(msg []byte) uint16 {
	sum := 0

	length := len(msg)
	for i := 0; i < length-1; i += 2 {
		sum += int(msg[i])*256 + int(msg[i+1])
	}
	if length%2 == 1 {
		sum += int(msg[length-1]) * 256 // notice here, why *256?
	}

	sum = (sum >> 16) + (sum & 0xffff)
	sum += (sum >> 16)
	var answer uint16 = uint16(^sum)
	return answer
}

func checkError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
		os.Exit(1)
	}
}

func gensequence(v int16) (byte, byte) {
	ret1 := byte(v >> 8)
	ret2 := byte(v & 255)
	return ret1, ret2
}

func genidentifier(host string) (byte, byte) {
	return host[0], host[1]
}

func stat(ip string, sendN int, lostN int, recvN int, shortT int, longT int, sumT int) {
	fmt.Println()
	fmt.Println(ip, " 的 Ping 统计信息:")
	fmt.Printf("    数据包: 已发送 = %d,已接收 = %d,丢失 = %d (%d%% 丢失),\n", sendN, recvN, lostN, int(lostN*100/sendN))
	fmt.Println("往返行程的估计时间(以毫秒为单位):")
	if recvN != 0 {
		fmt.Printf("    最短 = %dms,最长 = %dms,平均 = %dms\n", shortT, longT, sumT/sendN)
	}
}

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2016-04-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏腾讯NEXT学位

那些让编码效率起飞(前端)的工具了解一下

? | 导语 想晚上吃鸡?前端编码效率提升工具了解一下? 一、Bash篇(Mac) iTerm2 iTerm 2 is a terminal emulato...

1743
来自专栏Golang语言社区

从零开始创建一个基于Go语言的web service

20个小时的时间能干什么?也许浑浑噩噩就过去了,也许能看一些书、做一些工作、读几篇博客、再写个一两篇博客,等等。而黑客马拉松(HackAthon),其实是一种自...

4449
来自专栏FH云彩

本站使用的WordPress插件

1615
来自专栏魏琼东

AgileEAS.NET SOA 中间件平台.Net Socket通信框架-介绍

     AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平...

1040
来自专栏c#开发者

HTML5手机APP开发入(5)

HTML5手机APP开发入(5) 回顾一下 HTML5手机APP开发入(4) 如何自定义Component,directive HTML5手机APP开发入(...

6416
来自专栏SDNLAB

Netvirt之流表分析(一):Netvirt介绍

1. 架构 最近在看ODL的netvirt项目,netvirt是一个完整的网络虚拟机化解决方案,几乎可以实现neutron的所有功能,包括FWaaS,VPNaa...

3567
来自专栏菩提树下的杨过

jboss EAP 6.2 + Message Drive Bean(MDB) 整合IBM Webshpere MQ 7.5

上一篇我们知道了消息驱动Bean的基本用法,实际大型分布式企业应用中,往往会采用高性能的商业Queue产品,比如IBM Webshpere MQ(目前最新版本是...

2208
来自专栏杨建荣的学习笔记

关于奇怪的并行进程分析(一) (r6笔记第41天)

在使用orabbix进行监控的时候,得益于使用 实时DB time监控的选项,对于几分钟内的性能抖动也能够狠容易的记录下来,而且会把这个监控的结果基本真实反应出...

2877
来自专栏Hadoop实操

0001-CDH网络要求(Lenovo参考架构)

数据网络是用于数据访问的节点之间的私有集群数据互连,比如在集群内的节点之间移动数据,或者将数据导入到CDH集群。CDH集群通常会连接到企业内部的数据网络。

58714
来自专栏xingoo, 一个梦想做发明家的程序员

Elasticsearch推荐插件篇(head,sense,marvel)

安装head head插件可以用来快速查看elasticsearch中的数据概况以及非全量的数据,也支持控件化查询和rest请求,但是体验都不是很好。 一般就用...

2977

扫码关注云+社区