1 net_device結構:
包含一個features的域,這個表示設備的一些特性(比如控制校驗),下面的幾個flag就是用來控制校驗:
- #define NETIF_F_IP_CSUM 2 /* Can checksum TCP/UDP over IPv4. */
- #define NETIF_F_NO_CSUM 4 /* Does not require checksum. F.e. loopack. */
- #define NETIF_F_HW_CSUM 8 /* Can checksum all the packets. */
- #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的值。
- /* Don't change this without changing skb_csum_unnecessary! */
- #define CHECKSUM_NONE 0
- #define CHECKSUM_UNNECESSARY 1
- #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可以被設置的值有下面兩種:
- #define CHECKSUM_NONE 0
- #define CHECKSUM_COMPLETE 2
這時含義就完全不一樣了。第一個表示已經計算好了校驗值,設備不需要做任何事。
第二個表示4層的僞頭的校驗已經完畢,並且已經加入到ip頭中,此時只需要設備計算整個頭4層頭的校驗值。
主要來看一下ip輸入數據包的處理,也就是ip協議處理函數。
具體的協議註冊什麼的,可以看我前面的blog,這裏我們知道處理ip輸入的函數是ip_rcv.
先來看下當執行ip_rcv執行之前,sk_buff的結構:
- int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
- {
- struct iphdr *iph;
- u32 len;
- ///我們知道當爲PACKET_OTHERHOST是,2層就會直接丟掉所有的包,可是如果網卡被設置爲混雜模式,此時包就會傳遞到3層,這個時侯內核會有hook來處理這個,而我們這裏就只需要直接丟掉所有的包。
- if (skb->pkt_type == PACKET_OTHERHOST)
- goto drop;
- IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INRECEIVES);
- ///檢測這個數據包是否被內核其他部分使用,也就是監測引用計數。如果有被其他部分使用,則直接複製一份副本,然後返回。
- if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
- IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
- goto out;
- }
- //檢測skb->data的數據至少要和ip頭大小一樣。(這個原因很簡單,每個包都必須包含一個ip頭,如果比ip頭還小,說明包頭有錯誤了。
- if (!pskb_may_pull(skb, sizeof(struct iphdr)))
- goto inhdr_error;
- ///取出ip頭
- iph = ip_hdr(skb);
- /*
- * RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
- *
- * Is the datagram acceptable?
- *
- * 1. Length at least the size of an ip header
- * 2. Version of 4
- * 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
- * 4. Doesn't have a bogus length
- */
- ///ip頭的ihl域表示ip頭的大小(就是也就是IP層頭部包含多少個32位),version表示ip協議版本,這裏第一個檢測的原因是基本ip頭的大小是20個字節,也就是最小爲20個字節,20*8/32=5,所以最小必須是5。而這裏版本,由於這個只處理ipv4,因此version必須是4.
- if (iph->ihl < 5 || iph->version != 4)
- goto inhdr_error;
- ///這次來檢測整個ip頭的大小(包括option)和skb->data.這個檢測到這裏才執行,是因爲,必須首先確定ip頭的基本正確。
- if (!pskb_may_pull(skb, iph->ihl*4))
- goto inhdr_error;
- iph = ip_hdr(skb);
- ///開始校驗ip頭,也就是開始三層校驗。
- if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
- goto inhdr_error;
- ///取出整個ip頭的長度(包括option)
- len = ntohs(iph->tot_len);
- ///接下來的檢測是因爲在2層由於要滿足最小幀的大小,因此可能會填充一些空數據,而三層ip頭計算長度時,會忽略這些空數據,因此這裏的skb->len一定是大於或等於len
- if (skb->len < len) {
- IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
- goto drop;
- } else if (len < (iph->ihl*4))
- ///這個判斷是因爲ip頭不能被切包,也就是每個切好的包必須至少包含一個ip頭。
- goto inhdr_error;
- /* Our transport medium may have padded the buffer out. Now we know it
- * is IP we can trim to the true length of the frame.
- * Note this now means skb->len holds ntohs(iph->tot_len).
- */
- ///這裏也就是我們上面說的情況,需要把skb->len和len統一起來(去除掉空數據)
- if (pskb_trim_rcsum(skb, len)) {
- IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
- goto drop;
- }
- /* Remove any debris in the socket control block */
- memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
- ///調用net filter hook。
- return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,
- ip_rcv_finish);
- inhdr_error:
- IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
- drop:
- kfree_skb(skb);
- out:
- return NET_RX_DROP;
- }
我們這裏先不詳細介紹net filter,這裏我們只需要知道,在NF_HOOK中,會檢測每個包(通過用戶空間設置的規則)然後來決定要不要這個數據包通過。最後如果允許的話,就會調用 ip_rcv_finish函數。所以這裏我們詳細看下 ip_rcv_finish函數:
它主要會做兩件事:
1 決定這個包是被傳遞給高層,還是被forward。
2 解析並執行一些ip option。
- static int ip_rcv_finish(struct sk_buff *skb)
- {
- const struct iphdr *iph = ip_hdr(skb);
- struct rtable *rt;
- /*
- * Initialise the virtual path cache for the packet. It describes
- * how the packet travels inside Linux networking.
- */
- ///查找路由表的相關操作。
- if (skb->dst == NULL) {
- ///查找路由。這裏也會初始化skb->dst->input。
- int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
- skb->dev);
- if (unlikely(err)) {
- if (err == -EHOSTUNREACH)
- IP_INC_STATS_BH(dev_net(skb->dev),
- IPSTATS_MIB_INADDRERRORS);
- else if (err == -ENETUNREACH)
- IP_INC_STATS_BH(dev_net(skb->dev),
- IPSTATS_MIB_INNOROUTES);
- goto drop;
- }
- }
- ///QOS的相關操作.
- #ifdef CONFIG_NET_CLS_ROUTE
- if (unlikely(skb->dst->tclassid)) {
- struct ip_rt_acct *st = per_cpu_ptr(ip_rt_acct, smp_processor_id());
- u32 idx = skb->dst->tclassid;
- st[idx&0xFF].o_packets++;
- st[idx&0xFF].o_bytes+=skb->len;
- st[(idx>>16)&0xFF].i_packets++;
- st[(idx>>16)&0xFF].i_bytes+=skb->len;
- }
- #endif
- ///當ihl比5大,意味着有option。因此調用ip_rcv_options來進行解析和執行。
- if (iph->ihl > 5 && ip_rcv_options(skb))
- goto drop;
- rt = skb->rtable;
- if (rt->rt_type == RTN_MULTICAST)
- IP_INC_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INMCASTPKTS);
- else if (rt->rt_type == RTN_BROADCAST)
- IP_INC_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INBCASTPKTS);
- ///最後調用skb->dst->input,而這個虛函數的的值,首先是在ip_route_input中賦值,然後在 ip_rcv_options也有可能被修改。這個虛函數要麼被ip_local_deliver(也就是直接發向高層),要麼是ip_forward(直接被forward).這兩個函數以後會詳細介紹。
- return dst_input(skb);
- drop:
- kfree_skb(skb);
- return NET_RX_DROP;
- }