diff --git a/data/templates/firewall/nftables-bridge.j2 b/data/templates/firewall/nftables-bridge.j2
index dec027bf9..1975fb9b0 100644
--- a/data/templates/firewall/nftables-bridge.j2
+++ b/data/templates/firewall/nftables-bridge.j2
@@ -1,35 +1,120 @@
+{% import 'firewall/nftables-defines.j2' as group_tmpl %}
 {% macro bridge(bridge) %}
 {% set ns = namespace(sets=[]) %}
 {% if bridge.forward is vyos_defined %}
 {%     for prior, conf in bridge.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, 'bri') }}
 {%                 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', 'bri') }}
     }
 {%     endfor %}
 {% endif %}
 
+{% if bridge.input is vyos_defined %}
+{%     for prior, conf in bridge.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, 'bri') }}
+{%                 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', 'bri') }}
+    }
+{%     endfor %}
+{% endif %}
+
+{% if bridge.output is vyos_defined %}
+{%     for prior, conf in bridge.output.items() %}
+    chain VYOS_OUTUT_{{ 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, 'bri') }}
+{%                 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', 'bri') }}
+    }
+{%     endfor %}
+{% endif %}
+
 {% if bridge.name is vyos_defined %}
 {%     for name_text, conf in bridge.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, 'bri') }}
 {%                 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, 'bri') }}
     }
 {%     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 %}
+
+{{ group_tmpl.groups(group, False, True) }}
+{{ group_tmpl.groups(group, True, True) }}
+
+{% 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 %}
+
 {% endmacro %}
diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2
index 8a75ab2d6..fa6cd74c0 100644
--- a/data/templates/firewall/nftables-defines.j2
+++ b/data/templates/firewall/nftables-defines.j2
@@ -1,123 +1,123 @@
 {% macro groups(group, is_ipv6, is_l3) %}
 {% if group is vyos_defined %}
 {%     set ip_type = 'ipv6_addr' if is_ipv6 else 'ipv4_addr' %}
-{%     if group.address_group is vyos_defined and not is_ipv6 and is_l3 %}
+{%     if group.address_group is vyos_defined and not is_ipv6 %}
 {%         for group_name, group_conf in group.address_group.items() %}
 {%             set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
     set A_{{ group_name }} {
         type {{ ip_type }}
         flags interval
         auto-merge
 {%             if group_conf.address is vyos_defined or includes %}
         elements = { {{ group_conf.address | nft_nested_group(includes, group.address_group, 'address') | join(",") }} }
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
-{%     if group.ipv6_address_group is vyos_defined and is_ipv6 and is_l3 %}
+{%     if group.ipv6_address_group is vyos_defined and is_ipv6 %}
 {%         for group_name, group_conf in group.ipv6_address_group.items() %}
 {%             set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
     set A6_{{ group_name }} {
         type {{ ip_type }}
         flags interval
         auto-merge
 {%             if group_conf.address is vyos_defined or includes %}
         elements = { {{ group_conf.address | nft_nested_group(includes, group.ipv6_address_group, 'address') | join(",") }} }
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
 {%     if group.domain_group is vyos_defined and is_l3 %}
 {%         for name, name_config in group.domain_group.items() %}
     set D_{{ name }} {
         type {{ ip_type }}
         flags interval
     }
 {%         endfor %}
 {%     endif %}
 {%     if group.mac_group is vyos_defined %}
 {%         for group_name, group_conf in group.mac_group.items() %}
 {%             set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
     set M_{{ group_name }} {
         type ether_addr
 {%             if group_conf.mac_address is vyos_defined or includes %}
         elements = { {{ group_conf.mac_address | nft_nested_group(includes, group.mac_group, 'mac_address') | join(",") }} }
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
-{%     if group.network_group is vyos_defined and not is_ipv6 and is_l3 %}
+{%     if group.network_group is vyos_defined and not is_ipv6 %}
 {%         for group_name, group_conf in group.network_group.items() %}
 {%             set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
     set N_{{ group_name }} {
         type {{ ip_type }}
         flags interval
         auto-merge
 {%             if group_conf.network is vyos_defined or includes %}
         elements = { {{ group_conf.network | nft_nested_group(includes, group.network_group, 'network') | join(",") }} }
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
-{%     if group.ipv6_network_group is vyos_defined and is_ipv6 and is_l3 %}
+{%     if group.ipv6_network_group is vyos_defined and is_ipv6 %}
 {%         for group_name, group_conf in group.ipv6_network_group.items() %}
 {%             set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
     set N6_{{ group_name }} {
         type {{ ip_type }}
         flags interval
         auto-merge
 {%             if group_conf.network is vyos_defined or includes %}
         elements = { {{ group_conf.network | nft_nested_group(includes, group.ipv6_network_group, 'network') | join(",") }} }
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
-{%     if group.port_group is vyos_defined and is_l3 %}
+{%     if group.port_group is vyos_defined %}
 {%         for group_name, group_conf in group.port_group.items() %}
 {%             set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
     set P_{{ group_name }} {
         type inet_service
         flags interval
         auto-merge
 {%             if group_conf.port is vyos_defined or includes %}
         elements = { {{ group_conf.port | nft_nested_group(includes, group.port_group, 'port') | join(",") }} }
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
 {%     if group.interface_group is vyos_defined %}
 {%         for group_name, group_conf in group.interface_group.items() %}
 {%             set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
     set I_{{ group_name }} {
         type ifname
         flags interval
         auto-merge
 {%             if group_conf.interface is vyos_defined or includes %}
         elements = { {{ group_conf.interface | nft_nested_group(includes, group.interface_group, 'interface') | join(",") }} }
 {%             endif %}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if group.dynamic_group is vyos_defined %}
 {%         if group.dynamic_group.address_group is vyos_defined and not is_ipv6 and is_l3 %}
 {%             for group_name, group_conf in group.dynamic_group.address_group.items() %}
     set DA_{{ group_name }} {
         type {{ ip_type }}
         flags dynamic, timeout
     }
 {%             endfor %}
 {%         endif %}
 
 {%         if group.dynamic_group.ipv6_address_group is vyos_defined and is_ipv6 and is_l3 %}
 {%             for group_name, group_conf in group.dynamic_group.ipv6_address_group.items() %}
     set DA6_{{ group_name }} {
         type {{ ip_type }}
         flags dynamic, timeout
     }
 {%             endfor %}
 {%         endif %}
 {%     endif %}
 
 {% endif %}
 {% endmacro %}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 68a3bfd87..82dcefac0 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -1,345 +1,442 @@
 #!/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 vyos_global_rpfilter
 flush chain ip6 raw vyos_global_rpfilter
 
 table raw {
     chain vyos_global_rpfilter {
 {% if global_options.source_validation is vyos_defined('loose') %}
         fib saddr oif 0 counter drop
 {% elif global_options.source_validation is vyos_defined('strict') %}
         fib saddr . iif oif 0 counter drop
 {% endif %}
         return
     }
 }
 
 table ip6 raw {
     chain vyos_global_rpfilter {
 {% if global_options.ipv6_source_validation is vyos_defined('loose') %}
         fib saddr oif 0 counter drop
 {% elif global_options.ipv6_source_validation is vyos_defined('strict') %}
         fib saddr . iif oif 0 counter drop
 {% endif %}
         return
     }
 }
 
 {% if first_install is not vyos_defined %}
 delete table ip vyos_filter
 {% endif %}
 table ip vyos_filter {
 {% if ipv4 is vyos_defined %}
 {%     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-' + prior, 'ipv4') }}
     }
 {%         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-' + prior, 'ipv4') }}
     }
 {%         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 and prior == 'filter' %}
         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-' + prior, 'ipv4') }}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     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-' + prior, 'ipv4') }}
     }
 {%         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.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, 'ipv4') }}
     }
 {%         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, 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-' + prior, 'ipv6') }}
     }
 {%         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-' + prior, 'ipv6') }}
     }
 {%         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 and prior == 'filter' %}
         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-' + prior, 'ipv6') }}
     }
 {%         endfor %}
 {%     endif %}
 
 {%     if ipv6.prerouting is vyos_defined %}
 {%         for prior, conf in ipv6.prerouting.items() %}
     chain VYOS_IPV6_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, 'ip6') }}
 {%                     if rule_conf.recent is vyos_defined %}
 {%                         set ns.sets = ns.sets + ['PRE_' + prior + '_' + rule_id] %}
 {%                     endif %}
 {%                 endfor %}
 {%             endif %}
         {{ conf | nft_default_rule('PRE-' + prior, 'ipv6') }}
     }
 {%         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') }}
     }
 {%         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, 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) }}
+{% if bridge is vyos_defined %}
+{%     if bridge.forward is vyos_defined %}
+{%         for prior, conf in bridge.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, 'bri') }}
+{%                 endfor %}
+{%             endif %}
+        {{ conf | nft_default_rule('FWD-' + prior, 'bri') }}
+    }
+{%         endfor %}
+{%     endif %}
+
+{%     if bridge.input is vyos_defined %}
+{%         for prior, conf in bridge.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, 'bri') }}
+{%                 endfor %}
+{%             endif %}
+        {{ conf | nft_default_rule('INP-' + prior, 'bri') }}
+    }
+{%         endfor %}
+{%     endif %}
+
+{%     if bridge.output is vyos_defined %}
+{%         for prior, conf in bridge.output.items() %}
+    chain VYOS_OUTUT_{{ 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, 'bri') }}
+{%                 endfor %}
+{%             endif %}
+        {{ conf | nft_default_rule('OUT-' + prior, 'bri') }}
+    }
+{%         endfor %}
+{%     endif %}
+
+{%     if bridge.prerouting is vyos_defined %}
+{%         for prior, conf in bridge.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, 'bri') }}
+{%                 endfor %}
+{%             endif %}
+        {{ conf | nft_default_rule('PRE-' + prior, 'bri') }}
+    }
+{%         endfor %}
+{%     endif %}
+
+{%     if bridge.name is vyos_defined %}
+{%         for name_text, conf in bridge.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, 'bri') }}
+{%                     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, 'bri') }}
+    }
+{%         endfor %}
+{%     endif %}
+
+{% endif %}
 {{ group_tmpl.groups(group, False, False) }}
+{{ group_tmpl.groups(group, True, False) }}
 
-}
+{% 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 %}
+}
\ No newline at end of file
diff --git a/data/templates/firewall/sysctl-firewall.conf.j2 b/data/templates/firewall/sysctl-firewall.conf.j2
index b9c3311e2..6c33ffdc8 100644
--- a/data/templates/firewall/sysctl-firewall.conf.j2
+++ b/data/templates/firewall/sysctl-firewall.conf.j2
@@ -1,28 +1,36 @@
 # Autogenerated by firewall.py
 
 # gloabl options
 net.ipv4.icmp_echo_ignore_all = {{ 0 if global_options.all_ping == 'enable' else 1 }}
 net.ipv4.icmp_echo_ignore_broadcasts = {{ 0 if global_options.broadcast_ping == 'enable' else 1 }}
 net.ipv4.conf.all.bc_forwarding = {{ 1 if global_options.directed_broadcast == 'enable' else 0 }}
 net.ipv4.conf.*.accept_source_route = {{ 1 if global_options.ip_src_route == 'enable' else 0 }}
 net.ipv6.conf.*.accept_redirects = {{ 1 if global_options.ipv6_receive_redirects == 'enable' else 0 }}
 net.ipv6.conf.*.accept_source_route = {{ 0 if global_options.ipv6_src_route == 'enable' else -1 }}
 net.ipv4.conf.all.log_martians = {{ 1 if global_options.log_martians == 'enable' else 0 }}
 net.ipv4.conf.*.accept_redirects = {{ 1 if global_options.receive_redirects == 'enable' else 0 }}
 net.ipv4.conf.*.send_redirects = {{ 1 if global_options.send_redirects == 'enable' else 0 }}
 net.ipv4.tcp_syncookies = {{ 1 if global_options.syn_cookies == 'enable' else 0 }}
 net.ipv4.tcp_rfc1337 = {{ 1 if global_options.twa_hazards_protection == 'enable' else 0 }}
 
+{% if global_options.apply_to_bridged_traffic is vyos_defined %}
+net.bridge.bridge-nf-call-iptables = {{ 1 if global_options.apply_to_bridged_traffic.ipv4 is vyos_defined else 0 }}
+net.bridge.bridge-nf-call-ip6tables = {{ 1 if global_options.apply_to_bridged_traffic.ipv6 is vyos_defined else 0 }}
+{% else %}
+net.bridge.bridge-nf-call-iptables = 0
+net.bridge.bridge-nf-call-ip6tables = 0
+{% endif %}
+
 ## Timeout values:
 net.netfilter.nf_conntrack_icmp_timeout = {{ global_options.timeout.icmp }}
 net.netfilter.nf_conntrack_generic_timeout = {{ global_options.timeout.other }}
 net.netfilter.nf_conntrack_tcp_timeout_close_wait = {{ global_options.timeout.tcp.close_wait }}
 net.netfilter.nf_conntrack_tcp_timeout_close = {{ global_options.timeout.tcp.close }}
 net.netfilter.nf_conntrack_tcp_timeout_established = {{ global_options.timeout.tcp.established }}
 net.netfilter.nf_conntrack_tcp_timeout_fin_wait = {{ global_options.timeout.tcp.fin_wait }}
 net.netfilter.nf_conntrack_tcp_timeout_last_ack = {{ global_options.timeout.tcp.last_ack }}
 net.netfilter.nf_conntrack_tcp_timeout_syn_recv = {{ global_options.timeout.tcp.syn_recv }}
 net.netfilter.nf_conntrack_tcp_timeout_syn_sent = {{ global_options.timeout.tcp.syn_sent }}
 net.netfilter.nf_conntrack_tcp_timeout_time_wait = {{ global_options.timeout.tcp.time_wait }}
 net.netfilter.nf_conntrack_udp_timeout = {{ global_options.timeout.udp.other }}
 net.netfilter.nf_conntrack_udp_timeout_stream = {{ global_options.timeout.udp.stream }}
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index dc4625af0..816dd1855 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -1,541 +1,544 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="firewall" owner="${vyos_conf_scripts_dir}/firewall.py">
     <properties>
       <priority>319</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>
                 #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
               </constraint>
               <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
             </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 alphanumeric 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>
           <node name="dynamic-group">
             <properties>
               <help>Firewall dynamic group</help>
             </properties>
             <children>
               <tagNode name="address-group">
                 <properties>
                   <help>Firewall dynamic address group</help>
                   <constraint>
                     #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
                   </constraint>
                   <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
                 </properties>
                 <children>
                   #include <include/generic-description.xml.i>
                 </children>
               </tagNode>
               <tagNode name="ipv6-address-group">
                 <properties>
                   <help>Firewall dynamic IPv6 address group</help>
                   <constraint>
                     <regex>[a-zA-Z0-9][\w\-\.]*</regex>
                   </constraint>
                 </properties>
                 <children>
                   #include <include/generic-description.xml.i>
                 </children>
               </tagNode>
             </children>
           </node>
           <tagNode name="interface-group">
             <properties>
               <help>Firewall interface-group</help>
               <constraint>
                 #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
               </constraint>
               <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
             </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>
                 #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
               </constraint>
               <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
             </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>
                 #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
               </constraint>
               <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
             </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>
                 #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
               </constraint>
               <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
             </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>
                 #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
               </constraint>
               <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
             </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>
                 #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i>
               </constraint>
               <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
             </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-hook-input.xml.i>
