icmp报文及用go实现

2023-09-12 11:02:44

目录

一、概述

二、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"
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"time"
)

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

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

func main() {
	//获取命令输入内容
	getCommandArgs()
	if len(os.Args) < 2 {
		log.Fatal("Usage: programname target_ip")
	}
	raddr, _ := net.ResolveIPAddr("ip", os.Args[len(os.Args)-1])
	desIP := raddr.String()
	//建立连接,以及最后释放连接
	conn, err := net.DialTimeout("ip:icmp", desIP, time.Duration(timeout)*time.Millisecond)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	//发送报文展示格式
	// statistic := list.New()
	icmp := *&ICMP{
		Type:        typ,
		Code:        code,
		Checksum:    0,
		Identifier:  0,
		SequenceNum: 0,
	}
	icmpdata := []byte(data)
	recv := make([]byte, 1024)
	statistic := list.New()
	sended_packets := 0
	var buffer bytes.Buffer
	binary.Write(&buffer, binary.BigEndian, icmp)
	buffer.Write(icmpdata)
	icmp.Checksum = CheckSum(buffer.Bytes())
	buffer.Reset()
	binary.Write(&buffer, binary.BigEndian, icmp)
	buffer.Write(icmpdata)

	fmt.Printf("正在Ping %s [%s] 具有 %d 字节的数据:\n", raddr, conn.RemoteAddr(), len(data))
	for i := 0; i < count; i++ {
		t_start := time.Now()

		_, err := conn.Write(buffer.Bytes())
		if err != nil {
			log.Fatalln(err)
			continue
		}
		sended_packets++
		conn.SetReadDeadline((time.Now().Add(time.Second * time.Duration(timeout))))
		_, err = conn.Read(recv)
		if err != nil {
			fmt.Println("请求超时")
			continue
		}
		t_end := time.Now()

		dur := t_end.Sub(t_start).Nanoseconds() / 1e6 //计算请求时间
		// fmt.Printf("来自 %s 的回复: 字节 = %s 时间 = %dms\n", desIP, len(data), dur)
		fmt.Printf("来自 %s 的回复: 字节 = %d 时间 = %dms TTL = %d\n", conn.RemoteAddr(), len(data), dur, recv[8])
		statistic.PushBack(dur)
		defer buffer.Reset()
	}

	//最后统计请求包
	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(" %s 的Ping统计信息\n  数据包: 已发送 = %d, 已接收 = %d, 丢失 = %d (%.1f%% 丢失),\n往返行程的估计时间(以毫秒为单位):\n  最短 = %dms, 最长 = %dms, 平均 = %.0fms\n",
			desIP,
			sended_packets, recved, losted, float32(losted)/float32(sended_packets)*100,
			min, max, float32(sum)/float32(recved),
		)
	}()

}

func getCommandArgs() {
	flag.Int64Var(&timeout, "w", 5, "请求超时时长, 单位秒")
	flag.StringVar(&data, "d", "this is icmp databody", "请求的数据内容")
	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代理解决频繁封IP问题

在网络爬虫和数据采集等应用中,频繁遇到目标网站封锁或限制IP的情况是非常常见的。为了解决这个问题,使用HTTP代理是一种有效的方法。本文将与您分享一些实战经验,帮助您通过HTTP代理解决频繁封IP问题,确保您的数据采集工作顺利进行。一、了解频繁封IP问题频繁封IP问题是指目标网站采取措施检测并封锁过多请求来自同一IP地

Spring 篇

1、什么是Spring?Spring是一个轻量级的IOC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。主要由以下几个模块组成:SpringCore:核心类库,提

RFID技术在工业智能制造生产线中的应用

随着自动化和信息化的快速发展,工业智能制造成为制造业的重要趋势,在制造商的生产线上,准确获取和管理工艺流程等各个环节的信息至关重要,作为物联网感知层的核心组成部分,RFID技术以其非接触式、无感知的特点,实现了智能化的识别和数据采集,通过RFID电子标签实现了设备的互联,在复杂的工业制造环境中,结合RFID电子标签、R

xterm使用

xterm使用前言1.xterm介绍2.xterm使用2.1xterm简单示例2.2xterm监听输入并在终端中实时显示方式1:onKey监听方式2:onData监听onData和onKey什么区别2.3xterm与vue整合2.3xterm+vue+websocket附录配置说明前言vue与xterm整合记录1.xt

让开源数据开发平台助力提质增效!

用低代码技术平台,可以提高办公协作效率,可以让数据资源变得更有意义和价值,也可以为企业做出更理想的发展决策。作为开源数据开发平台服务商,流辰信息谨守研发初心,一直在低代码技术平台领域努力耕耘,为行业的进步和数字化发展贡献力量。由于社会的发展和进步,传统的表单制作工具已经无法为企业创造高效益,如果想要获得发展和壮大,需要

初探微前端

微前端一、微前端的背景和概述1.1概念1.2特点1.3背景二、微前端的实现方式2.1服务端集成2.2运行时集成三、现有的解决方案3.1single-spa3.2qiankun3.3micro-app四、总结🚀🚀🚀随着互联网技术的不断发展,前端应用规模和复杂性也在不断增加。传统的单体前端应用面临着很多挑战,比如应用

webpack:系统的了解webpack一些核心概念

文章目录webpack如何处理应用程序?何为webpack模块chunk?入口(entry)输出(output)loader开发loader插件(plugin)简介流程插件开发:Tapable类监听(watching)compiler钩子compilation钩子compiler和compilation创建自定义插件l

有关在 Windows 上使用 Python 的常见问题解答

🎬岸边的风:个人主页🔥个人专栏:《VUE》《javaScript》⛺️生活的理想,就是为了理想的生活!目录使用pipinstall解决包安装问题使用WSL解决pip安装问题什么是py.exe?为什么运行python.exe会打开MicrosoftStore?当我复制粘贴文件路径时,为什么在Python中不起作用?什

前端js面试题 (一)

文章目录1、请你阐述一下原型与原型链。2、开发中的闭包问题。3、call、apply、bind的用途与区别4、手写一个promise5、箭头函数与普通函数6、递归与尾递归。7、await返回值是什么。8、promise.then,setInterval,Promise.resolve,执行顺序9、letconstvar

Python打包教程 PyInstaller和cx_Freeze

当我们开发Python应用程序时,通常会将代码保存在.py文件中,然后通过Python解释器运行它。这对于开发和测试是非常方便的,但在将应用程序分享给其他人或在不同环境中部署时,可能会带来一些问题。为了解决这些问题,我们可以使用打包工具将Python应用程序转换为可执行文件,这样它就可以在不需要安装Python解释器的

热文推荐