diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2
index ee468c6c1..506ad815e 100644
--- a/data/templates/firewall/nftables-zone.j2
+++ b/data/templates/firewall/nftables-zone.j2
@@ -1,68 +1,77 @@
-{% macro zone_chains(zone, ipv6=False) %}
+{% macro zone_chains(zone, ipv6=False, state_policy=False) %}
 {% set fw_name = 'ipv6_name' if ipv6 else 'name' %}
 {% set suffix = '6' if ipv6 else '' %}
     chain VYOS_ZONE_FORWARD {
         type filter hook forward priority 1; policy accept;
+{% if state_policy %}
+        jump VYOS_STATE_POLICY{{ suffix }}
+{% endif %}
 {% for zone_name, zone_conf in zone.items() %}
 {%     if 'local_zone' not in zone_conf %}
         oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }}
 {%     endif %}
 {% endfor %}
     }
     chain VYOS_ZONE_LOCAL {
         type filter hook input priority 1; policy accept;
+{% if state_policy %}
+        jump VYOS_STATE_POLICY{{ suffix }}
+{% endif %}
 {% for zone_name, zone_conf in zone.items() %}
 {%     if 'local_zone' in zone_conf %}
         counter jump VZONE_{{ zone_name }}_IN
 {%     endif %}
 {% endfor %}
     }
     chain VYOS_ZONE_OUTPUT {
         type filter hook output priority 1; policy accept;
+{% if state_policy %}
+        jump VYOS_STATE_POLICY{{ suffix }}
+{% endif %}
 {% for zone_name, zone_conf in zone.items() %}
 {%     if 'local_zone' in zone_conf %}
         counter jump VZONE_{{ zone_name }}_OUT
 {%     endif %}
 {% endfor %}
     }
 {% for zone_name, zone_conf in zone.items() %}
 {%     if zone_conf.local_zone is vyos_defined %}
     chain VZONE_{{ zone_name }}_IN {
         iifname lo counter return
 {%         if zone_conf.from is vyos_defined %}
 {%             for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
         iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
         iifname { {{ zone[from_zone].interface | join(",") }} } counter return
 {%             endfor %}
 {%         endif %}
         {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
     }
     chain VZONE_{{ zone_name }}_OUT {
         oifname lo counter return
 {%         if zone_conf.from_local is vyos_defined %}
 {%             for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall[fw_name] is vyos_defined %}
         oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
         oifname { {{ zone[from_zone].interface | join(",") }} } counter return
 {%             endfor %}
 {%         endif %}
         {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
     }
 {%     else %}
     chain VZONE_{{ zone_name }} {
         iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }}
 {%         if zone_conf.intra_zone_filtering is vyos_defined %}
         iifname { {{ zone_conf.interface | join(",") }} } counter return
 {%         endif %}
 {%         if zone_conf.from is vyos_defined %}
 {%             for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
 {%                 if zone[from_zone].local_zone is not defined %}
         iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
         iifname { {{ zone[from_zone].interface | join(",") }} } counter return
 {%                 endif %}
 {%             endfor %}
 {%         endif %}
         {{ zone_conf | nft_default_rule('zone_' + zone_name) }}
     }
 {%     endif %}
 {% endfor %}
 {% endmacro %}
\ No newline at end of file
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 91c4d0788..3f7906628 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -1,287 +1,333 @@
 #!/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_tmpl %}
 {% import 'firewall/nftables-zone.j2' as zone_tmpl %}
 
 flush chain raw FW_CONNTRACK
 flush chain ip6 raw FW_CONNTRACK
 
 table raw {
     chain FW_CONNTRACK {
         {{ ipv4_conntrack_action }}
     }
 }
 
 table ip6 raw {
     chain FW_CONNTRACK {
         {{ ipv6_conntrack_action }}
     }
 }
 
 {% if first_install is not vyos_defined %}
 delete table inet vyos_global_rpfilter
 {% endif %}
 table inet vyos_global_rpfilter {
     chain PREROUTING {
         type filter hook prerouting priority -300; policy accept;
 {% 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 %}
     }
 }
 
 {% if first_install is not vyos_defined %}
 delete table ip vyos_filter
 {% endif %}
 table ip vyos_filter {
 {% if ipv4 is vyos_defined %}
 {%     if flowtable is vyos_defined %}
 {%         for name, flowtable_conf in flowtable.items() %}
 {{ offload_tmpl.flowtable(name, flowtable_conf) }}
 {%         endfor %}
 {%     endif %}
 
 {%     set ns = namespace(sets=[]) %}
 {%     if ipv4.forward is vyos_defined %}
 {%         for prior, conf in ipv4.forward.items() %}
     chain VYOS_FORWARD_{{ prior }} {
         type filter hook forward priority {{ prior }}; policy accept;
+{%             if global_options.state_policy is vyos_defined %}
+        jump VYOS_STATE_POLICY
+{%             endif %}
 {%             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 %}
         {{ conf | nft_default_rule('FWD-filter') }}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv4.input is vyos_defined %}
 {%         for prior, conf in ipv4.input.items() %}
     chain VYOS_INPUT_{{ prior }} {
         type filter hook input priority {{ prior }}; policy accept;
+{%             if global_options.state_policy is vyos_defined %}
+        jump VYOS_STATE_POLICY
+{%             endif %}
 {%             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 %}
         {{ conf | nft_default_rule('INP-filter') }}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv4.output is vyos_defined %}
 {%         for prior, conf in ipv4.output.items() %}
     chain VYOS_OUTPUT_{{ prior }} {
         type filter hook output priority {{ prior }}; policy accept;
+{%             if global_options.state_policy is vyos_defined %}
+        jump VYOS_STATE_POLICY
+{%             endif %}
 {%             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 %}
         {{ conf | nft_default_rule('OUT-filter') }}
     }
 {%         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() %}
     chain VYOS_PREROUTING_{{ prior }} {
         type filter hook prerouting priority {{ prior }}; policy accept;
 {%             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('PRE-filter') }}
     }
 {%         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 zone is vyos_defined %}
-{{ zone_tmpl.zone_chains(zone, False) }}
+{{ zone_tmpl.zone_chains(zone, False, global_options.state_policy is vyos_defined) }}
+{% endif %}
+{% if global_options.state_policy is vyos_defined %}
+    chain VYOS_STATE_POLICY {
+{%     if global_options.state_policy.established is vyos_defined %}
+        {{ global_options.state_policy.established | nft_state_policy('established') }}
+{%     endif %}
+{%     if global_options.state_policy.invalid is vyos_defined %}
+        {{ global_options.state_policy.invalid | nft_state_policy('invalid') }}
+{%     endif %}
+{%     if global_options.state_policy.related is vyos_defined %}
+        {{ global_options.state_policy.related | nft_state_policy('related') }}
+{%     endif %}
+        return
+    }
 {% endif %}
 }
 
 {% if first_install is not vyos_defined %}
 delete table ip6 vyos_filter
 {% endif %}
 table ip6 vyos_filter {
 {% if ipv6 is vyos_defined %}
 {%     if flowtable is vyos_defined %}
 {%         for name, flowtable_conf in flowtable.items() %}
 {{ offload_tmpl.flowtable(name, flowtable_conf) }}
 {%         endfor %}
 {%     endif %}
 
 {%     set ns = namespace(sets=[]) %}
 {%     if ipv6.forward is vyos_defined %}
 {%         for prior, conf in ipv6.forward.items() %}
     chain VYOS_IPV6_FORWARD_{{ prior }} {
         type filter hook forward priority {{ prior }}; policy accept;
+{%             if global_options.state_policy is vyos_defined %}
+        jump VYOS_STATE_POLICY6
+{%             endif %}
 {%             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 %}
         {{ conf | nft_default_rule('FWD-filter', ipv6=True) }}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv6.input is vyos_defined %}
 {%         for prior, conf in ipv6.input.items() %}
     chain VYOS_IPV6_INPUT_{{ prior }} {
         type filter hook input priority {{ prior }}; policy accept;
+{%             if global_options.state_policy is vyos_defined %}
+        jump VYOS_STATE_POLICY6
+{%             endif %}
 {%             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 %}
         {{ conf | nft_default_rule('INP-filter', ipv6=True) }}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv6.output is vyos_defined %}
 {%         for prior, conf in ipv6.output.items() %}
     chain VYOS_IPV6_OUTPUT_{{ prior }} {
         type filter hook output priority {{ prior }}; policy accept;
+{%             if global_options.state_policy is vyos_defined %}
+        jump VYOS_STATE_POLICY6
+{%             endif %}
 {%             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 %}
         {{ conf | nft_default_rule('OUT-filter', ipv6=True) }}
     }
 {%         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) }}
 
 {% if zone is vyos_defined %}
