add ipv6 ipset support
authorDietmar Maurer <dietmar@proxmox.com>
Tue, 4 Nov 2014 07:43:38 +0000 (08:43 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Tue, 4 Nov 2014 07:46:31 +0000 (08:46 +0100)
big change here,
we create now a ipset which include 2 others ipsets for ipv4 and ipv6

PVEFW-0-blacklist list:set
    PVEFW-0-blacklist-v4 hash:net family inet4
    PVEFW-0-blacklist-v6 hash:net family inet6

v4 and v6, are only created if ip address are defined in the set
in iptables rules, we use the main set.

Benchmark show no performance impact

Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
src/PVE/Firewall.pm

index 5c46563..e2cf5f8 100644 (file)
@@ -5,6 +5,7 @@ use strict;
 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);
@@ -755,10 +756,12 @@ sub local_network {
     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) = @_;
@@ -767,7 +770,6 @@ sub compute_ipset_chain_name {
 
     my $id = "$vmid-${ipset_name}";
 
-   
     if ((length($id) + 6) > $max_iptables_ipset_name_length) {
        $id = PVE::Tools::fnv31a_hex($id);
     }
@@ -2202,16 +2204,13 @@ sub parse_clusterfw_option {
 sub resolve_alias {
     my ($clusterfw_conf, $fw_conf, $cidr) = @_;
 
-    if ($cidr =~ m/^${ip_alias_pattern}$/) {
-       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 {
@@ -2738,39 +2737,64 @@ sub generate_ipset {
 
     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";
     }
 }
 
@@ -3171,6 +3195,11 @@ sub get_ipset_cmdlist {
                $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";
@@ -3183,10 +3212,9 @@ sub get_ipset_cmdlist {
            $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";