Blob Blame History Raw
Adapted versions of

commit d72384cfc3eba7db5739f83a28f3476b9553c856
Author: Jiri Popelka <jpopelka@redhat.com>
Date:   Fri Jul 25 14:12:19 2014 +0200

    Check built-in chains in direct chain handling functions. (RHBZ#1120619)
    
    also rename ipXtables/ebtables.CHAINS to BUILT_IN_CHAINS

commit d4c839f838d0772b19d521ff826065e14f9a569d
Author: Jiri Popelka <jpopelka@redhat.com>
Date:   Mon Aug 4 15:59:54 2014 +0200

    Direct & LockdownWhitelist: clear() -> cleanup()
    
    these were renamed in fb656f53bc

commit fb656f53bc0eac095694ba61af6933632abf0f20
Author: Thomas Woerner <twoerner@redhat.com>
Date:   Tue Oct 22 17:21:55 2013 +0200

    Fix cleanup and initializations to get leaked memory to 0 at all times

commit 07550d550e618a3040153341eb8218551c3aa776
Author: Jiri Popelka <jpopelka@redhat.com>
Date:   Tue Sep 30 16:39:41 2014 +0200

    permanent direct: more tests for ipv & table
    
    see also 9139b468e5

commit 9139b468e5ffbe515dfd9892401eadb13a293a0b
Author: Jiri Popelka <jpopelka@redhat.com>
Date:   Tue Jul 15 18:21:47 2014 +0200

    FirewallDirect: check ipv & table sooner to provide consistent errors
    
    thanks to Jakub Jelen

commit 1a5670befb208018196b4f897fb84033e544f886
Author: Jiri Popelka <jpopelka@redhat.com>
Date:   Tue Oct 14 09:46:46 2014 +0200

    Rich_Rule.check(): action can't be used with icmp-block/forward-port/masquerade

commit 76751826d97577fe2b41abf8c5448c653df49651
Author: Thomas Woerner <twoerner@redhat.com>
Date:   Tue Feb 11 23:34:09 2014 +0100

    firewalld: No load failed error for absent direct.xml file

commit 524438c41fae5a0b239d2273871ffe54c61e65de
Author: Thomas Woerner <twoerner@redhat.com>
Date:   Tue Jul 7 13:01:12 2015 +0200

    fw.py._start: Fix reload with runtime rules, but no direct.xml (RHBZ#1183008)

diff -up firewalld-0.3.9/src/firewall/core/ebtables.py.RHBZ#1183008 firewalld-0.3.9/src/firewall/core/ebtables.py
--- firewalld-0.3.9/src/firewall/core/ebtables.py.RHBZ#1183008	2015-07-07 13:10:10.938698154 +0200
+++ firewalld-0.3.9/src/firewall/core/ebtables.py	2015-07-07 13:10:11.074695768 +0200
@@ -25,7 +25,7 @@ from firewall.core.logger import log
 PROC_IPxTABLE_NAMES = {
 }
 
-CHAINS = {
+BUILT_IN_CHAINS = {
     "broute": [ "BROUTING" ],
     "nat": [ "PREROUTING", "POSTROUTING", "OUTPUT" ],
     "filter": [ "INPUT", "OUTPUT", "FORWARD" ],
@@ -60,7 +60,7 @@ class ebtables:
 
     def available_tables(self, table=None):
         ret = []
-        tables = [ table ] if table else CHAINS.keys()
+        tables = [ table ] if table else BUILT_IN_CHAINS.keys()
         for table in tables:
             try:
                 self.__run(["-t", table, "-L"])
@@ -71,7 +71,7 @@ class ebtables:
         return ret
 
     def used_tables(self):
-        return list(CHAINS.keys())
+        return list(BUILT_IN_CHAINS.keys())
 
     def flush(self):
         tables = self.used_tables()
@@ -86,13 +86,13 @@ class ebtables:
         if which == "used":
             tables = self.used_tables()
         else:
-            tables = list(CHAINS.keys())
+            tables = list(BUILT_IN_CHAINS.keys())
 
         if "nat" in tables:
             tables.remove("nat") # nat can not set policies in nat table
 
         for table in tables:
-            for chain in CHAINS[table]:
+            for chain in BUILT_IN_CHAINS[table]:
                 self.__run([ "-t", table, "-P", chain, policy ])
 
 ebtables_available_tables = ebtables().available_tables()
diff -up firewalld-0.3.9/src/firewall/core/fw_config.py.RHBZ#1183008 firewalld-0.3.9/src/firewall/core/fw_config.py
--- firewalld-0.3.9/src/firewall/core/fw_config.py.RHBZ#1183008	2013-12-03 14:59:48.000000000 +0100
+++ firewalld-0.3.9/src/firewall/core/fw_config.py	2015-07-07 13:13:50.709829789 +0200
@@ -131,7 +131,7 @@ class FirewallConfig:
 
     def update_direct(self):
         if not os.path.exists(FIREWALLD_DIRECT):
-            self._direct.clear()
+            self._direct.cleanup()
         else:
             self._direct.read()
 
diff -up firewalld-0.3.9/src/firewall/core/fw_direct.py.RHBZ#1183008 firewalld-0.3.9/src/firewall/core/fw_direct.py
--- firewalld-0.3.9/src/firewall/core/fw_direct.py.RHBZ#1183008	2015-07-07 13:10:11.072695804 +0200
+++ firewalld-0.3.9/src/firewall/core/fw_direct.py	2015-07-07 13:10:11.075695751 +0200
@@ -132,9 +132,41 @@ class FirewallDirect:
                     except FirewallError as error:
                         log.warning(str(error))
 
+    def _check_ipv(self, ipv):
+        ipvs = ['ipv4', 'ipv6', 'eb']
+        if ipv not in ipvs:
+            raise FirewallError(INVALID_IPV,
+                                "'%s' not in '%s'" % (ipv, ipvs))
+
+    def _check_ipv_table(self, ipv, table):
+        self._check_ipv(ipv)
+
+        tables = ipXtables.BUILT_IN_CHAINS.keys() if ipv in [ 'ipv4', 'ipv6' ] \
+                                         else ebtables.BUILT_IN_CHAINS.keys()
+        if table not in tables:
+            raise FirewallError(INVALID_TABLE,
+                                "'%s' not in '%s'" % (table, tables))
+
+    def _check_builtin_chain(self, ipv, table, chain):
+        if ipv in ['ipv4', 'ipv6']:
+            built_in_chains = ipXtables.BUILT_IN_CHAINS[table]
+            our_chains = ipXtables.OUR_CHAINS[table]
+        else:
+            built_in_chains = ebtables.BUILT_IN_CHAINS[table]
+            our_chains = ebtables.OUR_CHAINS[table]
+        if chain in built_in_chains:
+            raise FirewallError(BUILTIN_CHAIN,
+                 "chain '%s' is built-in chain" % chain)
+        if chain in our_chains:
+            raise FirewallError(BUILTIN_CHAIN,
+                 "chain '%s' is reserved" % chain)
+
+
     # DIRECT CHAIN
 
     def __chain(self, add, ipv, table, chain):
+        self._check_ipv_table(ipv, table)
+        self._check_builtin_chain(ipv, table, chain)
         table_id = (ipv, table)
 
         if add:
@@ -174,11 +206,14 @@ class FirewallDirect:
         self.__chain(False, ipv, table, chain)
 
     def query_chain(self, ipv, table, chain):
+        self._check_ipv_table(ipv, table)
+        self._check_builtin_chain(ipv, table, chain)
         table_id = (ipv, table)
-        return (table_id in self._chains and \
-                    chain in self._chains[table_id])
+        return (table_id in self._chains and
+                   chain in self._chains[table_id])
 
     def get_chains(self, ipv, table):
+        self._check_ipv_table(ipv, table)
         table_id = (ipv, table)
         if table_id in self._chains:
             return self._chains[table_id]
@@ -195,13 +230,14 @@ class FirewallDirect:
     # DIRECT RULE
 
     def __rule(self, enable, ipv, table, chain, priority, args):
+        self._check_ipv_table(ipv, table)
         _chain = chain
         # use "%s_chain" for built-in chains
 
         if ipv in [ "ipv4", "ipv6" ]:
-            _CHAINS = ipXtables.CHAINS
+            _CHAINS = ipXtables.BUILT_IN_CHAINS
         else:
-            _CHAINS = ebtables.CHAINS
+            _CHAINS = ebtables.BUILT_IN_CHAINS
 
         if table in _CHAINS and chain in _CHAINS[table]:
             _chain = "%s_direct" % (chain)
@@ -303,11 +339,13 @@ class FirewallDirect:
         self.__rule(False, ipv, table, chain, priority, args)
 
     def query_rule(self, ipv, table, chain, priority, args):
+        self._check_ipv_table(ipv, table)
         chain_id = (ipv, table, chain)
         return (chain_id in self._rules and \
                 (priority, args) in self._rules[chain_id])
 
     def get_rules(self, ipv, table, chain):
+        self._check_ipv_table(ipv, table)
         chain_id = (ipv, table, chain)
         if chain_id in self._rules:
             return list(self._rules[chain_id].keys())
@@ -332,12 +370,6 @@ class FirewallDirect:
 
     # DIRECT PASSTHROUGH (tracked)
 
-    def _check_ipv(self, ipv):
-        ipvs = [ 'ipv4', 'ipv6', 'eb' ]
-        if ipv not in ipvs:
-            raise FirewallError(INVALID_IPV,
-                                "'%s' not in '%s'" % (ipv, ipvs))
-
     def __passthrough(self, enable, ipv, args):
         self._check_ipv(ipv)
 
diff -up firewalld-0.3.9/src/firewall/core/fw.py.RHBZ#1183008 firewalld-0.3.9/src/firewall/core/fw.py
--- firewalld-0.3.9/src/firewall/core/fw.py.RHBZ#1183008	2015-07-07 13:10:11.072695804 +0200
+++ firewalld-0.3.9/src/firewall/core/fw.py	2015-07-07 13:10:11.075695751 +0200
@@ -203,15 +203,16 @@ class Firewall:
         self.zone.apply_zones()
 
         # load direct rules
-        log.debug1("Loading direct rules file '%s'" % FIREWALLD_DIRECT)
         obj = Direct(FIREWALLD_DIRECT)
-        try:
-            obj.read()
-        except Exception as msg:
-            log.debug1("Failed to load direct rules file '%s': %s",
-                      FIREWALLD_DIRECT, msg)
-        else:
-            self.direct.set_permanent_config(obj)
+        if os.path.exists(FIREWALLD_DIRECT):
+            log.debug1("Loading direct rules file '%s'" % FIREWALLD_DIRECT)
+            try:
+                obj.read()
+            except Exception as msg:
+                log.debug1("Failed to load direct rules file '%s': %s",
+                           FIREWALLD_DIRECT, msg)
+
+        self.direct.set_permanent_config(obj)
         self.config.set_direct(copy.deepcopy(obj))
 
         # check if default_zone is a valid zone
@@ -394,7 +395,7 @@ class Firewall:
                 rule.pop(1)
 
             table = None
-            for t in ipXtables.CHAINS.keys():
+            for t in ipXtables.BUILT_IN_CHAINS.keys():
                 if t in rule:
                     table = t
             if table and not self.is_table_available(ipv, table):
diff -up firewalld-0.3.9/src/firewall/core/fw_zone.py.RHBZ#1183008 firewalld-0.3.9/src/firewall/core/fw_zone.py
--- firewalld-0.3.9/src/firewall/core/fw_zone.py.RHBZ#1183008	2015-07-07 13:10:11.068695874 +0200
+++ firewalld-0.3.9/src/firewall/core/fw_zone.py	2015-07-07 13:10:11.076695733 +0200
@@ -26,7 +26,8 @@ from firewall.functions import portStr,
     checkProtocol, enable_ip_forwarding, check_single_address
 from firewall.core.rich import *
 from firewall.errors import *
-from firewall.core.ipXtables import ip4tables_available_tables, ip6tables_available_tables
+from firewall.core.ipXtables import ip4tables_available_tables,\
+    ip6tables_available_tables, OUR_CHAINS
 
 mangle = []
 if "mangle" in ip4tables_available_tables:
@@ -187,6 +188,10 @@ class FirewallZone:
                 ipvs.append("ipv6")
 
             for ipv in ipvs:
+                OUR_CHAINS[table].update(set([_zone,
+                                              "%s_log" % _zone,
+                                              "%s_deny" % _zone,
+                                              "%s_allow" % _zone]))
                 chains.append((ipv, [ _zone, "-t", table ]))
                 chains.append((ipv, [ "%s_log" % (_zone), "-t", table ]))
                 chains.append((ipv, [ "%s_deny" % (_zone), "-t", table ]))
diff -up firewalld-0.3.9/src/firewall/core/io/direct.py.RHBZ#1183008 firewalld-0.3.9/src/firewall/core/io/direct.py
--- firewalld-0.3.9/src/firewall/core/io/direct.py.RHBZ#1183008	2015-07-07 13:10:11.030696540 +0200
+++ firewalld-0.3.9/src/firewall/core/io/direct.py	2015-07-07 13:10:11.076695733 +0200
@@ -29,6 +29,9 @@ from firewall.functions import splitArgs
 from firewall.errors import *
 from firewall.core.io.io_object import *
 from firewall.core.logger import log
+from firewall.core import ipXtables
+from firewall.core import ebtables
+
 
 class direct_ContentHandler(IO_Object_ContentHandler):
     def __init__(self, item):
@@ -188,9 +191,25 @@ class Direct(IO_Object):
             for args in self.passthroughs[key]:
                 print ("    ('%s')" % ("','".join(args)))
 
+    def _check_ipv(self, ipv):
+        ipvs = ['ipv4', 'ipv6', 'eb']
+        if ipv not in ipvs:
+            raise FirewallError(INVALID_IPV,
+                                "'%s' not in '%s'" % (ipv, ipvs))
+
+    def _check_ipv_table(self, ipv, table):
+        self._check_ipv(ipv)
+
+        tables = ipXtables.BUILT_IN_CHAINS.keys() if ipv in ['ipv4', 'ipv6'] \
+                                         else ebtables.BUILT_IN_CHAINS.keys()
+        if table not in tables:
+            raise FirewallError(INVALID_TABLE,
+                                "'%s' not in '%s'" % (table, tables))
+
     # chains
 
     def add_chain(self, ipv, table, chain):
+        self._check_ipv_table(ipv, table)
         key = (ipv, table)
         if key not in self.chains:
             self.chains[key] = [ ]
@@ -202,6 +221,7 @@ class Direct(IO_Object):
                         + "already in list, ignoring")
 
     def remove_chain(self, ipv, table, chain):
+        self._check_ipv_table(ipv, table)
         key = (ipv, table)
         if key in self.chains and chain in self.chains[key]:
             self.chains[key].remove(chain)
@@ -213,10 +233,12 @@ class Direct(IO_Object):
                 (chain, table, ipv))
 
     def query_chain(self, ipv, table, chain):
+        self._check_ipv_table(ipv, table)
         key = (ipv, table)
         return (key in self.chains and chain in self.chains[key])
 
     def get_chains(self, ipv, table):
+        self._check_ipv_table(ipv, table)
         key = (ipv, table)
         if key in self.chains:
             return self.chains[key]
@@ -230,6 +252,7 @@ class Direct(IO_Object):
     # rules
 
     def add_rule(self, ipv, table, chain, priority, args):
+        self._check_ipv_table(ipv, table)
         key = (ipv, table, chain)
         if key not in self.rules:
             self.rules[key] = LastUpdatedOrderedDict()
@@ -243,6 +266,7 @@ class Direct(IO_Object):
                         + "already in list, ignoring")
 
     def remove_rule(self, ipv, table, chain, priority, args):
+        self._check_ipv_table(ipv, table)
         key = (ipv, table, chain)
         value = (priority, tuple(args))
         if key in self.rules and value in self.rules[key]:
@@ -255,6 +279,7 @@ class Direct(IO_Object):
                 "with ipv '%s' and priority %d not in list" % (ipv, priority))
 
     def remove_rules(self, ipv, table, chain):
