Linux:sk_buff完全剖析與理解【轉】
sk_buff
目錄
1 sk_buff介紹
2 sk_buff組成
3 struct sk_buff 結構體
4 sk_buff成員變數
4.1 Layout佈局
4.2 General通用
4.3 Feature-specific功能相關
5 sk_buff管理和操作函式
5.1緩衝區操作函式 skb_reserve skb_put skb_push skb_pull
5.2傳送tcp報文示例
5.3 緩衝區分配、克隆和釋放函式alloc_skb skb_clone pskb_copy skb_copy kfree_skb
1 sk_buff介紹
sk_buff(socket buffer
2 sk_buff組成
Packet data:通過網絡卡收發的報文,包括鏈路層、網路層、傳輸層的協議頭和攜帶的應用資料,包括head room,data,tail room三部分。
skb_shared_info 作為packet data的補充,用於儲存ip分片,其中sk_buff *frag_list是一系列子skbuff連結串列,而frag[]是由一組單獨的page組成的資料緩衝區。
Data buffer:用於儲存packet data的緩衝區,分為以上兩部分。
Sk_buff:緩衝區控制結構sk_buff
整個sk_buff結構圖如圖1。
圖1 sk_buff結構圖
3 struct sk_buff 結構體
/* struct sk_buff - socket buffer */
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
struct sock *sk;
struct skb_timeval tstamp; /* Time we arrived,記錄接收或傳送報文的時間戳*/
struct net_device *dev; /*
struct net_device *input_dev; /*接收資料的網路裝置
struct net_device *curlayer_input_dev;
struct net_device *l2tp_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; //鏈路層報頭
.
.
.
unsigned int len, //len緩衝區中資料部分的長度。
data_len, // data_len只計算分片中資料的長度
mac_len, //mac頭的長度
csum; //校驗和
__u32 priority;
__u8 local_df:1,
cloned:1, //表示該結構是另一個sk_buff克隆的
ip_summed:2,
nohdr:1,
nfctinfo:3;
__u8 pkt_type:3,
fclone:2,
ipvs_property:1;
__be16 protocol;
__u32 flag; /*packet flags*/
.
.
.
/* These elements must be at the end, see alloc_skb() for details. */
unsigned int truesize; //這是緩衝區的總長度,包括sk_buff結構和資料部分
atomic_t users;
unsigned char *head, //指向緩衝區的頭部
*data,// 指向實際資料的頭部
*tail, //指向實際資料的尾部
*end;//指向緩衝區的尾部
};
4 sk_buff成員變數
Sk_buff成員變數主要包括以下3類
1 Layout佈局
2 General通用
3 Feature-specific功能相關
4.1 Layout佈局
1 struct sk_buff *next, struct sk_buff *prev
有些sk_buff成員變數的作用是方便查詢,或者是連線資料結構本身. 核心可以把sk_buff組織成一個雙向連結串列。當然,這個連結串列的結構要比常見的雙向連結串列的結構複雜一點。就像任何一個雙向連結串列一樣,sk_buff中有兩個指標next和prev,其中,next指向下一個節點,而prev指向上一個節點。但是,這個連結串列還有另一個需求:每個sk_buff結構都必須能夠很快找到連結串列頭節點。為了滿足這個需求,在第一個節點前面會插入另一個結構sk_buff_head,這是一個輔助節點,它的定義如下
sk_buff和sk_buff_head的前兩個元素是一樣的:next和prev指標。這使得它們可以放到同一個連結串列中,儘管sk_buff_head要比sk_buff小得多。另外,相同的函式可以同樣應用於sk_buff和sk_buff_head。
圖2
2 struct sock *sk
這是一個指向擁有這個sk_buff的sock結構的指標。這個指標在網路包由本機發出或者由本機程序接收時有效,因為插口相關的資訊被L4(TCP或UDP)或者使用者空間程式使用。如果sk_buff只在轉發中使用(這意味著,源地址和目的地址都不是本機地址),這個指標是NULL。
3 unsigned int len
這是緩衝區中資料部分的長度。它包括主緩衝區中的資料長度(data指標指向它)和分片中的資料長度。它的值在緩衝區從一個層向另一個層傳遞時改變,因為往上層傳遞,舊的頭部就沒有用了,而往下層傳遞,需要新增本層的頭部。len同樣包含了協議頭的長度。
4 unsigned int data_len
和len不同,data_len只計算分片中資料的長度。
5 unsigned int mac_len
這是mac頭的長度。
6 atomic_t users
這是一個引用計數,用於計算有多少實體引用了這個sk_buff緩衝區。它的主要用途是防止釋放sk_buff後,還有其他實體引用這個sk_buff。因此,每個引用這個緩衝區的實體都必須在適當的時候增加或減小這個變數。這個計數器只保護sk_buff結構本身,而緩衝區的資料部分由類似的計數器(dataref)來保護。有時可以用atomic_inc和atomic_dec函式來直接增加或減小users,但是,通常還是使用函式skb_get和kfree_skb來操作這個變數。
7 unsigned int truesize
這是緩衝區的總長度,包括sk_buff結構和資料部分。如果申請一個len位元組的緩衝區,alloc_skb函式會把它初始化成len+sizeof(sk_buff)。
8 unsigned char *head ,*end, *data, *tail
它們表示緩衝區和資料部分的邊界。在每一層申請緩衝區時,它會分配比協議頭或協議資料大的空間。head和end指向緩衝區的頭部和尾部,而data和tail指向實際資料的頭部和尾部,參見圖3。每一層會在head和data之間填充協議頭,或者在tail和end之間新增新的協議資料。圖3中右邊資料部分會在尾部包含一個附加的頭部。
圖3
9 void (*destructor)(...)
這個函式指標可以初始化成一個在緩衝區釋放時完成某些動作的函式。如果緩衝區不屬於一個socket,這個函式指標通常是不會被賦值的。如果緩衝區屬於一個socket,這個函式指標會被賦值為sock_rfree或sock_wfree(分別由skb_set_owner_r或skb_set_owner_w函式初始化)。這兩個sock_xxx函式用於更新socket佇列中的記憶體容量。
4.2 General通用
本節描述sk_buff的主要成員變數,這些成員變數與特定的核心功能無關。
1struct timeval tstamp
這個變數只對接收到的包有意義。它代表包接收時的時間戳,或者有時代表包準備發出時的時間戳。它在netif_rx裡面由函式net_timestamp設定,而netif_rx是裝置驅動收到一個包後呼叫的函式。
2 struct net_device *dev
這個變數的型別是net_device,net_device它代表一個網路裝置。dev的作用與這個包是準備發出的包,還是剛接收的包有關。當收到一個包時,裝置驅動會把sk_buff的dev指標指向收到這個包的裝置的資料結構,就像下面的vortex_rx裡的一段程式碼所做的一樣,這個函式屬於3c59x系列乙太網卡驅動,用於接收一個幀。(drivers/net/3c59x.c):
當一個包被髮送時,這個變數代表將要傳送這個包的裝置。在傳送網路包時設定這個值的程式碼要比接收網路包時設定這個值的程式碼複雜。有些網路功能可以把多個網路裝置組成一個虛擬的網路裝置(也就是說,這些裝置沒有和物理裝置直接關聯),並由一個虛擬網路裝置驅動管理。當虛擬裝置被使用時,dev指標指向虛擬裝置的net_device結構。而虛擬裝置驅動會在一組裝置中選擇一個裝置並把dev指標修改為這個裝置的net_device結構。因此,在某些情況下,指向傳輸裝置的指標會在包處理過程中被改變。
3 struct net_device *input_dev
這是收到包的網路裝置的指標。如果包是本地生成的,這個值為NULL。對乙太網裝置來說,這個值由eth_type_trans初始化,它主要被流量控制程式碼使用。
4 struct net_device *real_dev
這個變數只對虛擬裝置有意義,它代表與虛擬裝置關聯的真實裝置。例如,Bonding和VLAN裝置都使用它來指向收到包的真實裝置。
5 union {...} h union {...} nh union {...} mac
這些是指向TCP/IP各層協議頭的指標:h指向L4,nh指向L3,mac指向L2。每個指標的型別都是一個聯合,包含多個數據結構,每一個數據結構都表示核心在這一層可以解析的協議。例如,h是一個包含核心所能解析的L4協議的資料結構的聯合。每一個聯合都有一個raw變數用於初始化,後續的訪問都是通過協議相關的變數進行的。
當接收一個包時,處理n層協議頭的函式從n-1層收到一個緩衝區,它的skb->data指向n層協議的頭。處理n層協議的函式把本層的指標(例如,L3對應的是skb->nh指標)初始化為skb->data,因為這個指標的值會在處理下一層協議時改變(skb->data將被初始化成緩衝區裡的其他地址)。在處理n層協議的函式結束時,在把包傳遞給n+1層的處理函式前,它會把skb->data指標指向n層協議頭的末尾,這正好是n+1層協議的協議頭(參見圖4)。
傳送包的過程與此相反,但是由於要為每一層新增新的協議頭,這個過程要比接收包的過程複雜。
圖4
6 struct dst_entry dst 這個變數在路由子系統中使用。
7 char cb[40]
這是一個控制快取,或者說是一個私有資訊的儲存空間,由每一層自己維護並使用。它在分配sk_buff結構時分配(它目前的大小是40位元組,已經足夠為每一層儲存必要的私有資訊了)。在每一層中,訪問這個變數的程式碼通常用巨集實現,以增強程式碼的可讀性。例如,TCP用這個變數儲存tcp_skb_cb結構,這個結構在include/net/tcp.h中定義:
下面這個巨集被TCP程式碼用來訪問cb變數。在這個巨集裡面,有一個簡單的型別轉換:
#define TCP_SKB_CB(__skb) ((struct tcp_skb_cb *)&((__skb)->cb[0]))
下面的例子是TCP子系統在收到一個分段時填充相關資料結構的程式碼:
int tcp_v4_rcv(struct sk_buff *skb)
8 unsigned int csum unsigned char ip_summed
表示校驗和以及相關狀態標記。
unsigned char cloned
一個布林標記,當被設定時,表示這個結構是另一個sk_buff的克隆。
9 unsigned char pkt_type
這個變量表示幀的型別,分類是由L2的目的地址來決定的。可能的取值都在include/linux/if_packet.h中定義。對乙太網裝置來說,這個變數由eth_type_trans函式初始化。
10 __u32 priority
這個變數描述傳送或轉發包的QoS類別。如果包是本地生成的,socket層會設定priority變數。如果包是將要被轉發的,rt_tos2priority函式會根據ip頭中的Tos域來計算賦給這個變數的值。這個變數的值與DSCP(DiffServ CodePoint)沒有任何關係。
unsigned short protocol
這個變數是高層協議從二層裝置的角度所看到的協議。典型的協議包括IP,IPV6和ARP。完整的列表在include/linux/if_ether.h中。由於每個協議都有自己的協議處理函式來處理接收到的包,因此,這個域被裝置驅動用於通知上層呼叫哪個協議處理函式。每個網路驅動都呼叫netif_rx來通知上層網路協議的協議處理函式,因此protocol變數必須在這些協議處理函式呼叫之前初始化。
unsigned short security
這是包的安全級別。這個變數最初由IPSec子系統使用,但現在已經作廢了。
4.3 Feature-specific功能相關
linux核心是模組化的,你可以選擇包含或者刪除某些功能。因此,sk_buff結構裡面的一些成員變數只有在核心選擇支援某些功能時才有效,比如防火牆(netfilter)或者qos:
1 unsigned long nfmark __u32 nfcache __u32 nfctinfo struct nf_conntrack *nfct
unsigned int nfdebug struct nf_bridge_info *nf_bridge
這些變數被netfilter使用(防火牆程式碼),核心編譯選項是“Device Drivers->Networking support-> Networking options-> Network packet filtering”和兩個子選項“Network packet filtering debugging”和“Bridged IP/ARP packets filtering”
2 union {...} private
這個聯合結構被高效能並行介面(HIPPI)使用。相應的核心編譯選項是“Device->Drivers ->Networking support ->Network device support ->HIPPI driver support”
3 __u32 tc_index __u32 tc_verd __u32 tc_classid
這些變數被流量控制程式碼使用,核心編譯選項是“Device Drivers ->Networking->support ->Networking options ->QoS and/or fair queueing”和它的子選項“Packetclassifier API”
4 struct sec_path *sp
這個變數被IPSec協議用於跟蹤傳輸的資訊。
5 sk_buff管理和操作函式
5.1緩衝區操作函式
有很多函式,通常都比較短小而且簡單,核心用這些函式操作sk_buff的成員變數或者sk_buff連結串列。首先來看分配和釋放緩衝區的函式,然後是一些通過移動指標在緩衝區的頭部或尾部預留空間的函式。如果你看過include/linux/skbuff.h和net/core/skbuff.c中的函式,你會發現,基本上每個函式都有兩個版本,名字分別是do_something和__do_something。通常第一種函式是一個包裝函式,它會在第二種函式的基礎上增加合法性檢查或者鎖。一般來說,類似__do_something的函式不能被直接呼叫(除非滿足特定的條件,比如說鎖)。那些違反這條規則而直接引用這些函式的不良程式碼會最終被更正。
各操作函式緩衝區與移動指標變化如圖5所示。
圖5 操作前與操作後指標變化圖: (a)skb_put, (b)skb_push, (c)skb_pull, and (d)skb_reserve
1 unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
在緩衝區的尾部空間擴充len位元組資料區l,將tail指標下移,並增加skb的len值。data和tail之間的空間就是可以存放網路報文的空間。這個操作增加了可以儲存網路報文的空間,但是增加不能使 tail的值大於end的值,skb的len值大於truesize 的值。
2 unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
在緩衝區的頭部空間擴充len位元組的資料區。將data指標上移,並增加skb的len值。這個操作在儲存空間的頭部增加了一段可以儲存網路報文的空間,但是增加不能使data的值小於 head的值,skb的len值大於truesize的值。
3 unsigned char * skb_pull(struct sk_buff *skb, unsigned int len)
從緩衝區的資料區刪除len位元組,把騰出的記憶體歸還給頭部空間。將data指標下移,並減小skb的len值。這個操作使data指標指向下一層網路報文的頭部。
4 void skb_reserve(struct sk_buff *skb, unsigned int len)
從空白緩衝區中分配len位元組的資料區,通過減少尾部空間,增加一個空&sk_buff的首部空間,將data指標和tail指標同時下移。這個操作在儲存空間的頭部預留len長度的空隙。
如果檢視某個乙太網裝置驅動的收包函式(例如,drivers/net/3c59x.c中的vortex_rx), 你就會發現它在分配緩衝區之後,在向緩衝區中填充資料之前,會呼叫下面的函式:
由於乙太網幀的頭部長度是14個八位組,這個函式把緩衝區的頭部指標向後移動了2個位元組。這樣,緊跟在乙太網頭部之後的IP頭部在緩衝區中儲存時就可以在16位元組的邊界上對齊。如圖6所示。
圖6 (a) skb_reserve開始前, (b) skb_reserve後(c) 複製資料到緩衝區
5.2傳送tcp報文示例
傳送報文時,在不同協議層處理資料時,該資料要新增相應的協議頭。因此,最高層新增資料和自身的協