[pve-devel] [PATCH] implement ipset ip/net groups

Alexandre Derumier aderumier at odiso.com
Thu Mar 27 11:22:06 CET 2014


This implement ipset groups of ips or network in groups.fw.

groups.fw
---------
[ipgroup ipgroup1]

192.168.0.1
192.168.0.2
192.168.0.3

[ipgroup ipgroup2]

192.168.0.3
192.168.0.4

[netgroup netgroup1]

192.168.0.0/24
10.0.0.0/8

Signed-off-by: Alexandre Derumier <aderumier at odiso.com>
---
 example/groups.fw   |   20 +++++++
 src/PVE/Firewall.pm |  165 ++++++++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 171 insertions(+), 14 deletions(-)

diff --git a/example/groups.fw b/example/groups.fw
index eab4b2d..b9c088f 100644
--- a/example/groups.fw
+++ b/example/groups.fw
@@ -10,3 +10,23 @@ IN  ACCEPT 10.0.0.1
 IN  ACCEPT 10.0.0.2
 IN  ACCEPT 10.0.0.2 
 
+
+#ipset hash:ip
+[ipgroup ipgroup1]
+
+192.168.0.1
+192.168.0.2
+192.168.0.3
+
+
+[ipgroup ipgroup2]
+
+192.168.0.3
+192.168.0.4
+
+#ipset hash:net
+[netgroup netgroup1]
+
+192.168.0.0/24
+10.0.0.0/8
+
diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm
index 630e5c4..b0bcd94 100644
--- a/src/PVE/Firewall.pm
+++ b/src/PVE/Firewall.pm
@@ -801,6 +801,12 @@ sub iptables_restore_cmdlist {
     run_command("/sbin/iptables-restore -n", input => $cmdlist);
 }
 
