概述
目录
1. 简介
2. ICMP 报文结构
3. ICMP 报文类型
3.1 ICMP 差错报告报文
3.1.1 目的不可达
3.1.2 源站抑制
3.1.3 重定向
3.1.4 超时
3.1.5 参数错误
3.2 ICMP 查询报文
4. LwIP 中的ICMP 实现
4.1 ICMP 报文数据结构
4.2 发送ICMP 差错报文
4.3 处理 ICMP 报文
1. 简介
ICMP 是“Internet Control Message Protocol”(因特网控制报文协议)的缩写。IP 协议是一种不可靠、无连接的协议,只在各个主机间交付数据,但是对于数据的到达与否,IP 协议并不关心,为了提高数据交付的准确性,ICMP 就随之出现,在交付数据的时候,如果由于网络状况、链路不通等情况数据报无法到达目标主机,ICMP 就会返回一个差错报文,让源主机知道数据没能正常到达目标主机,接着进行重发或者放弃发送都可以。如在IP 数据报生存时间TTL 为0 的时候,路由器不再对数据报进行转发操作,而是会直接丢弃这个数据报,并且返回一个ICMP 报文,告诉源主机。
为什么需要ICMP,因为IP 协议认为丢掉没用的数据是合理的,这样子能提高数据处理的效率,但是源主机更希望能得到当数据没能发送到目标的时候有个回应,不然目标主机都不知道发的数据到了哪里,到达接收没用,就像石沉大海一样,这是不可接受的。此外,ICMP 也被主机和路由器用来彼此沟通网络层的信息,如果链路都连不通,那发送数据就是没有意义的事情,ICMP 报文虽然不传输用户数据,但是对于用户数据的传递起着重要的作用。
ICMP 最典型的用途是差错报告。例如,当运行一个FTP 或HTTP 应用时,也许会遇到一些诸如“目的不可达”之类的错误报文,这种报文就是在ICMP 中产生的,可能在转发的时候,在某个位置,路由器不能找到一条合适的路径转发该数据包以通往FTP 或HTTP 应用所指定的主机,该路由器就会向源主机创建和发出一个类型为3 的ICMP 报文以指示该错误(目的不可达)。
ICMP 通常被认为是IP 层协议的一部分,但从体系结构上讲它是位于IP 之上的,因为ICMP 报文是承载在IP 数据报中的。这就是说,ICMP 报文是作为IP 数据报数据区域的(有一些书籍也称之为有效载荷),就像TCP 与UDP 报文段作为IP 数据报数据区域那样。类似地,当一台主机收到一个指明上层协议为ICMP 的IP 数据报时,它将分解出该数据报的内容给ICMP,就像分解出一个数据报的内容给TCP 或UDP 一样,但与TCP 或UDP 协议又有所不同,ICMP 出现的目的不是为上层应用程序提供服务,只是在IP 层传递差错的报文,依赖于IP 协议进行传输。
2. ICMP 报文结构
ICMP 报文是使用IP 数据报来封装发送的,所以ICMP 报文也是没有额外的可靠性与优先级,它一样会被别的路由器丢弃,与此同时,ICMP 报文封装在IP 数据报中,IP 数据报封装在以太网帧中,因此ICMP 报文是经过了两次的封装。
ICMP 报文格式
ICMP 报文与IP 数据报一样,都是由首部与数据区域组成,ICMP 首部是8 个字节,对于不同类型的ICMP 报文,ICMP 报文首部的格式也会有点差异,但是首部的前4 个字节都是通用的:
第一个字节(占据8bit 空间)是类型(Type)字段,表示产生这种类型ICMP 报文的原因。
第二个字节是代码(Code)字段,它进一步描述了产生这种类型ICMP 报文的具体原因。因为每种类型的报文都可能有多个,比如目的不可达报文,产生这种原因可能有主机不可达、协议不可达、端口不可达等多个原因。
接下来的校验和字段(占据16bit)用于记录包括ICMP 报文数据部分在内的整个ICMP 数据报的校验和,以检验报文在传输过程中是否出现了差错,其计算方法与在我们介绍IP 数据包首部中的校验和计算方法是一样的。
剩下的4 个字节部分在讲解报文类型的时候详细讲解,因为不同类型的报文都有不一样的定义,并且数据部分的长度也存在差异。
3. ICMP 报文类型
ICMP 报文有两大类型,可以划分为差错报告报文和查询报文,差错报告报文主要是用来向IP 数据报源主机返回一个差错报告信息,而这个差错报告信息产生的原因是路由器或者主机不能对当前数据报进行正常的处理,简单来说就是源主机发送的数据报没法到目标主机中,或者到达了目标主机而无法递交给上层协议。
查询报文是用于一台主机向另一台主机发起一个请求,如果目标主机收到这个查询的请求后,就会按照查询报文的格式向源主机做出应答,比如我们使用的ping 命令,它的本质就是一个ICMP 查询报文。
虽然ICMP 报文很多,但是它并不能纠正错误,它只是借助IP 协议简单报告差错,差错报文是被返回源主机的,因为出现差错了,数据报中唯一可用的就是目标IP 地址与源IP地址,源主机收到ICMP 差错报告后,传递给上层协议,要怎么样处理就不是ICMP 协议的事情了。
3.1 ICMP 差错报告报文
3.1.1 目的不可达
在日常生活中,邮寄包裹会经过多个传递环节,任意一环如果无法传下去,都会返回给寄件人,并附上无法邮寄的原因。同理,当路由器收到一个无法传递下去的IP 数据报时,那么路由器就会丢弃这个数据报,然后返回一个ICMP 目的不可达差错报告报文(类型为3)给IP 报文的源发送方,产生差错的原因有很多,如网络不可达、主机不可达、协议不可达、端口不可达等,在ICMP 报文中的代码字段(Code)就用于记录引起差错的原因。
同时ICMP 目的不可达报文首部剩下的4 字节全部未用,而ICMP 报文数据区域则装载IP 数据报首部及IP 数据报的数据区域前8 字节,为什么需要装载IP 数据报的数据区域中前8 个字节的数据呢?因为IP 数据报的数据区域前8 个字节刚好覆盖了传输层协议中的端口号字段,而IP 数据报首部就拥有目标IP 地址与源IP 地址,当源主机收到这样子的ICMP 报文后,它能根据ICMP 报文的数据区域判断出是哪个数据包出现问题,并且IP 层能够根据端口号将报文传递给对应的上层协议处理,ICMP 目的不可达报文的格式。
3.1.2 源站抑制
因为IP 协议本身就是一个无反馈的协议,IP 层发送数据出去的时候就不会收到任何回复,当然也没不会知道目标主机那边的网络状况如何,可能源主机就一直发送数据到目标主机那边,但是由于处理性能、网络等其他因素,就会导致拥塞现象,就好比我们的公路一样,IP 数据报就是在公路上行驶的车辆,如果数据报太多了,路由/目标主机来不及处理这些数据报,那么就会堵在这里,就跟堵车是一个道理,而ICMP 源站抑制报文的出现就是告诉源主机,你不要发送那么多数据报了。
这个对于我们来说也不太重要,就简单看看其格式,实际上ICMP 源站抑制报文的格式与ICMP 目的不可达报文的格式是一样的,但代码字段为0。
3.1.3 重定向
一般来说,某个主机在启动的时候只有一个路由表(即默认路由),所以它发送的数据就是都发给了默认路由,让其帮忙转发,而路由器发现数据应该是发给另一个路由器的,那么它就会返回一个ICMP 重定向报文给源主机,告诉它应该直接发给另一个路由器。举个例子,比如有人在你们班级找人,但是要找的人不在你们班,而在隔壁班,你们就会告诉他那个人在隔壁班,让他在隔壁班找就行了,路由器也是这样子。
重定向一般用来让刚启动的主机逐渐建立更完善的路由表,因为主机启动时路由表中可能只有一个默认路由。一旦默认路由器发现它可以转发给其他路由器的时候,默认路由器将通知它进行重定向,告诉主机对路由表作相应的改动,这样子就无需默认路由处理这些事情,而且数据报传输的效率更高了,当然啦,重定向报文是只能由路由器生成而不能由主机生成,但是使用重定向报文的只能是主机而非路由器,所以我们暂时不用理会这些报文的格式是怎么样的,而且LwIP 对于这类数据报都不给予理会。
3.1.4 超时
IP 数据报首部有一个TTL 字段,就是防止IP 数据报永远在网络中漂流,当数据报每被转发一次,TTL 的值就减一,如果TTL 为0,那么路由或者主机就会丢弃该数据报,并且返回一个ICMP 超时报文到源主机中;此外,在数据报分片重装的时候也使用了ICMP报文,当所有的IP 分片数据报无法在规定的时间内完成重装,那么主机也会认为它超时了,那么这些数据报就会被删除,同时也返回一个ICMP 超时报文到源主机中,ICMP 超时报文格式与ICMP 目的站不可达报文格式一样,但代码字段中有两种取值.
3.1.5 参数错误
IP 数据报在网络中传输的时候,都是根据其首部进行识别的,如果首部出现错误,那么就会产生严重的问题,因此如果IP 数据报首部出现错误就会丢弃数据报,并且向源主机返回一个ICMP 参数错误报文,当然啦,对于携带ICMP 差错报文的数据报、非第一个分片的分片数据报、具有特殊目的地址的数据报(如环回、多播、广播)这些类型的数据报,即使是出现了差错也不会返回对应的差错报文。
3.2 ICMP 查询报文
ping 命令使用的就是ICMP 查询报文,若能ping 成功,说明网卡、IP 层、ICMP 层都能通信正常,所以能证明LwIP 已经移植成功了,我们一般在移植完成的时候都会测试一下ping 命令,查看一下是否移植成功。
ping 这个名字源于声纳定位操作。 ping 程序由Mike Muuss 编写,目的是为了测试另一台主机是否可达。该程序发送一份 ICMP 回显请求报文给目标主机,并等待目标主机返回ICMP 回显应答报文。
ICMP 回显请求报文和回显应答报文是LwIP 中唯一实现的报文,而其他几种报文只是在以前的时候主机启动时确定自己的IP 地址、掩码、路由状况等信息,现在基本用不到了,因为DHCP 协议就已全部帮我们实现了,使用我们只讲解ICMP 回显请求报文和回显应答报文。
我们称发送回显请求的ping 程序为客户,而称被 ping 的主机为服务器。大多数的TCP/IP 协议栈中都在内核中直接支持ping 服务器,但是注意了,这种ping 服务器不是一个用户线程,只是在内核线程中进行处理的。
ICMP 回显请求和回显应答报文格式
ICMP 报文首部剩下的4 字节内容分为了两个字段,标识符用于标识在同一台主机上同时运行了多个ping 程序,ping 程序也可以识别出返回的信息。序列号从0 开始,每发送一次新的回显请求就进行加 1。 ping 程序打印出返回的每个分组的序列号,允许我们查看是否有分组丢失、失序或重复。数据选项区域表示回显请求报文中可包含的数据,其长度是可选的,发送方选择合适的长度并且填充该段数据,在接收方,它会根据这个会回显请求返回一个回显应答报文,回显应答报文中的数据选项区域是与回显请求报文的数据选项区域完全一致。
4. LwIP 中的ICMP 实现
4.1 ICMP 报文数据结构
ICMP 报文与IP 数据报的数据结构差不多,在LwIP 中定义一个结构体对其进行描述,该结构体名字为icmp_echo_hdr,看名字的话是不是很吃惊,居然是ICMP 回显报文的首部,其实的确也是这样子的,因为各个类型的ICMP 报文首部都是差不多的,所以能将ICMP回显报文首部用于其他ICMP 报文首部,其内的成员变量分别对应ICMP 报文首部的类型、代码、校验和、标识符、序号等字段.
/** This is the standard ICMP header only that the u32_t data
* is split to two u16_t like ICMP echo needs it.
* This header is also used for other ICMP types that do not
* use the data part.
*/
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
此外LwIP 还定义了很多宏与枚举类型的变量对ICMP 的类型及代码字段进行描述
#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 */
/** ICMP destination unreachable codes */
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
};
/** ICMP time exceeded codes */
enum icmp_te_type {
/** time to live exceeded in transit */
ICMP_TE_TTL = 0,
/** fragment reassembly time exceeded */
ICMP_TE_FRAG = 1
};
看到这些宏定义是不是发现很多类型的ICMP 报文都没被实现,在LwIP 中确实也是这样子,我们也知道,LwIP 是一个较为完整的TCP/IP 协议栈,只实现了对ICMP 回显请求报文的处理,以及某些差错发送报告,如目的不可达报告报文、超时报文,而对于其他的ICMP 报文,LwIP 均只做识别,而不给予理会。
4.2 发送ICMP 差错报文
无论是何种情况下,在数据报无法向上层递交的时候,协议栈就会向源主机返回一个类型为目的不可达的ICMP 报文(具体的类型还要看代码字段),如IP 数据报无法递交到传输层时,就会调用icmp_dest_unreach()函数返回一个ICMP 协议不可达报文;而如果在传输层中,UDP 协议无法向应用层递交数据报,那么也将调用该函数返回一个ICMP 端口不可达报文。另一种是超时差错报文,在转发数据报的时候如果数据报中TTL 为0,或者在分片数据报重装的时候超时,LwIP 将调用icmp_time_exceeded()函数会发送一个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);
}
void
icmp_time_exceeded(struct pbuf *p, enum icmp_te_type t)
{
MIB2_STATS_INC(mib2.icmpouttimeexcds);
icmp_send_response(p, ICMP_TE, t);
}
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);
}
发送的时候就单纯为ICMP 申请pbuf 空间,大小为ICMP 报文首部 + IP 数据报首部 +8 字节,然后直接填写ICMP 报文首部区域,再拷贝IP 数据报首部及IP 数据报数据区域的前8 字节内容,然后再直接调用发送函数将ICMP 报文发送出去。
4.3 处理 ICMP 报文
LwIP 协议是轻量级TCP/IP 协议栈,所以对ICMP 报文中很多类型的报文都不做处理,LwIP 会将这些不处理的报文丢掉,但是对ICMP 回显请求报文就做出处理,所以这也是为什么我们能ping 通开发板的原因。
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;
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) receivedn", hlen));
goto lenerr;
}
if (p->len < sizeof(u16_t) * 2) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: short ICMP (%"U16_F" bytes) receivedn", p->tot_len));
goto lenerr;
}
type = *((u8_t *)p->payload);
#ifdef LWIP_DEBUG
code = *(((u8_t *)p->payload) + 1);
/* if debug is enabled but debug statement below is somehow disabled: */
LWIP_UNUSED_ARG(code);
#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). */
MIB2_STATS_INC(mib2.icmpinechoreps);
break;
case ICMP_ECHO:
MIB2_STATS_INC(mib2.icmpinechos);
src = ip4_current_dest_addr();
/* multicast destination address? */
if (ip4_addr_ismulticast(ip4_current_dest_addr())) {
#if LWIP_MULTICAST_PING
/* For multicast, use address of receiving interface as source address */
src = netif_ip4_addr(inp);
#else /* LWIP_MULTICAST_PING */
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: Not echoing to multicast pingsn"));
goto icmperr;
#endif /* LWIP_MULTICAST_PING */
}
/* broadcast destination address? */
if (ip4_addr_isbroadcast(ip4_current_dest_addr(), ip_current_netif())) {
#if LWIP_BROADCAST_PING
/* For broadcast, use address of receiving interface as source address */
src = netif_ip4_addr(inp);
#else /* LWIP_BROADCAST_PING */
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: Not echoing to broadcast pingsn"));
goto icmperr;
#endif /* LWIP_BROADCAST_PING */
}
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: pingn"));
if (p->tot_len < sizeof(struct icmp_echo_hdr)) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: bad ICMP echo receivedn"));
goto lenerr;
}
#if CHECKSUM_CHECK_ICMP
IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_ICMP) {
if (inet_chksum_pbuf(p) != 0) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: checksum failed for received ICMP echon"));
pbuf_free(p);
ICMP_STATS_INC(icmp.chkerr);
MIB2_STATS_INC(mib2.icmpinerrors);
return;
}
}
#endif
#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
if (pbuf_add_header(p, hlen + PBUF_LINK_HLEN + PBUF_LINK_ENCAPSULATION_HLEN)) {
/* p is not big enough to contain link headers
* allocate a new one and copy p into it
*/
struct pbuf *r;
u16_t alloc_len = (u16_t)(p->tot_len + hlen);
if (alloc_len < p->tot_len) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: allocating new pbuf failed (tot_len overflow)n"));
goto icmperr;
}
/* allocate new packet buffer with space for link headers */
r = pbuf_alloc(PBUF_LINK, alloc_len, PBUF_RAM);
if (r == NULL) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: allocating new pbuf failedn"));
goto icmperr;
}
if (r->len < hlen + sizeof(struct icmp_echo_hdr)) {
LWIP_DEBUGF(ICMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("first pbuf cannot hold the ICMP header"));
pbuf_free(r);
goto icmperr;
}
/* copy the ip header */
MEMCPY(r->payload, iphdr_in, hlen);
/* switch r->payload back to icmp header (cannot fail) */
if (pbuf_remove_header(r, hlen)) {
LWIP_ASSERT("icmp_input: moving r->payload to icmp header failedn", 0);
pbuf_free(r);
goto icmperr;
}
/* copy the rest of the packet without ip header */
if (pbuf_copy(r, p) != ERR_OK) {
LWIP_DEBUGF(ICMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("icmp_input: copying to new pbuf failed"));
pbuf_free(r);
goto icmperr;
}
/* 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 (cannot fail) */
if (pbuf_remove_header(p, hlen + PBUF_LINK_HLEN + PBUF_LINK_ENCAPSULATION_HLEN)) {
LWIP_ASSERT("icmp_input: restoring original p->payload failedn", 0);
goto icmperr;
}
}
#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN */
/* At this point, all checks are OK. */
/* 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;
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) {
/* adjust the checksum */
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 /* LWIP_CHECKSUM_CTRL_PER_NETIF */
#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);
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 /* CHECKSUM_GEN_IP */
ICMP_STATS_INC(icmp.xmit);
/* increase number of messages attempted to send */
MIB2_STATS_INC(mib2.icmpoutmsgs);
/* increase number of echo replies attempted to send */
MIB2_STATS_INC(mib2.icmpoutechoreps);
/* send an ICMP packet */
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: %sn", 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 /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN || !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING */
}
最后
以上就是欢呼汉堡为你收集整理的lwIP TCP/IP 协议栈笔记之十三: ICMP协议1. 简介2. ICMP 报文结构3. ICMP 报文类型 4. LwIP 中的ICMP 实现的全部内容,希望文章能够帮你解决lwIP TCP/IP 协议栈笔记之十三: ICMP协议1. 简介2. ICMP 报文结构3. ICMP 报文类型 4. LwIP 中的ICMP 实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复