diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index bd9aa7bbf..933894447 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,57 +1,60 @@
 <!-- All PR should follow this template to allow a clean and transparent review -->
 <!-- Text placed between these delimiters is considered a comment and is not rendered -->
 
 ## Change Summary
 <!--- Provide a general summary of your changes in the Title above -->
 
 ## Types of changes
 <!---
 What types of changes does your code introduce? Put an 'x' in all the boxes that apply.
 NOTE: Markdown requires no leading or trailing whitespace inside the [ ] for checking
 the box, please use [x]
 -->
 - [ ] Bug fix (non-breaking change which fixes an issue)
 - [ ] New feature (non-breaking change which adds functionality)
 - [ ] Code style update (formatting, renaming)
 - [ ] Refactoring (no functional changes)
 - [ ] Migration from an old Vyatta component to vyos-1x, please link to related PR inside obsoleted component
 - [ ] Other (please describe):
 
 ## Related Task(s)
 <!-- All submitted PRs must be linked to a Task on Phabricator. -->
 * https://vyos.dev/Txxxx
 
+## Related PR(s)
+<!-- Link here any PRs in other repositories that are required by this PR -->
+
 ## Component(s) name
 <!-- A rather incomplete list of components: ethernet, wireguard, bgp, mpls, ldp, l2tp, dhcp ... -->
 
 ## Proposed changes
 <!--- Describe your changes in detail -->
 
 ## How to test
 <!---
 Please describe in detail how you tested your changes. Include details of your testing
 environment, and the tests you ran. When pasting configs, logs, shell output, backtraces,
 and other large chunks of text, surround this text with triple backtics
 ```
 like this
 ```
 -->
 
 ## Smoketest result
 <!-- Provide the output of the smoketest
 ```
 $ /usr/libexec/vyos/tests/smoke/cli/test_xxx_feature.py
 test_01_simple_options (__main__.TestFeature.test_01_simple_options) ... ok
 ```
 -->
 
 ## Checklist:
 <!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
 <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
 <!--- The entire development process is outlined here: https://docs.vyos.io/en/latest/contributing/development.html -->
 - [ ] I have read the [**CONTRIBUTING**](https://github.com/vyos/vyos-1x/blob/current/CONTRIBUTING.md) document
 - [ ] I have linked this PR to one or more Phabricator Task(s)
 - [ ] I have run the components [**SMOKETESTS**](https://github.com/vyos/vyos-1x/tree/current/smoketest/scripts/cli) if applicable
 - [ ] My commit headlines contain a valid Task id
 - [ ] My change requires a change to the documentation
 - [ ] I have updated the documentation accordingly
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index 08732bd4c..a433c2522 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -1,35 +1,38 @@
 {
-  "firewall": {"group_resync": ["conntrack", "nat", "policy-route"]},
+  "firewall": {"conntrack": ["conntrack"], "group_resync": ["conntrack", "nat", "policy-route"]},
   "http_api": {"https": ["https"]},
+  "load_balancing_wan": {"conntrack": ["conntrack"]},
+  "nat": {"conntrack": ["conntrack"]},
+  "nat66": {"conntrack": ["conntrack"]},
   "pki": {
            "ethernet": ["interfaces-ethernet"],
            "openvpn": ["interfaces-openvpn"],
            "https": ["https"],
            "ipsec": ["vpn_ipsec"],
            "openconnect": ["vpn_openconnect"],
            "sstp": ["vpn_sstp"]
          },
   "qos": {
            "bonding": ["interfaces-bonding"],
            "bridge": ["interfaces-bridge"],
            "dummy": ["interfaces-dummy"],
            "ethernet": ["interfaces-ethernet"],
            "geneve": ["interfaces-geneve"],
            "input": ["interfaces-input"],
            "l2tpv3": ["interfaces-l2tpv3"],
            "loopback": ["interfaces-loopback"],
            "macsec": ["interfaces-macsec"],
            "openvpn": ["interfaces-openvpn"],
            "pppoe": ["interfaces-pppoe"],
            "pseudo-ethernet": ["interfaces-pseudo-ethernet"],
            "tunnel": ["interfaces-tunnel"],
            "vti": ["interfaces-vti"],
            "vxlan": ["interfaces-vxlan"],
            "wireguard": ["interfaces-wireguard"],
            "wireless": ["interfaces-wireless"],
            "wwan": ["interfaces-wwan"]
          },
   "vpp": {
            "ethernet": ["interfaces-ethernet"]
          }
 }
diff --git a/data/templates/conntrack/nftables-ct.j2 b/data/templates/conntrack/nftables-ct.j2
index 3a5b5a87c..895f61a55 100644
--- a/data/templates/conntrack/nftables-ct.j2
+++ b/data/templates/conntrack/nftables-ct.j2
@@ -1,58 +1,179 @@
 #!/usr/sbin/nft -f
 
 {% import 'firewall/nftables-defines.j2' as group_tmpl %}
 
-{% set nft_ct_ignore_name = 'VYOS_CT_IGNORE' %}
-{% set nft_ct_timeout_name = 'VYOS_CT_TIMEOUT' %}
-
-# we first flush all chains and render the content from scratch - this makes
-# any delta check obsolete
-flush chain raw {{ nft_ct_ignore_name }}
-flush chain raw {{ nft_ct_timeout_name }}
-
-table raw {
-    chain {{ nft_ct_ignore_name }} {
+{% if first_install is not vyos_defined %}
+delete table ip vyos_conntrack
+{% endif %}
+table ip vyos_conntrack {
+    chain VYOS_CT_IGNORE {
 {% if ignore.ipv4.rule is vyos_defined %}
 {%     for rule, rule_config in ignore.ipv4.rule.items() %}
         # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }}
        {{ rule_config | conntrack_ignore_rule(rule, ipv6=False) }}
 {%     endfor %}
 {% endif %}
         return
     }
-    chain {{ nft_ct_timeout_name }} {
+    chain VYOS_CT_TIMEOUT {
 {% if timeout.custom.rule is vyos_defined %}
 {%     for rule, rule_config in timeout.custom.rule.items() %}
         # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }}
 {%     endfor %}
 {% endif %}
         return
     }
 
-{{ group_tmpl.groups(firewall_group, False, True) }}
-}
+    chain PREROUTING {
+        type filter hook prerouting priority -300; policy accept;
+{% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %}
+        counter jump VYOS_CT_HELPER
+{% endif %}
+        counter jump VYOS_CT_IGNORE
+        counter jump VYOS_CT_TIMEOUT
+        counter jump FW_CONNTRACK
+        counter jump NAT_CONNTRACK
+        counter jump WLB_CONNTRACK
+        notrack
+    }
+
+    chain OUTPUT {
+        type filter hook output priority -300; policy accept;
+{% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %}
+        counter jump VYOS_CT_HELPER
+{% endif %}
+        counter jump VYOS_CT_IGNORE
+        counter jump VYOS_CT_TIMEOUT
+        counter jump FW_CONNTRACK
+        counter jump NAT_CONNTRACK
+{% if wlb_local_action %}
+        counter jump WLB_CONNTRACK
+{% endif %}
+        notrack
+    }
+
+    ct helper rpc_tcp {
+        type "rpc" protocol tcp;
+    }
+
+    ct helper rpc_udp {
+        type "rpc" protocol udp;
+    }
+
+    ct helper tns_tcp {
+        type "tns" protocol tcp;
+    }
+
+    chain VYOS_CT_HELPER {
+{% for module, module_conf in module_map.items() %}
+{%     if modules[module] is vyos_defined %}
+{%         if 'nftables' in module_conf %}
+{%             for rule in module_conf.nftables %}
+        {{ rule }}
+{%             endfor %}
+{%         endif %}
+{%     endif %}
+{% endfor %}
+        return
+    }
+
+    chain FW_CONNTRACK {
+        {{ ipv4_firewall_action }}
+    }
+
+    chain NAT_CONNTRACK {
+        {{ ipv4_nat_action }}
+    }
+
+    chain WLB_CONNTRACK {
+        {{ wlb_action }}
+    }
 
-flush chain ip6 raw {{ nft_ct_ignore_name }}
-flush chain ip6 raw {{ nft_ct_timeout_name }}
+{% if firewall.group is vyos_defined %}
+{{ group_tmpl.groups(firewall.group, False, True) }}
+{% endif %}
+}
 
-table ip6 raw {
-    chain {{ nft_ct_ignore_name }} {
+{% if first_install is not vyos_defined %}
+delete table ip6 vyos_conntrack
+{% endif %}
+table ip6 vyos_conntrack {
+    chain VYOS_CT_IGNORE {
 {% if ignore.ipv6.rule is vyos_defined %}
 {%     for rule, rule_config in ignore.ipv6.rule.items() %}
         # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }}
        {{ rule_config | conntrack_ignore_rule(rule, ipv6=True) }}
 {%     endfor %}
 {% endif %}
         return
     }
-    chain {{ nft_ct_timeout_name }} {
+    chain VYOS_CT_TIMEOUT {
 {% if timeout.custom.rule is vyos_defined %}
 {%     for rule, rule_config in timeout.custom.rule.items() %}
         # rule-{{ rule }} {{ '- ' ~ rule_config.description if rule_config.description is vyos_defined }}
 {%     endfor %}
 {% endif %}
         return
     }
 
-{{ group_tmpl.groups(firewall_group, True, True) }}
+    chain PREROUTING {
+        type filter hook prerouting priority -300; policy accept;
+{% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %}
+        counter jump VYOS_CT_HELPER
+{% endif %}
+        counter jump VYOS_CT_IGNORE
+        counter jump VYOS_CT_TIMEOUT
+        counter jump FW_CONNTRACK
+        counter jump NAT_CONNTRACK
+        notrack
+    }
+
+    chain OUTPUT {
+        type filter hook output priority -300; policy accept;
+{% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %}
+        counter jump VYOS_CT_HELPER
+{% endif %}
+        counter jump VYOS_CT_IGNORE
+        counter jump VYOS_CT_TIMEOUT
+        counter jump FW_CONNTRACK
+        counter jump NAT_CONNTRACK
+        notrack
+    }
+
+    ct helper rpc_tcp {
+        type "rpc" protocol tcp;
+    }
+
+    ct helper rpc_udp {
+        type "rpc" protocol udp;
+    }
+
+    ct helper tns_tcp {
+        type "tns" protocol tcp;
+    }
+
+    chain VYOS_CT_HELPER {
+{% for module, module_conf in module_map.items() %}
+{%     if modules[module] is vyos_defined %}
+{%         if 'nftables' in module_conf %}
+{%             for rule in module_conf.nftables %}
+        {{ rule }}
+{%             endfor %}
+{%         endif %}
+{%     endif %}
+{% endfor %}
+        return
+    }
+
+    chain FW_CONNTRACK {
+        {{ ipv6_firewall_action }}
+    }
+
+    chain NAT_CONNTRACK {
+        {{ ipv6_nat_action }}
+    }
+
+{% if firewall.group is vyos_defined %}
+{{ group_tmpl.groups(firewall.group, True, True) }}
+{% endif %}
 }
diff --git a/data/templates/firewall/nftables-nat.j2 b/data/templates/firewall/nftables-nat.j2
index dcf28da88..4254f6a0e 100644
--- a/data/templates/firewall/nftables-nat.j2
+++ b/data/templates/firewall/nftables-nat.j2
@@ -1,67 +1,46 @@
 #!/usr/sbin/nft -f
 
 {% import 'firewall/nftables-defines.j2' as group_tmpl %}
 
-{% if helper_functions is vyos_defined('remove') %}
-{# NAT if going to be disabled - remove rules and targets from nftables #}
-{%     set base_command = 'delete rule ip raw' %}
-{{ base_command }} PREROUTING handle {{ pre_ct_ignore }}
-{{ base_command }} OUTPUT     handle {{ out_ct_ignore }}
-{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }}
-{{ base_command }} OUTPUT     handle {{ out_ct_conntrack }}
-
-delete chain ip raw NAT_CONNTRACK
-
-{% elif helper_functions is vyos_defined('add') %}
-{# NAT if enabled - add targets to nftables #}
-add chain ip raw NAT_CONNTRACK
-add rule ip raw NAT_CONNTRACK counter accept
-{%     set base_command = 'add rule ip raw' %}
-{{ base_command }} PREROUTING position {{ pre_ct_ignore }}    counter jump VYOS_CT_HELPER
-{{ base_command }} OUTPUT     position {{ out_ct_ignore }}    counter jump VYOS_CT_HELPER
-{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK
-{{ base_command }} OUTPUT     position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK
-{% endif %}
-
 {% if first_install is not vyos_defined %}
 delete table ip vyos_nat
 {% endif %}
 {% if deleted is not vyos_defined %}
 table ip vyos_nat {
     #
     # Destination NAT rules build up here
     #
     chain PREROUTING {
         type nat hook prerouting priority -100; policy accept;
         counter jump VYOS_PRE_DNAT_HOOK
 {%     if destination.rule is vyos_defined %}
 {%         for rule, config in destination.rule.items() if config.disable is not vyos_defined %}
         {{ config | nat_rule(rule, 'destination') }}
 {%         endfor %}
 {%     endif %}
     }
 
     #
     # Source NAT rules build up here
     #
     chain POSTROUTING {
         type nat hook postrouting priority 100; policy accept;
         counter jump VYOS_PRE_SNAT_HOOK
 {%     if source.rule is vyos_defined %}
 {%         for rule, config in source.rule.items() if config.disable is not vyos_defined %}
         {{ config | nat_rule(rule, 'source') }}
 {%         endfor %}
 {%     endif %}
     }
 
     chain VYOS_PRE_DNAT_HOOK {
         return
     }
 
     chain VYOS_PRE_SNAT_HOOK {
         return
     }
 
 {{ group_tmpl.groups(firewall_group, False, True) }}
 }
 {% endif %}
diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2
index 27b3eec88..67eb2c109 100644
--- a/data/templates/firewall/nftables-nat66.j2
+++ b/data/templates/firewall/nftables-nat66.j2
@@ -1,57 +1,40 @@
 #!/usr/sbin/nft -f
 
-{% if helper_functions is vyos_defined('remove') %}
-{# NAT if going to be disabled - remove rules and targets from nftables #}
-{%     set base_command = 'delete rule ip6 raw' %}
-{{ base_command }} PREROUTING handle {{ pre_ct_conntrack }}
-{{ base_command }} OUTPUT handle {{ out_ct_conntrack }}
-
-delete chain ip6 raw NAT_CONNTRACK
-
-{% elif helper_functions is vyos_defined('add') %}
-{# NAT if enabled - add targets to nftables #}
-add chain ip6 raw NAT_CONNTRACK
-add rule ip6 raw NAT_CONNTRACK counter accept
-{%     set base_command = 'add rule ip6 raw' %}
-{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK
-{{ base_command }} OUTPUT     position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK
-{% endif %}
-
 {% if first_install is not vyos_defined %}
 delete table ip6 vyos_nat
 {% endif %}
 table ip6 vyos_nat {
     #
     # Destination NAT66 rules build up here
     #
     chain PREROUTING {
         type nat hook prerouting priority -100; policy accept;
         counter jump VYOS_DNPT_HOOK
 {% if destination.rule is vyos_defined %}
 {%     for rule, config in destination.rule.items() if config.disable is not vyos_defined %}
         {{ config | nat_rule(rule, 'destination', ipv6=True) }}
 {%     endfor %}
 {% endif %}
     }
 
     #
     # Source NAT66 rules build up here
     #
     chain POSTROUTING {
         type nat hook postrouting priority 100; policy accept;
         counter jump VYOS_SNPT_HOOK
 {% if source.rule is vyos_defined %}
 {%     for rule, config in source.rule.items() if config.disable is not vyos_defined %}
         {{ config | nat_rule(rule, 'source', ipv6=True) }}
 {%     endfor %}
 {% endif %}
     }
 
     chain VYOS_DNPT_HOOK {
         return
     }
 
     chain VYOS_SNPT_HOOK {
         return
     }
 }
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index db010257d..1564b3ef8 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -1,295 +1,283 @@
 #!/usr/sbin/nft -f
 
 {% import 'firewall/nftables-defines.j2' as group_tmpl %}
 {% import 'firewall/nftables-bridge.j2' as bridge_tmpl %}
 {% import 'firewall/nftables-offload.j2' as offload %}
 
-flush chain raw FW_CONNTRACK
-flush chain ip6 raw FW_CONNTRACK
-
 flush chain raw vyos_global_rpfilter
 flush chain ip6 raw vyos_global_rpfilter
 
 table raw {
-    chain FW_CONNTRACK {
-        {{ ipv4_conntrack_action }}
-    }
-
     chain vyos_global_rpfilter {
 {% if global_options.source_validation is vyos_defined('loose') %}
         fib saddr oif 0 counter drop
 {% elif global_options.source_validation is vyos_defined('strict') %}
         fib saddr . iif oif 0 counter drop
 {% endif %}
         return
     }
 }
 
 table ip6 raw {
-    chain FW_CONNTRACK {
-        {{ ipv6_conntrack_action }}
-    }
-
     chain vyos_global_rpfilter {
 {% if global_options.ipv6_source_validation is vyos_defined('loose') %}
         fib saddr oif 0 counter drop
 {% elif global_options.ipv6_source_validation is vyos_defined('strict') %}
         fib saddr . iif oif 0 counter drop
 {% endif %}
         return
     }
 }
 
 {% if first_install is not vyos_defined %}
 delete table ip vyos_filter
 {% endif %}
 table ip vyos_filter {
 {% if ipv4 is vyos_defined %}
 {%     set ns = namespace(sets=[]) %}
 {%     if ipv4.forward is vyos_defined %}
 {%         for prior, conf in ipv4.forward.items() %}
 {%             set def_action = conf.default_action %}
     chain VYOS_FORWARD_{{ prior }} {
         type filter hook forward priority {{ prior }}; policy {{ def_action }};
 {%             if conf.rule is vyos_defined %}
 {%                 for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
         {{ rule_conf | nft_rule('FWD', prior, rule_id) }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['FWD_' + prior + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv4.input is vyos_defined %}
 {%         for prior, conf in ipv4.input.items() %}
 {%             set def_action = conf.default_action %}
     chain VYOS_INPUT_{{ prior }} {
         type filter hook input priority {{ prior }}; policy {{ def_action }};
 {%             if conf.rule is vyos_defined %}
 {%                 for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
         {{ rule_conf | nft_rule('INP',prior, rule_id) }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['INP_' + prior + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv4.output is vyos_defined %}
 {%         for prior, conf in ipv4.output.items() %}
 {%             set def_action = conf.default_action %}
     chain VYOS_OUTPUT_{{ prior }} {
         type filter hook output priority {{ prior }}; policy {{ def_action }};
 {%             if conf.rule is vyos_defined %}
 {%                 for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
         {{ rule_conf | nft_rule('OUT', prior, rule_id) }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['OUT_' + prior + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
     chain VYOS_FRAG_MARK {
         type filter hook prerouting priority -450; policy accept;
         ip frag-off & 0x3fff != 0 meta mark set 0xffff1 return
     }
 {%     if ipv4.prerouting is vyos_defined %}
 {%         for prior, conf in ipv4.prerouting.items() %}
 {%             set def_action = conf.default_action %}
     chain VYOS_PREROUTING_{{ prior }} {
         type filter hook prerouting priority {{ prior }}; policy {{ def_action }};
 {%             if conf.rule is vyos_defined %}
 {%                 for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
         {{ rule_conf | nft_rule('PRE', prior, rule_id) }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['PRE_' + prior + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
         {{ conf | nft_default_rule(prior) }}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv4.name is vyos_defined %}
 {%         for name_text, conf in ipv4.name.items() %}
     chain NAME_{{ name_text }} {
 {%             if conf.rule is vyos_defined %}
 {%                 for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
         {{ rule_conf | nft_rule('NAM', name_text, rule_id) }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
         {{ conf | nft_default_rule(name_text) }}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     for set_name in ns.sets %}
     set RECENT_{{ set_name }} {
         type ipv4_addr
         size 65535
         flags dynamic
     }
 {%     endfor %}
 {%     for set_name in ip_fqdn %}
     set FQDN_{{ set_name }} {
         type ipv4_addr
         flags interval
     }
 {%     endfor %}
 {%     if geoip_updated.name is vyos_defined %}
 {%         for setname in geoip_updated.name %}
     set {{ setname }} {
         type ipv4_addr
         flags interval
     }
 {%         endfor %}
 {%     endif %}
 {% endif %}
 {{ group_tmpl.groups(group, False, True) }}
 }
 
 {% if first_install is not vyos_defined %}
 delete table ip6 vyos_filter
 {% endif %}
 table ip6 vyos_filter {
 {% if ipv6 is vyos_defined %}
 {%     set ns = namespace(sets=[]) %}
 {%     if ipv6.forward is vyos_defined %}
 {%         for prior, conf in ipv6.forward.items() %}
 {%             set def_action = conf.default_action %}
     chain VYOS_IPV6_FORWARD_{{ prior }} {
         type filter hook forward priority {{ prior }}; policy {{ def_action }};
 {%             if conf.rule is vyos_defined %}
 {%                 for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
         {{ rule_conf | nft_rule('FWD', prior, rule_id ,'ip6') }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['FWD_' + prior + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv6.input is vyos_defined %}
 {%         for prior, conf in ipv6.input.items() %}
 {%             set def_action = conf.default_action %}
     chain VYOS_IPV6_INPUT_{{ prior }} {
         type filter hook input priority {{ prior }}; policy {{ def_action }};
 {%             if conf.rule is vyos_defined %}
 {%                 for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
         {{ rule_conf | nft_rule('INP', prior, rule_id ,'ip6') }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['INP_' + prior + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv6.output is vyos_defined %}
 {%         for prior, conf in ipv6.output.items() %}
 {%             set def_action = conf.default_action %}
     chain VYOS_IPV6_OUTPUT_{{ prior }} {
         type filter hook output priority {{ prior }}; policy {{ def_action }};
 {%             if conf.rule is vyos_defined %}
 {%                 for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
         {{ rule_conf | nft_rule('OUT', prior, rule_id ,'ip6') }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['OUT_ ' + prior + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
 
     chain VYOS_FRAG6_MARK {
         type filter hook prerouting priority -450; policy accept;
         exthdr frag exists meta mark set 0xffff1 return
     }
 
 {%     if ipv6.name is vyos_defined %}
 {%         for name_text, conf in ipv6.name.items() %}
     chain NAME6_{{ name_text }} {
 {%             if conf.rule is vyos_defined %}
 {%                 for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
         {{ rule_conf | nft_rule('NAM', name_text, rule_id, 'ip6') }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
         {{ conf | nft_default_rule(name_text, ipv6=True) }}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     for set_name in ns.sets %}
     set RECENT6_{{ set_name }} {
         type ipv6_addr
         size 65535
         flags dynamic
     }
 {%     endfor %}
 {%     for set_name in ip6_fqdn %}
     set FQDN_{{ set_name }} {
         type ipv6_addr
         flags interval
     }
 {%     endfor %}
 {%     if geoip_updated.ipv6_name is vyos_defined %}
 {%         for setname in geoip_updated.ipv6_name %}
     set {{ setname }} {
         type ipv6_addr
         flags interval
     }
 {%         endfor %}
 {%     endif %}
 {% endif %}
 {{ group_tmpl.groups(group, True, True) }}
 }
 
 ## Bridge Firewall
 {% if first_install is not vyos_defined %}
 delete table bridge vyos_filter
 {% endif %}
 {% if bridge is vyos_defined %}
 table bridge vyos_filter {
 {{ bridge_tmpl.bridge(bridge) }}
 {{ group_tmpl.groups(group, False, False) }}
 }
 {% endif %}
 
-table inet vyos_offload
+{% if first_install is not vyos_defined %}
 delete table inet vyos_offload
+{% endif %}
 table inet vyos_offload {
-{% if flowtable_enabled %}
-{%     if global_options.flow_offload.hardware.interface is vyos_defined %}
+{% if global_options.flow_offload.hardware.interface is vyos_defined %}
     {{- offload.render_flowtable('VYOS_FLOWTABLE_hardware', global_options.flow_offload.hardware.interface | list, priority='filter - 2', hardware_offload=true) }}
     chain VYOS_OFFLOAD_hardware {
         type filter hook forward priority filter - 2; policy accept;
         ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_hardware
     }
-{%     endif %}
-{%     if global_options.flow_offload.software.interface is vyos_defined %}
+{% endif %}
+{% if global_options.flow_offload.software.interface is vyos_defined %}
     {{- offload.render_flowtable('VYOS_FLOWTABLE_software', global_options.flow_offload.software.interface | list, priority='filter - 1') }}
     chain VYOS_OFFLOAD_software {
         type filter hook forward priority filter - 1; policy accept;
         ct state { established, related } meta l4proto { tcp, udp } flow add @VYOS_FLOWTABLE_software
     }
-{%     endif %}
 {% endif %}
 }
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
index 7e258e6f1..cd7d5011f 100644
--- a/data/vyos-firewall-init.conf
+++ b/data/vyos-firewall-init.conf
@@ -1,159 +1,56 @@
 #!/usr/sbin/nft -f
 
 # Required by wanloadbalance
 table ip nat {
     chain VYOS_PRE_SNAT_HOOK {
         type nat hook postrouting priority 99; policy accept;
         return
     }
 }
 
 table inet mangle {
+    # Used by system flow-accounting
     chain FORWARD {
         type filter hook forward priority -150; policy accept;
     }
 }
 
 table raw {
     chain VYOS_TCP_MSS {
         type filter hook forward priority -300; policy accept;
     }
 
     chain vyos_global_rpfilter {
         return
     }
 
     chain vyos_rpfilter {
         type filter hook prerouting priority -300; policy accept;
         counter jump vyos_global_rpfilter
     }
 
-    chain PREROUTING {
+    # Used by system flow-accounting
+    chain VYOS_PREROUTING_HOOK {
         type filter hook prerouting priority -300; policy accept;
-        counter jump VYOS_CT_IGNORE
-        counter jump VYOS_CT_TIMEOUT
-        counter jump VYOS_CT_PREROUTING_HOOK
-        counter jump FW_CONNTRACK
-        notrack
-    }
-
-    chain OUTPUT {
-        type filter hook output priority -300; policy accept;
-        counter jump VYOS_CT_IGNORE
-        counter jump VYOS_CT_TIMEOUT
-        counter jump VYOS_CT_OUTPUT_HOOK
-        counter jump FW_CONNTRACK
-        notrack
-    }
-
-    ct helper rpc_tcp {
-        type "rpc" protocol tcp;
-    }
-
-    ct helper rpc_udp {
-        type "rpc" protocol udp;
-    }
-
-    ct helper tns_tcp {
-        type "tns" protocol tcp;
-    }
-
-    chain VYOS_CT_HELPER {
-        ct helper set "rpc_tcp" tcp dport {111} return
-        ct helper set "rpc_udp" udp dport {111} return
-        ct helper set "tns_tcp" tcp dport {1521,1525,1536} return
-        return
-    }
-
-    chain VYOS_CT_IGNORE {
-        return
-    }
-
-    chain VYOS_CT_TIMEOUT {
-        return
-    }
-
-    chain VYOS_CT_PREROUTING_HOOK {
-        return
-    }
-
-    chain VYOS_CT_OUTPUT_HOOK {
-        return
-    }
-
-    chain FW_CONNTRACK {
-        return
     }
 }
 
 table ip6 raw {
     chain VYOS_TCP_MSS {
         type filter hook forward priority -300; policy accept;
     }
 
     chain vyos_global_rpfilter {
         return
     }
 
     chain vyos_rpfilter {
         type filter hook prerouting priority -300; policy accept;
         counter jump vyos_global_rpfilter
     }
 
-    chain PREROUTING {
+    # Used by system flow-accounting
+    chain VYOS_PREROUTING_HOOK {
         type filter hook prerouting priority -300; policy accept;
-        counter jump VYOS_CT_IGNORE
-        counter jump VYOS_CT_TIMEOUT
-        counter jump VYOS_CT_PREROUTING_HOOK
-        counter jump FW_CONNTRACK
-        notrack
-    }
-
-    chain OUTPUT {
-        type filter hook output priority -300; policy accept;
-        counter jump VYOS_CT_IGNORE
-        counter jump VYOS_CT_TIMEOUT
-        counter jump VYOS_CT_OUTPUT_HOOK
-        counter jump FW_CONNTRACK
-        notrack
-    }
-
-    ct helper rpc_tcp {
-        type "rpc" protocol tcp;
-    }
-
-    ct helper rpc_udp {
-        type "rpc" protocol udp;
-    }
-
-    ct helper tns_tcp {
-        type "tns" protocol tcp;
-    }
-
-    chain VYOS_CT_HELPER {
-        ct helper set "rpc_tcp" tcp dport {111} return
-        ct helper set "rpc_udp" udp dport {111} return
-        ct helper set "tns_tcp" tcp dport {1521,1525,1536} return
-        return
-    }
-
-    chain VYOS_CT_IGNORE {
-        return
-    }
-
-    chain VYOS_CT_TIMEOUT {
-        return
-    }
-
-    chain VYOS_CT_PREROUTING_HOOK {
-        return
-    }
-
-    chain VYOS_CT_OUTPUT_HOOK {
-        return
-    }
-
-    chain FW_CONNTRACK {
-        return
     }
 }
diff --git a/interface-definitions/include/firewall/common-rule-inet.xml.i b/interface-definitions/include/firewall/common-rule-inet.xml.i
index 7a2eb86d4..e51dd0056 100644
--- a/interface-definitions/include/firewall/common-rule-inet.xml.i
+++ b/interface-definitions/include/firewall/common-rule-inet.xml.i
@@ -1,374 +1,375 @@
 <!-- include start from firewall/common-rule-inet.xml.i -->
 #include <include/firewall/action.xml.i>
 #include <include/generic-description.xml.i>
 #include <include/firewall/dscp.xml.i>
 #include <include/firewall/packet-options.xml.i>
 #include <include/firewall/connection-mark.xml.i>
 #include <include/firewall/nft-queue.xml.i>
 <leafNode name="disable">
   <properties>
     <help>Option to disable firewall rule</help>
     <valueless/>
   </properties>
 </leafNode>
 <node name="fragment">
   <properties>
     <help>IP fragment match</help>
   </properties>
   <children>
     <leafNode name="match-frag">
       <properties>
         <help>Second and further fragments of fragmented packets</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="match-non-frag">
       <properties>
         <help>Head fragments or unfragmented packets</help>
         <valueless/>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="ipsec">
   <properties>
     <help>Inbound IPsec packets</help>
   </properties>
   <children>
     <leafNode name="match-ipsec">
       <properties>
         <help>Inbound IPsec packets</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="match-none">
       <properties>
         <help>Inbound non-IPsec packets</help>
         <valueless/>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="limit">
   <properties>
     <help>Rate limit using a token bucket filter</help>
   </properties>
   <children>
     <leafNode name="burst">
       <properties>
         <help>Maximum number of packets to allow in excess of rate</help>
         <valueHelp>
           <format>u32:0-4294967295</format>
           <description>Maximum number of packets to allow in excess of rate</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-4294967295"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="rate">
       <properties>
         <help>Maximum average matching rate</help>
         <valueHelp>
           <format>txt</format>
           <description>integer/unit (Example: 5/minute)</description>
         </valueHelp>
         <constraint>
           <regex>\d+/(second|minute|hour|day)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <leafNode name="log">
   <properties>
     <help>Option to log packets matching rule</help>
     <completionHelp>
       <list>enable disable</list>
     </completionHelp>
     <valueHelp>
       <format>enable</format>
       <description>Enable log</description>
     </valueHelp>
     <valueHelp>
       <format>disable</format>
       <description>Disable log</description>
     </valueHelp>
     <constraint>
       <regex>(enable|disable)</regex>
     </constraint>
   </properties>
 </leafNode>
 <leafNode name="log">
   <properties>
     <help>Option to log packets matching rule</help>
     <completionHelp>
       <list>enable disable</list>
     </completionHelp>
     <valueHelp>
       <format>enable</format>
       <description>Enable log</description>
     </valueHelp>
     <valueHelp>
       <format>disable</format>
       <description>Disable log</description>
     </valueHelp>
     <constraint>
       <regex>(enable|disable)</regex>
     </constraint>
   </properties>
 </leafNode>
 #include <include/firewall/rule-log-options.xml.i>
 <node name="connection-status">
   <properties>
     <help>Connection status</help>
   </properties>
   <children>
     <leafNode name="nat">
       <properties>
         <help>NAT connection status</help>
         <completionHelp>
           <list>destination source</list>
         </completionHelp>
         <valueHelp>
           <format>destination</format>
           <description>Match connections that are subject to destination NAT</description>
         </valueHelp>
         <valueHelp>
           <format>source</format>
           <description>Match connections that are subject to source NAT</description>
         </valueHelp>
         <constraint>
           <regex>(destination|source)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <leafNode name="protocol">
   <properties>
     <help>Protocol to match (protocol name, number, or "all")</help>
     <completionHelp>
       <script>${vyos_completion_dir}/list_protocols.sh</script>
       <list>all tcp_udp</list>
     </completionHelp>
     <valueHelp>
       <format>all</format>
       <description>All IP protocols</description>
     </valueHelp>
     <valueHelp>
       <format>tcp_udp</format>
       <description>Both TCP and UDP</description>
     </valueHelp>
     <valueHelp>
       <format>u32:0-255</format>
       <description>IP protocol number</description>
     </valueHelp>
     <valueHelp>
       <format>&lt;protocol&gt;</format>
       <description>IP protocol name</description>
     </valueHelp>
     <valueHelp>
       <format>!&lt;protocol&gt;</format>
       <description>IP protocol name</description>
     </valueHelp>
     <constraint>
       <validator name="ip-protocol"/>
     </constraint>
   </properties>
 </leafNode>
 <node name="recent">
   <properties>
     <help>Parameters for matching recently seen sources</help>
   </properties>
   <children>
     <leafNode name="count">
       <properties>
         <help>Source addresses seen more than N times</help>
         <valueHelp>
           <format>u32:1-255</format>
           <description>Source addresses seen more than N times</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 1-255"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="time">
       <properties>
         <help>Source addresses seen in the last second/minute/hour</help>
         <completionHelp>
           <list>second minute hour</list>
         </completionHelp>
         <valueHelp>
           <format>second</format>
           <description>Source addresses seen COUNT times in the last second</description>
         </valueHelp>
         <valueHelp>
           <format>minute</format>
           <description>Source addresses seen COUNT times in the last minute</description>
         </valueHelp>
         <valueHelp>
           <format>hour</format>
           <description>Source addresses seen COUNT times in the last hour</description>
         </valueHelp>
         <constraint>
           <regex>(second|minute|hour)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="state">
   <properties>
     <help>Session state</help>
   </properties>
   <children>
     <leafNode name="established">
       <properties>
         <help>Established state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="invalid">
       <properties>
         <help>Invalid state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="new">
       <properties>
         <help>New state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="related">
       <properties>
         <help>Related state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 #include <include/firewall/tcp-flags.xml.i>
+#include <include/firewall/tcp-mss.xml.i>
 <node name="time">
   <properties>
     <help>Time to match rule</help>
   </properties>
   <children>
     <leafNode name="startdate">
       <properties>
         <help>Date to start matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter date using following notation - YYYY-MM-DD</description>
         </valueHelp>
         <constraint>
           <regex>(\d{4}\-\d{2}\-\d{2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="starttime">
       <properties>
         <help>Time of day to start matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter time using using 24 hour notation - hh:mm:ss</description>
         </valueHelp>
         <constraint>
           <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="stopdate">
       <properties>
         <help>Date to stop matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter date using following notation - YYYY-MM-DD</description>
         </valueHelp>
         <constraint>
           <regex>(\d{4}\-\d{2}\-\d{2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="stoptime">
       <properties>
         <help>Time of day to stop matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter time using using 24 hour notation - hh:mm:ss</description>
         </valueHelp>
         <constraint>
           <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="weekdays">
       <properties>
         <help>Comma separated weekdays to match rule on</help>
         <valueHelp>
           <format>txt</format>
           <description>Name of day (Monday, Tuesday, Wednesday, Thursdays, Friday, Saturday, Sunday)</description>
         </valueHelp>
         <valueHelp>
           <format>u32:0-6</format>
           <description>Day number (0 = Sunday ... 6 = Saturday)</description>
         </valueHelp>
       </properties>
     </leafNode>
   </children>
 </node>
 <!-- include end -->
\ No newline at end of file
diff --git a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i
index a1071a09a..e040c9b13 100644
--- a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i
+++ b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i
@@ -1,331 +1,332 @@
 <!-- include start from firewall/common-rule-ipv4-raw.xml.i -->
 #include <include/firewall/action-and-notrack.xml.i>
 #include <include/generic-description.xml.i>
 #include <include/firewall/dscp.xml.i>
 #include <include/firewall/ttl.xml.i>
 #include <include/firewall/nft-queue.xml.i>
 <node name="destination">
   <properties>
     <help>Destination parameters</help>
   </properties>
   <children>
     #include <include/firewall/address.xml.i>
     #include <include/firewall/address-mask.xml.i>
     #include <include/firewall/fqdn.xml.i>
     #include <include/firewall/geoip.xml.i>
     #include <include/firewall/mac-address.xml.i>
     #include <include/firewall/port.xml.i>
     #include <include/firewall/source-destination-group.xml.i>
   </children>
 </node>
 <leafNode name="disable">
   <properties>
     <help>Option to disable firewall rule</help>
     <valueless/>
   </properties>
 </leafNode>
 <node name="fragment">
   <properties>
     <help>IP fragment match</help>
   </properties>
   <children>
     <leafNode name="match-frag">
       <properties>
         <help>Second and further fragments of fragmented packets</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="match-non-frag">
       <properties>
         <help>Head fragments or unfragmented packets</help>
         <valueless/>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="icmp">
   <properties>
     <help>ICMP type and code information</help>
   </properties>
   <children>
     <leafNode name="code">
       <properties>
         <help>ICMP code</help>
         <valueHelp>
           <format>u32:0-255</format>
           <description>ICMP code (0-255)</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-255"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="type">
       <properties>
         <help>ICMP type</help>
         <valueHelp>
           <format>u32:0-255</format>
           <description>ICMP type (0-255)</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-255"/>
         </constraint>
       </properties>
     </leafNode>
     #include <include/firewall/icmp-type-name.xml.i>
   </children>
 </node>
 <node name="ipsec">
   <properties>
     <help>Inbound IPsec packets</help>
   </properties>
   <children>
     <leafNode name="match-ipsec">
       <properties>
         <help>Inbound IPsec packets</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="match-none">
       <properties>
         <help>Inbound non-IPsec packets</help>
         <valueless/>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="limit">
   <properties>
     <help>Rate limit using a token bucket filter</help>
   </properties>
   <children>
     <leafNode name="burst">
       <properties>
         <help>Maximum number of packets to allow in excess of rate</help>
         <valueHelp>
           <format>u32:0-4294967295</format>
           <description>Maximum number of packets to allow in excess of rate</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-4294967295"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="rate">
       <properties>
         <help>Maximum average matching rate</help>
         <valueHelp>
           <format>txt</format>
           <description>integer/unit (Example: 5/minute)</description>
         </valueHelp>
         <constraint>
           <regex>\d+/(second|minute|hour|day)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <leafNode name="log">
   <properties>
     <help>Option to log packets matching rule</help>
     <completionHelp>
       <list>enable disable</list>
     </completionHelp>
     <valueHelp>
       <format>enable</format>
       <description>Enable log</description>
     </valueHelp>
     <valueHelp>
       <format>disable</format>
       <description>Disable log</description>
     </valueHelp>
     <constraint>
       <regex>(enable|disable)</regex>
     </constraint>
   </properties>
 </leafNode>
 #include <include/firewall/rule-log-options.xml.i>
 <node name="connection-status">
   <properties>
     <help>Connection status</help>
   </properties>
   <children>
     <leafNode name="nat">
       <properties>
         <help>NAT connection status</help>
         <completionHelp>
           <list>destination source</list>
         </completionHelp>
         <valueHelp>
           <format>destination</format>
           <description>Match connections that are subject to destination NAT</description>
         </valueHelp>
         <valueHelp>
           <format>source</format>
           <description>Match connections that are subject to source NAT</description>
         </valueHelp>
         <constraint>
           <regex>(destination|source)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <leafNode name="protocol">
   <properties>
     <help>Protocol to match (protocol name, number, or "all")</help>
     <completionHelp>
       <script>${vyos_completion_dir}/list_protocols.sh</script>
       <list>all tcp_udp</list>
     </completionHelp>
     <valueHelp>
       <format>all</format>
       <description>All IP protocols</description>
     </valueHelp>
     <valueHelp>
       <format>tcp_udp</format>
       <description>Both TCP and UDP</description>
     </valueHelp>
     <valueHelp>
       <format>u32:0-255</format>
       <description>IP protocol number</description>
     </valueHelp>
     <valueHelp>
       <format>&lt;protocol&gt;</format>
       <description>IP protocol name</description>
     </valueHelp>
     <valueHelp>
       <format>!&lt;protocol&gt;</format>
       <description>IP protocol name</description>
     </valueHelp>
     <constraint>
       <validator name="ip-protocol"/>
     </constraint>
   </properties>
 </leafNode>
 <node name="recent">
   <properties>
     <help>Parameters for matching recently seen sources</help>
   </properties>
   <children>
     <leafNode name="count">
       <properties>
         <help>Source addresses seen more than N times</help>
         <valueHelp>
           <format>u32:1-255</format>
           <description>Source addresses seen more than N times</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 1-255"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="time">
       <properties>
         <help>Source addresses seen in the last second/minute/hour</help>
         <completionHelp>
           <list>second minute hour</list>
         </completionHelp>
         <valueHelp>
           <format>second</format>
           <description>Source addresses seen COUNT times in the last second</description>
         </valueHelp>
         <valueHelp>
           <format>minute</format>
           <description>Source addresses seen COUNT times in the last minute</description>
         </valueHelp>
         <valueHelp>
           <format>hour</format>
           <description>Source addresses seen COUNT times in the last hour</description>
         </valueHelp>
         <constraint>
           <regex>(second|minute|hour)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="source">
   <properties>
     <help>Source parameters</help>
   </properties>
   <children>
     #include <include/firewall/address.xml.i>
     #include <include/firewall/address-mask.xml.i>
     #include <include/firewall/fqdn.xml.i>
     #include <include/firewall/geoip.xml.i>
     #include <include/firewall/mac-address.xml.i>
     #include <include/firewall/port.xml.i>
     #include <include/firewall/source-destination-group.xml.i>
   </children>
 </node>
 #include <include/firewall/tcp-flags.xml.i>
+#include <include/firewall/tcp-mss.xml.i>
 <node name="time">
   <properties>
     <help>Time to match rule</help>
   </properties>
   <children>
     <leafNode name="startdate">
       <properties>
         <help>Date to start matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter date using following notation - YYYY-MM-DD</description>
         </valueHelp>
         <constraint>
           <regex>(\d{4}\-\d{2}\-\d{2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="starttime">
       <properties>
         <help>Time of day to start matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter time using using 24 hour notation - hh:mm:ss</description>
         </valueHelp>
         <constraint>
           <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="stopdate">
       <properties>
         <help>Date to stop matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter date using following notation - YYYY-MM-DD</description>
         </valueHelp>
         <constraint>
           <regex>(\d{4}\-\d{2}\-\d{2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="stoptime">
       <properties>
         <help>Time of day to stop matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter time using using 24 hour notation - hh:mm:ss</description>
         </valueHelp>
         <constraint>
           <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="weekdays">
       <properties>
         <help>Comma separated weekdays to match rule on</help>
         <valueHelp>
           <format>txt</format>
           <description>Name of day (Monday, Tuesday, Wednesday, Thursdays, Friday, Saturday, Sunday)</description>
         </valueHelp>
         <valueHelp>
           <format>u32:0-6</format>
           <description>Day number (0 = Sunday ... 6 = Saturday)</description>
         </valueHelp>
       </properties>
     </leafNode>
   </children>
 </node>
 <!-- include end -->
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i
index 7417a3c58..c62bf2c5f 100644
--- a/interface-definitions/include/firewall/common-rule.xml.i
+++ b/interface-definitions/include/firewall/common-rule.xml.i
@@ -1,386 +1,387 @@
 <!-- include start from firewall/common-rule.xml.i -->
 #include <include/firewall/action.xml.i>
 #include <include/generic-description.xml.i>
 <node name="destination">
   <properties>
     <help>Destination parameters</help>
   </properties>
   <children>
     #include <include/firewall/mac-address.xml.i>
   </children>
 </node>
 <leafNode name="disable">
   <properties>
     <help>Option to disable firewall rule</help>
     <valueless/>
   </properties>
 </leafNode>
 <node name="fragment">
   <properties>
     <help>IP fragment match</help>
   </properties>
   <children>
     <leafNode name="match-frag">
       <properties>
         <help>Second and further fragments of fragmented packets</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="match-non-frag">
       <properties>
         <help>Head fragments or unfragmented packets</help>
         <valueless/>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="inbound-interface">
   <properties>
     <help>Match inbound-interface</help>
   </properties>
   <children>
     #include <include/firewall/match-interface.xml.i>
   </children>
 </node>
 <node name="outbound-interface">
   <properties>
     <help>Match outbound-interface</help>
   </properties>
   <children>
     #include <include/firewall/match-interface.xml.i>
   </children>
 </node>
 <node name="ipsec">
   <properties>
     <help>Inbound IPsec packets</help>
   </properties>
   <children>
     <leafNode name="match-ipsec">
       <properties>
         <help>Inbound IPsec packets</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="match-none">
       <properties>
         <help>Inbound non-IPsec packets</help>
         <valueless/>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="limit">
   <properties>
     <help>Rate limit using a token bucket filter</help>
   </properties>
   <children>
     <leafNode name="burst">
       <properties>
         <help>Maximum number of packets to allow in excess of rate</help>
         <valueHelp>
           <format>u32:0-4294967295</format>
           <description>Maximum number of packets to allow in excess of rate</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-4294967295"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="rate">
       <properties>
         <help>Maximum average matching rate</help>
         <valueHelp>
           <format>txt</format>
           <description>integer/unit (Example: 5/minute)</description>
         </valueHelp>
         <constraint>
           <regex>\d+/(second|minute|hour|day)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <leafNode name="log">
   <properties>
     <help>Option to log packets matching rule</help>
     <completionHelp>
       <list>enable disable</list>
     </completionHelp>
     <valueHelp>
       <format>enable</format>
       <description>Enable log</description>
     </valueHelp>
     <valueHelp>
       <format>disable</format>
       <description>Disable log</description>
     </valueHelp>
     <constraint>
       <regex>(enable|disable)</regex>
     </constraint>
   </properties>
 </leafNode>
 #include <include/firewall/rule-log-options.xml.i>
 <node name="connection-status">
   <properties>
     <help>Connection status</help>
   </properties>
   <children>
     <leafNode name="nat">
       <properties>
         <help>NAT connection status</help>
         <completionHelp>
           <list>destination source</list>
         </completionHelp>
         <valueHelp>
           <format>destination</format>
           <description>Match connections that are subject to destination NAT</description>
         </valueHelp>
         <valueHelp>
           <format>source</format>
           <description>Match connections that are subject to source NAT</description>
         </valueHelp>
         <constraint>
           <regex>^(destination|source)$</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <leafNode name="protocol">
   <properties>
     <help>Protocol to match (protocol name, number, or "all")</help>
     <completionHelp>
       <script>${vyos_completion_dir}/list_protocols.sh</script>
       <list>all tcp_udp</list>
     </completionHelp>
     <valueHelp>
       <format>all</format>
       <description>All IP protocols</description>
     </valueHelp>
     <valueHelp>
       <format>tcp_udp</format>
       <description>Both TCP and UDP</description>
     </valueHelp>
     <valueHelp>
       <format>u32:0-255</format>
       <description>IP protocol number</description>
     </valueHelp>
     <valueHelp>
       <format>&lt;protocol&gt;</format>
       <description>IP protocol name</description>
     </valueHelp>
     <valueHelp>
       <format>!&lt;protocol&gt;</format>
       <description>IP protocol name</description>
     </valueHelp>
     <constraint>
       <validator name="ip-protocol"/>
     </constraint>
   </properties>
 </leafNode>
 <node name="recent">
   <properties>
     <help>Parameters for matching recently seen sources</help>
   </properties>
   <children>
     <leafNode name="count">
       <properties>
         <help>Source addresses seen more than N times</help>
         <valueHelp>
           <format>u32:1-255</format>
           <description>Source addresses seen more than N times</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 1-255"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="time">
       <properties>
         <help>Source addresses seen in the last second/minute/hour</help>
         <completionHelp>
           <list>second minute hour</list>
         </completionHelp>
         <valueHelp>
           <format>second</format>
           <description>Source addresses seen COUNT times in the last second</description>
         </valueHelp>
         <valueHelp>
           <format>minute</format>
           <description>Source addresses seen COUNT times in the last minute</description>
         </valueHelp>
         <valueHelp>
           <format>hour</format>
           <description>Source addresses seen COUNT times in the last hour</description>
         </valueHelp>
         <constraint>
           <regex>(second|minute|hour)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="source">
   <properties>
     <help>Source parameters</help>
   </properties>
   <children>
     #include <include/firewall/address.xml.i>
     #include <include/firewall/source-destination-group.xml.i>
     #include <include/firewall/mac-address.xml.i>
     #include <include/firewall/port.xml.i>
   </children>
 </node>
 <node name="state">
   <properties>
     <help>Session state</help>
   </properties>
   <children>
     <leafNode name="established">
       <properties>
         <help>Established state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="invalid">
       <properties>
         <help>Invalid state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="new">
       <properties>
         <help>New state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="related">
       <properties>
         <help>Related state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 #include <include/firewall/tcp-flags.xml.i>
+#include <include/firewall/tcp-mss.xml.i>
 <node name="time">
   <properties>
     <help>Time to match rule</help>
   </properties>
   <children>
     <leafNode name="startdate">
       <properties>
         <help>Date to start matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter date using following notation - YYYY-MM-DD</description>
         </valueHelp>
         <constraint>
           <regex>(\d{4}\-\d{2}\-\d{2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="starttime">
       <properties>
         <help>Time of day to start matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter time using using 24 hour notation - hh:mm:ss</description>
         </valueHelp>
         <constraint>
           <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="stopdate">
       <properties>
         <help>Date to stop matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter date using following notation - YYYY-MM-DD</description>
         </valueHelp>
         <constraint>
           <regex>(\d{4}\-\d{2}\-\d{2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="stoptime">
       <properties>
         <help>Time of day to stop matching rule</help>
         <valueHelp>
           <format>txt</format>
           <description>Enter time using using 24 hour notation - hh:mm:ss</description>
         </valueHelp>
         <constraint>
           <regex>([0-2][0-9](\:[0-5][0-9]){1,2})</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="weekdays">
       <properties>
         <help>Comma separated weekdays to match rule on</help>
         <valueHelp>
           <format>txt</format>
           <description>Name of day (Monday, Tuesday, Wednesday, Thursdays, Friday, Saturday, Sunday)</description>
         </valueHelp>
         <valueHelp>
           <format>u32:0-6</format>
           <description>Day number (0 = Sunday ... 6 = Saturday)</description>
         </valueHelp>
       </properties>
     </leafNode>
   </children>
 </node>
 <!-- include end -->
diff --git a/interface-definitions/include/firewall/tcp-flags.xml.i b/interface-definitions/include/firewall/tcp-flags.xml.i
index e2ce7b9fd..36546c2e4 100644
--- a/interface-definitions/include/firewall/tcp-flags.xml.i
+++ b/interface-definitions/include/firewall/tcp-flags.xml.i
@@ -1,135 +1,119 @@
 <!-- include start from firewall/tcp-flags.xml.i -->
 <node name="tcp">
   <properties>
-    <help>TCP flags to match</help>
+    <help>TCP options to match</help>
   </properties>
   <children>
     <node name="flags">
       <properties>
         <help>TCP flags to match</help>
       </properties>
       <children>
         <leafNode name="syn">
           <properties>
             <help>Synchronise flag</help>
             <valueless/>
           </properties>
         </leafNode>
         <leafNode name="ack">
           <properties>
             <help>Acknowledge flag</help>
             <valueless/>
           </properties>
         </leafNode>
         <leafNode name="fin">
           <properties>
             <help>Finish flag</help>
             <valueless/>
           </properties>
         </leafNode>
         <leafNode name="rst">
           <properties>
             <help>Reset flag</help>
             <valueless/>
           </properties>
         </leafNode>
         <leafNode name="urg">
           <properties>
             <help>Urgent flag</help>
             <valueless/>
           </properties>
         </leafNode>
         <leafNode name="psh">
           <properties>
             <help>Push flag</help>
             <valueless/>
           </properties>
         </leafNode>
         <leafNode name="ecn">
           <properties>
             <help>Explicit Congestion Notification flag</help>
             <valueless/>
           </properties>
         </leafNode>
         <leafNode name="cwr">
           <properties>
             <help>Congestion Window Reduced flag</help>
             <valueless/>
           </properties>
         </leafNode>
         <node name="not">
           <properties>
             <help>Match flags not set</help>
           </properties>
           <children>
             <leafNode name="syn">
               <properties>
                 <help>Synchronise flag</help>
                 <valueless/>
               </properties>
             </leafNode>
             <leafNode name="ack">
               <properties>
                 <help>Acknowledge flag</help>
                 <valueless/>
               </properties>
             </leafNode>
             <leafNode name="fin">
               <properties>
                 <help>Finish flag</help>
                 <valueless/>
               </properties>
             </leafNode>
             <leafNode name="rst">
               <properties>
                 <help>Reset flag</help>
                 <valueless/>
               </properties>
             </leafNode>
             <leafNode name="urg">
               <properties>
                 <help>Urgent flag</help>
                 <valueless/>
               </properties>
             </leafNode>
             <leafNode name="psh">
               <properties>
                 <help>Push flag</help>
                 <valueless/>
               </properties>
             </leafNode>
             <leafNode name="ecn">
               <properties>
                 <help>Explicit Congestion Notification flag</help>
                 <valueless/>
               </properties>
             </leafNode>
             <leafNode name="cwr">
               <properties>
                 <help>Congestion Window Reduced flag</help>
                 <valueless/>
               </properties>
             </leafNode>
           </children>
         </node>
       </children>
     </node>
-    <leafNode name="mss">
-      <properties>
-        <help>Maximum segment size (MSS)</help>
-        <valueHelp>
-          <format>u32:1-16384</format>
-          <description>Maximum segment size</description>
-        </valueHelp>
-        <valueHelp>
-          <format>&lt;min&gt;-&lt;max&gt;</format>
-          <description>TCP MSS range (use '-' as delimiter)</description>
-        </valueHelp>
-        <constraint>
-          <validator name="numeric" argument="--allow-range --range 1-16384"/>
-        </constraint>
-      </properties>
-    </leafNode>
   </children>
 </node>
 <!-- include end -->
diff --git a/interface-definitions/include/firewall/tcp-mss.xml.i b/interface-definitions/include/firewall/tcp-mss.xml.i
new file mode 100644
index 000000000..dc49b4272
--- /dev/null
+++ b/interface-definitions/include/firewall/tcp-mss.xml.i
@@ -0,0 +1,25 @@
+<!-- include start from firewall/tcp-mss.xml.i -->
+<node name="tcp">
+  <properties>
+    <help>TCP options to match</help>
+  </properties>
+  <children>
+    <leafNode name="mss">
+      <properties>
+        <help>Maximum segment size (MSS)</help>
+        <valueHelp>
+          <format>u32:1-16384</format>
+          <description>Maximum segment size</description>
+        </valueHelp>
+        <valueHelp>
+          <format>&lt;min&gt;-&lt;max&gt;</format>
+          <description>TCP MSS range (use '-' as delimiter)</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--allow-range --range 1-16384"/>
+        </constraint>
+      </properties>
+    </leafNode>
+  </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i
index 216ec9bea..6551d23ab 100644
--- a/interface-definitions/include/policy/route-common.xml.i
+++ b/interface-definitions/include/policy/route-common.xml.i
@@ -1,360 +1,361 @@
 <!-- include start from policy/route-common.xml.i -->
 #include <include/policy/route-rule-action.xml.i>
 #include <include/generic-description.xml.i>
 <leafNode name="disable">
   <properties>
     <help>Option to disable firewall rule</help>
     <valueless/>
   </properties>
 </leafNode>
 <node name="fragment">
   <properties>
     <help>IP fragment match</help>
   </properties>
   <children>
     <leafNode name="match-frag">
       <properties>
         <help>Second and further fragments of fragmented packets</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="match-non-frag">
       <properties>
         <help>Head fragments or unfragmented packets</help>
         <valueless/>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="ipsec">
   <properties>
     <help>Inbound IPsec packets</help>
   </properties>
   <children>
     <leafNode name="match-ipsec">
       <properties>
         <help>Inbound IPsec packets</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="match-none">
       <properties>
         <help>Inbound non-IPsec packets</help>
         <valueless/>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="limit">
   <properties>
     <help>Rate limit using a token bucket filter</help>
   </properties>
   <children>
     <leafNode name="burst">
       <properties>
         <help>Maximum number of packets to allow in excess of rate</help>
         <valueHelp>
           <format>u32:0-4294967295</format>
           <description>Maximum number of packets to allow in excess of rate</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-4294967295"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="rate">
       <properties>
         <help>Maximum average matching rate</help>
         <valueHelp>
           <format>u32:0-4294967295</format>
           <description>Maximum average matching rate</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-4294967295"/>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <leafNode name="log">
   <properties>
     <help>Option to log packets matching rule</help>
     <completionHelp>
       <list>enable disable</list>
     </completionHelp>
     <valueHelp>
       <format>enable</format>
       <description>Enable log</description>
     </valueHelp>
     <valueHelp>
       <format>disable</format>
       <description>Disable log</description>
     </valueHelp>
     <constraint>
       <regex>(enable|disable)</regex>
     </constraint>
   </properties>
 </leafNode>
 <leafNode name="protocol">
   <properties>
     <help>Protocol to match (protocol name, number, or "all")</help>
     <completionHelp>
       <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
     </completionHelp>
     <valueHelp>
       <format>all</format>
       <description>All IP protocols</description>
     </valueHelp>
     <valueHelp>
       <format>tcp_udp</format>
       <description>Both TCP and UDP</description>
     </valueHelp>
     <valueHelp>
       <format>0-255</format>
       <description>IP protocol number</description>
     </valueHelp>
     <valueHelp>
       <format>!&lt;protocol&gt;</format>
       <description>IP protocol number</description>
     </valueHelp>
     <constraint>
       <validator name="ip-protocol"/>
     </constraint>
   </properties>
   <defaultValue>all</defaultValue>
 </leafNode>
 <node name="recent">
   <properties>
     <help>Parameters for matching recently seen sources</help>
   </properties>
   <children>
     <leafNode name="count">
       <properties>
         <help>Source addresses seen more than N times</help>
         <valueHelp>
           <format>u32:1-255</format>
           <description>Source addresses seen more than N times</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 1-255"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="time">
       <properties>
         <help>Source addresses seen in the last N seconds</help>
         <valueHelp>
           <format>u32:0-4294967295</format>
           <description>Source addresses seen in the last N seconds</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-4294967295"/>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="set">
   <properties>
     <help>Packet modifications</help>
   </properties>
   <children>
     <leafNode name="connection-mark">
       <properties>
         <help>Connection marking</help>
         <valueHelp>
           <format>u32:0-2147483647</format>
           <description>Connection marking</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-2147483647"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="dscp">
       <properties>
         <help>Packet Differentiated Services Codepoint (DSCP)</help>
         <valueHelp>
           <format>u32:0-63</format>
           <description>DSCP number</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-63"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="mark">
       <properties>
         <help>Packet marking</help>
         <valueHelp>
           <format>u32:1-2147483647</format>
           <description>Packet marking</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 1-2147483647"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="table">
       <properties>
         <help>Routing table to forward packet with</help>
         <valueHelp>
           <format>u32:1-200</format>
           <description>Table number</description>
         </valueHelp>
         <valueHelp>
           <format>main</format>
           <description>Main table</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 1-200"/>
           <regex>(main)</regex>
         </constraint>
         <completionHelp>
           <list>main</list>
           <path>protocols static table</path>
         </completionHelp>
       </properties>
     </leafNode>
     <leafNode name="tcp-mss">
       <properties>
         <help>TCP Maximum Segment Size</help>
         <valueHelp>
           <format>u32:500-1460</format>
           <description>Explicitly set TCP MSS value</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 500-1460"/>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 <node name="state">
   <properties>
     <help>Session state</help>
   </properties>
   <children>
     <leafNode name="established">
       <properties>
         <help>Established state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="invalid">
       <properties>
         <help>Invalid state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="new">
       <properties>
         <help>New state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="related">
       <properties>
         <help>Related state</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
 #include <include/firewall/tcp-flags.xml.i>
+#include <include/firewall/tcp-mss.xml.i>
 <node name="time">
   <properties>
     <help>Time to match rule</help>
   </properties>
   <children>
     <leafNode name="monthdays">
       <properties>
         <help>Monthdays to match rule on</help>
       </properties>
     </leafNode>
     <leafNode name="startdate">
       <properties>
         <help>Date to start matching rule</help>
       </properties>
     </leafNode>
     <leafNode name="starttime">
       <properties>
         <help>Time of day to start matching rule</help>
       </properties>
     </leafNode>
     <leafNode name="stopdate">
       <properties>
         <help>Date to stop matching rule</help>
       </properties>
     </leafNode>
     <leafNode name="stoptime">
       <properties>
         <help>Time of day to stop matching rule</help>
       </properties>
     </leafNode>
     <leafNode name="utc">
       <properties>
         <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="weekdays">
       <properties>
         <help>Weekdays to match rule on</help>
       </properties>
     </leafNode>
   </children>
 </node>
 <!-- include end -->
diff --git a/interface-definitions/system-conntrack.xml.in b/interface-definitions/system-conntrack.xml.in
index 78d19090c..4452f1a74 100644
--- a/interface-definitions/system-conntrack.xml.in
+++ b/interface-definitions/system-conntrack.xml.in
@@ -1,447 +1,449 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="system">
     <children>
       <node name="conntrack" owner="${vyos_conf_scripts_dir}/conntrack.py">
         <properties>
           <help>Connection Tracking Engine Options</help>
           <!-- Before NAT and conntrack-sync are configured -->
           <priority>218</priority>
         </properties>
         <children>
           <leafNode name="flow-accounting">
             <properties>
               <help>Enable connection tracking flow accounting</help>
               <valueless/>
             </properties>
           </leafNode>
           <leafNode name="expect-table-size">
             <properties>
               <help>Size of connection tracking expect table</help>
               <valueHelp>
                 <format>u32:1-50000000</format>
                 <description>Number of entries allowed in connection tracking expect table</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 1-50000000"/>
               </constraint>
             </properties>
             <defaultValue>2048</defaultValue>
           </leafNode>
           <leafNode name="hash-size">
             <properties>
               <help>Hash size for connection tracking table</help>
               <valueHelp>
                 <format>u32:1-50000000</format>
                 <description>Size of hash to use for connection tracking table</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 1-50000000"/>
               </constraint>
             </properties>
             <defaultValue>32768</defaultValue>
           </leafNode>
           <node name="ignore">
             <properties>
               <help>Customized rules to ignore selective connection tracking</help>
             </properties>
             <children>
               <node name="ipv4">
                 <properties>
                   <help>IPv4 rules</help>
                 </properties>
                 <children>
                   <tagNode name="rule">
                     <properties>
                       <help>Rule number</help>
                       <valueHelp>
                         <format>u32:1-999999</format>
                         <description>Number of conntrack ignore rule</description>
                       </valueHelp>
                       <constraint>
                         <validator name="numeric" argument="--range 1-999999"/>
                       </constraint>
                       <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
                     </properties>
                     <children>
                       #include <include/generic-description.xml.i>
                       <node name="destination">
                         <properties>
                           <help>Destination parameters</help>
                         </properties>
                         <children>
                           #include <include/firewall/source-destination-group-ipv4.xml.i>
                           #include <include/nat-address.xml.i>
                           #include <include/nat-port.xml.i>
                         </children>
                       </node>
                       <leafNode name="inbound-interface">
                         <properties>
                           <help>Interface to ignore connections tracking on</help>
                           <completionHelp>
                             <list>any</list>
                             <script>${vyos_completion_dir}/list_interfaces</script>
                           </completionHelp>
                         </properties>
                       </leafNode>
                       #include <include/ip-protocol.xml.i>
                       <leafNode name="protocol">
                         <properties>
                           <help>Protocol to match (protocol name, number, or "all")</help>
                           <completionHelp>
                             <script>${vyos_completion_dir}/list_protocols.sh</script>
                             <list>all tcp_udp</list>
                           </completionHelp>
                           <valueHelp>
                             <format>all</format>
                             <description>All IP protocols</description>
                           </valueHelp>
                           <valueHelp>
                             <format>tcp_udp</format>
                             <description>Both TCP and UDP</description>
                           </valueHelp>
                           <valueHelp>
                             <format>u32:0-255</format>
                             <description>IP protocol number</description>
                           </valueHelp>
                           <valueHelp>
                             <format>&lt;protocol&gt;</format>
                             <description>IP protocol name</description>
                           </valueHelp>
                           <valueHelp>
                             <format>!&lt;protocol&gt;</format>
                             <description>IP protocol name</description>
                           </valueHelp>
                           <constraint>
                             <validator name="ip-protocol"/>
                           </constraint>
                         </properties>
                       </leafNode>
                       <node name="source">
                         <properties>
                           <help>Source parameters</help>
                         </properties>
                         <children>
                           #include <include/firewall/source-destination-group-ipv4.xml.i>
                           #include <include/nat-address.xml.i>
                           #include <include/nat-port.xml.i>
                         </children>
                       </node>
+                      #include <include/firewall/tcp-flags.xml.i>
                     </children>
                   </tagNode>
                 </children>
               </node>
               <node name="ipv6">
                 <properties>
                   <help>IPv6 rules</help>
                 </properties>
                 <children>
                   <tagNode name="rule">
                     <properties>
                       <help>Rule number</help>
                       <valueHelp>
                         <format>u32:1-999999</format>
                         <description>Number of conntrack ignore rule</description>
                       </valueHelp>
                       <constraint>
                         <validator name="numeric" argument="--range 1-999999"/>
                       </constraint>
                       <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
                     </properties>
                     <children>
                       #include <include/generic-description.xml.i>
                       <node name="destination">
                         <properties>
                           <help>Destination parameters</help>
                         </properties>
                         <children>
                           #include <include/firewall/address-ipv6.xml.i>
                           #include <include/firewall/source-destination-group-ipv6.xml.i>
                           #include <include/nat-port.xml.i>
                         </children>
                       </node>
                       <leafNode name="inbound-interface">
                         <properties>
                           <help>Interface to ignore connections tracking on</help>
                           <completionHelp>
                             <list>any</list>
                             <script>${vyos_completion_dir}/list_interfaces</script>
                           </completionHelp>
                         </properties>
                       </leafNode>
                       #include <include/ip-protocol.xml.i>
                       <leafNode name="protocol">
                         <properties>
                           <help>Protocol to match (protocol name, number, or "all")</help>
                           <completionHelp>
                             <script>${vyos_completion_dir}/list_protocols.sh</script>
                             <list>all tcp_udp</list>
                           </completionHelp>
                           <valueHelp>
                             <format>all</format>
                             <description>All IP protocols</description>
                           </valueHelp>
                           <valueHelp>
                             <format>tcp_udp</format>
                             <description>Both TCP and UDP</description>
                           </valueHelp>
                           <valueHelp>
                             <format>u32:0-255</format>
                             <description>IP protocol number</description>
                           </valueHelp>
                           <valueHelp>
                             <format>&lt;protocol&gt;</format>
                             <description>IP protocol name</description>
                           </valueHelp>
                           <valueHelp>
                             <format>!&lt;protocol&gt;</format>
                             <description>IP protocol name</description>
                           </valueHelp>
                           <constraint>
                             <validator name="ip-protocol"/>
                           </constraint>
                         </properties>
                       </leafNode>
                       <node name="source">
                         <properties>
                           <help>Source parameters</help>
                         </properties>
                         <children>
                           #include <include/firewall/address-ipv6.xml.i>
                           #include <include/firewall/source-destination-group-ipv6.xml.i>
                           #include <include/nat-port.xml.i>
                         </children>
                       </node>
+                      #include <include/firewall/tcp-flags.xml.i>
                     </children>
                   </tagNode>
                 </children>
               </node>
               
             </children>
           </node>
           <node name="log">
             <properties>
               <help>Log connection tracking events per protocol</help>
             </properties>
             <children>
               <node name="icmp">
                 <properties>
                   <help>Log connection tracking events for ICMP</help>
                 </properties>
                 <children>
                   #include <include/conntrack/log-common.xml.i>
                 </children>
               </node>
               <node name="other">
                 <properties>
                   <help>Log connection tracking events for all protocols other than TCP, UDP and ICMP</help>
                 </properties>
                 <children>
                   #include <include/conntrack/log-common.xml.i>
                 </children>
               </node>
               <node name="tcp">
                 <properties>
                   <help>Log connection tracking events for TCP</help>
                 </properties>
                 <children>
                   #include <include/conntrack/log-common.xml.i>
                 </children>
               </node>
               <node name="udp">
                 <properties>
                   <help>Log connection tracking events for UDP</help>
                 </properties>
                 <children>
                   #include <include/conntrack/log-common.xml.i>
                 </children>
               </node>
             </children>
           </node>
           <node name="modules">
             <properties>
               <help>Connection tracking modules</help>
             </properties>
             <children>
               <leafNode name="ftp">
                 <properties>
                   <help>FTP connection tracking</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="h323">
                 <properties>
                   <help>H.323 connection tracking</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="nfs">
                 <properties>
                   <help>NFS connection tracking</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="pptp">
                 <properties>
                   <help>PPTP connection tracking</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="sip">
                 <properties>
                   <help>SIP connection tracking</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="sqlnet">
                 <properties>
                   <help>SQLnet connection tracking</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <leafNode name="tftp">
                 <properties>
                   <help>TFTP connection tracking</help>
                   <valueless/>
                 </properties>
               </leafNode>
             </children>
           </node>
           <leafNode name="table-size">
             <properties>
               <help>Size of connection tracking table</help>
               <valueHelp>
                 <format>u32:1-50000000</format>
                 <description>Number of entries allowed in connection tracking table</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 1-50000000"/>
               </constraint>
             </properties>
             <defaultValue>262144</defaultValue>
           </leafNode>
           <node name="tcp">
             <properties>
               <help>TCP options</help>
             </properties>
             <children>
               <leafNode name="half-open-connections">
                 <properties>
                   <help>Maximum number of TCP half-open connections</help>
                   <valueHelp>
                     <format>u32:1-2147483647</format>
                     <description>Generic connection timeout in seconds</description>
                   </valueHelp>
                   <constraint>
                     <validator name="numeric" argument="--range 1-2147483647"/>
                   </constraint>
                 </properties>
                 <defaultValue>512</defaultValue>
               </leafNode>
               <leafNode name="loose">
                 <properties>
                   <help>Policy to track previously established connections</help>
                   <completionHelp>
                     <list>enable disable</list>
                   </completionHelp>
                   <valueHelp>
                     <format>enable</format>
                     <description>Allow tracking of previously established connections</description>
                   </valueHelp>
                   <valueHelp>
                     <format>disable</format>
                     <description>Do not allow tracking of previously established connections</description>
                   </valueHelp>
                   <constraint>
                     <regex>(enable|disable)</regex>
                   </constraint>
                 </properties>
                 <defaultValue>enable</defaultValue>
               </leafNode>
               <leafNode name="max-retrans">
                 <properties>
                   <help>Maximum number of packets that can be retransmitted without received an ACK</help>
                   <valueHelp>
                     <format>u32:1-255</format>
                     <description>Number of packets to be retransmitted</description>
                   </valueHelp>
                   <constraint>
                     <validator name="numeric" argument="--range 1-255"/>
                   </constraint>
                 </properties>
                 <defaultValue>3</defaultValue>
               </leafNode>
             </children>
           </node>
           <node name="timeout">
             <properties>
               <help>Connection timeout options</help>
             </properties>
             <children>
               <node name="custom">
                 <properties>
                   <help>Define custom timeouts per connection</help>
                 </properties>
                 <children>
                   <tagNode name="rule">
                     <properties>
                       <help>Rule number</help>
                       <valueHelp>
                         <format>u32:1-999999</format>
                         <description>Number of conntrack rule</description>
                       </valueHelp>
                       <constraint>
                         <validator name="numeric" argument="--range 1-999999"/>
                       </constraint>
                       <constraintErrorMessage>Ignore rule number must be between 1 and 999999</constraintErrorMessage>
                     </properties>
                     <children>
                       #include <include/generic-description.xml.i>
                       <node name="destination">
                         <properties>
                           <help>Destination parameters</help>
                         </properties>
                         <children>
                           #include <include/nat-address.xml.i>
                           #include <include/nat-port.xml.i>
                         </children>
                       </node>
                       <leafNode name="inbound-interface">
                         <properties>
                           <help>Interface to ignore connections tracking on</help>
                           <completionHelp>
                             <list>any</list>
                             <script>${vyos_completion_dir}/list_interfaces</script>
                           </completionHelp>
                         </properties>
                       </leafNode>
                       #include <include/ip-protocol.xml.i>
                       <node name="protocol">
                         <properties>
                           <help>Customize protocol specific timers, one protocol configuration per rule</help>
                         </properties>
                         <children>
                           #include <include/conntrack/timeout-common-protocols.xml.i>
                         </children>
                       </node>
                       <node name="source">
                         <properties>
                           <help>Source parameters</help>
                         </properties>
                         <children>
                           #include <include/nat-address.xml.i>
                           #include <include/nat-port.xml.i>
                         </children>
                       </node>
                     </children>
                   </tagNode>
                 </children>
               </node>
               #include <include/conntrack/timeout-common-protocols.xml.i>
             </children>
           </node>
         </children>
       </node>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/python/vyos/template.py b/python/vyos/template.py
index add4d3ce5..3be486cc4 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -1,821 +1,826 @@
 # Copyright 2019-2023 VyOS maintainers and contributors <maintainers@vyos.io>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
 # version 2.1 of the License, or (at your option) any later version.
 #
 # This library 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
 # Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
 import functools
 import os
 
 from jinja2 import Environment
 from jinja2 import FileSystemLoader
 from jinja2 import ChainableUndefined
 from vyos.defaults import directories
 from vyos.utils.dict import dict_search_args
 from vyos.utils.file import makedir
 from vyos.utils.permission import chmod
 from vyos.utils.permission import chown
 
 # Holds template filters registered via register_filter()
 _FILTERS = {}
 _TESTS = {}
 
 # reuse Environments with identical settings to improve performance
 @functools.lru_cache(maxsize=2)
 def _get_environment(location=None):
     if location is None:
         loc_loader=FileSystemLoader(directories["templates"])
     else:
         loc_loader=FileSystemLoader(location)
     env = Environment(
         # Don't check if template files were modified upon re-rendering
         auto_reload=False,
         # Cache up to this number of templates for quick re-rendering
         cache_size=100,
         loader=loc_loader,
         trim_blocks=True,
         undefined=ChainableUndefined,
         extensions=['jinja2.ext.loopcontrols']
     )
     env.filters.update(_FILTERS)
     env.tests.update(_TESTS)
     return env
 
 
 def register_filter(name, func=None):
     """Register a function to be available as filter in templates under given name.
 
     It can also be used as a decorator, see below in this module for examples.
 
     :raise RuntimeError:
         when trying to register a filter after a template has been rendered already
     :raise ValueError: when trying to register a name which was taken already
     """
     if func is None:
         return functools.partial(register_filter, name)
     if _get_environment.cache_info().currsize:
         raise RuntimeError(
             "Filters can only be registered before rendering the first template"
         )
     if name in _FILTERS:
         raise ValueError(f"A filter with name {name!r} was registered already")
     _FILTERS[name] = func
     return func
 
 def register_test(name, func=None):
     """Register a function to be available as test in templates under given name.
 
     It can also be used as a decorator, see below in this module for examples.
 
     :raise RuntimeError:
         when trying to register a test after a template has been rendered already
     :raise ValueError: when trying to register a name which was taken already
     """
     if func is None:
         return functools.partial(register_test, name)
     if _get_environment.cache_info().currsize:
         raise RuntimeError(
             "Tests can only be registered before rendering the first template"
             )
     if name in _TESTS:
         raise ValueError(f"A test with name {name!r} was registered already")
     _TESTS[name] = func
     return func
 
 
 def render_to_string(template, content, formater=None, location=None):
     """Render a template from the template directory, raise on any errors.
 
     :param template: the path to the template relative to the template folder
     :param content: the dictionary of variables to put into rendering context
     :param formater:
         if given, it has to be a callable the rendered string is passed through
 
     The parsed template files are cached, so rendering the same file multiple times
     does not cause as too much overhead.
     If used everywhere, it could be changed to load the template from Python
     environment variables from an importable Python module generated when the Debian
     package is build (recovering the load time and overhead caused by having the
     file out of the code).
     """
     template = _get_environment(location).get_template(template)
     rendered = template.render(content)
     if formater is not None:
         rendered = formater(rendered)
     return rendered
 
 
 def render(
     destination,
     template,
     content,
     formater=None,
     permission=None,
     user=None,
     group=None,
     location=None,
 ):
     """Render a template from the template directory to a file, raise on any errors.
 
     :param destination: path to the file to save the rendered template in
     :param permission: permission bitmask to set for the output file
     :param user: user to own the output file
     :param group: group to own the output file
 
     All other parameters are as for :func:`render_to_string`.
     """
     # Create the directory if it does not exist
     folder = os.path.dirname(destination)
     makedir(folder, user, group)
 
     # As we are opening the file with 'w', we are performing the rendering before
     # calling open() to not accidentally erase the file if rendering fails
     rendered = render_to_string(template, content, formater, location)
 
     # Write to file
     with open(destination, "w") as file:
         chmod(file.fileno(), permission)
         chown(file.fileno(), user, group)
         file.write(rendered)
 
 
 ##################################
 # Custom template filters follow #
 ##################################
 @register_filter('force_to_list')
 def force_to_list(value):
     """ Convert scalars to single-item lists and leave lists untouched """
     if isinstance(value, list):
         return value
     else:
         return [value]
 
 @register_filter('seconds_to_human')
 def seconds_to_human(seconds, separator=""):
     """ Convert seconds to human-readable values like 1d6h15m23s """
     from vyos.utils.convert import seconds_to_human
     return seconds_to_human(seconds, separator=separator)
 
 @register_filter('bytes_to_human')
 def bytes_to_human(bytes, initial_exponent=0, precision=2):
     """ Convert bytes to human-readable values like 1.44M """
     from vyos.utils.convert import bytes_to_human
     return bytes_to_human(bytes, initial_exponent=initial_exponent, precision=precision)
 
 @register_filter('human_to_bytes')
 def human_to_bytes(value):
     """ Convert a data amount with a unit suffix to bytes, like 2K to 2048 """
     from vyos.utils.convert import human_to_bytes
     return human_to_bytes(value)
 
 @register_filter('ip_from_cidr')
 def ip_from_cidr(prefix):
     """ Take an IPv4/IPv6 CIDR host and strip cidr mask.
     Example:
     192.0.2.1/24 -> 192.0.2.1, 2001:db8::1/64 -> 2001:db8::1
     """
     from ipaddress import ip_interface
     return str(ip_interface(prefix).ip)
 
 @register_filter('address_from_cidr')
 def address_from_cidr(prefix):
     """ Take an IPv4/IPv6 CIDR prefix and convert the network to an "address".
     Example:
     192.0.2.0/24 -> 192.0.2.0, 2001:db8::/48 -> 2001:db8::
     """
     from ipaddress import ip_network
     return str(ip_network(prefix).network_address)
 
 @register_filter('bracketize_ipv6')
 def bracketize_ipv6(address):
     """ Place a passed IPv6 address into [] brackets, do nothing for IPv4 """
     if is_ipv6(address):
         return f'[{address}]'
     return address
 
 @register_filter('dot_colon_to_dash')
 def dot_colon_to_dash(text):
     """ Replace dot and colon to dash for string
     Example:
     192.0.2.1 => 192-0-2-1, 2001:db8::1 => 2001-db8--1
     """
     text = text.replace(":", "-")
     text = text.replace(".", "-")
     return text
 
 @register_filter('generate_uuid4')
 def generate_uuid4(text):
     """ Generate random unique ID
     Example:
       % uuid4()
       UUID('958ddf6a-ef14-4e81-8cfb-afb12456d1c5')
     """
     from uuid import uuid4
     return uuid4()
 
 @register_filter('netmask_from_cidr')
 def netmask_from_cidr(prefix):
     """ Take CIDR prefix and convert the prefix length to a "subnet mask".
     Example:
       - 192.0.2.0/24 -> 255.255.255.0
       - 2001:db8::/48 -> ffff:ffff:ffff::
     """
     from ipaddress import ip_network
     return str(ip_network(prefix).netmask)
 
 @register_filter('netmask_from_ipv4')
 def netmask_from_ipv4(address):
     """ Take IP address and search all attached interface IP addresses for the
     given one. After address has been found, return the associated netmask.
 
     Example:
       - 172.18.201.10 -> 255.255.255.128
     """
     from netifaces import interfaces
     from netifaces import ifaddresses
     from netifaces import AF_INET
     for interface in interfaces():
         tmp = ifaddresses(interface)
         if AF_INET in tmp:
             for af_addr in tmp[AF_INET]:
                 if 'addr' in af_addr:
                     if af_addr['addr'] == address:
                         return af_addr['netmask']
 
     raise ValueError
 
 @register_filter('is_ip_network')
 def is_ip_network(addr):
     """ Take IP(v4/v6) address and validate if the passed argument is a network
     or a host address.
 
     Example:
       - 192.0.2.0          -> False
       - 192.0.2.10/24      -> False
       - 192.0.2.0/24       -> True
       - 2001:db8::         -> False
       - 2001:db8::100      -> False
       - 2001:db8::/48      -> True
       - 2001:db8:1000::/64 -> True
     """
     try:
         from ipaddress import ip_network
         # input variables must contain a / to indicate its CIDR notation
         if len(addr.split('/')) != 2:
             raise ValueError()
         ip_network(addr)
         return True
     except:
         return False
 
 @register_filter('network_from_ipv4')
 def network_from_ipv4(address):
     """ Take IP address and search all attached interface IP addresses for the
     given one. After address has been found, return the associated network
     address.
 
     Example:
       - 172.18.201.10 has mask 255.255.255.128 -> network is 172.18.201.0
     """
     netmask = netmask_from_ipv4(address)
     from ipaddress import ip_interface
     cidr_prefix = ip_interface(f'{address}/{netmask}').network
     return address_from_cidr(cidr_prefix)
 
 @register_filter('is_interface')
 def is_interface(interface):
     """ Check if parameter is a valid local interface name """
     return os.path.exists(f'/sys/class/net/{interface}')
 
 @register_filter('is_ip')
 def is_ip(addr):
     """ Check addr if it is an IPv4 or IPv6 address """
     return is_ipv4(addr) or is_ipv6(addr)
 
 @register_filter('is_ipv4')
 def is_ipv4(text):
     """ Filter IP address, return True on IPv4 address, False otherwise """
     from ipaddress import ip_interface
     try: return ip_interface(text).version == 4
     except: return False
 
 @register_filter('is_ipv6')
 def is_ipv6(text):
     """ Filter IP address, return True on IPv6 address, False otherwise """
     from ipaddress import ip_interface
     try: return ip_interface(text).version == 6
     except: return False
 
 @register_filter('first_host_address')
 def first_host_address(text):
     """ Return first usable (host) IP address from given prefix.
     Example:
       - 10.0.0.0/24 -> 10.0.0.1
       - 2001:db8::/64 -> 2001:db8::
     """
     from ipaddress import ip_interface
     from ipaddress import IPv4Network
     from ipaddress import IPv6Network
 
     addr = ip_interface(text)
     if addr.version == 4:
         return str(addr.ip +1)
     return str(addr.ip)
 
 @register_filter('last_host_address')
 def last_host_address(text):
     """ Return first usable IP address from given prefix.
     Example:
       - 10.0.0.0/24 -> 10.0.0.254
       - 2001:db8::/64 -> 2001:db8::ffff:ffff:ffff:ffff
     """
     from ipaddress import ip_interface
     from ipaddress import IPv4Network
     from ipaddress import IPv6Network
 
     addr = ip_interface(text)
     if addr.version == 4:
         return str(IPv4Network(addr).broadcast_address - 1)
 
     return str(IPv6Network(addr).broadcast_address)
 
 @register_filter('inc_ip')
 def inc_ip(address, increment):
     """ Increment given IP address by 'increment'
 
     Example (inc by 2):
       - 10.0.0.0/24 -> 10.0.0.2
       - 2001:db8::/64 -> 2001:db8::2
     """
     from ipaddress import ip_interface
     return str(ip_interface(address).ip + int(increment))
 
 @register_filter('dec_ip')
 def dec_ip(address, decrement):
     """ Decrement given IP address by 'decrement'
 
     Example (inc by 2):
       - 10.0.0.0/24 -> 10.0.0.2
       - 2001:db8::/64 -> 2001:db8::2
     """
     from ipaddress import ip_interface
     return str(ip_interface(address).ip - int(decrement))
 
 @register_filter('compare_netmask')
 def compare_netmask(netmask1, netmask2):
     """
     Compare two IP netmask if they have the exact same size.
 
     compare_netmask('10.0.0.0/8', '20.0.0.0/8') -> True
     compare_netmask('10.0.0.0/8', '20.0.0.0/16') -> False
     """
     from ipaddress import ip_network
     try:
         return ip_network(netmask1).netmask == ip_network(netmask2).netmask
     except:
         return False
 
 @register_filter('isc_static_route')
 def isc_static_route(subnet, router):
     # https://ercpe.de/blog/pushing-static-routes-with-isc-dhcp-server
     # Option format is:
     # <netmask>, <network-byte1>, <network-byte2>, <network-byte3>, <router-byte1>, <router-byte2>, <router-byte3>
     # where bytes with the value 0 are omitted.
     from ipaddress import ip_network
     net = ip_network(subnet)
     # add netmask
     string = str(net.prefixlen) + ','
     # add network bytes
     if net.prefixlen:
         width = net.prefixlen // 8
         if net.prefixlen % 8:
             width += 1
         string += ','.join(map(str,tuple(net.network_address.packed)[:width])) + ','
 
     # add router bytes
     string += ','.join(router.split('.'))
 
     return string
 
 @register_filter('is_file')
 def is_file(filename):
     if os.path.exists(filename):
         return os.path.isfile(filename)
     return False
 
 @register_filter('get_dhcp_router')
 def get_dhcp_router(interface):
     """ Static routes can point to a router received by a DHCP reply. This
     helper is used to get the current default router from the DHCP reply.
 
     Returns False of no router is found, returns the IP address as string if
     a router is found.
     """
     lease_file = directories['isc_dhclient_dir'] + f'/dhclient_{interface}.leases'
     if not os.path.exists(lease_file):
         return None
 
     from vyos.utils.file import read_file
     for line in read_file(lease_file).splitlines():
         if 'option routers' in line:
             (_, _, address) = line.split()
             return address.rstrip(';')
 
 @register_filter('natural_sort')
 def natural_sort(iterable):
     import re
     from jinja2.runtime import Undefined
 
     if isinstance(iterable, Undefined) or iterable is None:
         return list()
 
     def convert(text):
         return int(text) if text.isdigit() else text.lower()
     def alphanum_key(key):
         return [convert(c) for c in re.split('([0-9]+)', str(key))]
 
     return sorted(iterable, key=alphanum_key)
 
 @register_filter('get_ipv4')
 def get_ipv4(interface):
     """ Get interface IPv4 addresses"""
     from vyos.ifconfig import Interface
     return Interface(interface).get_addr_v4()
 
 @register_filter('get_ipv6')
 def get_ipv6(interface):
     """ Get interface IPv6 addresses"""
     from vyos.ifconfig import Interface
     return Interface(interface).get_addr_v6()
 
 @register_filter('get_ip')
 def get_ip(interface):
     """ Get interface IP addresses"""
     from vyos.ifconfig import Interface
     return Interface(interface).get_addr()
 
 def get_first_ike_dh_group(ike_group):
     if ike_group and 'proposal' in ike_group:
         for priority, proposal in ike_group['proposal'].items():
             if 'dh_group' in proposal:
                 return 'dh-group' + proposal['dh_group']
     return 'dh-group2' # Fallback on dh-group2
 
 @register_filter('get_esp_ike_cipher')
 def get_esp_ike_cipher(group_config, ike_group=None):
     pfs_lut = {
         'dh-group1'  : 'modp768',
         'dh-group2'  : 'modp1024',
         'dh-group5'  : 'modp1536',
         'dh-group14' : 'modp2048',
         'dh-group15' : 'modp3072',
         'dh-group16' : 'modp4096',
         'dh-group17' : 'modp6144',
         'dh-group18' : 'modp8192',
         'dh-group19' : 'ecp256',
         'dh-group20' : 'ecp384',
         'dh-group21' : 'ecp521',
         'dh-group22' : 'modp1024s160',
         'dh-group23' : 'modp2048s224',
         'dh-group24' : 'modp2048s256',
         'dh-group25' : 'ecp192',
         'dh-group26' : 'ecp224',
         'dh-group27' : 'ecp224bp',
         'dh-group28' : 'ecp256bp',
         'dh-group29' : 'ecp384bp',
         'dh-group30' : 'ecp512bp',
         'dh-group31' : 'curve25519',
         'dh-group32' : 'curve448'
     }
 
     ciphers = []
     if 'proposal' in group_config:
         for priority, proposal in group_config['proposal'].items():
             # both encryption and hash need to be specified for a proposal
             if not {'encryption', 'hash'} <= set(proposal):
                 continue
 
             tmp = '{encryption}-{hash}'.format(**proposal)
             if 'prf' in proposal:
                 tmp += '-' + proposal['prf']
             if 'dh_group' in proposal:
                 tmp += '-' + pfs_lut[ 'dh-group' +  proposal['dh_group'] ]
             elif 'pfs' in group_config and group_config['pfs'] != 'disable':
                 group = group_config['pfs']
                 if group_config['pfs'] == 'enable':
                     group = get_first_ike_dh_group(ike_group)
                 tmp += '-' + pfs_lut[group]
 
             ciphers.append(tmp)
     return ciphers
 
 @register_filter('get_uuid')
 def get_uuid(interface):
     """ Get interface IP addresses"""
     from uuid import uuid1
     return uuid1()
 
 openvpn_translate = {
     'des': 'des-cbc',
     '3des': 'des-ede3-cbc',
     'bf128': 'bf-cbc',
     'bf256': 'bf-cbc',
     'aes128gcm': 'aes-128-gcm',
     'aes128': 'aes-128-cbc',
     'aes192gcm': 'aes-192-gcm',
     'aes192': 'aes-192-cbc',
     'aes256gcm': 'aes-256-gcm',
     'aes256': 'aes-256-cbc'
 }
 
 @register_filter('openvpn_cipher')
 def get_openvpn_cipher(cipher):
     if cipher in openvpn_translate:
         return openvpn_translate[cipher].upper()
     return cipher.upper()
 
 @register_filter('openvpn_ncp_ciphers')
 def get_openvpn_ncp_ciphers(ciphers):
     out = []
     for cipher in ciphers:
         if cipher in openvpn_translate:
             out.append(openvpn_translate[cipher])
         else:
             out.append(cipher)
     return ':'.join(out).upper()
 
 @register_filter('snmp_auth_oid')
 def snmp_auth_oid(type):
     if type not in ['md5', 'sha', 'aes', 'des', 'none']:
         raise ValueError()
 
     OIDs = {
         'md5' : '.1.3.6.1.6.3.10.1.1.2',
         'sha' : '.1.3.6.1.6.3.10.1.1.3',
         'aes' : '.1.3.6.1.6.3.10.1.2.4',
         'des' : '.1.3.6.1.6.3.10.1.2.2',
         'none': '.1.3.6.1.6.3.10.1.2.1'
     }
     return OIDs[type]
 
 @register_filter('nft_action')
 def nft_action(vyos_action):
     if vyos_action == 'accept':
         return 'return'
     return vyos_action
 
 @register_filter('nft_rule')
 def nft_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name='ip'):
     from vyos.firewall import parse_rule
     return parse_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name)
 
 @register_filter('nft_default_rule')
 def nft_default_rule(fw_conf, fw_name, ipv6=False):
     output = ['counter']
     default_action = fw_conf['default_action']
 
     if 'enable_default_log' in fw_conf:
         action_suffix = default_action[:1].upper()
         output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"')
 
     #output.append(nft_action(default_action))
     output.append(f'{default_action}')
     if 'default_jump_target' in fw_conf:
         target = fw_conf['default_jump_target']
         def_suffix = '6' if ipv6 else ''
         output.append(f'NAME{def_suffix}_{target}')
 
     output.append(f'comment "{fw_name} default-action {default_action}"')
     return " ".join(output)
 
 @register_filter('nft_state_policy')
 def nft_state_policy(conf, state):
     out = [f'ct state {state}']
 
     if 'log' in conf and 'enable' in conf['log']:
         log_state = state[:3].upper()
         log_action = (conf['action'] if 'action' in conf else 'accept')[:1].upper()
         out.append(f'log prefix "[STATE-POLICY-{log_state}-{log_action}]"')
 
         if 'log_level' in conf:
             log_level = conf['log_level']
             out.append(f'level {log_level}')
 
     out.append('counter')
 
     if 'action' in conf:
         out.append(conf['action'])
 
     return " ".join(out)
 
 @register_filter('nft_intra_zone_action')
 def nft_intra_zone_action(zone_conf, ipv6=False):
     if 'intra_zone_filtering' in zone_conf:
         intra_zone = zone_conf['intra_zone_filtering']
         fw_name = 'ipv6_name' if ipv6 else 'name'
         name_prefix = 'NAME6_' if ipv6 else 'NAME_'
 
         if 'action' in intra_zone:
             if intra_zone['action'] == 'accept':
                 return 'return'
             return intra_zone['action']
         elif dict_search_args(intra_zone, 'firewall', fw_name):
             name = dict_search_args(intra_zone, 'firewall', fw_name)
             return f'jump {name_prefix}{name}'
     return 'return'
 
 @register_filter('nft_nested_group')
 def nft_nested_group(out_list, includes, groups, key):
     if not vyos_defined(out_list):
         out_list = []
 
     def add_includes(name):
         if key in groups[name]:
             for item in groups[name][key]:
                 if item in out_list:
                     continue
                 out_list.append(item)
 
         if 'include' in groups[name]:
             for name_inc in groups[name]['include']:
                 add_includes(name_inc)
 
     for name in includes:
         add_includes(name)
     return out_list
 
 @register_filter('nat_rule')
 def nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
     from vyos.nat import parse_nat_rule
     return parse_nat_rule(rule_conf, rule_id, nat_type, ipv6)
 
 @register_filter('nat_static_rule')
 def nat_static_rule(rule_conf, rule_id, nat_type):
     from vyos.nat import parse_nat_static_rule
     return parse_nat_static_rule(rule_conf, rule_id, nat_type)
 
 @register_filter('conntrack_ignore_rule')
 def conntrack_ignore_rule(rule_conf, rule_id, ipv6=False):
     ip_prefix = 'ip6' if ipv6 else 'ip'
     def_suffix = '6' if ipv6 else ''
     output = []
 
     if 'inbound_interface' in rule_conf:
         ifname = rule_conf['inbound_interface']
         if ifname != 'any':
             output.append(f'iifname {ifname}')
 
     if 'protocol' in rule_conf:
         proto = rule_conf['protocol']
         output.append(f'meta l4proto {proto}')
 
+    tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+    if tcp_flags:
+        from vyos.firewall import parse_tcp_flags
+        output.append(parse_tcp_flags(tcp_flags))
+
     for side in ['source', 'destination']:
         if side in rule_conf:
             side_conf = rule_conf[side]
             prefix = side[0]
 
             if 'address' in side_conf:
                 address = side_conf['address']
                 operator = ''
                 if address[0] == '!':
                     operator = '!='
                     address = address[1:]
                 output.append(f'{ip_prefix} {prefix}addr {operator} {address}')
 
             if 'port' in side_conf:
                 port = side_conf['port']
                 operator = ''
                 if port[0] == '!':
                     operator = '!='
                     port = port[1:]
                 output.append(f'th {prefix}port {operator} {port}')
 
             if 'group' in side_conf:
                 group = side_conf['group']
 
                 if 'address_group' in group:
                     group_name = group['address_group']
                     operator = ''
                     if group_name[0] == '!':
                         operator = '!='
                         group_name = group_name[1:]
                     output.append(f'{ip_prefix} {prefix}addr {operator} @A{def_suffix}_{group_name}')
                 # Generate firewall group domain-group
                 elif 'domain_group' in group:
                     group_name = group['domain_group']
                     operator = ''
                     if group_name[0] == '!':
                         operator = '!='
                         group_name = group_name[1:]
                     output.append(f'{ip_prefix} {prefix}addr {operator} @D_{group_name}')
                 elif 'network_group' in group:
                     group_name = group['network_group']
                     operator = ''
                     if group_name[0] == '!':
                         operator = '!='
                         group_name = group_name[1:]
                     output.append(f'{ip_prefix} {prefix}addr {operator} @N{def_suffix}_{group_name}')
                 if 'port_group' in group:
                     group_name = group['port_group']
 
                     if proto == 'tcp_udp':
                         proto = 'th'
 
                     operator = ''
                     if group_name[0] == '!':
                         operator = '!='
                         group_name = group_name[1:]
 
                     output.append(f'{proto} {prefix}port {operator} @P_{group_name}')
 
     output.append('counter notrack')
     output.append(f'comment "ignore-{rule_id}"')
 
     return " ".join(output)
 
 @register_filter('range_to_regex')
 def range_to_regex(num_range):
     """Convert range of numbers or list of ranges
        to regex
 
        % range_to_regex('11-12')
        '(1[1-2])'
        % range_to_regex(['11-12', '14-15'])
        '(1[1-2]|1[4-5])'
     """
     from vyos.range_regex import range_to_regex
     if isinstance(num_range, list):
         data = []
         for entry in num_range:
             if '-' not in entry:
                 data.append(entry)
             else:
                 data.append(range_to_regex(entry))
         return f'({"|".join(data)})'
 
     if '-' not in num_range:
         return num_range
 
     regex = range_to_regex(num_range)
     return f'({regex})'
 
 @register_test('vyos_defined')
 def vyos_defined(value, test_value=None, var_type=None):
     """
     Jinja2 plugin to test if a variable is defined and not none - vyos_defined
     will test value if defined and is not none and return true or false.
 
     If test_value is supplied, the value must also pass == test_value to return true.
     If var_type is supplied, the value must also be of the specified class/type
 
     Examples:
     1. Test if var is defined and not none:
     {% if foo is vyos_defined %}
     ...
     {% endif %}
 
     2. Test if variable is defined, not none and has value "something"
     {% if bar is vyos_defined("something") %}
     ...
     {% endif %}
 
     Parameters
     ----------
     value : any
         Value to test from ansible
     test_value : any, optional
         Value to test in addition of defined and not none, by default None
     var_type : ['float', 'int', 'str', 'list', 'dict', 'tuple', 'bool'], optional
         Type or Class to test for
 
     Returns
     -------
     boolean
         True if variable matches criteria, False in other cases.
 
     Implementation inspired and re-used from https://github.com/aristanetworks/ansible-avd/
     """
 
     from jinja2 import Undefined
 
     if isinstance(value, Undefined) or value is None:
         # Invalid value - return false
         return False
     elif test_value is not None and value != test_value:
         # Valid value but not matching the optional argument
         return False
     elif str(var_type).lower() in ['float', 'int', 'str', 'list', 'dict', 'tuple', 'bool'] and str(var_type).lower() != type(value).__name__:
         # Invalid class - return false
         return False
     else:
         # Valid value and is matching optional argument if provided - return true
         return True
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 391ef03ff..75d6e9bb1 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -1,619 +1,619 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2022 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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 unittest
 
 from glob import glob
 from time import sleep
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 
 from vyos.configsession import ConfigSessionError
 from vyos.utils.process import cmd
 from vyos.utils.process import run
 
 sysfs_config = {
     'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'default': '0', 'test_value': 'disable'},
     'broadcast_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts', 'default': '1', 'test_value': 'enable'},
     'ip_src_route': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_source_route', 'default': '0', 'test_value': 'enable'},
     'ipv6_receive_redirects': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'},
     'ipv6_src_route': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_source_route', 'default': '-1', 'test_value': 'enable'},
     'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians', 'default': '1', 'test_value': 'disable'},
     'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects', 'default': '0', 'test_value': 'enable'},
     'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects', 'default': '1', 'test_value': 'disable'},
     'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies', 'default': '1', 'test_value': 'disable'},
     'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337', 'default': '0', 'test_value': 'enable'}
 }
 
 class TestFirewall(VyOSUnitTestSHIM.TestCase):
     @classmethod
     def setUpClass(cls):
         super(TestFirewall, cls).setUpClass()
 
         # ensure we can also run this test on a live system - so lets clean
         # out the current configuration :)
         cls.cli_delete(cls, ['firewall'])
 
     @classmethod
     def tearDownClass(cls):
         super(TestFirewall, cls).tearDownClass()
 
     def tearDown(self):
         self.cli_delete(['firewall'])
         self.cli_commit()
 
         # Verify chains/sets are cleaned up from nftables
         nftables_search = [
             ['set M_smoketest_mac'],
             ['set N_smoketest_network'],
             ['set P_smoketest_port'],
             ['set D_smoketest_domain'],
             ['set RECENT_smoketest_4'],
             ['chain NAME_smoketest']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter', inverse=True)
 
     def verify_nftables(self, nftables_search, table, inverse=False, args=''):
         nftables_output = cmd(f'sudo nft {args} list table {table}')
 
         for search in nftables_search:
             matched = False
             for line in nftables_output.split("\n"):
                 if all(item in line for item in search):
                     matched = True
                     break
             self.assertTrue(not matched if inverse else matched, msg=search)
 
     def verify_nftables_chain(self, nftables_search, table, chain, inverse=False, args=''):
         nftables_output = cmd(f'sudo nft {args} list chain {table} {chain}')
 
         for search in nftables_search:
             matched = False
             for line in nftables_output.split("\n"):
                 if all(item in line for item in search):
                     matched = True
                     break
             self.assertTrue(not matched if inverse else matched, msg=search)
 
     def wait_for_domain_resolver(self, table, set_name, element, max_wait=10):
         # Resolver no longer blocks commit, need to wait for daemon to populate set
         count = 0
         while count < max_wait:
             code = run(f'sudo nft get element {table} {set_name} {{ {element} }}')
             if code == 0:
                 return True
             count += 1
             sleep(1)
         return False
 
     def test_geoip(self):
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match'])
 
         self.cli_commit()
 
         nftables_search = [
             ['ip saddr @GEOIP_CC_name_smoketest_1', 'drop'],
             ['ip saddr != @GEOIP_CC_name_smoketest_2', 'accept']
         ]
 
         # -t prevents 1000+ GeoIP elements being returned
         self.verify_nftables(nftables_search, 'ip vyos_filter', args='-t')
 
     def test_groups(self):
         hostmap_path = ['system', 'static-host-mapping', 'host-name']
         example_org = ['192.0.2.8', '192.0.2.10', '192.0.2.11']
 
         self.cli_set(hostmap_path + ['example.com', 'inet', '192.0.2.5'])
         for ips in example_org:
             self.cli_set(hostmap_path + ['example.org', 'inet', ips])
 
         self.cli_commit()
 
         self.cli_set(['firewall', 'group', 'mac-group', 'smoketest_mac', 'mac-address', '00:01:02:03:04:05'])
         self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24'])
         self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53'])
         self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123'])
         self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.com'])
         self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.org'])
         self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'eth0'])
         self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'vtun0'])
 
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'address', '172.16.10.10'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'outbound-interface', 'interface-group', '!smoketest_interface'])
 
         self.cli_commit()
 
         self.wait_for_domain_resolver('ip vyos_filter', 'D_smoketest_domain', '192.0.2.5')
 
         nftables_search = [
             ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'accept'],
             ['elements = { 172.16.99.0/24 }'],
             ['elements = { 53, 123 }'],
             ['ether saddr @M_smoketest_mac', 'accept'],
             ['elements = { 00:01:02:03:04:05 }'],
             ['set D_smoketest_domain'],
             ['elements = { 192.0.2.5, 192.0.2.8,'],
             ['192.0.2.10, 192.0.2.11 }'],
             ['ip saddr @D_smoketest_domain', 'accept'],
             ['oifname != @I_smoketest_interface', 'accept']
         ]
         self.verify_nftables(nftables_search, 'ip vyos_filter')
 
         self.cli_delete(['system', 'static-host-mapping'])
         self.cli_commit()
 
     def test_nested_groups(self):
         self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24'])
         self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24'])
         self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'include', 'smoketest_network'])
         self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53'])
         self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'port', '123'])
         self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'include', 'smoketest_port'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network1'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp'])
 
         self.cli_commit()
 
         # Test circular includes
         self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1'])
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_delete(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1'])
 
         nftables_search = [
             ['ip saddr @N_smoketest_network1', 'th dport @P_smoketest_port1', 'accept'],
             ['elements = { 172.16.99.0/24, 172.16.101.0/24 }'],
             ['elements = { 53, 123 }']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
 
     def test_ipv4_basic_rules(self):
         name = 'smoketest'
         interface = 'eth0'
         interface_inv = '!eth0'
         interface_wc = 'l2tp*'
         mss_range = '501-1460'
         conn_mark = '555'
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'enable-default-log'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log', 'enable'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log-options', 'level', 'debug'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'ttl', 'eq', '15'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'destination', 'port', '8888'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log', 'enable'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log-options', 'level', 'err'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'ttl', 'gt', '102'])
 
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'destination', 'port', '22'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'limit', 'rate', '5/minute'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'log', 'disable'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'destination', 'port', '22'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'count', '10'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'time', 'minute'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'packet-type', 'host'])
 
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'flags', 'syn'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'mss', mss_range])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'packet-type', 'broadcast'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'inbound-interface', 'interface-name', interface_wc])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'action', 'return'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'protocol', 'gre'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'connection-mark', conn_mark])
 
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'protocol', 'gre'])
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'outbound-interface', 'interface-name', interface_inv])
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'action', 'return'])
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'protocol', 'icmp'])
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'connection-mark', conn_mark])
 
         self.cli_commit()
 
         mark_hex = "{0:#010x}".format(int(conn_mark))
 
         nftables_search = [
             ['chain VYOS_FORWARD_filter'],
             ['type filter hook forward priority filter; policy drop;'],
             ['tcp dport 22', 'limit rate 5/minute', 'accept'],
             ['tcp dport 22', 'add @RECENT_FWD_filter_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'],
             ['chain VYOS_INPUT_filter'],
             ['type filter hook input priority filter; policy accept;'],
             ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface_wc}"', 'meta pkttype broadcast', 'accept'],
             ['meta l4proto gre', f'ct mark {mark_hex}', 'return'],
             ['chain VYOS_OUTPUT_filter'],
             ['type filter hook output priority filter; policy accept;'],
             ['meta l4proto gre', f'oifname != "{interface}"', 'drop'],
             ['meta l4proto icmp', f'ct mark {mark_hex}', 'return'],
             ['chain NAME_smoketest'],
             ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[ipv4-NAM-smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'],
             ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[ipv4-NAM-smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'],
             ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
 
     def test_ipv4_advanced(self):
         name = 'smoketest-adv'
         name2 = 'smoketest-adv2'
         interface = 'eth0'
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'enable-default-log'])
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '64'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '512'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '1024'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '17'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '52'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log', 'enable'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'group', '66'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'snapshot-length', '6666'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'queue-threshold','32000'])
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length', '1-30000'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp', '3-11'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp-exclude', '21-25'])
 
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'address', '198.51.100.1'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', name])
 
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'action', 'queue'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'queue', '3'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'protocol', 'udp'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'action', 'queue'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'fanout'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'bypass'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue', '0-15'])
 
         self.cli_commit()
 
         nftables_search = [
             ['chain VYOS_FORWARD_filter'],
             ['type filter hook forward priority filter; policy drop;'],
             ['ip saddr 198.51.100.1', f'jump NAME_{name}'],
             ['chain VYOS_INPUT_filter'],
             ['type filter hook input priority filter; policy accept;'],
             [f'meta l4proto tcp','queue to 3'],
             [f'meta l4proto udp','queue flags bypass,fanout to 0-15'],
             [f'chain NAME_{name}'],
             ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[ipv4-NAM-{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'],
             ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'],
             [f'log prefix "[{name}-default-D]"', 'drop']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
 
     def test_ipv4_mask(self):
         name = 'smoketest-mask'
         interface = 'eth0'
 
         self.cli_set(['firewall', 'group', 'address-group', 'mask_group', 'address', '1.1.1.1'])
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'enable-default-log'])
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '0.0.1.2'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address-mask', '0.0.255.255'])
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address', '!0.0.3.4'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address-mask', '0.0.255.255'])
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'address-mask', '0.0.255.255'])
 
         self.cli_commit()
 
         nftables_search = [
             [f'daddr & 0.0.255.255 == 0.0.1.2'],
             [f'saddr & 0.0.255.255 != 0.0.3.4'],
             [f'saddr & 0.0.255.255 == @A_mask_group']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
 
 
     def test_ipv6_basic_rules(self):
         name = 'v6-smoketest'
         interface = 'eth0'
 
         self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'enable-default-log'])
 
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'source', 'address', '2002::1'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '2002::1:1'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log', 'enable'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log-options', 'level', 'crit'])
 
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-action', 'accept'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'action', 'reject'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'protocol', 'tcp_udp'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'destination', 'port', '8888'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'inbound-interface', 'interface-name', interface])
 
         self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'action', 'return'])
         self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'protocol', 'gre'])
         self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'outbound-interface', 'interface-name', interface])
 
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'protocol', 'udp'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'source', 'address', '2002::1:2'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'inbound-interface', 'interface-name', interface])
 
         self.cli_commit()
 
         nftables_search = [
             ['chain VYOS_IPV6_FORWARD_filter'],
             ['type filter hook forward priority filter; policy accept;'],
             ['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'],
             ['chain VYOS_IPV6_INPUT_filter'],
             ['type filter hook input priority filter; policy accept;'],
             ['meta l4proto udp', 'ip6 saddr 2002::1:2', f'iifname "{interface}"', 'accept'],
             ['chain VYOS_IPV6_OUTPUT_filter'],
             ['type filter hook output priority filter; policy drop;'],
             ['meta l4proto gre', f'oifname "{interface}"', 'return'],
             [f'chain NAME6_{name}'],
             ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'],
             [f'"{name} default-action drop"', f'log prefix "[{name}-default-D]"', 'drop']
         ]
 
         self.verify_nftables(nftables_search, 'ip6 vyos_filter')
 
     def test_ipv6_advanced(self):
         name = 'v6-smoketest-adv'
         name2 = 'v6-smoketest-adv2'
         interface = 'eth0'
 
         self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'enable-default-log'])
 
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '65'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '513'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '1025'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '18'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '53'])
 
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length', '1-1999'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length-exclude', '60000-65535'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp', '4-14'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp-exclude', '31-35'])
 
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'default-action', 'accept'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'source', 'address', '2001:db8::/64'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'jump-target', name])
 
         self.cli_commit()
 
         nftables_search = [
             ['chain VYOS_IPV6_FORWARD_filter'],
             ['type filter hook forward priority filter; policy accept;'],
             ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'accept'],
             ['chain VYOS_IPV6_INPUT_filter'],
             ['type filter hook input priority filter; policy accept;'],
             ['ip6 saddr 2001:db8::/64', f'jump NAME6_{name}'],
             [f'chain NAME6_{name}'],
             ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'accept'],
             [f'log prefix "[{name}-default-D]"', 'drop']
         ]
 
         self.verify_nftables(nftables_search, 'ip6 vyos_filter')
 
     def test_ipv6_mask(self):
         name = 'v6-smoketest-mask'
         interface = 'eth0'
 
         self.cli_set(['firewall', 'group', 'ipv6-address-group', 'mask_group', 'address', '::beef'])
 
         self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'enable-default-log'])
 
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '::1111:2222:3333:4444'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address-mask', '::ffff:ffff:ffff:ffff'])
 
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address', '!::aaaa:bbbb:cccc:dddd'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address-mask', '::ffff:ffff:ffff:ffff'])
 
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group'])
         self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'address-mask', '::ffff:ffff:ffff:ffff'])
 
         self.cli_commit()
 
         nftables_search = [
             ['daddr & ::ffff:ffff:ffff:ffff == ::1111:2222:3333:4444'],
             ['saddr & ::ffff:ffff:ffff:ffff != ::aaaa:bbbb:cccc:dddd'],
             ['saddr & ::ffff:ffff:ffff:ffff == @A6_mask_group']
         ]
 
         self.verify_nftables(nftables_search, 'ip6 vyos_filter')
 
     def test_ipv4_state_and_status_rules(self):
         name = 'smoketest-state'
         interface = 'eth0'
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'established', 'enable'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'related', 'enable'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'state', 'invalid', 'enable'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'state', 'new', 'enable'])
 
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'connection-status', 'nat', 'destination'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'new', 'enable'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'established', 'enable'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source'])
 
         self.cli_commit()
 
         nftables_search = [
             ['ct state { established, related }', 'accept'],
             ['ct state invalid', 'reject'],
             ['ct state new', 'ct status dnat', 'accept'],
             ['ct state { established, new }', 'ct status snat', 'accept'],
             ['drop', f'comment "{name} default-action drop"']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
 
         # Check conntrack
-        self.verify_nftables_chain([['accept']], 'raw', 'FW_CONNTRACK')
-        self.verify_nftables_chain([['return']], 'ip6 raw', 'FW_CONNTRACK')
+        self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
+        self.verify_nftables_chain([['return']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
 
     def test_bridge_basic_rules(self):
         name = 'smoketest'
         interface_in = 'eth0'
         mac_address = '00:53:00:00:00:01'
         vlan_id = '12'
         vlan_prior = '3'
 
         self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept'])
         self.cli_set(['firewall', 'bridge', 'name', name, 'enable-default-log'])
         self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'source', 'mac-address', mac_address])
         self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'inbound-interface', 'interface-name', interface_in])
         self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log', 'enable'])
         self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log-options', 'level', 'crit'])
 
         self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-action', 'drop'])
         self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'id', vlan_id])
         self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'action', 'jump'])
         self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name])
         self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior])
 
         self.cli_commit()
 
         nftables_search = [
             ['chain VYOS_FORWARD_filter'],
             ['type filter hook forward priority filter; policy drop;'],
             [f'vlan id {vlan_id}', 'accept'],
             [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'],
             [f'chain NAME_{name}'],
             [f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept']
         ]
 
         self.verify_nftables(nftables_search, 'bridge vyos_filter')
 
     def test_source_validation(self):
         # Strict
         self.cli_set(['firewall', 'global-options', 'source-validation', 'strict'])
         self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'strict'])
         self.cli_commit()
 
         nftables_strict_search = [
             ['fib saddr . iif oif 0', 'drop']
         ]
 
         self.verify_nftables_chain(nftables_strict_search, 'ip raw', 'vyos_global_rpfilter')
         self.verify_nftables_chain(nftables_strict_search, 'ip6 raw', 'vyos_global_rpfilter')
 
         # Loose
         self.cli_set(['firewall', 'global-options', 'source-validation', 'loose'])
         self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'loose'])
         self.cli_commit()
 
         nftables_loose_search = [
             ['fib saddr oif 0', 'drop']
         ]
 
         self.verify_nftables_chain(nftables_loose_search, 'ip raw', 'vyos_global_rpfilter')
         self.verify_nftables_chain(nftables_loose_search, 'ip6 raw', 'vyos_global_rpfilter')
 
     def test_sysfs(self):
         for name, conf in sysfs_config.items():
             paths = glob(conf['sysfs'])
             for path in paths:
                 with open(path, 'r') as f:
                     self.assertEqual(f.read().strip(), conf['default'], msg=path)
 
             self.cli_set(['firewall', 'global-options', name.replace("_", "-"), conf['test_value']])
 
         self.cli_commit()
 
         for name, conf in sysfs_config.items():
             paths = glob(conf['sysfs'])
             for path in paths:
                 with open(path, 'r') as f:
                     self.assertNotEqual(f.read().strip(), conf['default'], msg=path)
 
     def test_flow_offload_software(self):
         self.cli_set(['firewall', 'global-options', 'flow-offload', 'software', 'interface', 'eth0'])
         self.cli_commit()
         nftables_search = [
             ['flowtable VYOS_FLOWTABLE_software'],
             ['hook ingress priority filter - 1'],
             ['devices = { eth0 }'],
             ['flow add @VYOS_FLOWTABLE_software'],
         ]
         self.verify_nftables(nftables_search, 'inet vyos_offload')
 
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index ea304783d..c9f184558 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -1,293 +1,294 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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
 import re
 import unittest
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 
 from vyos.firewall import find_nftables_rule
 from vyos.utils.process import cmd
 from vyos.utils.file import read_file
 
 base_path = ['system', 'conntrack']
 
 def get_sysctl(parameter):
     tmp = parameter.replace(r'.', r'/')
     return read_file(f'/proc/sys/{tmp}')
 
 class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
     def tearDown(self):
         self.cli_delete(base_path)
         self.cli_commit()
 
     def verify_nftables(self, nftables_search, table, inverse=False, args=''):
         nftables_output = cmd(f'sudo nft {args} list table {table}')
 
         for search in nftables_search:
             matched = False
             for line in nftables_output.split("\n"):
                 if all(item in line for item in search):
                     matched = True
                     break
             self.assertTrue(not matched if inverse else matched, msg=search)
 
     def test_conntrack_options(self):
         conntrack_config = {
             'net.netfilter.nf_conntrack_expect_max' : {
                 'cli'           : ['expect-table-size'],
                 'test_value'    : '8192',
                 'default_value' : '2048',
             },
             'net.nf_conntrack_max' :{
                 'cli'           : ['table-size'],
                 'test_value'    : '500000',
                 'default_value' : '262144',
             },
             'net.ipv4.tcp_max_syn_backlog' :{
                 'cli'           : ['tcp', 'half-open-connections'],
                 'test_value'    : '2048',
                 'default_value' : '512',
             },
             'net.netfilter.nf_conntrack_tcp_loose' :{
                 'cli'           : ['tcp', 'loose'],
                 'test_value'    : 'disable',
                 'default_value' : '1',
             },
             'net.netfilter.nf_conntrack_tcp_max_retrans' :{
                 'cli'           : ['tcp', 'max-retrans'],
                 'test_value'    : '128',
                 'default_value' : '3',
             },
             'net.netfilter.nf_conntrack_icmp_timeout' :{
                 'cli'           : ['timeout', 'icmp'],
                 'test_value'    : '180',
                 'default_value' : '30',
             },
             'net.netfilter.nf_conntrack_generic_timeout' :{
                 'cli'           : ['timeout', 'other'],
                 'test_value'    : '1200',
                 'default_value' : '600',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_close_wait' :{
                 'cli'           : ['timeout', 'tcp', 'close-wait'],
                 'test_value'    : '30',
                 'default_value' : '60',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_close' :{
                 'cli'           : ['timeout', 'tcp', 'close'],
                 'test_value'    : '20',
                 'default_value' : '10',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_established' :{
                 'cli'           : ['timeout', 'tcp', 'established'],
                 'test_value'    : '1000',
                 'default_value' : '432000',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_fin_wait' :{
                 'cli'           : ['timeout', 'tcp', 'fin-wait'],
                 'test_value'    : '240',
                 'default_value' : '120',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_last_ack' :{
                 'cli'           : ['timeout', 'tcp', 'last-ack'],
                 'test_value'    : '300',
                 'default_value' : '30',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_syn_recv' :{
                 'cli'           : ['timeout', 'tcp', 'syn-recv'],
                 'test_value'    : '100',
                 'default_value' : '60',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_syn_sent' :{
                 'cli'           : ['timeout', 'tcp', 'syn-sent'],
                 'test_value'    : '300',
                 'default_value' : '120',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_time_wait' :{
                 'cli'           : ['timeout', 'tcp', 'time-wait'],
                 'test_value'    : '303',
                 'default_value' : '120',
             },
             'net.netfilter.nf_conntrack_udp_timeout' :{
                 'cli'           : ['timeout', 'udp', 'other'],
                 'test_value'    : '90',
                 'default_value' : '30',
             },
             'net.netfilter.nf_conntrack_udp_timeout_stream' :{
                 'cli'           : ['timeout', 'udp', 'stream'],
                 'test_value'    : '200',
                 'default_value' : '180',
             },
         }
 
         for parameter, parameter_config in conntrack_config.items():
             self.cli_set(base_path + parameter_config['cli'] + [parameter_config['test_value']])
 
         # commit changes
         self.cli_commit()
 
         # validate configuration
         for parameter, parameter_config in conntrack_config.items():
             tmp = parameter_config['test_value']
             # net.netfilter.nf_conntrack_tcp_loose has a fancy "disable" value,
             # make this work
             if tmp == 'disable':
                 tmp = '0'
             self.assertEqual(get_sysctl(f'{parameter}'), tmp)
 
         # delete all configuration options and revert back to defaults
         self.cli_delete(base_path)
         self.cli_commit()
 
         # validate configuration
         for parameter, parameter_config in conntrack_config.items():
             self.assertEqual(get_sysctl(f'{parameter}'), parameter_config['default_value'])
 
 
     def test_conntrack_module_enable(self):
         # conntrack helper modules are disabled by default
         modules = {
             'ftp' : {
                 'driver' : ['nf_nat_ftp', 'nf_conntrack_ftp'],
             },
             'h323' : {
                 'driver' : ['nf_nat_h323', 'nf_conntrack_h323'],
             },
             'nfs' : {
                 'nftables' : ['ct helper set "rpc_tcp"',
                               'ct helper set "rpc_udp"']
             },
             'pptp' : {
                 'driver' : ['nf_nat_pptp', 'nf_conntrack_pptp'],
              },
             'sip' : {
                 'driver' : ['nf_nat_sip', 'nf_conntrack_sip'],
              },
             'sqlnet' : {
                 'nftables' : ['ct helper set "tns_tcp"']
             },
             'tftp' : {
                 'driver' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
              },
         }
 
         # load modules
         for module in modules:
             self.cli_set(base_path + ['modules', module])
 
         # commit changes
         self.cli_commit()
 
         # verify modules are loaded on the system
         for module, module_options in modules.items():
             if 'driver' in module_options:
                 for driver in module_options['driver']:
                     self.assertTrue(os.path.isdir(f'/sys/module/{driver}'))
             if 'nftables' in module_options:
                 for rule in module_options['nftables']:
