#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
# Copyright 2022 Garrett D'Amore
#
# Hammerhead: Removed in Phase A: in.chargend, in.daytimed, in.discardd,
# in.echod, in.timed (obsolete RFC test services)
# Hammerhead: Removed in Phase A: slpd (replaced by mDNS), vrrpd (niche)
SUBDIRS= bridged ilbd in.mpathd in.ndpd \
in.ripngd inetd mdnsd pppoe \
wpad
MSGSUBDIRS= ilbd inetd
include ../../Makefile.cmd
include ./Makefile.lib
POFILES= inetd/inetd.po vrrpd/vrrpd.po
POFILE= usr.lib.po
all: TARGET= all
install: TARGET= install
clean: TARGET= clean
clobber: TARGET= clobber
lint: TARGET= lint
_msg: TARGET= _msg
.KEEP_STATE:
all clean clobber lint: $(SUBDIRS)
# Hammerhead: removed isaexec symlink for in.iked (64-bit only, no ISA dispatch)
install: $(SUBDIRS)
_msg: $(MSGSUBDIRS)
$(SUBDIRS): FRC
@cd $@; pwd; $(MAKE) $(TARGET)
FRC:
#
# 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 2006 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# ident "%Z%%M% %I% %E% SMI"
#
# cmd/cmd-inet/usr.lib/%M%
#
include ../../../Makefile.cmd
ROOTMANIFESTDIR= $(ROOTSVCNETWORK)
LDLIBS += -linetsvc
.KEEP_STATE:
all: $(PROG)
include ../Makefile.lib
install: all $(ROOTLIBINETPROG) $(ROOTMANIFEST)
check: $(CHKMANIFEST)
clean:
lint: lint_PROG
include ../../../Makefile.targ
#
# 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.
#
# cmd/cmd-inet/usr.lib/Makefile.lib
ROOTLIBINET= $(ROOTLIB)/inet
ROOTLIBINET32= $(ROOTLIBINET)/$(MACH32)
ROOTLIBINET64= $(ROOTLIBINET)/$(MACH64)
ROOTLIBINETPROG= $(PROG:%=$(ROOTLIBINET)/%)
ROOTLIBINETPROG32= $(PROG:%=$(ROOTLIBINET32)/%)
ROOTLIBINETPROG64= $(PROG:%=$(ROOTLIBINET64)/%)
$(ROOTLIBINET)/%: % $(ROOTLIBINET)
$(INS.file)
$(ROOTLIBINET32)/%: % $(ROOTLIBINET32)
$(INS.file)
$(ROOTLIBINET64)/%: % $(ROOTLIBINET64)
$(INS.file)
#
# 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.
#
# cmd/cmd-inet/usr.lib/bridged/Makefile
#
PROG= bridged
MANIFEST= bridge.xml
OBJS= dlpi.o door.o events.o main.o rstp.o
SRCS= $(OBJS:%.o=%.c)
include ../../../Makefile.cmd
ROOTMANIFESTDIR= $(ROOTSVCNETWORK)
.KEEP_STATE:
all: $(PROG)
LDLIBS += -lsocket -lrstp -ldlpi -ldladm -lumem
CFLAGS += $(CCVERBOSE)
CPPFLAGS += -D__SUN__
.PARALLEL: $(OBJS)
$(PROG): $(OBJS)
$(LINK.c) $(OBJS) -o $@ $(LDLIBS)
$(POST_PROCESS)
include ../Makefile.lib
install: $(PROG) $(ROOTLIBPROG) $(ROOTMANIFEST)
clean:
$(RM) $(OBJS)
lint: lint_SRCS
include ../../../Makefile.targ
bridge
The "bridge" service provides Ethernet bridging and related protocols.
/*
* 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.
*/
/*
* bridged - bridging control daemon. This module provides DLPI-specific
* functions for interface to libdlpi.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "global.h"
static const uchar_t bridge_group_address[] = BRIDGE_GROUP_ADDRESS;
static const ushort_t bpdu_filter[] = {
ENF_PUSHWORD | 0, /* check for 1:80:c2:0:0:0 dest. */
ENF_PUSHLIT | ENF_CAND,
#ifdef _BIG_ENDIAN
0x0180,
#else
0x8001,
#endif
ENF_PUSHWORD | 1,
ENF_PUSHLIT | ENF_CAND,
#ifdef _BIG_ENDIAN
0xC200,
#else
0x00C2,
#endif
ENF_PUSHWORD | 2,
ENF_PUSHZERO | ENF_CAND,
ENF_PUSHWORD | 7, /* check for SSAP/DSAP 42 42 */
ENF_PUSHLIT | ENF_CAND,
0x4242,
};
/*
* Because we're called by dlpi_recv(), we're called with the engine lock held.
*/
/*ARGSUSED*/
static void
dlpi_notify(dlpi_handle_t dlpi, dlpi_notifyinfo_t *info, void *arg)
{
struct portdata *port = arg;
int rc;
switch (info->dni_note) {
case DL_NOTE_SPEED:
/* libdlpi gives us Kbps, and we want Mbps */
if (port->speed == info->dni_speed / 1000)
break;
port->speed = info->dni_speed / 1000;
if ((rc = STP_IN_changed_port_speed(port->port_index,
port->speed)) != 0)
syslog(LOG_ERR, "STP can't change port speed on %s: %s",
port->name, STP_IN_get_error_explanation(rc));
break;
case DL_NOTE_PHYS_ADDR:
if (memcmp(info->dni_physaddr, port->mac_addr, ETHERADDRL) != 0)
rstp_change_mac(port, info->dni_physaddr);
break;
case DL_NOTE_LINK_DOWN:
if (!port->phys_status)
break;
port->phys_status = B_FALSE;
if (!port->admin_status || protect != DLADM_BRIDGE_PROT_STP ||
port->sdu_failed)
break;
if ((rc = STP_IN_enable_port(port->port_index, False)) != 0)
syslog(LOG_ERR, "STP can't disable port %s: %s",
port->name, STP_IN_get_error_explanation(rc));
break;
case DL_NOTE_LINK_UP:
if (port->phys_status)
break;
port->phys_status = B_TRUE;
if (!port->admin_status || protect != DLADM_BRIDGE_PROT_STP ||
port->sdu_failed) {
port->bpdu_protect = B_FALSE;
break;
}
/*
* If we're not running STP, and the link state has just come
* up, then clear out any protection shutdown state, and allow
* us to forward again.
*/
if (port->admin_non_stp && port->bpdu_protect) {
port->bpdu_protect = B_FALSE;
enable_forwarding(port);
}
if ((rc = STP_IN_enable_port(port->port_index, True)) != 0)
syslog(LOG_ERR, "STP can't enable port %s: %s",
port->name, STP_IN_get_error_explanation(rc));
break;
}
}
boolean_t
port_dlpi_open(const char *portname, struct portdata *port,
datalink_class_t class)
{
uchar_t addrbuf[DLPI_PHYSADDR_MAX];
size_t alen = DLPI_PHYSADDR_MAX;
int rc;
char addrstr[ETHERADDRL * 3];
/*
* We use DLPI 'raw' mode so that we get access to the received
* Ethernet 802 length field. libdlpi otherwise eats this value. Note
* that 'raw' mode support is required in order to use snoop, so it's
* expected to be common, even if it's not documented.
*/
rc = dlpi_open(portname, &port->dlpi, DLPI_RAW);
if (rc != DLPI_SUCCESS) {
syslog(LOG_ERR, "can't open %s: %s", portname,
dlpi_strerror(rc));
return (B_FALSE);
}
port->phys_status = B_TRUE;
port->sdu_failed = B_FALSE;
port->bpdu_protect = B_FALSE;
/*
* Now that the driver is open, we can get at least the initial value
* of the interface speed. We need to do this before establishing the
* notify callback, so that it can update us later.
*/
get_dladm_speed(port);
/*
* Save off the libdlpi port name, as it's dynamically allocated, and
* the name we're passed is not.
*/
port->name = dlpi_linkname(port->dlpi);
/*
* We can't bind SAP 0 or enable multicast on an etherstub. It's ok,
* though, because there's no real hardware involved.
*/
if (class != DATALINK_CLASS_ETHERSTUB) {
if ((rc = dlpi_bind(port->dlpi, 0, NULL)) != DLPI_SUCCESS) {
syslog(LOG_ERR, "can't bind %s: %s", portname,
dlpi_strerror(rc));
return (B_FALSE);
}
if ((rc = dlpi_enabmulti(port->dlpi, bridge_group_address,
sizeof (bridge_group_address))) != DLPI_SUCCESS) {
syslog(LOG_ERR, "can't enable multicast on %s: %s",
portname, dlpi_strerror(rc));
return (B_FALSE);
}
}
if ((rc = dlpi_enabnotify(port->dlpi,
DL_NOTE_PHYS_ADDR | DL_NOTE_LINK_DOWN | DL_NOTE_LINK_UP |
DL_NOTE_SPEED, dlpi_notify, port, &port->notifyid)) !=
DLPI_SUCCESS) {
syslog(LOG_WARNING, "no DLPI notification on %s: %s", portname,
dlpi_strerror(rc));
}
rc = dlpi_get_physaddr(port->dlpi, DL_CURR_PHYS_ADDR, addrbuf, &alen);
if (rc != DLPI_SUCCESS) {
syslog(LOG_ERR, "unable to get MAC address on %s: %s",
port->name, dlpi_strerror(rc));
return (B_FALSE);
}
if (alen != ETHERADDRL) {
syslog(LOG_ERR, "bad MAC address length %d on %s",
alen, port->name);
return (B_FALSE);
}
(void) memcpy(port->mac_addr, addrbuf, ETHERADDRL);
if (class != DATALINK_CLASS_ETHERSTUB) {
int fd = dlpi_fd(port->dlpi);
int lowflag = 1;
if (strioctl(fd, DLIOCLOWLINK, &lowflag, sizeof (lowflag)) != 0)
syslog(LOG_WARNING, "low-link notify failed on %s: %m",
portname);
if (ioctl(fd, I_PUSH, "pfmod") == 0) {
struct packetfilt pf;
pf.Pf_Priority = 0;
pf.Pf_FilterLen = sizeof (bpdu_filter) /
sizeof (*bpdu_filter);
(void) memcpy(pf.Pf_Filter, bpdu_filter,
sizeof (bpdu_filter));
if (strioctl(fd, PFIOCSETF, &pf, sizeof (pf)) == -1)
syslog(LOG_WARNING,
"pfil ioctl failed on %s: %m", portname);
} else {
syslog(LOG_WARNING, "pfil push failed on %s: %m",
portname);
}
}
if (debugging) {
(void) _link_ntoa(port->mac_addr, addrstr, ETHERADDRL,
IFT_OTHER);
syslog(LOG_DEBUG, "got MAC address %s on %s", addrstr,
port->name);
}
return (B_TRUE);
}
/*
* 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.
*/
/*
* bridged - bridging control daemon. This module provides the door-based
* interface used by user applications to gather bridge status information.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "global.h"
#define DOOR_DIRMODE 0755
#define DOOR_FILEMODE 0444
static int door_fd = -1;
static char doorname[MAXPATHLEN];
/*ARGSUSED*/
static void
bridge_door_server(void *cookie, char *argp, size_t arg_size, door_desc_t *dp,
uint_t ndesc)
{
/* LINTED: alignment */
bridge_door_cmd_t *bdc = (bridge_door_cmd_t *)argp;
int retv = EINVAL;
bridge_door_cfg_t bdcf;
UID_STP_STATE_T smstate;
UID_STP_PORT_CFG_T portcfg;
UID_STP_PORT_STATE_T portstate;
struct portdata *pdp;
int twoints[2];
if (arg_size < sizeof (*bdc) || lock_engine() != 0) {
(void) door_return((char *)&retv, sizeof (retv), NULL, 0);
return;
}
switch (bdc->bdc_type) {
case bdcBridgeGetConfig:
if ((retv = STP_IN_stpm_get_cfg(0, &bdcf.bdcf_cfg)) != 0)
break;
bdcf.bdcf_prot = protect;
unlock_engine();
(void) door_return((char *)&bdcf, sizeof (bdcf), NULL, 0);
return;
case bdcBridgeGetState:
if ((retv = STP_IN_stpm_get_state(0, &smstate)) != 0)
break;
unlock_engine();
(void) door_return((char *)&smstate, sizeof (smstate), NULL, 0);
return;
case bdcBridgeGetPorts: {
datalink_id_t *dlp;
int *rbuf;
size_t rlen;
int i, nports;
if (nextport == 0) {
twoints[0] = 0;
rbuf = twoints;
rlen = sizeof (twoints);
} else {
rlen = sizeof (int) + nextport * sizeof (datalink_id_t);
rbuf = alloca(rlen);
dlp = (datalink_id_t *)(rbuf + 1);
for (i = nports = 0; i < nextport; i++) {
if (allports[i]->kern_added)
dlp[nports++] = allports[i]->linkid;
}
rbuf[0] = nports;
rlen = sizeof (int) + nports * sizeof (datalink_id_t);
}
unlock_engine();
(void) door_return((char *)rbuf, rlen, NULL, 0);
return;
}
case bdcBridgeGetRefreshCount:
twoints[0] = refresh_count;
twoints[1] = 0;
unlock_engine();
(void) door_return((char *)twoints, sizeof (twoints), NULL, 0);
return;
case bdcPortGetConfig:
if ((pdp = find_by_linkid(bdc->bdc_linkid)) == NULL)
break;
retv = STP_IN_port_get_cfg(0, pdp->port_index, &portcfg);
if (retv != 0)
break;
unlock_engine();
(void) door_return((char *)&portcfg, sizeof (portcfg), NULL, 0);
return;
case bdcPortGetState:
if ((pdp = find_by_linkid(bdc->bdc_linkid)) == NULL)
break;
portstate.port_no = pdp->port_index;
if ((retv = STP_IN_port_get_state(0, &portstate)) != 0)
break;
if (pdp->sdu_failed)
portstate.state = UID_PORT_BADSDU;
else if (protect != DLADM_BRIDGE_PROT_STP)
portstate.state = UID_PORT_NON_STP;
else if (pdp->admin_non_stp && pdp->bpdu_protect)
portstate.state = UID_PORT_DISABLED;
unlock_engine();
(void) door_return((char *)&portstate, sizeof (portstate),
NULL, 0);
return;
case bdcPortGetForwarding:
if ((pdp = find_by_linkid(bdc->bdc_linkid)) == NULL)
break;
twoints[0] = pdp->admin_status ? 1 : 0;
twoints[1] = 0;
unlock_engine();
(void) door_return((char *)twoints, sizeof (twoints), NULL, 0);
return;
}
unlock_engine();
(void) door_return((char *)&retv, sizeof (retv), NULL, 0);
}
static void
cleanup_door(void)
{
if (door_fd != -1) {
(void) door_revoke(door_fd);
door_fd = -1;
}
if (doorname[0] != '\0') {
(void) unlink(doorname);
doorname[0] = '\0';
}
}
void
init_door(void)
{
int fd;
/* Make sure that the control directory exists */
(void) mkdir(DOOR_DIRNAME, DOOR_DIRMODE);
/* Each instance gets a separate door. */
(void) snprintf(doorname, sizeof (doorname), "%s/%s", DOOR_DIRNAME,
instance_name);
/* Do a low-overhead "touch" on the file that will be the door node. */
fd = open(doorname,
O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_NONBLOCK,
DOOR_FILEMODE);
if (fd != -1) {
(void) close(fd);
} else if (errno != EEXIST) {
syslog(LOG_ERR, "unable to create control door node: %m");
exit(EXIT_FAILURE);
}
(void) atexit(cleanup_door);
/* Create the door. */
door_fd = door_create(bridge_door_server, NULL,
DOOR_REFUSE_DESC | DOOR_NO_CANCEL);
if (door_fd == -1) {
syslog(LOG_ERR, "unable to create control door: %m");
exit(EXIT_FAILURE);
}
/* Attach the door to the file. */
(void) fdetach(doorname);
if (fattach(door_fd, doorname) == -1) {
syslog(LOG_ERR, "unable to attach control door: %m");
exit(EXIT_FAILURE);
}
}
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* bridged - bridging control daemon. This module handles events and general
* port-related operations.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "global.h"
int refresh_count = 1; /* never zero */
dladm_bridge_prot_t protect = DLADM_BRIDGE_PROT_STP;
/*
* The 'allports' array is an array of pointers to the struct portdata
* structures. We reallocate 'allports' as needed, but the portdata must
* remain where it's initially allocated, because libdlpi's notification
* mechanism has a copy of a pointer to this structure.
*/
uint_t nextport;
struct portdata **allports;
/* Port allocation increment (arbitrary) */
#define ALLOCINCR 10
static uint_t numports;
static datalink_id_t main_linkid;
int control_fd;
static void
linkdown(void)
{
(void) dladm_destroy_datalink_id(dlhandle, main_linkid,
DLADM_OPT_ACTIVE);
}
void
open_bridge_control(void)
{
bridge_newbridge_t bnb;
dladm_status_t status;
char buf[DLADM_STRSIZE];
if ((control_fd = open(BRIDGE_CTLPATH, O_RDWR | O_NONBLOCK)) == -1) {
perror(BRIDGE_CTLPATH);
exit(EXIT_FAILURE);
}
(void) snprintf(bnb.bnb_name, sizeof (bnb.bnb_name), "%s0",
instance_name);
status = dladm_name2info(dlhandle, bnb.bnb_name, &bnb.bnb_linkid, NULL,
NULL, NULL);
if (status != DLADM_STATUS_OK) {
(void) fprintf(stderr, "bridged: %s: %s\n", bnb.bnb_name,
dladm_status2str(status, buf));
exit(EXIT_FAILURE);
}
if (strioctl(control_fd, BRIOC_NEWBRIDGE, &bnb, sizeof (bnb)) == -1) {
perror("NEWBRIDGE");
exit(EXIT_FAILURE);
}
main_linkid = bnb.bnb_linkid;
if (strioctl(control_fd, BRIOC_TABLEMAX, &tablemax,
sizeof (tablemax)) == -1) {
syslog(LOG_ERR, "cannot set table max %lu on bridge %s: %m",
tablemax, instance_name);
exit(EXIT_FAILURE);
}
/*
* This covers for any previous incarnation where we might have crashed
* or been SIGKILL'd and failed to take down the datalink.
*/
linkdown();
(void) atexit(linkdown);
status = dladm_up_datalink_id(dlhandle, bnb.bnb_linkid);
if (status != DLADM_STATUS_OK) {
(void) fprintf(stderr, "bridged: %s link up: %s\n",
bnb.bnb_name, dladm_status2str(status, buf));
exit(EXIT_FAILURE);
}
}
struct portdata *
find_by_linkid(datalink_id_t linkid)
{
int i;
struct portdata *port;
for (i = 0; i < nextport; i++) {
port = allports[i];
if (port->linkid == linkid)
return (port);
}
return (NULL);
}
/*ARGSUSED2*/
static int
set_vlan(dladm_handle_t handle, datalink_id_t linkid, void *arg)
{
struct portdata *port;
dladm_status_t status;
dladm_vlan_attr_t vinfo;
char pointless[DLADM_STRSIZE];
bridge_vlanenab_t bve;
status = dladm_vlan_info(handle, linkid, &vinfo, DLADM_OPT_ACTIVE);
if (status != DLADM_STATUS_OK) {
syslog(LOG_DEBUG, "can't get VLAN info on link ID %u: %s",
linkid, dladm_status2str(status, pointless));
return (DLADM_WALK_CONTINUE);
}
port = find_by_linkid(vinfo.dv_linkid);
if (port == NULL || !port->kern_added)
return (DLADM_WALK_CONTINUE);
bve.bve_linkid = port->linkid;
bve.bve_vlan = vinfo.dv_vid;
bve.bve_onoff = B_TRUE;
if (strioctl(control_fd, BRIOC_VLANENAB, &bve, sizeof (bve)) == -1) {
syslog(LOG_ERR, "unable to enable VLAN %d on linkid %u: %m",
vinfo.dv_vid, port->linkid);
return (DLADM_WALK_TERMINATE);
} else {
return (DLADM_WALK_CONTINUE);
}
}
/*
* If the named port already exists, then update its configuration. If it
* doesn't, then create and enable it.
*/
static void
update_port(int vlan_id, const char *portname, datalink_id_t linkid,
datalink_class_t class)
{
int posn;
struct portdata *port;
struct pollfd *fds;
int port_index;
struct {
datalink_id_t linkid;
char linkname[MAXLINKNAMELEN];
} adddata;
bridge_setpvid_t bsv;
uint_t propval, valcnt;
dladm_status_t status;
for (posn = 0; posn < nextport; posn++) {
if (allports[posn]->linkid == linkid)
break;
}
/* If we need to allocate more array space, then do so in chunks. */
if (posn >= numports) {
struct portdata **newarr;
newarr = realloc(allports,
sizeof (*newarr) * (nextport + ALLOCINCR));
if (newarr != NULL)
allports = newarr;
fds = realloc(fdarray,
sizeof (*fds) * (nextport + ALLOCINCR + FDOFFSET));
if (fds != NULL)
fdarray = fds;
if (newarr == NULL || fds == NULL) {
syslog(LOG_ERR, "unable to add %s; no memory",
portname);
return;
}
numports = nextport + ALLOCINCR;
}
port_index = posn + 1;
fds = fdarray + posn + FDOFFSET;
/* If our linkid search ran to the end, then this is a new port. */
if (posn == nextport) {
if ((port = calloc(1, sizeof (*port))) == NULL) {
syslog(LOG_ERR, "unable to add %s; no memory",
portname);
return;
}
allports[posn] = port;
port->vlan_id = vlan_id;
port->linkid = linkid;
port->port_index = port_index;
port->phys_status = B_TRUE;
port->admin_status = B_TRUE;
port->state = BLS_BLOCKLISTEN;
nextport++;
} else {
/* Located port by linkid; we're just updating existing data */
port = allports[posn];
/*
* If it changed name, then close and reopen so we log under
* the most current name for this port.
*/
if (port->name != NULL && strcmp(portname, port->name) != 0) {
if (port->dlpi != NULL)
dlpi_close(port->dlpi);
port->dlpi = NULL;
port->name = NULL;
fds->fd = -1;
fds->events = 0;
}
}
/*
* If the port is not yet attached to the bridge in the kernel, then do
* that now.
*/
if (!port->kern_added) {
adddata.linkid = linkid;
(void) strlcpy(adddata.linkname, portname,
sizeof (adddata.linkname));
if (strioctl(control_fd, BRIOC_ADDLINK, &adddata,
sizeof (adddata.linkid) + strlen(adddata.linkname)) == -1) {
syslog(LOG_ERR, "cannot bridge %s: %m", portname);
goto failure;
}
port->kern_added = B_TRUE;
}
port->referenced = B_TRUE;
valcnt = 1;
status = dladm_get_linkprop_values(dlhandle, linkid,
DLADM_PROP_VAL_PERSISTENT, "forward", &propval, &valcnt);
if (status == DLADM_STATUS_OK)
port->admin_status = propval;
bsv.bsv_vlan = 1;
status = dladm_get_linkprop_values(dlhandle, linkid,
DLADM_PROP_VAL_PERSISTENT, "default_tag", &propval, &valcnt);
if (status == DLADM_STATUS_OK)
bsv.bsv_vlan = propval;
bsv.bsv_linkid = linkid;
if (strioctl(control_fd, BRIOC_SETPVID, &bsv, sizeof (bsv)) == -1) {
syslog(LOG_ERR, "can't set PVID on %s: %m", portname);
goto failure;
}
if (port->dlpi == NULL) {
if (!port_dlpi_open(portname, port, class))
goto failure;
fds->fd = dlpi_fd(port->dlpi);
fds->events = POLLIN;
}
if (rstp_add_port(port))
return;
failure:
if (port->dlpi != NULL) {
dlpi_close(port->dlpi);
port->dlpi = NULL;
port->name = NULL;
fds->fd = -1;
fds->events = 0;
}
if (port->kern_added) {
if (strioctl(control_fd, BRIOC_REMLINK, &port->linkid,
sizeof (port->linkid)) == -1)
syslog(LOG_ERR, "cannot remove from bridge %s: %m",
portname);
else
port->kern_added = B_FALSE;
}
if (posn + 1 == nextport) {
free(port);
nextport--;
}
}
/*ARGSUSED2*/
static int
update_link(dladm_handle_t handle, datalink_id_t linkid, void *arg)
{
dladm_status_t status;
char bridge[MAXLINKNAMELEN], linkname[MAXLINKNAMELEN];
char pointless[DLADM_STRSIZE];
datalink_class_t class;
status = dladm_bridge_getlink(handle, linkid, bridge, sizeof (bridge));
if (status == DLADM_STATUS_OK && strcmp(bridge, instance_name) == 0) {
status = dladm_datalink_id2info(handle, linkid, NULL, &class,
NULL, linkname, sizeof (linkname));
if (status == DLADM_STATUS_OK) {
update_port(0, linkname, linkid, class);
} else {
syslog(LOG_ERR, "unable to get link info for ID %u: %s",
linkid, dladm_status2str(status, pointless));
}
} else if (debugging) {
if (status != DLADM_STATUS_OK)
syslog(LOG_DEBUG,
"unable to get bridge data for ID %u: %s",
linkid, dladm_status2str(status, pointless));
else
syslog(LOG_DEBUG, "link ID %u is on bridge %s, not %s",
linkid, bridge, instance_name);
}
return (DLADM_WALK_CONTINUE);
}
/*
* Refresh action - reread configuration properties.
*/
static void
handle_refresh(int sigfd)
{
int i;
struct portdata *pdp;
struct pollfd *fdp;
char buf[16];
dladm_status_t status;
boolean_t new_debug;
uint32_t new_tablemax;
/* Drain signal events from pipe */
if (sigfd != -1)
(void) read(sigfd, buf, sizeof (buf));
status = dladm_bridge_get_privprop(instance_name, &new_debug,
&new_tablemax);
if (status == DLADM_STATUS_OK) {
if (debugging && !new_debug)
syslog(LOG_DEBUG, "disabling debugging");
debugging = new_debug;
if (new_tablemax != tablemax) {
syslog(LOG_DEBUG, "changed tablemax from %lu to %lu",
tablemax, new_tablemax);
if (strioctl(control_fd, BRIOC_TABLEMAX, &new_tablemax,
sizeof (tablemax)) == -1)
syslog(LOG_ERR, "cannot set table max "
"%lu on bridge %s: %m", tablemax,
instance_name);
else
tablemax = new_tablemax;
}
} else {
syslog(LOG_ERR, "%s: unable to refresh bridge properties: %s",
instance_name, dladm_status2str(status, buf));
}
rstp_refresh();
for (i = 0; i < nextport; i++)
allports[i]->referenced = B_FALSE;
/*
* libdladm doesn't guarantee anything about link ordering in a walk,
* so we do this walk twice: once to pick up the ports, and a second
* time to get the enabled VLANs on all ports.
*/
(void) dladm_walk_datalink_id(update_link, dlhandle, NULL,
DATALINK_CLASS_ALL, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
(void) dladm_walk_datalink_id(set_vlan, dlhandle, NULL,
DATALINK_CLASS_VLAN, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
/*
* If any ports now show up as unreferenced, then they've been removed
* from the configuration.
*/
for (i = 0; i < nextport; i++) {
pdp = allports[i];
fdp = fdarray + i + FDOFFSET;
if (!pdp->referenced) {
if (pdp->stp_added) {
(void) STP_IN_port_remove(pdp->vlan_id,
pdp->port_index);
pdp->stp_added = B_FALSE;
}
if (pdp->dlpi != NULL) {
dlpi_close(pdp->dlpi);
pdp->dlpi = NULL;
pdp->name = NULL;
fdp->fd = -1;
fdp->events = 0;
}
if (pdp->kern_added) {
if (strioctl(control_fd, BRIOC_REMLINK,
&pdp->linkid, sizeof (pdp->linkid)) == -1)
syslog(LOG_ERR, "cannot remove linkid "
"%u from bridge %s: %m",
pdp->linkid, instance_name);
pdp->kern_added = B_FALSE;
}
}
}
if (++refresh_count == 0)
refresh_count = 1;
}
/*
* Handle messages on the common control stream. This currently just deals
* with port SDU mismatches.
*/
static void
handle_control(void)
{
bridge_ctl_t bc;
ssize_t retv;
struct portdata *port;
int rc;
retv = read(control_fd, &bc, sizeof (bc));
if (retv != sizeof (bc))
return;
if ((port = find_by_linkid(bc.bc_linkid)) == NULL)
return;
if (port->sdu_failed == bc.bc_failed)
return;
port->sdu_failed = bc.bc_failed;
if (!port->phys_status || !port->admin_status ||
protect != DLADM_BRIDGE_PROT_STP)
return;
if (port->admin_non_stp) {
bridge_setstate_t bss;
bss.bss_linkid = port->linkid;
bss.bss_state = !port->sdu_failed && !port->bpdu_protect ?
BLS_FORWARDING : BLS_BLOCKLISTEN;
if (strioctl(control_fd, BRIOC_SETSTATE, &bss,
sizeof (bss)) == -1) {
syslog(LOG_ERR, "cannot set STP state on %s: %m",
port->name);
}
}
if ((rc = STP_IN_enable_port(port->port_index, !bc.bc_failed)) != 0)
syslog(LOG_ERR, "STP can't %s port %s for SDU failure: %s",
port->name, bc.bc_failed ? "disable" : "enable",
STP_IN_get_error_explanation(rc));
}
static void
receive_packet(struct portdata *port)
{
int rc;
size_t buflen;
uint16_t buffer[ETHERMAX / sizeof (uint16_t)];
struct ether_header *eh;
char sender[ETHERADDRL * 3];
buflen = sizeof (buffer);
rc = dlpi_recv(port->dlpi, NULL, NULL, buffer, &buflen, 1, NULL);
if (rc != DLPI_SUCCESS) {
if (rc != DLPI_ETIMEDOUT)
syslog(LOG_ERR, "receive failure on %s: %s", port->name,
dlpi_strerror(rc));
return;
}
/*
* If we're administratively disabled, then don't deliver packets to
* the STP state machine. It will re-enable the port because it uses
* the same variable for both link status and administrative state.
*/
if (!port->admin_status || protect != DLADM_BRIDGE_PROT_STP) {
if (debugging)
syslog(LOG_DEBUG,
"discard BPDU on non-forwarding interface %s",
port->name);
return;
}
/*
* There's a mismatch between the librstp and libdlpi expectations on
* receive. librstp wants the packet to start with the 802 length
* field, not the destination address.
*/
eh = (struct ether_header *)buffer;
rc = STP_IN_check_bpdu_header((BPDU_T *)&eh->ether_type, buflen);
/*
* Note that we attempt to avoid calling the relatively expensive
* _link_ntoa function unless we're going to use the result. In normal
* usage, we don't need this string.
*/
if (rc == 0) {
if (port->admin_non_stp && !port->bpdu_protect) {
bridge_setstate_t bss;
(void) _link_ntoa(eh->ether_shost.ether_addr_octet,
sender, ETHERADDRL, IFT_OTHER);
syslog(LOG_WARNING, "unexpected BPDU on %s from %s; "
"forwarding disabled", port->name, sender);
port->bpdu_protect = B_TRUE;
bss.bss_linkid = port->linkid;
bss.bss_state = BLS_BLOCKLISTEN;
if (strioctl(control_fd, BRIOC_SETSTATE, &bss,
sizeof (bss)) == -1) {
syslog(LOG_ERR, "cannot set STP state on "
"%s: %m", port->name);
}
return;
}
if (debugging) {
(void) _link_ntoa(eh->ether_shost.ether_addr_octet,
sender, ETHERADDRL, IFT_OTHER);
syslog(LOG_DEBUG, "got BPDU from %s on %s; %d bytes",
sender, port->name, buflen);
}
rc = STP_IN_rx_bpdu(port->vlan_id, port->port_index,
(BPDU_T *)&eh->ether_type, buflen);
}
if (rc != 0) {
(void) _link_ntoa(eh->ether_shost.ether_addr_octet, sender,
ETHERADDRL, IFT_OTHER);
syslog(LOG_DEBUG,
"discarded malformed packet on %s from %s: %s",
port->name, sender, STP_IN_get_error_explanation(rc));
}
}
void
get_dladm_speed(struct portdata *port)
{
dladm_status_t status;
uint64_t ifspeed;
status = dladm_get_single_mac_stat(dlhandle, port->linkid, "ifspeed",
KSTAT_DATA_UINT64, &ifspeed);
if (status == DLADM_STATUS_OK && ifspeed != 0)
port->speed = ifspeed / 1000000;
else
port->speed = 10UL;
}
void
enable_forwarding(struct portdata *port)
{
bridge_setstate_t bss;
bss.bss_linkid = port->linkid;
bss.bss_state = BLS_FORWARDING;
if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1)
syslog(LOG_ERR, "cannot set STP state on %s: %m", port->name);
}
void
event_loop(void)
{
int i;
hrtime_t last_time, now;
int tout;
if (lock_engine() != 0) {
syslog(LOG_ERR, "mutex lock");
exit(EXIT_FAILURE);
}
/* Bootstrap configuration */
handle_refresh(-1);
last_time = gethrtime();
while (!shutting_down) {
now = gethrtime();
if (now - last_time >= 1000000000ll) {
(void) STP_IN_one_second();
tout = 1000;
last_time = now;
} else {
tout = 1000 - (now - last_time) / 1000000ll;
}
unlock_engine();
(void) poll(fdarray, nextport + FDOFFSET, tout);
if (lock_engine() != 0) {
syslog(LOG_ERR, "mutex lock");
exit(EXIT_FAILURE);
}
if (fdarray[0].revents & POLLIN)
handle_refresh(fdarray[0].fd);
if (fdarray[1].revents & POLLIN)
handle_control();
for (i = 0; i < nextport; i++) {
if (fdarray[i + FDOFFSET].revents & POLLIN)
receive_packet(allports[i]);
}
}
unlock_engine();
}
/*
* 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 _BRIDGED_GLOBAL_H
#define _BRIDGED_GLOBAL_H
/*
* Globally visible symbols within the "bridged" bridging daemon
*/
#include
#include
#include
#include
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
struct portdata {
int vlan_id;
int port_index;
unsigned int speed;
boolean_t phys_status; /* physical layer status */
boolean_t admin_status; /* administrative status */
boolean_t kern_added; /* set when added to kernel bridge */
boolean_t stp_added; /* set when added to STP machine */
boolean_t referenced; /* used for refresh */
boolean_t sdu_failed; /* set for non-matching max SDU */
boolean_t admin_non_stp; /* copy of STP library config */
boolean_t bpdu_protect; /* BPDU seen when non-STP */
bridge_state_t state;
dlpi_handle_t dlpi;
dlpi_notifyid_t notifyid;
datalink_id_t linkid;
const char *name;
uchar_t mac_addr[ETHERADDRL];
};
/* Number of reserved (internal) fdarray entries */
#define FDOFFSET 2
/* main.c */
extern int lock_engine(void);
extern void unlock_engine(void);
extern ssize_t strioctl(int, int, void *, size_t);
extern struct portdata *find_by_linkid(datalink_id_t);
extern void get_dladm_speed(struct portdata *);
extern void enable_forwarding(struct portdata *);
extern boolean_t debugging;
extern uint32_t tablemax;
extern const char *instance_name;
extern dladm_handle_t dlhandle;
extern boolean_t shutting_down;
extern struct pollfd *fdarray;
/* door.c */
extern void init_door(void);
/* dlpi.c */
extern boolean_t port_dlpi_open(const char *, struct portdata *,
datalink_class_t);
/* rstp.c */
extern void rstp_init(void);
extern void rstp_refresh(void);
extern void rstp_change_mac(struct portdata *, const unsigned char *);
extern boolean_t rstp_add_port(struct portdata *);
/* events.c */
extern void open_bridge_control(void);
extern void event_loop(void);
extern int refresh_count;
extern dladm_bridge_prot_t protect;
extern uint_t nextport;
extern struct portdata **allports;
extern int control_fd;
#ifdef __cplusplus
}
#endif
#endif /* _BRIDGED_GLOBAL_H */
/*
* 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.
*/
/*
* bridged - bridging control daemon.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "global.h"
boolean_t debugging;
uint32_t tablemax;
const char *instance_name = "default";
struct pollfd *fdarray;
dladm_handle_t dlhandle;
boolean_t shutting_down;
static pthread_t sighand;
/*
* engine_lock is held while the main loop is busy calling librstp functions.
* Door threads take the lock to protect the library from reentrancy.
*/
static pthread_mutex_t engine_lock = PTHREAD_MUTEX_INITIALIZER;
/*
* These wrapper functions allow the other components in the daemon to remain
* ignorant of pthreads details.
*/
int
lock_engine(void)
{
return (pthread_mutex_lock(&engine_lock));
}
void
unlock_engine(void)
{
(void) pthread_mutex_unlock(&engine_lock);
}
/*
* Utility function for STREAMS ioctls.
*/
ssize_t
strioctl(int fd, int cmd, void *buf, size_t buflen)
{
int retv;
struct strioctl ic;
ic.ic_cmd = cmd;
ic.ic_timout = 0;
ic.ic_dp = buf;
ic.ic_len = buflen;
if ((retv = ioctl(fd, I_STR, &ic)) != 0)
return (retv);
else
return (ic.ic_len);
}
static void
daemonize(void)
{
pid_t pid;
/*
* A little bit of magic here. By the first fork+setsid, we
* disconnect from our current controlling terminal and become
* a session group leader. By forking again without calling
* setsid again, we make certain that we are not the session
* group leader and can never reacquire a controlling terminal.
*/
if ((pid = fork()) == (pid_t)-1) {
syslog(LOG_ERR, "fork 1 failed");
exit(EXIT_FAILURE);
}
if (pid != 0) {
(void) wait(NULL);
_exit(EXIT_SUCCESS);
}
if (setsid() == (pid_t)-1) {
syslog(LOG_ERR, "setsid");
exit(EXIT_FAILURE);
}
if ((pid = fork()) == (pid_t)-1) {
syslog(LOG_ERR, "fork 2 failed");
exit(EXIT_FAILURE);
}
if (pid != 0)
_exit(EXIT_SUCCESS);
(void) chdir("/");
(void) umask(022);
}
static void *
sighandler(void *arg)
{
sigset_t sigset;
int sig;
int sigfd = (int)(uintptr_t)arg;
(void) sigfillset(&sigset);
for (;;) {
sig = sigwait(&sigset);
switch (sig) {
case SIGHUP:
(void) write(sigfd, "", 1);
break;
default:
if (debugging)
syslog(LOG_NOTICE, "%s signal, shutting down",
strsignal(sig));
shutting_down = B_TRUE;
break;
}
/* if we're shutting down, exit this thread */
if (shutting_down)
return (NULL);
}
}
static void
init_signalhandling(void)
{
pthread_attr_t attr;
int err;
sigset_t new;
int fildes[2];
if ((fdarray = malloc(FDOFFSET * sizeof (struct pollfd))) == NULL) {
syslog(LOG_ERR, "unable to allocate fdarray: %m");
exit(EXIT_FAILURE);
}
if (pipe(fildes) != 0) {
syslog(LOG_ERR, "unable to create signal pipe: %m");
exit(EXIT_FAILURE);
}
fdarray[0].fd = fildes[0];
fdarray[0].events = POLLIN;
assert(control_fd != -1);
fdarray[1].fd = control_fd;
fdarray[1].events = POLLIN;
(void) sigfillset(&new);
(void) pthread_sigmask(SIG_BLOCK, &new, NULL);
(void) pthread_attr_init(&attr);
(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
err = pthread_create(&sighand, &attr, sighandler,
(void *)(uintptr_t)fildes[1]);
if (err != 0) {
syslog(LOG_ERR, "cannot create signal handling thread: %s",
strerror(err));
exit(EXIT_FAILURE);
}
(void) pthread_attr_destroy(&attr);
}
int
main(int argc, char **argv)
{
dladm_status_t status;
char buf[DLADM_STRSIZE];
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
shutting_down = B_FALSE;
openlog("bridged", LOG_PID | LOG_NDELAY, LOG_DAEMON);
if (argc != 2) {
syslog(LOG_ERR, "instance name is required");
exit(EXIT_FAILURE);
}
instance_name = argv[1];
if ((status = dladm_open(&dlhandle)) != DLADM_STATUS_OK) {
syslog(LOG_ERR, "%s: unable to open datalink control: %s",
instance_name, dladm_status2str(status, buf));
exit(EXIT_FAILURE);
}
status = dladm_bridge_get_privprop(instance_name, &debugging,
&tablemax);
if (status != DLADM_STATUS_OK) {
syslog(LOG_ERR, "%s: unable to read properties: %s",
instance_name, dladm_status2str(status, buf));
exit(EXIT_FAILURE);
}
/* Get the properties once so that we have the right initial values */
rstp_init();
open_bridge_control();
daemonize();
init_signalhandling();
init_door();
if (debugging)
syslog(LOG_INFO, "bridged started: instance %s", instance_name);
event_loop();
(void) pthread_cancel(sighand);
(void) pthread_join(sighand, NULL);
return (0);
}
/*
* 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.
*/
/*
* bridged - bridging control daemon. This module provides functions related
* to the librstp (Rapid Spanning Tree Protocol) library.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "global.h"
/* current engine configuration; access protected by engine_lock */
static UID_STP_CFG_T uid_cfg;
/*
* Our implementation doesn't have per-VLAN forwarding entries, so we just
* flush by the port. If port number is zero, then flush entries.
*/
/*ARGSUSED1*/
static int
flush_lt(int port_index, int vlan_id, LT_FLASH_TYPE_T type, char *reason)
{
struct portdata *pd;
const char *portname;
bridge_flushfwd_t bff;
if (port_index > nextport || port_index < 0)
return (0);
if (port_index == 0) {
type = LT_FLASH_ONLY_THE_PORT;
portname = "all";
bff.bff_linkid = DATALINK_INVALID_LINKID;
} else {
pd = allports[port_index - 1];
portname = pd->name;
bff.bff_linkid = pd->linkid;
}
if (debugging) {
syslog(LOG_DEBUG, "flush forwarding %s %s: %s",
type == LT_FLASH_ONLY_THE_PORT ? "to" : "except for",
portname, reason);
}
bff.bff_exclude = (type == LT_FLASH_ALL_PORTS_EXCLUDE_THIS);
/*
* If flushing fails, we can't return. The only safe thing to do is to
* tear down the bridge so that we're not harming the network.
*/
if (strioctl(control_fd, BRIOC_FLUSHFWD, &bff, sizeof (bff)) == -1) {
syslog(LOG_ERR, "cannot flush forwarding entries on %s %s: %m",
instance_name, portname);
unlock_engine();
exit(EXIT_FAILURE);
}
return (0);
}
static void
get_port_mac(int port_index, unsigned char *mac)
{
struct portdata *pd;
if (port_index > nextport || port_index <= 0)
return;
pd = allports[port_index - 1];
(void) memcpy(mac, pd->mac_addr, ETHERADDRL);
}
/* Returns speed in megabits per second */
static unsigned long
get_port_oper_speed(unsigned int port_index)
{
if (port_index > nextport || port_index == 0)
return (1000UL);
else
return (allports[port_index - 1]->speed);
}
static int
get_port_link_status(int port_index)
{
struct portdata *pd;
if (port_index > nextport || port_index <= 0) {
return (0);
} else {
pd = allports[port_index - 1];
return (pd->phys_status && pd->admin_status &&
protect == DLADM_BRIDGE_PROT_STP && !pd->sdu_failed ?
1 : 0);
}
}
static int
get_duplex(int port_index)
{
struct portdata *pd;
link_duplex_t link_duplex;
dladm_status_t status;
if (port_index > nextport || port_index <= 0)
return (False);
pd = allports[port_index - 1];
status = dladm_get_single_mac_stat(dlhandle, pd->linkid, "link_duplex",
KSTAT_DATA_UINT32, &link_duplex);
if (status == DLADM_STATUS_OK && link_duplex == LINK_DUPLEX_FULL)
return (True);
else
return (False);
}
static const char *
bls_state(bridge_state_t bstate)
{
switch (bstate) {
case BLS_LEARNING:
return ("learning");
case BLS_FORWARDING:
return ("forwarding");
default:
return ("block/listen");
}
}
/*ARGSUSED1*/
static int
set_port_state(int port_index, int vlan_id, RSTP_PORT_STATE state)
{
struct portdata *pd;
bridge_setstate_t bss;
if (port_index > nextport || port_index <= 0)
return (1);
pd = allports[port_index - 1];
if (debugging)
syslog(LOG_DEBUG, "setting port state on port %d (%s) to %d",
port_index, pd->name, state);
switch (state) {
case UID_PORT_LEARNING:
bss.bss_state = BLS_LEARNING;
break;
case UID_PORT_FORWARDING:
bss.bss_state = BLS_FORWARDING;
break;
default:
bss.bss_state = BLS_BLOCKLISTEN;
break;
}
bss.bss_linkid = pd->linkid;
if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) {
syslog(LOG_ERR, "cannot set STP state on %s from %s to %s: %m",
pd->name, bls_state(pd->state), bls_state(bss.bss_state));
/*
* If we've been unsuccessful in disabling forwarding, then the
* only safe thing to do is to make the daemon exit, so that
* the kernel will be forced to destroy the bridge state and
* terminate all forwarding.
*/
if (pd->state == BLS_FORWARDING &&
bss.bss_state != BLS_FORWARDING) {
unlock_engine();
exit(EXIT_FAILURE);
}
} else {
pd->state = bss.bss_state;
}
return (0);
}
/*
* Our hardware doesn't actually do anything different when STP is enabled or
* disabled, so this function does nothing. It would be possible to open and
* close the DLPI stream here, if such a thing were necessary.
*/
static int
set_hardware_mode(int vlan_id, UID_STP_MODE_T mode)
{
if (debugging)
syslog(LOG_DEBUG, "setting hardware mode on vlan %d to %d",
vlan_id, mode);
return (0);
}
/*ARGSUSED1*/
static int
tx_bpdu(int port_index, int vlan_id, unsigned char *bpdu, size_t bpdu_len)
{
struct portdata *pdp;
int rc;
if (port_index > nextport || port_index <= 0)
return (1);
pdp = allports[port_index - 1];
rc = dlpi_send(pdp->dlpi, NULL, 0, bpdu, bpdu_len, NULL);
if (rc == DLPI_SUCCESS) {
if (debugging)
syslog(LOG_DEBUG, "transmitted %d byte BPDU on %s",
bpdu_len, pdp->name);
return (0);
} else {
syslog(LOG_WARNING, "failed to send to %s: %s", pdp->name,
dlpi_strerror(rc));
return (1);
}
}
static const char *
get_port_name(int port_index)
{
if (port_index > nextport || port_index <= 0)
return ("unknown");
else
return (allports[port_index - 1]->name);
}
/*ARGSUSED*/
static int
get_init_stpm_cfg(int vlan_id, UID_STP_CFG_T *cfg)
{
/* under engine_lock because it's a callback from the engine */
*cfg = uid_cfg;
return (0);
}
/*ARGSUSED*/
static int
get_init_port_cfg(int vlan_id, int port_index, UID_STP_PORT_CFG_T *cfg)
{
struct portdata *pdp;
uint_t propval, valcnt;
datalink_id_t linkid;
dladm_status_t status;
if (port_index > nextport || port_index <= 0)
return (1);
pdp = allports[port_index - 1];
cfg->field_mask = 0;
cfg->port_priority = DEF_PORT_PRIO;
cfg->admin_non_stp = DEF_ADMIN_NON_STP;
cfg->admin_edge = DEF_ADMIN_EDGE;
cfg->admin_port_path_cost = ADMIN_PORT_PATH_COST_AUTO;
cfg->admin_point2point = DEF_P2P;
valcnt = 1;
linkid = pdp->linkid;
status = dladm_get_linkprop_values(dlhandle, linkid,
DLADM_PROP_VAL_PERSISTENT, "stp_priority", &propval, &valcnt);
if (status == DLADM_STATUS_OK) {
cfg->port_priority = propval;
cfg->field_mask |= PT_CFG_PRIO;
}
status = dladm_get_linkprop_values(dlhandle, linkid,
DLADM_PROP_VAL_PERSISTENT, "stp", &propval, &valcnt);
if (status == DLADM_STATUS_OK) {
cfg->admin_non_stp = !propval;
cfg->field_mask |= PT_CFG_NON_STP;
}
status = dladm_get_linkprop_values(dlhandle, linkid,
DLADM_PROP_VAL_PERSISTENT, "stp_edge", &propval, &valcnt);
if (status == DLADM_STATUS_OK) {
cfg->admin_edge = propval;
cfg->field_mask |= PT_CFG_EDGE;
}
status = dladm_get_linkprop_values(dlhandle, linkid,
DLADM_PROP_VAL_PERSISTENT, "stp_cost", &propval, &valcnt);
if (status == DLADM_STATUS_OK) {
cfg->admin_port_path_cost = propval;
cfg->field_mask |= PT_CFG_COST;
}
status = dladm_get_linkprop_values(dlhandle, linkid,
DLADM_PROP_VAL_PERSISTENT, "stp_p2p", &propval, &valcnt);
if (status == DLADM_STATUS_OK) {
cfg->admin_point2point = propval;
cfg->field_mask |= PT_CFG_P2P;
}
/*
* mcheck is special. It is actually a command, but the 802 documents
* define it as a variable that spontaneously resets itself. We need
* to handle that behavior here.
*/
status = dladm_get_linkprop_values(dlhandle, linkid,
DLADM_PROP_VAL_PERSISTENT, "stp_mcheck", &propval, &valcnt);
if (status == DLADM_STATUS_OK && propval != 0) {
char *pval = "0";
cfg->field_mask |= PT_CFG_MCHECK;
(void) dladm_set_linkprop(dlhandle, linkid, "stp_mcheck", &pval,
1, DLADM_OPT_ACTIVE|DLADM_OPT_PERSIST|DLADM_OPT_NOREFRESH);
}
pdp->admin_non_stp = cfg->admin_non_stp;
if (!pdp->admin_non_stp)
pdp->bpdu_protect = B_FALSE;
return (0);
}
static void
trace(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsyslog(LOG_DEBUG, fmt, ap);
va_end(ap);
}
static STP_VECTORS_T stp_vectors = {
flush_lt,
get_port_mac,
get_port_oper_speed,
get_port_link_status,
get_duplex,
set_port_state,
set_hardware_mode,
tx_bpdu,
get_port_name,
get_init_stpm_cfg,
get_init_port_cfg,
trace
};
void
rstp_init(void)
{
dladm_status_t status;
char buf[DLADM_STRSIZE];
STP_IN_init(&stp_vectors);
status = dladm_bridge_get_properties(instance_name, &uid_cfg, &protect);
if (status != DLADM_STATUS_OK) {
syslog(LOG_ERR, "%s: unable to read properties: %s",
instance_name, dladm_status2str(status, buf));
exit(EXIT_FAILURE);
}
}
/*
* This is called by a normal refresh operation. It gets the engine properties
* and resets.
*/
void
rstp_refresh(void)
{
dladm_status_t status;
int rc;
char buf[DLADM_STRSIZE];
UID_STP_CFG_T new_cfg;
dladm_bridge_prot_t new_prot;
status = dladm_bridge_get_properties(instance_name, &new_cfg,
&new_prot);
if (status != DLADM_STATUS_OK) {
syslog(LOG_ERR, "%s: unable to refresh bridge properties: %s",
instance_name, dladm_status2str(status, buf));
} else {
if (debugging && (protect != new_prot ||
uid_cfg.stp_enabled != new_cfg.stp_enabled)) {
syslog(LOG_DEBUG, "loop protection %s->%s, STP %d->%d",
dladm_bridge_prot2str(protect),
dladm_bridge_prot2str(new_prot),
uid_cfg.stp_enabled, new_cfg.stp_enabled);
}
/*
* The engine doesn't take kindly to parameter changes while
* running. Disable first if we must do this.
*/
if (uid_cfg.stp_enabled &&
memcmp(&uid_cfg, &new_cfg, sizeof (uid_cfg)) != 0) {
syslog(LOG_DEBUG, "resetting state machine");
uid_cfg.stp_enabled = STP_DISABLED;
rc = STP_IN_stpm_set_cfg(0, &uid_cfg);
if (rc != 0)
syslog(LOG_ERR, "STP machine reset config: %s",
STP_IN_get_error_explanation(rc));
}
uid_cfg = new_cfg;
protect = new_prot;
rc = STP_IN_stpm_set_cfg(0, &uid_cfg);
if (rc != 0)
syslog(LOG_ERR, "STP machine set config: %s",
STP_IN_get_error_explanation(rc));
}
}
/*
* This is called when a port changes its MAC address. If it's the main port,
* the one that supplies us our bridge ID, then we must choose a new ID, and to
* do that we shut the bridge down and bring it back up.
*/
void
rstp_change_mac(struct portdata *port, const unsigned char *newaddr)
{
unsigned short prio;
unsigned char mac[ETHERADDRL];
int rc;
char curid[ETHERADDRL * 3];
char newmac[ETHERADDRL * 3];
(void) _link_ntoa(port->mac_addr, curid, ETHERADDRL, IFT_OTHER);
(void) _link_ntoa(newaddr, newmac, ETHERADDRL, IFT_OTHER);
STP_IN_get_bridge_id(port->vlan_id, &prio, mac);
if (memcmp(port->mac_addr, mac, ETHERADDRL) == 0) {
syslog(LOG_NOTICE, "bridge ID must change: ID %s on %s changed "
"to %s", curid, port->name, newmac);
uid_cfg.stp_enabled = STP_DISABLED;
if ((rc = STP_IN_stpm_set_cfg(0, &uid_cfg)) != 0)
syslog(LOG_ERR, "STP machine set config: %s",
STP_IN_get_error_explanation(rc));
(void) memcpy(port->mac_addr, newaddr, ETHERADDRL);
uid_cfg.stp_enabled = STP_ENABLED;
if ((rc = STP_IN_stpm_set_cfg(0, &uid_cfg)) != 0)
syslog(LOG_ERR, "STP machine set config: %s",
STP_IN_get_error_explanation(rc));
} else {
syslog(LOG_DEBUG,
"MAC address on %s changed from %s to %s", port->name,
curid, newmac);
(void) memcpy(port->mac_addr, newaddr, ETHERADDRL);
}
}
boolean_t
rstp_add_port(struct portdata *port)
{
int rc;
UID_STP_PORT_CFG_T portcfg;
bridge_vlanenab_t bve;
bridge_setstate_t bss;
if (!port->stp_added &&
(rc = STP_IN_port_add(port->vlan_id, port->port_index)) != 0) {
syslog(LOG_ERR, "STP add %s %d: %s", port->name,
port->port_index, STP_IN_get_error_explanation(rc));
return (B_FALSE);
}
port->stp_added = B_TRUE;
/* guaranteed to succeed at this point */
(void) get_init_port_cfg(port->vlan_id, port->port_index, &portcfg);
/*
* Restore state when reenabling STP engine, set fixed state when
* disabling. For TRILL, we don't control forwarding at all, but we
* need to turn off our controls for TRILL to do its thing.
*/
bss.bss_linkid = port->linkid;
if (protect != DLADM_BRIDGE_PROT_STP) {
bss.bss_state = port->state = BLS_BLOCKLISTEN;
} else if (portcfg.admin_non_stp) {
bss.bss_state = port->admin_status && !port->sdu_failed &&
!port->bpdu_protect ? BLS_FORWARDING : BLS_BLOCKLISTEN;
} else {
bss.bss_state = port->state;
}
if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) {
syslog(LOG_ERR, "cannot set STP state on %s: %m", port->name);
goto failure;
}
rc = STP_IN_enable_port(port->port_index,
port->admin_status && port->phys_status && !port->sdu_failed &&
protect == DLADM_BRIDGE_PROT_STP);
if (rc != 0) {
syslog(LOG_ERR, "STP enable %s %d: %s", port->name,
port->port_index, STP_IN_get_error_explanation(rc));
goto failure;
}
if (debugging) {
rc = STP_IN_dbg_set_port_trace("all", True, 0,
port->port_index);
} else {
/* return to default debug state */
rc = STP_IN_dbg_set_port_trace("all", False, 0,
port->port_index);
if (rc == 0)
rc = STP_IN_dbg_set_port_trace("sttrans", True, 0,
port->port_index);
}
if (rc != 0) {
syslog(LOG_ERR, "STP trace %s %d: %s", port->name,
port->port_index, STP_IN_get_error_explanation(rc));
goto failure;
}
/* Clear out the kernel's allowed VLAN set; second walk will set */
bve.bve_linkid = port->linkid;
bve.bve_vlan = 0;
bve.bve_onoff = B_FALSE;
if (strioctl(control_fd, BRIOC_VLANENAB, &bve, sizeof (bve)) == -1) {
syslog(LOG_ERR, "unable to disable VLANs on %s: %m",
port->name);
goto failure;
}
if ((rc = STP_IN_port_set_cfg(0, port->port_index, &portcfg)) != 0) {
syslog(LOG_ERR, "STP port configure %s %d: %s", port->name,
port->port_index, STP_IN_get_error_explanation(rc));
goto failure;
}
return (B_TRUE);
failure:
(void) STP_IN_port_remove(port->vlan_id, port->port_index);
port->stp_added = B_FALSE;
return (B_FALSE);
}
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
#
# Copyright (c) 2018, Joyent, Inc.
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
LIB_INC= $(SRC)/lib/libilb/common
OBJS= $(ILBD_OBJS)
SRCS= $(ILBD_SRCS)
ILBSUBDIRS= ilb
include ../../../Makefile.cmd
include ../../../Makefile.ctf
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
CERRWARN += -Wno-switch
CERRWARN += $(CNOWARN_UNINIT)
# not linted
SMATCH=off
CSTD = $(CSTD_GNU99)
# I18n
POFILE = $(ILBD).po
POFILES = $(ILBD_SRCS:%.c=%.po)
all: TARGET= all
install: TARGET= install
clean: TARGET= clean
clobber: TARGET= clobber
LDLIBS += -lcmdutils -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)
$(POFILE): $(POFILES)
$(RM) $@
$(CAT) $(POFILES) > $@
all install clean clobber: $(ILBSUBDIRS)
include ../../../Makefile.targ
$(ILBSUBDIRS): FRC
@cd $@; pwd; $(MAKE) $(TARGET)
FRC:
#
# 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
CERRWARN += $(CNOWARN_UNINIT)
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
/*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* 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], ¶m->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, ¶m->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], ¶m->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, ¶m->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 = ¶m->vip;
nh = B_TRUE;
break;
case nat:
case half_nat:
param_addr = ¶m->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(¶m->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, ¶m->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(¶m->echo_cookie, icmph->icmp_data,
sizeof (param->echo_cookie));
bcopy(¶m->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, ¶m)) {
(void) printf("-1");
return (-1);
}
switch (param.probe) {
case ping_probe:
ret = ping_query(¶m);
break;
case tcp_probe:
ret = tcp_query(¶m);
break;
case udp_probe:
ret = udp_query(¶m);
break;
}
if (ret == -1)
(void) printf("-1");
return (ret);
}
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
*/
#ifndef _ILBD_H
#define _ILBD_H
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 {
int32_t ed_ipaddr_type; /* ADT_IPv4 or ADT_IPv6 */
uint32_t ed_server_address[4]; /* 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_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 *);
void cvt_addr(uint32_t *, int32_t, struct in6_addr);
int ilberror2auditerror(ilb_status_t);
#ifdef __cplusplus
}
#endif
#endif /* _ILBD_H */
Integrated layer 3/4 load balancer
/*
* 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.
* Copyright 2012 Milan Jurik. All rights reserved.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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);
list_destroy(&hc->ihc_rules);
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);
list_destroy(&hc->ihc_rules);
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 = ¬ify;
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);
list_destroy(&hc_rule->hcr_servers);
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--;
list_destroy(&hc_rule->hcr_servers);
free(hc_rule);
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");
case ILB_TOPO_NAT:
return ("NAT");
case ILB_TOPO_HALF_NAT:
return ("HALF_NAT");
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;
}
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;
}
(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;
boolean_t init_fd_actions = B_FALSE;
posix_spawnattr_t attr;
boolean_t init_attr = B_FALSE;
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;
}
init_fd_actions = B_TRUE;
if (posix_spawnattr_init(&attr) != 0) {
logdebug("ilbd_run_probe: posix_spawnattr_init");
goto cleanup;
}
init_attr = B_TRUE;
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]);
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;
}
destroy_argv(child_argv);
(void) posix_spawn_file_actions_destroy(&fd_actions);
(void) posix_spawnattr_destroy(&attr);
return (B_TRUE);
cleanup:
destroy_argv(child_argv);
if (init_fd_actions == B_TRUE)
(void) posix_spawn_file_actions_destroy(&fd_actions);
if (init_attr == B_TRUE)
(void) posix_spawnattr_destroy(&attr);
(void) close(fds[0]);
(void) close(fds[1]);
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);
}
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016 by Delphix. All rights reserved.
*/
/*
* 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, they 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 (c) 2005, 2010, Oracle and/or its affiliates. " \
"All rights reserved.\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);
if (cli->cli_peer_ucredp != NULL)
ucred_free(cli->cli_peer_ucredp);
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 = NULL;
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) {
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) {
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");
clean_up:
if (cli != NULL) {
if (cli->cli_peer_ucredp != NULL)
ucred_free(cli->cli_peer_ucredp);
free(cli->cli_pw_buf);
free(cli);
}
(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);
}
/*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#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++;
}
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++;
}
free(kcmd);
return (ret);
}
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "libilb_impl.h"
#include "ilbd.h"
/* until we all use AF_* macros ... */
#define AF_2_IPPROTO(_af) (_af == AF_INET)?IPPROTO_IP:IPPROTO_IPV6
#define IPPROTO_2_AF(_i) (_i == IPPROTO_IP)?AF_INET:AF_INET6
#define PROTOCOL_LEN 16 /* protocol type */
#define ADDR_LEN (2 * INET6_ADDRSTRLEN + 1) /* prxy src range */
#define PORT_LEN 6 /* hcport:1-65535 or "ANY" */
static ilb_status_t ilbd_disable_one_rule(ilbd_rule_t *, boolean_t);
static uint32_t i_flags_d2k(int);
#define ILB_SGSRV_2_KSRV(s, k) \
(k)->addr = (s)->sgs_addr; \
(k)->min_port = (s)->sgs_minport; \
(k)->max_port = (s)->sgs_maxport; \
(k)->flags = i_flags_d2k((s)->sgs_flags); \
(k)->err = 0; \
(void) strlcpy((k)->name, (s)->sgs_srvID, sizeof ((k)->name))
list_t ilbd_rule_hlist;
static ilb_algo_t
algo_impl2lib(ilb_algo_impl_t a)
{
switch (a) {
case ILB_ALG_IMPL_ROUNDROBIN:
return (ILB_ALG_ROUNDROBIN);
case ILB_ALG_IMPL_HASH_IP:
return (ILB_ALG_HASH_IP);
case ILB_ALG_IMPL_HASH_IP_SPORT:
return (ILB_ALG_HASH_IP_SPORT);
case ILB_ALG_IMPL_HASH_IP_VIP:
return (ILB_ALG_HASH_IP_VIP);
}
return (0);
}
static ilb_topo_t
topo_impl2lib(ilb_topo_impl_t t)
{
switch (t) {
case ILB_TOPO_IMPL_DSR:
return (ILB_TOPO_DSR);
case ILB_TOPO_IMPL_NAT:
return (ILB_TOPO_NAT);
case ILB_TOPO_IMPL_HALF_NAT:
return (ILB_TOPO_HALF_NAT);
}
return (0);
}
ilb_algo_impl_t
algo_lib2impl(ilb_algo_t a)
{
switch (a) {
case ILB_ALG_ROUNDROBIN:
return (ILB_ALG_IMPL_ROUNDROBIN);
case ILB_ALG_HASH_IP:
return (ILB_ALG_IMPL_HASH_IP);
case ILB_ALG_HASH_IP_SPORT:
return (ILB_ALG_IMPL_HASH_IP_SPORT);
case ILB_ALG_HASH_IP_VIP:
return (ILB_ALG_IMPL_HASH_IP_VIP);
}
return (0);
}
ilb_topo_impl_t
topo_lib2impl(ilb_topo_t t)
{
switch (t) {
case ILB_TOPO_DSR:
return (ILB_TOPO_IMPL_DSR);
case ILB_TOPO_NAT:
return (ILB_TOPO_IMPL_NAT);
case ILB_TOPO_HALF_NAT:
return (ILB_TOPO_IMPL_HALF_NAT);
}
return (0);
}
/*
* Walk the list of rules and check if its safe to add the
* the server to the rule (this is a list of rules hanging
* off of a server group)
*/
ilb_status_t
i_check_srv2rules(list_t *rlist, ilb_sg_srv_t *srv)
{
ilb_status_t rc = ILB_STATUS_OK;
ilbd_rule_t *rl;
int server_portrange, rule_portrange;
int srv_minport, srv_maxport;
int r_minport, r_maxport;
if (srv == NULL)
return (ILB_STATUS_OK);
srv_minport = ntohs(srv->sgs_minport);
srv_maxport = ntohs(srv->sgs_maxport);
for (rl = list_head(rlist); rl != NULL; rl = list_next(rlist, rl)) {
r_minport = ntohs(rl->irl_minport);
r_maxport = ntohs(rl->irl_maxport);
if ((srv_minport != 0) && (srv_minport == srv_maxport)) {
/* server has single port */
if (rl->irl_topo == ILB_TOPO_DSR) {
/*
* either we have a DSR rule with a port
* range, or both server and rule
* have single ports but their values
* don't match - this is incompatible
*/
if (r_maxport > r_minport) {
rc = ILB_STATUS_INVAL_SRVR;
break;
} else if (srv_minport != r_minport) {
rc = ILB_STATUS_BADPORT;
break;
}
}
if (rl->irl_hcpflag == ILB_HCI_PROBE_FIX &&
rl->irl_hcport != srv_minport) {
rc = ILB_STATUS_BADPORT;
break;
}
} else if (srv_maxport > srv_minport) {
/* server has a port range */
if ((rl->irl_topo == ILB_TOPO_DSR) &&
(r_maxport > r_minport)) {
if ((r_minport != srv_minport) ||
(r_maxport != srv_maxport)) {
/*
* we have a DSR rule with a port range
* and its min and max port values
* does not meet that of server's
* - this is incompatible
*/
rc = ILB_STATUS_BADPORT;
break;
}
} else if ((rl->irl_topo == ILB_TOPO_DSR) &&
(r_maxport == r_minport)) {
/*
* we have a DSR rule with a single
* port and a server with a port range
* - this is incompatible
*/
rc = ILB_STATUS_INVAL_SRVR;
break;
} else if (((rl->irl_topo == ILB_TOPO_NAT) ||
(rl->irl_topo == ILB_TOPO_HALF_NAT)) &&
(r_maxport > r_minport)) {
server_portrange = srv_maxport - srv_minport;
rule_portrange = r_maxport - r_minport;
if (rule_portrange != server_portrange) {
/*
* we have a NAT/Half-NAT rule with
* a port range and server with a port
* range and there is a mismatch in the
* sizes of the port ranges - this is
* incompatible
*/
rc = ILB_STATUS_INVAL_SRVR;
break;
}
}
if (rl->irl_hcpflag == ILB_HCI_PROBE_FIX &&
(rl->irl_hcport > srv_maxport ||
rl->irl_hcport < srv_minport)) {
rc = ILB_STATUS_BADPORT;
break;
}
}
}
return (rc);
}
void
i_setup_rule_hlist(void)
{
list_create(&ilbd_rule_hlist, sizeof (ilbd_rule_t),
offsetof(ilbd_rule_t, irl_link));
}
ilb_status_t
i_ilbd_save_rule(ilbd_rule_t *irl, ilbd_scf_cmd_t scf_cmd)
{
boolean_t enable = irl->irl_flags & ILB_FLAGS_RULE_ENABLED;
switch (scf_cmd) {
case ILBD_SCF_CREATE:
return (ilbd_create_pg(ILBD_SCF_RULE, (void *)irl));
case ILBD_SCF_DESTROY:
return (ilbd_destroy_pg(ILBD_SCF_RULE, irl->irl_name));
case ILBD_SCF_ENABLE_DISABLE:
return (ilbd_change_prop(ILBD_SCF_RULE, irl->irl_name,
"status", &enable));
default:
logdebug("i_ilbd_save_rule: invalid scf cmd %d", scf_cmd);
return (ILB_STATUS_INVAL_CMD);
}
}
/*
* allocate a new daemon-specific rule from the "template" passed
* in in *r
*/
static ilbd_rule_t *
i_alloc_ilbd_rule(ilb_rule_info_t *r)
{
ilbd_rule_t *rl;
rl = calloc(sizeof (*rl), 1);
if (rl != NULL && r != NULL)
bcopy(r, &rl->irl_info, sizeof (*r));
return (rl);
}
static ilbd_rule_t *
i_find_rule_byname(const char *name)
{
ilbd_rule_t *rl;
/* find position of rule in list */
rl = list_head(&ilbd_rule_hlist);
while (rl != NULL &&
strncmp(rl->irl_name, name, sizeof (rl->irl_name)) != 0) {
rl = list_next(&ilbd_rule_hlist, rl);
}
return (rl);
}
/*
* get exactly one rule (named in rl->irl_name) data from kernel
*/
static ilb_status_t
ilb_get_krule(ilb_rule_info_t *rl)
{
ilb_status_t rc;
ilb_rule_cmd_t kcmd;
kcmd.cmd = ILB_LIST_RULE;
(void) strlcpy(kcmd.name, rl->rl_name, sizeof (kcmd.name));
kcmd.flags = 0;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK)
return (rc);
rl->rl_flags = kcmd.flags;
rl->rl_ipversion = IPPROTO_2_AF(kcmd.ip_ver);
rl->rl_vip = kcmd.vip;
rl->rl_proto = kcmd.proto;
rl->rl_minport = kcmd.min_port;
rl->rl_maxport = kcmd.max_port;
rl->rl_algo = algo_impl2lib(kcmd.algo);
rl->rl_topo = topo_impl2lib(kcmd.topo);
rl->rl_stickymask = kcmd.sticky_mask;
rl->rl_nat_src_start = kcmd.nat_src_start;
rl->rl_nat_src_end = kcmd.nat_src_end;
(void) strlcpy(rl->rl_name, kcmd.name, sizeof (rl->rl_name));
rl->rl_conndrain = kcmd.conn_drain_timeout;
rl->rl_nat_timeout = kcmd.nat_expiry;
rl->rl_sticky_timeout = kcmd.sticky_expiry;
return (ILB_STATUS_OK);
}
ilb_status_t
ilbd_retrieve_rule(ilbd_name_t rl_name, uint32_t *rbuf, size_t *rbufsz)
{
ilbd_rule_t *irl = NULL;
ilb_status_t rc;
ilb_rule_info_t *rinfo;
irl = i_find_rule_byname(rl_name);
if (irl == NULL)
return (ILB_STATUS_ENOENT);
ilbd_reply_ok(rbuf, rbufsz);
rinfo = (ilb_rule_info_t *)&((ilb_comm_t *)rbuf)->ic_data;
bcopy(&irl->irl_info, rinfo, sizeof (*rinfo));
/*
* Check if the various timeout values are 0. If one is, get the
* default values from kernel.
*/
if (rinfo->rl_conndrain == 0 || rinfo->rl_nat_timeout == 0 ||
rinfo->rl_sticky_timeout == 0) {
ilb_rule_info_t tmp_info;
(void) strcpy(tmp_info.rl_name, rinfo->rl_name);
rc = ilb_get_krule(&tmp_info);
if (rc != ILB_STATUS_OK)
return (rc);
if (rinfo->rl_conndrain == 0)
rinfo->rl_conndrain = tmp_info.rl_conndrain;
if ((rinfo->rl_topo == ILB_TOPO_NAT ||
rinfo->rl_topo == ILB_TOPO_HALF_NAT) &&
rinfo->rl_nat_timeout == 0) {
rinfo->rl_nat_timeout = tmp_info.rl_nat_timeout;
}
if ((rinfo->rl_flags & ILB_FLAGS_RULE_STICKY) &&
rinfo->rl_sticky_timeout == 0) {
rinfo->rl_sticky_timeout = tmp_info.rl_sticky_timeout;
}
}
*rbufsz += sizeof (ilb_rule_info_t);
return (ILB_STATUS_OK);
}
static ilb_status_t
ilbd_destroy_one_rule(ilbd_rule_t *irl)
{
ilb_status_t rc;
ilb_name_cmd_t kcmd;
/*
* as far as talking to the kernel is concerned, "all rules"
* is handled in one go somewhere else, so we only
* tell the kernel about single rules here.
*/
if ((irl->irl_flags & ILB_FLAGS_RULE_ALLRULES) == 0) {
kcmd.cmd = ILB_DESTROY_RULE;
(void) strlcpy(kcmd.name, irl->irl_name, sizeof (kcmd.name));
kcmd.flags = 0;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK)
return (rc);
}
list_remove(&irl->irl_sg->isg_rulelist, irl);
list_remove(&ilbd_rule_hlist, irl);
/*
* When dissociating a rule, only two errors can happen. The hc
* name is incorrect or the rule is not associated with the hc
* object. Both should not happen.... The check is for debugging
* purpose.
*/
if (RULE_HAS_HC(irl) && (rc = ilbd_hc_dissociate_rule(irl)) !=
ILB_STATUS_OK) {
logerr("ilbd_destroy_one_rule: cannot "
"dissociate %s from hc object %s: %d",
irl->irl_name, irl->irl_hcname, rc);
}
rc = i_ilbd_save_rule(irl, ILBD_SCF_DESTROY);
if (rc != ILB_STATUS_OK)
logdebug("ilbd_destroy_rule: save rule failed");
free(irl);
return (rc);
}
/*
* the following two functions are the other's opposite, and can
* call into each other for roll back purposes in case of error.
* To avoid endless recursion, the 'is_rollback' parameter must be
* set to B_TRUE in the roll back case.
*/
static ilb_status_t
ilbd_enable_one_rule(ilbd_rule_t *irl, boolean_t is_rollback)
{
ilb_status_t rc = ILB_STATUS_OK;
ilb_name_cmd_t kcmd;
/* no use sending a no-op to the kernel */
if ((irl->irl_flags & ILB_FLAGS_RULE_ENABLED) != 0)
return (ILB_STATUS_OK);
irl->irl_flags |= ILB_FLAGS_RULE_ENABLED;
/* "all rules" is handled in one go somewhere else, not here */
if ((irl->irl_flags & ILB_FLAGS_RULE_ALLRULES) == 0) {
kcmd.cmd = ILB_ENABLE_RULE;
(void) strlcpy(kcmd.name, irl->irl_name, sizeof (kcmd.name));
kcmd.flags = 0;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK)
return (rc);
}
if (RULE_HAS_HC(irl) && (rc = ilbd_hc_enable_rule(irl)) !=
ILB_STATUS_OK) {
/* Undo the kernel work */
kcmd.cmd = ILB_DISABLE_RULE;
/* Cannot do much if ioctl fails... */
(void) do_ioctl(&kcmd, 0);
return (rc);
}
if (!is_rollback) {
if (rc == ILB_STATUS_OK)
rc = i_ilbd_save_rule(irl, ILBD_SCF_ENABLE_DISABLE);
if (rc != ILB_STATUS_OK)
/* ignore rollback return code */
(void) ilbd_disable_one_rule(irl, B_TRUE);
}
return (rc);
}
static ilb_status_t
ilbd_disable_one_rule(ilbd_rule_t *irl, boolean_t is_rollback)
{
ilb_status_t rc = ILB_STATUS_OK;
ilb_name_cmd_t kcmd;
/* no use sending a no-op to the kernel */
if ((irl->irl_flags & ILB_FLAGS_RULE_ENABLED) == 0)
return (ILB_STATUS_OK);
irl->irl_flags &= ~ILB_FLAGS_RULE_ENABLED;
/* "all rules" is handled in one go somewhere else, not here */
if ((irl->irl_flags & ILB_FLAGS_RULE_ALLRULES) == 0) {
kcmd.cmd = ILB_DISABLE_RULE;
(void) strlcpy(kcmd.name, irl->irl_name, sizeof (kcmd.name));
kcmd.flags = 0;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK)
return (rc);
}
if (RULE_HAS_HC(irl) && (rc = ilbd_hc_disable_rule(irl)) !=
ILB_STATUS_OK) {
/* Undo the kernel work */
kcmd.cmd = ILB_ENABLE_RULE;
/* Cannot do much if ioctl fails... */
(void) do_ioctl(&kcmd, 0);
return (rc);
}
if (!is_rollback) {
if (rc == ILB_STATUS_OK)
rc = i_ilbd_save_rule(irl, ILBD_SCF_ENABLE_DISABLE);
if (rc != ILB_STATUS_OK)
/* ignore rollback return code */
(void) ilbd_enable_one_rule(irl, B_TRUE);
}
return (rc);
}
/*
* Generates an audit record for a supplied rule name
* Used for enable_rule, disable_rule, delete_rule,
* and create_rule subcommands
*/
static void
ilbd_audit_rule_event(const char *audit_rule_name,
ilb_rule_info_t *rlinfo, ilbd_cmd_t cmd, ilb_status_t rc,
ucred_t *ucredp)
{
adt_session_data_t *ah;
adt_event_data_t *event;
au_event_t flag;
int scf_val_len = ILBD_MAX_VALUE_LEN;
char *aobuf = NULL; /* algo:topo */
char *valstr1 = NULL;
char *valstr2 = NULL;
char pbuf[PROTOCOL_LEN]; /* protocol */
char hcpbuf[PORT_LEN]; /* hcport */
int audit_error;
if ((ucredp == NULL) && (cmd == ILBD_CREATE_RULE)) {
/*
* we came here from the path where ilbd incorporates
* the configuration that is listed in SCF :
* i_ilbd_read_config->ilbd_walk_rule_pgs->
* ->ilbd_scf_instance_walk_pg->ilbd_create_rule
* We skip auditing in that case
*/
return;
}
if (adt_start_session(&ah, NULL, 0) != 0) {
logerr("ilbd_audit_rule_event: adt_start_session failed");
exit(EXIT_FAILURE);
}
if (adt_set_from_ucred(ah, ucredp, ADT_NEW) != 0) {
(void) adt_end_session(ah);
logerr("ilbd_audit_rule_event: adt_set_from_ucred failed");
exit(EXIT_FAILURE);
}
if (cmd == ILBD_ENABLE_RULE)
flag = ADT_ilb_enable_rule;
else if (cmd == ILBD_DISABLE_RULE)
flag = ADT_ilb_disable_rule;
else if (cmd == ILBD_DESTROY_RULE)
flag = ADT_ilb_delete_rule;
else if (cmd == ILBD_CREATE_RULE)
flag = ADT_ilb_create_rule;
if ((event = adt_alloc_event(ah, flag)) == NULL) {
logerr("ilbd_audit_rule_event: adt_alloc_event failed");
exit(EXIT_FAILURE);
}
(void) memset((char *)event, 0, sizeof (adt_event_data_t));
switch (cmd) {
case ILBD_DESTROY_RULE:
event->adt_ilb_delete_rule.auth_used = NET_ILB_CONFIG_AUTH;
event->adt_ilb_delete_rule.rule_name = (char *)audit_rule_name;
break;
case ILBD_ENABLE_RULE:
event->adt_ilb_enable_rule.auth_used = NET_ILB_ENABLE_AUTH;
event->adt_ilb_enable_rule.rule_name = (char *)audit_rule_name;
break;
case ILBD_DISABLE_RULE:
event->adt_ilb_disable_rule.auth_used = NET_ILB_ENABLE_AUTH;
event->adt_ilb_disable_rule.rule_name = (char *)audit_rule_name;
break;
case ILBD_CREATE_RULE:
if (((aobuf = malloc(scf_val_len)) == NULL) ||
((valstr1 = malloc(scf_val_len)) == NULL) ||
((valstr2 = malloc(scf_val_len)) == NULL)) {
logerr("ilbd_audit_rule_event: could not"
" allocate buffer");
exit(EXIT_FAILURE);
}
event->adt_ilb_create_rule.auth_used = NET_ILB_CONFIG_AUTH;
/* Fill in virtual IP address type */
if (IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_vip)) {
event->adt_ilb_create_rule.virtual_ipaddress_type =
ADT_IPv4;
cvt_addr(event->adt_ilb_create_rule.virtual_ipaddress,
ADT_IPv4, rlinfo->rl_vip);
} else {
event->adt_ilb_create_rule.virtual_ipaddress_type =
ADT_IPv6;
cvt_addr(event->adt_ilb_create_rule.virtual_ipaddress,
ADT_IPv6, rlinfo->rl_vip);
}
/* Fill in port - could be a single value or a range */
event->adt_ilb_create_rule.min_port = ntohs(rlinfo->rl_minport);
if (ntohs(rlinfo->rl_maxport) > ntohs(rlinfo->rl_minport)) {
/* port range */
event->adt_ilb_create_rule.max_port =
ntohs(rlinfo->rl_maxport);
} else {
/* in audit record, max=min when single port */
event->adt_ilb_create_rule.max_port =
ntohs(rlinfo->rl_minport);
}
/*
* Fill in protocol - if user does not specify it,
* its TCP by default
*/
if (rlinfo->rl_proto == IPPROTO_UDP)
(void) snprintf(pbuf, PROTOCOL_LEN, "UDP");
else
(void) snprintf(pbuf, PROTOCOL_LEN, "TCP");
event->adt_ilb_create_rule.protocol = pbuf;
/* Fill in algorithm and operation type */
ilbd_algo_to_str(rlinfo->rl_algo, valstr1);
ilbd_topo_to_str(rlinfo->rl_topo, valstr2);
(void) snprintf(aobuf, scf_val_len, "%s:%s",
valstr1, valstr2);
event->adt_ilb_create_rule.algo_optype = aobuf;
/* Fill in proxy-src for the NAT case */
if (rlinfo->rl_topo == ILB_TOPO_NAT) {
/* copy starting proxy-src address */
if (IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_nat_src_start)) {
/* V4 case */
event->adt_ilb_create_rule.proxy_src_min_type =
ADT_IPv4;
cvt_addr(
event->adt_ilb_create_rule.proxy_src_min,
ADT_IPv4, rlinfo->rl_nat_src_start);
} else {
/* V6 case */
event->adt_ilb_create_rule.proxy_src_min_type =
ADT_IPv6;
cvt_addr(
event->adt_ilb_create_rule.proxy_src_min,
ADT_IPv6, rlinfo->rl_nat_src_start);
}
/* copy ending proxy-src address */
if (IN6_IS_ADDR_UNSPECIFIED(&rlinfo->rl_nat_src_end)) {
/* proxy-src is a single address */
event->adt_ilb_create_rule.proxy_src_max_type =
event->
adt_ilb_create_rule.proxy_src_min_type;
(void) memcpy(
event->adt_ilb_create_rule.proxy_src_max,
event->adt_ilb_create_rule.proxy_src_min,
(4 * sizeof (uint32_t)));
} else if (
IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_nat_src_end)) {
/*
* proxy-src is a address range - copy ending
* proxy-src address
* V4 case
*/
event->adt_ilb_create_rule.proxy_src_max_type =
ADT_IPv4;
cvt_addr(
event->adt_ilb_create_rule.proxy_src_max,
ADT_IPv4, rlinfo->rl_nat_src_end);
} else {
/* V6 case */
event->adt_ilb_create_rule.proxy_src_max_type =
ADT_IPv6;
cvt_addr(
event->adt_ilb_create_rule.proxy_src_max,
ADT_IPv6, rlinfo->rl_nat_src_end);
}
}
/*
* Fill in pmask if user has specified one - 0 means
* no persistence
*/
valstr1[0] = '\0';
ilbd_ip_to_str(rlinfo->rl_ipversion, &rlinfo->rl_stickymask,
valstr1);
event->adt_ilb_create_rule.persist_mask = valstr1;
/* If there is a hcname */
if (rlinfo->rl_hcname[0] != '\0')
event->adt_ilb_create_rule.hcname = rlinfo->rl_hcname;
/* Fill in hcport */
if (rlinfo->rl_hcpflag == ILB_HCI_PROBE_FIX) {
/* hcport is specified by user */
(void) snprintf(hcpbuf, PORT_LEN, "%d",
rlinfo->rl_hcport);
event->adt_ilb_create_rule.hcport = hcpbuf;
} else if (rlinfo->rl_hcpflag == ILB_HCI_PROBE_ANY) {
/* user has specified "ANY" */
(void) snprintf(hcpbuf, PORT_LEN, "ANY");
event->adt_ilb_create_rule.hcport = hcpbuf;
}
/*
* Fill out the conndrain, nat_timeout and persist_timeout
* If the user does not specify them, the default value
* is set in the kernel. Userland does not know what
* the values are. So if the user
* does not specify these values they will show up as
* 0 in the audit record.
*/
event->adt_ilb_create_rule.conndrain_timeout =
rlinfo->rl_conndrain;
event->adt_ilb_create_rule.nat_timeout =
rlinfo->rl_nat_timeout;
event->adt_ilb_create_rule.persist_timeout =
rlinfo->rl_sticky_timeout;
/* Fill out servergroup and rule name */
event->adt_ilb_create_rule.server_group = rlinfo->rl_sgname;
event->adt_ilb_create_rule.rule_name = rlinfo->rl_name;
break;
}
if (rc == ILB_STATUS_OK) {
if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
logerr("ilbd_audit_rule_event:adt_put_event failed");
exit(EXIT_FAILURE);
}
} else {
audit_error = ilberror2auditerror(rc);
if (adt_put_event(event, ADT_FAILURE, audit_error) != 0) {
logerr("ilbd_audit_rule_event: adt_put_event failed");
exit(EXIT_FAILURE);
}
}
adt_free_event(event);
free(aobuf);
free(valstr1);
free(valstr2);
(void) adt_end_session(ah);
}
/*
* converts IP address from in6_addr format to uint32_t[4]
* This conversion is needed for recording IP address in
* audit records.
*/
void
cvt_addr(uint32_t *audit, int32_t type, struct in6_addr address)
{
if (type == ADT_IPv4) {
/* address is IPv4 */
audit[0] = address._S6_un._S6_u32[3];
} else {
/* address is IPv6 */
(void) memcpy(audit, address._S6_un._S6_u32,
(4 * sizeof (uint32_t)));
}
}
static ilb_status_t
i_ilbd_action_switch(ilbd_rule_t *irl, ilbd_cmd_t cmd,
boolean_t is_rollback, ucred_t *ucredp)
{
ilb_status_t rc;
switch (cmd) {
case ILBD_DESTROY_RULE:
rc = ilbd_destroy_one_rule(irl);
if (!is_rollback) {
ilbd_audit_rule_event(irl->irl_name, NULL,
cmd, rc, ucredp);
}
return (rc);
case ILBD_ENABLE_RULE:
rc = ilbd_enable_one_rule(irl, is_rollback);
if (!is_rollback) {
ilbd_audit_rule_event(irl->irl_name, NULL, cmd,
rc, ucredp);
}
return (rc);
case ILBD_DISABLE_RULE:
rc = ilbd_disable_one_rule(irl, is_rollback);
if (!is_rollback) {
ilbd_audit_rule_event(irl->irl_name, NULL, cmd,
rc, ucredp);
}
return (rc);
}
return (ILB_STATUS_INVAL_CMD);
}
static ilb_cmd_t
i_ilbd2ilb_cmd(ilbd_cmd_t c)
{
ilb_cmd_t r;
switch (c) {
case ILBD_CREATE_RULE:
r = ILB_CREATE_RULE;
break;
case ILBD_DESTROY_RULE:
r = ILB_DESTROY_RULE;
break;
case ILBD_ENABLE_RULE:
r = ILB_ENABLE_RULE;
break;
case ILBD_DISABLE_RULE:
r = ILB_DISABLE_RULE;
break;
}
return (r);
}
static ilbd_cmd_t
get_undo_cmd(ilbd_cmd_t cmd)
{
ilbd_cmd_t u_cmd;
switch (cmd) {
case ILBD_DESTROY_RULE:
u_cmd = ILBD_BAD_CMD;
break;
case ILBD_ENABLE_RULE:
u_cmd = ILBD_DISABLE_RULE;
break;
case ILBD_DISABLE_RULE:
u_cmd = ILBD_ENABLE_RULE;
break;
}
return (u_cmd);
}
static ilb_status_t
i_ilbd_rule_action(const char *rule_name, const struct passwd *ps,
ilbd_cmd_t cmd, ucred_t *ucredp)
{
ilbd_rule_t *irl, *irl_next;
boolean_t is_all_rules = B_FALSE;
ilb_status_t rc = ILB_STATUS_OK;
ilb_name_cmd_t kcmd;
ilbd_cmd_t u_cmd;
char rulename[ILB_NAMESZ];
if (ps != NULL) {
if ((cmd == ILBD_ENABLE_RULE) || (cmd == ILBD_DISABLE_RULE))
rc = ilbd_check_client_enable_auth(ps);
else
rc = ilbd_check_client_config_auth(ps);
/* generate the audit record before bailing out */
if (rc != ILB_STATUS_OK) {
if (*rule_name != '\0') {
ilbd_audit_rule_event(rule_name, NULL,
cmd, rc, ucredp);
} else {
(void) snprintf(rulename, sizeof (rulename),
"all");
ilbd_audit_rule_event(rulename, NULL, cmd, rc,
ucredp);
}
goto out;
}
}
is_all_rules = rule_name[0] == 0;
/* just one rule */
if (!is_all_rules) {
irl = i_find_rule_byname(rule_name);
if (irl == NULL) {
rc = ILB_STATUS_ENORULE;
ilbd_audit_rule_event(rule_name, NULL, cmd, rc, ucredp);
goto out;
}
/* auditing will be done by i_ilbd_action_switch() */
rc = i_ilbd_action_switch(irl, cmd, B_FALSE, ucredp);
goto out;
}
/* all rules: first tell the kernel, then walk the daemon's list */
kcmd.cmd = i_ilbd2ilb_cmd(cmd);
kcmd.flags = ILB_RULE_ALLRULES;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK) {
(void) snprintf(rulename, sizeof (rulename), "all");
ilbd_audit_rule_event(rulename, NULL, cmd, rc, ucredp);
goto out;
}
irl = list_head(&ilbd_rule_hlist);
while (irl != NULL) {
irl_next = list_next(&ilbd_rule_hlist, irl);
irl->irl_flags |= ILB_FLAGS_RULE_ALLRULES;
/* auditing will be done by i_ilbd_action_switch() */
rc = i_ilbd_action_switch(irl, cmd, B_FALSE, ucredp);
irl->irl_flags &= ~ILB_FLAGS_RULE_ALLRULES;
if (rc != ILB_STATUS_OK)
goto rollback_list;
irl = irl_next;
}
return (rc);
rollback_list:
u_cmd = get_undo_cmd(cmd);
if (u_cmd == ILBD_BAD_CMD)
return (rc);
if (is_all_rules) {
kcmd.cmd = i_ilbd2ilb_cmd(u_cmd);
(void) do_ioctl(&kcmd, 0);
}
/* current list element failed, so we start with previous one */
irl = list_prev(&ilbd_rule_hlist, irl);
while (irl != NULL) {
if (is_all_rules)
irl->irl_flags |= ILB_FLAGS_RULE_ALLRULES;
/*
* When the processing of a command consists of
* multiple sequential steps, and one of them fails,
* ilbd performs rollback to undo the steps taken before the
* failing step. Since ilbd is initiating these steps
* there is not need to audit them.
*/
rc = i_ilbd_action_switch(irl, u_cmd, B_TRUE, NULL);
irl->irl_flags &= ~ILB_FLAGS_RULE_ALLRULES;
irl = list_prev(&ilbd_rule_hlist, irl);
}
out:
return (rc);
}
ilb_status_t
ilbd_destroy_rule(ilbd_name_t rule_name, const struct passwd *ps,
ucred_t *ucredp)
{
return (i_ilbd_rule_action(rule_name, ps, ILBD_DESTROY_RULE, ucredp));
}
ilb_status_t
ilbd_enable_rule(ilbd_name_t rule_name, const struct passwd *ps,
ucred_t *ucredp)
{
return (i_ilbd_rule_action(rule_name, ps, ILBD_ENABLE_RULE, ucredp));
}
ilb_status_t
ilbd_disable_rule(ilbd_name_t rule_name, const struct passwd *ps,
ucred_t *ucredp)
{
return (i_ilbd_rule_action(rule_name, ps, ILBD_DISABLE_RULE, ucredp));
}
/*
* allocate storage for a kernel rule command and fill from
* "template" irl, if non-NULL
*/
static ilb_rule_cmd_t *
i_alloc_kernel_rule_cmd(ilbd_rule_t *irl)
{
ilb_rule_cmd_t *kcmd;
kcmd = (ilb_rule_cmd_t *)malloc(sizeof (*kcmd));
if (kcmd == NULL)
return (kcmd);
bzero(kcmd, sizeof (*kcmd));
if (irl != NULL) {
kcmd->flags = irl->irl_flags;
kcmd->ip_ver = AF_2_IPPROTO(irl->irl_ipversion);
kcmd->vip = irl->irl_vip;
kcmd->proto = irl->irl_proto;
kcmd->min_port = irl->irl_minport;
kcmd->max_port = irl->irl_maxport;
kcmd->algo = algo_lib2impl(irl->irl_algo);
kcmd->topo = topo_lib2impl(irl->irl_topo);
kcmd->sticky_mask = irl->irl_stickymask;
kcmd->nat_src_start = irl->irl_nat_src_start;
kcmd->nat_src_end = irl->irl_nat_src_end;
kcmd->conn_drain_timeout = irl->irl_conndrain;
kcmd->nat_expiry = irl->irl_nat_timeout;
kcmd->sticky_expiry = irl->irl_sticky_timeout;
(void) strlcpy(kcmd->name, irl->irl_name,
sizeof (kcmd->name));
}
return (kcmd);
}
/*
* ncount is the next to be used index into (*kcmdp)->servers
*/
static ilb_status_t
adjust_srv_info_cmd(ilb_servers_info_cmd_t **kcmdp, int index)
{
ilb_servers_info_cmd_t *kcmd = *kcmdp;
size_t sz;
if (kcmd != NULL && kcmd->num_servers > index + 1)
return (ILB_STATUS_OK);
/*
* the first ilb_server_info_t is part of *kcmd, so
* by using index (which is one less than the total needed) here,
* we allocate exactly the amount we need.
*/
sz = sizeof (*kcmd) + (index * sizeof (ilb_server_info_t));
kcmd = (ilb_servers_info_cmd_t *)realloc(kcmd, sz);
if (kcmd == NULL)
return (ILB_STATUS_ENOMEM);
/*
* we don't count the slot we newly allocated yet.
*/
kcmd->num_servers = index;
*kcmdp = kcmd;
return (ILB_STATUS_OK);
}
/*
* this function adds all servers in srvlist to the kernel(!) rule
* the name of which is passed as argument.
*/
static ilb_status_t
i_update_ksrv_rules(char *name, ilbd_sg_t *sg, ilbd_rule_t *rl)
{
ilb_status_t rc;
ilbd_srv_t *srvp;
ilb_servers_info_cmd_t *kcmd = NULL;
int i;
/*
* If the servergroup doesn't have any servers associated with
* it yet, there's nothing more to do here.
*/
if (sg->isg_srvcount == 0)
return (ILB_STATUS_OK);
/*
* walk the list of servers attached to this SG
*/
srvp = list_head(&sg->isg_srvlist);
for (i = 0; srvp != NULL; srvp = list_next(&sg->isg_srvlist, srvp)) {
rc = adjust_srv_info_cmd(&kcmd, i);
if (rc != ILB_STATUS_OK)
goto rollback_kcmd;
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++;
}
assert(kcmd != NULL);
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)
goto rollback_kcmd;
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);
goto rollback_kcmd;
}
}
rollback_kcmd:
free(kcmd);
return (rc);
}
/* 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);
}
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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) == 0) {
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 = NULL;
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);
if ((valstr = malloc(scf_val_len)) == NULL)
return (ILB_STATUS_ENOMEM);
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) {
free(valstr);
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, scf_val_len);
break;
}
case ILBD_RULE_PORT:
(void) snprintf(valstr, scf_val_len, "%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) {
free(valstr);
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) {
free(valstr);
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) {
free(valstr);
scf_value_destroy(v);
return (ret);
}
break;
case ILBD_RULE_SGNAME:
(void) strlcpy(valstr, r_ent->irl_sgname, scf_val_len);
break;
case ILBD_RULE_HCNAME:
if (r_ent->irl_hcname[0] != '\0')
(void) strlcpy(valstr, r_ent->irl_hcname,
scf_val_len);
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",
scf_val_len);
break;
}
varray = calloc(sizeof (*varray), s_ent->isg_srvcount);
if (varray == NULL) {
scf_value_destroy(v);
free(valstr);
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(valstr);
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(valstr);
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;
free(valstr);
return (ret);
case ILBD_HC_TEST:
(void) strlcpy(valstr, h_ent->ihc_test, scf_val_len);
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;
}
free(valstr);
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 *scfpgbuf; /* property group name or group type */
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);
}
if ((scfpgbuf = malloc(scf_name_len)) == NULL)
return (ILB_STATUS_ENOMEM);
ilbd_name_to_scfpgname(pg_type, pgname, scfpgbuf);
ret = ilbd_scf_retrieve_pg(scfpgbuf, &pg, B_TRUE);
if (ret != ILB_STATUS_OK) {
free(scfpgbuf);
return (ret);
}
h = scf_pg_handle(pg);
/* fill in props */
for (i = i_st; i < i_end; i++) {
int num, j;
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,
scfpgbuf);
if (ret == ILB_STATUS_ENOENT) {
(void) strlcpy(scfpgbuf,
"EMPTY_SERVER", scf_name_len);
}
ret = ilbd_scf_set_prop(pg, scfpgbuf,
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:
free(scfpgbuf);
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;
if ((scfname = malloc(scf_name_len)) == NULL)
return (ILB_STATUS_ENOMEM);
ilbd_name_to_scfpgname(pg_t, pgname, scfname);
ret = ilbd_scf_retrieve_pg(scfname, &pg, B_FALSE);
free(scfname);
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];
char *valstr;
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 */
if ((valstr = malloc(ILBD_MAX_VALUE_LEN)) == NULL)
return (ILB_STATUS_ENOMEM);
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) {
free(valstr);
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) {
free(valstr);
return (ILB_STATUS_INTERNAL);
}
ipversion = AF_INET6;
break;
case SCF_TYPE_BOOLEAN:
if (scf_value_get_boolean(val, &valbool) < 0) {
free(valstr);
return (ILB_STATUS_INTERNAL);
}
break;
case SCF_TYPE_ASTRING:
if (scf_value_get_astring(val, valstr,
ILBD_MAX_VALUE_LEN) < 0) {
free(valstr);
return (ILB_STATUS_INTERNAL);
}
break;
case SCF_TYPE_INTEGER:
if (scf_value_get_integer(val, &valint) < 0) {
free(valstr);
return (ILB_STATUS_INTERNAL);
}
break;
default:
free(valstr);
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;
}
free(valstr);
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);
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 = NULL;
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;
if ((prop_name = malloc(scf_name_len)) == NULL) {
ret = ILB_STATUS_ENOMEM;
goto out;
}
while ((scf_ret = scf_iter_next_property(propiter, prop)) == 1) {
if (scf_property_get_name(prop, prop_name, scf_name_len)
< 0) {
ret = ilbd_scf_err_to_ilb_err();
goto out;
}
ret = cb(pg, prop_name, arg);
if (ret != ILB_STATUS_OK)
break;
}
out:
if (prop_name != NULL)
free(prop_name);
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);
int scf_name_len = ILBD_MAX_NAME_LEN;
char *pg_name = NULL;
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;
if ((pg_name = malloc(scf_name_len)) == NULL) {
ret = ILB_STATUS_ENOMEM;
goto out;
}
while ((scf_ret = scf_iter_next_pg(pgiter, newpg)) > 0) {
ilbd_data_t data;
if (scf_pg_get_name(newpg, pg_name, scf_name_len) < 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) {
ret = ILB_STATUS_ENOMEM;
goto out;
}
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) {
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.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 (pg_name != NULL)
free(pg_name);
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;
char *scf_pgname = NULL;
scf_type_t scftype;
scf_value_t *scfval;
scf_handle_t *h;
if ((scf_pgname = malloc(ILBD_MAX_NAME_LEN)) == NULL)
return (ILB_STATUS_ENOMEM);
ilbd_name_to_scfpgname(pg_type, pg_name, scf_pgname);
ret = ilbd_scf_retrieve_pg(scf_pgname, &scfpg, B_FALSE);
free(scf_pgname);
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 = NULL;
if ((buf = malloc(scf_name_len)) == NULL)
return (ILB_STATUS_ENOMEM);
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);
free(buf);
return (ILB_STATUS_INTERNAL);
}
if ((h = scf_pg_handle(pg)) == NULL) {
ilbd_scf_destroy(NULL, NULL, NULL, pg);
free(buf);
return (ilbd_scf_err_to_ilb_err());
}
if ((val = scf_value_create(h)) == NULL) {
ilbd_scf_destroy(h, NULL, NULL, pg);
free(buf);
return (ILB_STATUS_ENOMEM);
}
ilbd_srv_scf_val(srv, buf);
(void) scf_value_set_astring(val, buf);
(void) snprintf(buf, scf_name_len, "server%d", srv->isv_id);
ret = ilbd_scf_set_prop(pg, buf, SCF_TYPE_ASTRING, val);
free(buf);
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_transaction_t *tx = NULL;
scf_transaction_entry_t *entry = NULL;
if ((buf = malloc(scf_name_len)) == NULL)
return (ILB_STATUS_ENOMEM);
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) {
free(buf);
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);
free(buf);
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, scf_name_len, "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:
free(buf);
if (entry != NULL)
scf_entry_destroy(entry);
if (tx != NULL)
scf_transaction_destroy(tx);
ilbd_scf_destroy(h, NULL, NULL, pg);
return (ret);
}
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016 by Delphix. All rights reserved.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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_type =
data->ed_ipaddr_type;
(void) memcpy(event->adt_ilb_enable_server.server_ipaddress,
data->ed_server_address,
(sizeof (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_type =
data->ed_ipaddr_type;
(void) memcpy(event->adt_ilb_disable_server.server_ipaddress,
data->ed_server_address,
(sizeof (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_type =
data->ed_ipaddr_type;
(void) memcpy(event->adt_ilb_remove_server.server_ipaddress,
data->ed_server_address,
(sizeof (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_type =
data->ed_ipaddr_type;
(void) memcpy(event->adt_ilb_add_server.server_ipaddress,
data->ed_server_address,
(sizeof (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) {
audit_sg_data->ed_serverid = NULL;
if (IN6_IS_ADDR_V4MAPPED(&tsrv->sgs_addr)) {
audit_sg_data->ed_ipaddr_type = ADT_IPv4;
cvt_addr(audit_sg_data->ed_server_address,
ADT_IPv4, tsrv->sgs_addr);
} else {
audit_sg_data->ed_ipaddr_type = ADT_IPv6;
cvt_addr(audit_sg_data->ed_server_address,
ADT_IPv6, tsrv->sgs_addr);
}
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_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;
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));
if (IN6_IS_ADDR_V4MAPPED(&srv->sgs_addr)) {
audit_sg_data.ed_ipaddr_type = ADT_IPv4;
cvt_addr(audit_sg_data.ed_server_address, ADT_IPv4,
srv->sgs_addr);
} else {
audit_sg_data.ed_ipaddr_type = ADT_IPv6;
cvt_addr(audit_sg_data.ed_server_address, ADT_IPv6,
srv->sgs_addr);
}
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 they encounter
* 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;
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;
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;
if (IN6_IS_ADDR_V4MAPPED(&tsrv->sgs_addr)) {
audit_sg_data.ed_ipaddr_type = ADT_IPv4;
cvt_addr(audit_sg_data.ed_server_address, ADT_IPv4,
tsrv->sgs_addr);
} else {
audit_sg_data.ed_ipaddr_type = ADT_IPv6;
cvt_addr(audit_sg_data.ed_server_address, ADT_IPv6,
tsrv->sgs_addr);
}
/*
* 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 = NULL;
ilb_ip_addr_t ipaddr;
void *addrptr;
char ipstr[INET6_ADDRSTRLEN], *valstr = NULL;
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;
(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;
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 "_""."
* 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 */
if (IN6_IS_ADDR_V4MAPPED(&tmp_srv->isv_addr)) {
audit_sg_data.ed_ipaddr_type = ADT_IPv4;
cvt_addr(audit_sg_data.ed_server_address, ADT_IPv4,
tmp_srv->isv_addr);
} else {
audit_sg_data.ed_ipaddr_type = ADT_IPv6;
cvt_addr(audit_sg_data.ed_server_address, ADT_IPv6,
tmp_srv->isv_addr);
}
/*
* 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;
}
if ((prop_name = malloc(scf_name_len)) == NULL)
return (ILB_STATUS_ENOMEM);
if ((valstr = malloc(scf_val_len)) == NULL) {
free(prop_name);
return (ILB_STATUS_ENOMEM);
}
(void) snprintf(valstr, scf_val_len, "%s;%d;%d-%d;%d",
ipstr, ipver,
ntohs(tmp_srv->isv_minport),
ntohs(tmp_srv->isv_maxport), nflags);
(void) snprintf(prop_name, scf_name_len, "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;
}
free(prop_name);
free(valstr);
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);
}
/*
* 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);
}
/*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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);
}
#
# 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.
#
# Copyright 2021 Tintri by DDN, Inc. All rights reserved.
#
PROG = in.mpathd
ROOTFS_PROG = $(PROG)
OBJS = mpd_tables.o mpd_main.o mpd_probe.o
DEFAULTFILES = mpathd.dfl
SVCMETHOD = net-ipmp
MANIFEST = network-ipmp.xml
include ../../../Makefile.cmd
ROOTCMDDIR = $(ROOT)/lib/inet
ROOTMANIFESTDIR = $(ROOTSVCNETWORK)
POFILE = $(PROG).po
POFILES = $(OBJS:%.o=%.po)
CSTD = $(CSTD_GNU99)
#
# We need access to the ancillary data features which are only available
# via the SUS standards. Further, C99 support requires SUSv3 or higher.
#
CPPFLAGS += -D_XOPEN_SOURCE=600 -D__EXTENSIONS__
LDLIBS += -lsocket -lnsl -lsysevent -lnvpair -lipmp -linetutil -ldlpi
CERRWARN += $(CNOWARN_UNINIT)
#
# Instrument in.mpathd with CTF data to ease debugging.
#
CTFCONVERT_HOOK = && $(CTFCONVERT_O)
CTFMERGE_HOOK = && $(CTFMERGE) -L VERSION -o $@ $(OBJS)
$(OBJS) : CFLAGS += $(CTF_FLAGS)
.KEEP_STATE:
all: $(PROG)
$(PROG): $(OBJS)
$(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(CTFMERGE_HOOK)
$(POST_PROCESS)
check: $(CHKMANIFEST)
install: all $(ROOTCMD) $(ROOTMANIFEST) $(ROOTSVCMETHOD) \
$(ROOTETCDEFAULTFILES)
clean:
$(RM) $(OBJS)
$(POFILE): $(POFILES)
$(RM) $@
$(CAT) $(POFILES) > $@
include ../../../Makefile.targ
* Copyright (c) 1987 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
PORTIONS OF MPATHD COMMAND FUNCTIONALITY
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License, Version 1.0 only
# (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 2000 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# ident "%Z%%M% %I% %E% SMI"
#
# Time taken by mpathd to detect a NIC failure in ms. The minimum time
# that can be specified is 100 ms.
#
FAILURE_DETECTION_TIME=10000
#
# Failback is enabled by default. To disable failback turn off this option
#
FAILBACK=yes
#
# By default only interfaces configured as part of multipathing groups
# are tracked. Turn off this option to track all network interfaces
# on the system
#
TRACK_INTERFACES_ONLY_WITH_GROUPS=yes
/*
* 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 _MPD_DEFS_H
#define _MPD_DEFS_H
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include