+          #include <include/firewall/bridge-hook-output.xml.i>
+          #include <include/firewall/bridge-hook-prerouting.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-hook-prerouting.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-hook-prerouting.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/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>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/address-inet.xml.i b/interface-definitions/include/firewall/address-inet.xml.i
new file mode 100644
index 000000000..02ed8f6e4
--- /dev/null
+++ b/interface-definitions/include/firewall/address-inet.xml.i
@@ -0,0 +1,63 @@
+<!-- include start from firewall/address-inet.xml.i -->
+<leafNode name="address">
+  <properties>
+    <help>IP address, subnet, or range</help>
+    <valueHelp>
+      <format>ipv4</format>
+      <description>IPv4 address to match</description>
+    </valueHelp>
+    <valueHelp>
+      <format>ipv4net</format>
+      <description>IPv4 prefix to match</description>
+    </valueHelp>
+    <valueHelp>
+      <format>ipv4range</format>
+      <description>IPv4 address range to match</description>
+    </valueHelp>
+    <valueHelp>
+      <format>!ipv4</format>
+      <description>Match everything except the specified address</description>
+    </valueHelp>
+    <valueHelp>
+      <format>!ipv4net</format>
+      <description>Match everything except the specified prefix</description>
+    </valueHelp>
+    <valueHelp>
+      <format>!ipv4range</format>
+      <description>Match everything except the specified range</description>
+    </valueHelp>
+    <valueHelp>
+      <format>ipv6net</format>
+      <description>Subnet to match</description>
+    </valueHelp>
+    <valueHelp>
+      <format>ipv6range</format>
+      <description>IP range to match</description>
+    </valueHelp>
+    <valueHelp>
+      <format>!ipv6</format>
+      <description>Match everything except the specified address</description>
+    </valueHelp>
+    <valueHelp>
+      <format>!ipv6net</format>
+      <description>Match everything except the specified prefix</description>
+    </valueHelp>
+    <valueHelp>
+      <format>!ipv6range</format>
+      <description>Match everything except the specified range</description>
+    </valueHelp>
+    <constraint>
+      <validator name="ipv4-address"/>
+      <validator name="ipv4-prefix"/>
+      <validator name="ipv4-range"/>
+      <validator name="ipv4-address-exclude"/>
+      <validator name="ipv4-prefix-exclude"/>
+      <validator name="ipv4-range-exclude"/>
+      <validator name="ipv6"/>
+      <validator name="ipv6-exclude"/>
+      <validator name="ipv6-range"/>
+      <validator name="ipv6-range-exclude"/>
+    </constraint>
+  </properties>
+</leafNode>
+<!-- include end -->
\ No newline at end of file
diff --git a/interface-definitions/include/firewall/address-mask-inet.xml.i b/interface-definitions/include/firewall/address-mask-inet.xml.i
new file mode 100644
index 000000000..e2a5927ab
--- /dev/null
+++ b/interface-definitions/include/firewall/address-mask-inet.xml.i
@@ -0,0 +1,19 @@
+<!-- include start from firewall/address-mask-inet.xml.i -->
+<leafNode name="address-mask">
+  <properties>
+    <help>IP mask</help>
+    <valueHelp>
+      <format>ipv4</format>
+      <description>IPv4 mask to apply</description>
+    </valueHelp>
+     <valueHelp>
+      <format>ipv6</format>
+      <description>IP mask to apply</description>
+    </valueHelp>
+    <constraint>
+      <validator name="ipv4-address"/>
+      <validator name="ipv6"/>
+    </constraint>
+  </properties>
+</leafNode>
+<!-- include end -->
\ No newline at end of file
diff --git a/interface-definitions/include/firewall/bridge-custom-name.xml.i b/interface-definitions/include/firewall/bridge-custom-name.xml.i
index 654493c0e..9a2a829d0 100644
--- a/interface-definitions/include/firewall/bridge-custom-name.xml.i
+++ b/interface-definitions/include/firewall/bridge-custom-name.xml.i
@@ -1,39 +1,45 @@
 <!-- include start from firewall/bridge-custom-name.xml.i -->
 <tagNode name="name">
   <properties>
     <help>Bridge custom firewall</help>
     <constraint>
       <regex>[a-zA-Z0-9][\w\-\.]*</regex>
     </constraint>
   </properties>
   <children>
     #include <include/firewall/default-action.xml.i>
     #include <include/firewall/default-log.xml.i>
     #include <include/generic-description.xml.i>
     <leafNode name="default-jump-target">
       <properties>
         <help>Set jump target. Action jump must be defined in default-action to use this setting</help>
         <completionHelp>
           <path>firewall bridge name</path>
         </completionHelp>
       </properties>
     </leafNode>
     <tagNode name="rule">
       <properties>
         <help>Bridge Firewall forward filter rule number</help>
         <valueHelp>
           <format>u32:1-999999</format>
           <description>Number for this firewall rule</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 1-999999"/>
         </constraint>
         <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
       </properties>
       <children>
         #include <include/firewall/common-rule-bridge.xml.i>
+        #include <include/firewall/action-l2.xml.i>
+        #include <include/firewall/connection-mark.xml.i>
+        #include <include/firewall/connection-status.xml.i>
+        #include <include/firewall/state.xml.i>
+        #include <include/firewall/inbound-interface.xml.i>
+        #include <include/firewall/outbound-interface.xml.i>
       </children>
     </tagNode>
   </children>
 </tagNode>
 <!-- include end -->
diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-forward.xml.i
index 99f66ec77..fcc981925 100644
--- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i
+++ b/interface-definitions/include/firewall/bridge-hook-forward.xml.i
@@ -1,35 +1,41 @@
 <!-- include start from firewall/bridge-hook-forward.xml.i -->
 <node name="forward">
   <properties>
     <help>Bridge forward firewall</help>
   </properties>
   <children>
     <node name="filter">
       <properties>
         <help>Bridge firewall forward filter</help>
       </properties>
       <children>
         #include <include/firewall/default-action-base-chains.xml.i>
         #include <include/firewall/default-log.xml.i>
         #include <include/generic-description.xml.i>
         <tagNode name="rule">
           <properties>
             <help>Bridge Firewall forward filter rule number</help>
             <valueHelp>
               <format>u32:1-999999</format>
               <description>Number for this firewall rule</description>
             </valueHelp>
             <constraint>
               <validator name="numeric" argument="--range 1-999999"/>
             </constraint>
             <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
           </properties>
           <children>
             #include <include/firewall/common-rule-bridge.xml.i>
+            #include <include/firewall/action-l2.xml.i>
+            #include <include/firewall/connection-mark.xml.i>
+            #include <include/firewall/connection-status.xml.i>
+            #include <include/firewall/state.xml.i>
+            #include <include/firewall/inbound-interface.xml.i>
+            #include <include/firewall/outbound-interface.xml.i>
           </children>
         </tagNode>
       </children>
     </node>
   </children>
 </node>
 <!-- include end -->
diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-input.xml.i
similarity index 64%
copy from interface-definitions/include/firewall/bridge-hook-forward.xml.i
copy to interface-definitions/include/firewall/bridge-hook-input.xml.i
index 99f66ec77..f6a11f8da 100644
--- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i
+++ b/interface-definitions/include/firewall/bridge-hook-input.xml.i
@@ -1,35 +1,40 @@
-<!-- include start from firewall/bridge-hook-forward.xml.i -->
-<node name="forward">
+<!-- include start from firewall/bridge-hook-input.xml.i -->
+<node name="input">
   <properties>
-    <help>Bridge forward firewall</help>
+    <help>Bridge input firewall</help>
   </properties>
   <children>
     <node name="filter">
       <properties>
-        <help>Bridge firewall forward filter</help>
+        <help>Bridge firewall input filter</help>
       </properties>
       <children>
         #include <include/firewall/default-action-base-chains.xml.i>
         #include <include/firewall/default-log.xml.i>
         #include <include/generic-description.xml.i>
         <tagNode name="rule">
           <properties>
-            <help>Bridge Firewall forward filter rule number</help>
+            <help>Bridge Firewall input filter rule number</help>
             <valueHelp>
               <format>u32:1-999999</format>
               <description>Number for this firewall rule</description>
             </valueHelp>
             <constraint>
               <validator name="numeric" argument="--range 1-999999"/>
             </constraint>
             <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
           </properties>
           <children>
             #include <include/firewall/common-rule-bridge.xml.i>
+            #include <include/firewall/action-l2.xml.i>
+            #include <include/firewall/connection-mark.xml.i>
+            #include <include/firewall/connection-status.xml.i>
+            #include <include/firewall/state.xml.i>
+            #include <include/firewall/inbound-interface.xml.i>
           </children>
         </tagNode>
       </children>
     </node>
   </children>
 </node>
 <!-- include end -->
diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-output.xml.i
similarity index 64%
copy from interface-definitions/include/firewall/bridge-hook-forward.xml.i
copy to interface-definitions/include/firewall/bridge-hook-output.xml.i
index 99f66ec77..38b8b08ca 100644
--- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i
+++ b/interface-definitions/include/firewall/bridge-hook-output.xml.i
@@ -1,35 +1,40 @@
-<!-- include start from firewall/bridge-hook-forward.xml.i -->
-<node name="forward">
+<!-- include start from firewall/bridge-hook-output.xml.i -->
+<node name="output">
   <properties>
-    <help>Bridge forward firewall</help>
+    <help>Bridge output firewall</help>
   </properties>
   <children>
     <node name="filter">
       <properties>
-        <help>Bridge firewall forward filter</help>
+        <help>Bridge firewall output filter</help>
       </properties>
       <children>
         #include <include/firewall/default-action-base-chains.xml.i>
         #include <include/firewall/default-log.xml.i>
         #include <include/generic-description.xml.i>
         <tagNode name="rule">
           <properties>
-            <help>Bridge Firewall forward filter rule number</help>
+            <help>Bridge Firewall output filter rule number</help>
             <valueHelp>
               <format>u32:1-999999</format>
               <description>Number for this firewall rule</description>
             </valueHelp>
             <constraint>
               <validator name="numeric" argument="--range 1-999999"/>
             </constraint>
             <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
           </properties>
           <children>
             #include <include/firewall/common-rule-bridge.xml.i>
+            #include <include/firewall/action-l2.xml.i>
+            #include <include/firewall/connection-mark.xml.i>
+            #include <include/firewall/connection-status.xml.i>
+            #include <include/firewall/state.xml.i>
+            #include <include/firewall/outbound-interface.xml.i>
           </children>
         </tagNode>
       </children>
     </node>
   </children>
 </node>
 <!-- include end -->
diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i
similarity index 71%
copy from interface-definitions/include/firewall/bridge-hook-forward.xml.i
copy to interface-definitions/include/firewall/bridge-hook-prerouting.xml.i
index 99f66ec77..ea567644f 100644
--- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i
+++ b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i
@@ -1,35 +1,37 @@
-<!-- include start from firewall/bridge-hook-forward.xml.i -->
-<node name="forward">
+<!-- include start from firewall/bridge-hook-prerouting.xml.i -->
+<node name="prerouting">
   <properties>
-    <help>Bridge forward firewall</help>
+    <help>Bridge prerouting firewall</help>
   </properties>
   <children>
     <node name="filter">
       <properties>
-        <help>Bridge firewall forward filter</help>
+        <help>Bridge firewall prerouting filter</help>
       </properties>
       <children>
         #include <include/firewall/default-action-base-chains.xml.i>
         #include <include/firewall/default-log.xml.i>
         #include <include/generic-description.xml.i>
         <tagNode name="rule">
           <properties>
-            <help>Bridge Firewall forward filter rule number</help>
+            <help>Bridge firewall prerouting filter rule number</help>
             <valueHelp>
               <format>u32:1-999999</format>
               <description>Number for this firewall rule</description>
             </valueHelp>
             <constraint>
               <validator name="numeric" argument="--range 1-999999"/>
             </constraint>
             <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
           </properties>
           <children>
             #include <include/firewall/common-rule-bridge.xml.i>
+            #include <include/firewall/action-and-notrack.xml.i>
+            #include <include/firewall/inbound-interface.xml.i>
           </children>
         </tagNode>
       </children>
     </node>
   </children>
 </node>
 <!-- include end -->
diff --git a/interface-definitions/include/firewall/common-rule-bridge.xml.i b/interface-definitions/include/firewall/common-rule-bridge.xml.i
index dcdd970ac..9ae28f7be 100644
--- a/interface-definitions/include/firewall/common-rule-bridge.xml.i
+++ b/interface-definitions/include/firewall/common-rule-bridge.xml.i
@@ -1,34 +1,54 @@
 <!-- include start from firewall/common-rule-bridge.xml.i -->
-#include <include/firewall/action-l2.xml.i>
+#include <include/generic-description.xml.i>
+#include <include/generic-disable-node.xml.i>
+#include <include/firewall/dscp.xml.i>
+#include <include/firewall/firewall-mark.xml.i>
+#include <include/firewall/fragment.xml.i>
+#include <include/firewall/hop-limit.xml.i>
+#include <include/firewall/icmp.xml.i>
+#include <include/firewall/icmpv6.xml.i>
+#include <include/firewall/limit.xml.i>
+#include <include/firewall/log.xml.i>
+#include <include/firewall/log-options.xml.i>
+#include <include/firewall/match-ipsec.xml.i>
+#include <include/firewall/match-vlan.xml.i>
 #include <include/firewall/nft-queue.xml.i>