-                    self.assertTrue(find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) != None)
+                    self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) != None)
 
         # unload modules
         for module in modules:
             self.cli_delete(base_path + ['modules', module])
 
         # commit changes
         self.cli_commit()
 
         # verify modules are not loaded on the system
         for module, module_options in modules.items():
             if 'driver' in module_options:
                 for driver in module_options['driver']:
                     self.assertFalse(os.path.isdir(f'/sys/module/{driver}'))
             if 'nftables' in module_options:
                 for rule in module_options['nftables']:
-                    self.assertTrue(find_nftables_rule('raw', 'VYOS_CT_HELPER', [rule]) == None)
+                    self.assertTrue(find_nftables_rule('ip vyos_conntrack', 'VYOS_CT_HELPER', [rule]) == None)
 
     def test_conntrack_hash_size(self):
         hash_size = '65536'
         hash_size_default = '32768'
 
         self.cli_set(base_path + ['hash-size', hash_size])
 
         # commit changes
         self.cli_commit()
 
         # verify new configuration - only effective after reboot, but
         # a valid config file is sufficient
         tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf')
         self.assertIn(hash_size, tmp)
 
         # Test default value by deleting the configuration
         self.cli_delete(base_path + ['hash-size'])
 
         # commit changes
         self.cli_commit()
 
         # verify new configuration - only effective after reboot, but
         # a valid config file is sufficient
         tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf')
         self.assertIn(hash_size_default, tmp)
 
     def test_conntrack_ignore(self):
         address_group = 'conntracktest'
         address_group_member = '192.168.0.1'
         ipv6_address_group = 'conntracktest6'
         ipv6_address_group_member = 'dead:beef::1'
 
         self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member])
         self.cli_set(['firewall', 'group', 'ipv6-address-group', ipv6_address_group, 'address', ipv6_address_group_member])
 
         self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1'])
         self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2'])
         self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'port', '22'])
         self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'protocol', 'tcp'])
