【LwIP源码学习7】ICMP部分源码分析

ICMP功能简介

当IP数据报由于网络状况、链路不通等问题无法到达目标主机时,ICMP会返回一个差错报文。

ICMP查询报文(ping)

标识符用于标识在同一台主机上同时运行了多个ping程序,序列号从0开始,每发送一次新的回显请求就进行加1。
在这里插入图片描述

源码分析

PACK_STRUCT_BEGIN
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;
PACK_STRUCT_END

对应着ICMP报文格式。
echo:回声,表示验证链路可达性。
hdr:header,头部。
seqnono表示number,No.1

ICMP目标不可达代码枚举为:

enum icmp_dur_type {
  /** net unreachable */
  ICMP_DUR_NET   = 0,
  /** host unreachable */
  ICMP_DUR_HOST  = 1,
  /** protocol unreachable */
  ICMP_DUR_PROTO = 2,
  /** port unreachable */
  ICMP_DUR_PORT  = 3,
  /** fragmentation needed and DF set */
  ICMP_DUR_FRAG  = 4,
  /** source route failed */
  ICMP_DUR_SR    = 5
};

dur :destination unreachable ,目标不可达
ICMP超时代码枚举为:

enum icmp_te_type {
  /** time to live exceeded in transit */
  ICMP_TE_TTL  = 0,
  /** fragment reassembly time exceeded */
  ICMP_TE_FRAG = 1
};

te:time exceeded,超时

当ip数据报无法到达传输层或者应用层时,调用icmp_dest_unreach()函数返回一个ICMP协议报文不可达。

void
icmp_dest_unreach(struct pbuf *p, enum icmp_dur_type t)
{
  MIB2_STATS_INC(mib2.icmpoutdestunreachs);
  icmp_send_response(p, ICMP_DUR, t);
}

dest_unreach:目标不可达

根据

#define MIB2_STATS_INC(x) STATS_INC(x)
#define STATS_INC(x) ++lwip_stats.x

两个宏可知,
MIB2_STATS_INC(mib2.icmpoutdestunreachs);相当于

++lwip_stats.mib2.icmpoutdestunreachs;

lwip_stats是全局统计数据容器,在stats.c文件中定义如下:

struct stats_ lwip_stats;

结构体struct stats_stats.h文件中定义:

/** lwIP stats container */
struct stats_ {
#if LINK_STATS
  /** Link level */
  struct stats_proto link;
#endif
。。。
#if MIB2_STATS
  /** SNMP MIB2 */
  struct stats_mib2 mib2;
#endif
};

mib2 : management information base,2表示版本号是2,base有库和集合的意思,所以含义是“管理数据集合”

stats.h中有如下两个宏

#define STATS_INC(x) ++lwip_stats.x
#define STATS_DEC(x) --lwip_stats.x

inc:increment,递增
dec:decrement,递减

如果数据报超时,lwip会调用icmp_time_exceeded()函数发送一个ICMP超时报文

void
icmp_time_exceeded(struct pbuf *p, enum icmp_te_type t)
{
  MIB2_STATS_INC(mib2.icmpouttimeexcds);
  icmp_send_response(p, ICMP_TE, t);
}

time_exceeded:超时

icmp_send_response分析
源码为:

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;
  ip4_addr_t iphdr_src;
  struct netif *netif;

  /* increase number of messages attempted to send */
  MIB2_STATS_INC(mib2.icmpoutmsgs);

  /* 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"));
    MIB2_STATS_INC(mib2.icmpouterrors);
    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;
  LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded from "));
  ip4_addr_debug_print_val(ICMP_DEBUG, iphdr->src);
  LWIP_DEBUGF(ICMP_DEBUG, (" to "));
  ip4_addr_debug_print_val(ICMP_DEBUG, iphdr->dest);
  LWIP_DEBUGF(ICMP_DEBUG, ("\n"));

  icmphdr = (struct icmp_echo_hdr *)q->payload;
  icmphdr->type = type;
  icmphdr->code = code;
  icmphdr->id = 0;
  icmphdr->seqno = 0;

  /* copy fields from original packet */
  SMEMCPY((u8_t *)q->payload + sizeof(struct icmp_echo_hdr), (u8_t *)p->payload,
          IP_HLEN + ICMP_DEST_UNREACH_DATASIZE);

  ip4_addr_copy(iphdr_src, iphdr->src);
#ifdef LWIP_HOOK_IP4_ROUTE_SRC
  {
    ip4_addr_t iphdr_dst;
    ip4_addr_copy(iphdr_dst, iphdr->dest);
    netif = ip4_route_src(&iphdr_dst, &iphdr_src);
  }
#else
  netif = ip4_route(&iphdr_src);
#endif
  if (netif != NULL) {
    /* calculate checksum */
    icmphdr->chksum = 0;
#if CHECKSUM_GEN_ICMP
    IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP) {
      icmphdr->chksum = inet_chksum(icmphdr, q->len);
    }
#endif
    ICMP_STATS_INC(icmp.xmit);
    ip4_output_if(q, NULL, &iphdr_src, ICMP_TTL, 0, IP_PROTO_ICMP, netif);
  }
  pbuf_free(q);
}

输入的三个变量中,
struct pbuf *p用于查找输出接口对象netif,和目标ip地址iphdr_src
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;
  ip4_addr_t iphdr_src;
  struct netif *netif;

与他们第一次被使用的顺序是相同的。

struct netif *
ip4_route(const ip4_addr_t *dest)

route:路由
该函数用于根据ip地址查找对应接口的netif,所有的netif都挂在netif_list链表中。

err_t
ip4_output_if(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
              u8_t ttl, u8_t tos,
              u8_t proto, struct netif *netif)

用IPv4协议发送数据。其上层还有一个函数:

err_t
ip4_output(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
           u8_t ttl, u8_t tos, u8_t proto)

接下来是icmp_input(struct pbuf *p, struct netif *inp)函数,用于处理输入的ICMP数据,其中只对回显请求报文做了处理,所以ping运行lwip的设备可以ping通。

其中有一段:

const ip4_addr_t *src;

src = ip4_current_dest_addr();

其中:

#define ip4_current_dest_addr()    (&ip_data.current_iphdr_dest)
struct ip_globals ip_data;
struct ip_globals
{
  /** The interface that accepted the packet for the current callback invocation. */
  struct netif *current_netif;
  /** The interface that received the packet for the current callback invocation. */
  struct netif *current_input_netif;
#if LWIP_IPV4
  /** Header of the input packet currently being processed. */
  const struct ip_hdr *current_ip4_header;
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
  /** Header of the input IPv6 packet currently being processed. */
  struct ip6_hdr *current_ip6_header;
#endif /* LWIP_IPV6 */
  /** Total header length of current_ip4/6_header (i.e. after this, the UDP/TCP header starts) */
  u16_t current_ip_header_tot_len;
  /** Source IP address of current_header */
  ip_addr_t current_iphdr_src;
  /** Destination IP address of current_header */
  ip_addr_t current_iphdr_dest;
};

ip_globals:表示ip层相关的一些全局变量。声明的变量为ip_data
其中“当前的”用current来表示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值