view usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_sg.c @ 10946:324bab2b3370

PSARC 2008/575 ILB: Integrated L3/L4 Load balancer 6882718 in-kernel simple L3/L4 load balancing service should be provided in Solaris 6884202 ipobs_hook() in ip_input() invalidates DB_REF assumption
author Sangeeta Misra <Sangeeta.Misra@Sun.COM>
date Tue, 03 Nov 2009 23:15:19 -0800
parents
children fe654b6a1645
line wrap: on
line source

/*
 * 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
 * or http://www.opensolaris.org/os/licensing.
 * 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.
 */

#include <stdlib.h>
#include <strings.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/list.h>
#include <assert.h>
#include <errno.h>
#include <libilb.h>
#include <net/if.h>
#include <inet/ilb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "libilb_impl.h"
#include "ilbd.h"

typedef enum {
	not_searched,
	stop_found,
	cont_search,
	fail_search
} srch_ind_t;

static list_t	ilbd_sg_hlist;

static ilb_status_t i_delete_srv(ilbd_sg_t *, ilbd_srv_t *, int);
static void i_ilbd_free_srvID(ilbd_sg_t *, int32_t);

/* Last parameter to pass to i_find_srv(), specifying the matching mode */
#define	MODE_ADDR	1
#define	MODE_SRVID	2

static ilbd_srv_t *i_find_srv(list_t *, ilb_sg_srv_t *, int);

void
i_setup_sg_hlist(void)
{
	list_create(&ilbd_sg_hlist, sizeof (ilbd_sg_t),
	    offsetof(ilbd_sg_t, isg_link));
}

/*
 * allocate storage for a daemon-internal server group, init counters
 */
static ilbd_sg_t *
i_ilbd_alloc_sg(char *name)
{
	ilbd_sg_t	*d_sg;

	d_sg = calloc(sizeof (*d_sg), 1);
	if (d_sg == NULL)
		goto out;

	(void) strlcpy(d_sg->isg_name, name, sizeof (d_sg->isg_name));

	list_create(&d_sg->isg_srvlist, sizeof (ilbd_srv_t),
	    offsetof(ilbd_srv_t, isv_srv_link));
	list_create(&d_sg->isg_rulelist, sizeof (ilbd_rule_t),
	    offsetof(ilbd_rule_t, irl_sglink));

	list_insert_tail(&ilbd_sg_hlist, d_sg);
out:
	return (d_sg);
}

static ilb_status_t
i_ilbd_save_sg(ilbd_sg_t *d_sg, ilbd_scf_cmd_t scf_cmd, const char *prop_name,
    char *valstr)
{
	switch (scf_cmd) {
	case ILBD_SCF_CREATE:
		return (ilbd_create_pg(ILBD_SCF_SG, (void *)d_sg));
	case ILBD_SCF_DESTROY:
		return (ilbd_destroy_pg(ILBD_SCF_SG, d_sg->isg_name));
	case ILBD_SCF_ENABLE_DISABLE:
		if (prop_name == NULL)
			return (ILB_STATUS_EINVAL);
		return (ilbd_change_prop(ILBD_SCF_SG, d_sg->isg_name,
		    prop_name, valstr));
	default:
		logdebug("i_ilbd_save_sg: invalid scf cmd %d", scf_cmd);
		return (ILB_STATUS_EINVAL);
	}
}

ilb_status_t
i_attach_rule2sg(ilbd_sg_t *sg, ilbd_rule_t *irl)
{
	/* assert: the same rule is attached to any sg only once */
	list_insert_tail(&sg->isg_rulelist, irl);
	return (ILB_STATUS_OK);
}

static void
i_ilbd_free_sg(ilbd_sg_t *sg)
{
	ilbd_srv_t *tmp_srv;

	if (sg == NULL)
		return;
	list_remove(&ilbd_sg_hlist, sg);
	while ((tmp_srv = list_remove_tail(&sg->isg_srvlist)) != NULL) {
		i_ilbd_free_srvID(sg, tmp_srv->isv_id);
		free(tmp_srv);
		sg->isg_srvcount--;
	}
	free(sg);
}

ilbd_sg_t *
i_find_sg_byname(const char *name)
{
	ilbd_sg_t *sg;

	/* find position of sg in list */
	for (sg = list_head(&ilbd_sg_hlist); sg != NULL;
	    sg = list_next(&ilbd_sg_hlist, sg)) {
		if (strncmp(sg->isg_name, name, sizeof (sg->isg_name)) == 0)
			return (sg);
	}
	return (sg);
}

/*
 * Generates an audit record for enable-server, disable-server, remove-server
 * delete-servergroup, create-servergroup and add-server subcommands.
 */
static void
ilbd_audit_server_event(audit_sg_event_data_t *data,
    ilbd_cmd_t cmd, ilb_status_t rc, ucred_t *ucredp)
{
	adt_session_data_t	*ah;
	adt_event_data_t	*event;
	au_event_t	flag;
	int	audit_error;

	if ((ucredp == NULL) && ((cmd == ILBD_ADD_SERVER_TO_GROUP) ||
	    (cmd == ILBD_CREATE_SERVERGROUP)))  {
		/*
		 * We came here from the path where ilbd is
		 * incorporating the ILB configuration from
		 * SCF. In that case, we skip auditing
		 */
		return;
	}

	if (adt_start_session(&ah, NULL, 0) != 0) {
		logerr("ilbd_audit_server_event: adt_start_session failed");
		exit(EXIT_FAILURE);
	}

	if (adt_set_from_ucred(ah, ucredp, ADT_NEW) != 0) {
		(void) adt_end_session(ah);
		logerr("ilbd_audit_server_event: adt_set_from_ucred failed");
		exit(EXIT_FAILURE);
	}

	if (cmd == ILBD_ENABLE_SERVER)
		flag = ADT_ilb_enable_server;
	else if (cmd == ILBD_DISABLE_SERVER)
		flag = ADT_ilb_disable_server;
	else if (cmd == ILBD_REM_SERVER_FROM_GROUP)
		flag = ADT_ilb_remove_server;
	else if (cmd == ILBD_ADD_SERVER_TO_GROUP)
		flag = ADT_ilb_add_server;
	else if (cmd == ILBD_CREATE_SERVERGROUP)
		flag = ADT_ilb_create_servergroup;
	else if (cmd == ILBD_DESTROY_SERVERGROUP)
		flag = ADT_ilb_delete_servergroup;

	if ((event = adt_alloc_event(ah, flag)) == NULL) {
		logerr("ilbd_audit_server_event: adt_alloc_event failed");
		exit(EXIT_FAILURE);
	}
	(void) memset((char *)event, 0, sizeof (adt_event_data_t));

	switch (cmd) {
	case ILBD_ENABLE_SERVER:
		event->adt_ilb_enable_server.auth_used =
		    NET_ILB_ENABLE_AUTH;
		event->adt_ilb_enable_server.server_id =
		    data->ed_serverid;
		event->adt_ilb_enable_server.server_ipaddress =
		    data->ed_server_address;
		break;
	case ILBD_DISABLE_SERVER:
		event->adt_ilb_disable_server.auth_used =
		    NET_ILB_ENABLE_AUTH;
		event->adt_ilb_disable_server.server_id =
		    data->ed_serverid;
		event->adt_ilb_disable_server.server_ipaddress =
		    data->ed_server_address;
		break;
	case ILBD_REM_SERVER_FROM_GROUP:
		event->adt_ilb_remove_server.auth_used =
		    NET_ILB_CONFIG_AUTH;
		event->adt_ilb_remove_server.server_id =
		    data->ed_serverid;
		event->adt_ilb_remove_server.server_group = data->ed_sgroup;
		event->adt_ilb_remove_server.server_ipaddress =
		    data->ed_server_address;
		break;
	case ILBD_CREATE_SERVERGROUP:
		event->adt_ilb_create_servergroup.auth_used =
		    NET_ILB_CONFIG_AUTH;
		event->adt_ilb_create_servergroup.server_group =
		    data->ed_sgroup;
		break;
	case ILBD_ADD_SERVER_TO_GROUP:
		event->adt_ilb_add_server.auth_used =
		    NET_ILB_CONFIG_AUTH;
		event->adt_ilb_add_server.server_ipaddress =
		    data->ed_server_address;
		event->adt_ilb_add_server.server_id =
		    data->ed_serverid;
		event->adt_ilb_add_server.server_group =
		    data->ed_sgroup;
		event->adt_ilb_add_server.server_minport =
		    ntohs(data->ed_minport);
		event->adt_ilb_add_server.server_maxport =
		    ntohs(data->ed_maxport);
		break;
	case ILBD_DESTROY_SERVERGROUP:
		event->adt_ilb_delete_servergroup.auth_used =
		    NET_ILB_CONFIG_AUTH;
		event->adt_ilb_delete_servergroup.server_group =
		    data->ed_sgroup;
		break;
	}

	/* Fill in success/failure */
	if (rc == ILB_STATUS_OK) {
		if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
			logerr("ilbd_audit_server_event:"
			    " adt_put_event failed");
			exit(EXIT_FAILURE);
		}
	} else {
		audit_error = ilberror2auditerror(rc);
		if (adt_put_event(event, ADT_FAILURE, audit_error) != 0) {
			logerr("ilbd_audit_server_event:"
			    " adt_put_event failed");
			exit(EXIT_FAILURE);
		}
	}
	adt_free_event(event);
	(void) adt_end_session(ah);
}