+        self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'tcp', 'flags', 'syn'])
 
         self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1'])
         self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group])
 
         self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1'])
         self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2'])
         self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'port', '22'])
         self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'protocol', 'tcp'])
 
         self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'source', 'address', 'fe80::1'])
         self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'destination', 'group', 'address-group', ipv6_address_group])
 
         self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'source', 'address', 'fe80::1'])
         self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'destination', 'address', '!fe80::3'])
 
         self.cli_commit()
 
         nftables_search = [
-            ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'notrack'],
+            ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'tcp flags & syn == syn', 'notrack'],
             ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack']
         ]
 
         nftables6_search = [
             ['ip6 saddr fe80::1', 'ip6 daddr fe80::2', 'tcp dport 22', 'notrack'],
             ['ip6 saddr fe80::1', 'ip6 daddr @A6_conntracktest6', 'notrack'],
             ['ip6 saddr fe80::1', 'ip6 daddr != fe80::3', 'notrack']
         ]
 
-        self.verify_nftables(nftables_search, 'raw')
-        self.verify_nftables(nftables6_search, 'ip6 raw')
+        self.verify_nftables(nftables_search, 'ip vyos_conntrack')
+        self.verify_nftables(nftables6_search, 'ip6 vyos_conntrack')
 
         self.cli_delete(['firewall'])
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py
index d55ea616e..6c761579b 100755
--- a/smoketest/scripts/cli/test_system_flow-accounting.py
+++ b/smoketest/scripts/cli/test_system_flow-accounting.py
@@ -1,296 +1,296 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2022 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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 unittest
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 
 from vyos.configsession import ConfigSessionError
 from vyos.ifconfig import Section
 from vyos.template import bracketize_ipv6
 from vyos.template import is_ipv6
 from vyos.utils.process import cmd
 from vyos.utils.process import process_named_running
 from vyos.utils.file import read_file
 
 PROCESS_NAME = 'uacctd'
 base_path = ['system', 'flow-accounting']
 
 uacctd_conf = '/run/pmacct/uacctd.conf'
 
 class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):
     @classmethod
     def setUpClass(cls):
         super(TestSystemFlowAccounting, cls).setUpClass()
 
         # ensure we can also run this test on a live system - so lets clean
         # out the current configuration :)
         cls.cli_delete(cls, base_path)
 
     def tearDown(self):
         # after service removal process must no longer run
         self.assertTrue(process_named_running(PROCESS_NAME))
 
         self.cli_delete(base_path)
         self.cli_commit()
 
         # after service removal process must no longer run
         self.assertFalse(process_named_running(PROCESS_NAME))
 
     def test_basic(self):
         buffer_size = '5' # MiB
         syslog = 'all'
 
         self.cli_set(base_path + ['buffer-size', buffer_size])
         self.cli_set(base_path + ['syslog-facility', syslog])
 
         # You need to configure at least one interface for flow-accounting
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
         for interface in Section.interfaces('ethernet'):
             self.cli_set(base_path + ['interface', interface])
 
         # commit changes
         self.cli_commit()
 
         # verify configuration
