LWIP学习笔记(7)ICMP协议

ICMP报文分类

常见ICMP报文类型如下,差错报文查询报文

ICMP报文种类具体类型功能
差错报文3目的站不可达
4源站抑制
5重定向(改变路由)
11数据报超时
12数据报参数错误
查询报文8或0回送请求或回送应答
10或9路由查询和通告
13或14时间戳请求或回答
15或16信息请求或回答
17或18地址掩码请求或回答
差错报文中

在目的不可达(类型3)下有如下不可达原因(位于ICMP代码字段)

代码字段取值含义
0网络不可达
1主机不可达
2协议不可达
3端口不可达
4需要分片但不分配被置位
5源路由失败
6目的网络未知
7目的主机未知

在数据报超时(类型11)下有如下超时原因(位于ICMP代码字段)

代码字段取值含义
0生存时间计数器超时
1分片重装超时
查询报文中

主要就是回送请求和回送应答

icmp.c/h 中实现相关数据结构和函数

数据结构

icmp.h
在这里插入图片描述
在这里插入图片描述
上面定义了icmp数据类型、首部结构、icmp函数声明

icmp发送处理

icmp.c
在这里插入图片描述
这个宏定义了如上解释
在这里插入图片描述
上面发送差错保温实际都是调用icmp_send_response,如下

/**
 * Send an icmp packet in response to an incoming packet.
 *
 * @param p the input packet for which the 'unreachable' should be sent,
 *          p->payload pointing to the IP header
 * @param type Type of the ICMP header
 * @param code Code of the ICMP header
 */
static void
icmp_send_response(struct pbuf *p, u8_t type, u8_t code)
{
  struct pbuf *q;
  struct ip_hdr *iphdr;
  /* we can use the echo header here */
  struct icmp_echo_hdr *icmphdr;//定义icmp数据结构指针
  ip_addr_t iphdr_src;//源ip
  //为差错报文申请pbuf,pbuf预留以太网首部和ip首部,申请数据长度为icmp首部长度+icmp数据长度(ip首部长度+8)
  /* ICMP header + IP header + 8 bytes of data */
  q = pbuf_alloc(PBUF_IP, sizeof(struct icmp_echo_hdr) + IP_HLEN + ICMP_DEST_UNREACH_DATASIZE,
                 PBUF_RAM);
  if (q == NULL) {
    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded: failed to allocate pbuf for ICMP packet.\n"));
    return;
  }
  LWIP_ASSERT("check that first pbuf can hold icmp message",
             (q->len >= (sizeof(struct icmp_echo_hdr) + IP_HLEN + ICMP_DEST_UNREACH_DATASIZE)));

  iphdr = (struct ip_hdr *)p->payload;//指向引起差错ip首部
  LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded from "));
  ip_addr_debug_print(ICMP_DEBUG, &(iphdr->src));
  LWIP_DEBUGF(ICMP_DEBUG, (" to "));
  ip_addr_debug_print(ICMP_DEBUG, &(iphdr->dest));
  LWIP_DEBUGF(ICMP_DEBUG, ("\n"));

  icmphdr = (struct icmp_echo_hdr *)q->payload;//指向带填写的icmp首部
  icmphdr->type = type;//填写icmp类型字段
  icmphdr->code = code;//填写icmp代码字段
  icmphdr->id = 0;
  icmphdr->seqno = 0;//剩余置0

  /* copy fields from original packet *///把引起差错ip数据报的ip首部和后续8字节拷贝到icmp数据报文中
  SMEMCPY((u8_t *)q->payload + sizeof(struct icmp_echo_hdr), (u8_t *)p->payload,
          IP_HLEN + ICMP_DEST_UNREACH_DATASIZE);

  /* calculate checksum */
  icmphdr->chksum = 0;
  icmphdr->chksum = inet_chksum(icmphdr, q->len);//计算校验和
  ICMP_STATS_INC(icmp.xmit);
  /* increase number of messages attempted to send */
  snmp_inc_icmpoutmsgs();
  /* increase number of destination unreachable messages attempted to send */
  snmp_inc_icmpouttimeexcds();
  ip_addr_copy(iphdr_src, iphdr->src);//获得源ip(即待发送的目的ip)
  ip_output(q, NULL, &iphdr_src, ICMP_TTL, 0, IP_PROTO_ICMP);//调用ip层输出函数发送出去
  pbuf_free(q);//释放icmp pbuf
}
icmp接收处理

