5
0
mirror of git://git.proxmox.com/git/pve-firewall.git synced 2025-01-07 21:17:59 +03:00

cleanup firewall service implementation

We now run a separate server called 'pve-firewall' (renamed 'pvefw').
So service and management tool use the same name:

 # service pve-firewall start

is the same as

 # pve-firewall start

Also removed the read_pvefw_status/save_pvefw_status code.
This commit is contained in:
Dietmar Maurer 2014-05-16 10:14:33 +02:00
parent 2708398494
commit e2beb7aa99
10 changed files with 472 additions and 310 deletions

2
debian/pve-firewall.default vendored Normal file
View File

@ -0,0 +1,2 @@
# Should pve-firewall run automatically on startup? (default: yes)
START_FIREWALL=yes

View File

@ -6,43 +6,66 @@
# Required-Stop: $remote_fs $network pvefw-logger pve-cluster
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: PVE firewall
# Short-Description: Proxmox VE firewall
### END INIT INFO
. /lib/lsb/init-functions
PATH=/sbin:/bin:/usr/bin:/usr/sbin
PVEFW=/usr/sbin/pvefw
NAME=pvefw
DESC="PVE firewall"
PIDFILE=/var/run/pvefw-logger.pid
NAME=pve-firewall
DAEMON=/usr/sbin/$NAME
DESC="Proxmox VE firewall"
PIDFILE=/var/run/$NAME.pid
test -f $PVEFW || exit 0
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
START_FIREWALL=yes
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
[ "$START_FIREWALL" = "yes" ] || exit 0
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
# avoid warnings about uninstalled locales
export LC_ALL="C"
case "$1" in
start)
log_daemon_msg "Starting $DESC" "$NAME"
$PVEFW start
log_end_msg $?
;;
stop)
log_daemon_msg "Stopping $DESC" "$NAME"
$PVEFW stop
log_end_msg $?
;;
reload|restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
$PVEFW update
log_end_msg $?
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|force-reload}"
exit 1
;;
start)
log_daemon_msg "Starting $DESC" "$NAME"
$DAEMON start
log_end_msg $?
;;
stop)
log_daemon_msg "Stopping $DESC" "$NAME"
$DAEMON stop
log_end_msg $?
;;
status)
$DAEMON status
;;
reload|restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
if ( [ -e $PIDFILE ] && kill -0 `cat $PIDFILE`) then
start-stop-daemon --stop --quiet --pidfile $PIDFILE --signal HUP
else
$DAEMON start
fi
log_end_msg $?
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|status|restart|force-reload}"
exit 1
;;
esac
exit 0

2
debian/rules vendored
View File

@ -8,4 +8,4 @@
override_dh_installinit:
dh_installinit -R -p pve-firewall
dh_installinit --name pvefw-logger
dh_installinit -R --name pvefw-logger

1
debian/triggers vendored Normal file
View File

@ -0,0 +1 @@
interest /usr/share/perl5

View File

@ -19,10 +19,10 @@ pvefw-logger: pvefw-logger.c
$(shell pkg-config libnetfilter_log glib-2.0 gthread-2.0 --libs --cflags)
.PHONY: install
install: pvefw pvefw-logger
install: pve-firewall pvefw-logger
make -C PVE install
install -d -m 0755 ${DESTDIR}/${SBINDIR}
install -m 0755 pvefw ${DESTDIR}/${SBINDIR}
install -m 0755 pve-firewall ${DESTDIR}/${SBINDIR}
install -m 0755 --strip pvefw-logger ${DESTDIR}/${SBINDIR}
.PHONY: clean

View File

