view usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_rules.c @ 13779:bf40125f4b37

3063 many instances of unlike enum comparison Reviewed by: Robert Mustacchi <rm@joyent.com> Approved by: Garrett D'Amore <garrett@damore.org>
author Richard Lowe <richlowe@richlowe.net>
date Sun, 08 Jul 2012 03:19:56 +0100
parents bb6d135b32d1
children
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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

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

/* until we all use AF_* macros ... */
#define	AF_2_IPPROTO(_af)	(_af == AF_INET)?IPPROTO_IP:IPPROTO_IPV6
#define	IPPROTO_2_AF(_i)	(_i == IPPROTO_IP)?AF_INET:AF_INET6

#define	PROTOCOL_LEN	16				/* protocol type */
#define	ADDR_LEN	(2 * INET6_ADDRSTRLEN + 1)	/* prxy src range */
#define	PORT_LEN	6			/* hcport:1-65535 or "ANY" */

static ilb_status_t ilbd_disable_one_rule(ilbd_rule_t *, boolean_t);
static uint32_t i_flags_d2k(int);

#define	ILB_SGSRV_2_KSRV(s, k)			\
	(k)->addr  = (s)->sgs_addr;		\
	(k)->min_port = (s)->sgs_minport;	\
	(k)->max_port = (s)->sgs_maxport;	\
	(k)->flags = i_flags_d2k((s)->sgs_flags);	\
	(k)->err = 0;				\
	(void) strlcpy((k)->name, (s)->sgs_srvID, sizeof ((k)->name))

list_t		ilbd_rule_hlist;

static ilb_algo_t
algo_impl2lib(ilb_algo_impl_t a)
{
	switch (a) {
	case ILB_ALG_IMPL_ROUNDROBIN:
		return (ILB_ALG_ROUNDROBIN);
	case ILB_ALG_IMPL_HASH_IP:
		return (ILB_ALG_HASH_IP);
	case ILB_ALG_IMPL_HASH_IP_SPORT:
		return (ILB_ALG_HASH_IP_SPORT);
	case ILB_ALG_IMPL_HASH_IP_VIP:
		return (ILB_ALG_HASH_IP_VIP);
	}
	return (0);
}

static ilb_topo_t
topo_impl2lib(ilb_topo_impl_t t)
{
	switch (t) {
	case ILB_TOPO_IMPL_DSR:
		return (ILB_TOPO_DSR);
	case ILB_TOPO_IMPL_NAT:
		return (ILB_TOPO_NAT);
	case ILB_TOPO_IMPL_HALF_NAT:
		return (ILB_TOPO_HALF_NAT);
	}
	return (0);
}

ilb_algo_impl_t
algo_lib2impl(ilb_algo_t a)
{
	switch (a) {
	case ILB_ALG_ROUNDROBIN:
		return (ILB_ALG_IMPL_ROUNDROBIN);
	case ILB_ALG_HASH_IP:
		return (ILB_ALG_IMPL_HASH_IP);
	case ILB_ALG_HASH_IP_SPORT:
		return (ILB_ALG_IMPL_HASH_IP_SPORT);
	case ILB_ALG_HASH_IP_VIP:
		return (ILB_ALG_IMPL_HASH_IP_VIP);
	}
	return (0);
}

ilb_topo_impl_t
topo_lib2impl(ilb_topo_t t)
{
	switch (t) {
	case ILB_TOPO_DSR:
		return (ILB_TOPO_IMPL_DSR);
	case ILB_TOPO_NAT:
		return (ILB_TOPO_IMPL_NAT);
	case ILB_TOPO_HALF_NAT:
		return (ILB_TOPO_IMPL_HALF_NAT);
	}
	return (0);
}

/*
 * Walk the list of rules and check if its safe to add the
 * the server to the rule (this is a list of rules hanging
 * off of a server group)
 */
ilb_status_t
i_check_srv2rules(list_t *rlist, ilb_sg_srv_t *srv)
{
	ilb_status_t	rc = ILB_STATUS_OK;
	ilbd_rule_t	*rl;
	int		server_portrange, rule_portrange;
	int		srv_minport, srv_maxport;
	int		r_minport, r_maxport;

	if (srv == NULL)
		return (ILB_STATUS_OK);

	srv_minport = ntohs(srv->sgs_minport);
	srv_maxport = ntohs(srv->sgs_maxport);

	for (rl = list_head(rlist); rl != NULL; rl = list_next(rlist, rl)) {
		r_minport = ntohs(rl->irl_minport);
		r_maxport = ntohs(rl->irl_maxport);

		if ((srv_minport != 0) && (srv_minport == srv_maxport)) {
			/* server has single port */
			if (rl->irl_topo == ILB_TOPO_DSR) {
				/*
				 * either we have a DSR rule with a port
				 * range, or both server and rule
				 * have single ports but their values
				 * don't match - this is incompatible
				 */
				if (r_maxport > r_minport) {
					rc = ILB_STATUS_INVAL_SRVR;
					break;
				} else if (srv_minport != r_minport) {
					rc = ILB_STATUS_BADPORT;
					break;
				}
			}
			if (rl->irl_hcpflag == ILB_HCI_PROBE_FIX &&
			    rl->irl_hcport != srv_minport) {
				rc = ILB_STATUS_BADPORT;
				break;
			}
		} else if (srv_maxport > srv_minport) {
			/* server has a port range */
			if ((rl->irl_topo == ILB_TOPO_DSR) &&
			    (r_maxport > r_minport)) {
				if ((r_minport != srv_minport) ||
				    (r_maxport != srv_maxport)) {
					/*
					 * we have a DSR rule with a port range
					 * and its min and max port values
					 * does not meet that of server's
					 * - this is incompatible
					 */
					rc = ILB_STATUS_BADPORT;
					break;
				}
			} else if ((rl->irl_topo == ILB_TOPO_DSR) &&
			    (r_maxport == r_minport)) {
					/*
					 * we have a DSR rule with a single
					 * port and a server with a port range
					 * - this is incompatible
					 */
					rc = ILB_STATUS_INVAL_SRVR;
					break;
			} else if (((rl->irl_topo == ILB_TOPO_NAT) ||
			    (rl->irl_topo == ILB_TOPO_HALF_NAT)) &&
			    (r_maxport > r_minport)) {
				server_portrange = srv_maxport - srv_minport;
				rule_portrange = r_maxport - r_minport;
				if (rule_portrange != server_portrange) {
					/*
					 * we have a NAT/Half-NAT rule with
					 * a port range and server with a port
					 * range and there is a mismatch in the
					 * sizes of the port ranges - this is
					 * incompatible
					 */
					rc = ILB_STATUS_INVAL_SRVR;
					break;
				}
			}
			if (rl->irl_hcpflag == ILB_HCI_PROBE_FIX &&
			    (rl->irl_hcport > srv_maxport ||
			    rl->irl_hcport < srv_minport)) {
				rc = ILB_STATUS_BADPORT;
				break;
			}
		}
	}

	return (rc);
}

