use PVE::JSONSchema qw(get_standard_option);
use PVE::Cluster;
use PVE::ProcFSTools;
-use PVE::Tools;
+use PVE::Tools qw($IPV4RE);
use File::Basename;
use File::Path;
use IO::File;
$have_pve_manager = 1;
};
+PVE::JSONSchema::register_format('IPv4orCIDR', \&pve_verify_ipv4_or_cidr);
+sub pve_verify_ipv4_or_cidr {
+ my ($cidr, $noerr) = @_;
+
+ if ($cidr =~ m!^(?:$IPV4RE)(/(\d+))?$!) {
+ return $cidr if Net::IP->new($cidr);
+ return undef if $noerr;
+ die Net::IP::Error() . "\n";
+ }
+ return undef if $noerr;
+ die "value does not look like a valid IP address or CIDR network\n";
+}
+
+
my $feature_ipset_nomatch = 0;
eval {
my (undef, undef, $release) = POSIX::uname();
my $services = PVE::Firewall::get_etc_services();
my $count = 0;
+ my $icmp_port = 0;
+
foreach my $item (split(/,/, $str)) {
$count++;
if ($item =~ m/^(\d+):(\d+)$/) {
my $port = $1;
die "invalid port '$port'\n" if $port > 65535;
} else {
- die "invalid port '$item'\n" if !$services->{byname}->{$item};
+ if ($icmp_type_names->{$item}) {
+ $icmp_port = 1;
+ } else {
+ die "invalid port '$item'\n" if !$services->{byname}->{$item};
+ }
}
}
+ die "ICPM ports not allowed in port range\n" if $icmp_port && $count > 1;
+
return $count;
}
}
push @{$res->{$section}->{$group}}, $rule;
} elsif ($section eq 'ipset') {
- chomp $line;
- $line =~ m/^(\!)?\s*((\d+)\.(\d+)\.(\d+)\.(\d+)(\/(\d+))?)/;
+ # we can add single line comments to the end of the rule
+ my $comment = decode('utf8', $1) if $line =~ s/#\s*(.*?)\s*$//;
+
+ $line =~ m/^(\!)?\s*(\S+)\s*$/;
my $nomatch = $1;
- my $ip = $2;
+ my $cidr = $2;
- if(!$ip){
- warn "$prefix: $line is not an valid ip address\n";
- next;
- }
- if (!Net::IP->new($ip)) {
- warn "$prefix: $line is not an valid ip address\n";
+ $cidr =~ s|/32$||;
+
+ eval { pve_verify_ipv4_or_cidr($cidr); };
+ if (my $err = $@) {
+ warn "$prefix: $cidr - $err";
next;
}
- if ($nomatch) {
- if ($feature_ipset_nomatch) {
- push @{$res->{$section}->{$group}}, "$ip nomatch";
- } else {
- warn "$prefix: ignore $line - nomatch not supported by kernel\n";
- }
- } else {
- push @{$res->{$section}->{$group}}, $ip;
- }
+ my $entry = { cidr => $cidr };
+ $entry->{nomatch} = 1 if $nomatch;
+ $entry->{comment} = $comment if $comment;
+
+ push @{$res->{$section}->{$group}}, $entry;
}
}
return $raw;
};
+my $format_ipset = sub {
+ my ($options) = @_;
+
+ my $raw = '';
+
+ my $nethash = {};
+ foreach my $entry (@$options) {
+ $nethash->{$entry->{cidr}} = $entry;
+ }
+
+ foreach my $cidr (sort keys %$nethash) {
+ my $entry = $nethash->{$cidr};
+ my $line = $entry->{nomatch} ? '!' : '';
+ $line .= $entry->{cidr};
+ $line .= " # " . encode('utf8', $entry->{comment})
+ if $entry->{comment} && $entry->{comment} !~ m/^\s*$/;
+ $raw .= "$line\n";
+ }
+
+ return $raw;
+};
+
sub save_vmfw_conf {
my ($vmid, $vmfw_conf) = @_;
push @{$ipset_ruleset->{$name}}, "create $name hash:net family inet hashsize $hashsize maxelem $hashsize";
- foreach my $ip (@$options) {
- push @{$ipset_ruleset->{$name}}, "add $name $ip";
+ # remove duplicates
+ my $nethash = {};
+ foreach my $entry (@$options) {
+ $nethash->{$entry->{cidr}} = $entry;
+ }
+
+ foreach my $cidr (sort keys %$nethash) {
+ my $entry = $nethash->{$cidr};
+
+ my $cmd = "add $name $cidr";
+ if ($entry->{nomatch}) {
+ if ($feature_ipset_nomatch) {
+ push @{$ipset_ruleset->{$name}}, "$cmd nomatch";
+ } else {
+ warn "ignore !$cidr - nomatch not supported by kernel\n";
+ }
+ } else {
+ push @{$ipset_ruleset->{$name}}, $cmd;
+ }
}
}
my $options = $cluster_conf->{options};
$raw .= &$format_options($options) if scalar(keys %$options);
- # fixme: save ipset
+ foreach my $ipset (sort keys %{$cluster_conf->{ipset}}) {
+ $raw .= "[IPSET $ipset]\n\n";
+ my $options = $cluster_conf->{ipset}->{$ipset};
+ $raw .= &$format_ipset($options);
+ $raw .= "\n";
+ }
my $rules = $cluster_conf->{rules};
if (scalar(@$rules)) {
my $active_chains = ipset_get_chains();
my $statushash = get_ruleset_status($ruleset, $active_chains, \&ipset_chain_digest, $verbose);
+ # remove stale _swap chains
+ foreach my $chain (keys %$active_chains) {
+ if ($chain =~ m/^PVEFW-\S+_swap$/) {
+ $cmdlist .= "destroy $chain\n";
+ }
+ }
+
foreach my $chain (sort keys %$ruleset) {
my $stat = $statushash->{$chain};
die "internal error" if !$stat;