changeset 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 b3afbf06f20d
children 2ecbb0a4d189
files usr/src/Makefile.lint usr/src/Targetdirs usr/src/cmd/Makefile.check usr/src/cmd/cmd-inet/usr.lib/Makefile usr/src/cmd/cmd-inet/usr.lib/ilbd/Makefile usr/src/cmd/cmd-inet/usr.lib/ilbd/ilb/Makefile usr/src/cmd/cmd-inet/usr.lib/ilbd/ilb/ilb_probe.c usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd.h usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd.xml usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_hc.c usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_main.c usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_nat.c usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_rules.c usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_scf.c usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_sg.c usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_support.c usr/src/cmd/cmd-inet/usr.sbin/Makefile usr/src/cmd/cmd-inet/usr.sbin/ilbadm/Makefile usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm.c usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm.h usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_hc.c usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_import.c usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_nat.c usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_rules.c usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_sg.c usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_stats.c usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_subr.c usr/src/cmd/mdb/common/modules/ip/ip.c usr/src/cmd/mdb/intel/amd64/ip/Makefile usr/src/cmd/mdb/intel/ia32/ip/Makefile usr/src/cmd/mdb/sparc/v9/ip/Makefile usr/src/head/auth_list.h usr/src/lib/Makefile usr/src/lib/libbsm/audit_event.txt usr/src/lib/libbsm/common/adt.xml usr/src/lib/libilb/Makefile usr/src/lib/libilb/Makefile.com usr/src/lib/libilb/amd64/Makefile usr/src/lib/libilb/common/ilb_comm.c usr/src/lib/libilb/common/ilb_hc.c usr/src/lib/libilb/common/ilb_nat.c usr/src/lib/libilb/common/ilb_rules.c usr/src/lib/libilb/common/ilb_sg.c usr/src/lib/libilb/common/ilb_subr.c usr/src/lib/libilb/common/libilb.h usr/src/lib/libilb/common/libilb_impl.h usr/src/lib/libilb/common/llib-lilb usr/src/lib/libilb/common/mapfile-vers usr/src/lib/libilb/i386/Makefile usr/src/lib/libilb/sparc/Makefile usr/src/lib/libilb/sparcv9/Makefile usr/src/lib/libinetutil/common/libinetutil.h usr/src/lib/libinetutil/common/ofmt.c usr/src/lib/libinetutil/common/ofmt.h usr/src/lib/libinetutil/common/tq.c usr/src/lib/libsecdb/auth_attr.txt usr/src/lib/libsecdb/help/auths/Makefile usr/src/lib/libsecdb/help/auths/NetworkILBconf.html usr/src/lib/libsecdb/help/auths/NetworkILBenable.html usr/src/lib/libsecdb/help/auths/SmfILBStates.html usr/src/lib/libsecdb/help/profiles/Makefile usr/src/lib/libsecdb/help/profiles/RtNetILB.html usr/src/lib/libsecdb/prof_attr.txt usr/src/lib/libsecdb/user_attr.txt usr/src/pkgdefs/Makefile usr/src/pkgdefs/SUNW0on/prototype_com usr/src/pkgdefs/SUNWcsu/prototype_com usr/src/pkgdefs/SUNWilb/Makefile usr/src/pkgdefs/SUNWilb/depend usr/src/pkgdefs/SUNWilb/pkginfo.tmpl usr/src/pkgdefs/SUNWilb/prototype_com usr/src/pkgdefs/SUNWilb/prototype_i386 usr/src/pkgdefs/SUNWilb/prototype_sparc usr/src/pkgdefs/SUNWilbr/Makefile usr/src/pkgdefs/SUNWilbr/depend usr/src/pkgdefs/SUNWilbr/pkginfo.tmpl usr/src/pkgdefs/SUNWilbr/prototype_com usr/src/pkgdefs/SUNWilbr/prototype_i386 usr/src/pkgdefs/SUNWilbr/prototype_sparc usr/src/uts/common/Makefile.files usr/src/uts/common/Makefile.rules usr/src/uts/common/inet/ilb.h usr/src/uts/common/inet/ilb/ilb.c usr/src/uts/common/inet/ilb/ilb_alg.h usr/src/uts/common/inet/ilb/ilb_alg_hash.c usr/src/uts/common/inet/ilb/ilb_alg_rr.c usr/src/uts/common/inet/ilb/ilb_conn.c usr/src/uts/common/inet/ilb/ilb_conn.h usr/src/uts/common/inet/ilb/ilb_impl.h usr/src/uts/common/inet/ilb/ilb_nat.c usr/src/uts/common/inet/ilb/ilb_nat.h usr/src/uts/common/inet/ilb/ilb_stack.h usr/src/uts/common/inet/ilb_ip.h usr/src/uts/common/inet/ip/ip.c usr/src/uts/common/inet/ip/ip6.c usr/src/uts/common/inet/ip/ip_if.c usr/src/uts/common/inet/ipnet/ipnet.c usr/src/uts/common/sys/netstack.h usr/src/uts/common/sys/sockio.h usr/src/uts/intel/ip/ip.global-objs.debug64 usr/src/uts/intel/ip/ip.global-objs.obj64 usr/src/uts/sparc/ip/ip.global-objs.debug64 usr/src/uts/sparc/ip/ip.global-objs.obj64
diffstat 103 files changed, 26993 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/Makefile.lint	Tue Nov 03 21:31:39 2009 -0800
+++ b/usr/src/Makefile.lint	Tue Nov 03 23:15:19 2009 -0800
@@ -72,6 +72,7 @@
 	cmd/cmd-inet/usr.bin \
 	cmd/cmd-inet/usr.lib/bridged \
 	cmd/cmd-inet/usr.lib/dsvclockd \
+	cmd/cmd-inet/usr.lib/ilbd \
 	cmd/cmd-inet/usr.lib/in.dhcpd \
 	cmd/cmd-inet/usr.lib/in.mpathd \
 	cmd/cmd-inet/usr.lib/in.ndpd \
@@ -82,6 +83,7 @@
 	cmd/cmd-inet/usr.lib/wanboot \
 	cmd/cmd-inet/usr.sadm \
 	cmd/cmd-inet/usr.sbin \
+	cmd/cmd-inet/usr.sbin/ilbadm \
 	cmd/col \
 	cmd/compress \
 	cmd/consadm \
@@ -365,6 +367,7 @@
 	lib/libgss \
 	lib/libhotplug \
 	lib/libidmap \
+	lib/libilb \
 	lib/libinetcfg \
 	lib/libinetsvc \
 	lib/libinetutil \
--- a/usr/src/Targetdirs	Tue Nov 03 21:31:39 2009 -0800
+++ b/usr/src/Targetdirs	Tue Nov 03 23:15:19 2009 -0800
@@ -234,6 +234,7 @@
 	/usr/lib/inet/dhcp/nsu \
 	/usr/lib/inet/dhcp/svc \
 	/usr/lib/inet/dhcp/svcadm \
+	/usr/lib/inet/ilb \
 	/usr/lib/inet/$(MACH32) \
 	$(XDIRS) \
 	/usr/lib/krb5 \
--- a/usr/src/cmd/Makefile.check	Tue Nov 03 21:31:39 2009 -0800
+++ b/usr/src/cmd/Makefile.check	Tue Nov 03 23:15:19 2009 -0800
@@ -82,6 +82,7 @@
 	agents/snmp/snmprelayd		\
 	boot/scripts			\
 	cmd-crypto/scripts		\
+	cmd-inet/usr.lib/ilbd		\
 	cmd-inet/usr.lib/in.chargend	\
 	cmd-inet/usr.lib/in.daytimed	\
 	cmd-inet/usr.lib/in.dhcpd	\
--- a/usr/src/cmd/cmd-inet/usr.lib/Makefile	Tue Nov 03 21:31:39 2009 -0800
+++ b/usr/src/cmd/cmd-inet/usr.lib/Makefile	Tue Nov 03 23:15:19 2009 -0800
@@ -23,12 +23,12 @@
 # Use is subject to license terms.
 #
 
-SUBDIRS=	bridged dhcp dsvclockd in.chargend in.daytimed \
+SUBDIRS=	bridged dhcp dsvclockd ilbd in.chargend in.daytimed \
 		in.discardd in.echod in.dhcpd in.mpathd in.ndpd \
 		in.ripngd in.timed inetd mdnsd ncaconfd pppoe \
 		slpd wanboot wpad
 
