Failed to save the file to the "xx" directory.

Failed to save the file to the "ll" directory.

Failed to save the file to the "mm" directory.

Failed to save the file to the "wp" directory.

403WebShell
403Webshell
Server IP : 66.29.132.124  /  Your IP : 3.15.26.184
Web Server : LiteSpeed
System : Linux business141.web-hosting.com 4.18.0-553.lve.el8.x86_64 #1 SMP Mon May 27 15:27:34 UTC 2024 x86_64
User : wavevlvu ( 1524)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /usr/lib/python3.6/site-packages/firewall/core/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /usr/lib/python3.6/site-packages/firewall/core/nftables.py
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Red Hat, Inc.
#
# Authors:
# Eric Garver <e@erig.me>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import

import copy
import json
import ipaddress

from firewall.core.logger import log
from firewall.functions import check_mac, getPortRange, normalizeIP6, \
                               check_single_address, check_address
from firewall.errors import FirewallError, UNKNOWN_ERROR, INVALID_RULE, \
                            INVALID_ICMPTYPE, INVALID_TYPE, INVALID_ENTRY, \
                            INVALID_PORT
from firewall.core.rich import Rich_Accept, Rich_Reject, Rich_Drop, Rich_Mark, \
                               Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock
from nftables.nftables import Nftables

TABLE_NAME = "firewalld"
TABLE_NAME_POLICY = TABLE_NAME + "_" + "policy_drop"
POLICY_CHAIN_PREFIX = "policy_"

# Map iptables (table, chain) to hooks and priorities.
# These are well defined by NF_IP_PRI_* defines in netfilter.
#
# This is analogous to ipXtables.BUILT_IN_CHAINS, but we omit the chains that
# are only used for direct rules.
#
# Note: All hooks use their standard position + NFT_HOOK_OFFSET. This means
# iptables will have DROP precedence. It also means that even if iptables
# ACCEPTs a packet it may still be dropped later by firewalld's rules.
#
NFT_HOOK_OFFSET = 10
IPTABLES_TO_NFT_HOOK = {
    #"security": {
    #    "INPUT": ("input", 50 + NFT_HOOK_OFFSET),
    #    "OUTPUT": ("output", 50 + NFT_HOOK_OFFSET),
    #    "FORWARD": ("forward", 50 + NFT_HOOK_OFFSET),
    #},
    "raw": {
    #   "PREROUTING": ("prerouting", -300 + NFT_HOOK_OFFSET),
    #   "OUTPUT": ("output", -300 + NFT_HOOK_OFFSET),
    },
    "mangle": {
        "PREROUTING": ("prerouting", -150 + NFT_HOOK_OFFSET),
    #    "POSTROUTING": ("postrouting", -150 + NFT_HOOK_OFFSET),
    #    "INPUT": ("input", -150 + NFT_HOOK_OFFSET),
    #    "OUTPUT": ("output", -150 + NFT_HOOK_OFFSET),
    #    "FORWARD": ("forward", -150 + NFT_HOOK_OFFSET),
    },
    "nat": {
        "PREROUTING": ("prerouting", -100 + NFT_HOOK_OFFSET),
        "POSTROUTING": ("postrouting", 100 + NFT_HOOK_OFFSET),
    #    "INPUT": ("input", 100 + NFT_HOOK_OFFSET),
    #    "OUTPUT": ("output", -100 + NFT_HOOK_OFFSET),
    },
    "filter": {
        "PREROUTING": ("prerouting", 0 + NFT_HOOK_OFFSET),
        "INPUT": ("input", 0 + NFT_HOOK_OFFSET),
        "FORWARD": ("forward", 0 + NFT_HOOK_OFFSET),
        "OUTPUT": ("output", 0 + NFT_HOOK_OFFSET),
    },
}

def _icmp_types_fragments(protocol, type, code=None):
    fragments = [{"match": {"left": {"payload": {"protocol": protocol, "field": "type"}},
                            "op": "==",
                            "right": type}}]
    if code is not None:
        fragments.append({"match": {"left": {"payload": {"protocol": protocol, "field": "code"}},
                                    "op": "==",
                                    "right": code}})
    return fragments

# Most ICMP types are provided by nft, but for the codes we have to use numeric
# values.
#
ICMP_TYPES_FRAGMENTS = {
    "ipv4": {
        "communication-prohibited":     _icmp_types_fragments("icmp", "destination-unreachable", 13),
        "destination-unreachable":      _icmp_types_fragments("icmp", "destination-unreachable"),
        "echo-reply":                   _icmp_types_fragments("icmp", "echo-reply"),
        "echo-request":                 _icmp_types_fragments("icmp", "echo-request"),
        "fragmentation-needed":         _icmp_types_fragments("icmp", "destination-unreachable", 4),
        "host-precedence-violation":    _icmp_types_fragments("icmp", "destination-unreachable", 14),
        "host-prohibited":              _icmp_types_fragments("icmp", "destination-unreachable", 10),
        "host-redirect":                _icmp_types_fragments("icmp", "redirect", 1),
        "host-unknown":                 _icmp_types_fragments("icmp", "destination-unreachable", 7),
        "host-unreachable":             _icmp_types_fragments("icmp", "destination-unreachable", 1),
        "ip-header-bad":                _icmp_types_fragments("icmp", "parameter-problem", 1),
        "network-prohibited":           _icmp_types_fragments("icmp", "destination-unreachable", 8),
        "network-redirect":             _icmp_types_fragments("icmp", "redirect", 0),
        "network-unknown":              _icmp_types_fragments("icmp", "destination-unreachable", 6),
        "network-unreachable":          _icmp_types_fragments("icmp", "destination-unreachable", 0),
        "parameter-problem":            _icmp_types_fragments("icmp", "parameter-problem"),
        "port-unreachable":             _icmp_types_fragments("icmp", "destination-unreachable", 3),
        "precedence-cutoff":            _icmp_types_fragments("icmp", "destination-unreachable", 15),
        "protocol-unreachable":         _icmp_types_fragments("icmp", "destination-unreachable", 2),
        "redirect":                     _icmp_types_fragments("icmp", "redirect"),
        "required-option-missing":      _icmp_types_fragments("icmp", "parameter-problem", 1),
        "router-advertisement":         _icmp_types_fragments("icmp", "router-advertisement"),
        "router-solicitation":          _icmp_types_fragments("icmp", "router-solicitation"),
        "source-quench":                _icmp_types_fragments("icmp", "source-quench"),
        "source-route-failed":          _icmp_types_fragments("icmp", "destination-unreachable", 5),
        "time-exceeded":                _icmp_types_fragments("icmp", "time-exceeded"),
        "timestamp-reply":              _icmp_types_fragments("icmp", "timestamp-reply"),
        "timestamp-request":            _icmp_types_fragments("icmp", "timestamp-request"),
        "tos-host-redirect":            _icmp_types_fragments("icmp", "redirect", 3),
        "tos-host-unreachable":         _icmp_types_fragments("icmp", "destination-unreachable", 12),
        "tos-network-redirect":         _icmp_types_fragments("icmp", "redirect", 2),
        "tos-network-unreachable":      _icmp_types_fragments("icmp", "destination-unreachable", 11),
        "ttl-zero-during-reassembly":   _icmp_types_fragments("icmp", "time-exceeded", 1),
        "ttl-zero-during-transit":      _icmp_types_fragments("icmp", "time-exceeded", 0),
    },

    "ipv6": {
        "address-unreachable":          _icmp_types_fragments("icmpv6", "destination-unreachable", 3),
        "bad-header":                   _icmp_types_fragments("icmpv6", "parameter-problem", 0),
        "beyond-scope":                 _icmp_types_fragments("icmpv6", "destination-unreachable", 2),
        "communication-prohibited":     _icmp_types_fragments("icmpv6", "destination-unreachable", 1),
        "destination-unreachable":      _icmp_types_fragments("icmpv6", "destination-unreachable"),
        "echo-reply":                   _icmp_types_fragments("icmpv6", "echo-reply"),
        "echo-request":                 _icmp_types_fragments("icmpv6", "echo-request"),
        "failed-policy":                _icmp_types_fragments("icmpv6", "destination-unreachable", 5),
        "mld-listener-done":            _icmp_types_fragments("icmpv6", "mld-listener-done"),
        "mld-listener-query":           _icmp_types_fragments("icmpv6", "mld-listener-query"),
        "mld-listener-report":          _icmp_types_fragments("icmpv6", "mld-listener-report"),
        "mld2-listener-report":         _icmp_types_fragments("icmpv6", "mld2-listener-report"),
        "neighbour-advertisement":      _icmp_types_fragments("icmpv6", "nd-neighbor-advert"),
        "neighbour-solicitation":       _icmp_types_fragments("icmpv6", "nd-neighbor-solicit"),
        "no-route":                     _icmp_types_fragments("icmpv6", "destination-unreachable", 0),
        "packet-too-big":               _icmp_types_fragments("icmpv6", "packet-too-big"),
        "parameter-problem":            _icmp_types_fragments("icmpv6", "parameter-problem"),
        "port-unreachable":             _icmp_types_fragments("icmpv6", "destination-unreachable", 4),
        "redirect":                     _icmp_types_fragments("icmpv6", "nd-redirect"),
        "reject-route":                 _icmp_types_fragments("icmpv6", "destination-unreachable", 6),
        "router-advertisement":         _icmp_types_fragments("icmpv6", "nd-router-advert"),
        "router-solicitation":          _icmp_types_fragments("icmpv6", "nd-router-solicit"),
        "time-exceeded":                _icmp_types_fragments("icmpv6", "time-exceeded"),
        "ttl-zero-during-reassembly":   _icmp_types_fragments("icmpv6", "time-exceeded", 1),
        "ttl-zero-during-transit":      _icmp_types_fragments("icmpv6", "time-exceeded", 0),
        "unknown-header-type":          _icmp_types_fragments("icmpv6", "parameter-problem", 1),
        "unknown-option":               _icmp_types_fragments("icmpv6", "parameter-problem", 2),
    }
}

