]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
fix iptables-restore failing if icmp-type value > 255
[pve-firewall.git] / src / PVE / Firewall.pm
index 4a534d06f9a2fc02d406403f9d109e786d598e96..da1784cf66cbbf7c4f72771724f8193691b9cd41 100644 (file)
@@ -394,6 +394,10 @@ my $pve_fw_macros = {
        { action => 'PARAM', proto => 'udp', dport => '5632' },
        { action => 'PARAM', proto => 'tcp', dport => '5631' },
     ],
+    'PMG' => [
+       "Proxmox Mail Gateway web interface",
+       { action => 'PARAM', proto => 'tcp', dport => '8006' },
+    ],
     'POP3' => [
        "POP3 traffic",
        { action => 'PARAM', proto => 'tcp', dport => '110' },
@@ -1619,6 +1623,8 @@ sub verify_rule {
                if !$allow_groups;
            &$add_error('action', "invalid characters in security group name")
                if $action && ($action !~ m/^${security_group_name_pattern}$/);
+           &$add_error('action', "security group '$action' does not exist")
+               if $action && !defined($cluster_conf->{groups}->{$action});
        } else {
            &$add_error('type', "unknown rule type '$type'");
        }
@@ -2037,11 +2043,17 @@ sub ipt_rule_to_cmds {
                    # Note: we use dport to store --icmp-type
                    die "unknown icmp-type '$rule->{dport}'\n"
                        if $rule->{dport} !~ /^\d+$/ && !defined($icmp_type_names->{$rule->{dport}});
+                   # values for icmp-type range between 0 and 255
+                   # higher values and iptables-restore fails
+                   die "invalid icmp-type '$rule->{dport}'\n" if ($rule->{dport} =~ m/^(\d+)$/) && ($1 > 255);
                    push @match, "-m icmp --icmp-type $rule->{dport}";
                } elsif ($proto eq 'icmpv6') {
                    # Note: we use dport to store --icmpv6-type
                    die "unknown icmpv6-type '$rule->{dport}'\n"
                        if $rule->{dport} !~ /^\d+$/ && !defined($icmpv6_type_names->{$rule->{dport}});
+                   # values for icmpv6-type range between 0 and 255
+                   # higher values and iptables-restore fails
+                   die "invalid icmpv6-type '$rule->{dport}'\n" if ($rule->{dport} =~ m/^(\d+)$/) && ($1 > 255);
                    push @match, "-m icmpv6 --icmpv6-type $rule->{dport}";
                } elsif (!$PROTOCOLS_WITH_PORTS->{$proto}) {
                    die "protocol $proto does not have ports\n";
@@ -2487,6 +2499,7 @@ sub enable_host_firewall {
        $rule->{iface_in} = $rule->{iface} if $rule->{iface};
 
        eval {
+           $rule->{logmsg} = "$rule->{action}: ";
            if ($rule->{type} eq 'group') {
                ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'IN', $accept_action, $ipversion);
            } elsif ($rule->{type} eq 'in') {
@@ -2892,6 +2905,8 @@ sub generic_fw_config_parser {
     }
     return {} if !$raw;
 
+    my $curr_group_keys = {};
+
     my $linenr = 0;
     while ($raw =~ /^\h*(.*?)\h*$/gm) {
        my $line = $1;
@@ -2952,6 +2967,8 @@ sub generic_fw_config_parser {
            }
 
            $res->{$section}->{$group} = [];
+           $curr_group_keys = {};
+
            $res->{ipset_comments}->{$group} = decode('utf8', $comment)
                if $comment;
            next;
@@ -3016,6 +3033,8 @@ sub generic_fw_config_parser {
                } else {
                    $cidr = parse_ip_or_cidr($cidr);
                }
+               die "duplicate ipset entry for '$cidr'\n"
+                   if defined($curr_group_keys->{$cidr});
            };
            if (my $err = $@) {
                chomp $err;
@@ -3039,6 +3058,7 @@ sub generic_fw_config_parser {
            }
 
            push @{$res->{$section}->{$group}}, $entry;
+           $curr_group_keys->{$cidr} = 1;
        } else {
            warn "$prefix: skip line - unknown section\n";
            next;
@@ -3048,6 +3068,8 @@ sub generic_fw_config_parser {
     return $res;
 }
 
+# this is only used to prevent concurrent runs of rule compilation/application
+# see lock_*_conf for cfs locks protectiong config modification
 sub run_locked {
     my ($code, @param) = @_;
 
@@ -3096,6 +3118,18 @@ sub read_local_vm_config {
     return $vmdata;
 };
 
+sub lock_vmfw_conf {
+    my ($vmid, $timeout, $code, @param) = @_;
+
+    die "can't lock VM firewall config for undefined VMID\n"
+       if !defined($vmid);
+
+    my $res = PVE::Cluster::cfs_lock_firewall("vm-$vmid", $timeout, $code, @param);
+    die $@ if $@;
+
+    return $res;
+}
+
 sub load_vmfw_conf {
     my ($cluster_conf, $rule_env, $vmid, $dir) = @_;
 
@@ -3202,7 +3236,13 @@ my $format_ipsets = sub {
 
        my $nethash = {};
        foreach my $entry (@$options) {
-           $nethash->{$entry->{cidr}} = $entry;
+           my $cidr = $entry->{cidr};
+           if (defined($nethash->{$cidr})) {
+               warn "ignoring duplicate ipset entry '$cidr'\n";
+               next;
+           }
+
+           $nethash->{$cidr} = $entry;
        }
 
        foreach my $cidr (sort keys %$nethash) {
@@ -3263,13 +3303,15 @@ sub clone_vmfw_conf {
     my $sourcevm_conffile = "$pvefw_conf_dir/$vmid.fw";
     my $clonevm_conffile = "$pvefw_conf_dir/$newid.fw";
 
-    if (-f $clonevm_conffile) {
-       unlink $clonevm_conffile;
-    }
-    if (-f $sourcevm_conffile) {
-       my $data = PVE::Tools::file_get_contents($sourcevm_conffile);
-       PVE::Tools::file_set_contents($clonevm_conffile, $data);
-    }
+    lock_vmfw_conf($newid, 10, sub {
+       if (-f $clonevm_conffile) {
+           unlink $clonevm_conffile;
+       }
+       if (-f $sourcevm_conffile) {
+           my $data = PVE::Tools::file_get_contents($sourcevm_conffile);
+           PVE::Tools::file_set_contents($clonevm_conffile, $data);
+       }
+    });
 }
 
 sub read_vm_firewall_configs {
@@ -3443,6 +3485,15 @@ my $set_global_log_ratelimit = sub {
     }
 };
 
+sub lock_clusterfw_conf {
+    my ($timeout, $code, @param) = @_;
+
+    my $res = PVE::Cluster::cfs_lock_firewall("cluster", $timeout, $code, @param);
+    die $@ if $@;
+
+    return $res;
+}
+
 sub load_clusterfw_conf {
     my ($filename) = @_;
 
@@ -3506,6 +3557,15 @@ sub save_clusterfw_conf {
     }
 }
 
+sub lock_hostfw_conf {
+    my ($timeout, $code, @param) = @_;
+
+    my $res = PVE::Cluster::cfs_lock_firewall("host-$nodename", $timeout, $code, @param);
+    die $@ if $@;
+
+    return $res;
+}
+
 sub load_hostfw_conf {
     my ($cluster_conf, $filename) = @_;
 
@@ -3899,7 +3959,9 @@ sub compile_ebtables_filter {
                    # ebtables changes this to a .0/MASK network but we just
                    # want the address here, no network - see #2193
                    $ip =~ s|/(\d+)$||;
-                   push @$arpfilter, $ip;
+                   if ($ip ne 'dhcp') {
+                       push @$arpfilter, $ip;
+                   }
                }
                generate_tap_layer2filter($ruleset, $iface, $macaddr, $vmfw_conf, $vmid, $arpfilter);
            }