use Data::Dumper;
use Digest::SHA;
use PVE::INotify;
+use PVE::Exception qw(raise raise_param_exc);
use PVE::JSONSchema qw(get_standard_option);
use PVE::Cluster;
use PVE::ProcFSTools;
my ($str) = @_;
my $services = PVE::Firewall::get_etc_services();
- my $nbports = 0;
+ my $count = 0;
+ my $icmp_port = 0;
+
foreach my $item (split(/,/, $str)) {
- my $portlist = "";
- my $oldpon = undef;
- $nbports++;
- foreach my $pon (split(':', $item, 2)) {
- $pon = $services->{byname}->{$pon}->{port} if $services->{byname}->{$pon}->{port};
- if ($pon =~ m/^\d+$/){
- die "invalid port '$pon'\n" if $pon < 0 && $pon > 65535;
- die "port '$pon' must be bigger than port '$oldpon' \n" if $oldpon && ($pon < $oldpon);
- $oldpon = $pon;
- }else{
- die "invalid port $services->{byname}->{$pon}\n" if !$services->{byname}->{$pon};
+ $count++;
+ if ($item =~ m/^(\d+):(\d+)$/) {
+ my ($port1, $port2) = ($1, $2);
+ die "invalid port '$port1'\n" if $port1 > 65535;
+ die "invalid port '$port2'\n" if $port2 > 65535;
+ } elsif ($item =~ m/^(\d+)$/) {
+ my $port = $1;
+ die "invalid port '$port'\n" if $port > 65535;
+ } else {
+ if ($icmp_type_names->{$item}) {
+ $icmp_port = 1;
+ } else {
+ die "invalid port '$item'\n" if !$services->{byname}->{$item};
}
}
}
- return ($nbports);
+ die "ICPM ports not allowed in port range\n" if $icmp_port && $count > 1;
+
+ return $count;
}
PVE::JSONSchema::register_format('pve-fw-port-spec', \&pve_fw_verify_port_spec);
sub pve_fw_verify_port_spec {
my ($portstr) = @_;
- parse_port_name_number_or_range($portstr);
+ parse_port_name_number_or_range($portstr);
return $portstr;
}
return $rule;
}
+my $apply_macro = sub {
+ my ($macro_name, $param, $verify) = @_;
+
+ my $macro_rules = $pve_fw_parsed_macros->{$macro_name};
+ die "unknown macro '$macro_name'\n" if !$macro_rules; # should not happen
+
+ my $rules = [];
+
+ foreach my $templ (@$macro_rules) {
+ my $rule = {};
+ my $param_used = {};
+ foreach my $k (keys %$templ) {
+ my $v = $templ->{$k};
+ if ($v eq 'PARAM') {
+ $v = $param->{$k};
+ $param_used->{$k} = 1;
+ } elsif ($v eq 'DEST') {
+ $v = $param->{dest};
+ $param_used->{dest} = 1;
+ } elsif ($v eq 'SOURCE') {
+ $v = $param->{source};
+ $param_used->{source} = 1;
+ }
+
+ if (!defined($v)) {
+ my $msg = "missing parameter '$k' in macro '$macro_name'";
+ raise_param_exc({ macro => $msg }) if $verify;
+ die "$msg\n";
+ }
+ $rule->{$k} = $v;
+ }
+ foreach my $k (keys %$param) {
+ next if $k eq 'macro';
+ next if !defined($param->{$k});
+ next if $param_used->{$k};
+ if (defined($rule->{$k})) {
+ if ($rule->{$k} ne $param->{$k}) {
+ my $msg = "parameter '$k' already define in macro (value = '$rule->{$k}')";
+ raise_param_exc({ $k => $msg }) if $verify;
+ die "$msg\n";
+ }
+ } else {
+ $rule->{$k} = $param->{$k};
+ }
+ }
+ push @$rules, $rule;
+ }
+
+ return $rules;
+};
+
+sub verify_rule {
+ my ($rule, $allow_groups) = @_;
+
+ my $type = $rule->{type};
+
+ raise_param_exc({ type => "missing property"}) if !$type;
+ raise_param_exc({ action => "missing property"}) if !$rule->{action};
+
+ if ($type eq 'in' || $type eq 'out') {
+ raise_param_exc({ action => "unknown action '$rule->{action}'"})
+ if $rule->{action} !~ m/^(ACCEPT|DROP|REJECT)$/;
+ } elsif ($type eq 'group') {
+ raise_param_exc({ type => "security groups not allowed"})
+ if !$allow_groups;
+ raise_param_exc({ action => "invalid characters in security group name"})
+ if $rule->{action} !~ m/^[A-Za-z0-9_\-]+$/;
+ } else {
+ raise_param_exc({ type => "unknown rule type '$type'"});
+ }
+
+ # fixme: verify $rule->{iface}?
+
+ if ($rule->{macro}) {
+ my $preferred_name = $pve_fw_preferred_macro_names->{lc($rule->{macro})};
+ raise_param_exc({ macro => "unknown macro '$rule->{macro}'"}) if !$preferred_name;
+ $rule->{macro} = $preferred_name;
+ }
+
+ if ($rule->{dport}) {
+ eval { parse_port_name_number_or_range($rule->{dport}); };
+ raise_param_exc({ dport => $@ }) if $@;
+ }
+
+ if ($rule->{sport}) {
+ eval { parse_port_name_number_or_range($rule->{sport}); };
+ raise_param_exc({ sport => $@ }) if $@;
+ }
+
+ if ($rule->{source}) {
+ eval { parse_address_list($rule->{source}); };
+ raise_param_exc({ source => $@ }) if $@;
+ }
+
+ if ($rule->{dest}) {
+ eval { parse_address_list($rule->{dest}); };
+ raise_param_exc({ dest => $@ }) if $@;
+ }
+
+ if ($rule->{macro}) {
+ &$apply_macro($rule->{macro}, $rule, 1);
+ }
+
+ return $rule;
+}
+
sub copy_rule_data {
my ($rule, $param) = @_;
delete $rule->{$k};
}
}
+
+ # verify rule now
+
return $rule;
}
return scalar(@cmd) ? join(' ', @cmd) : undef;
}
-my $apply_macro = sub {
- my ($macro_name, $param) = @_;
-
- my $macro_rules = $pve_fw_parsed_macros->{$macro_name};
- die "unknown macro '$macro_name'\n" if !$macro_rules; # should not happen
-
- my $rules = [];
-
- foreach my $templ (@$macro_rules) {
- my $rule = {};
- my $param_used = {};
- foreach my $k (keys %$templ) {
- my $v = $templ->{$k};
- if ($v eq 'PARAM') {
- $v = $param->{$k};
- $param_used->{$k} = 1;
- } elsif ($v eq 'DEST') {
- $v = $param->{dest};
- $param_used->{dest} = 1;
- } elsif ($v eq 'SOURCE') {
- $v = $param->{source};
- $param_used->{source} = 1;
- }
-
- die "missing parameter '$k' in macro '$macro_name'\n" if !defined($v);
- $rule->{$k} = $v;
- }
- foreach my $k (keys %$param) {
- next if $k eq 'macro';
- next if !defined($param->{$k});
- next if $param_used->{$k};
- if (defined($rule->{$k})) {
- die "parameter '$k' already define in macro (value = '$rule->{$k}')\n"
- if $rule->{$k} ne $param->{$k};
- } else {
- $rule->{$k} = $param->{$k};
- }
- }
- push @$rules, $rule;
- }
-
- return $rules;
-};
-
sub ruleset_generate_rule {
my ($ruleset, $chain, $rule, $actions, $goto, $cluster_conf) = @_;
}
push @{$res->{$section}->{$group}}, $rule;
} elsif ($section eq 'ipset') {
- chomp $line;
- $line =~ m/^(\!)?\s*((\d+)\.(\d+)\.(\d+)\.(\d+)(\/(\d+))?)/;
+ # we can add single line comments to the end of the rule
+ my $comment = decode('utf8', $1) if $line =~ s/#\s*(.*?)\s*$//;
+
+ $line =~ m/^(\!)?\s*((?:\d+)\.(?:\d+)\.(?:\d+)\.(?:\d+))(?:\/(\d+))?\s*$/;
my $nomatch = $1;
- my $ip = $2;
+ my $cidr = $2;
- if(!$ip){
- warn "$prefix: $line is not an valid ip address\n";
- next;
- }
- if (!Net::IP->new($ip)) {
- warn "$prefix: $line is not an valid ip address\n";
+ $cidr .= "/$3" if defined($3) && $3 != 32;
+
+ if (!Net::IP->new($cidr)) {
+ my $err = Net::IP::Error();
+ warn "$prefix: $cidr - $err\n";
next;
}
- if ($nomatch) {
- if ($feature_ipset_nomatch) {
- push @{$res->{$section}->{$group}}, "$ip nomatch";
- } else {
- warn "$prefix: ignore $line - nomatch not supported by kernel\n";
- }
- } else {
- push @{$res->{$section}->{$group}}, $ip;
- }
+ my $entry = { cidr => $cidr };
+ $entry->{nomatch} = 1 if $nomatch;
+ $entry->{comment} = $comment if $comment;
+
+ push @{$res->{$section}->{$group}}, $entry;
}
}
if ($rule->{type} eq 'in' || $rule->{type} eq 'out') {
$raw .= '|' if defined($rule->{enable}) && !$rule->{enable};
$raw .= uc($rule->{type});
- $raw .= " " . $rule->{action};
+ if ($rule->{macro}) {
+ $raw .= " $rule->{macro}($rule->{action})";
+ } else {
+ $raw .= " " . $rule->{action};
+ }
$raw .= " " . ($rule->{iface} || '-') if $need_iface;
$raw .= " " . ($rule->{source} || '-');
$raw .= " " . ($rule->{dest} || '-');
return $raw;
};
+my $format_ipset = sub {
+ my ($options) = @_;
+
+ my $raw = '';
+
+ foreach my $entry (@$options) {
+ my $line = $entry->{nomatch} ? '!' : '';
+ $line .= $entry->{cidr};
+ $line .= " # " . encode('utf8', $entry->{comment})
+ if $entry->{comment} && $entry->{comment} !~ m/^\s*$/;
+ $raw .= "$line\n";
+ }
+
+ return $raw;
+};
+
sub save_vmfw_conf {
my ($vmid, $vmfw_conf) = @_;
push @{$ipset_ruleset->{$name}}, "create $name hash:net family inet hashsize $hashsize maxelem $hashsize";
- foreach my $ip (@$options) {
- push @{$ipset_ruleset->{$name}}, "add $name $ip";
+ foreach my $entry (@$options) {
+ my $cidr = $entry->{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;
+ }
}
}
my $options = $cluster_conf->{options};
$raw .= &$format_options($options) if scalar(keys %$options);
- # fixme: save ipset
+ foreach my $ipset (sort keys %{$cluster_conf->{ipset}}) {
+ $raw .= "[IPSET $ipset]\n\n";
+ my $options = $cluster_conf->{ipset}->{$ipset};
+ $raw .= &$format_ipset($options);
+ $raw .= "\n";
+ }
my $rules = $cluster_conf->{rules};
if (scalar(@$rules)) {