+        self._check_ipv_table(ipv, table)
         key = (ipv, table, chain)
         if key in self.rules:
             for value in self.rules[key].keys():
@@ -263,11 +288,13 @@ class Direct(IO_Object):
                 del self.rules[key]
 
     def query_rule(self, ipv, table, chain, priority, args):
+        self._check_ipv_table(ipv, table)
         key = (ipv, table, chain)
         value = (priority, tuple(args))
         return (key in self.rules and value in self.rules[key])
 
     def get_rules(self, ipv, table, chain):
+        self._check_ipv_table(ipv, table)
         key = (ipv, table, chain)
         if key in self.rules:
             return self.rules[key]
@@ -281,6 +308,7 @@ class Direct(IO_Object):
 #    # passthrough
 #
     def add_passthrough(self, ipv, args):
+        self._check_ipv(ipv)
         if ipv not in self.passthroughs:
             self.passthroughs[ipv] = [ ]
         if args not in self.passthroughs[ipv]:
@@ -291,6 +319,7 @@ class Direct(IO_Object):
                         + "already in list, ignoring")
 
     def remove_passthrough(self, ipv, args):
+        self._check_ipv(ipv)
         if ipv in self.passthroughs and args in self.passthroughs[ipv]:
             self.passthroughs[ipv].remove(args)
             if len(self.passthroughs[ipv]) == 0:
