11 use PVE
::Tools
qw(run_command lock_file);
15 my $pve_fw_lock_filename = "/var/lock/pvefw.lck";
20 # todo: implement some kind of MACROS, like shorewall /usr/share/shorewall/macro.*
21 sub get_firewall_macros
{
23 return $macros if $macros;
25 #foreach my $path (</usr/share/shorewall/macro.*>) {
26 # if ($path =~ m|/macro\.(\S+)$|) {
31 $macros = {}; # fixme: implemet me
38 sub get_etc_services
{
40 return $etc_services if $etc_services;
42 my $filename = "/etc/services";
44 my $fh = IO
::File-
>new($filename, O_RDONLY
);
46 warn "unable to read '$filename' - $!\n";
52 while (my $line = <$fh>) {
54 next if $line =~m/^#/;
55 next if ($line =~m/^\s*$/);
57 if ($line =~ m!^(\S+)\s+(\S+)/(tcp|udp).*$!) {
58 $services->{byid
}->{$2}->{name
} = $1;
59 $services->{byid
}->{$2}->{$3} = 1;
60 $services->{byname
}->{$1} = $services->{byid
}->{$2};
66 $etc_services = $services;
74 sub get_etc_protocols
{
75 return $etc_protocols if $etc_protocols;
77 my $filename = "/etc/protocols";
79 my $fh = IO
::File-
>new($filename, O_RDONLY
);
81 warn "unable to read '$filename' - $!\n";
87 while (my $line = <$fh>) {
89 next if $line =~m/^#/;
90 next if ($line =~m/^\s*$/);
92 if ($line =~ m!^(\S+)\s+(\d+)\s+.*$!) {
93 $protocols->{byid
}->{$2}->{name
} = $1;
94 $protocols->{byname
}->{$1} = $protocols->{byid
}->{$2};
100 $etc_protocols = $protocols;
102 return $etc_protocols;
105 sub parse_address_list
{
109 foreach my $aor (split(/,/, $str)) {
110 if (!Net
::IP-
>new($aor)) {
111 my $err = Net
::IP
::Error
();
112 die "invalid IP address: $err\n";
120 sub parse_port_name_number_or_range
{
123 my $services = PVE
::Firewall
::get_etc_services
();
125 foreach my $item (split(/,/, $str)) {
127 foreach my $pon (split(':', $item, 2)) {
128 if ($pon =~ m/^\d+$/){
129 die "invalid port '$pon'\n" if $pon < 0 && $pon > 65536;
131 die "invalid port $services->{byname}->{$pon}\n" if !$services->{byname
}->{$pon};
140 my $rule_format = "%-15s %-30s %-30s %-15s %-15s %-15s\n";
145 run_command
("/sbin/iptables $cmd", outfunc
=> sub {}, errfunc
=> sub {});
148 sub iptables_restore
{
150 unshift (@ruleset, '*filter');
151 push (@ruleset, 'COMMIT');
153 my $cmdlist = join("\n", @ruleset) . "\n";
155 my $verbose = 1; # fixme: how/when do we set this
157 #run_command("echo '$cmdlist' | /sbin/iptables-restore -n");
158 eval { run_command
("/sbin/iptables-restore -n ", input
=> $cmdlist); };
160 print STDERR
$cmdlist if $verbose;
165 sub iptables_addrule
{
168 push (@ruleset, $rule);
171 sub iptables_chain_exist
{
175 iptables
("-n --list $chain");
182 sub iptables_rule_exist
{
186 iptables
("-C $rule");
193 sub iptables_generate_rule
{
194 my ($chain, $rule) = @_;
196 my $cmd = "-A $chain";
198 $cmd .= " -m iprange --src-range" if $rule->{nbsource
} && $rule->{nbsource
} > 1;
199 $cmd .= " -s $rule->{source}" if $rule->{source
};
200 $cmd .= " -m iprange --dst-range" if $rule->{nbdest
} && $rule->{nbdest
} > 1;
201 $cmd .= " -d $rule->{dest}" if $rule->{destination
};
202 $cmd .= " -p $rule->{proto}" if $rule->{proto
};
203 $cmd .= " --match multiport" if $rule->{nbdport
} && $rule->{nbdport
} > 1;
204 $cmd .= " --dport $rule->{dport}" if $rule->{dport
};
205 $cmd .= " --match multiport" if $rule->{nbsport
} && $rule->{nbsport
} > 1;
206 $cmd .= " --sport $rule->{sport}" if $rule->{sport
};
207 $cmd .= " -j $rule->{action}" if $rule->{action
};
209 iptables_addrule
($cmd);
213 sub generate_bridge_rules
{
216 if(!iptables_chain_exist
("BRIDGEFW-OUT")){
217 iptables_addrule
(":BRIDGEFW-OUT - [0:0]");
220 if(!iptables_chain_exist
("BRIDGEFW-IN")){
221 iptables_addrule
(":BRIDGEFW-IN - [0:0]");
224 if(!iptables_chain_exist
("proxmoxfw-FORWARD")){
225 iptables_addrule
(":proxmoxfw-FORWARD - [0:0]");
226 iptables_addrule
("-I FORWARD -j proxmoxfw-FORWARD");
227 iptables_addrule
("-A proxmoxfw-FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT");
228 iptables_addrule
("-A proxmoxfw-FORWARD -m physdev --physdev-is-in --physdev-is-bridged -j BRIDGEFW-OUT");
229 iptables_addrule
("-A proxmoxfw-FORWARD -m physdev --physdev-is-out --physdev-is-bridged -j BRIDGEFW-IN");
233 generate_proxmoxfwinput
();
235 if(!iptables_chain_exist
("$bridge-IN")){
236 iptables_addrule
(":$bridge-IN - [0:0]");
237 iptables_addrule
("-A proxmoxfw-FORWARD -i $bridge -j DROP"); #disable interbridge routing
238 iptables_addrule
("-A BRIDGEFW-IN -j $bridge-IN");
239 iptables_addrule
("-A $bridge-IN -j ACCEPT");
243 if(!iptables_chain_exist
("$bridge-OUT")){
244 iptables_addrule
(":$bridge-OUT - [0:0]");
245 iptables_addrule
("-A proxmoxfw-FORWARD -o $bridge -j DROP"); # disable interbridge routing
246 iptables_addrule
("-A BRIDGEFW-OUT -j $bridge-OUT");
253 sub generate_tap_rules_direction
{
254 my ($iface, $netid, $rules, $bridge, $direction) = @_;
256 my $tapchain = "$iface-$direction";
258 iptables_addrule
(":$tapchain - [0:0]");
260 iptables_addrule
("-A $tapchain -m state --state INVALID -j DROP");
261 iptables_addrule
("-A $tapchain -m state --state RELATED,ESTABLISHED -j ACCEPT");
263 if (scalar(@$rules)) {
264 foreach my $rule (@$rules) {
265 next if $rule->{iface
} && $rule->{iface
} ne $netid;
266 if($rule->{action
} =~ m/^(GROUP-(\S+))$/){
267 $rule->{action
} .= "-$direction";
268 #generate empty group rule if don't exist
269 if(!iptables_chain_exist
($rule->{action
})){
270 generate_group_rules
($2);
273 #we go to vmbr-IN if accept in out rules
274 $rule->{action
} = "$bridge-IN" if $rule->{action
} eq 'ACCEPT' && $direction eq 'OUT';
275 iptables_generate_rule
($tapchain, $rule);
279 iptables_addrule
("-A $tapchain -j LOG --log-prefix \"$tapchain-dropped: \" --log-level 4");
280 iptables_addrule
("-A $tapchain -j DROP");
282 #plug the tap chain to bridge chain
283 my $physdevdirection = $direction eq 'IN' ?
"out":"in";
284 my $rule = "$bridge-$direction -m physdev --physdev-$physdevdirection $iface --physdev-is-bridged -j $tapchain";
286 if(!iptables_rule_exist
($rule)){
287 iptables_addrule
("-I $rule");
290 if($direction eq 'OUT'){
292 my $rule = "proxmoxfw-INPUT -m physdev --physdev-$physdevdirection $iface -j $tapchain";
294 if(!iptables_rule_exist
($rule)){
295 iptables_addrule
("-A $rule");
300 sub generate_tap_rules
{
301 my ($net, $netid, $vmid) = @_;
303 my $filename = "/etc/pve/firewall/$vmid.fw";
304 my $fh = IO
::File-
>new($filename, O_RDONLY
);
307 #generate bridge rules
308 my $bridge = $net->{bridge
};
309 my $tag = $net->{tag
};
310 $bridge .= "v$tag" if $tag;
313 my $rules = parse_fw_rules
($filename, $fh);
315 my $inrules = $rules->{in};
316 my $outrules = $rules->{out
};
318 my $iface = "tap".$vmid."i".$1 if $netid =~ m/net(\d+)/;
320 generate_bridge_rules
($bridge);
321 generate_tap_rules_direction
($iface, $netid, $inrules, $bridge, 'IN');
322 generate_tap_rules_direction
($iface, $netid, $outrules, $bridge, 'OUT');
326 sub flush_tap_rules
{
327 my ($net, $netid, $vmid) = @_;
329 my $bridge = $net->{bridge
};
330 my $iface = "tap".$vmid."i".$1 if $netid =~ m/net(\d+)/;
332 flush_tap_rules_direction
($iface, $bridge, 'IN');
333 flush_tap_rules_direction
($iface, $bridge, 'OUT');
337 sub flush_tap_rules_direction
{
338 my ($iface, $bridge, $direction) = @_;
340 my $tapchain = "$iface-$direction";
342 if(iptables_chain_exist
($tapchain)){
343 iptables_addrule
("-F $tapchain");
345 my $physdevdirection = $direction eq 'IN' ?
"out":"in";
346 my $rule = "$bridge-$direction -m physdev --physdev-$physdevdirection $iface --physdev-is-bridged -j $tapchain";
347 if(iptables_rule_exist
($rule)){
348 iptables_addrule
("-D $rule");
351 if($direction eq 'OUT'){
352 my $rule = "proxmoxfw-INPUT -m physdev --physdev-$physdevdirection $iface -j $tapchain";
353 if(iptables_rule_exist
($rule)){
354 iptables_addrule
("-D $rule");
358 iptables_addrule
("-X $tapchain");
364 generate_proxmoxfwinput
();
365 generate_proxmoxfwoutput
();
367 my $filename = "/etc/pve/local/host.fw";
368 my $fh = IO
::File-
>new($filename, O_RDONLY
);
371 my $rules = parse_fw_rules
($filename, $fh);
372 my $inrules = $rules->{in};
373 my $outrules = $rules->{out
};
375 #host inbound firewall
376 iptables_addrule
(":host-IN - [0:0]");
377 iptables_addrule
("-A host-IN -m state --state INVALID -j DROP");
378 iptables_addrule
("-A host-IN -m state --state RELATED,ESTABLISHED -j ACCEPT");
379 iptables_addrule
("-A host-IN -i lo -j ACCEPT");
380 iptables_addrule
("-A host-IN -m addrtype --dst-type MULTICAST -j ACCEPT");
381 iptables_addrule
("-A host-IN -p udp -m state --state NEW -m multiport --dports 5404,5405 -j ACCEPT");
382 iptables_addrule
("-A host-IN -p udp -m udp --dport 9000 -j ACCEPT"); #corosync
384 if (scalar(@$inrules)) {
385 foreach my $rule (@$inrules) {
386 #we use RETURN because we need to check also tap rules
387 $rule->{action
} = 'RETURN' if $rule->{action
} eq 'ACCEPT';
388 iptables_generate_rule
('host-IN', $rule);
392 iptables_addrule
("-A host-IN -j LOG --log-prefix \"kvmhost-IN dropped: \" --log-level 4");
393 iptables_addrule
("-A host-IN -j DROP");
395 #host outbound firewall
396 iptables_addrule
(":host-OUT - [0:0]");
397 iptables_addrule
("-A host-OUT -m state --state INVALID -j DROP");
398 iptables_addrule
("-A host-OUT -m state --state RELATED,ESTABLISHED -j ACCEPT");
399 iptables_addrule
("-A host-OUT -o lo -j ACCEPT");
400 iptables_addrule
("-A host-OUT -m addrtype --dst-type MULTICAST -j ACCEPT");
401 iptables_addrule
("-A host-OUT -p udp -m state --state NEW -m multiport --dports 5404,5405 -j ACCEPT");
402 iptables_addrule
("-A host-OUT -p udp -m udp --dport 9000 -j ACCEPT"); #corosync
404 if (scalar(@$outrules)) {
405 foreach my $rule (@$outrules) {
406 #we use RETURN because we need to check also tap rules
407 $rule->{action
} = 'RETURN' if $rule->{action
} eq 'ACCEPT';
408 iptables_generate_rule
('host-OUT', $rule);
412 iptables_addrule
("-A host-OUT -j LOG --log-prefix \"kvmhost-OUT dropped: \" --log-level 4");
413 iptables_addrule
("-A host-OUT -j DROP");
416 my $rule = "proxmoxfw-INPUT -j host-IN";
417 if(!iptables_rule_exist
($rule)){
418 iptables_addrule
("-I $rule");
421 $rule = "proxmoxfw-OUTPUT -j host-OUT";
422 if(!iptables_rule_exist
($rule)){
423 iptables_addrule
("-I $rule");
433 my $chain = "host-IN";
435 my $rule = "proxmoxfw-INPUT -j $chain";
436 if(iptables_rule_exist
($rule)){
437 iptables_addrule
("-D $rule");
440 if(iptables_chain_exist
($chain)){
441 iptables_addrule
("-F $chain");
442 iptables_addrule
("-X $chain");
447 $rule = "proxmoxfw-OUTPUT -j $chain";
448 if(iptables_rule_exist
($rule)){
449 iptables_addrule
("-D $rule");
452 if(iptables_chain_exist
($chain)){
453 iptables_addrule
("-F $chain");
454 iptables_addrule
("-X $chain");
460 sub generate_proxmoxfwinput
{
462 if(!iptables_chain_exist
("proxmoxfw-INPUT")){
463 iptables_addrule
(":proxmoxfw-INPUT - [0:0]");
464 iptables_addrule
("-I INPUT -j proxmoxfw-INPUT");
465 iptables_addrule
("-A INPUT -j ACCEPT");
469 sub generate_proxmoxfwoutput
{
471 if(!iptables_chain_exist
("proxmoxfw-OUTPUT")){
472 iptables_addrule
(":proxmoxfw-OUTPUT - [0:0]");
473 iptables_addrule
("-I OUTPUT -j proxmoxfw-OUTPUT");
474 iptables_addrule
("-A OUTPUT -j ACCEPT");
479 sub enable_group_rules
{
482 generate_group_rules
($group);
486 sub generate_group_rules
{
489 my $filename = "/etc/pve/firewall/groups.fw";
490 my $fh = IO
::File-
>new($filename, O_RDONLY
);
493 my $rules = parse_fw_rules
($filename, $fh, $group);
494 my $inrules = $rules->{in};
495 my $outrules = $rules->{out
};
497 my $chain = "GROUP-".$group."-IN";
499 iptables_addrule
(":$chain - [0:0]");
501 if (scalar(@$inrules)) {
502 foreach my $rule (@$inrules) {
503 iptables_generate_rule
($chain, $rule);
507 $chain = "GROUP-".$group."-OUT";
509 iptables_addrule
(":$chain - [0:0]");
511 if(!iptables_chain_exist
("BRIDGEFW-OUT")){
512 iptables_addrule
(":BRIDGEFW-OUT - [0:0]");
515 if(!iptables_chain_exist
("BRIDGEFW-IN")){
516 iptables_addrule
(":BRIDGEFW-IN - [0:0]");
519 if (scalar(@$outrules)) {
520 foreach my $rule (@$outrules) {
521 #we go the BRIDGEFW-IN because we need to check also other tap rules
522 #(and group rules can be set on any bridge, so we can't go to VMBRXX-IN)
523 $rule->{action
} = 'BRIDGEFW-IN' if $rule->{action
} eq 'ACCEPT';
524 iptables_generate_rule
($chain, $rule);
530 sub disable_group_rules
{
533 my $chain = "GROUP-".$group."-IN";
535 if(iptables_chain_exist
($chain)){
536 iptables_addrule
("-F $chain");
537 iptables_addrule
("-X $chain");
540 $chain = "GROUP-".$group."-OUT";
542 if(iptables_chain_exist
($chain)){
543 iptables_addrule
("-F $chain");
544 iptables_addrule
("-X $chain");
547 #iptables_restore will die if security group is linked in a tap chain
548 #maybe can we improve that, parsing each vm config, or parsing iptables -S
549 #to see if the security group is linked or not
554 my ($filename, $fh, $group) = @_;
558 my $securitygroupexist;
560 my $res = { in => [], out
=> [] };
562 my $macros = get_firewall_macros
();
563 my $protocols = get_etc_protocols
();
565 while (defined(my $line = <$fh>)) {
566 next if $line =~ m/^#/;
567 next if $line =~ m/^\s*$/;
569 if ($line =~ m/^\[(in|out)(:(\S+))?\]\s*$/i) {
571 $securitygroup = lc($3) if $3;
572 $securitygroupexist = 1 if $securitygroup && $securitygroup eq $group;
576 next if $group && $securitygroup ne $group;
578 my ($action, $iface, $source, $dest, $proto, $dport, $sport) =
582 warn "skip incomplete line\n";
587 if ($action =~ m/^(ACCEPT|DROP|REJECT|GROUP-(\S+))$/) {
589 } elsif ($action =~ m/^(\S+)\((ACCEPT|DROP|REJECT)\)$/) {
590 ($service, $action) = ($1, $2);
591 if (!$macros->{$service}) {
592 warn "unknown service '$service'\n";
596 warn "unknown action '$action'\n";
600 $iface = undef if $iface && $iface eq '-';
601 if ($iface && $iface !~ m/^(net0|net1|net2|net3|net4|net5)$/) {
602 warn "unknown interface '$iface'\n";
606 $proto = undef if $proto && $proto eq '-';
607 if ($proto && !(defined($protocols->{byname
}->{$proto}) ||
608 defined($protocols->{byid
}->{$proto}))) {
609 warn "unknown protokol '$proto'\n";
613 $source = undef if $source && $source eq '-';
614 $dest = undef if $dest && $dest eq '-';
616 $dport = undef if $dport && $dport eq '-';
617 $sport = undef if $sport && $sport eq '-';
620 my $nbsource = undef;
624 $nbsource = parse_address_list
($source) if $source;
625 $nbdest = parse_address_list
($dest) if $dest;
626 $nbdport = parse_port_name_number_or_range
($dport) if $dport;
627 $nbsport = parse_port_name_number_or_range
($sport) if $sport;
642 nbsource
=> $nbsource,
652 push @{$res->{$section}}, $rule;
655 die "security group $group don't exist" if $group && !$securitygroupexist;
660 my ($code, @param) = @_;
664 my $res = lock_file
($pve_fw_lock_filename, $timeout, $code, @param);
671 sub read_local_vm_config
{
677 my $list = PVE
::QemuServer
::config_list
();
679 foreach my $vmid (keys %$list) {
680 #next if !($vmid eq '100' || $vmid eq '102');
681 my $cfspath = PVE
::QemuServer
::cfs_config_path
($vmid);
682 if (my $conf = PVE
::Cluster
::cfs_read_file
($cfspath)) {
683 $qemu->{$vmid} = $conf;
687 my $vmdata = { openvz
=> $openvz, qemu
=> $qemu };
692 sub read_vm_firewall_rules
{
695 foreach my $vmid (keys %{$vmdata->{qemu
}}, keys %{$vmdata->{openvz
}}) {
696 my $filename = "/etc/pve/firewall/$vmid.fw";
697 my $fh = IO
::File-
>new($filename, O_RDONLY
);
700 $rules->{$vmid} = parse_fw_rules
($filename, $fh);
707 my $vmdata = read_local_vm_config
();
708 my $rules = read_vm_firewall_rules
($vmdata);
710 # print Dumper($vmdata);
715 sub compile_and_start
{