ilb_status_t
ilbd_destroy_sg(const char *sg_name, const struct passwd *ps,
    ucred_t *ucredp)
{
	ilb_status_t	rc;
	ilbd_sg_t	*tmp_sg;
	audit_sg_event_data_t   audit_sg_data;

	(void) memset(&audit_sg_data, 0, sizeof (audit_sg_event_data_t));
	audit_sg_data.ed_sgroup = (char *)sg_name;

	rc = ilbd_check_client_config_auth(ps);
	if (rc != ILB_STATUS_OK) {
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_DESTROY_SERVERGROUP, rc, ucredp);
		return (rc);
	}

	tmp_sg = i_find_sg_byname(sg_name);
	if (tmp_sg == NULL) {
		logdebug("ilbd_destroy_sg: cannot find specified server"
		    " group %s", sg_name);
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_DESTROY_SERVERGROUP, ILB_STATUS_SGUNAVAIL, ucredp);
		return (ILB_STATUS_SGUNAVAIL);
	}

	/*
	 * we only destroy SGs that don't have any rules associated with
	 * them anymore.
	 */
	if (list_head(&tmp_sg->isg_rulelist) != NULL) {
		logdebug("ilbd_destroy_sg: server group %s has rules"
		" associated with it and thus cannot be"
		    " removed", tmp_sg->isg_name);
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_DESTROY_SERVERGROUP, ILB_STATUS_SGINUSE, ucredp);
		return (ILB_STATUS_SGINUSE);
	}

	if (ps != NULL) {
		rc = i_ilbd_save_sg(tmp_sg, ILBD_SCF_DESTROY, NULL, NULL);
		if (rc != ILB_STATUS_OK) {
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_DESTROY_SERVERGROUP, rc, ucredp);
			return (rc);
		}
	}
	i_ilbd_free_sg(tmp_sg);
	ilbd_audit_server_event(&audit_sg_data, ILBD_DESTROY_SERVERGROUP,
	    rc, ucredp);
	return (rc);
}

/* ARGSUSED */
/*
 * Parameter ev_port is not used but has to have for read persistent configure
 * ilbd_create_sg(), ilbd_create_hc() and ilbd_create_rule() are callbacks
 * for ilbd_scf_instance_walk_pg() which requires the same signature.
 */
ilb_status_t
ilbd_create_sg(ilb_sg_info_t *sg, int ev_port, const struct passwd *ps,
    ucred_t *ucredp)
{
	ilb_status_t	rc = ILB_STATUS_OK;
	ilbd_sg_t	*d_sg;
	audit_sg_event_data_t   audit_sg_data;

	(void) memset(&audit_sg_data, 0, sizeof (audit_sg_event_data_t));
	audit_sg_data.ed_sgroup = sg->sg_name;

	if (ps != NULL) {
		rc = ilbd_check_client_config_auth(ps);
		if (rc != ILB_STATUS_OK) {
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_CREATE_SERVERGROUP, rc, ucredp);
			return (rc);
		}
	}

	if (i_find_sg_byname(sg->sg_name) != NULL) {
		logdebug("ilbd_create_sg: server group %s already exists",
		    sg->sg_name);
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_CREATE_SERVERGROUP, ILB_STATUS_SGEXISTS, ucredp);
		return (ILB_STATUS_SGEXISTS);
	}

	d_sg = i_ilbd_alloc_sg(sg->sg_name);
	if (d_sg == NULL) {
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_CREATE_SERVERGROUP, ILB_STATUS_ENOMEM, ucredp);
		return (ILB_STATUS_ENOMEM);
	}

	/*
	 * we've successfully created the sg in memory. Before we can
	 * return "success", we need to reflect this in persistent
	 * storage
	 */
	if (ps != NULL) {
		rc = i_ilbd_save_sg(d_sg, ILBD_SCF_CREATE, NULL, NULL);
		if (rc != ILB_STATUS_OK) {
			i_ilbd_free_sg(d_sg);
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_CREATE_SERVERGROUP, rc, ucredp);
			return (rc);
		}
	}
	ilbd_audit_server_event(&audit_sg_data,
	    ILBD_CREATE_SERVERGROUP, rc, ucredp);
	return (rc);
}

/*
 * This function checks whether tsrv should/can be inserted before lsrv
 * and does so if possible.
 * We keep the list in sorted order so we don't have to search it
 * in its entirety for overlap every time we insert a new server.
 * Return code:
 *	stop_found: don't continue searching because we found a place
 *	cont_search: continue with next element in the list
 *	fail_search: search failed (caller translates to ILB_STATUS_EEXIST)
 */
static srch_ind_t
i_test_and_insert(ilbd_srv_t *tsrv, ilbd_srv_t *lsrv, list_t *srvlist)
{
	struct in6_addr	*t1, *l1;
	int		fnd;

	t1 = &tsrv->isv_addr;
	l1 = &lsrv->isv_addr;

	if ((fnd = ilb_cmp_in6_addr(t1, l1, NULL)) == 1)
		return (cont_search);	/* search can continue */

	if (fnd == 0) {
		logdebug("i_test_and_insert: specified server already exists");
		return (fail_search);
	}
	/* the list is kept in ascending order */
	list_insert_before(srvlist, lsrv, tsrv);
	return (stop_found);
}


/*
 * copy a server description [ip1,ip2,port1,port2,srvID,flags]
 */
