8 use Time
::HiRes qw
(gettimeofday
);
9 use PVE
::Tools
qw(dir_glob_foreach file_read_firstline);
12 use PVE
::Cluster
qw(cfs_read_file);
13 use PVE
::RPCEnvironment
;
16 use PVE
::FirewallSimulator
;
19 use base
qw(PVE::Daemon);
21 $SIG{'__WARN__'} = sub {
26 syslog
('warning', "%s", $t);
30 my $cmdline = [$0, @ARGV];
32 my %daemon_options = (restart_on_error
=> 5, stop_wait_time
=> 5);
36 $daemon = __PACKAGE__-
>new('pve-firewall', $cmdline, %daemon_options);
39 syslog
("err", "daemon init failed - $err");
43 my $rpcenv = PVE
::RPCEnvironment-
>init('cli');
45 $rpcenv->init_request();
46 $rpcenv->set_language($ENV{LANG
});
47 $rpcenv->set_user('root@pam');
49 my $nodename = PVE
::INotify
::nodename
();
53 PVE
::Cluster
::cfs_update
();
55 PVE
::Firewall
::init
();
58 my $restart_request = 0;
64 my $initial_memory_usage;
69 syslog
('info' , "server closing");
72 1 while (waitpid(-1, POSIX
::WNOHANG
()) > 0);
74 syslog
('info' , "clear firewall rules");
76 eval { PVE
::Firewall
::remove_pvefw_chains
(); };
79 $self->exit_daemon(0);
91 local $SIG{'__WARN__'} = 'IGNORE'; # do not fill up logs
95 $next_update = time() + $updatetime;
97 my ($ccsec, $cusec) = gettimeofday
();
99 PVE
::Cluster
::cfs_update
();
100 PVE
::Firewall
::update
();
105 syslog
('err', "status update error: $err");
108 my ($ccsec_end, $cusec_end) = gettimeofday
();
109 my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000;
111 syslog
('info', sprintf("firewall update time (%.3f seconds)", $cptime))
116 my $mem = PVE
::ProcFSTools
::read_memory_usage
();
118 if (!defined($initial_memory_usage) || ($cycle < 10)) {
119 $initial_memory_usage = $mem->{resident
};
121 my $diff = $mem->{resident
} - $initial_memory_usage;
122 if ($diff > 5*1024*1024) {
123 syslog
('info', "restarting server after $cycle cycles to " .
124 "reduce memory usage (free $mem->{resident} ($diff) bytes)");
125 $self->restart_daemon();
130 while ((time() < $next_update) &&
131 ($wcount < $updatetime) && # protect against time wrap
132 !$restart_request) { $wcount++; sleep (1); };
134 $self->restart_daemon() if $restart_request;
138 $daemon->register_start_command(__PACKAGE__
,
139 "Start the Proxmox VE firewall service.");
140 $daemon->register_restart_command(__PACKAGE__
, 1,
141 "Restart the Proxmox VE firewall service.");
142 $daemon->register_stop_command(__PACKAGE__
,
143 "Stop firewall. This removes all Proxmox VE " .
144 "related iptable rules. " .
145 "The host is unprotected afterwards.");
147 __PACKAGE__-
>register_method ({
151 description
=> "Get firewall status.",
153 additionalProperties
=> 0,
158 additionalProperties
=> 0,
162 enum
=> ['unknown', 'stopped', 'running'],
165 description
=> "Firewall is enabled (in 'cluster.fw')",
169 description
=> "Set when there are pending changes.",
178 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
182 my $status = $daemon->running() ?
'running' : 'stopped';
184 my $res = { status
=> $status };
186 my $verbose = 1; # show syntax errors
187 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
(undef, $verbose);
188 $res->{enable
} = $cluster_conf->{options
}->{enable
} ?
1 : 0;
190 if ($status eq 'running') {
192 my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE
::Firewall
::compile
($cluster_conf, undef, undef, $verbose);
194 $verbose = 0; # do not show iptables details
195 my (undef, undef, $ipset_changes) = PVE
::Firewall
::get_ipset_cmdlist
($ipset_ruleset, $verbose);
196 my ($test, $ruleset_changes) = PVE
::Firewall
::get_ruleset_cmdlist
($ruleset, $verbose);
197 my (undef, $ruleset_changesv6) = PVE
::Firewall
::get_ruleset_cmdlist
($rulesetv6, $verbose, "ip6tables");
199 $res->{changes
} = ($ipset_changes || $ruleset_changes || $ruleset_changesv6) ?
1 : 0;
205 return PVE
::Firewall
::run_locked
($code);
208 __PACKAGE__-
>register_method ({
212 description
=> "Compile and print firewall rules. This is useful for testing.",
214 additionalProperties
=> 0,
217 returns
=> { type
=> 'null' },
222 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
228 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
(undef, $verbose);
229 my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE
::Firewall
::compile
($cluster_conf, undef, undef, $verbose);
231 print "ipset cmdlist:\n";
232 my (undef, undef, $ipset_changes) = PVE
::Firewall
::get_ipset_cmdlist
($ipset_ruleset, $verbose);
234 print "\niptables cmdlist:\n";
235 my (undef, $ruleset_changes) = PVE
::Firewall
::get_ruleset_cmdlist
($ruleset, $verbose);
237 print "\nip6tables cmdlist:\n";
238 my (undef, $ruleset_changesv6) = PVE
::Firewall
::get_ruleset_cmdlist
($rulesetv6, $verbose, "ip6tables");
240 if ($ipset_changes || $ruleset_changes || $ruleset_changesv6) {
241 print "detected changes\n";
243 print "no changes\n";
245 if (!$cluster_conf->{options
}->{enable
}) {
246 print "firewall disabled\n";
251 PVE
::Firewall
::run_locked
($code);
256 __PACKAGE__-
>register_method ({
260 description
=> "Print information about local network.",
262 additionalProperties
=> 0,
265 returns
=> { type
=> 'null' },
269 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
271 my $nodename = PVE
::INotify
::nodename
();
272 print "local hostname: $nodename\n";
274 my $ip = PVE
::Cluster
::remote_node_ip
($nodename);
275 print "local IP address: $ip\n";
277 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
279 my $localnet = PVE
::Firewall
::local_network
() || '127.0.0.0/8';
280 print "network auto detect: $localnet\n";
281 if ($cluster_conf->{aliases
}->{local_network
}) {
282 print "using user defined local_network: $cluster_conf->{aliases}->{local_network}->{cidr}\n";
284 print "using detected local_network: $localnet\n";
290 __PACKAGE__-
>register_method ({
294 description
=> "Simulate firewall rules. This does not simulate kernel 'routing' table. Instead, this simply assumes that routing from source zone to destination zone is possible.",
296 additionalProperties
=> 0,
299 description
=> "Verbose output.",
305 description
=> "Source zone.",
307 pattern
=> '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
309 default => 'outside',
312 description
=> "Destination zone.",
314 pattern
=> '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
319 description
=> "Protocol.",
321 pattern
=> '(tcp|udp)',
326 description
=> "Destination port.",
333 description
=> "Source port.",
340 description
=> "Source IP address.",
341 type
=> 'string', format
=> 'ipv4',
345 description
=> "Destination IP address.",
346 type
=> 'string', format
=> 'ipv4',
351 returns
=> { type
=> 'null' },
355 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
357 my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE
::Firewall
::compile
(undef, undef, undef, $param->{verbose
});
359 PVE
::FirewallSimulator
::debug
($param->{verbose
} || 0);
361 my $host_ip = PVE
::Cluster
::remote_node_ip
($nodename);
363 PVE
::FirewallSimulator
::reset_trace
();
364 print Dumper
($ruleset) if $param->{verbose
};
367 from
=> $param->{from
},
369 proto
=> $param->{protocol
} || 'tcp',
370 source
=> $param->{source
},
371 dest
=> $param->{dest
},
372 dport
=> $param->{dport
},
373 sport
=> $param->{sport
},
376 if (!defined($test->{to
})) {
377 $test->{to
} = 'host';
378 PVE
::FirewallSimulator
::add_trace
("Set Zone: to => '$test->{to}'\n");
380 if (!defined($test->{from
})) {
381 $test->{from
} = 'outside',
382 PVE
::FirewallSimulator
::add_trace
("Set Zone: from => '$test->{from}'\n");
385 my $vmdata = PVE
::Firewall
::read_local_vm_config
();
387 print "Test packet:\n";
389 foreach my $k (qw(from to proto source dest dport sport)) {
390 printf(" %-8s: %s\n", $k, $test->{$k}) if defined($test->{$k});
393 $test->{action
} = 'QUERY';
395 my $res = PVE
::FirewallSimulator
::simulate_firewall
($ruleset, $ipset_ruleset,
396 $host_ip, $vmdata, $test);
398 print "ACTION: $res\n";
404 start
=> [ __PACKAGE__
, 'start', []],
405 restart
=> [ __PACKAGE__
, 'restart', []],
406 stop
=> [ __PACKAGE__
, 'stop', []],
407 compile
=> [ __PACKAGE__
, 'compile', []],
408 simulate
=> [ __PACKAGE__
, 'simulate', []],
409 localnet
=> [ __PACKAGE__
, 'localnet', []],
410 status
=> [ __PACKAGE__
, 'status', [], undef, sub {
412 my $status = ($res->{enable
} ?
"enabled" : "disabled") . '/' . $res->{status
};
414 if ($res->{changes
}) {
415 print "Status: $status (pending changes)\n";
417 print "Status: $status\n";
424 PVE
::CLIHandler
::handle_cmd
($cmddef, $0, $cmd, \
@ARGV, undef, $0);
432 pve-firewall - PVE Firewall Daemon
440 This service updates iptables rules periodically.
442 =include pve_copyright