my $ipfilter_name = compute_ipfilter_ipset_name($netid);
my $ipfilter_ipset = compute_ipset_chain_name($vmid, $ipfilter_name, $ipversion)
- if $vmfw_conf->{ipset}->{$ipfilter_name};
+ if $options->{ipfilter} || $vmfw_conf->{ipset}->{$ipfilter_name};
# create chain with mac and ip filter
ruleset_create_vm_chain($ruleset, $tapchain, $ipversion, $options, $macaddr, $ipfilter_ipset, $direction);
my $loglevels = "emerg|alert|crit|err|warning|notice|info|debug|nolog";
- if ($line =~ m/^(enable|dhcp|ndp|radv|macfilter|ips):\s*(0|1)\s*$/i) {
+ if ($line =~ m/^(enable|dhcp|ndp|radv|macfilter|ipfilter|ips):\s*(0|1)\s*$/i) {
$opt = lc($1);
$value = int($2);
} elsif ($line =~ m/^(log_level_in|log_level_out):\s*(($loglevels)\s*)?$/i) {
}
} elsif ($d->{type} eq 'lxc') {
if ($have_lxc) {
- my $cfspath = PVE::LXC::cfs_config_path($vmid);
+ my $cfspath = PVE::LXC::Config->cfs_config_path($vmid);
if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) {
$lxc->{$vmid} = $conf;
}
}
sub generate_ipset_chains {
- my ($ipset_ruleset, $clusterfw_conf, $fw_conf) = @_; #fixme
+ my ($ipset_ruleset, $clusterfw_conf, $fw_conf, $device_ips, $ipsets) = @_;
- foreach my $ipset (keys %{$fw_conf->{ipset}}) {
+ foreach my $ipset (keys %{$ipsets}) {
- my $options = $fw_conf->{ipset}->{$ipset};
+ my $options = $ipsets->{$ipset};
+
+ if ($device_ips && $ipset =~ /^ipfilter-(net\d+)$/) {
+ if (my $ips = $device_ips->{$1}) {
+ $options = [@$options, @$ips];
+ }
+ }
# remove duplicates
my $nethash = {};
}
#http://backreference.org/2013/03/01/ipv6-address-normalization/
if ($ver == 6) {
- $cidr = lc(Net::IP::ip_compress_address($cidr, 6));
+ # ip_compress_address takes an address only, no CIDR
+ my ($addr, $prefix_len) = ($cidr =~ m@^([^/]*)(/.*)?$@);
+ $cidr = lc(Net::IP::ip_compress_address($addr, 6));
+ $cidr .= $prefix_len if defined($prefix_len);
$cidr =~ s|/128$||;
} else {
$cidr =~ s|/32$||;
$vmfw_configs = read_vm_firewall_configs($cluster_conf, $vmdata, undef, $verbose);
}
- my ($ruleset, $ipset_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);
-
- return ($ruleset, $ipset_ruleset, $rulesetv6);
-}
-
-sub compile_iptables_filter {
- my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $ipversion, $verbose) = @_;
+ return ({},{},{}) if !$cluster_conf->{options}->{enable};
my $localnet;
if ($cluster_conf->{aliases}->{local_network}) {
push @{$cluster_conf->{ipset}->{management}}, { cidr => $localnet };
- return ({}, {}) if !$cluster_conf->{options}->{enable};
+ 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 $ipset_ruleset = compile_ipsets($cluster_conf, $vmfw_configs, $vmdata);
+
+ return ($ruleset, $ipset_ruleset, $rulesetv6);
+}
+
+sub compile_iptables_filter {
+ my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $ipversion, $verbose) = @_;
my $ruleset = {};
my $hostfw_enable = !(defined($hostfw_options->{enable}) && ($hostfw_options->{enable} == 0));
- my $ipset_ruleset = {};
-
if ($hostfw_enable) {
eval { enable_host_firewall($ruleset, $hostfw_conf, $cluster_conf, $ipversion); };
warn $@ if $@; # just to be sure - should not happen
my $vmfw_conf = $vmfw_configs->{$vmid};
return if !$vmfw_conf;
- generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf);
-
foreach my $netid (keys %$conf) {
next if $netid !~ m/^net(\d+)$/;
my $net = PVE::QemuServer::parse_net($conf->{$netid});
my $vmfw_conf = $vmfw_configs->{$vmid};
return if !$vmfw_conf;
- generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf);
-
if ($vmfw_conf->{options}->{enable}) {
foreach my $netid (keys %$conf) {
next if $netid !~ m/^net(\d+)$/;
- my $net = PVE::LXC::parse_lxc_network($conf->{$netid});
+ my $net = PVE::LXC::Config->parse_lxc_network($conf->{$netid});
next if !$net->{firewall};
my $iface = "veth${vmid}i$1";
my $macaddr = $net->{hwaddr};
ruleset_insertrule($ruleset, "PVEFW-FORWARD", "-m conntrack --ctstate RELATED,ESTABLISHED -j PVEFW-IPS");
}
- generate_ipset_chains($ipset_ruleset, undef, $cluster_conf);
+ return $ruleset;
+}
+
+sub mac_to_linklocal {
+ my ($macaddr) = @_;
+ my @parts = split(/:/, $macaddr);
+ # The standard link local address uses the fe80::/64 prefix with the
+ # modified EUI-64 identifier derived from the MAC address by flipping the
+ # universal/local bit and inserting FF:FE in the middle.
+ # See RFC 4291.
+ $parts[0] = sprintf("%02x", hex($parts[0]) ^ 0x02);
+ my @meui64 = (@parts[0,1,2], 'ff', 'fe', @parts[3,4,5]);
+ return "fe80::$parts[0]$parts[1]:$parts[2]FF:FE$parts[3]:$parts[4]$parts[5]";
+}
+
+sub compile_ipsets {
+ my ($cluster_conf, $vmfw_configs, $vmdata) = @_;
+
+ my $localnet;
+ if ($cluster_conf->{aliases}->{local_network}) {
+ $localnet = $cluster_conf->{aliases}->{local_network}->{cidr};
+ } else {
+ my $localnet_ver;
+ ($localnet, $localnet_ver) = parse_ip_or_cidr(local_network() || '127.0.0.0/8');
+
+ $cluster_conf->{aliases}->{local_network} = {
+ name => 'local_network', cidr => $localnet, ipversion => $localnet_ver };
+ }
+
+ push @{$cluster_conf->{ipset}->{management}}, { cidr => $localnet };
+
+
+ my $ipset_ruleset = {};
+
+ # generate ipsets 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;
+
+ # When the 'ipfilter' option is enabled every device for which there
+ # is no 'ipfilter-netX' ipset defiend gets an implicit empty default
+ # ipset.
+ # The reason is that ipfilter ipsets are always filled with standard
+ # IPv6 link-local filters.
+ my $ipsets = $vmfw_conf->{ipset};
+ my $implicit_sets = {};
+
+ my $device_ips = {};
+ foreach my $netid (keys %$conf) {
+ next if $netid !~ m/^net(\d+)$/;
+ my $net = PVE::QemuServer::parse_net($conf->{$netid});
+ next if !$net->{firewall};
+
+ if ($vmfw_conf->{options}->{ipfilter} && !$ipsets->{"ipfilter-$netid"}) {
+ $implicit_sets->{"ipfilter-$netid"} = [];
+ }
+
+ my $macaddr = $net->{macaddr};
+ my $linklocal = mac_to_linklocal($macaddr);
+ $device_ips->{$netid} = [
+ { cidr => $linklocal },
+ { cidr => 'fe80::/10', nomatch => 1 }
+ ];
+ }
+
+ generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf, $device_ips, $ipsets);
+ generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf, $device_ips, $implicit_sets);
+ };
+ 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;
+
+ # When the 'ipfilter' option is enabled every device for which there
+ # is no 'ipfilter-netX' ipset defiend gets an implicit empty default
+ # ipset.
+ # The reason is that ipfilter ipsets are always filled with standard
+ # IPv6 link-local filters, as well as the IP addresses configured
+ # for the container.
+ my $ipsets = $vmfw_conf->{ipset};
+ my $implicit_sets = {};
+
+ my $device_ips = {};
+ 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};
+
+ if ($vmfw_conf->{options}->{ipfilter} && !$ipsets->{"ipfilter-$netid"}) {
+ $implicit_sets->{"ipfilter-$netid"} = [];
+ }
+
+ my $macaddr = $net->{hwaddr};
+ my $linklocal = mac_to_linklocal($macaddr);
+ my $set = $device_ips->{$netid} = [
+ { cidr => $linklocal },
+ { cidr => 'fe80::/10', nomatch => 1 }
+ ];
+ if ($net->{ip} =~ m!^($IPV4RE)(?:/\d+)?$!) {
+ push @$set, { cidr => $1 };
+ }
+ if ($net->{ip6} =~ m!^($IPV6RE)(?:/\d+)?$!) {
+ push @$set, { cidr => $1 };
+ }
+ }
+
+ generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf, $device_ips, $ipsets);
+ generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf, $device_ips, $implicit_sets);
+ };
+ warn $@ if $@; # just to be sure - should not happen
+ }
+
+ generate_ipset_chains($ipset_ruleset, undef, $cluster_conf, undef, $cluster_conf->{ipset});
- return ($ruleset, $ipset_ruleset);
+ return $ipset_ruleset;
}
sub get_ruleset_status {