茗宸博客网

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

ICMP协议一文双篇详解

2024年4月23日 523点热度 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日

站长

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

点赞
< 上一篇
下一篇 >

文章评论

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

站长

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

最新 热点 随机
最新 热点 随机
Mbps、Kbps、bps 与 MB、KB、B 区别/联系/换算 Linux systemctl 命令 linux的service IPTABLES一文通 网安路线图 DOS相关常用命令了一篇了解大全
IPTABLES一文通linux的serviceLinux systemctl 命令Mbps、Kbps、bps 与 MB、KB、B 区别/联系/换算MYSQL数据库学习记录DOS相关常用命令了一篇了解大全
关于ensp待实践 SSH端口转发实现穿透内网 内网隐藏通信隧道技术——FRP隧道 RAID技术(磁盘阵列) SQL注入常用方式 E5邪教 850元 畅玩腾讯全家桶 3A性价比配置!
文章目录
  • 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号