+sub ipset_restore_cmdlist {
+    my ($cmdlist) = @_;
+
+    run_command(" /usr/sbin/ipset restore", input => $cmdlist);
+}
+
 sub iptables_get_chains {
 
     my $res = {};
@@ -857,6 +863,38 @@ sub iptables_get_chains {
     return $res;
 }
 
+sub ipset_get_chains {
+
+    my $res = {};
+    my $chains = {};
+
+    my $parser = sub {
+	my $line = shift;
+
+	return if $line =~ m/^#/;
+	return if $line =~ m/^\s*$/;
+	if ($line =~ m/^(\S+)\s(\S+)\s(\S+)/) {
+	   push @{$chains->{$2}}, $line;
+	} else {
+	    # simply ignore the rest
+	    return;
+	}
+    };
+
+    run_command(" /usr/sbin/ipset save", outfunc => $parser);
+
+    #comptute sig for each chain
+    foreach my $chain (keys %$chains){
+	my $digest = Digest::SHA->new('sha1');
+	foreach my $rule (@{$chains->{$chain}}) {
+	    $digest->add($rule);
+	}
+	$res->{$chain} = $digest->b64digest;
+    }
+
+    return $res;
+}
+
 sub iptables_chain_exist {
     my ($chain) = @_;
 
@@ -1709,7 +1747,7 @@ sub parse_group_fw_rules {
     my $section;
     my $group;
 
-    my $res = { rules => {} };
+    my $res = { rules => {}, ipset => {} };
 
     my $digest = Digest::SHA->new('sha1');
 
@@ -1727,19 +1765,46 @@ sub parse_group_fw_rules {
 	    $group = lc($1);
 	    next;
 	}
+
+	if ($line =~ m/^\[ipgroup\s+(\S+)\]\s*$/i) {
+	    $section = 'ipset';
+	    $group = lc($1);
+	    $res->{$section}->{$group}->{type} = 'hash:ip family inet hashsize 1024 maxelem 65536 ';
+
+	    next;
+	}
+	if ($line =~ m/^\[netgroup\s+(\S+)\]\s*$/i) {
+	    $section = 'ipset';
+	    $group = lc($1);
+	    $res->{$section}->{$group}->{type} = 'hash:net family inet hashsize 1024 maxelem 65536 ';
+
+	    next;
+	}
+
 	if (!$section || !$group) {
 	    warn "$prefix: skip line - no section";
 	    next;
 	}
 
-	my $rule;
-	eval { $rule = parse_fw_rule($line, 0, 0); };
-	if (my $err = $@) {
-	    warn "$prefix: $err";
-	    next;
+	if($section eq 'rules'){
+	    my $rule;
+	    eval { $rule = parse_fw_rule($line, 0, 0); };
+	    if (my $err = $@) {
+		warn "$prefix: $err";
+		next;
+	    }
+	push @{$res->{$section}->{$group}}, $rule;
+
+	}elsif($section eq 'ipset'){
+	    my $ip;
+	    if (!Net::IP->new($line)) {
+		warn "$prefix: $line is not an valid ip address";
+		next;
+	    }
+	    push @{$res->{$section}->{$group}->{ip}}, $line;
 	}
+
 	
-	push @{$res->{$section}->{$group}}, $rule;
     }
 
     $res->{digest} = $digest->b64digest;
@@ -1868,6 +1933,24 @@ sub generate_std_chains {
     }
 }
 
+sub generate_ipset_chains {
+    my ($ipset_ruleset, $options) = @_;
+
+    foreach my $ipset (keys %{$options->{ipset}}) {
+	generate_ipset($ipset_ruleset, $ipset, $options->{ipset}->{$ipset});
+    }
+}
+
+sub generate_ipset {
+    my ($ipset_ruleset, $name, $options) = @_;
+
+    push @{$ipset_ruleset->{$name}}, "create $name $options->{type}";
+
+    foreach my $ip (@{$options->{ip}}) {
+	push @{$ipset_ruleset->{$name}}, "add $name $ip";
+    }
+}
+
 sub save_pvefw_status {
     my ($status) = @_;
 
@@ -1928,7 +2011,7 @@ sub load_security_groups {
 	$groups_conf = parse_group_fw_rules($filename, $fh);
     }
 
-    return $groups_conf;
+    return ($groups_conf);
 }
 
 sub save_security_groups {
@@ -1983,6 +2066,9 @@ sub compile {
 
     my $groups_conf = load_security_groups();
 
+    my $ipset_ruleset = {};
+    generate_ipset_chains($ipset_ruleset, $groups_conf);
+
     my $ruleset = {};
 
     ruleset_create_chain($ruleset, "PVEFW-INPUT");
@@ -2086,13 +2172,18 @@ sub compile {
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j DROP");  
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j DROP");
 
-    return wantarray ? ($ruleset, $hostfw_conf) : $ruleset;
+    return wantarray ? ($ruleset, $hostfw_conf, $ipset_ruleset) : $ruleset;
 }
 
 sub get_ruleset_status {
-    my ($ruleset, $verbose) = @_;
+    my ($ruleset, $verbose, $ipset) = @_;
 
-    my $active_chains = iptables_get_chains();
+    my $active_chains = undef;
+    if($ipset){
+	$active_chains = ipset_get_chains();
+    }else{
+	$active_chains = iptables_get_chains();
+    }
 
     my $statushash = {};
 
@@ -2211,17 +2302,63 @@ sub get_rulset_cmdlist {
     return $cmdlist;
 }
 
+sub get_ipset_cmdlist {
+    my ($ruleset, $verbose) = @_;
+
+    my $cmdlist = "";
+
+    my $statushash = get_ruleset_status($ruleset, $verbose, 1);
+
+    foreach my $chain (sort keys %$ruleset) {
+	my $stat = $statushash->{$chain};
+	die "internal error" if !$stat;
+
+	if ($stat->{action} eq 'create') {
+	    foreach my $cmd (@{$ruleset->{$chain}}) {
+		$cmdlist .= "$cmd\n";
+	    }
+        }
+
+	if ($stat->{action} eq 'update') {
+	    my $chain_swap = $chain."_swap";
+
+	    foreach my $cmd (@{$ruleset->{$chain}}) {
+		$cmd =~ s/$chain/$chain_swap/;
+		$cmdlist .= "$cmd\n";
+
+	    }
+	    $cmdlist .= "swap $chain_swap $chain\n";
+	    $cmdlist .= "flush $chain_swap\n";
+	    $cmdlist .= "destroy $chain_swap\n";
+
+        }
+
+    }
+
+    foreach my $chain (keys %$statushash) {
+	next if $statushash->{$chain}->{action} ne 'delete';
+	$cmdlist .= "flush $chain\n";
+	$cmdlist .= "destroy $chain\n";
+    }
+
+    return $cmdlist;
+}
+
 sub apply_ruleset {
-    my ($ruleset, $hostfw_conf, $verbose) = @_;
+    my ($ruleset, $hostfw_conf, $ipset_ruleset, $verbose) = @_;
 
     enable_bridge_firewall();
 
     update_nf_conntrack_max($hostfw_conf);
 
+    my $ipsetcmdlist = get_ipset_cmdlist($ipset_ruleset, $verbose);
+
     my $cmdlist = get_rulset_cmdlist($ruleset, $verbose);
 
     print $cmdlist if $verbose;
 
+    ipset_restore_cmdlist($ipsetcmdlist);
+
     iptables_restore_cmdlist($cmdlist);
 
     # test: re-read status and check if everything is up to date
@@ -2269,13 +2406,13 @@ sub update {
     my $code = sub {
 	my $status = read_pvefw_status();
 
-	my ($ruleset, $hostfw_conf) = PVE::Firewall::compile();
+	my ($ruleset, $hostfw_conf, $ipset_ruleset) = PVE::Firewall::compile();
 
 	if ($start || $status eq 'active') {
 
 	    save_pvefw_status('active')	if ($status ne 'active');
 
-	    apply_ruleset($ruleset, $hostfw_conf, $verbose);
+	    apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $verbose);
 	} else {
 	    print "Firewall not active (status = $status)\n" if $verbose;
 	}
-- 
1.7.10.4




More information about the pve-devel mailing list