tcconfig/_common.py
def is_execute_tc_command(tc_command_output):
    return tc_command_output == TcCommandOutput.NOT_SET
tcconfig/_error.py
class ModuleNotFoundError(Exception):
    """
    Exception raised when mandatory kernel module not found.
    """
tcconfig/_netem_param.py
MAX_REORDERING_RATE = 100  # [%]
tcconfig/_const.py
IPV6_OPTION_ERROR_MSG_FORMAT = "{}. --ipv6 option required to use IPv6 address."
tcconfig/_argparse_wrapper.py
class ArgparseWrapper:
    """
    Wrapper class for argparse
    """

    def __init__(self, version, description=""):
        self.parser = argparse.ArgumentParser(
            formatter_class=argparse.RawDescriptionHelpFormatter,
            description=description,
            epilog=dedent(
                """\
                Documentation: https://tcconfig.rtfd.io/
                Issue tracker: https://github.com/thombashi/tcconfig/issues
                """
            ),
        )
        self.parser.add_argument(
            "-V", "--version", action="version", version="%(prog)s {}".format(version)
        )
        self._add_tc_command_arg_group()
        self._add_log_level_argument_group()

        group = self.parser.add_argument_group("Debug")
        group.add_argument(
            "--debug-query", action="store_true", default=False, help="for debug print."
        )
        group.add_argument(
            "--stacktrace",
            dest="is_output_stacktrace",
            action="store_true",
            default=False,
            help="""print stack trace for debug information.
            --debug option required to see the debug print.
            """,
        )

    def add_routing_group(self):
        group = self.parser.add_argument_group("Routing")
        group.add_argument(
            "--direction",
            choices=TrafficDirection.LIST,
            default=TrafficDirection.OUTGOING,
            help="""the direction of network communication that imposes traffic control.
            'incoming' requires ifb kernel module and Linux kernel 2.6.20 or later.
            (default = %(default)s)
            """,
        )
        group.add_argument(
            "--network",
            "--dst-network",
            dest="dst_network",
            help="""specify destination IP-address/network that applies traffic control.
            defaults to any.""",
        )
        group.add_argument(
            "--src-network",
            help="""specify source IP-address/network that applies traffic control.
            defaults to any.
            this option has no effect when executing with "--direction incoming" option.
            note: this option required to execute with the --iptables option when using tbf
            algorithm.
            """,
        )
        group.add_argument(
            "--port",
            "--dst-port",
            dest="dst_port",
            type=int,
            help="specify destination port number that applies traffic control. defaults to any.",
        )
        group.add_argument(
            "--src-port",
            type=int,
            help="specify source port number that applies traffic control. defaults to any.",
        )
        group.add_argument(
            "--ipv6",
            dest="is_ipv6",
            action="store_true",
            default=False,
            help="apply traffic control to IPv6 packets rather than IPv4.",
        )

        return group

    def _add_log_level_argument_group(self):
        dest = "log_level"

        group = self.parser.add_mutually_exclusive_group()
        group.add_argument(
            "--debug",
            dest=dest,
            action="store_const",
            const="DEBUG",
            default="INFO",
            help="for debug print.",
        )
        group.add_argument(
            "--quiet",
            dest=dest,
            action="store_const",
            const="QUIET",
            default="INFO",
            help="suppress execution log messages.",
        )

        return group

    def add_docker_group(self, is_add_srcdst=True):
        group = self.parser.add_argument_group("Docker")
        group.add_argument(
            "--docker",
            dest="use_docker",
            action="store_true",
            default=False,
            help="""
            apply traffic control to a docker container.
            to use this option, you will need to specify a container id as 'device' as follows:

                tcset --container <container id>
            """,
        )
        if is_add_srcdst:
            group.add_argument("--src-container", help="specify source container id or name.")
            group.add_argument("--dst-container", help="specify destination container id or name.")

        return group

    def _add_tc_command_arg_group(self):
        group = self.parser.add_mutually_exclusive_group()
        group.add_argument(
            "--tc-command",
            dest="tc_command_output",
            action="store_const",
            const=TcCommandOutput.STDOUT,
            default=TcCommandOutput.NOT_SET,
            help="""
            display tc commands to be executed and exit.
            these commands are not actually executed.
            """,
        )

        group.add_argument(
            "--tc-script",
            dest="tc_command_output",
            action="store_const",
            const=TcCommandOutput.SCRIPT,
            default=TcCommandOutput.NOT_SET,
            help="""
            generate a shell script file that described tc commands.
            this tc script execution result nearly equivalent with the tcconfig command.
            the script can be executed without tcconfig package installation.
            """,
        )
tcconfig/_const.py
class Tc:
    class Command:
        TCSET = "tcset"
        TCDEL = "tcdel"
        TCSHOW = "tcshow"

    class Param:
        DEVICE = "device"
        DIRECTION = "direction"
        FILTER_ID = "filter_id"
        CLASS_ID = "classid"
        DST_NETWORK = "dst-network"
        DST_PORT = "dst-port"
        FLOW_ID = "flowid"
        HANDLE = "handle"
        PARENT = "parent"
        PRIORITY = "priority"
        PROTOCOL = "protocol"
        SRC_NETWORK = "src-network"
        SRC_PORT = "src-port"

    class ValueRange:
        class LatencyTime:
            MIN = "0ms"
            MAX = "60min"

    class Min:
        LATENCY_TIME = "0ms"

    class Max:
        LATENCY_TIME = "60min"
