基于DWC_ether_qos的以太网驱动开发-LWIP的ICMP模块介绍与PING收发测

描述

本文转自公众号欢迎关注
https://mp.weixin.qq.com/s/6MTNop3zBKdQ-gabbWo63Q

一. 前言

ICMP即Internet Control Message Protocol因特网控制消息协议。

ICMP是网络层协议,IP不可分割的一部分。

ICMP用于报告数据报处理中的错误,比如以下情况下时发送ICMP消息:当数据报无法到达其目的地时,当网关没有转发数据报的缓冲能力时,以及当网关可以指示主机在较短的路由上发送数据时。

互联网协议的设计并不是绝对可靠的。ICMP这些控制消息的目的是提供有关通信环境中问题的反馈,而不是使IP可靠。不能确保数据报的传递或控制消息的返回,一些数据报可能无法送达时也没有任何丢失报告。如果需要可靠的通信,则使用IP的更高级别协议必须实现其自己的可靠性程序。

ICMP消息通常报告数据报处理中的错误,且不发送关于ICMP消息的ICMP消息,否则消息会无限递归。此外,ICMP消息只发送关于处理分段数据报的零分段时的错误。(片段零的片段offeset等于零)。

参考

RFC 792

https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml

二. ICMP消息格式

ICMP消息格式

从 ICMP 的报文格式来说,ICMP 是 IP 的上层协议,他是在IP报的基础上再添加ICMP报格式。但是 ICMP 是分担了 IP 的一部分功能。所以,他也被认为是与 IP 同层的协议。

以太网

我们这里只关注ICMP部分,ICMP由首部和数据两部分组成,如下

区域类型Type代码Code校验和Checksum
字节大小112
区域首部数据,根据类型不一样而不一样,对于ping拆为了16位的ID和16位的序列号
字节大小4
区域ICMP数据部分
字节大小类型不同长度不同

Type和Code如下表

类型TYPE代码CODE**用途描述 Description**查询类Query差错类Error
00Echo Reply——回显应答(Ping应答)x
30Network Unreachable——网络不可达x
31Host Unreachable——主机不可达x
32Protocol Unreachable——协议不可达x
33Port Unreachable——端口不可达x
34Fragmentation needed but no frag. bit set——需要进行分片但设置不分片比特x
35Source routing failed——源站选路失败x
36Destination network unknown——目的网络未知x
37Destination host unknown——目的主机未知x
38Source host isolated (obsolete)——源主机被隔离(作废不用)x
39Destination network administratively prohibited——目的网络被强制禁止x
310Destination host administratively prohibited——目的主机被强制禁止x
311Network unreachable for TOS——由于服务类型TOS,网络不可达x
312Host unreachable for TOS——由于服务类型TOS,主机不可达x
313Communication administratively prohibited by filtering——由于过滤,通信被强制禁止x
314Host precedence violation——主机越权x
315Precedence cutoff in effect——优先中止生效x
40Source quench——源端被关闭(基本流控制)
50Redirect for network——对网络重定向
51Redirect for host——对主机重定向
52Redirect for TOS and network——对服务类型和网络重定向
53Redirect for TOS and host——对服务类型和主机重定向
80Echo request——回显请求(Ping请求)x
90Router advertisement——路由器通告
100Route solicitation——路由器请求
110TTL equals 0 during transit——传输期间生存时间为0x
111TTL equals 0 during reassembly——在数据报组装期间生存时间为0x
120IP header bad (catchall error)——坏的IP首部(包括各种差错)x
121Required options missing——缺少必需的选项x
130Timestamp request (obsolete)——时间戳请求(作废不用)x
14Timestamp reply (obsolete)——时间戳应答(作废不用)x
150Information request (obsolete)——信息请求(作废不用)x
160Information reply (obsolete)——信息应答(作废不用)x
170Address mask request——地址掩码请求x
180Address mask reply——地址掩码应答

使用wirshark协助解析

以太网

以太网

三. LWIP中ICMP相关代码分析

