X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2Fpve-firewall;h=690c569854b67d335629ed22b3491f82bc8add37;hp=91fc531cf9181d7137a6eff5dc6ff285e11e4005;hb=88e44ee4eb6d432f22b876226325417a0f51e8b3;hpb=e7fb6ff270f833412f7fdc753e170e68b0041656 diff --git a/src/pve-firewall b/src/pve-firewall index 91fc531..690c569 100755 --- a/src/pve-firewall +++ b/src/pve-firewall @@ -3,11 +3,11 @@ use strict; use warnings; use PVE::SafeSyslog; -use POSIX ":sys_wait_h"; -use Fcntl ':flock'; -use Getopt::Long; +use PVE::Daemon; + use Time::HiRes qw (gettimeofday); use PVE::Tools qw(dir_glob_foreach file_read_firstline); +use PVE::ProcFSTools; use PVE::INotify; use PVE::Cluster qw(cfs_read_file); use PVE::RPCEnvironment; @@ -16,26 +16,22 @@ use PVE::Firewall; use PVE::FirewallSimulator; use Data::Dumper; -use base qw(PVE::CLIHandler); - -my $pve_firewall_pidfile = "/var/run/pve-firewall.pid"; +use base qw(PVE::Daemon); $SIG{'__WARN__'} = sub { my $err = $@; my $t = $_[0]; chomp $t; - print "$t\n"; - syslog('warning', "WARNING: %s", $t); + print STDERR "$t\n"; + syslog('warning', "%s", $t); $@ = $err; }; -initlog('pve-firewall'); +my $cmdline = [$0, @ARGV]; -$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin'; +my %daemon_options = (restart_on_error => 5, stop_wait_time => 5); -die "please run as root\n" if $> != 0; - -PVE::INotify::inotify_init(); +my $daemon = __PACKAGE__->new('pve-firewall', $cmdline, %daemon_options); my $rpcenv = PVE::RPCEnvironment->init('cli'); @@ -45,56 +41,11 @@ $rpcenv->set_user('root@pam'); my $nodename = PVE::INotify::nodename(); -my $commandline = [$0, @ARGV]; - -$0 = "pve-firewall"; - -sub restart_server { - my ($waittime) = @_; - - syslog('info', "server shutdown (restart)"); - - $ENV{RESTART_PVE_FIREWALL} = 1; - - sleep($waittime) if $waittime; # avoid high server load due to restarts - - exec (@$commandline); - exit (-1); # never reached? -} - -sub cleanup { - unlink "$pve_firewall_pidfile.lock"; - unlink $pve_firewall_pidfile; -} - -sub lockpidfile { - my $pidfile = shift; - my $lkfn = "$pidfile.lock"; - - if (!open (FLCK, ">>$lkfn")) { - my $msg = "can't aquire lock on file '$lkfn' - $!"; - syslog ('err', $msg); - die "ERROR: $msg\n"; - } - - if (!flock (FLCK, LOCK_EX|LOCK_NB)) { - close (FLCK); - my $msg = "can't aquire lock '$lkfn' - $!"; - syslog ('err', $msg); - die "ERROR: $msg\n"; - } -} +sub init { -sub writepidfile { - my $pidfile = shift; - - if (!open (PIDFH, ">$pidfile")) { - my $msg = "can't open pid file '$pidfile' - $!"; - syslog ('err', $msg); - die "ERROR: $msg\n"; - } - print PIDFH "$$\n"; - close (PIDFH); + PVE::Cluster::cfs_update(); + + PVE::Firewall::init(); } my $restart_request = 0; @@ -105,194 +56,83 @@ my $updatetime = 10; my $initial_memory_usage; -sub run_server { - my ($param) = @_; - - # try to get the lock - lockpidfile($pve_firewall_pidfile); - - # run in background - my $spid; - - my $restart = $ENV{RESTART_PVE_FIREWALL}; +sub shutdown { + my ($self) = @_; - delete $ENV{RESTART_PVE_FIREWALL}; + syslog('info' , "server closing"); - PVE::Cluster::cfs_update(); - - PVE::Firewall::init(); - - if (!$param->{debug}) { - open STDIN, '/dev/null' || die "can't write /dev/null"; - } - - if (!$restart && !$param->{debug}) { - $spid = fork(); - if (!defined ($spid)) { - my $msg = "can't put server into background - fork failed"; - syslog('err', $msg); - die "ERROR: $msg\n"; - } elsif ($spid) { # parent - exit (0); - } - } - - writepidfile($pve_firewall_pidfile); - - open STDERR, '>&STDOUT' || die "can't close STDERR\n"; - - $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub { - syslog('info' , "server closing"); - - $SIG{INT} = 'DEFAULT'; - - # wait for children - 1 while (waitpid(-1, POSIX::WNOHANG()) > 0); + # wait for children + 1 while (waitpid(-1, POSIX::WNOHANG()) > 0); - syslog('info' , "clear firewall rules"); - eval { PVE::Firewall::remove_pvefw_chains(); die "STOP";}; - warn $@ if $@; - - cleanup(); - - exit (0); - }; - - $SIG{HUP} = sub { - # wake up process, so this forces an immediate firewall rules update - syslog('info' , "received signal HUP (restart)"); - $restart_request = 1; - }; + syslog('info' , "clear firewall rules"); - if ($restart) { - syslog('info' , "restarting server"); - } else { - syslog('info' , "starting server"); - } - - for (;;) { # forever + eval { PVE::Firewall::remove_pvefw_chains(); }; + warn $@ if $@; - eval { - - local $SIG{'__WARN__'} = 'IGNORE'; # do not fill up logs - - $next_update = time() + $updatetime; - - my ($ccsec, $cusec) = gettimeofday (); - eval { - PVE::Cluster::cfs_update(); - PVE::Firewall::update(); - }; - my $err = $@; - - if ($err) { - syslog('err', "status update error: $err"); - } + $self->exit_daemon(0); +} - my ($ccsec_end, $cusec_end) = gettimeofday (); - my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000; +sub hup { + my ($self) = @_; - syslog('info', sprintf("firewall update time (%.3f seconds)", $cptime)) - if ($cptime > 5); + $restart_request = 1; +} - $cycle++; +sub run { + my ($self) = @_; - my $mem = PVE::ProcFSTools::read_memory_usage(); + local $SIG{'__WARN__'} = 'IGNORE'; # do not fill up logs - if (!defined($initial_memory_usage) || ($cycle < 10)) { - $initial_memory_usage = $mem->{resident}; - } else { - my $diff = $mem->{resident} - $initial_memory_usage; - if ($diff > 5*1024*1024) { - syslog ('info', "restarting server after $cycle cycles to " . - "reduce memory usage (free $mem->{resident} ($diff) bytes)"); - restart_server(); - } - } + for (;;) { # forever - my $wcount = 0; - while ((time() < $next_update) && - ($wcount < $updatetime) && # protect against time wrap - !$restart_request) { $wcount++; sleep (1); }; + $next_update = time() + $updatetime; - restart_server() if $restart_request; + my ($ccsec, $cusec) = gettimeofday (); + eval { + PVE::Cluster::cfs_update(); + PVE::Firewall::update(); }; - my $err = $@; - + if ($err) { - syslog ('err', "ERROR: $err"); - restart_server(5); - exit (0); + syslog('err', "status update error: $err"); } - } -} -__PACKAGE__->register_method ({ - name => 'start', - path => 'start', - method => 'POST', - description => "Start the Proxmox VE firewall service.", - parameters => { - additionalProperties => 0, - properties => { - debug => { - description => "Debug mode - stay in foreground", - type => "boolean", - optional => 1, - default => 0, - }, - }, - }, - returns => { type => 'null' }, + my ($ccsec_end, $cusec_end) = gettimeofday (); + my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000; - code => sub { - my ($param) = @_; - - run_server($param); - - return undef; - }}); - -__PACKAGE__->register_method ({ - name => 'stop', - path => 'stop', - method => 'POST', - description => "Stop firewall. This removes all Proxmox VE related iptable rules. The host is unprotected afterwards.", - parameters => { - additionalProperties => 0, - properties => {}, - }, - returns => { type => 'null' }, + syslog('info', sprintf("firewall update time (%.3f seconds)", $cptime)) + if ($cptime > 5); - code => sub { - my ($param) = @_; + $cycle++; - my $pid = int(PVE::Tools::file_read_firstline($pve_firewall_pidfile) || 0); - - if ($pid) { - if (PVE::ProcFSTools::check_process_running($pid)) { - kill(15, $pid); # send TERM signal - # give max 5 seconds to shut down - for (my $i = 0; $i < 5; $i++) { - last if !PVE::ProcFSTools::check_process_running($pid); - sleep (1); - } - - # to be sure - kill(9, $pid); - waitpid($pid, 0); - } - if (-f $pve_firewall_pidfile) { - # try to get the lock - lockpidfile($pve_firewall_pidfile); - cleanup(); + my $mem = PVE::ProcFSTools::read_memory_usage(); + + if (!defined($initial_memory_usage) || ($cycle < 10)) { + $initial_memory_usage = $mem->{resident}; + } else { + my $diff = $mem->{resident} - $initial_memory_usage; + if ($diff > 5*1024*1024) { + syslog ('info', "restarting server after $cycle cycles to " . + "reduce memory usage (free $mem->{resident} ($diff) bytes)"); + $self->restart_daemon(); } } - return undef; - }}); + my $wcount = 0; + while ((time() < $next_update) && + ($wcount < $updatetime) && # protect against time wrap + !$restart_request) { $wcount++; sleep (1); }; + + $self->restart_daemon() if $restart_request; + } +} + +$daemon->register_start_command("Start the Proxmox VE firewall service."); +$daemon->register_restart_command("Restart the Proxmox VE firewall service."); +$daemon->register_stop_command("Stop firewall. This removes all Proxmox VE " . + "related iptable rules. " . + "The host is unprotected afterwards."); __PACKAGE__->register_method ({ name => 'status', @@ -309,7 +149,11 @@ __PACKAGE__->register_method ({ properties => { status => { type => 'string', - enum => ['unknown', 'stopped', 'active'], + enum => ['unknown', 'stopped', 'running'], + }, + enable => { + description => "Firewall is enabled (in 'cluster.fw')", + type => 'boolean', }, changes => { description => "Set when there are pending changes.", @@ -325,19 +169,24 @@ __PACKAGE__->register_method ({ my $code = sub { - my $pid = int(PVE::Tools::file_read_firstline($pve_firewall_pidfile) || 0); - my $running = PVE::ProcFSTools::check_process_running($pid); - - my $status = $running ? 'active' : 'stopped'; + my $status = $daemon->running() ? 'running' : 'stopped'; my $res = { status => $status }; - if ($status eq 'active') { - my ($ruleset, $ipset_ruleset) = PVE::Firewall::compile(); - my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset); - my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset); - - $res->{changes} = ($ipset_changes || $ruleset_changes) ? 1 : 0; + my $verbose = 1; # show syntax errors + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef, $verbose); + $res->{enable} = $cluster_conf->{options}->{enable} ? 1 : 0; + + if ($status eq 'running') { + + my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose); + + $verbose = 0; # do not show iptables details + my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose); + my ($test, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset, $verbose); + my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6, $verbose, "ip6tables"); + + $res->{changes} = ($ipset_changes || $ruleset_changes || $ruleset_changesv6) ? 1 : 0; } return $res; @@ -349,7 +198,7 @@ __PACKAGE__->register_method ({ __PACKAGE__->register_method ({ name => 'compile', path => 'compile', - method => 'POST', + method => 'GET', description => "Compile and print firewall rules. This is useful for testing.", parameters => { additionalProperties => 0, @@ -363,15 +212,30 @@ __PACKAGE__->register_method ({ local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog my $code = sub { - my ($ruleset, $ipset_ruleset) = PVE::Firewall::compile(); - my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, 1); - my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset, 1); - if ($ipset_changes || $ruleset_changes) { + my $verbose = 1; + + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef, $verbose); + my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose); + + print "ipset cmdlist:\n"; + my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose); + + print "\niptables cmdlist:\n"; + my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset, $verbose); + + print "\nip6tables cmdlist:\n"; + my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6, $verbose, "ip6tables"); + + if ($ipset_changes || $ruleset_changes || $ruleset_changesv6) { print "detected changes\n"; } else { print "no changes\n"; } + if (!$cluster_conf->{options}->{enable}) { + print "firewall disabled\n"; + } + }; PVE::Firewall::run_locked($code); @@ -416,7 +280,7 @@ __PACKAGE__->register_method ({ __PACKAGE__->register_method ({ name => 'simulate', path => 'simulate', - method => 'POST', + method => 'GET', 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.", parameters => { additionalProperties => 0, @@ -480,7 +344,7 @@ __PACKAGE__->register_method ({ local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog - my ($ruleset, $ipset_ruleset) = PVE::Firewall::compile(); + my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile(undef, undef, undef, $param->{verbose}); PVE::FirewallSimulator::debug($param->{verbose} || 0); @@ -528,16 +392,19 @@ __PACKAGE__->register_method ({ my $cmddef = { start => [ __PACKAGE__, 'start', []], + restart => [ __PACKAGE__, 'restart', []], stop => [ __PACKAGE__, 'stop', []], compile => [ __PACKAGE__, 'compile', []], simulate => [ __PACKAGE__, 'simulate', []], localnet => [ __PACKAGE__, 'localnet', []], status => [ __PACKAGE__, 'status', [], undef, sub { my $res = shift; + my $status = ($res->{enable} ? "enabled" : "disabled") . '/' . $res->{status}; + if ($res->{changes}) { - print "Status: $res->{status} (pending changes)\n"; + print "Status: $status (pending changes)\n"; } else { - print "Status: $res->{status}\n"; + print "Status: $status\n"; } }], };