[pve-devel] [RFC firewall 5/8] compile ebtables rules

Wolfgang Bumiller w.bumiller at proxmox.com
Wed Mar 28 10:53:28 CEST 2018


From: Alexandre Derumier <aderumier at odiso.com>

-A FORWARD -j PVEFW-FORWARD
   -A PVEFW-FORWARD -p IPv4 -j ACCEPT  #filter mac in iptables for ipv4, so we can speedup rules with conntrack established
   -A PVEFW-FORWARD -p IPv6 -j ACCEPT
   -A PVEFW-FORWARD -o fwln+ -j PVEFW-FWBR-OUT
	-A PVEFW-FWBR-OUT -i tap110i0 -j tap110i0-OUT
		-A tap110i0-OUT -s ! 36:97:15:91:19:3c -j DROP
		-A tap110i0-OUT -p ARP -j ACCEPT
		-A tap110i0-OUT -j DROP
		-A tap110i0-OUT -j ACCEPT
	-A PVEFW-FWBR-OUT -i veth130.1 -j veth130.1-OUT
		-A veth130.1-OUT -s ! 36:95:a9:ae:f5:ec -j DROP
		-A veth130.1-OUT -j ACCEPT

Signed-off-by: Alexandre Derumier <aderumier at odiso.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
---
 debian/example/100.fw           |   3 ++
 src/PVE/Firewall.pm             | 105 +++++++++++++++++++++++++++++++++++++++-
 src/PVE/Service/pve_firewall.pm |   6 +--
 3 files changed, 109 insertions(+), 5 deletions(-)

diff --git a/debian/example/100.fw b/debian/example/100.fw
index 7a8da48..3a08b07 100644
--- a/debian/example/100.fw
+++ b/debian/example/100.fw
@@ -9,6 +9,9 @@ enable: 1
 # disable/enable MAC address filter
 macfilter: 0
 
+# limit layer2 specific protocols
+layer2_protocols: ARP,802_1Q,IPX,NetBEUI,PPP
+
 # default policy
 policy_in: DROP
 policy_out: REJECT
diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm
index c8a430c..82c6ac9 100644
--- a/src/PVE/Firewall.pm
+++ b/src/PVE/Firewall.pm
@@ -2548,6 +2548,14 @@ sub parse_fw_rule {
     return $rule;
 }
 
+sub verify_ethertype {
+    my ($value) = @_;
+    my $types = get_etc_ethertypes();
+    die "unknown ethernet protocol type: $value\n"
+	if !defined($types->{byname}->{$value}) &&
+	   !defined($types->{byid}->{$value});
+}
+
 sub parse_vmfw_option {
     my ($line) = @_;
 
@@ -2567,6 +2575,10 @@ sub parse_vmfw_option {
     } elsif ($line =~ m/^(ips_queues):\s*((\d+)(:(\d+))?)\s*$/i) {
 	$opt = lc($1);
 	$value = $2;
+    } elsif ($line =~ m/^(layer2_protocols):\s*(((\S+)[,]?)+)\s*$/i) {
+	$opt = lc($1);
+	$value = $2;
+	verify_ethertype($_) foreach split(/\s*,\s*/, $value);
     } else {
 	die "can't parse option '$line'\n"
     }
@@ -3380,9 +3392,10 @@ sub compile {
 
     my $ruleset = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 4, $verbose);
     my $rulesetv6 = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 6, $verbose);
+    my $ebtables_ruleset = compile_ebtables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $verbose);
     my $ipset_ruleset = compile_ipsets($cluster_conf, $vmfw_configs, $vmdata);
 