tcconfig/_netem_param.py
MIN_PACKET_DUPLICATE_RATE = 0  # [%]
tcconfig/_netem_param.py
MAX_PACKET_DUPLICATE_RATE = 100  # [%]
tcconfig/traffic_control.py
class TrafficControl:
    __MIN_PORT = 0
    __MAX_PORT = 65535

    REGEXP_FILE_EXISTS = re.compile("RTNETLINK answers: File exists")

    EXISTS_MSG_TEMPLATE = "\n".join(
        [
            "{:s} try to execute with: ",
            "  (a) --overwrite option if you want to overwrite the existing rule.",
            "  (b) --add option if you want to add a new rule in addition to the existing rules.",
            "  (c) --change option if you want to change the existing rule parameter.",
        ]
    )

    @property
    def device(self):
        return self.__device

    @property
    def tc_target(self):
        return self.__device

    @property
    def ifb_device(self):
        return "ifb{:d}".format(self.__qdisc_major_id)

    @property
    def direction(self):
        return self.__direction

    @property
    def netem_param(self):
        return self.__netem_param

    @property
    def dst_network(self):
        return self.__dst_network

    @property
    def exclude_dst_network(self):
        return self.__exclude_dst_network

    @property
    def src_network(self):
        return self.__src_network

    @property
    def exclude_src_network(self):
        return self.__exclude_src_network

    @property
    def src_port(self):
        return self.__src_port

    @property
    def exclude_src_port(self):
        return self.__exclude_src_port

    @property
    def dst_port(self):
        return self.__dst_port

    @property
    def exclude_dst_port(self):
        return self.__exclude_dst_port

    @property
    def is_change_shaping_rule(self):
        return self.__is_change_shaping_rule

    @property
    def is_add_shaping_rule(self):
        return self.__is_add_shaping_rule

    @property
    def is_enable_iptables(self):
        return self.__is_enable_iptables

    @property
    def qdisc_major_id(self):
        return self.__qdisc_major_id

    @property
    def qdisc_major_id_str(self):
        return "{:x}".format(self.__qdisc_major_id)

    @property
    def ip_version(self):
        return 6 if self.__is_ipv6 else 4

    @property
    def protocol(self):
        return "ipv6" if self.__is_ipv6 else "ip"

    @property
    def protocol_match(self):
        return "ip6" if self.__is_ipv6 else "ip"

    @property
    def tc_command_output(self):
        return self.__tc_command_output

    @property
    def iptables_ctrl(self):
        return self.__iptables_ctrl

    def __init__(
        self,
        device,
        direction=None,
        netem_param=None,
        dst_network=None,
        exclude_dst_network=None,
        src_network=None,
        exclude_src_network=None,
        dst_port=None,
        exclude_dst_port=None,
        src_port=None,
        exclude_src_port=None,
        is_ipv6=False,
        is_change_shaping_rule=False,
        is_add_shaping_rule=False,
        is_enable_iptables=False,
        shaping_algorithm=None,
        tc_command_output=TcCommandOutput.NOT_SET,
    ):
        self.__device = device

        self.__direction = direction
        self.__netem_param = netem_param
        self.__dst_network = dst_network
        self.__exclude_dst_network = exclude_dst_network
        self.__src_network = src_network
        self.__exclude_src_network = exclude_src_network
        self.__src_port = src_port
        self.__exclude_src_port = exclude_src_port
        self.__dst_port = dst_port
        self.__exclude_dst_port = exclude_dst_port
        self.__is_ipv6 = is_ipv6
        self.__is_change_shaping_rule = is_change_shaping_rule
        self.__is_add_shaping_rule = is_add_shaping_rule
        self.__is_enable_iptables = is_enable_iptables
        self.__tc_command_output = tc_command_output

        self.__qdisc_major_id = self.__get_device_qdisc_major_id()

        self.__iptables_ctrl = IptablesMangleController(is_enable_iptables, self.ip_version)

        self.__init_shaper(shaping_algorithm)

    def validate(self):
        verify_network_interface(self.device, self.__tc_command_output)
        self.__netem_param.validate_netem_parameter()
        self.__validate_src_network()
        self.__validate_port()

    def __validate_src_network(self):
        if any(
            [
                typepy.is_null_string(self.src_network),
                self.__shaper.algorithm_name == ShapingAlgorithm.HTB,
            ]
        ):
            return

        if not self.is_enable_iptables:
            raise ParameterError(
                "--iptables option required to use --src-network option",
                value=self.is_enable_iptables,
            )

    def sanitize(self):
        self.__dst_network = sanitize_network(self.dst_network, self.ip_version)
        self.__src_network = sanitize_network(self.src_network, self.ip_version)

    def get_tc_device(self):
        """
        Return a device name that associated network communication direction.
        """

        if self.direction == TrafficDirection.OUTGOING:
            return self.device

        if self.direction == TrafficDirection.INCOMING:
            return self.ifb_device

        raise ParameterError(
            "unknown direction", expected=TrafficDirection.LIST, value=self.direction
        )

    def get_tc_command(self, subcommand):
        return "{:s} {:s}".format(
            get_tc_base_command(subcommand), "change" if self.is_change_shaping_rule else "add"
        )

    def get_command_history(self):
        def tc_command_filter(command):
            if get_iptables_base_command():
                if re.search(
                    "^{:s} {:s}".format(
                        get_iptables_base_command(), re.escape(LIST_MANGLE_TABLE_OPTION)
                    ),
                    command,
                ):
                    return False

            if re.search("^{:s} .* show dev".format(find_bin_path("tc")), command):
                return False

            if re.search("^ip (netns exec |link show )", command):
                return False

            if find_bin_path("getcap"):
                if re.search("^{:s}".format(find_bin_path("getcap")), command):
                    return False

            return True

        return filter(tc_command_filter, spr.SubprocessRunner.get_history())

    def make_srcdst_text(self):
        return "".join(
            [
                str(item)
                for item in [
                    self.dst_network,
                    self.exclude_dst_network,
                    self.src_network,
                    self.exclude_src_network,
                    self.src_port,
                    self.exclude_src_port,
                    self.dst_port,
                    self.exclude_dst_port,
                ]
            ]
        )

    def set_shaping_rule(self):
        rule_finder = TcShapingRuleFinder(logger=logger, tc=self)
        if self.__is_change_shaping_rule and self.tc_command_output == TcCommandOutput.NOT_SET:
            if rule_finder.find_filter_param() is not None:
                self.__is_change_shaping_rule = True
            else:
                self.__is_change_shaping_rule = False
                self.__is_add_shaping_rule = True

        self.__setup_ifb()

        return self.__shaper.set_shaping()

    def delete_all_rules(self):
        result_list = []

        result_list.append(self.__delete_qdisc())
        result_list.append(self.__delete_ingress_qdisc())

        try:
            result_list.append(self.__delete_ifb_device() == 0)
        except NetworkInterfaceNotFoundError as e:
            logger.debug(msgfy.to_debug_message(e))
            result_list.append(False)

        with logging_context("delete iptables mangle table entries"):
            try:
                self.iptables_ctrl.clear()
            except OSError as e:
                logger.warning("{} (can not delete iptables entries)".format(e))

        return any(result_list)

    def delete_tc(self):
        """
        Delete a specific shaping rule.
        """

        rule_finder = TcShapingRuleFinder(logger=logger, tc=self)
        filter_param = rule_finder.find_filter_param()

        if not filter_param:
            message = "shaping rule not found ({}).".format(rule_finder.get_filter_string())
            if rule_finder.is_empty_filter_condition():
                message += " you can delete all of the shaping rules with --all option."
            logger.error(message)

            return 1

        logger.info("delete a shaping rule: {}".format(dict(filter_param)))

        filter_del_command = (
            "{command:s} del dev {dev:s} protocol {protocol:s} "
            "parent {parent:} handle {handle:s} prio {prio:} u32".format(
                command=get_tc_base_command(TcSubCommand.FILTER),
                dev=rule_finder.get_parsed_device(),
                protocol=filter_param.get(Tc.Param.PROTOCOL),
                parent="{:s}:".format(rule_finder.find_parent().split(":")[0]),
                handle=filter_param.get(Tc.Param.FILTER_ID),
                prio=filter_param.get(Tc.Param.PRIORITY),
            )
        )

        result = run_command_helper(
            command=filter_del_command, ignore_error_msg_regexp=None, notice_msg=None
        )

        rule_finder.clear()
        if not rule_finder.is_any_filter():
            logger.debug("there are no filters remain. delete qdiscs.")
            self.delete_all_rules()

        return result

    def __init_shaper(self, shaping_algorithm):
        if shaping_algorithm is None:
            self.__shaper = None
            return

        if shaping_algorithm == ShapingAlgorithm.HTB:
            self.__shaper = HtbShaper(self)
            return

        if shaping_algorithm == ShapingAlgorithm.TBF:
            self.__shaper = TbfShaper(self)
            return

        raise ParameterError(
            "unknown shaping algorithm", expected=ShapingAlgorithm.LIST, value=shaping_algorithm
        )

    def __validate_port(self):
        validate_within_min_max(
            "src_port", self.src_port, self.__MIN_PORT, self.__MAX_PORT, unit=None
        )

        validate_within_min_max(
            "dst_port", self.dst_port, self.__MIN_PORT, self.__MAX_PORT, unit=None
        )

    def __get_device_qdisc_major_id(self):
        import hashlib

        base_device_hash = hashlib.md5(self.device.encode("latin-1")).hexdigest()[:3]
        device_hash_prefix = "1"

        return int(device_hash_prefix + base_device_hash, 16)

    def __setup_ifb(self):
        if self.direction != TrafficDirection.INCOMING:
            return 0

        if typepy.is_null_string(self.ifb_device):
            return -1

        return_code = 0
        modprobe_proc = spr.SubprocessRunner("modprobe ifb")

        try:
            if modprobe_proc.run() != 0:
                logger.error(modprobe_proc.stderr)
        except spr.CommandError as e:
            logger.debug(msgfy.to_debug_message(e))

        if self.is_add_shaping_rule or self.is_change_shaping_rule:
            notice_message = None
        else:
            notice_message = self.EXISTS_MSG_TEMPLATE.format(
                "failed to add ip link: ip link already exists."
            )

        return_code |= run_command_helper(
            "{:s} link add {:s} type ifb".format(find_bin_path("ip"), self.ifb_device),
            ignore_error_msg_regexp=self.REGEXP_FILE_EXISTS,
            notice_msg=notice_message,
        )

        return_code |= spr.SubprocessRunner(
            "{:s} link set dev {:s} up".format(find_bin_path("ip"), self.ifb_device)
        ).run()

        base_command = "{:s} add".format(get_tc_base_command(TcSubCommand.QDISC))
        if self.is_add_shaping_rule or self.is_change_shaping_rule:
            notice_message = None
        else:
            notice_message = self.EXISTS_MSG_TEMPLATE.format(
                "failed to '{:s}': ingress qdisc already exists.".format(base_command)
            )
        return_code |= run_command_helper(
            "{:s} dev {:s} ingress".format(base_command, self.device),
            ignore_error_msg_regexp=self.REGEXP_FILE_EXISTS,
            notice_msg=notice_message,
        )

        return_code |= spr.SubprocessRunner(
            " ".join(
                [
                    "{:s} add".format(get_tc_base_command(TcSubCommand.FILTER)),
                    "dev {:s}".format(self.device),
                    "parent ffff: protocol {:s} u32 match u32 0 0".format(self.protocol),
                    "flowid {:x}:".format(self.__qdisc_major_id),
                    "action mirred egress redirect",
                    "dev {:s}".format(self.ifb_device),
                ]
            )
        ).run()

        return return_code

    def __delete_qdisc(self):
        logging_msg = "delete {} qdisc".format(self.device)

        with logging_context(logging_msg):
            returncode = run_command_helper(
                "{:s} del dev {:s} root".format(
                    get_tc_base_command(TcSubCommand.QDISC), self.device
                ),
                ignore_error_msg_regexp=re.compile(
                    "|".join(
                        [
                            "RTNETLINK answers: No such file or directory",  # Debian/Ubuntu
                            "Error: Cannot delete qdisc with handle of zero",  # Debian 10
                        ]
                    )
                ),
                notice_msg="no qdisc to delete for the outgoing device.",
                msg_log_level="INFO",
            )

            is_success = returncode == 0
            if is_success:
                logger.info(logging_msg)

            return is_success

    def __delete_ingress_qdisc(self):
        logging_msg = "delete {} ingress qdisc".format(self.device)

        with logging_context(logging_msg):
            returncode = run_command_helper(
                "{:s} del dev {:s} ingress".format(
                    get_tc_base_command(TcSubCommand.QDISC), self.device
                ),
                ignore_error_msg_regexp=re.compile(
                    "|".join(
                        [
                            "RTNETLINK answers: Invalid argument",  # debian/ubuntu
                            "RTNETLINK answers: No such file or directory",  # debian/ubuntu
                            "Error: Cannot find specified qdisc on specified device",  # RHEL/fedora
                        ]
                    )
                ),
                notice_msg="no qdisc to delete for the incoming device.",
                msg_log_level="INFO",
            )
            is_success = returncode == 0
            if is_success:
                logger.info(logging_msg)

            return is_success

    def __delete_ifb_device(self):
        from ._capabilities import get_permission_error_message, has_execution_authority

        logging_msg = "delete {} ifb device ({})".format(self.device, self.ifb_device)
        with logging_context(logging_msg):
            verify_network_interface(self.ifb_device, self.__tc_command_output)

            if is_execute_tc_command(self.__tc_command_output) and not has_execution_authority(
                "ip"
            ):
                logger.warning(get_permission_error_message("ip"))

                return errno.EPERM

            commands = [
                "{:s} del dev {:s} root".format(
                    get_tc_base_command(TcSubCommand.QDISC), self.ifb_device
                ),
                "{:s} link set dev {:s} down".format(find_bin_path("ip"), self.ifb_device),
                "{:s} link delete {:s} type ifb".format(find_bin_path("ip"), self.ifb_device),
            ]

            if all([spr.SubprocessRunner(command).run() != 0 for command in commands]):
                return 2

        logger.info(logging_msg)

        return 0