+#include <include/firewall/packet-options.xml.i>
+#include <include/firewall/protocol.xml.i>
+#include <include/firewall/tcp-flags.xml.i>
+#include <include/firewall/tcp-mss.xml.i>
+#include <include/firewall/time.xml.i>
+#include <include/firewall/ttl.xml.i>
 <node name="destination">
   <properties>
     <help>Destination parameters</help>
   </properties>
   <children>
     #include <include/firewall/mac-address.xml.i>
+    #include <include/firewall/address-inet.xml.i>
+    #include <include/firewall/address-mask-inet.xml.i>
+    #include <include/firewall/port.xml.i>
+    #include <include/firewall/source-destination-group-inet.xml.i>
   </children>
 </node>
-#include <include/generic-disable-node.xml.i>
 <leafNode name="jump-target">
   <properties>
     <help>Set jump target. Action jump must be defined to use this setting</help>
     <completionHelp>
       <path>firewall bridge name</path>
     </completionHelp>
   </properties>
 </leafNode>
-#include <include/firewall/log.xml.i>
-#include <include/firewall/log-options.xml.i>
 <node name="source">
   <properties>
     <help>Source parameters</help>
   </properties>
   <children>
     #include <include/firewall/mac-address.xml.i>
+    #include <include/firewall/address-inet.xml.i>
+    #include <include/firewall/address-mask-inet.xml.i>
+    #include <include/firewall/port.xml.i>
+    #include <include/firewall/source-destination-group-inet.xml.i>
   </children>
 </node>
-#include <include/firewall/inbound-interface.xml.i>
-#include <include/firewall/outbound-interface.xml.i>
-#include <include/firewall/match-vlan.xml.i>
 <!-- include end -->
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
index 9039b76fd..cee8f1854 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -1,341 +1,360 @@
 <!-- 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>
+    <node name="apply-to-bridged-traffic">
+      <properties>
+        <help>Apply configured firewall rules to traffic switched by bridges</help>
+      </properties>
+      <children>
+        <leafNode name="ipv4">
+          <properties>
+            <help>Apply configured IPv4 firewall rules</help>
+            <valueless/>
+          </properties>
+        </leafNode>
+        <leafNode name="ipv6">
+          <properties>
+            <help>Apply configured IPv6 firewall rules</help>
+            <valueless/>
+          </properties>
+        </leafNode>
+      </children>
+    </node>
     <leafNode name="directed-broadcast">
       <properties>
         <help>Policy for handling IPv4 directed broadcast forwarding on all interfaces</help>
         <completionHelp>
           <list>enable disable</list>
         </completionHelp>
         <valueHelp>
           <format>enable</format>
           <description>Enable IPv4 directed broadcast forwarding on all interfaces</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>Disable IPv4 directed broadcast forwarding on all interfaces</description>
         </valueHelp>
         <constraint>
           <regex>(enable|disable)</regex>
         </constraint>
       </properties>
       <defaultValue>enable</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 IPv4 source validation by reversed path, as specified in RFC3704</help>
         <completionHelp>
           <list>strict loose disable</list>
         </completionHelp>
         <valueHelp>
           <format>strict</format>
           <description>Enable IPv4 Strict Reverse Path Forwarding as defined in RFC3704</description>
         </valueHelp>
         <valueHelp>
           <format>loose</format>
           <description>Enable IPv4 Loose Reverse Path Forwarding as defined in RFC3704</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>No IPv4 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>
     <node name="timeout">
       <properties>
         <help>Connection timeout options</help>
       </properties>
       <children>
         #include <include/firewall/timeout-common-protocols.xml.i>
       </children>
     </node>
     <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-source-validation">
       <properties>
         <help>Policy for IPv6 source validation by reversed path, as specified in RFC3704</help>
         <completionHelp>
           <list>strict loose disable</list>
         </completionHelp>
         <valueHelp>
           <format>strict</format>
           <description>Enable IPv6 Strict Reverse Path Forwarding as defined in RFC3704</description>
         </valueHelp>
         <valueHelp>
           <format>loose</format>
           <description>Enable IPv6 Loose Reverse Path Forwarding as defined in RFC3704</description>
         </valueHelp>
         <valueHelp>
           <format>disable</format>
           <description>No IPv6 source validation</description>
         </valueHelp>
         <constraint>
           <regex>(strict|loose|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/interface-definitions/include/firewall/set-packet-modifications.xml.i b/interface-definitions/include/firewall/set-packet-modifications.xml.i
new file mode 100644
index 000000000..ee019b64e
--- /dev/null
+++ b/interface-definitions/include/firewall/set-packet-modifications.xml.i
@@ -0,0 +1,96 @@
+<!-- include start from firewall/set-packet-modifications.xml.i -->
+<node name="set">
+  <properties>
+    <help>Packet modifications</help>
+  </properties>
+  <children>
+    <leafNode name="connection-mark">
+      <properties>
+        <help>Set connection mark</help>
+        <valueHelp>
+          <format>u32:0-2147483647</format>
+          <description>Connection mark</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--range 0-2147483647"/>
+        </constraint>
+      </properties>
+    </leafNode>
+    <leafNode name="dscp">
+      <properties>
+        <help>Set DSCP (Packet Differentiated Services Codepoint) bits</help>
+        <valueHelp>
+          <format>u32:0-63</format>
+          <description>DSCP number</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--range 0-63"/>
+        </constraint>
+      </properties>
+    </leafNode>
+    <leafNode name="mark">
+      <properties>
+        <help>Set packet mark</help>
+        <valueHelp>
+          <format>u32:1-2147483647</format>
+          <description>Packet mark</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--range 1-2147483647"/>
+        </constraint>
+      </properties>
+    </leafNode>
+    <leafNode name="table">
+      <properties>
+        <help>Set the routing table for matched packets</help>
+        <valueHelp>
+          <format>u32:1-200</format>
+          <description>Table number</description>
+        </valueHelp>
+        <valueHelp>
+          <format>main</format>
+          <description>Main table</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--range 1-200"/>
+          <regex>(main)</regex>
+        </constraint>
+        <completionHelp>
+          <list>main</list>
+          <path>protocols static table</path>
+        </completionHelp>
+      </properties>
+    </leafNode>
+    <leafNode name="vrf">
+      <properties>
+        <help>VRF to forward packet with</help>
+        <valueHelp>
+          <format>txt</format>
+          <description>VRF instance name</description>
+        </valueHelp>
+        <valueHelp>
+          <format>default</format>
+          <description>Forward into default global VRF</description>
+        </valueHelp>
+        <completionHelp>
+          <list>default</list>
+          <path>vrf name</path>
+        </completionHelp>
+        #include <include/constraint/vrf.xml.i>
+      </properties>
+    </leafNode>
+    <leafNode name="tcp-mss">
+      <properties>
+        <help>Set TCP Maximum Segment Size</help>
+        <valueHelp>
+          <format>u32:500-1460</format>
+          <description>Explicitly set TCP MSS value</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--range 500-1460"/>
+        </constraint>
+      </properties>
+    </leafNode>
+  </children>
+</node>
+<!-- include end -->
\ No newline at end of file
diff --git a/interface-definitions/include/firewall/source-destination-group-inet.xml.i b/interface-definitions/include/firewall/source-destination-group-inet.xml.i
new file mode 100644
index 000000000..174051624
--- /dev/null
+++ b/interface-definitions/include/firewall/source-destination-group-inet.xml.i
@@ -0,0 +1,50 @@
+<!-- include start from firewall/source-destination-group-inet.xml.i -->
+<node name="group">
+  <properties>
+    <help>Group</help>
+  </properties>
+  <children>
+    <leafNode name="ipv4-address-group">
+      <properties>
+        <help>Group of IPv4 addresses</help>
+        <completionHelp>
+          <path>firewall group address-group</path>
+        </completionHelp>
+      </properties>
+    </leafNode>
+    <leafNode name="ipv6-address-group">
+      <properties>
+        <help>Group of IPv6 addresses</help>
+        <completionHelp>
+          <path>firewall group ipv6-address-group</path>
+        </completionHelp>
+      </properties>
+    </leafNode>
+    #include <include/firewall/mac-group.xml.i>
+    <leafNode name="ipv4-network-group">
+      <properties>
+        <help>Group of IPv4 networks</help>
+        <completionHelp>
+          <path>firewall group network-group</path>
+        </completionHelp>
+      </properties>
+    </leafNode>
+    <leafNode name="ipv6-network-group">
+      <properties>
+        <help>Group of IPv6 networks</help>
+        <completionHelp>
+          <path>firewall group ipv6-network-group</path>
+        </completionHelp>
+      </properties>
+    </leafNode>
+    <leafNode name="port-group">
+      <properties>
+        <help>Group of ports</help>
+        <completionHelp>
+          <path>firewall group port-group</path>
+        </completionHelp>
+      </properties>
+    </leafNode>
+  </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i
index 203be73e7..19ffc0506 100644
--- a/interface-definitions/include/policy/route-common.xml.i
+++ b/interface-definitions/include/policy/route-common.xml.i
@@ -1,209 +1,116 @@
 <!-- include start from policy/route-common.xml.i -->
 #include <include/policy/route-rule-action.xml.i>
 #include <include/generic-description.xml.i>
 #include <include/firewall/firewall-mark.xml.i>
 #include <include/generic-disable-node.xml.i>
 #include <include/firewall/fragment.xml.i>
 #include <include/firewall/match-ipsec.xml.i>
 #include <include/firewall/limit.xml.i>
 #include <include/firewall/log.xml.i>
 <leafNode name="protocol">
   <properties>
     <help>Protocol to match (protocol name, number, or "all")</help>
     <completionHelp>
       <script>cat /etc/protocols | sed -e '/^#.*/d' | awk '{ print $1 }'</script>
     </completionHelp>
     <valueHelp>
       <format>all</format>
       <description>All IP protocols</description>
     </valueHelp>
     <valueHelp>
       <format>tcp_udp</format>
       <description>Both TCP and UDP</description>
     </valueHelp>
     <valueHelp>
       <format>0-255</format>
       <description>IP protocol number</description>
     </valueHelp>
     <valueHelp>
       <format>!&lt;protocol&gt;</format>
       <description>IP protocol number</description>
     </valueHelp>
     <constraint>
       <validator name="ip-protocol"/>
     </constraint>
   </properties>
   <defaultValue>all</defaultValue>
 </leafNode>
 <node name="recent">
   <properties>
     <help>Parameters for matching recently seen sources</help>
   </properties>
   <children>
     <leafNode name="count">
       <properties>
         <help>Source addresses seen more than N times</help>
         <valueHelp>
           <format>u32:1-255</format>
           <description>Source addresses seen more than N times</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 1-255"/>
         </constraint>
       </properties>
     </leafNode>
     <leafNode name="time">
       <properties>
         <help>Source addresses seen in the last N seconds</help>
         <valueHelp>
           <format>u32:0-4294967295</format>
           <description>Source addresses seen in the last N seconds</description>
         </valueHelp>
         <constraint>
           <validator name="numeric" argument="--range 0-4294967295"/>
         </constraint>
       </properties>
     </leafNode>
   </children>
 </node>
-<node name="set">
-  <properties>
-    <help>Packet modifications</help>
-  </properties>
-  <children>
-    <leafNode name="connection-mark">
-      <properties>
-        <help>Connection marking</help>
-        <valueHelp>
-          <format>u32:0-2147483647</format>
-          <description>Connection marking</description>
-        </valueHelp>
-        <constraint>
-          <validator name="numeric" argument="--range 0-2147483647"/>
-        </constraint>
-      </properties>
-    </leafNode>
-    <leafNode name="dscp">
-      <properties>
-        <help>Packet Differentiated Services Codepoint (DSCP)</help>
-        <valueHelp>
-          <format>u32:0-63</format>
-          <description>DSCP number</description>
-        </valueHelp>
-        <constraint>
-          <validator name="numeric" argument="--range 0-63"/>
-        </constraint>
-      </properties>
-    </leafNode>
-    <leafNode name="mark">
-      <properties>
-        <help>Packet marking</help>
-        <valueHelp>
-          <format>u32:1-2147483647</format>
-          <description>Packet marking</description>
-        </valueHelp>
-        <constraint>
-          <validator name="numeric" argument="--range 1-2147483647"/>
-        </constraint>
-      </properties>
-    </leafNode>
-    <leafNode name="table">
-      <properties>
-        <help>Routing table to forward packet with</help>
-        <valueHelp>
-          <format>u32:1-200</format>
-          <description>Table number</description>
-        </valueHelp>
-        <valueHelp>
-          <format>main</format>
-          <description>Main table</description>
-        </valueHelp>
-        <constraint>
-          <validator name="numeric" argument="--range 1-200"/>
-          <regex>(main)</regex>
-        </constraint>
-        <completionHelp>
-          <list>main</list>
-          <path>protocols static table</path>
-        </completionHelp>
-      </properties>
-    </leafNode>
-    <leafNode name="vrf">
-      <properties>
-        <help>VRF to forward packet with</help>
-        <valueHelp>
-          <format>txt</format>
-          <description>VRF instance name</description>
-        </valueHelp>
-        <valueHelp>
-          <format>default</format>
-          <description>Forward into default global VRF</description>
-        </valueHelp>
-        <completionHelp>
-          <list>default</list>
-          <path>vrf name</path>
-        </completionHelp>
-        #include <include/constraint/vrf.xml.i>
-      </properties>
-    </leafNode>
-    <leafNode name="tcp-mss">
-      <properties>
-        <help>TCP Maximum Segment Size</help>
-        <valueHelp>
-          <format>u32:500-1460</format>
-          <description>Explicitly set TCP MSS value</description>
-        </valueHelp>
-        <constraint>
-          <validator name="numeric" argument="--range 500-1460"/>
-        </constraint>
-      </properties>
-    </leafNode>
-  </children>
-</node>
+#include <include/firewall/set-packet-modifications.xml.i>
 #include <include/firewall/state.xml.i>
 #include <include/firewall/tcp-flags.xml.i>
 #include <include/firewall/tcp-mss.xml.i>
 <node name="time">
   <properties>
     <help>Time to match rule</help>
   </properties>
   <children>
     <leafNode name="monthdays">
       <properties>
         <help>Monthdays to match rule on</help>
       </properties>
     </leafNode>
     <leafNode name="startdate">
       <properties>
         <help>Date to start matching rule</help>
       </properties>
     </leafNode>
     <leafNode name="starttime">
       <properties>
         <help>Time of day to start matching rule</help>
       </properties>
     </leafNode>
     <leafNode name="stopdate">
       <properties>
         <help>Date to stop matching rule</help>
       </properties>
     </leafNode>
     <leafNode name="stoptime">
       <properties>
         <help>Time of day to stop matching rule</help>
       </properties>
     </leafNode>
     <leafNode name="utc">
       <properties>
         <help>Interpret times for startdate, stopdate, starttime and stoptime to be UTC</help>
         <valueless/>
       </properties>
     </leafNode>
     <leafNode name="weekdays">
       <properties>
         <help>Weekdays to match rule on</help>
       </properties>
     </leafNode>
   </children>
 </node>
 <!-- include end -->
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index facd498ca..cac6d2953 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -1,676 +1,688 @@
 # Copyright (C) 2021-2024 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 csv
 import gzip
 import os
 import re
 
 from pathlib import Path
 from socket import AF_INET
 from socket import AF_INET6
 from socket import getaddrinfo
 from time import strftime
 
 from vyos.remote import download
 from vyos.template import is_ipv4
 from vyos.template import render
 from vyos.utils.dict import dict_search_args
 from vyos.utils.dict import dict_search_recursive
 from vyos.utils.process import cmd
 from vyos.utils.process import run
 from vyos.utils.network import get_vrf_tableid
 from vyos.defaults import rt_global_table
 from vyos.defaults import rt_global_vrf
 
 # Conntrack
 def conntrack_required(conf):
     required_nodes = ['nat', 'nat66', 'load-balancing wan']
 
     for path in required_nodes:
         if conf.exists(path):
             return True
 
     firewall = conf.get_config_dict(['firewall'], key_mangling=('-', '_'),
                                     no_tag_node_value_mangle=True, get_first_key=True)
 
     for rules, path in dict_search_recursive(firewall, 'rule'):
         if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()):
             return True
 
     return False
 
 # Domain Resolver
 
 def fqdn_config_parse(firewall):
     firewall['ip_fqdn'] = {}
     firewall['ip6_fqdn'] = {}
 
     for domain, path in dict_search_recursive(firewall, 'fqdn'):
         hook_name = path[1]
         priority = path[2]
 
         fw_name = path[2]
         rule = path[4]
         suffix = path[5][0]
         set_name = f'{hook_name}_{priority}_{rule}_{suffix}'
 
         if (path[0] == 'ipv4') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'):
             firewall['ip_fqdn'][set_name] = domain
         elif (path[0] == 'ipv6') and (path[1] == 'forward' or path[1] == 'input' or path[1] == 'output' or path[1] == 'name'):
             if path[1] == 'name':
                 set_name = f'name6_{priority}_{rule}_{suffix}'
             firewall['ip6_fqdn'][set_name] = domain
 
 def fqdn_resolve(fqdn, ipv6=False):
     try:
         res = getaddrinfo(fqdn, None, AF_INET6 if ipv6 else AF_INET)
         return set(item[4][0] for item in res)
     except:
         return None
 
 # End Domain Resolver
 
 def find_nftables_rule(table, chain, rule_matches=[]):
     # Find rule in table/chain that matches all criteria and return the handle
     results = cmd(f'sudo nft --handle list chain {table} {chain}').split("\n")
     for line in results:
         if all(rule_match in line for rule_match in rule_matches):
             handle_search = re.search('handle (\d+)', line)
             if handle_search:
                 return handle_search[1]
     return None
 
 def remove_nftables_rule(table, chain, handle):
     cmd(f'sudo nft delete rule {table} {chain} handle {handle}')
 
 # Functions below used by template generation
 
 def nft_action(vyos_action):
     if vyos_action == 'accept':
         return 'return'
     return vyos_action
 
 def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
     output = []
 
     if ip_name == 'ip6':
         def_suffix = '6'
         family = 'ipv6'
     else:
         def_suffix = ''
         family = 'bri' if ip_name == 'bri' else 'ipv4'
 
     if 'state' in rule_conf and rule_conf['state']:
         states = ",".join([s for s in rule_conf['state']])
 
         if states:
             output.append(f'ct state {{{states}}}')
 
     if 'conntrack_helper' in rule_conf:
         helper_map = {'h323': ['RAS', 'Q.931'], 'nfs': ['rpc'], 'sqlnet': ['tns']}
         helper_out = []
 
         for helper in rule_conf['conntrack_helper']:
             if helper in helper_map:
                 helper_out.extend(helper_map[helper])
             else:
                 helper_out.append(helper)
 
         if helper_out:
             helper_str = ','.join(f'"{s}"' for s in helper_out)
             output.append(f'ct helper {{{helper_str}}}')
 
     if 'connection_status' in rule_conf and rule_conf['connection_status']:
         status = rule_conf['connection_status']
         if status['nat'] == 'destination':
             nat_status = 'dnat'
             output.append(f'ct status {nat_status}')
         if status['nat'] == 'source':
             nat_status = 'snat'
             output.append(f'ct status {nat_status}')
 
     if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
         proto = rule_conf['protocol']
         operator = ''
         if proto[0] == '!':
             operator = '!='
             proto = proto[1:]
         if proto == 'tcp_udp':
             proto = '{tcp, udp}'
         output.append(f'meta l4proto {operator} {proto}')
 
     for side in ['destination', 'source']:
         if side in rule_conf:
             prefix = side[0]
             side_conf = rule_conf[side]
             address_mask = side_conf.get('address_mask', None)
 
             if 'address' in side_conf:
                 suffix = side_conf['address']
                 operator = ''
                 exclude = suffix[0] == '!'
                 if exclude:
                     operator = '!= '
                     suffix = suffix[1:]
                 if address_mask:
                     operator = '!=' if exclude else '=='
                     operator = f'& {address_mask} {operator} '