目前lwip(1.4.1)只处理icmp回送请求报文,当ip层收到数据,根据协议类型得知是icmp会调用icmp.c中的icmp_input处理

/**
 * Processes ICMP input packets, called from ip_input().
 *
 * Currently only processes icmp echo requests and sends
 * out the echo response.
 *
 * @param p the icmp echo request packet, p->payload pointing to the ip header
 * @param inp the netif on which this packet was received
 */
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;
  struct ip_hdr *iphdr;
  s16_t hlen;

  ICMP_STATS_INC(icmp.recv);
  snmp_inc_icmpinmsgs();


  iphdr = (struct ip_hdr *)p->payload;//指向pbuf数据中ip首部
  hlen = IPH_HL(iphdr) * 4;//获得ip首部长度
  if (pbuf_header(p, -hlen) || (p->tot_len < sizeof(u16_t)*2)) {
    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: short ICMP (%"U16_F" bytes) received\n", p->tot_len));
    goto lenerr;//调整payload到icmp首部,调整失败或者icmp首部小于4字节则释放pbuf返回
  }

  type = *((u8_t *)p->payload);//获得icmp类型字段
#ifdef LWIP_DEBUG
  code = *(((u8_t *)p->payload)+1);//获得icmp代码字段
#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). */
    break; 
  case ICMP_ECHO://回送请求
#if !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING
    {
      int accepted = 1;//标志是否要发生回送应答,1发送 0不发
#if !LWIP_MULTICAST_PING
      /* multicast destination address? *///目的地址为多播则不发
      if (ip_addr_ismulticast(&current_iphdr_dest)) {
        accepted = 0;
      }
#endif /* LWIP_MULTICAST_PING */
#if !LWIP_BROADCAST_PING
      /* broadcast destination address? *///目的地址为广播则不发
      if (ip_addr_isbroadcast(&current_iphdr_dest, inp)) {
        accepted = 0;
      }
#endif /* LWIP_BROADCAST_PING */
      /* broadcast or multicast destination address not acceptd? */
      if (!accepted) {
        LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: Not echoing to multicast or broadcast pings\n"));
        ICMP_STATS_INC(icmp.err);
        pbuf_free(p);
        return;
      }
    }
#endif /* !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING */
    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ping\n"));
    if (p->tot_len < sizeof(struct icmp_echo_hdr)) {//报文长度比icmp首部还小,释放返回
      LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: bad ICMP echo received\n"));
      goto lenerr;
    }
    if (inet_chksum_pbuf(p) != 0) {//校验和判断
      LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: checksum failed for received ICMP echo\n"));
      pbuf_free(p);
      ICMP_STATS_INC(icmp.chkerr);
      snmp_inc_icmpinerrors();
      return;
    }