tcconfig/_netem_param.py
MAX_PACKET_LOSS_RATE = 100  # [%]
tcconfig/__version__.py

tcconfig/_netem_param.py
MIN_PACKET_LOSS_RATE = 0  # [%]
tcconfig/_netem_param.py
MIN_REORDERING_RATE = 0  # [%]
tcconfig/_error.py
class ContainerNotFoundError(TargetNotFoundError):
    """
    Exception raised when container not found.
    """

    @property
    def _target_type(self):
        return "container"

    def __str__(self, *args, **kwargs):
        from ._docker import DockerClient

        dclient = DockerClient()
        container_names = dclient.extract_running_container_names()
        item_list = [super().__str__(*args, **kwargs)]

        if container_names:
            item_list.append(
                "(available running containers: {})".format(", ".join(container_names))
            )
        else:
            item_list.append("(running container not found)")

        return " ".join(item_list).strip()
tcconfig/_shaping_rule_finder.py
class TcShapingRuleFinder:
    @property
    def _parser(self):
        self.__shaping_rule_parser.parse()

        return self.__shaping_rule_parser

    def __init__(self, logger, tc):
        self.__logger = logger
        self.__tc = tc
        self.__shaping_rule_parser = TcShapingRuleParser(
            device=self.__tc.device,
            ip_version=self.__tc.ip_version,
            tc_command_output=self.__tc.tc_command_output,
            logger=self.__logger,
        )

    def clear(self):
        self.__shaping_rule_parser.clear()

    def find_qdisc_handle(self, parent):
        for qdisc in Qdisc.select(where=Where(Tc.Param.PARENT, parent)):
            return qdisc.handle

        return None

    def find_filter_param(self):
        where_query = And(self.__get_filter_conditions())
        table_name = Filter.get_table_name()
        self.__logger.debug("find filter param: table={}, where={}".format(table_name, where_query))

        for record in Filter.select(where=where_query):
            return record.as_dict()

        self.__logger.debug(
            "find filter param: empty result (table={}, where={})".format(table_name, where_query)
        )

        return None

    def find_parent(self):
        for record in Filter.select(where=And(self.__get_filter_conditions())):
            return record.flowid

        return None

    def is_exist_rule(self):
        return self.find_parent() is not None

    def is_any_filter(self):
        return Filter.fetch_num_records() > 0

    def is_empty_filter_condition(self):
        from typepy import is_null_string

        return all(
            [
                is_anywhere_network(self.__tc.dst_network, self.__tc.ip_version),
                is_anywhere_network(self.__tc.src_network, self.__tc.ip_version),
                is_null_string(self.__tc.dst_port),
                is_null_string(self.__tc.src_port),
            ]
        )

    def get_parsed_device(self):
        if self.__tc.direction == TrafficDirection.OUTGOING:
            device = self._parser.device
        elif self.__tc.direction == TrafficDirection.INCOMING:
            device = self._parser.ifb_device

        return device

    def get_filter_string(self):
        return ", ".join(
            [where.to_query() for where in self.__get_filter_conditions() if where.value]
        )

    def __get_filter_conditions(self):
        if self.__tc.direction == TrafficDirection.OUTGOING:
            device = self._parser.device
        elif self.__tc.direction == TrafficDirection.INCOMING:
            device = self._parser.ifb_device

        return [
            Where(Tc.Param.DEVICE, device),
            Where(Tc.Param.PROTOCOL, self.__tc.protocol),
            Where(Tc.Param.DST_NETWORK, self.__tc.dst_network),
            Where(Tc.Param.SRC_NETWORK, self.__tc.src_network),
            Where(Tc.Param.DST_PORT, self.__tc.dst_port),
            Where(Tc.Param.SRC_PORT, self.__tc.src_port),
        ]
