"""
NFTSocket -- low level nftables API

See also: pyroute2.nftables
"""

import enum
import struct
import threading

from pyroute2.netlink import (
    NETLINK_NETFILTER,
    NLM_F_ACK,
    NLM_F_APPEND,
    NLM_F_CREATE,
    NLM_F_DUMP,
    NLM_F_EXCL,
    NLM_F_REPLACE,
    NLM_F_REQUEST,
    nla,
    nla_base_string,
    nlmsg_atoms,
)
from pyroute2.netlink.nfnetlink import NFNL_SUBSYS_NFTABLES, nfgen_msg
from pyroute2.netlink.nlsocket import AsyncNetlinkSocket, NetlinkSocket

NFT_MSG_NEWTABLE = 0
NFT_MSG_GETTABLE = 1
NFT_MSG_DELTABLE = 2
NFT_MSG_NEWCHAIN = 3
NFT_MSG_GETCHAIN = 4
NFT_MSG_DELCHAIN = 5
NFT_MSG_NEWRULE = 6
NFT_MSG_GETRULE = 7
NFT_MSG_DELRULE = 8
NFT_MSG_NEWSET = 9
NFT_MSG_GETSET = 10
NFT_MSG_DELSET = 11
NFT_MSG_NEWSETELEM = 12
NFT_MSG_GETSETELEM = 13
NFT_MSG_DELSETELEM = 14
NFT_MSG_NEWGEN = 15
NFT_MSG_GETGEN = 16
NFT_MSG_TRACE = 17
NFT_MSG_NEWOBJ = 18
NFT_MSG_GETOBJ = 19
NFT_MSG_DELOBJ = 20
NFT_MSG_GETOBJ_RESET = 21
NFT_MSG_NEWFLOWTABLE = 22
NFT_MSG_GETFLOWTABLE = 23
NFT_MSG_DELFLOWTABLE = 24

# from nftables/include/datatype.h
DATA_TYPE_INVALID = 0
DATA_TYPE_VERDICT = 1
DATA_TYPE_NFPROTO = 2
DATA_TYPE_BITMASK = 3
DATA_TYPE_INTEGER = 4
DATA_TYPE_STRING = 5
DATA_TYPE_LLADDR = 6
DATA_TYPE_IPADDR = 7
DATA_TYPE_IP6ADDR = 8
DATA_TYPE_ETHERADDR = 9
DATA_TYPE_ETHERTYPE = 10
DATA_TYPE_ARPOP = 11
DATA_TYPE_INET_PROTOCOL = 12
DATA_TYPE_INET_SERVICE = 13
DATA_TYPE_ICMP_TYPE = 14
DATA_TYPE_TCP_FLAG = 15
DATA_TYPE_DCCP_PKTTYPE = 16
DATA_TYPE_MH_TYPE = 17
DATA_TYPE_TIME = 18
DATA_TYPE_MARK = 19
DATA_TYPE_IFINDEX = 20
DATA_TYPE_ARPHRD = 21
DATA_TYPE_REALM = 22
DATA_TYPE_CLASSID = 23
DATA_TYPE_UID = 24
DATA_TYPE_GID = 25
DATA_TYPE_CT_STATE = 26
DATA_TYPE_CT_DIR = 27
DATA_TYPE_CT_STATUS = 28
DATA_TYPE_ICMP6_TYPE = 29
DATA_TYPE_CT_LABEL = 30
DATA_TYPE_PKTTYPE = 31
DATA_TYPE_ICMP_CODE = 32
DATA_TYPE_ICMPV6_CODE = 33
DATA_TYPE_ICMPX_CODE = 34
DATA_TYPE_DEVGROUP = 35
DATA_TYPE_DSCP = 36
DATA_TYPE_ECN = 37
DATA_TYPE_FIB_ADDR = 38
DATA_TYPE_BOOLEAN = 39
DATA_TYPE_CT_EVENTBIT = 40
DATA_TYPE_IFNAME = 41
DATA_TYPE_IGMP_TYPE = 42
DATA_TYPE_TIME_DATE = 43
DATA_TYPE_TIME_HOUR = 44
DATA_TYPE_TIME_DAY = 45
DATA_TYPE_CGROUPV2 = 46

# from include/uapi/linux/netfilter.h
NFPROTO_INET = 1
NFPROTO_IPV4 = 2
NFPROTO_ARP = 3
NFPROTO_NETDEV = 5
NFPROTO_BRIDGE = 7
NFPROTO_IPV6 = 10


class Cmp(enum.IntEnum):
    NFT_CMP_EQ: int = 0
    NFT_CMP_NEQ: int = 1
    NFT_CMP_LT: int = 2
    NFT_CMP_LTE: int = 3
    NFT_CMP_GT: int = 4
    NFT_CMP_GTE: int = 5


class Meta(enum.IntEnum):
    NFT_META_LEN: int = 0
    NFT_META_PROTOCOL: int = 1
    NFT_META_PRIORITY: int = 2
    NFT_META_MARK: int = 3
    NFT_META_IIF: int = 4
    NFT_META_OIF: int = 5
    NFT_META_IIFNAME: int = 6
    NFT_META_OIFNAME: int = 7
    NFT_META_IIFTYPE: int = 8
    NFT_META_OIFTYPE: int = 9
    NFT_META_SKUID: int = 10
    NFT_META_SKGID: int = 11
    NFT_META_NFTRACE: int = 12
    NFT_META_RTCLASSID: int = 13
    NFT_META_SECMARK: int = 14
    NFT_META_NFPROTO: int = 15
    NFT_META_L4PROTO: int = 16
    NFT_META_BRI_IIFNAME: int = 17
    NFT_META_BRI_OIFNAME: int = 18
    NFT_META_PKTTYPE: int = 19
    NFT_META_CPU: int = 20
    NFT_META_IIFGROUP: int = 21
    NFT_META_OIFGROUP: int = 22
    NFT_META_CGROUP: int = 23
    NFT_META_PRANDOM: int = 24
    NFT_META_SECPATH: int = 25
    NFT_META_IIFKIND: int = 26
    NFT_META_OIFKIND: int = 27
    NFT_META_BRI_IIFPVID: int = 28
    NFT_META_BRI_IIFVPROTO: int = 29
    NFT_META_TIME_NS: int = 30
    NFT_META_TIME_DAY: int = 31
    NFT_META_TIME_HOUR: int = 32
    NFT_META_SDIF: int = 33
    NFT_META_SDIFNAME: int = 34


class Regs(enum.IntEnum):
    NFT_REG_VERDICT: int = 0x00
    NFT_REG_1: int = 0x01
    NFT_REG_2: int = 0x02
    NFT_REG_3: int = 0x03
    NFT_REG_4: int = 0x04
    NFT_REG32_00: int = 0x08
    NFT_REG32_01: int = 0x09
    NFT_REG32_02: int = 0x0A
    NFT_REG32_03: int = 0x0B
    NFT_REG32_04: int = 0x0C
    NFT_REG32_05: int = 0x0D
    NFT_REG32_06: int = 0x0E
    NFT_REG32_07: int = 0x0F
    NFT_REG32_08: int = 0x10
    NFT_REG32_09: int = 0x11
    NFT_REG32_10: int = 0x12
    NFT_REG32_11: int = 0x13
    NFT_REG32_12: int = 0x14
    NFT_REG32_13: int = 0x15
    NFT_REG32_14: int = 0x16
    NFT_REG32_15: int = 0x17


