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.
--- /dev/null
+# Should pve-firewall run automatically on startup? (default: yes)
+START_FIREWALL=yes
\ No newline at end of file
# 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
override_dh_installinit:
dh_installinit -R -p pve-firewall
- dh_installinit --name pvefw-logger
+ dh_installinit -R --name pvefw-logger
--- /dev/null
+interest /usr/share/perl5
$(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
description => "List security groups.",
parameters => {
additionalProperties => 0,
+ properties => {},
},
returns => {
type => 'array',
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';
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) = @_;
}
sub update {
- my ($start, $verbose) = @_;
+ my ($verbose) = @_;
my $code = sub {
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();
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);
--- /dev/null
+#!/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.
+
+
+
+
+++ /dev/null
-#!/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);
-
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);