茗宸博客网

  • 首页
  • 数据通信
    • 华为
    • 思科
    • 浪潮科技
    • 锐捷
  • 硬件瞎折腾
    • 电脑折腾
    • 软路由
    • 服务器
    • 私有NAS
    • FPV无人机瞎折腾
  • 网络安全
    • CTF经验
    • 实战环境
    • 渗透实战
    • 蓝队加固
    • 近期赛事
    • 漏洞分享
  • 网络技术
    • 网络基础
    • 网络技术精品
    • linux基础
    • 计算机基础
  • 编程学习
    • python
  • 运行维护
  • 服务器搭建
  • 资源分享
  • 随手笔记
    • 随手笔记之ensp
    • 随手笔记之mysql
  • 日常
    • 拍摄
茗宸博客
随手笔记
  1. 首页
  2. 网络技术
  3. 网络基础
  4. 正文

ICMP协议一文双篇详解

2024年4月23日 519点热度 0人点赞 0条评论
内容目录

ICMP协议是IP的一个组成部分,负责传递 「控制信息」。

ICMP的功能是检错而不是纠错;

它将出错的报文返回给发送方的设备,发送方根据ICMP报文确定「错误类型」,从而更好的重发错误的数据包。

我们用来测试网络连通性的 ping 命令,就是ICMP的工作过程。

二、数据报格式
ICMP是IP协议的一部分,因此,ICMP协议的报文包含在IP数据报的数据部分:

1)类型(Type):4位,标明ICMP报文的作用及格式。

2)代码(Code):4位,标明报文的类型。

3)校验和:8位,检验报文是否有误。

三、报文类型
ICMP协议主要通过 Type 和 Code 的组合,来标明报文的类型,常见的有三种:

1)请求响应

发送方发送一个 Type =8 的报文,途中没有异常,接收方就会返回一个 Type=0 的报文;

比如下面这一对请求和响应,注意看我圈中的地方:

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

这几种情况的报文类型都是一样的,接收方返回一个 Type = 3 , Code=3 的报文,意思就是端口不可达(访问了一个不存在的端口),比如下面这个响应,重点看我圈中的地方:

常见的不可到达类型还有网络不可到达(Code=0)、主机不可到达(Code=1)、协议不可到达(Code=2)。

完整的ICMP报文类型如下:

TYPE CODE Description
0 0 Echo Reply——回显应答(Ping应答)
3 0 Network Unreachable——网络不可达
3 1 Host Unreachable——主机不可达
3 2 Protocol Unreachable——协议不可达
3 3 Port Unreachable——端口不可达
3 4 Fragmentation needed but no frag. bit set——需要进行分片但设置不分片比特
3 5 Source routing failed——源站选路失败
3 6 Destination network unknown——目的网络未知
3 7 Destination host unknown——目的主机未知
3 8 Source host isolated (obsolete)——源主机被隔离(作废不用)
3 9 Destination network administratively prohibited——目的网络被强制禁止
3 10 Destination host administratively prohibited——目的主机被强制禁止
3 11 Network unreachable for TOS——由于服务类型TOS,网络不可达
3 12 Host unreachable for TOS——由于服务类型TOS,主机不可达
3 13 Communication administratively prohibited by filtering——由于过滤,通信被强制禁止
3 14 Host precedence violation——主机越权
3 15 Precedence cutoff in effect——优先中止生效
4 0 Source quench——源端被关闭(基本流控制)
5 0 Redirect for network——对网络重定向
5 1 Redirect for host——对主机重定向
5 2 Redirect for TOS and network——对服务类型和网络重定向
5 3 Redirect for TOS and host——对服务类型和主机重定向
8 0 Echo request——回显请求(Ping请求)
9 0 Router advertisement——路由器通告
10 0 Route solicitation——路由器请求
11 0 TTL equals 0 during transit——传输期间生存时间为0
11 1 TTL equals 0 during reassembly——在数据报组装期间生存时间为0
12 0 IP header bad (catchall error)——坏的IP首部(包括各种差错)
12 1 Required options missing——缺少必需的选项
13 0 Timestamp request (obsolete)——时间戳请求(作废不用)
14 Timestamp reply (obsolete)——时间戳应答(作废不用)
15 0 Information request (obsolete)——信息请求(作废不用)
16 0 Information reply (obsolete)——信息应答(作废不用)
17 0 Address mask request——地址掩码请求
18 0 Address mask reply——地址掩码应答