class nftnl_udata(nla_base_string):
    # TLV structures:
    # nftnl_udata
    #  <-------- HEADER --------> <------ PAYLOAD ------>
    # +------------+-------------+- - - - - - - - - - - -+
    # |    type    |     len     |         value         |
    # |  (1 byte)  |   (1 byte)  |                       |
    # +--------------------------+- - - - - - - - - - - -+
    #  <-- sizeof(nftnl_udata) -> <-- nftnl_udata->len -->
    __slots__ = ()

    @classmethod
    def udata_decode(cls, data):
        offset = 0
        result = []
        while offset + 2 < len(data):
            utype = data[offset]
            ulen = data[offset + 1]
            offset += 2
            if offset + ulen > len(data):
                return None  # bad decode
            try:
                type_name = cls.udata_types[utype]
            except IndexError:
                return None  # bad decode

            value = data[offset : offset + ulen]
            if type_name.endswith("_COMMENT") and value[-1] == 0:
                value = value[:-1]  # remove \x00 c *str
            result.append((type_name, value))
            offset += ulen
        return result

    @classmethod
    def udata_encode(cls, values):
        value = b""
        for type_name, udata in values:
            if isinstance(udata, str):
                udata = udata.encode()
            if type_name.endswith("_COMMENT") and udata[-1] != 0:
                udata = udata + b"\x00"
            utype = cls.udata_types.index(type_name)
            value += struct.pack("BB", utype, len(udata)) + udata
        return value

    def decode(self):
        nla_base_string.decode(self)
        value = self.udata_decode(self['value'])
        if value is not None:
            self.value = value

    def encode(self):
        if not isinstance(self.value, (bytes, str)):
            self['value'] = self.udata_encode(self.value)
        nla_base_string.encode(self)


class nft_map_uint8(nla):
    ops = {}
    fields = [('value', 'B')]

    def decode(self):
        nla.decode(self)
        self.value = self.ops.get(self['value'])


class nft_map_be32(nft_map_uint8):
    fields = [('value', '>I')]


class nft_map_be32_signed(nft_map_uint8):
    fields = [('value', '>i')]


class nft_flags_be32(nla):
    fields = [('value', '>I')]
    ops = None

    def decode(self):
        nla.decode(self)
        self.value = frozenset(
            o for i, o in enumerate(self.ops) if self['value'] & 1 << i
        )

    def encode(self):
        value = 0
        for i, name in enumerate(self.ops):
            if name in self.value:
                value |= 1 << i
        self["value"] = value
        nla.encode(self)


class nft_flags_be16(nla):
    fields = [('value', '>H')]
    ops = None

    def decode(self):
        nla.decode(self)
        self.value = frozenset(
            o for i, o in enumerate(self.ops) if self['value'] & 1 << i
        )


class nft_device(nla):
    class device_attributes(nla):
        nla_map = (
            ('NFTA_DEVICE_UNSPEC', 'none'),
            ('NFTA_DEVICE_NAME', 'asciiz'),
        )


class nft_gen_msg(nfgen_msg):
    nla_map = (
        ('NFTA_GEN_UNSPEC', 'none'),
        ('NFTA_GEN_ID', 'be32'),
        ('NFTA_GEN_PROC_PID', 'be32'),
        ('NFTA_GEN_PROC_NAME', 'asciiz'),
    )


class CData:
    @staticmethod
    def decode_from(data, offset=0):
        (length,) = struct.unpack_from('>H', data, offset)
        (data,) = struct.unpack_from(f'{length - 1}s', data, offset + 2)
        return (data.decode('utf-8'), offset + 2 + length)

    @staticmethod
    def encode_into(data, offset, value):
        encoded = value.encode('utf-8')
        length = len(encoded) + 1
        data.extend([0] * (length + 2))
        struct.pack_into(f'>H{length}s', data, offset, length, encoded)
        return offset + length + 2


class nft_has_comment:

    class comment(nla):
        fields = [('value', CData)]


class nft_chain_msg(nfgen_msg, nft_has_comment):
    prefix = 'NFTA_CHAIN_'
    nla_map = (
        ('NFTA_CHAIN_UNSPEC', 'none'),
        ('NFTA_CHAIN_TABLE', 'asciiz'),
        ('NFTA_CHAIN_HANDLE', 'be64'),
        ('NFTA_CHAIN_NAME', 'asciiz'),
        ('NFTA_CHAIN_HOOK', 'hook'),
        ('NFTA_CHAIN_POLICY', 'be32'),
        ('NFTA_CHAIN_USE', 'be32'),
        ('NFTA_CHAIN_TYPE', 'asciiz'),
        ('NFTA_CHAIN_COUNTERS', 'counters'),
        ('NFTA_CHAIN_PAD', 'hex'),
        ('NFTA_CHAIN_FLAGS', 'flags'),
        ('NFTA_CHAIN_ID', 'be32'),
        ('NFTA_CHAIN_USERDATA', 'comment'),
    )

    class counters(nla):
        nla_map = (
            ('NFTA_COUNTER_UNSPEC', 'none'),
            ('NFTA_COUNTER_BYTES', 'be64'),
            ('NFTA_COUNTER_PACKETS', 'be64'),
        )

    class hook(nft_device):
        nla_map = (
            ('NFTA_HOOK_UNSPEC', 'none'),
            ('NFTA_HOOK_HOOKNUM', 'be32'),
            ('NFTA_HOOK_PRIORITY', 'sbe32'),
            ('NFTA_HOOK_DEV', 'asciiz'),
            ('NFTA_HOOK_DEVS', 'device_attributes'),
        )

    class flags(nft_flags_be32):
        ops = ('NFT_CHAIN_HW_OFFLOAD', 'NFT_CHAIN_BINDING')


class nat_flags(nla):
    class nat_range(nft_flags_be32):
        ops = (
            'NF_NAT_RANGE_MAP_IPS',
            'NF_NAT_RANGE_PROTO_SPECIFIED',
            'NF_NAT_RANGE_PROTO_RANDOM',
            'NF_NAT_RANGE_PERSISTENT',
            'NF_NAT_RANGE_PROTO_RANDOM_FULLY',
            'NF_NAT_RANGE_PROTO_OFFSET',
            'NF_NAT_RANGE_NETMAP',
        )


class nft_regs(nla):
    class regs(nft_map_be32):
        ops = {x.value: x.name for x in Regs}


class nft_data(nla):
    class nfta_data(nla):
        nla_map = (
            ('NFTA_DATA_UNSPEC', 'none'),
            ('NFTA_DATA_VALUE', 'cdata'),
            ('NFTA_DATA_VERDICT', 'verdict'),
        )

        class verdict(nla):
            nla_map = (
                ('NFTA_VERDICT_UNSPEC', 'none'),
                ('NFTA_VERDICT_CODE', 'verdict_code'),
                ('NFTA_VERDICT_CHAIN', 'asciiz'),
            )

            class verdict_code(nft_map_be32_signed):
                ops = {
                    0: 'NF_DROP',
                    1: 'NF_ACCEPT',
                    2: 'NF_STOLEN',
                    3: 'NF_QUEUE',
                    4: 'NF_REPEAT',
                    5: 'NF_STOP',
                    -1: 'NFT_CONTINUE',
                    -2: 'NFT_BREAK',
                    -3: 'NFT_JUMP',
                    -4: 'NFT_GOTO',
                    -5: 'NFT_RETURN',
                }


