內核版本:2.6.34
前篇路由表http://blog.csdn.net/qy532846454/article/details/6423496說明了路由表的結構及路由 表的創建。下面是一些路由表的使用的細枝末節,作補充說明。
路由可以分為兩部分:路由緩存(rt_hash_table)和路由表 ()
路由緩存顧名思義就是加速路由查找的,路由緩存的插入是由內核控制的,而非人為的插入,與之相對比的是路由表是人 為插入的,而非內核插入的。在內核中,路由緩存組織成rt_hash_table的結構。
下面是一段IP層協議的代碼段 [net/ipv4/route.c],傳入IP層的協議在查找路由時先在路由緩存中查找,如果已存在,則skb_dst_set(skb, &rth- >u.dst)並返回;否則在路由表中查詢。
[cpp] view plaincopy hash = rt_hash(daddr, saddr, iif, rt_genid(net)); rcu_read_lock(); for (rth = rcu_dereference(rt_hash_table[hash].chain); rth; rth = rcu_dereference(rth->u.dst.rt_next)) { if (((rth->fl.fl4_dst ^ daddr) | (rth->fl.fl4_src ^ saddr) | (rth->fl.iif ^ iif) | rth->fl.oif | (rth->fl.fl4_tos ^ tos)) == 0 && rth->fl.mark == skb->mark && net_eq(dev_net(rth->u.dst.dev), net) && !rt_is_expired(rth)) { dst_use(&rth->u.dst, jiffies); RT_CACHE_STAT_INC(in_hit); rcu_read_unlock(); skb_dst_set(skb, &rth->u.dst); return 0; } RT_CACHE_STAT_INC(in_hlist_search); } rcu_read_unlock();
在ip_route_input()中查詢完陸由緩存後會處理組播地址,如果是組播地址,則下面判斷會 成功:ipv4_is_multicast(daddr)。
然後執行ip_route_input_mc(),它的主要作用就是生成路由緩存項rth,並插入緩 存。rth的生成與初始化只給出了input函數的,其它略去了,可以看出組播報文會通過ip_local_deliver()繼續向上傳遞。
rth->u.dst.input= ip_local_deliver; hash = rt_hash(daddr, saddr, dev->ifindex, rt_genid(dev_net(dev))); 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中。
register_netdevice_notifier(&fib_netdev_notifier); register_inetaddr_notifier(&fib_inetaddr_notifier);
而當在路由緩存中沒有查找到緩存項時,會進行路由表 查詢,還是以IP層協議中的代碼段為例[net/ipv4/route.c],fib_lookup()會在MAIN和LOCAL兩張表中進行查找。
if ((err = fib_lookup(net, &fl, &res)) != 0) { if (!IN_DEV_FORWARD(in_dev)) goto e_hostunreach; goto no_route; }
如果主機配置成了支持轉發,則無論在路由表中找到與否,都會生成這次查詢的一個緩存,包括源IP、目的IP、接收 的網卡,插入路由緩存中:
hash = rt_hash(daddr, saddr, fl.iif, rt_genid(net)); err = rt_intern_hash(hash, rth, NULL, skb, fl.iif);
不同的是,如果在路由表中查詢失敗,即數據包不是發往本 機,也不能被本機轉發,則會設置插入路由緩存的緩存項u.dst.input=ip_error,而u.dst.input即為IP層處理完後向上傳遞的 函數,而ip_error()會丟棄數據包,被發送相應的ICMP錯誤報文。不在路由表中的路由項也要插入路由緩存,這可以看作路由學 習功能,下次就可以直接在路由緩存中找到。
rth->u.dst.input= ip_error; rth->u.dst.error= -err; rth->rt_flags &= ~RTCF_LOCAL;
但如果主機不支持轉發,即沒有路由功能,則只有在找到時才會添加路由 緩存項,都不會生成路由緩存項。這是因為在LOCAL表中沒有找到,表明數據包不是發往本機的,此時緩存這樣的路由項對於主 機的數據包傳輸沒有一點意義。它只需要知道哪些數據包是發給它的,其余的一律不管!
路由查詢整合起來,就是由 ip_route_input()引入,然後依次進行路由緩存和路由表查詢,並對路由緩存進行更新。路由緩存在每個數據包到來時都可能發 生更新,但路由表則不一樣,只能通過RTM機制更新,LOCAL表是在網卡配置時更新的,MAIN表則是由人工插入的 (inet_rtm_newroute)。
ip_route_input()
- 路由緩存查詢
- 路由表查詢:ip_route_input_slow() -> fib_lookup()