IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 《lwip学习8》-- 网际控制报文协议ICMP -> 正文阅读

[网络协议]《lwip学习8》-- 网际控制报文协议ICMP

IP 协议是一种不可靠、无连接的协议,只在各个主机间交付数据,但是对于数据的到达与否, IP 协议并不关心,为了提高数据交付的准确性, ICMP 就随之出现,在交付数据的时候,如果由于网络状况、链路不通等情况数据报无法到达目标主机,ICMP 就会返回一个差错报文,让源主机知道数据没能正常到达目标主机,接着进行重发或者放弃发送都可以。
ICMP 通常被认为是 IP 层协议的一部分,但从体系结构上讲它是位于 IP 之上的,因为ICMP 报文是承载在 IP 数据报中的。这就是说, ICMP 报文是作为 IP 数据报数据区域的(有一些书籍也称之为有效载荷) ,就像 TCP 与 UDP 报文段作为 IP 数据报数据区域那样。类似地,当一台主机收到一个指明上层协议为 ICMP 的 IP 数据报时,它将分解出该数据报的内容给 ICMP,就像分解出一个数据报的内容给 TCP 或 UDP 一样,但与 TCP 或 UDP 协议又有所不同, ICMP 出现的目的不是为上层应用程序提供服务,只是在 IP 层传递差错的报文,依赖于 IP 协议进行传输。

ICMP报文结构

ICMP 报文是使用 IP 数据报来封装发送的, 所以 ICMP 报文也是没有额外的可靠性与优先级,它一样会被别的路由器丢弃,与此同时, ICMP 报文封装在 IP 数据报中, IP 数据报封装在以太网帧中,因此 ICMP 报文是经过了两次的封装。
在这里插入图片描述
ICMP 报文与 IP 数据报一样,都是由首部与数据区域组成, ICMP 首部是 8 个字节,对于不同类型的 ICMP 报文, ICMP 报文首部的格式也会有点差异,但是首部的前 4 个字节都是通用的:
第一个字节(占据 8bit 空间)是类型(Type) 字段,表示产生这种类型 ICMP 报文的原因。
第二个字节是代码(Code) 字段,它进一步描述了产生这种类型 ICMP 报文的具体原因。因为每种类型的报文都可能有多个,比如目的不可达报文,产生这种原因可能有主机不可达、协议不可达、端口不可达等多个原因。
接下来的校验和字段(占据 16bit) 用于记录包括 ICMP 报文数据部分在内的整个ICMP 数据报的校验和,以检验报文在传输过程中是否出现了差错, 其计算方法与IP 数据包首部中的校验和计算方法是一样的。
剩下的 4 个字节部分在讲解报文类型的时候详细讲解,因为不同类型的报文都有不一样的定义,并且数据部分的长度也存在差异, ICMP 报文格式
在这里插入图片描述

ICMP报文类型

ICMP 报文有两大类型, 可以划分为差错报告报文和查询报文, 差错报告报文主要是用来向 IP 数据报源主机返回一个差错报告信息, 而这个差错报告信息产生的原因是路由器或者主机不能对当前数据报进行正常的处理,简单来说就是源主机发送的数据报没法到目标主机中,或者到达了目标主机而无法递交给上层协议。
查询报文是用于一台主机向另一台主机发起一个请求,如果目标主机收到这个查询的请求后,就会按照查询报文的格式向源主机做出应答,比如我们使用的 ping 命令,它的本质就是一个 ICMP 查询报文。
在这里插入图片描述

ICMP差错报文

目的不可达(类型 3)

LwIP 实现的只有前 6 种,代码取值如下
在这里插入图片描述
同时 ICMP 目的不可达报文首部剩下的 4 字节全部未用,而 ICMP 报文数据区域则装载 IP 数据报首部及 IP 数据报的数据区域前 8 字节,为什么需要装载 IP 数据报的数据区域中前 8 个字节的数据呢?因为 IP 数据报的数据区域前 8 个字节刚好覆盖了传输层协议中的端口号字段,而 IP 数据报首部就拥有目标 IP 地址与源 IP 地址,当源主机收到这样子的ICMP 报文后,它能根据 ICMP 报文的数据区域判断出是哪个数据包出现问题,并且 IP 层能够根据端口号将报文传递给对应的上层协议处理, ICMP 目的不可达报文的格式具体见图
在这里插入图片描述