-{{ zone_tmpl.zone_chains(zone, True) }}
+{{ zone_tmpl.zone_chains(zone, True, global_options.state_policy is vyos_defined) }}
+{% endif %}
+{% if global_options.state_policy is vyos_defined %}
+    chain VYOS_STATE_POLICY6 {
+{%     if global_options.state_policy.established is vyos_defined %}
+        {{ global_options.state_policy.established | nft_state_policy('established') }}
+{%     endif %}
+{%     if global_options.state_policy.invalid is vyos_defined %}
+        {{ global_options.state_policy.invalid | nft_state_policy('invalid') }}
+{%     endif %}
+{%     if global_options.state_policy.related is vyos_defined %}
+        {{ global_options.state_policy.related | nft_state_policy('related') }}
+{%     endif %}
+        return
+    }
 {% endif %}
 
 }
 
 ## Bridge Firewall
 {% if first_install is not vyos_defined %}
 delete table bridge vyos_filter
 {% endif %}
 table bridge vyos_filter {
 {{ bridge_tmpl.bridge(bridge) }}
 {{ group_tmpl.groups(group, False, False) }}
 
 }
\ No newline at end of file
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index 0bb14a1b3..70afdc995 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -1,502 +1,502 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="firewall" owner="${vyos_conf_scripts_dir}/firewall.py">
     <properties>
       <priority>199</priority>
       <help>Firewall</help>
     </properties>
     <children>
       #include <include/firewall/global-options.xml.i>
       <tagNode name="flowtable">
         <properties>
           <help>Flowtable</help>
           <constraint>
             <regex>[a-zA-Z0-9][\w\-\.]*</regex>
           </constraint>
         </properties>
         <children>
           #include <include/generic-description.xml.i>
           <leafNode name="interface">
             <properties>
               <help>Interfaces to use this flowtable</help>
               <completionHelp>
                 <script>${vyos_completion_dir}/list_interfaces</script>
               </completionHelp>
               <multi/>
             </properties>
           </leafNode>
           <leafNode name="offload">
             <properties>
               <help>Offloading method</help>
               <completionHelp>
                 <list>hardware software</list>
               </completionHelp>
               <valueHelp>
                 <format>hardware</format>
                 <description>Hardware offload</description>
               </valueHelp>
               <valueHelp>
                 <format>software</format>
                 <description>Software offload</description>
               </valueHelp>
               <constraint>
                 <regex>(hardware|software)</regex>
               </constraint>
             </properties>
             <defaultValue>software</defaultValue>
           </leafNode>
         </children>
       </tagNode>
       <node name="group">
         <properties>
           <help>Firewall group</help>
         </properties>
         <children>
           <tagNode name="address-group">
             <properties>
               <help>Firewall address-group</help>
               <constraint>
                 <regex>[a-zA-Z0-9][\w\-\.]*</regex>
               </constraint>
             </properties>
             <children>
               <leafNode name="address">
                 <properties>
                   <help>Address-group member</help>
                   <valueHelp>
                     <format>ipv4</format>
                     <description>IPv4 address to match</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ipv4range</format>
                     <description>IPv4 range to match (e.g. 10.0.0.1-10.0.0.200)</description>
                   </valueHelp>
                   <constraint>
                     <validator name="ipv4-address"/>
                     <validator name="ipv4-range"/>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
               <leafNode name="include">
                 <properties>
                   <help>Include another address-group</help>
                   <completionHelp>
                     <path>firewall group address-group</path>
                   </completionHelp>
                   <multi/>
                 </properties>
               </leafNode>
               #include <include/generic-description.xml.i>
             </children>
           </tagNode>
           <tagNode name="domain-group">
             <properties>
               <help>Firewall domain-group</help>
               <constraint>
                 <regex>[a-zA-Z_][a-zA-Z0-9][\w\-\.]*</regex>
               </constraint>
               <constraintErrorMessage>Name of domain-group can only contain alpha-numeric letters, hyphen, underscores and not start with numeric</constraintErrorMessage>
             </properties>
             <children>
               <leafNode name="address">
                 <properties>
                   <help>Domain-group member</help>
                   <valueHelp>
                     <format>txt</format>
                     <description>Domain address to match</description>
                   </valueHelp>
                   <constraint>
                     <validator name="fqdn"/>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
               #include <include/generic-description.xml.i>
             </children>
           </tagNode>
           <tagNode name="interface-group">
             <properties>
               <help>Firewall interface-group</help>
               <constraint>
                 <regex>[a-zA-Z0-9][\w\-\.]*</regex>
               </constraint>
             </properties>
             <children>
               <leafNode name="interface">
                 <properties>
                   <help>Interface-group member</help>
                   <completionHelp>
                     <script>${vyos_completion_dir}/list_interfaces</script>
                   </completionHelp>
                   <multi/>
                 </properties>
               </leafNode>
               <leafNode name="include">
                 <properties>
                   <help>Include another interface-group</help>
                   <completionHelp>
                     <path>firewall group interface-group</path>
                   </completionHelp>
                   <multi/>
                 </properties>
               </leafNode>
               #include <include/generic-description.xml.i>
             </children>
           </tagNode>
           <tagNode name="ipv6-address-group">
             <properties>
               <help>Firewall ipv6-address-group</help>
               <constraint>
                 <regex>[a-zA-Z0-9][\w\-\.]*</regex>
               </constraint>
             </properties>
             <children>
               <leafNode name="address">
                 <properties>
                   <help>Address-group member</help>
                   <valueHelp>
                     <format>ipv6</format>
                     <description>IPv6 address to match</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ipv6range</format>
                     <description>IPv6 range to match (e.g. 2002::1-2002::ff)</description>
                   </valueHelp>
                   <constraint>
                     <validator name="ipv6-address"/>
                     <validator name="ipv6-range"/>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
               <leafNode name="include">
                 <properties>
                   <help>Include another ipv6-address-group</help>
                   <completionHelp>
                     <path>firewall group ipv6-address-group</path>
                   </completionHelp>
                   <multi/>
                 </properties>
               </leafNode>
               #include <include/generic-description.xml.i>
             </children>
           </tagNode>
           <tagNode name="ipv6-network-group">
             <properties>
               <help>Firewall ipv6-network-group</help>
               <constraint>
                 <regex>[a-zA-Z0-9][\w\-\.]*</regex>
               </constraint>
             </properties>
             <children>
               #include <include/generic-description.xml.i>
               <leafNode name="network">
                 <properties>
                   <help>Network-group member</help>
                   <valueHelp>
                     <format>ipv6net</format>
                     <description>IPv6 address to match</description>
                   </valueHelp>
                   <constraint>
                     <validator name="ipv6-prefix"/>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
               <leafNode name="include">
                 <properties>
                   <help>Include another ipv6-network-group</help>
                   <completionHelp>
                     <path>firewall group ipv6-network-group</path>
                   </completionHelp>
                   <multi/>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
           <tagNode name="mac-group">
             <properties>
               <help>Firewall mac-group</help>
               <constraint>
                 <regex>[a-zA-Z0-9][\w\-\.]*</regex>
               </constraint>
             </properties>
             <children>
               #include <include/generic-description.xml.i>
               <leafNode name="mac-address">
                 <properties>
                   <help>Mac-group member</help>
                   <valueHelp>
                     <format>macaddr</format>
                     <description>MAC address to match</description>
                   </valueHelp>
                   <constraint>
                     <validator name="mac-address"/>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
               <leafNode name="include">
                 <properties>
                   <help>Include another mac-group</help>
                   <completionHelp>
                     <path>firewall group mac-group</path>
                   </completionHelp>
                   <multi/>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
           <tagNode name="network-group">
             <properties>
               <help>Firewall network-group</help>
               <constraint>
                 <regex>[a-zA-Z0-9][\w\-\.]*</regex>
               </constraint>
             </properties>
             <children>
               #include <include/generic-description.xml.i>
               <leafNode name="network">
                 <properties>
                   <help>Network-group member</help>
                   <valueHelp>
                     <format>ipv4net</format>
                     <description>IPv4 Subnet to match</description>
                   </valueHelp>
                   <constraint>
                     <validator name="ipv4-prefix"/>
                   </constraint>
                   <multi/>
                 </properties>
               </leafNode>
               <leafNode name="include">
                 <properties>
                   <help>Include another network-group</help>
                   <completionHelp>
                     <path>firewall group network-group</path>
                   </completionHelp>
                   <multi/>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
           <tagNode name="port-group">
             <properties>
               <help>Firewall port-group</help>
               <constraint>
                 <regex>[a-zA-Z0-9][\w\-\.]*</regex>
               </constraint>
             </properties>
             <children>
               #include <include/generic-description.xml.i>
               <leafNode name="port">
                 <properties>
                   <help>Port-group member</help>
                   <valueHelp>
                     <format>txt</format>
                     <description>Named port (any name in /etc/services, e.g., http)</description>
                   </valueHelp>
                   <valueHelp>
                     <format>u32:1-65535</format>
                     <description>Numbered port</description>
                   </valueHelp>
                   <valueHelp>
                     <format>start-end</format>
                     <description>Numbered port range (e.g. 1001-1050)</description>
                   </valueHelp>
                   <multi/>
                   <constraint>
                     <validator name="port-range"/>
                   </constraint>
                 </properties>
               </leafNode>
               <leafNode name="include">
                 <properties>
                   <help>Include another port-group</help>
                   <completionHelp>
                     <path>firewall group port-group</path>
                   </completionHelp>
                   <multi/>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
         </children>
       </node>
       <node name="bridge">
         <properties>
           <help>Bridge firewall</help>
         </properties>
         <children>
           #include <include/firewall/bridge-hook-forward.xml.i>
           #include <include/firewall/bridge-custom-name.xml.i>
         </children>
       </node>
       <node name="ipv4">
         <properties>
           <help>IPv4 firewall</help>
         </properties>
         <children>
           #include <include/firewall/ipv4-hook-forward.xml.i>
           #include <include/firewall/ipv4-hook-input.xml.i>
           #include <include/firewall/ipv4-hook-output.xml.i>
           #include <include/firewall/ipv4-custom-name.xml.i>
         </children>
       </node>
       <node name="ipv6">
         <properties>
           <help>IPv6 firewall</help>
         </properties>
         <children>
           #include <include/firewall/ipv6-hook-forward.xml.i>
           #include <include/firewall/ipv6-hook-input.xml.i>
           #include <include/firewall/ipv6-hook-output.xml.i>
           #include <include/firewall/ipv6-custom-name.xml.i>
         </children>
       </node>
       <tagNode name="zone">
         <properties>
           <help>Zone-policy</help>
           <valueHelp>
             <format>txt</format>
             <description>Zone name</description>
           </valueHelp>
           <constraint>
             <regex>[a-zA-Z0-9][\w\-\.]*</regex>
           </constraint>
         </properties>
         <children>
           #include <include/generic-description.xml.i>
           #include <include/firewall/enable-default-log.xml.i>
           <leafNode name="default-action">
             <properties>
               <help>Default-action for traffic coming into this zone</help>
               <completionHelp>
                 <list>drop reject</list>
               </completionHelp>
               <valueHelp>
                 <format>drop</format>
                 <description>Drop silently</description>
               </valueHelp>
               <valueHelp>
                 <format>reject</format>
                 <description>Drop and notify source</description>
               </valueHelp>
               <constraint>
                 <regex>(drop|reject)</regex>
               </constraint>
             </properties>
             <defaultValue>drop</defaultValue>
           </leafNode>
           <tagNode name="from">
             <properties>
               <help>Zone from which to filter traffic</help>
               <completionHelp>