-                output.append(f'{ip_name} {prefix}addr {operator}{suffix}')
+                if is_ipv4(suffix):
+                    output.append(f'ip {prefix}addr {operator}{suffix}')
+                else:
+                    output.append(f'ip6 {prefix}addr {operator}{suffix}')
 
             if 'fqdn' in side_conf:
                 fqdn = side_conf['fqdn']
                 hook_name = ''
                 operator = ''
                 if fqdn[0] == '!':
                     operator = '!='
                 if hook == 'FWD':
                     hook_name = 'forward'
                 if hook == 'INP':
                     hook_name = 'input'
                 if hook == 'OUT':
                     hook_name = 'output'
                 if hook == 'PRE':
                     hook_name = 'prerouting'
                 if hook == 'NAM':
                     hook_name = f'name{def_suffix}'
                 output.append(f'{ip_name} {prefix}addr {operator} @FQDN_{hook_name}_{fw_name}_{rule_id}_{prefix}')
 
             if dict_search_args(side_conf, 'geoip', 'country_code'):
                 operator = ''
                 hook_name = ''
                 if dict_search_args(side_conf, 'geoip', 'inverse_match') != None:
                     operator = '!='
                 if hook == 'FWD':
                     hook_name = 'forward'
                 if hook == 'INP':
                     hook_name = 'input'
                 if hook == 'OUT':
                     hook_name = 'output'
                 if hook == 'PRE':
                     hook_name = 'prerouting'
                 if hook == 'NAM':
                     hook_name = f'name'
                 output.append(f'{ip_name} {prefix}addr {operator} @GEOIP_CC{def_suffix}_{hook_name}_{fw_name}_{rule_id}')
 
             if 'mac_address' in side_conf:
                 suffix = side_conf["mac_address"]
                 if suffix[0] == '!':
                     suffix = f'!= {suffix[1:]}'
                 output.append(f'ether {prefix}addr {suffix}')
 
             if 'port' in side_conf:
                 proto = rule_conf['protocol']
                 port = side_conf['port'].split(',')
 
                 ports = []
                 negated_ports = []
 
                 for p in port:
                     if p[0] == '!':
                         negated_ports.append(p[1:])
                     else:
                         ports.append(p)
 
                 if proto == 'tcp_udp':
                     proto = 'th'
 
                 if ports:
                     ports_str = ','.join(ports)
                     output.append(f'{proto} {prefix}port {{{ports_str}}}')
 
                 if negated_ports:
                     negated_ports_str = ','.join(negated_ports)
                     output.append(f'{proto} {prefix}port != {{{negated_ports_str}}}')
 
             if 'group' in side_conf:
                 group = side_conf['group']
-                if 'address_group' in group:
-                    group_name = group['address_group']
-                    operator = ''
-                    exclude = group_name[0] == "!"
-                    if exclude:
-                        operator = '!='
-                        group_name = group_name[1:]
-                    if address_mask:
-                        operator = '!=' if exclude else '=='
-                        operator = f'& {address_mask} {operator}'
-                    output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}')
-                elif 'dynamic_address_group' in group:
+                for ipvx_address_group in ['address_group', 'ipv4_address_group', 'ipv6_address_group']:
+                    if ipvx_address_group in group:
+                        group_name = group[ipvx_address_group]
+                        operator = ''
+                        exclude = group_name[0] == "!"
+                        if exclude:
+                            operator = '!='
+                            group_name = group_name[1:]
+                        if address_mask:
+                            operator = '!=' if exclude else '=='
+                            operator = f'& {address_mask} {operator}'
+                        # for bridge, change ip_name
+                        if ip_name == 'bri':
+                            ip_name = 'ip' if ipvx_address_group == 'ipv4_address_group' else 'ip6'
+                            def_suffix = '6' if ipvx_address_group == 'ipv6_address_group' else ''
+                        output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}')
+                for ipvx_network_group in ['network_group', 'ipv4_network_group', 'ipv6_network_group']:
+                    if ipvx_network_group in group:
+                        group_name = group[ipvx_network_group]
+                        operator = ''
+                        if group_name[0] == "!":
+                            operator = '!='
+                            group_name = group_name[1:]
+                        # for bridge, change ip_name
+                        if ip_name == 'bri':
+                            ip_name = 'ip' if ipvx_network_group == 'ipv4_network_group' else 'ip6'
+                            def_suffix = '6' if ipvx_network_group == 'ipv6_network_group' else ''
+                        output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}')
+                if 'dynamic_address_group' in group:
                     group_name = group['dynamic_address_group']
                     operator = ''
-                    exclude = group_name[0] == "!"
-                    if exclude:
+                    if group_name[0] == "!":
                         operator = '!='
                         group_name = group_name[1:]
                     output.append(f'{ip_name} {prefix}addr {operator} @DA{def_suffix}_{group_name}')
                 # Generate firewall group domain-group
                 elif 'domain_group' in group:
                     group_name = group['domain_group']
                     operator = ''
                     if group_name[0] == '!':
                         operator = '!='
                         group_name = group_name[1:]
                     output.append(f'{ip_name} {prefix}addr {operator} @D_{group_name}')