# nft_expr struct, used for rules and set
class nft_contains_expr:
    class nft_expr(nla):
        header_type = 1

        nla_map = (
            ('NFTA_EXPR_UNSPEC', 'none'),
            ('NFTA_EXPR_NAME', 'asciiz'),
            ('NFTA_EXPR_DATA', 'expr'),
        )

        class nft_bitwise(nft_data, nft_regs):
            nla_map = (
                ('NFTA_BITWISE_UNSPEC', 'none'),
                ('NFTA_BITWISE_SREG', 'regs'),
                ('NFTA_BITWISE_DREG', 'regs'),
                ('NFTA_BITWISE_LEN', 'be32'),
                ('NFTA_BITWISE_MASK', 'nfta_data'),
                ('NFTA_BITWISE_XOR', 'nfta_data'),
                ('NFTA_BITWISE_OP', 'bitwise_op'),
                ('NFTA_BITWISE_DATA', 'nfta_data'),
            )

            class bitwise_op(nft_map_be32):
                ops = {
                    0: 'NFT_BITWISE_BOOL',
                    1: 'NFT_BITWISE_LSHIFT',
                    2: 'NFT_BITWISE_RSHIFT',
                }

        class nft_byteorder(nft_regs):
            nla_map = (
                ('NFTA_BYTEORDER_UNSPEC', 'none'),
                ('NFTA_BYTEORDER_SREG', 'regs'),
                ('NFTA_BYTEORDER_DREG', 'regs'),
                ('NFTA_BYTEORDER_OP', 'ops'),
                ('NFTA_BYTEORDER_LEN', 'be32'),
                ('NFTA_BYTEORDER_SIZE', 'be32'),
            )

            class ops(nft_map_be32):
                ops = {0: 'NFT_BYTEORDER_NTOH', 1: 'NFT_BYTEORDER_HTON'}

        class nft_cmp(nft_data, nft_regs):
            nla_map = (
                ('NFTA_CMP_UNSPEC', 'none'),
                ('NFTA_CMP_SREG', 'regs'),
                ('NFTA_CMP_OP', 'ops'),
                ('NFTA_CMP_DATA', 'nfta_data'),
            )

            class ops(nft_map_be32):
                ops = {x.value: x.name for x in Cmp}

        class nft_match(nla):
            nla_map = (
                ('NFTA_MATCH_UNSPEC', 'none'),
                ('NFTA_MATCH_NAME', 'asciiz'),
                ('NFTA_MATCH_REV', 'be32'),
                ('NFTA_MATCH_INFO', 'hex'),
                ('NFTA_MATCH_PROTOCOL', 'hex'),
                ('NFTA_MATCH_FLAGS', 'hex'),
            )

        class nft_target(nla):
            nla_map = (
                ('NFTA_TARGET_UNSPEC', 'none'),
                ('NFTA_TARGET_NAME', 'asciiz'),
                ('NFTA_TARGET_REV', 'be32'),
                ('NFTA_TARGET_INFO', 'hex'),
                ('NFTA_TARGET_PROTOCOL', 'hex'),
                ('NFTA_TARGET_FLAGS', 'hex'),
            )

        class nft_connlimit(nla):
            nla_map = (
                ('NFTA_CONNLIMIT_UNSPEC', 'none'),
                ('NFTA_CONNLIMIT_COUNT', 'be32'),
                ('NFTA_CONNLIMIT_FLAGS', 'connlimit_flags'),
            )

            class connlimit_flags(nft_flags_be32):
                ops = ('NFT_LIMIT_F_INV',)

        class nft_counter(nla):
            nla_map = (
                ('NFTA_COUNTER_UNSPEC', 'none'),
                ('NFTA_COUNTER_BYTES', 'be64'),
                ('NFTA_COUNTER_PACKETS', 'be64'),
            )

        class nft_ct(nft_regs):
            nla_map = (
                ('NFTA_CT_UNSPEC', 'none'),
                ('NFTA_CT_DREG', 'regs'),
                ('NFTA_CT_KEY', 'keys'),
                ('NFTA_CT_DIRECTION', 'uint8'),
                ('NFTA_CT_SREG', 'regs'),
            )

            class keys(nft_map_be32):
                ops = {
                    0x00: 'NFT_CT_STATE',
                    0x01: 'NFT_CT_DIRECTION',
                    0x02: 'NFT_CT_STATUS',
                    0x03: 'NFT_CT_MARK',
                    0x04: 'NFT_CT_SECMARK',
                    0x05: 'NFT_CT_EXPIRATION',
                    0x06: 'NFT_CT_HELPER',
                    0x07: 'NFT_CT_L3PROTOCOL',
                    0x08: 'NFT_CT_SRC',
                    0x09: 'NFT_CT_DST',
                    0x0A: 'NFT_CT_PROTOCOL',
                    0x0B: 'NFT_CT_PROTO_SRC',
                    0x0C: 'NFT_CT_PROTO_DST',
                    0x0D: 'NFT_CT_LABELS',
                    0x0E: 'NFT_CT_PKTS',
                    0x0F: 'NFT_CT_BYTES',
                    0x10: 'NFT_CT_AVGPKT',
                    0x11: 'NFT_CT_ZONE',
                    0x12: 'NFT_CT_EVENTMASK',
                    0x13: 'NFT_CT_SRC_IP',
                    0x14: 'NFT_CT_DST_IP',
                    0x15: 'NFT_CT_SRC_IP6',
                    0x16: 'NFT_CT_DST_IP6',
                    0x17: 'NFT_CT_ID',
                }

        class nft_dup(nft_regs):
            nla_map = (
                ('NFTA_DUP_UNSPEC', 'none'),
                ('NFTA_DUP_SREG_ADDR', 'regs'),
                ('NFTA_DUP_SREG_DEV', 'regs'),
            )

        class nft_exthdr(nft_regs):
            nla_map = (
                ('NFTA_EXTHDR_UNSPEC', 'none'),
                ('NFTA_EXTHDR_DREG', 'regs'),
                ('NFTA_EXTHDR_TYPE', 'uint8'),
                ('NFTA_EXTHDR_OFFSET', 'be32'),
                ('NFTA_EXTHDR_LEN', 'be32'),
                ('NFTA_EXTHDR_FLAGS', 'exthdr_flags'),
                ('NFTA_EXTHDR_OP', 'exthdr_op'),
                ('NFTA_EXTHDR_SREG', 'regs'),
            )

            class exthdr_flags(nft_flags_be32):
                ops = ('NFT_EXTHDR_F_PRESENT',)

            class exthdr_op(nft_map_be32):
                ops = {
                    0: 'NFT_EXTHDR_OP_IPV6',
                    1: 'NFT_EXTHDR_OP_TCPOPT',
                    2: 'NFT_EXTHDR_OP_IPV4',
                }

        class nft_fib(nft_regs):
            nla_map = (
                ('NFTA_FIB_UNSPEC', 'none'),
                ('NFTA_FIB_DREG', 'regs'),
                ('NFTA_FIB_RESULT', 'fib_result'),
                ('NFTA_FIB_FLAGS', 'fib_flags'),
            )

            class fib_result(nft_flags_be32):
                ops = (
                    'NFT_FIB_RESULT_UNSPEC',
                    'NFT_FIB_RESULT_OIF',
                    'NFT_FIB_RESULT_OIFNAME',
                    'NFT_FIB_RESULT_ADDRTYPE',
                )

            class fib_flags(nft_map_be32):
                ops = {
                    0: 'NFTA_FIB_F_SADDR',
                    1: 'NFTA_FIB_F_DADDR',
                    2: 'NFTA_FIB_F_MARK',
                    3: 'NFTA_FIB_F_IIF',
                    4: 'NFTA_FIB_F_OIF',
                    5: 'NFTA_FIB_F_PRESENT',
                }

        class nft_fwd(nft_regs):
            nla_map = (
                ('NFTA_FWD_UNSPEC', 'none'),
                ('NFTA_FWD_SREG_DEV', 'regs'),
                ('NFTA_FWD_SREG_ADDR', 'regs'),
                ('NFTA_FWD_NFPROTO', 'u32'),
            )

        class nft_hash(nft_regs):
            nla_map = (
                ('NFTA_HASH_UNSPEC', 'none'),
                ('NFTA_HASH_SREG', 'regs'),
                ('NFTA_HASH_DREG', 'regs'),
                ('NFTA_HASH_LEN', 'be32'),
                ('NFTA_HASH_MODULUS', 'be32'),
                ('NFTA_HASH_SEED', 'be32'),
                ('NFTA_HASH_OFFSET', 'be32'),
                ('NFTA_HASH_TYPE', 'hash_type'),
                ('NFTA_HASH_SET_NAME', 'asciiz'),
                ('NFTA_HASH_SET_ID', 'be32'),
            )

            class hash_type(nft_map_be32):
                ops = {0: 'NFT_HASH_JENKINS', 1: 'NFT_HASH_SYM'}

        class nft_immediate(nft_data, nft_regs):
            nla_map = (
                ('NFTA_IMMEDIATE_UNSPEC', 'none'),
                ('NFTA_IMMEDIATE_DREG', 'regs'),
                ('NFTA_IMMEDIATE_DATA', 'nfta_data'),
            )

        class nft_limit(nla):
            nla_map = (
                ('NFTA_LIMIT_UNSPEC', 'none'),
                ('NFTA_LIMIT_RATE', 'be64'),
                ('NFTA_LIMIT_UNIT', 'be64'),
                ('NFTA_LIMIT_BURST', 'be32'),
                ('NFTA_LIMIT_TYPE', 'types'),
                ('NFTA_LIMIT_FLAGS', 'be32'),
            )  # make flags type

            class types(nft_map_be32):
                ops = {0: 'NFT_LIMIT_PKTS', 1: 'NFT_LIMIT_PKT_BYTES'}

        class nft_log(nla):
            nla_map = (
                ('NFTA_LOG_UNSPEC', 'none'),
                ('NFTA_LOG_GROUP', 'be32'),
                ('NFTA_LOG_PREFIX', 'asciiz'),
                ('NFTA_LOG_SNAPLEN', 'be32'),
                ('NFTA_LOG_QTHRESHOLD', 'be32'),
                ('NFTA_LOG_LEVEL', 'log_level'),
                ('NFTA_LOG_FLAGS', 'log_flags'),
            )

            class log_level(nft_map_be32):
                ops = {
                    0: 'NFT_LOGLEVEL_EMERG',
                    1: 'NFT_LOGLEVEL_ALERT',
                    2: 'NFT_LOGLEVEL_CRIT',
                    3: 'NFT_LOGLEVEL_ERR',
                    4: 'NFT_LOGLEVEL_WARNING',
                    5: 'NFT_LOGLEVEL_NOTICE',
                    6: 'NFT_LOGLEVEL_INFO',
                    7: 'NFT_LOGLEVEL_DEBUG',
                    8: 'NFT_LOGLEVEL_AUDIT',
                }

            class log_flags(nft_flags_be32):
                ops = (
                    'NF_LOG_TCPSEQ',
                    'NF_LOG_TCPOPT',
                    'NF_LOG_IPOPT',
                    'NF_LOG_UID',
                    'NF_LOG_NFLOG',
                    'NF_LOG_MACDECODE',
                )

        class nft_lookup(nft_regs):
            nla_map = (
                ('NFTA_LOOKUP_UNSPEC', 'none'),
                ('NFTA_LOOKUP_SET', 'asciiz'),
                ('NFTA_LOOKUP_SREG', 'regs'),
                ('NFTA_LOOKUP_DREG', 'regs'),
                ('NFTA_LOOKUP_SET_ID', 'be32'),
                ('NFTA_LOOKUP_FLAGS', 'lookup_flags'),
            )

            class lookup_flags(nft_flags_be32):
                ops = ('NFT_LOOKUP_F_INV',)

        class nft_masq(nft_regs, nat_flags):
            nla_map = (
                ('NFTA_MASQ_UNSPEC', 'none'),
                ('NFTA_MASQ_FLAGS', 'nat_range'),
                ('NFTA_MASQ_REG_PROTO_MIN', 'regs'),
                ('NFTA_MASQ_REG_PROTO_MAX', 'regs'),
            )

        class nft_meta(nft_regs):
            nla_map = (
                ('NFTA_META_UNSPEC', 'none'),
                ('NFTA_META_DREG', 'regs'),
                ('NFTA_META_KEY', 'meta_key'),
                ('NFTA_META_SREG', 'regs'),
            )

            class meta_key(nft_map_be32):
                ops = {x.value: x.name for x in Meta}

        class nft_nat(nft_regs, nat_flags):
            nla_map = (
                ('NFTA_NAT_UNSPEC', 'none'),
                ('NFTA_NAT_TYPE', 'types'),
                ('NFTA_NAT_FAMILY', 'be32'),
                ('NFTA_NAT_REG_ADDR_MIN', 'regs'),
                ('NFTA_NAT_REG_ADDR_MAX', 'regs'),
                ('NFTA_NAT_REG_PROTO_MIN', 'regs'),
                ('NFTA_NAT_REG_PROTO_MAX', 'regs'),
                ('NFTA_NAT_FLAGS', 'nat_range'),
            )

            class types(nft_map_be32):
                ops = {0: 'NFT_NAT_SNAT', 1: 'NFT_NAT_DNAT'}

        class nft_numgen(nft_regs):
            nla_map = (
                ('NFTA_NG_UNSPEC', 'none'),
                ('NFTA_NG_DREG', 'regs'),
                ('NFTA_NG_MODULUS', 'be32'),
                ('NFTA_NG_TYPE', 'types'),
                ('NFTA_NG_OFFSET', 'be32'),
                ('NFTA_NG_SET_NAME', 'asciiz'),
                ('NFTA_NG_SET_ID', 'be32'),
            )

            class types(nft_map_be32):
                ops = {0: 'NFT_NG_INCREMENTAL', 1: 'NFT_NG_RANDOM'}

        class nft_objref(nft_regs):
            nla_map = (
                ('NFTA_OBJREF_UNSPEC', 'none'),
                ('NFTA_OBJREF_IMM_TYPE', 'regs'),
                ('NFTA_OBJREF_IMM_NAME', 'asciiz'),
                ('NFTA_OBJREF_SET_SREG', 'regs'),
                ('NFTA_OBJREF_SET_NAME', 'asciiz'),
                ('NFTA_OBJREF_SET_ID', 'be32'),
            )

        class nft_offload(nla):
            nla_map = (
                ('NFTA_FLOW_UNSPEC', 'none'),
                ('NFTA_FLOW_TABLE_NAME', 'asciiz'),
            )

        class nft_osf(nft_regs):
            nla_map = (
                ('NFTA_OSF_UNSPEC', 'none'),
                ('NFTA_OSF_DREG', 'regs'),
                ('NFTA_OSF_TTL', 'uint8'),
                ('NFTA_OSF_FLAGS', 'osf_flags'),
            )

            class osf_flags(nft_flags_be32):
                ops = ('NFT_OSF_F_VERSION',)

        class nft_payload(nft_regs):
            nla_map = (
                ('NFTA_PAYLOAD_UNSPEC', 'none'),
                ('NFTA_PAYLOAD_DREG', 'regs'),
                ('NFTA_PAYLOAD_BASE', 'base_type'),
                ('NFTA_PAYLOAD_OFFSET', 'be32'),
                ('NFTA_PAYLOAD_LEN', 'be32'),
                ('NFTA_PAYLOAD_SREG', 'regs'),
                ('NFTA_PAYLOAD_CSUM_TYPE', 'csum_type'),
                ('NFTA_PAYLOAD_CSUM_OFFSET', 'be32'),
                ('NFTA_PAYLOAD_CSUM_FLAGS', 'csum_flags'),
            )

            class base_type(nft_map_be32):
                ops = {
                    0: 'NFT_PAYLOAD_LL_HEADER',
                    1: 'NFT_PAYLOAD_NETWORK_HEADER',
                    2: 'NFT_PAYLOAD_TRANSPORT_HEADER',
                }

            class csum_type(nft_map_be32):
                ops = {
                    0: 'NFT_PAYLOAD_CSUM_NONE',
                    1: 'NFT_PAYLOAD_CSUM_INET',  # RFC 791
                    2: 'NFT_PAYLOAD_CSUM_SCTP',
                }  # RFC 3309

            class csum_flags(nft_flags_be32):
                ops = ('NFT_PAYLOAD_L4CSUM_PSEUDOHDR',)

        class nft_queue(nft_regs):
            nla_map = (
                ('NFTA_QUEUE_UNSPEC', 'none'),
                ('NFTA_QUEUE_NUM', 'be16'),
                ('NFTA_QUEUE_TOTAL', 'be16'),
                ('NFTA_QUEUE_FLAGS', 'queue_flags'),
                ('NFTA_QUEUE_SREG_QNUM', 'regs'),
            )

            class queue_flags(nft_flags_be16):
                ops = ('NFT_QUEUE_FLAG_BYPASS', 'NFT_QUEUE_FLAG_CPU_FANOUT')

        class nft_quota(nla):
            nla_map = (
                ('NFTA_QUOTA_UNSPEC', 'none'),
                ('NFTA_QUOTA_BYTES', 'be16'),
                ('NFTA_QUOTA_FLAGS', 'quota_flags'),
                ('NFTA_QUOTA_PAD', 'hex'),
                ('NFTA_QUOTA_CONSUMED', 'be64'),
            )

            class quota_flags(nft_flags_be32):
                ops = ('NFT_QUOTA_F_INV', 'NFT_QUOTA_F_DEPLETED')

        class nft_range(nft_regs, nft_data):
            nla_map = (
                ('NFTA_RANGE_UNSPEC', 'none'),
                ('NFTA_RANGE_SREG', 'regs'),
                ('NFTA_RANGE_OP', 'range_op'),
                ('NFTA_RANGE_FROM_DATA', 'nfta_data'),
                ('NFTA_RANGE_TO_DATA', 'nfta_data'),
            )

            class range_op(nft_map_be32):
                ops = {0: 'NFT_RANGE_EQ', 1: 'NFT_RANGE_NEQ'}

        class nft_redir(nft_regs, nat_flags):
            nla_map = (
                ('NFTA_REDIR_UNSPEC', 'none'),
                ('NFTA_REDIR_REG_PROTO_MIN', 'regs'),
                ('NFTA_REDIR_REG_PROTO_MAX', 'regs'),
                ('NFTA_REDIR_FLAGS', 'nat_range'),
            )

        class nft_reject(nla):
            nla_map = (
                ('NFTA_REJECT_UNSPEC', 'none'),
                ('NFTA_REJECT_TYPE', 'types'),
                ('NFTA_REJECT_ICMP_CODE', 'codes'),
            )

            class types(nft_map_be32):
                ops = {
                    0: 'NFT_REJECT_ICMP_UNREACH',
                    1: 'NFT_REJECT_TCP_RST',
                    2: 'NFT_REJECT_ICMPX_UNREACH',
                }

            class codes(nft_map_uint8):
                ops = {
                    0: 'NFT_REJECT_ICMPX_NO_ROUTE',
                    1: 'NFT_REJECT_ICMPX_PORT_UNREACH',
                    2: 'NFT_REJECT_ICMPX_HOST_UNREACH',
                    3: 'NFT_REJECT_ICMPX_ADMIN_PROHIBITED',
                }

        class nft_rt(nft_regs):
            nla_map = (
                ('NFTA_RT_UNSPEC', 'none'),
                ('NFTA_RT_DREG', 'regs'),
                ('NFTA_RT_KEY', 'rt_keys'),
            )

            class rt_keys(nft_map_be32):
                ops = {
                    0: 'NFT_RT_CLASSID',
                    1: 'NFT_RT_NEXTHOP4',
                    2: 'NFT_RT_NEXTHOP6',
                    3: 'NFT_RT_TCPMSS',
                    4: 'NFT_RT_XFRM',
                }

        class nft_secmark(nla):
            nla_map = (
                ('NFTA_SECMARK_UNSPEC', 'none'),
                ('NFTA_SECMARK_CTX', 'asciiz'),
            )

        class nft_socket(nft_regs):
            nla_map = (
                ('NFTA_SOCKET_UNSPEC', 'none'),
                ('NFTA_SOCKET_KEY', 'socket_keys'),
                ('NFTA_SOCKET_DREG', 'regs'),
            )

            class socket_keys(nft_map_be32):
                ops = {
                    0: 'NFT_SOCKET_TRANSPARENT',
                    1: 'NFT_SOCKET_MARK',
                    2: 'NFT_SOCKET_WILDCARD',
                }

        class nft_synproxy(nla):
            nla_map = (
                ('NFTA_SYNPROXY_UNSPEC', 'none'),
                ('NFTA_SYNPROXY_MSS', 'u16'),
                ('NFTA_SYNPROXY_WSCALE', 'uint8'),
                ('NFTA_SYNPROXY_FLAGS', 'synproxy_flags'),
            )

            class synproxy_flags(nft_flags_be32):
                ops = (
                    'NF_SYNPROXY_OPT_MSS',
                    'NF_SYNPROXY_OPT_WSCALE',
                    'NF_SYNPROXY_OPT_SACK_PERM',
                    'NF_SYNPROXY_OPT_TIMESTAMP',
                    'NF_SYNPROXY_OPT_ECN',
                )

        class nft_tproxy(nft_regs):
            nla_map = (
                ('NFTA_TPROXY_UNSPEC', 'none'),
                ('NFTA_TPROXY_FAMILY', 'regs'),
                ('NFTA_TPROXY_REG_ADDR', 'regs'),
                ('NFTA_TPROXY_REG_PORT', 'regs'),
            )

        class nft_dynset(nft_regs):
            rule_expr = None
            nla_map = (
                ('NFTA_DYNSET_UNSPEC', 'none'),
                ('NFTA_DYNSET_SET_NAME', 'asciiz'),
                ('NFTA_DYNSET_SET_ID', 'be32'),
                ('NFTA_DYNSET_OP', 'dynset_op'),
                ('NFTA_DYNSET_SREG_KEY', 'regs'),
                ('NFTA_DYNSET_SREG_DATA', 'regs'),
                ('NFTA_DYNSET_TIMEOUT', 'be64'),
                ('NFTA_DYNSET_EXPR', 'rule_expr'),
                ('NFTA_DYNSET_PAD', 'hex'),
                ('NFTA_DYNSET_FLAGS', 'dynset_flags'),
            )

            class dynset_flags(nft_flags_be32):
                ops = ('NFT_DYNSET_F_INV',)

            class dynset_op(nft_map_be32):
                ops = {
                    0: 'NFT_DYNSET_OP_ADD',
                    1: 'NFT_DYNSET_OP_UPDATE',
                    2: 'NFT_DYNSET_OP_DELETE',
                }

        class nft_xfrm(nft_regs):
            nla_map = (
                ('NFTA_XFRM_UNSPEC', 'none'),
                ('NFTA_XFRM_DREG', 'regs'),
                ('NFTA_XFRM_KEY', 'xfrm_key'),
                ('NFTA_XFRM_DIR', 'uint8'),
                ('NFTA_XFRM_SPNUM', 'be32'),
            )

            class xfrm_key(nft_map_be32):
                ops = {
                    0: 'NFT_XFRM_KEY_UNSPEC',
                    1: 'NFT_XFRM_KEY_DADDR_IP4',
                    2: 'NFT_XFRM_KEY_DADDR_IP6',
                    3: 'NFT_XFRM_KEY_SADDR_IP4',
                    4: 'NFT_XFRM_KEY_SADDR_IP6',
                    5: 'NFT_XFRM_KEY_REQID',
                    6: 'NFT_XFRM_KEY_SPI',
                }

        @staticmethod
        def expr(self, *argv, **kwarg):
            data_type = self.get_attr('NFTA_EXPR_NAME')
            expr = getattr(self, 'nft_%s' % data_type, self.hex)
            if hasattr(expr, 'rule_expr'):
                expr.rule_expr = self.__class__
            return expr


