]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/PVE/Firewall.pm
ndp: use PVEFW-SET-ACCEPT-MARK and move rules further down
[pve-firewall.git] / src / PVE / Firewall.pm
index 762816fff102ff2c17721695f8ee59c10e6295cb..62b138935389fb68377e3bcc9d3d40846dbdcc94 100644 (file)
@@ -139,6 +139,7 @@ my $pve_ipv6fw_macros = {
     ],
     'NeighborDiscovery' => [
        "IPv6 neighbor solicitation, neighbor and router advertisement",
+       { action => 'PARAM', proto => 'icmpv6', dport => 'router-solicitation' },
        { action => 'PARAM', proto => 'icmpv6', dport => 'router-advertisement' },
        { action => 'PARAM', proto => 'icmpv6', dport => 'neighbor-solicitation' },
        { action => 'PARAM', proto => 'icmpv6', dport => 'neighbor-advertisement' },
@@ -984,7 +985,7 @@ sub parse_address_list {
 }
 
 sub parse_port_name_number_or_range {
-    my ($str) = @_;
+    my ($str, $dport) = @_;
 
     my $services = PVE::Firewall::get_etc_services();
     my $count = 0;
@@ -1000,9 +1001,9 @@ sub parse_port_name_number_or_range {
            my $port = $1;
            die "invalid port '$port'\n" if $port > 65535;
        } else {
-           if ($icmp_type_names->{$item}) {
+           if ($dport && $icmp_type_names->{$item}) {
                $icmp_port = 1;
-           } elsif ($icmpv6_type_names->{$item}) {
+           } elsif ($dport && $icmpv6_type_names->{$item}) {
                $icmp_port = 1;
            } else {
                die "invalid port '$item'\n" if !$services->{byname}->{$item};
@@ -1015,11 +1016,20 @@ sub parse_port_name_number_or_range {
     return $count;
 }
 
-PVE::JSONSchema::register_format('pve-fw-port-spec', \&pve_fw_verify_port_spec);
-sub pve_fw_verify_port_spec {
+PVE::JSONSchema::register_format('pve-fw-sport-spec', \&pve_fw_verify_sport_spec);
+sub pve_fw_verify_sport_spec {
    my ($portstr) = @_;
 
-   parse_port_name_number_or_range($portstr);
+   parse_port_name_number_or_range($portstr, 0);
+
+   return $portstr;
+}
+
+PVE::JSONSchema::register_format('pve-fw-dport-spec', \&pve_fw_verify_dport_spec);
+sub pve_fw_verify_dport_spec {
+   my ($portstr) = @_;
+
+   parse_port_name_number_or_range($portstr, 1);
 
    return $portstr;
 }
@@ -1082,7 +1092,10 @@ sub copy_list_with_digest {
            next if !defined($v);
            $data->{$k} = $v;
            # Note: digest ignores refs ($rule->{errors})
-           $sha->add($k, ':', $v, "\n") if !ref($v); ;
+           # since Digest::SHA expects a series of bytes,
+           #  we have to encode the value here to prevent errors when
+           #  using utf8 characters (eg. in comments)
+           $sha->add($k, ':', encode_utf8($v), "\n") if !ref($v); ;
        }
        push @$res, $data;
     }
@@ -1140,11 +1153,11 @@ my $rule_properties = {
        optional => 1,
     },
     sport => {
-       type => 'string', format => 'pve-fw-port-spec',
+       type => 'string', format => 'pve-fw-sport-spec',
        optional => 1,
     },
     dport => {
-       type => 'string', format => 'pve-fw-port-spec',
+       type => 'string', format => 'pve-fw-dport-spec',
        optional => 1,
     },
     comment => {
@@ -1351,14 +1364,14 @@ sub verify_rule {
     }
 
     if ($rule->{dport}) {
-       eval { parse_port_name_number_or_range($rule->{dport}); };
+       eval { parse_port_name_number_or_range($rule->{dport}, 1); };
        &$add_error('dport', $@) if $@;
        &$add_error('proto', "missing property - 'dport' requires this property")
            if !$rule->{proto};
     }
 
     if ($rule->{sport}) {
-       eval { parse_port_name_number_or_range($rule->{sport}); };
+       eval { parse_port_name_number_or_range($rule->{sport}, 0); };
        &$add_error('sport', $@) if $@;
        &$add_error('proto', "missing property - 'sport' requires this property")
            if !$rule->{proto};
@@ -1615,8 +1628,8 @@ sub ruleset_generate_cmdstr {
 
     die "unable to emit macro - internal error" if $rule->{macro}; # should not happen
 
-    my $nbdport = defined($rule->{dport}) ? parse_port_name_number_or_range($rule->{dport}) : 0;
-    my $nbsport = defined($rule->{sport}) ? parse_port_name_number_or_range($rule->{sport}) : 0;
+    my $nbdport = defined($rule->{dport}) ? parse_port_name_number_or_range($rule->{dport}, 1) : 0;
+    my $nbsport = defined($rule->{sport}) ? parse_port_name_number_or_range($rule->{sport}, 0) : 0;
 
     my @cmd = ();
 
@@ -1864,13 +1877,15 @@ sub ruleset_add_chain_policy {
 }
 
 sub ruleset_chain_add_ndp {
-    my ($ruleset, $chain, $ipversion, $options) = @_;
+    my ($ruleset, $chain, $ipversion, $options, $direction, $accept) = @_;
     return if $ipversion != 6 || (defined($options->{ndp}) && !$options->{ndp});
 
-    ruleset_addrule($ruleset, $chain, "-p icmpv6 --icmpv6-type router-solicitation -j ACCEPT");
-    ruleset_addrule($ruleset, $chain, "-p icmpv6 --icmpv6-type router-advertisement -j ACCEPT");
-    ruleset_addrule($ruleset, $chain, "-p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT");
-    ruleset_addrule($ruleset, $chain, "-p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT");
+    ruleset_addrule($ruleset, $chain, "-p icmpv6 --icmpv6-type router-solicitation $accept");
+    if ($direction ne 'OUT' || $options->{radv}) {
+       ruleset_addrule($ruleset, $chain, "-p icmpv6 --icmpv6-type router-advertisement $accept");
+    }
+    ruleset_addrule($ruleset, $chain, "-p icmpv6 --icmpv6-type neighbor-solicitation $accept");
+    ruleset_addrule($ruleset, $chain, "-p icmpv6 --icmpv6-type neighbor-advertisement $accept");
 }
 
 sub ruleset_chain_add_conn_filters {
@@ -1922,17 +1937,21 @@ sub ruleset_create_vm_chain {
        }
     }
 
-    ruleset_chain_add_ndp($ruleset, $chain, $ipversion, $options);
-
     if ($direction eq 'OUT') {
        if (defined($macaddr) && !(defined($options->{macfilter}) && $options->{macfilter} == 0)) {
            ruleset_addrule($ruleset, $chain, "-m mac ! --mac-source $macaddr -j DROP");
        }
+       if ($ipversion == 6 && !$options->{radv}) {
+           ruleset_addrule($ruleset, $chain, '-p icmpv6 --icmpv6-type router-advertisement -j DROP');
+       }
        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
     }
+
+    my $accept_action = $direction eq 'OUT' ? '-g PVEFW-SET-ACCEPT-MARK' : "-j $accept";
+    ruleset_chain_add_ndp($ruleset, $chain, $ipversion, $options, $direction, $accept_action);
 }
 
 sub ruleset_add_group_rule {
@@ -2130,8 +2149,8 @@ sub enable_host_firewall {
 
     ruleset_addrule($ruleset, $chain, "-i lo -j ACCEPT");
 
-    ruleset_chain_add_ndp($ruleset, $chain, $ipversion, $options);
     ruleset_chain_add_conn_filters($ruleset, $chain, 'ACCEPT');
+    ruleset_chain_add_ndp($ruleset, $chain, $ipversion, $options, 'IN', '-j RETURN');
     ruleset_chain_add_input_filters($ruleset, $chain, $ipversion, $options, $cluster_conf, $loglevel);
 
     # we use RETURN because we need to check also tap rules
@@ -2189,11 +2208,11 @@ sub enable_host_firewall {
 
     ruleset_addrule($ruleset, $chain, "-o lo -j ACCEPT");
 
-    ruleset_chain_add_ndp($ruleset, $chain, $ipversion, $options);
     ruleset_chain_add_conn_filters($ruleset, $chain, 'ACCEPT');
 
     # we use RETURN because we may want to check other thigs later
     $accept_action = 'RETURN';
+    ruleset_chain_add_ndp($ruleset, $chain, $ipversion, $options, 'OUT', "-j $accept_action");
 
     ruleset_addrule($ruleset, $chain, "-p igmp -j $accept_action"); # important for multicast
 
@@ -2364,7 +2383,7 @@ sub parse_vmfw_option {
 
     my $loglevels = "emerg|alert|crit|err|warning|notice|info|debug|nolog";
 
-    if ($line =~ m/^(enable|dhcp|ndp|macfilter|ips):\s*(0|1)\s*$/i) {
+    if ($line =~ m/^(enable|dhcp|ndp|radv|macfilter|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) {