-                elif 'network_group' in group:
-                    group_name = group['network_group']
-                    operator = ''
-                    if group_name[0] == '!':
-                        operator = '!='
-                        group_name = group_name[1:]
-                    output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}')
                 if 'mac_group' in group:
                     group_name = group['mac_group']
                     operator = ''
                     if group_name[0] == '!':
                         operator = '!='
                         group_name = group_name[1:]
                     output.append(f'ether {prefix}addr {operator} @M_{group_name}')
                 if 'port_group' in group:
                     proto = rule_conf['protocol']
                     group_name = group['port_group']
 
                     if proto == 'tcp_udp':
                         proto = 'th'
 
                     operator = ''
                     if group_name[0] == '!':
                         operator = '!='
                         group_name = group_name[1:]
 
                     output.append(f'{proto} {prefix}port {operator} @P_{group_name}')
 
     if dict_search_args(rule_conf, 'action') == 'synproxy':
         output.append('ct state invalid,untracked')
 
     if 'hop_limit' in rule_conf:
         operators = {'eq': '==', 'gt': '>', 'lt': '<'}
         for op, operator in operators.items():
             if op in rule_conf['hop_limit']:
                 value = rule_conf['hop_limit'][op]
                 output.append(f'ip6 hoplimit {operator} {value}')
 
     if 'inbound_interface' in rule_conf:
         operator = ''
         if 'name' in rule_conf['inbound_interface']:
             iiface = rule_conf['inbound_interface']['name']
             if iiface[0] == '!':
                 operator = '!='
                 iiface = iiface[1:]
             output.append(f'iifname {operator} {{{iiface}}}')
         elif 'group' in rule_conf['inbound_interface']:
             iiface = rule_conf['inbound_interface']['group']
             if iiface[0] == '!':
                 operator = '!='
                 iiface = iiface[1:]
             output.append(f'iifname {operator} @I_{iiface}')
 
     if 'outbound_interface' in rule_conf:
         operator = ''
         if 'name' in rule_conf['outbound_interface']:
             oiface = rule_conf['outbound_interface']['name']
             if oiface[0] == '!':
                 operator = '!='
                 oiface = oiface[1:]
             output.append(f'oifname {operator} {{{oiface}}}')
         elif 'group' in rule_conf['outbound_interface']:
             oiface = rule_conf['outbound_interface']['group']
             if oiface[0] == '!':
                 operator = '!='
                 oiface = oiface[1:]
             output.append(f'oifname {operator} @I_{oiface}')
 
     if 'ttl' in rule_conf:
         operators = {'eq': '==', 'gt': '>', 'lt': '<'}
         for op, operator in operators.items():
             if op in rule_conf['ttl']:
                 value = rule_conf['ttl'][op]
                 output.append(f'ip ttl {operator} {value}')
 
     for icmp in ['icmp', 'icmpv6']:
         if icmp in rule_conf:
             if 'type_name' in rule_conf[icmp]:
                 output.append(icmp + ' type ' + rule_conf[icmp]['type_name'])
             else:
                 if 'code' in rule_conf[icmp]:
                     output.append(icmp + ' code ' + rule_conf[icmp]['code'])
                 if 'type' in rule_conf[icmp]:
                     output.append(icmp + ' type ' + rule_conf[icmp]['type'])
 
 
     if 'packet_length' in rule_conf:
         lengths_str = ','.join(rule_conf['packet_length'])
         output.append(f'ip{def_suffix} length {{{lengths_str}}}')
 
     if 'packet_length_exclude' in rule_conf:
         negated_lengths_str = ','.join(rule_conf['packet_length_exclude'])
         output.append(f'ip{def_suffix} length != {{{negated_lengths_str}}}')
 
     if 'packet_type' in rule_conf:
         output.append(f'pkttype ' + rule_conf['packet_type'])
 
     if 'dscp' in rule_conf:
         dscp_str = ','.join(rule_conf['dscp'])
         output.append(f'ip{def_suffix} dscp {{{dscp_str}}}')
 
     if 'dscp_exclude' in rule_conf:
         negated_dscp_str = ','.join(rule_conf['dscp_exclude'])
         output.append(f'ip{def_suffix} dscp != {{{negated_dscp_str}}}')
 
     if 'ipsec' in rule_conf:
         if 'match_ipsec_in' in rule_conf['ipsec']:
             output.append('meta ipsec == 1')
         if 'match_none_in' in rule_conf['ipsec']:
             output.append('meta ipsec == 0')
         if 'match_ipsec_out' in rule_conf['ipsec']:
             output.append('rt ipsec exists')
         if 'match_none_out' in rule_conf['ipsec']:
             output.append('rt ipsec missing')
 
     if 'fragment' in rule_conf:
         # Checking for fragmentation after priority -400 is not possible,
         # so we use a priority -450 hook to set a mark
         if 'match_frag' in rule_conf['fragment']:
             output.append('meta mark 0xffff1')
         if 'match_non_frag' in rule_conf['fragment']:
             output.append('meta mark != 0xffff1')
 
     if 'limit' in rule_conf:
         if 'rate' in rule_conf['limit']:
             output.append(f'limit rate {rule_conf["limit"]["rate"]}')
             if 'burst' in rule_conf['limit']:
                 output.append(f'burst {rule_conf["limit"]["burst"]} packets')
 
     if 'recent' in rule_conf:
         count = rule_conf['recent']['count']
         time = rule_conf['recent']['time']
         output.append(f'add @RECENT{def_suffix}_{hook}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}')
 
     if 'time' in rule_conf:
         output.append(parse_time(rule_conf['time']))
 
     tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
     if tcp_flags:
         output.append(parse_tcp_flags(tcp_flags))
 
     # TCP MSS
     tcp_mss = dict_search_args(rule_conf, 'tcp', 'mss')
     if tcp_mss:
         output.append(f'tcp option maxseg size {tcp_mss}')
 
     if 'connection_mark' in rule_conf:
         conn_mark_str = ','.join(rule_conf['connection_mark'])
         output.append(f'ct mark {{{conn_mark_str}}}')
 
     if 'mark' in rule_conf:
         mark = rule_conf['mark']
         operator = ''
         if mark[0] == '!':
             operator = '!='
             mark = mark[1:]
         output.append(f'meta mark {operator} {{{mark}}}')
 
     if 'vlan' in rule_conf:
         if 'id' in rule_conf['vlan']:
             output.append(f'vlan id {rule_conf["vlan"]["id"]}')
         if 'priority' in rule_conf['vlan']:
             output.append(f'vlan pcp {rule_conf["vlan"]["priority"]}')
 
     if 'log' in rule_conf:
         action = rule_conf['action'] if 'action' in rule_conf else 'accept'
         #output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}]"')
         output.append(f'log prefix "[{family}-{hook}-{fw_name}-{rule_id}-{action[:1].upper()}]"')
                         ##{family}-{hook}-{fw_name}-{rule_id}
         if 'log_options' in rule_conf:
 
             if 'level' in rule_conf['log_options']:
                 log_level = rule_conf['log_options']['level']
                 output.append(f'log level {log_level}')
 
             if 'group' in rule_conf['log_options']:
                 log_group = rule_conf['log_options']['group']
                 output.append(f'log group {log_group}')
 
                 if 'queue_threshold' in rule_conf['log_options']:
                     queue_threshold = rule_conf['log_options']['queue_threshold']
                     output.append(f'queue-threshold {queue_threshold}')
 
                 if 'snapshot_length' in rule_conf['log_options']:
                     log_snaplen = rule_conf['log_options']['snapshot_length']
                     output.append(f'snaplen {log_snaplen}')
 
     output.append('counter')
 
     if 'add_address_to_group' in rule_conf:
         for side in ['destination_address', 'source_address']:
             if side in rule_conf['add_address_to_group']:
                 prefix = side[0]
                 side_conf = rule_conf['add_address_to_group'][side]
                 dyn_group = side_conf['address_group']
                 if 'timeout' in side_conf:
                     timeout_value = side_conf['timeout']
                     output.append(f'set update ip{def_suffix} {prefix}addr timeout {timeout_value} @DA{def_suffix}_{dyn_group}')
                 else:
                     output.append(f'set update ip{def_suffix} saddr @DA{def_suffix}_{dyn_group}')
 
     set_table = False
     if 'set' in rule_conf:
         # Parse set command used in policy route:
         if 'connection_mark' in rule_conf['set']:
             conn_mark = rule_conf['set']['connection_mark']
             output.append(f'ct mark set {conn_mark}')
         if 'dscp' in rule_conf['set']:
             dscp = rule_conf['set']['dscp']
             output.append(f'ip{def_suffix} dscp set {dscp}')
         if 'mark' in rule_conf['set']:
             mark = rule_conf['set']['mark']
             output.append(f'meta mark set {mark}')
         if 'vrf' in rule_conf['set']:
             set_table = True
             vrf_name = rule_conf['set']['vrf']
             if vrf_name == 'default':
                 table = rt_global_vrf
             else:
                 # NOTE: VRF->table ID lookup depends on the VRF iface already existing.
                 table = get_vrf_tableid(vrf_name)
         if 'table' in rule_conf['set']:
             set_table = True
             table = rule_conf['set']['table']
             if table == 'main':
                 table = rt_global_table
         if set_table:
             mark = 0x7FFFFFFF - int(table)
             output.append(f'meta mark set {mark}')
         if 'tcp_mss' in rule_conf['set']:
             mss = rule_conf['set']['tcp_mss']
             output.append(f'tcp option maxseg size set {mss}')
 
     if 'action' in rule_conf:
         if rule_conf['action'] == 'offload':
             offload_target = rule_conf['offload_target']
             output.append(f'flow add @VYOS_FLOWTABLE_{offload_target}')
         else:
             output.append(f'{rule_conf["action"]}')
 
             if 'jump' in rule_conf['action']:
                 target = rule_conf['jump_target']
                 output.append(f'NAME{def_suffix}_{target}')
 
             if 'queue' in rule_conf['action']:
                 if 'queue' in rule_conf:
                     target = rule_conf['queue']
                     output.append(f'num {target}')
 
                 if 'queue_options' in rule_conf:
                     queue_opts = ','.join(rule_conf['queue_options'])
                     output.append(f'{queue_opts}')
 
         # Synproxy
         if 'synproxy' in rule_conf:
             synproxy_mss = dict_search_args(rule_conf, 'synproxy', 'tcp', 'mss')
             if synproxy_mss:
                 output.append(f'mss {synproxy_mss}')
             synproxy_ws = dict_search_args(rule_conf, 'synproxy', 'tcp', 'window_scale')
             if synproxy_ws:
                 output.append(f'wscale {synproxy_ws} timestamp sack-perm')
 
     else:
         if set_table:
             output.append('return')
 
     output.append(f'comment "{family}-{hook}-{fw_name}-{rule_id}"')
     return " ".join(output)
 
 def parse_tcp_flags(flags):
     include = [flag for flag in flags if flag != 'not']
     exclude = list(flags['not']) if 'not' in flags else []
     return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include) if include else "0x0"}'
 
 def parse_time(time):
     out = []
     if 'startdate' in time:
         start = time['startdate']
         if 'T' not in start and 'starttime' in time:
             start += f' {time["starttime"]}'
         out.append(f'time >= "{start}"')
     if 'starttime' in time and 'startdate' not in time:
         out.append(f'hour >= "{time["starttime"]}"')
     if 'stopdate' in time:
         stop = time['stopdate']
         if 'T' not in stop and 'stoptime' in time:
             stop += f' {time["stoptime"]}'
         out.append(f'time < "{stop}"')
     if 'stoptime' in time and 'stopdate' not in time:
         out.append(f'hour < "{time["stoptime"]}"')
     if 'weekdays' in time:
         days = time['weekdays'].split(",")
         out_days = [f'"{day}"' for day in days if day[0] != '!']
         out.append(f'day {{{",".join(out_days)}}}')
     return " ".join(out)
 
 # GeoIP
 
 nftables_geoip_conf = '/run/nftables-geoip.conf'
 geoip_database = '/usr/share/vyos-geoip/dbip-country-lite.csv.gz'
 geoip_lock_file = '/run/vyos-geoip.lock'
 
 def geoip_load_data(codes=[]):
     data = None
 
     if not os.path.exists(geoip_database):
         return []
 
     try:
         with gzip.open(geoip_database, mode='rt') as csv_fh:
             reader = csv.reader(csv_fh)
             out = []
             for start, end, code in reader:
                 if code.lower() in codes:
                     out.append([start, end, code.lower()])
             return out
     except:
         print('Error: Failed to open GeoIP database')
     return []
 
 def geoip_download_data():
     url = 'https://download.db-ip.com/free/dbip-country-lite-{}.csv.gz'.format(strftime("%Y-%m"))
     try:
         dirname = os.path.dirname(geoip_database)
         if not os.path.exists(dirname):
             os.mkdir(dirname)
 
         download(geoip_database, url)
         print("Downloaded GeoIP database")
         return True
     except:
         print("Error: Failed to download GeoIP database")
     return False
 
 class GeoIPLock(object):
     def __init__(self, file):
         self.file = file
 
     def __enter__(self):
         if os.path.exists(self.file):
             return False
 
         Path(self.file).touch()
         return True
 
     def __exit__(self, exc_type, exc_value, tb):
         os.unlink(self.file)
 
 def geoip_update(firewall, force=False):
     with GeoIPLock(geoip_lock_file) as lock:
         if not lock:
             print("Script is already running")
             return False
 
         if not firewall:
             print("Firewall is not configured")
             return True
 
         if not os.path.exists(geoip_database):
             if not geoip_download_data():
                 return False
         elif force:
             geoip_download_data()
 
         ipv4_codes = {}
         ipv6_codes = {}
 
         ipv4_sets = {}
         ipv6_sets = {}
 
         # Map country codes to set names
         for codes, path in dict_search_recursive(firewall, 'country_code'):
             set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
             if ( path[0] == 'ipv4'):
                 for code in codes:
                     ipv4_codes.setdefault(code, []).append(set_name)
             elif ( path[0] == 'ipv6' ):
                 set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'
                 for code in codes:
                     ipv6_codes.setdefault(code, []).append(set_name)
 
         if not ipv4_codes and not ipv6_codes:
             if force:
                 print("GeoIP not in use by firewall")
             return True
 
         geoip_data = geoip_load_data([*ipv4_codes, *ipv6_codes])
 
         # Iterate IP blocks to assign to sets
         for start, end, code in geoip_data:
             ipv4 = is_ipv4(start)
             if code in ipv4_codes and ipv4:
                 ip_range = f'{start}-{end}' if start != end else start
                 for setname in ipv4_codes[code]:
                     ipv4_sets.setdefault(setname, []).append(ip_range)
             if code in ipv6_codes and not ipv4:
                 ip_range = f'{start}-{end}' if start != end else start
                 for setname in ipv6_codes[code]:
                     ipv6_sets.setdefault(setname, []).append(ip_range)
 
         render(nftables_geoip_conf, 'firewall/nftables-geoip-update.j2', {
             'ipv4_sets': ipv4_sets,
             'ipv6_sets': ipv6_sets
         })
 
         result = run(f'nft --file {nftables_geoip_conf}')
         if result != 0:
             print('Error: GeoIP failed to update firewall')
             return False
 
         return True
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index e6317050c..551f8ce65 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -1,1075 +1,1104 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import 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 run
 from vyos.utils.file import read_file
 
 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'},
     'directed_broadcast': {'sysfs': '/proc/sys/net/ipv4/conf/all/bc_forwarding', 'default': '1', 'test_value': 'disable'},
     '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'}
 }
 
 def get_sysctl(parameter):
     tmp = parameter.replace(r'.', r'/')
     return read_file(f'/proc/sys/{tmp}')
 
 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 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, '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', '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', '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_set(['firewall', 'ipv4', 'output', 'raw', 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'output', 'raw', 'rule', '1', 'protocol', 'udp'])
 
         self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'action', 'notrack'])
         self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'destination', 'port', '23'])
 
         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'],
             ['log prefix "[ipv4-OUT-filter-default-D]"','OUT-filter default-action drop', 'drop'],
             ['chain VYOS_OUTPUT_raw'],
             ['type filter hook output priority raw; policy accept;'],
             ['udp', 'accept'],
             ['OUT-raw default-action drop', 'drop'],
             ['chain VYOS_PREROUTING_raw'],
             ['type filter hook prerouting priority raw; policy accept;'],
             ['tcp dport 23', 'notrack'],
             ['PRE-raw default-action accept', 'accept'],
             ['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, '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_synproxy(self):
         tcp_mss = '1460'
         tcp_wscale = '7'
         dport = '22'
 
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'port', dport])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'synproxy', 'tcp', 'mss', tcp_mss])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'synproxy', 'tcp', 'window-scale', tcp_wscale])
 
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'synproxy'])
 
         self.cli_commit()
 
         nftables_search = [
             [f'tcp dport {dport} ct state invalid,untracked', f'synproxy mss {tcp_mss} wscale {tcp_wscale} timestamp sack-perm']
         ]
 
         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, '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_ipv4_dynamic_groups(self):
         group01 = 'knock01'
         group02 = 'allowed'
 
         self.cli_set(['firewall', 'group', 'dynamic-group', 'address-group', group01])
         self.cli_set(['firewall', 'group', 'dynamic-group', 'address-group', group02])
 
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'destination', 'port', '5151'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'address-group', group01])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'timeout', '30s'])
 
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'destination', 'port', '7272'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'source', 'group', 'dynamic-address-group', group01])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'address-group', group02])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'timeout', '5m'])
 
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'destination', 'port', '22'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '30', 'source', 'group', 'dynamic-address-group', group02])
 
         self.cli_commit()
 
         nftables_search = [
             [f'DA_{group01}'],
             [f'DA_{group02}'],
             ['type ipv4_addr'],
             ['flags dynamic,timeout'],
             ['chain VYOS_INPUT_filter {'],
             ['type filter hook input priority filter', 'policy accept'],
             ['tcp dport 5151', f'update @DA_{group01}', '{ ip saddr timeout 30s }', 'drop'],
             ['tcp dport 7272', f'ip saddr @DA_{group01}', f'update @DA_{group02}', '{ ip saddr timeout 5m }', 'drop'],
             ['tcp dport 22', f'ip saddr @DA_{group02}', 'accept']
         ]
 
         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, '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', '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', '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_set(['firewall', 'ipv6', 'output', 'filter', 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'output', 'filter', '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', 'output', 'raw', 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'rule', '1', 'action', 'notrack'])
         self.cli_set(['firewall', 'ipv6', 'output', 'raw', 'rule', '1', 'protocol', 'udp'])
 
         self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv6', 'prerouting', 'raw', 'rule', '1', 'destination', 'port', '23'])
 
         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'],
             ['chain VYOS_IPV6_OUTPUT_raw'],
             ['type filter hook output priority raw; policy accept;'],
             ['udp', 'notrack'],
             ['OUT-raw default-action drop', 'drop'],
             ['chain VYOS_IPV6_PREROUTING_raw'],
             ['type filter hook prerouting priority raw; policy accept;'],
             ['tcp dport 23', 'drop'],
             ['PRE-raw default-action accept', 'accept'],
             [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'],
             ['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, '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', 'mark', '!6655-7766'])
         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', 'meta mark != 0x000019ff-0x00001e56', 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, '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_ipv6_dynamic_groups(self):
         group01 = 'knock01'
         group02 = 'allowed'
 
         self.cli_set(['firewall', 'group', 'dynamic-group', 'ipv6-address-group', group01])
         self.cli_set(['firewall', 'group', 'dynamic-group', 'ipv6-address-group', group02])
 
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'destination', 'port', '5151'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'address-group', group01])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '10', 'add-address-to-group', 'source-address', 'timeout', '30s'])
 
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'destination', 'port', '7272'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'source', 'group', 'dynamic-address-group', group01])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'address-group', group02])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '20', 'add-address-to-group', 'source-address', 'timeout', '5m'])
 
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'protocol', 'tcp'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'destination', 'port', '22'])
         self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '30', 'source', 'group', 'dynamic-address-group', group02])
 
         self.cli_commit()
 
         nftables_search = [
             [f'DA6_{group01}'],
             [f'DA6_{group02}'],
             ['type ipv6_addr'],
             ['flags dynamic,timeout'],
             ['chain VYOS_IPV6_INPUT_filter {'],
             ['type filter hook input priority filter', 'policy accept'],
             ['tcp dport 5151', f'update @DA6_{group01}', '{ ip6 saddr timeout 30s }', 'drop'],
             ['tcp dport 7272', f'ip6 saddr @DA6_{group01}', f'update @DA6_{group02}', '{ ip6 saddr timeout 5m }', 'drop'],
             ['tcp dport 22', f'ip6 saddr @DA6_{group02}', 'accept']
         ]
 
         self.verify_nftables(nftables_search, 'ip6 vyos_filter')
 
     def test_ipv4_global_state(self):
         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_commit()
 
         nftables_search = [
             ['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 is enabled from state-policy
         self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
         self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
 
     def test_ipv4_state_and_status_rules(self):
         name = 'smoketest-state'
 
         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"']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
 
         # Check conntrack
         self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
         self.verify_nftables_chain([['return']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
 
-    def test_bridge_basic_rules(self):
+    def test_bridge_firewall(self):
         name = 'smoketest'
         interface_in = 'eth0'
         mac_address = '00:53:00:00:00:01'
         vlan_id = '12'
         vlan_prior = '3'
 
+        # Check bridge-nf-call-iptables default value: 0
+        self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '0')
+        self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0')
+
+        self.cli_set(['firewall', 'group', 'ipv6-address-group', 'AGV6', 'address', '2001:db1::1'])
+        self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept'])
+        self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'ipv4'])
+
         self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept'])
         self.cli_set(['firewall', 'bridge', 'name', name, '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', 'default-log'])
         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_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'action', 'accept'])
