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緩衝區操作函數sk... ...
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)結構是linux網路代碼中重要的數據結構,它管理和控制接收或發送數據包的信息。
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長度的空隙。