1.ICMP协议简介

ICMP(Internet Control Message Protocol)是一种网络协议,它用于在IP网络中传递控制信息和错误消息。它通常与IP协议一起使用,IP协议负责发送和路由数据包,而ICMP协议负责检查网络是否可达、路由是否正确、主机是否可达等网络状态的反馈信息。

ICMP协议的主要功能如下:

发现网络错误:当一个数据包在传输过程中出现错误时,ICMP协议通过向发送方发送错误通知来发现网络错误。

检查网络是否可达:通过发送ICMP ECHO请求并接收ICMP ECHO回复消息,可以确定目标主机是否可达。

发现主机错误:当一个主机无法正常工作时,ICMP协议通过向发送方发送错误通知来发现主机错误。

发送路由信息:ICMP协议可以向其他主机发送路由信息,以帮助它们在网络中找到合适的路由。

2.ICMP报文格式

2.1 ICMP报文以太网数据帧格式

img

图 1 ICMP以太网数据帧

ICMP报文属于IP子协议,协议号为1。

2.2 ICMP首部格式

img

图 2 ICMP首部格式

其中各字段的含义如下:

类型(Type):指定 ICMP 报文的类型,占 1 个字节。常见类型有:回显应答(Echo Reply:0)、回显请求(Echo Request:8)等。

代码(Code):指定 ICMP 报文的代码,占 1 个字节。用于进一步描述 ICMP 报文,与 Type 字段组合使用。

校验和(Checksum):校验和,用于检查 ICMP 报文是否有损坏,占 2 个字节。

由类型决定的4字节:根据类型不一样,4字节表达的意思不一样。

数据(Data):数据,可变长度。可以是任意数据,长度由具体的 ICMP 报文类型和代码决定。

2.3 ICMP报文类型列表

img

表 1 ICMP报文类型表

常见的ICMP报文类型:

Echo Reply(回显应答):用于回复Echo Request(回显请求)报文,通常用于测试网络连接是否正常。

Destination Unreachable(目的地不可达):用于指示主机或路由器无法到达目的地或某个网络服务不可用。

Source Quench(源站抑制):当接收方无法处理所有传入的数据报时,源站抑制报文会发送到发送方,以通知其减慢数据传输速度。

Redirect(重定向):用于通知发送方,其正在使用的路由不再是最佳路由,建议使用另一条路由。

Echo Request(回显请求):用于测试测试网络连接是否正常。

Time Exceeded(时间超时):用于指示一个数据包在传输过程中被丢弃,原因是数据包在经过路由器时超过了其生存时间。

Parameter Problem(参数问题):用于指示数据包头部中存在错误的参数或选项,导致数据包无法被识别或处理。

Timestamp Request/Reply(时间戳请求/应答):用于向另一个主机请求当前时间戳,并将其返回给请求方。

Information Request/Reply(信息请求/应答):用于向另一个主机请求特定信息,并将其返回给请求方。

Address Mask Request/Reply(地址掩码请求/应答):用于请求另一个主机的网络掩码,并将其返回给请求方。

3.ICMP故障排查工具

3.1 ping工具

Ping命令是一种常用的网络诊断工具,用于测试网络连接性和响应时间。它发送一个ICMP数据包(Internet控制消息协议),并在目标主机收到数据包后返回一个响应,以确定目标主机是否可达,以及响应时间。

Ping命令的语法如下:

ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS] [-r count] [-s count] [[-j host-list] | [-k host-list]] [-w timeout] destination-list

其中,常用的参数包括:

-t:持续发送数据包,直到手动停止

-a:解析IP地址为主机名

-n count:指定要发送的数据包数

-l size:指定要发送的数据包大小

-f:在数据包中设置“不分片”标志

-i TTL:设置数据包的存活时间

-w timeout:指定等待响应的最大时间

3.2 traceroute工具

traceroute命令用于检测网络连接的路径和延迟时间,以及确定网络上的故障点。它通过向目标主机发送一系列的数据包,并记录每个包从源主机到目标主机的路由路径上所经过的中间节点(路由器)。