@ -38,6 +38,7 @@ __PACKAGE__->register_method({
description => "List security groups.",
parameters => {
additionalProperties => 0,
properties => {},
},
returns => {
type => 'array',

View File

@ -98,7 +98,6 @@ use Data::Dumper;
my $nodename = PVE::INotify::nodename();
my $pve_fw_lock_filename = "/var/lock/pvefw.lck";
my $pve_fw_status_filename = "/var/lib/pve-firewall/pvefw.status";
my $default_log_level = 'info';
@ -2451,30 +2450,6 @@ sub round_powerof2 {
return ++$int;
}
sub save_pvefw_status {
my ($status) = @_;
die "unknown status '$status' - internal error"
if $status !~ m/^(stopped|active)$/;
mkdir dirname($pve_fw_status_filename);
PVE::Tools::file_set_contents($pve_fw_status_filename, $status);
}
sub read_pvefw_status {
my $status = 'unknown';
return 'stopped' if ! -f $pve_fw_status_filename;
eval {
$status = PVE::Tools::file_get_contents($pve_fw_status_filename);
};
warn $@ if $@;
return $status;
}
sub load_clusterfw_conf {
my ($filename) = @_;
@ -2969,7 +2944,7 @@ sub remove_pvefw_chains {
}
sub update {
my ($start, $verbose) = @_;
my ($verbose) = @_;
my $code = sub {
@ -2978,9 +2953,7 @@ sub update {
my $enable = $cluster_options->{enable};
my $status = read_pvefw_status();
die "Firewall is disabled - cannot start\n" if !$enable && $start;
die "Firewall is disabled - cannot start\n" if !$enable;
if (!$enable) {
PVE::Firewall::remove_pvefw_chains();
@ -2992,14 +2965,7 @@ sub update {
my ($ruleset, $ipset_ruleset) = compile($cluster_conf, $hostfw_conf);
if ($start || $status eq 'active') {
save_pvefw_status('active') if ($status ne 'active');
apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $verbose);
} else {
print "Firewall not active (status = $status)\n" if $verbose;
}
apply_ruleset($ruleset, $hostfw_conf, $ipset_ruleset, $verbose);
};
run_locked($code);

412
src/pve-firewall Executable file
View File

@ -0,0 +1,412 @@
#!/usr/bin/perl
use strict;
use warnings;
use PVE::SafeSyslog;
use POSIX ":sys_wait_h";
use Fcntl ':flock';
use Getopt::Long;
use Time::HiRes qw (gettimeofday);
use PVE::Tools qw(dir_glob_foreach file_read_firstline);
use PVE::INotify;
use PVE::Cluster qw(cfs_read_file);
use PVE::RPCEnvironment;
use PVE::CLIHandler;
use PVE::Firewall;
use base qw(PVE::CLIHandler);
my $pve_firewall_pidfile = "/var/run/pve-firewall.pid";
$SIG{'__WARN__'} = sub {
my $err = $@;
my $t = $_[0];
chomp $t;
print "$t\n";
syslog('warning', "WARNING: %s", $t);
$@ = $err;
};
initlog('pve-firewall');
$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
die "please run as root\n" if $> != 0;
PVE::INotify::inotify_init();
my $rpcenv = PVE::RPCEnvironment->init('cli');
$rpcenv->init_request();
$rpcenv->set_language($ENV{LANG});
$rpcenv->set_user('root@pam');
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 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);
}
my $restart_request = 0;
my $next_update = 0;
my $cycle = 0;
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};
delete $ENV{RESTART_PVE_FIREWALL};
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);
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;
};
if ($restart) {
syslog('info' , "restarting server");
} else {
syslog('info' , "starting server");
}
for (;;) { # forever
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");
}
my ($ccsec_end, $cusec_end) = gettimeofday ();
my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000;
syslog('info', sprintf("firewall update time (%.3f seconds)", $cptime))
if ($cptime > 5);
$cycle++;
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)");
restart_server();
}
}
my $wcount = 0;
while ((time() < $next_update) &&
($wcount < $updatetime) && # protect against time wrap
!$restart_request) { $wcount++; sleep (1); };
restart_server() if $restart_request;
};
my $err = $@;
if ($err) {
syslog ('err', "ERROR: $err");
restart_server(5);
exit (0);
}
}
}
__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' },
code => sub {
my ($param) = @_;
run_server($param);
return undef;
}});
__PACKAGE__->register_method ({
name => 'stop',
path => 'stop',
method => 'POST',
description => "Stop firewall. This will remove all rules installed by this script. The host is unprotected afterwards.",
parameters => {
additionalProperties => 0,
properties => {},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
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();
}
}
return undef;
}});
__PACKAGE__->register_method ({
name => 'status',
path => 'status',
method => 'GET',
description => "Get firewall status.",
parameters => {
additionalProperties => 0,
properties => {},
},
returns => {
type => 'object',
additionalProperties => 0,
properties => {
status => {
type => 'string',
enum => ['unknown', 'stopped', 'active'],
},
changes => {
description => "Set when there are pending changes.",
type => 'boolean',
optional => 1,
}
},
},
code => sub {
my ($param) = @_;
local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
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 $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;
}
return $res;
};
return PVE::Firewall::run_locked($code);
}});
__PACKAGE__->register_method ({
name => 'compile',
path => 'compile',
method => 'POST',
description => "Compile amd print firewall rules. This is only for testing.",
parameters => {
additionalProperties => 0,
properties => {},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
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) {
print "detected changes\n";
} else {
print "no changes\n";
}
};
PVE::Firewall::run_locked($code);
return undef;
}});
my $nodename = PVE::INotify::nodename();
my $cmddef = {
start => [ __PACKAGE__, 'start', []],
stop => [ __PACKAGE__, 'stop', []],
compile => [ __PACKAGE__, 'compile', []],
status => [ __PACKAGE__, 'status', [], undef, sub {
my $res = shift;
if ($res->{changes}) {
print "Status: $res->{status} (pending changes)\n";
} else {
print "Status: $res->{status}\n";
}
}],
};
my $cmd = shift;
PVE::CLIHandler::handle_cmd($cmddef, $0, $cmd, \@ARGV, undef, $0);
exit (0);
__END__
=head1 NAME
pvestatd - PVE Firewall Daemon
=head1 SYNOPSIS
pve-firewall
=head1 DESCRIPTION
This service updates iptables rules periodically.

242
src/pvefw
View File