void
i_setup_rule_hlist(void)
{
	list_create(&ilbd_rule_hlist, sizeof (ilbd_rule_t),
	    offsetof(ilbd_rule_t, irl_link));
}

ilb_status_t
i_ilbd_save_rule(ilbd_rule_t *irl, ilbd_scf_cmd_t scf_cmd)
{
	boolean_t enable = irl->irl_flags & ILB_FLAGS_RULE_ENABLED;

	switch (scf_cmd) {
	case ILBD_SCF_CREATE:
		return (ilbd_create_pg(ILBD_SCF_RULE, (void *)irl));
	case ILBD_SCF_DESTROY:
		return (ilbd_destroy_pg(ILBD_SCF_RULE, irl->irl_name));
	case ILBD_SCF_ENABLE_DISABLE:
		return (ilbd_change_prop(ILBD_SCF_RULE, irl->irl_name,
		    "status", &enable));
	default:
		logdebug("i_ilbd_save_rule: invalid scf cmd %d", scf_cmd);
		return (ILB_STATUS_INVAL_CMD);
	}
}

/*
 * allocate a new daemon-specific rule from the "template" passed
 * in in *r
 */
static ilbd_rule_t *
i_alloc_ilbd_rule(ilb_rule_info_t *r)
{
	ilbd_rule_t	*rl;

	rl = calloc(sizeof (*rl), 1);
	if (rl != NULL && r != NULL)
		bcopy(r, &rl->irl_info, sizeof (*r));

	return (rl);
}

static ilbd_rule_t *
i_find_rule_byname(const char *name)
{
	ilbd_rule_t	*rl;

	/* find position of rule in list */
	rl = list_head(&ilbd_rule_hlist);
	while (rl != NULL &&
	    strncmp(rl->irl_name, name, sizeof (rl->irl_name)) != 0) {
		rl = list_next(&ilbd_rule_hlist, rl);
	}

	return (rl);
}

/*
 * get exactly one rule (named in rl->irl_name) data from kernel
 */
static ilb_status_t
ilb_get_krule(ilb_rule_info_t *rl)
{
	ilb_status_t	rc;
	ilb_rule_cmd_t	kcmd;

	kcmd.cmd = ILB_LIST_RULE;
	(void) strlcpy(kcmd.name, rl->rl_name, sizeof (kcmd.name));
	kcmd.flags = 0;

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

	rl->rl_flags = kcmd.flags;
	rl->rl_ipversion = IPPROTO_2_AF(kcmd.ip_ver);
	rl->rl_vip = kcmd.vip;
	rl->rl_proto = kcmd.proto;
	rl->rl_minport = kcmd.min_port;
	rl->rl_maxport = kcmd.max_port;
	rl->rl_algo = algo_impl2lib(kcmd.algo);
	rl->rl_topo = topo_impl2lib(kcmd.topo);
	rl->rl_stickymask = kcmd.sticky_mask;
	rl->rl_nat_src_start = kcmd.nat_src_start;
	rl->rl_nat_src_end = kcmd.nat_src_end;
	(void) strlcpy(rl->rl_name, kcmd.name, sizeof (rl->rl_name));
	rl->rl_conndrain = kcmd.conn_drain_timeout;
	rl->rl_nat_timeout = kcmd.nat_expiry;
	rl->rl_sticky_timeout = kcmd.sticky_expiry;

	return (ILB_STATUS_OK);
}

ilb_status_t
ilbd_retrieve_rule(ilbd_name_t rl_name, uint32_t *rbuf, size_t *rbufsz)
{
	ilbd_rule_t	*irl = NULL;
	ilb_status_t	rc;
	ilb_rule_info_t	*rinfo;

	irl = i_find_rule_byname(rl_name);
	if (irl == NULL)
		return (ILB_STATUS_ENOENT);

	ilbd_reply_ok(rbuf, rbufsz);
	rinfo = (ilb_rule_info_t *)&((ilb_comm_t *)rbuf)->ic_data;
	bcopy(&irl->irl_info, rinfo, sizeof (*rinfo));

	/*
	 * Check if the various timeout values are 0.  If one is, get the
	 * default values from kernel.
	 */
	if (rinfo->rl_conndrain == 0 || rinfo->rl_nat_timeout == 0 ||
	    rinfo->rl_sticky_timeout == 0) {
		ilb_rule_info_t tmp_info;

		(void) strcpy(tmp_info.rl_name, rinfo->rl_name);
		rc = ilb_get_krule(&tmp_info);
		if (rc != ILB_STATUS_OK)
			return (rc);
		if (rinfo->rl_conndrain == 0)
			rinfo->rl_conndrain = tmp_info.rl_conndrain;
		if ((rinfo->rl_topo == ILB_TOPO_NAT ||
		    rinfo->rl_topo == ILB_TOPO_HALF_NAT) &&
		    rinfo->rl_nat_timeout == 0) {
			rinfo->rl_nat_timeout = tmp_info.rl_nat_timeout;
		}
		if ((rinfo->rl_flags & ILB_FLAGS_RULE_STICKY) &&
		    rinfo->rl_sticky_timeout == 0) {
			rinfo->rl_sticky_timeout = tmp_info.rl_sticky_timeout;
		}
	}
	*rbufsz += sizeof (ilb_rule_info_t);

	return (ILB_STATUS_OK);
}

