目录
1 Linux的协议栈
2 Linux网络子系统
3 数据结构
设备描述net_device
套接字缓冲区sk_buff
Linux具有丰富的网络协议栈,范围从协议无关层(例如通用 socket 层接口或设备层)到各种具体的网络协议实现。
在TCP/IP网络分层模型里,整个协议栈被分成了物理层、链路层、网络层,传输层和应用层。物理层对应的是网卡和线缆,应用层对应的是我们常见的FTP等等各种应用。Linux实现的是链路层、网络层和传输层这三层。
在Linux内核实现中,链路层协议靠网卡驱动来实现,内核协议栈来实现网络层和传输层。内核对更上层的应用层提供socket接口来供用户进程访问。
Linux网络子系统需要:
支持不同的协议族 ( INET, INET6, UNIX, NETLINK...)
支持不同的网络设备
支持统一的BSD socket API
需要屏蔽协议、硬件、平台(API)的差异
因而采用分层结构:
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 在上层协议队列中进行排队,供以后进行处理。
设备驱动
网络体系结构的最底部是负责管理物理网络设备的设备驱动程序层。
网络接口由net_device结构描述。
net_device 数据结构的主要成员包括:
该结构可使用如下内核函数动态分配:
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 结构的初始化
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 结构,供驱动代码使用