-        nftables_output = cmd('sudo nft list chain raw VYOS_CT_PREROUTING_HOOK').splitlines()
+        nftables_output = cmd('sudo nft list chain raw VYOS_PREROUTING_HOOK').splitlines()
         for interface in Section.interfaces('ethernet'):
             rule_found = False
             ifname_search = f'iifname "{interface}"'
 
             for nftables_line in nftables_output:
                 if 'FLOW_ACCOUNTING_RULE' in nftables_line and ifname_search in nftables_line:
                     self.assertIn('group 2', nftables_line)
                     self.assertIn('snaplen 128', nftables_line)
                     self.assertIn('queue-threshold 100', nftables_line)
                     rule_found = True
                     break
 
             self.assertTrue(rule_found)
 
         uacctd = read_file(uacctd_conf)
         # circular queue size - buffer_size
         tmp = int(buffer_size) *1024 *1024
         self.assertIn(f'plugin_pipe_size: {tmp}', uacctd)
         # transfer buffer size - recommended value from pmacct developers 1/1000 of pipe size
         tmp = int(buffer_size) *1024 *1024
         # do an integer division
         tmp //= 1000
         self.assertIn(f'plugin_buffer_size: {tmp}', uacctd)
 
         # when 'disable-imt' is not configured on the CLI it must be present
         self.assertIn(f'imt_path: /tmp/uacctd.pipe', uacctd)
         self.assertIn(f'imt_mem_pools_number: 169', uacctd)
         self.assertIn(f'syslog: {syslog}', uacctd)
         self.assertIn(f'plugins: memory', uacctd)
 
     def test_sflow(self):
         sampling_rate = '4000'
         source_address = '192.0.2.1'
         dummy_if = 'dum3841'
         agent_address = '192.0.2.2'
 
         sflow_server = {
             '1.2.3.4' : { },
             '5.6.7.8' : { 'port' : '6000' },
         }
 
         self.cli_set(['interfaces', 'dummy', dummy_if, 'address', agent_address + '/32'])
         self.cli_set(['interfaces', 'dummy', dummy_if, 'address', source_address + '/32'])
         self.cli_set(base_path + ['disable-imt'])
 
         # You need to configure at least one interface for flow-accounting
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
         for interface in Section.interfaces('ethernet'):
             self.cli_set(base_path + ['interface', interface])
 
 
         # You need to configure at least one sFlow or NetFlow protocol, or not
         # set "disable-imt" for flow-accounting
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_set(base_path + ['sflow', 'agent-address', agent_address])
         self.cli_set(base_path + ['sflow', 'sampling-rate', sampling_rate])
         self.cli_set(base_path + ['sflow', 'source-address', source_address])
         for server, server_config in sflow_server.items():
             self.cli_set(base_path + ['sflow', 'server', server])
             if 'port' in server_config:
                 self.cli_set(base_path + ['sflow', 'server', server, 'port', server_config['port']])
 
         # commit changes
         self.cli_commit()
 
         uacctd = read_file(uacctd_conf)
 
         # when 'disable-imt' is not configured on the CLI it must be present
         self.assertNotIn(f'imt_path: /tmp/uacctd.pipe', uacctd)
         self.assertNotIn(f'imt_mem_pools_number: 169', uacctd)
         self.assertNotIn(f'plugins: memory', uacctd)
 
         for server, server_config in sflow_server.items():
             plugin_name = server.replace('.', '-')
             if 'port' in server_config:
                 self.assertIn(f'sfprobe_receiver[sf_{plugin_name}]: {server}', uacctd)
             else:
                 self.assertIn(f'sfprobe_receiver[sf_{plugin_name}]: {server}:6343', uacctd)
 
             self.assertIn(f'sfprobe_agentip[sf_{plugin_name}]: {agent_address}', uacctd)
             self.assertIn(f'sampling_rate[sf_{plugin_name}]: {sampling_rate}', uacctd)
             self.assertIn(f'sfprobe_source_ip[sf_{plugin_name}]: {source_address}', uacctd)
 
         self.cli_delete(['interfaces', 'dummy', dummy_if])
 
     def test_sflow_ipv6(self):
         sampling_rate = '100'
         sflow_server = {
             '2001:db8::1' : { },
             '2001:db8::2' : { 'port' : '6000' },
         }
 
         self.cli_set(base_path + ['disable-imt'])
 
         # You need to configure at least one interface for flow-accounting
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
         for interface in Section.interfaces('ethernet'):
             self.cli_set(base_path + ['interface', interface])
 
 
         # You need to configure at least one sFlow or NetFlow protocol, or not
         # set "disable-imt" for flow-accounting
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_set(base_path + ['sflow', 'sampling-rate', sampling_rate])
         for server, server_config in sflow_server.items():
             self.cli_set(base_path + ['sflow', 'server', server])
             if 'port' in server_config:
                 self.cli_set(base_path + ['sflow', 'server', server, 'port', server_config['port']])
 
         # commit changes
         self.cli_commit()
 
         uacctd = read_file(uacctd_conf)
 
         # when 'disable-imt' is not configured on the CLI it must be present
         self.assertNotIn(f'imt_path: /tmp/uacctd.pipe', uacctd)
         self.assertNotIn(f'imt_mem_pools_number: 169', uacctd)
         self.assertNotIn(f'plugins: memory', uacctd)
 
         for server, server_config in sflow_server.items():
             tmp_srv = server
             tmp_srv = tmp_srv.replace(':', '-')
 
             if 'port' in server_config:
                 self.assertIn(f'sfprobe_receiver[sf_{tmp_srv}]: {bracketize_ipv6(server)}', uacctd)
             else:
                 self.assertIn(f'sfprobe_receiver[sf_{tmp_srv}]: {bracketize_ipv6(server)}:6343', uacctd)
             self.assertIn(f'sampling_rate[sf_{tmp_srv}]: {sampling_rate}', uacctd)
 
     def test_netflow(self):
         engine_id = '33'
         max_flows = '667'
         sampling_rate = '100'
         source_address = '192.0.2.1'
         dummy_if = 'dum3842'
         agent_address = '192.0.2.10'
         version = '10'
         tmo_expiry = '120'
         tmo_flow = '1200'
         tmo_icmp = '60'
         tmo_max = '50000'
         tmo_tcp_fin = '100'
         tmo_tcp_generic = '120'
         tmo_tcp_rst = '99'
         tmo_udp = '10'
 
         netflow_server = {
             '11.22.33.44' : { },
             '55.66.77.88' : { 'port' : '6000' },
             '2001:db8::1' : { },
         }
 
         self.cli_set(['interfaces', 'dummy', dummy_if, 'address', agent_address + '/32'])
         self.cli_set(['interfaces', 'dummy', dummy_if, 'address', source_address + '/32'])
 
         for interface in Section.interfaces('ethernet'):
             self.cli_set(base_path + ['interface', interface])
 
         self.cli_set(base_path + ['netflow', 'engine-id', engine_id])
         self.cli_set(base_path + ['netflow', 'max-flows', max_flows])
         self.cli_set(base_path + ['netflow', 'sampling-rate', sampling_rate])
         self.cli_set(base_path + ['netflow', 'source-address', source_address])
         self.cli_set(base_path + ['netflow', 'version', version])
 
         # timeouts
         self.cli_set(base_path + ['netflow', 'timeout', 'expiry-interval', tmo_expiry])
         self.cli_set(base_path + ['netflow', 'timeout', 'flow-generic', tmo_flow])
         self.cli_set(base_path + ['netflow', 'timeout', 'icmp', tmo_icmp])
         self.cli_set(base_path + ['netflow', 'timeout', 'max-active-life', tmo_max])
         self.cli_set(base_path + ['netflow', 'timeout', 'tcp-fin', tmo_tcp_fin])
         self.cli_set(base_path + ['netflow', 'timeout', 'tcp-generic', tmo_tcp_generic])
         self.cli_set(base_path + ['netflow', 'timeout', 'tcp-rst', tmo_tcp_rst])
         self.cli_set(base_path + ['netflow', 'timeout', 'udp', tmo_udp])
 
         # You need to configure at least one netflow server
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         for server, server_config in netflow_server.items():
             self.cli_set(base_path + ['netflow', 'server', server])
             if 'port' in server_config:
                 self.cli_set(base_path + ['netflow', 'server', server, 'port', server_config['port']])
 
         # commit changes
         self.cli_commit()
 
         uacctd = read_file(uacctd_conf)
 
         tmp = []
         for server, server_config in netflow_server.items():
             tmp_srv = server
             tmp_srv = tmp_srv.replace('.', '-')
             tmp_srv = tmp_srv.replace(':', '-')
             tmp.append(f'nfprobe[nf_{tmp_srv}]')
         tmp.append('memory')
         self.assertIn('plugins: ' + ','.join(tmp), uacctd)
 
         for server, server_config in netflow_server.items():
             tmp_srv = server
             tmp_srv = tmp_srv.replace('.', '-')
             tmp_srv = tmp_srv.replace(':', '-')
 
             self.assertIn(f'nfprobe_engine[nf_{tmp_srv}]: {engine_id}', uacctd)
             self.assertIn(f'nfprobe_maxflows[nf_{tmp_srv}]: {max_flows}', uacctd)
             self.assertIn(f'sampling_rate[nf_{tmp_srv}]: {sampling_rate}', uacctd)
             self.assertIn(f'nfprobe_source_ip[nf_{tmp_srv}]: {source_address}', uacctd)
             self.assertIn(f'nfprobe_version[nf_{tmp_srv}]: {version}', uacctd)
 
             if 'port' in server_config:
                 self.assertIn(f'nfprobe_receiver[nf_{tmp_srv}]: {bracketize_ipv6(server)}', uacctd)
             else:
                 self.assertIn(f'nfprobe_receiver[nf_{tmp_srv}]: {bracketize_ipv6(server)}:2055', uacctd)
 
             self.assertIn(f'nfprobe_timeouts[nf_{tmp_srv}]: expint={tmo_expiry}:general={tmo_flow}:icmp={tmo_icmp}:maxlife={tmo_max}:tcp.fin={tmo_tcp_fin}:tcp={tmo_tcp_generic}:tcp.rst={tmo_tcp_rst}:udp={tmo_udp}', uacctd)
 
 
         self.cli_delete(['interfaces', 'dummy', dummy_if])
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/conntrack.py b/src/conf_mode/conntrack.py
index a0de914bc..21a20ea8d 100755
--- a/src/conf_mode/conntrack.py
+++ b/src/conf_mode/conntrack.py
@@ -1,206 +1,227 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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
 import re
 
 from sys import exit
 
 from vyos.config import Config