#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
    if (pbuf_header(p, (PBUF_IP_HLEN + PBUF_LINK_HLEN))) {
      /* p is not big enough to contain link headers
       * allocate a new one and copy p into it
       */
      struct pbuf *r;
      /* switch p->payload to ip header */
      if (pbuf_header(p, hlen)) {
        LWIP_ASSERT("icmp_input: moving p->payload to ip header failed\n", 0);
        goto memerr;
      }
      /* allocate new packet buffer with space for link headers */
      r = pbuf_alloc(PBUF_LINK, p->tot_len, PBUF_RAM);
      if (r == NULL) {
        LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: allocating new pbuf failed\n"));
        goto memerr;
      }
      LWIP_ASSERT("check that first pbuf can hold struct the ICMP header",
                  (r->len >= hlen + sizeof(struct icmp_echo_hdr)));
      /* copy the whole packet including ip header */
      if (pbuf_copy(r, p) != ERR_OK) {
        LWIP_ASSERT("icmp_input: copying to new pbuf failed\n", 0);
        goto memerr;
      }
      iphdr = (struct ip_hdr *)r->payload;
      /* switch r->payload back to icmp header */
      if (pbuf_header(r, -hlen)) {
        LWIP_ASSERT("icmp_input: restoring original p->payload failed\n", 0);
        goto memerr;
      }
      /* free the original p */
      pbuf_free(p);
      /* we now have an identical copy of p that has room for link headers */
      p = r;
    } else {
      /* restore p->payload to point to icmp header */
      if (pbuf_header(p, -(s16_t)(PBUF_IP_HLEN + PBUF_LINK_HLEN))) {
        LWIP_ASSERT("icmp_input: restoring original p->payload failed\n", 0);
        goto memerr;
      }
    }
#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN */
    /* At this point, all checks are OK. *///到这里检查完毕,直接修改接收到的pbuf来作为回送应答
    /* We generate an answer by switching the dest and src ip addresses,
     * setting the icmp type to ECHO_RESPONSE and updating the checksum. */
    iecho = (struct icmp_echo_hdr *)p->payload;//只想请求报文icmp首部
    ip_addr_copy(iphdr->src, *ip_current_dest_addr());//用目的ip来修改源ip
    ip_addr_copy(iphdr->dest, *ip_current_src_addr());//用源ip来修改目的ip
    ICMPH_TYPE_SET(iecho, ICMP_ER);//修改icmp类型为回送应答
#if CHECKSUM_GEN_ICMP
    /* adjust the checksum *///调整icmp校验和
    if (iecho->chksum >= PP_HTONS(0xffffU - (ICMP_ECHO << 8))) {
      iecho->chksum += PP_HTONS(ICMP_ECHO << 8) + 1;
    } else {
      iecho->chksum += PP_HTONS(ICMP_ECHO << 8);
    }
#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);//设置ip首部ttl字段
    IPH_CHKSUM_SET(iphdr, 0);
#if CHECKSUM_GEN_IP
    IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));//计算ip首部校验和
#endif /* CHECKSUM_GEN_IP */

    ICMP_STATS_INC(icmp.xmit);
    /* increase number of messages attempted to send */
    snmp_inc_icmpoutmsgs();
    /* increase number of echo replies attempted to send */
    snmp_inc_icmpoutechoreps();

    if(pbuf_header(p, hlen)) {//调整payload到ip首部
      LWIP_ASSERT("Can't move over header in packet", 0);
    } else {
      err_t ret;
      /* send an ICMP packet, src addr is the dest addr of the curren packet */
      ret = ip_output_if(p, ip_current_dest_addr(), IP_HDRINCL,
                   ICMP_TTL, 0, IP_PROTO_ICMP, inp);
      if (ret != ERR_OK) {//调用ip层函数发送出去,并设置IP_HDRINCL表示ip首部字段已填好
        LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ip_output_if returned an error: %c.\n", ret));
      }
    }
    break;
  default://其他类型直接释放返回
    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);
  snmp_inc_icmpinerrors();
  return;
#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
memerr:
  pbuf_free(p);
  ICMP_STATS_INC(icmp.err);
  snmp_inc_icmpinerrors();
  return;
#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN */
}

ip_current_src_addr是在ip.h中宏定义,表示当前正在处理的ip数据报源目ip
在这里插入图片描述

实现ping

ip.c中ip层输入函数ip_input中对每个数据报都会调用raw_input,这里为用户直接处理ip数据报提供了方式,lwip把这种方式称作原始协议控制块(raw_pcb)机制,类似原始套接字。
raw.h中,定义了控制块结构
在这里插入图片描述
IP_PCBip.h
在这里插入图片描述

