Linux网络——协议栈、网络子系统及sk_buff数据结构

目录

1 Linux的协议栈

2 Linux网络子系统

3 数据结构

设备描述net_device

套接字缓冲区sk_buff


1 Linux的协议栈

Linux具有丰富的网络协议栈,范围从协议无关层(例如通用 socket 层接口或设备层)到各种具体的网络协议实现。

在TCP/IP网络分层模型里,整个协议栈被分成了物理层、链路层、网络层,传输层和应用层。物理层对应的是网卡和线缆,应用层对应的是我们常见的FTP等等各种应用。Linux实现的是链路层、网络层和传输层这三层。

在Linux内核实现中,链路层协议靠网卡驱动来实现,内核协议栈来实现网络层和传输层。内核对更上层的应用层提供socket接口来供用户进程访问。

Linux网络——协议栈、网络子系统及sk_buff数据结构_第1张图片

2 Linux网络子系统

Linux网络子系统需要:

  • 支持不同的协议族 ( INET, INET6, UNIX, NETLINK...)

  • 支持不同的网络设备

  • 支持统一的BSD socket API

  • 需要屏蔽协议、硬件、平台(API)的差异

因而采用分层结构:

Linux网络——协议栈、网络子系统及sk_buff数据结构_第2张图片

 Linux 网络子系统的顶部是系统调用接口层,为用户空间的应用程序提供访问内核网络子系统的方法。位于其下面的是一个协议无关层,提供通用方法来使用传输层协议。然后是具体协议的实现,在 Linux 中包括内嵌的协议 TCP、UDP,当然还有 IP。然后是设备无关层,提供协议与设备驱动通信的通用接口,最下面是设备驱动程序。

系统调用接口
为应用程序提供访问内核网络子系统的方法:Socket系统调用。

协议无关接口
实现一组通用函数来访问各种不同的协议:通过socket实现。Linux 中的 socket 使用struct sock来描述,这个结构包含了特定socket 所需要的所有状态信息,还包括socket 所使用的特定协议和在 socket 上可以执行的一些操作。

网络协议
网络协议层用于实现各种具体的网络协议,如: TCP、UDP 等。

设备无关接口
设备无关接口将协议与各种网络设备驱动连接在一起。这一层提供一组通用函数供底层网络设备驱动程序使用,让它们可以对高层协议栈进行操作。首先,设备驱动程序可能会通过调用 register_netdevice 或unregister_netdevice 在内核中进行注册或注销。调用者首先填写 net_device 结构,然后传递这个结构进行注册。内核调用它的 init 函数(如果定义了这种函数),然后执行一组健全性检查,并将新设备添加到设备列表中(内核中的活动设备链表)。

要从协议层向设备发送数据,需要使用dev_queue_xmit函数,这个函数对数据进行排队,并交由底层设备驱动程序进行最终传输报文的接收通常是使用netif_rx 执行的。当底层设备驱动程序接收到一个报文(包含在所分配的sk_buff中)时,就会通过调用 netif_rx 将 数据上传至设备无关层,然后,这个函数通过 netif_rx_schedule将 sk_buff 在上层协议队列中进行排队,供以后进行处理。

设备驱动
网络体系结构的最底部是负责管理物理网络设备的设备驱动程序层。

3 数据结构

设备描述net_device

网络接口由net_device结构描述。

net_device 数据结构的主要成员包括:

  • char name[IFNAMSIZ] 设备名,如:eth%d
  • unsigned long state 设备状态
  • unsigned long base_addr I/O 基地址
  • unsigned int irq 中断号

该结构可使用如下内核函数动态分配:
1、struct net_device *alloc_netdev(int sizeof_priv, const char *mask, void (*setup)(struct net_device *))
sizeof_priv 私有数据区大小; mask:设备名; setup 初始化函数
2、struct net_device *alloc_etherdev(int sizeof_priv)

3、int (*init)(struct net_device *dev)

初始化函数。该函数在register_netdev时被调用来完成对 net_device 结构的初始化

套接字缓冲区sk_buff

Linux内核中的每个网络数据包都由一个套接字缓冲区结构 struct sk_buff 描述,即一个sk_buff结构就是一个包,指向sk_buff的指针通常被称做skb。

sk_buff是贯穿网络协议栈各层的一个最关键数据结构,一个封包就存储在这个数据结构中。所有网络分层都会使用这个结构来存储其报头、有关数据的信息,以及用来协调工作的其他内部信息。

struct sk_buff {
         /* These two members must be first. */
         /*链表,放在结构头,用于强制转化得到,链表头为skb_buff_head*/
         struct sk_buff            *next;
         struct sk_buff            *prev;
        
         /*指向拥有此缓冲区的sock数据结构,当数据在本地
                   产生或者正由本地进程接收时,就需要这个指针
                   因为该数据以及套接字相关的信息会由L4
                   以及用户应用程序使用。当缓冲去只是被
                   转发时,该指针为NULL*/
         struct sock                  *sk;
        
