sk_buff演算関数学習

I.はじめに

        カーネルは、ネットワーク デバイス ドライバーの開発時やネットワーク プロトコル スタック コードの変更時に必要となる、sk_buff を操作するための実用的な関数を多数提供します。機能的には、これらの関数は、ソケット バッファの作成、解放、コピー、sk_buff 構造体のパラメータとポインタの操作、ソケット バッファ キューの管理の 3 つのカテゴリに分類できます。この記事では代表的な機能を中心に紹介します。この記事で使用されているカーネルのバージョンは 3.18.45 です。

2. ソケットバッファの作成と解放

1.alloc_skb

        alloc_skb は、ソケット バッファーにメモリを割り当てるメイン関数である __alloc_skb 関数をカプセル化したものです。具体的な実装についてはこれ以上解析しませんが、主にalloc_skb関数呼び出し後のsk_buffの各パラメータとポインタの様子を紹介します。このうち、skb->head、skb->data、skb->tailはすべて割り当てられたメモリの開始アドレスを指し、skb->endは割り当てられたメモリの終了アドレスを指し、割り当てられたデータサイズは4バイトアラインです。 。以下に示すように。

        注: 実際のデータ長は不確実であるため、skb は alloc_skb を呼び出して割り当てられます (skb->len = 0)。

2.kree_skb

        kree_skb 関数は __kfree_skb 関数のカプセル化です。同じソケット バッファが複数の場所で使用される可能性があるため、kfree_skb は skb->users = 1 の場合にのみソケット バッファを完全に解放します。その他の場合は、skb-> を減らすだけです。ユーザー1.

3. sk_buffパラメータとポインタを操作する関数

1.skb_reserve

skb_reserve の関数プロトタイプ:

static inline void skb_reserve(struct sk_buff *skb, int len)
{
    skb->data += len;
    skb->tail += len;
}

        skb_reserve 関数は、alloc_skb 関数の後に使用され、ネットワーク プロトコル スタックの第 2 層、第 3 層およびデータ ロード用のスペースを予約します。以下に示すように:

         上図において、reserved len は、プロトコルスタックの他のデータを押すためのスペースを確保するもので、skb->data と skb- の間に実ロードデータの内容が保存されていることがわかります。 >尻尾。

2.skb_put

skb_put の関数プロトタイプ:

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
	unsigned char *tmp = skb_tail_pointer(skb);
	SKB_LINEAR_ASSERT(skb);
	skb->tail += len;
	skb->len  += len;
	if (unlikely(skb->tail > skb->end))
		skb_over_panic(skb, len, __builtin_return_address(0));
	return tmp;
}

        関数の実装を見ると、skb_put 関数が len を skb->tail に追加していることがわかりますが、これは skb の実際のロード データ長に len を追加することを意味します。これを実現するには、alloc_skb によって割り当てられた len から skb_reserve の len を引いた値が大きくなければなりません。 skb_put の len 以上。この方法でのみ、skb_put 関数が正常に動作することを確認できます概略図は次のとおりです。

3.skb_push

skb_push の関数プロトタイプ:

unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
	skb->data -= len;
	skb->len  += len;
	if (unlikely(skb->data<skb->head))
		skb_under_panic(skb, len, __builtin_return_address(0));
	return skb->data;
}

        関数の実装から、skb_push がデータ ポインタを len の長さだけ下位アドレスに移動し、同時に skb->len の長さに len を追加すると、プログラムはある長さのデータをプッシュすることがわかります。 len をこのアドレスに送信します。概略図は次のとおりです。

4.skb_pull

skb_pull の関数プロトタイプ:

static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
	skb->len -= len;
	BUG_ON(skb->len < skb->data_len);
	return skb->data += len;
}

static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len)
{
	return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
}

unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
{
	return skb_pull_inline(skb, len);
}

        関数実装を見ると、skb_pull は __skb_pull 関数のカプセル化であることがわかり、skb_pull_inline は len が skb->len より小さいと判断します。skb_pull 関数は skb_push の逆演算関数で、 skb->data ポインタを len だけ上位アドレスに移動すると同時に、 skb->len から len を引いた長さのデータを削除するのと等価です。 SKBのレン。

5. skb_trim

skb_trim 関数のプロトタイプ:

static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)
{
	skb->tail = skb->data + offset;
}

static inline void __skb_trim(struct sk_buff *skb, unsigned int len)
{
	if (unlikely(skb_is_nonlinear(skb))) {
		WARN_ON(1);
		return;
	}
	skb->len = len;
	skb_set_tail_pointer(skb, len);
}

void skb_trim(struct sk_buff *skb, unsigned int len)
{
	if (skb->len > len)
		__skb_trim(skb, len);
}

        関数実装からわかるように、skb_trim は __skb_trim のカプセル化であり、その機能は skb のペイロード データの長さを len にトリミングすることであり、トリミング長は skb->tail のポインタを変更することで実現されます。概略図は次のとおりです。

4. ソケットバッファキューを管理する

1. skb_queue_head_init

skb_queue_head_init 関数のプロトタイプ:

static inline void __skb_queue_head_init(struct sk_buff_head *list)
{
	list->prev = list->next = (struct sk_buff *)list;
	list->qlen = 0;
}

static inline void skb_queue_head_init(struct sk_buff_head *list)
{
	spin_lock_init(&list->lock);
	__skb_queue_head_init(list);
}

        関数の実装から、skb_queue_head_init が __skb_queue_head_init 関数のカプセル化であることがわかります。この関数は、リンク リストのポインターとリンク リストのキューの長さを初期化するために使用されます。

2.skb_dequeue

skb_dequeue の関数プロトタイプ:

static inline struct sk_buff *__skb_dequeue(struct sk_buff_head *list)
{
	struct sk_buff *skb = skb_peek(list);
	if (skb)
		__skb_unlink(skb, list);
	return skb;
}

struct sk_buff *skb_dequeue(struct sk_buff_head *list)
{
	unsigned long flags;
	struct sk_buff *result;

	spin_lock_irqsave(&list->lock, flags);
	result = __skb_dequeue(list);
	spin_unlock_irqrestore(&list->lock, flags);
	return result;
}

        この関数の機能は、リンク リストの先頭から skb を取得し、リンク リストから skb を削除することです。

3. skb_dequeue_tail

skb_dequeue_tail の関数プロトタイプ:

static inline struct sk_buff *__skb_dequeue_tail(struct sk_buff_head *list)
{
	struct sk_buff *skb = skb_peek_tail(list);
	if (skb)
		__skb_unlink(skb, list);
	return skb;
}

struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list)
{
	unsigned long flags;
	struct sk_buff *result;

	spin_lock_irqsave(&list->lock, flags);
	result = __skb_dequeue_tail(list);
	spin_unlock_irqrestore(&list->lock, flags);
	return result;
}

        この関数の機能は、リンク リストの末尾から skb を取得し、リンク リストから skb を削除することです。

4. skb_append

skb_append の関数プロトタイプ:

static inline void __skb_insert(struct sk_buff *newsk,
				struct sk_buff *prev, struct sk_buff *next,
				struct sk_buff_head *list)
{
	newsk->next = next;
	newsk->prev = prev;
	next->prev  = prev->next = newsk;
	list->qlen++;
}

static inline void __skb_queue_after(struct sk_buff_head *list,
				     struct sk_buff *prev,
				     struct sk_buff *newsk)
{
	__skb_insert(newsk, prev, prev->next, list);
}

void skb_append(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list)
{
	unsigned long flags;

	spin_lock_irqsave(&list->lock, flags);
	__skb_queue_after(list, old, newsk);
	spin_unlock_irqrestore(&list->lock, flags);
}

        この関数の機能は、リンク リストの古いノードの後ろにニュースクの skb を挿入することであり、リンク リストのキューの長さも 1 増加します。

