]>
git.proxmox.com Git - pve-firewall.git/blob - src/pve-firewall
6 use POSIX
":sys_wait_h" ;
9 use Time
:: HiRes qw
( gettimeofday
);
10 use PVE
:: Tools
qw(dir_glob_foreach file_read_firstline) ;
12 use PVE
:: Cluster
qw(cfs_read_file) ;
13 use PVE
:: RPCEnvironment
;
16 use PVE
:: FirewallSimulator
;
19 use base
qw(PVE::CLIHandler) ;
21 my $pve_firewall_pidfile = "/var/run/pve-firewall.pid" ;
23 $SIG { '__WARN__' } = sub {
28 syslog
( 'warning' , "WARNING: %s " , $t );
32 initlog
( 'pve-firewall' );
34 $ENV { 'PATH' } = '/sbin:/bin:/usr/sbin:/usr/bin' ;
36 die "please run as root \n " if $> != 0 ;
38 PVE
:: INotify
:: inotify_init
();
40 my $rpcenv = PVE
:: RPCEnvironment-
> init ( 'cli' );
42 $rpcenv -> init_request ();
43 $rpcenv -> set_language ( $ENV { LANG
});
44 $rpcenv -> set_user ( 'root @pam ' );
46 my $nodename = PVE
:: INotify
:: nodename
();
48 my $commandline = [ $0, @ARGV ];
52 mkdir "/var/lib/pve-firewall" ;
57 syslog
( 'info' , "server shutdown (restart)" );
59 $ENV { RESTART_PVE_FIREWALL
} = 1 ;
61 sleep ( $waittime ) if $waittime ; # avoid high server load due to restarts
63 PVE
:: INotify
:: inotify_close
();
66 exit (- 1 ); # never reached?
70 unlink " $pve_firewall_pidfile .lock" ;
71 unlink $pve_firewall_pidfile ;
76 my $lkfn = " $pidfile .lock" ;
78 if (! open ( FLCK
, ">> $lkfn " )) {
79 my $msg = "can't aquire lock on file ' $lkfn ' - $! " ;
84 if (! flock ( FLCK
, LOCK_EX
| LOCK_NB
)) {
86 my $msg = "can't aquire lock ' $lkfn ' - $! " ;
95 if (! open ( PIDFH
, "> $pidfile " )) {
96 my $msg = "can't open pid file ' $pidfile ' - $! " ;
104 my $restart_request = 0 ;
110 my $initial_memory_usage ;
115 # try to get the lock
116 lockpidfile
( $pve_firewall_pidfile );
121 my $restart = $ENV { RESTART_PVE_FIREWALL
};
123 delete $ENV { RESTART_PVE_FIREWALL
};
125 PVE
:: Cluster
:: cfs_update
();
127 PVE
:: Firewall
:: init
();
129 if (! $param ->{ debug
}) {
130 open STDIN
, '</dev/null' || die "can't read /dev/null" ;
131 open STDOUT
, '>/dev/null' || die "can't write /dev/null" ;
134 if (! $restart && ! $param ->{ debug
}) {
136 if (! defined ( $spid )) {
137 my $msg = "can't put server into background - fork failed" ;
140 } elsif ( $spid ) { # parent
145 writepidfile
( $pve_firewall_pidfile );
147 open STDERR
, '>&STDOUT' || die "can't close STDERR \n " ;
149 $SIG { INT
} = $SIG { TERM
} = $SIG { QUIT
} = sub {
150 syslog
( 'info' , "server closing" );
152 $SIG { INT
} = 'DEFAULT' ;
155 1 while ( waitpid (- 1 , POSIX
:: WNOHANG
()) > 0 );
157 syslog
( 'info' , "clear firewall rules" );
158 eval { PVE
:: Firewall
:: remove_pvefw_chains
(); die "STOP" ;};
167 # wake up process, so this forces an immediate firewall rules update
168 syslog
( 'info' , "received signal HUP (restart)" );
169 $restart_request = 1 ;
173 syslog
( 'info' , "restarting server" );
175 syslog
( 'info' , "starting server" );
182 local $SIG { '__WARN__' } = 'IGNORE' ; # do not fill up logs
184 $next_update = time () + $updatetime ;
186 my ( $ccsec, $cusec ) = gettimeofday
();
188 PVE
:: Cluster
:: cfs_update
();
189 PVE
:: Firewall
:: update
();
194 syslog
( 'err' , "status update error: $err " );
197 my ( $ccsec_end, $cusec_end ) = gettimeofday
();
198 my $cptime = ( $ccsec_end - $ccsec ) + ( $cusec_end - $cusec )/ 1000000 ;
200 syslog
( 'info' , sprintf ( "firewall update time (%.3f seconds)" , $cptime ))
205 my $mem = PVE
:: ProcFSTools
:: read_memory_usage
();
207 if (! defined ( $initial_memory_usage ) || ( $cycle < 10 )) {
208 $initial_memory_usage = $mem ->{ resident
};
210 my $diff = $mem ->{ resident
} - $initial_memory_usage ;
211 if ( $diff > 5 * 1024 * 1024 ) {
212 syslog
( 'info' , "restarting server after $cycle cycles to " .
213 "reduce memory usage (free $mem ->{resident} ( $diff ) bytes)" );
219 while (( time () < $next_update ) &&
220 ( $wcount < $updatetime ) && # protect against time wrap
221 ! $restart_request ) { $wcount++ ; sleep ( 1 ); };
223 restart_server
() if $restart_request ;
229 syslog
( 'err' , "ERROR: $err " );
236 __PACKAGE__-
> register_method ({
240 description
=> "Start the Proxmox VE firewall service." ,
242 additionalProperties
=> 0 ,
245 description
=> "Debug mode - stay in foreground" ,
252 returns
=> { type
=> 'null' },
262 __PACKAGE__-
> register_method ({
266 description
=> "Stop firewall. This removes all Proxmox VE related iptable rules. The host is unprotected afterwards." ,
268 additionalProperties
=> 0 ,
271 returns
=> { type
=> 'null' },
276 my $pid = int ( PVE
:: Tools
:: file_read_firstline
( $pve_firewall_pidfile ) || 0 );
279 if ( PVE
:: ProcFSTools
:: check_process_running
( $pid )) {
280 kill ( 15 , $pid ); # send TERM signal
281 # give max 5 seconds to shut down
282 for ( my $i = 0 ; $i < 5 ; $i++ ) {
283 last if ! PVE
:: ProcFSTools
:: check_process_running
( $pid );
291 if (- f
$pve_firewall_pidfile ) {
292 # try to get the lock
293 lockpidfile
( $pve_firewall_pidfile );
301 __PACKAGE__-
> register_method ({
305 description
=> "Get firewall status." ,
307 additionalProperties
=> 0 ,
312 additionalProperties
=> 0 ,
316 enum
=> [ 'unknown' , 'stopped' , 'running' ],
319 description
=> "Firewall is enabled (in 'cluster.fw')" ,
323 description
=> "Set when there are pending changes." ,
332 local $SIG { '__WARN__' } = 'DEFAULT' ; # do not fill up syslog
336 my $pid = int ( PVE
:: Tools
:: file_read_firstline
( $pve_firewall_pidfile ) || 0 );
337 my $running = PVE
:: ProcFSTools
:: check_process_running
( $pid );
339 my $status = $running ?
'running' : 'stopped' ;
341 my $res = { status
=> $status };
343 my $verbose = 1 ; # show syntax errors
344 my $cluster_conf = PVE
:: Firewall
:: load_clusterfw_conf
( undef , $verbose );
345 $res ->{ enable
} = $cluster_conf ->{ options
}->{ enable
} ?
1 : 0 ;
347 if ( $status eq 'running' ) {
349 my ( $ruleset, $ipset_ruleset, $rulesetv6 ) = PVE
:: Firewall
:: compile
( $cluster_conf, undef , undef , $verbose );
351 $verbose = 0 ; # do not show iptables details
352 my ( undef , undef , $ipset_changes ) = PVE
:: Firewall
:: get_ipset_cmdlist
( $ipset_ruleset, $verbose );
353 my ( $test, $ruleset_changes ) = PVE
:: Firewall
:: get_ruleset_cmdlist
( $ruleset, $verbose );
354 my ( undef , $ruleset_changesv6 ) = PVE
:: Firewall
:: get_ruleset_cmdlist
( $rulesetv6, $verbose, "ip6tables" );
356 $res ->{ changes
} = ( $ipset_changes || $ruleset_changes || $ruleset_changesv6 ) ?
1 : 0 ;
362 return PVE
:: Firewall
:: run_locked
( $code );
365 __PACKAGE__-
> register_method ({
369 description
=> "Compile and print firewall rules. This is useful for testing." ,
371 additionalProperties
=> 0 ,
374 returns
=> { type
=> 'null' },
379 local $SIG { '__WARN__' } = 'DEFAULT' ; # do not fill up syslog
385 my $cluster_conf = PVE
:: Firewall
:: load_clusterfw_conf
( undef , $verbose );
386 my ( $ruleset, $ipset_ruleset, $rulesetv6 ) = PVE
:: Firewall
:: compile
( $cluster_conf, undef , undef , $verbose );
388 print "ipset cmdlist: \n " ;
389 my ( undef , undef , $ipset_changes ) = PVE
:: Firewall
:: get_ipset_cmdlist
( $ipset_ruleset, $verbose );
391 print " \n iptables cmdlist: \n " ;
392 my ( undef , $ruleset_changes ) = PVE
:: Firewall
:: get_ruleset_cmdlist
( $ruleset, $verbose );
394 print " \n ip6tables cmdlist: \n " ;
395 my ( undef , $ruleset_changesv6 ) = PVE
:: Firewall
:: get_ruleset_cmdlist
( $rulesetv6, $verbose, "ip6tables" );
397 if ( $ipset_changes || $ruleset_changes || $ruleset_changesv6 ) {
398 print "detected changes \n " ;
400 print "no changes \n " ;
402 if (! $cluster_conf ->{ options
}->{ enable
}) {
403 print "firewall disabled \n " ;
408 PVE
:: Firewall
:: run_locked
( $code );
413 __PACKAGE__-
> register_method ({
417 description
=> "Print information about local network." ,
419 additionalProperties
=> 0 ,
422 returns
=> { type
=> 'null' },
426 local $SIG { '__WARN__' } = 'DEFAULT' ; # do not fill up syslog
428 my $nodename = PVE
:: INotify
:: nodename
();
429 print "local hostname: $nodename\n " ;
431 my $ip = PVE
:: Cluster
:: remote_node_ip
( $nodename );
432 print "local IP address: $ip\n " ;
434 my $cluster_conf = PVE
:: Firewall
:: load_clusterfw_conf
();
436 my $localnet = PVE
:: Firewall
:: local_network
() || '127.0.0.0/8' ;
437 print "network auto detect: $localnet\n " ;
438 if ( $cluster_conf ->{ aliases
}->{ local_network
}) {
439 print "using user defined local_network: $cluster_conf ->{aliases}->{local_network}->{cidr} \n " ;
441 print "using detected local_network: $localnet\n " ;
447 __PACKAGE__-
> register_method ({
451 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." ,
453 additionalProperties
=> 0 ,
456 description
=> "Verbose output." ,
462 description
=> "Source zone." ,
464 pattern
=> '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)' ,
466 default => 'outside' ,
469 description
=> "Destination zone." ,
471 pattern
=> '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)' ,
476 description
=> "Protocol." ,
478 pattern
=> '(tcp|udp)' ,
483 description
=> "Destination port." ,
490 description
=> "Source port." ,
497 description
=> "Source IP address." ,
498 type
=> 'string' , format
=> 'ipv4' ,
502 description
=> "Destination IP address." ,
503 type
=> 'string' , format
=> 'ipv4' ,
508 returns
=> { type
=> 'null' },
512 local $SIG { '__WARN__' } = 'DEFAULT' ; # do not fill up syslog
514 my ( $ruleset, $ipset_ruleset, $rulesetv6 ) = PVE
:: Firewall
:: compile
( undef , undef , undef , $param ->{ verbose
});
516 PVE
:: FirewallSimulator
:: debug
( $param ->{ verbose
} || 0 );
518 my $host_ip = PVE
:: Cluster
:: remote_node_ip
( $nodename );
520 PVE
:: FirewallSimulator
:: reset_trace
();
521 print Dumper
( $ruleset ) if $param ->{ verbose
};
524 from
=> $param ->{ from
},
526 proto
=> $param ->{ protocol
} || 'tcp' ,
527 source
=> $param ->{ source
},
528 dest
=> $param ->{ dest
},
529 dport
=> $param ->{ dport
},
530 sport
=> $param ->{ sport
},
533 if (! defined ( $test ->{ to
})) {
534 $test ->{ to
} = 'host' ;
535 PVE
:: FirewallSimulator
:: add_trace
( "Set Zone: to => ' $test ->{to}' \n " );
537 if (! defined ( $test ->{ from
})) {
538 $test ->{ from
} = 'outside' ,
539 PVE
:: FirewallSimulator
:: add_trace
( "Set Zone: from => ' $test ->{from}' \n " );
542 my $vmdata = PVE
:: Firewall
:: read_local_vm_config
();
544 print "Test packet: \n " ;
546 foreach my $k ( qw(from to proto source dest dport sport) ) {
547 printf ( " %-8s: %s\n " , $k, $test ->{ $k }) if defined ( $test ->{ $k });
550 $test ->{ action
} = 'QUERY' ;
552 my $res = PVE
:: FirewallSimulator
:: simulate_firewall
( $ruleset, $ipset_ruleset,
553 $host_ip, $vmdata, $test );
555 print "ACTION: $res\n " ;
561 start
=> [ __PACKAGE__
, 'start' , []],
562 stop
=> [ __PACKAGE__
, 'stop' , []],
563 compile
=> [ __PACKAGE__
, 'compile' , []],
564 simulate
=> [ __PACKAGE__
, 'simulate' , []],
565 localnet
=> [ __PACKAGE__
, 'localnet' , []],
566 status
=> [ __PACKAGE__
, 'status' , [], undef , sub {
568 my $status = ( $res ->{ enable
} ?
"enabled" : "disabled" ) . '/' . $res ->{ status
};
570 if ( $res ->{ changes
}) {
571 print "Status: $status (pending changes) \n " ;
573 print "Status: $status\n " ;
580 PVE
:: CLIHandler
:: handle_cmd
( $cmddef, $0, $cmd, \
@ARGV, undef , $0 );
588 pve-firewall - PVE Firewall Daemon
596 This service updates iptables rules periodically.
598 =include pve_copyright