曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.かつては雑草やヨモギと共に雨や露を分かち合っていたが、今では松やヒノキと共に霜や雪に耐えている。曾与蒿藜同雨露,Once sharing rain and dew with weeds and wormwood, now enduring frost and snow with pines and cypresses.终随松柏到冰霜.曾与蒿藜同雨露한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.,终随松柏到冰霜.譖セ荳手珍阯懷酔髮ィ髴イ�檎サ磯囂譚セ譟丞芦蜀ー髴�曾与蒿藜同雨露,鏇句笌钂胯棞鍚岄洦闇诧紝缁堥殢鏉炬煆鍒板啺闇�终随松柏到冰霜.曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.曾与蒿藜同雨露,终随松柏到冰霜.
# -*- 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