V. 例

#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/udp.h>
#include <linux/ip.h>

#define UDP_SRC_PORT	3015
#define UDP_DST_PORT	3016

char data[32] = "chaos is a ladder!";
char src_ip[] = "192.168.0.56";
char dst_ip[] = "192.168.0.105";
char src_mac[] = {0x00, 0x0C, 0x43, 0x28, 0x80, 0xF5};
char dst_mac[] = {0x3C, 0x7C, 0x3F, 0xD7, 0x94, 0xAF};

struct sk_buff *skb;
struct udphdr *uhdr;
struct iphdr *ihdr;
struct ethhdr *ehdr;
struct net_device * dev = NULL;


void assemble_udp_header(struct udphdr *udp_hdr)
{
	udp_hdr->source = htons(UDP_SRC_PORT);
	udp_hdr->dest = htons(UDP_DST_PORT);
	udp_hdr->len = htons(sizeof(data) + sizeof(struct udphdr));
	udp_hdr->check = ~csum_tcpudp_magic(ihdr->saddr, ihdr->daddr, skb->len, IPPROTO_UDP, 0);
}

void assemble_ip_header(struct iphdr *ip_hdr)
{
	ip_hdr->version = IPVERSION;
	ip_hdr->ihl = sizeof(struct iphdr)/4;
	ip_hdr->tos =  0;
	ip_hdr->tot_len = htons(sizeof(data) + sizeof(struct udphdr) + sizeof(struct iphdr));
	ip_hdr->id = htons(42351);
	ip_hdr->frag_off = 0;
	ip_hdr->ttl = 64;
	ip_hdr->protocol = IPPROTO_UDP;
	ip_hdr->check = 0;
	ip_hdr->saddr = in_aton(src_ip);
	ip_hdr->daddr = in_aton(dst_ip);
}

void assemble_eth_header(struct ethhdr *eth_hdr)
{
	memcpy(eth_hdr->h_dest, dst_mac, 6);
	memcpy(eth_hdr->h_source, src_mac, 6) ;
	eth_hdr->h_proto = htons(ETH_P_IP);
}

int __init skb_package_init(void)
{
	printk("skb init\n");
	unsigned char *p;
	
	skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC);
	dev = dev_get_by_name(&init_net, "br0");
	skb->dev = dev;

	skb_reserve(skb, 2 + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(data));

	p = skb_push(skb, sizeof(data));
	memcpy(p, (void*)data, sizeof(data));

	p = skb_push(skb, sizeof(struct udphdr));
	uhdr = (struct udphdr *)p;
	skb_reset_transport_header(skb);

	p = skb_push(skb, sizeof(struct iphdr));
	ihdr = (struct iphdr *)p;
	skb_reset_network_header(skb);

	p = skb_push(skb, sizeof(struct ethhdr));
	ehdr = (struct ethhdr *)p;
	skb_reset_mac_header(skb);	

	assemble_ip_header(ihdr);
	assemble_udp_header(uhdr);
	assemble_eth_header(ehdr);

	dev_queue_xmit(skb);	

    return 0;
}

void __exit skb_package_exit(void)
{
	kfree_skb(skb);
    printk("skb exit\n");
}

module_init(skb_package_init);
module_exit(skb_package_exit);
MODULE_LICENSE("GPL");

        コードの機能は、UDP パケットを組み立て、ネットワーク ポート br0 経由で送信することです。これには、データ パケット用に予約された skb の割り当て、プロトコル ヘッダーと実際のロード データの skb->data への配置、そして最後に dev_queue_xmit を呼び出してパケットを送信することが含まれます。

6. まとめ

        この記事では、skb の割り当て、skb ポインタの操作、skb キューの操作など、ソケット バッファの一般的な操作関数をいくつかまとめて紹介します。これらの関数は、ネットワーク デバイス ドライバーを作成するための基礎であり、熟練する必要があります。

おすすめ

転載: blog.csdn.net/to_be_better_wen/article/details/132115348