traceroute命令会输出每个中间节点的IP地址、主机名(如果可用)、延迟时间和TTL值。TTL(Time to Live)值是每个数据包的生命周期,当数据包经过一个路由器时,TTL值就会减少1,如果TTL值降到0,则该数据包就会被丢弃并返回一个ICMP超时消息,这样我们就可以知道数据包到达了哪个中间节点。

例如,我们可以使用以下命令来traceroute到百度的IP地址(202.108.22.5):

traceroute 202.108.22.5

输出结果可能类似于以下内容:

traceroute to 202.108.22.5 (202.108.22.5), 30 hops max, 60 byte packets

 1  router (192.168.1.1)  2.025 ms  1.326 ms  1.115 ms

 2  100.64.0.1 (100.64.0.1)  4.505 ms  4.591 ms  4.659 ms

 3  218.240.40.121 (218.240.40.121)  7.131 ms  7.217 ms  7.291 ms

 4  218.240.40.146 (218.240.40.146)  25.398 ms  25.397 ms  25.393 ms

 5  202.96.12.26 (202.96.12.26)  25.373 ms 202.96.12.34 (202.96.12.34)  25.357 ms  25.344 ms

 6  202.96.12.110 (202.96.12.110)  25.314 ms  25.301 ms  25.289 ms

 7  202.97.94.118 (202.97.94.118)  25.492 ms 202.97.94.114 (202.97.94.114)  25.478 ms  25.463 ms

 8  202.97.58.237 (202.97.58.237)  25.434 ms  25.409 ms  25.394 ms

 9  202.97.58.233 (202.97.58.233)  25.372 ms  25.363 ms  25.349 ms

10  * * *

11  * * *

12  202.108.22.5 (202.108.22.5)  25.633 ms  25.618 ms  25.603 ms

从输出结果中可以看到,traceroute命令首先会输出目标主机的IP地址和最大跳数(30),然后每一行显示一个中间节点的信息。例如,第一行显示第一个中间节点的IP地址(192.168.1.1)、主机名(如果可用)、三次ping的延迟时间。最后一行显示目标主机的IP地址和延迟时间。在第10和11行中,我们看到了两个星号,这表示该数据包在到达该中间节点时已经超时并被丢弃了,因此我们无法确定该节点的IP地址。

4.常见ICMP报文

4.1 ICMP请求和应答

执行ping命令,ping一个可以通信的IP地址,如下命令:

ping 223.5.5.5

ping通后会收到对端发来的ICMP应答报文。

ICMP回显请求报文

img

图 3 ICMP回显请求报文

ICMP回显响应报文

img

图 4 ICMP回显响应报文

4.2 ICMP差错报告报文

TTL过期差错报告报文img

图 5 TTL过期原理

ping命令通过-i指定TTL值,如下命令:

ping 223.5.5.5 -i 5 -t

命令指定TTL值为5,也就是通过5个路由器后,TTL会变成0,数据包丢弃,路由器发送ICMP TTL过期报文给源主机。

ICMP TTL过期报文

img

图 6 TTL过期差错报告报文

4.3 目标主机不可达

当我们使用ping命令向一个主机发送ICMP(Internet控制消息协议)数据包时,如果目标主机无法到达,我们将会得到“目标主机不可达”的错误提示。

这个错误通常是由以下几种原因引起的:

  1. 目标主机已经关闭或没有连接到网络。这种情况下,我们无法通过网络与目标主机通信。
  2. 网络连接故障。如果网络连接故障,例如连接断开或路由器故障,那么我们无法到达目标主机。
  3. 防火墙阻止了ping请求。如果目标主机上的防火墙设置了规则以阻止ping请求,那么我们无法与目标主机进行通信。
  4. ICMP协议被禁用。有些主机可能会禁用ICMP协议,这意味着它们不会回应ping请求。

ICMP目标主机不可达报文

ICMP type:3,code:1

5.ICMP校验和计算

ICMP校验和计算的校验数据为整个ICMP数据包。

5.1 ICMP校验和计算

a.校验数据以16bit为单位进行累加求和,校验数据需为偶数字节,奇数字节末尾填充0变为偶数字节。