-from vyos.firewall import find_nftables_rule
-from vyos.firewall import remove_nftables_rule
 from vyos.utils.process import process_named_running
 from vyos.utils.dict import dict_search
 from vyos.utils.dict import dict_search_args
+from vyos.utils.dict import dict_search_recursive
 from vyos.utils.process import cmd
 from vyos.utils.process import rc_cmd
 from vyos.utils.process import run
 from vyos.template import render
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 conntrack_config = r'/etc/modprobe.d/vyatta_nf_conntrack.conf'
 sysctl_file = r'/run/sysctl/10-vyos-conntrack.conf'
 nftables_ct_file = r'/run/nftables-ct.conf'
 
 # Every ALG (Application Layer Gateway) consists of either a Kernel Object
 # also called a Kernel Module/Driver or some rules present in iptables
 module_map = {
     'ftp' : {
         'ko' : ['nf_nat_ftp', 'nf_conntrack_ftp'],
     },
     'h323' : {
         'ko' : ['nf_nat_h323', 'nf_conntrack_h323'],
     },
     'nfs' : {
-        'nftables' : ['ct helper set "rpc_tcp" tcp dport "{111}" return',
-                      'ct helper set "rpc_udp" udp dport "{111}" return']
+        'nftables' : ['ct helper set "rpc_tcp" tcp dport {111} return',
+                      'ct helper set "rpc_udp" udp dport {111} return']
     },
     'pptp' : {
         'ko' : ['nf_nat_pptp', 'nf_conntrack_pptp'],
      },
     'sip' : {
         'ko' : ['nf_nat_sip', 'nf_conntrack_sip'],
      },
     'sqlnet' : {
-        'nftables' : ['ct helper set "tns_tcp" tcp dport "{1521,1525,1536}" return']
+        'nftables' : ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return']
     },
     'tftp' : {
         'ko' : ['nf_nat_tftp', 'nf_conntrack_tftp'],
      },
 }
 
 valid_groups = [
     'address_group',
     'domain_group',
     'network_group',
     'port_group'
 ]
 
 def resync_conntrackd():
     tmp = run('/usr/libexec/vyos/conf_mode/conntrack_sync.py')
     if tmp > 0:
         print('ERROR: error restarting conntrackd!')
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['system', 'conntrack']
 
     conntrack = conf.get_config_dict(base, key_mangling=('-', '_'),
                                      get_first_key=True,
                                      with_recursive_defaults=True)
 