static ilb_status_t
ilbd_destroy_one_rule(ilbd_rule_t *irl)
{
	ilb_status_t	rc;
	ilb_name_cmd_t	kcmd;

	/*
	 * as far as talking to the kernel is concerned, "all rules"
	 * is handled in one go somewhere else, so we only
	 * tell the kernel about single rules here.
	 */
	if ((irl->irl_flags & ILB_FLAGS_RULE_ALLRULES) == 0) {
		kcmd.cmd = ILB_DESTROY_RULE;
		(void) strlcpy(kcmd.name, irl->irl_name, sizeof (kcmd.name));
		kcmd.flags = 0;

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

	}
	list_remove(&irl->irl_sg->isg_rulelist, irl);
	list_remove(&ilbd_rule_hlist, irl);

	/*
	 * When dissociating a rule, only two errors can happen.  The hc
	 * name is incorrect or the rule is not associated with the hc
	 * object.  Both should not happen....  The check is for debugging
	 * purpose.
	 */
	if (RULE_HAS_HC(irl) && (rc = ilbd_hc_dissociate_rule(irl)) !=
	    ILB_STATUS_OK) {
		logerr("ilbd_destroy_one_rule: cannot "
		    "dissociate %s from hc object %s: %d",
		    irl->irl_name, irl->irl_hcname, rc);
	}

	rc = i_ilbd_save_rule(irl, ILBD_SCF_DESTROY);
	if (rc != ILB_STATUS_OK)
		logdebug("ilbd_destroy_rule: save rule failed");

	free(irl);
	return (rc);
}

/*
 * the following two functions are the other's opposite, and can
 * call into each other for roll back purposes in case of error.
 * To avoid endless recursion, the 'is_rollback' parameter must be
 * set to B_TRUE in the roll back case.
 */
static ilb_status_t
ilbd_enable_one_rule(ilbd_rule_t *irl, boolean_t is_rollback)
{
	ilb_status_t	rc = ILB_STATUS_OK;
	ilb_name_cmd_t	kcmd;

	/* no use sending a no-op to the kernel */
	if ((irl->irl_flags & ILB_FLAGS_RULE_ENABLED) != 0)
		return (ILB_STATUS_OK);

	irl->irl_flags |= ILB_FLAGS_RULE_ENABLED;

	/* "all rules" is handled in one go somewhere else, not here */
	if ((irl->irl_flags & ILB_FLAGS_RULE_ALLRULES) == 0) {
		kcmd.cmd = ILB_ENABLE_RULE;
		(void) strlcpy(kcmd.name, irl->irl_name, sizeof (kcmd.name));
		kcmd.flags = 0;

		rc = do_ioctl(&kcmd, 0);
		if (rc != ILB_STATUS_OK)
			return (rc);
	}
	if (RULE_HAS_HC(irl) && (rc = ilbd_hc_enable_rule(irl)) !=
	    ILB_STATUS_OK) {
		/* Undo the kernel work */
		kcmd.cmd = ILB_DISABLE_RULE;
		/* Cannot do much if ioctl fails... */
		(void) do_ioctl(&kcmd, 0);
		return (rc);
	}

	if (!is_rollback) {
		if (rc == ILB_STATUS_OK)
			rc = i_ilbd_save_rule(irl, ILBD_SCF_ENABLE_DISABLE);
		if (rc != ILB_STATUS_OK)
			/* ignore rollback return code */
			(void) ilbd_disable_one_rule(irl, B_TRUE);
	}

	return (rc);
}

