首页 > 其他分享 >Traffic Control (TC) 简介和使用指南

Traffic Control (TC) 简介和使用指南

时间:2023-08-11 21:55:54浏览次数:44  
标签:Control loss ip self tc filter Traffic 使用指南 class

Traffic Control (TC) 简介和使用指南

Traffic Control(TC)是一个在 Linux 操作系统中用于控制网络流量的工具,允许对网络流量进行限速、排队、分流以及其他管理操作。TC 用于实现 QoS(Quality of Service)和流量整形,能够更好地控制网络资源和提供更好的用户体验。我们主要用于网损控制,测试设备在低质量网络下的使用情况。

基本概念

Queue Discipline (qdisc)

  • Qdisc 是用于控制数据包排队和处理的一种机制。不同类型的 qdisc 可以用来实现不同的流量控制策略。
  • 常见的 qdisc 类型包括 pfifo、bfifo、htb、fq_codel 等。

Class

  • 在 htb(Hierarchical Token Bucket)qdisc 中,class 用于对数据包进行分类和分流。
  • 每个 class 可以设置带宽、优先级等参数。

Filter

  • Filter 用于匹配和分类数据包,根据匹配的条件将数据包分配给相应的 class 进行处理。
  • 通过 filter,可以实现根据源 IP、目标 IP、协议、端口等条件进行流量分流。

基本命令

qdisc

  • tc qdisc add dev <网卡名称> root <qdisc类型> <qdisc参数>:添加根节点的 qdisc。
  • tc qdisc show dev <网卡名称>:查看网卡下的所有 qdiscs
  • tc qdisc del dev <网卡名称> parent <父类别标识符>:删除 qdisc
  • tc qdisc del dev <网卡名称> root:清理所有的设置

class

  • tc class add dev <网卡名称> parent <父类别标识符> classid <类别标识符> <class参数>:添加 class。
  • tc qdisc add dev <网卡名称> parent <父类别标识符> <qdisc类型> <qdisc参数>:在已有的 class 下添加子 qdisc。
  • tc class show dev <网卡名称>:查看网卡下的所有 class
  • tc class del dev <网卡名称> classid <类别标识符>:删除 class

filter

  • tc filter add dev <网卡名称> protocol <协议> prio <优先级> <filter表达式> <action>:添加 filter 规则。
  • tc filter del dev <网卡名称> protocol <协议> prio <优先级>:删除 filter 规则。
  • tc filter show dev <网卡名称>:查看网卡下的所有 filter
  • tc filter del dev <网卡名称> protocol ip prio <优先级> u32 match ip src <ip>/32:删除 filter

示例用法

  1. 限制网卡 eth0 上的带宽为 1Mbps:

    tc qdisc add dev eth0 root handle 1: htb default 10
    tc class add dev eth0 parent 1: classid 1:1 htb rate 1mbit
    
  2. 添加 filter 规则,根据源 IP 分流到不同的 class:

    tc filter add dev eth0 protocol ip prio 1 u32 match ip src 192.168.1.1/32 flowid 1:1
    tc filter add dev eth0 protocol ip prio 2 u32 match ip src 192.168.1.2/32 flowid 1:2
    

注意事项

  1. 需要有一个根 qdisc
  2. 一个 qdisc 可以挂载多个 class
  3. 一个 class 可以挂载多个子 class
  4. 一个 class 只能挂载一个 qdisc
  5. 一个 class 可以挂载多个 filter

参考

使用 python 设置 TC

import subprocess
from enum import Enum, unique


GLOBAL_LOSS_IP = '0.0.0.0/0'
MAX_PRIO = 65535