b.如果累加和超过16bit,产生了进位,需将高16bit和低16bit累加求和。

c.循环步骤2,直至未产生进位为止。

d.累加和取反得到校验和。

5.2 ICMP校验和验证

a.校验数据16bit为单位进行累加求和,校验数据需为偶数字节,奇数字节末尾填充0变为偶数字节。

b.如果累加和超过16bit,产生了进位,需将高16bit和低16bit累加求和。

c.循环步骤2,直至未产生进位为止。

d.累加和和校验和相加得到0xffff,校验成功,否则失败。

6.ICMP编程示例

6.1 发送回显请求

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <stdint.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netinet/ip.h>

#include <netinet/ip_icmp.h>

#include <arpa/inet.h>

#define PACKET_SIZE 4096

#define ICMP_PACKET_SIZE 28

uint16_t checksum(uint16_t *buf, int len)

{

    unsigned long sum = 0;

    while (len > 1) {

        sum += *buf++;

        len -= 2;

    }

    if (len == 1) {

        sum += *(unsigned char *)buf;

    }

    sum = (sum >> 16) + (sum & 0xffff);

    sum += (sum >> 16);

    return ~sum;

}

int main(int argc, char *argv[])

{

    if (argc != 2) {

        printf("Usage: %s <destination_ip>\n", argv[0]);

        return -1;

    }

    char buf[PACKET_SIZE] = {0};

    memset(buf, 0, sizeof(buf));

    int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

    if (sockfd < 0) {

        perror("socket error");

        return -1;

    }

    struct sockaddr_in dest_addr;

    memset(&dest_addr, 0, sizeof(dest_addr));

    dest_addr.sin_family = AF_INET;

    dest_addr.sin_addr.s_addr = inet_addr(argv[1]);

    uint16_t seq = 0;

    while(1) {

        memset(buf, 0, PACKET_SIZE);

        struct icmp *icmp_packet = (struct icmp *)buf;

        icmp_packet->icmp_type = ICMP_ECHO;

        icmp_packet->icmp_code = 0;

        icmp_packet->icmp_id = 0;

        icmp_packet->icmp_seq = seq++;

        memset(icmp_packet->icmp_data, 0, ICMP_PACKET_SIZE);

        icmp_packet->icmp_cksum = 0;

        icmp_packet->icmp_cksum = checksum((uint16_t *)icmp_packet, ICMP_PACKET_SIZE);

        printf("icmp_packet size:%lu\n", sizeof(struct icmp));

        int sent_bytes = sendto(sockfd, buf, sizeof(struct icmp), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));

        if (sent_bytes <= 0) {

            perror("sendto error");

            break;

        }

        printf("sent icmp request:%d bytes to:%s\n", sent_bytes, argv[1]);

        int recv_bytes = recv(sockfd, buf, PACKET_SIZE, 0);

        if (recv_bytes <= 0) {

            perror("recv");

            break;

        }

        struct iphdr *ip_packet = (struct iphdr *)buf;

        struct icmp *icmp_reply = (struct icmp *)(buf + (ip_packet->ihl << 2));

        printf("recv icmp reply:%d from:%s\n", recv_bytes, inet_ntoa(dest_addr.sin_addr));

        printf("icmp type:%d,code:%d\n", icmp_reply->icmp_type, icmp_reply->icmp_code);

        sleep(1);

    }

    close(sockfd);

    return 0;

}

6.2 发送回显应答

#include <stdio.h>

#include <string.h>

#include <stdint.h>

#include <stdbool.h>

#include <unistd.h>

#include <errno.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/ip.h>

#include <netinet/ip_icmp.h>

#include <linux/in.h>

#include <arpa/inet.h>

#define IP_HDRLEN (20)

#define PACKET_SIZE (4096)

uint16_t checksum(uint16_t *buf, int len)

{

    unsigned long sum = 0;

    while (len > 1) {

        sum += *buf++;

        len -= 2;

    }

    if (len == 1) {

        sum += *(unsigned char *)buf;

    }

    sum = (sum >> 16) + (sum & 0xffff);

    sum += (sum >> 16);

    return ~sum;

}

