?
Current Path : /lib/python2.7/site-packages/firewall/core/ |
Linux gator3171.hostgator.com 4.19.286-203.ELK.el7.x86_64 #1 SMP Wed Jun 14 04:33:55 CDT 2023 x86_64 |
Current File : //lib/python2.7/site-packages/firewall/core/nftables.py |
# -*- coding: utf-8 -*- # # Copyright (C) 2018 Red Hat, Inc. # # Authors: # Eric Garver <e@erig.me> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # 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