tcconfig/_netem_param.py
class NetemParameter:
    @property
    def bandwidth_rate(self):
        return self.__bandwidth_rate

    def __init__(
        self,
        device,
        bandwidth_rate=None,
        latency_time=None,
        latency_distro_time=None,
        latency_distribution=None,
        packet_loss_rate=None,
        packet_duplicate_rate=None,
        corruption_rate=None,
        reordering_rate=None,
    ):
        self.__device = device

        self.__bandwidth_rate = self.__normalize_bandwidth_rate(bandwidth_rate)
        self.__packet_loss_rate = convert_rate_to_f(packet_loss_rate)  # [%]
        self.__packet_duplicate_rate = convert_rate_to_f(packet_duplicate_rate)  # [%]
        self.__corruption_rate = convert_rate_to_f(corruption_rate)  # [%]
        self.__reordering_rate = convert_rate_to_f(reordering_rate)  # [%]

        self.__latency_time = None
        if latency_time:
            self.__latency_time = hr.Time(latency_time, hr.Time.Unit.MILLISECOND)

        self.__latency_distro_time = None
        if latency_distro_time:
            self.__latency_distro_time = hr.Time(latency_distro_time, hr.Time.Unit.MILLISECOND)

        self.__latency_distribution = (
            "normal" if latency_distribution is None else latency_distribution
        )
        if self.__latency_distribution not in DELAY_DISTRIBUTIONS:
            raise ValueError("latency_distribution must be one of {}".format(DELAY_DISTRIBUTIONS))

    def __normalize_bandwidth_rate(self, bandwidth_rate):
        if not bandwidth_rate:
            return None

        hr_bps = hr.BitPerSecond(bandwidth_rate)
        upper_limit_rate = get_upper_limit_rate(self.__device)

        if hr_bps > upper_limit_rate:
            logger.info(
                dedent(
                    """\
                    clipping specified bandwidth rate limit with the {device} maximum bandwidth rate
                    ({value}Mbps -> {limit}Mbps)
                    """
                ).format(
                    value=hr_bps.mega_bps, limit=upper_limit_rate.mega_bps, device=self.__device
                )
            )
            hr_bps = upper_limit_rate

        return hr_bps

    def validate_netem_parameter(self):
        self.validate_bandwidth_rate()
        self.__validate_network_delay()
        self.__validate_packet_loss_rate()
        self.__validate_packet_duplicate_rate()
        self.__validate_corruption_rate()
        self.__validate_reordering_rate()
        self.__validate_reordering_and_delay()

        netem_param_values = [
            self.__packet_loss_rate,
            self.__packet_duplicate_rate,
            self.__corruption_rate,
            self.__reordering_rate,
        ]
        if self.__bandwidth_rate:
            netem_param_values.append(self.__bandwidth_rate.kilo_bps)

        check_results = [
            not RealNumber(netem_param_value).is_type() or netem_param_value <= 0
            for netem_param_value in netem_param_values
        ]

        if self.__latency_time:
            check_results.append(self.__latency_time <= hr.Time(Tc.ValueRange.LatencyTime.MIN))

        if all(check_results):
            raise hr.ParameterError(
                "there are no valid net emulation parameters found. "
                "at least one or more following parameters are required: "
                "--rate, --delay, --loss, --duplicate, --corrupt, --reordering"
            )

    def validate_bandwidth_rate(self):
        hr_bps = self.__bandwidth_rate

        if not hr_bps:
            return

        if hr_bps.bps < 8:
            raise hr.ParameterError(
                "bandwidth rate must be greater or equals to 8bps", value="{}bps".format(hr_bps.bps)
            )

        upper_limit_rate = get_upper_limit_rate(self.__device)
        if hr_bps > upper_limit_rate:
            raise hr.ParameterError(
                "exceed bandwidth rate limit",
                value="{} kbps".format(hr_bps.kilo_bps),
                expected="less than {} kbps".format(upper_limit_rate.kilo_bps),
            )

    def make_param_name(self):
        item_list = [self.__device]

        if self.bandwidth_rate:
            item_list.append("rate{}kbps".format(int(self.bandwidth_rate.kilo_bps)))

        if self.__latency_time and self.__latency_time.milliseconds > 0:
            item_list.append("delay{}".format(self.__latency_time.milliseconds))

        if self.__latency_distro_time and self.__latency_distro_time.milliseconds > 0:
            item_list.append("distro{}".format(self.__latency_distro_time.milliseconds))

        if self.__packet_loss_rate:
            item_list.append("loss{}".format(self.__packet_loss_rate))

        if self.__packet_duplicate_rate:
            item_list.append("duplicate{}".format(self.__packet_duplicate_rate))

        if self.__corruption_rate:
            item_list.append("corrupt{}".format(self.__corruption_rate))

        if self.__reordering_rate:
            item_list.append("reordering{}".format(self.__reordering_rate))

        return "_".join(item_list)

    def make_netem_command_parts(self):
        item_list = ["netem"]

        if self.__packet_loss_rate > 0:
            item_list.append("loss {:f}%".format(self.__packet_loss_rate))

        if self.__packet_duplicate_rate > 0:
            item_list.append("duplicate {:f}%".format(self.__packet_duplicate_rate))

        if self.__latency_time and self.__latency_time > hr.Time(Tc.ValueRange.LatencyTime.MIN):
            item_list.append("delay {}ms".format(self.__latency_time.milliseconds))

            if self.__latency_distro_time and self.__latency_distro_time > hr.Time(
                Tc.ValueRange.LatencyTime.MIN
            ):
                item_list.append(
                    "{}ms distribution {}".format(
                        self.__latency_distro_time.milliseconds, self.__latency_distribution
                    )
                )

        if self.__corruption_rate > 0:
            item_list.append("corrupt {:f}%".format(self.__corruption_rate))

        if self.__reordering_rate > 0:
            item_list.append("reorder {:f}%".format(self.__reordering_rate))

        return " ".join(item_list)

    def calc_hash(self, extra=""):
        return hashlib.md5((self.make_param_name() + extra).encode("latin-1")).hexdigest()

    def calc_device_qdisc_major_id(self):
        base_device_hash = self.calc_hash()[:3]
        device_hash_prefix = "2"

        return int(device_hash_prefix + base_device_hash, 16)

    def __validate_network_delay(self):
        if self.__latency_time:
            try:
                self.__latency_time.validate(
                    min_value=Tc.ValueRange.LatencyTime.MIN, max_value=Tc.ValueRange.LatencyTime.MAX
                )
            except hr.ParameterError as e:
                raise hr.ParameterError("delay {}".format(e))

        if self.__latency_distro_time:
            try:
                self.__latency_distro_time.validate(
                    min_value=Tc.ValueRange.LatencyTime.MIN, max_value=Tc.ValueRange.LatencyTime.MAX
                )
            except hr.ParameterError as e:
                raise hr.ParameterError("delay-distro {}".format(e))

    def __validate_packet_loss_rate(self):
        validate_within_min_max(
            "loss (packet loss rate)",
            self.__packet_loss_rate,
            MIN_PACKET_LOSS_RATE,
            MAX_PACKET_LOSS_RATE,
            unit="%",
        )

    def __validate_packet_duplicate_rate(self):
        validate_within_min_max(
            "duplicate (packet duplicate rate)",
            self.__packet_duplicate_rate,
            MIN_PACKET_DUPLICATE_RATE,
            MAX_PACKET_DUPLICATE_RATE,
            unit="%",
        )

    def __validate_corruption_rate(self):
        validate_within_min_max(
            "corruption (packet corruption rate)",
            self.__corruption_rate,
            MIN_CORRUPTION_RATE,
            MAX_CORRUPTION_RATE,
            unit="%",
        )

    def __validate_reordering_rate(self):
        validate_within_min_max(
            "reordering (packet reordering rate)",
            self.__reordering_rate,
            MIN_REORDERING_RATE,
            MAX_REORDERING_RATE,
            unit="%",
        )

    def __validate_reordering_and_delay(self):
        if self.__reordering_rate and self.__latency_time and self.__latency_time.milliseconds <= 0:
            raise hr.ParameterError("reordering needs latency to be specified: set latency > 0")
