skb_buf结构分析-(dm9000-2)

本文深入探讨Linux网络数据传输中的skb_buf结构,解释了如何在不同网络层间传输数据,以及如何在传输过程中添加和移除协议头。通过详细分析,读者能够更好地理解Linux网络数据处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载http://blog.youkuaiyun.com/qq405180763/article/details/8797236

        实际上skb_buf结构只是一块已经申请好的套接字缓冲区的指针和属性数据的描述集合,netdev_alloc_skb函数申请到一块套接字缓冲区后,返回记录这块缓冲区信息的skb_buf结构,在各个网络层传输的只是skb_buf结构,换句话说,仅仅是该套接字缓冲区的指针而已,各个网络层根据传来的指针,对指针进行操作,往已经申请好的固定套接字缓冲区里读写数据

       sk_buff结构的成员skb->head指向一个已分配的空间的头部,即申请到的整个缓冲区的头,skb->end指向该空间的尾部,这两个成员指针从空间创建之后,就不能被修改。skb->data指向分配空间中数据的头部,skb->tail指向数据的尾部,这两个值随着网络数据在各层之间的传递、修改,会被不断改动。刚开始接触skb_buf的时候会产生一种错误的认识,就是以为协议头都会是放在skb->head和skb->data这两个指针之间,但实际上skb_buf的操作函数都无法直接对这一段内存进行操作,所有的操作函数所做的就仅仅是修改skb->dataskb->tail这两个指针而已,向套接字缓冲区拷贝数据也是由其它函数来完成的,所以不管是从网卡接受的数据还是上层发下来的数据,协议头都是被放在了skb->data到skb->tail之间,通过skb_push前移skb->data加入协议头,通过skb_pull后移skb->data剥离协议头。四个指针间存在如下关系:
           

        sk_buff结构被不同的网络层(MAC或者其他二层链路协议,三层的IP,四层的TCP或UDP等)使用,并且其中的成员变量(一般是数据指针)在结构从一层向另一层传递时改变。L4向L3传递前会添加一个L4的头部,同样,L3向L2传递前,会添加一个L3的头部。添加头部比在不同层之间拷贝数据的效率更高。由于在缓冲区的头部添加数据意味着要修改指向缓冲区的指针,这是个复杂的操作,所以内核提供了一个函数skb_reserve将数据部分往后移。协议栈中的每一层在往下一层传递缓冲区前,第一件事就是调用skb_reserve在缓冲区的头部给协议头预留一定的空间,再用skb_push函数将协议头查到skb->data指针之前

       skb_reserve同样被设备驱动使用来对齐接收到包的包头。如果缓冲区向上层协议传递,旧的协议层的头部信息就没什么用了。例如,L2的头部只有在网络驱动处理L2的协议时有用,L3是不会关心它的信息的。但是,内核并没有把L2的头部从缓冲区中删除,而是用sk_buff结构中对应的头指针指向这个头,再把有效荷载的指针指向L3的头部这样做,可以节省CPU时间。当接收一个包时,处理n层协议头的函数从n-1层收到一个缓冲区,因为在n-1层处理的最后,skb->data是指向n-1层的数据开始指针,所以到了n层后skb->data就是指向n层协议的头。处理n层协议的函数把本层的指针(例如,L3对应的是skb- >nh指针)初始化为skb->data,取出本层的协议头指针,在处理n层协议的函数结束时,在把包传递给n+1层的处理函数前,它会把skb->data指针指向n层协议头的末尾,这正好是n+1层协议的协议头(参见下图)。

     

           skb_buf在各个网络层传输过程。

        在网络数据发送过程中,由netdev_alloc_skb申请得到套接字缓冲区后,skb_reserve将整个数据块往后移动MAX_TCP_HEADER个字节,预留出skb中协议头的最大长度,确保在套接字从上层不断往下层传递的过程中,有足够的协议头空间。在数据每传到一个网络层的时候,通过skb_push函数前移skb->data将协议头插入数据区,一直到链路层将所有层的协议头加入到套接字中,skb_push函数就是用来前移skb->data,在数据头部插入协议头的。最后由驱动程序把数据(skb->data到skb->tail之间部分)发给网卡,释放掉套接字缓存,网卡负责把数据发到网络上。发送中加协议头的方式如下图:

        在网络数据接收过程中,网卡收到数据触发中断,驱动程序响应中断接收网卡缓存中数据,因为网卡中以太网帧头的长度为14个字节,而紧接着的IP协议头需要在16字节对齐的边界上,所以在申请到套接字缓存后,用skb_reserve函数先预留出2字节的空间,确保IP协议头的16字节对齐。刚申请到的套接字缓存,其sbk_buf的skb->data和skb->tail指向同一地址,是没有数据的,用skb_put函数将skb->tail往后拉数据包长度个字节空间,通过返回的原skb->tail地址,把网络数据从网卡中拷贝到套接字缓存里,skb_put函数就是在skb->tail和skb->end之间加数据。接着将数据往上层发送,在每一层中用skb_pull函数或者移动skb->data指针的方式,将各个层对应的协议头从数据中剥离开来,一直到最上面的应用层,剩下真正的从其他机器发过来的有用数据。

 