static ilb_status_t
ilbd_disable_one_rule(ilbd_rule_t *irl, boolean_t is_rollback)
{
	ilb_status_t	rc = ILB_STATUS_OK;
	ilb_name_cmd_t	kcmd;

	/* no use sending a no-op to the kernel */
	if ((irl->irl_flags & ILB_FLAGS_RULE_ENABLED) == 0)
		return (ILB_STATUS_OK);

	irl->irl_flags &= ~ILB_FLAGS_RULE_ENABLED;

	/* "all rules" is handled in one go somewhere else, not here */
	if ((irl->irl_flags & ILB_FLAGS_RULE_ALLRULES) == 0) {
		kcmd.cmd = ILB_DISABLE_RULE;
		(void) strlcpy(kcmd.name, irl->irl_name, sizeof (kcmd.name));
		kcmd.flags = 0;

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

	if (RULE_HAS_HC(irl) && (rc = ilbd_hc_disable_rule(irl)) !=
	    ILB_STATUS_OK) {
		/* Undo the kernel work */
		kcmd.cmd = ILB_ENABLE_RULE;
		/* Cannot do much if ioctl fails... */
		(void) do_ioctl(&kcmd, 0);
		return (rc);
	}

	if (!is_rollback) {
		if (rc == ILB_STATUS_OK)
			rc = i_ilbd_save_rule(irl, ILBD_SCF_ENABLE_DISABLE);
		if (rc != ILB_STATUS_OK)
			/* ignore rollback return code */
			(void) ilbd_enable_one_rule(irl, B_TRUE);
	}

	return (rc);
}

/*
 * Generates an audit record for a supplied rule name
 * Used for enable_rule, disable_rule, delete_rule,
 * and create_rule subcommands
 */
static void
ilbd_audit_rule_event(const char *audit_rule_name,
    ilb_rule_info_t *rlinfo, 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			scf_val_len = ILBD_MAX_VALUE_LEN;
	char			*aobuf = NULL; /* algo:topo */
	char			*valstr1 = NULL;
	char			*valstr2 = NULL;
	char			pbuf[PROTOCOL_LEN]; /* protocol */
	char			hcpbuf[PORT_LEN]; /* hcport */
	int			audit_error;

	if ((ucredp == NULL) && (cmd == ILBD_CREATE_RULE))  {
		/*
		 * we came here from the path where ilbd incorporates
		 * the configuration that is listed in SCF :
		 * i_ilbd_read_config->ilbd_walk_rule_pgs->
		 *    ->ilbd_scf_instance_walk_pg->ilbd_create_rule
		 * We skip auditing in that case
		 */
		return;
	}
	if (adt_start_session(&ah, NULL, 0) != 0) {
		logerr("ilbd_audit_rule_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_rule_event: adt_set_from_ucred failed");
		exit(EXIT_FAILURE);
	}
	if (cmd == ILBD_ENABLE_RULE)
		flag = ADT_ilb_enable_rule;
	else if (cmd == ILBD_DISABLE_RULE)
		flag = ADT_ilb_disable_rule;
	else if (cmd == ILBD_DESTROY_RULE)
		flag = ADT_ilb_delete_rule;
	else if (cmd == ILBD_CREATE_RULE)
		flag = ADT_ilb_create_rule;

	if ((event = adt_alloc_event(ah, flag)) == NULL) {
		logerr("ilbd_audit_rule_event: adt_alloc_event failed");
		exit(EXIT_FAILURE);
	}

	(void) memset((char *)event, 0, sizeof (adt_event_data_t));

	switch (cmd) {
	case ILBD_DESTROY_RULE:
		event->adt_ilb_delete_rule.auth_used = NET_ILB_CONFIG_AUTH;
		event->adt_ilb_delete_rule.rule_name = (char *)audit_rule_name;
		break;
	case ILBD_ENABLE_RULE:
		event->adt_ilb_enable_rule.auth_used = NET_ILB_ENABLE_AUTH;
		event->adt_ilb_enable_rule.rule_name = (char *)audit_rule_name;
		break;
	case ILBD_DISABLE_RULE:
		event->adt_ilb_disable_rule.auth_used = NET_ILB_ENABLE_AUTH;
		event->adt_ilb_disable_rule.rule_name = (char *)audit_rule_name;
		break;
	case ILBD_CREATE_RULE:
		if (((aobuf = malloc(scf_val_len)) == NULL) ||
		    ((valstr1 = malloc(scf_val_len)) == NULL) ||
		    ((valstr2 = malloc(scf_val_len)) == NULL)) {
			logerr("ilbd_audit_rule_event: could not"
			    " allocate buffer");
			exit(EXIT_FAILURE);
		}

		event->adt_ilb_create_rule.auth_used = NET_ILB_CONFIG_AUTH;

		/* Fill in virtual IP address type */
		if (IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_vip)) {
			event->adt_ilb_create_rule.virtual_ipaddress_type =
			    ADT_IPv4;
			cvt_addr(event->adt_ilb_create_rule.virtual_ipaddress,
			    ADT_IPv4, rlinfo->rl_vip);
		} else {
			event->adt_ilb_create_rule.virtual_ipaddress_type =
			    ADT_IPv6;
			cvt_addr(event->adt_ilb_create_rule.virtual_ipaddress,
			    ADT_IPv6, rlinfo->rl_vip);
		}
		/* Fill in port - could be a single value or a range */
		event->adt_ilb_create_rule.min_port = ntohs(rlinfo->rl_minport);
		if (ntohs(rlinfo->rl_maxport) > ntohs(rlinfo->rl_minport)) {
			/* port range */
			event->adt_ilb_create_rule.max_port =
			    ntohs(rlinfo->rl_maxport);
		} else {
			/* in audit record, max=min when single port */
			event->adt_ilb_create_rule.max_port =
			    ntohs(rlinfo->rl_minport);
		}

		/*
		 * Fill in  protocol - if user does not specify it,
		 * its TCP by default
		 */
		if (rlinfo->rl_proto == IPPROTO_UDP)
			(void) snprintf(pbuf, PROTOCOL_LEN, "UDP");
		else
			(void) snprintf(pbuf, PROTOCOL_LEN, "TCP");
		event->adt_ilb_create_rule.protocol = pbuf;

		/* Fill in algorithm and operation type */
		ilbd_algo_to_str(rlinfo->rl_algo, valstr1);
		ilbd_topo_to_str(rlinfo->rl_topo, valstr2);
		(void) snprintf(aobuf, scf_val_len, "%s:%s",
		    valstr1, valstr2);
		event->adt_ilb_create_rule.algo_optype = aobuf;

		/* Fill in proxy-src for the NAT case */
		if (rlinfo->rl_topo == ILB_TOPO_NAT)  {
			/* copy starting proxy-src address */
			if (IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_nat_src_start)) {
				/* V4 case */
				event->adt_ilb_create_rule.proxy_src_min_type =
				    ADT_IPv4;
				cvt_addr(
				    event->adt_ilb_create_rule.proxy_src_min,
				    ADT_IPv4, rlinfo->rl_nat_src_start);
			} else {
				/* V6 case */
				event->adt_ilb_create_rule.proxy_src_min_type =
				    ADT_IPv6;
				cvt_addr(
				    event->adt_ilb_create_rule.proxy_src_min,
				    ADT_IPv6, rlinfo->rl_nat_src_start);
			}

			/* copy ending proxy-src address */
			if (&rlinfo->rl_nat_src_end == 0) {
				/* proxy-src is a single address */
				event->adt_ilb_create_rule.proxy_src_max_type =
				    event->
				    adt_ilb_create_rule.proxy_src_min_type;
				(void) memcpy(
				    event->adt_ilb_create_rule.proxy_src_max,
				    event->adt_ilb_create_rule.proxy_src_min,
				    (4 * sizeof (uint32_t)));
			} else if (
			    IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_nat_src_end)) {
				/*
				 * proxy-src is a address range - copy ending
				 * proxy-src address
				 * V4 case
				 */
				event->adt_ilb_create_rule.proxy_src_max_type =
				    ADT_IPv4;
				cvt_addr(
				    event->adt_ilb_create_rule.proxy_src_max,
				    ADT_IPv4, rlinfo->rl_nat_src_end);
			} else {
				/* V6 case */
				event->adt_ilb_create_rule.proxy_src_max_type =
				    ADT_IPv6;
				cvt_addr(
				    event->adt_ilb_create_rule.proxy_src_max,
				    ADT_IPv6, rlinfo->rl_nat_src_end);
			}
		}

		/*
		 * Fill in pmask if user has specified one - 0 means
		 * no persistence
		 */
		valstr1[0] = '\0';
		ilbd_ip_to_str(rlinfo->rl_ipversion, &rlinfo->rl_stickymask,
		    valstr1);
			event->adt_ilb_create_rule.persist_mask = valstr1;

		/* If there is a hcname */
		if (rlinfo->rl_hcname[0] != '\0')
			event->adt_ilb_create_rule.hcname = rlinfo->rl_hcname;

		/* Fill in hcport */
		if (rlinfo->rl_hcpflag == ILB_HCI_PROBE_FIX) {
			/* hcport is specified by user */
			(void) snprintf(hcpbuf, PORT_LEN, "%d",
			    rlinfo->rl_hcport);
			event->adt_ilb_create_rule.hcport = hcpbuf;
		} else if (rlinfo->rl_hcpflag == ILB_HCI_PROBE_ANY) {
			/* user has specified "ANY" */
			(void) snprintf(hcpbuf, PORT_LEN, "ANY");
			event->adt_ilb_create_rule.hcport = hcpbuf;
		}
		/*
		 * Fill out the conndrain, nat_timeout and persist_timeout
		 * If the user does not specify them, the default value
		 * is set in the kernel. Userland does not know what
		 * the values are. So if the user
		 * does not specify these values they will show up as
		 * 0 in the audit record.
		 */
		event->adt_ilb_create_rule.conndrain_timeout =
		    rlinfo->rl_conndrain;
		event->adt_ilb_create_rule.nat_timeout =
		    rlinfo->rl_nat_timeout;
		event->adt_ilb_create_rule.persist_timeout =
		    rlinfo->rl_sticky_timeout;

		/* Fill out servergroup and rule name */
		event->adt_ilb_create_rule.server_group = rlinfo->rl_sgname;
		event->adt_ilb_create_rule.rule_name = rlinfo->rl_name;
		break;
	}
	if (rc == ILB_STATUS_OK) {
		if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
			logerr("ilbd_audit_rule_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_rule_event: adt_put_event failed");
			exit(EXIT_FAILURE);
		}
	}
	adt_free_event(event);
	free(aobuf);
	free(valstr1);
	free(valstr2);
	(void) adt_end_session(ah);
}
/*
 * converts IP address from in6_addr format to uint32_t[4]
 * This conversion is needed for recording IP address in
 * audit records.
 */
