]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
ruleset_generate_vm_rule: avoid multiple calls to generate_nfqueue()
[pve-firewall.git] / src / PVE / Firewall.pm
index aa3555a9786da965961064ee4103cd9f2d28a8cd..01de542d3305e8dc397ad15110eab2d5775bd0b3 100644 (file)
@@ -15,7 +15,7 @@ use File::Basename;
 use File::Path;
 use IO::File;
 use Net::IP;
-use PVE::Tools qw(run_command lock_file);
+use PVE::Tools qw(run_command lock_file dir_glob_foreach);
 use Encode;
 
 my $hostfw_conf_filename = "/etc/pve/local/host.fw";
@@ -56,20 +56,20 @@ PVE::JSONSchema::register_standard_option('ipset-name', {
     maxLength => 20,                     
 });
 
-PVE::JSONSchema::register_standard_option('pve-config-digest', {
-    description => "This digest/signature can be used to prevent updates when the original configuration was changed by somebody else.",
-    type => 'string',
+PVE::JSONSchema::register_standard_option('pve-fw-loglevel' => {
+    description => "Log level.",
+    type => 'string', 
+    enum => ['emerg', 'alert', 'crit', 'err', 'warning', 'notice', 'info', 'debug', 'nolog'],
     optional => 1,
-    maxLength => 27,
-    minLength => 27,                                     
 });
 
-my $security_group_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
+my $security_group_name_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
+my $ip_alias_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
 
 PVE::JSONSchema::register_standard_option('pve-security-group-name', {
     description => "Security Group name.",
     type => 'string',
-    pattern => $security_group_pattern,
+    pattern => $security_group_name_pattern,
     minLength => 2,
     maxLength => 20,                             
 });
@@ -713,6 +713,7 @@ sub parse_address_list {
     my ($str) = @_;
 
     return if $str =~ m/^(\+)(\S+)$/; # ipset ref
+    return if $str =~ m/^${ip_alias_pattern}$/;
 
     my $count = 0;
     my $iprange = 0;
@@ -792,6 +793,52 @@ sub pve_fw_verify_protocol_spec {
 
 # helper function for API
 
+sub copy_opject_with_digest {
+    my ($object) = @_;
+
+    my $sha = Digest::SHA->new('sha1');
+
+    my $res = {};
+    foreach my $k (sort keys %$object) {
+       my $v = $object->{$k};
+       next if !defined($v);
+       $res->{$k} = $v;
+       $sha->add($k, ':', $v, "\n");
+    }
+
+    my $digest = $sha->b64digest;
+
+    $res->{digest} = $digest;
+
+    return wantarray ? ($res, $digest) : $res;
+}
+
+sub copy_list_with_digest {
+    my ($list) = @_;
+
+    my $sha = Digest::SHA->new('sha1');
+
+    my $res = [];
+    foreach my $entry (@$list) {
+       my $data = {};
+       foreach my $k (sort keys %$entry) {
+           my $v = $entry->{$k};
+           next if !defined($v);
+           $data->{$k} = $v;
+           $sha->add($k, ':', $v, "\n");
+       }
+       push @$res, $data;
+    }
+
+    my $digest = $sha->b64digest;
+
+    foreach my $entry (@$res) {
+       $entry->{digest} = $digest;
+    }
+
+    return wantarray ? ($res, $digest) : $res;
+}
+
 my $rule_properties = {
     pos => {
        description => "Update rule at position <pos>.",
@@ -809,7 +856,7 @@ my $rule_properties = {
        description => "Rule action ('ACCEPT', 'DROP', 'REJECT') or security group name.",
        type => 'string',
        optional => 1,
-       pattern => $security_group_pattern,
+       pattern => $security_group_name_pattern,
        maxLength => 20,
        minLength => 2,
     },
@@ -849,23 +896,6 @@ my $rule_properties = {
     },
 };
 
-sub cleanup_fw_rule {
-    my ($rule, $digest, $pos) = @_;
-
-    my $r = {};
-
-    foreach my $k (keys %$rule) {
-       next if !$rule_properties->{$k};
-       my $v = $rule->{$k};
-       next if !defined($v);
-       $r->{$k} = $v;
-       $r->{digest} = $digest;
-       $r->{pos} = $pos;
-    }
-
-    return $r;
-}
-
 sub add_rule_properties {
     my ($properties) = @_;
 
@@ -958,7 +988,7 @@ sub verify_rule {
        raise_param_exc({ type => "security groups not allowed"}) 
            if !$allow_groups;
        raise_param_exc({ action => "invalid characters in security group name"}) 
-           if $rule->{action} !~ m/^${security_group_pattern}$/;
+           if $rule->{action} !~ m/^${security_group_name_pattern}$/;
     } else {
        raise_param_exc({ type => "unknown rule type '$type'"});
     }
@@ -1064,7 +1094,7 @@ sub iptables_get_chains {
 
        return 1 if $name =~ m/^venet0-\d+-(:?IN|OUT)$/;
 
-       return 1 if $name =~ m/^vmbr\d+-(:?FW|IN|OUT|IPS)$/;
+       return 1 if $name =~ m/^vmbr\d+(v\d+)?-(:?FW|IN|OUT|IPS)$/;
        return 1 if $name =~ m/^GROUP-(:?[^\s\-]+)-(:?IN|OUT)$/;
 
        return undef;
@@ -1176,9 +1206,16 @@ sub ruleset_generate_cmdstr {
     my $dest = $rule->{dest};
 
     if ($source) {
-        if ($source =~ m/^(\+)(\S+)$/) {
-           die "no such ipset $2" if !$cluster_conf->{ipset}->{$2};
-           push @cmd, "-m set --match-set PVEFW-$2 src";
+        if ($source =~ m/^\+/) {
+           if ($source =~ m/^\+(${security_group_name_pattern})$/) {
+               die "no such ipset '$1'\n" if !$cluster_conf->{ipset}->{$1};
+               push @cmd, "-m set --match-set PVEFW-$1 src";
+           } else {
+               die "invalid security group name '$source'\n";
+           }
+       } elsif ($source =~ m/^${ip_alias_pattern}$/){
+           die "no such alias $source\n" if !$cluster_conf->{aliases}->{$source};
+           push @cmd, "-s $cluster_conf->{aliases}->{$source}";
 
         } elsif ($source =~ m/\-/){
            push @cmd, "-m iprange --src-range $source";
@@ -1189,15 +1226,22 @@ sub ruleset_generate_cmdstr {
     }
 
     if ($dest) {
-        if ($dest =~ m/^(\+)(\S+)$/) {
-           die "no such ipset $2" if !$cluster_conf->{ipset}->{$2};
-           push @cmd, "-m set --match-set PVEFW-$2 dst";
+        if ($dest =~ m/^\+/) {
+           if ($dest =~ m/^\+(${security_group_name_pattern})$/) {
+               die "no such ipset '$1'\n" if !$cluster_conf->{ipset}->{$1};
+               push @cmd, "-m set --match-set PVEFW-$1 dst";
+           } else {
+               die "invalid security group name '$dest'\n";
+           }
+       } elsif ($dest =~ m/^${ip_alias_pattern}$/){
+           die "no such alias $dest" if !$cluster_conf->{aliases}->{$dest};
+           push @cmd, "-d $cluster_conf->{aliases}->{$dest}";
 
         } elsif ($dest =~ m/^(\d+)\.(\d+).(\d+).(\d+)\-(\d+)\.(\d+).(\d+).(\d+)$/){
            push @cmd, "-m iprange --dst-range $dest";
 
        } else {
-           push @cmd, "-s $dest";
+           push @cmd, "-d $dest";
         }
     }
 
@@ -1265,11 +1309,18 @@ sub ruleset_generate_rule {
        $rules = [ $rule ];
     }
 
+    # update all or nothing
+
+    my @cmds = ();
     foreach my $tmp (@$rules) {
        if (my $cmdstr = ruleset_generate_cmdstr($ruleset, $chain, $tmp, $actions, $goto, $cluster_conf)) {
-           ruleset_addrule($ruleset, $chain, $cmdstr);
+           push @cmds, $cmdstr;
        }
     }
+
+    foreach my $cmdstr (@cmds) {
+       ruleset_addrule($ruleset, $chain, $cmdstr);
+    }
 }
 
 sub ruleset_generate_rule_insert {
@@ -1341,7 +1392,7 @@ sub ruleset_addlog {
 }
 
 sub generate_bridge_chains {
-    my ($ruleset, $hostfw_conf, $bridge, $routing_table) = @_;
+    my ($ruleset, $hostfw_conf, $bridge, $routing_table, $bridges_config) = @_;
 
     my $options = $hostfw_conf->{options} || {};
 
@@ -1356,12 +1407,26 @@ sub generate_bridge_chains {
 
     if (!ruleset_chain_exist($ruleset, "$bridge-OUT")) {
        ruleset_create_chain($ruleset, "$bridge-OUT");
+
+       if($options->{optimize}){
+           foreach my $interface (@{$bridges_config->{$bridge}}) {
+               ruleset_addrule($ruleset, "$bridge-OUT", "-m physdev --physdev-is-bridged --physdev-in $interface -g PVEFW-SET-ACCEPT-MARK");
+           }
+       }
+
        ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-in -j $bridge-OUT");
        ruleset_insertrule($ruleset, "PVEFW-INPUT", "-i $bridge -m physdev --physdev-is-in -j $bridge-OUT");
     }
 
     if (!ruleset_chain_exist($ruleset, "$bridge-IN")) {
        ruleset_create_chain($ruleset, "$bridge-IN");
+
+       if($options->{optimize}){
+           foreach my $interface (@{$bridges_config->{$bridge}}) {
+               ruleset_addrule($ruleset, "$bridge-IN", "-m physdev --physdev-is-bridged --physdev-out $interface -j ACCEPT");
+           }
+       }
+
        ruleset_addrule($ruleset, "$bridge-FW", "-m physdev --physdev-is-out -j $bridge-IN");
        ruleset_addrule($ruleset, "$bridge-FW", "-m mark --mark 1 -j ACCEPT");
        # accept traffic to unmanaged bridge ports
@@ -1397,12 +1462,12 @@ sub ruleset_add_chain_policy {
 }
 
 sub ruleset_create_vm_chain {
-    my ($ruleset, $chain, $options, $macaddr, $direction) = @_;
+    my ($ruleset, $chain, $options, $host_options, $macaddr, $direction) = @_;
 
     ruleset_create_chain($ruleset, $chain);
     my $accept = generate_nfqueue($options);
 
-    if (!(defined($options->{nosmurfs}) && $options->{nosmurfs} == 0)) {
+    if (!(defined($host_options->{nosmurfs}) && $host_options->{nosmurfs} == 0)) {
        ruleset_addrule($ruleset, $chain, "-m conntrack --ctstate INVALID,NEW -j PVEFW-smurfs");
     }
 
@@ -1416,7 +1481,7 @@ sub ruleset_create_vm_chain {
        }
     }
 
-    if ($options->{tcpflags}) {
+    if ($host_options->{tcpflags}) {
        ruleset_addrule($ruleset, $chain, "-p tcp -j PVEFW-tcpflags");
     }
 
@@ -1440,6 +1505,8 @@ sub ruleset_generate_vm_rules {
 
     my $lc_direction = lc($direction);
 
+    my $in_accept = generate_nfqueue($options);
+
     foreach my $rule (@$rules) {
        next if $rule->{iface} && $rule->{iface} ne $netid;
        next if !$rule->{enable};
@@ -1462,8 +1529,7 @@ sub ruleset_generate_vm_rules {
                ruleset_generate_rule($ruleset, $chain, $rule,
                                      { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }, undef, $cluster_conf);
            } else {
-               my $accept = generate_nfqueue($options);
-               ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept , REJECT => "PVEFW-reject" }, undef, $cluster_conf);
+               ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $in_accept , REJECT => "PVEFW-reject" }, undef, $cluster_conf);
            }
        }
     }
@@ -1472,22 +1538,20 @@ sub ruleset_generate_vm_rules {
 sub generate_nfqueue {
     my ($options) = @_;
 
-    my $action = "";
-    if($options->{ips}){
-       $action = "NFQUEUE";
-       if($options->{ips_queues} && $options->{ips_queues} =~ m/^(\d+)(:(\d+))?$/) {
-           if(defined($3) && defined($1)) {
+    if ($options->{ips}) {
+       my $action = "NFQUEUE";
+       if ($options->{ips_queues} && $options->{ips_queues} =~ m/^(\d+)(:(\d+))?$/) {
+           if (defined($3) && defined($1)) {
                $action .= " --queue-balance $1:$3";
-           }elsif (defined($1)) {
+           } elsif (defined($1)) {
                $action .= " --queue-num $1";
            }
        }
-       $action .= " --queue-bypass";
-    }else{
-       $action = "ACCEPT";
+       $action .= " --queue-bypass" if $feature_ipset_nomatch; #need kernel 3.10
+       return $action;
+    } else {
+       return "ACCEPT";
     }
-
-    return $action;
 }
 
 sub ruleset_generate_vm_ipsrules {
@@ -1510,7 +1574,7 @@ sub ruleset_generate_vm_ipsrules {
 }
 
 sub generate_venet_rules_direction {
-    my ($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, $direction) = @_;
+    my ($ruleset, $cluster_conf, $hostfw_conf, $vmfw_conf, $vmid, $ip, $direction) = @_;
 
     parse_address_list($ip); # make sure we have a valid $ip list
 
@@ -1519,11 +1583,12 @@ sub generate_venet_rules_direction {
     my $rules = $vmfw_conf->{rules};
 
     my $options = $vmfw_conf->{options};
+    my $hostfw_options = $vmfw_conf->{options};
     my $loglevel = get_option_log_level($options, "log_level_${lc_direction}");
 
     my $chain = "venet0-$vmid-$direction";
 
-    ruleset_create_vm_chain($ruleset, $chain, $options, undef, $direction);
+    ruleset_create_vm_chain($ruleset, $chain, $options, $hostfw_options, undef, $direction);
 
     ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $chain, 'venet', $direction);
 
@@ -1565,18 +1630,19 @@ sub generate_venet_rules_direction {
 }
 
 sub generate_tap_rules_direction {
-    my ($ruleset, $cluster_conf, $iface, $netid, $macaddr, $vmfw_conf, $vmid, $bridge, $direction) = @_;
+    my ($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr, $vmfw_conf, $vmid, $bridge, $direction) = @_;
 
     my $lc_direction = lc($direction);
 
     my $rules = $vmfw_conf->{rules};
 
     my $options = $vmfw_conf->{options};
+    my $hostfw_options = $hostfw_conf->{options};
     my $loglevel = get_option_log_level($options, "log_level_${lc_direction}");
 
     my $tapchain = "$iface-$direction";
 
-    ruleset_create_vm_chain($ruleset, $tapchain, $options, $macaddr, $direction);
+    ruleset_create_vm_chain($ruleset, $tapchain, $options, $hostfw_options, $macaddr, $direction);
 
     ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $tapchain, $netid, $direction, $options);
 
@@ -1597,10 +1663,10 @@ sub generate_tap_rules_direction {
 
     # plug the tap chain to bridge chain
     if ($direction eq 'IN') {
-       ruleset_insertrule($ruleset, "$bridge-IN",
+       ruleset_addrule($ruleset, "$bridge-IN",
                           "-m physdev --physdev-is-bridged --physdev-out $iface -j $tapchain");
     } else {
-       ruleset_insertrule($ruleset, "$bridge-OUT",
+       ruleset_addrule($ruleset, "$bridge-OUT",
                           "-m physdev --physdev-in $iface -j $tapchain");
     }
 }
@@ -1608,10 +1674,10 @@ sub generate_tap_rules_direction {
 sub enable_host_firewall {
     my ($ruleset, $hostfw_conf, $cluster_conf) = @_;
 
-    # fixme: allow security groups
-
     my $options = $hostfw_conf->{options};
+    my $cluster_options = $cluster_conf->{options};
     my $rules = $hostfw_conf->{rules};
+    my $cluster_rules = $cluster_conf->{rules};
 
     # host inbound firewall
     my $chain = "PVEFW-HOST-IN";
@@ -1637,13 +1703,14 @@ sub enable_host_firewall {
     # we use RETURN because we need to check also tap rules
     my $accept_action = 'RETURN';
 
-    foreach my $rule (@$rules) {
+    # add host rules first, so that cluster wide rules can be overwritten
+    foreach my $rule (@$rules, @$cluster_rules) {
        next if $rule->{type} ne 'in';
        ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, undef, $cluster_conf);
     }
 
     # implement input policy
-    my $policy = $options->{policy_in} || 'DROP'; # allow nothing by default
+    my $policy = $cluster_options->{policy_in} || 'DROP'; # allow nothing by default
     ruleset_add_chain_policy($ruleset, $chain, 0, $policy, $loglevel, $accept_action);
 
     # host outbound firewall
@@ -1662,13 +1729,14 @@ sub enable_host_firewall {
     # we use RETURN because we may want to check other thigs later
     $accept_action = 'RETURN';
 
-    foreach my $rule (@$rules) {
+    # add host rules first, so that cluster wide rules can be overwritten
+    foreach my $rule (@$rules, @$cluster_rules) {
        next if $rule->{type} ne 'out';
        ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, undef, $cluster_conf);
     }
 
     # implement output policy
-    $policy = $options->{policy_out} || 'ACCEPT'; # allow everything by default
+    $policy = $cluster_options->{policy_out} || 'ACCEPT'; # allow everything by default
     ruleset_add_chain_policy($ruleset, $chain, 0, $policy, $loglevel, $accept_action);
 
     ruleset_addrule($ruleset, "PVEFW-OUTPUT", "-j PVEFW-HOST-OUT");
@@ -1756,7 +1824,7 @@ sub parse_fw_rule {
        die "wrong number of rule elements\n" if scalar(@data) != 3;
        die "groups disabled\n" if !$allow_groups;
 
-       die "invalid characters in group name\n" if $action !~ m/^${security_group_pattern}$/;
+       die "invalid characters in group name\n" if $action !~ m/^${security_group_name_pattern}$/;
     } else {
        die "unknown rule type '$type'\n";
     }
@@ -1802,7 +1870,7 @@ sub parse_vmfw_option {
 
     my $loglevels = "emerg|alert|crit|err|warning|notice|info|debug|nolog";
 
-    if ($line =~ m/^(enable|dhcp|macfilter|nosmurfs|tcpflags|ips):\s*(0|1)\s*$/i) {
+    if ($line =~ m/^(enable|dhcp|macfilter|ips):\s*(0|1)\s*$/i) {
        $opt = lc($1);
        $value = int($2);
     } elsif ($line =~ m/^(log_level_in|log_level_out):\s*(($loglevels)\s*)?$/i) {
@@ -1829,16 +1897,13 @@ sub parse_hostfw_option {
 
     my $loglevels = "emerg|alert|crit|err|warning|notice|info|debug|nolog";
 
-    if ($line =~ m/^(enable|dhcp|nosmurfs|tcpflags|allow_bridge_route|optimize):\s*(0|1)\s*$/i) {
+    if ($line =~ m/^(enable|nosmurfs|tcpflags|allow_bridge_route|optimize):\s*(0|1)\s*$/i) {
        $opt = lc($1);
        $value = int($2);
     } elsif ($line =~ m/^(log_level_in|log_level_out|tcp_flags_log_level|smurf_log_level):\s*(($loglevels)\s*)?$/i) {
        $opt = lc($1);
        $value = $2 ? lc($3) : '';
-    } elsif ($line =~ m/^(policy_(in|out)):\s*(ACCEPT|DROP|REJECT)\s*$/i) {
-       $opt = lc($1);
-       $value = uc($3);
-    } elsif ($line =~ m/^(nf_conntrack_max):\s*(\d+)\s*$/i) {
+    } elsif ($line =~ m/^(nf_conntrack_max|nf_conntrack_tcp_timeout_established):\s*(\d+)\s*$/i) {
        $opt = lc($1);
        $value = int($2);
     } else {
@@ -1857,6 +1922,9 @@ sub parse_clusterfw_option {
     if ($line =~ m/^(enable):\s*(0|1)\s*$/i) {
        $opt = lc($1);
        $value = int($2);
+    } elsif ($line =~ m/^(policy_(in|out)):\s*(ACCEPT|DROP|REJECT)\s*$/i) {
+       $opt = lc($1);
+       $value = uc($3);
     } else {
        chomp $line;
        die "can't parse option '$line'\n"
@@ -1865,6 +1933,25 @@ sub parse_clusterfw_option {
     return ($opt, $value);
 }
 
+sub parse_clusterfw_alias {
+    my ($line) = @_;
+
+    my ($opt, $value);
+    if ($line =~ m/^(\S+)\s(\S+)$/) {
+       $opt = lc($1);
+       if($2){
+           $2 =~ s|/32$||;
+           pve_verify_ipv4_or_cidr($2) if $2;
+           $value = $2;
+       }
+    } else {
+       chomp $line;
+       die "can't parse option '$line'\n";
+    }
+
+    return ($opt, $value);
+}
+
 sub parse_vm_fw_rules {
     my ($filename, $fh) = @_;
 
@@ -1872,11 +1959,7 @@ sub parse_vm_fw_rules {
 
     my $section;
 
-    my $digest = Digest::SHA->new('sha1');
-
     while (defined(my $line = <$fh>)) {
-       $digest->add($line);
-
        next if $line =~ m/^#/;
        next if $line =~ m/^\s*$/;
 
@@ -1914,8 +1997,6 @@ sub parse_vm_fw_rules {
        push @{$res->{$section}}, $rule;
     }
 
-    $res->{digest} = $digest->b64digest;
-
     return $res;
 }
 
@@ -1926,11 +2007,7 @@ sub parse_host_fw_rules {
 
     my $section;
 
-    my $digest = Digest::SHA->new('sha1');
-
     while (defined(my $line = <$fh>)) {
-       $digest->add($line);
-
        next if $line =~ m/^#/;
        next if $line =~ m/^\s*$/;
 
@@ -1968,8 +2045,6 @@ sub parse_host_fw_rules {
        push @{$res->{$section}}, $rule;
     }
 
-$res->{digest} = $digest->b64digest;
-
     return $res;
 }
 
@@ -1984,14 +2059,11 @@ sub parse_cluster_fw_rules {
        options => {}, 
        groups => {}, 
        group_comments => {}, 
-       ipset => {} 
+       ipset => {} ,
+       ipset_comments => {}, 
     };
 
-    my $digest = Digest::SHA->new('sha1');
-
     while (defined(my $line = <$fh>)) {
-       $digest->add($line);
-
        next if $line =~ m/^#/;
        next if $line =~ m/^\s*$/;
 
@@ -2003,12 +2075,18 @@ sub parse_cluster_fw_rules {
            next;
        }
 
+       if ($line =~ m/^\[aliases\]$/i) {
+           $section = 'aliases';
+           next;
+       }
+
        if ($line =~ m/^\[group\s+(\S+)\]\s*(?:#\s*(.*?)\s*)?$/i) {
            $section = 'groups';
            $group = lc($1);
            my $comment = $2;
            $res->{$section}->{$group} = [];
-           $res->{group_comments}->{$group} = $comment if $comment;
+           $res->{group_comments}->{$group} =  decode('utf8', $comment)
+               if $comment;
            next;
        }
 
@@ -2017,10 +2095,13 @@ sub parse_cluster_fw_rules {
            next;
        }
 
-       if ($line =~ m/^\[ipset\s+(\S+)\]\s*$/i) {
+       if ($line =~ m/^\[ipset\s+(\S+)\]\s*(?:#\s*(.*?)\s*)?$/i) {
            $section = 'ipset';
            $group = lc($1);
+           my $comment = $2;
            $res->{$section}->{$group} = [];
+           $res->{ipset_comments}->{$group} = decode('utf8', $comment) 
+               if $comment;
            next;
        }
 
@@ -2035,6 +2116,12 @@ sub parse_cluster_fw_rules {
                $res->{options}->{$opt} = $value;
            };
            warn "$prefix: $@" if $@;
+       } elsif ($section eq 'aliases') {
+           eval {
+               my ($opt, $value) = parse_clusterfw_alias($line);
+               $res->{aliases}->{$opt} = $value;
+           };
+           warn "$prefix: $@" if $@;
        } elsif ($section eq 'rules') {
            my $rule;
            eval { $rule = parse_fw_rule($line, 1, 1); };
@@ -2059,12 +2146,14 @@ sub parse_cluster_fw_rules {
            my $nomatch = $1;
            my $cidr = $2;
 
-           $cidr =~ s|/32$||;
+           if($cidr !~ m/^${ip_alias_pattern}$/) {
+               $cidr =~ s|/32$||;
            
-           eval { pve_verify_ipv4_or_cidr($cidr); };
-           if (my $err = $@) {
-               warn "$prefix: $cidr - $err";
-               next;
+               eval { pve_verify_ipv4_or_cidr($cidr); };
+               if (my $err = $@) {
+                   warn "$prefix: $cidr - $err";
+                   next;
+               }
            }
 
            my $entry = { cidr => $cidr }; 
@@ -2075,7 +2164,6 @@ sub parse_cluster_fw_rules {
        }
     }
 
-    $res->{digest} = $digest->b64digest;
     return $res;
 }
 
@@ -2127,6 +2215,22 @@ sub read_local_vm_config {
     return $vmdata;
 };
 
+sub read_bridges_config {
+
+    my $bridgehash = {};
+
+    dir_glob_foreach('/sys/class/net', 'vmbr(\d+)', sub {
+        my ($bridge) = @_;
+
+       dir_glob_foreach("/sys/class/net/$bridge/brif", '((eth|bond)(\d+)(\.(\d+))?)', sub {
+           my ($interface) = @_;
+           push @{$bridgehash->{$bridge}}, $interface;
+       });
+    });
+
+    return $bridgehash;
+};
+
 sub load_vmfw_conf {
     my ($vmid) = @_;
 
@@ -2267,6 +2371,7 @@ sub generate_std_chains {
 
     # same as shorewall smurflog.
     my $chain = 'PVEFW-smurflog';
+    $pve_std_chains->{$chain} = [];
 
     push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel;
     push @{$pve_std_chains->{$chain}}, "-j DROP";
@@ -2274,6 +2379,8 @@ sub generate_std_chains {
     # same as shorewall logflags action.
     $loglevel = get_option_log_level($options, 'tcp_flags_log_level');
     $chain = 'PVEFW-logflags';
+    $pve_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";
@@ -2294,12 +2401,12 @@ sub generate_ipset_chains {
     my ($ipset_ruleset, $fw_conf) = @_;
 
     foreach my $ipset (keys %{$fw_conf->{ipset}}) {
-       generate_ipset($ipset_ruleset, "PVEFW-$ipset", $fw_conf->{ipset}->{$ipset});
+       generate_ipset($ipset_ruleset, "PVEFW-$ipset", $fw_conf->{ipset}->{$ipset}, $fw_conf->{aliases});
     }
 }
 
 sub generate_ipset {
-    my ($ipset_ruleset, $name, $options) = @_;
+    my ($ipset_ruleset, $name, $options, $aliases) = @_;
 
     my $hashsize = scalar(@$options);
     if ($hashsize <= 64) {
@@ -2313,6 +2420,14 @@ sub generate_ipset {
     # remove duplicates
     my $nethash = {};
     foreach my $entry (@$options) {
+       my $cidr = $entry->{cidr};
+       if ($cidr =~ m/^${ip_alias_pattern}$/) {
+           if ($aliases->{$cidr}) {
+               $entry->{cidr} = $aliases->{$cidr};
+           } else {
+               warn "no such alias '$cidr'\n" if !$aliases->{$cidr};
+           }
+       }
        $nethash->{$entry->{cidr}} = $entry;
     }
 
@@ -2411,7 +2526,12 @@ sub save_clusterfw_conf {
     $raw .= &$format_options($options) if scalar(keys %$options);
 
     foreach my $ipset (sort keys %{$cluster_conf->{ipset}}) {
-       $raw .= "[IPSET $ipset]\n\n";
+       if (my $comment = $cluster_conf->{ipset_comments}->{$ipset}) {
+           my $utf8comment = encode('utf8', $comment);
+           $raw .= "[IPSET $ipset] # $utf8comment\n\n";
+       } else {
+           $raw .= "[IPSET $ipset]\n\n";
+       }
        my $options = $cluster_conf->{ipset}->{$ipset};
        $raw .= &$format_ipset($options);
        $raw .= "\n";
@@ -2427,7 +2547,8 @@ sub save_clusterfw_conf {
     foreach my $group (sort keys %{$cluster_conf->{groups}}) {
        my $rules = $cluster_conf->{groups}->{$group};
        if (my $comment = $cluster_conf->{group_comments}->{$group}) {
-           $raw .= "[group $group] # $comment\n\n";
+           my $utf8comment = encode('utf8', $comment);
+           $raw .= "[group $group] # $utf8comment\n\n";
        } else {
            $raw .= "[group $group]\n\n";
        }
@@ -2467,12 +2588,17 @@ sub save_hostfw_conf {
 }
 
 sub compile {
+    my ($cluster_conf, $hostfw_conf) = @_;
+
+    $cluster_conf = load_clusterfw_conf() if !$cluster_conf;
+    $hostfw_conf = load_hostfw_conf() if !$hostfw_conf;
+
     my $vmdata = read_local_vm_config();
     my $vmfw_configs = read_vm_firewall_configs($vmdata);
 
     my $routing_table = read_proc_net_route();
-
-    my $cluster_conf = load_clusterfw_conf();
+    
+    my $bridges_config = read_bridges_config();
 
     my $ipset_ruleset = {};
     generate_ipset_chains($ipset_ruleset, $cluster_conf);
@@ -2484,7 +2610,6 @@ sub compile {
 
     ruleset_create_chain($ruleset, "PVEFW-FORWARD");
 
-    my $hostfw_conf = load_hostfw_conf();
     my $hostfw_options = $hostfw_conf->{options} || {};
 
     generate_std_chains($ruleset, $hostfw_options);
@@ -2511,12 +2636,12 @@ sub compile {
 
            $bridge .= "v$net->{tag}" if $net->{tag};
 
-           generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table);
+           generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table, $bridges_config);
 
            my $macaddr = $net->{macaddr};
-           generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
+           generate_tap_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr,
                                         $vmfw_conf, $vmid, $bridge, 'IN');
-           generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
+           generate_tap_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr,
                                         $vmfw_conf, $vmid, $bridge, 'OUT');
        }
     }
@@ -2531,8 +2656,8 @@ sub compile {
 
        if ($conf->{ip_address} && $conf->{ip_address}->{value}) {
            my $ip = $conf->{ip_address}->{value};
-           generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'IN');
-           generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'OUT');
+           generate_venet_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $vmfw_conf, $vmid, $ip, 'IN');
+           generate_venet_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $vmfw_conf, $vmid, $ip, 'OUT');
        }
 
        if ($conf->{netif} && $conf->{netif}->{value}) {
@@ -2545,13 +2670,13 @@ sub compile {
                    next; # fixme?
                }
 
-               generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table);
+               generate_bridge_chains($ruleset, $hostfw_conf, $bridge, $routing_table, $bridges_config);
 
                my $macaddr = $d->{mac};
                my $iface = $d->{host_ifname};
-               generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
+               generate_tap_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr,
                                             $vmfw_conf, $vmid, $bridge, 'IN');
-               generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,
+               generate_tap_rules_direction($ruleset, $cluster_conf, $hostfw_conf, $iface, $netid, $macaddr,
                                             $vmfw_conf, $vmid, $bridge, 'OUT');
            }
        }
@@ -2580,7 +2705,7 @@ sub compile {
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j DROP");
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j DROP");
 
-    return wantarray ? ($ruleset, $hostfw_conf, $ipset_ruleset) : $ruleset;
+    return ($ruleset, $ipset_ruleset);
 }
 
 sub get_ruleset_status {
@@ -2747,8 +2872,6 @@ sub apply_ruleset {
 
     enable_bridge_firewall();
 
-    update_nf_conntrack_max($hostfw_conf);
-
     my ($ipset_create_cmdlist, $ipset_delete_cmdlist, $ipset_changes) =
        get_ipset_cmdlist($ipset_ruleset, undef, $verbose);
 
@@ -2787,6 +2910,11 @@ sub apply_ruleset {
     }
 
     die "unable to apply firewall changes\n" if $errors;
+
+    update_nf_conntrack_max($hostfw_conf);
+
+    update_nf_conntrack_tcp_timeout_established($hostfw_conf);
+
 }
 
 sub update_nf_conntrack_max {
@@ -2813,6 +2941,16 @@ sub update_nf_conntrack_max {
     }
 }
 
+sub update_nf_conntrack_tcp_timeout_established {
+    my ($hostfw_conf) = @_;
+
+    my $options = $hostfw_conf->{options} || {};
+
+    my $value = defined($options->{nf_conntrack_tcp_timeout_established}) ? $options->{nf_conntrack_tcp_timeout_established} : 432000;
+
+    PVE::ProcFSTools::write_proc_entry("/proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established", $value);
+}
+
 sub remove_pvefw_chains {
 
     my ($chash, $hooks) = iptables_get_chains();
@@ -2840,9 +2978,25 @@ sub update {
     my ($start, $verbose) = @_;
 
     my $code = sub {
+
+       my $cluster_conf = load_clusterfw_conf();
+       my $cluster_options = $cluster_conf->{options};
+
+       my $enable = $cluster_options->{enable};
+
        my $status = read_pvefw_status();
 
-       my ($ruleset, $hostfw_conf, $ipset_ruleset) = compile();
+       die "Firewall is disabled - cannot start\n" if !$enable && $start;
+
+       if (!$enable) {
+           PVE::Firewall::remove_pvefw_chains();
+           print "Firewall disabled\n" if $verbose;
+           return;
+       }
+
+       my $hostfw_conf = load_hostfw_conf();
+
+       my ($ruleset, $ipset_ruleset) = compile($cluster_conf, $hostfw_conf);
 
        if ($start || $status eq 'active') {