Linux使用tc对网络进行限速

1. 概述

linux tc工具限速是针对一个网卡的,主要分为三层:qdisc、filter、class;

  • qdisc: 队列,一个网卡一般只需要一个根队列(出向队列),如果有入向限速需求时,可以再创建一个入向队列;队列代表了所有经过这个网卡的流量;
  • filter: 过滤器,对qdisc中的数据流量进行匹配和操作,匹配符合规则的流量可以发送给对应的class进行操作;
  • class: 类别, 对filter符合的流量进行统一的带宽管理(多个filter过滤到的流量进行统一限速,类似共享带宽的实现),如果不需要统一限速,单独限速的场景中是不需要class的,filter可以直接对超过的带宽进行丢弃。

tc限制速度

2. 使用实例

2.1. 对指定IP段的出流量限制

  1. 对指定IP段的出流量限制

    tc qdisc del dev eth0 root 2>/dev/null
    tc qdisc add dev eth0 root handle 2:0 htb default 30
    tc class add dev eth0 parent 2:0 classid 2:11 htb rate 20Mbit ceil 20Mbit prio 1
    tc filter add dev eth0 parent 2:0 protocol ip prio 2 u32 match ip dst 10.140.0.0/16 flowid 2:11
    tc filter add dev eth0 parent 2:0 protocol ip prio 2 u32 match ip dst 10.0.3.11/32 flowid 2:11
  2. 解除限制

    tc qdisc del dev eth0 root

2.2. 对指定IP段的入流量限制

  1. 对指定IP段的入流量限制

    modprobe ifb numifbs=1
    ip link set ifb0 up
    # redirect ingress to ifb0
    tc qdisc add dev eth0 ingress handle ffff:
    tc filter add dev eth0 parent ffff: protocol ip prio 0 u32 match u32 0 0 flowid ffff: action mirred egress redirect dev ifb0
    # add qdisc
    tc qdisc add dev ifb0 root handle 2:0 htb default 30
    # add default class
    tc class add dev ifb0 parent 2:0 classid 2:11 htb rate 20Mbit ceil 20Mbit prio 1
    # add ingress rules for 192.168.0.9
    tc filter add dev ifb0 parent 2:0 protocol ip prio 2 u32 match ip src 172.28.0.93/32 flowid 2:11
  2. 解除限制

    tc qdisc del dev ifb0 root
    tc qdisc del dev eth0 ingress
    modprobe -r ifb

2.3. 默认的tc qdisc规则

$ tc qdisc
  qdisc noqueue 0: dev lo root refcnt 2
  qdisc pfifo_fast 0: dev eth0 root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
#@每个网卡都会产生一条

3. 出向限速(egress)

3.1. 对单一的IP进行限速

  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

3.2. 多个ip共享限速

  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

4. 入向限速(Ingress)

TC的入向限速是只支持filter,不支持class的,如果你只需要对单个ip或者网络限速,那和出向限速类似,但是如果需要共享限速或者其他的高级操作,需要创建class的时候,就不方便了。

大概是因为出向好做,入向不好实现。因为对于出向的流量,网络接口可以保存一时无法处理的数据包,再根据限速规则决定发送还是丢弃。但是对于入向流量,如果保存后再决定是否接受还是丢弃会有一些风险(接受流量如果过大,存不下之类的问题)。

因此入向的限速方法是将网卡的入向流量,导入到一个ifb类型的特殊虚拟网卡中,再由ifb网卡发出的时候对其进行出向限速。这个ifb网卡比较神奇,它就像嵌入到你的网卡中一样,可以只做限速不会影响原有的报文转发逻辑。

4.1. 添加ifb0虚拟网卡(以下两个命令都会生成一个ifb0网卡,任选一个即可)

modprobe ifb numifbs=1 
ip link add dev ifb0 type ifb

4.2. 在eth0添加入向队列

入向队列,默认就是ffff编号,不用设置

tc qdisc add dev eth0 ingress

4.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.4. ifb0网卡上进行出向限速,这个和eth0网卡出向限速的添加方式是一模一样的

唯一的区别是要注意,在filter中match里面匹配的要是dst

5. IPV6限速

前面说的都是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已经被剥离了,需要注意。

6. IPV6和IPV4共同限速

如果一个网卡既有IPV4地址又有IPV6地址,都要进行限速,需要注意:

在创建filter时,同一优先级 prio下,只能有一种protocol(协议)类型,如果ipv4的filter创建选择了prio 1,则ipv6需要配置其他prio (比如prio 2)
否则会报错如下:

RTNETLINK answers: Invalid argument
We have an error talking to the kernel

发表评论

邮箱地址不会被公开。 必填项已用*标注