]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
improve search for local-network
[pve-firewall.git] / src / PVE / Firewall.pm
index 4c1586afec58bb8f91639af7a942b93914c31e06..ef74ca2fae597a882ea30e778c49cee8cfc5ff77 100644 (file)
@@ -12,6 +12,7 @@ use PVE::JSONSchema qw(register_standard_option get_standard_option);
 use PVE::Cluster;
 use PVE::ProcFSTools;
 use PVE::Tools qw($IPV4RE $IPV6RE);
+use PVE::Network;
 use File::Basename;
 use File::Path;
 use IO::File;
@@ -49,7 +50,15 @@ our $ip_alias_pattern = '[A-Za-z][A-Za-z0-9\-\_]+';
 
 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 {
@@ -321,6 +330,10 @@ my $pve_fw_macros = {
        { 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' },
@@ -875,24 +888,6 @@ sub get_etc_protocols {
     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 {
@@ -916,13 +911,17 @@ 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;
            }
@@ -1257,6 +1256,7 @@ my $rule_properties = {
     },
     digest => get_standard_option('pve-config-digest'),
     type => {
+       description => "Rule type.",
        type => 'string',
        optional => 1,
        enum => ['in', 'out', 'group'],
@@ -1270,6 +1270,7 @@ my $rule_properties = {
        minLength => 2,
     },
     macro => {
+       description => "Use predefined standard macro.",
        type => 'string',
        optional => 1,
        maxLength => 128,
@@ -1294,6 +1295,7 @@ my $rule_properties = {
        optional => 1,
     },
     enable => {
+       description => "Flag to enable/disable a rule.",
         type => 'integer',
        minimum => 0,
        optional => 1,
@@ -1309,6 +1311,7 @@ my $rule_properties = {
        optional => 1,
     },
     comment => {
+       description => "Descriptive comment.",
        type => 'string',
        optional => 1,
     },
@@ -1511,15 +1514,22 @@ sub verify_rule {
     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}) {
@@ -1840,8 +1850,8 @@ sub ruleset_generate_cmdstr {
         }
     }
 
-    if ($rule->{proto}) {
-       push @cmd, "-p $rule->{proto}";
+    if (my $proto = $rule->{proto}) {
+       push @cmd, "-p $proto";
 
        my $multiport = 0;
        $multiport++ if $nbdport > 1;
@@ -1853,16 +1863,18 @@ sub ruleset_generate_cmdstr {
            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) {
@@ -1877,6 +1889,8 @@ sub ruleset_generate_cmdstr {
        }
 
        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 {
@@ -3368,13 +3382,13 @@ sub compile_iptables_filter {
     }
 
     # 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};
@@ -3391,14 +3405,14 @@ sub compile_iptables_filter {
     }
 
     # 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};