/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
* Copyright 2012 Milan Jurik. All rights reserved.
*/
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <stddef.h>
#include <unistd.h>
#include <libilb.h>
#include <port.h>
#include <time.h>
#include <signal.h>
#include <assert.h>
#include <errno.h>
#include <spawn.h>
#include <fcntl.h>
#include <limits.h>
#include "libilb_impl.h"
#include "ilbd.h"
/* Global list of HC objects */
/* Timer queue for all hc related timers. */
/* Indicate whether the timer needs to be updated */
static void ilbd_hc_probe_timer(iu_tq_t *, void *);
#define MAX(a, b) ((a) > (b) ? (a) : (b))
/*
* Number of arguments passed to a probe. argc[0] is the path name of
* the probe.
*/
/*
* Max number of characters to be read from the output of a probe. It
* is long enough to read in a 64 bit integer.
*/
void
i_ilbd_setup_hc_list(void)
{
}
/*
* Given a hc object name, return a pointer to hc object if found.
*/
{
return (hc);
}
return (NULL);
}
/*
* Generates an audit record for create-healthcheck,
* delete-healtcheck subcommands.
*/
static void
{
int audit_error;
/*
* we came here from the path where ilbd incorporates
* the configuration that is listed in SCF:
* i_ilbd_read_config->ilbd_walk_hc_pgs->
* ->ilbd_scf_instance_walk_pg->ilbd_create_hc
* We skip auditing in that case
*/
logdebug("ilbd_audit_hc_event: skipping auditing");
return;
}
logerr("ilbd_audit_hc_event: adt_start_session failed");
}
(void) adt_end_session(ah);
logerr("ilbd_audit_rule_event: adt_set_from_ucred failed");
}
if (cmd == ILBD_CREATE_HC)
else if (cmd == ILBD_DESTROY_HC)
logerr("ilbd_audit_hc_event: adt_alloc_event failed");
}
switch (cmd) {
case ILBD_CREATE_HC:
(char *)audit_hcinfo->hci_test;
(char *)audit_hcinfo->hci_name;
/*
* If the value 0 is stored, the default values are
* set in the kernel. User land does not know about them
* So if the user does not specify them, audit record
* will show them as 0
*/
break;
case ILBD_DESTROY_HC:
(char *)audit_hcname;
break;
}
if (rc == ILB_STATUS_OK) {
logerr("ilbd_audit_hc_event: adt_put_event failed");
}
} else {
logerr("ilbd_audit_hc_event: adt_put_event failed");
}
}
(void) adt_end_session(ah);
}
/*
* Given the ilb_hc_info_t passed in (from the libilb), create a hc object
* in ilbd. The parameter ev_port is not used, refer to comments of
* ilbd_create_sg() in ilbd_sg.c
*/
/* ARGSUSED */
{
/*
* ps == NULL is from the daemon when it starts and load configuration
* ps != NULL is from client.
*/
if (ret != ILB_STATUS_OK) {
return (ret);
}
}
logdebug("ilbd_create_hc: missing healthcheck info");
return (ILB_STATUS_ENOHCINFO);
}
logdebug("ilbd_create_hc: healthcheck name %s already"
return (ILB_STATUS_EEXIST);
}
/*
* Sanity check on user supplied probe. The given path name
* must be a full path name (starts with '/') and is
* executable.
*/
logdebug("ilbd_create_hc: user script %s doesn't "
return (ILB_STATUS_ENOENT);
} else {
logdebug("ilbd_create_hc: user script %s is "
return (ILB_STATUS_EINVAL);
}
}
/* Create and add the hc object */
return (ILB_STATUS_ENOMEM);
}
else
/* Update SCF */
return (ret);
}
}
/* Everything is fine, now add it to the global list. */
return (ret);
}
/*
* Given a name of a hc object, destroy it.
*/
{
/*
* No need to check ps == NULL, daemon won't call any destroy func
* at start up.
*/
if (ret != ILB_STATUS_OK) {
return (ret);
}
logdebug("ilbd_destroy_hc: healthcheck %s does not exist",
hc_name);
return (ILB_STATUS_ENOENT);
}
/* If hc is in use, cannot delete it */
if (hc->ihc_rule_cnt > 0) {
logdebug("ilbd_destroy_hc: healthcheck %s is associated"
" with a rule - cannot remove", hc_name);
return (ILB_STATUS_INUSE);
}
logdebug("ilbd_destroy_hc: cannot destroy healthcheck %s "
"property group", hc_name);
return (ret);
}
return (ret);
}
/*
* Given a hc object name, return its information. Used by libilb to
* get hc info.
*/
{
hc_name);
return (ILB_STATUS_ENOENT);
}
*rbufsz += sizeof (ilb_hc_info_t);
return (ILB_STATUS_OK);
}
static void
const char *rulename)
{
int i;
tmp_rbufsz = *rbufsz;
/* Set up the reply buffer. rbufsz will be set to the new size. */
/* Calculate how much space is left for holding server info. */
*rbufsz += sizeof (ilb_hc_rule_srv_t);
tmp_rbufsz -= *rbufsz;
tmp_rbufsz -= sizeof (*dst_srv);
}
srvs->rs_num_srvs = i;
}
/*
* Given a rule name, return the hc status of its servers.
*/
{
rulename) != 0) {
continue;
}
return (ILB_STATUS_OK);
}
}
return (ILB_STATUS_RULE_NO_HC);
}
/*
* Initialize the hc timer and associate the notification of timeout to
* the given event port.
*/
void
{
}
}
}
/*
* HC timeout handler.
*/
void
ilbd_hc_timeout(void)
{
(void) iu_expire_timers(ilbd_hc_timer_q);
}
/*
* Set up the timer to fire at the earliest timeout.
*/
void
{
int timeout;
/*
* There is no change on the timer list, so no need to set up the
* timer again.
*/
if (!hc_timer_restarted)
return;
return;
} else if (timeout == 0) {
/*
* Handle the timeout immediately. After that (clearing all
* the expired timers), check to see if there are still
* timers running. If yes, start them.
*/
(void) iu_expire_timers(ilbd_hc_timer_q);
goto restart;
}
/*
* Failure to set a timeout is "OK" since hopefully there will be
* other events and timer_settime() will be called again. So
* we will only miss some timeouts. But in the worst case, no event
* will happen and ilbd will get stuck...
*/
}
/*
* Kill the probe process of a server.
*/
static void
{
/*
* First dissociate the fd from the event port. It should not
* fail.
*/
srv->shc_child_fd) != 0) {
}
/* Then kill the probe process. */
}
/* Should not fail... */
}
srv->shc_child_pid = 0;
}
/*
* Disable the server, either because the server is dead or because a timer
* cannot be started for this server. Note that this only affects the
* transient configuration, meaning only in memory. The persistent
* configuration is not affected.
*/
static void
{
/* Disable the server in kernel. */
logerr("%s: cannot disable server in kernel: rule %s "
"server %s", __func__,
}
}
/*
* A probe fails, set the state of the server.
*/
static void
{
/* Probe again */
return;
}
srv->shc_fail_cnt);
/*
* If this is a ping test, mark the server as
* unreachable instead of dead.
*/
} else {
}
/* Disable the server in kernel. */
logerr("%s: cannot disable server in kernel: rule %s "
"server %s", __func__,
}
/* Still keep probing in case the server is alive again. */
/* Only thing to do is to disable the server... */
}
}
/*
* A probe process has not returned for the ihc_timeout period, we should
* kill it. This function is the handler of this.
*/
/* ARGSUSED */
static void
{
}
/*
* Probe timeout handler. Send out the appropriate probe.
*/
/* ARGSUSED */
static void
{
/*
* If starting the probe fails, just pretend that the timeout has
* extended.
*/
if (!ilbd_run_probe(srv)) {
/*
* If we cannot restart the timer, the only thing we can do
* is to disable this server. Hopefully the sys admin will
* notice this and enable this server again later.
*/
logerr("%s: cannot restart timer: rule %s server %s, "
"disabling it", __func__,
}
return;
}
/*
* Similar to above, if kill timer cannot be started, disable the
* server.
*/
logerr("%s: cannot start kill timer: rule %s server %s, "
"disabling it", __func__,
}
}
/* Restart the periodic timer for a given server. */
static ilb_status_t
{
int timeout;
/* Don't allow the timeout interval to be less than 1s */
/*
* If the probe is actually a ping probe, there is no need to
* do default pinging. Just skip the step.
*/
else
return (ILB_STATUS_TIMER);
return (ILB_STATUS_OK);
}
/* Helper routine to associate a server with its hc object. */
static ilb_status_t
{
return (ILB_STATUS_ENOMEM);
if (ret != ILB_STATUS_OK) {
return (ret);
}
} else {
}
return (ILB_STATUS_OK);
}
/* Handy macro to cancel a server's timer. */
{ \
void *arg; \
int ret; \
} \
hc_timer_restarted = B_TRUE; \
}
/* Helper routine to dissociate a server from its hc object. */
static ilb_status_t
{
if (tmp_srv->shc_child_pid != 0)
return (ILB_STATUS_OK);
}
}
return (ILB_STATUS_ENOENT);
}
/* Helper routine to dissociate all servers of a rule from its hc object. */
static void
{
if (srv->shc_child_pid != 0)
}
}
/* Associate a rule with its hc object. */
{
/* The rule is assumed to be initialized appropriately. */
logdebug("ilbd_hc_associate_rule: healthcheck %s does not "
return (ILB_STATUS_ENOHCINFO);
}
return (ILB_STATUS_RULE_HC_MISMATCH);
}
logdebug("ilbd_hc_associate_rule: out of memory");
return (ILB_STATUS_ENOMEM);
}
/* Add all the servers. */
ev_port)) != ILB_STATUS_OK) {
/* Remove all previously added servers */
return (ret);
}
}
hc->ihc_rule_cnt++;
return (ILB_STATUS_OK);
}
/* Dissociate a rule from its hc object. */
{
/* The rule is assumed to be initialized appropriately. */
logdebug("ilbd_hc_dissociate_rule: healthcheck %s does not "
return (ILB_STATUS_ENOENT);
}
break;
}
logdebug("ilbd_hc_dissociate_rule: rule %s is not associated "
return (ILB_STATUS_ENOENT);
}
hc->ihc_rule_cnt--;
return (ILB_STATUS_OK);
}
/*
* Given a hc object name and a rule, check to see if the rule is associated
* with the hc object. If it is, the hc object is returned in **hc and the
* ilbd_hc_rule_t is returned in **hc_rule.
*/
static boolean_t
{
return (B_FALSE);
*hc_rule = tmp_hc_rule;
return (B_TRUE);
}
}
return (B_FALSE);
}
/* Associate a server with its hc object. */
int ev_port)
{
return (ILB_STATUS_ENOENT);
}
/* Dissociate a server from its hc object. */
{
return (ILB_STATUS_ENOENT);
}
static ilb_status_t
{
return (ILB_STATUS_ENOENT);
continue;
}
if (enable) {
if (ret != ILB_STATUS_OK) {
logerr("%s: cannot start timers for "
"rule %s server %s", __func__,
return (ret);
}
/* Start from fresh... */
tmp_srv->shc_fail_cnt = 0;
}
} else {
if (tmp_srv->shc_child_pid != 0)
}
}
return (ILB_STATUS_OK);
}
return (ILB_STATUS_ENOENT);
}
{
}
{
}
/*
* servers).
*/
static ilb_status_t
{
int ret;
return (ILB_STATUS_ENOENT);
if (enable) {
/*
* If the server is disabled in the rule, do not
* restart its timer.
*/
if (ret != ILB_STATUS_OK) {
logerr("%s: cannot start timers for "
"rule %s server %s", __func__,
goto rollback;
} else {
/* Start from fresh... */
tmp_srv->shc_fail_cnt = 0;
}
}
} else {
if (tmp_srv->shc_child_pid != 0)
}
}
}
return (ILB_STATUS_OK);
if (enable) {
tmp_srv->shc_fail_cnt = 0;
}
} else {
if (tmp_srv->shc_child_pid != 0)
}
}
}
return (ret);
}
{
}
{
}
static const char *
{
switch (topo) {
case ILB_TOPO_DSR:
return ("DSR");
case ILB_TOPO_NAT:
return ("NAT");
case ILB_TOPO_HALF_NAT:
return ("HALF_NAT");
default:
/* Should not happen. */
break;
}
return ("");
}
/*
* Create the argument list to be passed to a hc probe command.
* The passed in argv is assumed to have HC_PROBE_ARGC elements.
*/
static boolean_t
{
int i;
return (B_FALSE);
} else {
case ILBD_HC_USER:
return (B_FALSE);
break;
case ILBD_HC_TCP:
case ILBD_HC_UDP:
NULL) {
return (B_FALSE);
}
break;
case ILBD_HC_PING:
return (B_FALSE);
}
break;
}
}
/*
* argv[1] is the VIP.
*
* Right now, the VIP and the backend server addresses should be
* in the same IP address family. Here we don't do that in case
* this assumption is changed in future.
*/
goto cleanup;
} else {
goto cleanup;
}
}
goto cleanup;
/*
* argv[2] is the backend server address.
*/
goto cleanup;
} else {
goto cleanup;
}
}
goto cleanup;
/*
* argv[3] is the transport protocol used in the rule.
*/
case IPPROTO_TCP:
break;
case IPPROTO_UDP:
break;
default:
goto cleanup;
}
goto cleanup;
/*
* argv[4] is the load balance mode, DSR, NAT, HALF-NAT.
*/
goto cleanup;
/*
* argv[5] is the port range. Right now, there should only be 1 port.
*/
switch (rule->irl_hcpflag) {
case ILB_HCI_PROBE_FIX:
break;
case ILB_HCI_PROBE_ANY: {
} else {
}
else
break;
}
default:
goto cleanup;
}
goto cleanup;
/*
* argv[6] is the probe timeout.
*/
goto cleanup;
return (B_TRUE);
for (i = 0; i < HC_PROBE_ARGC; i++) {
}
return (B_FALSE);
}
static void
{
int i;
}
/* Spawn a process to run the hc probe on the given server. */
static boolean_t
{
int fdflags;
char *probe_name;
logdebug("ilbd_run_probe: calloc");
return (B_FALSE);
}
/* Set up a pipe to get output from probe command. */
logdebug("ilbd_run_probe: cannot create pipe");
return (B_FALSE);
}
/* Set our side of the pipe to be non-blocking */
logdebug("ilbd_run_probe: fcntl(F_GETFL)");
goto cleanup;
}
logdebug("ilbd_run_probe: fcntl(F_SETFL)");
goto cleanup;
}
if (posix_spawn_file_actions_init(&fd_actions) != 0) {
logdebug("ilbd_run_probe: posix_spawn_file_actions_init");
goto cleanup;
}
if (posix_spawnattr_init(&attr) != 0) {
logdebug("ilbd_run_probe: posix_spawnattr_init");
goto cleanup;
}
logdebug("ilbd_run_probe: posix_spawn_file_actions_addclose");
goto cleanup;
}
STDOUT_FILENO) != 0) {
logdebug("ilbd_run_probe: posix_spawn_file_actions_dup2");
goto cleanup;
}
logdebug("ilbd_run_probe: posix_spawn_file_actions_addclose");
goto cleanup;
}
/* Reset all signal handling of the child to default. */
(void) sigfillset(&child_sigset);
logdebug("ilbd_run_probe: posix_spawnattr_setsigdefault");
goto cleanup;
}
/* Don't want SIGCHLD. */
POSIX_SPAWN_SETSIGDEF) != 0) {
logdebug("ilbd_run_probe: posix_spawnattr_setflags");
goto cleanup;
}
logdebug("ilbd_run_probe: create_argv");
goto cleanup;
}
/*
* If we are doing default pinging or not using a user supplied
* probe, we should execute our standard supplied probe. The
* supplied probe command handles all types of probes. And the
* type used depends on argv[0], as filled in by create_argv().
*/
} else {
}
NULL) != 0) {
goto cleanup;
}
POLLRDNORM, probe_ev) != 0) {
/*
* Need to kill the child. It will free the srv->shc_ev,
* which is probe_ev. So set probe_ev to NULL.
*/
goto cleanup;
}
(void) posix_spawn_file_actions_destroy(&fd_actions);
(void) posix_spawnattr_destroy(&attr);
return (B_TRUE);
if (init_fd_actions == B_TRUE)
(void) posix_spawn_file_actions_destroy(&fd_actions);
(void) posix_spawnattr_destroy(&attr);
return (B_FALSE);
}
/*
* Called by ild_hc_probe_return() to re-associate the fd to a child to
* the event port.
*/
static void
{
POLLRDNORM, ev) != 0) {
/*
* If we cannot reassociate with the port, the only
* thing we can do now is to kill the child and
* do a blocking wait here...
*/
}
}
/*
* To handle a child probe process hanging up.
*/
static void
{
int ret;
/* ilbd does not care about this process anymore ... */
srv->shc_child_pid = 0;
}
switch (ret_pid) {
case -1:
logperror("ilbd_hc_child_hup: waitpid");
/* FALLTHROUGH */
case 0:
/* The child has not completed the exit. Wait again. */
break;
default:
/* Right now, we just ignore the exit status. */
}
}
/*
* To read the output of a child probe process.
*/
static void
{
int ret;
/* Should not happen since event port should have caught this. */
/*
* We expect the probe command to print out the RTT only. But
* the command may misbehave and print out more than what we intend to
* read in. So need to do this check below to "flush" out all the
* output from the command.
*/
/* We don't need to know about this event anymore. */
srv->shc_child_pid = 0;
} else {
return;
}
/*
* -1 means the server is dead or the probe somehow fails. Treat
* them both as server is dead.
*/
if (rtt == -1) {
return;
} else if (rtt > 0) {
/* If the returned RTT value is not valid, just ignore it. */
/* Set rtt to be the simple smoothed average. */
} else {
(rtt >> 2);
}
}
}
case ilbd_hc_def_pinging:
/* Ping is OK, now start the probe. */
break;
case ilbd_hc_probing:
srv->shc_fail_cnt = 0;
/* Server is dead before, re-enable it. */
/*
* If enabling the server in kernel fails now,
* hopefully when the timer fires again later, the
* enabling can be done.
*/
logerr("%s: cannot enable server in kernel: "
" rule %s server %s", __func__,
} else {
}
} else {
}
logerr("%s: cannot restart timer: rule %s server %s",
}
break;
default:
break;
}
}
/*
* Handle the return event of a child probe fd.
*/
void
{
/*
* Note that there can be more than one events delivered to us at
* the same time. So we need to check them individually.
*/
if (port_events & POLLRDNORM)
return;
}
/*
* Re-associate the fd with the port so that when the child
* exits, we can reap the status.
*/
}