tcconfig/_const.py
DELAY_DISTRIBUTIONS = ("normal", "pareto", "paretonormal")
tcconfig/_capabilities.py
def check_execution_authority(command):
    if has_execution_authority(command):
        return

    logger.error(get_permission_error_message(command))
    sys.exit(errno.EPERM)
tcconfig/_netem_param.py
MAX_CORRUPTION_RATE = 100  # [%]
tcconfig/_error.py
class NetworkInterfaceNotFoundError(TargetNotFoundError):
    """
    Exception raised when network interface not found.
    """

    @property
    def _target_type(self):
        return "network interface"

    def __str__(self, *args, **kwargs):
        item_list = [super().__str__(*args, **kwargs)]

        with IPRoute() as ipr:
            avail_interfaces = [link.get_attr("IFLA_IFNAME") for link in ipr.get_links()]

        item_list.append("(available interfaces: {})".format(", ".join(avail_interfaces)))

        return " ".join(item_list).strip()
tcconfig/_common.py
def initialize_cli(options):
    set_log_level(options.log_level)

    spr.SubprocessRunner.is_save_history = True

    if options.is_output_stacktrace:
        spr.SubprocessRunner.is_output_stacktrace = options.is_output_stacktrace

    SimpleSQLite.global_debug_query = options.debug_query