# class
LIMIT_CLASS_ADD_CMD = 'tc class add dev {} parent 1:1 classid 1:{} htb rate {}Kbit'
LIMIT_CLASS_CHANGE_CMD = 'tc class change dev {} parent 1:1 classid 1:{} htb rate {}Kbit'
NO_LIMIT_CLASS_CMD = 'tc class add dev {} parent 1:1 classid 1:{} htb rate 10Gbit ceil 20Gbit'
# filter
UP_FILTER_CMD = 'tc filter add dev {} protocol ip parent 1: prio {} u32 match ip dst {} flowid 1:{}'
DOWN_FILTER_CMD = 'tc filter add dev {} protocol ip parent 1: prio {} u32 match ip src {} flowid 1:{}'
# 删除某一 ip 的网损,先删除 filter 和 qdisc,最后删除 class
DEL_QDISC_CMD = 'tc qdisc del dev {} parent 1:{}'
DEL_CLASS_CMD = 'tc class del dev {} classid 1:{}'
DEL_FILTER_CMD = 'tc filter del dev {} protocol ip prio {} u32 match ip src {}/32'

"""
tc 设置 ip 过滤,命令结构:
         root qdisc
             |
  class 1         class 2
     |               |
qdisc filter    qdisc filter
"""


@unique
class TYPE(Enum):
    DELAY = 0
    LOSS = 1
    DUP = 2
    DISORDER = 3
    IMPAIR = 4
    SPEED_LIMIT = 5
    SPEED_LOSS = 6
    PACKAGE_LOSS = 7
    PACKAGE_SPEED = 8
    LOSS_DELAY = 9
    BRUST_LOSS = 10


class Netem:
    """qdisc"""
    LOSS_TYPE_CMD_MAP = {
        TYPE.DELAY.value: 'delay {}ms',
        TYPE.LOSS.value: 'loss {}%',
        TYPE.DUP.value: 'duplicate {}%',
        TYPE.LOSS_DELAY.value: 'delay {}ms loss {}%',
        TYPE.DISORDER.value: 'reorder {}% delay {}ms',
        TYPE.IMPAIR.value: 'corrupt {}%',
        TYPE.BRUST_LOSS.value: 'loss gemodel {}% {}% 100% 0%',
    }

    def __init__(self, interface: str, parent: int, handle: int):
        self.interface = interface
        self.parent = parent
        self.handle = handle
        self.base_cmd = 'tc qdisc {} dev {} parent 1:{} handle {}: netem '
        self.loss_config_map = {}

    def generate_cmd(self, action: str, network_loss_type: int, values: tuple):
        base_cmd = self.base_cmd.format(action, self.interface, self.parent, self.handle)
        loss_cmd = self.LOSS_TYPE_CMD_MAP.get(network_loss_type)
        self.loss_config_map[network_loss_type] = loss_cmd.format(*values)
        return base_cmd + ' '.join(self.loss_config_map.values())

    def add_netem(self, network_loss_type: int, values: tuple):
        cmd = self.generate_cmd('add', network_loss_type, values)
        subprocess.run(cmd, shell=True)

    def change_netem(self, network_loss_type: int, values: tuple):
        cmd = self.generate_cmd('change', network_loss_type, values)
        subprocess.run(cmd, shell=True)

    def del_netem(self):
        cmd = DEL_QDISC_CMD.format(self.interface, self.parent)
        subprocess.run(cmd, shell=True)


class TrafficControlClass:
    """class"""
    def __init__(self, interface: str, handle: int):
        self.interface = interface
        self.handle = handle
        self.child = None

    def set_base_class(self):
        cmd = NO_LIMIT_CLASS_CMD.format(self.interface, self.handle)
        subprocess.run(cmd, shell=True)

    def set_limit_class(self, limit: int):
        cmd = LIMIT_CLASS_ADD_CMD.format(self.interface, self.handle, limit)
        subprocess.run(cmd, shell=True)

    def change_limit_class(self, limit: int):
        cmd = LIMIT_CLASS_CHANGE_CMD.format(self.interface, self.handle, limit)
        subprocess.run(cmd, shell=True)

    def del_class(self):
        cmd = DEL_CLASS_CMD.format(self.interface, self.handle)
        subprocess.run(cmd, shell=True)


