skb - Linux network buffers

本文围绕Linux网络缓冲区skb展开。介绍了作者撰写此文的缘由,skbuffs是Linux内核处理网络数据包的缓冲区,网卡接收数据包后存入其中再传至网络栈。还阐述了struct sk_buff的定义位置,以及skb支持函数,如分配、释放等相关操作。

skb - Linux network buffers

Harald Welte laforge@gnumonks.org

1.3, 2000/10/14 21:27:02


Short description about the linux network buffers (skb's)

1. Introduction

At the time I wanted to know more about the Linux network stack, I always wanted a document like this to exist. But unfortunately I never found one. After I gained some basic knowledge about the Linux network stack internals, I wrote one.

I'm happy if this document is of any use for other people trying to learn about the Linux kernel.

Please let me know of any bugs in this document. It should resemble kernel revision 2.4.0-test4

2. skbuff's

skbuffs are the buffers in which the linux kernel handles network packets. The packet is received by the network card, put into a skbuff and then passed to the network stack, which uses the skbuff all the time.

2.1 struct sk_buff

The struct sk_buff is defined in <linux/skbuff.h> as follows:

next

next buffer in list

prev

previous buffer in list

list

list we are on

sk

socket we belong to

stamp

timeval we arrived at

dev

device we are leaving by

rx_dev

device we arrived at

h

transport layer header (tcp,udp,icmp,igmp,spx,raw)

nh

network layer header (ip,ipv6,arp,ipx,raw)

mac

link layer header

dst

FIXME:

cb

control buffer, used internally

len

length of actual data

csum

checksum

used

FIXME: data moved to user and not MSG_PEEK

is_clone

we are a clone

cloned

head may be cloned

pkt_type

packet class

ip_summed

driver fed us ip checksum

priority

packet queuing priority

users

user count

protocol

packet protocol from driver

security

security level of packet

truesize

real size of the buffer

head

pointer to head of buffer

data

data head pointer

tail

tail pointer

end

end pointer

destructor

destructor function

nfmark

netfilter mark

nfcache

netfilter internal caching info

nfct

associated connection, if any

tc_index

traffic control index

2.2 skb support functions

There are a bunch of skb support functions provided by the sk_buff layer. I briefly describe the most important ones in this section.

allocation / free / copy / clone and expansion functions

struct sk_buff *alloc_skb(unsigned int size, int gfp_mask)

This function allocates a new skb. This is provided by the skb layer to initialize some privat data and do memory statistics. The returned buffer has no headroom and a tailroom of /size/ bytes.

void kfree_skb(struct sk_buff *skb)

Decrement the skb's usage count by one and free the skb if no references left.

struct sk_buff *skb_get(struct sk_buff *skb)

Increments the skb's usage count by one and returns a pointer to it.

struct sk_buff *skb_clone(struct sk_buff *skb, int gfp_mask)

This function clones a skb. Both copies share the packet data but have their own struct sk_buff. The new copy is not owned by any socket, reference count is 1.

struct sk_buff *skb_copy(const struct sk_buff *skb, int gfp_mask)

Makes a real copy of the skb, including packet data. This is needed, if You wish to modify the packet data. Reference count of the new skb is 1.

struct skb_copy_expand(const struct sk_buff *skb, int new_headroom, int new_tailroom, int gfp_mask)

Make a copy of the skb, including packet data. Additionally the new skb has a haedroom of /new_headroom/ bytes size and a tailroom of /new_tailroom/ bytes.

anciliary functions

int skb_cloned(struct sk_buff *skb)

Is the skb a clone?

int skb_shared(struct sk_Buff *skb)

Is this skb shared? (is the reference count > 1)?

operations on lists of skb's

struct sk_buff *skb_peek(struct sk_buff_head *list_)

peek a skb from front of the list; does not remove skb from the list

struct sk_buff *skb_peek_tail(struct sk_buff_head *list_)

peek a skb from tail of the list; does not remove sk from the list

__u32 skb_queue_len(sk_buff_head *list_)

return the length of the given skb list

void skb_queue_head(struct sk_buff_head *list_, struct sk_buff *newsk)

enqueue a skb at the head of a given list

void skb_queue_tail(struct sk_buff_head *list_, struct sk_buff *newsk)

enqueue a skb at the end of a given list.

struct sk_buff *skb_dequeue(struct sk_buff_head *list_)

dequeue a skb from the head of the given list.

struct sk_buff *sbk_dequeue_tail(struct sk_buff_head *list_)

dequeue a skb from the tail of the given list

operations on skb data

unsigned char *skb_put(struct sk_buff *sbk, int len)

extends the data area of the skb. if the total size exceeds the size of the skb, the kernel will panic. A pointer to the first byte of new data is returned.

unsigned char *skb_push(struct sk_buff *skb, int len)

extends the data area of the skb. if the total size exceeds the size of the skb, the kernel will panic. A pointer to the first byte of new data is returned.

unsigned char *skb_pull(struct sk_buff *skb, int len)

remove data from the start of a buffer, returning the bytes to headroom. A pointr to the next data in the buffer is returned.

int skb_headroom(struct sk_buff *skb)

return the amount of bytes of free space at the head of skb

int skb_tailroom(struct sk_buff *skb)

return the amount of bytes of free space at the end of skb

struct sk_buff *skb_cow(struct sk_buff *skb, int headroom)

if the buffer passed lacks sufficient headroom or is a clone it is copied and additional headroom made available.

<think> 我们正在讨论Linux网络栈中sk_buff结构体(skb)的字段含义和操作。用户的问题聚焦于如何通过skb->data和skb->head计算协议头的偏移量,以及skb_reset_network_header函数中计算skb->network_header的原理。 根据引用[1]和[2](它们实际上是同一段代码),我们看到在__netif_receive_skb_core函数中有如下操作: ```c skb_reset_network_header(skb); // 这实际上展开为: // skb->network_header = skb->data - skb->head; skb_reset_mac_len(skb); // 这展开为: // skb->mac_len = skb->network_header - skb->mac_header; ``` ### 1. 协议头偏移量的计算原理 在skb中,协议头的偏移量是相对于skb->head的。因为skb->head指向数据包内存块的起始位置,而skb->data指向当前有效数据的起始位置(即当前协议层的头部)。 - **偏移量定义**:某个协议头(如MAC头、IP头)在数据包内存块中的位置,可以用该头的起始地址减去skb->head得到。 - **计算示例**: 假设我们有一个数据包,其内存布局如下: ``` | headroom | MAC头 | IP头 | TCP头 | 数据 | tailroom | ^ ^ ^ ^ ^ head mac_header network_header data (当前指向TCP头) ``` 这里: - `skb->head` 指向内存块开始(headroom起始处)。 - `skb->mac_header` 指向MAC头的开始。 - `skb->network_header` 指向IP头的开始。 - 当前`skb->data`可能指向TCP头的开始(如果已经剥离了MAC头和IP头)。 那么,MAC头的偏移量就是 `skb->mac_header - skb->head`。 同样,IP头的偏移量就是 `skb->network_header - skb->head`。 ### 2. `skb_reset_network_header`函数详解 函数`skb_reset_network_header`的作用是设置网络层头(如IP头)的偏移量。其典型实现是: ```c static inline void skb_reset_network_header(struct sk_buff *skb) { skb->network_header = skb->data - skb->head; } ``` - **原理**:这个计算基于一个前提:在调用这个函数时,`skb->data`正指向网络层头(IP头)的起始位置。因此,用当前`skb->data`减去`skb->head`就得到了IP头相对于整个内存块起始地址(`skb->head`)的偏移量。 - **结果存储**:这个偏移量被存储在`skb->network_header`中。之后,无论`skb->data`如何变化(比如在协议栈处理中,通过`skb_pull`移动`data`指针剥离头部),我们都可以通过`skb_network_header(skb)`宏来获取网络层头的实际指针: ```c #define skb_network_header(skb) ((skb)->head + (skb)->network_header) ``` 这样,即使`skb->data`已经指向传输层(如TCP头),我们仍然可以方便地访问IP头。 ### 3. 为什么使用偏移量而不是直接指针? 因为skb可能会被复制(例如,当数据包需要分发给多个套接字时,如多播或tcpdump),或者被重新分配内存(如扩大headroom)。如果直接存储指针,那么在内存块变化时,这些指针就会失效。而存储相对于`skb->head`的偏移量,则可以保证在任何时候都能正确计算出绝对地址(因为`skb->head`在数据包生命周期内不变,即使内存块被重新分配,`skb->head`也会更新到新内存块的起始地址,而偏移量不变)。 ### 4. 实例演示 假设一个数据包刚被网卡驱动接收,经过如下步骤: 1. **驱动层**:分配skb,数据包内容被DMA到内存,`skb->head`指向内存块开始,`skb->data`初始也指向这里(即MAC头在`skb->data`处)。 2. **预留headroom**:调用`skb_reserve(skb, NET_SKB_PAD)`,将`skb->data`和`skb->tail`向后移动,此时`skb->data`指向MAC头。 3. **设置MAC头**:驱动设置`skb->mac_header = skb->data - skb->head`(或调用`skb_reset_mac_header`)。 4. **网络层处理**:当数据包进入网络层时,需要解析IP头。此时,以太网驱动已经将`skb->data`指向了IP头(通过`skb_pull(skb, ETH_HLEN)`移动`data`指针,跳过了MAC头)。然后调用`skb_reset_network_header(skb)`,这时: - `skb->data`指向IP头。 - `skb->network_header = skb->data - skb->head` 计算得到IP头的偏移量。 5. **后续使用**:在IP层处理中,我们可以通过`ip_hdr(skb)`宏(内部使用`skb_network_header`)来访问IP头。 ### 总结 - **偏移量计算**:任何协议头的偏移量都是其绝对指针减去`skb->head`。 - **`skb_reset_network_header`原理**:在`skb->data`指向网络层头时,通过`skb->data - skb->head`计算偏移量并存储到`skb->network_header`,以便后续通过`skb_network_header(skb)`快速定位。 --- ### 相关问题 1. 在什么情况下需要调用`skb_reset_network_header`? 2. `skb_push`和`skb_pull`操作如何影响协议头偏移量? 3. 如何获取TCP头的偏移量? 4. 为什么在skb生命周期内`skb->head`保持不变? [^1]: __netif_receive_skb_core函数中设置网络头偏移量的操作。 [^2]: 同上。 </think>### 如何通过 `skb->data` 和 `skb->head` 计算协议头偏移量 在 Linux 网络栈中,**协议头的偏移量**是通过指针差值计算的,公式为: $$\text{偏移量} = \text{skb->data} - \text{skb->head}$$ 这个差值表示**当前协议头**(如 IP 头)相对于数据包内存块起始位置(`skb->head`)的字节偏移量。 #### 计算示例: 1. 假设数据包内存布局为: ``` | headroom | MAC头 | IP头 | TCP头 | 数据 | tailroom | ^ ^ ^ ^ head mac_hdr net_hdr data ``` 2. 当 `skb->data` 指向 IP 头起始位置时: - `skb->head` = 内存块起始地址(例如 `0x1000`) - `skb->data` = IP 头起始地址(例如 `0x1020`) - **IP 头偏移量** = `0x1020 - 0x1000 = 0x20`(32 字节) --- ### `skb_reset_network_header` 函数原理 该函数用于标记网络层头(如 IP 头)的位置: ```c static inline void skb_reset_network_header(struct sk_buff *skb) { skb->network_header = skb->data - skb->head; } ``` #### 工作流程: 1. **前提条件**: 调用此函数时,`skb->data` **必须已指向网络层头部**(如 IP 头起始位置)。这通常由底层驱动或协议栈通过 `skb_pull()` 移动 `data` 指针实现。 2. **偏移量计算**: `skb->network_header = skb->data - skb->head` 计算出 IP 头相对于内存块起始位置的固定偏移量(如图中 `0x20`)。 3. **后续访问**: 通过宏 `skb_network_header(skb)` 可快速定位 IP 头: ```c #define skb_network_header(skb) ((skb)->head + (skb)->network_header) ``` 此时 `skb->head + 0x20` 直接返回 IP 头指针。 #### 关键优势: - **位置无关性**:即使后续 `skb->data` 移动(如剥离 TCP 头),`skb->network_header` 存储的偏移量仍能正确定位 IP 头[^1][^2]。 - **高效访问**:避免重复计算协议头位置。 --- ### 协议头偏移量计算的实际应用 | 协议层 | 相关字段 | 计算方式 | 访问宏 | |---------------|------------------------|-----------------------------------|-------------------------| | MAC 头 | `skb->mac_header` | `skb->data - skb->head` (初始时) | `skb_mac_header(skb)` | | IP 头 | `skb->network_header` | `skb->data - skb->head` | `skb_network_header(skb)` | | 传输层头 (TCP)| 无专用字段 | 通过 IP 头长度推算 | `tcp_hdr(skb)` | --- ### 总结图示 ``` 内存布局: 0x1000 [ headroom ] 0x1010 [ MAC头 ] 0x1020 [ IP头 ] 0x1040 [ TCP头 ] ^ ^ ^ ^ skb->head skb->mac_header skb->data skb->data+20 (偏移0x10) (此时偏移0x20) 调用 skb_reset_network_header(skb) 后: skb->network_header = 0x1020 - 0x1000 = 0x20 后续即使 data 移动: skb_pull(skb, 20); // data 指向 TCP 头 (0x1040) skb_network_header(skb) 仍返回 0x1000 + 0x20 = 0x1020 (IP头位置) ``` --- ### 相关问题 1. 为什么 `skb->head` 在数据包生命周期内保持不变? 2. `skb_pull()` 和 `skb_push()` 如何影响 `skb->data` 和协议头偏移量? 3. 如何通过 `skb_network_header(skb)` 安全访问 IP 头而不越界? 4. 当数据包被克隆(cloned)时,协议头偏移量是否会复制? [^1]: `__netif_receive_skb_core` 中通过 `skb->data - skb->head` 设置网络头偏移量。 [^2]: 协议栈通过固定偏移量访问协议头,确保处理效率。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值