-                <path>zone-policy zone</path>
+                <path>firewall zone</path>
               </completionHelp>
             </properties>
             <children>
               <node name="firewall">
                 <properties>
                   <help>Firewall options</help>
                 </properties>
                 <children>
                   <leafNode name="ipv6-name">
                     <properties>
                       <help>IPv6 firewall ruleset</help>
                       <completionHelp>
                         <path>firewall ipv6 name</path>
                       </completionHelp>
                     </properties>
                   </leafNode>
                   <leafNode name="name">
                     <properties>
                       <help>IPv4 firewall ruleset</help>
                       <completionHelp>
                         <path>firewall ipv4 name</path>
                       </completionHelp>
                     </properties>
                   </leafNode>
                 </children>
               </node>
             </children>
           </tagNode>
           <leafNode name="interface">
             <properties>
               <help>Interface associated with zone</help>
               <valueHelp>
                 <format>txt</format>
                 <description>Interface associated with zone</description>
               </valueHelp>
               <valueHelp>
                 <format>vrf</format>
                 <description>VRF associated with zone</description>
               </valueHelp>
               <completionHelp>
                 <script>${vyos_completion_dir}/list_interfaces</script>
                 <path>vrf name</path>
               </completionHelp>
               <multi/>
             </properties>
           </leafNode>
           <node name="intra-zone-filtering">
             <properties>
               <help>Intra-zone filtering</help>
             </properties>
             <children>
               <leafNode name="action">
                 <properties>
                   <help>Action for intra-zone traffic</help>
                   <completionHelp>
                     <list>accept drop</list>
                   </completionHelp>
                   <valueHelp>
                     <format>accept</format>
                     <description>Accept traffic</description>
                   </valueHelp>
                   <valueHelp>
                     <format>drop</format>
                     <description>Drop silently</description>
                   </valueHelp>
                   <constraint>
                     <regex>(accept|drop)</regex>
                   </constraint>
                 </properties>
               </leafNode>
               <node name="firewall">
                 <properties>
                   <help>Use the specified firewall chain</help>
                 </properties>
                 <children>
                   <leafNode name="ipv6-name">
                     <properties>
                       <help>IPv6 firewall ruleset</help>
                       <completionHelp>
                         <path>firewall ipv6 name</path>
                       </completionHelp>
                     </properties>
                   </leafNode>
                   <leafNode name="name">
                     <properties>
                       <help>IPv4 firewall ruleset</help>
                       <completionHelp>
                         <path>firewall ipv4 name</path>
                       </completionHelp>
                     </properties>
                   </leafNode>
                 </children>
               </node>
             </children>
           </node>
           <leafNode name="local-zone">
             <properties>
               <help>Zone to be local-zone</help>
               <valueless/>
             </properties>
           </leafNode>
         </children>
       </tagNode>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