-    conntrack['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'),
+    conntrack['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'),
                                                  get_first_key=True,
                                                  no_tag_node_value_mangle=True)
 
+    conntrack['flowtable_enabled'] = False
+    flow_offload = dict_search_args(conntrack['firewall'], 'global_options', 'flow_offload')
+    if flow_offload and 'disable' not in flow_offload:
+        for offload_type in ('software', 'hardware'):
+            if dict_search_args(flow_offload, offload_type, 'interface'):
+                conntrack['flowtable_enabled'] = True
+                break
+
+    conntrack['ipv4_nat_action'] = 'accept' if conf.exists(['nat']) else 'return'
+    conntrack['ipv6_nat_action'] = 'accept' if conf.exists(['nat66']) else 'return'
+    conntrack['wlb_action'] = 'accept' if conf.exists(['load-balancing', 'wan']) else 'return'
+    conntrack['wlb_local_action'] = conf.exists(['load-balancing', 'wan', 'enable-local-traffic'])
+
+    conntrack['module_map'] = module_map
+
     return conntrack
 
 def verify(conntrack):
     for inet in ['ipv4', 'ipv6']:
         if dict_search_args(conntrack, 'ignore', inet, 'rule') != None:
             for rule, rule_config in conntrack['ignore'][inet]['rule'].items():
                 if dict_search('destination.port', rule_config) or \
                    dict_search('destination.group.port_group', rule_config) or \
                    dict_search('source.port', rule_config) or \
                    dict_search('source.group.port_group', rule_config):
                    if 'protocol' not in rule_config or rule_config['protocol'] not in ['tcp', 'udp']:
                        raise ConfigError(f'Port requires tcp or udp as protocol in rule {rule}')
 
