tcconfig/_network.py
def is_anywhere_network(network, ip_version):
    try:
        network = network.strip()
    except AttributeError as e:
        raise ValueError(e)

    if ip_version == 4:
        return network == get_anywhere_network(ip_version)

    if ip_version == 6:
        return network in (get_anywhere_network(ip_version), "0:0:0:0:0:0:0:0/0")

    raise ValueError("invalid ip version: {}".format(ip_version))
tcconfig/parser/_class.py
class TcClassParser(AbstractParser):
    class Pattern:
        CLASS_ID = "[0-9a-z:]+"
        RATE = "[0-9]+[KMGT]?"

    class Key:
        DEVICE = Tc.Param.DEVICE
        CLASS_ID = Tc.Param.CLASS_ID
        RATE = "rate"

        LIST = (DEVICE, CLASS_ID, RATE)

    @property
    def _tc_subcommand(self):
        return TcSubCommand.CLASS.value

    def parse(self, device, text):
        entry_list = []

        for line in text.splitlines():
            self._clear()

            if typepy.is_null_string(line):
                continue

            line = self._to_unicode(line.lstrip())

            self.__parsed_param[self.Key.DEVICE] = device
            self.__parse_classid(line)
            self.__parse_rate(line)

            logger.debug("parse a class entry: {}".format(self.__parsed_param))
            entry_list.append(self.__parsed_param)

        if entry_list:
            self._con.create_table_from_data_matrix(self._tc_subcommand, self.Key.LIST, entry_list)

        logger.debug(
            "tc {:s} parse result: {}".format(self._tc_subcommand, json.dumps(entry_list, indent=4))
        )

        return entry_list

    def _clear(self):
        self.__parsed_param = {}

    def __parse_classid(self, line):
        self.__parsed_param[self.Key.CLASS_ID] = None
        tag = "class {:s} ".format(ShapingAlgorithm.HTB)

        match = re.search("{:s}{:s}".format(tag, self.Pattern.CLASS_ID), line)
        if match is None:
            return

        self.__parsed_param[self.Key.CLASS_ID] = re.search(
            self.Pattern.CLASS_ID, match.group().lstrip(tag)
        ).group()

    def __parse_rate(self, line):
        self.__parsed_param[self.Key.RATE] = None

        match = re.search("rate {:s}".format(self.Pattern.RATE), line)
        if match is None:
            return

        self.__parsed_param[self.Key.RATE] = (
            re.search(self.Pattern.RATE, match.group()).group() + "bps"
        )
tcconfig/_iptables.py
class IptablesMangleController:

    __RE_CHAIN = re.compile("Chain {:s} |Chain {:s} |Chain {:s} ".format(*VALID_CHAIN_LIST))
    __RE_CHAIN_NAME = re.compile("{:s}|{:s}|{:s}".format(*VALID_CHAIN_LIST))
    __MAX_MARK_ID = 0xFFFFFFFF
    __MARK_ID_OFFSET = 100

    @property
    def enable(self):
        return self.__enable

    def __init__(self, enable, ip_version):
        self.__enable = enable
        self.__ip_version = ip_version

    def clear(self):
        if not self.enable:
            return

        self.__check_execution_authority()

        for mangle in self.parse():
            proc = SubprocessRunner(mangle.to_delete_command())
            if proc.run() != 0:
                raise OSError(proc.returncode, proc.stderr)

    def get_iptables(self):
        self.__check_execution_authority()

        proc = SubprocessRunner(
            "{:s} {:s}".format(get_iptables_base_command(), LIST_MANGLE_TABLE_OPTION)
        )
        if proc.run() != 0:
            raise OSError(proc.returncode, proc.stderr)

        return proc.stdout

    def get_unique_mark_id(self):
        self.__check_execution_authority()

        mark_id_list = [mangle.mark_id for mangle in self.parse()]
        logger.debug("mangle mark list: {}".format(mark_id_list))

        unique_mark_id = 1 + self.__MARK_ID_OFFSET
        while unique_mark_id < self.__MAX_MARK_ID:
            if unique_mark_id not in mark_id_list:
                return unique_mark_id

            unique_mark_id += 1

        raise RuntimeError("usable mark id not found")

    def parse(self):
        self.__check_execution_authority()

        MANGLE_ITEM_COUNT = 6

        for block in split_line_list(self.get_iptables().splitlines()):
            if len(block) <= 1:
                # skip if there is no mangle table entry exists
                continue

            match = self.__RE_CHAIN.search(block[0])
            if match is None:
                continue

            chain = self.__RE_CHAIN_NAME.search(match.group()).group()

            for line in reversed(block[2:]):
                item_list = line.split()
                if len(item_list) < MANGLE_ITEM_COUNT:
                    continue

                line_number = int(item_list[0])
                target = item_list[1]
                protocol = item_list[2]
                source = item_list[4]
                destination = item_list[5]

                try:
                    mark = int(item_list[-1], 16)
                except ValueError:
                    continue

                if target != "MARK":
                    continue

                yield IptablesMangleMarkEntry(
                    ip_version=self.__ip_version,
                    mark_id=mark,
                    source=source,
                    destination=destination,
                    chain=chain,
                    protocol=protocol,
                    line_number=line_number,
                )

    @classmethod
    def add(cls, mangling_mark):
        if not cls.enable:
            return 0

        cls.__check_execution_authority()

        return SubprocessRunner(mangling_mark.to_append_command()).run()

    @staticmethod
    def __check_execution_authority():
        from ._capabilities import get_permission_error_message, has_execution_authority

        if not has_execution_authority("iptables"):
            raise OSError(errno.EPERM, get_permission_error_message("iptables"))
