# ip6tables -p icmpv6 -h
my $icmpv6_type_names = {
- 'any' => 1,
'destination-unreachable' => 1,
'no-route' => 1,
'communication-prohibited' => 1,
+ 'beyond-scope' => 1,
'address-unreachable' => 1,
'port-unreachable' => 1,
+ 'failed-policy' => 1,
+ 'reject-route' => 1,
'packet-too-big' => 1,
'time-exceeded' => 1,
'ttl-zero-during-transit' => 1,
'redirect' => 1,
};
+my $is_valid_icmp_type = sub {
+ my ($type, $valid_types) = @_;
+
+ if ($type =~ m/^\d+$/) {
+ # values for icmp-type range between 0 and 255 (8 bit field)
+ die "invalid icmp-type '$type'\n" if $type > 255;
+ } else {
+ die "unknown icmp-type '$type'\n" if !defined($valid_types->{$type});
+ }
+};
+
sub init_firewall_macros {
$pve_fw_parsed_macros = {};
}
}
- die "ICPM ports not allowed in port range\n" if $icmp_port && $count > 0;
+ die "ICMP ports not allowed in port range\n" if $icmp_port && $count > 0;
# I really don't like to use the word number here, but it's the only thing
# that makes sense in a literal way. The range 1:100 counts as 2, not as
my $multisport = defined($rule->{sport}) && parse_port_name_number_or_range($rule->{sport}, 0);
my $add_dport = sub {
- return if !$rule->{dport};
+ return if !defined($rule->{dport});
+ # NOTE: we re-use dport to store --icmp-type for icmp* protocol
if ($proto eq 'icmp') {
- # 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}});
+ $is_valid_icmp_type->($rule->{dport}, $icmp_type_names);
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}});
+ $is_valid_icmp_type->($rule->{dport}, $icmpv6_type_names);
push @match, "-m icmpv6 --icmpv6-type $rule->{dport}";
} elsif (!$PROTOCOLS_WITH_PORTS->{$proto}) {
die "protocol $proto does not have ports\n";
} elsif ($multidport) {
push @match, "--match multiport", "--dports $rule->{dport}";
} else {
+ return if !$rule->{dport};
push @match, "--dport $rule->{dport}";
}
};
}
return {} if !$raw;
+ my $curr_group_keys = {};
+
my $linenr = 0;
while ($raw =~ /^\h*(.*?)\h*$/gm) {
my $line = $1;
}
$res->{$section}->{$group} = [];
+ $curr_group_keys = {};
+
$res->{ipset_comments}->{$group} = decode('utf8', $comment)
if $comment;
next;
} 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;
}
push @{$res->{$section}->{$group}}, $entry;
+ $curr_group_keys->{$cidr} = 1;
} else {
warn "$prefix: skip line - unknown section\n";
next;
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) = @_;
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) = @_;
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) {
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 {
}
};
+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) = @_;
}
}
+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) = @_;
# 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);
}