void
cvt_addr(uint32_t *audit, int32_t type, struct in6_addr address)
{

	if (type == ADT_IPv4)  {
		/* address is IPv4 */
		audit[0] = address._S6_un._S6_u32[3];
	} else {
		/* address is IPv6 */
		(void) memcpy(audit, address._S6_un._S6_u32,
		    (4 * sizeof (uint32_t)));
	}
}

static ilb_status_t
i_ilbd_action_switch(ilbd_rule_t *irl, ilbd_cmd_t cmd,
    boolean_t is_rollback, ucred_t *ucredp)
{
	ilb_status_t    rc;

	switch (cmd) {
	case ILBD_DESTROY_RULE:
		rc = ilbd_destroy_one_rule(irl);
		if (!is_rollback) {
			ilbd_audit_rule_event(irl->irl_name, NULL,
			    cmd, rc, ucredp);
		}
		return (rc);
	case ILBD_ENABLE_RULE:
		rc = ilbd_enable_one_rule(irl, is_rollback);
		if (!is_rollback) {
			ilbd_audit_rule_event(irl->irl_name, NULL, cmd,
			    rc, ucredp);
		}
		return (rc);
	case ILBD_DISABLE_RULE:
		rc = ilbd_disable_one_rule(irl, is_rollback);
		if (!is_rollback) {
			ilbd_audit_rule_event(irl->irl_name, NULL, cmd,
			    rc, ucredp);
		}
		return (rc);
	}
	return (ILB_STATUS_INVAL_CMD);
}

static ilb_cmd_t
i_ilbd2ilb_cmd(ilbd_cmd_t c)
{
	ilb_cmd_t	r;

	switch (c) {
	case ILBD_CREATE_RULE:
		r = ILB_CREATE_RULE;
		break;
	case ILBD_DESTROY_RULE:
		r = ILB_DESTROY_RULE;
		break;
	case ILBD_ENABLE_RULE:
		r = ILB_ENABLE_RULE;
		break;
	case ILBD_DISABLE_RULE:
		r = ILB_DISABLE_RULE;
		break;
	}
	return (r);
}

static ilbd_cmd_t
get_undo_cmd(ilbd_cmd_t cmd)
{
	ilbd_cmd_t	u_cmd;

	switch (cmd) {
	case ILBD_DESTROY_RULE:
		u_cmd = ILBD_BAD_CMD;
		break;
	case ILBD_ENABLE_RULE:
		u_cmd = ILBD_DISABLE_RULE;
		break;
	case ILBD_DISABLE_RULE:
		u_cmd = ILBD_ENABLE_RULE;
		break;
	}

	return (u_cmd);
}

static ilb_status_t
i_ilbd_rule_action(const char *rule_name, const struct passwd *ps,
    ilbd_cmd_t cmd, ucred_t *ucredp)
{
	ilbd_rule_t	*irl, *irl_next;
	boolean_t	is_all_rules = B_FALSE;
	ilb_status_t	rc = ILB_STATUS_OK;
	ilb_name_cmd_t	kcmd;
	ilbd_cmd_t	u_cmd;
	char    rulename[ILB_NAMESZ];

	if (ps != NULL) {
		if ((cmd == ILBD_ENABLE_RULE) || (cmd == ILBD_DISABLE_RULE))
			rc = ilbd_check_client_enable_auth(ps);
		else
			rc = ilbd_check_client_config_auth(ps);
		/* generate the audit record before bailing out */
		if (rc != ILB_STATUS_OK) {
			if (rule_name != '\0') {
				ilbd_audit_rule_event(rule_name, NULL,
				    cmd, rc, ucredp);
			} else {
				(void) snprintf(rulename, sizeof (rulename),
				    "all");
				ilbd_audit_rule_event(rulename, NULL, cmd, rc,
				    ucredp);
			}
			goto out;
		}
	}
	is_all_rules = rule_name[0] == 0;

	/* just one rule */
	if (!is_all_rules) {
		irl = i_find_rule_byname(rule_name);
		if (irl == NULL) {
			rc = ILB_STATUS_ENORULE;
			ilbd_audit_rule_event(rule_name, NULL, cmd, rc, ucredp);
			goto out;
		}
		/* auditing will be done by i_ilbd_action_switch() */
		rc = i_ilbd_action_switch(irl, cmd, B_FALSE, ucredp);
		goto out;
	}

	/* all rules: first tell the kernel, then walk the daemon's list */
	kcmd.cmd = i_ilbd2ilb_cmd(cmd);
	kcmd.flags = ILB_RULE_ALLRULES;

	rc = do_ioctl(&kcmd, 0);
	if (rc != ILB_STATUS_OK) {
		(void) snprintf(rulename, sizeof (rulename), "all");
		ilbd_audit_rule_event(rulename, NULL, cmd, rc, ucredp);
		goto out;
	}

	irl = list_head(&ilbd_rule_hlist);
	while (irl != NULL) {
		irl_next = list_next(&ilbd_rule_hlist, irl);
		irl->irl_flags |= ILB_FLAGS_RULE_ALLRULES;
		/* auditing will be done by i_ilbd_action_switch() */
		rc = i_ilbd_action_switch(irl, cmd, B_FALSE, ucredp);
		irl->irl_flags &= ~ILB_FLAGS_RULE_ALLRULES;
		if (rc != ILB_STATUS_OK)
			goto rollback_list;
		irl = irl_next;
	}
	return (rc);

rollback_list:
	u_cmd = get_undo_cmd(cmd);
	if (u_cmd == ILBD_BAD_CMD)
		return (rc);

	if (is_all_rules) {
		kcmd.cmd = i_ilbd2ilb_cmd(u_cmd);
		(void) do_ioctl(&kcmd, 0);
	}
	/* current list element failed, so we start with previous one */
	irl = list_prev(&ilbd_rule_hlist, irl);
	while (irl != NULL) {
		if (is_all_rules)
			irl->irl_flags |= ILB_FLAGS_RULE_ALLRULES;

		/*
		 * When the processing of a command consists of
		 * multiple sequential steps, and one of them fails,
		 * ilbd performs rollback to undo the steps taken before the
		 * failing step. Since ilbd is initiating these steps
		 * there is not need to audit them.
		 */
		rc = i_ilbd_action_switch(irl, u_cmd, B_TRUE, NULL);
		irl->irl_flags &= ~ILB_FLAGS_RULE_ALLRULES;

		irl = list_prev(&ilbd_rule_hlist, irl);
	}
out:
	return (rc);
}