#define	COPY_SERVER(src, dest)					\
	(dest)->sgs_addr = (src)->sgs_addr;			\
	(dest)->sgs_minport = (src)->sgs_minport;		\
	(dest)->sgs_maxport = (src)->sgs_maxport;		\
	(dest)->sgs_id = (src)->sgs_id;				\
	(void) strlcpy((dest)->sgs_srvID, (src)->sgs_srvID,	\
	    sizeof ((dest)->sgs_srvID));			\
	(dest)->sgs_flags = (src)->sgs_flags

static ilb_status_t
i_add_srv2sg(ilbd_sg_t *dsg, ilb_sg_srv_t *srv, ilbd_srv_t **ret_srv)
{
	ilb_sg_srv_t	*n_sg_srv;
	list_t		*srvlist;
	srch_ind_t	search = not_searched;
	ilb_status_t	rc = ILB_STATUS_OK;
	ilbd_srv_t	*nsrv, *lsrv;
	in_port_t	h_minport, h_maxport;

	nsrv = calloc(sizeof (*nsrv), 1);
	if (nsrv == NULL)
		return (ILB_STATUS_ENOMEM);
	n_sg_srv = &nsrv->isv_srv;
	COPY_SERVER(srv, n_sg_srv);

	/*
	 * port info is in network byte order - we need host byte order
	 * for comparisons purposes
	 */
	h_minport = ntohs(n_sg_srv->sgs_minport);
	h_maxport = ntohs(n_sg_srv->sgs_maxport);
	if (h_minport != 0 && h_minport > h_maxport)
		n_sg_srv->sgs_maxport = n_sg_srv->sgs_minport;

	srvlist = &dsg->isg_srvlist;

	lsrv = list_head(srvlist);
	if (lsrv == NULL) {
		list_insert_head(srvlist, nsrv);
	} else {
		while (lsrv != NULL) {
			search = i_test_and_insert(nsrv, lsrv,
			    srvlist);

			if (search != cont_search)
				break;
			lsrv = list_next(srvlist, lsrv);

			/* if reaches the end of list, insert to the tail */
			if (search == cont_search && lsrv == NULL)
				list_insert_tail(srvlist, nsrv);
		}
		if (search == fail_search)
			rc = ILB_STATUS_EEXIST;
	}

	if (rc == ILB_STATUS_OK) {
		dsg->isg_srvcount++;
		*ret_srv = nsrv;
	} else {
		free(nsrv);
	}

	return (rc);
}

/*
 * Allocate a server ID.  The algorithm is simple.  Just check the ID array
 * of the server group and find an unused ID.  If *set_id is given, it
 * means that the ID is already allocated and the ID array needs to be
 * updated.  This is the case when ilbd reads from the persistent
 * configuration.
 */
static int32_t
i_ilbd_alloc_srvID(ilbd_sg_t *sg, int32_t *set_id)
{
	int32_t		id;
	int32_t		i;

	/* The server ID is already allocated, just update the ID array. */
	if (set_id != NULL) {
		assert(sg->isg_id_arr[*set_id] == 0);
		sg->isg_id_arr[*set_id] = 1;
		return (*set_id);
	}

	/* if we're "full up", give back something invalid */
	if (sg->isg_srvcount == MAX_SRVCOUNT)
		return (BAD_SRVID);

	i = sg->isg_max_id;
	for (id = 0; id < MAX_SRVCOUNT; id++) {
		if (sg->isg_id_arr[(id + i) % MAX_SRVCOUNT] == 0)
			break;
	}

	sg->isg_max_id = (id + i) % MAX_SRVCOUNT;
	sg->isg_id_arr[sg->isg_max_id] = 1;
	return (sg->isg_max_id);
}

/*
 * Free a server ID by updating the server group's ID array.
 */
static void
i_ilbd_free_srvID(ilbd_sg_t *sg, int32_t id)
{
	assert(sg->isg_id_arr[id] == 1);
	sg->isg_id_arr[id] = 0;
}

/*
 * This function is called by ilbd_add_server_to_group() and
 * ilb_remove_server_group() to create a audit record for a
 * failed servicing of add-server/remove-server command
 */
static void
fill_audit_record(ilb_sg_info_t *sg, audit_sg_event_data_t *audit_sg_data,
    ilbd_cmd_t cmd, ilb_status_t rc, ucred_t *ucredp)
{
	ilb_sg_srv_t	*tsrv;
	int	i;

	for (i = 0; i < sg->sg_srvcount; i++) {
		tsrv = &sg->sg_servers[i];
		if (cmd == ILBD_ADD_SERVER_TO_GROUP)  {
			char	addrstr_buf[INET6_ADDRSTRLEN];

			audit_sg_data->ed_serverid = NULL;
			ilbd_addr2str(&tsrv->sgs_addr, addrstr_buf,
			    sizeof (addrstr_buf));
			audit_sg_data->ed_server_address = addrstr_buf;
			audit_sg_data->ed_minport = tsrv->sgs_minport;
			audit_sg_data->ed_maxport = tsrv->sgs_maxport;
			audit_sg_data->ed_sgroup = sg->sg_name;
		} else if (cmd == ILBD_REM_SERVER_FROM_GROUP) {
			audit_sg_data->ed_serverid = tsrv->sgs_srvID;
			audit_sg_data->ed_sgroup = sg->sg_name;
			audit_sg_data->ed_server_address = NULL;
			audit_sg_data->ed_minport = 0;
			audit_sg_data->ed_maxport = 0;
		}
		ilbd_audit_server_event(audit_sg_data, cmd, rc, ucredp);
	}
}

/*
 * the name(s) of the server(s) are encoded in the sg.
 */
