1 package PVE
::Service
::pve_firewall
;
7 use Time
::HiRes qw
(gettimeofday usleep
);
10 use PVE
::Cluster
qw(cfs_read_file);
15 use PVE
::RPCEnvironment
;
17 use PVE
::Tools
qw(dir_glob_foreach file_read_firstline);
20 use PVE
::FirewallSimulator
;
21 use PVE
::FirewallSimulator
qw($bridge_interface_pattern);
23 use base
qw(PVE::Daemon);
25 my $cmdline = [$0, @ARGV];
27 my %daemon_options = (restart_on_error
=> 5, stop_wait_time
=> 5);
29 my $daemon = __PACKAGE__-
>new('pve-firewall', $cmdline, %daemon_options);
31 my $nodename = PVE
::INotify
::nodename
();
34 PVE
::Cluster
::cfs_update
();
36 PVE
::Firewall
::init
();
39 my ($next_update, $cycle, $restart_request) = (0, 0, 0);
42 my $initial_memory_usage;
47 syslog
('info' , "server shutting down");
50 1 while (waitpid(-1, POSIX
::WNOHANG
()) > 0);
52 syslog
('info' , "clear PVE-generated firewall rules");
54 eval { PVE
::Firewall
::remove_pvefw_chains
(); };
57 $self->exit_daemon(0);
69 local $SIG{'__WARN__'} = 'IGNORE'; # do not fill up logs
72 $next_update = time() + $updatetime;
74 my ($ccsec, $cusec) = gettimeofday
();
76 PVE
::Cluster
::cfs_update
();
77 PVE
::Firewall
::update
();
80 syslog
('err', "status update error: $err");
83 my ($ccsec_end, $cusec_end) = gettimeofday
();
84 my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000;
86 syslog
('info', sprintf("firewall update time (%.3f seconds)", $cptime))
91 my $mem = PVE
::ProcFSTools
::read_memory_usage
();
93 if (!defined($initial_memory_usage) || ($cycle < 10)) {
94 $initial_memory_usage = $mem->{resident
};
96 my $diff = $mem->{resident
} - $initial_memory_usage;
97 if ($diff > 5 * 1024 * 1024) {
98 syslog
('info', "restarting server after $cycle cycles to " .
99 "reduce memory usage (free $mem->{resident} ($diff) bytes)");
100 $self->restart_daemon();
105 while ((time() < $next_update) &&
106 ($wcount < $updatetime) && # protect against time wrap
107 !$restart_request) { $wcount++; sleep (1); };
109 $self->restart_daemon() if $restart_request;
113 $daemon->register_start_command("Start the Proxmox VE firewall service.");
114 $daemon->register_restart_command(1, "Restart the Proxmox VE firewall service.");
115 $daemon->register_stop_command(
116 "Stop the Proxmox VE firewall service. Note, stopping actively removes all Proxmox VE related"
117 ." iptable rules rendering the host potentially unprotected."
120 __PACKAGE__-
>register_method ({
124 description
=> "Get firewall status.",
126 additionalProperties
=> 0,
131 additionalProperties
=> 0,
135 enum
=> ['unknown', 'stopped', 'running'],
138 description
=> "Firewall is enabled (in 'cluster.fw')",
142 description
=> "Set when there are pending changes.",
151 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
155 my $status = $daemon->running() ?
'running' : 'stopped';
157 my $res = { status
=> $status };
159 PVE
::Firewall
::set_verbose
(1); # show syntax errors
161 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
(undef);
162 $res->{enable
} = $cluster_conf->{options
}->{enable
} ?
1 : 0;
164 if ($status eq 'running') {
166 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE
::Firewall
::compile
($cluster_conf, undef, undef);
168 PVE
::Firewall
::set_verbose
(0); # do not show iptables details
169 my (undef, undef, $ipset_changes) = PVE
::Firewall
::get_ipset_cmdlist
($ipset_ruleset);
170 my ($test, $ruleset_changes) = PVE
::Firewall
::get_ruleset_cmdlist
($ruleset->{filter
});
171 my (undef, $ruleset_changesv6) = PVE
::Firewall
::get_ruleset_cmdlist
($rulesetv6->{filter
}, "ip6tables");
172 my (undef, $ruleset_changes_raw) = PVE
::Firewall
::get_ruleset_cmdlist
($ruleset->{raw
}, undef, 'raw');
173 my (undef, $ruleset_changesv6_raw) = PVE
::Firewall
::get_ruleset_cmdlist
($rulesetv6->{raw
}, "ip6tables", 'raw');
174 my (undef, $ebtables_changes) = PVE
::Firewall
::get_ebtables_cmdlist
($ebtables_ruleset);
176 $res->{changes
} = ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes || $ruleset_changes_raw || $ruleset_changesv6_raw) ?
1 : 0;
182 return PVE
::Firewall
::run_locked
($code);
185 __PACKAGE__-
>register_method ({
189 description
=> "Compile and print firewall rules. This is useful for testing.",
191 additionalProperties
=> 0,
194 returns
=> { type
=> 'null' },
199 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
203 PVE
::Firewall
::set_verbose
(1);
205 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
(undef);
206 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE
::Firewall
::compile
($cluster_conf, undef, undef);
208 print "ipset cmdlist:\n";
209 my (undef, undef, $ipset_changes) = PVE
::Firewall
::get_ipset_cmdlist
($ipset_ruleset);
211 print "\niptables cmdlist:\n";
212 my (undef, $ruleset_changes) = PVE
::Firewall
::get_ruleset_cmdlist
($ruleset->{filter
});
214 print "\nip6tables cmdlist:\n";
215 my (undef, $ruleset_changesv6) = PVE
::Firewall
::get_ruleset_cmdlist
($rulesetv6->{filter
}, "ip6tables");
217 print "\nebtables cmdlist:\n";
218 my (undef, $ebtables_changes) = PVE
::Firewall
::get_ebtables_cmdlist
($ebtables_ruleset);
220 print "\niptables table raw cmdlist:\n";
221 my (undef, $ruleset_changes_raw) = PVE
::Firewall
::get_ruleset_cmdlist
($ruleset->{raw
}, undef, 'raw');
223 print "\nip6tables table raw cmdlist:\n";
224 my (undef, $ruleset_changesv6_raw) = PVE
::Firewall
::get_ruleset_cmdlist
($rulesetv6->{raw
}, "ip6tables", 'raw');
227 if ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes || $ruleset_changes_raw || $ruleset_changesv6_raw) {
228 print "detected changes\n";
230 print "no changes\n";
232 if (!$cluster_conf->{options
}->{enable
}) {
233 print "firewall disabled\n";
237 PVE
::Firewall
::run_locked
($code);
242 __PACKAGE__-
>register_method ({
246 description
=> "Print information about local network.",
248 additionalProperties
=> 0,
251 returns
=> { type
=> 'null' },
255 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
257 my $nodename = PVE
::INotify
::nodename
();
258 print "local hostname: $nodename\n";
260 my $ip = PVE
::Cluster
::remote_node_ip
($nodename);
261 print "local IP address: $ip\n";
263 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
265 my $localnet = PVE
::Firewall
::local_network
() || '127.0.0.0/8';
266 print "network auto detect: $localnet\n";
267 if (my $local_network = $cluster_conf->{aliases
}->{local_network
}) {
268 print "using user defined local_network: $local_network->{cidr}\n";
270 print "using detected local_network: $localnet\n";
273 if (PVE
::Corosync
::check_conf_exists
(1)) {
274 my $corosync_conf = PVE
::Cluster
::cfs_read_file
("corosync.conf");
275 my $corosync_node_found = 0;
277 print "\naccepting corosync traffic from/to:\n";
279 PVE
::Corosync
::for_all_corosync_addresses
($corosync_conf, undef, sub {
280 my ($curr_node_name, $curr_node_ip, undef, $key) = @_;
282 return if $curr_node_name eq $nodename;
284 $corosync_node_found = 1;
286 $key =~ m/(?:ring|link)(\d+)_addr/;
287 print " - $curr_node_name: $curr_node_ip (link: $1)\n";
290 if (!$corosync_node_found) {
291 print " - no nodes found\n";
298 __PACKAGE__-
>register_method ({
302 description
=> "Simulate firewall rules. This does not simulates the kernel 'routing' table,"
303 ." but simply assumes that routing from source zone to destination zone is possible.",
305 additionalProperties
=> 0,
308 description
=> "Verbose output.",
314 description
=> "Source zone.",
316 pattern
=> "(host|outside|vm\\d+|ct\\d+|$bridge_interface_pattern)",
318 default => 'outside',
321 description
=> "Destination zone.",
323 pattern
=> "(host|outside|vm\\d+|ct\\d+|$bridge_interface_pattern)",
328 description
=> "Protocol.",
330 pattern
=> '(tcp|udp)',
335 description
=> "Destination port.",
342 description
=> "Source port.",
349 description
=> "Source IP address.",
350 type
=> 'string', format
=> 'ipv4',
354 description
=> "Destination IP address.",
355 type
=> 'string', format
=> 'ipv4',
360 returns
=> { type
=> 'null' },
364 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
366 PVE
::Firewall
::set_verbose
($param->{verbose
});
368 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE
::Firewall
::compile
();
370 PVE
::FirewallSimulator
::debug
();
372 my $host_ip = PVE
::Cluster
::remote_node_ip
($nodename);
374 PVE
::FirewallSimulator
::reset_trace
();
375 print Dumper
($ruleset->{filter
}) if $param->{verbose
};
376 print Dumper
($ruleset->{raw
}) if $param->{verbose
};
379 from
=> $param->{from
},
381 proto
=> $param->{protocol
} || 'tcp',
382 source
=> $param->{source
},
383 dest
=> $param->{dest
},
384 dport
=> $param->{dport
},
385 sport
=> $param->{sport
},
388 if (!defined($test->{to
})) {
389 $test->{to
} = 'host';
390 PVE
::FirewallSimulator
::add_trace
("Set Zone: to => '$test->{to}'\n");
392 if (!defined($test->{from
})) {
393 $test->{from
} = 'outside',
394 PVE
::FirewallSimulator
::add_trace
("Set Zone: from => '$test->{from}'\n");
397 my $vmdata = PVE
::Firewall
::read_local_vm_config
();
399 print "Test packet:\n";
401 foreach my $k (qw(from to proto source dest dport sport)) {
402 printf(" %-8s: %s\n", $k, $test->{$k}) if defined($test->{$k});
405 $test->{action
} = 'QUERY';
407 my $res = PVE
::FirewallSimulator
::simulate_firewall
(
408 $ruleset->{filter
}, $ipset_ruleset, $host_ip, $vmdata, $test);
410 print "ACTION: $res\n";
416 start
=> [ __PACKAGE__
, 'start', []],
417 restart
=> [ __PACKAGE__
, 'restart', []],
418 stop
=> [ __PACKAGE__
, 'stop', []],
419 compile
=> [ __PACKAGE__
, 'compile', []],
420 simulate
=> [ __PACKAGE__
, 'simulate', []],
421 localnet
=> [ __PACKAGE__
, 'localnet', []],
422 status
=> [ __PACKAGE__
, 'status', [], undef, sub {
424 my $status = ($res->{enable
} ?
"enabled" : "disabled") . '/' . $res->{status
};
426 if ($res->{changes
}) {
427 print "Status: $status (pending changes)\n";
429 print "Status: $status\n";