]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
enable hostfw for ipv4 only
[pve-firewall.git] / src / PVE / Firewall.pm
index b2bcc32e5ca685cfafd84cc05019094c3ee6a925..3123550fb24805e6ee96c4a8613674dc24bc2e6d 100644 (file)
@@ -19,7 +19,8 @@ use PVE::Tools qw(run_command lock_file dir_glob_foreach);
 use Encode;
 
 my $hostfw_conf_filename = "/etc/pve/local/host.fw";
-my $clusterfw_conf_filename = "/etc/pve/firewall/cluster.fw";
+my $pvefw_conf_dir = "/etc/pve/firewall";
+my $clusterfw_conf_filename = "$pvefw_conf_dir/cluster.fw";
 
 # dynamically include PVE::QemuServer and PVE::OpenVZ
 # to avoid dependency problems
@@ -488,7 +489,8 @@ my $pve_fw_parsed_macros;
 my $pve_fw_macro_descr;
 my $pve_fw_preferred_macro_names = {};
 
-my $pve_std_chains = {
+my $pve_std_chains = {};
+$pve_std_chains->{4} = {
     'PVEFW-SET-ACCEPT-MARK' => [
        "-j MARK --set-mark 1",
     ],
@@ -759,7 +761,10 @@ sub local_network {
     return $__local_network;
 }
 
-my $max_iptables_ipset_name_length = 27;
+# ipset names are limited to 31 characters, and we use '_swap' 
+# suffix for atomic update, for example PVEFW-${VMID}-${ipset_name}_swap
+
+my $max_iptables_ipset_name_length = 31 - length("_swap");
 
 sub compute_ipset_chain_name {
     my ($vmid, $ipset_name) = @_;
@@ -776,6 +781,12 @@ sub compute_ipset_chain_name {
     return "PVEFW-$id";
 }
 
+sub compute_ipfilter_ipset_name {
+    my ($iface) = @_;
+
+    return "ipfilter-$iface";
+}
+
 sub parse_address_list {
     my ($str) = @_;
 
@@ -791,16 +802,28 @@ sub parse_address_list {
 
     my $count = 0;
     my $iprange = 0;
+    my $ipversion;
+
     foreach my $elem (split(/,/, $str)) {
        $count++;
-       if (!Net::IP->new($elem)) {
+       my $ip = Net::IP->new($elem);
+       if (!$ip) {
            my $err = Net::IP::Error();
            die "invalid IP address: $err\n";
        }
        $iprange = 1 if $elem =~ m/-/;
+
+       my $new_ipversion = Net::IP::ip_is_ipv6($ip->ip()) ? 6 : 4;
+
+       die "detected mixed ipv4/ipv6 addresses in address list '$str'\n"
+           if $ipversion && ($new_ipversion != $ipversion);
+
+       $ipversion = $new_ipversion;
     }
 
-    die "you can use a range in a list\n" if $iprange && $count > 1;
+    die "you can't use a range in a list\n" if $iprange && $count > 1;
+
+    return $ipversion;
 }
 
 sub parse_port_name_number_or_range {
@@ -842,8 +865,8 @@ sub pve_fw_verify_port_spec {
    return $portstr;
 }
 
-PVE::JSONSchema::register_format('pve-fw-v4addr-spec', \&pve_fw_verify_v4addr_spec);
-sub pve_fw_verify_v4addr_spec {
+PVE::JSONSchema::register_format('pve-fw-addr-spec', \&pve_fw_verify_addr_spec);
+sub pve_fw_verify_addr_spec {
    my ($list) = @_;
 
    parse_address_list($list);
@@ -942,11 +965,11 @@ my $rule_properties = {
     },
     iface => get_standard_option('pve-iface', { optional => 1 }),
     source => {
-       type => 'string', format => 'pve-fw-v4addr-spec',
+       type => 'string', format => 'pve-fw-addr-spec',
        optional => 1,
     },
     dest => {
-       type => 'string', format => 'pve-fw-v4addr-spec',
+       type => 'string', format => 'pve-fw-addr-spec',
        optional => 1,
     },
     proto => {
@@ -1157,14 +1180,21 @@ sub verify_rule {
            if !$rule->{proto};
     }
 
+    my $ipversion;
+
     if ($rule->{source}) {
-       eval { parse_address_list($rule->{source}); };
+       eval { $ipversion = parse_address_list($rule->{source}); };
        &$add_error('source', $@) if $@;
        &$check_ipset_or_alias_property('source');
     }
 
     if ($rule->{dest}) {
-       eval { parse_address_list($rule->{dest}); };
+       eval { 
+           my $dest_ipversion = parse_address_list($rule->{dest}); 
+           die "detected mixed ipv4/ipv6 adresses in rule\n"
+               if defined($ipversion) && ($dest_ipversion != $ipversion);
+           $ipversion = $dest_ipversion;
+       };
        &$add_error('dest', $@) if $@;
        &$check_ipset_or_alias_property('dest');
     }
@@ -1184,6 +1214,7 @@ sub verify_rule {
     }
 
     $rule->{errors} = $errors if $error_count;
+    $rule->{ipversion} = $ipversion if $ipversion;
 
     return $rule;
 }
@@ -1204,6 +1235,46 @@ sub copy_rule_data {
     return $rule;
 }
 
+sub rules_modify_permissions {
+    my ($rule_env) = @_;
+
+    if ($rule_env eq 'host') {
+       return {
+           check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
+       };
+    } elsif ($rule_env eq 'cluster' || $rule_env eq 'group') {
+       return {
+           check => ['perm', '/', [ 'Sys.Modify' ]],
+       };
+    } elsif ($rule_env eq 'vm' ||   $rule_env eq 'ct') {
+       return {
+           check => ['perm', '/vms/{vmid}', [ 'VM.Config.Network' ]],
+       }
+    }
+
+    return undef;
+}
+
+sub rules_audit_permissions {
+    my ($rule_env) = @_;
+
+    if ($rule_env eq 'host') {
+       return {
+           check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
+       };
+    } elsif ($rule_env eq 'cluster' || $rule_env eq 'group') {
+       return {
+           check => ['perm', '/', [ 'Sys.Audit' ]],
+       };
+    } elsif ($rule_env eq 'vm' ||   $rule_env eq 'ct') {
+       return {
+           check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
+       }
+    }
+
+    return undef;
+}
+
 # core functions
 my $bridge_firewall_enabled = 0;
 
@@ -1623,7 +1694,7 @@ sub ruleset_chain_add_input_filters {
 }
 
 sub ruleset_create_vm_chain {
-    my ($ruleset, $chain, $options, $macaddr, $direction) = @_;
+    my ($ruleset, $chain, $options, $macaddr, $ipfilter_ipset, $direction) = @_;
 
     ruleset_create_chain($ruleset, $chain);
     my $accept = generate_nfqueue($options);
@@ -1642,6 +1713,9 @@ sub ruleset_create_vm_chain {
        if (defined($macaddr) && !(defined($options->{macfilter}) && $options->{macfilter} == 0)) {
            ruleset_addrule($ruleset, $chain, "-m mac ! --mac-source $macaddr -j DROP");
        }
+       if ($ipfilter_ipset) {
+           ruleset_addrule($ruleset, $chain, "-m set ! --match-set $ipfilter_ipset src -j DROP");
+       }
        ruleset_addrule($ruleset, $chain, "-j MARK --set-mark 0"); # clear mark
     }
 }
@@ -1667,7 +1741,7 @@ sub ruleset_add_group_rule {
 }
 
 sub ruleset_generate_vm_rules {
-    my ($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, $netid, $direction, $options) = @_;
+    my ($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, $netid, $direction, $options, $ipversion) = @_;
 
     my $lc_direction = lc($direction);
 
@@ -1676,6 +1750,8 @@ sub ruleset_generate_vm_rules {
     foreach my $rule (@$rules) {
        next if $rule->{iface} && $rule->{iface} ne $netid;
        next if !$rule->{enable} || $rule->{errors};
+       next if $rule->{ipversion} && ($rule->{ipversion} != $ipversion);
+
        if ($rule->{type} eq 'group') {
            ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, $direction,
                                   $direction eq 'OUT' ? 'RETURN' : $in_accept);
@@ -1731,7 +1807,7 @@ sub ruleset_generate_vm_ipsrules {
 }
 
 sub generate_venet_rules_direction {
-    my ($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, $direction) = @_;
+    my ($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, $direction, $ipversion) = @_;
 
     my $lc_direction = lc($direction);
 
@@ -1742,9 +1818,9 @@ sub generate_venet_rules_direction {
 
     my $chain = "venet0-$vmid-$direction";
 
-    ruleset_create_vm_chain($ruleset, $chain, $options, undef, $direction);
+    ruleset_create_vm_chain($ruleset, $chain, $options, undef, undef, $direction);
 
-    ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, 'venet', $direction);
+    ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, 'venet', $direction, undef, $ipversion);
 
     # implement policy
     my $policy;
@@ -1773,7 +1849,7 @@ sub generate_venet_rules_direction {
 }
 
 sub generate_tap_rules_direction {
-    my ($ruleset, $cluster_conf, $iface, $netid, $macaddr, $vmfw_conf, $vmid, $direction) = @_;
+    my ($ruleset, $cluster_conf, $iface, $netid, $macaddr, $vmfw_conf, $vmid, $direction, $ipversion) = @_;
 
     my $lc_direction = lc($direction);
 
@@ -1784,24 +1860,34 @@ sub generate_tap_rules_direction {
 
     my $tapchain = "$iface-$direction";
 
-    ruleset_create_vm_chain($ruleset, $tapchain, $options, $macaddr, $direction);
+    my $ipfilter_name = compute_ipfilter_ipset_name($netid);
+    my $ipfilter_ipset = compute_ipset_chain_name($vmid, $ipfilter_name)
+       if $vmfw_conf->{ipset}->{$ipfilter_name};       
 
-    ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $tapchain, $netid, $direction, $options);
+    # create chain with mac and ip filter
+    ruleset_create_vm_chain($ruleset, $tapchain, $options, $macaddr, $ipfilter_ipset, $direction);
 
-    ruleset_generate_vm_ipsrules($ruleset, $options, $direction, $iface);
+    if ($options->{enable}) {
+       ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $tapchain, $netid, $direction, $options, $ipversion);
 
-    # implement policy
-    my $policy;
+       ruleset_generate_vm_ipsrules($ruleset, $options, $direction, $iface);
 
-    if ($direction eq 'OUT') {
-       $policy = $options->{policy_out} || 'ACCEPT'; # allow everything by default
-    } else {
+       # implement policy
+       my $policy;
+
+       if ($direction eq 'OUT') {
+           $policy = $options->{policy_out} || 'ACCEPT'; # allow everything by default
+       } else {
        $policy = $options->{policy_in} || 'DROP'; # allow nothing by default
-    }
+       }
 
-    my $accept = generate_nfqueue($options);
-    my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : $accept;
-    ruleset_add_chain_policy($ruleset, $tapchain, $vmid, $policy, $loglevel, $accept_action);
+       my $accept = generate_nfqueue($options);
+       my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : $accept;
+       ruleset_add_chain_policy($ruleset, $tapchain, $vmid, $policy, $loglevel, $accept_action);
+    } else {
+       my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : 'ACCEPT';
+       ruleset_add_chain_policy($ruleset, $tapchain, $vmid, 'ACCEPT', $loglevel, $accept_action);
+    }
 
     # plug the tap chain to bridge chain
     if ($direction eq 'IN') {
@@ -1971,8 +2057,6 @@ for (my $i = 0; $i < $MAX_NETS; $i++)  {
 sub parse_fw_rule {
     my ($prefix, $line, $cluster_conf, $fw_conf, $rule_env, $verbose) = @_;
 
-    chomp $line;
-
     my $orig_line = $line;
 
     my $rule = {};
@@ -2066,7 +2150,6 @@ sub parse_vmfw_option {
        $opt = lc($1);
        $value = $2;
     } else {
-       chomp $line;
        die "can't parse option '$line'\n"
     }
 
@@ -2090,7 +2173,6 @@ sub parse_hostfw_option {
        $opt = lc($1);
        $value = int($2);
     } else {
-       chomp $line;
        die "can't parse option '$line'\n"
     }
 
@@ -2109,7 +2191,6 @@ sub parse_clusterfw_option {
        $opt = lc($1);
        $value = uc($3);
     } else {
-       chomp $line;
        die "can't parse option '$line'\n"
     }
 
@@ -2164,6 +2245,8 @@ sub generic_fw_config_parser {
        next if $line =~ m/^#/;
        next if $line =~ m/^\s*$/;
 
+       chomp $line;
+
        my $linenr = $fh->input_line_number();
        my $prefix = "$filename (line $linenr)";
 
@@ -2284,6 +2367,7 @@ sub generic_fw_config_parser {
                }
            };
            if (my $err = $@) {
+               chomp $err;
                $errors->{cidr} = $err;
            }
 
@@ -2292,6 +2376,13 @@ sub generic_fw_config_parser {
            $entry->{comment} = $comment if $comment;
            $entry->{errors} =  $errors if $errors;
 
+           if ($verbose && $errors) {
+               warn "$prefix - errors in ipset '$group': $line\n";
+               foreach my $p (keys %{$errors}) {
+                   warn "  $p: $errors->{$p}\n";
+               }
+           }
+
            push @{$res->{$section}->{$group}}, $entry;
        } else {
            warn "$prefix: skip line - unknown section\n";
@@ -2396,7 +2487,7 @@ sub load_vmfw_conf {
 
     my $vmfw_conf = {};
 
-    $dir = "/etc/pve/firewall" if !defined($dir);
+    $dir = $pvefw_conf_dir if !defined($dir);
 
     my $filename = "$dir/$vmid.fw";
     if (my $fh = IO::File->new($filename, O_RDONLY)) {
@@ -2516,21 +2607,23 @@ sub save_vmfw_conf {
     my $raw = '';
 
     my $options = $vmfw_conf->{options};
-    $raw .= &$format_options($options) if scalar(keys %$options);
+    $raw .= &$format_options($options) if $options && scalar(keys %$options);
 
     my $aliases = $vmfw_conf->{aliases};
-    $raw .= &$format_aliases($aliases) if scalar(keys %$aliases);
+    $raw .= &$format_aliases($aliases) if $aliases && scalar(keys %$aliases);
 
-    $raw .= &$format_ipsets($vmfw_conf);
+    $raw .= &$format_ipsets($vmfw_conf) if $vmfw_conf->{ipset};
 
     my $rules = $vmfw_conf->{rules} || [];
-    if (scalar(@$rules)) {
+    if ($rules && scalar(@$rules)) {
        $raw .= "[RULES]\n\n";
        $raw .= &$format_rules($rules, 1);
        $raw .= "\n";
     }
 
-    my $filename = "/etc/pve/firewall/$vmid.fw";
+    mkdir $pvefw_conf_dir;
+
+    my $filename = "$pvefw_conf_dir/$vmid.fw";
     PVE::Tools::file_set_contents($filename, $raw);
 }
 
@@ -2571,29 +2664,35 @@ sub get_option_log_level {
 }
 
 sub generate_std_chains {
-    my ($ruleset, $options) = @_;
+    my ($ruleset, $options, $ipversion) = @_;
+
+    my $std_chains = $pve_std_chains->{$ipversion} || die "internal error";
 
     my $loglevel = get_option_log_level($options, 'smurf_log_level');
 
-    # same as shorewall smurflog.
-    my $chain = 'PVEFW-smurflog';
-    $pve_std_chains->{$chain} = [];
+    my $chain;
 
-    push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
-    push @{$pve_std_chains->{$chain}}, "-j DROP";
+    if ($ipversion == 4) {
+       # same as shorewall smurflog.
+       $chain = 'PVEFW-smurflog';
+       $std_chains->{$chain} = [];
+       
+       push @{$std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
+       push @{$std_chains->{$chain}}, "-j DROP";
+    }
 
     # same as shorewall logflags action.
     $loglevel = get_option_log_level($options, 'tcp_flags_log_level');
     $chain = 'PVEFW-logflags';
-    $pve_std_chains->{$chain} = [];
+    $std_chains->{$chain} = [];
 
     # fixme: is this correctly logged by pvewf-logger? (ther is no --log-ip-options for NFLOG)
-    push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
-    push @{$pve_std_chains->{$chain}}, "-j DROP";
+    push @{$std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
+    push @{$std_chains->{$chain}}, "-j DROP";
 
-    foreach my $chain (keys %$pve_std_chains) {
+    foreach my $chain (keys %$std_chains) {
        ruleset_create_chain($ruleset, $chain);
-       foreach my $rule (@{$pve_std_chains->{$chain}}) {
+       foreach my $rule (@{$std_chains->{$chain}}) {
            if (ref($rule)) {
                ruleset_generate_rule($ruleset, $chain, $rule);
            } else {
@@ -2629,6 +2728,7 @@ sub generate_ipset {
     # remove duplicates
     my $nethash = {};
     foreach my $entry (@$options) {
+       next if $entry->{errors}; # skip entries with errors
        eval {
            my $cidr = resolve_alias($clusterfw_conf, $fw_conf, $entry->{cidr});
            $nethash->{$cidr} = { cidr => $cidr, nomatch => $entry->{nomatch} };
@@ -2679,33 +2779,36 @@ sub save_clusterfw_conf {
     my $raw = '';
 
     my $options = $cluster_conf->{options};
-    $raw .= &$format_options($options) if scalar(keys %$options);
+    $raw .= &$format_options($options) if $options && scalar(keys %$options);
 
     my $aliases = $cluster_conf->{aliases};
-    $raw .= &$format_aliases($aliases) if scalar(keys %$aliases);
+    $raw .= &$format_aliases($aliases) if $aliases && scalar(keys %$aliases);
 
-    $raw .= &$format_ipsets($cluster_conf);
+    $raw .= &$format_ipsets($cluster_conf) if $cluster_conf->{ipset};
  
     my $rules = $cluster_conf->{rules};
-    if (scalar(@$rules)) {
+    if ($rules && scalar(@$rules)) {
        $raw .= "[RULES]\n\n";
        $raw .= &$format_rules($rules, 1);
        $raw .= "\n";
     }
 
-    foreach my $group (sort keys %{$cluster_conf->{groups}}) {
-       my $rules = $cluster_conf->{groups}->{$group};
-       if (my $comment = $cluster_conf->{group_comments}->{$group}) {
-           my $utf8comment = encode('utf8', $comment);
-           $raw .= "[group $group] # $utf8comment\n\n";
-       } else {
-           $raw .= "[group $group]\n\n";
-       }
+    if ($cluster_conf->{groups}) {
+       foreach my $group (sort keys %{$cluster_conf->{groups}}) {
+           my $rules = $cluster_conf->{groups}->{$group};
+           if (my $comment = $cluster_conf->{group_comments}->{$group}) {
+               my $utf8comment = encode('utf8', $comment);
+               $raw .= "[group $group] # $utf8comment\n\n";
+           } else {
+               $raw .= "[group $group]\n\n";
+           }
 
-       $raw .= &$format_rules($rules, 0);
-       $raw .= "\n";
+           $raw .= &$format_rules($rules, 0);
+           $raw .= "\n";
+       }
     }
 
+    mkdir $pvefw_conf_dir;
     PVE::Tools::file_set_contents($clusterfw_conf_filename, $raw);
 }
 
@@ -2727,10 +2830,10 @@ sub save_hostfw_conf {
     my $raw = '';
 
     my $options = $hostfw_conf->{options};
-    $raw .= &$format_options($options) if scalar(keys %$options);
+    $raw .= &$format_options($options) if $options && scalar(keys %$options);
 
     my $rules = $hostfw_conf->{rules};
-    if (scalar(@$rules)) {
+    if ($rules && scalar(@$rules)) {
        $raw .= "[RULES]\n\n";
        $raw .= &$format_rules($rules, 1);
        $raw .= "\n";
@@ -2762,6 +2865,13 @@ sub compile {
        $vmfw_configs = read_vm_firewall_configs($cluster_conf, $vmdata, undef, $verbose);
     }
 
+    my ($ruleset, $ipset_ruleset) = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 4, $verbose);
+    return ($ruleset, $ipset_ruleset);
+}
+
+sub compile_iptables_filter {
+    my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $ipversion, $verbose) = @_;
+
     $cluster_conf->{ipset}->{venet0} = [];
     my $venet0_ipset_chain = compute_ipset_chain_name(0, 'venet0');
 
@@ -2775,6 +2885,8 @@ sub compile {
 
     push @{$cluster_conf->{ipset}->{management}}, { cidr => $localnet };
 
+    return ({}, {}) if !$cluster_conf->{options}->{enable};
+
     my $ruleset = {};
 
     ruleset_create_chain($ruleset, "PVEFW-INPUT");
@@ -2807,13 +2919,13 @@ sub compile {
 
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o venet0 -m set --match-set ${venet0_ipset_chain} dst -j PVEFW-VENET-IN");
 
-    generate_std_chains($ruleset, $hostfw_options);
+    generate_std_chains($ruleset, $hostfw_options, $ipversion);
 
     my $hostfw_enable = !(defined($hostfw_options->{enable}) && ($hostfw_options->{enable} == 0));
 
     my $ipset_ruleset = {};
 
-    if ($hostfw_enable) {
+    if ($hostfw_enable && $ipversion eq 4) {
        eval { enable_host_firewall($ruleset, $hostfw_conf, $cluster_conf); };
        warn $@ if $@; # just to be sure - should not happen
     }
@@ -2826,7 +2938,6 @@ sub compile {
            my $conf = $vmdata->{qemu}->{$vmid};
            my $vmfw_conf = $vmfw_configs->{$vmid};
            return if !$vmfw_conf;
-           return if !$vmfw_conf->{options}->{enable};
 
            generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf);
 
@@ -2838,9 +2949,9 @@ sub compile {
 
                my $macaddr = $net->{macaddr};
                generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
-                                            $vmfw_conf, $vmid, 'IN');
+                                            $vmfw_conf, $vmid, 'IN', $ipversion);
                generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
-                                            $vmfw_conf, $vmid, 'OUT');
+                                            $vmfw_conf, $vmid, 'OUT', $ipversion);
            }
        };
        warn $@ if $@; # just to be sure - should not happen
@@ -2853,38 +2964,42 @@ sub compile {
 
            my $vmfw_conf = $vmfw_configs->{$vmid};
            return if !$vmfw_conf;
-           return if !$vmfw_conf->{options}->{enable};
 
            generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf);
 
-           if ($conf->{ip_address} && $conf->{ip_address}->{value}) {
-               my $ip = $conf->{ip_address}->{value};
-               $ip =~ s/\s+/,/g;
-               parse_address_list($ip); # make sure we have a valid $ip list
+           if ($vmfw_conf->{options}->{enable}) {
+               if ($conf->{ip_address} && $conf->{ip_address}->{value}) {
+                   my $ip = $conf->{ip_address}->{value};
+                   $ip =~ s/\s+/,/g;
 
-               my @ips = split(',', $ip);
+                   my @ips = ();
 
-               foreach my $singleip (@ips) {
-                   my $venet0ipset = {};
-                   $venet0ipset->{cidr} = $singleip;
-                   push @{$cluster_conf->{ipset}->{venet0}}, $venet0ipset;
-               }
+                   foreach my $singleip (split(',', $ip)) {
+                       my $singleip_ver = parse_address_list($singleip); # make sure we have a valid $ip list
+                       push @{$cluster_conf->{ipset}->{venet0}}, { cidr => $singleip };
+                       push @ips, $singleip if $singleip_ver == $ipversion;
+                   }
 
-               generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'IN');
-               generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'OUT');
+                   if (scalar(@ips)) {
+                       my $ip_list = join(',', @ips);
+                       generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip_list, 'IN', $ipversion);
+                       generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip_list, 'OUT', $ipversion);
+                   }
+               }
            }
 
            if ($conf->{netif} && $conf->{netif}->{value}) {
                my $netif = PVE::OpenVZ::parse_netif($conf->{netif}->{value});
                foreach my $netid (keys %$netif) {
                    my $d = $netif->{$netid};
-
+                   my $bridge = $d->{bridge};
+                   next if !$bridge || $bridge !~ m/^vmbr\d+(v(\d+))?f$/; # firewall enabled ?
                    my $macaddr = $d->{mac};
                    my $iface = $d->{host_ifname};
                    generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
-                                                $vmfw_conf, $vmid, 'IN');
+                                                $vmfw_conf, $vmid, 'IN', $ipversion);
                    generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
-                                                $vmfw_conf, $vmid, 'OUT');
+                                                $vmfw_conf, $vmid, 'OUT', $ipversion);
                }
            }
        };
@@ -2963,8 +3078,9 @@ sub get_ruleset_cmdlist {
     }
 
     foreach my $h (qw(INPUT OUTPUT FORWARD)) {
-       if (!$hooks->{$h}) {
-           $cmdlist .= "-A $h -j PVEFW-$h\n";
+       my $chain = "PVEFW-$h";
+       if ($ruleset->{$chain} && !$hooks->{$h}) {
+           $cmdlist .= "-A $h -j $chain\n";
        }
     }
 
@@ -3164,6 +3280,17 @@ sub remove_pvefw_chains {
     $cmdlist .= "COMMIT\n";
 
     iptables_restore_cmdlist($cmdlist);
+
+    my $ipset_chains = ipset_get_chains();
+
+    $cmdlist = "";
+    foreach my $chain (keys %$ipset_chains) {
+       $cmdlist .= "flush $chain\n";
+       $cmdlist .= "destroy $chain\n";
+    }
+
+    ipset_restore_cmdlist($cmdlist) if $cmdlist; 
 }
 
 sub init {
@@ -3182,11 +3309,7 @@ sub update {
        my $cluster_conf = load_clusterfw_conf();
        my $cluster_options = $cluster_conf->{options};
 
-       my $enable = $cluster_options->{enable};
-
-       die "Firewall is disabled - cannot start\n" if !$enable;
-
-       if (!$enable) {
+       if (!$cluster_options->{enable}) {
            PVE::Firewall::remove_pvefw_chains();
            return;
        }