这里只看IPV4相关的,IPV6下也有对应的实现。

ipv4/icmp.c

icmp.h

3.1调试打印

可以配置宏ICMP_DEBUG,使能调试打印,一遍跟踪对应的程序处理过程。

lwipopts.h中配置

#define ICMP_DEBUG LWIP_DBG_ON

3.2数据结构

实现了以下Type

#define ICMP_ER   0    /* echo reply */


#define ICMP_DUR  3    /* destination unreachable */


#define ICMP_SQ   4    /* source quench */


#define ICMP_RD   5    /* redirect */


#define ICMP_ECHO 8    /* echo */


#define ICMP_TE  11    /* time exceeded */


#define ICMP_PP  12    /* parameter problem */


#define ICMP_TS  13    /* timestamp */


#define ICMP_TSR 14    /* timestamp reply */


#define ICMP_IRQ 15    /* information request */


#define ICMP_IR  16    /* information reply */


#define ICMP_AM  17    /* address mask request */


#define ICMP_AMR 18    /* address mask reply */

定义了头的数据结构

struct icmp_hdr {


PACK_STRUCT_FLD_8(u8_t type);


PACK_STRUCT_FLD_8(u8_t code);


PACK_STRUCT_FIELD(u16_t chksum);


PACK_STRUCT_FIELD(u32_t data);


} PACK_STRUCT_STRUCT;

如果四echo则,ICMP首部后面4字节数据拆分成id和序列号

struct icmp_echo_hdr {


PACK_STRUCT_FLD_8(u8_t type);


PACK_STRUCT_FLD_8(u8_t code);


PACK_STRUCT_FIELD(u16_t chksum);


PACK_STRUCT_FIELD(u16_t id);


PACK_STRUCT_FIELD(u16_t seqno);


} PACK_STRUCT_STRUCT;

3.3输入数据流

关键代码是icmp_input

ethernet_input->Type=0x0800 ip4_input-> Protocol=0x01 icmp_input

通过switch处理各种类型