+        self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'inbound-interface', 'name', interface_in])
+        self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'source', 'address', '192.0.2.2'])
+        self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'state', 'new'])
+
+        self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'action', 'notrack'])
+        self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'destination', 'group', 'ipv6-address-group', 'AGV6'])
+
         self.cli_commit()
 
         nftables_search = [
+            ['set A6_AGV6'],
+            ['type ipv6_addr'],
+            ['elements', '2001:db1::1'],
             ['chain VYOS_FORWARD_filter'],
             ['type filter hook forward priority filter; policy accept;'],
+            ['jump VYOS_STATE_POLICY'],
             [f'vlan id {vlan_id}', 'accept'],
             [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'],
             ['log prefix "[bri-FWD-filter-default-D]"', 'drop', 'FWD-filter default-action drop'],
             [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'],
-            ['accept', f'{name} default-action accept']
+            ['accept', f'{name} default-action accept'],
+            ['chain VYOS_INPUT_filter'],
+            ['type filter hook input priority filter; policy accept;'],
+            ['ct state new', 'ip saddr 192.0.2.2', f'iifname "{interface_in}"', 'accept'],
+            ['chain VYOS_PREROUTING_filter'],
+            ['type filter hook prerouting priority filter; policy accept;'],
+            ['ip6 daddr @A6_AGV6', 'notrack']
         ]
 
         self.verify_nftables(nftables_search, 'bridge vyos_filter')
+        ## Check bridge-nf-call-iptables is set to 1, and for ipv6 remains on default 0
+        self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '1')
+        self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0')
 
     def test_source_validation(self):
         # Strict
         self.cli_set(['firewall', 'global-options', 'source-validation', 'strict'])
         self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'strict'])
         self.cli_commit()
 
         nftables_strict_search = [
             ['fib saddr . iif oif 0', 'drop']
         ]
 
         self.verify_nftables_chain(nftables_strict_search, 'ip raw', 'vyos_global_rpfilter')
         self.verify_nftables_chain(nftables_strict_search, 'ip6 raw', 'vyos_global_rpfilter')
 
         # Loose
         self.cli_set(['firewall', 'global-options', 'source-validation', 'loose'])
         self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'loose'])
         self.cli_commit()
 
         nftables_loose_search = [
             ['fib saddr oif 0', 'drop']
         ]
 
         self.verify_nftables_chain(nftables_loose_search, 'ip raw', 'vyos_global_rpfilter')
         self.verify_nftables_chain(nftables_loose_search, 'ip6 raw', 'vyos_global_rpfilter')
 
     def test_sysfs(self):
         for name, conf in sysfs_config.items():
             paths = glob(conf['sysfs'])
             for path in paths:
                 with open(path, 'r') as f:
                     self.assertEqual(f.read().strip(), conf['default'], msg=path)
 
             self.cli_set(['firewall', 'global-options', name.replace("_", "-"), conf['test_value']])
 
         self.cli_commit()
 
         for name, conf in sysfs_config.items():
             paths = glob(conf['sysfs'])
             for path in paths:
                 with open(path, 'r') as f:
                     self.assertNotEqual(f.read().strip(), conf['default'], msg=path)
 
     def test_timeout_sysctl(self):
         timeout_config = {
             'net.netfilter.nf_conntrack_icmp_timeout' :{
                 'cli'           : ['global-options', 'timeout', 'icmp'],
                 'test_value'    : '180',
                 'default_value' : '30',
             },
             'net.netfilter.nf_conntrack_generic_timeout' :{
                 'cli'           : ['global-options', 'timeout', 'other'],
                 'test_value'    : '1200',
                 'default_value' : '600',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_close_wait' :{
                 'cli'           : ['global-options', 'timeout', 'tcp', 'close-wait'],
                 'test_value'    : '30',
                 'default_value' : '60',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_close' :{
                 'cli'           : ['global-options', 'timeout', 'tcp', 'close'],
                 'test_value'    : '20',
                 'default_value' : '10',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_established' :{
                 'cli'           : ['global-options', 'timeout', 'tcp', 'established'],
                 'test_value'    : '1000',
                 'default_value' : '432000',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_fin_wait' :{
                 'cli'           : ['global-options', 'timeout', 'tcp', 'fin-wait'],
                 'test_value'    : '240',
                 'default_value' : '120',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_last_ack' :{
                 'cli'           : ['global-options', 'timeout', 'tcp', 'last-ack'],
                 'test_value'    : '300',
                 'default_value' : '30',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_syn_recv' :{
                 'cli'           : ['global-options', 'timeout', 'tcp', 'syn-recv'],
                 'test_value'    : '100',
                 'default_value' : '60',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_syn_sent' :{
                 'cli'           : ['global-options', 'timeout', 'tcp', 'syn-sent'],
                 'test_value'    : '300',
                 'default_value' : '120',
             },
             'net.netfilter.nf_conntrack_tcp_timeout_time_wait' :{
                 'cli'           : ['global-options', 'timeout', 'tcp', 'time-wait'],
                 'test_value'    : '303',
                 'default_value' : '120',
             },
             'net.netfilter.nf_conntrack_udp_timeout' :{
                 'cli'           : ['global-options', 'timeout', 'udp', 'other'],
                 'test_value'    : '90',
                 'default_value' : '30',
             },
             'net.netfilter.nf_conntrack_udp_timeout_stream' :{
                 'cli'           : ['global-options', 'timeout', 'udp', 'stream'],
                 'test_value'    : '200',
                 'default_value' : '180',
             },
         }
 
         for parameter, parameter_config in timeout_config.items():
             self.cli_set(['firewall'] + parameter_config['cli'] + [parameter_config['test_value']])
 
         # commit changes
         self.cli_commit()
 
         # validate configuration
         for parameter, parameter_config in timeout_config.items():
             tmp = parameter_config['test_value']
             self.assertEqual(get_sysctl(f'{parameter}'), tmp)
 
         # delete all configuration options and revert back to defaults
         self.cli_delete(['firewall', 'global-options', 'timeout'])
         self.cli_commit()
 
         # validate configuration
         for parameter, parameter_config in timeout_config.items():
             self.assertEqual(get_sysctl(f'{parameter}'), parameter_config['default_value'])
 
 ### Zone
     def test_zone_basic(self):
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketestv6', '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-eth0', 'intra-zone-filtering', 'firewall', 'ipv6-name', 'smoketestv6'])
         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'],
             ['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_search_v6 = [
             ['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 NAME6_smoketestv6'],
             ['jump VYOS_STATE_POLICY6'],
             ['chain VYOS_STATE_POLICY6'],
             ['ct state established', 'log prefix "[STATE-POLICY-EST-A]"', 'accept'],
             ['ct state invalid', 'drop'],
             ['ct state related', 'accept']
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
         self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter')
 
     def test_flow_offload(self):
         self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '10'])
         self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0.10'])
         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.10 }'],
             ['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')
 
     def test_ipsec_metadata_match(self):
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '1', 'ipsec', 'match-ipsec-in'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '2', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in4', 'rule', '2', 'ipsec', 'match-none-in'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '1', 'action', 'continue'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '1', 'ipsec', 'match-ipsec-out'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '2', 'action', 'reject'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-out4', 'rule', '2', 'ipsec', 'match-none-out'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '1', 'ipsec', 'match-ipsec-in'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '2', 'action', 'drop'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-in6', 'rule', '2', 'ipsec', 'match-none-in'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '1', 'action', 'continue'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '1', 'ipsec', 'match-ipsec-out'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '2', 'action', 'reject'])
         self.cli_set(['firewall', 'ipv6', 'name', 'smoketest-ipsec-out6', 'rule', '2', 'ipsec', 'match-none-out'])
 
         self.cli_commit()
 
         nftables_search = [
             ['meta ipsec exists', 'accept comment'],
             ['meta ipsec missing', 'drop comment'],
             ['rt ipsec exists', 'continue comment'],
             ['rt ipsec missing', 'reject comment'],
         ]
 
         self.verify_nftables(nftables_search, 'ip vyos_filter')
         self.verify_nftables(nftables_search, 'ip6 vyos_filter')
 
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4'])
         self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'prerouting', 'raw', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4'])
         
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-out4'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-out4'])
 
         # All valid directional usage of ipsec matches
         self.cli_commit()
 
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in-indirect', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-ipsec-in-indirect', 'rule', '1', 'jump-target', 'smoketest-ipsec-in4'])
 
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '1', 'jump-target', 'smoketest-ipsec-in-indirect'])
 
         # nft does not support ANY usage of 'meta ipsec' under an output hook, it will fail to load cfg
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
     def test_cyclic_jump_validation(self):
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-1', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-1', 'rule', '1', 'jump-target', 'smoketest-cycle-2'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-2', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-2', 'rule', '1', 'jump-target', 'smoketest-cycle-3'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'action', 'accept'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'log'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '1', 'jump-target', 'smoketest-cycle-1'])
 
         # Multi-level jumps are unwise but allowed
         self.cli_commit()
 
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'action', 'jump'])
         self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-cycle-3', 'rule', '1', 'jump-target', 'smoketest-cycle-1'])
 
         # nft will fail to load cyclic jumps in any form, whether the rule is reachable or not. 
         # It should be caught by conf validation. 
         with self.assertRaises(ConfigSessionError):
             self.cli_commit()
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 352d5cbb1..02bf00bcc 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -1,532 +1,567 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 import re
 
 from sys import exit
 
 from vyos.base import Warning
 from vyos.config import Config
 from vyos.configdict import is_node_changed
 from vyos.configdiff import get_config_diff, Diff
 from vyos.configdep import set_dependents, call_dependents
 from vyos.configverify import verify_interface_exists
 from vyos.ethtool import Ethtool
 from vyos.firewall import fqdn_config_parse
 from vyos.firewall import geoip_update
 from vyos.template import render
 from vyos.utils.dict import dict_search_args
 from vyos.utils.dict import dict_search_recursive
 from vyos.utils.process import call
 from vyos.utils.process import cmd
 from vyos.utils.process import rc_cmd
 from vyos import ConfigError
 from vyos import airbag
