linux下ip協議(V4)的實現(一)

首先來看校驗相關的一些結構: 

1 net_device結構: 

包含一個features的域,這個表示設備的一些特性(比如控制校驗),下面的幾個flag就是用來控制校驗: 

Java代碼  收藏代碼
  1. #define NETIF_F_IP_CSUM     2   /* Can checksum TCP/UDP over IPv4. */  
  2. #define NETIF_F_NO_CSUM     4   /* Does not require checksum. F.e. loopack. */  
  3. #define NETIF_F_HW_CSUM     8   /* Can checksum all the packets. */  
  4. #define NETIF_F_IPV6_CSUM   16  /* Can checksum TCP/UDP over IPV6 */  


每個flags的介紹,註釋裏面都寫得很清楚,這裏就不一一解釋了。這裏要注意的是NETIF_F_HW_CSUM,他其實表示在硬件上爲所有協議校驗。 

2 sk_buff: 

skb->csum和skb->ip_summed這兩個域也是與校驗相關的,這兩個域的含義依賴於skb表示的是一個輸入包還是一個輸出幀。 

當數據包是一個輸入包時,skb->csum表示的是當前數據包的4層的checksum值,skb->ip_summed表示的是四層校驗的狀態,下面的幾個宏定義表示了設備驅動傳遞給4層的一些信息(通過ip_sumed),這裏要注意,一旦當四層接受了這個包,他可能會改變ip_summed的值。 

Java代碼  收藏代碼
  1. /* Don't change this without changing skb_csum_unnecessary! */  
  2. #define CHECKSUM_NONE 0  
  3. #define CHECKSUM_UNNECESSARY 1  
  4. #define CHECKSUM_COMPLETE 2  


CHECKSUM_NONE表示csum域中的校驗值是錯誤的,也就是校驗失敗。這裏要注意的是,一般來說當2層的校驗失敗後,驅動會直接丟掉這個包,可是如果輸入幀是要被forward的,那麼路由器不應該由於一個四層的校驗失敗而丟掉這個包(路由器不建議查看四層的校驗值),它將會將這位置爲CHECKSUM_NONE,然後將包發向目的地址,交由目的地址的主機來進行處理。 

CHECKSUM_UNNECESSARY表示網卡已經計算和驗證了四層的頭和校驗值。也就是計算了tcp udp的僞頭。還有一種情況就是迴環,因爲在迴環中錯誤發生的概率太低了,因此就不需要計算校驗來節省cpu事件。 

CHECKSUM_COMPLETE表示nic已經計算了4層頭的校驗,並且csum已經被賦值,此時4層的接收者只需要加僞頭並驗證校驗結果。 

接下來我們來看當數據包是輸出包時的情況,此時csum表示爲一個指針,它表示硬件網卡存放將要計算的校驗值的地址。這個域在輸出包時使用,只在校驗值在硬件計算的情況下。比如NAT,它會修改ip頭,此時就需要重新計算4層的校驗值,也就是從4層傳遞下來的4層校驗值需要在底層進行修改。當修改後,我們在底層就可以通過csum來存取這個校驗值。 

而此時ip_summed可以被設置的值有下面兩種: 
Java代碼  收藏代碼
  1. #define CHECKSUM_NONE 0  
  2. #define CHECKSUM_COMPLETE 2  


這時含義就完全不一樣了。第一個表示已經計算好了校驗值,設備不需要做任何事。 

第二個表示4層的僞頭的校驗已經完畢,並且已經加入到ip頭中,此時只需要設備計算整個頭4層頭的校驗值。 


主要來看一下ip輸入數據包的處理,也就是ip協議處理函數。 

具體的協議註冊什麼的,可以看我前面的blog,這裏我們知道處理ip輸入的函數是ip_rcv. 

先來看下當執行ip_rcv執行之前,sk_buff的結構: 


 

