深入解析Linux网络过滤、防火墙及软中断机制
1. IP过滤与防火墙
在进行分支操作前,需要在回溯指针中指向当前链的下一个条目(第353行)。这是为了应对分支链中没有规则匹配的情况,此时需要从当前链的下一个条目开始匹配。同时,由于回溯指针已被修改,需要将当前链的当前回溯指针偏移量存储在下一个条目的
comefrom
字段中(第350行),之后开始遍历新的分支链。
当目标为非标准目标时,会为该目标设置一个目标回调例程,并在第364行调用。目标的返回值可以是最终裁决结果或
IPT_CONTINUE
。若是前者,将带着裁决结果从例程返回;否则,继续处理链中的下一个条目。
1.1
IPT_MATCH_ITERATE
宏
该宏用于遍历钩子条目的特定协议规则列表。这些匹配规则位于
ipt_entry
对象的末尾,目标则位于特定协议规则列表的末尾。从偏移量(即
ipt_entry
对象的大小)处开始访问第一条规则(第305行),在每次迭代中,通过加上当前规则的大小来计算下一条规则条目的偏移量(第307行)。循环迭代直至到达钩子条目目标的起始位置(第306行)。对于每条规则,使用函数指针在第310行处理过滤规则。若当前规则匹配成功,则继续匹配下一条规则;否则,在第一次匹配失败时返回(第311行)。
2. 网络过滤框架与防火墙实现
网络过滤框架用于在Linux中实现防火墙,同时还借助网络过滤钩子来实现IP栈的各种扩展,如IP安全、连接跟踪、IP伪装、NAT、重定向等。
网络过滤钩子的入口点是
NF_HOOK
宏。Linux 2.4内核的TCP/IP栈为上下栈都实现了网络过滤钩子条目,具体如下:
| 类型 | 描述 |
| ---- | ---- |
| 传出数据包钩子 | -
NF_IP_LOCAL_OUT
:应用于传出数据包的过滤规则
-
NF_IP_POST_ROUTING
:实现IP伪装、IP安全等 |
| 传入数据包钩子 | -
NF_IP_LOCAL_IN
:应用于传入数据包的过滤规则,在内核为本地传递路由数据包后应用
-
NF_IP_PRE_ROUTING
:数据包进入IP层后,在路由之前应用的钩子,IP安全、IP伪装、NAT等可能需要此钩子 |
Compat
提供了一个网络过滤框架,该框架下只能向内核注册一个防火墙。使用
register_firewall()
函数通过
Compat
框架注册
firewall_ops
类型的对象,
Ipchain
就是为配合
Compat
框架而设计的。
而
Iptables
与
Compat
框架不兼容,使用
nf_register_hook()
函数注册网络过滤钩子,它会为特定的钩子类型注册一个
nf_hook_ops
类型的对象,已注册的钩子会链接到全局哈希表
nf_hooks
中。同时,提供了
ipt_register_table()
接口来注册
Ipchain
表,它会将
ipt_table
类型的对象注册到全局列表
ipt_tables
中。与
Ipchains
相比,
Iptable
速度更快,且具有许多高级功能,它为每个CPU维护过滤表,由于缓存局部性,性能得到了显著提升。
3. 网络软中断概述
在Linux系统中,中断处理被分为两部分。小部分工作在中断处理程序中完成,大部分或下半部分工作则被推迟到安全的时间以最小的延迟进行处理,这样做是为了避免较长的中断延迟。在中断处理程序执行期间,中断会被禁用,处理完成后再启用。如果在中断处理程序中花费过多时间,中断延迟将会很高。
早期的Linux内核版本(2.2及以下)实现了底半(bottom - half)框架来处理大部分中断处理工作。该框架在单CPU机器上运行良好,因为它会持有一个大的底半锁来执行底半操作。但在对称多处理(SMP)机器上,由于执行底半操作需要持有锁,该框架会对每个CPU的底半执行进行串行化访问,无法实现良好的扩展性。
为了提高底半执行的可扩展性,对框架进行了修改,新框架被称为软中断(SoftIRQ)。软中断设计为可以在多个CPU上并行运行,同一个软中断也可以同时在不同的CPU上并行执行。由于软中断操作的数据是按CPU维护的,因此每个CPU可以独立地触发软中断。
并非每个中断事件都有单独的软中断。网络方面有两个软中断,分别用于发送(Tx)和接收(Rx)中断。其他中断事件将其底半操作注册为高优先级或低优先级的小任务(tasklet),高、低优先级各有一个软中断对应一个小任务。小任务的特点是同一时间只能在一个CPU上执行。
4. 为何需要网络软中断以及如何触发它们
4.1 传输情况
在具有两个CPU的SMP机器上,当需要从同一接口并行传输两个帧时,会出现性能问题。一个内核控制路径获取设备锁并传输帧后返回,而另一个也需要在同一传出接口传输帧的内核控制路径,要么等待设备锁释放,要么在循环中不断尝试获取锁。如果因为其他CPU正在传输帧而返回,内核会丢弃数据包,上层需要重新构建并重新传输整个数据包;如果在循环中等待设备锁释放,会浪费另一个CPU的周期。在SMP架构下,这种情况会严重影响系统性能,在中等网络流量下会显著降低系统速度。
为了解决这个问题,可以将待传输的帧排队,并将帧传输的处理推迟到不久后的某个时间,如下图所示:
graph LR
A[数据包待传输] --> B[加入传输队列]
B --> C[触发NET_TX_SOFTIRQ]
C --> D[软中断处理传输]
4.2 接收情况
以单个接口为例,当接收到一个帧时,中断处理程序需要完成很多工作,如从设备DMA缓冲区中提取帧、确定下一个协议层、在每个协议层处理数据包,最后将数据或控制消息传递到套接字层,这些操作会花费大量时间。由于长时间停留在中断处理程序中会增加网络接口的延迟,在此期间接收到的所有帧都会被丢弃,因此中断处理程序应尽可能快速地完成最少的工作。
可以在中断处理程序中仅将帧从设备DMA缓冲区提取到内核缓冲区并排队等待后续处理,接收到的帧可以由协议层安排后续处理,然后快速从中断返回,如下图所示:
graph LR
A[接收到帧] --> B[从DMA缓冲区提取到内核缓冲区]
B --> C[加入接收队列]
C --> D[触发NET_RX_SOFTIRQ]
D --> E[软中断处理接收]
综上所述,在传输和接收两种情况下都需要对帧进行延迟处理,这种延迟处理通过触发网络软中断来安排数据包的处理。传输和接收有相互独立的软中断,其概念与内核2.4之前的底半操作类似,但底半操作在多个CPU之间是串行执行的,而软中断可以在多个CPU上并行运行,无需获取全局锁,从而在SMP架构上显著提高了网络性能。
网络软中断可以通过调用
raise_softirq()
函数来触发。系统为每个注册的软中断分配一个位,发送软中断使用
NET_TX_SOFTIRQ
,接收软中断使用
NET_RX_SOFTIRQ
。软中断是按CPU划分的,不同的软中断可以在不同的CPU上独立调度。
调用
raise_softirq()
时传入对应软中断的位,由于需要为当前CPU触发中断,所以调用
cpu_raise_softirq()
。
cpu_raise_softirq()
实际上借助
__cpu_raise_softirq()
宏来触发软中断,该宏会设置CPU特定结构字段中对应软中断的位。通过调用
softirq_pending()
函数访问CPU特定字段,
softirq_pending()
借助
__IRQ_STAT()
宏访问
irq_cpustat_t
结构的
__softirq_pending
字段。系统为每个CPU维护一个
irq_cpustat_t
结构的数组
irq_stat
,最终是在当前CPU对应的
irq_stat[CPU].__softirq_pending
字段中设置对应软中断的位。
irq_cpustat_t
结构用于保存任何CPU的状态信息和进行统计,其字段如下:
| 字段名 | 描述 |
| ---- | ---- |
|
__softirq_pending
| 保存当前CPU上任何待处理软中断的信息,该字段中的每个位对应一个特定的中断。若该字段为正值,则表示有软中断待处理,需要检查位字段。 |
|
__local_irq_count
| 记录该CPU上触发的中断数量。 |
|
__local_bh_count
| 记录底半操作执行的次数。 |
|
__syscall_count
| 记录该CPU上进行的系统调用次数。 |
|
__ksoftirqd_task
| 保存负责处理当前CPU上软中断的
ksoftirqd
守护进程的
task_struct
结构的指针。 |
如果从中断或底半操作中触发软中断,则无需唤醒处理该CPU软中断的守护进程;否则,需要在
cpu_raise_softirq()
中唤醒它。
5. 软中断的处理时机和方式
软中断在
do_softirq()
函数中进行处理,该函数在内核的多个位置被调用。如果从中断模式调用
do_softirq()
,函数会直接返回(第68 - 69行)。因为若在中断处理程序中调用,会违背通过软中断进行延迟处理的初衷,导致中断延迟过高;若在底半处理程序中调用,会造成递归调用,可能导致内核栈溢出。
在
do_softirq()
函数中,使用
softirq_pending()
宏检查当前CPU上是否有待处理的软中断(第73行)。若有,将当前活跃软中断对应的位模式复制到本地变量
pending
中,并将
irq_stat[cpu].__softirq_pending
置为零,同时初始化
mask
为
pending
的补码。然后开始处理活跃软中断,通过左移
pending
一位的方式遍历其所有位,直到
pending
为零(第92行)。
处理完所有活跃软中断后,再次检查在此期间是否有新的软中断被触发(第97行)。由于
mask
中对应已处理软中断的位都被置为零,将
mask
与
pending
进行按位与操作,若结果为正数,则表示有未处理的软中断被触发,再次进入处理循环(第88 - 93行);若
pending
大于零但
mask
与
pending
按位与结果为零,则表示触发的是刚刚处理过的软中断,此时唤醒该CPU的
softirqd
守护进程,让其在稍后处理这些软中断,以保证用户空间应用程序能获得适当的CPU资源,因为内核是非抢占式的,软中断处理时间比中断长,若中断频率较高,会在软中断处理上花费过多时间。
在操作
irq_stat[cpu].__softirq_pending
时,通过调用
local_irq_save()
和
local_irq_disable()
函数禁用本地CPU的中断(第71和95行),操作完成后再调用
local_irq_enable()
和
local_irq_restore()
函数启用中断(第84和108行),这是因为
irq_stat[cpu].__softirq_pending
会在中断处理程序中被修改。
在处理软中断时,通过调用
local_bh_disable()
函数禁用底半操作(第79行),这会使
irq_stat[cpu].__local_bh_count
加一,目的是防止其他内核控制路径在当前CPU上处理软中断。例如,一个内核控制路径正在执行
do_softirq()
时,若有中断触发,在中断返回时可能会再次调用
do_softirq()
,禁用底半操作可以避免这种情况。
do_softirq()
函数的调用时机如下:
-
从
do_IRQ()
函数返回时
:从中断例程返回时,有可能触发了软中断,因为现在大部分中断工作都在底半操作(即软中断)中完成。但需要检查本地CPU上的软中断是否被禁用,若被禁用,则即使触发了软中断也不会处理。
-
启用本地底半操作时
:在某些情况下,为了避免在不禁用中断的情况下操作被软中断访问的数据时出现问题,会禁用软中断,此时只是增加本地底半计数器的值,允许本地CPU接收中断。这样做可以避免在SMP架构上出现死锁,以及防止单CPU机器冻结。当内核中的关键代码执行完成后,启用底半操作,此时会减少本地底半计数器的值,若计数器变为零,则调用
do_softirq()
函数处理待处理的软中断,从而实现底半操作的嵌套禁用,最外层启用软中断时会触发待处理软中断的处理。例如,在调用
lock_sock()
函数锁定套接字时会禁用底半操作,因为
tcp_v4_rcv()
函数在底半操作中运行,也需要获取套接字锁(
bh_lock_sock()
)。
以下是
do_softirq()
函数处理软中断的流程图:
graph TD
A[调用do_softirq()] --> B{是否从中断模式调用}
B -- 是 --> C[返回]
B -- 否 --> D{是否有软中断待处理}
D -- 否 --> E[结束]
D -- 是 --> F[初始化pending和mask]
F --> G[设置irq_stat[cpu].__softirq_pending为零]
G --> H[处理活跃软中断]
H --> I{是否有新软中断触发}
I -- 是 --> J{是否为未处理的软中断}
J -- 是 --> H
J -- 否 --> K[唤醒softirqd处理]
I -- 否 --> E
6. 软中断的注册
每个软中断与
irq_stat[cpu].__softirq_pending
中的一个特定位相关联。在当前的设计中,使用
struct softirq_action
结构体来表示软中断,该结构体有两个字段:
action
和
data
。
action
是指向软中断处理程序的函数指针,
data
保存传递给处理程序
action
的参数。
系统有一个名为
softirq_vec
的
struct softirq_action
数组,数组中的每个元素对应一个软中断。截至内核2.4.20版本,只有四个软中断,数组索引对应每个软中断关联的位编号。例如,
TASKLET_SOFTIRQ
被分配到第三位,在
softirq_vec
数组中对应第四个元素。这种设计使得在处理软中断时无需搜索软中断处理程序,只需遍历32位变量
pending
中的所有位,每次迭代将
pending
左移一位并检查该位是否被设置,若设置则执行对应的软中断处理程序。
总结
本文深入探讨了Linux系统中的IP过滤与防火墙机制,以及网络软中断的相关内容。在IP过滤和防火墙方面,介绍了分支链规则匹配的处理方式、
IPT_MATCH_ITERATE
宏的工作原理,以及不同网络过滤钩子的作用和特点,还对比了
Compat
框架下的
Ipchain
和
Iptables
的差异。在网络软中断方面,阐述了引入软中断的原因,包括解决传输和接收过程中的性能问题,详细说明了软中断的触发方式、处理时机和方式,以及软中断的注册机制。
通过合理利用网络过滤框架和软中断机制,可以有效提高Linux系统的网络性能和安全性,特别是在SMP架构下,软中断的并行执行能力能够充分发挥多CPU的优势,避免了传统底半框架在扩展性方面的不足。在实际应用中,开发者可以根据具体需求,灵活运用这些机制来优化系统的网络处理能力。
超级会员免费看
3960

被折叠的 条评论
为什么被折叠?