tcconfig/_common.py
def normalize_tc_value(tc_obj):
    import ipaddress

    try:
        tc_obj.sanitize()
    except ipaddress.AddressValueError as e:
        logger.error(IPV6_OPTION_ERROR_MSG_FORMAT.format(e))
        sys.exit(errno.EINVAL)
    except ValueError as e:
        logger.error(msgfy.to_error_message(e))
        sys.exit(errno.EINVAL)
tcconfig/_const.py
class ShapingAlgorithm:
    HTB = "htb"
    TBF = "tbf"
    LIST = [HTB, TBF]
tcconfig/_const.py
class TrafficDirection:
    OUTGOING = "outgoing"
    INCOMING = "incoming"
    LIST = [OUTGOING, INCOMING]
tcconfig/_netem_param.py
MIN_CORRUPTION_RATE = 0  # [%]
tcconfig/_logger.py
def set_log_level(log_level):
    if log_level == "DEBUG":
        log_format = (
            "<level>{level: <8}</level> | "
            "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>"
        )
    else:
        log_format = "<level>[{level}]</level> {message}"

    logger.remove()
    logger.add(sys.stderr, colorize=True, format=log_format, level=log_level)

    if log_level == "QUIET":
        set_logger(is_enable=False)
    else:
        set_logger(is_enable=True)