ilb_status_t
ilbd_destroy_rule(ilbd_name_t rule_name, const struct passwd *ps,
    ucred_t *ucredp)
{
	return (i_ilbd_rule_action(rule_name, ps, ILBD_DESTROY_RULE, ucredp));
}

ilb_status_t
ilbd_enable_rule(ilbd_name_t rule_name, const struct passwd *ps,
    ucred_t *ucredp)
{
	return (i_ilbd_rule_action(rule_name, ps, ILBD_ENABLE_RULE, ucredp));

}

ilb_status_t
ilbd_disable_rule(ilbd_name_t rule_name, const struct passwd *ps,
    ucred_t *ucredp)
{
	return (i_ilbd_rule_action(rule_name, ps, ILBD_DISABLE_RULE, ucredp));
}

/*
 * allocate storage for a kernel rule command and fill from
 * "template" irl, if non-NULL
 */
static ilb_rule_cmd_t *
i_alloc_kernel_rule_cmd(ilbd_rule_t *irl)
{
	ilb_rule_cmd_t *kcmd;

	kcmd = (ilb_rule_cmd_t *)malloc(sizeof (*kcmd));
	if (kcmd == NULL)
		return (kcmd);

	bzero(kcmd, sizeof (*kcmd));

	if (irl != NULL) {
		kcmd->flags = irl->irl_flags;
		kcmd->ip_ver = AF_2_IPPROTO(irl->irl_ipversion);
		kcmd->vip = irl->irl_vip;
		kcmd->proto = irl->irl_proto;
		kcmd->min_port = irl->irl_minport;
		kcmd->max_port = irl->irl_maxport;
		kcmd->algo = algo_lib2impl(irl->irl_algo);
		kcmd->topo = topo_lib2impl(irl->irl_topo);
		kcmd->sticky_mask = irl->irl_stickymask;
		kcmd->nat_src_start = irl->irl_nat_src_start;
		kcmd->nat_src_end = irl->irl_nat_src_end;
		kcmd->conn_drain_timeout = irl->irl_conndrain;
		kcmd->nat_expiry = irl->irl_nat_timeout;
		kcmd->sticky_expiry = irl->irl_sticky_timeout;
		(void) strlcpy(kcmd->name, irl->irl_name,
		    sizeof (kcmd->name));
	}
	return (kcmd);
}

/*
 * ncount is the next to be used index into (*kcmdp)->servers
 */
static ilb_status_t
adjust_srv_info_cmd(ilb_servers_info_cmd_t **kcmdp, int index)
{
	ilb_servers_info_cmd_t	*kcmd = *kcmdp;
	size_t			sz;

	if (kcmd != NULL && kcmd->num_servers > index + 1)
		return (ILB_STATUS_OK);

	/*
	 * the first ilb_server_info_t is part of *kcmd, so
	 * by using index (which is one less than the total needed) here,
	 * we allocate exactly the amount we need.
	 */
	sz = sizeof (*kcmd) + (index * sizeof (ilb_server_info_t));
	kcmd = (ilb_servers_info_cmd_t *)realloc(kcmd, sz);
	if (kcmd == NULL)
		return (ILB_STATUS_ENOMEM);

	/*
	 * we don't count the slot we newly allocated yet.
	 */
	kcmd->num_servers = index;
	*kcmdp = kcmd;

	return (ILB_STATUS_OK);
}

/*
 * this function adds all servers in srvlist to the kernel(!) rule
 * the name of which is passed as argument.
 */
static ilb_status_t
i_update_ksrv_rules(char *name, ilbd_sg_t *sg, ilbd_rule_t *rl)
{
	ilb_status_t		rc;
	ilbd_srv_t		*srvp;
	ilb_servers_info_cmd_t	*kcmd = NULL;
	int			i;

	/*
	 * If the servergroup doesn't have any servers associated with
	 * it yet, there's nothing more to do here.
	 */
	if (sg->isg_srvcount == 0)
		return (ILB_STATUS_OK);

	/*
	 * walk the list of servers attached to this SG
	 */
	srvp = list_head(&sg->isg_srvlist);
	for (i = 0; srvp != NULL; srvp = list_next(&sg->isg_srvlist, srvp)) {
		rc = adjust_srv_info_cmd(&kcmd, i);
		if (rc != ILB_STATUS_OK)
			return (rc);

		ILB_SGSRV_2_KSRV(&srvp->isv_srv, &kcmd->servers[i]);
		/*
		 * "no port" means "copy rule's port" (for kernel rule)
		 */
		if (kcmd->servers[i].min_port == 0) {
			kcmd->servers[i].min_port = rl->irl_minport;
			kcmd->servers[i].max_port = rl->irl_maxport;
		}
		i++;
	}

	kcmd->cmd = ILB_ADD_SERVERS;
	kcmd->num_servers = i;
	(void) strlcpy(kcmd->name, name, sizeof (kcmd->name));

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

	for (i = 0; i < kcmd->num_servers; i++) {
		int e;

		if ((e = kcmd->servers[i].err) != 0) {
			logerr("i_update_ksrv_rules "
			    "ioctl indicates failure: %s", strerror(e));
			rc = ilb_map_errno2ilbstat(e);
			/*
			 * if adding even a single server failed, we need to
			 * roll back the whole wad. We ignore any errors and
			 * return the one that was returned by the first ioctl.
			 */
			kcmd->cmd = ILB_DEL_SERVERS;
			(void) do_ioctl(kcmd, 0);
			return (rc);
		}
	}

	return (ILB_STATUS_OK);
}

