ip命令在linux上实现绝大部分专业路由器上实现的一部分功能,另一个命令tc实现了专业路由器上实现的几乎另一部分功能,而iptable一来,剩余的功能它就全包了,如果再加运行一个zebra的话,linux主机就俨然一个高级路由器了,这时其速率的快慢就不再取决于软件了,而是取决于你的硬件了,用386的机器上一个完全的linux虽然五脏俱全,但是它却不能被扔到公网作为一台真正的路由器来使用。顺便说一句,《Iptables 指南 1.1.19》和《Linux的高级路由和流量控制HOWTO》是两份非常全面,非常有趣,非常容易上手,讲述非常简单的文档。
在linux上,针对出口所作的流控很容易办到,并且其实现也是相当棒的,本质上说就是使用了递归的[排队规则,过滤器,分类队列]三元组,其中分类队列的排队规则归入更下一级别的三元组中,数据首先经过最上面的排队规则,然后使用其过滤器将数据包归入一个类别,这期间可以将数据包排入该类别的队列,如果该类别下面有队列的话。这就是递归的含义之所在。通过这些三元组,可以很方便的在出口配置复杂的流控策略。
除了在出口做流控,完全使用一个网桥来做专门的流控也是可以并且十分不错的,网桥流控受到的干扰要比路由器小很多,毕竟它几乎完全工作在链路层,速度想必是十分迅猛的。
流控无法单独控制入口的速率,但是却可以强制入口速率的上限,因为入口是没有诸如出口那么复杂的队列的。本质上说,对于入口并没有真正的队列,而tc针对入口所作的ingress也没有使用队列,它仅仅借用了tc的那一套分类框架而使用netfilter来实现的。我们在ingress_enqueue函数中看不到什么入队操作,仅仅调用tc_classify做出抉择,然后根据这个抉择返回NF_xxx这一类结果,很显然,要做出这样的抉择必然需要有一些指导信息,这些指导信息其实也是netfilter框架给与的,在调用ing_ops的hook之前就要给与,它使用的是mangle表,我们看到mangle表的hook和ing_ops一样都是挂在prerouting上,但是mangle的优先级比较高,它完全可以在ing_ops接管数据包之前为数据包打上一个标记,这个标记作为指导信息让ingress来做抉择,正如ingress_enqueue中所作的,它并不对数据包进行排队而只是简单的放过或者丢弃(完全按照netfilter的办法),没有队列就意味着没有缓存,没有队列,在操作上我们也无法向root挂载更多的排队规则和过滤器,本质上,递归的过滤器和分类器主要用于最终的调度而不是入队,调度的目的在于控制发往网络上的数据速率,对于入口流量我们没有必要这么做,因为在prerouting这个点上,下面的操作是路由,对于forward和local-in来说,下一步操作是传数据给网卡和进程,都没有必要做所谓的调度,因此对于ingress,只能很容易设定上限而想要限速则无法单独完成,必须要借助于netfilter机制。内核中的入口流量监管是通过police模块完成的,和大多数内核数据结构一样,它也是一个复杂的结构体,内置很多回调函数,其中有个结构体很重要,那就是struct tcf_police,包含burst,mtu,rate等很直观的字段。在其初始化函数tcf_act_police_locate中会根据用户空间的配置来设置这些字段,随即启动一个定时器,在定时器到期的回调函数中处理统计信息计算当前流量,在有流量到来的处理函数tcf_act_police中会更新统计信息(提供给定时器使用),根据定时器的计算结果来抉择如何处置数据包,如果超过设定速率或者其它超限发生,则按照配置的动作裁决。
总的动作实际上是,数据流经netfilter的prerouting这个链,依次经过mangle和ingress,mangle根据iptables的配置为数据包打上标签,然后ingress后续调用其上的qdisc的入队操作,后者调用tc_classfiy,然后遍历所有的策略,最终到达了police模块,然后流量被监管。
这么实现的本质是,我们无法控制路由器之外的主机何时以何速率发来数据,我们所能控制的是我们何时以何速率发出数据,背后的原理就是我们无法控制别人,但是却可以控制自己。另外,将入口流控增加一个队列如何呢?比如不使用netfilter,而是在网卡层面上增加一个队列--类似出口流控,这样如何呢?总的来说,这样不好,因为数据毕竟是别人发过来的,如果这样进行入口限速的话,流控纯粹就是为了限速,对带宽的节省起不到太大的作用,正如水库一样,一般都是在水库开闸泄洪,或者闭闸储存水,若不是这样,而是在水库入口限制山洪,那十有八九就成了抗洪了。
配置实例(改自《linux高级路由和流量控制》):
出口限速:
tc qdisc add dev eth1 root handle 1: cbq avpkt 1000 bandwidth 100mbit
tc class add dev eth1 parent 1: classid 1:1 cbq rate 512kbit allot 1500 prio 5 bounded isolated
tc filter add dev eth1 parent 1: protocol ip prio 16 u32 match ip dst 6.6.6.6 flowid 1:1
入口限速:
iptables -A PREROUTING -i eth0 -t mangle -p tcp -j MARK --set-mark 1
tc qdisc add dev eth0 handle ffff: ingress
tc filter add dev eth0 parent ffff: protocol ip prio 50 handle 1 fw police rate 512kbit burst 10000 mtu 9k drop flowid :1