uint16_t checksum_nofold(uint16_t *buf, int len)

{

    unsigned long sum = 0;

    while (len > 1) {

        sum += *buf++;

        len -= 2;

    }

    if (len == 1) {

        sum += *(unsigned char *)buf;

    }

    sum = (sum >> 16) + (sum & 0xffff);

    sum += (sum >> 16);

    return sum;

}

bool parse_pack(char *buf, uint32_t len) {

    struct icmp *icmp_packet = (struct icmp *)buf;

    uint16_t csum = checksum_nofold((uint16_t *)buf, len);

    printf("icmp csum:0x%04x\n", csum);

    return csum == 0xffff;

}

int main(int argc , char *argv[]) {

    int sockfd;

    int ret;

    char send_buf[PACKET_SIZE] = {0};

    char recv_buf[PACKET_SIZE] = {0};

    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

    if (sockfd == -1) {

        perror("socket error");

        return -1;

    }

    while(1) {

        struct sockaddr_in peer;

        socklen_t peerlen = sizeof(peer);

        memset(recv_buf, 0, PACKET_SIZE);

        ret = recvfrom(sockfd, recv_buf, PACKET_SIZE, 0, (struct sockaddr *)&peer, &peerlen);

        if (ret <= 0) {

            printf("ret:%d, errno:%d(%s)\n", ret, errno, strerror(errno));

        } else {

            printf("recv len:%d, peer src:port->%s:%d\n", ret, inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));

            bool bret = parse_pack(recv_buf, ret);

            if (bret) {

                struct icmp *recv_icmp = (struct icmp *)(recv_buf + sizeof(struct iphdr));

                memset(send_buf, 0, PACKET_SIZE);

                struct icmp *icmp_packet = (struct icmp *)send_buf;

                icmp_packet->icmp_type = ICMP_ECHOREPLY;

                icmp_packet->icmp_code = 0;

                icmp_packet->icmp_id = 0;

                icmp_packet->icmp_seq = recv_icmp->icmp_seq;

                memset(icmp_packet->icmp_data, 0, sizeof(struct icmp));

                icmp_packet->icmp_cksum = 0;

                icmp_packet->icmp_cksum = checksum((uint16_t *)icmp_packet, sizeof(struct icmp));

                int sent_bytes = sendto(sockfd, send_buf, sizeof(struct icmp), 0, (struct sockaddr *)&peer, sizeof(peer));

                if (sent_bytes <= 0) {

                    perror("sendto error");

                    break;

                }

            }

        }

    }

    close(sockfd);

    return 0;

}
标签: 暂无
最后更新:2024年9月3日

站长

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论

站长

这个人很懒,什么都没留下

最新 热点 随机
最新 热点 随机
Linux systemctl 命令 linux的service IPTABLES一文通 网安路线图 DOS相关常用命令了一篇了解大全 MYSQL数据库学习记录
IPTABLES一文通linux的serviceLinux systemctl 命令搬运 linux最常用的20个命令搬运 浅谈社工搬书Linux操作系统应用与安全项目化实战教程
反向代理进行内网穿透 SQL注入常用方式 关于ensp待实践 搬书Linux操作系统应用与安全项目化实战教程 树莓派pi zero 2w安装P4wnP1-aloa 一文双篇(快速搞懂TCP/UDP协议区别)
文章目录
  • 1.ICMP协议简介
  • 2.ICMP报文格式
    • 2.1 ICMP报文以太网数据帧格式
    • 2.2 ICMP首部格式
    • 2.3 ICMP报文类型列表
  • 3.ICMP故障排查工具
    • 3.1 ping工具
    • 3.2 traceroute工具
  • 4.常见ICMP报文
    • 4.1 ICMP请求和应答
    • 4.2 ICMP差错报告报文
    • 4.3 目标主机不可达
  • 5.ICMP校验和计算
    • 5.1 ICMP校验和计算
    • 5.2 ICMP校验和验证
  • 6.ICMP编程示例
    • 6.1 发送回显请求
    • 6.2 发送回显应答

COPYRIGHT © 2023 茗宸bk. ALL RIGHTS RESERVED.

站长微信:printJ7

鲁ICP备2024114188号

鲁公网安备37130202372760号