+from subprocess import run as subp_run
 
 airbag.enable()
 
 nftables_conf = '/run/nftables.conf'
 sysctl_file = r'/run/sysctl/10-vyos-firewall.conf'
 
 valid_groups = [
     'address_group',
     'domain_group',
     'network_group',
     'port_group',
-    'interface_group'
+    'interface_group',
+    ## Added for group ussage in bridge firewall
+    'ipv4_address_group',
+    'ipv6_address_group',
+    'ipv4_network_group',
+    'ipv6_network_group'
 ]
 
 nested_group_types = [
     'address_group', 'network_group', 'mac_group',
     'port_group', 'ipv6_address_group', 'ipv6_network_group'
 ]
 
 snmp_change_type = {
     'unknown': 0,
     'add': 1,
     'delete': 2,
     'change': 3
 }
 snmp_event_source = 1
 snmp_trap_mib = 'VYATTA-TRAP-MIB'
 snmp_trap_name = 'mgmtEventTrap'
 
 def geoip_updated(conf, firewall):
     diff = get_config_diff(conf)
     node_diff = diff.get_child_nodes_diff(['firewall'], expand_nodes=Diff.DELETE, recursive=True)
 
     out = {
         'name': [],
         'ipv6_name': [],
         'deleted_name': [],
         'deleted_ipv6_name': []
     }
     updated = False
 
     for key, path in dict_search_recursive(firewall, 'geoip'):
         set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
         if (path[0] == 'ipv4'):
             out['name'].append(set_name)
         elif (path[0] == 'ipv6'):
             set_name = f'GEOIP_CC6_{path[1]}_{path[2]}_{path[4]}'
             out['ipv6_name'].append(set_name)
 
         updated = True
 
     if 'delete' in node_diff:
         for key, path in dict_search_recursive(node_diff['delete'], 'geoip'):
             set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
             if (path[0] == 'ipv4'):
                 out['deleted_name'].append(set_name)
             elif (path[0] == 'ipv6'):
                 set_name = f'GEOIP_CC_{path[1]}_{path[2]}_{path[4]}'
                 out['deleted_ipv6_name'].append(set_name)
             updated = True
 
     if updated:
         return out
 
     return False
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['firewall']
 
     firewall = conf.get_config_dict(base, key_mangling=('-', '_'),
                                     no_tag_node_value_mangle=True,
                                     get_first_key=True,
                                     with_recursive_defaults=True)
 
 
     firewall['group_resync'] = bool('group' in firewall or is_node_changed(conf, base + ['group']))
     if firewall['group_resync']:
         # Update nat and policy-route as firewall groups were updated
         set_dependents('group_resync', conf)
 
     firewall['geoip_updated'] = geoip_updated(conf, firewall)
 
     fqdn_config_parse(firewall)
 
     set_dependents('conntrack', conf)
 
     return firewall
 
-def verify_jump_target(firewall, root_chain, jump_target, ipv6, recursive=False):
+def verify_jump_target(firewall, hook, jump_target, family, recursive=False):
     targets_seen = []
     targets_pending = [jump_target]
 
     while targets_pending:
         target = targets_pending.pop()
 
-        if not ipv6:
-            if target not in dict_search_args(firewall, 'ipv4', 'name'):
-                raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
-            target_rules = dict_search_args(firewall, 'ipv4', 'name', target, 'rule')
-        else:
-            if target not in dict_search_args(firewall, 'ipv6', 'name'):
-                raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system')
-            target_rules = dict_search_args(firewall, 'ipv6', 'name', target, 'rule')
+        if 'name' not in firewall[family]:
+            raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system')
+        elif target not in dict_search_args(firewall, family, 'name'):
+            raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system')
 
-        no_ipsec_in = root_chain in ('output', )
+        target_rules = dict_search_args(firewall, family, 'name', target, 'rule')
+        no_ipsec_in = hook in ('output', )
 
         if target_rules:
             for target_rule_conf in target_rules.values():
                 # Output hook types will not tolerate 'meta ipsec exists' matches even in jump targets:
                 if no_ipsec_in and (dict_search_args(target_rule_conf, 'ipsec', 'match_ipsec_in') is not None \
                                     or dict_search_args(target_rule_conf, 'ipsec', 'match_none_in') is not None):
-                    if not ipv6:
-                        raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall name {target} rules contain incompatible ipsec inbound matches')
-                    else:
-                        raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall ipv6 name {target} rules contain incompatible ipsec inbound matches')
+                    raise ConfigError(f'Invalid jump-target for {hook}. Firewall {family} name {target} rules contain incompatible ipsec inbound matches')
                 # Make sure we're not looping back on ourselves somewhere:
                 if recursive and 'jump_target' in target_rule_conf:
                     child_target = target_rule_conf['jump_target']
                     if child_target in targets_seen:
-                        if not ipv6:
-                            raise ConfigError(f'Loop detected in jump-targets, firewall name {target} refers to previously traversed name {child_target}')
-                        else:
-                            raise ConfigError(f'Loop detected in jump-targets, firewall ipv6 name {target} refers to previously traversed ipv6 name {child_target}')
+                        raise ConfigError(f'Loop detected in jump-targets, firewall {family} name {target} refers to previously traversed {family} name {child_target}')
                     targets_pending.append(child_target)
                     if len(targets_seen) == 7:
                         path_txt = ' -> '.join(targets_seen)
                         Warning(f'Deep nesting of jump targets has reached 8 levels deep, following the path {path_txt} -> {child_target}!')
 
         targets_seen.append(target)
 
-def verify_rule(firewall, chain_name, rule_conf, ipv6):
+def verify_rule(firewall, family, hook, priority, rule_id, rule_conf):
     if 'action' not in rule_conf:
         raise ConfigError('Rule action must be defined')
 
     if 'jump' in rule_conf['action'] and 'jump_target' not in rule_conf:
         raise ConfigError('Action set to jump, but no jump-target specified')
 
     if 'jump_target' in rule_conf:
         if 'jump' not in rule_conf['action']:
             raise ConfigError('jump-target defined, but action jump needed and it is not defined')
         target = rule_conf['jump_target']
-        if chain_name != 'name': # This is a bit clumsy, but consolidates a chunk of code. 
-            verify_jump_target(firewall, chain_name, target, ipv6, recursive=True)
+        if hook != 'name': # This is a bit clumsy, but consolidates a chunk of code. 
+            verify_jump_target(firewall, hook, target, family, recursive=True)
         else:
-            verify_jump_target(firewall, chain_name, target, ipv6, recursive=False)
+            verify_jump_target(firewall, hook, target, family, recursive=False)
 
     if rule_conf['action'] == 'offload':
         if 'offload_target' not in rule_conf:
             raise ConfigError('Action set to offload, but no offload-target specified')
 
         offload_target = rule_conf['offload_target']
 
         if not dict_search_args(firewall, 'flowtable', offload_target):
             raise ConfigError(f'Invalid offload-target. Flowtable "{offload_target}" does not exist on the system')
 
     if rule_conf['action'] != 'synproxy' and 'synproxy' in rule_conf:
         raise ConfigError('"synproxy" option allowed only for action synproxy')
     if rule_conf['action'] == 'synproxy':
         if 'state' in rule_conf:
             raise ConfigError('For action "synproxy" state cannot be defined')
         if not rule_conf.get('synproxy', {}).get('tcp'):
             raise ConfigError('synproxy TCP MSS is not defined')
         if rule_conf.get('protocol', {}) != 'tcp':
             raise ConfigError('For action "synproxy" the protocol must be set to TCP')
 
     if 'queue_options' in rule_conf:
         if 'queue' not in rule_conf['action']:
             raise ConfigError('queue-options defined, but action queue needed and it is not defined')
         if 'fanout' in rule_conf['queue_options'] and ('queue' not in rule_conf or '-' not in rule_conf['queue']):
             raise ConfigError('queue-options fanout defined, then queue needs to be defined as a range')
 
     if 'queue' in rule_conf and 'queue' not in rule_conf['action']:
         raise ConfigError('queue defined, but action queue needed and it is not defined')
 
     if 'fragment' in rule_conf:
         if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']):
             raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"')
 
     if 'limit' in rule_conf:
         if 'rate' in rule_conf['limit']:
             rate_int = re.sub(r'\D', '', rule_conf['limit']['rate'])
             if int(rate_int) < 1:
                 raise ConfigError('Limit rate integer cannot be less than 1')
 
     if 'ipsec' in rule_conf:
         if {'match_ipsec_in', 'match_none_in'} <= set(rule_conf['ipsec']):
             raise ConfigError('Cannot specify both "match-ipsec" and "match-none"')
         if {'match_ipsec_out', 'match_none_out'} <= set(rule_conf['ipsec']):
             raise ConfigError('Cannot specify both "match-ipsec" and "match-none"')
 
     if 'recent' in rule_conf:
         if not {'count', 'time'} <= set(rule_conf['recent']):
             raise ConfigError('Recent "count" and "time" values must be defined')
 
     tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
     if tcp_flags:
         if dict_search_args(rule_conf, 'protocol') != 'tcp':
             raise ConfigError('Protocol must be tcp when specifying tcp flags')
 
         not_flags = dict_search_args(rule_conf, 'tcp', 'flags', 'not')
         if not_flags:
             duplicates = [flag for flag in tcp_flags if flag in not_flags]
             if duplicates:
                 raise ConfigError(f'Cannot match a tcp flag as set and not set')
 
     if 'protocol' in rule_conf:
-        if rule_conf['protocol'] == 'icmp' and ipv6:
+        if rule_conf['protocol'] == 'icmp' and family == 'ipv6':
             raise ConfigError(f'Cannot match IPv4 ICMP protocol on IPv6, use ipv6-icmp')
-        if rule_conf['protocol'] == 'ipv6-icmp' and not ipv6:
+        if rule_conf['protocol'] == 'ipv6-icmp' and family == 'ipv4':
             raise ConfigError(f'Cannot match IPv6 ICMP protocol on IPv4, use icmp')
 
     for side in ['destination', 'source']:
         if side in rule_conf:
             side_conf = rule_conf[side]
 
             if len({'address', 'fqdn', 'geoip'} & set(side_conf)) > 1:
                 raise ConfigError('Only one of address, fqdn or geoip can be specified')
 
             if 'group' in side_conf:
                 if len({'address_group', 'network_group', 'domain_group'} & set(side_conf['group'])) > 1:
                     raise ConfigError('Only one address-group, network-group or domain-group can be specified')
 
                 for group in valid_groups:
                     if group in side_conf['group']:
                         group_name = side_conf['group'][group]
 
-                        fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
+                        if family == 'ipv6' and group in ['address_group', 'network_group']:
+                            fw_group = f'ipv6_{group}'
+                        elif family == 'bridge':
+                            if group =='ipv4_address_group':
+                                fw_group = 'address_group'
+                            elif group == 'ipv4_network_group':
+                                fw_group = 'network_group'
+                            else:
+                                fw_group = group
+                        else:
+                            fw_group = group
+
                         error_group = fw_group.replace("_", "-")
 
                         if group in ['address_group', 'network_group', 'domain_group']:
                             types = [t for t in ['address', 'fqdn', 'geoip'] if t in side_conf]
                             if types:
                                 raise ConfigError(f'{error_group} and {types[0]} cannot both be defined')
 
                         if group_name and group_name[0] == '!':
                             group_name = group_name[1:]
 
                         group_obj = dict_search_args(firewall, 'group', fw_group, group_name)
 
                         if group_obj is None:
                             raise ConfigError(f'Invalid {error_group} "{group_name}" on firewall rule')
 
                         if not group_obj:
                             Warning(f'{error_group} "{group_name}" has no members!')
 
             if 'port' in side_conf or dict_search_args(side_conf, 'group', 'port_group'):
                 if 'protocol' not in rule_conf:
                     raise ConfigError('Protocol must be defined if specifying a port or port-group')
 
                 if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']:
                     raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group')
 
             if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'):
                 raise ConfigError(f'{side} port-group and port cannot both be defined')
 
     if 'add_address_to_group' in rule_conf:
         for type in ['destination_address', 'source_address']:
             if type in rule_conf['add_address_to_group']:
                 if 'address_group' not in rule_conf['add_address_to_group'][type]:
                     raise ConfigError(f'Dynamic address group must be defined.')
                 else:
                     target = rule_conf['add_address_to_group'][type]['address_group']
-                    fwall_group = 'ipv6_address_group' if ipv6 else 'address_group'
+                    fwall_group = 'ipv6_address_group' if family == 'ipv6' else 'address_group'
                     group_obj = dict_search_args(firewall, 'group', 'dynamic_group', fwall_group, target)
                     if group_obj is None:
                             raise ConfigError(f'Invalid dynamic address group on firewall rule')
 
     if 'log_options' in rule_conf:
         if 'log' not in rule_conf:
             raise ConfigError('log-options defined, but log is not enable')
 
         if 'snapshot_length' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']:
             raise ConfigError('log-options snapshot-length defined, but log group is not define')
 
         if 'queue_threshold' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']:
             raise ConfigError('log-options queue-threshold defined, but log group is not define')
 
     for direction in ['inbound_interface','outbound_interface']:
         if direction in rule_conf:
             if 'name' in rule_conf[direction] and 'group' in rule_conf[direction]:
                 raise ConfigError(f'Cannot specify both interface group and interface name for {direction}')
             if 'group' in rule_conf[direction]:
                 group_name = rule_conf[direction]['group']
                 if group_name[0] == '!':
                     group_name = group_name[1:]
                 group_obj = dict_search_args(firewall, 'group', 'interface_group', group_name)
                 if group_obj is None:
                     raise ConfigError(f'Invalid interface group "{group_name}" on firewall rule')
                 if not group_obj:
                     Warning(f'interface-group "{group_name}" has no members!')
 
 def verify_nested_group(group_name, group, groups, seen):
     if 'include' not in group:
         return
 
     seen.append(group_name)
 
     for g in group['include']:
         if g not in groups:
             raise ConfigError(f'Nested group "{g}" does not exist')
 
         if g in seen:
             raise ConfigError(f'Group "{group_name}" has a circular reference')
 
         if 'include' in groups[g]:
             verify_nested_group(g, groups[g], groups, seen)
 
 def verify_hardware_offload(ifname):
     ethtool = Ethtool(ifname)
     enabled, fixed = ethtool.get_hw_tc_offload()
 
     if not enabled and fixed:
         raise ConfigError(f'Interface "{ifname}" does not support hardware offload')
 
     if not enabled:
         raise ConfigError(f'Interface "{ifname}" requires "offload hw-tc-offload"')
 
 def verify(firewall):
     if 'flowtable' in firewall:
         for flowtable, flowtable_conf in firewall['flowtable'].items():
             if 'interface' not in flowtable_conf:
                 raise ConfigError(f'Flowtable "{flowtable}" requires at least one interface')
 
             for ifname in flowtable_conf['interface']:
                 verify_interface_exists(ifname)
 
             if dict_search_args(flowtable_conf, 'offload') == 'hardware':
                 interfaces = flowtable_conf['interface']
 
                 for ifname in interfaces:
                     verify_hardware_offload(ifname)
 
     if 'group' in firewall:
         for group_type in nested_group_types:
             if group_type in firewall['group']:
                 groups = firewall['group'][group_type]
                 for group_name, group in groups.items():
                     verify_nested_group(group_name, group, groups, [])
 
