Next Previous Contents

13. 更多的分类器 (More classifiers)

分类器(classifier)就是核心用来决定封包需要送入哪一个伫列的方法。有许多形形色色的分类器﹐各司其职。

fw

根据防火墙如何标识封包来作判断。

u32

根据封包里面的栏位作判断 (例如﹕来源 IP 位址﹐等)。

route

根据封包之路由作判断。

rsvp, rsvp6

根据目标(目的位址﹐协定)﹐或以来源作判断。

tcindex

FIXME﹕有待补充

请注意﹐大体上您有多种办法来分类封包﹐但会降低系统之整体执行效率。

一般而言﹐分类器都能接受不同参数。为方便起见﹐兹列如下﹕

protocol

分类器所能接受的协定。通常您可能只会接受 IP 流量。必须指定。

parent

为分类器指定接管(handle)﹐只能是已经存在的类别(class)。必须指定。

prio

分类器的优先等级。数值越高越快。

handle

指定不同事物到不同的过滤器去。

FIXME﹕增加选项

下面的章节均假设您要将流量引导致 HostA ﹐还假设顶层(root)类别已经设定为 1﹕﹐同时您要将挑选出来的流量送至 1:1 去。

13.1 "fw" 分类器 (The "fw" classifier)

"fw" 分类器要依靠防火墙把需要引导的封包标识起来。所以﹐我们必须先设定好防火墙﹐为它们打标签。

# iptables -I PREROUTING -t mangle -p tcp -d HostA \
 -j MARK --set-mark 1

好了﹐所有传给该主机的封包都被标识为 1。现在我们建立规则﹐以真正引导封包﹐我们只要指定好被标识为 1 的封包要送到类别 1:1 那边去。透过如下命令就可以﹕

# tc filter add dev eth1 protocol ip parent 1:0 prio 1 handle 1 fw classid 1:1

命令本身应该说得蛮清楚的了。附在 1:0 类别的过滤器获得的优先值为 1﹐过滤哪些被防火墙标识为 1 的封包﹐再送到类别 1:1 那边去。注意﹐这里的 handle 要如何使用﹐取决于封包如何标识。

不经寒彻骨﹐哪得梅花香﹖这已是较为简单的方法了﹐至于其它方法﹐我觉得更为难懂。注意﹐您可以完全将防火墙程序的功能应用在此分类器上面﹐包括比对 MAC 位址﹑用户身份﹑以及所有其它防火墙所能比对的事物。

13.2 "u32" 分类器 (The "u32" classifier)

U32 过滤器是目前实作中所能找到的最强劲的过滤器。它完全依靠杂凑表(hashing tables)﹐配合众多过滤规则而变得扎实耐用。

最简单的做法是﹐U32 过滤器有一整列的记录(records)﹐各自包含两个栏位﹕选择器(selector)和动作(action)。选择器(后面会介绍)﹐会与当前处理的 IP 封包做比较﹐当碰到第一个符合的封包﹐然后就作出相应的动作。最简单的动作﹐是将封包送至指定的 CBQ 类别去。

我们可用 tc filter 命令程序去设定过滤器﹐一共有 3 个部份﹕过滤器的规格(specification)﹑选择器﹑以及动作。过滤器规则可以如下定义﹕

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

其中的 protocol 栏位说明过滤器所适用的协定﹐我们这里只就 ip 协定进行讨论。至于 preference(偏好值) 栏位(也可以用priority 来代替)﹐设定当前过滤器的优先值。这很重要﹐因为您或许会有好几个过滤器(规则列表)﹐各自拥有不同的优先值。每一列规则按照规则新增顺序通过﹐然后才处理低优先值(高偏好值)的规则列。最后的 parent 栏位定义出过滤器所属的 CBQ 树顶(如 1:0)。

以上选项均适用于所有过滤器﹐非 U32 独美。

U32 选择器 (U32 selector)

U32 选择器包含式样(pattern)定义﹐以比对当前处理的封包。它一丝不苟的定义出哪些封包标头的位元(bits) 要用来比对﹐心无旁骛﹐然却四两拨千斤。不如让我们看看以下范例﹐直接取自一个复杂且真实的过滤器﹕

# filter parent 1: protocol ip pref 10 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:3 \
  match 00100000/00ff0000 at 0

我们暂时不用管第一行 --- 全部参数度是用来描述过滤器之杂凑表而已。且让我们仔细看看关于选择器那一行﹐也就是带 match 关键字的那行。选择器会比对第 2 个 byte 为 0x10(0010) 的 IP 标头。或许您已猜到﹐那个 00ff 数字就是比对遮罩(mask)。目前为 0xff﹐所以﹐这个 byte 只能是 0x10。然后 at 关键字意思是说﹐这个比对是从指定的 offset(以 bytes 算)开始 --- 在目前范例中﹐在封包的一开始处。换成我们的人类语言来说的话﹐如果封包的 Type of Serice 栏位带有 `low delay` 位元设定﹐那这个比对就符合了。让我们在看看另一个规则吧﹕

# filter parent 1: protocol ip pref 10 u32 fh 800::803 order 2051 key ht 800 bkt 0 flowid 1:3 \
  match 00000016/0000ffff at nexthdr+0

此处﹐有一个 nexthdr 选项﹐代表 IP 封包里面的下一个标头﹐例如上层协定的标头。这个比对也是从下一个标头的一开始处进行。比对应该出现在标头首 32-bit 中的第二个字(word)。在 TCP 和 UDP 协定里﹐此栏位包含封包目的端埠口(port)。此数字以 big-endian 格式显示﹐例如 older 位元排前面﹐所以我们只要将 0x0016 换成十进位就是 22 了﹐换而言之﹐如果这是 TCP 的话﹐那他就是 SSH 服务。正如您所猜的﹐如果离开相关承接(我们后面再述)﹐这个比对会变得不明所以。