tcconfig/_common.py
def is_execute_tc_command(tc_command_output):
    return tc_command_output == TcCommandOutput.NOT_SET
tcconfig/parser/_model.py
class Qdisc(Model):
    device = Text(attr_name=Tc.Param.DEVICE, not_null=True)
    direct_qlen = Integer()
    parent = Text(attr_name=Tc.Param.PARENT)
    handle = Text(attr_name=Tc.Param.HANDLE)
    delay = Text()
    delay_distro = Text(attr_name="delay-distro")
    loss = Text()
    duplicate = Text()
    corrupt = Text()
    reorder = Text()
    rate = Text()
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/_tc_command_helper.py
def get_tc_base_command(tc_subcommand):
    if not isinstance(tc_subcommand, TcSubCommand):
        raise ValueError("the argument must be a TcSubCommand value")

    return "{:s} {:s}".format(find_bin_path("tc"), tc_subcommand.value)
tcconfig/parser/_qdisc.py
class TcQdiscParser(AbstractParser):
    __RE_DIRECT_QLEN = re.compile("direct_qlen (?P<number>[0-9]+)")

    @property
    def _tc_subcommand(self):
        return TcSubCommand.QDISC.value

    def __parse_direct_qlen(self, line):
        m = self.__RE_DIRECT_QLEN.search(line)
        if m is None:
            return

        self.__parsed_param["direct_qlen"] = int(m.group("number"))

    def parse(self, device, text):
        self._clear()

        if typepy.is_null_string(text):
            return []

        text = text.strip()

        for line in text.splitlines():
            if typepy.is_null_string(line):
                continue

            line = self._to_unicode(line.lstrip())

            if re.search("^qdisc netem |^qdisc htb |^qdisc tbf ", line) is None:
                continue

            if re.search("^qdisc htb ", line) is not None:
                self.__parse_direct_qlen(line)
                continue

            if re.search("^qdisc netem ", line) is not None:
                self.__parse_netem_param(line, "parent", pp.hexnums + ":")

            self.__parsed_param[Tc.Param.DEVICE] = device
            self.__parse_netem_param(line, "netem", pp.hexnums + ":", "handle")
            self.__parse_netem_param(line, "delay", pp.nums + ".msu")
            self.__parse_netem_delay_distro(line)
            self.__parse_netem_param(line, "loss", pp.nums + ".%")
            self.__parse_netem_param(line, "duplicate", pp.nums + ".%")
            self.__parse_netem_param(line, "corrupt", pp.nums + ".%")
            self.__parse_netem_param(line, "reorder", pp.nums + ".%")
            self.__parse_bandwidth_rate(line)

            logger.debug("parse a qdisc entry: {}".format(self.__parsed_param))

            Qdisc.insert(Qdisc(**self.__parsed_param))

            self._clear()

    def _clear(self):
        self.__parsed_param = {}

    def __parse_netem_delay_distro(self, line):
        parse_param_name = "delay"
        pattern = (
            pp.SkipTo(parse_param_name, include=True)
            + pp.Word(pp.nums + ".msu")
            + pp.Word(pp.nums + ".msu")
        )

        try:
            parsed_list = pattern.parseString(line)
            self.__parsed_param[parse_param_name] = parsed_list[2]
            self.__parsed_param["delay-distro"] = parsed_list[3]
        except pp.ParseException:
            pass

    def __parse_netem_param(self, line, parse_param_name, word_pattern, key_name=None):
        pattern = pp.SkipTo(parse_param_name, include=True) + pp.Word(word_pattern)
        if not key_name:
            key_name = parse_param_name

        try:
            result = pattern.parseString(line)[-1]
            if typepy.is_not_null_string(result):
                self.__parsed_param[key_name] = result
        except pp.ParseException:
            pass

    def __parse_bandwidth_rate(self, line):
        parse_param_name = "rate"
        pattern = pp.SkipTo(parse_param_name, include=True) + pp.Word(pp.alphanums + "." + ":")

        try:
            result = pattern.parseString(line)[-1]
            if typepy.is_not_null_string(result):
                result = result.rstrip("bit")
                self.__parsed_param[parse_param_name] = result
        except pp.ParseException:
            pass
