sk_buff 定義及其操作 (封包,數據包,packet …)

(出處: 333)

1. sk_buff 結構體
可以看出sk_buff 結構體很重要,
sk_buff — 套接字緩衝區,用來在linux網絡子系統中各層之間數據傳遞,起到了“神經中樞”的作用。
當發送數據包時,linux內核的網絡模塊必須建立一個包含要傳輸的數據包的sk_buff,然後將sk_buff傳遞給下一層,各層在sk_buff 中添加不同的協議頭,直到交給網絡設備發送。同樣,當接收數據包時,網絡設備從物理媒介層接收到數據後,他必須將接收到的數據轉換為sk_buff,並傳遞給上層,各層剝去相應的協議頭後直到交給用戶。
sk_buff結構如下圖所示: (define at include/linux/skbuff.h)

sk_buff定義如​​下:

sk_buff主要成員如下:
1.1 各層協議頭:
— transport_header : 傳輸層協議頭,如TCP, UDP , ICMP, IGMP等協議頭
— network_header : 網絡層協議頭, 如IP, IPv6, ARP 協議頭
— mac_header : 鏈路層協議頭。
— sk_buff_data_t 原型就是一個char 指針

1.2 數據緩衝區指針head, data, tail, end
— *head :指向內存中已分配的用於存放網絡數據緩衝區的起始地址, sk_buff和相關數據被分配後,該指針值就固定了
— *data : 指向對應當前協議層有效數據的起始地址。
每個協議層的有效數據內容不一樣,各層有效數據的內容如下:
a. 對於傳輸層,有效數據包括用戶數據和傳輸層協議頭
b. 對於網絡層,有效數據包括用戶數據、傳輸層協議和網絡層協議頭。
c. 對於數據鏈路層,有效數據包括用戶數據、傳輸層協議、網絡層協議和鏈路層協議。
因此,data指針隨著當前擁有sk_buff的協議層的變化而進行相應的移動。
— tail :指向對應當前協議層有效數據的結尾地址,與data指針相對應。
— end :指向內存中分配的網絡數據緩衝區的結尾,與head指針相對應。和head一樣,sk_buff被分配後,end指針就固定了。
head, data, tail, end 關係如下圖所示:

1.3 長度信息len, data_len, truesize
— len :指網絡數據包的有效數據的長度,包括協議頭和負載(payload).
— data_len : 記錄分片的數據長度
— truesize :表述緩存區的整體長度,一般為sizeof(sk_buff).
1.4 數據包類型
— pkt_type :指定數據包類型。驅動程序負責將其設置為:
PACKET_HOST — 該數據包是給我的。
PACKET_OTHERHOST — 該數據包不是給我的。
PACKET_BROADCAST — 廣播類型的數據包
PACKET_MULTICAST — 組播類型的數據包
驅動程序不必顯式的修改pkt_type,因為eth_type_trans會完成該工作。
2. 套接字緩衝區的操作
2.1 分配套接字緩衝區
struct sk_buff *alloc_skb(unsigned intlen, int priority);
alloc_skb()函數分配一個套接字緩衝區和一個數據緩衝區。
— len : 為數據緩衝區的大小
— priority : 內存分配的優先級

struct sk_buff *dev_alloc_skb(unsignedint len​​);
dev_alloc_skb()函數以GFP_ATOMIC 優先級調用上面的alloc_skb()函數。
並保存skb->dead 和skb->data之間的16個字節

2.2 釋放套接字緩衝區
void kfree_skb(struct sk_buff *skb);

— kfree_skb() 函數只能在內核內部使用,網絡設備驅動中必須使用dev_kfree_skb()、dev_kfree_skb_irq() 或dev_kfree_skb_any().
void dev_kfree_skb(struct sk_buff *skb);
— dev_kfree_skb()用於非中斷上下文。

void dev_kfree_skb_irq(struct sk_buff *skb);
— dev_kfree_skb_irq() 用於中斷上下文。

void dev_kfree_skb_any(struct sk_buff *skb);
— dev_kfree_skb_any() 在中斷或非中斷上下文中都能使用。

2.3移動指針
Linux套接字緩衝區中的指針移動操作有:put(放置), push(推), pull(拉)和reserve(保留)等。
2.3.1 put操作
unsigned char *skb_put(struct sk_buff *skb, unsigned int len​​);
將tail 指針下移,增加sk_buff 的len 值,並返回skb->tail 的當前值。
將數據添加在buffer的尾部。

unsigned char *__skb_put(struct sk_buff *skb, unsigned int len​​);
__skb_put() 與skb_put()的區別在於skb_put()會檢測放入緩衝區的數據, 而__skb_put()不會檢查

2.3.2 push操作:
unsigned char *skb_push(struct sk_buff *skb, unsigned int len​​);
skb_push()會將data指針上移,也就是將數據添加在buffer的起始點,因此也要增加sk_buff的len值。

unsigned char *__skb_push(struct sk_buff *skb, unsigned int len​​);

__skb_push()和skb_push()的區別與__skb_put() 和skb_put()的區別一樣。
push操作在緩衝區的頭部增加一段可以存儲網絡數據包的空間,而put操作在緩衝區的尾部增加一段可以存儲網絡數據包的空間。

2.3.3 pull操作:
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len​​);
skb_pull()將data指針下移,並減少skb的len值, 這個操作與skb_push()對應。
這個操作主要用於下層協議向上層協議移交數據包,使data指針指向上一層協議頭

2.3.4 reserve 操作
void skb_reserve(struct sk_buff *skb, unsigned int len​​);
skb_reserve()將data指針和tail 指針同時下移。
這個操作用於在緩衝區頭部預留len長度的空間

3. 例子:
Linux處理一個UDP數據包的接收流程,來說明對sk_buff的操作過程。
這一過程絕大部分工作會在內核完成,驅動中只需要完成涉及數據鏈路層部分。
假設網卡收到一個UDP數據包,Linux處理流程如下:

3.1 網卡收到一個UDP數據包後,驅動程序需要創建一個sk_buff結構體和數據緩衝區,將接收到的數據全部複製到data指向的空間,並將skb->mac_header指向data。
此時有效數據的開始位置data是一個以太網頭部,即鏈路層協議頭。
示例代碼如下:
//分配新的套接字緩衝區和數據緩衝區

工作內容如下圖所示:

3.2 數據鏈路層通過調用skb_pull() 剝掉以太網協議頭,向網絡層IP傳送數據包。
在剝離過程中,data指針會下移一個以太網頭部的長度sizeof(struct ethhdr), 而len 也減去sizeof(struct ethhdr)長度。
此時有效數據的開始位置是一個IP協議頭,skb->network_head指向data,即IP協議頭, 而skb->mac_header 依舊指向以太網頭, 即鏈路層協議頭。
內容如下圖所示:

3.3 網絡層通過skb_pull()剝掉IP協議頭,向UDP傳輸層傳遞數據包。
剝離過程中,data指針會下移一個IP協議頭長度sizeof(struct iphdr), 而len也會減少sizeof(struct iphdr)長度。
此時有效數據開始位置是一個UDP協議頭, skb->transport_header指向data,即UDP協議頭。
而skb->network_header繼續指向IP協議頭, skb->mac_header 繼續指向鏈路層協議頭。
如下圖所示:

3.4 應用程序在調用recv() 接收數據時,從skb->data + sizeof(struct udphdr) 的位置開始復製到應用層緩衝區。
可見,UPD協議頭到最後也沒有被剝離。

發表迴響