血衫目前专注于 kubernetes 网络相关的工作,但由于自己的关注点从纯粹的应用层开发到应用运维,再到现在的底层网络,非网络科班出身,对于网络相关的东西仍然觉得无论如何都摸不清楚。
这篇文章一些内容是机翻过来的,原文链接参考文末。希望这篇文章能理清自己的思路,也不断完善修正。
目录:
- 通信协议
- 协议栈
- 网络协议族
- tcp/ip概述
- 网络流量路径
- 协议栈
- 路由
- 消息发送
- 消息接收
- IP Forwarding
- IP 路由
- 内核态\用户态\ 进程上下文\中断上下文
- Netfilter
一、通信协议 communications protocol
通信协议也称传输协议,一般指电信领域,在任何物理介质中允许两个或多个在传输系统中的终端之间传播信息的系统标准,也是指计算机通信或网络设备的共同语言。
通信协议在硬件、软件或两者之间皆可实现。
编程语言是为了模式化的计算而传输协议为了更畅通的交流。
-
网络传输协议(Internet communication protocol)是互联网工程任务组 (IETF)制定的。Internet Engineering Task Force,互联网工程任务组。
我们平时提到的RFC文档都是他们的产物,例如常见的网络协议: IP/791、TCP/793、UDP/768、FTP/959、HTTP1.1/2616。
目前,IETF共包括八个研究领域,131个处于活动状态的工作组。
- 应用研究领域(app— Applications Area),含15个工作组(Work Group)
- 通用研究领域(gen—General Area)
- 网际互联研究领域(int—Internet Area),含25个工作组
- 操作与管理研究领域(ops—Operations and Management Area),含16个工作组
- 实时应用与基础设施领域(rai—Real-time Applications and Infrastructure Area)含28个工作组
- 路由研究领域(rtg—Routing Area),含17个工作组
- 安全研究领域(sec—Security Area),含13个工作组
- 传输研究领域(tsv—Transport Area),含17个工作组
-
电气电子工程师学会(IEEE)负责有线无线传输:IEEE 754 浮点算法规范、IEEE 802 局域网标准
-
ITU-T 负责电信通讯传输以及公共交换电话网 (PSTN)的格式,International Telecommunication Union,国际电信联盟
-
国际标准化组织 (ISO) 负责其他类别,International Organization for Standardization。
ITU最老,IETF最新,这方面也体现电信向互联网的转变。
二、协议栈 Protocol_stack
协议栈是指网络中各层协议的总和。
每个协议模块通常都要和上下两个其他协议模块通信,它们通常可以想象成是协议栈中的层。最低级的协议总是描述与硬件的物理交互。每个高级的层次增加更多的特性。用户应用程序只是处理最上层的协议。
在实际中,协议栈通常分为三个主要部分:媒体,传输和应用。一个特定的操作系统或平台往往有两个定义良好的软件接口:一个在媒体层与传输层之间,另一个在传输层和应用程序之间。
媒体到传输接口定义了传输协议的软件怎样使用特定的媒体和硬件(“驱动程序”)。例如,此接口定义的TCP/IP传输软件怎么与以太网硬件对话。
应用到传输接口定义了应用程序如何利用传输层。例如,此接口定义一个浏览器怎样和TCP/IP传输软件对话。这些接口的例子包括socks套接字和微软的Winsock。
一些著名的协议栈如下:
- TCP/IP
- OSI model
- ATM
- AppleTalk
三、网络协议族 Internet_protocol_suite
通称为TCP/IP协议,TCP/IP Protocol,简称TCP/IP。
因为该协议家族的两个核心协议:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准。这些协议最早发源于美国国防部的ARPA网项目。
TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议。
0. 一些报文流向图参考
本部分参考:linux网络协议栈(一)报文流向总览
另一个视角下的报文流向图:
1. 物理层
本部分参考:linux网络协议栈(三)网卡驱动层
由于物理层太过底层,这里就记录到网卡和网卡驱动好了。
在报文接收方向上,网卡驱动把接收到的数据按照其对应的链路层协议(如以太网)组装成报文,然后把它上交给链路层,接口是netif_receive_skb,至此网卡驱动的任务就结束了,报文交给链路层处理;
在报文发送方向上,网卡驱动受链路层驱使,链路层告知其有报文要发送时,网卡驱动才开始工作,接口是dev_queue_xmit。
在linux中,所有的网络传输媒介,都用一个叫接口的东西来定义,如eth0、wlan1、usb2、vlan3、br4、eth0.1、lo等等,所谓传输媒介:
- 可以是一个实际存在的以太网卡设备比如eth0,
- 也可以是其他可以传输以太网格式报文的“网卡设备”比如wlan1、usb2,
- 也可以是vlan或桥比如vlan3、eth0.1、br4等依赖于某个有物理设备支持的宿主接口,
- 还可以是根本不存在物理设备支撑的东西如lo
但无论哪种,在内核中均以接口来定义,由结构体struct net_device描述,它包括了一切的接口的属性和方法。
这是一个庞大的结构体,它抽象了每个接口的属性和能力,诸如open、close、ioctl、接收、发送等等,也就是说每个接口的属性和能力都封装在结构体net_device中。
报文的实际收发动作是在网卡驱动中完成的,不同情况的网卡会有不同的驱动,即便都是以太网卡,具体驱动也是各式各样:
- 它们最终发送出去的肯定必须是以太网格式的报文
- 它们收到的数据不论最开始是否是以太网格式的报文,最终交给链路层时肯定必须是以太网格式的报文
以上两点都是网卡驱动编写者的工作。
2. 数据链路层
本部分参考:linux网络协议栈(四)链路层原理
从功能上看,链路层完成以下任务:
1、 实现与网卡驱动和协议栈上层的接口;
2、 实现网桥,即实现按MAC地址转发的二层交换机功能;
3、 实现vlan处理,实现带vlan报文的传输;
4、 实现邻居子系统,即实现链路层和网络层的映射;
5、 根据以太网类型,实现根据链路层协议类型收发报文;
6、实现二层隧道功能(典型如依托于网络层IP地址转发的eoip隧道);
链路层不一定是报文最终的归宿。
从报文接收角度看,绝大多数报文都要最终给目的主机的应用层,协议报文会在不同协议所属层次终结。大多数报文由链路层处理后(或不处理)再向上层转发:
- 比如IP报文,在链路层由ip_rcv函数进入网络层处理,
- 比如由应用程序通过原始套接字获取的报文,将在链路层被直接送往socket层,
- 比如arp协议报文会被送往arp模块去处理
再从报文发送角度看,不同的处理模块,会通过其专有接口通知链路层要发送报文:
- 比如对于网络层,通过ip_finish_output (最终是ip_finish_output2) 告诉链路层要发送报文
- 比如原始套接字,情况则比较简单,直接调用dev_queue_xmit通知网卡驱动,
- 对于网桥、vlan子接口的收发,链路层的处理就完全是逻辑链路层的内容,属于linux链路层的核心工作。
如上图,报文接收方向上,链路层由netif_receive_skb收到报文后,依次进行
-
网桥处理
网桥处理中会根据报文是否发给本机还是转发做不同的处理,发给本机将修改报文输入接口为桥端口并返回netif_receive_skb重新进协议栈处理,转发则直接调用dev_queue_xmit从对应接口发送
-
L2隧道处理
L2隧道处理则根据所属隧道的目的IP寻找路由,并根据路由结果指示的出接口转发报文
-
(可能会有其他处理)
-
按以太网类型处理
根据以太网类型调用在之前注册过的协议处理函数,如vlan报文调用vlan_skb_recv,IP报文调用ip_rcv,arp报文调用arp_rcv等等
需要注意,网桥、vlan报文对于上层协议栈可见的是桥端口、vlan子接口(上层协议栈关心报文的“逻辑”输入接口,而不是“物理”输入接口)。
对于网桥和 vlan 的上本机的报文,会将报文的输入接口改为对应的桥端口或vlan子接口(vlan报文还会去除vlan头),反向报文也是由上层协议栈发送到桥端口或vlan子接口,再继而转换为其原始的报文输入接口通往网卡驱动。
接口的概念
接口可以理解为链路层的每一个收发单元:
- 每一个接口可以对应实际的一个网卡
- 也可以是一个没有实际物理设备驱动对应的“虚”接口,比如网桥接口、vlan子接口
不论是“虚”接口还是“实”接口,在内核中都是以net_device结构体描述;
对于网络层,最重要的工作是选路,即确定路由,而确定路由就是确定报文是转发还是上送本机:
- 如果是转发的话从哪个接口发送(skb->_skb_dst),由路由判决所需条件
- 如果是输入的话,即报文是从哪个接口上到网络层的(skb->dev)
- 除路由之外,上层协议栈其他部分也经常关注报文的输入接口和路由转发接口;
所以接口是报文实际输入输出的逻辑路径,但不一定是物理路径:
- 如某报文的路由结果是从eth0.5接口转发出去,那么这个eth0.5就是转发的逻辑路径,但它并没有实际的物理驱动,而是再通过vlan子接口处理最终仍由eth0实际转发报文
- 再如某报文由网桥br0上送到网络层,那么br0是该报文的逻辑路径,上层协议栈认为该报文就是从br0接收的,而实际该报文可能是由eth1实际接收的,经过网桥处理后输入接口变为br0的。
之所以会有“虚”接口,都是因为一些特殊的目的,linux需要实现网桥,需要实现vlan,而这些都是逻辑上的概念,并不真的存在某种物理设备叫网桥或者叫vlan,所以需要人为的制造出这些“虚”接口,以满足上层协议栈向下处理的统一性,而这些“虚”接口和“实接口的“虚实转换”,就是需要链路层实现的内容。
- “实”接口一般由网卡驱动生成,因为网卡驱动管理实际的物理设备,所以一般诸如eth0、wlan1、usb2之类“实”接口由网卡驱动生成,网卡驱动负责这些“实”接口的ops实现
- “虚”接口一般由应用程序生成,如网桥、vlan子接口,内核源码负责这些类型的“虚”接口的ops。
可以把linux机器想象成一台能够实现交换机、路由器、主机的L2/3/4/5功能的结合体机器,“实”接口就是这个结合体机器的每个物理端口,“虚”接口表面上实现一些二层网络功能,如vlan子接口实现物理端口的vlan功能,网桥接口实现二层交换功能,而实际上这些“虚”接口同样被这个结合体机器的L3/4/5所识别。
3. 网络层
4. 传输层
5. 应用层
四、tcp/ip概述
参考资料:https://www.cs.unh.edu/cnrg/people/gherrin/linux-net.html
1. 网络流量路径
当应用程序产生流量时,它将通过套接字将数据包发送到传输层(TCP或UDP),然后再发送到网络层(IP)。在IP层中,内核在路由缓存或其转发信息库(FIB)中查找到主机的路由。如果数据包是用于另一台计算机的,则内核将其寻址,然后将其发送到链路层输出接口(通常是以太网设备),该接口最终将数据包通过物理介质发送出去。
当数据包到达介质上时,输入接口将接收到该数据包,并检查该数据包是否确实适用于主机计算机。如果是这样,它将把数据包发送到IP层,该IP层将查找到数据包目的地的路由。如果数据包必须转发到另一台计算机,则IP层会将其向下发送回输出接口。如果该数据包用于某个应用程序,它将通过传输层和套接字将其向上发送,以供应用程序准备就绪时读取。
2. 协议栈
网络设备构成了协议栈的底层;他们使用链路层协议(通常是以太网)与其他设备进行通信,以发送和接收流量。
在Linux设备模型中,Bus(总线)是一类特殊的设备,它是连接处理器和其它设备之间的通道(channel)。 为了方便设备模型的实现,内核规定,系统中的每个设备都要连接在一个Bus上,这个Bus可以是一个内部Bus、虚拟Bus或者Platform Bus.
输入接口从介质复制数据包,执行一些错误检查,然后将其转发到网络层。输出接口从网络层接收数据包,执行一些错误检查,然后通过介质将其发送出去。
IP是标准的网络层协议。它检查传入的数据包,以查看它们是否用于主机计算机或是否需要转发。如有必要,它将对数据包进行碎片整理并将其传送到传输协议。它维护出站数据包路由的数据库;在将它们发送到链路层之前,如有必要,它会对其进行寻址和分段。
TCP和UDP是最常见的传输层协议。UDP只是提供了一种用于将数据包寻址到计算机内端口的框架,而TCP则允许基于更复杂的连接的操作,包括用于数据包丢失和流量管理实现的恢复机制。可以在用户空间和内核空间之间复制数据包的有效负载。但是,两者都只是应用程序和网络之间的中间层的一部分。
在用户空间中运行的应用程序构成了协议栈的顶层。
3. 路由
IP层处理计算机之间的路由。它保留两个数据结构:
- 转发信息库(FIB)跟踪每个已知路由的所有详细信息,
- 为当前使用的目的地提供更快的路由缓存。
4. 消息发送
4.1 应用层:写入套接字(socket)
- 将数据写入套接字(应用程序)
- 填写消息头
- 检查基本错误(套接字是否绑定到端口?套接字可以发送消息吗?套接字有错没?)
- 将消息头传递到适当的传输协议(INET套接字)
4.2 传输层:使用UDP创建数据包
- 检查错误(数据太大吗?是UDP连接吗?)
- 确保存在到目的地的路由(如果尚未建立路由,则调用 IP routing routines;如果没有路由返回失败)
- 创建一个UDP header(用于数据包)
- 调用IP build and transmit方法
4.2 传输层:使用TCP创建数据包
- 检查连接(是否建立?它是打开?套接字进程正常吗?)
- 检查数据并将其与部分数据包合并(可选)
- 创建一个数据包缓冲区
- 从用户空间复制payload
- 将数据包添加到出站队列
- 将当前TCP header构建到数据包中(包含 ACK,SYN 等)
- 调用 IP transmit 方法
4.3 网络层:在IP中包装数据包
- 创建一个数据包缓冲区(UDP可能不需要)
- 查找到目的地的路由(如有必要-TCP)
- 填写数据包 IP header
- 从用户空间复制 header和 payload
- 将数据包发送到目的地的路由所指向的设备(iface)
4.4 数据链路层:发送数据包
- 将数据包放在设备输出队列中
- 唤醒设备
- 等待调度程序运行设备驱动程序
- 测试设备
- 发送 link header
- 告诉总线通过媒介传输数据包
4.5 物理层
5. 消息接收
参考文末参考资料,这一部分还有两个步骤,但是我目前还不理解,先留空着。
5.1 物理层:接收报文
- 唤醒接收设备(系统中断)
- 设备测试介质
- 接收链接头
- 为数据包分配空间
- 告诉总线将数据包放入缓冲区
- 将数据包放入积压队列
- 设置标志位,系统空闲时进行下一步
- 结束中断
5.2 数据链路层
- 调度程序发现有网络任务要执行
- 发送先前积压的等待发送的数据包
- 循环遍历积压队列中的所有数据包,并将数据包传递到其网络层
- 刷新发送队列
5.3 网络层:解IP包
- 检查数据包是否有错误(太短了?太长?版本无效?校验和错误?)
- 如果必要,对数据包进行碎片整理
- 获取数据包的路由(判断是本机或者需要转发)
- 将数据包发送到目的地相关的进程(接收为本机TCP或UDP,或者重传到另一台主机)
5.4 在UDP中接受数据包
- 检查UDP标头是否存在错误
- 将目标与套接字匹配
- 如果没有这样的套接字,请发送回错误消息
- 将数据包放入适当的套接字接收队列
- 唤醒等待该套接字的进程
5.4 在TCP中接受数据包
- 检查顺序和标志;尽可能将数据包存储在正确的空间中
- 如果收到过这个包,立即发送ACK并丢弃当前的数据包
- 确定数据包属于哪个套接字
- 将数据包放入适当的套接字接收队列
- 唤醒并处理等待来自该套接字的数据
6. IP Forwarding
7.2.1 接收数据包
- 唤醒接收设备(系统中断)
- 设备测试介质
- 接收链接头
- 为数据包分配空间
- 告诉总线将数据包放入缓冲区
- 将数据包放入积压队列
- 设置标志,系统空闲时进行下一步
- 结束中断
6.2 数据链路层
- 调度程序发现有网络任务要执行
- 发送先前积压的等待发送的数据包
- 循环遍历积压队列中的所有数据包,并将数据包传递到其网络层
- 刷新发送队列
6.3 网络层:解IP包
- 检查数据包是否有错误-太短了?太长?版本无效?校验和错误?
- 必要时对数据包进行碎片整理
- 获取数据包的路由(判断是本机或者需要转发)
- 将数据包发送到其目的地处理例程(接收为本机TCP或UDP,或者重传到另一台主机,在这种情况下,将数据包重新传输到另一个主机)
6.4 网络层:转发IP报文
- 检查TTL字段(并将其减少)
- 检查数据包是否有错误的路由
- 如有错误,发送ICMP包给发件人
- 将数据包复制到新缓冲区中并释放旧缓冲区
- 设置 IP options
- 如果对于新目的地而言太大,则打包
- 将数据包发送到目的地的路由所指向的设备(iface)。
6.5 发送数据包
- 将数据包放在设备输出队列中
- 唤醒设备
- 等待调度程序运行设备驱动程序
- 设备测试介质
- 发送 link header
- 告诉总线通过媒介传输数据包
7. IP 路由
Linux维护三组路由数据:
- 一组用于直接连接到主机的计算机(例如,通过LAN),邻居表。
- 两组用于间接连接的计算机(通过IP网络)。
- 具有指向每个可能地址的方向的通用转发信息库(FIB),
- 具有较小频率(且速度更快)的路由缓存,其中包含经常使用的路由上的数据。
邻居表
邻居表包含物理连接到主机的计算机的地址信息(因此称为邻居)。
它包括有关哪个设备连接到哪个邻居以及在交换数据时使用什么协议的信息。Linux使用地址解析协议(ARP)来维护和更新该表。它是动态的,因为需要时会添加条目,但如果在一定时间内不再次使用则最终会消失(但是,管理员可以将条目设置为永久条目)
显示邻居:ip neighbour show
增加ipv4邻居: arp -s 192.168.0.2 00:01:02:03:04:05
删除ipv4邻居: arp -d 192.168.0.2
FIB 转发信息库
转发信息库(FIB)是内核中最重要的路由结构,包含通过网络掩码到达任何有效 IP 地址所需的路由信息。
IP 层使用数据包的目标地址进入表,并将其与最特定的网络掩码进行比较以查看它们是否匹配。如果没有,则IP转到下一个最通用的网络掩码,然后再次比较两者。当它最终找到一个匹配项时,IP会将到远程主机的方向复制到路由缓存中并按其方式发送数据包。
路由缓存
路由高速缓存是Linux查找路由的最快方法。它会将当前正在使用或最近使用过的每条路由保留在哈希表中。当IP需要路由时,它将转到适当的哈希存储桶并搜索缓存的路由链,直到找到匹配项,然后沿着该路径发送数据包。
路由按顺序链接,最先使用,并且有计时器和计数器,这些计时器和计数器在不再使用它们时将其从表中删除。
更新路由信息
Linux 仅在必要时更新路由信息,但是表以不同的方式更改。路由缓存是最易变的,而 FIB通常根本不会改变。
五、内核态\用户态\ 进程上下文\中断上下文
参考资料:Linux 网络协议栈之内核锁
1. 为什么有这些概念?
在现在操作系统中,内核功能模块运行在内核空间,而应用程序运行在用户空间。
现代的CPU都具有不同的操作模式,代表不同的级别,不同的级别具有不同的功能,其所拥有的资源也不同;在较低的级别中将禁止使用某些处理器的资源。
Linux系统设计时利用了这种硬件特性,使用了两个级别,最高级别和最低级别:
- 内核运行在最高级别(内核态),这个级别几乎可以使用处理器的所有资源,
- 而应用程序运行在较低级别(用户态),在这个级别的用户不能对硬件进行直接访问以及对内存的非授权访问。
内核态和用户态有自己的内存映射,即自己的地址空间。
当工作在用户态的进程想访问某些内核才能访问的资源时,必须通过系统调用或者中断切换到内核态,由内核代替其执行。
进程上下文和中断上下文就是完成这两种状态切换所进行的操作总称。我将其理解为保存用户空间状态是上文,切换后在内核态执行的程序是下文。
系统调用是对系统内置子例程的调用,而中断是一个事件,它使处理器暂时保留当前执行。
一个主要区别是系统调用是同步的,而中断不是同步的。
这意味着系统调用在固定时间(通常由程序员确定)发生,但是由于意外事件(例如用户在键盘上按下键)而可能在任何时间发生中断。
2. 内核态和用户态
内核态:在内核空间执行,通常是驱动程序,中断相关程序,内核调度程序,内存管理及其操作程序。
用户态:用户程序运行空间。
3. 进程上下文与中断上下文
- 进程上下文:
- 进程上文:其是指进程由用户态切换到内核态是需要保存用户态时cpu寄存器中的值,进程状态以及堆栈上的内容,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
- 进程下文:其是指切换到 内核态后执行的程序,即进程运行在内核空间的部分。
- 中断上下文:
- 中断上文:硬件通过中断触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。中断上文可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被中断的进程环境。
- 中断下文:执行在内核空间的中断服务程序。
运行于进程上下文的内核代码是可抢占的,但中断上下文则会一直运行至结束,不会被抢占。
所以使用中断时要特别注意,中断上下文占用CPU时间太长会严重影响系统功能。中断处理程序的任务尽可能放在中断下半部执行。
4. 使用场景
-
进程上下文主要是异常处理程序和内核线程。内核之所以进入进程上下文是因为进程自身的一些工作需要在内核中做。例如,系统调用是为当前进程服务的,异常通常是处理进程导致的错误状态等。
-
中断上下文是由于硬件发生中断时会触发中断信号请求,请求系统处理中断,执行中断服务子程序。
六、Netfilter
参考资料: Linux 网络协议栈开发(七)—— Netfilter 概述及其hook点
Netfilter/IPTables 是Linux2.4.x之后新一代的Linux防火墙机制,是linux内核的一个子系统。Netfilter采用模块化设计,具有良好的可扩充性。其重要工具模块IPTables从用户态的iptables连接到内核态的Netfilter的架构中,Netfilter与IP协议栈是无缝契合的,并允许使用者对数据报进行过滤、地址转换、处理等操作。
Netfilter在内核中位置如下图所示:
在每个关键点上,有很多已经按照优先级预先注册了的回调函数(称为“钩子函数”)埋伏在这些关键点,形成了一条链。对于每个到来的数据包会依次被回调函数“处理再视情况是将其放行,丢弃还是怎么滴。每个钩子函数最后必须向Netfilter框架返回下列几个值其中之一:
- NF_ACCEPT 继续正常传输数据报。
- NF_DROP丢弃该数据报,不再传输。
- NF_STOLEN 模块接管该数据报,告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。
- NF_QUEUE 对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理)
- NF_REPEAT 再次调用该回调函数,应当谨慎使用这个值,以免造成死循环。
数据报经过各个HOOK的流程如下:
- 数据报从进入系统,进行IP校验
- 经过第一个HOOK函数NF_IP_PRE_ROUTING进行处理;
- 进入路由判决,其决定该数据报是需要转发还是发给本机的;
- 若该数据报是发被本机的,则该数据经过HOOK函数NF_IP_LOCAL_IN处理以后然后传递给上层协议;
- 若该数据报应该被转发则它被NF_IP_FORWARD处理;
- 经过转发的数据报经过最后一个HOOK函数NF_IP_POST_ROUTING处理以后,再传输到网络上。
- 本地产生的数据经过HOOK函数NF_IP_LOCAL_OUT 处理
- 进行路由判决处理
- 经过NF_IP_POST_ROUTING处理后发送出去。
Linux内核中Netfilter框架的HOOK机制可以概括如下:
在数据包流经内核协议栈的整个过程中,在一些已预定义的关键点上PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING 会根据数据包的协议簇 PF_INET 到这些关键点去查找是否注册有钩子函数。
- 如果没有,则直接返回 okfn 函数指针所指向的函数继续走协议栈;
- 如果有,则调用 nf_hook_slow 函数,从而进入到Netfilter框架中去进一步调用已注册在该过滤点下的钩子函数,再根据其返回值来确定是否继续执行由函数指针okfn所指向的函数。
参考资料