Blob Blame History Raw
From f317fbc7e7ad1094b4cfb7570af29772d1a02fd7 Mon Sep 17 00:00:00 2001
From: Eric Garver <eric@garver.life>
Date: Wed, 31 Jul 2019 13:57:10 -0400
Subject: [PATCH 74/79] fix: guarantee zone source dispatch is sorted by zone
 name

Apparently users depend on firewalld sorting zone dispatch for sources
by the zone name. This is used to specify precedence for overlapping
address spaces.

Since we have to track rule positions of source based dispatch we might
as well abuse this and combine the source/interface dispatch into a
single chain.

Fixes: rhbz 1734765
Fixes: 70993581d79b ("fix: do not allow zone drifting")
(cherry picked from commit afc35c20e58b00b81cd2e1f3e863b3b3bac37c77)
(cherry picked from commit a3542499510658b7d93a83d47d3de090860d6e37)
---
 src/firewall/core/ipXtables.py |  93 ++++++++---
 src/firewall/core/nftables.py  |  93 ++++++++---
 src/tests/firewall-cmd.at      |   4 +-
 src/tests/regression/gh258.at  | 274 ++++++++++-----------------------
 4 files changed, 223 insertions(+), 241 deletions(-)

diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py
index c2339e40539a..647a7a161517 100644
--- a/src/firewall/core/ipXtables.py
+++ b/src/firewall/core/ipXtables.py
@@ -20,6 +20,7 @@
 #
 
 import os.path
+import copy
 
 from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET
 from firewall.core.prog import runProg
@@ -175,6 +176,7 @@ class ip4tables(object):
         self.restore_wait_option = self._detect_restore_wait_option()
         self.fill_exists()
         self.available_tables = []
+        self.zone_source_index_cache = []
         self.our_chains = {} # chains created by firewalld
 
     def fill_exists(self):
@@ -286,10 +288,49 @@ class ip4tables(object):
                     chain = args[i+1]
         return (table, chain)
 
+    def _run_replace_zone_source(self, rule, zone_source_index_cache):
+        try:
+            i = rule.index("%%ZONE_SOURCE%%")
+            rule.pop(i)
+            zone = rule.pop(i)
+            if "-m" == rule[4]: # ipset/mac
+                zone_source = (zone, rule[7]) # (zone, address)
+            else:
+                zone_source = (zone, rule[5]) # (zone, address)
+        except ValueError:
+            try:
+                i = rule.index("%%ZONE_INTERFACE%%")
+                rule.pop(i)
+                zone_source = None
+            except ValueError:
+                return
+
+        rule_add = True
+        if rule[0] in ["-D", "--delete"]:
+            rule_add = False
+
+        if zone_source and not rule_add:
+            if zone_source in zone_source_index_cache:
+                zone_source_index_cache.remove(zone_source)
+        elif rule_add:
+            if zone_source:
+                # order source based dispatch by zone name
+                if zone_source not in zone_source_index_cache:
+                    zone_source_index_cache.append(zone_source)
+                    zone_source_index_cache.sort(key=lambda x: x[0])
+
+                index = zone_source_index_cache.index(zone_source)
+            else:
+                index = len(zone_source_index_cache)
+                
+            rule[0] = "-I"
+            rule.insert(2, "%d" % (index + 1))
+
     def set_rules(self, rules, log_denied):
         temp_file = tempFile()
 
         table_rules = { }
+        zone_source_index_cache = copy.deepcopy(self.zone_source_index_cache)
         for _rule in rules:
             rule = _rule[:]
 
@@ -313,6 +354,8 @@ class ip4tables(object):
                 else:
                     rule.pop(i)
 
+            self._run_replace_zone_source(rule, zone_source_index_cache)
+
             table = "filter"
             # get table form rule
             for opt in [ "-t", "--table" ]:
@@ -374,6 +417,7 @@ class ip4tables(object):
         if status != 0:
             raise ValueError("'%s %s' failed: %s" % (self._restore_command,
                                                      " ".join(args), ret))
+        self.zone_source_index_cache = zone_source_index_cache
         return ret
 
     def set_rule(self, rule, log_denied):
@@ -397,7 +441,13 @@ class ip4tables(object):
             else:
                 rule.pop(i)
 
-        return self.__run(rule)
+        zone_source_index_cache = copy.deepcopy(self.zone_source_index_cache)
+        self._run_replace_zone_source(rule, zone_source_index_cache)
+
+        output = self.__run(rule)
+
+        self.zone_source_index_cache = zone_source_index_cache
+        return output
 
     def get_available_tables(self, table=None):
         ret = []
@@ -447,6 +497,7 @@ class ip4tables(object):
         return wait_option
 
     def build_flush_rules(self):
+        self.zone_source_index_cache = []
         rules = []
         for table in BUILT_IN_CHAINS.keys():
             if not self.get_available_tables(table):
@@ -527,10 +578,8 @@ class ip4tables(object):
 
                 if chain == "PREROUTING":
                     default_rules["raw"].append("-N %s_ZONES" % chain)
-                    default_rules["raw"].append("-N %s_ZONES_IFACES" % chain)
                     default_rules["raw"].append("-A %s -j %s_ZONES" % (chain, chain))