switch (type) {


  case ICMP_ER:

比如对于收到别人的ping请求就是进入

case ICMP_ECHO:

如果是多播地址不响应

然后检查ICMP头部至少要8字节。

然后检查checksum

最后调用ip4_output_if发送响应包。

3.4输出数据流

icmp_dest_unreach

icmp_time_exceeded

都是调用

icmp_send_response

3.5发送PING

收到响应进入icmp_input的

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

发送ping可以裸机可以使用raw PCB,带OS可以使用socket实现

详见ping.c/h

#include "lwip/opt.h"


#if LWIP_RAW /* don't build if not configured for use in lwipopts.h */


#include "ping.h"


#include "lwip/mem.h"
#include "lwip/raw.h"
#include "lwip/icmp.h"
#include "lwip/netif.h"
#include "lwip/sys.h"
#include "lwip/timeouts.h"
#include "lwip/inet_chksum.h"
#include "lwip/prot/ip4.h"


#if PING_USE_SOCKETS
#include "lwip/sockets.h"
#include "lwip/inet.h"
#include < string.h >
#endif /* PING_USE_SOCKETS */




/**
 * PING_DEBUG: Enable debugging for PING.
 */
#ifndef PING_DEBUG
#define PING_DEBUG     LWIP_DBG_ON
#endif


/** ping receive timeout - in milliseconds */
#ifndef PING_RCV_TIMEO
#define PING_RCV_TIMEO 1000
#endif


/** ping delay - in milliseconds */
#ifndef PING_DELAY
#define PING_DELAY     1000
#endif


/** ping identifier - must fit on a u16_t */
#ifndef PING_ID
#define PING_ID        0xAFAF
#endif


/** ping additional data size to include in the packet */
#ifndef PING_DATA_SIZE
#define PING_DATA_SIZE 32
#endif


/** ping result action - no default action */
#ifndef PING_RESULT
#define PING_RESULT(ping_ok)
#endif


/* ping variables */
static const ip_addr_t* ping_target;
static u16_t ping_seq_num;
#ifdef LWIP_DEBUG
static u32_t ping_time;
#endif /* LWIP_DEBUG */
#if !PING_USE_SOCKETS
static struct raw_pcb *ping_pcb;
#endif /* PING_USE_SOCKETS */


/** Prepare a echo ICMP request */
static void
ping_prepare_echo( struct icmp_echo_hdr *iecho, u16_t len)
{
  size_t i;
  size_t data_len = len - sizeof(struct icmp_echo_hdr);


  ICMPH_TYPE_SET(iecho, ICMP_ECHO);
  ICMPH_CODE_SET(iecho, 0);
  iecho- >chksum = 0;
  iecho- >id     = PING_ID;
  iecho- >seqno  = lwip_htons(++ping_seq_num);


  /* fill the additional data buffer with some data */
  for(i = 0; i < data_len; i++) {
    ((char*)iecho)[sizeof(struct icmp_echo_hdr) + i] = (char)i;
  }


  iecho- >chksum = inet_chksum(iecho, len);
}


#if PING_USE_SOCKETS


/* Ping using the socket ip */
static err_t
ping_send(int s, const ip_addr_t *addr)
{
  int err;
  struct icmp_echo_hdr *iecho;
  struct sockaddr_storage to;
  size_t ping_size = sizeof(struct icmp_echo_hdr) + PING_DATA_SIZE;
  LWIP_ASSERT("ping_size is too big", ping_size <= 0xffff);


#if LWIP_IPV6
  if(IP_IS_V6(addr) && !ip6_addr_isipv4mappedipv6(ip_2_ip6(addr))) {
    /* todo: support ICMP6 echo */
    return ERR_VAL;
  }
#endif /* LWIP_IPV6 */


  iecho = (struct icmp_echo_hdr *)mem_malloc((mem_size_t)ping_size);
  if (!iecho) {
    return ERR_MEM;
  }


  ping_prepare_echo(iecho, (u16_t)ping_size);


#if LWIP_IPV4
  if(IP_IS_V4(addr)) {
    struct sockaddr_in *to4 = (struct sockaddr_in*)&to;
    to4- >sin_len    = sizeof(*to4);
    to4- >sin_family = AF_INET;
    inet_addr_from_ip4addr(&to4- >sin_addr, ip_2_ip4(addr));
  }
#endif /* LWIP_IPV4 */


#if LWIP_IPV6
  if(IP_IS_V6(addr)) {
    struct sockaddr_in6 *to6 = (struct sockaddr_in6*)&to;
    to6- >sin6_len    = sizeof(*to6);
    to6- >sin6_family = AF_INET6;
    inet6_addr_from_ip6addr(&to6- >sin6_addr, ip_2_ip6(addr));
  }
#endif /* LWIP_IPV6 */


  err = lwip_sendto(s, iecho, ping_size, 0, (struct sockaddr*)&to, sizeof(to));


  mem_free(iecho);


  return (err ? ERR_OK : ERR_VAL);
}


static void
ping_recv(int s)
{
  char buf[64];
  int len;
  struct sockaddr_storage from;
  int fromlen = sizeof(from);


  while((len = lwip_recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, (socklen_t*)&fromlen)) > 0) {
    if (len >= (int)(sizeof(struct ip_hdr)+sizeof(struct icmp_echo_hdr))) {
      ip_addr_t fromaddr;
      memset(&fromaddr, 0, sizeof(fromaddr));


#if LWIP_IPV4
      if(from.ss_family == AF_INET) {
        struct sockaddr_in *from4 = (struct sockaddr_in*)&from;
        inet_addr_to_ip4addr(ip_2_ip4(&fromaddr), &from4- >sin_addr);
        IP_SET_TYPE_VAL(fromaddr, IPADDR_TYPE_V4);
      }
#endif /* LWIP_IPV4 */


#if LWIP_IPV6
      if(from.ss_family == AF_INET6) {
        struct sockaddr_in6 *from6 = (struct sockaddr_in6*)&from;
        inet6_addr_to_ip6addr(ip_2_ip6(&fromaddr), &from6- >sin6_addr);
        IP_SET_TYPE_VAL(fromaddr, IPADDR_TYPE_V6);
      }
#endif /* LWIP_IPV6 */


      LWIP_DEBUGF( PING_DEBUG, ("ping: recv "));
      ip_addr_debug_print_val(PING_DEBUG, fromaddr);
      LWIP_DEBUGF( PING_DEBUG, (" %"U32_F" msn", (sys_now() - ping_time)));


      /* todo: support ICMP6 echo */
#if LWIP_IPV4
      if (IP_IS_V4_VAL(fromaddr)) {
        struct ip_hdr *iphdr;
        struct icmp_echo_hdr *iecho;


        iphdr = (struct ip_hdr *)buf;
        iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4));
        if ((iecho- >id == PING_ID) && (iecho- >seqno == lwip_htons(ping_seq_num))) {
          /* do some ping result processing */
          PING_RESULT((ICMPH_TYPE(iecho) == ICMP_ER));
          return;
        } else {
          LWIP_DEBUGF( PING_DEBUG, ("ping: dropn"));
        }
      }
#endif /* LWIP_IPV4 */
    }
    fromlen = sizeof(from);
  }


  if (len == 0) {
    LWIP_DEBUGF( PING_DEBUG, ("ping: recv - %"U32_F" ms - timeoutn", (sys_now()-ping_time)));
  }


  /* do some ping result processing */
  PING_RESULT(0);
}