ilb_status_t
ilbd_add_server_to_group(ilb_sg_info_t *sg_info, int ev_port,
    const struct passwd *ps, ucred_t *ucredp)
{
	ilb_status_t	rc = ILB_STATUS_OK;
	ilbd_sg_t	*tmp_sg;
	int		i, j;
	int32_t		new_id = BAD_SRVID;
	int32_t		af = AF_UNSPEC;
	ilbd_srv_t	*nsrv;
	ilb_sg_srv_t	*srv;
	audit_sg_event_data_t   audit_sg_data;
	char    addrstr_buf[INET6_ADDRSTRLEN];

	if (ps != NULL) {
		rc = ilbd_check_client_config_auth(ps);
		if (rc != ILB_STATUS_OK) {
			fill_audit_record(sg_info, &audit_sg_data,
			    ILBD_ADD_SERVER_TO_GROUP, rc, ucredp);
			return (rc);
		}
	}

	tmp_sg = i_find_sg_byname(sg_info->sg_name);
	if (tmp_sg == NULL) {
		logdebug("ilbd_add_server_to_group: server"
		    " group %s does not exist", sg_info->sg_name);
		fill_audit_record(sg_info, &audit_sg_data,
		    ILBD_ADD_SERVER_TO_GROUP, ILB_STATUS_ENOENT, ucredp);
		return (ILB_STATUS_ENOENT);
	}

	/*
	 * we do the dance with address family below to make sure only
	 * IP addresses in the same AF get into an SG; the first one to get
	 * in sets the "tone"
	 * if this is the first server to join a group, check whether
	 * there's no mismatch with any *rules* already attached
	 */
	if (tmp_sg->isg_srvcount > 0) {
		ilbd_srv_t *tsrv = list_head(&tmp_sg->isg_srvlist);

		af = GET_AF(&tsrv->isv_addr);
	} else {
		ilbd_rule_t	*irl = list_head(&tmp_sg->isg_rulelist);

		if (irl != NULL)
			af = GET_AF(&irl->irl_vip);
	}

	for (i = 0; i < sg_info->sg_srvcount; i++) {
		srv = &sg_info->sg_servers[i];

		(void) memset(&audit_sg_data, 0, sizeof (audit_sg_data));
		ilbd_addr2str(&srv->sgs_addr, addrstr_buf,
		    sizeof (addrstr_buf));
		audit_sg_data.ed_server_address = addrstr_buf;
		audit_sg_data.ed_minport = srv->sgs_minport;
		audit_sg_data.ed_maxport = srv->sgs_maxport;
		audit_sg_data.ed_sgroup = sg_info->sg_name;

		/* only test if we have sth to test against */
		if (af != AF_UNSPEC) {
			int32_t	sgs_af = GET_AF(&srv->sgs_addr);

			if (af != sgs_af) {
				logdebug("address family mismatch with previous"
				    " hosts in servergroup or with rule");
				rc = ILB_STATUS_MISMATCHH;
				ilbd_audit_server_event(&audit_sg_data,
				    ILBD_ADD_SERVER_TO_GROUP, rc, ucredp);
				goto rollback;
			}
		}

		/*
		 * PS: NULL means daemon is loading configure from scf.
		 * ServerID is already assigned, just update the ID array.
		 */
		if (ps != NULL) {
			new_id = i_ilbd_alloc_srvID(tmp_sg, NULL);
			if (new_id == BAD_SRVID) {
				logdebug("ilbd_add_server_to_group: server"
				    "group %s is full, no more servers"
				    " can be added", sg_info->sg_name);
				rc = ILB_STATUS_SGFULL;
				ilbd_audit_server_event(&audit_sg_data,
				    ILBD_ADD_SERVER_TO_GROUP, rc, ucredp);
				goto rollback;
			}
			srv->sgs_id = new_id;
		} else {
			new_id = i_ilbd_alloc_srvID(tmp_sg, &srv->sgs_id);
		}

		/*
		 * here we implement the requirement that server IDs start
		 * with a character that is not legal in hostnames - in our
		 * case, a "_" (underscore).
		 */
		(void) snprintf(srv->sgs_srvID,
		    sizeof (srv->sgs_srvID), "%c%s.%d", ILB_SRVID_PREFIX,
		    tmp_sg->isg_name, srv->sgs_id);
		audit_sg_data.ed_serverid = srv->sgs_srvID;

		/*
		 * Before we update the kernel rules by adding the server,
		 * we need to make checks and fail if any of the
		 * following is true:
		 *
		 * o if the server has single port and the servergroup
		 *   is associated to a DSR rule with a port range
		 * o if the server has a port range and the servergroup
		 *   is associated to a DSR rule with a port range and
		 *   the rule's min and max port does not exactly
		 *   match that of the server's.
		 * o if the the server has a port range and the servergroup
		 *   is associated to a NAT/Half-NAT rule with a port range
		 *   and the rule's port range size does not match that
		 *   of the server's.
		 * o if the rule has a fixed hc port, check that this port
		 *   is valid in the server's port specification.
		 */
		rc = i_check_srv2rules(&tmp_sg->isg_rulelist, srv);
		if (rc != ILB_STATUS_OK) {
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ADD_SERVER_TO_GROUP, rc, ucredp);
			goto rollback;
		}

		if ((rc = i_add_srv2sg(tmp_sg, srv, &nsrv)) != ILB_STATUS_OK) {
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ADD_SERVER_TO_GROUP, rc, ucredp);
			goto rollback;
		}

		rc = i_add_srv2krules(&tmp_sg->isg_rulelist, &nsrv->isv_srv,
		    ev_port);
		if (rc != ILB_STATUS_OK) {
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ADD_SERVER_TO_GROUP, rc, ucredp);
			/*
			 * The failure may be due to the serverid being on
			 * hold in kernel for connection draining. But ilbd
			 * has no way of knowing that. So we are freeing up
			 * the serverid, and may run into the risk of
			 * having this failure again, if we choose this
			 * serverid  when processing the next add-server
			 * command for this servergroup, while connection
			 * draining is underway. We assume that the user
			 * will read the man page after he/she encounters
			 * this failure, and learn to not add any server
			 * to the servergroup until connection draining of
			 * all servers in the  servergroup is complete.
			 * XXX Need to revisit this when connection draining
			 * is reworked
			 */
			list_remove(&tmp_sg->isg_srvlist, nsrv);
			i_ilbd_free_srvID(tmp_sg, nsrv->isv_id);
			free(nsrv);
			tmp_sg->isg_srvcount--;
			goto rollback;
		}
		if (ps != NULL) {
			rc = ilbd_scf_add_srv(tmp_sg, nsrv);
			if (rc != ILB_STATUS_OK) {
				/*
				 * The following should not fail since the
				 * server is just added.  Just in case, we
				 * pass in -1 as the event port to avoid
				 * roll back in i_rem_srv_frm_krules() called
				 * by i_delete_srv().
				 */
				ilbd_audit_server_event(&audit_sg_data,
				    ILBD_ADD_SERVER_TO_GROUP, rc, ucredp);
				(void) i_delete_srv(tmp_sg, nsrv, -1);
				break;
			}
		}
	}

	if (rc == ILB_STATUS_OK) {
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_ADD_SERVER_TO_GROUP, rc, ucredp);
		return (rc);
	}

rollback:
	/*
	 * If ilbd is initializing based on the SCF data and something fails,
	 * the only choice is to transition the service to maintanence mode...
	 */
	if (ps == NULL) {
		logerr("%s: failure during initialization -"
		    " entering maintenance mode", __func__);
		(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
		return (rc);
	}

	/*
	 * we need to roll back all servers previous to the one
	 * that just caused the failure
	 */
	for (j = i-1; j >= 0; j--) {
		srv = &sg_info->sg_servers[j];

		/* We should be able to find those servers just added. */
		nsrv = i_find_srv(&tmp_sg->isg_srvlist, srv, MODE_SRVID);
		assert(nsrv != NULL);
		(void) i_delete_srv(tmp_sg, nsrv, -1);
	}
	return (rc);
}

static srch_ind_t
i_match_srvID(ilb_sg_srv_t *sg_srv, ilbd_srv_t *lsrv)
{
	if (strncmp(sg_srv->sgs_srvID, lsrv->isv_srvID,
	    sizeof (sg_srv->sgs_srvID)) == 0) {
		return (stop_found);
	}
	return (cont_search);
}

/*
 * Sanity check on a rule's port specification against all the servers'
 * specification in its associated server group.
 *
 * 1. If the health check's probe port (hcport) is specified.
 *    - if server port range is specified, check if hcport is inside
 *      the range
 *    - if no server port is specified (meaning the port range is the same as
 *      the rule's port range), check if hcport is inside the rule's range.
 *
 * 2. If a server has no port specification, there is no conflict.
 *
 * 3. If the rule's load balance mode is DSR, a server port specification must
 *    be exactly the same as the rule's.
 *
 * 4. In other modes (NAT and half-NAT), the server's port range must be
 *    the same as the rule's, unless it is doing port collapsing (the server's
 *    port range is only 1).
 */
