2 ######################################################################
4 # Linux Director Daemon - run "perldoc l7directord" for details
6 # Copyright (C) 2005-2010 NTT COMWARE Corporation.
8 # License: GNU General Public License (GPL)
10 # This program is developed on similar lines of ldirectord. It handles
11 # l7vsadm and monitoring of real servers.
13 # The version of ldirectord used as a reference for this l7directord is
14 # ldirectord,v 1.77.2.32 2005/09/21 04:00:41
16 # Note : * The existing code of ldirectord that is not required for
17 # l7directord is also maintained in the program but is
20 # This program is free software; you can redistribute it and/or
21 # modify it under the terms of the GNU General Public License as
22 # published by the Free Software Foundation; either version 2 of the
23 # License, or (at your option) any later version.
25 # This program is distributed in the hope that it will be useful, but
26 # WITHOUT ANY WARRANTY; without even the implied warranty of
27 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 # General Public License for more details.
30 # You should have received a copy of the GNU General Public License
31 # along with this program; if not, write to the Free Software
32 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
34 ######################################################################
37 # 0.5.0-0: Added code related to Sorry server and Max connection
38 # - 2006/11/03 NTT COMWARE
39 # 1.0.0-0: Added code related to weight of real server and QoS
40 # - 2007/10/12 NTT COMWARE
41 # 1.0.1-0: Added the code below.
42 # configuration of realdowncallback, realrecovercallback,
43 # and sessionless module.
44 # - 2007/12/28 NTT COMWARE
45 # 1.0.2-0: Added the code below.
46 # cookie insert with X-Forwarded-For module(cinsert_xf)
47 # - 2008/1/14 Shinya TAKEBAYASHI
48 # 2.0.0-0: Added code related to sslid module.
49 # cinsert_xf module is marged into cinsert module.
50 # Added code related to syntax test of configuration.
51 # Expanded checkcount setting to all service check.
52 # - 2008/03/25 Norihisa NAKAI
53 # 2.1.0-0: Changed helthcheck logic to multi-process.
54 # - 2008/12/17 NTT COMWARE
55 # 2.1.1-0: Fix 'Range iterator outside integer range' in parse_real.
56 # - 2009/01/06 NTT COMWARE
57 # 2.1.2-0: Added code related to some module. See below.
58 # (cpassive, crewrite, pfilter, url, ip)
59 # Add custom healthcheck.
60 # (checktype=custom, customcheck=exec_command)
61 # - 2009/02/14 NTT COMWARE
62 # 3.0.0-1: Add code related to l7vsd v3.0.0. See below.
63 # - Add accesslog option.
64 # - Add tproxy option.
65 # 3.0.4-1: Change module check rule. Allow module name
67 # 3.1.0-1: Add code related to l7vsd v3.1.0. See below.
68 # - Add session_thread_pool_size option.
74 use Getopt::Long qw(:config posix_default);
76 use POSIX qw(:sys_wait_h :signal_h);
77 use Sys::Syslog qw(:DEFAULT setlogsock);
79 use Fatal qw(open close);
82 use Time::HiRes qw(sleep);
88 our $VERSION = '3.1.0-1';
89 our $COPYRIGHT = 'Copyright (C) 2012 NTT COMWARE CORPORATION';
91 # default global config values
93 logfile => '/var/log/l7vs/l7directord.log',
100 negotiatetimeout => 5,
109 # default virtual config values
112 module => { name => 'sessionless', key => q{} },
115 checktype => 'negotiate',
121 sorryserver => { ip => '0.0.0.0', port => 0, forward => 'none' },
125 virtualhost => undef,
129 realdowncallback => undef,
130 realrecovercallback => undef,
131 customcheck => undef,
132 sslconfigfile => undef,
134 accesslogfile => undef,
135 socketoption => undef,
136 accesslog_rotate_type => undef,
137 accesslog_rotate_max_backup_index => undef,
138 accesslog_rotate_max_filesize => undef,
139 accesslog_rotate_rotation_timing => undef,
140 accesslog_rotate_rotation_timing_value => undef,
141 session_thread_pool_size => undef,
142 other_virtual_key => undef,
145 checkinterval => undef,
146 retryinterval => undef,
147 checktimeout => undef,
148 negotiatetimeout => undef,
153 # default real config values
162 # current config data
163 our %CONFIG = %GLOBAL;
173 # process environment
177 pid_prefix => '/var/run/l7directord',
192 our $DEBUG_LEVEL = 0;
194 # health check process data
195 our %HEALTH_CHECK = ();
197 # real server health flag
199 our $SERVICE_DOWN = 1;
201 # section virtual sub config prefix
202 our $SECTION_VIRTUAL_PREFIX = " ";
207 # Main method of this program.
208 # parse command line and run each command method.
211 start => \&cmd_start,
213 restart => \&cmd_restart,
214 'try-restart' => \&cmd_try_restart,
215 reload => \&cmd_reload,
216 status => \&cmd_status,
217 configtest => \&cmd_configtest,
218 version => \&cmd_version,
220 usage => \&cmd_usage,
223 # change program name for removing `perl' string from `ps' command result.
224 my $ps_name = @ARGV ? $PROGRAM_NAME . " @ARGV"
226 $PROGRAM_NAME = $ps_name;
228 my $cmd_mode = parse_cmd();
229 if ( !defined $cmd_mode || !exists $cmd_func->{$cmd_mode} ) {
232 if ($cmd_mode ne 'help' && $cmd_mode ne 'version' && $cmd_mode ne 'usage') {
237 my $cmd_result = &{ $cmd_func->{$cmd_mode} }();
239 ld_exit( $cmd_result, _message_only('INF0008') );
243 # Parse command line (ARGV)
245 # configtest or help command
246 my $cmd_mode = parse_option();
249 if (!defined $cmd_mode && @ARGV) {
250 $cmd_mode = pop @ARGV;
256 # Parse option strings by Getopt::Long
258 my $cmd_mode = undef;
260 # default option value
266 # parse command line options
267 my $result = GetOptions(
268 'd:3' => \$debug, # debug mode, arg: debug level (default 3)
269 'h|help' => \$help, # show help message
270 't' => \$test, # config syntax test
271 'v|version' => \$version, # show version
276 if (defined $debug) {
277 $DEBUG_LEVEL = $debug;
284 elsif (defined $version) {
285 $cmd_mode = 'version';
287 elsif (defined $test) {
288 $cmd_mode = 'configtest';
299 # Initialize file path settings.
300 sub initial_setting {
301 # search config and l7vsadm
302 $PROC_ENV{l7vsadm} = search_l7vsadm_file();
303 $CONFIG_FILE{path} = search_config_file();
305 # get config file name exclude `.cf' or `.conf'
306 ( $CONFIG_FILE{filename} )
307 = $CONFIG_FILE{path} =~ m{([^/]+?)(?:\.cf|\.conf)?$};
311 = defined $ENV{HOSTNAME} ? $ENV{HOSTNAME}
312 : ( POSIX::uname() )[1]
317 # Search l7directord.cf file from search path.
318 sub search_config_file {
319 my $config_file = undef;
320 my @search_path = qw(
321 /etc/ha.d/conf/l7directord.cf
322 /etc/ha.d/l7directord.cf
327 $config_file = $ARGV[0];
329 init_error( _message_only('ERR0404', $config_file) );
333 for my $file (@search_path) {
335 $config_file = $file;
339 if (!defined $config_file) {
340 init_error( _message_only('ERR0405', $config_file) );
344 return abs_path($config_file);
347 # search_l7vsadm_file
348 # Search l7vsadm file from search path.
349 sub search_l7vsadm_file {
350 my $l7vsadm_file = undef;
351 my @search_path = qw(
357 for my $file (@search_path) {
359 $l7vsadm_file = $file;
363 if (!defined $l7vsadm_file) {
364 init_error( _message_only('ERR0406', $l7vsadm_file) );
367 return abs_path($l7vsadm_file);
372 # Called if command argument is start
373 # return: 0 if success
374 # 1 if old process id is found.
379 ld_log( _message('INF0001', $PROGRAM_NAME) );
383 my $oldpid = read_pid();
385 # already other process is running
387 print {*STDERR} _message_only('INF0103', $oldpid) . "\n";
391 # supervised or debug mode (not daemon)
392 if ($CONFIG{supervised} || $DEBUG_LEVEL > 0) {
393 ld_log( _message( 'INF0002', $VERSION, $PID, $CONFIG_FILE{path} ) );
398 ld_log( _message( 'INF0003', $VERSION, $CONFIG_FILE{path} ) );
401 write_pid( $PROC_STAT{pid} );
402 ld_cmd_children('start');
404 ld_cmd_children('stop');
411 # Send stop signal (TERM)
412 # Called if command argument is stop
413 # return: 0 if success
414 # 2 if old process id is not found.
415 # 3 if signal failed.
417 my ($oldpid, $stalepid) = read_pid();
419 # process is not running
422 my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
423 print {*STDERR} _message_only('INF0102', $pid_file, $CONFIG_FILE{path}) . "\n";
425 print {*STDERR} _message_only('INF0104') . "\n";
430 my $signaled = kill 15, $oldpid;
431 if ($signaled != 1) {
432 print {*STDERR} _message('WRN0003', $oldpid);
446 # Called if command argument is restart
447 # return: see cmd_start return
449 # stop and ignore result
453 my $status = cmd_start();
459 # Trying restart process
460 # Called if command argument is try-restart
461 # return: see cmd_start, cmd_stop return
462 sub cmd_try_restart {
464 my $stop_result = cmd_stop();
466 # start only if stop succeed
467 if ($stop_result != 0) {
472 my $status = cmd_start();
478 # Send reload signal (HUP)
479 # Called if command argument is reload
480 # return: 0 if success
481 # 2 if old process id is not found.
482 # 3 if signal failed.
485 my ($oldpid, $stalepid) = read_pid();
488 my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
489 print {*STDERR} _message_only( 'INF0102', $pid_file, $CONFIG_FILE{path} ) . "\n";
491 print {*STDERR} _message_only('INF0104') . "\n";
496 my $signaled = kill 1, $oldpid;
497 if ($signaled != 1) {
498 print {*STDERR} _message('WRN0004', $oldpid);
505 # Show process id of running
506 # Called if command argument is status
507 # return: 0 if success
508 # 2 if old process id is not found.
510 my ($oldpid, $stalepid) = read_pid();
513 my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
514 print {*STDERR} _message_only('INF0102', $pid_file, $CONFIG_FILE{path}) . "\n";
516 print {*STDERR} _message_only('INF0104') . "\n";
517 ld_cmd_children('status');
522 print {*STDERR} _message_only('INF0101', $CONFIG_FILE{path}, $oldpid) . "\n";
525 ld_cmd_children('status');
531 # Configuration syntax check
532 # Called if command argument is configtest
533 # return: 0 if syntax ok
534 # otherwise, exit by read_config
537 print {*STDOUT} "Syntax OK\n";
542 # Show program version.
543 # Called if command argument is version
546 print {*STDOUT} "l7directord, version $VERSION\n$COPYRIGHT\n";
551 # Show command manual.
552 # Called if command argument is help
555 system_wrapper( '/usr/bin/perldoc ' . $PROC_ENV{l7directord} );
560 # Show command usage.
561 # Called if command argument is unknown or not specified.
565 "Usage: l7directord {start|stop|restart|try-restart|reload|status|configtest}\n"
566 . "Try `l7directord --help' for more information.\n";
571 # Set signal handler function.
573 $SIG{ INT } = \&ld_handler_term;
574 $SIG{ QUIT } = \&ld_handler_term;
575 $SIG{ ILL } = \&ld_handler_term;
576 $SIG{ ABRT } = \&ld_handler_term;
577 $SIG{ FPE } = \&ld_handler_term;
578 $SIG{ SEGV } = \&ld_handler_term;
579 $SIG{ TERM } = \&ld_handler_term;
580 $SIG{ BUS } = \&ld_handler_term;
581 $SIG{ SYS } = \&ld_handler_term;
582 $SIG{ XCPU } = \&ld_handler_term;
583 $SIG{ XFSZ } = \&ld_handler_term;
584 # HUP is actually used
585 $SIG{ HUP } = \&ld_handler_hup;
586 # This used to call a signal handler, that logged a message
587 # However, this typically goes to syslog and if syslog
588 # is playing up a loop will occur.
589 $SIG{ PIPE } = 'IGNORE';
590 # handle perl warn signal
591 $SIG{__WARN__} = \&ld_handler_perl_warn;
594 # ld_handler_perl_warn
595 # Handle Perl warnings for logging file.
596 sub ld_handler_perl_warn {
597 my $warning = join q{, }, @_;
598 $warning =~ s/[\r\n]//g;
599 ld_log( _message('WRN0301', $warning) );
603 # Read pid file and check if pid (l7directord) is still running
606 my $file_pid = undef;
607 my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
609 open my $pid_handle, '<', $pid_file;
610 $file_pid = <$pid_handle>;
614 # Check to make sure this isn't a stale pid file
615 my $proc_file = "/proc/$file_pid/cmdline";
616 open my $proc_handle, '<', $proc_file;
617 my $line = <$proc_handle>;
618 if ($line =~ /l7directord/) {
619 $old_pid = $file_pid;
624 return wantarray ? ($old_pid, $file_pid) : $old_pid;
628 # Write pid number to pid file.
632 my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
633 if (!defined $pid || $pid !~ /^\d+$/ || $pid < 1) {
634 $pid = defined $pid ? $pid : 'undef';
635 init_error( _message_only('ERR0412', $pid) );
638 open my $pid_handle, '>', $pid_file;
639 print {$pid_handle} $pid . "\n";
643 init_error( _message_only('ERR0409', $pid_file, $EVAL_ERROR) );
650 my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
651 ld_rm_file($pid_file);
655 # Handle error during initialization and exit.
659 if ($DEBUG_LEVEL == 0) {
660 print {*STDERR} $msg . "\n";
662 ld_log( _message('ERR0001', $msg) );
664 ld_exit( 4, _message_only('INF0004') );
668 # If we get a sinal then put a halt flag up
669 sub ld_handler_term {
671 $PROC_STAT{halt} = defined $signal ? $signal : 'undef';
675 # If we get a sinal then put a reload flag up
678 $PROC_STAT{reload} = defined $signal ? $signal : 'undef';
682 # Re-read config, and then re-setup l7vsd and child process.
684 my $old_virtual = defined $CONFIG{virtual} ? [ @{ $CONFIG{virtual} } ]
687 my %old_sub_config = defined $CONFIG{execute} ? %{ $CONFIG{execute} }
692 $CONFIG{old_virtual} = $old_virtual;
694 # analyze config and catch format error
701 my $exception = $EVAL_ERROR;
703 ld_log( _message('ERR0122', $exception) );
704 $CONFIG{virtual} = [ @{ $CONFIG{old_virtual} } ];
705 $CONFIG{execute} = \%old_sub_config;
708 my %new_sub_config = defined $CONFIG{execute} ? %{ $CONFIG{execute} }
711 for my $sub_config ( keys %old_sub_config ) {
712 if ( exists $new_sub_config{$sub_config} ) {
713 if ( system_wrapper($PROC_ENV{l7directord} . " $sub_config reload") ) {
714 system_wrapper($PROC_ENV{l7directord} . " $sub_config start");
716 delete $new_sub_config{$sub_config};
717 delete $old_sub_config{$sub_config};
720 ld_cmd_children('stop', \%old_sub_config);
721 ld_cmd_children('start', \%new_sub_config);
725 # Read configuration and parse settings.
728 my $current_global_name = q{};
732 open $config_handle, '<', $CONFIG_FILE{path};
735 config_error( 0, 'ERR0407', $CONFIG_FILE{path} );
738 while (my $config_line = <$config_handle>) {
741 $config_line =~ s/#.*//mg; # remove comment (FIXME optimize regex for "foo='#'")
742 $config_line =~ s/^\t/$SECTION_VIRTUAL_PREFIX/mg; # convert tab to prefix
744 next if ($config_line =~ /^(?:$SECTION_VIRTUAL_PREFIX)?\s*$/);
747 if ($config_line !~ /^$SECTION_VIRTUAL_PREFIX/) {
748 my ($name, $value) = validate_config($line, $config_line);
749 $current_global_name = $name;
750 if ($name eq 'virtual') {
751 my %virtual = %VIRTUAL;
752 $virtual{server} = $value;
753 push @{ $CONFIG{virtual} }, \%virtual;
754 _ld_service_resolve(\%virtual, $value->{port});
756 elsif ($name eq 'execute') {
757 $CONFIG{execute}{$value} = 1;
760 $CONFIG{$name} = $value;
765 if ($current_global_name ne 'virtual') {
766 config_error($line, 'ERR0119', $config_line);
768 my ($name, $value) = validate_config($line, $config_line);
769 if ($name eq 'real' && defined $value) {
770 push @{ $CONFIG{virtual}[-1]{real} }, @$value;
772 elsif (defined $value) {
773 $CONFIG{virtual}[-1]{$name} = $value;
779 close $config_handle;
782 config_error( 0, 'ERR0408', $CONFIG_FILE{path} );
785 ld_openlog( $CONFIG{logfile} ) if !$PROC_STAT{log_opened};
786 check_require_module();
787 undef $CONFIG_FILE{checksum};
788 undef $CONFIG_FILE{stattime};
791 $PROC_STAT{initialized} = 1;
795 # Validation check of configuration.
796 sub validate_config {
797 my ($line, $config) = @_;
798 my ($name, $value) = split /\s*=\s*/, $config, 2;
799 if (defined $value) {
801 $value =~ s/^("|')(.*)\1$/$2/;
804 # section global validate
805 if ($name !~ /^$SECTION_VIRTUAL_PREFIX/) {
806 if (!exists $GLOBAL{$name}) {
807 config_error($line, 'ERR0120', $config);
809 if ($name eq 'virtual') {
810 $value = ld_gethostservbyname($value, 'tcp');
811 if (!defined $value) {
812 config_error($line, 'ERR0114', $config);
815 elsif ( $name eq 'checktimeout'
816 || $name eq 'negotiatetimeout'
817 || $name eq 'checkinterval'
818 || $name eq 'retryinterval'
819 || $name eq 'configinterval'
820 || $name eq 'checkcount' ) {
821 if (!defined $value || $value !~ /^\d+$/ || $value == 0 ) {
822 config_error($line, 'ERR0101', $config);
825 elsif ( $name eq 'autoreload'
826 || $name eq 'quiescent' ) {
827 $value = defined $value && $value =~ /^yes$/i ? 1
828 : defined $value && $value =~ /^no$/i ? 0
831 if (!defined $value) {
832 config_error($line, 'ERR0102', $config);
835 elsif ($name eq 'fallback') {
836 my $fallback = parse_fallback($line, $value, $config);
837 $value = {tcp => $fallback};
839 elsif ($name eq 'callback') {
840 if (!defined $value || !-f $value || !-x $value) {
841 config_error($line, 'ERR0117', $config);
844 elsif ($name eq 'execute') {
845 if (!defined $value || !-f $value) {
846 config_error($line, 'ERR0116', $config);
849 elsif ($name eq 'logfile') {
850 if (!defined $value || ld_openlog($value) ) {
851 config_error($line, 'ERR0118', $config);
854 elsif ($name eq 'supervised') {
858 # section virtual validate
860 $name =~ s/^$SECTION_VIRTUAL_PREFIX\s*//g;
861 if (!exists $VIRTUAL{$name}) {
862 config_error($line, 'ERR0120', $config);
864 if ($name eq 'real') {
865 $value = parse_real($line, $value, $config);
867 elsif ( $name eq 'request'
868 || $name eq 'receive'
871 || $name eq 'database'
872 || $name eq 'customcheck'
873 || $name eq 'virtualhost' ) {
874 if (!defined $value || $value !~ /^.+$/) {
875 config_error($line, 'ERR0103', $config);
878 elsif ($name eq 'checktype') {
879 my $valid_type = qr{custom|connect|negotiate|ping|off|on|\d+};
881 if (!defined $value || $value !~ /^(?:$valid_type)$/) {
882 config_error($line, 'ERR0104', $config);
884 if ($value =~ /^\d+$/ && $value == 0) {
885 config_error($line, 'ERR0104', $config);
888 elsif ( $name eq 'checktimeout'
889 || $name eq 'negotiatetimeout'
890 || $name eq 'checkinterval'
891 || $name eq 'retryinterval'
892 || $name eq 'checkcount'
893 || $name eq 'maxconn' ) {
894 if (!defined $value || $value !~ /^\d+$/ || ($name ne 'maxconn' && $value == 0) ) {
895 config_error($line, 'ERR0101', $config);
898 elsif ($name eq 'checkport') {
899 if (!defined $value || $value !~ /^\d+$/ || $value == 0 || $value > 65535) {
900 config_error($line, 'ERR0108', $config);
903 elsif ($name eq 'scheduler') {
904 if ( $value =~ /[^a-z]/ ) {
905 config_error($line, 'ERR0105', $config);
908 elsif ($name eq 'protocol') {
910 if (!defined $value || $value !~ /^tcp$/) {
911 config_error($line, 'ERR0109', $config);
914 elsif ($name eq 'service') {
916 my $valid_service = qr{http|https|ldap|ftp|smtp|pop|imap|nntp|dns|mysql|pgsql|sip|none};
917 if (!defined $value || $value !~ /^(?:$valid_service)$/) {
918 config_error($line, 'ERR0106', $config);
921 elsif ($name eq 'httpmethod') {
922 my $valid_method = qr{GET|HEAD};
924 if (!defined $value || $value !~ /^(?:$valid_method)$/) {
925 config_error($line, 'ERR0110', $config);
928 elsif ($name eq 'fallback') {
929 my $fallback = parse_fallback($line, $value, $config);
930 $value = {tcp => $fallback};
932 elsif ( $name eq 'quiescent'
933 || $name eq 'accesslog') {
934 $value = defined $value && $value =~ /^yes$/i ? 1
935 : defined $value && $value =~ /^no$/i ? 0
938 if (!defined $value) {
939 config_error($line, 'ERR0102', $config);
942 elsif ($name eq 'module') {
945 if (defined $value) {
947 ($module, $option) = split /\s+/, $value, 2;
949 if ( $module =~ /[^a-z]/ ) {
950 config_error($line, 'ERR0111', $config);
952 $value = {name => $module, option => $option};
954 elsif ($name eq 'sorryserver') {
955 my $forward = 'masq';
956 if ($value =~ /^(\S+)\s+(\S+)/) {
960 my $sorry_server = ld_gethostservbyname($value, 'tcp');
961 if (!defined $sorry_server) {
962 config_error($line, 'ERR0114', $config);
964 if ($forward && $forward !~ /^(?:masq|tproxy)$/) {
965 config_error($line, 'ERR0107', $config);
967 $sorry_server->{forward} = $forward;
968 $value = $sorry_server;
970 elsif ( $name eq 'qosup'
971 || $name eq 'qosdown' ) {
973 if ( !defined $value || ($value ne '0' && $value !~ /^[1-9]\d{0,2}[KMG]$/) ) {
974 config_error($line, 'ERR0113', $config);
977 elsif ( $name eq 'realdowncallback'
978 || $name eq 'realrecovercallback' ) {
979 if (!defined $value || !-f $value || !-x $value) {
980 config_error($line, 'ERR0117', $config);
983 elsif ( $name eq 'socketoption') {
986 if (!defined $value) {
987 config_error($line, 'ERR0124', $config);
989 my @option_value = split /,/, $value;
990 # OPTION:transparent,deferaccept,nodelay,cork,quickackon|quickackoff
991 for my $option (@option_value) {
993 if($option !~ /^transparent|deferaccept|nodelay|cork|quickackon|quickackoff$/) {
994 config_error($line, 'ERR0124', $config);
998 elsif ($name eq 'sslconfigfile') {
999 if (!defined $value || !-f $value) {
1000 config_error($line, 'ERR0116', $config);
1003 elsif ( $name eq 'accesslogfile') {
1004 if (!defined $value || $value !~ /^\/.*/) {
1005 config_error($line, 'ERR0116', $config);
1009 elsif ($name eq 'accesslog_rotate_type') {
1011 my $valid_rotate_type = qr{date|size|datesize};
1012 if (!defined $value || $value !~ /^(?:$valid_rotate_type)$/) {
1013 config_error($line, 'ERR0124', $config);
1016 elsif ($name eq 'accesslog_rotate_max_backup_index') {
1017 if (!defined $value || $value !~ /^\d+$/ || $value <= 0 || $value >= 13) {
1018 config_error($line, 'ERR0126', $config);
1021 elsif ($name eq 'accesslog_rotate_max_filesize') {
1023 if ( !defined $value || ($value ne '0' && $value !~ /^([1-9]\d{0,2}[KMG]|\d{1,3})$/) ) {
1024 config_error($line, 'ERR0127', $config);
1027 elsif ($name eq 'accesslog_rotate_rotation_timing') {
1029 my $valid_rotation_timing = qr{year|month|week|date|hour};
1030 if (!defined $value || $value !~ /^(?:$valid_rotation_timing)$/) {
1031 config_error($line, 'ERR0128', $config);
1034 elsif ($name eq 'accesslog_rotate_rotation_timing_value') {
1036 $value =~ s/["']//g;
1037 if (!defined $value ) {
1038 config_error($line, 'ERR0129', $config);
1040 if ($value =~ /^\d{1,2}\/\d{1,2}\s\d{1,2}:\d{1,2}$/) {
1041 ## MM/dd hh:mm Check
1044 elsif ($value =~ /^\d{1,2}\s\d{1,2}:\d{1,2}$/) {
1048 elsif ($value =~ /^(sun|mon|tue|wed|thu|fri|sat)\s\d{1,2}:\d{1,2}$/i) {
1049 ## <week> hh:mm Check
1052 elsif ($value =~ /^\d{1,2}:\d{1,2}$/) {
1056 elsif ($value =~ /^\d{1,2}$/) {
1060 if ( !defined $check ) {
1061 config_error($line, 'ERR0129', $config);
1064 elsif ($name eq 'session_thread_pool_size') {
1065 if (!defined $value || $value !~ /^\d+$/ || $value == 0 ) {
1066 config_error($line, 'ERR0101', $config);
1071 return ($name, $value);
1074 # check_require_module
1075 # Check service setting and require module.
1076 sub check_require_module {
1077 my %require_module = (
1078 http => [ qw( LWP::UserAgent LWP::Debug ) ],
1079 https => [ qw( LWP::UserAgent LWP::Debug Crypt::SSLeay ) ],
1080 ftp => [ qw( Net::FTP ) ],
1081 smtp => [ qw( Net::SMTP ) ],
1082 pop => [ qw( Net::POP3 ) ],
1083 imap => [ qw( Mail::IMAPClient ) ],
1084 ldap => [ qw( Net::LDAP ) ],
1085 nntp => [ qw( IO::Socket IO::Select6 ) ],
1086 dns => [ qw( Net::DNS ) ],
1087 mysql => [ qw( DBI DBD::mysql ) ],
1088 pgsql => [ qw( DBI DBD::Pg ) ],
1089 sip => [ qw( IO::Socket::INET IO::Socket::INET6 ) ],
1090 ping => [ qw( Net::Ping ) ],
1091 connect => [ qw( IO::Socket::INET IO::Socket::INET6 ) ],
1094 for my $v ( @{ $CONFIG{virtual} } ) {
1095 next if !defined $v;
1096 next if ( !defined $v->{service} || !defined $v->{checktype} );
1097 my $check_service = q{};
1098 if ( $v->{checktype} eq 'negotiate' && $require_module{ $v->{service} } ) {
1099 $check_service = $v->{service};
1101 elsif ($v->{checktype} eq 'ping' || $v->{checktype} eq 'connect') {
1102 $check_service = $v->{checktype};
1107 for my $module ( @{ $require_module{$check_service} } ) {
1108 my $module_path = $module . '.pm';
1109 $module_path =~ s{::}{/}g;
1111 require $module_path;
1114 config_error(0, 'ERR0123', $module, $check_service);
1120 # _ld_service_resolve
1121 # Set service name from port number
1122 # pre: vsrv: Virtual Service to resolve port
1123 # port: port in the form
1124 # post: If $vsrv->{service} is not set, then set it to "http",
1125 # "https", "ftp", "smtp", "pop", "imap", "ldap", "nntp" or "none"
1126 # if $vsrv->{port} is 80, 443, 21, 25, 110, 143, 389 or
1127 # any other value, respectivley
1129 sub _ld_service_resolve {
1130 my ($vsrv, $port) = @_;
1133 my @p = qw( 80 443 21 25 110 119 143 389 53 3306 5432 5060 );
1134 my @s = qw( http https ftp smtp pop nntp imap ldap dns mysql pgsql sip );
1137 if (defined $vsrv && !defined $vsrv->{service} && defined $port) {
1138 $vsrv->{service} = exists $servname{$port} ? $servname{$port}
1145 # Parse a fallback server
1146 # pre: line: line number fallback server was read from
1147 # fallback: Should be of the form
1148 # ip_address|hostname[:port|:service_name] [masq|tproxy]
1149 # config_line: line read from configuration file
1150 # post: fallback is parsed
1151 # return: Reference to hash of the form
1152 # { server => blah, forward => blah }
1153 # Debugging message will be reported and programme will exit
1155 sub parse_fallback {
1156 my ($line, $fallback, $config_line) = @_;
1158 if (!defined $fallback || $fallback !~ /^(\S+)(?:\s+(\S+))?$/) {
1159 config_error($line, 'ERR0114', $config_line);
1161 my ($ip_port, $forward) = ($1, $2);
1162 $ip_port = ld_gethostservbyname($ip_port, 'tcp');
1163 if ( !defined $ip_port ) {
1164 config_error($line, 'ERR0114', $config_line);
1166 if (defined $forward && $forward !~ /^(?:masq|tproxy)$/i) {
1167 config_error($line, 'ERR0107', $config_line);
1170 my %fallback = %REAL;
1171 $fallback{server} = $ip_port;
1172 $fallback{option}{forward} = get_forward_flag($forward);
1178 # Parse a real server
1179 # pre: line: line number real server was read from
1180 # real: Should be of the form
1181 # ip_address|hostname[:port|:service_name] [masq|tproxy]
1182 # config_line: line read from configuration file
1183 # post: real is parsed
1184 # return: Reference to array include real server hash reference
1185 # [ {server...}, {server...} ... ]
1186 # Debugging message will be reported and programme will exit
1189 my ($line, $real, $config_line) = @_;
1191 my $ip_host = qr{\d+\.\d+\.\d+\.\d+|[a-z0-9.-]+|\[[a-zA-Z0-9:]+\]};
1192 my $port_service = qr{\d+|[a-z0-9-]+};
1195 ($ip_host) # ip or host
1196 (?:->($ip_host))? # range (optional)
1197 (?::($port_service))? # port or service (optional)
1198 (?:\s+([a-z]+))? # forwarding mode (optional)
1199 (?:\s+(\d+))? # weight (optional)
1201 ([^,\s]+) # "request
1202 \s*[ ,]\s* # separater
1206 config_error($line, 'ERR0114', $config_line);
1208 my ($ip1, $ip2, $port, $forward, $weight, $request, $receive)
1209 = ( $1, $2, $3, $4, $5, $6, $7);
1210 # set forward, weight and request-receive pair.
1212 if (defined $forward) {
1213 $forward = lc $forward;
1214 if ($forward !~ /^(?:masq|tproxy)$/) {
1215 config_error($line, 'ERR0107', $config_line);
1217 $real{forward} = $forward;
1219 if (defined $weight) {
1220 $real{weight} = $weight;
1222 if (defined $request && defined $receive) {
1223 $request =~ s/^\s*("|')(.*)\1\s*/$2/;
1224 $receive =~ s/^\s*("|')(.*)\1\s*/$2/;
1225 $real{request} = $request;
1226 $real{receive} = $receive;
1229 my $resolved_port = undef;
1230 if (defined $port) {
1231 $resolved_port = ld_getservbyname($port);
1232 if (!defined $resolved_port) {
1233 config_error($line, 'ERR0108', $config_line);
1237 my $resolved_ip1 = ld_gethostbyname($ip1);
1238 if (!defined $resolved_ip1) {
1239 config_error($line, 'ERR0114', $config_line);
1242 my $resolved_ip2 = $resolved_ip1;
1244 $resolved_ip2 = ld_gethostbyname($ip2);
1245 if (!defined $resolved_ip2) {
1246 config_error($line, 'ERR0114', $config_line);
1250 my ($ip_version , $int_ip1, $int_ip1_prefix ) = ip_to_int($resolved_ip1);
1251 my ($ip_version2, $int_ip2, $int_ip2_prefix ) = ip_to_int($resolved_ip2);
1253 if ( defined $int_ip1 && defined $int_ip2 ) {
1254 if ($int_ip1 > $int_ip2) {
1255 config_error($line, 'ERR0115', $resolved_ip1, $resolved_ip2, $config_line);
1257 elsif ($int_ip1 eq $int_ip2) {
1258 my %new_real = %real;
1259 $new_real{server}{ip } = $resolved_ip1;
1260 $new_real{server}{port} = $resolved_port;
1261 push @reals, \%new_real;
1264 for (my $int_ip = $int_ip1; $int_ip <= $int_ip2; $int_ip++) {
1265 my %new_real = %real;
1266 $new_real{server}{ip } = int_to_ip($ip_version, $int_ip, $int_ip1_prefix);
1267 $new_real{server}{port} = $resolved_port;
1268 push @reals, \%new_real;
1276 # Handle error during read configuration and validation check
1278 my ($line, $msg_code, @msg_args) = @_;
1280 if ($DEBUG_LEVEL > 0 || $PROC_STAT{initialized} == 0) {
1281 my $msg = _message_only($msg_code, @msg_args);
1282 if (defined $line && $line > 0) {
1283 print {*STDERR} _message_only('ERR0121', $CONFIG_FILE{path}, $line, $msg) . "\n";
1286 print {*STDERR} $msg . "\n";
1291 ld_log( _message('ERR0121', $CONFIG_FILE{path}, $line, q{}) );
1293 ld_log( _message($msg_code, @msg_args) );
1295 if ( $PROC_STAT{initialized} == 0 ) {
1296 ld_exit(5, _message_only('ERR0002') );
1299 die "Configuration error.\n";
1304 # Check configuration value and set default value, overwrite global config value and so on.
1306 if ( defined $CONFIG{virtual} ) {
1307 for my $v ( @{ $CONFIG{virtual} } ) {
1308 next if !defined $v;
1309 if (defined $v->{protocol} && $v->{protocol} eq 'tcp') {
1310 $v->{option}{protocol} = "-t";
1313 if ( defined $v->{option} && defined $v->{option}{protocol} && defined $v->{module} && defined $v->{module}{name} ) {
1314 my $module_option = $v->{module}{name};
1315 if ( defined $v->{module}{option} ) {
1316 $module_option .= q{ } . $v->{module}{option};
1318 $v->{option}{main} = sprintf "%s %s -m %s", $v->{option}{protocol}, get_ip_port($v), $module_option;
1319 $v->{option}{flags} = $v->{option}{main};
1320 if ( defined $v->{scheduler} ) {
1321 $v->{option}{flags} .= ' -s ' . $v->{scheduler};
1323 if ( defined $v->{maxconn} ) {
1324 $v->{option}{flags} .= ' -u ' . $v->{maxconn};
1326 if ( defined $v->{sorryserver} && defined $v->{sorryserver}{ip} && defined $v->{sorryserver}{port} ) {
1327 $v->{option}{flags} .= ' -b ' . $v->{sorryserver}{ip} . ':' . $v->{sorryserver}{port};
1329 if ( defined $v->{sorryserver}{forward} ) {
1330 $v->{option}{flags} .= ' ' . get_forward_flag( $v->{sorryserver}{forward} );
1332 if ( defined $v->{qosup} ) {
1333 $v->{option}{flags} .= ' -Q ' . $v->{qosup};
1335 if ( defined $v->{qosdown} ) {
1336 $v->{option}{flags} .= ' -q ' . $v->{qosdown};
1338 if ( defined $v->{sslconfigfile} ) {
1339 $v->{option}{flags} .= ' -z ' . $v->{sslconfigfile};
1340 $v->{other_virtual_key} .= ' ' . $v->{sslconfigfile};
1343 $v->{other_virtual_key} .= ' none';
1345 if ( defined $v->{socketoption} ) {
1346 $v->{option}{flags} .= ' -O ' . $v->{socketoption};
1347 $v->{other_virtual_key} .= ' ' . $v->{socketoption};
1350 $v->{other_virtual_key} .= ' none';
1352 if ( defined $v->{accesslog} ) {
1353 $v->{option}{flags} .= ' -L ' . $v->{accesslog};
1355 if ( defined $v->{accesslogfile} ) {
1356 $v->{option}{flags} .= ' -a ' . $v->{accesslogfile};
1357 $v->{other_virtual_key} .= ' ' . $v->{accesslogfile};
1360 $v->{other_virtual_key} .= ' none';
1362 my $option_key_flag = 0;
1363 if ( defined $v->{accesslog_rotate_type} ) {
1365 .= ' --ac-rotate-type ' . $v->{accesslog_rotate_type};
1366 $v->{other_virtual_key}
1367 .= ' --ac-rotate-type ' . $v->{accesslog_rotate_type};
1368 $option_key_flag = 1;
1370 if ( defined $v->{accesslog_rotate_max_backup_index} ) {
1372 .= ' --ac-rotate-max-backup-index '
1373 . $v->{accesslog_rotate_max_backup_index};
1374 $v->{other_virtual_key}
1375 .= ' --ac-rotate-max-backup-index '
1376 . $v->{accesslog_rotate_max_backup_index};
1377 $option_key_flag = 1;
1379 if ( defined $v->{accesslog_rotate_max_filesize} ) {
1381 .= ' --ac-rotate-max-filesize '
1382 . $v->{accesslog_rotate_max_filesize};
1383 $v->{other_virtual_key}
1384 .= ' --ac-rotate-max-filesize '
1385 . $v->{accesslog_rotate_max_filesize};
1386 $option_key_flag = 1;
1388 if ( defined $v->{accesslog_rotate_rotation_timing} ) {
1390 .= ' --ac-rotate-rotation-timing '
1391 . $v->{accesslog_rotate_rotation_timing};
1392 $v->{other_virtual_key}
1393 .= ' --ac-rotate-rotation-timing '
1394 . $v->{accesslog_rotate_rotation_timing};
1395 $option_key_flag = 1;
1397 if ( defined $v->{accesslog_rotate_rotation_timing_value} ) {
1399 .= ' --ac-rotate-rotation-timing-value '
1400 . q{"}. $v->{accesslog_rotate_rotation_timing_value}. q{"};
1401 $v->{other_virtual_key}
1402 .= ' --ac-rotate-rotation-timing-value '
1403 . $v->{accesslog_rotate_rotation_timing_value};
1404 $option_key_flag = 1;
1406 if ( $option_key_flag == 0 ) {
1407 $v->{other_virtual_key} .= ' none';
1409 if ( defined $v->{session_thread_pool_size} ) {
1410 $v->{option}{flags} .= ' --session-thread-pool-size ' . $v->{session_thread_pool_size};
1414 if ( !defined $v->{fallback} && defined $CONFIG{fallback} ) {
1415 $v->{fallback} = { %{ $CONFIG{fallback} } };
1417 if ( defined $v->{fallback} ) {
1418 for my $proto ( keys %{ $v->{fallback} } ) {
1419 $v->{fallback}{$proto}{option}{flags} = '-r ' . get_ip_port( $v->{fallback}{$proto} )
1420 . ' ' . $v->{fallback}{$proto}{option}{forward};
1423 if (defined $v->{checktype} && $v->{checktype} =~ /^\d+$/) {
1424 $v->{num_connects} = $v->{checktype};
1425 $v->{checktype} = 'combined';
1428 if ( defined $v->{login} && $v->{login} eq q{} ) {
1429 $v->{login} = defined $v->{service} && $v->{service} eq 'ftp' ? 'anonymous'
1430 : defined $v->{service} && $v->{service} eq 'sip' ? 'l7directord@' . $PROC_ENV{hostname}
1434 if ( defined $v->{passwd} && $v->{passwd} eq q{} ) {
1435 $v->{passwd} = defined $v->{service} && $v->{service} eq 'ftp' ? 'l7directord@' . $PROC_ENV{hostname}
1440 if ( defined $v->{real} ) {
1441 for my $r ( @{ $v->{real} } ) {
1442 next if !defined $r;
1443 if ( defined $r->{forward} ) {
1444 $r->{option}{forward} = get_forward_flag( $r->{forward} );
1446 if ( !defined $r->{weight} || $r->{weight} !~ /^\d+$/ ) {
1450 if ( !defined $r->{server}{port} ) {
1451 $r->{server}{port} = $v->{server}{port};
1454 $r->{option}{flags} = '-r ' . get_ip_port($r) . ' ' . $r->{option}{forward};
1457 if ( defined $v->{service} && defined $r->{server} ) {
1458 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
1459 my $ipaddress = $r->{server}{ip};
1460 if ( is_ip6($ipaddress)){
1461 $ipaddress = qq{ [$r->{server}{ip}] };
1463 $r->{url} = sprintf "%s://%s:%s/",
1464 $v->{service}, $ipaddress, $port;
1465 $r->{url} =~ s/\s//g;
1467 if ( !defined $r->{request} && defined $v->{request} ) {
1468 $r->{request} = $v->{request};
1470 if ( !defined $r->{receive} && defined $v->{receive} ) {
1471 $r->{receive} = $v->{receive};
1473 if ( defined $r->{request} ) {
1474 my $uri = $r->{request};
1475 my $service = $v->{service};
1476 if ( defined $v->{service} && $uri =~ m{^$service://} ) {
1485 # set connect count for combine check
1486 if (defined $v->{checktype} && $v->{checktype} eq 'combined') {
1487 $r->{num_connects} = undef;
1490 $r->{fail_counts} = 0;
1491 $r->{healthchecked} = 0;
1494 if ( !defined $v->{checkcount} || $v->{checkcount} <= 0 ) {
1495 $v->{checkcount} = $CONFIG{checkcount};
1497 if ( !defined $v->{checktimeout} || $v->{checktimeout} <= 0 ) {
1498 $v->{checktimeout} = $CONFIG{checktimeout};
1500 if ( !defined $v->{negotiatetimeout} || $v->{negotiatetimeout} <= 0 ) {
1501 $v->{negotiatetimeout} = $CONFIG{negotiatetimeout};
1503 if ( !defined $v->{checkinterval} || $v->{checkinterval} <= 0 ) {
1504 $v->{checkinterval} = $CONFIG{checkinterval};
1506 if ( !defined $v->{retryinterval} || $v->{retryinterval} <= 0 ) {
1507 $v->{retryinterval} = $CONFIG{retryinterval};
1509 if ( !defined $v->{quiescent} ) {
1510 $v->{quiescent} = $CONFIG{quiescent};
1515 if (defined $CONFIG{fallback}) {
1516 $CONFIG{fallback}{tcp}{option}{flags} = '-r ' . get_ip_port( $CONFIG{fallback}{tcp} )
1517 . ' ' . $CONFIG{fallback}{tcp}{option}{forward};
1521 # Removed persistent and netmask related hash entries from the structure of l7vsadm since it is not used - NTT COMWARE
1523 # Parses the output of "l7vsadm -K -n" and puts into a structure of
1524 # the following from:
1527 # (vip_address:vport) protocol module_name module_key_value => {
1528 # "scheduler" => scheduler,
1530 # rip_address:rport => {
1531 # "forward" => forwarding_mechanism,
1532 # "weight" => weight
1541 # vip_address: IP address of virtual service
1542 # vport: Port of virtual service
1543 # module_name: Depicts the name of the module (For example, pfilter)
1544 # module_key_value: Depicts the module key values (For example, --path-match xxxx)
1545 # scheduler: Scheduler for virtual service
1547 # rip_address: IP address of real server
1548 # rport: Port of real server
1549 # forwarding_mechanism: Forwarding mechanism for real server.(masq or tproxy)
1550 # weight: Weight of real server
1553 # post: l7vsadm -K -n is parsed
1554 # result: reference to structure detailed above.
1555 sub ld_read_l7vsadm {
1556 my $current_service = {};
1559 if ( !-f $PROC_ENV{l7vsadm} || !-x $PROC_ENV{l7vsadm} ) {
1560 ld_log( _message( 'FTL0101', $PROC_ENV{l7vsadm} ) );
1561 return $current_service;
1563 # read status of current l7vsadm -K -n
1564 # -K indicates Key parameters of the module included.
1565 my $list_command = $PROC_ENV{l7vsadm} . " -K -n";
1566 my $cmd_result = qx{$list_command};
1567 my @list_line = split /\n/, $cmd_result;
1568 my $other_virtual_flag = 'off';
1569 my $other_virtual_count = 0;
1570 my $other_virtual_option = undef;
1573 # [cf] Layer-7 Virtual Server version 2.0.0-0
1574 # [cf] Prot LocalAddress:Port ProtoMod Scheduler Reschedule Protomod_key_string
1575 # [cf] -> RemoteAddress:Port Forward Weight ActiveConn InactConn
1576 shift @list_line; shift @list_line; shift @list_line;
1578 for my $line (@list_line) {
1579 # check virtual service line format
1580 # [cf] TCP 192.168.0.4:12121 sessionless rr
1581 # TCP [2031:130f:876a::130b]:1231 sessionless rr
1582 #### ((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[0-9a-fA-F:])(%.+)?\]:\d{1,5}) \s+ # ip port
1586 (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}) \s+ # ip port
1587 (\w+) \s+ # protocol module
1596 (\[[0-9a-fA-F:]+(?:%.+)?\]:\d{1,5}) \s+ # ip port
1597 (\w+) \s+ # protocol module
1603 my ($proto, $ip_port, $module) = ($1, $2, $3);
1604 # vip_id MUST be same format as get_virtual_id_str
1606 $vip_id = "$proto:$ip_port:$module";
1607 $vip_id =~ s/\s+$//;
1608 $current_service->{$vip_id} = undef;
1609 $other_virtual_flag = 'on';
1610 $other_virtual_option = undef;
1611 $other_virtual_count = 0;
1614 # check real server line format
1615 # [cf] -> 192.168.0.4:7780 Masq 1 10 123456
1616 if ((defined $vip_id && $line =~ /
1619 (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5}) \s+ # ip port
1622 \d+ \s+ # active connections
1623 \d+ \s* # inactive connections
1627 ||(defined $vip_id && $line =~ /
1630 (\[[0-9a-fA-F:]+(?:%.+)?\]):(\d{1,5}) \s+ # ip port
1633 \d+ \s+ # active connections
1634 \d+ \s* # inactive connections
1638 my ($ip, $port, $forward, $weight) = ($1, $2, $3, $4);
1639 my $ip_port = "$ip:$port";
1641 server => { ip => $ip, port => $port },
1643 forward => $forward,
1645 flags => "-r $ip_port",
1646 forward => get_forward_flag($forward),
1649 $other_virtual_flag = 'off';
1650 $current_service->{$vip_id}{$ip_port} = $real;
1652 elsif ($other_virtual_flag eq 'on'){
1653 ## SSL_config_file value set D->A Command
1654 ## Socket option value set D->A Command
1655 ## Access_log_flag value set E Command
1656 ## Access_log_file value set D->A Command
1657 ## Access_log_rotate option value set D->A Command
1658 if ($other_virtual_count != 2 ) {
1660 $other_virtual_option .= $line;
1661 $current_service->{$vip_id}{other_virtual_option}
1662 = $other_virtual_option;
1664 $other_virtual_count++;
1667 return $current_service;
1670 # ld_operate_virtual
1671 # Operate virtual service on l7vsd by l7vsadm command.
1672 sub ld_operate_virtual {
1673 my ($v, $option, $success_code, $error_code) = @_;
1674 if (!defined $v || !defined $option || !defined $success_code || !defined $error_code) {
1675 ld_log( _message('ERR0501') );
1679 my $command = $PROC_ENV{l7vsadm} . " $option ";
1680 if ($option ne '-D') {
1681 $command .= $v->{option}{flags};
1684 $command .= $v->{option}{main};
1686 $command .= ' 2>&1';
1688 my ($result, $output) = command_wrapper($command);
1690 my $module_key = $v->{module}{name};
1691 if ( defined $v->{module}{key} ) {
1692 $module_key .= q{ } . $v->{module}{key};
1695 ld_log( _message($success_code, get_ip_port($v), $module_key) );
1698 ($output) = split /\n/, $output, 2;
1699 ld_log( _message($error_code, get_ip_port($v), $module_key, $output) );
1704 # Call operate virtual with add option.
1705 sub ld_add_virtual {
1707 ld_operate_virtual($v, '-A', 'INF0201', 'ERR0201');
1711 # Call operate virtual with edit option.
1712 sub ld_edit_virtual {
1714 ld_operate_virtual($v, '-E', 'INF0202', 'ERR0202');
1718 # Call operate virtual with delete option.
1719 sub ld_delete_virtual {
1721 ld_operate_virtual($v, '-D', 'INF0203', 'ERR0203');
1725 # Operate real server on l7vsd by l7vsadm command.
1726 sub ld_operate_real {
1727 my ($v, $r, $weight, $option, $success_code, $error_code) = @_;
1728 if (!defined $v || !defined $r || !defined $option || !defined $success_code || !defined $error_code) {
1729 ld_log( _message('ERR0501') );
1734 = $PROC_ENV{l7vsadm} . " $option " . $v->{option}{main} . q{ } . $r->{option}{flags};
1736 # replace weight value
1737 if (defined $weight) {
1738 $command .= ' -w ' . $weight;
1740 $command .= ' 2>&1';
1742 my ($result, $output) = command_wrapper($command);
1744 my $module_key = $v->{module}{name};
1745 if ( defined $v->{module}{key} ) {
1746 $module_key .= q{ } . $v->{module}{key};
1749 ld_log( _message($success_code, get_ip_port($r), $r->{forward}, get_ip_port($v), $module_key, $weight) );
1752 ($output) = split /\n/, $output, 2;
1753 ld_log( _message($error_code, get_ip_port($r), $r->{forward}, get_ip_port($v), $module_key, $output) );
1758 # Call operate real with add option.
1760 my ($v, $r, $weight) = @_;
1761 ld_operate_real($v, $r, $weight, '-a', 'INF0204', 'ERR0204');
1765 # Call operate real with edit option.
1767 my ($v, $r, $weight) = @_;
1768 ld_operate_real($v, $r, $weight, '-e', 'INF0205', 'ERR0205');
1772 # Call operate real with delete option.
1773 sub ld_delete_real {
1775 ld_operate_real($v, $r, undef, '-d', 'INF0206', 'ERR0206');
1779 # Check l7vsd by l7vsadm command and create virtual service on l7vsd.
1781 # read status of current l7vsadm -K -n
1782 my $current_service = ld_read_l7vsadm();
1783 if (!defined $current_service ) {
1784 ld_log( _message('FTL0201') );
1788 my %old_health_check = %HEALTH_CHECK;
1791 # make sure virtual servers are up to date
1792 if ( defined $CONFIG{virtual} ) {
1793 for my $nv ( @{ $CONFIG{virtual} } ) {
1794 my $vip_id = get_virtual_id_str($nv);
1795 if (!defined $vip_id) {
1796 ld_log( _message('ERR0502') );
1800 if ( exists( $current_service->{$vip_id} )){
1801 if(( defined $current_service->{$vip_id}{other_virtual_option}
1802 && defined $nv->{other_virtual_key})
1803 && $current_service->{$vip_id}{other_virtual_option}
1804 ne $nv->{other_virtual_key} ) {
1805 ld_delete_virtual($nv);
1806 # no such service, create a new one
1807 ld_add_virtual($nv);
1810 # service already exists, modify it
1811 ld_edit_virtual($nv);
1818 for my $check ( keys %{ $current_service } ){
1819 next if !defined $check ;
1820 $del_vip_id = $check;
1821 # protcol name delete
1822 $check =~ s/(^[\w]+:)//;
1823 ## module name delete
1824 $check =~ s/(:[\w]+$)//;
1825 $newipport = get_ip_port($nv);
1826 if ( $check eq $newipport) {
1827 for ( @{ $CONFIG{old_virtual} } ) {
1828 my $virtual_id = get_virtual_id_str($_);
1829 next if !defined $virtual_id ;
1830 if ( $del_vip_id eq $virtual_id ) {
1831 ld_delete_virtual($_);
1832 delete $current_service->{$del_vip_id};
1837 # no such service, create a new one
1838 ld_add_virtual($nv);
1841 my $or = $current_service->{$vip_id} || {};
1843 # Not delete fallback server from l7vsd if exist
1844 my $fallback = fallback_find($nv);
1845 if (defined $fallback) {
1846 my $fallback_ip_port = get_ip_port( $fallback->{ $nv->{protocol} } );
1847 delete $or->{$fallback_ip_port};
1851 if ( defined $nv->{real} ) {
1853 for my $nr ( @{ $nv->{real} } ) {
1854 delete $or->{ get_ip_port($nr) };
1856 my $health_check_id = get_health_check_id_str($nv, $nr);
1857 if (!defined $health_check_id) {
1858 ld_log( _message('ERR0503') );
1862 # search same health check process
1863 if ( exists $HEALTH_CHECK{$health_check_id} ) {
1864 # same health check process exist
1865 # then check real server and virtual service ($r, $v)
1866 for my $v_r_pair ( @{ $HEALTH_CHECK{$health_check_id}{manage} } ) {
1867 # completely same. check next real server
1868 next CHECK_REAL if ($nv eq $v_r_pair->[0] && $nr eq $v_r_pair->[1]);
1871 # add real server and virtual service to management list
1872 push @{ $HEALTH_CHECK{$health_check_id}{manage} }, [$nv, $nr];
1875 # add to health check process list
1876 $HEALTH_CHECK{$health_check_id}{manage} = [ [$nv, $nr] ];
1881 my $work_ip = undef;
1882 # remove remaining entries for real servers
1883 for my $remove_real_ip_port (keys %$or) {
1884 if ( 'other_virtual_option' eq $remove_real_ip_port ){
1887 $work_ip = $or->{$remove_real_ip_port}{server}{ip};
1888 if ( !is_ip ($work_ip)
1889 && !is_ip6($work_ip)){
1892 ld_delete_real( $nv, $or->{$remove_real_ip_port} );
1893 delete $or->{$remove_real_ip_port};
1896 delete $current_service->{$vip_id};
1900 # terminate old health check process
1901 # TODO should compare old and new, and only if different then re-create process...
1902 for my $id (keys %old_health_check) {
1903 # kill old health check process
1904 if ( defined $old_health_check{$id}{pid} ) {
1905 # TODO cannot kill process during pinging to unreachable host?
1907 local $SIG{ALRM} = sub { die; };
1908 kill 15, $old_health_check{$id}{pid};
1911 waitpid $old_health_check{$id}{pid}, 0;
1916 kill 9, $old_health_check{$id}{pid};
1917 waitpid $old_health_check{$id}{pid}, WNOHANG;
1923 # remove remaining entries for virtual servers
1924 if ( defined $CONFIG{old_virtual} ) {
1925 for my $nv ( @{ $CONFIG{old_virtual} } ) {
1926 my $vip_id = get_virtual_id_str($nv);
1927 next if !defined $vip_id ;
1928 if ( exists $current_service->{$vip_id} ) {
1929 # service still exists, remove it
1930 ld_delete_virtual($nv);
1934 delete $CONFIG{old_virtual};
1938 # Run l7directord command to child process.
1939 # Child process is not health check process,
1940 # but sub config (specified by configuration with `execute') process.
1941 sub ld_cmd_children {
1942 my $command_type = shift;
1943 my $execute = shift;
1945 # instantiate other l7directord, if specified
1946 if (!defined $execute) {
1947 if ( defined $CONFIG{execute} ) {
1948 for my $sub_config ( keys %{ $CONFIG{execute} } ) {
1949 if (defined $command_type && defined $sub_config) {
1950 my $command = $PROC_ENV{l7directord} . " $sub_config $command_type";
1951 system_wrapper($command);
1957 for my $sub_config ( keys %$execute ) {
1958 if (defined $command_type && defined $sub_config) {
1959 my $command = $PROC_ENV{l7directord} . " $sub_config $command_type";
1960 system_wrapper($command);
1967 # Remove virtual service for stopping this program.
1969 my $srv = ld_read_l7vsadm();
1970 if (!defined $srv) {
1971 ld_log( _message('FTL0201') );
1974 if ( defined $CONFIG{virtual} ) {
1975 for my $v ( @{ $CONFIG{virtual} } ) {
1976 my $vid = get_virtual_id_str($v);
1977 if (!defined $vid) {
1978 ld_log( _message('ERR0502') );
1981 if ( exists $srv->{$vid} ) {
1982 for my $rid ( keys %{ $srv->{$vid} } ) {
1988 ld_delete_real( $v, $srv->{$vid}{$rid} );
1991 ld_delete_virtual($v);
1997 # Main function of this program.
1998 # Create virtual service and loop below 3 steps.
1999 # 1. Check health check sub process and (re-)create sub process as needed
2000 # 2. Check signal in sleep and start to terminate program or reload config as needed
2001 # 3. Check config file and reload config as needed
2005 # Main failover checking code
2008 # manage real server check process.
2011 my @id_lists = check_child_process();
2012 # if child process is not running
2014 create_check_process(@id_lists);
2016 my $signal = sleep_and_check_signal( $CONFIG{configinterval} );
2017 last MAIN_LOOP if defined $signal && $signal eq 'halt';
2018 last REAL_CHECK if defined $signal && $signal eq 'reload';
2019 last REAL_CHECK if check_cfgfile();
2026 # signal TERM to child process
2027 for my $id (keys %HEALTH_CHECK) {
2028 if ( defined $HEALTH_CHECK{$id}{pid} ) {
2029 # TODO cannot kill process during pinging to unreachable host?
2031 local $SIG{ALRM} = sub { die; };
2032 kill 15, $HEALTH_CHECK{$id}{pid};
2035 waitpid $HEALTH_CHECK{$id}{pid}, 0;
2040 kill 9, $HEALTH_CHECK{$id}{pid};
2041 waitpid $HEALTH_CHECK{$id}{pid}, WNOHANG;
2049 # check_child_process
2050 # Check health check process by signal zero.
2051 # return: Health check id list that (re-)created later.
2052 sub check_child_process {
2053 my @down_process_ids = ();
2054 for my $id (sort keys %HEALTH_CHECK) {
2055 if ( !defined $HEALTH_CHECK{$id}{pid} ) {
2057 ld_log( _message('INF0401', $id) );
2058 push @down_process_ids, $id;
2062 my $signaled = kill 0, $HEALTH_CHECK{$id}{pid};
2063 if ($signaled != 1) {
2064 # maybe killed from outside
2065 ld_log( _message('ERR0603', $HEALTH_CHECK{$id}{pid}, $id) );
2066 push @down_process_ids, $id;
2070 return @down_process_ids;
2073 # create_check_process
2074 # Fork health check sub process.
2075 # And health check sub process run health_check sub function.
2076 sub create_check_process {
2078 for my $health_check_id (@id_lists) {
2081 ld_log( _message('INF0402', $pid, $health_check_id) );
2082 $HEALTH_CHECK{$health_check_id}{pid} = $pid;
2085 $PROC_STAT{parent_pid} = $PROC_STAT{pid};
2086 $PROC_STAT{pid} = $PID;
2087 health_check( $HEALTH_CHECK{$health_check_id}{manage} );
2090 ld_log( _message('ERR0604', $health_check_id) );
2097 # Main function of health check process.
2100 # 2. Status change and reflect to l7vsd as needed.
2101 # 3. Check signal in sleep.
2102 # pre: v_r_list: reference list of virtual service and real server pair
2103 # $v_r_list = [ [$virtual, $real], [$virtual, $real], ... ];
2105 # MUST use POSIX::_exit when terminate sub process.
2107 my $v_r_list = shift;
2108 if (!defined $v_r_list) {
2109 ld_log( _message('ERR0501') );
2110 ld_log( _message('FTL0001') );
2114 # you can use any virtual, real pair in $v_r_list.
2115 my ($v, $r) = @{ $v_r_list->[0] };
2116 if (!defined $v || !defined $r) {
2117 ld_log( _message('FTL0002') );
2121 my $health_check_func = get_check_func($v);
2122 my $current_status = get_status($v_r_list);
2124 my $status = 'STARTING';
2125 my $type = $v->{checktype} eq 'negotiate' ? $v->{service}
2126 : $v->{checktype} eq 'combined' ? $v->{service} . '(combined)'
2129 $PROGRAM_NAME = 'l7directord: ' . $type . ':' . get_ip_port($r) . ':' . $status;
2133 my $service_status = &$health_check_func($v, $r);
2135 if ($service_status == $SERVICE_DOWN) {
2136 undef $r->{num_connects};
2137 if (!defined $current_status || $current_status == $SERVICE_UP) {
2138 $r->{fail_counts}++;
2139 if ($r->{fail_counts} >= $v->{checkcount}) {
2140 ld_log( _message( 'ERR0602', get_ip_port($r) ) );
2141 service_set($v_r_list, 'down');
2142 $current_status = $SERVICE_DOWN;
2144 $r->{fail_counts} = 0;
2147 ld_log( _message( 'WRN1001', get_ip_port($r), $v->{checkcount} - $r->{fail_counts} ) );
2148 $status = sprintf "NG[%d/%d]", $r->{fail_counts}, $v->{checkcount};
2152 if ($service_status == $SERVICE_UP) {
2153 $r->{fail_counts} = 0;
2154 if (!defined $current_status || $current_status == $SERVICE_DOWN) {
2155 ld_log( _message( 'ERR0601', get_ip_port($r) ) );
2156 service_set($v_r_list, 'up');
2157 $current_status = $SERVICE_UP;
2162 $PROGRAM_NAME = 'l7directord: ' . $type . ':' . get_ip_port($r) . ':' . $status;
2164 my $sleeptime = $r->{fail_counts} ? $v->{retryinterval} : $v->{checkinterval};
2165 last if (sleep_and_check_signal($sleeptime, 1) eq 'halt');
2167 my $parent_process = kill 0, $PROC_STAT{parent_pid};
2168 if ($parent_process != 1) {
2169 ld_log( _message( 'FTL0003', $PROC_STAT{parent_pid} ) );
2174 ld_log( _message('INF0007') );
2178 # sleep_and_check_signal
2179 # Check signal flag each 0.1 secound with sleeping specified seconds.
2180 sub sleep_and_check_signal {
2181 my ($sec, $is_child) = @_;
2182 if (!defined $sec || $sec !~ /^\d+$/) {
2183 ld_log( _message('ERR0501') );
2188 while ($sec > $sleeped) {
2189 # non-blocking wait for zombie process
2190 waitpid(-1, WNOHANG); # TODO should move to sigchld handler?
2193 if ( defined $PROC_STAT{halt} ) {
2194 ld_log( _message( 'WRN0001', $CONFIG_FILE{path}, $PROC_STAT{halt} ) );
2199 if ( defined $PROC_STAT{halt} ) {
2200 ld_log( _message( 'WRN0001', $CONFIG_FILE{path}, $PROC_STAT{halt} ) );
2203 if ( defined $PROC_STAT{reload} ) {
2204 ld_log( _message( 'WRN0002', $CONFIG_FILE{path}, $PROC_STAT{reload} ) );
2205 undef $PROC_STAT{reload};
2216 # Determine check function by checktype and service.
2217 sub get_check_func {
2220 ld_log( _message('ERR0501') );
2224 my $type = $v->{checktype};
2225 my $service_func = {
2226 http => \&check_http,
2227 https => \&check_http,
2229 imap => \&check_imap,
2230 smtp => \&check_smtp,
2232 ldap => \&check_ldap,
2233 nntp => \&check_nntp,
2236 mysql => \&check_mysql,
2237 pgsql => \&check_pgsql,
2240 if ( defined $type && ($type eq 'negotiate' || $type eq 'combined') ) {
2241 if (defined $v->{service} && exists $service_func->{ $v->{service} } ) {
2242 my $negotiate_func = $service_func->{ $v->{service} };
2243 if ($type eq 'negotiate') {
2244 return $negotiate_func;
2246 elsif ($type eq 'combined') {
2247 my $combined_func = make_combined_func($negotiate_func);
2248 return $combined_func;
2252 return \&check_none;
2256 if (defined $type && $type eq 'custom') {
2257 my $custom_func = make_custom_func( $v->{customcheck} );
2258 return $custom_func;
2261 if (defined $type && $type eq 'connect') {
2262 if (defined $v->{protocol} && $v->{protocol} eq 'tcp') {
2263 return \&check_connect;
2266 return \&check_ping;
2270 if (defined $type && $type eq 'ping') {
2271 return \&check_ping;
2274 if (defined $type && $type eq 'off') {
2278 if (defined $type && $type eq 'on') {
2282 return \&check_none;
2285 # make_combined_func
2286 # Create combined function.
2287 sub make_combined_func {
2288 my $negotiate_func = shift;
2289 if (!defined $negotiate_func) {
2290 ld_log( _message('ERR0504') );
2291 return \&check_connect;
2295 my $combined_func = sub {
2297 my $timing = $v->{num_connects};
2298 my $connected = $r->{num_connects};
2300 if (!defined $connected ||
2301 (defined $timing && $timing <= $connected) ) {
2302 $r->{num_connects} = 0;
2303 return &$negotiate_func($v, $r);
2306 $r->{num_connects}++;
2307 return check_connect($v, $r);
2311 return $combined_func;
2315 # Create custom check function.
2316 sub make_custom_func {
2317 my $customcheck = shift;
2318 if (!defined $customcheck) {
2319 ld_log( _message('ERR0505') );
2324 my $custom_func = sub {
2326 my $status = get_status([[$v, $r]]);
2327 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2328 my $ip_port = $r->{server}{ip} . ':' . $port;
2331 $customcheck =~ s/_IP_/$r->{server}{ip}/g;
2332 $customcheck =~ s/_PORT_/$port/g;
2336 local $SIG{__DIE__} = 'DEFAULT';
2337 local $SIG{ALRM} = sub { die "custom check timeout\n"; };
2339 alarm $v->{checktimeout};
2340 $res = system_wrapper($customcheck);
2345 ld_log( _message('WRN3301', $v->{checktimeout}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2346 return $SERVICE_DOWN;
2350 ld_log( _message('WRN3302', $customcheck, $res) ) if (!defined $status || $status eq $SERVICE_UP);
2351 return $SERVICE_DOWN;
2353 ld_log( _message('WRN0215', $ip_port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2357 return $custom_func;
2361 # HTTP service health check.
2362 # Send GET/HEAD request, and check response
2364 require LWP::UserAgent;
2366 if ( $DEBUG_LEVEL > 2 ) {
2367 LWP::Debug::level('+');
2370 my $status = get_status([[$v, $r]]);
2372 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2374 if ( $r->{url} !~ m{^https?://([^:/]+)} ) {
2375 ld_log( _message( 'WRN1101', $r->{url}, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2376 return $SERVICE_DOWN;
2379 my $virtualhost = defined $v->{virtualhost} ? $v->{virtualhost} : $host;
2381 ld_debug(2, "check_http: url=\"$r->{url}\" " . "virtualhost=\"$virtualhost\"");
2384 if ( is_ip($r->{server}{ip})){
2385 my $ua = LWP::UserAgent->new( timeout => $v->{negotiatetimeout} );
2386 my $req = new HTTP::Request( $v->{httpmethod}, $r->{url}, [ Host => $virtualhost ] );
2389 # LWP makes ungaurded calls to eval
2390 # which throw a fatal exception if they fail
2391 local $SIG{__DIE__} = 'DEFAULT';
2392 local $SIG{ALRM} = sub { die "Can't connect to $r->{server}{ip}:$port (connect: timeout)\n"; };
2394 alarm $v->{negotiatetimeout};
2395 $res = $ua->request($req);
2401 $status_line = $res->status_line;
2402 $status_line =~ s/[\r\n]//g;
2404 my $response = $v->{httpmethod} eq "HEAD" ? $res->as_string : $res->content;
2405 my $recstr = $r->{receive};
2406 if (!$res->is_success) {
2407 ld_log( _message( 'WRN1102', $status_line, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2408 return $SERVICE_DOWN;
2410 elsif (defined $recstr && $response !~ /$recstr/) {
2411 ld_log( _message( 'WRN1103', $recstr, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2412 ld_debug(3, "HTTP Response " . $response);
2413 ld_debug(2, "check_http: $r->{url} is down\n");
2414 return $SERVICE_DOWN;
2419 ## Wget Comand Check
2420 my $https_option = '';
2421 if ( $v->{service} eq 'https'){
2422 $https_option = '--no-check-certificate';
2424 my $recstr = $r->{receive};
2425 my $command = "/usr/bin/wget " . "-q -t 1 --timeout $v->{negotiatetimeout} $https_option ". $r->{url} . ' -O - ';
2426 my ($result, $output) = command_wrapper( $command );
2428 ld_log( _message( 'WRN1103', 'web', $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2429 return $SERVICE_DOWN;
2431 elsif (defined $recstr && $output !~ /$recstr/) {
2432 ld_log( _message( 'WRN1103', $recstr, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2433 ld_debug(2, "check_http: $r->{url} is down\n");
2434 return $SERVICE_DOWN;
2437 $status_line = '200 OK';
2441 ld_debug(2, "check_http: $r->{url} is up\n");
2442 ld_log( _message( 'WRN0203', $status_line, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2447 # SMTP service health check.
2448 # Connect SMTP server and check first response
2452 my $status = get_status([[$v, $r]]);
2454 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2456 ld_debug(2, "Checking http: server=$r->{server}{ip} port=$port");
2457 my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2459 my $smtp = Net::SMTP->new(
2462 Timeout => $v->{negotiatetimeout},
2463 Debug => $debug_flag,
2466 ld_log( _message('WRN1201', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2467 return $SERVICE_DOWN;
2471 ld_log( _message('WRN0204', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2476 # POP3 service health check.
2477 # Connect POP3 server and login if user-pass specified.
2481 my $status = get_status([[$v, $r]]);
2483 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2485 ld_debug(2, "Checking pop server=$r->{server}{ip} port=$port");
2486 my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2488 my $pop = Net::POP3->new(
2491 Timeout => $v->{negotiatetimeout},
2492 Debug => $debug_flag,
2495 ld_log( _message('WRN1301', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2496 return $SERVICE_DOWN;
2499 if ( defined $v->{login} && defined $v->{passwd} && $v->{login} ne q{} ) {
2500 $pop->user( $v->{login} );
2501 my $num = $pop->pass( $v->{passwd} );
2502 if (!defined $num) {
2503 ld_log( _message('WRN1302', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2505 return $SERVICE_DOWN;
2510 ld_log( _message('WRN0205', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2515 # IMAP service health check.
2516 # Connect IMAP server and login if user-pass specified.
2518 require Mail::IMAPClient;
2520 my $status = get_status([[$v, $r]]);
2522 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2524 ld_debug(2, "Checking imap server=$r->{server}{ip} port=$port");
2525 my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2529 local $SIG{ALRM} = sub { die "Connection timeout\n"; };
2531 alarm $v->{negotiatetimeout};
2532 $imap = Mail::IMAPClient->new(
2533 Server => $r->{server}{ip},
2535 Timeout => $v->{negotiatetimeout},
2536 Debug => $debug_flag,
2542 ld_log( _message('WRN1403', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2543 return $SERVICE_DOWN;
2547 ld_log( _message('WRN1401', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2548 return $SERVICE_DOWN;
2551 if ( defined $v->{login} && defined $v->{passwd} && $v->{login} ne q{} ) {
2552 $imap->User( $v->{login} );
2553 $imap->Password( $v->{passwd} );
2554 my $authres = $imap->login();
2556 ld_log( _message('WRN1402', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2558 return $SERVICE_DOWN;
2563 ld_log( _message('WRN0206', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2568 # LDAP service health check.
2569 # Connect LDAP server and search if base-DN specified by 'request'
2573 my $status = get_status([[$v, $r]]);
2575 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2577 ld_debug(2, "Checking ldap server=$r->{server}{ip} port=$port");
2578 my $debug_flag = $DEBUG_LEVEL ? 15 : 0;
2580 my $ldap = Net::LDAP->new(
2583 timeout => $v->{negotiatetimeout},
2584 debug => $debug_flag,
2587 ld_log( _message('WRN1501', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2588 return $SERVICE_DOWN;
2593 local $SIG{ALRM} = sub { die "Connection timeout\n"; };
2595 alarm $v->{negotiatetimeout};
2596 $mesg = $ldap->bind;
2601 ld_log( _message('WRN1502', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2602 return $SERVICE_DOWN;
2605 if ($mesg->is_error) {
2606 ld_log( _message('WRN1503', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2607 return $SERVICE_DOWN;
2610 if ( defined $r->{request} && $r->{request} ne q{} ) {
2611 ld_debug( 4, "Base : " . $r->{request} );
2612 my $result = $ldap->search(
2613 base => $r->{request},
2615 filter => '(objectClass=*)',
2618 if ($result->count != 1) {
2619 ld_log( _message('WRN1504', $result->count, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2621 return $SERVICE_DOWN;
2624 if ( defined $r->{receive} ) {
2625 my $href = $result->as_struct;
2626 my @arrayOfDNs = keys %$href;
2627 my $recstr = $r->{receive};
2628 if ($recstr =~ /.+/ && $arrayOfDNs[0] !~ /$recstr/) {
2629 ld_log( _message('WRN1505', $recstr, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2631 return $SERVICE_DOWN;
2637 ld_log( _message('WRN0207', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2642 # NNTP service health check.
2643 # Connect NNTP server and check response start with '2**'
2648 my $status = get_status([[$v, $r]]);
2650 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2652 ld_debug(2, "Checking nntp server=$r->{server}{ip} port=$port");
2654 my $sock = ld_open_socket( $r->{server}{ip}, $port, $v->{protocol}, $v->{negotiatetimeout} );
2656 ld_log( _message('WRN1601', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2657 return $SERVICE_DOWN;
2660 ld_debug(3, "Connected to $r->{server}{ip} (port $port)");
2661 my $select = IO::Select->new();
2662 $select->add($sock);
2663 if ( !defined $select->can_read( $v->{negotiatetimeout} ) ) {
2664 ld_log( _message('WRN1602', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2665 $select->remove($sock);
2667 return $SERVICE_DOWN;
2671 sysread $sock, $buf, 64;
2672 $select->remove($sock);
2674 my ($response) = split /[\r\n]/, $buf;
2676 if ($response !~ /^2/) {
2677 ld_log( _message('WRN1603', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2678 return $SERVICE_DOWN;
2681 ld_log( _message('WRN0208', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2686 # MySQL service health check.
2687 # call check_sql and use MySQL driver
2689 return check_sql(@_, 'mysql', 'database');
2693 # PostgreSQL service health check.
2694 # call check_sql and use PostgreSQL driver
2696 return check_sql(@_, 'Pg', 'dbname');
2700 # DBI service health check.
2701 # Login DB and send query if query specified by 'request', check result row number same as 'receive'
2704 my ($v, $r, $dbd, $dbname) = @_;
2705 my $status = get_status([[$v, $r]]);
2707 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2709 if ( !defined $v->{login} || !defined $v->{passwd} || !defined $v->{database} ||
2710 $v->{login} eq q{} || $v->{database} eq q{} ) {
2711 ld_log( _message('WRN1701', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2712 return $SERVICE_DOWN;
2715 ld_debug(2, "Checking $v->{server}{ip} server=$r->{server}{ip} port=$port\n");
2717 my $mask = POSIX::SigSet->new(SIGALRM);
2718 my $action = POSIX::SigAction->new(
2719 sub { die "Connection timeout\n" },
2722 my $oldaction = POSIX::SigAction->new();
2723 sigaction(SIGALRM, $action, $oldaction);
2727 alarm $v->{negotiatetimeout};
2729 DBI->trace(15) if $DEBUG_LEVEL;
2730 $dbh = DBI->connect( "dbi:$dbd:$dbname=$v->{database};host=$r->{server}{ip};port=$port", $v->{login}, $v->{passwd} );
2733 if (!defined $dbh) {
2735 sigaction(SIGALRM, $oldaction);
2736 ld_log( _message('WRN1702', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2740 local $dbh->{TraceLevel} = $DEBUG_LEVEL ? 15 : 0;
2744 if ( defined $r->{request} && $r->{request} ne q{} ) {
2745 my $sth = $dbh->prepare( $r->{request} );
2746 $rows = $sth->execute;
2753 sigaction(SIGALRM, $oldaction);
2755 if ( defined $r->{request} && $r->{request} ne q{} ) {
2756 ld_debug(4, "Database search returned $rows rows");
2758 ld_log( _message('WRN1703', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2761 # If user defined a receive string (number of rows returned), only do
2762 # the check if the previous fetchall_arrayref succeeded.
2763 if (defined $r->{receive} && $r->{receive} =~ /^\d+$/) {
2764 # Receive string specifies an exact number of rows
2765 if ( $rows ne $r->{receive} ) {
2766 ld_log( _message('WRN1704', $r->{receive}, $rows, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2773 sigaction(SIGALRM, $oldaction);
2775 if ($EVAL_ERROR eq "Connection timeout\n") {
2776 ld_log( _message('WRN1705', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2778 return $SERVICE_DOWN;
2781 ld_log( _message('WRN0209', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2786 # Connect service health check.
2787 # Just connect port and close.
2790 my $status = get_status([[$v, $r]]);
2792 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2794 ld_debug(2, "Checking connect: real server=$r->{server}{ip}:$port");
2796 my $sock = ld_open_socket( $r->{server}{ip}, $port, $v->{protocol}, $v->{checktimeout} );
2797 if (!defined $sock) {
2798 ld_log( _message('WRN3201', $ERRNO, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2800 return $SERVICE_DOWN;
2804 ld_debug(3, "Connected to: (port $port)");
2806 ld_log( _message('WRN0210', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2811 # SIP service health check.
2812 # Send SIP OPTIONS request and check 200 response
2815 my $status = get_status([[$v, $r]]);
2817 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2819 ld_debug(2, "Checking sip server=$r->{server}{ip} port=$port");
2821 if ( !defined $v->{login} ) {
2822 ld_log( _message('WRN1801', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2823 return $SERVICE_DOWN;
2826 my $sock = ld_open_socket( $r->{server}{ip}, $port, $v->{protocol}, $v->{negotiatetimeout} );
2827 if (!defined $sock) {
2828 ld_log( _message('WRN1802', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2829 return $SERVICE_DOWN;
2832 my $sip_s_addr = $sock->sockhost;
2833 my $sip_s_port = $sock->sockport;
2835 ld_debug(3, "Connected from $sip_s_addr:$sip_s_port to " . $r->{server} . ":$port");
2837 my $id = $v->{login};
2839 "OPTIONS sip:$id SIP/2.0\r\n"
2840 . "Via: SIP/2.0/UDP $sip_s_addr:$sip_s_port;branch=z9hG4bKhjhs8ass877\r\n"
2841 . "Max-Forwards: 70\r\n"
2842 . "To: <sip:$id>\r\n"
2843 . "From: <sip:$id>;tag=1928301774\r\n"
2844 . "Call-ID: a84b4c76e66710\r\n"
2845 . "CSeq: 63104 OPTIONS\r\n"
2846 . "Contact: <sip:$id>\r\n"
2847 . "Accept: application/sdp\r\n"
2848 . "Content-Length: 0\r\n"
2851 ld_debug(3, "Request:\n$request");
2855 local $SIG{__DIE__} = 'DEFAULT';
2856 local $SIG{ALRM } = sub { die "Connection timeout\n"; };
2857 ld_debug(4, "Timeout is $v->{negotiatetimeout}");
2858 alarm $v->{negotiatetimeout};
2860 print {$sock} $request;
2861 $response = <$sock>;
2865 ld_debug(3, "Response:\n$response");
2867 if ( $response !~ m{^SIP/2\.0 200 OK} ) {
2868 ld_log( _message('WRN1803', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2874 if ($EVAL_ERROR eq "Connection timeout\n") {
2875 ld_log( _message('WRN1804', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2877 return $SERVICE_DOWN;
2880 ld_log( _message('WRN0211', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2885 # FTP service health check.
2886 # Login server and get file if 'request' specified, and check file include 'receive' string
2890 my $status = get_status([[$v, $r]]);
2892 my $ip_port = get_ip_port($r, $v->{checkport});
2894 if (is_ip6($r->{server}{ip}) ){
2896 ## use 'lftp' Command
2898 ## -e ' set net:max-retries 1;
2899 ## set net:reconnect-interval-multiplier 1;
2900 ## set cmd:fail-exit true;
2901 ## set net:reconnect-interval-base 1;
2903 ## -u user,passwd ipv6addr >/dev/null 2>&1
2905 my $ftp_command = "lftp ";
2906 my $ftp_environment1 = "-e \"set net:max-retries 2;";
2907 my $ftp_environment2 = "set net:reconnect-interval-multiplier 1;";
2908 my $ftp_environment3 = "set cmd:fail-exit true;";
2909 my $ftp_environment4 = "set net:reconnect-interval-base $v->{negotiatetimeout};";
2910 my $ftp_environment5 = "ls;ls;exit\" ";
2911 my $ftp_parameter = "-u $v->{login},$v->{passwd} $ip_port >/dev/null 2>&1";
2912 $ftp_command .= $ftp_environment1 . $ftp_environment2. $ftp_environment3. $ftp_environment4. $ftp_environment5. $ftp_parameter;
2914 ## print "ftpCommand:". $ftp_command;
2915 if( system_wrapper( $ftp_command )) {
2916 ld_log( _message('WRN3101', $v->{checktimeout}, $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_UP);
2917 return $SERVICE_DOWN;
2919 return $SERVICE_UP ;
2923 ld_debug(2, "Checking ftp server=$ip_port");
2924 my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2926 if ( !defined $v->{login} || !defined $v->{passwd} || $v->{login} eq q{} ) {
2927 ld_log( _message('WRN1901', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2928 return $SERVICE_DOWN;
2931 my $ftp = Net::FTP->new(
2933 Timeout => $v->{negotiatetimeout},
2935 Debug => $debug_flag,
2937 if (!defined $ftp) {
2938 ld_log( _message('WRN1902', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2939 return $SERVICE_DOWN;
2941 if ( !$ftp->login( $v->{login}, $v->{passwd} ) ) {
2942 ld_log( _message('WRN1903', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2944 return $SERVICE_DOWN;
2946 if ( !$ftp->cwd('/') ) {
2947 ld_log( _message('WRN1904', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2949 return $SERVICE_DOWN;
2951 if ( $r->{request} ) {
2954 local $SIG{__DIE__} = 'DEFAULT';
2955 local $SIG{ALRM } = sub { die "Connection timeout\n"; };
2956 alarm $v->{negotiatetimeout};
2958 open my $tmp, '+>', undef;
2960 if ( !$ftp->get( $r->{request}, *$tmp ) ) {
2962 ld_log( _message('WRN1905', $r->{request}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2967 elsif ( $r->{receive} ) {
2970 my $memory = <$tmp>;
2972 if ($memory !~ /$r->{receive}/) {
2975 ld_log( _message('WRN1906', $r->{receive}, $r->{request}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2983 my $error_message = $EVAL_ERROR;
2984 $error_message =~ s/[\r\n]//g;
2985 if ($error_message eq 'Connection timeout') {
2986 ld_log( _message('WRN1908', $v->{negotiatetimeout}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2989 ld_log( _message('WRN1907', $error_message, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2991 return $SERVICE_DOWN;
2995 return $SERVICE_DOWN;
3000 ld_log( _message('WRN0212', $ip_port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
3005 # DNS service health check.
3006 # Connect server and search 'request' A or PTR record and check result include 'response' string
3009 my $status = get_status([[$v, $r]]);
3011 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
3014 # Net::DNS makes ungaurded calls to eval
3015 # which throw a fatal exception if they fail
3016 local $SIG{__DIE__} = 'DEFAULT';
3019 my $res = Net::DNS::Resolver->new();
3025 if ( !defined $r->{request} || $r->{request} eq q{} || !defined $r->{receive} || $r->{receive} eq q{} ) {
3026 ld_log( _message('WRN2001', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3027 return $SERVICE_DOWN;
3029 ld_debug( 2, qq(Checking dns: request="$r->{request}" receive="$r->{receive}"\n) );
3033 local $SIG{__DIE__} = 'DEFAULT';
3034 local $SIG{ALRM } = sub { die "Connection timeout\n"; };
3035 alarm $v->{negotiatetimeout};
3036 $res->nameservers( $r->{server}{ip} );
3038 $packet = $res->search( $r->{request} );
3043 if ($EVAL_ERROR eq "Connection timeout\n") {
3044 ld_log( _message('WRN2002', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3047 ld_log( _message('WRN2003', $EVAL_ERROR, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3049 return $SERVICE_DOWN;
3052 ld_log( _message('WRN2004', $r->{request}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3053 return $SERVICE_DOWN;
3057 for my $rr ($packet->answer) {
3058 if ( ( $rr->type eq 'A' && $rr->address eq $r->{receive} )
3059 || ( $rr->type eq 'PTR' && $rr->ptrdname eq $r->{receive} ) ) {
3065 ld_log( _message('WRN2005', $r->{receive}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3066 return $SERVICE_DOWN;
3069 ld_log( _message('WRN0213', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
3074 # ICMP ping service health check.
3075 # Ping server and check response.
3079 my $status = get_status([[$v, $r]]);
3081 ld_debug( 2, qq(Checking ping: host="$r->{server}{ip}" checktimeout="$v->{checktimeout}"\n) );
3083 if ( is_ip( $r->{server}{ip})) {
3086 my $p = Net::Ping->new('icmp', 1);
3087 if ( !$p->ping( $r->{server}{ip}, $v->{checktimeout} ) ) {
3088 ld_log( _message('WRN3101', $v->{checktimeout}, $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_UP);
3089 return $SERVICE_DOWN;
3095 = sprintf "ping6 %s -c %d > /dev/null 2>&1",
3099 if( system_wrapper( $command )) {
3100 ld_log( _message('WRN3101', $v->{checktimeout}, $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_UP);
3101 return $SERVICE_DOWN;
3105 ld_log( _message('WRN0214', $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
3110 # Dummy function to check service if service type is none.
3111 # Just activates the real server
3114 ld_debug(2, "Checking none");
3119 # Check nothing and always return $SERVICE_DOWN
3122 return $SERVICE_DOWN;
3126 # Check nothing and always return $SERVICE_UP
3133 # Used to bring up and down real servers.
3134 # This is the function you should call if you want to bring a real
3135 # server up or down.
3136 # This function is safe to call regrdless of the current state of a
3138 # Do _not_ call _service_up or _service_down directly.
3139 # pre: v_r_list: virtual and real pair list
3140 # [ [$v, $r], [$v, $r] ... ]
3142 # up to bring the real service up
3143 # down to bring the real service up
3144 # post: The real server is brough up or down for each virtual service
3148 my ($v_r_list, $state) = @_;
3150 if (defined $state && $state eq 'up') {
3151 _service_up($v_r_list);
3153 elsif (defined $state && $state eq 'down') {
3154 _service_down($v_r_list);
3159 # Bring a real service up if it is down
3160 # Should be called by service_set only
3161 # I.e. If you want to change the state of a real server call service_set.
3162 # If you call this function directly then l7directord will lose track
3163 # of the state of real servers.
3164 # pre: v_r_list: virtual and real pair list
3165 # [ [$v, $r], [$v, $r] ... ]
3166 # post: real service is taken up from the respective virtual service
3170 my $v_r_list = shift;
3171 if ( !_status_up($v_r_list) ) {
3175 for my $v_r_pair (@$v_r_list) {
3176 my ($v, $r) = @$v_r_pair;
3177 _restore_service($v, $r, 'real');
3183 # Bring a real service down if it is up
3184 # Should be called by service_set only
3185 # I.e. if you want to change the state of a real server call service_set.
3186 # If you call this function directly then l7directord will lose track
3187 # of the state of real servers.
3188 # pre: v_r_list: virtual and real pair list
3189 # [ [$v, $r], [$v, $r] ... ]
3190 # post: real service is taken down from the respective virtual service
3194 my $v_r_list = shift;
3195 if ( !_status_down($v_r_list) ) {
3199 for my $v_r_pair (@$v_r_list) {
3200 my ($v, $r) = @$v_r_pair;
3201 _remove_service($v, $r, 'real');
3207 # Set the status of a server as up
3208 # Should only be called from _service_up or fallback_on
3210 my ($v_r_list, $is_fallback) = @_;
3211 if (!defined $v_r_list) {
3215 if (!$is_fallback) {
3216 my $current_status = get_status($v_r_list);
3217 if (defined $current_status && $current_status eq $SERVICE_UP) {
3221 my $id = get_health_check_id_str( @{ $v_r_list->[0] } );
3223 ld_log( _message('ERR0503') );
3226 $HEALTH_CHECK{$id}{status} = $SERVICE_UP;
3231 my $current_service = ld_read_l7vsadm();
3232 if (!defined $current_service) {
3233 ld_log( _message('FTL0201') );
3236 my $vid = get_virtual_id_str( $v_r_list->[0][0] );
3237 if ( exists $current_service->{$vid} ) {
3239 if ( !defined $current_service->{$vid} ) {
3243 # all real server's weight are zero.
3244 for my $real ( keys %{ $current_service->{$vid} } ) {
3245 if ( 'other_virtual_option' eq $real ){
3248 # already added fallback server.
3249 if ( $real eq get_ip_port( $v_r_list->[0][1] ) ) {
3252 $weight += $current_service->{$vid}{$real}{weight};
3263 # Set the status of a server as down
3264 # Should only be called from _service_down or _ld_stop
3266 my ($v_r_list, $is_fallback) = (@_);
3267 if (!defined $v_r_list) {
3271 if (!$is_fallback) {
3272 my $current_status = get_status($v_r_list);
3273 if ($current_status && $current_status eq $SERVICE_DOWN) {
3277 my $id = get_health_check_id_str( @{ $v_r_list->[0] } );
3279 ld_log( _message('ERR0503') );
3282 $HEALTH_CHECK{$id}{status} = $SERVICE_DOWN;
3287 my $current_service = ld_read_l7vsadm();
3288 if (!defined $current_service) {
3289 ld_log( _message('FTL0201') );
3292 my $vid = get_virtual_id_str( $v_r_list->[0][0] );
3293 if ( defined $current_service->{$vid} ) {
3295 my $fallback_exist = 0;
3296 # any real server has weight.
3297 for my $real ( keys %{ $current_service->{$vid} } ) {
3298 if ( 'other_virtual_option' eq $real ){
3301 if ( $real eq get_ip_port( $v_r_list->[0][1] ) ) {
3302 $fallback_exist = 1;
3304 $weight += $current_service->{$vid}{$real}{weight};
3306 if ($fallback_exist && $weight) {
3315 # Get health check server status
3316 # return $SERVICE_UP / $SERVICE_DOWN
3318 my $v_r_list = shift;
3320 my $id = get_health_check_id_str( @{ $v_r_list->[0] } );
3322 ld_log( _message('ERR0503') );
3325 return $HEALTH_CHECK{$id}{status};
3329 # Remove a real server by either making it quiescent or deleteing it
3330 # Should be called by _service_down or fallback_off
3331 # I.e. If you want to change the state of a real server call service_set.
3332 # If you call this function directly then l7directord will lose track
3333 # of the state of real servers.
3334 # If the real server exists (which it should) make it quiescent or
3335 # delete it, depending on the global and per virtual service quiecent flag.
3336 # If it # doesn't exist, just leave it as it will be added by the
3337 # _service_up code as appropriate.
3338 # pre: v: reference to virtual service to with the real server belongs
3339 # rservice: service to restore. Of the form server:port for tcp
3340 # rforw: Forwarding mechanism of service. Should be only "-m"
3341 # rforw is kept as it is, even though not used - NTT COMWARE
3342 # tag: Tag to use for logging. Should be either "real" or "fallback"
3343 # post: real service is taken up from the respective virtual service
3346 sub _remove_service {
3347 my ($v, $r, $tag) = @_;
3348 if (!defined $v || !defined $r) {
3349 ld_log( _message('ERR0501') );
3353 my $vip_id = get_virtual_id_str($v);
3354 if (!defined $vip_id) {
3355 ld_log( _message('ERR0502') );
3358 my $oldsrv = ld_read_l7vsadm();
3359 if (!defined $oldsrv) {
3360 ld_log( _message('FTL0201') );
3364 if ( !exists $oldsrv->{$vip_id} ) {
3365 ld_log( _message( 'ERR0208', get_ip_port($r), get_ip_port($v) ) );
3370 my $is_quiescent = 0;
3371 if (!defined $tag || $tag ne 'fallback') {
3372 if ( defined $v->{quiescent} && $v->{quiescent} ) {
3377 my $or = $oldsrv->{$vip_id}{ get_ip_port($r) };
3378 # already removed server
3379 if (!defined $or && !$is_quiescent) {
3380 my $module_key = $v->{module}{name} . q{ } . $v->{module}{key};
3381 ld_log( _message( 'ERR0210', get_ip_port($r), get_ip_port($v), $module_key ) );
3384 # already quiescent server
3385 if ( defined $or && $is_quiescent && $or->{weight} == 0 &&
3386 $or->{option}{forward} eq $r->{option}{forward} ) {
3387 my $module_key = $v->{module}{name} . q{ } . $v->{module}{key};
3388 ld_log( _message( 'ERR0211', get_ip_port($r), get_ip_port($v), $module_key ) );
3392 if ($is_quiescent) {
3394 ld_edit_real($v, $r, 0);
3397 ld_add_real($v, $r, 0);
3399 if (!defined $tag || $tag eq 'real') {
3400 ld_log( _message( 'INF0303', get_ip_port($r) ) );
3402 elsif ($tag eq 'fallback') {
3403 ld_log( _message( 'INF0304', get_ip_port($r) ) );
3407 ld_delete_real($v, $r);
3408 if (!defined $tag || $tag eq 'real') {
3409 ld_log( _message( 'INF0305', get_ip_port($r) ) );
3411 elsif ($tag eq 'fallback') {
3412 ld_log( _message( 'INF0306', get_ip_port($r) ) );
3416 if ( defined $v->{realdowncallback} && $r->{healthchecked} ) {
3417 system_wrapper( $v->{realdowncallback}, get_ip_port($r) );
3418 ld_log( _message( 'INF0501', $v->{realdowncallback}, get_ip_port($r) ) );
3420 $r->{healthchecked} = 1;
3424 # Make a retore a real server. The opposite of _quiescent_server.
3425 # Should be called by _service_up or fallback_on
3426 # I.e. If you want to change the state of a real server call service_set.
3427 # If you call this function directly then l7directord will lose track
3428 # of the state of real servers.
3429 # If the real server exists (which it should) make it quiescent. If it
3430 # doesn't exist, just leave it as it will be added by the _service_up code
3432 # pre: v: reference to virtual service to with the real server belongs
3433 # r: reference to real server to restore.
3434 # tag: Tag to use for logging. Should be either "real" or "fallback"
3435 # post: real service is taken up from the respective virtual service
3438 sub _restore_service {
3439 my ($v, $r, $tag) = @_;
3440 if (!defined $v || !defined $r) {
3441 ld_log( _message('ERR0501') );
3445 my $vip_id = get_virtual_id_str($v);
3446 if (!defined $vip_id) {
3447 ld_log( _message('ERR0502') );
3450 my $oldsrv = ld_read_l7vsadm();
3451 if (!defined $oldsrv) {
3452 ld_log( _message('FTL0201') );
3456 if ( !exists $oldsrv->{$vip_id} ) {
3457 ld_log( _message( 'ERR0207', get_ip_port($r), get_ip_port($v) ) );
3461 my $or = $oldsrv->{$vip_id}{ get_ip_port($r) };
3462 # already completely same server exist
3464 $or->{weight} eq $r->{weight} &&
3465 $or->{option}{forward} eq $r->{option}{forward} ) {
3466 my $module_key = $v->{module}{name} . q{ } . $v->{module}{key};
3467 ld_log( _message( 'ERR0209', get_ip_port($r), get_ip_port($v), $module_key ) );
3472 ld_edit_real( $v, $r, $r->{weight} );
3475 ld_add_real( $v, $r, $r->{weight} );
3478 if (!defined $tag || $tag eq 'real') {
3479 ld_log( _message( 'INF0301', get_ip_port($r) ) );
3481 elsif ($tag eq 'fallback') {
3482 ld_log( _message( 'INF0302', get_ip_port($r) ) );
3485 if ( defined $v->{realrecovercallback} && $r->{healthchecked} ){
3486 system_wrapper( $v->{realrecovercallback}, get_ip_port($r) );
3487 ld_log( _message( 'INF0502', $v->{realrecovercallback}, get_ip_port($r) ) );
3489 $r->{healthchecked} = 1;
3493 # Turn on the fallback server for a virtual service if it is inactive
3494 # pre: v: virtual to turn fallback service on for
3495 # post: fallback server is turned on if it was inactive
3500 my $fallback = fallback_find($v);
3501 if (defined $fallback) {
3502 my $v_r_list = [ [ $v, $fallback->{tcp} ] ];
3503 if ( _status_up($v_r_list, 'fallback') ) {
3504 _restore_service($v, $fallback->{tcp}, 'fallback');
3510 # Turn off the fallback server for a virtual service if it is active
3511 # pre: v: virtual to turn fallback service off for
3512 # post: fallback server is turned off if it was active
3517 my $fallback = fallback_find($v);
3518 if (defined $fallback) {
3519 my $v_r_list = [ [ $v, $fallback->{tcp} ] ];
3520 if ( _status_down($v_r_list, 'fallback') ) {
3521 _remove_service($v, $fallback->{tcp}, 'fallback');
3527 # Determine the fallback for a virtual service
3528 # pre: v: reference to a virtual service
3530 # return: $v->{fallback} if defined
3535 ld_log( _message('ERR0501') );
3538 return $v->{fallback};
3542 # Check configfile change.
3544 # post: check configfile size, and then check md5 sum
3545 # return: 1 if notice file change
3546 # 0 if not notice or not change
3548 if (!defined $CONFIG_FILE{path}) {
3549 ld_log( _message('FTL0102') );
3553 my $mtime = (stat $CONFIG_FILE{path})[9];
3554 if (!defined $mtime) {
3555 ld_log( _message( 'ERR0410', $CONFIG_FILE{path} ) );
3559 if ( defined $CONFIG_FILE{stattime} && $mtime == $CONFIG_FILE{stattime} ) {
3560 # file mtime is not change
3563 $CONFIG_FILE{stattime} = $mtime;
3565 my $digest = undef;;
3567 require Digest::MD5;
3569 my $ctx = Digest::MD5->new();
3570 open my $config, '<', $CONFIG_FILE{path};
3571 $ctx->addfile($config);
3572 $digest = $ctx->hexdigest;
3576 ld_log( _message( 'ERR0407', $CONFIG_FILE{path} ) );
3580 if (defined $CONFIG_FILE{checksum} && $digest &&
3581 $CONFIG_FILE{checksum} ne $digest ) {
3582 ld_log( _message('WRN0101', $CONFIG_FILE{path}) );
3583 $CONFIG_FILE{checksum} = $digest;
3585 if ( defined $CONFIG{callback} && -x $CONFIG{callback} ) {
3586 system_wrapper( $CONFIG{callback} . q{ } . $CONFIG_FILE{path} );
3587 ld_log( _message( 'INF0503', $CONFIG{callback}, $CONFIG_FILE{path} ) );
3590 if ( $CONFIG{autoreload} ) {
3591 ld_log( _message('WRN0102') );
3595 ld_log( _message('WRN0103') );
3600 $CONFIG_FILE{checksum} = $digest;
3606 # make log rotation work
3608 # post: If logger is a file, it opened and closed again as a test
3609 # If logger is syslog, it is opened so it can be used without
3610 # needing to be opened again.
3611 # Otherwiese, nothing is done.
3612 # return: 0 on success
3615 my $log_config = shift;
3616 if (!defined $log_config) {
3617 ld_log( _message('ERR0501') );
3621 if ( $DEBUG_LEVEL > 0 or $CONFIG{supervised} ) {
3622 # Instantly do nothing
3626 if ( $log_config =~ m{^/}) {
3627 # Open and close the file as a test.
3628 # We open the file each time we want to log to it
3630 open my $log_file, ">>", $log_config;
3634 ld_log( _message('ERR0118', $log_config) );
3639 # Assume $log_config is a logfacility, log to syslog
3641 openlog("l7directord", "pid", $log_config);
3642 # FIXME "closelog" not found
3645 $PROC_STAT{log_opened} = 1;
3651 # pre: message: Message to write
3652 # post: message and timetsamp is written to loged
3653 # If logger is a file, it is opened and closed again as a
3654 # primative means to make log rotation work
3655 # return: 0 on success
3658 my $message = shift;
3659 if (!defined $message) {
3660 ld_log( _message('ERR0501') );
3664 ld_debug(2, $message);
3667 if ( !$CONFIG{supervised} && !$PROC_STAT{log_opened} ) {
3671 my $now = localtime();
3672 my $line_header = sprintf "[%s|%d] ", $now, $PROC_STAT{pid};
3673 $message =~ s/^/$line_header/mg;
3675 if ( $CONFIG{supervised} ) {
3676 print {*STDOUT} $message . "\n";
3678 elsif ( $CONFIG{logfile} =~ m{^/} ) {
3680 open my $log_file, '>>', $CONFIG{logfile};
3681 flock $log_file, 2; # LOCK_EX
3682 print {$log_file} $message . "\n";
3686 print {*STDERR} _message_only( 'FTL0103', $CONFIG{logfile}, $message ) . "\n";
3691 # Assume LOGFILE is a logfacility, log to syslog
3692 syslog('info', $message);
3698 # Log a message to a STDOUT.
3699 # pre: priority: priority of message
3700 # message: Message to write
3701 # post: message is written to STDOUT if $DEBUG_LEVEL >= priority
3704 my ($priority, $message) = @_;
3706 if (defined $priority && $priority =~ /^\d+$/ &&
3707 defined $message && $DEBUG_LEVEL >= $priority) {
3709 $message =~ s/^/DEBUG[$priority]: /mg;
3710 print {*STDERR} $message . "\n";
3715 # Wrapper around command(qx) to get output
3716 # pre: command to execute
3717 # post: execute command and if it returns non-zero a failure
3719 # return: return value of command, and output
3720 sub command_wrapper {
3721 my $command = shift;
3723 if ($DEBUG_LEVEL > 2) {
3724 ld_log( _message( 'INF0506', $command) );
3727 $command =~ s/([{}\\])/\\$1/g;
3728 my $output = qx($command);
3729 if ($CHILD_ERROR != 0) {
3730 ld_log( _message('ERR0303', $command, $CHILD_ERROR) );
3732 return ($CHILD_ERROR, $output);
3736 # Wrapper around system() to log errors
3737 # pre: LIST: arguments to pass to system()
3738 # post: system() is called and if it returns non-zero a failure
3740 # return: return value of system()
3741 sub system_wrapper {
3744 if ($DEBUG_LEVEL > 2) {
3745 ld_log( _message( 'INF0504', join(q{ }, @args) ) );
3747 my $status = system(@args);
3748 if ($DEBUG_LEVEL > 2) {
3750 ld_log( _message('ERR0301', join(q{ }, @args), $status) );
3757 # Wrapper around exec() to log errors
3758 # pre: LIST: arguments to pass to exec()
3759 # post: exec() is called and if it returns non-zero a failure
3761 # return: return value of exec() on failure
3762 # does not return on success
3766 if ($DEBUG_LEVEL > 2) {
3767 ld_log( _message( 'INF0505', join(q{ }, @args) ) );
3769 my $status = exec(@args);
3771 ld_log( _message('ERR0302', join(q{ }, @args), $status) );
3777 # Remove a file, symink, or anything that isn't a directory
3779 # pre: filename: file to delete
3780 # post: If filename does not exist or is a directory an
3781 # error state is reached
3782 # Else filename is delete
3783 # If $DEBUG_LEVEL >=2 errors are logged
3784 # return: 0 on success
3787 my $filename = shift;
3788 if (!defined $filename) {
3789 ld_log( _message('ERR0411') );
3793 ld_log( _message('ERR0401', $filename) );
3796 if (!-e $filename) {
3797 ld_log( _message('ERR0402', $filename) );
3800 my $status = unlink $filename;
3802 ld_log( _message('ERR0403', $filename, $ERRNO) );
3809 # See if a number is an octet, that is >=0 and <=255
3810 # pre: alleged_octet: the octect to test
3811 # post: alleged_octect is checked to see if it is valid
3812 # return: 1 if the alleged_octet is an octet
3815 my $alleged_octet = shift;
3816 if (!defined $alleged_octet || $alleged_octet !~ /^\d+$/ || $alleged_octet > 255) {
3817 ld_log( _message('ERR0501') );
3824 # Check that a given string is an IP address
3825 # pre: alleged_ip: string representing ip address
3826 # post: alleged_ip is checked to see if it is valid
3827 # return: 1 if alleged_ip is a valid ip address
3830 my $alleged_ip = shift;
3832 # If we don't have four, . delimited numbers then we have no hope
3833 if (!defined $alleged_ip || $alleged_ip !~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) {
3834 ## ld_log( _message('ERR0501') );
3838 # Each octet must be >=0 and <=255
3839 is_octet($1) or return 0;
3840 is_octet($2) or return 0;
3841 is_octet($3) or return 0;
3842 is_octet($4) or return 0;
3848 # Check that a given string is an IPv6 address
3849 # pre: alleged_ip6: string representing ip address
3850 # post: alleged_ip6 is checked to see if it is valid
3851 # return: 1 if alleged_ip is a valid ipv6 address
3854 my $alleged_ip = shift;
3855 my @return_array = (0, undef);
3857 if (!defined $alleged_ip ) {
3858 ld_log( _message('ERR0501') );
3862 ## Change IPv6 Address
3863 $alleged_ip =~ s/[\[\]]//g;
3865 my ($work, $link_local) = split /%/, $alleged_ip;
3867 if ( $alleged_ip =~ /::/ ){
3868 my ($adr_a, $adr_b) = split /::/, $alleged_ip;
3869 my @adr_a = split /:/ , $adr_a;
3870 my @adr_b = split /:/ , $adr_b;
3871 for(scalar @adr_a .. 7 - scalar @adr_b){
3874 @address = (@adr_a, @adr_b);
3877 @address = split /:/, $alleged_ip;
3879 $alleged_ip = join ":", @address;
3880 if ( defined $link_local ){
3881 $alleged_ip .= '%' . $link_local;
3883 if (!defined $alleged_ip ||
3884 $alleged_ip !~ m/^([0-9a-fA-F]{1,4}):
3891 ([0-9a-fA-F]{1,4})(%.+)?$/x) {
3894 @return_array = (1, @address);
3895 return @return_array;
3900 # Turn an IP address given as a dotted quad into an integer
3901 # pre: ip_address: string representing IP address
3902 # post: post ip_address is converted to an integer
3903 # return: -1 if an error occurs
3904 # integer representation of IP address otherwise
3906 my $ip_address = shift;
3907 my $ip_version = 'ipv4';
3909 my $result2 = undef;
3910 my @return_array = (undef, -1);
3913 if ( is_ip($ip_address) ) {
3914 my ($oct1, $oct2, $oct3, $oct4)
3915 = $ip_address =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
3916 $result = ($oct1 << 24) + ($oct2 << 16) + ($oct3 << 8) + $oct4;
3919 my ( $ret, @address ) = is_ip6($ip_address);
3921 my ( $hex1, $hex2, $hex3, $hex4, $hex5, $hex6, $hex7, $hex8, $linklocal) = @address;
3922 $result = (hex($hex5) << 48) + (hex($hex6) << 32) + (hex($hex7) << 16) + hex($hex8);
3923 $result2 = (hex($hex1) << 48) + (hex($hex2) << 32) + (hex($hex3) << 16) + hex($hex4);
3924 $ip_version = 'ipv6';
3927 return @return_array;
3930 @return_array = ($ip_version, $result, $result2);
3931 return @return_array;
3935 # Turn an IP address given as an integer into a dotted quad
3936 # pre: ip_address: integer representation of IP address
3937 # post: Decimal is converted to a dotted quad
3938 # return: string representing IP address
3940 my ($ip_version, $ip_address,$ip_address2) = @_;
3941 if (!defined $ip_address || $ip_address !~ /^\d+$/ ) {
3942 ##|| !defined $ip_version || $ip_version !~ /ipv[46]/ ) {
3943 ld_log( _message('ERR0501') );
3948 if ($ip_version eq 'ipv6') {
3949 ## IPv6 Address Change
3950 $result = sprintf "%0x:%0x:%0x:%0x:%0x:%0x:%0x:%0x",
3951 ($ip_address2 >> 48) & 0xffff,
3952 ($ip_address2 >> 32) & 0xffff,
3953 ($ip_address2 >> 16) & 0xffff,
3954 ($ip_address2 ) & 0xffff,
3955 ($ip_address >> 48) & 0xffff,
3956 ($ip_address >> 32) & 0xffff,
3957 ($ip_address >> 16) & 0xffff,
3958 ($ip_address ) & 0xffff;
3961 ## IPv4 Address Change
3962 $result = sprintf "%d.%d.%d.%d",
3963 ($ip_address >> 24) & 0xff,
3964 ($ip_address >> 16) & 0xff,
3965 ($ip_address >> 8 ) & 0xff,
3966 ($ip_address ) & 0xff;
3972 # Get the service for a virtual or a real
3973 # pre: host: virtual or real to get the service for
3975 # return: ip_address:port
3977 my ($host, $checkport) = @_;
3978 my $server = defined $host && defined $host->{server} && defined $host->{server}{ip}
3979 ? $host->{server}{ip } : q{};
3980 if (is_ip6($server)) {
3981 $server = sprintf "[%s]" , $server;
3983 my $port = defined $checkport ? $checkport
3984 : defined $host && defined $host->{server} && defined $host->{server}{port}
3985 ? $host->{server}{port} : q{};
3987 my $ip_port = $server ne q{} && $port ne q{} ? "$server:$port" : q{};
3991 # get_health_check_id_str
3992 # Get an id string for a health check process
3993 # pre: r: Real service.
3994 # v: Virtual service
3996 # return: Id string for the health check process
3997 sub get_health_check_id_str {
3999 if ( !defined $v || !defined $r || !defined $r->{server} ) {
4000 ld_log( _message('ERR0501') );
4004 my $ip = defined $r->{server}{ip } ? $r->{server}{ip } : q{};
4005 my $port = defined $v->{checkport } ? $v->{checkport } :
4006 defined $r->{server}{port} ? $r->{server}{port} : q{};
4007 my $checktype = defined $v->{checktype } ? $v->{checktype } : q{};
4008 my $service = defined $v->{service } ? $v->{service } : q{};
4009 my $protocol = defined $v->{protocol } ? $v->{protocol } : q{};
4010 my $num_connects = defined $v->{num_connects} ? $v->{num_connects} : q{};
4011 my $request = defined $r->{request } ? $r->{request } : q{};
4012 my $receive = defined $r->{receive } ? $r->{receive } : q{};
4013 my $httpmethod = defined $v->{httpmethod } ? $v->{httpmethod } : q{};
4014 my $virtualhost = defined $v->{virtualhost } ? $v->{virtualhost } : q{};
4015 my $login = defined $v->{login } ? $v->{login } : q{};
4016 my $password = defined $v->{passwd } ? $v->{passwd } : q{};
4017 my $database = defined $v->{database } ? $v->{database } : q{};
4019 my $customcheck = defined $v->{customcheck } ? $v->{customcheck } : q{};
4020 my $checkinterval = defined $v->{checkinterval } ? $v->{checkinterval } : q{};
4021 my $checkcount = defined $v->{checkcount } ? $v->{checkcount } : q{};
4022 my $checktimeout = defined $v->{checktimeout } ? $v->{checktimeout } : q{};
4023 my $negotiatetimeout = defined $v->{negotiatetimeout } ? $v->{negotiatetimeout } : q{};
4024 my $retryinterval = defined $v->{retryinterval } ? $v->{retryinterval } : q{};
4026 # FIXME SHOULD change separator. (request, receive, login, passwd ,database may include ':')
4027 my $id = "$ip:$port:$checktype:$service:$protocol:$num_connects:$request:$receive:" .
4028 "$httpmethod:$virtualhost:$login:$password:$database:$customcheck:" .
4029 "$checkinterval:$checkcount:$checktimeout:$negotiatetimeout:$retryinterval";
4034 # get_virtual_id_str
4035 # Get an id string for a virtual service
4036 # pre: v: Virtual service
4038 # return: Id string for the virtual service
4039 sub get_virtual_id_str {
4041 if ( !defined $v || !defined $v->{module} ) {
4042 ld_log( _message('ERR0501') );
4046 my $ip_port = get_ip_port($v);
4047 my $protocol = defined $v->{protocol } ? $v->{protocol } : q{};
4048 my $module_name = defined $v->{module}{name} ? $v->{module}{name} : q{};
4049 my $module_key = defined $v->{module}{key } ? $v->{module}{key } : q{};
4051 my $id = "$protocol:$ip_port:$module_name $module_key";
4055 # [cf] id = "tcp:127.0.0.1:80:cinsert --cookie-name 'monkey'"
4059 # Get the l7vsadm flag corresponging to a forwarding mechanism
4060 # pre: forward: Name of forwarding mechanism. (masq or tproxy)
4062 # return: l7vsadm flag corresponding to the forwading mechanism
4063 # " " if $forward is unknown
4064 sub get_forward_flag {
4065 my $forward = shift;
4067 if (defined $forward && $forward =~ /^masq$/i) {
4070 elsif (defined $forward && $forward =~ /^tproxy$/i) {
4077 # Exit and log a message
4078 # pre: exit_status: Integer exit status to exit with
4079 # 0 wiil be used if parameter is omitted
4080 # message: Message to log when exiting. May be omitted
4081 # post: If exit_status is non-zero or $DEBUG_LEVEL>2 then
4083 # Programme exits with exit_status
4084 # return: does not return
4086 my ($exit_status, $message) = @_;
4087 if (defined $exit_status && defined $message) {
4088 ld_log( _message('INF0006', $exit_status, $message) );
4094 # Open a socket connection
4095 # pre: remote: IP address as a dotted quad of remote host to connect to
4096 # port: port to connect to
4097 # protocol: Prococol to use. Should be either "tcp" or "udp"
4098 # post: A Socket connection is opened to the remote host
4099 # return: Open socket
4100 sub ld_open_socket {
4101 require IO::Socket::INET6;
4102 my ($remote, $port, $protocol, $timeout) = @_;
4103 my $sock_handle = IO::Socket::INET6->new(
4104 PeerAddr => $remote,
4107 Timeout => $timeout,
4110 return $sock_handle;
4114 # Close and fork to become a daemon.
4116 # Notes from unix programmer faq
4117 # http://www.landfield.com/faqs/unix-faq/programmer/faq/
4119 # Almost none of this is necessary (or advisable) if your daemon is being
4120 # started by `inetd'. In that case, stdin, stdout and stderr are all set up
4121 # for you to refer to the network connection, and the `fork()'s and session
4122 # manipulation should *not* be done (to avoid confusing `inetd'). Only the
4123 # `chdir()' step remains useful.
4125 ld_daemon_become_child();
4127 if (POSIX::setsid() < 0) {
4128 ld_exit( 7, _message_only('ERR0702') );
4131 ld_daemon_become_child();
4133 if (chdir('/') < 0) {
4134 ld_exit( 8, _message_only('ERR0703') );
4141 eval { open *STDIN, '<', '/dev/null'; };
4142 ld_exit(9, _message_only('ERR0704') ) if ($EVAL_ERROR);
4143 eval { open *STDOUT, '>>', '/dev/console'; };
4144 ld_exit(10, _message_only('ERR0705') ) if ($EVAL_ERROR);
4145 eval { open *STDERR, '>>', '/dev/console'; };
4146 ld_exit(10, _message_only('ERR0705') ) if ($EVAL_ERROR);
4149 # ld_daemon_become_child
4150 # Fork, kill parent and return child process
4152 # post: process forkes and parent exits
4153 # All preocess exit with exit status -1 if an error occurs
4154 # return: parent: exits
4155 # child: none (this is the process that returns)
4156 sub ld_daemon_become_child {
4157 my $status = fork();
4158 $PROC_STAT{pid} = $PID;
4161 ld_exit( 6, _message_only('ERR0701', $ERRNO) );
4164 ld_exit( 0, _message_only('INF0005') );
4169 # Wrapper to gethostbyname. Look up the/an IP address of a hostname
4170 # If an IP address is given is it returned
4171 # pre: name: Hostname of IP address to lookup
4172 # post: gethostbyname is called to find an IP address for $name
4173 # This is converted to a string
4174 # return: IP address
4176 sub ld_gethostbyname {
4177 require IO::Socket::INET6;
4179 $name = q{} if !defined $name;
4180 my $addrs = ( gethostbyname($name) )[4];
4181 if ( defined $addrs && $addrs ){
4182 return Socket::inet_ntoa($addrs);
4185 $name =~ s/\[|\]//g;
4186 my $addrs = ( gethostbyname2($name, AF_INET6) )[4] or return;
4187 return inet_ntop(AF_INET6,$addrs);
4193 # Wraper for getservbyname. Look up the port for a service name
4194 # If a port is given it is returned.
4195 # pre: name: Port or Service name to look up
4196 # post: if $name is a number
4197 # if 0<=$name<=65536 $name is returned
4198 # else undef is returned
4199 # else getservbyname is called to look up the port for the service
4202 sub ld_getservbyname {
4203 my ($name, $protocol) = @_;
4204 $name = q{} if !defined $name;
4205 $protocol = q{} if !defined $protocol;
4207 if ($name =~ /^\d+$/) {
4208 if ($name > 65535) {
4214 my $port = ( getservbyname($name, $protocol) )[2];
4218 # ld_gethostservbyname
4219 # Wraper for ld_gethostbyname and ld_getservbyname. Given a server of the
4220 # form ip_address|hostname:port|servicename return hash refs of ip_address and port
4221 # pre: hostserv: Servver of the form ip_address|hostname:port|servicename
4222 # protocol: Protocol for service. Should be either "tcp" or "udp"
4223 # post: lookups performed as per ld_getservbyname and ld_gethostbyname
4224 # return: { ip => ip_address, port => port }
4226 sub ld_gethostservbyname {
4227 my ($hostserv, $protocol) = @_;
4231 if (!defined $hostserv || $hostserv !~ /
4233 (\d+\.\d+\.\d+\.\d+|[a-z0-9.-]+) # host or ip
4235 (\d+|[a-z0-9-]+) # serv or port
4238 if ( !defined $hostserv || $hostserv !~ /
4240 (\[[a-z0-9.-:%]+\]) # host or ip
4242 (\d+|[a-z0-9-]+) # serv or port
4257 $ip = ld_gethostbyname($ip) or return;
4258 $port = ld_getservbyname($port, $protocol);
4260 return if !defined $port;
4262 return {ip => $ip, port => $port};
4266 # Create message only.
4268 my ($code, @message_args) = @_;
4270 my $message_list = {
4271 # health check process exit
4272 FTL0001 => "health_check argument is invalid. Exit this monitor process with status: 1",
4273 FTL0002 => "health_check argument pair, virtual or real structure is invalid. Exit this monitor process with status: 2",
4274 FTL0003 => "Detected down management process (pid: %s). Exit this monitor process with status: 3",
4276 FTL0101 => "l7vsadm file `%s' is not found or cannot execute.",
4277 FTL0102 => "Config file is not defined. So cannot check configuration change.",
4278 FTL0103 => "Cannot open logfile `%s'. Log message: `%s'",
4279 # command fatal error
4280 FTL0201 => "Result of read from l7vsadm is not defined.",
4283 ERR0001 => "Initialization error: %s",
4284 ERR0002 => "Configuration error and exit.",
4286 ERR0101 => "Invalid value (set natural number) `%s'.",
4287 ERR0102 => "Invalid value (set `yes' or `no') `%s'.",
4288 ERR0103 => "Invalid value (set any word) `%s'.",
4289 ERR0104 => "Invalid value (set `custom', `connect', `negotiate', `ping', `off', `on' "
4290 . "or positive number) `%s'.",
4291 ERR0105 => "Invalid schedule module (should be only lowercase letters (a-z)) `%s'.",
4292 ERR0106 => "Invalid value (set `http', `https', `ftp', `smtp', `pop', `imap', "
4293 . "`ldap', `nntp', `dns', `mysql', `pgsql', `sip', or `none') `%s'.",
4294 ERR0107 => "Invalid value (forwarding mode must be `masq' or `tproxy') `%s'.",
4295 ERR0108 => "Invalid port number `%s'.",
4296 ERR0109 => "Invalid protocol (protocol must be `tcp') `%s'.",
4297 ERR0110 => "Invalid HTTP method (set `GET' or `HEAD') `%s'.",
4298 ERR0111 => "Invalid protocol module (should be only lowercase letters (a-z)) `%s'.",
4299 ERR0112 => "Invalid module key option (`%s' module must set `%s' option) `%s'.",
4300 ERR0113 => "Invalid QoS value (set 0 or 1-999[KMG]. must specify unit(KMG)) `%s'.",
4301 ERR0114 => "Invalid address `%s'.",
4302 ERR0115 => "Invalid address range (first value(%s) must be less than or equal to the second value(%s)) `%s'.",
4303 ERR0116 => "File not found `%s'.",
4304 ERR0117 => "File not found or cannot execute `%s'.",
4305 ERR0118 => "Unable to open logfile `%s'.",
4306 ERR0119 => "Virtual section not found for `%s'.",
4307 ERR0120 => "Unknown config `%s'.",
4308 ERR0121 => "Configuration error. Reading file `%s' at line %d: %s",
4309 ERR0122 => "Caught exception during re-read config file and re-setup l7vsd. (message: %s) "
4310 . "So config setting will be rollbacked.",
4311 ERR0123 => "`%s' is a required module for checking %s service.",
4312 ERR0124 => "Invalid value `%s'.",
4313 ERR0125 => "Invalid accesslog rotate type (set 'date', 'size' or 'datesize') `%s'.",
4314 ERR0126 => "Invalid accesslog rotate max backup index number `%s'.",
4315 ERR0127 => "Invalid accesslog rotate max filesize value (set 0 or 1-999[KMG]. must specify unit(KMG)) `%s'.",
4316 ERR0128 => "Invalid accesslog rotate rotation timing (set 'year','month','week','date', or 'hour') `%s'.",
4317 ERR0129 => "Invalid accesslog rotate rotation timing value `%s'.",
4318 # operate l7vsd error
4319 ERR0201 => "Failed to add virtual service to l7vsd: `%s %s', output: `%s'",
4320 ERR0202 => "Failed to edit virtual service on l7vsd: `%s %s', output: `%s'",
4321 ERR0203 => "Failed to delete virtual service from l7vsd: `%s %s', output: `%s'",
4322 ERR0204 => "Failed to add server to l7vsd: `%s %s' ( x `%s %s'), output: `%s'",
4323 ERR0205 => "Failed to edit server on l7vsd: `%s %s' ( x `%s %s'), output: `%s'",
4324 ERR0206 => "Failed to delete server from l7vsd: `%s %s' ( x `%s %s'), output: `%s'",
4325 ERR0207 => "Trying add server `%s', but virtual service `%s' is not found.",
4326 ERR0208 => "Trying delete server `%s', but virtual service `%s' is not found.",
4327 ERR0209 => "`%s' was already existed on l7vsd. ( x `%s %s')",
4328 ERR0210 => "`%s' was already deleted on l7vsd. ( x `%s %s')",
4329 ERR0211 => "`%s' was already changed to quiescent state on l7vsd. ( x `%s %s')",
4331 ERR0301 => "Failed to system `%s' with return: %s",
4332 ERR0302 => "Failed to exec `%s' with return: %s",
4333 ERR0303 => "Failed to command `%s' with return: %s",
4335 ERR0401 => "Failed to delete file `%s': `Is a directory'",
4336 ERR0402 => "Failed to delete file `%s': `No such file'",
4337 ERR0403 => "Failed to delete file `%s': `%s'",
4338 ERR0404 => "Config file `%s' is not found.",
4339 ERR0405 => "`l7directord.cf' is not found at default search paths.",
4340 ERR0406 => "`l7vsadm' file is not found at default search paths.",
4341 ERR0407 => "Cannot open config file `%s'.",
4342 ERR0408 => "Cannot close config file `%s'.",
4343 ERR0409 => "Cannot open pid file (%s): %s",
4344 ERR0410 => "Cannot get mtime of configuration file `%s'",
4345 ERR0411 => "No delete file specified.",
4346 ERR0412 => "Invalid pid specified. (pid: %s)",
4348 ERR0501 => "Some method arguments are undefined.",
4349 ERR0502 => "VirtualService ID is undefined.",
4350 ERR0503 => "HealthCheck ID is undefined.",
4351 ERR0504 => "negotiate function is undefined. So use check_connect function.",
4352 ERR0505 => "custom check script is undefined. So use check_off function.",
4353 # health check process
4354 ERR0601 => "Service up detected. (Real server `%s')",
4355 ERR0602 => "Service down detected. (Real server `%s')",
4356 ERR0603 => "Detected down monitor process (pid: %s). Prepare to re-start health check process. (id: `%s')",
4357 ERR0604 => "Failed to fork() on sub process creation. (id: `%s')",
4359 ERR0701 => "Cannot fork for become daemon (errno: `%s') and exit.",
4360 ERR0702 => "Cannot setsid for become daemon and exit.",
4361 ERR0703 => "Cannot chdir for become daemon and exit.",
4362 ERR0704 => "Cannot open /dev/null for become daemon and exit.",
4363 ERR0705 => "Cannot open /dev/console for become daemon and exit.",
4366 WRN0001 => "l7directord `%s' received signal: %s. Terminate process.",
4367 WRN0002 => "l7directord `%s' received signal: %s. Reload configuration.",
4368 WRN0003 => "Signal TERM send error(pid: %d)",
4369 WRN0004 => "Signal HUP send error(pid: %d)",
4371 WRN0101 => "Configuration file `%s' has changed on disk.",
4372 WRN0102 => "Reread new configuration.",
4373 WRN0103 => "Ignore new configuration.",
4375 WRN0203 => "Service check OK. HTTP response is valid. HTTP response status line is `%s' (real - `%s:%s')",
4376 WRN0204 => "Service check OK. Successfully connect SMTP server. (real - `%s:%s')",
4377 WRN0205 => "Service check OK. Successfully connect POP3 server. (real - `%s:%s')",
4378 WRN0206 => "Service check OK. Successfully connect IMAP server. (real - `%s:%s')",
4379 WRN0207 => "Service check OK. Successfully bind LDAP server. (real - `%s:%s')",
4380 WRN0208 => "Service check OK. NNTP response is valid. `%s' (real - `%s:%s')",
4381 WRN0209 => "Service check OK. Database response is valid. (real - `%s:%s')",
4382 WRN0210 => "Service check OK. Successfully connect socket to server. (real - `%s:%s')",
4383 WRN0211 => "Service check OK. SIP response is valid. `%s' (real - `%s:%s')",
4384 WRN0212 => "Service check OK. Successfully login FTP server. (real - `%s')",
4385 WRN0213 => "Service check OK. Successfully lookup DNS. (real - `%s:%s')",
4386 WRN0214 => "Service check OK. Successfully receive ping response. (real - `%s')",
4387 WRN0215 => "Custom check result OK. (real - `%s')",
4389 WRN0301 => "Perl warning: `%s'",
4391 WRN1001 => "Retry service check `%s' %d more time(s).",
4393 WRN1101 => "Service check NG. Check URL `%s' is not valid. (real - `%s:%s')",
4394 WRN1102 => "Service check NG. HTTP response is not ok. Response status line is `%s' (real - `%s:%s')",
4395 WRN1103 => "Service check NG. Check string `%s' is not found in HTTP response. (real - `%s:%s')",
4397 WRN1201 => "Service check NG. Cannot connect SMTP server. (real - `%s:%s')",
4399 WRN1301 => "Service check NG. Cannot connect POP3 server. (real - `%s:%s')",
4400 WRN1302 => "Service check NG. Cannot login POP3 server. (real - `%s:%s')",
4402 WRN1401 => "Service check NG. Cannot connect IMAP server. (real - `%s:%s')",
4403 WRN1402 => "Service check NG. Cannot login IMAP server. (real - `%s:%s')",
4404 WRN1403 => "Service check NG. Connection timeout from IMAP server in %d seconds. (real - `%s:%s')",
4406 WRN1501 => "Service check NG. Cannot connect LDAP server. (real - `%s:%s')",
4407 WRN1502 => "Service check NG. Connection timeout from LDAP server in %d seconds. (real - `%s:%s')",
4408 WRN1503 => "Service check NG. LDAP bind error. (real - `%s:%s')",
4409 WRN1504 => "Service check NG. Exists %d results (not one) on search Base DN. (real - `%s:%s')",
4410 WRN1505 => "Service check NG. Check string `%s' is not found in Base DN search result. (real - `%s:%s')",
4412 WRN1601 => "Service check NG. Cannot connect NNTP server. (real - `%s:%s')",
4413 WRN1602 => "Service check NG. Connection timeout from NNTP server in %d seconds. (real - `%s:%s')",
4414 WRN1603 => "Service check NG. NNTP response is not ok. `%s' (real - `%s:%s')",
4416 WRN1701 => "Service check NG. SQL check must set `database', `login', `passwd' by configuration. (real - `%s:%s')",
4417 WRN1702 => "Service check NG. Cannot connect database or cannot login database. (real - `%s:%s')",
4418 WRN1703 => "Service check NG. Query result has no row. (real - `%s:%s')",
4419 WRN1704 => "Service check NG. Expected %d rows of query results, but got %d rows. (real - `%s:%s')",
4420 WRN1705 => "Service check NG. Connection timeout from database in %d seconds. (real - `%s:%s')",
4422 WRN1801 => "Service check NG. SIP check must set `login' by configuration. (real - `%s:%s')",
4423 WRN1802 => "Service check NG. Cannot connect SIP server. (real - `%s:%s')",
4424 WRN1803 => "Service check NG. SIP response is not ok. `%s' (real - `%s:%s')",
4425 WRN1804 => "Service check NG. Connection timeout from SIP server in %d seconds. (real - `%s:%s')",
4427 WRN1901 => "Service check NG. FTP check must set `login', `passwd' by configuration. (real - `%s')",
4428 WRN1902 => "Service check NG. Cannot connect FTP server. (real - `%s')",
4429 WRN1903 => "Service check NG. Cannot login FTP server. (real - `%s')",
4430 WRN1904 => "Service check NG. Cannot chdir to / of FTP server. (real - `%s')",
4431 WRN1905 => "Service check NG. Cannot get file `%s' (real - `%s')",
4432 WRN1906 => "Service check NG. Check string `%s' is not found in file `%s' (real - `%s')",
4433 WRN1907 => "Service check NG. Exception occur during FTP check `%s' (real - `%s')",
4434 WRN1908 => "Service check NG. Connection timeout from FTP server in %d seconds. (real - `%s')",
4436 WRN2001 => "Service check NG. DNS check must set `request', `receive' by configuration. (real - `%s:%s')",
4437 WRN2002 => "Service check NG. Connection timeout from DNS server in %d seconds. (real - `%s:%s')",
4438 WRN2003 => "Service check NG. Net::DNS exception occur `%s' (real - `%s:%s')",
4439 WRN2004 => "Service check NG. DNS search `%s' not respond. (real - `%s:%s')",
4440 WRN2005 => "Service check NG. Check string `%s' is not found in search result. (real - `%s:%s')",
4442 WRN3101 => "Service check NG. Ping timeout in %d seconds. (real - `%s')",
4444 WRN3201 => "Service check NG. Cannot connect socket to server. (errno: `%s') (real - `%s:%s')",
4446 WRN3301 => "Custom check NG. Check timeout in %d seconds. (real - `%s')",
4447 WRN3302 => "Custom check NG. `%s' returns %d",
4450 INF0001 => "Starting program with command: `%s'",
4451 INF0002 => "Starting l7directord v%s with pid: %d (configuration: `%s')",
4452 INF0003 => "Starting l7directord v%s as daemon. (configuration: `%s')",
4453 INF0004 => "Exit by initialize error.",
4454 INF0005 => "Exit parent process for become daemon",
4455 INF0006 => "Exiting with exit status %d: %s",
4456 INF0007 => "Detected halt flag. Exit this monitor process with status: 0",
4457 INF0008 => "Reached end of `main'",
4459 INF0101 => "l7directord for `%s' is running with pid: %d",
4460 INF0102 => "l7directord stale pid file %s for %s",
4461 INF0103 => "Other l7directord process is running. (pid: %d)",
4462 INF0104 => "l7directord process is not running.",
4464 INF0201 => "Add virtual service to l7vsd: `%s %s'",
4465 INF0202 => "Edit virtual service on l7vsd: `%s %s'",
4466 INF0203 => "Delete virtual service from l7vsd: `%s %s'",
4467 INF0204 => "Add server to l7vsd: `%s %s' ( x `%s %s') (weight set to %d)",
4468 INF0205 => "Edit server on l7vsd: `%s %s' ( x `%s %s') (weight set to %d)",
4469 INF0206 => "Delete server from l7vsd: `%s %s' ( x `%s %s')",
4471 INF0301 => "Added real server. (`%s')",
4472 INF0302 => "Added fallback server. (`%s')",
4473 INF0303 => "Changed real server to quiescent state. (`%s')",
4474 INF0304 => "Changed fallback server to quiescent state. (`%s')",
4475 INF0305 => "Deleted real server. (`%s')",
4476 INF0306 => "Deleted fallback server. (`%s')",
4478 INF0401 => "Prepare to start health check process. (id: `%s')",
4479 INF0402 => "Create health check process with pid: %d. (id `%s')",
4481 INF0501 => "Real server down shell execute: `%s %s'",
4482 INF0502 => "Real server recovery shell execute: `%s %s'",
4483 INF0503 => "Config callback shell execute: `%s %s'",
4484 INF0504 => "Running system: `%s'",
4485 INF0505 => "Running exec: `%s'",
4486 INF0506 => "Running command: `%s'",
4490 = exists $message_list->{$code} ? sprintf $message_list->{$code}, @message_args
4491 : "Unknown message. (code:[$code] args:[" . join(q{, }, @message_args) . '])';
4497 # Create message by _message_only and add code header.
4499 my ($code, @message_args) = @_;
4500 my $message = _message_only($code, @message_args);
4501 $message = "[$code] $message";
4511 l7directord - UltraMonkey-L7 Director Daemon
4513 Daemon to monitor remote services and control UltraMonkey-L7
4518 B<l7directord> [B<-d>] [I<configuration>] {B<start>|B<stop>|B<restart>|B<try-restart>|B<reload>|B<status>|B<configtest>}
4520 B<l7directord> B<-t> [I<configuration>]
4522 B<l7directord> B<-h|--help>
4524 B<l7directord> B<-v|--version>
4528 B<l7directord> is a daemon to monitor and administer real servers in a
4529 cluster of load balanced virtual servers. B<l7directord> is similar to B<ldirectord>
4530 in terms of functionality except that it triggers B<l7vsadm>.
4531 B<l7directord> typically is started from command line but can be included
4532 to start from heartbeat. On startup B<l7directord> reads the file
4533 B</etc/ha.d/conf/>I<configuration>.
4534 After parsing the file, entries for virtual servers are created on the UltraMonkey-L7.
4535 Now at regular intervals the specified real servers are monitored and if
4536 they are considered alive, added to a list for each virtual server. If a
4537 real server fails, it is removed from that list. Only one instance of
4538 B<l7directord> can be started for each configuration, but more instances of
4539 B<l7directord> may be started for different configurations. This helps to
4540 group clusters of services. This can be done by putting an entry inside
4541 B</etc/ha.d/haresources>
4543 I<nodename virtual-ip-address l7directord::configuration>
4545 to start l7directord from heartbeat.
4552 =item I<configuration>:
4554 This is the name for the configuration as specified in the file
4555 B</etc/ha.d/conf/>I<configuration>
4559 Don't start as daemon. Useful for debugging.
4563 Help. Print user manual of l7directord.
4567 Version. Print version of l7directord.
4571 Run syntax tests for configuration files only. The program immediately exits after these syntax parsing tests
4572 with either a return code of 0 (Syntax OK) or return code not equal to 0 (Syntax Error).
4576 Start the daemon for the specified configuration.
4580 Stop the daemon for the specified configuration. This is the same as sending
4581 a TERM signal to the running daemon.
4585 Restart the daemon for the specified configuration. The same as stopping and starting.
4587 =item B<try-restart>
4589 Try to restart the daemon for the specified configuration. If l7directord is already running for the
4590 specified configuration, then the same is stopped and started (Similar to restart).
4591 However, if l7directord is not already running for the specified configuration, then an error message
4592 is thrown and the program exits.
4596 Reload the configuration file. This is only useful for modifications
4597 inside a virtual server entry. It will have no effect on adding or
4598 removing a virtual server block. This is the same as sending a HUP signal to
4603 Show status of the running daemon for the specified configuration.
4607 This is the same as B<-t>.
4614 =head2 Description how to write configuration files
4618 =item B<virtual = >I<(ip_address|hostname:portnumber|servicename)>
4620 Defines a virtual service by IP-address (or hostname) and port (or
4621 servicename). All real services and flags for a virtual
4622 service must follow this line immediately and be indented.
4623 For ldirectord, Firewall-mark settings could be set. But for l7directord
4624 Firewall-mark settings cannot be set.
4626 =item B<checktimeout = >I<n>
4628 Timeout in seconds for connect checks. If the timeout is exceeded then the
4629 real server is declared dead. Default is 5 seconds. If defined in virtual
4630 server section then the global value is overridden.
4632 =item B<negotiatetimeout = >I<n>
4634 Timeout in seconds for negotiate checks. Default is 5 seconds.
4635 If defined in virtual server section then the global value is overridden.
4637 =item B<checkinterval = >I<n>
4639 Defines the number of second between server checks. Default is 10 seconds.
4640 If defined in virtual server section then the global value is overridden.
4642 =item B<retryinterval = >I<n>
4644 Defines the number of second between server checks when server status is NG.
4645 Default is 10 seconds. If defined in virtual server section then the global
4646 value is overridden.
4648 =item B<checkcount = >I<n>
4650 The number of times a check will be attempted before it is considered
4651 to have failed. Note that the checktimeout is additive, so if checkcount
4652 is 3 and checktimeout is 2 seconds and retryinterval is 1 second,
4653 then a total of 8 seconds (2 + 1 + 2 + 1 + 2) worth of timeout will occur
4654 before the check fails. Default is 1. If defined in virtual server section
4655 then the global value is overridden.
4657 =item B<configinterval = >I<n>
4659 Defines the number of second between configuration checks.
4660 Default is 5 seconds.
4662 =item B<autoreload = >[B<yes>|B<no>]
4664 Defines if <l7directord> should continuously check the configuration file
4665 for modification each B<configinterval> seconds. If this is set to B<yes>
4666 and the configuration file changed on disk and its modification time (mtime)
4667 is newer than the previous version, the configuration is automatically reloaded.
4670 =item B<callback = ">I</path/to/callback>B<">
4672 If this directive is defined, B<l7directord> automatically calls
4673 the executable I</path/to/callback> after the configuration
4674 file has changed on disk. This is useful to update the configuration
4675 file through B<scp> on the other heartbeated host. The first argument
4676 to the callback is the name of the configuration.
4678 This directive might also be used to restart B<l7directord> automatically
4679 after the configuration file changed on disk. However, if B<autoreload>
4680 is set to B<yes>, the configuration is reloaded anyway.
4682 =item B<fallback = >I<ip_address|hostname[:portnumber|servicename]> [B<masq>|B<tproxy>]
4684 the server onto which a web service is redirected if all real
4685 servers are down. Typically this would be 127.0.0.1 with
4688 This directive may also appear within a virtual server, in which
4689 case it will override the global fallback server, if set.
4690 Also you can set either B<masq> or B<tproxy> as fallback forwarding
4691 mechanism. The default is B<masq>.
4693 =item B<logfile = ">I</path/to/logfile>B<">|syslog_facility
4695 An alternative logfile might be specified with this directive. If the logfile
4696 does not have a leading '/', it is assumed to be a syslog(3) facility name.
4698 The default is to log directly to the file I</var/log/l7vs/l7directord.log>.
4700 =item B<execute = ">I<configuration>B<">
4702 Use this directive to start an instance of l7directord for
4703 the named I<configuration>.
4707 If this directive is specified, the daemon does not go into background mode.
4708 All log-messages are redirected to stdout instead of a logfile.
4709 This is useful to run B<l7directord> supervised from daemontools.
4710 See http://untroubled.org/rpms/daemontools/ or http://cr.yp.to/daemontools.html
4713 =item B<quiescent = >[B<yes>|B<no>]
4715 If B<yes>, then when real or fallback servers are determined
4716 to be down, they are not actually removed from the UltraMonkey-L7,
4717 but set weight to zero.
4718 If B<no>, then the real or fallback servers will be removed
4719 from the UltraMonkey-L7. The default is B<yes>.
4721 This directive may also appear within a virtual server, in which
4722 case it will override the global fallback server, if set.
4727 =head2 Section virtual
4729 The following commands must follow a B<virtual> entry and must be indented
4730 with a minimum of 4 spaces or one tab.
4734 =item B<real => I<ip_address|hostname[-E<gt>ip_address|hostname][:portnumber|servicename>] [B<masq>|B<tproxy>] [I<n>] [B<">I<request>B<", ">I<receive>B<">]
4736 Defines a real service by IP-address (or hostname) and port (or
4737 servicename). If the port is omitted then a 0 will be used.
4738 Optionally a range of IP addresses (or two hostnames) may be
4739 given, in which case each IP address in the range will be treated as a real
4740 server using the given port. The second argument defines the forwarding
4741 mechanism, it must be B<masq> or B<tproxy>. The third argument defines the weight of
4742 each real service. This argument is optional. Default is 1. The last two
4743 arguments are optional too. They define a request-receive pair to be used to
4744 check if a server is alive. They override the request-receive pair in the
4745 virtual server section. These two strings must be quoted. If the request
4746 string starts with I<http://...> the IP-address and port of the real server
4747 is overridden, otherwise the IP-address and port of the real server is used.
4749 =item B<module => I<proto-module module-args [opt-module-args]>
4751 Indicates the module parameter of B<l7directord>. Here B<proto-module>
4752 denotes the protocol module name (For example, pfilter). B<module-args> denotes the
4753 arguments for the protocol module (For example, --pattern-match '*.html*').
4754 B<module-args> is optional only when set B<sessionless>, B<ip> and B<sslid> module to B<proto-module>.
4755 The last argument is optional (For example, --reschedule).
4759 =head2 More than one of these entries may be inside a virtual section:
4763 =item B<maxconn => I<n>
4765 Defines the maximum connection that the virtual service can handle. If the number of
4766 requests cross the maxconn limit, the requests would be redirected to the
4769 =item B<qosup => I<n>[B<K>|B<M>|B<G>]
4771 Defines the bandwidth quota size in bps for up stream. If the number of the
4772 bandwidth is over the qosup limit, a packet to the virtual service will be delayed
4773 until the number of bandwidth become below the qosup limit.
4774 B<K>(kilo), B<M>(mega) and B<G>(giga) unit are available.
4776 =item B<qosdown => I<n>[B<K>|B<M>|B<G>]
4778 Defines the bandwidth quota size in bps for down stream. If the number of the
4779 bandwidth is over the qosdown limit, a packet to the client will be delayed
4780 until the number of bandwidth become below the qosdown limit.
4781 B<K>(kilo), B<M>(mega) and B<G>(giga) unit are available.
4783 =item B<sorryserver =>I<ip_address|hostname[:portnumber|servicename]> [B<masq>|B<tproxy>]
4785 Defines a sorry server by IP-address (or hostname) and port (or
4786 servicename). The second argument defines the forwarding mechanism, it must be B<masq> or B<tproxy>.
4787 Firewall-mark settings cannot be set.
4788 If the number of requests to the virtual service cross the maxconn limit, or no available
4789 real server exists, then the requests would be redirected to the sorry server.
4791 =item B<checktype = negotiate>|B<connect>|I<N>|B<ping>|B<custom>|B<off>|B<on>
4793 Type of check to perform. Negotiate sends a request and matches a receive
4794 string. Connect only attempts to make a TCP/IP connection, thus the
4795 request and receive strings may be omitted. If checktype is a number then
4796 negotiate and connect is combined so that after each N connect attempts one
4797 negotiate attempt is performed. This is useful to check often if a service
4798 answers and in much longer intervals a negotiating check is done. Ping
4799 means that ICMP ping will be used to test the availability of real servers.
4800 Ping is also used as the connect check for UDP services. Custom means that
4801 custom command will be used to test the availability of real servers.
4802 Off means no checking will take place and no real or fallback servers will
4803 be activated. On means no checking will take place and real servers will
4804 always be activated. Default is I<negotiate>.
4806 =item B<service = ftp>|B<smtp>|B<http>|B<pop>|B<nntp>|B<imap>|B<ldap>|B<https>|B<dns>|B<mysql>|B<pgsql>|B<sip>|B<none>
4808 The type of service to monitor when using checktype=negotiate. None denotes
4809 a service that will not be monitored. If the port specified for the virtual
4810 server is 21, 25, 53, 80, 110, 119, 143, 389, 443, 3306, 5432 or 5060 then
4811 the default is B<ftp>, B<smtp>, B<dns>, B<http>, B<pop>, B<nntp>, B<imap>,
4812 B<ldap>, B<https>, B<mysql>, B<pgsql> or B<sip> respectively. Otherwise the
4813 default service is B<none>.
4815 =item B<checkport = >I<n>
4817 Number of port to monitor. Sometimes check port differs from service port.
4818 Default is port specified for the real server.
4820 =item B<request = ">I<uri to requested object>B<">
4822 This object will be requested each checkinterval seconds on each real
4823 server. The string must be inside quotes. Note that this string may be
4824 overridden by an optional per real-server based request-string.
4826 For a DNS check this should the name of an A record, or the address
4827 of a PTR record to look up.
4829 For a MySQL or PostgreSQL checks, this should be a SQL query.
4830 The data returned is not checked, only that the
4831 answer is one or more rows. This is a required setting.
4833 =item B<receive = ">I<regexp to compare>B<">
4835 If the requested result contains this I<regexp to compare>, the real server
4836 is declared alive. The regexp must be inside quotes. Keep in mind that
4837 regexps are not plain strings and that you need to escape the special
4838 characters if they should as literals. Note that this regexp may be
4839 overridden by an optional per real-server based receive regexp.
4841 For a DNS check this should be any one the A record's addresses or
4842 any one of the PTR record's names.
4844 For a MySQL check, the receive setting is not used.
4846 =item B<httpmethod = GET>|B<HEAD>
4848 Sets the HTTP method, which should be used to fetch the URI specified in
4849 the request-string. GET is the method used by default if the parameter is
4850 not set. If HEAD is used, the receive-string should be unset.
4852 =item B<virtualhost = ">I<hostname>B<">
4854 Used when using a negotiate check with HTTP or HTTPS. Sets the host header
4855 used in the HTTP request. In the case of HTTPS this generally needs to
4856 match the common name of the SSL certificate. If not set then the host
4857 header will be derived from the request url for the real server if present.
4858 As a last resort the IP address of the real server will be used.
4860 =item B<login = ">I<username>B<">
4862 Username to use to login to FTP, POP, IMAP, MySQL and PostgreSQL servers.
4863 For FTP, the default is anonymous. For POP and IMAP, the default is the
4864 empty string, in which case authentication will not be attempted.
4865 For a MySQL and PostgreSQL, the username must be provided.
4867 For SIP the username is used as both the to and from address
4868 for an OPTIONS query. If unset it defaults to l7directord\@<hostname>,
4869 hostname is derived as per the passwd option below.
4871 =item B<passwd = ">I<password>B<">
4873 Password to use to login to FTP, POP, IMAP, MySQL and PostgreSQL servers.
4874 Default is for FTP is l7directord\@<hostname>, where hostname is the
4875 environment variable HOSTNAME evaluated at run time, or sourced from uname
4876 if unset. The default for all other services is an empty password, in the
4877 case of MySQL and PostgreSQL this means authentication will not be
4880 =item B<database = ">I<databasename>B<">
4882 Database to use for MySQL and PostgreSQL servers, this is the database that
4883 the query (set by B<receive> above) will be performed against. This is a
4886 =item B<scheduler => I<scheduler_name>
4888 Scheduler to be used by UltraMonkey-L7 for load balancing.
4889 The available schedulers are only B<lc> and B<rr>. The default is I<rr>.
4891 =item B<protocol = tcp>
4893 Protocol to be used. B<l7vsadm> supports only B<tcp>.
4894 Since the virtual is specified as an IP address and port, it would be tcp
4895 and will default to tcp.
4897 =item B<realdowncallback = ">I</path/to/realdowncallback>B<">
4899 If this directive is defined, B<l7directord> automatically calls
4900 the executable I</path/to/realdowncallback> after a real server's status
4901 changes to down. The first argument to the realdowncallback is the real
4902 server's IP-address and port (ip_address:portnumber).
4904 =item B<realrecovercallback = ">I</path/to/realrecovercallback>B<">
4906 If this directive is defined, B<l7directord> automatically calls
4907 the executable I</path/to/realrecovercallback> after a real server's status
4908 changes to up. The first argument to the realrecovercallback is the real
4909 server's IP-address and port (ip_address:portnumber).
4911 =item B<customcheck = ">I<custom check command>B<">
4913 If this directive is defined and set B<checktype> to custom, B<l7directord>
4914 exec custom command for real servers health checking. Only if custom command
4915 returns 0, real servers will change to up. Otherwise real servers will change
4916 to down. Custom check command has some macro string. See below.
4922 Change to real server IP address.
4926 Change to real server port number.
4930 =item B<sslconfigfile = ">I</path/to/sslconfigfile>B<">
4932 When communication with Client is SSL, the file name for SSL setting is
4936 =item B<socketoption = ">I<OPTION...>B<">
4938 An option of the socket used in VirtualService is designated.
4939 The setting possible value is described.
4943 =item B<transparent>
4945 Set IP_TRANSPARENT option to the RealServer socket.
4947 =item B<deferaccept>
4949 Set TCP_DEFER_ACCEPT option to the listener socket of VirtualService.
4953 Set TCP_NODELAY option to the Client and RealServer socket.
4957 Set TCP_CORK option to the Client and RealServer socket.
4959 =item B<quickackon> or B<quickackoff>
4961 Set or unset TCP_QUICKACK option to the Client and RealServer socket.
4965 =item B<accesslog = >[B<yes>|B<no>]
4967 If B<yes>, then output client access log. The default is B<no>.
4969 =item B<accesslog_rotate_type = >[B<date>|B<size>|B<datesize>]
4971 B<date> means rotate access log with the specified date/time. B<size> means rotate access log when that file size exceeds the specified size. B<datesize> means both B<date> and B<size>.
4973 =item B<accesslog_rotate_max_backup_index = >I<n>
4975 Maximum number of backup files.
4977 =item B<accesslog_rotate_max_filesize = > I<n>[B<K>|B<M>|B<G>]
4979 Threshold file size of access log when B<accesslog_rotate_type> is set to B<size> or B<datesize>. B<K>(kilo), B<M>(mega) and B<G>(giga) units are available.
4981 =item B<accesslog_rotate_rotation_timing = >[B<year>|B<month>|B<week>|B<date>|B<hour>]
4983 Rotate timing type when B<accesslog_rotate_type> is set to B<date> or B<datesize>.
4985 =item B<accesslog_rotate_rotation_timing_value = ">I<rotation_timing_value>B<">
4987 Rotate timing. The formats are different by B<accesslog_rotate_rotation_timing> setting.
4991 =item B<accesslog_rotate_rotation_timing=year>
4993 FORMAT: B<"MM/dd HH:mm">
4995 =item B<accesslog_rotate_rotation_timing=month>
4997 FORMAT: B<"dd HH:mm">
4999 =item B<accesslog_rotate_rotation_timing=week>
5001 FORMAT: B<">[B<sun>|B<mon>|B<tue>|B<wed>|B<thu>|B<fri>|B<sat>] B<HH:mm">
5003 =item B<accesslog_rotate_rotation_timing=date>
5007 =item B<accesslog_rotate_rotation_timing=hour>
5011 Defines the size each of session_thread_pool_size.
5012 Default is session_thread_pool_size parameter at l7vs.cf.
5014 =item B<session_thread_pool_size = >I<n>
5023 B</etc/ha.d/conf/l7directord.cf>
5025 B</var/log/l7vs/l7directord.log>
5027 B</var/run/l7directord.>I<configuration>B<.pid>
5033 L<l7vsadm>, L<heartbeat>