正点原子lwIP学习笔记——ICMP协议

1.ICMP协议简介

ICMP协议是一个网络层协议。 一个新搭建好的网络,往往需要先进行一个简单的测试,来验证网络是否畅通;但是IP协议并不提供可靠传输。如果丢包了,IP协议并不能通知传输层是否丢包以及丢包的原因。因此我们需要ICMP协议来完成这样的功能。

需要ICMP协议的原因

  1. IP 协议本身不提供差错报告和差错控制机制来保证数据报递交的有效性,例如:数据报在网络中被丢弃了,源主机更希望等到该数据报递交过程中的异常信息;
  2. IP 协议不能进行主机管理与查询机制,例如:不知道对方主机或者路由器的活跃,对于不活跃的主机和路由器就没有必要发送数据报。

总结来说:为了更有效地转发IP数据报和提高交付成功机会

ICMP协议类型与结构

正点原子lwIP学习笔记——ICMP协议_第1张图片
对于ICMP协议中的差错报告报文,在lwIP中实现的是目的不可达以及超时的报文;对于超时报文又分为两种,一个是生存时间TTL(在IP首部中),另一个是分片传输中,接收到一个分片后的超时等待时间超时;

ICMP协议中的询问报文,lwIP实现的则是回送请求/应答报文

正点原子lwIP学习笔记——ICMP协议_第2张图片
无论是差错还是询问报文,前4个字节是一样的:第一个是类型,第二个是代码,例如超时就是0/1,0代表生存时间为0、1则是超时等待时间为0;后两个是校验和;

之后的4个字节则是取决于ICMP报文的类型;整个ICMP的数据部分,长度取决于类型;

整个ICMP报文是在网络层,可以说IP数据包包含了IP首部以及ICMP报文

ICMP差错报文

用于检测IP数据报在传输过程的异常信息目的不可达、源站抑制、重定向、超时、参数错误)

正点原子lwIP学习笔记——ICMP协议_第3张图片
ICMP类型为3,则代表了是目的不可达lwIP实现了代码值2、3、4的差错;ICMP类型为11则代表了是超时错误;代码值0代表传输期间生存时间为0,1代表数据报组装期间生存时间为0.

ICMP查询报文

用于诊断两个网络设备之间彼此是否能够通信
正点原子lwIP学习笔记——ICMP协议_第4张图片
lwIP只处理ICMP类型0/8,代表了回显请求/应答;目的主机收到 ICMP 回送请求报文后立刻回送应答报文,若源主机能收到 ICMP 回送应答报文,则说明到达该主机的网络正常(PING)。

2.ICMP协议原理

ICMP报文数据结构

正点原子lwIP学习笔记——ICMP协议_第5张图片
以上结构体位于icmp.h中;包括有ICMP的类型、代码、校验和、标志符以及序号五个变量。

正点原子lwIP学习笔记——ICMP协议_第6张图片
差错报文中,前4个字节是类型、代码和校验后;后4个字节全为0;然后传输的数据就是因其差错的IP首部以及他的pbuf的前8个字节的数据;

查询报文的前4个字节与差错报文一样;后4个字节中,2格式标识符,2个事序号;数据部分则是请求报文发送和应答报文重复(就是类型为8,就是回送请求,直接把类型改为0,变成回送应答)。

ICMP差错报文

lwIP只实现目的不可达、超时差错报文,它们分别为icmp_dest_unreach和icmp_time_exceeded函数;

正点原子lwIP学习笔记——ICMP协议_第7张图片
这两种差错报文都是调用icmp_send_response发送;其源码和注释如下:

static void
icmp_send_response(struct pbuf *p, u8_t type, u8_t code)
{
  struct pbuf *q;
  struct ip_hdr *iphdr;
  struct icmp_echo_hdr *icmphdr;
  ip4_addr_t iphdr_src;
  struct netif *netif;
  u16_t response_pkt_len;

  MIB2_STATS_INC(mib2.icmpoutmsgs);

  /* response_pkt_len = 20 + 8 = 28*/
  response_pkt_len = IP_HLEN + ICMP_DEST_UNREACH_DATASIZE;
  if (p->tot_len < response_pkt_len) {
    response_pkt_len = p->tot_len;
  }

  /* 申请pbuf内存 */
  q = pbuf_alloc(PBUF_IP, sizeof(struct icmp_echo_hdr) + response_pkt_len, PBUF_RAM);
  if (q == NULL) {
    MIB2_STATS_INC(mib2.icmpouterrors);
    return;
  }
  
  /* 获取对方主机数据报的IP首部 */
  iphdr = (struct ip_hdr *)p->payload;
  /* 在q->payload地址附加icmp_echo_hdr结构体 */
  icmphdr = (struct icmp_echo_hdr *)q->payload;
  /* 设置ICMP首部信息 */
  icmphdr->type = type;
  icmphdr->code = code;
  icmphdr->id = 0;
  icmphdr->seqno = 0;
  
  /* 把对方主机的数据复制IP首部和前8字节数据到新申请的pbuf当中 */
  SMEMCPY((u8_t *)q->payload + sizeof(struct icmp_echo_hdr), (u8_t *)p->payload,
          response_pkt_len);
  
  /* 复制目标地址 */
  ip4_addr_copy(iphdr_src, iphdr->src);
  /* 判断是否同一网段 */
  netif = ip4_route(&iphdr_src);

  if (netif != NULL) {
    icmphdr->chksum = 0;

    ICMP_STATS_INC(icmp.xmit);
	/* 发送ICMP差错报文 */
    ip4_output_if(q, NULL, &iphdr_src, ICMP_TTL, 0, IP_PROTO_ICMP, netif);
  }
  pbuf_free(q);
}