-                    default_rules["raw"].append("-A %s_ZONES -g %s_ZONES_IFACES" % (chain, chain))
-                    self.our_chains["raw"].update(set(["%s_ZONES" % chain, "%s_ZONES_IFACES" % chain]))
+                    self.our_chains["raw"].update(set(["%s_ZONES" % chain]))
 
         if self.get_available_tables("mangle"):
             default_rules["mangle"] = [ ]
@@ -542,10 +591,8 @@ class ip4tables(object):
 
                 if chain == "PREROUTING":
                     default_rules["mangle"].append("-N %s_ZONES" % chain)
-                    default_rules["mangle"].append("-N %s_ZONES_IFACES" % chain)
                     default_rules["mangle"].append("-A %s -j %s_ZONES" % (chain, chain))
-                    default_rules["mangle"].append("-A %s_ZONES -g %s_ZONES_IFACES" % (chain, chain))
-                    self.our_chains["mangle"].update(set(["%s_ZONES" % chain, "%s_ZONES_IFACES" % chain]))
+                    self.our_chains["mangle"].update(set(["%s_ZONES" % chain]))
 
         if self.get_available_tables("nat"):
             default_rules["nat"] = [ ]
@@ -557,21 +604,17 @@ class ip4tables(object):
 
                 if chain in [ "PREROUTING", "POSTROUTING" ]:
                     default_rules["nat"].append("-N %s_ZONES" % chain)
-                    default_rules["nat"].append("-N %s_ZONES_IFACES" % chain)
                     default_rules["nat"].append("-A %s -j %s_ZONES" % (chain, chain))
-                    default_rules["nat"].append("-A %s_ZONES -g %s_ZONES_IFACES" % (chain, chain))
-                    self.our_chains["nat"].update(set(["%s_ZONES" % chain, "%s_ZONES_IFACES" % chain]))
+                    self.our_chains["nat"].update(set(["%s_ZONES" % chain]))
 
         default_rules["filter"] = [
             "-N INPUT_direct",
             "-N INPUT_ZONES",
-            "-N INPUT_ZONES_IFACES",
 
             "-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT",
             "-A INPUT -i lo -j ACCEPT",
             "-A INPUT -j INPUT_direct",
             "-A INPUT -j INPUT_ZONES",
-            "-A INPUT_ZONES -g INPUT_ZONES_IFACES",
         ]
         if log_denied != "off":
             default_rules["filter"].append("-A INPUT -m conntrack --ctstate INVALID %%LOGTYPE%% -j LOG --log-prefix 'STATE_INVALID_DROP: '")
@@ -584,16 +627,12 @@ class ip4tables(object):
             "-N FORWARD_direct",
             "-N FORWARD_IN_ZONES",
             "-N FORWARD_OUT_ZONES",
-            "-N FORWARD_IN_ZONES_IFACES",
-            "-N FORWARD_OUT_ZONES_IFACES",
 
             "-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT",
             "-A FORWARD -i lo -j ACCEPT",
             "-A FORWARD -j FORWARD_direct",
             "-A FORWARD -j FORWARD_IN_ZONES",
             "-A FORWARD -j FORWARD_OUT_ZONES",
-            "-A FORWARD_IN_ZONES -g FORWARD_IN_ZONES_IFACES",
-            "-A FORWARD_OUT_ZONES -g FORWARD_OUT_ZONES_IFACES",
         ]
         if log_denied != "off":
             default_rules["filter"].append("-A FORWARD -m conntrack --ctstate INVALID %%LOGTYPE%% -j LOG --log-prefix 'STATE_INVALID_DROP: '")
@@ -609,10 +648,9 @@ class ip4tables(object):
             "-A OUTPUT -j OUTPUT_direct",
         ]
 
-        self.our_chains["filter"] = set(["INPUT_direct", "INPUT_ZONES", "INPUT_ZONES_IFACES"
+        self.our_chains["filter"] = set(["INPUT_direct", "INPUT_ZONES",
                                          "FORWARD_direct", "FORWARD_IN_ZONES",
-                                         "FORWARD_IN_ZONES_IFACES" "FORWARD_OUT_ZONES",
-                                         "FORWARD_OUT_ZONES_IFACES", "OUTPUT_direct"])
+                                         "FORWARD_OUT_ZONES", "OUTPUT_direct"])
 
         final_default_rules = []
         for table in default_rules:
@@ -656,11 +694,13 @@ class ip4tables(object):
         action = "-g"
 
         if enable and not append:
-            rule = [ "-I", "%s_ZONES_IFACES" % chain, "1" ]
+            rule = [ "-I", "%s_ZONES" % chain, "%%ZONE_INTERFACE%%" ]
         elif enable:
-            rule = [ "-A", "%s_ZONES_IFACES" % chain ]
+            rule = [ "-A", "%s_ZONES" % chain ]
         else:
-            rule = [ "-D", "%s_ZONES_IFACES" % chain ]
+            rule = [ "-D", "%s_ZONES" % chain ]
+            if not append:
+                rule += ["%%ZONE_INTERFACE%%"]
         rule += [ "-t", table, opt, interface, action, target ]
         return [rule]
 
