Linux網絡協議棧是內核中最大的組件之一,由於網絡部分應用的范圍很廣,也相對較熱,該部分現有的資料很多,學起來也比較容易。首先,我們看看貫穿網絡協議棧各層的一個最關鍵數據結構——套接字緩沖區(sk_buff結構)。
一個封包就存儲在這個數據結構中。所有網絡分層都會使用這個結構來存儲其報頭、有關數據的信息,以及用來協調工作的其他內部信息。在內核的進化歷程中,這個結構經歷多次變動,本文及後面的文章都是基於2.6.20版本,在2.6.32中該結構又變化了很多。該結構字段可粗略劃分為集中類型:布局、通用、專用、可選(可用宏開關)。
SKB在不同網絡層之間傳遞,可用於不同的網絡協議。協議棧中的每一層往下一層傳遞SKB之前,首先就是調用skb_reserve函數在數據緩存區頭部預留出的一定空間以保證每一層都能把本層的協議首部添加到數據緩沖區中。如果是向上層協議傳遞skb,則下層協議層的首部信息就沒有用了,內核實現上用指針改變指向來實現。
下面看看該結構體中的字段,大部分都給了注釋,後面的方法與實現中我們將看到他的各個字段的應用與實際意義。
struct sk_buff {
/* These two members must be first. */
/*鏈表,放在結構頭,用於強制轉化得到,鏈表頭為skb_buff_head*/
struct sk_buff *next;
struct sk_buff *prev;
/*指向擁有此緩沖區的sock數據結構,當數據在本地
產生或者正由本地進程接收時,就需要這個指針
因為該數據以及套接字相關的信息會由L4
以及用戶應用程序使用。當緩沖去只是被
轉發時,該指針為NULL*/
struct sock *sk;
/*通常只對一個以及接收的封包才有意義
這是一個時間戳記,用於表示封包何時被
接收,或者有時用於表示封包預定傳輸時間
*/
struct skb_timeval tstamp;
/*此字段描述一個網絡設備,該字段的作用與該SKB是發送包還是
接受包有關*/
struct net_device *dev;
/*接收報文的原始網絡設備,主要用於流量控制*/
struct net_device *input_dev;
union {
struct tcphdr *th;
struct udphdr *uh;
struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct ipv6hdr *ipv6h;
unsigned char *raw;
} h;/*四層協議首部*/
union {
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
unsigned char *raw;
} nh;/*三層協議首部*/
union {
unsigned char *raw;
} mac;/*二層協議首部*/
/*目的路由緩存項*/
struct dst_entry *dst;
/*IPSec協議用來跟蹤傳輸的信息*/
struct sec_path *sp;
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
/*SKB信息控制塊,是每層協議的私有信息存儲空間,由每層協議
自己使用並維護,並只有在本層有效*/
char cb[48];
/*數據總長度,包括線性緩沖區數據長度(data指向),SG類型的聚合分散I/O的數據
以及FRAGLIST類型的聚合分散I/O的數據長度,也包括協議首部的長度*/
unsigned int len,
/*SG類型和FRAGLIST類型聚合分散I/O存儲區中的數據長度*/
data_len,
/*二層首部長度。實際長度與網絡介質有關,在以太網中為以太網桢首部的長度*/
mac_len;
union {
__wsum csum;
__u32 csum_offset;
};
/*發送或轉發QOS類別。*/
__u32 priority;
/*表示此skb在本地允許分片*/
__u8 local_df:1,
/*標記所屬SKB是否已經克隆*/
cloned:1,
/*標記傳輸層校驗和的狀態*/
ip_summed:2,
/*標識payload是否被單獨引用,不存在協議首部*/
nohdr:1,
/*防火牆使用*/
nfctinfo:3;
/*桢類型,分類是由二層目的地址來決定的,對於以太網設備
來說,該字段由eth_type_trans()初始化*/
__u8 pkt_type:3,
/*當前克隆狀態*/
fclone:2,
ipvs_property:1;
/*從二層設備角度看到的上層協議,即鏈路層承載的三層協議類型*/
__be16 protocol;
/*skb析構函數指針,在釋放skb時被調用,完成某些必要的工作*/
void (*destructor)(struct sk_buff *skb);
/*在數據結構中定義的宏不能編譯成模塊;
原因在於內核編譯之後,開啟該選項所得
的多數結果為不可逆的,一般而言,任何
引起內核數據結構改變的選項,都不適合
編譯成一個模塊,編譯選項和特定的#ifdef
符號相配,以了解一個代碼區塊什麼時候
會包含到內核中,這種關聯在源碼樹的
Kconfig文件中*/
#ifdef CONFIG_NETFILTER
/*防火牆使用*/
struct nf_conntrack *nfct;
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
struct sk_buff *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
/*防火牆使用*/
struct nf_bridge_info *nf_bridge;
#endif
#endif /* CONFIG_NETFILTER */
#ifdef CONFIG_NET_SCHED
/*用於流量控制*/
__u16 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
/*用於流量控制*/
__u16 tc_verd; /* traffic control verdict */
#endif
#endif
#ifdef CONFIG_NET_DMA
dma_cookie_t dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
__u32 secmark;
#endif
__u32 mark;
/* These elements must be at the end, see alloc_skb() for details. */
/*全部數據的長度+該結構的長度,如果申請了一個len字節的緩沖區
該字段為len+sizeof(sk_buff)*/
unsigned int truesize;
/*引用計數,使用這個緩沖區的實例的數目*/
atomic_t users;
/*head和end指向數據在內存中的起始和結束位置,即
已經分配的緩沖區空間的開端和尾端
data和tail指向數據區域的起始和結束位置,即
實際數據的開端和尾端*/
unsigned char *head,
*data,
*tail,
*end;
};