class TrafficControlFilter:
    """filter"""
    def __init__(self, interface: str, prio: int, ip: str, flow_class: TrafficControlClass):
        self.interface = interface
        self.prio = prio
        self.ip = ip
        self.flow_class = flow_class

    def set_filter(self):
        # 添加 filter 限制 ip
        # 对源地址和目标地址都添加限制
        cmd = UP_FILTER_CMD.format(self.interface, self.prio, self.ip, self.flow_class.handle)
        subprocess.run(cmd, shell=True)
        cmd = DOWN_FILTER_CMD.format(self.interface, self.prio, self.ip, self.flow_class.handle)
        subprocess.run(cmd, shell=True)

    def del_filter(self):
        if self.ip == GLOBAL_LOSS_IP:
            cmd = DEL_FILTER_CMD.format(self.interface, self.prio, '0.0.0.0')
        else:
            cmd = DEL_FILTER_CMD.format(self.interface, self.prio, self.ip)
        subprocess.run(cmd, shell=True)

    def change_prio(self, prio: int):
        print('change prio: ', prio)
        self.del_filter()
        self.prio = prio
        self.set_filter()


class TrafficControl:
    """Traffic Control"""
    def __init__(self, interface: str = 'eth0', cycle_time: int = 1000):
        self.interface = interface  # 网卡
        self.cycle_time = cycle_time
        self.prio = 1  # qdisc 的 handle
        self.class_handle = 1  # class 的 handle
        self.qdisc_handle = 1  # filter 优先级
        self.ip_filter_map = {}
        self.init_tc()

    def init_tc(self):
        """
        初始化 TC 状态,判断根 qdisc 是否存在,不存在则创建根 qdisc
        """
        print('init_tc')
        # 清理网损
        cmd = 'tc qdisc del dev {} root'.format(self.interface)
        subprocess.run(cmd, shell=True)
        # 初始化参数
        self.qdisc_handle = 1
        self.class_handle = 1
        self.prio = 1
        self.ip_filter_map = {}

        # 创建根 qdisc,r2q 表示没有 default 的 root 使整个网络的带宽没有限制
        cmd = 'tc qdisc add dev {} root handle 1: htb r2q 1'.format(self.interface)
        subprocess.run(cmd, shell=True)
        self.qdisc_handle += 1
        # 创建一个限速非常大 class 用于后面绑定丢包等 netem 类型的 qdisc 和限速
        cmd = 'tc class add dev {} parent 1: classid 1:1 htb rate 10Gbit ceil 20Gbit'.format(self.interface)
        subprocess.run(cmd, shell=True)
        self.class_handle += 1

    def remove_ip_loss(self, ip: str):
        """移除 ip 的网损设置"""
        print('remove_loss: %s' % ip)
        tc_filter = self.ip_filter_map.pop(ip, None)  # type: TrafficControlFilter
        if tc_filter:
            tc_filter.del_filter()
            if tc_filter.flow_class.child:  # type: Netem
                tc_filter.flow_class.child.del_netem()
            tc_filter.flow_class.del_class()

    def process(self, network_loss_type: int, ip: str, data: dict, value=None):
        """
        网损设置
        :param network_loss_type: 网损类型
        :param ip: 设置的 ip
        :param data: 请求数据
        :param value: 周期数据,如果传入 value 代表为周期设置
        :return:
        """
        if not ip:
            ip = GLOBAL_LOSS_IP

        values = None
        if network_loss_type == TYPE.DELAY.value:
            if value is None:
                delay = data.get('delay', 0)
            else:
                delay = value
            values = (delay,)
        elif network_loss_type == TYPE.LOSS.value:
            if value is None:
                loss = data.get('loss', 0)
            else:
                loss = value
            values = (loss,)
        elif network_loss_type == TYPE.DUP.value:
            if value is None:
                dup = data.get('dup', 0)
            else:
                dup = value
            values = (dup,)
        elif network_loss_type == TYPE.DISORDER.value:
            disorder = data['disorder']['value']
            delay = data['disorder']['delay']
            values = (disorder, delay)
        elif network_loss_type == TYPE.IMPAIR.value:
            if value is None:
                impair = data.get('impair')
            else:
                impair = value
            values = (impair,)
        elif network_loss_type == TYPE.SPEED_LIMIT.value:
            if value is None:
                speed = data['speed_limit'].get('speed', 10000000)  # 10_000_000
            else:
                speed = value
            delay = data['speed_limit'].get('delay', 50)
            self.set_speed_limit(ip, speed, delay)
        elif network_loss_type == TYPE.LOSS_DELAY.value:
            delay = data['loss_delay']['delay']
            loss = data['loss_delay']['loss']
            values = (delay, loss)
        elif network_loss_type == TYPE.BRUST_LOSS.value:
            loss = data['brust']['loss']
            package = int(data['brust']['package'])
            p = float(loss) / 100  # p is the probability of transferring from Good State to the bad state
            loss_p = p * package
            r = p / loss_p - p  # r is the probability of transferring from the bad state to the Good
            values = (p * 100, r * 100)
        self.set_base(ip, network_loss_type, values)

    def set_speed_limit(self, ip: str, limit: int or float, latency: int = None):
        """
        设置限速
        :param ip: 网损设置 ip
        :param limit: 限速,单位 Kbit
        :param latency: 延迟,单位 ms
        :return:
        """
        if ip not in self.ip_filter_map:
            # 增加 class 设置限速
            tc_class = TrafficControlClass(self.interface, self.class_handle)
            tc_class.set_limit_class(limit)
            self.class_handle += 1
            # 创建 filter 类
            prio = MAX_PRIO if ip == GLOBAL_LOSS_IP else self.prio  # 保证全局网损的优先级最小
            tc_filter = TrafficControlFilter(self.interface, prio, ip, tc_class)
            tc_filter.set_filter()
            self.ip_filter_map[ip] = tc_filter
            self.prio += 1
        else:
            tc_filter = self.ip_filter_map[ip]
            tc_class = tc_filter.flow_class  # type: TrafficControlClass
            tc_class.change_limit_class(limit)
        if latency:
            self.set_base(ip, TYPE.DELAY.value, (latency,))

    def set_base(self, ip: str, network_loss_type: int, values: tuple = None):
        """
        设置 netem 类型下的基础网损,包括延迟、丢包、重复、乱序、损坏、延迟丢包、burst丢包等
        :param ip: 网损设置 ip
        :param network_loss_type: 网损类型
        :param values: 网损值
        :return:
        """
        if values is not None:
            if ip not in self.ip_filter_map:
                # 如果没有当前 ip 的网损设置,增加 class 和 filter
                # 增加 class 用来挂载 qdisc 设置 netem
                tc_class = TrafficControlClass(self.interface, self.class_handle)
                tc_class.set_base_class()
                self.class_handle += 1
                # 创建 filter 类
                prio = MAX_PRIO if ip == GLOBAL_LOSS_IP else self.prio  # 保证全局网损的优先级最小
                tc_filter = TrafficControlFilter(self.interface, prio, ip, tc_class)
                tc_filter.set_filter()
                self.ip_filter_map[ip] = tc_filter
                self.prio += 1
                netem = Netem(self.interface, tc_class.handle, self.qdisc_handle)
                netem.add_netem(network_loss_type, values)
                tc_class.child = netem
                self.qdisc_handle += 1
            else:
                # 如果存在当前 ip 的网损设置,取出对应的 class、filter 和 qdisc
                tc_filter = self.ip_filter_map[ip]
                tc_class = tc_filter.flow_class  # type: TrafficControlClass
                if tc_class.child:
                    netem = tc_class.child  # type: Netem
                    netem.change_netem(network_loss_type, values)
                else:
                    netem = Netem(self.interface, tc_class.handle, self.qdisc_handle)
                    netem.add_netem(network_loss_type, values)
                    tc_class.child = netem
                    self.qdisc_handle += 1

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.init_tc()