tcconfig/_const.py
class TrafficDirection:
    OUTGOING = "outgoing"
    INCOMING = "incoming"
    LIST = [OUTGOING, INCOMING]
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/_tc_command_helper.py
def run_tc_show(subcommand, device, tc_command_output):
    from ._network import verify_network_interface

    verify_network_interface(device, tc_command_output)

    runner = spr.SubprocessRunner(
        "{:s} show dev {:s}".format(get_tc_base_command(subcommand), device)
    )
    if runner.run() != 0 and runner.stderr.find("Cannot find device") != -1:
        # reach here if the device does not exist at the system and netiface
        # not installed.
        raise NetworkInterfaceNotFoundError(target=device)

    return runner.stdout
tcconfig/parser/_model.py
class Filter(Model):
    device = Text(attr_name=Tc.Param.DEVICE, not_null=True)
    filter_id = Text(attr_name=Tc.Param.FILTER_ID)
    flowid = Text(attr_name=Tc.Param.FLOW_ID)
    protocol = Text(attr_name=Tc.Param.PROTOCOL)
    priority = Integer(attr_name=Tc.Param.PRIORITY)
    src_network = Text(attr_name=Tc.Param.SRC_NETWORK)
    dst_network = Text(attr_name=Tc.Param.DST_NETWORK)
    src_port = Integer(attr_name=Tc.Param.SRC_PORT)
    dst_port = Integer(attr_name=Tc.Param.DST_PORT)

    classid = Text(attr_name=Tc.Param.CLASS_ID)
    handle = Integer(attr_name=Tc.Param.HANDLE)
tcconfig/_const.py
class TcSubCommand(enum.Enum):
    CLASS = "class"
    FILTER = "filter"
    QDISC = "qdisc"