class nft_rule_msg(nfgen_msg, nft_contains_expr, nft_has_comment):
    prefix = 'NFTA_RULE_'
    nla_map = (
        ('NFTA_RULE_UNSPEC', 'none'),
        ('NFTA_RULE_TABLE', 'asciiz'),
        ('NFTA_RULE_CHAIN', 'asciiz'),
        ('NFTA_RULE_HANDLE', 'be64'),
        ('NFTA_RULE_EXPRESSIONS', '*nft_expr'),
        ('NFTA_RULE_COMPAT', 'hex'),
        ('NFTA_RULE_POSITION', 'be64'),
        ('NFTA_RULE_USERDATA', 'comment'),
        ('NFTA_RULE_PAD', 'hex'),
        ('NFTA_RULE_ID', 'be32'),
        ('NFTA_RULE_POSITION_ID', 'be32'),
        ('NFTA_RULE_CHAIN_ID', 'be32'),
    )


class nft_set_msg(nfgen_msg, nft_contains_expr):
    prefix = 'NFTA_SET_'
    nla_map = (
        ('NFTA_SET_UNSPEC', 'none'),
        ('NFTA_SET_TABLE', 'asciiz'),
        ('NFTA_SET_NAME', 'asciiz'),
        ('NFTA_SET_FLAGS', 'set_flags'),
        ('NFTA_SET_KEY_TYPE', 'be32'),
        ('NFTA_SET_KEY_LEN', 'be32'),
        ('NFTA_SET_DATA_TYPE', 'be32'),
        ('NFTA_SET_DATA_LEN', 'be32'),
        ('NFTA_SET_POLICY', 'set_policy'),
        ('NFTA_SET_DESC', 'set_desc'),
        ('NFTA_SET_ID', 'be32'),
        ('NFTA_SET_TIMEOUT', 'be64'),
        ('NFTA_SET_GC_INTERVAL', 'be32'),
        ('NFTA_SET_USERDATA', 'set_udata'),
        ('NFTA_SET_PAD', 'hex'),
        ('NFTA_SET_OBJ_TYPE', 'be32'),
        ('NFTA_SET_HANDLE', 'be64'),
        ('NFTA_SET_EXPR', 'nft_expr'),
        ('NFTA_SET_EXPRESSIONS', '*nft_expr'),
    )

    class set_udata(nftnl_udata):
        udata_types = (
            "NFTNL_UDATA_SET_KEYBYTEORDER",
            "NFTNL_UDATA_SET_DATABYTEORDER",
            "NFTNL_UDATA_SET_MERGE_ELEMENTS",
            "NFTNL_UDATA_SET_KEY_TYPEOF",
            "NFTNL_UDATA_SET_DATA_TYPEOF",
            "NFTNL_UDATA_SET_EXPR",
            "NFTNL_UDATA_SET_DATA_INTERVAL",
            "NFTNL_UDATA_SET_COMMENT",
        )

    class set_flags(nft_flags_be32):
        ops = (
            'NFT_SET_ANONYMOUS',
            'NFT_SET_CONSTANT',
            'NFT_SET_INTERVAL',
            'NFT_SET_MAP',
            'NFT_SET_TIMEOUT',
            'NFT_SET_EVAL',
            'NFT_SET_OBJECT',
            'NFT_SET_CONCAT',
        )

    class set_policy(nft_map_be32):
        ops = {0: 'NFT_SET_POL_PERFORMANCE', 1: 'NFT_SET_POL_MEMORY'}

    class set_desc(nla):
        nla_map = (
            ('NFTA_SET_DESC_UNSPEC', 'none'),
            ('NFTA_SET_DESC_SIZE', 'be32'),
            ('NFTA_SET_DESC_CONCAT', '*list_elem'),
        )

        class list_elem(nla):
            nla_map = (
                ('NFTA_LIST_UNSPEC', 'none'),
                ('NFTA_LIST_ELEM', '*set_field_attribute'),
            )

            class set_field_attribute(nla):
                nla_map = (
                    ('NFTA_SET_FIELD_UNSPEC', 'none'),
                    ('NFTA_SET_FIELD_LEN', 'be32'),
                )