ICMP查询报文

ICMP 回显请求报文和回显应答报文是 LwIP 中唯一实现的报文,即 ping

处理ICMP报文

LwIP 协议是轻量级 TCP/IP 协议栈,所以对 ICMP 报文中很多类型的报文都不做处理,LwIP 会将这些不处理的报文丢掉,但是对 ICMP 回显请求报文就做出处理,所以这也是为什么我们能 ping 通开发板的原因。当 IP 层收到一个 ICMP 报文的时候, 会调用 icmp_input()函数将报文传递到 ICMP 协议去处理,关于这个函数的处理也是比较简单明了的,只处理 ICMP 请求报文,然后返回一个 ICMP 应答报文,具体看注释即可, icmp_input()函数具体见代码清单

void
icmp_input(struct pbuf *p, struct netif *inp)
{
  u8_t type;
#ifdef LWIP_DEBUG
  u8_t code;
#endif /* LWIP_DEBUG */
  struct icmp_echo_hdr *iecho;
  const struct ip_hdr *iphdr_in;
  u16_t hlen;
  const ip4_addr_t *src;

  ICMP_STATS_INC(icmp.recv);
  MIB2_STATS_INC(mib2.icmpinmsgs);

  iphdr_in = ip4_current_header();
  hlen = IPH_HL_BYTES(iphdr_in);
  if (hlen < IP_HLEN) {
    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: short IP header (%"S16_F" bytes) received\n", hlen));
    goto lenerr;
  }
  if (p->len < sizeof(u16_t) * 2) {
    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: short ICMP (%"U16_F" bytes) received\n", p->tot_len));
    goto lenerr;
  }

  type = *((u8_t *)p->payload);
#ifdef LWIP_DEBUG
  code = *(((u8_t *)p->payload) + 1);
  /* if debug is enabled but debug statement below is somehow disabled: */
  LWIP_UNUSED_ARG(code);
#endif /* LWIP_DEBUG */
  switch (type) {
    case ICMP_ER:
      /* This is OK, echo reply might have been parsed by a raw PCB
         (as obviously, an echo request has been sent, too). */
      MIB2_STATS_INC(mib2.icmpinechoreps);
      break;
    case ICMP_ECHO: //对回显报文进行处理
      MIB2_STATS_INC(mib2.icmpinechos);
      src = ip4_current_dest_addr();
      /* multicast destination address? */
      if (ip4_addr_ismulticast(ip4_current_dest_addr())) {
        goto icmperr;
      }
      /* broadcast destination address? */
      if (ip4_addr_isbroadcast(ip4_current_dest_addr(), ip_current_netif())) {
#if LWIP_BROADCAST_PING
        /* For broadcast, use address of receiving interface as source address */
        src = netif_ip4_addr(inp);
#else /* LWIP_BROADCAST_PING */
        LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: Not echoing to broadcast pings\n"));
        goto icmperr;
#endif /* LWIP_BROADCAST_PING */
      }
      LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ping\n"));
      if (p->tot_len < sizeof(struct icmp_echo_hdr)) {
        LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: bad ICMP echo received\n"));
        goto lenerr;
      }
      /* At this point, all checks are OK. */
      /* We generate an answer by switching the dest and src ip addresses,
       * setting the icmp type to ECHO_RESPONSE and updating the checksum. */
      /* 调整回显报文请求中的相关字段以生成回显应答报文 */
      //强制将数据区域转换为 ICMP 报文首部
      iecho = (struct icmp_echo_hdr *)p->payload;
      if (pbuf_add_header(p, hlen)) {
        LWIP_DEBUGF(ICMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("Can't move over header in packet"));
      } else {
        err_t ret;
        struct ip_hdr *iphdr = (struct ip_hdr *)p->payload;
        ip4_addr_copy(iphdr->src, *src); //拷贝源 IP 地址
        ip4_addr_copy(iphdr->dest, *ip4_current_src_addr()); //拷贝目标 IP 地址
        ICMPH_TYPE_SET(iecho, ICMP_ER); //填写报文类型
#if CHECKSUM_GEN_ICMP
        IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_ICMP) {
          /* adjust the checksum */
          if (iecho->chksum > PP_HTONS(0xffffU - (ICMP_ECHO << 8))) {
            iecho->chksum = (u16_t)(iecho->chksum + PP_HTONS((u16_t)(ICMP_ECHO << 8)) + 1);
          } else {
            iecho->chksum = (u16_t)(iecho->chksum + PP_HTONS(ICMP_ECHO << 8));
          }
        }
#if LWIP_CHECKSUM_CTRL_PER_NETIF
        else {
          iecho->chksum = 0;
        }
#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF */
#else /* CHECKSUM_GEN_ICMP */
        iecho->chksum = 0;
#endif /* CHECKSUM_GEN_ICMP */

        /* Set the correct TTL and recalculate the header checksum. */
        IPH_TTL_SET(iphdr, ICMP_TTL); //填写生存时间
        IPH_CHKSUM_SET(iphdr, 0);
#if CHECKSUM_GEN_IP
        IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_IP) {
          IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, hlen));
        }
