Linux网络中的数据包调度与防火墙机制解析
1. Linux IP QoS中的CBQ调度机制
在网络通信中,数据包的调度对于确保服务质量(QoS)至关重要。在Linux系统里,基于类的队列调度(CBQ)机制发挥着关键作用,它能有效管理不同类别的数据包传输。下面我们来详细了解CBQ调度机制中的核心函数。
1.1 cbq_dequeue()函数
cbq_dequeue()
函数的参数是网络设备的队列规则(qdisc)。当首次调用该函数开始从队列中出队数据包时,它会在第995行使用
PSCHED_GET_TIME
宏获取当前(开始)时间。接着,它会检查确定传输类(
q → tc_class
),不过最初在第998行这个条件为假,因为该值会在
cbq_dequeue_prio()
函数从活动类列表中选择传输类后设置。
若传输类(
q → tx_class
)已设置,函数会调用
cbq_update()
,此函数主要计算CBQ参数(空闲时间
idle
和平均空闲时间
avgidle
),用于根据分配的带宽速率判断传输类是否正在使用链路进行传输。判断依据包括类是否超限制、未达限制或达到限制:
- 超限制:类传输数据包的速度超过分配的带宽。
- 未达限制:类传输速度低于分配速率且有更多积压。
- 达到限制:类以分配的速率传输。
cbq_update()
函数具体执行以下操作:
1. 计算连续数据包之间的离开间隔时间(使用定时器),并减去该类分配的离开间隔时间(
cl → last
)以得到空闲时间。空闲时间定义为该类最后发送的两个数据包的期望时间与实际测量时间之差。
2. 使用空闲时间的指数加权移动平均计算平均空闲时间
avgidle
。
avgidle < 0
、
avgidle = 0
和
avgidle > 0
分别表示类超限制、达到限制和未达限制。
基于
avgidle
值,
cbq_update()
会判断类的状态,检查类是否可以从父类借用带宽,或者在传输数据包前等待一段时间以实现合理的链路共享。之后,
cbq_dequeue()
会在第1019行调用
cbq_dequeue_l()
函数从活动列表中选择合适的类。
1.2 cbq_dequeue_1()函数
该函数在第976行根据
q → activemask
值计算
activemask
,这个值在
cbq_enqueue()
函数将类入队时,由
cbq_activate class()
函数设置。此值用于在第978行获取优先级(
prio
),以便对活动类队列列表进行索引,并在第980行调用
cbq_dequeue_l()
函数根据优先级调度类。
1.3 cbq_dequeue_prio()函数
此函数负责从活动列表中选择类,并以分配的字节数运行该类。根据
cbq_dequeue_l()
函数传递的优先级,它会在第874 - 875行选择类。
cbq_dequeue_prio()
对活动类使用加权轮询算法,每个类在一轮中被分配一定数量的字节(量子)。在某些情况下,一个类在一轮中传输的字节数可能多于或少于其量子,因此会记录其赤字,以便在下一轮调整分配。
每个类所需的量子在
cbq_normalize_quanta()
函数中根据用户设置的类权重、分配和量子计算得出。
在一轮开始前,会在第885行检查该类是否未达限制,若未达限制则跳转到
skip_class
标签;若已达限制,则检查类的赤字值,若赤字小于0,则在第886行跳转到
next_class
标签;否则继续执行,并在第897行调用类队列规则的出队函数,默认是
pfi fo_dequeue()
函数。在第903行检查类的出队函数是否返回
sk_buff
,若返回则在第925行将
skb
返回给调用函数
cbq_dequeue_l()
,但在此之前,会在第920行再次检查类的赤字值。
skip_class
标签主要在第928行检查类是否为空或被惩罚,若被惩罚,则将该类从活动列表中移除并返回
NULL
。
next_class
标签会切换到活动列表中的下一个类进行下一轮调度,若第961 - 962行的
while
条件不满足,则将
NULL
返回给调用函数
cbq_dequeue_l()
,然后
cbq_dequeue_l()
也将
NULL
返回给调用函数
cbq_dequeue()
。
若
cbq_dequeue_l()
未返回
skb
,
cbq_dequeue()
会检查
q → toplevel
是否等于
TC_CBQ_MAXLEVEL
,以及是否到了特定时间。若是,则在第1046行跳出无限循环;否则,设置顶层和时间继续执行。当类超限制或顶层类被禁止借用带宽时会出现这种情况。若调度器中仍有数据包,会在第1055行启动看门狗定时器调度数据包,最后将
NULL
返回给调用函数
qdisc_restart()
。
CBQ出队过程的总结如下表所示:
| 步骤 | 操作 |
| ---- | ---- |
| 1 | 每个类在每一轮只能出队分配的一定量数据,不允许无限制发送。 |
| 2 | 使用加权轮询算法决定允许哪些类发送数据。 |
| 3 | 首先考虑最高优先级类进行数据包传输,直到该类无数据包,再考虑较低优先级类。 |
| 4 | 检查类是否超限制、未达限制或达到限制,并据此调度其他类。 |
下面是CBQ出队流程的mermaid流程图:
graph TD
A[开始cbq_dequeue] --> B[获取当前时间]
B --> C{传输类是否设置}
C -->|是| D[调用cbq_update]
C -->|否| E[等待设置传输类]
D --> F[调用cbq_dequeue_l]
F --> G[进入cbq_dequeue_prio]
G --> H{类是否未达限制}
H -->|是| I[跳转到skip_class]
H -->|否| J{赤字是否小于0}
J -->|是| K[跳转到next_class]
J -->|否| L[调用类的出队函数]
L --> M{是否返回sk_buff}
M -->|是| N[检查赤字值]
M -->|否| O{q → toplevel是否等于TC_CBQ_MAXLEVEL且时间合适}
N --> P[返回skb]
O -->|是| Q[跳出循环]
O -->|否| R[设置顶层和时间继续]
I --> S{类是否被惩罚}
S -->|是| T[移除类并返回NULL]
K --> U{while条件是否满足}
U -->|否| V[返回NULL]
R --> W{调度器是否有数据包}
W -->|是| X[启动看门狗定时器]
X --> Y[返回NULL]
2. Linux QoS基本原理与队列规则
服务质量(QoS)的基本原理是根据可用网络速度决定输入/输出数据包的接收/传输速率。在Linux系统中,网络接口默认附加的队列规则是“pfi fo_fast_qdisc”,可根据需求替换为其他类型的队列规则。基于类的队列规则允许我们对不同子类之间的链路速度进行整形,以实现基于质量的传输,并充分利用分配的带宽进行接收/传输。
3. IP过滤与防火墙的重要性
在计算机网络和互联网时代,计算机面临各种入侵威胁。私人网络和个人因各种需求连接到公共互联网,这可能会吸引恶意攻击,攻击原因可能是获取组织的私人信息或阻塞网络,这会对业务造成严重影响。此外,还存在一些需求,例如只允许特定主机访问特定服务,或者限制组织内某些组访问互联网,以及阻止路由器转发特定类型的流量。
这些情况都可以通过安装在网络单点进出口的防火墙软件来处理。防火墙主要针对以下三种流量方向进行工作:
- 入站流量
- 出站流量
- 转发流量
防火墙有一系列规则应用于特定流量,可配置为接受/拒绝特定IP的流量以及特定端口的流量,还可以配置为阻止ICMP消息。这种功能不仅能阻止来自不必要源的流量进出网络,还能限制特定网络服务仅对有限/已知主机开放。
4. Netfilter钩子框架
Linux在数据包传输路径的各个点安装了防火墙检查点,这些检查点被称为Netfilter钩子,定义为宏
NF_HOOK
。它会检查是否为特定检查和数据包所属的协议族注册了防火墙钩子,若是,则通过调用
nf_hook_slow()
函数遍历所有注册的防火墙检查点,该例程会根据防火墙策略决定如何处理数据包,可能接受或拒绝数据包。若未为该钩子类型注册防火墙,则调用作为宏参数传递的回调例程
okfn
,将数据包继续进行后续处理。
全局表
nf_hooks
是一个二维数组,用于注册每个钩子和协议族的防火墙检查列表。我们主要讨论互联网协议族
PF_INET
。
Netfilter钩子对应数据包在栈中传输时的各个检查点,具体如下:
| 钩子名称 | 作用 |
| ---- | ---- |
| NF_IP_PRE_ROUTING | 用于NAT/伪装,在传入数据包路由前,若应用了NAT/伪装,需更改目标地址,否则可能导致数据包本地交付。若规则不允许或未找到目标地址的转换,应丢弃请求。该操作针对第一个数据包,结果用于后续连接。此外,IPsec模块也可在此钩子上进行处理。 |
| NF_IP_POST_ROUTING | 用于NAT/伪装,更改数据包的源地址。NAT服务器需将发起者的源IP地址替换为直接连接到互联网的接口的IP地址,并更改源端口以区分连接。NAT只能使用可用的公共IP地址更改源IP地址,因此防火墙会检查是否允许此操作,若允许则进行更改,否则拒绝数据包。此操作在为出站数据包做出路由决策后进行,IPsec模块也可在此钩子上处理。 |
| NF_IP_LOCAL_IN | 应用于目的地为本机的数据包,即需要本地交付的数据包。在做出数据包需要本地交付的路由决策后进行检查,防火墙会检查是否允许从给定源接收特定端口(网络服务)的数据包。 |
| NF_IP_LOCAL_OUT | 用于所有本地生成的待传输数据包,在为数据包完成路由后安装此检查点。 |
| NF_IP_FORWARD | 用于需要通过不同接口转发的数据包。当Linux机器作为路由器时,此钩子会生效,用于检查是否允许转发数据包。若允许,则数据包在传输前需经过
NF_IP_POST_ROUTING
钩子,因为数据包可能需要NAT/伪装,若所有转发的数据包都需要加密,也会在此钩子中处理。 |
下面是Netfilter钩子的mermaid流程图:
graph TD
A[数据包进入] --> B{是否有注册的防火墙钩子}
B -->|是| C[调用nf_hook_slow]
B -->|否| D[调用okfn继续处理]
C --> E{防火墙策略决策}
E -->|接受| F[继续处理数据包]
E -->|拒绝| G[丢弃数据包]
以上就是Linux网络中数据包调度与防火墙机制的上半部分内容,这些机制共同保障了网络通信的高效性和安全性。
Linux网络中的数据包调度与防火墙机制解析
5. Netfilter钩子在IP栈中的位置
在这部分,我们将详细探讨Netfilter钩子在IP栈中的具体安装位置,分别从本地生成的数据包和传入的数据包两个方面进行分析。Netfilter在IP栈上的检查点如图16.1所示,为了便于理解,我们仅展示最少数量的Netfilter条目。
5.1 出站数据包的钩子
当数据包经过高层协议层(如TCP/UDP)处理后,需要找到通往目的地的路由。数据包会被发送到IP层,在
ip_queue_xmit()
函数中为数据包找到路由,并根据路由信息构建IP头。一旦IP头构建完成,数据包会经过
NF_IP_LOCAL_OUT
防火墙钩子的检查。此时,需要检查来自源端口/IP的数据包是否允许通过该路径路由,以及是否可以向给定目的地发送数据包并请求特定目的地运行的服务。如果钩子不认可该数据包,数据包将被丢弃。
若通过了第一个检查点,在将数据包放入设备队列进行最终传输之前,还需要经过另一个检查点。这个检查点通常用于NAT/伪装目的,IPsec模块也可以在此安装自己的钩子。此检查在
ip_finish_output()
函数中完成。如果防火墙策略允许,数据包最终将被传输;否则,数据包将在此级别被丢弃。
出站数据包处理流程如下表所示:
| 步骤 | 函数 | 操作 |
| ---- | ---- | ---- |
| 1 | ip_queue_xmit() | 为数据包找到路由,构建IP头 |
| 2 | NF_IP_LOCAL_OUT | 检查数据包是否允许路由和发送 |
| 3 | ip_finish_output() | 进行NAT/伪装或IPsec相关检查 |
| 4 | | 根据防火墙策略决定是否传输数据包 |
下面是出站数据包处理的mermaid流程图:
graph TD
A[高层协议处理后] --> B[ip_queue_xmit]
B --> C[构建IP头]
C --> D[NF_IP_LOCAL_OUT检查]
D -->|允许| E[ip_finish_output]
D -->|拒绝| F[丢弃数据包]
E --> G[NAT/伪装或IPsec检查]
G -->|允许| H[传输数据包]
G -->|拒绝| F
5.2 入站数据包的钩子
当数据包被接收并识别为IP数据报时,
ip_rcv()
函数会处理该数据包。它会对IP头进行所有的完整性检查,然后将数据包发送通过第一个防火墙钩子
NF_IP_PRE_ROUTING
。在此可以执行与NAT/伪装相关的解复用操作,也可用于实现IPsec。
通过该钩子后,下一步需要检查数据包是需要本地交付还是转发。如果数据包属于本地进程,它需要经过另一个安装在
ip_local_deliver()
函数中的钩子
NF_IP_LOCAL_IN
。在此可能有基于源和目的IP/端口的防火墙过滤器。
如果接收到的数据包需要转发,由
ip_forward()
函数处理。这里会安装IP防火墙规则,检查是否允许转发该数据包。如果允许,数据包需要经过另一个钩子
NF_IP_POST_ROUTING
。我们将转发的数据包视为本地生成的数据包进行处理,因为数据包可能需要NAT/伪装。如果所有通过此路由器转发的数据包都需要加密,也会在
NF_IP_POST_ROUTING
钩子中处理。
入站数据包处理流程如下表所示:
| 步骤 | 函数 | 操作 |
| ---- | ---- | ---- |
| 1 | ip_rcv() | 接收并检查IP头 |
| 2 | NF_IP_PRE_ROUTING | 进行NAT/伪装或IPsec处理 |
| 3 | {本地交付或转发} | 判断数据包处理方向 |
| 4 | ip_local_deliver() | 本地交付时,进行NF_IP_LOCAL_IN检查 |
| 5 | ip_forward() | 转发时,检查是否允许转发 |
| 6 | NF_IP_POST_ROUTING | 转发时,进行NAT/伪装或加密处理 |
下面是入站数据包处理的mermaid流程图:
graph TD
A[接收IP数据报] --> B[ip_rcv]
B --> C[检查IP头]
C --> D[NF_IP_PRE_ROUTING处理]
D --> E{本地交付或转发}
E -->|本地交付| F[ip_local_deliver]
E -->|转发| G[ip_forward]
F --> H[NF_IP_LOCAL_IN检查]
H --> I[本地处理数据包]
G --> J{是否允许转发}
J -->|是| K[NF_IP_POST_ROUTING处理]
J -->|否| L[丢弃数据包]
K --> M[转发数据包]
6. Netfilter钩子的注册
前面我们了解了Netfilter钩子在IP栈中的安装位置,接下来需要知道这些防火墙钩子是如何工作的。这些钩子首先由实现它们的模块进行注册,注册接口是
nf_register_hook()
函数。在注册钩子时,需要持有
BR_NETPROTO_LOCK
写锁。
如前文所述,
nf_hooks
是一个全局表,用于为不同的协议族注册钩子。我们需要将
nf_hook_ops
对象注册为Netfilter钩子。
nf_hook_ops
对象中嵌入了
list_head
,对于给定的钩子类型和协议族,可能会有多个Netfilter钩子注册,这些钩子通过
nf_hooks[pf][hooknum]
链进行链接,其中
pf
是协议族,
hooknum
是钩子类型(对于IP协议,我们将在后续讨论)。
注册钩子时,会根据
nf_hook_ops
对象的
priority
字段定义的钩子优先级将钩子插入链中。会遍历链中的每个条目,一旦找到优先级高于要注册钩子的优先级的钩子,就会将新钩子插入到该钩子之前。
priority
值越低表示优先级越高。
钩子按照优先级排列在链中,数据包会按照链中钩子的排列顺序依次通过每个钩子,即先通过最高优先级的钩子,再通过较低优先级的钩子。这是因为某些任务需要按照特定顺序执行。虽然并非所有在文档中提到优先级的钩子都属于同一钩子类型,但具有
NF_IP_PRI_CONN
等优先级的钩子在处理过程中会按照优先级顺序执行。
Netfilter钩子注册流程如下表所示:
| 步骤 | 操作 |
| ---- | ---- |
| 1 | 持有BR_NETPROTO_LOCK写锁 |
| 2 | 确定要注册的钩子类型和协议族 |
| 3 | 遍历nf_hooks[pf][hooknum]链 |
| 4 | 根据优先级找到插入位置 |
| 5 | 将nf_hook_ops对象插入链中 |
下面是Netfilter钩子注册的mermaid流程图:
graph TD
A[开始注册] --> B[持有写锁]
B --> C[确定钩子类型和协议族]
C --> D[遍历nf_hooks链]
D --> E{找到合适优先级位置}
E -->|是| F[插入nf_hook_ops对象]
E -->|否| D
F --> G[释放写锁]
G --> H[注册完成]
综上所述,Linux网络中的数据包调度与防火墙机制通过CBQ调度机制、Netfilter钩子框架等一系列技术,确保了网络通信的高效性和安全性。CBQ调度机制合理分配带宽,保证了不同类别的数据包能够有序传输;Netfilter钩子则在IP栈的各个关键位置进行防火墙检查,有效抵御了各种网络入侵。这些机制相互配合,为Linux网络环境提供了强大的支持。
Linux CBQ调度与Netfilter防火墙机制
超级会员免费看
382

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



