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);
$have_pve_manager = 1;
};
+my $pve_fw_status_dir = "/var/lib/pve-firewall";
+
+mkdir $pve_fw_status_dir; # make sure this exists
+
my $security_group_name_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
my $ipset_name_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
-my $ip_alias_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
+our $ip_alias_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
my $max_alias_name_length = 64;
my $max_ipset_name_length = 64;
],
};
+$pve_std_chains->{6} = {
+ 'PVEFW-SET-ACCEPT-MARK' => [
+ "-j MARK --set-mark 1",
+ ],
+ 'PVEFW-DropBroadcast' => [
+ # same as shorewall 'Broadcast'
+ # simply DROP BROADCAST/MULTICAST/ANYCAST
+ # we can use this to reduce logging
+ #{ action => 'DROP', dsttype => 'BROADCAST' }, #no broadcast in ipv6
+ # ipv6 addrtype does not work with kernel 2.6.32
+ #{ action => 'DROP', dsttype => 'MULTICAST' },
+ #{ action => 'DROP', dsttype => 'ANYCAST' },
+ { action => 'DROP', dest => 'ff00::/8' },
+ #{ action => 'DROP', dest => '224.0.0.0/4' },
+ ],
+ 'PVEFW-reject' => [
+ # same as shorewall 'reject'
+ #{ action => 'DROP', dsttype => 'BROADCAST' },
+ #{ action => 'DROP', source => '224.0.0.0/4' },
+ { action => 'DROP', proto => 'icmpv6' },
+ "-p tcp -j REJECT --reject-with tcp-reset",
+ #"-p udp -j REJECT --reject-with icmp-port-unreachable",
+ #"-p icmp -j REJECT --reject-with icmp-host-unreachable",
+ #"-j REJECT --reject-with icmp-host-prohibited",
+ ],
+ 'PVEFW-Drop' => [
+ # same as shorewall 'Drop', which is equal to DROP,
+ # but REJECT/DROP some packages to reduce logging,
+ # and ACCEPT critical ICMP types
+ { action => 'PVEFW-reject', proto => 'tcp', dport => '43' }, # REJECT 'auth'
+ # we are not interested in BROADCAST/MULTICAST/ANYCAST
+ { action => 'PVEFW-DropBroadcast' },
+ # ACCEPT critical ICMP types
+ { action => 'ACCEPT', proto => 'icmpv6', dport => 'destination-unreachable' },
+ { action => 'ACCEPT', proto => 'icmpv6', dport => 'time-exceeded' },
+ { action => 'ACCEPT', proto => 'icmpv6', dport => 'packet-too-big' },
+
+ # Drop packets with INVALID state
+ "-m conntrack --ctstate INVALID -j DROP",
+ # Drop Microsoft SMB noise
+ { action => 'DROP', proto => 'udp', dport => '135,445', nbdport => 2 },
+ { action => 'DROP', proto => 'udp', dport => '137:139'},
+ { action => 'DROP', proto => 'udp', dport => '1024:65535', sport => 137 },
+ { action => 'DROP', proto => 'tcp', dport => '135,139,445', nbdport => 3 },
+ { action => 'DROP', proto => 'udp', dport => 1900 }, # UPnP
+ # Drop new/NotSyn traffic so that it doesn't get logged
+ "-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -j DROP",
+ # Drop DNS replies
+ { action => 'DROP', proto => 'udp', sport => 53 },
+ ],
+ 'PVEFW-Reject' => [
+ # same as shorewall 'Reject', which is equal to Reject,
+ # but REJECT/DROP some packages to reduce logging,
+ # and ACCEPT critical ICMP types
+ { action => 'PVEFW-reject', proto => 'tcp', dport => '43' }, # REJECT 'auth'
+ # we are not interested in BROADCAST/MULTICAST/ANYCAST
+ { action => 'PVEFW-DropBroadcast' },
+ # ACCEPT critical ICMP types
+ { action => 'ACCEPT', proto => 'icmpv6', dport => 'destination-unreachable' },
+ { action => 'ACCEPT', proto => 'icmpv6', dport => 'time-exceeded' },
+ { action => 'ACCEPT', proto => 'icmpv6', dport => 'packet-too-big' },
+
+ # Drop packets with INVALID state
+ "-m conntrack --ctstate INVALID -j DROP",
+ # Drop Microsoft SMB noise
+ { action => 'PVEFW-reject', proto => 'udp', dport => '135,445', nbdport => 2 },
+ { action => 'PVEFW-reject', proto => 'udp', dport => '137:139'},
+ { action => 'PVEFW-reject', proto => 'udp', dport => '1024:65535', sport => 137 },
+ { action => 'PVEFW-reject', proto => 'tcp', dport => '135,139,445', nbdport => 3 },
+ { action => 'DROP', proto => 'udp', dport => 1900 }, # UPnP
+ # Drop new/NotSyn traffic so that it doesn't get logged
+ "-p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -j DROP",
+ # Drop DNS replies
+ { action => 'DROP', proto => 'udp', sport => 53 },
+ ],
+ 'PVEFW-tcpflags' => [
+ # same as shorewall tcpflags action.
+ # Packets arriving on this interface are checked for som illegal combinations of TCP flags
+ "-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,PSH,URG -g PVEFW-logflags",
+ "-p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -g PVEFW-logflags",
+ "-p tcp -m tcp --tcp-flags SYN,RST SYN,RST -g PVEFW-logflags",
+ "-p tcp -m tcp --tcp-flags FIN,SYN FIN,SYN -g PVEFW-logflags",
+ "-p tcp -m tcp --sport 0 --tcp-flags FIN,SYN,RST,ACK SYN -g PVEFW-logflags",
+ ],
+};
+
# iptables -p icmp -h
my $icmp_type_names = {
any => 1,
'address-mask-reply' => 1,
};
+# ip6tables -p icmpv6 -h
+
+my $icmpv6_type_names = {
+ 'any' => 1,
+ 'destination-unreachable' => 1,
+ 'no-route' => 1,
+ 'communication-prohibited' => 1,
+ 'address-unreachable' => 1,
+ 'port-unreachable' => 1,
+ 'packet-too-big' => 1,
+ 'time-exceeded' => 1,
+ 'ttl-zero-during-transit' => 1,
+ 'ttl-zero-during-reassembly' => 1,
+ 'parameter-problem' => 1,
+ 'bad-header' => 1,
+ 'unknown-header-type' => 1,
+ 'unknown-option' => 1,
+ 'echo-request' => 1,
+ 'echo-reply' => 1,
+ 'router-solicitation' => 1,
+ 'router-advertisement' => 1,
+ 'neighbour-solicitation' => 1,
+ 'neighbour-advertisement' => 1,
+ 'redirect' => 1,
+};
+
sub init_firewall_macros {
$pve_fw_parsed_macros = {};
close($fh);
+ # add special case for ICMP v6
+ $protocols->{byid}->{icmpv6}->{name} = "icmpv6";
+ $protocols->{byname}->{icmpv6} = $protocols->{byid}->{icmpv6};
+
$etc_protocols = $protocols;
return $etc_protocols;
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("PVEFW-") - length("_swap");
sub compute_ipset_chain_name {
- my ($vmid, $ipset_name) = @_;
+ my ($vmid, $ipset_name, $ipversion) = @_;
$vmid = 0 if !defined($vmid);
- my $id = "$vmid-${ipset_name}";
+ my $id = "$vmid-${ipset_name}-v$ipversion";
-
- if ((length($id) + 6) > $max_iptables_ipset_name_length) {
+ if (length($id) > $max_iptables_ipset_name_length) {
$id = PVE::Tools::fnv31a_hex($id);
}
} else {
if ($icmp_type_names->{$item}) {
$icmp_port = 1;
+ } elsif ($icmpv6_type_names->{$item}) {
+ $icmp_port = 1;
} else {
die "invalid port '$item'\n" if !$services->{byname}->{$item};
}
$errors->{$param} = $msg if !$errors->{$param};
};
+ my $ipversion;
+ my $set_ip_version = sub {
+ my $vers = shift;
+ if ($vers) {
+ die "detected mixed ipv4/ipv6 adresses in rule\n"
+ if $ipversion && ($vers != $ipversion);
+ $ipversion = $vers;
+ }
+ };
+
my $check_ipset_or_alias_property = sub {
my ($name, $expected_ipversion) = @_;
my $alias = lc($value);
&$add_error($name, "no such alias '$value'")
if !($cluster_conf->{aliases}->{$alias} || ($fw_conf && $fw_conf->{aliases}->{$alias}));
-
- my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
+ my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef;
$e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf;
- die "detected mixed ipv4/ipv6 adresses in rule\n"
- if $expected_ipversion && ($expected_ipversion != $e->{ipversion});
+ &$set_ip_version($e->{ipversion});
}
}
};
if ($rule->{proto}) {
eval { pve_fw_verify_protocol_spec($rule->{proto}); };
&$add_error('proto', $@) if $@;
+ &$set_ip_version(4) if $rule->{proto} eq 'icmp';
+ &$set_ip_version(6) if $rule->{proto} eq 'icmpv6';
}
if ($rule->{dport}) {
if !$rule->{proto};
}
- my $ipversion;
-
if ($rule->{source}) {
- eval { $ipversion = parse_address_list($rule->{source}); };
+ eval {
+ my $source_ipversion = parse_address_list($rule->{source});
+ &$set_ip_version($source_ipversion);
+ };
&$add_error('source', $@) if $@;
&$check_ipset_or_alias_property('source', $ipversion);
}
if ($rule->{dest}) {
eval {
my $dest_ipversion = parse_address_list($rule->{dest});
- die "detected mixed ipv4/ipv6 adresses in rule\n"
- if $ipversion && $dest_ipversion && ($dest_ipversion != $ipversion);
- $ipversion = $dest_ipversion if $dest_ipversion;
+ &$set_ip_version($dest_ipversion);
};
&$add_error('dest', $@) if $@;
&$check_ipset_or_alias_property('dest', $ipversion);
sub iptables_restore_cmdlist {
my ($cmdlist) = @_;
- run_command("/sbin/iptables-restore -n", input => $cmdlist);
+ run_command("/sbin/iptables-restore -n", input => $cmdlist, errmsg => "iptables_restore_cmdlist");
+}
+
+sub ip6tables_restore_cmdlist {
+ my ($cmdlist) = @_;
+
+ run_command("/sbin/ip6tables-restore -n", input => $cmdlist, errmsg => "iptables_restore_cmdlist");
}
sub ipset_restore_cmdlist {
my ($cmdlist) = @_;
- run_command("/usr/sbin/ipset restore", input => $cmdlist);
+ run_command("/usr/sbin/ipset restore", input => $cmdlist, errmsg => "ipset_restore_cmdlist");
}
sub iptables_get_chains {
+ my ($iptablescmd) = @_;
+
+ $iptablescmd = "iptables" if !$iptablescmd;
my $res = {};
}
};
- run_command("/sbin/iptables-save", outfunc => $parser);
+ run_command("/sbin/$iptablescmd-save", outfunc => $parser);
return wantarray ? ($res, $hooks) : $res;
}
}
sub ruleset_generate_cmdstr {
- my ($ruleset, $chain, $rule, $actions, $goto, $cluster_conf, $fw_conf) = @_;
+ my ($ruleset, $chain, $ipversion, $rule, $actions, $goto, $cluster_conf, $fw_conf) = @_;
return if defined($rule->{enable}) && !$rule->{enable};
return if $rule->{errors};
if ($source =~ m/^\+(${ipset_name_pattern})$/) {
my $name = $1;
if ($fw_conf && $fw_conf->{ipset}->{$name}) {
- my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name);
+ my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name, $ipversion);
push @cmd, "-m set --match-set ${ipset_chain} src";
} elsif ($cluster_conf && $cluster_conf->{ipset}->{$name}) {
- my $ipset_chain = compute_ipset_chain_name(0, $name);
+ my $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion);
push @cmd, "-m set --match-set ${ipset_chain} src";
} else {
die "no such ipset '$name'\n";
}
} elsif ($source =~ m/^${ip_alias_pattern}$/){
my $alias = lc($source);
- my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
+ my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef;
$e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf;
die "no such alias '$source'\n" if !$e;
push @cmd, "-s $e->{cidr}";
if ($dest =~ m/^\+(${ipset_name_pattern})$/) {
my $name = $1;
if ($fw_conf && $fw_conf->{ipset}->{$name}) {
- my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name);
+ my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $name, $ipversion);
push @cmd, "-m set --match-set ${ipset_chain} dst";
} elsif ($cluster_conf && $cluster_conf->{ipset}->{$name}) {
- my $ipset_chain = compute_ipset_chain_name(0, $name);
+ my $ipset_chain = compute_ipset_chain_name(0, $name, $ipversion);
push @cmd, "-m set --match-set ${ipset_chain} dst";
} else {
die "no such ipset '$name'\n";
}
} elsif ($dest =~ m/^${ip_alias_pattern}$/){
my $alias = lc($dest);
- my $e = $fw_conf->{aliases}->{$alias} if $fw_conf;
+ my $e = $fw_conf ? $fw_conf->{aliases}->{$alias} : undef;
$e = $cluster_conf->{aliases}->{$alias} if !$e && $cluster_conf;
die "no such alias '$dest'\n" if !$e;
push @cmd, "-d $e->{cidr}";
# Note: we use dport to store --icmp-type
die "unknown icmp-type '$rule->{dport}'\n" if !defined($icmp_type_names->{$rule->{dport}});
push @cmd, "-m icmp --icmp-type $rule->{dport}";
+ } elsif ($rule->{proto} && $rule->{proto} eq 'icmpv6') {
+ # Note: we use dport to store --icmpv6-type
+ die "unknown icmpv6-type '$rule->{dport}'\n" if !defined($icmpv6_type_names->{$rule->{dport}});
+ push @cmd, "-m icmpv6 --icmpv6-type $rule->{dport}";
} else {
if ($nbdport > 1) {
if ($multiport == 2) {
}
sub ruleset_generate_rule {
- my ($ruleset, $chain, $rule, $actions, $goto, $cluster_conf, $fw_conf) = @_;
+ my ($ruleset, $chain, $ipversion, $rule, $actions, $goto, $cluster_conf, $fw_conf) = @_;
my $rules;
my @cmds = ();
foreach my $tmp (@$rules) {
- if (my $cmdstr = ruleset_generate_cmdstr($ruleset, $chain, $tmp, $actions, $goto, $cluster_conf, $fw_conf)) {
+ if (my $cmdstr = ruleset_generate_cmdstr($ruleset, $chain, $ipversion, $tmp, $actions, $goto, $cluster_conf, $fw_conf)) {
push @cmds, $cmdstr;
}
}
}
sub ruleset_generate_rule_insert {
- my ($ruleset, $chain, $rule, $actions, $goto) = @_;
+ my ($ruleset, $chain, $ipversion, $rule, $actions, $goto) = @_;
die "implement me" if $rule->{macro}; # not implemented, because not needed so far
- if (my $cmdstr = ruleset_generate_cmdstr($ruleset, $chain, $rule, $actions, $goto)) {
+ if (my $cmdstr = ruleset_generate_cmdstr($ruleset, $chain, $ipversion, $rule, $actions, $goto)) {
ruleset_insertrule($ruleset, $chain, $cmdstr);
}
}
}
sub ruleset_add_chain_policy {
- my ($ruleset, $chain, $vmid, $policy, $loglevel, $accept_action) = @_;
+ my ($ruleset, $chain, $ipversion, $vmid, $policy, $loglevel, $accept_action) = @_;
if ($policy eq 'ACCEPT') {
- ruleset_generate_rule($ruleset, $chain, { action => 'ACCEPT' },
+ ruleset_generate_rule($ruleset, $chain, $ipversion, { action => 'ACCEPT' },
{ ACCEPT => $accept_action});
} elsif ($policy eq 'DROP') {
}
sub ruleset_chain_add_input_filters {
- my ($ruleset, $chain, $options, $cluster_conf, $loglevel) = @_;
+ my ($ruleset, $chain, $ipversion, $options, $cluster_conf, $loglevel) = @_;
if ($cluster_conf->{ipset}->{blacklist}){
if (!ruleset_chain_exist($ruleset, "PVEFW-blacklist")) {
ruleset_addlog($ruleset, "PVEFW-blacklist", 0, "DROP: ", $loglevel) if $loglevel;
ruleset_addrule($ruleset, "PVEFW-blacklist", "-j DROP");
}
- my $ipset_chain = compute_ipset_chain_name(0, 'blacklist');
+ my $ipset_chain = compute_ipset_chain_name(0, 'blacklist', $ipversion);
ruleset_addrule($ruleset, $chain, "-m set --match-set ${ipset_chain} src -j PVEFW-blacklist");
}
if (!(defined($options->{nosmurfs}) && $options->{nosmurfs} == 0)) {
- ruleset_addrule($ruleset, $chain, "-m conntrack --ctstate INVALID,NEW -j PVEFW-smurfs");
+ if ($ipversion == 4) {
+ ruleset_addrule($ruleset, $chain, "-m conntrack --ctstate INVALID,NEW -j PVEFW-smurfs");
+ }
}
if ($options->{tcpflags}) {
}
sub ruleset_create_vm_chain {
- my ($ruleset, $chain, $options, $macaddr, $ipfilter_ipset, $direction) = @_;
+ my ($ruleset, $chain, $ipversion, $options, $macaddr, $ipfilter_ipset, $direction) = @_;
ruleset_create_chain($ruleset, $chain);
my $accept = generate_nfqueue($options);
if (!(defined($options->{dhcp}) && $options->{dhcp} == 0)) {
if ($direction eq 'OUT') {
- ruleset_generate_rule($ruleset, $chain, { action => 'PVEFW-SET-ACCEPT-MARK',
- proto => 'udp', sport => 68, dport => 67 });
+ ruleset_generate_rule($ruleset, $chain, $ipversion,
+ { action => 'PVEFW-SET-ACCEPT-MARK',
+ proto => 'udp', sport => 68, dport => 67 });
} else {
- ruleset_generate_rule($ruleset, $chain, { action => 'ACCEPT',
- proto => 'udp', sport => 67, dport => 68 });
+ ruleset_generate_rule($ruleset, $chain, $ipversion,
+ { action => 'ACCEPT',
+ proto => 'udp', sport => 67, dport => 68 });
}
}
next if $rule->{type} ne $lc_direction;
eval {
if ($direction eq 'OUT') {
- ruleset_generate_rule($ruleset, $chain, $rule,
+ ruleset_generate_rule($ruleset, $chain, $ipversion, $rule,
{ ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" },
undef, $cluster_conf, $vmfw_conf);
} else {
- ruleset_generate_rule($ruleset, $chain, $rule,
+ ruleset_generate_rule($ruleset, $chain, $ipversion, $rule,
{ ACCEPT => $in_accept , REJECT => "PVEFW-reject" },
undef, $cluster_conf, $vmfw_conf);
}
my $chain = "venet0-$vmid-$direction";
- ruleset_create_vm_chain($ruleset, $chain, $options, undef, undef, $direction);
+ ruleset_create_vm_chain($ruleset, $chain, $ipversion, $options, undef, undef, $direction);
ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, 'venet', $direction, undef, $ipversion);
my $accept = generate_nfqueue($options);
my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : $accept;
- ruleset_add_chain_policy($ruleset, $chain, $vmid, $policy, $loglevel, $accept_action);
+ ruleset_add_chain_policy($ruleset, $chain, $ipversion, $vmid, $policy, $loglevel, $accept_action);
if ($direction eq 'OUT') {
- ruleset_generate_rule_insert($ruleset, "PVEFW-VENET-OUT", {
+ ruleset_generate_rule_insert($ruleset, "PVEFW-VENET-OUT", $ipversion, {
action => $chain,
source => $ip,
iface_in => 'venet0'});
} else {
- ruleset_generate_rule($ruleset, "PVEFW-VENET-IN", {
+ ruleset_generate_rule($ruleset, "PVEFW-VENET-IN", $ipversion, {
action => $chain,
dest => $ip,
iface_out => 'venet0'});
my $tapchain = "$iface-$direction";
my $ipfilter_name = compute_ipfilter_ipset_name($netid);
- my $ipfilter_ipset = compute_ipset_chain_name($vmid, $ipfilter_name)
+ my $ipfilter_ipset = compute_ipset_chain_name($vmid, $ipfilter_name, $ipversion)
if $vmfw_conf->{ipset}->{$ipfilter_name};
# create chain with mac and ip filter
- ruleset_create_vm_chain($ruleset, $tapchain, $options, $macaddr, $ipfilter_ipset, $direction);
+ ruleset_create_vm_chain($ruleset, $tapchain, $ipversion, $options, $macaddr, $ipfilter_ipset, $direction);
if ($options->{enable}) {
ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $tapchain, $netid, $direction, $options, $ipversion);
my $accept = generate_nfqueue($options);
my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : $accept;
- ruleset_add_chain_policy($ruleset, $tapchain, $vmid, $policy, $loglevel, $accept_action);
+ ruleset_add_chain_policy($ruleset, $tapchain, $ipversion, $vmid, $policy, $loglevel, $accept_action);
} else {
my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : 'ACCEPT';
- ruleset_add_chain_policy($ruleset, $tapchain, $vmid, 'ACCEPT', $loglevel, $accept_action);
+ ruleset_add_chain_policy($ruleset, $tapchain, $ipversion, $vmid, 'ACCEPT', $loglevel, $accept_action);
}
# plug the tap chain to bridge chain
ruleset_addrule($ruleset, $chain, "-i lo -j ACCEPT");
ruleset_chain_add_conn_filters($ruleset, $chain, 'ACCEPT');
- ruleset_chain_add_input_filters($ruleset, $chain, $options, $cluster_conf, $loglevel);
+ ruleset_chain_add_input_filters($ruleset, $chain, $ipversion, $options, $cluster_conf, $loglevel);
# we use RETURN because we need to check also tap rules
my $accept_action = 'RETURN';
if ($rule->{type} eq 'group') {
ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'IN', $accept_action, $ipversion);
} elsif ($rule->{type} eq 'in') {
- ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" },
+ ruleset_generate_rule($ruleset, $chain, $ipversion, $rule,
+ { ACCEPT => $accept_action, REJECT => "PVEFW-reject" },
undef, $cluster_conf, $hostfw_conf);
}
};
}
# allow standard traffic for management ipset (includes cluster network)
- my $mngmnt_ipset_chain = compute_ipset_chain_name(0, "management");
+ my $mngmnt_ipset_chain = compute_ipset_chain_name(0, "management", $ipversion);
my $mngmntsrc = "-m set --match-set ${mngmnt_ipset_chain} src";
ruleset_addrule($ruleset, $chain, "$mngmntsrc -p tcp --dport 8006 -j $accept_action"); # PVE API
ruleset_addrule($ruleset, $chain, "$mngmntsrc -p tcp --dport 5900:5999 -j $accept_action"); # PVE VNC Console
# implement input policy
my $policy = $cluster_options->{policy_in} || 'DROP'; # allow nothing by default
- ruleset_add_chain_policy($ruleset, $chain, 0, $policy, $loglevel, $accept_action);
+ ruleset_add_chain_policy($ruleset, $chain, $ipversion, 0, $policy, $loglevel, $accept_action);
# host outbound firewall
$chain = "PVEFW-HOST-OUT";
if ($rule->{type} eq 'group') {
ruleset_add_group_rule($ruleset, $cluster_conf, $chain, $rule, 'OUT', $accept_action, $ipversion);
} elsif ($rule->{type} eq 'out') {
- ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" },
+ ruleset_generate_rule($ruleset, $chain, $ipversion,
+ $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" },
undef, $cluster_conf, $hostfw_conf);
}
};
# implement output policy
$policy = $cluster_options->{policy_out} || 'ACCEPT'; # allow everything by default
- ruleset_add_chain_policy($ruleset, $chain, 0, $policy, $loglevel, $accept_action);
+ ruleset_add_chain_policy($ruleset, $chain, $ipversion, 0, $policy, $loglevel, $accept_action);
ruleset_addrule($ruleset, "PVEFW-OUTPUT", "-j PVEFW-HOST-OUT");
ruleset_addrule($ruleset, "PVEFW-INPUT", "-j PVEFW-HOST-IN");
foreach my $rule (@$rules) {
next if $rule->{type} ne 'in';
next if $rule->{ipversion} && $rule->{ipversion} ne $ipversion;
- ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" }, undef, $cluster_conf);
+ ruleset_generate_rule($ruleset, $chain, $ipversion, $rule,
+ { ACCEPT => "PVEFW-SET-ACCEPT-MARK", REJECT => "PVEFW-reject" },
+ undef, $cluster_conf);
}
$chain = "GROUP-${group}-OUT";
next if $rule->{ipversion} && $rule->{ipversion} ne $ipversion;
# we use PVEFW-SET-ACCEPT-MARK (Instead of ACCEPT) because we need to
# check also other tap rules later
- ruleset_generate_rule($ruleset, $chain, $rule,
- { ACCEPT => 'PVEFW-SET-ACCEPT-MARK', REJECT => "PVEFW-reject" }, undef, $cluster_conf);
+ ruleset_generate_rule($ruleset, $chain, $ipversion, $rule,
+ { ACCEPT => 'PVEFW-SET-ACCEPT-MARK', REJECT => "PVEFW-reject" },
+ undef, $cluster_conf);
}
}
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 ? $fw_conf->{aliases}->{$alias} : undef;
+ $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 {
ruleset_create_chain($ruleset, $chain);
foreach my $rule (@{$std_chains->{$chain}}) {
if (ref($rule)) {
- ruleset_generate_rule($ruleset, $chain, $rule);
+ ruleset_generate_rule($ruleset, $chain, $ipversion, $rule);
} else {
ruleset_addrule($ruleset, $chain, $rule);
}
}
sub generate_ipset_chains {
- my ($ipset_ruleset, $clusterfw_conf, $fw_conf) = @_;
+ my ($ipset_ruleset, $clusterfw_conf, $fw_conf) = @_; #fixme
foreach my $ipset (keys %{$fw_conf->{ipset}}) {
- my $ipset_chain = compute_ipset_chain_name($fw_conf->{vmid}, $ipset);
- generate_ipset($ipset_ruleset, $ipset_chain, $fw_conf->{ipset}->{$ipset}, $clusterfw_conf, $fw_conf);
- }
-}
-sub generate_ipset {
- my ($ipset_ruleset, $name, $options, $clusterfw_conf, $fw_conf) = @_;
+ my $options = $fw_conf->{ipset}->{$ipset};
- die "duplicate ipset chain '$name'\n" if defined($ipset_ruleset->{$name});
+ # remove duplicates
+ my $nethash = {};
+ foreach my $entry (@$options) {
+ next if $entry->{errors}; # skip entries with errors
+ eval {
+ my ($cidr, $ver);
+ if ($entry->{cidr} =~ m/^${ip_alias_pattern}$/) {
+ ($cidr, $ver) = resolve_alias($clusterfw_conf, $fw_conf, $entry->{cidr});
+ } else {
+ ($cidr, $ver) = parse_ip_or_cidr($entry->{cidr});
+ }
+ #http://backreference.org/2013/03/01/ipv6-address-normalization/
+ if ($ver == 6) {
+ my $ipv6 = inet_pton(AF_INET6, lc($cidr));
+ $cidr = inet_ntop(AF_INET6, $ipv6);
+ $cidr =~ s|/128$||;
+ } else {
+ $cidr =~ s|/32$||;
+ }
- my $hashsize = scalar(@$options);
- if ($hashsize <= 64) {
- $hashsize = 64;
- } else {
- $hashsize = round_powerof2($hashsize);
- }
+ $nethash->{$ver}->{$cidr} = { cidr => $cidr, nomatch => $entry->{nomatch} };
+ };
+ warn $@ if $@;
+ }
- $ipset_ruleset->{$name} = ["create $name hash:net family inet hashsize $hashsize maxelem $hashsize"];
+ foreach my $ipversion (4, 6) {
+ my $data = $nethash->{$ipversion};
- # 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} };
- };
- warn $@ if $@;
- }
-
- foreach my $cidr (sort keys %$nethash) {
- my $entry = $nethash->{$cidr};
+ my $name = compute_ipset_chain_name($fw_conf->{vmid}, $ipset, $ipversion);
- my $cmd = "add $name $cidr";
- if ($entry->{nomatch}) {
- if ($feature_ipset_nomatch) {
- push @{$ipset_ruleset->{$name}}, "$cmd nomatch";
+ my $hashsize = scalar(@$options);
+ if ($hashsize <= 64) {
+ $hashsize = 64;
} else {
- warn "ignore !$cidr - nomatch not supported by kernel\n";
+ $hashsize = round_powerof2($hashsize);
+ }
+
+ my $family = $ipversion == "6" ? "inet6" : "inet";
+
+ $ipset_ruleset->{$name} = ["create $name hash:net family $family hashsize $hashsize maxelem $hashsize"];
+
+ foreach my $cidr (sort keys %$data) {
+ my $entry = $data->{$cidr};
+
+ my $cmd = "add $name $cidr";
+ if ($entry->{nomatch}) {
+ if ($feature_ipset_nomatch) {
+ push @{$ipset_ruleset->{$name}}, "$cmd nomatch";
+ } else {
+ warn "ignore !$cidr - nomatch not supported by kernel\n";
+ }
+ } else {
+ push @{$ipset_ruleset->{$name}}, $cmd;
+ }
}
- } else {
- push @{$ipset_ruleset->{$name}}, $cmd;
}
}
}
}
my ($ruleset, $ipset_ruleset) = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 4, $verbose);
- return ($ruleset, $ipset_ruleset);
+ 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) = @_;
$cluster_conf->{ipset}->{venet0} = [];
- my $venet0_ipset_chain = compute_ipset_chain_name(0, 'venet0');
+ my $venet0_ipset_chain = compute_ipset_chain_name(0, 'venet0', $ipversion);
my $localnet;
if ($cluster_conf->{aliases}->{local_network}) {
ruleset_addrule($ruleset, "PVEFW-INPUT", "-i venet0 -m set --match-set ${venet0_ipset_chain} src -j PVEFW-VENET-OUT");
ruleset_create_chain($ruleset, "PVEFW-FWBR-IN");
- ruleset_chain_add_input_filters($ruleset, "PVEFW-FWBR-IN", $hostfw_options, $cluster_conf, $loglevel);
+ ruleset_chain_add_input_filters($ruleset, "PVEFW-FWBR-IN", $ipversion, $hostfw_options, $cluster_conf, $loglevel);
ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-bridged --physdev-in fwln+ -j PVEFW-FWBR-IN");
ruleset_addrule($ruleset, "PVEFW-FORWARD", "-m physdev --physdev-is-bridged --physdev-out fwln+ -j PVEFW-FWBR-OUT");
ruleset_create_chain($ruleset, "PVEFW-VENET-IN");
- ruleset_chain_add_input_filters($ruleset, "PVEFW-VENET-IN", $hostfw_options, $cluster_conf, $loglevel);
+ ruleset_chain_add_input_filters($ruleset, "PVEFW-VENET-IN", $ipversion, $hostfw_options, $cluster_conf, $loglevel);
ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o venet0 -m set --match-set ${venet0_ipset_chain} dst -j PVEFW-VENET-IN");
}
sub get_ruleset_cmdlist {
- my ($ruleset, $verbose) = @_;
+ my ($ruleset, $verbose, $iptablescmd) = @_;
my $cmdlist = "*filter\n"; # we pass this to iptables-restore;
- my ($active_chains, $hooks) = iptables_get_chains();
+ my ($active_chains, $hooks) = iptables_get_chains($iptablescmd);
my $statushash = get_ruleset_status($ruleset, $active_chains, \&iptables_chain_digest, $verbose);
# create missing chains first
}
}
- foreach my $chain (sort keys %$ruleset) {
+ foreach my $chain (keys %$ruleset) {
my $stat = $statushash->{$chain};
die "internal error" if !$stat;
$cmdlist .= "$cmd\n";
}
}
+ }
+
+ foreach my $chain (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";
}
-
}
+ # the remove unused chains
foreach my $chain (keys %$statushash) {
next if $statushash->{$chain}->{action} ne 'delete';
}
sub apply_ruleset {
- my ($ruleset, $hostfw_conf, $ipset_ruleset, $verbose) = @_;
+ my ($ruleset, $hostfw_conf, $ipset_ruleset, $rulesetv6, $verbose) = @_;
enable_bridge_firewall();
get_ipset_cmdlist($ipset_ruleset, undef, $verbose);
my ($cmdlist, $changes) = get_ruleset_cmdlist($ruleset, $verbose);
+ my ($cmdlistv6, $changesv6) = get_ruleset_cmdlist($rulesetv6, $verbose, "ip6tables");
if ($verbose) {
if ($ipset_changes) {
print "iptables changes:\n";
print $cmdlist;
}
+
+ if ($changesv6) {
+ print "ip6tables changes:\n";
+ print $cmdlistv6;
+ }
}
+ my $tmpfile = "$pve_fw_status_dir/ipsetcmdlist1";
+ PVE::Tools::file_set_contents($tmpfile, $ipset_create_cmdlist || '');
+
ipset_restore_cmdlist($ipset_create_cmdlist);
+ $tmpfile = "$pve_fw_status_dir/ip4cmdlist";
+ PVE::Tools::file_set_contents($tmpfile, $cmdlist || '');
+
iptables_restore_cmdlist($cmdlist);
+ $tmpfile = "$pve_fw_status_dir/ip6cmdlist";
+ PVE::Tools::file_set_contents($tmpfile, $cmdlistv6 || '');
+
+ ip6tables_restore_cmdlist($cmdlistv6);
+
+ $tmpfile = "$pve_fw_status_dir/ipsetcmdlist2";
+ PVE::Tools::file_set_contents($tmpfile, $ipset_delete_cmdlist || '');
+
ipset_restore_cmdlist($ipset_delete_cmdlist) if $ipset_delete_cmdlist;
# test: re-read status and check if everything is up to date
}
}
+ my $active_chainsv6 = iptables_get_chains("ip6tables");
+ my $statushashv6 = get_ruleset_status($rulesetv6, $active_chainsv6, \&iptables_chain_digest, 0);
+
+ foreach my $chain (sort keys %$rulesetv6) {
+ my $stat = $statushashv6->{$chain};
+ if ($stat->{action} ne 'exists') {
+ warn "unable to update chain '$chain'\n";
+ $errors = 1;
+ }
+ }
+
die "unable to apply firewall changes\n" if $errors;
update_nf_conntrack_max($hostfw_conf);
sub remove_pvefw_chains {
- my ($chash, $hooks) = iptables_get_chains();
+ PVE::Firewall::remove_pvefw_chains_iptables("iptables");
+ PVE::Firewall::remove_pvefw_chains_iptables("ip6tables");
+ PVE::Firewall::remove_pvefw_chains_ipset();
+
+}
+
+sub remove_pvefw_chains_iptables {
+ my ($iptablescmd) = @_;
+
+ my ($chash, $hooks) = iptables_get_chains($iptablescmd);
my $cmdlist = "*filter\n";
foreach my $h (qw(INPUT OUTPUT FORWARD)) {
}
$cmdlist .= "COMMIT\n";
- iptables_restore_cmdlist($cmdlist);
+ if($iptablescmd eq "ip6tables") {
+ ip6tables_restore_cmdlist($cmdlist);
+ } else {
+ iptables_restore_cmdlist($cmdlist);
+ }
+}
+
+sub remove_pvefw_chains_ipset {
my $ipset_chains = ipset_get_chains();
- $cmdlist = "";
+ my $cmdlist = "";
foreach my $chain (keys %$ipset_chains) {
- $cmdlist .= "flush $chain\n";
- $cmdlist .= "destroy $chain\n";
+ $cmdlist .= "flush $chain\n";
+ $cmdlist .= "destroy $chain\n";
}
- ipset_restore_cmdlist($cmdlist) if $cmdlist;
+ ipset_restore_cmdlist($cmdlist) if $cmdlist;
}
sub init {
return;
}
- my $hostfw_conf = load_hostfw_conf();
+ my $hostfw_conf = load_hostfw_conf($cluster_conf);
- my ($ruleset, $ipset_ruleset) = compile($cluster_conf, $hostfw_conf);
+ my ($ruleset, $ipset_ruleset, $rulesetv6) = compile($cluster_conf, $hostfw_conf);
- apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset);
+ apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $rulesetv6);
};
run_locked($code);