tcconfig/_main.py
class Main:
    def __init__(self, options):
        self._options = options

        self._dclient = None
        if self._options.use_docker:
            try:
                self._dclient = DockerClient(options.tc_command_output)
            except DockerException as e:
                logger.error(msgfy.to_error_message(e))
                sys.exit(1)

    def _extract_dst_network(self):
        if self._options.dst_container:
            return self._dclient.extract_container_info(self._options.dst_container).ipaddr

        return self._options.dst_network

    def _extract_src_network(self):
        if self._options.src_container:
            return self._dclient.extract_container_info(self._options.src_container).ipaddr

        return self._options.src_network

    def _fetch_tc_targets(self):
        if not self._options.use_docker:
            return [self._options.device]

        container = self._options.device

        self._dclient.verify_container(container, exit_on_exception=True)
        self._dclient.create_veth_table(container)

        return self._dclient.fetch_veth_list(self._dclient.extract_container_info(container).name)

    def _get_return_code(self, return_code_list):
        error_return_code = None

        for return_code in return_code_list:
            if return_code == 0:
                return return_code

            error_return_code = return_code

        return error_return_code

    def _dump_history(self, tc, tc_command):
        command_history = "\n".join(tc.get_command_history())
        command_output = self._options.tc_command_output

        if command_output == TcCommandOutput.STDOUT:
            print(command_history)
            return

        try:
            filename_suffix = tc.netem_param.make_param_name()
        except AttributeError:
            filename_suffix = None

        if command_output == TcCommandOutput.SCRIPT:
            write_tc_script(tc_command, command_history, filename_suffix=filename_suffix)
            return

        logger.debug("command history\n{}".format(command_history))
