use POSIX;
use Data::Dumper;
use Digest::SHA;
+use Socket qw(AF_INET6 inet_ntop inet_pton);
use PVE::INotify;
use PVE::Exception qw(raise raise_param_exc);
use PVE::JSONSchema qw(register_standard_option get_standard_option);
return $__local_network;
}
-# ipset names are limited to 31 characters, and we use '_swap'
-# suffix for atomic update, for example PVEFW-${VMID}-${ipset_name}_swap
+# ipset names are limited to 31 characters,
+# and we use '-v4' or '-v6' to indicate IP versions,
+# and we use '_swap' suffix for atomic update,
+# for example PVEFW-${VMID}-${ipset_name}_swap
-my $max_iptables_ipset_name_length = 31 - length("_swap");
+my $max_iptables_ipset_name_length = 31 - length("_swap") - length("-v4");
sub compute_ipset_chain_name {
my ($vmid, $ipset_name) = @_;
my $id = "$vmid-${ipset_name}";
-
if ((length($id) + 6) > $max_iptables_ipset_name_length) {
$id = PVE::Tools::fnv31a_hex($id);
}
sub resolve_alias {
my ($clusterfw_conf, $fw_conf, $cidr) = @_;
- if ($cidr !~ m/^\d/) {
- my $alias = lc($cidr);
- my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
- $e = $clusterfw_conf->{aliases}->{$alias} if !$e && $clusterfw_conf;
- return $e->{cidr} if $e;
-
- die "no such alias '$cidr'\n";
- }
+ my $alias = lc($cidr);
+ my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
+ $e = $clusterfw_conf->{aliases}->{$alias} if !$e && $clusterfw_conf;
- return $cidr;
+ die "no such alias '$cidr'\n" if !$e;;
+
+ return wantarray ? ($e->{cidr}, $e->{ipversion}) : $e->{cidr};
}
sub parse_ip_or_cidr {
die "duplicate ipset chain '$name'\n" if defined($ipset_ruleset->{$name});
- my $hashsize = scalar(@$options);
- if ($hashsize <= 64) {
- $hashsize = 64;
- } else {
- $hashsize = round_powerof2($hashsize);
- }
-
- $ipset_ruleset->{$name} = ["create $name hash:net family inet hashsize $hashsize maxelem $hashsize"];
+ $ipset_ruleset->{$name} = ["create $name list:set size 4"];
# 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} };
+ my ($cidr, $ipversion);
+ if ($entry->{cidr} =~ m/^${ip_alias_pattern}$/) {
+ ($cidr, $ipversion) = resolve_alias($clusterfw_conf, $fw_conf, $entry->{cidr});
+ } else {
+ ($cidr, $ipversion) = parse_ip_or_cidr($entry->{cidr});
+ }
+ #http://backreference.org/2013/03/01/ipv6-address-normalization/
+ if ($ipversion == 6) {
+ my $ipv6 = inet_pton(AF_INET6, lc($cidr));
+ $cidr = inet_ntop(AF_INET6, $ipv6);
+ $cidr =~ s|/128$||;
+ } else {
+ $cidr =~ s|/32$||;
+ }
+
+ $nethash->{$ipversion}->{$cidr} = { cidr => $cidr, nomatch => $entry->{nomatch} };
};
warn $@ if $@;
}
- foreach my $cidr (sort keys %$nethash) {
- my $entry = $nethash->{$cidr};
+ foreach my $ipversion (sort keys %$nethash) {
+ my $data = $nethash->{$ipversion};
+ my $subname = "$name-v$ipversion";
+
+ my $hashsize = scalar(@$options);
+ if ($hashsize <= 64) {
+ $hashsize = 64;
+ } else {
+ $hashsize = round_powerof2($hashsize);
+ }
+
+ my $family = $ipversion == "6" ? "inet6" : "inet";
- my $cmd = "add $name $cidr";
- if ($entry->{nomatch}) {
- if ($feature_ipset_nomatch) {
- push @{$ipset_ruleset->{$name}}, "$cmd nomatch";
+ $ipset_ruleset->{$subname} = ["create $subname hash:net family $family hashsize $hashsize maxelem $hashsize"];
+
+ foreach my $cidr (sort keys %$data) {
+ my $entry = $data->{$cidr};
+
+ my $cmd = "add $subname $cidr";
+ if ($entry->{nomatch}) {
+ if ($feature_ipset_nomatch) {
+ push @{$ipset_ruleset->{$subname}}, "$cmd nomatch";
+ } else {
+ warn "ignore !$cidr - nomatch not supported by kernel\n";
+ }
} else {
- warn "ignore !$cidr - nomatch not supported by kernel\n";
+ push @{$ipset_ruleset->{$subname}}, $cmd;
}
- } else {
- push @{$ipset_ruleset->{$name}}, $cmd;
}
+
+ push @{$ipset_ruleset->{$name}}, "add $name $subname";
}
}
$cmdlist .= "$cmd\n";
}
}
+ }
+
+ foreach my $chain (sort keys %$ruleset) {
+ my $stat = $statushash->{$chain};
+ die "internal error" if !$stat;
if ($stat->{action} eq 'update') {
my $chain_swap = $chain."_swap";
$cmdlist .= "flush $chain_swap\n";
$cmdlist .= "destroy $chain_swap\n";
}
-
}
- foreach my $chain (keys %$statushash) {
+ foreach my $chain (sort keys %$statushash) {
next if $statushash->{$chain}->{action} ne 'delete';
$delete_cmdlist .= "flush $chain\n";