# -*- 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/>.
#
import os.path
import copy
from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET
from firewall.core.prog import runProg
from firewall.core.logger import log
from firewall.functions import splitArgs, check_mac, portStr, \
check_single_address, check_address
from firewall import config
from firewall.errors import FirewallError, UNKNOWN_ERROR, INVALID_RULE, \
INVALID_ICMPTYPE, INVALID_TYPE, INVALID_ENTRY
from firewall.core.rich import Rich_Accept, Rich_Reject, Rich_Drop, Rich_Mark
TABLE_NAME = "firewalld"
# 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": {
"INPUT": ("input", 0 + NFT_HOOK_OFFSET),
"FORWARD": ("forward", 0 + NFT_HOOK_OFFSET),
# "OUTPUT": ("output", 0 + NFT_HOOK_OFFSET),
},
}
OUR_CHAINS = { # chains created by firewalld
# family: { chains ...}
"inet": {},
"ip": {},
"ip6": {},
}
# Most ICMP types are provided by nft, but for the codes we have to use numeric
# values.
#
ICMP_TYPES_FRAGMENT = {
"ipv4" : {
"communication-prohibited" : ["icmp", "type", "destination-unreachable", "icmp", "code", "13"],
"destination-unreachable" : ["icmp", "type", "destination-unreachable"],
"echo-reply" : ["icmp", "type", "echo-reply"],
"echo-request" : ["icmp", "type", "echo-request"],
"fragmentation-needed" : ["icmp", "type", "destination-unreachable", "icmp", "code", "4"],
"host-precedence-violation" : ["icmp", "type", "destination-unreachable", "icmp", "code", "14"],
"host-prohibited" : ["icmp", "type", "destination-unreachable", "icmp", "code", "10"],
"host-redirect" : ["icmp", "type", "redirect", "icmp", "code", "1"],
"host-unknown" : ["icmp", "type", "destination-unreachable", "icmp", "code", "7"],
"host-unreachable" : ["icmp", "type", "destination-unreachable", "icmp", "code", "1"],
"ip-header-bad" : ["icmp", "type", "parameter-problem", "icmp", "code", "1"],
"network-prohibited" : ["icmp", "type", "destination-unreachable", "icmp", "code", "8"],
"network-redirect" : ["icmp", "type", "redirect", "icmp", "code", "0"],
"network-unknown" : ["icmp", "type", "destination-unreachable", "icmp", "code", "6"],
"network-unreachable" : ["icmp", "type", "destination-unreachable", "icmp", "code", "0"],
"parameter-problem" : ["icmp", "type", "parameter-problem"],
"port-unreachable" : ["icmp", "type", "destination-unreachable", "icmp", "code", "3"],
"precedence-cutoff" : ["icmp", "type", "destination-unreachable", "icmp", "code", "15"],
"protocol-unreachable" : ["icmp", "type", "destination-unreachable", "icmp", "code", "2"],
"redirect" : ["icmp", "type", "redirect"],
"required-option-missing" : ["icmp", "type", "parameter-problem", "icmp", "code", "1"],
"router-advertisement" : ["icmp", "type", "router-advertisement"],
"router-solicitation" : ["icmp", "type", "router-solicitation"],
"source-quench" : ["icmp", "type", "source-quench"],
"source-route-failed" : ["icmp", "type", "destination-unreachable", "icmp", "code", "5"],
"time-exceeded" : ["icmp", "type", "time-exceeded"],
"timestamp-reply" : ["icmp", "type", "timestamp-reply"],
"timestamp-request" : ["icmp", "type", "timestamp-request"],
"tos-host-redirect" : ["icmp", "type", "redirect", "icmp", "code", "3"],
"tos-host-unreachable" : ["icmp", "type", "destination-unreachable", "icmp", "code", "12"],
"tos-network-redirect" : ["icmp", "type", "redirect", "icmp", "code", "2"],
"tos-network-unreachable" : ["icmp", "type", "destination-unreachable", "icmp", "code", "11"],
"ttl-zero-during-reassembly" : ["icmp", "type", "time-exceeded", "icmp", "code", "1"],
"ttl-zero-during-transit" : ["icmp", "type", "time-exceeded", "icmp", "code", "0"],
},
"ipv6" : {
"address-unreachable" : ["icmpv6", "type", "destination-unreachable", "icmpv6", "code", "3"],
"bad-header" : ["icmpv6", "type", "parameter-problem", "icmpv6", "code", "0"],
"beyond-scope" : ["icmpv6", "type", "destination-unreachable", "icmpv6", "code", "2"],
"communication-prohibited" : ["icmpv6", "type", "destination-unreachable", "icmpv6", "code", "1"],
"destination-unreachable" : ["icmpv6", "type", "destination-unreachable"],
"echo-reply" : ["icmpv6", "type", "echo-reply"],
"echo-request" : ["icmpv6", "type", "echo-request"],
"failed-policy" : ["icmpv6", "type", "destination-unreachable", "icmpv6", "code", "5"],
"neighbour-advertisement" : ["icmpv6", "type", "nd-neighbor-advert"],
"neighbour-solicitation" : ["icmpv6", "type", "nd-neighbor-solicit"],
"no-route" : ["icmpv6", "type", "destination-unreachable", "icmpv6", "code", "0"],
"packet-too-big" : ["icmpv6", "type", "packet-too-big"],
"parameter-problem" : ["icmpv6", "type", "parameter-problem"],
"port-unreachable" : ["icmpv6", "type", "destination-unreachable", "icmpv6", "code", "4"],
"redirect" : ["icmpv6", "type", "nd-redirect"],
"reject-route" : ["icmpv6", "type", "destination-unreachable", "icmpv6", "code", "6"],
"router-advertisement" : ["icmpv6", "type", "nd-router-advert"],
"router-solicitation" : ["icmpv6", "type", "nd-router-solicit"],
"time-exceeded" : ["icmpv6", "type", "time-exceeded"],
"ttl-zero-during-reassembly" : ["icmpv6", "type", "time-exceeded", "icmpv6", "code", "1"],
"ttl-zero-during-transit" : ["icmpv6", "type", "time-exceeded", "icmpv6", "code", "0"],
"unknown-header-type" : ["icmpv6", "type", "parameter-problem", "icmpv6", "code", "1"],
"unknown-option" : ["icmpv6", "type", "parameter-problem", "icmpv6", "code", "2"],
}
}
class nftables(object):
name = "nftables"
zones_supported = True
def __init__(self, fw):
self._fw = fw
self._command = config.COMMANDS["nft"]
self.fill_exists()
self.available_tables = []
self.rule_to_handle = {}
self.rule_ref_count = {}
self.zone_source_index_cache = {}
def fill_exists(self):
self.command_exists = os.path.exists(self._command)
self.restore_command_exists = False
def _run_replace_zone_source(self, rule_add, rule, zone_source_index_cache):
try:
i = rule.index("%%ZONE_SOURCE%%")
rule.pop(i)
zone = rule.pop(i)
zone_source = (zone, rule[7]) # (zone, address)
except ValueError:
try:
i = rule.index("%%ZONE_INTERFACE%%")
rule.pop(i)
zone_source = None
except ValueError:
return
family = rule[2]
if zone_source and not rule_add:
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 rule_add:
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])
if index == 0:
rule[0] = "insert"
else:
index -= 1 # point to the rule before insertion point
rule[0] = "add"
rule.insert(i, "index")
rule.insert(i+1, "%d" % index)
def __run(self, args):
nft_opts = ["--echo", "--handle"]
_args = args[:]
# If we're deleting a table (i.e. build_flush_rules())
# then check if its exist first to avoid nft throwing an error
if _args[0] == "delete" and _args[1] == "table":
_args_test = _args[:]
_args_test[0] = "list"
(status, output) = runProg(self._command, nft_opts + _args_test)
if status != 0:
return ""
rule_key = None
if _args[0] in ["add", "insert"] and _args[1] == "rule":
rule_add = True
rule_key = _args[2:]
if rule_key[3] == "position":
# strip "position #"
# "insert rule family table chain position <num>"
# ^^ rule_key starts here
try:
int(rule_key[4])
except Exception:
raise FirewallError(INVALID_RULE, "position without a number")
else:
rule_key.pop(3)
rule_key.pop(3)
rule_key = " ".join(rule_key)
elif _args[0] in ["delete"] and _args[1] == "rule":
rule_add = False
rule_key = _args[2:]
rule_key = " ".join(rule_key)
# rule deduplication
if rule_key in self.rule_ref_count:
if rule_add:
self.rule_ref_count[rule_key] += 1
return ""
if not rule_add and self.rule_ref_count[rule_key] > 1:
self.rule_ref_count[rule_key] -= 1
return ""
elif self.rule_ref_count[rule_key] == 1:
self.rule_ref_count[rule_key] -= 1
else:
raise FirewallError(UNKNOWN_ERROR, "rule ref count bug: rule_key '%s', cnt %d"
% (rule_key, self.rule_ref_count[rule_key]))
log.debug2("%s: rule ref cnt %d, %s %s", self.__class__,
self.rule_ref_count[rule_key], self._command, " ".join(_args))
if rule_key:
zone_source_index_cache = copy.deepcopy(self.zone_source_index_cache)
self._run_replace_zone_source(rule_add, _args, zone_source_index_cache)
if not rule_key or (not rule_add and self.rule_ref_count[rule_key] == 0) \
or ( rule_add and rule_key not in self.rule_ref_count):
# delete using rule handle
if rule_key and not rule_add:
_args = ["delete", "rule"] + _args[2:5] + \
["handle", self.rule_to_handle[rule_key]]
_args_str = " ".join(_args)
log.debug2("%s: %s %s", self.__class__, self._command, _args_str)
(status, output) = runProg(self._command, nft_opts + _args)
if status != 0:
raise ValueError("'%s %s' failed: %s" % (self._command,
_args_str, output))
if rule_key:
self.zone_source_index_cache = zone_source_index_cache
# nft requires deleting rules by handle. So we must cache the rule
# handle when adding/inserting rules.
#
if rule_key:
if rule_add:
str = "# handle "
offset = output.index(str) + len(str)
self.rule_to_handle[rule_key] = output[offset:].strip()
self.rule_ref_count[rule_key] = 1
else:
del self.rule_to_handle[rule_key]
del self.rule_ref_count[rule_key]
return output
def _rule_replace(self, rule, pattern, replacement):
try:
i = rule.index(pattern)
except ValueError:
return False
else:
rule[i:i+1] = replacement
return True
def reverse_rule(self, args):
ret_args = args[:]
ret_args[0] = "delete"
return ret_args
def set_rules(self, rules, log_denied):
# We can't support using "nft -f" because we need to retrieve the
# handles for each rules so we can delete them later on.
# See also: self.restore_command_exists
#
# We can implement this once libnftables in ready.
#
raise FirewallError(UNKNOWN_ERROR, "not implemented")
def set_rule(self, rule, log_denied):
# replace %%REJECT%%
#
# HACK: work around nft bug in which icmpx does not work if the rule
# has qualified the ip family.
icmp_keyword = "icmpx"
if "ipv4" in rule or "ip" in rule or "icmp" in rule:
icmp_keyword = "icmp"
elif "ipv6" in rule or "ip6" in rule or "icmpv6" in rule:
icmp_keyword = "icmpv6"
self._rule_replace(rule, "%%REJECT%%",
["reject", "with", icmp_keyword, "type", "admin-prohibited"])
# replace %%ICMP%%
self._rule_replace(rule, "%%ICMP%%", ["meta", "l4proto", "{icmp, icmpv6}"])
# replace %%LOGTYPE%%
try:
i = rule.index("%%LOGTYPE%%")
except ValueError:
pass
else:
if log_denied == "off":
return ""
if log_denied in ["unicast", "broadcast", "multicast"]:
rule[i:i+1] = ["pkttype", log_denied]
else:
rule.pop(i)
return self.__run(rule)
def get_available_tables(self, table=None):
# Tables always exist in nftables
return [table] if table else IPTABLES_TO_NFT_HOOK.keys()
def build_flush_rules(self):
self.rule_to_handle = {}
self.rule_ref_count = {}
self.zone_source_index_cache = {}
rules = []
for family in OUR_CHAINS.keys():
rules.append(["delete", "table", family, "%s" % TABLE_NAME])
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 initially starting and for panic mode. As such, using
# hooks with a higher priority than our base chains is sufficient.
#
table_name = TABLE_NAME + "_" + "policy_drop"
rules = []
if policy == "DROP":
rules.append(["add", "table", "inet", table_name])
# To drop everything we need to use the "raw" priority. These occur
# before conntrack, mangle, nat, etc
for hook in ["prerouting", "output"]:
_add_chain = "add chain inet %s %s_%s '{ type filter hook %s priority %d ; policy drop ; }'" % \
(table_name, "raw", hook, hook, -300 + NFT_HOOK_OFFSET - 1)
rules.append(splitArgs(_add_chain))
elif policy == "ACCEPT":
rules.append(["delete", "table", "inet", table_name])
else:
FirewallError(UNKNOWN_ERROR, "not implemented")
return rules
def supported_icmp_types(self):
# nftables supports any icmp_type via arbitrary type/code matching.
# We just need a translation for it in ICMP_TYPES_FRAGMENT.
supported = set()
for ipv in ICMP_TYPES_FRAGMENT.keys():
supported.update(ICMP_TYPES_FRAGMENT[ipv].keys())
return list(supported)
def build_default_tables(self):
default_tables = []
for family in OUR_CHAINS.keys():
default_tables.append("add table %s %s" % (family, TABLE_NAME))
return map(splitArgs, default_tables)
def build_default_rules(self, log_denied="off"):
default_rules = []
OUR_CHAINS["inet"]["raw"] = set()
for chain in IPTABLES_TO_NFT_HOOK["raw"].keys():
default_rules.append("add chain inet %s raw_%s '{ type filter hook %s priority %d ; }'" %
(TABLE_NAME, chain,
IPTABLES_TO_NFT_HOOK["raw"][chain][0],
IPTABLES_TO_NFT_HOOK["raw"][chain][1]))
for dispatch_suffix in ["ZONES_SOURCE", "ZONES"] if self._fw._allow_zone_drifting else ["ZONES"]:
default_rules.append("add chain inet %s raw_%s_%s" % (TABLE_NAME, chain, dispatch_suffix))
default_rules.append("add rule inet %s raw_%s jump raw_%s_%s" % (TABLE_NAME, chain, chain, dispatch_suffix))
OUR_CHAINS["inet"]["raw"].update(set(["%s_%s" % (chain, dispatch_suffix)]))
OUR_CHAINS["inet"]["mangle"] = set()
for chain in IPTABLES_TO_NFT_HOOK["mangle"].keys():
default_rules.append("add chain inet %s mangle_%s '{ type filter hook %s priority %d ; }'" %
(TABLE_NAME, chain,
IPTABLES_TO_NFT_HOOK["mangle"][chain][0],
IPTABLES_TO_NFT_HOOK["mangle"][chain][1]))
for dispatch_suffix in ["ZONES_SOURCE", "ZONES"] if self._fw._allow_zone_drifting else ["ZONES"]:
default_rules.append("add chain inet %s mangle_%s_%s" % (TABLE_NAME, chain, dispatch_suffix))
default_rules.append("add rule inet %s mangle_%s jump mangle_%s_%s" % (TABLE_NAME, chain, chain, dispatch_suffix))
OUR_CHAINS["inet"]["mangle"].update(set(["%s_%s" % (chain, dispatch_suffix)]))
OUR_CHAINS["ip"]["nat"] = set()
OUR_CHAINS["ip6"]["nat"] = set()
for family in ["ip", "ip6"]:
for chain in IPTABLES_TO_NFT_HOOK["nat"].keys():
default_rules.append("add chain %s %s nat_%s '{ type nat hook %s priority %d ; }'" %
(family, TABLE_NAME, chain,
IPTABLES_TO_NFT_HOOK["nat"][chain][0],
IPTABLES_TO_NFT_HOOK["nat"][chain][1]))
for dispatch_suffix in ["ZONES_SOURCE", "ZONES"] if self._fw._allow_zone_drifting else ["ZONES"]:
default_rules.append("add chain %s %s nat_%s_%s" % (family, TABLE_NAME, chain, dispatch_suffix))
default_rules.append("add rule %s %s nat_%s jump nat_%s_%s" % (family, TABLE_NAME, chain, chain, dispatch_suffix))
OUR_CHAINS[family]["nat"].update(set(["%s_%s" % (chain, dispatch_suffix)]))
OUR_CHAINS["inet"]["filter"] = set()
for chain in IPTABLES_TO_NFT_HOOK["filter"].keys():
default_rules.append("add chain inet %s filter_%s '{ type filter hook %s priority %d ; }'" %
(TABLE_NAME, chain,
IPTABLES_TO_NFT_HOOK["filter"][chain][0],
IPTABLES_TO_NFT_HOOK["filter"][chain][1]))
# filter, INPUT
default_rules.append("add rule inet %s filter_%s ct state established,related accept" % (TABLE_NAME, "INPUT"))
default_rules.append("add rule inet %s filter_%s iifname lo accept" % (TABLE_NAME, "INPUT"))
for dispatch_suffix in ["ZONES_SOURCE", "ZONES"] if self._fw._allow_zone_drifting else ["ZONES"]:
default_rules.append("add chain inet %s filter_%s_%s" % (TABLE_NAME, "INPUT", dispatch_suffix))
default_rules.append("add rule inet %s filter_%s jump filter_%s_%s" % (TABLE_NAME, "INPUT", "INPUT", dispatch_suffix))
if log_denied != "off":
default_rules.append("add rule inet %s filter_%s ct state invalid %%%%LOGTYPE%%%% log prefix '\"STATE_INVALID_DROP: \"'" % (TABLE_NAME, "INPUT"))
default_rules.append("add rule inet %s filter_%s ct state invalid drop" % (TABLE_NAME, "INPUT"))
if log_denied != "off":
default_rules.append("add rule inet %s filter_%s %%%%LOGTYPE%%%% log prefix '\"FINAL_REJECT: \"'" % (TABLE_NAME, "INPUT"))
default_rules.append("add rule inet %s filter_%s reject with icmpx type admin-prohibited" % (TABLE_NAME, "INPUT"))
# filter, FORWARD
default_rules.append("add chain inet %s filter_%s_IN_ZONES" % (TABLE_NAME, "FORWARD"))
default_rules.append("add rule inet %s filter_%s ct state established,related accept" % (TABLE_NAME, "FORWARD"))
default_rules.append("add rule inet %s filter_%s iifname lo accept" % (TABLE_NAME, "FORWARD"))
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 inet %s filter_%s_%s_%s" % (TABLE_NAME, "FORWARD", direction, dispatch_suffix))
default_rules.append("add rule inet %s filter_%s jump filter_%s_%s_%s" % (TABLE_NAME, "FORWARD", "FORWARD", direction, dispatch_suffix))
if log_denied != "off":
default_rules.append("add rule inet %s filter_%s ct state invalid %%%%LOGTYPE%%%% log prefix '\"STATE_INVALID_DROP: \"'" % (TABLE_NAME, "FORWARD"))
default_rules.append("add rule inet %s filter_%s ct state invalid drop" % (TABLE_NAME, "FORWARD"))
if log_denied != "off":
default_rules.append("add rule inet %s filter_%s %%%%LOGTYPE%%%% log prefix '\"FINAL_REJECT: \"'" % (TABLE_NAME, "FORWARD"))
default_rules.append("add rule inet %s filter_%s reject with icmpx type admin-prohibited" % (TABLE_NAME, "FORWARD"))
OUR_CHAINS["inet"]["filter"] = set(["INPUT_ZONES_SOURCE",
"INPUT_ZONES",
"FORWARD_IN_ZONES_SOURCE",
"FORWARD_IN_ZONES",
"FORWARD_OUT_ZONES_SOURCE",
"FORWARD_OUT_ZONES"])
return map(splitArgs, 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"]
if table == "raw":
return ["PREROUTING"]
return {}
def build_zone_source_interface_rules(self, enable, zone, 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,
interface, table, chain, append, "ip"))
rules.extend(self.build_zone_source_interface_rules(enable, zone,
interface, table, chain, append, "ip6"))
return rules
# handle all zones in the same way here, now
# trust and block zone targets are handled now in __chain
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] + "*"
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone)
action = "goto"
if enable and not append:
rule = ["insert", "rule", family, "%s" % TABLE_NAME, "%s_%s_ZONES" % (table, chain),
"%%ZONE_INTERFACE%%"]
elif enable:
rule = ["add", "rule", family, "%s" % TABLE_NAME, "%s_%s_ZONES" % (table, chain)]
else:
rule = ["delete", "rule", family, "%s" % TABLE_NAME, "%s_%s_ZONES" % (table, chain)]
if not append:
rule += ["%%ZONE_INTERFACE%%"]
if interface == "*":
rule += [action, "%s_%s" % (table, target)]
else:
rule += [opt, "\"" + interface + "\"", action, "%s_%s" % (table, target)]
return [rule]
def build_zone_source_address_rules(self, enable, zone,
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,
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,
address, table, chain, "ip6"))
return rules
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)
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone)
action = "goto"
if address.startswith("ipset:"):
ipset = address[len("ipset:"):]
rule_family = self._set_get_family(ipset)
address = "@" + ipset
else:
if check_mac(address):
# outgoing can not be set
if opt == "daddr":
return ""
rule_family = "ether"
elif check_address("ipv4", address):
rule_family = "ip"
else:
rule_family = "ip6"
rule = [add_del, "rule", family, "%s" % TABLE_NAME,
zone_dispatch_chain,
"%%ZONE_SOURCE%%", zone,
rule_family, opt, address, action, "%s_%s" % (table, target)]
return [rule]
def build_zone_chain_rules(self, zone, table, chain, family="inet"):
# nat tables needs to use ip/ip6 family
if table == "nat" and family == "inet":
rules = []
rules.extend(self.build_zone_chain_rules(zone, table, chain, "ip"))
rules.extend(self.build_zone_chain_rules(zone, table, chain, "ip6"))
return rules
_zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone)
OUR_CHAINS[family][table].update(set([_zone,
"%s_log" % _zone,
"%s_deny" % _zone,
"%s_allow" % _zone]))
rules = []
rules.append(["add", "chain", family, "%s" % TABLE_NAME,
"%s_%s" % (table, _zone)])
rules.append(["add", "chain", family, "%s" % TABLE_NAME,
"%s_%s_log" % (table, _zone)])
rules.append(["add", "chain", family, "%s" % TABLE_NAME,
"%s_%s_deny" % (table, _zone)])
rules.append(["add", "chain", family, "%s" % TABLE_NAME,
"%s_%s_allow" % (table, _zone)])
rules.append(["add", "rule", family, "%s" % TABLE_NAME,
"%s_%s" % (table, _zone),
"jump", "%s_%s_log" % (table, _zone)])
rules.append(["add", "rule", family, "%s" % TABLE_NAME,
"%s_%s" % (table, _zone),
"jump", "%s_%s_deny" % (table, _zone)])
rules.append(["add", "rule", family, "%s" % TABLE_NAME,
"%s_%s" % (table, _zone),
"jump", "%s_%s_allow" % (table, _zone)])
target = self._fw.zone._zones[zone].target
if self._fw.get_log_denied() != "off":
if table == "filter" and \
chain in ["INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT"]:
if target in ["REJECT", "%%REJECT%%", "DROP"]:
log_suffix = target
if target == "%%REJECT%%":
log_suffix = "REJECT"
rules.append(["add", "rule", family, "%s" % TABLE_NAME,
"%s_%s" % (table, _zone), "%%LOGTYPE%%",
"log", "prefix",
"\"filter_%s_%s: \"" % (_zone, log_suffix)])
# Handle trust, block and drop zones:
# Add an additional rule with the zone target (accept, reject
# or drop) to the base zone only in the filter table.
# Otherwise it is not be possible to have a zone with drop
# target, that is allowing traffic that is locally initiated
# or that adds additional rules. (RHBZ#1055190)
if table == "filter" and \
target in ["ACCEPT", "REJECT", "%%REJECT%%", "DROP"] and \
chain in ["INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT"]:
rules.append(["add", "rule", family, "%s" % TABLE_NAME,
"%s_%s" % (table, _zone),
target.lower() if target != "%%REJECT%%" else "%%REJECT%%"])
return rules
def _reject_types_fragment(self, reject_type):
frags = {
# REJECT_TYPES : <nft reject rule fragment>
"icmp-host-prohibited" : ["with", "icmp", "type", "host-prohibited"],
"host-prohib" : ["with", "icmp", "type", "host-prohibited"],
"icmp-net-prohibited" : ["with", "icmp", "type", "net-prohibited"],
"net-prohib" : ["with", "icmp", "type", "net-prohibited"],
"icmp-admin-prohibited" : ["with", "icmp", "type", "admin-prohibited"],
"admin-prohib" : ["with", "icmp", "type", "admin-prohibited"],
"icmp6-adm-prohibited" : ["with", "icmpv6", "type", "admin-prohibited"],
"adm-prohibited" : ["with", "icmpv6", "type", "admin-prohibited"],
"icmp-net-unreachable" : ["with", "icmp", "type", "net-unreachable"],
"net-unreach" : ["with", "icmp", "type", "net-unreachable"],
"icmp-host-unreachable" : ["with", "icmp", "type", "host-unreachable"],
"host-unreach" : ["with", "icmp", "type", "host-unreachable"],
"icmp-port-unreachable" : ["with", "icmp", "type", "port-unreachable"],
"icmp6-port-unreachable" : ["with", "icmpv6", "type", "port-unreachable"],
"port-unreach" : ["with", "icmpx", "type", "port-unreachable"],
"icmp-proto-unreachable" : ["with", "icmp", "type", "prot-unreachable"],
"proto-unreach" : ["with", "icmp", "type", "prot-unreachable"],
"icmp6-addr-unreachable" : ["with", "icmpv6", "type", "addr-unreachable"],
"addr-unreach" : ["with", "icmpv6", "type", "addr-unreachable"],
"icmp6-no-route" : ["with", "icmpv6", "type", "no-route"],
"no-route" : ["with", "icmpv6", "type", "no-route"],
"tcp-reset" : ["with", "tcp", "reset"],
"tcp-rst" : ["with", "tcp", "reset"],
}
return frags[reject_type]
def _rich_rule_limit_fragment(self, limit):
if not limit:
return []
rich_to_nft = {
"s" : "second",
"m" : "minute",
"h" : "hour",
"d" : "day",
}
try:
i = limit.value.index("/")
except ValueError:
raise FirewallError(INVALID_RULE, "Expected '/' in limit")
return ["limit", "rate", limit.value[0:i], "/",
rich_to_nft[limit.value[i+1]]]
def _rich_rule_log(self, rich_rule, enable, table, target, rule_fragment):
if not rich_rule.log:
return []
add_del = { True: "add", False: "delete" }[enable]
rule = [add_del, "rule", "inet", "%s" % TABLE_NAME,
"%s_%s_log" % (table, target)]
rule += rule_fragment + ["log"]
if rich_rule.log.prefix:
rule += ["prefix", "\"%s\"" % rich_rule.log.prefix]
if rich_rule.log.level:
rule += ["level", '"%s"' % rich_rule.log.level]
rule += self._rich_rule_limit_fragment(rich_rule.log.limit)
return rule
def _rich_rule_audit(self, rich_rule, enable, table, target, rule_fragment):
if not rich_rule.audit:
return []
add_del = { True: "add", False: "delete" }[enable]
rule = [add_del, "rule", "inet", "%s" % TABLE_NAME,
"%s_%s_log" % (table, target)]
rule += rule_fragment + ["log", "level", "audit"]
rule += self._rich_rule_limit_fragment(rich_rule.audit.limit)
return rule
def _rich_rule_action(self, zone, rich_rule, enable, table, target, rule_fragment):
if not rich_rule.action:
return []
add_del = { True: "add", False: "delete" }[enable]
if type(rich_rule.action) == Rich_Accept:
chain = "%s_%s_allow" % (table, target)
rule_action = ["accept"]
elif type(rich_rule.action) == Rich_Reject:
chain = "%s_%s_deny" % (table, target)
rule_action = ["reject"]
if rich_rule.action.type:
rule_action += self._reject_types_fragment(rich_rule.action.type)
elif type(rich_rule.action) == Rich_Drop:
chain = "%s_%s_deny" % (table, target)
rule_action = ["drop"]
elif type(rich_rule.action) == Rich_Mark:
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"],
zone=zone)
table = "mangle"
chain = "%s_%s_allow" % (table, target)
rule_action = ["meta", "mark", "set", rich_rule.action.set]
else:
raise FirewallError(INVALID_RULE,
"Unknown action %s" % type(rich_rule.action))
rule = [add_del, "rule", "inet", "%s" % TABLE_NAME, chain]
rule += rule_fragment
rule += self._rich_rule_limit_fragment(rich_rule.action.limit)
rule += rule_action
return rule
def _rich_rule_family_fragment(self, rich_family):
if not rich_family:
return []
if rich_family == "ipv4":
return ["meta", "nfproto", "ipv4"]
if rich_family == "ipv6":
return ["meta", "nfproto", "ipv6"]
raise FirewallError(INVALID_RULE,
"Invalid family" % rich_family)
def _rich_rule_destination_fragment(self, rich_dest):
if not rich_dest:
return []
rule_fragment = []
if check_address("ipv4", rich_dest.addr):
rule_fragment += ["ip"]
else:
rule_fragment += ["ip6"]
if rich_dest.invert:
rule_fragment += ["daddr", "!=", rich_dest.addr]
else:
rule_fragment += ["daddr", rich_dest.addr]
return rule_fragment
def _rich_rule_source_fragment(self, rich_source):
if not rich_source:
return []
rule_fragment = []
if rich_source.addr:
if check_address("ipv4", rich_source.addr):
rule_fragment += ["ip"]
else:
rule_fragment += ["ip6"]
if rich_source.invert:
rule_fragment += ["saddr", "!=", rich_source.addr]
else:
rule_fragment += ["saddr", rich_source.addr]
elif hasattr(rich_source, "mac") and rich_source.mac:
if rich_source.invert:
rule_fragment += ["ether", "saddr", "!=", rich_source.mac]
else:
rule_fragment += ["ether", "saddr", rich_source.mac]
elif hasattr(rich_source, "ipset") and rich_source.ipset:
family = self._set_get_family(rich_source.ipset)
if rich_source.invert:
rule_fragment += [family, "saddr", "!=", "@" + rich_source.ipset]
else:
rule_fragment += [family, "saddr", "@" + rich_source.ipset]
return rule_fragment
def build_zone_ports_rules(self, enable, zone, proto, port, destination=None, rich_rule=None):
add_del = { True: "add", False: "delete" }[enable]
table = "filter"
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone)
rule_fragment = []
if rich_rule:
rule_fragment += self._rich_rule_family_fragment(rich_rule.family)
if destination:
if check_address("ipv4", destination):
rule_fragment += ["ip"]
else:
rule_fragment += ["ip6"]
rule_fragment += ["daddr", destination]
if rich_rule:
rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination)
rule_fragment += self._rich_rule_source_fragment(rich_rule.source)
rule_fragment += [proto, "dport", "%s" % portStr(port, "-")]
if not rich_rule or type(rich_rule.action) != Rich_Mark:
rule_fragment += ["ct", "state", "new,untracked"]
rules = []
if rich_rule:
rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment))
rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment))
rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment))
else:
rules.append([add_del, "rule", "inet", "%s" % TABLE_NAME,
"%s_%s_allow" % (table, target)] +
rule_fragment + ["accept"])
return rules
def build_zone_protocol_rules(self, enable, zone, protocol, destination=None, rich_rule=None):
add_del = { True: "add", False: "delete" }[enable]
table = "filter"
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone)
rule_fragment = []
if rich_rule:
rule_fragment += self._rich_rule_family_fragment(rich_rule.family)
if destination:
if check_address("ipv4", destination):
rule_fragment += ["ip"]
else:
rule_fragment += ["ip6"]
rule_fragment += ["daddr", destination]
if rich_rule:
rule_fragment += self._rich_rule_family_fragment(rich_rule.family)
rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination)
rule_fragment += self._rich_rule_source_fragment(rich_rule.source)
rule_fragment = ["meta", "l4proto", protocol]
if not rich_rule or type(rich_rule.action) != Rich_Mark:
rule_fragment += ["ct", "state", "new,untracked"]
rules = []
if rich_rule:
rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment))
rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment))
rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment))
else:
rules.append([add_del, "rule", "inet", "%s" % TABLE_NAME,
"filter_%s_allow" % (target)] +
rule_fragment + ["accept"])
return rules
def build_zone_source_ports_rules(self, enable, zone, proto, port,
destination=None, rich_rule=None):
add_del = { True: "add", False: "delete" }[enable]
table = "filter"
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone)
rule_fragment = []
if rich_rule:
rule_fragment += self._rich_rule_family_fragment(rich_rule.family)
if destination:
if check_address("ipv4", destination):
rule_fragment += ["ip"]
else:
rule_fragment += ["ip6"]
rule_fragment += ["daddr", destination]
if rich_rule:
rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination)
rule_fragment += self._rich_rule_source_fragment(rich_rule.source)
rule_fragment += [proto, "sport", "%s" % portStr(port, "-")]
if not rich_rule or type(rich_rule.action) != Rich_Mark:
rule_fragment += ["ct", "state", "new,untracked"]
rules = []
if rich_rule:
rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment))
rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment))
rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment))
else:
rules.append([add_del, "rule", "inet", "%s" % TABLE_NAME,
"%s_%s_allow" % (table, target)] +
rule_fragment + ["accept"])
return rules
def build_zone_helper_ports_rules(self, enable, zone, proto, port,
destination, helper_name, module_short_name):
add_del = { True: "add", False: "delete" }[enable]
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"],
zone=zone)
rule = [add_del, "rule", "inet", "%s" % TABLE_NAME,
"filter_%s_allow" % (target)]
if destination:
if check_address("ipv4", destination):
rule += ["ip"]
else:
rule += ["ip6"]
rule += ["daddr", destination]
rule += [proto, "dport", "%s" % portStr(port, "-")]
rule += ["ct", "helper", "set", "\"helper-%s-%s\"" % (helper_name, proto)]
helper_object = ["ct", "helper", "inet", TABLE_NAME,
"helper-%s-%s" % (helper_name, proto),
"{", "type", "\"%s\"" % (module_short_name), "protocol",
proto, ";", "}"]
return [helper_object, rule]
def _build_zone_masquerade_nat_rules(self, enable, zone, family, rich_rule=None):
add_del = { True: "add", False: "delete" }[enable]
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["POSTROUTING"],
zone=zone)
rule_fragment = []
if rich_rule:
rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination)
rule_fragment += self._rich_rule_source_fragment(rich_rule.source)
return [[add_del, "rule", family, "%s" % TABLE_NAME,
"nat_%s_allow" % (target)]
+ rule_fragment + ["oifname", "!=", "lo", "masquerade"]]
def build_zone_masquerade_rules(self, enable, zone, 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_zone_masquerade_nat_rules(enable, zone, "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_zone_masquerade_nat_rules(enable, zone, "ip", rich_rule))
else:
rules.extend(self._build_zone_masquerade_nat_rules(enable, zone, "ip", rich_rule))
add_del = { True: "add", False: "delete" }[enable]
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["FORWARD_OUT"],
zone=zone)
rule_fragment = []
if rich_rule:
rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination)
rule_fragment += self._rich_rule_source_fragment(rich_rule.source)
rules.append([add_del, "rule", "inet", "%s" % TABLE_NAME,
"filter_%s_allow" % (target)]
+ rule_fragment + ["ct", "state", "new,untracked", "accept"])
return rules
def _build_zone_forward_port_nat_rules(self, enable, zone, protocol,
mark_fragment, toaddr, toport,
family):
add_del = { True: "add", False: "delete" }[enable]
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"],
zone=zone)
dnat_fragment = []
if toaddr:
dnat_fragment += ["dnat", "to", toaddr]
else:
dnat_fragment += ["redirect", "to"]
if toport and toport != "":
dnat_fragment += [":%s" % portStr(toport, "-")]
return [[add_del, "rule", family, "%s" % TABLE_NAME,
"nat_%s_allow" % (target), "meta", "l4proto", protocol]
+ mark_fragment + dnat_fragment]
def build_zone_forward_port_rules(self, enable, zone, filter_chain, port,
protocol, toport, toaddr, mark_id, rich_rule=None):
add_del = { True: "add", False: "delete" }[enable]
mark_str = "0x%x" % mark_id
mark_fragment = ["meta", "mark", mark_str]
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"],
zone=zone)
rule_fragment = []
if rich_rule:
rule_fragment += self._rich_rule_family_fragment(rich_rule.family)
rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination)
rule_fragment += self._rich_rule_source_fragment(rich_rule.source)
rules = []
rules.append([add_del, "rule", "inet", "%s" % TABLE_NAME,
"mangle_%s_allow" % (target)]
+ rule_fragment +
[protocol, "dport", port, "meta", "mark", "set", mark_str])
if rich_rule and (rich_rule.family and rich_rule.family == "ipv6"
or toaddr and check_single_address("ipv6", toaddr)):
rules.extend(self._build_zone_forward_port_nat_rules(enable, zone,
protocol, mark_fragment, toaddr, toport, "ip6"))
elif rich_rule and (rich_rule.family and rich_rule.family == "ipv4"
or toaddr and check_single_address("ipv4", toaddr)):
rules.extend(self._build_zone_forward_port_nat_rules(enable, zone,
protocol, mark_fragment, toaddr, toport, "ip"))
else:
if toaddr and check_single_address("ipv6", toaddr):
rules.extend(self._build_zone_forward_port_nat_rules(enable, zone,
protocol, mark_fragment, toaddr, toport, "ip6"))
else:
rules.extend(self._build_zone_forward_port_nat_rules(enable, zone,
protocol, mark_fragment, toaddr, toport, "ip"))
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[filter_chain],
zone=zone)
rules.append([add_del, "rule", "inet", "%s" % TABLE_NAME,
"filter_%s_allow" % (target), "ct", "state", "new,untracked"]
+ mark_fragment + ["accept"])
return rules
def _icmp_types_to_nft_fragment(self, ipv, icmp_type):
if icmp_type in ICMP_TYPES_FRAGMENT[ipv]:
return ICMP_TYPES_FRAGMENT[ipv][icmp_type]
else:
raise FirewallError(INVALID_ICMPTYPE,
"ICMP type '%s' not supported by %s" % (icmp_type, self.name))
def build_zone_icmp_block_rules(self, enable, zone, ict, rich_rule=None):
table = "filter"
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:
for chain in ["INPUT", "FORWARD_IN"]:
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain],
zone=zone)
if self._fw.zone.query_icmp_block_inversion(zone):
final_chain = "%s_%s_allow" % (table, target)
final_target = "accept"
else:
final_chain = "%s_%s_deny" % (table, target)
final_target = "%%REJECT%%"
rule_fragment = []
if rich_rule:
rule_fragment += self._rich_rule_family_fragment(rich_rule.family)
rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination)
rule_fragment += self._rich_rule_source_fragment(rich_rule.source)
rule_fragment += self._icmp_types_to_nft_fragment(ipv, ict.name)
if rich_rule:
rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment))
rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment))
if rich_rule.action:
rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment))
else:
rules.append([add_del, "rule", "inet", "%s" % TABLE_NAME,
"%s_%s_deny" % (table, target)]
+ rule_fragment + ["%%REJECT%%"])
else:
if self._fw.get_log_denied() != "off" and final_target != "accept":
rules.append([add_del, "rule", "inet", "%s" % TABLE_NAME,
final_chain] + rule_fragment +
["%%LOGTYPE%%", "log", "prefix",
"\"%s_%s_ICMP_BLOCK: \"" % (table, zone)])
rules.append([add_del, "rule", "inet", "%s" % TABLE_NAME,
final_chain] + rule_fragment + [final_target])
return rules
def build_zone_icmp_block_inversion_rules(self, enable, zone):
table = "filter"
rules = []
for chain in ["INPUT", "FORWARD_IN"]:
_zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain],
zone=zone)
# HACK: nft position is actually a handle, so we need to lookup the
# handle of the rule we want to insert this after.
#
# This must be kept in sync with build_zone_chain_rules()
#
# WARN: This does not work if we haven't executed the transaction
# yet, because we don't have a handle for our rule_key!! As such,
# we execute transactions before calling this function.
#
rule_key = " ".join(["inet", "%s" % TABLE_NAME,
"%s_%s" % (table, _zone),
"jump", "%s_%s_allow" % (table, _zone)])
rule_handle = self.rule_to_handle[rule_key]
if self._fw.zone.query_icmp_block_inversion(zone):
ibi_target = "%%REJECT%%"
else:
ibi_target = "accept"
if enable:
# FIXME: can we get rid of position ?
rule = ["add", "rule", "inet", "%s" % TABLE_NAME,
"%s_%s" % (table, _zone), "position", rule_handle]
else:
rule = ["delete", "rule", "inet", "%s" % TABLE_NAME,
"%s_%s" % (table, _zone)]
rule += ["%%ICMP%%", ibi_target]
rules.append(rule)
if self._fw.zone.query_icmp_block_inversion(zone):
if self._fw.get_log_denied() != "off":
if enable:
# FIXME: can we get rid of position ?
rule = ["add", "rule", "inet", "%s" % TABLE_NAME,
"%s_%s" % (table, _zone), "position", rule_handle]
else:
rule = ["delete", "rule", "inet", "%s" % TABLE_NAME,
"%s_%s" % (table, _zone)]
rule += ["%%ICMP%%", "%%LOGTYPE%%", "log", "prefix",
"\"%s_%s_ICMP_BLOCK: \"" % (table, _zone)]
rules.append(rule)
return rules
def build_rpfilter_rules(self, log_denied=False):
rules = []
rules.append(["insert", "rule", "inet", "%s" % TABLE_NAME,
"raw_%s" % "PREROUTING",
"meta", "nfproto", "ipv6", "fib", "saddr", ".", "iif",
"oif", "missing", "drop"])
if log_denied != "off":
rules.append(["insert", "rule", "inet", "%s" % TABLE_NAME,
"raw_%s" % "PREROUTING",
"meta", "nfproto", "ipv6", "fib", "saddr", ".", "iif",
"oif", "missing", "log", "prefix", "\"rpfilter_DROP: \""])
rules.append(["insert", "rule", "inet", "%s" % TABLE_NAME,
"raw_%s" % "PREROUTING",
"icmpv6", "type", "{ nd-router-advert, nd-neighbor-solicit }",
"accept"]) # RHBZ#1058505, RHBZ#1575431 (bug in kernel 4.16-4.17)
return rules
def build_zone_rich_source_destination_rules(self, enable, zone, rich_rule):
table = "filter"
target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"],
zone=zone)
rule_fragment = []
rule_fragment += self._rich_rule_family_fragment(rich_rule.family)
rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination)
rule_fragment += self._rich_rule_source_fragment(rich_rule.source)
rules = []
rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment))
rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment))
rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment))
return rules
def is_ipv_supported(self, ipv):
if ipv in ["ipv4", "ipv6", "eb"]:
return True
return False
def _set_type_fragment(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,port" : [ipv_addr[ipv], ". inet_proto", ". inet_service"],
"hash:net,port,ip" : [ipv_addr[ipv], ". inet_proto", ". inet_service .", ipv_addr[ipv]],
"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"],
}
try:
return ["type"] + types[type] + [";"]
except KeyError:
raise FirewallError(INVALID_TYPE,
"ipset type name '%s' is not valid" % type)
def set_create(self, name, type, options=None):
if options and "family" in options and options["family"] == "inet6":
ipv = "ipv6"
else:
ipv = "ipv4"
cmd = [name, "{"]
cmd += self._set_type_fragment(ipv, type)
if options:
if "timeout" in options:
cmd += ["timeout", options["timeout"]+ "s", ";"]
if "maxelem" in options:
cmd += ["size", options["maxelem"], ";"]
# flag "interval" currently does not work with timeouts or
# concatenations. See rhbz 1576426, 1576430.
if (not options or "timeout" not in options) \
and "," not in type: # e.g. hash:net,port
cmd += ["flags", "interval", ";"]
cmd += ["}"]
for family in ["inet", "ip", "ip6"]:
self.__run(["add", "set", family, TABLE_NAME] + cmd)
def set_destroy(self, name):
for family in ["inet", "ip", "ip6"]:
self.__run(["delete", "set", family, TABLE_NAME, 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
type_format = self._fw.ipset.get_type(name).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 += ["tcp", ".", entry_tokens[i]]
else:
fragment += [entry_tokens[i][:index], ".", entry_tokens[i][index+1:]]
else:
fragment.append(entry_tokens[i])
fragment.append(".")
return fragment[:-1] # snip last concat operator
def set_add(self, name, entry):
for family in ["inet", "ip", "ip6"]:
self.__run(["add", "element", family, TABLE_NAME, name, "{"]
+ self._set_entry_fragment(name, entry) + ["}"])
def set_delete(self, name, entry):
for family in ["inet", "ip", "ip6"]:
self.__run(["delete", "element", family, TABLE_NAME, name, "{"]
+ self._set_entry_fragment(name, entry) + ["}"])
def set_flush(self, name):
for family in ["inet", "ip", "ip6"]:
self.__run(["flush", "set", family, TABLE_NAME, name])
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