class nft_table_msg(nfgen_msg, nft_contains_expr, nft_has_comment):
    prefix = 'NFTA_TABLE_'
    nla_map = (
        ('NFTA_TABLE_UNSPEC', 'none'),
        ('NFTA_TABLE_NAME', 'asciiz'),
        ('NFTA_TABLE_FLAGS', 'be32'),
        ('NFTA_TABLE_USE', 'be32'),
        ('NFTA_TABLE_HANDLE', 'be64'),
        ('NFTA_TABLE_PAD', 'hex'),
        ('NFTA_TABLE_USERDATA', 'comment'),
    )


class nft_set_elem_list_msg(nfgen_msg):
    prefix = 'NFTA_SET_ELEM_LIST_'
    nla_map = (
        ('NFTA_SET_ELEM_LIST_UNSPEC', 'none'),
        ('NFTA_SET_ELEM_LIST_TABLE', 'asciiz'),
        ('NFTA_SET_ELEM_LIST_SET', 'asciiz'),
        ('NFTA_SET_ELEM_LIST_ELEMENTS', '*set_elem'),
        ('NFTA_SET_ELEM_LIST_SET_ID', 'be32'),
    )

    class set_elem(nla, nft_contains_expr):
        nla_map = (
            ('NFTA_SET_ELEM_UNSPEC', 'none'),
            ('NFTA_SET_ELEM_KEY', 'data_attributes'),
            ('NFTA_SET_ELEM_DATA', 'data_attributes'),
            ('NFTA_SET_ELEM_FLAGS', 'set_elem_flags'),
            ('NFTA_SET_ELEM_TIMEOUT', 'be64'),
            ('NFTA_SET_ELEM_EXPIRATION', 'be64'),
            ('NFTA_SET_ELEM_USERDATA', 'set_elem_udata'),
            ('NFTA_SET_ELEM_EXPR', 'nft_expr'),
            ('NFTA_SET_ELEM_PAD', 'hex'),
            ('NFTA_SET_ELEM_OBJREF', 'asciiz'),
            ('NFTA_SET_ELEM_KEY_END', 'data_attributes'),
            ('NFTA_SET_ELEM_EXPRESSIONS', '*nft_expr'),
        )

        class set_elem_udata(nftnl_udata):
            udata_types = (
                "NFTNL_UDATA_SET_ELEM_COMMENT",
                "NFTNL_UDATA_SET_ELEM_FLAGS",
            )

        class set_elem_flags(nft_flags_be32):
            ops = {1: 'NFT_SET_ELEM_INTERVAL_END'}

        class data_attributes(nla):
            nla_map = (
                ('NFTA_DATA_UNSPEC', 'none'),
                ('NFTA_DATA_VALUE', 'binary'),
                ('NFTA_DATA_VERDICT', 'verdict_attributes'),
            )

            class verdict_attributes(nla):
                nla_map = (
                    ('NFTA_VERDICT_UNSPEC', 'none'),
                    ('NFTA_VERDICT_CODE', 'verdict_code'),
                    ('NFTA_VERDICT_CHAIN', 'asciiz'),
                    ('NFTA_VERDICT_CHAIN_ID', 'be32'),
                )

                class verdict_code(nft_map_be32_signed):
                    ops = {
                        0: 'NF_DROP',
                        1: 'NF_ACCEPT',
                        2: 'NF_STOLEN',
                        3: 'NF_QUEUE',
                        4: 'NF_REPEAT',
                        5: 'NF_STOP',
                        -1: 'NFT_CONTINUE',
                        -2: 'NFT_BREAK',
                        -3: 'NFT_JUMP',
                        -4: 'NFT_GOTO',
                        -5: 'NFT_RETURN',
                    }


