【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,头部。
seqno
中no
表示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);}
#elsenetif = ip4_route(&iphdr_src);
#endifif (netif != NULL) {/* calculate checksum */icmphdr->chksum = 0;
#if CHECKSUM_GEN_ICMPIF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP) {icmphdr->chksum = inet_chksum(icmphdr, q->len);}
#endifICMP_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
来表示。