#endif /* CHECKSUM_GEN_IP */

        ICMP_STATS_INC(icmp.xmit);
        /* increase number of messages attempted to send */
        MIB2_STATS_INC(mib2.icmpoutmsgs);
        /* increase number of echo replies attempted to send */
        MIB2_STATS_INC(mib2.icmpoutechoreps);

        /* send an ICMP packet */
        ret = ip4_output_if(p, src, LWIP_IP_HDRINCL, /* 发送 ICMP 回显应答报文 */
                            ICMP_TTL, 0, IP_PROTO_ICMP, inp);
        if (ret != ERR_OK) {
          LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ip_output_if returned an error: %s\n", lwip_strerr(ret)));
        }
      }
      break;
    default: //对于其他类型的报文,直接丢掉
      if (type == ICMP_DUR) {
        MIB2_STATS_INC(mib2.icmpindestunreachs);
      } else if (type == ICMP_TE) {
        MIB2_STATS_INC(mib2.icmpintimeexcds);
      } else if (type == ICMP_PP) {
        MIB2_STATS_INC(mib2.icmpinparmprobs);
      } else if (type == ICMP_SQ) {
        MIB2_STATS_INC(mib2.icmpinsrcquenchs);
      } else if (type == ICMP_RD) {
        MIB2_STATS_INC(mib2.icmpinredirects);
      } else if (type == ICMP_TS) {
        MIB2_STATS_INC(mib2.icmpintimestamps);
      } else if (type == ICMP_TSR) {
        MIB2_STATS_INC(mib2.icmpintimestampreps);
      } else if (type == ICMP_AM) {
        MIB2_STATS_INC(mib2.icmpinaddrmasks);
      } else if (type == ICMP_AMR) {
        MIB2_STATS_INC(mib2.icmpinaddrmaskreps);
      }
      LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ICMP type %"S16_F" code %"S16_F" not supported.\n",
                               (s16_t)type, (s16_t)code));
      ICMP_STATS_INC(icmp.proterr);
      ICMP_STATS_INC(icmp.drop);
  }
  pbuf_free(p);
  return;
lenerr:
  pbuf_free(p);
  ICMP_STATS_INC(icmp.lenerr);
  MIB2_STATS_INC(mib2.icmpinerrors);
  return;
#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN || !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING
icmperr:
  pbuf_free(p);
  ICMP_STATS_INC(icmp.err);
  MIB2_STATS_INC(mib2.icmpinerrors);
  return;
#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN || !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING */
}

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 13:08:02  更:2022-10-17 13:09:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 5:26:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计