正点原子lwIP学习笔记——ICMP协议_第8张图片
以上源码的逻辑,就是根据当前的type和code判断处理方式,判断得到是差错报文,就把被丢弃数据包的pbuf中的IP首部和前8个字节数据拷贝到差错报文中(同样也是一个pbuf)。

ICMP查询报文

请求报文发送,应答报文重复。简单来讲,应答包是在请求包的基础上修改得来;查询报文的源码和注释如下:

void
icmp_input(struct pbuf *p, struct netif *inp)
{
  u8_t type;

  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();
  /* 获取IP首部 */
  hlen = IPH_HL_BYTES(iphdr_in);
  
  if (hlen < IP_HLEN) {
    goto lenerr;
  }
  if (p->len < sizeof(u16_t) * 2) {
    goto lenerr;
  }
  
  /* 获取 ICMP 的类型字段 */
  type = *((u8_t *)p->payload);
  switch (type) {
    case ICMP_ER:/* 回送应答 */
      MIB2_STATS_INC(mib2.icmpinechoreps);
      break;
    case ICMP_ECHO:/* 回送请求 */
      MIB2_STATS_INC(mib2.icmpinechos);
	  /* 获取IP地址 */
      src = ip4_current_dest_addr();
      /* 判断是否为多播地址 */
      if (ip4_addr_ismulticast(ip4_current_dest_addr())) {
        goto icmperr;
      }
      /* 判断是否为广播地址 */
      if (ip4_addr_isbroadcast(ip4_current_dest_addr(), ip_current_netif())) {
        goto icmperr;
      }
	  /* 判断这个pbuf的总长度是否小于icmp首部 */
      if (p->tot_len < sizeof(struct icmp_echo_hdr)) {
        goto lenerr;
      }
#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
      /* 偏移paylosd指针 = IP首部(20) + 14 + 0 = 34 */
      if (pbuf_add_header(p, hlen + PBUF_LINK_HLEN + PBUF_LINK_ENCAPSULATION_HLEN)) {

        struct pbuf *r;
		/* 申请大小 = p->tot_len + hlen */
        u16_t alloc_len = (u16_t)(p->tot_len + hlen);
		
        if (alloc_len < p->tot_len) {
          goto icmperr;
        }
		
        /* 申请pbuf */
        r = pbuf_alloc(PBUF_LINK, alloc_len, PBUF_RAM);
        if (r == NULL) {
          goto icmperr;
        }
		
        if (r->len < hlen + sizeof(struct icmp_echo_hdr)) {
          pbuf_free(r);
          goto icmperr;
        }
        /* 拷贝IP首部到新申请的pbuf当中 */
        MEMCPY(r->payload, iphdr_in, hlen);
		
        /* 偏移paylosd指针 */
        if (pbuf_remove_header(r, hlen)) {
          pbuf_free(r);
          goto icmperr;
        }
        /* 复制没有ip首部的其余数据包 */
        if (pbuf_copy(r, p) != ERR_OK) {
          pbuf_free(r);
          goto icmperr;
        }
        /* 释放pbuf(p) */
        pbuf_free(p);
        /* p -> r */
        p = r;
      } else {
        /* 偏移paylosd指针 IP首部(20) + 14 + 0 = 34 */
        if (pbuf_remove_header(p, hlen + PBUF_LINK_HLEN + PBUF_LINK_ENCAPSULATION_HLEN)) {
          goto icmperr;
        }
      }
#endif
      /* p->payload地址上附加icmp_echo_hdr结构体 */
      iecho = (struct icmp_echo_hdr *)p->payload;
	  /* 添加IP首部 */
      if (pbuf_add_header(p, hlen)) {
      } else {
        err_t ret;
		/* 设置IP首部信息 */
        struct ip_hdr *iphdr = (struct ip_hdr *)p->payload;
        ip4_addr_copy(iphdr->src, *src);
        ip4_addr_copy(iphdr->dest, *ip4_current_src_addr());
		/* 设置ICMP首部信息 */
        ICMPH_TYPE_SET(iecho, ICMP_ER);
        iecho->chksum = 0;

        /* 设置生存时间 */
        IPH_TTL_SET(iphdr, ICMP_TTL);
        IPH_CHKSUM_SET(iphdr, 0);
        ICMP_STATS_INC(icmp.xmit);
        MIB2_STATS_INC(mib2.icmpoutmsgs);
        MIB2_STATS_INC(mib2.icmpoutechoreps);

        /* 发送ICMP回显应答包 */
        ret = ip4_output_if(p, src, LWIP_IP_HDRINCL,
                            ICMP_TTL, 0, IP_PROTO_ICMP, inp);
        if (ret != ERR_OK) {
        }
      }
      break;
    default:  /* lwIP不作处理 */
      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);
      }
  }
  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协议_第9张图片
总结来说,ICMP的回送请求,把ICMP结构体的type从8改成0,然后把pbuf的payload上移20个字节,添加IP首部,就变成了回送应答包。

总结

这一篇的源码还是比较简单易懂的,没有太多要F12跳转的内容,总的原理也比较清晰。

至此,lwIP的大部分协议都学完了,还剩下TCP和UDP协议,现在的lwIP框架如下:
正点原子lwIP学习笔记——ICMP协议_第10张图片

你可能感兴趣的:(lwIP学习,学习,笔记,stm32,网络协议)