原始协议控制块(raw_pcb)机制原理

内核会用控制块结构raw_pcb构建一条全局链表raw_pcbs,每一个raw_pcb可以定制特定协议的ip数据报、ip地址、执行函数等,当收到的ip数据报中ip地址和协议字段和某个raw_pcb吻合时就调用回调函数。其中为ip数据报寻找匹配的raw_pcb就是raw_input
如下

/** The list of RAW PCBs *///内核中原始协议控制快全局链表
static struct raw_pcb *raw_pcbs;

/**
 * Determine if in incoming IP packet is covered by a RAW PCB
 * and if so, pass it to a user-provided receive callback function.
 *
 * Given an incoming IP datagram (as a chain of pbufs) this function
 * finds a corresponding RAW PCB and calls the corresponding receive
 * callback function.
 *
 * @param p pbuf to be demultiplexed to a RAW PCB.
 * @param inp network interface on which the datagram was received.
 * @return - 1 if the packet has been eaten by a RAW PCB receive
 *           callback function. The caller MAY NOT not reference the
 *           packet any longer, and MAY NOT call pbuf_free().
 * @return - 0 if packet is not eaten (pbuf is still referenced by the
 *           caller).
 *返回0表示 p被处理并释放,协议栈不需再处理 返回1表示p未被处理,继续交给协议栈上层处理
 */
u8_t raw_input(struct pbuf *p, struct netif *inp)
{
  struct raw_pcb *pcb, *prev;
  struct ip_hdr *iphdr;
  s16_t proto;
  u8_t eaten = 0;//raw_input返回值

  LWIP_UNUSED_ARG(inp);

  iphdr = (struct ip_hdr *)p->payload;//指向ip首部
  proto = IPH_PROTO(iphdr);//取出协议字段

  prev = NULL;
  pcb = raw_pcbs;//指向raw_pcbs链表头
  /* loop through all raw pcbs until the packet is eaten by one */
  /* this allows multiple pcbs to match against the packet by design */
  while ((eaten == 0) && (pcb != NULL)) {//遍历链表
    if ((pcb->protocol == proto) && //协议字段匹配且目的ip吻合
        (ip_addr_isany(&pcb->local_ip) ||
         ip_addr_cmp(&(pcb->local_ip), &current_iphdr_dest))) {
#if IP_SOF_BROADCAST_RECV
      /* broadcast filter? */
      if (ip_get_option(pcb, SOF_BROADCAST) || !ip_addr_isbroadcast(&current_iphdr_dest, inp))
#endif /* IP_SOF_BROADCAST_RECV */
      {
        /* receive callback function available? */
        if (pcb->recv != NULL) {
          /* the receive callback function did not eat the packet? */
          if (pcb->recv(pcb->recv_arg, pcb, p, ip_current_src_addr()) != 0) {
            /* receive function ate the packet */
            p = NULL;
            eaten = 1;//ip数据报被已处理
            if (prev != NULL) {//找到匹配的pcbs不再链表头则把他移到链表头缩短下次找到时间
            /* move the pcb to the front of raw_pcbs so that is
               found faster next time */
              prev->next = pcb->next;
              pcb->next = raw_pcbs;
              raw_pcbs = pcb;
            }
          }
        }
        /* no receive callback function was set for this raw PCB */
      }
      /* drop the packet */
    }
    prev = pcb;
    pcb = pcb->next;
  }
  return eaten;
}

了解了上面内容,可以这样实现ping:定制一个icmp类型控制块把他加入到raw_pcbs
中,其中回调函数用于处理接收到的回送响应,计算时间差。

/** 
 * This is an example of a "ping" sender (with raw API).
 * It can be used as a start point to maintain opened a network connection, or
 * like a network "watchdog" for your device.
 *
 */

#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/timers.h"
#include "lwip/inet_chksum.h"

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