当我们对前述都有一定了解之后﹐就会发现如下这个选择器其实蛮好理解的﹕match c0a80100/ffffff00 at 16 。我们只要从 IP 标头开始﹐比对第 17 个 byte 起的 3 个 byte 即可。这会比对所有目的地为网路 192.168.1/24 的封包。看过这些范例之后﹐然后让我们将所学的归纳一下吧。

通用选择器 (General selectors)

通用选择器定义出式样(pattern)﹑遮罩(mask)﹑还有封包内容里关于比对式样的 offsest。使用通用选择器﹐您实际上可以比对 IP (或上层) 标头里的每一个单独的位元(bit) 。比较后面介绍的特定选择器﹐它们更难读写。通用选择器的语法如下﹕

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

u32u16﹑或 u8 这些关键字﹐各自指定位元里的式样长度。PATTERN 和 MASK 必须接在前面关键词定义的长度后面。至于 OFFSET 参数﹐则以 byte 为单位﹐指定开始比对的 offset 所在。如果 nexthdr+ 有设定﹐那么 offset 则相对的从上层协定标头开始算起。

范例﹕

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

如果存活期(TTL)为 64 的话﹐封包就符合比对。从 IP 标头数起﹐第 8 个 byte 开始就是 TTL 栏位了。

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

此规则仅比对带 ACK 位元设定的 TCP 封包。这里我们可以看到一个范例中使用两个选择器﹐最终结果则将两个结果用 AND 逻辑运算得出。如果我们仔细的看看 TCP 标头结构图﹐我们会发现 ACK 位元﹐是 TCP 标头第 14 个 byte 算起(at nexthdr+13)第 2 个 older 位元(0x10)。至于第二个选择器﹐如果我们想试试克难的方法﹐不指定选择器使用 protocol tcp﹐可这样写﹕ match u8 0x06 0xff at 9﹐因为 TCP 的协定号码是 6﹐位于 IP 标头的第 10 个 byte。相对而言﹐在此范例中﹐我们不能用特定选择器来做第一个比对 --- 这是因为没有特定选择器可以比对 TCP ACK 位于之故。

特定选择器 (Specific selectors)

如下表格列出了本章作者所从 tc 程序中发现的全部特定选择器。他们帮您省掉许多工作﹐同时让您的过滤器设定更具可读性。

FIXME: 表格存放位置 --- 存于另外的文档﹕selector.html。

FIXME: 只有波兰语 :-(

FIXME: 有待转成 sgml 格式。

范例:

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

以上规则会比对哪些 TOS 栏位设为 0x10 的封包。TOS 栏位从封包的第 2 个 byte 开始﹐并占一个 byte 的长度﹐这样好比我们另写一个相等的通用选择器﹕match u8 0x10 0xff at 1。由此﹐我们就可以一窥 U32 过滤器的内里乾坤 --- 特定规则都会被转换为通用规则﹐同时以此形式存于核心内存之内。举一反三 --- tcpudp 选择器也是如法泡制﹐这也是为何您不能用单独的 match tcp dst 53 0xffff 选择器﹐来比对哪些送给特定埠口(port) 的 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

13.3 "route" 分类器 (The "route" classifier)

此分类过滤器根据路由表格(routing tables)而定。当一个封包穿越分类(classes)抵达被标识为 "route" 的过滤器之后﹐它会按照路由表格的信息分离(split)这个封包。

# tc filter add dev eth1 parent 1:0 protocol ip prio 100 route

这里﹐我们新增一个 route 分类器给上游节点(parent node) 1:0 ﹐其优先值为 100。当封包抵达这个节点后(因为这是顶层(root) 分类﹐所以马上生效)﹐它会查询路由表格﹐并且﹐如果有一个比对符合的话﹐就把它送给指定的分类﹐同时赋予 100 的优先值。最后﹐您增加一个恰当的路由记录﹐交由动作(action)处理﹕

这里的诀窍是﹐要根据目的(destination)或来源(source)来定义一个所谓的 'realm' ﹐参考如下﹕

# ip route add Host/Network via Gateway dev Device realm RealmNumber

比方说﹐我们可以定义目的网路 192.168.10.0﹐它的 realm 号码是 10﹕

# ip route add 192.168.10.0/24 via 192.168.10.1 dev eth1 realm 10

在增加 route 过滤器之后﹐我们可以使用 realm 号码来代表网路或主机﹐同时指定路由如何配对(match)过滤器。

# tc filter add dev eth1 parent 1:0 protocol ip prio 100 \
  route to 10 classid 1:10

以上规则是说﹐送给网路 192.168.10.0 的封包﹐会配对 class id 1:10。

Route 过滤器还可以用来比对源路由(source routes)。例如﹐有一个子网接到 Linux router 的 eth2 界面﹕

# ip route add 192.168.2.0/24 dev eth2 realm 2
# tc filter add dev eth1 parent 1:0 protocol ip prio 100 \
  route from 2 classid 1:2

这里﹐过滤器指定出来自子网 192.168.2.0 (realm 2) 的封包﹐就配对 class id 1:2 。

13.4 "rsvp" 分类器 (The "rsvp" classifier)

FIXME: 有待补充

13.5 "tcindex" 分类器 (The "tcindex" classifier)

FIXME: 有待补充


Next Previous Contents