tcconfig/parser/_filter.py
class TcFilterParser(AbstractParser):
    class FilterMatchIdIpv4:
        INCOMING_NETWORK = 12
        OUTGOING_NETWORK = 16
        PORT = 20

    class FilterMatchIdIpv6:
        INCOMING_NETWORK_LIST = [8, 12, 16, 20]
        OUTGOING_NETWORK_LIST = [24, 28, 32, 36]
        PORT = 40

    __FILTER_FLOWID_PATTERN = (
        pp.Literal("filter parent") + pp.SkipTo("flowid", include=True) + pp.Word(pp.hexnums + ":")
    )
    __FILTER_PROTOCOL_PATTERN = (
        pp.Literal("filter parent") + pp.SkipTo("protocol", include=True) + pp.Word(pp.alphanums)
    )
    __FILTER_PRIORITY_PATTERN = (
        pp.Literal("filter parent") + pp.SkipTo("pref", include=True) + pp.Word(pp.nums)
    )
    __FILTER_ID_PATTERN = (
        pp.Literal("filter parent") + pp.SkipTo("fh", include=True) + pp.Word(pp.hexnums + ":")
    )
    __FILTER_MATCH_PATTERN = (
        pp.Literal("match") + pp.Word(pp.alphanums + "/") + pp.Literal("at") + pp.Word(pp.nums)
    )
    __FILTER_MANGLE_MARK_PATTERN = (
        pp.Literal("filter parent")
        + pp.SkipTo("handle", include=True)
        + pp.Word(pp.hexnums)
        + pp.SkipTo("classid", include=True)
        + pp.Word(pp.hexnums + ":")
    )

    @property
    def protocol(self):
        return self.__protocol

    @property
    def _tc_subcommand(self):
        return TcSubCommand.FILTER.value

    def __init__(self, con, ip_version):
        super().__init__(con)

        self.__ip_version = ip_version
        self.__buffer = None
        self.__parse_idx = 0

        self.__protocol = None

        self._clear()

    def parse(self, device, text):
        self._clear()

        if typepy.is_null_string(text):
            return []

        self.__buffer = self._to_unicode(text).splitlines()
        self.__parse_idx = 0

        while self.__parse_idx < len(self.__buffer):
            line = self._to_unicode(self.__buffer[self.__parse_idx].strip())
            self.__parse_idx += 1

            if typepy.is_null_string(line):
                continue

            self.__device = device

            try:
                self.__parse_mangle_mark(line)
            except pp.ParseException:
                logger.debug("failed to parse mangle: {}".format(line))
            else:
                Filter.insert(
                    Filter(
                        **{
                            Tc.Param.DEVICE: self.__device,
                            Tc.Param.CLASS_ID: self.__classid,
                            Tc.Param.HANDLE: self.__handle,
                        }
                    )
                )
                self._clear()
                continue

            tc_filter = self.__get_filter()

            try:
                self.__parse_flow_id(line)
                self.__parse_protocol(line)
                self.__parse_priority(line)
                self.__parse_filter_id(line)

                if tc_filter.flowid:
                    logger.debug("store filter: {}".format(tc_filter))
                    Filter.insert(tc_filter)
                    self._clear()

                    self.__device = device
                    self.__parse_flow_id(line)
                    self.__parse_protocol(line)
                    self.__parse_priority(line)
                    self.__parse_filter_id(line)

                continue
            except pp.ParseException:
                logger.debug("failed to parse flow id: {}".format(line))

            try:
                if self.__ip_version == 4:
                    self.__parse_filter_ipv4(line)
                elif self.__ip_version == 6:
                    self.__parse_filter_ipv6(line)
                else:
                    raise ValueError("unknown ip version: {}".format(self.__ip_version))
            except pp.ParseException:
                logger.debug("failed to parse filter: {}".format(line))

        if self.__flow_id:
            Filter.insert(self.__get_filter())

    def parse_incoming_device(self, text):
        if typepy.is_null_string(text):
            return None

        match = re.search(
            r"Egress Redirect to device ifb[\d]+", self._to_unicode(text), re.MULTILINE
        )
        if match is None:
            return None

        return re.search(r"ifb[\d]+", match.group()).group()

    def _clear(self):
        self.__device = None
        self.__filter_id = None
        self.__flow_id = None
        self.__protocol = None
        self.__priority = None
        self.__filter_src_network = None
        self.__filter_dst_network = None
        self.__filter_src_port = None
        self.__filter_dst_port = None

        self.__handle = None
        self.__classid = None

    def __get_filter(self):
        return Filter(
            device=self.__device,
            filter_id=self.__filter_id,
            flowid=self.__flow_id,
            protocol=self.protocol,
            priority=self.__priority,
            src_network=sanitize_network(self.__filter_src_network, self.__ip_version),
            dst_network=sanitize_network(self.__filter_dst_network, self.__ip_version),
            src_port=self.__filter_src_port,
            dst_port=self.__filter_dst_port,
        )

    def __parse_flow_id(self, line):
        parsed_list = self.__FILTER_FLOWID_PATTERN.parseString(line)
        self.__flow_id = parsed_list[-1]
        logger.debug("succeed to parse flow id: flow-id={}, line={}".format(self.__flow_id, line))

    def __parse_protocol(self, line):
        parsed_list = self.__FILTER_PROTOCOL_PATTERN.parseString(line)
        self.__protocol = parsed_list[-1]
        logger.debug(
            "succeed to parse protocol: protocol={}, line={}".format(self.__protocol, line)
        )

    def __parse_priority(self, line):
        parsed_list = self.__FILTER_PRIORITY_PATTERN.parseString(line)
        self.__priority = int(parsed_list[-1])
        logger.debug(
            "succeed to parse priority: priority={}, line={}".format(self.__priority, line)
        )

    def __parse_filter_id(self, line):
        parsed_list = self.__FILTER_ID_PATTERN.parseString(line)
        self.__filter_id = parsed_list[-1]
        logger.debug(
            "succeed to parse filter id: filter-id={}, line={}".format(self.__filter_id, line)
        )

    def __parse_mangle_mark(self, line):
        parsed_list = self.__FILTER_MANGLE_MARK_PATTERN.parseString(line)
        self.__classid = parsed_list[-1]
        self.__handle = int("0" + parsed_list[-3], 16)
        logger.debug(
            "succeed to parse mangle mark: "
            "classid={}, handle={}, line={}".format(self.__classid, self.__handle, line)
        )

    def __parse_filter_ip_line(self, line):
        parsed_list = self.__FILTER_MATCH_PATTERN.parseString(line)
        value_hex, mask_hex = parsed_list[1].split("/")
        match_id = int(parsed_list[3])

        return (value_hex, mask_hex, match_id)

    def __parse_filter_ipv4_network(self, value_hex, mask_hex, match_id):
        ipaddr = ".".join([str(int(value_hex[i : i + 2], 16)) for i in range(0, len(value_hex), 2)])
        netmask = bin(int(mask_hex, 16)).count("1")
        network = "{:s}/{:d}".format(ipaddr, netmask)

        if match_id == self.FilterMatchIdIpv4.INCOMING_NETWORK:
            self.__filter_src_network = network
        elif match_id == self.FilterMatchIdIpv4.OUTGOING_NETWORK:
            self.__filter_dst_network = network
        else:
            logger.warning("unknown match id: {}".format(match_id))

    def __parse_filter_ipv6_network(self, value_hex, mask_hex, match_id):
        from collections import namedtuple

        Ipv6Entry = namedtuple("Ipv6Entry", "match_id octet_list mask_hex")

        OCTET_LEN = 4
        ipv6_entry_list = [
            Ipv6Entry(
                match_id=match_id,
                octet_list=[
                    value_hex[i : i + OCTET_LEN] for i in range(0, len(value_hex), OCTET_LEN)
                ],
                mask_hex=mask_hex,
            )
        ]

        while True:
            try:
                line = self.__buffer[self.__parse_idx].strip()
            except IndexError:
                break

            try:
                value_hex, mask_hex, match_id = self.__parse_filter_ip_line(line)
            except pp.ParseException:
                break

            if (
                match_id in self.FilterMatchIdIpv6.INCOMING_NETWORK_LIST
                or match_id in self.FilterMatchIdIpv6.OUTGOING_NETWORK_LIST
            ):
                ipv6_entry_list.append(
                    Ipv6Entry(
                        match_id=match_id,
                        octet_list=[
                            value_hex[i : i + OCTET_LEN]
                            for i in range(0, len(value_hex), OCTET_LEN)
                        ],
                        mask_hex=mask_hex,
                    )
                )
            else:
                break

            self.__parse_idx += 1

        src_octet_list = []
        dst_octet_list = []
        src_netmask = 0
        dst_netmask = 0

        for ipv6_entry in ipv6_entry_list:
            part_netmask = bin(int(ipv6_entry.mask_hex, 16)).count("1")

            if ipv6_entry.match_id in self.FilterMatchIdIpv6.INCOMING_NETWORK_LIST:
                src_octet_list.extend(ipv6_entry.octet_list)
                src_netmask += part_netmask
            elif ipv6_entry.match_id in self.FilterMatchIdIpv6.OUTGOING_NETWORK_LIST:
                dst_octet_list.extend(ipv6_entry.octet_list)
                dst_netmask += part_netmask
            else:
                raise ValueError("unexpected ipv6 entry: {}".format(ipv6_entry))

        while len(src_octet_list) < 8:
            src_octet_list.append("0000")
        while len(dst_octet_list) < 8:
            dst_octet_list.append("0000")

        self.__filter_dst_network = ipaddress.IPv6Network(
            "{:s}/{:d}".format(":".join(dst_octet_list), dst_netmask), strict=False
        ).compressed
        self.__filter_src_network = ipaddress.IPv6Network(
            "{:s}/{:d}".format(":".join(src_octet_list), src_netmask), strict=False
        ).compressed

    def __parse_filter_port(self, value_hex):
        # Port filter consists eight hex digits.
        # The upper-half represents source port filter and
        # the bottom-half represents destination port filter.

        if len(value_hex) != 8:
            raise ValueError("invalid port filter value: {}".format(value_hex))

        src_port_hex = value_hex[:4]
        dst_port_hex = value_hex[4:]

        logger.debug(
            "parse ipv4 port: src-port-hex={}, dst-port-hex={}".format(src_port_hex, dst_port_hex)
        )

        src_port_decimal = int(src_port_hex, 16)
        self.__filter_src_port = src_port_decimal if src_port_decimal != 0 else None

        dst_port_decimal = int(dst_port_hex, 16)
        self.__filter_dst_port = dst_port_decimal if dst_port_decimal != 0 else None

    def __parse_filter_ipv4(self, line):
        value_hex, mask_hex, match_id = self.__parse_filter_ip_line(line)

        if match_id in [
            self.FilterMatchIdIpv4.INCOMING_NETWORK,
            self.FilterMatchIdIpv4.OUTGOING_NETWORK,
        ]:
            self.__parse_filter_ipv4_network(value_hex, mask_hex, match_id)
        elif match_id == self.FilterMatchIdIpv4.PORT:
            self.__parse_filter_port(value_hex)
        elif match_id in (
            self.FilterMatchIdIpv6.INCOMING_NETWORK_LIST
            + self.FilterMatchIdIpv6.OUTGOING_NETWORK_LIST
            + [self.FilterMatchIdIpv6.PORT]
        ):
            logger.warning(
                "unknown match id for an IPv4 filter: might be an IPv6 filter. "
                "try to use --ipv6 option. (id={})".format(match_id)
            )
            return
        else:
            logger.debug("unknown match id: {}".format(match_id))
            return

        logger.debug(
            "succeed to parse ipv4 filter: "
            + ", ".join(
                [
                    "src_network={}".format(self.__filter_src_network),
                    "dst_network={}".format(self.__filter_dst_network),
                    "src_port={}".format(self.__filter_src_port),
                    "dst_port={}".format(self.__filter_dst_port),
                    "line={}".format(line),
                ]
            )
        )

    def __parse_filter_ipv6(self, line):
        value_hex, mask_hex, match_id = self.__parse_filter_ip_line(line)

        if (
            match_id in self.FilterMatchIdIpv6.INCOMING_NETWORK_LIST
            or match_id in self.FilterMatchIdIpv6.OUTGOING_NETWORK_LIST
        ):
            self.__parse_filter_ipv6_network(value_hex, mask_hex, match_id)
        elif match_id == self.FilterMatchIdIpv6.PORT:
            self.__parse_filter_port(value_hex)
        else:
            logger.debug("unknown match id: {}".format(match_id))
            return

        logger.debug(
            "succeed to parse ipv6 filter: "
            + ", ".join(
                [
                    "src_network={}".format(self.__filter_src_network),
                    "dst_network={}".format(self.__filter_dst_network),
                    "src_port={}".format(self.__filter_src_port),
                    "dst_port={}".format(self.__filter_dst_port),
                    "line={}".format(line),
                ]
            )
        )


