随笔-88  评论-224  文章-22  trackbacks-0
   ICMP在IP系统间传递差错和管理报文,是任何IP系统必须实现的组成部分。Linux 2.6.34中ICMP模块的实现在linux/icmp.h,net/icmp.h和ipv4/icmp.c中,导出了icmp_err_convert数组和icmp_send函数,供其它网络子系统使用。在其它网络子系统中,当检测到错误时,调用icmp_send产生并发送相应的ICMP差错消息到源主机;当源主机收到ICMP不可达差错消息,传递到原始套接字和传输层,而它们使用icmp_err_convert把对应的消息代码转换成套接字层比较容易理解的错误代码。在内核空间中可发送的ICMP消息包括查询应答和差错报文,下面总结了产生这两类消息的网络子系统(及函数)与错误转换。


应答消息
   应答消息由ICMP模块的内部函数icmp_reply而非icmp_send发送。根据RFC1122 3.2.2.9规范, 除非一个主机作为地址掩码代理,否则不能发送回复,这对应ICMP的icmp_address实现为空,因此上表没有列出地址掩码应答项(内核符号为ICMP_ADDRESSREPLY)。

差错消息
   差错消息由中间路由器或目的主机产生,当数据报不能成功提交给目的主机时。从上表可见,在IP层的接收、本地处理、转发和输出各过程中,都可能产生差错消息;在传输层如果对应的端口没有打开,那么UDP会产生ICMP端口不可达差错,而TCP则会使用自己的差错处理机制发送一个RST复位包,这也是上表没有列出TCP子系统的原因。对于重定向差错,由ICMP模块的icmp_redirect调用ip_rt_redirect更新路由;其它差错则由icmp_unreach处理。


错误转换
   第2列为icmp_err_convert数组索引,第4列也就是调用socket API出错时返回的errno,最后1列为icmp_err_convert中的fatal成员取值,0表示非致命错误,1表示致命错误,需要报告给用户进程。错误转换会被RAW的raw_err、TCP的tcp_v4_err和UDP的udp_err用到,对于ICMP_DEST_UNREACH类型的差错,使用上表转换;ICMP_SOURCE_QUENCH类型的忽略不处理;ICMP_PARAMETERPROB类型的转换成EPROTO(协议错误);ICMP_TIME_EXCEEDED类型的转换成EHOSTUNREACH
   在这要注意,从ICMP_PORT_UNREACH到ECONNREFUSED的转换,不适用于TCP,原因已在上节说明;而对于UDP的未连接套接字,如果主机在线而端口没打开,调用sendto得不到ECONNREFUSED错误,但recvfrom会阻塞,这是因为虽然内核收到了ICMP差错,但没上报给应用进程。尽管如此,如果想得到ECONNREFUSED错误,那么可以写个ICMP守护进程,应用进程先把它的套接字描述符通过unix域套接口传递到ICMP守护进程,而守护进程使用raw socket来接收ICMP差错,再发给应用进程。


发送限速
   不论一般差错消息还是重定向差错消息,发送限速针对的都是特定目标主机。
   一般限速
   在使用icmp_send发送差错消息(PMTU消息除外)时,为减少网络拥塞而限制了发送的速率,限速由xrlim_allow函数实现,定义在ipv4/icmp.c中。
 1#define XRLIM_BURST_FACTOR 6
 2int xrlim_allow(struct dst_entry *dst, int timeout)
 3{
 4    unsigned long now, token = dst->rate_tokens;
 5    int rc = 0;
 6
 7    now = jiffies;
 8    token += now - dst->rate_last;
 9    dst->rate_last = now;
10    if (token > XRLIM_BURST_FACTOR * timeout)
11        token = XRLIM_BURST_FACTOR * timeout;
12    if (token >= timeout) {
13        token -= timeout;
14        rc = 1;
15    }

16    dst->rate_tokens = token;
17    return rc;
18}
   dst为目标路由缓存,timeout为允许发送的超时(单位为jiffies),dst->rate_tokens记录令牌的个数,当令牌个数不小于timeout时,则减少timeout并允许发送一个消息;反之则不能发送,需等到令牌个数累积到大于timeout时才能发送,但是不能无限大,否则就会导致在一个可能很短的timeout内,发送远多于6个的消息,引起ICMP风暴,所以这里限制了令牌的最大值为XRLIM_BURST_FACTOR*timeout即6倍的超时,也就是说在一个timeout内,最多能发送6个差错消息。 
  
   重定向限速
   路由子系统使用ip_rt_send_redirect来发送重定向消息,定义在ipv4/route.c中,该函数内部调用icmp_send实现,在它的限速基础上,使用指数回退算法控制发送速率。
 1void ip_rt_send_redirect(struct sk_buff *skb)
 2{
 3    struct rtable *rt = skb_rtable(skb);
 4    
   
 5
 6    /* No redirected packets during ip_rt_redirect_silence;
 7     * reset the algorithm.
 8     */

 9    if (time_after(jiffies, rt->u.dst.rate_last + ip_rt_redirect_silence))
10        rt->u.dst.rate_tokens = 0;
11
12    /* Too many ignored redirects; do not send anything
13     * set u.dst.rate_last to the last seen redirected packet.
14     */

15    if (rt->u.dst.rate_tokens >= ip_rt_redirect_number{
16        rt->u.dst.rate_last = jiffies;
17        return;
18    }

19
20    /* Check for load limit; set rate_last to the latest sent
21     * redirect.
22     */

23    if (rt->u.dst.rate_tokens == 0 || time_after(jiffies, (rt->u.dst.rate_last (ip_rt_redirect_load << rt->u.dst.rate_tokens)))) {
24        icmp_send(skb, ICMP_REDIRECT, ICMP_REDIR_HOST, rt->rt_gateway);
25        rt->u.dst.rate_last = jiffies;
26        ++rt->u.dst.rate_tokens;
27        
28    }

29}
   重定向差错使用ip_rt_redirect_silence(默认为(HZ/50)<<10)、ip_rt_redirect_number(默认为9)和ip_rt_redirect_load(默认为HZ/50)3个量来控制发送的速率;rt->u.dst.rate_last记录上次发送的时间,rt->u.dst.rate_tokens累计发送总数,最大值为ip_rt_redirect_number;当两次发送的时间间隔超过ip_rt_redirect_silence或ip_rt_redirect_load<<rt->u.dst.rate_tokens,并且发送总数不超过ip_rt_redirect_number时,才允许发送一个,这样一来,在ip_rt_redirect_silence间隔内,每次发送的超时呈2的指数增长,达到了变减速发送的效果,直到总数达到ip_rt_redirect_number时停止发送,这是因为源主机可能忽略了重定向消息所以停止发送;当ip_rt_redirect_silence时间过后,又允许发送了,这是因为认为源主机没有更新路由所以又需要发送。
posted on 2015-05-18 19:52 春秋十二月 阅读(1154) 评论(0)  编辑 收藏 引用 所属分类: Network

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理