static void
ping_thread(void *arg)
{
  int s;
  int ret;


#if LWIP_SO_SNDRCVTIMEO_NONSTANDARD
  int timeout = PING_RCV_TIMEO;
#else
  struct timeval timeout;
  timeout.tv_sec = PING_RCV_TIMEO/1000;
  timeout.tv_usec = (PING_RCV_TIMEO%1000)*1000;
#endif
  LWIP_UNUSED_ARG(arg);


#if LWIP_IPV6
  if(IP_IS_V4(ping_target) || ip6_addr_isipv4mappedipv6(ip_2_ip6(ping_target))) {
    s = lwip_socket(AF_INET6, SOCK_RAW, IP_PROTO_ICMP);
  } else {
    s = lwip_socket(AF_INET6, SOCK_RAW, IP6_NEXTH_ICMP6);
  }
#else
  s = lwip_socket(AF_INET,  SOCK_RAW, IP_PROTO_ICMP);
#endif
  if (s < 0) {
    return;
  }


  ret = lwip_setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
  LWIP_ASSERT("setting receive timeout failed", ret == 0);
  LWIP_UNUSED_ARG(ret);


  while (1) {
    if (ping_send(s, ping_target) == ERR_OK) {
      LWIP_DEBUGF( PING_DEBUG, ("ping: send "));
      ip_addr_debug_print(PING_DEBUG, ping_target);
      LWIP_DEBUGF( PING_DEBUG, ("n"));


#ifdef LWIP_DEBUG
      ping_time = sys_now();
#endif /* LWIP_DEBUG */
      ping_recv(s);
    } else {
      LWIP_DEBUGF( PING_DEBUG, ("ping: send "));
      ip_addr_debug_print(PING_DEBUG, ping_target);
      LWIP_DEBUGF( PING_DEBUG, (" - errorn"));
    }
    sys_msleep(PING_DELAY);
  }
}


#else /* PING_USE_SOCKETS */