ilb_status_t
ilbd_sg_check_rule_port(ilbd_sg_t *sg, ilb_rule_info_t *rl)
{
	ilbd_srv_t	*srv;
	in_port_t	r_minport, r_maxport;

	/* Don't allow adding a rule to a sg with no server, for now... */
	if (sg->isg_srvcount == 0)
		return (ILB_STATUS_SGEMPTY);

	r_minport = ntohs(rl->rl_minport);
	r_maxport = ntohs(rl->rl_maxport);

	for (srv = list_head(&sg->isg_srvlist); srv != NULL;
	    srv = list_next(&sg->isg_srvlist, srv)) {
		in_port_t srv_minport, srv_maxport;
		int range;

		srv_minport = ntohs(srv->isv_minport);
		srv_maxport = ntohs(srv->isv_maxport);
		range = srv_maxport - srv_minport;

		/*
		 * If the rule has a specific probe port, check if that port is
		 * valid in all the servers' port specification.
		 */
		if (rl->rl_hcpflag == ILB_HCI_PROBE_FIX) {
			in_port_t hcport = ntohs(rl->rl_hcport);

			/* No server port specified. */
			if (srv_minport == 0) {
				if (hcport > r_maxport || hcport < r_minport) {
					return (ILB_STATUS_BADSG);
				}
			} else {
				if (hcport > srv_maxport ||
				    hcport < srv_minport) {
					return (ILB_STATUS_BADSG);
				}
			}
		}

		/*
		 * There is no server port specification, so there cannot be
		 * any conflict.
		 */
		if (srv_minport == 0)
			continue;

		if (rl->rl_topo == ILB_TOPO_DSR) {
			if (r_minport != srv_minport ||
			    r_maxport != srv_maxport) {
				return (ILB_STATUS_BADSG);
			}
		} else {
			if ((range != r_maxport - r_minport) && range != 0)
				return (ILB_STATUS_BADSG);
		}
	}

	return (ILB_STATUS_OK);
}

static srch_ind_t
i_match_srvIP(ilb_sg_srv_t *sg_srv, ilbd_srv_t *lsrv)
{
	if (IN6_ARE_ADDR_EQUAL(&sg_srv->sgs_addr, &lsrv->isv_addr))
		return (stop_found);
	return (cont_search);
}

static ilbd_srv_t *
i_find_srv(list_t *srvlist, ilb_sg_srv_t *sg_srv, int cmpmode)
{
	ilbd_srv_t	*tmp_srv;
	srch_ind_t	srch_res = cont_search;

	for (tmp_srv = list_head(srvlist); tmp_srv != NULL;
	    tmp_srv = list_next(srvlist, tmp_srv)) {
		switch (cmpmode) {
		case MODE_ADDR:
			srch_res = i_match_srvIP(sg_srv, tmp_srv);
			break;
		case MODE_SRVID:
			srch_res = i_match_srvID(sg_srv, tmp_srv);
			break;
		}
		if (srch_res == stop_found)
			break;
	}

	if (srch_res == stop_found)
		return (tmp_srv);
	return (NULL);
}

static ilb_status_t
i_delete_srv(ilbd_sg_t *sg, ilbd_srv_t *srv, int ev_port)
{
	ilb_status_t	rc;

	rc = i_rem_srv_frm_krules(&sg->isg_rulelist, &srv->isv_srv, ev_port);
	if (rc != ILB_STATUS_OK)
		return (rc);
	list_remove(&sg->isg_srvlist, srv);
	i_ilbd_free_srvID(sg, srv->isv_id);
	free(srv);
	sg->isg_srvcount--;
	return (ILB_STATUS_OK);
}

/*
 * some people argue that returning anything here is
 * useless - what *do* you do if you can't remove/destroy
 * something anyway?
 */
ilb_status_t
ilbd_rem_server_from_group(ilb_sg_info_t *sg_info, int ev_port,
    const struct passwd *ps, ucred_t *ucredp)
{
	ilb_status_t	rc = ILB_STATUS_OK;
	ilbd_sg_t	*tmp_sg;
	ilbd_srv_t	*srv, tmp_srv;
	ilb_sg_srv_t    *tsrv;
	audit_sg_event_data_t   audit_sg_data;
	char    addrstr_buf[INET6_ADDRSTRLEN];

	rc = ilbd_check_client_config_auth(ps);
	if (rc != ILB_STATUS_OK) {
		fill_audit_record(sg_info, &audit_sg_data,
		    ILBD_REM_SERVER_FROM_GROUP, rc, ucredp);
		return (rc);
	}

	tmp_sg = i_find_sg_byname(sg_info->sg_name);
	if (tmp_sg == NULL) {
		logdebug("%s: server group %s\n does not exist", __func__,
		    sg_info->sg_name);
		fill_audit_record(sg_info, &audit_sg_data,
		    ILBD_REM_SERVER_FROM_GROUP, ILB_STATUS_SGUNAVAIL, ucredp);
		return (ILB_STATUS_SGUNAVAIL);
	}
	tsrv = &sg_info->sg_servers[0];
	audit_sg_data.ed_serverid = tsrv->sgs_srvID;
	audit_sg_data.ed_sgroup = sg_info->sg_name;
	audit_sg_data.ed_server_address = NULL;

	assert(sg_info->sg_srvcount == 1);
	srv = i_find_srv(&tmp_sg->isg_srvlist, &sg_info->sg_servers[0],
	    MODE_SRVID);
	if (srv == NULL) {
		logdebug("%s: cannot find server in server group %s", __func__,
		    sg_info->sg_name);
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_REM_SERVER_FROM_GROUP, ILB_STATUS_SRVUNAVAIL, ucredp);
		return (ILB_STATUS_SRVUNAVAIL);
	}
	tsrv = &srv->isv_srv;
	ilbd_addr2str(&tsrv->sgs_addr, addrstr_buf,
	    sizeof (addrstr_buf));
	audit_sg_data.ed_server_address = addrstr_buf;
	/*
	 * i_delete_srv frees srv, therefore we need to save
	 * this information for ilbd_scf_del_srv
	 */
	(void) memcpy(&tmp_srv, srv, sizeof (tmp_srv));

	rc = i_delete_srv(tmp_sg, srv, ev_port);
	if (rc != ILB_STATUS_OK) {
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_REM_SERVER_FROM_GROUP, rc, ucredp);
		return (rc);
	}

	if (ps != NULL) {
		if ((rc = ilbd_scf_del_srv(tmp_sg, &tmp_srv)) !=
		    ILB_STATUS_OK) {
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_REM_SERVER_FROM_GROUP, rc, ucredp);
			logerr("%s: SCF update failed - entering maintenance"
			    " mode", __func__);
			(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
		}
	}
	ilbd_audit_server_event(&audit_sg_data,
	    ILBD_REM_SERVER_FROM_GROUP, rc, ucredp);
	return (rc);
}