index a63874cb0..3026b54ab 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -1,252 +1,289 @@
 <!-- include start from firewall/global-options.xml.i -->
 <node name="global-options">
   <properties>
     <help>Global Options</help>
   </properties>
   <children>
     <leafNode name="all-ping">
       <properties>
         <help>Policy for handling of all IPv4 ICMP echo requests</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable processing of all IPv4 ICMP echo requests</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable processing of all IPv4 ICMP echo requests</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>enable</defaultValue>
     </leafNode>
     <leafNode name="broadcast-ping">
       <properties>
         <help>Policy for handling broadcast IPv4 ICMP echo and timestamp requests</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable processing of broadcast IPv4 ICMP echo/timestamp requests</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable processing of broadcast IPv4 ICMP echo/timestamp requests</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>disable</defaultValue>
     </leafNode>
     <leafNode name="ip-src-route">
       <properties>
         <help>Policy for handling IPv4 packets with source route option</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable processing of IPv4 packets with source route option</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable processing of IPv4 packets with source route option</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>disable</defaultValue>
     </leafNode>
     <leafNode name="log-martians">
       <properties>
         <help>Policy for logging IPv4 packets with invalid addresses</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable logging of IPv4 packets with invalid addresses</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable logging of Ipv4 packets with invalid addresses</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>enable</defaultValue>
     </leafNode>
     <leafNode name="receive-redirects">
       <properties>
         <help>Policy for handling received IPv4 ICMP redirect messages</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable processing of received IPv4 ICMP redirect messages</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable processing of received IPv4 ICMP redirect messages</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>disable</defaultValue>
     </leafNode>
     <leafNode name="resolver-cache">
       <properties>
         <help>Retains last successful value if domain resolution fails</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="resolver-interval">
       <properties>
         <help>Domain resolver update interval</help>
         <valueHelp>
           <format>u32:10-3600</format>
           <description>Interval (seconds)</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 10-3600"/>
         </constraint>
       </properties>
       <defaultValue>300</defaultValue>
     </leafNode>
     <leafNode name="send-redirects">
       <properties>
         <help>Policy for sending IPv4 ICMP redirect messages</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable sending IPv4 ICMP redirect messages</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable sending IPv4 ICMP redirect messages</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>enable</defaultValue>
     </leafNode>
     <leafNode name="source-validation">
       <properties>
         <help>Policy for source validation by reversed path, as specified in RFC3704</help>
         <completionHelp>
           <list>strict loose disable</list>
         </completionHelp>
         <valueHelp>
           <format>strict</format>
           <description>Enable Strict Reverse Path Forwarding as defined in RFC3704</description>
         </valueHelp>
         <valueHelp>
           <format>loose</format>
           <description>Enable Loose Reverse Path Forwarding as defined in RFC3704</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>No source validation</description>
         </valueHelp>
         <constraint>
           <regex>(strict|loose|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>disable</defaultValue>
     </leafNode>
+    <node name="state-policy">
+      <properties>
+        <help>Global firewall state-policy</help>
+      </properties>
+      <children>
+        <node name="established">
+          <properties>
+            <help>Global firewall policy for packets part of an established connection</help>
+          </properties>
+          <children>
+            #include <include/firewall/action-accept-drop-reject.xml.i>
+            #include <include/firewall/log.xml.i>
+            #include <include/firewall/rule-log-level.xml.i>
+          </children>
+        </node>
+        <node name="invalid">
+          <properties>
+            <help>Global firewall policy for packets part of an invalid connection</help>
+          </properties>
+          <children>
+            #include <include/firewall/action-accept-drop-reject.xml.i>
+            #include <include/firewall/log.xml.i>
+            #include <include/firewall/rule-log-level.xml.i>
+          </children>
+        </node>
+        <node name="related">
+          <properties>
+            <help>Global firewall policy for packets part of a related connection</help>
+          </properties>
+          <children>
+            #include <include/firewall/action-accept-drop-reject.xml.i>
+            #include <include/firewall/log.xml.i>
+            #include <include/firewall/rule-log-level.xml.i>
+          </children>
+        </node>
+      </children>
+    </node>
     <leafNode name="syn-cookies">
       <properties>
         <help>Policy for using TCP SYN cookies with IPv4</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable use of TCP SYN cookies with IPv4</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable use of TCP SYN cookies with IPv4</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>enable</defaultValue>
     </leafNode>
     <leafNode name="twa-hazards-protection">
       <properties>
         <help>RFC1337 TCP TIME-WAIT assasination hazards protection</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable RFC1337 TIME-WAIT hazards protection</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable RFC1337 TIME-WAIT hazards protection</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>disable</defaultValue>
     </leafNode>
     <leafNode name="ipv6-receive-redirects">
       <properties>
         <help>Policy for handling received ICMPv6 redirect messages</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable processing of received ICMPv6 redirect messages</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable processing of received ICMPv6 redirect messages</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>disable</defaultValue>
     </leafNode>
     <leafNode name="ipv6-src-route">
       <properties>
         <help>Policy for handling IPv6 packets with routing extension header</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable processing of IPv6 packets with routing header type 2</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable processing of IPv6 packets with routing header</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>disable</defaultValue>
     </leafNode>
   </children>
 </node>
 <!-- include end -->
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 320abe256..216f967a7 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -1,743 +1,743 @@
 # 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']
     family = 'ipv6' if ipv6 else 'ipv4'
 
     if 'enable_default_log' in fw_conf:
         action_suffix = default_action[:1].upper()
         output.append(f'log prefix "[{family}-{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']:
+    if 'log' in conf:
         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('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 85b2fbbc7..980b50556 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -1,731 +1,758 @@
 #!/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', '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'])
         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'])
         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', 'enable-default-log'])
         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'])
         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', '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', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'enable-default-log'])
         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', '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 accept;'],
             ['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'],
             ['log prefix "[ipv4-FWD-filter-default-D]"','FWD-filter default-action drop', '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'],
             ['INP-filter default-action accept', 'accept'],
             ['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 "[ipv4-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'])
         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', 'mark', '1010'])
         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', 'mark', '!98765'])
         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 accept;'],
             ['ip saddr 198.51.100.1', 'meta mark 0x000003f2', f'jump NAME_{name}'],
             ['FWD-filter default-action drop', 'drop'],
             ['chain VYOS_INPUT_filter'],
             ['type filter hook input priority filter; policy accept;'],
             ['meta mark != 0x000181cd', 'meta l4proto tcp','queue to 3'],
             ['meta l4proto udp','queue flags bypass,fanout to 0-15'],
             ['INP-filter default-action accept', 'accept'],
             [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 "[ipv4-{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', 'global-options', 'state-policy', 'established', 'action', 'accept'])
+        self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept'])
+        self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop'])
+
         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'])
         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', 'enable-default-log'])
         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', 'name', interface])
 
         self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'enable-default-log'])
         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', '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', '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'],
             ['log prefix "[ipv6-FWD-filter-default-A]"','FWD-filter default-action accept', 'accept'],
             ['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'],
             ['INP-filter default-action accept', 'accept'],
             ['chain VYOS_IPV6_OUTPUT_filter'],
             ['type filter hook output priority filter; policy accept;'],
             ['meta l4proto gre', f'oifname "{interface}"', 'return'],
             ['log prefix "[ipv6-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'],
             [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 "[ipv6-{name}-default-D]"', 'drop']
+            [f'"{name} default-action drop"', f'log prefix "[ipv6-{name}-default-D]"', 'drop'],
+            ['jump VYOS_STATE_POLICY6'],
+            ['chain VYOS_STATE_POLICY6'],
+            ['ct state established', 'accept'],
+            ['ct state invalid', 'drop'],
+            ['ct state related', 'accept']
         ]
 
         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 "[ipv6-{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', 'global-options', 'state-policy', 'established', 'action', 'accept'])