         /*通常只对一个以及接收的封包才有意义
         这是一个时间戳记,用于表示封包何时被
         接收,或者有时用于表示封包预定传输时间
         */
         struct skb_timeval     tstamp;
        
         /*此字段描述一个网络设备,该字段的作用与该SKB是发送包还是
         接受包有关*/
         struct net_device      *dev;
 
         /*接收报文的原始网络设备,主要用于流量控制*/
         struct net_device      *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;/*二层协议首部*/
 
         /*目的路由缓存项*/
         struct  dst_entry      *dst;
 
         /*IPSec协议用来跟踪传输的信息*/
         struct        sec_path  *sp;
 
         /*
          * This is the control buffer. It is free to use for every
          * layer. Please put your private variables there. If you
          * want to keep them across layers you have to do a skb_clone()
          * first. This is owned by whoever has the skb queued ATM.
          */
          /*SKB信息控制块,是每层协议的私有信息存储空间,由每层协议
自己使用并维护,并只有在本层有效*/
         char                    cb[48];
        
         /*数据总长度,包括线性缓冲区数据长度(data指向),SG类型的聚合分散I/O的数据
                   以及FRAGLIST类型的聚合分散I/O的数据长度,也包括协议首部的长度*/
         unsigned int               len,
        
         /*SG类型和FRAGLIST类型聚合分散I/O存储区中的数据长度*/
                                     data_len,
                                    
         /*二层首部长度。实际长度与网络介质有关,在以太网中为以太网桢首部的长度*/
                                     mac_len;
         union {
                   __wsum             csum;
                   __u32                 csum_offset;
         };
        
         /*发送或转发QOS类别。*/
         __u32                          priority;
        
         /*表示此skb在本地允许分片*/
         __u8                            local_df:1,
 
         /*标记所属SKB是否已经克隆*/
                                     cloned:1,
 
         /*标记传输层校验和的状态*/
                                     ip_summed:2,
                                    
         /*标识payload是否被单独引用,不存在协议首部*/      
                                     nohdr:1,
 
         /*防火墙使用*/
                                     nfctinfo:3;
 
         /*桢类型,分类是由二层目的地址来决定的,对于以太网设备
         来说,该字段由eth_type_trans()初始化*/
         __u8                            pkt_type:3,
 
         /*当前克隆状态*/
                                     fclone:2,
 
                                     ipvs_property:1;
        
         /*从二层设备角度看到的上层协议,即链路层承载的三层协议类型*/
         __be16                        protocol;
        
         /*skb析构函数指针,在释放skb时被调用,完成某些必要的工作*/
         void                    (*destructor)(struct sk_buff *skb);
 
/*在数据结构中定义的宏不能编译成模块;
原因在于内核编译之后,开启该选项所得
的多数结果为不可逆的,一般而言,任何
引起内核数据结构改变的选项,都不适合
编译成一个模块,编译选项和特定的#ifdef
符号相配,以了解一个代码区块什么时候
会包含到内核中,这种关联在源码树的
Kconfig文件中*/
#ifdef CONFIG_NETFILTER
         /*防火墙使用*/
         struct nf_conntrack  *nfct;
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
         struct sk_buff            *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
         /*防火墙使用*/
         struct nf_bridge_info         *nf_bridge;
#endif
#endif /* CONFIG_NETFILTER */
#ifdef CONFIG_NET_SCHED
/*用于流量控制*/
         __u16                          tc_index;  /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
         /*用于流量控制*/
         __u16                          tc_verd;    /* traffic control verdict */
#endif
#endif
#ifdef CONFIG_NET_DMA
         dma_cookie_t            dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
         __u32                          secmark;
#endif
 
         __u32                          mark;
 
         /* These elements must be at the end, see alloc_skb() for details.  */
         /*全部数据的长度+该结构的长度,如果申请了一个len字节的缓冲区
         该字段为len+sizeof(sk_buff)*/
         unsigned int               truesize;
        
         /*引用计数,使用这个缓冲区的实例的数目*/
         atomic_t             users;
        
         /*head和end指向数据在内存中的起始和结束位置,即已经分配的缓冲区空间的开端和尾端
         data和tail指向数据区域的起始和结束位置,即实际数据的开端和尾端*/
         unsigned char            *head,
                                     *data,
                                     *tail,
                                     *end;
};

Skb操作函数
操作sk_buff的内核函数如下
struct sk_buff *alloc_skb(unsigned int len, int priority)
分配一个sk_buff 结构,供协议栈代码使用

struct sk_buff *dev_alloc_skb(unsigned int len)
分配一个sk_buff 结构,供驱动代码使用

unsigned char *skb_push(struct sk_buff *skb, int len)
向后移动skb的tail指针,并返回tail移动之前的值。函数常用来:

unsigned char *skb_put(struct sk_buff *skb, int len)
向前移动skb的head指针,并返回head移动之后的值。函数常用来:

kfree_skb(struct sk_buff *skb)
释放一个sk_buff 结构,供协议栈代码使用

dev_kfree_skb(struct sk_buff *skb)
释放一个sk_buff 结构,供驱动代码使用
 

你可能感兴趣的:(linux网络,网络,linux,网络协议)