tcconfig/_importer.py
def set_tc_from_file(
    logger, config_file_path: str, is_overwrite: bool, tc_command_output: Optional[str]
) -> int:
    return_code = 0

    loader = TcConfigLoader(logger)
    loader.is_overwrite = is_overwrite
    loader.tc_command_output = tc_command_output

    try:
        loader.load_tcconfig(config_file_path)
    except OSError as e:
        logger.error(msgfy.to_error_message(e))
        return errno.EIO

    for tcconfig_command in loader.get_tcconfig_commands():
        runner = spr.SubprocessRunner(tcconfig_command)
        return_code |= runner.run()

        if return_code != 0:
            logger.error(runner.stderr)
        elif tc_command_output == TcCommandOutput.STDOUT:
            print(runner.stdout.strip())

    return return_code


Question: import errno
import ipaddress
import sys
import humanreadable as hr
import msgfy
import subprocrunner as spr
    import re
from loguru import logger
from .__version__ import __version__
from ._argparse_wrapper import ArgparseWrapper
from ._capabilities import check_execution_authority
from ._common import initialize_cli, is_execute_tc_command, normalize_tc_value
from ._const import (
    DELAY_DISTRIBUTIONS,
    IPV6_OPTION_ERROR_MSG_FORMAT,
    ShapingAlgorithm,
    Tc,
    TrafficDirection,
)
from ._error import ContainerNotFoundError, ModuleNotFoundError, NetworkInterfaceNotFoundError
from ._importer import set_tc_from_file
from ._logger import set_log_level
from ._main import Main
from ._netem_param import (
    MAX_CORRUPTION_RATE,
    MAX_PACKET_DUPLICATE_RATE,
    MAX_PACKET_LOSS_RATE,
    MAX_REORDERING_RATE,
    MIN_CORRUPTION_RATE,
    MIN_PACKET_DUPLICATE_RATE,
    MIN_PACKET_LOSS_RATE,
    MIN_REORDERING_RATE,
    NetemParameter,
)
from ._shaping_rule_finder import TcShapingRuleFinder
from .traffic_control import TrafficControl
#!/usr/bin/env python3

"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""






def _get_unit_help_msg():
    return ", ".join(["/".join(values) for values in hr.Time.get_text_units().values()])


def get_arg_parser():
    parser = ArgparseWrapper(__version__)

    if {"-d", "--device"}.intersection(set(sys.argv)):
        # deprecated: remain for backward compatibility
        parser.parser.add_argument(
            "-d", "--device", required=True, help="network device name (e.g. eth0)"
        )
    else:
        parser.parser.add_argument(
            "device", help="target name: network-interface/config-file (e.g. eth0)"
        )

    parser.parser.add_argument(
        "--import-setting",
        action="store_true",
        default=False,
        help="import traffic control settings from a configuration file.",
    )

    group = parser.parser.add_mutually_exclusive_group()
    group.add_argument(
        "--overwrite",
        action="store_true",
        default=False,
        help="overwrite existing traffic shaping rules.",
    )
    group.add_argument(
        "--change",
        dest="is_change_shaping_rule",
        action="store_true",
        default=False,
        help="""change existing traffic shaping rules to the new one.
        this option is effective to reduce the time between the shaping rule switching
        compared to --overwrite option.
        note: just adds a shaping rule if there are no existing shaping rules.
        """,
    )
    group.add_argument(
        "--add",
        dest="is_add_shaping_rule",
        action="store_true",
        default=False,
        help="add a traffic shaping rule in addition to existing rules.",
    )

    group = parser.parser.add_argument_group("Traffic Control Parameters")
    group.add_argument(
        "--rate",
        "--bandwidth-rate",
        dest="bandwidth_rate",
        help="""network bandwidth rate [bit per second].
        the minimum bandwidth rate is 8 bps.
        valid units are either: {}.
        e.g. tcset eth0 --rate 10Mbps
        """.format(
            ", ".join([", ".join(values) for values in hr.BitPerSecond.get_text_units().values()])
        ),
    )
    group.add_argument(
        "--delay",
        dest="latency_time",

Answer:         default=Tc.ValueRange.LatencyTime.MIN,