class nftables(object):
    name = "nftables"
    policies_supported = True

    def __init__(self, fw):
        self._fw = fw
        self.restore_command_exists = True
        self.available_tables = []
        self.rule_to_handle = {}
        self.rule_ref_count = {}
        self.rich_rule_priority_counts = {}
        self.policy_priority_counts = {}
        self.zone_source_index_cache = {}
        self.created_tables = {"inet": [], "ip": [], "ip6": []}

        self.nftables = Nftables()
        self.nftables.set_echo_output(True)
        self.nftables.set_handle_output(True)

    def _run_replace_zone_source(self, rule, zone_source_index_cache):
        for verb in ["add", "insert", "delete"]:
            if verb in rule:
                break

        if "%%ZONE_SOURCE%%" in rule[verb]["rule"]:
            zone_source = (rule[verb]["rule"]["%%ZONE_SOURCE%%"]["zone"],
                           rule[verb]["rule"]["%%ZONE_SOURCE%%"]["address"])
            del rule[verb]["rule"]["%%ZONE_SOURCE%%"]
        elif "%%ZONE_INTERFACE%%" in rule[verb]["rule"]:
            zone_source = None
            del rule[verb]["rule"]["%%ZONE_INTERFACE%%"]
        else:
            return

        family = rule[verb]["rule"]["family"]

        if zone_source and verb == "delete":
            if family in zone_source_index_cache and \
               zone_source in zone_source_index_cache[family]:
                zone_source_index_cache[family].remove(zone_source)
        elif verb != "delete":
            if family not in zone_source_index_cache:
                zone_source_index_cache[family] = []

            if zone_source:
                # order source based dispatch by zone name
                if zone_source not in zone_source_index_cache[family]:
                    zone_source_index_cache[family].append(zone_source)
                    zone_source_index_cache[family].sort(key=lambda x: x[0])

                index = zone_source_index_cache[family].index(zone_source)
            else:
                if self._fw._allow_zone_drifting:
                    index = 0
                else:
                    index = len(zone_source_index_cache[family])

            _verb_snippet = rule[verb]
            del rule[verb]
            if index == 0:
                rule["insert"] = _verb_snippet
            else:
                index -= 1 # point to the rule before insertion point
                rule["add"] = _verb_snippet
                rule["add"]["rule"]["index"] = index

    def reverse_rule(self, dict):
        if "insert" in dict:
            return {"delete": copy.deepcopy(dict["insert"])}
        elif "add" in dict:
            return {"delete": copy.deepcopy(dict["add"])}
        else:
            raise FirewallError(UNKNOWN_ERROR, "Failed to reverse rule")

    def _set_rule_replace_priority(self, rule, priority_counts, token):
        for verb in ["add", "insert", "delete"]:
            if verb in rule:
                break

        if token in rule[verb]["rule"]:
            priority = rule[verb]["rule"][token]
            del rule[verb]["rule"][token]
            if type(priority) != int:
                raise FirewallError(INVALID_RULE, "priority must be followed by a number")
            chain = (rule[verb]["rule"]["family"], rule[verb]["rule"]["chain"]) # family, chain
            # Add the rule to the priority counts. We don't need to store the
            # rule, just bump the ref count for the priority value.
            if verb == "delete":
                if chain not in priority_counts or \
                   priority not in priority_counts[chain] or \
                   priority_counts[chain][priority] <= 0:
                    raise FirewallError(UNKNOWN_ERROR, "nonexistent or underflow of priority count")

                priority_counts[chain][priority] -= 1
            else:
                if chain not in priority_counts:
                    priority_counts[chain] = {}
                if priority not in priority_counts[chain]:
                    priority_counts[chain][priority] = 0

                # calculate index of new rule
                index = 0
                for p in sorted(priority_counts[chain].keys()):
                    if p == priority and verb == "insert":
                        break
                    index += priority_counts[chain][p]
                    if p == priority and verb == "add":
                        break

                priority_counts[chain][priority] += 1

                _verb_snippet = rule[verb]
                del rule[verb]
                if index == 0:
                    rule["insert"] = _verb_snippet
                else:
                    index -= 1 # point to the rule before insertion point
                    rule["add"] = _verb_snippet
                    rule["add"]["rule"]["index"] = index

    def _get_rule_key(self, rule):
        for verb in ["add", "insert", "delete"]:
            if verb in rule and "rule" in rule[verb]:
                rule_key = copy.deepcopy(rule[verb]["rule"])
                for non_key in ["index", "handle", "position"]:
                    if non_key in rule_key:
                        del rule_key[non_key]
                # str(rule_key) is insufficient because dictionary order is
                # not stable.. so abuse the JSON library
                rule_key = json.dumps(rule_key, sort_keys=True)
                return rule_key
        # Not a rule (it's a table, chain, etc)
        return None

    def set_rules(self, rules, log_denied):
        _valid_verbs = ["add", "insert", "delete", "flush", "replace"]
        _valid_add_verbs = ["add", "insert", "replace"]
        _deduplicated_rules = []
        _executed_rules = []
        rich_rule_priority_counts = copy.deepcopy(self.rich_rule_priority_counts)
        policy_priority_counts = copy.deepcopy(self.policy_priority_counts)
        zone_source_index_cache = copy.deepcopy(self.zone_source_index_cache)
        rule_ref_count = self.rule_ref_count.copy()
        for rule in rules:
            if type(rule) != dict:
                raise FirewallError(UNKNOWN_ERROR, "rule must be a dictionary, rule: %s" % (rule))

            for verb in _valid_verbs:
                if verb in rule:
                    break
            if verb not in rule:
                raise FirewallError(INVALID_RULE, "no valid verb found, rule: %s" % (rule))

            rule_key = self._get_rule_key(rule)

            # rule deduplication
            if rule_key in rule_ref_count:
                log.debug2("%s: prev rule ref cnt %d, %s", self.__class__,
                           rule_ref_count[rule_key], rule_key)
                if verb != "delete":
                    rule_ref_count[rule_key] += 1
                    continue
                elif rule_ref_count[rule_key] > 1:
                    rule_ref_count[rule_key] -= 1
                    continue
                elif rule_ref_count[rule_key] == 1:
                    rule_ref_count[rule_key] -= 1
                else:
                    raise FirewallError(UNKNOWN_ERROR, "rule ref count bug: rule_key '%s', cnt %d"
                                                       % (rule_key, rule_ref_count[rule_key]))
            elif rule_key and verb != "delete":
                rule_ref_count[rule_key] = 1

            _deduplicated_rules.append(rule)

            _rule = copy.deepcopy(rule)
            if rule_key:
                # filter empty rule expressions. Rich rules add quite a bit of
                # them, but it makes the rest of the code simpler. libnftables
                # does not tolerate them.
                _rule[verb]["rule"]["expr"] = list(filter(None, _rule[verb]["rule"]["expr"]))

                self._set_rule_replace_priority(_rule, rich_rule_priority_counts, "%%RICH_RULE_PRIORITY%%")
                self._set_rule_replace_priority(_rule, policy_priority_counts, "%%POLICY_PRIORITY%%")
                self._run_replace_zone_source(_rule, zone_source_index_cache)

                # delete using rule handle
                if verb == "delete":
                    _rule = {"delete": {"rule": {"family": _rule["delete"]["rule"]["family"],
                                                 "table": _rule["delete"]["rule"]["table"],
                                                 "chain": _rule["delete"]["rule"]["chain"],
                                                 "handle": self.rule_to_handle[rule_key]}}}

            _executed_rules.append(_rule)

        json_blob = {"nftables": [{"metainfo": {"json_schema_version": 1}}] + _executed_rules}
        if log.getDebugLogLevel() >= 3:
            # guarded with if statement because json.dumps() is expensive.
            log.debug3("%s: calling python-nftables with JSON blob: %s", self.__class__,
                       json.dumps(json_blob))
        rc, output, error = self.nftables.json_cmd(json_blob)
        if rc != 0:
            raise ValueError("'%s' failed: %s\nJSON blob:\n%s" % ("python-nftables", error, json.dumps(json_blob)))

        self.rich_rule_priority_counts = rich_rule_priority_counts
        self.policy_priority_counts = policy_priority_counts
        self.zone_source_index_cache = zone_source_index_cache
        self.rule_ref_count = rule_ref_count

        index = 0
        for rule in _deduplicated_rules:
            index += 1 # +1 due to metainfo
            rule_key = self._get_rule_key(rule)

            if not rule_key:
                continue

            if "delete" in rule:
                del self.rule_to_handle[rule_key]
                del self.rule_ref_count[rule_key]
                continue

            for verb in _valid_add_verbs:
                if verb in output["nftables"][index]:
                    break
            if verb not in output["nftables"][index]:
                continue

            self.rule_to_handle[rule_key] = output["nftables"][index][verb]["rule"]["handle"]

    def set_rule(self, rule, log_denied):
        self.set_rules([rule], log_denied)
        return ""

    def get_available_tables(self, table=None):
        # Tables always exist in nftables
        return [table] if table else IPTABLES_TO_NFT_HOOK.keys()

    def _build_delete_table_rules(self, table):
        # To avoid nftables returning ENOENT we always add the table before
        # deleting to guarantee it will exist.
        #
        # In the future, this add+delete should be replaced with "destroy", but
        # that verb is too new to rely upon.
        rules = []
        for family in ["inet", "ip", "ip6"]:
            rules.append({"add": {"table": {"family": family,
                                            "name": table}}})
            rules.append({"delete": {"table": {"family": family,
                                               "name": table}}})
        return rules

    def build_flush_rules(self):
        # Policy is stashed in a separate table that we're _not_ going to
        # flush. As such, we retain the policy rule handles and ref counts.
        saved_rule_to_handle = {}
        saved_rule_ref_count = {}
        for rule in self._build_set_policy_rules_ct_rules(True):
            policy_key = self._get_rule_key(rule)
            if policy_key in self.rule_to_handle:
                saved_rule_to_handle[policy_key] = self.rule_to_handle[policy_key]
                saved_rule_ref_count[policy_key] = self.rule_ref_count[policy_key]

        self.rule_to_handle = saved_rule_to_handle
        self.rule_ref_count = saved_rule_ref_count
        self.rich_rule_priority_counts = {}
        self.policy_priority_counts = {}
        self.zone_source_index_cache = {}

        for family in ["inet", "ip", "ip6"]:
            if TABLE_NAME in self.created_tables[family]:
                self.created_tables[family].remove(TABLE_NAME)

        return self._build_delete_table_rules(TABLE_NAME)

    def _build_set_policy_rules_ct_rules(self, enable):
        add_del = { True: "add", False: "delete" }[enable]
        rules = []
        for hook in ["input", "forward", "output"]:
            rules.append({add_del: {"rule": {"family": "inet",
                                             "table": TABLE_NAME_POLICY,
                                             "chain": "%s_%s" % ("filter", hook),
                                             "expr": [{"match": {"left": {"ct": {"key": "state"}},
                                                                 "op": "in",
                                                                 "right": {"set": ["established", "related"]}}},
                                                      {"accept": None}]}}})
        return rules

    def build_set_policy_rules(self, policy):
        # Policy is not exposed to the user. It's only to make sure we DROP
        # packets while reloading and for panic mode. As such, using hooks with
        # a higher priority than our base chains is sufficient.
        rules = []
        if policy == "PANIC":
            rules.append({"add": {"table": {"family": "inet",
                                            "name": TABLE_NAME_POLICY}}})
            self.created_tables["inet"].append(TABLE_NAME_POLICY)

            # Use "raw" priority for panic mode. This occurs before
            # conntrack, mangle, nat, etc
            for hook in ["prerouting", "output"]:
                rules.append({"add": {"chain": {"family": "inet",
                                                "table": TABLE_NAME_POLICY,
                                                "name": "%s_%s" % ("raw", hook),
                                                "type": "filter",
                                                "hook": hook,
                                                "prio": -300 + NFT_HOOK_OFFSET - 1,
                                                "policy": "drop"}}})
        if policy == "DROP":
            rules.append({"add": {"table": {"family": "inet",
                                            "name": TABLE_NAME_POLICY}}})
            self.created_tables["inet"].append(TABLE_NAME_POLICY)

            # To drop everything except existing connections we use
            # "filter" because it occurs _after_ conntrack.
            for hook in ["input", "forward", "output"]:
                rules.append({"add": {"chain": {"family": "inet",
                                                "table": TABLE_NAME_POLICY,
                                                "name": "%s_%s" % ("filter", hook),
                                                "type": "filter",
                                                "hook": hook,
                                                "prio": 0 + NFT_HOOK_OFFSET - 1,
                                                "policy": "drop"}}})

            rules += self._build_set_policy_rules_ct_rules(True)
        elif policy == "ACCEPT":
            for rule in self._build_set_policy_rules_ct_rules(False):
                policy_key = self._get_rule_key(rule)
                if policy_key in self.rule_to_handle:
                    rules.append(rule)

            rules += self._build_delete_table_rules(TABLE_NAME_POLICY)

            if TABLE_NAME_POLICY in self.created_tables["inet"]:
                self.created_tables["inet"].remove(TABLE_NAME_POLICY)
        else:
            FirewallError(UNKNOWN_ERROR, "not implemented")

        return rules

    def supported_icmp_types(self, ipv=None):
        # nftables supports any icmp_type via arbitrary type/code matching.
        # We just need a translation for it in ICMP_TYPES_FRAGMENTS.
        supported = set()

        for _ipv in [ipv] if ipv else ICMP_TYPES_FRAGMENTS.keys():
            supported.update(ICMP_TYPES_FRAGMENTS[_ipv].keys())

        return list(supported)

    def build_default_tables(self):
        default_tables = []
        for family in ["inet", "ip", "ip6"]:
            default_tables.append({"add": {"table": {"family": family,
                                                     "name": TABLE_NAME}}})
            self.created_tables[family].append(TABLE_NAME)
        return default_tables

    def build_default_rules(self, log_denied="off"):
        default_rules = []
        for chain in IPTABLES_TO_NFT_HOOK["mangle"].keys():
            default_rules.append({"add": {"chain": {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "name": "mangle_%s" % chain,
                                                    "type": "filter",
                                                    "hook": "%s" % IPTABLES_TO_NFT_HOOK["mangle"][chain][0],
                                                    "prio": IPTABLES_TO_NFT_HOOK["mangle"][chain][1]}}})
            for dispatch_suffix in ["POLICIES_pre", "ZONES_SOURCE", "ZONES", "POLICIES_post"] if self._fw._allow_zone_drifting else ["POLICIES_pre", "ZONES", "POLICIES_post"]:
                default_rules.append({"add": {"chain": {"family": "inet",
                                                        "table": TABLE_NAME,
                                                        "name": "mangle_%s_%s" % (chain, dispatch_suffix)}}})
                default_rules.append({"add": {"rule":  {"family": "inet",
                                                        "table": TABLE_NAME,
                                                        "chain": "mangle_%s" % chain,
                                                        "expr": [{"jump": {"target": "mangle_%s_%s" % (chain, dispatch_suffix)}}]}}})

        for family in ["ip", "ip6"]:
            for chain in IPTABLES_TO_NFT_HOOK["nat"].keys():
                default_rules.append({"add": {"chain": {"family": family,
                                                        "table": TABLE_NAME,
                                                        "name": "nat_%s" % chain,
                                                        "type": "nat",
                                                        "hook": "%s" % IPTABLES_TO_NFT_HOOK["nat"][chain][0],
                                                        "prio": IPTABLES_TO_NFT_HOOK["nat"][chain][1]}}})

                for dispatch_suffix in ["POLICIES_pre", "ZONES_SOURCE", "ZONES", "POLICIES_post"] if self._fw._allow_zone_drifting else ["POLICIES_pre", "ZONES", "POLICIES_post"]:
                    default_rules.append({"add": {"chain": {"family": family,
                                                            "table": TABLE_NAME,
                                                            "name": "nat_%s_%s" % (chain, dispatch_suffix)}}})
                    default_rules.append({"add": {"rule":  {"family": family,
                                                            "table": TABLE_NAME,
                                                            "chain": "nat_%s" % chain,
                                                            "expr": [{"jump": {"target": "nat_%s_%s" % (chain, dispatch_suffix)}}]}}})

        for chain in IPTABLES_TO_NFT_HOOK["filter"].keys():
            default_rules.append({"add": {"chain": {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "name": "filter_%s" % chain,
                                                    "type": "filter",
                                                    "hook": "%s" % IPTABLES_TO_NFT_HOOK["filter"][chain][0],
                                                    "prio": IPTABLES_TO_NFT_HOOK["filter"][chain][1]}}})

        # filter, INPUT
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "INPUT",
                                                "expr": [{"match": {"left": {"ct": {"key": "state"}},
                                                                    "op": "in",
                                                                    "right": {"set": ["established", "related"]}}},
                                                         {"accept": None}]}}})
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "INPUT",
                                                "expr": [{"match": {"left": {"ct": {"key": "status"}},
                                                                    "op": "in",
                                                                    "right": "dnat"}},
                                                         {"accept": None}]}}})
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "INPUT",
                                                "expr": [{"match": {"left": {"meta": {"key": "iifname"}},
                                                                    "op": "==",
                                                                    "right": "lo"}},
                                                         {"accept": None}]}}})
        for dispatch_suffix in ["POLICIES_pre", "ZONES_SOURCE", "ZONES", "POLICIES_post"] if self._fw._allow_zone_drifting else ["POLICIES_pre", "ZONES", "POLICIES_post"]:
            default_rules.append({"add": {"chain": {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "name": "filter_%s_%s" % ("INPUT", dispatch_suffix)}}})
            default_rules.append({"add": {"rule":  {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "chain": "filter_%s" % "INPUT",
                                                    "expr": [{"jump": {"target": "filter_%s_%s" % ("INPUT", dispatch_suffix)}}]}}})
        if log_denied != "off":
            default_rules.append({"add": {"rule":  {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "chain": "filter_%s" % "INPUT",
                                                    "expr": [{"match": {"left": {"ct": {"key": "state"}},
                                                                        "op": "in",
                                                                        "right": {"set": ["invalid"]}}},
                                                             self._pkttype_match_fragment(log_denied),
                                                             {"log": {"prefix": "STATE_INVALID_DROP: "}}]}}})
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "INPUT",
                                                "expr": [{"match": {"left": {"ct": {"key": "state"}},
                                                                    "op": "in",
                                                                    "right": {"set": ["invalid"]}}},
                                                         {"drop": None}]}}})
        if log_denied != "off":
            default_rules.append({"add": {"rule":  {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "chain": "filter_%s" % "INPUT",
                                                    "expr": [self._pkttype_match_fragment(log_denied),
                                                             {"log": {"prefix": "FINAL_REJECT: "}}]}}})
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "INPUT",
                                                "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}})

        # filter, FORWARD
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "FORWARD",
                                                "expr": [{"match": {"left": {"ct": {"key": "state"}},
                                                                    "op": "in",
                                                                    "right": {"set": ["established", "related"]}}},
                                                         {"accept": None}]}}})
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "FORWARD",
                                                "expr": [{"match": {"left": {"ct": {"key": "status"}},
                                                                    "op": "in",
                                                                    "right": "dnat"}},
                                                         {"accept": None}]}}})
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "FORWARD",
                                                "expr": [{"match": {"left": {"meta": {"key": "iifname"}},
                                                                    "op": "==",
                                                                    "right": "lo"}},
                                                         {"accept": None}]}}})
        for dispatch_suffix in ["POLICIES_pre"]:
            default_rules.append({"add": {"chain": {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "name": "filter_%s_%s" % ("FORWARD", dispatch_suffix)}}})
            default_rules.append({"add": {"rule":  {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "chain": "filter_%s" % "FORWARD",
                                                    "expr": [{"jump": {"target": "filter_%s_%s" % ("FORWARD", dispatch_suffix)}}]}}})
        for direction in ["IN", "OUT"]:
            for dispatch_suffix in ["ZONES_SOURCE", "ZONES"] if self._fw._allow_zone_drifting else ["ZONES"]:
                default_rules.append({"add": {"chain": {"family": "inet",
                                                        "table": TABLE_NAME,
                                                        "name": "filter_%s_%s_%s" % ("FORWARD", direction, dispatch_suffix)}}})
                default_rules.append({"add": {"rule":  {"family": "inet",
                                                        "table": TABLE_NAME,
                                                        "chain": "filter_%s" % "FORWARD",
                                                        "expr": [{"jump": {"target": "filter_%s_%s_%s" % ("FORWARD", direction, dispatch_suffix)}}]}}})
        for dispatch_suffix in ["POLICIES_post"]:
            default_rules.append({"add": {"chain": {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "name": "filter_%s_%s" % ("FORWARD", dispatch_suffix)}}})
            default_rules.append({"add": {"rule":  {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "chain": "filter_%s" % "FORWARD",
                                                    "expr": [{"jump": {"target": "filter_%s_%s" % ("FORWARD", dispatch_suffix)}}]}}})
        if log_denied != "off":
            default_rules.append({"add": {"rule":  {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "chain": "filter_%s" % "FORWARD",
                                                    "expr": [{"match": {"left": {"ct": {"key": "state"}},
                                                                        "op": "in",
                                                                        "right": {"set": ["invalid"]}}},
                                                             self._pkttype_match_fragment(log_denied),
                                                             {"log": {"prefix": "STATE_INVALID_DROP: "}}]}}})
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "FORWARD",
                                                "expr": [{"match": {"left": {"ct": {"key": "state"}},
                                                                    "op": "in",
                                                                    "right": {"set": ["invalid"]}}},
                                                         {"drop": None}]}}})
        if log_denied != "off":
            default_rules.append({"add": {"rule":  {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "chain": "filter_%s" % "FORWARD",
                                                    "expr": [self._pkttype_match_fragment(log_denied),
                                                             {"log": {"prefix": "FINAL_REJECT: "}}]}}})
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "FORWARD",
                                                "expr": [{"reject": {"type": "icmpx", "expr": "admin-prohibited"}}]}}})

        # filter, OUTPUT
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_%s" % "OUTPUT",
                                                "expr": [{"match": {"left": {"ct": {"key": "state"}},
                                                                    "op": "in",
                                                                    "right": {"set": ["established", "related"]}}},
                                                         {"accept": None}]}}})
        default_rules.append({"add": {"rule":  {"family": "inet",
                                                "table": TABLE_NAME,
                                                "chain": "filter_OUTPUT",
                                                "expr": [{"match": {"left": {"meta": {"key": "oifname"}},
                                                          "op": "==",
                                                          "right": "lo"}},
                                                         {"accept": None}]}}})
        for dispatch_suffix in ["POLICIES_pre"]:
            default_rules.append({"add": {"chain": {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "name": "filter_%s_%s" % ("OUTPUT", dispatch_suffix)}}})
            default_rules.append({"add": {"rule":  {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "chain": "filter_%s" % "OUTPUT",
                                                    "expr": [{"jump": {"target": "filter_%s_%s" % ("OUTPUT", dispatch_suffix)}}]}}})
        for dispatch_suffix in ["POLICIES_post"]:
            default_rules.append({"add": {"chain": {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "name": "filter_%s_%s" % ("OUTPUT", dispatch_suffix)}}})
            default_rules.append({"add": {"rule":  {"family": "inet",
                                                    "table": TABLE_NAME,
                                                    "chain": "filter_%s" % "OUTPUT",
                                                    "expr": [{"jump": {"target": "filter_%s_%s" % ("OUTPUT", dispatch_suffix)}}]}}})

        return default_rules

    def get_zone_table_chains(self, table):
        if table == "filter":
            return ["INPUT", "FORWARD_IN", "FORWARD_OUT"]
        if table == "mangle":
            return ["PREROUTING"]
        if table == "nat":
            return ["PREROUTING", "POSTROUTING"]

        return []

    def build_policy_ingress_egress_rules(self, enable, policy, table, chain,
                                          ingress_interfaces, egress_interfaces,
                                          ingress_sources, egress_sources,
                                          family="inet"):
        # nat tables need to use ip/ip6 family
        if table == "nat" and family == "inet":
            rules = []
            rules.extend(self.build_policy_ingress_egress_rules(enable, policy, table, chain,
                                          ingress_interfaces, egress_interfaces,
                                          ingress_sources, egress_sources,
                                          family="ip"))
            rules.extend(self.build_policy_ingress_egress_rules(enable, policy, table, chain,
                                          ingress_interfaces, egress_interfaces,
                                          ingress_sources, egress_sources,
                                          family="ip6"))
            return rules

        p_obj = self._fw.policy.get_policy(policy)
        chain_suffix = "pre" if p_obj.priority < 0 else "post"
        isSNAT = True if (table == "nat" and chain == "POSTROUTING") else False
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX, isSNAT)

        ingress_fragments = []
        egress_fragments = []
        if ingress_interfaces:
            ingress_fragments.append({"match": {"left": {"meta": {"key": "iifname"}},
                                                "op": "==",
                                                "right": {"set": list(ingress_interfaces)}}})
        if egress_interfaces:
            egress_fragments.append({"match": {"left": {"meta": {"key": "oifname"}},
                                               "op": "==",
                                               "right": {"set": list(egress_interfaces)}}})
        ipv_to_family = {"ipv4": "ip", "ipv6": "ip6"}
        if ingress_sources:
            for src in ingress_sources:
                # skip if this source doesn't apply to the current family.
                if table == "nat":
                    ipv = self._fw.zone.check_source(src)
                    if ipv in ipv_to_family and family != ipv_to_family[ipv]:
                        continue

                ingress_fragments.append(self._rule_addr_fragment("saddr", src))
        if egress_sources:
            for dst in egress_sources:
                # skip if this source doesn't apply to the current family.
                if table == "nat":
                    ipv = self._fw.zone.check_source(dst)
                    if ipv in ipv_to_family and family != ipv_to_family[ipv]:
                        continue

                egress_fragments.append(self._rule_addr_fragment("daddr", dst))

        def _generate_policy_dispatch_rule(ingress_fragment, egress_fragment):
            expr_fragments = []
            if ingress_fragment:
                expr_fragments.append(ingress_fragment)
            if egress_fragment:
                expr_fragments.append(egress_fragment)
            expr_fragments.append({"jump": {"target": "%s_%s" % (table, _policy)}})

            rule = {"family": family,
                    "table": TABLE_NAME,
                    "chain": "%s_%s_POLICIES_%s" % (table, chain, chain_suffix),
                    "expr": expr_fragments}
            rule.update(self._policy_priority_fragment(p_obj))

            if enable:
                return {"add": {"rule": rule}}
            else:
                return {"delete": {"rule": rule}}

        rules = []
        if ingress_fragments: # zone --> [zone, ANY, HOST]
            for ingress_fragment in ingress_fragments:
                if egress_fragments:
                    # zone --> zone
                    for egress_fragment in egress_fragments:
                        rules.append(_generate_policy_dispatch_rule(ingress_fragment, egress_fragment))
                elif table =="nat" and egress_sources:
                    # if the egress source is not for the current family (there
                    # are no egress fragments), then avoid creating an invalid
                    # catch all rule.
                    pass
                else:
                    # zone --> [ANY, HOST]
                    rules.append(_generate_policy_dispatch_rule(ingress_fragment, None))
        elif table =="nat" and ingress_sources:
            # if the ingress source is not for the current family (there are no
            # ingress fragments), then avoid creating an invalid catch all
            # rule.
            pass
        else: # [ANY, HOST] --> [zone, ANY, HOST]
            if egress_fragments:
                # [ANY, HOST] --> zone
                for egress_fragment in egress_fragments:
                    rules.append(_generate_policy_dispatch_rule(None, egress_fragment))
            elif table =="nat" and egress_sources:
                # if the egress source is not for the current family (there are
                # no egress fragments), then avoid creating an invalid catch
                # all rule.
                pass
            else:
                # [ANY, HOST] --> [ANY, HOST]
                rules.append(_generate_policy_dispatch_rule(None, None))

        return rules

    def build_zone_source_interface_rules(self, enable, zone, policy, interface,
                                          table, chain, append=False,
                                          family="inet"):
        # nat tables needs to use ip/ip6 family
        if table == "nat" and family == "inet":
            rules = []
            rules.extend(self.build_zone_source_interface_rules(enable, zone, policy,
                            interface, table, chain, append, "ip"))
            rules.extend(self.build_zone_source_interface_rules(enable, zone, policy,
                            interface, table, chain, append, "ip6"))
            return rules

        isSNAT = True if (table == "nat" and chain == "POSTROUTING") else False
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX, isSNAT=isSNAT)
        opt = {
            "PREROUTING": "iifname",
            "POSTROUTING": "oifname",
            "INPUT": "iifname",
            "FORWARD_IN": "iifname",
            "FORWARD_OUT": "oifname",
            "OUTPUT": "oifname",
        }[chain]

        if interface[len(interface)-1] == "+":
            interface = interface[:len(interface)-1] + "*"

        action = "goto"

        if interface == "*":
            expr_fragments = [{action: {"target": "%s_%s" % (table, _policy)}}]
        else:
            expr_fragments = [{"match": {"left": {"meta": {"key": opt}},
                                         "op": "==",
                                         "right": interface}},
                              {action: {"target": "%s_%s" % (table, _policy)}}]

        if enable and not append:
            verb = "insert"
            rule = {"family": family,
                    "table": TABLE_NAME,
                    "chain": "%s_%s_ZONES" % (table, chain),
                    "expr": expr_fragments}
            rule.update(self._zone_interface_fragment())
        elif enable:
            verb = "add"
            rule = {"family": family,
                    "table": TABLE_NAME,
                    "chain": "%s_%s_ZONES" % (table, chain),
                    "expr": expr_fragments}
        else:
            verb = "delete"
            rule = {"family": family,
                    "table": TABLE_NAME,
                    "chain": "%s_%s_ZONES" % (table, chain),
                    "expr": expr_fragments}
            if not append:
                rule.update(self._zone_interface_fragment())

        return [{verb: {"rule": rule}}]

    def build_zone_source_address_rules(self, enable, zone, policy,
                                        address, table, chain, family="inet"):
        # nat tables needs to use ip/ip6 family
        if table == "nat" and family == "inet":
            rules = []
            if address.startswith("ipset:"):
                ipset_family = self._set_get_family(address[len("ipset:"):])
            else:
                ipset_family = None

            if check_address("ipv4", address) or check_mac(address) or ipset_family == "ip":
                rules.extend(self.build_zone_source_address_rules(enable, zone, policy,
                                    address, table, chain, "ip"))
            if check_address("ipv6", address) or check_mac(address) or ipset_family == "ip6":
                rules.extend(self.build_zone_source_address_rules(enable, zone, policy,
                                    address, table, chain, "ip6"))
            return rules

        isSNAT = True if (table == "nat" and chain == "POSTROUTING") else False
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX, isSNAT=isSNAT)
        add_del = { True: "insert", False: "delete" }[enable]

        opt = {
            "PREROUTING": "saddr",
            "POSTROUTING": "daddr",
            "INPUT": "saddr",
            "FORWARD_IN": "saddr",
            "FORWARD_OUT": "daddr",
            "OUTPUT": "daddr",
        }[chain]

        if self._fw._allow_zone_drifting:
            zone_dispatch_chain = "%s_%s_ZONES_SOURCE" % (table, chain)
        else:
            zone_dispatch_chain = "%s_%s_ZONES" % (table, chain)

        action = "goto"

        rule = {"family": family,
                "table": TABLE_NAME,
                "chain": zone_dispatch_chain,
                "expr": [self._rule_addr_fragment(opt, address),
                         {action: {"target": "%s_%s" % (table, _policy)}}]}
        rule.update(self._zone_source_fragment(zone, address))
        return [{add_del: {"rule": rule}}]

    def build_policy_chain_rules(self, enable, policy, table, chain, family="inet"):
        # nat tables needs to use ip/ip6 family
        if table == "nat" and family == "inet":
            rules = []
            rules.extend(self.build_policy_chain_rules(enable, policy, table, chain, "ip"))
            rules.extend(self.build_policy_chain_rules(enable, policy, table, chain, "ip6"))
            return rules

        add_del = { True: "add", False: "delete" }[enable]
        isSNAT = True if (table == "nat" and chain == "POSTROUTING") else False
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX, isSNAT=isSNAT)

        rules = []
        rules.append({add_del: {"chain": {"family": family,
                                          "table": TABLE_NAME,
                                          "name": "%s_%s" % (table, _policy)}}})
        for chain_suffix in ["pre", "log", "deny", "allow", "post"]:
            rules.append({add_del: {"chain": {"family": family,
                                              "table": TABLE_NAME,
                                              "name": "%s_%s_%s" % (table, _policy, chain_suffix)}}})

        for chain_suffix in ["pre", "log", "deny", "allow", "post"]:
            rules.append({add_del: {"rule": {"family": family,
                                             "table": TABLE_NAME,
                                             "chain": "%s_%s" % (table, _policy),
                                             "expr": [{"jump": {"target": "%s_%s_%s" % (table, _policy, chain_suffix)}}]}}})

        target = self._fw.policy._policies[policy].target

        if self._fw.get_log_denied() != "off":
            if table == "filter":
                if target in ["REJECT", "%%REJECT%%", "DROP"]:
                    log_suffix = target
                    if target == "%%REJECT%%":
                        log_suffix = "REJECT"
                    rules.append({add_del: {"rule": {"family": family,
                                                     "table": TABLE_NAME,
                                                     "chain": "%s_%s" % (table, _policy),
                                                     "expr": [self._pkttype_match_fragment(self._fw.get_log_denied()),
                                                              {"log": {"prefix": "\"filter_%s_%s: \"" % (_policy, log_suffix)}}]}}})

        if table == "filter" and \
           target in ["ACCEPT", "REJECT", "%%REJECT%%", "DROP"]:
            if target in ["%%REJECT%%", "REJECT"]:
                target_fragment = self._reject_fragment()
            else:
                target_fragment = {target.lower(): None}
            rules.append({add_del: {"rule": {"family": family,
                                             "table": TABLE_NAME,
                                             "chain": "%s_%s" % (table, _policy),
                                             "expr": [target_fragment]}}})

        if not enable:
            rules.reverse()

        return rules

    def _pkttype_match_fragment(self, pkttype):
        if pkttype == "all":
            return {}
        elif pkttype in ["unicast", "broadcast", "multicast"]:
            return {"match": {"left": {"meta": {"key": "pkttype"}},
                               "op": "==",
                               "right": pkttype}}

        raise FirewallError(INVALID_RULE, "Invalid pkttype \"%s\"", pkttype)

    def _reject_types_fragment(self, reject_type):
        frags = {
            # REJECT_TYPES              : <nft reject rule fragment>
            "icmp-host-prohibited"      : {"reject": {"type": "icmp", "expr": "host-prohibited"}},
            "host-prohib"               : {"reject": {"type": "icmp", "expr": "host-prohibited"}},
            "icmp-net-prohibited"       : {"reject": {"type": "icmp", "expr": "net-prohibited"}},
            "net-prohib"                : {"reject": {"type": "icmp", "expr": "net-prohibited"}},
            "icmp-admin-prohibited"     : {"reject": {"type": "icmp", "expr": "admin-prohibited"}},
            "admin-prohib"              : {"reject": {"type": "icmp", "expr": "admin-prohibited"}},
            "icmp6-adm-prohibited"      : {"reject": {"type": "icmpv6", "expr": "admin-prohibited"}},
            "adm-prohibited"            : {"reject": {"type": "icmpv6", "expr": "admin-prohibited"}},

            "icmp-net-unreachable"      : {"reject": {"type": "icmp", "expr": "net-unreachable"}},
            "net-unreach"               : {"reject": {"type": "icmp", "expr": "net-unreachable"}},
            "icmp-host-unreachable"     : {"reject": {"type": "icmp", "expr": "host-unreachable"}},
            "host-unreach"              : {"reject": {"type": "icmp", "expr": "host-unreachable"}},
            "icmp-port-unreachable"     : {"reject": {"type": "icmp", "expr": "port-unreachable"}},
            "icmp6-port-unreachable"    : {"reject": {"type": "icmpv6", "expr": "port-unreachable"}},
            "port-unreach"              : {"reject": {"type": "icmpx", "expr": "port-unreachable"}},
            "icmp-proto-unreachable"    : {"reject": {"type": "icmp", "expr": "prot-unreachable"}},
            "proto-unreach"             : {"reject": {"type": "icmp", "expr": "prot-unreachable"}},
            "icmp6-addr-unreachable"    : {"reject": {"type": "icmpv6", "expr": "addr-unreachable"}},
            "addr-unreach"              : {"reject": {"type": "icmpv6", "expr": "addr-unreachable"}},

            "icmp6-no-route"            : {"reject": {"type": "icmpv6", "expr": "no-route"}},
            "no-route"                  : {"reject": {"type": "icmpv6", "expr": "no-route"}},

            "tcp-reset"                 : {"reject": {"type": "tcp reset"}},
            "tcp-rst"                   : {"reject": {"type": "tcp reset"}},
        }
        return frags[reject_type]

    def _reject_fragment(self):
        return {"reject": {"type": "icmpx",
                           "expr": "admin-prohibited"}}

    def _icmp_match_fragment(self):
        return {"match": {"left": {"meta": {"key": "l4proto"}},
                          "op": "==",
                          "right": {"set": ["icmp", "icmpv6"]}}}

    def _rich_rule_limit_fragment(self, limit):
        if not limit:
            return {}

        rich_to_nft = {
            "s" : "second",
            "m" : "minute",
            "h" : "hour",
            "d" : "day",
        }

        rate, duration = limit.value_parse()

        d = {
            "rate": rate,
            "per": rich_to_nft[duration],
        }

        burst = limit.burst_parse()
        if burst is not None:
            d["burst"] = burst

        return {"limit": d}

    def _rich_rule_chain_suffix(self, rich_rule):
        if type(rich_rule.element) in [Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock]:
            # These are special and don't have an explicit action
            pass
        elif rich_rule.action:
            if type(rich_rule.action) not in [Rich_Accept, Rich_Reject, Rich_Drop, Rich_Mark]:
                raise FirewallError(INVALID_RULE, "Unknown action %s" % type(rich_rule.action))
        else:
            raise FirewallError(INVALID_RULE, "No rule action specified.")

        if rich_rule.priority == 0:
            if type(rich_rule.element) in [Rich_Masquerade, Rich_ForwardPort] or \
               type(rich_rule.action) in [Rich_Accept, Rich_Mark]:
                return "allow"
            elif type(rich_rule.element) in [Rich_IcmpBlock] or \
                 type(rich_rule.action) in [Rich_Reject, Rich_Drop]:
                return "deny"
        elif rich_rule.priority < 0:
            return "pre"
        else:
            return "post"

    def _rich_rule_chain_suffix_from_log(self, rich_rule):
        if not rich_rule.log and not rich_rule.audit:
            raise FirewallError(INVALID_RULE, "Not log or audit")

        if rich_rule.priority == 0:
            return "log"
        elif rich_rule.priority < 0:
            return "pre"
        else:
            return "post"

    def _zone_interface_fragment(self):
        return {"%%ZONE_INTERFACE%%": None}

    def _zone_source_fragment(self, zone, address):
        if check_single_address("ipv6", address):
            address = normalizeIP6(address)
        elif check_address("ipv6", address):
            addr_split = address.split("/")
            address = normalizeIP6(addr_split[0]) + "/" + addr_split[1]
        return {"%%ZONE_SOURCE%%": {"zone": zone, "address": address}}

    def _policy_priority_fragment(self, policy):
        return {"%%POLICY_PRIORITY%%": policy.priority}

    def _rich_rule_priority_fragment(self, rich_rule):
        if not rich_rule or rich_rule.priority == 0:
            return {}
        return {"%%RICH_RULE_PRIORITY%%": rich_rule.priority}

    def _rich_rule_log(self, policy, rich_rule, enable, table, expr_fragments):
        if not rich_rule.log:
            return {}

        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)

        add_del = { True: "add", False: "delete" }[enable]

        chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule)

        log_options = {}
        if rich_rule.log.prefix:
            log_options["prefix"] = "%s" % rich_rule.log.prefix
        if rich_rule.log.level:
            level = "warn" if "warning" == rich_rule.log.level else rich_rule.log.level
            log_options["level"] = "%s" % level

        rule = {"family": "inet",
                "table": TABLE_NAME,
                "chain": "%s_%s_%s" % (table, _policy, chain_suffix),
                "expr": expr_fragments +
                        [self._rich_rule_limit_fragment(rich_rule.log.limit),
                         {"log": log_options}]}
        rule.update(self._rich_rule_priority_fragment(rich_rule))
        return {add_del: {"rule": rule}}

    def _rich_rule_audit(self, policy, rich_rule, enable, table, expr_fragments):
        if not rich_rule.audit:
            return {}

        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)

        add_del = { True: "add", False: "delete" }[enable]

        chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule)
        rule = {"family": "inet",
                "table": TABLE_NAME,
                "chain": "%s_%s_%s" % (table, _policy, chain_suffix),
                "expr": expr_fragments +
                        [self._rich_rule_limit_fragment(rich_rule.audit.limit),
                         {"log": {"level": "audit"}}]}
        rule.update(self._rich_rule_priority_fragment(rich_rule))
        return {add_del: {"rule": rule}}

    def _rich_rule_action(self, policy, rich_rule, enable, table, expr_fragments):
        if not rich_rule.action:
            return {}

        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)

        add_del = { True: "add", False: "delete" }[enable]

        chain_suffix = self._rich_rule_chain_suffix(rich_rule)
        chain = "%s_%s_%s" % (table, _policy, chain_suffix)
        if type(rich_rule.action) == Rich_Accept:
            rule_action = {"accept": None}
        elif type(rich_rule.action) == Rich_Reject:
            if rich_rule.action.type:
                rule_action = self._reject_types_fragment(rich_rule.action.type)
            else:
                rule_action = {"reject": None}
        elif type(rich_rule.action) ==  Rich_Drop:
            rule_action = {"drop": None}
        elif type(rich_rule.action) == Rich_Mark:
            table = "mangle"
            _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)
            chain = "%s_%s_%s" % (table, _policy, chain_suffix)
            value = rich_rule.action.set.split("/")
            if len(value) > 1:
                rule_action = {"mangle": {"key": {"meta": {"key": "mark"}},
                                          "value": {"^": [{"&": [{"meta": {"key": "mark"}}, value[1]]}, value[0]]}}}
            else:
                rule_action = {"mangle": {"key": {"meta": {"key": "mark"}},
                                          "value": value[0]}}

        else:
            raise FirewallError(INVALID_RULE,
                                "Unknown action %s" % type(rich_rule.action))

        rule = {"family": "inet",
                "table": TABLE_NAME,
                "chain": chain,
                "expr": expr_fragments +
                        [self._rich_rule_limit_fragment(rich_rule.action.limit), rule_action]}
        rule.update(self._rich_rule_priority_fragment(rich_rule))
        return {add_del: {"rule": rule}}

    def _rule_addr_fragment(self, addr_field, address, invert=False):
        if address.startswith("ipset:"):
            return self._set_match_fragment(address[len("ipset:"):], True if "daddr" == addr_field else False, invert)
        else:
            if check_mac(address):
                family = "ether"
            elif check_single_address("ipv4", address):
                family = "ip"
            elif check_address("ipv4", address):
                family = "ip"
                normalized_address = ipaddress.IPv4Network(address, strict=False)
                address = {"prefix": {"addr": normalized_address.network_address.compressed, "len": normalized_address.prefixlen}}
            elif check_single_address("ipv6", address):
                family = "ip6"
                address = normalizeIP6(address)
            else:
                family = "ip6"
                addr_len = address.split("/")
                address = {"prefix": {"addr": normalizeIP6(addr_len[0]), "len": int(addr_len[1])}}

            return {"match": {"left": {"payload": {"protocol": family,
                                                   "field": addr_field}},
                              "op": "!=" if invert else "==",
                              "right": address}}

    def _rich_rule_family_fragment(self, rich_family):
        if not rich_family:
            return {}
        if rich_family not in ["ipv4", "ipv6"]:
            raise FirewallError(INVALID_RULE,
                                "Invalid family" % rich_family)

        return {"match": {"left": {"meta": {"key": "nfproto"}},
                          "op": "==",
                          "right": rich_family}}

    def _rich_rule_destination_fragment(self, rich_dest):
        if not rich_dest:
            return {}
        if rich_dest.addr:
            address = rich_dest.addr
        elif rich_dest.ipset:
            address = "ipset:" + rich_dest.ipset

        return self._rule_addr_fragment("daddr", address, invert=rich_dest.invert)

    def _rich_rule_source_fragment(self, rich_source):
        if not rich_source:
            return {}

        if rich_source.addr:
            address = rich_source.addr
        elif hasattr(rich_source, "mac") and rich_source.mac:
            address = rich_source.mac
        elif hasattr(rich_source, "ipset") and rich_source.ipset:
            address = "ipset:" + rich_source.ipset

        return self._rule_addr_fragment("saddr", address, invert=rich_source.invert)

    def _port_fragment(self, port):
        range = getPortRange(port)
        if isinstance(range, int) and range < 0:
            raise FirewallError(INVALID_PORT)
        elif len(range) == 1:
            return range[0]
        else:
            return {"range": [range[0], range[1]]}

    def build_policy_ports_rules(self, enable, policy, proto, port, destination=None, rich_rule=None):
        add_del = { True: "add", False: "delete" }[enable]
        table = "filter"
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)

        expr_fragments = []
        if rich_rule:
            expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family))
        if destination:
            expr_fragments.append(self._rule_addr_fragment("daddr", destination))
        if rich_rule:
            expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination))
            expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source))

        expr_fragments.append({"match": {"left": {"payload": {"protocol": proto,
                                                              "field": "dport"}},
                                         "op": "==",
                                         "right": self._port_fragment(port)}})
        if not rich_rule or type(rich_rule.action) != Rich_Mark:
            expr_fragments.append({"match": {"left": {"ct": {"key": "state"}},
                                             "op": "in",
                                             "right": {"set": ["new", "untracked"]}}})

        rules = []
        if rich_rule:
            rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments))
            rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments))
            rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments))
        else:
            rules.append({add_del: {"rule": {"family": "inet",
                                             "table": TABLE_NAME,
                                             "chain": "%s_%s_allow" % (table, _policy),
                                             "expr": expr_fragments + [{"accept": None}]}}})

        return rules

    def build_policy_protocol_rules(self, enable, policy, protocol, destination=None, rich_rule=None):
        add_del = { True: "add", False: "delete" }[enable]
        table = "filter"
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)

        expr_fragments = []
        if rich_rule:
            expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family))
        if destination:
            expr_fragments.append(self._rule_addr_fragment("daddr", destination))
        if rich_rule:
            expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination))
            expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source))

        expr_fragments.append({"match": {"left": {"meta": {"key": "l4proto"}},
                                         "op": "==",
                                         "right": protocol}})
        if not rich_rule or type(rich_rule.action) != Rich_Mark:
            expr_fragments.append({"match": {"left": {"ct": {"key": "state"}},
                                             "op": "in",
                                             "right": {"set": ["new", "untracked"]}}})

        rules = []
        if rich_rule:
            rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments))
            rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments))
            rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments))
        else:
            rules.append({add_del: {"rule": {"family": "inet",
                                             "table": TABLE_NAME,
                                             "chain": "%s_%s_allow" % (table, _policy),
                                             "expr": expr_fragments + [{"accept": None}]}}})

        return rules

    def build_policy_source_ports_rules(self, enable, policy, proto, port,
                                      destination=None, rich_rule=None):
        add_del = { True: "add", False: "delete" }[enable]
        table = "filter"
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)

        expr_fragments = []
        if rich_rule:
            expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family))
        if destination:
            expr_fragments.append(self._rule_addr_fragment("daddr", destination))
        if rich_rule:
            expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination))
            expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source))

        expr_fragments.append({"match": {"left": {"payload": {"protocol": proto,
                                                              "field": "sport"}},
                                         "op": "==",
                                         "right": self._port_fragment(port)}})
        if not rich_rule or type(rich_rule.action) != Rich_Mark:
            expr_fragments.append({"match": {"left": {"ct": {"key": "state"}},
                                             "op": "in",
                                             "right": {"set": ["new", "untracked"]}}})

        rules = []
        if rich_rule:
            rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments))
            rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments))
            rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments))
        else:
            rules.append({add_del: {"rule": {"family": "inet",
                                             "table": TABLE_NAME,
                                             "chain": "%s_%s_allow" % (table, _policy),
                                             "expr": expr_fragments + [{"accept": None}]}}})

        return rules

    def build_policy_helper_ports_rules(self, enable, policy, proto, port,
                                        destination, helper_name, module_short_name):
        table = "filter"
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)
        add_del = { True: "add", False: "delete" }[enable]
        rules = []

        if enable:
            rules.append({"add": {"ct helper": {"family": "inet",
                                                "table": TABLE_NAME,
                                                "name": "helper-%s-%s" % (helper_name, proto),
                                                "type": module_short_name,
                                                "protocol": proto}}})

        expr_fragments = []
        if destination:
            expr_fragments.append(self._rule_addr_fragment("daddr", destination))
        expr_fragments.append({"match": {"left": {"payload": {"protocol": proto,
                                                              "field": "dport"}},
                                         "op": "==",
                                         "right": self._port_fragment(port)}})
        expr_fragments.append({"ct helper": "helper-%s-%s" % (helper_name, proto)})
        rules.append({add_del: {"rule": {"family": "inet",
                                         "table": TABLE_NAME,
                                         "chain": "filter_%s_allow" % (_policy),
                                         "expr": expr_fragments}}})

        return rules

    def build_zone_forward_rules(self, enable, zone, policy, table, interface=None, source=None):
        add_del = { True: "add", False: "delete" }[enable]
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)

        rules = []

        if interface:
            if interface[len(interface)-1] == "+":
                interface = interface[:len(interface)-1] + "*"

            expr = [{"match": {"left": {"meta": {"key": "oifname"}},
                               "op": "==",
                               "right": interface}},
                    {"accept": None}]
        else: # source
            expr = [self._rule_addr_fragment("daddr", source), {"accept": None}]

        rule = {"family": "inet",
                "table": TABLE_NAME,
                "chain": "filter_%s_allow" % (_policy),
                "expr": expr}
        rules.append({add_del: {"rule": rule}})

        return rules

    def _build_policy_masquerade_nat_rules(self, enable, policy, family, rich_rule=None):
        table = "nat"
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX, isSNAT=True)

        add_del = { True: "add", False: "delete" }[enable]

        expr_fragments = []
        if rich_rule:
            expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination))
            expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source))
            chain_suffix = self._rich_rule_chain_suffix(rich_rule)
        else:
            chain_suffix = "allow"

        rule = {"family": family,
                "table": TABLE_NAME,
                "chain": "nat_%s_%s" % (_policy, chain_suffix),
                "expr": expr_fragments +
                        [{"match": {"left": {"meta": {"key": "oifname"}},
                                    "op": "!=",
                                    "right": "lo"}},
                         {"masquerade": None}]}
        rule.update(self._rich_rule_priority_fragment(rich_rule))
        return [{add_del: {"rule": rule}}]

    def build_policy_masquerade_rules(self, enable, policy, rich_rule=None):
        # nat tables needs to use ip/ip6 family
        rules = []
        if rich_rule and (rich_rule.family and rich_rule.family == "ipv6"
           or rich_rule.source and check_address("ipv6", rich_rule.source.addr)):
            rules.extend(self._build_policy_masquerade_nat_rules(enable, policy, "ip6", rich_rule))
        elif rich_rule and (rich_rule.family and rich_rule.family == "ipv4"
           or rich_rule.source and check_address("ipv4", rich_rule.source.addr)):
            rules.extend(self._build_policy_masquerade_nat_rules(enable, policy, "ip", rich_rule))
        else:
            rules.extend(self._build_policy_masquerade_nat_rules(enable, policy, "ip", rich_rule))

        table = "filter"
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)
        add_del = { True: "add", False: "delete" }[enable]

        expr_fragments = []
        if rich_rule:
            expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination))
            expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source))
            chain_suffix = self._rich_rule_chain_suffix(rich_rule)
        else:
            chain_suffix = "allow"

        rule = {"family": "inet",
                "table": TABLE_NAME,
                "chain": "filter_%s_%s" % (_policy, chain_suffix),
                "expr": expr_fragments +
                        [{"match": {"left": {"ct": {"key": "state"}},
                                    "op": "in",
                                    "right": {"set": ["new", "untracked"]}}},
                         {"accept": None}]}
        rule.update(self._rich_rule_priority_fragment(rich_rule))
        rules.append({add_del: {"rule": rule}})

        return rules

    def _build_policy_forward_port_nat_rules(self, enable, policy, port, protocol,
                                           toaddr, toport, family,
                                           rich_rule=None):
        table = "nat"
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)
        add_del = { True: "add", False: "delete" }[enable]

        expr_fragments = []
        if rich_rule:
            expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination))
            expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source))
            chain_suffix = self._rich_rule_chain_suffix(rich_rule)
        else:
            chain_suffix = "allow"

        expr_fragments.append({"match": {"left": {"payload": {"protocol": protocol,
                                                              "field": "dport"}},
                                         "op": "==",
                                         "right": self._port_fragment(port)}})

        if toaddr:
            if check_single_address("ipv6", toaddr):
                toaddr = normalizeIP6(toaddr)
            if toport and toport != "":
                expr_fragments.append({"dnat": {"addr": toaddr, "port": self._port_fragment(toport)}})
            else:
                expr_fragments.append({"dnat": {"addr": toaddr}})
        else:
            expr_fragments.append({"redirect": {"port": self._port_fragment(toport)}})

        rule = {"family": family,
                "table": TABLE_NAME,
                "chain": "nat_%s_%s" % (_policy, chain_suffix),
                "expr": expr_fragments}
        rule.update(self._rich_rule_priority_fragment(rich_rule))
        return [{add_del: {"rule": rule}}]

    def build_policy_forward_port_rules(self, enable, policy, port,
                                      protocol, toport, toaddr, rich_rule=None):
        rules = []
        if rich_rule and (rich_rule.family and rich_rule.family == "ipv6"
           or toaddr and check_single_address("ipv6", toaddr)):
            rules.extend(self._build_policy_forward_port_nat_rules(enable, policy,
                                port, protocol, toaddr, toport, "ip6", rich_rule))
        elif rich_rule and (rich_rule.family and rich_rule.family == "ipv4"
           or toaddr and check_single_address("ipv4", toaddr)):
            rules.extend(self._build_policy_forward_port_nat_rules(enable, policy,
                                port, protocol, toaddr, toport, "ip", rich_rule))
        else:
            if toaddr and check_single_address("ipv6", toaddr):
                rules.extend(self._build_policy_forward_port_nat_rules(enable, policy,
                                    port, protocol, toaddr, toport, "ip6", rich_rule))
            else:
                rules.extend(self._build_policy_forward_port_nat_rules(enable, policy,
                                    port, protocol, toaddr, toport, "ip", rich_rule))

        return rules

    def _icmp_types_to_nft_fragments(self, ipv, icmp_type):
        if icmp_type in ICMP_TYPES_FRAGMENTS[ipv]:
            return ICMP_TYPES_FRAGMENTS[ipv][icmp_type]
        else:
            raise FirewallError(INVALID_ICMPTYPE,
                                "ICMP type '%s' not supported by %s for %s" % (icmp_type, self.name, ipv))

    def build_policy_icmp_block_rules(self, enable, policy, ict, rich_rule=None):
        table = "filter"
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)
        add_del = { True: "add", False: "delete" }[enable]

        if rich_rule and rich_rule.ipvs:
            ipvs = rich_rule.ipvs
        elif ict.destination:
            ipvs = []
            if "ipv4" in ict.destination:
                ipvs.append("ipv4")
            if "ipv6" in ict.destination:
                ipvs.append("ipv6")
        else:
            ipvs = ["ipv4", "ipv6"]

        rules = []
        for ipv in ipvs:
            if self._fw.policy.query_icmp_block_inversion(policy):
                final_chain = "%s_%s_allow" % (table, _policy)
                target_fragment = {"accept": None}
            else:
                final_chain = "%s_%s_deny" % (table, _policy)
                target_fragment = self._reject_fragment()

            expr_fragments = []
            if rich_rule:
                expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family))
                expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination))
                expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source))
            expr_fragments.extend(self._icmp_types_to_nft_fragments(ipv, ict.name))

            if rich_rule:
                rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments))
                rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments))
                if rich_rule.action:
                    rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments))
                else:
                    chain_suffix = self._rich_rule_chain_suffix(rich_rule)
                    rule = {"family": "inet",
                            "table": TABLE_NAME,
                            "chain": "%s_%s_%s" % (table, _policy, chain_suffix),
                            "expr": expr_fragments + [self._reject_fragment()]}
                    rule.update(self._rich_rule_priority_fragment(rich_rule))
                    rules.append({add_del: {"rule": rule}})
            else:
                if self._fw.get_log_denied() != "off" and not self._fw.policy.query_icmp_block_inversion(policy):
                    rules.append({add_del: {"rule": {"family": "inet",
                                                     "table": TABLE_NAME,
                                                     "chain": final_chain,
                                                     "expr": (expr_fragments +
                                                              [self._pkttype_match_fragment(self._fw.get_log_denied()),
                                                               {"log": {"prefix": "\"%s_%s_ICMP_BLOCK: \"" % (table, policy)}}])}}})
                rules.append({add_del: {"rule": {"family": "inet",
                                                 "table": TABLE_NAME,
                                                 "chain": final_chain,
                                                 "expr": expr_fragments + [target_fragment]}}})

        return rules

    def build_policy_icmp_block_inversion_rules(self, enable, policy):
        table = "filter"
        _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX)
        rules = []
        add_del = { True: "add", False: "delete" }[enable]

        if self._fw.policy.query_icmp_block_inversion(policy):
            target_fragment = self._reject_fragment()
        else:
            target_fragment = {"accept": None}

        # WARN: The "index" used here must be kept in sync with
        # build_policy_chain_rules()
        #
        rules.append({add_del: {"rule": {"family": "inet",
                                         "table": TABLE_NAME,
                                         "chain": "%s_%s" % (table, _policy),
                                         "index": 4,
                                         "expr": [self._icmp_match_fragment(),
                                                  target_fragment]}}})

        if self._fw.get_log_denied() != "off" and self._fw.policy.query_icmp_block_inversion(policy):
            rules.append({add_del: {"rule": {"family": "inet",
                                             "table": TABLE_NAME,
                                             "chain": "%s_%s" % (table, _policy),
                                             "index": 4,
                                             "expr": [self._icmp_match_fragment(),
                                                      self._pkttype_match_fragment(self._fw.get_log_denied()),
                                                      {"log": {"prefix": "%s_%s_ICMP_BLOCK: " % (table, policy)}}]}}})
        return rules

    def build_rpfilter_rules(self, log_denied=False):
        rules = []
        expr_fragments = [{"match": {"left": {"meta": {"key": "nfproto"}},
                                     "op": "==",
                                     "right": "ipv6"}},
                          {"match": {"left": {"fib": {"flags": ["saddr", "iif", "mark"],
                                                      "result": "oif"}},
                                     "op": "==",
                                     "right": False}}]
        if log_denied != "off":
            expr_fragments.append({"log": {"prefix": "rpfilter_DROP: "}})
        expr_fragments.append({"drop": None})

        rules.append({"insert": {"rule": {"family": "inet",
                                          "table": TABLE_NAME,
                                          "chain": "filter_PREROUTING",
                                          "expr": expr_fragments}}})
        # RHBZ#1058505, RHBZ#1575431 (bug in kernel 4.16-4.17)
        rules.append({"insert": {"rule": {"family": "inet",
                                          "table": TABLE_NAME,
                                          "chain": "filter_PREROUTING",
                                          "expr": [{"match": {"left": {"payload": {"protocol": "icmpv6",
                                                                                   "field": "type"}},
                                                              "op": "==",
                                                              "right": {"set": ["nd-router-advert", "nd-neighbor-solicit"]}}},
                                                   {"accept": None}]}}})
        return rules

    def build_rfc3964_ipv4_rules(self):
        daddr_set = ["::0.0.0.0/96", # IPv4 compatible
                     "::ffff:0.0.0.0/96", # IPv4 mapped
                     "2002:0000::/24", # 0.0.0.0/8 (the system has no address assigned yet)
                     "2002:0a00::/24", # 10.0.0.0/8 (private)
                     "2002:7f00::/24", # 127.0.0.0/8 (loopback)
                     "2002:ac10::/28", # 172.16.0.0/12 (private)
                     "2002:c0a8::/32", # 192.168.0.0/16 (private)
                     "2002:a9fe::/32", # 169.254.0.0/16 (IANA Assigned DHCP link-local)
                     "2002:e000::/19", # 224.0.0.0/4 (multicast), 240.0.0.0/4 (reserved and broadcast)
                     ]
        daddr_set = [{"prefix": {"addr": x.split("/")[0], "len": int(x.split("/")[1])}} for x in daddr_set]

        expr_fragments = [{"match": {"left": {"payload": {"protocol": "ip6",
                                                          "field": "daddr"}},
                                     "op": "==",
                                     "right": {"set": daddr_set}}}]
        if self._fw._log_denied in ["unicast", "all"]:
            expr_fragments.append({"log": {"prefix": "RFC3964_IPv4_REJECT: "}})
        expr_fragments.append(self._reject_types_fragment("addr-unreach"))

        rules = []
        # WARN: index must be kept in sync with build_default_rules()
        rules.append({"add": {"rule": {"family": "inet",
                                       "table": TABLE_NAME,
                                       "chain": "filter_OUTPUT",
                                       "index": 1,
                                       "expr": expr_fragments}}})
        rules.append({"add": {"rule": {"family": "inet",
                                       "table": TABLE_NAME,
                                       "chain": "filter_FORWARD",
                                       "index": 2,
                                       "expr": expr_fragments}}})
        return rules

    def build_policy_rich_source_destination_rules(self, enable, policy, rich_rule):
        table = "filter"

        expr_fragments = []
        expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family))
        expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination))
        expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source))

        rules = []
        rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments))
        rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments))
        rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments))

        return rules

    def is_ipv_supported(self, ipv):
        if ipv in ["ipv4", "ipv6", "eb"]:
            return True
        return False

    def _set_type_list(self, ipv, type):
        ipv_addr = {
            "ipv4" : "ipv4_addr",
            "ipv6" : "ipv6_addr",
        }
        types = {
            "hash:ip" : ipv_addr[ipv],
            "hash:ip,port" : [ipv_addr[ipv], "inet_proto", "inet_service"],
            "hash:ip,port,ip" : [ipv_addr[ipv], "inet_proto", "inet_service", ipv_addr[ipv]],
            "hash:ip,port,net" : [ipv_addr[ipv], "inet_proto", "inet_service", ipv_addr[ipv]],
            "hash:ip,mark" : [ipv_addr[ipv], "mark"],

            "hash:net" : ipv_addr[ipv],
            "hash:net,net" : [ipv_addr[ipv], ipv_addr[ipv]],
            "hash:net,port" : [ipv_addr[ipv], "inet_proto", "inet_service"],
            "hash:net,port,net" : [ipv_addr[ipv], "inet_proto", "inet_service", ipv_addr[ipv]],
            "hash:net,iface" : [ipv_addr[ipv], "ifname"],

            "hash:mac" : "ether_addr",
        }
        if type in types:
            return types[type]
        else:
            raise FirewallError(INVALID_TYPE,
                                "ipset type name '%s' is not valid" % type)

    def build_set_create_rules(self, name, type, options=None):
        if options and "family" in options and options["family"] == "inet6":
            ipv = "ipv6"
        else:
            ipv = "ipv4"

        set_dict = {"table": TABLE_NAME,
                    "name": name,
                    "type": self._set_type_list(ipv, type)}

        # Some types need the interval flag
        for t in type.split(":")[1].split(","):
            if t in ["ip", "net", "port"]:
                set_dict["flags"] = ["interval"]
                break

        if options:
            if "timeout" in options:
                set_dict["timeout"] = options["timeout"]
            if "maxelem" in options:
                set_dict["size"] = options["maxelem"]

        rules = []
        for family in ["inet", "ip", "ip6"]:
            rule_dict = {"family": family}
            rule_dict.update(set_dict)
            rules.append({"add": {"set": rule_dict}})

        return rules

    def set_create(self, name, type, options=None):
        rules = self.build_set_create_rules(name, type, options)
        self.set_rules(rules, self._fw.get_log_denied())

    def set_destroy(self, name):
        for family in ["inet", "ip", "ip6"]:
            rule = {"delete": {"set": {"family": family,
                                       "table": TABLE_NAME,
                                       "name": name}}}
            self.set_rule(rule, self._fw.get_log_denied())

    def _set_match_fragment(self, name, match_dest, invert=False):
        type_format = self._fw.ipset.get_ipset(name).type.split(":")[1].split(",")

        fragments = []
        for i in range(len(type_format)):
            if type_format[i] == "port":
                fragments.append({"meta": {"key": "l4proto"}})
                fragments.append({"payload": {"protocol": "th",
                                              "field": "dport" if match_dest else "sport"}})
            elif type_format[i] in ["ip", "net", "mac"]:
                fragments.append({"payload": {"protocol": self._set_get_family(name),
                                              "field": "daddr" if match_dest else "saddr"}})
            elif type_format[i] == "iface":
                fragments.append({"meta": {"key": "iifname" if match_dest else "oifname"}})
            elif type_format[i] == "mark":
                fragments.append({"meta": {"key": "mark"}})
            else:
                raise FirewallError("Unsupported ipset type for match fragment: %s" % (type_format[i]))

        return {"match": {"left": {"concat": fragments} if len(type_format) > 1 else fragments[0],
                          "op": "!=" if invert else "==",
                          "right": "@" + name}}

    def _set_entry_fragment(self, name, entry):
        # convert something like
        #    1.2.3.4,sctp:8080 (type hash:ip,port)
        # to
        #    ["1.2.3.4", "sctp", "8080"]
        obj = self._fw.ipset.get_ipset(name)
        type_format = obj.type.split(":")[1].split(",")
        entry_tokens = entry.split(",")
        if len(type_format) != len(entry_tokens):
            raise FirewallError(INVALID_ENTRY,
                                "Number of values does not match ipset type.")
        fragment = []
        for i in range(len(type_format)):
            if type_format[i] == "port":
                try:
                    index = entry_tokens[i].index(":")
                except ValueError:
                    # no protocol means default tcp
                    fragment.append("tcp")
                    port_str = entry_tokens[i]
                else:
                    fragment.append(entry_tokens[i][:index])
                    port_str = entry_tokens[i][index+1:]

                try:
                    index = port_str.index("-")
                except ValueError:
                    fragment.append(port_str)
                else:
                    fragment.append({"range": [port_str[:index], port_str[index+1:]]})

            elif type_format[i] in ["ip", "net"]:
                if '-' in entry_tokens[i]:
                    fragment.append({"range": entry_tokens[i].split('-') })
                else:
                    try:
                        index = entry_tokens[i].index("/")
                    except ValueError:
                        addr = entry_tokens[i]
                        if "family" in obj.options and obj.options["family"] == "inet6":
                            addr = normalizeIP6(addr)
                        fragment.append(addr)
                    else:
                        addr = entry_tokens[i][:index]
                        if "family" in obj.options and obj.options["family"] == "inet6":
                            addr = normalizeIP6(addr)
                        fragment.append({"prefix": {"addr": addr,
                                                    "len": int(entry_tokens[i][index+1:])}})
            else:
                fragment.append(entry_tokens[i])
        return [{"concat": fragment}] if len(type_format) > 1 else fragment

    def build_set_add_rules(self, name, entry):
        rules = []
        element = self._set_entry_fragment(name, entry)
        for family in ["inet", "ip", "ip6"]:
            rules.append({"add": {"element": {"family": family,
                                              "table": TABLE_NAME,
                                              "name": name,
                                              "elem": element}}})
        return rules

    def set_add(self, name, entry):
        rules = self.build_set_add_rules(name, entry)
        self.set_rules(rules, self._fw.get_log_denied())

    def set_delete(self, name, entry):
        element = self._set_entry_fragment(name, entry)
        for family in ["inet", "ip", "ip6"]:
            rule = {"delete": {"element": {"family": family,
                                           "table": TABLE_NAME,
                                           "name": name,
                                           "elem": element}}}
            self.set_rule(rule, self._fw.get_log_denied())

    def build_set_flush_rules(self, name):
        rules = []
        for family in ["inet", "ip", "ip6"]:
            rule = {"flush": {"set": {"family": family,
                                      "table": TABLE_NAME,
                                      "name": name}}}
            rules.append(rule)
        return rules

    def set_flush(self, name):
        rules = self.build_set_flush_rules(name)
        self.set_rules(rules, self._fw.get_log_denied())

    def _set_get_family(self, name):
        ipset = self._fw.ipset.get_ipset(name)

        if ipset.type == "hash:mac":
            family = "ether"
        elif ipset.options and "family" in ipset.options \
             and ipset.options["family"] == "inet6":
            family = "ip6"
        else:
            family = "ip"

        return family

    def set_restore(self, set_name, type_name, entries,
                    create_options=None, entry_options=None):
        rules = []
        rules.extend(self.build_set_create_rules(set_name, type_name, create_options))
        rules.extend(self.build_set_flush_rules(set_name))

        # avoid large memory usage by chunking the entries
        chunk = 0
        for entry in entries:
            rules.extend(self.build_set_add_rules(set_name, entry))
            chunk += 1
            if chunk >= 1000:
                self.set_rules(rules, self._fw.get_log_denied())
                rules.clear()
                chunk = 0
        else:
            self.set_rules(rules, self._fw.get_log_denied())

Youez - 2016 - github.com/yon3zu
LinuXploit