ilb_status_t
ilbd_retrieve_names(ilbd_cmd_t cmd, uint32_t *rbuf, size_t *rbufsz)
{
	ilb_status_t	rc = ILB_STATUS_OK;
	ilbd_namelist_t	*nlist;
	size_t		tmp_rbufsz;

	tmp_rbufsz = *rbufsz;
	/* Set up the reply buffer.  rbufsz will be set to the new size. */
	ilbd_reply_ok(rbuf, rbufsz);

	/* Calculate how much space is left for holding name info. */
	*rbufsz += sizeof (ilbd_namelist_t);
	tmp_rbufsz -= *rbufsz;

	nlist = (ilbd_namelist_t *)&((ilb_comm_t *)rbuf)->ic_data;
	nlist->ilbl_count = 0;

	switch (cmd) {
	case ILBD_RETRIEVE_SG_NAMES: {
		ilbd_sg_t	*sg;

		for (sg = list_head(&ilbd_sg_hlist);
		    sg != NULL && tmp_rbufsz >= sizeof (ilbd_name_t);
		    sg = list_next(&ilbd_sg_hlist, sg),
		    tmp_rbufsz -= sizeof (ilbd_name_t)) {
			(void) strlcpy(nlist->ilbl_name[nlist->ilbl_count++],
			    sg->isg_name, sizeof (ilbd_name_t));
		}
		break;
	}
	case ILBD_RETRIEVE_RULE_NAMES: {
		ilbd_rule_t	*irl;
		extern list_t	ilbd_rule_hlist;

		for (irl = list_head(&ilbd_rule_hlist);
		    irl != NULL && tmp_rbufsz >= sizeof (ilbd_name_t);
		    irl = list_next(&ilbd_rule_hlist, irl),
		    tmp_rbufsz -= sizeof (ilbd_name_t)) {
			(void) strlcpy(nlist->ilbl_name[nlist->ilbl_count++],
			    irl->irl_name, sizeof (ilbd_name_t));
		}
		break;
	}
	case ILBD_RETRIEVE_HC_NAMES: {
		extern list_t	ilbd_hc_list;
		ilbd_hc_t	*hc;

		for (hc = list_head(&ilbd_hc_list);
		    hc != NULL && tmp_rbufsz >= sizeof (ilbd_name_t);
		    hc = list_next(&ilbd_hc_list, hc)) {
			(void) strlcpy(nlist->ilbl_name[nlist->ilbl_count++],
			    hc->ihc_name, sizeof (ilbd_name_t));
		}
		break;
	}
	default:
		logdebug("ilbd_retrieve_names: unknown command");
		return (ILB_STATUS_INVAL_CMD);
	}

	*rbufsz += nlist->ilbl_count * sizeof (ilbd_name_t);
	return (rc);
}

ilb_status_t
ilbd_retrieve_sg_hosts(const char *sg_name, uint32_t *rbuf, size_t *rbufsz)
{
	ilbd_sg_t	*dsg;
	ilbd_srv_t	*dsrv;
	list_t		*srvlist;
	ilb_sg_info_t	*sg_info;
	size_t		tmp_rbufsz;

	dsg = i_find_sg_byname(sg_name);
	if (dsg == NULL) {
		logdebug("ilbd_retrieve_sg_hosts: server group"
		    " %s not found", sg_name);
		return (ILB_STATUS_SGUNAVAIL);
	}

	srvlist = &dsg->isg_srvlist;
	dsrv = list_head(srvlist);

	tmp_rbufsz = *rbufsz;
	ilbd_reply_ok(rbuf, rbufsz);

	/* Calculate the size to hold all the hosts info. */
	*rbufsz += sizeof (ilb_sg_info_t);
	tmp_rbufsz -= *rbufsz;

	sg_info = (ilb_sg_info_t *)&((ilb_comm_t *)rbuf)->ic_data;
	(void) strlcpy(sg_info->sg_name, sg_name, sizeof (sg_info->sg_name));
	sg_info->sg_srvcount = 0;

	while (dsrv != NULL && tmp_rbufsz >= sizeof (ilb_sg_srv_t)) {
		sg_info->sg_servers[sg_info->sg_srvcount++] = dsrv->isv_srv;
		dsrv = list_next(srvlist, dsrv);
		tmp_rbufsz -= sizeof (ilb_sg_srv_t);
	}
	*rbufsz += sg_info->sg_srvcount * sizeof (ilb_sg_srv_t);
	return (ILB_STATUS_OK);
}

/*
 * this mapping function works on the assumption that HC only is
 * active when a server is enabled.
 */
static ilb_cmd_t
i_srvcmd_d2k(ilbd_srv_status_ind_t dcmd)
{
	ilb_cmd_t	cmd;

	switch (dcmd) {
	case stat_enable_server:
	case stat_declare_srv_alive:
		cmd = ILB_ENABLE_SERVERS;
		break;
	case stat_disable_server:
	case stat_declare_srv_dead:
		cmd = ILB_DISABLE_SERVERS;
		break;
	}

	return (cmd);
}

ilb_status_t
ilbd_k_Xable_server(const struct in6_addr *addr, const char *rlname,
    ilbd_srv_status_ind_t cmd)
{
	ilb_status_t		rc;
	ilb_servers_cmd_t	kcmd;
	int			e;

	kcmd.cmd = i_srvcmd_d2k(cmd);
	(void) strlcpy(kcmd.name, rlname, sizeof (kcmd.name));
	kcmd.num_servers = 1;

	kcmd.servers[0].addr = *addr;
	kcmd.servers[0].err = 0;

	rc = do_ioctl(&kcmd, 0);
	if (rc != ILB_STATUS_OK)
		return (rc);

	if ((e = kcmd.servers[0].err) != 0) {
		logdebug("ilbd_k_Xable_server: error %s occurred",
		    strerror(e));
		return (ilb_map_errno2ilbstat(e));
	}

	return (rc);
}

#define	IS_SRV_ENABLED(s)	ILB_IS_SRV_ENABLED((s)->sgs_flags)
#define	IS_SRV_DISABLED(s)	(!(IS_SRV_ENABLED(s)))

#define	SET_SRV_ENABLED(s)	ILB_SET_ENABLED((s)->sgs_flags)
#define	SET_SRV_DISABLED(s)	ILB_SET_DISABLED((s)->sgs_flags)

