一。为什么需要ICMP?
因为IP协议不提供可靠性且不能保证信息传递,因此发生问题时,通知发送人是很重要的。(IP协议是一种不可靠的协议,无法进行差错控制。但IP协议可以借助其他协议来实现这一功能,如ICMP)
二。什么是ICMP?
ICMP: Internet Control Message Protocol 即Internet消息控制协议。ICMP报文分为两种类型(1)ICMP差错报告报文 (2)ICMP询问报文
ICMP差错报告报文共有5种
1、终点不可达:终点不可达分为:网络不可达,主机不可达,协议不可达,端口不可达,需要分片但DF比特已置为1,以及源路由失败等六种情况,其代码字段分别置为0至5。当出现以上六种情况时就向源站发送终点不可达报文。
说明:
端口不可达:UDP的规则之一是:如果收到UDP数据报而且目的端口与某个正在使用的进程不相符,那么UDP返回一个ICMP不可达报文。
2、 源站抑制:当路由器或主机由于拥塞而丢弃数据报时,就向源站发送源站抑制报文,使源站知道应当将数据报的发送速率放慢。
3、 时间超过:当路由器收到生存时间为零的数据报时,除丢弃该数据报外,还要向源站发送时间超过报文。当目的站在预先规定的时间内不能收到一个数据报的全部数据报片时,就将已收到的数据报片都丢弃,并向源站发送时间超过报文。
4、参数问题:当路由器或目的主机收到的数据报的首部中的字段的值不正确时,就丢弃该数据报,并向源站发送参数问题报文。
5、改变路由(重定向)路由器将改变路由报文发送给主机,让主机知道下次应将数据报发送给另外的路由器。
ICMP定义了一套差错报文和控制报文,用于用户主机与路由之间交换不可到达地址、网络拥塞、重定向到更好的路径、报文生命周期超时等信息。
ICMP协议是一种提供(有关阻止数据包传递的)网络故障问题反馈信息的机制。(针对阻止数据包传递或者网络故障。)它让TCP等上层协议能够意识到数据包没有送达目的地。他的主要功能:
支持网络管理PING命令
报告一个特定的目的地不可到达
请求一个发送主机减少它的数据流量,以缓解网络拥塞
报告配置和路由的变化
报告一个特定的数据报中IP报头参数的问题
因超时而丢弃一个数据报
CMP差错报文
差错报告作为ICMP最初始的功能,具有两大特点:
第一,ICMP作为差错报文传输机制,最根本的功能是提供差错报告,但ICMP并不严格规定对 某种差错应采取什么处理方式。
第二,ICMP的差错报告都是网关——源机模式的,因为:首先,IP数据报只包含信源地址 和信宿地址(除非选用“记录路径”选项),一旦发生错误,差错情况要么报告信源机, 要么报告信宿机,但报告信宿机首先没有意义(因为传输与信宿机毫无关系),其次也许 根本做不到(因为传输错误可能使信宿机不可到达),于是,差错信息只能传给信源机。(这句话的意思就是,网络层提供的是点对点的服务,就是一个路由给另一个路由发送数据的时候出现错误,这个使用ICMP的差错报告都是网关,都是发送端的网关!它只是报告,并不能采取什么措施)
关于ICMP与TCP的差错控制对比? 比如主机A到主机B的通信,中间Router r1 到Router r2 的网络连接断了。 通过ICMP,则可以快速诊断出出错原因,并且报告主机。(差错诊断) TCP的差错控制,主要是体现在,网络断了,收不到确认回复,TCP会一直再次发送数据包,直到收到确认回复。(差错处理) |
ICMP提供一组易懂的出错报告信息。发送的出错报文返回到发送原数据的设备,因为只有发送设备才是出错报文的逻辑接受者。发送设备随后可根据ICMP报文确定发生错误的类型,确定如何才能更好地重发失败的数据报。但是ICMP唯一的功能是报告问题而不是纠正错误,纠正错误的任务由发送方完成。
三。ICMP协议有哪些数据包?
一个ICMP数据包实际上就是一个(数据部分为ICMP协议数据的)IP数据包。
IP头 | ICMP头
| ICMP数据 |
如前所述,ICMP主要分为差错报文和控制报文。
差错报文包括:目标不可到达(网络、协议、主机、端口不可到达;禁止分割、目标网络不认识、目标主机不认识等等)、超时、参数问题、重定向(网络重定向、主机重定向等)等等
控制报文包括:请求回显(ping请求)、回显应答(ping应答)、地址掩码请求、地址掩码应答等等
如上,我们可以发现,同一类型的错误(不可到达)可能有不同种类(网络不可到达、主机不可到达),因此,我们使用type来code两个标志来确定一个具体的错误。
因为需要指出具体是哪个主机上的哪个程序发出的信息没有到达。
因此,每一个ICMP的错误消息,应该包含:
1.具体的错误类型(Type/code 决定)
2.引发ICMP错误消息的数据包的完全IP包头(哪个主机的数据)
3.数据报的前8个字节----UDP报头或者TCP中的port部分(主机上的哪个程序)
因此,ICMP错误消息的格式应该为如下:
Type(8) | Code(8) | Checksum(16) |
Unused(32) | ||
Internet Header +64 Bits of Original Data Datagram |
Type | Code | Checksum |
Identifier | Sequence Number | |
Data |
当主机A需要知道和主机B通信的状况(信息传递延时、丢包率)时,我们该怎么办呢?
我们可以参考声纳和雷达的原理:主机A发送一个ICMP回显请求(type=8,code=0)报文,数据域中存放当前时间T1,目的IP为主机B。主机B收到该ICMP回显请求报文后,将目的IP和源IP调换位置,其他信息都不变(Indentifier,SequenceNumber),回复一个ICMP回显应答(type=0,code=0)。主机A收到改ICMP回显应答的时间为T2。则,到主机B的通信时间为:T2-T1。 又因为,要考虑丢包,所以我们向主机B发送多个回显请求,用Sequence Number来区分各个请求,根据Sequence Number,即可知道应答对应的请求数据包。
Ping命令就是这样的一个实现,其实我们在命令行下输入ping ip命令时,就是调用Ping程序。Ping程序根据输入的IP(域名)封装ICMP请求应答,发送出去,并且接受ICMP回显应答,进行解析,输出。 |
关于超时:IP报头中的生存期(TTL)属性,用来控制报文段在网络中的生存期。
试想一下,当网络中的某些路由出现问题,Router A将IP a 的下一跳路由为Router B。同时在Router B中,又将IP a 的下一跳路由为 Router A。那么显然,两个路由器之将会间形成回路,通往网段a的数据包,将会一直在两个路由之间发送,导致网络流量爆炸,同时,数据包也无法正确的到达网络a。
因此,当IP数据包每经过一个路由器时,路由器将IP数据包中的TTL值减一。当TTL值为0时,路由器判断数据包超时,发送ICMP超时信息给源主机。 |
当我们想知道:从主机A发送到主机B的数据包在网络中都经过了哪些路由器的时候,我们有什么办法呢?
我们知道,当IP数据包在路由中出错时,路由器会向发送源发送一个ICMP错误报文,发送端从该ICMP错误报文中,可以得到该路由的IP。
我们可以利用此原理。要得到从主机A到目标主机B之间的所有路由的IP,那么我们必须让IP数据包在每个路由器中都出错一次。
又因此,TTL值在经过的每个路由器中都会减1。因此,我们可以利用TTL的超时信息,在每个路由中都发生一次。即可得到从从主机A到主机B之间的所有路由的IP。
PS.怎么判断数据包正确到达了目标主机B?
当IP数据包到达了目标主机,将不会再发送TTL超时错误。而且在目标主机B中,没有运行相对应的应用层程序,因此,将没有程序会回应我们发送的IP数据包。那么,我们将如何知道IP数据包已经到达了目标主机呢?
我们可以为数据包分配一个目标主机几乎不可能监听的端口,从而,当IP数据包到达目标主机后,目标主机会回复相应的”不可到达、端口不可到达”的ICMP错误信息,从而,我们可以确认IP数据包已经到达了目标主机。
综上,我们可以:源主机A发送IP数据包,IP为目标主机B,port几乎不可能监听的port,TTL从一开始一直往上增加,直道收到来自主机B的ICMP 不可到达(端口不可到达)信息。
Tracerouter 命令就是实现这样功能的一个程序。我们可以通过tracerouter ip来调用此功能。
PS.因为每次路由时的路由路径可能不一样,那么在tracerouter过程中,我们又如何解决这个问题?
而且,如果目标主机正好监听了这一我们认为不可能监听的端口呢?
1. 回送或回送响应
我们使用一个ICMPECHO数据包来探测主机地址是否存活(当然在主机没有被配置为过滤ICMP形式),通过简单的发送一个ICMPECHO(Type 8)数据包到目标主机,如果ICMPECHOReply(ICMPtype0)数据包接受到,说明主机是存活状态。 如果没有就可以初步判断主机没有在线或者使用了某些过滤设备过滤了ICMP的REPLY。这种机制就是我们通常所用的ping命令来检测目标主机是否可以ping到.
回送消息的源地址是回送响应消息的目的地址。若要形成一个回送响应消息,应该将源和目的地址交换,将类型代码更改为0,重新计算机校验码。
下面是这个报文的格式:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data ...
+-+-+-+-+-
类型:
8代表回送消息;
0代表回送响应消息。
代码:0
校验码:
16位数据(从ICMP类型开始)的反码和再取反而得。为计算校验码,校验码域应该为零。这些零在 以后会被校验码取代。
标识符:如果代码=0,帮助匹配回送和回送响应的代码可以为0。
序列码:如果代码=0,帮助匹配回送和回送响应的序列码可以为0。
说明:
回送消息中接收到的消息应该在回送响应消息中返回。标识符和序列码由回送发送者使用帮助匹配
回送请求的响应。代码: 从主机或网关接收0
2. 超时报文
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unused |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Internet Header + 64 bits of Original Data Datagram |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
类型:11
代码:
0 = 传送超时;
1 = 分段级装超时。
校验码:
16位数据(从ICMP类型开始)的反码和再取反而得。为计算校验码,校验码域应该为零。
这些零在以后会被校验码取代。
Internet包头+64位源数据报数据:
Internet包头加上源数据的头64位而得。此数据用于主机匹配信息到相应的进程。
如果高层协议使用端口号,应该假设其在源数据的头64个字节之中。
说明:
如果网关在处理数据报时发现生存周期域为零,此数据报必须抛弃。网关同时必须通过超
时信息通知源主机。如果主机在组装分段的数据报时因为丢失段未能在规定时间内组装数据,
此数据报必须抛弃。网关发送超时信息。
如果段零不可用则不用发送超时信息。
代码0由网关发送,代码1由主机发送。
3. 目标主机不可达报文
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unused |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Internet Header + 64 bits of Original Data Datagram |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
类型:3
代码:
0 = 网络不可达;
1 = 主机不可达;
2 = 协议不可用;
3 = 端口不可达;
4 = 需要段和DF设置;
5 = 源路由失败;
校验码:
16位数据(从ICMP类型开始)的反码和再取反而得。为计算校验码,校验码域应该为零。
这些零在以后会被校验码取代。
Internet包头+源数据报:
Internet包头加上源数据的头64位而得。此数据用于主机匹配信息到相应的进程。
如果高层协议使用端口号,应该假设其在源数据的头64个字节之中。
说明:
相应于网关的路由表,如果在目的域中指定的网络不可达,如网络距离为无限远,网关会向发送
源数据的主机发送目的不可达消息。而且,在一些网络中,网关有能力决定目的主机是否可达。
如果目的地不可达,它将向发送源数据的主机发送不可达信息。
在目的主机,如果IP模块因为指定的协议模块和进程端口不可用而不能提交数据报,目的主机将
向发送源数据的主机发送不可达信息。
另外一种情况是当数据报必须被分段传送,而“不可分段”位打开,在这种情况下,网关必须抛弃
此数据报,并向向发送源数据的主机发送不可达信息。
代码0,1,4和5由网关发送,而代码2和3由主机发送。
ICMP差错报文的格式
不可达,超时,源泉抑制
type | len | cksum | Void(必须是0) | 被破坏分组的IP首部 |
需要分片
type | len | cksum | Pmvoid(必须是0) | nextmtu | 被破坏分组的IP首部 |
参数问题
type | len | cksum | pptr | (必须是0) | 被破坏分组的IP首部 |
信宿不可达报文类型表
代码值 | 类型描述 |
| 代码值 | 类型描述 |
0 | 网络不可达 |
| 7 | 信宿主机未知 |
1 | 主机不可达 |
| 8 | 信源主机被隔离 |
2 | 协议不可达 |
| 9 | 与信宿网络的通信被禁止 |
3 | 端口不可达 |
| 10 | 与信宿主机的通信杯禁止 |
4 | 需要分片和DF设置 |
| 11 | 对请求的服务类型,网络不可达 |
5 | 源路由失败 |
| 12 | 对请求的服务类型,主机不可达 |
6 | 信宿网络未知 |
|
|
|
/* Handle ICMP_UNREACH and ICMP_QUENCH. */处理ICMP_UNREACH和ICMP_QUENCH。//处理不可达和源终止//
static void
icmp_unreach(struct icmphdr *icmph, struct sk_buff *skb);//调用icmp_unreach函数类型3
{
struct inet_protocol *ipprot;
struct iphdr *iph; //定义IP头部
unsigned char hash;
int err;//定义变量
err = (icmph->type << 8) | icmph->code;// ICMP包类型小于8,响应请求
iph = (struct iphdr *) (icmph + 1);//IP的头标位ICMP的头标加1
switch(icmph->code & 7) //将icmph指向代码(为8位)并与7与后进行判断,得到不可达类型的代号
{
case ICMP_NET_UNREACH://当网络不可达时
DPRINTF((DBG_ICMP, "ICMP: %s: network unreachable.\n",
in_ntoa(iph->daddr)));//输出网络不可访问的目的IP地址
break;
case ICMP_HOST_UNREACH://当主机不可达时
DPRINTF((DBG_ICMP, "ICMP: %s: host unreachable.\n",
in_ntoa(iph->daddr))); //输出主机不可访问 目的IP地址
break;
break;
case ICMP_PROT_UNREACH://当端口不可达时
printk("ICMP: %s:%d: protocol unreachable.\n",
in_ntoa(iph->daddr), ntohs(iph->protocol));
//输出ICMP协议不可达 目的IP地址 协议首地址
break;
case ICMP_PORT_UNREACH:
DPRINTF((DBG_ICMP, "ICMP: %s:%d: port unreachable.\n",
in_ntoa(iph->daddr), -1 /* FIXME: ntohs(iph->port) */));
break;
case ICMP_FRAG_NEEDED://当需要分片时和DF设置时
printk("ICMP: %s: fragmentation needed and DF set.\n",
in_ntoa(iph->daddr));//输出碎片需要和DF设置目的IP地址
break;
case ICMP_SR_FAILED://当源路由失败时
printk("ICMP: %s: Source Route Failed.\n", in_ntoa(iph->daddr));
break;//目的IP地址 源路由失败
default:
DPRINTF((DBG_ICMP, "ICMP: Unreachable: CODE=%d from %s\n",
(icmph->code & 7), in_ntoa(iph->daddr)));
break;
}
//若不是上述情况 则输出DBG_ICMP icmp不可达 code的值 目的ip地址
/* Get the protocol(s). *///获取通讯协定
hash = iph->protocol & (MAX_INET_PROTOS -1);
//hash等于iph->protocol并(MAX_INET_PROTOS -1)
/* This can change while we are doing it. */
ipprot = (struct inet_protocol *) inet_protos[hash];//如果找到相关的协议
while(ipprot != NULL) {
struct inet_protocol *nextip;//到路由的下一跳
nextip = (struct inet_protocol *) ipprot->next;// ipprot等于inet的协议 inet_protos[hash],ipprot不为空,ipprot等于 inet的协议 *,ipprot指向下一个值
/* Pass it off to everyone who wants it. */
if (iph->protocol == ipprot->protocol && ipprot->err_handler) {
ipprot->err_handler(err, (unsigned char *)(icmph + 1),
iph->daddr, iph->saddr, ipprot);
}//发送给目的端口
ipprot = nextip;
}
skb->sk = NULL;
kfree_skb(skb, FREE_READ);//查看接收缓存还够不够
}
功能:处理ICMP_DEST_UNREACH, ICMP_TIME_EXCEED, and ICMP_QUENCH.
1.长度小于IP头结构长度,则转入FLUSHIT(6)
2.长度小于ihl*4,则转入FLUSHIT(6)
3.处理ICMP-DEST-UNREACH:CODE(低4个bit)为
A.NET、HOST-UNREACH:转入4
B.PROT-UNREACH:调用NETDEBUG(protocol unreachable),其它同C
C.PORT-UNREACH:match_addr=1,其它同A
D.FRAG-NEEDED:没有PMTU-Discovery,则同B(参数不同,为fragmentation needed and DF set.)其它同A;否则,①skb结构对应的路由表的MTU小于等于IP头的TTL,发NETDEBUG(指明基于4.2BSD的路由),然后将MTU置为TTL-4*IHL②若新估MTU小于68或大于旧MTU,则据RFC1191重新定新的MTU③将新MTU放入IP头的ID域,然后动作同A。
E.SR-FAILED:NETDEBUG(源路由失败)其它同A
F.其它情况同A
4.CODE码大于NR-ICMP-UREACH(为非法类型),转入FLUSHIT
5.若match_addr为0或入参saddr等于IP包头的目标地址,据IP头的协议号找到协议哈希表的对应表,遍历这个表,若协议相同且差错处理不为空,则调用差错处理,否则转FLUSHIT:以FR入6。
6.EE_READ为参数释放skb结构空间。
小节:这段代码主要的功能是查看在发送数据包源端口到目的端口这个路程中有没有出现异常,出现异常则打印其类型,否则进行路由的转发。