/** ping target - should be a "ip_addr_t" */
#ifndef PING_TARGET
#define PING_TARGET   (netif_default?netif_default->gw:ip_addr_any)
#endif

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

/** ping delay - in milliseconds *///ping请求发送间隔
#ifndef PING_DELAY
#define PING_DELAY     1000
#endif

/** ping identifier - must fit on a u16_t *///设置自定义icmp标识符
#ifndef PING_ID
#define PING_ID        0xAFAF
#endif

/** ping additional data size to include in the packet *///icmp数据长度
#ifndef PING_DATA_SIZE
#define PING_DATA_SIZE 32
#endif

/** ping result action - no default action */
#ifndef PING_RESULT
#define PING_RESULT(ping_ok) //if(ping_ok) printf("ping [%u] OK: time=[%u]ms\n",ping_seq_num, sys_now()-ping_time)
#endif

/* ping variables */
static u16_t ping_seq_num;//icmp首部序号字段
static u32_t ping_time;//ping往返时间
static struct raw_pcb *ping_pcb = NULL;//ping的控制块
static ip_addr_t ping_dst;//ping目的ip

/** 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  = 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);

}

/* 接收到icmp回送响应 */
static u8_t ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, 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);

  //we can also check src ip here, but just egnore it
  if ((p->tot_len >= (PBUF_IP_HLEN + sizeof(struct icmp_echo_hdr))))
  {
      iecho = (struct icmp_echo_hdr *)((u8_t*)p->payload + PBUF_IP_HLEN);
      if ((iecho->type == ICMP_ER) && (iecho->id == PING_ID) && (iecho->seqno == htons(ping_seq_num))) 
			{//判断类型、标识符、序号
	  
          LWIP_DEBUGF( PING_DEBUG, ("ping: recv "));
          ip_addr_debug_print(PING_DEBUG, addr);
          LWIP_DEBUGF( PING_DEBUG, (" time=%"U32_F" ms\n", (sys_now()-ping_time)));
					//计算往返时间
          /* do some ping result processing */
          PING_RESULT(1);
          pbuf_free(p);
          return 1; /* eat the packet */
      }
  }
  return 0; /* don't eat the packet */
}

static void ping_send(struct raw_pcb *raw, 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_ASSERT("ping_size <= 0xffff", ping_size <= 0xffff);
 
  p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM);
  if (!p) {
    return;
  }
	//一个pbuf
  if ((p->len == p->tot_len) && (p->next == NULL)) {
    iecho = (struct icmp_echo_hdr *)p->payload;
    //填写icmp首部
    ping_prepare_echo(iecho, (u16_t)ping_size);
    //发送数据包,最终是调用ip_output_if
    raw_sendto(raw, p, addr);
    ping_time = sys_now();//记下发生时间

	LWIP_DEBUGF(PING_DEBUG, ("ping:[%"U32_F"] send ", ping_seq_num));
	ip_addr_debug_print(PING_DEBUG, addr);
    LWIP_DEBUGF( PING_DEBUG, ("\n"));
  }
  pbuf_free(p);//释放pbuf
}
//内核超时后调用次函数
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_dst);//发送回送请求

  sys_timeout(PING_DELAY, ping_timeout, pcb);//再次注册超时事件
}

static void ping_raw_init(void)
{
  ping_pcb = raw_new(IP_PROTO_ICMP);//申请一个icmp类型pcb
  LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL);

  raw_recv(ping_pcb, ping_recv, NULL);//注册recv回调函数为ping_recv
  raw_bind(ping_pcb, IP_ADDR_ANY);//绑定ip为0(本机任一端口)
	//设置超时事件PING_DELAY后ping_timeout被内核调用,回调参数为ping_pcb
  sys_timeout(PING_DELAY, ping_timeout, ping_pcb);
}

void ping_init(void)//ping初始化
{
  IP4_ADDR(&ping_dst, 192,168,1,103);//ping ip
  ping_raw_init();//
}

#endif /* LWIP_RAW */

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值