本文主要是对Linux的QoS限速和OVS QoS限速使用的一些理解(不包含TC各个算法具体实现),如有错误,望能批评指正。
常用流控算法的理解
classless qdisc
- [p or b]fifo,最简单的qdisc,纯粹的先进先出,只有一个参数limit设置队列长度,pfifo以报文个数为单位,bfifo以字节为单位。
- pfifo_fast,系统标准的qdisc,他的队列分为三个band,band0最高,优先处理,band0有数据就不会处理band1,根据报文的TOS分配到三个band。
- red,当带宽接近于规定的带宽时,系统会随机丢弃一些报文,非常适合高带宽应用。
- sfq,按照session为流量进行排序,然后循环发送每个session的报文。
- tbf,适用于把流速降低到某个值。
- ingress,用于入流量。
classful qdisc
- cbq,既有限制宽带的能力,也有带宽优先级管理的功能。根据报文离队频率和链路层的带宽计算空闲时间,通过调整空闲时间限制带宽。
- htb,保证每个类型的带宽,但是也允许特定的类可以突破带宽上限,占用别的类的带宽。
- prio,不能限制带宽,只能对流量优先级管理。
- hfsc,为leaf class保证精确的带宽和分配延时,以及剩下带宽的公平,和htb不同的是,他通过丢包,让低延时的会话受益。
我们今天主要针对classless的ingress和classful的htb,前者是入口的流向限制,后者是出口的流量限制。
TC命令
TC命令入口的限速
命令思路
- 首先删除网卡配置的ingress QDisc。
- 为网卡创建一个基于ingress的QDisc。
- 为该QDisc建立一个基于ip或者其他的过滤器filter,限制速度为多少。
实际命令
tc qdisc del dev <devname> handle ffff: ingress 2>/dev/null
tc qdisc add dev <devname> handle ffff: ingress
tc filter add dev <devname> parent ffff: protocol ip prio <priority> u32 match ip src <ipaddr> police rate <kbits_rate>kbit burst <kbits_burst>k mtu 65535 drop
TC命令的出口限速
方式1
命令思路
该方式的思路和格式类似上面入口的配置,这里不推荐这种方式,经过验证准确性并不好。没有体现出classful的优势。
- 首先删除网卡配置的HTB QDisc。
- 为网卡创建一个基于HTB的QDisc。
- 为该QDisc建立一个基于ip或者其他的过滤器filter,限制速度为多少。
实际命令
tc qdisc del dev <devname> root 2>/dev/null
tc qdisc add dev <devname> root handle 1: htb
tc filter add dev <devname> parent 1: protocol ip prio <priority> u32 match ip src <ipaddr> police rate <kbits_rate>kbit burst <kbits_burst>k mtu 65535 drop
方式2
命令思路
- 首先删除网卡配置的HTB QDisc。
- 为网卡创建一个基于HTB的QDisc。
- 为该QDisc建立一个子类的队列,并未该队列配置限速。
- 建一个过滤器filter,将符合的报文送入上面配置的队列。
实际命令
tc qdisc del dev <devname> root 2>/dev/null
tc qdisc add dev <devname> root handle 1: htb
tc class add dev <devname> parent 1: classid 1:queue_id htb rate <kbits_rate>kbit ceil <kbits_rate>kbit burst <kbits_burst>k
tc filter add dev <devname> protocol ip parent 1: prio <priority> u32 match ip src <ipaddr> flowid 1:queue_id
方式3
命令思路
- 首先删除网卡配置的HTB QDisc。
- 为网卡创建一个基于HTB的QDisc。
- 为该QDisc建立一个子类的队列,并未该队列配置限速。
- 建一个过滤器filter,将iptables指定的报文导入上面队列中。
实际命令
tc qdisc del dev <devname> root 2>/dev/null
tc qdisc add dev <devname> root handle 1: htb
tc class add dev <devname> parent 1: classid 1:queue_id htb rate <kbits_rate>kbit ceil <kbits_rate>kbit burst <kbits_burst>k
tc filter add dev <devname> protocol all parent 1: prio <priority> handle 1 fw classid 1:queue_id
iptables配合的命令如下
iptables -t mangle -A POSTROUTING -d <ipaddr> -j MARK --set-mark queue_id
方式4
命令思路
- 首先删除网卡配置的HTB QDisc。
- 为网卡创建一个基于HTB的QDisc。
- 为该QDisc建立一个子类的队列,并未该队列配置限速。
- 建一个过滤器filter,将router指定的报文导入上面队列中。
实际命令
tc qdisc del dev <devname> root 2>/dev/null
tc qdisc add dev <devname> root handle 1: htb
tc class add dev <devname> parent 1: classid 1:queue_id htb rate <kbits_rate>kbit ceil <kbits_rate>kbit burst <kbits_burst>k
tc filter add dev <devname> protocol all parent 1: prio <priority> route to queue_id classid 1:queue_id
router配合的命令如下
ip route add <ipaddr> dev eth0 via <device_ip> realm queue_id
OVS的QOS
OVS配置命令
ovs-vsctl set interface <devname> ingress_policing_rate=<kbits_rate> ingress_policing_burst=<kbits_burst>
//配置ingress,这个地方只是限制整个端口的,目前不能细分,跟实现有关。他的filter是all。
ovs-vsctl set port <devname> qos=@qosname -- --id=@qosname create qos type=linux-htb queues=queue_id=@queue_name -- --id=@queue_name create queue other-config:max-rate=<bytes_rate>
//配置出口HTB类型的一个队列,需要ovs-ofctl配置将需要的报文导入该队列
ovs-ofctl add-flow brname in_port=portno,actions=set_queue:queue_id,normal
//最简单的倒流量的方式,可以在流表添加过滤条件。
OVS运行时对TC的配置
OVS启动的时候会调用如下代码对QoS进行配置。
ovs-vswitchd.c
main-->bridge_run-->bridge_reconfigure-->iface_configure_qos
iface_configure_qos
netdev_set_qos
为接口创建qdisc。- 根据队列的queue数量,分别调用
netdev_set_queue
为队列创建class。 ofproto_port_set_queues
为ofproto_port
设置queue的数据结构,为了和上面的class对应。netdev_set_policing
netdev_set_qos-->netdev_linux_set_qos
tc_lookup_ovs_name
根据OVS的qos算法找到对应的ops,之前各种类型的ovs的qos算法都已经注册,比较好找,轮询即可。- 如果是
tc_ops_noop
,直接调用对应的tc_install
,即noop_tc_install
,然后返回。 tc_query_qdisc
主要创建qdisc,类似命令行tc qdisc add dev <dev> root handle 1: kind
,如果已经创建直接返回。- 最早查询的ops和在Linux Kernel创建的ops比较是不是同一种,是的话调用
ops->qdisc_set
,即htb_qdisc_set
,创建class,类似命令行tc class add dev dev_name parent 1: classid 1:fffe htb rate <min_rate>bps ceil <max_rate>bps burst 0
。 - 如果不是同一种则调用
tc_del_qdisc
删除存在的qdisc,调用ops->tc_install
,即htb_tc_install
,创建qdisc和class。
htb_tc_install
htb_setup_qdisc__
相对于命令行tc qdisc add dev <dev> root handle 1: htb default 1
。htb_setup_class__
相当于命令行tc class replace dev <dev> classid 1:fffe parent 1: htb rate <min_rate>bps ceil <max_rate>bps burst <burst>b prio <priority>
。
netdev_set_queue-->netdev_linux_set_queue
tc_query_qdisc
主要创建qdisc,类似命令行tc qdisc add dev <dev> root handle 1: kind
,如果已经创建直接返回。- 调用
ops->class_set
,即htb_class_set
,类似命令行tc class replace dev <dev> classid 1:queue_id+1 parent 1:fffe htb rate <min_rate>bps ceil <max_rate>bps burst <burst>b prio <priority>
。
ofproto_port_set_queues
不是特别了解这个操作的意义,可能是修改tos/dscp的
- 将上面
netdev_set_queue
配置的queue的数据ofproto_port_queue
设置到ofport中。
netdev_set_policing-->netdev_linux_set_policing
tc_add_del_ingress_qdisc
首先删除ingress的qdisc,类似命令行tc qdisc del dev <devname> handle ffff: ingress
。tc_add_del_ingress_qdisc
接着创建ingress的qdisc,类似命令行tc qdisc add dev <devname> handle ffff: ingress
。tc_add_policer
配置限速速率,类似命令行tc filter add dev <devname> parent ffff: protocol all prio 49 basic police rate <kbits_rate>kbit burst <kbits_burst>k mtu 65535 drop
。
以上就是OVS配置QOS的流程,出口的时候使用HTB。
目前得出的结论就是OVSingress就是和上面TC命令介绍的方式一致,出口策略没有采用TC的filter命令,而是根据queue_id创建相应的class,而流表会使用set_queue的action设置queue。而ovs-ofctl中的set_queue action
是设置的skb的priority,而在htb的htb_classify
会根据skb->priority
找到对应的class。
限速的实现点有以下几个
- ingress限速:
__netif_receive_skb-->__netif_receive_skb_core-->handle_ing
(常用,配置了CONFIG_NET_CLS_ACT
)。 - egress限速:
dev_queue_xmit-->__dev_xmit_skb
。
vhost网口处的限速调用为
- ingress限速:
vhost_worker-->handle_tx_kick-->handle_tx(sock->ops->sendmsg)-->tun_sendmsg-->tun_get_user(内部的tun_alloc_skb?)-->netif_rx_ni(netif_rx没看到多占cpu)-->do_softirq-->call_softirq-->__do_softirq-->net_rx_action-->process_backlog-->__netif_receive_skb-->__netif_receive_skb_core-->handle_ing
- egress限速:
ovs_vport_send-->netdev_send-->dev_queue_xmit-->__dev_xmit_skb