if __name__ == '__main__':
    tc = TrafficControl('eth0')
    # 对全局设置 20ms 延迟
    tc.process(TYPE.DELAY.value, '0.0.0.0/0', {'delay': 20})

标签:Control,loss,ip,self,tc,filter,Traffic,使用指南,class
From: https://www.cnblogs.com/shouwangrenjian/p/17624016.html

相关文章

  • Stable Diffusion基础:ControlNet之人体姿势控制
    在AI绘画中精确控制图片是一件比较困难的事情,不过随着ControlNet的诞生,这一问题得到了很大的缓解。今天我就给大家分享一个使用StableDiffusionWebUI+OpenPoseControlNet复制照片人物姿势的方法,效果可以参考上图。OpenPose可以控制人体的姿态、面部的表情,有时候还能......
  • 黑魂224 武器上级空物件自动挂载WeaponController
    首先在WeaponController脚本里写上WeaponManager的变量。 新创建两个绑定武器的变量wcL和wcR,通过深度搜索函数搜索模型里武器上级空物件的whL和whR。 下方这个代码是用来给武器上级空物件创建一个新的WeaponController,用以调整武器参数。......
  • Angular FormControl value属性的一些事
    背景:一个输入校验,允许输入多行,每一行是ip或网段。写了个校验,将其按行拆分后单独校验。1. FormControl无法深复制使用JSON.parse(JSON.stringify(control))进行简单深复制报错,因为不是json类型;使用deepClone进行递归深复制,直接栈溢出。考虑到代码的健壮性,已经有单独校验......
  • 【javascript】关于 AbortController
    相关概念:https://developer.mozilla.org/zh-CN/docs/Web/API/AbortController需求描述:后台返回10000条图片url,前端拿到后需要做成假分页,假设1页显示20张图,分成50页。部分逻辑:1for(leti=0;i<imgUrlList.length;i++){2letimage=newImage()3image.src=imgUrlLi......
  • [Python爬虫]selenium4新版本使用指南
    From:码同学测试公众号------------------------------------Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7,8,9,10,11),MozillaFirefox,Safari,GoogleChrome,Opera,Edge等。这个工具的主要功能包括......
  • Java Web Service Get请求使用指南
    JavaWebServiceGet请求使用指南在当今互联网时代,WebService已经成为了现代软件开发中不可或缺的一部分。而Java作为一种广泛使用的编程语言,自然也提供了丰富的工具和库来支持WebService的开发。本文将为大家介绍如何使用Java编程语言进行WebService的Get请求。JavaWebserv......
  • .Net Web API 005 Controller上传小文件
    1、附属文件对象定义一般情况下,系统里面的文件都会附属一个对象存在,例如用户的头像文件,会附属用户对象存在。邮件中的文件会附属邮件存在。所以在系统里面,我们会创建一个附属文件对象,命名为AttachedFileEntity。其定义如下所示。///<summary>///附属文件实体对象///</summ......
  • NLTK 使用指南
    NLTK安装pip3installnltkNLTK数据下载代码中下载importnltknltk.download()手工下载https://www.nltk.org/nltk_data/根据需要,进行下载。NLTK使用......
  • [应求汉化]LubbosFanControl100 风扇控速软件 汉化版
    相信MBP的同志们用win系统都经历过高温暖手的情况~今天也是偶然找到一个蛮好用的软件发给大家用~~~不过貌似也会降低电脑性能,请大家斟酌呦,平常写文档、看视频啥的反正没什么变慢的反映=。=(我智商低,除了下下五子棋、国际象棋别的都不会)水了那么久,也来点实在的!~~ http://www.iqho......
  • @ControllerAdvice注解使用及原理探究 | 京东物流技术团队
    最近在新项目的开发过程中,遇到了个问题,需要将一些异常的业务流程返回给前端,需要提供给前端不同的响应码,前端再在次基础上做提示语言的国际化适配。这些异常流程涉及业务层和控制层的各个地方,如果每个地方都写一些重复代码显得很冗余。然后查询解决方案时发现了@ControllerAdvice这......