]> git.proxmox.com Git - pve-firewall.git/blame - src/PVE/Service/pve_firewall.pm
daemon: cleanup '+' character at begin of line
[pve-firewall.git] / src / PVE / Service / pve_firewall.pm
CommitLineData
0c32b7fb
DM
1package PVE::Service::pve_firewall;
2
3use strict;
4use warnings;
5use PVE::SafeSyslog;
6use PVE::Daemon;
7
8use Time::HiRes qw (gettimeofday);
9use PVE::Tools qw(dir_glob_foreach file_read_firstline);
10use PVE::ProcFSTools;
11use PVE::INotify;
12use PVE::Cluster qw(cfs_read_file);
13use PVE::RPCEnvironment;
14use PVE::CLIHandler;
15use PVE::Firewall;
16use PVE::FirewallSimulator;
17use Data::Dumper;
18
19use base qw(PVE::Daemon);
20
21my $cmdline = [$0, @ARGV];
22
23my %daemon_options = (restart_on_error => 5, stop_wait_time => 5);
24
25my $daemon = __PACKAGE__->new('pve-firewall', $cmdline, %daemon_options);
26
27my $nodename = PVE::INotify::nodename();
28
29sub init {
30
31 PVE::Cluster::cfs_update();
32
33 PVE::Firewall::init();
34}
35
36my $restart_request = 0;
37my $next_update = 0;
38
39my $cycle = 0;
40my $updatetime = 10;
41
42my $initial_memory_usage;
43
44sub shutdown {
45 my ($self) = @_;
46
47 syslog('info' , "server closing");
48
49 # wait for children
50 1 while (waitpid(-1, POSIX::WNOHANG()) > 0);
51
52 syslog('info' , "clear firewall rules");
53
54 eval { PVE::Firewall::remove_pvefw_chains(); };
55 warn $@ if $@;
56
57 $self->exit_daemon(0);
58}
59
60sub hup {
61 my ($self) = @_;
62
63 $restart_request = 1;
64}
65
66sub run {
67 my ($self) = @_;
68
69 local $SIG{'__WARN__'} = 'IGNORE'; # do not fill up logs
70
71 for (;;) { # forever
72
73 $next_update = time() + $updatetime;
74
75 my ($ccsec, $cusec) = gettimeofday ();
76 eval {
77 PVE::Cluster::cfs_update();
78 PVE::Firewall::update();
79 };
80 my $err = $@;
81
82 if ($err) {
83 syslog('err', "status update error: $err");
84 }
85
86 my ($ccsec_end, $cusec_end) = gettimeofday ();
87 my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000;
88
89 syslog('info', sprintf("firewall update time (%.3f seconds)", $cptime))
90 if ($cptime > 5);
91
92 $cycle++;
93
94 my $mem = PVE::ProcFSTools::read_memory_usage();
95
96 if (!defined($initial_memory_usage) || ($cycle < 10)) {
97 $initial_memory_usage = $mem->{resident};
98 } else {
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();
104 }
105 }
106
107 my $wcount = 0;
108 while ((time() < $next_update) &&
109 ($wcount < $updatetime) && # protect against time wrap
110 !$restart_request) { $wcount++; sleep (1); };
111
112 $self->restart_daemon() if $restart_request;
113 }
114}
115
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.");
121
122__PACKAGE__->register_method ({
123 name => 'status',
124 path => 'status',
125 method => 'GET',
126 description => "Get firewall status.",
127 parameters => {
128 additionalProperties => 0,
129 properties => {},
130 },
131 returns => {
132 type => 'object',
133 additionalProperties => 0,
134 properties => {
135 status => {
136 type => 'string',
137 enum => ['unknown', 'stopped', 'running'],
138 },
139 enable => {
140 description => "Firewall is enabled (in 'cluster.fw')",
141 type => 'boolean',
142 },
143 changes => {
144 description => "Set when there are pending changes.",
145 type => 'boolean',
146 optional => 1,
147 }
148 },
149 },
150 code => sub {
151 my ($param) = @_;
152
153 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
154
155 my $code = sub {
156
157 my $status = $daemon->running() ? 'running' : 'stopped';
158
159 my $res = { status => $status };
160
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;
164
165 if ($status eq 'running') {
166
c5e8b008 167 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose);
0c32b7fb
DM
168
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");
b4c72cfa 173 my (undef, $ebtables_changes) = PVE::Firewall::get_ebtables_cmdlist($ebtables_ruleset, $verbose);
0c32b7fb 174
151c209e 175 $res->{changes} = ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes) ? 1 : 0;
0c32b7fb
DM
176 }
177
178 return $res;
179 };
180
181 return PVE::Firewall::run_locked($code);
182 }});
183
184__PACKAGE__->register_method ({
185 name => 'compile',
186 path => 'compile',
187 method => 'GET',
188 description => "Compile and print firewall rules. This is useful for testing.",
189 parameters => {
190 additionalProperties => 0,
191 properties => {},
192 },
193 returns => { type => 'null' },
194
195 code => sub {
196 my ($param) = @_;
197
198 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
199
200 my $code = sub {
201
202 my $verbose = 1;
203
204 my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef, $verbose);
c5e8b008 205 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose);
0c32b7fb
DM
206
207 print "ipset cmdlist:\n";
208 my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose);
209
210 print "\niptables cmdlist:\n";
211 my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset, $verbose);
212
213 print "\nip6tables cmdlist:\n";
214 my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6, $verbose, "ip6tables");
215
151c209e
AD
216 print "\nebtables cmdlist:\n";
217 my (undef, $ebtables_changes) = PVE::Firewall::get_ebtables_cmdlist($ebtables_ruleset, $verbose);
218
219 if ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes) {
0c32b7fb
DM
220 print "detected changes\n";
221 } else {
222 print "no changes\n";
223 }
224 if (!$cluster_conf->{options}->{enable}) {
225 print "firewall disabled\n";
226 }
227
228 };
229
230 PVE::Firewall::run_locked($code);
231
232 return undef;
233 }});
234
235__PACKAGE__->register_method ({
236 name => 'localnet',
237 path => 'localnet',
238 method => 'GET',
239 description => "Print information about local network.",
240 parameters => {
241 additionalProperties => 0,
242 properties => {},
243 },
244 returns => { type => 'null' },
245 code => sub {
246 my ($param) = @_;
247
248 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
249
250 my $nodename = PVE::INotify::nodename();
251 print "local hostname: $nodename\n";
252
253 my $ip = PVE::Cluster::remote_node_ip($nodename);
254 print "local IP address: $ip\n";
255
256 my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
257
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";
262 } else {
263 print "using detected local_network: $localnet\n";
264 }
265
266 return undef;
267 }});
268
269__PACKAGE__->register_method ({
270 name => 'simulate',
271 path => 'simulate',
272 method => 'GET',
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.",
274 parameters => {
275 additionalProperties => 0,
276 properties => {
277 verbose => {
278 description => "Verbose output.",
279 type => 'boolean',
280 optional => 1,
281 default => 0,
282 },
283 from => {
284 description => "Source zone.",
285 type => 'string',
286 pattern => '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
287 optional => 1,
288 default => 'outside',
289 },
290 to => {
291 description => "Destination zone.",
292 type => 'string',
293 pattern => '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
294 optional => 1,
295 default => 'host',
296 },
297 protocol => {
298 description => "Protocol.",
299 type => 'string',
300 pattern => '(tcp|udp)',
301 optional => 1,
302 default => 'tcp',
303 },
304 dport => {
305 description => "Destination port.",
306 type => 'integer',
307 minValue => 1,
308 maxValue => 65535,
309 optional => 1,
310 },
311 sport => {
312 description => "Source port.",
313 type => 'integer',
314 minValue => 1,
315 maxValue => 65535,
316 optional => 1,
317 },
318 source => {
319 description => "Source IP address.",
320 type => 'string', format => 'ipv4',
321 optional => 1,
322 },
323 dest => {
324 description => "Destination IP address.",
325 type => 'string', format => 'ipv4',
326 optional => 1,
327 },
328 },
329 },
330 returns => { type => 'null' },
331 code => sub {
332 my ($param) = @_;
333
334 local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
335
c5e8b008 336 my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile(undef, undef, undef, $param->{verbose});
0c32b7fb
DM
337
338 PVE::FirewallSimulator::debug($param->{verbose} || 0);
339
340 my $host_ip = PVE::Cluster::remote_node_ip($nodename);
341
342 PVE::FirewallSimulator::reset_trace();
343 print Dumper($ruleset) if $param->{verbose};
344
345 my $test = {
346 from => $param->{from},
347 to => $param->{to},
348 proto => $param->{protocol} || 'tcp',
349 source => $param->{source},
350 dest => $param->{dest},
351 dport => $param->{dport},
352 sport => $param->{sport},
353 };
354
355 if (!defined($test->{to})) {
356 $test->{to} = 'host';
357 PVE::FirewallSimulator::add_trace("Set Zone: to => '$test->{to}'\n");
358 }
359 if (!defined($test->{from})) {
360 $test->{from} = 'outside',
361 PVE::FirewallSimulator::add_trace("Set Zone: from => '$test->{from}'\n");
362 }
363
364 my $vmdata = PVE::Firewall::read_local_vm_config();
365
366 print "Test packet:\n";
367
368 foreach my $k (qw(from to proto source dest dport sport)) {
369 printf(" %-8s: %s\n", $k, $test->{$k}) if defined($test->{$k});
370 }
371
372 $test->{action} = 'QUERY';
373
374 my $res = PVE::FirewallSimulator::simulate_firewall($ruleset, $ipset_ruleset,
375 $host_ip, $vmdata, $test);
376
377 print "ACTION: $res\n";
378
379 return undef;
380 }});
381
382our $cmddef = {
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 {
390 my $res = shift;
391 my $status = ($res->{enable} ? "enabled" : "disabled") . '/' . $res->{status};
392
393 if ($res->{changes}) {
394 print "Status: $status (pending changes)\n";
395 } else {
396 print "Status: $status\n";
397 }
398 }],
399 };
400
4011;