+        self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept'])
+        self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop'])
+
         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'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'related'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'state', 'invalid'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'state', 'new'])
         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'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'established'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'state', 'related'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'conntrack-helper', 'ftp'])
         self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '5', 'conntrack-helper', 'pptp'])
 
         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'],
             ['ct state related', 'ct helper { "ftp", "pptp" }', 'accept'],
-            ['drop', f'comment "{name} default-action drop"']
+            ['drop', f'comment "{name} default-action drop"'],
+            ['jump VYOS_STATE_POLICY'],
+            ['chain VYOS_STATE_POLICY'],
+            ['ct state established', 'accept'],
+            ['ct state invalid', 'drop'],
+            ['ct state related', 'accept']
         ]
 
         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')
 
     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', 'name', interface_in])
         self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log'])
         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_commit()
 
         nftables_strict_search = [
             ['fib saddr . iif oif 0', 'drop']
         ]
 
         self.verify_nftables(nftables_strict_search, 'inet vyos_global_rpfilter')
 
         # Loose
         self.cli_set(['firewall', 'global-options', 'source-validation', 'loose'])
         self.cli_commit()
 
         nftables_loose_search = [
             ['fib saddr oif 0', 'drop']
         ]
 
         self.verify_nftables(nftables_loose_search, 'inet 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)
 
 ### Zone
     def test_zone_basic(self):
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop'])
         self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0'])
         self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest'])
         self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone'])
         self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest'])
+        self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept'])
+        self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'log'])
+        self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept'])
+        self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop'])
 
         self.cli_commit()
 
         nftables_search = [
             ['chain VYOS_ZONE_FORWARD'],
             ['type filter hook forward priority filter + 1'],
             ['chain VYOS_ZONE_OUTPUT'],
             ['type filter hook output priority filter + 1'],
             ['chain VYOS_ZONE_LOCAL'],
             ['type filter hook input priority filter + 1'],
             ['chain VZONE_smoketest-eth0'],
             ['chain VZONE_smoketest-local_IN'],
             ['chain VZONE_smoketest-local_OUT'],
             ['oifname "eth0"', 'jump VZONE_smoketest-eth0'],
             ['jump VZONE_smoketest-local_IN'],
             ['jump VZONE_smoketest-local_OUT'],
             ['iifname "eth0"', 'jump NAME_smoketest'],
-            ['oifname "eth0"', 'jump NAME_smoketest']
+            ['oifname "eth0"', 'jump NAME_smoketest'],
+            ['jump VYOS_STATE_POLICY'],
+            ['chain VYOS_STATE_POLICY'],
+            ['ct state established', 'log prefix "[STATE-POLICY-EST-A]"', 'accept'],
+            ['ct state invalid', 'drop'],
+            ['ct state related', 'accept']
         ]
 
         nftables_output = cmd('sudo nft list table ip vyos_filter')
 
         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(matched)
 
     def test_flow_offload(self):
         self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0'])
         self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware'])
 
         # QEMU virtual NIC does not support hw-tc-offload
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software'])
 
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'offload'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'established'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'state', 'related'])
 
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'action', 'offload'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'offload-target', 'smoketest'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'established'])
         self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '1', 'state', 'related'])
 
         self.cli_commit()
 
         nftables_search = [
             ['flowtable VYOS_FLOWTABLE_smoketest'],
             ['hook ingress priority filter'],
             ['devices = { eth0 }'],
             ['ct state { established, related }', 'meta l4proto { tcp, udp }', 'flow add @VYOS_FLOWTABLE_smoketest'],
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
         self.verify_nftables(nftables_search, 'ip6 vyos_filter')
 
         # Check conntrack
         #self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
         #self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
 
     def test_zone_flow_offload(self):
         self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0'])
         self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware'])
 
         # QEMU virtual NIC does not support hw-tc-offload
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software'])
 
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'offload'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest'])
 
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'action', 'offload'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest'])
 
         self.cli_commit()
 
         nftables_search = [
             ['chain NAME_smoketest'],
             ['flow add @VYOS_FLOWTABLE_smoketest']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
 
         nftables_search = [
             ['chain NAME6_smoketest'],
             ['flow add @VYOS_FLOWTABLE_smoketest']
         ]
 
         self.verify_nftables(nftables_search, 'ip6 vyos_filter')
 
         # Check conntrack
         #self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
         #self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/migration-scripts/firewall/10-to-11 b/src/migration-scripts/firewall/10-to-11
index b739fb139..e14ea0e51 100755
--- a/src/migration-scripts/firewall/10-to-11
+++ b/src/migration-scripts/firewall/10-to-11
@@ -1,189 +1,181 @@
 #!/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/>.
 
 # T5160: Firewall re-writing
 
 #  cli changes from:       
 #  set firewall name <name> ...
 #  set firewall ipv6-name <name> ...
 #  To
 #  set firewall ipv4 name <name> 
 #  set firewall ipv6 name <name> 
 
 ## Also from 'firewall interface' removed.
 ## in and out:
     # set firewall interface <iface> [in|out] [name | ipv6-name] <name>
     # To
     # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> [inbound-interface | outboubd-interface] interface-name <iface>
     # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> action jump
     # set firewall [ipv4 | ipv6] forward filter rule <5,10,15,...> jump-target <name>
 ## local:
     # set firewall interface <iface> local [name | ipv6-name] <name>
     # To
     # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> inbound-interface interface-name <iface>
     # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> action jump
     # set firewall [ipv4 | ipv6] input filter rule <5,10,15,...> jump-target <name>
 
 import re
 
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
 from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['firewall']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
 ### Migration of state policies
 if config.exists(base + ['state-policy']):
-    for family in ['ipv4', 'ipv6']:
-        for hook in ['forward', 'input', 'output']:
-            for priority in ['filter']:
-                # Add default-action== accept for compatibility reasons:
-                config.set(base + [family, hook, priority, 'default-action'], value='accept')
-                position = 1
-                for state in config.list_nodes(base + ['state-policy']):
-                    action = config.return_value(base + ['state-policy', state, 'action'])
-                    config.set(base + [family, hook, priority, 'rule'])
-                    config.set_tag(base + [family, hook, priority, 'rule'])
-                    config.set(base + [family, hook, priority, 'rule', position, 'state', state], value='enable')
-                    config.set(base + [family, hook, priority, 'rule', position, 'action'], value=action)
-                    position = position + 1
+    for state in config.list_nodes(base + ['state-policy']):
+        action = config.return_value(base + ['state-policy', state, 'action'])
+        config.set(base + ['global-options', 'state-policy', state, 'action'], value=action)
+        if config.exists(base + ['state-policy', state, 'log']):
+            config.set(base + ['global-options', 'state-policy', state, 'log'], value='enable')
     config.delete(base + ['state-policy'])
 
 ## migration of global options:
 for option in ['all-ping', 'broadcast-ping', 'config-trap', 'ip-src-route', 'ipv6-receive-redirects', 'ipv6-src-route', 'log-martians',
                 'receive-redirects', 'resolver-cache', 'resolver-internal', 'send-redirects', 'source-validation', 'syn-cookies', 'twa-hazards-protection']:
     if config.exists(base + [option]):
         if option != 'config-trap':
             val = config.return_value(base + [option])
             config.set(base + ['global-options', option], value=val)
         config.delete(base + [option])
 
 ### Migration of firewall name and ipv6-name
 if config.exists(base + ['name']):
     config.set(['firewall', 'ipv4', 'name'])
     config.set_tag(['firewall', 'ipv4', 'name'])
 
     for ipv4name in config.list_nodes(base + ['name']):
         config.copy(base + ['name', ipv4name], base + ['ipv4', 'name', ipv4name])
     config.delete(base + ['name'])
 
 if config.exists(base + ['ipv6-name']):
     config.set(['firewall', 'ipv6', 'name'])
     config.set_tag(['firewall', 'ipv6', 'name'])
 
     for ipv6name in config.list_nodes(base + ['ipv6-name']):
         config.copy(base + ['ipv6-name', ipv6name], base + ['ipv6', 'name', ipv6name])
     config.delete(base + ['ipv6-name'])
 
 ### Migration of firewall interface
 if config.exists(base + ['interface']):
     fwd_ipv4_rule = 5
     inp_ipv4_rule = 5
     fwd_ipv6_rule = 5
     inp_ipv6_rule = 5
     for iface in config.list_nodes(base + ['interface']):
         for direction in ['in', 'out', 'local']:
             if config.exists(base + ['interface', iface, direction]):
                 if config.exists(base + ['interface', iface, direction, 'name']):
                     target = config.return_value(base + ['interface', iface, direction, 'name'])
                     if direction == 'in':
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv4', 'forward', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [fwd_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump')
                         config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target)
                         fwd_ipv4_rule = fwd_ipv4_rule + 5
                     elif direction == 'out':
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv4', 'forward', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv4', 'forward', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [fwd_ipv4_rule, 'outbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [fwd_ipv4_rule, 'action'], value='jump')
                         config.set(new_base + [fwd_ipv4_rule, 'jump-target'], value=target)
                         fwd_ipv4_rule = fwd_ipv4_rule + 5
                     else:
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv4', 'input', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv4', 'input', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [inp_ipv4_rule, 'inbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [inp_ipv4_rule, 'action'], value='jump')
                         config.set(new_base + [inp_ipv4_rule, 'jump-target'], value=target)
                         inp_ipv4_rule = inp_ipv4_rule + 5
 
                 if config.exists(base + ['interface', iface, direction, 'ipv6-name']):
                     target = config.return_value(base + ['interface', iface, direction, 'ipv6-name'])
                     if direction == 'in':
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv6', 'forward', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [fwd_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump')
                         config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target)
                         fwd_ipv6_rule = fwd_ipv6_rule + 5
                     elif direction == 'out':
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv6', 'forward', 'filter', 'default-action'], value='accept')
                         new_base = base + ['ipv6', 'forward', 'filter', 'rule']
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [fwd_ipv6_rule, 'outbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [fwd_ipv6_rule, 'action'], value='jump')
                         config.set(new_base + [fwd_ipv6_rule, 'jump-target'], value=target)
                         fwd_ipv6_rule = fwd_ipv6_rule + 5
                     else:
                         new_base = base + ['ipv6', 'input', 'filter', 'rule']
                         # Add default-action== accept for compatibility reasons:
                         config.set(base + ['ipv6', 'input', 'filter', 'default-action'], value='accept')
                         config.set(new_base)
                         config.set_tag(new_base)
                         config.set(new_base + [inp_ipv6_rule, 'inbound-interface', 'interface-name'], value=iface)
                         config.set(new_base + [inp_ipv6_rule, 'action'], value='jump')
                         config.set(new_base + [inp_ipv6_rule, 'jump-target'], value=target)
                         inp_ipv6_rule = inp_ipv6_rule + 5
 
     config.delete(base + ['interface'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
\ No newline at end of file
diff --git a/src/migration-scripts/firewall/12-to-13 b/src/migration-scripts/firewall/12-to-13
index 4eaae779b..8396dd9d1 100755
--- a/src/migration-scripts/firewall/12-to-13
+++ b/src/migration-scripts/firewall/12-to-13
@@ -1,83 +1,92 @@
 #!/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/>.
 
 # T5729: Switch to valueless whenever is possible.
 # From
     # set firewall ... rule <rule> log enable
     # set firewall ... rule <rule> state <state> enable
     # set firewall ... rule <rule> log disable
     # set firewall ... rule <rule> state <state> disable
 # To
     # set firewall ... rule <rule> log
     # set firewall ... rule <rule> state <state>
     # Remove command if log=disable or <state>=disable
 
 import re
 
 from sys import argv
 from sys import exit
 
 from vyos.configtree import ConfigTree
 from vyos.ifconfig import Section
 
 if len(argv) < 2:
     print("Must specify file name!")
     exit(1)
 
 file_name = argv[1]
 
 with open(file_name, 'r') as f:
     config_file = f.read()
 
 base = ['firewall']
 config = ConfigTree(config_file)
 
 if not config.exists(base):
     # Nothing to do
     exit(0)
 
+# State Policy logs:
+if config.exists(base + ['global-options', 'state-policy']):
+    for state in config.list_nodes(base + ['global-options', 'state-policy']):
+        if config.exists(base + ['global-options', 'state-policy', state, 'log']):
+            log_value = config.return_value(base + ['global-options', 'state-policy', state, 'log'])
+            config.delete(base + ['global-options', 'state-policy', state, 'log'])
+            if log_value == 'enable':
+                config.set(base + ['global-options', 'state-policy', state, 'log'])
+
 for family in ['ipv4', 'ipv6', 'bridge']:
     if config.exists(base + [family]):
         for hook in ['forward', 'input', 'output', 'name']:
             if config.exists(base + [family, hook]):
                 for priority in config.list_nodes(base + [family, hook]):
                     if config.exists(base + [family, hook, priority, 'rule']):
                         for rule in config.list_nodes(base + [family, hook, priority, 'rule']):
                             # Log
                             if config.exists(base + [family, hook, priority, 'rule', rule, 'log']):
                                 log_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'log'])
                                 config.delete(base + [family, hook, priority, 'rule', rule, 'log'])
                                 if log_value == 'enable':
                                     config.set(base + [family, hook, priority, 'rule', rule, 'log'])
                             # State
                             if config.exists(base + [family, hook, priority, 'rule', rule, 'state']):
                                 flag_enable = 'False'
                                 for state in ['established', 'invalid', 'new', 'related']:
                                     if config.exists(base + [family, hook, priority, 'rule', rule, 'state', state]):
                                         state_value = config.return_value(base + [family, hook, priority, 'rule', rule, 'state', state])
                                         config.delete(base + [family, hook, priority, 'rule', rule, 'state', state])
                                         if state_value == 'enable':
                                             config.set(base + [family, hook, priority, 'rule', rule, 'state'], value=state, replace=False)
                                             flag_enable = 'True'
                                 if flag_enable == 'False':
                                     config.delete(base + [family, hook, priority, 'rule', rule, 'state'])
 
 try:
     with open(file_name, 'w') as f:
         f.write(config.to_string())
 except OSError as e:
     print("Failed to save the modified config: {}".format(e))
     exit(1)
\ No newline at end of file