static ilb_status_t
ilbd_Xable_server(ilb_sg_info_t *sg, const struct passwd *ps,
    ilbd_srv_status_ind_t cmd, ucred_t *ucredp)
{
	ilb_status_t	rc = ILB_STATUS_OK;
	ilbd_sg_t	*isg;
	ilbd_srv_t	*tmp_srv;
	ilb_sg_srv_t 	*srv;
	ilbd_rule_t	*irl;
	char		*dot;
	int		scf_name_len = ILBD_MAX_NAME_LEN;
	int		scf_val_len = ILBD_MAX_VALUE_LEN;
	char		prop_name[scf_name_len];
	ilb_ip_addr_t	ipaddr;
	void		*addrptr;
	char		ipstr[INET6_ADDRSTRLEN], valstr[scf_val_len];
	int		ipver, vallen;
	char		sgname[ILB_NAMESZ];
	uint32_t	nflags;
	ilbd_srv_status_ind_t u_cmd;
	audit_sg_event_data_t   audit_sg_data;
	char    addrstr_buf[INET6_ADDRSTRLEN];

	(void) memset(&audit_sg_data, 0, sizeof (audit_sg_data));

	/* we currently only implement a "list" of one */
	assert(sg->sg_srvcount == 1);

	srv = &sg->sg_servers[0];
	audit_sg_data.ed_serverid = srv->sgs_srvID;
	audit_sg_data.ed_server_address = NULL;

	rc = ilbd_check_client_enable_auth(ps);
	if (rc != ILB_STATUS_OK) {
		ilbd_audit_server_event(&audit_sg_data,
		    ILBD_ENABLE_SERVER, rc, ucredp);
		return (rc);
	}

	if (srv->sgs_srvID[0] != ILB_SRVID_PREFIX) {
		switch (cmd) {
		case stat_disable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_DISABLE_SERVER,
			    ILB_STATUS_EINVAL, ucredp);
			break;
		case stat_enable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ENABLE_SERVER,
			    ILB_STATUS_EINVAL, ucredp);
			break;
		}
		return (ILB_STATUS_EINVAL);
	}

	/*
	 * the following asserts that serverIDs are constructed
	 * along the pattern "_"<SG name>"."<number>
	 * so we look for the final "." to recreate the SG name.
	 */
	(void) strlcpy(sgname, srv->sgs_srvID + 1, sizeof (sgname));
	dot = strrchr(sgname, (int)'.');
	if (dot == NULL) {
		switch (cmd) {
		case stat_disable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_DISABLE_SERVER,
			    ILB_STATUS_EINVAL, ucredp);
			break;
		case stat_enable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ENABLE_SERVER,
			    ILB_STATUS_EINVAL, ucredp);
			break;
		}
		return (ILB_STATUS_EINVAL);
	}

	/* make the non-sg_name part "invisible" */
	*dot = '\0';
	isg = i_find_sg_byname(sgname);
	if (isg == NULL) {
		switch (cmd) {
		case stat_disable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_DISABLE_SERVER,
			    ILB_STATUS_ENOENT, ucredp);
			break;
		case stat_enable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ENABLE_SERVER,
			    ILB_STATUS_ENOENT, ucredp);
			break;
		}
		return (ILB_STATUS_ENOENT);
	}

	tmp_srv = i_find_srv(&isg->isg_srvlist, srv, MODE_SRVID);
	if (tmp_srv == NULL) {
		switch (cmd) {
		case stat_disable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_DISABLE_SERVER,
			    ILB_STATUS_ENOENT, ucredp);
			break;
		case stat_enable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ENABLE_SERVER,
			    ILB_STATUS_ENOENT, ucredp);
			break;
		}
		return (ILB_STATUS_ENOENT);
	}

	/*
	 * if server's servergroup is not associated with
	 * a rule, do not enable it.
	 */
	irl = list_head(&isg->isg_rulelist);
	if (irl == NULL) {
		switch (cmd) {
		case stat_disable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_DISABLE_SERVER,
			    ILB_STATUS_INVAL_ENBSRVR, ucredp);
			break;
		case stat_enable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ENABLE_SERVER,
			    ILB_STATUS_INVAL_ENBSRVR, ucredp);
			break;
		}
		return (ILB_STATUS_INVAL_ENBSRVR);
	}
	/* Fill in the server IP address for audit record */
	ilbd_addr2str(&tmp_srv->isv_addr, addrstr_buf,
	    sizeof (addrstr_buf));
	audit_sg_data.ed_server_address = addrstr_buf;

	/*
	 * We have found the server in memory, perform the following
	 * tasks.
	 *
	 * 1. For every rule associated with this SG,
	 *    - tell the kernel
	 *    - tell the hc
	 * 2. Update our internal state and persistent configuration
	 *    if the new state is not the same as the old one.
	 */
	/* 1. */
	for (; irl != NULL; irl = list_next(&isg->isg_rulelist, irl)) {
		rc = ilbd_k_Xable_server(&tmp_srv->isv_addr,
		    irl->irl_name, cmd);
		if (rc != ILB_STATUS_OK) {
			switch (cmd) {
			case stat_disable_server:
				ilbd_audit_server_event(&audit_sg_data,
				    ILBD_DISABLE_SERVER, rc, ucredp);
				break;
			case stat_enable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ENABLE_SERVER, rc, ucredp);
			break;
			}
			goto rollback_rules;
		}
		if (!RULE_HAS_HC(irl))
			continue;

		if (cmd == stat_disable_server) {
			rc = ilbd_hc_disable_server(irl,
			    &tmp_srv->isv_srv);
		} else {
			assert(cmd == stat_enable_server);
			rc = ilbd_hc_enable_server(irl,
			    &tmp_srv->isv_srv);
		}
		if (rc != ILB_STATUS_OK) {
			logdebug("ilbd_Xable_server: cannot toggle srv "
			    "timer, rc =%d, srv =%s%d\n", rc,
			    tmp_srv->isv_srvID,
			    tmp_srv->isv_id);
		}
	}

	/* 2. */
	if ((cmd == stat_disable_server &&
	    IS_SRV_DISABLED(&tmp_srv->isv_srv)) ||
	    (cmd == stat_enable_server &&
	    IS_SRV_ENABLED(&tmp_srv->isv_srv))) {
		switch (cmd) {
		case stat_disable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_DISABLE_SERVER, ILB_STATUS_OK, ucredp);
			break;
		case stat_enable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ENABLE_SERVER, ILB_STATUS_OK, ucredp);
			break;
		}
		return (ILB_STATUS_OK);
	}

	nflags = tmp_srv->isv_flags;
	if (cmd == stat_enable_server)
		ILB_SET_ENABLED(nflags);
	else
		ILB_SET_DISABLED(nflags);

	IP_COPY_IMPL_2_CLI(&tmp_srv->isv_addr, &ipaddr);
	ipver = GET_AF(&tmp_srv->isv_addr);
	vallen = (ipver == AF_INET) ? INET_ADDRSTRLEN :
	    INET6_ADDRSTRLEN;
	addrptr = (ipver == AF_INET) ? (void *)&ipaddr.ia_v4 :
	    (void *)&ipaddr.ia_v6;
	if (inet_ntop(ipver, addrptr, ipstr, vallen) == NULL) {
		logerr("ilbd_Xable_server: failed transfer ip addr to"
		    " str");
		if (errno == ENOSPC)
			rc = ILB_STATUS_ENOMEM;
		else
			rc = ILB_STATUS_GENERIC;
		switch (cmd) {
		case stat_disable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_DISABLE_SERVER, rc, ucredp);
			break;
		case stat_enable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ENABLE_SERVER, rc, ucredp);
			break;
		}
		goto rollback_rules;
	}

	(void) snprintf(valstr, sizeof (valstr), "%s;%d;%d-%d;%d",
	    ipstr, ipver,
	    ntohs(tmp_srv->isv_minport),
	    ntohs(tmp_srv->isv_maxport), nflags);
	(void) snprintf(prop_name, sizeof (prop_name), "server%d",
	    tmp_srv->isv_id);

	switch (cmd) {
	case stat_disable_server:
		rc = i_ilbd_save_sg(isg, ILBD_SCF_ENABLE_DISABLE,
		    prop_name, valstr);
		if (rc == ILB_STATUS_OK)
			SET_SRV_DISABLED(&tmp_srv->isv_srv);
		break;
	case stat_enable_server:
		rc = i_ilbd_save_sg(isg, ILBD_SCF_ENABLE_DISABLE,
		    prop_name, valstr);
		if (rc == ILB_STATUS_OK)
			SET_SRV_ENABLED(&tmp_srv->isv_srv);
		break;
	}
	if (rc == ILB_STATUS_OK) {
		switch (cmd) {
		case stat_disable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_DISABLE_SERVER, ILB_STATUS_OK, ucredp);
			break;
		case stat_enable_server:
			ilbd_audit_server_event(&audit_sg_data,
			    ILBD_ENABLE_SERVER, ILB_STATUS_OK, ucredp);
			break;
		}
		return (ILB_STATUS_OK);
	}

