1、linux tc工具限速是针对一个网卡的,主要分为三层:qdisc、filter、class;
qdisc: 队列,一个网卡一般只需要一个根队列(出向队列),如果有入向限速需求时,可以再创建一个入向队列;队列代表了所有经过这个网卡的流量;
filter: 过滤器,对qdisc中的数据流量进行匹配和操作,匹配符合规则的流量可以发送给对应的class进行操作;
class: 类别, 对filter符合的流量进行统一的带宽管理(多个filter过滤到的流量进行统一限速,类似共享带宽的实现),如果不需要统一限速,单独限速的场景中是不需要class的,filter可以直接对超过的带宽进行丢弃。
(1)添加根队列:
其中网卡是eth0, handle后的值是根队列的唯一编号,后续所有的filter都需要以这个编号为parent;
tc qdisc add dev eth0 root handle 3: htb
(2)对单一的IP进行限速:
限速源ip为192.168.0.3的报文,限速为400mbit,突发值为200mbit,溢出的报文会丢弃;
tc filter add dev eth0 parent 3: protocol ip prio 1 u32 match ip src 192.168.0.3 police rate 400mbit burst 200mbit drop flowid :1
(1)根队列添加和单一IP相同
(2)添加class
class也必须以qdisc id为parent,classid是它的编号,以冒号隔开,冒号前是qdisc id,冒号后是自己的id
tc class add dev eth0 parent 3: classid 3:2 htb rate 100mbit ceil 100mbit
(3)添加filter
将两个ip发出的流量都指向这个class(3:2),filter的parent必须是qdisc 的id
tc filter add dev qg-97d17332-6b parent 3:0 protocol ip prio 1 u32 match ip src 192.168.0.4 flowid 3:2
tc filter add dev qg-97d17332-6b parent 3:0 protocol ip prio 1 u32 match ip src 192.168.0.3 flowid 3:2
TC之所以不能使用class直接进行入向限速,大概是因为出向好做,入向不好实现。因为对于出向的流量,网络接口可以保存一时无法处理的数据包,再根据限速规则决定发送还是丢弃。但是对于入向流量,如果保存后再决定是否接受还是丢弃会有一些风险(接受流量如果过大,存不下之类的问题)。
因此入向的限速方法是将网卡的入向流量,导入到一个ifb类型的特殊虚拟网卡中,再由ifb网卡发出的时候对其进行出向限速。这个ifb网卡比较神奇,它就像嵌入到你的网卡中一样,可以只做限速不会影响原有的报文转发逻辑。
1、添加ifb0虚拟网卡(以下两个命令都会生成一个ifb0网卡,任选一个即可)
modprobe ifb numifbs=1
ip link add dev ifb0 type ifb
2、在eth0添加入向队列
入向队列,默认就是ffff编号,不用设置
tc qdisc add dev eth0 ingress
3、添加filter将流量转发到ifb0网卡
将目的地址(dst)为192.168.0.3的流量进行转发,mirred后面就是转发的目的和方式,redirect就是重定向。如果有多个流量可以多加filter。
tc filter add dev eth0 parent ffff: protocol ip prio 1 u32 match ip dst 192.168.0.3 action mirred egress redirect dev ifb0
4、再ifb0网卡上进行出向限速,这个和eth0网卡出向限速的添加方式是一模一样的。唯一的区别是要注意,在filter中match里面匹配的要是dst。
前面说的都是IPv4的限速,对于IPv6报文的限速就比较麻烦了,它不能直接匹配IPv6地址,只能匹配里面的具体字节,这就需要了解报文的结构了(实际上抓个包拿wireshark看看,也就知道ip对应的字节位置了)
以下命令匹配的报文时,源ip地址地址是1211::338的报文。命令中at后面的值就是这个ip字节在报文中所处的相对位置。
tc filter add dev eth0 parent 3: protocol ipv6 prio 1 u32 \
match u16 0x1211 0xffff at 8 \
match u16 0x0000 0xffff at 10 \
match u16 0x0000 0xffff at 12 \
match u16 0x0000 0xffff at 14 \
match u16 0x0000 0xffff at 16 \
match u16 0x0000 0xffff at 18 \
match u16 0x0000 0xffff at 20 \
match u16 0x0338 0xffff at 22 classid 3:2
显然,命令里面是从8开始匹配的,但是实际报文如下图,对应的src_ip是在第22字节,大概是在tc匹配的时候,报文前面14字节的源目的mac和报文的eth.type已经被剥离了,需要注意。