文章目录
一、ICMP协议简介
架构IP网络时需要特别注意两点:确认网络是否正常工作;遇到异常时进行问题诊断。例如,一个刚刚搭建好的网络,需要验证该网络的设置是否正确,为了确保网络能够按照预期正常工作,一旦遇到什么问题需要立即制止问题的蔓延。IP协议虽然完成了数据报在各个主机之间的递交,但它只提供了一种无连接不可靠的数据报交付服务,协议本身并不提供任何错误检验与恢复机制,这就需要另一种协议ICMP(Internet Control Message Protocol)提供相应的错误检验与状态查询机制。
ICMP协议的主要功能包括,确认IP包是否成功送达目标地址,通知在发送过程当中IP包被废弃的具体原因,改善网络设置等。有了这些功能后,就可以获得网络是否正常、设置是否有误以及设备有何异常等信息,从而便于进行网络上的问题诊断。
在IP通信中如果某个IP包因为某种原因未能到达目标地址,那么这个具体的原因将由ICMP负责通知。ICMP的这种通知消息会使用IP数据报进行发送,从这点看ICMP有点像上层传输层协议,但由于ICMP并不为应用程序提供传输服务,所以仍算作网络层协议。ICMP报文封装位置与格式如下图示:
1.1 ICMPv4报文功能
从功能上划分,ICMP消息报文大致可以分为两类:一类是通知出错原因的差错报告报文;另一类是用于诊断的查询消息报文。差错报告报文主要用来向IP数据报源主机返回一个差错报告信息,这个错误报告信息产生的原因是路由器或主机不能对当前数据报进行正常的处理,例如无法将数据报递交给有效的上层协议、数据报因为生存时间TTL减为0而被删除等。查询报文用于一台主机向另一台主机查询特定的信息,通常查询报文都是成对出现的,即源主机发起一个查询报文,在目的主机收到该报文后,会按照查询报文约定的格式为源主机返回一个应答报文。两大种类的ICMPv4报文及其常见类型如下表示:
目的站不可达:IP路由器无法将UO数据报发送到目的地址时,会给发送端主机返回一个目的不可达的ICMP消息报文,并在这个消息报文中显示不可达的具体原因(前篇介绍的路径MTU发现就是根据代码4的分片位实现的),如下表示:
数据报超时:数据报超时可以用来防止数据报在网络中被循环的路由,在IP包中有一个字段叫TTL(Time To Live,生存时间),它的值随着每经过一次路由器就会减1,直到减到0时该IP包会被丢弃,IP路由器将会发送一个ICMP超时消息报文给发送端主机以通知该包已被丢弃(网络上常用的traceroute命令就是充分利用ICMP超时消息实现的),超时原因主要有以下两类:
源站抑制:为了给IP协议增加一种流量控制而设计,当路由器或主机因拥塞而丢弃数据报时,它可以向源站发送ICMP源站抑制报文,这个报文将告诉源站两个消息:第一,你的数据报发得太快,我已经丢弃了;第二,路径中出现了拥塞,请放慢你的数据报发送频率。
重定向:如果路由器发现发送端主机使用了次优的路径发送数据包,那么它会返回一个ICMP重定向消息报文告诉源主机改变它的路由表,这个消息报文中包含了最合适的路由信息和源数据,以提高数据报的递交效率。
数据报参数错误:数据报在网络中传输时,其首部中出现的任何二义性都可能会产生严重的问题,如果路由器或主机发现了这种二义性或者数据报中的某个字段丢失,路由器会直接丢弃数据报,并向源主机返回一个数据报参数错误报文。
回送请求或应答:用于进行通信的主机或路由器之间,判断所发送的数据包是否已经成功到达对端的一种消息报文。可以向对端主机发送回送请求消息,也可以接收对端主机发回来的回送应答消息,网络上最常用的ping命令就是利用这个消息报文实现的。
路由器询问和通告:主要用于发现与自己相连网络中的路由器,当一台主机发出ICMP路由器请求时,路由器则返回相应的通告报文。
时间戳请求或回答:在互联网中的两台主机能够使用时间戳请求或回答报文来确定数据报在彼此之间往返所需要的时间。
地址掩码请求或回答:主要用于主机或路由器想要了解子网掩码的情况,可以向那些目标主机或路由器发送ICMP地址掩码请求报文,然后通过接收ICMP地址掩码应答报文获取子网掩码的信息。
1.2 ICMPv6报文功能
IPv4中ICMP仅作为一个辅助作用支持IPv4,即使没有ICMP仍可以实现IP通信。然而在IPv6中,ICMP的作用被扩大了,如果没有ICMPv6,IPv6就无法进行正常通信。比如在IPv6中从IP地址定位MAC地址的协议从ARP转为ICMP的邻居探索消息(Neighbor Discovery),这种邻居探索消息融合了IPv4的ARP、ICMP重定向以及ICMP路由器选择消息等功能于一体,甚至还提供自动设置IP地址的功能。
ICMPv6中将ICMP也大致分为两类:一类是错误报告消息(类型0–127);另一类是信息查询消息(类型128–255)。常用的消息类型如下表示:
上面的错误报告消息含义跟ICMPv4类似,其中的数据包过大是指数据包在传递过程中,其大小超过了链路的MTU值,路由器会向源节点发送此消息,此消息也被用于链路MTU发现协议。
上面的信息查询报文多数也跟ICMPv4类似,下面主要介绍下多播监听发现消息(Multicast Listener Discovery)、邻居探索消息(Neighbor Discovery Protocol)、反邻居探索消息等,更多信息可以参考博客:IPv6重臣之ICMPv6。
多播监听发现:包括从类型130至类型132的消息,在多播通信中,用于确认是否有接收端,这里的MLD(Multicast Listener Discovery)可以实现IPv4中IGMP(Internet Group Management Protocol)的功能。其中多播监听报告与结束消息用于通知多播路由器(需要支持多播路由协议,便于将组关系转发给互联网上其它的多播路由器)加入与退出多播组,多播监听查询消息用于周期性探寻本地局域网上主机是否还为多播组成员。
邻居探索消息:ICMPv6中从类型133至类型137的消息叫做邻居探索消息,其中邻居请求消息用于查询IPv6的地址与MAC地址的对应关系(与IPv4中的ARP协议功能类似),并由邻居宣告消息得知MAC地址,邻居请求消息利用IPv6的多播地址实现传输。
反邻居探索消息:反向邻居探索请求消息用于查询MAC地址与IPv6地址的对应关系(与IPv4中的RARP协议功能类似),并由反向邻居探索宣告消息得知IPv6地址,反向邻居探索消息也利用了IPv6的多播地址实现传输。
由于在IPv6中实现了即插即用的功能,所以在没有DHCP服务器的环境下也能实现IP地址的自动获取。如果是一个没有路由器的网络,就使用MAC地址作为链路本地单播地址(前篇介绍过IPv6地址中的一种,网络标识为FE80::/10,主机标识为64比特版的MAC地址EUI-64)。而在一个有路由器的网络环境中,可以从路由器获得IPv6地址的前面部分的网络标识,后面部分的主机标识则由MAC地址进行设置(需要转换为EUI-64),此时可以利用路由器请求与宣告消息进行设置。
二、PC常用网络命令
前面介绍ICMP时已经提到了两个常用的网络命令ping与traceroute:ping命令利用ICMP的回送请求/应答消息报文检查网络的连通性与往返估计时间;traceroute命令利用ICMP超时消息报文显示出由执行程序的源主机到达目的主机之前历经多少路由器。下面分别看下这两个命令的使用示例:
上面是在windows系统上运行命令截的图,主要是考虑到windows上命令支持的参数排版解释更详细。这两个命令也是在进行网络错误监测时最常用到的命令,前面介绍的查看ARP缓存表,查看路由表,查看网卡接口信息,甚至后面将要介绍的查询网络端口连接信息都有相关的命令提供功能支持,下面列举出linux常用的网络命令如下:
Linux常用网络命令 | 命令功能描述 |
---|---|
ifconfig | 可以手动启动、查看、修改网络接口的相关参数,可以修改的参数包括IP地址、子网掩码、默认网关、MTU等; |
iwlist iwconfig |
iwlist可以利用无线网卡进行无线AP的检测并获得相关数据; iwconfig可以设置无线网卡的相关参数; |
ip | 网络参数综合命令,除了可以设置一些基本的网络参数外,还能执行额外的IP协议,包括多IP的设置,功能很强大; |
arp | 查看IP地址与MAC地址对的缓存表信息; |
route | 查看目的IP地址、子网掩码、默认网关等路由状态信息; |
nslookup host |
查看主机名与IP地址的对应关系信息; |
ping | 查看目的主机是否可以访问到,并可获知往返时间等信息; |
traceroute | 跟踪源主机到目的主机所通过的各个路由节点信息; |
netstat | 查看网络传输层各端口的连接状态信息,比如目前有多少连接已建立或出现问题等; |
telnet | 可用于远程登录并访问目的主机; |
ftp lftp |
可与远程主机间进行文件传送; |
tcpdump wireshark |
可捕获网络数据包,用于分析数据包流向甚至监听数据包内容; 其中tcpdump是命令接口式数据包分析软件,wireshark是图形接口数据包分析软件; |
上面的命令使用时可以直接查看命令帮助,如果只查看简略的命令参数信息,可以使用–help(或-h)获得简略命令帮助信息,如果想查看详细的帮助信息可以使用man,即在命令名前加man(全称manual使用手册的意思)。下面看看windows系统的常用网络命令:
windows常用网络命令 | 命令功能描述 |
---|---|
ipconfig | 查询网络接口信息,包括各网卡的MAC地址、IP地址/子网掩码/默认网关,甚至DHCP/DNS服务器地址等信息; |
netsh | Network Shell是一个 Windows 系统本身提供的网络配置命令行工具; |
arp | 查看或修改IP地址与MAC地址对的缓存表信息; |
route | 查看或修改目的IP地址、子网掩码、默认网关等路由状态信息; |
nslookup | 查看主机名与IP地址的对应关系信息; |
ping | 查看目的主机是否可以访问到,并可获知往返时间等信息; |
tracert | 跟踪源主机到目的主机所通过的各个路由节点信息; |
netstat | 查看网络传输层各端口的连接状态信息,比如目前有多少连接已建立或出现问题等; |
net | 可以查看我们的管理网络环境、服务、用户、登陆等信息内容; |
telnet | 可用于远程登录并访问目的主机; |
ftp | 可与远程主机间进行文件传送; |
wireshark | 可捕获网络数据包,用于分析数据包流向甚至监听数据包内容; |
上面的命令依然可以直接查看帮助信息获得所支持的参数及用法,在命令后加上"/?"即可获得该命令的帮助信息。也可以在命令名前加help查询该命令的用法,但这种方式支持的命令相对较少,如果想获得更强大的命令交互支持,可以使用powershell。
三、ICMP协议实现
总结下LwIP中实现了ICMP协议的哪些功能?在数据报处理过程中,根据差错情况的不同,能够发送两种类型的差错报文:目的站不可达差错报文和数据报超时差错报文。此外,LwIP能够响应一种查询报文,即回送请求报文,协议栈会根据收到的回送请求报文产生一个回送应答报文。
3.1 ICMPv4数据报描述
ICMP的数据报格式前面介绍过,其中的首部剩余字节在差错报文与查询报文中有些不同,两种报文的结构分别如下图示:
ICMP报文相比IP报文简单些,在LwIP中描述ICMP报文的数据结构如下:
// rt-thread\components\net\lwip-1.4.1\src\include\ipv4\lwip\icmp.h
#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 */
enum icmp_dur_type {
ICMP_DUR_NET = 0, /* net unreachable */
ICMP_DUR_HOST = 1, /* host unreachable */
ICMP_DUR_PROTO = 2, /* protocol unreachable */
ICMP_DUR_PORT = 3, /* port unreachable */
ICMP_DUR_FRAG = 4, /* fragmentation needed and DF set */
ICMP_DUR_SR = 5 /* source route failed */
};
enum icmp_te_type {
ICMP_TE_TTL = 0, /* time to live exceeded in transit */
ICMP_TE_FRAG = 1 /* fragment reassembly time exceeded */
};
PACK_STRUCT_BEGIN
struct icmp_echo_hdr {
PACK_STRUCT_FIELD(u8_t type);
PACK_STRUCT_FIELD(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
#define ICMPH_TYPE(hdr) ((hdr)->type)
#define ICMPH_CODE(hdr) ((hdr)->code)
/** Combines type and code to an u16_t */
#define ICMPH_TYPE_SET(hdr, t) ((hdr)->type = (t))
#define ICMPH_CODE_SET(hdr, c) ((hdr)->code = (c))
上面这些宏及数据结构的定义相对简单,前面的宏定义主要定义了ICMP的报文类型,接下来的枚举类型定义了目的不可达和数据报超时的报文代码。后面的结构体定义了ICMP回送报文首部(PACK_STRUCT_FIELD禁止编译器自对齐),这个结构体也可以拿来描述其他类型的首部;最后的宏定义分别用于查询、设置ICMP首部中的部分字段,其中宏变量hdr指向ICMP首部结构体指针。
3.2 ICMPv4数据报操作函数
在数据报不能递交给任何一个上层协议时,函数icmp_dest_unreach会被调用,以发送一个目的不可达ICMP差错报文给源主机,引起目的不可达的原因是协议不可达;在UDP层处理时还将看到如果UDP数据不能被递交给任何一个应用程序,函数icmp_dest_unreach也会被调用,这里引起目的不可达的具体原因是端口不可达。另一种差错报文是超时报文,发送超时报文的函数叫icmp_time_exceeded,在数据报转发和分片重装过程中,都可能调用该函数,引发超时的具体原因可能有两种:一种是数据报TTL为0;另一种是分片重装时间超时。下面来看看两种差错报文具体是怎么被发送的:
// rt-thread\components\net\lwip-1.4.1\src\core\ipv4\icmp.c
/* The amount of data from the original packet to return in a dest-unreachable */
#define ICMP_DEST_UNREACH_DATASIZE 8
/**
* Send an icmp 'destination unreachable' packet, called from ip_input() if
* the transport layer protocol is unknown and from udp_input() if the local
* port is not bound.
* @param p the input packet for which the 'unreachable' should be sent,
* p->payload pointing to the IP header
* @param t type of the 'unreachable' packet
*/
void icmp_dest_unreach(struct pbuf *p, enum icmp_dur_type t)
{
icmp_send_response(p, ICMP_DUR, t);
}
#if IP_FORWARD || IP_REASSEMBLY
/**
* Send a 'time exceeded' packet, called from ip_forward() if TTL is 0.
* @param p the input packet for which the 'time exceeded' should be sent,
* p->payload pointing to the IP header
* @param t type of the 'time exceeded' packet
*/
void icmp_time_exceeded(struct pbuf *p, enum icmp_te_type t)
{
icmp_send_response(p, ICMP_TE, t);
}
#endif /* IP_FORWARD || IP_REASSEMBLY */
/**
* 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;
ip_addr_t iphdr_src;
/* 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) {
return;
}
iphdr = (struct ip_hdr *)p->payload;
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);
/* calculate checksum */
icmphdr->chksum = 0;
icmphdr->chksum = inet_chksum(icmphdr, q->len);
ip_addr_copy(iphdr_src, iphdr->src);
ip_output(q, NULL, &iphdr_src, ICMP_TTL, 0, IP_PROTO_ICMP);
pbuf_free(q);
}
这里的重点是icmp_send_response函数,它为报文申请空间,然后根据报文类型和代码字段值填写数据,然后计算校验和,最后通过函数ip_output将数据报发送出去。
IP层收到ICMP报文会