IP 协议是一种不可靠、无连接的协议,只在各个主机间交付数据,但是对于数据的到达与否, IP 协议并不关心,为了提高数据交付的准确性, ICMP 就随之出现,在交付数据的时候,如果由于网络状况、链路不通等情况数据报无法到达目标主机,ICMP 就会返回一个差错报文,让源主机知道数据没能正常到达目标主机,接着进行重发或者放弃发送都可以。 ICMP 通常被认为是 IP 层协议的一部分,但从体系结构上讲它是位于 IP 之上的,因为ICMP 报文是承载在 IP 数据报中的。这就是说, ICMP 报文是作为 IP 数据报数据区域的(有一些书籍也称之为有效载荷) ,就像 TCP 与 UDP 报文段作为 IP 数据报数据区域那样。类似地,当一台主机收到一个指明上层协议为 ICMP 的 IP 数据报时,它将分解出该数据报的内容给 ICMP,就像分解出一个数据报的内容给 TCP 或 UDP 一样,但与 TCP 或 UDP 协议又有所不同, ICMP 出现的目的不是为上层应用程序提供服务,只是在 IP 层传递差错的报文,依赖于 IP 协议进行传输。
ICMP报文结构
ICMP 报文是使用 IP 数据报来封装发送的, 所以 ICMP 报文也是没有额外的可靠性与优先级,它一样会被别的路由器丢弃,与此同时, ICMP 报文封装在 IP 数据报中, IP 数据报封装在以太网帧中,因此 ICMP 报文是经过了两次的封装。 ICMP 报文与 IP 数据报一样,都是由首部与数据区域组成, ICMP 首部是 8 个字节,对于不同类型的 ICMP 报文, ICMP 报文首部的格式也会有点差异,但是首部的前 4 个字节都是通用的: 第一个字节(占据 8bit 空间)是类型(Type) 字段,表示产生这种类型 ICMP 报文的原因。 第二个字节是代码(Code) 字段,它进一步描述了产生这种类型 ICMP 报文的具体原因。因为每种类型的报文都可能有多个,比如目的不可达报文,产生这种原因可能有主机不可达、协议不可达、端口不可达等多个原因。 接下来的校验和字段(占据 16bit) 用于记录包括 ICMP 报文数据部分在内的整个ICMP 数据报的校验和,以检验报文在传输过程中是否出现了差错, 其计算方法与IP 数据包首部中的校验和计算方法是一样的。 剩下的 4 个字节部分在讲解报文类型的时候详细讲解,因为不同类型的报文都有不一样的定义,并且数据部分的长度也存在差异, ICMP 报文格式
ICMP报文类型
ICMP 报文有两大类型, 可以划分为差错报告报文和查询报文, 差错报告报文主要是用来向 IP 数据报源主机返回一个差错报告信息, 而这个差错报告信息产生的原因是路由器或者主机不能对当前数据报进行正常的处理,简单来说就是源主机发送的数据报没法到目标主机中,或者到达了目标主机而无法递交给上层协议。 查询报文是用于一台主机向另一台主机发起一个请求,如果目标主机收到这个查询的请求后,就会按照查询报文的格式向源主机做出应答,比如我们使用的 ping 命令,它的本质就是一个 ICMP 查询报文。
ICMP差错报文
目的不可达(类型 3)
LwIP 实现的只有前 6 种,代码取值如下 同时 ICMP 目的不可达报文首部剩下的 4 字节全部未用,而 ICMP 报文数据区域则装载 IP 数据报首部及 IP 数据报的数据区域前 8 字节,为什么需要装载 IP 数据报的数据区域中前 8 个字节的数据呢?因为 IP 数据报的数据区域前 8 个字节刚好覆盖了传输层协议中的端口号字段,而 IP 数据报首部就拥有目标 IP 地址与源 IP 地址,当源主机收到这样子的ICMP 报文后,它能根据 ICMP 报文的数据区域判断出是哪个数据包出现问题,并且 IP 层能够根据端口号将报文传递给对应的上层协议处理, ICMP 目的不可达报文的格式具体见图
ICMP查询报文
ICMP 回显请求报文和回显应答报文是 LwIP 中唯一实现的报文,即 ping
处理ICMP报文
LwIP 协议是轻量级 TCP/IP 协议栈,所以对 ICMP 报文中很多类型的报文都不做处理,LwIP 会将这些不处理的报文丢掉,但是对 ICMP 回显请求报文就做出处理,所以这也是为什么我们能 ping 通开发板的原因。当 IP 层收到一个 ICMP 报文的时候, 会调用 icmp_input()函数将报文传递到 ICMP 协议去处理,关于这个函数的处理也是比较简单明了的,只处理 ICMP 请求报文,然后返回一个 ICMP 应答报文,具体看注释即可, icmp_input()函数具体见代码清单
void
icmp_input(struct pbuf *p, struct netif *inp)
{
u8_t type;
#ifdef LWIP_DEBUG
u8_t code;
#endif
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();
hlen = IPH_HL_BYTES(iphdr_in);
if (hlen < IP_HLEN) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: short IP header (%"S16_F" bytes) received\n", hlen));
goto lenerr;
}
if (p->len < sizeof(u16_t) * 2) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: short ICMP (%"U16_F" bytes) received\n", p->tot_len));
goto lenerr;
}
type = *((u8_t *)p->payload);
#ifdef LWIP_DEBUG
code = *(((u8_t *)p->payload) + 1);
LWIP_UNUSED_ARG(code);
#endif
switch (type) {
case ICMP_ER:
MIB2_STATS_INC(mib2.icmpinechoreps);
break;
case ICMP_ECHO:
MIB2_STATS_INC(mib2.icmpinechos);
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())) {
#if LWIP_BROADCAST_PING
src = netif_ip4_addr(inp);
#else
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: Not echoing to broadcast pings\n"));
goto icmperr;
#endif
}
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ping\n"));
if (p->tot_len < sizeof(struct icmp_echo_hdr)) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: bad ICMP echo received\n"));
goto lenerr;
}
iecho = (struct icmp_echo_hdr *)p->payload;
if (pbuf_add_header(p, hlen)) {
LWIP_DEBUGF(ICMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("Can't move over header in packet"));
} else {
err_t ret;
struct ip_hdr *iphdr = (struct ip_hdr *)p->payload;
ip4_addr_copy(iphdr->src, *src);
ip4_addr_copy(iphdr->dest, *ip4_current_src_addr());
ICMPH_TYPE_SET(iecho, ICMP_ER);
#if CHECKSUM_GEN_ICMP
IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_ICMP) {
if (iecho->chksum > PP_HTONS(0xffffU - (ICMP_ECHO << 8))) {
iecho->chksum = (u16_t)(iecho->chksum + PP_HTONS((u16_t)(ICMP_ECHO << 8)) + 1);
} else {
iecho->chksum = (u16_t)(iecho->chksum + PP_HTONS(ICMP_ECHO << 8));
}
}
#if LWIP_CHECKSUM_CTRL_PER_NETIF
else {
iecho->chksum = 0;
}
#endif
#else
iecho->chksum = 0;
#endif
IPH_TTL_SET(iphdr, ICMP_TTL);
IPH_CHKSUM_SET(iphdr, 0);
#if CHECKSUM_GEN_IP
IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_IP) {
IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, hlen));
}
#endif
ICMP_STATS_INC(icmp.xmit);
MIB2_STATS_INC(mib2.icmpoutmsgs);
MIB2_STATS_INC(mib2.icmpoutechoreps);
ret = ip4_output_if(p, src, LWIP_IP_HDRINCL,
ICMP_TTL, 0, IP_PROTO_ICMP, inp);
if (ret != ERR_OK) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ip_output_if returned an error: %s\n", lwip_strerr(ret)));
}
}
break;
default:
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);
}
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);
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
}
|