分类器(classifier)就是核心用来决定封包需要送入哪一个伫列的方法。有许多形形色色的分类器﹐各司其职。
根据防火墙如何标识封包来作判断。
根据封包里面的栏位作判断 (例如﹕来源 IP 位址﹐等)。
根据封包之路由作判断。
根据目标(目的位址﹐协定)﹐或以来源作判断。
FIXME﹕有待补充
请注意﹐大体上您有多种办法来分类封包﹐但会降低系统之整体执行效率。
一般而言﹐分类器都能接受不同参数。为方便起见﹐兹列如下﹕
分类器所能接受的协定。通常您可能只会接受 IP 流量。必须指定。
为分类器指定接管(handle)﹐只能是已经存在的类别(class)。必须指定。
分类器的优先等级。数值越高越快。
指定不同事物到不同的过滤器去。
FIXME﹕增加选项
下面的章节均假设您要将流量引导致 HostA
﹐还假设顶层(root)类别已经设定为 1﹕﹐同时您要将挑选出来的流量送至 1:1 去。
"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 位址﹑用户身份﹑以及所有其它防火墙所能比对的事物。
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 选择器包含式样(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 的封包。看过这些范例之后﹐然后让我们将所学的归纳一下吧。
通用选择器定义出式样(pattern)﹑遮罩(mask)﹑还有封包内容里关于比对式样的 offsest。使用通用选择器﹐您实际上可以比对 IP (或上层) 标头里的每一个单独的位元(bit) 。比较后面介绍的特定选择器﹐它们更难读写。通用选择器的语法如下﹕
match [ u32 | u16 | u8 ] PATTERN MASK [ at OFFSET | nexthdr+OFFSET]
u32
﹑u16
﹑或 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 位于之故。
如下表格列出了本章作者所从 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 过滤器的内里乾坤 --- 特定规则都会被转换为通用规则﹐同时以此形式存于核心内存之内。举一反三 --- tcp
和 udp
选择器也是如法泡制﹐这也是为何您不能用单独的 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
此分类过滤器根据路由表格(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 。
FIXME: 有待补充
FIXME: 有待补充