use PVE::Cluster;
use PVE::ProcFSTools;
use PVE::Tools qw($IPV4RE $IPV6RE);
+use PVE::Network;
use File::Basename;
use File::Path;
use IO::File;
my $max_alias_name_length = 64;
my $max_ipset_name_length = 64;
-my $max_group_name_length = 20;
+my $max_group_name_length = 18;
+
+my $PROTOCOLS_WITH_PORTS = {
+ udp => 1, 17 => 1,
+ udplite => 1, 136 => 1,
+ tcp => 1, 6 => 1,
+ dccp => 1, 33 => 1,
+ sctp => 1, 132 => 1,
+};
PVE::JSONSchema::register_format('IPorCIDR', \&pve_verify_ip_or_cidr);
sub pve_verify_ip_or_cidr {
{ action => 'PARAM', proto => 'tcp', dport => '465' },
{ action => 'PARAM', proto => 'tcp', dport => '587' },
],
+ 'MDNS' => [
+ "Multicast DNS",
+ { action => 'PARAM', proto => 'udp', dport => '5353' },
+ ],
'Munin' => [
"Munin networked resource monitoring traffic",
{ action => 'PARAM', proto => 'tcp', dport => '4949' },
return $etc_protocols;
}
-my $ipv4_mask_hash_localnet = {
- '255.255.0.0' => 16,
- '255.255.128.0' => 17,
- '255.255.192.0' => 18,
- '255.255.224.0' => 19,
- '255.255.240.0' => 20,
- '255.255.248.0' => 21,
- '255.255.252.0' => 22,
- '255.255.254.0' => 23,
- '255.255.255.0' => 24,
- '255.255.255.128' => 25,
- '255.255.255.192' => 26,
- '255.255.255.224' => 27,
- '255.255.255.240' => 28,
- '255.255.255.248' => 29,
- '255.255.255.252' => 30,
-};
-
my $__local_network;
sub local_network {
my $mask;
if ($isv6) {
$mask = $entry->{prefix};
+ next if !$mask; # skip the default route...
} else {
- $mask = $ipv4_mask_hash_localnet->{$entry->{mask}};
+ $mask = $PVE::Network::ipv4_mask_hash_localnet->{$entry->{mask}};
next if !defined($mask);
}
my $cidr = "$entry->{dest}/$mask";
my $testnet = Net::IP->new($cidr);
- if ($testnet->overlaps($testip) == $Net::IP::IP_B_IN_A_OVERLAP) {
+ my $overlap = $testnet->overlaps($testip);
+ if ($overlap == $Net::IP::IP_B_IN_A_OVERLAP ||
+ $overlap == $Net::IP::IP_IDENTICAL)
+ {
$__local_network = $cidr;
return;
}
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.";
},
digest => get_standard_option('pve-config-digest'),
type => {
+ description => "Rule type.",
type => 'string',
optional => 1,
enum => ['in', 'out', 'group'],
minLength => 2,
},
macro => {
+ description => "Use predefined standard macro.",
type => 'string',
optional => 1,
maxLength => 128,
optional => 1,
},
enable => {
+ description => "Flag to enable/disable a rule.",
type => 'integer',
minimum => 0,
optional => 1,
optional => 1,
},
comment => {
+ description => "Descriptive comment.",
type => 'string',
optional => 1,
},
if ($rule->{dport}) {
eval { parse_port_name_number_or_range($rule->{dport}, 1); };
&$add_error('dport', $@) if $@;
+ my $proto = $rule->{proto};
&$add_error('proto', "missing property - 'dport' requires this property")
- if !$rule->{proto};
+ if !$proto;
+ &$add_error('dport', "protocol '$proto' does not support ports")
+ if !$PROTOCOLS_WITH_PORTS->{$proto} &&
+ $proto ne 'icmp' && $proto ne 'icmpv6'; # special cases
}
if ($rule->{sport}) {
eval { parse_port_name_number_or_range($rule->{sport}, 0); };
&$add_error('sport', $@) if $@;
+ my $proto = $rule->{proto};
&$add_error('proto', "missing property - 'sport' requires this property")
- if !$rule->{proto};
+ if !$proto;
+ &$add_error('sport', "protocol '$proto' does not support ports")
+ if !$PROTOCOLS_WITH_PORTS->{$proto};
}
if ($rule->{source}) {
}
}
- if ($rule->{proto}) {
- push @cmd, "-p $rule->{proto}";
+ if (my $proto = $rule->{proto}) {
+ push @cmd, "-p $proto";
my $multiport = 0;
$multiport++ if $nbdport > 1;
if ($multiport == 2) && ($rule->{dport} ne $rule->{sport});
if ($rule->{dport}) {
- if ($rule->{proto} && $rule->{proto} eq 'icmp') {
+ if ($proto eq 'icmp') {
# Note: we use dport to store --icmp-type
die "unknown icmp-type '$rule->{dport}'\n"
if $rule->{dport} !~ /^\d+$/ && !defined($icmp_type_names->{$rule->{dport}});
push @cmd, "-m icmp --icmp-type $rule->{dport}";
- } elsif ($rule->{proto} && $rule->{proto} eq 'icmpv6') {
+ } elsif ($proto eq 'icmpv6') {
# Note: we use dport to store --icmpv6-type
die "unknown icmpv6-type '$rule->{dport}'\n"
if $rule->{dport} !~ /^\d+$/ && !defined($icmpv6_type_names->{$rule->{dport}});
push @cmd, "-m icmpv6 --icmpv6-type $rule->{dport}";
+ } elsif (!$PROTOCOLS_WITH_PORTS->{$proto}) {
+ die "protocol $proto does not have ports\n";
} else {
if ($nbdport > 1) {
if ($multiport == 2) {
}
if ($rule->{sport}) {
+ die "protocol $proto does not have ports\n"
+ if !$PROTOCOLS_WITH_PORTS->{$proto};
if ($nbsport > 1) {
push @cmd, "--sports $rule->{sport}" if $multiport != 2;
} else {
}
# generate firewall rules for QEMU VMs
- foreach my $vmid (keys %{$vmdata->{qemu}}) {
+ foreach my $vmid (sort keys %{$vmdata->{qemu}}) {
eval {
my $conf = $vmdata->{qemu}->{$vmid};
my $vmfw_conf = $vmfw_configs->{$vmid};
return if !$vmfw_conf;
- foreach my $netid (keys %$conf) {
+ foreach my $netid (sort keys %$conf) {
next if $netid !~ m/^net(\d+)$/;
my $net = PVE::QemuServer::parse_net($conf->{$netid});
next if !$net->{firewall};
}
# generate firewall rules for LXC containers
- foreach my $vmid (keys %{$vmdata->{lxc}}) {
+ foreach my $vmid (sort keys %{$vmdata->{lxc}}) {
eval {
my $conf = $vmdata->{lxc}->{$vmid};
my $vmfw_conf = $vmfw_configs->{$vmid};
return if !$vmfw_conf;
if ($vmfw_conf->{options}->{enable}) {
- foreach my $netid (keys %$conf) {
+ foreach my $netid (sort keys %$conf) {
next if $netid !~ m/^net(\d+)$/;
my $net = PVE::LXC::Config->parse_lxc_network($conf->{$netid});
next if !$net->{firewall};
{ cidr => $linklocal },
{ cidr => 'fe80::/10', nomatch => 1 }
];
- if ($net->{ip} =~ m!^($IPV4RE)(?:/\d+)?$!) {
+ if (defined($net->{ip}) && $net->{ip} =~ m!^($IPV4RE)(?:/\d+)?$!) {
push @$set, { cidr => $1 };
}
- if ($net->{ip6} =~ m!^($IPV6RE)(?:/\d+)?$!) {
+ if (defined($net->{ip6}) && $net->{ip6} =~ m!^($IPV6RE)(?:/\d+)?$!) {
push @$set, { cidr => $1 };
}
}