+                tcp_flags = dict_search_args(rule_config, 'tcp', 'flags')
+                if tcp_flags:
+                    if dict_search_args(rule_config, 'protocol') != 'tcp':
+                        raise ConfigError('Protocol must be tcp when specifying tcp flags')
+
+                    not_flags = dict_search_args(rule_config, 'tcp', 'flags', 'not')
+                    if not_flags:
+                        duplicates = [flag for flag in tcp_flags if flag in not_flags]
+                        if duplicates:
+                            raise ConfigError(f'Cannot match a tcp flag as set and not set')
+
                 for side in ['destination', 'source']:
                     if side in rule_config:
                         side_conf = rule_config[side]
 
                         if 'group' in side_conf:
                             if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1:
                                 raise ConfigError('Only one address-group, network-group or domain-group can be specified')
 
                             for group in valid_groups:
                                 if group in side_conf['group']:
                                     group_name = side_conf['group'][group]
                                     error_group = group.replace("_", "-")
 
                                     if group in ['address_group', 'network_group', 'domain_group']:
                                         if 'address' in side_conf:
                                             raise ConfigError(f'{error_group} and address cannot both be defined')
 
                                     if group_name and group_name[0] == '!':
                                         group_name = group_name[1:]
 
                                     if inet == 'ipv6':
                                         group = f'ipv6_{group}'
 
-                                    group_obj = dict_search_args(conntrack['firewall_group'], group, group_name)
+                                    group_obj = dict_search_args(conntrack['firewall'], 'group', group, group_name)
 
                                     if group_obj is None:
                                         raise ConfigError(f'Invalid {error_group} "{group_name}" on ignore rule')
 
                                     if not group_obj:
                                         Warning(f'{error_group} "{group_name}" has no members!')
 
     return None
 
 def generate(conntrack):
+    if not os.path.exists(nftables_ct_file):
+        conntrack['first_install'] = True
+
+    # Determine if conntrack is needed
+    conntrack['ipv4_firewall_action'] = 'return'
+    conntrack['ipv6_firewall_action'] = 'return'
+
+    if conntrack['flowtable_enabled']:
+        conntrack['ipv4_firewall_action'] = 'accept'
+        conntrack['ipv6_firewall_action'] = 'accept'
+    else:
+        for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'):
+            if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()):
+                if path[0] == 'ipv4':
+                    conntrack['ipv4_firewall_action'] = 'accept'
+                elif path[0] == 'ipv6':
+                    conntrack['ipv6_firewall_action'] = 'accept'
+
     render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack)
     render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack)
     render(nftables_ct_file, 'conntrack/nftables-ct.j2', conntrack)
     return None
 
-def find_nftables_ct_rule(table, chain, rule):
-    helper_search = re.search('ct helper set "(\w+)"', rule)
-    if helper_search:
-        rule = helper_search[1]
-    return find_nftables_rule(table, chain, [rule])
-
-def find_remove_rule(table, chain, rule):
-    handle = find_nftables_ct_rule(table, chain, rule)
-    if handle:
-        remove_nftables_rule(table, chain, handle)
-
 def apply(conntrack):
     # Depending on the enable/disable state of the ALG (Application Layer Gateway)
     # modules we need to either insmod or rmmod the helpers.
     for module, module_config in module_map.items():
         if dict_search(f'modules.{module}', conntrack) is None:
             if 'ko' in module_config:
                 for mod in module_config['ko']:
                     # Only remove the module if it's loaded
                     if os.path.exists(f'/sys/module/{mod}'):
                         cmd(f'rmmod {mod}')
-            if 'nftables' in module_config:
-                for rule in module_config['nftables']:
-                    find_remove_rule('raw', 'VYOS_CT_HELPER', rule)
-                    find_remove_rule('ip6 raw', 'VYOS_CT_HELPER', rule)
         else:
             if 'ko' in module_config:
                 for mod in module_config['ko']:
                     cmd(f'modprobe {mod}')
-            if 'nftables' in module_config:
-                for rule in module_config['nftables']:
-                    if not find_nftables_ct_rule('raw', 'VYOS_CT_HELPER', rule):
-                        cmd(f'nft insert rule raw VYOS_CT_HELPER {rule}')
-
-                    if not find_nftables_ct_rule('ip6 raw', 'VYOS_CT_HELPER', rule):
-                        cmd(f'nft insert rule ip6 raw VYOS_CT_HELPER {rule}')
 
     # Load new nftables ruleset
     install_result, output = rc_cmd(f'nft -f {nftables_ct_file}')
     if install_result == 1:
         raise ConfigError(f'Failed to apply configuration: {output}')
 
     if process_named_running('conntrackd'):
         # Reload conntrack-sync daemon to fetch new sysctl values
         resync_conntrackd()
 
     # We silently ignore all errors
     # See: https://bugzilla.redhat.com/show_bug.cgi?id=1264080
     cmd(f'sysctl -f {sysctl_file}')
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 769cc598f..d999b2a64 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -1,422 +1,402 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2022 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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
 import re
 
 from glob import glob
 from json import loads
 from sys import exit
 
 from vyos.base import Warning
 from vyos.config import Config
 from vyos.configdict import node_changed
 from vyos.configdiff import get_config_diff, Diff
 from vyos.configdep import set_dependents, call_dependents
 from vyos.configverify import verify_interface_exists
 from vyos.firewall import fqdn_config_parse
 from vyos.firewall import geoip_update
 from vyos.template import render
 from vyos.utils.process import call
 from vyos.utils.process import cmd
 from vyos.utils.dict import dict_search_args
 from vyos.utils.dict import dict_search_recursive
 from vyos.utils.process import process_named_running
 from vyos.utils.process import rc_cmd
 from vyos import ConfigError
 from vyos import airbag
 
 airbag.enable()
 
 nat_conf_script = 'nat.py'
 policy_route_conf_script = 'policy-route.py'
 
 nftables_conf = '/run/nftables.conf'
 
 sysfs_config = {
     'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'},
     'broadcast_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts', 'enable': '0', 'disable': '1'},
     'ip_src_route': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_source_route'},
     'ipv6_receive_redirects': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_redirects'},
     'ipv6_src_route': {'sysfs': '/proc/sys/net/ipv6/conf/*/accept_source_route', 'enable': '0', 'disable': '-1'},
     'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians'},
     'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects'},
     'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects'},
     'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies'},
     'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'}
 }
 
 valid_groups = [
     'address_group',
     'domain_group',
     'network_group',
     'port_group',
     'interface_group'
 ]
 
 nested_group_types = [
     'address_group', 'network_group', 'mac_group',
     'port_group', 'ipv6_address_group', 'ipv6_network_group'
 ]
 
 snmp_change_type = {
     'unknown': 0,
     'add': 1,
     'delete': 2,
     'change': 3
 }
 snmp_event_source = 1
 snmp_trap_mib = 'VYATTA-TRAP-MIB'
 snmp_trap_name = 'mgmtEventTrap'
 
 def geoip_updated(conf, firewall):
     diff = get_config_diff(conf)
     node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True)
 
     out = {
         'name': [],
         'ipv6_name': [],
         'deleted_name': [],
         'deleted_ipv6_name': []
     }
     updated = False
 
     for key, path in dict_search_recursive(firewall, 'geoip'):
         set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
         if (path[0] == 'ipv4'):
             out['name'].append(set_name)
         elif (path[0] == 'ipv6'):
             set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'
             out['ipv6_name'].append(set_name)
 
         updated = True
 
     if 'delete' in node_diff:
         for key, path in dict_search_recursive(node_diff['delete'], 'geoip'):
             set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
             if (path[0] == 'ipv4'):
                 out['deleted_name'].append(set_name)
             elif (path[0] == 'ipv6'):
                 set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
                 out['deleted_ipv6_name'].append(set_name)
             updated = True
 
     if updated:
         return out
 
     return False
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['firewall']
 
     firewall = conf.get_config_dict(base, key_mangling=('-', '_'),
                                     no_tag_node_value_mangle=True,
                                     get_first_key=True,
                                     with_recursive_defaults=True)
 
 
     firewall['group_resync'] = bool('group' in firewall or node_changed(conf, base + ['group']))
     if firewall['group_resync']:
         # Update nat and policy-route as firewall groups were updated
         set_dependents('group_resync', conf)
 
     firewall['geoip_updated'] = geoip_updated(conf, firewall)
 
     fqdn_config_parse(firewall)
 
-    firewall['flowtable_enabled'] = False
-    flow_offload = dict_search_args(firewall, 'global_options', 'flow_offload')
-    if flow_offload and 'disable' not in flow_offload:
-        for offload_type in ('software', 'hardware'):
-            if dict_search_args(flow_offload, offload_type, 'interface'):
-                firewall['flowtable_enabled'] = True
-                break
+    set_dependents('conntrack', conf)
 
     return firewall
 
 def verify_rule(firewall, rule_conf, ipv6):
     if 'action' not in rule_conf:
         raise ConfigError('Rule action must be defined')
 
     if 'jump' in rule_conf['action'] and 'jump_target' not in rule_conf:
         raise ConfigError('Action set to jump, but no jump-target specified')
 
     if 'jump_target' in rule_conf:
         if 'jump' not in rule_conf['action']:
             raise ConfigError('jump-target defined, but action jump needed and it is not defined')
         target = rule_conf['jump_target']
         if not ipv6:
             if target not in dict_search_args(firewall, 'ipv4', 'name'):
                 raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
         else:
             if target not in dict_search_args(firewall, 'ipv6', 'name'):
                 raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system')
 
     if 'queue_options' in rule_conf:
         if 'queue' not in rule_conf['action']:
             raise ConfigError('queue-options defined, but action queue needed and it is not defined')
         if 'fanout' in rule_conf['queue_options'] and ('queue' not in rule_conf or '-' not in rule_conf['queue']):
             raise ConfigError('queue-options fanout defined, then queue needs to be defined as a range')
 
     if 'queue' in rule_conf and 'queue' not in rule_conf['action']:
         raise ConfigError('queue defined, but action queue needed and it is not defined')
 
     if 'fragment' in rule_conf:
         if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']):
             raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"')
 
     if 'limit' in rule_conf:
         if 'rate' in rule_conf['limit']:
             rate_int = re.sub(r'\D', '', rule_conf['limit']['rate'])
             if int(rate_int) < 1:
                 raise ConfigError('Limit rate integer cannot be less than 1')
 
     if 'ipsec' in rule_conf:
         if {'match_ipsec', 'match_non_ipsec'} <= set(rule_conf['ipsec']):
             raise ConfigError('Cannot specify both "match-ipsec" and "match-non-ipsec"')
 
     if 'recent' in rule_conf:
         if not {'count', 'time'} <= set(rule_conf['recent']):
             raise ConfigError('Recent "count" and "time" values must be defined')
 
     tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
     if tcp_flags:
         if dict_search_args(rule_conf, 'protocol') != 'tcp':
             raise ConfigError('Protocol must be tcp when specifying tcp flags')
 
         not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not')
         if not_flags:
             duplicates = [flag for flag in tcp_flags if flag in not_flags]
             if duplicates:
                 raise ConfigError(f'Cannot match a tcp flag as set and not set')
 
     if 'protocol' in rule_conf:
         if rule_conf['protocol'] == 'icmp' and ipv6:
             raise ConfigError(f'Cannot match IPv4 ICMP protocol on IPv6, use ipv6-icmp')
         if rule_conf['protocol'] == 'ipv6-icmp' and not ipv6:
             raise ConfigError(f'Cannot match IPv6 ICMP protocol on IPv4, use icmp')
 
     for side in ['destination', 'source']:
         if side in rule_conf:
             side_conf = rule_conf[side]
 
             if len({'address', 'fqdn', 'geoip'} & set(side_conf)) > 1:
                 raise ConfigError('Only one of address, fqdn or geoip can be specified')
 
             if 'group' in side_conf:
                 if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1:
                     raise ConfigError('Only one address-group, network-group or domain-group can be specified')
 
                 for group in valid_groups:
                     if group in side_conf['group']:
                         group_name = side_conf['group'][group]
 
                         fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
                         error_group = fw_group.replace("_", "-")
 
                         if group in ['address_group', 'network_group', 'domain_group']:
                             types = [t for t in ['address', 'fqdn', 'geoip'] if t in side_conf]
                             if types:
                                 raise ConfigError(f'{error_group} and {types[0]} cannot both be defined')
 
                         if group_name and group_name[0] == '!':
                             group_name = group_name[1:]
 
                         group_obj = dict_search_args(firewall, 'group', fw_group, group_name)
 
                         if group_obj is None:
                             raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
 
                         if not group_obj:
                             Warning(f'{error_group} "{group_name}" has no members!')
 
             if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
                 if 'protocol' not in rule_conf:
                     raise ConfigError('Protocol must be defined if specifying a port or port-group')
 
                 if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
                     raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group')
 
             if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'):
                 raise ConfigError(f'{side} port-group and port cannot both be defined')
 
     if 'log_options' in rule_conf:
         if 'log' not in rule_conf or 'enable' not in rule_conf['log']:
             raise ConfigError('log-options defined, but log is not enable')
 
         if 'snapshot_length' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']:
             raise ConfigError('log-options snapshot-length defined, but log group is not define')
 
         if 'queue_threshold' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']:
             raise ConfigError('log-options queue-threshold defined, but log group is not define')
 
     for direction in ['inbound_interface','outbound_interface']:
         if direction in rule_conf:
             if 'interface_name' in rule_conf[direction] and 'interface_group' in rule_conf[direction]:
                 raise ConfigError(f'Cannot specify both interface-group and interface-name for {direction}')
 
 def verify_nested_group(group_name, group, groups, seen):
     if 'include' not in group:
         return
 
     seen.append(group_name)
 
     for g in group['include']:
         if g not in groups:
             raise ConfigError(f'Nested group "{g}" does not exist')
 
         if g in seen:
             raise ConfigError(f'Group "{group_name}" has a circular reference')
 
         if 'include' in groups[g]:
             verify_nested_group(g, groups[g], groups, seen)
 
 def verify(firewall):
     if 'group' in firewall:
         for group_type in nested_group_types:
             if group_type in firewall['group']:
                 groups = firewall['group'][group_type]
                 for group_name, group in groups.items():
                     verify_nested_group(group_name, group, groups, [])
 
     if 'ipv4' in firewall:
         for name in ['name','forward','input','output']:
             if name in firewall['ipv4']:
                 for name_id, name_conf in firewall['ipv4'][name].items():
                     if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
                         raise ConfigError('default-action set to jump, but no default-jump-target specified')
                     if 'default_jump_target' in name_conf:
                         target = name_conf['default_jump_target']
                         if 'jump' not in name_conf['default_action']:
                             raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
                         if name_conf['default_jump_target'] == name_id:
                             raise ConfigError(f'Loop detected on default-jump-target.')
                         ## Now need to check that default-jump-target exists (other firewall chain/name)
                         if target not in dict_search_args(firewall['ipv4'], 'name'):
                             raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
 
                     if 'rule' in name_conf:
                         for rule_id, rule_conf in name_conf['rule'].items():
                             verify_rule(firewall, rule_conf, False)
 
     if 'ipv6' in firewall:
         for name in ['name','forward','input','output']:
             if name in firewall['ipv6']:
                 for name_id, name_conf in firewall['ipv6'][name].items():
                     if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
                         raise ConfigError('default-action set to jump, but no default-jump-target specified')
                     if 'default_jump_target' in name_conf:
                         target = name_conf['default_jump_target']
                         if 'jump' not in name_conf['default_action']:
                             raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
                         if name_conf['default_jump_target'] == name_id:
                             raise ConfigError(f'Loop detected on default-jump-target.')
                         ## Now need to check that default-jump-target exists (other firewall chain/name)
                         if target not in dict_search_args(firewall['ipv6'], 'name'):
                             raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
 
                     if 'rule' in name_conf:
                         for rule_id, rule_conf in name_conf['rule'].items():
                             verify_rule(firewall, rule_conf, True)
 
     # Verify flow offload options
     flow_offload = dict_search_args(firewall, 'global_options', 'flow_offload')
     for offload_type in ('software', 'hardware'):
         interfaces = dict_search_args(flow_offload, offload_type, 'interface') or []
         for interface in interfaces:
             # nft will raise an error when adding a non-existent interface to a flowtable
             verify_interface_exists(interface)
 
     return None
 
 def generate(firewall):
     if not os.path.exists(nftables_conf):
         firewall['first_install'] = True
 
-    # Determine if conntrack is needed
-    firewall['ipv4_conntrack_action'] = 'return'
-    firewall['ipv6_conntrack_action'] = 'return'
-    if firewall['flowtable_enabled']:  # Netfilter's flowtable offload requires conntrack
-        firewall['ipv4_conntrack_action'] = 'accept'
-        firewall['ipv6_conntrack_action'] = 'accept'
-    else:  # Check if conntrack is needed by firewall rules
-        for proto in ('ipv4', 'ipv6'):
-            for rules, _ in dict_search_recursive(firewall.get(proto, {}), 'rule'):
-                if any(('state' in rule_conf or 'connection_status' in rule_conf) for rule_conf in rules.values()):
-                    firewall[f'{proto}_conntrack_action'] = 'accept'
-                    break
-
     render(nftables_conf, 'firewall/nftables.j2', firewall)
     return None
 
 def apply_sysfs(firewall):
     for name, conf in sysfs_config.items():
         paths = glob(conf['sysfs'])
         value = None
 
         if name in firewall['global_options']:
             conf_value = firewall['global_options'][name]
             if conf_value in conf:
                 value = conf[conf_value]
             elif conf_value == 'enable':
                 value = '1'
             elif conf_value == 'disable':
                 value = '0'
 
         if value:
             for path in paths:
                 with open(path, 'w') as f:
                     f.write(value)
 
 def apply(firewall):
     install_result, output = rc_cmd(f'nft -f {nftables_conf}')
     if install_result == 1:
         raise ConfigError(f'Failed to apply firewall: {output}')
 
     apply_sysfs(firewall)
 
-    if firewall['group_resync']:
-        call_dependents()
+    call_dependents()
 
     # T970 Enable a resolver (systemd daemon) that checks
     # domain-group/fqdn addresses and update entries for domains by timeout
     # If router loaded without internet connection or for synchronization
     domain_action = 'stop'
     if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'] or firewall['ip6_fqdn']:
         domain_action = 'restart'
     call(f'systemctl {domain_action} vyos-domain-resolver.service')
 
     if firewall['geoip_updated']:
         # Call helper script to Update set contents
         if 'name' in firewall['geoip_updated'] or 'ipv6_name' in firewall['geoip_updated']:
             print('Updating GeoIP. Please wait...')
             geoip_update(firewall)
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 71acd69fa..81ee39df1 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -1,288 +1,288 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2018-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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
 import re
 
 from sys import exit
 from ipaddress import ip_address
 
 from vyos.base import Warning
 from vyos.config import Config
 from vyos.config import config_dict_merge
 from vyos.configverify import verify_vrf
 from vyos.ifconfig import Section
 from vyos.template import render
 from vyos.utils.process import call
 from vyos.utils.process import cmd
 from vyos.utils.network import is_addr_assigned
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 uacctd_conf_path = '/run/pmacct/uacctd.conf'
 systemd_service = 'uacctd.service'
 systemd_override = f'/run/systemd/system/{systemd_service}.d/override.conf'
 nftables_nflog_table = 'raw'
