]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/pve-firewall
remove class paramenter from register_XXX_command
[pve-firewall.git] / src / pve-firewall
index 91fc531cf9181d7137a6eff5dc6ff285e11e4005..690c569854b67d335629ed22b3491f82bc8add37 100755 (executable)
@@ -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 read /dev/null";
-       open STDOUT, '>/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";
        }
     }],
  };