class nft_flowtable_msg(nfgen_msg):
    prefix = 'NFTA_FLOWTABLE_'
    nla_map = (
        ('NFTA_FLOWTABLE_UNSPEC', 'none'),
        ('NFTA_FLOWTABLE_TABLE', 'asciiz'),
        ('NFTA_FLOWTABLE_NAME', 'asciiz'),
        ('NFTA_FLOWTABLE_HOOK', 'flowtable_hook'),
        ('NFTA_FLOWTABLE_USE', 'be32'),
        ('NFTA_FLOWTABLE_HANDLE', 'be64'),
        ('NFTA_FLOWTABLE_PAD', 'hex'),
        ('NFTA_FLOWTABLE_FLAGS', 'nft_flowtable_flags'),
    )

    class nft_flowtable_flags(nft_flags_be32):
        ops = ('NFT_FLOWTABLE_HW_OFFLOAD', 'NFT_FLOWTABLE_COUNTER')

    class flowtable_hook(nft_device):
        nla_map = (
            ('NFTA_FLOWTABLE_HOOK_UNSPEC', 'none'),
            ('NFTA_FLOWTABLE_HOOK_NUM', 'be32'),
            ('NFTA_FLOWTABLE_HOOK_PRIORITY', 'be32'),
            ('NFTA_FLOWTABLE_HOOK_DEVS', 'device_attributes'),
        )