/* convert a struct in6_addr to valstr */
void
ilbd_ip_to_str(uint16_t ipversion, struct in6_addr *addr, char *valstr)
{
	size_t	vallen;
	ilb_ip_addr_t	ipaddr;
	void	*addrptr;

	vallen = (ipversion == AF_INET) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN;

	IP_COPY_IMPL_2_CLI(addr, &ipaddr);
	addrptr = (ipversion == AF_INET) ?
	    (void *)&ipaddr.ia_v4 : (void *)&ipaddr.ia_v6;
	if (inet_ntop(ipversion, (void *)addrptr, valstr, vallen == NULL))
		logerr("ilbd_ip_to_str: inet_ntop failed");
	return;

}

ilb_status_t
ilbd_create_rule(ilb_rule_info_t *rl, int ev_port,
    const struct passwd *ps, ucred_t *ucredp)
{
	ilb_status_t	rc;
	ilbd_rule_t	*irl = NULL;
	ilbd_sg_t	*sg;
	ilb_rule_cmd_t	*kcmd = NULL;

	if (ps != NULL) {
		if ((rc = ilbd_check_client_config_auth(ps)) != ILB_STATUS_OK)
			goto out;
	}

	if (i_find_rule_byname(rl->rl_name) != NULL) {
		logdebug("ilbd_create_rule: rule %s"
		    " already exists", rl->rl_name);
		ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE,
		    ILB_STATUS_DUP_RULE, ucredp);
		return (ILB_STATUS_DUP_RULE);
	}

	sg = i_find_sg_byname(rl->rl_sgname);
	if (sg == NULL) {
		logdebug("ilbd_create_rule: rule %s uses non-existent"
		    " servergroup name %s", rl->rl_name, rl->rl_sgname);
		ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE,
		    ILB_STATUS_SGUNAVAIL, ucredp);
		return (ILB_STATUS_SGUNAVAIL);
	}

	if ((rc = ilbd_sg_check_rule_port(sg, rl)) != ILB_STATUS_OK) {
		ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE, rc, ucredp);
		return (rc);
	}

	/* allocs and copies contents of arg (if != NULL) into new rule */
	irl = i_alloc_ilbd_rule(rl);
	if (irl == NULL) {
		ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE,
		    ILB_STATUS_ENOMEM, ucredp);
		return (ILB_STATUS_ENOMEM);
	}

	/* make sure rule's IPversion (via vip) and SG's match */
	if (sg->isg_srvcount > 0) {
		ilbd_srv_t	*srv = list_head(&sg->isg_srvlist);
		int32_t		r_af = rl->rl_ipversion;
		int32_t		s_af = GET_AF(&srv->isv_addr);

		if (r_af != s_af) {
			logdebug("address family mismatch with servergroup");
			rc = ILB_STATUS_MISMATCHSG;
			goto out;
		}
	}
	irl->irl_sg = sg;

	/* Try associating the rule with the given hc oject. */
	if (RULE_HAS_HC(irl)) {
		if ((rc = ilbd_hc_associate_rule(irl, ev_port)) !=
		    ILB_STATUS_OK)
			goto out;
	}

	/*
	 * checks are done, now:
	 * 1. create rule in kernel
	 * 2. tell it about the backend server (which we maintain in SG)
	 * 3. attach the rule in memory
	 */
	/* 1. */
	/* allocs and copies contents of arg (if != NULL) into new rule */
	kcmd = i_alloc_kernel_rule_cmd(irl);
	if (kcmd == NULL) {
		rc = ILB_STATUS_ENOMEM;
		goto rollback_hc;
	}
	kcmd->cmd = ILB_CREATE_RULE;

	rc = do_ioctl(kcmd, 0);
	if (rc != ILB_STATUS_OK)
		goto rollback_kcmd;

	/* 2. */
	rc = i_update_ksrv_rules(kcmd->name, sg, irl);
	if (rc != ILB_STATUS_OK)
		goto rollback_kcmd;

	/* 3. */
	(void) i_attach_rule2sg(sg, irl);
	list_insert_tail(&ilbd_rule_hlist, irl);

	if (ps != NULL) {
		rc = i_ilbd_save_rule(irl, ILBD_SCF_CREATE);
		if (rc != ILB_STATUS_OK)
			goto rollback_rule;
	}

	free(kcmd);
	ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE,
	    ILB_STATUS_OK, ucredp);
	return (ILB_STATUS_OK);

rollback_rule:
	/*
	 * ilbd_destroy_one_rule() also frees irl, as well as dissociate
	 * rule and HC, so all we need to do afterwards is free the kcmd
	 * and return.
	 */
	(void) ilbd_destroy_one_rule(irl);
	ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE, rc, ucredp);
	free(kcmd);
	return (rc);

rollback_kcmd:
	free(kcmd);
rollback_hc:
	/* Cannot fail since the rule is just associated with the hc object. */
	if (RULE_HAS_HC(irl))
		(void) ilbd_hc_dissociate_rule(irl);
out:
	ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE, rc, ucredp);
	free(irl);
	return (rc);
}

static uint32_t
i_flags_d2k(int f)
{
	uint32_t	r = 0;

	if (ILB_IS_SRV_ENABLED(f))
		r |= ILB_SERVER_ENABLED;
	/* more as they are defined */

	return (r);
}

/*
 * walk the list of rules and add srv to the *kernel* rule
 * (this is a list of rules hanging off of a server group)
 */