-nftables_nflog_chain = 'VYOS_CT_PREROUTING_HOOK'
+nftables_nflog_chain = 'VYOS_PREROUTING_HOOK'
 egress_nftables_nflog_table = 'inet mangle'
 egress_nftables_nflog_chain = 'FORWARD'
 
 # get nftables rule dict for chain in table
 def _nftables_get_nflog(chain, table):
     # define list with rules
     rules = []
 
     # prepare regex for parsing rules
     rule_pattern = '[io]ifname "(?P<interface>[\w\.\*\-]+)".*handle (?P<handle>[\d]+)'
     rule_re = re.compile(rule_pattern)
 
     # run nftables, save output and split it by lines
     nftables_command = f'nft -a list chain {table} {chain}'
     tmp = cmd(nftables_command, message='Failed to get flows list')
     # parse each line and add information to list
     for current_rule in tmp.splitlines():
         if 'FLOW_ACCOUNTING_RULE' not in current_rule:
             continue
         current_rule_parsed = rule_re.search(current_rule)
         if current_rule_parsed:
             groups = current_rule_parsed.groupdict()
             rules.append({ 'interface': groups["interface"], 'table': table, 'handle': groups["handle"] })
 
     # return list with rules
     return rules
 
 def _nftables_config(configured_ifaces, direction, length=None):
     # define list of nftables commands to modify settings
     nftable_commands = []
     nftables_chain = nftables_nflog_chain
     nftables_table = nftables_nflog_table
 
     if direction == "egress":
         nftables_chain = egress_nftables_nflog_chain
         nftables_table = egress_nftables_nflog_table
 
     # prepare extended list with configured interfaces
     configured_ifaces_extended = []
     for iface in configured_ifaces:
         configured_ifaces_extended.append({ 'iface': iface })
 
     # get currently configured interfaces with nftables rules
     active_nflog_rules = _nftables_get_nflog(nftables_chain, nftables_table)
 
     # compare current active list with configured one and delete excessive interfaces, add missed
     active_nflog_ifaces = []
     for rule in active_nflog_rules:
         interface = rule['interface']
         if interface not in configured_ifaces:
             table = rule['table']
             handle = rule['handle']
             nftable_commands.append(f'nft delete rule {table} {nftables_chain} handle {handle}')
         else:
             active_nflog_ifaces.append({
                 'iface': interface,
             })
 
     # do not create new rules for already configured interfaces
     for iface in active_nflog_ifaces:
         if iface in active_nflog_ifaces and iface in configured_ifaces_extended:
             configured_ifaces_extended.remove(iface)
 
     # create missed rules
     for iface_extended in configured_ifaces_extended:
         iface = iface_extended['iface']
         iface_prefix = "o" if direction == "egress" else "i"
         rule_definition = f'{iface_prefix}ifname "{iface}" counter log group 2 snaplen {length} queue-threshold 100 comment "FLOW_ACCOUNTING_RULE"'
         nftable_commands.append(f'nft insert rule {nftables_table} {nftables_chain} {rule_definition}')
         # Also add IPv6 ingres logging
         if nftables_table == nftables_nflog_table:
             nftable_commands.append(f'nft insert rule ip6 {nftables_table} {nftables_chain} {rule_definition}')
 
     # change nftables
     for command in nftable_commands:
         cmd(command, raising=ConfigError)
 
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['system', 'flow-accounting']
     if not conf.exists(base):
         return None
 
     flow_accounting = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
 
     # We have gathered the dict representation of the CLI, but there are
     # default values which we need to conditionally update into the
     # dictionary retrieved.
     default_values = conf.get_config_defaults(**flow_accounting.kwargs,
                                               recursive=True)
 
     # delete individual flow type defaults - should only be added if user
     # sets this feature
     for flow_type in ['sflow', 'netflow']:
         if flow_type not in flow_accounting and flow_type in default_values:
             del default_values[flow_type]
 
     flow_accounting = config_dict_merge(default_values, flow_accounting)
 
     return flow_accounting
 
 def verify(flow_config):
     if not flow_config:
         return None
 
     # check if at least one collector is enabled
     if 'sflow' not in flow_config and 'netflow' not in flow_config and 'disable_imt' in flow_config:
         raise ConfigError('You need to configure at least sFlow or NetFlow, ' \
                           'or not set "disable-imt" for flow-accounting!')
 
     # Check if at least one interface is configured
     if 'interface' not in flow_config:
         raise ConfigError('Flow accounting requires at least one interface to ' \
                           'be configured!')
 
     # check that all configured interfaces exists in the system
     for interface in flow_config['interface']:
         if interface not in Section.interfaces():
             # Changed from error to warning to allow adding dynamic interfaces
             # and interface templates
             Warning(f'Interface "{interface}" is not presented in the system')
 
     # check sFlow configuration
     if 'sflow' in flow_config:
         # check if at least one sFlow collector is configured
         if 'server' not in flow_config['sflow']:
             raise ConfigError('You need to configure at least one sFlow server!')
 
         # check that all sFlow collectors use the same IP protocol version
         sflow_collector_ipver = None
         for server in flow_config['sflow']['server']:
             if sflow_collector_ipver:
                 if sflow_collector_ipver != ip_address(server).version:
                     raise ConfigError("All sFlow servers must use the same IP protocol")
             else:
                 sflow_collector_ipver = ip_address(server).version
 
         # check if vrf is defined for Sflow
         verify_vrf(flow_config)
         sflow_vrf = None
         if 'vrf' in flow_config:
             sflow_vrf = flow_config['vrf']
 
         # check agent-id for sFlow: we should avoid mixing IPv4 agent-id with IPv6 collectors and vice-versa
         for server in flow_config['sflow']['server']:
             if 'agent_address' in flow_config['sflow']:
                 if ip_address(server).version != ip_address(flow_config['sflow']['agent_address']).version:
                     raise ConfigError('IPv4 and IPv6 addresses can not be mixed in "sflow agent-address" and "sflow '\
                                       'server". You need to set the same IP version for both "agent-address" and '\
                                       'all sFlow servers')
 
         if 'agent_address' in flow_config['sflow']:
             tmp = flow_config['sflow']['agent_address']
             if not is_addr_assigned(tmp, sflow_vrf):
                 raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!')
 
         # Check if configured sflow source-address exist in the system
         if 'source_address' in flow_config['sflow']:
             if not is_addr_assigned(flow_config['sflow']['source_address'], sflow_vrf):
                 tmp = flow_config['sflow']['source_address']
                 raise ConfigError(f'Configured "sflow source-address {tmp}" does not exist on the system!')
 
     # check NetFlow configuration
     if 'netflow' in flow_config:
         # check if vrf is defined for netflow
         netflow_vrf = None
         if 'vrf' in flow_config:
             netflow_vrf = flow_config['vrf']
 
         # check if at least one NetFlow collector is configured if NetFlow configuration is presented
         if 'server' not in flow_config['netflow']:
             raise ConfigError('You need to configure at least one NetFlow server!')
 
         # Check if configured netflow source-address exist in the system
         if 'source_address' in flow_config['netflow']:
             if not is_addr_assigned(flow_config['netflow']['source_address'], netflow_vrf):
                 tmp = flow_config['netflow']['source_address']
                 raise ConfigError(f'Configured "netflow source-address {tmp}" does not exist on the system!')
 
         # Check if engine-id compatible with selected protocol version
         if 'engine_id' in flow_config['netflow']:
             v5_filter = '^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]):(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$'
             v9v10_filter = '^(\d|[1-9]\d{1,8}|[1-3]\d{9}|4[01]\d{8}|42[0-8]\d{7}|429[0-3]\d{6}|4294[0-8]\d{5}|42949[0-5]\d{4}|429496[0-6]\d{3}|4294967[01]\d{2}|42949672[0-8]\d|429496729[0-5])$'
             engine_id = flow_config['netflow']['engine_id']
             version = flow_config['netflow']['version']
 
             if flow_config['netflow']['version'] == '5':
                 regex_filter = re.compile(v5_filter)
                 if not regex_filter.search(engine_id):
                     raise ConfigError(f'You cannot use NetFlow engine-id "{engine_id}" '\
                                       f'together with NetFlow protocol version "{version}"!')
             else:
                 regex_filter = re.compile(v9v10_filter)
                 if not regex_filter.search(flow_config['netflow']['engine_id']):
                     raise ConfigError(f'Can not use NetFlow engine-id "{engine_id}" together '\
                                       f'with NetFlow protocol version "{version}"!')
 
     # return True if all checks were passed
     return True
 
 def generate(flow_config):
     if not flow_config:
         return None
 
     render(uacctd_conf_path, 'pmacct/uacctd.conf.j2', flow_config)
     render(systemd_override, 'pmacct/override.conf.j2', flow_config)
     # Reload systemd manager configuration
     call('systemctl daemon-reload')
 
 def apply(flow_config):
     action = 'restart'
     # Check if flow-accounting was removed and define command
     if not flow_config:
         _nftables_config([], 'ingress')
         _nftables_config([], 'egress')
 
         # Stop flow-accounting daemon and remove configuration file
         call(f'systemctl stop {systemd_service}')
         if os.path.exists(uacctd_conf_path):
             os.unlink(uacctd_conf_path)
         return
 
     # Start/reload flow-accounting daemon
     call(f'systemctl restart {systemd_service}')
 
     # configure nftables rules for defined interfaces
     if 'interface' in flow_config:
         _nftables_config(flow_config['interface'], 'ingress', flow_config['packet_length'])
 
         # configure egress the same way if configured otherwise remove it
         if 'enable_egress' in flow_config:
             _nftables_config(flow_config['interface'], 'egress', flow_config['packet_length'])
         else:
             _nftables_config([], 'egress')
 
 if __name__ == '__main__':
     try:
         config = get_config()
         verify(config)
         generate(config)
         apply(config)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/load-balancing-wan.py b/src/conf_mode/load-balancing-wan.py
index ad9c80d72..5da0b906b 100755
--- a/src/conf_mode/load-balancing-wan.py
+++ b/src/conf_mode/load-balancing-wan.py
@@ -1,146 +1,151 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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
 
 from sys import exit
 from shutil import rmtree
 
 from vyos.base import Warning
 from vyos.config import Config
+from vyos.configdep import set_dependents, call_dependents
 from vyos.utils.process import cmd
 from vyos.template import render
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 load_balancing_dir = '/run/load-balance'
 load_balancing_conf_file = f'{load_balancing_dir}/wlb.conf'
 systemd_service = 'vyos-wan-load-balance.service'
 
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
 
     base = ['load-balancing', 'wan']
     lb = conf.get_config_dict(base, key_mangling=('-', '_'),
                               no_tag_node_value_mangle=True,
                               get_first_key=True,
                               with_recursive_defaults=True)
 
     # prune limit key if not set by user
     for rule in lb.get('rule', []):
         if lb.from_defaults(['rule', rule, 'limit']):
             del lb['rule'][rule]['limit']
 
+    set_dependents('conntrack', conf)
+
     return lb
 
 
 def verify(lb):
     if not lb:
         return None
 
     if 'interface_health' not in lb:
         raise ConfigError(
             'A valid WAN load-balance configuration requires an interface with a nexthop!'
         )
 
     for interface, interface_config in lb['interface_health'].items():
         if 'nexthop' not in interface_config:
             raise ConfigError(
                 f'interface-health {interface} nexthop must be specified!')
 
         if 'test' in interface_config:
             for test_rule, test_config in interface_config['test'].items():
                 if 'type' in test_config:
                     if test_config['type'] == 'user-defined' and 'test_script' not in test_config:
                         raise ConfigError(
                             f'test {test_rule} script must be defined for test-script!'
                         )
 
     if 'rule' not in lb:
         Warning(
             'At least one rule with an (outbound) interface must be defined for WAN load balancing to be active!'
         )
     else:
         for rule, rule_config in lb['rule'].items():
             if 'inbound_interface' not in rule_config:
                 raise ConfigError(f'rule {rule} inbound-interface must be specified!')
             if {'failover', 'exclude'} <= set(rule_config):
                 raise ConfigError(f'rule {rule} failover cannot be configured with exclude!')
             if {'limit', 'exclude'} <= set(rule_config):
                 raise ConfigError(f'rule {rule} limit cannot be used with exclude!')
             if 'interface' not in rule_config:
                 if 'exclude' not in rule_config:
                     Warning(
                         f'rule {rule} will be inactive because no (outbound) interfaces have been defined for this rule'
                     )
             for direction in {'source', 'destination'}:
                 if direction in rule_config:
                     if 'protocol' in rule_config and 'port' in rule_config[
                             direction]:
                         if rule_config['protocol'] not in {'tcp', 'udp'}:
                             raise ConfigError('ports can only be specified when protocol is "tcp" or "udp"')
 
 
 def generate(lb):
     if not lb:
         # Delete /run/load-balance/wlb.conf
         if os.path.isfile(load_balancing_conf_file):
             os.unlink(load_balancing_conf_file)
         # Delete old directories
         if os.path.isdir(load_balancing_dir):
             rmtree(load_balancing_dir, ignore_errors=True)
         if os.path.exists('/var/run/load-balance/wlb.out'):
             os.unlink('/var/run/load-balance/wlb.out')
 
         return None
 
     # Create load-balance dir
     if not os.path.isdir(load_balancing_dir):
         os.mkdir(load_balancing_dir)
 
     render(load_balancing_conf_file, 'load-balancing/wlb.conf.j2', lb)
 
     return None
 
 
 def apply(lb):
     if not lb:
         try:
             cmd(f'systemctl stop {systemd_service}')
         except Exception as e:
             print(f"Error message: {e}")
 
     else:
         cmd('sudo sysctl -w net.netfilter.nf_conntrack_acct=1')
         cmd(f'systemctl restart {systemd_service}')
 
+    call_dependents()
+
     return None
 
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index e37a7011c..52a7a71fd 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -1,281 +1,237 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2020-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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 jmespath
 import json
 import os
 
-from distutils.version import LooseVersion
-from platform import release as kernel_version
 from sys import exit
 from netifaces import interfaces
 
 from vyos.base import Warning
 from vyos.config import Config
+from vyos.configdep import set_dependents, call_dependents
 from vyos.template import render
 from vyos.template import is_ip_network
 from vyos.utils.kernel import check_kmod
 from vyos.utils.dict import dict_search
 from vyos.utils.dict import dict_search_args
 from vyos.utils.process import cmd
 from vyos.utils.process import run
 from vyos.utils.network import is_addr_assigned
 from vyos import ConfigError
 
 from vyos import airbag
 airbag.enable()
 
-if LooseVersion(kernel_version()) > LooseVersion('5.1'):
-    k_mod = ['nft_nat', 'nft_chain_nat']
-else:
-    k_mod = ['nft_nat', 'nft_chain_nat_ipv4']
+k_mod = ['nft_nat', 'nft_chain_nat']
 
 nftables_nat_config = '/run/nftables_nat.conf'
 nftables_static_nat_conf = '/run/nftables_static-nat-rules.nft'
 
 valid_groups = [
     'address_group',
     'domain_group',
     'network_group',
     'port_group'
 ]
 
-def get_handler(json, chain, target):
-    """ Get nftable rule handler number of given chain/target combination.
-    Handler is required when adding NAT/Conntrack helper targets """
-    for x in json:
-        if x['chain'] != chain:
-            continue
-        if x['target'] != target:
-            continue
-        return x['handle']
+def get_config(config=None):
+    if config:
+        conf = config
+    else:
+        conf = Config()
 
-    return None
+    base = ['nat']
+    nat = conf.get_config_dict(base, key_mangling=('-', '_'),
+                               get_first_key=True,
+                               with_recursive_defaults=True)
 
+    set_dependents('conntrack', conf)
+
+    if not conf.exists(base):
+        nat['deleted'] = ''
+        return nat
+
+    nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
+                                    no_tag_node_value_mangle=True)
+
+    return nat
 
 def verify_rule(config, err_msg, groups_dict):
     """ Common verify steps used for both source and destination NAT """
 
     if (dict_search('translation.port', config) != None or
         dict_search('translation.redirect.port', config) != None or
         dict_search('destination.port', config) != None or
         dict_search('source.port', config)):
 
         if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
             raise ConfigError(f'{err_msg}\n' \
                               'ports can only be specified when protocol is '\
                               'either tcp, udp or tcp_udp!')
 
         if is_ip_network(dict_search('translation.address', config)):
             raise ConfigError(f'{err_msg}\n' \
                              'Cannot use ports with an IPv4 network as translation address as it\n' \
                              'statically maps a whole network of addresses onto another\n' \
                              'network of addresses')
 
     for side in ['destination', 'source']:
         if side in config:
             side_conf = config[side]
 
             if len({'address', 'fqdn'} & set(side_conf)) > 1:
                 raise ConfigError('Only one of address, fqdn or geoip can be specified')
 
             if 'group' in side_conf:
                 if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1:
                     raise ConfigError('Only one address-group, network-group or domain-group can be specified')
 
                 for group in valid_groups:
                     if group in side_conf['group']:
                         group_name = side_conf['group'][group]
                         error_group = group.replace("_", "-")
 
                         if group in ['address_group', 'network_group', 'domain_group']:
                             types = [t for t in ['address', 'fqdn'] if t in side_conf]
                             if types:
                                 raise ConfigError(f'{error_group} and {types[0]} cannot both be defined')
 
                         if group_name and group_name[0] == '!':
                             group_name = group_name[1:]
 
                         group_obj = dict_search_args(groups_dict, group, group_name)
 
                         if group_obj is None:
                             raise ConfigError(f'Invalid {error_group} "{group_name}" on nat rule')
 
                         if not group_obj:
                             Warning(f'{error_group} "{group_name}" has no members!')
 
             if dict_search_args(side_conf, 'group', 'port_group'):
                 if 'protocol' not in config:
                     raise ConfigError('Protocol must be defined if specifying a port-group')
 
                 if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
                     raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port-group')
 
     if 'load_balance' in config:
         for item in ['source-port', 'destination-port']:
             if item in config['load_balance']['hash'] and config['protocol'] not in ['tcp', 'udp']:
                 raise ConfigError('Protocol must be tcp or udp when specifying hash ports')
         count = 0
         if 'backend' in config['load_balance']:
             for member in config['load_balance']['backend']:
                 weight = config['load_balance']['backend'][member]['weight']
                 count = count +  int(weight)
             if count != 100:
                 Warning(f'Sum of weight for nat load balance rule is not 100. You may get unexpected behaviour')
 
-def get_config(config=None):
-    if config:
-        conf = config
-    else:
-        conf = Config()
-
-    base = ['nat']
-    nat = conf.get_config_dict(base, key_mangling=('-', '_'),
-                               get_first_key=True,
-                               with_recursive_defaults=True)
-
-    # read in current nftable (once) for further processing
-    tmp = cmd('nft -j list table raw')
-    nftable_json = json.loads(tmp)
-
-    # condense the full JSON table into a list with only relevand informations
-    pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}'
-    condensed_json = jmespath.search(pattern, nftable_json)
-
-    if not conf.exists(base):
-        if get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER'):
-            nat['helper_functions'] = 'remove'
-
-            # Retrieve current table handler positions
-            nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
-            nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
-            nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
-            nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
-        nat['deleted'] = ''
-        return nat
-
-    nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
-                                    no_tag_node_value_mangle=True)
-
-    # check if NAT connection tracking helpers need to be set up - this has to
-    # be done only once
-    if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'):
-        nat['helper_functions'] = 'add'
-
-        # Retrieve current table handler positions
-        nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE')
-        nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK')
-        nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE')
-        nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK')
-
-    return nat
-
 def verify(nat):
     if not nat or 'deleted' in nat:
         # no need to verify the CLI as NAT is going to be deactivated
         return None
 
-    if 'helper_functions' in nat:
-        if not (nat['pre_ct_ignore'] or nat['pre_ct_conntrack'] or nat['out_ct_ignore'] or nat['out_ct_conntrack']):
-            raise Exception('could not determine nftable ruleset handlers')
-
     if dict_search('source.rule', nat):
         for rule, config in dict_search('source.rule', nat).items():
             err_msg = f'Source NAT configuration error in rule {rule}:'
 
             if 'outbound_interface' in config:
                 if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
                     Warning(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
 
             if not dict_search('translation.address', config) and not dict_search('translation.port', config):
                 if 'exclude' not in config and 'backend' not in config['load_balance']:
                     raise ConfigError(f'{err_msg} translation requires address and/or port')
 
             addr = dict_search('translation.address', config)
             if addr != None and addr != 'masquerade' and not is_ip_network(addr):
                 for ip in addr.split('-'):
                     if not is_addr_assigned(ip):
                         Warning(f'IP address {ip} does not exist on the system!')
 
             # common rule verification
             verify_rule(config, err_msg, nat['firewall_group'])
 
     if dict_search('destination.rule', nat):
         for rule, config in dict_search('destination.rule', nat).items():
             err_msg = f'Destination NAT configuration error in rule {rule}:'
 
             if 'inbound_interface' in config:
                 if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
                     Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
 
             if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']:
                 if 'exclude' not in config and 'backend' not in config['load_balance']:
                     raise ConfigError(f'{err_msg} translation requires address and/or port')
 
             # common rule verification
             verify_rule(config, err_msg, nat['firewall_group'])
 
     if dict_search('static.rule', nat):
         for rule, config in dict_search('static.rule', nat).items():
             err_msg = f'Static NAT configuration error in rule {rule}:'
 
             if 'inbound_interface' not in config:
                 raise ConfigError(f'{err_msg}\n' \
                                   'inbound-interface not specified')
 
             # common rule verification
             verify_rule(config, err_msg, nat['firewall_group'])
 
     return None
 
 def generate(nat):
     if not os.path.exists(nftables_nat_config):
         nat['first_install'] = True
 
     render(nftables_nat_config, 'firewall/nftables-nat.j2', nat)
     render(nftables_static_nat_conf, 'firewall/nftables-static-nat.j2', nat)
 
     # dry-run newly generated configuration
     tmp = run(f'nft -c -f {nftables_nat_config}')
     if tmp > 0:
         raise ConfigError('Configuration file errors encountered!')
 
     tmp = run(f'nft -c -f {nftables_static_nat_conf}')
     if tmp > 0:
         raise ConfigError('Configuration file errors encountered!')
 
     return None
 
 def apply(nat):
     cmd(f'nft -f {nftables_nat_config}')
     cmd(f'nft -f {nftables_static_nat_conf}')
 
     if not nat or 'deleted' in nat:
         os.unlink(nftables_nat_config)
         os.unlink(nftables_static_nat_conf)
 
+    call_dependents()
+
     return None
 
 if __name__ == '__main__':
     try:
         check_kmod(k_mod)
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index 4c12618bc..46d796bc8 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -1,169 +1,131 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2020-2021 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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 jmespath
 import json
 import os
 
 from sys import exit
 from netifaces import interfaces
 
 from vyos.base import Warning
 from vyos.config import Config
+from vyos.configdep import set_dependents, call_dependents
 from vyos.template import render
 from vyos.utils.process import cmd
 from vyos.utils.kernel import check_kmod
 from vyos.utils.dict import dict_search
 from vyos.template import is_ipv6
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 k_mod = ['nft_nat', 'nft_chain_nat']
 
 nftables_nat66_config = '/run/nftables_nat66.nft'
 ndppd_config = '/run/ndppd/ndppd.conf'
 
-def get_handler(json, chain, target):
-    """ Get nftable rule handler number of given chain/target combination.
-    Handler is required when adding NAT66/Conntrack helper targets """
-    for x in json:
-        if x['chain'] != chain:
-            continue
-        if x['target'] != target:
-            continue
-        return x['handle']
-
-    return None
-
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
 
     base = ['nat66']
     nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
 
-    # read in current nftable (once) for further processing
-    tmp = cmd('nft -j list table ip6 raw')
-    nftable_json = json.loads(tmp)
-
-    # condense the full JSON table into a list with only relevand informations
-    pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}'
-    condensed_json = jmespath.search(pattern, nftable_json)
+    set_dependents('conntrack', conf)
 
     if not conf.exists(base):
-        nat['helper_functions'] = 'remove'
-        nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_HELPER')
-        nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
-        nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_HELPER')
-        nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK')
         nat['deleted'] = ''
-        return nat
-
-    # check if NAT66 connection tracking helpers need to be set up - this has to
-    # be done only once
-    if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'):
-        nat['helper_functions'] = 'add'
-
-        # Retrieve current table handler positions
-        nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_IGNORE')
-        nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYOS_CT_PREROUTING_HOOK')
-        nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_IGNORE')
-        nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYOS_CT_OUTPUT_HOOK')
-    else:
-        nat['helper_functions'] = 'has'
 
     return nat
 
 def verify(nat):
     if not nat or 'deleted' in nat:
         # no need to verify the CLI as NAT66 is going to be deactivated
         return None
 
-    if 'helper_functions' in nat and nat['helper_functions'] != 'has':
-        if not (nat['pre_ct_conntrack'] or nat['out_ct_conntrack']):
-            raise Exception('could not determine nftable ruleset handlers')
-
     if dict_search('source.rule', nat):
         for rule, config in dict_search('source.rule', nat).items():
             err_msg = f'Source NAT66 configuration error in rule {rule}:'
             if 'outbound_interface' not in config:
                 raise ConfigError(f'{err_msg} outbound-interface not specified')
 
             if config['outbound_interface'] not in interfaces():
                 raise ConfigError(f'rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
 
             addr = dict_search('translation.address', config)
             if addr != None:
                 if addr != 'masquerade' and not is_ipv6(addr):
                     raise ConfigError(f'IPv6 address {addr} is not a valid address')
             else:
                 if 'exclude' not in config:
                     raise ConfigError(f'{err_msg} translation address not specified')
 
             prefix = dict_search('source.prefix', config)
             if prefix != None:
                 if not is_ipv6(prefix):
                     raise ConfigError(f'{err_msg} source-prefix not specified')
 
     if dict_search('destination.rule', nat):
         for rule, config in dict_search('destination.rule', nat).items():
             err_msg = f'Destination NAT66 configuration error in rule {rule}:'
 
             if 'inbound_interface' not in config:
                 raise ConfigError(f'{err_msg}\n' \
                                   'inbound-interface not specified')
             else:
                 if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
                     Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
 
     return None
 
 def generate(nat):
     if not os.path.exists(nftables_nat66_config):
         nat['first_install'] = True
 
     render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat, permission=0o755)
     render(ndppd_config, 'ndppd/ndppd.conf.j2', nat, permission=0o755)
     return None
 
 def apply(nat):
     if not nat:
         return None
 
     cmd(f'nft -f {nftables_nat66_config}')
 
     if 'deleted' in nat or not dict_search('source.rule', nat):
         cmd('systemctl stop ndppd')
         if os.path.isfile(ndppd_config):
             os.unlink(ndppd_config)
     else:
         cmd('systemctl restart ndppd')
 
+    call_dependents()
+
     return None
 
 if __name__ == '__main__':
     try:
         check_kmod(k_mod)
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)