my $have_qemu_server;
eval {
require PVE::QemuServer;
+ require PVE::QemuConfig;
$have_qemu_server = 1;
};
{ action => 'PARAM', proto => 'icmpv6', dport => 'neighbor-advertisement' },
],
'DHCPv6' => [
+ "DHCPv6 traffic",
{ action => 'PARAM', proto => 'udp', dport => '546:547', sport => '546:547' },
],
'Trcrt' => [
my $pve_fw_macro_ipversion = {};
my $pve_fw_preferred_macro_names = {};
+my $FWACCEPTMARK_ON = "0x80000000/0x80000000";
+my $FWACCEPTMARK_OFF = "0x00000000/0x80000000";
+
my $pve_std_chains = {};
$pve_std_chains->{4} = {
'PVEFW-SET-ACCEPT-MARK' => [
- "-j MARK --set-mark 1",
+ "-j MARK --set-mark $FWACCEPTMARK_ON",
],
'PVEFW-DropBroadcast' => [
# same as shorewall 'Broadcast'
$pve_std_chains->{6} = {
'PVEFW-SET-ACCEPT-MARK' => [
- "-j MARK --set-mark 1",
+ "-j MARK --set-mark $FWACCEPTMARK_ON",
],
'PVEFW-DropBroadcast' => [
# same as shorewall 'Broadcast'
return wantarray ? ($res, $digest) : $res;
}
+our $cluster_option_properties = {
+ enable => {
+ description => "Enable or disable the firewall cluster wide.",
+ type => 'integer',
+ minimum => 0,
+ optional => 1,
+ },
+ policy_in => {
+ description => "Input policy.",
+ type => 'string',
+ optional => 1,
+ enum => ['ACCEPT', 'REJECT', 'DROP'],
+ },
+ policy_out => {
+ description => "Output policy.",
+ type => 'string',
+ optional => 1,
+ enum => ['ACCEPT', 'REJECT', 'DROP'],
+ },
+};
+
+our $host_option_properties = {
+ enable => {
+ description => "Enable host firewall rules.",
+ type => 'boolean',
+ optional => 1,
+ },
+ log_level_in => get_standard_option('pve-fw-loglevel', {
+ description => "Log level for incoming traffic." }),
+ log_level_out => get_standard_option('pve-fw-loglevel', {
+ description => "Log level for outgoing traffic." }),
+ tcp_flags_log_level => get_standard_option('pve-fw-loglevel', {
+ description => "Log level for illegal tcp flags filter." }),
+ smurf_log_level => get_standard_option('pve-fw-loglevel', {
+ description => "Log level for SMURFS filter." }),
+ nosmurfs => {
+ description => "Enable SMURFS filter.",
+ type => 'boolean',
+ optional => 1,
+ },
+ tcpflags => {
+ description => "Filter illegal combinations of TCP flags.",
+ type => 'boolean',
+ optional => 1,
+ },
+ nf_conntrack_max => {
+ description => "Maximum number of tracked connections.",
+ type => 'integer',
+ optional => 1,
+ minimum => 32768,
+ },
+ nf_conntrack_tcp_timeout_established => {
+ description => "Conntrack established timeout.",
+ type => 'integer',
+ optional => 1,
+ minimum => 7875,
+ },
+ ndp => {
+ description => "Enable NDP.",
+ type => 'boolean',
+ optional => 1,
+ },
+};
+
+our $vm_option_properties = {
+ enable => {
+ description => "Enable/disable firewall rules.",
+ type => 'boolean',
+ optional => 1,
+ },
+ macfilter => {
+ description => "Enable/disable MAC address filter.",
+ type => 'boolean',
+ optional => 1,
+ },
+ dhcp => {
+ description => "Enable DHCP.",
+ type => 'boolean',
+ optional => 1,
+ },
+ ndp => {
+ description => "Enable NDP.",
+ type => 'boolean',
+ optional => 1,
+ },
+ radv => {
+ description => "Allow sending Router Advertisement.",
+ type => 'boolean',
+ optional => 1,
+ },
+ ipfilter => {
+ description => "Enable default IP filters. " .
+ "This is equivalent to adding an empty ipfilter-net<id> ipset " .
+ "for every interface. Such ipsets implicitly contain sane default " .
+ "restrictions such as restricting IPv6 link local addresses to " .
+ "the one derived from the interface's MAC address. For containers " .
+ "the configured IP addresses will be implicitly added.",
+ type => 'boolean',
+ optional => 1,
+ },
+ policy_in => {
+ description => "Input policy.",
+ type => 'string',
+ optional => 1,
+ enum => ['ACCEPT', 'REJECT', 'DROP'],
+ },
+ policy_out => {
+ description => "Output policy.",
+ type => 'string',
+ optional => 1,
+ enum => ['ACCEPT', 'REJECT', 'DROP'],
+ },
+ log_level_in => get_standard_option('pve-fw-loglevel', {
+ description => "Log level for incoming traffic." }),
+ log_level_out => get_standard_option('pve-fw-loglevel', {
+ description => "Log level for outgoing traffic." }),
+
+};
+
+
+my $addr_list_descr = "This can refer to a single IP address, an IP set ('+ipsetname') or an IP alias definition. You can also specify an address range like '20.34.101.207-201.3.9.99', or a list of IP addresses and networks (entries are separated by comma). Please do not mix IPv4 and IPv6 addresses inside such lists.";
+
+my $port_descr = "You can use service names or simple numbers (0-65535), as defined in '/etc/services'. Port ranges can be specified with '\\d+:\\d+', for example '80:85', and you can use comma separated list to match several ports or ranges.";
+
my $rule_properties = {
pos => {
description => "Update rule at position <pos>.",
optional => 1,
maxLength => 128,
},
- iface => get_standard_option('pve-iface', { optional => 1 }),
+ iface => get_standard_option('pve-iface', {
+ description => "Network interface name. You have to use network configuration key names for VMs and containers ('net\\d+'). Host related rules can use arbitrary strings.",
+ optional => 1
+ }),
source => {
+ description => "Restrict packet source address. $addr_list_descr",
type => 'string', format => 'pve-fw-addr-spec',
optional => 1,
},
dest => {
+ description => "Restrict packet destination address. $addr_list_descr",
type => 'string', format => 'pve-fw-addr-spec',
optional => 1,
},
proto => {
+ description => "IP protocol. You can use protocol names ('tcp'/'udp') or simple numbers, as defined in '/etc/protocols'.",
type => 'string', format => 'pve-fw-protocol-spec',
optional => 1,
},
optional => 1,
},
sport => {
+ description => "Restrict TCP/UDP source port. $port_descr",
type => 'string', format => 'pve-fw-sport-spec',
optional => 1,
},
dport => {
+ description => "Restrict TCP/UDP destination port. $port_descr",
type => 'string', format => 'pve-fw-dport-spec',
optional => 1,
},
if ($ipfilter_ipset) {
ruleset_addrule($ruleset, $chain, "-m set ! --match-set $ipfilter_ipset src -j DROP");
}
- ruleset_addrule($ruleset, $chain, "-j MARK --set-mark 0"); # clear mark
+ ruleset_addrule($ruleset, $chain, "-j MARK --set-mark $FWACCEPTMARK_OFF"); # clear mark
}
my $accept_action = $direction eq 'OUT' ? '-g PVEFW-SET-ACCEPT-MARK' : "-j $accept";
ruleset_addrule($ruleset, $chain, "-j $group_chain");
}
- ruleset_addrule($ruleset, $chain, "-m mark --mark 1 -j $action");
+ ruleset_addrule($ruleset, $chain, "-m mark --mark $FWACCEPTMARK_ON -j $action");
}
sub ruleset_generate_vm_rules {
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 $chain = "GROUP-${group}-IN";
ruleset_create_chain($ruleset, $chain);
- ruleset_addrule($ruleset, $chain, "-j MARK --set-mark 0"); # clear mark
+ ruleset_addrule($ruleset, $chain, "-j MARK --set-mark $FWACCEPTMARK_OFF"); # clear mark
foreach my $rule (@$rules) {
next if $rule->{type} ne 'in';
$chain = "GROUP-${group}-OUT";
ruleset_create_chain($ruleset, $chain);
- ruleset_addrule($ruleset, $chain, "-j MARK --set-mark 0"); # clear mark
+ ruleset_addrule($ruleset, $chain, "-j MARK --set-mark $FWACCEPTMARK_OFF"); # clear mark
foreach my $rule (@$rules) {
next if $rule->{type} ne 'out';
$valid_netdev_names->{"net$i"} = 1;
}
+sub get_mark_values {
+ my ($value, $mask) = @_;
+ $value = hex($value) if $value =~ /^0x/;
+ $mask = hex($mask) if defined($mask) && $mask =~ /^0x/;
+ $mask = 0xffffffff if !defined($mask);
+ return ($value, $mask);
+}
+
sub parse_fw_rule {
my ($prefix, $line, $cluster_conf, $fw_conf, $rule_env, $verbose) = @_;
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) {
next if !$d->{type};
if ($d->{type} eq 'qemu') {
if ($have_qemu_server) {
- my $cfspath = PVE::QemuServer::cfs_config_path($vmid);
+ my $cfspath = PVE::QemuConfig->cfs_config_path($vmid);
if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) {
$qemu->{$vmid} = $conf;
}
}
} 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, $device_ips) = @_; #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}) {
}
#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$||;
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};
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} = [
];
}
- generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf, $device_ips);
+ 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;
+ 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::parse_lxc_network($conf->{$netid});
+ 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);
- $device_ips->{$netid} = [
+ my $set = $device_ips->{$netid} = [
{ cidr => $linklocal },
{ cidr => 'fe80::/10', nomatch => 1 }
];
+ if (defined($net->{ip}) && $net->{ip} =~ m!^($IPV4RE)(?:/\d+)?$!) {
+ push @$set, { cidr => $1 };
+ }
+ if (defined($net->{ip6}) && $net->{ip6} =~ m!^($IPV6RE)(?:/\d+)?$!) {
+ push @$set, { cidr => $1 };
+ }
}
- generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf, $device_ips);
- };
- warn $@ if $@; # just to be sure - should not happen
+ 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);
+ generate_ipset_chains($ipset_ruleset, undef, $cluster_conf, undef, $cluster_conf->{ipset});
return $ipset_ruleset;
}