/* Ping using the raw ip */
static u8_t
ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr)
{
  struct icmp_echo_hdr *iecho;
  LWIP_UNUSED_ARG(arg);
  LWIP_UNUSED_ARG(pcb);
  LWIP_UNUSED_ARG(addr);
  LWIP_ASSERT("p != NULL", p != NULL);


  if ((p- >tot_len >= (PBUF_IP_HLEN + sizeof(struct icmp_echo_hdr))) &&
      pbuf_remove_header(p, PBUF_IP_HLEN) == 0) {
    iecho = (struct icmp_echo_hdr *)p- >payload;


    if ((iecho- >id == PING_ID) && (iecho- >seqno == lwip_htons(ping_seq_num))) {
      LWIP_DEBUGF( PING_DEBUG, ("ping: recv "));
      ip_addr_debug_print(PING_DEBUG, addr);
      LWIP_DEBUGF( PING_DEBUG, (" %"U32_F" msn", (sys_now()-ping_time)));


      /* do some ping result processing */
      PING_RESULT(1);
      pbuf_free(p);
      return 1; /* eat the packet */
    }
    /* not eaten, restore original packet */
    pbuf_add_header(p, PBUF_IP_HLEN);
  }


  return 0; /* don't eat the packet */
}


static void
ping_send(struct raw_pcb *raw, const ip_addr_t *addr)
{
  struct pbuf *p;
  struct icmp_echo_hdr *iecho;
  size_t ping_size = sizeof(struct icmp_echo_hdr) + PING_DATA_SIZE;


  LWIP_DEBUGF( PING_DEBUG, ("ping: send "));
  ip_addr_debug_print(PING_DEBUG, addr);
  LWIP_DEBUGF( PING_DEBUG, ("n"));
  LWIP_ASSERT("ping_size <= 0xffff", ping_size <= 0xffff);


  p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM);
  if (!p) {
    return;
  }
  if ((p- >len == p- >tot_len) && (p- >next == NULL)) {
    iecho = (struct icmp_echo_hdr *)p- >payload;


    ping_prepare_echo(iecho, (u16_t)ping_size);


    raw_sendto(raw, p, addr);
#ifdef LWIP_DEBUG
    ping_time = sys_now();
#endif /* LWIP_DEBUG */
  }
  pbuf_free(p);
}


static void
ping_timeout(void *arg)
{
  struct raw_pcb *pcb = (struct raw_pcb*)arg;


  LWIP_ASSERT("ping_timeout: no pcb given!", pcb != NULL);


  ping_send(pcb, ping_target);


  sys_timeout(PING_DELAY, ping_timeout, pcb);
}


static void
ping_raw_init(void)
{
  ping_pcb = raw_new(IP_PROTO_ICMP);
  LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL);


  raw_recv(ping_pcb, ping_recv, NULL);
  raw_bind(ping_pcb, IP_ADDR_ANY);
  sys_timeout(PING_DELAY, ping_timeout, ping_pcb);
}


void
ping_send_now(void)
{
  LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL);
  ping_send(ping_pcb, ping_target);
}


#endif /* PING_USE_SOCKETS */


void
ping_init(const ip_addr_t* ping_addr)
{
  ping_target = ping_addr;


#if PING_USE_SOCKETS
  sys_thread_new("ping_thread", ping_thread, NULL, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
#else /* PING_USE_SOCKETS */
  ping_raw_init();
#endif /* PING_USE_SOCKETS */
}


#endif /* LWIP_RAW */
#ifndef LWIP_PING_H
#define LWIP_PING_H


#include "lwip/ip_addr.h"


/**
 * PING_USE_SOCKETS: Set to 1 to use sockets, otherwise the raw api is used
 */
#ifndef PING_USE_SOCKETS
#define PING_USE_SOCKETS    LWIP_SOCKET
#endif


void ping_init(const ip_addr_t* ping_addr);


#if !PING_USE_SOCKETS
void ping_send_now(void);
#endif /* !PING_USE_SOCKETS */


#endif /* LWIP_PING_H */

如下是带OS的测试

ip_addr_t ping_addr;


  IP4_ADDR(&ping_addr, 192, 168, 1, 9);


  ping_init(&ping_addr);

打印如下,(这里printf不支持某些格式所以IP地址打印显示不对)

以太网

四. 总结

了解ICMP包的格式,了解LWIP发送ping和对堆ping请求响应的过程。

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分