@@ -300,9 +329,11 @@ class Direct(IO_Object):
                 ("',".join(args), ipv) + "not in list"
 
     def query_passthrough(self, ipv, args):
+        self._check_ipv(ipv)
         return (ipv in self.passthroughs and args in self.passthroughs[ipv])
 
     def get_passthroughs(self, ipv):
+        self._check_ipv(ipv)
         if ipv in self.passthroughs:
             return self.passthroughs[ipv]
         else:
diff -up firewalld-0.3.9/src/firewall/core/ipXtables.py.RHBZ#1183008 firewalld-0.3.9/src/firewall/core/ipXtables.py
--- firewalld-0.3.9/src/firewall/core/ipXtables.py.RHBZ#1183008	2015-07-07 13:10:11.066695909 +0200
+++ firewalld-0.3.9/src/firewall/core/ipXtables.py	2015-07-07 13:10:11.076695733 +0200
@@ -34,7 +34,7 @@ PROC_IPxTABLE_NAMES = {
     "ipv6": "/proc/net/ip6_tables_names",
 }
 
-CHAINS = {
+BUILT_IN_CHAINS = {
     "security": [ "INPUT", "OUTPUT", "FORWARD" ],
     "raw": [ "PREROUTING", "OUTPUT" ],
     "mangle": [ "PREROUTING", "POSTROUTING", "INPUT", "OUTPUT", "FORWARD" ],
@@ -53,38 +53,49 @@ ICMP = {
 }
 
 DEFAULT_RULES = { }
+OUR_CHAINS = {} # chains created by firewalld
 
 DEFAULT_RULES["security"] = [ ]
-for chain in CHAINS["security"]:
+OUR_CHAINS["security"] = set()
+for chain in BUILT_IN_CHAINS["security"]:
     DEFAULT_RULES["security"].append("-N %s_direct" % chain)
     DEFAULT_RULES["security"].append("-I %s 1 -j %s_direct" % (chain, chain))
+    OUR_CHAINS["security"].add("%s_direct" % chain)
 
 DEFAULT_RULES["raw"] = [ ]
-for chain in CHAINS["raw"]:
+OUR_CHAINS["raw"] = set()
+for chain in BUILT_IN_CHAINS["raw"]:
     DEFAULT_RULES["raw"].append("-N %s_direct" % chain)
     DEFAULT_RULES["raw"].append("-I %s 1 -j %s_direct" % (chain, chain))
+    OUR_CHAINS["raw"].add("%s_direct" % chain)
 
 DEFAULT_RULES["mangle"] = [ ]
-for chain in CHAINS["mangle"]:
+OUR_CHAINS["mangle"] = set()
+for chain in BUILT_IN_CHAINS["mangle"]:
     DEFAULT_RULES["mangle"].append("-N %s_direct" % chain)
     DEFAULT_RULES["mangle"].append("-I %s 1 -j %s_direct" % (chain, chain))
+    OUR_CHAINS["mangle"].add("%s_direct" % chain)
 
     if chain == "PREROUTING":
         DEFAULT_RULES["mangle"].append("-N %s_ZONES_SOURCE" % chain)
         DEFAULT_RULES["mangle"].append("-N %s_ZONES" % chain)
         DEFAULT_RULES["mangle"].append("-I %s 2 -j %s_ZONES_SOURCE" % (chain, chain))
         DEFAULT_RULES["mangle"].append("-I %s 3 -j %s_ZONES" % (chain, chain))
+        OUR_CHAINS["mangle"].update(set(["%s_ZONES_SOURCE" % chain, "%s_ZONES" % chain]))
 
 DEFAULT_RULES["nat"] = [ ]
-for chain in CHAINS["nat"]:
+OUR_CHAINS["nat"] = set()
+for chain in BUILT_IN_CHAINS["nat"]:
     DEFAULT_RULES["nat"].append("-N %s_direct" % chain)
     DEFAULT_RULES["nat"].append("-I %s 1 -j %s_direct" % (chain, chain))
+    OUR_CHAINS["nat"].add("%s_direct" % chain)
 
     if chain in [ "PREROUTING", "POSTROUTING" ]:
         DEFAULT_RULES["nat"].append("-N %s_ZONES_SOURCE" % chain)
         DEFAULT_RULES["nat"].append("-N %s_ZONES" % chain)
         DEFAULT_RULES["nat"].append("-I %s 2 -j %s_ZONES_SOURCE" % (chain, chain))
         DEFAULT_RULES["nat"].append("-I %s 3 -j %s_ZONES" % (chain, chain))
+        OUR_CHAINS["nat"].update(set(["%s_ZONES_SOURCE" % chain, "%s_ZONES" % chain]))
 
 DEFAULT_RULES["filter"] = [
     "-N INPUT_direct",
@@ -119,6 +130,11 @@ DEFAULT_RULES["filter"] = [
 
     "-I OUTPUT 1 -j OUTPUT_direct",
 ]
+OUR_CHAINS["filter"] = set(["INPUT_direct", "INPUT_ZONES_SOURCE", "INPUT_ZONES",
+                          "FORWARD_direct", "FORWARD_IN_ZONES_SOURCE",
+                          "FORWARD_IN_ZONES", "FORWARD_OUT_ZONES_SOURCE",
+                          "FORWARD_OUT_ZONES", "OUTPUT_direct"])
+
 
 class ip4tables:
     ipv = "ipv4"
@@ -151,7 +167,7 @@ class ip4tables:
 
     def available_tables(self, table=None):
         ret = []
-        tables = [ table ] if table else CHAINS.keys()
+        tables = [ table ] if table else BUILT_IN_CHAINS.keys()
         for table in tables:
             try:
                 self.__run(["-t", table, "-L"])
@@ -199,13 +215,13 @@ class ip4tables:
         if which == "used":
             tables = self.used_tables()
         else:
-            tables = list(CHAINS.keys())
+            tables = list(BUILT_IN_CHAINS.keys())
 
         if "nat" in tables:
             tables.remove("nat") # nat can not set policies in nat table
 
         for table in tables:
-            for chain in CHAINS[table]:
+            for chain in BUILT_IN_CHAINS[table]:
                 self.__run([ "-t", table, "-P", chain, policy ])
 
 class ip6tables(ip4tables):
diff -up firewalld-0.3.9/src/firewall/core/rich.py.RHBZ#1183008 firewalld-0.3.9/src/firewall/core/rich.py
--- firewalld-0.3.9/src/firewall/core/rich.py.RHBZ#1183008	2015-07-07 13:10:11.070695839 +0200
+++ firewalld-0.3.9/src/firewall/core/rich.py	2015-07-07 13:10:11.076695733 +0200
@@ -481,6 +481,8 @@ class Rich_Rule(object):
         elif type(self.element) == Rich_Masquerade:
             if self.destination != None:
                 raise FirewallError(INVALID_RULE, "masquerade and destination")
+            if self.action:
+                raise FirewallError(INVALID_RULE, "masquerade and action")
 
         # icmp-block
         elif type(self.element) == Rich_IcmpBlock:
@@ -488,8 +490,8 @@ class Rich_Rule(object):
             # knowledge about this, therefore only simple check
             if self.element.name == None or len(self.element.name) < 1:
                 raise FirewallError(INVALID_ICMPTYPE, str(self.element.name))
-            if self.action and type(self.action) == Rich_Accept:
-                raise FirewallError(INVALID_RULE, "icmpblock and accept")
+            if self.action:
+                raise FirewallError(INVALID_RULE, "icmp-block and action")
 
         # forward-port
         elif type(self.element) == Rich_ForwardPort:
@@ -508,6 +510,8 @@ class Rich_Rule(object):
                 raise FirewallError(INVALID_ADDR, self.element.to_address)
             if self.family == None:
                 raise FirewallError(INVALID_FAMILY)
+            if self.action:
+                raise FirewallError(INVALID_RULE, "forward-port and action")
 
         # other element and not empty?
         elif self.element != None: