cataloghtml
1. 经过套接字通讯 2. 网络实现的分层模型 3. 网络命名空间 4. 套接字缓冲区 5. 网络访问层 6. 网络层 7. 传输层 8. 应用层 9. 内核内部的网络通讯
1. 经过套接字通讯node
Linux的设计思想是"万物皆文件",从开发者角度来看,外部设备在Linux(以及UNIX)中都是普通文件,经过正常的读写操做便可访问,可是对于网卡而言,状况会复杂的多。网卡的运做方式与普通的块设备和字符设备彻底不一样,一个缘由是(全部层次)使用了许多不一样的通讯协议,为创建链接须要指定许多选项,且没法在打开设备文件时完成这些任务,所以,在/dev目录下没有与网卡对应的项
为此,Linux采用的解决方案是将一种称为套接字的特殊结构用做到网络实现的接口,这种方案如今已经成为工业标准,POSIX标准中也定义了套接字linux
1. 套接字用于定义和创建网络链接,以即可以用操做inode的普通方法(特别是读写操做)来访问网络 2. 从开发者角度,建立套接字的最终结果是一个文件描述符,它不只提供全部的标准函数,还包括几个加强的函数 3. 用于实际数据交换的接口对全部的协议和地址族都是相同的 4. 在建立套接字时,不只要区分地址和协议族,还要区分基于流的通讯、基于数据报的通讯
0x1: 建立套接字数组
套接字不只能够用于各类传输协议的IP链接,也能够用于内核支持的全部其余地址和协议类型(例如: IPX、Appletalk、本地UNIX套接字、DECNet、Netlink、以及在<socket.h>中列出的许多其余类型)。所以,在建立套接字时,必须指定所须要的地址和协议类型的组合
须要注意的是,每一个地址族都只支持一个协议族(所以在建立socket的时候第三个参数经常为0便可,即通知函数使用适当的默认协议),并且只能区分面向流的通讯和面向数据报的通讯安全
0x2: 使用套接字cookie
0x3: 数据报套接字UDP是创建在IP链接之上的第二种大量使用的传输协议,UDP标识User Datagram Protocol(用户数据报协议)网络
Relevant Link:数据结构
http://www.cnblogs.com/LittleHann/p/3875451.html
2. 网络实现的分层模型app
内核网络子系统的实现和TCP/IP参考模型很是类似,相关的C语言代码划分为不一样层次,各层次都有明肯定义的任务,各个层次只能经过明肯定义的接口与上下紧邻的层次通讯,这种作法的好处在于框架
1. 能够组合使用各类设备、传输机制和协议 2. 一般的以太网卡不只可用于创建因特网(IP)链接,还能够在其上传输其余类型的协议,如Appletalk、IPX,而无须对网卡的设备驱动程序作任何类型的修改
分层模型不只反映在网络子系统的设计上,并且也反映在数据传输的方式上(对各层产生和传输的数据进行封装的方式)
3. 网络命名空间
内核的许多部分包含在命名空间中,经过命名空间能够创建系统的多个虚拟视图,而且彼此分隔开来,每一个实例看起来像是一台运行Linux的独立机器,可是在一台物理机器上,能够同时运行许多这样的实例,从内核2.6.24开始,Linux内核也开始对网络子系统采用命名空间,这对网络子系统增长了一些额外的复杂性
4. 套接字缓冲区
在内核分析(收到的)网络分组时,底层协议的数据将传递到更高的层,发送数据时顺序相反,各类协议产生的数据(首部和载荷)依次向更低的层传递,直至最终发送。这些操做的速度对网络子系统的性能有决定性的影响,所以内核使用了一种特殊的结构,称为套接字缓冲区(socket buffer)
/source/include/linux/skbuff.h
struct sk_buff { /* These two members must be first. */ struct sk_buff *next; struct sk_buff *prev; struct sock *sk; ktime_t tstamp; struct net_device *dev; unsigned long _skb_dst; #ifdef CONFIG_XFRM struct sec_path *sp; #endif /* * 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. */ char cb[48]; unsigned int len, data_len; __u16 mac_len, hdr_len; union { __wsum csum; struct { __u16 csum_start; __u16 csum_offset; }; }; __u32 priority; kmemcheck_bitfield_begin(flags1); __u8 local_df:1, cloned:1, ip_summed:2, nohdr:1, nfctinfo:3; __u8 pkt_type:3, fclone:2, ipvs_property:1, peeked:1, nf_trace:1; __be16 protocol:16; kmemcheck_bitfield_end(flags1); void (*destructor)(struct sk_buff *skb); #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) struct nf_conntrack *nfct; struct sk_buff *nfct_reasm; #endif #ifdef CONFIG_BRIDGE_NETFILTER struct nf_bridge_info *nf_bridge; #endif int iif; #ifdef CONFIG_NET_SCHED __u16 tc_index; /* traffic control index */ #ifdef CONFIG_NET_CLS_ACT __u16 tc_verd; /* traffic control verdict */ #endif #endif kmemcheck_bitfield_begin(flags2); __u16 queue_mapping:16; #ifdef CONFIG_IPV6_NDISC_NODETYPE __u8 ndisc_nodetype:2; #endif kmemcheck_bitfield_end(flags2); /* 0/14 bit hole */ #ifdef CONFIG_NET_DMA dma_cookie_t dma_cookie; #endif #ifdef CONFIG_NETWORK_SECMARK __u32 secmark; #endif __u32 mark; __u16 vlan_tci; sk_buff_data_t transport_header; sk_buff_data_t network_header; sk_buff_data_t mac_header; /* These elements must be at the end, see alloc_skb() for details. */ sk_buff_data_t tail; sk_buff_data_t end; unsigned char *head, *data; unsigned int truesize; atomic_t users; };
套接字缓冲区用于在网络实现的各个层次之间交换数据,而无须来回复制分组数据,对性能带来了很大的提升。套接字结构是网络子系统的基石之一,由于在产生和分析分组时,在各个协议层次上都须要处理该结构
0x1: 使用套接字缓冲区管理数据
套接字缓冲区经过其中包含的各类指针与一个内存区域相关联,网络分组的数据就位于该区域中,套接字缓冲区的基本思想是
经过操做指针来增删协议首部
1. head和end指向数据在内存中的起始和结束位置: 这个区域可能大于实际须要的长度,由于在产生分组时,商不清楚分组的长度 2. data和tail指向协议数据区域的起始和结束位置 3. mac_header指向MAC协议首部的起始 4. network_header指向网络层协议首部的起始 5. transport_header指向传输层协议首部的起始
这使得内核能够将套接字缓冲区用于全部协议类型,正确地解释数据须要作简单的类型转换,为此内核提供了几个辅助函数
data和tail使得在不一样协议层之间传递数据时,无须显式的复制操做
1. 在一个新分组产生时,TCP层首先在用户空间中分配内存来容纳该分组数据(首部和载荷),分配的空间大于数据实际须要的长度,所以较低的协议层能够进一步增长首部 2. 分配一个套接字缓冲区,使得head和end分别指向新分配的较低层协议内存区的起始和结束地址,而TCP数据位于data和tail之间 3. 在套接字缓冲区传递到互联网网络层时,必须增长一个新层,只须要向已经分配但还没有占用的那部份内存空间写入数据便可,除了data以外的全部指针都不变,data如今指向IP首部的起始处,再往下的各层会重复一样的操做,直至分组完成,即将经过网络发送 4. 对接收的分组进行分析的过程是相似的,分组数据复制到内核分配的一个内存区中,并在整个分析期间一直处于该内存区中,与该分组相关联的套接字缓冲区在各层之间顺序传递,各层依次将其中的各个指针设置为正确值 //套接字缓冲区之因此可使用头尾指针进行跨层传递处理的缘由在于,TCP/IP协议栈各层之间本质上是在进行逐层扩大的数据包装,从内存空间视角看到的就是从栈顶逐渐向栈底发展,因此能够经过预申请一块大内存,在逐层封装的时候经过移动头尾指针实现协议层切换
套接字缓冲区须要不少指针来表示缓冲区中内容的不容部分,因为网络子系统必须保证较低的内存占用和较高的处理速度,于是对于struct sk_buff来讲,咱们须要保持该结构的长度尽量小
0x2: 管理套接字缓冲区数据
5. 网络访问层
网络访问层主要负责在计算机之间传输信息,与网卡的设备驱动程序直接协做
0x1: 网络设备的表示
在内核中,每一个网络设备都表示为net_device结构的一个实例,在分配并填充该结构的一个实例以后,必须用net/core/dev.c中的register_netdev函数将其注册到内核,该函数完成一些初始化任务,并将该设备注册到通用设备机制内,这会建立一个sysfs项
[root@iZ23er0navtZ ~]# ll /sys/class/net total 0 lrwxrwxrwx 1 root root 0 Dec 9 20:00 eth0 -> ../../devices/vif-0/net/eth0 lrwxrwxrwx 1 root root 0 Dec 9 20:00 eth1 -> ../../devices/vif-1/net/eth1 lrwxrwxrwx 1 root root 0 Dec 9 20:00 lo -> ../../devices/virtual/net/lo
0x2: 接收分组
分组到达内核的时间是不可预测的,全部现代的设备驱动程序都使用中断来通知内核有分组到达,网络驱动程序对特定于设备的中断设置了一个处理例程,所以每当该中断被触发时(即分组到达),内核都会自动调用该处理例程,将数据从网卡传输到物理内存,或通知内核在必定时间后进行处理
几乎全部的网卡都支持DMA模式,可以自行将数据传输到物理内存,但这些数据仍然须要解释和处理,这会在稍后进行
1. 传统方法
下面学习一个分组到达网络适配器以后,该分组穿过内核到达网络层函数的路径
1. 分组是在中断上下文中接收到的,因此处理例程只能执行一些基本的任务,避免系统(或当前CPU)的其余任务延迟太长时间 2. net_interrupt是由设备驱动程序设置的中断处理程序,它将肯定该中断是否真的是由接收到的分组引起的,若是确实如此,则控制将转移到net_rx 3. net_rx函数也是特定于网卡的 1) 首先建立一个新的套接字缓冲区 2) 分组的内容接下来从网卡传输到缓冲区(即进入物理内存) 3) 而后使用内核源码中针对各类传输类型的库函数来分析首部数据,这项分析将肯定分组数据所使用的网络层协议,例如IP协议 4. netif_rx函数不是特定于网络驱动程序的,该函数位于net/core/dev.c,调用该函数,标志着控制由特定于网卡的代码转移到了网络层的通用接口部分。该函数的做用在于,将接收到的分组放置到一个特定于CPU的等待队列上,并退出中断上下文,使得CPU能够执行其余任务 5. 内核在全局定义的softnet_data数组中管理进出分组的等待队列,数据项类型为softnet_data
2. 对高速接口的支持
0x3: 发送分组
在网络层中特定于协议的函数通知网络访问层处理由套接字缓冲区定义的一个分组时,将发送完成的分组
6. 网络层
网络访问层仍然收到传输介质的性质以及相关适配器的设备驱动程序的很大影响,网络层(具体地说是IP协议)与网络适配器的硬件性质几乎是彻底分离的
须要明白的是,硬件的性质是须要将较大的分组分隔为较小的单位的首要缘由,由于每一种传输技术所支持的分组长度都有一个最大值,IP协议必须将较大的分组划分为较小的单位,由接收方从新组合,更高层协议不会注意到这一点,划分后分组的长度取决于特定传输协议的能力
0x1: IPv4
ip_rcv函数是网络层的入口点,分组向上穿过内核的路线以下
发送和接收操做的程序流程并不老是分离的,若是分组只经过当前计算机转发,那么发送和接收操做是交织的,这种分组不会传递到更高的协议层(或应用程序),而是当即离开计算机,发往新的目的地
0x2: 接收分组
1. 在分组(以及对应的套接字缓冲区,其中的指针已经设置了适当的值)转发到ip_rcv以后,必须检查接收到的信息,确保它是正确的 1) 主要检查计算的校验和与首部中存储的校验和是否一致 2) 其余的检查包括分组是否达到了IP首部的最小长度 3) 以及分组的协议是否确实是IPv4(IPv6的接收例程是另外一个) 2. 在进行了这些检查以后,内核并不当即继续对分组的处理,而是调用一个netfilter挂钩,使得用户空间能够对分组数据进行操做,netfilter挂钩插入到内核源代码中定义好的各个位置,使得分组可以被外部动态操做,挂钩存在于网络系统的各个位置,每种挂钩都有一个特别的标记,例如: NF_IP_POST_ROUTING 3. 在内核到达一个挂钩位置时,将在用户空间调用对该标记支持的例程,接下来,在另外一个内核函数中继续内核端的处理(分组可能在这个环节被修改) 4. 在下一步中,接收到的分组到达一个十字路口,此时须要判断该分组的目的地是本地系统仍是远程计算机。根据对分组目的地的判断,须要将分组转发到更高层、或转到互联网络层的输出路径上 5. ip_route_input负责选择路由,判断路由的结果是,选择一个函数,进行进一步的分组结果,可用的函数是 1) ip_local_deliver: 分组是交付到本地计算机下一个更高层的 2) ip_forward: 分组是转发到网络中的另外一台计算机
0x3: 交付到本地传输层
若是分组的目的地是本地计算机,ip_local_deliver必须设法找到一个适当的传输层函数,将分组转送过去,IP分组一般对应的传输层协议是TCP或UDP
0x4: 分组转发
IP分组可能如上所述交付给本地计算机处理,它们也可能离开互联网络层,转发到另外一台计算机,而不牵涉本地计算机的高层协议实例,分组的目标地址可分为如下两类
1. 目标计算机在某个本地网络中,发送计算机与该网络有链接 2. 目标计算机在地理上属于远程计算机,不链接到本地网络,只能经过网关访问
该信息由路由表(routing table)提供,路由表由内核经过多种数据结构实现并管理。在接收分组时调用的ip_route_input函数充当路由实现的接口,这一方面是由于该函数可以识别出分组是交付到本地仍是转发出去,另外一方面是该函数可以找到通向目标地址的路由。目标地址存储在套接字缓冲区的dst字段中
0x5: 发送分组
内核提供了几个经过互联网络层发送数据的函数,可由较高协议层使用,其中ip_queue_xmit是最常使用的一个
0x6: netfilter
netfilter是一个Linux内核框架,使得能够根据动态定义的条件来过滤和操做分组,这显著增长了可能的网络选项的数目,从简单的防火墙,到对网络通讯数据的详细分析,甚至更复杂的依赖于状态的分组过滤器
1. 扩展网络功能
1. 根据状态及其条件,对不一样数据流方向(进入、外出、转发)进行分组过滤(packet filtering) 2. NAT(network address translation 网络地址转换)根据某些规则来转换源地址和目标地址(一般用于IP假装或透明代理) 3. 分组处理(packet manghing)和操做(manipulation),根据特定的规则拆分和修改分组
能够经过在运行期间动态向内核载入模块来加强netfilter功能,一个定义好的规则集,告知内核在什么时候使用各个模块的代码。内核和netfilter之间的接口保持在很小的规则上,尽量使两个领域彼此隔离,避免两者的互相干扰并改进网络代码的稳定性
netfilter挂钩位于内核中的各个位置,以支持netfilter代码的执行,这些不只用于IPv4,也用于IPv6和DECNET协议,netfilter实现划分为以下几个部分
1. 内核代码中的挂钩,位于网络实现的核心,用于调用netfilter代码 2. netfilter模块,其代码挂钩内部调用,但其独立于其他的网络代码,一组标准模块提供了经常使用的函数,但能够在扩展模块中定义用户相关的函数
2. 调用挂钩函数
在经过挂钩执行netfilter代码时,网络层的函数将会被中断。挂钩的一个重要特性是,它们将一个函数划分为两部分
1. 前一部分在netfilter代码调用前运行 2. 后一部分在netfilter代码调用后运行
3. 扫描挂钩表
若是至少注册了一个挂钩函数并须要调用,那么会调用nf_hook_slow,全部挂钩都保存在二维数组nf_hooks中
4. 激活挂钩函数
每一个hook函数都返回下列值之一
1. NF_ACCEPT: 表示接受分组,这意味着所述例程没有修改数据,内核将继续使用未修改的分组,使之穿过网络实现中剩余的协议层(或经过后续的挂钩) 2. NF_STOLEN: 表示挂钩函数"窃取"了一个分组并处理该分组,此时,该分组已经与内核无关,没必要再调用其余挂钩,还必须取消其余协议层的处理 3. NF_DROP: 经过内核丢弃该分组,如同NF_STOLEN,其余挂钩或网络层的处理都再也不须要了,套接字缓冲区(和分组)占用的内存空间能够释放,由于其中包含的数据能够被丢弃,例如挂钩可能认定分组是损坏的 4. NF_QUEUE: 将分组置于一个等待队列上,以便其数据能够由用户空间代码处理,不会执行其余挂钩函数 5. NF_REPEAT: 表示再次调用该挂钩
除非全部挂钩函数都返回NF_ACCEPT(NF_REPEAT不是最终结果),不然分组不会在网络子系统进一步处理,全部其余的分组,不是被丢弃,就是由netfilter子系统处理
内核提供了一个挂钩函数的集合,使得没必要为每一个场合都单独定义挂钩函数,这些称为iptables,用于分组的高层处理,它们使用用户空间工具iptables配置
0x7: IPv6
7. 传输层
两个基于IP的主要传输协议分别是UDP和TCP,前者用于发送数据报,后者可创建安全的、面向链接的服务
0x1: UDP
0x2: TCP
TCP提供的函数比UDP多得多,所以在其内核中的实现要困可贵多,也牵涉更广的内容。TCP链接老是处于某个明肯定义的状态,各个状态之间遵循必定的规范进行迁移
8. 应用层
套接字将UNIX隐喻"万物皆文件"应用到了网络链接上,内核与用户空间套接字之间的接口实如今C标准库中,使用了socketcall系统调用(老的Linux内核采用多路复用方式,新内核已经将多路复用拆解到了单独的系统调用中了)
Linux采用了内核套接字的概念,使得与用户空间中的套接字的通讯尽量简单,对程序使用的每一个套接字来讲,都对应于
1. socket结构实例: 充当向下(到内核) 2. sock结构实例: 充当向上(到用户空间)接口
0x1: socekt数据结构
0x2: 套接字和文件
在链接创建后,用户空间进程使用普通的文件操做来访问套接字,因为VFS层的开放结构,内核只须要不多的工做。在VFS虚拟文件系统的VFS inode中,每一个套接字都分配了一个该类型的inode,inode又关联到另外一个与普通文件相关的结构,用于操做文件的函数保存在一个单独的指针表中
<fs.h> struct inode { .. struct file_operations *i_fop; .. }
所以,对套接字文件描述符的文件操做,能够透明地重定向到网络子系统的代码
9. 内核内部的网络通讯
与其余主机通讯,不仅是用户层应用程序的需求,内核一样须要与其余计算机通讯,例如网络文件系统如CIFS或NCPFS都依赖于内核内部提供的网络通讯支持。同时,内核各组件之间也须要进行通讯,以及用户层和内核之间的通讯,netlink机制提供了所需的框架
0x1: 通讯函数
0x2: netlink机制
netlink是一种基于网络的机制,容许在内核内部以及内核与用户层之间进行通讯,它的思想是,基于BSD的网络套接字使用网络框架在内核和用户层之间进行通讯,但netlink套接字大大扩展了可能的用途,该机制不只仅用于网络通讯,其更重要的用户是对象模型,它使用netlink套接字将各类关于内核事务的状态信息传递到用户层,其中包括新设备的注册和移除、硬件层次上发生的特别事件等
内核中还有其余一些可选的方法可以实现相似的功能,例如procfs或sysfs中的文件,但与这些方法相比,netlink机制有一些很明显的优点
1. 任何一方都不须要轮询,若是经过文件传递状态信息,那么用户层须要不断检查是否有新消息到达 2. 系统调用和ioctl也可以从用户层向内核传递信息,但比简单的netlink链接更难于实现 3. 使用netlink不会与模块有任何冲突,但模块和系统调用显然配合的不是很好 4. 内核能够直接向用户层发送信息,而无须用户层事先请求,使用文件也能够作到,但系统调用和ioctl是不可能的 5. 除了标准的套接字,用户空间应用程序不须要使用其余东西来与内核交互
netlink只支持数据报信息,但提供了双向通讯,另外,netlink不只支持单播消息,也能够进行多播,相似于其余套接字的机制,netlink的工做方式是异步的
Relevant Link:
Copyright (c) 2015 LittleHann All rights reserved