-    return ($ruleset, $ipset_ruleset, $rulesetv6);
+    return ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset);
 }
 
 sub compile_iptables_filter {
@@ -3594,6 +3607,94 @@ sub compile_ipsets {
     return $ipset_ruleset;
 }
 
+sub compile_ebtables_filter {
+    my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $verbose) = @_;
+
+    return ({}, {}) if !$cluster_conf->{options}->{enable};
+
+    my $ruleset = {};
+
+    ruleset_create_chain($ruleset, "PVEFW-FORWARD");
+
+
+    ruleset_create_chain($ruleset, "PVEFW-FWBR-OUT");
+    #for ipv4 and ipv6, check macaddress in iptables, so we use conntrack 'ESTABLISHED', to speedup rules
+    ruleset_addrule($ruleset, 'PVEFW-FORWARD', '-p IPv4', '-j ACCEPT');
+    ruleset_addrule($ruleset, 'PVEFW-FORWARD', '-p IPv6', '-j ACCEPT');
+    ruleset_addrule($ruleset, 'PVEFW-FORWARD', '-o fwln+', '-j PVEFW-FWBR-OUT');
+
+    # generate firewall rules for QEMU VMs
+    foreach my $vmid (keys %{$vmdata->{qemu}}) {
+	eval {
+	    my $conf = $vmdata->{qemu}->{$vmid};
+	    my $vmfw_conf = $vmfw_configs->{$vmid};
+	    return if !$vmfw_conf;
+
+	    foreach my $netid (keys %$conf) {
+		next if $netid !~ m/^net(\d+)$/;
+		my $net = PVE::QemuServer::parse_net($conf->{$netid});
+		next if !$net->{firewall};
+		my $iface = "tap${vmid}i$1";
+		my $macaddr = $net->{macaddr};
+
+		generate_tap_layer2filter($ruleset, $iface, $macaddr, $vmfw_conf, $vmid);
+
+	    }
+	};
+	warn $@ if $@; # just to be sure - should not happen
+    }
+
+    # generate firewall rules for LXC containers
+    foreach my $vmid (keys %{$vmdata->{lxc}}) {
+	eval {
+	    my $conf = $vmdata->{lxc}->{$vmid};
+
+	    my $vmfw_conf = $vmfw_configs->{$vmid};
+	    return if !$vmfw_conf || !$vmfw_conf->{options}->{enable};
+
+	    foreach my $netid (keys %$conf) {
+		next if $netid !~ m/^net(\d+)$/;
+		my $net = PVE::LXC::Config->parse_lxc_network($conf->{$netid});
+		next if !$net->{firewall};
+		my $iface = "veth${vmid}i$1";
+		my $macaddr = $net->{hwaddr};
+		generate_tap_layer2filter($ruleset, $iface, $macaddr, $vmfw_conf, $vmid);
+	    }
+	};
+	warn $@ if $@; # just to be sure - should not happen
+    }
+
+    return $ruleset;
+}
+
+sub generate_tap_layer2filter {
+    my ($ruleset, $iface, $macaddr, $vmfw_conf, $vmid) = @_;
+    my $options = $vmfw_conf->{options};
+
+    my $tapchain = $iface."-OUT";
+
+    # ebtables remove zeros from mac pairs
+    $macaddr =~ s/0([0-9a-f])/$1/ig;
+    $macaddr = lc($macaddr);
+
+    ruleset_create_chain($ruleset, $tapchain);
+
+    if (defined($macaddr) && !(defined($options->{macfilter}) && $options->{macfilter} == 0)) {
+	    ruleset_addrule($ruleset, $tapchain, "-s ! $macaddr", '-j DROP');
+    }
+
+    if (defined($options->{layer2_protocols})){
+	foreach my $proto (split(/,/, $options->{layer2_protocols})) {
+	    ruleset_addrule($ruleset, $tapchain, "-p $proto", '-j ACCEPT');
+	}
+	ruleset_addrule($ruleset, $tapchain, '', "-j DROP");
+    } else {
+	ruleset_addrule($ruleset, $tapchain, '', '-j ACCEPT');
+    }
+
+    ruleset_addrule($ruleset, 'PVEFW-FWBR-OUT', "-i $iface", "-j $tapchain");
+}
+
 sub get_ruleset_status {
     my ($ruleset, $active_chains, $digest_fn, $verbose) = @_;
 
@@ -3947,7 +4048,7 @@ sub update {
 
 	my $hostfw_conf = load_hostfw_conf($cluster_conf);
 
-	my ($ruleset, $ipset_ruleset, $rulesetv6) = compile($cluster_conf, $hostfw_conf);
+	my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = compile($cluster_conf, $hostfw_conf);
 
 	apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $rulesetv6);
     };
diff --git a/src/PVE/Service/pve_firewall.pm b/src/PVE/Service/pve_firewall.pm
index 0ba8a84..a9a3435 100755
--- a/src/PVE/Service/pve_firewall.pm
+++ b/src/PVE/Service/pve_firewall.pm
@@ -164,7 +164,7 @@ __PACKAGE__->register_method ({
 
 	    if ($status eq 'running') {
 		
-		my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose);
+		my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose);
 
 		$verbose = 0; # do not show iptables details
 		my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose);
@@ -201,7 +201,7 @@ __PACKAGE__->register_method ({
 	    my $verbose = 1;
 
 	    my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef, $verbose); 
-	    my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose);
+	    my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose);
 
 	    print "ipset cmdlist:\n";
 	    my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose);
@@ -329,7 +329,7 @@ __PACKAGE__->register_method ({
 
 	local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
 
-	my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile(undef, undef, undef, $param->{verbose});
+	my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile(undef, undef, undef, $param->{verbose});
 
 	PVE::FirewallSimulator::debug($param->{verbose} || 0);
 	
-- 
2.11.0





More information about the pve-devel mailing list