@ -1,242 +0,0 @@
#!/usr/bin/perl -T
use strict;
use warnings;
use lib qw(.);
use PVE::Firewall;
use PVE::SafeSyslog;
use PVE::Cluster;
use PVE::INotify;
use PVE::RPCEnvironment;
use PVE::JSONSchema qw(get_standard_option);
use PVE::CLIHandler;
use PVE::API2::Firewall::Groups;
use base qw(PVE::CLIHandler);
use Data::Dumper;
$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
initlog ('pvefw');
die "please run as root\n" if $> != 0;
PVE::INotify::inotify_init();
my $rpcenv = PVE::RPCEnvironment->init('cli');
$rpcenv->init_request();
$rpcenv->set_language($ENV{LANG});
$rpcenv->set_user('root@pam');
__PACKAGE__->register_method ({
name => 'compile',
path => 'compile',
method => 'POST',
description => "Compile amd print firewall rules. This is only for testing.",
parameters => {
additionalProperties => 0,
properties => {
verbose => {
description => "Verbose output.",
type => "boolean",
optional => 1,
},
},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
$param->{verbose} = 1
if !defined($param->{verbose}) && ($rpcenv->{type} eq 'cli');
my $code = sub {
my ($ruleset, $ipset_ruleset) = PVE::Firewall::compile();
if ($param->{verbose}) {
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) {
print "detected changes\n";
} else {
print "no changes\n";
}
}
};
PVE::Firewall::run_locked($code);
return undef;
}});
__PACKAGE__->register_method ({
name => 'status',
path => 'status',
method => 'GET',
description => "Get firewall status.",
parameters => {
additionalProperties => 0,
properties => {},
},
returns => {
type => 'object',
additionalProperties => 0,
properties => {
status => {
type => 'string',
enum => ['unknown', 'stopped', 'active'],
},
changes => {
description => "Set when there are pending changes.",
type => 'boolean',
optional => 1,
}
},
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
$param->{verbose} = 1
if !defined($param->{verbose}) && ($rpcenv->{type} eq 'cli');
my $code = sub {
my $status = PVE::Firewall::read_pvefw_status();
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);
# fixme: ipset changes
$res->{changes} = ($ipset_changes || $ruleset_changes) ? 1 : 0;
}
return $res;
};
return PVE::Firewall::run_locked($code);
}});
__PACKAGE__->register_method ({
name => 'start',
path => 'start',
method => 'POST',
description => "Start (or simply update if already active) firewall.",
parameters => {
additionalProperties => 0,
properties => {
verbose => {
description => "Verbose output.",
type => "boolean",
optional => 1,
default => 0,
},
},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
PVE::Firewall::update(1, $param->{verbose});
return undef;
}});
__PACKAGE__->register_method ({
name => 'update',
path => 'update',
method => 'POST',
description => "Check firewall rules. Then update the rules if the firewall is active.",
parameters => {
additionalProperties => 0,
properties => {
verbose => {
description => "Verbose output.",
type => "boolean",
optional => 1,
default => 0,
},
},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
PVE::Firewall::update(0, $param->{verbose});
return undef;
}});
__PACKAGE__->register_method ({
name => 'stop',
path => 'stop',
method => 'POST',
description => "Stop firewall. This will remove all rules installed by this script. The host is then unprotected.",
parameters => {
additionalProperties => 0,
properties => {},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my $code = sub {
PVE::Firewall::remove_pvefw_chains();
PVE::Firewall::save_pvefw_status('stopped');
};
PVE::Firewall::run_locked($code);
return undef;
}});
my $nodename = PVE::INotify::nodename();
my $cmddef = {
compile => [ __PACKAGE__, 'compile', []],
start => [ __PACKAGE__, 'start', []],
update => [ __PACKAGE__, 'update', []],
status => [ __PACKAGE__, 'status', [], undef, sub {
my $res = shift;
if ($res->{changes}) {
print "Status: $res->{status} (pending changes)\n";
} else {
print "Status: $res->{status}\n";
}
}],
stop => [ __PACKAGE__, 'stop', []],
# This is for debugging
listgroups => [ 'PVE::API2::Firewall::Groups', 'list', [],
{ node => $nodename }, sub {
my $res = shift;
print Dumper($res);
}],
grouprules => [ 'PVE::API2::Firewall::Groups', 'get_rules', ['group'],
{ node => $nodename }, sub {
my $res = shift;
print Dumper($res);
}],
};
my $cmd = shift;
PVE::CLIHandler::handle_cmd($cmddef, "pvefw", $cmd, \@ARGV, undef, $0);
exit(0);

View File

@ -807,9 +807,8 @@ main(int argc, char *argv[])
g_io_add_watch(nflog_ch, G_IO_IN, nflog_read_cb, NULL);
GIOChannel *sig_ch = g_io_channel_unix_new(sigfd);
printf("TEST0: %p %d\n", sig_ch, sigfd);
if (!g_io_add_watch(sig_ch, G_IO_IN, signal_read_cb, NULL)) {
printf("TEST1\n"); exit(-1);
exit(-1);
}
GThread *wthread = g_thread_new("log_writer_thread", log_writer_thread, NULL);