Linux TC 流量控制介绍
前段时间在做一些测试的时候接触到了Linux tc,因为需要对数据包添加延迟,用到了tc中的netem。添加简单的延迟非常简单,像这样一条命令就搞定了:$ tc qdisc add dev eth0 root netem delay 1s,你甚至不需要完全理解命令中参数的含义。但是当你想做一些更加特殊的限制的时候,(比如只对某个特定的ip端口添加延迟、或者只对入站的流量添加延迟),事情就变得有些棘手了,简单的百度貌似已经满足不了要求了。你不得不了解TC中的一些基本概念,以及tc[2]命令中相关参数的含义。
本文正是带你了解这些TC中的基本概念,并通过一个实际例子,将这些概念与tc命令联系起来。
本博客已迁移至CatBro's Blog,那是我自己搭建的个人博客,欢迎关注。本文链接
示例命令
考虑到这是一个科普向的介绍,这里只举了一个最简单的例子,但是基本上包含了重要的概念。本文的期望是,让读者在阅读后可以完全理解下面的例子,并且知道如何根据自身的需求编写自己的命令。
sudo tc qdisc add dev eth0 root handle 1: prio bands 4
sudo tc qdisc add dev eth0 parent 1:4 handle 40: netem loss 10% delay 40ms
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 4 u32 match ip dst 192.168.190.7 match ip dport 36000 0xffff flowid 1:4
TC基本概念
QDISCS
全称是queueing discipline,我们姑且称其为排队规则吧。它是协议栈和网络接口之间的一个缓冲层。你可以在qdisc上对数据包做一些你想做的操作,比如分类、整形、调度等。
qdisc分为无类(classless)qdisc和有类(classful)qdisc。无类qdisc不再内部细分类,有类qdisc可进一步包含多个分类,每个class上可以进一步包含子qdisc,子qdisc也可以是有类qdisc,这样就形成了树状的分层结构。
CLASSES
有类qdisc可以有多个子类(class),有些qdisc预定义了子类(如prio),有些则需要用户添加类。一个类上又可以附加其他类。最末端没有子类的类称为叶子类,它上面附加了一个qdisc。当创建一个class的时候,默认会附加一个fifo qdisc,它只是一个简单的队列,不对数据包进行任何的操作。当在这个类上增加子类的时候,这个默认的qdisc被移除。你可以将这个默认的fifo qdisc替换成其他任意你想用的qdisc。
FILTERS
过滤器,用于有类qdisc中,决定将包入队到哪个类中。每当一个包到达有子类的类时,就需要进行分类。其中一种分类的方法就是使用过滤器(另外两个是ToS和skb->priority)。所有附加到类上的过滤器会被依次调用,直到其中一个返回裁决。一个filter包含了一些条件,当一个包到达该节点时,会根据包的特征判断是否匹配。
以上3个是TC中最基本的3个概念,任何复杂的流量控制都是通过这个三元组递归实现的。
层级结构
每个接口有一个egress 'root qdisc',默认是pfifo_fast。每个qdisc和class都分配一个句柄handle,句柄用于在后续的配置语句中进行引用。除了egress qdisc,一个接口也可以有一个ingress qdisc,负责管制入站的流量。但是ingress qdisc相比classful qdisc其可能性是非常有限。(所以才有所谓的控发不控收,对入站流量进行控制通常需要借助ifb[6]或者imq)。
这些qdisc的handles有两个部分组成,一个major数和一个minor数:<major>:<minor>。习惯上将root qdisc命名为1:,等价于1:0。一个qdisc的minor数总是0。
子类需要跟它们的parent有相同的major数。major数在一个egress或ingress内必须是唯一的,minor数在一个qdisc和它的class中必须是唯一的。
一个典型的层级结构如下:
1: root qdisc
|
1:1 child class
/ | \
/ | \
/ | \
/ | \
1:10 1:11 1:12 child classes
| | |
| 11: | leaf class
| |
10: 12: qdisc
/ \ / \
10:1 10:2 12:1 12:2 leaf classes
内核只跟root qdisc进行通信,每当包需要入队或者出队的时候,都需要从root节点开始,最终到达叶子节点,从而决定入队到哪里,或者从哪里出队。
比如当一个包入队时,它可能会经过如下路径:
1: -> 1:1 -> 1:12 -> 12: -> 12:2
当然也可能直接走如下路径:
1: -> 12:2
这种情况,就是root qdisc上的过滤器决定把包直接送到12:2。
{% label warning@注意:%}入队和出队时虽然节点的拓扑图是一样的,但是每个节点表示的含义却有所不同[4]。入队时是根据过滤器和包的特征决定走哪条路径,而出队时则取决于qdisc本身的调度算法,比如FIFO、优先级队列、SFQ的顺序调度等。
过滤器
前面已经提到了过滤器用于将包分类到子类,那么具体是如何对包进行分类的呢?tc支持很多类型的分类器,它们根据数据包相关的不同信息来作出决策。其中最常用的就是u32分类器,它根据数据包中的字段做出决策(例如源IP地址等)。还有比如fw分类器,根据防火墙如何标记数据包来做出决策,你可以使用iptables标记目标数据包,然后通过fw分类器进行过滤。另外还有诸如route分类器、cgroup分类器、bpf分类器等,篇幅原因不再赘述。下面仅介绍最常见的u32分类器。
公共参数
分类器一般接收以下几个公共的参数:
protocol
分类器接受的协议,通常你只接受IP流量。必须。
parent
分类器附加到哪个handle上。这个handle必须是一个已经存在的类。必须。
prio|perf
分类器的优先级。数字越小的越先进行匹配尝试。
handle
这个handle对于不同的过滤器表示不同的含义。
u32分类器[3]
u32过滤器最简单的格式是设置一组选择器对包进行匹配,匹配的包分到特定的子类中,或者执行一个action。u32分类器提供了多种不同的选择器,可以大致分成特殊选择器和通用选择器两类。
特殊选择器
常用的有ip选择器和tcp选择器。特殊选择器简化了一些常用字段的设置,可以匹配包头中的各种字段,比如:
tc filter add dev eth0 protocol ip parent 1:0 prio 10 u32 \
match ip src 192.168.8.0/24 flowid 1:4
上例匹配ip源地址在192.168.8.0/24子网的包。
tc filter add dev eth0 protocol ip parent 1:0 prio 10 u32 \
match ip protocol 0x6 0xff \
match tcp dport 53 0xffff \
flowid 1:2
上例匹配TCP协议(0x6)、且目的端口为53的包。
通用选择器
特殊选择器总是可以改写成对应的通用选择器,通用选择器可以匹配 IP(或上层)头中的几乎任何位,不过相比特殊选择器较难编写和阅读。其语法如下:
match [ u32 | u16 | u8 ] PATTERN MASK at [OFFSET | nexthdr+OFFSET]
其中u32|u16|u8指定pattern的长度,分别为4个字节、2个字节、1个字节。PATTERN表示匹配的包的pattern,MASK告诉过滤器匹配哪些位,at表示从包的指定偏移处开始匹配。
来看一个例子:
tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
match u32 00100000 00ff0000 at 0 flowid 1:10
选择器会匹配IP头第二个字节为0x10的包,at 0表示从头开始匹配,mask为00ff0000所以只匹配第二个字节,pattern为00100000即第二个字节为0x10。
再来看另一个例子:
tc filter add dev eth0 protocol ip parent 1:0 pref 10 u32 \
match u32 00000016 0000ffff at nexthdr+0 flowid 1:10
nexthdr选项表示封装在IP包里的下一个头,即上层协议的头。at nexthdr+0表示从下一个头第一个字节开始匹配。因为mask为0000ffff,所以匹配发生在头的第三和第四个字节。在TCP和UDP协议中这两个字节是包的目的端口。数字是由大段格式给出的,所以pattern 00000016转换成十进制是22。即该选择器会匹配目的端口为22的包。
示例解析
好了,现在我们可以回过头来看最初的那个示例了,看看这些命令到底是什么意思。
sudo tc qdisc add dev eth0 root handle 1: prio bands 4
sudo tc qdisc add dev eth0 parent 1:4 handle 40: netem loss 10% delay 40ms
sudo tc filter add dev eth0 protocol ip parent 1:0 prio 4 u32 match ip dst 192.168.190.7 match ip dport 36000 0xffff flowid 1:4
我们一行行来看,第一行在设备eth0上添加了一个root qdisc,句柄为1:,qdisc类型为prio,bands数为4。
prio是一个有类的qdisc。它的作用跟默认的qdisc pfifo_fast类似。pfifo_fast有三个所谓的band,不同band的流量具有不同的优先级。每个band内,则应用FIFO规则。
prio qdisc,默认会创建3个子类,包含纯FIFO qdisc,默认根据ToS位进行分类。你可以使用过滤器来对流量进行分类,你也可以在子类上附加其他qdisc替换默认的FIFO。
接下来看第二个命令,parent 1:4表示在子类1:4上,handle 40:表示句柄为40:,netem表示添加一个netem qdisc,loss 10% delay 40ms则是netem的参数,表示丢包10%、延迟40ms。netem[5]是一个用于提供网络仿真功能的无类qdisc,可以模拟延迟、丢包、包重复、包失序等各种情况。
第三个命令则是添加了一个过滤器,parent 1:0表示在根节点上添加该过滤器,prio 4是过滤器的优先级,如果有很多过滤器会根据优先级的值按顺序进行尝试。u32表示使用u32分类器。match ip dst 192.168.190.7表示匹配ip地址为192.168.190.7的包,match ip dport 36000 0xffff表示匹配目的端口为36000的包,多个选择器之间是“与”的关系,flowid 1:4表示将匹配的包分类到1:4子类中。
所以最终的效果是,发往192.168.190.7且目的端口为36000的包,会分类到1:4子类,添加40ms的延迟,且有10%的丢包率。其他包则还是默认的行为,即根据ToS字段分类到1:1、1:2或1:3子类中,然后根据优先级依次发送。
画出该例子的分层结构图,大致如下:
1: root qdisc (prio)
/ | \ \
/ | \ \
/ | \ \
1:1 1:2 1:3 1:4 classes
| | | |
40: qdiscs
pfifo pfifo pfifo netem
band 0 1 2 3
后记
本文只介绍了tc的基本概念和简单用法。prio qdisc只对包做了一个分类,并没有进行整形。实际上,你也可以使用更复杂的带整形的qdisc,比如CBQ、HTB等,也可以增加更多的层级。你还可以在叶子节点上添加SFQ qdisc以实现会话级的带宽公平性。相信理解了TC的这些基本概念,再根据自身需求使用其他qdisc也不是什么难事了。
参考资料
Linux TC 流量控制介绍的更多相关文章
- linux tc流量控制
tc流量控制 项目背景 vintage3.0接口lookupforupdage增加一个策略,当带宽流量tx或rx超过40%,75%随机返回304:超过60%,此接口均返回304 为了对测试机器进行流量 ...
- Linux TC流量控制HOWTO中文版
<本文摘自Linux的高级路由和流量控制HOWTO中文版 第9章节>网人郭工进行再次编译: 利用队列,我们可以控制数据发送的方式.记住我们只能对发送数据进行控制(或称为整形).其实,我们无 ...
- Linux内核策略介绍
Linux内核策略介绍学习笔记 主要内容 硬件 策略 CPU 进程调度.系统调用.中断 内存 内存管理 外存 文件IO 网络 协议栈 其他 时间管理 进程调度 内核的运行时间 系统启动.中断发 ...
- Linux实战教学笔记07:Linux系统目录结构介绍
第七节 Linux系统目录结构介绍 标签(空格分隔):Linux实战教学笔记 第1章 前言 windows目录结构 C:\windows D:\Program Files E:\你懂的\精品 F:\你 ...
- Linux的简单介绍和常用命令的介绍
Linux的简单介绍和常用命令的介绍 本说明以Ubuntu系统为例 Ubuntu系统的安装自行百度,或者参考http://www.cnblogs.com/CoderJYF/p/6091068.html ...
- Linux性能工具介绍
l Linux性能工具介绍 p CPU高 p 磁盘I/O p 网络 p 内存 p 应用程序跟踪 l 操作系统与应用程序的关系比喻为“唇亡齿寒”一点不为过 l 应用程序的性能问题/功能问 ...
- Linux core 文件介绍
Linux core 文件介绍 http://www.cnblogs.com/dongzhiquan/archive/2012/01/20/2328355.html 1. core文件的简单介绍在一个 ...
- Linux 启动参数介绍
Linux 启动参数介绍 取自2.6.18 kernel Documentation/i386/boot.txt 文件中介绍 vga= 这里的不是一个整数(在C语言表示法中,应是十进制,八进制或者十六 ...
- Linux系统启动过程介绍
Linux系统启动过程介绍 学习操作系统有必要了解一下系统的启动过程,这样在面对各种系统故障的时候能快速定位解决问题,下面以Centos来分析linux系统的启动过程. 1.BIOS自检:当开机的时候 ...
随机推荐
- requests实现接口测试
python+requests实现接口测试 - get与post请求基本使用方法 http://www.cnblogs.com/nizhihong/p/6567928.html Requests ...
- postman中用当前时间戳做请求的入参
用postman做接口测试的,有些接口需要带上当前时间的时间戳作为请求的入参,postman支持这种功能吗? 答案是肯定的. 文中有使用时间戳的两种方法和postman常用的预定义变量. 例子中接口的 ...
- 【Linux/Oracle】ORA-00031:标记要终止的会话 解决
在PL/SQL操作了一条delete语句用于删除这张1.4亿条数据的表,执行了12个小时还没删完 (经DB指导,量级大的需要使用truncate table table_name 进行删除) --查询 ...
- Oracle 获取表注释和列注释
全部表 select table_name from user_tables; //当前用户拥有的表 select table_name from all_tables; //所有用户的表 selec ...
- Maven依赖,去哪儿找
1. 前言 maven是作为Javer日常开发中必不可少的工具,但是很多人对于它的使用也只是仅限于的几个功能. 前几天在使用一个依赖总是说找不到该依赖,但是在中央仓库中的确存在该构建.这个问题让我很困 ...
- 基于Oracle数据库登陆界面及功能实现 Java版
首先要在Oracle数据库创建表文件,包括建立表头以及关键字(唯一标识符),此次程序所用的表名称为SW_USER_INFO,表头有UNAME.UKEY.USEX等,关键字为UCC,然后添加一条记录,用 ...
- 组合&反射&面向对象内置函数
内容概要 组合 反射 面向对象的内置函数 异常 内容详细 一.组合 组合:在对象中定义一个属性,属性的值是另一个对象 除了继承父类的方法,这是获取另一个类中属性的另一种方式 如果想给学生对象添加课程属 ...
- suse 12 编译部署Keepalived + nginx 为 kube-apiserver 提供高可用
文章目录 编译部署nginx 下载nginx源码包 编译nginx 配置nginx.conf 配置nginx为systemctl管理 分发nginx二进制文件和配置文件 启动kube-nginx服务 ...
- suse 12 二进制部署 Kubernetets 1.19.7 - 第06章 - 部署kube-apiserver组件
文章目录 1.6.部署kube-apiserver 1.6.0.创建kubernetes证书和私钥 1.6.1.生成kubernetes证书和私钥 1.6.2.创建metrics-server证书和私 ...
- Django创建第一个应用App(3)
创建一个投票的应用app.现在已经创建好了一个项目,就是有了一个框架,有了框架之后就可以往框架里面填写一些自己的需求,就是放一些功能在里面即可.一个项目可以包含多个应用app,一个应用app可以属于多 ...