ilb_status_t
i_add_srv2krules(list_t *rlist, ilb_sg_srv_t *srv, int ev_port)
{
	ilb_status_t		rc = ILB_STATUS_OK;
	ilbd_rule_t		*rl, *del_rl;
	ilb_servers_info_cmd_t	kcmd;
	ilb_servers_cmd_t	del_kcmd;

	kcmd.cmd = ILB_ADD_SERVERS;
	kcmd.num_servers = 1;
	kcmd.servers[0].err = 0;
	kcmd.servers[0].addr = srv->sgs_addr;
	kcmd.servers[0].flags = i_flags_d2k(srv->sgs_flags);
	(void) strlcpy(kcmd.servers[0].name, srv->sgs_srvID,
	    sizeof (kcmd.servers[0].name));

	/*
	 * a note about rollback: since we need to start rollback with the
	 * current list element in some case, and with the previous one
	 * in others, we must "go back" in this latter case before
	 * we jump to the rollback code.
	 */
	for (rl = list_head(rlist); rl != NULL; rl = list_next(rlist, rl)) {
		(void) strlcpy(kcmd.name, rl->irl_name, sizeof (kcmd.name));
		/*
		 * sgs_minport == 0 means "no port specified"; this
		 * indicates that the server matches anything the rule
		 * provides.
		 * NOTE: this can be different for different rules
		 * using the same server group, therefore we don't modify
		 * this information in the servergroup, but *only* in
		 * the kernel's rule.
		 */
		if (srv->sgs_minport == 0) {
			kcmd.servers[0].min_port = rl->irl_minport;
			kcmd.servers[0].max_port = rl->irl_maxport;
		} else {
			kcmd.servers[0].min_port = srv->sgs_minport;
			kcmd.servers[0].max_port = srv->sgs_maxport;
		}
		rc = do_ioctl((void *)&kcmd, 0);
		if (rc != ILB_STATUS_OK) {
			logdebug("i_add_srv2krules: do_ioctl call failed");
			del_rl = list_prev(rlist, rl);
			goto rollback;
		}

		/*
		 * if ioctl() returns != 0, it doesn't perform the copyout
		 * necessary to indicate *which* server failed (we could be
		 * adding more than one); therefore we must check this
		 * 'err' field even if ioctl() returns 0.
		 */
		if (kcmd.servers[0].err != 0) {
			logerr("i_add_srv2krules: SIOCILB ioctl returned"
			    " error %d", kcmd.servers[0].err);
			rc = ilb_map_errno2ilbstat(kcmd.servers[0].err);
			del_rl = list_prev(rlist, rl);
			goto rollback;
		}
		if (RULE_HAS_HC(rl)) {
			if ((rc = ilbd_hc_add_server(rl, srv, ev_port)) !=
			    ILB_STATUS_OK) {
				logerr("i_add_srv2krules: cannot start timer "
				    " for rules %s server %s", rl->irl_name,
				    srv->sgs_srvID);

				del_rl = rl;
				goto rollback;
			}
		}
	}

	return (rc);

rollback:
	/*
	 * this is almost, but not quite, the same as i_rem_srv_frm_krules()
	 * therefore we keep it seperate.
	 */
	del_kcmd.cmd = ILB_DEL_SERVERS;
	del_kcmd.num_servers = 1;
	del_kcmd.servers[0].addr = srv->sgs_addr;
	while (del_rl != NULL) {
		if (RULE_HAS_HC(del_rl))
			(void) ilbd_hc_del_server(del_rl, srv);
		(void) strlcpy(del_kcmd.name, del_rl->irl_name,
		    sizeof (del_kcmd.name));
		(void) do_ioctl((void *)&del_kcmd, 0);
		del_rl = list_prev(rlist, del_rl);
	}

	return (rc);
}

/*
 * ev_port is only used for rollback purposes in this function
 */
ilb_status_t
i_rem_srv_frm_krules(list_t *rlist, ilb_sg_srv_t *srv, int ev_port)
{
	ilb_status_t		rc = ILB_STATUS_OK;
	ilbd_rule_t		*rl, *add_rl;
	ilb_servers_cmd_t	kcmd;
	ilb_servers_info_cmd_t	add_kcmd;

	kcmd.cmd = ILB_DEL_SERVERS;
	kcmd.num_servers = 1;
	kcmd.servers[0].err = 0;
	kcmd.servers[0].addr = srv->sgs_addr;

	for (rl = list_head(rlist); rl != NULL; rl = list_next(rlist, rl)) {
		(void) strlcpy(kcmd.name, rl->irl_name, sizeof (kcmd.name));
		rc = do_ioctl((void *)&kcmd, 0);
		if (rc != ILB_STATUS_OK) {
			logdebug("i_rem_srv_frm_krules: do_ioctl"
			    "call failed");
			add_rl = list_prev(rlist, rl);
			goto rollback;
		}
		/*
		 * if ioctl() returns != 0, it doesn't perform the copyout
		 * necessary to indicate *which* server failed (we could be
		 * removing more than one); therefore we must check this
		 * 'err' field even if ioctl() returns 0.
		 */
		if (kcmd.servers[0].err != 0) {
			logerr("i_rem_srv_frm_krules: SIOCILB ioctl"
			    " returned error %s",
			    strerror(kcmd.servers[0].err));
			rc = ilb_map_errno2ilbstat(kcmd.servers[0].err);
			add_rl = list_prev(rlist, rl);
			goto rollback;
		}
		if (RULE_HAS_HC(rl) &&
		    (rc = ilbd_hc_del_server(rl, srv)) != ILB_STATUS_OK) {
			logerr("i_rem_srv_frm_krules: cannot delete "
			    "timer for rules %s server %s", rl->irl_name,
			    srv->sgs_srvID);
			add_rl = rl;
			goto rollback;
		}
	}

	return (rc);

rollback:
	/* Don't do roll back if ev_port == -1. */
	if (ev_port == -1)
		return (rc);

	add_kcmd.cmd = ILB_ADD_SERVERS;
	add_kcmd.num_servers = 1;
	add_kcmd.servers[0].err = 0;
	add_kcmd.servers[0].addr = srv->sgs_addr;
	add_kcmd.servers[0].flags = i_flags_d2k(srv->sgs_flags);
	(void) strlcpy(add_kcmd.servers[0].name, srv->sgs_srvID,
	    sizeof (add_kcmd.servers[0].name));
	while (add_rl != NULL) {
		if (srv->sgs_minport == 0) {
			add_kcmd.servers[0].min_port = add_rl->irl_minport;
			add_kcmd.servers[0].max_port = add_rl->irl_maxport;
		} else {
			add_kcmd.servers[0].min_port = srv->sgs_minport;
			add_kcmd.servers[0].max_port = srv->sgs_maxport;
		}
		if (RULE_HAS_HC(add_rl))
			(void) ilbd_hc_add_server(add_rl, srv, ev_port);
		(void) strlcpy(add_kcmd.name, add_rl->irl_name,
		    sizeof (add_kcmd.name));
		(void) do_ioctl((void *)&add_kcmd, 0);
		add_rl = list_prev(rlist, add_rl);
	}

	return (rc);
}