12.1.u32分类器

U32 过滤器是当前实现中最先进的过滤器。它完全基于哈希表,这使其在存在许多过滤器规则时依然稳健。

在其最简单的形式中,U32 过滤器是由记录列表组成,每个记录包含两个字段:选择器和动作。将下面描述的选择器与当前处理的 IP 数据包进行比较,直到出现第一个匹配项,然后执行关联的动作。最简单的动作类型是将数据包定向到定义的 CBQ 类。

的命令行tc filter程序,用于配置过滤器,由三个部分组成:过滤器规范、选择器和动作。过滤器规范可以定义为

tc filter add dev IF [ protocol PROTO ]
                     [ (preference|priority) PRIO ]
                     [ parent CBQ ]

字段protocol描述了过滤器将应用到的协议。我们只讨论ip协议的情况。preference字段(priority可以替代使用)设置当前定义的过滤器的优先级。这很重要,因为您可以拥有多个具有不同优先级的过滤器(规则列表)。每个列表将按照规则添加的顺序传递,然后处理优先级较低(偏好编号较高)的列表。parent字段定义了 CBQ 树的顶部(例如 1:0),过滤器应附加到该顶部。

上面描述的选项适用于所有过滤器,而不仅仅是 U32。

12.1.1. U32 选择器

U32 选择器包含将与当前处理的数据包匹配的模式的定义。准确地说,它定义了数据包头部中要匹配的位,仅此而已,但这种简单的方法非常强大。让我们看一下以下示例,这些示例直接取自一个非常复杂的真实世界过滤器

# tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
  match u32 00100000 00ff0000 at 0 flowid 1:10

现在,先不用管第一行 - 所有这些参数都描述了过滤器的哈希表。关注包含match关键字的选择器行。此选择器将匹配 IP 头部,其第二个字节为 0x10 (0010)。正如您可能猜到的,00ff 数字是匹配掩码,它准确地告诉过滤器要匹配哪些位。这里是 0xff,因此如果字节正好是 0x10,则它将匹配。at关键字表示匹配将从指定的偏移量(以字节为单位)开始 -- 在这种情况下,它是数据包的开头。将所有这些翻译成人类语言,如果数据包的服务类型字段设置了“低延迟”位,则该数据包将匹配。让我们分析另一个规则

# tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
  match u32 00000016 0000ffff at nexthdr+0 flowid 1:10

字段nexthdr选项表示封装在 IP 数据包中的下一个头部,即上层协议的头部。匹配也将从下一个头部的开头开始。匹配应发生在头部的第二个 32 位字中。在 TCP 和 UDP 协议中,此字段包含数据包的目标端口。数字以大端格式给出,即高位在前,因此我们将 0x0016 简单地读作十进制 22,如果这是 TCP,则表示 SSH 服务。正如您所猜测的,如果没有上下文,此匹配是模棱两可的,我们将在稍后讨论这一点。

在理解了以上所有内容之后,我们会发现以下选择器很容易阅读match c0a80100 ffffff00 at 16。我们在这里得到的是从 IP 头部开始计数的第 17 个字节处的三字节匹配。这将匹配目标地址在 192.168.1/24 网络中的任何位置的数据包。在分析了示例之后,我们可以总结一下我们学到的知识。

12.1.2. 通用选择器

通用选择器定义了模式、掩码和偏移量,模式将与数据包内容匹配。使用通用选择器,您可以匹配 IP(或上层)头部中的几乎任何单个位。但是,它们比下面描述的特定选择器更难编写和阅读。通用选择器语法为

match [ u32 | u16 | u8 ] PATTERN MASK [ at OFFSET | nexthdr+OFFSET]

关键字之一u32, u16u8指定模式的长度(以位为单位)。PATTERN 和 MASK 应紧随其后,长度由前面的关键字定义。OFFSET 参数是开始匹配的偏移量(以字节为单位)。如果给出nexthdr+关键字,则偏移量相对于上层头部的开头。

一些例子

# tc filter add dev ppp14 parent 1:0 prio 10 u32 \
     match u8 64 0xff at 8 \
     flowid 1:4

如果数据包的生存时间 (TTL) 为 64,则数据包将与此规则匹配。TTL 是紧跟在 IP 头部第 8 个字节之后的字段。

# tc filter add dev ppp14 parent 1:0 prio 10 u32 \
     match u8 0x10 0xff at nexthdr+13 \
     protocol tcp \
     flowid 1:3 

FIXME: 据指出,此语法目前不起作用。

使用此项来匹配小于 64 字节的数据包上的 ACK

## match acks the hard way,
## IP protocol 6,
## IP header length 0x5(32 bit words),
## IP Total length 0x34 (ACK + 12 bytes of TCP options)
## TCP ack set (bit 5, offset 33)
# tc filter add dev ppp14 parent 1:0 protocol ip prio 10 u32 \
            match ip protocol 6 0xff \
            match u8 0x05 0x0f at 0 \
            match u16 0x0000 0xffc0 at 2 \
            match u8 0x10 0xff at 33 \
            flowid 1:3

此规则将仅匹配设置了 ACK 位且没有进一步有效负载的 TCP 数据包。在这里,我们可以看到使用两个选择器的示例,最终结果将是它们结果的逻辑 AND。如果我们看一下 TCP 头部图,我们可以看到 ACK 位是 TCP 头部第 14 个字节(at nexthdr+13)中的第二个高位 (0x10)。至于第二个选择器,如果我们想让我们的生活更艰难,我们可以写match u8 0x06 0xff at 9而不是使用特定的选择器protocol tcp,因为 6 是 TCP 协议的编号,存在于 IP 头部的第 10 个字节中。另一方面,在此示例中,我们无法为第一个匹配使用任何特定选择器 - 仅仅是因为没有特定的选择器来匹配 TCP ACK 位。

12.1.3. 特定选择器

下表包含本节作者在tc程序源代码中找到的所有特定选择器的列表。它们只是让您的生活更轻松,并提高过滤器配置的可读性。

FIXME: 表格占位符 - 表格在单独的文件 ,,selector.html'' 中

FIXME: 它仍然是波兰语 :-(

FIXME: 必须 sgml'ized

一些例子

# tc filter add dev ppp0 parent 1:0 prio 10 u32 \
     match ip tos 0x10 0xff \
     flowid 1:4

FIXME: tcp dst 匹配不像下面描述的那样工作

以上规则将匹配 TOS 字段设置为 0x10 的数据包。TOS 字段从数据包的第二个字节开始,大小为一个字节,因此我们可以编写等效的通用选择器match u8 0x10 0xff at 1。这为我们提供了 U32 过滤器内部结构的提示 -- 特定规则总是转换为通用规则,并以这种形式存储在内核内存中。这引出了另一个结论 --tcpudp选择器是完全相同的,这就是为什么您不能使用单个match tcp dst 53 0xffff选择器来匹配发送到给定端口的 TCP 数据包 -- 它们也会匹配发送到此端口的 UDP 数据包。您必须记住还要指定协议,并最终得到以下规则

# tc filter add dev ppp0 parent 1:0 prio 10 u32 \
        match tcp dst 53 0xffff \
        match ip protocol 0x6 0xff \
        flowid 1:2