-    if 'ipv4' in firewall:
-        for name in ['name','forward','input','output', 'prerouting']:
-            if name in firewall['ipv4']:
-                for name_id, name_conf in firewall['ipv4'][name].items():
-                    if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
-                        raise ConfigError('default-action set to jump, but no default-jump-target specified')
-                    if 'default_jump_target' in name_conf:
-                        target = name_conf['default_jump_target']
-                        if 'jump' not in name_conf['default_action']:
-                            raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
-                        if name_conf['default_jump_target'] == name_id:
-                            raise ConfigError(f'Loop detected on default-jump-target.')
-                        verify_jump_target(firewall, name, target, False, recursive=True)
-
-                    if 'rule' in name_conf:
-                        for rule_id, rule_conf in name_conf['rule'].items():
-                            verify_rule(firewall, name, rule_conf, False)
-
-    if 'ipv6' in firewall:
-        for name in ['name','forward','input','output', 'prerouting']:
-            if name in firewall['ipv6']:
-                for name_id, name_conf in firewall['ipv6'][name].items():
-                    if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
-                        raise ConfigError('default-action set to jump, but no default-jump-target specified')
-                    if 'default_jump_target' in name_conf:
-                        target = name_conf['default_jump_target']
-                        if 'jump' not in name_conf['default_action']:
-                            raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
-                        if name_conf['default_jump_target'] == name_id:
-                            raise ConfigError(f'Loop detected on default-jump-target.')
-                        verify_jump_target(firewall, name, target, True, recursive=True)
-
-                    if 'rule' in name_conf:
-                        for rule_id, rule_conf in name_conf['rule'].items():
-                            verify_rule(firewall, name, rule_conf, True)
-
-    #### ZONESSSS
+    for family in ['ipv4', 'ipv6', 'bridge']:
+        if family in firewall:
+            for chain in ['name','forward','input','output', 'prerouting']:
+                if chain in firewall[family]:
+                    for priority, priority_conf in firewall[family][chain].items():
+                        if 'jump' in priority_conf['default_action'] and 'default_jump_target' not in priority_conf:
+                            raise ConfigError('default-action set to jump, but no default-jump-target specified')
+                        if 'default_jump_target' in priority_conf:
+                            target = priority_conf['default_jump_target']
+                            if 'jump' not in priority_conf['default_action']:
+                                raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
+                            if priority_conf['default_jump_target'] == priority:
+                                raise ConfigError(f'Loop detected on default-jump-target.')
+                            if target not in dict_search_args(firewall[family], 'name'):
+                                raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
+                        if 'rule' in priority_conf:
+                            for rule_id, rule_conf in priority_conf['rule'].items():
+                                verify_rule(firewall, family, chain, priority, rule_id, rule_conf)
+
     local_zone = False
     zone_interfaces = []
 
     if 'zone' in firewall:
         for zone, zone_conf in firewall['zone'].items():
             if 'local_zone' not in zone_conf and 'interface' not in zone_conf:
                 raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone')
 
             if 'local_zone' in zone_conf:
                 if local_zone:
                     raise ConfigError('There cannot be multiple local zones')
                 if 'interface' in zone_conf:
                     raise ConfigError('Local zone cannot have interfaces assigned')
                 if 'intra_zone_filtering' in zone_conf:
                     raise ConfigError('Local zone cannot use intra-zone-filtering')
                 local_zone = True
 
             if 'interface' in zone_conf:
                 found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces]
 
                 if found_duplicates:
                     raise ConfigError(f'Interfaces cannot be assigned to multiple zones')
 
                 zone_interfaces += zone_conf['interface']
 
             if 'intra_zone_filtering' in zone_conf:
                 intra_zone = zone_conf['intra_zone_filtering']
 
                 if len(intra_zone) > 1:
                     raise ConfigError('Only one intra-zone-filtering action must be specified')
 
                 if 'firewall' in intra_zone:
                     v4_name = dict_search_args(intra_zone, 'firewall', 'name')
                     if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name):
                         raise ConfigError(f'Firewall name "{v4_name}" does not exist')
 
                     v6_name = dict_search_args(intra_zone, 'firewall', 'ipv6_name')
                     if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name):
                         raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
 
                     if not v4_name and not v6_name:
                         raise ConfigError('No firewall names specified for intra-zone-filtering')
 
             if 'from' in zone_conf:
                 for from_zone, from_conf in zone_conf['from'].items():
                     if from_zone not in firewall['zone']:
                         raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"')
 
                     v4_name = dict_search_args(from_conf, 'firewall', 'name')
                     if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name):
                         raise ConfigError(f'Firewall name "{v4_name}" does not exist')
 
                     v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
                     if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name):
                         raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist')
 
     return None
 
 def generate(firewall):
     if not os.path.exists(nftables_conf):
         firewall['first_install'] = True
 
     if 'zone' in firewall:
         for local_zone, local_zone_conf in firewall['zone'].items():
             if 'local_zone' not in local_zone_conf:
                 continue
 
             local_zone_conf['from_local'] = {}
 
             for zone, zone_conf in firewall['zone'].items():
                 if zone == local_zone or 'from' not in zone_conf:
                     continue
                 if local_zone in zone_conf['from']:
                     local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone]
 
     render(nftables_conf, 'firewall/nftables.j2', firewall)
     render(sysctl_file, 'firewall/sysctl-firewall.conf.j2', firewall)
     return None
 
+def parse_firewall_error(output):
+    # Define the regex patterns to extract the error message and the comment
+    error_pattern = re.compile(r'Error:\s*(.*?)\n')
+    comment_pattern = re.compile(r'comment\s+"([^"]+)"')
+    error_output = []
+
+    # Find all error messages in the output
+    error_matches = error_pattern.findall(output)
+    # Find all comment matches in the output
+    comment_matches = comment_pattern.findall(output)
+
+    if not error_matches or not comment_matches:
+        raise ConfigError(f'Unknown firewall error detected: {output}')
+
+    error_output.append('Fail to apply firewall')
+    # Loop over the matches and process them
+    for error_message, comment in zip(error_matches, comment_matches):
+        # Parse the comment
+        parsed_entries = comment.split('-')
+        family = 'bridge' if parsed_entries[0] == 'bri' else parsed_entries[0]
+        if parsed_entries[1] == 'NAM':
+            chain = 'name'
+        elif parsed_entries[1] == 'FWD':
+            chain = 'forward'
+        elif parsed_entries[1] == 'INP':
+            chain = 'input'
+        elif parsed_entries[1] == 'OUT':
+            chain = 'output'
+        elif parsed_entries[1] == 'PRE':
+            chain = 'prerouting'
+        error_output.append(f'Error found on: firewall {family} {chain} {parsed_entries[2]} rule {parsed_entries[3]}')
+        error_output.append(f'\tError message: {error_message.strip()}')
+
+    raise ConfigError('\n'.join(error_output))
+
 def apply(firewall):
+    # Use nft -c option to check current configuration file
+    completed_process = subp_run(['nft', '-c', '--file', nftables_conf], capture_output=True)
+    install_result = completed_process.returncode
+    if install_result == 1:
+        # We need to handle firewall error
+        output = completed_process.stderr
+        parse_firewall_error(output.decode())
+
+    # No error detected during check, we can apply the new configuration
     install_result, output = rc_cmd(f'nft --file {nftables_conf}')
+    # Double check just in case
     if install_result == 1:
         raise ConfigError(f'Failed to apply firewall: {output}')
 
     # Apply firewall global-options sysctl settings
     cmd(f'sysctl -f {sysctl_file}')
 
     call_dependents()
 
     # T970 Enable a resolver (systemd daemon) that checks
     # domain-group/fqdn addresses and update entries for domains by timeout
     # If router loaded without internet connection or for synchronization
     domain_action = 'stop'
     if dict_search_args(firewall, 'group', 'domain_group') or firewall['ip_fqdn'] or firewall['ip6_fqdn']:
         domain_action = 'restart'
     call(f'systemctl {domain_action} vyos-domain-resolver.service')
 
     if firewall['geoip_updated']:
         # Call helper script to Update set contents
         if 'name' in firewall['geoip_updated'] or 'ipv6_name' in firewall['geoip_updated']:
             print('Updating GeoIP. Please wait...')
             geoip_update(firewall)
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index c9b8ef8fe..76be41ddc 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -1,112 +1,117 @@
 #
 # VyOS specific sysctl settings, see sysctl.conf (5) for information.
 #
 
 # Panic on OOPS
 kernel.panic_on_oops=1
 
 # Timeout before rebooting on panic
 kernel.panic=60
 
 # Send all core files to /var/core/core.program.pid.time
 kernel.core_pattern=/var/core/core-%e-%p-%t
 
 # ARP configuration
 #  arp_filter - allow multiple network interfaces on same subnet
 #  arp_announce - avoid local addresses no on target's subnet
 #  arp_ignore - reply only if target IP is local_address on the interface
 
 #  arp_filter defaults to 1 so set all to 0 so vrrp interfaces can override it.
 net.ipv4.conf.all.arp_filter=0
 
 # https://vyos.dev/T300
 net.ipv4.conf.all.arp_ignore=0
 net.ipv4.conf.all.arp_announce=2
 
 # Enable packet forwarding for IPv4
 net.ipv4.ip_forward=1
 
 # Enable directed broadcast forwarding feature described in rfc1812#section-5.3.5.2 and rfc2644.
 # Note that setting the 'all' entry to 1 doesn't enable directed broadcast forwarding on all interfaces.
 # To enable directed broadcast forwarding on an interface, both the 'all' entry and the input interface entry should be set to 1.
 net.ipv4.conf.all.bc_forwarding=1
 net.ipv4.conf.default.bc_forwarding=0
 
 # if a primary address is removed from an interface promote the
 # secondary address if available
 net.ipv4.conf.all.promote_secondaries=1
 
 # Ignore ICMP broadcasts sent to broadcast/multicast
 net.ipv4.icmp_echo_ignore_broadcasts=1
 
 # Ignore bogus ICMP errors
 net.ipv4.icmp_ignore_bogus_error_responses=1
 
 # Send ICMP responses with primary address of exiting interface
 net.ipv4.icmp_errors_use_inbound_ifaddr=1
 
 # Log packets with impossible addresses to kernel log
 net.ipv4.conf.all.log_martians=1
 
 # Do not ignore all ICMP ECHO requests by default
 net.ipv4.icmp_echo_ignore_all=0
 
 # Disable source validation by default
 net.ipv4.conf.all.rp_filter=0
 net.ipv4.conf.default.rp_filter=0
 
 # Enable tcp syn-cookies by default
 net.ipv4.tcp_syncookies=1
 
 # Disable accept_redirects by default for any interface
 net.ipv4.conf.all.accept_redirects=0
 net.ipv4.conf.default.accept_redirects=0
 net.ipv6.conf.all.accept_redirects=0
 net.ipv6.conf.default.accept_redirects=0
 
 # Disable accept_source_route by default
 net.ipv4.conf.all.accept_source_route=0
 net.ipv4.conf.default.accept_source_route=0
 net.ipv6.conf.all.accept_source_route=0
 net.ipv6.conf.default.accept_source_route=0
 
 # Enable send_redirects by default
 net.ipv4.conf.all.send_redirects=1
 net.ipv4.conf.default.send_redirects=1
 
 # Increase size of buffer for netlink
 net.core.rmem_max=2097152
 
 # Remove IPv4 and IPv6 routes from forward information base when link goes down
 net.ipv4.conf.all.ignore_routes_with_linkdown=1
 net.ipv4.conf.default.ignore_routes_with_linkdown=1
 net.ipv6.conf.all.ignore_routes_with_linkdown=1
 net.ipv6.conf.default.ignore_routes_with_linkdown=1
 
 # Enable packet forwarding for IPv6
 net.ipv6.conf.all.forwarding=1
 
 # Increase route table limit
 net.ipv6.route.max_size = 262144
 
 # Do not forget IPv6 addresses when a link goes down
 net.ipv6.conf.default.keep_addr_on_down=1
 net.ipv6.conf.all.keep_addr_on_down=1
 net.ipv6.route.skip_notify_on_dev_down=1
 
 # Default value of 20 seems to interfere with larger OSPF and VRRP setups
 net.ipv4.igmp_max_memberships = 512
 
 # Enable global RFS (Receive Flow Steering) configuration. RFS is inactive
 # until explicitly configured at the interface level
 net.core.rps_sock_flow_entries = 32768
 
 # Congestion control
 net.core.default_qdisc=fq_codel
 net.ipv4.tcp_congestion_control=bbr
 
 # Disable IPv6 Segment Routing packets by default
 net.ipv6.conf.all.seg6_enabled = 0
 net.ipv6.conf.default.seg6_enabled = 0
 
 net.vrf.strict_mode = 1
+
+# https://vyos.dev/T6570
+# By default, do not forward traffic from bridge to IPvX layer
+net.bridge.bridge-nf-call-iptables = 0
+net.bridge.bridge-nf-call-ip6tables = 0
\ No newline at end of file