Java代碼  收藏代碼
  1. int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)  
  2. {  
  3.     struct iphdr *iph;  
  4.     u32 len;  
  5.   
  6.     ///我們知道當爲PACKET_OTHERHOST是,2層就會直接丟掉所有的包,可是如果網卡被設置爲混雜模式,此時包就會傳遞到3層,這個時侯內核會有hook來處理這個,而我們這裏就只需要直接丟掉所有的包。  
  7.     if (skb->pkt_type == PACKET_OTHERHOST)  
  8.         goto drop;  
  9.   
  10.     IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INRECEIVES);  
  11.   
  12. ///檢測這個數據包是否被內核其他部分使用,也就是監測引用計數。如果有被其他部分使用,則直接複製一份副本,然後返回。  
  13.     if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {  
  14.         IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);  
  15.         goto out;  
  16.     }  
  17.   
  18. //檢測skb->data的數據至少要和ip頭大小一樣。(這個原因很簡單,每個包都必須包含一個ip頭,如果比ip頭還小,說明包頭有錯誤了。  
  19.     if (!pskb_may_pull(skb, sizeof(struct iphdr)))  
  20.         goto inhdr_error;  
  21. ///取出ip頭  
  22.     iph = ip_hdr(skb);  
  23.   
  24.     /* 
  25.      *  RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum. 
  26.      * 
  27.      *  Is the datagram acceptable? 
  28.      * 
  29.      *  1.  Length at least the size of an ip header 
  30.      *  2.  Version of 4 
  31.      *  3.  Checksums correctly. [Speed optimisation for later, skip loopback checksums] 
  32.      *  4.  Doesn't have a bogus length 
  33.      */  
  34.   
  35. ///ip頭的ihl域表示ip頭的大小(就是也就是IP層頭部包含多少個32位),version表示ip協議版本,這裏第一個檢測的原因是基本ip頭的大小是20個字節,也就是最小爲20個字節,20*8/32=5,所以最小必須是5。而這裏版本,由於這個只處理ipv4,因此version必須是4.  
  36.     if (iph->ihl < 5 || iph->version != 4)  
  37.         goto inhdr_error;  
  38.   
  39. ///這次來檢測整個ip頭的大小(包括option)和skb->data.這個檢測到這裏才執行,是因爲,必須首先確定ip頭的基本正確。  
  40.     if (!pskb_may_pull(skb, iph->ihl*4))  
  41.         goto inhdr_error;  
  42.   
  43.     iph = ip_hdr(skb);  
  44.   
  45. ///開始校驗ip頭,也就是開始三層校驗。  
  46.     if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))  
  47.         goto inhdr_error;  
  48. ///取出整個ip頭的長度(包括option)  
  49.     len = ntohs(iph->tot_len);  
  50. ///接下來的檢測是因爲在2層由於要滿足最小幀的大小,因此可能會填充一些空數據,而三層ip頭計算長度時,會忽略這些空數據,因此這裏的skb->len一定是大於或等於len  
  51.     if (skb->len < len) {  
  52.         IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);  
  53.         goto drop;  
  54.     } else if (len < (iph->ihl*4))   
  55. ///這個判斷是因爲ip頭不能被切包,也就是每個切好的包必須至少包含一個ip頭。  
  56.         goto inhdr_error;  
  57.   
  58.     /* Our transport medium may have padded the buffer out. Now we know it 
  59.      * is IP we can trim to the true length of the frame. 
  60.      * Note this now means skb->len holds ntohs(iph->tot_len). 
  61.      */  
  62. ///這裏也就是我們上面說的情況,需要把skb->len和len統一起來(去除掉空數據)  
  63.     if (pskb_trim_rcsum(skb, len)) {  
  64.         IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);  
  65.         goto drop;  
  66.     }  
  67.   
  68.     /* Remove any debris in the socket control block */  
  69.     memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));  
  70.   
  71. ///調用net filter hook。  
  72.     return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,  
  73.                ip_rcv_finish);  
  74.   
  75. inhdr_error:  
  76.     IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);  
  77. drop:  
  78.     kfree_skb(skb);  
  79. out:  
  80.     return NET_RX_DROP;  
  81. }  


我們這裏先不詳細介紹net filter,這裏我們只需要知道,在NF_HOOK中,會檢測每個包(通過用戶空間設置的規則)然後來決定要不要這個數據包通過。最後如果允許的話,就會調用 ip_rcv_finish函數。所以這裏我們詳細看下 ip_rcv_finish函數: 

它主要會做兩件事: 

1 決定這個包是被傳遞給高層,還是被forward。 

2 解析並執行一些ip option。 

Java代碼  收藏代碼
  1. static int ip_rcv_finish(struct sk_buff *skb)  
  2. {  
  3.     const struct iphdr *iph = ip_hdr(skb);  
  4.     struct rtable *rt;  
  5.   
  6.     /* 
  7.      *  Initialise the virtual path cache for the packet. It describes 
  8.      *  how the packet travels inside Linux networking. 
  9.      */  
  10.   
  11. ///查找路由表的相關操作。  
  12.     if (skb->dst == NULL) {  
  13. ///查找路由。這裏也會初始化skb->dst->input。  
  14.     int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,  
  15.                      skb->dev);  
  16.         if (unlikely(err)) {  
  17.             if (err == -EHOSTUNREACH)  
  18.                 IP_INC_STATS_BH(dev_net(skb->dev),  
  19.                         IPSTATS_MIB_INADDRERRORS);  
  20.             else if (err == -ENETUNREACH)  
  21.                 IP_INC_STATS_BH(dev_net(skb->dev),  
  22.                         IPSTATS_MIB_INNOROUTES);  
  23.             goto drop;  
  24.         }  
  25.     }  
  26.   
  27. ///QOS的相關操作.  
  28. #ifdef CONFIG_NET_CLS_ROUTE  
  29.     if (unlikely(skb->dst->tclassid)) {  
  30.         struct ip_rt_acct *st = per_cpu_ptr(ip_rt_acct, smp_processor_id());  
  31.         u32 idx = skb->dst->tclassid;  
  32.         st[idx&0xFF].o_packets++;  
  33.         st[idx&0xFF].o_bytes+=skb->len;  
  34.         st[(idx>>16)&0xFF].i_packets++;  
  35.         st[(idx>>16)&0xFF].i_bytes+=skb->len;  
  36.     }  
  37. #endif  
  38.   
  39.   
  40. ///當ihl比5大,意味着有option。因此調用ip_rcv_options來進行解析和執行。  
  41.     if (iph->ihl > 5 && ip_rcv_options(skb))  
  42.         goto drop;  
  43.     rt = skb->rtable;  
  44.     if (rt->rt_type == RTN_MULTICAST)  
  45.         IP_INC_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INMCASTPKTS);  
  46.     else if (rt->rt_type == RTN_BROADCAST)  
  47.         IP_INC_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INBCASTPKTS);  
  48.   
  49. ///最後調用skb->dst->input,而這個虛函數的的值,首先是在ip_route_input中賦值,然後在 ip_rcv_options也有可能被修改。這個虛函數要麼被ip_local_deliver(也就是直接發向高層),要麼是ip_forward(直接被forward).這兩個函數以後會詳細介紹。  
  50.     return dst_input(skb);  
  51.   
  52. drop:  
  53.     kfree_skb(skb);  
  54.     return NET_RX_DROP;  
  55. }  
發佈了7 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章