前段时间在做一些测试的时候接触到了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也不是什么难事了。

参考资料

  1. lartc
  2. tc(8)
  3. tc-u32(8)
  4. 数据包的分类和调度
  5. netem
  6. ifb

Linux TC 流量控制介绍的更多相关文章

  1. linux tc流量控制

    tc流量控制 项目背景 vintage3.0接口lookupforupdage增加一个策略,当带宽流量tx或rx超过40%,75%随机返回304:超过60%,此接口均返回304 为了对测试机器进行流量 ...

  2. Linux TC流量控制HOWTO中文版

    <本文摘自Linux的高级路由和流量控制HOWTO中文版 第9章节>网人郭工进行再次编译: 利用队列,我们可以控制数据发送的方式.记住我们只能对发送数据进行控制(或称为整形).其实,我们无 ...

  3. Linux内核策略介绍

      Linux内核策略介绍学习笔记   主要内容 硬件 策略 CPU 进程调度.系统调用.中断 内存 内存管理 外存 文件IO 网络 协议栈 其他 时间管理 进程调度 内核的运行时间 系统启动.中断发 ...

  4. Linux实战教学笔记07:Linux系统目录结构介绍

    第七节 Linux系统目录结构介绍 标签(空格分隔):Linux实战教学笔记 第1章 前言 windows目录结构 C:\windows D:\Program Files E:\你懂的\精品 F:\你 ...

  5. Linux的简单介绍和常用命令的介绍

    Linux的简单介绍和常用命令的介绍 本说明以Ubuntu系统为例 Ubuntu系统的安装自行百度,或者参考http://www.cnblogs.com/CoderJYF/p/6091068.html ...

  6. Linux性能工具介绍

    l  Linux性能工具介绍 p  CPU高 p  磁盘I/O p  网络 p  内存 p  应用程序跟踪 l  操作系统与应用程序的关系比喻为“唇亡齿寒”一点不为过 l  应用程序的性能问题/功能问 ...

  7. Linux core 文件介绍

    Linux core 文件介绍 http://www.cnblogs.com/dongzhiquan/archive/2012/01/20/2328355.html 1. core文件的简单介绍在一个 ...

  8. Linux 启动参数介绍

    Linux 启动参数介绍 取自2.6.18 kernel Documentation/i386/boot.txt 文件中介绍 vga= 这里的不是一个整数(在C语言表示法中,应是十进制,八进制或者十六 ...

  9. Linux系统启动过程介绍

    Linux系统启动过程介绍 学习操作系统有必要了解一下系统的启动过程,这样在面对各种系统故障的时候能快速定位解决问题,下面以Centos来分析linux系统的启动过程. 1.BIOS自检:当开机的时候 ...

随机推荐

  1. 警惕!Python 中少为人知的 10 个安全陷阱!

    作者:Dennis Brinkrolf 译者:豌豆花下猫@Python猫 原题:10 Unknown Security Pitfalls for Python 英文:https://blog.sona ...

  2. 04.python语法入门--基本数据类型

    # python是一门解释型的.强类型的.动态语言​​# 一:数字类型# 1.1 整型int:记录人的年龄.等级.号码.个数# age = 18# print(type(age))​# 1.2 浮点数 ...

  3. 在线pdf请你谨慎打开

    本篇其实算之前安全整改话题的一点补充,对之前内容感兴趣的可以走以下快捷通道: 安全漏洞整改系列(二) 安全漏洞整改系列(一) 背景 前不久某家客户对我们提供的系统又进行了一轮安全测试,其中有一条我觉得 ...

  4. 『无为则无心』Python面向对象 — 52、私有成员方法(类中行为的封装)

    Python对于类的成员没有严格的访问控制限制,这与其他面向对象的编程语言是有所区别的. 关于私有方法其实和私有属性差不多,有如下要点: 1.通常我们约定,两个下划线开头的方法是私有方法. 2.类内部 ...

  5. Ubuntu20重装nvidia驱动

    终端:nvidia-smi 查看驱动信息 错误:NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver ...

  6. 在k8s中使用性能分析神器:arthas

    Arthas(阿尔萨斯)是阿里巴巴开源的性能分析神器. k8s中使用arthas的三种方式 [bak]https://www.cnblogs.com/uncleyong/p/15498842.html ...

  7. BI能给企业带来什么收益,企业应该如何选择好的BI工具?

    ​任何从事数据分析的小伙伴,或多或少都会听说BI这个数据分析工具.希望进入数据分析工作的小伙伴也一定绕不开BI这个话题,那么BI到底是什么呢?为什么BI如此重要?BI能给企业带来什么?如何选择BI工具 ...

  8. Sencha Cmd 常用命令

    1.获取帮助 sencha help generate app 2.创建应用程序 sencha -sdk e:\ext\ext6 generate app -classic SimpleCMS e:\ ...

  9. 程序与CPU,内核,寄存器,缓存,RAM,ROM、总线、Cache line缓存行的作用和他们之间的联系?

    目录 缓存 什么是缓存 L1.L2.L3 为什么要设置那么多缓存.缓存在cup内还是cup外 MESI协议----主流的处理缓存和主存数据不一样问题 Cache line是什么已经 对编程中数组的影响 ...

  10. 《Java编程思想》学习笔记(详细)

    目录 01 对象导论 02 一切都是对象 03 操作符 04 控制执行流程 05 初始化与清理 06 访问权限控制 07 复用类(继承) 08 多态 09 接口 10 内部类 11 持有对象 12 通 ...