-MSGSUBDIRS=	dsvclockd in.dhcpd inetd ncaconfd wanboot
+MSGSUBDIRS=	dsvclockd ilbd in.dhcpd inetd ncaconfd wanboot
 
 include ../../Makefile.cmd
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/Makefile	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,112 @@
+#
+# 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.
+#
+
+ILBD=		ilbd
+PROG=		$(ILBD)
+MANIFEST=	ilbd.xml
+
+ILBD_OBJS=	ilbd_main.o ilbd_support.o ilbd_sg.o ilbd_rules.o ilbd_hc.o \
+		ilbd_nat.o ilbd_scf.o
+ILBD_SRCS=	$(ILBD_OBJS:%.o=%.c)
+HDRS=		ilbd.h
+
+LIST_OBJS=	list.o
+LIST_SRCS=	$(LIST_OBJS:%.o=../../../uts/common/os/%.c) 
+
+LIB_INC=	$(SRC)/lib/libilb/common
+
+OBJS=		$(ILBD_OBJS) $(LIST_OBJS)
+SRCS=		$(ILBD_SRCS) $(LIST_SRCS)
+
+ILBSUBDIRS=	ilb
+
+include		../../../Makefile.cmd
+include		../../Makefile.cmd-inet
+
+ROOTMANIFESTDIR=	$(ROOTSVCNETWORK)/loadbalancer
+
+CHECKHDRS=	$(HDRS:%.h=%.check)
+
+CPPFLAGS +=	-D_FILE_OFFSET_BITS=64 -I$(CMDINETCOMMONDIR) -D_REENTRANT
+CPPFLAGS +=	-I$(LIB_INC)
+CPPFLAGS +=	-I$(SRC)/uts/common
+
+C99MODE =      $(C99_ENABLE)
+
+# I18n
+POFILE =        $(ILBD).po
+POFILES =       $(ILBD_SRCS:%.c=%.po)
+
+all:=		TARGET= all
+install:=	TARGET= install
+clean:=		TARGET= clean
+clobber:=	TARGET= clobber
+lint:=		TARGET= lint
+
+# used for debugging ONLY:
+
+CFLAGS =		-g -K pic 
+STRIP_STABS=		:
+CTFCVTFLAGS +=		-g
+
+
+LDLIBS +=	-lsocket -lsecdb -lnsl -lilb -lscf -linetutil -lbsm
+
+.KEEP_STATE:
+
+all:		$(PROG)
+
+$(ILBD): 	$(OBJS)
+		$(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(CTFMERGE_HOOK)
+		$(POST_PROCESS)
+
+include ../Makefile.lib
+
+install:	all $(ROOTLIBINETPROG) $(ETCILBDFILES) $(ROOTMANIFEST)
+
+check:	$(CHKMANIFEST) $(CHECKHDRS)
+
+clean:
+	$(RM) $(OBJS)
+
+lint:
+	$(LINT.c) $(ILBD_SRCS) $(LDLIBS)
+
+$(POFILE):	$(POFILES)
+		$(RM) $@
+		$(CAT) $(POFILES) > $@
+
+all install clean clobber lint: $(ILBSUBDIRS)
+
+include		../../../Makefile.targ
+
+# the below is needed to get list.o built
+%.o: ../../../../uts/common/os/%.c
+	$(COMPILE.c) -o $@ $<
+	$(POST_PROCESS_O)
+
+$(ILBSUBDIRS): FRC
+	@cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilb/Makefile	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,55 @@
+#
+# 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.
+#
+
+PROG =		ilb_probe
+OBJS =		$(PROG:%=%.o)
+SRCS =		$(OBJS:%.o=%.c)
+
+include	../../../../Makefile.cmd
+
+LDLIBS +=	-lsocket -lnsl
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+include	../../Makefile.lib
+
+ROOTLIBINETILB =	$(ROOTLIBINET)/ilb
+ROOTLIBINETILBPROG =	$(PROG:%=$(ROOTLIBINETILB)/%)
+
+$(ROOTLIBINETILB): $(ROOTLIBINET)
+	$(INS.dir)
+
+$(ROOTLIBINETILB)/%: % $(ROOTLIBINETILB)
+	$(INS.file)
+
+install: all $(ROOTLIBINETILBPROG)
+
+clean:
+	$(RM) $(OBJS)
+
+lint:		lint_SRCS
+
+include		../../../../Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilb/ilb_probe.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,875 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <arpa/inet.h>
+#include <netinet/tcp.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <netinet/udp.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <libgen.h>
+#include <fcntl.h>
+
+/*
+ * The following values are what ilbd will set argv[0] to.  This determines
+ * what type of probe to send out.
+ */
+#define	PROBE_PING	"ilb_ping"
+#define	PROBE_PROTO	"ilb_probe"
+
+/* The transport protocol to use in the probe.  Value of argv[3]. */
+#define	PROTO_TCP	"TCP"
+#define	PROTO_UDP	"UDP"
+
+enum probe_type { ping_probe, tcp_probe, udp_probe };
+
+/* Load balance mode.  Value of argv[4]. */
+#define	MODE_DSR	"DSR"
+#define	MODE_NAT	"NAT"
+#define	MODE_HALF_NAT	"HALF_NAT"
+
+enum lb_mode { dsr, nat, half_nat };
+
+/* Number of arguments to the command from ilbd. */
+#define	PROG_ARGC	7
+
+/* Size of buffer used to receive ICMP packet */
+#define	RECV_PKT_SZ	256
+
+/*
+ * Struct to store the probe info (most is passed in using the argv[] array to
+ * the command given by ilbd).  The argv[] contains the following.
+ *
+ * argv[0] is either PROBE_PING or PROBE_PROTO
+ * argv[1] is the VIP
+ * argv[2] is the backend server address
+ * argv[3] is the transport protocol used in the rule
+ * argv[4] is the load balance mode, "DSR", "NAT", "HALF-NAT"
+ * argv[5] is the probe port
+ * argv[6] is the probe timeout
+ *
+ * The following three fields are used in sending ICMP ECHO probe.
+ *
+ * echo_id is the ID set in the probe
+ * echo_seq is the sequence set in the probe
+ * echo_cookie is the random number data in a probe
+ * lport is the local port (in network byte order) used to send the probe
+ */
+typedef struct {
+	enum probe_type		probe;
+	struct in6_addr		vip;		/* argv[1] */
+	struct in6_addr		srv_addr;	/* argv[2] */
+	int			proto;		/* argv[3] */
+	enum lb_mode		mode;		/* argv[4] */
+	in_port_t		port;		/* argv[5] */
+	uint32_t		timeout;	/* argv[6] */
+
+	uint16_t		echo_id;
+	uint16_t		echo_seq;
+	uint32_t		echo_cookie;
+	in_port_t		lport;
+} probe_param_t;
+
+/* Global variable to indicate whether a timeout means success. */
+static boolean_t timeout_is_good;
+
+/* SIGALRM handler */
+/* ARGSUSED */
+static void
+probe_exit(int s)
+{
+	if (timeout_is_good) {
+		(void) printf("0");
+		exit(0);
+	} else {
+		(void) printf("-1");
+		exit(255);
+	}
+}
+
+/*
+ * Checksum routine for Internet Protocol family headers (C Version)
+ * (copied from ping.c)
+ */
+static ushort_t
+in_cksum(ushort_t *addr, int len)
+{
+	int nleft = len;
+	ushort_t *w = addr;
+	ushort_t answer;
+	ushort_t odd_byte = 0;
+	int sum = 0;
+
+	/*
+	 *  Our algorithm is simple, using a 32 bit accumulator (sum),
+	 *  we add sequential 16 bit words to it, and at the end, fold
+	 *  back all the carry bits from the top 16 bits into the lower
+	 *  16 bits.
+	 */
+	while (nleft > 1) {
+		sum += *w++;
+		nleft -= 2;
+	}
+
+	/* mop up an odd byte, if necessary */
+	if (nleft == 1) {
+		*(uchar_t *)(&odd_byte) = *(uchar_t *)w;
+		sum += odd_byte;
+	}
+
+	/*
+	 * add back carry outs from top 16 bits to low 16 bits
+	 */
+	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
+	sum += (sum >> 16);			/* add carry */
+	answer = ~sum;				/* truncate to 16 bits */
+	return (answer);
+}
+
+/* It is assumed that argv[] contains PROBE_ARGC arguments. */
+static boolean_t
+parse_probe_param(char *argv[], probe_param_t *param)
+{
+	int32_t port;
+	int64_t timeout;
+	struct in_addr v4addr;
+
+	if (strcmp(basename(argv[0]), PROBE_PING) == 0) {
+		param->probe = ping_probe;
+	} else {
+		if (strcmp(basename(argv[0]), PROBE_PROTO) != 0)
+			return (B_FALSE);
+
+		if (strcasecmp(argv[3], PROTO_TCP) == 0) {
+			param->probe = tcp_probe;
+			param->proto = IPPROTO_TCP;
+		} else if (strcasecmp(argv[3], PROTO_UDP) == 0) {
+			param->probe = udp_probe;
+			param->proto = IPPROTO_UDP;
+		} else {
+			return (B_FALSE);
+		}
+	}
+
+	if (strchr(argv[1], ':') != NULL) {
+		if (inet_pton(AF_INET6, argv[1], &param->vip) == 0)
+			return (B_FALSE);
+	} else if (strchr(argv[1], '.') != NULL) {
+		if (inet_pton(AF_INET, argv[1], &v4addr) == 0)
+			return (B_FALSE);
+		IN6_INADDR_TO_V4MAPPED(&v4addr, &param->vip);
+	} else {
+		return (B_FALSE);
+	}
+
+	/*
+	 * The address family of vip and srv_addr should be the same for
+	 * now.  But in future, we may allow them to be different...  So
+	 * we don't do a check here.
+	 */
+	if (strchr(argv[2], ':') != NULL) {
+		if (inet_pton(AF_INET6, argv[2], &param->srv_addr) == 0)
+			return (B_FALSE);
+	} else if (strchr(argv[2], '.') != NULL) {
+		if (inet_pton(AF_INET, argv[2], &v4addr) == 0)
+			return (B_FALSE);
+		IN6_INADDR_TO_V4MAPPED(&v4addr, &param->srv_addr);
+	} else {
+		return (B_FALSE);
+	}
+
+	if (strcasecmp(argv[4], MODE_DSR) == 0)
+		param->mode = dsr;
+	else if (strcasecmp(argv[4], MODE_NAT) == 0)
+		param->mode = nat;
+	else if (strcasecmp(argv[4], MODE_HALF_NAT) == 0)
+		param->mode = half_nat;
+	else
+		return (B_FALSE);
+
+	if ((port = atoi(argv[5])) <= 0 || port > USHRT_MAX)
+		return (B_FALSE);
+	param->port = port;
+
+	if ((timeout = strtoll(argv[6], NULL, 10)) <= 0 || timeout > UINT_MAX)
+		return (B_FALSE);
+	param->timeout = timeout;
+
+	return (B_TRUE);
+}
+
+/*
+ * Set up the destination address to be used to send a probe based on
+ * param.
+ */
+static int
+set_sockaddr(struct sockaddr_storage *addr, socklen_t *addr_len,
+    void **next_hop, probe_param_t *param)
+{
+	int af;
+	struct in6_addr *param_addr;
+	struct sockaddr_in *v4_addr;
+	struct sockaddr_in6 *v6_addr;
+	boolean_t nh = B_FALSE;
+
+	switch (param->mode) {
+	case dsr:
+		param_addr = &param->vip;
+		nh = B_TRUE;
+		break;
+	case nat:
+	case half_nat:
+		param_addr = &param->srv_addr;
+		break;
+	}
+	if (IN6_IS_ADDR_V4MAPPED(param_addr)) {
+		af = AF_INET;
+		v4_addr = (struct sockaddr_in *)addr;
+		IN6_V4MAPPED_TO_INADDR(param_addr, &v4_addr->sin_addr);
+		v4_addr->sin_family = AF_INET;
+		v4_addr->sin_port = htons(param->port);
+
+		*addr_len = sizeof (*v4_addr);
+	} else {
+		af = AF_INET6;
+		v6_addr = (struct sockaddr_in6 *)addr;
+		v6_addr->sin6_family = AF_INET6;
+		v6_addr->sin6_addr = *param_addr;
+		v6_addr->sin6_port = htons(param->port);
+		v6_addr->sin6_flowinfo = 0;
+		v6_addr->sin6_scope_id = 0;
+
+		*addr_len = sizeof (*v6_addr);
+	}
+
+	if (!nh) {
+		*next_hop = NULL;
+		return (af);
+	}
+
+	if (af == AF_INET) {
+		ipaddr_t *nh_addr;
+
+		nh_addr = malloc(sizeof (ipaddr_t));
+		IN6_V4MAPPED_TO_IPADDR(&param->srv_addr, *nh_addr);
+		*next_hop = nh_addr;
+	} else {
+		struct sockaddr_in6 *nh_addr;
+
+		nh_addr = malloc(sizeof (*nh_addr));
+		nh_addr->sin6_family = AF_INET6;
+		nh_addr->sin6_addr = param->srv_addr;
+		nh_addr->sin6_flowinfo = 0;
+		nh_addr->sin6_scope_id = 0;
+		*next_hop = nh_addr;
+	}
+
+	return (af);
+}
+
+/*
+ * Use TCP to check if the peer server is alive.  Create a TCP socket and
+ * then call connect() to reach the peer server.  If connect() does not
+ * return within the timeout period, the SIGALRM handler will be invoked
+ * and tell ilbd that the peer server is not alive.
+ */
+static int
+tcp_query(probe_param_t *param)
+{
+	int ret;
+	int sd, af;
+	struct sockaddr_storage dst_addr;
+	socklen_t dst_addr_len;
+	void *next_hop;
+	hrtime_t start, end;
+	uint32_t rtt;
+
+	ret = 0;
+	next_hop = NULL;
+
+	af = set_sockaddr(&dst_addr, &dst_addr_len, &next_hop, param);
+
+	if ((sd = socket(af, SOCK_STREAM, param->proto)) == -1)
+		return (-1);
+
+	/* DSR mode, need to set the next hop */
+	if (next_hop != NULL) {
+		if (af == AF_INET) {
+			if (setsockopt(sd, IPPROTO_IP, IP_NEXTHOP, next_hop,
+			    sizeof (ipaddr_t)) < 0) {
+				ret = -1;
+				goto out;
+			}
+		} else {
+			if (setsockopt(sd, IPPROTO_IPV6, IPV6_NEXTHOP,
+			    next_hop, sizeof (struct sockaddr_in6)) < 0) {
+				ret = -1;
+				goto out;
+			}
+		}
+	}
+
+	timeout_is_good = B_FALSE;
+	(void) alarm(param->timeout);
+	start = gethrtime();
+	if (connect(sd, (struct sockaddr *)&dst_addr, dst_addr_len) != 0) {
+		ret = -1;
+		goto out;
+	}
+	end = gethrtime();
+
+	rtt = (end - start) / (NANOSEC / MICROSEC);
+	if (rtt == 0)
+		rtt = 1;
+	(void) printf("%u", rtt);
+
+out:
+	(void) close(sd);
+	return (ret);
+}
+
+/*
+ * Check if the ICMP packet is a port unreachable message in respnsed to
+ * our probe.  Return -1 if no, 0 if yes.
+ */
+static int
+check_icmp_unreach_v4(struct icmp *icmph, probe_param_t *param)
+{
+	struct udphdr *udph;
+	struct ip *iph;
+
+	if (icmph->icmp_type != ICMP_UNREACH)
+		return (-1);
+	if (icmph->icmp_code != ICMP_UNREACH_PORT)
+		return (-1);
+
+	/* LINTED E_BAD_PTR_CAST_ALIGN */
+	iph = (struct ip *)((char *)icmph + ICMP_MINLEN);
+	if (iph->ip_p != IPPROTO_UDP)
+		return (-1);
+
+	/* LINTED E_BAD_PTR_CAST_ALIGN */
+	udph = (struct udphdr *)((char *)iph + (iph->ip_hl << 2));
+	if (udph->uh_dport != htons(param->port))
+		return (-1);
+	if (udph->uh_sport != param->lport)
+		return (-1);
+
+	/* All matched, it is a response to the probe we sent. */
+	return (0);
+}
+
+/*
+ * Check if the ICMP packet is a reply to our echo request.  Need to match
+ * the ID and sequence.
+ */
+static int
+check_icmp_echo_v4(struct icmp *icmph, probe_param_t *param)
+{
+	uint32_t cookie;
+	in_port_t port;
+
+	if (icmph->icmp_type != ICMP_ECHOREPLY)
+		return (-1);
+	if (icmph->icmp_id != param->echo_id)
+		return (-1);
+	if (icmph->icmp_seq != param->echo_seq)
+		return (-1);
+
+	bcopy(icmph->icmp_data, &cookie, sizeof (cookie));
+	if (cookie != param->echo_cookie)
+		return (-1);
+	bcopy(icmph->icmp_data + sizeof (cookie), &port, sizeof (port));
+	if (port != param->port)
+		return (-1);
+
+	/* All matched, it is a response to the echo we sent. */
+	return (0);
+}
+
+/* Verify if an ICMP packet is what we expect. */
+static int
+check_icmp_v4(char *buf, ssize_t rcvd, probe_param_t *param)
+{
+	struct ip *iph;
+	struct icmp *icmph;
+
+	/*
+	 * We can dereference the length field without worry since the stack
+	 * should not have sent up the packet if it is smaller than a normal
+	 * ICMPv4 packet.
+	 */
+	/* LINTED E_BAD_PTR_CAST_ALIGN */
+	iph = (struct ip *)buf;
+	/* LINTED E_BAD_PTR_CAST_ALIGN */
+	icmph = (struct icmp *)((char *)iph + (iph->ip_hl << 2));
+
+	/*
+	 * If we sent an UDP probe, check if the packet is a port
+	 * unreachable message in response to our probe.
+	 *
+	 * If we sent an ICMP echo request, check if the packet is a reply
+	 * to our echo request.
+	 */
+	if (param->probe == udp_probe) {
+		/* Is the packet large enough for further checking? */
+		if (rcvd < 2 * sizeof (struct ip) + ICMP_MINLEN +
+		    sizeof (struct udphdr)) {
+			return (-1);
+		}
+		return (check_icmp_unreach_v4(icmph, param));
+	} else {
+		if (rcvd < sizeof (struct ip) + ICMP_MINLEN)
+			return (-1);
+		return (check_icmp_echo_v4(icmph, param));
+	}
+}
+
+/*
+ * Check if the ICMPv6 packet is a port unreachable message in respnsed to
+ * our probe.  Return -1 if no, 0 if yes.
+ */
+static int
+check_icmp_unreach_v6(icmp6_t *icmp6h, probe_param_t *param)
+{
+	ip6_t *ip6h;
+	struct udphdr *udph;
+
+	if (icmp6h->icmp6_type != ICMP6_DST_UNREACH)
+		return (-1);
+	if (icmp6h->icmp6_code != ICMP6_DST_UNREACH_NOPORT)
+		return (-1);
+
+	/* LINTED E_BAD_PTR_CAST_ALIGN */
+	ip6h = (ip6_t *)((char *)icmp6h + ICMP6_MINLEN);
+	if (ip6h->ip6_nxt != IPPROTO_UDP)
+		return (-1);
+
+	udph = (struct udphdr *)(ip6h + 1);
+
+	if (udph->uh_dport != htons(param->port))
+		return (-1);
+	if (udph->uh_sport != param->lport)
+		return (-1);
+
+	return (0);
+}
+
+/*
+ * Check if the ICMPv6 packet is a reply to our echo request.  Need to match
+ * the ID and sequence.
+ */
+static int
+check_icmp_echo_v6(icmp6_t *icmp6h, probe_param_t *param)
+{
+	char *tmp;
+	uint32_t cookie;
+	in_port_t port;
+
+	if (icmp6h->icmp6_type != ICMP6_ECHO_REPLY)
+		return (-1);
+	if (icmp6h->icmp6_id != param->echo_id)
+		return (-1);
+	if (icmp6h->icmp6_seq != param->echo_seq)
+		return (-1);
+	tmp = (char *)icmp6h + ICMP6_MINLEN;
+	bcopy(tmp, &cookie, sizeof (cookie));
+	if (cookie != param->echo_cookie)
+		return (-1);
+	tmp += sizeof (cookie);
+	bcopy(tmp, &port, sizeof (port));
+	if (port != param->port)
+		return (-1);
+
+	/* All matched, it is a response to the echo we sent. */
+	return (0);
+}
+
+/* Verify if an ICMPv6 packet is what we expect. */
+static int
+check_icmp_v6(char *buf, ssize_t rcvd, probe_param_t *param)
+{
+	icmp6_t *icmp6h;
+
+	/* LINTED E_BAD_PTR_CAST_ALIGN */
+	icmp6h = (icmp6_t *)(buf);
+
+	/*
+	 * If we sent an UDP probe, check if the packet is a port
+	 * unreachable message.
+	 *
+	 * If we sent an ICMPv6 echo request, check if the packet is a reply.
+	 */
+	if (param->probe == udp_probe) {
+		/* Is the packet large enough for further checking? */
+		if (rcvd < sizeof (ip6_t) + ICMP6_MINLEN +
+		    sizeof (struct udphdr)) {
+			return (-1);
+		}
+		return (check_icmp_unreach_v6(icmp6h, param));
+	} else {
+		if (rcvd < ICMP6_MINLEN)
+			return (-1);
+		return (check_icmp_echo_v6(icmp6h, param));
+	}
+}
+
+/*
+ * Wait for an ICMP reply indefinitely.  If we get what we expect, return 0.
+ * If an error happnes, return -1.
+ */
+static int
+wait_icmp_reply(int af, int recv_sd, struct sockaddr_storage *exp_from,
+    probe_param_t *param)
+{
+	char buf[RECV_PKT_SZ];
+	socklen_t from_len;
+	ssize_t rcvd;
+	int ret;
+
+	for (;;) {
+		if (af == AF_INET) {
+			struct sockaddr_in v4_from;
+
+			from_len = sizeof (v4_from);
+			if ((rcvd = recvfrom(recv_sd, buf, RECV_PKT_SZ, 0,
+			    (struct sockaddr *)&v4_from, &from_len)) < 0) {
+				ret = -1;
+				break;
+			}
+
+			/* Packet not from our peer, ignore it. */
+			if ((((struct sockaddr_in *)exp_from)->sin_addr.s_addr)
+			    != v4_from.sin_addr.s_addr) {
+				continue;
+			}
+			if (check_icmp_v4(buf, rcvd, param) == 0) {
+				ret = 0;
+				break;
+			}
+		} else {
+			struct sockaddr_in6 v6_from;
+
+			from_len = sizeof (struct sockaddr_in6);
+			if ((rcvd = recvfrom(recv_sd, buf, RECV_PKT_SZ, 0,
+			    (struct sockaddr *)&v6_from, &from_len)) < 0) {
+				ret = -1;
+				break;
+			}
+
+			if (!IN6_ARE_ADDR_EQUAL(&(v6_from.sin6_addr),
+			    &((struct sockaddr_in6 *)exp_from)->sin6_addr)) {
+				continue;
+			}
+			if (check_icmp_v6(buf, rcvd, param) == 0) {
+				ret = 0;
+				break;
+			}
+		}
+	}
+	return (ret);
+}
+
+/* Return the local port used (network byte order) in a socket. */
+static int
+get_lport(int sd, in_port_t *lport)
+{
+	struct sockaddr_storage addr;
+	socklen_t addr_sz;
+
+	addr_sz = sizeof (addr);
+	if (getsockname(sd, (struct sockaddr *)&addr, &addr_sz) != 0)
+		return (-1);
+	if (addr.ss_family == AF_INET)
+		*lport = ((struct sockaddr_in *)&addr)->sin_port;
+	else
+		*lport = ((struct sockaddr_in6 *)&addr)->sin6_port;
+	return (0);
+}
+
+/*
+ * Use UDP to check if the peer server is alive.  Send a 0 length UDP packet
+ * to the peer server.  If there is no one listening, the peer IP stack
+ * should send back a port unreachable ICMP(v4/v6) packet.  If the peer
+ * server is alive, there should be no response.  So if we get SIGALRM,
+ * the peer is alive.
+ */
+static int
+udp_query(probe_param_t *param)
+{
+	int ret;
+	int send_sd, recv_sd, af;
+	struct sockaddr_storage dst_addr;
+	socklen_t addr_len;
+	void *next_hop;
+	char buf[1];
+	struct itimerval timeout;
+	uint64_t tm;
+
+	ret = 0;
+	next_hop = NULL;
+
+	af = set_sockaddr(&dst_addr, &addr_len, &next_hop, param);
+
+	if ((send_sd = socket(af, SOCK_DGRAM, param->proto)) == -1)
+		return (-1);
+	if ((recv_sd = socket(af, SOCK_RAW, (af == AF_INET) ? IPPROTO_ICMP :
+	    IPPROTO_ICMPV6)) == -1) {
+		return (-1);
+	}
+
+	/* DSR mode, need to set the next hop */
+	if (next_hop != NULL) {
+		if (af == AF_INET) {
+			if (setsockopt(send_sd, IPPROTO_IP, IP_NEXTHOP,
+			    next_hop, sizeof (ipaddr_t)) < 0) {
+				ret = -1;
+				goto out;
+			}
+		} else {
+			if (setsockopt(send_sd, IPPROTO_IPV6, IPV6_NEXTHOP,
+			    next_hop, sizeof (struct sockaddr_in6)) < 0) {
+				ret = -1;
+				goto out;
+			}
+		}
+	}
+
+	/*
+	 * If ilbd asks us to wait at most t, we will wait for at most
+	 * t', which is 3/4 of t.  If we wait for too long, ilbd may
+	 * timeout and kill us.
+	 */
+	timeout.it_interval.tv_sec = 0;
+	timeout.it_interval.tv_usec = 0;
+	tm = (param->timeout * MICROSEC >> 2) * 3;
+	if (tm > MICROSEC) {
+		timeout.it_value.tv_sec = tm / MICROSEC;
+		timeout.it_value.tv_usec = tm - (timeout.it_value.tv_sec *
+		    MICROSEC);
+	} else {
+		timeout.it_value.tv_sec = 0;
+		timeout.it_value.tv_usec = tm;
+	}
+	timeout_is_good = B_TRUE;
+	if (setitimer(ITIMER_REAL, &timeout, NULL) != 0) {
+		ret = -1;
+		goto out;
+	}
+
+	if (sendto(send_sd, buf, 0, 0, (struct sockaddr *)&dst_addr,
+	    addr_len) != 0) {
+		ret = -1;
+		goto out;
+	}
+	if ((ret = get_lport(send_sd, &param->lport)) != 0)
+		goto out;
+
+	/*
+	 * If the server app is listening, we should not get back a
+	 * response.  So if wait_icmp_reply() returns, either there
+	 * is an error or we get back something.
+	 */
+	(void) wait_icmp_reply(af, recv_sd, &dst_addr, param);
+	ret = -1;
+
+out:
+	(void) close(send_sd);
+	(void) close(recv_sd);
+	return (ret);
+}
+
+/*
+ * Size (in uint32_t) of the ping packet to be sent to server.  It includes
+ * a cookie (random number) + the target port.  The cookie and port are used
+ * for matching ping request since there can be many such ping packets sent
+ * to different servers from the same source address and using the same VIP.
+ * The last two bytes are for padding.
+ *
+ */
+#define	PING_PKT_LEN \
+	((ICMP_MINLEN + 2 * sizeof (uint32_t)) / sizeof (uint32_t))
+
+/*
+ * Try to get a random number from the pseudo random number device
+ * /dev/urandom.  If there is any error, return (uint32_t)gethrtime()
+ * as a back up.
+ */
+static uint32_t
+get_random(void)
+{
+	int fd;
+	uint32_t num;
+
+	if ((fd = open("/dev/urandom", O_RDONLY)) == -1)
+		return ((uint32_t)gethrtime());
+
+	if (read(fd, &num, sizeof (num)) != sizeof (num))
+		num = ((uint32_t)gethrtime());
+
+	(void) close(fd);
+	return (num);
+}
+
+/*
+ * Use ICMP(v4/v6) echo request to check if the peer server machine is
+ * reachable.  Send a echo request and expect to get back a echo reply.
+ */
+static int
+ping_query(probe_param_t *param)
+{
+	int ret;
+	int sd, af;
+	struct sockaddr_storage dst_addr;
+	socklen_t dst_addr_len;
+	void *next_hop;
+	hrtime_t start, end;
+	uint32_t rtt;
+	uint32_t buf[PING_PKT_LEN];
+	struct icmp *icmph;
+
+	ret = 0;
+	next_hop = NULL;
+
+	af = set_sockaddr(&dst_addr, &dst_addr_len, &next_hop, param);
+
+	if ((sd = socket(af, SOCK_RAW, (af == AF_INET) ? IPPROTO_ICMP :
+	    IPPROTO_ICMPV6)) == -1) {
+		return (-1);
+	}
+
+	/* DSR mode, need to set the next hop */
+	if (next_hop != NULL) {
+		if (af == AF_INET) {
+			if (setsockopt(sd, IPPROTO_IP, IP_NEXTHOP, next_hop,
+			    sizeof (ipaddr_t)) < 0) {
+				ret = -1;
+				goto out;
+			}
+		} else {
+			if (setsockopt(sd, IPPROTO_IPV6, IPV6_NEXTHOP,
+			    next_hop, sizeof (struct sockaddr_in6)) < 0) {
+				ret = -1;
+				goto out;
+			}
+		}
+	}
+
+	bzero(buf, sizeof (buf));
+	icmph = (struct icmp *)buf;
+	icmph->icmp_type = af == AF_INET ? ICMP_ECHO : ICMP6_ECHO_REQUEST;
+	icmph->icmp_code = 0;
+	icmph->icmp_cksum = 0;
+	icmph->icmp_id = htons(gethrtime() % USHRT_MAX);
+	icmph->icmp_seq = htons(gethrtime() % USHRT_MAX);
+
+	param->echo_cookie = get_random();
+	bcopy(&param->echo_cookie, icmph->icmp_data,
+	    sizeof (param->echo_cookie));
+	bcopy(&param->port, icmph->icmp_data + sizeof (param->echo_cookie),
+	    sizeof (param->port));
+	icmph->icmp_cksum = in_cksum((ushort_t *)buf, sizeof (buf));
+	param->echo_id = icmph->icmp_id;
+	param->echo_seq = icmph->icmp_seq;
+
+	timeout_is_good = B_FALSE;
+	(void) alarm(param->timeout);
+	start = gethrtime();
+	if (sendto(sd, buf, sizeof (buf), 0, (struct sockaddr *)&dst_addr,
+	    dst_addr_len) != sizeof (buf)) {
+		ret = -1;
+		goto out;
+	}
+	if (wait_icmp_reply(af, sd, &dst_addr, param) != 0) {
+		ret = -1;
+		goto out;
+	}
+	end = gethrtime();
+
+	rtt = (end - start) / (NANOSEC / MICROSEC);
+	if (rtt == 0)
+		rtt = 1;
+	(void) printf("%u", rtt);
+
+out:
+	(void) close(sd);
+	return (ret);
+}
+
+int
+main(int argc, char *argv[])
+{
+	probe_param_t param;
+	int ret;
+
+	/* ilbd should pass in PROG_ARGC parameters. */
+	if (argc != PROG_ARGC) {
+		(void) printf("-1");
+		return (-1);
+	}
+
+	if (signal(SIGALRM, probe_exit) == SIG_ERR) {
+		(void) printf("-1");
+		return (-1);
+	}
+
+	if (!parse_probe_param(argv, &param)) {
+		(void) printf("-1");
+		return (-1);
+	}
+
+	switch (param.probe) {
+	case ping_probe:
+		ret = ping_query(&param);
+		break;
+	case tcp_probe:
+		ret = tcp_query(&param);
+		break;
+	case udp_probe:
+		ret = udp_query(&param);
+		break;
+	}
+
+	if (ret == -1)
+		(void) printf("-1");
+
+	return (ret);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd.h	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,435 @@
+/*
+ * 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.
+ */
+#ifndef _ILBD_H
+#define	_ILBD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <ucred.h>
+#include <pwd.h>
+#include <priv.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <sys/list.h>
+#include <libscf.h>
+#include <libintl.h>
+#include <locale.h>
+#include <libinetutil.h>
+#include <auth_list.h>
+#include <bsm/adt.h>
+#include <bsm/adt_event.h>
+
+#define	SGNAME_SZ	80
+#define	ILB_FMRI	"svc:/network/loadbalancer/ilb:default"
+
+#define	HC_ACTION		ILB_SRV_DISABLED_HC
+#define	ADMIN_ACTION		ILB_SRV_DISABLED_ADMIN
+
+/* Max name and value length for scf properties */
+#define	ILBD_MAX_NAME_LEN	ilbd_scf_limit(SCF_LIMIT_MAX_NAME_LENGTH)
+#define	ILBD_MAX_VALUE_LEN	ilbd_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH)
+
+/* Different events ILBD is interested in. */
+typedef enum {
+	ILBD_EVENT_NEW_REQ,	/* New client request */
+	ILBD_EVENT_REQ,		/* Client request comes in */
+	ILBD_EVENT_REP_OK,	/* Reply channel to client is writeable */
+	ILBD_EVENT_PROBE,	/* A HC returns some result */
+	ILBD_EVENT_TIMER	/* ilbd_timer_q fired */
+} ilbd_event_t;
+
+typedef enum {
+	ILBD_SCF_RULE,	/* prop group for rules */
+	ILBD_SCF_SG,	/* prop group for servergroups */
+	ILBD_SCF_HC	/* prop group for healthchecks */
+} ilbd_scf_pg_type_t;
+
+typedef enum {
+	ILBD_SCF_CREATE,
+	ILBD_SCF_DESTROY,
+	ILBD_SCF_ENABLE_DISABLE
+} ilbd_scf_cmd_t;
+
+typedef enum {
+	ILBD_STRING,	/* string */
+	ILBD_INT,	/* int */
+	ILBD_ADDR_V4,	/* ipv4 addr */
+	ILBD_ADDR_V6	/* ipv6 addr */
+} ilbd_scf_data_type_t;
+
+typedef enum {
+	stat_enable_server,
+	stat_disable_server,
+	stat_declare_srv_dead,
+	stat_declare_srv_alive
+} ilbd_srv_status_ind_t;
+
+/*
+ * All user struct pointer passed to port_associate() should have the first
+ * field as ilbd_event_t.  The following struct can be used to find the
+ * event.
+ */
+typedef struct {
+	ilbd_event_t	ev;
+} ilbd_event_obj_t;
+
+typedef struct {
+	ilbd_event_t	ev;
+	timer_t		timerid;
+} ilbd_timer_event_obj_t;
+
+typedef struct ilbd_srv {
+	list_node_t	isv_srv_link;
+	ilb_sg_srv_t	isv_srv;
+#define	isv_addr	isv_srv.sgs_addr
+#define	isv_minport	isv_srv.sgs_minport
+#define	isv_maxport	isv_srv.sgs_maxport
+#define	isv_flags	isv_srv.sgs_flags
+#define	isv_id		isv_srv.sgs_id
+#define	isv_srvID	isv_srv.sgs_srvID
+} ilbd_srv_t;
+
+#define	MAX_SRVCOUNT	1000
+#define	MAX_SRVID	(MAX_SRVCOUNT - 1)
+#define	BAD_SRVID	(-1)
+
+typedef struct ilbd_sg {
+	list_t		isg_srvlist;	/* list of ilbd_srv_t */
+	char		isg_name[ILB_SGNAME_SZ];
+	int32_t		isg_srvcount;
+	int32_t		isg_max_id;
+	list_t		isg_rulelist;	/* list of ilbd_rule_t */
+	char		isg_id_arr[MAX_SRVCOUNT]; /* for server ID allocation */
+
+	list_node_t	isg_link;	/* linkage for sg list */
+} ilbd_sg_t;
+
+typedef struct ilbd_rule {
+	list_node_t		irl_link;
+	list_node_t		irl_sglink;
+	ilbd_sg_t		*irl_sg;
+	ilb_rule_info_t		irl_info;
+#define	irl_flags	irl_info.rl_flags
+#define	irl_name	irl_info.rl_name
+#define	irl_vip		irl_info.rl_vip
+#define	irl_proto	irl_info.rl_proto
+#define	irl_ipversion	irl_info.rl_ipversion
+#define	irl_minport	irl_info.rl_minport
+#define	irl_maxport	irl_info.rl_maxport
+#define	irl_algo	irl_info.rl_algo
+#define	irl_topo	irl_info.rl_topo
+#define	irl_nat_src_start	irl_info.rl_nat_src_start
+#define	irl_nat_src_end	irl_info.rl_nat_src_end
+#define	irl_stickymask	irl_info.rl_stickymask
+#define	irl_conndrain	irl_info.rl_conndrain
+#define	irl_nat_timeout	irl_info.rl_nat_timeout
+#define	irl_sticky_timeout	irl_info.rl_sticky_timeout
+#define	irl_hcport	irl_info.rl_hcport
+#define	irl_hcpflag	irl_info.rl_hcpflag
+#define	irl_sgname	irl_info.rl_sgname
+#define	irl_hcname	irl_info.rl_hcname
+} ilbd_rule_t;
+
+/*
+ * Health check related definitions
+ */
+
+/* Default health check probe program provided */
+#define	ILB_PROBE_PROTO	"/usr/lib/inet/ilb/ilb_probe"
+
+/* Command name (argv[0]) passed to ilb_probe to indicate a ping test */
+#define	ILB_PROBE_PING	"ilb_ping"
+
+/* Use the first character of the rule's hcname to decide if rule has HC. */
+#define	RULE_HAS_HC(irl)	((irl)->irl_info.rl_hcname[0] != '\0')
+
+/* Type of probe test */
+typedef enum {
+	ILBD_HC_PING = 1,	/* ICMP Echo probe */
+	ILBD_HC_TCP,		/* TCP connect probe */
+	ILBD_HC_UDP,		/* UDP packet probe */
+	ILBD_HC_USER		/* User supplied probe */
+} ilbd_hc_test_t;
+
+/* Struct representing a hc object in ilbd */
+typedef struct {
+	list_node_t	ihc_link;	/* List linkage */
+
+	ilb_hc_info_t	ihc_info;
+/* Short hand for the fields inside ilb_hc_info_t */
+#define	ihc_name	ihc_info.hci_name
+#define	ihc_test	ihc_info.hci_test
+#define	ihc_timeout	ihc_info.hci_timeout
+#define	ihc_count	ihc_info.hci_count
+#define	ihc_interval	ihc_info.hci_interval
+#define	ihc_def_ping	ihc_info.hci_def_ping
+
+	ilbd_hc_test_t	ihc_test_type;	/* Type of probe test */
+	int		ihc_rule_cnt;	/* Num of rules associated with hc */
+	list_t		ihc_rules;	/* Rules associated with this hc */
+} ilbd_hc_t;
+
+struct ilbd_hc_srv_s;
+
+/*
+ * Struct representing a hc rule object
+ *
+ * hcr_link: list linkage
+ * hcr_rule: pointer to the ilbd rule object
+ * hcr_servers: list of servers of this rule
+ */
+typedef struct {
+	list_node_t		hcr_link;
+	ilbd_rule_t const 	*hcr_rule;
+	list_t			hcr_servers;
+} ilbd_hc_rule_t;
+
+struct ilbd_hc_srv_s;
+
+/*
+ * Struct representing a event of the probe process
+ *
+ * ihp_ev: the event type, which is ILBD_EVENT_PROBE
+ * ihp_srv: pointer to the hc server object
+ * ihp_pid: pid of the probe process
+ * ihp_done: is ilbd done reading the output of the probe process
+ */
+typedef struct {
+	ilbd_event_t		ihp_ev;
+	struct ilbd_hc_srv_s	*ihp_srv;
+	pid_t			ihp_pid;
+	boolean_t		ihp_done;
+} ilbd_hc_probe_event_t;
+
+/*
+ * ilbd_hc_srv_t state
+ *
+ * ihd_hc_def_pinging: the default ping should be run
+ * ihd-hc_probing: the probe process should be started
+ */
+enum ilbd_hc_state {
+	ilbd_hc_def_pinging,
+	ilbd_hc_probing
+};
+
+/*
+ * Struct representing a server associated with a hc object
+ *
+ * shc_srv_link: list linkage
+ * shc_hc: pointer to the hc object
+ * shc_hc_rule: pointer to the hc rule object
+ * shc_sg_srv: pointer to the server group object
+ * shc_tid: timeout ID
+ * shc_cur_cnt: number of times the hc probe has been run
+ * shc_fail_cnt: number of consecutive probe failure
+ * shc_status: health status
+ * shc_rtt: rtt (in micro sec) to the backend server
+ * shc_lasttimer: last time a probe sequence is executed
+ * shc_nexttime: next time a probe sequence is executed
+ * shc_state: hc probe state
+ * shc_child_pid: pid of the probe process
+ * shc_child_fd: fd to the output of the probe process
+ * shc_ev: event object of the probe process
+ * shc_ev_port: event port of the event object
+ */
+typedef struct ilbd_hc_srv_s {
+	list_node_t		shc_srv_link;
+	ilbd_hc_t		*shc_hc;
+	ilbd_hc_rule_t		*shc_hc_rule;
+	ilb_sg_srv_t const	*shc_sg_srv;
+
+	iu_timer_id_t		shc_tid;
+	uint_t			shc_cur_cnt;
+	uint_t			shc_fail_cnt;
+	ilb_hc_srv_status_t	shc_status;
+	uint32_t		shc_rtt;
+	time_t			shc_lasttime;
+	time_t			shc_nexttime;
+
+	enum ilbd_hc_state	shc_state;
+	pid_t			shc_child_pid;
+	int			shc_child_fd;
+	ilbd_hc_probe_event_t	*shc_ev;
+	int			shc_ev_port;
+} ilbd_hc_srv_t;
+
+/*
+ * Structure for holding audit server and servergroup event
+ * data. Not all events use all members of the structure.
+ */
+typedef struct audit_sg_event_data {
+	char	*ed_server_address;	/* server's IP address */
+	char	*ed_serverid;   /* serverid. */
+	uint16_t	ed_minport;	/* server's minport */
+	uint16_t	ed_maxport;	/* server's maxport */
+	char		*ed_sgroup;	/* servergroup */
+} audit_sg_event_data_t;
+
+/* Struct to store client info */
+typedef struct {
+	ilbd_event_t	cli_ev;
+	int	cli_sd;
+	struct passwd	cli_pw;
+	size_t		cli_pw_bufsz;
+	char		*cli_pw_buf;
+	ilbd_cmd_t	cli_cmd;
+	ilb_comm_t	*cli_saved_reply;
+	size_t		cli_saved_size;
+	ucred_t		*cli_peer_ucredp; /* needed for auditing */
+} ilbd_client_t;
+
+void		ilbd_reply_ok(uint32_t *, size_t *);
+void		ilbd_reply_err(uint32_t *, size_t *, ilb_status_t);
+
+ilb_status_t	ilbd_check_client_config_auth(const struct passwd *);
+ilb_status_t	ilbd_check_client_enable_auth(const struct passwd *);
+ilb_status_t	ilbd_retrieve_names(ilbd_cmd_t, uint32_t *, size_t *);
+void		i_setup_sg_hlist(void);
+void		i_setup_rule_hlist(void);
+void		logperror(const char *);
+ilb_status_t	ilbd_add_server_to_group(ilb_sg_info_t *, int,
+	const struct passwd *, ucred_t *);
+ilb_status_t	ilbd_rem_server_from_group(ilb_sg_info_t *, int,
+	const struct passwd *, ucred_t *);
+ilb_status_t	ilbd_create_sg(ilb_sg_info_t *, int,
+	const struct passwd *, ucred_t *);
+
+ilb_status_t	ilbd_destroy_sg(const char *, const struct passwd *,
+		ucred_t *);
+ilb_status_t	ilbd_retrieve_sg_hosts(const char *, uint32_t *, size_t *);
+
+ilb_status_t	ilbd_enable_server(ilb_sg_info_t *, const struct passwd *,
+		ucred_t *);
+ilb_status_t	ilbd_disable_server(ilb_sg_info_t *, const struct passwd *,
+		ucred_t *);
+ilb_status_t	ilbd_k_Xable_server(const struct in6_addr *, const char *,
+		    ilbd_srv_status_ind_t);
+
+ilb_status_t	i_add_srv2krules(list_t *, ilb_sg_srv_t *, int);
+ilb_status_t	i_rem_srv_frm_krules(list_t *, ilb_sg_srv_t *, int);
+int		ilbd_get_num_krules(void);
+ilb_status_t	ilbd_get_krule_names(ilbd_namelist_t **, int);
+ilb_status_t	ilb_get_krule_servers(ilb_sg_info_t *);
+ilbd_sg_t	*i_find_sg_byname(const char *);
+ilb_status_t	i_check_srv2rules(list_t *, ilb_sg_srv_t *);
+
+ilb_status_t	ilbd_address_to_srvID(ilb_sg_info_t *, uint32_t *, size_t *);
+ilb_status_t	ilbd_srvID_to_address(ilb_sg_info_t *, uint32_t *, size_t *);
+
+ilb_status_t	do_ioctl(void *, ssize_t);
+
+ilb_status_t	ilbd_create_rule(ilb_rule_info_t *, int, const struct passwd *,
+		ucred_t *);
+ilb_status_t	ilbd_retrieve_rule(ilbd_name_t, uint32_t *, size_t *);
+
+ilb_status_t	ilbd_destroy_rule(ilbd_name_t, const struct passwd *,
+		ucred_t *);
+ilb_status_t	ilbd_enable_rule(ilbd_name_t, const struct passwd *, ucred_t *);
+ilb_status_t	ilbd_disable_rule(ilbd_name_t, const struct passwd *,
+		ucred_t *);
+
+boolean_t	is_debugging_on(void);
+ilb_status_t	ilbd_sg_check_rule_port(ilbd_sg_t *, ilb_rule_info_t *);
+
+void		ilbd_enable_debug(void);
+ilb_status_t	ilb_map_errno2ilbstat(int);
+
+ilb_status_t	i_attach_rule2sg(ilbd_sg_t *, ilbd_rule_t *);
+
+/* Logging routine and macros */
+void		ilbd_log(int, const char *, ...);
+#define	logerr(...)	ilbd_log(LOG_ERR, __VA_ARGS__)
+#define	logdebug(...)	ilbd_log(LOG_DEBUG, __VA_ARGS__)
+
+/* Health check manipulation routines */
+void		i_ilbd_setup_hc_list(void);
+ilb_status_t	ilbd_create_hc(const ilb_hc_info_t *, int,
+		    const struct passwd *, ucred_t *);
+ilb_status_t	ilbd_destroy_hc(const char *, const struct passwd *, ucred_t *);
+ilbd_hc_t	*ilbd_get_hc(const char *);
+ilb_status_t	ilbd_get_hc_info(const char *, uint32_t *, size_t *);
+ilb_status_t	ilbd_get_hc_srvs(const char *, uint32_t *, size_t *);
+ilb_status_t	ilbd_hc_associate_rule(const ilbd_rule_t *, int);
+ilb_status_t	ilbd_hc_dissociate_rule(const ilbd_rule_t *);
+ilb_status_t	ilbd_hc_add_server(const ilbd_rule_t *, const ilb_sg_srv_t *,
+		    int);
+ilb_status_t	ilbd_hc_del_server(const ilbd_rule_t *, const ilb_sg_srv_t *);
+ilb_status_t	ilbd_hc_enable_rule(const ilbd_rule_t *);
+ilb_status_t	ilbd_hc_disable_rule(const ilbd_rule_t *);
+ilb_status_t	ilbd_hc_enable_server(const ilbd_rule_t *,
+		    const ilb_sg_srv_t *);
+ilb_status_t	ilbd_hc_disable_server(const ilbd_rule_t *,
+		    const ilb_sg_srv_t *);
+
+/* Health check timer routines */
+void		ilbd_hc_probe_return(int, int, int, ilbd_hc_probe_event_t *);
+void		ilbd_hc_timer_init(int, ilbd_timer_event_obj_t *);
+void		ilbd_hc_timeout(void);
+void		ilbd_hc_timer_update(ilbd_timer_event_obj_t *);
+
+/* Show NAT info routines */
+ilb_status_t	ilbd_show_nat(void *, const ilb_comm_t *, uint32_t *,
+		    size_t *);
+void		ilbd_show_nat_cleanup(void);
+
+
+/* Show sticky info routines */
+ilb_status_t	ilbd_show_sticky(void *, const ilb_comm_t *, uint32_t *,
+		    size_t *);
+void		ilbd_show_sticky_cleanup(void);
+
+ilb_status_t	ilbd_create_pg(ilbd_scf_pg_type_t, void *);
+ilb_status_t	ilbd_destroy_pg(ilbd_scf_pg_type_t, const char *);
+ilb_status_t	ilbd_change_prop(ilbd_scf_pg_type_t, const char *,
+		    const char *, void *);
+void		ilbd_scf_str_to_ip(int, char *, struct in6_addr *);
+ilb_status_t	ilbd_scf_ip_to_str(uint16_t, struct in6_addr *, scf_type_t *,
+		    char *);
+ilb_status_t	ilbd_scf_add_srv(ilbd_sg_t *, ilbd_srv_t *);
+ilb_status_t	ilbd_scf_del_srv(ilbd_sg_t *, ilbd_srv_t *);
+int		ilbd_scf_limit(int);
+
+ilb_status_t	ilbd_walk_rule_pgs(ilb_status_t (*)(ilb_rule_info_t *, int,
+		    const struct passwd *, ucred_t *), void *, void *);
+ilb_status_t	ilbd_walk_sg_pgs(ilb_status_t (*)(ilb_sg_info_t *, int,
+		    const struct passwd *, ucred_t *), void *, void *);
+ilb_status_t	ilbd_walk_hc_pgs(ilb_status_t (*)(const ilb_hc_info_t *, int,
+		    const struct passwd *, ucred_t *), void *, void *);
+void		ilbd_addr2str(struct in6_addr *, char *, size_t);
+void		addr2str(ilb_ip_addr_t, char *, size_t);
+void		ilbd_algo_to_str(ilb_algo_t, char *);
+void		ilbd_topo_to_str(ilb_topo_t, char *);
+void		ilbd_ip_to_str(uint16_t, struct in6_addr *, char *);
+int		ilberror2auditerror(ilb_status_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ILBD_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd.xml	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+<!--
+ Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+ Use is subject to license terms.
+
+ 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
+
+	NOTE:  This service manifest is not editable; its contents will
+	be overwritten by package or patch operations, including
+	operating system upgrade.  Make customizations in a different
+	file.
+
+	Service manifest for Integrated Load balancer(ILB).
+-->
+
+<service_bundle type='manifest' name='SUNWilbr:ilb'>
+
+<service
+	name='network/loadbalancer/ilb'
+	type='service'
+	version='1'>
+
+	<create_default_instance enabled='false' />
+
+	<single_instance />
+
+	<!--
+	  Ensure that name services is enabled before ILB service begins.
+	-->
+	<dependency
+		name='name-services'
+		grouping='require_all'
+		restart_on='error'
+		type='service'>
+		<service_fmri value='svc:/milestone/name-services' />
+	</dependency>
+
+
+	<!--
+	  Ensure that forwarding service is enabled before load balancing
+	  service begins. The forwarding service is dependent on
+	  the filesystem and devices services.
+	-->
+	<dependency
+		name='network'
+		grouping='require_any'
+		restart_on='error'
+		type='service'>
+		<service_fmri value='svc:/network/ipv4-forwarding' />
+		<service_fmri value='svc:/network/ipv6-forwarding' />
+	</dependency>
+
+	<exec_method
+		type='method'
+		name='stop'
+		exec=':kill'
+		timeout_seconds='60' >
+	</exec_method>
+
+	<exec_method
+		type='method'
+		name='start'
+		exec='/usr/lib/inet/ilbd'
+		timeout_seconds='60' >
+		<method_context>
+			<method_credential user='root' group='root' />
+		</method_context>
+	</exec_method>
+
+        <!-- to start stop routing services -->
+        <property_group name='general' type='framework'>
+                <propval name='action_authorization' type='astring'
+                        value='solaris.smf.manage.ilb' />
+                <propval name='value_authorization' type='astring'
+                        value='solaris.smf.manage.ilb' />
+        </property_group>
+
+
+	<stability value='Unstable' />
+
+	<template>
+
+	<common_name>
+		<loctext xml:lang='C'> Integrated layer 3/4 load balancer
+		</loctext>
+	</common_name>
+	<documentation>
+		<manpage title='ilbd' section='1M'
+			manpath='/usr/share/man' />
+		<manpage title='ilbadm' section='1M'
+			manpath='/usr/share/man' />
+	</documentation>
+
+	</template>
+</service>
+
+</service_bundle>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_hc.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,1574 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/list.h>
+#include <sys/stropts.h>
+#include <sys/siginfo.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#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 */
+list_t ilbd_hc_list;
+
+/* Timer queue for all hc related timers. */
+static iu_tq_t *ilbd_hc_timer_q;
+
+/* Indicate whether the timer needs to be updated */
+static boolean_t hc_timer_restarted;
+
+static void ilbd_hc_probe_timer(iu_tq_t *, void *);
+static ilb_status_t ilbd_hc_restart_timer(ilbd_hc_t *, ilbd_hc_srv_t *);
+static boolean_t ilbd_run_probe(ilbd_hc_srv_t *);
+
+#define	MAX(a, b)	((a) > (b) ? (a) : (b))
+
+/*
+ * Number of arguments passed to a probe.  argc[0] is the path name of
+ * the probe.
+ */
+#define	HC_PROBE_ARGC	8
+
+/*
+ * Max number of characters to be read from the output of a probe.  It
+ * is long enough to read in a 64 bit integer.
+ */
+#define	HC_MAX_PROBE_OUTPUT	24
+
+void
+i_ilbd_setup_hc_list(void)
+{
+	list_create(&ilbd_hc_list, sizeof (ilbd_hc_t),
+	    offsetof(ilbd_hc_t, ihc_link));
+}
+
+/*
+ * Given a hc object name, return a pointer to hc object if found.
+ */
+ilbd_hc_t *
+ilbd_get_hc(const char *name)
+{
+	ilbd_hc_t *hc;
+
+	for (hc = list_head(&ilbd_hc_list); hc != NULL;
+	    hc = list_next(&ilbd_hc_list, hc)) {
+		if (strcasecmp(hc->ihc_name, name) == 0)
+			return (hc);
+	}
+	return (NULL);
+}
+
+/*
+ * Generates an audit record for create-healthcheck,
+ * delete-healtcheck subcommands.
+ */
+static void
+ilbd_audit_hc_event(const char *audit_hcname,
+    const ilb_hc_info_t *audit_hcinfo, 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_CREATE_HC))  {
+		/*
+		 * 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;
+	}
+
+	if (adt_start_session(&ah, NULL, 0) != 0) {
+		logerr("ilbd_audit_hc_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_CREATE_HC)
+		flag = ADT_ilb_create_healthcheck;
+	else if (cmd == ILBD_DESTROY_HC)
+		flag = ADT_ilb_delete_healthcheck;
+
+	if ((event = adt_alloc_event(ah, flag)) == NULL) {
+		logerr("ilbd_audit_hc_event: adt_alloc_event failed");
+		exit(EXIT_FAILURE);
+	}
+	(void) memset((char *)event, 0, sizeof (adt_event_data_t));
+
+	switch (cmd) {
+	case ILBD_CREATE_HC:
+		event->adt_ilb_create_healthcheck.auth_used =
+		    NET_ILB_CONFIG_AUTH;
+		event->adt_ilb_create_healthcheck.hc_test =
+		    (char *)audit_hcinfo->hci_test;
+		event->adt_ilb_create_healthcheck.hc_name =
+		    (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
+		 */
+		event->adt_ilb_create_healthcheck.hc_timeout =
+		    audit_hcinfo->hci_timeout;
+		event->adt_ilb_create_healthcheck.hc_count =
+		    audit_hcinfo->hci_count;
+		event->adt_ilb_create_healthcheck.hc_interval =
+		    audit_hcinfo->hci_interval;
+		break;
+	case ILBD_DESTROY_HC:
+		event->adt_ilb_delete_healthcheck.auth_used =
+		    NET_ILB_CONFIG_AUTH;
+		event->adt_ilb_delete_healthcheck.hc_name =
+		    (char *)audit_hcname;
+		break;
+	}
+
+	/* Fill in success/failure */
+	if (rc == ILB_STATUS_OK) {
+		if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
+			logerr("ilbd_audit_hc_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_hc_event: adt_put_event failed");
+			exit(EXIT_FAILURE);
+		}
+	}
+	adt_free_event(event);
+	(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 */
+ilb_status_t
+ilbd_create_hc(const ilb_hc_info_t *hc_info, int ev_port,
+    const struct passwd *ps, ucred_t *ucredp)
+{
+	ilbd_hc_t *hc;
+	ilb_status_t ret = ILB_STATUS_OK;
+
+	/*
+	 * ps == NULL is from the daemon when it starts and load configuration
+	 * ps != NULL is from client.
+	 */
+	if (ps != NULL) {
+		ret = ilbd_check_client_config_auth(ps);
+		if (ret != ILB_STATUS_OK) {
+			ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC,
+			    ret, ucredp);
+			return (ret);
+		}
+	}
+
+	if (hc_info->hci_name[0] == '\0') {
+		logdebug("ilbd_create_hc: missing healthcheck info");
+		ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC,
+		    ILB_STATUS_ENOHCINFO, ucredp);
+		return (ILB_STATUS_ENOHCINFO);
+	}
+
+	hc = ilbd_get_hc(hc_info->hci_name);
+	if (hc != NULL) {
+		logdebug("ilbd_create_hc: healthcheck name %s already"
+		    " exists", hc_info->hci_name);
+		ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC,
+		    ILB_STATUS_EEXIST, ucredp);
+		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.
+	 */
+	if (strcasecmp(hc_info->hci_test, ILB_HC_STR_TCP) != 0 &&
+	    strcasecmp(hc_info->hci_test, ILB_HC_STR_UDP) != 0 &&
+	    strcasecmp(hc_info->hci_test, ILB_HC_STR_PING) != 0 &&
+	    (hc_info->hci_test[0] != '/' ||
+	    access(hc_info->hci_test, X_OK) == -1)) {
+		if (errno == ENOENT) {
+			logdebug("ilbd_create_hc: user script %s doesn't "
+			    "exist", hc_info->hci_test);
+			ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC,
+			    ILB_STATUS_ENOENT, ucredp);
+			return (ILB_STATUS_ENOENT);
+		} else {
+			logdebug("ilbd_create_hc: user script %s is "
+			    "invalid", hc_info->hci_test);
+			ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC,
+			    ILB_STATUS_EINVAL, ucredp);
+			return (ILB_STATUS_EINVAL);
+		}
+	}
+
+	/* Create and add the hc object */
+	hc = calloc(1, sizeof (ilbd_hc_t));
+	if (hc == NULL) {
+		ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC,
+		    ILB_STATUS_ENOMEM, ucredp);
+		return (ILB_STATUS_ENOMEM);
+	}
+	(void) memcpy(&hc->ihc_info, hc_info, sizeof (ilb_hc_info_t));
+	if (strcasecmp(hc->ihc_test, ILB_HC_STR_TCP) == 0)
+		hc->ihc_test_type = ILBD_HC_TCP;
+	else if (strcasecmp(hc->ihc_test, ILB_HC_STR_UDP) == 0)
+		hc->ihc_test_type = ILBD_HC_UDP;
+	else if (strcasecmp(hc->ihc_test, ILB_HC_STR_PING) == 0)
+		hc->ihc_test_type = ILBD_HC_PING;
+	else
+		hc->ihc_test_type = ILBD_HC_USER;
+	list_create(&hc->ihc_rules, sizeof (ilbd_hc_rule_t),
+	    offsetof(ilbd_hc_rule_t, hcr_link));
+
+	/* Update SCF */
+	if (ps != NULL) {
+		if ((ret = ilbd_create_pg(ILBD_SCF_HC, (void *)hc)) !=
+		    ILB_STATUS_OK) {
+			ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC,
+			    ret, ucredp);
+			free(hc);
+			return (ret);
+		}
+	}
+
+	/* Everything is fine, now add it to the global list. */
+	list_insert_tail(&ilbd_hc_list, hc);
+	ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC, ret, ucredp);
+	return (ret);
+}
+
+/*
+ * Given a name of a hc object, destroy it.
+ */
+ilb_status_t
+ilbd_destroy_hc(const char *hc_name, const struct passwd *ps,
+    ucred_t *ucredp)
+{
+	ilb_status_t ret;
+	ilbd_hc_t *hc;
+
+	/*
+	 * No need to check ps == NULL, daemon won't call any destroy func
+	 * at start up.
+	 */
+	ret = ilbd_check_client_config_auth(ps);
+	if (ret != ILB_STATUS_OK) {
+		ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC,
+		    ret, ucredp);
+		return (ret);
+	}
+
+	hc = ilbd_get_hc(hc_name);
+	if (hc == NULL) {
+		logdebug("ilbd_destroy_hc: healthcheck %s does not exist",
+		    hc_name);
+		ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC,
+		    ILB_STATUS_ENOENT, ucredp);
+		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);
+		ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC,
+		    ILB_STATUS_INUSE, ucredp);
+		return (ILB_STATUS_INUSE);
+	}
+
+	if ((ret = ilbd_destroy_pg(ILBD_SCF_HC, hc_name)) !=
+	    ILB_STATUS_OK) {
+		logdebug("ilbd_destroy_hc: cannot destroy healthcheck %s "
+		    "property group", hc_name);
+		ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC,
+		    ret, ucredp);
+		return (ret);
+	}
+
+	list_remove(&ilbd_hc_list, hc);
+	free(hc);
+	ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC, ret, ucredp);
+	return (ret);
+}
+
+/*
+ * Given a hc object name, return its information.  Used by libilb to
+ * get hc info.
+ */
+ilb_status_t
+ilbd_get_hc_info(const char *hc_name, uint32_t *rbuf, size_t *rbufsz)
+{
+	ilbd_hc_t	*hc;
+	ilb_hc_info_t	*hc_info;
+	ilb_comm_t	*ic = (ilb_comm_t *)rbuf;
+
+	hc = ilbd_get_hc(hc_name);
+	if (hc == NULL) {
+		logdebug("%s: healthcheck %s does not exist", __func__,
+		    hc_name);
+		return (ILB_STATUS_ENOENT);
+	}
+	ilbd_reply_ok(rbuf, rbufsz);
+	hc_info = (ilb_hc_info_t *)&ic->ic_data;
+
+	(void) strlcpy(hc_info->hci_name, hc->ihc_name, sizeof (hc->ihc_name));
+	(void) strlcpy(hc_info->hci_test, hc->ihc_test, sizeof (hc->ihc_test));
+	hc_info->hci_timeout = hc->ihc_timeout;
+	hc_info->hci_count = hc->ihc_count;
+	hc_info->hci_interval = hc->ihc_interval;
+	hc_info->hci_def_ping = hc->ihc_def_ping;
+
+	*rbufsz += sizeof (ilb_hc_info_t);
+
+	return (ILB_STATUS_OK);
+}
+
+static void
+ilbd_hc_copy_srvs(uint32_t *rbuf, size_t *rbufsz, ilbd_hc_rule_t *hc_rule,
+    const char *rulename)
+{
+	ilbd_hc_srv_t		*tmp_srv;
+	ilb_hc_srv_t		*dst_srv;
+	ilb_hc_rule_srv_t	*srvs;
+	size_t			tmp_rbufsz;
+	int			i;
+
+	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 server info. */
+	*rbufsz += sizeof (ilb_hc_rule_srv_t);
+	tmp_rbufsz -= *rbufsz;
+
+	srvs = (ilb_hc_rule_srv_t *)&((ilb_comm_t *)rbuf)->ic_data;
+
+	tmp_srv = list_head(&hc_rule->hcr_servers);
+	for (i = 0; tmp_srv != NULL && tmp_rbufsz >= sizeof (*dst_srv); i++) {
+		dst_srv = &srvs->rs_srvs[i];
+
+		(void) strlcpy(dst_srv->hcs_rule_name, rulename, ILB_NAMESZ);
+		(void) strlcpy(dst_srv->hcs_ID, tmp_srv->shc_sg_srv->sgs_srvID,
+		    ILB_NAMESZ);
+		(void) strlcpy(dst_srv->hcs_hc_name,
+		    tmp_srv->shc_hc->ihc_name, ILB_NAMESZ);
+		dst_srv->hcs_IP = tmp_srv->shc_sg_srv->sgs_addr;
+		dst_srv->hcs_fail_cnt = tmp_srv->shc_fail_cnt;
+		dst_srv->hcs_status = tmp_srv->shc_status;
+		dst_srv->hcs_rtt = tmp_srv->shc_rtt;
+		dst_srv->hcs_lasttime = tmp_srv->shc_lasttime;
+		dst_srv->hcs_nexttime = tmp_srv->shc_nexttime;
+
+		tmp_srv = list_next(&hc_rule->hcr_servers, tmp_srv);
+		tmp_rbufsz -= sizeof (*dst_srv);
+	}
+	srvs->rs_num_srvs = i;
+	*rbufsz += i * sizeof (*dst_srv);
+}
+
+/*
+ * Given a rule name, return the hc status of its servers.
+ */
+ilb_status_t
+ilbd_get_hc_srvs(const char *rulename, uint32_t *rbuf, size_t *rbufsz)
+{
+	ilbd_hc_t	*hc;
+	ilbd_hc_rule_t	*hc_rule;
+
+	for (hc = list_head(&ilbd_hc_list); hc != NULL;
+	    hc = list_next(&ilbd_hc_list, hc)) {
+		for (hc_rule = list_head(&hc->ihc_rules); hc_rule != NULL;
+		    hc_rule = list_next(&hc->ihc_rules, hc_rule)) {
+			if (strcasecmp(hc_rule->hcr_rule->irl_name,
+			    rulename) != 0) {
+				continue;
+			}
+			ilbd_hc_copy_srvs(rbuf, rbufsz, hc_rule, rulename);
+			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
+ilbd_hc_timer_init(int ev_port, ilbd_timer_event_obj_t *ev_obj)
+{
+	struct sigevent sigev;
+	port_notify_t notify;
+
+	if ((ilbd_hc_timer_q = iu_tq_create()) == NULL) {
+		logerr("%s: cannot create hc timer queue", __func__);
+		exit(EXIT_FAILURE);
+	}
+	hc_timer_restarted = B_FALSE;
+
+	ev_obj->ev = ILBD_EVENT_TIMER;
+	ev_obj->timerid = -1;
+
+	notify.portnfy_port = ev_port;
+	notify.portnfy_user = ev_obj;
+	sigev.sigev_notify = SIGEV_PORT;
+	sigev.sigev_value.sival_ptr = &notify;
+	if (timer_create(CLOCK_REALTIME, &sigev, &ev_obj->timerid) == -1) {
+		logerr("%s: cannot create timer", __func__);
+		exit(EXIT_FAILURE);
+	}
+}
+
+/*
+ * HC timeout handler.
+ */
+void
+ilbd_hc_timeout(void)
+{
+	(void) iu_expire_timers(ilbd_hc_timer_q);
+	hc_timer_restarted = B_TRUE;
+}
+
+/*
+ * Set up the timer to fire at the earliest timeout.
+ */
+void
+ilbd_hc_timer_update(ilbd_timer_event_obj_t *ev_obj)
+{
+	itimerspec_t itimeout;
+	int timeout;
+
+	/*
+	 * There is no change on the timer list, so no need to set up the
+	 * timer again.
+	 */
+	if (!hc_timer_restarted)
+		return;
+
+restart:
+	if ((timeout = iu_earliest_timer(ilbd_hc_timer_q)) == INFTIM) {
+		hc_timer_restarted = B_FALSE;
+		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;
+	}
+
+	itimeout.it_value.tv_sec = timeout / MILLISEC + 1;
+	itimeout.it_value.tv_nsec = 0;
+	itimeout.it_interval.tv_sec = 0;
+	itimeout.it_interval.tv_nsec = 0;
+
+	/*
+	 * 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...
+	 */
+	if (timer_settime(ev_obj->timerid, 0, &itimeout, NULL) == -1)
+		logerr("%s: cannot set timer", __func__);
+	hc_timer_restarted = B_FALSE;
+}
+
+/*
+ * Kill the probe process of a server.
+ */
+static void
+ilbd_hc_kill_probe(ilbd_hc_srv_t *srv)
+{
+	/*
+	 * First dissociate the fd from the event port.  It should not
+	 * fail.
+	 */
+	if (port_dissociate(srv->shc_ev_port, PORT_SOURCE_FD,
+	    srv->shc_child_fd) != 0) {
+		logdebug("%s: port_dissociate: %s", __func__, strerror(errno));
+	}
+	(void) close(srv->shc_child_fd);
+	free(srv->shc_ev);
+	srv->shc_ev = NULL;
+
+	/* Then kill the probe process. */
+	if (kill(srv->shc_child_pid, SIGKILL) != 0) {
+		logerr("%s: rule %s server %s: %s", __func__,
+		    srv->shc_hc_rule->hcr_rule->irl_name,
+		    srv->shc_sg_srv->sgs_srvID, strerror(errno));
+	}
+	/* Should not fail... */
+	if (waitpid(srv->shc_child_pid, NULL, 0) != srv->shc_child_pid) {
+		logdebug("%s: waitpid: rule %s server %s", __func__,
+		    srv->shc_hc_rule->hcr_rule->irl_name,
+		    srv->shc_sg_srv->sgs_srvID);
+	}
+	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
+ilbd_mark_server_disabled(ilbd_hc_srv_t *srv)
+{
+	srv->shc_status = ILB_HCS_DISABLED;
+
+	/* Disable the server in kernel. */
+	if (ilbd_k_Xable_server(&srv->shc_sg_srv->sgs_addr,
+	    srv->shc_hc_rule->hcr_rule->irl_name,
+	    stat_declare_srv_dead) != ILB_STATUS_OK) {
+		logerr("%s: cannot disable server in kernel: rule %s "
+		    "server %s", __func__,
+		    srv->shc_hc_rule->hcr_rule->irl_name,
+		    srv->shc_sg_srv->sgs_srvID);
+	}
+}
+
+/*
+ * A probe fails, set the state of the server.
+ */
+static void
+ilbd_set_fail_state(ilbd_hc_srv_t *srv)
+{
+	if (++srv->shc_fail_cnt < srv->shc_hc->ihc_count) {
+		/* Probe again */
+		ilbd_hc_probe_timer(ilbd_hc_timer_q, srv);
+		return;
+	}
+
+	logdebug("%s: rule %s server %s fails %u", __func__,
+	    srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID,
+	    srv->shc_fail_cnt);
+
+	/*
+	 * If this is a ping test, mark the server as
+	 * unreachable instead of dead.
+	 */
+	if (srv->shc_hc->ihc_test_type == ILBD_HC_PING ||
+	    srv->shc_state == ilbd_hc_def_pinging) {
+		srv->shc_status = ILB_HCS_UNREACH;
+	} else {
+		srv->shc_status = ILB_HCS_DEAD;
+	}
+
+	/* Disable the server in kernel. */
+	if (ilbd_k_Xable_server(&srv->shc_sg_srv->sgs_addr,
+	    srv->shc_hc_rule->hcr_rule->irl_name, stat_declare_srv_dead) !=
+	    ILB_STATUS_OK) {
+		logerr("%s: cannot disable server in kernel: rule %s "
+		    "server %s", __func__,
+		    srv->shc_hc_rule->hcr_rule->irl_name,
+		    srv->shc_sg_srv->sgs_srvID);
+	}
+
+	/* Still keep probing in case the server is alive again. */
+	if (ilbd_hc_restart_timer(srv->shc_hc, srv) != ILB_STATUS_OK) {
+		/* Only thing to do is to disable the server... */
+		logerr("%s: cannot restart timer: rule %s server %s", __func__,
+		    srv->shc_hc_rule->hcr_rule->irl_name,
+		    srv->shc_sg_srv->sgs_srvID);
+		srv->shc_status = ILB_HCS_DISABLED;
+	}
+}
+
+/*
+ * 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
+ilbd_hc_kill_timer(iu_tq_t *tq, void *arg)
+{
+	ilbd_hc_srv_t *srv = (ilbd_hc_srv_t *)arg;
+
+	ilbd_hc_kill_probe(srv);
+	ilbd_set_fail_state(srv);
+}
+
+/*
+ * Probe timeout handler.  Send out the appropriate probe.
+ */
+/* ARGSUSED */
+static void
+ilbd_hc_probe_timer(iu_tq_t *tq, void *arg)
+{
+	ilbd_hc_srv_t *srv = (ilbd_hc_srv_t *)arg;
+
+	/*
+	 * 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.
+		 */
+		if (ilbd_hc_restart_timer(srv->shc_hc, srv) != ILB_STATUS_OK) {
+			logerr("%s: cannot restart timer: rule %s server %s, "
+			    "disabling it", __func__,
+			    srv->shc_hc_rule->hcr_rule->irl_name,
+			    srv->shc_sg_srv->sgs_srvID);
+			ilbd_mark_server_disabled(srv);
+		}
+		return;
+	}
+
+	/*
+	 * Similar to above, if kill timer cannot be started, disable the
+	 * server.
+	 */
+	if ((srv->shc_tid = iu_schedule_timer(ilbd_hc_timer_q,
+	    srv->shc_hc->ihc_timeout, ilbd_hc_kill_timer, srv)) == -1) {
+		logerr("%s: cannot start kill timer: rule %s server %s, "
+		    "disabling it", __func__,
+		    srv->shc_hc_rule->hcr_rule->irl_name,
+		    srv->shc_sg_srv->sgs_srvID);
+		ilbd_mark_server_disabled(srv);
+	}
+	hc_timer_restarted = B_TRUE;
+}
+
+/* Restart the periodic timer for a given server. */
+static ilb_status_t
+ilbd_hc_restart_timer(ilbd_hc_t *hc, ilbd_hc_srv_t *srv)
+{
+	int timeout;
+
+	/* Don't allow the timeout interval to be less than 1s */
+	timeout = MAX((hc->ihc_interval >> 1) + (gethrtime() %
+	    (hc->ihc_interval + 1)), 1);
+
+	/*
+	 * If the probe is actually a ping probe, there is no need to
+	 * do default pinging.  Just skip the step.
+	 */
+	if (hc->ihc_def_ping && hc->ihc_test_type != ILBD_HC_PING)
+		srv->shc_state = ilbd_hc_def_pinging;
+	else
+		srv->shc_state = ilbd_hc_probing;
+	srv->shc_tid = iu_schedule_timer(ilbd_hc_timer_q, timeout,
+	    ilbd_hc_probe_timer, srv);
+
+	if (srv->shc_tid == -1)
+		return (ILB_STATUS_TIMER);
+	srv->shc_lasttime = time(NULL);
+	srv->shc_nexttime = time(NULL) + timeout;
+
+	hc_timer_restarted = B_TRUE;
+	return (ILB_STATUS_OK);
+}
+
+/* Helper routine to associate a server with its hc object. */
+static ilb_status_t
+ilbd_hc_srv_add(ilbd_hc_t *hc, ilbd_hc_rule_t *hc_rule,
+    const ilb_sg_srv_t *srv, int ev_port)
+{
+	ilbd_hc_srv_t *new_srv;
+	ilb_status_t ret;
+
+	if ((new_srv = calloc(1, sizeof (ilbd_hc_srv_t))) == NULL)
+		return (ILB_STATUS_ENOMEM);
+	new_srv->shc_hc = hc;
+	new_srv->shc_hc_rule = hc_rule;
+	new_srv->shc_sg_srv = srv;
+	new_srv->shc_ev_port = ev_port;
+	new_srv->shc_tid = -1;
+	new_srv->shc_nexttime = time(NULL);
+	new_srv->shc_lasttime = new_srv->shc_nexttime;
+
+	if ((hc_rule->hcr_rule->irl_flags & ILB_FLAGS_RULE_ENABLED) &&
+	    ILB_IS_SRV_ENABLED(srv->sgs_flags)) {
+		new_srv->shc_status = ILB_HCS_UNINIT;
+		ret = ilbd_hc_restart_timer(hc, new_srv);
+		if (ret != ILB_STATUS_OK) {
+			free(new_srv);
+			return (ret);
+		}
+	} else {
+		new_srv->shc_status = ILB_HCS_DISABLED;
+	}
+
+	list_insert_tail(&hc_rule->hcr_servers, new_srv);
+	return (ILB_STATUS_OK);
+}
+
+/* Handy macro to cancel a server's timer. */
+#define	HC_CANCEL_TIMER(srv)						\
+{									\
+	void *arg;							\
+	int ret;							\
+	if ((srv)->shc_tid != -1) {					\
+		ret = iu_cancel_timer(ilbd_hc_timer_q, (srv)->shc_tid, &arg); \
+		(srv)->shc_tid = -1;					\
+		assert(ret == 1);					\
+		assert(arg == (srv));					\
+	}								\
+	hc_timer_restarted = B_TRUE;					\
+}
+
+/* Helper routine to dissociate a server from its hc object. */
+static ilb_status_t
+ilbd_hc_srv_rem(ilbd_hc_rule_t *hc_rule, const ilb_sg_srv_t *srv)
+{
+	ilbd_hc_srv_t *tmp_srv;
+
+	for (tmp_srv = list_head(&hc_rule->hcr_servers); tmp_srv != NULL;
+	    tmp_srv = list_next(&hc_rule->hcr_servers, tmp_srv)) {
+		if (tmp_srv->shc_sg_srv == srv) {
+			list_remove(&hc_rule->hcr_servers, tmp_srv);
+			HC_CANCEL_TIMER(tmp_srv);
+			if (tmp_srv->shc_child_pid != 0)
+				ilbd_hc_kill_probe(tmp_srv);
+			free(tmp_srv);
+			return (ILB_STATUS_OK);
+		}
+	}
+	return (ILB_STATUS_ENOENT);
+}
+
+/* Helper routine to dissociate all servers of a rule from its hc object. */
+static void
+ilbd_hc_srv_rem_all(ilbd_hc_rule_t *hc_rule)
+{
+	ilbd_hc_srv_t *srv;
+
+	while ((srv = list_remove_head(&hc_rule->hcr_servers)) != NULL) {
+		HC_CANCEL_TIMER(srv);
+		if (srv->shc_child_pid != 0)
+			ilbd_hc_kill_probe(srv);
+		free(srv);
+	}
+}
+
+/* Associate a rule with its hc object. */
+ilb_status_t
+ilbd_hc_associate_rule(const ilbd_rule_t *rule, int ev_port)
+{
+	ilbd_hc_t	*hc;
+	ilbd_hc_rule_t	*hc_rule;
+	ilb_status_t	ret;
+	ilbd_sg_t	*sg;
+	ilbd_srv_t	*ilbd_srv;
+
+	/* The rule is assumed to be initialized appropriately. */
+	if ((hc = ilbd_get_hc(rule->irl_hcname)) == NULL) {
+		logdebug("ilbd_hc_associate_rule: healthcheck %s does not "
+		    "exist", rule->irl_hcname);
+		return (ILB_STATUS_ENOHCINFO);
+	}
+	if ((hc->ihc_test_type == ILBD_HC_TCP &&
+	    rule->irl_proto != IPPROTO_TCP) ||
+	    (hc->ihc_test_type == ILBD_HC_UDP &&
+	    rule->irl_proto != IPPROTO_UDP)) {
+		return (ILB_STATUS_RULE_HC_MISMATCH);
+	}
+	if ((hc_rule = calloc(1, sizeof (ilbd_hc_rule_t))) == NULL) {
+		logdebug("ilbd_hc_associate_rule: out of memory");
+		return (ILB_STATUS_ENOMEM);
+	}
+
+	hc_rule->hcr_rule = rule;
+	list_create(&hc_rule->hcr_servers, sizeof (ilbd_hc_srv_t),
+	    offsetof(ilbd_hc_srv_t, shc_srv_link));
+
+	/* Add all the servers. */
+	sg = rule->irl_sg;
+	for (ilbd_srv = list_head(&sg->isg_srvlist); ilbd_srv != NULL;
+	    ilbd_srv = list_next(&sg->isg_srvlist, ilbd_srv)) {
+		if ((ret = ilbd_hc_srv_add(hc, hc_rule, &ilbd_srv->isv_srv,
+		    ev_port)) != ILB_STATUS_OK) {
+			/* Remove all previously added servers */
+			ilbd_hc_srv_rem_all(hc_rule);
+			free(hc_rule);
+			return (ret);
+		}
+	}
+	list_insert_tail(&hc->ihc_rules, hc_rule);
+	hc->ihc_rule_cnt++;
+
+	return (ILB_STATUS_OK);
+}
+
+/* Dissociate a rule from its hc object. */
+ilb_status_t
+ilbd_hc_dissociate_rule(const ilbd_rule_t *rule)
+{
+	ilbd_hc_t	*hc;
+	ilbd_hc_rule_t	*hc_rule;
+
+	/* The rule is assumed to be initialized appropriately. */
+	if ((hc = ilbd_get_hc(rule->irl_hcname)) == NULL) {
+		logdebug("ilbd_hc_dissociate_rule: healthcheck %s does not "
+		    "exist", rule->irl_hcname);
+		return (ILB_STATUS_ENOENT);
+	}
+	for (hc_rule = list_head(&hc->ihc_rules); hc_rule != NULL;
+	    hc_rule = list_next(&hc->ihc_rules, hc_rule)) {
+		if (hc_rule->hcr_rule == rule)
+			break;
+	}
+	if (hc_rule == NULL) {
+		logdebug("ilbd_hc_dissociate_rule: rule %s is not associated "
+		    "with healtcheck %s", rule->irl_hcname, hc->ihc_name);
+		return (ILB_STATUS_ENOENT);
+	}
+	ilbd_hc_srv_rem_all(hc_rule);
+	list_remove(&hc->ihc_rules, hc_rule);
+	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
+ilbd_hc_check_rule(const char *hc_name, const ilbd_rule_t *rule,
+    ilbd_hc_t **hc, ilbd_hc_rule_t **hc_rule)
+{
+	ilbd_hc_t	*tmp_hc;
+	ilbd_hc_rule_t	*tmp_hc_rule;
+
+	if ((tmp_hc = ilbd_get_hc(hc_name)) == NULL)
+		return (B_FALSE);
+	for (tmp_hc_rule = list_head(&tmp_hc->ihc_rules); tmp_hc_rule != NULL;
+	    tmp_hc_rule = list_next(&tmp_hc->ihc_rules, tmp_hc_rule)) {
+		if (tmp_hc_rule->hcr_rule == rule) {
+			*hc = tmp_hc;
+			*hc_rule = tmp_hc_rule;
+			return (B_TRUE);
+		}
+	}
+	return (B_FALSE);
+}
+
+/* Associate a server with its hc object. */
+ilb_status_t
+ilbd_hc_add_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv,
+    int ev_port)
+{
+	ilbd_hc_t	*hc;
+	ilbd_hc_rule_t	*hc_rule;
+
+	if (!ilbd_hc_check_rule(rule->irl_hcname, rule, &hc, &hc_rule))
+		return (ILB_STATUS_ENOENT);
+	return (ilbd_hc_srv_add(hc, hc_rule, srv, ev_port));
+}
+
+/* Dissociate a server from its hc object. */
+ilb_status_t
+ilbd_hc_del_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv)
+{
+	ilbd_hc_t	*hc;
+	ilbd_hc_rule_t	*hc_rule;
+
+	if (!ilbd_hc_check_rule(rule->irl_hcname, rule, &hc, &hc_rule))
+		return (ILB_STATUS_ENOENT);
+	return (ilbd_hc_srv_rem(hc_rule, srv));
+}
+
+/* Helper routine to enable/disable a server's hc probe. */
+static ilb_status_t
+ilbd_hc_toggle_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv,
+    boolean_t enable)
+{
+	ilbd_hc_t	*hc;
+	ilbd_hc_rule_t	*hc_rule;
+	ilbd_hc_srv_t	*tmp_srv;
+	ilb_status_t	ret;
+
+	if (!ilbd_hc_check_rule(rule->irl_hcname, rule, &hc, &hc_rule))
+		return (ILB_STATUS_ENOENT);
+	for (tmp_srv = list_head(&hc_rule->hcr_servers); tmp_srv != NULL;
+	    tmp_srv = list_next(&hc_rule->hcr_servers, tmp_srv)) {
+		if (tmp_srv->shc_sg_srv != srv) {
+			continue;
+		}
+		if (enable) {
+			if (tmp_srv->shc_status == ILB_HCS_DISABLED) {
+				ret = ilbd_hc_restart_timer(hc, tmp_srv);
+				if (ret != ILB_STATUS_OK) {
+					logerr("%s: cannot start timers for "
+					    "rule %s server %s", __func__,
+					    rule->irl_name,
+					    tmp_srv->shc_sg_srv->sgs_srvID);
+					return (ret);
+				}
+				/* Start from fresh... */
+				tmp_srv->shc_status = ILB_HCS_UNINIT;
+				tmp_srv->shc_rtt = 0;
+				tmp_srv->shc_fail_cnt = 0;
+			}
+		} else {
+			if (tmp_srv->shc_status != ILB_HCS_DISABLED) {
+				tmp_srv->shc_status = ILB_HCS_DISABLED;
+				HC_CANCEL_TIMER(tmp_srv);
+				if (tmp_srv->shc_child_pid != 0)
+					ilbd_hc_kill_probe(tmp_srv);
+			}
+		}
+		return (ILB_STATUS_OK);
+	}
+	return (ILB_STATUS_ENOENT);
+}
+
+ilb_status_t
+ilbd_hc_enable_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv)
+{
+	return (ilbd_hc_toggle_server(rule, srv, B_TRUE));
+}
+
+ilb_status_t
+ilbd_hc_disable_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv)
+{
+	return (ilbd_hc_toggle_server(rule, srv, B_FALSE));
+}
+
+/*
+ * Helper routine to enable/disable a rule's hc probe (including all its
+ * servers).
+ */
+static ilb_status_t
+ilbd_hc_toggle_rule(const ilbd_rule_t *rule, boolean_t enable)
+{
+	ilbd_hc_t	*hc;
+	ilbd_hc_rule_t	*hc_rule;
+	ilbd_hc_srv_t	*tmp_srv;
+	int		ret;
+
+	if (!ilbd_hc_check_rule(rule->irl_hcname, rule, &hc, &hc_rule))
+		return (ILB_STATUS_ENOENT);
+
+	for (tmp_srv = list_head(&hc_rule->hcr_servers); tmp_srv != NULL;
+	    tmp_srv = list_next(&hc_rule->hcr_servers, tmp_srv)) {
+		if (enable) {
+			/*
+			 * If the server is disabled in the rule, do not
+			 * restart its timer.
+			 */
+			if (tmp_srv->shc_status == ILB_HCS_DISABLED &&
+			    ILB_IS_SRV_ENABLED(
+			    tmp_srv->shc_sg_srv->sgs_flags)) {
+				ret = ilbd_hc_restart_timer(hc, tmp_srv);
+				if (ret != ILB_STATUS_OK) {
+					logerr("%s: cannot start timers for "
+					    "rule %s server %s", __func__,
+					    rule->irl_name,
+					    tmp_srv->shc_sg_srv->sgs_srvID);
+					goto rollback;
+				} else {
+					/* Start from fresh... */
+					tmp_srv->shc_status = ILB_HCS_UNINIT;
+					tmp_srv->shc_rtt = 0;
+					tmp_srv->shc_fail_cnt = 0;
+				}
+			}
+		} else {
+			if (tmp_srv->shc_status != ILB_HCS_DISABLED) {
+				HC_CANCEL_TIMER(tmp_srv);
+				tmp_srv->shc_status = ILB_HCS_DISABLED;
+				if (tmp_srv->shc_child_pid != 0)
+					ilbd_hc_kill_probe(tmp_srv);
+			}
+		}
+	}
+	return (ILB_STATUS_OK);
+rollback:
+	enable = !enable;
+	for (tmp_srv = list_prev(&hc_rule->hcr_servers, tmp_srv);
+	    tmp_srv != NULL;
+	    tmp_srv = list_prev(&hc_rule->hcr_servers, tmp_srv)) {
+		if (enable) {
+			if (tmp_srv->shc_status == ILB_HCS_DISABLED &&
+			    ILB_IS_SRV_ENABLED(
+			    tmp_srv->shc_sg_srv->sgs_flags)) {
+				(void) ilbd_hc_restart_timer(hc, tmp_srv);
+				tmp_srv->shc_status = ILB_HCS_UNINIT;
+				tmp_srv->shc_rtt = 0;
+				tmp_srv->shc_fail_cnt = 0;
+			}
+		} else {
+			if (tmp_srv->shc_status != ILB_HCS_DISABLED) {
+				HC_CANCEL_TIMER(tmp_srv);
+				tmp_srv->shc_status = ILB_HCS_DISABLED;
+				if (tmp_srv->shc_child_pid != 0)
+					ilbd_hc_kill_probe(tmp_srv);
+			}
+		}
+	}
+	return (ret);
+}
+
+ilb_status_t
+ilbd_hc_enable_rule(const ilbd_rule_t *rule)
+{
+	return (ilbd_hc_toggle_rule(rule, B_TRUE));
+}
+
+ilb_status_t
+ilbd_hc_disable_rule(const ilbd_rule_t *rule)
+{
+	return (ilbd_hc_toggle_rule(rule, B_FALSE));
+}
+
+static const char *
+topo_2_str(ilb_topo_t topo)
+{
+	switch (topo) {
+	case ILB_TOPO_DSR:
+		return ("DSR");
+		break;
+	case ILB_TOPO_NAT:
+		return ("NAT");
+		break;
+	case ILB_TOPO_HALF_NAT:
+		return ("HALF_NAT");
+		break;
+	default:
+		/* Should not happen. */
+		logerr("%s: unknown topology", __func__);
+		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
+create_argv(ilbd_hc_srv_t *srv, char *argv[])
+{
+	char buf[INET6_ADDRSTRLEN];
+	ilbd_rule_t const *rule;
+	ilb_sg_srv_t const *sg_srv;
+	struct in_addr v4_addr;
+	in_port_t port;
+	int i;
+
+	rule = srv->shc_hc_rule->hcr_rule;
+	sg_srv = srv->shc_sg_srv;
+
+	if (srv->shc_state == ilbd_hc_def_pinging) {
+		if ((argv[0] = strdup(ILB_PROBE_PING)) == NULL)
+			return (B_FALSE);
+	} else {
+		switch (srv->shc_hc->ihc_test_type) {
+		case ILBD_HC_USER:
+			if ((argv[0] = strdup(srv->shc_hc->ihc_test)) == NULL)
+				return (B_FALSE);
+			break;
+		case ILBD_HC_TCP:
+		case ILBD_HC_UDP:
+			if ((argv[0] = strdup(ILB_PROBE_PROTO)) ==
+			    NULL) {
+				return (B_FALSE);
+			}
+			break;
+		case ILBD_HC_PING:
+			if ((argv[0] = strdup(ILB_PROBE_PING)) == NULL) {
+				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.
+	 */
+	if (IN6_IS_ADDR_V4MAPPED(&rule->irl_vip)) {
+		IN6_V4MAPPED_TO_INADDR(&rule->irl_vip, &v4_addr);
+		if (inet_ntop(AF_INET, &v4_addr, buf, sizeof (buf)) == NULL)
+			goto cleanup;
+	} else {
+		if (inet_ntop(AF_INET6, &rule->irl_vip, buf,
+		    sizeof (buf)) == NULL) {
+			goto cleanup;
+		}
+	}
+	if ((argv[1] = strdup(buf)) == NULL)
+		goto cleanup;
+
+	/*
+	 * argv[2] is the backend server address.
+	 */
+	if (IN6_IS_ADDR_V4MAPPED(&sg_srv->sgs_addr)) {
+		IN6_V4MAPPED_TO_INADDR(&sg_srv->sgs_addr, &v4_addr);
+		if (inet_ntop(AF_INET, &v4_addr, buf, sizeof (buf)) == NULL)
+			goto cleanup;
+	} else {
+		if (inet_ntop(AF_INET6, &sg_srv->sgs_addr, buf,
+		    sizeof (buf)) == NULL) {
+			goto cleanup;
+		}
+	}
+	if ((argv[2] = strdup(buf)) == NULL)
+		goto cleanup;
+
+	/*
+	 * argv[3] is the transport protocol used in the rule.
+	 */
+	switch (rule->irl_proto) {
+	case IPPROTO_TCP:
+		argv[3] = strdup("TCP");
+		break;
+	case IPPROTO_UDP:
+		argv[3] = strdup("UDP");
+		break;
+	default:
+		logerr("%s: unknown protocol", __func__);
+		goto cleanup;
+		break;
+	}
+	if (argv[3] == NULL)
+		goto cleanup;
+
+	/*
+	 * argv[4] is the load balance mode, DSR, NAT, HALF-NAT.
+	 */
+	if ((argv[4] = strdup(topo_2_str(rule->irl_topo))) == NULL)
+		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:
+		port = ntohs(rule->irl_hcport);
+		break;
+	case ILB_HCI_PROBE_ANY: {
+		in_port_t min, max;
+
+		if (ntohs(sg_srv->sgs_minport) == 0) {
+			min = ntohs(rule->irl_minport);
+			max = ntohs(rule->irl_maxport);
+		} else {
+			min = ntohs(sg_srv->sgs_minport);
+			max = ntohs(sg_srv->sgs_maxport);
+		}
+		if (max > min)
+			port = min + gethrtime() % (max - min + 1);
+		else
+			port = min;
+		break;
+	}
+	default:
+		logerr("%s: unknown HC flag", __func__);
+		goto cleanup;
+		break;
+	}
+	(void) sprintf(buf, "%d", port);
+	if ((argv[5] = strdup(buf)) == NULL)
+		goto cleanup;
+
+	/*
+	 * argv[6] is the probe timeout.
+	 */
+	(void) sprintf(buf, "%d", srv->shc_hc->ihc_timeout);
+	if ((argv[6] = strdup(buf)) == NULL)
+		goto cleanup;
+
+	argv[7] = NULL;
+	return (B_TRUE);
+
+cleanup:
+	for (i = 0; i < HC_PROBE_ARGC; i++) {
+		if (argv[i] != NULL)
+			free(argv[i]);
+	}
+	return (B_FALSE);
+}
+
+static void
+destroy_argv(char *argv[])
+{
+	int i;
+
+	for (i = 0; argv[i] != NULL; i++)
+		free(argv[i]);
+}
+
+/* Spawn a process to run the hc probe on the given server. */
+static boolean_t
+ilbd_run_probe(ilbd_hc_srv_t *srv)
+{
+	posix_spawn_file_actions_t	fd_actions;
+	posix_spawnattr_t		attr;
+	sigset_t			child_sigset;
+	int				fds[2];
+	int				fdflags;
+	pid_t				pid;
+	char				*child_argv[HC_PROBE_ARGC];
+	ilbd_hc_probe_event_t		*probe_ev;
+	char				*probe_name;
+
+	bzero(child_argv, HC_PROBE_ARGC * sizeof (char *));
+	if ((probe_ev = calloc(1, sizeof (*probe_ev))) == NULL) {
+		logdebug("ilbd_run_probe: calloc");
+		return (B_FALSE);
+	}
+
+	/* Set up a pipe to get output from probe command. */
+	if (pipe(fds) < 0) {
+		logdebug("ilbd_run_probe: cannot create pipe");
+		free(probe_ev);
+		return (B_FALSE);
+	}
+	/* Set our side of the pipe to be non-blocking */
+	if ((fdflags = fcntl(fds[0], F_GETFL, 0)) == -1) {
+		logdebug("ilbd_run_probe: fcntl(F_GETFL)");
+		goto cleanup;
+	}
+	if (fcntl(fds[0], F_SETFL, fdflags | O_NONBLOCK) == -1) {
+		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;
+	}
+	if (posix_spawn_file_actions_addclose(&fd_actions, fds[0]) != 0) {
+		logdebug("ilbd_run_probe: posix_spawn_file_actions_addclose");
+		goto cleanup;
+	}
+	if (posix_spawn_file_actions_adddup2(&fd_actions, fds[1],
+	    STDOUT_FILENO) != 0) {
+		logdebug("ilbd_run_probe: posix_spawn_file_actions_dup2");
+		goto cleanup;
+	}
+	if (posix_spawn_file_actions_addclose(&fd_actions, fds[1]) != 0) {
+		logdebug("ilbd_run_probe: posix_spawn_file_actions_addclose");
+		goto cleanup;
+	}
+
+	/* Reset all signal handling of the child to default. */
+	(void) sigfillset(&child_sigset);
+	if (posix_spawnattr_setsigdefault(&attr, &child_sigset) != 0) {
+		logdebug("ilbd_run_probe: posix_spawnattr_setsigdefault");
+		goto cleanup;
+	}
+	/* Don't want SIGCHLD. */
+	if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_NOSIGCHLD_NP|
+	    POSIX_SPAWN_SETSIGDEF) != 0) {
+		logdebug("ilbd_run_probe: posix_spawnattr_setflags");
+		goto cleanup;
+	}
+
+	if (!create_argv(srv, child_argv)) {
+		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().
+	 */
+	if (srv->shc_state == ilbd_hc_def_pinging ||
+	    srv->shc_hc->ihc_test_type != ILBD_HC_USER) {
+		probe_name = ILB_PROBE_PROTO;
+	} else {
+		probe_name = srv->shc_hc->ihc_test;
+	}
+	if (posix_spawn(&pid, probe_name, &fd_actions, &attr, child_argv,
+	    NULL) != 0) {
+		logerr("%s: posix_spawn: %s for server %s: %s", __func__,
+		    srv->shc_hc->ihc_test, srv->shc_sg_srv->sgs_srvID,
+		    strerror(errno));
+		goto cleanup;
+	}
+
+	(void) close(fds[1]);
+	destroy_argv(child_argv);
+	srv->shc_child_pid = pid;
+	srv->shc_child_fd = fds[0];
+	srv->shc_ev = probe_ev;
+
+	probe_ev->ihp_ev = ILBD_EVENT_PROBE;
+	probe_ev->ihp_srv = srv;
+	probe_ev->ihp_pid = pid;
+	if (port_associate(srv->shc_ev_port, PORT_SOURCE_FD, fds[0],
+	    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.
+		 */
+		ilbd_hc_kill_probe(srv);
+		probe_ev = NULL;
+		goto cleanup;
+	}
+
+	return (B_TRUE);
+
+cleanup:
+	(void) close(fds[0]);
+	(void) close(fds[1]);
+	destroy_argv(child_argv);
+	if (probe_ev != NULL)
+		free(probe_ev);
+	return (B_FALSE);
+}
+
+/*
+ * Called by ild_hc_probe_return() to re-associate the fd to a child to
+ * the event port.
+ */
+static void
+reassociate_port(int ev_port, int fd, ilbd_hc_probe_event_t *ev)
+{
+	if (port_associate(ev_port, PORT_SOURCE_FD, fd,
+	    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...
+		 */
+		logdebug("%s: port_associate: %s", __func__, strerror(errno));
+		if (kill(ev->ihp_pid, SIGKILL) != 0)
+			logerr("%s: kill: %s", __func__, strerror(errno));
+		if (waitpid(ev->ihp_pid, NULL, 0) != ev->ihp_pid)
+			logdebug("%s: waitpid: %s", __func__, strerror(errno));
+		free(ev);
+	}
+}
+
+/*
+ * To handle a child probe process hanging up.
+ */
+static void
+ilbd_hc_child_hup(int ev_port, int fd, ilbd_hc_probe_event_t *ev)
+{
+	ilbd_hc_srv_t *srv;
+	pid_t ret_pid;
+	int ret;
+
+	srv = ev->ihp_srv;
+
+	if (!ev->ihp_done) {
+		/* ilbd does not care about this process anymore ... */
+		ev->ihp_done = B_TRUE;
+		srv->shc_ev = NULL;
+		srv->shc_child_pid = 0;
+		HC_CANCEL_TIMER(srv);
+		ilbd_set_fail_state(srv);
+	}
+	ret_pid = waitpid(ev->ihp_pid, &ret, WNOHANG);
+	switch (ret_pid) {
+	case -1:
+		logperror("ilbd_hc_child_hup: waitpid");
+		/* FALLTHROUGH */
+	case 0:
+		/* The child has not completed the exit. Wait again. */
+		reassociate_port(ev_port, fd, ev);
+		break;
+	default:
+		/* Right now, we just ignore the exit status. */
+		if (WIFEXITED(ret))
+			ret = WEXITSTATUS(ret);
+		(void) close(fd);
+		free(ev);
+	}
+}
+
+/*
+ * To read the output of a child probe process.
+ */
+static void
+ilbd_hc_child_data(int fd, ilbd_hc_probe_event_t *ev)
+{
+	ilbd_hc_srv_t *srv;
+	char buf[HC_MAX_PROBE_OUTPUT];
+	int ret;
+	int64_t rtt;
+
+	srv = ev->ihp_srv;
+
+	bzero(buf, HC_MAX_PROBE_OUTPUT);
+	ret = read(fd, buf, HC_MAX_PROBE_OUTPUT - 1);
+	/* Should not happen since event port should have caught this. */
+	assert(ret > 0);
+
+	/*
+	 * 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.
+	 */
+	if (!ev->ihp_done) {
+		ev->ihp_done = B_TRUE;
+		/* We don't need to know about this event anymore. */
+		srv->shc_ev = NULL;
+		srv->shc_child_pid = 0;
+		HC_CANCEL_TIMER(srv);
+	} else {
+		return;
+	}
+
+	rtt = strtoll(buf, NULL, 10);
+
+	/*
+	 * -1 means the server is dead or the probe somehow fails.  Treat
+	 * them both as server is dead.
+	 */
+	if (rtt == -1) {
+		ilbd_set_fail_state(srv);
+		return;
+	} else if (rtt > 0) {
+		/* If the returned RTT value is not valid, just ignore it. */
+		if (rtt > 0 && rtt <= UINT_MAX) {
+			/* Set rtt to be the simple smoothed average. */
+			if (srv->shc_rtt == 0) {
+				srv->shc_rtt = rtt;
+			} else {
+				srv->shc_rtt = 3 * ((srv)->shc_rtt >> 2) +
+				    (rtt >> 2);
+			}
+		}
+
+	}
+
+	switch (srv->shc_state) {
+	case ilbd_hc_def_pinging:
+		srv->shc_state = ilbd_hc_probing;
+
+		/* Ping is OK, now start the probe. */
+		ilbd_hc_probe_timer(ilbd_hc_timer_q, srv);
+		break;
+	case ilbd_hc_probing:
+		srv->shc_fail_cnt = 0;
+
+		/* Server is dead before, re-enable it. */
+		if (srv->shc_status == ILB_HCS_UNREACH ||
+		    srv->shc_status == ILB_HCS_DEAD) {
+			/*
+			 * If enabling the server in kernel fails now,
+			 * hopefully when the timer fires again later, the
+			 * enabling can be done.
+			 */
+			if (ilbd_k_Xable_server(&srv->shc_sg_srv->sgs_addr,
+			    srv->shc_hc_rule->hcr_rule->irl_name,
+			    stat_declare_srv_alive) != ILB_STATUS_OK) {
+				logerr("%s: cannot enable server in kernel: "
+				    " rule %s server %s", __func__,
+				    srv->shc_hc_rule->hcr_rule->irl_name,
+				    srv->shc_sg_srv->sgs_srvID);
+			} else {
+				srv->shc_status = ILB_HCS_ALIVE;
+			}
+		} else {
+			srv->shc_status = ILB_HCS_ALIVE;
+		}
+		if (ilbd_hc_restart_timer(srv->shc_hc, srv) != ILB_STATUS_OK) {
+			logerr("%s: cannot restart timer: rule %s server %s",
+			    __func__, srv->shc_hc_rule->hcr_rule->irl_name,
+			    srv->shc_sg_srv->sgs_srvID);
+			ilbd_mark_server_disabled(srv);
+		}
+		break;
+	default:
+		logdebug("%s: unknown state", __func__);
+		break;
+	}
+}
+
+/*
+ * Handle the return event of a child probe fd.
+ */
+void
+ilbd_hc_probe_return(int ev_port, int fd, int port_events,
+    ilbd_hc_probe_event_t *ev)
+{
+	/*
+	 * 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)
+		ilbd_hc_child_data(fd, ev);
+
+	if (port_events & (POLLHUP|POLLERR)) {
+		ilbd_hc_child_hup(ev_port, fd, ev);
+		return;
+	}
+
+	/*
+	 * Re-associate the fd with the port so that when the child
+	 * exits, we can reap the status.
+	 */
+	reassociate_port(ev_port, fd, ev);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_main.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,1025 @@
+/*
+ * 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.
+ */
+
+/*
+ * The core of ilbd daemon is a single-threaded event loop using
+ * event completion framework; it receives requests from client using
+ * the libilb functions, handles timeouts, initiates health checks, and
+ * populates the kernel state.
+ *
+ * The daemon has the following privileges (in addition to the basic ones):
+ *
+ * 	PRIV_PROC_OWNER, PRIV_NET_ICMPACCESS,
+ *	PRIV_SYS_IP_CONFIG, PRIV_PROC_AUDIT
+ *
+ * The aforementioned  privileges will be specified in the SMF manifest.
+ *
+ * AF_UNIX socket is used for IPC between libilb and this daemon as
+ * both processes will run on the same machine.
+ *
+ * To do health check, the daemon will create a timer for every health
+ * check probe. Each of these timers will be  associated with the
+ * event port. When a timer goes off, the daemon will initiate a
+ * pipe to a separate process to execute the specific health check
+ * probe. This new process will run with the same user-id as that of
+ * ilbd daemon and will inherit all the privileges from the ilbd
+ * daemon parent process except the following:
+ *
+ * PRIV_PROC_OWNER, PRIV_PROC_AUDIT
+ *
+ * All health checks, will be implemented as external methods
+ * (binary or script). The following arguments will be passed
+ * to external methods:
+ *
+ *	$1	VIP (literal IPv4 or IPv6 address)
+ *	$2	Server IP (literal IPv4 or IPv6 address)
+ *	$3	Protocol (UDP, TCP as a string)
+ *	$4	The load balance mode, "DSR", "NAT", "HALF_NAT"
+ *	$5	Numeric port range
+ *	$6	maximum time (in seconds) the method
+ * should wait before returning failure. If the method runs for
+ * longer, it may be killed, and the test considered failed.
+ *
+ * Upon success, a health check method should print the RTT to the
+ * it finds to its STDOUT for ilbd to consume.  The implicit unit
+ * is microseconds but only the number needs to be printed.  If it
+ * cannot find the RTT, it should print 0.  If the method decides
+ * that the server is dead, it should print -1 to its STDOUT.
+ *
+ * By default, an user-supplied health check probe process will
+ * also run with the same set of privileges as ILB's built-in
+ * probes.  If the administrator has an user-supplied health check
+ * program that requires a larger privilege set, he/she will have
+ * to implement setuid program.
+ *
+ * Each health check will have a timeout, such that if the health
+ * check process is hung, it will be killed after the timeout interval
+ * and the daemon will notify the kernel ILB engine of the server's
+ * unresponsiveness, so that load distribution can be appropriately
+ * adjusted.  If on the other hand the health check is successful
+ * the timeout timer is cancelled.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <signal.h>
+#include <port.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/note.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <ucred.h>
+#include <priv_utils.h>
+#include <net/if.h>
+#include <libilb.h>
+#include <assert.h>
+#include <inet/ilb.h>
+#include <libintl.h>
+#include <fcntl.h>
+#include <rpcsvc/daemon_utils.h>
+#include "libilb_impl.h"
+#include "ilbd.h"
+
+/*
+ * NOTE: The following needs to be kept up to date.
+ */
+#define	ILBD_VERSION	"1.0"
+#define	ILBD_COPYRIGHT	\
+	"Copyright 2009 Sun Microsystems, Inc.  All rights reserved.\n" \
+	"Use is subject to license terms.\n"
+
+/*
+ * Global reply buffer to client request.  Note that ilbd is single threaded,
+ * so a global buffer is OK.  If ilbd becomes multi-threaded, this needs to
+ * be changed.
+ */
+static uint32_t reply_buf[ILBD_MSG_SIZE / sizeof (uint32_t)];
+
+static void
+ilbd_free_cli(ilbd_client_t *cli)
+{
+	(void) close(cli->cli_sd);
+	if (cli->cli_cmd == ILBD_SHOW_NAT)
+		ilbd_show_nat_cleanup();
+	if (cli->cli_cmd == ILBD_SHOW_PERSIST)
+		ilbd_show_sticky_cleanup();
+	if (cli->cli_saved_reply != NULL)
+		free(cli->cli_saved_reply);
+	free(cli->cli_pw_buf);
+	free(cli);
+}
+
+static void
+ilbd_reset_kernel_state(void)
+{
+	ilb_status_t	rc;
+	ilb_name_cmd_t	kcmd;
+
+	kcmd.cmd = ILB_DESTROY_RULE;
+	kcmd.flags = ILB_RULE_ALLRULES;
+	kcmd.name[0] = '\0';
+
+	rc = do_ioctl(&kcmd, 0);
+	if (rc != ILB_STATUS_OK)
+		logdebug("ilbd_reset_kernel_state: do_ioctl failed: %s",
+		    strerror(errno));
+}
+
+/* Signal handler to do clean up. */
+/* ARGSUSED */
+static void
+ilbd_cleanup(int sig)
+{
+	(void) remove(SOCKET_PATH);
+	ilbd_reset_kernel_state();
+	exit(0);
+}
+
+/*
+ * Create a socket and return it to caller.  If there is a failure, this
+ * function calls exit(2).  Hence it always returns a valid listener socket.
+ *
+ * Note that this function is called before ilbd becomes a daemon.  So
+ * we call perror(3C) to print out error message directly so that SMF can
+ * catch them.
+ */
+static int
+ilbd_create_client_socket(void)
+{
+	int			s;
+	mode_t			omask;
+	struct sockaddr_un	sa;
+	int			sobufsz;
+
+	s = socket(PF_UNIX, SOCK_SEQPACKET, 0);
+	if (s == -1) {
+		perror("ilbd_create_client_socket: socket to"
+		    " client failed");
+		exit(errno);
+	}
+	if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
+		perror("ilbd_create_client_socket: fcntl(FD_CLOEXEC)");
+		exit(errno);
+	}
+
+	sobufsz = ILBD_MSG_SIZE;
+	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sobufsz,
+	    sizeof (sobufsz)) != 0) {
+		perror("ilbd_creat_client_socket: setsockopt(SO_SNDBUF) "
+		    "failed");
+		exit(errno);
+	}
+	if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sobufsz,
+	    sizeof (sobufsz)) != 0) {
+		perror("ilbd_creat_client_socket: setsockopt(SO_RCVBUF) "
+		    "failed");
+		exit(errno);
+	}
+
+	/*
+	 * since everybody can talk to us, we need to open up permissions
+	 * we check peer privileges on a per-operation basis.
+	 * This is no security issue as long as we're single-threaded.
+	 */
+	omask = umask(0);
+
+	/* just in case we didn't clean up properly after last exit */
+	(void) remove(SOCKET_PATH);
+
+	bzero(&sa, sizeof (sa));
+	sa.sun_family = AF_UNIX;
+	(void) strlcpy(sa.sun_path, SOCKET_PATH, sizeof (sa.sun_path));
+
+	if (bind(s, (struct sockaddr *)&sa, sizeof (sa)) != 0) {
+		perror("ilbd_create_client_socket(): bind to client"
+		    " socket failed");
+		exit(errno);
+	}
+
+	/* re-instate old umask */
+	(void) umask(omask);
+
+#define	QLEN	16
+
+	if (listen(s, QLEN) != 0) {
+		perror("ilbd_create_client_socket: listen to client"
+		    " socket failed");
+		exit(errno);
+	}
+
+	(void) signal(SIGHUP, SIG_IGN);
+	(void) signal(SIGPIPE, SIG_IGN);
+	(void) signal(SIGSTOP, SIG_IGN);
+	(void) signal(SIGTSTP, SIG_IGN);
+	(void) signal(SIGTTIN, SIG_IGN);
+	(void) signal(SIGTTOU, SIG_IGN);
+
+	(void) signal(SIGINT, ilbd_cleanup);
+	(void) signal(SIGTERM, ilbd_cleanup);
+	(void) signal(SIGQUIT, ilbd_cleanup);
+
+	return (s);
+}
+
+/*
+ * Return the minimum size of a given request.  The returned size does not
+ * include the variable part of a request.
+ */
+static size_t
+ilbd_cmd_size(const ilb_comm_t *ic)
+{
+	size_t cmd_sz;
+
+	cmd_sz = sizeof (*ic);
+	switch (ic->ic_cmd) {
+	case ILBD_RETRIEVE_SG_NAMES:
+	case ILBD_RETRIEVE_RULE_NAMES:
+	case ILBD_RETRIEVE_HC_NAMES:
+	case ILBD_CMD_OK:
+		break;
+	case ILBD_CMD_ERROR:
+		cmd_sz += sizeof (ilb_status_t);
+		break;
+	case ILBD_RETRIEVE_SG_HOSTS:
+	case ILBD_CREATE_SERVERGROUP:
+	case ILBD_DESTROY_SERVERGROUP:
+	case ILBD_DESTROY_RULE:
+	case ILBD_ENABLE_RULE:
+	case ILBD_DISABLE_RULE:
+	case ILBD_RETRIEVE_RULE:
+	case ILBD_DESTROY_HC:
+	case ILBD_GET_HC_INFO:
+	case ILBD_GET_HC_SRVS:
+		cmd_sz += sizeof (ilbd_name_t);
+		break;
+	case ILBD_ENABLE_SERVER:
+	case ILBD_DISABLE_SERVER:
+	case ILBD_ADD_SERVER_TO_GROUP:
+	case ILBD_REM_SERVER_FROM_GROUP:
+		cmd_sz += sizeof (ilb_sg_info_t);
+		break;
+	case ILBD_SRV_ADDR2ID:
+	case ILBD_SRV_ID2ADDR:
+		cmd_sz += sizeof (ilb_sg_info_t) + sizeof (ilb_sg_srv_t);
+		break;
+	case ILBD_CREATE_RULE:
+		cmd_sz += sizeof (ilb_rule_info_t);
+		break;
+	case ILBD_CREATE_HC:
+		cmd_sz += sizeof (ilb_hc_info_t);
+		break;
+	case ILBD_SHOW_NAT:
+	case ILBD_SHOW_PERSIST:
+		cmd_sz += sizeof (ilb_show_info_t);
+		break;
+	}
+
+	return (cmd_sz);
+}
+
+/*
+ * Given a request and its size, check that the size is big enough to
+ * contain the variable part of a request.
+ */
+static ilb_status_t
+ilbd_check_req_size(ilb_comm_t *ic, size_t ic_sz)
+{
+	ilb_status_t rc = ILB_STATUS_OK;
+	ilb_sg_info_t *sg_info;
+	ilbd_namelist_t *nlist;
+
+	switch (ic->ic_cmd) {
+	case ILBD_CREATE_SERVERGROUP:
+	case ILBD_ENABLE_SERVER:
+	case ILBD_DISABLE_SERVER:
+	case ILBD_ADD_SERVER_TO_GROUP:
+	case ILBD_REM_SERVER_FROM_GROUP:
+		sg_info = (ilb_sg_info_t *)&ic->ic_data;
+
+		if (ic_sz < ilbd_cmd_size(ic) + sg_info->sg_srvcount *
+		    sizeof (ilb_sg_srv_t)) {
+			rc = ILB_STATUS_EINVAL;
+		}
+		break;
+	case ILBD_ENABLE_RULE:
+	case ILBD_DISABLE_RULE:
+	case ILBD_DESTROY_RULE:
+		nlist = (ilbd_namelist_t *)&ic->ic_data;
+
+		if (ic_sz < ilbd_cmd_size(ic) + nlist->ilbl_count *
+		    sizeof (ilbd_name_t)) {
+			rc = ILB_STATUS_EINVAL;
+		}
+		break;
+	}
+	return (rc);
+}
+
+/*
+ * this function *relies* on a complete message/data struct
+ * being passed in (currently via the SOCK_SEQPACKET socket type).
+ *
+ * Note that the size of ip is at most ILBD_MSG_SIZE.
+ */
+static ilb_status_t
+consume_common_struct(ilb_comm_t *ic, size_t ic_sz, ilbd_client_t *cli,
+    int ev_port)
+{
+	ilb_status_t	rc;
+	struct passwd	*ps;
+	size_t		rbufsz;
+	ssize_t		ret;
+	boolean_t	standard_reply = B_TRUE;
+	ilbd_name_t	name;
+
+	/*
+	 * cli_ev must be overridden during handling of individual commands,
+	 * if there's a special need; otherwise, leave this for
+	 * the "default" case
+	 */
+	cli->cli_ev = ILBD_EVENT_REQ;
+
+	ps = &cli->cli_pw;
+	rbufsz = ILBD_MSG_SIZE;
+
+	/* Sanity check on the size of the static part of a request. */
+	if (ic_sz < ilbd_cmd_size(ic)) {
+		rc = ILB_STATUS_EINVAL;
+		goto out;
+	}
+
+	switch (ic->ic_cmd) {
+	case ILBD_CREATE_SERVERGROUP: {
+		ilb_sg_info_t sg_info;
+
+		/*
+		 * ilbd_create_sg() only needs the sg_name field.  But it
+		 * takes in a ilb_sg_info_t because it is used as a callback
+		 * in ilbd_walk_sg_pgs().
+		 */
+		(void) strlcpy(sg_info.sg_name, (char *)&(ic->ic_data),
+		    sizeof (sg_info.sg_name));
+		rc = ilbd_create_sg(&sg_info, ev_port, ps,
+		    cli->cli_peer_ucredp);
+		break;
+	}
+
+	case ILBD_DESTROY_SERVERGROUP:
+		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
+		rc = ilbd_destroy_sg(name, ps, cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_ADD_SERVER_TO_GROUP:
+		if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
+			break;
+		rc = ilbd_add_server_to_group((ilb_sg_info_t *)&ic->ic_data,
+		    ev_port, ps, cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_REM_SERVER_FROM_GROUP:
+		if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
+			break;
+		rc = ilbd_rem_server_from_group((ilb_sg_info_t *)&ic->ic_data,
+		    ev_port, ps, cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_ENABLE_SERVER:
+		if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
+			break;
+		rc = ilbd_enable_server((ilb_sg_info_t *)&ic->ic_data, ps,
+		    cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_DISABLE_SERVER:
+		if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
+			break;
+		rc = ilbd_disable_server((ilb_sg_info_t *)&ic->ic_data, ps,
+		    cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_SRV_ADDR2ID:
+		rc = ilbd_address_to_srvID((ilb_sg_info_t *)&ic->ic_data,
+		    reply_buf, &rbufsz);
+		if (rc == ILB_STATUS_OK)
+			standard_reply = B_FALSE;
+		break;
+
+	case ILBD_SRV_ID2ADDR:
+		rc = ilbd_srvID_to_address((ilb_sg_info_t *)&ic->ic_data,
+		    reply_buf, &rbufsz);
+		if (rc == ILB_STATUS_OK)
+			standard_reply = B_FALSE;
+		break;
+
+	case ILBD_RETRIEVE_SG_HOSTS:
+		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
+		rc = ilbd_retrieve_sg_hosts(name, reply_buf, &rbufsz);
+		if (rc == ILB_STATUS_OK)
+			standard_reply = B_FALSE;
+		break;
+
+	case ILBD_RETRIEVE_SG_NAMES:
+	case ILBD_RETRIEVE_RULE_NAMES:
+	case ILBD_RETRIEVE_HC_NAMES:
+		rc = ilbd_retrieve_names(ic->ic_cmd, reply_buf, &rbufsz);
+		if (rc == ILB_STATUS_OK)
+			standard_reply = B_FALSE;
+		break;
+
+	case ILBD_CREATE_RULE:
+		rc = ilbd_create_rule((ilb_rule_info_t *)&ic->ic_data, ev_port,
+		    ps, cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_DESTROY_RULE:
+		/* Copy the name to ensure that name is NULL terminated. */
+		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
+		rc = ilbd_destroy_rule(name, ps, cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_ENABLE_RULE:
+		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
+		rc = ilbd_enable_rule(name, ps, cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_DISABLE_RULE:
+		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
+		rc = ilbd_disable_rule(name, ps, cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_RETRIEVE_RULE:
+		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
+		rc = ilbd_retrieve_rule(name, reply_buf, &rbufsz);
+		if (rc == ILB_STATUS_OK)
+			standard_reply = B_FALSE;
+		break;
+
+	case ILBD_CREATE_HC:
+		rc = ilbd_create_hc((ilb_hc_info_t *)&ic->ic_data, ev_port, ps,
+		    cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_DESTROY_HC:
+		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
+		rc = ilbd_destroy_hc(name, ps, cli->cli_peer_ucredp);
+		break;
+
+	case ILBD_GET_HC_INFO:
+		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
+		rc = ilbd_get_hc_info(name, reply_buf, &rbufsz);
+		if (rc == ILB_STATUS_OK)
+			standard_reply = B_FALSE;
+		break;
+
+	case ILBD_GET_HC_SRVS:
+		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
+		rc = ilbd_get_hc_srvs(name, reply_buf, &rbufsz);
+		if (rc == ILB_STATUS_OK)
+			standard_reply = B_FALSE;
+		break;
+
+	case ILBD_SHOW_NAT:
+		rc = ilbd_show_nat(cli, ic, reply_buf, &rbufsz);
+		if (rc == ILB_STATUS_OK)
+			standard_reply = B_FALSE;
+		break;
+
+	case ILBD_SHOW_PERSIST:
+		rc = ilbd_show_sticky(cli, ic, reply_buf, &rbufsz);
+		if (rc == ILB_STATUS_OK)
+			standard_reply = B_FALSE;
+		break;
+
+	default:
+		logdebug("consume_common_struct: unknown command");
+		rc = ILB_STATUS_INVAL_CMD;
+		break;
+	}
+
+out:
+	/*
+	 * The message exchange is always in pairs, request/response.  If
+	 * a transaction requires multiple exchanges, the client will send
+	 * in multiple requests to get multiple responses.  The show-nat and
+	 * show-persist request are examples of this.  The end of transaction
+	 * is marked with ic_flags set to ILB_COMM_END.
+	 */
+
+	/* This is the standard reply. */
+	if (standard_reply) {
+		if (rc == ILB_STATUS_OK)
+			ilbd_reply_ok(reply_buf, &rbufsz);
+		else
+			ilbd_reply_err(reply_buf, &rbufsz, rc);
+	}
+
+	if ((ret = send(cli->cli_sd, reply_buf, rbufsz, 0)) != rbufsz) {
+		if (ret == -1) {
+			if (errno != EWOULDBLOCK) {
+				logdebug("consume_common_struct: send: %s",
+				    strerror(errno));
+				rc = ILB_STATUS_SEND;
+				goto err_out;
+			}
+			/*
+			 * The reply is blocked, save the reply.  handle_req()
+			 * will associate the event port for the re-send.
+			 */
+			assert(cli->cli_saved_reply == NULL);
+			if ((cli->cli_saved_reply = malloc(rbufsz)) == NULL) {
+				/*
+				 * Set the error to ILB_STATUS_SEND so that
+				 * handle_req() will free the client.
+				 */
+				logdebug("consume_common_struct: failure to "
+				    "allocate memory to save reply");
+				rc = ILB_STATUS_SEND;
+				goto err_out;
+			}
+			bcopy(reply_buf, cli->cli_saved_reply, rbufsz);
+			cli->cli_saved_size = rbufsz;
+			return (ILB_STATUS_EWOULDBLOCK);
+		}
+	}
+err_out:
+	return (rc);
+}
+
+/*
+ * Accept a new client request.  A struct ilbd_client_t is allocated to
+ * store the client info.  The accepted socket is port_associate() with
+ * the given port.  And the allocated ilbd_client_t struct is passed as
+ * the user pointer.
+ */
+static void
+new_req(int ev_port, int listener, void *ev_obj)
+{
+	struct sockaddr	sa;
+	int		sa_len;
+	int		new_sd;
+	int		sflags;
+	ilbd_client_t	*cli;
+	int		res;
+	uid_t		uid;
+
+	sa_len = sizeof (sa);
+	if ((new_sd = accept(listener, &sa, &sa_len)) == -1) {
+		/* don't log if we're out of file descriptors */
+		if (errno != EINTR && errno != EMFILE)
+			logperror("new_req: accept failed");
+		goto done;
+	}
+
+	/* Set the new socket to be non-blocking. */
+	if ((sflags = fcntl(new_sd, F_GETFL, 0)) == -1) {
+		logperror("new_req: fcntl(F_GETFL)");
+		goto clean_up;
+	}
+	if (fcntl(new_sd, F_SETFL, sflags | O_NONBLOCK) == -1) {
+		logperror("new_req: fcntl(F_SETFL)");
+		goto clean_up;
+	}
+	if (fcntl(new_sd, F_SETFD, FD_CLOEXEC) == -1) {
+		logperror("new_req: fcntl(FD_CLOEXEC)");
+		goto clean_up;
+	}
+	if ((cli = calloc(1, sizeof (ilbd_client_t))) == NULL) {
+		logerr("new_req: malloc(ilbd_client_t)");
+		goto clean_up;
+	}
+	res = getpeerucred(new_sd, &cli->cli_peer_ucredp);
+	if (res == -1) {
+		logperror("new_req: getpeerucred failed");
+		goto clean_up;
+	}
+	if ((uid = ucred_getruid(cli->cli_peer_ucredp)) == (uid_t)-1) {
+		logperror("new_req: ucred_getruid failed");
+		goto clean_up;
+	}
+	cli->cli_pw_bufsz = (size_t)sysconf(_SC_GETPW_R_SIZE_MAX);
+	if ((cli->cli_pw_buf = malloc(cli->cli_pw_bufsz)) == NULL) {
+		free(cli);
+		logerr("new_req: malloc(cli_pw_buf)");
+		goto clean_up;
+	}
+	if (getpwuid_r(uid, &cli->cli_pw, cli->cli_pw_buf,
+	    cli->cli_pw_bufsz) == NULL) {
+		free(cli->cli_pw_buf);
+		free(cli);
+		logperror("new_req: invalid user");
+		goto clean_up;
+	}
+	cli->cli_ev = ILBD_EVENT_REQ;
+	cli->cli_sd = new_sd;
+	cli->cli_cmd = ILBD_BAD_CMD;
+	cli->cli_saved_reply = NULL;
+	cli->cli_saved_size = 0;
+	if (port_associate(ev_port, PORT_SOURCE_FD, new_sd, POLLRDNORM,
+	    cli) == -1) {
+		logperror("new_req: port_associate(cli) failed");
+		free(cli->cli_pw_buf);
+		free(cli);
+clean_up:
+		(void) close(new_sd);
+	}
+
+done:
+	/* Re-associate the listener with the event port. */
+	if (port_associate(ev_port, PORT_SOURCE_FD, listener, POLLRDNORM,
+	    ev_obj) == -1) {
+		logperror("new_req: port_associate(listener) failed");
+		exit(1);
+	}
+}
+
+static void
+handle_req(int ev_port, ilbd_event_t event, ilbd_client_t *cli)
+{
+	/* All request should be smaller than ILBD_MSG_SIZE */
+	union {
+		ilb_comm_t	ic;
+		uint32_t	buf[ILBD_MSG_SIZE / sizeof (uint32_t)];
+	} ic_u;
+	int	rc = ILB_STATUS_OK;
+	ssize_t	r;
+
+	if (event == ILBD_EVENT_REQ) {
+		/*
+		 * Something is wrong with the client since there is a
+		 * pending reply, the client should not send us another
+		 * request.  Kill this client.
+		 */
+		if (cli->cli_saved_reply != NULL) {
+			logerr("handle_req: misbehaving client, more than one "
+			    "outstanding request");
+			rc = ILB_STATUS_INTERNAL;
+			goto err_out;
+		}
+
+		/*
+		 * Our socket is message based so we should be able
+		 * to get the request in one single read.
+		 */
+		r = recv(cli->cli_sd, (void *)ic_u.buf, sizeof (ic_u.buf), 0);
+		if (r < 0) {
+			if (errno != EINTR) {
+				logperror("handle_req: read failed");
+				rc = ILB_STATUS_READ;
+				goto err_out;
+			}
+			/*
+			 * If interrupted, just re-associate the cli_sd
+			 * with the port.
+			 */
+			goto done;
+		}
+		cli->cli_cmd = ic_u.ic.ic_cmd;
+
+		rc = consume_common_struct(&ic_u.ic, r, cli, ev_port);
+		if (rc == ILB_STATUS_EWOULDBLOCK)
+			goto blocked;
+		/* Fatal error communicating with client, free it. */
+		if (rc == ILB_STATUS_SEND)
+			goto err_out;
+	} else {
+		assert(event == ILBD_EVENT_REP_OK);
+		assert(cli->cli_saved_reply != NULL);
+
+		/*
+		 * The reply to client was previously blocked, we will
+		 * send again.
+		 */
+		if (send(cli->cli_sd, cli->cli_saved_reply,
+		    cli->cli_saved_size, 0) != cli->cli_saved_size) {
+			if (errno != EWOULDBLOCK) {
+				logdebug("handle_req: send: %s",
+				    strerror(errno));
+				rc = ILB_STATUS_SEND;
+				goto err_out;
+			}
+			goto blocked;
+		}
+		free(cli->cli_saved_reply);
+		cli->cli_saved_reply = NULL;
+		cli->cli_saved_size = 0;
+	}
+done:
+	/* Re-associate with the event port for more requests. */
+	cli->cli_ev = ILBD_EVENT_REQ;
+	if (port_associate(ev_port, PORT_SOURCE_FD, cli->cli_sd,
+	    POLLRDNORM, cli) == -1) {
+		logperror("handle_req: port_associate(POLLRDNORM)");
+		rc = ILB_STATUS_INTERNAL;
+		goto err_out;
+	}
+	return;
+
+blocked:
+	/* Re-associate with the event port. */
+	cli->cli_ev = ILBD_EVENT_REP_OK;
+	if (port_associate(ev_port, PORT_SOURCE_FD, cli->cli_sd, POLLWRNORM,
+	    cli) == -1) {
+		logperror("handle_req: port_associate(POLLWRNORM)");
+		rc = ILB_STATUS_INTERNAL;
+		goto err_out;
+	}
+	return;
+
+err_out:
+	ilbd_free_cli(cli);
+}
+
+static void
+i_ilbd_read_config(int ev_port)
+{
+	logdebug("i_ilbd_read_config: port %d", ev_port);
+	(void) ilbd_walk_sg_pgs(ilbd_create_sg, &ev_port, NULL);
+	(void) ilbd_walk_hc_pgs(ilbd_create_hc, &ev_port, NULL);
+	(void) ilbd_walk_rule_pgs(ilbd_create_rule, &ev_port, NULL);
+}
+
+/*
+ * main event loop for ilbd
+ * asserts that argument 'listener' is a server socket ready to accept() on.
+ */
+static void
+main_loop(int listener)
+{
+	port_event_t		p_ev;
+	int			ev_port, ev_port_obj;
+	ilbd_event_obj_t	ev_obj;
+	ilbd_timer_event_obj_t	timer_ev_obj;
+
+	ev_port = port_create();
+	if (ev_port == -1) {
+		logperror("main_loop: port_create failed");
+		exit(-1);
+	}
+	ilbd_hc_timer_init(ev_port, &timer_ev_obj);
+
+	ev_obj.ev = ILBD_EVENT_NEW_REQ;
+	if (port_associate(ev_port, PORT_SOURCE_FD, listener, POLLRDNORM,
+	    &ev_obj) == -1) {
+		logperror("main_loop: port_associate failed");
+		exit(1);
+	}
+
+	i_ilbd_read_config(ev_port);
+	ilbd_hc_timer_update(&timer_ev_obj);
+
+	_NOTE(CONSTCOND)
+	while (B_TRUE) {
+		int r;
+		ilbd_event_t event;
+		ilbd_client_t *cli;
+
+		r = port_get(ev_port, &p_ev, NULL);
+		if (r == -1) {
+			if (errno == EINTR)
+				continue;
+			logperror("main_loop: port_get failed");
+			break;
+		}
+
+		ev_port_obj = p_ev.portev_object;
+		event = ((ilbd_event_obj_t *)p_ev.portev_user)->ev;
+
+		switch (event) {
+		case ILBD_EVENT_TIMER:
+			ilbd_hc_timeout();
+			break;
+
+		case ILBD_EVENT_PROBE:
+			ilbd_hc_probe_return(ev_port, ev_port_obj,
+			    p_ev.portev_events,
+			    (ilbd_hc_probe_event_t *)p_ev.portev_user);
+			break;
+
+		case ILBD_EVENT_NEW_REQ:
+			assert(ev_port_obj == listener);
+			/*
+			 * An error happens in the listener.  Exit
+			 * for now....
+			 */
+			if (p_ev.portev_events & (POLLHUP|POLLERR)) {
+				logerr("main_loop: listener error");
+				exit(1);
+			}
+			new_req(ev_port, ev_port_obj, &ev_obj);
+			break;
+
+		case ILBD_EVENT_REP_OK:
+		case ILBD_EVENT_REQ:
+			cli = (ilbd_client_t *)p_ev.portev_user;
+			assert(ev_port_obj == cli->cli_sd);
+
+			/*
+			 * An error happens in the newly accepted
+			 * client request.  Clean up the client.
+			 * this also happens when client closes socket,
+			 * so not necessarily a reason for alarm
+			 */
+			if (p_ev.portev_events & (POLLHUP|POLLERR)) {
+				ilbd_free_cli(cli);
+				break;
+			}
+
+			handle_req(ev_port, event, cli);
+			break;
+
+		default:
+			logerr("main_loop: unknown event %d", event);
+			exit(EXIT_FAILURE);
+			break;
+		}
+
+		ilbd_hc_timer_update(&timer_ev_obj);
+	}
+}
+
+static void
+i_ilbd_setup_lists(void)
+{
+	i_setup_sg_hlist();
+	i_setup_rule_hlist();
+	i_ilbd_setup_hc_list();
+}
+
+/*
+ * Usage message - call only during startup. it will print its
+ * message on stderr and exit
+ */
+static void
+Usage(char *name)
+{
+	(void) fprintf(stderr, gettext("Usage: %s [-d|--debug]\n"), name);
+	exit(1);
+}
+
+static void
+print_version(char *name)
+{
+	(void) printf("%s %s\n", basename(name), ILBD_VERSION);
+	(void) printf(gettext(ILBD_COPYRIGHT));
+	exit(0);
+}
+
+/*
+ * Increase the file descriptor limit for handling a lot of health check
+ * processes (each requires a pipe).
+ *
+ * Note that this function is called before ilbd becomes a daemon.  So
+ * we call perror(3C) to print out error message directly so that SMF
+ * can catch them.
+ */
+static void
+set_rlim(void)
+{
+	struct rlimit rlp;
+
+	if (getrlimit(RLIMIT_NOFILE, &rlp) == -1) {
+		perror("ilbd: getrlimit");
+		exit(errno);
+	}
+	rlp.rlim_cur = rlp.rlim_max;
+	if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) {
+		perror("ilbd: setrlimit");
+		exit(errno);
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	int	s;
+	int	c;
+
+	(void) setlocale(LC_ALL, "");
+#if !defined(TEXT_DOMAIN)
+#define	TEXT_DOMAIN "SYS_TEST"
+#endif
+	static const char daemon_dir[] = DAEMON_DIR;
+
+	(void) textdomain(TEXT_DOMAIN);
+
+	while ((c = getopt(argc, argv, ":V?d(debug)")) != -1) {
+		switch ((char)c) {
+		case '?': Usage(argv[0]);
+			/* not reached */
+			break;
+		case 'V': print_version(argv[0]);
+			/* not reached */
+			break;
+		case 'd': ilbd_enable_debug();
+			break;
+		default: Usage(argv[0]);
+			/* not reached */
+			break;
+		}
+	}
+
+	/*
+	 * Whenever the daemon starts, it needs to start with a clean
+	 * slate in the kernel. We need sys_ip_config privilege for
+	 * this.
+	 */
+	ilbd_reset_kernel_state();
+
+	/* Increase the limit on the number of file descriptors. */
+	set_rlim();
+
+	/*
+	 * ilbd daemon starts off as root, just so it can create
+	 * /var/run/daemon if one does not exist. After that is done
+	 * the daemon switches to "daemon" uid. This is similar to what
+	 * rpcbind does.
+	 */
+	if (mkdir(daemon_dir, DAEMON_DIR_MODE) == 0 || errno == EEXIST) {
+		(void) chmod(daemon_dir, DAEMON_DIR_MODE);
+		(void) chown(daemon_dir, DAEMON_UID, DAEMON_GID);
+	} else {
+		perror("main: mkdir failed");
+		exit(errno);
+	}
+	/*
+	 * Now lets switch ilbd as uid = daemon, gid = daemon with a
+	 * trimmed down privilege set
+	 */
+	if (__init_daemon_priv(PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
+	    DAEMON_UID, DAEMON_GID, PRIV_PROC_OWNER, PRIV_PROC_AUDIT,
+	    PRIV_NET_ICMPACCESS, PRIV_SYS_IP_CONFIG, NULL) == -1) {
+		(void) fprintf(stderr, "Insufficient privileges\n");
+		exit(EXIT_FAILURE);
+	}
+
+	/*
+	 * Opens a PF_UNIX socket to the client. No privilege needed
+	 * for this.
+	 */
+	s = ilbd_create_client_socket();
+
+	/*
+	 * Daemonify if ilbd is not running with -d option
+	 * Need proc_fork privilege for this
+	 */
+	if (!is_debugging_on()) {
+		logdebug("daemonizing...");
+		if (daemon(0, 0) != 0) {
+			logperror("daemon failed");
+			exit(EXIT_FAILURE);
+		}
+	}
+	(void) priv_set(PRIV_OFF, PRIV_INHERITABLE, PRIV_PROC_OWNER,
+	    PRIV_PROC_AUDIT, NULL);
+
+	/* if daemonified then set up syslog */
+	if (!is_debugging_on())
+		openlog("ilbd", LOG_PID, LOG_DAEMON);
+
+	i_ilbd_setup_lists();
+
+	main_loop(s);
+
+	/*
+	 * if we come here, then we experienced an error or a shutdown
+	 * indicator, so clean up after ourselves.
+	 */
+	logdebug("main(): terminating");
+
+	(void) remove(SOCKET_PATH);
+	ilbd_reset_kernel_state();
+
+	return (0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_nat.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,295 @@
+/*
+ * 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 <sys/types.h>
+#include <libilb.h>
+#include <inet/ilb.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <assert.h>
+#include <macros.h>
+#include "libilb_impl.h"
+#include "ilbd.h"
+
+/*
+ * We only allow one show nat/persist command running at any time.  Note that
+ * there is no lock for this since ilbd is single threaded.  And we only care
+ * about the pointer value of client, not its type.
+ *
+ * The following variables store the current client making the request.
+ */
+static void *nat_cur_cli;
+static void *sticky_cur_cli;
+
+/* Maximum number of NAT/sticky entries to request from kernel. */
+#define	NUM_ENTRIES	500
+
+/*
+ * Clear the current requesting client.  This will allow a new client
+ * to make a request.
+ */
+void
+ilbd_show_nat_cleanup(void)
+{
+	nat_cur_cli = NULL;
+}
+
+void
+ilbd_show_sticky_cleanup(void)
+{
+	sticky_cur_cli = NULL;
+}
+
+/*
+ * To show the kernel NAT table.
+ *
+ * cli: the client pointer making the request.
+ * ic: the client request.
+ * rbuf: reply buffer to be filled in.
+ * rbufsz: reply buffer size.
+ */
+ilb_status_t
+ilbd_show_nat(void *cli, const ilb_comm_t *ic, uint32_t *rbuf, size_t *rbufsz)
+{
+	ilb_show_info_t *req_si = (ilb_show_info_t *)&ic->ic_data;
+	ilb_list_nat_cmd_t *kcmd;
+	boolean_t start;
+	size_t tmp_rbufsz, kbufsz;
+	uint32_t max_num;
+	ilb_status_t ret;
+	int i;
+	ilb_show_info_t *reply;
+	ilb_nat_info_t *nat_ret;
+
+	/* For new client request, start from the beginning of the table. */
+	if (nat_cur_cli == NULL) {
+		nat_cur_cli = cli;
+		start = B_TRUE;
+	} else if (cli == nat_cur_cli) {
+		/*
+		 * Another request from client.  If the client does not
+		 * want to continue, reset the current client and reply OK.
+		 */
+		if (ic->ic_flags & ILB_COMM_END) {
+			ilbd_show_nat_cleanup();
+			ilbd_reply_ok(rbuf, rbufsz);
+			return (ILB_STATUS_OK);
+		}
+		start = B_FALSE;
+	} else {
+		/* A request is on-going, so reject a new client. */
+		return (ILB_STATUS_INPROGRESS);
+	}
+
+	tmp_rbufsz = *rbufsz;
+	ilbd_reply_ok(rbuf, rbufsz);
+	reply = (ilb_show_info_t *)&((ilb_comm_t *)rbuf)->ic_data;
+
+	/*
+	 * Calculate the max number of ilb_nat_info_t can be fitted in the
+	 * reply.
+	 */
+	*rbufsz += sizeof (ilb_show_info_t *);
+	tmp_rbufsz -= *rbufsz;
+	max_num = tmp_rbufsz / sizeof (ilb_nat_info_t);
+
+	/*
+	 * Calculate the exact number of entries we should request from kernel.
+	 */
+	max_num = min(req_si->sn_num, min(NUM_ENTRIES, max_num));
+
+	kbufsz = max_num * sizeof (ilb_nat_entry_t) +
+	    offsetof(ilb_list_nat_cmd_t, entries);
+	if ((kcmd = malloc(kbufsz)) == NULL) {
+		logdebug("ilbd_show_nat: malloc(cmd)");
+		ilbd_reply_err(rbuf, rbufsz, ILB_STATUS_ENOMEM);
+		return (ILB_STATUS_ENOMEM);
+	}
+
+	kcmd->cmd = ILB_LIST_NAT_TABLE;
+	kcmd->flags = start ? ILB_LIST_BEGIN : ILB_LIST_CONT;
+	kcmd->num_nat = max_num;
+	if ((ret = do_ioctl(kcmd, kbufsz)) != ILB_STATUS_OK) {
+		logperror("ilbd_show_nat: ioctl(ILB_LIST_NAT_TABLE)");
+		ilbd_reply_err(rbuf, rbufsz, ret);
+		free(kcmd);
+		return (ret);
+	}
+
+	reply->sn_num = kcmd->num_nat;
+	*rbufsz += reply->sn_num * sizeof (ilb_nat_info_t);
+
+	/*
+	 * It is the end of table, let the client know.  And the transaction
+	 * is done.
+	 */
+	if (kcmd->flags & ILB_LIST_END) {
+		nat_cur_cli = NULL;
+	} else {
+		/*
+		 * ilbd_reply_ok() sets ic_flags to ILB_COMM_END by default.
+		 * Need to clear it here.
+		 */
+		((ilb_comm_t *)rbuf)->ic_flags = 0;
+	}
+
+	nat_ret = (ilb_nat_info_t *)&reply->sn_data;
+
+	for (i = 0; i < kcmd->num_nat; i++) {
+		ilb_nat_entry_t *nat;
+
+		nat = &kcmd->entries[i];
+
+		nat_ret->nat_proto = nat->proto;
+
+		nat_ret->nat_in_local = nat->in_local;
+		nat_ret->nat_in_global = nat->in_global;
+		nat_ret->nat_out_local = nat->out_local;
+		nat_ret->nat_out_global = nat->out_global;
+
+		nat_ret->nat_in_local_port = nat->in_local_port;
+		nat_ret->nat_in_global_port = nat->in_global_port;
+		nat_ret->nat_out_local_port = nat->out_local_port;
+		nat_ret->nat_out_global_port = nat->out_global_port;
+
+		nat_ret++;
+	}
+
+end:
+	free(kcmd);
+	return (ret);
+}
+
+/*
+ * To show the kernel sticky table.
+ *
+ * cli: the client pointer making the request.
+ * req_si: information about the show-persist request.
+ * rbuf: reply buffer to be filled in.
+ * rbufsz: reply buffer size.
+ */
+ilb_status_t
+ilbd_show_sticky(void *cli, const ilb_comm_t *ic, uint32_t *rbuf,
+    size_t *rbufsz)
+{
+	ilb_show_info_t *req_si = (ilb_show_info_t *)&ic->ic_data;
+	ilb_list_sticky_cmd_t *kcmd;
+	boolean_t start;
+	size_t tmp_rbufsz, kbufsz;
+	uint32_t max_num;
+	ilb_status_t ret;
+	int i;
+	ilb_show_info_t *reply;
+	ilb_persist_info_t *st_ret;
+
+	/* For new client request, start from the beginning of the table. */
+	if (sticky_cur_cli == NULL) {
+		sticky_cur_cli = cli;
+		start = B_TRUE;
+	} else if (cli == sticky_cur_cli) {
+		/*
+		 * Another request from client.  If the client does not
+		 * want to continue, reset the current client and reply OK.
+		 */
+		if (ic->ic_flags & ILB_COMM_END) {
+			ilbd_show_sticky_cleanup();
+			ilbd_reply_ok(rbuf, rbufsz);
+			return (ILB_STATUS_OK);
+		}
+		start = B_FALSE;
+	} else {
+		/* A request is on-going, so reject a new client. */
+		return (ILB_STATUS_INPROGRESS);
+	}
+
+	tmp_rbufsz = *rbufsz;
+	ilbd_reply_ok(rbuf, rbufsz);
+	reply = (ilb_show_info_t *)&((ilb_comm_t *)rbuf)->ic_data;
+
+	/*
+	 * Calculate the max number of ilb_persist_info_t can be fitted in the
+	 * reply.
+	 */
+	*rbufsz += sizeof (ilb_show_info_t *);
+	tmp_rbufsz -= *rbufsz;
+	max_num = tmp_rbufsz / sizeof (ilb_persist_info_t);
+
+	/*
+	 * Calculate the exact number of entries we should request from kernel.
+	 */
+	max_num = min(req_si->sn_num, min(NUM_ENTRIES, max_num));
+
+	kbufsz = max_num * sizeof (ilb_sticky_entry_t) +
+	    offsetof(ilb_list_sticky_cmd_t, entries);
+	if ((kcmd = malloc(kbufsz)) == NULL) {
+		logdebug("ilbd_show_nat: malloc(cmd)");
+		ilbd_reply_err(rbuf, rbufsz, ILB_STATUS_ENOMEM);
+		return (ILB_STATUS_ENOMEM);
+	}
+
+	kcmd->cmd = ILB_LIST_STICKY_TABLE;
+	kcmd->flags = start ? ILB_LIST_BEGIN : ILB_LIST_CONT;
+	kcmd->num_sticky = max_num;
+	if ((ret = do_ioctl(kcmd, kbufsz)) != ILB_STATUS_OK) {
+		logperror("ilbd_show_nat: ioctl(ILB_LIST_STICKY_TABLE)");
+		ilbd_reply_err(rbuf, rbufsz, ret);
+		free(kcmd);
+		return (ret);
+	}
+
+	reply->sn_num = kcmd->num_sticky;
+	*rbufsz += reply->sn_num * sizeof (ilb_persist_info_t);
+
+	if (kcmd->flags & ILB_LIST_END) {
+		sticky_cur_cli = NULL;
+	} else {
+		/*
+		 * ilbd_reply_ok() sets ic_flags to ILB_COMM_END by default.
+		 * Need to clear it here.
+		 */
+		((ilb_comm_t *)rbuf)->ic_flags = 0;
+	}
+
+	st_ret = (ilb_persist_info_t *)&reply->sn_data;
+
+	for (i = 0; i < kcmd->num_sticky; i++) {
+		ilb_sticky_entry_t *st;
+
+		st = &kcmd->entries[i];
+
+		(void) strlcpy(st_ret->persist_rule_name, st->rule_name,
+		    ILB_NAMESZ);
+		st_ret->persist_req_addr = st->req_addr;
+		st_ret->persist_srv_addr = st->srv_addr;
+		st_ret++;
+	}
+
+end:
+	free(kcmd);
+	return (ret);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_rules.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,1357 @@
+/*
+ * 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 <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
+
+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_IMPL_NAT ||
+		    rinfo->rl_topo == ILB_TOPO_IMPL_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[scf_val_len]; /* algo:topo */
+	char			pbuf[scf_val_len]; /* protocol */
+	char			pxbuf[scf_val_len]; /* prxy src range */
+	char			hcpbuf[scf_val_len]; /* hcport: num or "ANY" */
+	char			valstr1[scf_val_len];
+	char			valstr2[scf_val_len];
+	char			addrstr_buf[INET6_ADDRSTRLEN];
+	char			addrstr_buf1[INET6_ADDRSTRLEN];
+	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:
+		aobuf[0] = '\0';
+		pbuf[0] = '\0';
+		valstr1[0] = '\0';
+		valstr2[0] = '\0';
+		hcpbuf[0] = '\0';
+
+		event->adt_ilb_create_rule.auth_used = NET_ILB_CONFIG_AUTH;
+
+		/* Fill in virtual IP address */
+		addrstr_buf[0] = '\0';
+		ilbd_addr2str(&rlinfo->rl_vip, addrstr_buf,
+		    sizeof (addrstr_buf));
+		event->adt_ilb_create_rule.virtual_ipaddress = addrstr_buf;
+
+		/* 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, sizeof (pbuf), "UDP");
+		else
+			(void) snprintf(pbuf, sizeof (pbuf), "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, sizeof (aobuf), "%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)  {
+			ilbd_addr2str(&rlinfo->rl_nat_src_start, addrstr_buf,
+			    sizeof (addrstr_buf));
+			if (&rlinfo->rl_nat_src_end == 0) {
+				/* Single address */
+				(void) snprintf(pxbuf, sizeof (pxbuf),
+				    "%s", addrstr_buf);
+			} else {
+				/* address range */
+				ilbd_addr2str(&rlinfo->rl_nat_src_end,
+				    addrstr_buf1, sizeof (addrstr_buf1));
+				(void) snprintf(pxbuf, sizeof (pxbuf),
+				    "%s-%s", addrstr_buf, addrstr_buf1);
+			}
+			event->adt_ilb_create_rule.proxy_src = pxbuf;
+		}
+
+		/*
+		 * 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, sizeof (hcpbuf), "%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, sizeof (hcpbuf), "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);
+	(void) adt_end_session(ah);
+}
+
+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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_scf.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,1692 @@
+/*
+ * 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 <stdio.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/list.h>
+#include <libilb.h>
+#include <assert.h>
+#include <libscf.h>
+#include "libilb_impl.h"
+#include "ilbd.h"
+
+#define	ILBD_PG_NAME_RULE "rule_"
+#define	ILBD_PG_NAME_SG "sg_"
+#define	ILBD_PG_NAME_HC "hc_"
+#define	ILBD_SVC_FMRI "svc:/network/loadbalancer/ilb"
+#define	ILBD_INST_NAME "default"
+
+typedef enum {
+	ILBD_RULE_STATUS,
+	ILBD_RULE_VIP,
+	ILBD_RULE_PROTO,
+	ILBD_RULE_PORT,
+	ILBD_RULE_ALGO,
+	ILBD_RULE_TOPO,
+	ILBD_RULE_NAT_STR,
+	ILBD_RULE_NAT_END,
+	ILBD_RULE_STI_MASK,
+	ILBD_RULE_SGNAME,
+	ILBD_RULE_HCNAME,
+	ILBD_RULE_HCPORT,
+	ILBD_RULE_HCPFLAG,
+	ILBD_RULE_DRAINTIME,
+	ILBD_RULE_NAT_TO,
+	ILBD_RULE_PERS_TO,
+
+	ILBD_SG_SERVER,
+
+	ILBD_HC_TEST,
+	ILBD_HC_TIMEOUT,
+	ILBD_HC_INTERVAL,
+	ILBD_HC_DEF_PING,
+	ILBD_HC_COUNT,
+
+	ILBD_VAR_INVALID
+} ilbd_var_type_t;
+
+typedef struct prop_tbl_entry {
+	ilbd_var_type_t val_type;
+	const char *scf_propname;
+	scf_type_t scf_proptype;
+} prop_tbl_entry_t;
+
+/*
+ * this table contains a map of all SCF properties, including rules,
+ * servergroups and health checks. The place to add new property needs to be
+ * watched carefully. When new properties are added, corresponding *VAR_NUM
+ * needs to be adjusted to reflect the correct index of the table
+ */
+prop_tbl_entry_t prop_tbl[] = {
+	/* entried for rule */
+	{ILBD_RULE_STATUS, "status", SCF_TYPE_BOOLEAN},
+	/* SCF_TYPE_NET_ADDR_V4 or SCF_TYPE_NET_ADDR_V6 */
+	{ILBD_RULE_VIP, "vip", SCF_TYPE_INVALID},
+	{ILBD_RULE_PROTO, "protocol", SCF_TYPE_ASTRING},
+	{ILBD_RULE_PORT, "port", SCF_TYPE_ASTRING},
+	{ILBD_RULE_ALGO, "ilb-algo", SCF_TYPE_ASTRING},
+	{ILBD_RULE_TOPO, "ilb-type", SCF_TYPE_ASTRING},
+	{ILBD_RULE_NAT_STR, "ilb-nat-start", SCF_TYPE_INVALID},
+	{ILBD_RULE_NAT_END, "ilb-nat-end", SCF_TYPE_INVALID},
+	{ILBD_RULE_STI_MASK, "ilb-sti-mask", SCF_TYPE_INVALID},
+	{ILBD_RULE_SGNAME, "servergroup", SCF_TYPE_ASTRING},
+	{ILBD_RULE_HCNAME, "healthcheck", SCF_TYPE_ASTRING},
+	{ILBD_RULE_HCPORT, "hc-port", SCF_TYPE_INTEGER},
+	{ILBD_RULE_HCPFLAG, "hcp-flag", SCF_TYPE_INTEGER},
+	{ILBD_RULE_DRAINTIME, "drain-time", SCF_TYPE_INTEGER},
+	{ILBD_RULE_NAT_TO, "nat-timeout", SCF_TYPE_INTEGER},
+	{ILBD_RULE_PERS_TO, "pers-timeout", SCF_TYPE_INTEGER},
+	/* add new rule related prop here */
+	/* entries for sg */
+	{ILBD_SG_SERVER, "server", SCF_TYPE_ASTRING},
+	/* add new sg related prop here */
+	/* entries for hc */
+	{ILBD_HC_TEST, "test", SCF_TYPE_ASTRING},
+	{ILBD_HC_TIMEOUT, "timeout", SCF_TYPE_INTEGER},
+	{ILBD_HC_INTERVAL, "interval", SCF_TYPE_INTEGER},
+	{ILBD_HC_DEF_PING, "ping", SCF_TYPE_BOOLEAN},
+	/* add new hc related prop here */
+	{ILBD_HC_COUNT, "count", SCF_TYPE_INTEGER}
+};
+
+#define	ILBD_PROP_VAR_NUM (ILBD_HC_COUNT + 1)
+#define	ILBD_RULE_VAR_NUM (ILBD_SG_SERVER)
+#define	ILBD_SG_VAR_NUM (ILBD_HC_TEST - ILBD_SG_SERVER)
+#define	ILBD_HC_VAR_NUM (ILBD_PROP_VAR_NUM - ILBD_HC_TEST)
+
+static ilb_status_t ilbd_scf_set_prop(scf_propertygroup_t *, const char *,
+    scf_type_t, scf_value_t *);
+static ilb_status_t ilbd_scf_retrieve_pg(const char *, scf_propertygroup_t **,
+    boolean_t);
+static ilb_status_t ilbd_scf_delete_pg(scf_propertygroup_t *);
+static ilb_status_t ilbd_scf_get_prop_val(scf_propertygroup_t *, const char *,
+    scf_value_t **);
+
+#define	MIN(a, b)	((a) < (b) ? (a) : (b))
+
+int
+ilbd_scf_limit(int type)
+{
+	return (MIN(scf_limit(type), 120));
+}
+
+/*
+ * Translate libscf error to libilb status
+ */
+ilb_status_t
+ilbd_scf_err_to_ilb_err()
+{
+	switch (scf_error()) {
+	case SCF_ERROR_NONE:
+		return (ILB_STATUS_OK);
+	case SCF_ERROR_HANDLE_MISMATCH:
+	case SCF_ERROR_HANDLE_DESTROYED:
+	case SCF_ERROR_VERSION_MISMATCH:
+	case SCF_ERROR_NOT_BOUND:
+	case SCF_ERROR_CONSTRAINT_VIOLATED:
+	case SCF_ERROR_NOT_SET:
+	case SCF_ERROR_TYPE_MISMATCH:
+	case SCF_ERROR_INVALID_ARGUMENT:
+		return (ILB_STATUS_EINVAL);
+	case SCF_ERROR_NO_MEMORY:
+	case SCF_ERROR_NO_RESOURCES:
+		return (ILB_STATUS_ENOMEM);
+	case SCF_ERROR_NOT_FOUND:
+	case SCF_ERROR_DELETED:
+		return (ILB_STATUS_ENOENT);
+	case SCF_ERROR_EXISTS:
+		return (ILB_STATUS_EEXIST);
+	case SCF_ERROR_PERMISSION_DENIED:
+		return (ILB_STATUS_PERMIT);
+	case SCF_ERROR_CALLBACK_FAILED:
+		return (ILB_STATUS_CALLBACK);
+	case SCF_ERROR_IN_USE:
+		return (ILB_STATUS_INUSE);
+	default:
+		return (ILB_STATUS_INTERNAL);
+	}
+}
+
+static void
+ilbd_name_to_scfpgname(ilbd_scf_pg_type_t pg_type, const char *pgname,
+    char *scf_pgname)
+{
+	switch (pg_type) {
+	case ILBD_SCF_RULE:
+		(void) snprintf(scf_pgname, ILBD_MAX_NAME_LEN,
+		    ILBD_PG_NAME_RULE "%s", pgname);
+		return;
+	case ILBD_SCF_SG:
+		(void) snprintf(scf_pgname, ILBD_MAX_NAME_LEN,
+		    ILBD_PG_NAME_SG "%s", pgname);
+		return;
+	case ILBD_SCF_HC:
+		(void) snprintf(scf_pgname, ILBD_MAX_NAME_LEN,
+		    ILBD_PG_NAME_HC "%s", pgname);
+		return;
+	/* Should not happen.  Log it and put ILB service in maintenance. */
+	default:
+		logerr("ilbd_name_to_scfpgname: invalid pg type %d for pg %s",
+		    pg_type, pgname);
+		(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
+		exit(EXIT_FAILURE);
+		return;
+	}
+}
+
+static void
+ilbd_scf_destroy(scf_handle_t *h, scf_service_t *s, scf_instance_t *inst,
+    scf_propertygroup_t *pg)
+{
+	if (pg != NULL)
+		scf_pg_destroy(pg);
+	if (inst != NULL)
+		scf_instance_destroy(inst);
+	if (s != NULL)
+		scf_service_destroy(s);
+	if (h != NULL)
+		scf_handle_destroy(h);
+}
+
+
+static ilb_status_t
+ilbd_scf_get_inst(scf_handle_t **h, scf_service_t **svc, scf_instance_t **inst)
+{
+	if ((*h = scf_handle_create(SCF_VERSION)) == NULL)
+		return (ILB_STATUS_INTERNAL);
+
+	if (scf_handle_bind(*h) != 0) {
+		ilbd_scf_destroy(*h, NULL, NULL, NULL);
+		return (ilbd_scf_err_to_ilb_err());
+	}
+
+	if ((*svc = scf_service_create(*h)) == NULL) {
+		ilbd_scf_destroy(*h, NULL, NULL, NULL);
+		return (ilbd_scf_err_to_ilb_err());
+	}
+
+	if (scf_handle_decode_fmri(*h, ILBD_SVC_FMRI, NULL, *svc, NULL, NULL,
+	    NULL, SCF_DECODE_FMRI_EXACT) != 0) {
+		ilbd_scf_destroy(*h, *svc, NULL, NULL);
+		return (ilbd_scf_err_to_ilb_err());
+	}
+
+	if ((*inst = scf_instance_create(*h)) == NULL) {
+		ilbd_scf_destroy(*h, *svc, NULL, NULL);
+		return (ilbd_scf_err_to_ilb_err());
+	}
+
+	if (scf_service_get_instance(*svc, ILBD_INST_NAME, *inst) != 0) {
+		ilbd_scf_destroy(*h, *svc, *inst, NULL);
+		return (ilbd_scf_err_to_ilb_err());
+	}
+	return (ILB_STATUS_OK);
+}
+
+/*
+ * If create is set, create a new prop group, destroy the old one if exists.
+ * If create not set, try to find the prop group with given name.
+ * The created or found entry is returned as *pg.
+ * Caller frees *pg and its handle scf_pg_handle(pg)
+ */
+static ilb_status_t
+ilbd_scf_retrieve_pg(const char *pgname, scf_propertygroup_t **pg,
+    boolean_t create)
+{
+	scf_instance_t *inst;
+	scf_handle_t *h;
+	scf_service_t *svc;
+	ilb_status_t ret;
+
+	ret = ilbd_scf_get_inst(&h, &svc, &inst);
+	if (ret != ILB_STATUS_OK)
+		return (ret);
+
+	*pg = scf_pg_create(h);
+	if (*pg == NULL)
+		return (ILB_STATUS_INTERNAL);
+
+	if (scf_instance_get_pg(inst, pgname, *pg) != 0) {
+		if (scf_error() != SCF_ERROR_NOT_FOUND ||
+		    (scf_error() == SCF_ERROR_NOT_FOUND && (!create))) {
+			ilbd_scf_destroy(h, svc, inst, *pg);
+			*pg = NULL;
+			return (ilbd_scf_err_to_ilb_err());
+		}
+	} else {
+		/*
+		 * Found pg, don't want to create, return EEXIST.  Note that
+		 * h cannot be destroyed here since the caller needs to use it.
+		 * The caller gets it by calling scf_pg_handle().
+		 */
+		if (!create) {
+			ilbd_scf_destroy(NULL, svc, inst, NULL);
+			return (ILB_STATUS_EEXIST);
+		}
+		/* found pg, need to create, destroy the existing one */
+		else
+			(void) ilbd_scf_delete_pg(*pg);
+	}
+
+	if (create) {
+		if (scf_instance_add_pg(inst, pgname,
+		    SCF_GROUP_APPLICATION, 0, *pg) != 0) {
+			ilbd_scf_destroy(h, svc, inst, *pg);
+			*pg = NULL;
+			return (ilbd_scf_err_to_ilb_err());
+		}
+	}
+
+	/*
+	 * Note that handle cannot be destroyed here, caller sometimes needs
+	 * to use it.  It gets the handle by calling scf_pg_handle().
+	 */
+	ilbd_scf_destroy(NULL, svc, inst, NULL);
+	return (ILB_STATUS_OK);
+}
+
+struct algo_tbl_entry {
+	ilb_algo_t algo_type;
+	const char *algo_str;
+} algo_tbl[] = {
+	{ILB_ALG_ROUNDROBIN, "ROUNDROBIN"},
+	{ILB_ALG_HASH_IP, "HASH-IP"},
+	{ILB_ALG_HASH_IP_SPORT, "HASH-IP-PORT"},
+	{ILB_ALG_HASH_IP_VIP, "HASH-IP-VIP"}
+};
+
+#define	ILBD_ALGO_TBL_SIZE (sizeof (algo_tbl) / \
+	sizeof (*algo_tbl))
+
+void
+ilbd_algo_to_str(ilb_algo_t algo_type, char *valstr)
+{
+	int i;
+
+	for (i = 0; i < ILBD_ALGO_TBL_SIZE; i++) {
+		if (algo_type == algo_tbl[i].algo_type) {
+			(void) strlcpy(valstr, algo_tbl[i].algo_str,
+			    ILBD_MAX_VALUE_LEN);
+			return;
+		}
+	}
+	logerr("ilbd_algo_to_str: algo not found");
+}
+
+static void
+ilbd_scf_str_to_algo(ilb_algo_t *algo_type, char *valstr)
+{
+	int i;
+
+	for (i = 0; i < ILBD_ALGO_TBL_SIZE; i++) {
+		if (strcmp(valstr, algo_tbl[i].algo_str) == 0) {
+			*algo_type = algo_tbl[i].algo_type;
+			return;
+		}
+	}
+	logerr("ilbd_scf_str_to_algo: algo not found");
+}
+
+struct topo_tbl_entry {
+	ilb_topo_t topo_type;
+	const char *topo_str;
+} topo_tbl[] = {
+	{ILB_TOPO_DSR, "DSR"},
+	{ILB_TOPO_NAT, "NAT"},
+	{ILB_TOPO_HALF_NAT, "HALF-NAT"}
+};
+
+#define	ILBD_TOPO_TBL_SIZE (sizeof (topo_tbl) / \
+	sizeof (*topo_tbl))
+
+void
+ilbd_topo_to_str(ilb_topo_t topo_type, char *valstr)
+{
+	int i;
+
+	for (i = 0; i < ILBD_TOPO_TBL_SIZE; i++) {
+		if (topo_type == topo_tbl[i].topo_type) {
+			(void) strlcpy(valstr, topo_tbl[i].topo_str,
+			    ILBD_MAX_VALUE_LEN);
+			return;
+		}
+	}
+	logerr("ilbd_scf_topo_to_str: topo not found");
+}
+
+static void
+ilbd_scf_str_to_topo(ilb_topo_t *topo_type, char *valstr)
+{
+	int i;
+
+	for (i = 0; i < ILBD_TOPO_TBL_SIZE; i++) {
+		if (strcmp(valstr, topo_tbl[i].topo_str) == 0) {
+			*topo_type = topo_tbl[i].topo_type;
+			return;
+		}
+	}
+	logerr("ilbd_scf_str_to_topo: topo not found");
+}
+
+static void
+ilbd_get_svr_field(char *valstr, struct in6_addr *sgs_addr,
+    int32_t *min_port, int32_t *max_port, int32_t *sgs_flags)
+{
+	char *ipaddr, *ipverstr, *portstr, *flagstr;
+	int ip_ver;
+	ilb_ip_addr_t temp_ip;
+	void *addrptr;
+	char *max_portstr;
+
+	ipaddr = strtok(valstr, ";");
+	ipverstr = strtok(NULL, ";");
+	portstr = strtok(NULL, ";");
+	flagstr = strtok(NULL, ";");
+
+	if (ipaddr == NULL || ipverstr == NULL || portstr == NULL ||
+	    flagstr == NULL) {
+		logerr("%s: invalid server fields", __func__);
+		(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
+		exit(EXIT_FAILURE);
+	}
+	ip_ver = atoi(ipverstr);
+	addrptr = (ip_ver == AF_INET) ? (void *)&temp_ip.ia_v4 :
+	    (void *)&temp_ip.ia_v6;
+	if (inet_pton(ip_ver, ipaddr, addrptr) == NULL) {
+		logerr("ilbd_get_svr_field: inet_pton failed");
+		return;
+	}
+
+	if (ip_ver == AF_INET) {
+		IN6_INADDR_TO_V4MAPPED(&(temp_ip.ia_v4), sgs_addr);
+	} else {
+		(void) memcpy(sgs_addr, &(temp_ip.ia_v6),
+		    sizeof (struct in6_addr));
+	}
+
+	*sgs_flags = atoi(flagstr);
+	*min_port = atoi(strtok(portstr, "-"));
+	*min_port = ntohs(*min_port);
+	max_portstr = strtok(NULL, "-");
+	if (max_portstr != NULL) {
+		*max_port = atoi(max_portstr);
+		*max_port = ntohs(*max_port);
+	}
+}
+
+/*
+ * Convert the info of a server to its SCF string value representation.
+ * Argument value is assumed to be of size ILBD_MAX_VALUE_LEN.
+ */
+static void
+ilbd_srv_scf_val(ilbd_srv_t *srv, char *value)
+{
+	char ipstr[INET6_ADDRSTRLEN];
+	int ipver;
+
+	if (GET_AF(&srv->isv_addr) == AF_INET) {
+		struct in_addr v4_addr;
+
+		IN6_V4MAPPED_TO_INADDR(&srv->isv_addr, &v4_addr);
+		(void) inet_ntop(AF_INET, &v4_addr, ipstr, sizeof (ipstr));
+		ipver = AF_INET;
+	} else {
+		(void) inet_ntop(AF_INET6, &srv->isv_addr, ipstr,
+		    sizeof (ipstr));
+		ipver = AF_INET6;
+	}
+	(void) snprintf(value, ILBD_MAX_VALUE_LEN, "%s;%d;%d-%d;%d",
+	    ipstr, ipver, ntohs(srv->isv_minport), ntohs(srv->isv_maxport),
+	    srv->isv_flags);
+}
+
+/* get the "ip:port:status" str of the #num server in the servergroup */
+ilb_status_t
+ilbd_get_svr_info(ilbd_sg_t *sg, int num, char *valstr, char *svrname)
+{
+	int i;
+	ilbd_srv_t *tmp_srv = NULL;
+
+	tmp_srv = list_head(&sg->isg_srvlist);
+	if (tmp_srv == NULL)
+		return (ILB_STATUS_ENOENT);
+
+	for (i = 0; i < num; i++)
+		tmp_srv = list_next(&sg->isg_srvlist, tmp_srv);
+
+	assert(tmp_srv != NULL);
+	if (valstr != NULL)
+		ilbd_srv_scf_val(tmp_srv, valstr);
+
+	if (svrname != NULL) {
+		(void) snprintf(svrname, ILBD_MAX_NAME_LEN, "server%d",
+		    tmp_srv->isv_id);
+	}
+
+	return (ILB_STATUS_OK);
+}
+
+/* convert a struct in6_addr to valstr */
+ilb_status_t
+ilbd_scf_ip_to_str(uint16_t ipversion, struct in6_addr *addr,
+    scf_type_t *scftype, char *valstr)
+{
+	size_t vallen;
+	ilb_ip_addr_t ipaddr;
+	void *addrptr;
+
+	vallen = (ipversion == AF_INET) ? INET_ADDRSTRLEN :
+	    INET6_ADDRSTRLEN;
+	if (scftype != NULL)
+		*scftype = (ipversion == AF_INET) ? SCF_TYPE_NET_ADDR_V4 :
+		    SCF_TYPE_NET_ADDR_V6;
+
+	IP_COPY_IMPL_2_CLI(addr, &ipaddr);
+	addrptr = (ipversion == AF_INET) ?
+	    (void *)&ipaddr.ia_v4 : (void *)&ipaddr.ia_v6;
+	(void) inet_ntop(ipversion, (void *)addrptr, valstr, vallen);
+	return (ILB_STATUS_OK);
+}
+
+/*
+ * This function takes a ilbd internal data struct and translate its value to
+ * scf value. The data struct is passed in within "data".
+ * Upon successful return, the scf val will be stored in "val" and the scf type
+ * will be returned in "scftype" if scftype != NULL, the number of values
+ * translated will be in "numval"
+ * If it failed, no data will be written to SCF
+ */
+static ilb_status_t
+ilbd_data_to_scfval(ilbd_scf_pg_type_t pg_type, ilbd_var_type_t type,
+    scf_handle_t *h, void *data, scf_value_t ***val, scf_type_t *scftype,
+    int *numval)
+{
+	scf_value_t *v, **varray = NULL;
+	int ret = ILB_STATUS_OK;
+	int i;
+	int scf_val_len = ILBD_MAX_VALUE_LEN;
+	char valstr[scf_val_len];
+	int valint;
+	uint8_t valbool = 0;
+	ilbd_rule_t *r_ent = NULL;
+	ilbd_sg_t *s_ent = NULL;
+	ilbd_hc_t *h_ent = NULL;
+
+	switch (pg_type) {
+	case ILBD_SCF_RULE:
+		r_ent = (ilbd_rule_t *)data;
+		break;
+	case ILBD_SCF_SG:
+		s_ent = (ilbd_sg_t *)data;
+		break;
+	case ILBD_SCF_HC:
+		h_ent = (ilbd_hc_t *)data;
+		break;
+	}
+
+	v = scf_value_create(h);
+	if (v == NULL)
+		return (ILB_STATUS_INTERNAL);
+
+	switch (type) {
+	case ILBD_RULE_STATUS:
+		valbool = r_ent->irl_flags & ILB_FLAGS_RULE_ENABLED;
+		break;
+	case ILBD_RULE_VIP:
+		ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion, &r_ent->irl_vip,
+		    scftype, valstr);
+		if (ret != ILB_STATUS_OK) {
+			scf_value_destroy(v);
+			return (ret);
+		}
+		break;
+	case ILBD_RULE_PROTO: {
+		struct protoent *protoent;
+
+		protoent = getprotobynumber(r_ent->irl_proto);
+		(void) strlcpy(valstr, protoent->p_name, sizeof (valstr));
+		break;
+	}
+	case ILBD_RULE_PORT:
+		(void) snprintf(valstr, sizeof (valstr), "%d-%d",
+		    r_ent->irl_minport, r_ent->irl_maxport);
+		break;
+	case ILBD_RULE_ALGO:
+		ilbd_algo_to_str(r_ent->irl_algo, valstr);
+		break;
+	case ILBD_RULE_TOPO:
+		ilbd_topo_to_str(r_ent->irl_topo, valstr);
+		break;
+	case ILBD_RULE_NAT_STR:
+		ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion,
+		    &r_ent->irl_nat_src_start, scftype, valstr);
+		if (ret != ILB_STATUS_OK) {
+			scf_value_destroy(v);
+			return (ret);
+		}
+		break;
+	case ILBD_RULE_NAT_END:
+		ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion,
+		    &r_ent->irl_nat_src_end, scftype, valstr);
+		if (ret != ILB_STATUS_OK) {
+			scf_value_destroy(v);
+			return (ret);
+		}
+		break;
+	case ILBD_RULE_STI_MASK:
+		ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion,
+		    &r_ent->irl_stickymask, scftype, valstr);
+		if (ret != ILB_STATUS_OK) {
+			scf_value_destroy(v);
+			return (ret);
+		}
+		break;
+	case ILBD_RULE_SGNAME:
+		(void) strlcpy(valstr, r_ent->irl_sgname, sizeof (valstr));
+		break;
+	case ILBD_RULE_HCNAME:
+		if (r_ent->irl_hcname[0] != '\0')
+			(void) strlcpy(valstr, r_ent->irl_hcname,
+			    sizeof (valstr));
+		else
+			bzero(valstr, ILBD_MAX_VALUE_LEN);
+		break;
+	case ILBD_RULE_HCPORT:
+		valint = r_ent->irl_hcport;
+		break;
+	case ILBD_RULE_HCPFLAG:
+		valint = r_ent->irl_hcpflag;
+		break;
+	case ILBD_RULE_DRAINTIME:
+		valint = r_ent->irl_conndrain;
+		break;
+	case ILBD_RULE_NAT_TO:
+		valint = r_ent->irl_nat_timeout;
+		break;
+	case ILBD_RULE_PERS_TO:
+		valint = r_ent->irl_sticky_timeout;
+		break;
+
+	case ILBD_SG_SERVER:
+		if (s_ent->isg_srvcount == 0) {
+			(void) strlcpy(valstr, "EMPTY_SERVERGROUP",
+			    sizeof (valstr));
+			break;
+		}
+
+		varray = calloc(sizeof (*varray), s_ent->isg_srvcount);
+		if (varray == NULL) {
+			scf_value_destroy(v);
+			return (ILB_STATUS_ENOMEM);
+		}
+
+		for (i = 0; i < s_ent->isg_srvcount; i++) {
+			if (v == NULL) {
+				for (i--; i >= 0; i--)
+					scf_value_destroy(varray[i]);
+				free(varray);
+				return (ILB_STATUS_ENOMEM);
+			}
+
+			ret = ilbd_get_svr_info(s_ent, i, valstr, NULL);
+			if (ret != ILB_STATUS_OK) {
+				scf_value_destroy(v);
+				for (i--; i >= 0; i--)
+					scf_value_destroy(varray[i]);
+				free(varray);
+				return (ret);
+			}
+			(void) scf_value_set_astring(v, valstr);
+			varray[i] = v;
+			v = scf_value_create(h);
+		}
+		/* the last 'v' we created will go unused, so drop it */
+		scf_value_destroy(v);
+		*numval = s_ent->isg_srvcount;
+		*val = varray;
+		return (ret);
+	case ILBD_HC_TEST:
+		(void) strlcpy(valstr, h_ent->ihc_test, sizeof (valstr));
+		break;
+	case ILBD_HC_TIMEOUT:
+		valint = h_ent->ihc_timeout;
+		break;
+	case ILBD_HC_INTERVAL:
+		valint = h_ent->ihc_interval;
+		break;
+	case ILBD_HC_DEF_PING:
+		valbool = h_ent->ihc_def_ping;
+		break;
+	case ILBD_HC_COUNT:
+		valint = h_ent->ihc_count;
+		break;
+	}
+
+	switch (*scftype) {
+	case SCF_TYPE_BOOLEAN:
+		scf_value_set_boolean(v, valbool);
+		break;
+	case SCF_TYPE_ASTRING:
+		(void) scf_value_set_astring(v, valstr);
+		break;
+	case SCF_TYPE_INTEGER:
+		scf_value_set_integer(v, valint);
+		break;
+	case SCF_TYPE_NET_ADDR_V4:
+		(void) scf_value_set_from_string(v, SCF_TYPE_NET_ADDR_V4,
+		    valstr);
+		break;
+	case SCF_TYPE_NET_ADDR_V6:
+		(void) scf_value_set_from_string(v, SCF_TYPE_NET_ADDR_V6,
+		    valstr);
+		break;
+	}
+
+	varray = calloc(1, sizeof (*varray));
+	if (varray == NULL) {
+		scf_value_destroy(v);
+		return (ILB_STATUS_ENOMEM);
+	}
+	varray[0] = v;
+	*val = varray;
+	*numval = 1;
+
+	return (ret);
+}
+
+/*
+ * create a scf property group
+ */
+ilb_status_t
+ilbd_create_pg(ilbd_scf_pg_type_t pg_type, void *data)
+{
+	ilb_status_t ret;
+	char *pgname;
+	scf_propertygroup_t *pg = NULL;
+	scf_value_t **val;
+	scf_handle_t *h;
+	int scf_name_len = ILBD_MAX_NAME_LEN;
+	char scfpgname[scf_name_len];
+	int i, i_st, i_end;
+
+	switch (pg_type) {
+	case ILBD_SCF_RULE: {
+		ilbd_rule_t *r_ent = (ilbd_rule_t *)data;
+
+		pgname = r_ent->irl_name;
+		i_st = 0;
+		i_end = ILBD_RULE_VAR_NUM;
+		break;
+	}
+	case ILBD_SCF_SG: {
+		ilbd_sg_t *s_ent = (ilbd_sg_t *)data;
+
+		pgname = s_ent->isg_name;
+		i_st = ILBD_RULE_VAR_NUM;
+		i_end = ILBD_RULE_VAR_NUM + ILBD_SG_VAR_NUM;
+		break;
+	}
+	case ILBD_SCF_HC: {
+		ilbd_hc_t *h_ent = (ilbd_hc_t *)data;
+
+		pgname = h_ent->ihc_name;
+		i_st = ILBD_RULE_VAR_NUM + ILBD_SG_VAR_NUM;
+		i_end = ILBD_PROP_VAR_NUM;
+		break;
+	}
+	default:
+		logdebug("ilbd_create_pg: invalid pg type %d for pg %s",
+		    pg_type, pgname);
+		return (ILB_STATUS_EINVAL);
+	}
+
+	ilbd_name_to_scfpgname(pg_type, pgname, scfpgname);
+
+	ret = ilbd_scf_retrieve_pg(scfpgname, &pg, B_TRUE);
+	if (ret != ILB_STATUS_OK)
+		return (ret);
+	h = scf_pg_handle(pg);
+
+	/* fill in props */
+	for (i = i_st; i < i_end; i++) {
+		int num, j;
+		int scf_name_len = ILBD_MAX_NAME_LEN;
+		char propname[scf_name_len];
+		scf_type_t scftype = prop_tbl[i].scf_proptype;
+
+		ret = ilbd_data_to_scfval(pg_type, prop_tbl[i].val_type, h,
+		    data, &val, &scftype, &num);
+		if (ret != ILB_STATUS_OK)
+			goto done;
+
+		for (j = 0; j < num; j++) {
+			if (pg_type == ILBD_SCF_SG) {
+				ret = ilbd_get_svr_info(data, j, NULL,
+				    propname);
+				if (ret == ILB_STATUS_ENOENT) {
+					(void) strlcpy(propname, "EMPTY_SERVER",
+					    ILBD_MAX_NAME_LEN);
+				}
+				ret = ilbd_scf_set_prop(pg, propname,
+				    scftype, val[j]);
+			} else {
+				ret = ilbd_scf_set_prop(pg,
+				    prop_tbl[i].scf_propname, scftype, val[j]);
+			}
+			scf_value_destroy(val[j]);
+		}
+		free(val);
+	}
+
+done:
+	ilbd_scf_destroy(h, NULL, NULL, pg);
+	return (ret);
+}
+
+/*
+ * destroy a scf property group
+ */
+static ilb_status_t
+ilbd_scf_delete_pg(scf_propertygroup_t *pg)
+{
+	if (scf_pg_delete(pg) != 0)
+		return (ilbd_scf_err_to_ilb_err());
+	return (ILB_STATUS_OK);
+}
+
+/* sg can have same name as rule */
+ilb_status_t
+ilbd_destroy_pg(ilbd_scf_pg_type_t pg_t, const char *pgname)
+{
+	ilb_status_t ret;
+	scf_propertygroup_t *pg;
+	int scf_name_len = ILBD_MAX_NAME_LEN;
+	char scfname[scf_name_len];
+
+	ilbd_name_to_scfpgname(pg_t, pgname, scfname);
+
+	ret = ilbd_scf_retrieve_pg(scfname, &pg, B_FALSE);
+	if (ret != ILB_STATUS_EEXIST)
+		return (ret);
+
+	ret = ilbd_scf_delete_pg(pg);
+	ilbd_scf_destroy(scf_pg_handle(pg), NULL, NULL, pg);
+	return (ret);
+}
+
+/*
+ * Set named property to scf value specified.  If property is new,
+ * create it.
+ */
+static ilb_status_t
+ilbd_scf_set_prop(scf_propertygroup_t *pg, const char *propname,
+    scf_type_t proptype, scf_value_t *val)
+{
+	scf_handle_t *h = NULL;
+	scf_property_t *prop = NULL;
+	scf_value_t *oldval = NULL;
+	scf_transaction_t *tx = NULL;
+	scf_transaction_entry_t *ent = NULL;
+	boolean_t new = B_FALSE;
+	ilb_status_t ret = ILB_STATUS_OK;
+	int commit_ret;
+
+	h = scf_pg_handle(pg);
+	if (h == NULL || propname == NULL)
+		return (ILB_STATUS_EINVAL);
+
+	ret = ilbd_scf_get_prop_val(pg, propname, &oldval);
+	if (oldval != NULL)
+		scf_value_destroy(oldval);
+	if (ret == ILB_STATUS_ENOENT)
+		new = B_TRUE;
+	else if (ret != ILB_STATUS_OK)
+		return (ret);
+
+	if ((prop = scf_property_create(h)) == NULL)
+		return (ilbd_scf_err_to_ilb_err());
+	if ((tx = scf_transaction_create(h)) == NULL ||
+	    (ent = scf_entry_create(h)) == NULL) {
+		ret = ilbd_scf_err_to_ilb_err();
+		logdebug("ilbd_scf_set_prop: create scf transaction failed\n");
+		goto out;
+	}
+
+	if (scf_transaction_start(tx, pg) == -1) {
+		ret = ilbd_scf_err_to_ilb_err();
+		logdebug("ilbd_scf_set_prop: start scf transaction failed\n");
+		goto out;
+	}
+
+	if (new) {
+		if (scf_transaction_property_new(tx, ent, propname,
+		    proptype) == -1) {
+			ret = ilbd_scf_err_to_ilb_err();
+			logdebug("ilbd_scf_set_prop: create scf prop failed\n");
+			goto out;
+		}
+	} else {
+		if (scf_transaction_property_change(tx, ent, propname, proptype)
+		    == -1) {
+			ret = ilbd_scf_err_to_ilb_err();
+			logdebug("ilbd_scf_set_prop: change scf prop failed\n");
+			goto out;
+		}
+	}
+
+	if (scf_entry_add_value(ent, val) != 0) {
+		logdebug("ilbd_scf_set_prop: add scf entry failed\n");
+		ret = ilbd_scf_err_to_ilb_err();
+		goto out;
+	}
+
+	commit_ret = scf_transaction_commit(tx);
+	switch (commit_ret) {
+	case 1:
+		ret = ILB_STATUS_OK;
+		/* update pg here, so subsequent property setting  succeeds */
+		(void) scf_pg_update(pg);
+		break;
+	case 0:
+		/* transaction failed due to not having most recent pg */
+		ret = ILB_STATUS_INUSE;
+		break;
+	default:
+		ret = ilbd_scf_err_to_ilb_err();
+		break;
+	}
+out:
+	if (tx != NULL)
+		scf_transaction_destroy(tx);
+	if (ent != NULL)
+		scf_entry_destroy(ent);
+	if (prop != NULL)
+		scf_property_destroy(prop);
+
+	return (ret);
+}
+
+/*
+ * get a prop's scf val
+ */
+static ilb_status_t
+ilbd_scf_get_prop_val(scf_propertygroup_t *pg, const char *propname,
+    scf_value_t **val)
+{
+	scf_handle_t *h = NULL;
+	scf_property_t *prop = NULL;
+	scf_value_t *value = NULL;
+	ilb_status_t ret = ILB_STATUS_OK;
+
+	h = scf_pg_handle(pg);
+	if (h == NULL || propname == NULL)
+		return (ILB_STATUS_EINVAL);
+
+	if ((prop = scf_property_create(h)) == NULL)
+		return (ilbd_scf_err_to_ilb_err());
+
+	if (scf_pg_get_property(pg, propname, prop) != 0) {
+		ret = ilbd_scf_err_to_ilb_err();
+		goto out;
+	}
+
+	if ((value = scf_value_create(h)) == NULL) {
+		ret = ilbd_scf_err_to_ilb_err();
+		goto out;
+	}
+
+	if (scf_property_get_value(prop, value) != 0) {
+		scf_value_destroy(value);
+		ret = ilbd_scf_err_to_ilb_err();
+		goto out;
+	}
+
+	*val = value;
+out:
+	if (prop != NULL)
+		scf_property_destroy(prop);
+
+	return (ret);
+}
+
+typedef struct ilbd_data
+{
+	union {
+		ilb_sg_info_t *sg_info;
+		ilb_hc_info_t *hc_info;
+		ilb_rule_info_t *rule_info;
+	} data;
+	ilbd_scf_pg_type_t pg_type;	/* type of data */
+#define	sg_data data.sg_info
+#define	hc_data data.hc_info
+#define	rule_data data.rule_info
+} ilbd_data_t;
+
+void
+ilbd_scf_str_to_ip(int ipversion, char *ipstr, struct in6_addr *addr)
+{
+	ilb_ip_addr_t ipaddr;
+	void *addrptr;
+
+	addrptr = (ipversion == AF_INET) ?
+	    (void *)&ipaddr.ia_v4 : (void *)&ipaddr.ia_v6;
+	(void) inet_pton(ipversion, ipstr, addrptr);
+	if (ipversion == AF_INET) {
+		IN6_INADDR_TO_V4MAPPED(&(ipaddr.ia_v4), addr);
+	} else {
+		(void) memcpy(addr, &(ipaddr.ia_v6),
+		    sizeof (struct in6_addr));
+	}
+}
+
+/*
+ * This function takes a scf value and writes it to the correct field of the
+ * corresponding data struct.
+ */
+static ilb_status_t
+ilbd_scfval_to_data(const char *propname, ilbd_var_type_t ilb_type,
+    scf_value_t *val, ilbd_data_t *ilb_data)
+{
+
+	scf_type_t scf_type = scf_value_type(val);
+	ilbd_scf_pg_type_t pg_type = ilb_data->pg_type;
+	int ret = 0;
+	ilb_rule_info_t *r_ent = NULL;
+	ilb_sg_info_t *s_ent = NULL;
+	ilb_hc_info_t *h_ent = NULL;
+	char ipstr[INET6_ADDRSTRLEN];
+	int scf_val_len = ILBD_MAX_VALUE_LEN;
+	char valstr[scf_val_len];
+	int64_t valint;
+	uint8_t valbool;
+	int ipversion;
+
+	switch (pg_type) {
+	case ILBD_SCF_RULE:
+		r_ent = ilb_data->rule_data;
+		break;
+	case ILBD_SCF_HC:
+		h_ent = ilb_data->hc_data;
+		break;
+	case ILBD_SCF_SG:
+		s_ent = ilb_data->sg_data;
+		break;
+	}
+
+	/* get scf value out */
+	switch (scf_type) {
+		case SCF_TYPE_NET_ADDR_V4:
+			if (scf_value_get_as_string_typed(val,
+			    SCF_TYPE_NET_ADDR_V4, ipstr, INET_ADDRSTRLEN) < 0)
+				return (ILB_STATUS_INTERNAL);
+			ipversion = AF_INET;
+			break;
+		case SCF_TYPE_NET_ADDR_V6:
+			if (scf_value_get_as_string_typed(val,
+			    SCF_TYPE_NET_ADDR_V6, ipstr, INET6_ADDRSTRLEN) < 0)
+				return (ILB_STATUS_INTERNAL);
+			ipversion = AF_INET6;
+			break;
+		case SCF_TYPE_BOOLEAN:
+			if (scf_value_get_boolean(val, &valbool) < 0)
+				return (ILB_STATUS_INTERNAL);
+			break;
+		case SCF_TYPE_ASTRING:
+			if (scf_value_get_astring(val, valstr, sizeof (valstr))
+			    < 0)
+				return (ILB_STATUS_INTERNAL);
+			break;
+		case SCF_TYPE_INTEGER:
+			if (scf_value_get_integer(val, &valint) < 0)
+				return (ILB_STATUS_INTERNAL);
+			break;
+		default:
+			return (ILB_STATUS_INTERNAL);
+	}
+
+	ret = ILB_STATUS_OK;
+	switch (ilb_type) {
+	case ILBD_RULE_STATUS:
+		if (valbool)
+			r_ent->rl_flags |= ILB_FLAGS_RULE_ENABLED;
+		break;
+	case ILBD_RULE_VIP:
+		r_ent->rl_ipversion = ipversion;
+		ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_vip);
+		break;
+	case ILBD_RULE_PROTO: {
+		struct protoent *protoent;
+
+		protoent = getprotobyname(valstr);
+		r_ent->rl_proto = protoent->p_proto;
+		break;
+	}
+	case ILBD_RULE_PORT: {
+		char *token1, *token2;
+
+		token1 = strtok(valstr, "-");
+		token2 = strtok(NULL, "-");
+		r_ent->rl_minport = atoi(token1);
+		r_ent->rl_maxport = atoi(token2);
+		break;
+	}
+	case ILBD_RULE_ALGO:
+		ilbd_scf_str_to_algo(&(r_ent->rl_algo), valstr);
+		break;
+	case ILBD_RULE_TOPO:
+		ilbd_scf_str_to_topo(&(r_ent->rl_topo), valstr);
+		break;
+	case ILBD_RULE_NAT_STR:
+		ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_nat_src_start);
+		break;
+	case ILBD_RULE_NAT_END:
+		ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_nat_src_end);
+		break;
+	case ILBD_RULE_STI_MASK:
+		ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_stickymask);
+		if (ipversion == AF_INET) {
+			if (!IN6_IS_ADDR_V4MAPPED_ANY(&r_ent->rl_stickymask))
+				r_ent->rl_flags |= ILB_FLAGS_RULE_STICKY;
+		} else {
+			if (!IN6_IS_ADDR_UNSPECIFIED(&r_ent->rl_stickymask))
+				r_ent->rl_flags |= ILB_FLAGS_RULE_STICKY;
+		}
+		break;
+	case ILBD_RULE_SGNAME:
+		(void) strlcpy(r_ent->rl_sgname, valstr,
+		    sizeof (r_ent->rl_sgname));
+		break;
+	case ILBD_RULE_HCNAME:
+		(void) strlcpy(r_ent->rl_hcname, valstr,
+		    sizeof (r_ent->rl_hcname));
+		break;
+	case ILBD_RULE_HCPORT:
+		r_ent->rl_hcport = valint;
+		break;
+	case ILBD_RULE_HCPFLAG:
+		r_ent->rl_hcpflag = valint;
+		break;
+	case ILBD_RULE_DRAINTIME:
+		r_ent->rl_conndrain = valint;
+		break;
+	case ILBD_RULE_NAT_TO:
+		r_ent->rl_nat_timeout = valint;
+		break;
+	case ILBD_RULE_PERS_TO:
+		r_ent->rl_sticky_timeout = valint;
+		break;
+
+	case ILBD_SG_SERVER: {
+		int svr_cnt = s_ent->sg_srvcount;
+
+		/* found a new server, increase the svr count of this sg */
+		s_ent->sg_srvcount++;
+
+		/*
+		 * valstr contains information of one server in the servergroup
+		 * valstr is in the format of "ip:minport-maxport:enable"
+		 */
+		s_ent = realloc(s_ent, sizeof (ilb_sg_info_t) +
+		    s_ent->sg_srvcount * sizeof (ilb_sg_srv_t));
+
+		/* sgs_srvID is the sg name, leave it blank */
+		/*
+		 * sgs_id is the digit in propname, propname is in a format of
+		 * "server" + the digital serverID. We get the serverID by
+		 * reading from the 7th char of propname.
+		 */
+		s_ent->sg_servers[svr_cnt].sgs_id = atoi(&propname[6]);
+
+		ilbd_get_svr_field(valstr,
+		    &s_ent->sg_servers[svr_cnt].sgs_addr,
+		    &s_ent->sg_servers[svr_cnt].sgs_minport,
+		    &s_ent->sg_servers[svr_cnt].sgs_maxport,
+		    &s_ent->sg_servers[svr_cnt].sgs_flags);
+		ilb_data->sg_data = s_ent;
+
+		break;
+	}
+	case ILBD_HC_TEST:
+		(void) strlcpy(h_ent->hci_test, valstr,
+		    sizeof (h_ent->hci_test));
+		break;
+	case ILBD_HC_TIMEOUT:
+		h_ent->hci_timeout = valint;
+		break;
+	case ILBD_HC_INTERVAL:
+		h_ent->hci_interval = valint;
+		break;
+	case ILBD_HC_DEF_PING:
+		h_ent->hci_def_ping = valbool;
+		break;
+	case ILBD_HC_COUNT:
+		h_ent->hci_count = valint;
+		break;
+	case ILBD_VAR_INVALID:
+		/*
+		 * An empty server group is represented by an invalid
+		 * SCF property.  So when loading a server group, this
+		 * case can be hit.  But it should happen only for this
+		 * single case.  So if it happens in another case, move
+		 * the service into maintenance mode.
+		 */
+		if (pg_type != ILBD_SCF_SG || scf_type != SCF_TYPE_ASTRING) {
+			logerr("%s: invalid ilb type", __func__);
+			(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
+		} else {
+			logdebug("%s: invalid ilb type", __func__);
+		}
+		break;
+	}
+
+	return (ret);
+}
+
+static ilbd_var_type_t
+ilbd_name_to_valtype(const char *prop_name)
+{
+	int i;
+
+	for (i = 0; i < ILBD_PROP_VAR_NUM; i++)
+		if (strncmp(prop_name, prop_tbl[i].scf_propname,
+		    strlen(prop_tbl[i].scf_propname)) == 0)
+			return (prop_tbl[i].val_type);
+
+	logdebug("ilbd_name_to_valtype: couldn't find prop %s", prop_name);
+	return (ILBD_VAR_INVALID);
+}
+
+/* callback for pg_walk_prop, arg is ilbd_data_t */
+static ilb_status_t
+ilbd_scf_load_prop(scf_propertygroup_t *pg, const char *prop_name, void *arg)
+{
+	scf_handle_t *h;
+	scf_value_t *val;
+	ilb_status_t ret;
+	ilbd_data_t *ilb_data = (ilbd_data_t *)arg;
+	ilbd_var_type_t val_type = ilbd_name_to_valtype(prop_name);
+
+	h = scf_pg_handle(pg);
+	if (h == NULL)
+		return (ILB_STATUS_EINVAL);
+
+	ret = ilbd_scf_get_prop_val(pg, prop_name, &val);
+	if (ret == ILB_STATUS_ENOENT)
+		return (ILB_STATUS_OK);
+	else if (ret != ILB_STATUS_OK)
+		return (ret);
+
+	/*
+	 * Load value to ilb_data.
+	 */
+	ret = ilbd_scfval_to_data(prop_name, val_type, val, ilb_data);
+
+out:
+	if (val != NULL)
+		scf_value_destroy(val);
+
+	return (ret);
+}
+
+/*
+ * walk properties in one prop group, arg is ilbd_data
+ * cb is ilbd_scf_load_prop()
+ */
+static ilb_status_t
+ilbd_scf_pg_walk_props(scf_propertygroup_t *pg,
+    ilb_status_t (*cb)(scf_propertygroup_t *, const char *, void *),
+    void *arg)
+{
+	scf_handle_t *h;
+	scf_iter_t *propiter;
+	scf_property_t *prop;
+	int scf_name_len = ILBD_MAX_NAME_LEN;
+	char prop_name[scf_name_len];
+	ilb_status_t ret = ILB_STATUS_OK;
+	int scf_ret = -1;
+
+	h = scf_pg_handle(pg);
+	if (h == NULL)
+		return (ILB_STATUS_EINVAL);
+
+	prop = scf_property_create(h);
+	propiter = scf_iter_create(h);
+	if (prop == NULL || propiter == NULL)
+		goto out;
+
+	if (scf_iter_pg_properties(propiter, pg) != 0)
+		goto out;
+
+	while ((scf_ret = scf_iter_next_property(propiter, prop)) == 1) {
+		if (scf_property_get_name(prop, prop_name, sizeof (prop_name))
+		    < 0) {
+			ret = ilbd_scf_err_to_ilb_err();
+			goto out;
+		}
+		ret = cb(pg, prop_name, arg);
+		if (ret != ILB_STATUS_OK)
+			break;
+	}
+out:
+	if (scf_ret == -1)
+		ret = ilbd_scf_err_to_ilb_err();
+	if (prop != NULL)
+		scf_property_destroy(prop);
+	if (propiter != NULL)
+		scf_iter_destroy(propiter);
+
+	return (ret);
+}
+
+/* cbs are libd_create_X */
+static ilb_status_t
+ilbd_scf_instance_walk_pg(scf_instance_t *inst,
+    ilbd_scf_pg_type_t pg_type,
+    ilb_status_t (*cb)(void *, int, struct passwd *, ucred_t *),
+    void *arg1, void *arg2)
+{
+	int			scf_ret;
+	ilb_status_t		ret;
+	scf_handle_t		*h;
+	scf_iter_t		*pgiter;
+	scf_propertygroup_t	*newpg;
+	int			port = *((int *)arg1);
+
+	if (inst == NULL)
+		return (ILB_STATUS_EINVAL);
+
+	h = scf_instance_handle(inst);
+	if (h == NULL)
+		return (ILB_STATUS_EINVAL);
+
+	if ((newpg = scf_pg_create(h)) == NULL)
+		return (ilbd_scf_err_to_ilb_err());
+
+	if ((pgiter = scf_iter_create(h)) == NULL) {
+		scf_pg_destroy(newpg);
+		return (ilbd_scf_err_to_ilb_err());
+	}
+
+	if ((scf_ret = scf_iter_instance_pgs(pgiter, inst)) < 0)
+		goto out;
+
+	while ((scf_ret = scf_iter_next_pg(pgiter, newpg)) > 0) {
+		ilbd_data_t data;
+		int scf_name_len = ILBD_MAX_NAME_LEN;
+		char pg_name[scf_name_len];
+
+		if (scf_pg_get_name(newpg, pg_name, sizeof (pg_name)) < 0) {
+			ret = ilbd_scf_err_to_ilb_err();
+			goto out;
+		}
+
+		/*
+		 * if pg name indicates it's a ilb configuration, walk its prop
+		 */
+		data.pg_type = pg_type;
+		data.hc_data = NULL;
+		data.sg_data = NULL;
+		data.rule_data = NULL;
+
+		switch (pg_type) {
+		case ILBD_SCF_RULE:
+			if (strncmp(ILBD_PG_NAME_RULE, pg_name,
+			    strlen(ILBD_PG_NAME_RULE)) == 0) {
+				data.rule_data = calloc(1,
+				    sizeof (ilb_rule_info_t));
+				if (data.rule_data == NULL) {
+					ret = ILB_STATUS_ENOMEM;
+					goto out;
+				}
+				ret = ilbd_scf_pg_walk_props(newpg,
+				    ilbd_scf_load_prop, &data);
+				if (ret != ILB_STATUS_OK)
+					goto out;
+				assert(data.rule_data != NULL);
+				/* set rule name */
+				(void) strlcpy(data.rule_data->rl_name,
+				    &pg_name[strlen(ILBD_PG_NAME_RULE)],
+				    sizeof (data.rule_data->rl_name));
+
+				ret = cb(data.rule_data, port, arg2, NULL);
+				free(data.rule_data);
+				if (ret != ILB_STATUS_OK)
+					goto out;
+			}
+			break;
+		case ILBD_SCF_SG:
+			if (strncmp(ILBD_PG_NAME_SG, pg_name,
+			    strlen(ILBD_PG_NAME_SG)) == 0) {
+				data.sg_data = calloc(1,
+				    sizeof (ilb_sg_info_t));
+				if (data.sg_data == NULL)
+					return (ILB_STATUS_ENOMEM);
+				ret = ilbd_scf_pg_walk_props(newpg,
+				    ilbd_scf_load_prop, &data);
+				if (ret != ILB_STATUS_OK) {
+					free(data.sg_data);
+					goto out;
+				}
+				assert(data.sg_data != NULL);
+				/* set sg name */
+				(void) strlcpy(data.sg_data->sg_name,
+				    &pg_name[strlen(ILBD_PG_NAME_SG)],
+				    sizeof (data.sg_data->sg_name));
+				ret = cb(data.sg_data, port, arg2, NULL);
+				if (ret != ILB_STATUS_OK) {
+					free(data.sg_data);
+					goto out;
+				}
+				/*
+				 * create a servergroup is two-step operation.
+				 * 1. create an empty servergroup.
+				 * 2. add server(s) to the group.
+				 *
+				 * since we are here from:
+				 * main_loop()->ilbd_read_config()->
+				 * ilbd_walk_sg_pgs()
+				 * there is no cli to send. So in this
+				 * path auditing will skip the
+				 * adt_set_from_ucred() check
+				 */
+				if (data.sg_data->sg_srvcount > 0) {
+					ret = ilbd_add_server_to_group(
+					    data.sg_data, port, NULL, NULL);
+					if (ret != ILB_STATUS_OK) {
+						free(data.sg_data);
+						goto out;
+					}
+					free(data.sg_data);
+				}
+			}
+			break;
+		case ILBD_SCF_HC:
+			if (strncmp(ILBD_PG_NAME_HC, pg_name,
+			    strlen(ILBD_PG_NAME_HC)) == 0) {
+				data.hc_data = calloc(1,
+				    sizeof (ilb_hc_info_t));
+				if (data.hc_data == NULL)
+					return (ILB_STATUS_ENOMEM);
+				ret = ilbd_scf_pg_walk_props(newpg,
+				    ilbd_scf_load_prop, &data);
+				if (ret != ILB_STATUS_OK)
+					goto out;
+				assert(data.hc_data != NULL);
+				/* set hc name */
+				(void) strlcpy(data.hc_data->hci_name,
+				    &pg_name[strlen(ILBD_PG_NAME_HC)],
+				    sizeof (data.hc_data->hci_name));
+				ret = cb(data.hc_data, port, arg2, NULL);
+				free(data.hc_data);
+				if (ret != ILB_STATUS_OK)
+					goto out;
+			}
+			break;
+		}
+	}
+
+out:
+	if (scf_ret < 0)
+		ret = ilbd_scf_err_to_ilb_err();
+	scf_pg_destroy(newpg);
+	scf_iter_destroy(pgiter);
+	return (ret);
+}
+
+typedef ilb_status_t (*ilbd_scf_walker_fn)(void *, int, struct passwd *,
+    ucred_t *);
+
+ilb_status_t
+ilbd_walk_rule_pgs(ilb_status_t (*func)(ilb_rule_info_t *, int,
+    const struct passwd *, ucred_t *), void *arg1, void *arg2)
+{
+	scf_instance_t *inst;
+	scf_handle_t *h;
+	scf_service_t *svc;
+	ilb_status_t ret;
+
+	ret = ilbd_scf_get_inst(&h, &svc, &inst);
+	if (ret != ILB_STATUS_OK)
+		return (ret);
+
+	/* get rule prop group, transfer it to ilb_lrule_info_t */
+	ret = ilbd_scf_instance_walk_pg(inst, ILBD_SCF_RULE,
+	    (ilbd_scf_walker_fn)func, arg1, arg2);
+	ilbd_scf_destroy(h, svc, inst, NULL);
+	return (ret);
+}
+
+ilb_status_t
+ilbd_walk_sg_pgs(ilb_status_t (*func)(ilb_sg_info_t *, int,
+    const struct passwd *, ucred_t *), void *arg1, void *arg2)
+{
+	scf_instance_t *inst;
+	scf_handle_t *h;
+	scf_service_t *svc;
+	ilb_status_t ret;
+
+	ret = ilbd_scf_get_inst(&h, &svc, &inst);
+	if (ret != ILB_STATUS_OK)
+		return (ret);
+
+	ret = ilbd_scf_instance_walk_pg(inst, ILBD_SCF_SG,
+	    (ilbd_scf_walker_fn)func, arg1, arg2);
+	ilbd_scf_destroy(h, svc, inst, NULL);
+	return (ret);
+}
+
+ilb_status_t
+ilbd_walk_hc_pgs(ilb_status_t (*func)(const ilb_hc_info_t *, int,
+    const struct passwd *, ucred_t *), void *arg1, void *arg2)
+{
+	scf_instance_t *inst;
+	scf_handle_t *h;
+	scf_service_t *svc;
+	ilb_status_t ret;
+
+	ret = ilbd_scf_get_inst(&h, &svc, &inst);
+	if (ret != ILB_STATUS_OK)
+		return (ret);
+
+	ret = ilbd_scf_instance_walk_pg(inst, ILBD_SCF_HC,
+	    (ilbd_scf_walker_fn)func, arg1, arg2);
+	ilbd_scf_destroy(h, svc, inst, NULL);
+	return (ret);
+}
+
+ilb_status_t
+ilbd_change_prop(ilbd_scf_pg_type_t pg_type, const char *pg_name,
+    const char *prop_name, void *new_val)
+{
+	int ret;
+	scf_propertygroup_t *scfpg = NULL;
+	int scf_name_len = ILBD_MAX_NAME_LEN;
+	char scf_pgname[scf_name_len];
+	scf_type_t scftype;
+	scf_value_t *scfval;
+	scf_handle_t *h;
+
+	ilbd_name_to_scfpgname(pg_type, pg_name, scf_pgname);
+	ret = ilbd_scf_retrieve_pg(scf_pgname, &scfpg, B_FALSE);
+	if (ret != ILB_STATUS_EEXIST)
+		return (ret);
+
+	assert(scfpg != NULL);
+
+	h = scf_pg_handle(scfpg);
+	if (h == NULL) {
+		ret = ILB_STATUS_EINVAL;
+		goto done;
+	}
+
+	if ((scfval = scf_value_create(h)) == NULL) {
+		ret = ILB_STATUS_ENOMEM;
+		goto done;
+	}
+
+	if (pg_type == ILBD_SCF_RULE) {
+		scftype = SCF_TYPE_BOOLEAN;
+		scf_value_set_boolean(scfval, *(boolean_t *)new_val);
+	} else if (pg_type == ILBD_SCF_SG) {
+		scftype = SCF_TYPE_ASTRING;
+		(void) scf_value_set_astring(scfval, (char *)new_val);
+	}
+	ret = ilbd_scf_set_prop(scfpg, prop_name, scftype, scfval);
+
+done:
+	if (scf_pg_handle(scfpg) != NULL)
+		scf_handle_destroy(scf_pg_handle(scfpg));
+	if (scfpg != NULL)
+		scf_pg_destroy(scfpg);
+	if (scfval != NULL)
+		scf_value_destroy(scfval);
+	return (ret);
+}
+
+/*
+ * Update the persistent configuration with a new server, srv, added to a
+ * server group, sg.
+ */
+ilb_status_t
+ilbd_scf_add_srv(ilbd_sg_t *sg, ilbd_srv_t *srv)
+{
+	scf_propertygroup_t *pg;
+	scf_handle_t *h;
+	scf_value_t *val;
+	ilb_status_t ret;
+	int scf_name_len = ILBD_MAX_NAME_LEN;
+	char buf[scf_name_len];
+	char propname[scf_name_len];
+
+	ilbd_name_to_scfpgname(ILBD_SCF_SG, sg->isg_name, buf);
+	ret = ilbd_scf_retrieve_pg(buf, &pg, B_FALSE);
+	/*
+	 * The server group does not exist in persistent storage.  This
+	 * cannot happen.  Should probably transition the service to
+	 * maintenance since it should be there.
+	 */
+	if (ret != ILB_STATUS_EEXIST) {
+		logerr("ilbd_scf_add_srv: SCF update failed - entering"
+		    " maintenance mode");
+		(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
+		return (ILB_STATUS_INTERNAL);
+	}
+
+	if ((h = scf_pg_handle(pg)) == NULL) {
+		ilbd_scf_destroy(NULL, NULL, NULL, pg);
+		return (ilbd_scf_err_to_ilb_err());
+	}
+
+	if ((val = scf_value_create(h)) == NULL) {
+		ilbd_scf_destroy(h, NULL, NULL, pg);
+		return (ILB_STATUS_ENOMEM);
+	}
+	ilbd_srv_scf_val(srv, buf);
+	(void) scf_value_set_astring(val, buf);
+	(void) snprintf(propname, sizeof (propname), "server%d", srv->isv_id);
+	ret = ilbd_scf_set_prop(pg, propname, SCF_TYPE_ASTRING, val);
+
+	ilbd_scf_destroy(h, NULL, NULL, pg);
+	scf_value_destroy(val);
+
+	return (ret);
+}
+
+/*
+ * Delete a server, srv, of a server group, sg, from the persistent
+ * configuration.
+ */
+ilb_status_t
+ilbd_scf_del_srv(ilbd_sg_t *sg, ilbd_srv_t *srv)
+{
+	ilb_status_t ret;
+	scf_propertygroup_t *pg;
+	scf_handle_t *h;
+	int scf_name_len = ILBD_MAX_NAME_LEN;
+	char buf[scf_name_len];
+	scf_transaction_t *tx = NULL;
+	scf_transaction_entry_t *entry = NULL;
+
+	ilbd_name_to_scfpgname(ILBD_SCF_SG, sg->isg_name, buf);
+	ret = ilbd_scf_retrieve_pg(buf, &pg, B_FALSE);
+	/*
+	 * The server group does not exist in persistent storage.  This
+	 * cannot happen. THe caller of this function puts service in
+	 * maintenance mode.
+	 */
+	if (ret != ILB_STATUS_EEXIST)
+		return (ILB_STATUS_INTERNAL);
+	ret = ILB_STATUS_OK;
+
+	if ((h = scf_pg_handle(pg)) == NULL) {
+		logdebug("ilbd_scf_del_srv: scf_pg_handle: %s\n",
+		    scf_strerror(scf_error()));
+		ilbd_scf_destroy(NULL, NULL, NULL, pg);
+		return (ilbd_scf_err_to_ilb_err());
+	}
+
+	if ((tx = scf_transaction_create(h)) == NULL ||
+	    (entry = scf_entry_create(h)) == NULL) {
+		logdebug("ilbd_scf_del_srv: create scf transaction failed: "
+		    "%s\n", scf_strerror(scf_error()));
+		ret = ilbd_scf_err_to_ilb_err();
+		goto out;
+	}
+
+	(void) snprintf(buf, sizeof (buf), "server%d", srv->isv_id);
+
+	if (scf_transaction_start(tx, pg) == -1) {
+		logdebug("ilbd_scf_set_prop: start scf transaction failed: "
+		    "%s\n", scf_strerror(scf_error()));
+		ret = ilbd_scf_err_to_ilb_err();
+		goto out;
+	}
+	if (scf_transaction_property_delete(tx, entry, buf) == -1) {
+		logdebug("ilbd_scf_set_prop: delete property failed: %s\n",
+		    scf_strerror(scf_error()));
+		ret = ilbd_scf_err_to_ilb_err();
+		goto out;
+	}
+	if (scf_transaction_commit(tx) != 1) {
+		logdebug("ilbd_scf_set_prop: commit transaction failed: %s\n",
+		    scf_strerror(scf_error()));
+		ret = ilbd_scf_err_to_ilb_err();
+	}
+
+out:
+	if (entry != NULL)
+		scf_entry_destroy(entry);
+	if (tx != NULL)
+		scf_transaction_destroy(tx);
+	ilbd_scf_destroy(h, NULL, NULL, pg);
+
+	return (ret);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_sg.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,1644 @@
+/*
+ * 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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_support.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,296 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <stropts.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stropts.h>
+#include <sys/sockio.h>
+#include <errno.h>
+#include <sys/list.h>
+#include <auth_attr.h>
+#include <auth_list.h>
+#include <secdb.h>
+#include <libilb.h>
+#include "libilb_impl.h"
+#include "ilbd.h"
+
+/*
+ * logs error messages, either to stderr or syslog, depending on
+ * the -d option
+ */
+static boolean_t	ilbd_debugging = B_FALSE;
+
+/* Socket to issue ioctl() to the kernel */
+static	int	ksock = -1;
+
+void
+ilbd_enable_debug(void)
+{
+	ilbd_debugging = B_TRUE;
+}
+
+boolean_t
+is_debugging_on(void)
+{
+	return (ilbd_debugging);
+}
+
+/*
+ * All routines log to syslog, unless the daemon is running in
+ * the foreground, in which case the logging goes to stderr.
+ * The following logging functions are available:
+ *
+ *
+ *      logdebug(): A printf-like function for outputting debug messages
+ *      (messages at LOG_DEBUG) that are only of use to developers.
+ *
+ *      logerr(): A printf-like function for outputting error messages
+ *      (messages at LOG_ERR) from the daemon.
+ *
+ *      logperror*(): A set of functions used to output error messages
+ *      (messages at LOG_ERR); these automatically append strerror(errno)
+ *      and a newline to the message passed to them.
+ *
+ * NOTE: since the logging functions write to syslog, the messages passed
+ *      to them are not eligible for localization.  Thus, gettext() must
+ *      *not* be used.
+ *
+ */
+/* PRINTFLIKE2 */
+void
+ilbd_log(int pri, const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+
+	if (ilbd_debugging == B_TRUE) {
+		(void) vfprintf(stderr, fmt, ap);
+		(void) fprintf(stderr, "\n");
+	} else {
+		vsyslog(pri, fmt, ap);
+	}
+	va_end(ap);
+
+}
+
+/* PRINTFLIKE1 */
+void
+logperror(const char *str)
+{
+	if (ilbd_debugging == B_TRUE)
+		(void) fprintf(stderr, "%s: %s\n", str, strerror(errno));
+	else
+		syslog(LOG_ERR, "%s: %m", str);
+}
+
+
+ilb_status_t
+ilbd_check_client_config_auth(const struct passwd *pwd)
+{
+	if (chkauthattr(NET_ILB_CONFIG_AUTH, pwd->pw_name) == 0) {
+		logdebug("user %s is not authorized for"
+		    " configuration operation", pwd->pw_name);
+		return (ILB_STATUS_CFGAUTH);
+	}
+	return (ILB_STATUS_OK);
+
+}
+
+ilb_status_t
+ilbd_check_client_enable_auth(const struct passwd *pwd)
+{
+	if (chkauthattr(NET_ILB_ENABLE_AUTH, pwd->pw_name) == 0) {
+		logdebug("user %s is not authorized for"
+		    " enable/disable operation", pwd->pw_name);
+		return (ILB_STATUS_CFGAUTH);
+	}
+	return (ILB_STATUS_OK);
+
+}
+
+/*
+ * input param. "err" should be one of the errnos defined in
+ * /usr/include/sys/errno.h
+ * this list is NOT complete.
+ */
+ilb_status_t
+ilb_map_errno2ilbstat(int err)
+{
+	ilb_status_t	rc = ILB_STATUS_INTERNAL;
+
+	switch (err) {
+	case 0:
+		rc = ILB_STATUS_OK; /* for completeness' sake */
+		break;
+	case EINVAL:
+		rc = ILB_STATUS_EINVAL;
+		break;
+	case ENOENT:
+		rc = ILB_STATUS_ENOENT;
+		break;
+	case ENOMEM:
+		rc = ILB_STATUS_ENOMEM;
+		break;
+	case EINPROGRESS:
+		rc = ILB_STATUS_INPROGRESS;
+		break;
+	case EEXIST:
+		rc = ILB_STATUS_EEXIST;
+		break;
+	}
+	return (rc);
+}
+
+static int
+i_get_kcmd_sz(void *cmdp)
+{
+	int		sz;
+
+	switch (((ilb_rule_cmd_t *)cmdp)->cmd) {
+	case ILB_DESTROY_RULE:
+	case ILB_ENABLE_RULE:
+	case ILB_DISABLE_RULE:
+		sz = sizeof (ilb_name_cmd_t);
+		break;
+	case ILB_CREATE_RULE:
+	case ILB_LIST_RULE:
+		sz = sizeof (ilb_rule_cmd_t);
+		break;
+	case ILB_NUM_RULES:
+		sz = sizeof (ilb_num_rules_cmd_t);
+		break;
+	case ILB_NUM_SERVERS:
+		sz = sizeof (ilb_num_servers_cmd_t);
+		break;
+	case ILB_ADD_SERVERS: {
+		ilb_servers_info_cmd_t *kcmd = (ilb_servers_info_cmd_t *)cmdp;
+
+		sz = sizeof (*kcmd) + ((kcmd->num_servers - 1) *
+		    sizeof (kcmd->servers));
+		break;
+	}
+	case ILB_RULE_NAMES: {
+		ilb_rule_names_cmd_t *kcmd = (ilb_rule_names_cmd_t *)cmdp;
+
+		sz = sizeof (*kcmd) +
+		    ((kcmd->num_names - 1) * sizeof (kcmd->buf));
+		break;
+	}
+	case ILB_DEL_SERVERS:
+	case ILB_ENABLE_SERVERS:
+	case ILB_DISABLE_SERVERS: {
+		ilb_servers_cmd_t *kcmd = (ilb_servers_cmd_t *)cmdp;
+
+		sz = sizeof (*kcmd) +
+		    ((kcmd->num_servers - 1) * sizeof (kcmd->servers));
+		break;
+	}
+	default: sz = -1;
+		break;
+	}
+	return (sz);
+}
+
+/*
+ * parameter 'sz' is optional (indicated by == 0); if it's not set
+ * we try to derive it from cmdp->cmd
+ */
+ilb_status_t
+do_ioctl(void *cmdp, ssize_t sz)
+{
+	struct strioctl	ioc;
+	int		i_rc;
+
+	if (ksock == -1) {
+		ksock = socket(AF_INET, SOCK_DGRAM, 0);
+		if (ksock == -1) {
+			logperror("do_ioctl: AF_INET socket call"
+			    "  failed");
+			return (ILB_STATUS_INTERNAL);
+		}
+	}
+
+	(void) memset(&ioc, 0, sizeof (ioc));
+	ioc.ic_cmd = SIOCILB;
+	ioc.ic_timout = 0;
+	ioc.ic_dp = cmdp;
+
+	if (sz == 0) {
+		sz = i_get_kcmd_sz(cmdp);
+
+		if (sz == -1) {
+			logdebug("do_ioctl: unknown command");
+			return (ILB_STATUS_INVAL_CMD);
+		}
+	}
+
+	ioc.ic_len = sz;
+
+	i_rc = ioctl(ksock, I_STR, (caddr_t)&ioc);
+	if (i_rc == -1) {
+		logdebug("do_ioctl: SIOCILB ioctl (%d) failed: %s",
+		    *(ilb_cmd_t *)cmdp, strerror(errno));
+		return (ilb_map_errno2ilbstat(errno));
+	}
+
+	return (ILB_STATUS_OK);
+}
+
+/*
+ * Create an OK reply to a client request.  It is assumed that the passed
+ * in buffer is large enough to hold the reply.
+ */
+void
+ilbd_reply_ok(uint32_t *rbuf, size_t *rbufsz)
+{
+	ilb_comm_t *ic = (ilb_comm_t *)rbuf;
+
+	ic->ic_cmd = ILBD_CMD_OK;
+	/* Default is one exchange of request/response. */
+	ic->ic_flags = ILB_COMM_END;
+	*rbufsz = sizeof (ilb_comm_t);
+}
+
+/*
+ * Create an error reply to a client request.  It is assumed that the passed
+ * in buffer is large enough to hold the reply.
+ */
+void
+ilbd_reply_err(uint32_t *rbuf, size_t *rbufsz, ilb_status_t status)
+{
+	ilb_comm_t *ic = (ilb_comm_t *)rbuf;
+
+	ic->ic_cmd = ILBD_CMD_ERROR;
+	/* Default is one exchange of request/response. */
+	ic->ic_flags = ILB_COMM_END;
+	*(ilb_status_t *)&ic->ic_data = status;
+	*rbufsz = sizeof (ilb_comm_t) + sizeof (ilb_status_t);
+}
--- a/usr/src/cmd/cmd-inet/usr.sbin/Makefile	Tue Nov 03 21:31:39 2009 -0800
+++ b/usr/src/cmd/cmd-inet/usr.sbin/Makefile	Tue Nov 03 23:15:19 2009 -0800
@@ -65,14 +65,14 @@
 K5TELNETOBJS=	in.telnetd.o
 SRCS=		$(PROGSRCS) $(OTHERSRC)
 
-SUBDIRS=	bootconfchk htable ifconfig in.ftpd in.rdisc in.routed \
+SUBDIRS=	bootconfchk htable ifconfig ilbadm in.ftpd in.rdisc in.routed \
 		in.talkd inetadm inetconv ipmpstat ipqosconf ipsecutils \
 		kssl/kssladm kssl/ksslcfg ping routeadm snoop sppptun \
 		traceroute wificonfig
 
-MSGSUBDIRS=	bootconfchk htable ifconfig in.ftpd in.routed in.talkd inetadm \
-		inetconv ipmpstat ipqosconf ipsecutils kssl/ksslcfg routeadm \
-		sppptun snoop wificonfig
+MSGSUBDIRS=	bootconfchk htable ifconfig ilbadm in.ftpd in.routed in.talkd \
+		inetadm inetconv ipmpstat ipqosconf ipsecutils kssl/ksslcfg \
+		routeadm sppptun snoop wificonfig
 
 # As programs get lint-clean, add them here and to the 'lint' target.
 # Eventually this hack should go away, and all in PROG should be
@@ -84,9 +84,9 @@
 # they're all clean, replace the dependency of the lint target
 # with SUBDIRS.  Also (sigh) deal with the commented-out build lines
 # for the lint rule.
-LINTSUBDIRS=	bootconfchk in.rdisc in.routed in.talkd inetadm inetconv \
-		ipmpstat ipqosconf ipsecutils ping routeadm sppptun traceroute \
-		wificonfig
+LINTSUBDIRS=	bootconfchk ilbadm in.rdisc in.routed in.talkd inetadm \
+		inetconv ipmpstat ipqosconf ipsecutils ping routeadm sppptun \
+		traceroute wificonfig
 # And as programs are verified not to attempt to write into constants,
 # -xstrconst should be used to ensure they stay that way.
 CONSTCLEAN=
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.sbin/ilbadm/Makefile	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,82 @@
+#
+# 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.
+#
+
+PROG=		ilbadm
+
+ILB_OBJS =	ilbadm.o ilbadm_sg.o ilbadm_rules.o ilbadm_hc.o
+ILB_OBJS +=	ilbadm_subr.o ilbadm_import.o ilbadm_nat.o ilbadm_stats.o
+LIST_OBJS =	list.o
+OBJS =		$(ILB_OBJS) $(LIST_OBJS)
+
+ILB_SRCS=	$(ILB_OBJS:.o=.c)
+LIST_SRCS=	$(LIST_OBJS:%.o=../../../../uts/common/os/%.c)
+
+SRCS=		$(ILB_SRC) $(LIST_SRCS)
+
+include	../../../Makefile.cmd
+include ../../Makefile.cmd-inet
+
+LDLIBS +=	-lsocket -lnsl -lilb -linetutil -lkstat
+CPPFLAGS +=	-I$(SRC)/lib/libilb/common -I$(SRC)/uts/common
+
+C99MODE =	$(C99_ENABLE)
+
+# for debug:
+CFLAGS = -g
+STRIP_STABS= :
+
+POFILES =	$(ILB_OBJS:%.o=%.po)
+POFILE =	$(PROG)_all.po
+
+.KEEP_STATE:
+.PARALLEL:
+
+all:	$(PROG)
+
+$(PROG): $(OBJS)
+	$(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+	$(POST_PROCESS)
+
+$(POFILE): $(POFILES)
+	$(RM) $@
+	cat $(POFILES) > $@
+
+install: all $(ROOTUSRSBINPROG)
+
+clean:	
+	$(RM) $(OBJS) $(POFILES)
+
+lint:	$(ILB_SRCS)
+	$(LINT.c) $(ILB_SRCS) $(LDLIBS)
+
+check:	$(ILB_SRCS) $(PROG).h
+	$(CSTYLE) -pP $(ILB_SRCS) $(PROG).h
+	$(HDRCHK) $(PROG).h
+
+include ../../../Makefile.targ
+
+# the below is needed to get list.o built
+%.o: ../../../../uts/common/os/%.c
+	$(COMPILE.c) -o $@ $<
+	$(POST_PROCESS_O)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,249 @@
+/*
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <libgen.h>
+#include <libilb.h>
+#include "ilbadm.h"
+
+/*
+ * Error strings for error values returned by ilbadm functions
+ */
+const char *
+ilbadm_errstr(ilbadm_status_t rc)
+{
+	switch (rc) {
+	case ILBADM_OK:
+		return (gettext("no error"));
+	case ILBADM_FAIL:
+		return (gettext("processing of command failed"));
+	case ILBADM_ENOMEM:
+		return (gettext("memory allocation failure"));
+	case ILBADM_EINVAL:
+		return (gettext("invalid value  - refer to ilbadm(1M)"));
+	case ILBADM_HCPRINT:
+		return (gettext("failed to print healthcheck values"));
+	case ILBADM_INVAL_AF:
+		return (gettext("address family is invalid"));
+	case ILBADM_INVAL_PORT:
+		return (gettext("port value is invalid"));
+	case ILBADM_INVAL_SRVID:
+		return (gettext("server ID is invalid"));
+	case ILBADM_INVAL_ADDR:
+		return (gettext("address is invalid"));
+	case ILBADM_INVAL_ARGS:
+		return (gettext("invalid/incompatible keywords - refer to"
+		    " ilbadm(1M)"));
+	case ILBADM_ENOSGNAME:
+		return (gettext("servergroup name missing"));
+	case ILBADM_ENORULE:
+		return (gettext("rule name missing or specified"
+		    " rule not found"));
+	case ILBADM_ENOSERVER:
+		return (gettext("server name missing or specified"
+		    " server not found"));
+	case ILBADM_INVAL_ALG:
+		return (gettext("LB algorithm is invalid"));
+	case ILBADM_ENOPROTO:
+		return (gettext("protocol does not exist in"
+		    " protocol database"));
+	case ILBADM_ENOSERVICE:
+		return (gettext("servicename does not exist in nameservices"));
+	case ILBADM_INVAL_OPER:
+		return (gettext("operation type is invalid"));
+	case ILBADM_INVAL_KEYWORD:
+		return (gettext("keyword is invalid - please refer"
+		    " to ilbadm(1M)"));
+	case ILBADM_ASSIGNREQ:
+		return (gettext("assignment '=' missing"));
+	case ILBADM_NORECURSIVE:
+		return (gettext("recursive import not allowed"));
+	case ILBADM_INVAL_COMMAND:
+		return (gettext("subcommand is invalid - please refer"
+		    " to ilbadm(1M)"));
+	case ILBADM_ENOPROXY:
+		return (gettext("proxy-src is missing"));
+	case ILBADM_INVAL_PROXY:
+		return (gettext("proxy-src not allowed"));
+	case ILBADM_ENOOPTION:
+		return (gettext("mandatory argument(s) missing - refer"
+		    " to ilbadm(1M)"));
+	case ILBADM_TOOMANYIPADDR:
+		return (gettext("address range contains more than 255"
+		    " IP addresses"));
+	case ILBADM_EXPORTFAIL:
+		return (gettext("could not export servergroup because"
+		    " of lack of space"));
+	case ILBADM_INVAL_SYNTAX:
+		return (gettext("syntax failure - refer to ilbadm(1M)"));
+	case ILBADM_NOKEYWORD_VAL:
+		return (gettext("missing value"));
+	case ILBADM_LIBERR:
+		return (gettext("library error"));
+	default:
+		return (gettext("unknown error"));
+
+
+	}
+}
+
+/* PRINTFLIKE1 */
+void
+ilbadm_err(const char *format, ...)
+{
+	/* similar to warn() of dladm.c */
+	va_list alist;
+
+	(void) fprintf(stderr, "ilbadm: ");
+
+	va_start(alist, format);
+	(void) vfprintf(stderr, format, alist);
+	va_end(alist);
+
+	(void) fprintf(stderr, "\n");
+}
+
+void
+Usage(char *name)
+{
+	(void) fprintf(stderr, gettext("Usage:\n"));
+	print_cmdlist_short(basename(name), stderr);
+	exit(1);
+}
+
+static void
+print_version(char *name)
+{
+	(void) printf("%s %s\n", basename(name), ILBADM_VERSION);
+	(void) printf(gettext(ILBADM_COPYRIGHT));
+	exit(0);
+}
+
+void
+unknown_opt(char **argv, int optind)
+{
+	ilbadm_err(gettext("bad or misplaced option %s"), argv[optind]);
+	exit(1);
+}
+
+void
+incomplete_cmdline(char *name)
+{
+	ilbadm_err(gettext("the command line is incomplete "
+	    "(more arguments expected)"));
+	Usage(name);
+}
+
+static void
+bad_importfile(char *name, char *filename)
+{
+	ilbadm_err(gettext("file %s cannot be opened for reading"), filename);
+	Usage(name);
+}
+
+int
+main(int argc, char *argv[])
+{
+	ilbadm_status_t	rc;
+	int		c;
+	int		fd = -1;
+	int		flags = 0;
+
+	(void) setlocale(LC_ALL, "");
+
+#if !defined(TEXT_DOMAIN)
+#define	TEXT_DOMAIN "SYS_TEST"
+#endif
+	(void) textdomain(TEXT_DOMAIN);
+
+	/* handle global options (-?, -V) first */
+	while ((c = getopt(argc, argv, ":V:?")) != -1) {
+		switch ((char)c) {
+		case 'V': print_version(argv[0]);
+			/* not reached */
+			break;
+		case '?':
+			Usage(argv[0]);
+			/* not reached */
+			break;
+		default: unknown_opt(argv, optind - 1);
+			/* not reached */
+			break;
+		}
+	}
+
+	if (optind >= argc)
+		incomplete_cmdline(argv[0]);
+
+	/*
+	 * we can import from a given file (argv[2]) or from
+	 * stdin (if no file given)
+	 */
+	if (strcasecmp(argv[1], "import-config") == 0 ||
+	    strcasecmp(argv[1], "import-cf") == 0) {
+		int shift = 0;
+
+		if (argc > 2 && strcmp(argv[2], "-p") == 0) {
+			shift++;
+			flags |= ILBADM_IMPORT_PRESERVE;
+		}
+
+		if (argc - shift < 3)
+			fd = 0;
+		else
+			if ((fd = open(argv[2+shift], O_RDONLY)) == -1)
+				bad_importfile(argv[0], argv[2+shift]);
+	}
+
+	argv++;
+	argc--;
+
+	/*
+	 * re-set optind for next callers of getopt() - they all believe they're
+	 * the first.
+	 */
+	optind = 1;
+	optopt = 0;
+
+	rc = ilbadm_import(fd, argc, argv, flags);
+
+	/*
+	 * The error messages have been printed out, using
+	 * ilbadm_errstr() and ilb_errstr(), before we get here.
+	 * So just set the exit value
+	 */
+	if (rc != ILBADM_OK)
+		return (1);
+	/* success */
+	return (0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm.h	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,242 @@
+/*
+ * 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.
+ */
+
+#ifndef	_ILBADM_H
+#define	_ILBADM_H
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <sys/list.h>
+#include <net/if.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <libilb.h>
+#include <libintl.h>
+#include <locale.h>
+
+#define	ILBADM_VERSION  "1.0"
+#define	ILBADM_COPYRIGHT \
+	"Copyright 2009 Sun Microsystems, Inc.  All rights reserved.\n" \
+	"Use is subject to license terms.\n"
+
+/*
+ * flag values
+ */
+#define	OPT_VALUE_LIST		0x0001
+#define	OPT_IP_RANGE		0x0002
+#define	OPT_PORTS		0x0004
+#define	OPT_PORTS_ONLY		0x0008
+#define	OPT_NAT			0x0010
+#define	OPT_NUMERIC_ONLY	0x0020
+
+#define	ILBD_BAD_VAL	(-1)
+
+#define	ILBADM_LIST_FULL	0x0001
+#define	ILBADM_LIST_PARSE	0x0002
+#define	ILBADM_LIST_ENABLED	0x0004
+#define	ILBADM_LIST_NOENABLED	(~ILBADM_LIST_ENABLED)
+#define	ILBADM_LIST_DISABLED	0x0008
+#define	ILBADM_LIST_NODISABLED	(~ILBADM_LIST_DISABLED)
+
+#define	ILBADM_IMPORT_PRESERVE	0x1000
+
+#define	V6_ADDRONLY	0x1	/* don't print surrounding "[]"s */
+
+#define	ILB_SRVID_SZ	(ILB_NAMESZ - 5)
+#define	ILBD_NAMESZ	ILB_NAMESZ
+
+#define	ILB_MAX_PORT	UINT16_MAX
+
+typedef enum {
+	ILBADM_OK = 0,
+	ILBADM_ASSIGNREQ,	/* assignment '=' required */
+	ILBADM_EINVAL,		/* invalid value */
+	ILBADM_ENOMEM,		/* malloc failed */
+	ILBADM_ENOOPTION,	/* mandatory option missing */
+	ILBADM_ENOPROTO,	/* protocol not found in database */
+	ILBADM_ENOPROXY,	/* proxy-src is missing */
+	ILBADM_ENOSERVICE,	/* servicename not found in database */
+	ILBADM_ENOSGNAME,	/* servergroup name missing */
+	ILBADM_ENORULE,		/* rulename missing or no such rule */
+	ILBADM_ENOSERVER,	/* rulename missing or no such rule */
+	ILBADM_EXPORTFAIL,	/* too little space to do export servergroup */
+	ILBADM_FAIL,		/* processing of command failed */
+	ILBADM_HCPRINT,		/* failed to print healthcheck */
+	ILBADM_INVAL_ADDR,	/* invalid address */
+	ILBADM_INVAL_AF,	/* invalid address family */
+	ILBADM_INVAL_ALG,	/* LB algorithm failure */
+	ILBADM_INVAL_ARGS,	/* invalid arguments to command */
+	ILBADM_INVAL_COMMAND,	/* invalid command */
+	ILBADM_INVAL_KEYWORD,	/* invalid keyword */
+	ILBADM_INVAL_OPER,	/* invalid operation type */
+	ILBADM_INVAL_PORT,	/* invalid value specified for port */
+	ILBADM_INVAL_PROXY,	/* proxy-src not allowed   */
+	ILBADM_INVAL_SYNTAX,	/* syntax error */
+	ILBADM_INVAL_SRVID,	/* server id is invalid (missing "_" ?) */
+	ILBADM_LIBERR,		/* translation of libilb errors. We also */
+				/* set it in ilbadm fuctions to indicate */
+				/* printing of non-generic error messages */
+	ILBADM_NORECURSIVE,	/* recursive import not allowed */
+	ILBADM_TOOMANYIPADDR,	/* too many addresses */
+	ILBADM_NOKEYWORD_VAL	/* no value specified for a keyword */
+} ilbadm_status_t;
+
+
+typedef enum {
+	ILB_KEY_BAD = -1,
+	ILB_KEY_SERVER,
+	ILB_KEY_SERVRANGE,	/* pseudo-key for SG creation */
+	ILB_KEY_SERVERID,
+	ILB_KEY_VIP,
+	ILB_KEY_PORT,
+	ILB_KEY_PROTOCOL,
+	ILB_KEY_IPVERSION,
+	ILB_KEY_ALGORITHM,
+	ILB_KEY_TYPE,
+	ILB_KEY_SERVERGROUP,
+	ILB_KEY_HEALTHCHECK,
+	ILB_KEY_HCPORT,
+	ILB_KEY_SRC,
+	ILB_KEY_STICKY,
+	ILB_KEY_CONNDRAIN,	/* otional timers ... */
+	ILB_KEY_NAT_TO,
+	ILB_KEY_STICKY_TO,
+	ILB_KEY_HC_TEST,
+	ILB_KEY_HC_COUNT,
+	ILB_KEY_HC_INTERVAL,
+	ILB_KEY_HC_TIMEOUT
+} ilbadm_key_code_t;
+
+/*
+ * we need a few codes for commands, can't use libilb ones
+ */
+typedef enum {
+	cmd_create_sg,
+	cmd_add_srv,
+	cmd_rem_srv,
+	cmd_enable_rule,
+	cmd_disable_rule,
+	cmd_enable_server,
+	cmd_disable_server
+} ilbadm_cmd_t;
+
+/* filched from snoop_ether.c */
+typedef struct val_type {
+	int	v_type;
+	char	v_name[20];
+	char 	v_alias[8];	/* undocumented */
+} ilbadm_val_type_t;
+
+typedef struct key_names {
+	ilbadm_key_code_t	k_key;
+	char		k_name[20];
+	char		k_alias[12];	/* undocumented */
+} ilbadm_key_name_t;
+
+typedef struct servnode {
+	list_node_t	s_link;
+	ilb_server_data_t	s_spec;
+} ilbadm_servnode_t;
+
+typedef struct sgroup {
+	list_t		sg_serv_list;	/* list of servnode_t elements */
+	int		sg_count;
+	char 		*sg_name;
+} ilbadm_sgroup_t;
+
+typedef	struct cmd_hlp {
+	char	*h_help;
+} ilbadm_cmd_help_t;
+
+typedef ilbadm_status_t	(* cmdfunc_t)(int, char **);
+
+typedef struct cmd_names {
+	char		c_name[25];
+	char		c_alias[20];	/* undocumented */
+	cmdfunc_t	c_action;
+	ilbadm_cmd_help_t	*c_help;	/* for "usage" */
+} ilbadm_cmd_desc_t;
+
+ilbadm_status_t	ilbadm_add_server_to_group(int, char **);
+ilbadm_status_t	ilbadm_create_servergroup(int, char **);
+ilbadm_status_t	ilbadm_destroy_servergroup(int, char **);
+ilbadm_status_t	ilbadm_rem_server_from_group(int, char **);
+
+ilbadm_status_t	ilbadm_create_rule(int, char **);
+ilbadm_status_t	ilbadm_destroy_rule(int, char **);
+ilbadm_status_t	ilbadm_enable_rule(int, char **);
+ilbadm_status_t	ilbadm_disable_rule(int, char **);
+ilbadm_status_t	ilbadm_show_server(int, char **);
+ilbadm_status_t	ilbadm_enable_server(int, char **);
+ilbadm_status_t	ilbadm_disable_server(int, char **);
+
+ilbadm_status_t	ilbadm_show_servergroups(int, char **);
+ilbadm_status_t	ilbadm_show_rules(int, char **);
+ilbadm_status_t	ilbadm_show_stats(int, char **);
+
+ilbadm_status_t	ilbadm_create_hc(int, char **);
+ilbadm_status_t	ilbadm_destroy_hc(int, char **);
+ilbadm_status_t	ilbadm_show_hc(int, char **);
+ilbadm_status_t	ilbadm_show_hc_result(int, char **);
+
+ilbadm_status_t	ilbadm_noimport(int, char **);
+
+ilbadm_status_t	ilbadm_show_nat(int, char **);
+ilbadm_status_t	ilbadm_show_persist(int, char **);
+
+ilbadm_status_t	i_parse_optstring(char *, void *, ilbadm_key_name_t *,
+    int, int *);
+ilbadm_servnode_t	*i_new_sg_elem(ilbadm_sgroup_t *);
+ilbadm_status_t	ilbadm_import(int, int, char *[], int);
+ilbadm_status_t	ilbadm_export(int, char *[]);
+ilbadm_status_t	ilbadm_export_servergroups(ilb_handle_t h, FILE *);
+ilbadm_status_t	ilbadm_export_hc(ilb_handle_t h, FILE *);
+ilbadm_status_t	ilbadm_export_rules(ilb_handle_t h, FILE *);
+
+ilbadm_status_t	i_check_rule_spec(ilb_rule_data_t *);
+ilbadm_status_t ilbadm_set_netmask(char *, ilb_ip_addr_t *, int);
+int		ilbadm_mask_to_prefixlen(ilb_ip_addr_t *);
+
+void		print_cmdlist_short(char *, FILE *);
+extern int	ilb_cmp_ipaddr(ilb_ip_addr_t *, ilb_ip_addr_t *,
+    longlong_t *);
+
+void	ip2str(ilb_ip_addr_t *, char *, size_t, int);
+char	*i_str_from_val(int, ilbadm_val_type_t *);
+char	*ilbadm_key_to_opt(ilbadm_key_code_t);
+
+void	Usage(char *);
+void	unknown_opt(char **, int);
+const char	*ilbadm_errstr(ilbadm_status_t);
+void	ilbadm_err(const char *format, ...);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif /* _ILBADM_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_hc.c	Tue Nov 03 23:15:19 2009 -0800
@@ -0,0 +1,520 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/list.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <ofmt.h>
+#include <libilb.h>
+#include "ilbadm.h"
+
+extern int	optind, optopt, opterr;
+extern char	*optarg;
+
+typedef struct hc_export_arg {
+	FILE	*fp;
+} hc_export_arg_t;
+
+/* Maximum columns for printing hc output. */
+#define	SHOW_HC_COLS	80
+
+/* OFMT call back to print out a hc server result field. */
+static boolean_t print_hc_result(ofmt_arg_t *, char *, uint_t);
+
+/* ID to indicate which field to be printed. */
+enum hc_print_id {
+	hc_of_rname, hc_of_hname, hc_of_sname, hc_of_status, hc_of_fail_cnt,
+	hc_of_lasttime, hc_of_nexttime, hc_of_rtt,
+	hc_of_name, hc_of_timeout, hc_of_count, hc_of_interval, hc_of_def_ping,
+	hc_of_test
+};
+
+/*
+ * Fields of a hc server result.  The sum of all fields' width is SHOW_HC_COLS.
+ */
+static ofmt_field_t hc_results[] = {
+	{"RULENAME",	14,	hc_of_rname,	print_hc_result},
+	{"HCNAME",	14,	hc_of_hname,	print_hc_result},
+	{"SERVERID",	14,	hc_of_sname,	print_hc_result},
+	{"STATUS",	9,	hc_of_status,	print_hc_result},
+	{"FAIL",	5,	hc_of_fail_cnt,	print_hc_result},
+	{"LAST",	9,	hc_of_lasttime,	print_hc_result},
+	{"NEXT",	9,	hc_of_nexttime,	print_hc_result},
+	{"RTT",		6,	hc_of_rtt,	print_hc_result},
+	{NULL,		0,	0,		NULL}
+};
+
+/* OFMT call back to print out a hc info field. */
+static boolean_t print_hc(ofmt_arg_t *, char *, uint_t);
+
+/*
+ * Fields of a hc info.  The sume of all fields' width is SHOW_HC_COLS.
+ */
+static ofmt_field_t hc_fields[] = {
+	{"HCNAME",	14,	hc_of_name,	print_hc},
+	{"TIMEOUT",	8,	hc_of_timeout,	print_hc},
+	{"COUNT",	8,	hc_of_count,	print_hc},
+	{"INTERVAL",	9,	hc_of_interval,	print_hc},
+	{"DEF_PING",	9,	hc_of_def_ping,	print_hc},
+	{"TEST",	32,	hc_of_test,	print_hc},
+	{NULL,		0,	0,		NULL}
+};
+
+static boolean_t
+print_hc(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
+{
+	enum hc_print_id id = of_arg->ofmt_id;
+	ilb_hc_info_t *info = (ilb_hc_info_t *)of_arg->ofmt_cbarg;
+
+	switch (id) {
+	case hc_of_name:
+		(void) strlcpy(buf, info->hci_name, bufsize);
+		break;
+	case hc_of_timeout:
+		(void) snprintf(buf, bufsize, "%d", info->hci_timeout);
+		break;
+	case hc_of_count:
+		(void) snprintf(buf, bufsize, "%d", info->hci_count);
+		break;
+	case hc_of_interval:
+		(void) snprintf(buf, bufsize, "%d", info->hci_interval);
+		break;
+	case hc_of_def_ping:
+		(void) snprintf(buf, bufsize, "%c",
+		    info->hci_def_ping ? 'Y' : 'N');
+		break;
+	case hc_of_test:
+		(void) snprintf(buf, bufsize, "%s", info->hci_test);
+		break;
+	}
+	return (B_TRUE);
+}
+
+/* Call back to ilb_walk_hc(). */
+/* ARGSUSED */
+static ilb_status_t
+ilbadm_print_hc(ilb_handle_t h, ilb_hc_info_t *hc_info, void *arg)
+{
+	ofmt_handle_t	ofmt_h = arg;
+
+	ofmt_print(ofmt_h, hc_info);
+	return (ILB_STATUS_OK);
+}
+
+/*
+ * Print out health check objects given their name.
+ * Or print out all health check objects if no name given.
+ */
+/* ARGSUSED */
+ilbadm_status_t
+ilbadm_show_hc(int argc, char *argv[])
+{
+	ilb_handle_t	h = ILB_INVALID_HANDLE;
+	ilb_status_t	rclib;
+	ofmt_handle_t	ofmt_h;
+	ofmt_status_t	ofmt_ret;
+
+	if ((ofmt_ret = ofmt_open("all", hc_fields, 0, SHOW_HC_COLS,
+	    &ofmt_h)) != OFMT_SUCCESS) {
+		char err_buf[SHOW_HC_COLS];
+
+		ilbadm_err(gettext("ofmt_open failed: %s"),
+		    ofmt_strerror(ofmt_h, ofmt_ret, err_buf, SHOW_HC_COLS));
+		return (ILBADM_LIBERR);
+	}
+	rclib = ilb_open(&h);
+	if (rclib != ILB_STATUS_OK)
+		goto out;
+
+	if (argc == 1) {
+		rclib = ilb_walk_hc(h, ilbadm_print_hc, ofmt_h);
+	} else {
+		ilb_hc_info_t hc_info;
+		int i;
+
+		for (i = 1; i < argc; i++) {
+			rclib = ilb_get_hc_info(h, argv[i], &hc_info);
+			if (rclib == ILB_STATUS_OK)
+				ofmt_print(ofmt_h, &hc_info);
+			else
+				break;
+		}
+	}
+out:
+	ofmt_close(ofmt_h);
+
+	if (h != ILB_INVALID_HANDLE)
+		(void) ilb_close(h);
+
+	if (rclib != ILB_STATUS_OK) {
+		ilbadm_err(ilb_errstr(rclib));
+		return (ILBADM_LIBERR);
+	}
+
+	return (ILBADM_OK);
+}
+
+static boolean_t
+print_hc_result(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
+{
+	enum hc_print_id id = of_arg->ofmt_id;
+	ilb_hc_srv_t *srv = (ilb_hc_srv_t *)of_arg->ofmt_cbarg;
+	struct tm tv;
+
+	switch (id) {
+	case hc_of_rname:
+		(void) strlcpy(buf, srv->hcs_rule_name, bufsize);
+		break;
+	case hc_of_hname:
+		(void) strlcpy(buf, srv->hcs_hc_name, bufsize);
+		break;
+	case hc_of_sname:
+		(void) strlcpy(buf, srv->hcs_ID, bufsize);
+		break;
+	case hc_of_status:
+		switch (srv->hcs_status) {
+		case ILB_HCS_UNINIT:
+			(void) strlcpy(buf, "un-init", bufsize);
+			break;
+		case ILB_HCS_UNREACH:
+			(void) strlcpy(buf, "unreach", bufsize);
+			break;
+		case ILB_HCS_ALIVE:
+			(void) strlcpy(buf, "alive", bufsize);
+			break;
+		case ILB_HCS_DEAD:
+			(void) strlcpy(buf, "dead", bufsize);
+			break;
+		case ILB_HCS_DISABLED:
+			(void) strlcpy(buf, "disabled", bufsize);
+			break;
+		}
+		break;
+	case hc_of_fail_cnt:
+		(void) snprintf(buf, bufsize, "%u", srv->hcs_fail_cnt);
+		break;
+	case hc_of_lasttime:
+		if (localtime_r(&srv->hcs_lasttime, &tv) == NULL)
+			return (B_FALSE);
+		(void) snprintf(buf, bufsize, "%02d:%02d:%02d", tv.tm_hour,
+		    tv.tm_min, tv.tm_sec);
+		break;
+	case hc_of_nexttime:
+		if (srv->hcs_status == ILB_HCS_DISABLED)
+			break;
+		if (localtime_r(&srv->hcs_nexttime, &tv) == NULL)
+			return (B_FALSE);
+		(void) snprintf(buf, bufsize, "%02d:%02d:%02d", tv.tm_hour,
+		    tv.tm_min, tv.tm_sec);
+		break;
+	case hc_of_rtt:
+		(void) snprintf(buf, bufsize, "%u", srv->hcs_rtt);
+		break;
+	}
+	return (B_TRUE);
+}
+
+/* Call back to ilbd_walk_hc_srvs(). */
+/* ARGSUSED */
+static ilb_status_t
+ilbadm_print_hc_result(ilb_handle_t h, ilb_hc_srv_t *srv, void *arg)
+{
+	ofmt_handle_t	ofmt_h = arg;
+
+	ofmt_print(ofmt_h, srv);
+	return (ILB_STATUS_OK);
+}
+
+/*
+ * Output hc result of a specified rule or all rules.
+ */
+ilbadm_status_t
+ilbadm_show_hc_result(int argc, char *argv[])
+{
+	ilb_handle_t	h = ILB_INVALID_HANDLE;
+	ilb_status_t 	rclib = ILB_STATUS_OK;
+	int		i;
+	ofmt_handle_t	ofmt_h;
+	ofmt_status_t	ofmt_ret;
+
+	/* ilbadm show-hc-result [rule-name] */
+	if (argc < 1) {
+		ilbadm_err(gettext("usage: ilbadm show-hc-result"
+		    " [rule-name]"));
+		return (ILBADM_LIBERR);
+	}
+
+	if ((ofmt_ret = ofmt_open("all", hc_results, 0, SHOW_HC_COLS,
+	    &ofmt_h)) != OFMT_SUCCESS) {
+		char err_buf[SHOW_HC_COLS];
+
+		ilbadm_err(gettext("ofmt_open failed: %s"),
+		    ofmt_strerror(ofmt_h, ofmt_ret, err_buf, SHOW_HC_COLS));
+		return (ILBADM_LIBERR);
+	}
+
+	rclib = ilb_open(&h);
+	if (rclib != ILB_STATUS_OK)
+		goto out;
+
+	/* If no rule name is given, show results for all rules. */
+	if (argc == 1) {
+		rclib = ilb_walk_hc_srvs(h, ilbadm_print_hc_result, NULL,
+		    ofmt_h);
+	} else {
+		for (i = 1; i < argc; i++) {
+			rclib = ilb_walk_hc_srvs(h, ilbadm_print_hc_result,
+			    argv[i], ofmt_h);
+			if (rclib != ILB_STATUS_OK)
+				break;
+		}
+	}
+out:
+	ofmt_close(ofmt_h);
+
+	if (h != ILB_INVALID_HANDLE)
+		(void) ilb_close(h);
+
+	if (rclib != ILB_STATUS_OK) {
+		ilbadm_err(ilb_errstr(rclib));
+		return (ILBADM_LIBERR);
+	}
+	return (ILBADM_OK);
+}
+
+#define	ILBADM_DEF_HC_COUNT	3
+#define	ILBADM_DEF_HC_INTERVAL	30	/* in sec */
+#define	ILBADM_DEF_HC_TIMEOUT	5	/* in sec */
+
+static ilbadm_key_name_t hc_parse_keys[] = {
+	{ILB_KEY_HC_TEST, "hc-test", "hc-test"},
+	{ILB_KEY_HC_COUNT, "hc-count", "hc-count"},
+	{ILB_KEY_HC_TIMEOUT, "hc-timeout", "hc-tout"},
+	{ILB_KEY_HC_INTERVAL, "hc-interval", "hc-intl"},
+	{ILB_KEY_BAD, "", ""}
+};
+
+static ilbadm_status_t
+ilbadm_hc_parse_arg(char *arg, ilb_hc_info_t *hc)
+{
+	ilbadm_status_t ret;
+
+	/* set default value for count, interval, timeout */
+	hc->hci_count = ILBADM_DEF_HC_COUNT;
+	hc->hci_interval = ILBADM_DEF_HC_INTERVAL;
+	hc->hci_timeout = ILBADM_DEF_HC_TIMEOUT;
+	hc->hci_test[0] = '\0';
+
+	ret = i_parse_optstring(arg, hc, hc_parse_keys, 0, NULL);
+	if (ret != ILBADM_OK && ret != ILBADM_LIBERR) {
+		ilbadm_err(ilbadm_errstr(ret));