Question: import copy
import subprocrunner
import typepy
from collections import OrderedDict
from simplesqlite import SimpleSQLite, TableNotFoundError, connect_memdb
from simplesqlite.query import And, Where
from .._common import is_execute_tc_command
from .._const import Tc, TcSubCommand, TrafficDirection
from .._error import NetworkInterfaceNotFoundError
from .._iptables import IptablesMangleController
from .._network import is_anywhere_network
from .._tc_command_helper import get_tc_base_command, run_tc_show
from ._class import TcClassParser
from ._filter import TcFilterParser
from ._model import Filter, Qdisc
from ._qdisc import TcQdiscParser
            if typepy.Integer(dst_port).is_type():
                key_items[Tc.Param.DST_PORT] = "{}".format(dst_port)
            elif src_port is not None:
                self.__logger.warning(
                    "expected a integer value for {}, actual {}".format(
                        Tc.Param.DST_PORT, type(dst_port)
                    )
                )

            protocol = filter_param.get(Tc.Param.PROTOCOL)
            if typepy.is_not_null_string(protocol):
                key_items[Tc.Param.PROTOCOL] = protocol

        key = ", ".join(["{}={}".format(key, value) for key, value in key_items.items()])

        return key, key_items

    def __get_shaping_rule(self, device):
        if typepy.is_null_string(device):
            return ({}, [])

        self.__parse_device(device)
        where_dev_query = Where(Tc.Param.DEVICE, device)

        try:
            class_params = self.__con.select_as_dict(
                table_name=TcSubCommand.CLASS.value, where=where_dev_query
            )
        except TableNotFoundError:
            class_params = []

        try:
            filter_params = Filter.select(where=where_dev_query)
        except TableNotFoundError:
            filter_params = []

        shaping_rule_mapping = {}
        shaping_rules = []

        for filter_param in filter_params:
            filter_param = filter_param.as_dict()
            self.__logger.debug("{:s} param: {}".format(TcSubCommand.FILTER, filter_param))
            shaping_rule = {}

            filter_key, rule_with_keys = self.__get_filter_key(filter_param)
            if typepy.is_null_string(filter_key):
                self.__logger.debug("empty filter key: {}".format(filter_param))
                continue

            qdisc_id = filter_param.get(Tc.Param.FLOW_ID)
            if qdisc_id is None:
                qdisc_id = filter_param.get(Tc.Param.CLASS_ID)

            try:
                qdisc_params = Qdisc.select(
                    where=And([where_dev_query, Where(Tc.Param.PARENT, qdisc_id)])
                )
            except TableNotFoundError:
                qdisc_params = []

            for qdisc_param in qdisc_params:
                qdisc_param = qdisc_param.as_dict()
                self.__logger.debug("{:s} param: {}".format(TcSubCommand.QDISC, qdisc_param))

                if self.is_parse_filter_id:
                    shaping_rule[Tc.Param.FILTER_ID] = filter_param.get(Tc.Param.FILTER_ID)

                # shaping_rule[Tc.Param.PRIORITY] = filter_param.get(
                #    Tc.Param.PRIORITY)

                shaping_rule.update(
                    self.__strip_param(
                        qdisc_param,
                        [Tc.Param.DEVICE, Tc.Param.PARENT, Tc.Param.HANDLE, "direct_qlen"],
                    )
                )

            for class_param in class_params:
                self.__logger.debug("{:s} param: {}".format(TcSubCommand.CLASS, class_param))

                if class_param.get(Tc.Param.CLASS_ID) not in (
                    filter_param.get(Tc.Param.FLOW_ID),
                    filter_param.get(Tc.Param.CLASS_ID),
                ):
                    continue

                if self.is_parse_filter_id:
                    shaping_rule[Tc.Param.FILTER_ID] = filter_param.get(Tc.Param.FILTER_ID)

                # shaping_rule[Tc.Param.PRIORITY] = filter_param.get(
                #    Tc.Param.PRIORITY)

                shaping_rule.update(
                    self.__strip_param(class_param, [Tc.Param.DEVICE, Tc.Param.CLASS_ID])
                )

            if not shaping_rule:
                self.__logger.debug("shaping rule not found for '{}'".format(filter_param))
                continue

            self.__logger.debug("shaping rule found: {} {}".format(filter_key, shaping_rule))

            rule_with_keys.update(shaping_rule)
            shaping_rules.append(rule_with_keys)

            shaping_rule_mapping[filter_key] = shaping_rule

        return (shaping_rule_mapping, shaping_rules)

    def __parse_tc_qdisc(self, device):
        TcQdiscParser(self.__con).parse(
            device, run_tc_show(TcSubCommand.QDISC, device, self.__tc_command_output)
        )

    def __parse_tc_filter(self, device):
        self.__filter_parser.parse(
            device, run_tc_show(TcSubCommand.FILTER, device, self.__tc_command_output)
        )

    def __parse_tc_class(self, device):

Answer:         TcClassParser(self.__con).parse(