歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

Linux內核分析 - 網絡[四補]:路由表補充

內核版本:2.6.34
      前篇路由表http://www.linuxidc.com/Linux/2011-05/36066.htm
說明了路由表的結構及路由表的創建。下面是一些路由表的使用的細枝末節,作補充說明。
      路由可以分為兩部分:路由緩存(rt_hash_table)和路由表()
      路由緩存顧名思義就是加速路由查找的,路由緩存的插入是由內核控制的,而非人為的插入,與之相對比的是路由表是人為插入的,而非內核插入的。在內核中,路由緩存組織成rt_hash_table的結構。

      下面是一段IP層協議的代碼段[net/ipv4/route.c],傳入IP層的協議在查找路由時先在路由緩存中查找,如果已存在,則skb_dst_set(skb, &rth->u.dst)並返回;否則在路由表中查詢。

  1. hash = rt_hash(daddr, saddr, iif, rt_genid(net));  
  2.   
  3. rcu_read_lock();  
  4. for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;  
  5.      rth = rcu_dereference(rth->u.dst.rt_next)) {  
  6.     if (((rth->fl.fl4_dst ^ daddr) |  
  7.          (rth->fl.fl4_src ^ saddr) |  
  8.          (rth->fl.iif ^ iif) |  
  9.          rth->fl.oif |  
  10.          (rth->fl.fl4_tos ^ tos)) == 0 &&  
  11.         rth->fl.mark == skb->mark &&  
  12.         net_eq(dev_net(rth->u.dst.dev), net) &&  
  13.         !rt_is_expired(rth)) {  
  14.         dst_use(&rth->u.dst, jiffies);  
  15.         RT_CACHE_STAT_INC(in_hit);  
  16.         rcu_read_unlock();  
  17.         skb_dst_set(skb, &rth->u.dst);  
  18.         return 0;  
  19.     }  
  20.     RT_CACHE_STAT_INC(in_hlist_search);  
  21. }  
  22. rcu_read_unlock();  

        在ip_route_input()中查詢完陸由緩存後會處理組播地址,如果是組播地址,則下面判斷會成功:ipv4_is_multicast(daddr)。
然後執行ip_route_input_mc(),它的主要作用就是生成路由緩存項rth,並插入緩存。rth的生成與初始化只給出了input函數的,其它略去了,可以看出組播報文會通過ip_local_deliver()繼續向上傳遞。

 
  1. rth->u.dst.input= ip_local_deliver;  
  2. hash = rt_hash(daddr, saddr, dev->ifindex, rt_genid(dev_net(dev)));  
  3. return rt_intern_hash(hash, rth, NULL, skb, dev->ifindex);  

      路由表又可以分為兩個:RT_TABLE_LOCAL和RT_TABLE_MAIN
        RT_TABLE_LOCAL存儲目的地址是本機的路由表項,這些目的地址就是為各個網卡配置的IP地址;
        RT_TABLE_MAIN存儲到其它主機的路由表項;
      顯然,RT_TABLE_MAIN路由表只有當主機作為路由器時才有作用,一般主機該表是空的,因為主機不具有轉發數據包的功能。RT_TABLE_LOCAL對主機就足夠了,為各個網卡配置的IP地址都會加入RT_TABLE_LOCAL中,如為eth1配置了1.2.3.4的地址,則RT_TABLE_LOCAL中會存在1.2.3.4的路由項。只有本地的網卡地址會被加入,比如lo、eth1。IP模塊在初始化時ip_init() -> ip_rt_init() - > ip_fib_init()會注冊notifier機制,當為網卡地址配置時會執行fib_netdev_notifier和fib_inetaddr_notifier,使更改反映到RT_TABLE_LOCAL中。

 
  1. register_netdevice_notifier(&fib_netdev_notifier);  
  2. register_inetaddr_notifier(&fib_inetaddr_notifier);  

          而當在路由緩存中沒有查找到緩存項時,會進行路由表查詢,還是以IP層協議中的代碼段為例[net/ipv4/route.c],fib_lookup()會在MAIN和LOCAL兩張表中進行查找。

  1. if ((err = fib_lookup(net, &fl, &res)) != 0) {  
  2.     if (!IN_DEV_FORWARD(in_dev))  
  3.         goto e_hostunreach;  
  4.     goto no_route;  
  5. }  

        如果主機配置成了支持轉發,則無論在路由表中找到與否,都會生成這次查詢的一個緩存,包括源IP、目的IP、接收的網卡,插入路由緩存中:

  1. hash = rt_hash(daddr, saddr, fl.iif, rt_genid(net));  
  2. err = rt_intern_hash(hash, rth, NULL, skb, fl.iif);  

      不同的是,如果在路由表中查詢失敗,即數據包不是發往本機,也不能被本機轉發,則會設置插入路由緩存的緩存項u.dst.input=ip_error,而u.dst.input即為IP層處理完後向上傳遞的函數,而ip_error()會丟棄數據包,被發送相應的ICMP錯誤報文。不在路由表中的路由項也要插入路由緩存,這可以看作路由學習功能,下次就可以直接在路由緩存中找到。

  1. rth->u.dst.input= ip_error;  
  2. rth->u.dst.error= -err;  
  3. rth->rt_flags    &= ~RTCF_LOCAL;  

      但如果主機不支持轉發,即沒有路由功能,則只有在找到時才會添加路由緩存項,都不會生成路由緩存項。這是因為在LOCAL表中沒有找到,表明數據包不是發往本機的,此時緩存這樣的路由項對於主機的數據包傳輸沒有一點意義。它只需要知道哪些數據包是發給它的,其余的一律不管!

       路由查詢整合起來,就是由ip_route_input()引入,然後依次進行路由緩存和路由表查詢,並對路由緩存進行更新。路由緩存在每個數據包到來時都可能發生更新,但路由表則不一樣,只能通過RTM機制更新,LOCAL表是在網卡配置時更新的,MAIN表則是由人工插入的(inet_rtm_newroute)。
       ip_route_input()
         - 路由緩存查詢
         - 路由表查詢:ip_route_input_slow() -> fib_lookup()

Copyright © Linux教程網 All Rights Reserved