@@ -688,7 +728,8 @@ class ip4tables(object):
                 opt = "src"
             flags = ",".join([opt] * self._fw.ipset.get_dimension(name))
             rule = [ add_del,
-                     "%s_ZONES" % chain, "-t", table,
+                     "%s_ZONES" % chain, "%%ZONE_SOURCE%%", zone,
+                     "-t", table,
                      "-m", "set", "--match-set", name,
                      flags, action, target ]
         else:
@@ -697,12 +738,14 @@ class ip4tables(object):
                 if opt == "-d":
                     return ""
                 rule = [ add_del,
-                         "%s_ZONES" % chain, "-t", table,
+                         "%s_ZONES" % chain, "%%ZONE_SOURCE%%", zone,
+                         "-t", table,
                          "-m", "mac", "--mac-source", address.upper(),
                          action, target ]
             else:
                 rule = [ add_del,
-                         "%s_ZONES" % chain, "-t", table,
+                         "%s_ZONES" % chain, "%%ZONE_SOURCE%%", zone,
+                         "-t", table,
                          opt, address, action, target ]
         return [rule]
 
diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py
index 0fe686a01878..05376fdd68d8 100644
--- a/src/firewall/core/nftables.py
+++ b/src/firewall/core/nftables.py
@@ -20,6 +20,7 @@
 #
 
 import os.path
+import copy
 
 from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET
 from firewall.core.prog import runProg
@@ -160,11 +161,54 @@ class nftables(object):
         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:
+                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[:]
@@ -198,11 +242,6 @@ class nftables(object):
             rule_add = False
             rule_key = _args[2:]
             rule_key = " ".join(rule_key)
-            # delete using rule handle
-            _args = ["delete", "rule"] + _args[2:5] + \
-                    ["handle", self.rule_to_handle[rule_key]]
-
-        _args_str = " ".join(_args)
 
         # rule deduplication
         if rule_key in self.rule_ref_count:
@@ -218,15 +257,28 @@ class nftables(object):
                 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, _args_str)
+                       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.
             #
@@ -303,6 +355,7 @@ class nftables(object):
     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():
@@ -359,10 +412,8 @@ class nftables(object):
                                   IPTABLES_TO_NFT_HOOK["raw"][chain][1]))
 
             default_rules.append("add chain inet %s raw_%s_ZONES" % (TABLE_NAME, chain))
-            default_rules.append("add chain inet %s raw_%s_ZONES_IFACES" % (TABLE_NAME, chain))
             default_rules.append("add rule inet %s raw_%s jump raw_%s_ZONES" % (TABLE_NAME, chain, chain))
-            default_rules.append("add rule inet %s raw_%s_ZONES goto raw_%s_ZONES_IFACES" % (TABLE_NAME, chain, chain))
-            OUR_CHAINS["inet"]["raw"].update(set(["%s_ZONES_IFACES" % chain, "%s_ZONES" % chain]))
+            OUR_CHAINS["inet"]["raw"].update(set(["%s_ZONES" % chain]))
 
         OUR_CHAINS["inet"]["mangle"] = set()
         for chain in IPTABLES_TO_NFT_HOOK["mangle"].keys():
@@ -372,10 +423,8 @@ class nftables(object):
                                   IPTABLES_TO_NFT_HOOK["mangle"][chain][1]))
 
             default_rules.append("add chain inet %s mangle_%s_ZONES" % (TABLE_NAME, chain))
-            default_rules.append("add chain inet %s mangle_%s_ZONES_IFACES" % (TABLE_NAME, chain))
             default_rules.append("add rule inet %s mangle_%s jump mangle_%s_ZONES" % (TABLE_NAME, chain, chain))
-            default_rules.append("add rule inet %s mangle_%s_ZONES goto mangle_%s_ZONES_IFACES" % (TABLE_NAME, chain, chain))
-            OUR_CHAINS["inet"]["mangle"].update(set(["%s_ZONES_IFACES" % chain, "%s_ZONES" % chain]))
+            OUR_CHAINS["inet"]["mangle"].update(set(["%s_ZONES" % chain]))
 
         OUR_CHAINS["ip"]["nat"] = set()
         OUR_CHAINS["ip6"]["nat"] = set()
@@ -387,10 +436,8 @@ class nftables(object):
                                       IPTABLES_TO_NFT_HOOK["nat"][chain][1]))
 
                 default_rules.append("add chain %s %s nat_%s_ZONES" % (family, TABLE_NAME, chain))
-                default_rules.append("add chain %s %s nat_%s_ZONES_IFACES" % (family, TABLE_NAME, chain))
                 default_rules.append("add rule %s %s nat_%s jump nat_%s_ZONES" % (family, TABLE_NAME, chain, chain))
-                default_rules.append("add rule %s %s nat_%s_ZONES goto nat_%s_ZONES_IFACES" % (family, TABLE_NAME, chain, chain))
-                OUR_CHAINS[family]["nat"].update(set(["%s_ZONES_IFACES" % chain, "%s_ZONES" % chain]))
+                OUR_CHAINS[family]["nat"].update(set(["%s_ZONES" % chain]))
 
         OUR_CHAINS["inet"]["filter"] = set()
         for chain in IPTABLES_TO_NFT_HOOK["filter"].keys():
@@ -401,11 +448,9 @@ class nftables(object):
 
         # filter, INPUT
         default_rules.append("add chain inet %s filter_%s_ZONES" % (TABLE_NAME, "INPUT"))