class NFTSocket(NetlinkSocket):
    def __init__(self, version=1, attr_revision=0, nfgen_family=2):
        self.asyncore = AsyncNFTSocket(version, attr_revision, nfgen_family)
        self.asyncore.local.keep_event_loop = True
        self.asyncore.event_loop.run_until_complete(
            self.asyncore.setup_endpoint()
        )

    def begin(self):
        return self.asyncore.begin()

    def commit(self):
        return self.asyncore.commit()

    def request_get(
        self,
        msg,
        msg_type,
        msg_flags=NLM_F_REQUEST | NLM_F_DUMP,
        terminate=None,
    ):
        async def collect_data():
            return [
                x
                async for x in await self.asyncore.request_get(
                    msg, msg_type, msg_flags, terminate
                )
            ]

        return self._run_with_cleanup(collect_data)

    def request_put(self, msg, msg_type, msg_flags=NLM_F_REQUEST):
        return self._run_with_cleanup(
            self.asyncore.request_put, msg, msg_type, msg_flags
        )

    def _command(self, msg_class, commands, cmd, kwarg):
        return self._run_with_cleanup(
            self.asyncore._command, msg_class, commands, cmd, kwarg
        )


class AsyncNFTSocket(AsyncNetlinkSocket):
    '''
    NFNetlink socket (family=NETLINK_NETFILTER).

    Implements API to the nftables functionality.
    '''

    policy = {
        NFT_MSG_NEWTABLE: nft_table_msg,
        NFT_MSG_GETTABLE: nft_table_msg,
        NFT_MSG_DELTABLE: nft_table_msg,
        NFT_MSG_NEWCHAIN: nft_chain_msg,
        NFT_MSG_GETCHAIN: nft_chain_msg,
        NFT_MSG_DELCHAIN: nft_chain_msg,
        NFT_MSG_NEWRULE: nft_rule_msg,
        NFT_MSG_GETRULE: nft_rule_msg,
        NFT_MSG_DELRULE: nft_rule_msg,
        NFT_MSG_NEWSET: nft_set_msg,
        NFT_MSG_GETSET: nft_set_msg,
        NFT_MSG_DELSET: nft_set_msg,
        NFT_MSG_NEWGEN: nft_gen_msg,
        NFT_MSG_GETGEN: nft_gen_msg,
        NFT_MSG_NEWSETELEM: nft_set_elem_list_msg,
        NFT_MSG_GETSETELEM: nft_set_elem_list_msg,
        NFT_MSG_DELSETELEM: nft_set_elem_list_msg,
        NFT_MSG_NEWFLOWTABLE: nft_flowtable_msg,
        NFT_MSG_GETFLOWTABLE: nft_flowtable_msg,
        NFT_MSG_DELFLOWTABLE: nft_flowtable_msg,
    }

    def __init__(self, version=1, attr_revision=0, nfgen_family=2):
        super().__init__(family=NETLINK_NETFILTER)
        policy = dict(
            [
                (x | (NFNL_SUBSYS_NFTABLES << 8), y)
                for (x, y) in self.policy.items()
            ]
        )
        self.register_policy(policy)
        self._proto_version = version
        self._attr_revision = attr_revision
        self._nfgen_family = nfgen_family
        self._ts = threading.local()
        self._write_lock = threading.RLock()

    def begin(self):
        with self._write_lock:
            if hasattr(self._ts, 'data'):
                # transaction is already started
                return False

            self._ts.data = b''
            self._ts.seqnum = (
                self.addr_pool.alloc(),  # begin
                self.addr_pool.alloc(),  # tx
                self.addr_pool.alloc(),
            )  # commit
            msg = nfgen_msg()
            msg['res_id'] = NFNL_SUBSYS_NFTABLES
            msg['header']['type'] = 0x10
            msg['header']['flags'] = NLM_F_REQUEST
            msg['header']['sequence_number'] = self._ts.seqnum[0]
            msg.encode()
            self._ts.data += msg.data
            return True

    def commit(self):
        with self._write_lock:
            msg = nfgen_msg()
            msg['res_id'] = NFNL_SUBSYS_NFTABLES
            msg['header']['type'] = 0x11
            msg['header']['flags'] = NLM_F_REQUEST
            msg['header']['sequence_number'] = self._ts.seqnum[2]
            msg.encode()
            self._ts.data += msg.data
            self.sendto(self._ts.data, (0, 0))
            for seqnum in self._ts.seqnum:
                self.addr_pool.free(seqnum, ban=10)
            del self._ts.data

    async def request_get(
        self,
        msg,
        msg_type,
        msg_flags=NLM_F_REQUEST | NLM_F_DUMP,
        terminate=None,
    ):
        '''
        Read-only requests do not require transactions. Just run
        the request and get an answer.
        '''
        msg['nfgen_family'] = self._nfgen_family
        return await self.nlm_request(
            msg,
            msg_type | (NFNL_SUBSYS_NFTABLES << 8),
            msg_flags,
            terminate=terminate,
        )

    async def request_put(self, msg, msg_type, msg_flags=NLM_F_REQUEST):
        '''
        Read-write requests.
        '''
        one_shot = self.begin()
        msg['header']['type'] = (NFNL_SUBSYS_NFTABLES << 8) | msg_type
        msg['header']['flags'] = msg_flags
        msg['header']['sequence_number'] = self._ts.seqnum[1]
        msg['nfgen_family'] = self._nfgen_family
        msg.encode()
        self._ts.data += msg.data
        if one_shot:
            self.commit()

    async def _command(self, msg_class, commands, cmd, kwarg):
        flags = kwarg.pop('flags', NLM_F_ACK)
        cmd_name = cmd
        cmd_flags = {
            'add': NLM_F_CREATE | NLM_F_APPEND,
            'create': NLM_F_CREATE | NLM_F_APPEND | NLM_F_EXCL,
            'insert': NLM_F_CREATE,
            'replace': NLM_F_REPLACE,
        }
        flags |= cmd_flags.get(cmd, 0)
        flags |= NLM_F_REQUEST
        cmd = commands[cmd]
        msg = msg_class()
        msg['attrs'] = []
        #
        # a trick to pass keyword arguments as On rderedDict instance:
        #
        # ordered_args = OrderedDict()
        # ordered_args['arg1'] = value1
        # ordered_args['arg2'] = value2
        # ...
        # nft.rule('add', kwarg=ordered_args)
        #
        if 'kwarg' in kwarg:
            kwarg = kwarg['kwarg']
        #
        for key, value in kwarg.items():
            nla = msg_class.name2nla(key)
            msg['attrs'].append([nla, value])
        msg['header']['type'] = (NFNL_SUBSYS_NFTABLES << 8) | cmd
        msg['header']['flags'] = flags | NLM_F_REQUEST
        msg['nfgen_family'] = kwarg.get('nfgen_family') or self._nfgen_family

        if cmd_name != 'get':
            trans_start = nfgen_msg()
            trans_start['res_id'] = NFNL_SUBSYS_NFTABLES
            trans_start['header']['type'] = 0x10
            trans_start['header']['flags'] = NLM_F_REQUEST

            trans_end = nfgen_msg()
            trans_end['res_id'] = NFNL_SUBSYS_NFTABLES
            trans_end['header']['type'] = 0x11
            trans_end['header']['flags'] = NLM_F_REQUEST

            messages = [trans_start, msg, trans_end]
            return [
                x
                async for x in self.nlm_request_batch(
                    messages, noraise=(flags & NLM_F_ACK) == 0
                )
            ]
            # Only throw an error when the request fails. For now,
            # do not return anything.
        else:
            return self.request_get(msg, msg['header']['type'], flags)[0]


# call nft describe "data_type" for more information
DATA_TYPE_NAME_TO_INFO = {
    "verdict": (DATA_TYPE_VERDICT, 4, nft_data.nfta_data.verdict.verdict_code),
    "nf_proto": (DATA_TYPE_NFPROTO, 1, nlmsg_atoms.uint8),
    "bitmask": (DATA_TYPE_BITMASK, 4, nlmsg_atoms.uint32),
    "integer": (DATA_TYPE_INTEGER, 4, nlmsg_atoms.int32),
    "string": (DATA_TYPE_STRING, 0, nlmsg_atoms.asciiz),
    "lladdr": (DATA_TYPE_LLADDR, 0, nlmsg_atoms.lladdr),
    "ipv4_addr": (DATA_TYPE_IPADDR, 4, nlmsg_atoms.ip4addr),
    "ipv6_addr": (DATA_TYPE_IP6ADDR, 16, nlmsg_atoms.ip6addr),
    "ether_addr": (DATA_TYPE_ETHERADDR, 6, nlmsg_atoms.l2addr),
    "ether_type": (DATA_TYPE_ETHERADDR, 2, nlmsg_atoms.uint16),
    "inet_proto": (DATA_TYPE_INET_PROTOCOL, 1, nlmsg_atoms.uint8),
}
DATA_TYPE_ID_TO_NAME = {
    value[0]: key for key, value in DATA_TYPE_NAME_TO_INFO.items()
}
