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') {
946 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, key => $key};
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 if (!defined $value ) {
1037 config_error($line, 'ERR0129', $config);
1039 if ($value =~ /^\d{1,2}\/\d{1,2}\s\d{1,2}:\d{1,2}$/) {
1040 ## MM/dd hh:mm Check
1043 elsif ($value =~ /^\d{1,2}\s\d{1,2}:\d{1,2}$/) {
1047 elsif ($value =~ /^(sun|mon|tue|wed|thu|fri|sat)\s\d{1,2}:\d{1,2}$/i) {
1048 ## <week> hh:mm Check
1051 elsif ($value =~ /^\d{1,2}:\d{1,2}$/) {
1055 elsif ($value =~ /^\d{1,2}$/) {
1059 if ( !defined $check ) {
1060 config_error($line, 'ERR0129', $config);
1063 elsif ($name eq 'session_thread_pool_size') {
1064 if (!defined $value || $value !~ /^\d+$/ || $value == 0 ) {
1065 config_error($line, 'ERR0101', $config);
1070 return ($name, $value);
1073 # check_require_module
1074 # Check service setting and require module.
1075 sub check_require_module {
1076 my %require_module = (
1077 http => [ qw( LWP::UserAgent LWP::Debug ) ],
1078 https => [ qw( LWP::UserAgent LWP::Debug Crypt::SSLeay ) ],
1079 ftp => [ qw( Net::FTP ) ],
1080 smtp => [ qw( Net::SMTP ) ],
1081 pop => [ qw( Net::POP3 ) ],
1082 imap => [ qw( Mail::IMAPClient ) ],
1083 ldap => [ qw( Net::LDAP ) ],
1084 nntp => [ qw( IO::Socket IO::Select6 ) ],
1085 dns => [ qw( Net::DNS ) ],
1086 mysql => [ qw( DBI DBD::mysql ) ],
1087 pgsql => [ qw( DBI DBD::Pg ) ],
1088 sip => [ qw( IO::Socket::INET IO::Socket::INET6 ) ],
1089 ping => [ qw( Net::Ping ) ],
1090 connect => [ qw( IO::Socket::INET IO::Socket::INET6 ) ],
1093 for my $v ( @{ $CONFIG{virtual} } ) {
1094 next if !defined $v;
1095 next if ( !defined $v->{service} || !defined $v->{checktype} );
1096 my $check_service = q{};
1097 if ( $v->{checktype} eq 'negotiate' && $require_module{ $v->{service} } ) {
1098 $check_service = $v->{service};
1100 elsif ($v->{checktype} eq 'ping' || $v->{checktype} eq 'connect') {
1101 $check_service = $v->{checktype};
1106 for my $module ( @{ $require_module{$check_service} } ) {
1107 my $module_path = $module . '.pm';
1108 $module_path =~ s{::}{/}g;
1110 require $module_path;
1113 config_error(0, 'ERR0123', $module, $check_service);
1119 # _ld_service_resolve
1120 # Set service name from port number
1121 # pre: vsrv: Virtual Service to resolve port
1122 # port: port in the form
1123 # post: If $vsrv->{service} is not set, then set it to "http",
1124 # "https", "ftp", "smtp", "pop", "imap", "ldap", "nntp" or "none"
1125 # if $vsrv->{port} is 80, 443, 21, 25, 110, 143, 389 or
1126 # any other value, respectivley
1128 sub _ld_service_resolve {
1129 my ($vsrv, $port) = @_;
1132 my @p = qw( 80 443 21 25 110 119 143 389 53 3306 5432 5060 );
1133 my @s = qw( http https ftp smtp pop nntp imap ldap dns mysql pgsql sip );
1136 if (defined $vsrv && !defined $vsrv->{service} && defined $port) {
1137 $vsrv->{service} = exists $servname{$port} ? $servname{$port}
1144 # Parse a fallback server
1145 # pre: line: line number fallback server was read from
1146 # fallback: Should be of the form
1147 # ip_address|hostname[:port|:service_name] [masq|tproxy]
1148 # config_line: line read from configuration file
1149 # post: fallback is parsed
1150 # return: Reference to hash of the form
1151 # { server => blah, forward => blah }
1152 # Debugging message will be reported and programme will exit
1154 sub parse_fallback {
1155 my ($line, $fallback, $config_line) = @_;
1157 if (!defined $fallback || $fallback !~ /^(\S+)(?:\s+(\S+))?$/) {
1158 config_error($line, 'ERR0114', $config_line);
1160 my ($ip_port, $forward) = ($1, $2);
1161 $ip_port = ld_gethostservbyname($ip_port, 'tcp');
1162 if ( !defined $ip_port ) {
1163 config_error($line, 'ERR0114', $config_line);
1165 if (defined $forward && $forward !~ /^(?:masq|tproxy)$/i) {
1166 config_error($line, 'ERR0107', $config_line);
1169 my %fallback = %REAL;
1170 $fallback{server} = $ip_port;
1171 $fallback{option}{forward} = get_forward_flag($forward);
1177 # Parse a real server
1178 # pre: line: line number real server was read from
1179 # real: Should be of the form
1180 # ip_address|hostname[:port|:service_name] [masq|tproxy]
1181 # config_line: line read from configuration file
1182 # post: real is parsed
1183 # return: Reference to array include real server hash reference
1184 # [ {server...}, {server...} ... ]
1185 # Debugging message will be reported and programme will exit
1188 my ($line, $real, $config_line) = @_;
1190 my $ip_host = qr{\d+\.\d+\.\d+\.\d+|[a-z0-9.-]+|\[[a-zA-Z0-9:]+\]};
1191 my $port_service = qr{\d+|[a-z0-9-]+};
1194 ($ip_host) # ip or host
1195 (?:->($ip_host))? # range (optional)
1196 (?::($port_service))? # port or service (optional)
1197 (?:\s+([a-z]+))? # forwarding mode (optional)
1198 (?:\s+(\d+))? # weight (optional)
1200 ([^,\s]+) # "request
1201 \s*[ ,]\s* # separater
1205 config_error($line, 'ERR0114', $config_line);
1207 my ($ip1, $ip2, $port, $forward, $weight, $request, $receive)
1208 = ( $1, $2, $3, $4, $5, $6, $7);
1209 # set forward, weight and request-receive pair.
1211 if (defined $forward) {
1212 $forward = lc $forward;
1213 if ($forward !~ /^(?:masq|tproxy)$/) {
1214 config_error($line, 'ERR0107', $config_line);
1216 $real{forward} = $forward;
1218 if (defined $weight) {
1219 $real{weight} = $weight;
1221 if (defined $request && defined $receive) {
1222 $request =~ s/^\s*("|')(.*)\1\s*/$2/;
1223 $receive =~ s/^\s*("|')(.*)\1\s*/$2/;
1224 $real{request} = $request;
1225 $real{receive} = $receive;
1228 my $resolved_port = undef;
1229 if (defined $port) {
1230 $resolved_port = ld_getservbyname($port);
1231 if (!defined $resolved_port) {
1232 config_error($line, 'ERR0108', $config_line);
1236 my $resolved_ip1 = ld_gethostbyname($ip1);
1237 if (!defined $resolved_ip1) {
1238 config_error($line, 'ERR0114', $config_line);
1241 my $resolved_ip2 = $resolved_ip1;
1243 $resolved_ip2 = ld_gethostbyname($ip2);
1244 if (!defined $resolved_ip2) {
1245 config_error($line, 'ERR0114', $config_line);
1249 my ($ip_version , $int_ip1, $int_ip1_prefix ) = ip_to_int($resolved_ip1);
1250 my ($ip_version2, $int_ip2, $int_ip2_prefix ) = ip_to_int($resolved_ip2);
1252 if ( defined $int_ip1 && defined $int_ip2 ) {
1253 if ($int_ip1 > $int_ip2) {
1254 config_error($line, 'ERR0115', $resolved_ip1, $resolved_ip2, $config_line);
1256 elsif ($int_ip1 eq $int_ip2) {
1257 my %new_real = %real;
1258 $new_real{server}{ip } = $resolved_ip1;
1259 $new_real{server}{port} = $resolved_port;
1260 push @reals, \%new_real;
1263 for (my $int_ip = $int_ip1; $int_ip <= $int_ip2; $int_ip++) {
1264 my %new_real = %real;
1265 $new_real{server}{ip } = int_to_ip($ip_version, $int_ip, $int_ip1_prefix);
1266 $new_real{server}{port} = $resolved_port;
1267 push @reals, \%new_real;
1275 # Handle error during read configuration and validation check
1277 my ($line, $msg_code, @msg_args) = @_;
1279 if ($DEBUG_LEVEL > 0 || $PROC_STAT{initialized} == 0) {
1280 my $msg = _message_only($msg_code, @msg_args);
1281 if (defined $line && $line > 0) {
1282 print {*STDERR} _message_only('ERR0121', $CONFIG_FILE{path}, $line, $msg) . "\n";
1285 print {*STDERR} $msg . "\n";
1290 ld_log( _message('ERR0121', $CONFIG_FILE{path}, $line, q{}) );
1292 ld_log( _message($msg_code, @msg_args) );
1294 if ( $PROC_STAT{initialized} == 0 ) {
1295 ld_exit(5, _message_only('ERR0002') );
1298 die "Configuration error.\n";
1303 # Check configuration value and set default value, overwrite global config value and so on.
1305 if ( defined $CONFIG{virtual} ) {
1306 for my $v ( @{ $CONFIG{virtual} } ) {
1307 next if !defined $v;
1308 if (defined $v->{protocol} && $v->{protocol} eq 'tcp') {
1309 $v->{option}{protocol} = "-t";
1312 if ( defined $v->{option} && defined $v->{option}{protocol} && defined $v->{module} && defined $v->{module}{name} ) {
1313 my $module_option = $v->{module}{name};
1314 if ( defined $v->{module}{option} ) {
1315 $module_option .= q{ } . $v->{module}{option};
1317 $v->{option}{main} = sprintf "%s %s -m %s", $v->{option}{protocol}, get_ip_port($v), $module_option;
1318 $v->{option}{flags} = $v->{option}{main};
1319 if ( defined $v->{scheduler} ) {
1320 $v->{option}{flags} .= ' -s ' . $v->{scheduler};
1322 if ( defined $v->{maxconn} ) {
1323 $v->{option}{flags} .= ' -u ' . $v->{maxconn};
1325 if ( defined $v->{sorryserver} && defined $v->{sorryserver}{ip} && defined $v->{sorryserver}{port} ) {
1326 $v->{option}{flags} .= ' -b ' . $v->{sorryserver}{ip} . ':' . $v->{sorryserver}{port};
1328 if ( defined $v->{sorryserver}{forward} ) {
1329 $v->{option}{flags} .= ' ' . get_forward_flag( $v->{sorryserver}{forward} );
1331 if ( defined $v->{qosup} ) {
1332 $v->{option}{flags} .= ' -Q ' . $v->{qosup};
1334 if ( defined $v->{qosdown} ) {
1335 $v->{option}{flags} .= ' -q ' . $v->{qosdown};
1337 if ( defined $v->{sslconfigfile} ) {
1338 $v->{option}{flags} .= ' -z ' . $v->{sslconfigfile};
1339 $v->{other_virtual_key} .= ' ' . $v->{sslconfigfile};
1342 $v->{other_virtual_key} .= ' none';
1344 if ( defined $v->{socketoption} ) {
1345 $v->{option}{flags} .= ' -O ' . $v->{socketoption};
1346 $v->{other_virtual_key} .= ' ' . $v->{socketoption};
1349 $v->{other_virtual_key} .= ' none';
1351 if ( defined $v->{accesslog} ) {
1352 $v->{option}{flags} .= ' -L ' . $v->{accesslog};
1354 if ( defined $v->{accesslogfile} ) {
1355 $v->{option}{flags} .= ' -a ' . $v->{accesslogfile};
1356 $v->{other_virtual_key} .= ' ' . $v->{accesslogfile};
1359 $v->{other_virtual_key} .= ' none';
1361 my $option_key_flag = 0;
1362 if ( defined $v->{accesslog_rotate_type} ) {
1364 .= ' --ac-rotate-type ' . $v->{accesslog_rotate_type};
1365 $v->{other_virtual_key}
1366 .= ' --ac-rotate-type ' . $v->{accesslog_rotate_type};
1367 $option_key_flag = 1;
1369 if ( defined $v->{accesslog_rotate_max_backup_index} ) {
1371 .= ' --ac-rotate-max-backup-index '
1372 . $v->{accesslog_rotate_max_backup_index};
1373 $v->{other_virtual_key}
1374 .= ' --ac-rotate-max-backup-index '
1375 . $v->{accesslog_rotate_max_backup_index};
1376 $option_key_flag = 1;
1378 if ( defined $v->{accesslog_rotate_max_filesize} ) {
1380 .= ' --ac-rotate-max-filesize '
1381 . $v->{accesslog_rotate_max_filesize};
1382 $v->{other_virtual_key}
1383 .= ' --ac-rotate-max-filesize '
1384 . $v->{accesslog_rotate_max_filesize};
1385 $option_key_flag = 1;
1387 if ( defined $v->{accesslog_rotate_rotation_timing} ) {
1389 .= ' --ac-rotate-rotation-timing '
1390 . $v->{accesslog_rotate_rotation_timing};
1391 $v->{other_virtual_key}
1392 .= ' --ac-rotate-rotation-timing '
1393 . $v->{accesslog_rotate_rotation_timing};
1394 $option_key_flag = 1;
1396 if ( defined $v->{accesslog_rotate_rotation_timing_value} ) {
1398 .= ' --ac-rotate-rotation-timing-value '
1399 . q{"}. $v->{accesslog_rotate_rotation_timing_value}. q{"};
1400 $v->{other_virtual_key}
1401 .= ' --ac-rotate-rotation-timing-value '
1402 . $v->{accesslog_rotate_rotation_timing_value};
1403 $option_key_flag = 1;
1405 if ( $option_key_flag == 0 ) {
1406 $v->{other_virtual_key} .= ' none';
1408 if ( defined $v->{session_thread_pool_size} ) {
1409 $v->{option}{flags} .= ' --session-thread-pool-size ' . $v->{session_thread_pool_size};
1413 if ( !defined $v->{fallback} && defined $CONFIG{fallback} ) {
1414 $v->{fallback} = { %{ $CONFIG{fallback} } };
1416 if ( defined $v->{fallback} ) {
1417 for my $proto ( keys %{ $v->{fallback} } ) {
1418 $v->{fallback}{$proto}{option}{flags} = '-r ' . get_ip_port( $v->{fallback}{$proto} )
1419 . ' ' . $v->{fallback}{$proto}{option}{forward};
1422 if (defined $v->{checktype} && $v->{checktype} =~ /^\d+$/) {
1423 $v->{num_connects} = $v->{checktype};
1424 $v->{checktype} = 'combined';
1427 if ( defined $v->{login} && $v->{login} eq q{} ) {
1428 $v->{login} = defined $v->{service} && $v->{service} eq 'ftp' ? 'anonymous'
1429 : defined $v->{service} && $v->{service} eq 'sip' ? 'l7directord@' . $PROC_ENV{hostname}
1433 if ( defined $v->{passwd} && $v->{passwd} eq q{} ) {
1434 $v->{passwd} = defined $v->{service} && $v->{service} eq 'ftp' ? 'l7directord@' . $PROC_ENV{hostname}
1439 if ( defined $v->{real} ) {
1440 for my $r ( @{ $v->{real} } ) {
1441 next if !defined $r;
1442 if ( defined $r->{forward} ) {
1443 $r->{option}{forward} = get_forward_flag( $r->{forward} );
1445 if ( !defined $r->{weight} || $r->{weight} !~ /^\d+$/ ) {
1449 if ( !defined $r->{server}{port} ) {
1450 $r->{server}{port} = $v->{server}{port};
1453 $r->{option}{flags} = '-r ' . get_ip_port($r) . ' ' . $r->{option}{forward};
1456 if ( defined $v->{service} && defined $r->{server} ) {
1457 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
1458 my $ipaddress = $r->{server}{ip};
1459 if ( is_ip6($ipaddress)){
1460 $ipaddress = qq{ [$r->{server}{ip}] };
1462 $r->{url} = sprintf "%s://%s:%s/",
1463 $v->{service}, $ipaddress, $port;
1464 $r->{url} =~ s/\s//g;
1466 if ( !defined $r->{request} && defined $v->{request} ) {
1467 $r->{request} = $v->{request};
1469 if ( !defined $r->{receive} && defined $v->{receive} ) {
1470 $r->{receive} = $v->{receive};
1472 if ( defined $r->{request} ) {
1473 my $uri = $r->{request};
1474 my $service = $v->{service};
1475 if ( defined $v->{service} && $uri =~ m{^$service://} ) {
1484 # set connect count for combine check
1485 if (defined $v->{checktype} && $v->{checktype} eq 'combined') {
1486 $r->{num_connects} = undef;
1489 $r->{fail_counts} = 0;
1490 $r->{healthchecked} = 0;
1493 if ( !defined $v->{checkcount} || $v->{checkcount} <= 0 ) {
1494 $v->{checkcount} = $CONFIG{checkcount};
1496 if ( !defined $v->{checktimeout} || $v->{checktimeout} <= 0 ) {
1497 $v->{checktimeout} = $CONFIG{checktimeout};
1499 if ( !defined $v->{negotiatetimeout} || $v->{negotiatetimeout} <= 0 ) {
1500 $v->{negotiatetimeout} = $CONFIG{negotiatetimeout};
1502 if ( !defined $v->{checkinterval} || $v->{checkinterval} <= 0 ) {
1503 $v->{checkinterval} = $CONFIG{checkinterval};
1505 if ( !defined $v->{retryinterval} || $v->{retryinterval} <= 0 ) {
1506 $v->{retryinterval} = $CONFIG{retryinterval};
1508 if ( !defined $v->{quiescent} ) {
1509 $v->{quiescent} = $CONFIG{quiescent};
1514 if (defined $CONFIG{fallback}) {
1515 $CONFIG{fallback}{tcp}{option}{flags} = '-r ' . get_ip_port( $CONFIG{fallback}{tcp} )
1516 . ' ' . $CONFIG{fallback}{tcp}{option}{forward};
1520 # Removed persistent and netmask related hash entries from the structure of l7vsadm since it is not used - NTT COMWARE
1522 # Parses the output of "l7vsadm -K -n" and puts into a structure of
1523 # the following from:
1526 # (vip_address:vport) protocol module_name module_key_value => {
1527 # "scheduler" => scheduler,
1529 # rip_address:rport => {
1530 # "forward" => forwarding_mechanism,
1531 # "weight" => weight
1540 # vip_address: IP address of virtual service
1541 # vport: Port of virtual service
1542 # module_name: Depicts the name of the module (For example, pfilter)
1543 # module_key_value: Depicts the module key values (For example, --path-match xxxx)
1544 # scheduler: Scheduler for virtual service
1546 # rip_address: IP address of real server
1547 # rport: Port of real server
1548 # forwarding_mechanism: Forwarding mechanism for real server.(masq or tproxy)
1549 # weight: Weight of real server
1552 # post: l7vsadm -K -n is parsed
1553 # result: reference to structure detailed above.
1554 sub ld_read_l7vsadm {
1555 my $current_service = {};
1558 if ( !-f $PROC_ENV{l7vsadm} || !-x $PROC_ENV{l7vsadm} ) {
1559 ld_log( _message( 'FTL0101', $PROC_ENV{l7vsadm} ) );
1560 return $current_service;
1562 # read status of current l7vsadm -K -n
1563 # -K indicates Key parameters of the module included.
1564 my $list_command = $PROC_ENV{l7vsadm} . " -K -n";
1565 my $cmd_result = qx{$list_command};
1566 my @list_line = split /\n/, $cmd_result;
1567 my $other_virtual_flag = 'off';
1568 my $other_virtual_count = 0;
1569 my $other_virtual_option = undef;
1572 # [cf] Layer-7 Virtual Server version 2.0.0-0
1573 # [cf] Prot LocalAddress:Port ProtoMod Scheduler Reschedule Protomod_key_string
1574 # [cf] -> RemoteAddress:Port Forward Weight ActiveConn InactConn
1575 shift @list_line; shift @list_line; shift @list_line;
1577 for my $line (@list_line) {
1578 # check virtual service line format
1579 # [cf] TCP 192.168.0.4:12121 sessionless rr
1580 # TCP [2031:130f:876a::130b]:1231 sessionless rr
1581 #### ((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[0-9a-fA-F:])(%.+)?\]:\d{1,5}) \s+ # ip port
1585 (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}) \s+ # ip port
1586 (\w+) \s+ # protocol module
1595 (\[[0-9a-fA-F:]+(?:%.+)?\]:\d{1,5}) \s+ # ip port
1596 (\w+) \s+ # protocol module
1602 my ($proto, $ip_port, $module) = ($1, $2, $3);
1603 # vip_id MUST be same format as get_virtual_id_str
1605 $vip_id = "$proto:$ip_port:$module";
1606 $vip_id =~ s/\s+$//;
1607 $current_service->{$vip_id} = undef;
1608 $other_virtual_flag = 'on';
1609 $other_virtual_option = undef;
1610 $other_virtual_count = 0;
1613 # check real server line format
1614 # [cf] -> 192.168.0.4:7780 Masq 1 10 123456
1615 if ((defined $vip_id && $line =~ /
1618 (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5}) \s+ # ip port
1621 \d+ \s+ # active connections
1622 \d+ \s* # inactive connections
1626 ||(defined $vip_id && $line =~ /
1629 (\[[0-9a-fA-F:]+(?:%.+)?\]):(\d{1,5}) \s+ # ip port
1632 \d+ \s+ # active connections
1633 \d+ \s* # inactive connections
1637 my ($ip, $port, $forward, $weight) = ($1, $2, $3, $4);
1638 my $ip_port = "$ip:$port";
1640 server => { ip => $ip, port => $port },
1642 forward => $forward,
1644 flags => "-r $ip_port",
1645 forward => get_forward_flag($forward),
1648 $other_virtual_flag = 'off';
1649 $current_service->{$vip_id}{$ip_port} = $real;
1651 elsif ($other_virtual_flag eq 'on'){
1652 ## SSL_config_file value set D->A Command
1653 ## Socket option value set D->A Command
1654 ## Access_log_flag value set E Command
1655 ## Access_log_file value set D->A Command
1656 ## Access_log_rotate option value set D->A Command
1657 if ($other_virtual_count != 2 ) {
1659 $other_virtual_option .= $line;
1660 $current_service->{$vip_id}{other_virtual_option}
1661 = $other_virtual_option;
1663 $other_virtual_count++;
1666 return $current_service;
1669 # ld_operate_virtual
1670 # Operate virtual service on l7vsd by l7vsadm command.
1671 sub ld_operate_virtual {
1672 my ($v, $option, $success_code, $error_code) = @_;
1673 if (!defined $v || !defined $option || !defined $success_code || !defined $error_code) {
1674 ld_log( _message('ERR0501') );
1678 my $command = $PROC_ENV{l7vsadm} . " $option ";
1679 if ($option ne '-D') {
1680 $command .= $v->{option}{flags};
1683 $command .= $v->{option}{main};
1685 $command .= ' 2>&1';
1687 my ($result, $output) = command_wrapper($command);
1689 my $module_key = $v->{module}{name};
1690 if ( defined $v->{module}{key} ) {
1691 $module_key .= q{ } . $v->{module}{key};
1694 ld_log( _message($success_code, get_ip_port($v), $module_key) );
1697 ($output) = split /\n/, $output, 2;
1698 ld_log( _message($error_code, get_ip_port($v), $module_key, $output) );
1703 # Call operate virtual with add option.
1704 sub ld_add_virtual {
1706 ld_operate_virtual($v, '-A', 'INF0201', 'ERR0201');
1710 # Call operate virtual with edit option.
1711 sub ld_edit_virtual {
1713 ld_operate_virtual($v, '-E', 'INF0202', 'ERR0202');
1717 # Call operate virtual with delete option.
1718 sub ld_delete_virtual {
1720 ld_operate_virtual($v, '-D', 'INF0203', 'ERR0203');
1724 # Operate real server on l7vsd by l7vsadm command.
1725 sub ld_operate_real {
1726 my ($v, $r, $weight, $option, $success_code, $error_code) = @_;
1727 if (!defined $v || !defined $r || !defined $option || !defined $success_code || !defined $error_code) {
1728 ld_log( _message('ERR0501') );
1733 = $PROC_ENV{l7vsadm} . " $option " . $v->{option}{main} . q{ } . $r->{option}{flags};
1735 # replace weight value
1736 if (defined $weight) {
1737 $command .= ' -w ' . $weight;
1739 $command .= ' 2>&1';
1741 my ($result, $output) = command_wrapper($command);
1743 my $module_key = $v->{module}{name};
1744 if ( defined $v->{module}{key} ) {
1745 $module_key .= q{ } . $v->{module}{key};
1748 ld_log( _message($success_code, get_ip_port($r), $r->{forward}, get_ip_port($v), $module_key, $weight) );
1751 ($output) = split /\n/, $output, 2;
1752 ld_log( _message($error_code, get_ip_port($r), $r->{forward}, get_ip_port($v), $module_key, $output) );
1757 # Call operate real with add option.
1759 my ($v, $r, $weight) = @_;
1760 ld_operate_real($v, $r, $weight, '-a', 'INF0204', 'ERR0204');
1764 # Call operate real with edit option.
1766 my ($v, $r, $weight) = @_;
1767 ld_operate_real($v, $r, $weight, '-e', 'INF0205', 'ERR0205');
1771 # Call operate real with delete option.
1772 sub ld_delete_real {
1774 ld_operate_real($v, $r, undef, '-d', 'INF0206', 'ERR0206');
1778 # Check l7vsd by l7vsadm command and create virtual service on l7vsd.
1780 # read status of current l7vsadm -K -n
1781 my $current_service = ld_read_l7vsadm();
1782 if (!defined $current_service ) {
1783 ld_log( _message('FTL0201') );
1787 my %old_health_check = %HEALTH_CHECK;
1790 # make sure virtual servers are up to date
1791 if ( defined $CONFIG{virtual} ) {
1792 for my $nv ( @{ $CONFIG{virtual} } ) {
1793 my $vip_id = get_virtual_id_str($nv);
1794 if (!defined $vip_id) {
1795 ld_log( _message('ERR0502') );
1799 if ( exists( $current_service->{$vip_id} )){
1800 if(( defined $current_service->{$vip_id}{other_virtual_option}
1801 && defined $nv->{other_virtual_key})
1802 && $current_service->{$vip_id}{other_virtual_option}
1803 ne $nv->{other_virtual_key} ) {
1804 ld_delete_virtual($nv);
1805 # no such service, create a new one
1806 ld_add_virtual($nv);
1809 # service already exists, modify it
1810 ld_edit_virtual($nv);
1817 for my $check ( keys %{ $current_service } ){
1818 next if !defined $check ;
1819 $del_vip_id = $check;
1820 # protcol name delete
1821 $check =~ s/(^[\w]+:)//;
1822 ## module name delete
1823 $check =~ s/(:[\w]+$)//;
1824 $newipport = get_ip_port($nv);
1825 if ( $check eq $newipport) {
1826 for ( @{ $CONFIG{old_virtual} } ) {
1827 my $virtual_id = get_virtual_id_str($_);
1828 next if !defined $virtual_id ;
1829 if ( $del_vip_id eq $virtual_id ) {
1830 ld_delete_virtual($_);
1831 delete $current_service->{$del_vip_id};
1836 # no such service, create a new one
1837 ld_add_virtual($nv);
1840 my $or = $current_service->{$vip_id} || {};
1842 # Not delete fallback server from l7vsd if exist
1843 my $fallback = fallback_find($nv);
1844 if (defined $fallback) {
1845 my $fallback_ip_port = get_ip_port( $fallback->{ $nv->{protocol} } );
1846 delete $or->{$fallback_ip_port};
1850 if ( defined $nv->{real} ) {
1852 for my $nr ( @{ $nv->{real} } ) {
1853 delete $or->{ get_ip_port($nr) };
1855 my $health_check_id = get_health_check_id_str($nv, $nr);
1856 if (!defined $health_check_id) {
1857 ld_log( _message('ERR0503') );
1861 # search same health check process
1862 if ( exists $HEALTH_CHECK{$health_check_id} ) {
1863 # same health check process exist
1864 # then check real server and virtual service ($r, $v)
1865 for my $v_r_pair ( @{ $HEALTH_CHECK{$health_check_id}{manage} } ) {
1866 # completely same. check next real server
1867 next CHECK_REAL if ($nv eq $v_r_pair->[0] && $nr eq $v_r_pair->[1]);
1870 # add real server and virtual service to management list
1871 push @{ $HEALTH_CHECK{$health_check_id}{manage} }, [$nv, $nr];
1874 # add to health check process list
1875 $HEALTH_CHECK{$health_check_id}{manage} = [ [$nv, $nr] ];
1880 my $work_ip = undef;
1881 # remove remaining entries for real servers
1882 for my $remove_real_ip_port (keys %$or) {
1883 if ( 'other_virtual_option' eq $remove_real_ip_port ){
1886 $work_ip = $or->{$remove_real_ip_port}{server}{ip};
1887 if ( !is_ip ($work_ip)
1888 && !is_ip6($work_ip)){
1891 ld_delete_real( $nv, $or->{$remove_real_ip_port} );
1892 delete $or->{$remove_real_ip_port};
1895 delete $current_service->{$vip_id};
1899 # terminate old health check process
1900 # TODO should compare old and new, and only if different then re-create process...
1901 for my $id (keys %old_health_check) {
1902 # kill old health check process
1903 if ( defined $old_health_check{$id}{pid} ) {
1904 # TODO cannot kill process during pinging to unreachable host?
1906 local $SIG{ALRM} = sub { die; };
1907 kill 15, $old_health_check{$id}{pid};
1910 waitpid $old_health_check{$id}{pid}, 0;
1915 kill 9, $old_health_check{$id}{pid};
1916 waitpid $old_health_check{$id}{pid}, WNOHANG;
1922 # remove remaining entries for virtual servers
1923 if ( defined $CONFIG{old_virtual} ) {
1924 for my $nv ( @{ $CONFIG{old_virtual} } ) {
1925 my $vip_id = get_virtual_id_str($nv);
1926 next if !defined $vip_id ;
1927 if ( exists $current_service->{$vip_id} ) {
1928 # service still exists, remove it
1929 ld_delete_virtual($nv);
1933 delete $CONFIG{old_virtual};
1937 # Run l7directord command to child process.
1938 # Child process is not health check process,
1939 # but sub config (specified by configuration with `execute') process.
1940 sub ld_cmd_children {
1941 my $command_type = shift;
1942 my $execute = shift;
1944 # instantiate other l7directord, if specified
1945 if (!defined $execute) {
1946 if ( defined $CONFIG{execute} ) {
1947 for my $sub_config ( keys %{ $CONFIG{execute} } ) {
1948 if (defined $command_type && defined $sub_config) {
1949 my $command = $PROC_ENV{l7directord} . " $sub_config $command_type";
1950 system_wrapper($command);
1956 for my $sub_config ( keys %$execute ) {
1957 if (defined $command_type && defined $sub_config) {
1958 my $command = $PROC_ENV{l7directord} . " $sub_config $command_type";
1959 system_wrapper($command);
1966 # Remove virtual service for stopping this program.
1968 my $srv = ld_read_l7vsadm();
1969 if (!defined $srv) {
1970 ld_log( _message('FTL0201') );
1973 if ( defined $CONFIG{virtual} ) {
1974 for my $v ( @{ $CONFIG{virtual} } ) {
1975 my $vid = get_virtual_id_str($v);
1976 if (!defined $vid) {
1977 ld_log( _message('ERR0502') );
1980 if ( exists $srv->{$vid} ) {
1981 for my $rid ( keys %{ $srv->{$vid} } ) {
1987 ld_delete_real( $v, $srv->{$vid}{$rid} );
1990 ld_delete_virtual($v);
1996 # Main function of this program.
1997 # Create virtual service and loop below 3 steps.
1998 # 1. Check health check sub process and (re-)create sub process as needed
1999 # 2. Check signal in sleep and start to terminate program or reload config as needed
2000 # 3. Check config file and reload config as needed
2004 # Main failover checking code
2007 # manage real server check process.
2010 my @id_lists = check_child_process();
2011 # if child process is not running
2013 create_check_process(@id_lists);
2015 my $signal = sleep_and_check_signal( $CONFIG{configinterval} );
2016 last MAIN_LOOP if defined $signal && $signal eq 'halt';
2017 last REAL_CHECK if defined $signal && $signal eq 'reload';
2018 last REAL_CHECK if check_cfgfile();
2025 # signal TERM to child process
2026 for my $id (keys %HEALTH_CHECK) {
2027 if ( defined $HEALTH_CHECK{$id}{pid} ) {
2028 # TODO cannot kill process during pinging to unreachable host?
2030 local $SIG{ALRM} = sub { die; };
2031 kill 15, $HEALTH_CHECK{$id}{pid};
2034 waitpid $HEALTH_CHECK{$id}{pid}, 0;
2039 kill 9, $HEALTH_CHECK{$id}{pid};
2040 waitpid $HEALTH_CHECK{$id}{pid}, WNOHANG;
2048 # check_child_process
2049 # Check health check process by signal zero.
2050 # return: Health check id list that (re-)created later.
2051 sub check_child_process {
2052 my @down_process_ids = ();
2053 for my $id (sort keys %HEALTH_CHECK) {
2054 if ( !defined $HEALTH_CHECK{$id}{pid} ) {
2056 ld_log( _message('INF0401', $id) );
2057 push @down_process_ids, $id;
2061 my $signaled = kill 0, $HEALTH_CHECK{$id}{pid};
2062 if ($signaled != 1) {
2063 # maybe killed from outside
2064 ld_log( _message('ERR0603', $HEALTH_CHECK{$id}{pid}, $id) );
2065 push @down_process_ids, $id;
2069 return @down_process_ids;
2072 # create_check_process
2073 # Fork health check sub process.
2074 # And health check sub process run health_check sub function.
2075 sub create_check_process {
2077 for my $health_check_id (@id_lists) {
2080 ld_log( _message('INF0402', $pid, $health_check_id) );
2081 $HEALTH_CHECK{$health_check_id}{pid} = $pid;
2084 $PROC_STAT{parent_pid} = $PROC_STAT{pid};
2085 $PROC_STAT{pid} = $PID;
2086 health_check( $HEALTH_CHECK{$health_check_id}{manage} );
2089 ld_log( _message('ERR0604', $health_check_id) );
2096 # Main function of health check process.
2099 # 2. Status change and reflect to l7vsd as needed.
2100 # 3. Check signal in sleep.
2101 # pre: v_r_list: reference list of virtual service and real server pair
2102 # $v_r_list = [ [$virtual, $real], [$virtual, $real], ... ];
2104 # MUST use POSIX::_exit when terminate sub process.
2106 my $v_r_list = shift;
2107 if (!defined $v_r_list) {
2108 ld_log( _message('ERR0501') );
2109 ld_log( _message('FTL0001') );
2113 # you can use any virtual, real pair in $v_r_list.
2114 my ($v, $r) = @{ $v_r_list->[0] };
2115 if (!defined $v || !defined $r) {
2116 ld_log( _message('FTL0002') );
2120 my $health_check_func = get_check_func($v);
2121 my $current_status = get_status($v_r_list);
2123 my $status = 'STARTING';
2124 my $type = $v->{checktype} eq 'negotiate' ? $v->{service}
2125 : $v->{checktype} eq 'combined' ? $v->{service} . '(combined)'
2128 $PROGRAM_NAME = 'l7directord: ' . $type . ':' . get_ip_port($r) . ':' . $status;
2132 my $service_status = &$health_check_func($v, $r);
2134 if ($service_status == $SERVICE_DOWN) {
2135 undef $r->{num_connects};
2136 if (!defined $current_status || $current_status == $SERVICE_UP) {
2137 $r->{fail_counts}++;
2138 if ($r->{fail_counts} >= $v->{checkcount}) {
2139 ld_log( _message( 'ERR0602', get_ip_port($r) ) );
2140 service_set($v_r_list, 'down');
2141 $current_status = $SERVICE_DOWN;
2143 $r->{fail_counts} = 0;
2146 ld_log( _message( 'WRN1001', get_ip_port($r), $v->{checkcount} - $r->{fail_counts} ) );
2147 $status = sprintf "NG[%d/%d]", $r->{fail_counts}, $v->{checkcount};
2151 if ($service_status == $SERVICE_UP) {
2152 $r->{fail_counts} = 0;
2153 if (!defined $current_status || $current_status == $SERVICE_DOWN) {
2154 ld_log( _message( 'ERR0601', get_ip_port($r) ) );
2155 service_set($v_r_list, 'up');
2156 $current_status = $SERVICE_UP;
2161 $PROGRAM_NAME = 'l7directord: ' . $type . ':' . get_ip_port($r) . ':' . $status;
2163 my $sleeptime = $r->{fail_counts} ? $v->{retryinterval} : $v->{checkinterval};
2164 last if (sleep_and_check_signal($sleeptime, 1) eq 'halt');
2166 my $parent_process = kill 0, $PROC_STAT{parent_pid};
2167 if ($parent_process != 1) {
2168 ld_log( _message( 'FTL0003', $PROC_STAT{parent_pid} ) );
2173 ld_log( _message('INF0007') );
2177 # sleep_and_check_signal
2178 # Check signal flag each 0.1 secound with sleeping specified seconds.
2179 sub sleep_and_check_signal {
2180 my ($sec, $is_child) = @_;
2181 if (!defined $sec || $sec !~ /^\d+$/) {
2182 ld_log( _message('ERR0501') );
2187 while ($sec > $sleeped) {
2188 # non-blocking wait for zombie process
2189 waitpid(-1, WNOHANG); # TODO should move to sigchld handler?
2192 if ( defined $PROC_STAT{halt} ) {
2193 ld_log( _message( 'WRN0001', $CONFIG_FILE{path}, $PROC_STAT{halt} ) );
2198 if ( defined $PROC_STAT{halt} ) {
2199 ld_log( _message( 'WRN0001', $CONFIG_FILE{path}, $PROC_STAT{halt} ) );
2202 if ( defined $PROC_STAT{reload} ) {
2203 ld_log( _message( 'WRN0002', $CONFIG_FILE{path}, $PROC_STAT{reload} ) );
2204 undef $PROC_STAT{reload};
2215 # Determine check function by checktype and service.
2216 sub get_check_func {
2219 ld_log( _message('ERR0501') );
2223 my $type = $v->{checktype};
2224 my $service_func = {
2225 http => \&check_http,
2226 https => \&check_http,
2228 imap => \&check_imap,
2229 smtp => \&check_smtp,
2231 ldap => \&check_ldap,
2232 nntp => \&check_nntp,
2235 mysql => \&check_mysql,
2236 pgsql => \&check_pgsql,
2239 if ( defined $type && ($type eq 'negotiate' || $type eq 'combined') ) {
2240 if (defined $v->{service} && exists $service_func->{ $v->{service} } ) {
2241 my $negotiate_func = $service_func->{ $v->{service} };
2242 if ($type eq 'negotiate') {
2243 return $negotiate_func;
2245 elsif ($type eq 'combined') {
2246 my $combined_func = make_combined_func($negotiate_func);
2247 return $combined_func;
2251 return \&check_none;
2255 if (defined $type && $type eq 'custom') {
2256 my $custom_func = make_custom_func( $v->{customcheck} );
2257 return $custom_func;
2260 if (defined $type && $type eq 'connect') {
2261 if (defined $v->{protocol} && $v->{protocol} eq 'tcp') {
2262 return \&check_connect;
2265 return \&check_ping;
2269 if (defined $type && $type eq 'ping') {
2270 return \&check_ping;
2273 if (defined $type && $type eq 'off') {
2277 if (defined $type && $type eq 'on') {
2281 return \&check_none;
2284 # make_combined_func
2285 # Create combined function.
2286 sub make_combined_func {
2287 my $negotiate_func = shift;
2288 if (!defined $negotiate_func) {
2289 ld_log( _message('ERR0504') );
2290 return \&check_connect;
2294 my $combined_func = sub {
2296 my $timing = $v->{num_connects};
2297 my $connected = $r->{num_connects};
2299 if (!defined $connected ||
2300 (defined $timing && $timing <= $connected) ) {
2301 $r->{num_connects} = 0;
2302 return &$negotiate_func($v, $r);
2305 $r->{num_connects}++;
2306 return check_connect($v, $r);
2310 return $combined_func;
2314 # Create custom check function.
2315 sub make_custom_func {
2316 my $customcheck = shift;
2317 if (!defined $customcheck) {
2318 ld_log( _message('ERR0505') );
2323 my $custom_func = sub {
2325 my $status = get_status([[$v, $r]]);
2326 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2327 my $ip_port = $r->{server}{ip} . ':' . $port;
2330 $customcheck =~ s/_IP_/$r->{server}{ip}/g;
2331 $customcheck =~ s/_PORT_/$port/g;
2335 local $SIG{__DIE__} = 'DEFAULT';
2336 local $SIG{ALRM} = sub { die "custom check timeout\n"; };
2338 alarm $v->{checktimeout};
2339 $res = system_wrapper($customcheck);
2344 ld_log( _message('WRN3301', $v->{checktimeout}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2345 return $SERVICE_DOWN;
2349 ld_log( _message('WRN3302', $customcheck, $res) ) if (!defined $status || $status eq $SERVICE_UP);
2350 return $SERVICE_DOWN;
2352 ld_log( _message('WRN0215', $ip_port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2356 return $custom_func;
2360 # HTTP service health check.
2361 # Send GET/HEAD request, and check response
2363 require LWP::UserAgent;
2365 if ( $DEBUG_LEVEL > 2 ) {
2366 LWP::Debug::level('+');
2369 my $status = get_status([[$v, $r]]);
2371 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2373 if ( $r->{url} !~ m{^https?://([^:/]+)} ) {
2374 ld_log( _message( 'WRN1101', $r->{url}, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2375 return $SERVICE_DOWN;
2378 my $virtualhost = defined $v->{virtualhost} ? $v->{virtualhost} : $host;
2380 ld_debug(2, "check_http: url=\"$r->{url}\" " . "virtualhost=\"$virtualhost\"");
2383 if ( is_ip($r->{server}{ip})){
2384 my $ua = LWP::UserAgent->new( timeout => $v->{negotiatetimeout} );
2385 my $req = new HTTP::Request( $v->{httpmethod}, $r->{url}, [ Host => $virtualhost ] );
2388 # LWP makes ungaurded calls to eval
2389 # which throw a fatal exception if they fail
2390 local $SIG{__DIE__} = 'DEFAULT';
2391 local $SIG{ALRM} = sub { die "Can't connect to $r->{server}{ip}:$port (connect: timeout)\n"; };
2393 alarm $v->{negotiatetimeout};
2394 $res = $ua->request($req);
2400 $status_line = $res->status_line;
2401 $status_line =~ s/[\r\n]//g;
2403 my $response = $v->{httpmethod} eq "HEAD" ? $res->as_string : $res->content;
2404 my $recstr = $r->{receive};
2405 if (!$res->is_success) {
2406 ld_log( _message( 'WRN1102', $status_line, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2407 return $SERVICE_DOWN;
2409 elsif (defined $recstr && $response !~ /$recstr/) {
2410 ld_log( _message( 'WRN1103', $recstr, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2411 ld_debug(3, "HTTP Response " . $response);
2412 ld_debug(2, "check_http: $r->{url} is down\n");
2413 return $SERVICE_DOWN;
2418 ## Wget Comand Check
2419 my $https_option = '';
2420 if ( $v->{service} eq 'https'){
2421 $https_option = '--no-check-certificate';
2423 my $recstr = $r->{receive};
2424 my $command = "/usr/bin/wget " . "-q -t 1 --timeout $v->{negotiatetimeout} $https_option ". $r->{url} . ' -O - ';
2425 my ($result, $output) = command_wrapper( $command );
2427 ld_log( _message( 'WRN1103', 'web', $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2428 return $SERVICE_DOWN;
2430 elsif (defined $recstr && $output !~ /$recstr/) {
2431 ld_log( _message( 'WRN1103', $recstr, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2432 ld_debug(2, "check_http: $r->{url} is down\n");
2433 return $SERVICE_DOWN;
2436 $status_line = '200 OK';
2440 ld_debug(2, "check_http: $r->{url} is up\n");
2441 ld_log( _message( 'WRN0203', $status_line, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2446 # SMTP service health check.
2447 # Connect SMTP server and check first response
2451 my $status = get_status([[$v, $r]]);
2453 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2455 ld_debug(2, "Checking http: server=$r->{server}{ip} port=$port");
2456 my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2458 my $smtp = Net::SMTP->new(
2461 Timeout => $v->{negotiatetimeout},
2462 Debug => $debug_flag,
2465 ld_log( _message('WRN1201', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2466 return $SERVICE_DOWN;
2470 ld_log( _message('WRN0204', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2475 # POP3 service health check.
2476 # Connect POP3 server and login if user-pass specified.
2480 my $status = get_status([[$v, $r]]);
2482 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2484 ld_debug(2, "Checking pop server=$r->{server}{ip} port=$port");
2485 my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2487 my $pop = Net::POP3->new(
2490 Timeout => $v->{negotiatetimeout},
2491 Debug => $debug_flag,
2494 ld_log( _message('WRN1301', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2495 return $SERVICE_DOWN;
2498 if ( defined $v->{login} && defined $v->{passwd} && $v->{login} ne q{} ) {
2499 $pop->user( $v->{login} );
2500 my $num = $pop->pass( $v->{passwd} );
2501 if (!defined $num) {
2502 ld_log( _message('WRN1302', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2504 return $SERVICE_DOWN;
2509 ld_log( _message('WRN0205', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2514 # IMAP service health check.
2515 # Connect IMAP server and login if user-pass specified.
2517 require Mail::IMAPClient;
2519 my $status = get_status([[$v, $r]]);
2521 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2523 ld_debug(2, "Checking imap server=$r->{server}{ip} port=$port");
2524 my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2528 local $SIG{ALRM} = sub { die "Connection timeout\n"; };
2530 alarm $v->{negotiatetimeout};
2531 $imap = Mail::IMAPClient->new(
2532 Server => $r->{server}{ip},
2534 Timeout => $v->{negotiatetimeout},
2535 Debug => $debug_flag,
2541 ld_log( _message('WRN1403', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2542 return $SERVICE_DOWN;
2546 ld_log( _message('WRN1401', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2547 return $SERVICE_DOWN;
2550 if ( defined $v->{login} && defined $v->{passwd} && $v->{login} ne q{} ) {
2551 $imap->User( $v->{login} );
2552 $imap->Password( $v->{passwd} );
2553 my $authres = $imap->login();
2555 ld_log( _message('WRN1402', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2557 return $SERVICE_DOWN;
2562 ld_log( _message('WRN0206', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2567 # LDAP service health check.
2568 # Connect LDAP server and search if base-DN specified by 'request'
2572 my $status = get_status([[$v, $r]]);
2574 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2576 ld_debug(2, "Checking ldap server=$r->{server}{ip} port=$port");
2577 my $debug_flag = $DEBUG_LEVEL ? 15 : 0;
2579 my $ldap = Net::LDAP->new(
2582 timeout => $v->{negotiatetimeout},
2583 debug => $debug_flag,
2586 ld_log( _message('WRN1501', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2587 return $SERVICE_DOWN;
2592 local $SIG{ALRM} = sub { die "Connection timeout\n"; };
2594 alarm $v->{negotiatetimeout};
2595 $mesg = $ldap->bind;
2600 ld_log( _message('WRN1502', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2601 return $SERVICE_DOWN;
2604 if ($mesg->is_error) {
2605 ld_log( _message('WRN1503', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2606 return $SERVICE_DOWN;
2609 if ( defined $r->{request} && $r->{request} ne q{} ) {
2610 ld_debug( 4, "Base : " . $r->{request} );
2611 my $result = $ldap->search(
2612 base => $r->{request},
2614 filter => '(objectClass=*)',
2617 if ($result->count != 1) {
2618 ld_log( _message('WRN1504', $result->count, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2620 return $SERVICE_DOWN;
2623 if ( defined $r->{receive} ) {
2624 my $href = $result->as_struct;
2625 my @arrayOfDNs = keys %$href;
2626 my $recstr = $r->{receive};
2627 if ($recstr =~ /.+/ && $arrayOfDNs[0] !~ /$recstr/) {
2628 ld_log( _message('WRN1505', $recstr, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2630 return $SERVICE_DOWN;
2636 ld_log( _message('WRN0207', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2641 # NNTP service health check.
2642 # Connect NNTP server and check response start with '2**'
2647 my $status = get_status([[$v, $r]]);
2649 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2651 ld_debug(2, "Checking nntp server=$r->{server}{ip} port=$port");
2653 my $sock = ld_open_socket( $r->{server}{ip}, $port, $v->{protocol}, $v->{negotiatetimeout} );
2655 ld_log( _message('WRN1601', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2656 return $SERVICE_DOWN;
2659 ld_debug(3, "Connected to $r->{server}{ip} (port $port)");
2660 my $select = IO::Select->new();
2661 $select->add($sock);
2662 if ( !defined $select->can_read( $v->{negotiatetimeout} ) ) {
2663 ld_log( _message('WRN1602', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2664 $select->remove($sock);
2666 return $SERVICE_DOWN;
2670 sysread $sock, $buf, 64;
2671 $select->remove($sock);
2673 my ($response) = split /[\r\n]/, $buf;
2675 if ($response !~ /^2/) {
2676 ld_log( _message('WRN1603', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2677 return $SERVICE_DOWN;
2680 ld_log( _message('WRN0208', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2685 # MySQL service health check.
2686 # call check_sql and use MySQL driver
2688 return check_sql(@_, 'mysql', 'database');
2692 # PostgreSQL service health check.
2693 # call check_sql and use PostgreSQL driver
2695 return check_sql(@_, 'Pg', 'dbname');
2699 # DBI service health check.
2700 # Login DB and send query if query specified by 'request', check result row number same as 'receive'
2703 my ($v, $r, $dbd, $dbname) = @_;
2704 my $status = get_status([[$v, $r]]);
2706 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2708 if ( !defined $v->{login} || !defined $v->{passwd} || !defined $v->{database} ||
2709 $v->{login} eq q{} || $v->{database} eq q{} ) {
2710 ld_log( _message('WRN1701', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2711 return $SERVICE_DOWN;
2714 ld_debug(2, "Checking $v->{server}{ip} server=$r->{server}{ip} port=$port\n");
2716 my $mask = POSIX::SigSet->new(SIGALRM);
2717 my $action = POSIX::SigAction->new(
2718 sub { die "Connection timeout\n" },
2721 my $oldaction = POSIX::SigAction->new();
2722 sigaction(SIGALRM, $action, $oldaction);
2726 alarm $v->{negotiatetimeout};
2728 DBI->trace(15) if $DEBUG_LEVEL;
2729 $dbh = DBI->connect( "dbi:$dbd:$dbname=$v->{database};host=$r->{server}{ip};port=$port", $v->{login}, $v->{passwd} );
2732 if (!defined $dbh) {
2734 sigaction(SIGALRM, $oldaction);
2735 ld_log( _message('WRN1702', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2739 local $dbh->{TraceLevel} = $DEBUG_LEVEL ? 15 : 0;
2743 if ( defined $r->{request} && $r->{request} ne q{} ) {
2744 my $sth = $dbh->prepare( $r->{request} );
2745 $rows = $sth->execute;
2752 sigaction(SIGALRM, $oldaction);
2754 if ( defined $r->{request} && $r->{request} ne q{} ) {
2755 ld_debug(4, "Database search returned $rows rows");
2757 ld_log( _message('WRN1703', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2760 # If user defined a receive string (number of rows returned), only do
2761 # the check if the previous fetchall_arrayref succeeded.
2762 if (defined $r->{receive} && $r->{receive} =~ /^\d+$/) {
2763 # Receive string specifies an exact number of rows
2764 if ( $rows ne $r->{receive} ) {
2765 ld_log( _message('WRN1704', $r->{receive}, $rows, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2772 sigaction(SIGALRM, $oldaction);
2774 if ($EVAL_ERROR eq "Connection timeout\n") {
2775 ld_log( _message('WRN1705', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2777 return $SERVICE_DOWN;
2780 ld_log( _message('WRN0209', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2785 # Connect service health check.
2786 # Just connect port and close.
2789 my $status = get_status([[$v, $r]]);
2791 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2793 ld_debug(2, "Checking connect: real server=$r->{server}{ip}:$port");
2795 my $sock = ld_open_socket( $r->{server}{ip}, $port, $v->{protocol}, $v->{checktimeout} );
2796 if (!defined $sock) {
2797 ld_log( _message('WRN3201', $ERRNO, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2799 return $SERVICE_DOWN;
2803 ld_debug(3, "Connected to: (port $port)");
2805 ld_log( _message('WRN0210', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2810 # SIP service health check.
2811 # Send SIP OPTIONS request and check 200 response
2814 my $status = get_status([[$v, $r]]);
2816 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2818 ld_debug(2, "Checking sip server=$r->{server}{ip} port=$port");
2820 if ( !defined $v->{login} ) {
2821 ld_log( _message('WRN1801', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2822 return $SERVICE_DOWN;
2825 my $sock = ld_open_socket( $r->{server}{ip}, $port, $v->{protocol}, $v->{negotiatetimeout} );
2826 if (!defined $sock) {
2827 ld_log( _message('WRN1802', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2828 return $SERVICE_DOWN;
2831 my $sip_s_addr = $sock->sockhost;
2832 my $sip_s_port = $sock->sockport;
2834 ld_debug(3, "Connected from $sip_s_addr:$sip_s_port to " . $r->{server} . ":$port");
2836 my $id = $v->{login};
2838 "OPTIONS sip:$id SIP/2.0\r\n"
2839 . "Via: SIP/2.0/UDP $sip_s_addr:$sip_s_port;branch=z9hG4bKhjhs8ass877\r\n"
2840 . "Max-Forwards: 70\r\n"
2841 . "To: <sip:$id>\r\n"
2842 . "From: <sip:$id>;tag=1928301774\r\n"
2843 . "Call-ID: a84b4c76e66710\r\n"
2844 . "CSeq: 63104 OPTIONS\r\n"
2845 . "Contact: <sip:$id>\r\n"
2846 . "Accept: application/sdp\r\n"
2847 . "Content-Length: 0\r\n"
2850 ld_debug(3, "Request:\n$request");
2854 local $SIG{__DIE__} = 'DEFAULT';
2855 local $SIG{ALRM } = sub { die "Connection timeout\n"; };
2856 ld_debug(4, "Timeout is $v->{negotiatetimeout}");
2857 alarm $v->{negotiatetimeout};
2859 print {$sock} $request;
2860 $response = <$sock>;
2864 ld_debug(3, "Response:\n$response");
2866 if ( $response !~ m{^SIP/2\.0 200 OK} ) {
2867 ld_log( _message('WRN1803', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2873 if ($EVAL_ERROR eq "Connection timeout\n") {
2874 ld_log( _message('WRN1804', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2876 return $SERVICE_DOWN;
2879 ld_log( _message('WRN0211', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2884 # FTP service health check.
2885 # Login server and get file if 'request' specified, and check file include 'receive' string
2889 my $status = get_status([[$v, $r]]);
2891 my $ip_port = get_ip_port($r, $v->{checkport});
2893 if (is_ip6($r->{server}{ip}) ){
2895 ## use 'lftp' Command
2897 ## -e ' set net:max-retries 1;
2898 ## set net:reconnect-interval-multiplier 1;
2899 ## set cmd:fail-exit true;
2900 ## set net:reconnect-interval-base 1;
2902 ## -u user,passwd ipv6addr >/dev/null 2>&1
2904 my $ftp_command = "lftp ";
2905 my $ftp_environment1 = "-e \"set net:max-retries 2;";
2906 my $ftp_environment2 = "set net:reconnect-interval-multiplier 1;";
2907 my $ftp_environment3 = "set cmd:fail-exit true;";
2908 my $ftp_environment4 = "set net:reconnect-interval-base $v->{negotiatetimeout};";
2909 my $ftp_environment5 = "ls;ls;exit\" ";
2910 my $ftp_parameter = "-u $v->{login},$v->{passwd} $ip_port >/dev/null 2>&1";
2911 $ftp_command .= $ftp_environment1 . $ftp_environment2. $ftp_environment3. $ftp_environment4. $ftp_environment5. $ftp_parameter;
2913 ## print "ftpCommand:". $ftp_command;
2914 if( system_wrapper( $ftp_command )) {
2915 ld_log( _message('WRN3101', $v->{checktimeout}, $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_UP);
2916 return $SERVICE_DOWN;
2918 return $SERVICE_UP ;
2922 ld_debug(2, "Checking ftp server=$ip_port");
2923 my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2925 if ( !defined $v->{login} || !defined $v->{passwd} || $v->{login} eq q{} ) {
2926 ld_log( _message('WRN1901', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2927 return $SERVICE_DOWN;
2930 my $ftp = Net::FTP->new(
2932 Timeout => $v->{negotiatetimeout},
2934 Debug => $debug_flag,
2936 if (!defined $ftp) {
2937 ld_log( _message('WRN1902', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2938 return $SERVICE_DOWN;
2940 if ( !$ftp->login( $v->{login}, $v->{passwd} ) ) {
2941 ld_log( _message('WRN1903', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2943 return $SERVICE_DOWN;
2945 if ( !$ftp->cwd('/') ) {
2946 ld_log( _message('WRN1904', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2948 return $SERVICE_DOWN;
2950 if ( $r->{request} ) {
2953 local $SIG{__DIE__} = 'DEFAULT';
2954 local $SIG{ALRM } = sub { die "Connection timeout\n"; };
2955 alarm $v->{negotiatetimeout};
2957 open my $tmp, '+>', undef;
2959 if ( !$ftp->get( $r->{request}, *$tmp ) ) {
2961 ld_log( _message('WRN1905', $r->{request}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2966 elsif ( $r->{receive} ) {
2969 my $memory = <$tmp>;
2971 if ($memory !~ /$r->{receive}/) {
2974 ld_log( _message('WRN1906', $r->{receive}, $r->{request}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2982 my $error_message = $EVAL_ERROR;
2983 $error_message =~ s/[\r\n]//g;
2984 if ($error_message eq 'Connection timeout') {
2985 ld_log( _message('WRN1908', $v->{negotiatetimeout}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2988 ld_log( _message('WRN1907', $error_message, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2990 return $SERVICE_DOWN;
2994 return $SERVICE_DOWN;
2999 ld_log( _message('WRN0212', $ip_port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
3004 # DNS service health check.
3005 # Connect server and search 'request' A or PTR record and check result include 'response' string
3008 my $status = get_status([[$v, $r]]);
3010 my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
3013 # Net::DNS makes ungaurded calls to eval
3014 # which throw a fatal exception if they fail
3015 local $SIG{__DIE__} = 'DEFAULT';
3018 my $res = Net::DNS::Resolver->new();
3024 if ( !defined $r->{request} || $r->{request} eq q{} || !defined $r->{receive} || $r->{receive} eq q{} ) {
3025 ld_log( _message('WRN2001', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3026 return $SERVICE_DOWN;
3028 ld_debug( 2, qq(Checking dns: request="$r->{request}" receive="$r->{receive}"\n) );
3032 local $SIG{__DIE__} = 'DEFAULT';
3033 local $SIG{ALRM } = sub { die "Connection timeout\n"; };
3034 alarm $v->{negotiatetimeout};
3035 $res->nameservers( $r->{server}{ip} );
3037 $packet = $res->search( $r->{request} );
3042 if ($EVAL_ERROR eq "Connection timeout\n") {
3043 ld_log( _message('WRN2002', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3046 ld_log( _message('WRN2003', $EVAL_ERROR, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3048 return $SERVICE_DOWN;
3051 ld_log( _message('WRN2004', $r->{request}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3052 return $SERVICE_DOWN;
3056 for my $rr ($packet->answer) {
3057 if ( ( $rr->type eq 'A' && $rr->address eq $r->{receive} )
3058 || ( $rr->type eq 'PTR' && $rr->ptrdname eq $r->{receive} ) ) {
3064 ld_log( _message('WRN2005', $r->{receive}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
3065 return $SERVICE_DOWN;
3068 ld_log( _message('WRN0213', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
3073 # ICMP ping service health check.
3074 # Ping server and check response.
3078 my $status = get_status([[$v, $r]]);
3080 ld_debug( 2, qq(Checking ping: host="$r->{server}{ip}" checktimeout="$v->{checktimeout}"\n) );
3082 if ( is_ip( $r->{server}{ip})) {
3085 my $p = Net::Ping->new('icmp', 1);
3086 if ( !$p->ping( $r->{server}{ip}, $v->{checktimeout} ) ) {
3087 ld_log( _message('WRN3101', $v->{checktimeout}, $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_UP);
3088 return $SERVICE_DOWN;
3094 = sprintf "ping6 %s -c %d > /dev/null 2>&1",
3098 if( system_wrapper( $command )) {
3099 ld_log( _message('WRN3101', $v->{checktimeout}, $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_UP);
3100 return $SERVICE_DOWN;
3104 ld_log( _message('WRN0214', $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
3109 # Dummy function to check service if service type is none.
3110 # Just activates the real server
3113 ld_debug(2, "Checking none");
3118 # Check nothing and always return $SERVICE_DOWN
3121 return $SERVICE_DOWN;
3125 # Check nothing and always return $SERVICE_UP
3132 # Used to bring up and down real servers.
3133 # This is the function you should call if you want to bring a real
3134 # server up or down.
3135 # This function is safe to call regrdless of the current state of a
3137 # Do _not_ call _service_up or _service_down directly.
3138 # pre: v_r_list: virtual and real pair list
3139 # [ [$v, $r], [$v, $r] ... ]
3141 # up to bring the real service up
3142 # down to bring the real service up
3143 # post: The real server is brough up or down for each virtual service
3147 my ($v_r_list, $state) = @_;
3149 if (defined $state && $state eq 'up') {
3150 _service_up($v_r_list);
3152 elsif (defined $state && $state eq 'down') {
3153 _service_down($v_r_list);
3158 # Bring a real service up if it is down
3159 # Should be called by service_set only
3160 # I.e. If you want to change the state of a real server call service_set.
3161 # If you call this function directly then l7directord will lose track
3162 # of the state of real servers.
3163 # pre: v_r_list: virtual and real pair list
3164 # [ [$v, $r], [$v, $r] ... ]
3165 # post: real service is taken up from the respective virtual service
3169 my $v_r_list = shift;
3170 if ( !_status_up($v_r_list) ) {
3174 for my $v_r_pair (@$v_r_list) {
3175 my ($v, $r) = @$v_r_pair;
3176 _restore_service($v, $r, 'real');
3182 # Bring a real service down if it is up
3183 # Should be called by service_set only
3184 # I.e. if you want to change the state of a real server call service_set.
3185 # If you call this function directly then l7directord will lose track
3186 # of the state of real servers.
3187 # pre: v_r_list: virtual and real pair list
3188 # [ [$v, $r], [$v, $r] ... ]
3189 # post: real service is taken down from the respective virtual service
3193 my $v_r_list = shift;
3194 if ( !_status_down($v_r_list) ) {
3198 for my $v_r_pair (@$v_r_list) {
3199 my ($v, $r) = @$v_r_pair;
3200 _remove_service($v, $r, 'real');
3206 # Set the status of a server as up
3207 # Should only be called from _service_up or fallback_on
3209 my ($v_r_list, $is_fallback) = @_;
3210 if (!defined $v_r_list) {
3214 if (!$is_fallback) {
3215 my $current_status = get_status($v_r_list);
3216 if (defined $current_status && $current_status eq $SERVICE_UP) {
3220 my $id = get_health_check_id_str( @{ $v_r_list->[0] } );
3222 ld_log( _message('ERR0503') );
3225 $HEALTH_CHECK{$id}{status} = $SERVICE_UP;
3230 my $current_service = ld_read_l7vsadm();
3231 if (!defined $current_service) {
3232 ld_log( _message('FTL0201') );
3235 my $vid = get_virtual_id_str( $v_r_list->[0][0] );
3236 if ( exists $current_service->{$vid} ) {
3238 if ( !defined $current_service->{$vid} ) {
3242 # all real server's weight are zero.
3243 for my $real ( keys %{ $current_service->{$vid} } ) {
3244 if ( 'other_virtual_option' eq $real ){
3247 # already added fallback server.
3248 if ( $real eq get_ip_port( $v_r_list->[0][1] ) ) {
3251 $weight += $current_service->{$vid}{$real}{weight};
3262 # Set the status of a server as down
3263 # Should only be called from _service_down or _ld_stop
3265 my ($v_r_list, $is_fallback) = (@_);
3266 if (!defined $v_r_list) {
3270 if (!$is_fallback) {
3271 my $current_status = get_status($v_r_list);
3272 if ($current_status && $current_status eq $SERVICE_DOWN) {
3276 my $id = get_health_check_id_str( @{ $v_r_list->[0] } );
3278 ld_log( _message('ERR0503') );
3281 $HEALTH_CHECK{$id}{status} = $SERVICE_DOWN;
3286 my $current_service = ld_read_l7vsadm();
3287 if (!defined $current_service) {
3288 ld_log( _message('FTL0201') );
3291 my $vid = get_virtual_id_str( $v_r_list->[0][0] );
3292 if ( defined $current_service->{$vid} ) {
3294 my $fallback_exist = 0;
3295 # any real server has weight.
3296 for my $real ( keys %{ $current_service->{$vid} } ) {
3297 if ( 'other_virtual_option' eq $real ){
3300 if ( $real eq get_ip_port( $v_r_list->[0][1] ) ) {
3301 $fallback_exist = 1;
3303 $weight += $current_service->{$vid}{$real}{weight};
3305 if ($fallback_exist && $weight) {
3314 # Get health check server status
3315 # return $SERVICE_UP / $SERVICE_DOWN
3317 my $v_r_list = shift;
3319 my $id = get_health_check_id_str( @{ $v_r_list->[0] } );
3321 ld_log( _message('ERR0503') );
3324 return $HEALTH_CHECK{$id}{status};
3328 # Remove a real server by either making it quiescent or deleteing it
3329 # Should be called by _service_down or fallback_off
3330 # I.e. If you want to change the state of a real server call service_set.
3331 # If you call this function directly then l7directord will lose track
3332 # of the state of real servers.
3333 # If the real server exists (which it should) make it quiescent or
3334 # delete it, depending on the global and per virtual service quiecent flag.
3335 # If it # doesn't exist, just leave it as it will be added by the
3336 # _service_up code as appropriate.
3337 # pre: v: reference to virtual service to with the real server belongs
3338 # rservice: service to restore. Of the form server:port for tcp
3339 # rforw: Forwarding mechanism of service. Should be only "-m"
3340 # rforw is kept as it is, even though not used - NTT COMWARE
3341 # tag: Tag to use for logging. Should be either "real" or "fallback"
3342 # post: real service is taken up from the respective virtual service
3345 sub _remove_service {
3346 my ($v, $r, $tag) = @_;
3347 if (!defined $v || !defined $r) {
3348 ld_log( _message('ERR0501') );
3352 my $vip_id = get_virtual_id_str($v);
3353 if (!defined $vip_id) {
3354 ld_log( _message('ERR0502') );
3357 my $oldsrv = ld_read_l7vsadm();
3358 if (!defined $oldsrv) {
3359 ld_log( _message('FTL0201') );
3363 if ( !exists $oldsrv->{$vip_id} ) {
3364 ld_log( _message( 'ERR0208', get_ip_port($r), get_ip_port($v) ) );
3369 my $is_quiescent = 0;
3370 if (!defined $tag || $tag ne 'fallback') {
3371 if ( defined $v->{quiescent} && $v->{quiescent} ) {
3376 my $or = $oldsrv->{$vip_id}{ get_ip_port($r) };
3377 # already removed server
3378 if (!defined $or && !$is_quiescent) {
3379 my $module_key = $v->{module}{name} . q{ } . $v->{module}{key};
3380 ld_log( _message( 'ERR0210', get_ip_port($r), get_ip_port($v), $module_key ) );
3383 # already quiescent server
3384 if ( defined $or && $is_quiescent && $or->{weight} == 0 &&
3385 $or->{option}{forward} eq $r->{option}{forward} ) {
3386 my $module_key = $v->{module}{name} . q{ } . $v->{module}{key};
3387 ld_log( _message( 'ERR0211', get_ip_port($r), get_ip_port($v), $module_key ) );
3391 if ($is_quiescent) {
3393 ld_edit_real($v, $r, 0);
3396 ld_add_real($v, $r, 0);
3398 if (!defined $tag || $tag eq 'real') {
3399 ld_log( _message( 'INF0303', get_ip_port($r) ) );
3401 elsif ($tag eq 'fallback') {
3402 ld_log( _message( 'INF0304', get_ip_port($r) ) );
3406 ld_delete_real($v, $r);
3407 if (!defined $tag || $tag eq 'real') {
3408 ld_log( _message( 'INF0305', get_ip_port($r) ) );
3410 elsif ($tag eq 'fallback') {
3411 ld_log( _message( 'INF0306', get_ip_port($r) ) );
3415 if ( defined $v->{realdowncallback} && $r->{healthchecked} ) {
3416 system_wrapper( $v->{realdowncallback}, get_ip_port($r) );
3417 ld_log( _message( 'INF0501', $v->{realdowncallback}, get_ip_port($r) ) );
3419 $r->{healthchecked} = 1;
3423 # Make a retore a real server. The opposite of _quiescent_server.
3424 # Should be called by _service_up or fallback_on
3425 # I.e. If you want to change the state of a real server call service_set.
3426 # If you call this function directly then l7directord will lose track
3427 # of the state of real servers.
3428 # If the real server exists (which it should) make it quiescent. If it
3429 # doesn't exist, just leave it as it will be added by the _service_up code
3431 # pre: v: reference to virtual service to with the real server belongs
3432 # r: reference to real server to restore.
3433 # tag: Tag to use for logging. Should be either "real" or "fallback"
3434 # post: real service is taken up from the respective virtual service
3437 sub _restore_service {
3438 my ($v, $r, $tag) = @_;
3439 if (!defined $v || !defined $r) {
3440 ld_log( _message('ERR0501') );
3444 my $vip_id = get_virtual_id_str($v);
3445 if (!defined $vip_id) {
3446 ld_log( _message('ERR0502') );
3449 my $oldsrv = ld_read_l7vsadm();
3450 if (!defined $oldsrv) {
3451 ld_log( _message('FTL0201') );
3455 if ( !exists $oldsrv->{$vip_id} ) {
3456 ld_log( _message( 'ERR0207', get_ip_port($r), get_ip_port($v) ) );
3460 my $or = $oldsrv->{$vip_id}{ get_ip_port($r) };
3461 # already completely same server exist
3463 $or->{weight} eq $r->{weight} &&
3464 $or->{option}{forward} eq $r->{option}{forward} ) {
3465 my $module_key = $v->{module}{name} . q{ } . $v->{module}{key};
3466 ld_log( _message( 'ERR0209', get_ip_port($r), get_ip_port($v), $module_key ) );
3471 ld_edit_real( $v, $r, $r->{weight} );
3474 ld_add_real( $v, $r, $r->{weight} );
3477 if (!defined $tag || $tag eq 'real') {
3478 ld_log( _message( 'INF0301', get_ip_port($r) ) );
3480 elsif ($tag eq 'fallback') {
3481 ld_log( _message( 'INF0302', get_ip_port($r) ) );
3484 if ( defined $v->{realrecovercallback} && $r->{healthchecked} ){
3485 system_wrapper( $v->{realrecovercallback}, get_ip_port($r) );
3486 ld_log( _message( 'INF0502', $v->{realrecovercallback}, get_ip_port($r) ) );
3488 $r->{healthchecked} = 1;
3492 # Turn on the fallback server for a virtual service if it is inactive
3493 # pre: v: virtual to turn fallback service on for
3494 # post: fallback server is turned on if it was inactive
3499 my $fallback = fallback_find($v);
3500 if (defined $fallback) {
3501 my $v_r_list = [ [ $v, $fallback->{tcp} ] ];
3502 if ( _status_up($v_r_list, 'fallback') ) {
3503 _restore_service($v, $fallback->{tcp}, 'fallback');
3509 # Turn off the fallback server for a virtual service if it is active
3510 # pre: v: virtual to turn fallback service off for
3511 # post: fallback server is turned off if it was active
3516 my $fallback = fallback_find($v);
3517 if (defined $fallback) {
3518 my $v_r_list = [ [ $v, $fallback->{tcp} ] ];
3519 if ( _status_down($v_r_list, 'fallback') ) {
3520 _remove_service($v, $fallback->{tcp}, 'fallback');
3526 # Determine the fallback for a virtual service
3527 # pre: v: reference to a virtual service
3529 # return: $v->{fallback} if defined
3534 ld_log( _message('ERR0501') );
3537 return $v->{fallback};
3541 # Check configfile change.
3543 # post: check configfile size, and then check md5 sum
3544 # return: 1 if notice file change
3545 # 0 if not notice or not change
3547 if (!defined $CONFIG_FILE{path}) {
3548 ld_log( _message('FTL0102') );
3552 my $mtime = (stat $CONFIG_FILE{path})[9];
3553 if (!defined $mtime) {
3554 ld_log( _message( 'ERR0410', $CONFIG_FILE{path} ) );
3558 if ( defined $CONFIG_FILE{stattime} && $mtime == $CONFIG_FILE{stattime} ) {
3559 # file mtime is not change
3562 $CONFIG_FILE{stattime} = $mtime;
3564 my $digest = undef;;
3566 require Digest::MD5;
3568 my $ctx = Digest::MD5->new();
3569 open my $config, '<', $CONFIG_FILE{path};
3570 $ctx->addfile($config);
3571 $digest = $ctx->hexdigest;
3575 ld_log( _message( 'ERR0407', $CONFIG_FILE{path} ) );
3579 if (defined $CONFIG_FILE{checksum} && $digest &&
3580 $CONFIG_FILE{checksum} ne $digest ) {
3581 ld_log( _message('WRN0101', $CONFIG_FILE{path}) );
3582 $CONFIG_FILE{checksum} = $digest;
3584 if ( defined $CONFIG{callback} && -x $CONFIG{callback} ) {
3585 system_wrapper( $CONFIG{callback} . q{ } . $CONFIG_FILE{path} );
3586 ld_log( _message( 'INF0503', $CONFIG{callback}, $CONFIG_FILE{path} ) );
3589 if ( $CONFIG{autoreload} ) {
3590 ld_log( _message('WRN0102') );
3594 ld_log( _message('WRN0103') );
3599 $CONFIG_FILE{checksum} = $digest;
3605 # make log rotation work
3607 # post: If logger is a file, it opened and closed again as a test
3608 # If logger is syslog, it is opened so it can be used without
3609 # needing to be opened again.
3610 # Otherwiese, nothing is done.
3611 # return: 0 on success
3614 my $log_config = shift;
3615 if (!defined $log_config) {
3616 ld_log( _message('ERR0501') );
3620 if ( $DEBUG_LEVEL > 0 or $CONFIG{supervised} ) {
3621 # Instantly do nothing
3625 if ( $log_config =~ m{^/}) {
3626 # Open and close the file as a test.
3627 # We open the file each time we want to log to it
3629 open my $log_file, ">>", $log_config;
3633 ld_log( _message('ERR0118', $log_config) );
3638 # Assume $log_config is a logfacility, log to syslog
3640 openlog("l7directord", "pid", $log_config);
3641 # FIXME "closelog" not found
3644 $PROC_STAT{log_opened} = 1;
3650 # pre: message: Message to write
3651 # post: message and timetsamp is written to loged
3652 # If logger is a file, it is opened and closed again as a
3653 # primative means to make log rotation work
3654 # return: 0 on success
3657 my $message = shift;
3658 if (!defined $message) {
3659 ld_log( _message('ERR0501') );
3663 ld_debug(2, $message);
3666 if ( !$CONFIG{supervised} && !$PROC_STAT{log_opened} ) {
3670 my $now = localtime();
3671 my $line_header = sprintf "[%s|%d] ", $now, $PROC_STAT{pid};
3672 $message =~ s/^/$line_header/mg;
3674 if ( $CONFIG{supervised} ) {
3675 print {*STDOUT} $message . "\n";
3677 elsif ( $CONFIG{logfile} =~ m{^/} ) {
3679 open my $log_file, '>>', $CONFIG{logfile};
3680 flock $log_file, 2; # LOCK_EX
3681 print {$log_file} $message . "\n";
3685 print {*STDERR} _message_only( 'FTL0103', $CONFIG{logfile}, $message ) . "\n";
3690 # Assume LOGFILE is a logfacility, log to syslog
3691 syslog('info', $message);
3697 # Log a message to a STDOUT.
3698 # pre: priority: priority of message
3699 # message: Message to write
3700 # post: message is written to STDOUT if $DEBUG_LEVEL >= priority
3703 my ($priority, $message) = @_;
3705 if (defined $priority && $priority =~ /^\d+$/ &&
3706 defined $message && $DEBUG_LEVEL >= $priority) {
3708 $message =~ s/^/DEBUG[$priority]: /mg;
3709 print {*STDERR} $message . "\n";
3714 # Wrapper around command(qx) to get output
3715 # pre: command to execute
3716 # post: execute command and if it returns non-zero a failure
3718 # return: return value of command, and output
3719 sub command_wrapper {
3720 my $command = shift;
3722 if ($DEBUG_LEVEL > 2) {
3723 ld_log( _message( 'INF0506', $command) );
3726 $command =~ s/([{}\\])/\\$1/g;
3727 my $output = qx($command);
3728 if ($CHILD_ERROR != 0) {
3729 ld_log( _message('ERR0303', $command, $CHILD_ERROR) );
3731 return ($CHILD_ERROR, $output);
3735 # Wrapper around system() to log errors
3736 # pre: LIST: arguments to pass to system()
3737 # post: system() is called and if it returns non-zero a failure
3739 # return: return value of system()
3740 sub system_wrapper {
3743 if ($DEBUG_LEVEL > 2) {
3744 ld_log( _message( 'INF0504', join(q{ }, @args) ) );
3746 my $status = system(@args);
3747 if ($DEBUG_LEVEL > 2) {
3749 ld_log( _message('ERR0301', join(q{ }, @args), $status) );
3756 # Wrapper around exec() to log errors
3757 # pre: LIST: arguments to pass to exec()
3758 # post: exec() is called and if it returns non-zero a failure
3760 # return: return value of exec() on failure
3761 # does not return on success
3765 if ($DEBUG_LEVEL > 2) {
3766 ld_log( _message( 'INF0505', join(q{ }, @args) ) );
3768 my $status = exec(@args);
3770 ld_log( _message('ERR0302', join(q{ }, @args), $status) );
3776 # Remove a file, symink, or anything that isn't a directory
3778 # pre: filename: file to delete
3779 # post: If filename does not exist or is a directory an
3780 # error state is reached
3781 # Else filename is delete
3782 # If $DEBUG_LEVEL >=2 errors are logged
3783 # return: 0 on success
3786 my $filename = shift;
3787 if (!defined $filename) {
3788 ld_log( _message('ERR0411') );
3792 ld_log( _message('ERR0401', $filename) );
3795 if (!-e $filename) {
3796 ld_log( _message('ERR0402', $filename) );
3799 my $status = unlink $filename;
3801 ld_log( _message('ERR0403', $filename, $ERRNO) );
3808 # See if a number is an octet, that is >=0 and <=255
3809 # pre: alleged_octet: the octect to test
3810 # post: alleged_octect is checked to see if it is valid
3811 # return: 1 if the alleged_octet is an octet
3814 my $alleged_octet = shift;
3815 if (!defined $alleged_octet || $alleged_octet !~ /^\d+$/ || $alleged_octet > 255) {
3816 ld_log( _message('ERR0501') );
3823 # Check that a given string is an IP address
3824 # pre: alleged_ip: string representing ip address
3825 # post: alleged_ip is checked to see if it is valid
3826 # return: 1 if alleged_ip is a valid ip address
3829 my $alleged_ip = shift;
3831 # If we don't have four, . delimited numbers then we have no hope
3832 if (!defined $alleged_ip || $alleged_ip !~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) {
3833 ## ld_log( _message('ERR0501') );
3837 # Each octet must be >=0 and <=255
3838 is_octet($1) or return 0;
3839 is_octet($2) or return 0;
3840 is_octet($3) or return 0;
3841 is_octet($4) or return 0;
3847 # Check that a given string is an IPv6 address
3848 # pre: alleged_ip6: string representing ip address
3849 # post: alleged_ip6 is checked to see if it is valid
3850 # return: 1 if alleged_ip is a valid ipv6 address
3853 my $alleged_ip = shift;
3854 my @return_array = (0, undef);
3856 if (!defined $alleged_ip ) {
3857 ld_log( _message('ERR0501') );
3861 ## Change IPv6 Address
3862 $alleged_ip =~ s/[\[\]]//g;
3864 my ($work, $link_local) = split /%/, $alleged_ip;
3866 if ( $alleged_ip =~ /::/ ){
3867 my ($adr_a, $adr_b) = split /::/, $alleged_ip;
3868 my @adr_a = split /:/ , $adr_a;
3869 my @adr_b = split /:/ , $adr_b;
3870 for(scalar @adr_a .. 7 - scalar @adr_b){
3873 @address = (@adr_a, @adr_b);
3876 @address = split /:/, $alleged_ip;
3878 $alleged_ip = join ":", @address;
3879 if ( defined $link_local ){
3880 $alleged_ip .= '%' . $link_local;
3882 if (!defined $alleged_ip ||
3883 $alleged_ip !~ m/^([0-9a-fA-F]{1,4}):
3890 ([0-9a-fA-F]{1,4})(%.+)?$/x) {
3893 @return_array = (1, @address);
3894 return @return_array;
3899 # Turn an IP address given as a dotted quad into an integer
3900 # pre: ip_address: string representing IP address
3901 # post: post ip_address is converted to an integer
3902 # return: -1 if an error occurs
3903 # integer representation of IP address otherwise
3905 my $ip_address = shift;
3906 my $ip_version = 'ipv4';
3908 my $result2 = undef;
3909 my @return_array = (undef, -1);
3912 if ( is_ip($ip_address) ) {
3913 my ($oct1, $oct2, $oct3, $oct4)
3914 = $ip_address =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
3915 $result = ($oct1 << 24) + ($oct2 << 16) + ($oct3 << 8) + $oct4;
3918 my ( $ret, @address ) = is_ip6($ip_address);
3920 my ( $hex1, $hex2, $hex3, $hex4, $hex5, $hex6, $hex7, $hex8, $linklocal) = @address;
3921 $result = (hex($hex5) << 48) + (hex($hex6) << 32) + (hex($hex7) << 16) + hex($hex8);
3922 $result2 = (hex($hex1) << 48) + (hex($hex2) << 32) + (hex($hex3) << 16) + hex($hex4);
3923 $ip_version = 'ipv6';
3926 return @return_array;
3929 @return_array = ($ip_version, $result, $result2);
3930 return @return_array;
3934 # Turn an IP address given as an integer into a dotted quad
3935 # pre: ip_address: integer representation of IP address
3936 # post: Decimal is converted to a dotted quad
3937 # return: string representing IP address
3939 my ($ip_version, $ip_address,$ip_address2) = @_;
3940 if (!defined $ip_address || $ip_address !~ /^\d+$/ ) {
3941 ##|| !defined $ip_version || $ip_version !~ /ipv[46]/ ) {
3942 ld_log( _message('ERR0501') );
3947 if ($ip_version eq 'ipv6') {
3948 ## IPv6 Address Change
3949 $result = sprintf "%0x:%0x:%0x:%0x:%0x:%0x:%0x:%0x",
3950 ($ip_address2 >> 48) & 0xffff,
3951 ($ip_address2 >> 32) & 0xffff,
3952 ($ip_address2 >> 16) & 0xffff,
3953 ($ip_address2 ) & 0xffff,
3954 ($ip_address >> 48) & 0xffff,
3955 ($ip_address >> 32) & 0xffff,
3956 ($ip_address >> 16) & 0xffff,
3957 ($ip_address ) & 0xffff;
3960 ## IPv4 Address Change
3961 $result = sprintf "%d.%d.%d.%d",
3962 ($ip_address >> 24) & 0xff,
3963 ($ip_address >> 16) & 0xff,
3964 ($ip_address >> 8 ) & 0xff,
3965 ($ip_address ) & 0xff;
3971 # Get the service for a virtual or a real
3972 # pre: host: virtual or real to get the service for
3974 # return: ip_address:port
3976 my ($host, $checkport) = @_;
3977 my $server = defined $host && defined $host->{server} && defined $host->{server}{ip}
3978 ? $host->{server}{ip } : q{};
3979 if (is_ip6($server)) {
3980 $server = sprintf "[%s]" , $server;
3982 my $port = defined $checkport ? $checkport
3983 : defined $host && defined $host->{server} && defined $host->{server}{port}
3984 ? $host->{server}{port} : q{};
3986 my $ip_port = $server ne q{} && $port ne q{} ? "$server:$port" : q{};
3990 # get_health_check_id_str
3991 # Get an id string for a health check process
3992 # pre: r: Real service.
3993 # v: Virtual service
3995 # return: Id string for the health check process
3996 sub get_health_check_id_str {
3998 if ( !defined $v || !defined $r || !defined $r->{server} ) {
3999 ld_log( _message('ERR0501') );
4003 my $ip = defined $r->{server}{ip } ? $r->{server}{ip } : q{};
4004 my $port = defined $v->{checkport } ? $v->{checkport } :
4005 defined $r->{server}{port} ? $r->{server}{port} : q{};
4006 my $checktype = defined $v->{checktype } ? $v->{checktype } : q{};
4007 my $service = defined $v->{service } ? $v->{service } : q{};
4008 my $protocol = defined $v->{protocol } ? $v->{protocol } : q{};
4009 my $num_connects = defined $v->{num_connects} ? $v->{num_connects} : q{};
4010 my $request = defined $r->{request } ? $r->{request } : q{};
4011 my $receive = defined $r->{receive } ? $r->{receive } : q{};
4012 my $httpmethod = defined $v->{httpmethod } ? $v->{httpmethod } : q{};
4013 my $virtualhost = defined $v->{virtualhost } ? $v->{virtualhost } : q{};
4014 my $login = defined $v->{login } ? $v->{login } : q{};
4015 my $password = defined $v->{passwd } ? $v->{passwd } : q{};
4016 my $database = defined $v->{database } ? $v->{database } : q{};
4018 my $customcheck = defined $v->{customcheck } ? $v->{customcheck } : q{};
4019 my $checkinterval = defined $v->{checkinterval } ? $v->{checkinterval } : q{};
4020 my $checkcount = defined $v->{checkcount } ? $v->{checkcount } : q{};
4021 my $checktimeout = defined $v->{checktimeout } ? $v->{checktimeout } : q{};
4022 my $negotiatetimeout = defined $v->{negotiatetimeout } ? $v->{negotiatetimeout } : q{};
4023 my $retryinterval = defined $v->{retryinterval } ? $v->{retryinterval } : q{};
4025 # FIXME SHOULD change separator. (request, receive, login, passwd ,database may include ':')
4026 my $id = "$ip:$port:$checktype:$service:$protocol:$num_connects:$request:$receive:" .
4027 "$httpmethod:$virtualhost:$login:$password:$database:$customcheck:" .
4028 "$checkinterval:$checkcount:$checktimeout:$negotiatetimeout:$retryinterval";
4033 # get_virtual_id_str
4034 # Get an id string for a virtual service
4035 # pre: v: Virtual service
4037 # return: Id string for the virtual service
4038 sub get_virtual_id_str {
4040 if ( !defined $v || !defined $v->{module} ) {
4041 ld_log( _message('ERR0501') );
4045 my $ip_port = get_ip_port($v);
4046 my $protocol = defined $v->{protocol } ? $v->{protocol } : q{};
4047 my $module_name = defined $v->{module}{name} ? $v->{module}{name} : q{};
4048 my $module_key = defined $v->{module}{key } ? $v->{module}{key } : q{};
4050 my $id = "$protocol:$ip_port:$module_name $module_key";
4054 # [cf] id = "tcp:127.0.0.1:80:cinsert --cookie-name 'monkey'"
4058 # Get the l7vsadm flag corresponging to a forwarding mechanism
4059 # pre: forward: Name of forwarding mechanism. (masq or tproxy)
4061 # return: l7vsadm flag corresponding to the forwading mechanism
4062 # " " if $forward is unknown
4063 sub get_forward_flag {
4064 my $forward = shift;
4066 if (defined $forward && $forward =~ /^masq$/i) {
4069 elsif (defined $forward && $forward =~ /^tproxy$/i) {
4076 # Exit and log a message
4077 # pre: exit_status: Integer exit status to exit with
4078 # 0 wiil be used if parameter is omitted
4079 # message: Message to log when exiting. May be omitted
4080 # post: If exit_status is non-zero or $DEBUG_LEVEL>2 then
4082 # Programme exits with exit_status
4083 # return: does not return
4085 my ($exit_status, $message) = @_;
4086 if (defined $exit_status && defined $message) {
4087 ld_log( _message('INF0006', $exit_status, $message) );
4093 # Open a socket connection
4094 # pre: remote: IP address as a dotted quad of remote host to connect to
4095 # port: port to connect to
4096 # protocol: Prococol to use. Should be either "tcp" or "udp"
4097 # post: A Socket connection is opened to the remote host
4098 # return: Open socket
4099 sub ld_open_socket {
4100 require IO::Socket::INET6;
4101 my ($remote, $port, $protocol, $timeout) = @_;
4102 my $sock_handle = IO::Socket::INET6->new(
4103 PeerAddr => $remote,
4106 Timeout => $timeout,
4109 return $sock_handle;
4113 # Close and fork to become a daemon.
4115 # Notes from unix programmer faq
4116 # http://www.landfield.com/faqs/unix-faq/programmer/faq/
4118 # Almost none of this is necessary (or advisable) if your daemon is being
4119 # started by `inetd'. In that case, stdin, stdout and stderr are all set up
4120 # for you to refer to the network connection, and the `fork()'s and session
4121 # manipulation should *not* be done (to avoid confusing `inetd'). Only the
4122 # `chdir()' step remains useful.
4124 ld_daemon_become_child();
4126 if (POSIX::setsid() < 0) {
4127 ld_exit( 7, _message_only('ERR0702') );
4130 ld_daemon_become_child();
4132 if (chdir('/') < 0) {
4133 ld_exit( 8, _message_only('ERR0703') );
4140 eval { open *STDIN, '<', '/dev/null'; };
4141 ld_exit(9, _message_only('ERR0704') ) if ($EVAL_ERROR);
4142 eval { open *STDOUT, '>>', '/dev/console'; };
4143 ld_exit(10, _message_only('ERR0705') ) if ($EVAL_ERROR);
4144 eval { open *STDERR, '>>', '/dev/console'; };
4145 ld_exit(10, _message_only('ERR0705') ) if ($EVAL_ERROR);
4148 # ld_daemon_become_child
4149 # Fork, kill parent and return child process
4151 # post: process forkes and parent exits
4152 # All preocess exit with exit status -1 if an error occurs
4153 # return: parent: exits
4154 # child: none (this is the process that returns)
4155 sub ld_daemon_become_child {
4156 my $status = fork();
4157 $PROC_STAT{pid} = $PID;
4160 ld_exit( 6, _message_only('ERR0701', $ERRNO) );
4163 ld_exit( 0, _message_only('INF0005') );
4168 # Wrapper to gethostbyname. Look up the/an IP address of a hostname
4169 # If an IP address is given is it returned
4170 # pre: name: Hostname of IP address to lookup
4171 # post: gethostbyname is called to find an IP address for $name
4172 # This is converted to a string
4173 # return: IP address
4175 sub ld_gethostbyname {
4176 require IO::Socket::INET6;
4178 $name = q{} if !defined $name;
4179 my $addrs = ( gethostbyname($name) )[4];
4180 if ( defined $addrs && $addrs ){
4181 return Socket::inet_ntoa($addrs);
4184 $name =~ s/\[|\]//g;
4185 my $addrs = ( gethostbyname2($name, AF_INET6) )[4] or return;
4186 return inet_ntop(AF_INET6,$addrs);
4192 # Wraper for getservbyname. Look up the port for a service name
4193 # If a port is given it is returned.
4194 # pre: name: Port or Service name to look up
4195 # post: if $name is a number
4196 # if 0<=$name<=65536 $name is returned
4197 # else undef is returned
4198 # else getservbyname is called to look up the port for the service
4201 sub ld_getservbyname {
4202 my ($name, $protocol) = @_;
4203 $name = q{} if !defined $name;
4204 $protocol = q{} if !defined $protocol;
4206 if ($name =~ /^\d+$/) {
4207 if ($name > 65535) {
4213 my $port = ( getservbyname($name, $protocol) )[2];
4217 # ld_gethostservbyname
4218 # Wraper for ld_gethostbyname and ld_getservbyname. Given a server of the
4219 # form ip_address|hostname:port|servicename return hash refs of ip_address and port
4220 # pre: hostserv: Servver of the form ip_address|hostname:port|servicename
4221 # protocol: Protocol for service. Should be either "tcp" or "udp"
4222 # post: lookups performed as per ld_getservbyname and ld_gethostbyname
4223 # return: { ip => ip_address, port => port }
4225 sub ld_gethostservbyname {
4226 my ($hostserv, $protocol) = @_;
4230 if (!defined $hostserv || $hostserv !~ /
4232 (\d+\.\d+\.\d+\.\d+|[a-z0-9.-]+) # host or ip
4234 (\d+|[a-z0-9-]+) # serv or port
4237 if ( !defined $hostserv || $hostserv !~ /
4239 (\[[a-z0-9.-:%]+\]) # host or ip
4241 (\d+|[a-z0-9-]+) # serv or port
4256 $ip = ld_gethostbyname($ip) or return;
4257 $port = ld_getservbyname($port, $protocol);
4259 return if !defined $port;
4261 return {ip => $ip, port => $port};
4265 # Create message only.
4267 my ($code, @message_args) = @_;
4269 my $message_list = {
4270 # health check process exit
4271 FTL0001 => "health_check argument is invalid. Exit this monitor process with status: 1",
4272 FTL0002 => "health_check argument pair, virtual or real structure is invalid. Exit this monitor process with status: 2",
4273 FTL0003 => "Detected down management process (pid: %s). Exit this monitor process with status: 3",
4275 FTL0101 => "l7vsadm file `%s' is not found or cannot execute.",
4276 FTL0102 => "Config file is not defined. So cannot check configuration change.",
4277 FTL0103 => "Cannot open logfile `%s'. Log message: `%s'",
4278 # command fatal error
4279 FTL0201 => "Result of read from l7vsadm is not defined.",
4282 ERR0001 => "Initialization error: %s",
4283 ERR0002 => "Configuration error and exit.",
4285 ERR0101 => "Invalid value (set natural number) `%s'.",
4286 ERR0102 => "Invalid value (set `yes' or `no') `%s'.",
4287 ERR0103 => "Invalid value (set any word) `%s'.",
4288 ERR0104 => "Invalid value (set `custom', `connect', `negotiate', `ping', `off', `on' "
4289 . "or positive number) `%s'.",
4290 ERR0105 => "Invalid schedule module (should be only lowercase letters (a-z)) `%s'.",
4291 ERR0106 => "Invalid value (set `http', `https', `ftp', `smtp', `pop', `imap', "
4292 . "`ldap', `nntp', `dns', `mysql', `pgsql', `sip', or `none') `%s'.",
4293 ERR0107 => "Invalid value (forwarding mode must be `masq' or `tproxy') `%s'.",
4294 ERR0108 => "Invalid port number `%s'.",
4295 ERR0109 => "Invalid protocol (protocol must be `tcp') `%s'.",
4296 ERR0110 => "Invalid HTTP method (set `GET' or `HEAD') `%s'.",
4297 ERR0111 => "Invalid protocol module (should be only lowercase letters (a-z)) `%s'.",
4298 ERR0112 => "Invalid module key option (`%s' module must set `%s' option) `%s'.",
4299 ERR0113 => "Invalid QoS value (set 0 or 1-999[KMG]. must specify unit(KMG)) `%s'.",
4300 ERR0114 => "Invalid address `%s'.",
4301 ERR0115 => "Invalid address range (first value(%s) must be less than or equal to the second value(%s)) `%s'.",
4302 ERR0116 => "File not found `%s'.",
4303 ERR0117 => "File not found or cannot execute `%s'.",
4304 ERR0118 => "Unable to open logfile `%s'.",
4305 ERR0119 => "Virtual section not found for `%s'.",
4306 ERR0120 => "Unknown config `%s'.",
4307 ERR0121 => "Configuration error. Reading file `%s' at line %d: %s",
4308 ERR0122 => "Caught exception during re-read config file and re-setup l7vsd. (message: %s) "
4309 . "So config setting will be rollbacked.",
4310 ERR0123 => "`%s' is a required module for checking %s service.",
4311 ERR0124 => "Invalid value `%s'.",
4312 ERR0125 => "Invalid accesslog rotate type (set 'date', 'size' or 'datesize') `%s'.",
4313 ERR0126 => "Invalid accesslog rotate max backup index number `%s'.",
4314 ERR0127 => "Invalid accesslog rotate max filesize value (set 0 or 1-999[KMG]. must specify unit(KMG)) `%s'.",
4315 ERR0128 => "Invalid accesslog rotate rotation timing (set 'year','month','week','date', or 'hour') `%s'.",
4316 ERR0129 => "Invalid accesslog rotate rotation timing value `%s'.",
4317 # operate l7vsd error
4318 ERR0201 => "Failed to add virtual service to l7vsd: `%s %s', output: `%s'",
4319 ERR0202 => "Failed to edit virtual service on l7vsd: `%s %s', output: `%s'",
4320 ERR0203 => "Failed to delete virtual service from l7vsd: `%s %s', output: `%s'",
4321 ERR0204 => "Failed to add server to l7vsd: `%s %s' ( x `%s %s'), output: `%s'",
4322 ERR0205 => "Failed to edit server on l7vsd: `%s %s' ( x `%s %s'), output: `%s'",
4323 ERR0206 => "Failed to delete server from l7vsd: `%s %s' ( x `%s %s'), output: `%s'",
4324 ERR0207 => "Trying add server `%s', but virtual service `%s' is not found.",
4325 ERR0208 => "Trying delete server `%s', but virtual service `%s' is not found.",
4326 ERR0209 => "`%s' was already existed on l7vsd. ( x `%s %s')",
4327 ERR0210 => "`%s' was already deleted on l7vsd. ( x `%s %s')",
4328 ERR0211 => "`%s' was already changed to quiescent state on l7vsd. ( x `%s %s')",
4330 ERR0301 => "Failed to system `%s' with return: %s",
4331 ERR0302 => "Failed to exec `%s' with return: %s",
4332 ERR0303 => "Failed to command `%s' with return: %s",
4334 ERR0401 => "Failed to delete file `%s': `Is a directory'",
4335 ERR0402 => "Failed to delete file `%s': `No such file'",
4336 ERR0403 => "Failed to delete file `%s': `%s'",
4337 ERR0404 => "Config file `%s' is not found.",
4338 ERR0405 => "`l7directord.cf' is not found at default search paths.",
4339 ERR0406 => "`l7vsadm' file is not found at default search paths.",
4340 ERR0407 => "Cannot open config file `%s'.",
4341 ERR0408 => "Cannot close config file `%s'.",
4342 ERR0409 => "Cannot open pid file (%s): %s",
4343 ERR0410 => "Cannot get mtime of configuration file `%s'",
4344 ERR0411 => "No delete file specified.",
4345 ERR0412 => "Invalid pid specified. (pid: %s)",
4347 ERR0501 => "Some method arguments are undefined.",
4348 ERR0502 => "VirtualService ID is undefined.",
4349 ERR0503 => "HealthCheck ID is undefined.",
4350 ERR0504 => "negotiate function is undefined. So use check_connect function.",
4351 ERR0505 => "custom check script is undefined. So use check_off function.",
4352 # health check process
4353 ERR0601 => "Service up detected. (Real server `%s')",
4354 ERR0602 => "Service down detected. (Real server `%s')",
4355 ERR0603 => "Detected down monitor process (pid: %s). Prepare to re-start health check process. (id: `%s')",
4356 ERR0604 => "Failed to fork() on sub process creation. (id: `%s')",
4358 ERR0701 => "Cannot fork for become daemon (errno: `%s') and exit.",
4359 ERR0702 => "Cannot setsid for become daemon and exit.",
4360 ERR0703 => "Cannot chdir for become daemon and exit.",
4361 ERR0704 => "Cannot open /dev/null for become daemon and exit.",
4362 ERR0705 => "Cannot open /dev/console for become daemon and exit.",
4365 WRN0001 => "l7directord `%s' received signal: %s. Terminate process.",
4366 WRN0002 => "l7directord `%s' received signal: %s. Reload configuration.",
4367 WRN0003 => "Signal TERM send error(pid: %d)",
4368 WRN0004 => "Signal HUP send error(pid: %d)",
4370 WRN0101 => "Configuration file `%s' has changed on disk.",
4371 WRN0102 => "Reread new configuration.",
4372 WRN0103 => "Ignore new configuration.",
4374 WRN0203 => "Service check OK. HTTP response is valid. HTTP response status line is `%s' (real - `%s:%s')",
4375 WRN0204 => "Service check OK. Successfully connect SMTP server. (real - `%s:%s')",
4376 WRN0205 => "Service check OK. Successfully connect POP3 server. (real - `%s:%s')",
4377 WRN0206 => "Service check OK. Successfully connect IMAP server. (real - `%s:%s')",
4378 WRN0207 => "Service check OK. Successfully bind LDAP server. (real - `%s:%s')",
4379 WRN0208 => "Service check OK. NNTP response is valid. `%s' (real - `%s:%s')",
4380 WRN0209 => "Service check OK. Database response is valid. (real - `%s:%s')",
4381 WRN0210 => "Service check OK. Successfully connect socket to server. (real - `%s:%s')",
4382 WRN0211 => "Service check OK. SIP response is valid. `%s' (real - `%s:%s')",
4383 WRN0212 => "Service check OK. Successfully login FTP server. (real - `%s')",
4384 WRN0213 => "Service check OK. Successfully lookup DNS. (real - `%s:%s')",
4385 WRN0214 => "Service check OK. Successfully receive ping response. (real - `%s')",
4386 WRN0215 => "Custom check result OK. (real - `%s')",
4388 WRN0301 => "Perl warning: `%s'",
4390 WRN1001 => "Retry service check `%s' %d more time(s).",
4392 WRN1101 => "Service check NG. Check URL `%s' is not valid. (real - `%s:%s')",
4393 WRN1102 => "Service check NG. HTTP response is not ok. Response status line is `%s' (real - `%s:%s')",
4394 WRN1103 => "Service check NG. Check string `%s' is not found in HTTP response. (real - `%s:%s')",
4396 WRN1201 => "Service check NG. Cannot connect SMTP server. (real - `%s:%s')",
4398 WRN1301 => "Service check NG. Cannot connect POP3 server. (real - `%s:%s')",
4399 WRN1302 => "Service check NG. Cannot login POP3 server. (real - `%s:%s')",
4401 WRN1401 => "Service check NG. Cannot connect IMAP server. (real - `%s:%s')",
4402 WRN1402 => "Service check NG. Cannot login IMAP server. (real - `%s:%s')",
4403 WRN1403 => "Service check NG. Connection timeout from IMAP server in %d seconds. (real - `%s:%s')",
4405 WRN1501 => "Service check NG. Cannot connect LDAP server. (real - `%s:%s')",
4406 WRN1502 => "Service check NG. Connection timeout from LDAP server in %d seconds. (real - `%s:%s')",
4407 WRN1503 => "Service check NG. LDAP bind error. (real - `%s:%s')",
4408 WRN1504 => "Service check NG. Exists %d results (not one) on search Base DN. (real - `%s:%s')",
4409 WRN1505 => "Service check NG. Check string `%s' is not found in Base DN search result. (real - `%s:%s')",
4411 WRN1601 => "Service check NG. Cannot connect NNTP server. (real - `%s:%s')",
4412 WRN1602 => "Service check NG. Connection timeout from NNTP server in %d seconds. (real - `%s:%s')",
4413 WRN1603 => "Service check NG. NNTP response is not ok. `%s' (real - `%s:%s')",
4415 WRN1701 => "Service check NG. SQL check must set `database', `login', `passwd' by configuration. (real - `%s:%s')",
4416 WRN1702 => "Service check NG. Cannot connect database or cannot login database. (real - `%s:%s')",
4417 WRN1703 => "Service check NG. Query result has no row. (real - `%s:%s')",
4418 WRN1704 => "Service check NG. Expected %d rows of query results, but got %d rows. (real - `%s:%s')",
4419 WRN1705 => "Service check NG. Connection timeout from database in %d seconds. (real - `%s:%s')",
4421 WRN1801 => "Service check NG. SIP check must set `login' by configuration. (real - `%s:%s')",
4422 WRN1802 => "Service check NG. Cannot connect SIP server. (real - `%s:%s')",
4423 WRN1803 => "Service check NG. SIP response is not ok. `%s' (real - `%s:%s')",
4424 WRN1804 => "Service check NG. Connection timeout from SIP server in %d seconds. (real - `%s:%s')",
4426 WRN1901 => "Service check NG. FTP check must set `login', `passwd' by configuration. (real - `%s')",
4427 WRN1902 => "Service check NG. Cannot connect FTP server. (real - `%s')",
4428 WRN1903 => "Service check NG. Cannot login FTP server. (real - `%s')",
4429 WRN1904 => "Service check NG. Cannot chdir to / of FTP server. (real - `%s')",
4430 WRN1905 => "Service check NG. Cannot get file `%s' (real - `%s')",
4431 WRN1906 => "Service check NG. Check string `%s' is not found in file `%s' (real - `%s')",
4432 WRN1907 => "Service check NG. Exception occur during FTP check `%s' (real - `%s')",
4433 WRN1908 => "Service check NG. Connection timeout from FTP server in %d seconds. (real - `%s')",
4435 WRN2001 => "Service check NG. DNS check must set `request', `receive' by configuration. (real - `%s:%s')",
4436 WRN2002 => "Service check NG. Connection timeout from DNS server in %d seconds. (real - `%s:%s')",
4437 WRN2003 => "Service check NG. Net::DNS exception occur `%s' (real - `%s:%s')",
4438 WRN2004 => "Service check NG. DNS search `%s' not respond. (real - `%s:%s')",
4439 WRN2005 => "Service check NG. Check string `%s' is not found in search result. (real - `%s:%s')",
4441 WRN3101 => "Service check NG. Ping timeout in %d seconds. (real - `%s')",
4443 WRN3201 => "Service check NG. Cannot connect socket to server. (errno: `%s') (real - `%s:%s')",
4445 WRN3301 => "Custom check NG. Check timeout in %d seconds. (real - `%s')",
4446 WRN3302 => "Custom check NG. `%s' returns %d",
4449 INF0001 => "Starting program with command: `%s'",
4450 INF0002 => "Starting l7directord v%s with pid: %d (configuration: `%s')",
4451 INF0003 => "Starting l7directord v%s as daemon. (configuration: `%s')",
4452 INF0004 => "Exit by initialize error.",
4453 INF0005 => "Exit parent process for become daemon",
4454 INF0006 => "Exiting with exit status %d: %s",
4455 INF0007 => "Detected halt flag. Exit this monitor process with status: 0",
4456 INF0008 => "Reached end of `main'",
4458 INF0101 => "l7directord for `%s' is running with pid: %d",
4459 INF0102 => "l7directord stale pid file %s for %s",
4460 INF0103 => "Other l7directord process is running. (pid: %d)",
4461 INF0104 => "l7directord process is not running.",
4463 INF0201 => "Add virtual service to l7vsd: `%s %s'",
4464 INF0202 => "Edit virtual service on l7vsd: `%s %s'",
4465 INF0203 => "Delete virtual service from l7vsd: `%s %s'",
4466 INF0204 => "Add server to l7vsd: `%s %s' ( x `%s %s') (weight set to %d)",
4467 INF0205 => "Edit server on l7vsd: `%s %s' ( x `%s %s') (weight set to %d)",
4468 INF0206 => "Delete server from l7vsd: `%s %s' ( x `%s %s')",
4470 INF0301 => "Added real server. (`%s')",
4471 INF0302 => "Added fallback server. (`%s')",
4472 INF0303 => "Changed real server to quiescent state. (`%s')",
4473 INF0304 => "Changed fallback server to quiescent state. (`%s')",
4474 INF0305 => "Deleted real server. (`%s')",
4475 INF0306 => "Deleted fallback server. (`%s')",
4477 INF0401 => "Prepare to start health check process. (id: `%s')",
4478 INF0402 => "Create health check process with pid: %d. (id `%s')",
4480 INF0501 => "Real server down shell execute: `%s %s'",
4481 INF0502 => "Real server recovery shell execute: `%s %s'",
4482 INF0503 => "Config callback shell execute: `%s %s'",
4483 INF0504 => "Running system: `%s'",
4484 INF0505 => "Running exec: `%s'",
4485 INF0506 => "Running command: `%s'",
4489 = exists $message_list->{$code} ? sprintf $message_list->{$code}, @message_args
4490 : "Unknown message. (code:[$code] args:[" . join(q{, }, @message_args) . '])';
4496 # Create message by _message_only and add code header.
4498 my ($code, @message_args) = @_;
4499 my $message = _message_only($code, @message_args);
4500 $message = "[$code] $message";
4510 l7directord - UltraMonkey-L7 Director Daemon
4512 Daemon to monitor remote services and control UltraMonkey-L7
4517 B<l7directord> [B<-d>] [I<configuration>] {B<start>|B<stop>|B<restart>|B<try-restart>|B<reload>|B<status>|B<configtest>}
4519 B<l7directord> B<-t> [I<configuration>]
4521 B<l7directord> B<-h|--help>
4523 B<l7directord> B<-v|--version>
4527 B<l7directord> is a daemon to monitor and administer real servers in a
4528 cluster of load balanced virtual servers. B<l7directord> is similar to B<ldirectord>
4529 in terms of functionality except that it triggers B<l7vsadm>.
4530 B<l7directord> typically is started from command line but can be included
4531 to start from heartbeat. On startup B<l7directord> reads the file
4532 B</etc/ha.d/conf/>I<configuration>.
4533 After parsing the file, entries for virtual servers are created on the UltraMonkey-L7.
4534 Now at regular intervals the specified real servers are monitored and if
4535 they are considered alive, added to a list for each virtual server. If a
4536 real server fails, it is removed from that list. Only one instance of
4537 B<l7directord> can be started for each configuration, but more instances of
4538 B<l7directord> may be started for different configurations. This helps to
4539 group clusters of services. This can be done by putting an entry inside
4540 B</etc/ha.d/haresources>
4542 I<nodename virtual-ip-address l7directord::configuration>
4544 to start l7directord from heartbeat.
4551 =item I<configuration>:
4553 This is the name for the configuration as specified in the file
4554 B</etc/ha.d/conf/>I<configuration>
4558 Don't start as daemon. Useful for debugging.
4562 Help. Print user manual of l7directord.
4566 Version. Print version of l7directord.
4570 Run syntax tests for configuration files only. The program immediately exits after these syntax parsing tests
4571 with either a return code of 0 (Syntax OK) or return code not equal to 0 (Syntax Error).
4575 Start the daemon for the specified configuration.
4579 Stop the daemon for the specified configuration. This is the same as sending
4580 a TERM signal to the running daemon.
4584 Restart the daemon for the specified configuration. The same as stopping and starting.
4586 =item B<try-restart>
4588 Try to restart the daemon for the specified configuration. If l7directord is already running for the
4589 specified configuration, then the same is stopped and started (Similar to restart).
4590 However, if l7directord is not already running for the specified configuration, then an error message
4591 is thrown and the program exits.
4595 Reload the configuration file. This is only useful for modifications
4596 inside a virtual server entry. It will have no effect on adding or
4597 removing a virtual server block. This is the same as sending a HUP signal to
4602 Show status of the running daemon for the specified configuration.
4606 This is the same as B<-t>.
4613 =head2 Description how to write configuration files
4617 =item B<virtual = >I<(ip_address|hostname:portnumber|servicename)>
4619 Defines a virtual service by IP-address (or hostname) and port (or
4620 servicename). All real services and flags for a virtual
4621 service must follow this line immediately and be indented.
4622 For ldirectord, Firewall-mark settings could be set. But for l7directord
4623 Firewall-mark settings cannot be set.
4625 =item B<checktimeout = >I<n>
4627 Timeout in seconds for connect checks. If the timeout is exceeded then the
4628 real server is declared dead. Default is 5 seconds. If defined in virtual
4629 server section then the global value is overridden.
4631 =item B<negotiatetimeout = >I<n>
4633 Timeout in seconds for negotiate checks. Default is 5 seconds.
4634 If defined in virtual server section then the global value is overridden.
4636 =item B<checkinterval = >I<n>
4638 Defines the number of second between server checks. Default is 10 seconds.
4639 If defined in virtual server section then the global value is overridden.
4641 =item B<retryinterval = >I<n>
4643 Defines the number of second between server checks when server status is NG.
4644 Default is 10 seconds. If defined in virtual server section then the global
4645 value is overridden.
4647 =item B<checkcount = >I<n>
4649 The number of times a check will be attempted before it is considered
4650 to have failed. Note that the checktimeout is additive, so if checkcount
4651 is 3 and checktimeout is 2 seconds and retryinterval is 1 second,
4652 then a total of 8 seconds (2 + 1 + 2 + 1 + 2) worth of timeout will occur
4653 before the check fails. Default is 1. If defined in virtual server section
4654 then the global value is overridden.
4656 =item B<configinterval = >I<n>
4658 Defines the number of second between configuration checks.
4659 Default is 5 seconds.
4661 =item B<autoreload = >[B<yes>|B<no>]
4663 Defines if <l7directord> should continuously check the configuration file
4664 for modification each B<configinterval> seconds. If this is set to B<yes>
4665 and the configuration file changed on disk and its modification time (mtime)
4666 is newer than the previous version, the configuration is automatically reloaded.
4669 =item B<callback = ">I</path/to/callback>B<">
4671 If this directive is defined, B<l7directord> automatically calls
4672 the executable I</path/to/callback> after the configuration
4673 file has changed on disk. This is useful to update the configuration
4674 file through B<scp> on the other heartbeated host. The first argument
4675 to the callback is the name of the configuration.
4677 This directive might also be used to restart B<l7directord> automatically
4678 after the configuration file changed on disk. However, if B<autoreload>
4679 is set to B<yes>, the configuration is reloaded anyway.
4681 =item B<fallback = >I<ip_address|hostname[:portnumber|servicename]> [B<masq>|B<tproxy>]
4683 the server onto which a web service is redirected if all real
4684 servers are down. Typically this would be 127.0.0.1 with
4687 This directive may also appear within a virtual server, in which
4688 case it will override the global fallback server, if set.
4689 Also you can set either B<masq> or B<tproxy> as fallback forwarding
4690 mechanism. The default is B<masq>.
4692 =item B<logfile = ">I</path/to/logfile>B<">|syslog_facility
4694 An alternative logfile might be specified with this directive. If the logfile
4695 does not have a leading '/', it is assumed to be a syslog(3) facility name.
4697 The default is to log directly to the file I</var/log/l7vs/l7directord.log>.
4699 =item B<execute = ">I<configuration>B<">
4701 Use this directive to start an instance of l7directord for
4702 the named I<configuration>.
4706 If this directive is specified, the daemon does not go into background mode.
4707 All log-messages are redirected to stdout instead of a logfile.
4708 This is useful to run B<l7directord> supervised from daemontools.
4709 See http://untroubled.org/rpms/daemontools/ or http://cr.yp.to/daemontools.html
4712 =item B<quiescent = >[B<yes>|B<no>]
4714 If B<yes>, then when real or fallback servers are determined
4715 to be down, they are not actually removed from the UltraMonkey-L7,
4716 but set weight to zero.
4717 If B<no>, then the real or fallback servers will be removed
4718 from the UltraMonkey-L7. The default is B<yes>.
4720 This directive may also appear within a virtual server, in which
4721 case it will override the global fallback server, if set.
4726 =head2 Section virtual
4728 The following commands must follow a B<virtual> entry and must be indented
4729 with a minimum of 4 spaces or one tab.
4733 =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<">]
4735 Defines a real service by IP-address (or hostname) and port (or
4736 servicename). If the port is omitted then a 0 will be used.
4737 Optionally a range of IP addresses (or two hostnames) may be
4738 given, in which case each IP address in the range will be treated as a real
4739 server using the given port. The second argument defines the forwarding
4740 mechanism, it must be B<masq> or B<tproxy>. The third argument defines the weight of
4741 each real service. This argument is optional. Default is 1. The last two
4742 arguments are optional too. They define a request-receive pair to be used to
4743 check if a server is alive. They override the request-receive pair in the
4744 virtual server section. These two strings must be quoted. If the request
4745 string starts with I<http://...> the IP-address and port of the real server
4746 is overridden, otherwise the IP-address and port of the real server is used.
4748 =item B<module => I<proto-module [opt-module-args]>
4750 Indicates the module parameter of B<l7directord>. Here B<proto-module>
4751 denotes the protocol module name (For example, sessionless).
4752 The last argument B<opt-module-args> is optional (For example, --reschedule).
4756 =head2 More than one of these entries may be inside a virtual section:
4760 =item B<maxconn => I<n>
4762 Defines the maximum connection that the virtual service can handle. If the number of
4763 requests cross the maxconn limit, the requests would be redirected to the
4766 =item B<qosup => I<n>[B<K>|B<M>|B<G>]
4768 Defines the bandwidth quota size in bps for up stream. If the number of the
4769 bandwidth is over the qosup limit, a packet to the virtual service will be delayed
4770 until the number of bandwidth become below the qosup limit.
4771 B<K>(kilo), B<M>(mega) and B<G>(giga) unit are available.
4773 =item B<qosdown => I<n>[B<K>|B<M>|B<G>]
4775 Defines the bandwidth quota size in bps for down stream. If the number of the
4776 bandwidth is over the qosdown limit, a packet to the client will be delayed
4777 until the number of bandwidth become below the qosdown limit.
4778 B<K>(kilo), B<M>(mega) and B<G>(giga) unit are available.
4780 =item B<sorryserver =>I<ip_address|hostname[:portnumber|servicename]> [B<masq>|B<tproxy>]
4782 Defines a sorry server by IP-address (or hostname) and port (or
4783 servicename). The second argument defines the forwarding mechanism, it must be B<masq> or B<tproxy>.
4784 Firewall-mark settings cannot be set.
4785 If the number of requests to the virtual service cross the maxconn limit, or no available
4786 real server exists, then the requests would be redirected to the sorry server.
4788 =item B<checktype = negotiate>|B<connect>|I<N>|B<ping>|B<custom>|B<off>|B<on>
4790 Type of check to perform. Negotiate sends a request and matches a receive
4791 string. Connect only attempts to make a TCP/IP connection, thus the
4792 request and receive strings may be omitted. If checktype is a number then
4793 negotiate and connect is combined so that after each N connect attempts one
4794 negotiate attempt is performed. This is useful to check often if a service
4795 answers and in much longer intervals a negotiating check is done. Ping
4796 means that ICMP ping will be used to test the availability of real servers.
4797 Ping is also used as the connect check for UDP services. Custom means that
4798 custom command will be used to test the availability of real servers.
4799 Off means no checking will take place and no real or fallback servers will
4800 be activated. On means no checking will take place and real servers will
4801 always be activated. Default is I<negotiate>.
4803 =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>
4805 The type of service to monitor when using checktype=negotiate. None denotes
4806 a service that will not be monitored. If the port specified for the virtual
4807 server is 21, 25, 53, 80, 110, 119, 143, 389, 443, 3306, 5432 or 5060 then
4808 the default is B<ftp>, B<smtp>, B<dns>, B<http>, B<pop>, B<nntp>, B<imap>,
4809 B<ldap>, B<https>, B<mysql>, B<pgsql> or B<sip> respectively. Otherwise the
4810 default service is B<none>.
4812 =item B<checkport = >I<n>
4814 Number of port to monitor. Sometimes check port differs from service port.
4815 Default is port specified for the real server.
4817 =item B<request = ">I<uri to requested object>B<">
4819 This object will be requested each checkinterval seconds on each real
4820 server. The string must be inside quotes. Note that this string may be
4821 overridden by an optional per real-server based request-string.
4823 For a DNS check this should the name of an A record, or the address
4824 of a PTR record to look up.
4826 For a MySQL or PostgreSQL checks, this should be a SQL query.
4827 The data returned is not checked, only that the
4828 answer is one or more rows. This is a required setting.
4830 =item B<receive = ">I<regexp to compare>B<">
4832 If the requested result contains this I<regexp to compare>, the real server
4833 is declared alive. The regexp must be inside quotes. Keep in mind that
4834 regexps are not plain strings and that you need to escape the special
4835 characters if they should as literals. Note that this regexp may be
4836 overridden by an optional per real-server based receive regexp.
4838 For a DNS check this should be any one the A record's addresses or
4839 any one of the PTR record's names.
4841 For a MySQL check, the receive setting is not used.
4843 =item B<httpmethod = GET>|B<HEAD>
4845 Sets the HTTP method, which should be used to fetch the URI specified in
4846 the request-string. GET is the method used by default if the parameter is
4847 not set. If HEAD is used, the receive-string should be unset.
4849 =item B<virtualhost = ">I<hostname>B<">
4851 Used when using a negotiate check with HTTP or HTTPS. Sets the host header
4852 used in the HTTP request. In the case of HTTPS this generally needs to
4853 match the common name of the SSL certificate. If not set then the host
4854 header will be derived from the request url for the real server if present.
4855 As a last resort the IP address of the real server will be used.
4857 =item B<login = ">I<username>B<">
4859 Username to use to login to FTP, POP, IMAP, MySQL and PostgreSQL servers.
4860 For FTP, the default is anonymous. For POP and IMAP, the default is the
4861 empty string, in which case authentication will not be attempted.
4862 For a MySQL and PostgreSQL, the username must be provided.
4864 For SIP the username is used as both the to and from address
4865 for an OPTIONS query. If unset it defaults to l7directord\@<hostname>,
4866 hostname is derived as per the passwd option below.
4868 =item B<passwd = ">I<password>B<">
4870 Password to use to login to FTP, POP, IMAP, MySQL and PostgreSQL servers.
4871 Default is for FTP is l7directord\@<hostname>, where hostname is the
4872 environment variable HOSTNAME evaluated at run time, or sourced from uname
4873 if unset. The default for all other services is an empty password, in the
4874 case of MySQL and PostgreSQL this means authentication will not be
4877 =item B<database = ">I<databasename>B<">
4879 Database to use for MySQL and PostgreSQL servers, this is the database that
4880 the query (set by B<receive> above) will be performed against. This is a
4883 =item B<scheduler => I<scheduler_name>
4885 Scheduler to be used by UltraMonkey-L7 for load balancing.
4886 The available schedulers are only B<lc> and B<rr>. The default is I<rr>.
4888 =item B<protocol = tcp>
4890 Protocol to be used. B<l7vsadm> supports only B<tcp>.
4891 Since the virtual is specified as an IP address and port, it would be tcp
4892 and will default to tcp.
4894 =item B<realdowncallback = ">I</path/to/realdowncallback>B<">
4896 If this directive is defined, B<l7directord> automatically calls
4897 the executable I</path/to/realdowncallback> after a real server's status
4898 changes to down. The first argument to the realdowncallback is the real
4899 server's IP-address and port (ip_address:portnumber).
4901 =item B<realrecovercallback = ">I</path/to/realrecovercallback>B<">
4903 If this directive is defined, B<l7directord> automatically calls
4904 the executable I</path/to/realrecovercallback> after a real server's status
4905 changes to up. The first argument to the realrecovercallback is the real
4906 server's IP-address and port (ip_address:portnumber).
4908 =item B<customcheck = ">I<custom check command>B<">
4910 If this directive is defined and set B<checktype> to custom, B<l7directord>
4911 exec custom command for real servers health checking. Only if custom command
4912 returns 0, real servers will change to up. Otherwise real servers will change
4913 to down. Custom check command has some macro string. See below.
4919 Change to real server IP address.
4923 Change to real server port number.
4927 =item B<sslconfigfile = ">I</path/to/sslconfigfile>B<">
4929 When communication with Client is SSL, the file name for SSL setting is
4933 =item B<socketoption = ">I<OPTION...>B<">
4935 An option of the socket used in VirtualService is designated.
4936 The setting possible value is described.
4940 =item B<transparent>
4942 Set IP_TRANSPARENT option to the RealServer socket.
4944 =item B<deferaccept>
4946 Set TCP_DEFER_ACCEPT option to the listener socket of VirtualService.
4950 Set TCP_NODELAY option to the Client and RealServer socket.
4954 Set TCP_CORK option to the Client and RealServer socket.
4956 =item B<quickackon> or B<quickackoff>
4958 Set or unset TCP_QUICKACK option to the Client and RealServer socket.
4962 =item B<accesslog = >[B<yes>|B<no>]
4964 If B<yes>, then output client access log. The default is B<no>.
4966 =item B<accesslog_rotate_type = >[B<date>|B<size>|B<datesize>]
4968 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>.
4970 =item B<accesslog_rotate_max_backup_index = >I<n>
4972 Maximum number of backup files.
4974 =item B<accesslog_rotate_max_filesize = > I<n>[B<K>|B<M>|B<G>]
4976 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.
4978 =item B<accesslog_rotate_rotation_timing = >[B<year>|B<month>|B<week>|B<date>|B<hour>]
4980 Rotate timing type when B<accesslog_rotate_type> is set to B<date> or B<datesize>.
4982 =item B<accesslog_rotate_rotation_timing_value = ">I<rotation_timing_value>B<">
4984 Rotate timing. The formats are different by B<accesslog_rotate_rotation_timing> setting.
4988 =item B<accesslog_rotate_rotation_timing=year>
4990 FORMAT: B<"MM/dd HH:mm">
4992 =item B<accesslog_rotate_rotation_timing=month>
4994 FORMAT: B<"dd HH:mm">
4996 =item B<accesslog_rotate_rotation_timing=week>
4998 FORMAT: B<">[B<sun>|B<mon>|B<tue>|B<wed>|B<thu>|B<fri>|B<sat>] B<HH:mm">
5000 =item B<accesslog_rotate_rotation_timing=date>
5004 =item B<accesslog_rotate_rotation_timing=hour>
5010 =item B<session_thread_pool_size = >I<n>
5012 Defines the size each of session_thread_pool_size.
5013 Default is session_thread_pool_size parameter at l7vs.cf.
5020 B</etc/ha.d/conf/l7directord.cf>
5022 B</var/log/l7vs/l7directord.log>
5024 B</var/run/l7directord.>I<configuration>B<.pid>
5030 L<l7vsadm>, L<heartbeat>