icmp报文及用go实现

news2024/12/24 0:14:22

目录

一、概述

二、ICMP报文格式详解

2.1 什么是ICMP

2.2 ICMP报文格式

2.3 ICMP报文类型

2.4 实际报文举例

三、使用go实现icmp请求以及接收响应内容


一、概述

本文主要旨在学习icmp报文格式,以及通过go语言来实现ICMP发包。

二、ICMP报文格式详解

2.1 什么是ICMP

因特网控制报文协议ICMP(Internet Control Message Protocol)是一个差错报告机制,是TCP/IP协议簇中的一个重要子协议,通常被IP层或更高层协议(TCP或UDP)使用,属于网络层协议,主要用于在IP主机和路由器之间传递控制消息,用于报告主机是否可达、路由是否可用等。这些控制消息虽然并不传输用户数据,但是对于收集各种网络信息、诊断和排除各种网络故障以及用户数据的传递具有至关重要的作用。ICMP的功能是检错而不是纠错。

2.2 ICMP报文格式

CMP报文包含在IP数据报中,属于IP的一个用户,IP头部就在ICMP报文的前面,所以一个ICMP报文包括IP头部、ICMP头部和ICMP报文,IP头部的Protocol值为1就说明这是一个ICMP报文,ICMP头部中的类型(Type)域用于说明ICMP报文的作用及格式,此外还有一个代码(Code)域用于详细说明某种ICMP报文的类型,所有数据都在ICMP头部后面

  • type:类型,1字节,报文类型,用来标识报文
  • code:代码,1字节,提供报文类型的进一步信息
  • checksum:校验和,2字节,使用和IP相同的加法校验和算法,但是icmp校验仅覆盖ICMP报文
  • Message body:数据部分,长度可变,字段的长度的和内容,取决于消息的类型和代码

2.3 ICMP报文类型

1、类型比对表

typecode描述查询/差错
0--Echo(应答)响应0Echo Reply -- 回显应答(Ping应答)        查询
3--目的不可达0

Network Unreachable -- 网络不可达

差错
1Host Unreachable -- 主机不可达差错
2Protocol Unreachable --协议不可达差错
3Port Unreachable --端口不可达差错
4Fragmentation needed but no frag. bit set--要求分段并设置DF flag标志报文差错
5Source routing failed --源路由失败报文差错
6Destination network unknown --目的网络未知差错
7Destination host unknown --目的主机未知差错
8Source host isolated(obsolete)--源主机被隔离(作废不用)差错
9Destination network administratively prohibited -- 目的网络被强制禁止差错
10Destination host administratively prohibited --目的主机被强制禁止差错
11Network unreachable for TOS --对特定的TOS网络不可达报文差错
12Host unreachable for  TOS --对特定的TOS主机不可达报文差错
13Communiation administratively prohibited by filtering --由于过滤 网络流量被禁止报文差错
14Host precedence violation --主机越权报文差错
15Precedence cutoff ineffect --优先权终止生效报文差错
4--流量控制0Source quench --源端被关闭(基本流控制)差错
5--重定向0Redirect for network --对网络重定向差错
1Redirect for host --主机重定向差错
2Redirect for TOS and network --对服务类型和网络重定向差错
3Redirect for TOS and host --对服务类型和主机重定向差错
8--Echo请求0Echo request -- 回显请求(ping请求)查询
9-路由器通告0Router advertisement --路由器通告查询
10--路由器请求0Route solicitation --路由器的发现/选择/请求报文查询
11--ICMP超时0TTL equals 0 during transit --传输期间生存时间为0差错
1TTL equals 0 during reassembly --在数据报组装期间生存时间为0差错
12--参数问题0IP header bad(catchall error) --坏的IP首部(包括各种差错)差错
1Required options missing --缺少必须的选项差错
2不支持的长度报文差错
13--时间戳请求0Timestamp request(obsolete) --时间戳请求(作废不用)查询
14--时间戳应答Timestamp reply(obsolete) --时间戳应答(作废不用)查询
15--信息请求Information request(obsolete) --信息请求(作废不用)查询
16--信息应答0Information reply(obsolete) --信息应答(作废不用)查询
17--掩码请求0Address mask request --地址掩码请求查询
18--掩码应答0Address mask reply --地址掩码应答查询

