e70f6e590149bf9261729c4463939dab9d755116
1 package PVE
::Service
::pve_firewall
;
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 my $cmdline = [$0, @ARGV];
23 my %daemon_options = (restart_on_error
=> 5, stop_wait_time
=> 5);
25 my $daemon = __PACKAGE__-
>new('pve-firewall', $cmdline, %daemon_options);
27 my $nodename = PVE
::INotify
::nodename
();
31 PVE
::Cluster
::cfs_update
();
33 PVE
::Firewall
::init
();
36 my $restart_request = 0;
42 my $initial_memory_usage;
47 syslog
('info' , "server closing");
50 1 while (waitpid(-1, POSIX
::WNOHANG
()) > 0);
52 syslog
('info' , "clear firewall rules");
54 eval { PVE
::Firewall
::remove_pvefw_chains
(); };
57 $self->exit_daemon(0);
69 local $SIG{'__WARN__'} = 'IGNORE'; # do not fill up logs
73 $next_update = time() + $updatetime;
75 my ($ccsec, $cusec) = gettimeofday
();
77 PVE
::Cluster
::cfs_update
();
78 PVE
::Firewall
::update
();
83 syslog
('err', "status update error: $err");
86 my ($ccsec_end, $cusec_end) = gettimeofday
();
87 my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000;
89 syslog
('info', sprintf("firewall update time (%.3f seconds)", $cptime))
94 my $mem = PVE
::ProcFSTools
::read_memory_usage
();
96 if (!defined($initial_memory_usage) || ($cycle < 10)) {
97 $initial_memory_usage = $mem->{resident
};
99 my $diff = $mem->{resident
} - $initial_memory_usage;
100 if ($diff > 5*1024*1024) {
101 syslog
('info', "restarting server after $cycle cycles to " .
102 "reduce memory usage (free $mem->{resident} ($diff) bytes)");
103 $self->restart_daemon();
108 while ((time() < $next_update) &&
109 ($wcount < $updatetime) && # protect against time wrap
110 !$restart_request) { $wcount++; sleep (1); };
112 $self->restart_daemon() if $restart_request;
116 $daemon->register_start_command("Start the Proxmox VE firewall service.");
117 $daemon->register_restart_command(1, "Restart the Proxmox VE firewall service.");
118 $daemon->register_stop_command("Stop firewall. This removes all Proxmox VE " .
119 "related iptable rules. " .
120 "The host is unprotected afterwards.");
122 __PACKAGE__-
>register_method ({
126 description
=> "Get firewall status.",
128 additionalProperties
=> 0,
133 additionalProperties
=> 0,
137 enum
=> ['unknown', 'stopped', 'running'],
140 description
=> "Firewall is enabled (in 'cluster.fw')",
144 description
=> "Set when there are pending changes.",
153 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
157 my $status = $daemon->running() ?
'running' : 'stopped';
159 my $res = { status
=> $status };
161 my $verbose = 1; # show syntax errors
162 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
(undef, $verbose);
163 $res->{enable
} = $cluster_conf->{options
}->{enable
} ?
1 : 0;
165 if ($status eq 'running') {
167 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE
::Firewall
::compile
($cluster_conf, undef, undef, $verbose);
169 $verbose = 0; # do not show iptables details
170 my (undef, undef, $ipset_changes) = PVE
::Firewall
::get_ipset_cmdlist
($ipset_ruleset, $verbose);
171 my ($test, $ruleset_changes) = PVE
::Firewall
::get_ruleset_cmdlist
($ruleset, $verbose);
172 my (undef, $ruleset_changesv6) = PVE
::Firewall
::get_ruleset_cmdlist
($rulesetv6, $verbose, "ip6tables");
173 my (undef, $ebtables_changes) = PVE
::Firewall
::get_ebtables_cmdlist
($ebtables_ruleset, $verbose);
175 $res->{changes
} = ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes) ?
1 : 0;
181 return PVE
::Firewall
::run_locked
($code);
184 __PACKAGE__-
>register_method ({
188 description
=> "Compile and print firewall rules. This is useful for testing.",
190 additionalProperties
=> 0,
193 returns
=> { type
=> 'null' },
198 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
204 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
(undef, $verbose);
205 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE
::Firewall
::compile
($cluster_conf, undef, undef, $verbose);
207 print "ipset cmdlist:\n";
208 my (undef, undef, $ipset_changes) = PVE
::Firewall
::get_ipset_cmdlist
($ipset_ruleset, $verbose);
210 print "\niptables cmdlist:\n";
211 my (undef, $ruleset_changes) = PVE
::Firewall
::get_ruleset_cmdlist
($ruleset, $verbose);
213 print "\nip6tables cmdlist:\n";
214 my (undef, $ruleset_changesv6) = PVE
::Firewall
::get_ruleset_cmdlist
($rulesetv6, $verbose, "ip6tables");
216 print "\nebtables cmdlist:\n";
217 my (undef, $ebtables_changes) = PVE
::Firewall
::get_ebtables_cmdlist
($ebtables_ruleset, $verbose);
219 if ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes) {
220 print "detected changes\n";
222 print "no changes\n";
224 if (!$cluster_conf->{options
}->{enable
}) {
225 print "firewall disabled\n";
230 PVE
::Firewall
::run_locked
($code);
235 __PACKAGE__-
>register_method ({
239 description
=> "Print information about local network.",
241 additionalProperties
=> 0,
244 returns
=> { type
=> 'null' },
248 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
250 my $nodename = PVE
::INotify
::nodename
();
251 print "local hostname: $nodename\n";
253 my $ip = PVE
::Cluster
::remote_node_ip
($nodename);
254 print "local IP address: $ip\n";
256 my $cluster_conf = PVE
::Firewall
::load_clusterfw_conf
();
258 my $localnet = PVE
::Firewall
::local_network
() || '127.0.0.0/8';
259 print "network auto detect: $localnet\n";
260 if ($cluster_conf->{aliases
}->{local_network
}) {
261 print "using user defined local_network: $cluster_conf->{aliases}->{local_network}->{cidr}\n";
263 print "using detected local_network: $localnet\n";
269 __PACKAGE__-
>register_method ({
273 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.",
275 additionalProperties
=> 0,
278 description
=> "Verbose output.",
284 description
=> "Source zone.",
286 pattern
=> '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
288 default => 'outside',
291 description
=> "Destination zone.",
293 pattern
=> '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
298 description
=> "Protocol.",
300 pattern
=> '(tcp|udp)',
305 description
=> "Destination port.",
312 description
=> "Source port.",
319 description
=> "Source IP address.",
320 type
=> 'string', format
=> 'ipv4',
324 description
=> "Destination IP address.",
325 type
=> 'string', format
=> 'ipv4',
330 returns
=> { type
=> 'null' },
334 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
336 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE
::Firewall
::compile
(undef, undef, undef, $param->{verbose
});
338 PVE
::FirewallSimulator
::debug
($param->{verbose
} || 0);
340 my $host_ip = PVE
::Cluster
::remote_node_ip
($nodename);
342 PVE
::FirewallSimulator
::reset_trace
();
343 print Dumper
($ruleset) if $param->{verbose
};
346 from
=> $param->{from
},
348 proto
=> $param->{protocol
} || 'tcp',
349 source
=> $param->{source
},
350 dest
=> $param->{dest
},
351 dport
=> $param->{dport
},
352 sport
=> $param->{sport
},
355 if (!defined($test->{to
})) {
356 $test->{to
} = 'host';
357 PVE
::FirewallSimulator
::add_trace
("Set Zone: to => '$test->{to}'\n");
359 if (!defined($test->{from
})) {
360 $test->{from
} = 'outside',
361 PVE
::FirewallSimulator
::add_trace
("Set Zone: from => '$test->{from}'\n");
364 my $vmdata = PVE
::Firewall
::read_local_vm_config
();
366 print "Test packet:\n";
368 foreach my $k (qw(from to proto source dest dport sport)) {
369 printf(" %-8s: %s\n", $k, $test->{$k}) if defined($test->{$k});
372 $test->{action
} = 'QUERY';
374 my $res = PVE
::FirewallSimulator
::simulate_firewall
($ruleset, $ipset_ruleset,
375 $host_ip, $vmdata, $test);
377 print "ACTION: $res\n";
383 start
=> [ __PACKAGE__
, 'start', []],
384 restart
=> [ __PACKAGE__
, 'restart', []],
385 stop
=> [ __PACKAGE__
, 'stop', []],
386 compile
=> [ __PACKAGE__
, 'compile', []],
387 simulate
=> [ __PACKAGE__
, 'simulate', []],
388 localnet
=> [ __PACKAGE__
, 'localnet', []],
389 status
=> [ __PACKAGE__
, 'status', [], undef, sub {
391 my $status = ($res->{enable
} ?
"enabled" : "disabled") . '/' . $res->{status
};
393 if ($res->{changes
}) {
394 print "Status: $status (pending changes)\n";
396 print "Status: $status\n";