-        default_rules.append("add chain inet %s filter_%s_ZONES_IFACES" % (TABLE_NAME, "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"))
         default_rules.append("add rule inet %s filter_%s jump filter_%s_ZONES" % (TABLE_NAME, "INPUT", "INPUT"))
-        default_rules.append("add rule inet %s filter_%s_ZONES goto filter_%s_ZONES_IFACES" % (TABLE_NAME, "INPUT", "INPUT"))
         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"))
@@ -415,15 +460,11 @@ class nftables(object):
 
         # filter, FORWARD
         default_rules.append("add chain inet %s filter_%s_IN_ZONES" % (TABLE_NAME, "FORWARD"))
-        default_rules.append("add chain inet %s filter_%s_IN_ZONES_IFACES" % (TABLE_NAME, "FORWARD"))
         default_rules.append("add chain inet %s filter_%s_OUT_ZONES" % (TABLE_NAME, "FORWARD"))
-        default_rules.append("add chain inet %s filter_%s_OUT_ZONES_IFACES" % (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"))
         default_rules.append("add rule inet %s filter_%s jump filter_%s_IN_ZONES" % (TABLE_NAME, "FORWARD", "FORWARD"))
         default_rules.append("add rule inet %s filter_%s jump filter_%s_OUT_ZONES" % (TABLE_NAME, "FORWARD", "FORWARD"))
-        default_rules.append("add rule inet %s filter_%s_IN_ZONES goto filter_%s_IN_ZONES_IFACES" % (TABLE_NAME, "FORWARD", "FORWARD"))
-        default_rules.append("add rule inet %s filter_%s_OUT_ZONES goto filter_%s_OUT_ZONES_IFACES" % (TABLE_NAME, "FORWARD", "FORWARD"))
         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"))
@@ -482,11 +523,14 @@ class nftables(object):
         action = "goto"
 
         if enable and not append:
-            rule = ["insert", "rule", family, "%s" % TABLE_NAME, "%s_%s_ZONES_IFACES" % (table, chain)]
+            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_IFACES" % (table, chain)]
+            rule = ["add", "rule", family, "%s" % TABLE_NAME, "%s_%s_ZONES" % (table, chain)]
         else:
-            rule = ["delete", "rule", family, "%s" % TABLE_NAME, "%s_%s_ZONES_IFACES" % (table, chain)]
+            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:
@@ -537,6 +581,7 @@ class nftables(object):
 
         rule = [add_del, "rule", family, "%s" % TABLE_NAME,
                 "%s_%s_ZONES" % (table, chain),
+                "%%ZONE_SOURCE%%", zone,
                 rule_family, opt, address, action, "%s_%s" % (table, target)]
         return [rule]
 
diff --git a/src/tests/firewall-cmd.at b/src/tests/firewall-cmd.at
index 28948636172d..6a4b670d7935 100644
--- a/src/tests/firewall-cmd.at
+++ b/src/tests/firewall-cmd.at
@@ -138,9 +138,9 @@ FWD_START_TEST([zone interfaces])
     FWD_CHECK([--add-interface=foobar+++], 0, ignore)
     FWD_CHECK([--add-interface=foobar+], 0, ignore)
     m4_if(nftables, FIREWALL_BACKEND, [
-    NFT_LIST_RULES([inet], [filter_INPUT_ZONES_IFACES], 0, [dnl
+    NFT_LIST_RULES([inet], [filter_INPUT_ZONES], 0, [dnl
         table inet firewalld {
-        chain filter_INPUT_ZONES_IFACES {
+        chain filter_INPUT_ZONES {
             iifname "foobar*" goto filter_IN_public
             iifname "foobar++*" goto filter_IN_public
             goto filter_IN_trusted
diff --git a/src/tests/regression/gh258.at b/src/tests/regression/gh258.at
index 3e5e961f6599..fb863c35528e 100644
--- a/src/tests/regression/gh258.at
+++ b/src/tests/regression/gh258.at
@@ -26,13 +26,6 @@ NFT_LIST_RULES([inet], [filter_INPUT_ZONES], 0, [dnl
         chain filter_INPUT_ZONES {
             ip6 saddr dead:beef::/54 goto filter_IN_public
             ip saddr 1.2.3.0/24 goto filter_IN_work
-            goto filter_INPUT_ZONES_IFACES
-        }
-    }
-])
-NFT_LIST_RULES([inet], [filter_INPUT_ZONES_IFACES], 0, [dnl
-    table inet firewalld {
-        chain filter_INPUT_ZONES_IFACES {
             iifname "dummy1" goto filter_IN_public
             iifname "dummy0" goto filter_IN_work
             goto filter_IN_public
@@ -56,13 +49,6 @@ NFT_LIST_RULES([inet], [filter_FORWARD_IN_ZONES], 0, [dnl
         chain filter_FORWARD_IN_ZONES {
             ip6 saddr dead:beef::/54 goto filter_FWDI_public
             ip saddr 1.2.3.0/24 goto filter_FWDI_work
-            goto filter_FORWARD_IN_ZONES_IFACES
-        }
-    }
-])
-NFT_LIST_RULES([inet], [filter_FORWARD_IN_ZONES_IFACES], 0, [dnl
-    table inet firewalld {
-        chain filter_FORWARD_IN_ZONES_IFACES {
             iifname "dummy1" goto filter_FWDI_public
             iifname "dummy0" goto filter_FWDI_work
             goto filter_FWDI_public
@@ -74,13 +60,6 @@ NFT_LIST_RULES([inet], [filter_FORWARD_OUT_ZONES], 0, [dnl
         chain filter_FORWARD_OUT_ZONES {
             ip6 daddr dead:beef::/54 goto filter_FWDO_public
             ip daddr 1.2.3.0/24 goto filter_FWDO_work
-            goto filter_FORWARD_OUT_ZONES_IFACES
-        }
-    }
-])
-NFT_LIST_RULES([inet], [filter_FORWARD_OUT_ZONES_IFACES], 0, [dnl
-    table inet firewalld {
-        chain filter_FORWARD_OUT_ZONES_IFACES {
             oifname "dummy1" goto filter_FWDO_public
             oifname "dummy0" goto filter_FWDO_work
             goto filter_FWDO_public
@@ -103,13 +82,6 @@ NFT_LIST_RULES([inet], [raw_PREROUTING_ZONES], 0, [dnl
         chain raw_PREROUTING_ZONES {
             ip6 saddr dead:beef::/54 goto raw_PRE_public
             ip saddr 1.2.3.0/24 goto raw_PRE_work
-            goto raw_PREROUTING_ZONES_IFACES
-        }
-    }
-])
-NFT_LIST_RULES([inet], [raw_PREROUTING_ZONES_IFACES], 0, [dnl
-    table inet firewalld {
-        chain raw_PREROUTING_ZONES_IFACES {
             iifname "dummy1" goto raw_PRE_public
             iifname "dummy0" goto raw_PRE_work
             goto raw_PRE_public
@@ -128,13 +100,6 @@ NFT_LIST_RULES([inet], [mangle_PREROUTING_ZONES], 0, [dnl
         chain mangle_PREROUTING_ZONES {
             ip6 saddr dead:beef::/54 goto mangle_PRE_public
             ip saddr 1.2.3.0/24 goto mangle_PRE_work
-            goto mangle_PREROUTING_ZONES_IFACES
-        }
-    }
-])
-NFT_LIST_RULES([inet], [mangle_PREROUTING_ZONES_IFACES], 0, [dnl
-    table inet firewalld {
-        chain mangle_PREROUTING_ZONES_IFACES {
             iifname "dummy1" goto mangle_PRE_public
             iifname "dummy0" goto mangle_PRE_work
             goto mangle_PRE_public
@@ -152,13 +117,6 @@ NFT_LIST_RULES([ip], [nat_PREROUTING_ZONES], 0, [dnl
     table ip firewalld {
         chain nat_PREROUTING_ZONES {
             ip saddr 1.2.3.0/24 goto nat_PRE_work
-            goto nat_PREROUTING_ZONES_IFACES
-        }
-    }
-])
-NFT_LIST_RULES([ip], [nat_PREROUTING_ZONES_IFACES], 0, [dnl
-    table ip firewalld {
-        chain nat_PREROUTING_ZONES_IFACES {
             iifname "dummy1" goto nat_PRE_public
             iifname "dummy0" goto nat_PRE_work
             goto nat_PRE_public
@@ -176,13 +134,6 @@ NFT_LIST_RULES([ip], [nat_POSTROUTING_ZONES], 0, [dnl
     table ip firewalld {
         chain nat_POSTROUTING_ZONES {
             ip daddr 1.2.3.0/24 goto nat_POST_work
-            goto nat_POSTROUTING_ZONES_IFACES
-        }
-    }
-])
-NFT_LIST_RULES([ip], [nat_POSTROUTING_ZONES_IFACES], 0, [dnl
-    table ip firewalld {
-        chain nat_POSTROUTING_ZONES_IFACES {
             oifname "dummy1" goto nat_POST_public
             oifname "dummy0" goto nat_POST_work
             goto nat_POST_public
@@ -200,13 +151,6 @@ NFT_LIST_RULES([ip6], [nat_PREROUTING_ZONES], 0, [dnl
     table ip6 firewalld {
         chain nat_PREROUTING_ZONES {
             ip6 saddr dead:beef::/54 goto nat_PRE_public
-            goto nat_PREROUTING_ZONES_IFACES
-        }
-    }
-])
-NFT_LIST_RULES([ip6], [nat_PREROUTING_ZONES_IFACES], 0, [dnl
-    table ip6 firewalld {
-        chain nat_PREROUTING_ZONES_IFACES {
             iifname "dummy1" goto nat_PRE_public
             iifname "dummy0" goto nat_PRE_work
             goto nat_PRE_public
@@ -224,13 +168,6 @@ NFT_LIST_RULES([ip6], [nat_POSTROUTING_ZONES], 0, [dnl
     table ip6 firewalld {
         chain nat_POSTROUTING_ZONES {
             ip6 daddr dead:beef::/54 goto nat_POST_public
-            goto nat_POSTROUTING_ZONES_IFACES
-        }
-    }
-])
-NFT_LIST_RULES([ip], [nat_POSTROUTING_ZONES_IFACES], 0, [dnl
-    table ip firewalld {
-        chain nat_POSTROUTING_ZONES_IFACES {
             oifname "dummy1" goto nat_POST_public
             oifname "dummy0" goto nat_POST_work
             goto nat_POST_public
@@ -247,15 +184,12 @@ IPTABLES_LIST_RULES([filter], [INPUT], 0, [dnl
     DROP all -- 0.0.0.0/0 0.0.0.0/0 ctstate INVALID
     REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
 ])
-IPTABLES_LIST_RULES([filter], [INPUT_ZONES], 0, [dnl
-    IN_work all -- 1.2.3.0/24 0.0.0.0/0 @<:@goto@:>@
-    INPUT_ZONES_IFACES all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
-IPTABLES_LIST_RULES([filter], [INPUT_ZONES_IFACES], 0, [dnl
-    IN_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    IN_work all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    IN_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
+IPTABLES_LIST_RULES([filter], [INPUT_ZONES], 0,
+  [[IN_work all -- 1.2.3.0/24 0.0.0.0/0 [goto]
+    IN_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    IN_work all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    IN_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+]])
 IPTABLES_LIST_RULES([filter], [FORWARD], 0, [dnl
     ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
     ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
@@ -265,77 +199,58 @@ IPTABLES_LIST_RULES([filter], [FORWARD], 0, [dnl
     DROP all -- 0.0.0.0/0 0.0.0.0/0 ctstate INVALID
     REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
 ])
-IPTABLES_LIST_RULES([filter], [FORWARD_IN_ZONES], 0, [dnl
-    FWDI_work all -- 1.2.3.0/24 0.0.0.0/0 @<:@goto@:>@
-    FORWARD_IN_ZONES_IFACES all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
-IPTABLES_LIST_RULES([filter], [FORWARD_IN_ZONES_IFACES], 0, [dnl
-    FWDI_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    FWDI_work all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    FWDI_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
-IPTABLES_LIST_RULES([filter], [FORWARD_OUT_ZONES], 0, [dnl
-    FWDO_work all -- 0.0.0.0/0 1.2.3.0/24 @<:@goto@:>@
-    FORWARD_OUT_ZONES_IFACES all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
-IPTABLES_LIST_RULES([filter], [FORWARD_OUT_ZONES_IFACES], 0, [dnl
-    FWDO_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    FWDO_work all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    FWDO_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
+IPTABLES_LIST_RULES([filter], [FORWARD_IN_ZONES], 0,
+  [[FWDI_work all -- 1.2.3.0/24 0.0.0.0/0 [goto]
+    FWDI_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    FWDI_work all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    FWDI_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+]])
+IPTABLES_LIST_RULES([filter], [FORWARD_OUT_ZONES], 0,
+  [[FWDO_work all -- 0.0.0.0/0 1.2.3.0/24 [goto]
+    FWDO_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    FWDO_work all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    FWDO_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+]])
 IPTABLES_LIST_RULES([raw], [PREROUTING], 0, [dnl
     PREROUTING_direct all -- 0.0.0.0/0 0.0.0.0/0
     PREROUTING_ZONES all -- 0.0.0.0/0 0.0.0.0/0
 ])
-IPTABLES_LIST_RULES([raw], [PREROUTING_ZONES], 0, [dnl
-    PRE_work all -- 1.2.3.0/24 0.0.0.0/0 @<:@goto@:>@
-    PREROUTING_ZONES_IFACES all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
-IPTABLES_LIST_RULES([raw], [PREROUTING_ZONES_IFACES], 0, [dnl
-    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    PRE_work all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
+IPTABLES_LIST_RULES([raw], [PREROUTING_ZONES], 0,
+  [[PRE_work all -- 1.2.3.0/24 0.0.0.0/0 [goto]
+    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    PRE_work all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+]])
 IPTABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
     PREROUTING_direct all -- 0.0.0.0/0 0.0.0.0/0
     PREROUTING_ZONES all -- 0.0.0.0/0 0.0.0.0/0
 ])
-IPTABLES_LIST_RULES([mangle], [PREROUTING_ZONES], 0, [dnl
-    PRE_work all -- 1.2.3.0/24 0.0.0.0/0 @<:@goto@:>@
-    PREROUTING_ZONES_IFACES all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
-IPTABLES_LIST_RULES([mangle], [PREROUTING_ZONES_IFACES], 0, [dnl
-    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    PRE_work all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
+IPTABLES_LIST_RULES([mangle], [PREROUTING_ZONES], 0,
+  [[PRE_work all -- 1.2.3.0/24 0.0.0.0/0 [goto]
+    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    PRE_work all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+]])
 IPTABLES_LIST_RULES([nat], [PREROUTING], 0, [dnl
     PREROUTING_direct all -- 0.0.0.0/0 0.0.0.0/0
     PREROUTING_ZONES all -- 0.0.0.0/0 0.0.0.0/0
 ])
-IPTABLES_LIST_RULES([nat], [PREROUTING_ZONES], 0, [dnl
-    PRE_work all -- 1.2.3.0/24 0.0.0.0/0 @<:@goto@:>@
-    PREROUTING_ZONES_IFACES all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
-IPTABLES_LIST_RULES([nat], [PREROUTING_ZONES_IFACES], 0, [dnl
-    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    PRE_work all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
+IPTABLES_LIST_RULES([nat], [PREROUTING_ZONES], 0,
+  [[PRE_work all -- 1.2.3.0/24 0.0.0.0/0 [goto]
+    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    PRE_work all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    PRE_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+]])
 IPTABLES_LIST_RULES([nat], [POSTROUTING], 0, [dnl
     POSTROUTING_direct all -- 0.0.0.0/0 0.0.0.0/0
     POSTROUTING_ZONES all -- 0.0.0.0/0 0.0.0.0/0
 ])
-IPTABLES_LIST_RULES([nat], [POSTROUTING_ZONES], 0, [dnl
-    POST_work all -- 0.0.0.0/0 1.2.3.0/24 @<:@goto@:>@
-    POSTROUTING_ZONES_IFACES all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
-IPTABLES_LIST_RULES([nat], [POSTROUTING_ZONES_IFACES], 0, [dnl
-    POST_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    POST_work all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-    POST_public all -- 0.0.0.0/0 0.0.0.0/0 @<:@goto@:>@
-])
-
+IPTABLES_LIST_RULES([nat], [POSTROUTING_ZONES], 0,
+  [[POST_work all -- 0.0.0.0/0 1.2.3.0/24 [goto]
+    POST_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    POST_work all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+    POST_public all -- 0.0.0.0/0 0.0.0.0/0 [goto]
+]])
 
 IP6TABLES_LIST_RULES([filter], [INPUT], 0, [dnl
     ACCEPT all ::/0 ::/0 ctstate RELATED,ESTABLISHED
@@ -345,15 +260,12 @@ IP6TABLES_LIST_RULES([filter], [INPUT], 0, [dnl
     DROP all ::/0 ::/0 ctstate INVALID
     REJECT all ::/0 ::/0 reject-with icmp6-adm-prohibited
 ])
-IP6TABLES_LIST_RULES([filter], [INPUT_ZONES], 0, [dnl
-    IN_public all dead:beef::/54 ::/0 @<:@goto@:>@
-    INPUT_ZONES_IFACES all ::/0 ::/0 @<:@goto@:>@
-])
-IP6TABLES_LIST_RULES([filter], [INPUT_ZONES_IFACES], 0, [dnl
-    IN_public all ::/0 ::/0 @<:@goto@:>@
-    IN_work all ::/0 ::/0 @<:@goto@:>@
-    IN_public all ::/0 ::/0 @<:@goto@:>@
-])
+IP6TABLES_LIST_RULES([filter], [INPUT_ZONES], 0,
+  [[IN_public all dead:beef::/54 ::/0 [goto]
+    IN_public all ::/0 ::/0 [goto]
+    IN_work all ::/0 ::/0 [goto]
+    IN_public all ::/0 ::/0 [goto]
+]])
 IP6TABLES_LIST_RULES([filter], [FORWARD], 0, [dnl
     ACCEPT all ::/0 ::/0 ctstate RELATED,ESTABLISHED
     ACCEPT all ::/0 ::/0
@@ -363,24 +275,18 @@ IP6TABLES_LIST_RULES([filter], [FORWARD], 0, [dnl
     DROP all ::/0 ::/0 ctstate INVALID
     REJECT all ::/0 ::/0 reject-with icmp6-adm-prohibited
 ])
-IP6TABLES_LIST_RULES([filter], [FORWARD_IN_ZONES], 0, [dnl
-    FWDI_public all dead:beef::/54 ::/0 @<:@goto@:>@
-    FORWARD_IN_ZONES_IFACES all ::/0 ::/0 @<:@goto@:>@
-])
-IP6TABLES_LIST_RULES([filter], [FORWARD_IN_ZONES_IFACES], 0, [dnl
-    FWDI_public all ::/0 ::/0 @<:@goto@:>@
-    FWDI_work all ::/0 ::/0 @<:@goto@:>@
-    FWDI_public all ::/0 ::/0 @<:@goto@:>@
-])
-IP6TABLES_LIST_RULES([filter], [FORWARD_OUT_ZONES], 0, [dnl
-    FWDO_public all ::/0 dead:beef::/54 @<:@goto@:>@
-    FORWARD_OUT_ZONES_IFACES all ::/0 ::/0 @<:@goto@:>@
-])
-IP6TABLES_LIST_RULES([filter], [FORWARD_OUT_ZONES_IFACES], 0, [dnl
-    FWDO_public all ::/0 ::/0 @<:@goto@:>@
-    FWDO_work all ::/0 ::/0 @<:@goto@:>@
-    FWDO_public all ::/0 ::/0 @<:@goto@:>@
-])
+IP6TABLES_LIST_RULES([filter], [FORWARD_IN_ZONES], 0,
+  [[FWDI_public all dead:beef::/54 ::/0 [goto]
+    FWDI_public all ::/0 ::/0 [goto]
+    FWDI_work all ::/0 ::/0 [goto]
+    FWDI_public all ::/0 ::/0 [goto]
+]])
+IP6TABLES_LIST_RULES([filter], [FORWARD_OUT_ZONES], 0,
+  [[FWDO_public all ::/0 dead:beef::/54 [goto]
+    FWDO_public all ::/0 ::/0 [goto]
+    FWDO_work all ::/0 ::/0 [goto]
+    FWDO_public all ::/0 ::/0 [goto]
+]])
 IP6TABLES_LIST_RULES([raw], [PREROUTING], 0, [dnl
     ACCEPT icmpv6 ::/0 ::/0 ipv6-icmptype 134
     ACCEPT icmpv6 ::/0 ::/0 ipv6-icmptype 135
@@ -388,54 +294,42 @@ IP6TABLES_LIST_RULES([raw], [PREROUTING], 0, [dnl
     PREROUTING_direct all ::/0 ::/0
     PREROUTING_ZONES all ::/0 ::/0
 ])
-IP6TABLES_LIST_RULES([raw], [PREROUTING_ZONES], 0, [dnl
-    PRE_public all dead:beef::/54 ::/0 @<:@goto@:>@
-    PREROUTING_ZONES_IFACES all ::/0 ::/0 @<:@goto@:>@
-])
-IP6TABLES_LIST_RULES([raw], [PREROUTING_ZONES_IFACES], 0, [dnl
-    PRE_public all ::/0 ::/0 @<:@goto@:>@
-    PRE_work all ::/0 ::/0 @<:@goto@:>@
-    PRE_public all ::/0 ::/0 @<:@goto@:>@
-])
+IP6TABLES_LIST_RULES([raw], [PREROUTING_ZONES], 0,
+  [[PRE_public all dead:beef::/54 ::/0 [goto]
+    PRE_public all ::/0 ::/0 [goto]
+    PRE_work all ::/0 ::/0 [goto]
+    PRE_public all ::/0 ::/0 [goto]
+]])
 IP6TABLES_LIST_RULES([mangle], [PREROUTING], 0, [dnl
     PREROUTING_direct all ::/0 ::/0
     PREROUTING_ZONES all ::/0 ::/0
 ])
-IP6TABLES_LIST_RULES([mangle], [PREROUTING_ZONES], 0, [dnl
-    PRE_public all dead:beef::/54 ::/0 @<:@goto@:>@
-    PREROUTING_ZONES_IFACES all ::/0 ::/0 @<:@goto@:>@
-])
-IP6TABLES_LIST_RULES([mangle], [PREROUTING_ZONES_IFACES], 0, [dnl
-    PRE_public all ::/0 ::/0 @<:@goto@:>@
-    PRE_work all ::/0 ::/0 @<:@goto@:>@
-    PRE_public all ::/0 ::/0 @<:@goto@:>@
-])
+IP6TABLES_LIST_RULES([mangle], [PREROUTING_ZONES], 0,
+  [[PRE_public all dead:beef::/54 ::/0 [goto]
+    PRE_public all ::/0 ::/0 [goto]
+    PRE_work all ::/0 ::/0 [goto]
+    PRE_public all ::/0 ::/0 [goto]
+]])
 IP6TABLES_LIST_RULES([nat], [PREROUTING], 0, [dnl
     PREROUTING_direct all ::/0 ::/0
     PREROUTING_ZONES all ::/0 ::/0
 ])
-IP6TABLES_LIST_RULES([nat], [PREROUTING_ZONES], 0, [dnl
-    PRE_public all dead:beef::/54 ::/0 @<:@goto@:>@
-    PREROUTING_ZONES_IFACES all ::/0 ::/0 @<:@goto@:>@
-])
-IP6TABLES_LIST_RULES([nat], [PREROUTING_ZONES_IFACES], 0, [dnl
-    PRE_public all ::/0 ::/0 @<:@goto@:>@
-    PRE_work all ::/0 ::/0 @<:@goto@:>@
-    PRE_public all ::/0 ::/0 @<:@goto@:>@
-])
+IP6TABLES_LIST_RULES([nat], [PREROUTING_ZONES], 0,
+  [[PRE_public all dead:beef::/54 ::/0 [goto]
+    PRE_public all ::/0 ::/0 [goto]
+    PRE_work all ::/0 ::/0 [goto]
+    PRE_public all ::/0 ::/0 [goto]
+]])
 IP6TABLES_LIST_RULES([nat], [POSTROUTING], 0, [dnl
     POSTROUTING_direct all ::/0 ::/0
     POSTROUTING_ZONES all ::/0 ::/0
 ])
-IP6TABLES_LIST_RULES([nat], [POSTROUTING_ZONES], 0, [dnl
-    POST_public all ::/0 dead:beef::/54 @<:@goto@:>@
-    POSTROUTING_ZONES_IFACES all ::/0 ::/0 @<:@goto@:>@
-])
-IP6TABLES_LIST_RULES([nat], [POSTROUTING_ZONES_IFACES], 0, [dnl
-    POST_public all ::/0 ::/0 @<:@goto@:>@
-    POST_work all ::/0 ::/0 @<:@goto@:>@
-    POST_public all ::/0 ::/0 @<:@goto@:>@
-])
+IP6TABLES_LIST_RULES([nat], [POSTROUTING_ZONES], 0,
+  [[POST_public all ::/0 dead:beef::/54 [goto]
+    POST_public all ::/0 ::/0 [goto]
+    POST_work all ::/0 ::/0 [goto]
+    POST_public all ::/0 ::/0 [goto]
+]])
 ])
 
 FWD_END_TEST
-- 
2.20.1