2、ICMP分类

  • 差错报文

①目的不可达

目的不可达的类型字段值为3,代码字段有为0-15,也就是说若将目的不可达的ICMP报文再做一个细分,会将目的不可达的原因分为16种,并用不同ICMP差错报文进行表示。

  • code=0;代表着网络不可达,出现这个ICMP差错报文,就代表着报文在路由过程的时候出现了问题,比如报文的目的网络在路由器上没有相应的条目,于是该路由器就回送网络不可达的报文
  • code=1;代表主机不可达,这个报文的来源一般是目的主机所处的网关发送的,因为目的主机所处的网关没有找到对应的目的主机的IP地址,而无法转交该数据报文,所以将数据报文丢弃并回送该ICMP差错报文。
  • code=2;代表着协议不可达,这就说明数据交互的双方在协议上的出现了问题。
  • code=3;代表着端口不可达,这就说明数据包上指定的目的端口在目的主机上可能没有监听
  • code=4;代表一个原本需要分片的数据包,但是IP头部上的表示是不进行分片,由此就出现了错误。比如我们可以设置自己的网卡的MTU大小比网关的MTU大,那么我们发送过去的数据在被网关接收后可能会出现错误,因为网关网卡的最大接收MTU数比发送过来的数据包小,而且这个数据包还标识不进行分片,这就会出现错误。

②参数问题

参数问题的类型字段值为12,它主要是因为对IP头部中的字段值出现了问题,从而导致收到这些问题报文的主机返送一个参数问题的ICMP差错报文

  • ICMP控制报文
  • ①源站抑制

type=4,code=0

源站抑制是拥塞控制的一种方式,虽然TCP在端到端上使用了窗口机制和慢开始,拥塞避免和快重传对流量进行了控制,网关通过对链路上的链路情况进行监控,对信源发送源站抑制里面包含着目的网络的信息,当接收方接收该信息后根据目的网络信息知道去往该网络的链路发生拥塞,于是减少信息的发送。

  • ②路由重定向

type=5;code=0-3

路由重定向是指当主机发送给某个路由器的时候,这个路由器会判断自己是否是最佳的转发设备,如果根据它的路由信息发现其他的转发设备对于该主机来说最好,也就是能够更快的将数转发到目的对象,那么它就将发送路由重定向给这个主机让它将路由修改为更佳的路由。更佳路由的信息存储在ICMP的后4个字节上,

  • ICMP查询报文
  • ①请求和回应报文

type=8,code=0

需要注意的是请求和回应的ICMP报文使用到了ICMP头部的后4个字节,分为两个字段,即标识(和序列号,标识一般是发送该报文的进程号,标识和序列号是标识一对请求和回应报文,只有与某请求报文对应的回应报文,它们的标识与序列号才是相同的。

需要注意的是,请求和回应的ICMP数据包中的数据部分都是相同的。

  • ②路由询问或通告

路由询问的类型字段值为10,通告的类型字段值为9,只有一个代码0

该类型报文主要用于无盘工作站,没有办法保存网关的情况,它就只能靠发送路由询问,来询问网关信息。路由询问报文只用了ICMP头部的前面4个字节,但是路由通告使用了全部的8个字节。、后4个字节有三个字段,分别为“地址数,地址项长度,生存时间”,它们占用的长度是1B,1B,2B这三个字段记载着数据部分包含的路由条目数量,路由条目的长度(即IP地址的长度),以及路由条目在路由器上面的有效生存时间。

在该ICMP报文中,每个路由信息分为路由地址和优先级,各自占用4个字节,优先级越高越有可能成为该主机的默认网关。

  • ③时间戳请求与应答

时间戳的请求的类型字段为13,应答为14,只有一个代码0

它的头部与请求与回应的ICMP报文一致,但是数据部分它使用了12个字节,每4个字节记录一段时间信息,总共有三段,分别是“发送时间戳 ,接收时间戳,回送时间戳”,发送时间戳的信息由时间戳请求者记录,后面两个字段由回送者记录。字段里面记录的是有关当前时间的毫秒数的表示,发送者只要根据回送者发送的时间信息就可以很容易的求出往返时长。

  • ④地址掩码请求和应答

请求的类型字段值为17,应答的类型字段为18,只有一个代码0

它的ICMP头部与请求的ICMP包的头部相同,数据字段存储的是请求的子网掩码

 PS:

  1. Identifier(标识符):Identifier 是一个16位的字段,通常用于标识 ICMP Echo 请求和响应之间的匹配。当发送 ICMP Echo 请求时,Identifier 字段的值会被设置为一个特定的标识符(通常是随机生成的),然后在接收到 ICMP Echo 响应时,接收端会将相同的标识符字段包含在响应中,以便发送端能够识别与响应相关联的请求。

  2. Sequence Number(序列号):Sequence Number 是一个16位的字段,它通常用于按顺序对 ICMP Echo 请求和响应进行排序。每个 ICMP Echo 请求都会包含一个唯一的序列号,然后在接收到 ICMP Echo 响应时,接收端会将相同的序列号字段包含在响应中,以便发送端能够识别响应与哪个请求相对应。

这两个字段的组合(标识符和序列号)允许发送端将 ICMP Echo 请求与响应正确匹配,从而可以测量网络的延迟和连通性。当发送多个 ICMP Echo 请求时,这些字段的组合确保了每个响应都与特定的请求关联,并且可以按顺序排列。

需要注意的是,Identifier 和 Sequence Number 的确切含义可能因 ICMP 报文的类型和用途而有所不同。上述解释是针对 ICMP Echo 请求和响应的常见用法。其他类型的 ICMP 报文可能会使用这些字段以不同的方式

2.4 实际报文举例

1、请求响应

请求包

响应包

2、网络、主机、协议、端口不可达

三、使用go实现icmp请求以及接收响应内容

package main

import (
	"bytes"
	"container/list"
	"encoding/binary"
	"fmt"
	"net"
	"os"
	"time"
)

type ICMP struct {
	Type        uint8
	Code        uint8
	Checksum    uint16
	Identifier  uint16
	SequenceNum uint16
}

func main() {
	var (
		icmp     ICMP
		laddr    = net.IPAddr{IP: net.ParseIP("0.0.0.0")}
		raddr, _ = net.ResolveIPAddr("ip", os.Args[1])
	)

	conn, err := net.DialIP("ip4:icmp", &laddr, raddr)

	if err != nil {
		fmt.Println(err.Error())
		return
	}

	defer conn.Close()

	icmp.Type = 8
	icmp.Checksum = 0
	icmp.Code = 0
	icmp.Identifier = 0
	icmp.SequenceNum = 0

	var buffer bytes.Buffer
	binary.Write(&buffer, binary.BigEndian, icmp)

	data := []byte("This is a custom ICMP payload.")

	// Append the custom data to the buffer
	buffer.Write(data)

	icmp.Checksum = CheckSum(buffer.Bytes())
	buffer.Reset()
	binary.Write(&buffer, binary.BigEndian, icmp)
	buffer.Write(data)

	fmt.Printf("\nPing %s with 0 bytes of data:\n", raddr.String())
	recv := make([]byte, 1024)

	statistic := list.New()
	sended_packets := 0

	for i := 4; i > 0; i-- {
		if _, err := conn.Write(buffer.Bytes()); err != nil {
			fmt.Println(err.Error())
			return
		}
		sended_packets++
		t_start := time.Now()

		conn.SetReadDeadline((time.Now().Add(time.Second * 5)))
		_, err := conn.Read(recv)

		if err != nil {
			fmt.Println("Request timeout")
			continue
		}
		t_end := time.Now()

		dur := t_end.Sub(t_start).Nanoseconds() / 1e6
		fmt.Printf("Reply from %s: time = %dms\n", raddr.String(), dur)
		statistic.PushBack(dur)
	}

	defer func() {
		fmt.Println("")
		var min, max, sum int64
		if statistic.Len() == 0 {
			min, max, sum = 0, 0, 0
		} else {
			min, max, sum = statistic.Front().Value.(int64), statistic.Front().Value.(int64), int64(0)
		}

		for v := statistic.Front(); v != nil; v = v.Next() {

			val := v.Value.(int64)

			switch {
			case val < min:
				min = val
			case val > max:
				max = val
			}

			sum = sum + val
		}
		recved, losted := statistic.Len(), sended_packets-statistic.Len()
		fmt.Printf("Ping statistics for %s:\n  Packets: Sent = %d, Received = %d, Lost = %d (%.1f%% loss),\nRound-Trip Time (ms):\n  Min = %dms, Max = %dms, Avg = %.0fms\n",
			raddr.String(),
			sended_packets, recved, losted, float32(losted)/float32(sended_packets)*100,
			min, max, float32(sum)/float32(recved),
		)
	}()

}

func CheckSum(data []byte) uint16 {
	var (
		sum    uint32
		length int = len(data)
		index  int
	)
	for length > 1 {
		sum += uint32(data[index])<<8 + uint32(data[index+1])
		index += 2
		length -= 2
	}
	if length > 0 {
		sum += uint32(data[index])
	}
	sum += (sum >> 16)

	return uint16(^sum)
}

或者

package main

import (
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"time"
)

var (
	timeout int64
	size    int
	count   int
	typ     uint8 = 8
	code    uint8 = 0
)

type ICMP struct {
	Type       uint8
	Code       uint8
	CheckSum   uint16
	ID         uint16
	SequnceNum uint16
}

func main() {
	getCommandArgs()
	desIP := os.Args[len(os.Args)-1]
	conn, err := net.DialTimeout("ip:icmp", desIP, time.Duration(timeout)*time.Millisecond)
	if err != nil {
		log.Fatal(err)

	}

	defer conn.Close()
	fmt.Printf("正在ping %s [%s] 具有 %d 字节的数据:", desIP, conn.RemoteAddr(), size)
	for i := 0; i < count; i++ {
		t1 := time.Now()
		icmp := *&ICMP{
			Type:       typ,
			Code:       code,
			CheckSum:   0,
			ID:         1,
			SequnceNum: 1,
		}

		data := make([]byte, size)
		var buffer bytes.Buffer
		binary.Write(&buffer, binary.BigEndian, icmp)
		buffer.Write(data)
		data = buffer.Bytes()
		checkSum := checkSum(data)
		data[2] = byte(checkSum >> 8)
		data[3] = byte(checkSum)
		conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))
		n, err := conn.Write(data)
		if err != nil {
			log.Fatalln(err)
			continue
		}

		buf := make([]byte, 65535)
		n, err = conn.Read(buf)
		if err != nil {
			log.Println(err)
			continue
		}
		ts := time.Since(t1).Milliseconds()
		fmt.Printf("来自 %d.%d.%d.%d  的回复: 字节= %d 时间= %dms TTL=%d\n", buf[12], buf[13], buf[14], buf[15], n-28, ts, buf[8])

	}
}

func getCommandArgs() {
	flag.Int64Var(&timeout, "w", 1000, "请求超时时长,单位毫秒")
	flag.IntVar(&size, "l", 32, "请求发送缓冲区大小,单位字节")
	flag.IntVar(&count, "n", 4, "发送请求数")
	flag.Parse()
}

func checkSum(data []byte) uint16 {
	length := len(data)
	index := 0
	var sum uint32 = 0
	for length > 1 {
		sum += uint32(data[index])<<8 + uint32(data[index+1])
		length -= 2
		index += 2

	}

	if length != 0 {
		sum += uint32(data[index])
	}
	hi16 := sum >> 16
	for hi16 != 0 {
		sum = hi16 + uint32(uint16(sum))
		hi16 = sum >> 16
	}

	return uint16(^sum)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1003451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis缓存设计与性能优化

多级缓存架构 缓存设计 缓存穿透 缓存穿透是指查询一个根本不存在的数据&#xff0c; 缓存层和存储层都不会命中&#xff0c; 通常出于容错的考虑&#xff0c; 如果从存储层查不到数据则不写入缓存层。缓存穿透将导致不存在的数据每次请求都要到存储层去查询&#xff0c; 失去…

【解决】80端口被占用,无法启动apache

1.cmd输入netstat -nao,查看是什么进程占用80端口。进程显示为pid4。 2.打开任务管理器&#xff0c;详细信息选项卡&#xff0c;可以看到pid4的进程是system。 右键打开文件所在位置&#xff0c;可以看到是ntoskrnl.exe这个文件占用80端口 3.【解决方法】 &#xff08;1&am…

开源即时通讯(IM)项目OpenIM源码部署流程

由于OpenIM依赖的组件较多&#xff0c;开发者需求不一&#xff0c;导致OpenIM部署一直被人诟病&#xff0c;经过几次迭代优化&#xff0c;包括依赖的组件compose的一键部署&#xff0c;环境变量设置一次&#xff0c;全局生效&#xff0c;以及脚本重构&#xff0c;目前OpenIM部署…

淘宝直通车智能计划怎么开?

很多中小卖家不会开直通车&#xff0c;会遇到整个直通车账户亏钱又对店铺没起到正向作用的问题&#xff0c;开车目的也不明确。对于小卖家&#xff0c;店铺最缺的就是流量&#xff0c;那么如何解决流量问题&#xff1f;今天我教大家一种直通车低价引流的打法。 具体步骤&…

【halcon】halcon字符识别——OCR

前言 OCR&#xff08;Optical Character Recongnition&#xff09;光学字符识别。 halcon 的OCR&#xff0c;提供了几种方式&#xff0c;我们应该如何选择&#xff1f; 自动文本阅读器&#xff08;find_text&#xff09;手动文本阅读器&#xff08;find_text&#xff09;自己…

IDEA安装翻译插件

IDEA安装翻译插件 File->Settings->Plugins 在Marketplace中&#xff0c;找到Translation&#xff0c;点击Install 更换翻译引擎 勾选自动翻译文档 翻译 鼠标右击->点击Translate

【Vue】vue2使用vue-pdf预览pdf文件,预览多页,在线预览方式二,vue页面内预览,无需额外pdfjs包,保姆级教程

系列文章目录 【Vue】vue2预览显示quill富文本内容&#xff0c;vue-quill-editor回显页面&#xff0c;v-html回显富文本内容 【Vue】vue2项目使用swiper轮播图2023年8月21日实战保姆级教程 【Vue】vue2使用pdfjs预览pdf文件&#xff0c;在线预览方式一&#xff0c;pdfjs文件包…

手写RPC框架--11.spi机制

spi机制 spi机制a.spi介绍b.缓存spi到本地c.加载spi并将实例缓存d.统一spi加载的配置 spi机制 a.spi介绍 SPI&#xff08;Service Provider Interface&#xff09;&#xff0c;是JDK内置的一种服务提供发现机制&#xff0c;可以用来启用框架扩展和替换组件&#xff0c;主要是…

Scrum工作模式的角色和活动

​Scrum工作模式是一种敏捷软件开发方法&#xff0c;其核心是团队合作和自我组织&#xff0c;旨在通过短周期的迭代开发&#xff0c;实现快速反馈和持续改进。 Scrum工作模式包括以下角色和活动&#xff1a; 1、产品负责人&#xff08;Product Owner&#xff09;&#xff1a;…

Spring-Cloud GateWay+Vue 跨域方案汇总

文章目录 一、简介背景和概述 二、前端跨域解决方案Axios跨域CORS跨域 三、后端跨域解决方案反向代理服务器 四、Spring Cloud中的跨域解决方案Gateway网关的跨域配置 五、基于Vue和Spring Cloud的跨域整合实践**这两种配置只需配置一种即可生效&#xff08;前端or后端&#xf…

Unity和C#游戏编程入门:创建迷宫小球游戏示例

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 当涉及到Unity和C#游戏编…

电脑连接电视怎么做?学会这4个方法,轻松连接!

“我的电脑屏幕太小了&#xff0c;想将它连接到电视上看电影。有什么方法可以将电脑与电视进行连接吗&#xff1f;请教教我&#xff01;” 在今天的数字时代&#xff0c;将电脑连接到电视已经成为了常见的需求。无论是观看电影、演示文稿还是玩游戏&#xff0c;电脑连接电视可以…

蓝桥杯打卡Day7

文章目录 阶乘的末尾0整除问题 一、阶乘的末尾0IO链接 本题思路&#xff1a;由于本题需要求阶乘的末尾0&#xff0c;由于我们知道2*510可以得到一个0&#xff0c;那么我们就可以找出2的数和5的数&#xff0c;但是由于是阶乘&#xff0c;所以5的数量肯定是小于2的数量&#xf…

leetcode:67. 二进制求和

题目&#xff1a; 函数原型&#xff1a; char * addBinary(char * a, char * b) 思路&#xff1a; 二进制相加&#xff0c;首先我们考虑先将字符串逆序。由此要写一个逆序函数reserve。字符串逆序后&#xff0c;从前往后相加&#xff0c;以较长的字符串的长度为标准长度n&#…

前端vue按钮控制切换按钮是否禁用和颜色和显示隐藏,利用v-if和v-else

效果 未输入input前图片 输入input后图片 html <input type"number" placeholder"请输入分润数量" placeholder-class"shareprofit_placeholder_num" v-model"money"> <!-- 金钱 --> {{money}} <!-- 可提现余额 --&g…

518电脑端抽奖软件,可用作婚庆大屏幕滚动抽奖

518抽奖软件简介 518抽奖软件&#xff0c;518我要发&#xff0c;超好用的年会抽奖软件&#xff0c;简约设计风格。 包含文字号码抽奖、照片抽奖两种模式&#xff0c;支持姓名抽奖、号码抽奖、数字抽奖、照片抽奖。(www.518cj.net) 婚礼抽奖活动意义 抽奖类似于买彩票&#x…

大数据技术之Hive:先导篇(一)

目录 一、什么是Hive 二、思考如何设计出Hive功能 2.1 提问 2.2 案例分析 2.3 小结 三、掌握Hive的基础架构 3.1 Hive组件 - 元数据存储 3.2 Hive组件 - Driver驱动程序 3.3 Hive组件 - 用户接口 一、什么是Hive 什么是分布式SQL计算 我们知道&#xff0c;在进行数据统…

cf 交互题

今天cf遇到了交互题&#xff0c;这个交互题的算法很很很简单&#xff0c;但是在交互上卡了&#xff0c;导致交上的代码都不算罚时。&#xff08;更伤心了。 所以&#xff0c;现在写一下交互题的做法&#xff0c;印象深刻嘛。 交互题&#xff0c;就是跟机器进行交互。你代码运…

开始撸 Android 源码

启动找工作模式&#xff0c;发现无比困难。搁在往日&#xff0c;大龄程序员找工作都是一件困难的事情&#xff0c;加上今年形势很差&#xff0c;更是难上加难。关键是我这十几年来主攻的浏览器内核方向&#xff0c;需求量更是几乎为零。在 BOSS 直聘上以 Chromium 为关键词&…

DeepSpeed

DeepSpeed概念 DeepSpeed中用到的技术包括以下几个等级&#xff1a; ZeRO-1&#xff1a;只对optimizer进行切片后分布式保存 ZeRO-2&#xff1a;对optimizer和grad进行切片后分布式保存 ZeRO-3&#xff1a;对optimizer、grad和模型参数进行切片后分布式保存 offload&#xff1…