这本书主要涉及了Linux内核网络协议栈的实现和它背后的理论。你会在后续章节发现更深层次和更细节地针对网络子系统的分析和它的结构。我不会讨论和网络没有直接关系的话题内容,比如你在读内核里网络代码的时候会遇到锁,同步,SMP,原子操作等等。关于这些内容,网上有很多资源。相反,聚焦在内核态网络相关的却少有更新的资源。基于此,我主要是描述Linux内核网络协议栈上的数据包传输,和他们在多个网络协议层和子系统之间的交互,也就是这么多的网络协议是如何实现的。
这本书不是臃肿的,一行行的代码走查。我主要关心每层网络协议的实现本质,理论指导,和它们的实现原理。在最近几年,Linux操作系统被证明是成功,可靠,稳定且流行的操作系统。似乎它的欢迎程度在各行业范围内稳步地增长,从主机,数据中心,核心路由器,网站服务器到嵌入式设备,比如无线路由器,机顶盒,医疗仪器,导航设备(如GPS设备),消费电子等产品。许多半导体厂商使用Linux为它们BSP的基础。Linux操作系统是从1991年一个名叫Linus Torvalds的毕业生开始的,基于UNIX操作系统,被证明是严谨的可靠的操作系统,是老牌商业操作系统的有力对手。
Linux一开始一款基于X86的操作系统,但被移植到非常广范围的处理器上,包括ARM,PowerPC,MIPS,SPARC等等。安卓操作系统也是基于Linux内核,今天被广泛使用在平板和智能手机上,很显然将来也能在智能电视上流行。除了安卓,谷歌还为内核网络里一些特性做出了贡献。Linux是一个开源项目,这是因为此,它比其他商业操作系统有着优势:在基于GPL授权下,它的源代码是很容易拿到的。其他开源的操作系统,比如不同类型的BSD,有着极少的欢迎度。
在这章节我必须提下OpenSolaris项目。这个项目是由Sun Microsystems公司发起的,没有达到Linux的受欢迎度。在庞大的活跃的Linux开发者社区中,一些贡献代码都是以他们服务的公司为代表的,还有是自愿贡献的代码。所有的内核开发进程都可以通过内核邮件列表获取到。有一个中央邮件列表,Linux内核邮件列表(LKML)和许多子系统有他们各自的邮件列表。贡献代码是通过向对应的内核邮件列表和管理者发送打包文件实现的,这些打包文件会在邮件列表上讨论。
Linux内核网络协议栈是Linux内核里一个非常重要的子系统。很难找到一个以Linux为基础的系统,无论它是桌面版,服务器版,移动设备版还是其他嵌入式版本不使用网络的。即使最罕见的情况,一个设备没有任何网络硬件设备,你也仍然要使用网络功能(可能不知不觉地),比如当你使用X-Windows时,X-Windows本身就是基于client-server网络的。更大范围的项目都和Linux网络协议栈有关系,从核心路由器到小型嵌入式设备。这些项目中的一些专门处理添加些特殊厂家的特性。比如,有些硬件厂家在一些网络设备里实现了通用段卸载(GSO)。GSO是内核网络协议栈的一个网络特性,就是在Tx路径里把一个大数据包飞哥成更小的块。很多硬件厂商在他们的网络设备里硬件实现了生成校验码。校验码是一种通过计算数据包的摘要值并把它贴到数据包的后面来确保数据包在传输过程中没有被破坏的机制。
很多项目为Linux提供了一些安全增强机制。有时候这些增强机制要求在网络子系统里做一些改变,就像你将在第三章看到的那样,讨论Openwall GNU/*/Linux项目。在嵌入式设备的竞争市场上,许多无线路由器是以Linux为基础的。举个例子,就是 WRT54GL Linksys路由器就是运行的Linux。也有一个开源的,以Linux为基础的操作系统能运行在WRT54GL设备上(也可以运行在其他设备上),它的名字是OpenWrt,有着一大群活跃的开发者社区支撑着它。(详见https://openwrt.org/)
学习各种协议是如何被Linux内核网络协议栈实现的,并熟悉主要的数据结构和一个数据包的主要传输路径将会从本质上更好地理解内核协议栈。
Linux网络协议栈
根据OSI模型,有7层网络。最底层的是物理层,也就是硬件层,最高层是应用层,也就是用户态软件程序运行的地方。我们来描述下这七层:
1、物理层:处理电信号和底层细节。
2、数据链路层:在端点间处理数据传输。最通用的数据链路层是以太网。Linux以太网网络设备驱动就是在这一层。
3、网络层:处理数据包转发和主机地址管理。这本书里,我回讨论Linux内核网络子系统最通用的网络层协议:IPv4和IPv6。还有其他不怎么通用的网络层实现,比如DECnet,但他们不会被讨论。
4、协议层或传输层:处理两个业务点之间的数据。TCP和UDP协议是广为人知的协议。
5、会话层:处理两个端点之间的会话。
6、表示层:处理分发和格式化。
7、应用层:给终端用户的应用程序提供网络服务。
图1-1 显示OSI模型的七层结构.
Figure 1-1. The OSI seven-layer model
图1-2显示了Linux内核网络协议栈处理的三层。
在这张图中L2,L3和L4分别对应7层模型里的数据链路层,网络层和传输层。Linux内核协议栈的本质是把从L2(网络设备驱动程序)里进来的数据包传递给L3层(网络层,通常就是IPv4或IPv6),随后如果是本地的数据包就传给L4层(传输层,比如TCP或UDP,监听的socket),如果数据包应该被转发,那就回给L2层。出去的数据包是本地生成从L4传给L3,随后通过网络设备驱动程序传给L2层做实际传输。在这条路上有很多步骤,有很多事情发生了。比如:
- 数据包可以根据协议规则被改变(比如,因为IPsec规则或者NAT规则)。
- 数据包可以被丢弃。
- 数据包能导致一条错误信息被发送出去
- 数据包可以被整合
- 数据包可以被分片
- 数据包的校验值应该被计算。
Figure 1-2. Linux内核网络层
内核不处理L4层以上的任何层业务;那些层(会话层,表示层和应用层)由用户态应用程序处理。物理层也不由Linux内核处理。
如果你觉得不知所措,不要担心。在接下来的章节,你会更深入地学到这里描述的所有内容。
网络设备
底层,第二层(L2),像图1-2看到的那样。网络设备驱动程序工作在这层。
这本书不是关于网络设备驱动开发的,因为它聚焦在Linux内核网络协议栈。
我会简要描述下net_device结构体,代表了一个网络设备,和与它相关的一些内容。
为了更好地了解网络协议栈,你应该对网络设备结构体有个基本的了解。
设备参数-像MTU大小(对以太网来说一般是1500字节)决定了一个数据包是否要分片。
net_device是个非常大的结构体,包含以下设备参数:
· 设备的中断数量
· 设备的MTU
· 设备的MAC地址
· 设备名称 (比如 eth0 或 eth1).
· 设备标记 (例如, 是Link Down的还是Link Up的).
· 设备相关的组播地址链表.
· 混杂模式计数 (本章稍后讨论).
· 设备支持的特性 (比如GSO 或 GRO 硬件卸载).
· 网络设备回调函数对象(net_device_ops对象)包含函数指针,比如打开和关闭一个网络设备,开始传输,改变网络设备的MTU值等等。
· ethtoll回调调用对象,通过命令行工具ethtool获取设备信息。
· Tx和Rx queues数量,如果设备支持多队列的话。
· 这个设备上最后一次发送数据包的时间戳。
· 这个设备上最后一次接收数据包的时间戳。
以下列出net_device结构体的一些成员的定义来给你展示第一印象:
struct net_device {
unsigned int irq; /* device IRQ number */
. . .
const struct net_device_ops *netdev_ops;
. . .
unsigned int mtu;
. . .
unsigned int promiscuity;
. . .
unsigned char *dev_addr;
. . .
};
(include/linux/netdevice.h)
这本书的附录A包含了一份非常详细的net_device结构体描述以及它的大部分成员。在那份附录里你会看到irq,mtu和稍后本章节提到的其他成员。
当混杂计数器大于0时,网络协议栈不会丢弃目的地址不是本机地址的数据包。这是因为,比如,网络包分析仪(sniffers)像tcpdump和wireshark,需要在用户空间打开原始socket并想接收这个类型的数据传输。它是个计数器,而不是个布尔值为了能同时打开多个sniffers:打开每一个sniffer都会将计数器加一。每一个sniffer关闭的时候,混杂计数器就会减一;如果混杂计数器为0时,说明没有sniffer在运行了,设备就退出了混杂模式。
当浏览内核网络核心源代码时,你可能会在好几个地方看到关键词NAPI(New API),是如今大部分网络设备驱动程序实现的一个特点。你应该了解NAPI是什么并网络设备驱动如何使用它的。
网络设备里的New API(NAPI)
老的网络设备驱动是工作在中断驱动的模式下,也就说每接收到一个数据包都要触发一次中断。
这被证明在高速传输情况下性能不好。
一种新的软件技术被开发了出来,就是New API(NAPI),现在几乎所有的Linux网络设备驱动都支持。NAPI在2.5/2.6内核的时候第一被介绍,后来给2.4.20内核打上了该补丁。有了NAPI,在高负载下,网络设备驱动工作在polling模式而不是在中断驱动模式。也就是说每收到一个数据包不会出发一个中断。相反数据包被存放在驱动里,内核会一次又一次的轮询来获取数据包。使用NAPI在高负载下能有效提高性能。对于socket应用程序需要有最低的延迟,并花费最小的CPU开销,Linux已经从内核3.11开始在socket操作上支持Busy Polling方式。这项技术将会在第12章,“Busy Poll sockets”节被讨论。
刚刚认识了网络设备相关知识,现在是时候开始学习Linux内核网络协议栈里数据包的传输了。
接收并发送数据包
网络设备驱动的主要任务如下:
· 接收到的数据包是指向本机地址的并把他们传输给网络层(L3层),并随后传给传输层(L4)。
· 传输本机生成的数据包并发送出去,或者在本机转发接收到的数据包。
对于每个数据包,进来的或者出去的,都会去路由子系统里查询下。一个数据包是否要被转发,它应该被发往哪个接口都是基于路由子系统的,我将描述在第5章和第6章描述它们。
在网络协议栈中,路由子系统里查询结果并不是决定数据包传输的唯一因素。
例如,在网络协议栈中有5个桩点是netfilter子系统(经常被称为netfiler hooks)的回调函数可以被注册。接收到的数据包的第一个桩点是NF_INET_PRE_ROUTING,在路由查询前生效。当一个数据包是被这样的回调函数处理时,会涉及到一个叫NF_HOOK()的宏,它会根据这次回调函数的结果(也叫判决)继续在网络协议栈内传输。比如,如果有个判决是NF_DROP,那这个数据包就会被丢掉,如果这个判决是NF_ACCEPT,这个数据包会按正常情况那样继续传输。
Netfilter hook callbacks是被nf_register_hook()函数或被nf_register_hooks()函数来注册的,你会遇到好多这些函数,比如,在各种netfiler内核模块中。内核里的netfilter子系统是用户空间里广为人知的iptables软件包的基础设施。第九章会描述netfilter子系统和netfiler hooks,与netfilter的连接跟踪层的联系。
除了netfiler hooks,数据包传输能被IPsec子系统影响-比如,当数据包匹配到一个配置好的IPsec策略时。IPsec提供了一个网络层安全解决方案,并且它使用ESP和AH协议。IPsec在IPv6里是必须的,在IPv4里是可选的,然后大部分操作系统,包括Linux都实现了IPv4的IPsec。
IPsec有两种传输模式:传输模式和隧道模式。它被作为许多虚拟专有网络(VPN)解决方案的基础,当然也有非IPsec的VPN解决方案。你会在第十章学习到IPsec子系统和IPsec策略的相关内容,也会讨论当使用IPsec穿透NAT网络时会遇到的问题,还有IPsec NAT传输方案等。
仍然还有其他的因素会影响到一个数据包的传输——比如,在需要被转发的数据包的IPv4头里TTL域里的值。这个TTL值在每个转发设备上都会被减1。
当它达到0值时,这个数据包就会被丢弃,并且一条带着“TTL Count Exceeded”码的超时ICMPv4数据包会被发送回来。这样做是为了避免因为某些错误而没完没了的转发。
更有甚者,每次数据包被成功转发的时候,它的TTL值会被减1,这个数据包的IPv4报文头里的校验码就要被重新计算,因为它的值依赖于IPv4报文头,而TTL是它IPv4报文头里的成员。
第四章,会涉及到IPv4子系统,会着重讲到它。IPv6里有些相似性,但在IPv6报文头里的跳数被称作hop_limit而不是ttl。在第八章,你会了解到这一点,因为会涉及到IPv6子系统。在第三章,你也会学习到IPv4和IPv6里面的ICMP。
A large part of the book discusses the traversal of a packet in the networking stack, whether it is in the receive path (Rx path, also known as ingress traffic) or the transmit path (Tx path, also known as egress traffic).
本书的很大一部分章节是在讨论网络协议栈里数据包的传输,接收路径(Rx path,也就是ingress traffic)或者发送路径(Tx path,也就是egress traffic)。
传输是复杂的,并有很多变化:大包在发送前可能被分片;当然被分片的包要被汇集(在第四章讨论)。不同类型的数据包会被不同地处理。例如,比如组播包会被一组主机处理(相对地,单播包只会被发送到指定地址的主机上。)组播能被应用在流媒体程序中来减少网络资源。处理IPv4组播传输会在第四章被讨论。你也会学到一台主机如何加入并离开一个组播组。在IPv4协议里,互联网组播管理协议(IGMP)处理组播关系。当主机被配置为组播路由器后,组播包应该被转发,而不是传输给本机。当他们需要结合用户态组播路由程序来处理的话,情况会变得更加复杂,比如pimd程序或mrouted程序。这些场景,称为组播路由,会在第六章被讨论。
为了更好地理解数据包的传输,你必须学到一个数据包在Linux内核里是如何被表示的。sk_buff结构体代表了进来或者出去的数据包,包括它的头(include/linux/skbuff.h)。在本书中,我会用SKB来表示sk_buff对象,因为这是种很通用的表示方式(SKB代表socket buffer)。socket buffer(sk_buff)结构体是一个很大的结构体——在这章节中我只会讨论它的少量成员。
The Socket Buffer
sk_buff结构体在附录A中被详细讨论。当你需要知道更多的SKB内成员或如何视同SKB API,我建议你好好参考下附录A。注意当你喝SKB打交道时,你必须遵守SKB API规范。因而,比如,当你想要调整skb->data指针,你不需要直接做,可以使用skb_pull_inline()方法或者skb_pull()方法(你会在这章的后面看到示例)。如果你想从SKB里获取L4层报文头(传输层的头),你可以调用skb_transport_header()方法来实现它。同样地,你想获取L3层报文头(网络层头),你可以调用skb_network_header()方法来实现它,如果你想获取L2层报文头(MAC层头),你可以调用skb_mac_header()方法来实现它。这三种方法都是塞SKB一个参数来获取。
下面是sk_buff结构体的定义(节选):
struct sk_buff {
. . .
struct sock *sk;
struct net_device *dev;
. . .
__u8 pkt_type:3,
. . .
__be16 protocol;
. . .
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head,
*data;
sk_buff_data_t transport_header;
sk_buff_data_t network_header;
sk_buff_data_t mac_header;
. . .
};
(include/linux/skbuff.h)
当一个数据包在物理线路上被接收,网路设备驱动程序就分配SKB,典型地是调用netdev_alloc_skb()方法(或者dev_alloc_skb()方法,该方法已停用,就是第一个参数为NULL的netdev_alloc_skb方法)。
在数据包传输过程中,有很多场景下数据包是要被丢掉的,然后会调用kfree_skb()或dev_kfree_skb()方法,这两种方法都是只有一个参数就是SKB指针。
SKB的一些成员是在数据链路层(L2层)被决定的。例如,pkt_type就是有eth_type_trans()方法根据以太网目的地址来决定的。如果这个以太网目的地之是组播地址,那pkt_type就会被设置为PACKET_MULTICAST;如果这个以太网目的地址是组播地址,pkt_type会被设置为PACKET_BROADCAST;如果这个以太网地址是本地主机的地址,那pkt_type会被设置为PACKET_HOST。大多数以太网网络驱动会在他们的Rx路径上调用eth_type_trans()方法。eth_type_trans()方法会根据以太网报文头的ethertype来设置SKB的协议域。eth_type_trans()方法也是通过调用skb_pull_inline()方法给14字节(ETH_HLEN)来偏移获取SKB里数据指针的,14字节是以太网报文头长度。这样做的原因是skb->data应该被指向当前处理所在层的报文头处。当数据包在第二层的时候,也就是网络设备驱动程序的Rx路径上,skb->data会指向第二层(以太层)的报文头。随后数据包会被传给第三层,在调用eth_type_trans()方法后,skb->data应该立即指向了网络层(L3)报文头(就是紧跟着Ethernet报文头后面,详见图1-3).
Figure 1-3. An IPv4 packet
SKB包括数据包的报文头(L2, L3和L4的报文头)以及数据载荷。数据包在网络协议栈的传输过程中,报文头会被添加或移除。比如,用套接字本地生成一个IPv4数据报文,并发送出去,网络层(IPv4)会添加IPv4报文头到SKB里。IPv4报文头长度的最小值时20字节。当添加IP选项时,IPv4报文头长度能最大到60字节。IP选项会在第四章节被描述,第四章主要淘箩IPv4协议的实现。
图1-3显示了带L2,L3,L4报文头的IPv4数据包实例。在图1-3中的示例是UDPv4数据包。
第一个报文头是以太网报文(第二层)的14个字节。随后是IPv4(第三层)报文头(长度从20字节到60字节),随后是UDPv4(第四层)报文头(8字节长度)。紧接着就是数据包的载荷。每个SKB都有一个dev成员,代表一个net_device结构体实例。对于进来的数据包,它是接收数据报文的网络设备,对于出去的数据包,它是发送数据报文的网络设备。跟SKB关联的网络设备有时候需要去获取信息,这些信息会影响到SKB在网络协议栈里的传输。比如,网络设备的MTU会要求分片(之前有涉及)。
每个传输的SKB都有一个sock对象与之关联(sk成员)。如果这个数据包是个要转发的数据包,那么sk就是NULL,因为它不是本地主机生成的。每个接收到的数据包都会被匹配的网络层协议处理函数处理。例如,一个IPv4数据包会被ip_rcv()函数处理,而一个IPv6数据包会被ipv6_rcv()函数处理。你会在第四章学习到用 dev_add_pack()函数来注册IPv4协议的处理函数,在第八章学习到用dev_add_pack()函数来注册IPv6协议的处理函数。甚至,我会跟踪在IPv4和IPv6协议里数据包的收和发的传输流程。
For example, in the ip_rcv() method, mostly sanity checks are performed, and if everything is fine the packet proceeds to an NF_INET_PRE_ROUTING hook callback, if such a callback is registered, and the next step, if it was not discarded by such a hook, is the ip_rcv_finish() method, where a lookup in the routing subsystem is performed.
例如,在ip_rcv()方法里,很多明智的检查是要做的,。在路由子系统里的查找是基于目的地址缓存表的(dst_entry_object)。你会在第5章和第6章(描述IPv4路由子系统)学习到dst_enty和与之关联的输入输出的回调函数。在IPv4里有一个被限制的地址空间的问题,就是IPv4的地址大小只有32位。组织使用NAT(在第九章描述)来为他们的主机提供本地地址,但IPv4地址空间在几年后仍将耗竭。所以开发IPv6协议的最主要原因之一是,IPv6的地址空间相对IPv4地址空间来说是巨大的,因为IPv6地址长度是128位的。但IPv6协议不仅仅只是一个更大地址空间的协议。IPv6协议包括了很多改善和补充,都是根据从IPv4协议发展过程的总结出来的经验而来的。例如,IPv6有个固定的40字节长度的报文头,而不是IPv4那种由于IP options的拓展而可变长的报文头(20字节或者60字节)。在IPv4里处理IP options是相当复杂且影响性能的。换句话说,在IPv6里,你一点也不能拓展报文头(就像上面提到的,它是固定长度的)。相反拓展报文头这种机制比IPv4里的IP options在性能方面更有效率。另一个值得注意的改变就是ICMP协议;在IPv4里,ICMP只是用来作错误报告和通知消息。在IPv6里,ICMP协议可以用作很多目的:比如邻居发现协议(ND),组播侦听发现(MLD)等等。第三章专门讲了ICMP(IPv4里和IPv6里都有)。在第七章讲了IPv6的邻居发现协议,第八章讲了MLD协议,这些都涉及到IPv6子系统。就像稍后提及的那样,接收到的数据包是通过网络设备驱动传给网络层的,像IPv4或IPv6。如果数据包是本地交付的,它们就会被侦听的套接字交付给传输层(L4)来处理。最常用的传输协议是UDP和TCP,将会在第11章讨论,也就是讨论第四层,传输层。这章会覆盖到两个新的传输协议,流控制传输协议(SCTP)和数据报文拥塞控制协议(DCCP)。你会发现,SCTP和DCCP都采纳了一些TCP特性和一些UDP特性。数据包是由本地主机的第4层socket创建的,比如TCP套接字或UDP套接字。它们是用户应用程序用套接字APIs创建的。这里有两种主要的套接字:数据报文套接字和流套接字。在第11章,这两种套接字和POSIX-based套接字接口会被讨论,你会学习到这些套接字接口的内核实现(struct socket提供了面向用户空间的接口,struct sock提供了面向网络层的接口)。本地生成的数据包是传给网络层,L3层(在第4章描述,“发送IPv4数据包”的章节)并随后传输给网络设备驱动(第2层)。有种情况,是在第三层,也就是网络层,需要分片,这些会在第4章讨论。每个二层网络接口都有个二层物理地址来表示它。在以太网这种情况下,物理地址是48位地址,每个以太网接口都有一个MAC地址,是厂商提供的,可以被认为是唯一的(当然很多网络接口的MAC地址是可以被用户空间的命令如ifconfig或ip命令改变。)每个以太网数据包是从以太头开始的,也就是14字节长度。它包含了两个字节的以太类型,6字节的源MAC地址和6字节的目的MAC地址,以太网类型值是0x0800代表IPv4,0x86DD代表IPv6。对于每个出去的数据包,以太网头是要被组装的。当一个用户态socket发送一个数据包时,它会指定它的目的地址(可以是IPv4地址或者IPv6地址)。但还不足以组建一个数据包,目的地址还需要被知道。根据IP地址查找到主机的MAC地址,是由邻居子系统负责,会在第7章被讨论。邻居发现协议在IPv4里是由ARP协议处理,在IPv6里是由NDISC协议处理。这些协议是不同的:ARP协议依赖于发送广播包请求,而NDISC协议依赖于发送ICMPv6请求,实际是组播包。在第7章,ARP协议和NDSIC协议都会被讨论到。网络协议栈能和用户态的任务进行交互通信,比如添加或删除路由,配置邻居表,设置IPsec policies和states等等。用户空间和内核空间之间的通信是靠netlink套接字完成的,会在第2章描述。iproute2用户空间程序是基于netlink套接字的,会在第2章被讨论,也就是一般的netlink套接字和他们的增强版。
无线子系统会在第12章被讨论。这个子系统是单独拎出来的,它有自己的git仓库树和邮件列表。在无线网协议栈里有一些特有的特性,在其他网络协议栈里没有,比如节能模式(当一个基站或者接入点进入睡眠模式)。Linux无线网子系统会提供特殊的网络拓扑,比如Mesh网络,ad-hoc网络等等。这些拓扑有时要求使用一些特殊的属性。比如,Mesh网络使用了称作Hybrid Wireless Mesh Protocol(HWMP)的路由协议,在第12章被讨论。这个协议工作在第二层,并处理MAC物理地址,和IPv4路由协议相反。第12章会讨论mac80211框架,被无线设备驱动程序使用。无线子系统的另一个非常有意思的特点是IEEE802.11n里的块知识机制,也会在第12章被讨论。
近些年InfiniBand技术在企业数据中心非常流行。InifiBand是基于一种叫Remote Direct Memory Access(RDMA)的技术。RDMA接口是在Linux内核2.6.11被引进来的。在第13章,你会发现一个很好的解释关于Linux InfiniBand的实现,RDMA接口和它的基础数据结构。
虚拟化的解决方案变得越来越流行了,尤其是因为Xen或KVM项目。也有硬件实现方案,比如Intel处理器的VT-x或AMD处理器的AMD-V,都使得虚拟化变得更有效。还有种形式的虚拟化,可能很少有人知道,但有着它特有的优势。这个虚拟化是基于一种不同的方法:进程虚拟化。它是通过Linux的namespaces实现的。现在Linux里支持6个命令空间,未来会有更多。命名空间的特点已经被很多项目使用,比如Linux容器(http://lxc.sourceforge.net/)和用户空间的Checkpoint/Restore(CRIU)。为了支持命令空间,两个系统调用被添加进内核:unshare()和setns();还有六个新的宏被添加到CLONE_*宏里,每一种代表一个命令空间的类型。我回在第14章讨论下命名空间,并着重讲下网络命令空间。第14章会涉及蓝牙子系统和一个简短概要关于PCI子系统,因为许多网络设备是PCI设备。我不会钻研到PCI子系统的内部,因为它超出了本书的范围。在第14章,另一个会被讨论的有意思的子系统是IEEE 8012.15.4,针对低功耗低成本设备。这些设备有时会被以物联网的概念被提及,物联网就是让嵌入式设备连接到IP网络。越来越显示,为这些设备使用IPv6可能是个好主意。这个解决方案被称为IPv6 over Low Power Wireless Personal Area Networks (6LoWPAN)。它会有些自己的挑战,比如拓展IPv6的邻居发现协议来适配这些设备,让他们偶尔进入睡眠模式(与常用的IPv6网络相反)。这个IPv6邻居发现协议的改动还没有被实现,但去考虑下这些改动背后涉及的理论也会比较有意思。除了这些,第14章会有些章节讨论些增强型主题,比如NFC,cgroups,安卓等等。
为了更好地理解Linux内个网络协议栈,尤其是它的开发部分,你必须熟悉它的开发工作是如何处理的。
Linux内核网络开发模型
内核网络子系统是非常复杂的,它的开发也是动态的。就像Linux内核子系统,开发者用过给邮件列表发送git patch文件,并最终被子系统管理者接受或拒绝。
Learning about the Kernel Networking Development Model is important for many reasons.
学习内核网络开发模型如此重要是因为:
To better understand the code, to debug and solve problems in Linux Kernel Networking–based projects, to implement performance improvements and optimizations patches, or to implement new features, in many cases you need to learn many things such as the following:
· How to apply a patch
· How to read and interpret a patch
· How to find which patches could cause a given problem
· How to revert a patch
· How to find which patches are relevant to some feature
· How to adjust a project to an older kernel version (backporting)
· How to adjust a project to a newer kernel version (upgrading)
· How to clone a git tree
· How to rebase a git tree
· How to find out in which kernel version a specified git patch was applied
There are cases when you need to work with new features that were just added, and for this you need to know how to work with the latest, bleeding-edge tree. And there are cases when you encounter some bug or you want to add some new feature to the network stack, and you need to prepare a patch and submit it. The Linux Kernel Networking subsystem, like the other parts of the kernel, is managed by git, a source code management (SCM) system, developed by Linus Torvalds. If you intend to send patches for the mainline kernel, or if your project is managed by git, you must learn to use the git tool.
Sometimes you may even need to install a git server for development of local projects. Even if you are not intending to send any patches, you can use the git tool to retrieve a lot of information about the code and about the history of the development of the code. There are many available resources on the web about git; I recommend the free online book Pro Git, by Scott Chacon, available at http://git-scm.com/book. If you intend to submit your patches to the mainline, you must adhere to some strict rules for writing, checking, and submitting patches so that your patch will be applied. Your patch should conform to the kernel coding style and should be tested. You also need to be patient, as sometimes even a trivial patch can be applied only after several days. I recommend learning to configure a host for using the git send-emailcommand to submit patches (though submitting patches can be done with other mail clients, even with the popular Gmail webmail client). There are plenty of guides on the web about how to use git to prepare and send kernel patches. I also recommend readingDocumentation/SubmittingPatches and Documentation/CodingStyle in the kernel tree before submitting your first patch.
And I recommended using the following PERL scripts:
· scripts/checkpatch.pl to check the correctness of a patch
· scripts/get_maintainer.pl to find out to which maintainers a patch should be sent
One of the most important resources of information is the Kernel Networking Development mailing list, netdev: netdev@vger.kernel.org, archived at www.spinics.net/lists/netdev. This is a high volume list. Most of the posts are patches and Request for Comments (RFCs) for new code, along with comments and discussions about patches. This mailing list handles the Linux Kernel Networking stack and network device drivers, except for cases when dealing with a subsystem that has a specific mailing list and a specific git repository (such as the wireless subsystem, discussed in Chapter 12). Development of the iproute2 and the ethtool userspace packages is also handled in the netdev mailing list. It should be mentioned here that not every networking subsystem has a mailing list of its own; for example, the IPsec subsystem (discussed in Chapter 10), does not have a mailing list, nor does the IEEE 802.15.4 subsystem (Chapter 14). Some networking subsystems have their own specific git tree, maintainer, and mailing list, such as the wireless mailing list and the Bluetooth mailing list. From time to time the maintainers of these subsystems send a pull request for their git trees over the netdev mailing list. Another source of information is Documentation/networking in the kernel tree. It has a lot of information in many files about various networking topics, but keep in mind that the file that you find there is not always up to date.
The Linux Kernel Networking subsystem is maintained in two git repositories. Patches and RFCs are sent to the netdev mailing list for both repositories. Here are the two git trees:
· net:http://git.kernel.org/?p=linux/kernel/git/davem/net.git: for fixes to existing code already in the mainline tree
· net-next:http://git.kernel.org/?p=linux/kernel/git/davem/net-next.git: new code for the future kernel release
From time to time the maintainer of the networking subsystem, David Miller, sends pull requests for mainline for these git trees to Linus over the LKML. You should be aware that there are periods of time, during merge with mainline, when the net-next git tree is closed, and no patches should be sent. An announcement about when this period starts and another one when it ends is sent over the netdev mailing list.
Note This book is based on kernel 3.9. All the code snippets are from this version, unless explicitly specified otherwise. The kernel tree is available from www.kernel.org as a tar file. Alternatively, you can download a kernel git tree with gitclone (for example, using the URLs of the git net tree or the git net-next tree, which were mentioned earlier, or other git kernel repositories). There are plenty of guides on the Internet covering how to configure, build, and boot a Linux kernel. You can also browse various kernel versions online athttp://lxr.free-electrons.com/. This website lets you follow where each method and each variable is referenced; moreover, you can navigate easily with a click of a mouse to previous versions of the Linux kernel. In case you are working with your own version of a Linux kernel tree, where some changes were made locally, you can locally install and configure a Linux Cross-Referencer server (LXR) on a local Linux machine. See http://lxr.sourceforge.net/en/index.shtml.
总结
这章是对Linux内核网络子系统的简单介绍。我描述了使用Linux的好处,一个流行的开源项目,内核网络开发模型。我也描述了网络设备结构体(net_device)和套接字数据结构(sk_buff),对于网路子系统来说是两个很重要的基础设施。
I described the benefits of using Linux, a popular open source project, and the Kernel Networking Development Model. I also described the network device structure (net_device) and the socket buffer structure (sk_buff), which are the two most fundamental structures of the networking subsystem. You should refer to Appendix A for a detailed description of almost all the members of these structures and their uses. This chapter covered other important topics related to the traversal of a packet in the kernel networking stack, such as the lookup in the routing subsystem, fragmentation and defragmentation, protocol handler registration, and more. Some of these protocols are discussed in later chapters, including IPv4, IPv6, ICMP4 and ICMP6, ARP, and Neighbour Discovery. Several important subsystems, including the wireless subsystem, the Bluetooth subsystem, and the IEEE 812.5.4 subsystem, are also covered in later chapters. Chapter 2 starts the journey in the kernel network stack with netlink sockets, which provide a way for bidirectional communication between the userspace and the kernel, and which are talked about in several other chapters.