rollback_rules:
	if (cmd == stat_disable_server)
		u_cmd = stat_enable_server;
	else
		u_cmd = stat_disable_server;

	if (irl == NULL)
		irl = list_tail(&isg->isg_rulelist);
	else
		irl = list_prev(&isg->isg_rulelist, irl);

	for (; irl != NULL; irl = list_prev(&isg->isg_rulelist, irl)) {
		(void) ilbd_k_Xable_server(&tmp_srv->isv_addr,
		    irl->irl_name, u_cmd);
		if (!RULE_HAS_HC(irl))
			continue;

		if (u_cmd == stat_disable_server)
			(void) ilbd_hc_disable_server(irl, &tmp_srv->isv_srv);
		else
			(void) ilbd_hc_enable_server(irl, &tmp_srv->isv_srv);
	}

	return (rc);
}

ilb_status_t
ilbd_disable_server(ilb_sg_info_t *sg, const struct passwd *ps,
    ucred_t *ucredp)
{
	return (ilbd_Xable_server(sg, ps, stat_disable_server, ucredp));
}

ilb_status_t
ilbd_enable_server(ilb_sg_info_t *sg, const struct passwd *ps,
    ucred_t *ucredp)
{
	return (ilbd_Xable_server(sg, ps, stat_enable_server, ucredp));
}

/*
 * fill in the srvID for the given IP address in the 0th server
 */
ilb_status_t
ilbd_address_to_srvID(ilb_sg_info_t *sg, uint32_t *rbuf, size_t *rbufsz)
{
	ilbd_srv_t 	*tmp_srv;
	ilb_sg_srv_t 	*tsrv;
	ilbd_sg_t	*tmp_sg;

	ilbd_reply_ok(rbuf, rbufsz);
	tsrv = (ilb_sg_srv_t *)&((ilb_comm_t *)rbuf)->ic_data;
	*rbufsz += sizeof (ilb_sg_srv_t);

	tmp_sg = i_find_sg_byname(sg->sg_name);
	if (tmp_sg == NULL)
		return (ILB_STATUS_SGUNAVAIL);
	tsrv->sgs_addr = sg->sg_servers[0].sgs_addr;

	tmp_srv = i_find_srv(&tmp_sg->isg_srvlist, tsrv, MODE_ADDR);
	if (tmp_srv == NULL)
		return (ILB_STATUS_ENOENT);

	(void) strlcpy(tsrv->sgs_srvID, tmp_srv->isv_srvID,
	    sizeof (tsrv->sgs_srvID));

	return (ILB_STATUS_OK);
}

/*
 * fill in the address for the given serverID in the 0th server
 */
ilb_status_t
ilbd_srvID_to_address(ilb_sg_info_t *sg, uint32_t *rbuf, size_t *rbufsz)
{
	ilbd_srv_t 	*tmp_srv;
	ilb_sg_srv_t 	*tsrv;
	ilbd_sg_t	*tmp_sg;

	ilbd_reply_ok(rbuf, rbufsz);
	tsrv = (ilb_sg_srv_t *)&((ilb_comm_t *)rbuf)->ic_data;

	tmp_sg = i_find_sg_byname(sg->sg_name);
	if (tmp_sg == NULL)
		return (ILB_STATUS_SGUNAVAIL);
	(void) strlcpy(tsrv->sgs_srvID, sg->sg_servers[0].sgs_srvID,
	    sizeof (tsrv->sgs_srvID));

	tmp_srv = i_find_srv(&tmp_sg->isg_srvlist, tsrv, MODE_SRVID);
	if (tmp_srv == NULL)
		return (ILB_STATUS_ENOENT);

	tsrv->sgs_addr = tmp_srv->isv_addr;
	*rbufsz += sizeof (ilb_sg_srv_t);

	return (ILB_STATUS_OK);
}

void
ilbd_addr2str(struct in6_addr *ipaddr, char *addrstr_buf, size_t sz)
{
	ilb_ip_addr_t	ilb_ip;

	IP_COPY_IMPL_2_CLI(ipaddr, &ilb_ip);
	addr2str(ilb_ip, addrstr_buf, sz);
}

/* Convert ip address to a address string */
void
addr2str(ilb_ip_addr_t ip, char *buf, size_t sz)
{

	switch (ip.ia_af) {
	case AF_INET:
		if ((uint32_t *)&(ip).ia_v4 == 0)
			buf[0] = '\0';
		else
			(void) inet_ntop(AF_INET, (void *)&(ip).ia_v4, buf, sz);
		break;
	case AF_INET6:
		if (IN6_IS_ADDR_UNSPECIFIED(&(ip).ia_v6)) {
			buf[0] = '\0';
			break;
		}
		(void) inet_ntop(ip.ia_af, (void *)&(ip).ia_v6, buf, sz);
		break;
	default: buf[0] = '\0';
	}
}

/*
 * Map ilb_status errors to similar errno values from errno.h or
 * adt_event.h to be used for audit record
 */
int
ilberror2auditerror(ilb_status_t rc)
{
	int audit_error;

	switch (rc) {
	case ILB_STATUS_CFGAUTH:
		audit_error = ADT_FAIL_VALUE_AUTH;
		break;
	case ILB_STATUS_ENOMEM:
		audit_error = ENOMEM;
		break;
	case ILB_STATUS_ENOENT:
	case ILB_STATUS_ENOHCINFO:
	case ILB_STATUS_INVAL_HCTESTTYPE:
	case ILB_STATUS_INVAL_CMD:
	case ILB_STATUS_DUP_RULE:
	case ILB_STATUS_ENORULE:
	case ILB_STATUS_SGUNAVAIL:
		audit_error = ENOENT;
		break;
	case ILB_STATUS_EINVAL:
	case ILB_STATUS_MISMATCHSG:
	case ILB_STATUS_MISMATCHH:
	case ILB_STATUS_BADSG:
	case ILB_STATUS_INVAL_SRVR:
	case ILB_STATUS_INVAL_ENBSRVR:
	case ILB_STATUS_BADPORT:
		audit_error = EINVAL;
		break;
	case ILB_STATUS_EEXIST:
	case ILB_STATUS_SGEXISTS:
		audit_error = EEXIST;
		break;
	case ILB_STATUS_EWOULDBLOCK:
		audit_error = EWOULDBLOCK;
		break;
	case ILB_STATUS_INPROGRESS:
		audit_error = EINPROGRESS;
		break;
	case ILB_STATUS_INTERNAL:
	case ILB_STATUS_CALLBACK:
	case ILB_STATUS_PERMIT:
	case ILB_STATUS_RULE_NO_HC:
		audit_error = ADT_FAIL_VALUE_PROGRAM;
		break;
	case ILB_STATUS_SOCKET:
		audit_error = ENOTSOCK;
		break;
	case ILB_STATUS_READ:
	case ILB_STATUS_WRITE:
		audit_error = ENOTCONN;
		break;
	case ILB_STATUS_SGINUSE:
		audit_error = EADDRINUSE;
		break;
	case ILB_STATUS_SEND:
		audit_error = ECOMM;
		break;
	case ILB_STATUS_SGFULL:
		audit_error = EOVERFLOW;
		break;
	case ILB_STATUS_NAMETOOLONG:
		audit_error = ENAMETOOLONG;
		break;
	case ILB_STATUS_SRVUNAVAIL:
		audit_error = EHOSTUNREACH;
		break;
	default:
		audit_error = ADT_FAIL_VALUE_UNKNOWN;
		break;
	}
	return (audit_error);
}