<think>嗯,用户这次问的是Linux网络栈中两个关键字段skb->skb_iif和skb->dev->ifindex的区别和使用场景。看来用户是在深入分析网络协议栈实现,可能是内核开发者或网络方向研究人员。从用户之前的提问脉络看,ta已经对ip_route_output_key函数、路由查找流程有基础理解,现在聚焦到网络包路径追踪的核心字段了。这个问题涉及到网络数据包从接收到转发的全过程,需要拆解不同阶段的设备标识原理。用户引用的几个片段很有意思:-引用[1]中的deliver_skb展示了数据包交付时的设备传递逻辑-引用[2]说明了skb操作函数集-引用[4]提及__netif_receive_skb_core里处理设备时的“vlan剥除”和“gotoanother_round”机制这些正是解释差异的关键切入点。具体回答要解决三个层面问题:第一是基本定义差异:skb_iif是原始入口设备快照,始终不变;而skb_dev->ifindex会随着包的处理动态变化。比如vlan解封装后设备对象就被替换了。第二是生命周期对比:在netif_receive_skb阶段,skb_dev可能被修改的次数体现在引用[4]的“another_round”循环中——每次解封装都可能更新设备。但skb_iif在__netif_receive_skb_core开头设置后就不再改变。第三是使用场景:路由子系统的fib规则查找必须依赖原始入口设备(skb_iif),否则在多网卡环境下会选错路由表。而流量统计、netfilter等机制更关注当前设备(skb_dev)。用户可能还隐含想知道真实场景证据。可以举两个例子:当ICMP错误报文生成时,RFC规定必须包含原始入口接口索引,这时就用到skb_iif;当网桥收到包时,虽然skb_dev变成br0,但skb_iif仍记录着eth0的原始物理接口。考虑到用户的技术深度,应该准备扩展问题方向:比如OVS这种虚拟交换机如何篡改这两个字段,或者在容器网络命名空间里的特殊行为。</think> 以下是对`skb->skb_iif`与`skb->dev->ifindex`在Linux网络栈中的关系、区别及使用场景的详细解析: --- ### 一、核心区别与定义 #### 1. `skb->skb_iif` (Input Interface Index) - **物理属性**:记录数据包**最初进入内核时的物理接收接口索引**[^4] - **只读性质**:一旦在数据包接收路径(`__netif_receive_skb_core`)中被设置后 **不再修改** - **生命周期**: $$ \text{设置点}: \text{网卡驱动}\rightarrow \text{NAPI} \xrightarrow{\text{netif_receive_skb}} \text{协议栈}$$ ```c // 内核设置点 (net/core/dev.c) if (skb->skb_iif == 0) skb->skb_iif = skb->dev->ifindex; // 初始记录 [^4] ``` - **典型用途**: - 路由子系统的输入路径决策(`ip_route_input`) - ICMP错误报文中的原始接口信息 - Netfilter的`NF_INET_PRE_ROUTING`钩子处理 #### 2. `skb->dev->ifindex` (Current Device Index) - **逻辑属性**:表示数据包**当前所属的网络设备索引** - **动态变化**:在转发/处理过程中可能被修改多次: ```mermaid graph LR A[物理网卡eth0] -->|接收时 skb->dev=eth0| B[VLAN剥离] B -->|skb->dev=vlan10| C[网桥处理] C -->|转发到veth1| D[skb->dev=veth1] ``` - **修改场景**: - VLAN解封装(如引用[4]中`vlan剥除`流程) - 隧道封装/解封装 - 网桥转发或虚拟设备切换 --- ### 二、关键使用场景对比 #### 1. 路由决策阶段 | 字段 | 作用 | 示例 | |------|------|------| | `skb_iif` | 确定输入接口对应的路由表 | 多路由表策略(如`ip rule iif eth0 table 100`)| | `skb_dev->ifindex` | 检查当前设备状态 | 验证设备UP状态、MTU等 | #### 2. 桥接与转发场景 ```c // 网桥转发逻辑 (net/bridge/br_forward.c) if (should_deliver(p, skb)) { skb->dev = p->dev; // 更新当前设备! br_deliver(p, skb); } ``` - **`skb->dev`**:被显式修改为目标转发端口 - **`skb_iif`**:保持不变,仍为原始物理接口 #### 3. 协议处理层 - **TCP/UDP连接跟踪**: - `skb_iif` 用于绑定连接与入口接口 - `skb_dev` 用于校验当前路径可达性 - **Netfilter钩子**: ```c // PRE_ROUTING钩子使用原始接口 nf_hook(NF_INET_PRE_ROUTING, skb, skb->skb_iif, ...); // POST_ROUTING钩子使用当前设备 nf_hook(NF_INET_POST_ROUTING, skb, skb->dev->ifindex, ...); ``` --- ### 三、典型代码流程分析 以带VLAN的数据包接收为例(引用[4]扩展): ```mermaid graph TD A[eth0收到VLAN=10的帧] --> B[__netif_receive_skb_core] B --> C{skb_iif设置} C -->|skb->skb_iif=eth0_idx| D[VLAN处理] D -->|剥离VLAN标记| E[skb->dev更新为vlan10_dev] E --> F[协议处理ip_rcv] F -->|路由查找| G[使用skb->skb_iif=eth0_idx] G -->|转发决策| H[修改skb->dev为输出设备] ``` 1. **初始设置**: ```c // net/core/dev.c if (!skb->skb_iif) skb->skb_iif = dev->ifindex; // 此时dev=eth0 [^4] ``` 2. **VLAN处理**(引用[4]): ```c // vlan剥除后更新当前设备 skb->dev = vlan_dev; // ifindex变为vlan10的索引 __vlan_hwaccel_clear_tag(skb); // 清除VLAN标记 ``` 3. **路由决策**: ```c // 仍使用原始skb_iif=eth0_idx ip_route_input(skb, skb->skb_iif, ...); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值